.. _lwm2m_interface: Lightweight M2M (LWM2M) ####################### .. contents:: :local: :depth: 2 Overview ******** Lightweight Machine to Machine (LwM2M) is an application layer protocol designed with device management, data reporting and device actuation in mind. Based on CoAP/UDP, `LwM2M`_ is a `standard `_ defined by the Open Mobile Alliance and suitable for constrained devices by its use of CoAP packet-size optimization and a simple, stateless flow that supports a REST API. One of the key differences between LwM2M and CoAP is that an LwM2M client initiates the connection to an LwM2M server. The server can then use the REST API to manage various interfaces with the client. LwM2M uses a simple resource model with the core set of objects and resources defined in the specification. Example LwM2M object and resources: Device ****************************************** *Object definition* .. list-table:: :header-rows: 1 * - Object ID - Name - Instance - Mandatory * - 3 - Device - Single - Mandatory *Resource definitions* ``* R=Read, W=Write, E=Execute`` .. list-table:: :header-rows: 1 * - ID - Name - OP\* - Instance - Mandatory - Type * - 0 - Manufacturer - R - Single - Optional - String * - 1 - Model - R - Single - Optional - String * - 2 - Serial number - R - Single - Optional - String * - 3 - Firmware version - R - Single - Optional - String * - 4 - Reboot - E - Single - Mandatory - * - 5 - Factory Reset - E - Single - Optional - * - 6 - Available Power Sources - R - Multiple - Optional - Integer 0-7 * - 7 - Power Source Voltage (mV) - R - Multiple - Optional - Integer * - 8 - Power Source Current (mA) - R - Multiple - Optional - Integer * - 9 - Battery Level % - R - Single - Optional - Integer * - 10 - Memory Free (Kb) - R - Single - Optional - Integer * - 11 - Error Code - R - Multiple - Optional - Integer 0-8 * - 12 - Reset Error - E - Single - Optional - * - 13 - Current Time - RW - Single - Optional - Time * - 14 - UTC Offset - RW - Single - Optional - String * - 15 - Timezone - RW - Single - Optional - String * - 16 - Supported Binding - R - Single - Mandatory - String * - 17 - Device Type - R - Single - Optional - String * - 18 - Hardware Version - R - Single - Optional - String * - 19 - Software Version - R - Single - Optional - String * - 20 - Battery Status - R - Single - Optional - Integer 0-6 * - 21 - Memory Total (Kb) - R - Single - Optional - Integer * - 22 - ExtDevInfo - R - Multiple - Optional - ObjLnk The server could query the ``Manufacturer`` resource for ``Device`` object instance 0 (the default and only instance) by sending a ``READ 3/0/0`` operation to the client. The full list of registered objects and resource IDs can be found in the `LwM2M registry`_. Zephyr's LwM2M library lives in the :zephyr_file:`subsys/net/lib/lwm2m`, with a client sample in :zephyr_file:`samples/net/lwm2m_client`. For more information about the provided sample see: :ref:`lwm2m-client-sample` The sample can be configured to use normal unsecure network sockets or sockets secured via DTLS. The Zephyr LwM2M library implements the following items: * engine to process networking events and core functions * RD client which performs BOOTSTRAP and REGISTRATION functions * SenML CBOR, SenML JSON, CBOR, TLV, JSON, and plain text formatting functions * LwM2M Technical Specification Enabler objects such as Security, Server, Device, Firmware Update, etc. * Extended IPSO objects such as Light Control, Temperature Sensor, and Timer By default, the library implements `LwM2M specification 1.0.2`_ and can be set to `LwM2M specification 1.1.1`_ with a Kconfig option. For more information about LwM2M visit `OMA Specworks LwM2M`_. Sample usage ************ To use the LwM2M library, start by creating an LwM2M client context :c:struct:`lwm2m_ctx` structure: .. code-block:: c /* LwM2M client context */ static struct lwm2m_ctx client; Create callback functions for LwM2M resource executions: .. code-block:: c static int device_reboot_cb(uint16_t obj_inst_id, uint8_t *args, uint16_t args_len) { LOG_INF("Device rebooting."); LOG_PANIC(); sys_reboot(0); return 0; /* won't reach this */ } The LwM2M RD client can send events back to the sample. To receive those events, setup a callback function: .. code-block:: c static void rd_client_event(struct lwm2m_ctx *client, enum lwm2m_rd_client_event client_event) { switch (client_event) { case LWM2M_RD_CLIENT_EVENT_NONE: /* do nothing */ break; case LWM2M_RD_CLIENT_EVENT_BOOTSTRAP_REG_FAILURE: LOG_DBG("Bootstrap registration failure!"); break; case LWM2M_RD_CLIENT_EVENT_BOOTSTRAP_REG_COMPLETE: LOG_DBG("Bootstrap registration complete"); break; case LWM2M_RD_CLIENT_EVENT_BOOTSTRAP_TRANSFER_COMPLETE: LOG_DBG("Bootstrap transfer complete"); break; case LWM2M_RD_CLIENT_EVENT_REGISTRATION_FAILURE: LOG_DBG("Registration failure!"); break; case LWM2M_RD_CLIENT_EVENT_REGISTRATION_COMPLETE: LOG_DBG("Registration complete"); break; case LWM2M_RD_CLIENT_EVENT_REG_TIMEOUT: LOG_DBG("Registration timeout!"); break; case LWM2M_RD_CLIENT_EVENT_REG_UPDATE_COMPLETE: LOG_DBG("Registration update complete"); break; case LWM2M_RD_CLIENT_EVENT_DEREGISTER_FAILURE: LOG_DBG("Deregister failure!"); break; case LWM2M_RD_CLIENT_EVENT_DISCONNECT: LOG_DBG("Disconnected"); break; } } Next we assign ``Security`` resource values to let the client know where and how to connect as well as set the ``Manufacturer`` and ``Reboot`` resources in the ``Device`` object with some data and the callback we defined above: .. code-block:: c /* * Server URL of default Security object = 0/0/0 * Use leshan.eclipse.org server IP (5.39.83.206) for connection */ lwm2m_engine_set_string("0/0/0", "coap://5.39.83.206"); /* * Security Mode of default Security object = 0/0/2 * 3 = NoSec mode (no security beware!) */ lwm2m_engine_set_u8("0/0/2", 3); #define CLIENT_MANUFACTURER "Zephyr Manufacturer" /* * Manufacturer resource of Device object = 3/0/0 * We use lwm2m_engine_set_res_data() function to set a pointer to the * CLIENT_MANUFACTURER string. * Note the LWM2M_RES_DATA_FLAG_RO flag which stops the engine from * trying to assign a new value to the buffer. */ lwm2m_engine_set_res_data("3/0/0", CLIENT_MANUFACTURER, sizeof(CLIENT_MANUFACTURER), LWM2M_RES_DATA_FLAG_RO); /* Reboot resource of Device object = 3/0/4 */ lwm2m_engine_register_exec_callback("3/0/4", device_reboot_cb); Lastly, we start the LwM2M RD client (which in turn starts the LwM2M engine). The second parameter of :c:func:`lwm2m_rd_client_start` is the client endpoint name. This is important as it needs to be unique per LwM2M server: .. code-block:: c (void)memset(&client, 0x0, sizeof(client)); lwm2m_rd_client_start(&client, "unique-endpoint-name", 0, rd_client_event); Using LwM2M library with DTLS ***************************** The Zephyr LwM2M library can be used with DTLS transport for secure communication by selecting :kconfig:option:`CONFIG_LWM2M_DTLS_SUPPORT`. In the client initialization we need to create a PSK and identity. These need to match the security information loaded onto the LwM2M server. Normally, the endpoint name is used to lookup the related security information: .. code-block:: c /* "000102030405060708090a0b0c0d0e0f" */ static unsigned char client_psk[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; static const char client_identity[] = "Client_identity"; Next we alter the ``Security`` object resources to include DTLS security information. The server URL should begin with ``coaps://`` to indicate security is required. Assign a 0 value (Pre-shared Key mode) to the ``Security Mode`` resource. Lastly, set the client identity and PSK resources. .. code-block:: c /* Use coaps:// for server URL protocol */ lwm2m_engine_set_string("0/0/0", "coaps://5.39.83.206"); /* 0 = Pre-Shared Key mode */ lwm2m_engine_set_u8("0/0/2", 0); /* Set the client identity */ lwm2m_engine_set_string("0/0/3", (char *)client_identity); /* Set the client pre-shared key (PSK) */ lwm2m_engine_set_opaque("0/0/5", (void *)client_psk, sizeof(client_psk)); Before calling :c:func:`lwm2m_rd_client_start` assign the tls_tag # where the LwM2M library should store the DTLS information prior to connection (normally a value of 1 is ok here). .. code-block:: c (void)memset(&client, 0x0, sizeof(client)); client.tls_tag = 1; /* <---- */ lwm2m_rd_client_start(&client, "endpoint-name", 0, rd_client_event); For a more detailed LwM2M client sample see: :ref:`lwm2m-client-sample`. Multi-thread usage ****************** Writing a value to a resource can be done using functions like lwm2m_engine_set_u8. When writing to multiple resources, the function lwm2m_registry_lock will ensure that the client halts until all writing operations are finished: .. code-block:: c lwm2m_registry_lock(); lwm2m_engine_set_u32("1/0/1", 60); lwm2m_engine_set_u8("5/0/3", 0); lwm2m_engine_set_float("3303/0/5700", &value); lwm2m_registry_unlock(); This is especially useful if the server is composite-observing the resources being written to. Locking will then ensure that the client only updates and sends notifications to the server after all operations are done, resulting in fewer messages in general. Support for time series data **************************** LwM2M version 1.1 adds support for SenML CBOR and SenML JSON data formats. These data formats add support for time series data. Time series formats can be used for READ, NOTIFY and SEND operations. When data cache is enabled for a resource, each write will create a timestamped entry in a cache, and its content is then returned as a content in in READ, NOTIFY or SEND operation for a given resource. Data cache is only supported for resources with a fixed data size. Supported resource types: * Signed and unsigned 8-64-bit integers * Float * Boolean Enabling and configuring ======================== Enable data cache by selecting :kconfig:option:`CONFIG_LWM2M_RESOURCE_DATA_CACHE_SUPPORT`. Application needs to allocate an array of :c:struct:`lwm2m_time_series_elem` structures and then enable the cache by calling :c:func:`lwm2m_engine_enable_cache` for a given resource. Earch resource must be enabled separately and each resource needs their own storage. .. code-block:: c /* Allocate data cache storage */ static struct lwm2m_time_series_elem temperature_cache[10]; /* Enable data cache */ lwm2m_engine_enable_cache(LWM2M_PATH(IPSO_OBJECT_TEMP_SENSOR_ID, 0, SENSOR_VALUE_RID), temperature_cache, ARRAY_SIZE(temperature_cache)); LwM2M engine have room for four resources that have cache enabled. Limit can be increased by changing :kconfig:option:`CONFIG_LWM2M_MAX_CACHED_RESOURCES`. This affects a static memory usage of engine. Data caches depends on one of the SenML data formats :kconfig:option:`CONFIG_LWM2M_RW_SENML_CBOR_SUPPORT` or :kconfig:option:`CONFIG_LWM2M_RW_SENML_JSON_SUPPORT` and needs :kconfig:option:`CONFIG_POSIX_CLOCK` so it can request a timestamp from the system and :kconfig:option:`CONFIG_RING_BUFFER` for ring buffer. Read and Write operations ========================= Full content of data cache is written into a payload when any READ, SEND or NOTIFY operation internally reads the content of a given resource. This has a side effect that any read callbacks registered for a that resource are ignored when cache is enabled. Data is written into a cache when any of the ``lwm2m_engine_set_*`` functions are called. To filter the data entering the cache, application may register a validation callback using :c:func:`lwm2m_engine_register_validate_callback`. Limitations =========== Cache size should be manually set so small that the content can fit normal packets sizes. When cache is full, new values are dropped. LwM2M engine and application events *********************************** The Zephyr LwM2M engine defines events that can be sent back to the application through callback functions. The engine state machine shows when the events are spawned. Events depicted in the diagram are listed in the table. The events are prefixed with ``LWM2M_RD_CLIENT_EVENT_``. .. figure:: ../../../../../../../../zephyr/doc/connectivity/networking/api/images/lwm2m_engine_state_machine.png :alt: LwM2M engine state machine State machine for the LwM2M engine .. list-table:: LwM2M RD Client events :widths: auto :header-rows: 1 * - Event ID - Event Name - Description - Actions * - 0 - NONE - No event - Do nothing * - 1 - BOOTSTRAP_REG_FAILURE - Bootstrap registration failed. Occurs if there is a timeout or failure in bootstrap registration. - Retry bootstrap * - 2 - BOOTSTRAP_REG_COMPLETE - Bootstrap registration complete. Occurs after successful bootstrap registration. - No actions needed * - 3 - BOOTSTRAP_TRANSFER_COMPLETE - Bootstrap finish command received from the server. - No actions needed, client proceeds to registration. * - 4 - REGISTRATION_FAILURE - Registration to LwM2M server failed. Occurs if there is a failure in the registration. - Retry registration * - 5 - REGISTRATION_COMPLETE - Registration to LwM2M server successful. Occurs after a successful registration reply from the LwM2M server or when session resumption is used. - No actions needed * - 6 - REG_TIMEOUT - Registration or registration update timeout. Occurs if there is a timeout during registration. NOTE: If registration fails without a timeout, a full registration is triggered automatically and no registration update failure event is generated. - No actions needed, client proceeds to re-registration automatically. * - 7 - REG_UPDATE_COMPLETE - Registration update completed. Occurs after successful registration update reply from the LwM2M server. - No actions needed * - 8 - DEREGISTER_FAILURE - Deregistration to LwM2M server failed. Occurs if there is a timeout or failure in the deregistration. - No actions needed, client proceeds to idle state automatically. * - 9 - DISCONNECT - Disconnected from LwM2M server. Occurs if there is a timeout during communication with server. Also triggered after deregistration has been done. - If connection is required, the application should restart the client. * - 10 - QUEUE_MODE_RX_OFF - Used only in queue mode, not actively listening for incoming packets. In queue mode the client is not required to actively listen for the incoming packets after a configured time period. - No actions needed * - 11 - NETWORK_ERROR - Sending messages to the network failed too many times. If sending a message fails, it will be retried. If the retry counter reaches its limits, this event will be triggered. - No actions needed, client will do a re-registrate automatically. .. _lwm2m_shell: LwM2M shell *********** For testing the client it is possible to enable Zephyr's shell and LwM2M specific commands which support changing the state of the client. Operations supported are read, write and execute resources. Client start, stop, pause and resume are also available. The feature is enabled by selecting :kconfig:option:`CONFIG_LWM2M_SHELL`. The shell is meant for testing so productions systems should not enable it. One imaginable scenario, where to use the shell, would be executing client side actions over UART when a server side tests would require those. It is assumed that not all tests are able to trigger required actions from the server side. .. code-block:: console uart:~$ lwm2m lwm2m - LwM2M commands Subcommands: exec :Execute a resource exec PATH read :Read value from LwM2M resource read PATH [OPTIONS] -s Read value as string (default) -b Read value as bool (1/0) -uX Read value as uintX_t -sX Read value as intX_t -f Read value as float write :Write into LwM2M resource write PATH [OPTIONS] VALUE -s Value as string (default) -b Value as bool -uX Value as uintX_t -sX Value as intX_t -f Value as float start :Start the LwM2M RD (Registration / Discovery) Client start EP_NAME [BOOTSTRAP FLAG] -b Set the bootstrap flag (default 0) stop :Stop the LwM2M RD (De-register) Client stop [OPTIONS] -f Force close the connection update :Trigger Registration Update of the LwM2M RD Client pause :LwM2M engine thread pause resume :LwM2M engine thread resume .. _lwm2m_api_reference: API Reference ************* .. doxygengroup:: lwm2m_api .. _LwM2M: https://www.omaspecworks.org/what-is-oma-specworks/iot/lightweight-m2m-lwm2m/ .. _LwM2M registry: http://www.openmobilealliance.org/wp/OMNA/LwM2M/LwM2MRegistry.html .. _OMA Specworks LwM2M: https://www.omaspecworks.org/what-is-oma-specworks/iot/lightweight-m2m-lwm2m/ .. _LwM2M specification 1.0.2: http://openmobilealliance.org/release/LightweightM2M/V1_0_2-20180209-A/OMA-TS-LightweightM2M-V1_0_2-20180209-A.pdf .. _LwM2M specification 1.1.1: http://openmobilealliance.org/release/LightweightM2M/V1_1_1-20190617-A/