|
/* |
|
* Adafruit nRF52 Feather Example |
|
* Copyright (c) 2018-2019 George White <stonehippo@gmail.com> |
|
* |
|
* MIT LICENSE, see LICENSE.txt at https://gist.github.com/stonehippo/ffbed2f32e1813f4019ecedd22062636 |
|
* |
|
* I made this example file for a coworker. It illustrates a few things: |
|
* |
|
* - Setting up a custom service |
|
* - Setting up custom characteristics on that service |
|
* - Connecting to the littlefs file system to read and write files for persistance |
|
* - Setting manufacturer data in the BLE scan response |
|
* - using littlefs-persisted data to maintain characteristic state between device power cycles |
|
* - very basic connect/disconnect event callbacks |
|
* - multiple characteristics for "slots" |
|
* |
|
* This example is dependent on the Adafruit nRF52 Feather board support; because it uses littlefs |
|
* to persist data on device, it requires at least version 0.11.1. For more info see |
|
* https://github.com/adafruit/Adafruit_nRF52_Arduino |
|
*/ |
|
#include <bluefruit.h> |
|
#include <Adafruit_LittleFS.h> |
|
#include <InternalFileSystem.h> |
|
|
|
using namespace Adafruit_LittleFS_Namespace; |
|
|
|
const uint8_t SERVICE_UUID[16] = {0x3,0x4E,0x53,0xD7,0xD1,0xC4,0x3C,0x8A,0x60,0x40,0x85,0xC8,0xE,0xA9,0x44,0x63}; |
|
const uint8_t CHARACTERISTIC_UUID[16] = {0x1,0x0,0x0,0x0,0xD1,0xC4,0x3C,0x8A,0x60,0x40,0x85,0xC8,0xE,0xA9,0x44,0x63}; |
|
const uint8_t MANUFACTURER_DATA_UUID[16] = {0x2,0x0,0x0,0x0,0xD1,0xC4,0x3C,0x8A,0x60,0x40,0x85,0xC8,0xE,0xA9,0x44,0x63}; |
|
const uint8_t ADDRESS_ONE[16] = {0x3,0x0,0x0,0x0,0xD1,0xC4,0x3C,0x8A,0x60,0x40,0x85,0xC8,0xE,0xA9,0x44,0x63}; |
|
const uint8_t ADDRESS_TWO[16] = {0x4,0x0,0x0,0x0,0xD1,0xC4,0x3C,0x8A,0x60,0x40,0x85,0xC8,0xE,0xA9,0x44,0x63}; |
|
const uint8_t ADDRESS_THREE[16] = {0x5,0x0,0x0,0x0,0xD1,0xC4,0x3C,0x8A,0x60,0x40,0x85,0xC8,0xE,0xA9,0x44,0x63}; |
|
const uint8_t ADDRESS_FOUR[16] = {0x6,0x0,0x0,0x0,0xD1,0xC4,0x3C,0x8A,0x60,0x40,0x85,0xC8,0xE,0xA9,0x44,0x63}; |
|
const uint8_t ADDRESS_FIVE[16] = {0x7,0x0,0x0,0x0,0xD1,0xC4,0x3C,0x8A,0x60,0x40,0x85,0xC8,0xE,0xA9,0x44,0x63}; |
|
BLEService single = BLEService(SERVICE_UUID); |
|
BLECharacteristic data = BLECharacteristic(CHARACTERISTIC_UUID); |
|
BLECharacteristic manData = BLECharacteristic(MANUFACTURER_DATA_UUID); |
|
BLECharacteristic addressOne = BLECharacteristic(ADDRESS_ONE); |
|
BLECharacteristic addressTwo = BLECharacteristic(ADDRESS_TWO); |
|
BLECharacteristic addressThree = BLECharacteristic(ADDRESS_THREE); |
|
BLECharacteristic addressFour = BLECharacteristic(ADDRESS_FOUR); |
|
BLECharacteristic addressFive = BLECharacteristic(ADDRESS_FIVE); |
|
|
|
uint8_t state = 1; |
|
|
|
uint8_t randomByte() { |
|
return random(255); |
|
} |
|
|
|
char uniqueID[4]; |
|
|
|
// Managing state persistance in LittleFS file system |
|
#define STATE_FILENAME "/state.txt" |
|
#define ID_FILENAME "/id.txt" |
|
|
|
File file(InternalFS); |
|
|
|
void setup() { |
|
Serial.begin(115200); |
|
|
|
char a1[3] = { 0xAF, 0x07, 0xCF }; |
|
char a2[3] = { 0xAF, 0x24, 0xAF }; |
|
|
|
// seed the PRNG with random noise from measuring an analog input |
|
randomSeed(analogRead(A5)); |
|
|
|
Bluefruit.begin(); |
|
Bluefruit.setName("Adafruit nrf52 Demo"); |
|
|
|
// start the file system lib and see if we have anything persisted |
|
InternalFS.begin(); |
|
|
|
//InternalFS.format(true); // Uncomment to wipe out all persisted data, including state and unique ID |
|
|
|
setState(); |
|
|
|
// set the unique ID for this device |
|
if (!getUniqueID()) { |
|
setUniqueID(); |
|
} |
|
|
|
Bluefruit.Periph.setConnectCallback(connectCallback); |
|
Bluefruit.Periph.setDisconnectCallback(disconnectCallback); |
|
|
|
// Adverstising |
|
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); |
|
Bluefruit.Advertising.addService(single); |
|
Bluefruit.ScanResponse.addName(); |
|
Bluefruit.ScanResponse.addManufacturerData(uniqueID, sizeof(uniqueID) - 1); |
|
Bluefruit.Advertising.restartOnDisconnect(true); |
|
Bluefruit.Advertising.setInterval(32, 244); |
|
Bluefruit.Advertising.setFastTimeout(30); |
|
Bluefruit.Advertising.start(0); |
|
|
|
// Service |
|
single.begin(); |
|
data.setProperties(CHR_PROPS_READ | CHR_PROPS_WRITE); |
|
data.setPermission(SECMODE_OPEN, SECMODE_OPEN); |
|
data.setUserDescriptor("Test Data"); |
|
data.setFixedLen(1); |
|
data.begin(); |
|
// set the initial value |
|
data.write8(state); |
|
|
|
manData.setProperties(CHR_PROPS_READ); |
|
manData.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); |
|
manData.setUserDescriptor("Unique Device ID"); |
|
manData.setMaxLen(8); |
|
manData.begin(); |
|
manData.write(uniqueID, sizeof(uniqueID) - 1); // truncate the last bit, since it's there to make it a string |
|
|
|
addressOne.setProperties(CHR_PROPS_READ | CHR_PROPS_WRITE); |
|
addressOne.setPermission(SECMODE_OPEN, SECMODE_OPEN); |
|
addressOne.setMaxLen(3); |
|
addressOne.begin(); |
|
addressOne.write(a1, sizeof(a1));// Set this to a fake address for fun |
|
|
|
addressTwo.setProperties(CHR_PROPS_READ | CHR_PROPS_WRITE); |
|
addressTwo.setPermission(SECMODE_OPEN, SECMODE_OPEN); |
|
addressTwo.setMaxLen(3); |
|
addressTwo.begin(); |
|
addressTwo.write(a2, sizeof(a2)); |
|
|
|
addressThree.setProperties(CHR_PROPS_READ | CHR_PROPS_WRITE); |
|
addressThree.setPermission(SECMODE_OPEN, SECMODE_OPEN); |
|
addressThree.setMaxLen(3); |
|
addressThree.begin(); |
|
|
|
addressFour.setProperties(CHR_PROPS_READ | CHR_PROPS_WRITE); |
|
addressFour.setPermission(SECMODE_OPEN, SECMODE_OPEN); |
|
addressFour.setMaxLen(3); |
|
addressFour.begin(); |
|
|
|
addressFive.setProperties(CHR_PROPS_READ | CHR_PROPS_WRITE); |
|
addressFive.setPermission(SECMODE_OPEN, SECMODE_OPEN); |
|
addressFive.setMaxLen(3); |
|
addressFive.begin(); |
|
} |
|
|
|
void loop() { |
|
// do a primitive check to see if we should report that the value |
|
// has changed as an example of reading the value |
|
uint8_t value = data.read8(); |
|
if (state != value) { |
|
Serial.print("State has changed: "); |
|
Serial.print(state); |
|
Serial.print(" -> "); |
|
Serial.println(value); |
|
state = value; |
|
writeState(); |
|
readState(); |
|
} |
|
|
|
} |
|
|
|
bool getUniqueID() { |
|
file.open(ID_FILENAME, FILE_O_READ); |
|
if(file) { |
|
uint32_t readLen; |
|
char buffer[64] = { 0 }; |
|
readLen = file.read(buffer, sizeof(buffer)); |
|
buffer[readLen] = 0; |
|
Serial.println("Got unique ID from persistance"); |
|
Serial.println(buffer); |
|
file.close(); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
void setUniqueID() { |
|
if (file.open(ID_FILENAME, FILE_O_WRITE)) { |
|
uniqueID[0] = randomByte(); |
|
uniqueID[1] = randomByte(); |
|
uniqueID[2] = randomByte(); |
|
uniqueID[3] = 0; |
|
file.write(uniqueID, sizeof(uniqueID)); |
|
file.close(); |
|
Serial.println("set a new unique ID in persistance"); |
|
} |
|
} |
|
|
|
// Write the state to the local file system |
|
void writeState() { |
|
if(file.open(STATE_FILENAME, FILE_O_WRITE)) { |
|
char buffer[12]; |
|
itoa(state, buffer, 10); |
|
file.write(buffer, sizeof(buffer)); |
|
file.close(); |
|
} |
|
} |
|
|
|
// Read the state from the local file system |
|
uint8_t readState() { |
|
uint8_t value = 1; // set the default to return |
|
file.open(STATE_FILENAME, FILE_O_READ); |
|
if (file) { |
|
Serial.println("Value from " STATE_FILENAME); |
|
uint32_t readLen; |
|
char buffer[64] = { 0 }; // buffer starts as an empty char array (C string) |
|
readLen = file.read(buffer, sizeof(buffer)); |
|
buffer[readLen] = 0; // drop any last character and make sure the buffer contains a C string |
|
value = atoi(buffer); // convert the string in the file to a number |
|
Serial.println(value); |
|
file.close(); |
|
} else { |
|
Serial.println("State file does not yet exist"); |
|
} |
|
return value; |
|
} |
|
|
|
// take whatever number we read from the file and store it in current state |
|
void setState() { |
|
state = readState(); |
|
} |
|
|
|
void connectCallback(uint16_t handle) { |
|
// As of Adafruit nRF52 Arduino 0.10.1, use BLEConnection |
|
BLEConnection* connection = Bluefruit.Connection(handle); |
|
|
|
// handle checks for running sessions and other reconnection details here |
|
char central[32] = { 0 }; |
|
connection->getPeerName(central, sizeof(central)); |
|
Serial.print("Connected to "); |
|
Serial.println(central); |
|
} |
|
|
|
void disconnectCallback(uint16_t handle, uint8_t reason) { |
|
// handle anything disconnection details here |
|
Serial.println("Disconnected, resuming advertising"); |
|
} |
Thanks stonehippo for the clarification, in fact it took me another 5 mins to figure it out myself, I also do not fully understand why they choose to do it in 3 layers, as they made a wrapper already around littleFS, so InternalFileSystem is in fact the 3rd wrapper. But on the other hand side this is by far the most user friendly persistent storage for the Nordic nRF52. I'm grateful for Adafruit to provide libs for this platform, as Nordic is absolutely not interested in the Arduino community...