Compare commits
16 Commits
8c638f00a5
...
main
Author | SHA1 | Date | |
---|---|---|---|
69f24c63bd
|
|||
54c23fc67b
|
|||
e579ea1dfc
|
|||
86740e1d6b
|
|||
ab88ae4fac
|
|||
da9a2acf4e
|
|||
8f4fd1958d
|
|||
1c2a99a865
|
|||
9b89fc5c94
|
|||
19156062c2
|
|||
62c79197a6
|
|||
6f9cf0df25
|
|||
8fac0dfcfd
|
|||
6bb004f588
|
|||
940c5b3f99
|
|||
56a5060fa1
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@ bin/
|
||||
compile_commands.json
|
||||
.cache/
|
||||
config.conf
|
||||
.clangd
|
||||
|
6
Makefile
6
Makefile
@ -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/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/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
|
||||
|
||||
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/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/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/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/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/async.h
|
||||
|
||||
.if "${DEFAULT_TEMP_UNIT}" != "F" && "${DEFAULT_TEMP_UNIT}" != "C" &&\
|
||||
"${DEFAULT_TEMP_UNIT}" != "c" && "${DEFAULT_TEMP_UNIT}" != "c"
|
||||
|
67
README.md
67
README.md
@ -193,14 +193,6 @@ armstub=armstub8-gic.bin
|
||||
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:
|
||||
|
||||
```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
|
||||
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
|
||||
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
|
||||
```
|
||||
|
||||
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
|
||||
default options. These can also be changed after installation by editing
|
||||
`/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
|
||||
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:
|
||||
- "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
111
src/async.c
Normal 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
75
src/async.h
Normal 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
|
198
src/drive.c
198
src/drive.c
@ -19,7 +19,6 @@
|
||||
#include <dirent.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/mount.h>
|
||||
#include <pthread.h>
|
||||
|
||||
static int compare_drives(const Drive *d1, const Drive *d2) {
|
||||
return strcmp(d1->name, d2->name);
|
||||
@ -62,29 +61,16 @@ void drive_free_drives(Drive *drives, size_t count) {
|
||||
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 *result_fs = NULL;
|
||||
int ctop[2] = {0, 0};
|
||||
pthread_cleanup_push(close_pipe, ctop);
|
||||
pthread_cleanup_push(free, result_fs);
|
||||
if (pipe(ctop) < 0) {
|
||||
warn("failed to create pipe");
|
||||
goto cleanup;
|
||||
return NULL;
|
||||
}
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
warn("fork(2) failed");
|
||||
goto cleanup;
|
||||
return NULL;
|
||||
} else if (pid == 0) {
|
||||
// child
|
||||
close(ctop[0]); // close read end
|
||||
@ -104,125 +90,119 @@ char *drive_guess_fs(const char *dev) {
|
||||
int status;
|
||||
if (waitpid(pid, &status, 0) < 0) {
|
||||
warn("waiting for child failed");
|
||||
goto cleanup;
|
||||
return NULL;
|
||||
}
|
||||
if (WEXITSTATUS(status) != 0) {
|
||||
// no error, we probably just don't know the FS type
|
||||
goto cleanup;
|
||||
return NULL;
|
||||
}
|
||||
char read_buf[32];
|
||||
ssize_t read_count = read(ctop[0], read_buf, 32);
|
||||
if (read_count < 0) {
|
||||
warn("reading from pipe failed");
|
||||
goto cleanup;
|
||||
return NULL;
|
||||
}
|
||||
read_buf[read_count - 1] = '\0';
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
||||
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;
|
||||
return strdup_checked(read_buf);
|
||||
}
|
||||
|
||||
bool drive_newfs_msdos(Drive *drive) {
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
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);
|
||||
static bool newfs_operation_poll(AsyncOperation *op) {
|
||||
if (op->done && !async_operation_failed(op)) {
|
||||
Drive *drive = op->data1;
|
||||
free(drive->fs);
|
||||
drive->fs = strdup_checked("msdosfs");
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
return true;
|
||||
}
|
||||
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);
|
||||
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");
|
||||
status = false;
|
||||
goto cleanup;
|
||||
return NULL;
|
||||
}
|
||||
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);
|
||||
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;
|
||||
}
|
||||
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;
|
||||
return op;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
bool unmount_operation_poll(AsyncOperation *op) {
|
||||
if (op->done) {
|
||||
Drive *drive = op->data1;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
23
src/drive.h
23
src/drive.h
@ -10,8 +10,10 @@
|
||||
#ifndef INCLUDED_DRIVE_H
|
||||
#define INCLUDED_DRIVE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include "async.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
@ -23,7 +25,8 @@ typedef struct {
|
||||
/*
|
||||
* Attempt to list all connected USB drives and their file systems. The list is
|
||||
* 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);
|
||||
|
||||
@ -40,21 +43,21 @@ void drive_free_drives(Drive *drives, size_t count);
|
||||
char *drive_guess_fs(const char *dev);
|
||||
|
||||
/*
|
||||
* Create an MSDOS (FAT32) FS on DRIVE. DRIVE MUST NOT BE MOUNTED!
|
||||
* Return: true on success, false otherwise
|
||||
* Create an MSDOS (FAT32) FS on DRIVE.
|
||||
* 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!
|
||||
* Return: true on success, false otherwise
|
||||
* Mount DRIVE onto a new temp. dir in /tmp.
|
||||
* 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.
|
||||
* 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
|
||||
|
13
src/main.c
13
src/main.c
@ -10,8 +10,6 @@
|
||||
#include "lcd.h"
|
||||
#include "util.h"
|
||||
#include "ths.h"
|
||||
#include "button.h"
|
||||
#include "menu.h"
|
||||
#include "config.h"
|
||||
#include "ui/screen.h"
|
||||
#include "ui/statsby.h"
|
||||
@ -27,17 +25,13 @@
|
||||
|
||||
#include <unistd.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <figpar.h>
|
||||
#include <inttypes.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
#include <signal.h>
|
||||
#include <sqlite3.h>
|
||||
#include <libgen.h>
|
||||
@ -75,7 +69,8 @@ void start_update_thread(pthread_t *thread);
|
||||
// cross thread variables
|
||||
sqlite3 *DATABASE;
|
||||
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;
|
||||
/*
|
||||
* Lock the cross thread variables above.
|
||||
@ -98,7 +93,8 @@ int main(int argc, char *const *argv) {
|
||||
setenv("TZ", GLOBAL_OPTS.timezone, true);
|
||||
NEED_CLEAR_TZ = true;
|
||||
} else {
|
||||
LOG_VERBOSE("Config timezone option shadowed by local environment variable\n");
|
||||
LOG_VERBOSE("Config timezone option shadowed by local environment "
|
||||
"variable.\n");
|
||||
}
|
||||
}
|
||||
tzset();
|
||||
@ -224,7 +220,6 @@ err(1, "failed to setup signal %s", #sig);\
|
||||
void setup_signals() {
|
||||
SIGNAL_SETUP_CHECKED(SIGTERM, 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 =
|
||||
|
@ -27,7 +27,7 @@ static bool blank_screen_dispatch(Screen *screen,
|
||||
|
||||
Screen *blank_screen_new() {
|
||||
Screen *s = malloc_checked(sizeof(Screen));
|
||||
screen_init(s, "Blank Display",
|
||||
screen_init(s, "Blank display",
|
||||
(ScreenDispatchFunc) blank_screen_dispatch,
|
||||
(ScreenCleanupFunc) free);
|
||||
return s;
|
||||
|
@ -155,7 +155,7 @@ static bool data_points_screen_dispatch(DataPointsScreen *screen, SensorState *s
|
||||
|
||||
DataPointsScreen *data_points_screen_new() {
|
||||
DataPointsScreen *s = malloc_checked(sizeof(DataPointsScreen));
|
||||
screen_init(&s->parent, "Data Points",
|
||||
screen_init(&s->parent, "Data points",
|
||||
(ScreenDispatchFunc) data_points_screen_dispatch,
|
||||
(ScreenCleanupFunc) free);
|
||||
s->need_redraw = true;
|
||||
|
@ -10,14 +10,11 @@
|
||||
#include "exportscreen.h"
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <pthread.h>
|
||||
#include <pthread_np.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <signal.h>
|
||||
|
||||
static void export_screen_reset(ExportScreen *screen,
|
||||
SensorState *state) {
|
||||
@ -36,19 +33,24 @@ static void export_screen_reset(ExportScreen *screen,
|
||||
* Retrun: true if task is done, false otherwise
|
||||
*/
|
||||
static bool export_check_bg_task(ExportScreen *screen) {
|
||||
void *status;
|
||||
if (pthread_peekjoin_np(screen->backgroud_oper, &status) != EBUSY) {
|
||||
// done
|
||||
screen->bg_inited = false;
|
||||
if ((intptr_t) status == EXPORT_SCREEN_FAILED) {
|
||||
// error happened
|
||||
warnx("background export operation failed");
|
||||
switch (async_operation_poll(screen->current_op, false)) {
|
||||
case ASYNC_OPERATION_DONE:
|
||||
if (async_operation_failed(screen->current_op)) {
|
||||
goto failed;
|
||||
}
|
||||
screen->stage = (intptr_t) status;
|
||||
screen->need_redraw = true;
|
||||
screen->current_op = NULL;
|
||||
return true;
|
||||
}
|
||||
case ASYNC_OPERATION_FAILED:
|
||||
goto failed;
|
||||
case ASYNC_OPERATION_WORKING:
|
||||
return false;
|
||||
}
|
||||
failed:
|
||||
screen->stage = EXPORT_SCREEN_FAILED;
|
||||
screen->need_redraw = true;
|
||||
screen->current_op = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *FORMAT_STRINGS[] = {
|
||||
@ -150,15 +152,9 @@ static enum {
|
||||
} export_confirm_action(ExportScreen *screen,
|
||||
SensorState *state,
|
||||
const char *fmt, ...) {
|
||||
int done_status = EXPORT_CONFIRM_CONTINUE;
|
||||
if (screen->bg_inited && export_check_bg_task(screen)) {
|
||||
// background task done
|
||||
done_status = EXPORT_CONFIRM_CONTINUE;
|
||||
goto confirm_done;
|
||||
}
|
||||
int done_status;
|
||||
if (state->back_down || (state->sel_down && !screen->confirm_state)) {
|
||||
done_status = EXPORT_CONFIRM_NO;
|
||||
screen->need_redraw = true;
|
||||
goto confirm_done;
|
||||
}
|
||||
if (state->sel_down) {
|
||||
@ -191,6 +187,7 @@ static enum {
|
||||
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
|
||||
LCD_DISPLAY_ON);
|
||||
screen->confirm_state = false;
|
||||
screen->need_redraw = true;
|
||||
return done_status;
|
||||
}
|
||||
|
||||
@ -203,6 +200,8 @@ static bool export_show_message(ExportScreen *screen,
|
||||
return true;
|
||||
}
|
||||
if (screen->need_redraw) {
|
||||
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
|
||||
LCD_DISPLAY_ON);
|
||||
lcd_clear(state->lcd);
|
||||
lcd_move_to(state->lcd, 0, 0);
|
||||
lcd_write_string(state->lcd, message);
|
||||
@ -211,97 +210,49 @@ static bool export_show_message(ExportScreen *screen,
|
||||
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[] = {
|
||||
'|',
|
||||
'-'
|
||||
};
|
||||
#define NBACKGROUND_CHAR sizeof(BACKGROUND_CHARS)
|
||||
|
||||
static void export_do_background_action(ExportScreen *screen,
|
||||
SensorState *state,
|
||||
intptr_t(*action)(ExportScreen *screen)) {
|
||||
if (!screen->bg_inited) {
|
||||
if (pthread_create(&screen->backgroud_oper, NULL,
|
||||
(void*(*)(void *)) action, screen)) {
|
||||
warnx("could not start background export operation");
|
||||
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;
|
||||
/*
|
||||
* True if done, false otherwise
|
||||
*/
|
||||
static bool export_do_background_action(ExportScreen *screen, SensorState *state) {
|
||||
if (!screen->show_cancel_screen && (state->down_down || state->up_down ||
|
||||
state->back_down || state->sel_down)) {
|
||||
screen->show_cancel_screen = 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)) {
|
||||
// bg task done
|
||||
return;
|
||||
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);
|
||||
@ -312,6 +263,62 @@ static void export_do_background_action(ExportScreen *screen,
|
||||
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;
|
||||
}
|
||||
// if we have no current op, start a new one
|
||||
screen->show_cancel_screen = false;
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
warn("failed to start write background task");
|
||||
screen->need_redraw = true;
|
||||
screen->stage = EXPORT_SCREEN_FAILED;
|
||||
return;
|
||||
} else if (pid == 0) {
|
||||
// child
|
||||
export_async_write_action(screen);
|
||||
abort();
|
||||
} else {
|
||||
// parent
|
||||
screen->current_op = async_operation_take(NULL, NULL, NULL, NULL, pid);
|
||||
}
|
||||
}
|
||||
|
||||
static bool export_screen_dispatch(ExportScreen *screen,
|
||||
@ -349,10 +356,31 @@ static bool export_screen_dispatch(ExportScreen *screen,
|
||||
}
|
||||
break;
|
||||
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;
|
||||
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;
|
||||
case EXPORT_SCREEN_CONFIRM_OVERWRITE:
|
||||
confirm_status = export_confirm_action(screen, state, "Overwrite?");
|
||||
@ -364,24 +392,21 @@ static bool export_screen_dispatch(ExportScreen *screen,
|
||||
screen->need_redraw = true;
|
||||
// on the main thread so force unmount
|
||||
// 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;
|
||||
case EXPORT_SCREEN_WRITING:
|
||||
export_do_background_action(screen, state, export_write_action);
|
||||
export_handle_write(screen, state);
|
||||
break;
|
||||
case EXPORT_SCREEN_CONFIRM_CANCEL:
|
||||
confirm_status = export_confirm_action(screen, state, "Really cancel?");
|
||||
if (confirm_status == EXPORT_CONFIRM_YES) {
|
||||
kill(0, SIGUSR1);
|
||||
pthread_cancel(screen->backgroud_oper);
|
||||
pthread_detach(screen->backgroud_oper);
|
||||
drive_unmount(cur_drive, true);
|
||||
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;
|
||||
case EXPORT_SCREEN_UNMOUNT:
|
||||
if (!screen->current_op) {
|
||||
screen->current_op = drive_unmount(cur_drive, false);
|
||||
screen->show_cancel_screen = false;
|
||||
}
|
||||
if (export_do_background_action(screen, state)) {
|
||||
screen->stage = EXPORT_SCREEN_DONE;
|
||||
screen->need_redraw = true;
|
||||
}
|
||||
break;
|
||||
@ -398,10 +423,7 @@ static bool export_screen_dispatch(ExportScreen *screen,
|
||||
}
|
||||
|
||||
static void export_screen_cleanup(ExportScreen *screen) {
|
||||
if (screen->bg_inited) {
|
||||
pthread_cancel(screen->backgroud_oper);
|
||||
pthread_detach(screen->backgroud_oper);
|
||||
}
|
||||
async_operation_free(screen->current_op);
|
||||
}
|
||||
|
||||
ExportScreen *export_screen_new() {
|
||||
@ -412,5 +434,6 @@ ExportScreen *export_screen_new() {
|
||||
s->read_drives = true;
|
||||
s->stage = EXPORT_SCREEN_FORMAT;
|
||||
s->format = EXPORT_FORMAT_SQLITE;
|
||||
s->current_op = NULL;
|
||||
return s;
|
||||
}
|
||||
|
@ -11,11 +11,11 @@
|
||||
#define INCLUDED_EXPORTSCREEN_H
|
||||
|
||||
#include "screen.h"
|
||||
#include "../async.h"
|
||||
#include "../drive.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
#include <stdatomic.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
typedef struct {
|
||||
@ -28,8 +28,8 @@ typedef struct {
|
||||
EXPORT_SCREEN_MOUNTING,
|
||||
EXPORT_SCREEN_CONFIRM_OVERWRITE,
|
||||
EXPORT_SCREEN_WRITING,
|
||||
EXPORT_SCREEN_UNMOUNT,
|
||||
EXPORT_SCREEN_DONE,
|
||||
EXPORT_SCREEN_CONFIRM_CANCEL,
|
||||
EXPORT_SCREEN_CANCELED,
|
||||
EXPORT_SCREEN_FAILED,
|
||||
EXPORT_SCREEN_NO_DRIVES,
|
||||
@ -47,13 +47,13 @@ typedef struct {
|
||||
size_t ndrive;
|
||||
size_t cur_drive;
|
||||
int confirm_state;
|
||||
bool bg_inited;
|
||||
pthread_t backgroud_oper;
|
||||
int working_char;
|
||||
time_t last_char_change;
|
||||
bool need_overwrite;
|
||||
sqlite3 *db_cache;
|
||||
int last_cancel_stage;
|
||||
AsyncOperation *current_op;
|
||||
bool show_cancel_screen;
|
||||
} ExportScreen;
|
||||
|
||||
ExportScreen *export_screen_new(void);
|
||||
|
@ -24,19 +24,25 @@ static bool power_screen_dispatch(PowerScreen *screen,
|
||||
if (state->sel_down) {
|
||||
switch (screen->choice) {
|
||||
case POWER_SCREEN_RESTART_PROGRAM:
|
||||
request_restart();
|
||||
request_exit(true);
|
||||
break;
|
||||
case POWER_SCREEN_REBOOT:
|
||||
LOG_VERBOSE("Rebooting...");
|
||||
if (system("reboot") != 0) {
|
||||
warnx("reboot(8) failed");
|
||||
// just force it if doing it cleanly failed
|
||||
reboot(RB_AUTOBOOT);
|
||||
abort();
|
||||
// how did we even get here???
|
||||
}
|
||||
request_exit(false);
|
||||
break;
|
||||
case POWER_SCREEN_POWEROFF:
|
||||
LOG_VERBOSE("Powering off...");
|
||||
if (system("poweroff") != 0) {
|
||||
warnx("poweroff(8) failed");
|
||||
// just force it if doing it cleanly failed
|
||||
reboot(RB_POWEROFF);
|
||||
abort();
|
||||
// how did we even get here???
|
||||
}
|
||||
request_exit(false);
|
||||
break;
|
||||
default:
|
||||
warnx("invalid power screen choice");
|
||||
@ -44,9 +50,13 @@ static bool power_screen_dispatch(PowerScreen *screen,
|
||||
LCD_DISPLAY_ON);
|
||||
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) {
|
||||
lcd_clear(state->lcd);
|
||||
lcd_move_to(state->lcd, 0, 0);
|
||||
|
@ -152,6 +152,8 @@ static bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) {
|
||||
int cur_len;
|
||||
if (state->temp == UINT32_MAX || state->humid == UINT32_MAX) {
|
||||
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 {
|
||||
cur_len = snprintf(buff, sizeof(buff), "%-4.1f%c %3" PRIu32 "%% ",
|
||||
convert_temperature(state->temp),
|
||||
@ -169,7 +171,7 @@ static bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) {
|
||||
|
||||
StatsScreen *stats_screen_new() {
|
||||
StatsScreen *s = malloc_checked(sizeof(StatsScreen));
|
||||
screen_init(&s->parent, "Current Stats",
|
||||
screen_init(&s->parent, "Current stats",
|
||||
(ScreenDispatchFunc) stats_screen_dispatch,
|
||||
(ScreenCleanupFunc) free);
|
||||
s->last_humid = 0;
|
||||
|
@ -476,7 +476,7 @@ static bool set_tz_screen_finish(SetTZScreen *screen,
|
||||
LOG_VERBOSE("Set timezone to: \"%s\"\n", buf);
|
||||
}
|
||||
free(buf);
|
||||
request_restart();
|
||||
request_exit(true);
|
||||
}
|
||||
if (state->up_down || state->back_down ||
|
||||
state->sel_down || state->down_down) {
|
||||
|
@ -189,6 +189,7 @@ static bool stats_by_screen_dispatch(StatsByScreen *screen,
|
||||
}
|
||||
if (state->back_down) {
|
||||
if (screen->stage == STATS_BY_SELECT_PERIOD) {
|
||||
screen->period = PERIOD_HOUR;
|
||||
screen->need_redraw = true;
|
||||
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
|
||||
LCD_DISPLAY_ON);
|
||||
@ -215,7 +216,7 @@ static bool stats_by_screen_dispatch(StatsByScreen *screen,
|
||||
|
||||
StatsByScreen *stats_by_screen_new() {
|
||||
StatsByScreen *s = malloc_checked(sizeof(StatsByScreen));
|
||||
screen_init(&s->parent, "Stats by...",
|
||||
screen_init(&s->parent, "Stats by",
|
||||
(ScreenDispatchFunc) stats_by_screen_dispatch,
|
||||
(ScreenCleanupFunc) free);
|
||||
s->need_redraw = true;
|
||||
|
@ -171,6 +171,7 @@ TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state,
|
||||
void time_sel_reset(TimeSelection *ts) {
|
||||
ts->hour = 0;
|
||||
ts->minute = 0;
|
||||
ts->second = 0;
|
||||
ts->stage = TS_STAGE_HOUR;
|
||||
}
|
||||
|
||||
|
102
src/util.c
102
src/util.c
@ -17,9 +17,8 @@
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <errno.h>
|
||||
#include <stdatomic.h>
|
||||
#include <sys/signal.h>
|
||||
|
||||
const char *PERIOD_LABELS[] = {
|
||||
"HOUR",
|
||||
@ -414,12 +413,6 @@ char *pad_humid_str(int humid, char *buf, size_t buf_size) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void close_dir_if_set(void *dir) {
|
||||
if (dir) {
|
||||
closedir(dir);
|
||||
}
|
||||
}
|
||||
|
||||
bool recursive_rm_at(int dfd, const char *path) {
|
||||
if (dfd != AT_FDCWD) {
|
||||
LOG_VERBOSE("Recursive delete: \"%s\"\n", path);
|
||||
@ -432,18 +425,12 @@ bool recursive_rm_at(int dfd, const char *path) {
|
||||
int flag = 0;
|
||||
if (S_ISDIR(statbuf.st_mode)) {
|
||||
flag = AT_REMOVEDIR;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
||||
int fd = openat(dfd, path, O_DIRECTORY | O_RDONLY);
|
||||
if (fd < 0) {
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
warn("open \"%s\"", path);
|
||||
return false;
|
||||
}
|
||||
DIR *dir = NULL;
|
||||
bool error = false;
|
||||
pthread_cleanup_push(close_dir_if_set, dir);
|
||||
dir = fdopendir(fd);
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
DIR *dir = fdopendir(fd);
|
||||
if (!dir) {
|
||||
close(fd);
|
||||
warn("fdopendir \"%s\"", path);
|
||||
@ -453,15 +440,11 @@ bool recursive_rm_at(int dfd, const char *path) {
|
||||
while ((sd = readdir(dir))) {
|
||||
if (strcmp(sd->d_name, ".") != 0 && strcmp(sd->d_name, "..") != 0 &&
|
||||
!recursive_rm_at(dirfd(dir), sd->d_name)) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_cleanup_pop(true);
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
if (unlinkat(dfd, path, flag)< 0) {
|
||||
warn("remove \"%s\"", path);
|
||||
return false;
|
||||
@ -474,48 +457,31 @@ bool recursive_rm(const char *path) {
|
||||
}
|
||||
|
||||
bool export_database(sqlite3 *db, const char *dest) {
|
||||
bool status = true;
|
||||
sqlite3 *new_db = 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);
|
||||
if (res != SQLITE_OK) {
|
||||
warnx("could not open new db: \"%s\": %s", dest,
|
||||
sqlite3_errstr(res));
|
||||
status = false;
|
||||
goto cleanup;
|
||||
return false;
|
||||
}
|
||||
oper = sqlite3_backup_init(new_db, "main", db, "main");
|
||||
if (!oper) {
|
||||
warnx("could not start backup: \"%s\": %s", dest,
|
||||
sqlite3_errstr(res));
|
||||
status = false;
|
||||
goto cleanup;
|
||||
return false;
|
||||
}
|
||||
sqlite3_backup_step(oper, -1);
|
||||
cleanup:
|
||||
pthread_cleanup_pop(true); // cleanup oper
|
||||
pthread_cleanup_pop(true); // cleanup new_db
|
||||
return status;
|
||||
}
|
||||
|
||||
static void cleanup_file_obj(void *obj) {
|
||||
if (obj) {
|
||||
fclose(obj);
|
||||
}
|
||||
sqlite3_backup_finish(oper);
|
||||
sqlite3_close(new_db);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool export_database_as_csv(sqlite3 *db, const char *dest) {
|
||||
bool status = true;
|
||||
FILE *outfile = NULL;
|
||||
pthread_cleanup_push((void(*)(void *))sqlite3_reset, EXPORT_CSV_QUERY);
|
||||
pthread_cleanup_push(cleanup_file_obj, outfile);
|
||||
outfile = fopen(dest, "w");
|
||||
FILE *outfile = fopen(dest, "w");
|
||||
if (!outfile) {
|
||||
warn("fopen \"%s\"", dest);
|
||||
status = false;
|
||||
goto cleanup;
|
||||
return false;
|
||||
}
|
||||
int res;
|
||||
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) {
|
||||
warnx("CSV export to \"%s\" faield: %s\n", dest,
|
||||
sqlite3_errstr(res));
|
||||
status = false;
|
||||
goto cleanup; // readability
|
||||
return false;
|
||||
}
|
||||
fflush(outfile);
|
||||
cleanup:
|
||||
pthread_cleanup_pop(true); // reset query
|
||||
pthread_cleanup_pop(true); // close file
|
||||
return status;
|
||||
sqlite3_reset(EXPORT_CSV_QUERY);
|
||||
fclose(outfile);
|
||||
return true;
|
||||
}
|
||||
|
||||
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) {
|
||||
LOG_VERBOSE("Creating directory: \"%s\"\n", path)
|
||||
LOG_VERBOSE("Creating directory: \"%s\"\n", path);
|
||||
char *copy = strdup_checked(path);
|
||||
char *work_str = copy, *token;
|
||||
while ((token = strsep(&work_str, "/"))) {
|
||||
@ -572,7 +535,36 @@ bool mkdirs(const char *path, mode_t mode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void request_restart() {
|
||||
void request_exit(bool restart) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
15
src/util.h
15
src/util.h
@ -18,6 +18,7 @@
|
||||
#include <sys/types.h>
|
||||
#include <libgpio.h>
|
||||
#include <sqlite3.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#define _STRINGIFY_LIT(s) (#s)
|
||||
#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);
|
||||
|
||||
/*
|
||||
* Request that this program re-invoke itself with the same arguments. Used to
|
||||
* re-initialize configuration values.
|
||||
* Request that this program exit. If RESTART request instead that it re-invoke
|
||||
* 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
|
||||
|
Reference in New Issue
Block a user