From c5b9d4379747dca032f3c56b1b66e817368587c1 Mon Sep 17 00:00:00 2001 From: Edward Emelianov Date: Tue, 5 May 2026 17:57:42 +0300 Subject: [PATCH] add lightning module --- .../weatherdaemon_multimeteo/CMakeLists.txt | 1 + Daemons/weatherdaemon_multimeteo/fd.c | 84 +------- .../weatherdaemon_multimeteo/mainweather.c | 29 ++- .../plugins/CMakeLists.txt | 5 + .../weatherdaemon_multimeteo/plugins/dummy.c | 4 +- .../plugins/lightning.c | 199 ++++++++++++++++++ Daemons/weatherdaemon_multimeteo/sensors.c | 4 +- .../weatherdaemon.files | 1 + Daemons/weatherdaemon_multimeteo/weathlib.h | 2 +- 9 files changed, 234 insertions(+), 95 deletions(-) create mode 100644 Daemons/weatherdaemon_multimeteo/plugins/lightning.c diff --git a/Daemons/weatherdaemon_multimeteo/CMakeLists.txt b/Daemons/weatherdaemon_multimeteo/CMakeLists.txt index 71ff98f..b4ef2ee 100644 --- a/Daemons/weatherdaemon_multimeteo/CMakeLists.txt +++ b/Daemons/weatherdaemon_multimeteo/CMakeLists.txt @@ -20,6 +20,7 @@ option(BTAMETEO "BTA main meteostation plugin" ON) option(REINHARDT "Old Reinhardt meteostation plugin" ON) option(WXA100 "WXA100-06 meteostation plugin" ON) option(SNMP "SNMP UPS monitoring module" ON) +option(LIGHTNING "AS3935-based lightning sensor" ON) # default flags set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -W -Wextra -fPIC") diff --git a/Daemons/weatherdaemon_multimeteo/fd.c b/Daemons/weatherdaemon_multimeteo/fd.c index 4414ee9..7e34af0 100644 --- a/Daemons/weatherdaemon_multimeteo/fd.c +++ b/Daemons/weatherdaemon_multimeteo/fd.c @@ -59,84 +59,6 @@ static int openserial(const char *path){ return serial->comfd; } -static char *convunsname(const char *path){ - char *apath = MALLOC(char, 106); - if(*path == 0 || *path == '@'){ - DBG("convert name starting from 0 or @"); - apath[0] = 0; - strncpy(apath+1, path+1, 104); - }else if(strncmp("\\0", path, 2) == 0){ - DBG("convert name starting from \\0"); - apath[0] = 0; - strncpy(apath+1, path+2, 104); - }else strncpy(apath, path, 105); - return apath; -} - -/** - * @brief opensocket - try to open socket - * @param sock - UNIX socket path or hostname:port for INET socket - * @param type - UNIX or INET - * @return -1 if failed or opened FD - */ -static int opensocket(const char *path, sl_socktype_e type){ - FNAME(); - DBG("path: '%s'", path); - int sock = -1; - struct addrinfo ai = {0}, *res = &ai; - struct sockaddr_un unaddr = {0}; - ai.ai_socktype = 0; // try to get socket type from `getaddrinfo` - switch(type){ - case SOCKT_UNIX: - { - char *str = convunsname(path); - if(!str) return -1; - unaddr.sun_family = AF_UNIX; - ai.ai_addr = (struct sockaddr*) &unaddr; - ai.ai_addrlen = sizeof(unaddr); - memcpy(unaddr.sun_path, str, 106); - FREE(str); - ai.ai_family = AF_UNIX; - // TODO: add `socket` and `connect` - } - break; - case SOCKT_NET: - case SOCKT_NETLOCAL: - ai.ai_family = AF_INET; - char *str = strdup(path); - char *delim = strchr(str, ':'); - char *node = str, *service = NULL; - if(delim){ - *delim = 0; - service = delim+1; - if(delim == str) node = NULL; // only port - } - DBG("node: '%s', service: '%s'", node, service); - int e = getaddrinfo(node, service, &ai, &res); - if(e){ - WARNX("getaddrinfo(): %s", gai_strerror(e)); - FREE(str); - return -1; - } - for(struct addrinfo *p = res; p; p = p->ai_next){ - if((sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) continue; - DBG("Try proto %d, type %d", p->ai_protocol, p->ai_socktype); - if(connect(sock, p->ai_addr, p->ai_addrlen) == -1){ - WARN("connect()"); - close(sock); sock = -1; - } else break; - } - freeaddrinfo(res); - FREE(str); - break; - default: // never reached - WARNX("Unsupported socket type %d", type); - return -1; - } - DBG("FD: %d", sock); - return sock; -} - /** * @brief getFD - try to open given device/socket * @param path - rest of string for --plugin= (e.g. "N:host.com:12345") @@ -153,9 +75,11 @@ int getFD(const char *path){ case 'D': // serial device return openserial(path); case 'N': // INET socket - return opensocket(path, SOCKT_NET); + //return opensocket(path, SOCKT_NET); + return sl_sock_open(SOCKT_NET, path, 0, 0); case 'U': // UNIX socket - return opensocket(path, SOCKT_UNIX); + //return opensocket(path, SOCKT_UNIX); + return sl_sock_open(SOCKT_UNIX, path, 0, 0); } WARNX("Wrong plugin format: '%c', should be 'D', 'N' or 'U'", type); return -1; diff --git a/Daemons/weatherdaemon_multimeteo/mainweather.c b/Daemons/weatherdaemon_multimeteo/mainweather.c index 32a57d3..3e148e5 100644 --- a/Daemons/weatherdaemon_multimeteo/mainweather.c +++ b/Daemons/weatherdaemon_multimeteo/mainweather.c @@ -53,7 +53,7 @@ enum{ NCOMMWEATH, NLASTAHTUNG, NAHTUNGRSN, - NLIGHTDIST, +// NLIGHTDIST, NBADWEATH, NTERRWEATH, NFORCEDSHTDN, @@ -77,7 +77,7 @@ static val_t collected_data[NAMOUNT_OF_DATA] = { [NMIST] = {.sense = VAL_BROKEN, .type = VALT_UINT, .meaning = IS_MIST}, [NCLOUDS] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_CLOUDS}, [NSKYTEMP] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_SKYTEMP}, - [NLIGHTDIST] = {.sense = VAL_FORCEDSHTDN, .type = VALT_FLOAT, .meaning = IS_LIGTDIST}, + // [NLIGHTDIST] = {.sense = VAL_FORCEDSHTDN, .type = VALT_FLOAT, .meaning = IS_LIGTDIST}, // these are calculated values [NCOMMWEATH] = {.sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_OTHER, .name = "WEATHER", .comment = "Weather (0..3: good/bad/terrible/prohibited)"}, [NLASTAHTUNG] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "EVTTIME", .comment = "UNIX-time of last weather level changing"}, @@ -120,6 +120,7 @@ int weather_level(int newlvl){ * @return current value */ int force_off(int flag){ + DBG("Force OFF to %d", flag); if(flag > -1 && flag < 2){ pthread_mutex_lock(&datamutex); uint32_t curt = time(NULL); @@ -239,7 +240,11 @@ int get_collected(val_t *val, int N){ return FALSE; } pthread_mutex_lock(&datamutex); - //DBG("Copied data of %d", N); +#ifdef EBUG + char buf[KEY_LEN+1]; + get_fieldname(&collected_data[N], buf); + DBG("Copied data of %d (u=%d, nm=%s)", N, collected_data[N].value.u, buf); +#endif *val = collected_data[N]; pthread_mutex_unlock(&datamutex); return TRUE; @@ -348,13 +353,14 @@ static int chkweatherlevel(uint32_t *curlevel, double curvalue, weather_cond_t c val_t *f = &collected_data[NFORCEDSHTDN]; f->value.u = 1; f->time = (int) curt; + DBG("forced = %u", collected_data[NFORCEDSHTDN].value.u); // and set current weather level to prohibited newlevel = WEATHER_PROHIBITED; rtn = 1; } if((uint32_t)newlevel > *curlevel){ // TODO: add logging - //DBG("local level increased to %d", newlevel); + DBG("local level increased to %d", newlevel); *curlevel = (uint32_t)newlevel; rtn = 1; } @@ -434,10 +440,10 @@ void refresh_sensval(sensordata_t *s){ idx = NSKYTEMP; curcond = &WeatherConf.sky; break; - case IS_LIGTDIST: + /*case IS_LIGTDIST: idx = NLIGHTDIST; curcond = &WeatherConf.ligtdist; - break; + break;*/ case IS_BADWEATH: idx = NBADWEATH; curcond = &badweathflag; @@ -449,6 +455,7 @@ void refresh_sensval(sensordata_t *s){ case IS_FORCEDSHTDN: idx = NFORCEDSHTDN; curcond = &shtdnflag; + //DBG("%s have shtdn flag", value.name); break; default : break; } @@ -461,12 +468,15 @@ void refresh_sensval(sensordata_t *s){ if(1 == chkweatherlevel(&curlevel, curvalue, curcond)){ get_fieldname(&value, reason); // copy to `reason` reason of last level increasing force = 1; + DBG("reason: %s; forceflag=%u, old=%d", reason, collected_data[NFORCEDSHTDN].value.u, oldshtdn); if(collected_data[NFORCEDSHTDN].value.u - oldshtdn == 1){ // got shutdown LOGWARN("Forced shutdown flag is set by '%s' of '%s'", reason, s->name); } } } - fix_new_data(&collected_data[idx], &value, force); + if(idx == NFORCEDSHTDN && value.value.u == 0){ + //DBG("Don't clear forced flag by other station"); + }else fix_new_data(&collected_data[idx], &value, force); pthread_mutex_unlock(&datamutex); } pthread_mutex_lock(&datamutex); @@ -492,13 +502,12 @@ void refresh_sensval(sensordata_t *s){ DBG("newlevel: %d, current: %d DECREASED", curlevel, collected_data[NCOMMWEATH].value.u); if(curlevel < WEATHER_TERRIBLE){ // clear forced shutdown flag if(collected_data[NFORCEDSHTDN].value.u){ - LOGMSGADD("Clear forced shutdown flag"); + LOGMSG("Clear forced shutdown flag by '%s' and set weather level to %d", s->name, WEATHER_TERRIBLE); DBG("Clear FORCED SHUTDOWN flag"); collected_data[NCOMMWEATH].value.u = WEATHER_TERRIBLE; + collected_data[NFORCEDSHTDN].value.u = 0; }else collected_data[NCOMMWEATH].value.u = curlevel; - collected_data[NFORCEDSHTDN].value.u = 0; }else --collected_data[NCOMMWEATH].value.u; - collected_data[NCOMMWEATH].time = curtime; collected_data[NLASTAHTUNG].value.u = curtime; LOGMSG("Station '%s', decrease weather level to %d", s->name, collected_data[NCOMMWEATH].value.u); } diff --git a/Daemons/weatherdaemon_multimeteo/plugins/CMakeLists.txt b/Daemons/weatherdaemon_multimeteo/plugins/CMakeLists.txt index 6b5a87a..10e1ce1 100644 --- a/Daemons/weatherdaemon_multimeteo/plugins/CMakeLists.txt +++ b/Daemons/weatherdaemon_multimeteo/plugins/CMakeLists.txt @@ -58,4 +58,9 @@ if(SNMP) list(APPEND LIBS snmp) endif() +if(LIGHTNING) + add_library(lightning SHARED lightning.c) + list(APPEND LIBS lightning) +endif() + install(TARGETS ${LIBS} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/Daemons/weatherdaemon_multimeteo/plugins/dummy.c b/Daemons/weatherdaemon_multimeteo/plugins/dummy.c index d0079d3..e126431 100644 --- a/Daemons/weatherdaemon_multimeteo/plugins/dummy.c +++ b/Daemons/weatherdaemon_multimeteo/plugins/dummy.c @@ -22,7 +22,7 @@ #define SENSOR_NAME "Dummy weatherstation" -#define NS (7) +#define NS (6) static const val_t values[NS] = { // fields `name` and `comment` have no sense until value meaning is `IS_OTHER` {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_WIND}, @@ -31,7 +31,7 @@ static const val_t values[NS] = { // fields `name` and `comment` have no sense u {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_PRESSURE}, {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_HUMIDITY}, {.sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_PRECIP}, - {.sense = VAL_FORCEDSHTDN, .type = VALT_FLOAT, .meaning = IS_LIGTDIST}, + //{.sense = VAL_FORCEDSHTDN, .type = VALT_FLOAT, .meaning = IS_LIGTDIST}, }; static void *mainthread(void *s){ diff --git a/Daemons/weatherdaemon_multimeteo/plugins/lightning.c b/Daemons/weatherdaemon_multimeteo/plugins/lightning.c new file mode 100644 index 0000000..75cf882 --- /dev/null +++ b/Daemons/weatherdaemon_multimeteo/plugins/lightning.c @@ -0,0 +1,199 @@ +/* + * This file is part of the weatherdaemon project. + * Copyright 2026 Edward V. Emelianov . + * + * 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 . + */ + +/** + * Plugin for AS3935-based lightning sensor + * https://github.com/eddyem/stm32samples/tree/master/F1:F103/AS3935-lightning + **/ + +#include +#include "weathlib.h" + +#define SENSOR_NAME "AS3935 lightning sensor" +// minimal distance for forced shutdown +#define MINDIST (5.0) +// time to check wether sensor is alive, seconds +#define TCHECK (30) + +// indexes for text commands and answers +enum{ + CMD_INTERRUPT, + CMD_ENERGY, + CMD_DISTANCE, + ANS_LIGHTNING, + ANS_NOICE, + ANS_DISTURBER, + CMD_AMOUNT +}; + +static const char * commands[CMD_AMOUNT] = { + [CMD_INTERRUPT] = "INTERRUPT", + [CMD_ENERGY] = "energy", + [CMD_DISTANCE] = "distance", + [ANS_LIGHTNING] = "LIGHTNING", + [ANS_NOICE] = "NOICE", + [ANS_DISTURBER] = "DISTURBER", +}; + +// indexes of weather values +enum{ + NINTERRUPT, + NENERGY, + NDISTANCE, + NLIGHTNING, + NSENSNO, + NAMOUNT +}; + +static const val_t values[NAMOUNT] = { + [NINTERRUPT] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "LINTR", .comment = "Lightning int.: 3 - lightning, 4 - noice, 5 - disturber"}, + [NENERGY] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "LENERGY", .comment = "Last lightning energy"}, + [NDISTANCE] = {.sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_OTHER, .name = "LIGTDIST", .comment = "Distance to last lightning, km"}, + [NLIGHTNING] = {.sense = VAL_FORCEDSHTDN, .type = VALT_UINT, .meaning = IS_FORCEDSHTDN, .name = "LIGHTNIN", .comment = "Lightning event occured < 5km"}, + [NSENSNO] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "LSENSNO", .comment = "Last lightning event sensor number"}, +}; + +/** + * @brief parse_string - parsing of sensor's answer + * @param str - text string with data + * @param val - value of key + * @return index of key in `values` or -1 if not found + */ +int parse_string(const char *str, uint32_t *val, uint32_t *nsens){ + if(!str) return -1; + char key[SL_KEY_LEN], value[SL_VAL_LEN]; + DBG("String: %s", str); + if(2 != sl_get_keyval(str, key, value)) return -1; + DBG("key=%s, val=%s", key, value); + int l = strlen(key); + int SensNo = key[--l] - '0'; + DBG("SensNo=%d", SensNo); + if(SensNo < 0 || SensNo > 1) return -1; + key[l] = 0; + int idx = 0; + for(; idx < NLIGHTNING; ++idx){ + if(0 == strcmp(key, commands[idx])) break; + } + if(idx == NLIGHTNING) return -1; + uint32_t u32; + if(idx == 0){ // check interrupt source + if(strstr(value, commands[ANS_LIGHTNING])) u32 = ANS_LIGHTNING; + else if(strstr(value, commands[ANS_DISTURBER])) u32 = ANS_DISTURBER; + else u32 = ANS_NOICE; + }else u32 = (uint32_t)atoi(value); + DBG("idx = %u, val=%u", idx, u32); + if(val) *val = u32; + if(nsens) *nsens = (uint32_t)SensNo; + return idx; +} + +static void *mainthread(void *s){ + FNAME(); + char buf[BUFSIZ]; + time_t tpoll = 0; + sensordata_t *sensor = (sensordata_t *)s; + while(sensor->fdes > -1){ + time_t tnow = time(NULL); + if(tnow - tpoll > sensor->tpoll){ + int dlen = sprintf(buf, "%s0\n%s1\n", commands[CMD_DISTANCE], commands[CMD_DISTANCE]); + if(sl_tty_write(sensor->fdes, buf, dlen)){ + WARN("Can't ask new data"); + break; + } + DBG("poll @%zd, pollt=%zd", tnow, sensor->tpoll); + tpoll = tnow; + } + int canread = sl_canread(sensor->fdes); + if(canread < 0){ + WARNX("Disconnected fd %d", sensor->fdes); + break; + }else if(canread == 1){ + ssize_t got = read(sensor->fdes, buf, BUFSIZ); + if(got > 0){ + sl_RB_write(sensor->ringbuffer, (uint8_t*)buf, got); + }else if(got < 0){ + WARNX("Disconnected?"); + break; + } + } + if(sl_RB_datalen(sensor->ringbuffer) > BUFSIZ-1){ + WARNX("Overfull? Clear data from ring buffer"); + sl_RB_clearbuf(sensor->ringbuffer); + } + int gotfresh = FALSE; + pthread_mutex_lock(&sensor->valmutex); + while(1){ + if(sl_RB_readline(sensor->ringbuffer, buf, BUFSIZ-1) > 0){ + tpoll = tnow; + uint32_t val, nsens; + int idx = parse_string(buf, &val, &nsens); + if(idx > -1){ + DBG("Got index=%d", idx); + gotfresh = TRUE; + if(idx == NINTERRUPT && val == ANS_LIGHTNING){ + DBG("Interrupt: lightning"); + sensor->values[NSENSNO].value.u = nsens; + sensor->values[NSENSNO].time = tnow; + + } + sensor->values[idx].value.u = val; + sensor->values[idx].time = tnow; + } + }else break; + } + // now check values + if(sensor->values[NINTERRUPT].time == tnow && sensor->values[NINTERRUPT].value.u == ANS_LIGHTNING){ // fresh strike + if(tnow - sensor->values[NDISTANCE].time < 3 && sensor->values[NDISTANCE].value.u <= MINDIST){ // ahtung! + if(sensor->values[NLIGHTNING].value.u == 0) DBG("Ahtung!"); + sensor->values[NLIGHTNING].time = tnow; + sensor->values[NLIGHTNING].value.u = 1; + } + }else if(tnow - sensor->values[NINTERRUPT].time > TCHECK && sensor->values[NLIGHTNING].value.u){ // remove old lightning flag + DBG("Clear ahtung"); + sensor->values[NLIGHTNING].value.u = 0; + sensor->values[NLIGHTNING].time = tnow; + } + pthread_mutex_unlock(&sensor->valmutex); + if(gotfresh && sensor->freshdatahandler) sensor->freshdatahandler(sensor); + usleep(1000); + } + sensor->kill(sensor); + return NULL; +} + +sensordata_t *sensor_new(int N, time_t _U_ pollt, const char *descr){ + FNAME(); + if(!descr || !*descr) return NULL; + int fd = getFD(descr); + if(fd < 0) return NULL; + sensordata_t *s = common_new(); + if(!s) return NULL; + snprintf(s->name, NAME_LEN, "%s @ %s", SENSOR_NAME, descr); + s->PluginNo = N; + s->fdes = fd; + s->Nvalues = NAMOUNT; + s->tpoll = TCHECK; + s->values = MALLOC(val_t, NAMOUNT); + for(int i = 0; i < NAMOUNT; ++i) s->values[i] = values[i]; + if(!(s->ringbuffer = sl_RB_new(BUFSIZ)) || + pthread_create(&s->thread, NULL, mainthread, (void*)s)){ + s->kill(s); + return NULL; + } + return s; +} diff --git a/Daemons/weatherdaemon_multimeteo/sensors.c b/Daemons/weatherdaemon_multimeteo/sensors.c index 30246a2..e1b4616 100644 --- a/Daemons/weatherdaemon_multimeteo/sensors.c +++ b/Daemons/weatherdaemon_multimeteo/sensors.c @@ -176,7 +176,7 @@ static const char* const NM[IS_OTHER] = { // names of standard fields [IS_MIST] = "MIST", [IS_CLOUDS] = "CLOUDS", [IS_SKYTEMP] = "SKYTEMP", - [IS_LIGTDIST] = "LIGTDIST", + //[IS_LIGTDIST] = "LIGTDIST", }; // format "sense" of sensor, like "[WIND][1]=2" @@ -235,7 +235,7 @@ int format_sensval(const val_t *v, char *buf, int buflen, int Np){ [IS_MIST] = "Mist (1 - yes, 0 - no)", [IS_CLOUDS] = "Integral sky quality value (bigger - better)", [IS_SKYTEMP] = "Mean sky temperatyre", - [IS_LIGTDIST] = "Distance to last lightning, km", + //[IS_LIGTDIST] = "Distance to last lightning, km", }; const char *name = NULL, *comment = NULL; int idx = v->meaning; diff --git a/Daemons/weatherdaemon_multimeteo/weatherdaemon.files b/Daemons/weatherdaemon_multimeteo/weatherdaemon.files index 13e8954..4a33c63 100644 --- a/Daemons/weatherdaemon_multimeteo/weatherdaemon.files +++ b/Daemons/weatherdaemon_multimeteo/weatherdaemon.files @@ -11,6 +11,7 @@ plugins/btameteo.c plugins/dummy.c plugins/fdexample.c plugins/hydreon.c +plugins/lightning.c plugins/reinhardt.c plugins/snmp.c plugins/wxa100.c diff --git a/Daemons/weatherdaemon_multimeteo/weathlib.h b/Daemons/weatherdaemon_multimeteo/weathlib.h index f074d2e..eea1607 100644 --- a/Daemons/weatherdaemon_multimeteo/weathlib.h +++ b/Daemons/weatherdaemon_multimeteo/weathlib.h @@ -60,7 +60,7 @@ typedef enum{ IS_MIST, // mist (1 - yes, 0 - no) IS_CLOUDS, // integral clouds value (bigger - better) IS_SKYTEMP, // mean sky temperatyre - IS_LIGTDIST, // distance to lightning + //IS_LIGTDIST, // distance to lightning IS_OTHER, // something other - read "name" and "comment" // values after `IS_OTHER` have no pre-defined names and comments!!! IS_BADWEATH, // if meet this flag, set weather level to "BAD"