An mbed BLE-to-Cloud Gateway using Nucleo-F429ZI+X-Nucleo-IDB05A1 or Nucleo-L476RG+X-Nucleo-IDB05A1+X-Nucleo-IDW01M1.
Information
Nucleo- F429ZI configuration requires two hardware patches:
- on Nucleo-F429ZI open SB121 and close SB122
- on X-Nucleo-IDB05A1 move R4 to R6
The BLE client searches for and connects to a MotEnv node.
Diff: main.cpp
- Revision:
- 1:d9c0c4889bd2
- Parent:
- 0:c7083010ae49
- Child:
- 3:39c8d17bed52
--- a/main.cpp Thu Oct 12 13:58:45 2017 +0200 +++ b/main.cpp Thu Oct 12 17:28:58 2017 +0200 @@ -21,6 +21,10 @@ #include <vector> #include "mbed-trace/mbed_trace.h" #include "mbedtls/entropy_poll.h" +#include "ble\BLE.h" +#include "ble\DiscoveredCharacteristic.h" +#include "ble\DiscoveredService.h" + #include "security.h" @@ -29,6 +33,8 @@ // easy-connect compliancy, it has 2 sets of wifi pins we have only one #define MBED_CONF_APP_ESP8266_TX MBED_CONF_APP_WIFI_TX #define MBED_CONF_APP_ESP8266_RX MBED_CONF_APP_WIFI_RX +#define MBED_CFG_SPWF01SA_TX MBED_CONF_APP_WIFI_TX +#define MBED_CFG_SPWF01SA_RX MBED_CONF_APP_WIFI_RX #include "easy-connect/easy-connect.h" #ifdef TARGET_STM @@ -49,11 +55,221 @@ DigitalOut green_led(GREEN_LED); DigitalOut blue_led(BLUE_LED); -Ticker status_ticker; -void blinky() { - green_led = !green_led; + +/************************************************************BLE Stuff from here *********************************/ + +BLE &ble = BLE::Instance(); +static EventQueue eventQueue(/* event count */ 16 * EVENTS_EVENT_SIZE); +Thread BLE_thread; +static bool triggerEnvCharacteristic; +static bool envDataAvailable = false; +static DiscoveredCharacteristic envCharacteristic; +uint16_t payload_length = 0; +uint8_t dataforClient[12]; + +void BLEConnect(BLEProtocol::AddressBytes_t address) { + BLE::Instance().gap().connect(address, Gap::ADDR_TYPE_RANDOM_STATIC, NULL, NULL); +} + + +void advertisementCallback(const Gap::AdvertisementCallbackParams_t *params) { + // parse the advertising payload, looking for data type MANUFACTURER_SPECIFIC_DATA + // 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) { + char devName[16]; + int strLength = value_length > 15 ? 15 : value_length; + memcpy (devName, value, strLength); + devName[strLength] = '\0'; + printf("Found a device with name: %s\n\r", devName); + + if (memcmp(value, "BlueMbedOS", value_length) == 0) { + printf("Found an mbed device node\n"); + BLEProtocol::AddressBytes_t devAddress; + memcpy (devAddress, params->peerAddr,BLEProtocol::ADDR_LEN); + eventQueue.call(BLEConnect,devAddress); + break; + } + } +/* + if(type == GapAdvertisingData::MANUFACTURER_SPECIFIC_DATA) { + printf("length: %d - mask: 0x%x\n\r", value_length, *((uint32_t*)(value+2))); + if (((value_length == 6) || (value_length == 12)) && (*((uint32_t*)(value+2)) & 0x00001C00) == 0x00001C00) { + printf("Found an ST device with environmental data\n"); + //BLE::Instance().gap().connect(params->peerAddr, Gap::ADDR_TYPE_RANDOM_STATIC, NULL, NULL); + BLEProtocol::AddressBytes_t devAddress; + memcpy (devAddress, params->peerAddr,BLEProtocol::ADDR_LEN); + eventQueue.call(BLEConnect,devAddress); + break; + } + } +*/ + i += record_length; + } +} + +void serviceDiscoveryCallback(const DiscoveredService *service) { + + if (service->getUUID().shortOrLong() == UUID::UUID_TYPE_SHORT) { + printf("S UUID-%x attrs[%u %u]\r\n", service->getUUID().getShortUUID(), service->getStartHandle(), service->getEndHandle()); + } else { + printf("S 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()); + } +} + +//read data from BLE +void updateEnvCharacteristic(void) { + if (!BLE::Instance().gattClient().isServiceDiscoveryActive()) { + //printf("Reading environmental data\n\n"); + envCharacteristic.read(); + envDataAvailable = true; + } else { + envDataAvailable = false; + } + +} + + +void characteristicDiscoveryCallback(const DiscoveredCharacteristic *characteristicP) { + /* + printf(" C UUID-%x valueAttr[%u] props[%x]\r\n", characteristicP->getUUID().getShortUUID(), characteristicP->getValueHandle(), (uint8_t)characteristicP->getProperties().broadcast()); + if (characteristicP->getUUID().getShortUUID() == 0xA001) { /* !ALERT! Alter this filter to suit your device. */ + /* optCharacteristic = *characteristicP; + triggerLedCharacteristic = true; + }*/ + printf("Found environmental data\n"); + envCharacteristic = *characteristicP; + triggerEnvCharacteristic = true; } +void discoveryTerminationCallback(Gap::Handle_t connectionHandle) { + + //printf("terminated SD for handle %u\r\n", connectionHandle); + + if (triggerEnvCharacteristic) { + triggerEnvCharacteristic = false; + eventQueue.call(updateEnvCharacteristic); + } +} + +void connectionCallback(const Gap::ConnectionCallbackParams_t *params) { + printf("Connected to ST Node now...\r\n"); + if (params->role == Gap::CENTRAL) { + BLE &ble = BLE::Instance(); + ble.gattClient().onServiceDiscoveryTermination(discoveryTerminationCallback); + const char *servUUIDString = "00000000-0001-11e1-9ab4-0002a5d5c51b"; + UUID servUUID(servUUIDString); + const char *charUUIDString = "001c0000-0001-11e1-ac36-0002a5d5c51b"; + UUID charUUID(charUUIDString); + ble.gattClient().launchServiceDiscovery(params->handle, serviceDiscoveryCallback, characteristicDiscoveryCallback, servUUID, charUUID); + } +} + + +void triggerRead(const GattReadCallbackParams *response) { + + if (response->handle == envCharacteristic.getValueHandle()) { + payload_length = response-> len; + for(int i=0; i< response-> len; i++) { +// printf("%02x", response->data[i]); + dataforClient[i] = response -> data[i]; + // printf("%d", dataforClient[i]); + } + + //printf("Temperature: %f\r\n", (uint32_t)((dataforClient[9]<<8) | dataforClient[8])/10.0); + //printf("Humidity: %f\r\n", (uint32_t)((dataforClient[7]<<8) | dataforClient[6])/10.0); + //printf("Pressure: %f\r\n\r\n\r\n", (uint32_t)((dataforClient[5]<<24) |(dataforClient[4]<<16) |(dataforClient[3]<<8) | dataforClient[2])/100.0); + + eventQueue.call(updateEnvCharacteristic); // triggering BLE data read again + } +} + +void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *) { + printf("Got disconnected from the device!\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\r\n"); + BLE& ble = params->ble; + ble_error_t error = params->error; + ble_error_t error1 = 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; + } + + 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); + if (error) { + 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 thread init and further calls to other BLE methods. +void BLE_thread_init(void){ + //printf("I'm inside BLE thread.....\r\n"); + +//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(); +} + +/************************************************************BLE Stuff to here *********************************/ + // These are example resource values for the Device Object struct MbedClientDevice device = { "Manufacturer_String", // Manufacturer @@ -66,266 +282,109 @@ MbedClient mbed_client(device); -// In case of K64F board , there is button resource available -// to change resource value and unregister -#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 +class EnvResource { +public: + EnvResource() { + + temp_object = M2MInterfaceFactory::create_object("3303"); + temp_inst = temp_object->create_object_instance(); + temp_res = temp_inst->create_dynamic_resource("5700", "Temperature", + M2MResourceInstance::FLOAT, true); // observable + // we can only read this value + temp_res->set_operation(M2MBase::GET_ALLOWED); + temp_res->set_value((uint8_t*)"20.0", 4); + + hum_object = M2MInterfaceFactory::create_object("3304"); + hum_inst = hum_object->create_object_instance(); + hum_res = hum_inst->create_dynamic_resource("5700", "Humidity", + M2MResourceInstance::FLOAT, true); // observable + // we can only read this value + hum_res->set_operation(M2MBase::GET_ALLOWED); + hum_res->set_value((uint8_t*)"50.0", 4); + + press_object = M2MInterfaceFactory::create_object("3300"); + press_inst = press_object->create_object_instance(); + press_res = press_inst->create_dynamic_resource("5700", "Pressure", + M2MResourceInstance::FLOAT, true); // observable + // we can only read this value + press_res->set_operation(M2MBase::GET_ALLOWED); + press_res->set_value((uint8_t*)"1000", 4); + + timer_res = press_inst->create_dynamic_resource("5603", "PollingPeriodMs", // one polling time for all env values, associated to humidity + M2MResourceInstance::INTEGER, false); // not observable + // we can read/wr this value + timer_res->set_operation(M2MBase::GET_PUT_ALLOWED); + timer_res->set_value((uint8_t*)"1000",4); // default 1s polling + + } + + M2MObject* get_temp_object() { + return temp_object; + } + + M2MObject* get_hum_object() { + return hum_object; + } + + M2MObject* get_press_object() { + return press_object; + } + + int get_polling_period() { + return timer_res->get_value_int(); + } -/* - * Arguments for running "blink" in it's own thread. - */ -class BlinkArgs { -public: - BlinkArgs() { - clear(); - } - void clear() { - position = 0; - blink_pattern.clear(); - } - uint16_t position; - std::vector<uint32_t> blink_pattern; + void update_resources() { + float temp; + float hum; + float press; + + if (!envDataAvailable) { + return; + } + + temp = (uint32_t)((dataforClient[9]<<8) | dataforClient[8])/10.0; + hum = ((dataforClient[7]<<8) | dataforClient[6])/10.0; + press = (uint32_t)((dataforClient[5]<<24) |(dataforClient[4]<<16) |(dataforClient[3]<<8) | dataforClient[2])/100.0; + + stringstream ss_temp; + ss_temp << temp; + std::string stringified = ss_temp.str(); + temp_res->set_value((uint8_t*)stringified.c_str(), stringified.length()); + + stringstream ss_hum; + ss_hum << hum; + stringified = ss_hum.str(); + hum_res->set_value((uint8_t*)stringified.c_str(), stringified.length()); + + stringstream ss_press; + ss_press << press; + stringified = ss_press.str(); + press_res->set_value((uint8_t*)stringified.c_str(), stringified.length()); + + } + +private: + + M2MObject* temp_object; + M2MObjectInstance* temp_inst; + M2MResource* temp_res; + + M2MObject* hum_object; + M2MObjectInstance* hum_inst; + M2MResource* hum_res; + + M2MObject* press_object; + M2MObjectInstance* press_inst; + M2MResource* press_res; + + M2MResource* timer_res; + }; -/* - * The Led contains one property (pattern) and a function (blink). - * When the function blink is executed, the pattern is read, and the LED - * will blink based on the pattern. - */ -class LedResource { -public: - LedResource() { - // create ObjectID with metadata tag of '3201', which is 'digital output' - led_object = M2MInterfaceFactory::create_object("3201"); - M2MObjectInstance* led_inst = led_object->create_object_instance(); - // 5853 = Multi-state output - M2MResource* pattern_res = led_inst->create_dynamic_resource("5853", "Pattern", - M2MResourceInstance::STRING, false); - // read and write - pattern_res->set_operation(M2MBase::GET_PUT_ALLOWED); - // set initial pattern (toggle every 200ms. 7 toggles in total) - pattern_res->set_value((const uint8_t*)"500:500:500:500:500:500:500", 27); - - // there's not really an execute LWM2M ID that matches... hmm... - M2MResource* led_res = led_inst->create_dynamic_resource("5850", "Blink", - M2MResourceInstance::OPAQUE, false); - // we allow executing a function here... - led_res->set_operation(M2MBase::POST_ALLOWED); - // when a POST comes in, we want to execute the led_execute_callback - led_res->set_execute_function(execute_callback(this, &LedResource::blink)); - // Completion of execute function can take a time, that's why delayed response is used - led_res->set_delayed_response(true); - blink_args = new BlinkArgs(); - } - - ~LedResource() { - delete blink_args; - } - - M2MObject* get_object() { - return led_object; - } - - void blink(void *argument) { - // read the value of 'Pattern' - status_ticker.detach(); - green_led = LED_OFF; - - M2MObjectInstance* inst = led_object->object_instance(); - M2MResource* res = inst->resource("5853"); - // Clear previous blink data - blink_args->clear(); - - // values in mbed Client are all buffers, and we need a vector of int's - uint8_t* buffIn = NULL; - uint32_t sizeIn; - res->get_value(buffIn, sizeIn); - - // turn the buffer into a string, and initialize a vector<int> on the heap - std::string s((char*)buffIn, sizeIn); - free(buffIn); - printf("led_execute_callback pattern=%s\n", s.c_str()); - - // our pattern is something like 500:200:500, so parse that - std::size_t found = s.find_first_of(":"); - while (found!=std::string::npos) { - blink_args->blink_pattern.push_back(atoi((const char*)s.substr(0,found).c_str())); - s = s.substr(found+1); - found=s.find_first_of(":"); - if(found == std::string::npos) { - blink_args->blink_pattern.push_back(atoi((const char*)s.c_str())); - } - } - // check if POST contains payload - if (argument) { - M2MResource::M2MExecuteParameter* param = (M2MResource::M2MExecuteParameter*)argument; - String object_name = param->get_argument_object_name(); - uint16_t object_instance_id = param->get_argument_object_instance_id(); - String resource_name = param->get_argument_resource_name(); - int payload_length = param->get_argument_value_length(); - uint8_t* payload = param->get_argument_value(); - printf("Resource: %s/%d/%s executed\n", object_name.c_str(), object_instance_id, resource_name.c_str()); - printf("Payload: %.*s\n", payload_length, payload); - } - // do_blink is called with the vector, and starting at -1 - blinky_thread.start(callback(this, &LedResource::do_blink)); - } - -private: - M2MObject* led_object; - Thread blinky_thread; - BlinkArgs *blink_args; - void do_blink() { - for (;;) { - // blink the LED - red_led = !red_led; - // up the position, if we reached the end of the vector - if (blink_args->position >= blink_args->blink_pattern.size()) { - // send delayed response after blink is done - M2MObjectInstance* inst = led_object->object_instance(); - M2MResource* led_res = inst->resource("5850"); - led_res->send_delayed_post_response(); - red_led = LED_OFF; - status_ticker.attach_us(blinky, 250000); - return; - } - // Wait requested time, then continue prosessing the blink pattern from next position. - Thread::wait(blink_args->blink_pattern.at(blink_args->position)); - blink_args->position++; - } - } -}; - -/* - * 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 '3200', which is 'digital input' - btn_object = M2MInterfaceFactory::create_object("3200"); - 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() { - if (mbed_client.register_successful()) { - M2MObjectInstance* inst = btn_object->object_instance(); - M2MResource* res = inst->resource("5501"); - - // up counter - counter++; - #ifdef TARGET_K64F - printf("handle_button_click, new value of counter is %d\n", counter); - #else - printf("simulate button_click, new value of counter is %d\n", counter); - #endif - // 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); - } else { - printf("simulate button_click, device not registered\n"); - } - } - -private: - M2MObject* btn_object; - uint16_t counter; -}; - -class BigPayloadResource { -public: - BigPayloadResource() { - big_payload = M2MInterfaceFactory::create_object("1000"); - M2MObjectInstance* payload_inst = big_payload->create_object_instance(); - M2MResource* payload_res = payload_inst->create_dynamic_resource("1", "BigData", - M2MResourceInstance::STRING, true /* observable */); - payload_res->set_operation(M2MBase::GET_PUT_ALLOWED); - payload_res->set_value((uint8_t*)"0", 1); - payload_res->set_incoming_block_message_callback( - incoming_block_message_callback(this, &BigPayloadResource::block_message_received)); - payload_res->set_outgoing_block_message_callback( - outgoing_block_message_callback(this, &BigPayloadResource::block_message_requested)); - } - - M2MObject* get_object() { - return big_payload; - } - - void block_message_received(M2MBlockMessage *argument) { - if (argument) { - if (M2MBlockMessage::ErrorNone == argument->error_code()) { - if (argument->is_last_block()) { - printf("Last block received\n"); - } - printf("Block number: %d\n", argument->block_number()); - // First block received - if (argument->block_number() == 0) { - // Store block - // More blocks coming - } else { - // Store blocks - } - } else { - printf("Error when receiving block message! - EntityTooLarge\n"); - } - printf("Total message size: %" PRIu32 "\n", argument->total_message_size()); - } - } - - void block_message_requested(const String& resource, uint8_t *&/*data*/, uint32_t &/*len*/) { - printf("GET request received for resource: %s\n", resource.c_str()); - // Copy data and length to coap response - } - -private: - M2MObject* big_payload; -}; - -// Network interaction must be performed outside of interrupt context -Semaphore updates(0); -volatile bool registered = false; -volatile bool clicked = false; osThreadId mainThread; -void unregister() { - registered = false; - updates.release(); -} - -void button_clicked() { - clicked = true; - updates.release(); -} - // Entry point to the program int main() { @@ -354,37 +413,22 @@ red_led = LED_OFF; blue_led = LED_OFF; - status_ticker.attach_us(blinky, 250000); // Keep track of the main thread mainThread = osThreadGetId(); printf("\nStarting mbed Client example\n"); - mbed_trace_init(); + //mbed_trace_init(); NetworkInterface* network = easy_connect(true); if(network == NULL) { printf("\nConnection to Network Failed - exiting application...\n"); return -1; } - - // we create our button and LED resources - ButtonResource button_resource; - LedResource led_resource; - BigPayloadResource big_payload_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 15 seconds periodically - timer.attach(&button_clicked, 15.0); -#endif + + // environmental data + EnvResource env_resource; + // Create endpoint interface to manage register and unregister mbed_client.create_interface(MBED_SERVER_ADDRESS, network); @@ -398,32 +442,37 @@ // Add objects to list object_list.push_back(device_object); - object_list.push_back(button_resource.get_object()); - object_list.push_back(led_resource.get_object()); - object_list.push_back(big_payload_resource.get_object()); - + object_list.push_back(env_resource.get_temp_object()); + object_list.push_back(env_resource.get_hum_object()); + object_list.push_back(env_resource.get_press_object()); + // 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; + // wait for registration and BLE data started flushing + while (!mbed_client.register_successful()) { + Thread::wait(500); + } + + printf("\nNow starting BLE thread\n"); + BLE_thread.start(BLE_thread_init); + + while (!envDataAvailable) { + Thread::wait(500); + } + + printf ("\nNow pushing data to the mbed device connector.\n"); + while (true) { - updates.wait(25000); - if(registered) { - if(!clicked) { - mbed_client.test_update_register(); - } - }else { - break; - } - if(clicked) { - clicked = false; - button_resource.handle_button_click(); - } + + env_resource.update_resources(); + int timer_val = env_resource.get_polling_period(); + Thread::wait(timer_val); } mbed_client.test_unregister(); - status_ticker.detach(); + }