Work on the second stats screen
This commit is contained in:
parent
81eb22e83a
commit
da83874d11
2
Makefile
2
Makefile
@ -2,7 +2,7 @@
|
||||
.include "config.mk"
|
||||
|
||||
CC=clang
|
||||
CFLAGS=-std=c11 -Wall ${SQLITE3_CFLAGS}
|
||||
CFLAGS=-g -std=c11 -Wall ${SQLITE3_CFLAGS}
|
||||
LD=clang
|
||||
LDFLAGS=-lgpio -lfigpar -lpthread ${SQLITE3_LDFLAGS}
|
||||
SRCS=src/main.c src/util.c src/lcd.c src/ths.c src/button.c src/screen.c
|
||||
|
17
src/main.c
17
src/main.c
@ -91,14 +91,17 @@ int main(int argc, char *const *argv) {
|
||||
GLOBAL_OPTS.data_pins[7]);
|
||||
setup_signals();
|
||||
open_database();
|
||||
initialize_util_queries(DATABASE);
|
||||
pthread_t bg_update;
|
||||
start_update_thread(&bg_update);
|
||||
ScreenManager *screen_manager = screen_manager_new(lcd, handle,
|
||||
ScreenManager *screen_manager = screen_manager_new(DATABASE,
|
||||
lcd, handle,
|
||||
GLOBAL_OPTS.back_pin,
|
||||
GLOBAL_OPTS.up_pin,
|
||||
GLOBAL_OPTS.down_pin,
|
||||
GLOBAL_OPTS.sel_pin);
|
||||
screen_manager_add(screen_manager, (Screen *) stats_screen_new());
|
||||
screen_manager_add(screen_manager, (Screen *) stats_by_screen_new());
|
||||
while (RUNNING) {
|
||||
lock_stat_globals();
|
||||
uint32_t temp = LAST_TEMP;
|
||||
@ -115,6 +118,7 @@ int main(int argc, char *const *argv) {
|
||||
err(1, "join of background thread failed");
|
||||
}
|
||||
// this needs to be done after bg_update exits
|
||||
cleanup_util_queries();
|
||||
sqlite3_close(DATABASE);
|
||||
pthread_mutex_destroy(&STAT_MUTEX);
|
||||
gpio_close(handle);
|
||||
@ -386,12 +390,12 @@ void parse_config_file(const char *path) {
|
||||
.action = parse_uint_callback,
|
||||
},
|
||||
{
|
||||
.directive = "down_pin",
|
||||
.directive = "up_pin",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_callback,
|
||||
},
|
||||
{
|
||||
.directive = "up_pin",
|
||||
.directive = "down_pin",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_callback,
|
||||
},
|
||||
@ -430,6 +434,9 @@ void parse_config_file(const char *path) {
|
||||
static void exit_signal_callback(int sig) {
|
||||
LOG_VERBOSE("Caught signal %d. Exiting...\n", sig);
|
||||
RUNNING = false;
|
||||
if (signal(sig, SIG_DFL) == SIG_ERR) {
|
||||
err(1, "resetting signal handler failed");
|
||||
}
|
||||
}
|
||||
|
||||
#define SIGNAL_SETUP_CHECKED(sig) \
|
||||
@ -497,13 +504,13 @@ static void create_db_table() {
|
||||
LOG_VERBOSE("Ensured env_data table existance\n");
|
||||
}
|
||||
|
||||
static const char *UPDATE_STATES_QUERY =
|
||||
static const char *UPDATE_STATS_QUERY =
|
||||
"INSERT INTO env_data (time, temp, humid) "
|
||||
"VALUES(?, ?, ?);";
|
||||
static void *update_thread_action(void *_ignored) {
|
||||
create_db_table();
|
||||
sqlite3_stmt *insert_statement = NULL;
|
||||
int status = sqlite3_prepare_v2(DATABASE, UPDATE_STATES_QUERY, -1,
|
||||
int status = sqlite3_prepare_v2(DATABASE, UPDATE_STATS_QUERY, -1,
|
||||
&insert_statement, NULL);
|
||||
if (status != SQLITE_OK) {
|
||||
errx(1, "could not compile SQL query. sqlite3 error follows:\n%s",
|
||||
|
297
src/screen.c
297
src/screen.c
@ -8,11 +8,12 @@
|
||||
* version. See the LICENSE file for more information.
|
||||
*/
|
||||
#include "screen.h"
|
||||
#include "util.h"
|
||||
#include "ths.h"
|
||||
|
||||
#include <err.h>
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
void screen_init(Screen *screen, const char *name,
|
||||
@ -30,10 +31,11 @@ void screen_delete(Screen *screen) {
|
||||
}
|
||||
}
|
||||
|
||||
ScreenManager *screen_manager_new(LCD *lcd, gpio_handle_t handle,
|
||||
ScreenManager *screen_manager_new(sqlite3 *db, LCD *lcd, gpio_handle_t handle,
|
||||
gpio_pin_t back_pin, gpio_pin_t up_pin,
|
||||
gpio_pin_t down_pin, gpio_pin_t sel_pin) {
|
||||
ScreenManager *sm = malloc_checked(sizeof(ScreenManager));
|
||||
sm->db = db;
|
||||
sm->lcd = lcd;
|
||||
sm->screens = NULL;
|
||||
sm->screen_count = 0;
|
||||
@ -133,7 +135,7 @@ void screen_manager_dispatch(ScreenManager *sm, uint32_t temp, uint32_t humid) {
|
||||
}
|
||||
}
|
||||
|
||||
bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) {
|
||||
static bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) {
|
||||
if (state->back_down) {
|
||||
return true;
|
||||
}
|
||||
@ -168,3 +170,292 @@ StatsScreen *stats_screen_new() {
|
||||
s->last_temp = 0;
|
||||
return s;
|
||||
}
|
||||
|
||||
static const char *PERIOD_LABELS[] = {
|
||||
"HOUR",
|
||||
"DAY",
|
||||
"WEEK",
|
||||
"MONTH",
|
||||
"YEAR",
|
||||
};
|
||||
static const size_t NPERIOD = sizeof(PERIOD_LABELS) / sizeof(char *);
|
||||
|
||||
static void stats_by_select_period(StatsByScreen *screen, SensorState *state) {
|
||||
if (state->up_down) {
|
||||
screen->period = (screen->period + 1) % NPERIOD;
|
||||
screen->need_redraw = true;
|
||||
}
|
||||
if (state->down_down) {
|
||||
if (--screen->period < 0) {
|
||||
screen->period = NPERIOD - 1;
|
||||
}
|
||||
screen->need_redraw = true;
|
||||
}
|
||||
if (screen->need_redraw) {
|
||||
lcd_clear(state->lcd);
|
||||
lcd_move_to(state->lcd, 0, 0);
|
||||
lcd_write_string(state->lcd, "Period:");
|
||||
lcd_move_to(state->lcd, 1, 0);
|
||||
lcd_write_string(state->lcd, ">");
|
||||
lcd_write_string(state->lcd, PERIOD_LABELS[screen->period]);
|
||||
lcd_move_to(state->lcd, 1, 0);
|
||||
lcd_display_control(state->lcd, LCD_CURSOR_BLINK, LCD_CURSOR_ON,
|
||||
LCD_DISPLAY_ON);
|
||||
screen->need_redraw = false;
|
||||
}
|
||||
if (state->sel_down) {
|
||||
++screen->stage;
|
||||
switch (screen->period) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
screen->ds.max_stage = DATE_SEL_DAY;
|
||||
break;
|
||||
case 3:
|
||||
screen->ds.max_stage = DATE_SEL_MONTH;
|
||||
break;
|
||||
case 4:
|
||||
screen->ds.max_stage = DATE_SEL_YEAR;
|
||||
break;
|
||||
}
|
||||
screen->need_redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
void date_sel_init(DateSelection *ds, int start_line, int start_col,
|
||||
DateSelectionStage max_stage) {
|
||||
ds->max_stage = max_stage;
|
||||
ds->start_line = start_line;
|
||||
ds->start_col = start_col;
|
||||
memset(&ds->start_time, 0, sizeof(UtilDate)); // min date
|
||||
memset(&ds->end_time, 0xff, sizeof(UtilDate)); // max date
|
||||
date_sel_reset(ds);
|
||||
}
|
||||
|
||||
void date_sel_reset(DateSelection *ds) {
|
||||
ds->year = -1;
|
||||
ds->stage = DATE_SEL_YEAR;
|
||||
}
|
||||
|
||||
static void date_sel_clamp_time(DateSelection *ds) {
|
||||
if (ds->year > ds->end_time.local_year) {
|
||||
ds->year = ds->end_time.local_year;
|
||||
}
|
||||
if (ds->year == ds->end_time.local_year &&
|
||||
ds->month > ds->end_time.local_month) {
|
||||
ds->month = ds->end_time.local_month;
|
||||
}
|
||||
if (ds->month == ds->end_time.local_year &&
|
||||
ds->month == ds->end_time.local_month &&
|
||||
ds->day > ds->end_time.local_day) {
|
||||
printf("Clamp Day High!\n");
|
||||
ds->day = ds->end_time.local_day;
|
||||
}
|
||||
if (ds->year < ds->start_time.local_year) {
|
||||
ds->year = ds->start_time.local_year;
|
||||
}
|
||||
if (ds->year == ds->start_time.local_year &&
|
||||
ds->month < ds->start_time.local_month) {
|
||||
ds->month = ds->start_time.local_month;
|
||||
}
|
||||
if (ds->year == ds->start_time.local_year &&
|
||||
ds->month == ds->start_time.local_month &&
|
||||
ds->day < ds->start_time.local_day) {
|
||||
printf("Clamp Day Low!\n");
|
||||
ds->day = ds->start_time.local_day;
|
||||
}
|
||||
printf("Max: %d %d %d\n", ds->end_time.local_year, ds->end_time.local_month, ds->end_time.local_day);
|
||||
printf("Min: %d %d %d\n", ds->start_time.local_year, ds->start_time.local_month, ds->start_time.local_day);
|
||||
printf("Cur: %d %d %d\n", ds->year, ds->month, ds->day);
|
||||
}
|
||||
|
||||
static void date_sel_cleanup(DateSelection *ds) {
|
||||
if (ds->month < 1) {
|
||||
ds->month = 12 - ds->month;
|
||||
--ds->year;
|
||||
} else if (ds->month > 12) {
|
||||
ds->month = (ds->month % 13) + 1;
|
||||
++ds->year;
|
||||
}
|
||||
int ndays = days_in_month(ds->month, ds->year);
|
||||
if (ds->day == 33) {
|
||||
ds->day = ndays;
|
||||
} else if (ds->day < 1) {
|
||||
ds->day = 33; // this means last day of month
|
||||
--ds->month;
|
||||
date_sel_cleanup(ds);
|
||||
} else if (ds->day > ndays) {
|
||||
ds->day = 1;
|
||||
++ds->month;
|
||||
date_sel_cleanup(ds);
|
||||
}
|
||||
date_sel_clamp_time(ds);
|
||||
}
|
||||
|
||||
static void date_sel_add_units(DateSelection *ds, int n) {
|
||||
switch (ds->stage) {
|
||||
case DATE_SEL_YEAR:
|
||||
ds->year += n;
|
||||
break;
|
||||
case DATE_SEL_MONTH:
|
||||
ds->month += n;
|
||||
break;
|
||||
case DATE_SEL_DAY:
|
||||
ds->day += n;
|
||||
break;
|
||||
}
|
||||
date_sel_cleanup(ds);
|
||||
}
|
||||
|
||||
DateSelectionState date_sel_dispatch(DateSelection *ds, SensorState *state) {
|
||||
if (ds->year == -1) {
|
||||
time_t utc_time = time(NULL);
|
||||
struct tm local_time;
|
||||
localtime_r(&utc_time, &local_time);
|
||||
ds->year = local_time.tm_year + 1900;
|
||||
if (ds->max_stage > DATE_SEL_YEAR) {
|
||||
ds->month = local_time.tm_mon + 1;
|
||||
}
|
||||
if (ds->max_stage > DATE_SEL_MONTH) {
|
||||
ds->day = local_time.tm_mday;
|
||||
}
|
||||
date_sel_cleanup(ds);
|
||||
}
|
||||
if (ds->max_stage < DATE_SEL_MONTH) {
|
||||
ds->month = 1;
|
||||
}
|
||||
if (ds->max_stage < DATE_SEL_DAY) {
|
||||
ds->day = 1;
|
||||
}
|
||||
if (state->back_down) {
|
||||
if (ds->stage == DATE_SEL_YEAR) {
|
||||
return DATE_SEL_BACK;
|
||||
}
|
||||
--ds->stage;
|
||||
}
|
||||
if (state->up_down || state->down_down) {
|
||||
date_sel_add_units(ds, state->up_down - state->down_down);
|
||||
}
|
||||
if (state->sel_down) {
|
||||
if (ds->stage == ds->max_stage) {
|
||||
return DATE_SEL_DONE;
|
||||
}
|
||||
++ds->stage;
|
||||
}
|
||||
int buf_size = 17 - ds->start_col;
|
||||
char buff[buf_size];
|
||||
int cursor_pos;
|
||||
const char *format;
|
||||
switch (ds->stage) {
|
||||
case DATE_SEL_YEAR:
|
||||
cursor_pos = 0;
|
||||
format = ">%04d/%02d/%02d";
|
||||
break;
|
||||
case DATE_SEL_MONTH:
|
||||
cursor_pos = 5;
|
||||
format = "%04d/>%02d/%02d";
|
||||
break;
|
||||
case DATE_SEL_DAY:
|
||||
cursor_pos = 8;
|
||||
format = "%04d/%02d/>%02d";
|
||||
break;
|
||||
default:
|
||||
LOG_VERBOSE("Date selector tried to select bad field\n");
|
||||
return DATE_SEL_BACK;
|
||||
}
|
||||
snprintf(buff, buf_size, format, ds->year, ds->month, ds->day);
|
||||
cursor_pos += ds->start_col;
|
||||
lcd_move_to(state->lcd, ds->start_line, ds->start_col);
|
||||
lcd_write_string(state->lcd, buff);
|
||||
lcd_display_control(state->lcd, LCD_CURSOR_BLINK, LCD_CURSOR_ON,
|
||||
LCD_DISPLAY_ON);
|
||||
lcd_move_to(state->lcd, ds->start_line, cursor_pos);
|
||||
return DATE_SEL_CONTINUE;
|
||||
}
|
||||
|
||||
static void stats_by_select_start(StatsByScreen *screen, SensorState *state) {
|
||||
if (state->up_down || state->down_down || state->sel_down || state->back_down) {
|
||||
screen->need_redraw = true;
|
||||
UtilDate start, end;
|
||||
if (!get_database_limits(state->db, PERIOD_LABELS[screen->period],
|
||||
&start, &end)) {
|
||||
warnx("failed to query database limits");
|
||||
--screen->stage;
|
||||
return;
|
||||
}
|
||||
screen->ds.start_time = start;
|
||||
screen->ds.end_time = end;
|
||||
}
|
||||
if (screen->need_redraw) {
|
||||
lcd_clear(state->lcd);
|
||||
lcd_move_to(state->lcd, 0, 0);
|
||||
lcd_write_string(state->lcd, "Start: ");
|
||||
lcd_move_to(state->lcd, 1, 0);
|
||||
screen->need_redraw = false;
|
||||
DateSelectionState dss = date_sel_dispatch(&screen->ds, state);
|
||||
switch (dss) {
|
||||
case DATE_SEL_BACK:
|
||||
screen->need_redraw = true;
|
||||
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
|
||||
LCD_DISPLAY_ON);
|
||||
--screen->stage;
|
||||
return;
|
||||
case DATE_SEL_CONTINUE:
|
||||
break; // ignore
|
||||
case DATE_SEL_DONE:
|
||||
++screen->stage;
|
||||
screen->need_redraw = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool stats_by_screen_dispatch(StatsByScreen *screen,
|
||||
SensorState *state) {
|
||||
if (state->force_draw) {
|
||||
screen->need_redraw = true;
|
||||
}
|
||||
if (state->back_down) {
|
||||
if (screen->stage == STATS_BY_SELECT_PERIOD) {
|
||||
screen->need_redraw = true;
|
||||
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
|
||||
LCD_DISPLAY_ON);
|
||||
date_sel_reset(&screen->ds);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
switch (screen->stage) {
|
||||
case STATS_BY_SELECT_PERIOD:
|
||||
stats_by_select_period(screen, state);
|
||||
break;
|
||||
case STATS_BY_SELECT_START:
|
||||
stats_by_select_start(screen, state);
|
||||
break;
|
||||
case STATS_BY_SHOWING:
|
||||
if (state->back_down) {
|
||||
screen->stage = STATS_BY_SELECT_PERIOD;
|
||||
screen->need_redraw = true;
|
||||
screen->ds.stage = DATE_SEL_YEAR;
|
||||
} else if (screen->need_redraw) {
|
||||
lcd_clear(state->lcd);
|
||||
lcd_move_to(state->lcd, 0, 0);
|
||||
lcd_write_string(state->lcd, "Some data!");
|
||||
screen->need_redraw = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_VERBOSE("Attempt to show bad stats by screen\n");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
StatsByScreen *stats_by_screen_new() {
|
||||
StatsByScreen *s = malloc_checked(sizeof(StatsByScreen));
|
||||
screen_init(&s->parent, "Stats by...",
|
||||
(ScreenDispatchFunc) stats_by_screen_dispatch,
|
||||
(ScreenCleanupFunc) free);
|
||||
s->need_redraw = true;
|
||||
date_sel_init(&s->ds, 1, 0, DATE_SEL_DAY);
|
||||
return s;
|
||||
}
|
||||
|
74
src/screen.h
74
src/screen.h
@ -12,11 +12,14 @@
|
||||
|
||||
#include "button.h"
|
||||
#include "lcd.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <time.h>
|
||||
#include <libgpio.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
typedef struct {
|
||||
sqlite3 *db;
|
||||
LCD *lcd;
|
||||
uint32_t temp;
|
||||
uint32_t humid;
|
||||
@ -52,6 +55,7 @@ void screen_init(Screen *screen, const char *name,
|
||||
void screen_delete(Screen *screen);
|
||||
|
||||
typedef struct {
|
||||
sqlite3 *db;
|
||||
LCD *lcd;
|
||||
Button *back_btn;
|
||||
Button *up_btn;
|
||||
@ -68,10 +72,11 @@ typedef struct {
|
||||
} ScreenManager;
|
||||
|
||||
/*
|
||||
* Crate a new ScreenManager with the given LCD and buttons
|
||||
* Crate a new ScreenManager with the given DB, LCD and buttons. DB and LCD
|
||||
* still owned by the caller!
|
||||
* Return: the new ScreenManager
|
||||
*/
|
||||
ScreenManager *screen_manager_new(LCD *lcd, gpio_handle_t handle,
|
||||
ScreenManager *screen_manager_new(sqlite3 *db, LCD *lcd, gpio_handle_t handle,
|
||||
gpio_pin_t back_pin, gpio_pin_t up_pin,
|
||||
gpio_pin_t down_pin, gpio_pin_t sel_pin);
|
||||
|
||||
@ -99,6 +104,71 @@ typedef struct {
|
||||
time_t last_min;
|
||||
} StatsScreen;
|
||||
|
||||
/*
|
||||
* Create a new stats screen. This screen will display the current temp. and
|
||||
* humidity data from a THS sensor.
|
||||
*/
|
||||
StatsScreen *stats_screen_new(void);
|
||||
|
||||
typedef enum {
|
||||
DATE_SEL_YEAR,
|
||||
DATE_SEL_MONTH,
|
||||
DATE_SEL_DAY,
|
||||
} DateSelectionStage;
|
||||
|
||||
typedef struct {
|
||||
int year;
|
||||
int month;
|
||||
int day;
|
||||
DateSelectionStage stage;
|
||||
DateSelectionStage max_stage;
|
||||
int start_line;
|
||||
int start_col;
|
||||
UtilDate start_time;
|
||||
UtilDate end_time;
|
||||
} DateSelection;
|
||||
|
||||
typedef enum {
|
||||
DATE_SEL_BACK,
|
||||
DATE_SEL_CONTINUE,
|
||||
DATE_SEL_DONE
|
||||
} DateSelectionState;
|
||||
|
||||
/*
|
||||
* Initialize a DateSelection. START_LINE and START_COL are the first line and
|
||||
* column where it should draw the widget. MAX_STAGE is the most specific field
|
||||
* to select.
|
||||
*/
|
||||
void date_sel_init(DateSelection *ds, int start_line, int start_col,
|
||||
DateSelectionStage max_stage);
|
||||
|
||||
/*
|
||||
* Reset the date on DS.
|
||||
*/
|
||||
void date_sel_reset(DateSelection *ds);
|
||||
|
||||
/*
|
||||
* Dispatch a DateSelection by processing STATE to continue the selection.
|
||||
* Return: weather the user backed out, is still working, or is done
|
||||
*/
|
||||
DateSelectionState date_sel_dispatch(DateSelection *ds, SensorState *state);
|
||||
|
||||
typedef struct {
|
||||
Screen parent;
|
||||
enum {
|
||||
STATS_BY_SELECT_PERIOD = 0,
|
||||
STATS_BY_SELECT_START,
|
||||
STATS_BY_SHOWING,
|
||||
} stage;
|
||||
bool need_redraw;
|
||||
int period;
|
||||
DateSelection ds;
|
||||
} StatsByScreen;
|
||||
|
||||
/*
|
||||
* Create a new stats by screen. This screen will display data from the database
|
||||
* by hour, day, week, month, and year.
|
||||
*/
|
||||
StatsByScreen *stats_by_screen_new(void);
|
||||
|
||||
#endif
|
||||
|
100
src/util.c
100
src/util.c
@ -45,3 +45,103 @@ void *strdup_checked(const char *str) {
|
||||
}
|
||||
return new_str;
|
||||
}
|
||||
|
||||
int days_in_month(int m, int y) {
|
||||
switch (m) {
|
||||
case 2:
|
||||
if (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) {
|
||||
return 29;
|
||||
} else {
|
||||
return 28;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
case 3:
|
||||
case 5:
|
||||
case 7:
|
||||
case 8:
|
||||
case 10:
|
||||
case 12:
|
||||
return 31;
|
||||
default:
|
||||
return 30;
|
||||
}
|
||||
}
|
||||
|
||||
// is this even a C program anymore?
|
||||
static const char *DB_LIMITS_QUERY_STR =
|
||||
"SELECT\n"
|
||||
"MinUTC,\n"
|
||||
// labels below are for debugging
|
||||
"strftime('%Y', MinUTC, 'unixepoch') as MinUTCYear,\n"
|
||||
"strftime('%m', MinUTC, 'unixepoch') as MinUTCMonth,\n"
|
||||
"strftime('%e', MinUTC, 'unixepoch') as MinUTCDay,\n"
|
||||
"MinLocal,\n"
|
||||
"strftime('%Y', MinLocal, 'unixepoch') as MinLocalYear,\n"
|
||||
"strftime('%m', MinLocal, 'unixepoch') as MinLocalMonth,\n"
|
||||
"strftime('%e', MinLocal, 'unixepoch') as MinLocalDay,\n"
|
||||
"MaxUTC,\n"
|
||||
"strftime('%Y', MaxUTC, 'unixepoch') as MaxUTCYear,\n"
|
||||
"strftime('%m', MaxUTC, 'unixepoch') as MaxUTCMonth,\n"
|
||||
"strftime('%e', MaxUTC, 'unixepoch') as MaxUTCDay,\n"
|
||||
"MaxLocal,\n"
|
||||
"strftime('%Y', MaxLocal, 'unixepoch') as MaxLocalYear,\n"
|
||||
"strftime('%m', MaxLocal, 'unixepoch') as MaxLocalMonth,\n"
|
||||
"strftime('%e', MaxLocal, 'unixepoch') as MaxLocalDay\n"
|
||||
"FROM\n"
|
||||
"(SELECT\n"
|
||||
"unixepoch(min(time), 'unixepoch', 'start of ' || ?1) as MinUTC,\n"
|
||||
"unixepoch(max(time), 'unixepoch', '+1 ' || ?1, 'start of ' || ?1, '-1 second') as MaxUTC,\n"
|
||||
"unixepoch(min(time), 'unixepoch', 'localtime', 'start of ' || ?1) as MinLocal,\n"
|
||||
"unixepoch(max(time), 'unixepoch', 'localtime', '+1 ' || ?1, 'start of ' || ?1, '-1 second') as MaxLocal\n"
|
||||
"FROM env_data);";
|
||||
static sqlite3_stmt *DB_LIMITS_QUERY;
|
||||
void initialize_util_queries(sqlite3 *db) {
|
||||
int status = sqlite3_prepare_v2(db, DB_LIMITS_QUERY_STR, -1,
|
||||
&DB_LIMITS_QUERY, NULL);
|
||||
if (status != SQLITE_OK) {
|
||||
errx(1, "failed to compile limits query: %s", sqlite3_errstr(status));
|
||||
}
|
||||
}
|
||||
|
||||
void cleanup_util_queries() {
|
||||
sqlite3_finalize(DB_LIMITS_QUERY);
|
||||
}
|
||||
|
||||
bool get_database_limits(sqlite3 *db, const char *period, UtilDate *start,
|
||||
UtilDate *end) {
|
||||
if (strcasecmp(period, "week") == 0 || strcasecmp(period, "hour") == 0) {
|
||||
period = "day";
|
||||
}
|
||||
bool success = true;
|
||||
sqlite3_bind_text(DB_LIMITS_QUERY, 1, period, -1, SQLITE_TRANSIENT);
|
||||
int status = sqlite3_step(DB_LIMITS_QUERY);
|
||||
if (status == SQLITE_ROW) {
|
||||
if (start) {
|
||||
start->utc = sqlite3_column_int64(DB_LIMITS_QUERY, 0);
|
||||
start->utc_year = sqlite3_column_int64(DB_LIMITS_QUERY, 1);
|
||||
start->utc_month = sqlite3_column_int64(DB_LIMITS_QUERY, 2);
|
||||
start->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 3);
|
||||
start->local = sqlite3_column_int64(DB_LIMITS_QUERY, 4);
|
||||
start->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 5);
|
||||
start->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 6);
|
||||
start->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 7);
|
||||
}
|
||||
if (end) {
|
||||
end->utc = sqlite3_column_int64(DB_LIMITS_QUERY, 8);
|
||||
end->utc_year = sqlite3_column_int64(DB_LIMITS_QUERY, 9);
|
||||
end->utc_month = sqlite3_column_int64(DB_LIMITS_QUERY, 10);
|
||||
end->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 11);
|
||||
end->local = sqlite3_column_int64(DB_LIMITS_QUERY, 12);
|
||||
end->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 13);
|
||||
end->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 14);
|
||||
end->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 15);
|
||||
}
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
// unbind the string so it can be freed by the caller
|
||||
sqlite3_bind_null(DB_LIMITS_QUERY, 1);
|
||||
sqlite3_reset(DB_LIMITS_QUERY);
|
||||
return success;
|
||||
}
|
||||
|
34
src/util.h
34
src/util.h
@ -17,6 +17,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
#include <libgpio.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
typedef struct {
|
||||
char *config_path; // path to config file
|
||||
@ -76,4 +77,37 @@ void *strdup_checked(const char *str);
|
||||
*/
|
||||
#define LOG_VERBOSE(...) if (GLOBAL_OPTS.verbose) {fprintf(stderr, __VA_ARGS__);}
|
||||
|
||||
/*
|
||||
* Return: the number of days in month M. 1 is January. Y is the year.
|
||||
*/
|
||||
int days_in_month(int m, int y);
|
||||
|
||||
/*
|
||||
* Initialize SQL queries used by this file.
|
||||
*/
|
||||
void initialize_util_queries(sqlite3 *db);
|
||||
|
||||
/*
|
||||
* Cleanup SQL queries used by this file.
|
||||
*/
|
||||
void cleanup_util_queries(void);
|
||||
|
||||
typedef struct {
|
||||
int64_t utc;
|
||||
int utc_year;
|
||||
int utc_month;
|
||||
int utc_day;
|
||||
int64_t local;
|
||||
int local_year;
|
||||
int local_month;
|
||||
int local_day;
|
||||
} UtilDate;
|
||||
|
||||
/*
|
||||
* Return the START of the first and END of the last PERIOD (ex. week) of DB.
|
||||
* Return: false if an error occurred, true otherwise.
|
||||
*/
|
||||
bool get_database_limits(sqlite3 *db, const char *period, UtilDate *start,
|
||||
UtilDate *end);
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user