/* * Copyright (C) 2015 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. */ /** * @defgroup sys_phydat Phydat * @ingroup sys * @brief Generic data container for physical data and utility functions * * The purpose of this module is to introduce a common view on physical data * throughout RIOT. This data is typically the output from sensor readings, data * aggregation, and also the input for actuators. * * The idea is to enable different sensor/actuator drivers and other RIOT * modules to exchange and have the same view on this kind of data. Labeling * data with a unit type it's scaling makes it possible to pipe data between * modules in an automated fashion without the need of specialized software * wrappers and/or data normalization modules. * * @todo It might make sense to introduce additional data types for * increased precision, i.e. something like phydat_float_t... * * @{ * * @file * @brief Generic data container for physical data interface * * @author Hauke Petersen */ #ifndef PHYDAT_H #define PHYDAT_H #include #include #include "kernel_defines.h" #ifdef __cplusplus extern "C" { #endif /** * @brief The fixed number of dimensions we work with * * We use a fixed number of 3 dimensions, as many physical values we encounter * can be expressed this way. In practice we have e.g. readings from * accelerometers, gyros, color sensors, or set data for RGB LEDs. * * When expressing 1-dimensional data we just ignore the 2 higher dimension. * This leads to a slight overhead of some byte of memory - but we benefit from * a unified data structure for passing around physical data. */ #define PHYDAT_DIM (3U) /** * @brief The maximum length of a scaling string */ #define PHYDAT_SCALE_STR_MAXLEN (sizeof("*E-128\0")) /** * @brief Definition of physical units and comparable data types * * This list should contain all needed physical units (e.g. SI units), but also * non-physical units that can be used to define the type of data passed around. * This can be for example BOOL or aggregate values. As rule of thumb, the unit * list can contain anything that helps two modules automatically negotiate, if * they can understand each other. * * @note Extent this list as needed. */ enum { /* generic values */ UNIT_UNDEF, /**< unit undefined */ UNIT_NONE, /**< data has no physical unit */ /* temperature */ UNIT_TEMP_C, /**< degree Celsius */ UNIT_TEMP_F, /**< degree Fahrenheit */ UNIT_TEMP_K, /**< Kelvin */ /* illuminance */ UNIT_LUX, /**< Lux (lx) */ /* dimension */ UNIT_M, /**< meters */ UNIT_M2, /**< square meters */ UNIT_M3, /**< cubic meters */ /* kinetic */ UNIT_G, /**< gravitational force */ UNIT_DPS, /**< degree per second */ /* weight */ UNIT_GR, /**< grams - not using the SI unit (kg) here to make scale * handling simpler */ /* electricity */ UNIT_A, /**< Ampere */ UNIT_V, /**< Volts */ UNIT_W, /**< Watt */ UNIT_GS, /**< gauss */ UNIT_T, /**< Tesla */ UNIT_DBM, /**< decibel-milliwatts */ UNIT_COULOMB, /**< coulomb */ UNIT_F, /**< Farad */ UNIT_OHM, /**< Ohm */ /* electrochemical */ UNIT_PH, /**< pH */ /* pressure */ UNIT_BAR, /**< Beer? */ UNIT_PA, /**< Pascal */ /* light */ UNIT_CD, /**< Candela */ /* logical */ UNIT_BOOL, /**< boolean value [0|1] */ UNIT_CTS, /**< counts */ UNIT_PERCENT, /**< out of 100 */ UNIT_PERMILL, /**< out of 1000 */ UNIT_PPM, /**< part per million */ UNIT_PPB, /**< part per billion */ /* aggregate values */ UNIT_TIME, /**< the three dimensions contain sec, min, and hours */ UNIT_DATE, /**< the 3 dimensions contain days, months and years */ /* mass concentration */ UNIT_GPM3, /**< grams per cubic meter */ /* number concentration */ UNIT_CPM3 /**< count per cubic meter */ /* extend this list as needed */ }; /** * @brief Generic data structure for expressing physical values * * Physical data is expressed in a 3-dimensional tuple of values. In addition * to the data fields, this struct contains further the (physical) unit and the * scale factor of the data. The unit is expressed as constant. The scale factor * is expressed as power of 10 (10^factor). * * The combination of signed 16-bit numbers with and the scale factor gives us a * very high dynamic range (from -32*10^-131 to 32*10^130). In a wider sense we * are saving the values as fixed floating points... * * The scale factor is identical for all 3 values. * * In a traditional (scientific) computational system the obvious choice for the * used data type would be to use floats. We are however on heavily resource * constrained (even 8-bit) embedded systems, so we use int16_t here. As most * sensor are in some way ADC based, they normally do not use a higher accuracy * than 12-14bit, so using 16-bit integers to represent this data is good enough * in most cases. */ typedef struct { int16_t val[PHYDAT_DIM]; /**< the 3 generic dimensions of data */ uint8_t unit; /**< the (physical) unit of the data */ int8_t scale; /**< the scale factor, 10^*scale* */ } phydat_t; /** * @brief Minimum value for phydat_t::val */ #define PHYDAT_MIN (INT16_MIN) /** * @brief Maximum value for phydat_t::val */ #define PHYDAT_MAX (INT16_MAX) /** * @brief Dump the given data container to STDIO * * @param[in] data data container to dump * @param[in] dim number of dimension of @p data to dump */ void phydat_dump(phydat_t *data, uint8_t dim); /** * @brief Convert the given unit to a string * * @param[in] unit unit to convert * * @return string representation of given unit (e.g. V or m) * @return NULL if unit was not recognized */ 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 * * The given scaling factor is returned as a SI unit prefix (e.g. M for Mega, u * for micro, etc), or `\0` otherwise. * * @param[in] scale scale factor to convert * * @return SI prefix if applicable * @return `\0` if no SI prefix was found */ char phydat_prefix_from_scale(int8_t scale); /** * @brief Scale integer value(s) to fit into a @ref phydat_t * * Inserts the @p values in the given @p dat so that all @p dim values in * @p values fit inside the limits of the data type, * [@ref PHYDAT_MIN, @ref PHYDAT_MAX], and updates the stored scale factor. * The @ref phydat_t::scale member in @p dat is used as the the original scale * of the @p values. * The value is rounded to the nearest integer if possible, otherwise away from * zero. E.g. `0.5` and `0.6` are rounded to `1`, `0.4` and `-0.4` are rounded * to `0`, `-0.5` and `-0.6` are rounded to `-1`. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.c} * int32_t values[] = { 100000, 2000000, 30000000 }; * phydat_t dat = { .scale = 0 }; * phydat_fit(&dat, values, 3); * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * @note Unless compiled with `-DPHYDAT_FIT_TRADE_PRECISION_FOR_ROM=0`, this * function will scale the value `-32768`, even though it would fit into a * @ref phydat_t. Statistically, this precision loss happens in 0.00153% * of the calls. This optimization saves a bit more than 20 bytes. * * @pre The @ref phydat_t::scale member in @p dat is initialized to the scale * of the @p values by the caller prior to calling this function. * * @param[in, out] dat the value will be written into this data array * @param[in] values value(s) to rescale * @param[in] dim Number of elements in @p values */ 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 #endif /* PHYDAT_H */ /** @} */