diff --git a/examples/lwm2m/Makefile b/examples/lwm2m/Makefile index 741773e8e40f..a214c2079cbe 100644 --- a/examples/lwm2m/Makefile +++ b/examples/lwm2m/Makefile @@ -32,6 +32,7 @@ SERVER_URI ?= '"coap://[fd00:dead:beef::1]"' # NOTE: Add the package for wakaama USEPKG += wakaama +USEMODULE += wakaama_objects_light_control # Uncomment to enable Wakaama debug log #CFLAGS += -DCONFIG_LWM2M_WITH_LOGS=1 diff --git a/examples/lwm2m/README.md b/examples/lwm2m/README.md index 1f51a119f609..8a722ca85fa4 100644 --- a/examples/lwm2m/README.md +++ b/examples/lwm2m/README.md @@ -6,10 +6,10 @@ on the node with instances of the following objects: - [Security object](http://www.openmobilealliance.org/tech/profiles/LWM2M_Security-v1_0.xml) - [Server object](http://www.openmobilealliance.org/tech/profiles/LWM2M_Server-v1_0.xml) - [Device object](http://www.openmobilealliance.org/tech/profiles/LWM2M_Device-v1_0_3.xml) +- [Light control object](https://raw.githubusercontent.com/OpenMobileAlliance/lwm2m-registry/prod/3311.xml) The application is based on the Eclipse Wakaama -[example client](https://github.com/eclipse/wakaama/tree/master/examples/client) -. +[example client](https://github.com/eclipse/wakaama/tree/master/examples/client). ## Usage @@ -18,10 +18,13 @@ To test the client a LwM2M server where to register is needed. [Eclipse Leshan](https://github.com/eclipse/leshan) demo is a good option for running one locally. -To run the demo server: +To run the demo server, download the jar file: ```shell wget https://hudson.eclipse.org/leshan/job/leshan/lastSuccessfulBuild/artifact/leshan-server-demo.jar +``` +And run it: +```shell java -jar ./leshan-server-demo.jar ``` It will output the addresses where it is listening: @@ -48,11 +51,15 @@ By default the bootstrap server option is disabled, it can be enabled by definin To run the bootstrap server, make sure that the ports it uses are different from the ones of previous server (default are 5683 for CoAP, 5684 for CoAPs, and 8080 for the webserver), and that it corresponds to the one set in -`lwm2m.h` as `CONFIG_LWM2M_BSSERVER_PORT`: +`lwm2m.h` as `CONFIG_LWM2M_BSSERVER_PORT`. + +Download the jar file: ```shell -# download demo wget https://hudson.eclipse.org/leshan/job/leshan/lastSuccessfulBuild/artifact/leshan-bsserver-demo.jar +``` +And run it: +```shell# download demo # set CoAP, CoAPs and webserver ports for bootstrap server BS_COAPPORT=5685 BS_COAPSPORT=5686 @@ -111,3 +118,7 @@ BOARD= make clean all flash term #### Shell commands - `lwm2m start`: Starts the LwM2M by configuring the module and registering to the server. +- `lwm2m light [color]`: Sets the light state to on or off, + with a given dimmer value and optional color. +- `lwm2m mem`: (Only available if `DEVELHELP` is enabled in your Makefile) Prints the memory + usage of the LwM2M module. diff --git a/examples/lwm2m/lwm2m_cli.c b/examples/lwm2m/lwm2m_cli.c index 696a9b153ecd..b2030846695a 100644 --- a/examples/lwm2m/lwm2m_cli.c +++ b/examples/lwm2m/lwm2m_cli.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 HAW Hamburg + * 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 @@ -11,23 +11,47 @@ * @{ * * @file - * @brief Wakaama LwM2M Client CLI support + * @brief Wakaama LwM2M Client example CLI support * * @author Leandro Lanzieri * @} */ -#include "kernel_defines.h" +#include "board.h" #include "lwm2m_client.h" #include "lwm2m_client_objects.h" #include "lwm2m_platform.h" +#include "objects/light_control.h" +#include "objects/common.h" -#define OBJ_COUNT (3) +#define LED_COLOR "FFFFFF" +#define LED_APP_TYPE "LED 0" +# define OBJ_COUNT (4) uint8_t connected = 0; lwm2m_object_t *obj_list[OBJ_COUNT]; lwm2m_client_data_t client_data; +void _light_cb(lwm2m_object_t *object, uint16_t instance_id, bool status, uint8_t dimmer, + const char* color, const char* app_type, void *arg) +{ + (void)object; + (void)instance_id; + (void)arg; + + printf("LED (%s) is now %s, with color %s and intensity %d%%\n", app_type, status? "ON":"OFF", + color, dimmer); + +#ifdef LED0_ON + if (status) { + LED0_ON; + } + else { + LED0_OFF; + } +#endif +} + void lwm2m_cli_init(void) { /* this call is needed before creating any objects */ @@ -37,12 +61,56 @@ void lwm2m_cli_init(void) obj_list[0] = lwm2m_client_get_security_object(&client_data); obj_list[1] = lwm2m_client_get_server_object(&client_data); obj_list[2] = lwm2m_client_get_device_object(&client_data); + obj_list[3] = lwm2m_object_light_control_init(&client_data); + + lwm2m_obj_light_control_args_t args = { + .cb = _light_cb, + .cb_arg = NULL, + .color = LED_COLOR, + .color_len = sizeof(LED_COLOR) - 1, + .app_type = LED_APP_TYPE, + .app_type_len = sizeof(LED_APP_TYPE) - 1 + }; + + int res = lwm2m_object_light_control_instance_create(&args, 0); + + if (res < 0) { + puts("Error instantiating light control"); + } if (!obj_list[0] || !obj_list[1] || !obj_list[2]) { puts("Could not create mandatory objects"); } } +static int _parse_lwm2m_light_cmd(int argc, char **argv) +{ + if (argc < 4) { + printf("usage: %s light [color]\n", argv[0]); + return 1; + } + + if (!connected) { + puts("LwM2M client not connected"); + return 1; + } + + bool status = !strcmp(argv[2], "on"); + uint8_t dimmer = atoi(argv[3]); + + if (argc > 4) { + char* color = argv[4]; + lwm2m_object_light_control_update_color(0, color, strlen(color), false); + } + + lwm2m_object_light_control_update_status(0, status, false); + + /* call the callback now to actually update the light */ + lwm2m_object_light_control_update_dimmer(0, dimmer, true); + + return 0; +} + int lwm2m_cli_cmd(int argc, char **argv) { if (argc == 1) { @@ -57,17 +125,21 @@ int lwm2m_cli_cmd(int argc, char **argv) return 0; } - if (IS_ACTIVE(DEVELHELP) && !strcmp(argv[1],"mem")) { + if (IS_ACTIVE(DEVELHELP) && !strcmp(argv[1], "mem")) { lwm2m_tlsf_status(); return 0; } + if (!strcmp(argv[1], "light")) { + return _parse_lwm2m_light_cmd(argc, argv); + } + help_error: if (IS_ACTIVE(DEVELHELP)) { - printf("usage: %s \n", argv[0]); + printf("usage: %s \n", argv[0]); } else { - printf("usage: %s \n", argv[0]); + printf("usage: %s \n", argv[0]); } return 1; diff --git a/pkg/wakaama/Kconfig b/pkg/wakaama/Kconfig index 1375adae59de..1e03df74537b 100644 --- a/pkg/wakaama/Kconfig +++ b/pkg/wakaama/Kconfig @@ -126,4 +126,6 @@ config LWM2M_TLSF_BUFFER int "Allocation buffer size" default 5120 +rsource "contrib/objects/Kconfig" + endif # KCONFIG_USEPKG_WAKAAMA diff --git a/pkg/wakaama/contrib/objects/Kconfig b/pkg/wakaama/contrib/objects/Kconfig new file mode 100644 index 000000000000..dbd167c552b7 --- /dev/null +++ b/pkg/wakaama/contrib/objects/Kconfig @@ -0,0 +1,8 @@ +# Copyright (c) 2021 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. +# + +rsource "Kconfig.light_control" diff --git a/pkg/wakaama/contrib/objects/Kconfig.light_control b/pkg/wakaama/contrib/objects/Kconfig.light_control new file mode 100644 index 000000000000..b8b734e1b15a --- /dev/null +++ b/pkg/wakaama/contrib/objects/Kconfig.light_control @@ -0,0 +1,22 @@ +# Copyright (c) 2021 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. +# + +menu "Light Control object" + +config LWM2M_LIGHT_INSTANCES_MAX + int "Maximum number of Light Control object instances" + default 3 + +config LWM2M_LIGHT_CONTROL_COLOR_MAX_SIZE + int "Maximum size of a Light Control color string" + default 16 + +config LWM2M_LIGHT_CONTROL_APP_TYPE_MAX_SIZE + int "Maximum size of a Light Control application type string" + default 16 + +endmenu # Light Control object diff --git a/pkg/wakaama/contrib/objects/light_control.c b/pkg/wakaama/contrib/objects/light_control.c new file mode 100644 index 000000000000..4f61c6fc9cec --- /dev/null +++ b/pkg/wakaama/contrib/objects/light_control.c @@ -0,0 +1,619 @@ +/* + * 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_light_control + * + * @file + * @brief Light Control object implementation for LwM2M client using Wakaama + * + * @author Leandro Lanzieri + * @} + */ + +#include "mutex.h" +#include "inttypes.h" +#include "liblwm2m.h" +#include "lwm2m_client.h" +#include "objects/light_control.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 Light Control object instance + */ +typedef struct lwm2m_obj_light_control_inst { + lwm2m_list_t list; /**< list handle */ + bool status; /**< light status */ + uint8_t dimmer; /**< dimmer value */ + ztimer_stopwatch_t stopwatch; /**< stopwatch for on_time */ + lwm2m_obj_light_control_cb_t cb; /**< application callback */ + void *cb_arg; /**< callback argument */ + char color[CONFIG_LWM2M_LIGHT_CONTROL_COLOR_MAX_SIZE]; /**< light color */ + char app_type[CONFIG_LWM2M_LIGHT_CONTROL_APP_TYPE_MAX_SIZE]; /**< application type */ +} lwm2m_obj_light_control_inst_t; + +/** + * @brief 'Read' callback for the LwM2M Light Control object implementation. + * + * @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 Light Control 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(uint16_t instance_id, int *num_data, lwm2m_data_t **data_array, + lwm2m_object_t *object); + +/** + * @brief 'Write' callback for the LwM2M Light Control object implementation. + * + * @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 Light Control 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(uint16_t instance_id, int num_data, lwm2m_data_t * data_array, + lwm2m_object_t * object); + +/** + * @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_light_control_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_light_control_object { + lwm2m_object_t wakaama_object; /**< Wakaama internal object */ + mutex_t lock; /**< mutex for the instances access */ + lwm2m_obj_light_control_inst_t *free_instances; /**< list of free instances */ + lwm2m_obj_light_control_inst_t instances[CONFIG_LWM2M_LIGHT_INSTANCES_MAX]; /**< instances */ +}; + +struct lwm2m_light_control_object _light_control_object = { + .lock = MUTEX_INIT, + .wakaama_object = { + .objID = LWM2M_LIGHT_CONTROL_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_light_control_inst_t *instance) +{ + assert(data); + assert(instance); + + switch (data->id) { + case LWM2M_LIGHT_CONTROL_ON_OFF_ID: + lwm2m_data_encode_bool(instance->status, data); + break; + + case LWM2M_LIGHT_CONTROL_DIMMER_ID: + lwm2m_data_encode_int(instance->dimmer, data); + break; + + case LWM2M_LIGHT_CONTROL_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_LIGHT_CONTROL_COLOUR_ID: + lwm2m_data_encode_string(instance->color, data); + break; + + case LWM2M_LIGHT_CONTROL_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(uint16_t instance_id, int *num_data, lwm2m_data_t **data_array, + lwm2m_object_t *object) +{ + lwm2m_obj_light_control_inst_t *instance; + uint8_t result = COAP_404_NOT_FOUND; + int i = 0; + + mutex_lock(&_light_control_object.lock); + + /* try to get the requested instance from the object list */ + instance = (lwm2m_obj_light_control_inst_t *)lwm2m_list_find(object->instanceList, instance_id); + if (!instance) { + DEBUG("[lwm2m:light_control: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:light_control:read]: reading all resources\n"); + + uint16_t res_list[] = { + LWM2M_LIGHT_CONTROL_ON_OFF_ID, + LWM2M_LIGHT_CONTROL_DIMMER_ID, + LWM2M_LIGHT_CONTROL_ON_TIME_ID, + LWM2M_LIGHT_CONTROL_COLOUR_ID, + LWM2M_LIGHT_CONTROL_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:light_control: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(&_light_control_object.lock); + return result; +} + +static uint8_t _write_cb(uint16_t instance_id, int num_data, lwm2m_data_t * data_array, + lwm2m_object_t * object) +{ + lwm2m_obj_light_control_inst_t *instance; + uint8_t result = COAP_204_CHANGED; + bool call_cb = false; + + mutex_lock(&_light_control_object.lock); + + /* try to get the requested instance from the object list */ + instance = (lwm2m_obj_light_control_inst_t *)lwm2m_list_find(object->instanceList, instance_id); + if (!instance) { + DEBUG("[lwm2m:light_control: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_LIGHT_CONTROL_ON_OFF_ID: { + bool prev_status = instance->status; + + lwm2m_data_decode_bool(&data_array[i], &instance->status); + + /* reset timer on OFF -> ON transitions */ + if (instance->status && !prev_status) { + ztimer_stopwatch_reset(&instance->stopwatch); + } + + call_cb = true; + _mark_resource_changed(instance_id, LWM2M_LIGHT_CONTROL_ON_OFF_ID); + break; + } + + case LWM2M_LIGHT_CONTROL_ON_TIME_ID: + { + int64_t val; + lwm2m_data_decode_int(&data_array[i], &val); + if (val != 0) { + DEBUG("[lwm2m:light_control: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_LIGHT_CONTROL_ON_TIME_ID); + } + break; + } + + case LWM2M_LIGHT_CONTROL_DIMMER_ID: + { + int64_t val; + lwm2m_data_decode_int(&data_array[i], &val); + if (val < 0 || val > 100) { + DEBUG("[lwm2m:light_control:write]: invalid dimmer value\n"); + result = COAP_400_BAD_REQUEST; + } else { + instance->dimmer = (uint8_t)val; + call_cb = true; + _mark_resource_changed(instance_id, LWM2M_LIGHT_CONTROL_DIMMER_ID); + } + break; + } + + case LWM2M_LIGHT_CONTROL_COLOUR_ID: + if (data_array[i].type != LWM2M_TYPE_STRING && data_array[i].type != LWM2M_TYPE_OPAQUE) { + DEBUG("[lwm2m:light_control:write]: invalid type for color" + "(%" PRId8 ")\n", (uint8_t)(data_array[i].type)); + result = COAP_400_BAD_REQUEST; + break; + } + + if (data_array[i].value.asBuffer.length > + CONFIG_LWM2M_LIGHT_CONTROL_COLOR_MAX_SIZE - 1) { + DEBUG("[lwm2m:light_control:write]: value too big for color\n"); + result = COAP_500_INTERNAL_SERVER_ERROR; + break; + } + + memcpy(instance->color, data_array[i].value.asBuffer.buffer, + data_array[i].value.asBuffer.length); + instance->color[data_array[i].value.asBuffer.length] = '\0'; + call_cb = true; + _mark_resource_changed(instance_id, LWM2M_LIGHT_CONTROL_COLOUR_ID); + break; + + case LWM2M_LIGHT_CONTROL_APP_TYPE_ID: + if (data_array[i].type != LWM2M_TYPE_STRING && data_array[i].type != LWM2M_TYPE_OPAQUE) { + DEBUG("[lwm2m:light_control: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_LIGHT_CONTROL_APP_TYPE_MAX_SIZE - 1) { + DEBUG("[lwm2m:light_control: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'; + call_cb = true; + _mark_resource_changed(instance_id, LWM2M_LIGHT_CONTROL_APP_TYPE_ID); + break; + } + } + + if (call_cb && instance->cb) { + instance->cb(object, instance_id, instance->status, instance->dimmer, instance->color, + instance->app_type, instance->cb_arg); + } + +free_out: + mutex_unlock(&_light_control_object.lock); + return result; +} + +static void _mark_resource_changed(uint16_t instance_id, uint16_t resource_id) +{ + lwm2m_uri_t uri; + uri.flag = LWM2M_URI_FLAG_OBJECT_ID | LWM2M_URI_FLAG_INSTANCE_ID | LWM2M_URI_FLAG_RESOURCE_ID; + uri.objectId = LWM2M_LIGHT_CONTROL_OBJECT_ID; + uri.instanceId = instance_id; + uri.resourceId = resource_id; + + lwm2m_client_data_t *client_data; + client_data = (lwm2m_client_data_t *)_light_control_object.wakaama_object.userData; + lwm2m_resource_value_changed(client_data->lwm2m_ctx, &uri); +} + + +lwm2m_object_t *lwm2m_object_light_control_init(lwm2m_client_data_t *client_data) +{ + /* initialize the instances */ + for (unsigned i = 0; i < CONFIG_LWM2M_LIGHT_INSTANCES_MAX; i++) { + _light_control_object.instances[i].list.next = NULL; + _light_control_object.instances[i].list.id = UINT16_MAX; + + _FREE_INSTANCES(_light_control_object) = (lwm2m_obj_light_control_inst_t *) LWM2M_LIST_ADD( + _FREE_INSTANCES(_light_control_object), + &(_light_control_object.instances[i]) + ); + } + + _light_control_object.wakaama_object.userData = client_data; + + return &(_light_control_object.wakaama_object); +} + +int lwm2m_object_light_control_instance_create(const lwm2m_obj_light_control_args_t *args, + int32_t instance_id) +{ + assert(args); + int result = -ENOMEM; + lwm2m_obj_light_control_inst_t *instance = NULL; + uint16_t _instance_id; + + /* lock object */ + mutex_lock(&_light_control_object.lock); + + /* determine ID for new instance */ + if (instance_id < 0) { + _instance_id = lwm2m_list_newId((lwm2m_list_t *)_USED_INSTANCES(_light_control_object)); + } + else { + /* sanity check */ + if (instance_id >= (UINT16_MAX - 1)) { + DEBUG("[lwm2m:light_control]: 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(_light_control_object), _instance_id ) != NULL) + { + DEBUG("[lwm2m:light_control]: 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(_light_control_object) = (lwm2m_obj_light_control_inst_t *) lwm2m_list_remove( + (lwm2m_list_t *) _FREE_INSTANCES(_light_control_object), + UINT16_MAX, + (lwm2m_list_t **) &instance + ); + + if (!instance) { + DEBUG("[lwm2m:light_control]: can't allocate new instance\n"); + goto free_out; + } + + memset(instance, 0, sizeof(lwm2m_obj_light_control_inst_t)); + + instance->list.id = _instance_id; + instance->status = false; + + ztimer_stopwatch_init(ZTIMER_SEC, &instance->stopwatch); + ztimer_stopwatch_start(&instance->stopwatch); + + /* if callback specified set it */ + if (args->cb) { + instance->cb = args->cb; + } + + /* if callback argument specified set it */ + if (args->cb_arg) { + instance->cb_arg = args->cb_arg; + } + + /* if color is specified, copy locally */ + if (args->color) { + if (args->color_len > CONFIG_LWM2M_LIGHT_CONTROL_COLOR_MAX_SIZE) { + DEBUG("[lwm2m:light_control]: not enough space for color string\n"); + goto free_out; + } + memcpy(instance->color, args->color, args->color_len); + } + + /* if app is specified, copy locally */ + if (args->app_type) { + if (args->app_type_len > CONFIG_LWM2M_LIGHT_CONTROL_APP_TYPE_MAX_SIZE) { + DEBUG("[lwm2m:light_control]: not enough space for app_type string\n"); + goto free_out; + } + memcpy(instance->app_type, args->app_type, args->app_type_len); + } + + DEBUG("[lwm2m:light_control]: new instance with ID %" PRIu16 "\n", _instance_id); + + /* add the new instance to the list */ + _USED_INSTANCES(_light_control_object) = LWM2M_LIST_ADD( + _USED_INSTANCES(_light_control_object), + instance + ); + + result = 0; + +free_out: + mutex_unlock(&_light_control_object.lock); + return result; +} + +int lwm2m_object_light_control_update_dimmer(uint16_t instance_id, uint8_t dimmer, bool call_cb) +{ + int result = -EINVAL; + lwm2m_obj_light_control_inst_t *instance; + + mutex_lock(&_light_control_object.lock); + + if (dimmer > 100) { + DEBUG("[lwm2m:light_control]: invalid dimmer value\n"); + goto free_out; + } + + instance = (lwm2m_obj_light_control_inst_t *)LWM2M_LIST_FIND( + _USED_INSTANCES(_light_control_object), + instance_id + ); + + if (!instance) { + DEBUG("[lwm2m:light_control]: can't find instance %" PRId16 "\n", instance_id); + goto free_out; + } + + instance->dimmer = dimmer; + + if (call_cb && instance->cb) { + instance->cb(&_light_control_object.wakaama_object, instance_id, instance->status, + instance->dimmer, instance->color, instance->app_type, instance->cb_arg); + } + + _mark_resource_changed(instance_id, LWM2M_LIGHT_CONTROL_DIMMER_ID); + + result = 0; + +free_out: + mutex_unlock(&_light_control_object.lock); + return result; +} + +int lwm2m_object_light_control_update_status(uint16_t instance_id, bool status, bool call_cb) +{ + int result = -EINVAL; + lwm2m_obj_light_control_inst_t *instance; + + mutex_lock(&_light_control_object.lock); + + instance = (lwm2m_obj_light_control_inst_t *)LWM2M_LIST_FIND( + _USED_INSTANCES(_light_control_object), + instance_id + ); + + if (!instance) { + DEBUG("[lwm2m:light_control]: can't find instance %" PRId16 "\n", instance_id); + goto free_out; + } + + if (status && !instance->status) { + ztimer_stopwatch_start(&instance->stopwatch); + } + + instance->status = status; + + if (call_cb && instance->cb) { + instance->cb(&_light_control_object.wakaama_object, instance_id, instance->status, + instance->dimmer, instance->color, instance->app_type, instance->cb_arg); + } + + _mark_resource_changed(instance_id, LWM2M_LIGHT_CONTROL_ON_OFF_ID); + + result = 0; + +free_out: + mutex_unlock(&_light_control_object.lock); + return result; +} + +int lwm2m_object_light_control_update_color(uint16_t instance_id, const char *color, size_t len, + bool call_cb) +{ + int result = -EINVAL; + lwm2m_obj_light_control_inst_t *instance; + + mutex_lock(&_light_control_object.lock); + + if (len > CONFIG_LWM2M_LIGHT_CONTROL_COLOR_MAX_SIZE - 1) { + DEBUG("[lwm2m:light_control]: color string too long\n"); + result = -ENOBUFS; + goto free_out; + } + + instance = (lwm2m_obj_light_control_inst_t *)LWM2M_LIST_FIND( + _USED_INSTANCES(_light_control_object), + instance_id + ); + + if (!instance) { + DEBUG("[lwm2m:light_control]: can't find instance %" PRId16 "\n", instance_id); + goto free_out; + } + + memcpy(instance->color, color, len); + instance->color[len] = '\0'; + + if (call_cb && instance->cb) { + instance->cb(&_light_control_object.wakaama_object, instance_id, instance->status, + instance->dimmer, instance->color, instance->app_type, instance->cb_arg); + } + + _mark_resource_changed(instance_id, LWM2M_LIGHT_CONTROL_COLOUR_ID); + + result = 0; + +free_out: + mutex_unlock(&_light_control_object.lock); + return result; +} + +int lwm2m_object_light_control_update_app_type(uint16_t instance_id, const char *app_type, + size_t len, bool call_cb) +{ + int result = -EINVAL; + lwm2m_obj_light_control_inst_t *instance; + + mutex_lock(&_light_control_object.lock); + + if (len > CONFIG_LWM2M_LIGHT_CONTROL_APP_TYPE_MAX_SIZE - 1) { + DEBUG("[lwm2m:light_control]: app_type string too long\n"); + result = -ENOBUFS; + goto free_out; + } + + instance = (lwm2m_obj_light_control_inst_t *)LWM2M_LIST_FIND( + _USED_INSTANCES(_light_control_object), + instance_id + ); + + if (!instance) { + DEBUG("[lwm2m:light_control]: can't find instance %" PRId16 "\n", instance_id); + goto free_out; + } + + memcpy(instance->app_type, app_type, len); + instance->app_type[len] = '\0'; + + if (call_cb && instance->cb) { + instance->cb(&_light_control_object.wakaama_object, instance_id, instance->status, + instance->dimmer, instance->color, instance->app_type, instance->cb_arg); + } + + _mark_resource_changed(instance_id, LWM2M_LIGHT_CONTROL_APP_TYPE_ID); + + result = 0; + +free_out: + mutex_unlock(&_light_control_object.lock); + return result; +} diff --git a/pkg/wakaama/include/objects/light_control.h b/pkg/wakaama/include/objects/light_control.h new file mode 100644 index 000000000000..924f92e0bb37 --- /dev/null +++ b/pkg/wakaama/include/objects/light_control.h @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2021 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_light_control Light Control + * @brief Light Control 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 Light Control object (ID 3311) as specified in the LwM2M registry. + * + * This Object is used to control a light source, such as a LED or other light. It allows a light to + * be turned on or off and dim it. + * + * To use this object add `USEMODULE += wakaama_objects_light_control` to the application Makefile. + * + * ## Resources + * + * For an XML description of the object see + * https://raw.githubusercontent.com/OpenMobileAlliance/lwm2m-registry/prod/3311.xml + * +| Name | ID | Mandatory | Type | Range | Units | Implemented | +|-------------------------|:----:|:---------:|:-------:|:-------:|:-----:|:-----------:| +| On/Off | 5850 | Yes | Boolean | - | - | Yes[1] | +| Dimmer | 5851 | No | Integer | 0 - 100 | /100 | Yes[1] | +| On time | 5852 | No | Integer | - | s | Yes | +| Cumulative active power | 5805 | No | Float | - | Wh | No | +| Power factor | 5820 | No | Float | - | - | No | +| Colour | 5706 | No | String | - | - | Yes | +| Sensor Units | 5701 | No | String | - | - | No | +| Application Type | 5750 | No | String | - | - | Yes | + * + * [1]: The handling of these resources are implemented, but its actual impact on the light state + * depends on the application. + * ## Usage + * + * 1. Initialize the LwM2M client with @ref lwm2m_object_light_control_init, by passing a pointer + * to the LwM2M client data. + * + * 2. Now you can create instances of the Light Control object with + * @ref lwm2m_object_light_control_instance_create. As part of the arguments, + * you can pass a callback that will be called when the light resources are updated + * (i.e. status, dimmer, color, app_type), as well as an user argument. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.c} + * #define LIGHT_COLOR "FFFFFF" + * #define LIGHT_APP_TYPE "LED 0" + * + * // ... /// + * + * void _light_cb(lwm2m_object_t *object, uint16_t instance_id, bool status, uint8_t dimmer, + * const char* color, const char* app_type, void *arg) + * { + * (void)object; + * (void)instance_id; + * (void)arg; + * + * printf("%s is now %s, ", app_type, status? "ON":"OFF"); + * printf("with color %s and intensity %d%%\n", color, dimmer); + * } + * + * // ... /// + * + * lwm2m_object_t *light_control; + * lwm2m_client_data_t client_data; + * + * lwm2m_client_init(&client_data); + * light_control = lwm2m_object_light_control_init(&client_data); + * + * lwm2m_obj_light_control_args_t args = { + * .cb = _light_cb, + * .cb_arg = NULL, + * .color = LIGHT_COLOR, + * .color_len = sizeof(LIGHT_COLOR) - 1, + * .app_type = LIGHT_APP_TYPE, + * .app_type_len = sizeof(LIGHT_APP_TYPE) - 1 + * }; + * + * int result = lwm2m_object_light_control_instance_create(&args, 0); + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 3. You can update the status, dimmer, color and app_type of the light control instance with + * @ref lwm2m_object_light_control_update_status, @ref lwm2m_object_light_control_update_dimmer, + * @ref lwm2m_object_light_control_update_color and + * @ref lwm2m_object_light_control_update_app_type respectively. This will make sure to send + * notifications to servers that may be observing these resources. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.c} + * lwm2m_object_light_control_update_status(0, true, false); + * lwm2m_object_light_control_update_dimmer(0, 50, false); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * @{ + * + * @file + * + * @author Leandro Lanzieri + */ + +#ifndef OBJECTS_LIGHT_CONTROL_H +#define OBJECTS_LIGHT_CONTROL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "liblwm2m.h" + +/** + * @defgroup lwm2m_objects_light_control_config LwM2M Light Control object compile configurations + * @ingroup lwm2m_client_config + * @{ + */ +/** + * @brief Maximum number of instances of the object + */ +#ifndef CONFIG_LWM2M_LIGHT_INSTANCES_MAX +#define CONFIG_LWM2M_LIGHT_INSTANCES_MAX (3U) +#endif + +/** + * @brief Maximum size for the color string + */ +#ifndef CONFIG_LWM2M_LIGHT_CONTROL_COLOR_MAX_SIZE +#define CONFIG_LWM2M_LIGHT_CONTROL_COLOR_MAX_SIZE (16U) +#endif + +/** + * @brief Maximum size for the application type string + */ +#ifndef CONFIG_LWM2M_LIGHT_CONTROL_APP_TYPE_MAX_SIZE +#define CONFIG_LWM2M_LIGHT_CONTROL_APP_TYPE_MAX_SIZE (16U) +#endif +/** @} */ + +/** + * @brief Light Control object ID. + */ +#define LWM2M_LIGHT_CONTROL_OBJECT_ID 3311 + +/** + * @name Light Control object resource's IDs. + * @{ + */ +/** + * @brief Light status resource ID. + */ +#define LWM2M_LIGHT_CONTROL_ON_OFF_ID 5850 +/** + * @brief Dimmer value resource ID. + */ +#define LWM2M_LIGHT_CONTROL_DIMMER_ID 5851 +/** + * @brief On Time resource ID. + */ +#define LWM2M_LIGHT_CONTROL_ON_TIME_ID 5852 +/** + * @brief Light active power resource ID. + */ +#define LWM2M_LIGHT_CONTROL_ACT_PWR_ID 5805 +/** + * @brief Light power factor resource ID. + */ +#define LWM2M_LIGHT_CONTROL_PWR_FACTOR_ID 5820 +/** + * @brief Light color resource ID. + */ +#define LWM2M_LIGHT_CONTROL_COLOUR_ID 5706 +/** + * @brief Units of the power sensing resource ID. + */ +#define LWM2M_LIGHT_CONTROL_UNITS_ID 5701 +/** + * @brief Light application type resource ID. + */ +#define LWM2M_LIGHT_CONTROL_APP_TYPE_ID 5750 +/** @} */ + +/** + * @brief Signature of the callback called when the light resources are updated. + * + * @param[in] object Light Control object handle. + * @param[in] instance_id ID of the instance where the resource changed. + * @param[in] status Light status. + * @param[in] dimmer Dimmer value. + * @param[in] color Light color NULL-terminated string. + * @param[in] app_type Light application type NULL-terminated string. + * @param[in] arg Argument registered in + * @ref lwm2m_object_light_control_instance_create. + */ +typedef void (*lwm2m_obj_light_control_cb_t)(lwm2m_object_t *object, uint16_t instance_id, + bool status, uint8_t dimmer, const char* color, + const char* app_type, void *arg); + +/** + * @brief Arguments for the creation of a Light Control object instance. + */ +typedef struct lwm2m_obj_light_control_args { + lwm2m_obj_light_control_cb_t cb; /**< Callback for status and dimmer changes. May be NULL. */ + void *cb_arg; /**< Argument to call cb with. May be NULL. */ + const char *color; /**< Array of chars with the light color. May be NULL. */ + size_t color_len; /**< Length of color */ + const char *app_type; /**< Array of chars with the light app type. May be NULL. */ + size_t app_type_len; /**< Length of app_type */ +} lwm2m_obj_light_control_args_t; + +/** + * @brief Initialize the Light Control object. + * + * @param[in] client_data LwM2M client data. + * + * @return Pointer to the Light Control object on success + */ +lwm2m_object_t *lwm2m_object_light_control_init(lwm2m_client_data_t *client_data); + +/** + * @brief Create a new Light Control 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_light_control_instance_create(const lwm2m_obj_light_control_args_t *args, + int32_t instance_id); + +/** + * @brief Update the status of a light control instance. + * + * @param[in] instance_id ID of the instance to update. + * @param[in] status New status of the light. + * @param[in] call_cb If true, the callback @ref lwm2m_obj_light_control_args_t::cb will be + * called. + * + * @return 0 on success + * @return -EINVAL if the instance does not exist + */ +int lwm2m_object_light_control_update_status(uint16_t instance_id, bool status, bool call_cb); + +/** + * @brief Update the dimmer value of a light control instance. + * + * @param[in] instance_id ID of the instance to update. + * @param[in] dimmer New dimmer value. + * @param[in] call_cb If true, the callback @ref lwm2m_obj_light_control_args_t::cb will be + * called. + * + * @return 0 on success + * @return -EINVAL if the instance does not exist + */ +int lwm2m_object_light_control_update_dimmer(uint16_t instance_id, uint8_t dimmer, bool call_cb); + +/** + * @brief Update the color of a light control instance. + * + * @param[in] instance_id ID of the instance to update. + * @param[in] color New color of the light. + * @param[in] len Length of the color string. + * @param[in] call_cb If true, the callback @ref lwm2m_obj_light_control_args_t::cb will be + * called. + * + * @return 0 on success + * @return -EINVAL if the instance does not exist + * @return -ENOBUFS if the color string is too long + */ +int lwm2m_object_light_control_update_color(uint16_t instance_id, const char *color, size_t len, + bool call_cb); + +/** + * @brief Update the application type of a light control instance. + * + * @param[in] instance_id ID of the instance to update. + * @param[in] app_type New application type of the light. + * @param[in] len Length of the app_type string. + * @param[in] call_cb If true, the callback @ref lwm2m_obj_light_control_args_t::cb will be + * called. + * + * @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_light_control_update_app_type(uint16_t instance_id, const char *app_type, + size_t len, bool call_cb); + +#ifdef __cplusplus +} +#endif + +#endif /* OBJECTS_LIGHT_CONTROL_H */ +/** @} */