|
| 1 | +/********************************************************************* |
| 2 | + This is an example for our nRF52 based Bluefruit LE modules |
| 3 | +
|
| 4 | + Pick one up today in the adafruit shop! |
| 5 | +
|
| 6 | + Adafruit invests time and resources providing this open source code, |
| 7 | + please support Adafruit and open-source hardware by purchasing |
| 8 | + products from Adafruit! |
| 9 | +
|
| 10 | + MIT license, check LICENSE for more information |
| 11 | + All text above, and the splash screen below must be included in |
| 12 | + any redistribution |
| 13 | +*********************************************************************/ |
| 14 | + |
| 15 | +/* This sketch shows how to use BLEClientService and BLEClientCharacteristic |
| 16 | + * to implement a custom client that is used to talk with Gatt server on |
| 17 | + * the TI SensorTag peripheral device. |
| 18 | + * |
| 19 | + * Online uuid/hex converters |
| 20 | + * https://yupana-engineering.com/online-uuid-to-c-array-converter |
| 21 | + * https://www.scadacore.com/tools/programming-calculators/online-hex-converter/ |
| 22 | + * https://codebeautify.org/string-hex-converter |
| 23 | + * |
| 24 | + * Adafruit Bluefruit Feather nRF52 |
| 25 | + * https://learn.adafruit.com/bluefruit-nrf52-feather-learning-guide/blescanner |
| 26 | + * |
| 27 | + * Sensor Tag |
| 28 | + * http://processors.wiki.ti.com/index.php/CC2650_SensorTag_User%27s_Guide#Optical_Sensor |
| 29 | + * |
| 30 | + * Scan Response (Extended Advertisement data retrieval) |
| 31 | + * https://devzone.nordicsemi.com/tutorials/b/bluetooth-low-energy/posts/ble-advertising-a-beginners-tutorial |
| 32 | + * |
| 33 | + */ |
| 34 | + |
| 35 | + #include <bluefruit.h> |
| 36 | + |
| 37 | +/* TI Sensor Tag UUID Definitions |
| 38 | + * Base SensorTag UUID pattern: F000-0000-0451-4000-B000-000000000000 |
| 39 | + * Optical Service: 0xA070 |
| 40 | + * Optical Characteristic: 0xAA71 |
| 41 | + * Optical Data Collection Enabler: 0xAA72 |
| 42 | + * Optical Measurement Period: 0xAA73 |
| 43 | + * Local Name: "CC2650 SensorTag" |
| 44 | + */ |
| 45 | + |
| 46 | + uint8_t SENSORTAG_OPTICAL_SERVICE_UUID[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB0,0x00,0x40,0x51,0x04,0x70,0xAA,0x00,0xF0}; |
| 47 | + uint8_t SENSORTAG_OPTICAL_CHARACTERISTIC_UUID[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB0,0x00,0x40,0x51,0x04,0x71,0xAA,0x00,0xF0}; |
| 48 | + uint8_t SENSORTAG_OPTICAL_ENABLE_CHARACTERISTIC_UUID[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB0,0x00,0x40,0x51,0x04,0x72,0xAA,0x00,0xF0}; |
| 49 | + uint8_t SENSORTAG_OPTICAL_PERIOD_CHARACTERISTIC_UUID[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB0,0x00,0x40,0x51,0x04,0x73,0xAA,0x00,0xF0}; |
| 50 | + uint8_t SENSORTAG_ADV_COMPLETE_LOCAL_NAME[] = {0x43,0x43,0x32,0x36,0x35,0x30,0x20,0x53,0x65,0x6e,0x73,0x6f,0x72,0x54,0x61,0x67}; |
| 51 | + |
| 52 | +BLEClientService sensorTagOpticalService(SENSORTAG_OPTICAL_SERVICE_UUID); |
| 53 | +BLEClientCharacteristic sensorTagOpticalCharacteristic(SENSORTAG_OPTICAL_CHARACTERISTIC_UUID); |
| 54 | +BLEClientCharacteristic opticalCharacteristicDataCollectionEnabler(SENSORTAG_OPTICAL_ENABLE_CHARACTERISTIC_UUID); |
| 55 | +BLEClientCharacteristic opticalCharacteristicPeriod(SENSORTAG_OPTICAL_PERIOD_CHARACTERISTIC_UUID); |
| 56 | + |
| 57 | + |
| 58 | +void setup() |
| 59 | +{ |
| 60 | + Serial.begin(115200); |
| 61 | + |
| 62 | + Serial.println("Bluefruit52 Central TI Sensor Tag Example"); |
| 63 | + Serial.println("-----------------------------------------\n"); |
| 64 | + |
| 65 | + // Initialize Bluefruit with maximum connections as Peripheral = 0, Central = 1 |
| 66 | + // SRAM usage required by SoftDevice will increase dramatically with number of connections |
| 67 | + Bluefruit.begin(0, 1); |
| 68 | + |
| 69 | + Bluefruit.setName("Bluefruit52 TI SensorTag Central"); |
| 70 | + |
| 71 | + // Initialize Service |
| 72 | + sensorTagOpticalService.begin(); |
| 73 | + |
| 74 | + // set up callback for receiving measurement |
| 75 | + sensorTagOpticalCharacteristic.setNotifyCallback(sensortag_optical_notify_callback); |
| 76 | + sensorTagOpticalCharacteristic.begin(); |
| 77 | + |
| 78 | + // set up the characteristic to enable the optical component on the device |
| 79 | + opticalCharacteristicDataCollectionEnabler.begin(); |
| 80 | + |
| 81 | + // set up the characteristic to adjust the measurement period |
| 82 | + opticalCharacteristicPeriod.begin(); |
| 83 | + |
| 84 | + // Increase Blink rate to different from PrPh advertising mode |
| 85 | + Bluefruit.setConnLedInterval(250); |
| 86 | + |
| 87 | + // Callbacks for Central |
| 88 | + Bluefruit.Central.setDisconnectCallback(disconnect_callback); |
| 89 | + Bluefruit.Central.setConnectCallback(connect_callback); |
| 90 | + |
| 91 | + /* Start Central Scanning |
| 92 | + * - Enable auto scan if disconnected |
| 93 | + * - Interval = 100 ms, window = 80 ms |
| 94 | + * - No UUID Filter |
| 95 | + * - Use active scan |
| 96 | + * - Start(timeout) with timeout = 0 will scan forever (until connected) |
| 97 | + */ |
| 98 | + |
| 99 | + Bluefruit.Scanner.setRxCallback(scan_callback); |
| 100 | + Bluefruit.Scanner.restartOnDisconnect(true); |
| 101 | + Bluefruit.Scanner.setInterval(160, 80); // in unit of 0.625 ms |
| 102 | + // Bluefruit.Scanner.filterUuid(); // do not not set anything here or Scan Response won't work |
| 103 | + Bluefruit.Scanner.useActiveScan(true); // required for SensorTag to reveal the Local Name in the advertisement. |
| 104 | + Bluefruit.Scanner.start(0); // 0 = Don't stop scanning after n seconds |
| 105 | +} |
| 106 | + |
| 107 | +void loop() |
| 108 | +{ |
| 109 | + // do nothing |
| 110 | +} |
| 111 | + |
| 112 | +/** |
| 113 | + * Callback invoked when scanner pick up an advertising data |
| 114 | + * @param report Structural advertising data |
| 115 | + */ |
| 116 | +void scan_callback(ble_gap_evt_adv_report_t* report) |
| 117 | +{ |
| 118 | + Serial.println(""); |
| 119 | + Serial.println("Scan Callback"); |
| 120 | + printReport( report ); |
| 121 | + |
| 122 | + /* Choose a peripheral to connect with by searching for an advertisement packet with a |
| 123 | + Complete Local Name matching our target device*/ |
| 124 | + uint8_t buffer[BLE_GAP_ADV_MAX_SIZE] = { 0 }; |
| 125 | + |
| 126 | + Serial.print("Parsing report for Local Name ... "); |
| 127 | + if(Bluefruit.Scanner.parseReportByType(report, BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME, buffer, sizeof(buffer))) |
| 128 | + { |
| 129 | + Serial.println("Found Local Name"); |
| 130 | + Serial.printf("%14s %s\n", "Local Name:", buffer); |
| 131 | + |
| 132 | + Serial.print(" Local Name data: "); |
| 133 | + printHexList(buffer, BLE_GAP_ADV_MAX_SIZE ); |
| 134 | + |
| 135 | + Serial.print("Determining Local Name Match ... "); |
| 136 | + if ( !memcmp( buffer, SENSORTAG_ADV_COMPLETE_LOCAL_NAME, sizeof(SENSORTAG_ADV_COMPLETE_LOCAL_NAME)) ) |
| 137 | + { |
| 138 | + Serial.println("Local Name Match!"); |
| 139 | + |
| 140 | + Serial.println("Connecting to Peripheral ... "); |
| 141 | + Bluefruit.Central.connect(report); |
| 142 | + } |
| 143 | + else |
| 144 | + { |
| 145 | + Serial.println("No Match"); |
| 146 | + } |
| 147 | + } |
| 148 | + else |
| 149 | + { |
| 150 | + Serial.println("Failed"); |
| 151 | + } |
| 152 | +} |
| 153 | + |
| 154 | +/** |
| 155 | + * Callback invoked when an connection is established |
| 156 | + * @param conn_handle |
| 157 | + */ |
| 158 | +void connect_callback(uint16_t conn_handle) |
| 159 | +{ |
| 160 | + Serial.println(""); |
| 161 | + Serial.print("Connect Callback, conn_handle: "); |
| 162 | + Serial.println( conn_handle ); |
| 163 | + |
| 164 | + /* Complete Local Name */ |
| 165 | + uint8_t buffer[BLE_GAP_ADV_MAX_SIZE] = { 0 }; |
| 166 | + |
| 167 | + // If Service is not found, disconnect and return |
| 168 | + Serial.print("Discovering Optical Service ... "); |
| 169 | + if ( !sensorTagOpticalService.discover(conn_handle) ) |
| 170 | + { |
| 171 | + Serial.println("No Service Found"); |
| 172 | + |
| 173 | + // disconect since we couldn't find service |
| 174 | + Bluefruit.Central.disconnect(conn_handle); |
| 175 | + |
| 176 | + return; |
| 177 | + } |
| 178 | + Serial.println("Service Found"); |
| 179 | + |
| 180 | + // Once service is found, we continue to discover the primary characteristic |
| 181 | + Serial.print("Discovering Optical Characteristic ... "); |
| 182 | + if ( !sensorTagOpticalCharacteristic.discover() ) |
| 183 | + { |
| 184 | + // Measurement chr is mandatory, if it is not found (valid), then disconnect |
| 185 | + Serial.println("No Characteristic Found. Characteristic is mandatory but not found. "); |
| 186 | + Bluefruit.Central.disconnect(conn_handle); |
| 187 | + return; |
| 188 | + } |
| 189 | + Serial.println("Characteristic Found"); |
| 190 | + |
| 191 | + // Data Collection Charactistic. Find and enable. |
| 192 | + // You enable data collection on the Characteristic before the peripheral will start measuring values. |
| 193 | + // This is different than setting the Notify descriptor, which is handled below. |
| 194 | + Serial.print("Discovering Data Collection Configuration Characteristic ... "); |
| 195 | + if ( !opticalCharacteristicDataCollectionEnabler.discover() ) |
| 196 | + { |
| 197 | + Serial.println("No Characteristic Found. Characteristic is mandatory but not found."); |
| 198 | + Bluefruit.Central.disconnect(conn_handle); |
| 199 | + return; |
| 200 | + } |
| 201 | + Serial.println("Characteristic Found"); |
| 202 | + // Found it, now write 0x01 to turn on optical data collection |
| 203 | + Serial.print("Enabling Data Collection, return value: "); |
| 204 | + Serial.println( opticalCharacteristicDataCollectionEnabler.write8( 0x01 ) ); |
| 205 | + |
| 206 | + // Measurement Period Characteristic. Find and adjust. |
| 207 | + Serial.print("Measurement Period Characteristic ... "); |
| 208 | + if ( !opticalCharacteristicPeriod.discover() ) |
| 209 | + { |
| 210 | + Serial.println("No Characteristic Found, but not mandatory so not disconnecting"); |
| 211 | + } |
| 212 | + Serial.println("Characteristic Found"); |
| 213 | + // Found it, now adjust it: |
| 214 | + // Resolution 10 ms. Range 100 ms (0x0A) to 2.55 sec (0xFF). Default is 800 milliseconds (0x50). |
| 215 | + // 19 is 250 |
| 216 | + Serial.print("Adjusting Measurement Period, return value: "); |
| 217 | + // Serial.println( opticalCharacteristicPeriod.write8( 0xFF ) ); // Slowest |
| 218 | + Serial.println( opticalCharacteristicPeriod.write8( 0x0A ) ); // Fastest |
| 219 | + |
| 220 | + // Reaching here means we are ready to go, let's enable notification on measurement chr |
| 221 | + Serial.print("Enabling Notify on the Characteristic ... "); |
| 222 | + if ( sensorTagOpticalCharacteristic.enableNotify() ) |
| 223 | + { |
| 224 | + Serial.println("enableNotify success, now ready to receive Characteristic values"); |
| 225 | + }else |
| 226 | + { |
| 227 | + Serial.println("Couldn't enable notify for Characteristic. Increase DEBUG LEVEL for troubleshooting"); |
| 228 | + } |
| 229 | +} |
| 230 | + |
| 231 | +/** |
| 232 | + * Callback invoked when a connection is dropped |
| 233 | + * @param conn_handle |
| 234 | + * @param reason |
| 235 | + */ |
| 236 | +void disconnect_callback(uint16_t conn_handle, uint8_t reason) |
| 237 | +{ |
| 238 | + (void) conn_handle; |
| 239 | + (void) reason; |
| 240 | + |
| 241 | + Serial.println("Disconnected"); |
| 242 | +} |
| 243 | + |
| 244 | +/** |
| 245 | + * Hooked callback that triggered when a measurement value is sent from peripheral |
| 246 | + * @param chr Pointer client characteristic that even occurred, |
| 247 | + * in this example it should be sensorTagOpticalCharacteristic |
| 248 | + * @param data Pointer to received data |
| 249 | + * @param len Length of received data |
| 250 | + */ |
| 251 | +void sensortag_optical_notify_callback(BLEClientCharacteristic* chr, uint8_t* data, uint16_t len) |
| 252 | +{ |
| 253 | + Serial.print("Optical data: "); |
| 254 | + uint16_t tData = (uint16_t) ( ( data[0] & 0xFF ) | ( data[1] & 0xFF ) << 8 ); |
| 255 | + Serial.print( sensorOpt3001Convert( tData ) ); |
| 256 | + Serial.println( " Lux" ); |
| 257 | +} |
| 258 | + |
| 259 | +/* |
| 260 | + * Conversion to Lux. Algorithm from the TI Sensor Tag Wiki documentation page |
| 261 | + */ |
| 262 | +float sensorOpt3001Convert(uint16_t rawData) |
| 263 | +{ |
| 264 | + uint16_t e, m; |
| 265 | + |
| 266 | + m = rawData & 0x0FFF; |
| 267 | + e = (rawData & 0xF000) >> 12; |
| 268 | + |
| 269 | + /** e on 4 bits stored in a 16 bit unsigned => it can store 2 << (e - 1) with e < 16 */ |
| 270 | + e = (e == 0) ? 1 : 2 << (e - 1); |
| 271 | + |
| 272 | + return m * (0.01 * e); |
| 273 | +} |
| 274 | + |
| 275 | +/* Prints a hex list to the Serial Monitor */ |
| 276 | +void printHexList(uint8_t* buffer, uint8_t len) |
| 277 | +{ |
| 278 | + // print forward order |
| 279 | + for(int i=0; i<len; i++) |
| 280 | + { |
| 281 | + Serial.printf("%02X-", buffer[i]); |
| 282 | + } |
| 283 | + Serial.println(); |
| 284 | +} |
| 285 | + |
| 286 | +void printReport( const ble_gap_evt_adv_report_t* report ) |
| 287 | +{ |
| 288 | + Serial.print( " rssi: " ); |
| 289 | + Serial.println( report->rssi ); |
| 290 | + Serial.print( " scan_rsp: " ); |
| 291 | + Serial.println( report->scan_rsp ); |
| 292 | + Serial.print( " type: " ); |
| 293 | + Serial.println( report->type ); |
| 294 | + Serial.print( " dlen: " ); |
| 295 | + Serial.println( report->dlen ); |
| 296 | + Serial.print( " data: " ); |
| 297 | + for( int i = 0; i < report->dlen; i+= sizeof(uint8_t) ) |
| 298 | + { |
| 299 | + Serial.printf( "%02X-", report->data[ i ] ); |
| 300 | + } |
| 301 | + Serial.println(""); |
| 302 | +} |
0 commit comments