File syn_lwm2m.c¶
File List > proto > syn_lwm2m.c
Go to the documentation of this file
#include "syn_lwm2m.h"
#if !defined(SYN_USE_LWM2M) || SYN_USE_LWM2M
#include "syntropic/port/syn_port_system.h"
#include "syntropic/util/syn_assert.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* ── TLV (Type-Length-Value) Binary Codec ─────────────────────────────────── */
size_t syn_lwm2m_tlv_encode(const SYN_LwM2M_TLV *tlv, uint8_t *buf, size_t max_len)
{
if (tlv == NULL || buf == NULL) {
return 0U;
}
size_t id_len = (tlv->id > 0xFFU) ? 2U : 1U;
size_t len_bytes = 0U;
uint8_t len_type_bits = 0U;
uint8_t direct_len_bits = 0U;
if (tlv->len <= 7U) {
len_bytes = 0U;
len_type_bits = 0x00U;
direct_len_bits = (uint8_t)tlv->len;
} else if (tlv->len <= 0xFFU) {
len_bytes = 1U;
len_type_bits = 0x08U;
} else if (tlv->len <= 0xFFFFU) {
len_bytes = 2U;
len_type_bits = 0x10U;
} else {
len_bytes = 3U;
len_type_bits = 0x18U;
}
size_t total_header_len = 1U + id_len + len_bytes;
if (total_header_len + tlv->len > max_len) {
return 0U;
}
uint8_t hdr = (uint8_t)(tlv->type & 0xC0U);
if (id_len == 2U) {
hdr |= 0x20U;
}
hdr |= len_type_bits;
hdr |= direct_len_bits;
size_t offset = 0U;
buf[offset++] = hdr;
if (id_len == 1U) {
buf[offset++] = (uint8_t)(tlv->id & 0xFFU);
} else {
buf[offset++] = (uint8_t)((tlv->id >> 8U) & 0xFFU);
buf[offset++] = (uint8_t)(tlv->id & 0xFFU);
}
if (len_bytes == 1U) {
buf[offset++] = (uint8_t)(tlv->len & 0xFFU);
} else if (len_bytes == 2U) {
buf[offset++] = (uint8_t)((tlv->len >> 8U) & 0xFFU);
buf[offset++] = (uint8_t)(tlv->len & 0xFFU);
} else if (len_bytes == 3U) {
buf[offset++] = (uint8_t)((tlv->len >> 16U) & 0xFFU);
buf[offset++] = (uint8_t)((tlv->len >> 8U) & 0xFFU);
buf[offset++] = (uint8_t)(tlv->len & 0xFFU);
}
if (tlv->len > 0U && tlv->val != NULL) {
(void)memcpy(buf + offset, tlv->val, tlv->len);
offset += tlv->len;
}
return offset;
}
size_t syn_lwm2m_tlv_encode_int(uint16_t res_id, int64_t val, uint8_t *buf, size_t max_len)
{
uint8_t val_buf[8];
size_t val_len = 0U;
if (val >= -128 && val <= 127) {
val_buf[0] = (uint8_t)((int8_t)val);
val_len = 1U;
} else if (val >= -32768 && val <= 32767) {
int16_t v16 = (int16_t)val;
val_buf[0] = (uint8_t)((v16 >> 8) & 0xFF);
val_buf[1] = (uint8_t)(v16 & 0xFF);
val_len = 2U;
} else if (val >= -2147483648LL && val <= 2147483647LL) {
int32_t v32 = (int32_t)val;
val_buf[0] = (uint8_t)((v32 >> 24) & 0xFF);
val_buf[1] = (uint8_t)((v32 >> 16) & 0xFF);
val_buf[2] = (uint8_t)((v32 >> 8) & 0xFF);
val_buf[3] = (uint8_t)(v32 & 0xFF);
val_len = 4U;
} else {
uint64_t u64 = (uint64_t)val;
for (size_t i = 0U; i < 8U; i++) {
val_buf[7U - i] = (uint8_t)((u64 >> (i * 8U)) & 0xFFU);
}
val_len = 8U;
}
SYN_LwM2M_TLV tlv = {
.type = SYN_LWM2M_TLV_RESOURCE,
.id = res_id,
.val = val_buf,
.len = val_len,
};
return syn_lwm2m_tlv_encode(&tlv, buf, max_len);
}
size_t syn_lwm2m_tlv_encode_float(uint16_t res_id, double val, uint8_t *buf, size_t max_len)
{
float f32 = (float)val;
uint8_t val_buf[4];
uint32_t u32 = 0U;
(void)memcpy(&u32, &f32, sizeof(u32));
val_buf[0] = (uint8_t)((u32 >> 24) & 0xFF);
val_buf[1] = (uint8_t)((u32 >> 16) & 0xFF);
val_buf[2] = (uint8_t)((u32 >> 8) & 0xFF);
val_buf[3] = (uint8_t)(u32 & 0xFF);
SYN_LwM2M_TLV tlv = {
.type = SYN_LWM2M_TLV_RESOURCE,
.id = res_id,
.val = val_buf,
.len = sizeof(val_buf),
};
return syn_lwm2m_tlv_encode(&tlv, buf, max_len);
}
size_t syn_lwm2m_tlv_encode_bool(uint16_t res_id, bool val, uint8_t *buf, size_t max_len)
{
uint8_t b_val = val ? 1U : 0U;
SYN_LwM2M_TLV tlv = {
.type = SYN_LWM2M_TLV_RESOURCE,
.id = res_id,
.val = &b_val,
.len = 1U,
};
return syn_lwm2m_tlv_encode(&tlv, buf, max_len);
}
size_t syn_lwm2m_tlv_encode_string(uint16_t res_id, const char *str, uint8_t *buf, size_t max_len)
{
if (str == NULL) {
return 0U;
}
SYN_LwM2M_TLV tlv = {
.type = SYN_LWM2M_TLV_RESOURCE,
.id = res_id,
.val = (const uint8_t *)str,
.len = strlen(str),
};
return syn_lwm2m_tlv_encode(&tlv, buf, max_len);
}
size_t syn_lwm2m_tlv_encode_opaque(uint16_t res_id, const uint8_t *data, size_t len, uint8_t *buf,
size_t max_len)
{
SYN_LwM2M_TLV tlv = {
.type = SYN_LWM2M_TLV_RESOURCE,
.id = res_id,
.val = data,
.len = len,
};
return syn_lwm2m_tlv_encode(&tlv, buf, max_len);
}
size_t syn_lwm2m_tlv_encode_value(uint16_t res_id, const SYN_LwM2M_Value *val, uint8_t *buf,
size_t max_len)
{
if (val == NULL) {
return 0U;
}
switch (val->type) {
case SYN_LWM2M_TYPE_INT:
case SYN_LWM2M_TYPE_TIME:
return syn_lwm2m_tlv_encode_int(res_id, val->val.integer, buf, max_len);
case SYN_LWM2M_TYPE_FLOAT:
return syn_lwm2m_tlv_encode_float(res_id, val->val.floating, buf, max_len);
case SYN_LWM2M_TYPE_BOOL:
return syn_lwm2m_tlv_encode_bool(res_id, val->val.boolean, buf, max_len);
case SYN_LWM2M_TYPE_STRING:
return syn_lwm2m_tlv_encode_string(res_id, val->val.str, buf, max_len);
case SYN_LWM2M_TYPE_OPAQUE:
return syn_lwm2m_tlv_encode_opaque(res_id, val->val.opaque.data, val->val.opaque.len, buf,
max_len);
case SYN_LWM2M_TYPE_OBJLNK: {
uint8_t link_buf[4];
link_buf[0] = (uint8_t)((val->val.objlnk.obj_id >> 8) & 0xFF);
link_buf[1] = (uint8_t)(val->val.objlnk.obj_id & 0xFF);
link_buf[2] = (uint8_t)((val->val.objlnk.inst_id >> 8) & 0xFF);
link_buf[3] = (uint8_t)(val->val.objlnk.inst_id & 0xFF);
SYN_LwM2M_TLV tlv = {
.type = SYN_LWM2M_TLV_RESOURCE,
.id = res_id,
.val = link_buf,
.len = 4U,
};
return syn_lwm2m_tlv_encode(&tlv, buf, max_len);
}
default:
return 0U;
}
}
size_t syn_lwm2m_tlv_encode_instance(uint16_t inst_id, const uint8_t *tlv_payload,
size_t payload_len, uint8_t *buf, size_t max_len)
{
SYN_LwM2M_TLV tlv = {
.type = SYN_LWM2M_TLV_OBJECT_INSTANCE,
.id = inst_id,
.val = tlv_payload,
.len = payload_len,
};
return syn_lwm2m_tlv_encode(&tlv, buf, max_len);
}
bool syn_lwm2m_tlv_decode(const uint8_t *buf, size_t buf_len, SYN_LwM2M_TLV *tlv, size_t *consumed)
{
if (buf == NULL || tlv == NULL || consumed == NULL || buf_len < 2U) {
return false;
}
uint8_t hdr = buf[0];
tlv->type = (SYN_LwM2M_TLV_Type)(hdr & 0xC0U);
bool id_16bit = (hdr & 0x20U) != 0U;
uint8_t len_type = (hdr & 0x18U) >> 3U;
uint8_t direct_len = hdr & 0x07U;
size_t offset = 1U;
if (id_16bit) {
if (offset + 2U > buf_len) {
return false;
}
tlv->id = ((uint16_t)buf[offset] << 8U) | (uint16_t)buf[offset + 1U];
offset += 2U;
} else {
tlv->id = (uint16_t)buf[offset++];
}
size_t val_len = 0U;
if (len_type == 0U) {
val_len = direct_len;
} else if (len_type == 1U) {
if (offset + 1U > buf_len) {
return false;
}
val_len = (size_t)buf[offset++];
} else if (len_type == 2U) {
if (offset + 2U > buf_len) {
return false;
}
val_len = ((size_t)buf[offset] << 8U) | (size_t)buf[offset + 1U];
offset += 2U;
} else {
if (offset + 3U > buf_len) {
return false;
}
val_len = ((size_t)buf[offset] << 16U) | ((size_t)buf[offset + 1U] << 8U) |
(size_t)buf[offset + 2U];
offset += 3U;
}
if (offset + val_len > buf_len) {
return false;
}
tlv->val = buf + offset;
tlv->len = val_len;
*consumed = offset + val_len;
return true;
}
bool syn_lwm2m_tlv_decode_int(const SYN_LwM2M_TLV *tlv, int64_t *out_val)
{
if (tlv == NULL || out_val == NULL || tlv->len == 0U || tlv->len > 8U || tlv->val == NULL) {
return false;
}
uint64_t uval = 0U;
for (size_t i = 0U; i < tlv->len; i++) {
uval = (uval << 8U) | (uint64_t)tlv->val[i];
}
if (tlv->len < 8U && (tlv->val[0] & 0x80U) != 0U) {
uint64_t mask = ~0ULL << (tlv->len * 8U);
uval |= mask;
}
int64_t val = 0;
(void)memcpy(&val, &uval, sizeof(val));
*out_val = val;
return true;
}
bool syn_lwm2m_tlv_decode_float(const SYN_LwM2M_TLV *tlv, double *out_val)
{
if (tlv == NULL || out_val == NULL || tlv->val == NULL) {
return false;
}
if (tlv->len == 4U) {
uint32_t u32 = ((uint32_t)tlv->val[0] << 24U) | ((uint32_t)tlv->val[1] << 16U) |
((uint32_t)tlv->val[2] << 8U) | (uint32_t)tlv->val[3];
float f32 = 0.0f;
(void)memcpy(&f32, &u32, sizeof(f32));
*out_val = (double)f32;
return true;
}
if (tlv->len == 8U) {
uint64_t u64 = 0U;
for (size_t i = 0U; i < 8U; i++) {
u64 = (u64 << 8U) | (uint64_t)tlv->val[i];
}
double d64 = 0.0;
(void)memcpy(&d64, &u64, sizeof(d64));
*out_val = d64;
return true;
}
return false;
}
bool syn_lwm2m_tlv_decode_bool(const SYN_LwM2M_TLV *tlv, bool *out_val)
{
if (tlv == NULL || out_val == NULL || tlv->len != 1U || tlv->val == NULL) {
return false;
}
*out_val = (tlv->val[0] != 0U);
return true;
}
bool syn_lwm2m_tlv_decode_string(const SYN_LwM2M_TLV *tlv, char *out_str, size_t max_len)
{
if (tlv == NULL || out_str == NULL || max_len == 0U) {
return false;
}
if (tlv->len >= max_len) {
return false;
}
if (tlv->len > 0U && tlv->val != NULL) {
(void)memcpy(out_str, tlv->val, tlv->len);
}
out_str[tlv->len] = '\0';
return true;
}
bool syn_lwm2m_tlv_decode_value(const SYN_LwM2M_TLV *tlv, SYN_LwM2M_ValType expected_type,
SYN_LwM2M_Value *out_val)
{
if (tlv == NULL || out_val == NULL) {
return false;
}
out_val->type = expected_type;
switch (expected_type) {
case SYN_LWM2M_TYPE_INT:
case SYN_LWM2M_TYPE_TIME:
return syn_lwm2m_tlv_decode_int(tlv, &out_val->val.integer);
case SYN_LWM2M_TYPE_FLOAT:
return syn_lwm2m_tlv_decode_float(tlv, &out_val->val.floating);
case SYN_LWM2M_TYPE_BOOL:
return syn_lwm2m_tlv_decode_bool(tlv, &out_val->val.boolean);
case SYN_LWM2M_TYPE_OPAQUE:
out_val->val.opaque.data = tlv->val;
out_val->val.opaque.len = tlv->len;
return true;
case SYN_LWM2M_TYPE_OBJLNK:
if (tlv->len == 4U && tlv->val != NULL) {
out_val->val.objlnk.obj_id = ((uint16_t)tlv->val[0] << 8U) | (uint16_t)tlv->val[1];
out_val->val.objlnk.inst_id = ((uint16_t)tlv->val[2] << 8U) | (uint16_t)tlv->val[3];
return true;
}
return false;
default:
return false;
}
}
/* ── Core Link Format & Registration ─────────────────────────────────────── */
size_t syn_lwm2m_generate_link_format(const SYN_LwM2M_Client *client, char *buf, size_t max_len)
{
if (client == NULL || buf == NULL || max_len == 0U) {
return 0U;
}
size_t written = 0U;
buf[0] = '\0';
for (size_t i = 0U; i < client->object_count; i++) {
const SYN_LwM2M_Object *obj = client->objects[i];
if (obj == NULL) {
continue;
}
for (size_t j = 0U; j < obj->instance_count; j++) {
uint16_t inst_id = (obj->instances != NULL) ? obj->instances[j] : (uint16_t)j;
char link[32];
int n = 0;
if (written == 0U) {
n = snprintf(link, sizeof(link), "</%u/%u>", obj->id, inst_id);
} else {
n = snprintf(link, sizeof(link), ",</%u/%u>", obj->id, inst_id);
}
if (n < 0 || (size_t)n >= sizeof(link)) {
return 0U;
}
if (written + (size_t)n >= max_len) {
return 0U;
}
(void)memcpy(buf + written, link, (size_t)n);
written += (size_t)n;
buf[written] = '\0';
}
}
return written;
}
bool syn_lwm2m_client_init(SYN_LwM2M_Client *client, const char *endpoint_name, uint32_t lifetime_s,
SYN_Transport *transport)
{
if (client == NULL || endpoint_name == NULL || transport == NULL || lifetime_s == 0U) {
return false;
}
(void)memset(client, 0, sizeof(*client));
client->endpoint_name = endpoint_name;
client->lifetime_s = lifetime_s;
client->binding = "U";
client->transport = transport;
client->state = SYN_LWM2M_STATE_DEREGISTERED;
return true;
}
bool syn_lwm2m_register_object(SYN_LwM2M_Client *client, const SYN_LwM2M_Object *obj)
{
if (client == NULL || obj == NULL) {
return false;
}
if (client->object_count >= SYN_LWM2M_MAX_OBJECTS) {
return false;
}
client->objects[client->object_count++] = obj;
return true;
}
size_t syn_lwm2m_build_register_request(const SYN_LwM2M_Client *client, uint16_t msg_id,
const uint8_t *token, uint8_t token_len, SYN_CoapMsg *req,
SYN_CoapOption *req_opts, size_t max_req_opts,
size_t *req_opt_cnt, uint8_t *payload_buf,
size_t payload_buf_sz)
{
if (client == NULL || req == NULL || req_opts == NULL || req_opt_cnt == NULL ||
payload_buf == NULL || max_req_opts < 6U || token_len > 8U) {
return 0U;
}
size_t link_len = syn_lwm2m_generate_link_format(client, (char *)payload_buf, payload_buf_sz);
if (link_len == 0U) {
return 0U;
}
req->type = COAP_TYPE_CON;
req->code = COAP_CODE_POST;
req->msg_id = msg_id;
req->token_len = token_len;
if (token_len > 0U && token != NULL) {
(void)memcpy(req->token, token, token_len);
}
req->payload = payload_buf;
req->payload_len = link_len;
/* Options: Uri-Path: "rd", Uri-Query: "ep=...", Uri-Query: "lt=...", Uri-Query: "lwm2m=1.1",
* Uri-Query: "b=U", Content-Format: 40 */
static char query_ep[64];
static char query_lt[32];
(void)snprintf(query_ep, sizeof(query_ep), "ep=%s", client->endpoint_name);
(void)snprintf(query_lt, sizeof(query_lt), "lt=%u", client->lifetime_s);
static const uint8_t fmt_link = SYN_LWM2M_FORMAT_LINK_FORMAT;
req_opts[0].num = COAP_OPT_URI_PATH;
req_opts[0].val = (const uint8_t *)"rd";
req_opts[0].len = 2U;
req_opts[1].num = COAP_OPT_CONTENT_FORMAT;
req_opts[1].val = &fmt_link;
req_opts[1].len = 1U;
req_opts[2].num = COAP_OPT_URI_QUERY;
req_opts[2].val = (const uint8_t *)query_ep;
req_opts[2].len = strlen(query_ep);
req_opts[3].num = COAP_OPT_URI_QUERY;
req_opts[3].val = (const uint8_t *)query_lt;
req_opts[3].len = strlen(query_lt);
req_opts[4].num = COAP_OPT_URI_QUERY;
req_opts[4].val = (const uint8_t *)"lwm2m=1.1";
req_opts[4].len = 9U;
req_opts[5].num = COAP_OPT_URI_QUERY;
req_opts[5].val = (const uint8_t *)"b=U";
req_opts[5].len = 3U;
*req_opt_cnt = 6U;
return link_len;
}
size_t syn_lwm2m_build_update_request(const SYN_LwM2M_Client *client, uint16_t msg_id,
const uint8_t *token, uint8_t token_len, uint32_t lifetime_s,
SYN_CoapMsg *req, SYN_CoapOption *req_opts,
size_t max_req_opts, size_t *req_opt_cnt,
uint8_t *payload_buf, size_t payload_buf_sz)
{
(void)payload_buf;
(void)payload_buf_sz;
if (client == NULL || req == NULL || req_opts == NULL || req_opt_cnt == NULL ||
max_req_opts < 3U || token_len > 8U || client->location_path[0] == '\0') {
return 0U;
}
req->type = COAP_TYPE_CON;
req->code = COAP_CODE_POST;
req->msg_id = msg_id;
req->token_len = token_len;
if (token_len > 0U && token != NULL) {
(void)memcpy(req->token, token, token_len);
}
req->payload = NULL;
req->payload_len = 0U;
const char *loc = client->location_path;
if (loc[0] == '/') {
loc++;
}
req_opts[0].num = COAP_OPT_URI_PATH;
req_opts[0].val = (const uint8_t *)"rd";
req_opts[0].len = 2U;
const char *slash = strchr(loc, '/');
const char *id_part = (slash != NULL) ? (slash + 1) : loc;
req_opts[1].num = COAP_OPT_URI_PATH;
req_opts[1].val = (const uint8_t *)id_part;
req_opts[1].len = strlen(id_part);
static char query_lt[32];
uint32_t lt = (lifetime_s > 0U) ? lifetime_s : client->lifetime_s;
(void)snprintf(query_lt, sizeof(query_lt), "lt=%u", lt);
req_opts[2].num = COAP_OPT_URI_QUERY;
req_opts[2].val = (const uint8_t *)query_lt;
req_opts[2].len = strlen(query_lt);
*req_opt_cnt = 3U;
return 1U;
}
size_t syn_lwm2m_build_deregister_request(const SYN_LwM2M_Client *client, uint16_t msg_id,
const uint8_t *token, uint8_t token_len, SYN_CoapMsg *req,
SYN_CoapOption *req_opts, size_t max_req_opts,
size_t *req_opt_cnt)
{
if (client == NULL || req == NULL || req_opts == NULL || req_opt_cnt == NULL ||
max_req_opts < 2U || token_len > 8U || client->location_path[0] == '\0') {
return 0U;
}
req->type = COAP_TYPE_CON;
req->code = COAP_CODE_DELETE;
req->msg_id = msg_id;
req->token_len = token_len;
if (token_len > 0U && token != NULL) {
(void)memcpy(req->token, token, token_len);
}
req->payload = NULL;
req->payload_len = 0U;
const char *loc = client->location_path;
if (loc[0] == '/') {
loc++;
}
req_opts[0].num = COAP_OPT_URI_PATH;
req_opts[0].val = (const uint8_t *)"rd";
req_opts[0].len = 2U;
const char *slash = strchr(loc, '/');
const char *id_part = (slash != NULL) ? (slash + 1) : loc;
req_opts[1].num = COAP_OPT_URI_PATH;
req_opts[1].val = (const uint8_t *)id_part;
req_opts[1].len = strlen(id_part);
*req_opt_cnt = 2U;
return 1U;
}
bool syn_lwm2m_handle_register_response(SYN_LwM2M_Client *client, const SYN_CoapMsg *resp,
const SYN_CoapOption *resp_opts, size_t resp_opt_cnt)
{
if (client == NULL || resp == NULL) {
return false;
}
if (resp->code != COAP_RESP_CREATED) {
return false;
}
/* Extract Location-Path options */
char loc_buf[SYN_LWM2M_MAX_URI_PATH_LEN] = {0};
size_t loc_len = 0U;
if (resp_opts != NULL) {
for (size_t i = 0U; i < resp_opt_cnt; i++) {
if (resp_opts[i].num == COAP_OPT_LOCATION_PATH && resp_opts[i].val != NULL) {
if (loc_len > 0U && loc_len + 1U < sizeof(loc_buf)) {
loc_buf[loc_len++] = '/';
}
size_t copy_len = resp_opts[i].len;
if (loc_len + copy_len < sizeof(loc_buf)) {
(void)memcpy(loc_buf + loc_len, resp_opts[i].val, copy_len);
loc_len += copy_len;
}
}
}
}
if (loc_len > 0U) {
loc_buf[loc_len] = '\0';
(void)strncpy(client->location_path, loc_buf, sizeof(client->location_path) - 1U);
} else {
(void)strncpy(client->location_path, "rd/0", sizeof(client->location_path) - 1U);
}
client->state = SYN_LWM2M_STATE_REGISTERED;
client->last_update_ms = syn_port_get_tick_ms();
return true;
}
/* ── Observation Table ───────────────────────────────────────────────────── */
bool syn_lwm2m_observe_add(SYN_LwM2M_Client *client, uint16_t obj_id, uint16_t inst_id,
int32_t res_id, const uint8_t *token, uint8_t token_len, uint32_t pmin,
uint32_t pmax)
{
if (client == NULL || token_len > 8U) {
return false;
}
/* Search for existing matching observation to update */
int free_slot = -1;
for (size_t i = 0U; i < SYN_LWM2M_MAX_OBSERVERS; i++) {
if (client->observations[i].active) {
if (client->observations[i].token_len == token_len &&
memcmp(client->observations[i].token, token, token_len) == 0) {
free_slot = (int)i;
break;
}
} else if (free_slot < 0) {
free_slot = (int)i;
}
}
if (free_slot < 0) {
return false;
}
SYN_LwM2M_Observation *obs = &client->observations[free_slot];
obs->active = true;
obs->obj_id = obj_id;
obs->inst_id = inst_id;
obs->res_id = res_id;
obs->token_len = token_len;
if (token_len > 0U && token != NULL) {
(void)memcpy(obs->token, token, token_len);
}
obs->pmin = pmin;
obs->pmax = (pmax > 0U) ? pmax : 60000U;
obs->last_notify_ms = syn_port_get_tick_ms();
obs->seq = 0U;
return true;
}
bool syn_lwm2m_observe_remove(SYN_LwM2M_Client *client, const uint8_t *token, uint8_t token_len)
{
if (client == NULL || token_len > 8U) {
return false;
}
for (size_t i = 0U; i < SYN_LWM2M_MAX_OBSERVERS; i++) {
if (client->observations[i].active && client->observations[i].token_len == token_len) {
if (token_len == 0U || memcmp(client->observations[i].token, token, token_len) == 0) {
client->observations[i].active = false;
return true;
}
}
}
return false;
}
size_t syn_lwm2m_build_notify(SYN_LwM2M_Client *client, uint16_t obj_id, uint16_t inst_id,
int32_t res_id, uint16_t msg_id, SYN_CoapMsg *resp,
SYN_CoapOption *resp_opts, size_t max_resp_opts, size_t *resp_opt_cnt,
uint8_t *resp_buf, size_t resp_buf_sz)
{
if (client == NULL || resp == NULL || resp_opts == NULL || resp_opt_cnt == NULL ||
resp_buf == NULL || max_resp_opts < 2U || resp_buf_sz == 0U) {
return 0U;
}
/* Find matching observation */
SYN_LwM2M_Observation *obs = NULL;
for (size_t i = 0U; i < SYN_LWM2M_MAX_OBSERVERS; i++) {
if (client->observations[i].active && client->observations[i].obj_id == obj_id &&
client->observations[i].inst_id == inst_id &&
client->observations[i].res_id == res_id) {
obs = &client->observations[i];
break;
}
}
if (obs == NULL) {
return 0U;
}
/* Find target object and resource */
const SYN_LwM2M_Object *obj = NULL;
for (size_t i = 0U; i < client->object_count; i++) {
if (client->objects[i] != NULL && client->objects[i]->id == obj_id) {
obj = client->objects[i];
break;
}
}
if (obj == NULL || obj->read == NULL || res_id < 0) {
return 0U;
}
SYN_LwM2M_Value val = {0};
if (obj->read(inst_id, (uint16_t)res_id, &val, obj->user_ctx) != SYN_OK) {
return 0U;
}
size_t payload_len = syn_lwm2m_tlv_encode_value((uint16_t)res_id, &val, resp_buf, resp_buf_sz);
if (payload_len == 0U) {
return 0U;
}
resp->type = COAP_TYPE_NON;
resp->code = COAP_RESP_CONTENT;
resp->msg_id = msg_id;
resp->token_len = obs->token_len;
(void)memcpy(resp->token, obs->token, obs->token_len);
resp->payload = resp_buf;
resp->payload_len = payload_len;
static uint8_t obs_seq_buf[3];
obs->seq++;
obs_seq_buf[0] = (uint8_t)((obs->seq >> 16) & 0xFF);
obs_seq_buf[1] = (uint8_t)((obs->seq >> 8) & 0xFF);
obs_seq_buf[2] = (uint8_t)(obs->seq & 0xFF);
static const uint8_t fmt_tlv[2] = {(uint8_t)(SYN_LWM2M_FORMAT_TLV >> 8),
(uint8_t)(SYN_LWM2M_FORMAT_TLV & 0xFF)};
resp_opts[0].num = COAP_OPT_OBSERVE;
resp_opts[0].val = obs_seq_buf;
resp_opts[0].len = 3U;
resp_opts[1].num = COAP_OPT_CONTENT_FORMAT;
resp_opts[1].val = fmt_tlv;
resp_opts[1].len = 2U;
*resp_opt_cnt = 2U;
obs->last_notify_ms = syn_port_get_tick_ms();
return payload_len;
}
/* ── Request Dispatcher ──────────────────────────────────────────────────── */
static const SYN_LwM2M_Object *find_object(const SYN_LwM2M_Client *client, uint16_t obj_id)
{
for (size_t i = 0U; i < client->object_count; i++) {
if (client->objects[i] != NULL && client->objects[i]->id == obj_id) {
return client->objects[i];
}
}
return NULL;
}
static bool validate_instance(const SYN_LwM2M_Object *obj, uint16_t inst_id)
{
if (obj->instances == NULL) {
return (inst_id < obj->instance_count);
}
for (size_t i = 0U; i < obj->instance_count; i++) {
if (obj->instances[i] == inst_id) {
return true;
}
}
return false;
}
static const SYN_LwM2M_ResourceDesc *find_resource(const SYN_LwM2M_Object *obj, uint16_t res_id)
{
if (obj->resources == NULL) {
return NULL;
}
for (size_t i = 0U; i < obj->resource_count; i++) {
if (obj->resources[i].id == res_id) {
return &obj->resources[i];
}
}
return NULL;
}
SYN_Status syn_lwm2m_process_request(SYN_LwM2M_Client *client, const SYN_CoapMsg *req,
const SYN_CoapOption *req_opts, size_t req_opt_cnt,
SYN_CoapMsg *resp, SYN_CoapOption *resp_opts,
size_t max_resp_opts, size_t *resp_opt_cnt, uint8_t *resp_buf,
size_t resp_buf_sz)
{
if (client == NULL || req == NULL || resp == NULL || resp_opts == NULL ||
resp_opt_cnt == NULL || resp_buf == NULL || max_resp_opts < 2U || resp_buf_sz == 0U) {
return SYN_ERROR;
}
/* Initialize response */
resp->type = (req->type == COAP_TYPE_CON) ? COAP_TYPE_ACK : COAP_TYPE_NON;
resp->msg_id = req->msg_id;
resp->token_len = req->token_len;
if (req->token_len > 0U) {
(void)memcpy(resp->token, req->token, req->token_len);
}
resp->payload = NULL;
resp->payload_len = 0U;
*resp_opt_cnt = 0U;
/* Parse URI Path segments (1, 2, or 3 segments) */
size_t path_segs = 0U;
uint16_t obj_id = 0U;
uint16_t inst_id = 0U;
uint16_t res_id = 0U;
bool has_observe = false;
if (req_opts != NULL) {
for (size_t i = 0U; i < req_opt_cnt; i++) {
if (req_opts[i].num == COAP_OPT_URI_PATH && req_opts[i].val != NULL) {
char seg[16] = {0};
size_t c_len =
(req_opts[i].len < sizeof(seg) - 1U) ? req_opts[i].len : sizeof(seg) - 1U;
(void)memcpy(seg, req_opts[i].val, c_len);
seg[c_len] = '\0';
unsigned int num = (unsigned int)strtoul(seg, NULL, 10);
if (path_segs == 0U) {
obj_id = (uint16_t)num;
} else if (path_segs == 1U) {
inst_id = (uint16_t)num;
} else if (path_segs == 2U) {
res_id = (uint16_t)num;
}
path_segs++;
} else if (req_opts[i].num == COAP_OPT_OBSERVE) {
has_observe = true;
}
}
}
if (path_segs == 0U) {
resp->code = COAP_RESP_BAD_REQ;
return SYN_OK;
}
const SYN_LwM2M_Object *obj = find_object(client, obj_id);
if (obj == NULL) {
resp->code = COAP_RESP_NOT_FOUND;
return SYN_OK;
}
if (path_segs >= 2U && !validate_instance(obj, inst_id)) {
resp->code = COAP_RESP_NOT_FOUND;
return SYN_OK;
}
static const uint8_t fmt_tlv[2] = {(uint8_t)(SYN_LWM2M_FORMAT_TLV >> 8),
(uint8_t)(SYN_LWM2M_FORMAT_TLV & 0xFF)};
/* ── GET (Read / Observe / Discover) ─────────────────────────────────── */
if (req->code == COAP_CODE_GET) {
/* Single resource read */
if (path_segs == 3U) {
const SYN_LwM2M_ResourceDesc *desc = find_resource(obj, res_id);
if (desc == NULL) {
resp->code = COAP_RESP_NOT_FOUND;
return SYN_OK;
}
if ((desc->operations & SYN_LWM2M_OP_R) == 0U || obj->read == NULL) {
resp->code = COAP_RESP_METHOD_NA;
return SYN_OK;
}
SYN_LwM2M_Value val = {0};
if (obj->read(inst_id, res_id, &val, obj->user_ctx) != SYN_OK) {
resp->code = COAP_RESP_INTERNAL;
return SYN_OK;
}
size_t enc_len = syn_lwm2m_tlv_encode_value(res_id, &val, resp_buf, resp_buf_sz);
if (enc_len == 0U) {
resp->code = COAP_RESP_INTERNAL;
return SYN_OK;
}
resp->code = COAP_RESP_CONTENT;
resp->payload = resp_buf;
resp->payload_len = enc_len;
size_t opt_i = 0U;
if (has_observe) {
syn_lwm2m_observe_add(client, obj_id, inst_id, (int32_t)res_id, req->token,
req->token_len, 0U, 60000U);
static const uint8_t obs_val = 0U;
resp_opts[opt_i].num = COAP_OPT_OBSERVE;
resp_opts[opt_i].val = &obs_val;
resp_opts[opt_i].len = 1U;
opt_i++;
}
resp_opts[opt_i].num = COAP_OPT_CONTENT_FORMAT;
resp_opts[opt_i].val = fmt_tlv;
resp_opts[opt_i].len = 2U;
opt_i++;
*resp_opt_cnt = opt_i;
return SYN_OK;
}
/* Object instance read (whole instance) */
if (path_segs == 2U) {
if (obj->read == NULL || obj->resources == NULL) {
resp->code = COAP_RESP_NOT_FOUND;
return SYN_OK;
}
size_t total_written = 0U;
for (size_t i = 0U; i < obj->resource_count; i++) {
const SYN_LwM2M_ResourceDesc *r_desc = &obj->resources[i];
if ((r_desc->operations & SYN_LWM2M_OP_R) != 0U) {
SYN_LwM2M_Value r_val = {0};
if (obj->read(inst_id, r_desc->id, &r_val, obj->user_ctx) == SYN_OK) {
size_t w =
syn_lwm2m_tlv_encode_value(r_desc->id, &r_val, resp_buf + total_written,
resp_buf_sz - total_written);
total_written += w;
}
}
}
resp->code = COAP_RESP_CONTENT;
resp->payload = resp_buf;
resp->payload_len = total_written;
size_t opt_i = 0U;
if (has_observe) {
syn_lwm2m_observe_add(client, obj_id, inst_id, -1, req->token, req->token_len, 0U,
60000U);
static const uint8_t obs_val = 0U;
resp_opts[opt_i].num = COAP_OPT_OBSERVE;
resp_opts[opt_i].val = &obs_val;
resp_opts[opt_i].len = 1U;
opt_i++;
}
resp_opts[opt_i].num = COAP_OPT_CONTENT_FORMAT;
resp_opts[opt_i].val = fmt_tlv;
resp_opts[opt_i].len = 2U;
opt_i++;
*resp_opt_cnt = opt_i;
return SYN_OK;
}
resp->code = COAP_RESP_BAD_REQ;
return SYN_OK;
}
/* ── PUT / POST (Write) ──────────────────────────────────────────────── */
if (req->code == COAP_CODE_PUT || (req->code == COAP_CODE_POST && path_segs == 3U)) {
const SYN_LwM2M_ResourceDesc *desc = find_resource(obj, res_id);
if (desc == NULL) {
resp->code = COAP_RESP_NOT_FOUND;
return SYN_OK;
}
/* Check if this is an Execute call on an executable-only resource */
if ((desc->operations & SYN_LWM2M_OP_E) != 0U &&
(desc->operations & SYN_LWM2M_OP_W) == 0U) {
if (obj->exec == NULL) {
resp->code = COAP_RESP_METHOD_NA;
return SYN_OK;
}
if (obj->exec(inst_id, res_id, req->payload, req->payload_len, obj->user_ctx) ==
SYN_OK) {
resp->code = COAP_RESP_CHANGED;
} else {
resp->code = COAP_RESP_INTERNAL;
}
return SYN_OK;
}
if ((desc->operations & SYN_LWM2M_OP_W) == 0U || obj->write == NULL) {
resp->code = COAP_RESP_METHOD_NA;
return SYN_OK;
}
/* Decode value from payload (supports TLV or plain text) */
SYN_LwM2M_Value in_val = {0};
bool decoded = false;
if (req->payload_len > 0U && req->payload != NULL) {
SYN_LwM2M_TLV tlv;
size_t consumed = 0U;
if (syn_lwm2m_tlv_decode(req->payload, req->payload_len, &tlv, &consumed)) {
decoded = syn_lwm2m_tlv_decode_value(&tlv, desc->type, &in_val);
}
if (!decoded) {
/* Text fallback */
if (desc->type == SYN_LWM2M_TYPE_STRING) {
char str_buf[64] = {0};
size_t c_len = (req->payload_len < sizeof(str_buf) - 1U) ? req->payload_len
: sizeof(str_buf) - 1U;
(void)memcpy(str_buf, req->payload, c_len);
str_buf[c_len] = '\0';
in_val.type = SYN_LWM2M_TYPE_STRING;
in_val.val.str = str_buf;
decoded = true;
} else if (desc->type == SYN_LWM2M_TYPE_INT || desc->type == SYN_LWM2M_TYPE_TIME) {
in_val.type = desc->type;
in_val.val.integer = (int64_t)strtoll((const char *)req->payload, NULL, 10);
decoded = true;
} else if (desc->type == SYN_LWM2M_TYPE_OPAQUE) {
in_val.type = SYN_LWM2M_TYPE_OPAQUE;
in_val.val.opaque.data = req->payload;
in_val.val.opaque.len = req->payload_len;
decoded = true;
}
}
}
if (!decoded) {
resp->code = COAP_RESP_BAD_REQ;
return SYN_OK;
}
if (obj->write(inst_id, res_id, &in_val, obj->user_ctx) == SYN_OK) {
resp->code = COAP_RESP_CHANGED;
} else {
resp->code = COAP_RESP_INTERNAL;
}
return SYN_OK;
}
/* ── DELETE ─────────────────────────────────────────────────────────── */
if (req->code == COAP_CODE_DELETE) {
resp->code = COAP_RESP_DELETED;
return SYN_OK;
}
resp->code = COAP_RESP_METHOD_NA;
return SYN_OK;
}
/* ── Standard Standard Object Handlers ───────────────────────────────────── */
static const uint16_t s_single_inst[1] = {0U};
/* ── Object 3 (Device) ── */
static const SYN_LwM2M_ResourceDesc s_device_resources[] = {
{0U, SYN_LWM2M_OP_R, SYN_LWM2M_TYPE_STRING}, /* Manufacturer */
{1U, SYN_LWM2M_OP_R, SYN_LWM2M_TYPE_STRING}, /* Model Number */
{2U, SYN_LWM2M_OP_R, SYN_LWM2M_TYPE_STRING}, /* Serial Number */
{3U, SYN_LWM2M_OP_R, SYN_LWM2M_TYPE_STRING}, /* Firmware Version */
{4U, SYN_LWM2M_OP_E, SYN_LWM2M_TYPE_NONE}, /* Reboot */
{5U, SYN_LWM2M_OP_E, SYN_LWM2M_TYPE_NONE}, /* Factory Reset */
{9U, SYN_LWM2M_OP_R, SYN_LWM2M_TYPE_INT}, /* Battery Level */
{10U, SYN_LWM2M_OP_R, SYN_LWM2M_TYPE_INT}, /* Memory Free */
{11U, SYN_LWM2M_OP_R, SYN_LWM2M_TYPE_INT}, /* Error Code */
{13U, SYN_LWM2M_OP_RW, SYN_LWM2M_TYPE_TIME}, /* Current Time */
{14U, SYN_LWM2M_OP_RW, SYN_LWM2M_TYPE_STRING}, /* UTC Offset */
{15U, SYN_LWM2M_OP_RW, SYN_LWM2M_TYPE_STRING}, /* Timezone */
};
static SYN_Status device_read_cb(uint16_t instance_id, uint16_t resource_id,
SYN_LwM2M_Value *out_val, void *user_ctx)
{
(void)instance_id;
SYN_LwM2M_DeviceContext *ctx = (SYN_LwM2M_DeviceContext *)user_ctx;
if (ctx == NULL || out_val == NULL) {
return SYN_ERROR;
}
switch (resource_id) {
case 0U:
out_val->type = SYN_LWM2M_TYPE_STRING;
out_val->val.str = (ctx->manufacturer != NULL) ? ctx->manufacturer : "Syntropic";
return SYN_OK;
case 1U:
out_val->type = SYN_LWM2M_TYPE_STRING;
out_val->val.str = (ctx->model_number != NULL) ? ctx->model_number : "SyntropicOS-Node";
return SYN_OK;
case 2U:
out_val->type = SYN_LWM2M_TYPE_STRING;
out_val->val.str = (ctx->serial_number != NULL) ? ctx->serial_number : "SYN-001";
return SYN_OK;
case 3U:
out_val->type = SYN_LWM2M_TYPE_STRING;
out_val->val.str = (ctx->firmware_ver != NULL) ? ctx->firmware_ver : "1.0.0";
return SYN_OK;
case 9U:
out_val->type = SYN_LWM2M_TYPE_INT;
out_val->val.integer = ctx->battery_level;
return SYN_OK;
case 10U:
out_val->type = SYN_LWM2M_TYPE_INT;
out_val->val.integer = ctx->memory_free_kb;
return SYN_OK;
case 11U:
out_val->type = SYN_LWM2M_TYPE_INT;
out_val->val.integer = ctx->error_code;
return SYN_OK;
case 13U:
out_val->type = SYN_LWM2M_TYPE_TIME;
out_val->val.integer = ctx->current_time;
return SYN_OK;
case 14U:
out_val->type = SYN_LWM2M_TYPE_STRING;
out_val->val.str = (ctx->utc_offset != NULL) ? ctx->utc_offset : "+00:00";
return SYN_OK;
case 15U:
out_val->type = SYN_LWM2M_TYPE_STRING;
out_val->val.str = (ctx->timezone != NULL) ? ctx->timezone : "UTC";
return SYN_OK;
default:
return SYN_ERROR;
}
}
static SYN_Status device_write_cb(uint16_t instance_id, uint16_t resource_id,
const SYN_LwM2M_Value *in_val, void *user_ctx)
{
(void)instance_id;
SYN_LwM2M_DeviceContext *ctx = (SYN_LwM2M_DeviceContext *)user_ctx;
if (ctx == NULL || in_val == NULL) {
return SYN_ERROR;
}
if (resource_id == 13U &&
(in_val->type == SYN_LWM2M_TYPE_INT || in_val->type == SYN_LWM2M_TYPE_TIME)) {
ctx->current_time = in_val->val.integer;
return SYN_OK;
}
return SYN_ERROR;
}
static SYN_Status device_exec_cb(uint16_t instance_id, uint16_t resource_id, const uint8_t *args,
size_t args_len, void *user_ctx)
{
(void)instance_id;
(void)args;
(void)args_len;
SYN_LwM2M_DeviceContext *ctx = (SYN_LwM2M_DeviceContext *)user_ctx;
if (ctx == NULL) {
return SYN_ERROR;
}
if (resource_id == 4U) {
ctx->reboot_requested = true;
return SYN_OK;
}
if (resource_id == 5U) {
ctx->factory_reset_requested = true;
return SYN_OK;
}
return SYN_ERROR;
}
SYN_LwM2M_Object syn_lwm2m_make_device_object(SYN_LwM2M_DeviceContext *ctx)
{
SYN_LwM2M_Object obj = {
.id = SYN_LWM2M_OBJ_DEVICE,
.instance_count = 1U,
.instances = s_single_inst,
.resource_count = sizeof(s_device_resources) / sizeof(s_device_resources[0]),
.resources = s_device_resources,
.read = device_read_cb,
.write = device_write_cb,
.exec = device_exec_cb,
.user_ctx = ctx,
};
return obj;
}
/* ── Object 5 (Firmware Update) ── */
static const SYN_LwM2M_ResourceDesc s_fw_resources[] = {
{1U, SYN_LWM2M_OP_RW, SYN_LWM2M_TYPE_STRING}, /* Package URI */
{2U, SYN_LWM2M_OP_E, SYN_LWM2M_TYPE_NONE}, /* Update */
{3U, SYN_LWM2M_OP_R, SYN_LWM2M_TYPE_INT}, /* State */
{5U, SYN_LWM2M_OP_R, SYN_LWM2M_TYPE_INT}, /* Update Result */
{6U, SYN_LWM2M_OP_R, SYN_LWM2M_TYPE_STRING}, /* Package Name */
{7U, SYN_LWM2M_OP_R, SYN_LWM2M_TYPE_STRING}, /* Package Version */
};
static SYN_Status fw_read_cb(uint16_t instance_id, uint16_t resource_id, SYN_LwM2M_Value *out_val,
void *user_ctx)
{
(void)instance_id;
SYN_LwM2M_FirmwareContext *ctx = (SYN_LwM2M_FirmwareContext *)user_ctx;
if (ctx == NULL || out_val == NULL) {
return SYN_ERROR;
}
switch (resource_id) {
case 1U:
out_val->type = SYN_LWM2M_TYPE_STRING;
out_val->val.str = ctx->package_uri;
return SYN_OK;
case 3U:
out_val->type = SYN_LWM2M_TYPE_INT;
out_val->val.integer = (int64_t)ctx->state;
return SYN_OK;
case 5U:
out_val->type = SYN_LWM2M_TYPE_INT;
out_val->val.integer = (int64_t)ctx->result;
return SYN_OK;
case 6U:
out_val->type = SYN_LWM2M_TYPE_STRING;
out_val->val.str = ctx->pkg_name;
return SYN_OK;
case 7U:
out_val->type = SYN_LWM2M_TYPE_STRING;
out_val->val.str = ctx->pkg_version;
return SYN_OK;
default:
return SYN_ERROR;
}
}
static SYN_Status fw_write_cb(uint16_t instance_id, uint16_t resource_id,
const SYN_LwM2M_Value *in_val, void *user_ctx)
{
(void)instance_id;
SYN_LwM2M_FirmwareContext *ctx = (SYN_LwM2M_FirmwareContext *)user_ctx;
if (ctx == NULL || in_val == NULL) {
return SYN_ERROR;
}
if (resource_id == 1U && in_val->type == SYN_LWM2M_TYPE_STRING && in_val->val.str != NULL) {
(void)strncpy(ctx->package_uri, in_val->val.str, sizeof(ctx->package_uri) - 1U);
ctx->state = SYN_LWM2M_FW_STATE_DOWNLOADED;
return SYN_OK;
}
return SYN_ERROR;
}
static SYN_Status fw_exec_cb(uint16_t instance_id, uint16_t resource_id, const uint8_t *args,
size_t args_len, void *user_ctx)
{
(void)instance_id;
(void)args;
(void)args_len;
SYN_LwM2M_FirmwareContext *ctx = (SYN_LwM2M_FirmwareContext *)user_ctx;
if (ctx == NULL) {
return SYN_ERROR;
}
if (resource_id == 2U) {
ctx->update_requested = true;
ctx->state = SYN_LWM2M_FW_STATE_UPDATING;
ctx->result = SYN_LWM2M_FW_RESULT_SUCCESS;
return SYN_OK;
}
return SYN_ERROR;
}
SYN_LwM2M_Object syn_lwm2m_make_firmware_object(SYN_LwM2M_FirmwareContext *ctx)
{
SYN_LwM2M_Object obj = {
.id = SYN_LWM2M_OBJ_FIRMWARE_UPDATE,
.instance_count = 1U,
.instances = s_single_inst,
.resource_count = sizeof(s_fw_resources) / sizeof(s_fw_resources[0]),
.resources = s_fw_resources,
.read = fw_read_cb,
.write = fw_write_cb,
.exec = fw_exec_cb,
.user_ctx = ctx,
};
return obj;
}
/* ── Object 3303 (IPSO Temperature Sensor) ── */
static const SYN_LwM2M_ResourceDesc s_temp_resources[] = {
{5700U, SYN_LWM2M_OP_R, SYN_LWM2M_TYPE_FLOAT}, /* Sensor Value */
{5701U, SYN_LWM2M_OP_R, SYN_LWM2M_TYPE_STRING}, /* Sensor Unit */
{5601U, SYN_LWM2M_OP_R, SYN_LWM2M_TYPE_FLOAT}, /* Min Measured Value */
{5602U, SYN_LWM2M_OP_R, SYN_LWM2M_TYPE_FLOAT}, /* Max Measured Value */
};
static SYN_Status temp_read_cb(uint16_t instance_id, uint16_t resource_id, SYN_LwM2M_Value *out_val,
void *user_ctx)
{
(void)instance_id;
SYN_LwM2M_SensorContext *ctx = (SYN_LwM2M_SensorContext *)user_ctx;
if (ctx == NULL || out_val == NULL) {
return SYN_ERROR;
}
switch (resource_id) {
case 5700U:
out_val->type = SYN_LWM2M_TYPE_FLOAT;
out_val->val.floating = ctx->sensor_value;
return SYN_OK;
case 5701U:
out_val->type = SYN_LWM2M_TYPE_STRING;
out_val->val.str = (ctx->unit != NULL) ? ctx->unit : "Cel";
return SYN_OK;
case 5601U:
out_val->type = SYN_LWM2M_TYPE_FLOAT;
out_val->val.floating = ctx->min_measured_val;
return SYN_OK;
case 5602U:
out_val->type = SYN_LWM2M_TYPE_FLOAT;
out_val->val.floating = ctx->max_measured_val;
return SYN_OK;
default:
return SYN_ERROR;
}
}
SYN_LwM2M_Object syn_lwm2m_make_temperature_object(SYN_LwM2M_SensorContext *ctx)
{
SYN_LwM2M_Object obj = {
.id = SYN_LWM2M_OBJ_TEMPERATURE,
.instance_count = 1U,
.instances = s_single_inst,
.resource_count = sizeof(s_temp_resources) / sizeof(s_temp_resources[0]),
.resources = s_temp_resources,
.read = temp_read_cb,
.write = NULL,
.exec = NULL,
.user_ctx = ctx,
};
return obj;
}
#endif /* SYN_USE_LWM2M */