blob: 19b1df5190accfe49349e2f713854ae7b2bc5f78 [file] [log] [blame]
/*
* 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 <inttypes.h>
#include <cstdint>
#include "chre/util/nanoapp/ble.h"
#include "chre/util/nanoapp/log.h"
#include "chre/util/time.h"
#include "chre_api/chre.h"
#define BLE_FILTER_TYPE_SERVICE_DATA 0
#define BLE_FILTER_TYPE_MANUFACTURER_DATA 1
#define BLE_FILTER_TYPE_BROADCASTER_ADDRESS 2
/**
* @file
*
* This nanoapp is designed to verify the functionality of CHRE's BLE APIs.
* After confirming whether the platform has the expected capabilities, It tests
* scan functionality by continually starting and stopping scan requests and
* decoding scan results to be verified manually via the logs. It tests read
* RSSI functionality by continually requesting to read RSSI from a hard coded
* connection handle.
*
* The BLE scanning test can be configured to test batching and flushing by
* defining BLE_WORLD_ENABLE_BATCHING. If the platform supports the
* CHRE_BLE_CAPABILITIES_SCAN_RESULT_BATCHING capability, this flag will modify
* the BLE scan request to use a batch window and periodically make flush
* requests to get batched BLE scan result events.
*
* The BLE scanning test can also be configured by filter type. By default, the
* test will filter by service data, but it can be modified to filter by
* manufacturer data or broadcaster address by setting the BLE_FILTER_TYPE flag
* to either BLE_FILTER_TYPE_MANUFACTURER_DATA or
* BLE_FILTER_TYPE_BROADCASTER_ADDRESS. It is recommended to use an app that can
* create advertisers corresponding to the filters to do the tests.
*/
#ifdef CHRE_NANOAPP_INTERNAL
namespace chre {
namespace {
#endif // CHRE_NANOAPP_INTERNAL
using chre::ble_constants::kNumBroadcasterFilters;
using chre::ble_constants::kNumManufacturerDataFilters;
using chre::ble_constants::kNumScanFilters;
constexpr uint8_t kDataTypeServiceData = 0x16;
constexpr uint8_t kDataTypeManufacturerData = 0xFF;
constexpr uint8_t kUuidLengthInBytes = 2;
constexpr uint32_t kScanCookie = 10;
#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 isScanningSupported(uint32_t capabilities, uint32_t filterCapabilities) {
if ((capabilities & CHRE_BLE_CAPABILITIES_SCAN) == 0) {
LOGE("BLE scan is not supported");
return false;
}
#if BLE_FILTER_TYPE == BLE_FILTER_TYPE_MANUFACTURER_DATA
if ((filterCapabilities & CHRE_BLE_FILTER_CAPABILITIES_MANUFACTURER_DATA) ==
0) {
LOGE("BLE manufacturer data filters are not supported");
return false;
}
#elif BLE_FILTER_TYPE == BLE_FILTER_TYPE_BROADCASTER_ADDRESS
if ((filterCapabilities & CHRE_BLE_FILTER_CAPABILITIES_BROADCASTER_ADDRESS) ==
0) {
LOGE("BLE broadcaster address filters are not supported");
return false;
}
#else
if ((filterCapabilities & CHRE_BLE_FILTER_CAPABILITIES_SERVICE_DATA) == 0) {
LOGE("BLE service data filters are not supported");
return false;
}
#endif
return true;
}
bool enableBleScans() {
struct chreBleScanFilterV1_9 filter;
#if BLE_FILTER_TYPE == BLE_FILTER_TYPE_MANUFACTURER_DATA
chreBleGenericFilter genericFilters[kNumManufacturerDataFilters];
chre::createBleManufacturerDataFilter(kNumManufacturerDataFilters,
genericFilters, filter);
#elif BLE_FILTER_TYPE == BLE_FILTER_TYPE_BROADCASTER_ADDRESS
chreBleBroadcasterAddressFilter broadcasterFilters[kNumBroadcasterFilters];
if (!chre::createBleScanFilterForAdvertiser(filter, broadcasterFilters,
kNumBroadcasterFilters)) {
LOGE("Failed to create BLE scan filters for known beacons and advertiser");
}
#else
chreBleGenericFilter genericFilters[kNumScanFilters];
if (!chre::createBleScanFilterForKnownBeaconsV1_9(filter, genericFilters,
kNumScanFilters)) {
LOGE("Failed to create BLE scan filters for known beacons");
}
#endif
return chreBleStartScanAsyncV1_9(CHRE_BLE_SCAN_MODE_BACKGROUND,
gBleBatchDurationMs, &filter, &kScanCookie);
}
bool disableBleScans() {
return chreBleStopScanAsync();
}
bool nanoappStart() {
LOGI("BLE world from version 0x%08" PRIx32, chreGetVersion());
uint32_t capabilities = chreBleGetCapabilities();
uint32_t filterCapabilities = chreBleGetFilterCapabilities();
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
if (!isScanningSupported(capabilities, filterCapabilities)) {
LOGE("BLE scanning is not supported");
} else if (!enableBleScans()) {
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;
}
uint16_t getUuidInLittleEndian(const uint8_t data[kUuidLengthInBytes]) {
return static_cast<uint16_t>(data[0] + (data[1] << 8));
}
void parseReport(const chreBleAdvertisingReport *report) {
for (uint16_t i = 0; i < report->dataLength;) {
// First byte has the advertisement data length.
uint16_t adDataLength = report->data[i];
// Early termination with zero length advertisement.
if (adDataLength == 0) break;
// Log 2 byte UUIDs for service data or manufacturer data AD types.
if (adDataLength < kUuidLengthInBytes) {
i += adDataLength + 1;
continue;
}
uint8_t adDataType = report->data[++i];
switch (adDataType) {
case kDataTypeServiceData:
LOGD("Service Data UUID: %" PRIx16,
getUuidInLittleEndian(&report->data[i + 1]));
break;
case kDataTypeManufacturerData:
LOGD("Manufacturer Data UUID: %" PRIx16,
getUuidInLittleEndian(&report->data[i + 1]));
break;
default:
break;
}
// Moves to next advertisement.
i += adDataLength;
}
LOGD("application address type 0x%" PRIx8, report->addressType);
LOGD("address=%02X:%02X:%02X:%02X:%02X:%02X", report->address[0],
report->address[1], report->address[2], report->address[3],
report->address[4], report->address[5]);
LOGD("direct address=%02X:%02X:%02X:%02X:%02X:%02X", report->directAddress[0],
report->directAddress[1], report->directAddress[2],
report->directAddress[3], report->directAddress[4],
report->directAddress[5]);
}
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<uint32_t>(i + 1));
LOGD("Event type and data status: 0x%" PRIx8,
event->reports[i].eventTypeAndDataStatus);
LOGD("Timestamp: %" PRIu64 " ms",
event->reports[i].timestamp / chre::kOneMillisecondInNanoseconds);
parseReport(&event->reports[i]);
}
}
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 handleFlushCompleteEvent(const chreAsyncResult *event) {
LOGI("Received flush complete event with status 0x%" PRIx8, event->errorCode);
}
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<const chreBleAdvertisementEvent *>(eventData));
break;
case CHRE_EVENT_BLE_ASYNC_RESULT:
handleAsyncResultEvent(static_cast<const chreAsyncResult *>(eventData));
break;
case CHRE_EVENT_TIMER:
handleTimerEvent(eventData);
break;
case CHRE_EVENT_BLE_FLUSH_COMPLETE:
handleFlushCompleteEvent(static_cast<const chreAsyncResult *>(eventData));
break;
case CHRE_EVENT_BLE_RSSI_READ:
handleRssiEvent(static_cast<const chreBleReadRssiEvent *>(eventData));
break;
case CHRE_EVENT_BLE_BATCH_COMPLETE:
handleBatchCompleteEvent(
static_cast<const chreBatchCompleteEvent *>(eventData));
break;
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