mirror of
https://github.com/eddyem/small_tel.git
synced 2025-12-06 02:35:14 +03:00
154 lines
5.8 KiB
C
154 lines
5.8 KiB
C
/*
|
|
* This file is part of the weatherdaemon project.
|
|
* Copyright 2023 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <math.h>
|
|
#include <pthread.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <usefull_macros.h>
|
|
|
|
#include "stat.h"
|
|
|
|
// add BUFSZ_INCR records to buffer each time when no free space left
|
|
#define BUFSZ_INCR (2048)
|
|
static weather_t *buf = NULL;
|
|
// current size of `buf`
|
|
static size_t buflen = 0;
|
|
// indexes of first and last element in buffer
|
|
static size_t firstidx = 0, lastidx = 0;
|
|
// maximal current time delta between last and first items of `buf`
|
|
static double tdiffmax = 0.;
|
|
|
|
// mutex for working with buffer
|
|
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
double get_tmax(){
|
|
return tdiffmax;
|
|
}
|
|
|
|
// add new record to buffer
|
|
void addtobuf(weather_t *record){
|
|
if(!record) return;
|
|
pthread_mutex_lock(&mutex);
|
|
if(!buf){ // first run
|
|
buf = MALLOC(weather_t, BUFSZ_INCR);
|
|
buflen = BUFSZ_INCR;
|
|
memcpy(&buf[0], record, sizeof(weather_t));
|
|
//DBG("init buff to %zd", buflen);
|
|
pthread_mutex_unlock(&mutex);
|
|
return;
|
|
}
|
|
if(++lastidx == buflen){ // end of buffer reached: decide wether to increase buffer or not
|
|
if(tdiffmax < STATMAXT){
|
|
buflen += BUFSZ_INCR;
|
|
buf = realloc(buf, sizeof(weather_t)*buflen);
|
|
DBG("realloc buf to %zd", buflen);
|
|
}else lastidx = 0;
|
|
}
|
|
if(lastidx == firstidx){
|
|
if(++firstidx == buflen) firstidx = 0;
|
|
}
|
|
memcpy(&buf[lastidx], record, sizeof(weather_t));
|
|
tdiffmax = buf[lastidx].tmeasure - buf[firstidx].tmeasure;
|
|
//DBG("add record, last=%zd, first=%zd, dT=%.3f", lastidx, firstidx, tdiffmax);
|
|
pthread_mutex_unlock(&mutex);
|
|
}
|
|
|
|
|
|
|
|
// get statistics for last `Tsec` seconds; @return real dT for given interval
|
|
double stat_for(double Tsec, weatherstat_t *wstat){
|
|
if(!wstat || Tsec < 1.) return 0.;
|
|
double dt = 0., tlast = buf[lastidx].tmeasure;
|
|
size_t startfrom = lastidx;
|
|
pthread_mutex_lock(&mutex);
|
|
if(tdiffmax <= Tsec){ // use all data
|
|
startfrom = firstidx;
|
|
dt = tdiffmax;
|
|
}else while(dt < Tsec && startfrom != firstidx){ // search from which index we should start
|
|
if(startfrom == 0) startfrom = buflen - 1;
|
|
else --startfrom;
|
|
dt = tlast - buf[startfrom].tmeasure;
|
|
}
|
|
DBG("got indexes: start=%zd, end=%zd, dt=%.2f (dtiffmax=%.1f)", startfrom, lastidx, dt, tdiffmax);
|
|
weather_t min, max, sum = {0}, sum2 = {0};
|
|
size_t amount = 0;
|
|
memcpy(&min, &buf[lastidx], sizeof(weather_t));
|
|
memcpy(&max, &buf[lastidx], sizeof(weather_t));
|
|
min.tmeasure = buf[startfrom].tmeasure;
|
|
max.tmeasure = buf[lastidx].tmeasure;
|
|
double tmean = (max.tmeasure+min.tmeasure)/2.;
|
|
// DBG("tmean=%.1f, min=%.1f, max=%.1f", tmean, min.tmeasure, max.tmeasure);
|
|
size_t st = startfrom;
|
|
// calculate amount of north and south winds
|
|
size_t north = 0, south = 0;
|
|
while(st != lastidx){
|
|
double w = buf[st].winddir;
|
|
if(w < 90. || w > 270.) ++north;
|
|
else ++south;
|
|
if(++st == buflen) st = 0;
|
|
}
|
|
int minus = 0;
|
|
if(north > 2*south) minus = 1;
|
|
while(startfrom != lastidx){
|
|
weather_t *curw = &buf[startfrom];
|
|
#define CHK(field) do{register double d = curw->field; if(d > max.field) max.field = d; if(d < min.field) min.field = d; \
|
|
sum.field += d; sum2.field += d*d;}while(0)
|
|
CHK(windspeed);
|
|
register double dir = curw->winddir, s = curw->windspeed;
|
|
if(minus && dir > 180.) dir -= 360.;
|
|
register double sd = dir * s; // weighted sum
|
|
if(dir > max.winddir) max.winddir = dir;
|
|
if(dir < min.winddir) min.winddir = dir;
|
|
sum.winddir += sd;
|
|
sum2.winddir += sd * dir; // v * dir^2
|
|
CHK(pressure);
|
|
CHK(temperature);
|
|
CHK(humidity);
|
|
CHK(rainfall);
|
|
s = curw->tmeasure;
|
|
sum.tmeasure += s; s -= tmean;
|
|
sum2.tmeasure += s * s; // correct tmeasure by mean time to exclude cumulative error of double
|
|
#undef CHK
|
|
++amount;
|
|
if(++startfrom == buflen) startfrom = 0;
|
|
}
|
|
// DBG("Got %zd records; tsum/sum2: %g/%g", amount, sum.tmeasure, sum2.tmeasure);
|
|
wstat->winddir.max = max.winddir;
|
|
wstat->winddir.min = min.winddir;
|
|
register double s2 = sum2.winddir / sum.windspeed, s = sum.winddir / sum.windspeed;
|
|
wstat->winddir.mean = s;
|
|
wstat->winddir.rms = sqrt(s2 - s*s);
|
|
// correct RMS by float epsilon to exclude negative values when near zero
|
|
#define STAT(field) do{ register double ms = sum.field/amount, ms2 = sum2.field/amount; \
|
|
wstat->field.min = min.field; wstat->field.max = max.field; wstat->field.mean = ms; \
|
|
wstat->field.rms = sqrt(ms2 - ms*ms + __FLT_EPSILON__); }while(0)
|
|
STAT(windspeed);
|
|
STAT(pressure);
|
|
STAT(temperature);
|
|
STAT(humidity);
|
|
STAT(rainfall);
|
|
wstat->tmeasure.max = max.tmeasure;
|
|
wstat->tmeasure.min = min.tmeasure;
|
|
wstat->tmeasure.mean = sum.tmeasure/amount;
|
|
wstat->tmeasure.rms = sqrt(sum2.tmeasure/amount); // sum2.tmeasure is already corrected by mean
|
|
// DBG("tmean=%.1f, min=%.1f, max=%.1f", wstat->tmeasure.mean, wstat->tmeasure.min, wstat->tmeasure.max);
|
|
pthread_mutex_unlock(&mutex);
|
|
return dt;
|
|
}
|