Add stat range menu
This commit is contained in:
parent
be0040bb2f
commit
e1146fe006
16
Makefile
16
Makefile
@ -6,7 +6,8 @@ CFLAGS=-g -std=c11 -Wall ${SQLITE3_CFLAGS}
|
|||||||
LD=clang
|
LD=clang
|
||||||
LDFLAGS=-lgpio -lfigpar -lpthread ${SQLITE3_LDFLAGS}
|
LDFLAGS=-lgpio -lfigpar -lpthread ${SQLITE3_LDFLAGS}
|
||||||
SRCS=src/main.c src/util.c src/lcd.c src/ths.c src/button.c src/ui/screen.c\
|
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
|
||||||
PROG=rpi4b-temp-humidity
|
PROG=rpi4b-temp-humidity
|
||||||
|
|
||||||
OBJS=${SRCS:C/^src/bin/:C/.c$/.o/}
|
OBJS=${SRCS:C/^src/bin/:C/.c$/.o/}
|
||||||
@ -15,24 +16,29 @@ bin/${PROG}: ${OBJS}
|
|||||||
|
|
||||||
bin/main.o bin/util.o bin/lcd.o bin/ths.o bin/button.o: src/util.h
|
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: src/util.h
|
bin/ui/datapoints.o bin/ui/timesel.o bin/ui/statrange.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: src/lcd.h
|
bin/ui/statsby.o bin/ui/timesel.o bin/ui/statrange.o: src/lcd.h
|
||||||
|
bin/ui/datapoints.o: src/lcd.h
|
||||||
|
|
||||||
bin/main.o bin/ui/screen.o bin/ui/statsby.o: src/ui/screen.h
|
bin/main.o bin/ui/screen.o bin/ui/statsby.o: src/ui/screen.h
|
||||||
bin/ui/datesel.o bin/ui/datapoints.o bin/ui/timesel.o: src/ui/screen.h
|
bin/ui/datesel.o bin/ui/datapoints.o bin/ui/timesel.o: src/ui/screen.h
|
||||||
|
bin/ui/statrange.o: src/ui/screen.h
|
||||||
|
|
||||||
bin/main.o bin/ui/datesel.o bin/ui/statsby.o: src/ui/datesel.h
|
bin/main.o bin/ui/datesel.o bin/ui/statsby.o: src/ui/datesel.h
|
||||||
bin/ui/datapoints.o bin/ui/timesel.o: src/ui/datesel.h
|
bin/ui/datapoints.o bin/ui/timesel.o bin/ui/statrange.o: src/ui/datesel.h
|
||||||
|
|
||||||
bin/main.o bin/ths.o bin/ui/screen.o bin/ui/statsby.o: src/ths.h
|
bin/main.o bin/ths.o bin/ui/screen.o bin/ui/statsby.o: src/ths.h
|
||||||
bin/ui/timesel.o: src/ths.h
|
bin/ui/timesel.o src/ui/statrange.o: src/ths.h
|
||||||
|
|
||||||
bin/main.o bin/ui/timesel.o bin/ui/datapoints.o: src/ui/timesel.h
|
bin/main.o bin/ui/timesel.o bin/ui/datapoints.o: src/ui/timesel.h
|
||||||
|
bin/ui/statrange.o: src/ui/timesel.h
|
||||||
|
|
||||||
bin/main.o bin/button.o bin/ui/screen.o: src/button.h
|
bin/main.o bin/button.o bin/ui/screen.o: src/button.h
|
||||||
bin/main.o bin/ui/statsby.o: src/ui/statsby.h
|
bin/main.o bin/ui/statsby.o: src/ui/statsby.h
|
||||||
bin/main.o bin/ui/datapoints.o: src/ui/datapoints.h
|
bin/main.o bin/ui/datapoints.o: src/ui/datapoints.h
|
||||||
|
bin/main.o bin/ui/statrange.o: src/ui/statrange.h
|
||||||
|
|
||||||
${OBJS}: ${.TARGET:C/^bin/src/:C/.o$/.c/}
|
${OBJS}: ${.TARGET:C/^bin/src/:C/.o$/.c/}
|
||||||
@mkdir -p ${.TARGET:H}
|
@mkdir -p ${.TARGET:H}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
#include "ui/screen.h"
|
#include "ui/screen.h"
|
||||||
#include "ui/statsby.h"
|
#include "ui/statsby.h"
|
||||||
#include "ui/datapoints.h"
|
#include "ui/datapoints.h"
|
||||||
|
#include "ui/statrange.h"
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <err.h>
|
#include <err.h>
|
||||||
@ -105,6 +106,7 @@ int main(int argc, char *const *argv) {
|
|||||||
screen_manager_add(screen_manager, (Screen *) stats_screen_new());
|
screen_manager_add(screen_manager, (Screen *) stats_screen_new());
|
||||||
screen_manager_add(screen_manager, (Screen *) stats_by_screen_new());
|
screen_manager_add(screen_manager, (Screen *) stats_by_screen_new());
|
||||||
screen_manager_add(screen_manager, (Screen *) data_points_screen_new());
|
screen_manager_add(screen_manager, (Screen *) data_points_screen_new());
|
||||||
|
screen_manager_add(screen_manager, (Screen *) stat_range_screen_new());
|
||||||
while (RUNNING) {
|
while (RUNNING) {
|
||||||
lock_stat_globals();
|
lock_stat_globals();
|
||||||
uint32_t temp = LAST_TEMP;
|
uint32_t temp = LAST_TEMP;
|
||||||
|
@ -21,8 +21,7 @@ static bool data_points_select_date(DataPointsScreen *screen,
|
|||||||
}
|
}
|
||||||
if (screen->need_redraw) {
|
if (screen->need_redraw) {
|
||||||
screen->need_redraw = false;
|
screen->need_redraw = false;
|
||||||
DateSelectionState dss = date_sel_dispatch(&screen->ds, state,
|
DateSelectionState dss = date_sel_dispatch(&screen->ds, state, "Date:");
|
||||||
PERIOD_DAY, "Date:");
|
|
||||||
switch (dss) {
|
switch (dss) {
|
||||||
case DATE_SEL_CONTINUE:
|
case DATE_SEL_CONTINUE:
|
||||||
break; // ignore
|
break; // ignore
|
||||||
@ -33,6 +32,8 @@ static bool data_points_select_date(DataPointsScreen *screen,
|
|||||||
case DATE_SEL_BACK:
|
case DATE_SEL_BACK:
|
||||||
case DATE_SEL_ERROR:
|
case DATE_SEL_ERROR:
|
||||||
screen->need_redraw = true;
|
screen->need_redraw = true;
|
||||||
|
date_sel_reset(&screen->ds);
|
||||||
|
time_sel_reset(&screen->ts);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,7 +66,7 @@ static void data_points_select_time(DataPointsScreen *screen,
|
|||||||
}
|
}
|
||||||
if (screen->need_redraw) {
|
if (screen->need_redraw) {
|
||||||
screen->need_redraw = false;
|
screen->need_redraw = false;
|
||||||
TimeSelState tss = time_sel_dispatch(&screen->ts, state);
|
TimeSelState tss = time_sel_dispatch(&screen->ts, state, "Time:");
|
||||||
switch (tss) {
|
switch (tss) {
|
||||||
case TIME_SEL_CONTINUE:
|
case TIME_SEL_CONTINUE:
|
||||||
break; // ignore
|
break; // ignore
|
||||||
@ -85,6 +86,8 @@ static void data_points_select_time(DataPointsScreen *screen,
|
|||||||
static void data_points_show(DataPointsScreen *screen, SensorState *state) {
|
static void data_points_show(DataPointsScreen *screen, SensorState *state) {
|
||||||
if (state->back_down) {
|
if (state->back_down) {
|
||||||
screen->stage = DP_STAGE_DATE;
|
screen->stage = DP_STAGE_DATE;
|
||||||
|
screen->ds.stage = DATE_SEL_YEAR;
|
||||||
|
screen->ts.stage = TS_STAGE_HOUR;
|
||||||
screen->need_redraw = true;
|
screen->need_redraw = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -158,7 +161,7 @@ DataPointsScreen *data_points_screen_new() {
|
|||||||
(ScreenCleanupFunc) free);
|
(ScreenCleanupFunc) free);
|
||||||
s->need_redraw = true;
|
s->need_redraw = true;
|
||||||
s->stage = DP_STAGE_DATE;
|
s->stage = DP_STAGE_DATE;
|
||||||
date_sel_init(&s->ds, DATE_SEL_DAY);
|
date_sel_init(&s->ds, PERIOD_DAY);
|
||||||
time_sel_init(&s->ts, &s->ds);
|
time_sel_init(&s->ts, &s->ds, false);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,10 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <err.h>
|
#include <err.h>
|
||||||
|
|
||||||
void date_sel_init(DateSelection *ds, DateSelectionStage max_stage) {
|
void date_sel_init(DateSelection *ds, UtilPeriod limit_period) {
|
||||||
ds->max_stage = max_stage;
|
|
||||||
memset(&ds->start_time, 0, sizeof(UtilDate)); // min date
|
memset(&ds->start_time, 0, sizeof(UtilDate)); // min date
|
||||||
memset(&ds->end_time, 0xff, sizeof(UtilDate)); // max date
|
memset(&ds->end_time, 0xff, sizeof(UtilDate)); // max date
|
||||||
|
date_sel_set_period(ds, limit_period);
|
||||||
date_sel_reset(ds);
|
date_sel_reset(ds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,6 +24,23 @@ void date_sel_reset(DateSelection *ds) {
|
|||||||
ds->stage = DATE_SEL_YEAR;
|
ds->stage = DATE_SEL_YEAR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void date_sel_set_period(DateSelection *ds, UtilPeriod limit_period) {
|
||||||
|
ds->limit_period = limit_period;
|
||||||
|
switch (limit_period) {
|
||||||
|
case PERIOD_HOUR:
|
||||||
|
case PERIOD_DAY:
|
||||||
|
case PERIOD_WEEK:
|
||||||
|
ds->max_stage = DATE_SEL_DAY;
|
||||||
|
break;
|
||||||
|
case PERIOD_MONTH:
|
||||||
|
ds->max_stage = DATE_SEL_MONTH;
|
||||||
|
break;
|
||||||
|
case PERIOD_YEAR:
|
||||||
|
ds->max_stage = DATE_SEL_YEAR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void date_sel_clamp_time(DateSelection *ds) {
|
static void date_sel_clamp_time(DateSelection *ds) {
|
||||||
if (ds->year > ds->end_time.local_year) {
|
if (ds->year > ds->end_time.local_year) {
|
||||||
ds->year = ds->end_time.local_year;
|
ds->year = ds->end_time.local_year;
|
||||||
@ -99,10 +116,9 @@ static void date_sel_add_units(DateSelection *ds, int n) {
|
|||||||
|
|
||||||
DateSelectionState date_sel_dispatch(DateSelection *ds,
|
DateSelectionState date_sel_dispatch(DateSelection *ds,
|
||||||
SensorState *state,
|
SensorState *state,
|
||||||
UtilPeriod limit_period,
|
|
||||||
const char *label) {
|
const char *label) {
|
||||||
UtilDate start, end;
|
UtilDate start, end;
|
||||||
if (!get_database_limits(state->db, limit_period, &start, &end)) {
|
if (!get_database_limits(state->db, ds->limit_period, &start, &end)) {
|
||||||
warnx("failed to query database limits");
|
warnx("failed to query database limits");
|
||||||
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);
|
||||||
|
@ -25,6 +25,7 @@ typedef struct {
|
|||||||
int day;
|
int day;
|
||||||
DateSelectionStage stage;
|
DateSelectionStage stage;
|
||||||
DateSelectionStage max_stage;
|
DateSelectionStage max_stage;
|
||||||
|
UtilPeriod limit_period;
|
||||||
UtilDate start_time;
|
UtilDate start_time;
|
||||||
UtilDate end_time;
|
UtilDate end_time;
|
||||||
} DateSelection;
|
} DateSelection;
|
||||||
@ -37,23 +38,27 @@ typedef enum {
|
|||||||
} DateSelectionState;
|
} DateSelectionState;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize a DateSelection. MAX_STAGE is the most specific field
|
* Initialize a DateSelection. LIMIT_PERIOD is the most specific field
|
||||||
* to select.
|
* to select and the period by which to select.
|
||||||
*/
|
*/
|
||||||
void date_sel_init(DateSelection *ds, DateSelectionStage max_stage);
|
void date_sel_init(DateSelection *ds, UtilPeriod limit_period);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Reset the date on DS.
|
* Reset the date on DS.
|
||||||
*/
|
*/
|
||||||
void date_sel_reset(DateSelection *ds);
|
void date_sel_reset(DateSelection *ds);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the LIMIT_PERIOD for DS.
|
||||||
|
*/
|
||||||
|
void date_sel_set_period(DateSelection *ds, UtilPeriod limit_period);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dispatch a DateSelection by processing STATE to continue the selection.
|
* Dispatch a DateSelection by processing STATE to continue the selection.
|
||||||
* Return: weather the user backed out, is still working, or is done
|
* Return: weather the user backed out, is still working, or is done
|
||||||
*/
|
*/
|
||||||
DateSelectionState date_sel_dispatch(DateSelection *ds,
|
DateSelectionState date_sel_dispatch(DateSelection *ds,
|
||||||
SensorState *state,
|
SensorState *state,
|
||||||
UtilPeriod limit_period,
|
|
||||||
const char *label);
|
const char *label);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
173
src/ui/statrange.c
Normal file
173
src/ui/statrange.c
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
/*
|
||||||
|
* statrange.c - Screen that displays stats between two times
|
||||||
|
* 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 "statrange.h"
|
||||||
|
#include "../ths.h"
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <err.h>
|
||||||
|
|
||||||
|
// return true to go back
|
||||||
|
static bool stat_range_handle_date(StatRangeScreen *screen, DateSelection *ds,
|
||||||
|
SensorState *state, const char *label) {
|
||||||
|
if (state->down_down || state->up_down || state->back_down ||
|
||||||
|
state->sel_down) {
|
||||||
|
screen->need_redraw = true;
|
||||||
|
}
|
||||||
|
if (screen->need_redraw) {
|
||||||
|
screen->need_redraw = false;
|
||||||
|
DateSelectionState res = date_sel_dispatch(ds, state, label);
|
||||||
|
switch (res) {
|
||||||
|
case DATE_SEL_CONTINUE:
|
||||||
|
break; // ignore
|
||||||
|
case DATE_SEL_DONE:
|
||||||
|
++screen->stage;
|
||||||
|
screen->need_redraw = true;
|
||||||
|
break;
|
||||||
|
case DATE_SEL_BACK:
|
||||||
|
case DATE_SEL_ERROR:
|
||||||
|
screen->need_redraw = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stat_range_handle_time(StatRangeScreen *screen, TimeSelection *ts,
|
||||||
|
SensorState *state, const char *label) {
|
||||||
|
if (state->down_down || state->up_down || state->back_down ||
|
||||||
|
state->sel_down) {
|
||||||
|
screen->need_redraw = true;
|
||||||
|
}
|
||||||
|
if (screen->need_redraw) {
|
||||||
|
screen->need_redraw = false;
|
||||||
|
TimeSelState res = time_sel_dispatch(ts, state, label);
|
||||||
|
switch (res) {
|
||||||
|
case TIME_SEL_CONTINUE:
|
||||||
|
break; // ignore
|
||||||
|
case TIME_SEL_DONE:
|
||||||
|
++screen->stage;
|
||||||
|
screen->need_redraw = true;
|
||||||
|
break;
|
||||||
|
case TIME_SEL_BACK:
|
||||||
|
case TIME_SEL_ERROR:
|
||||||
|
screen->need_redraw = true;
|
||||||
|
--screen->stage;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t to_uts_timestamp(DateSelection *ds, TimeSelection *ts) {
|
||||||
|
struct tm broken_time = {
|
||||||
|
.tm_sec = ts->second,
|
||||||
|
.tm_min = ts->minute,
|
||||||
|
.tm_hour = ts->hour,
|
||||||
|
.tm_mday = ds->day,
|
||||||
|
.tm_mon = ds->month - 1,
|
||||||
|
.tm_year = ds->year - 1900,
|
||||||
|
.tm_isdst = -1,
|
||||||
|
};
|
||||||
|
time_t it = mktime(&broken_time);
|
||||||
|
if (it < 0) {
|
||||||
|
warnx("failed to convert to UTC timestamp");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stat_range_show_data(StatRangeScreen *screen, SensorState *state) {
|
||||||
|
if (state->back_down) {
|
||||||
|
screen->need_redraw = true;
|
||||||
|
screen->sds.stage = DATE_SEL_YEAR;
|
||||||
|
screen->sts.stage = TS_STAGE_HOUR;
|
||||||
|
screen->eds.stage = DATE_SEL_YEAR;
|
||||||
|
screen->ets.stage = TS_STAGE_HOUR;
|
||||||
|
screen->stage = STAT_RANGE_START_DATE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (screen->need_redraw) {
|
||||||
|
screen->need_redraw = false;
|
||||||
|
uint64_t start = to_uts_timestamp(&screen->sds, &screen->sts);
|
||||||
|
uint64_t end = to_uts_timestamp(&screen->eds, &screen->ets);
|
||||||
|
if (start > end) {
|
||||||
|
uint64_t t = end;
|
||||||
|
end = start;
|
||||||
|
start = t;
|
||||||
|
}
|
||||||
|
UtilAverageRange data;
|
||||||
|
if (!get_average_for_range(state->db, start, end, &data)) {
|
||||||
|
warnx("could not get average for range: %" PRIu64 "-%" PRIu64,
|
||||||
|
start, end);
|
||||||
|
--screen->stage;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lcd_clear(state->lcd);
|
||||||
|
lcd_move_to(state->lcd, 0, 0);
|
||||||
|
char points_line[17];
|
||||||
|
snprintf(points_line, 17, "%" PRIi64 "pts:", data.npoints);
|
||||||
|
lcd_write_string(state->lcd, points_line);
|
||||||
|
lcd_move_to(state->lcd, 1, 0);
|
||||||
|
if (data.npoints) {
|
||||||
|
char data_line[17];
|
||||||
|
snprintf(data_line, 17, "%.1fF %d%%", DK_TO_F(data.temp), data.humid);
|
||||||
|
lcd_write_string(state->lcd, data_line);
|
||||||
|
} else {
|
||||||
|
lcd_write_string(state->lcd, "No data!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool stat_range_screen_dispatch(StatRangeScreen *screen,
|
||||||
|
SensorState *state) {
|
||||||
|
if (state->force_draw) {
|
||||||
|
screen->need_redraw = true;
|
||||||
|
}
|
||||||
|
switch (screen->stage) {
|
||||||
|
case STAT_RANGE_START_DATE:
|
||||||
|
if(stat_range_handle_date(screen, &screen->sds, state, "Start Date:")) {
|
||||||
|
date_sel_reset(&screen->sds);
|
||||||
|
time_sel_reset(&screen->sts);
|
||||||
|
date_sel_reset(&screen->eds);
|
||||||
|
time_sel_reset(&screen->ets);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case STAT_RANGE_START_TIME:
|
||||||
|
stat_range_handle_time(screen, &screen->sts, state, "Start Time:");
|
||||||
|
break;
|
||||||
|
case STAT_RANGE_END_DATE:
|
||||||
|
if (stat_range_handle_date(screen, &screen->eds, state, "End Date:")) {
|
||||||
|
--screen->stage;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case STAT_RANGE_END_TIME:
|
||||||
|
stat_range_handle_time(screen, &screen->ets, state, "End Time:");
|
||||||
|
break;
|
||||||
|
case STAT_RANGE_SHOW:
|
||||||
|
stat_range_show_data(screen, state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
StatRangeScreen *stat_range_screen_new(void) {
|
||||||
|
StatRangeScreen *s = malloc_checked(sizeof(StatRangeScreen));
|
||||||
|
screen_init(&s->parent, "Average range",
|
||||||
|
(ScreenDispatchFunc) stat_range_screen_dispatch,
|
||||||
|
(ScreenCleanupFunc) free);
|
||||||
|
s->need_redraw = true;
|
||||||
|
s->stage = STAT_RANGE_START_DATE;
|
||||||
|
date_sel_init(&s->sds, PERIOD_DAY);
|
||||||
|
time_sel_init(&s->sts, &s->sds, true);
|
||||||
|
date_sel_init(&s->eds, PERIOD_DAY);
|
||||||
|
time_sel_init(&s->ets, &s->eds, true);
|
||||||
|
return s;
|
||||||
|
}
|
38
src/ui/statrange.h
Normal file
38
src/ui/statrange.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* statrange.h - Screen that displays stats between two times
|
||||||
|
* 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_STATRANGE_H
|
||||||
|
#define INCLUDED_STATRANGE_H
|
||||||
|
|
||||||
|
#include "datesel.h"
|
||||||
|
#include "timesel.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Screen parent;
|
||||||
|
enum {
|
||||||
|
STAT_RANGE_START_DATE,
|
||||||
|
STAT_RANGE_START_TIME,
|
||||||
|
STAT_RANGE_END_DATE,
|
||||||
|
STAT_RANGE_END_TIME,
|
||||||
|
STAT_RANGE_SHOW
|
||||||
|
} stage;
|
||||||
|
bool need_redraw;
|
||||||
|
DateSelection sds;
|
||||||
|
TimeSelection sts;
|
||||||
|
DateSelection eds;
|
||||||
|
TimeSelection ets;
|
||||||
|
} StatRangeScreen;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a new stat range screen. The screen will display average data between
|
||||||
|
* two times.
|
||||||
|
*/
|
||||||
|
StatRangeScreen *stat_range_screen_new(void);
|
||||||
|
|
||||||
|
#endif
|
@ -37,19 +37,7 @@ static void stats_by_select_period(StatsByScreen *screen, SensorState *state) {
|
|||||||
}
|
}
|
||||||
if (state->sel_down) {
|
if (state->sel_down) {
|
||||||
++screen->stage;
|
++screen->stage;
|
||||||
switch (screen->period) {
|
date_sel_set_period(&screen->ds, screen->period);
|
||||||
case PERIOD_HOUR:
|
|
||||||
case PERIOD_DAY:
|
|
||||||
case PERIOD_WEEK:
|
|
||||||
screen->ds.max_stage = DATE_SEL_DAY;
|
|
||||||
break;
|
|
||||||
case PERIOD_MONTH:
|
|
||||||
screen->ds.max_stage = DATE_SEL_MONTH;
|
|
||||||
break;
|
|
||||||
case PERIOD_YEAR:
|
|
||||||
screen->ds.max_stage = DATE_SEL_YEAR;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
screen->need_redraw = true;
|
screen->need_redraw = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,8 +48,7 @@ static void stats_by_select_start(StatsByScreen *screen, SensorState *state) {
|
|||||||
}
|
}
|
||||||
if (screen->need_redraw) {
|
if (screen->need_redraw) {
|
||||||
screen->need_redraw = false;
|
screen->need_redraw = false;
|
||||||
DateSelectionState dss = date_sel_dispatch(&screen->ds, state,
|
DateSelectionState dss = date_sel_dispatch(&screen->ds, state, "Start:");
|
||||||
screen->period, "Start:");
|
|
||||||
switch (dss) {
|
switch (dss) {
|
||||||
case DATE_SEL_CONTINUE:
|
case DATE_SEL_CONTINUE:
|
||||||
break; // ignore
|
break; // ignore
|
||||||
@ -99,8 +86,8 @@ static void stats_by_show_stats(StatsByScreen *screen, SensorState *state) {
|
|||||||
screen->ds.day == screen->ds.start_time.local_day) {
|
screen->ds.day == screen->ds.start_time.local_day) {
|
||||||
screen->offset_scale = screen->ds.start_time.local_hour;
|
screen->offset_scale = screen->ds.start_time.local_hour;
|
||||||
}
|
}
|
||||||
UtilAverageRange data;
|
UtilAveragePeriod data;
|
||||||
if (!get_average_for_range(state->db, screen->ds.year, screen->ds.month,
|
if (!get_average_for_period(state->db, screen->ds.year, screen->ds.month,
|
||||||
screen->ds.day, screen->offset_scale,
|
screen->ds.day, screen->offset_scale,
|
||||||
screen->period, &data)) {
|
screen->period, &data)) {
|
||||||
lcd_write_string(state->lcd, "Query failed!");
|
lcd_write_string(state->lcd, "Query failed!");
|
||||||
@ -190,6 +177,6 @@ StatsByScreen *stats_by_screen_new() {
|
|||||||
s->need_redraw = true;
|
s->need_redraw = true;
|
||||||
s->stage = STATS_BY_SELECT_PERIOD;
|
s->stage = STATS_BY_SELECT_PERIOD;
|
||||||
s->period = PERIOD_HOUR;
|
s->period = PERIOD_HOUR;
|
||||||
date_sel_init(&s->ds, DATE_SEL_DAY);
|
date_sel_init(&s->ds, PERIOD_DAY);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
121
src/ui/timesel.c
121
src/ui/timesel.c
@ -11,11 +11,10 @@
|
|||||||
|
|
||||||
#include <err.h>
|
#include <err.h>
|
||||||
|
|
||||||
void time_sel_init(TimeSelection *ts, DateSelection *ds) {
|
void time_sel_init(TimeSelection *ts, DateSelection *ds, bool do_second) {
|
||||||
ts->hour = 0;
|
|
||||||
ts->minute = 0;
|
|
||||||
ts->hour_set = false;
|
|
||||||
ts->ds = ds;
|
ts->ds = ds;
|
||||||
|
ts->do_second = do_second;
|
||||||
|
time_sel_reset(ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void time_sel_clamp(TimeSelection *ts) {
|
static void time_sel_clamp(TimeSelection *ts) {
|
||||||
@ -30,6 +29,12 @@ static void time_sel_clamp(TimeSelection *ts) {
|
|||||||
ts->minute > ts->ds->end_time.local_minute) {
|
ts->minute > ts->ds->end_time.local_minute) {
|
||||||
ts->minute = ts->ds->end_time.local_minute;
|
ts->minute = ts->ds->end_time.local_minute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ts->hour == ts->ds->end_time.local_hour &&
|
||||||
|
ts->minute == ts->ds->end_time.local_minute &&
|
||||||
|
ts->second > ts->ds->end_time.local_second) {
|
||||||
|
ts->second = ts->ds->end_time.local_second;
|
||||||
|
}
|
||||||
} else if (ts->ds->start_time.local_year == ts->ds->year &&
|
} else if (ts->ds->start_time.local_year == ts->ds->year &&
|
||||||
ts->ds->start_time.local_month == ts->ds->month &&
|
ts->ds->start_time.local_month == ts->ds->month &&
|
||||||
ts->ds->start_time.local_day == ts->ds->day) {
|
ts->ds->start_time.local_day == ts->ds->day) {
|
||||||
@ -40,34 +45,57 @@ static void time_sel_clamp(TimeSelection *ts) {
|
|||||||
ts->minute < ts->ds->start_time.local_minute) {
|
ts->minute < ts->ds->start_time.local_minute) {
|
||||||
ts->minute = ts->ds->start_time.local_minute;
|
ts->minute = ts->ds->start_time.local_minute;
|
||||||
}
|
}
|
||||||
|
if (ts->hour == ts->ds->start_time.local_hour &&
|
||||||
|
ts->minute == ts->ds->start_time.local_minute &&
|
||||||
|
ts->second > ts->ds->start_time.local_second) {
|
||||||
|
ts->second = ts->ds->start_time.local_second;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void time_sel_add(TimeSelection *ts, int amount) {
|
static void time_sel_add(TimeSelection *ts, int amount) {
|
||||||
if (!ts->hour_set) {
|
switch (ts->stage) {
|
||||||
ts->hour = (ts->hour + amount) % 24;
|
case TS_STAGE_HOUR:
|
||||||
} else {
|
ts->hour += amount;
|
||||||
if (ts->minute + amount >= 60) {
|
break;
|
||||||
ts->hour = (ts->hour + 1) % 24;
|
case TS_STAGE_MINUTE:
|
||||||
} else if (ts->minute + amount < 0) {
|
ts->minute += amount;
|
||||||
ts->hour = (ts->hour - 1) % 24;
|
break;
|
||||||
}
|
case TS_STAGE_SECOND:
|
||||||
ts->minute = (ts->minute + amount) % 60;
|
ts->second += amount;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
if (ts->second >= 60) {
|
||||||
|
++ts->minute;
|
||||||
|
} else if (ts->second < 0) {
|
||||||
|
--ts->minute;
|
||||||
|
}
|
||||||
|
if (ts->minute >= 60) {
|
||||||
|
++ts->hour;
|
||||||
|
} else if (ts->minute < 0) {
|
||||||
|
--ts->hour;
|
||||||
|
}
|
||||||
|
ts->hour %= 24;
|
||||||
|
ts->minute %= 60;
|
||||||
|
ts->second %= 60;
|
||||||
if (ts->hour < 0) {
|
if (ts->hour < 0) {
|
||||||
ts->hour = 24 + ts->hour;
|
ts->hour += 24;
|
||||||
}
|
}
|
||||||
if (ts->minute < 0) {
|
if (ts->minute < 0) {
|
||||||
ts->minute = 60 + ts->minute;
|
ts->minute += 60;
|
||||||
|
}
|
||||||
|
if (ts->second < 0) {
|
||||||
|
ts->second += 60;
|
||||||
}
|
}
|
||||||
time_sel_clamp(ts);
|
time_sel_clamp(ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state) {
|
TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state,
|
||||||
|
const char *label) {
|
||||||
if (state->back_down) {
|
if (state->back_down) {
|
||||||
if (ts->hour_set) {
|
if (ts->stage != TS_STAGE_HOUR) {
|
||||||
ts->hour_set = false;
|
--ts->stage;
|
||||||
} else {
|
} else {
|
||||||
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);
|
||||||
@ -75,12 +103,13 @@ TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (state->sel_down) {
|
if (state->sel_down) {
|
||||||
if (!ts->hour_set) {
|
if ((ts->do_second && ts->stage == TS_STAGE_SECOND) ||
|
||||||
ts->hour_set = true;
|
(!ts->do_second && ts->stage == TS_STAGE_MINUTE)) {
|
||||||
} else {
|
|
||||||
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);
|
||||||
return TIME_SEL_DONE;
|
return TIME_SEL_DONE;
|
||||||
|
} else {
|
||||||
|
++ts->stage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ts->ds) {
|
if (ts->ds) {
|
||||||
@ -97,18 +126,40 @@ TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state) {
|
|||||||
time_sel_add(ts, state->up_down - state->down_down);
|
time_sel_add(ts, 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);
|
||||||
lcd_write_string(state->lcd, "Time:");
|
lcd_write_string(state->lcd, label);
|
||||||
const char *format;
|
|
||||||
int cursor_pos;
|
|
||||||
if (!ts->hour_set) {
|
|
||||||
format = ">%02d:%02d";
|
|
||||||
cursor_pos = 0;
|
|
||||||
} else {
|
|
||||||
format = "%02d:>%02d";
|
|
||||||
cursor_pos = 3;
|
|
||||||
}
|
|
||||||
char time_line[17];
|
char time_line[17];
|
||||||
snprintf(time_line, 17, format, ts->hour, ts->minute);
|
int cursor_pos;
|
||||||
|
if (ts->do_second) {
|
||||||
|
const char *format;
|
||||||
|
switch (ts->stage) {
|
||||||
|
case TS_STAGE_HOUR:
|
||||||
|
format = ">%02d:%02d:%02d";
|
||||||
|
cursor_pos = 0;
|
||||||
|
break;
|
||||||
|
case TS_STAGE_MINUTE:
|
||||||
|
format = "%02d:>%02d:%02d";
|
||||||
|
cursor_pos = 3;
|
||||||
|
break;
|
||||||
|
case TS_STAGE_SECOND:
|
||||||
|
format = "%02d:%02d:>%02d";
|
||||||
|
cursor_pos = 6;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
snprintf(time_line, 17, format, ts->hour, ts->minute, ts->second);
|
||||||
|
} else {
|
||||||
|
const char *format;
|
||||||
|
switch (ts->stage) {
|
||||||
|
case TS_STAGE_HOUR:
|
||||||
|
format = ">%02d:%02d";
|
||||||
|
cursor_pos = 0;
|
||||||
|
break;
|
||||||
|
default: // minute or second (which shouldn't happen)
|
||||||
|
format = "%02d:>%02d";
|
||||||
|
cursor_pos = 3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
snprintf(time_line, 17, format, ts->hour, ts->minute);
|
||||||
|
}
|
||||||
lcd_move_to(state->lcd, 1, 0);
|
lcd_move_to(state->lcd, 1, 0);
|
||||||
lcd_write_string(state->lcd, time_line);
|
lcd_write_string(state->lcd, time_line);
|
||||||
lcd_move_to(state->lcd, 1, cursor_pos);
|
lcd_move_to(state->lcd, 1, cursor_pos);
|
||||||
@ -116,3 +167,9 @@ TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state) {
|
|||||||
LCD_DISPLAY_ON);
|
LCD_DISPLAY_ON);
|
||||||
return TIME_SEL_CONTINUE;
|
return TIME_SEL_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void time_sel_reset(TimeSelection *ts) {
|
||||||
|
ts->hour = 0;
|
||||||
|
ts->minute = 0;
|
||||||
|
ts->stage = TS_STAGE_HOUR;
|
||||||
|
}
|
||||||
|
@ -16,9 +16,15 @@
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
DateSelection *ds;
|
DateSelection *ds;
|
||||||
|
enum {
|
||||||
|
TS_STAGE_HOUR,
|
||||||
|
TS_STAGE_MINUTE,
|
||||||
|
TS_STAGE_SECOND
|
||||||
|
} stage;
|
||||||
int hour;
|
int hour;
|
||||||
bool hour_set;
|
|
||||||
int minute;
|
int minute;
|
||||||
|
bool do_second;
|
||||||
|
int second;
|
||||||
} TimeSelection;
|
} TimeSelection;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@ -32,12 +38,18 @@ typedef enum {
|
|||||||
* Initialize the TimeSelection TS. Optionally, get the year, month, and day,
|
* Initialize the TimeSelection TS. Optionally, get the year, month, and day,
|
||||||
* and limits from DS.
|
* and limits from DS.
|
||||||
*/
|
*/
|
||||||
void time_sel_init(TimeSelection *ts, DateSelection *ds);
|
void time_sel_init(TimeSelection *ts, DateSelection *ds, bool do_second);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dispatch (process input and draw) the TimeSelection TS according to STATE.
|
* Dispatch (process input and draw) the TimeSelection TS according to STATE.
|
||||||
* Return: the current state of the selection (back, continue, done).
|
* Return: the current state of the selection (back, continue, done).
|
||||||
*/
|
*/
|
||||||
TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state);
|
TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state,
|
||||||
|
const char *label);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reset TS back to 00:00.
|
||||||
|
*/
|
||||||
|
void time_sel_reset(TimeSelection *ts);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
136
src/util.c
136
src/util.c
@ -88,24 +88,28 @@ static const char *DB_LIMITS_QUERY_STR =
|
|||||||
"strftime('%e', MinUTC, 'unixepoch') as MinUTCDay,\n"
|
"strftime('%e', MinUTC, 'unixepoch') as MinUTCDay,\n"
|
||||||
"strftime('%H', RealMin, 'unixepoch') as MinUTCHour,\n"
|
"strftime('%H', RealMin, 'unixepoch') as MinUTCHour,\n"
|
||||||
"strftime('%M', RealMin, 'unixepoch') as MinUTCMin,\n"
|
"strftime('%M', RealMin, 'unixepoch') as MinUTCMin,\n"
|
||||||
|
"strftime('%S', RealMin, 'unixepoch') as MinUTCSec,\n"
|
||||||
"MinLocal,\n"
|
"MinLocal,\n"
|
||||||
"strftime('%Y', MinLocal, 'unixepoch') as MinLocalYear,\n"
|
"strftime('%Y', MinLocal, 'unixepoch') as MinLocalYear,\n"
|
||||||
"strftime('%m', MinLocal, 'unixepoch') as MinLocalMonth,\n"
|
"strftime('%m', MinLocal, 'unixepoch') as MinLocalMonth,\n"
|
||||||
"strftime('%e', MinLocal, 'unixepoch') as MinLocalDay,\n"
|
"strftime('%e', MinLocal, 'unixepoch') as MinLocalDay,\n"
|
||||||
"strftime('%H', RealMin, 'unixepoch', 'localtime') as MinLocalHour,\n"
|
"strftime('%H', RealMin, 'unixepoch', 'localtime') as MinLocalHour,\n"
|
||||||
"strftime('%M', RealMin, 'unixepoch', 'localtime') as MinLocalMin,\n"
|
"strftime('%M', RealMin, 'unixepoch', 'localtime') as MinLocalMin,\n"
|
||||||
|
"strftime('%S', RealMin, 'unixepoch', 'localtime') as MinLocalSec,\n"
|
||||||
"MaxUTC,\n"
|
"MaxUTC,\n"
|
||||||
"strftime('%Y', MaxUTC, 'unixepoch') as MaxUTCYear,\n"
|
"strftime('%Y', MaxUTC, 'unixepoch') as MaxUTCYear,\n"
|
||||||
"strftime('%m', MaxUTC, 'unixepoch') as MaxUTCMonth,\n"
|
"strftime('%m', MaxUTC, 'unixepoch') as MaxUTCMonth,\n"
|
||||||
"strftime('%e', MaxUTC, 'unixepoch') as MaxUTCDay,\n"
|
"strftime('%e', MaxUTC, 'unixepoch') as MaxUTCDay,\n"
|
||||||
"strftime('%H', RealMax, 'unixepoch') as MaxUTCHour,\n"
|
"strftime('%H', RealMax, 'unixepoch') as MaxUTCHour,\n"
|
||||||
"strftime('%M', RealMax, 'unixepoch') as MaxUTCMin,\n"
|
"strftime('%M', RealMax, 'unixepoch') as MaxUTCMin,\n"
|
||||||
|
"strftime('%S', RealMax, 'unixepoch') as MaxUTCSec,\n"
|
||||||
"MaxLocal,\n"
|
"MaxLocal,\n"
|
||||||
"strftime('%Y', MaxLocal, 'unixepoch') as MaxLocalYear,\n"
|
"strftime('%Y', MaxLocal, 'unixepoch') as MaxLocalYear,\n"
|
||||||
"strftime('%m', MaxLocal, 'unixepoch') as MaxLocalMonth,\n"
|
"strftime('%m', MaxLocal, 'unixepoch') as MaxLocalMonth,\n"
|
||||||
"strftime('%e', MaxLocal, 'unixepoch') as MaxLocalDay,\n"
|
"strftime('%e', MaxLocal, 'unixepoch') as MaxLocalDay,\n"
|
||||||
"strftime('%H', RealMax, 'unixepoch', 'localtime') as MaxLocalHour,\n"
|
"strftime('%H', RealMax, 'unixepoch', 'localtime') as MaxLocalHour,\n"
|
||||||
"strftime('%M', RealMax, 'unixepoch', 'localtime') as MaxLocalMin\n"
|
"strftime('%M', RealMax, 'unixepoch', 'localtime') as MaxLocalMin,\n"
|
||||||
|
"strftime('%S', RealMax, 'unixepoch', 'localtime') as MaxLocalSec\n"
|
||||||
"FROM\n"
|
"FROM\n"
|
||||||
"(SELECT\n"
|
"(SELECT\n"
|
||||||
"RealMax, RealMin,\n"
|
"RealMax, RealMin,\n"
|
||||||
@ -115,7 +119,7 @@ static const char *DB_LIMITS_QUERY_STR =
|
|||||||
"unixepoch(RealMax, 'unixepoch', 'localtime', '+1 ' || ?1, 'start of ' || ?1, '-1 second') as MaxLocal\n"
|
"unixepoch(RealMax, 'unixepoch', 'localtime', '+1 ' || ?1, 'start of ' || ?1, '-1 second') as MaxLocal\n"
|
||||||
"FROM (SELECT max(time) as RealMax, min(time) as RealMin FROM env_data));";
|
"FROM (SELECT max(time) as RealMax, min(time) as RealMin FROM env_data));";
|
||||||
static sqlite3_stmt *DB_LIMITS_QUERY;
|
static sqlite3_stmt *DB_LIMITS_QUERY;
|
||||||
static const char *AVG_FOR_RANGE_QUERY_STR =
|
static const char *AVG_FOR_PERIOD_QUERY_STR =
|
||||||
"SELECT\n"
|
"SELECT\n"
|
||||||
"strftime('%Y', stime, 'unixepoch', 'localtime') as y,\n"
|
"strftime('%Y', stime, 'unixepoch', 'localtime') as y,\n"
|
||||||
"strftime('%m', stime, 'unixepoch', 'localtime') as m,\n"
|
"strftime('%m', stime, 'unixepoch', 'localtime') as m,\n"
|
||||||
@ -133,9 +137,9 @@ static const char *AVG_FOR_RANGE_QUERY_STR =
|
|||||||
"round(avg(temp)) AS atemp,\n"
|
"round(avg(temp)) AS atemp,\n"
|
||||||
"round(avg(humid)) AS ahumid\n"
|
"round(avg(humid)) AS ahumid\n"
|
||||||
"FROM env_data\n"
|
"FROM env_data\n"
|
||||||
"WHERE time > stime\n"
|
"WHERE time >= stime\n"
|
||||||
"AND time < etime);\n";
|
"AND time <= etime);\n";
|
||||||
static sqlite3_stmt *AVG_FOR_RANGE_QUERY;
|
static sqlite3_stmt *AVG_FOR_PERIOD_QUERY;
|
||||||
static const char *DATA_POINT_QUERY_STR =
|
static const char *DATA_POINT_QUERY_STR =
|
||||||
"SELECT max(time) IS NULL, max(time), temp, humid FROM env_data WHERE time < ?1\n"
|
"SELECT max(time) IS NULL, max(time), temp, humid FROM env_data WHERE time < ?1\n"
|
||||||
"UNION ALL\n"
|
"UNION ALL\n"
|
||||||
@ -143,28 +147,38 @@ static const char *DATA_POINT_QUERY_STR =
|
|||||||
"UNION ALL\n"
|
"UNION ALL\n"
|
||||||
"SELECT false, * FROM env_data WHERE time == ?1;";
|
"SELECT false, * FROM env_data WHERE time == ?1;";
|
||||||
static sqlite3_stmt *DATA_POINT_QUERY;
|
static sqlite3_stmt *DATA_POINT_QUERY;
|
||||||
|
static const char *AVG_FOR_RANGE_QUERY_STR =
|
||||||
|
"SELECT count(time), round(avg(temp)), round(avg(humid)) FROM env_data\n"
|
||||||
|
"WHERE time >= ?1 AND time <= ?2;";
|
||||||
|
static sqlite3_stmt *AVG_FOR_RANGE_QUERY;
|
||||||
void initialize_util_queries(sqlite3 *db) {
|
void initialize_util_queries(sqlite3 *db) {
|
||||||
int status = sqlite3_prepare_v2(db, DB_LIMITS_QUERY_STR, -1,
|
int status = sqlite3_prepare_v2(db, DB_LIMITS_QUERY_STR, -1,
|
||||||
&DB_LIMITS_QUERY, NULL);
|
&DB_LIMITS_QUERY, NULL);
|
||||||
if (status != SQLITE_OK) {
|
if (status != SQLITE_OK) {
|
||||||
errx(1, "failed to compile limits query: %s", sqlite3_errstr(status));
|
errx(1, "failed to compile limits query: %s", sqlite3_errstr(status));
|
||||||
}
|
}
|
||||||
status = sqlite3_prepare_v2(db, AVG_FOR_RANGE_QUERY_STR, -1,
|
status = sqlite3_prepare_v2(db, AVG_FOR_PERIOD_QUERY_STR, -1,
|
||||||
&AVG_FOR_RANGE_QUERY, NULL);
|
&AVG_FOR_PERIOD_QUERY, NULL);
|
||||||
if (status != SQLITE_OK) {
|
if (status != SQLITE_OK) {
|
||||||
errx(1, "failed to compile range average query: %s", sqlite3_errstr(status));
|
errx(1, "failed to compile period average query: %s", sqlite3_errstr(status));
|
||||||
}
|
}
|
||||||
status = sqlite3_prepare_v2(db, DATA_POINT_QUERY_STR, -1,
|
status = sqlite3_prepare_v2(db, DATA_POINT_QUERY_STR, -1,
|
||||||
&DATA_POINT_QUERY, NULL);
|
&DATA_POINT_QUERY, NULL);
|
||||||
if (status != SQLITE_OK) {
|
if (status != SQLITE_OK) {
|
||||||
errx(1, "failed to compile data point query: %s", sqlite3_errstr(status));
|
errx(1, "failed to compile data point query: %s", sqlite3_errstr(status));
|
||||||
}
|
}
|
||||||
|
status = sqlite3_prepare_v2(db, AVG_FOR_RANGE_QUERY_STR, -1,
|
||||||
|
&AVG_FOR_RANGE_QUERY, NULL);
|
||||||
|
if (status != SQLITE_OK) {
|
||||||
|
errx(1, "failed to compile range average query: %s", sqlite3_errstr(status));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanup_util_queries() {
|
void cleanup_util_queries() {
|
||||||
sqlite3_finalize(DB_LIMITS_QUERY);
|
sqlite3_finalize(DB_LIMITS_QUERY);
|
||||||
sqlite3_finalize(AVG_FOR_RANGE_QUERY);
|
sqlite3_finalize(AVG_FOR_PERIOD_QUERY);
|
||||||
sqlite3_finalize(DATA_POINT_QUERY);
|
sqlite3_finalize(DATA_POINT_QUERY);
|
||||||
|
sqlite3_finalize(AVG_FOR_RANGE_QUERY);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get_database_limits(sqlite3 *db, UtilPeriod period, UtilDate *start,
|
bool get_database_limits(sqlite3 *db, UtilPeriod period, UtilDate *start,
|
||||||
@ -184,26 +198,30 @@ bool get_database_limits(sqlite3 *db, UtilPeriod period, UtilDate *start,
|
|||||||
start->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 3);
|
start->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 3);
|
||||||
start->utc_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 4);
|
start->utc_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 4);
|
||||||
start->utc_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 5);
|
start->utc_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 5);
|
||||||
start->local = sqlite3_column_int64(DB_LIMITS_QUERY, 6);
|
start->utc_second = sqlite3_column_int64(DB_LIMITS_QUERY, 6);
|
||||||
start->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 7);
|
start->local = sqlite3_column_int64(DB_LIMITS_QUERY, 7);
|
||||||
start->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 8);
|
start->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 8);
|
||||||
start->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 9);
|
start->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 9);
|
||||||
start->local_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 10);
|
start->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 10);
|
||||||
start->local_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 11);
|
start->local_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 11);
|
||||||
|
start->local_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 12);
|
||||||
|
start->local_second = sqlite3_column_int64(DB_LIMITS_QUERY, 13);
|
||||||
}
|
}
|
||||||
if (end) {
|
if (end) {
|
||||||
end->utc = sqlite3_column_int64(DB_LIMITS_QUERY, 12);
|
end->utc = sqlite3_column_int64(DB_LIMITS_QUERY, 14);
|
||||||
end->utc_year = sqlite3_column_int64(DB_LIMITS_QUERY, 13);
|
end->utc_year = sqlite3_column_int64(DB_LIMITS_QUERY, 15);
|
||||||
end->utc_month = sqlite3_column_int64(DB_LIMITS_QUERY, 14);
|
end->utc_month = sqlite3_column_int64(DB_LIMITS_QUERY, 16);
|
||||||
end->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 15);
|
end->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 17);
|
||||||
end->utc_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 16);
|
end->utc_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 18);
|
||||||
end->utc_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 17);
|
end->utc_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 19);
|
||||||
end->local = sqlite3_column_int64(DB_LIMITS_QUERY, 18);
|
end->utc_second = sqlite3_column_int64(DB_LIMITS_QUERY, 20);
|
||||||
end->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 19);
|
end->local = sqlite3_column_int64(DB_LIMITS_QUERY, 21);
|
||||||
end->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 20);
|
end->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 22);
|
||||||
end->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 21);
|
end->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 23);
|
||||||
end->local_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 22);
|
end->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 24);
|
||||||
end->local_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 23);
|
end->local_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 25);
|
||||||
|
end->local_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 26);
|
||||||
|
end->local_second = sqlite3_column_int64(DB_LIMITS_QUERY, 27);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
success = false;
|
success = false;
|
||||||
@ -212,39 +230,37 @@ bool get_database_limits(sqlite3 *db, UtilPeriod period, UtilDate *start,
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get_average_for_range(sqlite3 *db, int year, int month, int day,
|
bool get_average_for_period(sqlite3 *db, int year, int month, int day,
|
||||||
int64_t count, UtilPeriod period,
|
int64_t count, UtilPeriod period,
|
||||||
UtilAverageRange *data) {
|
UtilAveragePeriod *data) {
|
||||||
bool success = true;
|
bool success = true;
|
||||||
sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 1, year);
|
sqlite3_bind_int(AVG_FOR_PERIOD_QUERY, 1, year);
|
||||||
sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 2, month);
|
sqlite3_bind_int(AVG_FOR_PERIOD_QUERY, 2, month);
|
||||||
sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 3, day);
|
sqlite3_bind_int(AVG_FOR_PERIOD_QUERY, 3, day);
|
||||||
sqlite3_bind_int64(AVG_FOR_RANGE_QUERY, 4, count);
|
sqlite3_bind_int64(AVG_FOR_PERIOD_QUERY, 4, count);
|
||||||
if (period == PERIOD_WEEK) {
|
if (period == PERIOD_WEEK) {
|
||||||
sqlite3_bind_text(AVG_FOR_RANGE_QUERY, 5, "days", -1, SQLITE_STATIC);
|
sqlite3_bind_text(AVG_FOR_PERIOD_QUERY, 5, "days", -1, SQLITE_STATIC);
|
||||||
sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 6, 7);
|
sqlite3_bind_int(AVG_FOR_PERIOD_QUERY, 6, 7);
|
||||||
} else {
|
} else {
|
||||||
sqlite3_bind_text(AVG_FOR_RANGE_QUERY, 5, PERIOD_LABELS[period], -1,
|
sqlite3_bind_text(AVG_FOR_PERIOD_QUERY, 5, PERIOD_LABELS[period], -1,
|
||||||
SQLITE_STATIC);
|
SQLITE_STATIC);
|
||||||
sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 6, 1);
|
sqlite3_bind_int(AVG_FOR_PERIOD_QUERY, 6, 1);
|
||||||
}
|
}
|
||||||
int status = sqlite3_step(AVG_FOR_RANGE_QUERY);
|
int status = sqlite3_step(AVG_FOR_PERIOD_QUERY);
|
||||||
if (status == SQLITE_ROW) {
|
if (status != SQLITE_ROW) {
|
||||||
if (data) {
|
|
||||||
data->year = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 0);
|
|
||||||
data->month = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 1);
|
|
||||||
data->day = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 2);
|
|
||||||
data->hour = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 3);
|
|
||||||
data->npoints = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 4);
|
|
||||||
data->upper_bound = sqlite3_column_int64(AVG_FOR_RANGE_QUERY, 5);
|
|
||||||
data->lower_bound = sqlite3_column_int64(AVG_FOR_RANGE_QUERY, 6);
|
|
||||||
data->temp = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 7);
|
|
||||||
data->humid = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 8);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
success = false;
|
success = false;
|
||||||
|
} else if (data) {
|
||||||
|
data->year = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 0);
|
||||||
|
data->month = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 1);
|
||||||
|
data->day = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 2);
|
||||||
|
data->hour = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 3);
|
||||||
|
data->npoints = sqlite3_column_int64(AVG_FOR_PERIOD_QUERY, 4);
|
||||||
|
data->upper_bound = sqlite3_column_int64(AVG_FOR_PERIOD_QUERY, 5);
|
||||||
|
data->lower_bound = sqlite3_column_int64(AVG_FOR_PERIOD_QUERY, 6);
|
||||||
|
data->temp = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 7);
|
||||||
|
data->humid = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 8);
|
||||||
}
|
}
|
||||||
sqlite3_reset(AVG_FOR_RANGE_QUERY);
|
sqlite3_reset(AVG_FOR_PERIOD_QUERY);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,3 +315,19 @@ bool get_data_point_info(sqlite3 *db, int64_t time, UtilDataPointInfo *info) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get_average_for_range(sqlite3 *db, uint64_t start, uint64_t end,
|
||||||
|
UtilAverageRange *data) {
|
||||||
|
bool success = true;
|
||||||
|
sqlite3_bind_int64(AVG_FOR_RANGE_QUERY, 1, start);
|
||||||
|
sqlite3_bind_int64(AVG_FOR_RANGE_QUERY, 2, end);
|
||||||
|
if (sqlite3_step(AVG_FOR_RANGE_QUERY) != SQLITE_ROW) {
|
||||||
|
success = false;
|
||||||
|
} else if (data) {
|
||||||
|
data->npoints = sqlite3_column_int64(AVG_FOR_RANGE_QUERY, 0);
|
||||||
|
data->temp = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 1);
|
||||||
|
data->humid = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 2);
|
||||||
|
}
|
||||||
|
sqlite3_reset(AVG_FOR_RANGE_QUERY);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
25
src/util.h
25
src/util.h
@ -99,12 +99,14 @@ typedef struct {
|
|||||||
int utc_day;
|
int utc_day;
|
||||||
int utc_hour;
|
int utc_hour;
|
||||||
int utc_minute;
|
int utc_minute;
|
||||||
|
int utc_second;
|
||||||
int64_t local;
|
int64_t local;
|
||||||
int local_year;
|
int local_year;
|
||||||
int local_month;
|
int local_month;
|
||||||
int local_day;
|
int local_day;
|
||||||
int local_hour;
|
int local_hour;
|
||||||
int local_minute;
|
int local_minute;
|
||||||
|
int local_second;
|
||||||
} UtilDate;
|
} UtilDate;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@ -120,14 +122,14 @@ extern const size_t NPERIOD;
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Return the START of the first and END of the last PERIOD (ex. week) of DB.
|
* Return the START of the first and END of the last PERIOD (ex. week) of DB.
|
||||||
* Also get the hour and minute limits for the first and last day.
|
* Also get the hour, minute, and second limits for the first and last day.
|
||||||
* Return: false if an error occurred, true otherwise.
|
* Return: false if an error occurred, true otherwise.
|
||||||
*/
|
*/
|
||||||
bool get_database_limits(sqlite3 *db, UtilPeriod period, UtilDate *start,
|
bool get_database_limits(sqlite3 *db, UtilPeriod period, UtilDate *start,
|
||||||
UtilDate *end);
|
UtilDate *end);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int npoints;
|
int64_t npoints;
|
||||||
int temp;
|
int temp;
|
||||||
int humid;
|
int humid;
|
||||||
int year;
|
int year;
|
||||||
@ -136,16 +138,16 @@ typedef struct {
|
|||||||
int hour;
|
int hour;
|
||||||
bool lower_bound;
|
bool lower_bound;
|
||||||
bool upper_bound;
|
bool upper_bound;
|
||||||
} UtilAverageRange;
|
} UtilAveragePeriod;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the average temp. and humid. between YEAR-MONTH-DAY + COUNT * PERIOD and
|
* Get the average temp. and humid. between YEAR-MONTH-DAY + COUNT * PERIOD and
|
||||||
* YEAR-MONTH-DAY + (COUNT + 1) * PERIOD.
|
* YEAR-MONTH-DAY + (COUNT + 1) * PERIOD.
|
||||||
* Return: false if an error occurred, true otherwise.
|
* Return: false if an error occurred, true otherwise.
|
||||||
*/
|
*/
|
||||||
bool get_average_for_range(sqlite3 *db, int year, int month, int day,
|
bool get_average_for_period(sqlite3 *db, int year, int month, int day,
|
||||||
int64_t count, UtilPeriod period,
|
int64_t count, UtilPeriod period,
|
||||||
UtilAverageRange *data);
|
UtilAveragePeriod *data);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int64_t time;
|
int64_t time;
|
||||||
@ -168,4 +170,17 @@ typedef struct {
|
|||||||
*/
|
*/
|
||||||
bool get_data_point_info(sqlite3 *db, int64_t time, UtilDataPointInfo *info);
|
bool get_data_point_info(sqlite3 *db, int64_t time, UtilDataPointInfo *info);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int64_t npoints;
|
||||||
|
int temp;
|
||||||
|
int humid;
|
||||||
|
} UtilAverageRange;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the average DATA for the range of UTC times START to END (inclusive).
|
||||||
|
* Return: false if an error occurred, true otherwise.
|
||||||
|
*/
|
||||||
|
bool get_average_for_range(sqlite3 *db, uint64_t start, uint64_t end,
|
||||||
|
UtilAverageRange *data);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user