From 39d4e220618ebbb967dc20d08067301ef0b6ba91 Mon Sep 17 00:00:00 2001 From: Edward Emelianov Date: Wed, 8 Apr 2026 11:37:27 +0300 Subject: [PATCH] add mean wind directions and max wind speed --- Daemons/weatherdaemon_multimeteo/cmdlnopts.c | 7 +- .../weatherdaemon_multimeteo/mainweather.c | 129 +++++++++++++++++- Daemons/weatherdaemon_multimeteo/sensors.c | 4 +- Daemons/weatherdaemon_multimeteo/server.c | 6 +- 4 files changed, 135 insertions(+), 11 deletions(-) diff --git a/Daemons/weatherdaemon_multimeteo/cmdlnopts.c b/Daemons/weatherdaemon_multimeteo/cmdlnopts.c index 3944c67..8e2b21b 100644 --- a/Daemons/weatherdaemon_multimeteo/cmdlnopts.c +++ b/Daemons/weatherdaemon_multimeteo/cmdlnopts.c @@ -83,7 +83,7 @@ static glob_pars G; sl_option_t cmdlnopts[] = { {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), "show this help"}, - {"conffile",NEED_ARG, NULL, 'c', arg_string, APTR(&G.conffile), "configuration file name (consists all or a part of long-named parameters and their values (e.g. plugin=liboldweather.so:D:/dev/ttyS0:115200)"}, + {"conffile",NEED_ARG, NULL, 'c', arg_string, APTR(&G.conffile), "configuration file name (or non-existant file for help)"}, {"verb", NO_ARGS, NULL, 'v', arg_none, APTR(&G.verb), "logfile verbocity level (each -v increased)"}, COMMON_OPTS end_option @@ -181,7 +181,10 @@ glob_pars *parse_args(int argc, char **argv){ if(G.conffile){ // read conffile and fix parameters (cmdline args are in advantage) glob_pars oldpars = G; // save cmdline opts G = defconf; - if(!sl_conf_readopts(oldpars.conffile, confopts)) ERRX("Can't get options from %s", G.conffile); + if(!sl_conf_readopts(oldpars.conffile, confopts)){ + sl_conf_showhelp(-1, confopts); + return NULL; + } DBG("CONF: \n-------------\n%s-------------\n\n", sl_print_opts(confopts, 1)); if((0 == strcmp(oldpars.port, DEFAULT_PORT)) && G.port) oldpars.port = G.port; if(!oldpars.logfile && G.logfile) oldpars.logfile = G.logfile; diff --git a/Daemons/weatherdaemon_multimeteo/mainweather.c b/Daemons/weatherdaemon_multimeteo/mainweather.c index 8f861fc..8617b0d 100644 --- a/Daemons/weatherdaemon_multimeteo/mainweather.c +++ b/Daemons/weatherdaemon_multimeteo/mainweather.c @@ -24,13 +24,24 @@ #include "sensors.h" #include "weathlib.h" +// wind speed history array size (not less than for one hour) +#define MAX_HISTORY 3600 +// throw out data older than 24 hours +#define TOO_OLD_DATA 86400 +// one hour +#define T_ONE_HOUR 3600 + static pthread_mutex_t datamutex = PTHREAD_MUTEX_INITIALIZER; static int Forbidden = 0; // index of meteodata in array enum{ NWIND, + NWINDMAX, + NWINDMAX1, NWINDDIR, + NWINDDIR1, + NWINDDIR2, NHUMIDITY, NABM_TEMP, NPRESSURE, @@ -46,7 +57,11 @@ enum{ static val_t collected_data[NAMOUNT_OF_DATA] = { [NWIND] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_WIND}, + [NWINDMAX] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDMAX", .comment = "Maximal wind speed for last 24 hours"}, + [NWINDMAX1] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDMAX1", .comment = "Maximal wind speed for last hour"}, [NWINDDIR] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_WINDDIR}, + [NWINDDIR1] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDDIR1", .comment = "Mean wind speed direction for last hour"}, + [NWINDDIR2] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDDIR2", .comment = "Mean wind speed^2 direction for last hour"}, [NHUMIDITY] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_HUMIDITY}, [NABM_TEMP] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_AMB_TEMP}, [NPRESSURE] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_PRESSURE}, @@ -55,11 +70,101 @@ static val_t collected_data[NAMOUNT_OF_DATA] = { [NMIST] = {.sense = VAL_OBLIGATORY, .type = VALT_INT, .meaning = IS_MIST}, [NCLOUDS] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_CLOUDS}, [NSKYTEMP] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_SKYTEMP}, - [NCOMMWEATH] = {.value.i = 0, .sense = VAL_OBLIGATORY, .type = VALT_INT, .meaning = IS_OTHER, .name = "weather", .comment = "Weather level (0 - good, 3 - obs. prohibited)"}, - [NLASTAHTUNG] = {.value.i = 0, .sense = VAL_RECOMMENDED, .type = VALT_INT, .meaning = IS_OTHER, .name = "evttime", .comment = "UNIX-time of last weather level increasing"}, + [NCOMMWEATH] = {.value.i = 0, .sense = VAL_OBLIGATORY, .type = VALT_INT, .meaning = IS_OTHER, .name = "WEATHER", .comment = "Weather level (0 - good, 3 - obs. prohibited)"}, + [NLASTAHTUNG] = {.value.i = 0, .sense = VAL_RECOMMENDED, .type = VALT_INT, .meaning = IS_OTHER, .name = "EVTTIME", .comment = "UNIX-time of last weather level increasing"}, // {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_OTHER}, }; +typedef struct{ + double array[MAX_HISTORY]; + double sum; +} sumval_t; + +typedef struct{ + int write_idx; + sumval_t C; // (speed * cos(dir)) + sumval_t C2; // (speed^2 * cos(dir)) + sumval_t S; // (speed * sin(dir)) + sumval_t S2; // (speed^2 * sin(dir)) +} meanwinddir_t; + +static meanwinddir_t winddirs = {0}; + +static double update_sum(sumval_t *sv, double newval, int oldidx){ + double old = sv->array[oldidx]; + sv->array[oldidx] = newval; + return (sv->sum += newval - old); +} + +// add current value into floating array and recalculate mean speeds +// data mutex should be locked outside this function +static void wind_dir_add(double curspeed, double curdir, double *dir, double *dir2){ + double C, S; + sincos(curdir * M_PI / 180., &S, &C); + double vS = curspeed * S, vC = curspeed * C, v2S = curspeed * vS, v2C = curspeed * vC; + int idx = winddirs.write_idx; + winddirs.write_idx = (idx + 1) % MAX_HISTORY; + // calculate sums + vS = update_sum(&winddirs.S, vS, idx); + vC = update_sum(&winddirs.C, vC, idx); + v2S = update_sum(&winddirs.S2, v2S, idx); + v2C = update_sum(&winddirs.C2, v2C, idx); + *dir = atan2(vS, vC) * 180. / M_PI; + *dir2 = atan2(v2S, v2C) * 180. / M_PI; + if(*dir < 0.) *dir += 360.; + if(*dir2 < 0.) *dir2 += 360.; + +} + +typedef struct{ + double speeds[MAX_HISTORY]; + time_t timestamps[MAX_HISTORY]; + int write_idx; // index in `speeds` and `timestamps` for new value + + int deq[MAX_HISTORY]; // array of indexes in queue + int deq_head, deq_tail; // queue's head and tail +} sliding_max_t; + +static sliding_max_t windspeeds = {0}; + +static void add_windspeed(sliding_max_t *sm, double speed, time_t now) { + // Write new data portion into queue + int idx = sm->write_idx; + sm->speeds[idx] = speed; + sm->timestamps[idx] = now; + sm->write_idx = (idx + 1) % MAX_HISTORY; + + // Remove values older than `TOO_OLD_DATA` + time_t cutoff = now - TOO_OLD_DATA; + while(sm->deq_head != sm->deq_tail && sm->timestamps[sm->deq[sm->deq_head]] < cutoff){ + sm->deq_head = (sm->deq_head + 1) % MAX_HISTORY; + } + + // Remove small values less than current + while(sm->deq_head != sm->deq_tail && sm->speeds[sm->deq[(sm->deq_tail - 1 + MAX_HISTORY) % MAX_HISTORY]] <= speed){ + sm->deq_tail = (sm->deq_tail - 1 + MAX_HISTORY) % MAX_HISTORY; + } + + // Add new index into queue + sm->deq[sm->deq_tail] = idx; + sm->deq_tail = (sm->deq_tail + 1) % MAX_HISTORY; +} + +static double get_current_max(sliding_max_t *sm){ + if(sm->deq_head == sm->deq_tail) return 0.0; // No data + return sm->speeds[sm->deq[sm->deq_head]]; +} + +static double get_max_forT(sliding_max_t *sm, time_t tcutoff){ + if(sm->deq_head == sm->deq_tail) return 0.0; // No data + int idx = sm->deq_head; + while(idx != sm->deq_tail && sm->timestamps[sm->deq[sm->deq_head]] < tcutoff){ + idx = (idx + 1) % MAX_HISTORY; + } + if(idx == sm->deq_tail) return 0.0; // No fresh data + return sm->speeds[sm->deq[idx]]; +} + int collected_amount(){ return NAMOUNT_OF_DATA; } @@ -137,7 +242,7 @@ static void chkweatherlevel(int *curlevel, double curvalue, weather_cond_t *curc DBG("newlevel: %d, current: %d INCREASED", newlevel, *curlevel); *curlevel = newlevel; if(*ahtungtime < curt) *ahtungtime = (int) curt; // refresh event time - }else{ // check timeout to make level lower + }else if(newlevel < *curlevel){ // check timeout to make level lower if(curt - *ahtungtime > WeatherConf.ahtung_delay){ DBG("newlevel: %d, current: %d DECREASED", newlevel, *curlevel); *curlevel = newlevel; @@ -154,6 +259,7 @@ void refresh_sensval(sensordata_t *s){ int curlevel = collected_data[NCOMMWEATH].value.i; int curahtungtime = collected_data[NLASTAHTUNG].value.i; time_t curtime = time(NULL); + double dir = -100., dir2 = -100.; // mean wind directions DBG("%d meteo values", s->Nvalues); for(int i = 0; i < s->Nvalues; ++i){ DBG("Try to get %dth value", i); @@ -167,9 +273,11 @@ void refresh_sensval(sensordata_t *s){ idx = NWIND; curvalue = (double) value.value.f; curcond = &WeatherConf.wind; + add_windspeed(&windspeeds, curvalue, curtime); break; case IS_WINDDIR: idx = NWINDDIR; + wind_dir_add(collected_data[NWIND].value.f, value.value.f, &dir, &dir2); break; case IS_HUMIDITY: idx = NHUMIDITY; @@ -224,8 +332,21 @@ void refresh_sensval(sensordata_t *s){ pthread_mutex_unlock(&datamutex); if(!Forbidden && curcond) chkweatherlevel(&curlevel, curvalue, curcond, &curahtungtime); } - DBG("check ahtung"); pthread_mutex_lock(&datamutex); + // refresh max + if(dir >= 0.){ + collected_data[NWINDDIR1].value.f = (float) dir; + collected_data[NWINDDIR1].time = curtime; + } + if(dir2 >= 0.){ + collected_data[NWINDDIR2].value.f = (float) dir2; + collected_data[NWINDDIR2].time = curtime; + } + collected_data[NWINDMAX].value.f = (float) get_current_max(&windspeeds); + collected_data[NWINDMAX].time = curtime; + collected_data[NWINDMAX1].value.f = (float) get_max_forT(&windspeeds, curtime - T_ONE_HOUR); + collected_data[NWINDMAX1].time = curtime; + DBG("check ahtung"); if(Forbidden) collected_data[NCOMMWEATH].value.i = WEATHER_PROHIBITED; else collected_data[NCOMMWEATH].value.i = curlevel; if(collected_data[NLASTAHTUNG].value.i < curahtungtime) collected_data[NLASTAHTUNG].value.i = curahtungtime; diff --git a/Daemons/weatherdaemon_multimeteo/sensors.c b/Daemons/weatherdaemon_multimeteo/sensors.c index eac83cb..b0fb2cb 100644 --- a/Daemons/weatherdaemon_multimeteo/sensors.c +++ b/Daemons/weatherdaemon_multimeteo/sensors.c @@ -177,7 +177,7 @@ int format_sensval(const val_t *v, char *buf, int buflen, int Np){ switch(v->type){ case VALT_UINT: snprintf(strval, VAL_LEN, "%u", v->value.u); break; case VALT_INT: snprintf(strval, VAL_LEN, "%d", v->value.i); break; - case VALT_FLOAT: snprintf(strval, VAL_LEN, "%g", v->value.f); break; + case VALT_FLOAT: snprintf(strval, VAL_LEN, "%.2f", v->value.f); break; default: sprintf(strval, "'ERROR'"); } const char* const NM[IS_OTHER] = { // names of standard fields @@ -196,7 +196,7 @@ int format_sensval(const val_t *v, char *buf, int buflen, int Np){ }; const char* const CMT[IS_OTHER] = { // comments for standard fields [IS_WIND] = "Wind, m/s", - [IS_WINDDIR] = "Wind direction, degr (CW from north to FROM)", + [IS_WINDDIR] = "Instant wind direction, degr (CW from north to FROM)", [IS_HUMIDITY] = "Humidity, percent", [IS_AMB_TEMP] = "Ambient temperature, degC", [IS_INNER_TEMP] = "In-dome temperature, degC", diff --git a/Daemons/weatherdaemon_multimeteo/server.c b/Daemons/weatherdaemon_multimeteo/server.c index 92738e6..8e05edd 100644 --- a/Daemons/weatherdaemon_multimeteo/server.c +++ b/Daemons/weatherdaemon_multimeteo/server.c @@ -101,9 +101,9 @@ static void showdata(sl_sock_t *client){ time_t oldest = time(NULL) - oldest_interval; uint64_t Tsum = 0; int nsum = 0; for(int i = 0; i < Ncoll; ++i){ - if(!get_collected(&v, i)) continue; - if(v.time < oldest) continue; - if(1 > format_sensval(&v, buf, FULL_LEN+1, -1)) continue; + if(!get_collected(&v, i)){ DBG("Can't get %dth value", i); continue; } + if(v.time < oldest){ DBG("%dth value is too old", i); continue; } + if(1 > format_sensval(&v, buf, FULL_LEN+1, -1)){ DBG("Can't format"); continue; } DBG("formatted: '%s'", buf); sl_sock_sendstrmessage(client, buf); sl_sock_sendbyte(client, '\n');