From 9d61bdbb06c7756463af352815eea48bf544e412 Mon Sep 17 00:00:00 2001 From: Silke Hofstra Date: Fri, 23 Apr 2021 11:32:36 +0200 Subject: [PATCH] sys/senml: add SenML modules Add a basic SenML module and submodules with support for: - Encoding SenML values as CBOR using NanoCBOR. - Converting from Phydat to SenML. - Reading and encoding SAUL sensors. --- dist/tools/codespell/ignored_words.txt | 3 + examples/senml_saul/Makefile | 23 ++ examples/senml_saul/Makefile.ci | 9 + examples/senml_saul/main.c | 52 +++ makefiles/pseudomodules.inc.mk | 3 + sys/Kconfig | 1 + sys/Makefile.dep | 4 + sys/include/senml.h | 526 +++++++++++++++++++++++++ sys/include/senml/cbor.h | 121 ++++++ sys/include/senml/phydat.h | 98 +++++ sys/include/senml/saul.h | 76 ++++ sys/senml/Kconfig | 38 ++ sys/senml/Makefile | 7 + sys/senml/Makefile.dep | 16 + sys/senml/cbor.c | 155 ++++++++ sys/senml/phydat.c | 153 +++++++ sys/senml/saul.c | 99 +++++ sys/senml/senml.c | 127 ++++++ tests/senml_cbor/Makefile | 16 + tests/senml_cbor/Makefile.ci | 10 + tests/senml_cbor/app.config.test | 3 + tests/senml_cbor/main.c | 126 ++++++ tests/senml_cbor/tests/01-run.py | 14 + tests/senml_phydat/Makefile | 7 + tests/senml_phydat/Makefile.ci | 16 + tests/senml_phydat/app.config.test | 3 + tests/senml_phydat/main.c | 174 ++++++++ tests/senml_phydat/tests/01-run.py | 14 + tests/senml_saul/Makefile | 8 + tests/senml_saul/Makefile.ci | 12 + tests/senml_saul/main.c | 61 +++ tests/senml_saul/tests/01-run.py | 14 + 32 files changed, 1989 insertions(+) create mode 100644 examples/senml_saul/Makefile create mode 100644 examples/senml_saul/Makefile.ci create mode 100644 examples/senml_saul/main.c create mode 100644 sys/include/senml.h create mode 100644 sys/include/senml/cbor.h create mode 100644 sys/include/senml/phydat.h create mode 100644 sys/include/senml/saul.h create mode 100644 sys/senml/Kconfig create mode 100644 sys/senml/Makefile create mode 100644 sys/senml/Makefile.dep create mode 100644 sys/senml/cbor.c create mode 100644 sys/senml/phydat.c create mode 100644 sys/senml/saul.c create mode 100644 sys/senml/senml.c create mode 100644 tests/senml_cbor/Makefile create mode 100644 tests/senml_cbor/Makefile.ci create mode 100644 tests/senml_cbor/app.config.test create mode 100644 tests/senml_cbor/main.c create mode 100755 tests/senml_cbor/tests/01-run.py create mode 100644 tests/senml_phydat/Makefile create mode 100644 tests/senml_phydat/Makefile.ci create mode 100644 tests/senml_phydat/app.config.test create mode 100644 tests/senml_phydat/main.c create mode 100755 tests/senml_phydat/tests/01-run.py create mode 100644 tests/senml_saul/Makefile create mode 100644 tests/senml_saul/Makefile.ci create mode 100644 tests/senml_saul/main.c create mode 100755 tests/senml_saul/tests/01-run.py diff --git a/dist/tools/codespell/ignored_words.txt b/dist/tools/codespell/ignored_words.txt index 1b9c188706..6a8693fbff 100644 --- a/dist/tools/codespell/ignored_words.txt +++ b/dist/tools/codespell/ignored_words.txt @@ -116,3 +116,6 @@ ether # crate (Rust's package format) => create crate + +# VAs (volt ampere second) => was +vas diff --git a/examples/senml_saul/Makefile b/examples/senml_saul/Makefile new file mode 100644 index 0000000000..d6fd68d83d --- /dev/null +++ b/examples/senml_saul/Makefile @@ -0,0 +1,23 @@ +# name of your application +APPLICATION = senml_saul_example + +# If no BOARD is found in the environment, use this default: +BOARD ?= native + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +# we want to use SAUL and SenML +USEMODULE += saul_default +USEMODULE += senml_saul +USEMODULE += fmt + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +DEVELHELP ?= 1 + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +include $(RIOTBASE)/Makefile.include diff --git a/examples/senml_saul/Makefile.ci b/examples/senml_saul/Makefile.ci new file mode 100644 index 0000000000..b5de876337 --- /dev/null +++ b/examples/senml_saul/Makefile.ci @@ -0,0 +1,9 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-nano \ + arduino-uno \ + atmega328p \ + atmega328p-xplained-mini \ + nucleo-l011k4 \ + # diff --git a/examples/senml_saul/main.c b/examples/senml_saul/main.c new file mode 100644 index 0000000000..940fb52b5f --- /dev/null +++ b/examples/senml_saul/main.c @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 Silke Hofstra + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup examples + * @{ + * + * @file + * @brief Short SenML SAUL example + * + * @author Silke Hofstra + * + * @} + */ + +#include +#include + +#include "senml/saul.h" +#include "fmt.h" + +static uint8_t cbor_buf[1024]; + +void print_hex(uint8_t *a, size_t len) +{ + for (size_t i = 0; i < len; i++) { + print_byte_hex(a[i]); + } +} + +int main(void) +{ + size_t len = senml_saul_encode_cbor(cbor_buf, sizeof cbor_buf, saul_reg); + + if (len == 0) { + print_str("SenML/SAUL error\n"); + return 1; + } + + print_str("CBOR ("); + print_u32_dec(len); + print_str(" B): "); + print_hex(cbor_buf, len); + print_str("\n"); + + return 0; +} diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 7844c8e4a2..240737e41b 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -186,6 +186,9 @@ PSEUDOMODULES += scanf_float PSEUDOMODULES += sched_cb PSEUDOMODULES += sched_runq_callback PSEUDOMODULES += semtech_loramac_rx +PSEUDOMODULES += senml_cbor +PSEUDOMODULES += senml_phydat +PSEUDOMODULES += senml_saul PSEUDOMODULES += shell_hooks PSEUDOMODULES += slipdev_stdio PSEUDOMODULES += slipdev_l2addr diff --git a/sys/Kconfig b/sys/Kconfig index 5d9a13db69..97248ec4f4 100644 --- a/sys/Kconfig +++ b/sys/Kconfig @@ -75,6 +75,7 @@ rsource "rtc_utils/Kconfig" rsource "saul_reg/Kconfig" rsource "schedstatistics/Kconfig" rsource "sema/Kconfig" +rsource "senml/Kconfig" rsource "seq/Kconfig" rsource "shell/Kconfig" rsource "test_utils/Kconfig" diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 4363025af6..53fd1ad29b 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -386,6 +386,10 @@ ifneq (,$(filter saul_default,$(USEMODULE))) USEMODULE += saul_reg endif +ifneq (,$(filter senml%,$(USEMODULE))) + include $(RIOTBASE)/sys/senml/Makefile.dep +endif + ifneq (,$(filter phydat,$(USEMODULE))) USEMODULE += fmt endif diff --git a/sys/include/senml.h b/sys/include/senml.h new file mode 100644 index 0000000000..0b41122182 --- /dev/null +++ b/sys/include/senml.h @@ -0,0 +1,526 @@ +/* + * Copyright (C) 2021 Silke Hofstra + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_senml SenML + * @ingroup sys + * @brief Basic SenML types. + * + * The `senml` module contains the building blocks for using + * [SenML](https://www.rfc-editor.org/rfc/rfc8428). + * This module provides the basic types that can be used with (for example) + * @ref sys_senml_cbor for encoding measurement data. + * + * Some attributes defined in SenML need to be enabled explicitly, + * see @ref senml_attr_t for details. To enable all attributes, set: + * + * ``` + * CFLAGS += -DCONFIG_SENML_ATTR_SUM=1 + * CFLAGS += -DCONFIG_SENML_ATTR_VERSION=1 + * CFLAGS += -DCONFIG_SENML_ATTR_UPDATE_TIME=1 + * ``` + * + * @{ + * + * @file + * @brief Basic SenML types. + * + * @author Silke Hofstra + */ + +#ifndef SENML_H +#define SENML_H + +#include +#include +#include + +#include "kernel_defines.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enable the SenML 'sum' and 'base sum' attributes. + */ +#ifndef CONFIG_SENML_ATTR_SUM +#define CONFIG_SENML_ATTR_SUM 0 +#endif + +/** + * @brief Enable the SenML 'version' and 'base version' attributes. + */ +#ifndef CONFIG_SENML_ATTR_VERSION +#define CONFIG_SENML_ATTR_VERSION 0 +#endif + +/** + * @brief Enable the SenML 'update time' attribute. + */ +#ifndef CONFIG_SENML_ATTR_UPDATE_TIME +#define CONFIG_SENML_ATTR_UPDATE_TIME 0 +#endif + +/** + * @brief SenML units and secondary units + * + * This list contains the SenML units and secondary units as assigned by + * [IANA](https://www.iana.org/assignments/senml/senml.xhtml). + * Units in italic are not recommended to be produced by new implementations. + * Secondary units include the equivalent primary unit. + */ +typedef enum { + /* SenML units from RFC8428 */ + SENML_UNIT_NONE, /**< No unit specified */ + SENML_UNIT_METER, /**< meter (m) */ + SENML_UNIT_KILOGRAM, /**< kilogram (kg) */ + SENML_UNIT_GRAM, /**< *gram* (g) */ + SENML_UNIT_SECOND, /**< second (s) */ + SENML_UNIT_AMPERE, /**< ampere (A) */ + SENML_UNIT_KELVIN, /**< kelvin (K) */ + SENML_UNIT_CANDELA, /**< candela (cd) */ + SENML_UNIT_MOLE, /**< mole (mol) */ + SENML_UNIT_HERTZ, /**< hertz (Hz) */ + SENML_UNIT_RADIAN, /**< radian (rad) */ + SENML_UNIT_STERADIAN, /**< steradian (sr) */ + SENML_UNIT_NEWTON, /**< newton (N) */ + SENML_UNIT_PASCAL, /**< pascal (Pa) */ + SENML_UNIT_JOULE, /**< joule (J) */ + SENML_UNIT_WATT, /**< watt (W) */ + SENML_UNIT_COULOMB, /**< coulomb (C) */ + SENML_UNIT_VOLT, /**< volt (V) */ + SENML_UNIT_FARAD, /**< farad (F) */ + SENML_UNIT_OHM, /**< ohm (Ohm) */ + SENML_UNIT_SIEMENS, /**< siemens (S) */ + SENML_UNIT_WEBER, /**< weber (Wb) */ + SENML_UNIT_TESLA, /**< tesla (T) */ + SENML_UNIT_HENRY, /**< henry (H) */ + SENML_UNIT_CELSIUS, /**< degrees Celsius (Cel) */ + SENML_UNIT_LUMEN, /**< lumen (lm) */ + SENML_UNIT_LUX, /**< lux (lx) */ + SENML_UNIT_BECQUEREL, /**< becquerel (Bq) */ + SENML_UNIT_GRAY, /**< gray (Gy) */ + SENML_UNIT_SIEVERT, /**< sievert (Sv) */ + SENML_UNIT_KATAL, /**< katal (kat) */ + SENML_UNIT_SQUARE_METER, /**< square meter (area) (m2) */ + SENML_UNIT_CUBIC_METER, /**< cubic meter (volume) (m3) */ + SENML_UNIT_LITER, /**< *liter (volume)* (l) */ + SENML_UNIT_METER_PER_SECOND, /**< meter per second (velocity) (m/s) */ + SENML_UNIT_METER_PER_SQUARE_SECOND, /**< meter per square second (acceleration) (m/s2) */ + SENML_UNIT_CUBIC_METER_PER_SECOND, /**< cubic meter per second (flow rate) (m3/s) */ + SENML_UNIT_LITER_PER_SECOND, /**< *liter per second (flow rate)* (l/s) */ + SENML_UNIT_WATT_PER_SQUARE_METER, /**< watt per square meter (irradiance) (W/m2) */ + SENML_UNIT_CANDELA_PER_SQUARE_METER, /**< candela per square meter (luminance) (cd/m2) */ + SENML_UNIT_BIT, /**< bit (information content) (bit) */ + SENML_UNIT_BIT_PER_SECOND, /**< bit per second (data rate) (bit/s) */ + SENML_UNIT_LATITUDE, /**< degrees latitude (lat) */ + SENML_UNIT_LONGITUDE, /**< degrees longitude (lon) */ + SENML_UNIT_PH, /**< pH value (acidity; logarithmic quantity) (pH) */ + SENML_UNIT_DECIBEL, /**< decibel (logarithmic quantity) (dB) */ + SENML_UNIT_DBW, /**< decibel relative to 1 W (power level) (dBW) */ + SENML_UNIT_BEL, /**< *bel (sound pressure level; logarithmic quantity)* (Bspl) */ + SENML_UNIT_COUNT, /**< 1 (counter value) (count) */ + SENML_UNIT_RATIO, /**< 1 (ratio e.g., value of a switch) (/) */ + SENML_UNIT_RATIO_2, /**< *1 (ratio e.g., value of a switch)* (%) */ + SENML_UNIT_RELATIVE_HUMIDITY_PERCENT, /**< Percentage (Relative Humidity) (%RH) */ + SENML_UNIT_REMAINING_BATTERY_PERCENT, /**< Percentage (remaining battery energy level) (%EL) */ + SENML_UNIT_REMAINING_BATTERY_SECONDS, /**< seconds (remaining battery energy level) (EL) */ + SENML_UNIT_RATE, /**< 1 per second (event rate) (1/s) */ + SENML_UNIT_RPM, /**< *1 per minute (event rate, "rpm")* (1/min) */ + SENML_UNIT_BEAT_PER_MINUTE, /**< *1 per minute (heart rate in beats per minute)* (beat/min)) */ + SENML_UNIT_BEATS, /**< *1 (Cumulative number of heart beats)* (beats) */ + SENML_UNIT_SIEMENS_PER_METER, /**< Siemens per meter (conductivity) (S/m) */ + + /* SenML units from RFC8798 */ + SENML_UNIT_BYTE, /**< Byte (information content) (B) */ + SENML_UNIT_VOLT_AMPERE, /**< volt-ampere (Apparent Power) (VA) */ + SENML_UNIT_VOLT_AMPERE_SECOND, /**< volt-ampere second (Apparent Energy) (VAs) */ + SENML_UNIT_VOLT_AMPERE_REACTIVE, /**< volt-ampere reactive (Reactive Power) (var) */ + SENML_UNIT_VOLT_AMPERE_REACTIVE_SECOND, /**< volt-ampere-reactive second (Reactive Energy) (vars) */ + SENML_UNIT_JOULE_PER_METER, /**< joule per meter (Energy per distance) (J/m) */ + SENML_UNIT_KILOGRAM_PER_CUBIC_METER, /**< kilogram per cubic meter (mass density, mass concentration) (kg/m3) */ + SENML_UNIT_DEGREE, /**< *degree (angle)* (deg) */ + + /* SenML units from ISO7027-1:2016 */ + SENML_UNIT_NEPHELOMETRIC_TURBIDITY_UNIT, /**< Nephelometric Turbidity Unit (NTU) */ + + /* SenML secondary units from RFC8798 */ + SENML_UNIT_MILLISECOND, /**< millisecond (ms, equivalent to 1/1000 s) */ + SENML_UNIT_MINUTE, /**< minute (min, equivalent to 60 s) */ + SENML_UNIT_HOUR, /**< hour (h, equivalent to 3600 s) */ + SENML_UNIT_MEGAHERTZ, /**< megahertz (MHz, equivalent to 1000000 Hz) */ + SENML_UNIT_KILOWATT, /**< kilowatt (kW, equivalent to 1000 W) */ + SENML_UNIT_KILOVOLT_AMPERE, /**< kilovolt-ampere (kVA, equivalent to 1000 VA) */ + SENML_UNIT_KILOVAR, /**< kilovar (kvar, equivalent to 1000 var) */ + SENML_UNIT_AMPERE_HOUR, /**< ampere-hour (Ah, equivalent to 3600 C) */ + SENML_UNIT_WATT_HOUR, /**< watt-hour (Wh, equivalent to 3600 J) */ + SENML_UNIT_KILOWATT_HOUR, /**< kilowatt-hour (kWh, equivalent to 3600000 J) */ + SENML_UNIT_VAR_HOUR, /**< var-hour (varh, equivalent to 3600 vars) */ + SENML_UNIT_KILOVAR_HOUR, /**< kilovar-hour (kvarh, equivalent to 3600000 vars) */ + SENML_UNIT_KILOVOLT_AMPERE_HOUR, /**< kilovolt-ampere-hour (kVAh, equivalent to 3600000 VAs) */ + SENML_UNIT_WATT_HOUR_PER_KILOMETER, /**< watt-hour per kilometer (Wh/km, equivalent to 3.6 J/m) */ + SENML_UNIT_KIBIBYTE, /**< kibibyte (KiB, equivalent to 1024 B) */ + SENML_UNIT_GIGABYTE, /**< gigabyte (GB, equivalent to 1e9 B) */ + SENML_UNIT_MEGABIT_PER_SECOND, /**< megabit per second (Mbit/s, equivalent to 1000000 bit/s) */ + SENML_UNIT_BYTE_PER_SECOND, /**< byte per second (B/s, equivalent to 8 bit/s) */ + SENML_UNIT_MEGABYTE_PER_SECOND, /**< megabyte per second (MB/s, equivalent to 8000000 bit/s) */ + SENML_UNIT_MILLIVOLT, /**< millivolt (mV, equivalent to 1/1000 V) */ + SENML_UNIT_MILLIAMPERE, /**< milliampere (mA, equivalent to 1/1000 A) */ + SENML_UNIT_DECIBEL_MILLIWATT, /**< decibel (milliwatt) (dBm, equivalent to -29 dBW) */ + SENML_UNIT_MICROGRAM_PER_CUBIC_METER, /**< microgram per cubic meter (ug/m3, equivalent to 1e-9 kg/m3) */ + SENML_UNIT_MILLIMETER_PER_HOUR, /**< millimeter per hour (mm/h, equivalent to 1/3600000 m/s) */ + SENML_UNIT_METER_PER_HOUR, /**< meter per hour (m/h, equivalent to 1/3600 m/s) */ + SENML_UNIT_PARTS_PER_MILLION, /**< parts per million (ppm, equivalent to 1e-6 '/') */ + SENML_UNIT_PERCENT, /**< percent (/100, equivalent to 1/100 '/') */ + SENML_UNIT_PERMILLE, /**< permille (/1000, equivalent to 1/1000 '/') */ + SENML_UNIT_HECTOPASCAL, /**< hectopascal (hPa, equivalent to 100 Pa) */ + SENML_UNIT_MILLIMETER, /**< millimeter (mm, equivalent to 1/1000 m) */ + SENML_UNIT_CENTIMETER, /**< centimeter (cm, equivalent to 1/100 m) */ + SENML_UNIT_KILOMETER, /**< kilometer (km, equivalent to 1000 m) */ + SENML_UNIT_KILOMETER_PER_HOUR, /**< kilometer per hour (km/h, equivalent to 1/3.6 m/s) */ + + /* SenML secondary units from CoRE-1 */ + SENML_UNIT_PARTS_PER_BILLION, /**< parts per billion (ppb, equivalent to 1e-9 '/') */ + SENML_UNIT_PARTS_PER_TRILLION, /**< parts per trillion (ppt, equivalent to 1e-12 '/') */ + SENML_UNIT_VOLT_AMPERE_HOUR, /**< volt-ampere-hour (VAh, equivalent to 3600 VAs) */ + SENML_UNIT_MILLIGRAM_PER_LITER, /**< milligram per liter (mg/l, equivalent to 1/1000 kg/m3) */ + SENML_UNIT_MICROGRAM_PER_LITER, /**< microgram per liter (ug/l, equivalent to 1e-6 kg/m3) */ + SENML_UNIT_GRAM_PER_LITER, /**< gram per liter (g/l, equivalent to 1 kg/m3) */ +} senml_unit_t; + +/** + * @brief SenML numeric value types. + * + */ +typedef enum { + SENML_TYPE_NUMERIC_UINT, /**< Unsigned integer */ + SENML_TYPE_NUMERIC_INT, /**< Integer */ + SENML_TYPE_NUMERIC_FLOAT, /**< Floating point number */ + SENML_TYPE_NUMERIC_DOUBLE, /**< Double-precision floating point number */ + SENML_TYPE_NUMERIC_DECFRAC, /**< Decimal fraction */ +} senml_value_type_t; + +/** + * @brief Decimal fraction containing a value in the form of m * 10^e. + */ +typedef struct { + int32_t e; /**< Exponent */ + int32_t m; /**< Mantissa */ +} senml_decfrac_t; + +/** + * @brief SenML numeric value. + * + * Various SenML attributes (see @ref senml_attr_t) may contain any 'numeric' + * types. This struct is used to contain these. + */ +typedef struct { + senml_value_type_t type; /**< Type of the value */ + union { + uint64_t u; + int64_t i; + float f; + double d; + struct { int32_t e; int32_t m; } df /** Decimal fraction */; + } value; /**< Value data */ +} senml_numeric_t; + +/** + * @brief SenML common record attributes. + * All of these values are optional: empty or 0 values will not be encoded. + * Note that some attributes need to be enabled explicitly. + */ +typedef struct { + const char *base_name; /**< Base Name */ + senml_numeric_t base_time; /**< Base Time */ + senml_unit_t base_unit; /**< Base Unit */ + senml_numeric_t base_value; /**< Base Value */ +#if IS_ACTIVE(CONFIG_SENML_ATTR_SUM) || defined(DOXYGEN) + senml_numeric_t base_sum; /**< Base Sum, set `CONFIG_SENML_ATTR_SUM` to 1 to enable */ +#endif +#if IS_ACTIVE(CONFIG_SENML_ATTR_VERSION) || defined(DOXYGEN) + uint64_t base_version; /**< Base Version, set `CONFIG_SENML_ATTR_VERSION` to 1 to enable */ +#endif + const char *name; /**< Name of the measurement */ + senml_unit_t unit; /**< Unit */ +#if IS_ACTIVE(CONFIG_SENML_ATTR_SUM) || defined(DOXYGEN) + senml_numeric_t sum; /**< Sum, set `CONFIG_SENML_ATTR_SUM` to 1 to enable */ +#endif + senml_numeric_t time; /**< Time of the measurement (relative or Unix) in seconds */ +#if IS_ACTIVE(CONFIG_SENML_ATTR_UPDATE_TIME) || defined(DOXYGEN) + senml_numeric_t update_time; /**< Maximum time before the next sensor value, set `CONFIG_SENML_ATTR_UPDATE_TIME` to 1 to enable */ +#endif +} senml_attr_t; + +/** + * @brief SenML string value. + */ +typedef struct { + senml_attr_t attr; /**< SenML attributes */ + senml_numeric_t value; /**< Value */ +} senml_value_t; + +/** + * @brief SenML string value. + */ +typedef struct { + senml_attr_t attr; /**< SenML attributes */ + const char *value; /**< Value */ + size_t len; /**< Value length */ +} senml_string_value_t; + +/** + * @brief SenML boolean value. + */ +typedef struct { + senml_attr_t attr; /**< SenML attributes */ + bool value; /**< Value */ +} senml_bool_value_t; + +/** + * @brief SenML data value. + */ +typedef struct { + senml_attr_t attr; /**< SenML attributes */ + const uint8_t *value; /**< Value */ + size_t len; /**< Value length */ +} senml_data_value_t; + +/** + * @brief Create a floating point numeric value. + * + * @param v Value to encode. + * @return Numeric value containing the given value. + */ +static inline senml_numeric_t senml_float(float v) +{ + return (senml_numeric_t){ .type = SENML_TYPE_NUMERIC_FLOAT, + .value = { .f = v } }; +} + +/** + * @brief Set a floating point numeric value. + * + * @param n Numeric value to set. + * @param v Value to encode. + */ +static inline void senml_set_float(senml_numeric_t *n, float v) +{ + n->type = SENML_TYPE_NUMERIC_FLOAT; + n->value.f = v; +} + +/** + * @brief Create a double precision floating point numeric value. + * + * @param v Value to encode. + * @return Numeric value containing the given value. + */ +static inline senml_numeric_t senml_double(double v) +{ + return (senml_numeric_t){ .type = SENML_TYPE_NUMERIC_DOUBLE, + .value = { .d = v } }; +} + +/** + * @brief Set a double precision floating point numeric value. + * + * @param n Numeric value to set. + * @param v Value to encode. + */ +static inline void senml_set_double(senml_numeric_t *n, double v) +{ + n->type = SENML_TYPE_NUMERIC_DOUBLE; + n->value.d = v; +} + +/** + * @brief Create an integer numeric value. + * + * @param v Value to encode. + * @return Numeric value containing the given value. + */ +static inline senml_numeric_t senml_int(int64_t v) +{ + return (senml_numeric_t){ .type = SENML_TYPE_NUMERIC_INT, + .value = { .i = v } }; +} + +/** + * @brief Set an integer numeric value. + * + * @param n Numeric value to set. + * @param v Value to encode. + */ +static inline void senml_set_int(senml_numeric_t *n, int64_t v) +{ + n->type = SENML_TYPE_NUMERIC_INT; + n->value.i = v; +} + +/** + * @brief Create an unsigned integer numeric value. + * + * @param v Value to encode. + * @return Numeric value containing the given value. + */ +static inline senml_numeric_t senml_uint(uint64_t v) +{ + return (senml_numeric_t){ .type = SENML_TYPE_NUMERIC_UINT, + .value = { .u = v } }; +} + +/** + * @brief Set an unsigned integer numeric value. + * + * @param n Numeric value to set. + * @param v Value to encode. + */ +static inline void set_senml_uint(senml_numeric_t *n, uint64_t v) +{ + n->type = SENML_TYPE_NUMERIC_UINT; + n->value.u = v; +} + +/** + * @brief Create a decimal fraction numeric value in the form `m*10^e`. + * + * @param m Mantissa (value) to encode. + * @param e Exponent (scale) to encode. + * @return Numeric value containing the given value. + */ +static inline senml_numeric_t senml_decfrac(int32_t m, int32_t e) +{ + return (senml_numeric_t){ .type = SENML_TYPE_NUMERIC_DECFRAC, + .value = { .df = { .e = e, .m = m } } }; +} + +/** + * @brief Set a decimal fraction numeric value in the form `m*10^e`. + * + * @param n Numeric value to set. + * @param m Mantissa (value) to encode. + * @param e Exponent (scale) to encode. + */ +static inline void senml_set_decfrac(senml_numeric_t *n, int32_t m, int32_t e) +{ + n->type = SENML_TYPE_NUMERIC_DECFRAC; + n->value.df.e = e; + n->value.df.m = m; +} + +/** + * @brief Get an integer representation of a duration in seconds. + * + * @param s Duration in seconds. + * @return Numeric representation of the duration in seconds. + */ +static inline senml_numeric_t senml_duration_s(int64_t s) +{ + return senml_int(s); +} + +/** + * @brief Set an integer representation of a duration in seconds. + * + * @param n Numeric value to set. + * @param s Duration in seconds. + */ +static inline void senml_set_duration_s(senml_numeric_t *n, int64_t s) +{ + senml_set_int(n, s); +} + +/** + * @brief Get a @ref senml_decfrac_t representation of a duration in milliseconds. + * + * @param ms Duration in milliseconds. + * @return Numeric representation of the duration. + */ +static inline senml_numeric_t senml_duration_ms(int32_t ms) +{ + return senml_decfrac(ms, -3); +} + +/** + * @brief Set a @ref senml_decfrac_t representation of a duration in milliseconds. + * + * @param n Numeric value to set. + * @param ms Duration in milliseconds. + * @return Numeric representation of the duration. + */ +static inline void senml_set_duration_ms(senml_numeric_t *n, int32_t ms) +{ + senml_set_decfrac(n, ms, -3); +} + +/** + * @brief Get a @ref senml_decfrac_t representation of a duration in microseconds. + * + * @param us Duration in microseconds. + * @return Numeric representation of the duration. + */ +static inline senml_numeric_t senml_duration_us(int32_t us) +{ + return senml_decfrac(us, -6); +} + +/** + * @brief Get a @ref senml_decfrac_t representation of a duration in microseconds. + * + * @param n Numeric value to set. + * @param us Duration in microseconds. + * @return Numeric representation of the duration. + */ +static inline void senml_set_duration_us(senml_numeric_t *n, int32_t us) +{ + senml_set_decfrac(n, us, -6); +} + +/** + * @brief Get a @ref senml_decfrac_t representation of a duration in nanoseconds. + * + * @param ns Duration in nanoseconds. + * @return Numeric representation of the duration. + */ +static inline senml_numeric_t senml_duration_ns(int32_t ns) +{ + return senml_decfrac(ns, -9); +} + +/** + * @brief Set a @ref senml_decfrac_t representation of a duration in nanoseconds. + * + * @param n Numeric value to set. + * @param ns Duration in nanoseconds. + */ +static inline void senml_set_duration_ns(senml_numeric_t *n, int32_t ns) +{ + senml_set_decfrac(n, ns, -9); +} + +/** + * @brief Convert a SenML unit to a string. + * + * See the [SenML units](https://www.iana.org/assignments/senml/senml.xhtml#senml-units) and + * [Secondary units](https://www.iana.org/assignments/senml/senml.xhtml#secondary-units) from IANA. + * Values not defined in @ref senml_unit_t will result in an empty string. + * + * @param unit Unit to convert to string. + * + * @return String representation of the unit. + */ +const char *senml_unit_to_str(senml_unit_t unit); + +#ifdef __cplusplus +} +#endif + +#endif /* SENML_H */ +/** @} */ diff --git a/sys/include/senml/cbor.h b/sys/include/senml/cbor.h new file mode 100644 index 0000000000..00676e7a88 --- /dev/null +++ b/sys/include/senml/cbor.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2021 Silke Hofstra + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_senml_cbor SenML CBOR + * @ingroup sys_senml + * @brief Functionality for encoding SenML values as CBOR + * + * The `senml_cbor` module contains functionality for encoding @ref sys_senml + * values to CBOR using @ref pkg_nanocbor. + * + * @{ + * + * @file + * @brief Functionality for encoding SenML values as CBOR + * + * @author Silke Hofstra + */ + +#ifndef SENML_CBOR_H +#define SENML_CBOR_H + +#include +#include +#include + +#include "senml.h" +#include "nanocbor/nanocbor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief SenML CBOR labels + * + * This list contains the SenML CBOR labels as assigned by IANA. + */ +typedef enum { + SENML_LABEL_BASE_VERSION = -1, + SENML_LABEL_BASE_NAME = -2, + SENML_LABEL_BASE_TIME = -3, + SENML_LABEL_BASE_UNIT = -4, + SENML_LABEL_BASE_VALUE = -5, + SENML_LABEL_BASE_SUM = -6, + SENML_LABEL_NAME = 0, + SENML_LABEL_UNIT = 1, + SENML_LABEL_VALUE = 2, + SENML_LABEL_STRING_VALUE = 3, + SENML_LABEL_BOOLEAN_VALUE = 4, + SENML_LABEL_SUM = 5, + SENML_LABEL_TIME = 6, + SENML_LABEL_UPDATE_TIME = 7, + SENML_LABEL_DATA_VALUE = 8, +} senml_cbor_label_t; + +#if IS_ACTIVE(CONFIG_SENML_ATTR_SUM) || defined(DOXYGEN) +/** + * @brief Encode @ref senml_attr_t containing `sum` as CBOR. + * + * Requires the `sum` attribute to be enabled by setting `CONFIG_SENML_ATTR_SUM` to 1. + * + * @param enc NanoCBOR encoder. + * @param attr Attributes (including `sum`) to encode. + * + * @return Size of the encoded data. + */ +int senml_encode_sum_cbor(nanocbor_encoder_t *enc, const senml_attr_t *attr); +#endif + +/** + * @brief Encode @ref senml_bool_value_t as CBOR. + * + * @param enc NanoCBOR encoder. + * @param val value to encode. + * + * @return Size of the encoded data. + */ +int senml_encode_bool_cbor(nanocbor_encoder_t *enc, const senml_bool_value_t *val); + +/** + * @brief Encode @ref senml_value_t as CBOR. + * + * @param enc NanoCBOR encoder. + * @param val value to encode. + * + * @return Size of the encoded data. + */ +int senml_encode_value_cbor(nanocbor_encoder_t *enc, const senml_value_t *val); + +/** + * @brief Encode @ref senml_string_value_t as CBOR. + * + * @param enc NanoCBOR encoder. + * @param val value to encode. + * + * @return Size of the encoded data. + */ +int senml_encode_string_cbor(nanocbor_encoder_t *enc, const senml_string_value_t *val); + +/** + * @brief Encode @ref senml_data_value_t as CBOR. + * + * @param enc NanoCBOR encoder. + * @param val value to encode. + * + * @return Size of the encoded data. + */ +int senml_encode_data_cbor(nanocbor_encoder_t *enc, const senml_data_value_t *val); + +#ifdef __cplusplus +} +#endif + +#endif /* SENML_CBOR_H */ +/** @} */ diff --git a/sys/include/senml/phydat.h b/sys/include/senml/phydat.h new file mode 100644 index 0000000000..1c645c5444 --- /dev/null +++ b/sys/include/senml/phydat.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2021 Silke Hofstra + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_senml_phydat SenML Phydat + * @ingroup sys_senml + * @brief Functionality for converting from @ref sys_phydat to @ref sys_senml + * + * The `senml_phydat` module contains various functions for converting + * @ref sys_phydat values to @ref sys_senml. + * + * @{ + * + * @file + * @brief Functionality for converting from @ref sys_phydat to @ref sys_senml + * + * @author Silke Hofstra + */ + +#ifndef SENML_PHYDAT_H +#define SENML_PHYDAT_H + +#include +#include +#include + +#include "senml.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Create a SenML boolean value. + * + * Writes the value of the given @p dim of @p phydat as a boolean. + * @p phydat is assumed to be of @ref UNIT_BOOL. + * + * @note `phydat->scale` must be zero. + * + * @param[out] senml SenML value to store value in. + * @param[in] phydat Phydat to convert. + * @param[in] dim Dimension of @p phydat to convert. + */ +void phydat_to_senml_bool(senml_bool_value_t *senml, const phydat_t *phydat, const uint8_t dim); + +/** + * @brief Create a SenML float value. + * + * Writes the value of the given @p dim of @p phydat as a float. + * The unit of @p phydat is converted using the following rules: + * + * - @ref UNIT_TIME is converted to @ref SENML_UNIT_SECOND. + * - @ref UNIT_F is converted to @ref SENML_UNIT_KELVIN. + * - @ref UNIT_G is converted to @ref SENML_UNIT_METER_PER_SQUARE_SECOND. + * - @ref UNIT_BAR is converted to @ref SENML_UNIT_PASCAL. + * - @ref UNIT_GPM3 is converted to @ref SENML_UNIT_KILOGRAM_PER_CUBIC_METER. + * - @ref UNIT_GS is converted to @ref SENML_UNIT_TESLA. + * - Compatible units are set to their SenML equivalent. + * - Incompatible (or unknown) units are set to @ref SENML_UNIT_NONE. + * + * @param[out] senml SenML value to store value in. + * @param[in] phydat Phydat to convert. + * @param[in] dim Dimension of @p phydat to convert. + */ +void phydat_to_senml_float(senml_value_t *senml, const phydat_t *phydat, const uint8_t dim); + +/** + * @brief Create a SenML decimal fraction value. + * + * Writes the value of the given @p dim of @p phydat as a decimal value. + * The unit of @p phydat is converted using the following rules: + * + * - @ref UNIT_TIME is converted to @ref SENML_UNIT_SECOND. + * - @ref UNIT_BAR is converted to @ref SENML_UNIT_PASCAL. + * - @ref UNIT_GPM3 is converted to @ref SENML_UNIT_KILOGRAM_PER_CUBIC_METER. + * - @ref UNIT_GS is converted to @ref SENML_UNIT_TESLA. + * - Compatible units are set to their SenML equivalent. + * - Incompatible (or unknown) units are set to @ref SENML_UNIT_NONE. + * + * @param[out] senml SenML value to store value in. + * @param[in] phydat Phydat to convert. + * @param[in] dim Dimension of @p phydat to convert. + */ +void phydat_to_senml_decimal(senml_value_t *senml, const phydat_t *phydat, const uint8_t dim); + +#ifdef __cplusplus +} +#endif + +#endif /* SENML_PHYDAT_H */ +/** @} */ diff --git a/sys/include/senml/saul.h b/sys/include/senml/saul.h new file mode 100644 index 0000000000..5ffa27c46f --- /dev/null +++ b/sys/include/senml/saul.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 Silke Hofstra + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_senml_saul SenML SAUL + * @ingroup sys_senml + * @brief Functionality for reading @ref drivers_saul sensors as @ref sys_senml + * + * The `senml_saul` module contains functions for reading sensors using + * @ref drivers_saul and converting them to @ref sys_senml_cbor. + * + * @{ + * + * @file + * @brief Functionality for reading @ref drivers_saul sensors as @ref sys_senml + * + * @author Silke Hofstra + */ + +#ifndef SENML_SAUL_H +#define SENML_SAUL_H + +#include +#include "nanocbor/nanocbor.h" +#include "saul_reg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Use floats instead of decimal types when encoding SAUL measurements. + * + * If this is set to `1` the @ref phydat_t values from SAUL are converted to + * @ref senml_numeric_t using @ref phydat_to_senml_float. + * Values are converted using @ref phydat_to_senml_decimal otherwise. + */ +#ifndef CONFIG_SENML_SAUL_USE_FLOATS +#define CONFIG_SENML_SAUL_USE_FLOATS 0 +#endif + +/** + * @brief Encode a single @ref drivers_saul sensor as senml+cbor. + * + * @param enc NanoCBOR encoder. + * @param dev SAUL sensor to encode. + * + * @return Number of dimensions encoded. Less or equal to 0 on error. + */ +int senml_saul_reg_encode_cbor(nanocbor_encoder_t *enc, saul_reg_t *dev); + +/** + * @brief Encode all sensors from a @ref drivers_saul registry as senml+cbor. + * + * This functions reads all @ref drivers_saul sensors in a registry and encodes + * the values as SenML/CBOR. + * + * @param buf Buffer to store the CBOR in. + * @param len Length of the buffer. + * @param reg SAUL registry to encode. + * + * @return Size of the encoded data. + */ +size_t senml_saul_encode_cbor(uint8_t *buf, size_t len, saul_reg_t *reg); + +#ifdef __cplusplus +} +#endif + +#endif /* SENML_SAUL_H */ +/** @} */ diff --git a/sys/senml/Kconfig b/sys/senml/Kconfig new file mode 100644 index 0000000000..5c2a0a1d9e --- /dev/null +++ b/sys/senml/Kconfig @@ -0,0 +1,38 @@ +# Copyright (c) 2021 Silke Hofstra +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# + +config MODULE_SENML + bool "SenML" + depends on TEST_KCONFIG + help + Generic data container for physical data and utility functions. + +config MODULE_SENML_SAUL + bool "SenML SAUL integration" + depends on TEST_KCONFIG + select MODULE_SENML_PHYDAT + select MODULE_SENML_CBOR + depends on MODULE_SAUL + depends on MODULE_SAUL_REG + help + Generic data container for physical data and utility functions. + +config MODULE_SENML_PHYDAT + bool "SenML Phydat support" + depends on TEST_KCONFIG + select MODULE_SENML + select MODULE_PHYDAT + help + Utilities to convert Phydat valus to SenML + +config MODULE_SENML_CBOR + bool "SenML CBOR enconding" + depends on TEST_KCONFIG + select MODULE_SENML + select PACKAGE_NANOCBOR + help + Support for CBOR encoding of SenML values diff --git a/sys/senml/Makefile b/sys/senml/Makefile new file mode 100644 index 0000000000..097d026097 --- /dev/null +++ b/sys/senml/Makefile @@ -0,0 +1,7 @@ +MODULE = senml + +SRC = senml.c + +SUBMODULES = 1 + +include $(RIOTBASE)/Makefile.base diff --git a/sys/senml/Makefile.dep b/sys/senml/Makefile.dep new file mode 100644 index 0000000000..b7766e0464 --- /dev/null +++ b/sys/senml/Makefile.dep @@ -0,0 +1,16 @@ +ifneq (,$(filter senml_saul,$(USEMODULE))) + USEMODULE += senml + USEMODULE += senml_cbor + USEMODULE += senml_phydat + USEMODULE += saul_reg +endif + +ifneq (,$(filter senml_cbor,$(USEMODULE))) + USEPKG += nanocbor + USEMODULE += senml +endif + +ifneq (,$(filter senml_phydat,$(USEMODULE))) + USEMODULE += senml + USEMODULE += phydat +endif diff --git a/sys/senml/cbor.c b/sys/senml/cbor.c new file mode 100644 index 0000000000..6f0047c7e5 --- /dev/null +++ b/sys/senml/cbor.c @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2021 Silke Hofstra + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +#include "senml.h" +#include "senml/cbor.h" +#include "nanocbor/nanocbor.h" + +static int senml_encode_numeric_cbor(nanocbor_encoder_t *enc, const senml_numeric_t *v) +{ + switch (v->type) { + case SENML_TYPE_NUMERIC_FLOAT: + return nanocbor_fmt_float(enc, v->value.f); + case SENML_TYPE_NUMERIC_DOUBLE: + return nanocbor_fmt_double(enc, v->value.d); + case SENML_TYPE_NUMERIC_INT: + return nanocbor_fmt_int(enc, v->value.i); + case SENML_TYPE_NUMERIC_UINT: + return nanocbor_fmt_uint(enc, v->value.u); + case SENML_TYPE_NUMERIC_DECFRAC: + return nanocbor_fmt_decimal_frac(enc, v->value.df.e, v->value.df.m); + default: + return 0; + } +} + +static int senml_encode_start_cbor(nanocbor_encoder_t *enc, const senml_attr_t *attr, + bool sum_value) +{ + int n = nanocbor_fmt_map(enc, !sum_value + + (attr->base_name != NULL) + + (attr->base_time.value.u != 0) + + (attr->base_unit != SENML_UNIT_NONE) + + (attr->base_value.value.u != 0) +#if IS_ACTIVE(CONFIG_SENML_ATTR_SUM) + + (attr->base_sum.value.u != 0) +#endif +#if IS_ACTIVE(CONFIG_SENML_ATTR_VERSION) + + (attr->base_version != 0 && attr->base_version != 10) +#endif + + (attr->name != NULL) + + (attr->unit != SENML_UNIT_NONE) +#if IS_ACTIVE(CONFIG_SENML_ATTR_SUM) + + (sum_value || attr->sum.value.u != 0) +#endif + + (attr->time.value.u != 0) +#if IS_ACTIVE(CONFIG_SENML_ATTR_UPDATE_TIME) + + (attr->update_time.value.u != 0) +#endif + ); + + if (attr->base_name != NULL) { + n += nanocbor_fmt_int(enc, SENML_LABEL_BASE_NAME); + n += nanocbor_put_tstr(enc, attr->base_name); + } + + if (attr->base_time.value.u != 0) { + n += nanocbor_fmt_int(enc, SENML_LABEL_BASE_TIME); + n += senml_encode_numeric_cbor(enc, &attr->base_time); + } + + if (attr->base_unit != SENML_UNIT_NONE) { + n += nanocbor_fmt_int(enc, SENML_LABEL_BASE_UNIT); + n += nanocbor_put_tstr(enc, senml_unit_to_str(attr->base_unit)); + } + + if (attr->base_value.value.u != 0) { + n += nanocbor_fmt_int(enc, SENML_LABEL_BASE_VALUE); + n += senml_encode_numeric_cbor(enc, &attr->base_value); + } + +#if IS_ACTIVE(CONFIG_SENML_ATTR_SUM) + if (attr->base_sum.value.u != 0) { + n += nanocbor_fmt_int(enc, SENML_LABEL_BASE_SUM); + n += senml_encode_numeric_cbor(enc, &attr->base_sum); + } +#endif + +#if IS_ACTIVE(CONFIG_SENML_ATTR_VERSION) + if (attr->base_version != 0 && attr->base_version != 10) { + n += nanocbor_fmt_int(enc, SENML_LABEL_BASE_VERSION); + n += nanocbor_fmt_uint(enc, attr->base_version); + } +#endif + + if (attr->name != NULL) { + n += nanocbor_fmt_int(enc, SENML_LABEL_NAME); + n += nanocbor_put_tstr(enc, attr->name); + } + + if (attr->unit != SENML_UNIT_NONE) { + n += nanocbor_fmt_int(enc, SENML_LABEL_UNIT); + n += nanocbor_put_tstr(enc, senml_unit_to_str(attr->unit)); + } + +#if IS_ACTIVE(CONFIG_SENML_ATTR_SUM) + if (sum_value || attr->sum.value.u != 0) { + n += nanocbor_fmt_int(enc, SENML_LABEL_SUM); + n += senml_encode_numeric_cbor(enc, &attr->sum); + } +#endif + + if (attr->time.value.u != 0) { + n += nanocbor_fmt_int(enc, SENML_LABEL_TIME); + n += senml_encode_numeric_cbor(enc, &attr->time); + } + +#if IS_ACTIVE(CONFIG_SENML_ATTR_UPDATE_TIME) + if (attr->update_time.value.u != 0) { + n += nanocbor_fmt_int(enc, SENML_LABEL_UPDATE_TIME); + n += senml_encode_numeric_cbor(enc, &attr->update_time); + } +#endif + + return n; +} + +#if IS_ACTIVE(CONFIG_SENML_ATTR_SUM) +int senml_encode_sum_cbor(nanocbor_encoder_t *enc, const senml_attr_t *attr) +{ + return senml_encode_start_cbor(enc, attr, true); +} +#endif + +int senml_encode_bool_cbor(nanocbor_encoder_t *enc, const senml_bool_value_t *val) +{ + return senml_encode_start_cbor(enc, &val->attr, false) + + nanocbor_fmt_int(enc, SENML_LABEL_BOOLEAN_VALUE) + + nanocbor_fmt_bool(enc, val->value); +} + +int senml_encode_value_cbor(nanocbor_encoder_t *enc, const senml_value_t *val) +{ + return senml_encode_start_cbor(enc, &val->attr, false) + + nanocbor_fmt_int(enc, SENML_LABEL_VALUE) + + senml_encode_numeric_cbor(enc, &val->value); +} + +int senml_encode_string_cbor(nanocbor_encoder_t *enc, const senml_string_value_t *val) +{ + return senml_encode_start_cbor(enc, &val->attr, false) + + nanocbor_fmt_int(enc, SENML_LABEL_STRING_VALUE) + + nanocbor_put_tstrn(enc, val->value, val->len); +} + +int senml_encode_data_cbor(nanocbor_encoder_t *enc, const senml_data_value_t *val) +{ + return senml_encode_start_cbor(enc, &val->attr, false) + + nanocbor_fmt_int(enc, SENML_LABEL_DATA_VALUE) + + nanocbor_put_bstr(enc, val->value, val->len); +} diff --git a/sys/senml/phydat.c b/sys/senml/phydat.c new file mode 100644 index 0000000000..31b3c2dd48 --- /dev/null +++ b/sys/senml/phydat.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2021 Silke Hofstra + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +#include + +#include "math.h" +#include "senml.h" +#include "senml/phydat.h" + + + +static uint8_t phydat_unit_to_senml_unit(uint8_t unit) +{ + switch (unit) { + /* Compatible units */ + case UNIT_TEMP_C: return SENML_UNIT_CELSIUS; + case UNIT_TEMP_K: return SENML_UNIT_KELVIN; + case UNIT_LUX: return SENML_UNIT_LUX; + case UNIT_M: return SENML_UNIT_METER; + case UNIT_M2: return SENML_UNIT_SQUARE_METER; + case UNIT_M3: return SENML_UNIT_CUBIC_METER; + case UNIT_GR: return SENML_UNIT_GRAM; + case UNIT_A: return SENML_UNIT_AMPERE; + case UNIT_V: return SENML_UNIT_VOLT; + case UNIT_W: return SENML_UNIT_WATT; + case UNIT_T: return SENML_UNIT_TESLA; + case UNIT_COULOMB: return SENML_UNIT_COULOMB; + case UNIT_F: return SENML_UNIT_FARAD; + case UNIT_OHM: return SENML_UNIT_OHM; + case UNIT_PH: return SENML_UNIT_PH; + case UNIT_PA: return SENML_UNIT_PASCAL; + case UNIT_CD: return SENML_UNIT_CANDELA; + + /* Compatible Secondary units */ + case UNIT_DBM: return SENML_UNIT_DECIBEL_MILLIWATT; + case UNIT_PERCENT: return SENML_UNIT_PERCENT; + case UNIT_PERMILL: return SENML_UNIT_PERMILLE; + case UNIT_PPM: return SENML_UNIT_PARTS_PER_MILLION; + case UNIT_PPB: return SENML_UNIT_PARTS_PER_BILLION; + + /* Incompatible units */ + case UNIT_TEMP_F: return SENML_UNIT_NONE; /* use K or Cel instead */ + case UNIT_GS: return SENML_UNIT_NONE; /* use T instead */ + case UNIT_G: return SENML_UNIT_NONE; /* use m/s2 instead */ + case UNIT_BAR: return SENML_UNIT_NONE; /* use Pa or hPa instead */ + case UNIT_TIME: return SENML_UNIT_NONE; /* split into second/minute/hour */ + case UNIT_DATE: return SENML_UNIT_NONE; /* split into day/month/year */ + case UNIT_GPM3: return SENML_UNIT_NONE; /* use kg/m3 instead */ + case UNIT_DPS: return SENML_UNIT_NONE; /* no alternative */ + case UNIT_CPM3: return SENML_UNIT_NONE; /* no alternative */ + default: return SENML_UNIT_NONE; + } +} + +void phydat_to_senml_bool(senml_bool_value_t *senml, const phydat_t *phydat, const uint8_t dim) +{ + senml->value = phydat->val[dim] == 1; + senml->attr.unit = SENML_UNIT_NONE; +} + +void phydat_to_senml_float(senml_value_t *senml, const phydat_t *phydat, const uint8_t dim) +{ + float value = (float)(phydat->val[dim]); + + if (phydat->scale) { + value *= pow(10, phydat->scale); + } + + switch (phydat->unit) { + /* time conversion */ + case UNIT_TIME: + senml->attr.unit = (dim == 0) + ? SENML_UNIT_SECOND + : (dim == 1) + ? SENML_UNIT_MINUTE + : SENML_UNIT_HOUR; + break; + + /* simple conversions */ + case UNIT_TEMP_F: + /* convert fahrenheit to kelvin */ + value = (value + 459.67) * (5. / 9.); + senml->attr.unit = SENML_UNIT_KELVIN; + break; + case UNIT_G: + /* convert gravitational acceleration to acceleration */ + value *= 9.80665; + senml->attr.unit = SENML_UNIT_METER_PER_SQUARE_SECOND; + break; + case UNIT_BAR: + value *= 100000; + senml->attr.unit = SENML_UNIT_PASCAL; + break; + case UNIT_GPM3: + value *= 0.001; + senml->attr.unit = SENML_UNIT_KILOGRAM_PER_CUBIC_METER; + break; + case UNIT_GS: + value *= 0.0001; + senml->attr.unit = SENML_UNIT_TESLA; + break; + + /* compatible (or not converted) */ + default: + senml->attr.unit = phydat_unit_to_senml_unit(phydat->unit); + break; + } + + senml->value = senml_float(value); +} + +void phydat_to_senml_decimal(senml_value_t *senml, const phydat_t *phydat, const uint8_t dim) +{ + int32_t m = phydat->val[dim]; + int32_t e = phydat->scale; + + switch (phydat->unit) { + /* time conversion */ + case UNIT_TIME: + senml->attr.unit = (dim == 0) + ? SENML_UNIT_SECOND + : (dim == 1) + ? SENML_UNIT_MINUTE + : SENML_UNIT_HOUR; + break; + + /* simple conversions */ + case UNIT_BAR: + e += 5; + senml->attr.unit = SENML_UNIT_PASCAL; + break; + case UNIT_GPM3: + e -= 3; + senml->attr.unit = SENML_UNIT_KILOGRAM_PER_CUBIC_METER; + break; + case UNIT_GS: + e -= 4; + senml->attr.unit = SENML_UNIT_TESLA; + break; + + /* compatible, or not converted */ + default: + senml->attr.unit = phydat_unit_to_senml_unit(phydat->unit); + break; + } + + senml->value = senml_decfrac(m, e); +} diff --git a/sys/senml/saul.c b/sys/senml/saul.c new file mode 100644 index 0000000000..b5879e9822 --- /dev/null +++ b/sys/senml/saul.c @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2021 Silke Hofstra + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +#include "nanocbor/nanocbor.h" +#include "saul_reg.h" +#include "senml.h" +#include "senml/cbor.h" +#include "senml/phydat.h" +#include "senml/saul.h" + +static inline void senml_encode_phydat_bool(nanocbor_encoder_t *enc, + const saul_reg_t *dev, + const phydat_t *data, + const uint8_t dim) +{ + senml_bool_value_t val = { .attr = { .name = dev->name } }; + + phydat_to_senml_bool(&val, data, dim); + senml_encode_bool_cbor(enc, &val); +} + +static inline uint8_t senml_fix_unit(const saul_reg_t *dev, const uint8_t unit) +{ + /* Fix the unit for relative humidity. */ + if (dev->driver->type == SAUL_SENSE_HUM && + unit == SENML_UNIT_PERCENT) { + return SENML_UNIT_RELATIVE_HUMIDITY_PERCENT; + } + return unit; +} + +static void senml_encode_phydat_float(nanocbor_encoder_t *enc, + const saul_reg_t *dev, + const phydat_t *data, const uint8_t dim) +{ + senml_value_t val = { .attr = { .name = dev->name } }; + + phydat_to_senml_float(&val, data, dim); + val.attr.unit = senml_fix_unit(dev, val.attr.unit); + senml_encode_value_cbor(enc, &val); +} + +static void senml_encode_phydat_decimal(nanocbor_encoder_t *enc, + const saul_reg_t *dev, + const phydat_t *data, const uint8_t dim) +{ + senml_value_t val = { .attr = { .name = dev->name } }; + + phydat_to_senml_decimal(&val, data, dim); + val.attr.unit = senml_fix_unit(dev, val.attr.unit); + senml_encode_value_cbor(enc, &val); +} + +int senml_saul_reg_encode_cbor(nanocbor_encoder_t *enc, saul_reg_t *dev) +{ + phydat_t data; + int dim = saul_reg_read(dev, &data); + + if (dim <= 0) { + return dim; + } + + for (uint8_t i = 0; i < dim; i++) { + if (data.unit == UNIT_BOOL) { + senml_encode_phydat_bool(enc, dev, &data, i); + } + else if (CONFIG_SENML_SAUL_USE_FLOATS) { + senml_encode_phydat_float(enc, dev, &data, i); + } + else { + senml_encode_phydat_decimal(enc, dev, &data, i); + } + } + + return dim; +} + +size_t senml_saul_encode_cbor(uint8_t *buf, size_t len, saul_reg_t *dev) +{ + nanocbor_encoder_t enc; + + nanocbor_encoder_init(&enc, buf, len); + nanocbor_fmt_array_indefinite(&enc); + + while (dev) { + if (senml_saul_reg_encode_cbor(&enc, dev) <= 0) { + return 0; + } + dev = dev->next; + } + + nanocbor_fmt_end_indefinite(&enc); + return nanocbor_encoded_len(&enc); +} diff --git a/sys/senml/senml.c b/sys/senml/senml.c new file mode 100644 index 0000000000..c484b5187f --- /dev/null +++ b/sys/senml/senml.c @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2021 Silke Hofstra + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +#include "senml.h" + +const char *senml_unit_to_str(senml_unit_t unit) +{ + switch (unit) { + case SENML_UNIT_NONE: return ""; + case SENML_UNIT_METER: return "m"; + case SENML_UNIT_KILOGRAM: return "kg"; + case SENML_UNIT_GRAM: return "g"; + case SENML_UNIT_SECOND: return "s"; + case SENML_UNIT_AMPERE: return "A"; + case SENML_UNIT_KELVIN: return "K"; + case SENML_UNIT_CANDELA: return "cd"; + case SENML_UNIT_MOLE: return "mol"; + case SENML_UNIT_HERTZ: return "Hz"; + case SENML_UNIT_RADIAN: return "rad"; + case SENML_UNIT_STERADIAN: return "sr"; + case SENML_UNIT_NEWTON: return "N"; + case SENML_UNIT_PASCAL: return "Pa"; + case SENML_UNIT_JOULE: return "J"; + case SENML_UNIT_WATT: return "W"; + case SENML_UNIT_COULOMB: return "C"; + case SENML_UNIT_VOLT: return "V"; + case SENML_UNIT_FARAD: return "F"; + case SENML_UNIT_OHM: return "Ohm"; + case SENML_UNIT_SIEMENS: return "S"; + case SENML_UNIT_WEBER: return "Wb"; + case SENML_UNIT_TESLA: return "T"; + case SENML_UNIT_HENRY: return "H"; + case SENML_UNIT_CELSIUS: return "Cel"; + case SENML_UNIT_LUMEN: return "lm"; + case SENML_UNIT_LUX: return "lx"; + case SENML_UNIT_BECQUEREL: return "Bq"; + case SENML_UNIT_GRAY: return "Gy"; + case SENML_UNIT_SIEVERT: return "Sv"; + case SENML_UNIT_KATAL: return "kat"; + case SENML_UNIT_SQUARE_METER: return "m2"; + case SENML_UNIT_CUBIC_METER: return "m3"; + case SENML_UNIT_LITER: return "l"; + case SENML_UNIT_METER_PER_SECOND: return "m/s"; + case SENML_UNIT_METER_PER_SQUARE_SECOND: return "m/s2"; + case SENML_UNIT_CUBIC_METER_PER_SECOND: return "m3/s"; + case SENML_UNIT_LITER_PER_SECOND: return "l/s"; + case SENML_UNIT_WATT_PER_SQUARE_METER: return "W/m2"; + case SENML_UNIT_CANDELA_PER_SQUARE_METER: return "cd/m2"; + case SENML_UNIT_BIT: return "bit"; + case SENML_UNIT_BIT_PER_SECOND: return "bit/s"; + case SENML_UNIT_LATITUDE: return "lat"; + case SENML_UNIT_LONGITUDE: return "lon"; + case SENML_UNIT_PH: return "pH"; + case SENML_UNIT_DECIBEL: return "dB"; + case SENML_UNIT_DBW: return "dBW"; + case SENML_UNIT_BEL: return "Bspl"; + case SENML_UNIT_COUNT: return "count"; + case SENML_UNIT_RATIO: return "/"; + case SENML_UNIT_RATIO_2: return "%"; + case SENML_UNIT_RELATIVE_HUMIDITY_PERCENT: return "%RH"; + case SENML_UNIT_REMAINING_BATTERY_PERCENT: return "%EL"; + case SENML_UNIT_REMAINING_BATTERY_SECONDS: return "EL"; + case SENML_UNIT_RATE: return "1/s"; + case SENML_UNIT_RPM: return "1/min"; + case SENML_UNIT_BEAT_PER_MINUTE: return "beat/min"; + case SENML_UNIT_BEATS: return "beats"; + case SENML_UNIT_SIEMENS_PER_METER: return "S/m"; + + case SENML_UNIT_BYTE: return "B"; + case SENML_UNIT_VOLT_AMPERE: return "VA"; + case SENML_UNIT_VOLT_AMPERE_SECOND: return "VAs"; + case SENML_UNIT_VOLT_AMPERE_REACTIVE: return "var"; + case SENML_UNIT_VOLT_AMPERE_REACTIVE_SECOND: return "vars"; + case SENML_UNIT_JOULE_PER_METER: return "J/m"; + case SENML_UNIT_KILOGRAM_PER_CUBIC_METER: return "kg/m3"; + case SENML_UNIT_DEGREE: return "deg"; + + case SENML_UNIT_NEPHELOMETRIC_TURBIDITY_UNIT: return "NTU"; + + case SENML_UNIT_MILLISECOND: return "ms"; + case SENML_UNIT_MINUTE: return "min"; + case SENML_UNIT_HOUR: return "h"; + case SENML_UNIT_MEGAHERTZ: return "MHz"; + case SENML_UNIT_KILOWATT: return "kW"; + case SENML_UNIT_KILOVOLT_AMPERE: return "kVA"; + case SENML_UNIT_KILOVAR: return "kvar"; + case SENML_UNIT_AMPERE_HOUR: return "Ah"; + case SENML_UNIT_WATT_HOUR: return "Wh"; + case SENML_UNIT_KILOWATT_HOUR: return "kWh"; + case SENML_UNIT_VAR_HOUR: return "varh"; + case SENML_UNIT_KILOVAR_HOUR: return "kvarh"; + case SENML_UNIT_KILOVOLT_AMPERE_HOUR: return "kVAh"; + case SENML_UNIT_WATT_HOUR_PER_KILOMETER: return "Wh/km"; + case SENML_UNIT_KIBIBYTE: return "KiB"; + case SENML_UNIT_GIGABYTE: return "GB"; + case SENML_UNIT_MEGABIT_PER_SECOND: return "MBit/s"; + case SENML_UNIT_BYTE_PER_SECOND: return "B/s"; + case SENML_UNIT_MEGABYTE_PER_SECOND: return "MB/s"; + case SENML_UNIT_MILLIVOLT: return "mV"; + case SENML_UNIT_MILLIAMPERE: return "mA"; + case SENML_UNIT_DECIBEL_MILLIWATT: return "dBm"; + case SENML_UNIT_MICROGRAM_PER_CUBIC_METER: return "ug/m3"; + case SENML_UNIT_MILLIMETER_PER_HOUR: return "mm/h"; + case SENML_UNIT_METER_PER_HOUR: return "m/h"; + case SENML_UNIT_PARTS_PER_MILLION: return "ppm"; + case SENML_UNIT_PERCENT: return "/100"; + case SENML_UNIT_PERMILLE: return "/1000"; + case SENML_UNIT_HECTOPASCAL: return "hPa"; + case SENML_UNIT_MILLIMETER: return "mm"; + case SENML_UNIT_CENTIMETER: return "cm"; + case SENML_UNIT_KILOMETER: return "km"; + case SENML_UNIT_KILOMETER_PER_HOUR: return "km/h"; + case SENML_UNIT_PARTS_PER_BILLION: return "ppb"; + case SENML_UNIT_PARTS_PER_TRILLION: return "ppt"; + case SENML_UNIT_VOLT_AMPERE_HOUR: return "VAh"; + case SENML_UNIT_MILLIGRAM_PER_LITER: return "mg/l"; + case SENML_UNIT_MICROGRAM_PER_LITER: return "ug/l"; + case SENML_UNIT_GRAM_PER_LITER: return "g/l"; + + default: return ""; + } +} diff --git a/tests/senml_cbor/Makefile b/tests/senml_cbor/Makefile new file mode 100644 index 0000000000..8fb4ddec77 --- /dev/null +++ b/tests/senml_cbor/Makefile @@ -0,0 +1,16 @@ +include ../Makefile.tests_common + +USEMODULE += senml_cbor +USEMODULE += fmt +USEMODULE += embunit + +CFLAGS += -DCONFIG_SENML_ATTR_SUM +CFLAGS += -DCONFIG_SENML_ATTR_VERSION +CFLAGS += -DCONFIG_SENML_ATTR_UPDATE_TIME + +CFLAGS += -DTHREAD_STACKSIZE_DEFAULT=1536 + +# The following BOARDs redefine THREAD_STACKSIZE_DEFAULT +BOARD_BLACKLIST += nucleo-l011k4 stk3200 + +include $(RIOTBASE)/Makefile.include diff --git a/tests/senml_cbor/Makefile.ci b/tests/senml_cbor/Makefile.ci new file mode 100644 index 0000000000..696e4e21c7 --- /dev/null +++ b/tests/senml_cbor/Makefile.ci @@ -0,0 +1,10 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-nano \ + arduino-uno \ + atmega328p \ + atmega328p-xplained-mini \ + nucleo-f031k6 \ + nucleo-l011k4 \ + # diff --git a/tests/senml_cbor/app.config.test b/tests/senml_cbor/app.config.test new file mode 100644 index 0000000000..ff0dae5786 --- /dev/null +++ b/tests/senml_cbor/app.config.test @@ -0,0 +1,3 @@ +CONFIG_MODULE_SENML_CBOR=y +CONFIG_MODULE_FMT=y +CONFIG_MODULE_EMBUNIT=y diff --git a/tests/senml_cbor/main.c b/tests/senml_cbor/main.c new file mode 100644 index 0000000000..0d7f5203a0 --- /dev/null +++ b/tests/senml_cbor/main.c @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2021 Silke Hofstra + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Short SenML CBOR test + * + * @author Silke Hofstra + * + * @} + */ + +#include +#include + +#include "embUnit.h" +#include "senml/cbor.h" +#include "fmt.h" + +#define BUF_SIZE (128) + +static uint8_t cbor_buf[BUF_SIZE]; +static char result[2 * BUF_SIZE]; +static char expect[] = "89A5216943424F522074657374221A608404D001616D07187802F953B0A206" + "0102F953B0A2060202183DA2060302183DA2060402C48220190267A2060504" + "F5A20606036752494F54204F53A20607084400010203A201626B6705183D"; + +void test_senml_encode(void) +{ + nanocbor_encoder_t enc; + + /* Some common attributes to set on the first element */ + senml_attr_t attr = { + .base_name = "CBOR test", + .base_time = senml_duration_s(1619264720), + .update_time = senml_duration_s(120), + .unit = SENML_UNIT_METER, + }; + + /* A numeric (float) value */ + senml_value_t vf = { .attr = attr, .value = senml_float(61.5) }; + + /* A numeric (double) value */ + senml_value_t vd = { .attr = { .time = senml_duration_s(1) }, + .value = senml_double(61.5) }; + + /* A numeric (int) value */ + senml_value_t vi = { .attr = { .time = senml_duration_s(2) }, + .value = senml_int(61) }; + + /* A numeric (uint) value */ + senml_value_t vu = { .attr = { .time = senml_duration_s(3) }, + .value = senml_uint(61) }; + + /* A numeric (decimal fraction) value */ + senml_value_t vdf = { .attr = { .time = senml_duration_s(4) }, + .value = senml_decfrac(615, -1) }; + + /* A boolean value */ + senml_bool_value_t vb = { .attr = { .time = senml_duration_s(5) }, + .value = true }; + + /* A string value */ + char string[] = "RIOT OS"; + senml_string_value_t vs = { .attr = { .time = senml_duration_s(6) }, + .value = string, .len = sizeof string - 1 }; + + /* A data value */ + uint8_t data[] = { 0x00, 0x01, 0x02, 0x03 }; + senml_data_value_t vdat = { .attr = { .time = senml_duration_s(7) }, + .value = data, .len = sizeof data }; + + /* A numeric (float) sum value */ + senml_attr_t sum = { + .sum = senml_int(61), + .unit = SENML_UNIT_KILOGRAM, + }; + + /* Initialize encoder, and start array */ + nanocbor_encoder_init(&enc, cbor_buf, sizeof cbor_buf); + nanocbor_fmt_array(&enc, 9); + + /* Encode the values */ + senml_encode_value_cbor(&enc, &vf); + senml_encode_value_cbor(&enc, &vd); + senml_encode_value_cbor(&enc, &vi); + senml_encode_value_cbor(&enc, &vu); + senml_encode_value_cbor(&enc, &vdf); + senml_encode_bool_cbor(&enc, &vb); + senml_encode_string_cbor(&enc, &vs); + senml_encode_data_cbor(&enc, &vdat); + senml_encode_sum_cbor(&enc, &sum); + + size_t len = nanocbor_encoded_len(&enc); + + fmt_bytes_hex(result, cbor_buf, len); + + /* Compare hex result */ + TEST_ASSERT_EQUAL_INT(2 * len, sizeof expect - 1); + TEST_ASSERT_EQUAL_INT(0, strncmp(expect, result, len)); +} + +Test *tests_senml(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_senml_encode), + }; + EMB_UNIT_TESTCALLER(senml_tests, NULL, NULL, fixtures); + return (Test *)&senml_tests; +} + +int main(void) +{ + TESTS_START(); + TESTS_RUN(tests_senml()); + TESTS_END(); + return 0; +} diff --git a/tests/senml_cbor/tests/01-run.py b/tests/senml_cbor/tests/01-run.py new file mode 100755 index 0000000000..8b4e23f2e7 --- /dev/null +++ b/tests/senml_cbor/tests/01-run.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2017 Freie Universität Berlin +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +import sys +from testrunner import run_check_unittests + + +if __name__ == "__main__": + sys.exit(run_check_unittests()) diff --git a/tests/senml_phydat/Makefile b/tests/senml_phydat/Makefile new file mode 100644 index 0000000000..452d330fda --- /dev/null +++ b/tests/senml_phydat/Makefile @@ -0,0 +1,7 @@ +include ../Makefile.tests_common + +USEMODULE += senml_phydat +USEMODULE += embunit +USEMODULE += printf_float + +include $(RIOTBASE)/Makefile.include diff --git a/tests/senml_phydat/Makefile.ci b/tests/senml_phydat/Makefile.ci new file mode 100644 index 0000000000..0f18f55cfa --- /dev/null +++ b/tests/senml_phydat/Makefile.ci @@ -0,0 +1,16 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-nano \ + arduino-uno \ + atmega328p \ + atmega328p-xplained-mini \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-l011k4 \ + nucleo-l031k6 \ + samd10-xmini \ + stk3200 \ + stm32f030f4-demo \ + stm32g0316-disco \ + # diff --git a/tests/senml_phydat/app.config.test b/tests/senml_phydat/app.config.test new file mode 100644 index 0000000000..16bdcfd387 --- /dev/null +++ b/tests/senml_phydat/app.config.test @@ -0,0 +1,3 @@ +CONFIG_MODULE_SENML_PHYDAT=y +CONFIG_MODULE_EMBUNIT=y +CONFIG_MODULE_PRINTF_FLOAT=y diff --git a/tests/senml_phydat/main.c b/tests/senml_phydat/main.c new file mode 100644 index 0000000000..0781912f11 --- /dev/null +++ b/tests/senml_phydat/main.c @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2021 Silke Hofstra + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief SenML Phydat tests + * + * @author Silke Hofstra + * + * @} + */ + +#include +#include +#include + +#include "embUnit.h" +#include "senml/phydat.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#ifndef PRIi64 +#define PRIi64 "lli" +#endif + +typedef struct { + phydat_t phydat; + senml_value_t senml; +} time_test_t; + +typedef struct { + phydat_t phydat; + senml_value_t senml1; + senml_value_t senml2; + uint8_t dim; +} value_test_t; + +#define senml_s(s) { .attr = { .unit = SENML_UNIT_SECOND }, \ + .value = { SENML_TYPE_NUMERIC_INT, { .i = s } } } + +#define senml_f(v, u) { .attr = { .unit = u }, \ + .value = { SENML_TYPE_NUMERIC_FLOAT, { .f = v } } } + +#define senml_df(m, e, u) { .attr = { .unit = u }, \ + .value = { SENML_TYPE_NUMERIC_DECFRAC, { .df = { e, m } } } } + +static value_test_t value_tests[] = { + { + .phydat = { { 360, 0, 0 }, UNIT_M, 6 }, + .senml1 = senml_f(360e6, SENML_UNIT_METER), + .senml2 = senml_df(360, 6, SENML_UNIT_METER), + .dim = 0 + }, + { + .phydat = { { 864, 0, 0 }, UNIT_TIME, 2 }, + .senml1 = senml_f(86400, SENML_UNIT_SECOND), + .senml2 = senml_df(864, 2, SENML_UNIT_SECOND), + .dim = 0 + }, + { + .phydat = { { 0, 144, 0 }, UNIT_TIME, 1 }, + .senml1 = senml_f(1440, SENML_UNIT_MINUTE), + .senml2 = senml_df(144, 1, SENML_UNIT_MINUTE), + .dim = 1 + }, + { + .phydat = { { 0, 0, 24 }, UNIT_TIME, 0 }, + .senml1 = senml_f(24, SENML_UNIT_HOUR), + .senml2 = senml_df(24, 0, SENML_UNIT_HOUR), + .dim = 2 + }, + { + .phydat = { { 0, 0, 0 }, UNIT_TEMP_F, 3 }, + .senml1 = senml_f(255.37, SENML_UNIT_KELVIN), + .senml2 = senml_df(0, 3, SENML_UNIT_NONE), + }, + { + .phydat = { { 314, 0, 0 }, UNIT_G, -2 }, + .senml1 = senml_f(30.792881, SENML_UNIT_METER_PER_SQUARE_SECOND), + .senml2 = senml_df(314, -2, SENML_UNIT_NONE), + }, + { + .phydat = { { 988, 0, 0 }, UNIT_BAR, -3 }, + .senml1 = senml_f(98.8e3, SENML_UNIT_PASCAL), + .senml2 = senml_df(988, 2, SENML_UNIT_PASCAL), + }, + { + .phydat = { { 193, 0, 0 }, UNIT_GPM3, 5 }, + .senml1 = senml_f(19.3e3, SENML_UNIT_KILOGRAM_PER_CUBIC_METER), + .senml2 = senml_df(193, 2, SENML_UNIT_KILOGRAM_PER_CUBIC_METER), + }, + { + .phydat = { { 220, 0, 0 }, UNIT_GS, 3 }, + .senml1 = senml_f(22, SENML_UNIT_TESLA), + .senml2 = senml_df(220, -1, SENML_UNIT_TESLA), + } +}; + +void test_phydat_to_senml_float(void) +{ + senml_value_t res; + + for (size_t i = 0; i < ARRAY_SIZE(value_tests); i++) { + senml_value_t *exp = &(value_tests[i].senml1); + + phydat_to_senml_float(&res, &(value_tests[i].phydat), value_tests[i].dim); + + DEBUG("Float: %" PRIi16 "e%" PRIi16 " %s -> %.f %s\n", + value_tests[i].phydat.val[value_tests[i].dim], value_tests[i].phydat.scale, + phydat_unit_to_str(value_tests[i].phydat.unit), + res.value.value.f, + senml_unit_to_str(res.attr.unit)); + + TEST_ASSERT_EQUAL_STRING(senml_unit_to_str(exp->attr.unit), + senml_unit_to_str(res.attr.unit)); + TEST_ASSERT_EQUAL_INT(exp->value.type, res.value.type); + TEST_ASSERT_EQUAL_INT((int)roundf(exp->value.value.f * 100), + (int)roundf(res.value.value.f * 100)); + } +} + +void test_phydat_to_senml_decimal(void) +{ + senml_value_t res; + + for (size_t i = 0; i < ARRAY_SIZE(value_tests); i++) { + senml_value_t *exp = &(value_tests[i].senml2); + + phydat_to_senml_decimal(&res, &(value_tests[i].phydat), value_tests[i].dim); + + DEBUG("Decimal: %" PRIi16 "e%" PRIi16 " %s -> %" PRIi32 "e%" PRIi32 " %s\n", + value_tests[i].phydat.val[value_tests[i].dim], value_tests[i].phydat.scale, + phydat_unit_to_str(value_tests[i].phydat.unit), + res.value.value.df.m, res.value.value.df.e, + senml_unit_to_str(res.attr.unit)); + + TEST_ASSERT_EQUAL_STRING(senml_unit_to_str(exp->attr.unit), + senml_unit_to_str(res.attr.unit)); + TEST_ASSERT_EQUAL_INT(exp->value.type, res.value.type); + TEST_ASSERT_EQUAL_INT(exp->value.value.df.m, res.value.value.df.m); + TEST_ASSERT_EQUAL_INT(exp->value.value.df.e, res.value.value.df.e); + } +} + + +Test *tests_senml(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { +/* Don't run this test on CPUs with unpredictable rounding */ +#if !defined(__AVR__) && !defined(CPU_MSP430FXYZ) + new_TestFixture(test_phydat_to_senml_float), +#endif + new_TestFixture(test_phydat_to_senml_decimal), + }; + EMB_UNIT_TESTCALLER(senml_tests, NULL, NULL, fixtures); + return (Test *)&senml_tests; +} + +int main(void) +{ + TESTS_START(); + TESTS_RUN(tests_senml()); + TESTS_END(); + return 0; +} diff --git a/tests/senml_phydat/tests/01-run.py b/tests/senml_phydat/tests/01-run.py new file mode 100755 index 0000000000..8b4e23f2e7 --- /dev/null +++ b/tests/senml_phydat/tests/01-run.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2017 Freie Universität Berlin +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +import sys +from testrunner import run_check_unittests + + +if __name__ == "__main__": + sys.exit(run_check_unittests()) diff --git a/tests/senml_saul/Makefile b/tests/senml_saul/Makefile new file mode 100644 index 0000000000..a69acec8c5 --- /dev/null +++ b/tests/senml_saul/Makefile @@ -0,0 +1,8 @@ +include ../Makefile.tests_common + +USEMODULE += saul_default +USEMODULE += senml_saul +USEMODULE += fmt +USEMODULE += embunit + +include $(RIOTBASE)/Makefile.include diff --git a/tests/senml_saul/Makefile.ci b/tests/senml_saul/Makefile.ci new file mode 100644 index 0000000000..d5368f025e --- /dev/null +++ b/tests/senml_saul/Makefile.ci @@ -0,0 +1,12 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-nano \ + arduino-uno \ + atmega328p \ + atmega328p-xplained-mini \ + nucleo-f031k6 \ + nucleo-l011k4 \ + samd10-xmini \ + stm32f030f4-demo \ + # diff --git a/tests/senml_saul/main.c b/tests/senml_saul/main.c new file mode 100644 index 0000000000..002f7bcde2 --- /dev/null +++ b/tests/senml_saul/main.c @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 Silke Hofstra + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Short SenML SAUL test + * + * @author Silke Hofstra + * + * @} + */ + +#include +#include + +#include "embUnit.h" +#include "senml/saul.h" +#include "fmt.h" + +#define BUF_SIZE (128) + +static uint8_t cbor_buf[BUF_SIZE]; +static char result[2 * BUF_SIZE]; +static char expect[] = "9FFF"; + +void test_senml_encode(void) +{ + size_t len = senml_saul_encode_cbor(cbor_buf, sizeof cbor_buf, saul_reg); + + /* Encode as hex */ + fmt_bytes_hex(result, cbor_buf, len); + + /* Compare hex result */ + TEST_ASSERT_EQUAL_INT(2 * len, sizeof expect - 1); + TEST_ASSERT_EQUAL_INT(0, strncmp(expect, result, len)); +} + +Test *tests_senml(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_senml_encode), + }; + EMB_UNIT_TESTCALLER(senml_tests, NULL, NULL, fixtures); + return (Test *)&senml_tests; +} + +int main(void) +{ + TESTS_START(); + TESTS_RUN(tests_senml()); + TESTS_END(); + return 0; +} diff --git a/tests/senml_saul/tests/01-run.py b/tests/senml_saul/tests/01-run.py new file mode 100755 index 0000000000..8b4e23f2e7 --- /dev/null +++ b/tests/senml_saul/tests/01-run.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2017 Freie Universität Berlin +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +import sys +from testrunner import run_check_unittests + + +if __name__ == "__main__": + sys.exit(run_check_unittests())