BLE demo for the Cycling Power service.

Dependencies:   BLE_API mbed nRF51822

CyclingPowerService.h

Committer:
p3miya
Date:
2016-02-27
Revision:
0:d9d7edb1ddfc
Child:
1:e2e4def8a6bb

File content as of revision 0:d9d7edb1ddfc:

/*
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__*/