1 /*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include <inttypes.h>
17
18 #include "chre/util/nanoapp/ble.h"
19 #include "chre/util/nanoapp/log.h"
20 #include "chre/util/time.h"
21 #include "chre_api/chre.h"
22
23 /**
24 * @file
25 *
26 * This nanoapp is designed to continually start and stop BLE scans and verify
27 * that the expected data is delivered. BLE_WORLD_ENABLE_BATCHING can be defined
28 * to test batching and flushing if the nanoapp has the
29 * CHRE_BLE_CAPABILITIES_SCAN_RESULT_BATCHING capability. This will configure
30 * the BLE scans with a batch window and periodically make flush requests to get
31 * batched BLE scan result events.
32 */
33
34 #ifdef CHRE_NANOAPP_INTERNAL
35 namespace chre {
36 namespace {
37 #endif // CHRE_NANOAPP_INTERNAL
38
39 using chre::ble_constants::kNumScanFilters;
40
41 constexpr int8_t kDataTypeServiceData = 0x16;
42
43 #ifdef BLE_WORLD_ENABLE_BATCHING
44 //! A timer handle to request the BLE flush.
45 uint32_t gFlushTimerHandle = 0;
46 //! The period to which to make the BLE flush request.
47 uint64_t gFlushPeriodNs = 7 * chre::kOneSecondInNanoseconds;
48 #endif // BLE_WORLD_ENABLE_BATCHING
49
50 //! Report delay for BLE scans.
51 uint32_t gBleBatchDurationMs = 0;
52 //! A timer handle to toggle enable/disable BLE scans.
53 uint32_t gEnableDisableTimerHandle = 0;
54 //! The period at which to enable/disable BLE scans.
55 uint64_t gEnableDisablePeriodNs = 10 * chre::kOneSecondInNanoseconds;
56 //! True if BLE scans are currently enabled
57 bool gBleEnabled = false;
58
59 //! A timer handle to poll for RSSI.
60 uint32_t gReadRssiTimerHandle = CHRE_TIMER_INVALID;
61 //! A hardcoded connection handle on which the RSSI will be read
62 //! On the Broadcom controllers used by Pixel, if a connection is made
63 //! immediately after startup, it will be on this handle.
64 uint16_t gReadRssiConnectionHandle = 0x40;
65 //! The period at which to read RSSI of kConnectionHandle.
66 uint64_t gReadRssiPeriodNs = 3 * chre::kOneSecondInNanoseconds;
67
enableBleScans()68 bool enableBleScans() {
69 struct chreBleScanFilter filter;
70 chreBleGenericFilter genericFilters[kNumScanFilters];
71 chre::createBleScanFilterForKnownBeacons(filter, genericFilters,
72 kNumScanFilters);
73 return chreBleStartScanAsync(CHRE_BLE_SCAN_MODE_BACKGROUND,
74 gBleBatchDurationMs, &filter);
75 }
76
disableBleScans()77 bool disableBleScans() {
78 return chreBleStopScanAsync();
79 }
80
nanoappStart()81 bool nanoappStart() {
82 LOGI("BLE world from version 0x%08" PRIx32, chreGetVersion());
83 uint32_t capabilities = chreBleGetCapabilities();
84 LOGI("Got BLE capabilities 0x%" PRIx32, capabilities);
85 #ifdef BLE_WORLD_ENABLE_BATCHING
86 bool batchingAvailable =
87 ((capabilities & CHRE_BLE_CAPABILITIES_SCAN_RESULT_BATCHING) != 0);
88 if (!batchingAvailable) {
89 LOGE("BLE scan result batching is unavailable");
90 } else {
91 gBleBatchDurationMs = 5000;
92 LOGI("BLE batching enabled");
93 }
94 #endif // BLE_WORLD_ENABLE_BATCHING
95 bool success = enableBleScans();
96 if (!success) {
97 LOGE("Failed to send BLE start scan request");
98 } else {
99 gEnableDisableTimerHandle =
100 chreTimerSet(gEnableDisablePeriodNs, &gEnableDisableTimerHandle,
101 false /* oneShot */);
102 if (gEnableDisableTimerHandle == CHRE_TIMER_INVALID) {
103 LOGE("Could not set enable/disable timer");
104 }
105
106 #ifdef BLE_WORLD_ENABLE_BATCHING
107 if (batchingAvailable) {
108 gFlushTimerHandle =
109 chreTimerSet(gFlushPeriodNs, &gFlushTimerHandle, false /* oneShot */);
110 if (gFlushTimerHandle == CHRE_TIMER_INVALID) {
111 LOGE("Could not set flush timer");
112 }
113 }
114 #endif // BLE_WORLD_ENABLE_BATCHING
115 }
116
117 if (capabilities & CHRE_BLE_CAPABILITIES_READ_RSSI) {
118 gReadRssiPeriodNs = chreTimerSet(gReadRssiPeriodNs, &gReadRssiTimerHandle,
119 false /* oneShot */);
120 if (gReadRssiTimerHandle == CHRE_TIMER_INVALID) {
121 LOGE("Could not set RSSI timer");
122 }
123 } else {
124 LOGW(
125 "Skipping RSSI read since CHRE_BLE_CAPABILITIES_READ_RSSI not "
126 "supported");
127 }
128
129 return true;
130 }
131
parseAdData(const uint8_t * data,uint16_t size)132 void parseAdData(const uint8_t *data, uint16_t size) {
133 for (uint16_t i = 0; i < size;) {
134 // First byte has the dvertisement data length.
135 uint16_t ad_data_length = data[i];
136 // Early termination with zero length advertisement.
137 if (ad_data_length == 0) break;
138 // Second byte has advertisement data type.
139 // Only retrieves service data for Nearby.
140 if (data[++i] == kDataTypeServiceData) {
141 // First two bytes of the service data are service data UUID in little
142 // endian.
143 uint16_t uuid = static_cast<uint16_t>(data[i + 1] + (data[i + 2] << 8));
144 LOGD("Service Data UUID: %" PRIx16, uuid);
145 }
146 // Moves to next advertisement.
147 i += ad_data_length;
148 }
149 }
150
handleAsyncResultEvent(const chreAsyncResult * result)151 void handleAsyncResultEvent(const chreAsyncResult *result) {
152 const char *requestType =
153 result->requestType == CHRE_BLE_REQUEST_TYPE_START_SCAN ? "start"
154 : "stop";
155 if (result->success) {
156 LOGI("BLE %s scan success", requestType);
157 gBleEnabled = (result->requestType == CHRE_BLE_REQUEST_TYPE_START_SCAN);
158 } else {
159 LOGE("BLE %s scan failure: %" PRIu8, requestType, result->errorCode);
160 }
161 }
162
handleAdvertismentEvent(const chreBleAdvertisementEvent * event)163 void handleAdvertismentEvent(const chreBleAdvertisementEvent *event) {
164 for (uint8_t i = 0; i < event->numReports; i++) {
165 LOGD("BLE Report %" PRIu32, static_cast<uint32_t>(i + 1));
166 LOGD("Event type and data status: 0x%" PRIx8,
167 event->reports[i].eventTypeAndDataStatus);
168 LOGD("Timestamp: %" PRIu64 " ms",
169 event->reports[i].timestamp / chre::kOneMillisecondInNanoseconds);
170 parseAdData(event->reports[i].data, event->reports[i].dataLength);
171 }
172 }
173
handleTimerEvent(const void * cookie)174 void handleTimerEvent(const void *cookie) {
175 if (cookie == &gEnableDisableTimerHandle) {
176 bool success = false;
177 if (!gBleEnabled) {
178 success = enableBleScans();
179 } else {
180 success = disableBleScans();
181 }
182 if (!success) {
183 LOGE("Failed to send BLE %s scan request",
184 !gBleEnabled ? "start" : "stop");
185 }
186 #ifdef BLE_WORLD_ENABLE_BATCHING
187 } else if (cookie == &gFlushTimerHandle) {
188 if (gBleEnabled) {
189 if (!chreBleFlushAsync(nullptr /* cookie */)) {
190 LOGE("Could not send flush request");
191 } else {
192 LOGI("Successfully sent flush request at time %" PRIu64 " ms",
193 chreGetTime() / chre::kOneMillisecondInNanoseconds);
194 }
195 }
196 #endif // BLE_WORLD_ENABLE_BATCHING
197 } else if (cookie == &gReadRssiTimerHandle) {
198 bool success = chreBleReadRssiAsync(gReadRssiConnectionHandle, nullptr);
199 LOGI("Reading RSSI for handle 0x%" PRIx16 ", status=%d",
200 gReadRssiConnectionHandle, success);
201 } else {
202 LOGE("Received unknown timer cookie %p", cookie);
203 }
204 }
205
handleRssiEvent(const chreBleReadRssiEvent * event)206 void handleRssiEvent(const chreBleReadRssiEvent *event) {
207 LOGI("Received RSSI Read with status 0x%" PRIx8 " and rssi %" PRIi8,
208 event->result.errorCode, event->rssi);
209 }
210
handleBatchCompleteEvent(const chreBatchCompleteEvent * event)211 void handleBatchCompleteEvent(const chreBatchCompleteEvent *event) {
212 LOGI("Received Batch complete event with event type %" PRIu16,
213 event->eventType);
214 }
215
nanoappHandleEvent(uint32_t senderInstanceId,uint16_t eventType,const void * eventData)216 void nanoappHandleEvent(uint32_t senderInstanceId, uint16_t eventType,
217 const void *eventData) {
218 LOGI("Received event 0x%" PRIx16 " from 0x%" PRIx32 " at time %" PRIu64 " ms",
219 eventType, senderInstanceId,
220 chreGetTime() / chre::kOneMillisecondInNanoseconds);
221 switch (eventType) {
222 case CHRE_EVENT_BLE_ADVERTISEMENT:
223 handleAdvertismentEvent(
224 static_cast<const chreBleAdvertisementEvent *>(eventData));
225 break;
226 case CHRE_EVENT_BLE_ASYNC_RESULT:
227 handleAsyncResultEvent(static_cast<const chreAsyncResult *>(eventData));
228 break;
229 case CHRE_EVENT_TIMER:
230 handleTimerEvent(eventData);
231 break;
232 case CHRE_EVENT_BLE_FLUSH_COMPLETE:
233 LOGI("Received flush complete");
234 break;
235 case CHRE_EVENT_BLE_RSSI_READ:
236 handleRssiEvent(static_cast<const chreBleReadRssiEvent *>(eventData));
237 break;
238 case CHRE_EVENT_BLE_BATCH_COMPLETE:
239 handleBatchCompleteEvent(
240 static_cast<const chreBatchCompleteEvent *>(eventData));
241 break;
242 default:
243 LOGW("Unhandled event type %" PRIu16, eventType);
244 break;
245 }
246 }
247
nanoappEnd()248 void nanoappEnd() {
249 if (gBleEnabled && !chreBleStopScanAsync()) {
250 LOGE("Error sending BLE stop scan request sent to PAL");
251 }
252 if (!chreTimerCancel(gEnableDisableTimerHandle)) {
253 LOGE("Error canceling BLE scan timer");
254 }
255 #ifdef BLE_WORLD_ENABLE_BATCHING
256 if (!chreTimerCancel(gFlushTimerHandle)) {
257 LOGE("Error canceling BLE flush timer");
258 }
259 #endif
260 if (!chreTimerCancel(gReadRssiTimerHandle)) {
261 LOGE("Error canceling RSSI read timer");
262 }
263 LOGI("nanoapp stopped");
264 }
265
266 #ifdef CHRE_NANOAPP_INTERNAL
267 } // anonymous namespace
268 } // namespace chre
269
270 #include "chre/platform/static_nanoapp_init.h"
271 #include "chre/util/nanoapp/app_id.h"
272 #include "chre/util/system/napp_permissions.h"
273
274 CHRE_STATIC_NANOAPP_INIT(BleWorld, kBleWorldAppId, 0,
275 NanoappPermissions::CHRE_PERMS_BLE);
276 #endif // CHRE_NANOAPP_INTERNAL
277