/* * MIT License * * Copyright (c) 2025 Konrad Rieck * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include "lis2dw_monitor_face.h" #include "watch.h" #include "watch_utility.h" /* Display frequency */ #define DISPLAY_FREQUENCY 8 /* Settings */ #define NUM_SETTINGS 7 static void _settings_title_display(lis2dw_monitor_state_t *state, char *buf1, char *buf2) { char buf[10]; watch_display_text_with_fallback(WATCH_POSITION_TOP, buf1, buf2); if (watch_get_lcd_type() != WATCH_LCD_TYPE_CUSTOM) { snprintf(buf, sizeof(buf), "%2d", state->settings_page + 1); watch_display_text_with_fallback(WATCH_POSITION_TOP_RIGHT, buf, buf); } } static bool _settings_blink(uint8_t subsecond) { if (subsecond % 2 == 0) { watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, " ", " "); return true; } return false; } static void _settings_mode_display(void *context, uint8_t subsecond) { char buf[10]; lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; _settings_title_display(state, "MODE ", "MO"); if (_settings_blink(subsecond)) return; switch (state->ds.mode) { case LIS2DW_MODE_LOW_POWER: snprintf(buf, sizeof(buf), " LO "); break; case LIS2DW_MODE_HIGH_PERFORMANCE: snprintf(buf, sizeof(buf), " HI "); break; case LIS2DW_MODE_ON_DEMAND: snprintf(buf, sizeof(buf), " OD "); break; } watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, buf, buf); } static void _settings_mode_advance(void *context) { lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; switch (state->ds.mode) { case LIS2DW_MODE_LOW_POWER: state->ds.mode = LIS2DW_MODE_HIGH_PERFORMANCE; break; case LIS2DW_MODE_HIGH_PERFORMANCE: state->ds.mode = LIS2DW_MODE_ON_DEMAND; break; case LIS2DW_MODE_ON_DEMAND: state->ds.mode = LIS2DW_MODE_LOW_POWER; break; } } static void _settings_data_rate_display(void *context, uint8_t subsecond) { char buf[10]; lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; _settings_title_display(state, "RATE ", "DR"); if (_settings_blink(subsecond)) return; switch (state->ds.data_rate) { case LIS2DW_DATA_RATE_POWERDOWN: snprintf(buf, sizeof(buf), " -- "); break; case LIS2DW_DATA_RATE_LOWEST: snprintf(buf, sizeof(buf), " LO "); break; case LIS2DW_DATA_RATE_12_5_HZ: snprintf(buf, sizeof(buf), " 12Hz"); break; case LIS2DW_DATA_RATE_25_HZ: snprintf(buf, sizeof(buf), " 25Hz"); break; default: snprintf(buf, sizeof(buf), " HI "); break; } watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, buf, buf); } static void _settings_data_rate_advance(void *context) { lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; switch (state->ds.data_rate) { case LIS2DW_DATA_RATE_POWERDOWN: state->ds.data_rate = LIS2DW_DATA_RATE_LOWEST; break; case LIS2DW_DATA_RATE_LOWEST: state->ds.data_rate = LIS2DW_DATA_RATE_12_5_HZ; break; case LIS2DW_DATA_RATE_12_5_HZ: state->ds.data_rate = LIS2DW_DATA_RATE_25_HZ; break; case LIS2DW_DATA_RATE_25_HZ: state->ds.data_rate = LIS2DW_DATA_RATE_POWERDOWN; break; default: state->ds.data_rate = LIS2DW_DATA_RATE_POWERDOWN; break; } } static void _settings_low_power_display(void *context, uint8_t subsecond) { char buf[10]; lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; _settings_title_display(state, "LO PM", "LP"); if (_settings_blink(subsecond)) return; switch (state->ds.low_power) { case LIS2DW_LP_MODE_1: snprintf(buf, sizeof(buf), " L1 12"); break; case LIS2DW_LP_MODE_2: snprintf(buf, sizeof(buf), " L2 14"); break; case LIS2DW_LP_MODE_3: snprintf(buf, sizeof(buf), " L3 14"); break; case LIS2DW_LP_MODE_4: snprintf(buf, sizeof(buf), " L4 14"); break; } watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, buf, buf); } static void _settings_low_power_advance(void *context) { lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; switch (state->ds.low_power) { case LIS2DW_LP_MODE_1: state->ds.low_power = LIS2DW_LP_MODE_2; break; case LIS2DW_LP_MODE_2: state->ds.low_power = LIS2DW_LP_MODE_3; break; case LIS2DW_LP_MODE_3: state->ds.low_power = LIS2DW_LP_MODE_4; break; case LIS2DW_LP_MODE_4: state->ds.low_power = LIS2DW_LP_MODE_1; break; } } static void _settings_bwf_mode_display(void *context, uint8_t subsecond) { char buf[10]; lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; _settings_title_display(state, "BWF ", "BW"); if (_settings_blink(subsecond)) return; switch (state->ds.bwf_mode) { case LIS2DW_BANDWIDTH_FILTER_DIV2: snprintf(buf, sizeof(buf), " 2 "); break; case LIS2DW_BANDWIDTH_FILTER_DIV4: snprintf(buf, sizeof(buf), " 4 "); break; case LIS2DW_BANDWIDTH_FILTER_DIV10: snprintf(buf, sizeof(buf), " 10 "); break; case LIS2DW_BANDWIDTH_FILTER_DIV20: snprintf(buf, sizeof(buf), " 20 "); break; } watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, buf, buf); } static void _settings_bwf_mode_advance(void *context) { lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; switch (state->ds.bwf_mode) { case LIS2DW_BANDWIDTH_FILTER_DIV2: state->ds.bwf_mode = LIS2DW_BANDWIDTH_FILTER_DIV4; break; case LIS2DW_BANDWIDTH_FILTER_DIV4: state->ds.bwf_mode = LIS2DW_BANDWIDTH_FILTER_DIV10; break; case LIS2DW_BANDWIDTH_FILTER_DIV10: state->ds.bwf_mode = LIS2DW_BANDWIDTH_FILTER_DIV20; break; case LIS2DW_BANDWIDTH_FILTER_DIV20: state->ds.bwf_mode = LIS2DW_BANDWIDTH_FILTER_DIV2; break; } } static void _settings_range_display(void *context, uint8_t subsecond) { char buf[10]; lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; _settings_title_display(state, "RANGE", "RA"); if (_settings_blink(subsecond)) return; switch (state->ds.range) { case LIS2DW_RANGE_2_G: snprintf(buf, sizeof(buf), " 2g "); break; case LIS2DW_RANGE_4_G: snprintf(buf, sizeof(buf), " 4g "); break; case LIS2DW_RANGE_8_G: snprintf(buf, sizeof(buf), " 8g "); break; case LIS2DW_RANGE_16_G: snprintf(buf, sizeof(buf), " 16g "); break; } watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, buf, buf); } static void _settings_range_advance(void *context) { lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; switch (state->ds.range) { case LIS2DW_RANGE_2_G: state->ds.range = LIS2DW_RANGE_4_G; break; case LIS2DW_RANGE_4_G: state->ds.range = LIS2DW_RANGE_8_G; break; case LIS2DW_RANGE_8_G: state->ds.range = LIS2DW_RANGE_16_G; break; case LIS2DW_RANGE_16_G: state->ds.range = LIS2DW_RANGE_2_G; break; } } static void _settings_filter_display(void *context, uint8_t subsecond) { char buf[10]; lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; _settings_title_display(state, "FLT ", "FL"); if (_settings_blink(subsecond)) return; switch (state->ds.filter) { case LIS2DW_FILTER_LOW_PASS: snprintf(buf, sizeof(buf), " LP "); break; case LIS2DW_FILTER_HIGH_PASS: snprintf(buf, sizeof(buf), " HP "); break; } watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, buf, buf); } static void _settings_filter_advance(void *context) { lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; switch (state->ds.filter) { case LIS2DW_FILTER_LOW_PASS: state->ds.filter = LIS2DW_FILTER_HIGH_PASS; break; case LIS2DW_FILTER_HIGH_PASS: state->ds.filter = LIS2DW_FILTER_LOW_PASS; break; } } static void _settings_low_noise_display(void *context, uint8_t subsecond) { char buf[10]; lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; _settings_title_display(state, "LO NO", "LN"); if (_settings_blink(subsecond)) return; snprintf(buf, sizeof(buf), " %3s ", state->ds.low_noise ? "ON" : "OFF"); watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, buf, buf); } static void _settings_low_noise_advance(void *context) { lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; state->ds.low_noise = !state->ds.low_noise; } /* Play beep sound */ static inline void _beep() { if (!movement_button_should_sound()) return; watch_buzzer_play_note(BUZZER_NOTE_C7, 50); } /* Print lis2dw status to console. */ static void _lis2dw_print_state(lis2dw_device_state_t *ds) { printf("LIS2DW status:\n"); printf(" Power mode:\t%x\n", ds->mode); printf(" Data rate:\t%x\n", ds->data_rate); printf(" LP mode:\t%x\n", ds->low_power); printf(" BW filter:\t%x\n", ds->bwf_mode); printf(" Range:\t%x \n", ds->range); printf(" Filter type:\t%x\n", ds->filter); printf(" Low noise:\t%x\n", ds->low_noise); printf("\n"); } static void _lis2dw_get_state(lis2dw_device_state_t *ds) { ds->mode = lis2dw_get_mode(); ds->data_rate = lis2dw_get_data_rate(); ds->low_power = lis2dw_get_low_power_mode(); ds->bwf_mode = lis2dw_get_bandwidth_filtering(); ds->range = lis2dw_get_range(); ds->filter = lis2dw_get_filter_type(); ds->low_noise = lis2dw_get_low_noise_mode(); } static void _lis2dw_set_state(lis2dw_device_state_t *ds) { lis2dw_set_mode(ds->mode); lis2dw_set_data_rate(ds->data_rate); lis2dw_set_low_power_mode(ds->low_power); lis2dw_set_bandwidth_filtering(ds->bwf_mode); lis2dw_set_range(ds->range); lis2dw_set_filter_type(ds->filter); lis2dw_set_low_noise_mode(ds->low_noise); /* Additionally, set the background rate to the data rate. */ movement_set_accelerometer_background_rate(ds->data_rate); } static void _monitor_display(lis2dw_monitor_state_t *state) { char buf[10]; snprintf(buf, sizeof(buf), " %C ", "XYZ"[state->axis]); watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, buf, buf); snprintf(buf, sizeof(buf), "%2d", state->axis + 1); watch_display_text_with_fallback(WATCH_POSITION_TOP_RIGHT, buf, buf); if (state->show_title) { snprintf(buf, sizeof(buf), "LIS2DW"); watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, buf, buf); return; } if (state->ds.data_rate == LIS2DW_DATA_RATE_POWERDOWN) { /* No measurements available. */ snprintf(buf, sizeof(buf), " -- "); } else if (state->axis == 0) { char sign = (state->reading.x) >= 0 ? ' ' : '-'; snprintf(buf, sizeof(buf), "%c%.5d", sign, abs(state->reading.x)); } else if (state->axis == 1) { char sign = (state->reading.y) >= 0 ? ' ' : '-'; snprintf(buf, sizeof(buf), "%c%.5d", sign, abs(state->reading.y)); } else { char sign = (state->reading.z) >= 0 ? ' ' : '-'; snprintf(buf, sizeof(buf), "%c%.5d", sign, abs(state->reading.z)); } watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, buf, buf); } static void _monitor_update(lis2dw_monitor_state_t *state) { lis2dw_fifo_t fifo; float x = 0, y = 0, z = 0; lis2dw_read_fifo(&fifo); if (fifo.count == 0) { return; } /* Add up samples in fifo */ for (uint8_t i = 0; i < fifo.count; i++) { x += fifo.readings[i].x; y += fifo.readings[i].y; z += fifo.readings[i].z; } /* Divide by number of samples */ state->reading.x = (int16_t) (x / fifo.count); state->reading.y = (int16_t) (y / fifo.count); state->reading.z = (int16_t) (z / fifo.count); lis2dw_clear_fifo(); } static void _switch_to_monitor(lis2dw_monitor_state_t *state) { /* Switch to recording page */ movement_request_tick_frequency(DISPLAY_FREQUENCY); state->page = PAGE_LIS2DW_MONITOR; state->show_title = DISPLAY_FREQUENCY; _monitor_display(state); } static void _switch_to_settings(lis2dw_monitor_state_t *state) { /* Switch to chirping page */ movement_request_tick_frequency(4); state->page = PAGE_LIS2DW_SETTINGS; state->settings_page = 0; state->settings[state->settings_page].display(state, 0); } static bool _monitor_loop(movement_event_t event, void *context) { lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; switch (event.event_type) { case EVENT_ACTIVATE: watch_clear_colon(); _monitor_update(state); _monitor_display(state); break; case EVENT_TICK: _monitor_update(state); _monitor_display(state); state->show_title = (state->show_title > 0) ? state->show_title - 1 : 0; break; case EVENT_ALARM_BUTTON_UP: state->axis = (state->axis + 1) % 3; _monitor_display(state); break; case EVENT_LIGHT_BUTTON_DOWN: /* Do nothing. */ break; case EVENT_LIGHT_LONG_PRESS: _switch_to_settings(state); _beep(); break; default: movement_default_loop_handler(event); break; } return true; } static bool _settings_loop(movement_event_t event, void *context) { lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; switch (event.event_type) { case EVENT_ACTIVATE: case EVENT_TICK: state->settings[state->settings_page].display(context, event.subsecond); break; case EVENT_LIGHT_BUTTON_UP: state->settings_page = (state->settings_page + 1) % NUM_SETTINGS; state->settings[state->settings_page].display(context, event.subsecond); break; case EVENT_MODE_BUTTON_UP: _lis2dw_set_state(&state->ds); _lis2dw_print_state(&state->ds); _switch_to_monitor(state); _beep(); break; case EVENT_LIGHT_BUTTON_DOWN: /* Do nothing. */ break; case EVENT_ALARM_BUTTON_UP: /* Advance current settings */ state->settings[state->settings_page].advance(context); state->settings[state->settings_page].display(context, event.subsecond); break; default: _lis2dw_set_state(&state->ds); movement_default_loop_handler(event); break; } return true; } void lis2dw_monitor_face_setup(uint8_t watch_face_index, void **context_ptr) { (void) watch_face_index; if (*context_ptr == NULL) { *context_ptr = malloc(sizeof(lis2dw_monitor_state_t)); memset(*context_ptr, 0, sizeof(lis2dw_monitor_state_t)); } lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) * context_ptr; /* Default setup */ state->axis = 0; /* Initialize settings */ uint8_t settings_page = 0; state->settings = malloc(NUM_SETTINGS * sizeof(lis2dw_settings_t)); state->settings[settings_page].display = _settings_mode_display; state->settings[settings_page].advance = _settings_mode_advance; settings_page++; state->settings[settings_page].display = _settings_data_rate_display; state->settings[settings_page].advance = _settings_data_rate_advance; settings_page++; state->settings[settings_page].display = _settings_low_power_display; state->settings[settings_page].advance = _settings_low_power_advance; settings_page++; state->settings[settings_page].display = _settings_bwf_mode_display; state->settings[settings_page].advance = _settings_bwf_mode_advance; settings_page++; state->settings[settings_page].display = _settings_range_display; state->settings[settings_page].advance = _settings_range_advance; settings_page++; state->settings[settings_page].display = _settings_filter_display; state->settings[settings_page].advance = _settings_filter_advance; settings_page++; state->settings[settings_page].display = _settings_low_noise_display; state->settings[settings_page].advance = _settings_low_noise_advance; settings_page++; } void lis2dw_monitor_face_activate(void *context) { lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; /* Setup lis2dw to run in background at 12.5 Hz sampling rate. */ movement_set_accelerometer_background_rate(LIS2DW_DATA_RATE_12_5_HZ); /* Enable fifo and clear it. */ lis2dw_enable_fifo(); lis2dw_clear_fifo(); /* Print lis2dw status to console. */ _lis2dw_get_state(&state->ds); _lis2dw_print_state(&state->ds); /* Switch to monitor page. */ _switch_to_monitor(state); } bool lis2dw_monitor_face_loop(movement_event_t event, void *context) { lis2dw_monitor_state_t *state = (lis2dw_monitor_state_t *) context; switch (state->page) { default: case PAGE_LIS2DW_MONITOR: return _monitor_loop(event, context); case PAGE_LIS2DW_SETTINGS: return _settings_loop(event, context); } } void lis2dw_monitor_face_resign(void *context) { (void) context; lis2dw_clear_fifo(); lis2dw_disable_fifo(); } movement_watch_face_advisory_t lis2dw_monitor_face_advise(void *context) { (void) context; movement_watch_face_advisory_t retval = { 0 }; return retval; }