diff --git a/src/lcd.c b/src/lcd.c index c11b0fb..126f51b 100644 --- a/src/lcd.c +++ b/src/lcd.c @@ -48,6 +48,8 @@ static void lcd_pulse_enable(LCD *lcd) { LCD *lcd_open(gpio_handle_t handle, gpio_pin_t rs, gpio_pin_t rw, gpio_pin_t en, gpio_pin_t d0, gpio_pin_t d1, gpio_pin_t d2, gpio_pin_t d3, gpio_pin_t d4, gpio_pin_t d5, gpio_pin_t d6, gpio_pin_t d7) { + LOG_VERBOSE("Initializing LCD: rs=%d, rw=%d, en=%d, d={%d, %d, %d, %d, %d, " + "%d, %d, %d}\n", rs, rw, en, d0, d1, d2, d3, d4, d5, d6, d7); LCD *lcd = malloc_checked(sizeof(LCD)); lcd->handle = handle; lcd->rs = rs; @@ -68,8 +70,7 @@ LCD *lcd_open(gpio_handle_t handle, gpio_pin_t rs, gpio_pin_t rw, gpio_pin_t en, lcd_call(lcd, 0, 0, 0, 0, 1, 1, 1, 0, 0); // 5x8 font, 2 lines, 8bit lcd_entry_mode(lcd, LCD_INCREMENT, LCD_CURSOR_MOVE); lcd_display_control(lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, LCD_DISPLAY_ON); - LOG_VERBOSE("Initializing LCD: rs=%d, rw=%d, en=%d, d={%d, %d, %d, %d, %d, " - "%d, %d, %d}\n", rs, rw, en, d0, d1, d2, d3, d4, d5, d6, d7); + LOG_VERBOSE("LCD Initialization done\n"); return lcd; } diff --git a/src/screen.c b/src/screen.c index 7a08c21..85f174b 100644 --- a/src/screen.c +++ b/src/screen.c @@ -171,15 +171,6 @@ StatsScreen *stats_screen_new() { 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; @@ -206,15 +197,15 @@ static void stats_by_select_period(StatsByScreen *screen, SensorState *state) { if (state->sel_down) { ++screen->stage; switch (screen->period) { - case 0: - case 1: - case 2: + case PERIOD_HOUR: + case PERIOD_DAY: + case PERIOD_WEEK: screen->ds.max_stage = DATE_SEL_DAY; break; - case 3: + case PERIOD_MONTH: screen->ds.max_stage = DATE_SEL_MONTH; break; - case 4: + case PERIOD_YEAR: screen->ds.max_stage = DATE_SEL_YEAR; break; } @@ -244,11 +235,14 @@ static void date_sel_clamp_time(DateSelection *ds) { if (ds->year == ds->end_time.local_year && ds->month > ds->end_time.local_month) { ds->month = ds->end_time.local_month; + if (ds->stage == DATE_SEL_DAY) { + // last days of previous month + ds->day = days_in_month(ds->month, ds->year); + } } - if (ds->month == ds->end_time.local_year && + if (ds->year == 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) { @@ -257,16 +251,15 @@ static void date_sel_clamp_time(DateSelection *ds) { if (ds->year == ds->start_time.local_year && ds->month < ds->start_time.local_month) { ds->month = ds->start_time.local_month; + if (ds->stage == DATE_SEL_DAY) { + ds->day = 1; // first day of the next 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) { @@ -404,12 +397,77 @@ static void stats_by_select_start(StatsByScreen *screen, SensorState *state) { break; // ignore case DATE_SEL_DONE: ++screen->stage; + screen->offset_scale = 0; screen->need_redraw = true; + screen->ulimit_reached = false; break; } } } +static void stats_by_show_stats(StatsByScreen *screen, SensorState *state) { + 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) { + screen->need_redraw = false; + 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); + UtilAverageRange data; + if (!get_average_for_range(state->db, screen->ds.year, screen->ds.month, + screen->ds.day, screen->offset_scale, + screen->period, &data)) { + lcd_write_string(state->lcd, "Query failed!"); + warnx("failed to query average temperature and humidity"); + return; + } + screen->ulimit_reached = data.upper_bound; + screen->blimit_reached = data.lower_bound; + char period_string[17]; + switch (screen->period) { + case PERIOD_YEAR: + snprintf(period_string, 17, "Year>%d", data.year); + break; + case PERIOD_MONTH: + snprintf(period_string, 17, "Month>%d-%d", data.year, + data.month); + break; + case PERIOD_WEEK: + snprintf(period_string, 17, "Week>%d-%d-%d", data.year, + data.month, data.day); + break; + case PERIOD_DAY: + snprintf(period_string, 17, "Day>%d-%d-%d", data.year, + data.month, data.day); + break; + case PERIOD_HOUR: + snprintf(period_string, 17, "Hour>%d-%d %d:00", data.month, + data.day, data.hour); + break; + } + lcd_write_string(state->lcd, period_string); + lcd_move_to(state->lcd, 1, 0); + if (data.npoints) { + char data_string[17]; + snprintf(data_string, 17, "T:%.1fF H:%d%%", DK_TO_F(data.temp), data.humid); + lcd_write_string(state->lcd, data_string); + } else { + lcd_write_string(state->lcd, "No data!"); + } + } + if (!screen->ulimit_reached && state->up_down) { + ++screen->offset_scale; + screen->need_redraw = true; + } + if (!screen->blimit_reached && state->down_down) { + --screen->offset_scale; + screen->need_redraw = true; + } +} + static bool stats_by_screen_dispatch(StatsByScreen *screen, SensorState *state) { if (state->force_draw) { @@ -432,16 +490,7 @@ static bool stats_by_screen_dispatch(StatsByScreen *screen, 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; - } + stats_by_show_stats(screen, state); break; default: LOG_VERBOSE("Attempt to show bad stats by screen\n"); diff --git a/src/screen.h b/src/screen.h index 81bac00..9216b64 100644 --- a/src/screen.h +++ b/src/screen.h @@ -6,8 +6,7 @@ * 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_SCREEN_H + */ #ifndef INCLUDED_SCREEN_H #define INCLUDED_SCREEN_H #include "button.h" @@ -161,8 +160,11 @@ typedef struct { STATS_BY_SHOWING, } stage; bool need_redraw; - int period; + UtilPeriod period; DateSelection ds; + int offset_scale; + bool ulimit_reached; + bool blimit_reached; } StatsByScreen; /* diff --git a/src/util.c b/src/util.c index cc0ff18..9029104 100644 --- a/src/util.c +++ b/src/util.c @@ -12,6 +12,15 @@ #include #include +const char *PERIOD_LABELS[] = { + "HOUR", + "DAY", + "WEEK", + "MONTH", + "YEAR", +}; +const size_t NPERIOD = sizeof(PERIOD_LABELS) / sizeof(char *); + Options GLOBAL_OPTS; void cleanup_options(Options *opts) { @@ -96,25 +105,53 @@ static const char *DB_LIMITS_QUERY_STR = "unixepoch(max(time), 'unixepoch', 'localtime', '+1 ' || ?1, 'start of ' || ?1, '-1 second') as MaxLocal\n" "FROM env_data);"; static sqlite3_stmt *DB_LIMITS_QUERY; +const char *AVG_FOR_RANGE_QUERY_STR = + "SELECT\n" + "strftime('%Y', stime, 'unixepoch', 'localtime') as y,\n" + "strftime('%m', stime, 'unixepoch', 'localtime') as m,\n" + "strftime('%e', stime, 'unixepoch', 'localtime') as d,\n" + "strftime('%H', stime, 'unixepoch', 'localtime') as h,\n" + "ndata,\n" + "etime >= (select max(time) from env_data) as ulimit,\n" + "stime <= (select min(time) from env_data) as blimit,\n" + "atemp,\n" + "ahumid\n" + "FROM (SELECT\n" + "unixepoch(printf('%04d-%02d-%02d', ?1, ?2, ?3), 'utc', printf('%d %s', ?4 * ?6, ?5)) as stime,\n" + "unixepoch(printf('%04d-%02d-%02d', ?1, ?2, ?3), 'utc', printf('%d %s', (?4 + 1) * ?6, ?5)) as etime,\n" + "count(time) AS ndata,\n" + "round(avg(temp)) AS atemp,\n" + "round(avg(humid)) AS ahumid\n" + "FROM env_data\n" + "WHERE time > stime\n" + "AND time < etime);\n"; +static sqlite3_stmt *AVG_FOR_RANGE_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)); } + 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() { sqlite3_finalize(DB_LIMITS_QUERY); + sqlite3_finalize(AVG_FOR_RANGE_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); + sqlite3_bind_text(DB_LIMITS_QUERY, 1, period, -1, SQLITE_STATIC); int status = sqlite3_step(DB_LIMITS_QUERY); if (status == SQLITE_ROW) { if (start) { @@ -145,3 +182,39 @@ bool get_database_limits(sqlite3 *db, const char *period, UtilDate *start, sqlite3_reset(DB_LIMITS_QUERY); return success; } + +bool get_average_for_range(sqlite3 *db, int year, int month, int day, + int64_t count, UtilPeriod period, + UtilAverageRange *data) { + bool success = true; + sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 1, year); + sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 2, month); + sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 3, day); + sqlite3_bind_int64(AVG_FOR_RANGE_QUERY, 4, count); + if (period == PERIOD_WEEK) { + sqlite3_bind_text(AVG_FOR_RANGE_QUERY, 5, "days", -1, SQLITE_STATIC); + sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 6, 7); + } else { + sqlite3_bind_text(AVG_FOR_RANGE_QUERY, 5, PERIOD_LABELS[period], -1, + SQLITE_STATIC); + sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 6, 1); + } + int status = sqlite3_step(AVG_FOR_RANGE_QUERY); + 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; + } + sqlite3_reset(AVG_FOR_RANGE_QUERY); + return success; +} diff --git a/src/util.h b/src/util.h index fd32dc7..786d646 100644 --- a/src/util.h +++ b/src/util.h @@ -103,6 +103,16 @@ typedef struct { int local_day; } UtilDate; +typedef enum { + PERIOD_HOUR = 0, + PERIOD_DAY, + PERIOD_WEEK, + PERIOD_MONTH, + PERIOD_YEAR, +} UtilPeriod; +extern const char *PERIOD_LABELS[]; +extern const size_t NPERIOD; + /* * Return the START of the first and END of the last PERIOD (ex. week) of DB. * Return: false if an error occurred, true otherwise. @@ -110,4 +120,25 @@ typedef struct { bool get_database_limits(sqlite3 *db, const char *period, UtilDate *start, UtilDate *end); +typedef struct { + int npoints; + int temp; + int humid; + int year; + int month; + int day; + int hour; + bool lower_bound; + bool upper_bound; +} UtilAverageRange; + +/* + * Get the average temp. and humid. between YEAR-MONTH-DAY + COUNT * PERIOD and + * YEAR-MONTH-DAY + (COUNT + 1) * PERIOD. + * Return: false if an error occurred, true otherwise. + */ +bool get_average_for_range(sqlite3 *db, int year, int month, int day, + int64_t count, UtilPeriod period, + UtilAverageRange *data); + #endif