From 2e2676fbc129618b6feb1b023f70b658a52a80ee Mon Sep 17 00:00:00 2001 From: EdouardMalot Date: Fri, 11 Sep 2026 16:50:28 +0200 Subject: [PATCH 1/2] lib: zcl: treat the float types as analog data The ZCL spec lists the semi, single and double precision floats among the analog data types, so a Configure Reporting record for such an attribute carries a reportable change field of the type's size. zb_zcl_is_analog_data_type() stopped at the integers, and zb_zcl_get_analog_attribute_size() and zb_zcl_fix_endian() had no case for them either: a client configuring reporting on a single float attribute sent the record without the field and the device answered MALFORMED_COMMAND, and the Read Reporting Configuration Response it parsed was cut after the intervals. zb_zcl_put_value_to_packet() knew single alone. Seen from an nRF52840 coordinator with a CO2 sensor, cluster 0x040D attribute 0x0000, type 0x39. Signed-off-by: EdouardMalot --- lib/zboss/src/zcl/zcl_common.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/zboss/src/zcl/zcl_common.c b/lib/zboss/src/zcl/zcl_common.c index c42e7ec4..1af6f6c2 100644 --- a/lib/zboss/src/zcl/zcl_common.c +++ b/lib/zboss/src/zcl/zcl_common.c @@ -421,6 +421,7 @@ zb_uint8_t* zb_zcl_put_value_to_packet(zb_uint8_t *cmd_ptr, zb_uint8_t attr_type case ZB_ZCL_ATTR_TYPE_S16: case ZB_ZCL_ATTR_TYPE_16BITMAP: case ZB_ZCL_ATTR_TYPE_16BIT_ENUM: + case ZB_ZCL_ATTR_TYPE_SEMI: ZB_ZCL_PACKET_PUT_DATA16(cmd_ptr, attr_value); break; @@ -509,6 +510,7 @@ zb_uint8_t* zb_zcl_put_value_to_packet(zb_uint8_t *cmd_ptr, zb_uint8_t attr_type case ZB_ZCL_ATTR_TYPE_64BITMAP: case ZB_ZCL_ATTR_TYPE_U64: case ZB_ZCL_ATTR_TYPE_S64: + case ZB_ZCL_ATTR_TYPE_DOUBLE: case ZB_ZCL_ATTR_TYPE_IEEE_ADDR: ZB_ZCL_PACKET_PUT_DATA64(cmd_ptr, attr_value); break; @@ -543,6 +545,7 @@ zb_uint8_t zb_zcl_get_analog_attribute_size(zb_uint8_t attr_type) case ZB_ZCL_ATTR_TYPE_U16: case ZB_ZCL_ATTR_TYPE_S16: + case ZB_ZCL_ATTR_TYPE_SEMI: ret = sizeof(zb_uint16_t); break; @@ -553,6 +556,7 @@ zb_uint8_t zb_zcl_get_analog_attribute_size(zb_uint8_t attr_type) case ZB_ZCL_ATTR_TYPE_U32: case ZB_ZCL_ATTR_TYPE_S32: + case ZB_ZCL_ATTR_TYPE_SINGLE: ret = sizeof(zb_uint32_t); break; @@ -563,6 +567,7 @@ zb_uint8_t zb_zcl_get_analog_attribute_size(zb_uint8_t attr_type) case ZB_ZCL_ATTR_TYPE_U64: case ZB_ZCL_ATTR_TYPE_S64: + case ZB_ZCL_ATTR_TYPE_DOUBLE: ret = sizeof(zb_uint64_t); break; @@ -595,6 +600,9 @@ zb_bool_t zb_zcl_is_analog_data_type(zb_uint8_t attr_type) case ZB_ZCL_ATTR_TYPE_S32: case ZB_ZCL_ATTR_TYPE_S48: case ZB_ZCL_ATTR_TYPE_S64: + case ZB_ZCL_ATTR_TYPE_SEMI: + case ZB_ZCL_ATTR_TYPE_SINGLE: + case ZB_ZCL_ATTR_TYPE_DOUBLE: case ZB_ZCL_ATTR_TYPE_UTC_TIME: ret = ZB_TRUE; break; @@ -824,6 +832,7 @@ void zb_zcl_fix_endian(zb_uint8_t *data_ptr, zb_uint8_t data_type) case ZB_ZCL_ATTR_TYPE_U16: case ZB_ZCL_ATTR_TYPE_S16: case ZB_ZCL_ATTR_TYPE_16BITMAP: + case ZB_ZCL_ATTR_TYPE_SEMI: ZB_HTOLE16(&int_vars.u16, data_ptr); ZB_MEMCPY(data_ptr, &int_vars.u16, sizeof(zb_uint16_t)); break; @@ -832,11 +841,13 @@ void zb_zcl_fix_endian(zb_uint8_t *data_ptr, zb_uint8_t data_type) case ZB_ZCL_ATTR_TYPE_U32: case ZB_ZCL_ATTR_TYPE_S32: case ZB_ZCL_ATTR_TYPE_32BITMAP: + case ZB_ZCL_ATTR_TYPE_SINGLE: ZB_HTOLE32(&int_vars.u32, data_ptr); ZB_MEMCPY(data_ptr, &int_vars.u32, sizeof(zb_uint32_t)); break; case ZB_ZCL_ATTR_TYPE_64BIT: + case ZB_ZCL_ATTR_TYPE_DOUBLE: case ZB_ZCL_ATTR_TYPE_IEEE_ADDR: ZB_HTOLE64(&int_vars.u64, data_ptr); ZB_MEMCPY(data_ptr, &int_vars.u64, sizeof(zb_ieee_addr_t)); From 737b41fbedd49f342873889027e0963e9b8cc18a Mon Sep 17 00:00:00 2001 From: EdouardMalot Date: Fri, 11 Sep 2026 16:50:28 +0200 Subject: [PATCH 2/2] lib: zcl: report float attributes by reportable change With the float types now analog, the server side of reporting fell into the default branch of its switches: no reported value saved, no delta compared, and a Read Reporting Configuration Response without its reportable change. Compare single and double the way the integers are. The double lands in the union as raw bytes so its alignment does not change the layout of zb_zcl_reporting_info_t. The delta of a semi float is emitted in the response but not compared: the targets have no half precision type, so such an attribute keeps reporting every change, as the 64-bit integers do today. Signed-off-by: EdouardMalot --- lib/zboss/include/zcl/zb_zcl_reporting.h | 2 + lib/zboss/src/zcl/zcl_general_commands.c | 9 ++++ lib/zboss/src/zcl/zcl_reporting.c | 60 ++++++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/lib/zboss/include/zcl/zb_zcl_reporting.h b/lib/zboss/include/zcl/zb_zcl_reporting.h index 62015dfd..50e349bd 100644 --- a/lib/zboss/include/zcl/zb_zcl_reporting.h +++ b/lib/zboss/include/zcl/zb_zcl_reporting.h @@ -89,6 +89,8 @@ union zb_zcl_attr_var_u zb_uint32_t u32; zb_int32_t s32; zb_uint48_t u48; + zb_single_t single; + zb_64bit_data_t data64; /* DOUBLE, raw bytes: keeps the union 4-byte aligned */ zb_uint8_t data_buf[4]; zb_uint32_t data_buf_crc32; }; diff --git a/lib/zboss/src/zcl/zcl_general_commands.c b/lib/zboss/src/zcl/zcl_general_commands.c index 682e9729..37ea332b 100644 --- a/lib/zboss/src/zcl/zcl_general_commands.c +++ b/lib/zboss/src/zcl/zcl_general_commands.c @@ -1242,6 +1242,15 @@ void zb_zcl_read_report_config_cmd_handler(zb_uint8_t param) case ZB_ZCL_ATTR_TYPE_S32: ZB_ZCL_PACKET_PUT_DATA32(resp_data, &attr_rep_info->u.send_info.delta.s32); /*Add signed 32-bit delta field*/ break; + case ZB_ZCL_ATTR_TYPE_SEMI: + ZB_ZCL_PACKET_PUT_DATA16(resp_data, &attr_rep_info->u.send_info.delta.u16); /*Add half float delta field, raw bits*/ + break; + case ZB_ZCL_ATTR_TYPE_SINGLE: + ZB_ZCL_PACKET_PUT_DATA32(resp_data, &attr_rep_info->u.send_info.delta.single); /*Add single float delta field*/ + break; + case ZB_ZCL_ATTR_TYPE_DOUBLE: + ZB_ZCL_PACKET_PUT_DATA64(resp_data, attr_rep_info->u.send_info.delta.data64); /*Add double float delta field*/ + break; case ZB_ZCL_ATTR_TYPE_U48: ZB_ZCL_PACKET_PUT_DATA48(resp_data, &attr_rep_info->u.send_info.delta.u48); /*Add 48-bit delta field*/ break; diff --git a/lib/zboss/src/zcl/zcl_reporting.c b/lib/zboss/src/zcl/zcl_reporting.c index fa55e364..d9c7cb58 100644 --- a/lib/zboss/src/zcl/zcl_reporting.c +++ b/lib/zboss/src/zcl/zcl_reporting.c @@ -1273,6 +1273,29 @@ void zb_zcl_save_reported_value(zb_zcl_reporting_info_t *rep_info, zb_zcl_attr_t break; } + case ZB_ZCL_ATTR_TYPE_SINGLE: + { + /* memcpy: attribute storage is not guaranteed to be float-aligned */ + ZB_MEMCPY(&rep_info->u.send_info.reported_value.single, attr_desc->data_p, sizeof(zb_single_t)); + TRACE_MSG(TRACE_ZCL3, "reported SINGLE [%hd %hd %hd %hd]", + (FMT__H_H_H_H, + rep_info->u.send_info.reported_value.data_buf[0], rep_info->u.send_info.reported_value.data_buf[1], + rep_info->u.send_info.reported_value.data_buf[2], rep_info->u.send_info.reported_value.data_buf[3])); + break; + } + + case ZB_ZCL_ATTR_TYPE_DOUBLE: + { + ZB_MEMCPY(rep_info->u.send_info.reported_value.data64, attr_desc->data_p, ZB_64BIT_SIZE); + TRACE_MSG(TRACE_ZCL3, "reported DOUBLE [%hd %hd %hd %hd %hd %hd %hd %hd]", + (FMT__H_H_H_H_H_H_H_H, + rep_info->u.send_info.reported_value.data64[0], rep_info->u.send_info.reported_value.data64[1], + rep_info->u.send_info.reported_value.data64[2], rep_info->u.send_info.reported_value.data64[3], + rep_info->u.send_info.reported_value.data64[4], rep_info->u.send_info.reported_value.data64[5], + rep_info->u.send_info.reported_value.data64[6], rep_info->u.send_info.reported_value.data64[7])); + break; + } + case ZB_ZCL_ATTR_TYPE_U48: { rep_info->u.send_info.reported_value.u48 = *(zb_uint48_t*)attr_desc->data_p; @@ -1416,6 +1439,43 @@ static zb_bool_t check_delta_value(zb_zcl_reporting_info_t *rep_info) break; } + case ZB_ZCL_ATTR_TYPE_SINGLE: + { + zb_single_t cur_value; + zb_single_t delta; + + ZB_MEMCPY(&cur_value, attr_desc->data_p, sizeof(zb_single_t)); + delta = ZB_ABS(cur_value - rep_info->u.send_info.reported_value.single); + + TRACE_MSG(TRACE_ZCL3, "reported SINGLE [%hd %hd %hd %hd]", + (FMT__H_H_H_H, + rep_info->u.send_info.reported_value.data_buf[0], rep_info->u.send_info.reported_value.data_buf[1], + rep_info->u.send_info.reported_value.data_buf[2], rep_info->u.send_info.reported_value.data_buf[3])); + ret = (delta >= rep_info->u.send_info.delta.single)?(RET_OK ):(RET_IGNORE ); + break; + } + + case ZB_ZCL_ATTR_TYPE_DOUBLE: + { + double cur_value; + double reported_value; + double min_delta; + double delta; + + ZB_MEMCPY(&cur_value, attr_desc->data_p, sizeof(double)); + ZB_MEMCPY(&reported_value, rep_info->u.send_info.reported_value.data64, sizeof(double)); + ZB_MEMCPY(&min_delta, rep_info->u.send_info.delta.data64, sizeof(double)); + delta = ZB_ABS(cur_value - reported_value); + + TRACE_MSG(TRACE_ZCL3, "reported DOUBLE [%hd %hd %hd %hd %hd %hd %hd %hd]", + (FMT__H_H_H_H_H_H_H_H, + rep_info->u.send_info.reported_value.data64[0], rep_info->u.send_info.reported_value.data64[1], + rep_info->u.send_info.reported_value.data64[2], rep_info->u.send_info.reported_value.data64[3], + rep_info->u.send_info.reported_value.data64[4], rep_info->u.send_info.reported_value.data64[5], + rep_info->u.send_info.reported_value.data64[6], rep_info->u.send_info.reported_value.data64[7])); + ret = (delta >= min_delta)?(RET_OK ):(RET_IGNORE ); + break; + } case ZB_ZCL_ATTR_TYPE_U48: {