Linux Embedded

Le blog des technologies libres et embarquées

LWM2M over LTE-M, mise en oeuvre via Zephyr

1. Introduction

L'objectif de cet article est la présentation d’un écosystème complet autour de la technologie LTE-M et la mise en œuvre d’un démonstrateur LWM2M au travers de l’OS Zephyr.
Le LWM2M est un protocole qui commence à faire ses preuves dans le monde de l’IoT et qui est totalement adapté à la technologie LPWAN (Low Power Area Network), le LTE-M.
Le monde de l’Open Source met à disposition tout l’environnement nécessaire pour une mise en place rapide et efficace.

2. Le LT-M et ses caractéristiques

Le LTE-M est la technologie de radiocommunication sur laquelle va reposer le démonstrateur décrit par cet article. Il convient d’en brosser les principales caractéristiques afin de pouvoir projeter le potentiel des objets utilisant cette technologie. Les aspects débit, couverture, consommation d’énergie seront adressés dans les paragraphes suivants.

2.1. Un nouveau réseau cellulaire

Le LTE-M (Long-Term Evolution for Machines) est un réseau cellulaire basé sur la technologie LTE utilisée notamment par la 4G. Il englobe les appellations LTE CAT-M1 introduite par la release 13 de la 3GPP et bientôt LTE CAT-M2, introduite par la release 14. Ci-dessous une vue macroscopique des évolutions depuis le début du LTE.

Le LTE-M, à l’instar du NB-IOT, repose sur des technologies éprouvées dans le monde du cellulaire

2.2. Des caractéristiques orientées LPWAN

Le LTE-M est une technologie cellulaire mais qui possède des caractéristiques permettant de l’orienter vers des produits nécessitant une autonomie élevée. Les principales caractéristiques de cette technologie sont les suivantes :

Modulation OFDMA
Bandwidth 1.4 - 20 MHz
Débit 200kbps - 1Mbps (Cat-M1)
Latence 10ms - 15ms
Autonomie Entre 5 et 10 ans suivant les cas d’usage
Bilan de liaison 156dB
Portée Environ 10kms
 

Les bandes de fréquences adressées sont spécifiées ci-dessous, car à traiter de manière plus globale :

Ces bandes de fréquence sont en constante évolution et de nouvelles bandes intègrent régulièrement l’écosystème permettant d’adresser de nouveaux cas d’usage et de nouvelles zones géographiques.
Lors de la sélection de cette technologie, il est important de se pencher sur les marchés visés et sur les partenaires/fournisseurs de connectivité pouvant répondre au besoin exprimé.

2.3. Des modes particuliers

La technologie LTE-M, tout comme son proche parent le NB-IOT, possède des paramètres très particuliers lui permettant notamment d'accroître la durée de vie des objets lorsque ces derniers sont sur batteries. Ces modes sont appelés PSM (Power Saving Mode) et eDRx (extended Discontinuous Reception)

Le mode PSM permet au modem de rentrer dans un endormissement profond sans pour autant perdre toutes les informations de connexions avec la cellule à laquelle il est rattaché. Au réveil du modem, ce dernier est très rapidement opérationnel.
L’endormissement du modem se produit après une transmission selon un réglage de timing définis dans ce même modem.

 

L’eDRx, quant à lui, est un mode où le device est joignable par le coeur de réseau pendant une phase de paging. Un cycle standard est de 1.28s.
Les intervalles de paging peuvent être configurés afin d’améliorer l’autonomie des devices. Ces intervalles peuvent aller de 5.12s à 40 minutes.

 

 

Même avec ces nouveaux modes propres à l'économie d'énergie, le handover d'une cellule à un autre sur le réseau LTE est garanti par la technologie LTE-M.

Tout comme le roaming, traité au travers du paragraphe suivant.

2.4. Accessible mais où ?

Un des enjeux majeurs de ce type de technologie est l’accessibilité et la couverture garantie par les fournisseurs de connectivités. En effet, avant de déployer un objet capable de transmettre en LTE-M, il est nécessaire de faire une étude sur la disponibilité du service sur le territoire visé. En s’appuyant sur les cartes mises à disposition par la GSMA, organisme assurant les intérêts des différents opérateurs, on constate que le LTE-M n’est pas encore accessible partout dans le monde :


Cela est à prendre en compte lors du choix du modem et il apparaît que le fallback 2G est indispensable pour un produit worldwide.
De même, lors du choix du fournisseur de connectivité, il faut s’assurer que ce dernier possède des accords de roaming avec les opérateurs des zones géographiques visées.

Avec tous ces éléments disponibles dans votre boîte à outil, vous êtes à présent prêts à vous pencher sur le protocole se posant sur la technologie de connectivité.

 

3. Le LWM2M, pourquoi ce protocole

Ce protocole est en pleine expansion dans le monde de l’IoT. Il doit faire face à la concurrence rude du MQTT mais il parvient à faire la différence sur certains aspects lui permettant de se démarquer.

Le LWM2M (Lightweight Machine to Machine) est porté par l’OMA Alliance. Les spécifications évoluent régulièrement et sont ouvertes à tous.

Voici un graphique intéressant permettant de voir les différents layers sur lesquels reposent ce protocole :

Dans notre article, nous allons porter notre attention sur le lien CoAP/UDP/IP.
En effet, le LWM2M over UDP permet d’avoir des performances intéressantes en termes de consommation de données et de consommation d’énergie.

Une étude de MachNation sur le sujet permet de donner un aperçu de ce que ce protocole peut apporter :

 

On constate que le MQTT est un protocole simple à mettre en œuvre mais que lorsque l'économie d’énergie doit être optimisée, il est plus intéressant de se tourner vers le LWM2M.

De même, le fait que le LWM2M soit un protocole standardisé est un plus pour les entités qui n’ont pas les ressources pour définir et maintenir un protocole propriétaire. Ceci est notamment le cas pour le MQTT.

Au niveau de l’infrastructure à prévoir, le LWM2M est assez simple :

LWM2M interfaces and workflow. | Download Scientific Diagram

On voit ainsi les différentes interfaces entre le client, le serveur et le serveur bootstrap.

Entre le serveur LWM2M et le client, nous avons :

  • Register, Deregister, Notify : Commandes propres au provisionnement des différents clients
  • Read/write, Discover, Observe, Execute, Ping : Commandes permettant de visualiser des données, modifier des valeurs, permettre de mettre des valeurs en observation, pinger le serveur ou le client
  • Request bootstrap, Write, Delete : Commandes d'interfaces avec le serveur de bootstrap

La notion de serveur bootstrap intervient aussi dans cette architecture. Ce dernier permet d’ajouter une notion supplémentaire quant à la gestion sécurisée du provisionnement des différents clients. Ce serveur n’est pas obligatoire pour le bon fonctionnement de l’ensemble. Il ne sera d’ailleurs pas mise en place dans l’exemple présenté dans un souci de simplicité de l'architecture présentée.

4. Zéphyr, la mise en place du démonstrateur

Après avoir fait un rapide tour des technologies radio et protocolaires, il convient de se pencher sur la mise en œuvre. Celle-ci doit se faire en 2 étapes distinctes : la mise en place du serveur LWM2M puis la mise en place du client sur carte électronique.

4.1. Partie serveur

Dans cette partie, nous allons nous concentrer sur la partie serveur.  Nous nous baserons sur le serveur Open Source Leshan. Seule la partie serveur sera installée, la partie bootstrap ne sera pas traitée dans un objectif de simplicité.
Afin  d’être accessible partout, nous allons l’installer sur une instance EC2 d’AWS. Cela peut bien évidemment être installé sur un serveur OnPremise accessible par internet.

4.1.1. Instance EC2 AWS

En premier lieu, nous allons mettre en place une instance EC2 sur AWS.
Cette étape est accessible via la console AWS : https://aws.amazon.com/fr/console/.
Une instance gratuite suffit.

Dans Services, sélectionner EC2

Puis sur lancer une instance

Il faut alors choisir une image correspondant à la distribution à embarquer.

Celle proposée par AWS (Amazon Linux 2 AMI) convient parfaitement.

Ensuite vient le choix des performances visées pour l’instance en question. Pour des essais comme prévus par notre démonstrateur, l’offre gratuite de base est suffisante.

Attention, si vous souhaitez un serveur avec des capacités importantes : hautement disponible, scalable, possibilité de gérer beaucoup de endpoints, il sera nécessaire de passer sur des instances plus conséquentes et des architectures clouds plus adaptées et plus complexes.

Cliquez ensuite sur “Vérifier et lancer” puis “Lancer”.

Une fenêtre vous demandant de gérer les clefs de connexion en ssh apparaitra.

Je vous recommande, si ce n’est déjà fait, de venir télécharger des clefs via “Télécharger une paire de clefs” pour vous permettre de vous connecter à l’instance en ssh. Il faut spécifier un nom pour cette paire de clefs dans le champs adéquat.

Il est alors possible de se connecter à l’instance en ssh via les commandes suivantes :

ssh -i /path/to/key/new_pair.pem ec2-user@public-dns
 

ec2-user@public-dns est le dns public de l’instance EC2 donné dans les paramètres de cette même instance.

 

Il reste enfin à ouvrir les ports nécessaires pour le dialogue entre le nœud et le serveur. Les ports suivants doivent être accessibles : 

 

  • 8080 : Port traditionnellement utilisé pour le trafic Web utilisant HTTP. Le serveur Leshan utilise ce port pour permettre à l’utilisateur d’accéder à l’interface du serveur, et donc de communiquer avec le client.
  • 22 : Port utilisé dans le contexte d’une communication TCP/IP permettant de se connecter au shell Linux de l’instance (SSH).
  • 5683 & 5684 : Ports utilisés dans le cadre du trafic Web utilisant CoAP (& CoAPs). Le port 5683 utilise le protocole UDP sans protection comme couche de transport tandis que le port 5684 utilise DTLS.

Sur l’image, les ports 1883 & 8883  sont ouverts: Ports traditionnellement utilisés par un broker MQTT lors d’une communication TCP/IP (non-sécurisée pour le port 1883 et utilisant des certificats SSL pour le port 8883. Ils ne sont donc pas obligatoires.


L’instance est alors prête pour y installer le serveur Leshan.
 

4.1.2. Serveur Leshan

Le serveur Leshan est issu du projet Open Source maintenu par la fondation Eclipse.
Le projet est accessible sur Github ici : https://github.com/eclipse/leshan

 

Au même titre que pour les performances de l’instance, ce serveur est choisi pour le démonstrateur. Il conviendra de se pencher plus finement sur ses configurations pour le rendre plus adapté lorsque de nombreux nœuds voudront s’y connecter.

L’installation est très facile, une fois connectée à l’instance EC2 en ssh, il suffit de lancer les commandes suivantes : 

wget https://ci.eclipse.org/leshan/job/leshan/lastSuccessfulBuild/artifact/leshan-server-demo.jar
java -jar ./leshan-server-demo.jar

Le serveur est alors prêt et accessible via l’adresse IP public de l’instance EC2 au travers du port 8080, ouvert dans le paragraphe suivant.

4.2. Partie client

Une fois le serveur installé, il faut à présent construire la partie client qui communiquera sur ce serveur en LWM2M au travers de la technologie LTE-M.
Cette partie peut être présentée selon 2 aspects : 

  • Le matériel
  • Le software embarqué

 

4.2.1. Quel matériel choisir ?

Beaucoup de cartes existent sur le marché pour couvrir le besoin. A minima, il nous faut : 

  • Un microcontroleur
  • Un modem LTE-M
  • Une carte SIM
  • Une antenne GSM

Un acteur montant dans le monde de l’IoT et partageant beaucoup de designs propose un produit tout en un. Il s’agit de la société RAKWIRELESS et de la carte Wistrio RAK5010 présentée ci-dessous :

Elle a les caractéristiques suivantes : 

  • Microcontroleur : nRF52840, cortex-M4 avec BLE intégré (le BLE peut être intéressant pour développer une application mobile associée)
  • Modem : BG96 de chez Quectel. Ce modem couvre les réseaux LTE-M, NB-IOT avec un fallback 2G. Cela permet notamment de s’assurer de la connectivité même sur les zones non couvertes par le LTE-M
  • Carte SIM : Carte SIM d’Hologram permettant le LTE-M et le 2G.
  • Antenne : Antenne externe à connecter via un connecteur iPEX.

L’alimentation de cette carte se fait au travers du connecteur micro-USB visible sur l’image précédente.

4.2.2. Programmation de la carte

Pour la programmation de la carte, il sera nécessaire de faire une adaptation entre un SEGGER J-Link de votre choix. Pour ma part, j’ai utilisé le J-Link Mini EDU, suffisant pour mon besoin.

L’adaptation doit se faire au niveau du connecteur 7 de l’image ci-dessous correspondant au connecteur J9 de la carte.

Voici le descriptif du connecteur en question :

Une adaptation vers le connecteur du SEGGER JLink suivant sera à faire soit via un câblage à la main ou avec des adaptateurs du marché (je n'en ai pas trouvé à moindre coût).

J-Link EDU Mini

Tout est à présent réuni pour pouvoir builder et flasher la carte.

4.2.3. Au tour de Zéphyr pour l'envoi des trames

Zéphyr est un  RTOS qui met à disposition tout l’environnement nécessaire pour construire une application LWM2M et notamment sur la carte précédemment citée.
La documentation associée est disponible ici : https://docs.zephyrproject.org/2.5.0/boards/arm/rak5010_nrf52840/doc/index.html
La documentation du client est, quant à elle, présente ici : https://docs.zephyrproject.org/latest/samples/net/lwm2m_client/README.html

Cependant, il est nécessaire de procéder à quelques configurations afin d’être totalement capable d’envoyer des trames en LTE-M et donc en LWM2M vers le serveur précédemment configuré.

Après avoir cloné et installé le projet Zephyr tel qu’expliqué dans le Getting started ici : https://docs.zephyrproject.org/latest/getting_started/index.html, les configurations sont à faire dans le fichier prj.conf relatif à l’application lwm2m client.

Ce fichier se présente de la façon suivante :

CONFIG_NETWORKING=y
CONFIG_LOG=y
CONFIG_LWM2M_LOG_LEVEL_DBG=y
CONFIG_TEST_RANDOM_GENERATOR=y
CONFIG_NET_IPV6=y
CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3
CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=2
CONFIG_NET_IPV4=y
CONFIG_NET_DHCPV4=n
CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=3
CONFIG_NET_IF_MCAST_IPV4_ADDR_COUNT=2
CONFIG_PRINTK=y
CONFIG_NET_PKT_RX_COUNT=10
CONFIG_NET_PKT_TX_COUNT=10
CONFIG_NET_BUF_RX_COUNT=10
CONFIG_NET_BUF_TX_COUNT=10
CONFIG_NET_MAX_CONTEXTS=5

CONFIG_NET_LOG=y
CONFIG_NET_SHELL=y

CONFIG_NET_CONFIG_NEED_IPV6=y
CONFIG_NET_CONFIG_NEED_IPV4=y
CONFIG_NET_CONFIG_SETTINGS=y

CONFIG_LWM2M=y
CONFIG_LWM2M_COAP_BLOCK_SIZE=512
CONFIG_LWM2M_IPSO_SUPPORT=y
CONFIG_LWM2M_IPSO_TEMP_SENSOR=y
CONFIG_LWM2M_IPSO_LIGHT_CONTROL=y
CONFIG_LWM2M_IPSO_TIMER=y
# compile test
CONFIG_LWM2M_CONN_MON_OBJ_SUPPORT=y
CONFIG_LWM2M_LOCATION_OBJ_SUPPORT=y
CONFIG_LWM2M_IPSO_ACCELEROMETER=y
CONFIG_LWM2M_IPSO_BUZZER=y
CONFIG_LWM2M_IPSO_ONOFF_SWITCH=y
CONFIG_LWM2M_IPSO_PUSH_BUTTON=y

CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"
CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2"
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1"
CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2"

Dans samples/net/lwm2m_client, modifier et ajouter à la fin de ce fichier :

CONFIG_MODEM=y
CONFIG_MODEM_QUECTEL_BG9=y
CONFIG_MODEM_QUECTEL_BG9X_APN="hologram"
CONFIG_MODEM_QUECTEL_BG9X_RX_STACK_SIZE=2048

Il faut aussi modifier la variable CONFIG_NET_CONFIG_PEER_IPV4_ADDR avec la valeur de l'adresse IP publique de l'instance EC2 d'AWS.

Il reste alors à builder le projet via 

west build -b qemu_x86 samples/net/lwm2m_client

Puis flasher l’applicatif

west flash

 

Après un reboot, si nécessaire, il suffit alors d’ouvrir l’interface web du serveur Leshan pour voir son objet connecté et qui reçoit les données venant du client

 

4.2.4. Un peu plus loin dans l'application

Comme abordé dans les paragraphes précédents, il est possible de s'approprier le code et ainsi construire sa propre application.
L'architecture du dossier de travail a son importance tant dans la lisibilité du code que de la prise en compte des différentes variables par l'OS Zéphyr.
Voici une proposition d'arbre : 

.
├── boards
│   └── own_board.conf
├── CMakeLists.txt
├── overlay-bootstrap.conf
├── prj.conf
├── README.rst
└── src
    └── my_app.c

On a : un fichier de configuration propre à notre board, le fichier CMakeList pour la compilation de l'application, un fichier overlay afin de surcharger la configuration si le bootstrap est utilisé, le fichier prj.conf pour les configurations minimales, un README et le fichier source contenant le code applicatif.

Voici ci-dessous, le code applicatif du lwm2m_client.

/*
 * Copyright (c) 2017 Linaro Limited
 * Copyright (c) 2017-2019 Foundries.io
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define LOG_MODULE_NAME net_lwm2m_client_app
#define LOG_LEVEL LOG_LEVEL_DBG

#include <logging/log.h>
LOG_MODULE_REGISTER(LOG_MODULE_NAME);

#include <drivers/hwinfo.h>
#include <zephyr.h>
#include <drivers/gpio.h>
#include <drivers/sensor.h>
#include <net/lwm2m.h>

#define APP_BANNER "Run LWM2M client"

#if !defined(CONFIG_NET_CONFIG_PEER_IPV4_ADDR)
#define CONFIG_NET_CONFIG_PEER_IPV4_ADDR ""
#endif

#if !defined(CONFIG_NET_CONFIG_PEER_IPV6_ADDR)
#define CONFIG_NET_CONFIG_PEER_IPV6_ADDR ""
#endif

#if defined(CONFIG_NET_IPV6)
#define SERVER_ADDR CONFIG_NET_CONFIG_PEER_IPV6_ADDR
#elif defined(CONFIG_NET_IPV4)
#define SERVER_ADDR CONFIG_NET_CONFIG_PEER_IPV4_ADDR
#else
#error LwM2M requires either IPV6 or IPV4 support
#endif


#define WAIT_TIME	K_SECONDS(10)
#define CONNECT_TIME	K_SECONDS(10)

#define CLIENT_MANUFACTURER	"Zephyr"
#define CLIENT_MODEL_NUMBER	"OMA-LWM2M Sample Client"
#define CLIENT_SERIAL_NUMBER	"345000123"
#define CLIENT_FIRMWARE_VER	"1.0"
#define CLIENT_DEVICE_TYPE	"OMA-LWM2M Client"
#define CLIENT_HW_VER		"1.0.1"

#define LIGHT_NAME		"Test light"
#define TIMER_NAME		"Test timer"

#define ENDPOINT_LEN		32

#if DT_NODE_HAS_STATUS(DT_ALIAS(led0), okay)
#define LED_GPIO_PORT	DT_GPIO_LABEL(DT_ALIAS(led0), gpios)
#define LED_GPIO_PIN	DT_GPIO_PIN(DT_ALIAS(led0), gpios)
#define LED_GPIO_FLAGS	DT_GPIO_FLAGS(DT_ALIAS(led0), gpios)
#else
/* Not an error; the relevant IPSO object will simply not be created. */
#define LED_GPIO_PORT	""
#define LED_GPIO_PIN	0
#define LED_GPIO_FLAGS	0
#endif

static uint8_t bat_idx = LWM2M_DEVICE_PWR_SRC_TYPE_BAT_INT;
static int bat_mv = 3800;
static int bat_ma = 125;
static uint8_t usb_idx = LWM2M_DEVICE_PWR_SRC_TYPE_USB;
static int usb_mv = 5000;
static int usb_ma = 900;
static uint8_t bat_level = 95;
static uint8_t bat_status = LWM2M_DEVICE_BATTERY_STATUS_CHARGING;
static int mem_free = 15;
static int mem_total = 25;

static const struct device *led_dev;
static uint32_t led_state;

static struct lwm2m_ctx client;

#if defined(CONFIG_LWM2M_DTLS_SUPPORT)
#define TLS_TAG			1

/* "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_psk_id[] = "Client_identity";
#endif /* CONFIG_LWM2M_DTLS_SUPPORT */

static struct k_sem quit_lock;

#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT)
static uint8_t firmware_buf[64];
#endif

/* TODO: Move to a pre write hook that can handle ret codes once available */
static int led_on_off_cb(uint16_t obj_inst_id, uint16_t res_id, uint16_t res_inst_id,
			 uint8_t *data, uint16_t data_len,
			 bool last_block, size_t total_size)
{
	int ret = 0;
	uint32_t led_val;

	led_val = *(uint8_t *) data;
	if (led_val != led_state) {
		ret = gpio_pin_set(led_dev, LED_GPIO_PIN, (int) led_val);
		if (ret) {
			/*
			 * We need an extra hook in LWM2M to better handle
			 * failures before writing the data value and not in
			 * post_write_cb, as there is not much that can be
			 * done here.
			 */
			LOG_ERR("Fail to write to GPIO %d", LED_GPIO_PIN);
			return ret;
		}

		led_state = led_val;
		/* TODO: Move to be set by an internal post write function */
		lwm2m_engine_set_s32("3311/0/5852", 0);
	}

	return ret;
}

static int init_led_device(void)
{
	int ret;

	led_dev = device_get_binding(LED_GPIO_PORT);
	if (!led_dev) {
		return -ENODEV;
	}

	ret = gpio_pin_configure(led_dev, LED_GPIO_PIN, LED_GPIO_FLAGS |
							GPIO_OUTPUT_INACTIVE);
	if (ret) {
		return ret;
	}

	return 0;
}

static int device_reboot_cb(uint16_t obj_inst_id,
			    uint8_t *args, uint16_t args_len)
{
	LOG_INF("DEVICE: REBOOT");
	/* Add an error for testing */
	lwm2m_device_add_err(LWM2M_DEVICE_ERROR_LOW_POWER);
	/* Change the battery voltage for testing */
	lwm2m_engine_set_s32("3/0/7/0", (bat_mv - 1));

	return 0;
}

static int device_factory_default_cb(uint16_t obj_inst_id,
				     uint8_t *args, uint16_t args_len)
{
	LOG_INF("DEVICE: FACTORY DEFAULT");
	/* Add an error for testing */
	lwm2m_device_add_err(LWM2M_DEVICE_ERROR_GPS_FAILURE);
	/* Change the USB current for testing */
	lwm2m_engine_set_s32("3/0/8/1", (usb_ma - 1));

	return 0;
}

#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT)
static int firmware_update_cb(uint16_t obj_inst_id,
			      uint8_t *args, uint16_t args_len)
{
	LOG_DBG("UPDATE");

	/* TODO: kick off update process */

	/* If success, set the update result as RESULT_SUCCESS.
	 * In reality, it should be set at function lwm2m_setup()
	 */
	lwm2m_engine_set_u8("5/0/3", STATE_IDLE);
	lwm2m_engine_set_u8("5/0/5", RESULT_SUCCESS);
	return 0;
}
#endif


static void *temperature_get_buf(uint16_t obj_inst_id, uint16_t res_id,
				 uint16_t res_inst_id, size_t *data_len)
{
	/* Last read temperature value, will use 25.5C if no sensor available */
	static struct float32_value v = { 25, 500000 };
	const struct device *dev = NULL;

#if defined(CONFIG_FXOS8700_TEMP)
	dev = device_get_binding(DT_LABEL(DT_INST(0, nxp_fxos8700)));
#endif

	if (dev != NULL) {
		if (sensor_sample_fetch(dev)) {
			LOG_ERR("temperature data update failed");
		}

		sensor_channel_get(dev, SENSOR_CHAN_DIE_TEMP,
				  (struct sensor_value *) &v);
		LOG_DBG("LWM2M temperature set to %d.%d", v.val1, v.val2);
	}

	/* echo the value back through the engine to update min/max values */
	lwm2m_engine_set_float32("3303/0/5700", &v);
	*data_len = sizeof(v);
	return &v;
}


#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT)
static void *firmware_get_buf(uint16_t obj_inst_id, uint16_t res_id,
			      uint16_t res_inst_id, size_t *data_len)
{
	*data_len = sizeof(firmware_buf);
	return firmware_buf;
}

static int firmware_block_received_cb(uint16_t obj_inst_id,
				      uint16_t res_id, uint16_t res_inst_id,
				      uint8_t *data, uint16_t data_len,
				      bool last_block, size_t total_size)
{
	LOG_INF("FIRMWARE: BLOCK RECEIVED: len:%u last_block:%d",
		data_len, last_block);
	return 0;
}
#endif

/* An example data validation callback. */
static int timer_on_off_validate_cb(uint16_t obj_inst_id, uint16_t res_id,
				    uint16_t res_inst_id, uint8_t *data,
				    uint16_t data_len, bool last_block,
				    size_t total_size)
{
	LOG_INF("Validating On/Off data");

	if (data_len != 1) {
		return -EINVAL;
	}

	if (*data > 1) {
		return -EINVAL;
	}

	return 0;
}

static int timer_digital_state_cb(uint16_t obj_inst_id,
				  uint16_t res_id, uint16_t res_inst_id,
				  uint8_t *data, uint16_t data_len,
				  bool last_block, size_t total_size)
{
	bool *digital_state = (bool *)data;

	if (*digital_state) {
		LOG_INF("TIMER: ON");
	} else {
		LOG_INF("TIMER: OFF");
	}

	return 0;
}

static int lwm2m_setup(void)
{
	int ret;
	char *server_url;
	uint16_t server_url_len;
	uint8_t server_url_flags;

	/* setup SECURITY object */

	/* Server URL */
	ret = lwm2m_engine_get_res_data("0/0/0",
					(void **)&server_url, &server_url_len,
					&server_url_flags);
	if (ret < 0) {
		return ret;
	}

	snprintk(server_url, server_url_len, "coap%s//%s%s%s",
		 IS_ENABLED(CONFIG_LWM2M_DTLS_SUPPORT) ? "s:" : ":",
		 strchr(SERVER_ADDR, ':') ? "[" : "", SERVER_ADDR,
		 strchr(SERVER_ADDR, ':') ? "]" : "");

	/* Security Mode */
	lwm2m_engine_set_u8("0/0/2",
			    IS_ENABLED(CONFIG_LWM2M_DTLS_SUPPORT) ? 0 : 3);
#if defined(CONFIG_LWM2M_DTLS_SUPPORT)
	lwm2m_engine_set_string("0/0/3", (char *)client_psk_id);
	lwm2m_engine_set_opaque("0/0/5",
				(void *)client_psk, sizeof(client_psk));
#endif /* CONFIG_LWM2M_DTLS_SUPPORT */

#if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP)
	/* Mark 1st instance of security object as a bootstrap server */
	lwm2m_engine_set_u8("0/0/1", 1);

	/* Create 2nd instance of security object needed for bootstrap */
	lwm2m_engine_create_obj_inst("0/1");
#else
	/* Match Security object instance with a Server object instance with
	 * Short Server ID.
	 */
	lwm2m_engine_set_u16("0/0/10", 101);
	lwm2m_engine_set_u16("1/0/0", 101);
#endif

	/* setup SERVER object */

	/* setup DEVICE object */

	lwm2m_engine_set_res_data("3/0/0", CLIENT_MANUFACTURER,
				  sizeof(CLIENT_MANUFACTURER),
				  LWM2M_RES_DATA_FLAG_RO);
	lwm2m_engine_set_res_data("3/0/1", CLIENT_MODEL_NUMBER,
				  sizeof(CLIENT_MODEL_NUMBER),
				  LWM2M_RES_DATA_FLAG_RO);
	lwm2m_engine_set_res_data("3/0/2", CLIENT_SERIAL_NUMBER,
				  sizeof(CLIENT_SERIAL_NUMBER),
				  LWM2M_RES_DATA_FLAG_RO);
	lwm2m_engine_set_res_data("3/0/3", CLIENT_FIRMWARE_VER,
				  sizeof(CLIENT_FIRMWARE_VER),
				  LWM2M_RES_DATA_FLAG_RO);
	lwm2m_engine_register_exec_callback("3/0/4", device_reboot_cb);
	lwm2m_engine_register_exec_callback("3/0/5", device_factory_default_cb);
	lwm2m_engine_set_res_data("3/0/9", &bat_level, sizeof(bat_level), 0);
	lwm2m_engine_set_res_data("3/0/10", &mem_free, sizeof(mem_free), 0);
	lwm2m_engine_set_res_data("3/0/17", CLIENT_DEVICE_TYPE,
				  sizeof(CLIENT_DEVICE_TYPE),
				  LWM2M_RES_DATA_FLAG_RO);
	lwm2m_engine_set_res_data("3/0/18", CLIENT_HW_VER,
				  sizeof(CLIENT_HW_VER),
				  LWM2M_RES_DATA_FLAG_RO);
	lwm2m_engine_set_res_data("3/0/20", &bat_status, sizeof(bat_status), 0);
	lwm2m_engine_set_res_data("3/0/21", &mem_total, sizeof(mem_total), 0);

	/* add power source resource instances */
	lwm2m_engine_create_res_inst("3/0/6/0");
	lwm2m_engine_set_res_data("3/0/6/0", &bat_idx, sizeof(bat_idx), 0);

	lwm2m_engine_create_res_inst("3/0/7/0");
	lwm2m_engine_set_res_data("3/0/7/0", &bat_mv, sizeof(bat_mv), 0);
	lwm2m_engine_create_res_inst("3/0/8/0");
	lwm2m_engine_set_res_data("3/0/8/0", &bat_ma, sizeof(bat_ma), 0);
	lwm2m_engine_create_res_inst("3/0/6/1");
	lwm2m_engine_set_res_data("3/0/6/1", &usb_idx, sizeof(usb_idx), 0);
	lwm2m_engine_create_res_inst("3/0/7/1");
	lwm2m_engine_set_res_data("3/0/7/1", &usb_mv, sizeof(usb_mv), 0);
	lwm2m_engine_create_res_inst("3/0/8/1");
	lwm2m_engine_set_res_data("3/0/8/1", &usb_ma, sizeof(usb_ma), 0);

	/* setup FIRMWARE object */

#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT)
	/* setup data buffer for block-wise transfer */
	lwm2m_engine_register_pre_write_callback("5/0/0", firmware_get_buf);
	lwm2m_firmware_set_write_cb(firmware_block_received_cb);
#endif
#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT)
	lwm2m_firmware_set_update_cb(firmware_update_cb);
#endif

	/* setup TEMP SENSOR object */
	lwm2m_engine_create_obj_inst("3303/0");
	lwm2m_engine_register_read_callback("3303/0/5700", temperature_get_buf);

	/* IPSO: Light Control object */
	if (init_led_device() == 0) {
		lwm2m_engine_create_obj_inst("3311/0");
		lwm2m_engine_register_post_write_callback("3311/0/5850",
				led_on_off_cb);
		lwm2m_engine_set_res_data("3311/0/5750",
					  LIGHT_NAME, sizeof(LIGHT_NAME),
					  LWM2M_RES_DATA_FLAG_RO);
	}

	/* IPSO: Timer object */
	lwm2m_engine_create_obj_inst("3340/0");
	lwm2m_engine_register_validate_callback("3340/0/5850",
			timer_on_off_validate_cb);
	lwm2m_engine_register_post_write_callback("3340/0/5543",
			timer_digital_state_cb);
	lwm2m_engine_set_res_data("3340/0/5750", TIMER_NAME, sizeof(TIMER_NAME),
				  LWM2M_RES_DATA_FLAG_RO);

	return 0;
}

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_UPDATE_FAILURE:
		LOG_DBG("Registration update failure!");
		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;

	case LWM2M_RD_CLIENT_EVENT_QUEUE_MODE_RX_OFF:
		LOG_DBG("Queue mode RX window closed");
		break;

	case LWM2M_RD_CLIENT_EVENT_NETWORK_ERROR:
		LOG_ERR("LwM2M engine reported a network erorr.");
		lwm2m_rd_client_stop(client, rd_client_event);
		break;
	}
}

void main(void)
{
	uint32_t flags = IS_ENABLED(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP) ?
				LWM2M_RD_CLIENT_FLAG_BOOTSTRAP : 0;
	int ret;

	LOG_INF(APP_BANNER);

	k_sem_init(&quit_lock, 0, K_SEM_MAX_LIMIT);

	ret = lwm2m_setup();
	if (ret < 0) {
		LOG_ERR("Cannot setup LWM2M fields (%d)", ret);
		return;
	}

	(void)memset(&client, 0x0, sizeof(client));
#if defined(CONFIG_LWM2M_DTLS_SUPPORT)
	client.tls_tag = TLS_TAG;
#endif

#if defined(CONFIG_HWINFO)
	uint8_t dev_id[16];
	char dev_str[33];
	ssize_t length;
	int i;

	(void)memset(dev_id, 0x0, sizeof(dev_id));

	/* Obtain the device id */
	length = hwinfo_get_device_id(dev_id, sizeof(dev_id));

	/* If this fails for some reason, use all zeros instead */
	if (length <= 0) {
		length = sizeof(dev_id);
	}

	/* Render the obtained serial number in hexadecimal representation */
	for (i = 0 ; i < length ; i++) {
		sprintf(&dev_str[i*2], "%02x", dev_id[i]);
	}

	lwm2m_rd_client_start(&client, dev_str, flags, rd_client_event);
#else
	/* client.sec_obj_inst is 0 as a starting point */
	lwm2m_rd_client_start(&client, CONFIG_BOARD, flags, rd_client_event);
#endif

	k_sem_take(&quit_lock, K_FOREVER);
}

Dans le main, on initialise tout le client lwm2m au travers de la fonction : "lwm2m_setup".
Cette fonction instancie tous les objets lwm2m qui seront accessibles par le serveur et en lien avec la spécification donnée par l'OMA alliance.
il est possible de créer ses propres objets lwm2m et ainsi contribuer au développement de l'application.
En amont de cette fonction, sont définies les différentes callbacks de traitement des données lorsque des actions sont faites sur les objets définis.

Enfin le main lance la connexion vers le serveur via la fonction lwm2m_rd_client_start

Libre à vous d'ajouter vos propres objets LWM2M dans l'application !

5. Conclusion

L'article a permis de présenter la nouvelle technologie LPWAN le LTE-M disponible dans de nombreux pays.
Il a aussi projeté une mise en application au travers de l’OS Zephyr et ouvrant la possibilité de construire un projet plus conséquent sur cette base de communication. Pour tirer pleinement partie du LTE-M, les modes de veilles de la technologie seront à mettre en œuvre pour permettre d’atteindre des autonomies intéressantes sur batterie. Le Hardware choisi ouvrira la porte à la mise en place d’une application mobile en BLE permettant la configuration du produit final.

 

6. Sources

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée.