Merge pull request #7271 from haukepetersen/add_phydat_tojson

sys/phydat: added phydat to JSON converter
This commit is contained in:
Martine Lenders 2020-05-22 09:08:18 +02:00 committed by GitHub
commit 0ba2df9b4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 341 additions and 4 deletions

View File

@ -35,6 +35,7 @@
#ifndef PHYDAT_H
#define PHYDAT_H
#include <stddef.h>
#include <stdint.h>
#include "kernel_defines.h"
@ -179,6 +180,23 @@ void phydat_dump(phydat_t *data, uint8_t dim);
*/
const char *phydat_unit_to_str(uint8_t unit);
/**
* @brief Return a string representation for every unit, including
* non-physical units like 'none' or 'time'
*
* This function is useful when converting phydat_t structures to non-binary
* representations like JSON or XML.
*
* In practice, this function extends phydat_unit_to_str() with additional
* identifiers for non physical units.
*
* @param[in] unit unit to convert
*
* @return string representation of given unit
* @return empty string ("") if unit was not recognized
*/
const char *phydat_unit_to_str_verbose(uint8_t unit);
/**
* @brief Convert the given scale factor to an SI prefix
*
@ -222,6 +240,51 @@ char phydat_prefix_from_scale(int8_t scale);
*/
void phydat_fit(phydat_t *dat, const int32_t *values, unsigned int dim);
/**
* @brief Convert the given phydat_t structure into a JSON string
*
* The output string written to @p buf will be `\0` terminated. You must make
* sure, that the given @p buf is large enough to hold the resulting string. You
* can call the function with `@p buf := NULL` to simply calculate the size of
* the JSON string without writing anything.
*
* The formatted JSON string will have the following format:
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.json}
* // case (dim == 1):
* {
* "d": 21.45,
* "u": "°C"
* }
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.json}
* // case (dim > 1), dim := 3 in this case:
* {
* "d": [1.02, 0.23, -0.81],
* "u": "g"
* }
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* The data will be encoded as fixed point number based on the given scale
* factor.
*
* For encoding the unit, this function uses the extended
* phydat_unit_to_str_verbose() function to also print units for non-SI types,
* e.g. it will produce `..."u":"date"}` for @ref UNIT_DATE or `..."u":"none"}`
* for @ref UNIT_NONE.
*
* @param[in] data data to encode
* @param[in] dim dimensions used in @p data, MUST be > 0 and < PHYDAT_DIM
* @param[out] buf target buffer for the JSON string, or NULL
*
* @pre @p dim > 0
* @pre @p dim < PHYDAT_DIM
*
* @return number of bytes (potentially) written to @p buf, including `\0`
* terminator
*/
size_t phydat_to_json(const phydat_t *data, size_t dim, char *buf);
#ifdef __cplusplus
}
#endif

99
sys/phydat/phydat_json.c Normal file
View File

@ -0,0 +1,99 @@
/*
* 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.
*/
/**
* @ingroup sys_phydat
* @{
*
* @file
* @brief Convert phydat_t structs to human readable JSON strings
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
*
* @}
*/
#include <string.h>
#include "fmt.h"
#include "assert.h"
#include "phydat.h"
#define STATIC_LEN (14U)
/**
* @note @p buf must be at least 5 bytes of size
*/
static size_t _bool_to_str(int16_t val, char *buf)
{
if (val) {
memcpy(buf, "true", 4);
return 4;
}
else {
memcpy(buf, "false", 5);
return 5;
}
}
size_t phydat_to_json(const phydat_t *data, size_t dim, char *buf)
{
assert((dim > 0) && (dim < PHYDAT_DIM));
size_t pos = 0;
if (buf == NULL) {
pos = STATIC_LEN;
if (dim > 1) {
pos += (2 + (dim - 1)); /* array parens + separating commas */
}
for (size_t i = 0; i < dim; i++) {
if (data->unit != UNIT_BOOL) {
pos += fmt_s16_dfp(NULL, data->val[i], (int)data->scale);
}
else {
pos += (data->val[i]) ? 4 : 5; /* true: 4, false: 5 */
}
}
pos += strlen(phydat_unit_to_str_verbose(data->unit));
}
else {
memcpy(buf, "{\"d\":", 5);
pos += 5;
/* write data */
if (dim > 1) {
buf[pos++] = '[';
}
for (size_t i = 0; i < dim; i++) {
if (data->unit != UNIT_BOOL) {
pos += fmt_s16_dfp(&buf[pos], data->val[i], (int)data->scale);
}
else {
pos += _bool_to_str(data->val[i], &buf[pos]);
}
buf[pos++] = ',';
}
/* override last comma if needed */
if (dim > 1) {
buf[pos - 1] = ']';
buf[pos++] = ',';
}
/* add unit */
memcpy(&buf[pos], "\"u\":\"", 5);
pos += 5;
const char *u = phydat_unit_to_str_verbose(data->unit);
strcpy(&buf[pos], u);
pos += strlen(u);
/* terminate the JSON string */
memcpy(&buf[pos], "\"}", 2);
pos += 2;
buf[pos++] = '\0';
}
return pos;
}

View File

@ -11,7 +11,7 @@
* @{
*
* @file
* @brief Generic sensor/actuator data handling
* @brief String helper functions for formatting and dumping phydat data
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
*
@ -133,6 +133,19 @@ const char *phydat_unit_to_str(uint8_t unit)
}
}
const char *phydat_unit_to_str_verbose(uint8_t unit)
{
switch (unit) {
case UNIT_UNDEF: return "undefined";
case UNIT_NONE: /* fall through */
case UNIT_BOOL:
return "none";
case UNIT_TIME: return "time";
case UNIT_DATE: return "date";
default: return phydat_unit_to_str(unit);
}
}
char phydat_prefix_from_scale(int8_t scale)
{
switch (scale) {

View File

@ -1,12 +1,27 @@
/*
* Copyright (C) 2018 Eistec AB
* 2018 Otto-von-Guericke-Universität Magdeburg
* 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.
*/
/**
* @{
*
* @file
* @brief Unittests for the phydat module
*
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se>
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
*
* @}
*/
#include <string.h>
#include "embUnit.h"
#include "tests-phydat.h"
@ -20,6 +35,65 @@
#define PHYDAT_FIT_TRADE_PRECISION_FOR_ROM 1
#endif
#if (PHYDAT_DIM != 3)
#error "PHYDAT unittests are only applicable if PHYDAT_DIM is 3"
#endif
#define BUFSIZE (128U)
static char test[BUFSIZE];
typedef struct {
int dim;
phydat_t dat;
const char *json;
} tdat_t;
/* define test data */
static tdat_t data[] = {
{
.dim = 1,
.dat = { { 2345, 0, 0 }, UNIT_TEMP_C, -2 },
.json = "{\"d\":23.45,\"u\":\"°C\"}",
},
{
.dim = 3,
.dat = { { 1032, 10, -509 }, UNIT_G, -3 },
.json = "{\"d\":[1.032,0.010,-0.509],\"u\":\"g\"}",
},
{
.dim = 3,
.dat = { { 1200, 38, 98 }, UNIT_M, -3 },
.json = "{\"d\":[1.200,0.038,0.098],\"u\":\"m\"}",
},
{
.dim = 2,
.dat = { { 19, 23, 0 }, UNIT_NONE, 0 },
.json = "{\"d\":[19,23],\"u\":\"none\"}",
},
{
.dim = 1,
.dat = { { 1, 0, 0 }, UNIT_BOOL, 0 },
.json = "{\"d\":true,\"u\":\"none\"}",
},
{
.dim = 3,
.dat = { { 1, 0, 1 }, UNIT_BOOL, 0 },
.json = "{\"d\":[true,false,true],\"u\":\"none\"}",
},
{
.dim = 3,
.dat = { { 1, 1, 1970 }, UNIT_DATE, 0 },
.json = "{\"d\":[1,1,1970],\"u\":\"date\"}",
},
{
.dim = 3,
.dat = { { 23, 59, 14}, UNIT_TIME, 0 },
.json = "\"d\":[23,59,14],\"u\":\"time\"}",
}
};
static void test_phydat_fit(void)
{
/* Input values for each test: */
@ -114,10 +188,94 @@ static void test_phydat_fit(void)
}
}
static void test_unitstr__success(void)
{
/* test the verbose cases */
TEST_ASSERT_EQUAL_STRING("undefined", phydat_unit_to_str_verbose(UNIT_UNDEF));
TEST_ASSERT_EQUAL_STRING("none", phydat_unit_to_str_verbose(UNIT_NONE));
TEST_ASSERT_EQUAL_STRING("none", phydat_unit_to_str_verbose(UNIT_BOOL));
TEST_ASSERT_EQUAL_STRING("time", phydat_unit_to_str_verbose(UNIT_TIME));
TEST_ASSERT_EQUAL_STRING("date", phydat_unit_to_str_verbose(UNIT_DATE));
TEST_ASSERT_EQUAL_STRING("°C", phydat_unit_to_str_verbose(UNIT_TEMP_C));
TEST_ASSERT_EQUAL_STRING("°F", phydat_unit_to_str_verbose(UNIT_TEMP_F));
TEST_ASSERT_EQUAL_STRING("K", phydat_unit_to_str_verbose(UNIT_TEMP_K));
TEST_ASSERT_EQUAL_STRING("lx", phydat_unit_to_str_verbose(UNIT_LUX));
TEST_ASSERT_EQUAL_STRING("m", phydat_unit_to_str_verbose(UNIT_M));
TEST_ASSERT_EQUAL_STRING("m^2", phydat_unit_to_str_verbose(UNIT_M2));
TEST_ASSERT_EQUAL_STRING("m^3", phydat_unit_to_str_verbose(UNIT_M3));
TEST_ASSERT_EQUAL_STRING("g", phydat_unit_to_str_verbose(UNIT_G));
TEST_ASSERT_EQUAL_STRING("dps", phydat_unit_to_str_verbose(UNIT_DPS));
TEST_ASSERT_EQUAL_STRING("G", phydat_unit_to_str_verbose(UNIT_GR));
TEST_ASSERT_EQUAL_STRING("A", phydat_unit_to_str_verbose(UNIT_A));
TEST_ASSERT_EQUAL_STRING("V", phydat_unit_to_str_verbose(UNIT_V));
TEST_ASSERT_EQUAL_STRING("W", phydat_unit_to_str_verbose(UNIT_W));
TEST_ASSERT_EQUAL_STRING("dBm", phydat_unit_to_str_verbose(UNIT_DBM));
TEST_ASSERT_EQUAL_STRING("Gs", phydat_unit_to_str_verbose(UNIT_GS));
TEST_ASSERT_EQUAL_STRING("Bar", phydat_unit_to_str_verbose(UNIT_BAR));
TEST_ASSERT_EQUAL_STRING("Pa", phydat_unit_to_str_verbose(UNIT_PA));
TEST_ASSERT_EQUAL_STRING("permille", phydat_unit_to_str_verbose(UNIT_PERMILL));
TEST_ASSERT_EQUAL_STRING("ppm", phydat_unit_to_str_verbose(UNIT_PPM));
TEST_ASSERT_EQUAL_STRING("ppb", phydat_unit_to_str_verbose(UNIT_PPB));
TEST_ASSERT_EQUAL_STRING("cd", phydat_unit_to_str_verbose(UNIT_CD));
TEST_ASSERT_EQUAL_STRING("%", phydat_unit_to_str_verbose(UNIT_PERCENT));
TEST_ASSERT_EQUAL_STRING("cts", phydat_unit_to_str_verbose(UNIT_CTS));
TEST_ASSERT_EQUAL_STRING("C", phydat_unit_to_str_verbose(UNIT_COULOMB));
TEST_ASSERT_EQUAL_STRING("g/m^3", phydat_unit_to_str_verbose(UNIT_GPM3));
TEST_ASSERT_EQUAL_STRING("F", phydat_unit_to_str_verbose(UNIT_F));
TEST_ASSERT_EQUAL_STRING("pH", phydat_unit_to_str_verbose(UNIT_PH));
TEST_ASSERT_EQUAL_STRING("#/m^3", phydat_unit_to_str_verbose(UNIT_CPM3));
TEST_ASSERT_EQUAL_STRING("ohm", phydat_unit_to_str_verbose(UNIT_OHM));
}
static void test_json__success(void)
{
size_t len;
len = phydat_to_json(&data[0].dat, data[0].dim, NULL);
TEST_ASSERT_EQUAL_INT((strlen(data[0].json) + 1), len);
len = phydat_to_json(&data[0].dat, data[0].dim, test);
TEST_ASSERT_EQUAL_INT(strlen(data[0].json), strlen(test));
TEST_ASSERT_EQUAL_STRING(data[0].json, (const char *)test);
len = phydat_to_json(&data[1].dat, data[1].dim, NULL);
TEST_ASSERT_EQUAL_INT((strlen(data[1].json) + 1), len);
len = phydat_to_json(&data[1].dat, data[1].dim, test);
TEST_ASSERT_EQUAL_INT(strlen(data[1].json), strlen(test));
TEST_ASSERT_EQUAL_STRING(data[1].json, (const char *)test);
len = phydat_to_json(&data[2].dat, data[2].dim, NULL);
TEST_ASSERT_EQUAL_INT((strlen(data[2].json) + 1), len);
len = phydat_to_json(&data[2].dat, data[2].dim, test);
TEST_ASSERT_EQUAL_INT(strlen(data[2].json), strlen(test));
TEST_ASSERT_EQUAL_STRING(data[2].json, (const char *)test);
len = phydat_to_json(&data[3].dat, data[3].dim, NULL);
TEST_ASSERT_EQUAL_INT((strlen(data[3].json) + 1), len);
len = phydat_to_json(&data[3].dat, data[3].dim, test);
TEST_ASSERT_EQUAL_INT(strlen(data[3].json), strlen(test));
TEST_ASSERT_EQUAL_STRING(data[3].json, (const char *)test);
len = phydat_to_json(&data[4].dat, data[4].dim, NULL);
TEST_ASSERT_EQUAL_INT((strlen(data[4].json) + 1), len);
len = phydat_to_json(&data[4].dat, data[4].dim, test);
TEST_ASSERT_EQUAL_INT(strlen(data[4].json), strlen(test));
TEST_ASSERT_EQUAL_STRING(data[4].json, (const char *)test);
len = phydat_to_json(&data[5].dat, data[5].dim, NULL);
TEST_ASSERT_EQUAL_INT((strlen(data[5].json) + 1), len);
len = phydat_to_json(&data[5].dat, data[5].dim, test);
TEST_ASSERT_EQUAL_INT(strlen(data[5].json), strlen(test));
TEST_ASSERT_EQUAL_STRING(data[5].json, (const char *)test);
}
Test *tests_phydat_tests(void)
{
EMB_UNIT_TESTFIXTURES(fixtures) {
new_TestFixture(test_phydat_fit),
new_TestFixture(test_unitstr__success),
new_TestFixture(test_json__success),
};
EMB_UNIT_TESTCALLER(phydat_tests, NULL, NULL, fixtures);

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2018 Eistec AB
* 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
@ -14,18 +15,21 @@
* @brief Unittests for the phydat module
*
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se>
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
*/
#ifndef TESTS_PHYDAT_H
#define TESTS_PHYDAT_H
#include "embUnit/embUnit.h"
#include "embUnit.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief The entry point of this test suite.
*/
* @brief The entry point of this test suite.
*/
void tests_phydat(void);
#ifdef __cplusplus