1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

pkg/wakaama/objects: add IPSO on/off switch

This commit is contained in:
Leandro Lanzieri 2024-12-25 11:18:19 +01:00
parent 95fe972aee
commit 8b0a86fda3
2 changed files with 695 additions and 0 deletions

View File

@ -0,0 +1,492 @@
/*
* Copyright (C) 2024 HAW Hamburg
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @{
* @ingroup lwm2m_objects_on_off_switch
*
* @file
* @brief On/off switch object implementation for LwM2M client using Wakaama
*
* @author Leandro Lanzieri <leandro.lanzieri@haw-hamburg.de>
* @}
*/
#include "mutex.h"
#include "inttypes.h"
#include "liblwm2m.h"
#include "lwm2m_client.h"
#include "objects/on_off_switch.h"
#include "ztimer.h"
#include "ztimer/stopwatch.h"
#define ENABLE_DEBUG 0
#include "debug.h"
#define _USED_INSTANCES(_obj) (_obj.wakaama_object.instanceList)
#define _FREE_INSTANCES(_obj) (_obj.free_instances)
/**
* @brief LwM2M On/off switch object instance
*/
typedef struct lwm2m_obj_on_off_switch_inst {
lwm2m_list_t list; /**< list handle */
bool status; /**< digital input status */
uint32_t counter; /**< counter for the digital input */
ztimer_stopwatch_t stopwatch; /**< stopwatch for time periods */
char app_type[CONFIG_LWM2M_ON_OFF_SWITCH_APP_TYPE_MAX_SIZE]; /**< application type */
} lwm2m_obj_on_off_switch_inst_t;
/**
* @brief 'Read' callback for the LwM2M On/off switch object implementation.
*
* @param[in] context LWM2M Context
* @param[in] instance_id ID of the instance to read resource from.
* @param[in] num_data Number of elements in @p data_array.
* @param[in, out] data_array IDs of resources to read. Array of data structures to place values.
* @param[in] object On/off switch object handle
*
* @return COAP_205_CONTENT on success
* @return COAP_404_NOT_FOUND if the instance was not found
* @return COAP_500_INTERNAL_SERVER_ERROR otherwise
*/
static uint8_t _read_cb(lwm2m_context_t * context, uint16_t instance_id, int * num_data,
lwm2m_data_t ** data_array, lwm2m_object_t * object);
/**
* @brief 'Write' callback for the LwM2M On/off switch object implementation.
*
* @param[in] context LWM2M Context
* @param[in] instance_id ID of the instance to write resource to.
* @param[in] num_data Number of elements in @p data_array.
* @param[in] data_array IDs of resources to write and values.
* @param[in] object On/off switch object handle
*
* @return COAP_204_CHANGED on success
* @return COAP_404_NOT_FOUND if the instance was not found
* @return COAP_400_BAD_REQUEST if a value is not encoded correctly
* @return COAP_500_INTERNAL_SERVER_ERROR otherwise
*/
static uint8_t _write_cb(lwm2m_context_t * context, uint16_t instance_id, int num_data,
lwm2m_data_t * data_array, lwm2m_object_t * object, lwm2m_write_type_t write_type);
/**
* @brief Gets the current value of a given @p instance.
*
* @param[in, out] data Initialized data structure.
* @param[in] instance Pointer to the instance to get the value from.
*
* @return COAP_205_CONTENT on success
* @return COAP_404_NOT_FOUND if the value is not found
*/
static uint8_t _get_value(lwm2m_data_t *data, lwm2m_obj_on_off_switch_inst_t *instance);
/**
* @brief Mark a resource as changed on the LwM2M engine.
*
* @param[in] instance_id ID of the instance to mark the resource as changed.
* @param[in] resource_id ID of the resource to mark as changed.
*/
static void _mark_resource_changed(uint16_t instance_id, uint16_t resource_id);
struct lwm2m_on_off_switch_object {
lwm2m_object_t wakaama_object; /**< Wakaama internal object */
mutex_t lock; /**< mutex for the instances access */
lwm2m_obj_on_off_switch_inst_t *free_instances; /**< list of free instances */
lwm2m_obj_on_off_switch_inst_t instances[CONFIG_LWM2M_ON_OFF_SWITCH_INSTANCES_MAX]; /**< instances */
};
struct lwm2m_on_off_switch_object _on_off_switch_object = {
.lock = MUTEX_INIT,
.wakaama_object = {
.objID = LWM2M_ON_OFF_SWITCH_OBJECT_ID,
.instanceList = NULL,
.readFunc = _read_cb,
.writeFunc = _write_cb,
.executeFunc = NULL,
.createFunc = NULL,
.deleteFunc = NULL,
.discoverFunc = NULL,
.userData = NULL
}
};
static uint8_t _get_value(lwm2m_data_t *data, lwm2m_obj_on_off_switch_inst_t *instance)
{
assert(data);
assert(instance);
switch (data->id) {
case LWM2M_ON_OFF_SWITCH_DIGITAL_INPUT_STATE_ID:
lwm2m_data_encode_bool(instance->status, data);
break;
case LWM2M_ON_OFF_SWITCH_ON_TIME_ID:
if (instance->status) {
int64_t time = (int64_t) ztimer_stopwatch_measure(&instance->stopwatch);
lwm2m_data_encode_int(time, data);
}
else {
lwm2m_data_encode_int(0, data);
}
break;
case LWM2M_ON_OFF_SWITCH_OFF_TIME_ID:
if (!instance->status) {
int64_t time = (int64_t) ztimer_stopwatch_measure(&instance->stopwatch);
lwm2m_data_encode_int(time, data);
}
else {
lwm2m_data_encode_int(0, data);
}
break;
case LWM2M_ON_OFF_SWITCH_APP_TYPE_ID:
lwm2m_data_encode_string(instance->app_type, data);
break;
default:
return COAP_404_NOT_FOUND;
}
return COAP_205_CONTENT;
}
static uint8_t _read_cb(lwm2m_context_t * context, uint16_t instance_id, int * num_data,
lwm2m_data_t ** data_array, lwm2m_object_t * object)
{
(void)context;
lwm2m_obj_on_off_switch_inst_t *instance;
uint8_t result = COAP_404_NOT_FOUND;
int i = 0;
mutex_lock(&_on_off_switch_object.lock);
/* try to get the requested instance from the object list */
instance = (lwm2m_obj_on_off_switch_inst_t *)lwm2m_list_find(object->instanceList, instance_id);
if (!instance) {
DEBUG("[lwm2m:on_off_switch:read]: can't find instance %" PRId16 "\n", instance_id);
result = COAP_404_NOT_FOUND;
goto free_out;
}
/* if the number of resources is not specified, we need to read all resources */
if (!*num_data) {
DEBUG("[lwm2m:on_off_switch:read]: reading all resources\n");
uint16_t res_list[] = {
LWM2M_ON_OFF_SWITCH_DIGITAL_INPUT_STATE_ID,
LWM2M_ON_OFF_SWITCH_ON_TIME_ID,
LWM2M_ON_OFF_SWITCH_OFF_TIME_ID,
LWM2M_ON_OFF_SWITCH_APP_TYPE_ID
};
/* allocate structures to return resources */
int res_num = ARRAY_SIZE(res_list);
*data_array = lwm2m_data_new(res_num);
if (NULL == *data_array) {
result = COAP_500_INTERNAL_SERVER_ERROR;
goto free_out;
}
/* return the number of resources being read */
*num_data = res_num;
/* set the IDs of the resources in the data structures */
for (i = 0; i < res_num; i++) {
(*data_array)[i].id = res_list[i];
}
}
/* now get the values */
i = 0;
do {
DEBUG("[lwm2m:on_off_switch:read]: reading resource %" PRId16 "\n", (*data_array)[i].id);
result = _get_value(&(*data_array)[i], instance);
i++;
} while (i < *num_data && COAP_205_CONTENT == result);
free_out:
mutex_unlock(&_on_off_switch_object.lock);
return result;
}
static uint8_t _write_cb(lwm2m_context_t * context, uint16_t instance_id, int num_data,
lwm2m_data_t * data_array, lwm2m_object_t * object, lwm2m_write_type_t write_type)
{
(void)context;
(void)write_type;
lwm2m_obj_on_off_switch_inst_t *instance;
uint8_t result = COAP_204_CHANGED;
mutex_lock(&_on_off_switch_object.lock);
/* try to get the requested instance from the object list */
instance = (lwm2m_obj_on_off_switch_inst_t *)lwm2m_list_find(object->instanceList, instance_id);
if (!instance) {
DEBUG("[lwm2m:on_off_switch:write]: can't find instance %" PRId16 "\n", instance_id);
result = COAP_404_NOT_FOUND;
goto free_out;
}
for (int i = 0; i < num_data && result == COAP_204_CHANGED; i++) {
switch (data_array[i].id) {
case LWM2M_ON_OFF_SWITCH_DIGITAL_INPUT_STATE_ID: {
bool prev_status = instance->status;
lwm2m_data_decode_bool(&data_array[i], &instance->status);
/* reset timer on transitions */
if (instance->status != prev_status) {
ztimer_stopwatch_reset(&instance->stopwatch);
if (instance->status) {
instance->counter++;
}
}
_mark_resource_changed(instance_id, LWM2M_ON_OFF_SWITCH_DIGITAL_INPUT_STATE_ID);
break;
}
case LWM2M_ON_OFF_SWITCH_ON_TIME_ID:
{
int64_t val;
lwm2m_data_decode_int(&data_array[i], &val);
if (val != 0) {
DEBUG("[lwm2m:on_off_switch:write]: invalid on_time value, only can write 0\n");
result = COAP_400_BAD_REQUEST;
} else {
ztimer_stopwatch_reset(&instance->stopwatch);
_mark_resource_changed(instance_id, LWM2M_ON_OFF_SWITCH_ON_TIME_ID);
}
break;
}
case LWM2M_ON_OFF_SWITCH_OFF_TIME_ID:
{
int64_t val;
lwm2m_data_decode_int(&data_array[i], &val);
if (val != 0) {
DEBUG("[lwm2m:on_off_switch:write]: invalid off_time value, only can write 0\n");
result = COAP_400_BAD_REQUEST;
} else {
ztimer_stopwatch_reset(&instance->stopwatch);
_mark_resource_changed(instance_id, LWM2M_ON_OFF_SWITCH_OFF_TIME_ID);
}
break;
}
case LWM2M_ON_OFF_SWITCH_APP_TYPE_ID:
if (data_array[i].type != LWM2M_TYPE_STRING && data_array[i].type != LWM2M_TYPE_OPAQUE) {
DEBUG("[lwm2m:on_off_switch:write]: invalid type for app_type"
"(%" PRId8 ")\n", (uint8_t)(data_array[i].type));
result = COAP_400_BAD_REQUEST;
break;
}
if (data_array[i].value.asBuffer.length >
CONFIG_LWM2M_ON_OFF_SWITCH_APP_TYPE_MAX_SIZE - 1) {
DEBUG("[lwm2m:on_off_switch:write]: value too big for app_type\n");
result = COAP_500_INTERNAL_SERVER_ERROR;
break;
}
memcpy(instance->app_type, data_array[i].value.asBuffer.buffer,
data_array[i].value.asBuffer.length);
instance->app_type[data_array[i].value.asBuffer.length] = '\0';
_mark_resource_changed(instance_id, LWM2M_ON_OFF_SWITCH_APP_TYPE_ID);
break;
}
}
free_out:
mutex_unlock(&_on_off_switch_object.lock);
return result;
}
static void _mark_resource_changed(uint16_t instance_id, uint16_t resource_id)
{
lwm2m_uri_t uri;
LWM2M_URI_RESET(&uri);
uri.objectId = LWM2M_ON_OFF_SWITCH_OBJECT_ID;
uri.instanceId = instance_id;
uri.resourceId = resource_id;
lwm2m_client_data_t *client_data;
client_data = (lwm2m_client_data_t *)_on_off_switch_object.wakaama_object.userData;
lwm2m_resource_value_changed(client_data->lwm2m_ctx, &uri);
}
lwm2m_object_t *lwm2m_object_on_off_switch_init(lwm2m_client_data_t *client_data)
{
/* initialize the instances */
for (unsigned i = 0; i < CONFIG_LWM2M_ON_OFF_SWITCH_INSTANCES_MAX; i++) {
_on_off_switch_object.instances[i].list.next = NULL;
_on_off_switch_object.instances[i].list.id = UINT16_MAX;
_FREE_INSTANCES(_on_off_switch_object) = (lwm2m_obj_on_off_switch_inst_t *) LWM2M_LIST_ADD(
_FREE_INSTANCES(_on_off_switch_object),
&(_on_off_switch_object.instances[i])
);
}
_on_off_switch_object.wakaama_object.userData = client_data;
return &(_on_off_switch_object.wakaama_object);
}
int lwm2m_object_on_off_switch_instance_create(const lwm2m_obj_on_off_switch_args_t *args,
int32_t instance_id)
{
assert(args);
int result = -ENOMEM;
lwm2m_obj_on_off_switch_inst_t *instance = NULL;
uint16_t _instance_id;
/* lock object */
mutex_lock(&_on_off_switch_object.lock);
/* determine ID for new instance */
if (instance_id < 0) {
_instance_id = lwm2m_list_newId((lwm2m_list_t *) _USED_INSTANCES(_on_off_switch_object));
}
else {
/* sanity check */
if (instance_id >= (UINT16_MAX - 1)) {
DEBUG("[lwm2m:on_off_switch]: instance ID %" PRIi32 " is too big\n", instance_id);
result = -EINVAL;
goto free_out;
}
_instance_id = (uint16_t)instance_id;
/* check that the ID is free to use */
if (LWM2M_LIST_FIND(_USED_INSTANCES(_on_off_switch_object), _instance_id ) != NULL)
{
DEBUG("[lwm2m:on_off_switch]: instance ID %" PRIi32 " already in use\n", instance_id);
goto free_out;
}
}
/* try to allocate an instance, by popping a free node from the list */
_FREE_INSTANCES(_on_off_switch_object) = (lwm2m_obj_on_off_switch_inst_t *) lwm2m_list_remove(
(lwm2m_list_t *) _FREE_INSTANCES(_on_off_switch_object),
UINT16_MAX,
(lwm2m_list_t **) &instance
);
if (!instance) {
DEBUG("[lwm2m:on_off_switch]: can't allocate new instance\n");
goto free_out;
}
memset(instance, 0, sizeof(lwm2m_obj_on_off_switch_inst_t));
instance->list.id = _instance_id;
instance->status = false;
ztimer_stopwatch_init(ZTIMER_SEC, &instance->stopwatch);
ztimer_stopwatch_start(&instance->stopwatch);
/* if app is specified, copy locally */
if (args->app_type) {
if (args->app_type_len > CONFIG_LWM2M_ON_OFF_SWITCH_APP_TYPE_MAX_SIZE) {
DEBUG("[lwm2m:on_off_switch]: not enough space for app_type string\n");
goto free_out;
}
memcpy(instance->app_type, args->app_type, args->app_type_len);
}
DEBUG("[lwm2m:on_off_switch]: new instance with ID %" PRIu16 "\n", _instance_id);
/* add the new instance to the list */
_USED_INSTANCES(_on_off_switch_object) = LWM2M_LIST_ADD(
_USED_INSTANCES(_on_off_switch_object),
instance
);
result = 0;
free_out:
mutex_unlock(&_on_off_switch_object.lock);
return result;
}
int lwm2m_object_on_off_switch_update_status(uint16_t instance_id, bool status)
{
int result = -EINVAL;
lwm2m_obj_on_off_switch_inst_t *instance;
mutex_lock(&_on_off_switch_object.lock);
instance = (lwm2m_obj_on_off_switch_inst_t *)LWM2M_LIST_FIND(
_USED_INSTANCES(_on_off_switch_object),
instance_id
);
if (!instance) {
DEBUG("[lwm2m:on_off_switch]: can't find instance %" PRId16 "\n", instance_id);
goto free_out;
}
if (status != instance->status) {
if (status) {
instance->counter++;
}
ztimer_stopwatch_start(&instance->stopwatch);
}
instance->status = status;
_mark_resource_changed(instance_id, LWM2M_ON_OFF_SWITCH_DIGITAL_INPUT_STATE_ID);
result = 0;
free_out:
mutex_unlock(&_on_off_switch_object.lock);
return result;
}
int lwm2m_object_on_off_switch_update_app_type(uint16_t instance_id, const char *app_type,
size_t len)
{
int result = -EINVAL;
lwm2m_obj_on_off_switch_inst_t *instance;
mutex_lock(&_on_off_switch_object.lock);
if (len > CONFIG_LWM2M_ON_OFF_SWITCH_APP_TYPE_MAX_SIZE - 1) {
DEBUG("[lwm2m:on_off_switch]: app_type string too long\n");
result = -ENOBUFS;
goto free_out;
}
instance = (lwm2m_obj_on_off_switch_inst_t *)LWM2M_LIST_FIND(
_USED_INSTANCES(_on_off_switch_object),
instance_id
);
if (!instance) {
DEBUG("[lwm2m:on_off_switch]: can't find instance %" PRId16 "\n", instance_id);
goto free_out;
}
memcpy(instance->app_type, app_type, len);
instance->app_type[len] = '\0';
_mark_resource_changed(instance_id, LWM2M_ON_OFF_SWITCH_APP_TYPE_ID);
result = 0;
free_out:
mutex_unlock(&_on_off_switch_object.lock);
return result;
}

View File

@ -0,0 +1,203 @@
/*
* Copyright (C) 2024 HAW Hamburg
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup lwm2m_objects
* @defgroup lwm2m_objects_on_off-switch On/Off Switch
* @brief On/Off switch object implementation for LwM2M client using Wakaama
*
* @experimental This API is considered experimental and may change in future releases without
* deprecation process.
*
* This implements the LwM2M IPSO on/off switch object (ID 3342) as specified in the LwM2M registry.
*
* This IPSO object should be used with an On/Off switch to report the state of the switch.
*
* To use this object add `USEMODULE += wakaama_objects_on_off_switch` to the application Makefile.
*
* ## Resources
*
* For an XML description of the object see
* https://raw.githubusercontent.com/OpenMobileAlliance/lwm2m-registry/prod/3342.xml
*
* | Name | ID | Mandatory | Type | Range | Units | Implemented |
* |-----------------------|------|-----------|---------|-------|-------|-------------|
* | Digital Input State | 5500 | Yes | Boolean | - | - | Yes |
* | Digital Input Counter | 5501 | No | Integer | - | - | Yes |
* | On time | 5852 | No | Integer | - | s | Yes |
* | Off Time | 5854 | No | Integer | - | s | Yes |
* | Application Type | 5750 | No | String | - | - | Yes |
*
* ## Usage
*
* 1. Initialize the LwM2M client with @ref lwm2m_object_on_off_switch_init, by passing a pointer
* to the LwM2M client data.
*
* 2. Now you can create instances of the on/off switch object with
* @ref lwm2m_object_on_off_switch_instance_create.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.c}
* #define APP_TYPE "BTN 0"
*
* // ... ///
*
* lwm2m_object_t *on_off_switch;
* lwm2m_client_data_t client_data;
*
* lwm2m_client_init(&client_data);
* on_off_switch = lwm2m_object_on_off_switch_init(&client_data);
*
* lwm2m_obj_on_off_switch_args_t args = {
* .app_type = APP_TYPE,
* .app_type_len = sizeof(APP_TYPE) - 1
* };
*
* int result = lwm2m_object_on_off_switch_instance_create(&args, 0);
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* 3. You can update the status and app_type of the on/off switch instance with
* @ref lwm2m_object_on_off_switch_update_status and
* @ref lwm2m_object_on_off_switch_update_app_type respectively. As this will make sure to send
* notifications to servers that may be observing these resources, avoid calling them from
* interrupt contexts.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.c}
* lwm2m_object_on_off_switch_update_app_type(0, true);
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* @{
*
* @file
*
* @author Leandro Lanzieri <leandro.lanzieri@haw-hamburg.de>
*/
#ifndef OBJECTS_ON_OFF_SWITCH_H
#define OBJECTS_ON_OFF_SWITCH_H
#ifdef __cplusplus
extern "C" {
#endif
#include "liblwm2m.h"
/**
* @defgroup lwm2m_objects_on_off_switch_config LwM2M On/Off switch object compile configurations
* @ingroup lwm2m_client_config
* @{
*/
/**
* @brief Maximum number of instances of the object
*/
#ifndef CONFIG_LWM2M_ON_OFF_SWITCH_INSTANCES_MAX
#define CONFIG_LWM2M_ON_OFF_SWITCH_INSTANCES_MAX (3U)
#endif
/**
* @brief Maximum size for the application type string
*/
#ifndef CONFIG_LWM2M_ON_OFF_SWITCH_APP_TYPE_MAX_SIZE
#define CONFIG_LWM2M_ON_OFF_SWITCH_APP_TYPE_MAX_SIZE (16U)
#endif
/** @} */
/**
* @brief On/Off switch object ID.
*/
#define LWM2M_ON_OFF_SWITCH_OBJECT_ID 3342
/**
* @name On/Off switch object resource's IDs.
* @{
*/
/**
* @brief Digital input state esource ID.
*/
#define LWM2M_ON_OFF_SWITCH_DIGITAL_INPUT_STATE_ID 5500
/**
* @brief Digital Input Counter resource ID.
*/
#define LWM2M_ON_OFF_SWITCH_DIGITAL_INPUT_COUNTER_ID 5501
/**
* @brief On Time resource ID.
*/
#define LWM2M_ON_OFF_SWITCH_ON_TIME_ID 5852
/**
* @brief Off Time resource ID.
*/
#define LWM2M_ON_OFF_SWITCH_OFF_TIME_ID 5854
/**
* @brief Application type resource ID.
*/
#define LWM2M_ON_OFF_SWITCH_APP_TYPE_ID 5750
/** @} */
/**
* @brief Arguments for the creation of an on/off switch object instance.
*/
typedef struct lwm2m_obj_on_off_switch_args {
const char *app_type; /**< Array of chars with the app type. May be NULL. */
size_t app_type_len; /**< Length of app_type */
} lwm2m_obj_on_off_switch_args_t;
/**
* @brief Initialize the on/off switch object.
*
* @param[in] client_data LwM2M client data.
*
* @return Pointer to the On/Off switch object on success
*/
lwm2m_object_t *lwm2m_object_on_off_switch_init(lwm2m_client_data_t *client_data);
/**
* @brief Create a new on/off switch instance and add it to the @p object list.
*
* @param[in] args Initialize structure with the parameter for the instance. May
* not be NULL.
* @param[in] instance_id ID for the new instance. It must be between 0 and
* (UINT16_MAX - 1), if -1 the next available ID will be used.
*
* @return 0 on success
* @return -EINVAL if an invalid @p instance_id is given
* @return -ENOMEM if no memory is available to create a new instance
*/
int lwm2m_object_on_off_switch_instance_create(const lwm2m_obj_on_off_switch_args_t *args,
int32_t instance_id);
/**
* @brief Update the status of a on/off switch instance.
*
* @param[in] instance_id ID of the instance to update.
* @param[in] status New status of the switch.
*
* @return 0 on success
* @return -EINVAL if the instance does not exist
*/
int lwm2m_object_on_off_switch_update_status(uint16_t instance_id, bool status);
/**
* @brief Update the application type of a on/off switch instance.
*
* @param[in] instance_id ID of the instance to update.
* @param[in] app_type New application type.
* @param[in] len Length of the app_type string.
*
* @return 0 on success
* @return -EINVAL if the instance does not exist
* @return -ENOBUFS if the app_type string is too long
*/
int lwm2m_object_on_off_switch_update_app_type(uint16_t instance_id, const char *app_type,
size_t len);
#ifdef __cplusplus
}
#endif
#endif /* OBJECTS_ON_OFF_SWITCH_H */
/** @} */