/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include "chre/util/nanoapp/ble.h" #include "chre/util/nanoapp/log.h" #include "chre/util/time.h" #include "chre_api/chre.h" /** * @file * * This nanoapp is designed to continually start and stop BLE scans and verify * that the expected data is delivered. BLE_WORLD_ENABLE_BATCHING can be defined * to test batching and flushing if the nanoapp has the * CHRE_BLE_CAPABILITIES_SCAN_RESULT_BATCHING capability. This will configure * the BLE scans with a batch window and periodically make flush requests to get * batched BLE scan result events. */ #ifdef CHRE_NANOAPP_INTERNAL namespace chre { namespace { #endif // CHRE_NANOAPP_INTERNAL using chre::ble_constants::kNumScanFilters; constexpr int8_t kDataTypeServiceData = 0x16; #ifdef BLE_WORLD_ENABLE_BATCHING //! A timer handle to request the BLE flush. uint32_t gFlushTimerHandle = 0; //! The period to which to make the BLE flush request. uint64_t gFlushPeriodNs = 7 * chre::kOneSecondInNanoseconds; #endif // BLE_WORLD_ENABLE_BATCHING //! Report delay for BLE scans. uint32_t gBleBatchDurationMs = 0; //! A timer handle to toggle enable/disable BLE scans. uint32_t gEnableDisableTimerHandle = 0; //! The period at which to enable/disable BLE scans. uint64_t gEnableDisablePeriodNs = 10 * chre::kOneSecondInNanoseconds; //! True if BLE scans are currently enabled bool gBleEnabled = false; //! A timer handle to poll for RSSI. uint32_t gReadRssiTimerHandle = CHRE_TIMER_INVALID; //! A hardcoded connection handle on which the RSSI will be read //! On the Broadcom controllers used by Pixel, if a connection is made //! immediately after startup, it will be on this handle. uint16_t gReadRssiConnectionHandle = 0x40; //! The period at which to read RSSI of kConnectionHandle. uint64_t gReadRssiPeriodNs = 3 * chre::kOneSecondInNanoseconds; bool enableBleScans() { struct chreBleScanFilter filter; chreBleGenericFilter genericFilters[kNumScanFilters]; chre::createBleScanFilterForKnownBeacons(filter, genericFilters, kNumScanFilters); return chreBleStartScanAsync(CHRE_BLE_SCAN_MODE_BACKGROUND, gBleBatchDurationMs, &filter); } bool disableBleScans() { return chreBleStopScanAsync(); } bool nanoappStart() { LOGI("BLE world from version 0x%08" PRIx32, chreGetVersion()); uint32_t capabilities = chreBleGetCapabilities(); LOGI("Got BLE capabilities 0x%" PRIx32, capabilities); #ifdef BLE_WORLD_ENABLE_BATCHING bool batchingAvailable = ((capabilities & CHRE_BLE_CAPABILITIES_SCAN_RESULT_BATCHING) != 0); if (!batchingAvailable) { LOGE("BLE scan result batching is unavailable"); } else { gBleBatchDurationMs = 5000; LOGI("BLE batching enabled"); } #endif // BLE_WORLD_ENABLE_BATCHING bool success = enableBleScans(); if (!success) { LOGE("Failed to send BLE start scan request"); } else { gEnableDisableTimerHandle = chreTimerSet(gEnableDisablePeriodNs, &gEnableDisableTimerHandle, false /* oneShot */); if (gEnableDisableTimerHandle == CHRE_TIMER_INVALID) { LOGE("Could not set enable/disable timer"); } #ifdef BLE_WORLD_ENABLE_BATCHING if (batchingAvailable) { gFlushTimerHandle = chreTimerSet(gFlushPeriodNs, &gFlushTimerHandle, false /* oneShot */); if (gFlushTimerHandle == CHRE_TIMER_INVALID) { LOGE("Could not set flush timer"); } } #endif // BLE_WORLD_ENABLE_BATCHING } if (capabilities & CHRE_BLE_CAPABILITIES_READ_RSSI) { gReadRssiPeriodNs = chreTimerSet(gReadRssiPeriodNs, &gReadRssiTimerHandle, false /* oneShot */); if (gReadRssiTimerHandle == CHRE_TIMER_INVALID) { LOGE("Could not set RSSI timer"); } } else { LOGW( "Skipping RSSI read since CHRE_BLE_CAPABILITIES_READ_RSSI not " "supported"); } return true; } void parseAdData(const uint8_t *data, uint16_t size) { for (uint16_t i = 0; i < size;) { // First byte has the dvertisement data length. uint16_t ad_data_length = data[i]; // Early termination with zero length advertisement. if (ad_data_length == 0) break; // Second byte has advertisement data type. // Only retrieves service data for Nearby. if (data[++i] == kDataTypeServiceData) { // First two bytes of the service data are service data UUID in little // endian. uint16_t uuid = static_cast(data[i + 1] + (data[i + 2] << 8)); LOGD("Service Data UUID: %" PRIx16, uuid); } // Moves to next advertisement. i += ad_data_length; } } void handleAsyncResultEvent(const chreAsyncResult *result) { const char *requestType = result->requestType == CHRE_BLE_REQUEST_TYPE_START_SCAN ? "start" : "stop"; if (result->success) { LOGI("BLE %s scan success", requestType); gBleEnabled = (result->requestType == CHRE_BLE_REQUEST_TYPE_START_SCAN); } else { LOGE("BLE %s scan failure: %" PRIu8, requestType, result->errorCode); } } void handleAdvertismentEvent(const chreBleAdvertisementEvent *event) { for (uint8_t i = 0; i < event->numReports; i++) { LOGD("BLE Report %" PRIu32, static_cast(i + 1)); LOGD("Event type and data status: 0x%" PRIx8, event->reports[i].eventTypeAndDataStatus); LOGD("Timestamp: %" PRIu64 " ms", event->reports[i].timestamp / chre::kOneMillisecondInNanoseconds); parseAdData(event->reports[i].data, event->reports[i].dataLength); } } void handleTimerEvent(const void *cookie) { if (cookie == &gEnableDisableTimerHandle) { bool success = false; if (!gBleEnabled) { success = enableBleScans(); } else { success = disableBleScans(); } if (!success) { LOGE("Failed to send BLE %s scan request", !gBleEnabled ? "start" : "stop"); } #ifdef BLE_WORLD_ENABLE_BATCHING } else if (cookie == &gFlushTimerHandle) { if (gBleEnabled) { if (!chreBleFlushAsync(nullptr /* cookie */)) { LOGE("Could not send flush request"); } else { LOGI("Successfully sent flush request at time %" PRIu64 " ms", chreGetTime() / chre::kOneMillisecondInNanoseconds); } } #endif // BLE_WORLD_ENABLE_BATCHING } else if (cookie == &gReadRssiTimerHandle) { bool success = chreBleReadRssiAsync(gReadRssiConnectionHandle, nullptr); LOGI("Reading RSSI for handle 0x%" PRIx16 ", status=%d", gReadRssiConnectionHandle, success); } else { LOGE("Received unknown timer cookie %p", cookie); } } void handleRssiEvent(const chreBleReadRssiEvent *event) { LOGI("Received RSSI Read with status 0x%" PRIx8 " and rssi %" PRIi8, event->result.errorCode, event->rssi); } void handleBatchCompleteEvent(const chreBatchCompleteEvent *event) { LOGI("Received Batch complete event with event type %" PRIu16, event->eventType); } void nanoappHandleEvent(uint32_t senderInstanceId, uint16_t eventType, const void *eventData) { LOGI("Received event 0x%" PRIx16 " from 0x%" PRIx32 " at time %" PRIu64 " ms", eventType, senderInstanceId, chreGetTime() / chre::kOneMillisecondInNanoseconds); switch (eventType) { case CHRE_EVENT_BLE_ADVERTISEMENT: handleAdvertismentEvent( static_cast(eventData)); break; case CHRE_EVENT_BLE_ASYNC_RESULT: handleAsyncResultEvent(static_cast(eventData)); break; case CHRE_EVENT_TIMER: handleTimerEvent(eventData); break; case CHRE_EVENT_BLE_FLUSH_COMPLETE: LOGI("Received flush complete"); break; case CHRE_EVENT_BLE_RSSI_READ: handleRssiEvent(static_cast(eventData)); break; case CHRE_EVENT_BLE_BATCH_COMPLETE: handleBatchCompleteEvent( static_cast(eventData)); default: LOGW("Unhandled event type %" PRIu16, eventType); break; } } void nanoappEnd() { if (gBleEnabled && !chreBleStopScanAsync()) { LOGE("Error sending BLE stop scan request sent to PAL"); } if (!chreTimerCancel(gEnableDisableTimerHandle)) { LOGE("Error canceling BLE scan timer"); } #ifdef BLE_WORLD_ENABLE_BATCHING if (!chreTimerCancel(gFlushTimerHandle)) { LOGE("Error canceling BLE flush timer"); } #endif if (!chreTimerCancel(gReadRssiTimerHandle)) { LOGE("Error canceling RSSI read timer"); } LOGI("nanoapp stopped"); } #ifdef CHRE_NANOAPP_INTERNAL } // anonymous namespace } // namespace chre #include "chre/platform/static_nanoapp_init.h" #include "chre/util/nanoapp/app_id.h" #include "chre/util/system/napp_permissions.h" CHRE_STATIC_NANOAPP_INIT(BleWorld, kBleWorldAppId, 0, NanoappPermissions::CHRE_PERMS_BLE); #endif // CHRE_NANOAPP_INTERNAL