Merge pull request #89 from lancaster-university/ble-ccccd-persist

Basic Persistent Storage
This commit is contained in:
James Devine 2016-02-02 21:50:33 +00:00
commit 41447b634d
9 changed files with 375 additions and 32 deletions

View file

@ -36,6 +36,7 @@
#include "MicroBitBLEManager.h"
#include "MicroBitRadio.h"
#include "MicroBitStorage.h"
// MicroBit::flags values
#define MICROBIT_FLAG_SCHEDULER_RUNNING 0x00000001

View file

@ -50,7 +50,7 @@ extern const int8_t MICROBIT_BLE_POWER_LEVEL[];
*
*/
class MicroBitBLEManager
{
{
public:
// The mbed abstraction of the BlueTooth Low Energy (BLE) hardware

View file

@ -97,6 +97,16 @@ struct CompassSample
this->y = y;
this->z = z;
}
bool operator==(const CompassSample& other) const
{
return x == other.x && y == other.y && z == other.z;
}
bool operator!=(const CompassSample& other) const
{
return !(x == other.x && y == other.y && z == other.z);
}
};
/**

74
inc/MicroBitStorage.h Normal file
View file

@ -0,0 +1,74 @@
#ifndef MICROBIT_STORAGE_H
#define MICROBIT_STORAGE_H
#include "mbed.h"
#include "MicroBitBLEManager.h"
#include "MicroBitCompass.h"
#define MICROBIT_STORAGE_CONFIG_MAGIC 0xCAFECAFE
struct BLESysAttribute
{
uint32_t magic;
uint8_t sys_attr[8];
};
struct MicroBitConfigurationBlock
{
uint32_t magic;
BLESysAttribute sysAttrs[MICROBIT_BLE_MAXIMUM_BONDS];
CompassSample compassCalibrationData;
int thermometerCalibration;
};
/**
* Class definition for the MicroBitStorage class.
* This allows reading and writing of small blocks of data to FLASH memory.
*/
class MicroBitStorage
{
public:
/*
* Default constructor.
*/
MicroBitStorage();
/*
* Writes the given number of bytes to the address specified.
* @param buffer the data to write.
* @param address the location in memory to write to.
* @param length the number of bytes to write.
* TODO: Write this one!
*/
int writeBytes(uint8_t *buffer, uint32_t address, int length);
/**
* Method for erasing a page in flash.
* @param page_address Address of the first word in the page to be erased.
*/
void flashPageErase(uint32_t * page_address);
/**
* Method for writing a word of data in flash with a value.
* @param address Address of the word to change.
* @param value Value to be written to flash.
*/
void flashWordWrite(uint32_t * address, uint32_t value);
/*
* Reads the micro:bit's configuration data block from FLASH into a RAM buffer.
* @return a structure containing the stored data.
*/
MicroBitConfigurationBlock *getConfigurationBlock();
/*
* Writes the micro:bit's configuration data block from FLASH into a RAM buffer.
* @return a structure containing the stored data.
*/
int setConfigurationBlock(MicroBitConfigurationBlock *block);
};
#endif

View file

@ -32,6 +32,7 @@ set(YOTTA_AUTO_MICROBIT-DAL_CPP_FILES
"MicroBitLightSensor.cpp"
"RefCounted.cpp"
"MemberFunctionCallback.cpp"
"MicroBitStorage.cpp"
"ble-services/MicroBitBLEManager.cpp"
"ble-services/MicroBitDFUService.cpp"
"ble-services/MicroBitEventService.cpp"

View file

@ -488,6 +488,24 @@ void MicroBitCompass::calibrateEnd()
*/
void MicroBitCompass::setCalibration(CompassSample calibration)
{
MicroBitStorage s = MicroBitStorage();
MicroBitConfigurationBlock *b = s.getConfigurationBlock();
//check we are not storing our restored calibration data.
if(b->compassCalibrationData != calibration)
{
b->magic = MICROBIT_STORAGE_CONFIG_MAGIC;
b->compassCalibrationData.x = calibration.x;
b->compassCalibrationData.y = calibration.y;
b->compassCalibrationData.z = calibration.z;
s.setConfigurationBlock(b);
}
delete b;
average = calibration;
status |= MICROBIT_COMPASS_STATUS_CALIBRATED;
}

189
source/MicroBitStorage.cpp Normal file
View file

@ -0,0 +1,189 @@
/**
* Class definition for the MicroBitStorage class.
* This allows reading and writing of FLASH memory.
*/
#include "MicroBit.h"
/*
* Default constructor
*/
MicroBitStorage::MicroBitStorage()
{
}
/*
* Writes the given number of bytes to the address specified.
* TODO: Complete this function to provide an abstraction across SD and no SD builds.
*
* @param buffer the data to write.
* @param address the location in memory to write to.
* @param length the number of bytes to write.
*/
int MicroBitStorage::writeBytes(uint8_t *buffer, uint32_t address, int length)
{
(void) buffer;
(void) address;
(void) length;
return MICROBIT_OK;
}
/**
* Method for erasing a page in flash.
*
* @param page_address Address of the first word in the page to be erased.
*/
void MicroBitStorage::flashPageErase(uint32_t * page_address)
{
// Turn on flash erase enable and wait until the NVMC is ready:
NRF_NVMC->CONFIG = (NVMC_CONFIG_WEN_Een << NVMC_CONFIG_WEN_Pos);
while (NRF_NVMC->READY == NVMC_READY_READY_Busy);
// Erase page:
NRF_NVMC->ERASEPAGE = (uint32_t)page_address;
while (NRF_NVMC->READY == NVMC_READY_READY_Busy);
// Turn off flash erase enable and wait until the NVMC is ready:
NRF_NVMC->CONFIG = (NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos);
while (NRF_NVMC->READY == NVMC_READY_READY_Busy);
}
/*
* Reads the micro:bit's configuration data block from FLASH into a RAM buffer.
* @return a pointer to the structure containing the stored data.
* NOTE: it is the callers responsibility to free the buffer.
*/
MicroBitConfigurationBlock *MicroBitStorage::getConfigurationBlock()
{
uint32_t pg_size = NRF_FICR->CODEPAGESIZE;
uint32_t pg_num = NRF_FICR->CODESIZE - 19; // Use the page just below the BLE Bond Data
MicroBitConfigurationBlock *block = new MicroBitConfigurationBlock();
memcpy(block, (uint32_t *)(pg_size * pg_num), sizeof(MicroBitConfigurationBlock));
if (block->magic != MICROBIT_STORAGE_CONFIG_MAGIC)
memclr(block, sizeof(MicroBitConfigurationBlock));
#if CONFIG_ENABLED(MICROBIT_DBG)
uBit.serial.printf("RETREIVE:\r\n");
if(block->magic == MICROBIT_STORAGE_CONFIG_MAGIC)
{
uBit.serial.printf("magic: %.2x\r\n", block->magic);
for(int attrIterator = 0; attrIterator < MICROBIT_BLE_MAXIMUM_BONDS; attrIterator++)
{
if(block->sysAttrs[attrIterator].magic == MICROBIT_STORAGE_CONFIG_MAGIC)
{
uBit.serial.printf("systemAttrs[%d]: ", attrIterator);
for(int i = 0; i < 8; i++)
{
uBit.serial.printf("%.2x\r\n", block->sysAttrs[attrIterator].sys_attr[i]);
}
uBit.serial.printf("\r\n");
}
}
uBit.serial.printf("compass x: %d y: %d z: %d\r\n", block->compassCalibrationData.x, block->compassCalibrationData.y, block->compassCalibrationData.z);
uBit.serial.printf("temperature: %d\r\n", block->thermometerCalibration);
}
#endif
return block;
}
/**
* Function for filling a page in flash with a value.
*
* @param address Address of the first word in the page to be filled.
* @param value Value to be written to flash.
*/
void MicroBitStorage::flashWordWrite(uint32_t * address, uint32_t value)
{
// Turn on flash write enable and wait until the NVMC is ready:
NRF_NVMC->CONFIG = (NVMC_CONFIG_WEN_Wen << NVMC_CONFIG_WEN_Pos);
while (NRF_NVMC->READY == NVMC_READY_READY_Busy);
*address = value;
while (NRF_NVMC->READY == NVMC_READY_READY_Busy);
// Turn off flash write enable and wait until the NVMC is ready:
NRF_NVMC->CONFIG = (NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos);
while (NRF_NVMC->READY == NVMC_READY_READY_Busy);
}
/*
* Writes the micro:bit's configuration data block from FLASH into a RAM buffer.
* @return a structure containing the stored data.
*/
int MicroBitStorage::setConfigurationBlock(MicroBitConfigurationBlock *block)
{
#if CONFIG_ENABLED(MICROBIT_DBG)
uBit.serial.printf("STORE:\r\n");
if(block->magic == MICROBIT_STORAGE_CONFIG_MAGIC)
{
uBit.serial.printf("magic: %.2x\r\n", block->magic);
for(int attrIterator = 0; attrIterator < MICROBIT_BLE_MAXIMUM_BONDS; attrIterator++)
{
if(block->sysAttrs[attrIterator].magic == MICROBIT_STORAGE_CONFIG_MAGIC)
{
uBit.serial.printf("systemAttrs[%d]: ", attrIterator);
for(int i = 0; i < 8; i++)
{
uBit.serial.printf("%.2x\r\n", block->sysAttrs[attrIterator].sys_attr[i]);
}
uBit.serial.printf("\r\n");
}
}
uBit.serial.printf("compass x: %d y: %d z: %d\r\n", block->compassCalibrationData.x, block->compassCalibrationData.y, block->compassCalibrationData.z);
uBit.serial.printf("temperature: %d\r\n", block->thermometerCalibration);
}
#endif
uint32_t * addr;
uint32_t pg_size;
uint32_t pg_num;
int wordsToWrite = sizeof(MicroBitConfigurationBlock) / 4 + 1;
pg_size = NRF_FICR->CODEPAGESIZE;
pg_num = NRF_FICR->CODESIZE - 19; // Use the page just below the BLE Bond Data
addr = (uint32_t *)(pg_size * pg_num);
flashPageErase(addr);
uint32_t *b = (uint32_t *) block;
for (int i = 0; i < wordsToWrite; i++)
{
flashWordWrite(addr, *b);
addr++;
b++;
}
return MICROBIT_OK;
}

View file

@ -36,6 +36,19 @@ int main()
// Provide time for all threaded initialisers to complete.
uBit.sleep(100);
//check our persistent storage for compassCalibrationData
MicroBitStorage s = MicroBitStorage();
MicroBitConfigurationBlock *b = s.getConfigurationBlock();
//if we have some calibrated data, calibrate the compass!
if(b->magic == MICROBIT_STORAGE_CONFIG_MAGIC)
{
if(b->compassCalibrationData != CompassSample(0,0,0))
uBit.compass.setCalibration(b->compassCalibrationData);
}
delete b;
#if CONFIG_ENABLED(MICROBIT_BLE_PAIRING_MODE)
// Test if we need to enter BLE pairing mode...
int i=0;

View file

@ -13,6 +13,10 @@
#endif
#include "ble.h"
extern "C"
{
#include "device_manager.h"
}
/*
* Return to our predefined compiler settings.
@ -36,6 +40,7 @@ const int8_t MICROBIT_BLE_POWER_LEVEL[] = {-30, -20, -16, -12, -8, -4, 0, 4};
* whilst keeping the code modular.
*/
static MicroBitBLEManager *manager = NULL;
static uint8_t deviceID = 255;
/**
* Callback when a BLE GATT disconnect occurs.
@ -44,12 +49,26 @@ static void bleDisconnectionCallback(const Gap::DisconnectionCallbackParams_t *r
{
(void) reason; /* -Wunused-param */
// configure the stack to release CPU during critical timing events.
// mbed-classic performs __disabe_irq calls in its timers, which can cause MIC failures
// on secure BLE channels.
ble_common_opt_radio_cpu_mutex_t opt;
opt.enable = 0;
sd_ble_opt_set(BLE_COMMON_OPT_RADIO_CPU_MUTEX, (const ble_opt_t *)&opt);
BLESysAttribute attrib;
uint16_t len = sizeof(BLESysAttribute);
sd_ble_gatts_sys_attr_get(reason->handle, attrib.sys_attr, &len, BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS);
if (deviceID < MICROBIT_BLE_MAXIMUM_BONDS)
{
MicroBitStorage s = MicroBitStorage();
MicroBitConfigurationBlock *b = s.getConfigurationBlock();
if(b->sysAttrs[deviceID].magic != MICROBIT_STORAGE_CONFIG_MAGIC || memcmp(b->sysAttrs[deviceID].sys_attr, attrib.sys_attr, sizeof(BLESysAttribute)) != 0)
{
b->magic = MICROBIT_STORAGE_CONFIG_MAGIC;
b->sysAttrs[deviceID].magic = MICROBIT_STORAGE_CONFIG_MAGIC;
memcpy(b->sysAttrs[deviceID].sys_attr, attrib.sys_attr, sizeof(BLESysAttribute));
s.setConfigurationBlock(b);
}
delete b;
}
if (manager)
manager->advertise();
@ -60,23 +79,29 @@ static void bleDisconnectionCallback(const Gap::DisconnectionCallbackParams_t *r
*/
static void bleConnectionCallback(const Gap::ConnectionCallbackParams_t *reason)
{
// configure the stack to hold on to CPU during critical timing events.
// mbed-classic performs __disabe_irq calls in its timers, which can cause MIC failures
// on secure BLE channels.
ble_common_opt_radio_cpu_mutex_t opt;
opt.enable = 1;
sd_ble_opt_set(BLE_COMMON_OPT_RADIO_CPU_MUTEX, (const ble_opt_t *)&opt);
deviceID = 255;
// Ensure that there's no stale, cached information in the client... invalidate all characteristics.
uint16_t len = 8;
dm_handle_t dm_handle = {0,0,0,0};
// Configure the ServiceChanged characteristic to receive service changed indications
// TODO: This is really a workaround as we can't maintain persistent state on the micro:bit across USB
// reprogramming flashes.... yet.
uint8_t data[] = {0x0B,0x00,0x02,0x00,0x02,0x00,0xB8,0x46};
int ret = dm_handle_get(reason->handle, &dm_handle);
sd_ble_gatts_sys_attr_set(reason->handle, data, len, BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS);
sd_ble_gatts_service_changed(reason->handle, 0x000c, 0xffff);
if (ret == 0)
deviceID = dm_handle.device_id;
if (deviceID < MICROBIT_BLE_MAXIMUM_BONDS)
{
// Ensure that there's no stale, cached information in the client... invalidate all characteristics.
MicroBitStorage s = MicroBitStorage();
MicroBitConfigurationBlock *b = s.getConfigurationBlock();
if(b->sysAttrs[deviceID].magic == MICROBIT_STORAGE_CONFIG_MAGIC)
{
sd_ble_gatts_sys_attr_set(reason->handle, b->sysAttrs[deviceID].sys_attr, sizeof(BLESysAttribute), BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS);
sd_ble_gatts_service_changed(reason->handle, 0x000c, 0xffff);
}
delete b;
}
}
static void passkeyDisplayCallback(Gap::Handle_t handle, const SecurityManager::Passkey_t passkey)
@ -93,6 +118,12 @@ static void securitySetupCompletedCallback(Gap::Handle_t handle, SecurityManager
{
(void) handle; /* -Wunused-param */
dm_handle_t dm_handle = {0,0,0,0};
int ret = dm_handle_get(handle, &dm_handle);
if (ret == 0)
deviceID = dm_handle.device_id;
if (manager)
manager->pairingComplete(status == SecurityManager::SEC_STATUS_SUCCESS);
}
@ -144,11 +175,16 @@ void MicroBitBLEManager::init(ManagedString deviceName, ManagedString serialNumb
ble = new BLEDevice();
ble->init();
// automatically restart advertising after a device disconnects.
ble->onDisconnection(bleDisconnectionCallback);
ble->onConnection(bleConnectionCallback);
// Configure the stack to hold onto the CPU during critical timing events.
// mbed-classic performs __disable_irq() calls in its timers that can cause
// MIC failures on secure BLE channels...
ble_common_opt_radio_cpu_mutex_t opt;
opt.enable = 1;
sd_ble_opt_set(BLE_COMMON_OPT_RADIO_CPU_MUTEX, (const ble_opt_t *)&opt);
#if CONFIG_ENABLED(MICROBIT_BLE_PRIVATE_ADDRESSES)
// Configure for private addresses, so kids' behaviour can't be easily tracked.
@ -160,12 +196,13 @@ void MicroBitBLEManager::init(ManagedString deviceName, ManagedString serialNumb
ble->securityManager().onSecuritySetupCompleted(securitySetupCompletedCallback);
ble->securityManager().init(enableBonding, MICROBIT_BLE_REQUIRE_MITM, SecurityManager::IO_CAPS_DISPLAY_ONLY);
// If we're in pairing mode, review the size of the bond table.
if (enableBonding)
{
// If we're in pairing mode, review the size of the bond table.
int bonds = getBondCount();
// TODO: It would be much better to implement some sort of LRU/NFU policy here,
// but this isn't currently supported in mbed, so we'd need to layer break...
int bonds = getBondCount();
// If we're full, empty the bond table.
if (bonds >= MICROBIT_BLE_MAXIMUM_BONDS)
@ -173,7 +210,7 @@ void MicroBitBLEManager::init(ManagedString deviceName, ManagedString serialNumb
}
#if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST)
// Configure a whitelist to filter all connection requetss from unbonded devices.
// Configure a whitelist to filter all connection requetss from unbonded devices.
// Most BLE stacks only permit one connection at a time, so this prevents denial of service attacks.
BLEProtocol::Address_t bondedAddresses[MICROBIT_BLE_MAXIMUM_BONDS];
Gap::Whitelist_t whitelist;
@ -182,13 +219,12 @@ void MicroBitBLEManager::init(ManagedString deviceName, ManagedString serialNumb
ble->securityManager().getAddressesFromBondTable(whitelist);
ble->gap().setWhitelist(whitelist);
ble->gap().setScanningPolicyMode(Gap::SCAN_POLICY_IGNORE_WHITELIST);
ble->gap().setAdvertisingPolicyMode(Gap::ADV_POLICY_FILTER_CONN_REQS);
#endif
#endif
// Configure the radio at our default power level
setTransmitPower(MICROBIT_BLE_DEFAULT_TX_POWER);
setTransmitPower(MICROBIT_BLE_DEFAULT_TX_POWER);
// Bring up any configured auxiliary services.
#if CONFIG_ENABLED(MICROBIT_BLE_DFU_SERVICE)
@ -255,14 +291,14 @@ void MicroBitBLEManager::init(ManagedString deviceName, ManagedString serialNumb
// If whiltelisting is disabled, then we always advertise.
#if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST)
if (whitelist.size > 0)
#endif
#endif
ble->startAdvertising();
}
/**
* Change the output power level of the transmitter to the given value.
*
* @param power a value in the range 0..7, where 0 is the lowest power and 7 is the highest.
* @param power a value in the range 0..7, where 0 is the lowest power and 7 is the highest.
* @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the value is out of range.
*
*/
@ -299,7 +335,7 @@ int MicroBitBLEManager::getBondCount()
*/
void MicroBitBLEManager::pairingRequested(ManagedString passKey)
{
// Update our mode to display the passkey.
// Update our mode to display the passkey.
this->passKey = passKey;
this->pairingStatus = MICROBIT_BLE_PAIR_REQUEST;
}
@ -416,7 +452,8 @@ void MicroBitBLEManager::pairingMode(MicroBitDisplay &display)
{
MicroBitImage tick("0,0,0,0,0\n0,0,0,0,255\n0,0,0,255,0\n255,0,255,0,0\n0,255,0,0,0\n");
display.print(tick,0,0,0);
uBit.sleep(5000);
uBit.sleep(15000);
timeInPairingMode = MICROBIT_BLE_PAIRING_TIMEOUT * 30;
/*
* Disabled, as the API to return the number of active bonds is not reliable at present...