blob: 0470a0e0237aa129676eef8cec62891800a2832e [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 "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<uint16_t>(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<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);
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 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