mbedOS client example, modified to push X-Nucleo-IKS01A1/2 Environmental Sensor data to mbed Cloud Connector.
Dependencies: X_NUCLEO_IKS01A1 X_NUCLEO_IKS01A2
The application is derived from the official mbedOS client example (link) and has been tested using 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-IKS02A1 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.
- Connect the board to your ethernet network, open a serial terminal (params 115200N1) and wait that the client is connected to the mbed Connector.
- Press user button to start acquiring and pushing the environmental (pressure, temperature and humidity) data.
Note: environmental data are expressed using IPSO representation based on OMA LWM2M standard.
Diff: main.cpp
- Revision:
- 0:003e60a0deb8
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.cpp Wed Apr 26 18:23:56 2017 +0200 @@ -0,0 +1,648 @@ +/* + * 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. + */ + + +#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(); + } + + ~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++; + #if defined(TARGET_K64F) || defined(TARGET_NUCLEO_F429ZI) + 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(const char* endpointNumber, const char* resName) { +#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 + env_object = M2MInterfaceFactory::create_object(endpointNumber); + M2MObjectInstance* env_inst = env_object->create_object_instance(); + M2MResource* env_res = env_inst->create_dynamic_resource("5700", resName, + M2MResourceInstance::FLOAT, true); // observable + // we can only read this value + env_res->set_operation(M2MBase::GET_ALLOWED); + + M2MResource* timer_res = env_inst->create_dynamic_resource("5603", "PollingPeriodMs", + M2MResourceInstance::INTEGER, false); // not observable + // we can read/wr this value + timer_res->set_operation(M2MBase::GET_PUT_ALLOWED); + timer_res->set_value(DEFAULT_ENV_POLLING_PERIOD_MS); + } + + virtual void init_resource()=0; + + M2MObject* get_object() { + return env_object; + } + + void start_measure (void) { + env_thread.start(this, &EnvResource::read_env); + } + +protected: +#ifdef IKS01A2 + XNucleoIKS01A2 *p_mems_expansion_board; +#else + XNucleoIKS01A1 *p_mems_expansion_board; +#endif + M2MObject* env_object; + + virtual void update_resource(M2MResource* resource)=0; + + void read_env(void) { + + M2MObjectInstance* inst = env_object->object_instance(); + M2MResource* res_env = inst->resource("5700"); + M2MResource* res_timer = inst->resource("5603"); + + while (1) { + env_mutex.lock(); // avoid concurrent access to the component + update_resource(res_env); + env_mutex.unlock(); + int timer_val = res_timer->get_value_int(); + Thread::wait(timer_val); + } + } + +private: + Thread env_thread; + static Mutex env_mutex; +}; + +Mutex EnvResource::env_mutex; + +class TempResource : public EnvResource { +public: + TempResource() : EnvResource("3303", "Temperature") {} + + void init_resource () { + M2MObjectInstance* inst = env_object->object_instance(); + M2MResource* resource = inst->resource("5700"); + resource->set_value((uint8_t*)"20.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 + } + +protected: + void update_resource (M2MResource* resource) { + float temp; + int err; + err = p_mems_expansion_board->ht_sensor->get_temperature(&temp); + if ( err != SENSOR_OK) { + printf ("= * ERROR %d get_temperature\n\r", err); + return; + } else { + printf ("Temp C: %f\n\r", temp); + } + stringstream ss; + ss << temp; + std::string stringified = ss.str(); + resource->set_value((uint8_t*)stringified.c_str(), stringified.length()); + } + +}; + +class HumResource : public EnvResource { +public: + HumResource() : EnvResource("3304", "Humidity") {} + + void init_resource () { + M2MObjectInstance* inst = env_object->object_instance(); + M2MResource* resource = inst->resource("5700"); + resource->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 + } + +protected: + void update_resource (M2MResource* resource) { + float hum; + int err; + err = p_mems_expansion_board->ht_sensor->get_humidity(&hum); + if ( err != SENSOR_OK) { + printf ("= * ERROR %d get_humidity\n\r", err); + return; + } else { + printf ("Humidity %: %f\n\r", hum); + } + stringstream ss; + ss << hum; + std::string stringified = ss.str(); + resource->set_value((uint8_t*)stringified.c_str(), stringified.length()); + } +}; + +class PressResource : public EnvResource { +public: + PressResource() : EnvResource("3300", "Pressure") {} + + void init_resource () { + M2MObjectInstance* inst = env_object->object_instance(); + M2MResource* resource = inst->resource("5700"); + resource->set_value((uint8_t*)"1000", 4); +#ifdef IKS01A2 // need to explicitly enable the sensor with IKS01A2 + LPS22HBSensor *press_temp = p_mems_expansion_board->pt_sensor; + press_temp->enable(); +#endif + } + +protected: + void update_resource (M2MResource* resource) { + float press; + int err; + err = p_mems_expansion_board->pt_sensor->get_pressure(&press); + if ( err != SENSOR_OK) { + printf ("= * ERROR %d get_pressure\n\r", err); + return; + } else { + printf ("Pressure mBar: %f\n\r", press); + } + stringstream ss; + ss << press; + std::string stringified = ss.str(); + resource->set_value((uint8_t*)stringified.c_str(), stringified.length()); + } +}; + +/* 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; + updates.release(); +} + +void button_clicked() { + clicked = true; + updates.release(); +} + +// 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 in "); +#if defined (MESH) || (MBED_CONF_LWIP_IPV6_ENABLED==true) + printf("IPv6 mode\n"); +#else + printf("IPv4 mode\n"); +#endif + + 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; + + // STMicroelectronics IKS01A1/2 + TempResource temp_resource; + temp_resource.init_resource(); // set the default value + HumResource hum_resource; + hum_resource.init_resource(); // set the default value + PressResource press_resource; + press_resource.init_resource(); // set the default value + +#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) + 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(temp_resource.get_object()); + object_list.push_back(hum_resource.get_object()); + object_list.push_back(press_resource.get_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; + + // STMicroelectronics IKS01A1/2 + updates.wait(); // do not start before the user button is pressed once + clicked = false; // do not increase the counter + printf ("\r\nStart getting sensor data\r\n\n"); + temp_resource.start_measure(); // start getting data from sensor + hum_resource.start_measure(); // start getting data from sensor + press_resource.start_measure(); // start getting data from sensor + + 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(); + } + } + + mbed_client.test_unregister(); + status_ticker.detach(); +}