Demo program of a simple BLE temperature gateway that scans for temperature broadcasts from beacons while simultaneously acting as a peripheral.

Dependencies:   BLE_API mbed nRF51822

Files at this revision

API Documentation at this revision

Comitter:
andresag
Date:
Mon Oct 05 12:59:40 2015 +0000
Commit message:
Initial version of a simple BLE temperature gateway that scans temperature broadcasts while simultaneously acting as a peripheral.

Changed in this revision

BLE_API.lib Show annotated file Show diff for this revision Revisions of this file
TMP_nrf51/TMP_nrf51.cpp Show annotated file Show diff for this revision Revisions of this file
TMP_nrf51/TMP_nrf51.h Show annotated file Show diff for this revision Revisions of this file
TemperatureGatewayService.h Show annotated file Show diff for this revision Revisions of this file
TemperatureTable.h Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed.bld Show annotated file Show diff for this revision Revisions of this file
nRF51822.lib Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/BLE_API.lib	Mon Oct 05 12:59:40 2015 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/#d494ad3e87bd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TMP_nrf51/TMP_nrf51.cpp	Mon Oct 05 12:59:40 2015 +0000
@@ -0,0 +1,31 @@
+/* mbed Microcontroller Library
+ * Copyright (c) 2006-2015 ARM Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TMP_nrf51.h"
+#include "nrf_soc.h" // for internal thermometer sensor
+
+/**
+ * @brief Get the temperature value.
+ *
+ * @return Die temperature in °C
+ */
+TMP_nrf51::TempSensorValue_t TMP_nrf51::get(void)
+{
+    int32_t p_temp;
+    sd_temp_get(&p_temp);
+
+    return ((TempSensorValue_t)p_temp * 0.25); /* 0.25 is temperature sensor resolution */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TMP_nrf51/TMP_nrf51.h	Mon Oct 05 12:59:40 2015 +0000
@@ -0,0 +1,37 @@
+/* mbed Microcontroller Library
+ * Copyright (c) 2006-2015 ARM Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef TMP_NRF51_H
+#define TMP_NRF51_H
+
+#include "mbed.h"
+
+//!Library for the nrf51822 internal temperature sensor.
+
+class TMP_nrf51
+{
+public:
+    typedef float TempSensorValue_t;
+
+    /**
+    * @brief Get the temperature value.
+    *
+    * @return Die temperature in °C.
+    */
+    TempSensorValue_t get(void);
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TemperatureGatewayService.h	Mon Oct 05 12:59:40 2015 +0000
@@ -0,0 +1,108 @@
+/* mbed Microcontroller Library
+ * Copyright (c) 2006-2015 ARM Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __BLE_TEMP_GATEWAY_SERVICE_H__
+#define __BLE_TEMP_GATEWAY_SERVICE_H__
+
+template <unsigned MAX_BEACONS>
+class TemperatureGatewayService {
+public:
+    /* UUIDs for service and characteristic. Note that the values are not meaningful,
+       but they are convenient for debugging. */
+    const static uint16_t TEMPERATURE_SERVICE_UUID              = 0xFEFF;
+    const static uint16_t TEMPERATURE_LIST_CHARACTERISTIC_UUID  = 0xFAFA;
+
+    TemperatureGatewayService(BLEDevice &_ble) : ble(_ble), tempList(), tempListChar(TEMPERATURE_LIST_CHARACTERISTIC_UUID, tempList.getPointer(), 0, MAX_BEACONS * TemperatureListRepresentation::OFFSET_PER_BEACON, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NONE)
+    {
+        GattCharacteristic *charTable[] = {&tempListChar};
+        GattService         temperatureService(TEMPERATURE_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
+        ble.addService(temperatureService);
+    }
+
+    /* Return the characteristic UUID */
+    GattAttribute::Handle_t getValueHandle() const {
+        return tempListChar.getValueHandle();
+    }
+
+    void updateTemperature(uint8_t totalBeacons, uint32_t* beaconIds, float* temperatures) {
+        size_t payloadSize = tempList.updateBytes(totalBeacons, beaconIds, temperatures);
+        /* We want to send the exact number of bytes i.e. 8 * totalBeacons
+        since each beacon has 4 bytes Id and 4 bytes temperature */
+        ble.gattServer().write(tempListChar.getValueHandle(), tempList.getPointer(), payloadSize);
+    }
+
+private:
+    /* Private internal representation for the bytes sent over BLE to the client device */
+    struct TemperatureListRepresentation {
+        static const unsigned OFFSET_PER_BEACON = 2 * sizeof(uint32_t);
+        static const unsigned MIN_PAYLOAD_SIZE = 10;
+
+        TemperatureListRepresentation(void) : bytes() {
+        }
+
+        /* Transform the TemperatureTable into plain bytes */
+        size_t updateBytes(uint8_t totalBeacons, const uint32_t* beaconIds, const float* temperatures) {
+            size_t payloadSize = totalBeacons * OFFSET_PER_BEACON * sizeof(uint8_t);
+            if (payloadSize < MIN_PAYLOAD_SIZE) {
+                payloadSize = MIN_PAYLOAD_SIZE;
+            }
+
+            /* Copy each beacon key-value pair into the array */
+            for (uint32_t i = 0; i < totalBeacons; i++) {
+                    memcpy(&bytes[i * OFFSET_PER_BEACON], &beaconIds[i], sizeof(uint32_t));
+                    uint32_t temp_ieee11073 = quick_ieee11073_from_float(temperatures[i]);
+                    memcpy(&bytes[i * OFFSET_PER_BEACON + sizeof(uint32_t)], &temp_ieee11073 , sizeof(uint32_t));
+            }
+            return payloadSize;
+        }
+
+        uint8_t *getPointer(void) {
+            return bytes;
+        }
+
+        const uint8_t *getPointer(void) const {
+            return bytes;
+        }
+
+private:
+        /**
+         * @brief A very quick conversion between a float temperature and 11073-20601 FLOAT-Type.
+         * @param temperature The temperature as a float.
+         * @return The temperature in 11073-20601 FLOAT-Type format.
+         */
+        uint32_t quick_ieee11073_from_float(float temperature) {
+            uint8_t  exponent = 0xFE; //exponent is -2
+            uint32_t mantissa = (uint32_t)(temperature * 100);
+
+            return (((uint32_t)exponent) << 24) | mantissa;
+        }
+
+private:
+        /* This array contains the actual bytes to be sent over BLE. However, we send as much data
+           as we need to, not the whole array (unless we reached max capacity.
+           The data is organised as follows:
+                BeaconId0 - Temperature0 - BeaconId1 - Temperature1 -.....- BeaconIdN - TemperatureN
+           Note that each item is composed of 4 bytes. */
+        uint8_t bytes[MAX_BEACONS * OFFSET_PER_BEACON];
+    };
+
+private:
+    BLEDevice                                    &ble;
+    TemperatureListRepresentation                tempList;
+    GattCharacteristic                           tempListChar;
+};
+
+#endif /* #ifndef __BLE_TEMP_GATEWAY_SERVICE_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TemperatureTable.h	Mon Oct 05 12:59:40 2015 +0000
@@ -0,0 +1,76 @@
+/* mbed Microcontroller Library
+ * Copyright (c) 2006-2015 ARM Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __BLE_TEMPERATURE_TABLE_H__
+#define __BLE_TEMPERATURE_TABLE_H__
+
+template <typename BeaconIDType, typename TemperatureType, unsigned MAX_SIZE>
+class TemperatureTable {
+public:
+    TemperatureTable(void) : totalBeacons(0), hasUpdatedData(true), tempValues(), beaconIds() {
+        /* empty */
+    }
+
+    /* Add temperature to the table IF AND ONLY IF the data is not already there */
+    void addBeacon(BeaconIDType beaconId, TemperatureType temperature) {
+        for (unsigned i = 0; i < totalBeacons; i++) {
+            if (beaconId == beaconIds[i]) {
+                if (tempValues[i] != temperature) { /* Update old temperature value */
+                    tempValues[i]  = temperature;
+                    hasUpdatedData = true;
+                }
+                return;
+            }
+        }
+
+        /* The beacon doesn't exist in the table; add an entry if there is space. */
+        if (totalBeacons < MAX_SIZE) {
+            beaconIds[totalBeacons]  = beaconId;
+            tempValues[totalBeacons] = temperature;
+            totalBeacons++;
+            hasUpdatedData = true;
+        }
+    }
+
+    
+    TemperatureType *getTemperatures(void) {
+        return tempValues;
+    }
+
+    size_t getTotalBeacons(void) const {
+        return totalBeacons;
+    }
+
+    BeaconIDType *getBeaconIds(void) {
+        return beaconIds;
+    }
+
+    bool hasUpdate(void) const {
+        return hasUpdatedData;
+    }
+
+    void resetHasUpdate(void) {
+        hasUpdatedData = false;
+    }
+
+private:
+    size_t          totalBeacons;
+    bool            hasUpdatedData;
+    TemperatureType tempValues[MAX_SIZE];
+    BeaconIDType    beaconIds[MAX_SIZE];
+};
+
+#endif /* #ifndef __BLE_TEMPERATURE_TABLE_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Mon Oct 05 12:59:40 2015 +0000
@@ -0,0 +1,130 @@
+/* mbed Microcontroller Library
+ * Copyright (c) 2006-2015 ARM Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mbed.h"
+#include "ble/BLE.h"
+#include "ble/services/BatteryService.h"
+#include "ble/services/DeviceInformationService.h"
+
+#include "toolchain.h"
+#include "TMP_nrf51/TMP_nrf51.h"
+#include "TemperatureGatewayService.h"
+#include "TemperatureTable.h"
+
+#define MAX_BEACONS 10
+
+BLE  ble;
+DigitalOut led1(LED1);
+
+TemperatureGatewayService<MAX_BEACONS>* tempGatewayServicePtr;
+TemperatureTable<uint32_t, float, MAX_BEACONS> temperatureTable;
+
+/* Setup device name and new service information */
+const static char     DEVICE_NAME[]        = "TGW1";
+static const uint16_t uuid16_list[]        = {GattService::UUID_HEALTH_THERMOMETER_SERVICE,
+                                              GattService::UUID_DEVICE_INFORMATION_SERVICE};
+
+void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
+{
+    ble.gap().startAdvertising(); // restart advertising
+}
+
+void periodicCallback(void)
+{
+    led1 = !led1; /* Do blinky on LED1 while we're waiting for BLE events */
+}
+
+void advertisementCallback(const Gap::AdvertisementCallbackParams_t *params) {
+
+    struct AdvertisingData_t {
+        uint8_t                        length; /* doesn't include itself */
+        GapAdvertisingData::DataType_t dataType;
+        uint8_t                        data[0];
+    } PACKED;
+
+    struct ApplicationData_t {
+        uint16_t applicationSpecificId;             /* An ID used to identify temperature value
+                                                       in the manufacture specific AD data field */
+        TMP_nrf51::TempSensorValue_t tmpSensorValue; /* User defined application data */
+    } PACKED;
+
+    /* This ID is not very meaningful, but makes it easy to find the device for debugging */
+    static const uint16_t APP_SPECIFIC_ID_TEST = 0xFEFE;
+
+    /* Search for the manufacturer specific data with matching application-ID */
+    AdvertisingData_t *pAdvData;
+    for (size_t index = 0; index < params->advertisingDataLen; index += (pAdvData->length + 1)) {
+        pAdvData = (AdvertisingData_t *)&params->advertisingData[index];
+
+        if (pAdvData->dataType != GapAdvertisingData::MANUFACTURER_SPECIFIC_DATA) {
+            continue;
+        }
+
+        const ApplicationData_t *pAppData = (const ApplicationData_t *)pAdvData->data;
+        if (pAppData->applicationSpecificId == APP_SPECIFIC_ID_TEST) {
+            uint32_t beaconId = (params->peerAddr[3] << 24) | (params->peerAddr[2] << 16) | (params->peerAddr[1] << 8)  | (params->peerAddr[0]);
+            TMP_nrf51::TempSensorValue_t temperature = (TMP_nrf51::TempSensorValue_t)pAppData->tmpSensorValue;
+
+            /* If new data was added to the table, then update the payload */
+            temperatureTable.addBeacon(beaconId, temperature);
+            if (temperatureTable.hasUpdate()) {
+                tempGatewayServicePtr->updateTemperature(temperatureTable.getTotalBeacons(), temperatureTable.getBeaconIds(), temperatureTable.getTemperatures());
+                temperatureTable.resetHasUpdate();
+            }
+            break;
+        }
+    }
+}
+
+void configureScanTemperature(void)
+{
+    ble.gap().setScanParams(500 /* scan interval */, 200 /* scan window */);
+    ble.gap().startScan(advertisementCallback);
+}
+
+int main(void)
+{
+    /* Blink LED every second to tell whether the app is running */
+    led1 = 1;
+    Ticker ticker;
+    ticker.attach(periodicCallback, 1);
+
+    ble.init();
+
+    configureScanTemperature();
+
+    ble.gap().onDisconnection(disconnectionCallback);
+
+    /* Setup primary service. */
+    TemperatureGatewayService<MAX_BEACONS> temperatureGatewayService(ble);
+    tempGatewayServicePtr = &temperatureGatewayService;
+
+    /* Setup auxiliary service. */
+    DeviceInformationService deviceInfo(ble, "ARM", "Model1", "SN1", "hw-rev1", "fw-rev1", "soft-rev1");
+
+    /* Setup advertising. */
+    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
+    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
+    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
+    ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
+    ble.gap().setAdvertisingInterval(1000); /* 1000ms */
+    ble.gap().startAdvertising();
+
+    /* infinite loop */
+    while (1) {
+        ble.waitForEvent(); // low power wait for event
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed.bld	Mon Oct 05 12:59:40 2015 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/mbed_official/code/mbed/builds/4f6c30876dfa
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nRF51822.lib	Mon Oct 05 12:59:40 2015 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/teams/Nordic-Semiconductor/code/nRF51822/#088f5738bf18