From 29a600475f4716e77dc822e55396f10e1402fb80 Mon Sep 17 00:00:00 2001 From: Jakob Krantz Date: Thu, 17 Oct 2024 23:24:08 +0200 Subject: [PATCH 1/8] Fitness App: Basic daily steps chart with history. --- app/src/applications/fitness/fitness_app.c | 98 +++++++++++++++++++++- app/src/applications/fitness/fitness_ui.c | 50 ++++++++++- app/src/applications/fitness/fitness_ui.h | 4 +- app/src/zsw_clock.c | 13 +++ app/src/zsw_clock.h | 12 ++- 5 files changed, 172 insertions(+), 5 deletions(-) diff --git a/app/src/applications/fitness/fitness_app.c b/app/src/applications/fitness/fitness_app.c index f7f9af03..1facd194 100644 --- a/app/src/applications/fitness/fitness_app.c +++ b/app/src/applications/fitness/fitness_app.c @@ -9,21 +9,38 @@ #include "sensors/zsw_imu.h" #include "zsw_clock.h" #include "zsw_alarm.h" +#include "history/zsw_history.h" +#include "zsw_clock.h" LOG_MODULE_REGISTER(fitness_app, LOG_LEVEL_DBG); #define STEP_RESET_COUNTER_INTERVAL_S 50 +#define SETTING_BATTERY_HIST "fitness/step/hist" +#define SAMPLE_INTERVAL_MIN 60 +#define SAMPLE_INTERVAL_MS (SAMPLE_INTERVAL_MIN * 60 * 1000) +#define MAX_SAMPLES (7 * 24) // One week of hourly samples + +typedef struct { + zsw_timeval_t time; + uint32_t steps; +} zsw_step_sample_t; + static void fitness_app_start(lv_obj_t *root, lv_group_t *group); static void fitness_app_stop(void); +static void step_sample_work(struct k_work *work); + static application_t app = { .name = "Fitness", .start_func = fitness_app_start, .stop_func = fitness_app_stop, - .hidden = true, }; +static zsw_history_t fitness_history_context; +static zsw_step_sample_t samples[MAX_SAMPLES]; +K_WORK_DELAYABLE_DEFINE(step_work, step_sample_work); + ZBUS_CHAN_DECLARE(accel_data_chan); #ifndef CONFIG_RTC static void step_work_callback(struct k_work *work); @@ -57,9 +74,46 @@ static void step_work_callback(struct k_work *work) } } +static void step_sample_work(struct k_work *work) +{ + zsw_step_sample_t sample; + int next_sample_seconds; + + if (zsw_imu_fetch_num_steps(&sample.steps) != 0) { +#ifdef CONFIG_BOARD_NATIVE_POSIX + sample.steps = k_uptime_get_32() % 100; +#else + LOG_WRN("Error during fetching of steps!"); + return; +#endif + } + zsw_clock_get_time(&sample.time); + zsw_history_add(&fitness_history_context, &sample); + if (zsw_history_save(&fitness_history_context)) { + LOG_ERR("Error during saving of step samples!"); + } + LOG_DBG("______STEP HIST ADD________"); + LOG_DBG("Step sample hist add: %d", sample.steps); + LOG_DBG("Time: %d:%d:%d", sample.time.tm.tm_hour, sample.time.tm.tm_min, sample.time.tm.tm_sec); + next_sample_seconds = 60 * (SAMPLE_INTERVAL_MIN - sample.time.tm.tm_min) - sample.time.tm.tm_sec; + LOG_DBG("Next sample in %d:%d", next_sample_seconds / 60, next_sample_seconds % 60); + //next_sample_seconds = 5; + k_work_reschedule(&step_work, K_SECONDS(next_sample_seconds)); +} + static void fitness_app_start(lv_obj_t *root, lv_group_t *group) { - fitness_ui_show(root); + zsw_step_sample_t sample; + fitness_ui_show(root, MAX_SAMPLES); + uint16_t steps[MAX_SAMPLES]; + int num_steps_hist = zsw_history_samples(&fitness_history_context); + + for (int i = 0; i < num_steps_hist; i++) { + zsw_history_get(&fitness_history_context, &sample, i); + LOG_DBG("Add step[%d]: %d", i, sample.steps); + steps[i] = sample.steps; + } + fitness_ui_set_weekly_steps(steps, num_steps_hist); } static void fitness_app_stop(void) @@ -69,6 +123,12 @@ static void fitness_app_stop(void) static int fitness_app_add(void) { + struct tm tm; + struct tm prev_tm; + int num_hist_samples; + zsw_timeval_t time; + zsw_step_sample_t sample; + int next_sample_seconds = 0; zsw_app_manager_add_application(&app); #ifdef CONFIG_RTC @@ -82,6 +142,40 @@ static int fitness_app_add(void) k_work_reschedule(&step_work, K_SECONDS(STEP_RESET_COUNTER_INTERVAL_S)); #endif + zsw_history_init(&fitness_history_context, MAX_SAMPLES, sizeof(zsw_step_sample_t), samples, SETTING_BATTERY_HIST); + + if (zsw_history_load(&fitness_history_context)) { + LOG_ERR("Error during settings_load_subtree!"); + return -EFAULT; + } + + num_hist_samples = zsw_history_samples(&fitness_history_context); + + zsw_clock_get_time(&time); + + // Print curent minute and second + LOG_DBG("fitness_app_add time: %d:%d:%d", time.tm.tm_hour, time.tm.tm_min, time.tm.tm_sec); + + if (num_hist_samples == 0) { + // Try to sample about every full hour + next_sample_seconds = 60 * (SAMPLE_INTERVAL_MIN - time.tm.tm_min) - time.tm.tm_sec; + } else { + zsw_history_get(&fitness_history_context, &sample, zsw_history_samples(&fitness_history_context) - 1); + zsw_timeval_to_tm(&time, &tm); + zsw_timeval_to_tm(&sample.time, &prev_tm); + time_t time_now = mktime(&tm); + time_t time_last_sample = mktime(&prev_tm); + __ASSERT(time_now != -1, "Invalid time_now!"); + __ASSERT(time_last_sample != -1, "Invalid time_last_sample!"); + + double difference = difftime(time_now, time_last_sample); + __ASSERT(difference >= 0, "Time difference is negative!"); + next_sample_seconds = MIN(3600, 3600 - difference); + } + LOG_DBG("Next sample in %d:%d", next_sample_seconds / 60, next_sample_seconds % 60); + //next_sample_seconds = 10; + k_work_schedule(&step_work, K_SECONDS(next_sample_seconds)); + return 0; } diff --git a/app/src/applications/fitness/fitness_ui.c b/app/src/applications/fitness/fitness_ui.c index e32b9267..f6ade7d7 100644 --- a/app/src/applications/fitness/fitness_ui.c +++ b/app/src/applications/fitness/fitness_ui.c @@ -1,9 +1,46 @@ #include "fitness_ui.h" #include "ui/zsw_ui.h" +#include static lv_obj_t *root_page = NULL; +static lv_obj_t *ui_title_label = NULL; +static lv_obj_t *ui_weekly_chart = NULL; +static lv_chart_series_t *ui_weekly_chart_series_1 = NULL; -void fitness_ui_show(lv_obj_t *root) +static void create_step_chart(lv_obj_t *ui_root_container, uint16_t max_samples) +{ + //lv_obj_remove_style_all(ui_root_container); + lv_obj_set_width(ui_root_container, lv_pct(100)); + lv_obj_set_height(ui_root_container, lv_pct(100)); + lv_obj_set_align(ui_root_container, LV_ALIGN_CENTER); + lv_obj_clear_flag(ui_root_container, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + + ui_title_label = lv_label_create(ui_root_container); + lv_obj_set_width(ui_title_label, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_title_label, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_title_label, 0); + lv_obj_set_y(ui_title_label, -90); + lv_obj_set_align(ui_title_label, LV_ALIGN_CENTER); + lv_label_set_text(ui_title_label, "Steps last 7 days"); + + ui_weekly_chart = lv_chart_create(ui_root_container); + lv_obj_set_width(ui_weekly_chart, 150); + lv_obj_set_height(ui_weekly_chart, 127); + lv_obj_set_x(ui_weekly_chart, 20); + lv_obj_set_y(ui_weekly_chart, 0); + lv_obj_set_align(ui_weekly_chart, LV_ALIGN_CENTER); + lv_chart_set_type(ui_weekly_chart, LV_CHART_TYPE_BAR); + lv_chart_set_point_count(ui_weekly_chart, max_samples); + lv_chart_set_range(ui_weekly_chart, LV_CHART_AXIS_PRIMARY_Y, 0, 100); + lv_chart_set_range(ui_weekly_chart, LV_CHART_AXIS_SECONDARY_Y, 0, 0); + lv_chart_set_div_line_count(ui_weekly_chart, 0, 0); + lv_chart_set_axis_tick(ui_weekly_chart, LV_CHART_AXIS_PRIMARY_X, 5, 1, 7, 1, true, 50); + lv_chart_set_axis_tick(ui_weekly_chart, LV_CHART_AXIS_PRIMARY_Y, 5, 1, 5, 1, true, 50); + lv_chart_set_axis_tick(ui_weekly_chart, LV_CHART_AXIS_SECONDARY_Y, 1, 1, 1, 1, false, 25); + ui_weekly_chart_series_1 = lv_chart_add_series(ui_weekly_chart, lv_color_hex(0x808080), LV_CHART_AXIS_PRIMARY_Y); +} + +void fitness_ui_show(lv_obj_t *root, uint16_t max_samples) { assert(root_page == NULL); @@ -14,6 +51,17 @@ void fitness_ui_show(lv_obj_t *root) lv_obj_set_width(root_page, lv_pct(100)); lv_obj_set_height(root_page, lv_pct(100)); lv_obj_set_align(root_page, LV_ALIGN_CENTER); + + create_step_chart(root_page, max_samples); +} + +void fitness_ui_set_weekly_steps(uint16_t *samples, uint16_t num_samples) +{ + assert(ui_weekly_chart_series_1 != NULL); + + for (int i = 0; i < num_samples; i++) { + lv_chart_set_next_value(ui_weekly_chart, ui_weekly_chart_series_1, samples[i]); + } } void fitness_ui_remove(void) diff --git a/app/src/applications/fitness/fitness_ui.h b/app/src/applications/fitness/fitness_ui.h index 741d8d74..f5a2c032 100644 --- a/app/src/applications/fitness/fitness_ui.h +++ b/app/src/applications/fitness/fitness_ui.h @@ -3,6 +3,8 @@ #include #include -void fitness_ui_show(lv_obj_t *root); +void fitness_ui_show(lv_obj_t *root, uint16_t max_samples); + +void fitness_ui_set_weekly_steps(uint16_t *samples, uint16_t num_samples); void fitness_ui_remove(void); diff --git a/app/src/zsw_clock.c b/app/src/zsw_clock.c index c8610eb5..4a4c5677 100644 --- a/app/src/zsw_clock.c +++ b/app/src/zsw_clock.c @@ -113,6 +113,19 @@ void zsw_clock_set_timezone(char *tz) } } +void zsw_timeval_to_tm(zsw_timeval_t *ztm, struct tm *tm) +{ + tm->tm_sec = ztm->tm.tm_sec; + tm->tm_min = ztm->tm.tm_min; + tm->tm_hour = ztm->tm.tm_hour; + tm->tm_mday = ztm->tm.tm_mday; + tm->tm_mon = ztm->tm.tm_mon; + tm->tm_year = ztm->tm.tm_year - 1900; + tm->tm_wday = ztm->tm.tm_wday; + tm->tm_yday = ztm->tm.tm_yday; + tm->tm_isdst = ztm->tm.tm_isdst; +} + static int zsw_clock_init(void) { #if CONFIG_RTC diff --git a/app/src/zsw_clock.h b/app/src/zsw_clock.h index de51abe6..71dc04ce 100644 --- a/app/src/zsw_clock.h +++ b/app/src/zsw_clock.h @@ -59,4 +59,14 @@ void zsw_clock_get_time(zsw_timeval_t *ztm); * * @param tz The timezone string to set. */ -void zsw_clock_set_timezone(char *tz); \ No newline at end of file +void zsw_clock_set_timezone(char *tz); + +/** + * @brief Converts a zsw_timeval_t structure to a struct tm structure. + * + * This function converts a zsw_timeval_t structure to a struct tm structure. + * + * @param ztm Pointer to the zsw_timeval_t structure to convert. + * @param tm Pointer to the struct tm structure where the converted time will be stored. + */ +void zsw_timeval_to_tm(zsw_timeval_t *ztm, struct tm *tm); From 22561adb524867813217ca8225a98c51ee433d79 Mon Sep 17 00:00:00 2001 From: Jakob Krantz Date: Mon, 21 Oct 2024 23:07:42 +0200 Subject: [PATCH 2/8] Fitness App: Redesign on chart and add arc showing step goal progress. --- app/src/applications/fitness/fitness_app.c | 6 +- app/src/applications/fitness/fitness_ui.c | 106 ++++++++++++++++----- 2 files changed, 89 insertions(+), 23 deletions(-) diff --git a/app/src/applications/fitness/fitness_app.c b/app/src/applications/fitness/fitness_app.c index 1facd194..ed36836b 100644 --- a/app/src/applications/fitness/fitness_app.c +++ b/app/src/applications/fitness/fitness_app.c @@ -10,6 +10,7 @@ #include "zsw_clock.h" #include "zsw_alarm.h" #include "history/zsw_history.h" +#include "ui/zsw_ui.h" #include "zsw_clock.h" LOG_MODULE_REGISTER(fitness_app, LOG_LEVEL_DBG); @@ -31,10 +32,13 @@ static void fitness_app_stop(void); static void step_sample_work(struct k_work *work); +ZSW_LV_IMG_DECLARE(move); + static application_t app = { .name = "Fitness", .start_func = fitness_app_start, .stop_func = fitness_app_stop, + .icon = ZSW_LV_IMG_USE(move), }; static zsw_history_t fitness_history_context; @@ -81,7 +85,7 @@ static void step_sample_work(struct k_work *work) if (zsw_imu_fetch_num_steps(&sample.steps) != 0) { #ifdef CONFIG_BOARD_NATIVE_POSIX - sample.steps = k_uptime_get_32() % 100; + sample.steps = rand() % 10000; #else LOG_WRN("Error during fetching of steps!"); return; diff --git a/app/src/applications/fitness/fitness_ui.c b/app/src/applications/fitness/fitness_ui.c index f6ade7d7..918c2bf4 100644 --- a/app/src/applications/fitness/fitness_ui.c +++ b/app/src/applications/fitness/fitness_ui.c @@ -3,41 +3,100 @@ #include static lv_obj_t *root_page = NULL; -static lv_obj_t *ui_title_label = NULL; +static lv_obj_t *ui_step_progress_label = NULL; static lv_obj_t *ui_weekly_chart = NULL; +static lv_obj_t *ui_step_goal_arc = NULL; static lv_chart_series_t *ui_weekly_chart_series_1 = NULL; static void create_step_chart(lv_obj_t *ui_root_container, uint16_t max_samples) { - //lv_obj_remove_style_all(ui_root_container); - lv_obj_set_width(ui_root_container, lv_pct(100)); - lv_obj_set_height(ui_root_container, lv_pct(100)); - lv_obj_set_align(ui_root_container, LV_ALIGN_CENTER); - lv_obj_clear_flag(ui_root_container, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags - - ui_title_label = lv_label_create(ui_root_container); - lv_obj_set_width(ui_title_label, LV_SIZE_CONTENT); /// 1 - lv_obj_set_height(ui_title_label, LV_SIZE_CONTENT); /// 1 - lv_obj_set_x(ui_title_label, 0); - lv_obj_set_y(ui_title_label, -90); - lv_obj_set_align(ui_title_label, LV_ALIGN_CENTER); - lv_label_set_text(ui_title_label, "Steps last 7 days"); + lv_obj_t *ui_step_goal_chart_line = lv_obj_create(ui_root_container); + lv_obj_set_height(ui_step_goal_chart_line, 5); + lv_obj_set_width(ui_step_goal_chart_line, lv_pct(100)); + lv_obj_set_x(ui_step_goal_chart_line, 0); + lv_obj_set_y(ui_step_goal_chart_line, -27); + lv_obj_set_align(ui_step_goal_chart_line, LV_ALIGN_CENTER); + lv_obj_clear_flag(ui_step_goal_chart_line, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + lv_obj_set_style_bg_color(ui_step_goal_chart_line, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_step_goal_chart_line, 50, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_blend_mode(ui_step_goal_chart_line, LV_BLEND_MODE_NORMAL, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_opa(ui_step_goal_chart_line, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_step_goal_chart_line, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_step_goal_chart_line, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_step_goal_chart_line, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_step_goal_chart_line, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_step_progress_label = lv_label_create(ui_root_container); + lv_obj_set_width(ui_step_progress_label, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_step_progress_label, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_step_progress_label, 0); + lv_obj_set_y(ui_step_progress_label, -90); + lv_obj_set_align(ui_step_progress_label, LV_ALIGN_CENTER); + lv_label_set_text(ui_step_progress_label, "1234/10000"); ui_weekly_chart = lv_chart_create(ui_root_container); - lv_obj_set_width(ui_weekly_chart, 150); + lv_obj_set_width(ui_weekly_chart, 231); lv_obj_set_height(ui_weekly_chart, 127); - lv_obj_set_x(ui_weekly_chart, 20); - lv_obj_set_y(ui_weekly_chart, 0); + lv_obj_set_x(ui_weekly_chart, 0); + lv_obj_set_y(ui_weekly_chart, 10); lv_obj_set_align(ui_weekly_chart, LV_ALIGN_CENTER); lv_chart_set_type(ui_weekly_chart, LV_CHART_TYPE_BAR); - lv_chart_set_point_count(ui_weekly_chart, max_samples); - lv_chart_set_range(ui_weekly_chart, LV_CHART_AXIS_PRIMARY_Y, 0, 100); + lv_chart_set_point_count(ui_weekly_chart, 7); + lv_chart_set_range(ui_weekly_chart, LV_CHART_AXIS_PRIMARY_Y, 0, 12000); lv_chart_set_range(ui_weekly_chart, LV_CHART_AXIS_SECONDARY_Y, 0, 0); lv_chart_set_div_line_count(ui_weekly_chart, 0, 0); - lv_chart_set_axis_tick(ui_weekly_chart, LV_CHART_AXIS_PRIMARY_X, 5, 1, 7, 1, true, 50); - lv_chart_set_axis_tick(ui_weekly_chart, LV_CHART_AXIS_PRIMARY_Y, 5, 1, 5, 1, true, 50); + lv_chart_set_axis_tick(ui_weekly_chart, LV_CHART_AXIS_PRIMARY_X, 1, 1, 1, 1, false, 50); + lv_chart_set_axis_tick(ui_weekly_chart, LV_CHART_AXIS_PRIMARY_Y, 1, 1, 1, 1, false, 50); lv_chart_set_axis_tick(ui_weekly_chart, LV_CHART_AXIS_SECONDARY_Y, 1, 1, 1, 1, false, 25); - ui_weekly_chart_series_1 = lv_chart_add_series(ui_weekly_chart, lv_color_hex(0x808080), LV_CHART_AXIS_PRIMARY_Y); + ui_weekly_chart_series_1 = lv_chart_add_series(ui_weekly_chart, lv_color_hex(0x9E3939), + LV_CHART_AXIS_PRIMARY_Y); + + lv_obj_set_style_bg_color(ui_weekly_chart, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_weekly_chart, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_color(ui_weekly_chart, lv_color_hex(0x000000), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_opa(ui_weekly_chart, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(ui_weekly_chart, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_step_goal_arc = lv_arc_create(ui_root_container); + lv_obj_set_width(ui_step_goal_arc, lv_pct(100)); + lv_obj_set_height(ui_step_goal_arc, lv_pct(100)); + lv_obj_set_align(ui_step_goal_arc, LV_ALIGN_CENTER); + lv_obj_clear_flag(ui_step_goal_arc, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | + LV_OBJ_FLAG_CLICK_FOCUSABLE); /// Flags + lv_arc_set_range(ui_step_goal_arc, 0, 10000); + lv_arc_set_value(ui_step_goal_arc, 2000); + lv_arc_set_bg_angles(ui_step_goal_arc, 0, 359); + lv_arc_set_rotation(ui_step_goal_arc, 270); + lv_obj_set_style_pad_left(ui_step_goal_arc, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_step_goal_arc, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_step_goal_arc, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_step_goal_arc, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_row(ui_step_goal_arc, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_column(ui_step_goal_arc, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_arc_color(ui_step_goal_arc, lv_color_hex(0x5F6571), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_arc_opa(ui_step_goal_arc, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_arc_width(ui_step_goal_arc, 4, LV_PART_MAIN | LV_STATE_DEFAULT); + + lv_obj_set_style_pad_left(ui_step_goal_arc, 0, LV_PART_INDICATOR | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_step_goal_arc, 0, LV_PART_INDICATOR | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_step_goal_arc, 0, LV_PART_INDICATOR | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_step_goal_arc, 0, LV_PART_INDICATOR | LV_STATE_DEFAULT); + lv_obj_set_style_pad_row(ui_step_goal_arc, 0, LV_PART_INDICATOR | LV_STATE_DEFAULT); + lv_obj_set_style_pad_column(ui_step_goal_arc, 0, LV_PART_INDICATOR | LV_STATE_DEFAULT); + lv_obj_set_style_arc_color(ui_step_goal_arc, lv_color_hex(0x00921A), LV_PART_INDICATOR | LV_STATE_DEFAULT); + lv_obj_set_style_arc_opa(ui_step_goal_arc, 255, LV_PART_INDICATOR | LV_STATE_DEFAULT); + lv_obj_set_style_arc_width(ui_step_goal_arc, 4, LV_PART_INDICATOR | LV_STATE_DEFAULT); + + lv_obj_set_style_bg_color(ui_step_goal_arc, lv_color_hex(0xFFFFFF), LV_PART_KNOB | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_step_goal_arc, 0, LV_PART_KNOB | LV_STATE_DEFAULT); + + lv_obj_t *ui_last_7_days_title_label = lv_label_create(ui_root_container); + lv_obj_set_width(ui_last_7_days_title_label, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_last_7_days_title_label, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_last_7_days_title_label, 0); + lv_obj_set_y(ui_last_7_days_title_label, -20); + lv_obj_set_align(ui_last_7_days_title_label, LV_ALIGN_BOTTOM_MID); + lv_label_set_text(ui_last_7_days_title_label, "Last 7 days"); } void fitness_ui_show(lv_obj_t *root, uint16_t max_samples) @@ -51,6 +110,7 @@ void fitness_ui_show(lv_obj_t *root, uint16_t max_samples) lv_obj_set_width(root_page, lv_pct(100)); lv_obj_set_height(root_page, lv_pct(100)); lv_obj_set_align(root_page, LV_ALIGN_CENTER); + lv_obj_set_style_pad_all(root_page, 0, LV_PART_MAIN); create_step_chart(root_page, max_samples); } @@ -62,6 +122,8 @@ void fitness_ui_set_weekly_steps(uint16_t *samples, uint16_t num_samples) for (int i = 0; i < num_samples; i++) { lv_chart_set_next_value(ui_weekly_chart, ui_weekly_chart_series_1, samples[i]); } + lv_label_set_text_fmt(ui_step_progress_label, "%d / %d", samples[num_samples - 1], 10000); + lv_arc_set_value(ui_step_goal_arc, samples[num_samples - 1]); } void fitness_ui_remove(void) From 6f2d3af1fbd5b8bb24e7e06100861515cd87685d Mon Sep 17 00:00:00 2001 From: Jakob Krantz Date: Tue, 22 Oct 2024 21:57:12 +0200 Subject: [PATCH 3/8] zsw_imu: Add step counter offset set. If watch is reset the BMI270 step counter is reset. Add function to insert an offset making the fetch_num_steps function return for example the steps today. --- app/src/applications/fitness/fitness_app.c | 7 ++++++- app/src/sensors/zsw_imu.c | 10 +++++++++- app/src/sensors/zsw_imu.h | 7 +++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/app/src/applications/fitness/fitness_app.c b/app/src/applications/fitness/fitness_app.c index ed36836b..39c705b2 100644 --- a/app/src/applications/fitness/fitness_app.c +++ b/app/src/applications/fitness/fitness_app.c @@ -85,7 +85,7 @@ static void step_sample_work(struct k_work *work) if (zsw_imu_fetch_num_steps(&sample.steps) != 0) { #ifdef CONFIG_BOARD_NATIVE_POSIX - sample.steps = rand() % 10000; + sample.steps = rand() % 1000; #else LOG_WRN("Error during fetching of steps!"); return; @@ -157,6 +157,11 @@ static int fitness_app_add(void) zsw_clock_get_time(&time); + // If watch was reset the step counter restarts at 0, so we need to update the offset. + if (num_hist_samples > 0 && time.tm.tm_mday == samples[num_hist_samples - 1].time.tm.tm_mday) { + zsw_imu_set_step_offset(samples[num_hist_samples - 1].steps); + } + // Print curent minute and second LOG_DBG("fitness_app_add time: %d:%d:%d", time.tm.tm_hour, time.tm.tm_min, time.tm.tm_sec); diff --git a/app/src/sensors/zsw_imu.c b/app/src/sensors/zsw_imu.c index a1ca5bac..00b38ff2 100644 --- a/app/src/sensors/zsw_imu.c +++ b/app/src/sensors/zsw_imu.c @@ -30,6 +30,7 @@ LOG_MODULE_REGISTER(zsw_imu, CONFIG_ZSW_SENSORS_LOG_LEVEL); ZBUS_CHAN_DECLARE(accel_data_chan); static const struct device *const bmi270 = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(bmi270)); static struct sensor_trigger bmi270_trigger; +static int step_offset = 0; static void bmi270_trigger_handler(const struct device *dev, const struct sensor_trigger *trig) { @@ -249,11 +250,17 @@ int zsw_imu_fetch_num_steps(uint32_t *num_steps) return -ENODATA; } - *num_steps = sensor_val.val1; + *num_steps = sensor_val.val1 + step_offset; return 0; } +int zsw_imu_set_step_offset(int starting_step_offset) +{ + step_offset = starting_step_offset; + return 0; +} + int zsw_imu_fetch_temperature(float *temperature) { struct sensor_value sensor_val; @@ -283,6 +290,7 @@ int zsw_imu_reset_step_count(void) return -ENODEV; } + step_offset = 0; value.val1 = 0; value.val2 = 0; diff --git a/app/src/sensors/zsw_imu.h b/app/src/sensors/zsw_imu.h index 73452043..39101150 100644 --- a/app/src/sensors/zsw_imu.h +++ b/app/src/sensors/zsw_imu.h @@ -102,6 +102,13 @@ int zsw_imu_fetch_temperature(float *temperature); int zsw_imu_fetch_num_steps(uint32_t *num_steps); +/* +* Set an offset when fetching the number of steps. +* This can be used if zsw_imu_fetch_num_steps should +* return daily steps. +*/ +int zsw_imu_set_step_offset(int starting_step_offset); + int zsw_imu_reset_step_count(void); int zsw_imu_feature_disable(zsw_imu_feature_t feature); From d2803bb92becde313541b9f1441a793d033900f5 Mon Sep 17 00:00:00 2001 From: Jakob Krantz Date: Tue, 22 Oct 2024 21:58:00 +0200 Subject: [PATCH 4/8] Open Fitness App when watchface step counter arc/label is clicked on. --- app/src/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main.c b/app/src/main.c index 0f1c7796..61915615 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -529,6 +529,7 @@ static void on_watchface_app_event_callback(watchface_app_evt_t evt) open_application_manager_page("Battery"); break; case WATCHFACE_APP_EVT_CLICK_STEP: + open_application_manager_page("Fitness"); break; case WATCHFACE_APP_EVT_CLICK_WEATHER: break; From ebd39fd81d63b0281725d11372401577445e117c Mon Sep 17 00:00:00 2001 From: Jakob Krantz Date: Tue, 22 Oct 2024 22:34:14 +0200 Subject: [PATCH 5/8] Fitness App: Calculate the daily steps for 1 week and plot them weekly. --- app/src/applications/fitness/fitness_app.c | 42 ++++++++++++++++------ 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/app/src/applications/fitness/fitness_app.c b/app/src/applications/fitness/fitness_app.c index 39c705b2..18e6f485 100644 --- a/app/src/applications/fitness/fitness_app.c +++ b/app/src/applications/fitness/fitness_app.c @@ -15,7 +15,8 @@ LOG_MODULE_REGISTER(fitness_app, LOG_LEVEL_DBG); -#define STEP_RESET_COUNTER_INTERVAL_S 50 +#define STEP_RESET_COUNTER_INTERVAL_S 50 +#define DAYS_IN_WEEK 7 #define SETTING_BATTERY_HIST "fitness/step/hist" #define SAMPLE_INTERVAL_MIN 60 @@ -105,19 +106,40 @@ static void step_sample_work(struct k_work *work) k_work_reschedule(&step_work, K_SECONDS(next_sample_seconds)); } -static void fitness_app_start(lv_obj_t *root, lv_group_t *group) +static void get_steps_per_day(uint16_t weekdays[DAYS_IN_WEEK]) { - zsw_step_sample_t sample; - fitness_ui_show(root, MAX_SAMPLES); - uint16_t steps[MAX_SAMPLES]; - int num_steps_hist = zsw_history_samples(&fitness_history_context); + int day; + int num_samples = zsw_history_samples(&fitness_history_context); - for (int i = 0; i < num_steps_hist; i++) { + for (int i = 0; i < num_samples; i++) { + zsw_step_sample_t sample; zsw_history_get(&fitness_history_context, &sample, i); - LOG_DBG("Add step[%d]: %d", i, sample.steps); - steps[i] = sample.steps; + day = sample.time.tm.tm_wday; + printk("Day: %d, Steps: %d\n", day, sample.steps); + weekdays[day] = MAX(sample.steps, weekdays[day]); } - fitness_ui_set_weekly_steps(steps, num_steps_hist); +} + +static void fitness_app_start(lv_obj_t *root, lv_group_t *group) +{ + zsw_timeval_t time; + uint16_t step_weekdays[DAYS_IN_WEEK] = {0}; + + zsw_clock_get_time(&time); + get_steps_per_day(step_weekdays); + + // Rotate the array (left/counter-clockwise) so the last element is the current day + int shifts = time.tm.tm_wday + 1; + for (int i = 0; i < shifts; i++) { + int first = step_weekdays[0]; + for (int j = 0; j < DAYS_IN_WEEK - 1; j++) { + step_weekdays[j] = step_weekdays[j + 1]; + } + step_weekdays[DAYS_IN_WEEK - 1] = first; + } + + fitness_ui_show(root, DAYS_IN_WEEK); + fitness_ui_set_weekly_steps(step_weekdays, DAYS_IN_WEEK); } static void fitness_app_stop(void) From 05fc4a7f472a9abcf14997c4f1f9994952626a35 Mon Sep 17 00:00:00 2001 From: Jakob Krantz Date: Wed, 23 Oct 2024 23:06:13 +0200 Subject: [PATCH 6/8] Fitness App: Draw weekdays above chart bars and draw step value when clicking on a bar in the chart. --- app/src/applications/fitness/fitness_app.c | 17 ++-- app/src/applications/fitness/fitness_ui.c | 100 +++++++++++++++++---- 2 files changed, 92 insertions(+), 25 deletions(-) diff --git a/app/src/applications/fitness/fitness_app.c b/app/src/applications/fitness/fitness_app.c index 18e6f485..fc5b5893 100644 --- a/app/src/applications/fitness/fitness_app.c +++ b/app/src/applications/fitness/fitness_app.c @@ -24,7 +24,7 @@ LOG_MODULE_REGISTER(fitness_app, LOG_LEVEL_DBG); #define MAX_SAMPLES (7 * 24) // One week of hourly samples typedef struct { - zsw_timeval_t time; + zsw_timeval_t time; // TODO optimize size as NVS settings backend can do < 4096 bytes per store uint32_t steps; } zsw_step_sample_t; @@ -44,7 +44,8 @@ static application_t app = { static zsw_history_t fitness_history_context; static zsw_step_sample_t samples[MAX_SAMPLES]; -K_WORK_DELAYABLE_DEFINE(step_work, step_sample_work); + +K_WORK_DELAYABLE_DEFINE(sample_step_work, step_sample_work); ZBUS_CHAN_DECLARE(accel_data_chan); #ifndef CONFIG_RTC @@ -102,8 +103,8 @@ static void step_sample_work(struct k_work *work) LOG_DBG("Time: %d:%d:%d", sample.time.tm.tm_hour, sample.time.tm.tm_min, sample.time.tm.tm_sec); next_sample_seconds = 60 * (SAMPLE_INTERVAL_MIN - sample.time.tm.tm_min) - sample.time.tm.tm_sec; LOG_DBG("Next sample in %d:%d", next_sample_seconds / 60, next_sample_seconds % 60); - //next_sample_seconds = 5; - k_work_reschedule(&step_work, K_SECONDS(next_sample_seconds)); + //next_sample_seconds = 10; + k_work_reschedule(&sample_step_work, K_SECONDS(next_sample_seconds)); } static void get_steps_per_day(uint16_t weekdays[DAYS_IN_WEEK]) @@ -115,7 +116,7 @@ static void get_steps_per_day(uint16_t weekdays[DAYS_IN_WEEK]) zsw_step_sample_t sample; zsw_history_get(&fitness_history_context, &sample, i); day = sample.time.tm.tm_wday; - printk("Day: %d, Steps: %d\n", day, sample.steps); + printk("Day: %d, HH: %d, Steps: %d\n", day, sample.time.tm.tm_hour, sample.steps); weekdays[day] = MAX(sample.steps, weekdays[day]); } } @@ -138,6 +139,10 @@ static void fitness_app_start(lv_obj_t *root, lv_group_t *group) step_weekdays[DAYS_IN_WEEK - 1] = first; } + for (int i = 0; i < DAYS_IN_WEEK; i++) { + printk("Day %d: %d\n", i, step_weekdays[i]); + } + fitness_ui_show(root, DAYS_IN_WEEK); fitness_ui_set_weekly_steps(step_weekdays, DAYS_IN_WEEK); } @@ -205,7 +210,7 @@ static int fitness_app_add(void) } LOG_DBG("Next sample in %d:%d", next_sample_seconds / 60, next_sample_seconds % 60); //next_sample_seconds = 10; - k_work_schedule(&step_work, K_SECONDS(next_sample_seconds)); + k_work_schedule(&sample_step_work, K_SECONDS(next_sample_seconds)); return 0; } diff --git a/app/src/applications/fitness/fitness_ui.c b/app/src/applications/fitness/fitness_ui.c index 918c2bf4..daf99195 100644 --- a/app/src/applications/fitness/fitness_ui.c +++ b/app/src/applications/fitness/fitness_ui.c @@ -8,41 +8,101 @@ static lv_obj_t *ui_weekly_chart = NULL; static lv_obj_t *ui_step_goal_arc = NULL; static lv_chart_series_t *ui_weekly_chart_series_1 = NULL; -static void create_step_chart(lv_obj_t *ui_root_container, uint16_t max_samples) +static void event_cb(lv_event_t *e) { - lv_obj_t *ui_step_goal_chart_line = lv_obj_create(ui_root_container); - lv_obj_set_height(ui_step_goal_chart_line, 5); - lv_obj_set_width(ui_step_goal_chart_line, lv_pct(100)); - lv_obj_set_x(ui_step_goal_chart_line, 0); - lv_obj_set_y(ui_step_goal_chart_line, -27); - lv_obj_set_align(ui_step_goal_chart_line, LV_ALIGN_CENTER); - lv_obj_clear_flag(ui_step_goal_chart_line, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags - lv_obj_set_style_bg_color(ui_step_goal_chart_line, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); - lv_obj_set_style_bg_opa(ui_step_goal_chart_line, 50, LV_PART_MAIN | LV_STATE_DEFAULT); - lv_obj_set_style_blend_mode(ui_step_goal_chart_line, LV_BLEND_MODE_NORMAL, LV_PART_MAIN | LV_STATE_DEFAULT); - lv_obj_set_style_opa(ui_step_goal_chart_line, 255, LV_PART_MAIN | LV_STATE_DEFAULT); - lv_obj_set_style_pad_left(ui_step_goal_chart_line, 0, LV_PART_MAIN | LV_STATE_DEFAULT); - lv_obj_set_style_pad_right(ui_step_goal_chart_line, 0, LV_PART_MAIN | LV_STATE_DEFAULT); - lv_obj_set_style_pad_top(ui_step_goal_chart_line, 0, LV_PART_MAIN | LV_STATE_DEFAULT); - lv_obj_set_style_pad_bottom(ui_step_goal_chart_line, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t *chart = lv_event_get_target(e); + + if (code == LV_EVENT_VALUE_CHANGED) { + lv_obj_invalidate(chart); + } else if (code == LV_EVENT_DRAW_POST_END) { + lv_chart_series_t *ser = lv_chart_get_series_next(chart, NULL); + if (!ser) { + return; + } + int num_points = lv_chart_get_point_count(chart); + + static char *weekdays[] = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}; // TODO correct order ha to come from fitness_app.c + for (int id = 0; id < num_points; id++) { + lv_point_t p; + lv_chart_get_point_pos_by_id(chart, ser, id, &p); + + char buf[16]; + lv_snprintf(buf, sizeof(buf), LV_SYMBOL_DUMMY"%s", weekdays[id]); + + lv_draw_rect_dsc_t draw_rect_dsc; + lv_draw_rect_dsc_init(&draw_rect_dsc); + draw_rect_dsc.bg_color = lv_color_black(); + draw_rect_dsc.bg_opa = LV_OPA_50; + draw_rect_dsc.radius = 3; + draw_rect_dsc.bg_img_src = buf; + draw_rect_dsc.bg_img_recolor = lv_color_white(); + + lv_area_t a; + a.x1 = chart->coords.x1 + p.x - 20; + a.x2 = chart->coords.x1 + p.x + 20; + a.y1 = chart->coords.y1 + 20 - 30; + a.y2 = chart->coords.y1 + 20 - 10; + + lv_draw_ctx_t *draw_ctx = lv_event_get_draw_ctx(e); + lv_draw_rect(draw_ctx, &draw_rect_dsc, &a); + } + + static int32_t id = LV_CHART_POINT_NONE; + if (lv_chart_get_pressed_point(chart) != LV_CHART_POINT_NONE) { + id = lv_chart_get_pressed_point(chart); + } + if (id != LV_CHART_POINT_NONE) { + // Draw a rectangle with the value of the point + // Keep point value drawn until another bar is clicked. + lv_point_t p; + lv_chart_get_point_pos_by_id(chart, ser, id, &p); + + lv_coord_t *y_array = lv_chart_get_y_array(chart, ser); + lv_coord_t value = y_array[id]; + + char buf[16]; + lv_snprintf(buf, sizeof(buf), LV_SYMBOL_DUMMY"%d", value); + + lv_draw_rect_dsc_t draw_rect_dsc; + lv_draw_rect_dsc_init(&draw_rect_dsc); + draw_rect_dsc.bg_color = lv_color_white(); + draw_rect_dsc.bg_opa = LV_OPA_30; + draw_rect_dsc.radius = 5; + draw_rect_dsc.bg_img_src = buf; + draw_rect_dsc.bg_img_recolor = lv_color_black(); + + lv_area_t a; + a.x1 = chart->coords.x1 + p.x - 20; + a.x2 = chart->coords.x1 + p.x + 20; + a.y1 = chart->coords.y1 + p.y - 30; + a.y2 = chart->coords.y1 + p.y - 10; + + lv_draw_ctx_t *draw_ctx = lv_event_get_draw_ctx(e); + lv_draw_rect(draw_ctx, &draw_rect_dsc, &a); + } + } +} +static void create_step_chart(lv_obj_t *ui_root_container, uint16_t max_samples) +{ ui_step_progress_label = lv_label_create(ui_root_container); lv_obj_set_width(ui_step_progress_label, LV_SIZE_CONTENT); /// 1 lv_obj_set_height(ui_step_progress_label, LV_SIZE_CONTENT); /// 1 lv_obj_set_x(ui_step_progress_label, 0); lv_obj_set_y(ui_step_progress_label, -90); lv_obj_set_align(ui_step_progress_label, LV_ALIGN_CENTER); - lv_label_set_text(ui_step_progress_label, "1234/10000"); + lv_label_set_text(ui_step_progress_label, "- / 10000"); ui_weekly_chart = lv_chart_create(ui_root_container); lv_obj_set_width(ui_weekly_chart, 231); - lv_obj_set_height(ui_weekly_chart, 127); + lv_obj_set_height(ui_weekly_chart, 120); lv_obj_set_x(ui_weekly_chart, 0); lv_obj_set_y(ui_weekly_chart, 10); lv_obj_set_align(ui_weekly_chart, LV_ALIGN_CENTER); lv_chart_set_type(ui_weekly_chart, LV_CHART_TYPE_BAR); lv_chart_set_point_count(ui_weekly_chart, 7); - lv_chart_set_range(ui_weekly_chart, LV_CHART_AXIS_PRIMARY_Y, 0, 12000); + lv_chart_set_range(ui_weekly_chart, LV_CHART_AXIS_PRIMARY_Y, 0, 10000); lv_chart_set_range(ui_weekly_chart, LV_CHART_AXIS_SECONDARY_Y, 0, 0); lv_chart_set_div_line_count(ui_weekly_chart, 0, 0); lv_chart_set_axis_tick(ui_weekly_chart, LV_CHART_AXIS_PRIMARY_X, 1, 1, 1, 1, false, 50); @@ -97,6 +157,8 @@ static void create_step_chart(lv_obj_t *ui_root_container, uint16_t max_samples) lv_obj_set_y(ui_last_7_days_title_label, -20); lv_obj_set_align(ui_last_7_days_title_label, LV_ALIGN_BOTTOM_MID); lv_label_set_text(ui_last_7_days_title_label, "Last 7 days"); + + lv_obj_add_event_cb(ui_weekly_chart, event_cb, LV_EVENT_ALL, NULL); } void fitness_ui_show(lv_obj_t *root, uint16_t max_samples) From ade37ce36dfa6b1284c8214cbd23656c92c5e54f Mon Sep 17 00:00:00 2001 From: Jakob Krantz Date: Thu, 24 Oct 2024 19:08:08 +0200 Subject: [PATCH 7/8] Fitness App: Make the graph look nicer. Update colors. Draws lines between the bars for better visual style. --- app/src/applications/fitness/fitness_app.c | 62 +++++++++++----------- app/src/applications/fitness/fitness_ui.c | 54 +++++++++++++------ app/src/applications/fitness/fitness_ui.h | 2 +- app/src/history/zsw_history.c | 6 ++- 4 files changed, 74 insertions(+), 50 deletions(-) diff --git a/app/src/applications/fitness/fitness_app.c b/app/src/applications/fitness/fitness_app.c index fc5b5893..5fbe0652 100644 --- a/app/src/applications/fitness/fitness_app.c +++ b/app/src/applications/fitness/fitness_app.c @@ -103,7 +103,6 @@ static void step_sample_work(struct k_work *work) LOG_DBG("Time: %d:%d:%d", sample.time.tm.tm_hour, sample.time.tm.tm_min, sample.time.tm.tm_sec); next_sample_seconds = 60 * (SAMPLE_INTERVAL_MIN - sample.time.tm.tm_min) - sample.time.tm.tm_sec; LOG_DBG("Next sample in %d:%d", next_sample_seconds / 60, next_sample_seconds % 60); - //next_sample_seconds = 10; k_work_reschedule(&sample_step_work, K_SECONDS(next_sample_seconds)); } @@ -116,35 +115,52 @@ static void get_steps_per_day(uint16_t weekdays[DAYS_IN_WEEK]) zsw_step_sample_t sample; zsw_history_get(&fitness_history_context, &sample, i); day = sample.time.tm.tm_wday; - printk("Day: %d, HH: %d, Steps: %d\n", day, sample.time.tm.tm_hour, sample.steps); + LOG_DBG("Day: %d, HH: %d, Steps: %d", day, sample.time.tm.tm_hour, sample.steps); weekdays[day] = MAX(sample.steps, weekdays[day]); } } +static void shift_array_n_left(uint16_t *arr, int n, int size) +{ + for (int i = 0; i < n; i++) { + int first = arr[0]; + for (int j = 0; j < size - 1; j++) { + arr[j] = arr[j + 1]; + } + arr[size - 1] = first; + } +} + +static void shift_char_array_n_left(char **arr, int n, int size) +{ + for (int i = 0; i < n; i++) { + char *first = arr[0]; + for (int j = 0; j < size - 1; j++) { + arr[j] = arr[j + 1]; + } + arr[size - 1] = first; + } +} + static void fitness_app_start(lv_obj_t *root, lv_group_t *group) { zsw_timeval_t time; uint16_t step_weekdays[DAYS_IN_WEEK] = {0}; - + static char *weekday_names[] = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}; zsw_clock_get_time(&time); get_steps_per_day(step_weekdays); // Rotate the array (left/counter-clockwise) so the last element is the current day int shifts = time.tm.tm_wday + 1; - for (int i = 0; i < shifts; i++) { - int first = step_weekdays[0]; - for (int j = 0; j < DAYS_IN_WEEK - 1; j++) { - step_weekdays[j] = step_weekdays[j + 1]; - } - step_weekdays[DAYS_IN_WEEK - 1] = first; - } + shift_array_n_left(step_weekdays, shifts, DAYS_IN_WEEK); + shift_char_array_n_left(weekday_names, shifts, DAYS_IN_WEEK); for (int i = 0; i < DAYS_IN_WEEK; i++) { - printk("Day %d: %d\n", i, step_weekdays[i]); + printk("%s %d: %d\n", weekday_names[i], i, step_weekdays[i]); } fitness_ui_show(root, DAYS_IN_WEEK); - fitness_ui_set_weekly_steps(step_weekdays, DAYS_IN_WEEK); + fitness_ui_set_weekly_steps(step_weekdays, weekday_names, DAYS_IN_WEEK); } static void fitness_app_stop(void) @@ -192,25 +208,11 @@ static int fitness_app_add(void) // Print curent minute and second LOG_DBG("fitness_app_add time: %d:%d:%d", time.tm.tm_hour, time.tm.tm_min, time.tm.tm_sec); - if (num_hist_samples == 0) { - // Try to sample about every full hour - next_sample_seconds = 60 * (SAMPLE_INTERVAL_MIN - time.tm.tm_min) - time.tm.tm_sec; - } else { - zsw_history_get(&fitness_history_context, &sample, zsw_history_samples(&fitness_history_context) - 1); - zsw_timeval_to_tm(&time, &tm); - zsw_timeval_to_tm(&sample.time, &prev_tm); - time_t time_now = mktime(&tm); - time_t time_last_sample = mktime(&prev_tm); - __ASSERT(time_now != -1, "Invalid time_now!"); - __ASSERT(time_last_sample != -1, "Invalid time_last_sample!"); - - double difference = difftime(time_now, time_last_sample); - __ASSERT(difference >= 0, "Time difference is negative!"); - next_sample_seconds = MIN(3600, 3600 - difference); - } + // Try to sample about every full hour + next_sample_seconds = 60 * (SAMPLE_INTERVAL_MIN - time.tm.tm_min) - time.tm.tm_sec; + LOG_DBG("Next sample in %d:%d", next_sample_seconds / 60, next_sample_seconds % 60); - //next_sample_seconds = 10; - k_work_schedule(&sample_step_work, K_SECONDS(next_sample_seconds)); + k_work_reschedule(&sample_step_work, K_SECONDS(next_sample_seconds)); return 0; } diff --git a/app/src/applications/fitness/fitness_ui.c b/app/src/applications/fitness/fitness_ui.c index daf99195..39e341e6 100644 --- a/app/src/applications/fitness/fitness_ui.c +++ b/app/src/applications/fitness/fitness_ui.c @@ -7,6 +7,7 @@ static lv_obj_t *ui_step_progress_label = NULL; static lv_obj_t *ui_weekly_chart = NULL; static lv_obj_t *ui_step_goal_arc = NULL; static lv_chart_series_t *ui_weekly_chart_series_1 = NULL; +static char **chart_bar_names = NULL; static void event_cb(lv_event_t *e) { @@ -22,30 +23,44 @@ static void event_cb(lv_event_t *e) } int num_points = lv_chart_get_point_count(chart); - static char *weekdays[] = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}; // TODO correct order ha to come from fitness_app.c for (int id = 0; id < num_points; id++) { lv_point_t p; lv_chart_get_point_pos_by_id(chart, ser, id, &p); char buf[16]; - lv_snprintf(buf, sizeof(buf), LV_SYMBOL_DUMMY"%s", weekdays[id]); + lv_snprintf(buf, sizeof(buf), "%s", chart_bar_names[id]); - lv_draw_rect_dsc_t draw_rect_dsc; - lv_draw_rect_dsc_init(&draw_rect_dsc); - draw_rect_dsc.bg_color = lv_color_black(); - draw_rect_dsc.bg_opa = LV_OPA_50; - draw_rect_dsc.radius = 3; - draw_rect_dsc.bg_img_src = buf; - draw_rect_dsc.bg_img_recolor = lv_color_white(); + // Draw the day of week above each bar + lv_draw_label_dsc_t draw_label_dsc; + lv_draw_label_dsc_init(&draw_label_dsc); + draw_label_dsc.color = zsw_color_red(); + draw_label_dsc.align = LV_TEXT_ALIGN_CENTER; lv_area_t a; - a.x1 = chart->coords.x1 + p.x - 20; - a.x2 = chart->coords.x1 + p.x + 20; + a.x1 = chart->coords.x1 + p.x - 15; + a.x2 = chart->coords.x1 + p.x + 17; a.y1 = chart->coords.y1 + 20 - 30; a.y2 = chart->coords.y1 + 20 - 10; lv_draw_ctx_t *draw_ctx = lv_event_get_draw_ctx(e); - lv_draw_rect(draw_ctx, &draw_rect_dsc, &a); + lv_draw_label(draw_ctx, &draw_label_dsc, &a, buf, NULL); + + // Draw a vertical line to separate the bars + lv_draw_rect_dsc_t draw_rect_dsc; + lv_draw_rect_dsc_init(&draw_rect_dsc); + draw_rect_dsc.bg_opa = LV_OPA_TRANSP; + draw_rect_dsc.border_color = zsw_color_gray(); + draw_rect_dsc.border_width = 1; + draw_rect_dsc.border_side = LV_BORDER_SIDE_RIGHT; + + // Note thise values are not dynamic, so needs change if graph size changes. + a.y1 = chart->coords.y1 + 20 - 30; + a.y2 = chart->coords.y1 + 20 + 99; + + // Don't draw a line after the last bar + if (id != num_points - 1) { + lv_draw_rect(draw_ctx, &draw_rect_dsc, &a); + } } static int32_t id = LV_CHART_POINT_NONE; @@ -67,7 +82,7 @@ static void event_cb(lv_event_t *e) lv_draw_rect_dsc_t draw_rect_dsc; lv_draw_rect_dsc_init(&draw_rect_dsc); draw_rect_dsc.bg_color = lv_color_white(); - draw_rect_dsc.bg_opa = LV_OPA_30; + draw_rect_dsc.bg_opa = LV_OPA_70; draw_rect_dsc.radius = 5; draw_rect_dsc.bg_img_src = buf; draw_rect_dsc.bg_img_recolor = lv_color_black(); @@ -92,6 +107,8 @@ static void create_step_chart(lv_obj_t *ui_root_container, uint16_t max_samples) lv_obj_set_x(ui_step_progress_label, 0); lv_obj_set_y(ui_step_progress_label, -90); lv_obj_set_align(ui_step_progress_label, LV_ALIGN_CENTER); + lv_obj_set_style_text_color(ui_step_progress_label, zsw_color_blue(), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_step_progress_label, &lv_font_montserrat_18, LV_PART_MAIN | LV_STATE_DEFAULT); lv_label_set_text(ui_step_progress_label, "- / 10000"); ui_weekly_chart = lv_chart_create(ui_root_container); @@ -102,13 +119,13 @@ static void create_step_chart(lv_obj_t *ui_root_container, uint16_t max_samples) lv_obj_set_align(ui_weekly_chart, LV_ALIGN_CENTER); lv_chart_set_type(ui_weekly_chart, LV_CHART_TYPE_BAR); lv_chart_set_point_count(ui_weekly_chart, 7); - lv_chart_set_range(ui_weekly_chart, LV_CHART_AXIS_PRIMARY_Y, 0, 10000); + lv_chart_set_range(ui_weekly_chart, LV_CHART_AXIS_PRIMARY_Y, 0, 12000); lv_chart_set_range(ui_weekly_chart, LV_CHART_AXIS_SECONDARY_Y, 0, 0); lv_chart_set_div_line_count(ui_weekly_chart, 0, 0); lv_chart_set_axis_tick(ui_weekly_chart, LV_CHART_AXIS_PRIMARY_X, 1, 1, 1, 1, false, 50); lv_chart_set_axis_tick(ui_weekly_chart, LV_CHART_AXIS_PRIMARY_Y, 1, 1, 1, 1, false, 50); lv_chart_set_axis_tick(ui_weekly_chart, LV_CHART_AXIS_SECONDARY_Y, 1, 1, 1, 1, false, 25); - ui_weekly_chart_series_1 = lv_chart_add_series(ui_weekly_chart, lv_color_hex(0x9E3939), + ui_weekly_chart_series_1 = lv_chart_add_series(ui_weekly_chart, zsw_color_blue(), LV_CHART_AXIS_PRIMARY_Y); lv_obj_set_style_bg_color(ui_weekly_chart, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); @@ -156,7 +173,7 @@ static void create_step_chart(lv_obj_t *ui_root_container, uint16_t max_samples) lv_obj_set_x(ui_last_7_days_title_label, 0); lv_obj_set_y(ui_last_7_days_title_label, -20); lv_obj_set_align(ui_last_7_days_title_label, LV_ALIGN_BOTTOM_MID); - lv_label_set_text(ui_last_7_days_title_label, "Last 7 days"); + lv_label_set_text(ui_last_7_days_title_label, "Steps per day"); lv_obj_add_event_cb(ui_weekly_chart, event_cb, LV_EVENT_ALL, NULL); } @@ -177,14 +194,17 @@ void fitness_ui_show(lv_obj_t *root, uint16_t max_samples) create_step_chart(root_page, max_samples); } -void fitness_ui_set_weekly_steps(uint16_t *samples, uint16_t num_samples) +void fitness_ui_set_weekly_steps(uint16_t *samples, char **weekday_names, uint16_t num_samples) { assert(ui_weekly_chart_series_1 != NULL); + chart_bar_names = weekday_names; + for (int i = 0; i < num_samples; i++) { lv_chart_set_next_value(ui_weekly_chart, ui_weekly_chart_series_1, samples[i]); } lv_label_set_text_fmt(ui_step_progress_label, "%d / %d", samples[num_samples - 1], 10000); + lv_obj_set_style_text_align(ui_step_progress_label, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT); lv_arc_set_value(ui_step_goal_arc, samples[num_samples - 1]); } diff --git a/app/src/applications/fitness/fitness_ui.h b/app/src/applications/fitness/fitness_ui.h index f5a2c032..590e7148 100644 --- a/app/src/applications/fitness/fitness_ui.h +++ b/app/src/applications/fitness/fitness_ui.h @@ -5,6 +5,6 @@ void fitness_ui_show(lv_obj_t *root, uint16_t max_samples); -void fitness_ui_set_weekly_steps(uint16_t *samples, uint16_t num_samples); +void fitness_ui_set_weekly_steps(uint16_t *samples, char **weekday_names, uint16_t num_samples); void fitness_ui_remove(void); diff --git a/app/src/history/zsw_history.c b/app/src/history/zsw_history.c index 05ce2562..0724098c 100644 --- a/app/src/history/zsw_history.c +++ b/app/src/history/zsw_history.c @@ -34,10 +34,12 @@ static int zsw_history_load_header_cb(const char *p_key, size_t len, settings_re LOG_ERR("Invalid header. Struct size changed!"); zsw_history_del(p_history); } else if (temp_stored_history.max_samples != p_history->max_samples) { - LOG_ERR("max_samples does not match what's stored in settings. Erasing history."); + LOG_ERR("max_samples does not match what's stored in settings. Erasing history: %d != %d", + temp_stored_history.max_samples, p_history->max_samples); zsw_history_del(p_history); } else if (temp_stored_history.sample_size != p_history->sample_size) { - LOG_ERR("sample_size does not match what's stored in settings. Erasing history."); + LOG_ERR("sample_size does not match what's stored in settings. Erasing history: %d != %d", + temp_stored_history.sample_size, p_history->sample_size); zsw_history_del(p_history); } else { // Everything is fine, we can load the history From 682bc25912d86d45fce087f19e05df5155ac6b6c Mon Sep 17 00:00:00 2001 From: Jakob Krantz Date: Thu, 24 Oct 2024 22:38:05 +0200 Subject: [PATCH 8/8] Fitness App: Use latest steps and not latest stored steps as current steps. --- app/src/applications/fitness/fitness_app.c | 18 +++++++++--------- app/src/applications/fitness/fitness_ui.c | 9 ++++++++- app/src/applications/fitness/fitness_ui.h | 2 ++ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/src/applications/fitness/fitness_app.c b/app/src/applications/fitness/fitness_app.c index 5fbe0652..80360719 100644 --- a/app/src/applications/fitness/fitness_app.c +++ b/app/src/applications/fitness/fitness_app.c @@ -13,7 +13,7 @@ #include "ui/zsw_ui.h" #include "zsw_clock.h" -LOG_MODULE_REGISTER(fitness_app, LOG_LEVEL_DBG); +LOG_MODULE_REGISTER(fitness_app, LOG_LEVEL_INF); #define STEP_RESET_COUNTER_INTERVAL_S 50 #define DAYS_IN_WEEK 7 @@ -98,7 +98,6 @@ static void step_sample_work(struct k_work *work) if (zsw_history_save(&fitness_history_context)) { LOG_ERR("Error during saving of step samples!"); } - LOG_DBG("______STEP HIST ADD________"); LOG_DBG("Step sample hist add: %d", sample.steps); LOG_DBG("Time: %d:%d:%d", sample.time.tm.tm_hour, sample.time.tm.tm_min, sample.time.tm.tm_sec); next_sample_seconds = 60 * (SAMPLE_INTERVAL_MIN - sample.time.tm.tm_min) - sample.time.tm.tm_sec; @@ -145,22 +144,29 @@ static void shift_char_array_n_left(char **arr, int n, int size) static void fitness_app_start(lv_obj_t *root, lv_group_t *group) { zsw_timeval_t time; + uint32_t steps; uint16_t step_weekdays[DAYS_IN_WEEK] = {0}; static char *weekday_names[] = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}; zsw_clock_get_time(&time); get_steps_per_day(step_weekdays); + // Data is in the order of the days of the week, starting from Sunday (index 0) // Rotate the array (left/counter-clockwise) so the last element is the current day + // As we want the last bar in the chart to be "Today". int shifts = time.tm.tm_wday + 1; shift_array_n_left(step_weekdays, shifts, DAYS_IN_WEEK); shift_char_array_n_left(weekday_names, shifts, DAYS_IN_WEEK); for (int i = 0; i < DAYS_IN_WEEK; i++) { - printk("%s %d: %d\n", weekday_names[i], i, step_weekdays[i]); + LOG_DBG("%s %d: %d\n", weekday_names[i], i, step_weekdays[i]); } fitness_ui_show(root, DAYS_IN_WEEK); fitness_ui_set_weekly_steps(step_weekdays, weekday_names, DAYS_IN_WEEK); + + if (zsw_imu_fetch_num_steps(&steps) == 0) { + fitness_ui_set_daily_steps(steps); + } } static void fitness_app_stop(void) @@ -170,11 +176,8 @@ static void fitness_app_stop(void) static int fitness_app_add(void) { - struct tm tm; - struct tm prev_tm; int num_hist_samples; zsw_timeval_t time; - zsw_step_sample_t sample; int next_sample_seconds = 0; zsw_app_manager_add_application(&app); @@ -205,9 +208,6 @@ static int fitness_app_add(void) zsw_imu_set_step_offset(samples[num_hist_samples - 1].steps); } - // Print curent minute and second - LOG_DBG("fitness_app_add time: %d:%d:%d", time.tm.tm_hour, time.tm.tm_min, time.tm.tm_sec); - // Try to sample about every full hour next_sample_seconds = 60 * (SAMPLE_INTERVAL_MIN - time.tm.tm_min) - time.tm.tm_sec; diff --git a/app/src/applications/fitness/fitness_ui.c b/app/src/applications/fitness/fitness_ui.c index 39e341e6..8a99b8c9 100644 --- a/app/src/applications/fitness/fitness_ui.c +++ b/app/src/applications/fitness/fitness_ui.c @@ -160,7 +160,7 @@ static void create_step_chart(lv_obj_t *ui_root_container, uint16_t max_samples) lv_obj_set_style_pad_bottom(ui_step_goal_arc, 0, LV_PART_INDICATOR | LV_STATE_DEFAULT); lv_obj_set_style_pad_row(ui_step_goal_arc, 0, LV_PART_INDICATOR | LV_STATE_DEFAULT); lv_obj_set_style_pad_column(ui_step_goal_arc, 0, LV_PART_INDICATOR | LV_STATE_DEFAULT); - lv_obj_set_style_arc_color(ui_step_goal_arc, lv_color_hex(0x00921A), LV_PART_INDICATOR | LV_STATE_DEFAULT); + lv_obj_set_style_arc_color(ui_step_goal_arc, lv_color_hex(0xffd147), LV_PART_INDICATOR | LV_STATE_DEFAULT); lv_obj_set_style_arc_opa(ui_step_goal_arc, 255, LV_PART_INDICATOR | LV_STATE_DEFAULT); lv_obj_set_style_arc_width(ui_step_goal_arc, 4, LV_PART_INDICATOR | LV_STATE_DEFAULT); @@ -208,6 +208,13 @@ void fitness_ui_set_weekly_steps(uint16_t *samples, char **weekday_names, uint16 lv_arc_set_value(ui_step_goal_arc, samples[num_samples - 1]); } +void fitness_ui_set_daily_steps(uint32_t steps) +{ + lv_label_set_text_fmt(ui_step_progress_label, "%d / %d", steps, 10000); + lv_obj_set_style_text_align(ui_step_progress_label, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_arc_set_value(ui_step_goal_arc, steps); +} + void fitness_ui_remove(void) { lv_obj_del(root_page); diff --git a/app/src/applications/fitness/fitness_ui.h b/app/src/applications/fitness/fitness_ui.h index 590e7148..2a917791 100644 --- a/app/src/applications/fitness/fitness_ui.h +++ b/app/src/applications/fitness/fitness_ui.h @@ -7,4 +7,6 @@ void fitness_ui_show(lv_obj_t *root, uint16_t max_samples); void fitness_ui_set_weekly_steps(uint16_t *samples, char **weekday_names, uint16_t num_samples); +void fitness_ui_set_daily_steps(uint32_t steps); + void fitness_ui_remove(void);