BLE demo for the Cycling Power service.

Dependencies:   BLE_API mbed nRF51822

Revision:
0:d9d7edb1ddfc
Child:
1:e2e4def8a6bb
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CyclingPowerService.h	Sat Feb 27 12:27:48 2016 +0000
@@ -0,0 +1,268 @@
+/*
+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_POWER_SERVICE_H__
+#define __BLE_CYCLING_POWER_SERVICE_H__
+
+#include "ble/BLE.h"
+
+#define UUID_CYCLING_POWER_SERVICE            0x1818
+#define UUID_SENSOR_LOCATION_CHAR             0x2A5D
+#define UUID_CYCLING_POWER_MEASUREMENT_CHAR   0x2A63
+#define UUID_CYCLING_POWER_FEATURE_CHAR       0x2A65
+#define UUID_CYCLING_POWER_VECTOR_CHAR        0x2A64
+#define UUID_CYCLING_POWER_CONTROL_POINT_CHAR 0x2A66
+
+
+/**
+* @class CyclingPowerService
+* @brief BLE Service for CyclingPower. <br>
+* Service:  https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.cycling_power.xml <br>
+* CyclingPower Char: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.cycling_power_measurement.xml <br>
+* Location: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.sensor_location.xml
+*/
+class CyclingPowerService {
+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_PEDAL_POWER_BALANCE                 = 0x00000001,
+        FEATURE_ACCUMULATED_TORQUE                  = 0x00000002,
+        FEATURE_WHEEL_REVOLUTION_DATA               = 0x00000004,
+        FEATURE_CRANK_REVOLUTION_DATA               = 0x00000008,
+        FEATURE_EXTREME_MAGNITUDES                  = 0x00000010,
+        FEATURE_EXTREME_ANGLES                      = 0x00000020,
+        FEATURE_TOP_AND_BOTTOM_DEAD_SPOT_ANGLES     = 0x00000040,
+        FEATURE_ACCUMULATED_ENERGY                  = 0x00000080,
+        FEATURE_OFFSET_COMPENSATION_INDICATOR       = 0x00000100,
+        FEATURE_OFFSET_COMPENSATION                 = 0x00000200,
+        FEATURE_MEASUREMENT_CONTENT_MASKING         = 0x00000400,
+        FEATURE_MULTIPLE_SENSOR_LOCATIONS           = 0x00000800,
+        FEATURE_CRANK_LENGTH_ADJUSTMENT             = 0x00001000,
+        FEATURE_CHAIN_LENGTH_ADJUSTMENT             = 0x00002000,
+        FEATURE_CHAIN_WEIGHT_ADJUSTMENT             = 0x00004000,
+        FEATURE_SPAN_LENGTH_ADJUSTMENT              = 0x00008000,
+        FEATURE_SENSOR_MEASUREMENT_TORQUE_BASED     = 0x00010000,
+        FEATURE_INSTANTANEOUS_MEASUREMENT_DIRECTION = 0x00020000,
+        FEATURE_FACTORY_CALIBRATION_DATE            = 0x00040000,
+    };
+
+    enum {
+        FLAG_PEDAL_POWER_BALANCE_PRESENT           = 0x0001,
+        FLAG_PEDAL_POWER_BALANCE_REFERENCE_LEFT    = 0x0002,
+        FLAG_ACCUMULATED_TORQUE_PRESENT            = 0x0004,
+        FLAG_ACCUMULATED_TORQUE_SOURCE_CRANK_BASED = 0x0008,
+        FLAG_WHEEL_REVOLUTION_DATA_PRESENT         = 0x0010,
+        FLAG_CRANK_REVOLUTION_DATA_PRESENT         = 0x0020,
+        FLAG_EXTREME_FORCE_MAGNITUDES_PRESENT      = 0x0040,
+        FLAG_EXTREME_TORQUE_MAGNITUDES_PRESENT     = 0x0080,
+        FLAG_EXTREME_ANGLES_PRESENT                = 0x0100,
+        FLAG_TOP_DEAD_SPOT_ANGLE_PRESENT           = 0x0200,
+        FLAG_BOTTOM_DEAD_SPOT_ANGLE_PRESENT        = 0x0400,
+        FLAG_ACCUMULATED_ENERGY_PRESENT            = 0x0800,
+        FLAG_OFFSET_COMPENSATION_INDICATOR         = 0x1000,
+    };
+
+    enum {
+        OPCODE_SET_CUMULATIVE_VALUE               =  1,
+        OPCODE_UPDATE_SENSOR_LOCATION             =  2,
+        OPCODE_REQUEST_SUPPORTED_SENSOR_LOCATIONS =  3,
+        OPCODE_SET_CRANK_LENGTH                   =  4,
+        OPCODE_REQUEST_CRANK_LENGTH               =  5,
+        OPCODE_SET_CHAIN_LENGTH                   =  6,
+        OPCODE_REQUEST_CHAIN_LENGTH               =  7,
+        OPCODE_SET_CHAIN_WEIGHT                   =  8,
+        OPCODE_REQUEST_CHAIN_WEIGHT               =  9,
+        OPCODE_SET_SPAN_LENGTH                    = 10,
+        OPCODE_REQUEST_SPAN_LENGTH                = 11,
+        OPCODE_START_OFFSET_COMPENSATION          = 12,
+        OPCODE_MASK_CYCLING_POWER_MEASUREMENT_CHARACTERISTIC_CONTENT = 13,
+        OPCODE_REQUEST_SAMPLING_RATE              = 14,
+        OPCODE_REQUEST_FACTORY_CALIBRATION_DATE   = 15,
+        OPCODE_RESPONSE_CODE                      = 32,
+    };
+
+    enum {
+        RESPONSE_SUCCESS = 1,
+        RESPONSE_OP_CODE_NOT_SUPPORTED,
+        RESPONSE_INVALID_PARAMETER,
+        RESPONSE_OPERATION_FAILED,
+    };
+
+public:
+    CyclingPowerService(BLE &_ble, uint32_t feature, uint8_t location) :
+        ble(_ble),
+        cyclingPowerMeasurement(UUID_CYCLING_POWER_MEASUREMENT_CHAR, measureData,
+                       MAX_MEASURE_BYTES, MAX_MEASURE_BYTES,
+                       GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY),
+        cyclingPowerControlPoint(UUID_CYCLING_POWER_CONTROL_POINT_CHAR, controlData,
+                       MAX_CONTROL_BYTES, MAX_CONTROL_BYTES,
+                       GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_INDICATE),
+        cyclingPowerFeature(UUID_CYCLING_POWER_FEATURE_CHAR, &feature),
+        cyclingPowerLocation(UUID_SENSOR_LOCATION_CHAR, &location) {
+        setupService();
+    }
+
+    void updateCyclingPower(uint16_t flags, int16_t pwr, uint8_t bal, int16_t maxTorque, int16_t minTorque, uint16_t dnw, uint16_t dtw, uint16_t dnc, uint16_t dtc) {
+        instantaneousPower = pwr;
+        pedalPowerBalance  = bal;
+        maximumTorqueMagnitude = maxTorque;
+        minimumTorqueMagnitude = minTorque;
+
+        cumulativeWheelRev += dnw;
+        lastWheelEventTime += dtw;
+        cumulativeCrankRev += dnc;
+        lastCrankEventTime += dtc;
+
+        unsigned i = 0;
+        measureData[i++] = (uint8_t)(flags & 0xFF);
+        measureData[i++] = (uint8_t)(flags >> 8);
+
+        measureData[i++] = (uint8_t)(instantaneousPower & 0xFF);
+        measureData[i++] = (uint8_t)(instantaneousPower >> 8);
+
+        if (flags & FLAG_PEDAL_POWER_BALANCE_PRESENT) {
+            measureData[i++] = pedalPowerBalance;
+        }
+        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);
+        }
+        if (flags & FLAG_EXTREME_TORQUE_MAGNITUDES_PRESENT) {
+            measureData[i++] = (uint8_t)(maximumTorqueMagnitude & 0xFF);
+            measureData[i++] = (uint8_t)(maximumTorqueMagnitude >> 8);
+
+            measureData[i++] = (uint8_t)(minimumTorqueMagnitude & 0xFF);
+            measureData[i++] = (uint8_t)(minimumTorqueMagnitude >> 8);
+        }
+        unsigned nbyte = i;
+        ble.updateCharacteristicValue(cyclingPowerMeasurement.getValueAttribute().getHandle(), measureData, nbyte);
+    }
+
+    void indicateResponse(uint8_t opCode, uint8_t responseValue) {
+        controlData[0] = 0x20;    // Response Code
+        controlData[1] = opCode;
+        controlData[2] = responseValue;
+        unsigned nbyte = 3;
+        ble.updateCharacteristicValue(cyclingPowerControlPoint.getValueAttribute().getHandle(), controlData, nbyte);
+    }
+
+    void indicateResponse(uint8_t opCode, uint8_t responseValue, uint16_t responseParameter) {
+        controlData[0] = 0x20;    // Response Code
+        controlData[1] = opCode;
+        controlData[2] = responseValue;
+        controlData[3] = (uint8_t)(responseParameter & 0xFF);
+        controlData[4] = (uint8_t)(responseParameter >> 8);
+        unsigned nbyte = 5;
+        ble.updateCharacteristicValue(cyclingPowerControlPoint.getValueAttribute().getHandle(), controlData, nbyte);
+    }
+
+    virtual void onDataWritten(const GattWriteCallbackParams *params) {
+        if (params->handle == cyclingPowerControlPoint.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, CyclingPowerService::RESPONSE_SUCCESS);
+                break;
+            default:
+                indicateResponse(opCode, CyclingPowerService::RESPONSE_OP_CODE_NOT_SUPPORTED);
+            }
+        }
+    }
+
+protected:
+    void setupService(void) {
+        instantaneousPower = 0;
+        pedalPowerBalance = 50 * 2;   // 50%
+        maximumTorqueMagnitude = 0;
+        minimumTorqueMagnitude = 0;
+
+        cumulativeWheelRev = 0;
+        lastWheelEventTime = 0;
+        cumulativeCrankRev = 0;
+        lastCrankEventTime = 0;
+
+        GattCharacteristic *charTable[] = {&cyclingPowerMeasurement, &cyclingPowerFeature, &cyclingPowerLocation, &cyclingPowerControlPoint};
+        GattService         cyclingPowerService(UUID_CYCLING_POWER_SERVICE, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
+
+        ble.addService(cyclingPowerService);
+        ble.onDataWritten(this, &CyclingPowerService::onDataWritten);
+    }
+
+protected:
+    BLE &ble;
+
+    GattCharacteristic                   cyclingPowerMeasurement;
+    GattCharacteristic                   cyclingPowerControlPoint;
+    ReadOnlyGattCharacteristic<uint32_t> cyclingPowerFeature;
+    ReadOnlyGattCharacteristic<uint8_t>  cyclingPowerLocation;
+
+    static const unsigned MAX_MEASURE_BYTES  = 19;
+    static const unsigned MAX_CONTROL_BYTES  = 5;
+
+    uint8_t measureData[MAX_MEASURE_BYTES];
+    uint8_t controlData[MAX_CONTROL_BYTES];
+
+    int16_t instantaneousPower;
+    uint8_t pedalPowerBalance;
+    int16_t maximumTorqueMagnitude;
+    int16_t minimumTorqueMagnitude;
+
+    uint32_t cumulativeWheelRev;
+    uint16_t lastWheelEventTime;
+    uint16_t cumulativeCrankRev;
+    uint16_t lastCrankEventTime;
+};
+
+#endif /* #ifndef __BLE_CYCLING_POWER_SERVICE_H__*/