This is an example of BLE GATT Client, which receives broadcast data from BLE_Server_BME280 ( a GATT server) , then transfers values up to mbed Device Connector (cloud).
Please refer details about BLEClient_mbedDevConn below. https://github.com/soramame21/BLEClient_mbedDevConn
The location of required BLE GATT server, BLE_Server_BME280, is at here. https://developer.mbed.org/users/edamame22/code/BLE_Server_BME280/
Diff: main.cpp
- Revision:
- 0:29983394c6b6
- Child:
- 1:8950e6a891df
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.cpp Thu Apr 13 04:48:11 2017 +0000 @@ -0,0 +1,596 @@ +/* + * Copyright (c) 2015 ARM Limited. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * 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 "simpleclient.h" +#include <string> +#include <sstream> +#include <vector> +#include "mbed-trace/mbed_trace.h" +#include "mbedtls/entropy_poll.h" + +#include <events/mbed_events.h> +#include <mbed.h> +#include "ble/BLE.h" +#include "ble/DiscoveredCharacteristic.h" +#include "ble/DiscoveredService.h" + +#include "security.h" + +#include "mbed.h" +#include "rtos.h" + +#if MBED_CONF_APP_NETWORK_INTERFACE == WIFI +#include "ESP8266Interface.h" +ESP8266Interface esp(MBED_CONF_APP_WIFI_TX, MBED_CONF_APP_WIFI_RX); +#elif MBED_CONF_APP_NETWORK_INTERFACE == ETHERNET +#include "EthernetInterface.h" +EthernetInterface eth; +#elif MBED_CONF_APP_NETWORK_INTERFACE == MESH_LOWPAN_ND +#define MESH +#include "NanostackInterface.h" +LoWPANNDInterface mesh; +#elif MBED_CONF_APP_NETWORK_INTERFACE == MESH_THREAD +#define MESH +#include "NanostackInterface.h" +ThreadInterface mesh; +#endif + +#if defined(MESH) +#if MBED_CONF_APP_MESH_RADIO_TYPE == ATMEL +#include "NanostackRfPhyAtmel.h" +NanostackRfPhyAtmel rf_phy(ATMEL_SPI_MOSI, ATMEL_SPI_MISO, ATMEL_SPI_SCLK, ATMEL_SPI_CS, + ATMEL_SPI_RST, ATMEL_SPI_SLP, ATMEL_SPI_IRQ, ATMEL_I2C_SDA, ATMEL_I2C_SCL); +#elif MBED_CONF_APP_MESH_RADIO_TYPE == MCR20 +#include "NanostackRfPhyMcr20a.h" +NanostackRfPhyMcr20a rf_phy(MCR20A_SPI_MOSI, MCR20A_SPI_MISO, MCR20A_SPI_SCLK, MCR20A_SPI_CS, MCR20A_SPI_RST, MCR20A_SPI_IRQ); +#endif //MBED_CONF_APP_RADIO_TYPE +#endif //MESH + +#ifndef MESH +// This is address to mbed Device Connector +#define MBED_SERVER_ADDRESS "coap://api.connector.mbed.com:5684" +#else +// This is address to mbed Device Connector +#define MBED_SERVER_ADDRESS "coaps://[2607:f0d0:2601:52::20]:5684" +#endif + +Serial output(USBTX, USBRX); + +// Status indication +DigitalOut red_led(LED1); +DigitalOut green_led(LED2); +DigitalOut blue_led(LED3); +Ticker status_ticker; +// Ren, begin +enum charType { + PRESSURE, + TEMPERATURE, + HUMIDITY +}; +const char * dbg_CharType[HUMIDITY+1]={"Pressure","Temperature","Humidity"}; +const char * objName[HUMIDITY+1]={"3323","3303","3304"}; +static bool is_active[HUMIDITY+1]; +static float dataprint[HUMIDITY+1]={0,0,0}; +/* + * The BME280 sensor contains 3 float properties. + * Those are updated once BLE Gatt client read the values. + */ +class BME280Resource { +public: + BME280Resource() { + // create Pressure object '3323'. + for(int m=0; m<HUMIDITY+1; m++) { + bme280[m] = M2MInterfaceFactory::create_object(objName[m]); + tmp_inst[m] = bme280[m] ->create_object_instance(); + tmp_res[m] = tmp_inst[m]->create_dynamic_resource("5700", dbg_CharType[m], + M2MResourceInstance::STRING, true /* observable */); + tmp_res[m]->set_operation(M2MBase::GET_ALLOWED); + tmp_res[m]->set_value((uint8_t*)"0.0", 3); + } + } + + void set_bme280_value(float val, int h){ + char tmp_buf[50]; + int len; + if (h<PRESSURE || h>HUMIDITY) { + printf("data type h (input) is wrong!!"); + return; + } + if (h==HUMIDITY) + len=sprintf(tmp_buf,"%0.2f%%",val); + else if (h==PRESSURE) + len=sprintf(tmp_buf,"%0.1f hPa",val); + else + len=sprintf(tmp_buf,"%0.2f degC",val); + + tmp_res[h]->set_value((uint8_t*)tmp_buf, len); + //printf("set_value(tmp_bug=%s\r\n", tmp_buf); + } + + + M2MObject* get_object(int idx) { + if (idx<PRESSURE || idx>HUMIDITY) return NULL; + return bme280[idx]; + } +private: + M2MObject* bme280[HUMIDITY+1]; + M2MObjectInstance* tmp_inst[HUMIDITY+1]; + M2MResource* tmp_res[HUMIDITY+1]; +}; + +static BME280Resource *demo1; +// Ren, end + +/************************************************************BLE Stuff from here *********************************/ +BLE &ble = BLE::Instance(); +static DiscoveredCharacteristic bme280Characteristic[HUMIDITY+1]; +static bool triggerLedCharacteristic; +static const char PEER_NAME[] = "BME280"; +uint16_t payload_length = 0; +uint8_t dataforClient[] = {0}; +uint32_t final_dataforClient = 0; + +static EventQueue eventQueue( + /* event count */ 16 * /* event size */ 32 +); + +void advertisementCallback(const Gap::AdvertisementCallbackParams_t *params) { + // parse the advertising payload, looking for data type COMPLETE_LOCAL_NAME + // The advertising payload is a collection of key/value records where + // byte 0: length of the record excluding this byte + // byte 1: The key, it is the type of the data + // byte [2..N] The value. N is equal to byte0 - 1 + + //printf("Starting advertisementCallback...\r\n"); + for (uint8_t i = 0; i < params->advertisingDataLen; ++i) { + + const uint8_t record_length = params->advertisingData[i]; + if (record_length == 0) { + continue; + } + const uint8_t type = params->advertisingData[i + 1]; + const uint8_t* value = params->advertisingData + i + 2; + const uint8_t value_length = record_length - 1; + + if (type == GapAdvertisingData::COMPLETE_LOCAL_NAME) { + if ((value_length == sizeof(PEER_NAME)) && (memcmp(value, PEER_NAME, value_length) == 0)) { + printf( + "adv peerAddr[%02x %02x %02x %02x %02x %02x] rssi %d, isScanResponse %u, AdvertisementType %u\r\n", + params->peerAddr[5], params->peerAddr[4], params->peerAddr[3], params->peerAddr[2], + params->peerAddr[1], params->peerAddr[0], params->rssi, params->isScanResponse, params->type + ); + BLE::Instance().gap().connect(params->peerAddr, Gap::ADDR_TYPE_RANDOM_STATIC, NULL, NULL); + break; + } + } + i += record_length; + } +} + +void serviceDiscoveryCallback(const DiscoveredService *service) { + if (service->getUUID().shortOrLong() == UUID::UUID_TYPE_SHORT) { + printf("S type short UUID-%x attrs[%u %u]\r\n", service->getUUID().getShortUUID(), service->getStartHandle(), service->getEndHandle()); + } else { + printf("S type long UUID-"); + const uint8_t *longUUIDBytes = service->getUUID().getBaseUUID(); + for (unsigned i = 0; i < UUID::LENGTH_OF_LONG_UUID; i++) { + printf("%02x", longUUIDBytes[i]); + } + printf(" attrs[%u %u]\r\n", service->getStartHandle(), service->getEndHandle()); + } +} + +/*** + This function called read() initially, following read() calls + are repeated inside triggerRead function +***/ +void updateLedCharacteristic(void) { + if (!BLE::Instance().gattClient().isServiceDiscoveryActive()) { + //printf("02 updateLedCharacteristic\n"); + for(int g=0; g<HUMIDITY+1; g++) { + if (is_active[g]) bme280Characteristic[g].read(); + } + } +} + + +void characteristicDiscoveryCallback(const DiscoveredCharacteristic *characteristicP) { + int tmp_uuid; + tmp_uuid=characteristicP->getUUID().getShortUUID(); + printf(" C UUID-%x valueAttr[%u] props[%x]\r\n", characteristicP->getUUID().getShortUUID(), characteristicP->getValueHandle(), (uint8_t)characteristicP->getProperties().broadcast()); + if ((tmp_uuid < GattCharacteristic::UUID_PRESSURE_CHAR) || + (tmp_uuid > GattCharacteristic::UUID_HUMIDITY_CHAR)) return; + triggerLedCharacteristic = true; + if (tmp_uuid == GattCharacteristic::UUID_PRESSURE_CHAR) { + bme280Characteristic[PRESSURE] = *characteristicP; is_active[PRESSURE] = true; + printf(" is_active[PRESSURE] = true\r\n"); + } + else if (tmp_uuid == GattCharacteristic::UUID_TEMPERATURE_CHAR) { + bme280Characteristic[TEMPERATURE] = *characteristicP; is_active[TEMPERATURE] = true; + printf(" is_active[TEMPERATURE] = true\r\n"); + } else { + bme280Characteristic[HUMIDITY] = *characteristicP; is_active[HUMIDITY] = true; + printf(" is_active[HUMIDITY] = true\r\n"); + } +} + +void discoveryTerminationCallback(Gap::Handle_t connectionHandle) { + printf("terminated SD for handle %u\r\n", connectionHandle); + if (triggerLedCharacteristic) { + triggerLedCharacteristic = false; + eventQueue.call(updateLedCharacteristic); + } +} + +void connectionCallback(const Gap::ConnectionCallbackParams_t *params) { + int ret; + printf("Connected to BME280 now...\r\n"); + if (params->role == Gap::CENTRAL) { + BLE &ble = BLE::Instance(); + ble.gattClient().onServiceDiscoveryTermination(discoveryTerminationCallback); + // Ren, connect to ENVIRONMENT service + ret=ble.gattClient().launchServiceDiscovery(params->handle, serviceDiscoveryCallback, characteristicDiscoveryCallback, GattService::UUID_ENVIRONMENTAL_SERVICE); + printf("ble.gattClient().launchServiceDiscovery = %d\r\n", ret); + } +} + +//ASHOK's triggerRead function + +void triggerRead(const GattReadCallbackParams *response) { + int k; + for(int j=0; j<HUMIDITY+1; j++) { + if (is_active[j]) { + if (response->handle == bme280Characteristic[j].getValueHandle()){ + payload_length = response-> len; + for(int i=0; i< response-> len; i++) { + dataforClient[i] = response -> data[i]; + } + //BLE packet contains 4 bytes of 8 bit int's each. Combine all of them to form a single 32-bit int. + final_dataforClient = ((uint32_t)dataforClient[3]<<24) | (dataforClient[2]<<16) | (dataforClient[1] << 8) | (dataforClient[0]); + //Ren debug + dataprint[j] = ( j==PRESSURE)? (float) final_dataforClient/10 : (float) final_dataforClient/100; + demo1->set_bme280_value(dataprint[j], j); + if (j==HUMIDITY) { + k=0; printf("%s = %0.2f%% ", dbg_CharType[j], dataprint[j]); + } else + { + k=j+1; + if(j==PRESSURE) printf("%s = %0.1f hPa ", dbg_CharType[j], dataprint[j]); + else printf("%s = %0.2f degC ", dbg_CharType[j], dataprint[j]); + } + break; + } + } + } + printf("\r\n"); + bme280Characteristic[k].read(); +} + +// BLE disconnected +void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *) { + printf("BLE disconnected\r\n"); + /* Start scanning and try to connect again */ + BLE::Instance().gap().startScan(advertisementCallback); +} + +void onBleInitError(BLE &ble, ble_error_t error) +{ + /* Initialization error handling should go here */ + printf("BLE Error = %u\r\n", error); +} + +void bleInitComplete(BLE::InitializationCompleteCallbackContext *params) +{ + printf("I'm inside BLE init Complete\r\n"); + 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) { + printf("Not the default instance\r\n"); + return; + } + + // Ren, clear discovered Characteristic at beginning + for(int i=0; i<HUMIDITY+1; i++) { + dataprint[i]=0.0; is_active[i]=false; + } + + ble.gap().onDisconnection(disconnectionCallback); + ble.gap().onConnection(connectionCallback); + + // On reading data, call triggerRead function. + ble.gattClient().onDataRead(triggerRead); + + // scan interval: 400ms and scan window: 400ms. + // Every 400ms the device will scan for 400ms + // This means that the device will scan continuously. + ble.gap().setScanParams(400, 400); + error = ble.gap().startScan(advertisementCallback); + printf("BLE Error startScan = %u\r\n", error); + +} + +void scheduleBleEventsProcessing(BLE::OnEventsToProcessCallbackContext* context) { + BLE &ble = BLE::Instance(); + eventQueue.call(Callback<void()>(&ble, &BLE::processEvents)); +} + +/************************************************************BLE Stuff to here *********************************/ + +void blinky() { + green_led = !green_led; +} + +// These are example resource values for the Device Object +struct MbedClientDevice device = { + "Manufacturer_String", // Manufacturer + "Type_String", // Type + "ModelNumber_String", // ModelNumber + "SerialNumber_String" // SerialNumber +}; + +// Instantiate the class which implements LWM2M Client API (from simpleclient.h) +MbedClient mbed_client(device); + +// Network interaction must be performed outside of interrupt context +Semaphore updates(0); +volatile bool registered = false; +volatile bool clicked = false; +osThreadId mainThread; + +#ifdef TARGET_K64F +// Set up Hardware interrupt button. +InterruptIn obs_button(SW2); +InterruptIn unreg_button(SW3); +#else +//In non K64F boards , set up a timer to simulate updating resource, +// there is no functionality to unregister. +Ticker timer; +#endif + +/* + * The button contains one property (click count). + * When `handle_button_click` is executed, the counter updates. + */ +class ButtonResource { +public: + ButtonResource(): counter(0) { + // create ObjectID with metadata tag of 'BTN_SW2', which is 'digital input' + btn_object = M2MInterfaceFactory::create_object("BTN_SW2"); + M2MObjectInstance* btn_inst = btn_object->create_object_instance(); + // create resource with ID '5501', which is digital input counter + M2MResource* btn_res = btn_inst->create_dynamic_resource("5501", "Button", + M2MResourceInstance::INTEGER, true /* observable */); + // we can read this value + btn_res->set_operation(M2MBase::GET_ALLOWED); + // set initial value (all values in mbed Client are buffers) + // to be able to read this data easily in the Connector console, we'll use a string + btn_res->set_value((uint8_t*)"0", 1); + } + + ~ButtonResource() { + } + + M2MObject* get_object() { + return btn_object; + } + + /* + * When you press the button, we read the current value of the click counter + * from mbed Device Connector, then up the value with one. + */ + void handle_button_click() { + M2MObjectInstance* inst = btn_object->object_instance(); + M2MResource* res = inst->resource("5501"); + + // up counter + counter++; + printf("handle_button_click, new value of counter is %d\r\n", counter); + // serialize the value of counter as a string, and tell connector + char buffer[20]; + int size = sprintf(buffer,"%d",counter); + res->set_value((uint8_t*)buffer, size); + } + +private: + M2MObject* btn_object; + uint16_t counter; +}; + + +void unregister() { + registered = false; + updates.release(); +} + +void button_clicked() { + clicked = true; + updates.release(); +} + +// debug printf function +void trace_printer(const char* str) { + printf("%s\r\n", str); +} + +/****************************************************************More BLE Stuff from here****************/ +//BLE thread init and further calls to other BLE methods. +void BLE_thread_init(void){ + printf("I'm inside BLE thread_init.....\r\n"); + eventQueue.call_every(500, blinky); + //Schedule events before starting the thread since there might be some missed events while scanning / pairing. + ble.onEventsToProcess(scheduleBleEventsProcessing); + ble.init(bleInitComplete); + //Loop forever the BLE thread + eventQueue.dispatch_forever(); +} + +/****************************************************************More BLE Stuff to here****************/ + +// Entry point to the program +int main() { + + unsigned int seed; + size_t len; + + //Create a new thread for BLE + Thread BLE_thread; + + +#ifdef MBEDTLS_ENTROPY_HARDWARE_ALT + // Used to randomize source port + mbedtls_hardware_poll(NULL, (unsigned char *) &seed, sizeof seed, &len); + +#elif defined MBEDTLS_TEST_NULL_ENTROPY + +#warning "mbedTLS security feature is disabled. Connection will not be secure !! Implement proper hardware entropy for your selected hardware." + // Used to randomize source port + mbedtls_null_entropy_poll( NULL,(unsigned char *) &seed, sizeof seed, &len); + +#else + +#error "This hardware does not have entropy, endpoint will not register to Connector.\ +You need to enable NULL ENTROPY for your application, but if this configuration change is made then no security is offered by mbed TLS.\ +Add MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES and MBEDTLS_TEST_NULL_ENTROPY in mbed_app.json macros to register your endpoint." + +#endif + + srand(seed); + red_led = 1; + blue_led = 1; + status_ticker.attach_us(blinky, 250000); + // Keep track of the main thread + mainThread = osThreadGetId(); + + // Sets the console baud-rate + output.baud(9600); + + output.printf("Starting mbed Client example...\r\n"); + + mbed_trace_init(); + mbed_trace_print_function_set(trace_printer); + NetworkInterface *network_interface = 0; + int connect_success = -1; +#if MBED_CONF_APP_NETWORK_INTERFACE == WIFI + output.printf("\n\rUsing WiFi \r\n"); + output.printf("\n\rConnecting to WiFi..\r\n"); + connect_success = esp.connect(MBED_CONF_APP_WIFI_SSID, MBED_CONF_APP_WIFI_PASSWORD); + network_interface = &esp; +#elif MBED_CONF_APP_NETWORK_INTERFACE == ETHERNET + output.printf("Using Ethernet\r\n"); + connect_success = eth.connect(); + network_interface = ð +#endif +#ifdef MESH + output.printf("Using Mesh\r\n"); + output.printf("\n\rConnecting to Mesh..\r\n"); + mesh.initialize(&rf_phy); + connect_success = mesh.connect(); + network_interface = &mesh; +#endif + if(connect_success == 0) { + output.printf("\n\rConnected to Network successfully\r\n"); + } else { + output.printf("\n\rConnection to Network Failed %d! Exiting application....\r\n", connect_success); + return 0; + } + const char *ip_addr = network_interface->get_ip_address(); + if (ip_addr) { + output.printf("IP address %s\r\n", ip_addr); + } else { + output.printf("No IP address\r\n"); + } + + // create our button resources + ButtonResource button_resource; +#ifdef TARGET_K64F + // On press of SW3 button on K64F board, example application + // will call unregister API towards mbed Device Connector + //unreg_button.fall(&mbed_client,&MbedClient::test_unregister); + unreg_button.fall(&unregister); + + // Observation Button (SW2) press will send update of endpoint resource values to connector + obs_button.fall(&button_clicked); +#else + // Send update of endpoint resource values to connector every 5 seconds periodically + timer.attach(&button_clicked, 5.0); +#endif + + // Create endpoint interface to manage register and unregister + mbed_client.create_interface(MBED_SERVER_ADDRESS, network_interface); + + // Create Objects of varying types, see simpleclient.h for more details on implementation. + M2MSecurity* register_object = mbed_client.create_register_object(); // server object specifying connector info + M2MDevice* device_object = mbed_client.create_device_object(); // device resources object + + // Create list of Objects to register + M2MObjectList object_list; + + // Add objects to list + object_list.push_back(device_object); + object_list.push_back(button_resource.get_object()); + // add bme280 data objects + if (demo1==NULL) { + // Create bme280 instance + demo1=new BME280Resource(); + printf("created bme280 instance now!!\r\n"); + } + object_list.push_back(demo1->get_object(PRESSURE)); + object_list.push_back(demo1->get_object(TEMPERATURE)); + object_list.push_back(demo1->get_object(HUMIDITY)); + + // Set endpoint registration object + mbed_client.set_register_object(register_object); + + // Register with mbed Device Connector + mbed_client.test_register(register_object, object_list); + registered = true; + + //Start BLE thread after connection is established to device connector. Else, there is conflict. + BLE_thread.start(BLE_thread_init); + // waiting for completion of BLE_thread_init + Thread::wait(2000); + while (true) { + + printf("inside main for client\r\n"); + triggerLedCharacteristic = false; + + updates.wait(25000); + if(registered) { + if(!clicked) { + //printf("Inside registered ... clicked \r\n"); + mbed_client.test_update_register(); + } + } else { // not registered, then stop; + break; + } + if(clicked) { + clicked = false; + button_resource.handle_button_click(); + } + + } + + mbed_client.test_unregister(); + status_ticker.detach(); +}