mirror of
https://github.com/eddyem/small_tel.git
synced 2026-06-19 10:26:25 +03:00
add docs, make little fixes
This commit is contained in:
127
Daemons/weather_logger/Readme.md
Normal file
127
Daemons/weather_logger/Readme.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# meteologger
|
||||
|
||||
**meteologger** is a client that collects meteorological data from the
|
||||
[`weatherdaemon_multimeteo`](https://github.com/eddyem/small_tel/tree/master/Daemons/weatherdaemon_multimeteo)
|
||||
daemon. It connects to the daemon over a TCP or UNIX socket, periodically requests data from all
|
||||
available weather stations, and saves the received values into separate files (one per station).
|
||||
|
||||
The project is written in C and uses the
|
||||
[`usefull_macros`](https://github.com/eddyem/snippets_library) library for socket handling,
|
||||
logging, argument parsing, and helper macros.
|
||||
|
||||
## Repository
|
||||
|
||||
Source code is located in the [small_tel](https://github.com/eddyem/small_tel) repository under
|
||||
`Daemons/weather_logger`.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Linux (uses Linux‑specific calls: `prctl`, `fork`, `waitpid`, signals)
|
||||
- CMake ≥ 4.0
|
||||
- C compiler (GCC, Clang)
|
||||
- `usefull_macros` library ≥ 0.3.5 (must be installed and discoverable via `pkg-config`)
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
git clone --depth=1 https://github.com/eddyem/small_tel.git
|
||||
cd small_tel/Daemons/weather_logger
|
||||
mkdir build && cd build
|
||||
cmake .. # or -DDEBUG=on for debug build
|
||||
make
|
||||
```
|
||||
|
||||
The executable `meteologger` will be placed in `build/` (or `bin/` after installation).
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
su -c "make install"
|
||||
```
|
||||
|
||||
By default the program is installed into `bin` (usually `/usr/local/bin`).
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
meteologger -n <node> -o <directory> [options]
|
||||
```
|
||||
|
||||
### Required arguments
|
||||
|
||||
| Option | Long form | Description |
|
||||
|--------|----------------|-------------|
|
||||
| `-n` | `--node` | Node to connect to: `<host>:<port>` (e.g., `127.0.0.1:5555`) or a UNIX socket path when `-u` is given. |
|
||||
| `-o` | `--output` | Directory where database files will be stored (must exist and be writable). |
|
||||
|
||||
### Optional arguments
|
||||
|
||||
| Option | Long form | Default | Description |
|
||||
|--------|----------------|-----------------------------|-------------|
|
||||
| `-h` | `--help` | - | Show help and exit. |
|
||||
| `-u` | `--isunix` | - | Use a UNIX socket instead of TCP. |
|
||||
| `-l` | `--logfile` | (none) | File to write logs |
|
||||
| `-p` | `--pidfile` | `/tmp/meteologger.pid` | File where the process PID is written. |
|
||||
| `-v` | `--verbose` | 0 | Increase logging verbosity (each `-v` raises the level). |
|
||||
| `-i` | `--interval` | `0.5` | Request interval in seconds. Allowed range: `[0.2, 900]`. |
|
||||
| `-t` | `--timeout` | `1.0` | Network timeout for server responses (seconds). Allowed range: `[0.1, 30]`. |
|
||||
|
||||
### Examples
|
||||
|
||||
1. Connect to a local daemon on port 5555 and store data in `/var/log/weather`:
|
||||
```bash
|
||||
meteologger -n 127.0.0.1:5555 -o /var/log/weather
|
||||
```
|
||||
|
||||
2. Use a UNIX socket `/tmp/weather.sock`, write PID to `/run/meteologger.pid`:
|
||||
```bash
|
||||
meteologger -n /tmp/weather.sock -o /data/weather -u -p /run/meteologger.pid
|
||||
```
|
||||
|
||||
3. Enable verbose logging to a file:
|
||||
```bash
|
||||
meteologger -n localhost:5555 -o /srv/weather -vv -l /var/log/meteologger.log
|
||||
```
|
||||
|
||||
## Output file format
|
||||
|
||||
Files are stored in the directory given by `-o` and are named `weatherXX.log`, where `XX` is a
|
||||
two‑digit station number (from `00` to `99`). If a file already exists for a station (matching the
|
||||
station name in the header comment), new data is appended.
|
||||
|
||||
File structure:
|
||||
|
||||
```
|
||||
# <station_name>
|
||||
# Station #<number>, format: KEYWORD[level],...
|
||||
# TIMESTAMP, <key1>[<level1>], <key2>[<level2>], ...
|
||||
<timestamp>, <value1>, <value2>, ...
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
# WXA100-06 ultrasonic meteostation @ D:/dev/pl2303_0
|
||||
# Station #0, format: KEYWORD[level],...
|
||||
# TIMESTAMP, WIND[1], WINDDIR[2], HUMIDITY[1], EXTTEMP[1], PRESSURE[1], PRECIP[3], PRECIPLV[3], PRECRATE[3]
|
||||
1779369905, 0.50, 120.70, 76.90, 8.40, 590.00, 1, 374.70, 0.00
|
||||
1779369907, 0.60, 128.20, 76.90, 8.40, 590.00, 1, 374.70, 0.00
|
||||
1779369909, 0.70, 130.30, 76.90, 8.40, 590.00, 1, 374.70, 0.00
|
||||
...
|
||||
```
|
||||
|
||||
- First line: comment with station name.
|
||||
- Second line: station number and format description.
|
||||
- Third line: column headers: `TIMESTAMP` followed by keys with their levels.
|
||||
- Following lines: data – Unix timestamp (integer) and values (floating‑point, integer or string).
|
||||
|
||||
-----
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2026 Edward V. Emelianov.
|
||||
**GNU General Public License v3.0** or later.
|
||||
|
||||
See the `LICENSE` file in repository's root directory or [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/)
|
||||
for details.
|
||||
|
||||
@@ -79,6 +79,15 @@ int main(int argc, char **argv){
|
||||
signal(SIGUSR1, signals); // reload DB
|
||||
#ifndef EBUG
|
||||
if(sl_daemonize()) ERRX("Can't daemonize");
|
||||
#endif
|
||||
if(G->logfile){
|
||||
sl_loglevel_e lvl = LOGLEVEL_ERR + G->verb;
|
||||
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
|
||||
DBG("Loglevel: %d", lvl);
|
||||
OPENLOG(G->logfile, lvl, 1);
|
||||
}
|
||||
LOGMSG("Started");
|
||||
#ifndef EBUG
|
||||
catchsig = INT_MAX; // now `signals` won't run exit()
|
||||
while(catchsig == INT_MAX){ // guard for dead processes
|
||||
childpid = fork();
|
||||
@@ -114,8 +123,10 @@ int main(int argc, char **argv){
|
||||
}else{
|
||||
pid_t self = getpid();
|
||||
prepare_and_run(G);
|
||||
if(catchsig == INT_MAX) LOGERR("Child process %d died", self);
|
||||
else LOGERR("Child process %d exits with status %d", self, catchsig);
|
||||
if(catchsig == INT_MAX){
|
||||
LOGERR("Child process %d died", self);
|
||||
catchsig = 0; // like normal exit
|
||||
}else LOGERR("Child process %d exits with status %d", self, catchsig);
|
||||
; // cleanup child here
|
||||
#ifdef EBUG
|
||||
if(G->pidfile) unlink(G->pidfile); // unlink PID-file in debug-mode
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
CMakeLists.txt
|
||||
Readme.md
|
||||
main.c
|
||||
parseargs.c
|
||||
parseargs.h
|
||||
|
||||
@@ -61,7 +61,7 @@ glob_pars *parseargs(int *argc, char ***argv){
|
||||
// remove trailing '/'
|
||||
int eol = strlen(G.bddir) - 1;
|
||||
DBG("eol=%d", eol);
|
||||
while(eol > 0){
|
||||
while(eol > 0){ // don't remove leading slash in case of "/"
|
||||
DBG("before: %s", G.bddir);
|
||||
if(G.bddir[eol] == '/') G.bddir[eol] = 0;
|
||||
else break;
|
||||
@@ -73,10 +73,7 @@ glob_pars *parseargs(int *argc, char ***argv){
|
||||
if(G.net_timeout < MIN_NET_TMOUT || G.net_timeout > MAX_NET_TMOUT)
|
||||
ERRX("Wrong network timeout %g, should be in [%g, %g]", G.net_timeout, MIN_NET_TMOUT, MAX_NET_TMOUT);
|
||||
if(G.logfile){
|
||||
sl_loglevel_e lvl = LOGLEVEL_ERR + G.verb;
|
||||
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
|
||||
DBG("Loglevel: %d", lvl);
|
||||
if(!OPENLOG(G.logfile, lvl, 1)) ERRX("Can't open log file %s", G.logfile);
|
||||
if(*G.logfile != '/') ERRX("Logging file path should be absolute!");
|
||||
}
|
||||
return &G;
|
||||
}
|
||||
|
||||
@@ -128,7 +128,15 @@ void set_nettimeout(double dt){
|
||||
*/
|
||||
static bool write2fd(senslog_t *sensor, const char *str, size_t len){
|
||||
if(!sensor || !str || len < 1) return false;
|
||||
if(write(sensor->fd, str, len) != (ssize_t)len){
|
||||
const char *ptr = str;
|
||||
size_t written = 0;
|
||||
while(len){
|
||||
ssize_t l = write(sensor->fd, ptr, len);
|
||||
if(l < 0) break;
|
||||
len -= l;
|
||||
ptr += l;
|
||||
}
|
||||
if(written != len){
|
||||
LOGERR("Can't write '%s' to file %s: %s", str, sensor->path, strerror(errno));
|
||||
WARNX("Can't write data to file");
|
||||
close(sensor->fd);
|
||||
@@ -405,8 +413,10 @@ static bool analyse_list(sl_sock_t *sock){
|
||||
if(Nsensors <= idx){
|
||||
int oldN = Nsensors;
|
||||
Nsensors = idx + 1;
|
||||
senslog_t *old_sensors = sensors;
|
||||
sensors = realloc(sensors, Nsensors * sizeof(senslog_t));
|
||||
if(!sensors){
|
||||
sensors = old_sensors; // restore old pointer instead of NULL
|
||||
sensors_delete();
|
||||
LOGERR("analyse_list(): error in realloc()");
|
||||
WARNX("analyse_list(): error in realloc()");
|
||||
@@ -545,8 +555,11 @@ static bool poll_server_answers(sl_sock_t *sock){
|
||||
// now throw data to disk
|
||||
DBG("throw '%s' with length %d to disk", sensor->buf, sensor->buflen);
|
||||
allOK = write2fd(sensor, sensor->buf, sensor->buflen);
|
||||
write2fd(sensor, "\n", 1);
|
||||
sensor->lasttimestamp = ts;
|
||||
if(allOK){
|
||||
write2fd(sensor, "\n", 1);
|
||||
fsync(sensor->fd); // sync last full record
|
||||
sensor->lasttimestamp = ts;
|
||||
}
|
||||
DBG("Data for %d is %swritten to disk", sensor->idx, allOK ? "" : "not ");
|
||||
}
|
||||
}
|
||||
@@ -596,8 +609,22 @@ static bool poll_server_answers(sl_sock_t *sock){
|
||||
}
|
||||
}
|
||||
}
|
||||
sensor->buflen += snprintf(sensor->buf + sensor->buflen, BUFSIZ-sensor->buflen, "%s%s",
|
||||
value, sensor->nvalues-1 != idx ? ", " : "");
|
||||
int avail = BUFSIZ - sensor->buflen;
|
||||
if(avail <= 0){
|
||||
LOGWARN("Sensor %d: writing buffer overfull", sensor->idx);
|
||||
WARNX("Overfull; clear all");
|
||||
sensor->lastvalidx = -1;
|
||||
sensor->buflen = 0;
|
||||
return false;
|
||||
}
|
||||
int n = snprintf(sensor->buf + sensor->buflen, avail, "%s%s",
|
||||
value, sensor->nvalues-1 != idx ? ", " : "");
|
||||
if(n >= avail){
|
||||
// Data would be truncated handle error or discard
|
||||
n = avail - 1;
|
||||
LOGWARN("Sensor %d: writing buffer truncated", sensor->idx);
|
||||
}
|
||||
sensor->buflen += n;
|
||||
sensor->lastvalidx = idx;
|
||||
DBG("Now buf[%d]=%s", sensor->idx, sensor->buf);
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user