Compare commits

...

16 Commits

19 changed files with 612 additions and 362 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ bin/
compile_commands.json compile_commands.json
.cache/ .cache/
config.conf config.conf
.clangd

View File

@ -9,7 +9,8 @@ SRCS=src/main.c src/util.c src/lcd.c src/ths.c src/button.c src/ui/screen.c\
src/ui/datesel.c src/ui/statsby.c src/ui/datapoints.c src/ui/timesel.c\ src/ui/datesel.c src/ui/statsby.c src/ui/datapoints.c src/ui/timesel.c\
src/ui/statrange.c src/config.c src/ui/blankscreen.c src/drive.c\ src/ui/statrange.c src/config.c src/ui/blankscreen.c src/drive.c\
src/ui/exportscreen.c src/ui/setdatescreen.c src/ui/viewdatescreen.c\ src/ui/exportscreen.c src/ui/setdatescreen.c src/ui/viewdatescreen.c\
src/ui/powerscreen.c src/ui/settzscreen.c src/ui/cleardatascreen.c src/ui/powerscreen.c src/ui/settzscreen.c src/ui/cleardatascreen.c\
src/async.c
PROG=rpi4b-temp-humidity PROG=rpi4b-temp-humidity
all: config.conf bin/${PROG} all: config.conf bin/${PROG}
@ -24,7 +25,7 @@ bin/main.o bin/util.o bin/lcd.o bin/ths.o bin/button.o: src/util.h
bin/ui/screen.o bin/ui/statsby.o bin/ui/datesel.o: src/util.h bin/ui/screen.o bin/ui/statsby.o bin/ui/datesel.o: src/util.h
bin/ui/datapoints.o bin/ui/timesel.o bin/ui/statrange.o: src/util.h bin/ui/datapoints.o bin/ui/timesel.o bin/ui/statrange.o: src/util.h
bin/ui/viewdatescreen.o bin/ui/setdatescreen.o bin/ui/powerscreen.o: src/util.h bin/ui/viewdatescreen.o bin/ui/setdatescreen.o bin/ui/powerscreen.o: src/util.h
bin/ui/settzscreen.o bin/ui/cleardatascreen.o: src/util.h bin/ui/settzscreen.o bin/ui/cleardatascreen.o bin/async.o: src/util.h
bin/main.o bin/lcd.o bin/screen.o bin/ui/datesel.o: src/lcd.h bin/main.o bin/lcd.o bin/screen.o bin/ui/datesel.o: src/lcd.h
bin/ui/statsby.o bin/ui/timesel.o bin/ui/statrange.o: src/lcd.h bin/ui/statsby.o bin/ui/timesel.o bin/ui/statrange.o: src/lcd.h
@ -59,6 +60,7 @@ bin/main.o bin/ui/powerscreen.o: src/ui/powerscreen.h
bin/main.o bin/ui/settzscreen.o: src/ui/settzscreen.h bin/main.o bin/ui/settzscreen.o: src/ui/settzscreen.h
bin/main.o bin/ui/cleardatascreen.o: src/ui/cleardatascreen.h bin/main.o bin/ui/cleardatascreen.o: src/ui/cleardatascreen.h
bin/main.o bin/drive.o bin/ui/exportscreen.o: src/drive.h bin/main.o bin/drive.o bin/ui/exportscreen.o: src/drive.h
bin/main.o bin/drive.o bin/ui/exportscreen.o: src/async.h
.if "${DEFAULT_TEMP_UNIT}" != "F" && "${DEFAULT_TEMP_UNIT}" != "C" &&\ .if "${DEFAULT_TEMP_UNIT}" != "F" && "${DEFAULT_TEMP_UNIT}" != "C" &&\
"${DEFAULT_TEMP_UNIT}" != "c" && "${DEFAULT_TEMP_UNIT}" != "c" "${DEFAULT_TEMP_UNIT}" != "c" && "${DEFAULT_TEMP_UNIT}" != "c"

View File

@ -193,14 +193,6 @@ armstub=armstub8-gic.bin
enable_uart=1 enable_uart=1
``` ```
Then enable the `ntpd` and `ntpdate` services to sync with the RTC on boot and
periodically:
```sh
service ntpd enable
service ntpdate enable
```
If you want, you can also change the `root` password at this point: If you want, you can also change the `root` password at this point:
```sh ```sh
@ -214,7 +206,15 @@ sysctl dev.ds3231.0.temperature dev.gpioths.0.temperature
``` ```
If you get a warning about an unknown oid, it means that the sensor is not If you get a warning about an unknown oid, it means that the sensor is not
installed or configured correctly. installed or configured correctly. The following commands can help in debugging
the problem. For example, if the battery in the DS3231 has gone bad, the above
command will show no error, however, the below command will print a warning
message.
```sh
dmesg | grep -i ds3231 # for ds3231 debugging
dmesg | grep -i gpioths # for temperature sensor debugging
```
It should be noted that FreeBSD does not currently support wireless on the It should be noted that FreeBSD does not currently support wireless on the
Raspberry Pi 4 at the time of writing. Thus, you will need to use a wired Raspberry Pi 4 at the time of writing. Thus, you will need to use a wired
@ -242,6 +242,15 @@ tar xf main.tar.gz
cd rpi4b-temp-humidity cd rpi4b-temp-humidity
``` ```
If you do plan on connecting the device to the internet long term (remember to
arrange for it to be updated!!!), you can enable the ntpd and ntpdate services
to sync with an NTP server.
```sh
service ntpd enable # sync periodically
service ntpdate enable # sync on boot
```
At this point, take a moment to edit the `config.mk` file to to change any At this point, take a moment to edit the `config.mk` file to to change any
default options. These can also be changed after installation by editing default options. These can also be changed after installation by editing
`/usr/local/etc/rpi4b-temp-humidity/config.conf`. `/usr/local/etc/rpi4b-temp-humidity/config.conf`.
@ -290,5 +299,41 @@ The buttons from closest to the display to farthest are: select, back, up, and
down. When the device first turns on, the stats screen will be shown. This screen down. When the device first turns on, the stats screen will be shown. This screen
displays the current temperature, humidity, and time. Pressing the back button displays the current temperature, humidity, and time. Pressing the back button
will take you to the main menu. The following is a short description of each screen: will take you to the main menu. The following is a short description of each screen:
- "Stats by" screen shows stats by a specific period (e.g. hour, day, week)
- - `Current stats` - the current time, temperature, and humidity
- `Stats by` - show stats by a specific period (e.g. hour, day, week). Press
the select button while on a stats screen to cycle what is displayed. Use up
and down while on a stats screen to got to the next or previous period.
- `Data points` - view individual data points. Press up or down to go to the next
or previous data point.
- `Average range` - show stats between two times. This has the same controls as
the "Stats by" screen above.
- `Export` - export data to a plugged in USB drive. Only file-systems detected
by [fstyp(8)][11] are supported. If a drive with an unsupported file-system,
or no file-system, is selected, you will be prompted to format it as
FAT32. Exports can be done in both sqlite3 and CSV format. The temperature is
in deci-Kelvin (1/10 of a kelvin) and the humidity is an integer percent
(ex. 53% would just be 53). The time is seconds since January 1, 1970 UTC.
- `Blank display` - turn off the display. Press any button to cancel. You must
have specified a "bl_pin" in the config file.
- `Power options` - re-initialize the program, shutdown or reboot the device
- `View date/time` - show the current date and time down to the second
- `Set date/time` - set the current date and time. The time entered is in local
time.
- `Set timezone` - set the timezone. The prompts will guide you through creating a
timezone as described in [tzset(3)][12]. Another, possibly easier to
understand description can be found in the corresponding Linux man page
[tzset(3)][13].
- `Clear data` - **DELETE ALL DATA** on the device. When the operation is complete,
the database will be empty. **NO BACKUP IS MADE** so be careful.
As a final note on the usage of the "Stats by", "Data points", and "Average
range" screens, pressing the up and down buttons to move across different DST
stats can cause some weird-looking behavior. If this causes you trouble, simply
back out of the stats screen and enter back in from a different start date. As
this is the expected behavior of the various sqlite3 and libc date and time
functions, it will not be fixed (because it's not broken).
[11]: https://man.freebsd.org/cgi/man.cgi?query=fstyp&manpath=FreeBSD+14.0-RELEASE+and+Ports
[12]: https://man.freebsd.org/cgi/man.cgi?query=tzset&manpath=FreeBSD+14.0-RELEASE+and+Ports
[13]: https://man.archlinux.org/man/tzset.3

111
src/async.c Normal file
View File

@ -0,0 +1,111 @@
/*
* async.c - Utilities for async operations
* Copyright (C) 2024 Alexander Rosenberg
*
* 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. See the LICENSE file for more information.
*/
#include "async.h"
#include "signal.h"
#include "util.h"
#include <sys/wait.h>
#include <sys/signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <err.h>
AsyncOperation *async_operation_start(AsyncOperationPollFunc poll,
AsyncOperationCleanupFunc cleanup,
void *data1, void *data2,
const char *path, char * const *args) {
pid_t pid = fork();
if (pid < 0) {
warn("fork(2)");
return NULL;
} else if (pid == 0) {
// child
if (!GLOBAL_OPTS.verbose) {
freopen("/dev/null", "w", stdout);
}
execvp(path, args);
err(1, "execvp(2) for \"%s\"", path);
} else {
// parent
AsyncOperation *op = malloc_checked(sizeof(AsyncOperation));
op->pid = pid;
op->data1 = data1;
op->data2 = data2;
op->poll_func = poll;
op->cleanup_func = cleanup;
op->done = false;
return op;
}
}
AsyncOperation *async_operation_take(AsyncOperationPollFunc poll,
AsyncOperationCleanupFunc cleanup,
void *data1, void *data2, pid_t pid) {
AsyncOperation *op = malloc_checked(sizeof(AsyncOperation));
op->pid = pid;
op->data1 = data1;
op->data2 = data2;
op->poll_func = poll;
op->cleanup_func = cleanup;
op->done = false;
return op;
}
AsyncOperationStatus async_operation_poll(AsyncOperation *op, bool hang) {
if (op->done) {
// polling a done process is a no-op
return ASYNC_OPERATION_DONE;
}
pid_t proc = waitpid(op->pid, &op->status, hang ? 0 : WNOHANG);
if (proc < 0) {
warn("waitpid(2)");
async_operation_cancel(op, false);
return ASYNC_OPERATION_FAILED;
} else if (proc > 0) {
op->done = true;
}
if (op->poll_func && !op->poll_func(op)) {
async_operation_cancel(op, false);
return ASYNC_OPERATION_FAILED;
}
return op->done ? ASYNC_OPERATION_DONE : ASYNC_OPERATION_WORKING;
}
void async_operation_cancel(AsyncOperation *op, bool force) {
if (op->done) {
// cancelling a done process is a no-op
return;
}
kill(op->pid, force ? SIGKILL : SIGTERM);
if (!force) {
int status;
if (!timed_wait(op->pid, &status, 1000 /* 1ms */)) {
warn("waitpid(2)");
}
if (!WIFEXITED(status) && !WIFSIGNALED(status)) {
// force kill it if it refuses to exit itself
async_operation_cancel(op, true);
}
}
op->done = true;
}
void async_operation_free(AsyncOperation *op) {
if (op) {
if (!op->done) {
async_operation_cancel(op, false);
}
if (op->cleanup_func) {
op->cleanup_func(op);
}
free(op);
}
}

75
src/async.h Normal file
View File

@ -0,0 +1,75 @@
/*
* async.h - Utilities for async operations
* Copyright (C) 2024 Alexander Rosenberg
*
* 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. See the LICENSE file for more information.
*/
#ifndef INCLUDED_ASYNC_H
#define INCLUDED_ASYNC_H
#include <stdbool.h>
#include <sys/types.h>
typedef enum {
ASYNC_OPERATION_DONE,
ASYNC_OPERATION_FAILED,
ASYNC_OPERATION_WORKING
} AsyncOperationStatus;
typedef struct _AsyncOperation AsyncOperation;
// true on success, false otherwise
typedef bool(*AsyncOperationPollFunc)(AsyncOperation *op);
typedef void(*AsyncOperationCleanupFunc)(AsyncOperation *op);
struct _AsyncOperation {
AsyncOperationPollFunc poll_func;
AsyncOperationCleanupFunc cleanup_func;
int status;
pid_t pid;
bool done;
void *data1;
void *data2;
};
#define async_operation_failed(o) (WEXITSTATUS((o)->status) != 0)
/*
* Start a new async operation using the executable PATH and the arguments
* ARGS. The first element of ARGS should be the name of the new process, by
* convention. DATA1 and DATA2 are arbitrary pointers for storing data needed by
* CLEANUP or POLL.
* Return: NULL on error, the operation otherwise
*/
AsyncOperation *async_operation_start(AsyncOperationPollFunc poll,
AsyncOperationCleanupFunc cleanup,
void *data1, void *data2,
const char *path, char * const *args);
/*
* Create a new async operation from an already running process with PID.
*/
AsyncOperation *async_operation_take(AsyncOperationPollFunc poll,
AsyncOperationCleanupFunc cleanup,
void *data1, void *data2, pid_t pid);
/*
* Poll and return the status of OP. If HANG, wait until OP is done.
*/
AsyncOperationStatus async_operation_poll(AsyncOperation *op, bool hang);
/*
* Kill OP, by FORCE if necessary. This does NOT free the process, you need to
* do that yourself.
*/
void async_operation_cancel(AsyncOperation *op, bool force);
/*
* Free OP by calling its cleanup function. This will cancel it if it's still
* running!
*/
void async_operation_free(AsyncOperation *op);
#endif

View File

@ -19,7 +19,6 @@
#include <dirent.h> #include <dirent.h>
#include <sys/param.h> #include <sys/param.h>
#include <sys/mount.h> #include <sys/mount.h>
#include <pthread.h>
static int compare_drives(const Drive *d1, const Drive *d2) { static int compare_drives(const Drive *d1, const Drive *d2) {
return strcmp(d1->name, d2->name); return strcmp(d1->name, d2->name);
@ -62,29 +61,16 @@ void drive_free_drives(Drive *drives, size_t count) {
free(drives); free(drives);
} }
static void close_pipe(void *ptr) {
int *pipe = ptr;
if (pipe[0] >= 0) {
close(pipe[0]);
}
if (pipe[1] >= 0) {
close(pipe[1]);
}
}
char *drive_guess_fs(const char *dev) { char *drive_guess_fs(const char *dev) {
char *result_fs = NULL;
int ctop[2] = {0, 0}; int ctop[2] = {0, 0};
pthread_cleanup_push(close_pipe, ctop);
pthread_cleanup_push(free, result_fs);
if (pipe(ctop) < 0) { if (pipe(ctop) < 0) {
warn("failed to create pipe"); warn("failed to create pipe");
goto cleanup; return NULL;
} }
pid_t pid = fork(); pid_t pid = fork();
if (pid < 0) { if (pid < 0) {
warn("fork(2) failed"); warn("fork(2) failed");
goto cleanup; return NULL;
} else if (pid == 0) { } else if (pid == 0) {
// child // child
close(ctop[0]); // close read end close(ctop[0]); // close read end
@ -104,125 +90,119 @@ char *drive_guess_fs(const char *dev) {
int status; int status;
if (waitpid(pid, &status, 0) < 0) { if (waitpid(pid, &status, 0) < 0) {
warn("waiting for child failed"); warn("waiting for child failed");
goto cleanup; return NULL;
} }
if (WEXITSTATUS(status) != 0) { if (WEXITSTATUS(status) != 0) {
// no error, we probably just don't know the FS type // no error, we probably just don't know the FS type
goto cleanup; return NULL;
} }
char read_buf[32]; char read_buf[32];
ssize_t read_count = read(ctop[0], read_buf, 32); ssize_t read_count = read(ctop[0], read_buf, 32);
if (read_count < 0) { if (read_count < 0) {
warn("reading from pipe failed"); warn("reading from pipe failed");
goto cleanup; return NULL;
} }
read_buf[read_count - 1] = '\0'; read_buf[read_count - 1] = '\0';
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); return strdup_checked(read_buf);
result_fs = strdup_checked(read_buf);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
cleanup:
pthread_cleanup_pop(false); // dont free result
pthread_cleanup_pop(true);
return result_fs;
} }
bool drive_newfs_msdos(Drive *drive) { static bool newfs_operation_poll(AsyncOperation *op) {
pid_t pid = fork(); if (op->done && !async_operation_failed(op)) {
if (pid < 0) { Drive *drive = op->data1;
warn("fork(2) failed");
return false;
} else if (pid == 0) {
// child
if (!GLOBAL_OPTS.verbose) {
freopen("/dev/null", "w", stdout);
}
execl("/sbin/newfs_msdos", "/sbin/newfs_msdos", "-F32", drive->path, NULL);
warn("execl(2) failed");
exit(1);
} else {
// parent
int status;
if (waitpid(pid, &status, 0) < 0) {
warn("waiting for child failed");
return false;
}
if (WEXITSTATUS(status) != 0) {
warnx("creating FAT32 FS failed");
return false;
}
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
free(drive->fs); free(drive->fs);
drive->fs = strdup_checked("msdosfs"); drive->fs = strdup_checked("msdosfs");
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
return true;
} }
}
bool drive_mount(Drive *drive) {
bool status = true;
char *name = NULL;
pthread_cleanup_push(free, name);
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
name = strdup_checked("/tmp/mntXXXXXX");
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
if (!mkdtemp(name)) {
warn("failed to create temp mount point");
status = false;
goto cleanup;
}
pid_t pid = fork();
if (pid < 0) {
warn("fork(2) failed");
status = false;
goto cleanup;
} else if (pid == 0) {
// child
if (!GLOBAL_OPTS.verbose) {
freopen("/dev/null", "w", stdout);
}
execl("/sbin/mount", "/sbin/mount", "-t", drive->fs, drive->path, name, NULL);
warn("execl(2) failed");
abort();
}
// parent
int exit_code;
if (waitpid(pid, &exit_code, 0) < 0) {
warn("waiting for child failed");
rmdir(name);
status = false;
goto cleanup;
}
if (WEXITSTATUS(exit_code) != 0) {
warnx("mount(8) failed: \"%s\"", drive->path);
rmdir(name);
status = false;
goto cleanup;
}
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
drive->mnt = strdup_checked(name);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
LOG_VERBOSE("Mounted \"%s\" with fs %s to \"%s\"\n", drive->path, drive->fs,
drive->mnt);
cleanup:
pthread_cleanup_pop(true);
return status;
}
bool drive_unmount(Drive *drive, bool force) {
if (!drive->mnt) {
warnx("\"%s\" is not mounted", drive->path);
return false;
}
int flags = force ? MNT_FORCE : 0;
if (unmount(drive->mnt, flags) < 0) {
warn("unmount(2) failed for \"%s\"", drive->path);
return false;
}
rmdir(drive->mnt);
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
free(drive->mnt);
drive->mnt = NULL;
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
LOG_VERBOSE("Unmounted \"%s\"\n", drive->path);
return true; return true;
} }
AsyncOperation *drive_newfs_msdos(Drive *drive) {
if (drive->mnt) {
warnx("\"%s\" is mounted", drive->path);
return NULL;
}
char * const args[] = {"newfs_msdos", "-F32", drive->path, NULL};
AsyncOperation * op= async_operation_start(newfs_operation_poll, NULL,
drive, NULL, args[0], args);
if (!op) {
warnx("%s: failed to start newfs_msdos process", drive->path);
return NULL;
}
return op;
}
static bool mount_operation_poll(AsyncOperation *op) {
Drive *drive = op->data1;
if (op->done && !async_operation_failed(op)) {
// steal the mount point string
drive->mnt = op->data2;
op->data2 = NULL;
LOG_VERBOSE("Mounted \"%s\" at \"%s\"\n", drive->path, drive->mnt);
} else if (op->done) {
warnx("mount of \"%s\" failed with status %d", drive->path,
WEXITSTATUS(op->status));
}
return true;
}
static void mount_operation_cleanup(AsyncOperation *op) {
free(op->data2);
}
AsyncOperation *drive_mount(Drive *drive) {
if (drive->mnt) {
warnx("\"%s\" is already mounted", drive->path);
return NULL;
}
char *name = strdup_checked("/tmp/mntXXXXXX");
if (!mkdtemp(name)) {
warn("failed to create temp mount point");
return NULL;
}
char * const args[] = { "mount", "-t", drive->fs, drive->path, name, NULL };
AsyncOperation *op = async_operation_start(mount_operation_poll,
mount_operation_cleanup,
drive, name, args[0], args);
if (!op) {
warnx("%s: failed to start mount process", drive->path);
free(name);
return NULL;
}
return op;
}
bool unmount_operation_poll(AsyncOperation *op) {
if (op->done) {
Drive *drive = op->data1;
rmdir(drive->mnt);
free(drive->mnt);
drive->mnt = NULL;
LOG_VERBOSE("Unmounted \"%s\"\n", drive->path);
}
return true;
}
AsyncOperation *drive_unmount(Drive *drive, bool force) {
if (!drive->mnt) {
warnx("\"%s\" is not mounted", drive->path);
return NULL;
}
const char *args[4];
if (force) {
args[0] = "umount";
args[1] = "-f";
args[2] = drive->mnt;
args[3] = NULL;
} else {
args[0] = "umount";
args[1] = drive->mnt;
args[2] = NULL;
}
AsyncOperation *op = async_operation_start(unmount_operation_poll, NULL,
drive, NULL, args[0],
(char *const *) args);
if (!op) {
warnx("%s: failed to unmount drive", drive->mnt);
return NULL;
}
return op;
}

View File

@ -10,8 +10,10 @@
#ifndef INCLUDED_DRIVE_H #ifndef INCLUDED_DRIVE_H
#define INCLUDED_DRIVE_H #define INCLUDED_DRIVE_H
#include <stddef.h> #include "async.h"
#include <stdbool.h> #include <stdbool.h>
#include <sys/types.h>
typedef struct { typedef struct {
char *name; char *name;
@ -23,7 +25,8 @@ typedef struct {
/* /*
* Attempt to list all connected USB drives and their file systems. The list is * Attempt to list all connected USB drives and their file systems. The list is
* sorted alphabetically. * sorted alphabetically.
* Return: all connected USB (actually any serial drive) and their file systems. * Return: all connected USB (actually any serial drive, so SATA too) and their
* file systems.
*/ */
Drive *drive_list_usb_drives(size_t *count); Drive *drive_list_usb_drives(size_t *count);
@ -40,21 +43,21 @@ void drive_free_drives(Drive *drives, size_t count);
char *drive_guess_fs(const char *dev); char *drive_guess_fs(const char *dev);
/* /*
* Create an MSDOS (FAT32) FS on DRIVE. DRIVE MUST NOT BE MOUNTED! * Create an MSDOS (FAT32) FS on DRIVE.
* Return: true on success, false otherwise * Return: an operation on success, NULL otherwise
*/ */
bool drive_newfs_msdos(Drive *drive); AsyncOperation *drive_newfs_msdos(Drive *drive);
/* /*
* Mount DRIVE onto a new temp. dir in /tmp. DRIVE MUST NOT BE MOUNTED! * Mount DRIVE onto a new temp. dir in /tmp.
* Return: true on success, false otherwise * Return: an operation on success, NULL otherwise
*/ */
bool drive_mount(Drive *drive); AsyncOperation *drive_mount(Drive *drive);
/* /*
* Unmount DRIVE. Force the operation if FORCE. Also rmdir(2) the mount point. * Unmount DRIVE. Force the operation if FORCE. Also rmdir(2) the mount point.
* Return: true of success, false on failure. * Return: an operation on success, NULL otherwise
*/ */
bool drive_unmount(Drive *drive, bool force); AsyncOperation *drive_unmount(Drive *drive, bool force);
#endif #endif

View File

@ -10,8 +10,6 @@
#include "lcd.h" #include "lcd.h"
#include "util.h" #include "util.h"
#include "ths.h" #include "ths.h"
#include "button.h"
#include "menu.h"
#include "config.h" #include "config.h"
#include "ui/screen.h" #include "ui/screen.h"
#include "ui/statsby.h" #include "ui/statsby.h"
@ -27,17 +25,13 @@
#include <unistd.h> #include <unistd.h>
#include <err.h> #include <err.h>
#include <errno.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <ctype.h>
#include <figpar.h> #include <figpar.h>
#include <inttypes.h>
#include <time.h> #include <time.h>
#include <sys/time.h> #include <sys/time.h>
#include <unistd.h> #include <unistd.h>
#include <pthread.h> #include <pthread.h>
#include <stdatomic.h>
#include <signal.h> #include <signal.h>
#include <sqlite3.h> #include <sqlite3.h>
#include <libgen.h> #include <libgen.h>
@ -75,7 +69,8 @@ void start_update_thread(pthread_t *thread);
// cross thread variables // cross thread variables
sqlite3 *DATABASE; sqlite3 *DATABASE;
pthread_mutex_t STAT_MUTEX; pthread_mutex_t STAT_MUTEX;
uint32_t LAST_TEMP, LAST_HUMID; // these values mean that we haven't yet read the temperature
uint32_t LAST_TEMP = UINT32_MAX - 1, LAST_HUMID = UINT32_MAX - 1;
_Atomic bool RUNNING = true; _Atomic bool RUNNING = true;
/* /*
* Lock the cross thread variables above. * Lock the cross thread variables above.
@ -98,7 +93,8 @@ int main(int argc, char *const *argv) {
setenv("TZ", GLOBAL_OPTS.timezone, true); setenv("TZ", GLOBAL_OPTS.timezone, true);
NEED_CLEAR_TZ = true; NEED_CLEAR_TZ = true;
} else { } else {
LOG_VERBOSE("Config timezone option shadowed by local environment variable\n"); LOG_VERBOSE("Config timezone option shadowed by local environment "
"variable.\n");
} }
} }
tzset(); tzset();
@ -224,7 +220,6 @@ err(1, "failed to setup signal %s", #sig);\
void setup_signals() { void setup_signals() {
SIGNAL_SETUP_CHECKED(SIGTERM, exit_signal_callback); SIGNAL_SETUP_CHECKED(SIGTERM, exit_signal_callback);
SIGNAL_SETUP_CHECKED(SIGINT, exit_signal_callback); SIGNAL_SETUP_CHECKED(SIGINT, exit_signal_callback);
SIGNAL_SETUP_CHECKED(SIGUSR1, SIG_IGN); // used to kill export operations
} }
static const char *CREATE_DB_TABLE_QUERY = static const char *CREATE_DB_TABLE_QUERY =

View File

@ -27,7 +27,7 @@ static bool blank_screen_dispatch(Screen *screen,
Screen *blank_screen_new() { Screen *blank_screen_new() {
Screen *s = malloc_checked(sizeof(Screen)); Screen *s = malloc_checked(sizeof(Screen));
screen_init(s, "Blank Display", screen_init(s, "Blank display",
(ScreenDispatchFunc) blank_screen_dispatch, (ScreenDispatchFunc) blank_screen_dispatch,
(ScreenCleanupFunc) free); (ScreenCleanupFunc) free);
return s; return s;

View File

@ -155,7 +155,7 @@ static bool data_points_screen_dispatch(DataPointsScreen *screen, SensorState *s
DataPointsScreen *data_points_screen_new() { DataPointsScreen *data_points_screen_new() {
DataPointsScreen *s = malloc_checked(sizeof(DataPointsScreen)); DataPointsScreen *s = malloc_checked(sizeof(DataPointsScreen));
screen_init(&s->parent, "Data Points", screen_init(&s->parent, "Data points",
(ScreenDispatchFunc) data_points_screen_dispatch, (ScreenDispatchFunc) data_points_screen_dispatch,
(ScreenCleanupFunc) free); (ScreenCleanupFunc) free);
s->need_redraw = true; s->need_redraw = true;

View File

@ -10,14 +10,11 @@
#include "exportscreen.h" #include "exportscreen.h"
#include <err.h> #include <err.h>
#include <errno.h>
#include <sys/types.h> #include <sys/types.h>
#include <signal.h>
#include <stdarg.h> #include <stdarg.h>
#include <pthread.h>
#include <pthread_np.h>
#include <unistd.h> #include <unistd.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <signal.h>
static void export_screen_reset(ExportScreen *screen, static void export_screen_reset(ExportScreen *screen,
SensorState *state) { SensorState *state) {
@ -36,19 +33,24 @@ static void export_screen_reset(ExportScreen *screen,
* Retrun: true if task is done, false otherwise * Retrun: true if task is done, false otherwise
*/ */
static bool export_check_bg_task(ExportScreen *screen) { static bool export_check_bg_task(ExportScreen *screen) {
void *status; switch (async_operation_poll(screen->current_op, false)) {
if (pthread_peekjoin_np(screen->backgroud_oper, &status) != EBUSY) { case ASYNC_OPERATION_DONE:
// done if (async_operation_failed(screen->current_op)) {
screen->bg_inited = false; goto failed;
if ((intptr_t) status == EXPORT_SCREEN_FAILED) {
// error happened
warnx("background export operation failed");
} }
screen->stage = (intptr_t) status;
screen->need_redraw = true; screen->need_redraw = true;
screen->current_op = NULL;
return true; return true;
case ASYNC_OPERATION_FAILED:
goto failed;
case ASYNC_OPERATION_WORKING:
return false;
} }
return false; failed:
screen->stage = EXPORT_SCREEN_FAILED;
screen->need_redraw = true;
screen->current_op = NULL;
return true;
} }
static const char *FORMAT_STRINGS[] = { static const char *FORMAT_STRINGS[] = {
@ -150,15 +152,9 @@ static enum {
} export_confirm_action(ExportScreen *screen, } export_confirm_action(ExportScreen *screen,
SensorState *state, SensorState *state,
const char *fmt, ...) { const char *fmt, ...) {
int done_status = EXPORT_CONFIRM_CONTINUE; int done_status;
if (screen->bg_inited && export_check_bg_task(screen)) {
// background task done
done_status = EXPORT_CONFIRM_CONTINUE;
goto confirm_done;
}
if (state->back_down || (state->sel_down && !screen->confirm_state)) { if (state->back_down || (state->sel_down && !screen->confirm_state)) {
done_status = EXPORT_CONFIRM_NO; done_status = EXPORT_CONFIRM_NO;
screen->need_redraw = true;
goto confirm_done; goto confirm_done;
} }
if (state->sel_down) { if (state->sel_down) {
@ -188,9 +184,10 @@ static enum {
} }
return EXPORT_CONFIRM_CONTINUE; return EXPORT_CONFIRM_CONTINUE;
confirm_done: confirm_done:
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON); LCD_DISPLAY_ON);
screen->confirm_state = false; screen->confirm_state = false;
screen->need_redraw = true;
return done_status; return done_status;
} }
@ -203,6 +200,8 @@ static bool export_show_message(ExportScreen *screen,
return true; return true;
} }
if (screen->need_redraw) { if (screen->need_redraw) {
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
lcd_clear(state->lcd); lcd_clear(state->lcd);
lcd_move_to(state->lcd, 0, 0); lcd_move_to(state->lcd, 0, 0);
lcd_write_string(state->lcd, message); lcd_write_string(state->lcd, message);
@ -211,106 +210,114 @@ static bool export_show_message(ExportScreen *screen,
return false; return false;
} }
static intptr_t export_newfs_action(ExportScreen *screen) {
if (drive_newfs_msdos(&screen->drives[screen->cur_drive])) {
return EXPORT_SCREEN_MOUNTING;
} else {
return EXPORT_SCREEN_FAILED;
}
}
static intptr_t export_mount_action(ExportScreen *screen) {
Drive *drive = &screen->drives[screen->cur_drive];
if (drive_mount(drive)) {
struct stat statbuf;
char *path = NULL;
int exist;
pthread_cleanup_push(free, path);
path = export_build_cur_path(screen);
exist = stat(path, &statbuf);
screen->need_overwrite = exist == 0;
pthread_cleanup_pop(true); // free(path)
return screen->need_overwrite ? EXPORT_SCREEN_CONFIRM_OVERWRITE :
EXPORT_SCREEN_WRITING;
} else {
return EXPORT_SCREEN_FAILED;
}
}
static intptr_t export_write_action(ExportScreen *screen) {
char *path = NULL;
intptr_t next_target = EXPORT_SCREEN_DONE;
pthread_cleanup_push(free, path);
path = export_build_cur_path(screen);
if (screen->need_overwrite && !recursive_rm(path)) {
next_target = EXPORT_SCREEN_FAILED;
goto cleanup;
}
LOG_VERBOSE("Exporting to \"%s\"\n", path);
switch (screen->format) {
case EXPORT_FORMAT_SQLITE:
if (!export_database(screen->db_cache, path)) {
warnx("export to sqlite3 db failed: \"%s\"", path);
next_target = EXPORT_SCREEN_FAILED;
}
break;
case EXPORT_FORMAT_CSV:
if (!export_database_as_csv(screen->db_cache, path)) {
warnx("export to CSV failed: \"%s\"", path);
next_target = EXPORT_SCREEN_FAILED;
}
break;
default:
warnx("unknown export format");
next_target = EXPORT_SCREEN_FAILED;
break;
}
LOG_VERBOSE("Export done\n");
cleanup:
pthread_cleanup_pop(true); // free(path)
drive_unmount(&screen->drives[screen->cur_drive], true);
return next_target;
}
static const char BACKGROUND_CHARS[] = { static const char BACKGROUND_CHARS[] = {
'|', '|',
'-' '-'
}; };
#define NBACKGROUND_CHAR sizeof(BACKGROUND_CHARS) #define NBACKGROUND_CHAR sizeof(BACKGROUND_CHARS)
static void export_do_background_action(ExportScreen *screen, /*
SensorState *state, * True if done, false otherwise
intptr_t(*action)(ExportScreen *screen)) { */
if (!screen->bg_inited) { static bool export_do_background_action(ExportScreen *screen, SensorState *state) {
if (pthread_create(&screen->backgroud_oper, NULL, if (!screen->show_cancel_screen && (state->down_down || state->up_down ||
(void*(*)(void *)) action, screen)) { state->back_down || state->sel_down)) {
warnx("could not start background export operation"); screen->show_cancel_screen = true;
screen->stage = EXPORT_SCREEN_FAILED;
}
screen->bg_inited = true;
screen->working_char = 0;
}
if (state->down_down || state->up_down || state->back_down ||
state->sel_down) {
screen->last_cancel_stage = screen->stage;
screen->stage = EXPORT_SCREEN_CONFIRM_CANCEL;
screen->need_redraw = true; screen->need_redraw = true;
return; // we need to make sure that the confirm action screen ignores actions
// for this iteration of the UI loop
state->back_down = state->up_down =
state->sel_down = state->down_down = false;
}
if (!screen->current_op) {
screen->stage = EXPORT_SCREEN_FAILED;
screen->need_redraw = true;
return false;
} }
time_t now = time(NULL);
if (export_check_bg_task(screen)) { if (export_check_bg_task(screen)) {
// bg task done return true;
}
if (screen->show_cancel_screen) {
int confirm_status = export_confirm_action(screen, state, "Really cancel?");
if (confirm_status == EXPORT_CONFIRM_YES) {
async_operation_free(screen->current_op);
screen->current_op = NULL;
AsyncOperation *op = drive_unmount(&screen->drives[screen->cur_drive], true);
async_operation_poll(op, true);
async_operation_free(op);
screen->stage = EXPORT_SCREEN_CANCELED;
screen->need_redraw = true;
} else if (confirm_status == EXPORT_CONFIRM_NO) {
screen->need_redraw = true;
screen->show_cancel_screen = false;
}
} else {
time_t now = time(NULL);
if (screen->need_redraw || now != screen->last_char_change) {
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
screen->last_char_change = now;
screen->need_redraw = false;
lcd_clear(state->lcd);
lcd_move_to(state->lcd, 0, 0);
lcd_write_string(state->lcd, "Working");
lcd_write_char(state->lcd, BACKGROUND_CHARS[screen->working_char++ % NBACKGROUND_CHAR]);
}
}
return false;
}
static void export_async_write_action(ExportScreen *screen) {
signal(SIGINT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
char *path = export_build_cur_path(screen);
if (screen->need_overwrite && !recursive_rm(path)) {
exit(1);
}
LOG_VERBOSE("Exporting to \"%s\"\n", path);
switch (screen->format) {
case EXPORT_FORMAT_SQLITE:
if (!export_database(screen->db_cache, path)) {
errx(1, "export to sqlite3 db failed: \"%s\"", path);
}
break;
case EXPORT_FORMAT_CSV:
if (!export_database_as_csv(screen->db_cache, path)) {
errx(1, "export to CSV failed: \"%s\"", path);
}
break;
default:
errx(1, "unknown export format");
break;
}
free(path);
LOG_VERBOSE("Export done\n");
exit(0);
}
static void export_handle_write(ExportScreen *screen, SensorState *state) {
if (screen->current_op) {
if (export_do_background_action(screen, state)) {
screen->need_redraw = true;
screen->stage = EXPORT_SCREEN_UNMOUNT;
}
return; return;
} }
if (screen->need_redraw || now != screen->last_char_change) { // if we have no current op, start a new one
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, screen->show_cancel_screen = false;
LCD_DISPLAY_ON); pid_t pid = fork();
screen->last_char_change = now; if (pid < 0) {
screen->need_redraw = false; warn("failed to start write background task");
lcd_clear(state->lcd); screen->need_redraw = true;
lcd_move_to(state->lcd, 0, 0); screen->stage = EXPORT_SCREEN_FAILED;
lcd_write_string(state->lcd, "Working"); return;
lcd_write_char(state->lcd, BACKGROUND_CHARS[screen->working_char++ % NBACKGROUND_CHAR]); } else if (pid == 0) {
// child
export_async_write_action(screen);
abort();
} else {
// parent
screen->current_op = async_operation_take(NULL, NULL, NULL, NULL, pid);
} }
} }
@ -349,10 +356,31 @@ static bool export_screen_dispatch(ExportScreen *screen,
} }
break; break;
case EXPORT_SCREEN_WRITE_NEWFS: case EXPORT_SCREEN_WRITE_NEWFS:
export_do_background_action(screen, state, export_newfs_action); if (!screen->current_op) {
screen->current_op = drive_newfs_msdos(cur_drive);
screen->show_cancel_screen = false;
}
if (export_do_background_action(screen, state)) {
screen->stage = EXPORT_SCREEN_MOUNTING;
screen->need_redraw = true;
}
break; break;
case EXPORT_SCREEN_MOUNTING: case EXPORT_SCREEN_MOUNTING:
export_do_background_action(screen, state, export_mount_action); if (!screen->current_op) {
screen->current_op = drive_mount(cur_drive);
screen->show_cancel_screen = false;
}
if (export_do_background_action(screen, state)) {
char *path = export_build_cur_path(screen);
struct stat statbuf;
if (stat(path, &statbuf) == 0) {
screen->stage = EXPORT_SCREEN_CONFIRM_OVERWRITE;
} else {
screen->stage = EXPORT_SCREEN_WRITING;
}
screen->need_redraw = true;
free(path);
}
break; break;
case EXPORT_SCREEN_CONFIRM_OVERWRITE: case EXPORT_SCREEN_CONFIRM_OVERWRITE:
confirm_status = export_confirm_action(screen, state, "Overwrite?"); confirm_status = export_confirm_action(screen, state, "Overwrite?");
@ -364,24 +392,21 @@ static bool export_screen_dispatch(ExportScreen *screen,
screen->need_redraw = true; screen->need_redraw = true;
// on the main thread so force unmount // on the main thread so force unmount
// also, nothing should be writing, so kill it if it is // also, nothing should be writing, so kill it if it is
drive_unmount(cur_drive, true); AsyncOperation *op = drive_unmount(cur_drive, true);
async_operation_poll(op, true);
async_operation_free(op);
} }
break; break;
case EXPORT_SCREEN_WRITING: case EXPORT_SCREEN_WRITING:
export_do_background_action(screen, state, export_write_action); export_handle_write(screen, state);
break; break;
case EXPORT_SCREEN_CONFIRM_CANCEL: case EXPORT_SCREEN_UNMOUNT:
confirm_status = export_confirm_action(screen, state, "Really cancel?"); if (!screen->current_op) {
if (confirm_status == EXPORT_CONFIRM_YES) { screen->current_op = drive_unmount(cur_drive, false);
kill(0, SIGUSR1); screen->show_cancel_screen = false;
pthread_cancel(screen->backgroud_oper); }
pthread_detach(screen->backgroud_oper); if (export_do_background_action(screen, state)) {
drive_unmount(cur_drive, true); screen->stage = EXPORT_SCREEN_DONE;
screen->bg_inited = false;
screen->stage = EXPORT_SCREEN_CANCELED;
screen->need_redraw = true;
} else if (confirm_status == EXPORT_CONFIRM_NO) {
screen->stage = screen->last_cancel_stage;
screen->need_redraw = true; screen->need_redraw = true;
} }
break; break;
@ -398,10 +423,7 @@ static bool export_screen_dispatch(ExportScreen *screen,
} }
static void export_screen_cleanup(ExportScreen *screen) { static void export_screen_cleanup(ExportScreen *screen) {
if (screen->bg_inited) { async_operation_free(screen->current_op);
pthread_cancel(screen->backgroud_oper);
pthread_detach(screen->backgroud_oper);
}
} }
ExportScreen *export_screen_new() { ExportScreen *export_screen_new() {
@ -412,5 +434,6 @@ ExportScreen *export_screen_new() {
s->read_drives = true; s->read_drives = true;
s->stage = EXPORT_SCREEN_FORMAT; s->stage = EXPORT_SCREEN_FORMAT;
s->format = EXPORT_FORMAT_SQLITE; s->format = EXPORT_FORMAT_SQLITE;
s->current_op = NULL;
return s; return s;
} }

View File

@ -11,11 +11,11 @@
#define INCLUDED_EXPORTSCREEN_H #define INCLUDED_EXPORTSCREEN_H
#include "screen.h" #include "screen.h"
#include "../async.h"
#include "../drive.h" #include "../drive.h"
#include <pthread.h> #include <pthread.h>
#include <time.h> #include <time.h>
#include <stdatomic.h>
#include <sqlite3.h> #include <sqlite3.h>
typedef struct { typedef struct {
@ -28,8 +28,8 @@ typedef struct {
EXPORT_SCREEN_MOUNTING, EXPORT_SCREEN_MOUNTING,
EXPORT_SCREEN_CONFIRM_OVERWRITE, EXPORT_SCREEN_CONFIRM_OVERWRITE,
EXPORT_SCREEN_WRITING, EXPORT_SCREEN_WRITING,
EXPORT_SCREEN_UNMOUNT,
EXPORT_SCREEN_DONE, EXPORT_SCREEN_DONE,
EXPORT_SCREEN_CONFIRM_CANCEL,
EXPORT_SCREEN_CANCELED, EXPORT_SCREEN_CANCELED,
EXPORT_SCREEN_FAILED, EXPORT_SCREEN_FAILED,
EXPORT_SCREEN_NO_DRIVES, EXPORT_SCREEN_NO_DRIVES,
@ -47,13 +47,13 @@ typedef struct {
size_t ndrive; size_t ndrive;
size_t cur_drive; size_t cur_drive;
int confirm_state; int confirm_state;
bool bg_inited;
pthread_t backgroud_oper;
int working_char; int working_char;
time_t last_char_change; time_t last_char_change;
bool need_overwrite; bool need_overwrite;
sqlite3 *db_cache; sqlite3 *db_cache;
int last_cancel_stage; int last_cancel_stage;
AsyncOperation *current_op;
bool show_cancel_screen;
} ExportScreen; } ExportScreen;
ExportScreen *export_screen_new(void); ExportScreen *export_screen_new(void);

View File

@ -24,19 +24,25 @@ static bool power_screen_dispatch(PowerScreen *screen,
if (state->sel_down) { if (state->sel_down) {
switch (screen->choice) { switch (screen->choice) {
case POWER_SCREEN_RESTART_PROGRAM: case POWER_SCREEN_RESTART_PROGRAM:
request_restart(); request_exit(true);
break; break;
case POWER_SCREEN_REBOOT: case POWER_SCREEN_REBOOT:
LOG_VERBOSE("Rebooting..."); LOG_VERBOSE("Rebooting...");
reboot(RB_AUTOBOOT); if (system("reboot") != 0) {
abort(); warnx("reboot(8) failed");
// how did we even get here??? // just force it if doing it cleanly failed
reboot(RB_AUTOBOOT);
}
request_exit(false);
break; break;
case POWER_SCREEN_POWEROFF: case POWER_SCREEN_POWEROFF:
LOG_VERBOSE("Powering off..."); LOG_VERBOSE("Powering off...");
reboot(RB_POWEROFF); if (system("poweroff") != 0) {
abort(); warnx("poweroff(8) failed");
// how did we even get here??? // just force it if doing it cleanly failed
reboot(RB_POWEROFF);
}
request_exit(false);
break; break;
default: default:
warnx("invalid power screen choice"); warnx("invalid power screen choice");
@ -44,9 +50,13 @@ static bool power_screen_dispatch(PowerScreen *screen,
LCD_DISPLAY_ON); LCD_DISPLAY_ON);
return true; return true;
} }
return false;
}
screen->choice = (screen->choice + state->up_down - state->down_down) %
POWER_SCREEN_NCHOICE;
if (screen->choice < 0) {
screen->choice = POWER_SCREEN_NCHOICE + screen->choice;
} }
screen->choice = abs((screen->choice + state->up_down - state->down_down) %
POWER_SCREEN_NCHOICE);
if (state->force_draw || state->up_down || state->down_down) { if (state->force_draw || state->up_down || state->down_down) {
lcd_clear(state->lcd); lcd_clear(state->lcd);
lcd_move_to(state->lcd, 0, 0); lcd_move_to(state->lcd, 0, 0);

View File

@ -151,7 +151,9 @@ static bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) {
char buff[17]; char buff[17];
int cur_len; int cur_len;
if (state->temp == UINT32_MAX || state->humid == UINT32_MAX) { if (state->temp == UINT32_MAX || state->humid == UINT32_MAX) {
cur_len = snprintf(buff, sizeof(buff), "ERROR! "); cur_len = snprintf(buff, sizeof(buff), "ERROR! ");
} else if (state->temp == UINT_MAX - 1 || state->humid == UINT32_MAX - 1) {
cur_len = snprintf(buff, sizeof(buff), "LOADING! ");
} else { } else {
cur_len = snprintf(buff, sizeof(buff), "%-4.1f%c %3" PRIu32 "%% ", cur_len = snprintf(buff, sizeof(buff), "%-4.1f%c %3" PRIu32 "%% ",
convert_temperature(state->temp), convert_temperature(state->temp),
@ -169,7 +171,7 @@ static bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) {
StatsScreen *stats_screen_new() { StatsScreen *stats_screen_new() {
StatsScreen *s = malloc_checked(sizeof(StatsScreen)); StatsScreen *s = malloc_checked(sizeof(StatsScreen));
screen_init(&s->parent, "Current Stats", screen_init(&s->parent, "Current stats",
(ScreenDispatchFunc) stats_screen_dispatch, (ScreenDispatchFunc) stats_screen_dispatch,
(ScreenCleanupFunc) free); (ScreenCleanupFunc) free);
s->last_humid = 0; s->last_humid = 0;

View File

@ -476,7 +476,7 @@ static bool set_tz_screen_finish(SetTZScreen *screen,
LOG_VERBOSE("Set timezone to: \"%s\"\n", buf); LOG_VERBOSE("Set timezone to: \"%s\"\n", buf);
} }
free(buf); free(buf);
request_restart(); request_exit(true);
} }
if (state->up_down || state->back_down || if (state->up_down || state->back_down ||
state->sel_down || state->down_down) { state->sel_down || state->down_down) {

View File

@ -189,6 +189,7 @@ static bool stats_by_screen_dispatch(StatsByScreen *screen,
} }
if (state->back_down) { if (state->back_down) {
if (screen->stage == STATS_BY_SELECT_PERIOD) { if (screen->stage == STATS_BY_SELECT_PERIOD) {
screen->period = PERIOD_HOUR;
screen->need_redraw = true; screen->need_redraw = true;
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON); LCD_DISPLAY_ON);
@ -215,7 +216,7 @@ static bool stats_by_screen_dispatch(StatsByScreen *screen,
StatsByScreen *stats_by_screen_new() { StatsByScreen *stats_by_screen_new() {
StatsByScreen *s = malloc_checked(sizeof(StatsByScreen)); StatsByScreen *s = malloc_checked(sizeof(StatsByScreen));
screen_init(&s->parent, "Stats by...", screen_init(&s->parent, "Stats by",
(ScreenDispatchFunc) stats_by_screen_dispatch, (ScreenDispatchFunc) stats_by_screen_dispatch,
(ScreenCleanupFunc) free); (ScreenCleanupFunc) free);
s->need_redraw = true; s->need_redraw = true;

View File

@ -171,6 +171,7 @@ TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state,
void time_sel_reset(TimeSelection *ts) { void time_sel_reset(TimeSelection *ts) {
ts->hour = 0; ts->hour = 0;
ts->minute = 0; ts->minute = 0;
ts->second = 0;
ts->stage = TS_STAGE_HOUR; ts->stage = TS_STAGE_HOUR;
} }

View File

@ -17,9 +17,8 @@
#include <dirent.h> #include <dirent.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <pthread.h>
#include <errno.h> #include <errno.h>
#include <stdatomic.h> #include <sys/signal.h>
const char *PERIOD_LABELS[] = { const char *PERIOD_LABELS[] = {
"HOUR", "HOUR",
@ -414,12 +413,6 @@ char *pad_humid_str(int humid, char *buf, size_t buf_size) {
return buf; return buf;
} }
static void close_dir_if_set(void *dir) {
if (dir) {
closedir(dir);
}
}
bool recursive_rm_at(int dfd, const char *path) { bool recursive_rm_at(int dfd, const char *path) {
if (dfd != AT_FDCWD) { if (dfd != AT_FDCWD) {
LOG_VERBOSE("Recursive delete: \"%s\"\n", path); LOG_VERBOSE("Recursive delete: \"%s\"\n", path);
@ -432,18 +425,12 @@ bool recursive_rm_at(int dfd, const char *path) {
int flag = 0; int flag = 0;
if (S_ISDIR(statbuf.st_mode)) { if (S_ISDIR(statbuf.st_mode)) {
flag = AT_REMOVEDIR; flag = AT_REMOVEDIR;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
int fd = openat(dfd, path, O_DIRECTORY | O_RDONLY); int fd = openat(dfd, path, O_DIRECTORY | O_RDONLY);
if (fd < 0) { if (fd < 0) {
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
warn("open \"%s\"", path); warn("open \"%s\"", path);
return false; return false;
} }
DIR *dir = NULL; DIR *dir = fdopendir(fd);
bool error = false;
pthread_cleanup_push(close_dir_if_set, dir);
dir = fdopendir(fd);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
if (!dir) { if (!dir) {
close(fd); close(fd);
warn("fdopendir \"%s\"", path); warn("fdopendir \"%s\"", path);
@ -453,14 +440,10 @@ bool recursive_rm_at(int dfd, const char *path) {
while ((sd = readdir(dir))) { while ((sd = readdir(dir))) {
if (strcmp(sd->d_name, ".") != 0 && strcmp(sd->d_name, "..") != 0 && if (strcmp(sd->d_name, ".") != 0 && strcmp(sd->d_name, "..") != 0 &&
!recursive_rm_at(dirfd(dir), sd->d_name)) { !recursive_rm_at(dirfd(dir), sd->d_name)) {
error = true; return false;
break;
} }
} }
pthread_cleanup_pop(true); closedir(dir);
if (error) {
return false;
}
} }
if (unlinkat(dfd, path, flag)< 0) { if (unlinkat(dfd, path, flag)< 0) {
warn("remove \"%s\"", path); warn("remove \"%s\"", path);
@ -474,48 +457,31 @@ bool recursive_rm(const char *path) {
} }
bool export_database(sqlite3 *db, const char *dest) { bool export_database(sqlite3 *db, const char *dest) {
bool status = true;
sqlite3 *new_db = NULL; sqlite3 *new_db = NULL;
sqlite3_backup *oper = NULL; sqlite3_backup *oper = NULL;
pthread_cleanup_push((void(*)(void*))sqlite3_close, new_db);
pthread_cleanup_push((void(*)(void*))sqlite3_backup_finish, oper);
int res = sqlite3_open(dest, &new_db); int res = sqlite3_open(dest, &new_db);
if (res != SQLITE_OK) { if (res != SQLITE_OK) {
warnx("could not open new db: \"%s\": %s", dest, warnx("could not open new db: \"%s\": %s", dest,
sqlite3_errstr(res)); sqlite3_errstr(res));
status = false; return false;
goto cleanup;
} }
oper = sqlite3_backup_init(new_db, "main", db, "main"); oper = sqlite3_backup_init(new_db, "main", db, "main");
if (!oper) { if (!oper) {
warnx("could not start backup: \"%s\": %s", dest, warnx("could not start backup: \"%s\": %s", dest,
sqlite3_errstr(res)); sqlite3_errstr(res));
status = false; return false;
goto cleanup;
} }
sqlite3_backup_step(oper, -1); sqlite3_backup_step(oper, -1);
cleanup: sqlite3_backup_finish(oper);
pthread_cleanup_pop(true); // cleanup oper sqlite3_close(new_db);
pthread_cleanup_pop(true); // cleanup new_db return true;
return status;
}
static void cleanup_file_obj(void *obj) {
if (obj) {
fclose(obj);
}
} }
bool export_database_as_csv(sqlite3 *db, const char *dest) { bool export_database_as_csv(sqlite3 *db, const char *dest) {
bool status = true; FILE *outfile = fopen(dest, "w");
FILE *outfile = NULL;
pthread_cleanup_push((void(*)(void *))sqlite3_reset, EXPORT_CSV_QUERY);
pthread_cleanup_push(cleanup_file_obj, outfile);
outfile = fopen(dest, "w");
if (!outfile) { if (!outfile) {
warn("fopen \"%s\"", dest); warn("fopen \"%s\"", dest);
status = false; return false;
goto cleanup;
} }
int res; int res;
while ((res = sqlite3_step(EXPORT_CSV_QUERY)) == SQLITE_ROW) { while ((res = sqlite3_step(EXPORT_CSV_QUERY)) == SQLITE_ROW) {
@ -526,14 +492,11 @@ bool export_database_as_csv(sqlite3 *db, const char *dest) {
if (res != SQLITE_DONE) { if (res != SQLITE_DONE) {
warnx("CSV export to \"%s\" faield: %s\n", dest, warnx("CSV export to \"%s\" faield: %s\n", dest,
sqlite3_errstr(res)); sqlite3_errstr(res));
status = false; return false;
goto cleanup; // readability
} }
fflush(outfile); sqlite3_reset(EXPORT_CSV_QUERY);
cleanup: fclose(outfile);
pthread_cleanup_pop(true); // reset query return true;
pthread_cleanup_pop(true); // close file
return status;
} }
static bool ensure_dir_exists(const char *path, mode_t mode) { static bool ensure_dir_exists(const char *path, mode_t mode) {
@ -556,7 +519,7 @@ static bool ensure_dir_exists(const char *path, mode_t mode) {
} }
bool mkdirs(const char *path, mode_t mode) { bool mkdirs(const char *path, mode_t mode) {
LOG_VERBOSE("Creating directory: \"%s\"\n", path) LOG_VERBOSE("Creating directory: \"%s\"\n", path);
char *copy = strdup_checked(path); char *copy = strdup_checked(path);
char *work_str = copy, *token; char *work_str = copy, *token;
while ((token = strsep(&work_str, "/"))) { while ((token = strsep(&work_str, "/"))) {
@ -572,7 +535,36 @@ bool mkdirs(const char *path, mode_t mode) {
return true; return true;
} }
void request_restart() { void request_exit(bool restart) {
RUNNING = false; RUNNING = false;
SHOULD_RESTART = true; SHOULD_RESTART = restart;
}
bool timed_wait(pid_t pid, int *out_status, useconds_t micro) {
struct timespec waittime = {
.tv_sec = micro / 1000000,
.tv_nsec = micro % 1000000,
};
int status;
struct timespec start, now, tmp;
clock_gettime(CLOCK_UPTIME, &start);
while (true) {
if (waitpid(pid, &status, WNOHANG) < 0) {
return false;
}
if (WIFEXITED(status) || WIFSIGNALED(status)) {
if (out_status) {
*out_status = status;
}
return true;
}
clock_gettime(CLOCK_UPTIME, &now);
timespecsub(&now, &start, &tmp);
if (timespeccmp(&tmp, &waittime, >=)) {
if (out_status) {
*out_status = 0;
}
return true;
}
}
} }

View File

@ -18,6 +18,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <libgpio.h> #include <libgpio.h>
#include <sqlite3.h> #include <sqlite3.h>
#include <sys/wait.h>
#define _STRINGIFY_LIT(s) (#s) #define _STRINGIFY_LIT(s) (#s)
#define STRINGIFY(v) _STRINGIFY_LIT(v) #define STRINGIFY(v) _STRINGIFY_LIT(v)
@ -250,9 +251,17 @@ bool export_database_as_csv(sqlite3 *db, const char *dest);
bool mkdirs(const char *path, mode_t mode); bool mkdirs(const char *path, mode_t mode);
/* /*
* Request that this program re-invoke itself with the same arguments. Used to * Request that this program exit. If RESTART request instead that it re-invoke
* re-initialize configuration values. * itself with the same arguments.
*/ */
void request_restart(void); void request_exit(bool restart);
/*
* waitpid(2) for PID for MICRO microseconds. If STATUS is not NULL, store the
* exit status there. Use the various WIF* macros to test the status. This
* function sets errno to the errno value of waitpid(2) on error.
* Return: true if successful, false otherwise and the error is in errno
*/
bool timed_wait(pid_t pid, int *out_status, useconds_t micro);
#endif #endif