BLE demo for the Cycling Speed and Cadence service.
Dependencies: BLE_API mbed nRF51822
Revision 0:ea0fa3076efd, committed 2016-03-10
- 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
--- /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