A simple interface to mbed Device Connector, where you just declare variables to push them to the cloud.

Dependents:   Wifi_Get_Test_V1 simple-mbed-client-example simple-client-app-shield simple-sensor-client

Fork of simple-mbed-client by Jan Jongboom

TL;DR? See simple-mbed-client-example to get started immediately.

This library is a simpler interface to mbed Client, making it trivial to expose sensors, actuators and other variables to the cloud. It does not require you to change how you write your code. You can take any local variable, swap it out for a call to Simple Mbed Client, and the variable will automatically be synchronised with mbed Cloud.

For example, here's how you expose the value of a light sensor to the cloud:

SimpleMbedClient client;

SimpleResourceInt light_value = client.define_resource("light/0/value", 0);     // create the var

AnalogIn light(A1);

void read_light_sensor() {
    // light_value behaves just like a normal variable that you can read and write to!
    light_value = light.read_u16();
}

// update every second
Ticker t;
t.attach(&read_light_sensor, 1.0f);

Setting up

First import this library to your project. As Simple Mbed Client also needs a way to talk to the outside world, you'll need a NetworkInterface-object. The easiest way is by using the easy-connect library, so add that to your project as well. See the easy-connect docs on how to specify the connectivity method.

We also need a way of authenticating with mbed Cloud. For this we need a security certificate. Go to mbed Cloud, and select 'GET MY DEVICE SECURITY CREDENTIALS'. Save the certificate as security.h in your project folder.

Now we can initiate Simple Mbed Client and connect it to the internet.

#include "mbed.h"
#include "security.h"
#include "easy-connect.h"
#include "simple-mbed-client.h"

SimpleMbedClient client;

DigitalOut led(LED1, 0);

void registered() {
    led = 1;
}

int main() {
    NetworkInterface* network = connect_to_network(); // if connection failed, network will be NULL
    client.setup(network); // returns a bool, check if it's true

    client.on_registered(&registered);

    while (1) {
        wait_ms(25000);
        client.keep_alive();
    }
}

Defining variables

You can define a new variable by a call to client.define_resource. This function takes five arguments:

  1. path - The URL on which your variable is exposed in mbed Cloud. Needs to be three (3) segments, split by a slash (/) in the form of 'sensor/0/value'. The second segment always needs to be numeric.
  2. defaultValue - The default value of the variable. Needs to be either a string or an integer. Depending on the type that you pass in here the type of the variable is defined.
  3. operation - Some variables might be read-only or write-only (seen from the cloud). Use the operation to define these constraints. It's of type M2MBase::Operation. Default is GET_PUT_ALLOWED.
  4. observable - If set to false, cloud applications cannot subscribe to updates on this variable. Default is true.
  5. callback - Function pointer which is called whenever the value of the variable is changed from the cloud.

The type returned by the function is either SimpleResourceInt or SimpleResourceString. You can assign and read from these variables like any normal local variable.

void name_updated(string new_value) {
    printf("Value is now %s\n", new_value.c_str());
}

SimpleResourceString name = client.define_resource("device/0/name", "jan", M2MBase::GET_PUT_ALLOWED, true, &name_updated);

// we can read and write to this variable, e.g.:
stringstream ss;
ss << name;

// or
name = "pietje";

// are all valid

Defining functions

You can define functions, which do not have a value, but can just be invoked from the cloud, by a call to client.define_function. This function takes two arguments:

  1. path - The URL on which your variable is exposed in mbed Cloud. Needs to be three (3) segments, split by a slash (/) in the form of 'sensor/0/value'. The second segment always needs to be numeric.
  2. callback - Function pointer which is invoked when the function is called. Takes in a pointer, which contains the data being passed in from the cloud.

void play(void* data) {
    if (data) { // data can be NULL!
        // cast it to something useful
    }
}

client.define_function("music/0/play", &play);

Accessing the underlying M2MResource

If you need access to the underlying M2MResource you can do so by calling get_resource on a variable, or by calling client.get_resource if it's a function.

SimpleResourceInt led = client.define_resource("led/0/value", true);

client.define_function("led/0/toggle", &toggleLed);

// now to get the resource
M2MResource* ledResource = led.get_resource();
M2MResource* toggleResource = client.get_resource("led/0/toggle");

Printing variables

Unfortunately printf is kind of dumb, and does not automatically cast the variables. If you want to print any of the Simple Mbed Client variables you'll need to cast yourself.

SimpleResourceInt led = client.define_resource("led/0/value", true);

printf("Value is currently %d\n", static_cast<int>(led));

Event Queue

Simple Mbed Client uses an mbed-events EventQueue - running on a separate RTOS thread - to handle incoming events without blocking the main loop. Both the thread and event queue are created when initializing the library. You can override this behavior by providing your own event queue. In this case no thread is created.

EventQueue myQueue;
SimpleMbedClient client(&myQueue);

You can also use the queue to process your own events, which is very useful when dealing with ISRs. The queue is accessible through the eventQueue() function on the client object and returns a pointer to the queue.

SimpleMbedClient client;

InterruptIn btn(D2);

int main() {
  btn.fall(client.eventQueue()->event(&fall));
}

mbed-client-wrapper.h

Committer:
Jan Jongboom
Date:
2017-03-21
Revision:
23:c89df15e88d2
Parent:
12:26810c6b58e1

File content as of revision 23:c89df15e88d2:

/*
 * 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.
 */

#ifndef __SIMPLE_MBED_CLIENT_WRAPPER_H__
#define __SIMPLE_MBED_CLIENT_WRAPPER_H__

#include "mbed-client/m2minterfacefactory.h"
#include "mbed-client/m2mdevice.h"
#include "mbed-client/m2minterfaceobserver.h"
#include "mbed-client/m2minterface.h"
#include "mbed-client/m2mobjectinstance.h"
#include "mbed-client/m2mresource.h"

struct MbedClientOptions {
    const char* Manufacturer;
    const char* Type;
    const char* ModelNumber;
    const char* SerialNumber;
    const char* DeviceType;
    M2MInterface::BindingMode SocketMode;
    const char* ServerAddress;
};

/*
* Wrapper for mbed client stack that handles all callbacks, error handling, and
* other schenanigans to make the mbed client stack easier to use.
*
* The end user should only have to care about configuring the parameters at the
* top of this file and making sure they add the security.h file correctly.
* To add resources you can copy the _TODO__ function and add as many instances as
* you want.
*
*/
class MbedClient: public M2MInterfaceObserver {
public:
    // constructor for MbedClient object, initialize private variables
    MbedClient(struct MbedClientOptions options, Callback<void(string)> onValueChanged, bool debug) :
        _onValueChanged(onValueChanged), _debug(debug)
    {
        _interface = NULL;
        _bootstrapped = false;
        _error = false;
        _registered = false;
        _unregistered = false;
        _register_security = NULL;
        _value = 0;
        _object = NULL;
        _options = options;
        _onRegistered = NULL;
        _onUnregistered = NULL;
    }

    // de-constructor for MbedClient object, you can ignore this
    ~MbedClient() {
        if(_interface) {
            delete _interface;
        }
        if(_register_security){
            delete _register_security;
        }
    }

    // debug printf function
    void trace_printer(const char* str) {
        if (_debug) printf("[SMC] %s\r\n", str);
    }

    void set_registered_function(Callback<void()> onRegistered) {
        _onRegistered = onRegistered;
    }

    void set_unregistered_function(Callback<void()> onUnregistered) {
        _onUnregistered = onUnregistered;
    }

    /*
    *  Creates M2MInterface using which endpoint can
    *  setup its name, resource type, life time, connection mode,
    *  Currently only LwIPv4 is supported.
    */
    void create_interface(NetworkInterface* iface) {
        // Randomizing listening port for Certificate mode connectivity
        srand(time(NULL));
        uint16_t port = rand() % 65535 + 12345;

        // create mDS interface object, this is the base object everything else attaches to
        _interface = M2MInterfaceFactory::create_interface(*this,
                                                          MBED_ENDPOINT_NAME,       // endpoint name string
                                                          _options.DeviceType,      // endpoint type string
                                                          100,                      // lifetime
                                                          port,                     // listen port
                                                          MBED_DOMAIN,              // domain string
                                                          _options.SocketMode,      // binding mode
                                                          M2MInterface::LwIP_IPv4,  // network stack
                                                          "");                      // context address string

        _interface->set_platform_network_handler((void*)iface);
    }

    /*
    *  check private variable to see if the registration was sucessful or not
    */
    bool register_successful() {
        return _registered;
    }

    /*
    *  check private variable to see if un-registration was sucessful or not
    */
    bool unregister_successful() {
        return _unregistered;
    }

    /*
    *  Creates register server object with mbed device server address and other parameters
    *  required for client to connect to mbed device server.
    */
    M2MSecurity* create_register_object() {
        // create security object using the interface factory.
        // this will generate a security ObjectID and ObjectInstance
        M2MSecurity *security = M2MInterfaceFactory::create_security(M2MSecurity::M2MServer);

        // make sure security ObjectID/ObjectInstance was created successfully
        if(security) {
            // Add ResourceID's and values to the security ObjectID/ObjectInstance
            security->set_resource_value(M2MSecurity::M2MServerUri, _options.ServerAddress);
            security->set_resource_value(M2MSecurity::SecurityMode, M2MSecurity::Certificate);
            security->set_resource_value(M2MSecurity::ServerPublicKey, SERVER_CERT, sizeof(SERVER_CERT));
            security->set_resource_value(M2MSecurity::PublicKey, CERT, sizeof(CERT));
            security->set_resource_value(M2MSecurity::Secretkey, KEY, sizeof(KEY));
        }
        return security;
    }

    /*
    * Creates device object which contains mandatory resources linked with
    * device endpoint.
    */
    M2MDevice* create_device_object() {
        // create device objectID/ObjectInstance
        M2MDevice *device = M2MInterfaceFactory::create_device();
        // make sure device object was created successfully
        if(device) {
            // add resourceID's to device objectID/ObjectInstance
            device->create_resource(M2MDevice::Manufacturer, _options.Manufacturer);
            device->create_resource(M2MDevice::DeviceType, _options.Type);
            device->create_resource(M2MDevice::ModelNumber, _options.ModelNumber);
            device->create_resource(M2MDevice::SerialNumber, _options.SerialNumber);
        }
        return device;
    }

    /*
    * register an object
    */
    void test_register(M2MSecurity *register_object, M2MObjectList object_list){
        if(_interface) {
            // Register function
            _interface->register_object(register_object, object_list);
            _registered = true;
        }
    }

    /*
    * unregister all objects
    */
    void test_unregister() {
        if(_interface) {
            // Unregister function
            _interface->unregister_object(NULL); // NULL will unregister all objects
        }
    }

    //Callback from mbed client stack when the bootstrap
    // is successful, it returns the mbed Device Server object
    // which will be used for registering the resources to
    // mbed Device server.
    virtual void bootstrap_done(M2MSecurity *server_object){
        if(server_object) {
            _bootstrapped = true;
            _error = false;
            trace_printer("Bootstrapped");
        }
    }

    //Callback from mbed client stack when the registration
    // is successful, it returns the mbed Device Server object
    // to which the resources are registered and registered objects.
    virtual void object_registered(M2MSecurity */*security_object*/, const M2MServer &/*server_object*/){
        _registered = true;
        _unregistered = false;
        trace_printer("Registered object successfully!");

        if (_onRegistered) {
            _onRegistered.call();
        }
    }

    //Callback from mbed client stack when the unregistration
    // is successful, it returns the mbed Device Server object
    // to which the resources were unregistered.
    virtual void object_unregistered(M2MSecurity */*security_object*/){
        _registered = false;
        _unregistered = true;
        trace_printer("Unregistered Object Successfully");

        if (_onUnregistered) {
            _onUnregistered.call();
        }
    }

    /*
    * Callback from mbed client stack when registration is updated
    */
    virtual void registration_updated(M2MSecurity */*security_object*/, const M2MServer & /*server_object*/){
        /* The registration is updated automatically and frequently by the
        *  mbed client stack. This print statement is turned off because it
        *  tends to happen alot.
        */
        //trace_printer("\r\nRegistration Updated\r\n");
    }

    // Callback from mbed client stack if any error is encountered
    // during any of the LWM2M operations. Error type is passed in
    // the callback.
    virtual void error(M2MInterface::Error error){
        _error = true;
        switch(error){
            case M2MInterface::AlreadyExists:
                trace_printer("[ERROR:] M2MInterface::AlreadyExist");
                break;
            case M2MInterface::BootstrapFailed:
                trace_printer("[ERROR:] M2MInterface::BootstrapFailed");
                break;
            case M2MInterface::InvalidParameters:
                trace_printer("[ERROR:] M2MInterface::InvalidParameters");
                break;
            case M2MInterface::NotRegistered:
                trace_printer("[ERROR:] M2MInterface::NotRegistered");
                break;
            case M2MInterface::Timeout:
                trace_printer("[ERROR:] M2MInterface::Timeout");
                break;
            case M2MInterface::NetworkError:
                trace_printer("[ERROR:] M2MInterface::NetworkError");
                break;
            case M2MInterface::ResponseParseFailed:
                trace_printer("[ERROR:] M2MInterface::ResponseParseFailed");
                break;
            case M2MInterface::UnknownError:
                trace_printer("[ERROR:] M2MInterface::UnknownError");
                break;
            case M2MInterface::MemoryFail:
                trace_printer("[ERROR:] M2MInterface::MemoryFail");
                break;
            case M2MInterface::NotAllowed:
                trace_printer("[ERROR:] M2MInterface::NotAllowed");
                break;
            default:
                break;
        }
    }

    /* Callback from mbed client stack if any value has changed
    *  during PUT operation. Object and its type is passed in
    *  the callback.
    *  BaseType enum from m2mbase.h
    *       Object = 0x0, Resource = 0x1, ObjectInstance = 0x2, ResourceInstance = 0x3
    */
    virtual void value_updated(M2MBase *base, M2MBase::BaseType type) {
        if (_debug) printf("[SMC] PUT Request Received, type=%d\n", type);
        if (strcmp(base->uri_path(), "") != 0) {
            if (_debug) printf("[SMC] PUT came in for %s\n", base->uri_path());

            _onValueChanged.call(string(base->uri_path()));
        }
    }

    /*
    * update the registration period
    */
    void test_update_register() {
        if (_registered) {
            _interface->update_registration(_register_security, 100);
        }
    }

    /*
    * manually configure the security object private variable
    */
   void set_register_object(M2MSecurity *register_object) {
        if (_register_security == NULL) {
            _register_security = register_object;
        }
    }

private:

    /*
    *  Private variables used in class
    */
    M2MInterface                        *_interface;
    M2MSecurity                         *_register_security;
    M2MObject                           *_object;
    volatile bool                       _bootstrapped;
    volatile bool                       _error;
    volatile bool                       _registered;
    volatile bool                       _unregistered;
    int                                 _value;
    struct MbedClientOptions            _options;
    Callback<void()>                    _onRegistered;
    Callback<void()>                    _onUnregistered;
    Callback<void(string)>              _onValueChanged;
    bool                                _debug;
};

#endif // __SIMPLE_MBED_CLIENT_WRAPPER_H__