Mbed OS Device Connector client pushing environmental sensor data via 6LowPan.

Dependencies:   X_NUCLEO_IKS01A2

Fork of mbed-os-example-client-Sensors_6LowPan by Nicola Capovilla

The application is derived from the official mbedOS client example (link) and has been tested using a X-NUCLEO-IDS01A4 SubGHz 6LowPan connectivity board and a X-NUCLEO-IKS01A2 (default) or a X-NUCLEO-IKS01A1 motion and environmental sensors expansion board connected to a ST NUCLEO-F429ZI platform.
The following steps should be performed to make the application work:

  • Register and login into ARM mbed Connector.
  • Replace the default and empty security.h file with the one associated with your account and provided by the Connector (Security Credentials menu).
  • In order to use X-NUCLEO-IKS01A1 instead of default X-NUCLEO-IKS01A2 comment out the IKS01A2 macro definition in main.cpp file.
  • Choose NUCLEO-F429ZI as a target either from online compiler or from CLI, compile and flash.
  • Setup and connect a NUCLEO-F429ZI + X-NUCLEO-IDS01A4 6LowPan border router as explained here
  • Open a serial terminal (params 115200N1) and wait that the client is connected to the mbed Connector via the border router.
  • As soon as the client is connected it will start acquiring and pushing the environmental (pressure, temperature and humidity) data to the cloud.

Note: environmental data are expressed using IPSO representation based on OMA LWM2M standard.

Revision:
0:694e888fd1b5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Thu Jan 25 18:00:04 2018 +0100
@@ -0,0 +1,634 @@
+/*
+ * Copyright (c) 2015, 2016 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.
+ */
+ 
+ /* 
+  * Modified by STMicroelectronics to support X-NUCLEO-IKS01A1/2 environmental 
+  * sensors on top of Nucleo-F429ZI/Nucleo-L476RG.
+ */
+ 
+
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+#include "simpleclient.h"
+#include <string>
+#include <sstream>
+#include <vector>
+#include "mbed-trace/mbed_trace.h"
+#include "mbedtls/entropy_poll.h"
+
+#include "security.h"
+
+#include "mbed.h"
+
+// 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
+#include "easy-connect/easy-connect.h"
+
+#ifdef TARGET_STM
+#define RED_LED (LED3)
+#define GREEN_LED (LED1)
+#define BLUE_LED (LED2)
+#define LED_ON (1)		     
+#else // !TARGET_STM
+#define RED_LED (LED1)
+#define GREEN_LED (LED2)
+#define BLUE_LED (LED3)			     
+#define LED_ON (0) 
+#endif // !TARGET_STM
+#define LED_OFF (!LED_ON)
+
+// Status indication
+DigitalOut red_led(RED_LED);
+DigitalOut green_led(GREEN_LED);
+DigitalOut blue_led(BLUE_LED);
+
+Ticker status_ticker;
+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);
+
+
+// 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
+
+/*
+ * 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;
+};
+
+/*
+ * 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();
+
+        // betzw: Start blinky thread
+        blinky_thread.start(callback(this, &LedResource::do_blink)); // betzw - NOTE: threads can only be started once!
+    }
+
+    ~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)); // betzw - NOTE: threads can only be started once!
+        blinky_thread.signal_set(1);
+    }
+
+private:
+    M2MObject* led_object;
+    Thread blinky_thread;
+    BlinkArgs *blink_args;
+    void do_blink() {
+    	blinky_thread.signal_wait(0);
+        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);
+                // betzw - WAS: return;
+                blinky_thread.signal_wait(0);
+                continue;
+            }
+            // Wait requested time, then continue processing 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++;
+    #if defined(TARGET_K64F) || defined(TARGET_NUCLEO_F429ZI) || defined(TARGET_NUCLEO_L476RG)
+            printf("handle_button_click, new value of counter is %d\r\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;
+};
+
+/* STMicroelectronics IKS01A1/2 code  - BEGIN */
+
+#define IKS01A2  // comment out for IKS01A1
+
+#ifdef IKS01A2
+#define __ARCHDEP__TYPES  // fix to a typedef redefinition with LWIP stack
+typedef unsigned char u8_t;
+typedef unsigned short int u16_t;
+//typedef unsigned int u32_t;  // conflictiong definition
+typedef int i32_t;
+typedef short int i16_t;
+typedef signed char i8_t;
+#include "XNucleoIKS01A2.h"
+#else
+#include "XNucleoIKS01A1.h"
+#endif
+
+#define SENSOR_OK	0
+#define DEFAULT_ENV_POLLING_PERIOD_MS	5000   // default polling time 
+
+class EnvResource {
+public:
+	EnvResource() {
+#ifdef IKS01A2
+		p_mems_expansion_board = XNucleoIKS01A2::instance(IKS01A2_PIN_I2C_SDA,IKS01A2_PIN_I2C_SCL);
+#else
+		p_mems_expansion_board = XNucleoIKS01A1::instance(IKS01A1_PIN_I2C_SDA,IKS01A1_PIN_I2C_SCL);
+#endif
+		
+        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);
+		
+#ifdef IKS01A2 // need to explicitly enable the sensor with IKS01A2
+		HTS221Sensor *hum_temp = p_mems_expansion_board->ht_sensor;
+		hum_temp->enable();
+#endif
+		
+		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*)"5000", 4);
+
+#ifdef IKS01A2 // need to explicitly enable the sensor with IKS01A2
+		LPS22HBSensor *press_temp = p_mems_expansion_board->pt_sensor;
+		press_temp->enable();
+#endif
+		
+        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();
+	}
+
+	void update_resources() {
+	   float temp;
+	   float hum;
+	   float press;
+	   int err;
+	   
+		err = p_mems_expansion_board->ht_sensor->get_temperature(&temp);
+       if ( err != SENSOR_OK) {  
+			printf ("= * ERROR %d get_temperature\n\r", err);
+			temp = 20.0;
+			red_led = 1;
+		} else {
+			printf ("Temp C: %f\n\r", temp);
+			red_led = 0;
+		}
+		
+		err = p_mems_expansion_board->ht_sensor->get_humidity(&hum);
+        if ( err != SENSOR_OK) {  
+	       printf ("= * ERROR %d get_humidity\n\r", err);
+		   hum= 50.0;
+		   red_led = 1;
+		} else {
+		   printf ("Humidity %: %f\n\r", hum);
+		   red_led = 0;
+		}
+		
+		err = p_mems_expansion_board->pt_sensor->get_pressure(&press);
+        if ( err != SENSOR_OK) {  
+	      printf ("= * ERROR %d get_pressure\n\r", err);
+		  red_led = 1;
+	      return;
+        } else {
+	      printf ("Pressure mBar: %f\n\r", press); 
+		  red_led = 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;
+	
+#ifdef IKS01A2
+   XNucleoIKS01A2 *p_mems_expansion_board;
+#else
+   XNucleoIKS01A1 *p_mems_expansion_board; 
+#endif
+
+};
+
+
+/* STMicroelectronics IKS01A1/2 code  - END */
+
+// 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;
+	blue_led = 0;
+    updates.release();
+}
+
+void button_clicked() {
+    clicked = true;
+}
+
+// debug printf function
+void trace_printer(const char* str) {
+    printf("%s\r\n", str);
+}
+
+// Entry point to the program
+int main() {
+
+    unsigned int seed;
+    size_t len;
+
+#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 = 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_print_function_set(trace_printer);
+    mbed_trace_config_set(TRACE_MODE_COLOR | TRACE_ACTIVE_LEVEL_INFO | TRACE_CARRIAGE_RETURN);
+
+    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;
+	
+	// STMicroelectronics IKS01A1/2
+	EnvResource env_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);
+#elif defined(TARGET_NUCLEO_F429ZI) || defined (TARGET_NUCLEO_L476RG)
+    InterruptIn user_button(USER_BUTTON);
+	user_button.fall(&button_clicked);
+#else
+    // Send update of endpoint resource values to connector every 15 seconds periodically
+    timer.attach(&button_clicked, 15.0);
+#endif
+
+    // Create endpoint interface to manage register and unregister
+    mbed_client.create_interface(MBED_SERVER_ADDRESS, network);
+
+    // 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());
+    object_list.push_back(led_resource.get_object());
+    object_list.push_back(big_payload_resource.get_object());
+	
+	// STMicroelectronics IKS01A1/2
+	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);
+
+
+	// STMicroelectronics IKS01A1/2
+	// wait for registration before getting data
+	while (!mbed_client.register_successful())  {
+		Thread::wait(500);
+	}
+	registered = true;
+	blue_led = 1;
+	printf ("\r\nRegistered! Now getting sensor data\r\n\n");
+		
+    while (true) {
+           	  
+		int timer_val = env_resource.get_polling_period();
+		env_resource.update_resources();
+
+        if(clicked) {
+            clicked = false;
+            button_resource.handle_button_click();
+        }
+		Thread::wait(timer_val);
+    }
+
+    mbed_client.test_unregister();
+    status_ticker.detach();
+}