From 840b96ac02a9f07080f615467a509316f15d14ce Mon Sep 17 00:00:00 2001 From: TheSomeMan Date: Wed, 9 Oct 2024 12:52:55 +0700 Subject: [PATCH] Add support for data formats 0xE0 and 0xF0 (RuuviAir) --- CMakeLists.txt | 6 + Makefile | 2 + src/ruuvi_endpoint_e0.c | 583 ++++++++++++++++++++++++++++++++ src/ruuvi_endpoint_e0.h | 201 +++++++++++ src/ruuvi_endpoint_f0.c | 550 ++++++++++++++++++++++++++++++ src/ruuvi_endpoint_f0.h | 167 ++++++++++ test/test_ruuvi_endpoint_6.c | 14 - test/test_ruuvi_endpoint_e0.c | 608 ++++++++++++++++++++++++++++++++++ test/test_ruuvi_endpoint_f0.c | 538 ++++++++++++++++++++++++++++++ 9 files changed, 2655 insertions(+), 14 deletions(-) create mode 100644 src/ruuvi_endpoint_e0.c create mode 100644 src/ruuvi_endpoint_e0.h create mode 100644 src/ruuvi_endpoint_f0.c create mode 100644 src/ruuvi_endpoint_f0.h create mode 100644 test/test_ruuvi_endpoint_e0.c create mode 100644 test/test_ruuvi_endpoint_f0.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 607affc..1425b6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,10 @@ if(IDF_VERSION_MAJOR GREATER_EQUAL 4) SRCS "src/ruuvi_endpoint_6.h" SRCS "src/ruuvi_endpoint_c5.c" SRCS "src/ruuvi_endpoint_c5.h" + SRCS "src/ruuvi_endpoint_e0.c" + SRCS "src/ruuvi_endpoint_e0.h" + SRCS "src/ruuvi_endpoint_f0.c" + SRCS "src/ruuvi_endpoint_f0.h" SRCS "src/ruuvi_endpoint_ca_uart.c" SRCS "src/ruuvi_endpoint_ca_uart.h" SRCS "src/ruuvi_endpoint_ibeacon.c" @@ -25,6 +29,8 @@ elseif (DEFINED ENV{ZEPHYR_BASE}) src/ruuvi_endpoint_5.c src/ruuvi_endpoint_6.c src/ruuvi_endpoint_c5.c + src/ruuvi_endpoint_e0.c + src/ruuvi_endpoint_f0.c src/ruuvi_endpoint_ca_uart.c src/ruuvi_endpoint_ibeacon.c ) diff --git a/Makefile b/Makefile index 979b1b9..7faf0d2 100644 --- a/Makefile +++ b/Makefile @@ -225,6 +225,8 @@ SOURCES=\ src/ruuvi_endpoint_5.c \ src/ruuvi_endpoint_6.c \ src/ruuvi_endpoint_c5.c \ + src/ruuvi_endpoint_e0.c \ + src/ruuvi_endpoint_f0.c \ src/ruuvi_endpoint_ibeacon.c ANALYSIS=$(SOURCES:.c=.a) diff --git a/src/ruuvi_endpoint_e0.c b/src/ruuvi_endpoint_e0.c new file mode 100644 index 0000000..9e0d724 --- /dev/null +++ b/src/ruuvi_endpoint_e0.c @@ -0,0 +1,583 @@ +#include "ruuvi_endpoint_e0.h" +#include "ruuvi_endpoints.h" +#include +#include +#include + +#define RE_E0_ENABLED 1 + +#if RE_E0_ENABLED + +#define RE_E0_MAC_MAX (0xFFFFFFFFFFFFU) +#define RE_E0_MAC_MIN (0) + +#define RE_E0_BYTE_0_SHIFT (0U) +#define RE_E0_BYTE_1_SHIFT (8U) +#define RE_E0_BYTE_2_SHIFT (16U) +#define RE_E0_BYTE_3_SHIFT (24U) +#define RE_E0_BYTE_4_SHIFT (32U) +#define RE_E0_BYTE_5_SHIFT (40U) +#define RE_E0_BYTE_MASK (0xFFU) + +#define RE_E0_RAW_PACKET_LENGTH_OFFSET (0U) +#define RE_E0_RAW_PACKET_LENGTH_VAL (43U) +#define RE_E0_RAW_PACKET_TYPE_OFFSET (1U) +#define RE_E0_RAW_PACKET_TYPE_VAL (0xFFU) +#define RE_E0_RAW_PACKET_MANUFACTURER_ID_OFFSET_LO (2U) +#define RE_E0_RAW_PACKET_MANUFACTURER_ID_OFFSET_HI (3U) +#define RE_E0_RAW_PACKET_MANUFACTURER_ID_VAL (0x499U) + +// Avoid mocking simple function +#ifdef TEST +void +re_clip (re_float * const value, const re_float min, const re_float max) +{ + if (*value > max) + { + *value = max; + } + + if (*value < min) + { + *value = min; + } +} +#endif + +static void +re_e0_encode_temperature (uint8_t * const p_slot, re_float val) +{ + int16_t val_i16 = RE_E0_INVALID_TEMPERATURE; + + if (!isnan (val)) + { + re_clip (&val, RE_E0_TEMPERATURE_MIN, RE_E0_TEMPERATURE_MAX); + val_i16 = (int16_t) lrintf (val * RE_E0_TEMPERATURE_RATIO); + } + + const uint16_t coded_val = (uint16_t) val_i16; + p_slot[0] |= ( (uint32_t) coded_val >> RE_E0_BYTE_1_SHIFT) & RE_E0_BYTE_MASK; + p_slot[1] |= (coded_val & RE_E0_BYTE_MASK); +} + +static re_float +re_e0_decode_temperature (const uint8_t * const p_slot) +{ + uint16_t coded_val = 0; + coded_val |= p_slot[1] & RE_E0_BYTE_MASK; + coded_val |= ( (uint16_t) p_slot[0] & RE_E0_BYTE_MASK) << RE_E0_BYTE_1_SHIFT; + + if (RE_E0_INVALID_TEMPERATURE == coded_val) + { + return NAN; + } + + const int16_t val_i16 = (int16_t) coded_val; + return (re_float) val_i16 / RE_E0_TEMPERATURE_RATIO; +} + +static void +re_e0_encode_humidity (uint8_t * const p_slot, re_float val) +{ + uint16_t coded_val = RE_E0_INVALID_HUMIDITY; + + if (!isnan (val)) + { + re_clip (&val, RE_E0_HUMIDITY_MIN, RE_E0_HUMIDITY_MAX); + coded_val = (uint16_t) lrintf (val * RE_E0_HUMIDITY_RATIO); + } + + p_slot[0] |= ( (uint32_t) coded_val >> RE_E0_BYTE_1_SHIFT) & RE_E0_BYTE_MASK; + p_slot[1] |= (coded_val & RE_E0_BYTE_MASK); +} + +static re_float +re_e0_decode_humidity (const uint8_t * const p_slot) +{ + uint16_t coded_val = 0; + coded_val |= p_slot[1] & RE_E0_BYTE_MASK; + coded_val |= ( (uint16_t) p_slot[0] & RE_E0_BYTE_MASK) << RE_E0_BYTE_1_SHIFT; + + if (RE_E0_INVALID_HUMIDITY == coded_val) + { + return NAN; + } + + return (re_float) coded_val / RE_E0_HUMIDITY_RATIO; +} + +static void +re_e0_encode_pressure (uint8_t * const p_slot, re_float val) +{ + uint16_t coded_val = RE_E0_INVALID_PRESSURE; + + if (!isnan (val)) + { + re_clip (&val, RE_E0_PRESSURE_MIN, RE_E0_PRESSURE_MAX); + coded_val = (uint16_t) lrintf (val - RE_E0_PRESSURE_MIN); + } + + p_slot[0] |= ( (uint32_t) coded_val >> RE_E0_BYTE_1_SHIFT) & RE_E0_BYTE_MASK; + p_slot[1] |= (coded_val & RE_E0_BYTE_MASK); +} + +static re_float +re_e0_decode_pressure (const uint8_t * const p_slot) +{ + uint16_t coded_val = 0; + coded_val |= p_slot[1] & RE_E0_BYTE_MASK; + coded_val |= ( (uint16_t) p_slot[0] & RE_E0_BYTE_MASK) << RE_E0_BYTE_1_SHIFT; + + if (RE_E0_INVALID_PRESSURE == coded_val) + { + return NAN; + } + + return (re_float) coded_val + RE_E0_PRESSURE_MIN; +} + +static void +re_e0_encode_pm (uint8_t * const p_slot, re_float val) +{ + uint16_t coded_val = RE_E0_INVALID_PM; + + if (!isnan (val)) + { + re_clip (&val, RE_E0_PM_MIN, RE_E0_PM_MAX); + coded_val = (uint16_t) lrintf (val * RE_E0_PM_RATIO); + } + + p_slot[0] |= ( ( (uint32_t) coded_val >> RE_E0_BYTE_1_SHIFT) & RE_E0_BYTE_MASK); + p_slot[1] |= (coded_val & RE_E0_BYTE_MASK); +} + +static re_float +re_e0_decode_pm (const uint8_t * const p_slot) +{ + uint16_t coded_val = 0; + coded_val |= p_slot[1] & RE_E0_BYTE_MASK; + coded_val |= ( (uint16_t) p_slot[0] & RE_E0_BYTE_MASK) << RE_E0_BYTE_1_SHIFT; + + if (RE_E0_INVALID_PM == coded_val) + { + return NAN; + } + + return (re_float) coded_val / RE_E0_PM_RATIO; +} + +static void +re_e0_encode_co2 (uint8_t * const p_slot, re_float val) +{ + uint16_t coded_val = RE_E0_INVALID_CO2; + + if (!isnan (val)) + { + re_clip (&val, RE_E0_CO2_MIN, RE_E0_CO2_MAX); + coded_val = (uint16_t) lrintf (val * RE_E0_CO2_RATIO); + } + + p_slot[0] |= ( (uint32_t) coded_val >> RE_E0_BYTE_1_SHIFT) & RE_E0_BYTE_MASK; + p_slot[1] |= (coded_val & RE_E0_BYTE_MASK); +} + +static re_float +re_e0_decode_co2 (const uint8_t * const p_slot) +{ + uint16_t coded_val = 0; + coded_val |= p_slot[1] & RE_E0_BYTE_MASK; + coded_val |= ( (uint16_t) p_slot[0] & RE_E0_BYTE_MASK) << RE_E0_BYTE_1_SHIFT; + + if (RE_E0_INVALID_CO2 == coded_val) + { + return NAN; + } + + return (re_float) coded_val / RE_E0_CO2_RATIO; +} + +static void +re_e0_encode_voc (uint8_t * const p_slot, re_float val) +{ + uint16_t coded_val = RE_E0_INVALID_VOC_INDEX; + + if ( (!isnan (val)) && (val >= RE_E0_VOC_INDEX_MIN) && (val <= RE_E0_VOC_INDEX_MAX)) + { + coded_val = (uint16_t) lrintf (val); + } + + p_slot[0] |= ( (uint32_t) coded_val >> RE_E0_BYTE_1_SHIFT) & RE_E0_BYTE_MASK; + p_slot[1] |= (coded_val & RE_E0_BYTE_MASK); +} + +static re_float +re_e0_decode_voc (const uint8_t * const p_slot) +{ + uint16_t coded_val = 0; + coded_val |= p_slot[1] & RE_E0_BYTE_MASK; + coded_val |= ( (uint16_t) p_slot[0] & RE_E0_BYTE_MASK) << RE_E0_BYTE_1_SHIFT; + + if (RE_E0_INVALID_VOC_INDEX == coded_val) + { + return NAN; + } + + return (re_float) coded_val; +} + +static void +re_e0_encode_nox (uint8_t * const p_slot, re_float val) +{ + uint16_t coded_val = RE_E0_INVALID_NOX_INDEX; + + if ( (!isnan (val)) && (val >= RE_E0_NOX_INDEX_MIN) && (val <= RE_E0_NOX_INDEX_MAX)) + { + coded_val = (uint16_t) lrintf (val); + } + + p_slot[0] |= ( (uint32_t) coded_val >> RE_E0_BYTE_1_SHIFT) & RE_E0_BYTE_MASK; + p_slot[1] |= (coded_val & RE_E0_BYTE_MASK); +} + +static re_float +re_e0_decode_nox (const uint8_t * const p_slot) +{ + uint16_t coded_val = 0; + coded_val |= p_slot[1] & RE_E0_BYTE_MASK; + coded_val |= ( (uint16_t) p_slot[0] & RE_E0_BYTE_MASK) << RE_E0_BYTE_1_SHIFT; + + if (RE_E0_INVALID_NOX_INDEX == coded_val) + { + return NAN; + } + + return (re_float) coded_val; +} + +static void +re_e0_encode_luminosity (uint8_t * const p_slot, re_float val) +{ + uint16_t coded_val = RE_E0_INVALID_LUMINOSITY; + + if (!isnan (val)) + { + re_clip (&val, RE_E0_LUMINOSITY_MIN, RE_E0_LUMINOSITY_MAX); + coded_val = (uint16_t) lrintf (val); + } + + p_slot[0] |= ( (uint32_t) coded_val >> RE_E0_BYTE_1_SHIFT) & RE_E0_BYTE_MASK; + p_slot[1] |= (coded_val & RE_E0_BYTE_MASK); +} + +static re_float +re_e0_decode_luminosity (const uint8_t * const p_slot) +{ + uint16_t coded_val = 0; + coded_val |= p_slot[1] & RE_E0_BYTE_MASK; + coded_val |= ( (uint16_t) p_slot[0] & RE_E0_BYTE_MASK) << RE_E0_BYTE_1_SHIFT; + + if (RE_E0_INVALID_LUMINOSITY == coded_val) + { + return NAN; + } + + return (re_float) coded_val; +} + +static void +re_e0_encode_sound_dba (uint8_t * const p_slot, re_float val) +{ + uint8_t coded_val = RE_E0_INVALID_SOUND_DBA; + + if (!isnan (val)) + { + re_clip (&val, RE_E0_SOUND_DBA_MIN, RE_E0_SOUND_DBA_MAX); + coded_val = (uint16_t) lrintf (val * RE_E0_SOUND_DBA_RATIO); + } + + p_slot[0] |= coded_val; +} + +static re_float +re_e0_decode_sound_dba (const uint8_t * const p_slot) +{ + uint8_t coded_val = p_slot[0]; + + if (RE_E0_INVALID_SOUND_DBA == coded_val) + { + return NAN; + } + + return (re_float) coded_val / RE_E0_SOUND_DBA_RATIO; +} + +static void +re_e0_encode_sequence (uint8_t * const p_slot, const uint16_t measurement_seq) +{ + p_slot[0] = (uint8_t) ( (measurement_seq >> RE_E0_BYTE_1_SHIFT) & RE_E0_BYTE_MASK); + p_slot[1] = (uint8_t) (measurement_seq & RE_E0_BYTE_MASK); +} + +static uint16_t +re_e0_decode_sequence (const uint8_t * const p_buffer) +{ + uint16_t measurement_seq = 0; + measurement_seq |= p_buffer[1] & RE_E0_BYTE_MASK; + measurement_seq |= (p_buffer[0] & RE_E0_BYTE_MASK) << RE_E0_BYTE_1_SHIFT; + return measurement_seq; +} + +static void +re_e0_encode_voltage (uint8_t * const p_slot, re_float val) +{ + uint8_t coded_val = RE_E0_INVALID_VOLTAGE; + + if (!isnan (val)) + { + re_clip (&val, RE_E0_VOLTAGE_MIN, RE_E0_VOLTAGE_MAX); + coded_val = (uint16_t) lrintf (val * RE_E0_VOLTAGE_RATIO); + } + + p_slot[0] |= coded_val; +} + +static re_float +re_e0_decode_voltage (const uint8_t * const p_slot) +{ + uint8_t coded_val = p_slot[0]; + + if (RE_E0_INVALID_VOLTAGE == coded_val) + { + return NAN; + } + + return (re_float) coded_val / RE_E0_VOLTAGE_RATIO; +} + +static void +re_e0_encode_flags (uint8_t * const p_slot, const re_e0_data_t * const p_data) +{ + uint8_t flags = 0; + flags |= (p_data->flag_usb_on ? RE_E0_FLAGS_USB_ON : 0U); + flags |= (p_data->flag_low_battery ? RE_E0_FLAGS_LOW_BATTERY : 0U); + flags |= (p_data->flag_calibration_in_progress ? RE_E0_FLAGS_CALIBRATION_IN_PROGRESS : + 0U); + flags |= (p_data->flag_boost_mode ? RE_E0_FLAGS_BOOST_MODE : 0U); + *p_slot = flags; +} + +static void +re_e0_decode_flags (const uint8_t * const p_slot, re_e0_data_t * const p_data) +{ + const uint8_t flags = *p_slot; + p_data->flag_usb_on = (flags & RE_E0_FLAGS_USB_ON) ? true : false; + p_data->flag_low_battery = (flags & RE_E0_FLAGS_LOW_BATTERY) ? true : false; + p_data->flag_calibration_in_progress = (flags & RE_E0_FLAGS_CALIBRATION_IN_PROGRESS) ? + true : false; + p_data->flag_boost_mode = (flags & RE_E0_FLAGS_BOOST_MODE) ? true : false; +} + +static void +re_e0_encode_set_address (uint8_t * const p_buffer, const re_e0_data_t * p_data) +{ + // Address is 64 bits, skip 2 first bytes + uint8_t addr_offset = RE_E0_OFFSET_ADDR_MSB; + uint64_t mac = p_data->address; + + if (RE_E0_MAC_MAX < p_data->address) + { + mac = RE_E0_INVALID_MAC; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + + if (RE_E0_MAC_MIN > p_data->address) + { + mac = RE_E0_INVALID_MAC; + } + +#pragma GCC diagnostic pop + p_buffer[addr_offset] = (mac >> RE_E0_BYTE_5_SHIFT) & RE_E0_BYTE_MASK; + addr_offset++; + p_buffer[addr_offset] = (mac >> RE_E0_BYTE_4_SHIFT) & RE_E0_BYTE_MASK; + addr_offset++; + p_buffer[addr_offset] = (mac >> RE_E0_BYTE_3_SHIFT) & RE_E0_BYTE_MASK; + addr_offset++; + p_buffer[addr_offset] = (mac >> RE_E0_BYTE_2_SHIFT) & RE_E0_BYTE_MASK; + addr_offset++; + p_buffer[addr_offset] = (mac >> RE_E0_BYTE_1_SHIFT) & RE_E0_BYTE_MASK; + addr_offset++; + p_buffer[addr_offset] = (mac >> RE_E0_BYTE_0_SHIFT) & RE_E0_BYTE_MASK; +} + +static uint64_t +re_e0_decode_address (const uint8_t * const p_buffer) +{ + // Address is 64 bits, skip 2 first bytes + uint8_t addr_offset = 0; + uint64_t mac = 0; + mac |= p_buffer[addr_offset]; + mac <<= RE_E0_BYTE_1_SHIFT; + addr_offset++; + mac |= p_buffer[addr_offset]; + mac <<= RE_E0_BYTE_1_SHIFT; + addr_offset++; + mac |= p_buffer[addr_offset]; + mac <<= RE_E0_BYTE_1_SHIFT; + addr_offset++; + mac |= p_buffer[addr_offset]; + mac <<= RE_E0_BYTE_1_SHIFT; + addr_offset++; + mac |= p_buffer[addr_offset]; + mac <<= RE_E0_BYTE_1_SHIFT; + addr_offset++; + mac |= p_buffer[addr_offset]; + return mac; +} + +re_status_t +re_e0_encode (uint8_t * const p_buffer, const re_e0_data_t * const p_data) +{ + re_status_t result = RE_SUCCESS; + + if ( (NULL == p_buffer) || (NULL == p_data)) + { + result |= RE_ERROR_NULL; + } + else + { + memset (p_buffer, 0x00U, RE_E0_DATA_LENGTH); + p_buffer[RE_E0_OFFSET_HEADER] = RE_E0_DESTINATION; + re_e0_encode_temperature (&p_buffer[RE_E0_OFFSET_TEMPERATURE_MSB], p_data->temperature_c); + re_e0_encode_humidity (&p_buffer[RE_E0_OFFSET_HUMIDITY_MSB], p_data->humidity_rh); + re_e0_encode_pressure (&p_buffer[RE_E0_OFFSET_PRESSURE_MSB], p_data->pressure_pa); + re_e0_encode_pm (&p_buffer[RE_E0_OFFSET_PM_1_0_MSB], p_data->pm1p0_ppm); + re_e0_encode_pm (&p_buffer[RE_E0_OFFSET_PM_2_5_MSB], p_data->pm2p5_ppm); + re_e0_encode_pm (&p_buffer[RE_E0_OFFSET_PM_4_0_MSB], p_data->pm4p0_ppm); + re_e0_encode_pm (&p_buffer[RE_E0_OFFSET_PM_10_0_MSB], p_data->pm10p0_ppm); + re_e0_encode_co2 (&p_buffer[RE_E0_OFFSET_CO2_MSB], p_data->co2); + re_e0_encode_voc (&p_buffer[RE_E0_OFFSET_VOC_INDEX_MSB], p_data->voc_index); + re_e0_encode_nox (&p_buffer[RE_E0_OFFSET_NOX_INDEX_MSB], p_data->nox_index); + re_e0_encode_luminosity (&p_buffer[RE_E0_OFFSET_LUMINOSITY_MSB], p_data->luminosity); + re_e0_encode_sound_dba (&p_buffer[RE_E0_OFFSET_SOUND_DBA_AVG], p_data->sound_dba_avg); + re_e0_encode_sound_dba (&p_buffer[RE_E0_OFFSET_SOUND_DBA_PEAK], p_data->sound_dba_peak); + re_e0_encode_sequence (&p_buffer[RE_E0_OFFSET_SEQ_CTR_MSB], p_data->measurement_count); + re_e0_encode_voltage (&p_buffer[RE_E0_OFFSET_VOLTAGE], p_data->voltage); + re_e0_encode_flags (&p_buffer[RE_E0_OFFSET_FLAGS], p_data); + p_buffer[RE_E0_OFFSET_RESERVED + 0] = 0xFFU; + p_buffer[RE_E0_OFFSET_RESERVED + 1] = 0xFFU; + p_buffer[RE_E0_OFFSET_RESERVED + 2] = 0xFFU; + p_buffer[RE_E0_OFFSET_RESERVED + 3] = 0xFFU; + p_buffer[RE_E0_OFFSET_RESERVED + 4] = 0xFFU; + p_buffer[RE_E0_OFFSET_RESERVED + 5] = 0xFFU; + re_e0_encode_set_address (p_buffer, p_data); + } + + return result; +} + +bool re_e0_check_format (const uint8_t * const p_buffer) +{ + if (NULL == p_buffer) + { + return false; + } + + if (RE_E0_RAW_PACKET_LENGTH_VAL != p_buffer[RE_E0_RAW_PACKET_LENGTH_OFFSET]) + { + return false; + } + + if (RE_E0_RAW_PACKET_TYPE_VAL != p_buffer[RE_E0_RAW_PACKET_TYPE_OFFSET]) + { + return false; + } + + const uint16_t manufacturer_id = (uint16_t) ( (uint16_t) + p_buffer[RE_E0_RAW_PACKET_MANUFACTURER_ID_OFFSET_HI] << RE_E0_BYTE_1_SHIFT) + + p_buffer[RE_E0_RAW_PACKET_MANUFACTURER_ID_OFFSET_LO]; + + if (RE_E0_RAW_PACKET_MANUFACTURER_ID_VAL != manufacturer_id) + { + return false; + } + + if (RE_E0_DESTINATION != p_buffer[RE_E0_OFFSET_PAYLOAD + RE_E0_OFFSET_HEADER]) + { + return false; + } + + return true; +} + +re_status_t re_e0_decode (const uint8_t * const p_buffer, re_e0_data_t * const p_data) +{ + const uint8_t * const p_payload = &p_buffer[RE_E0_OFFSET_PAYLOAD]; + re_status_t result = RE_SUCCESS; + + if ( (NULL == p_payload) || (NULL == p_data)) + { + return RE_ERROR_NULL; + } + + memset (p_data, 0, sizeof (*p_data)); + + if (RE_E0_DESTINATION != p_payload[RE_E0_OFFSET_HEADER]) + { + return RE_ERROR_INVALID_PARAM; + } + + p_data->temperature_c = re_e0_decode_temperature ( + &p_payload[RE_E0_OFFSET_TEMPERATURE_MSB]); + p_data->humidity_rh = re_e0_decode_humidity (&p_payload[RE_E0_OFFSET_HUMIDITY_MSB]); + p_data->pressure_pa = re_e0_decode_pressure (&p_payload[RE_E0_OFFSET_PRESSURE_MSB]); + p_data->pm1p0_ppm = re_e0_decode_pm (&p_payload[RE_E0_OFFSET_PM_1_0_MSB]); + p_data->pm2p5_ppm = re_e0_decode_pm (&p_payload[RE_E0_OFFSET_PM_2_5_MSB]); + p_data->pm4p0_ppm = re_e0_decode_pm (&p_payload[RE_E0_OFFSET_PM_4_0_MSB]); + p_data->pm10p0_ppm = re_e0_decode_pm (&p_payload[RE_E0_OFFSET_PM_10_0_MSB]); + p_data->co2 = re_e0_decode_co2 (&p_payload[RE_E0_OFFSET_CO2_MSB]); + p_data->voc_index = re_e0_decode_voc (&p_payload[RE_E0_OFFSET_VOC_INDEX_MSB]); + p_data->nox_index = re_e0_decode_nox (&p_payload[RE_E0_OFFSET_NOX_INDEX_MSB]); + p_data->luminosity = re_e0_decode_luminosity (&p_payload[RE_E0_OFFSET_LUMINOSITY_MSB]); + p_data->sound_dba_avg = re_e0_decode_sound_dba (&p_payload[RE_E0_OFFSET_SOUND_DBA_AVG]); + p_data->sound_dba_peak = re_e0_decode_sound_dba (&p_payload[RE_E0_OFFSET_SOUND_DBA_PEAK]); + p_data->measurement_count = re_e0_decode_sequence (&p_payload[RE_E0_OFFSET_SEQ_CTR_MSB]); + p_data->voltage = re_e0_decode_voltage (&p_payload[RE_E0_OFFSET_VOLTAGE]); + re_e0_decode_flags (&p_payload[RE_E0_OFFSET_FLAGS], p_data); + p_data->address = re_e0_decode_address (&p_payload[RE_E0_OFFSET_ADDR_MSB]); + return result; +} + +re_e0_data_t +re_e0_data_invalid (const uint16_t measurement_cnt, const uint64_t radio_mac) +{ + const re_e0_data_t data = + { + .temperature_c = NAN, + .humidity_rh = NAN, + .pressure_pa = NAN, + .pm1p0_ppm = NAN, + .pm2p5_ppm = NAN, + .pm4p0_ppm = NAN, + .pm10p0_ppm = NAN, + .co2 = NAN, + .voc_index = NAN, + .nox_index = NAN, + .luminosity = NAN, + .sound_dba_avg = NAN, + .sound_dba_peak = NAN, + .measurement_count = measurement_cnt, + .voltage = NAN, + + .flag_usb_on = false, + .flag_low_battery = false, + .flag_calibration_in_progress = false, + .flag_boost_mode = false, + + .address = radio_mac, + }; + return data; +} + +#endif diff --git a/src/ruuvi_endpoint_e0.h b/src/ruuvi_endpoint_e0.h new file mode 100644 index 0000000..de182ea --- /dev/null +++ b/src/ruuvi_endpoint_e0.h @@ -0,0 +1,201 @@ +/** + * Ruuvi Endpoint 0xE0 helper. + * Defines necessary data for creating a Ruuvi data format 0xE0 broadcast. + * + * License: BSD-3 + * Author: TheSomeMan + */ + +#ifndef RUUVI_ENDPOINT_E0_H +#define RUUVI_ENDPOINT_E0_H + +#include "ruuvi_endpoints.h" +#include + +#define RE_E0_DESTINATION (0xE0U) + +#define RE_E0_INVALID_TEMPERATURE (0x8000U) +#define RE_E0_INVALID_HUMIDITY (0xFFFFU) +#define RE_E0_INVALID_PRESSURE (0xFFFFU) +#define RE_E0_INVALID_PM (0xFFFFU) +#define RE_E0_INVALID_CO2 (0xFFFFU) +#define RE_E0_INVALID_VOC_INDEX (511) +#define RE_E0_INVALID_NOX_INDEX (511) +#define RE_E0_INVALID_LUMINOSITY (0xFFFFU) +#define RE_E0_INVALID_SOUND_DBA (0xFFU) +#define RE_E0_INVALID_VOLTAGE (0xFFU) +#define RE_E0_INVALID_SEQUENCE (0xFFFFU) +#define RE_E0_INVALID_MAC (0xFFFFFFFFFFFFU) + +#define RE_E0_FLAGS_USB_ON (0x01U) +#define RE_E0_FLAGS_LOW_BATTERY (0x02U) +#define RE_E0_FLAGS_CALIBRATION_IN_PROGRESS (0x04U) +#define RE_E0_FLAGS_BOOST_MODE (0x08U) + +#define RE_E0_DATA_LENGTH (40U) + +#define RE_E0_TEMPERATURE_MIN (-126.0f) +#define RE_E0_TEMPERATURE_MAX (126.0f) +#define RE_E0_TEMPERATURE_RATIO (200.0f) + +#define RE_E0_HUMIDITY_MIN (0.0f) +#define RE_E0_HUMIDITY_MAX (100.0f) +#define RE_E0_HUMIDITY_RATIO (400.0f) + +#define RE_E0_PRESSURE_MIN (50000.0f) +#define RE_E0_PRESSURE_MAX (115534.0f) + +#define RE_E0_PM_MIN (0.0f) +#define RE_E0_PM_MAX (1000.0f) +#define RE_E0_PM_RATIO (10.0f) + +#define RE_E0_CO2_MIN (0.0f) +#define RE_E0_CO2_MAX (40000.0f) +#define RE_E0_CO2_RATIO (1.0f) + +#define RE_E0_VOC_INDEX_MIN (1.0f) +#define RE_E0_VOC_INDEX_MAX (500.0f) + +#define RE_E0_NOX_INDEX_MIN (1.0f) +#define RE_E0_NOX_INDEX_MAX (500.0f) + +#define RE_E0_LUMINOSITY_MIN (0.0f) +#define RE_E0_LUMINOSITY_MAX (65534.0f) + +#define RE_E0_SOUND_DBA_MIN (0.0f) +#define RE_E0_SOUND_DBA_MAX (127.0f) +#define RE_E0_SOUND_DBA_RATIO (2.0f) + +#define RE_E0_VOLTAGE_MIN (0.0f) +#define RE_E0_VOLTAGE_MAX (7.62f) +#define RE_E0_VOLTAGE_RATIO (100.0f / 3.0f) + +#define RE_E0_OFFSET_PAYLOAD (4U) + +#define RE_E0_OFFSET_HEADER (0U) + +#define RE_E0_OFFSET_TEMPERATURE_MSB (1U) +#define RE_E0_OFFSET_TEMPERATURE_LSB (2U) + +#define RE_E0_OFFSET_HUMIDITY_MSB (3U) +#define RE_E0_OFFSET_HUMIDITY_LSB (4U) + +#define RE_E0_OFFSET_PRESSURE_MSB (5U) +#define RE_E0_OFFSET_PRESSURE_LSB (6U) + +#define RE_E0_OFFSET_PM_1_0_MSB (7U) +#define RE_E0_OFFSET_PM_1_0_LSB (8U) + +#define RE_E0_OFFSET_PM_2_5_MSB (9U) +#define RE_E0_OFFSET_PM_2_5_LSB (10U) + +#define RE_E0_OFFSET_PM_4_0_MSB (11U) +#define RE_E0_OFFSET_PM_4_0_LSB (12U) + +#define RE_E0_OFFSET_PM_10_0_MSB (13U) +#define RE_E0_OFFSET_PM_10_0_LSB (14U) + +#define RE_E0_OFFSET_CO2_MSB (15U) +#define RE_E0_OFFSET_CO2_LSB (16U) + +#define RE_E0_OFFSET_VOC_INDEX_MSB (17U) +#define RE_E0_OFFSET_VOC_INDEX_LSB (18U) + +#define RE_E0_OFFSET_NOX_INDEX_MSB (19U) +#define RE_E0_OFFSET_NOX_INDEX_LSB (20U) + +#define RE_E0_OFFSET_LUMINOSITY_MSB (21U) +#define RE_E0_OFFSET_LUMINOSITY_LSB (22U) + +#define RE_E0_OFFSET_SOUND_DBA_AVG (23U) +#define RE_E0_OFFSET_SOUND_DBA_PEAK (24U) + +#define RE_E0_OFFSET_SEQ_CTR_MSB (25U) +#define RE_E0_OFFSET_SEQ_CTR_LSB (26U) + +#define RE_E0_OFFSET_VOLTAGE (27U) + +#define RE_E0_OFFSET_FLAGS (28U) + +#define RE_E0_OFFSET_RESERVED (29U) + +#define RE_E0_OFFSET_ADDR_MSB (34U) + +/** @brief All data required for Ruuvi dataformat 0xE0 package. */ +typedef struct +{ + re_float temperature_c; //!< Temperature in degrees Celsius. + re_float humidity_rh; //!< Humidity in relative humidity percentage. + re_float pressure_pa; //!< Humidity in relative humidity percentage. + re_float pm1p0_ppm; //!< Particulate matter mass concentration PM1.0 in micrograms/m3. + re_float pm2p5_ppm; //!< Particulate matter mass concentration PM2.5 in micrograms/m3. + re_float pm4p0_ppm; //!< Particulate matter mass concentration PM4.0 in micrograms/m3. + re_float pm10p0_ppm; //!< Particulate matter mass concentration PM10.0 in micrograms/m3. + re_float co2; //!< CO2 concentration in ppm + re_float voc_index; //!< VOC index points. + re_float nox_index; //!< NOx index points. + re_float luminosity; //!< Luminosity. + re_float sound_dba_avg; //!< Sound dBA avg. + re_float sound_dba_peak; //!< Sound dBA peak. + uint16_t measurement_count; //!< Running counter of measurement. + re_float voltage; //!< Voltage in volts. + + bool flag_usb_on : 1; //!< Flag: USB ON + bool flag_low_battery : 1; //!< Flag: LOW BATTERY + bool flag_calibration_in_progress : 1; //!< Flag: Calibration in progress + bool flag_boost_mode : 1; //!< Flag: Boost mode + + uint64_t address; //!< BLE address of device, most significant byte first. +} re_e0_data_t; + +/** + * @brief Encode given data to given buffer in Ruuvi DFxE0. + * + * NAN can be used as a placeholder for invalid / not available values. + * + * @param[out] buffer uint8_t array with length of 24 bytes. + * @param[in] data Struct containing all necessary information + * for encoding the data into buffer. + * @retval RE_SUCCESS if data was encoded successfully. + */ +re_status_t +re_e0_encode (uint8_t * const buffer, const re_e0_data_t * data); + +/** + * @brief Checks if the provided buffer conforms to the Ruuvi DFxE0 format. + * + * This function examines the input buffer to determine if its content + * represents data in the Ruuvi DFxE0 format. + * + * @param[in] p_buffer Pointer to a uint8_t input array with a length of 31 bytes to be checked. + * @return Returns 'true' if the buffer format is Ruuvi DFxE0, 'false' otherwise. + */ +bool +re_e0_check_format (const uint8_t * const p_buffer); + +/** + * @brief Decodes a given buffer using the Ruuvi DFxE0 format. + * + * Ruuvi DFxE0 is a data format used by the Ruuvi AirQ environmental sensor. + * + * @param[in] p_buffer Pointer to a uint8_t input array with a length of 31 bytes + * representing a Bluetooth frame with Ruuvi DFxE0 formatted payload. + * @param[out] p_data Pointer to a re_e0_data_t struct. + * After the function executes, this struct will contain the data decoded from the input buffer. + * @return Returns RE_SUCCESS if the data was decoded successfully, + * i.e., if the input buffer was a valid Ruuvi DFxE0 buffer and `p_data` now points to a valid `re_e0_data_t` object. + * If the decoding fails, the function returns a code indicating the type of error occurred. + */ +re_status_t +re_e0_decode (const uint8_t * const p_buffer, re_e0_data_t * const p_data); + +/** + * @brief Create invalid Ruuvi DFxE0 data. + * @param measurement_cnt Running counter of measurement. + * @param radio_mac BLE address of device. + * @return /ref re_e0_data_t with all values set to NAN. + */ +re_e0_data_t +re_e0_data_invalid (const uint16_t measurement_cnt, const uint64_t radio_mac); + +#endif diff --git a/src/ruuvi_endpoint_f0.c b/src/ruuvi_endpoint_f0.c new file mode 100644 index 0000000..1d8d0e7 --- /dev/null +++ b/src/ruuvi_endpoint_f0.c @@ -0,0 +1,550 @@ +#include "ruuvi_endpoint_f0.h" +#include "ruuvi_endpoints.h" +#include +#include +#include + +#define RE_F0_ENABLED 1 + +#if RE_F0_ENABLED + +#define RE_F0_MAC_MAX (0xFFFFFFFFFFFFU) +#define RE_F0_MAC_MIN (0) + +#define RE_F0_BYTE_0_SHIFT (0U) +#define RE_F0_BYTE_1_SHIFT (8U) +#define RE_F0_BYTE_2_SHIFT (16U) +#define RE_F0_BYTE_3_SHIFT (24U) +#define RE_F0_BYTE_4_SHIFT (32U) +#define RE_F0_BYTE_5_SHIFT (40U) +#define RE_F0_BYTE_MASK (0xFFU) + +#define RE_F0_RAW_PACKET_ADV_DATA_FLAGS_LEN_OFFSET (0U) +#define RE_F0_RAW_PACKET_ADV_DATA_FLAGS_LEN_VAL (2U) +#define RE_F0_RAW_PACKET_ADV_DATA_FLAGS_DATA_TYPE_OFFSET (1U) +#define RE_F0_RAW_PACKET_ADV_DATA_FLAGS_DATA_TYPE_VAL (1U) /* BT_DATA_FLAGS - AD flags */ + +#define RE_F0_RAW_PACKET_ADV_DATA_UUID16_LEN_OFFSET (3U) +#define RE_F0_RAW_PACKET_ADV_DATA_UUID16_LEN_VAL (3U) +#define RE_F0_RAW_PACKET_ADV_DATA_UUID16_DATA_TYPE_OFFSET (4U) +#define RE_F0_RAW_PACKET_ADV_DATA_UUID16_DATA_TYPE_VAL (3U) /* BT_DATA_UUID16_ALL - 16-bit UUID, all listed */ +#define RE_F0_RAW_PACKET_ADV_DATA_UUID16_BYTE1_OFFSET (5U) +#define RE_F0_RAW_PACKET_ADV_DATA_UUID16_BYTE2_OFFSET (6U) +#define RE_F0_RAW_PACKET_ADV_DATA_UUID16_VAL (0xFC98U) + +#define RE_F0_RAW_PACKET_MANUFACTURER_DATA_LENGTH_OFFSET (7U) +#define RE_F0_RAW_PACKET_MANUFACTURER_DATA_LENGTH_VAL (23U) +#define RE_F0_RAW_PACKET_MANUFACTURER_DATA_TYPE_OFFSET (8U) +#define RE_F0_RAW_PACKET_MANUFACTURER_DATA_TYPE_VAL (0xFFU) /* BT_DATA_MANUFACTURER_DATA */ +#define RE_F0_RAW_PACKET_MANUFACTURER_ID_OFFSET_LO (9U) +#define RE_F0_RAW_PACKET_MANUFACTURER_ID_OFFSET_HI (10U) +#define RE_F0_RAW_PACKET_MANUFACTURER_ID_VAL (0x499U) + +// Avoid mocking simple function +#ifdef TEST +void +re_clip (re_float * const value, const re_float min, const re_float max) +{ + if (*value > max) + { + *value = max; + } + + if (*value < min) + { + *value = min; + } +} +#endif + +static void +re_f0_encode_temperature (uint8_t * const p_slot, re_float val) +{ + uint8_t coded_val = RE_F0_INVALID_TEMPERATURE; + + if (!isnan (val)) + { + re_clip (&val, RE_F0_TEMPERATURE_MIN, RE_F0_TEMPERATURE_MAX); + const int8_t signed_coded_val = (int8_t) lrintf (val * RE_F0_TEMPERATURE_SCALE_FACTOR); + coded_val = (uint8_t) signed_coded_val; + } + + *p_slot = coded_val; +} + +static re_float +re_f0_decode_temperature (const uint8_t * const p_slot) +{ + uint8_t coded_val = * (uint8_t *) p_slot; + + if (RE_F0_INVALID_TEMPERATURE == coded_val) + { + return NAN; + } + + const int8_t signed_coded_val = (int8_t) coded_val; + return (re_float) signed_coded_val / RE_F0_TEMPERATURE_SCALE_FACTOR; +} + +static void +re_f0_encode_humidity (uint8_t * const p_slot, re_float val) +{ + uint8_t coded_val = RE_F0_INVALID_HUMIDITY; + + if (!isnan (val)) + { + re_clip (&val, RE_F0_HUMIDITY_MIN, RE_F0_HUMIDITY_MAX); + coded_val = (uint8_t) lrintf (val * RE_F0_HUMIDITY_SCALE_FACTOR); + } + + *p_slot = coded_val; +} + +static re_float +re_f0_decode_humidity (const uint8_t * const p_slot) +{ + const uint8_t coded_val = *p_slot; + + if (RE_F0_INVALID_HUMIDITY == coded_val) + { + return NAN; + } + + return (re_float) coded_val / RE_F0_HUMIDITY_SCALE_FACTOR; +} + +static void +re_f0_encode_pressure (uint8_t * const p_slot, re_float val) +{ + uint8_t coded_val = RE_F0_INVALID_PRESSURE; + + if (!isnan (val)) + { + re_clip (&val, RE_F0_PRESSURE_MIN, RE_F0_PRESSURE_MAX); + coded_val = (uint8_t) lrintf ( ( (val - RE_F0_PRESSURE_MIN) / + RE_F0_PRESSURE_SCALE_FACTOR)); + } + + *p_slot = coded_val; +} + +static re_float +re_f0_decode_pressure (const uint8_t * const p_slot) +{ + const uint8_t coded_val = *p_slot; + + if (RE_F0_INVALID_PRESSURE == coded_val) + { + return NAN; + } + + return ( (re_float) coded_val * RE_F0_PRESSURE_SCALE_FACTOR) + RE_F0_PRESSURE_MIN; +} + +static void +re_f0_encode_pm (uint8_t * const p_slot, re_float val) +{ + uint8_t coded_val = RE_F0_INVALID_PM; + + if (!isnan (val)) + { + re_clip (&val, RE_F0_PM_MIN, RE_F0_PM_MAX); + coded_val = (uint8_t) (int) lrintf (logf (val + 1) * RE_F0_PM_SCALE_FACTOR); + } + + *p_slot = coded_val; +} + +static re_float +re_f0_decode_pm (const uint8_t * const p_slot) +{ + const uint8_t coded_val = *p_slot; + + if (RE_F0_INVALID_PM == coded_val) + { + return NAN; + } + + return (re_float) (expf ( ( (re_float) coded_val / RE_F0_PM_SCALE_FACTOR)) - 1); +} + +static void +re_f0_encode_co2 (uint8_t * const p_slot, re_float val) +{ + uint8_t coded_val = RE_F0_INVALID_CO2; + + if (!isnan (val)) + { + re_clip (&val, RE_F0_CO2_MIN, RE_F0_CO2_MAX); + coded_val = (uint8_t) (int) lrintf (logf (val + 1) * RE_F0_CO2_SCALE_FACTOR); + } + + *p_slot = coded_val; +} + +static re_float +re_f0_decode_co2 (const uint8_t * const p_slot) +{ + const uint8_t coded_val = *p_slot; + + if (RE_F0_INVALID_CO2 == coded_val) + { + return NAN; + } + + return (re_float) (expf ( ( (re_float) coded_val / RE_F0_CO2_SCALE_FACTOR)) - 1); +} + +static void +re_f0_encode_voc (uint8_t * const p_slot, re_float val) +{ + uint8_t coded_val = RE_F0_INVALID_VOC_INDEX; + + if ( (!isnan (val)) && (val >= RE_F0_VOC_INDEX_MIN) && (val <= RE_F0_VOC_INDEX_MAX)) + { + coded_val = (uint8_t) (int) lrintf (logf (val) * RE_F0_VOC_SCALE_FACTOR); + } + + *p_slot = coded_val; +} + +static re_float +re_f0_decode_voc (const uint8_t * const p_slot) +{ + uint8_t coded_val = *p_slot; + + if (RE_F0_INVALID_VOC_INDEX == coded_val) + { + return NAN; + } + + return (re_float) (expf ( ( (re_float) coded_val / RE_F0_VOC_SCALE_FACTOR))); +} + +static void +re_f0_encode_nox (uint8_t * const p_slot, re_float val) +{ + uint8_t coded_val = RE_F0_INVALID_NOX_INDEX; + + if ( (!isnan (val)) && (val >= RE_F0_NOX_INDEX_MIN) && (val <= RE_F0_NOX_INDEX_MAX)) + { + coded_val = (uint8_t) (int) lrintf (logf (val) * RE_F0_NOX_SCALE_FACTOR); + } + + *p_slot = coded_val; +} + +static re_float +re_f0_decode_nox (const uint8_t * const p_slot) +{ + uint8_t coded_val = *p_slot; + + if (RE_F0_INVALID_NOX_INDEX == coded_val) + { + return NAN; + } + + return (re_float) (expf ( ( (re_float) coded_val / RE_F0_NOX_SCALE_FACTOR))); +} + +static void +re_f0_encode_luminosity (uint8_t * const p_slot, re_float val) +{ + uint8_t coded_val = RE_F0_INVALID_LUMINOSITY; + + if (!isnan (val)) + { + re_clip (&val, RE_F0_LUMINOSITY_MIN, RE_F0_LUMINOSITY_MAX); + coded_val = (uint8_t) (int) lrintf (logf (val + 1) * RE_F0_LUMINOSITY_SCALE_FACTOR); + } + + *p_slot = coded_val; +} + +static re_float +re_f0_decode_luminosity (const uint8_t * const p_slot) +{ + uint8_t coded_val = *p_slot; + + if (RE_F0_INVALID_LUMINOSITY == coded_val) + { + return NAN; + } + + return (re_float) (expf ( ( (re_float) coded_val / RE_F0_LUMINOSITY_SCALE_FACTOR)) - 1); +} + +static void +re_f0_encode_sound (uint8_t * const p_slot, re_float val) +{ + uint8_t coded_val = RE_F0_INVALID_SOUND; + + if (!isnan (val)) + { + re_clip (&val, RE_F0_SOUND_MIN, RE_F0_SOUND_MAX); + coded_val = (uint8_t) (int) lrintf (val * RE_F0_SOUND_SCALE_FACTOR); + } + + *p_slot = coded_val; +} + +static re_float +re_f0_decode_sound (const uint8_t * const p_slot) +{ + uint8_t coded_val = *p_slot; + + if (RE_F0_INVALID_SOUND == coded_val) + { + return NAN; + } + + return (re_float) ( (re_float) coded_val / RE_F0_SOUND_SCALE_FACTOR); +} + +static void +re_f0_encode_flags (uint8_t * const p_slot, const re_f0_data_t * p_data) +{ + uint8_t flags = ( (p_data->flag_seq_cnt & RE_F0_FLAGS_SEQ_MASK) << + RE_F0_FLAGS_SEQ_OFFSET); + flags |= (p_data->flag_usb_on ? RE_F0_FLAGS_USB_ON : 0U); + flags |= (p_data->flag_low_battery ? RE_F0_FLAGS_LOW_BATTERY : 0U); + flags |= (p_data->flag_calibration_in_progress ? RE_F0_FLAGS_CALIBRATION_IN_PROGRESS : + 0U); + flags |= (p_data->flag_boost_mode ? RE_F0_FLAGS_BOOST_MODE : 0U); + *p_slot = flags; +} + +static void +re_f0_decode_flags (const uint8_t * const p_slot, re_f0_data_t * const p_data) +{ + const uint8_t flags = *p_slot; + p_data->flag_seq_cnt = (flags >> RE_F0_FLAGS_SEQ_OFFSET) & RE_F0_FLAGS_SEQ_MASK; + p_data->flag_usb_on = (flags & RE_F0_FLAGS_USB_ON) ? true : false; + p_data->flag_low_battery = (flags & RE_F0_FLAGS_LOW_BATTERY) ? true : false; + p_data->flag_calibration_in_progress = (flags & RE_F0_FLAGS_CALIBRATION_IN_PROGRESS) ? + true : false; + p_data->flag_boost_mode = (flags & RE_F0_FLAGS_BOOST_MODE) ? true : false; +} + +static void +re_f0_encode_set_address (uint8_t * const p_buffer, const re_f0_data_t * p_data) +{ + // Address is 64 bits, skip 2 first bytes + uint8_t addr_offset = RE_F0_OFFSET_ADDR_MSB; + uint64_t mac = p_data->address; + + if (RE_F0_MAC_MAX < p_data->address) + { + mac = RE_F0_INVALID_MAC; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + + if (RE_F0_MAC_MIN > p_data->address) + { + mac = RE_F0_INVALID_MAC; + } + +#pragma GCC diagnostic pop + p_buffer[addr_offset] = (mac >> RE_F0_BYTE_5_SHIFT) & RE_F0_BYTE_MASK; + addr_offset++; + p_buffer[addr_offset] = (mac >> RE_F0_BYTE_4_SHIFT) & RE_F0_BYTE_MASK; + addr_offset++; + p_buffer[addr_offset] = (mac >> RE_F0_BYTE_3_SHIFT) & RE_F0_BYTE_MASK; + addr_offset++; + p_buffer[addr_offset] = (mac >> RE_F0_BYTE_2_SHIFT) & RE_F0_BYTE_MASK; + addr_offset++; + p_buffer[addr_offset] = (mac >> RE_F0_BYTE_1_SHIFT) & RE_F0_BYTE_MASK; + addr_offset++; + p_buffer[addr_offset] = (mac >> RE_F0_BYTE_0_SHIFT) & RE_F0_BYTE_MASK; +} + +static uint64_t +re_f0_decode_address (const uint8_t * const p_buffer) +{ + // Address is 64 bits, skip 2 first bytes + uint8_t addr_offset = 0; + uint64_t mac = 0; + mac |= p_buffer[addr_offset]; + mac <<= RE_F0_BYTE_1_SHIFT; + addr_offset++; + mac |= p_buffer[addr_offset]; + mac <<= RE_F0_BYTE_1_SHIFT; + addr_offset++; + mac |= p_buffer[addr_offset]; + mac <<= RE_F0_BYTE_1_SHIFT; + addr_offset++; + mac |= p_buffer[addr_offset]; + mac <<= RE_F0_BYTE_1_SHIFT; + addr_offset++; + mac |= p_buffer[addr_offset]; + mac <<= RE_F0_BYTE_1_SHIFT; + addr_offset++; + mac |= p_buffer[addr_offset]; + return mac; +} + +re_status_t +re_f0_encode (uint8_t * const p_buffer, const re_f0_data_t * const p_data) +{ + re_status_t result = RE_SUCCESS; + + if ( (NULL == p_buffer) || (NULL == p_data)) + { + result |= RE_ERROR_NULL; + } + else + { + memset (p_buffer, 0, RE_F0_DATA_LENGTH); + p_buffer[RE_F0_OFFSET_HEADER] = RE_F0_DESTINATION; + re_f0_encode_temperature (&p_buffer[RE_F0_OFFSET_TEMPERATURE], p_data->temperature_c); + re_f0_encode_humidity (&p_buffer[RE_F0_OFFSET_HUMIDITY], p_data->humidity_rh); + re_f0_encode_pressure (&p_buffer[RE_F0_OFFSET_PRESSURE], p_data->pressure_pa); + re_f0_encode_pm (&p_buffer[RE_F0_OFFSET_PM_1_0], p_data->pm1p0_ppm); + re_f0_encode_pm (&p_buffer[RE_F0_OFFSET_PM_2_5], p_data->pm2p5_ppm); + re_f0_encode_pm (&p_buffer[RE_F0_OFFSET_PM_4_0], p_data->pm4p0_ppm); + re_f0_encode_pm (&p_buffer[RE_F0_OFFSET_PM_10_0], p_data->pm10p0_ppm); + re_f0_encode_co2 (&p_buffer[RE_F0_OFFSET_CO2], p_data->co2); + re_f0_encode_voc (&p_buffer[RE_F0_OFFSET_VOC_INDEX], p_data->voc_index); + re_f0_encode_nox (&p_buffer[RE_F0_OFFSET_NOX_INDEX], p_data->nox_index); + re_f0_encode_luminosity (&p_buffer[RE_F0_OFFSET_LUMINOSITY], p_data->luminosity); + re_f0_encode_sound (&p_buffer[RE_F0_OFFSET_SOUND], p_data->sound_dba_avg); + re_f0_encode_flags (&p_buffer[RE_F0_OFFSET_FLAGS], p_data); + re_f0_encode_set_address (p_buffer, p_data); + } + + return result; +} + +bool re_f0_check_format (const uint8_t * const p_buffer) +{ + if (NULL == p_buffer) + { + return false; + } + + if (RE_F0_RAW_PACKET_ADV_DATA_FLAGS_LEN_VAL != + p_buffer[RE_F0_RAW_PACKET_ADV_DATA_FLAGS_LEN_OFFSET]) + { + return false; + } + + if (RE_F0_RAW_PACKET_ADV_DATA_FLAGS_DATA_TYPE_VAL != + p_buffer[RE_F0_RAW_PACKET_ADV_DATA_FLAGS_DATA_TYPE_OFFSET]) + { + return false; + } + + if (RE_F0_RAW_PACKET_ADV_DATA_UUID16_LEN_VAL != + p_buffer[RE_F0_RAW_PACKET_ADV_DATA_UUID16_LEN_OFFSET]) + { + return false; + } + + if (RE_F0_RAW_PACKET_ADV_DATA_UUID16_DATA_TYPE_VAL != + p_buffer[RE_F0_RAW_PACKET_ADV_DATA_UUID16_DATA_TYPE_OFFSET]) + { + return false; + } + + const uint16_t service_uuid = (uint16_t) ( (uint16_t) + p_buffer[RE_F0_RAW_PACKET_ADV_DATA_UUID16_BYTE2_OFFSET] << RE_F0_BYTE_1_SHIFT) + + p_buffer[RE_F0_RAW_PACKET_ADV_DATA_UUID16_BYTE1_OFFSET]; + + if (RE_F0_RAW_PACKET_ADV_DATA_UUID16_VAL != service_uuid) + { + return false; + } + + if (RE_F0_RAW_PACKET_MANUFACTURER_DATA_LENGTH_VAL != + p_buffer[RE_F0_RAW_PACKET_MANUFACTURER_DATA_LENGTH_OFFSET]) + { + return false; + } + + if (RE_F0_RAW_PACKET_MANUFACTURER_DATA_TYPE_VAL != + p_buffer[RE_F0_RAW_PACKET_MANUFACTURER_DATA_TYPE_OFFSET]) + { + return false; + } + + const uint16_t manufacturer_id = (uint16_t) ( (uint16_t) + p_buffer[RE_F0_RAW_PACKET_MANUFACTURER_ID_OFFSET_HI] << RE_F0_BYTE_1_SHIFT) + + p_buffer[RE_F0_RAW_PACKET_MANUFACTURER_ID_OFFSET_LO]; + + if (RE_F0_RAW_PACKET_MANUFACTURER_ID_VAL != manufacturer_id) + { + return false; + } + + if (RE_F0_DESTINATION != p_buffer[RE_F0_OFFSET_PAYLOAD + RE_F0_OFFSET_HEADER]) + { + return false; + } + + return true; +} + +re_status_t re_f0_decode (const uint8_t * const p_buffer, re_f0_data_t * const p_data) +{ + const uint8_t * const p_payload = &p_buffer[RE_F0_OFFSET_PAYLOAD]; + re_status_t result = RE_SUCCESS; + + if ( (NULL == p_payload) || (NULL == p_data)) + { + return RE_ERROR_NULL; + } + + memset (p_data, 0, sizeof (*p_data)); + + if (RE_F0_DESTINATION != p_payload[RE_F0_OFFSET_HEADER]) + { + return RE_ERROR_INVALID_PARAM; + } + + p_data->temperature_c = re_f0_decode_temperature (&p_payload[RE_F0_OFFSET_TEMPERATURE]); + p_data->humidity_rh = re_f0_decode_humidity (&p_payload[RE_F0_OFFSET_HUMIDITY]); + p_data->pressure_pa = re_f0_decode_pressure (&p_payload[RE_F0_OFFSET_PRESSURE]); + p_data->pm1p0_ppm = re_f0_decode_pm (&p_payload[RE_F0_OFFSET_PM_1_0]); + p_data->pm2p5_ppm = re_f0_decode_pm (&p_payload[RE_F0_OFFSET_PM_2_5]); + p_data->pm4p0_ppm = re_f0_decode_pm (&p_payload[RE_F0_OFFSET_PM_4_0]); + p_data->pm10p0_ppm = re_f0_decode_pm (&p_payload[RE_F0_OFFSET_PM_10_0]); + p_data->co2 = re_f0_decode_co2 (&p_payload[RE_F0_OFFSET_CO2]); + p_data->voc_index = re_f0_decode_voc (&p_payload[RE_F0_OFFSET_VOC_INDEX]); + p_data->nox_index = re_f0_decode_nox (&p_payload[RE_F0_OFFSET_NOX_INDEX]); + p_data->luminosity = re_f0_decode_luminosity (&p_payload[RE_F0_OFFSET_LUMINOSITY]); + p_data->sound_dba_avg = re_f0_decode_sound (&p_payload[RE_F0_OFFSET_SOUND]); + re_f0_decode_flags (&p_payload[RE_F0_OFFSET_FLAGS], p_data); + p_data->address = re_f0_decode_address (&p_payload[RE_F0_OFFSET_ADDR_MSB]); + return result; +} + +re_f0_data_t +re_f0_data_invalid (const uint8_t measurement_cnt, const uint64_t radio_mac) +{ + const re_f0_data_t data = + { + .temperature_c = NAN, + .humidity_rh = NAN, + .pressure_pa = NAN, + .pm1p0_ppm = NAN, + .pm2p5_ppm = NAN, + .pm4p0_ppm = NAN, + .pm10p0_ppm = NAN, + .co2 = NAN, + .voc_index = NAN, + .nox_index = NAN, + .luminosity = NAN, + .sound_dba_avg = NAN, + .flag_seq_cnt = measurement_cnt & RE_F0_FLAGS_SEQ_MASK, + .flag_usb_on = false, + .flag_low_battery = false, + .flag_calibration_in_progress = false, + .flag_boost_mode = false, + .address = radio_mac, + }; + return data; +} + +#endif diff --git a/src/ruuvi_endpoint_f0.h b/src/ruuvi_endpoint_f0.h new file mode 100644 index 0000000..316d78e --- /dev/null +++ b/src/ruuvi_endpoint_f0.h @@ -0,0 +1,167 @@ +/** + * Ruuvi Endpoint 0xF0 helper. + * Defines necessary data for creating a Ruuvi data format 0xF0 broadcast. + * + * License: BSD-3 + * Author: TheSomeMan + */ + +#ifndef RUUVI_ENDPOINT_F0_H +#define RUUVI_ENDPOINT_F0_H + +#include "ruuvi_endpoints.h" +#include + +#define RE_F0_DESTINATION (0xF0U) + +#define RE_F0_INVALID_TEMPERATURE (0x80U) +#define RE_F0_INVALID_HUMIDITY (0xFFU) +#define RE_F0_INVALID_PRESSURE (0xFFU) +#define RE_F0_INVALID_PM (0xFFU) +#define RE_F0_INVALID_CO2 (0xFFU) +#define RE_F0_INVALID_VOC_INDEX (0xFFU) +#define RE_F0_INVALID_NOX_INDEX (0xFFU) +#define RE_F0_INVALID_LUMINOSITY (0xFFU) +#define RE_F0_INVALID_SOUND (0xFFU) +#define RE_F0_INVALID_MAC (0xFFFFFFFFFFFFU) + +#define RE_F0_FLAGS_SEQ_MASK (0x0FU) +#define RE_F0_FLAGS_SEQ_OFFSET (4U) +#define RE_F0_FLAGS_SEQ_INVALID (0x0FU) +#define RE_F0_FLAGS_USB_ON (0x01U) +#define RE_F0_FLAGS_LOW_BATTERY (0x02U) +#define RE_F0_FLAGS_CALIBRATION_IN_PROGRESS (0x04U) +#define RE_F0_FLAGS_BOOST_MODE (0x08U) + +#define RE_F0_DATA_LENGTH (20U) + +#define RE_F0_TEMPERATURE_MIN (-126.0f) +#define RE_F0_TEMPERATURE_MAX (126.0f) +#define RE_F0_TEMPERATURE_SCALE_FACTOR (1) + +#define RE_F0_HUMIDITY_MIN (0.0f) +#define RE_F0_HUMIDITY_MAX (100.0f) +#define RE_F0_HUMIDITY_SCALE_FACTOR (2) + +#define RE_F0_PRESSURE_MIN (90000.0f) +#define RE_F0_PRESSURE_MAX (115400.0f) +#define RE_F0_PRESSURE_SCALE_FACTOR (100) + +#define RE_F0_PM_MIN (0.0f) +#define RE_F0_PM_MAX (1000.0f) +#define RE_F0_PM_SCALE_FACTOR (254.0f / logf(RE_F0_PM_MAX + 1)) + +#define RE_F0_CO2_MIN (0.0f) +#define RE_F0_CO2_MAX (40000.0f) +#define RE_F0_CO2_SCALE_FACTOR (254.0f / logf(RE_F0_CO2_MAX + 1)) + +#define RE_F0_VOC_INDEX_MIN (1.0f) +#define RE_F0_VOC_INDEX_MAX (500.0f) +#define RE_F0_VOC_SCALE_FACTOR (254.0f / logf(RE_F0_VOC_INDEX_MAX)) + +#define RE_F0_NOX_INDEX_MIN (1.0f) +#define RE_F0_NOX_INDEX_MAX (500.0f) +#define RE_F0_NOX_SCALE_FACTOR (254.0f / logf(RE_F0_NOX_INDEX_MAX)) + +#define RE_F0_LUMINOSITY_MIN (0.0f) +#define RE_F0_LUMINOSITY_MAX (40000.0f) +#define RE_F0_LUMINOSITY_SCALE_FACTOR (254.0f / logf(RE_F0_LUMINOSITY_MAX + 1)) + +#define RE_F0_SOUND_MIN (0.0f) +#define RE_F0_SOUND_MAX (127.0f) +#define RE_F0_SOUND_SCALE_FACTOR (2.0f) + +#define RE_F0_OFFSET_PAYLOAD (11U) + +#define RE_F0_OFFSET_HEADER (0U) +#define RE_F0_OFFSET_TEMPERATURE (1U) +#define RE_F0_OFFSET_HUMIDITY (2U) +#define RE_F0_OFFSET_PRESSURE (3U) +#define RE_F0_OFFSET_PM_1_0 (4U) +#define RE_F0_OFFSET_PM_2_5 (5U) +#define RE_F0_OFFSET_PM_4_0 (6U) +#define RE_F0_OFFSET_PM_10_0 (7U) +#define RE_F0_OFFSET_CO2 (8U) +#define RE_F0_OFFSET_VOC_INDEX (9U) +#define RE_F0_OFFSET_NOX_INDEX (10U) +#define RE_F0_OFFSET_LUMINOSITY (11U) +#define RE_F0_OFFSET_SOUND (12U) +#define RE_F0_OFFSET_FLAGS (13U) +#define RE_F0_OFFSET_ADDR_MSB (14U) + +/** @brief All data required for Ruuvi dataformat 0xF0 package. */ +typedef struct +{ + re_float temperature_c; //!< Temperature in degrees Celsius. + re_float humidity_rh; //!< Humidity in relative humidity percentage. + re_float pressure_pa; //!< Pressure in Pa. + re_float pm1p0_ppm; //!< Particulate matter mass concentration PM1.0 in micrograms/m3. + re_float pm2p5_ppm; //!< Particulate matter mass concentration PM2.5 in micrograms/m3. + re_float pm4p0_ppm; //!< Particulate matter mass concentration PM4.0 in micrograms/m3. + re_float pm10p0_ppm; //!< Particulate matter mass concentration PM10.0 in micrograms/m3. + re_float co2; //!< CO2 concentration in ppm + re_float voc_index; //!< VOC index points. + re_float nox_index; //!< NOx index points. + re_float luminosity; //!< Luminosity. + re_float sound_dba_avg; //!< Sound dBA avg. + + bool flag_usb_on : 1; //!< Flag: USB ON + bool flag_low_battery : 1; //!< Flag: LOW BATTERY + bool flag_calibration_in_progress : 1; //!< Flag: Calibration in progress + bool flag_boost_mode : 1; //!< Flag: Boost mode + uint8_t flag_seq_cnt : 4; //!< Flag: Sequence counter + + uint64_t address; //!< BLE address of device, most significant byte first. +} re_f0_data_t; + +/** + * @brief Encode given data to given buffer in Ruuvi DFxF0. + * + * NAN can be used as a placeholder for invalid / not available values. + * + * @param[out] buffer uint8_t array with length of 24 bytes. + * @param[in] data Struct containing all necessary information + * for encoding the data into buffer. + * @retval RE_SUCCESS if data was encoded successfully. + */ +re_status_t +re_f0_encode (uint8_t * const buffer, const re_f0_data_t * data); + +/** + * @brief Checks if the provided buffer conforms to the Ruuvi DFxFE format. + * + * This function examines the input buffer to determine if its content + * represents data in the Ruuvi DFxFE format. + * + * @param[in] p_buffer Pointer to a uint8_t input array with a length of 31 bytes to be checked. + * @return Returns 'true' if the buffer format is Ruuvi DFxFE, 'false' otherwise. + */ +bool +re_f0_check_format (const uint8_t * const p_buffer); + +/** + * @brief Decodes a given buffer using the Ruuvi DFxFE format. + * + * Ruuvi DFxFE is a data format used by the Ruuvi AirQ environmental sensor. + * + * @param[in] p_buffer Pointer to a uint8_t input array with a length of 31 bytes + * representing a Bluetooth frame with Ruuvi DFxFE formatted payload. + * @param[out] p_data Pointer to a re_f0_data_t struct. + * After the function executes, this struct will contain the data decoded from the input buffer. + * @return Returns RE_SUCCESS if the data was decoded successfully, + * i.e., if the input buffer was a valid Ruuvi DFxFE buffer and `p_data` now points to a valid `re_f0_data_t` object. + * If the decoding fails, the function returns a code indicating the type of error occurred. + */ +re_status_t +re_f0_decode (const uint8_t * const p_buffer, re_f0_data_t * const p_data); + +/** + * @brief Create invalid Ruuvi DFxFE data. + * @param measurement_cnt Running counter of measurement. + * @param radio_mac BLE address of device. + * @return /ref re_f0_data_t with all values set to NAN. + */ +re_f0_data_t +re_f0_data_invalid (const uint8_t measurement_cnt, const uint64_t radio_mac); + +#endif diff --git a/test/test_ruuvi_endpoint_6.c b/test/test_ruuvi_endpoint_6.c index f2fd58b..418a82a 100644 --- a/test/test_ruuvi_endpoint_6.c +++ b/test/test_ruuvi_endpoint_6.c @@ -368,20 +368,6 @@ void test_re_6_data_invalid (void) const uint16_t measurement_cnt = 123; const uint64_t radio_mac = 0xCBB8334C884FULL; const re_6_data_t data = re_6_data_invalid (measurement_cnt, radio_mac); - static const re_6_data_t m_re_6_data_invalid = - { - .pm1p0_ppm = NAN, - .pm2p5_ppm = NAN, - .pm4p0_ppm = NAN, - .pm10p0_ppm = NAN, - .co2 = NAN, - .humidity_rh = NAN, - .voc_index = NAN, - .nox_index = NAN, - .temperature_c = NAN, - .measurement_count = 65535, - .address = 0xFFFFFFFFFFFF, - }; TEST_ASSERT_EQUAL (data.measurement_count, measurement_cnt); TEST_ASSERT_EQUAL (data.address, radio_mac); TEST_ASSERT_TRUE (isnan (data.pm1p0_ppm)); diff --git a/test/test_ruuvi_endpoint_e0.c b/test/test_ruuvi_endpoint_e0.c new file mode 100644 index 0000000..fbde47c --- /dev/null +++ b/test/test_ruuvi_endpoint_e0.c @@ -0,0 +1,608 @@ +#include "unity.h" + +#include "ruuvi_endpoint_e0.h" +#include +#include + +void setUp (void) +{ +} + +void tearDown (void) +{ +} + +/** + * @brief Typical encode operation + * + * @retval RD_SUCCESS on success test. + */ +void test_ruuvi_endpoint_e0_get_ok (void) +{ + static const re_e0_data_t m_re_e0_data_ok = + { + .temperature_c = 26.5f, + .humidity_rh = 80.5f, + .pressure_pa = 101355, + .pm1p0_ppm = 10.2f, + .pm2p5_ppm = 11.3f, + .pm4p0_ppm = 12.4f, + .pm10p0_ppm = 13.5f, + .co2 = 1129, + .voc_index = 11, + .nox_index = 12, + .luminosity = 15123, + .sound_dba_avg = 20.5f, + .sound_dba_peak = 21.0f, + .measurement_count = 65533, + .voltage = 3.2f, + .flag_usb_on = true, + .flag_low_battery = false, + .flag_calibration_in_progress = false, + .flag_boost_mode = false, + .address = 0xCBB8334C884F, + }; + static const uint8_t valid_data[] = + { + 0xE0, + 5300U >> 8U, 5300U & 0xFFU, // Temperature + 32200U >> 8U, 32200U & 0xFFU, // Humidity + 51355U >> 8U, 51355U & 0xFFU, // Pressure + 102U >> 8U, 102U & 0xFFU, // PM1.0 + 113U >> 8U, 113U & 0xFFU, // PM2.5 + 124U >> 8U, 124U & 0xFFU, // PM4.0 + 135U >> 8U, 135U & 0xFFU, // PM10.0 + 1129U >> 8U, 1129U & 0xFFU, // CO2 + 11U >> 8U, 11U & 0xFFU, // VOX + 12U >> 8U, 12U & 0xFFU, // NOX + 15123U >> 8U, 15123U & 0xFFU, // Luminosity + 41, // Sound avg + 42, // Sound peak + 65533U >> 8U, 65533U & 0xFFU, // Measurement count + 107, // Voltage + 0x01, // Flags + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xCB, 0xB8, 0x33, 0x4C, 0x88, 0x4F + }; + _Static_assert (sizeof (valid_data) == RE_E0_DATA_LENGTH); + re_status_t err_code = RE_SUCCESS; + uint8_t test_buffer[40] = {0}; + _Static_assert (sizeof (valid_data) == sizeof (test_buffer), "Invalid test data size"); + err_code = re_e0_encode (test_buffer, &m_re_e0_data_ok); + TEST_ASSERT_EQUAL (RE_SUCCESS, err_code); + TEST_ASSERT_EQUAL_HEX8_ARRAY (valid_data, test_buffer, sizeof (valid_data)); + const uint8_t raw_buf_prefix[] = {0x2B, 0xFF, 0x99, 0x04}; + _Static_assert (sizeof (raw_buf_prefix) == RE_E0_OFFSET_PAYLOAD, + "Invalid raw_buf_prefix size"); + uint8_t raw_buf[48]; + memcpy (&raw_buf[0], raw_buf_prefix, sizeof (raw_buf_prefix)); + memcpy (&raw_buf[RE_E0_OFFSET_PAYLOAD], test_buffer, sizeof (test_buffer)); + re_e0_data_t decoded_data = {0}; + TEST_ASSERT_EQUAL (RE_SUCCESS, re_e0_decode (raw_buf, &decoded_data)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok.temperature_c * 10.0f), + lrintf (decoded_data.temperature_c * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok.humidity_rh * 10.0f), + lrintf (decoded_data.humidity_rh * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok.pressure_pa * 10.0f), + lrintf (decoded_data.pressure_pa * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok.pm1p0_ppm * 10.0f), + lrintf (decoded_data.pm1p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok.pm2p5_ppm * 10.0f), + lrintf (decoded_data.pm2p5_ppm * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok.pm4p0_ppm * 10.0f), + lrintf (decoded_data.pm4p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok.pm10p0_ppm * 10.0f), + lrintf (decoded_data.pm10p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok.co2), lrintf (decoded_data.co2)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok.voc_index), lrintf (decoded_data.voc_index)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok.nox_index), lrintf (decoded_data.nox_index)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok.luminosity), lrintf (decoded_data.luminosity)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok.sound_dba_avg * 10.0f), + lrintf (decoded_data.sound_dba_avg * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok.sound_dba_peak * 10.0f), + lrintf (decoded_data.sound_dba_peak * 10.0f)); + TEST_ASSERT_EQUAL (m_re_e0_data_ok.measurement_count, decoded_data.measurement_count); + TEST_ASSERT_EQUAL (321 /* 3.20 */, lrintf (decoded_data.voltage * 100.0f)); + TEST_ASSERT_EQUAL (m_re_e0_data_ok.flag_usb_on, decoded_data.flag_usb_on); + TEST_ASSERT_EQUAL (m_re_e0_data_ok.flag_low_battery, decoded_data.flag_low_battery); + TEST_ASSERT_EQUAL (m_re_e0_data_ok.flag_calibration_in_progress, + decoded_data.flag_calibration_in_progress); + TEST_ASSERT_EQUAL (m_re_e0_data_ok.flag_boost_mode, decoded_data.flag_boost_mode); + TEST_ASSERT_EQUAL (m_re_e0_data_ok.address, decoded_data.address); +} + +void test_ruuvi_endpoint_e0_get_ok_max (void) +{ + static const re_e0_data_t m_re_e0_data_ok_max = + { + .temperature_c = 126.0f, + .humidity_rh = 100.0f, + .pressure_pa = 115534, + .pm1p0_ppm = 1000.0f, + .pm2p5_ppm = 1000.0f, + .pm4p0_ppm = 1000.0f, + .pm10p0_ppm = 1000.0f, + .co2 = 40000, + .voc_index = 500, + .nox_index = 500, + .luminosity = 65534, + .sound_dba_avg = 127.0f, + .sound_dba_peak = 127.0f, + .measurement_count = 65534, + .voltage = 7.62f, + .flag_usb_on = true, + .flag_low_battery = true, + .flag_calibration_in_progress = true, + .flag_boost_mode = true, + .address = 0xCBB8334C884F, + }; + static const uint8_t max_data[] = + { + 0xE0, + 25200U >> 8U, 25200U & 0xFFU, // Temperature + 40000U >> 8U, 40000U & 0xFFU, // Humidity + 65534U >> 8U, 65534U & 0xFFU, // Pressure + 10000U >> 8U, 10000U & 0xFFU, // PM1.0 + 10000U >> 8U, 10000U & 0xFFU, // PM2.5 + 10000U >> 8U, 10000U & 0xFFU, // PM4.0 + 10000U >> 8U, 10000U & 0xFFU, // PM10.0 + 40000U >> 8U, 40000U & 0xFFU, // CO2 + 500U >> 8U, 500U & 0xFFU, // VOX + 500U >> 8U, 500U & 0xFFU, // NOX + 65534U >> 8U, 65534U & 0xFFU, // Luminosity + 254U, // Sound avg + 254U, // Sound peak + 65534U >> 8U, 65534U & 0xFFU, // Measurement count + 254, // Voltage + 0x0F, // Flags + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xCB, 0xB8, 0x33, 0x4C, 0x88, 0x4F + }; + _Static_assert (sizeof (max_data) == RE_E0_DATA_LENGTH); + re_status_t err_code = RE_SUCCESS; + uint8_t test_buffer[40] = {0}; + _Static_assert (sizeof (max_data) == sizeof (test_buffer), "Invalid test data size"); + err_code = re_e0_encode ( (uint8_t * const) &test_buffer, &m_re_e0_data_ok_max); + TEST_ASSERT (RE_SUCCESS == err_code); + TEST_ASSERT_EQUAL_HEX8_ARRAY (max_data, test_buffer, sizeof (max_data)); + const uint8_t raw_buf_prefix[] = {0x2B, 0xFF, 0x99, 0x04}; + _Static_assert (sizeof (raw_buf_prefix) == RE_E0_OFFSET_PAYLOAD, + "Invalid raw_buf_prefix size"); + uint8_t raw_buf[48]; + memcpy (&raw_buf[0], raw_buf_prefix, sizeof (raw_buf_prefix)); + memcpy (&raw_buf[RE_E0_OFFSET_PAYLOAD], test_buffer, sizeof (test_buffer)); + re_e0_data_t decoded_data = {0}; + TEST_ASSERT_EQUAL (RE_SUCCESS, re_e0_decode (raw_buf, &decoded_data)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_max.temperature_c * 10.0f), + lrintf (decoded_data.temperature_c * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_max.humidity_rh * 10.0f), + lrintf (decoded_data.humidity_rh * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_max.pressure_pa * 10.0f), + lrintf (decoded_data.pressure_pa * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_max.pm1p0_ppm * 10.0f), + lrintf (decoded_data.pm1p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_max.pm2p5_ppm * 10.0f), + lrintf (decoded_data.pm2p5_ppm * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_max.pm4p0_ppm * 10.0f), + lrintf (decoded_data.pm4p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_max.pm10p0_ppm * 10.0f), + lrintf (decoded_data.pm10p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_max.co2), lrintf (decoded_data.co2)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_max.voc_index), + lrintf (decoded_data.voc_index)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_max.nox_index), + lrintf (decoded_data.nox_index)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_max.luminosity), + lrintf (decoded_data.luminosity)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_max.sound_dba_avg * 10.0f), + lrintf (decoded_data.sound_dba_avg * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_max.sound_dba_peak * 10.0f), + lrintf (decoded_data.sound_dba_peak * 10.0f)); + TEST_ASSERT_EQUAL (m_re_e0_data_ok_max.measurement_count, decoded_data.measurement_count); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_max.voltage * 100.0f), + lrintf (decoded_data.voltage * 100.0f)); + TEST_ASSERT_EQUAL (m_re_e0_data_ok_max.flag_usb_on, decoded_data.flag_usb_on); + TEST_ASSERT_EQUAL (m_re_e0_data_ok_max.flag_low_battery, decoded_data.flag_low_battery); + TEST_ASSERT_EQUAL (m_re_e0_data_ok_max.flag_calibration_in_progress, + decoded_data.flag_calibration_in_progress); + TEST_ASSERT_EQUAL (m_re_e0_data_ok_max.flag_boost_mode, decoded_data.flag_boost_mode); + TEST_ASSERT_EQUAL (m_re_e0_data_ok_max.address, decoded_data.address); +} + +void test_ruuvi_endpoint_e0_get_ok_min (void) +{ + static const re_e0_data_t m_re_e0_data_ok_min = + { + .temperature_c = -126.0, + .humidity_rh = 0, + .pressure_pa = 50000, + .pm1p0_ppm = 0, + .pm2p5_ppm = 0, + .pm4p0_ppm = 0, + .pm10p0_ppm = 0, + .co2 = 0, + .voc_index = 1, + .nox_index = 1, + .luminosity = 0, + .sound_dba_avg = 0, + .sound_dba_peak = 0, + .measurement_count = 0, + .voltage = 0.0f, + .flag_usb_on = false, + .flag_low_battery = false, + .flag_calibration_in_progress = false, + .flag_boost_mode = false, + .address = 0xCBB8334C884F, + }; + static const uint8_t min_data[] = + { + 0xE0, + 40336U >> 8U, 40336U & 0xFFU, // Temperature + 0U >> 8U, 0U & 0xFFU, // Humidity + 0U >> 8U, 0U & 0xFFU, // Pressure + 0U >> 8U, 0U & 0xFFU, // PM1.0 + 0U >> 8U, 0U & 0xFFU, // PM2.5 + 0U >> 8U, 0U & 0xFFU, // PM4.0 + 0U >> 8U, 0U & 0xFFU, // PM10.0 + 0U >> 8U, 0U & 0xFFU, // CO2 + 1U >> 8U, 1U & 0xFFU, // VOX + 1U >> 8U, 1U & 0xFFU, // NOX + 0U >> 8U, 0U & 0xFFU, // Luminosity + 0U, // Sound avg + 0U, // Sound peak + 0U >> 8U, 0U & 0xFFU, // Measurement count + 0, // Voltage + 0x00, // Flags + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xCB, 0xB8, 0x33, 0x4C, 0x88, 0x4F + }; + _Static_assert (sizeof (min_data) == RE_E0_DATA_LENGTH); + re_status_t err_code = RE_SUCCESS; + uint8_t test_buffer[40] = {0}; + _Static_assert (sizeof (min_data) == sizeof (test_buffer), "Invalid test data size"); + err_code = re_e0_encode ( (uint8_t * const) &test_buffer, &m_re_e0_data_ok_min); + TEST_ASSERT (RE_SUCCESS == err_code); + TEST_ASSERT_EQUAL_HEX8_ARRAY (min_data, test_buffer, sizeof (min_data)); + uint8_t raw_buf[48]; + const uint8_t raw_buf_prefix[] = {0x2B, 0xFF, 0x99, 0x04}; + _Static_assert (sizeof (raw_buf_prefix) == RE_E0_OFFSET_PAYLOAD, + "Invalid raw_buf_prefix size"); + memcpy (&raw_buf[0], raw_buf_prefix, sizeof (raw_buf_prefix)); + memcpy (&raw_buf[RE_E0_OFFSET_PAYLOAD], test_buffer, sizeof (test_buffer)); + re_e0_data_t decoded_data = {0}; + TEST_ASSERT_EQUAL (RE_SUCCESS, re_e0_decode (raw_buf, &decoded_data)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_min.temperature_c * 10.0f), + lrintf (decoded_data.temperature_c * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_min.humidity_rh * 10.0f), + lrintf (decoded_data.humidity_rh * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_min.pressure_pa * 10.0f), + lrintf (decoded_data.pressure_pa * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_min.pm1p0_ppm * 10.0f), + lrintf (decoded_data.pm1p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_min.pm2p5_ppm * 10.0f), + lrintf (decoded_data.pm2p5_ppm * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_min.pm4p0_ppm * 10.0f), + lrintf (decoded_data.pm4p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_min.pm10p0_ppm * 10.0f), + lrintf (decoded_data.pm10p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_min.co2), lrintf (decoded_data.co2)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_min.voc_index), + lrintf (decoded_data.voc_index)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_min.nox_index), + lrintf (decoded_data.nox_index)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_min.luminosity), + lrintf (decoded_data.luminosity)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_min.sound_dba_avg * 10.0f), + lrintf (decoded_data.sound_dba_avg * 10.0f)); + TEST_ASSERT_EQUAL (lrintf (m_re_e0_data_ok_min.sound_dba_peak * 10.0f), + lrintf (decoded_data.sound_dba_peak * 10.0f)); + TEST_ASSERT_EQUAL (m_re_e0_data_ok_min.measurement_count, decoded_data.measurement_count); + TEST_ASSERT_EQUAL (0.0f, lrintf (decoded_data.voltage * 100.0f)); + TEST_ASSERT_EQUAL (m_re_e0_data_ok_min.flag_usb_on, decoded_data.flag_usb_on); + TEST_ASSERT_EQUAL (m_re_e0_data_ok_min.flag_low_battery, decoded_data.flag_low_battery); + TEST_ASSERT_EQUAL (m_re_e0_data_ok_min.flag_calibration_in_progress, + decoded_data.flag_calibration_in_progress); + TEST_ASSERT_EQUAL (m_re_e0_data_ok_min.flag_boost_mode, decoded_data.flag_boost_mode); + TEST_ASSERT_EQUAL (m_re_e0_data_ok_min.address, decoded_data.address); +} + +/** + * @brief Null buffer + * + * @retval RE_ERROR_NULL on success test. + */ +void test_ruuvi_endpoint_e0_get_error_null_buffer (void) +{ + static const re_e0_data_t m_re_e0_data_ok = + { + .temperature_c = 26.5f, + .humidity_rh = 80.5f, + .pressure_pa = 101355, + .pm1p0_ppm = 10.2f, + .pm2p5_ppm = 11.3f, + .pm4p0_ppm = 12.4f, + .pm10p0_ppm = 13.5f, + .co2 = 1129, + .voc_index = 11, + .nox_index = 12, + .luminosity = 15123, + .sound_dba_avg = 20.5f, + .sound_dba_peak = 21.0f, + .measurement_count = 65533, + .voltage = 3.2f, + .flag_usb_on = true, + .flag_low_battery = false, + .flag_calibration_in_progress = false, + .flag_boost_mode = false, + .address = 0xCBB8334C884F, + }; + re_status_t err_code = RE_ERROR_NULL; + uint8_t * const p_test_buffer = NULL; + err_code = re_e0_encode (p_test_buffer, &m_re_e0_data_ok); + TEST_ASSERT (RE_ERROR_NULL == err_code); +} + +/** + * @brief Null data + * + * @retval RE_ERROR_NULL on success test. + */ +void test_ruuvi_endpoint_e0_get_error_null_data (void) +{ + re_status_t err_code = RE_ERROR_NULL; + uint8_t test_buffer[40] = {0}; + const re_e0_data_t * p_re_e0_data = NULL; + err_code = re_e0_encode ( (uint8_t * const) &test_buffer, p_re_e0_data); + TEST_ASSERT (RE_ERROR_NULL == err_code); +} + +/** + * @brief True to test encode operation with invalid data + * + * @retval RD_SUCCESS on success test. + */ +void test_ruuvi_endpoint_e0_get_invalid_data (void) +{ + static const re_e0_data_t m_re_e0_data_invalid = + { + .temperature_c = NAN, + .humidity_rh = NAN, + .pressure_pa = NAN, + .pm1p0_ppm = NAN, + .pm2p5_ppm = NAN, + .pm4p0_ppm = NAN, + .pm10p0_ppm = NAN, + .co2 = NAN, + .voc_index = NAN, + .nox_index = NAN, + .luminosity = NAN, + .sound_dba_avg = NAN, + .sound_dba_peak = NAN, + .measurement_count = RE_E0_INVALID_SEQUENCE, + .voltage = NAN, + .flag_usb_on = false, + .flag_low_battery = false, + .flag_calibration_in_progress = false, + .flag_boost_mode = false, + .address = 0xFFFFFFFFFFFF, + }; + static const uint8_t invalid_data[] = + { + 0xE0, + 0x8000U >> 8U, 0x8000U & 0xFFU, // Temperature + 0xFFFFU >> 8U, 0xFFFFU & 0xFFU, // Humidity + 0xFFFFU >> 8U, 0xFFFFU & 0xFFU, // Pressure + 0xFFFFU >> 8U, 0xFFFFU & 0xFFU, // PM1.0 + 0xFFFFU >> 8U, 0xFFFFU & 0xFFU, // PM2.5 + 0xFFFFU >> 8U, 0xFFFFU & 0xFFU, // PM4.0 + 0xFFFFU >> 8U, 0xFFFFU & 0xFFU, // PM10.0 + 0xFFFFU >> 8U, 0xFFFFU & 0xFFU, // CO2 + 511U >> 8U, 511U & 0xFFU, // VOX + 511U >> 8U, 511U & 0xFFU, // NOX + 0xFFFFU >> 8U, 0xFFFFU & 0xFFU, // Luminosity + 0xFFU, // Sound avg + 0xFFU, // Sound peak + 0xFFFFU >> 8U, 0xFFFFU & 0xFFU, // Measurement count + 0xFFU, // Voltage + 0x00, // Flags + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + _Static_assert (sizeof (invalid_data) == RE_E0_DATA_LENGTH); + re_status_t err_code = RE_SUCCESS; + uint8_t test_buffer[40] = {0}; + err_code = re_e0_encode ( (uint8_t * const) &test_buffer, &m_re_e0_data_invalid); + TEST_ASSERT (RE_SUCCESS == err_code); + TEST_ASSERT_EQUAL_HEX8_ARRAY (invalid_data, test_buffer, sizeof (invalid_data)); + const uint8_t raw_buf_prefix[] = {0x2B, 0xFF, 0x99, 0x04}; + _Static_assert (sizeof (raw_buf_prefix) == RE_E0_OFFSET_PAYLOAD, + "Invalid raw_buf_prefix size"); + uint8_t raw_buf[48]; + memcpy (&raw_buf[0], raw_buf_prefix, sizeof (raw_buf_prefix)); + memcpy (&raw_buf[RE_E0_OFFSET_PAYLOAD], test_buffer, sizeof (test_buffer)); + re_e0_data_t decoded_data = {0}; + TEST_ASSERT_EQUAL (RE_SUCCESS, re_e0_decode (raw_buf, &decoded_data)); + TEST_ASSERT (isnan (decoded_data.temperature_c)); + TEST_ASSERT (isnan (decoded_data.humidity_rh)); + TEST_ASSERT (isnan (decoded_data.pressure_pa)); + TEST_ASSERT (isnan (decoded_data.pm1p0_ppm)); + TEST_ASSERT (isnan (decoded_data.pm1p0_ppm)); + TEST_ASSERT (isnan (decoded_data.pm1p0_ppm)); + TEST_ASSERT (isnan (decoded_data.pm1p0_ppm)); + TEST_ASSERT (isnan (decoded_data.co2)); + TEST_ASSERT (isnan (decoded_data.voc_index)); + TEST_ASSERT (isnan (decoded_data.nox_index)); + TEST_ASSERT (isnan (decoded_data.luminosity)); + TEST_ASSERT (isnan (decoded_data.sound_dba_avg)); + TEST_ASSERT (isnan (decoded_data.sound_dba_peak)); + TEST_ASSERT_EQUAL (m_re_e0_data_invalid.measurement_count, + decoded_data.measurement_count); + TEST_ASSERT (isnan (decoded_data.voltage)); + TEST_ASSERT_EQUAL (m_re_e0_data_invalid.flag_usb_on, decoded_data.flag_usb_on); + TEST_ASSERT_EQUAL (m_re_e0_data_invalid.flag_low_battery, decoded_data.flag_low_battery); + TEST_ASSERT_EQUAL (m_re_e0_data_invalid.flag_calibration_in_progress, + decoded_data.flag_calibration_in_progress); + TEST_ASSERT_EQUAL (m_re_e0_data_invalid.flag_boost_mode, decoded_data.flag_boost_mode); + TEST_ASSERT_EQUAL (m_re_e0_data_invalid.address, decoded_data.address); +} + +void test_ruuvi_endpoint_e0_underflow (void) +{ + static const re_e0_data_t m_re_fe_data_underflow = + { + .temperature_c = -126.1, + .humidity_rh = -1, + .pressure_pa = 49999, + .pm1p0_ppm = -1, + .pm2p5_ppm = -1, + .pm4p0_ppm = -1, + .pm10p0_ppm = -1, + .co2 = -1, + .voc_index = 0, + .nox_index = 0, + .luminosity = -1, + .sound_dba_avg = -1, + .sound_dba_peak = -1, + .measurement_count = 0, + .voltage = -0.1f, + .flag_usb_on = false, + .flag_low_battery = false, + .flag_calibration_in_progress = false, + .flag_boost_mode = false, + .address = 0xCBB8334C884F, + }; + static const uint8_t min_data[] = + { + 0xE0, + 40336U >> 8U, 40336U & 0xFFU, // Temperature + 0U >> 8U, 0U & 0xFFU, // Humidity + 0U >> 8U, 0U & 0xFFU, // Pressure + 0U >> 8U, 0U & 0xFFU, // PM1.0 + 0U >> 8U, 0U & 0xFFU, // PM2.5 + 0U >> 8U, 0U & 0xFFU, // PM4.0 + 0U >> 8U, 0U & 0xFFU, // PM10.0 + 0U >> 8U, 0U & 0xFFU, // CO2 + 511U >> 8U, 511U & 0xFFU, // VOX + 511U >> 8U, 511U & 0xFFU, // NOX + 0U >> 8U, 0U & 0xFFU, // Luminosity + 0U, // Sound avg + 0U, // Sound peak + 0U >> 8U, 0U & 0xFFU, // Measurement count + 0, // Voltage + 0x00, // Flags + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xCB, 0xB8, 0x33, 0x4C, 0x88, 0x4F + }; + _Static_assert (sizeof (min_data) == RE_E0_DATA_LENGTH); + re_status_t err_code = RE_SUCCESS; + uint8_t test_buffer[40] = {0}; + err_code = re_e0_encode (test_buffer, &m_re_fe_data_underflow); + TEST_ASSERT (RE_SUCCESS == err_code); + TEST_ASSERT_EQUAL_HEX8_ARRAY (min_data, test_buffer, sizeof (min_data)); +} + +void test_ruuvi_endpoint_e0_overflow (void) +{ + static const re_e0_data_t m_re_fe_data_overflow = + { + .temperature_c = 126.1, + .humidity_rh = 100.1, + .pressure_pa = 115535, + .pm1p0_ppm = 1000.1, + .pm2p5_ppm = 1000.1, + .pm4p0_ppm = 1000.1, + .pm10p0_ppm = 1000.1, + .co2 = 40001, + .voc_index = 501, + .nox_index = 501, + .luminosity = 65535, + .sound_dba_avg = 127.1f, + .sound_dba_peak = 127.1f, + .measurement_count = 65534, + .voltage = 7.63f, + .flag_usb_on = true, + .flag_low_battery = true, + .flag_calibration_in_progress = true, + .flag_boost_mode = true, + .address = 0xCBB8334C884F, + }; + static const uint8_t max_data[] = + { + 0xE0, + 25200U >> 8U, 25200U & 0xFFU, // Temperature + 40000U >> 8U, 40000U & 0xFFU, // Humidity + 65534U >> 8U, 65534U & 0xFFU, // Pressure + 10000U >> 8U, 10000U & 0xFFU, // PM1.0 + 10000U >> 8U, 10000U & 0xFFU, // PM2.5 + 10000U >> 8U, 10000U & 0xFFU, // PM4.0 + 10000U >> 8U, 10000U & 0xFFU, // PM10.0 + 40000U >> 8U, 40000U & 0xFFU, // CO2 + 511U >> 8U, 511U & 0xFFU, // VOX + 511U >> 8U, 511U & 0xFFU, // NOX + 65534U >> 8U, 65534U & 0xFFU, // Luminosity + 254U, // Sound avg + 254U, // Sound peak + 65534U >> 8U, 65534U & 0xFFU, // Measurement count + 254, // Voltage + 0x0F, // Flags + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xCB, 0xB8, 0x33, 0x4C, 0x88, 0x4F + }; + _Static_assert (sizeof (max_data) == RE_E0_DATA_LENGTH); + re_status_t err_code = RE_SUCCESS; + uint8_t test_buffer[40] = {0}; + err_code = re_e0_encode (test_buffer, &m_re_fe_data_overflow); + TEST_ASSERT (RE_SUCCESS == err_code); + TEST_ASSERT_EQUAL_HEX8_ARRAY (max_data, test_buffer, sizeof (max_data)); +} + +void test_ruuvi_endpoint_e0_check_format_ok (void) +{ + const uint8_t raw_buf[48] = {0x2B, 0xFF, 0x99, 0x04, 0xE0}; + TEST_ASSERT_TRUE (re_e0_check_format (raw_buf)); +} + +void test_ruuvi_endpoint_e0_check_format_fail (void) +{ + const uint8_t raw_buf_payload_format_E1[48] = {0x02, 0x01, 0x04, 0x1B, 0xFF, 0x99, 0x04, 0xE1}; + TEST_ASSERT_FALSE (re_e0_check_format (raw_buf_payload_format_E1)); + const uint8_t raw_buf_manufacturer_id_1[48] = {0x02, 0x01, 0x04, 0x1B, 0xFF, 0x99, 0x05, 0xE0}; + TEST_ASSERT_FALSE (re_e0_check_format (raw_buf_manufacturer_id_1)); + const uint8_t raw_buf_manufacturer_id_2[48] = {0x02, 0x01, 0x04, 0x1B, 0xFF, 0x9a, 0x04, 0xE0}; + TEST_ASSERT_FALSE (re_e0_check_format (raw_buf_manufacturer_id_2)); + const uint8_t raw_buf_type[48] = {0x02, 0x01, 0x04, 0x1B, 0xFE, 0x99, 0x04, 0xE0}; + TEST_ASSERT_FALSE (re_e0_check_format (raw_buf_type)); + const uint8_t raw_buf_len[48] = {0x02, 0x01, 0x04, 0x1A, 0xFF, 0x99, 0x04, 0xE0}; + TEST_ASSERT_FALSE (re_e0_check_format (raw_buf_len)); + const uint8_t raw_buf_byte1[48] = {0x02, 0x02, 0x04, 0x1B, 0xFF, 0x99, 0x04, 0xE0}; + TEST_ASSERT_FALSE (re_e0_check_format (raw_buf_byte1)); + const uint8_t raw_buf_byte0[48] = {0x03, 0x01, 0x04, 0x1B, 0xFF, 0x99, 0x04, 0xE0}; + TEST_ASSERT_FALSE (re_e0_check_format (raw_buf_byte0)); +} + +void test_re_6_data_invalid (void) +{ + const uint16_t measurement_cnt = 123; + const uint64_t radio_mac = 0xCBB8334C884FULL; + const re_e0_data_t data = re_e0_data_invalid (measurement_cnt, radio_mac); + TEST_ASSERT (isnan (data.temperature_c)); + TEST_ASSERT (isnan (data.humidity_rh)); + TEST_ASSERT (isnan (data.pressure_pa)); + TEST_ASSERT (isnan (data.pm1p0_ppm)); + TEST_ASSERT (isnan (data.pm1p0_ppm)); + TEST_ASSERT (isnan (data.pm1p0_ppm)); + TEST_ASSERT (isnan (data.pm1p0_ppm)); + TEST_ASSERT (isnan (data.co2)); + TEST_ASSERT (isnan (data.voc_index)); + TEST_ASSERT (isnan (data.nox_index)); + TEST_ASSERT (isnan (data.luminosity)); + TEST_ASSERT (isnan (data.sound_dba_avg)); + TEST_ASSERT (isnan (data.sound_dba_peak)); + TEST_ASSERT (isnan (data.voltage)); + TEST_ASSERT_EQUAL (false, data.flag_usb_on); + TEST_ASSERT_EQUAL (false, data.flag_low_battery); + TEST_ASSERT_EQUAL (false, data.flag_calibration_in_progress); + TEST_ASSERT_EQUAL (false, data.flag_boost_mode); + TEST_ASSERT_EQUAL (data.measurement_count, measurement_cnt); + TEST_ASSERT_EQUAL (data.address, radio_mac); +} diff --git a/test/test_ruuvi_endpoint_f0.c b/test/test_ruuvi_endpoint_f0.c new file mode 100644 index 0000000..4a734c2 --- /dev/null +++ b/test/test_ruuvi_endpoint_f0.c @@ -0,0 +1,538 @@ +#include "unity.h" + +#include "ruuvi_endpoint_f0.h" +#include +#include + + +void setUp (void) +{ +} + +void tearDown (void) +{ +} + +/** + * @brief Typical encode operation + * + * @retval RD_SUCCESS on success test. + */ +void test_ruuvi_endpoint_f0_get_ok (void) +{ + static const re_f0_data_t m_re_f0_data_ok = + { + .temperature_c = 26.5f, + .humidity_rh = 80.5f, + .pressure_pa = 101355, + .pm1p0_ppm = 10.2f, + .pm2p5_ppm = 11.3f, + .pm4p0_ppm = 12.4f, + .pm10p0_ppm = 13.5f, + .co2 = 1129, + .voc_index = 11, + .nox_index = 12, + .luminosity = 15123, + .sound_dba_avg = 20.5f, + .flag_usb_on = true, + .flag_low_battery = false, + .flag_calibration_in_progress = false, + .flag_boost_mode = false, + .flag_seq_cnt = 5, + .address = 0xCBB8334C884F, + }; + static const uint8_t valid_data[] = + { + 0xF0, + 26, // Temperature + 161, // Humidity + 114, // Pressure + 89, // PM1.0 + 92, // PM2.5 + 95, // PM4.0 + 98, // PM10.0 + 169, // CO2 + 98, // VOX + 102, // NOX + 231, // Luminosity + 41, // Sound avg + 0x51, // Flags + 0xCB, 0xB8, 0x33, 0x4C, 0x88, 0x4F + }; + _Static_assert (sizeof (valid_data) == RE_F0_DATA_LENGTH); + re_status_t err_code = RE_SUCCESS; + uint8_t test_buffer[20] = {0}; + _Static_assert (sizeof (valid_data) == sizeof (test_buffer), "Invalid test data size"); + err_code = re_f0_encode (test_buffer, &m_re_f0_data_ok); + TEST_ASSERT_EQUAL (RE_SUCCESS, err_code); + TEST_ASSERT_EQUAL_HEX8_ARRAY (valid_data, test_buffer, sizeof (valid_data)); + const uint8_t raw_buf_prefix[] = {0x02, 0x01, 0x06, 0x03, 0x03, 0x98, 0xFC, 0x17, 0xFF, 0x99, 0x04}; + _Static_assert (sizeof (raw_buf_prefix) == RE_F0_OFFSET_PAYLOAD, + "Invalid raw_buf_prefix size"); + uint8_t raw_buf[31]; + memcpy (&raw_buf[0], raw_buf_prefix, sizeof (raw_buf_prefix)); + memcpy (&raw_buf[RE_F0_OFFSET_PAYLOAD], test_buffer, sizeof (test_buffer)); + re_f0_data_t decoded_data = {0}; + TEST_ASSERT_EQUAL (RE_SUCCESS, re_f0_decode (raw_buf, &decoded_data)); + TEST_ASSERT_EQUAL (260 /* 26.5 */, lrintf (decoded_data.temperature_c * 10.0f)); + TEST_ASSERT_EQUAL (805 /* 80.5 */, lrintf (decoded_data.humidity_rh * 10.0f)); + TEST_ASSERT_EQUAL (101400 /* 101355 */, lrintf (decoded_data.pressure_pa)); + TEST_ASSERT_EQUAL (103 /* 10.2 */, lrintf (decoded_data.pm1p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (112 /* 11.3 */, lrintf (decoded_data.pm2p5_ppm * 10.0f)); + TEST_ASSERT_EQUAL (122 /* 12.4 */, lrintf (decoded_data.pm4p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (134 /* 13.5 */, lrintf (decoded_data.pm10p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (1152 /* 1129 */, lrintf (decoded_data.co2)); + TEST_ASSERT_EQUAL (11, lrintf (decoded_data.voc_index)); + TEST_ASSERT_EQUAL (12, lrintf (decoded_data.nox_index)); + TEST_ASSERT_EQUAL (15322 /* 15123 */, lrintf (decoded_data.luminosity)); + TEST_ASSERT_EQUAL (205 /* 20.5 */, lrintf (decoded_data.sound_dba_avg * 10.0f)); + TEST_ASSERT_EQUAL (m_re_f0_data_ok.flag_usb_on, decoded_data.flag_usb_on); + TEST_ASSERT_EQUAL (m_re_f0_data_ok.flag_low_battery, decoded_data.flag_low_battery); + TEST_ASSERT_EQUAL (m_re_f0_data_ok.flag_calibration_in_progress, + decoded_data.flag_calibration_in_progress); + TEST_ASSERT_EQUAL (m_re_f0_data_ok.flag_boost_mode, decoded_data.flag_boost_mode); + TEST_ASSERT_EQUAL (m_re_f0_data_ok.flag_seq_cnt, decoded_data.flag_seq_cnt); + TEST_ASSERT_EQUAL (m_re_f0_data_ok.address, decoded_data.address); +} + +void test_ruuvi_endpoint_f0_get_ok_max (void) +{ + static const re_f0_data_t m_re_f0_data_ok_max = + { + .temperature_c = 126.0f, + .humidity_rh = 100.0f, + .pressure_pa = 115400, + .pm1p0_ppm = 1000.0f, + .pm2p5_ppm = 1000.0f, + .pm4p0_ppm = 1000.0f, + .pm10p0_ppm = 1000.0f, + .co2 = 40000, + .voc_index = 500, + .nox_index = 500, + .luminosity = 40000, + .sound_dba_avg = 127.0f, + .flag_usb_on = true, + .flag_low_battery = true, + .flag_calibration_in_progress = true, + .flag_boost_mode = true, + .flag_seq_cnt = 14, + .address = 0xCBB8334C884F, + }; + static const uint8_t max_data[] = + { + 0xF0, + 126, // Temperature + 200, // Humidity + 254, // Pressure + 254, // PM1.0 + 254, // PM2.5 + 254, // PM4.0 + 254, // PM10.0 + 254, // CO2 + 254, // VOX + 254, // NOX + 254, // Luminosity + 254, // Sound avg + 0xEF, // Flags + 0xCB, 0xB8, 0x33, 0x4C, 0x88, 0x4F + }; + re_status_t err_code = RE_SUCCESS; + uint8_t test_buffer[20] = {0}; + _Static_assert (sizeof (max_data) == sizeof (test_buffer), "Invalid test data size"); + err_code = re_f0_encode (test_buffer, &m_re_f0_data_ok_max); + TEST_ASSERT_EQUAL (RE_SUCCESS, err_code); + TEST_ASSERT_EQUAL_HEX8_ARRAY (max_data, test_buffer, sizeof (max_data)); + const uint8_t raw_buf_prefix[] = {0x02, 0x01, 0x06, 0x03, 0x03, 0x98, 0xFC, 0x17, 0xFF, 0x99, 0x04}; + _Static_assert (sizeof (raw_buf_prefix) == RE_F0_OFFSET_PAYLOAD, + "Invalid raw_buf_prefix size"); + uint8_t raw_buf[31]; + memcpy (&raw_buf[0], raw_buf_prefix, sizeof (raw_buf_prefix)); + memcpy (&raw_buf[RE_F0_OFFSET_PAYLOAD], test_buffer, sizeof (test_buffer)); + re_f0_data_t decoded_data = {0}; + TEST_ASSERT_EQUAL (RE_SUCCESS, re_f0_decode (raw_buf, &decoded_data)); + TEST_ASSERT_EQUAL (1260 /* 126.0 */, lrintf (decoded_data.temperature_c * 10.0f)); + TEST_ASSERT_EQUAL (1000 /* 100.0 */, lrintf (decoded_data.humidity_rh * 10.0f)); + TEST_ASSERT_EQUAL (115400 /* 115400 */, lrintf (decoded_data.pressure_pa)); + TEST_ASSERT_EQUAL (10000 /* 1000.0 */, lrintf (decoded_data.pm1p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (10000 /* 1000.0 */, lrintf (decoded_data.pm2p5_ppm * 10.0f)); + TEST_ASSERT_EQUAL (10000 /* 1000.0 */, lrintf (decoded_data.pm4p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (10000 /* 1000.0 */, lrintf (decoded_data.pm10p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (40000 /* 40000 */, lrintf (decoded_data.co2)); + TEST_ASSERT_EQUAL (500, lrintf (decoded_data.voc_index)); + TEST_ASSERT_EQUAL (500, lrintf (decoded_data.nox_index)); + TEST_ASSERT_EQUAL (40000 /* 40000 */, lrintf (decoded_data.luminosity)); + TEST_ASSERT_EQUAL (1270 /* 127.0 */, lrintf (decoded_data.sound_dba_avg * 10.0f)); + TEST_ASSERT_EQUAL (m_re_f0_data_ok_max.flag_usb_on, decoded_data.flag_usb_on); + TEST_ASSERT_EQUAL (m_re_f0_data_ok_max.flag_low_battery, decoded_data.flag_low_battery); + TEST_ASSERT_EQUAL (m_re_f0_data_ok_max.flag_calibration_in_progress, + decoded_data.flag_calibration_in_progress); + TEST_ASSERT_EQUAL (m_re_f0_data_ok_max.flag_boost_mode, decoded_data.flag_boost_mode); + TEST_ASSERT_EQUAL (m_re_f0_data_ok_max.flag_seq_cnt, decoded_data.flag_seq_cnt); + TEST_ASSERT_EQUAL (m_re_f0_data_ok_max.address, decoded_data.address); +} + +void test_ruuvi_endpoint_f0_get_ok_min (void) +{ + static const re_f0_data_t m_re_f0_data_ok_min = + { + .temperature_c = -126.0, + .humidity_rh = 0, + .pressure_pa = 90000, + .pm1p0_ppm = 0, + .pm2p5_ppm = 0, + .pm4p0_ppm = 0, + .pm10p0_ppm = 0, + .co2 = 0, + .voc_index = 1, + .nox_index = 1, + .luminosity = 0, + .sound_dba_avg = 0, + .flag_usb_on = false, + .flag_low_battery = false, + .flag_calibration_in_progress = false, + .flag_boost_mode = false, + .flag_seq_cnt = 0, + .address = 0xCBB8334C884F, + }; + static const uint8_t min_data[] = + { + 0xF0, + -126, // Temperature + 0, // Humidity + 0, // Pressure + 0, // PM1.0 + 0, // PM2.5 + 0, // PM4.0 + 0, // PM10.0 + 0, // CO2 + 0, // VOX + 0, // NOX + 0, // Luminosity + 0, // Sound avg + 0x00, // Flags + 0xCB, 0xB8, 0x33, 0x4C, 0x88, 0x4F + }; + re_status_t err_code = RE_SUCCESS; + uint8_t test_buffer[20] = {0}; + _Static_assert (sizeof (min_data) == sizeof (test_buffer), "Invalid test data size"); + err_code = re_f0_encode (test_buffer, &m_re_f0_data_ok_min); + TEST_ASSERT_EQUAL (RE_SUCCESS, err_code); + TEST_ASSERT_EQUAL_HEX8_ARRAY (min_data, test_buffer, sizeof (min_data)); + const uint8_t raw_buf_prefix[] = {0x02, 0x01, 0x06, 0x03, 0x03, 0x98, 0xFC, 0x17, 0xFF, 0x99, 0x04}; + _Static_assert (sizeof (raw_buf_prefix) == RE_F0_OFFSET_PAYLOAD, + "Invalid raw_buf_prefix size"); + uint8_t raw_buf[31]; + memcpy (&raw_buf[0], raw_buf_prefix, sizeof (raw_buf_prefix)); + memcpy (&raw_buf[RE_F0_OFFSET_PAYLOAD], test_buffer, sizeof (test_buffer)); + re_f0_data_t decoded_data = {0}; + TEST_ASSERT_EQUAL (RE_SUCCESS, re_f0_decode (raw_buf, &decoded_data)); + TEST_ASSERT_EQUAL (-1260 /* -126.0 */, lrintf (decoded_data.temperature_c * 10.0f)); + TEST_ASSERT_EQUAL (0 /* 0.0 */, lrintf (decoded_data.humidity_rh * 10.0f)); + TEST_ASSERT_EQUAL (90000 /* 90000 */, lrintf (decoded_data.pressure_pa)); + TEST_ASSERT_EQUAL (0 /* 0.0 */, lrintf (decoded_data.pm1p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (0 /* 0.0 */, lrintf (decoded_data.pm2p5_ppm * 10.0f)); + TEST_ASSERT_EQUAL (0 /* 0.0 */, lrintf (decoded_data.pm4p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (0 /* 0.0 */, lrintf (decoded_data.pm10p0_ppm * 10.0f)); + TEST_ASSERT_EQUAL (0 /* 0 */, lrintf (decoded_data.co2)); + TEST_ASSERT_EQUAL (1, lrintf (decoded_data.voc_index)); + TEST_ASSERT_EQUAL (1, lrintf (decoded_data.nox_index)); + TEST_ASSERT_EQUAL (0 /* 0 */, lrintf (decoded_data.luminosity)); + TEST_ASSERT_EQUAL (0 /* 0.0 */, lrintf (decoded_data.sound_dba_avg * 10.0f)); + TEST_ASSERT_EQUAL (m_re_f0_data_ok_min.flag_usb_on, decoded_data.flag_usb_on); + TEST_ASSERT_EQUAL (m_re_f0_data_ok_min.flag_low_battery, decoded_data.flag_low_battery); + TEST_ASSERT_EQUAL (m_re_f0_data_ok_min.flag_calibration_in_progress, + decoded_data.flag_calibration_in_progress); + TEST_ASSERT_EQUAL (m_re_f0_data_ok_min.flag_boost_mode, decoded_data.flag_boost_mode); + TEST_ASSERT_EQUAL (m_re_f0_data_ok_min.flag_seq_cnt, decoded_data.flag_seq_cnt); + TEST_ASSERT_EQUAL (m_re_f0_data_ok_min.address, decoded_data.address); +} + +/** + * @brief Null buffer + * + * @retval RE_ERROR_NULL on success test. + */ +void test_ruuvi_endpoint_f0_get_error_null_buffer (void) +{ + static const re_f0_data_t m_re_f0_data_ok = + { + .temperature_c = 26.5f, + .humidity_rh = 80.5f, + .pressure_pa = 101355, + .pm1p0_ppm = 10.2f, + .pm2p5_ppm = 11.3f, + .pm4p0_ppm = 12.4f, + .pm10p0_ppm = 13.5f, + .co2 = 1129, + .voc_index = 11, + .nox_index = 12, + .luminosity = 15123, + .sound_dba_avg = 20.5f, + .flag_usb_on = true, + .flag_low_battery = false, + .flag_calibration_in_progress = false, + .flag_boost_mode = false, + .flag_seq_cnt = 5, + .address = 0xCBB8334C884F, + }; + re_status_t err_code = RE_ERROR_NULL; + uint8_t * const p_test_buffer = NULL; + err_code = re_f0_encode (p_test_buffer, &m_re_f0_data_ok); + TEST_ASSERT (RE_ERROR_NULL == err_code); +} + +/** + * @brief Null data + * + * @retval RE_ERROR_NULL on success test. + */ +void test_ruuvi_endpoint_f0_get_error_null_data (void) +{ + re_status_t err_code = RE_ERROR_NULL; + uint8_t test_buffer[20] = {0}; + const re_f0_data_t * p_re_f0_data = NULL; + err_code = re_f0_encode ( (uint8_t * const) &test_buffer, p_re_f0_data); + TEST_ASSERT (RE_ERROR_NULL == err_code); +} + +/** + * @brief True to test encode operation with invalid data + * + * @retval RD_SUCCESS on success test. + */ +void test_ruuvi_endpoint_f0_get_invalid_data (void) +{ + static const re_f0_data_t m_re_f0_data_invalid = + { + .temperature_c = NAN, + .humidity_rh = NAN, + .pressure_pa = NAN, + .pm1p0_ppm = NAN, + .pm2p5_ppm = NAN, + .pm4p0_ppm = NAN, + .pm10p0_ppm = NAN, + .co2 = NAN, + .voc_index = NAN, + .nox_index = NAN, + .luminosity = NAN, + .sound_dba_avg = NAN, + .flag_usb_on = false, + .flag_low_battery = false, + .flag_calibration_in_progress = false, + .flag_boost_mode = false, + .flag_seq_cnt = RE_F0_FLAGS_SEQ_INVALID, + .address = 0xFFFFFFFFFFFF, + }; + static const uint8_t invalid_data[] = + { + 0xF0, + 0x80, // Temperature + 0xFF, // Humidity + 0xFF, // Pressure + 0xFF, // PM1.0 + 0xFF, // PM2.5 + 0xFF, // PM4.0 + 0xFF, // PM10.0 + 0xFF, // CO2 + 0xFF, // VOX + 0xFF, // NOX + 0xFF, // Luminosity + 0xFF, // Sound avg + 0xF0, // Flags + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + re_status_t err_code = RE_SUCCESS; + uint8_t test_buffer[20] = {0}; + _Static_assert (sizeof (invalid_data) == sizeof (test_buffer), "Invalid test data size"); + err_code = re_f0_encode ( (uint8_t * const) &test_buffer, + (const re_f0_data_t *) &m_re_f0_data_invalid); + TEST_ASSERT (RE_SUCCESS == err_code); + TEST_ASSERT_EQUAL_HEX8_ARRAY (invalid_data, test_buffer, sizeof (invalid_data)); + const uint8_t raw_buf_prefix[] = {0x02, 0x01, 0x06, 0x03, 0x03, 0x98, 0xFC, 0x17, 0xFF, 0x99, 0x04}; + _Static_assert (sizeof (raw_buf_prefix) == RE_F0_OFFSET_PAYLOAD, + "Invalid raw_buf_prefix size"); + uint8_t raw_buf[31]; + memcpy (&raw_buf[0], raw_buf_prefix, sizeof (raw_buf_prefix)); + memcpy (&raw_buf[RE_F0_OFFSET_PAYLOAD], test_buffer, sizeof (test_buffer)); + re_f0_data_t decoded_data = {0}; + TEST_ASSERT_EQUAL (RE_SUCCESS, re_f0_decode (raw_buf, &decoded_data)); + TEST_ASSERT (isnan (decoded_data.temperature_c)); + TEST_ASSERT (isnan (decoded_data.humidity_rh)); + TEST_ASSERT (isnan (decoded_data.pressure_pa)); + TEST_ASSERT (isnan (decoded_data.pm1p0_ppm)); + TEST_ASSERT (isnan (decoded_data.pm1p0_ppm)); + TEST_ASSERT (isnan (decoded_data.pm1p0_ppm)); + TEST_ASSERT (isnan (decoded_data.pm1p0_ppm)); + TEST_ASSERT (isnan (decoded_data.co2)); + TEST_ASSERT (isnan (decoded_data.voc_index)); + TEST_ASSERT (isnan (decoded_data.nox_index)); + TEST_ASSERT (isnan (decoded_data.luminosity)); + TEST_ASSERT (isnan (decoded_data.sound_dba_avg)); + TEST_ASSERT_EQUAL (m_re_f0_data_invalid.flag_usb_on, decoded_data.flag_usb_on); + TEST_ASSERT_EQUAL (m_re_f0_data_invalid.flag_low_battery, decoded_data.flag_low_battery); + TEST_ASSERT_EQUAL (m_re_f0_data_invalid.flag_calibration_in_progress, + decoded_data.flag_calibration_in_progress); + TEST_ASSERT_EQUAL (m_re_f0_data_invalid.flag_boost_mode, decoded_data.flag_boost_mode); + TEST_ASSERT_EQUAL (m_re_f0_data_invalid.flag_seq_cnt, decoded_data.flag_seq_cnt); + TEST_ASSERT_EQUAL (m_re_f0_data_invalid.address, decoded_data.address); +} + +void test_ruuvi_endpoint_f0_underflow (void) +{ + static const re_f0_data_t m_re_f0_data_underflow = + { + .temperature_c = -126.1, + .humidity_rh = -1, + .pressure_pa = 89999, + .pm1p0_ppm = -1, + .pm2p5_ppm = -1, + .pm4p0_ppm = -1, + .pm10p0_ppm = -1, + .co2 = -1, + .voc_index = 0, + .nox_index = 0, + .luminosity = -1, + .sound_dba_avg = -1, + .flag_usb_on = false, + .flag_low_battery = false, + .flag_calibration_in_progress = false, + .flag_boost_mode = false, + .flag_seq_cnt = 0, + .address = 0xCBB8334C884F, + }; + static const uint8_t min_data[] = + { + 0xF0, + -126, // Temperature + 0, // Humidity + 0, // Pressure + 0, // PM1.0 + 0, // PM2.5 + 0, // PM4.0 + 0, // PM10.0 + 0, // CO2 + 0xFF, // VOX + 0xFF, // NOX + 0, // Luminosity + 0, // Sound avg + 0x00, // Flags + 0xCB, 0xB8, 0x33, 0x4C, 0x88, 0x4F + }; + re_status_t err_code = RE_SUCCESS; + uint8_t test_buffer[20] = {0}; + err_code = re_f0_encode (test_buffer, &m_re_f0_data_underflow); + TEST_ASSERT (RE_SUCCESS == err_code); + TEST_ASSERT_EQUAL_HEX8_ARRAY (min_data, test_buffer, sizeof (min_data)); +} + +void test_ruuvi_endpoint_f0_overflow (void) +{ + static const re_f0_data_t m_re_f0_data_overflow = + { + .temperature_c = 126.1, + .humidity_rh = 100.1, + .pressure_pa = 115401, + .pm1p0_ppm = 1000.1, + .pm2p5_ppm = 1000.1, + .pm4p0_ppm = 1000.1, + .pm10p0_ppm = 1000.1, + .co2 = 40001, + .voc_index = 501, + .nox_index = 501, + .luminosity = 40001, + .sound_dba_avg = 127.1f, + .flag_usb_on = true, + .flag_low_battery = true, + .flag_calibration_in_progress = true, + .flag_boost_mode = true, + .flag_seq_cnt = 14, + .address = 0xCBB8334C884F, + }; + static const uint8_t max_data[] = + { + 0xF0, + 126, // Temperature + 200, // Humidity + 254, // Pressure + 254, // PM1.0 + 254, // PM2.5 + 254, // PM4.0 + 254, // PM10.0 + 254, // CO2 + 0xFF, // VOX + 0xFF, // NOX + 254, // Luminosity + 254, // Sound avg + 0xEF, // Flags + 0xCB, 0xB8, 0x33, 0x4C, 0x88, 0x4F + }; + re_status_t err_code = RE_SUCCESS; + uint8_t test_buffer[20] = {0}; + err_code = re_f0_encode (test_buffer, &m_re_f0_data_overflow); + TEST_ASSERT (RE_SUCCESS == err_code); + TEST_ASSERT_EQUAL_HEX8_ARRAY (max_data, test_buffer, sizeof (max_data)); +} + +void test_ruuvi_endpoint_f0_check_format_ok (void) +{ + const uint8_t raw_buf_prefix[] = {0x02, 0x01, 0x06, 0x03, 0x03, 0x98, 0xFC, 0x17, 0xFF, 0x99, 0x04}; + _Static_assert (sizeof (raw_buf_prefix) == RE_F0_OFFSET_PAYLOAD, + "Invalid raw_buf_prefix size"); + uint8_t raw_buf[31]; + memcpy (&raw_buf[0], raw_buf_prefix, sizeof (raw_buf_prefix)); + const uint8_t test_buffer[] = { 0xF0 }; + memcpy (&raw_buf[RE_F0_OFFSET_PAYLOAD], test_buffer, sizeof (test_buffer)); + TEST_ASSERT_TRUE (re_f0_check_format (raw_buf)); +} + +void test_ruuvi_endpoint_f0_check_format_fail (void) +{ + const uint8_t raw_buf_payload_format_F1[31] = {0x02, 0x01, 0x06, 0x03, 0x03, 0x98, 0xFC, 0x17, 0xFF, 0x99, 0x04, 0xF1}; + TEST_ASSERT_FALSE (re_f0_check_format (raw_buf_payload_format_F1)); + const uint8_t raw_buf_manufacturer_id_1[31] = {0x02, 0x01, 0x06, 0x03, 0x03, 0x98, 0xFC, 0x17, 0xFF, 0x99, 0x05, 0xF1}; + TEST_ASSERT_FALSE (re_f0_check_format (raw_buf_manufacturer_id_1)); + const uint8_t raw_buf_manufacturer_id_2[31] = {0x02, 0x01, 0x06, 0x03, 0x03, 0x98, 0xFC, 0x17, 0xFF, 0x9a, 0x04, 0xF1}; + TEST_ASSERT_FALSE (re_f0_check_format (raw_buf_manufacturer_id_2)); + const uint8_t raw_buf_payload_format_type[31] = {0x02, 0x01, 0x06, 0x03, 0x03, 0x98, 0xFC, 0x17, 0xFE, 0x99, 0x04, 0xF0}; + TEST_ASSERT_FALSE (re_f0_check_format (raw_buf_payload_format_type)); + const uint8_t raw_buf_payload_length[31] = {0x02, 0x01, 0x06, 0x03, 0x03, 0x98, 0xFC, 0x16, 0xFF, 0x99, 0x04, 0xF0}; + TEST_ASSERT_FALSE (re_f0_check_format (raw_buf_payload_length)); + const uint8_t raw_buf_uuid_1[31] = {0x02, 0x01, 0x06, 0x03, 0x03, 0x98, 0xFD, 0x17, 0xFF, 0x99, 0x04, 0xF0}; + TEST_ASSERT_FALSE (re_f0_check_format (raw_buf_uuid_1)); + const uint8_t raw_buf_uuid_2[31] = {0x02, 0x01, 0x06, 0x03, 0x03, 0x99, 0xFC, 0x17, 0xFF, 0x99, 0x04, 0xF0}; + TEST_ASSERT_FALSE (re_f0_check_format (raw_buf_uuid_2)); + const uint8_t raw_buf_uuid_type[31] = {0x02, 0x01, 0x06, 0x03, 0x02, 0x98, 0xFC, 0x17, 0xFF, 0x99, 0x04, 0xF0}; + TEST_ASSERT_FALSE (re_f0_check_format (raw_buf_uuid_type)); + const uint8_t raw_buf_uuid_len[31] = {0x02, 0x01, 0x06, 0x04, 0x03, 0x98, 0xFC, 0x17, 0xFF, 0x99, 0x04, 0xF0}; + TEST_ASSERT_FALSE (re_f0_check_format (raw_buf_uuid_len)); + const uint8_t raw_buf_flags_type[31] = {0x02, 0x02, 0x06, 0x03, 0x03, 0x98, 0xFC, 0x17, 0xFF, 0x99, 0x04, 0xF0}; + TEST_ASSERT_FALSE (re_f0_check_format (raw_buf_flags_type)); + const uint8_t raw_buf_flags_len[31] = {0x03, 0x01, 0x06, 0x03, 0x03, 0x98, 0xFC, 0x17, 0xFF, 0x99, 0x04, 0xF0}; + TEST_ASSERT_FALSE (re_f0_check_format (raw_buf_flags_len)); + const uint8_t raw_buf_format_6[31] = {0x02, 0x01, 0x04, 0x1B, 0xFF, 0x99, 0x04, 0x06}; + TEST_ASSERT_FALSE (re_f0_check_format (raw_buf_format_6)); +} + +void test_re_f0_data_invalid (void) +{ + const uint16_t measurement_cnt = 1; + const uint64_t radio_mac = 0xCBB8334C884FULL; + const re_f0_data_t data = re_f0_data_invalid (measurement_cnt, radio_mac); + TEST_ASSERT_TRUE (isnan (data.temperature_c)); + TEST_ASSERT (isnan (data.pressure_pa)); + TEST_ASSERT (isnan (data.pm1p0_ppm)); + TEST_ASSERT (isnan (data.pm1p0_ppm)); + TEST_ASSERT (isnan (data.pm1p0_ppm)); + TEST_ASSERT (isnan (data.pm1p0_ppm)); + TEST_ASSERT (isnan (data.co2)); + TEST_ASSERT (isnan (data.voc_index)); + TEST_ASSERT (isnan (data.nox_index)); + TEST_ASSERT (isnan (data.luminosity)); + TEST_ASSERT (isnan (data.sound_dba_avg)); + TEST_ASSERT_EQUAL (false, data.flag_usb_on); + TEST_ASSERT_EQUAL (false, data.flag_low_battery); + TEST_ASSERT_EQUAL (false, data.flag_calibration_in_progress); + TEST_ASSERT_EQUAL (false, data.flag_boost_mode); + TEST_ASSERT_EQUAL (measurement_cnt, data.flag_seq_cnt); + TEST_ASSERT_EQUAL (radio_mac, data.address); +}