BLE demo for the Cycling Speed and Cadence service.

Dependencies:   BLE_API mbed nRF51822

Files at this revision

API Documentation at this revision

Comitter:
p3miya
Date:
Thu Mar 10 13:09:47 2016 +0000
Commit message:
BLE demo for the Cycling Speed and Cadence service.

Changed in this revision

BLE_API.lib Show annotated file Show diff for this revision Revisions of this file
CyclingSpeedAndCadenceService.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	Thu Mar 10 13:09:47 2016 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/#ff83f0020480
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CyclingSpeedAndCadenceService.h	Thu Mar 10 13:09:47 2016 +0000
@@ -0,0 +1,196 @@
+/*
+Copyright (c) 2016 Y. Miyakawa
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
+and associated documentation files (the "Software"), to deal in the Software without restriction, 
+including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
+and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
+subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
+FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef __BLE_CYCLING_SPEED_AND_CADENCE_SERVICE_H__
+#define __BLE_CYCLING_SPEED_AND_CADENCE_SERVICE_H__
+
+#include "ble/BLE.h"
+
+#define UUID_SENSOR_LOCATION_CHAR   0x2A5D
+#define UUID_CSC_CONTROL_POINT_CHAR 0x2A55
+
+
+/**
+* @class CyclingSpeedAndCadenceService
+* @brief BLE Service for Cycling Speed and Cadence. <br>
+* Service:  https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.cycling_speed_and_cadence.xml <br>
+* CyclingSpeedAndCadenceService Char: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.csc_measurement.xml <br>
+* Location: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.sensor_location.xml
+*/
+class CyclingSpeedAndCadenceService {
+public:
+    enum {
+        LOCATION_OTHER = 0,
+        LOCATION_TOP_OF_SHOE,
+        LOCATION_IN_SHOE,
+        LOCATION_HIP,
+        LOCATION_FRONT_WHEEL,
+        LOCATION_LEFT_CRANK,
+        LOCATION_RIGHT_CRANK,
+        LOCATION_LEFT_PEDAL,
+        LOCATION_RIGHT_PEDAL,
+        LOCATION_FRONT_HUB,
+        LOCATION_REAR_DROPOUT,
+        LOCATION_CHAINSTAY,
+        LOCATION_REAR_WHEEL,
+        LOCATION_REAR_HUB,
+        LOCATION_CHEST,
+    };
+
+    enum {
+        FEATURE_WHEEL_REVOLUTION_DATA      = 0x0001,
+        FEATURE_CRANK_REVOLUTION_DATA      = 0x0002,
+        FEATURE_MULTIPLE_SENSOR_LOCATIONS  = 0x0004,
+    };
+
+    enum {
+        FLAG_WHEEL_REVOLUTION_DATA_PRESENT = 0x01,
+        FLAG_CRANK_REVOLUTION_DATA_PRESENT = 0x02,
+    };
+
+    enum {
+        OPCODE_SET_CUMULATIVE_VALUE               =  1,
+        OPCODE_START_SENSOR_CALIBRATION           =  2,
+        OPCODE_UPDATE_SENSOR_LOCATION             =  3,
+        OPCODE_REQUEST_SUPPORTED_SENSOR_LOCATIONS =  4,
+        OPCODE_RESPONSE_CODE                      = 16,
+    };
+
+    enum {
+        RESPONSE_SUCCESS               = 1,
+        RESPONSE_OP_CODE_NOT_SUPPORTED = 2,
+        RESPONSE_INVALID_PARAMETER     = 3,
+        RESPONSE_OPERATION_FAILED      = 4,
+    };
+
+public:
+    CyclingSpeedAndCadenceService(BLE &_ble, uint16_t feature, uint8_t location) :
+        ble(_ble),
+        cyclingSpeedAndCadenceMeasurement(GattCharacteristic::UUID_CSC_MEASUREMENT_CHAR, measureData,
+                       MAX_MEASURE_BYTES, MAX_MEASURE_BYTES,
+                       GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY),
+        cyclingSpeedAndCadenceControlPoint(UUID_CSC_CONTROL_POINT_CHAR, controlData,
+                       MAX_CONTROL_BYTES, MAX_CONTROL_BYTES,
+                       GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_INDICATE),
+        cyclingSpeedAndCadenceFeature(GattCharacteristic::UUID_CSC_FEATURE_CHAR, &feature),
+        cyclingSpeedAndCadenceLocation(UUID_SENSOR_LOCATION_CHAR, &location) {
+        setupService();
+    }
+
+    void updateCyclingSpeedAndCadence(uint8_t flags, uint16_t dnw, uint16_t dtw, uint16_t dnc, uint16_t dtc) {
+        cumulativeWheelRev += dnw;
+        lastWheelEventTime += dtw;
+        cumulativeCrankRev += dnc;
+        lastCrankEventTime += dtc;
+
+        unsigned i = 0;
+        measureData[i++] = flags;
+
+        if (flags & FLAG_WHEEL_REVOLUTION_DATA_PRESENT) {
+            measureData[i++] = (uint8_t)(cumulativeWheelRev & 0xFF);
+            measureData[i++] = (uint8_t)((cumulativeWheelRev >>  8) & 0xFF);
+            measureData[i++] = (uint8_t)((cumulativeWheelRev >> 16) & 0xFF);
+            measureData[i++] = (uint8_t)((cumulativeWheelRev >> 24) & 0xFF);
+
+            measureData[i++] = (uint8_t)(lastWheelEventTime & 0xFF);
+            measureData[i++] = (uint8_t)(lastWheelEventTime >> 8);
+        }
+        if (flags & FLAG_CRANK_REVOLUTION_DATA_PRESENT) {
+            measureData[i++] = (uint8_t)(cumulativeCrankRev & 0xFF);
+            measureData[i++] = (uint8_t)(cumulativeCrankRev >> 8);
+
+            measureData[i++] = (uint8_t)(lastCrankEventTime & 0xFF);
+            measureData[i++] = (uint8_t)(lastCrankEventTime >> 8);
+        }
+        unsigned nbyte = i;
+        ble.updateCharacteristicValue(cyclingSpeedAndCadenceMeasurement.getValueAttribute().getHandle(), measureData, nbyte);
+    }
+
+    void indicateResponse(uint8_t opCode, uint8_t responseValue) {
+        controlData[0] = OPCODE_RESPONSE_CODE;
+        controlData[1] = opCode;
+        controlData[2] = responseValue;
+        unsigned nbyte = 3;
+        ble.updateCharacteristicValue(cyclingSpeedAndCadenceControlPoint.getValueAttribute().getHandle(), controlData, nbyte);
+    }
+
+    void indicateResponse(uint8_t opCode, uint8_t responseValue, uint8_t responseParameter) {
+        controlData[0] = OPCODE_RESPONSE_CODE;
+        controlData[1] = opCode;
+        controlData[2] = responseValue;
+        controlData[3] = responseParameter;
+        unsigned nbyte = 4;
+        ble.updateCharacteristicValue(cyclingSpeedAndCadenceControlPoint.getValueAttribute().getHandle(), controlData, nbyte);
+    }
+
+    virtual void onDataWritten(const GattWriteCallbackParams *params) {
+        if (params->handle == cyclingSpeedAndCadenceControlPoint.getValueAttribute().getHandle()) {
+            uint32_t uu;
+            uint8_t opCode = params->data[0];
+            switch(opCode) {
+            case OPCODE_SET_CUMULATIVE_VALUE:
+                uu  = params->data[4];
+                uu *= 256;
+                uu += params->data[3];
+                uu *= 256;
+                uu += params->data[2];
+                uu *= 256;
+                uu += params->data[1];
+                cumulativeWheelRev = uu;
+                indicateResponse(opCode, CyclingSpeedAndCadenceService::RESPONSE_SUCCESS);
+                break;
+            default:
+                indicateResponse(opCode, CyclingSpeedAndCadenceService::RESPONSE_OP_CODE_NOT_SUPPORTED);
+            }
+        }
+    }
+
+protected:
+    void setupService(void) {
+        cumulativeWheelRev = 0;
+        lastWheelEventTime = 0;
+        cumulativeCrankRev = 0;
+        lastCrankEventTime = 0;
+
+        GattCharacteristic *charTable[] = {&cyclingSpeedAndCadenceMeasurement, &cyclingSpeedAndCadenceFeature, &cyclingSpeedAndCadenceLocation, &cyclingSpeedAndCadenceControlPoint};
+        GattService         cyclingSpeedAndCadenceService(GattService::UUID_CYCLING_SPEED_AND_CADENCE, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
+
+        ble.addService(cyclingSpeedAndCadenceService);
+        ble.onDataWritten(this, &CyclingSpeedAndCadenceService::onDataWritten);
+    }
+
+protected:
+    BLE &ble;
+
+    GattCharacteristic                   cyclingSpeedAndCadenceMeasurement;
+    GattCharacteristic                   cyclingSpeedAndCadenceControlPoint;
+    ReadOnlyGattCharacteristic<uint16_t> cyclingSpeedAndCadenceFeature;
+    ReadOnlyGattCharacteristic<uint8_t>  cyclingSpeedAndCadenceLocation;
+
+    static const unsigned MAX_MEASURE_BYTES  = 11;
+    static const unsigned MAX_CONTROL_BYTES  = 4;
+
+    uint8_t measureData[MAX_MEASURE_BYTES];
+    uint8_t controlData[MAX_CONTROL_BYTES];
+
+    uint32_t cumulativeWheelRev;
+    uint16_t lastWheelEventTime;
+    uint16_t cumulativeCrankRev;
+    uint16_t lastCrankEventTime;
+};
+
+#endif /* #ifndef __BLE_CYCLING_SPEED_AND_CADENCE_SERVICE_H__*/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Thu Mar 10 13:09:47 2016 +0000
@@ -0,0 +1,129 @@
+/*
+Copyright (c) 2016 Y. Miyakawa
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
+and associated documentation files (the "Software"), to deal in the Software without restriction, 
+including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
+and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
+subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
+FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "mbed.h"
+#include "ble/BLE.h"
+#include "CyclingSpeedAndCadenceService.h"
+#include "ble/services/DeviceInformationService.h"
+
+DigitalOut led1(LED1);
+
+const static char     DEVICE_NAME[] = "CSC1";
+static const uint16_t uuid16_list[] = {GattService::UUID_CYCLING_SPEED_AND_CADENCE,
+                                       GattService::UUID_DEVICE_INFORMATION_SERVICE};
+
+static volatile bool triggerSensorPolling = false;
+
+CyclingSpeedAndCadenceService *cyclingSpeedAndCadenceService;
+DeviceInformationService *deviceInfo;
+
+uint16_t feature = CyclingSpeedAndCadenceService::FEATURE_WHEEL_REVOLUTION_DATA
+                 + CyclingSpeedAndCadenceService::FEATURE_CRANK_REVOLUTION_DATA;
+
+uint8_t flags = CyclingSpeedAndCadenceService::FLAG_WHEEL_REVOLUTION_DATA_PRESENT
+              + CyclingSpeedAndCadenceService::FLAG_CRANK_REVOLUTION_DATA_PRESENT;
+
+uint8_t location = CyclingSpeedAndCadenceService::LOCATION_CHAINSTAY;
+
+void connectionCallback(const Gap::ConnectionCallbackParams_t *params)
+{
+}
+
+void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
+{
+    triggerSensorPolling = false;
+    BLE::Instance(BLE::DEFAULT_INSTANCE).gap().startAdvertising(); // restart advertising
+}
+
+void confirmationRcvCallBack(Gap::Handle_t handle)
+{
+}
+
+void periodicCallback(void)
+{
+    led1 = !led1; /* Do blinky on LED1 while we're waiting for BLE events */
+
+    /* Note that the periodicCallback() executes in interrupt context, so it is safer to do
+     * heavy-weight sensor polling from the main thread. */
+    triggerSensorPolling = true;
+}
+
+void bleInitComplete(BLE::InitializationCompleteCallbackContext *params)
+{
+    BLE &ble          = params->ble;
+    ble_error_t error = params->error;
+
+    if (error != BLE_ERROR_NONE) {
+        return;
+    }
+
+    ble.gap().onConnection(connectionCallback);
+    ble.gap().onDisconnection(disconnectionCallback);
+    ble.onConfirmationReceived(confirmationRcvCallBack);
+
+    /* Setup primary service. */
+    cyclingSpeedAndCadenceService = new CyclingSpeedAndCadenceService(ble, feature, location);
+
+    /* Setup auxiliary service. */
+    deviceInfo = new DeviceInformationService(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::GENERIC_HEART_RATE_SENSOR);
+    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();
+}
+
+int main(void)
+{
+    uint16_t dCumulativeWheelRev =    4;
+    uint16_t dLastWheelEventTime = 1024;
+    uint16_t dCumulativeCrankRev =    1;
+    uint16_t dLastCrankEventTime = 1024;
+
+    led1 = 1;
+
+    Ticker ticker;
+    ticker.attach(periodicCallback, 1); // blink LED every second
+
+    BLE& ble = BLE::Instance(BLE::DEFAULT_INSTANCE);
+    ble.init(bleInitComplete);
+
+    /* SpinWait for initialization to complete. This is necessary because the
+     * BLE object is used in the main loop below. */
+    while (ble.hasInitialized() == false) { /* spin loop */ }
+
+    // infinite loop
+    while(1) {
+        if (triggerSensorPolling && ble.getGapState().connected) {
+            triggerSensorPolling = false;
+            dLastWheelEventTime -= 32;
+            dLastCrankEventTime -= 32;
+            if (dLastWheelEventTime < 512) {
+                dLastWheelEventTime = 1024;
+                dLastCrankEventTime = 1024;
+            }
+            cyclingSpeedAndCadenceService->updateCyclingSpeedAndCadence(flags, 
+                dCumulativeWheelRev, dLastWheelEventTime, dCumulativeCrankRev, dLastCrankEventTime);
+        } else {
+            ble.waitForEvent(); // low power wait for event
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed.bld	Thu Mar 10 13:09:47 2016 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/mbed_official/code/mbed/builds/87f2f5183dfb
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nRF51822.lib	Thu Mar 10 13:09:47 2016 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/teams/Nordic-Semiconductor/code/nRF51822/#1751e2e2637a