Store timestamp for each value in timeseries
This fixes a bug that caused the time values in the plots drift away with increasing device uptime.
This commit is contained in:
parent
fb026d93a5
commit
77e987bccc
4 changed files with 81 additions and 46 deletions
|
@ -4,16 +4,19 @@
|
|||
|
||||
#include <deque>
|
||||
|
||||
typedef std::pair<uint64_t, float> timeseries_entry_t;
|
||||
typedef std::deque< timeseries_entry_t > timeseries_data_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t tstart, tend, interval;
|
||||
std::deque<float> data;
|
||||
uint64_t tstart, tend;
|
||||
timeseries_data_t data;
|
||||
const char *unit;
|
||||
const char *format;
|
||||
} timeseries_t;
|
||||
|
||||
void timeseries_init(timeseries_t *ts, uint64_t tstart, uint64_t interval, const char *unit, const char *format);
|
||||
void timeseries_init(timeseries_t *ts, uint64_t tstart, const char *unit, const char *format);
|
||||
|
||||
void timeseries_append(timeseries_t *ts, float value);
|
||||
void timeseries_append(timeseries_t *ts, uint64_t timestamp, float value);
|
||||
|
||||
// remove oldest values
|
||||
void timeseries_prune(timeseries_t *ts, float duration);
|
||||
void timeseries_prune(timeseries_t *ts, uint64_t duration);
|
||||
|
|
|
@ -64,7 +64,7 @@ void epaper_draw_and_hibernate(epaper_callback cb, bool full_refresh)
|
|||
}
|
||||
|
||||
// find minimum and maximum values in a vector. NaN is ignored.
|
||||
template<typename T>
|
||||
template<typename T>
|
||||
void minmax(const std::deque<T> &vec, T *min, T *max)
|
||||
{
|
||||
bool first = true;
|
||||
|
@ -94,12 +94,42 @@ void minmax(const std::deque<T> &vec, T *min, T *max)
|
|||
}
|
||||
}
|
||||
|
||||
void minmax(const timeseries_data_t &ts, float *min, float *max)
|
||||
{
|
||||
bool first = true;
|
||||
|
||||
// fallback values: will be used if the vector is empty or only consists of NaNs
|
||||
*min = 0;
|
||||
*max = 1;
|
||||
|
||||
for(auto &p: ts) {
|
||||
float v = p.second;
|
||||
if(isnan(v)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(first) {
|
||||
*min = *max = v;
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(v < *min) {
|
||||
*min = v;
|
||||
}
|
||||
|
||||
if(v > *max) {
|
||||
*max = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void epaper_plot(uint16_t x, uint16_t y, uint16_t w, uint16_t h, timeseries_t *timeseries, uint32_t time_tick_interval)
|
||||
{
|
||||
char s[32];
|
||||
|
||||
uint32_t trange = timeseries->tend - timeseries->tstart;
|
||||
uint32_t tstep = timeseries->interval;
|
||||
uint32_t tstep = trange / timeseries->data.size();
|
||||
|
||||
if(trange == 0) {
|
||||
trange = 3600;
|
||||
|
@ -216,17 +246,10 @@ void epaper_plot(uint16_t x, uint16_t y, uint16_t w, uint16_t h, timeseries_t *t
|
|||
}
|
||||
|
||||
size_t i = 0;
|
||||
for(uint32_t reltime = 0; reltime < trange; reltime += tstep) {
|
||||
if(i >= timeseries->data.size()) {
|
||||
Serial.println(F("Index out of range! Time range does not match data points."));
|
||||
break;
|
||||
}
|
||||
for(auto &p: timeseries->data) {
|
||||
uint32_t reltime = p.first - timeseries->tstart;
|
||||
|
||||
/*Serial.print(F("Processing data point at dt="));
|
||||
Serial.print(reltime);
|
||||
Serial.print(F("… "));*/
|
||||
|
||||
float val = timeseries->data[i++];
|
||||
float val = timeseries->data[i++].second;
|
||||
if(isnan(val)) {
|
||||
//Serial.println(F("Skipping NaN!"));
|
||||
|
||||
|
|
34
src/main.cpp
34
src/main.cpp
|
@ -99,14 +99,14 @@ void wifi_setup(void)
|
|||
|
||||
void initSCD30(void)
|
||||
{
|
||||
timeseries_init(&ts_scd30_co2, timeClient.getEpochTime(), 15, "ppm", "%.0f");
|
||||
timeseries_init(&ts_scd30_temperature, timeClient.getEpochTime(), 15, "°C", "%.1f");
|
||||
timeseries_init(&ts_scd30_humidity, timeClient.getEpochTime(), 15, "%", "%.1f");
|
||||
timeseries_init(&ts_bme680_temperature, timeClient.getEpochTime(), 15, "°C", "%.1f");
|
||||
timeseries_init(&ts_bme680_humidity, timeClient.getEpochTime(), 15, "%", "%.1f");
|
||||
timeseries_init(&ts_bme680_pressure, timeClient.getEpochTime(), 15, "hPa", "%.2f");
|
||||
timeseries_init(&ts_bme680_gas_resistance, timeClient.getEpochTime(), 15, "kOhm", "%.1f");
|
||||
timeseries_init(&ts_bme680_db, timeClient.getEpochTime(), 15, "dB", "%.2f");
|
||||
timeseries_init(&ts_scd30_co2, timeClient.getEpochTime(), "ppm", "%.0f");
|
||||
timeseries_init(&ts_scd30_temperature, timeClient.getEpochTime(), "°C", "%.1f");
|
||||
timeseries_init(&ts_scd30_humidity, timeClient.getEpochTime(), "%", "%.1f");
|
||||
timeseries_init(&ts_bme680_temperature, timeClient.getEpochTime(), "°C", "%.1f");
|
||||
timeseries_init(&ts_bme680_humidity, timeClient.getEpochTime(), "%", "%.1f");
|
||||
timeseries_init(&ts_bme680_pressure, timeClient.getEpochTime(), "hPa", "%.2f");
|
||||
timeseries_init(&ts_bme680_gas_resistance, timeClient.getEpochTime(), "kOhm", "%.1f");
|
||||
timeseries_init(&ts_bme680_db, timeClient.getEpochTime(), "dB", "%.2f");
|
||||
|
||||
// Try to initialize!
|
||||
if (!scd30.begin()) {
|
||||
|
@ -433,9 +433,10 @@ void loop(void)
|
|||
|
||||
// skip the first readout because the CO2 reading is then invalid
|
||||
if(!first_scd30_readout) {
|
||||
timeseries_append(&ts_scd30_co2, scd30.CO2);
|
||||
timeseries_append(&ts_scd30_temperature, scd30.temperature);
|
||||
timeseries_append(&ts_scd30_humidity, scd30.relative_humidity);
|
||||
uint64_t updatetime = timeClient.getEpochTime();
|
||||
timeseries_append(&ts_scd30_co2, updatetime, scd30.CO2);
|
||||
timeseries_append(&ts_scd30_temperature, updatetime, scd30.temperature);
|
||||
timeseries_append(&ts_scd30_humidity, updatetime, scd30.relative_humidity);
|
||||
} else {
|
||||
first_scd30_readout = false;
|
||||
}
|
||||
|
@ -444,10 +445,11 @@ void loop(void)
|
|||
// milliseconds), but the SCD30 is running autonomously, so this does not
|
||||
// disturb that measurement.
|
||||
if(bme680.performReading()) {
|
||||
timeseries_append(&ts_bme680_temperature, bme680.temperature);
|
||||
timeseries_append(&ts_bme680_humidity, bme680.humidity);
|
||||
timeseries_append(&ts_bme680_pressure, bme680.pressure / 100.0);
|
||||
timeseries_append(&ts_bme680_gas_resistance, bme680.gas_resistance / 1000.0);
|
||||
uint64_t updatetime = timeClient.getEpochTime();
|
||||
timeseries_append(&ts_bme680_temperature, updatetime, bme680.temperature);
|
||||
timeseries_append(&ts_bme680_humidity, updatetime, bme680.humidity);
|
||||
timeseries_append(&ts_bme680_pressure, updatetime, bme680.pressure / 100.0);
|
||||
timeseries_append(&ts_bme680_gas_resistance, updatetime, bme680.gas_resistance / 1000.0);
|
||||
|
||||
bme680_max_resistance *= BME680_MAX_ADAPTATION_FACTOR;
|
||||
if(bme680.gas_resistance > bme680_max_resistance) {
|
||||
|
@ -455,7 +457,7 @@ void loop(void)
|
|||
}
|
||||
|
||||
bme680_db = 10 * log10(bme680.gas_resistance / bme680_max_resistance);
|
||||
timeseries_append(&ts_bme680_db, bme680_db);
|
||||
timeseries_append(&ts_bme680_db, updatetime, bme680_db);
|
||||
|
||||
Serial.println("BME680:");
|
||||
Serial.print("Temperature: ");
|
||||
|
|
|
@ -1,32 +1,39 @@
|
|||
#include "timeseries.h"
|
||||
|
||||
|
||||
void timeseries_init(timeseries_t *ts, uint64_t tstart, uint64_t interval, const char *unit, const char *format)
|
||||
void timeseries_init(timeseries_t *ts, uint64_t tstart, const char *unit, const char *format)
|
||||
{
|
||||
ts->tstart = tstart;
|
||||
ts->tend = tstart;
|
||||
ts->interval = interval;
|
||||
ts->unit = unit;
|
||||
ts->format = format;
|
||||
}
|
||||
|
||||
|
||||
void timeseries_append(timeseries_t *ts, float value)
|
||||
void timeseries_append(timeseries_t *ts, uint64_t timestamp, float value)
|
||||
{
|
||||
ts->data.push_back(value);
|
||||
ts->tend += ts->interval;
|
||||
ts->data.push_back( {timestamp, value} );
|
||||
ts->tend = timestamp;
|
||||
|
||||
if(ts->data.size() == 1) {
|
||||
ts->tstart = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void timeseries_prune(timeseries_t *ts, float duration)
|
||||
void timeseries_prune(timeseries_t *ts, uint64_t duration)
|
||||
{
|
||||
std::deque<float>::size_type to_keep = duration / ts->interval;
|
||||
if(to_keep >= ts->data.size()) {
|
||||
return;
|
||||
timeseries_data_t::iterator iter = ts->data.begin();
|
||||
|
||||
uint64_t new_tstart = ts->tend - duration;
|
||||
|
||||
while(iter != ts->data.end() && iter->second < new_tstart) {
|
||||
iter = ts->data.erase(iter);
|
||||
}
|
||||
|
||||
std::deque<float>::size_type to_delete = ts->data.size() - to_keep;
|
||||
ts->data.erase(ts->data.begin(), ts->data.begin() + to_delete);
|
||||
|
||||
ts->tstart += to_delete * ts->interval;
|
||||
if(iter != ts->data.end()) {
|
||||
ts->tstart = iter->first;
|
||||
} else {
|
||||
ts->tstart = ts->tend;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue