|
/* |
|
* 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"); |
|
} |
I'm not sure I get it right: above you write that the sketch uses the littleFS, but it seems to me you initialize the InternalFS instead. Do you have an example for the litteFS usage as well?