1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-12-24 22:13:52 +01:00
RIOT/sys/include/phydat.h
Silke Hofstra 26653dd446 sys/phydat: add functions for Unix time conversion to phydat
Add functionality for calculating Unix timestamps to phydat.
This allows one to convert dates received from RTCs and the like
to Unix timestamps.
2023-01-21 11:01:42 +01:00

326 lines
11 KiB
C

/*
* 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 <hauke.petersen@fu-berlin.de>
*/
#ifndef PHYDAT_H
#define PHYDAT_H
#include <stddef.h>
#include <stdint.h>
#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);
/**
* @brief Convert a date and time contained in phydat structs to a Unix timestamp.
* See phydat_unix() for the date notation and peculiarities.
*
* @param date Date to use in the timestamp.
* @param time Time to use in the timestamp.
* @param offset_seconds Timezone offset in seconds to use in the timestamp.
*
* @return A unix timestamp
*/
int64_t phydat_date_time_to_unix(phydat_t *date, phydat_t *time, int32_t offset_seconds);
/**
* @brief Convert a date and time (per ISO8601) to a Unix timestamp (seconds since 1970).
*
* @param year Year in the Common Era (CE). Note that 0 is 1 BCE, 1 is 2 BCE, etc.
* @param month Month of the year.
* @param day Day of the month.
* @param hour Hour of the day.
* @param minute Minute of the hour.
* @param second Second of the minute.
* @param offset Timezone offset in seconds.
*
* @return A Unix timestamp (seconds since 1970).
*/
int64_t phydat_unix(int16_t year, int16_t month, int16_t day,
int16_t hour, int16_t minute, int16_t second,
int32_t offset);
#ifdef __cplusplus
}
#endif
#endif /* PHYDAT_H */
/** @} */