Experimental BLE Blinky Application using a LED and button service server. This is a small custom service that is used to toggle LEDs and receive button status from wirelessly connected Bluetooth Smart development boards.

For information on the GATT service UUID, characteristic UUIDs, or what to expect when manipulating them, please refer to:

http://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.sdk5.v13.0.0%2Fble_sdk_app_blinky.html http://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.sdk5.v11.0.0%2Fgroup__ble__sdk__srv__lbs.html http://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.sdk5.v11.0.0%2Fgroup__ble__sdk__srv__lbs__c.html

Good luck with that.

For an easy 5 minute tour of how Bluetooth Smart works, program a developer kit (nRF52-DK for example) with this application. Once it starts to run it will indicate advertising by blinking LED1. At this time, install a corresponding client application like the nRF Blinky Android app [1] and watch it detect the developer kit. Click on the 'Lightsw' identifier to connect with the GATT service and wait until the developer kit LED1 stops blinking to indicate a connected state. Now, illuminate LED2 by clicking on the lightbulb. Pressing one of the developer kit's buttons will cause the Android app's background to illuminate illustrating bidirectional communication over Bluetooth Smart.

[1] https://play.google.com/store/apps/details?id=no.nordicsemi.android.nrfblinky

Files at this revision

API Documentation at this revision

Comitter:
michaesc
Date:
Tue Mar 28 15:57:46 2017 +0000
Parent:
1:f8f97a4d8f02
Commit message:
Implemented Blinkyserv class and GATT derived service and characteristic logic.

Changed in this revision

source/Service.h Show annotated file Show diff for this revision Revisions of this file
source/main.cpp Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/source/Service.h	Tue Mar 28 15:57:46 2017 +0000
@@ -0,0 +1,63 @@
+/* Blinky service application
+ *
+ * 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_BUTLED_SERVICE_H__
+#define __BLE_BUTLED_SERVICE_H__
+
+class Blinkyserv {
+public:
+    // These are stale UUIDs, not corresponding to Nordic
+    //const static uint16_t LED_SERVICE_UUID = 0xA002;
+    //const static uint16_t LED_STATE_CHARACTERISTIC_UUID = 0xA004;
+    //const static uint16_t BUT_STATE_CHARACTERISTIC_UUID = 0xA005;
+
+    // https://github.com/NordicSemiconductor/Android-nRF-Blinky/tree/master/app/src/main/java/no/nordicsemi/android/blinky/service/BlinkyManager.java
+    // BaseUUID = {0x23, 0xd1, 0xbc, 0xea, 0x5f, 0x78, 0x23, 0x15, 0xde, 0xef, 0x12, 0x12, 0x23, 0x15, 0x00, 0x00};
+    //const static UUID *LED_SERVICE_UUID = new UUID("00001523-1212-efde-1523-785feabcd123");
+    //std::shared_ptr<UUID> LED_SERVICE_UUID;
+    const UUID *LED_SERVICE_UUID;
+
+    Blinkyserv(BLEDevice &_ble, bool initialValueForLEDCharacteristic, bool initialValueForButCharacteristic) :
+        ble(_ble), ledState(UUID("00001525-1212-efde-1523-785feabcd123"), &initialValueForLEDCharacteristic),
+        butState(UUID("00001524-1212-efde-1523-785feabcd123"), &initialValueForButCharacteristic, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY)
+    {
+        // Service definition logic
+        GattCharacteristic *charTable[] = {&ledState, &butState};
+        LED_SERVICE_UUID = new UUID("00001523-1212-efde-1523-785feabcd123");
+
+        GattService GATTServ(static_cast<UUID>(*LED_SERVICE_UUID), charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
+        ble.addService(GATTServ);
+    }
+
+    // FIXME: Does this need a butState equivalent?
+    GattAttribute::Handle_t getValueHandle() const {
+        return ledState.getValueHandle();
+    }
+
+    void updateButtonState(bool newState) { // Sets button state characteristic appropriately
+        //ble.gattServer().write(butState.getValueHandle(), (uint8_t *)&newState, sizeof(bool));
+        ble.updateCharacteristicValue(butState.getValueHandle(), (uint8_t *)&newState, sizeof(bool));
+    }
+
+private:
+    BLEDevice                         &ble;
+    ReadWriteGattCharacteristic<bool>  ledState;
+    ReadOnlyGattCharacteristic<bool>   butState;
+    ~Blinkyserv() {
+        delete LED_SERVICE_UUID;
+    }
+};
+
+#endif /* #ifndef __BLE_BUTLED_SERVICE_H__ */
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/source/main.cpp	Tue Mar 28 15:57:46 2017 +0000
@@ -0,0 +1,209 @@
+/* Blinky service application
+ *
+ * This Bluetooth Smart application intends to serve as a substitute
+ * for existing applications implemented using the Nordic Semiconductor
+ * proprietary nRF 5 SDK. This application interfaces with example
+ * client applications such as the preexisting 'nRF Blinky' and
+ * 'nRF52 Blinky Web App', while complying with proprietary Nordic
+ * Semiconductor service and characteristic UUIDs.
+ *
+ * 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/Gap.h"
+#include "Service.h"
+
+// FIXME: initializing has no effect!
+DigitalOut alivenessLED(LED1);
+DigitalOut actuatedLED(LED2);
+InterruptIn button(BUTTON1);
+
+enum { // Button!
+    RELEASED = 0,
+    PRESSED,
+    IDLE
+};
+
+// Stores the state of button sensor
+static uint8_t buttonState = IDLE;
+
+const static char DEVICE_NAME[] = "Lightsw"; // Seems this string has a limited maximum length?
+//static const uint16_t uuid16_list[] = {Blinkyserv::LED_SERVICE_UUID}; // Only works on short URLs
+//static const uint8_t uuid128_list[] = {0x0000, 0x1523, 0x1212, 0xefde, 0x1523, 0x785f, 0xeabc, 0xd123};
+
+Blinkyserv *blinkySvcptr;
+
+static EventQueue eventQueue(
+    /* event count */ 16 * /* event size */ 32
+);
+
+void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
+{
+    printf("Disconnected handle %u!\n", params->handle);
+    printf("Restarting the advertising process\n");
+    BLE::Instance().gap().startAdvertising();
+    actuatedLED = 1; // Reinitialize actuator,
+                     // according to svc value
+                     // initialValueForLEDCharacteristic
+}
+
+void connectionCallback(const Gap::ConnectionCallbackParams_t *params)
+{
+    printf("Connected handle %u!\n", params->handle);
+    printf("Stopping the advertising process\n");
+    BLE::Instance().gap().stopAdvertising();
+    alivenessLED = 1; // Indicate end of adverts
+}
+
+void blinkCallback(void)
+{
+    BLE &ble = BLE::Instance();
+
+    // Hack to avoid hung connections    
+    if (!ble.gap().getState().connected
+        && !ble.gap().getState().advertising) {
+        BLE::Instance().gap().startAdvertising();
+    }
+
+    // Indicate adverts by blinking a LED
+    if (ble.gap().getState().advertising) {
+        alivenessLED = !alivenessLED;
+    }
+}
+
+/**
+ * Allows the LED service to receive updates to the ledState characteristic.
+ *
+ * @param[in] params
+ *     Information about the characterisitc being updated.
+ */
+void onDataWrittenCallback(const GattWriteCallbackParams *params) {
+    if ((params->handle == blinkySvcptr->getValueHandle()) && (params->len == 1)) {
+        printf("Going to set the LED actuator!\n");
+        actuatedLED = !*(params->data); // Inverse
+    }
+}
+
+/**
+ * Allows the LED service to receive updates to the ledState characteristic.
+ */
+void onUpdatesCallback(Gap::Handle_t handle) {
+    printf("Notifications enabled for %d\n!", handle);
+}
+
+/* Callback user space helper method */
+void fall_handler_user_context(void) {
+    // Fall handler is called on button actions
+    blinkySvcptr->updateButtonState(buttonState);
+    buttonState = IDLE;
+}
+
+/* Callback user space helper method */
+void rise_handler_user_context(void) {
+    // Fall handler is called on button actions
+    blinkySvcptr->updateButtonState(buttonState);
+    buttonState = IDLE;
+}
+
+/* Note that the buttonPressedCallback() executes in interrupt context,
+ * so it is safer to access BLE device API from the main thread. For
+ * example, do not try to call printf(3) inside interrupt context! */
+void buttonPressedCallback(void)
+{
+    buttonState = PRESSED;
+    eventQueue.call(fall_handler_user_context);
+}
+
+/* Note that the buttonReleasedCallback() executes in interrupt context,
+ * so it is safer to access BLE device API from the main thread. For
+ * example, do not try to call printf(3) inside interrupt context! */
+void buttonReleasedCallback(void)
+{
+    buttonState = RELEASED;
+    eventQueue.call(rise_handler_user_context);
+}
+
+/**
+ * This function is called when the ble initialization process has failed
+ */
+void onBleInitError(BLE &ble, ble_error_t error)
+{
+    /* Initialization error handling should go here */
+}
+
+/**
+ * Callback triggered when the ble initialization process has finished
+ */
+void bleInitComplete(BLE::InitializationCompleteCallbackContext *params)
+{
+    BLE&        ble   = params->ble;
+    ble_error_t error = params->error;
+
+    if (error != BLE_ERROR_NONE) {
+        /* In case of error, forward the error handling to onBleInitError */
+        onBleInitError(ble, error);
+        return;
+    }
+
+    /* Ensure that it is the default instance of BLE */
+    if(ble.getInstanceID() != BLE::DEFAULT_INSTANCE) {
+        return;
+    }
+
+    ble.gap().onConnection(connectionCallback);
+    ble.gap().onDisconnection(disconnectionCallback);
+    ble.gattServer().onDataWritten(onDataWrittenCallback);
+    ble.gattServer().onUpdatesEnabled(onUpdatesCallback);
+
+    /* Setup primary service */
+    bool initialValueForLEDCharacteristic = false;
+    bool initialValueForButCharacteristic = false;
+    blinkySvcptr = new Blinkyserv(ble, initialValueForLEDCharacteristic, initialValueForButCharacteristic);
+
+    /* 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_LIST_128BIT_SERVICE_IDS, (uint8_t *) uuid128_list, sizeof(uuid128_list));
+    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS, blinkySvcptr->LED_SERVICE_UUID->getBaseUUID(), blinkySvcptr->LED_SERVICE_UUID->getLen());
+    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();
+}
+
+void scheduleBleEventsProcessing(BLE::OnEventsToProcessCallbackContext* context) {
+    BLE &ble = BLE::Instance();
+
+    // Finally redirect call to BLE callback handler
+    eventQueue.call(Callback<void()>(&ble, &BLE::processEvents));
+}
+
+int main()
+{
+    printf("Starting application main entry point...\n");
+    eventQueue.call_every(200, blinkCallback);
+
+    BLE &ble = BLE::Instance();
+    ble.onEventsToProcess(scheduleBleEventsProcessing);
+    ble.init(bleInitComplete);
+    
+    actuatedLED = 1; // initialize off
+    button.fall(buttonPressedCallback);
+    button.rise(buttonReleasedCallback);
+
+    eventQueue.dispatch_forever();
+
+    return 0;
+}
\ No newline at end of file