diff --git a/sys/include/phydat.h b/sys/include/phydat.h index e7847ce549..73fc4cea9b 100644 --- a/sys/include/phydat.h +++ b/sys/include/phydat.h @@ -37,6 +37,8 @@ #include #include +#include + #include "modules.h" #ifdef __cplusplus @@ -177,30 +179,55 @@ void phydat_dump(phydat_t *data, uint8_t dim); /** * @brief Convert the given unit to a string * - * @param[in] unit unit to convert + * @param[in] unit unit to convert * * @return string representation of given unit (e.g. V or m) * @return NULL if unit was not recognized + * + * @deprecated Use @ref phydat_unit_print or @ref phydat_unit_write instead + * + * @warning For classic Harvard architectures a small buffer is used to store + * the string, so that subsequent (or concurrent!) calls will + * overwrite the output. */ 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' + * @brief Same as @ref phydat_unit_to_str * - * This function is useful when converting phydat_t structures to non-binary - * representations like JSON or XML. + * In practise all users used the verbose function anyway. Hence, + * @ref phydat_unit_to_str just covers all units and this is just a backward + * compatibility wrapper. * - * 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 + * @deprecated Use @ref phydat_unit_print or @ref phydat_unit_write instead */ const char *phydat_unit_to_str_verbose(uint8_t unit); +/** + * @brief Print a unit + * + * @param[in] unit unit to print + */ +void phydat_unit_print(uint8_t unit); + +/** + * @brief Write the string representation of the given unit into the given + * buffer + * + * @param[out] dest destination buffer to write to + * @param[in] max_size size of the buffer at @p dest + * @param[in] unit unit to convert + * + * @return Number of bytes written + * @retval -EOVERFLOW buffer at @p dest is too small + * @retval -EINVAL invalid unit in @p unit + * + * @warning The function will never write a terminating zero byte + * @note If you pass `NULL` for @p dest, it will return the number of bytes + * it would write (regardless of @p max_size) + */ +ssize_t phydat_unit_write(char *dest, size_t max_size, uint8_t unit); + /** * @brief Convert the given scale factor to an SI prefix * diff --git a/sys/phydat/phydat_json.c b/sys/phydat/phydat_json.c index 176016f8c0..9ebdd2eb06 100644 --- a/sys/phydat/phydat_json.c +++ b/sys/phydat/phydat_json.c @@ -20,8 +20,9 @@ #include -#include "fmt.h" #include "assert.h" +#include "flash_utils.h" +#include "fmt.h" #include "phydat.h" #define STATIC_LEN (14U) @@ -32,11 +33,11 @@ static size_t _bool_to_str(int16_t val, char *buf) { if (val) { - memcpy(buf, "true", 4); + flash_memcpy(buf, TO_FLASH("true"), 4); return 4; } else { - memcpy(buf, "false", 5); + flash_memcpy(buf, TO_FLASH("false"), 5); return 5; } } @@ -60,10 +61,10 @@ size_t phydat_to_json(const phydat_t *data, size_t dim, char *buf) pos += (data->val[i]) ? 4 : 5; /* true: 4, false: 5 */ } } - pos += strlen(phydat_unit_to_str_verbose(data->unit)); + pos += phydat_unit_write(NULL, 0, data->unit); } else { - memcpy(buf, "{\"d\":", 5); + flash_memcpy(buf, TO_FLASH("{\"d\":"), 5); pos += 5; /* write data */ if (dim > 1) { @@ -84,13 +85,11 @@ size_t phydat_to_json(const phydat_t *data, size_t dim, char *buf) buf[pos++] = ','; } /* add unit */ - memcpy(&buf[pos], "\"u\":\"", 5); + flash_memcpy(&buf[pos], TO_FLASH("\"u\":\""), 5); pos += 5; - const char *u = phydat_unit_to_str_verbose(data->unit); - strcpy(&buf[pos], u); - pos += strlen(u); + pos += phydat_unit_write(&buf[pos], SIZE_MAX, data->unit); /* terminate the JSON string */ - memcpy(&buf[pos], "\"}", 2); + flash_memcpy(&buf[pos], TO_FLASH("\"}"), 2); pos += 2; buf[pos++] = '\0'; } diff --git a/sys/phydat/phydat_str.c b/sys/phydat/phydat_str.c index 566a1ca8a2..b2c03b8861 100644 --- a/sys/phydat/phydat_str.c +++ b/sys/phydat/phydat_str.c @@ -18,17 +18,19 @@ * @} */ -#include +#include #include +#include #include "assert.h" +#include "flash_utils.h" #include "fmt.h" #include "phydat.h" void phydat_dump(phydat_t *data, uint8_t dim) { if (data == NULL || dim > PHYDAT_DIM) { - puts("Unable to display data object"); + printf("Unable to display data object\n"); return; } printf("Data:"); @@ -50,19 +52,19 @@ void phydat_dump(phydat_t *data, uint8_t dim) char scale_prefix; switch (data->unit) { - case UNIT_UNDEF: - case UNIT_NONE: - case UNIT_M2: - case UNIT_M3: - case UNIT_PERCENT: - case UNIT_TEMP_C: - case UNIT_TEMP_F: - case UNIT_DBM: - /* no string conversion */ - scale_prefix = '\0'; - break; - default: - scale_prefix = phydat_prefix_from_scale(data->scale); + case UNIT_UNDEF: + case UNIT_NONE: + case UNIT_M2: + case UNIT_M3: + case UNIT_PERCENT: + case UNIT_TEMP_C: + case UNIT_TEMP_F: + case UNIT_DBM: + /* no string conversion */ + scale_prefix = '\0'; + break; + default: + scale_prefix = phydat_prefix_from_scale(data->scale); } printf("\t"); @@ -92,75 +94,158 @@ void phydat_dump(phydat_t *data, uint8_t dim) printf("%11s ", num); } - printf("%s\n", phydat_unit_to_str(data->unit)); + if ((data->unit != UNIT_NONE) && (data->unit != UNIT_UNDEF) + && (data->unit != UNIT_BOOL)) { + phydat_unit_print(data->unit); + } + puts(""); } } +static FLASH_ATTR const char _unit_celsius[] = "°C"; +static FLASH_ATTR const char _unit_fahrenheit[] = "°F"; +static FLASH_ATTR const char _unit_kelvin[] = "K"; +static FLASH_ATTR const char _unit_lux[] = "lx"; +static FLASH_ATTR const char _unit_metre[] = "m"; +static FLASH_ATTR const char _unit_square_metre[] = "m^2"; +static FLASH_ATTR const char _unit_cubic_metre[] = "m^3"; +static FLASH_ATTR const char _unit_g_force[] = "gₙ"; +static FLASH_ATTR const char _unit_degree_per_second[] = "dps"; +static FLASH_ATTR const char _unit_gram[] = "g"; +static FLASH_ATTR const char _unit_ampere[] = "A"; +static FLASH_ATTR const char _unit_volt[] = "V"; +static FLASH_ATTR const char _unit_watt[] = "W"; +static FLASH_ATTR const char _unit_decibel_milliwatts[] = "dBm"; +static FLASH_ATTR const char _unit_gauss[] = "Gs"; +static FLASH_ATTR const char _unit_tesla[] = "T"; +static FLASH_ATTR const char _unit_bar[] = "Bar"; +static FLASH_ATTR const char _unit_pascal[] = "Pa"; +static FLASH_ATTR const char _unit_permille[] = "permille"; +static FLASH_ATTR const char _unit_parts_per_million[] = "ppm"; +static FLASH_ATTR const char _unit_parts_per_billion[] = "ppb"; +static FLASH_ATTR const char _unit_candela[] = "cd"; +static FLASH_ATTR const char _unit_percent[] = "%"; +static FLASH_ATTR const char _unit_counts[] = "cts"; +static FLASH_ATTR const char _unit_coulomb[] = "C"; +static FLASH_ATTR const char _unit_gram_per_cubic_metre[] = "g/m^3"; +static FLASH_ATTR const char _unit_farad[] = "F"; +static FLASH_ATTR const char _unit_potential_of_hydrogen[] = "pH"; +static FLASH_ATTR const char _unit_count_per_cubic_metre[] = "#/m^3"; +static FLASH_ATTR const char _unit_ohm[] = "ohm"; +static FLASH_ATTR const char _unit_undefined[] = "undefined"; +static FLASH_ATTR const char _unit_none[] = "none"; +static FLASH_ATTR const char _unit_time[] = "time"; +static FLASH_ATTR const char _unit_date[] = "date"; + +static FLASH_ATTR const char * FLASH_ATTR const _unit_to_str[] = { + [UNIT_TEMP_C] = _unit_celsius, + [UNIT_TEMP_F] = _unit_fahrenheit, + [UNIT_TEMP_K] = _unit_kelvin, + [UNIT_LUX] = _unit_lux, + [UNIT_M] = _unit_metre, + [UNIT_M2] = _unit_square_metre, + [UNIT_M3] = _unit_cubic_metre, + [UNIT_G_FORCE] = _unit_g_force, + [UNIT_DPS] = _unit_degree_per_second, + [UNIT_GRAM] = _unit_gram, + [UNIT_A] = _unit_ampere, + [UNIT_V] = _unit_volt, + [UNIT_W] = _unit_watt, + [UNIT_DBM] = _unit_decibel_milliwatts, + [UNIT_GS] = _unit_gauss, + [UNIT_T] = _unit_tesla, + [UNIT_BAR] = _unit_bar, + [UNIT_PA] = _unit_pascal, + [UNIT_PERMILL] = _unit_permille, + [UNIT_PPM] = _unit_parts_per_million, + [UNIT_PPB] = _unit_parts_per_billion, + [UNIT_CD] = _unit_candela, + [UNIT_PERCENT] = _unit_percent, + [UNIT_CTS] = _unit_counts, + [UNIT_COULOMB] = _unit_coulomb, + [UNIT_GPM3] = _unit_gram_per_cubic_metre, + [UNIT_F] = _unit_farad, + [UNIT_PH] = _unit_potential_of_hydrogen, + [UNIT_CPM3] = _unit_count_per_cubic_metre, + [UNIT_OHM] = _unit_ohm, + [UNIT_UNDEF] = _unit_undefined, + [UNIT_NONE] = _unit_none, + [UNIT_BOOL] = _unit_none, + [UNIT_TIME] = _unit_time, + [UNIT_DATE] = _unit_date, +}; + +ssize_t phydat_unit_write(char *dest, size_t max_size, uint8_t unit) +{ + if ((unit >= ARRAY_SIZE(_unit_to_str)) || (_unit_to_str[unit]) == NULL) { + return -EINVAL; + } + size_t len = flash_strlen(_unit_to_str[unit]); + if (dest) { + if (max_size < len) { + return -EOVERFLOW; + } + flash_memcpy(dest, _unit_to_str[unit], len); + } + + return len; +} + const char *phydat_unit_to_str(uint8_t unit) { - switch (unit) { - case UNIT_TEMP_C: return "°C"; - case UNIT_TEMP_F: return "°F"; - case UNIT_TEMP_K: return "K"; - case UNIT_LUX: return "lx"; - case UNIT_M: return "m"; - case UNIT_M2: return "m^2"; - case UNIT_M3: return "m^3"; - case UNIT_G_FORCE: return "gₙ"; - case UNIT_DPS: return "dps"; - case UNIT_GRAM: return "g"; - case UNIT_A: return "A"; - case UNIT_V: return "V"; - case UNIT_W: return "W"; - case UNIT_DBM: return "dBm"; - case UNIT_GAUSS: return "Gs"; - case UNIT_T: return "T"; - case UNIT_BAR: return "Bar"; - case UNIT_PA: return "Pa"; - case UNIT_PERMILL: return "permille"; - case UNIT_PPM: return "ppm"; - case UNIT_PPB: return "ppb"; - case UNIT_CD: return "cd"; - case UNIT_PERCENT: return "%"; - case UNIT_CTS: return "cts"; - case UNIT_COULOMB: return "C"; - case UNIT_GPM3: return "g/m^3"; - case UNIT_F: return "F"; - case UNIT_PH: return "pH"; - case UNIT_CPM3: return "#/m^3"; - case UNIT_OHM: return "ohm"; - - default: return ""; +#if IS_ACTIVE(HAS_FLASH_UTILS_ARCH) + /* Yeah, this is as bad as it looks... The function is deprecated for this + * reason and it will only affect AVR users, for whom this is a good + * trade-off. */ + static char buf[8]; + ssize_t pos = phydat_unit_write(buf, sizeof(buf) - 1, unit); + assert(pos >= 0); + if (pos < 0) { + pos = 0; } + buf[pos] = '\0'; + return buf; +#else + if ((unit < ARRAY_SIZE(_unit_to_str)) && (_unit_to_str[unit])) { + return _unit_to_str[unit]; + } + return ""; +#endif } 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); + return phydat_unit_to_str(unit); +} + +void phydat_unit_print(uint8_t unit) +{ + if ((unit < ARRAY_SIZE(_unit_to_str)) && (_unit_to_str[unit]) != NULL) { + flash_print_str(_unit_to_str[unit]); } } char phydat_prefix_from_scale(int8_t scale) { - switch (scale) { - case -3: return 'm'; - case -6: return 'u'; - case -9: return 'n'; - case -12: return 'p'; - case -15: return 'f'; - case 2: return 'h'; - case 3: return 'k'; - case 6: return 'M'; - case 9: return 'G'; - case 12: return 'T'; - case 15: return 'P'; - default: return '\0'; + static FLASH_ATTR const char _prefix[] = { + 'f', '\0', '\0', + 'p', '\0', '\0', + 'n', '\0', '\0', + 'u', '\0', '\0', + 'm', '\0', '\0', + '\0', '\0', 'h', + 'k', '\0', '\0', + 'M', '\0', '\0', + 'G', '\0', '\0', + 'T', '\0', '\0', + 'P', + }; + + int8_t idx = scale + ARRAY_SIZE(_prefix) / 2; + + if ((idx < 0) || (idx >= (int8_t)ARRAY_SIZE(_prefix))) { + return '\0'; } + + return _prefix[idx]; } diff --git a/tests/senml_phydat/main.c b/tests/senml_phydat/main.c index 2704bb168d..60de8e0ba2 100644 --- a/tests/senml_phydat/main.c +++ b/tests/senml_phydat/main.c @@ -23,6 +23,7 @@ #include #include "embUnit.h" +#include "flash_utils.h" #include "senml/phydat.h" #define ENABLE_DEBUG (0) @@ -114,7 +115,7 @@ void test_phydat_to_senml_float(void) phydat_to_senml_float(&res, &(value_tests[i].phydat), value_tests[i].dim); - DEBUG("Float: %" PRIi16 "e%" PRIi16 " %s -> %.f %s\n", + DEBUG("Float: %" PRIi16 "e%" PRIi16 " %" PRIsflash " -> %.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, @@ -137,7 +138,7 @@ void test_phydat_to_senml_decimal(void) phydat_to_senml_decimal(&res, &(value_tests[i].phydat), value_tests[i].dim); - DEBUG("Decimal: %" PRIi16 "e%" PRIi16 " %s -> %" PRIi32 "e%" PRIi32 " %s\n", + DEBUG("Decimal: %" PRIi16 "e%" PRIi16 " %s -> %" PRIi32 "e%" PRIi32 " %" PRIsflash"\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,