diff --git a/Makefile b/Makefile index e1bf8414..baf7493f 100644 --- a/Makefile +++ b/Makefile @@ -135,6 +135,7 @@ include watch-faces.mk SRCS += \ ./movement.c \ + ./movement_activity.c \ # Finally, leave this line at the bottom of the file. include $(GOSSAMER_PATH)/rules.mk diff --git a/movement.c b/movement.c index 8fe408b3..cf978a4e 100644 --- a/movement.c +++ b/movement.c @@ -43,6 +43,8 @@ #include "lis2dw.h" #include "tc.h" #include "evsys.h" +#include "delay.h" +#include "movement_activity.h" #include "movement_config.h" @@ -170,6 +172,10 @@ static void _movement_handle_top_of_minute(void) { movement_request_sleep(); } } + + if ((date_time.unit.minute % 5) == 0) { + _movement_log_data(); + } #endif // update the DST offset cache every 30 minutes, since someplace in the world could change. diff --git a/movement_activity.c b/movement_activity.c new file mode 100644 index 00000000..ea2d9d6f --- /dev/null +++ b/movement_activity.c @@ -0,0 +1,76 @@ +/* + * MIT License + * + * Copyright (c) 2025 Joey Castillo + * + * 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 "movement.h" +#include "movement_activity.h" +#include "tc.h" +#include "thermistor_driver.h" + +#ifdef HAS_ACCELEROMETER + +// RAM to stash the data points. +movement_activity_data_point movement_activity_log[MOVEMENT_NUM_DATA_POINTS] = {0}; +// the absolute number of data points logged +uint32_t data_points = 0; + +// hacky: we're just tapping into Movement's global state for stationary detection. +// do we need better API for this? i'm less bothered now that it's all in Movement. +extern uint8_t stationary_minutes; + +void _movement_log_data(void) { + size_t pos = data_points % MOVEMENT_NUM_DATA_POINTS; + movement_activity_data_point data_point = {0}; + + // Movement tracks stationary minutes when deciding whether to sleep. + data_point.bit.stationary_minutes = stationary_minutes; + + // orientation changes are counted in TC2. stash them in the data point... + data_point.bit.orientation_changes = tc_count16_get_count(2); + // ...and then reset the number of orientation changes. + tc_count16_set_count(2, 0); + + // log the temperature + thermistor_driver_enable(); + float temperature_c = thermistor_driver_get_temperature(); + thermistor_driver_disable(); + // offset the temperature by 30, so -30°C is 0, and 72.3°C is 102.3 + temperature_c = temperature_c + 30; + if (temperature_c < 0) temperature_c = 0; + if (temperature_c > 102.3) temperature_c = 102.3; + // now we have can fit temperature into a 10-bit value + data_point.bit.measured_temperature = temperature_c * 10; + + /// TODO: log light level + + // log the data point + movement_activity_log[pos].reg = data_point.reg; + data_points++; +} + +movement_activity_data_point *movement_get_data_log(uint32_t *count) { + *count = data_points; + return movement_activity_log; +} + +#endif diff --git a/movement_activity.h b/movement_activity.h new file mode 100644 index 00000000..68f0f2ee --- /dev/null +++ b/movement_activity.h @@ -0,0 +1,52 @@ +/* + * MIT License + * + * Copyright (c) 2025 Joey Castillo + * + * 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. + */ + +#pragma once + +#ifdef HAS_ACCELEROMETER + +#include + +// Log 36 hours of data points. Each data point captures 5 minutes. +#define MOVEMENT_NUM_DATA_POINTS (36 * (60 / 5)) + +typedef union { + struct { + uint32_t stationary_minutes: 3; + uint32_t orientation_changes: 9; + uint32_t measured_temperature: 10; + uint32_t measured_light: 10; + } bit; + uint32_t reg; +} movement_activity_data_point; + +/// @brief Internal function, called every 5 minutes to log data. +void _movement_log_data(void); + +/// @brief Returns a pointer to the data log. +/// @param count is a pointer to a uint32_t. The absolute number of data points logged is returned by reference. +/// You can assume that log[count % MOVEMENT_NUM_DATA_POINTS] is the latest data point, and work backwards from there. +movement_activity_data_point *movement_get_data_log(uint32_t *count); + +#endif \ No newline at end of file diff --git a/watch-faces/sensor/activity_logging_face.c b/watch-faces/sensor/activity_logging_face.c index a6d58010..07da1eec 100644 --- a/watch-faces/sensor/activity_logging_face.c +++ b/watch-faces/sensor/activity_logging_face.c @@ -27,56 +27,24 @@ #include "activity_logging_face.h" #include "filesystem.h" #include "watch.h" -#include "tc.h" -#include "thermistor_driver.h" +#include "movement_activity.h" +#include "watch_utility.h" #ifdef HAS_ACCELEROMETER -// hacky: we're just tapping into Movement's global state. -// we should make better API for this. -extern uint8_t stationary_minutes; - -static void _activity_logging_face_log_data(activity_logging_state_t *state) { - watch_date_time_t date_time = movement_get_local_date_time(); - size_t pos = state->data_points % ACTIVITY_LOGGING_NUM_DATA_POINTS; - activity_logging_data_point_t data_point = {0}; - - data_point.bit.day = date_time.unit.day; - data_point.bit.month = date_time.unit.month; - data_point.bit.hour = date_time.unit.hour; - data_point.bit.minute = date_time.unit.minute; - data_point.bit.stationary_minutes = stationary_minutes; - data_point.bit.orientation_changes = tc_count16_get_count(2); // orientation changes are counted in TC2 - - // write data point. WARNING: Crashes the system if out of space, freezing the time at the moment of the crash. - // In this exploratory phase, I'm treating this as a "feature" that tells me to dump the data and begin again. - if (filesystem_append_file("activity.dat", (char *)&data_point, sizeof(activity_logging_data_point_t))) { - printf("Data point written\n"); - } else { - printf("Failed to write data point\n"); - } - - // test for off-wrist temperature stuff - thermistor_driver_enable(); - float temperature_c = thermistor_driver_get_temperature(); - thermistor_driver_disable(); - uint16_t temperature = temperature_c * 1000; - if (filesystem_append_file("activity.dat", (char *)&temperature, sizeof(uint16_t))) { - printf("Temperature written: %d\n", temperature); - } else { - printf("Failed to write temperature\n"); - } - - state->data[pos].reg = data_point.reg; - state->data_points++; - - // reset the number of orientation changes. - tc_count16_set_count(2, 0); -} - static void _activity_logging_face_update_display(activity_logging_state_t *state, bool clock_mode_24h) { - int8_t pos = (state->data_points - 1 - state->display_index) % ACTIVITY_LOGGING_NUM_DATA_POINTS; char buf[8]; + uint32_t count = 0; + movement_activity_data_point *data_points = movement_get_data_log(&count); + int8_t pos = (count - 1 - state->display_index) % ACTIVITY_LOGGING_NUM_DATA_POINTS; + watch_date_time_t timestamp = movement_get_local_date_time(); + + // round to previous 5 minute increment + timestamp.unit.minute = timestamp.unit.minute - (timestamp.unit.minute % 5); + // advance backward by 5 minutes for each increment of state->display_index + uint32_t unix_timestamp = watch_utility_date_time_to_unix_time(timestamp, movement_get_current_timezone_offset()); + unix_timestamp -= 300 * state->display_index; + timestamp = watch_utility_date_time_from_unix_time(unix_timestamp, movement_get_current_timezone_offset()); watch_clear_indicator(WATCH_INDICATOR_24H); watch_clear_indicator(WATCH_INDICATOR_PM); @@ -94,21 +62,21 @@ static void _activity_logging_face_update_display(activity_logging_state_t *stat if (clock_mode_24h) { watch_set_indicator(WATCH_INDICATOR_24H); } else { - if (state->data[pos].bit.hour > 11) watch_set_indicator(WATCH_INDICATOR_PM); - state->data[pos].bit.hour %= 12; - if (state->data[pos].bit.hour == 0) state->data[pos].bit.hour = 12; + if (timestamp.unit.hour > 11) watch_set_indicator(WATCH_INDICATOR_PM); + timestamp.unit.hour %= 12; + if (timestamp.unit.hour == 0) timestamp.unit.hour = 12; } watch_display_text(WATCH_POSITION_TOP_LEFT, "AT"); - sprintf(buf, "%2d", state->data[pos].bit.day); + sprintf(buf, "%2d", timestamp.unit.day); watch_display_text(WATCH_POSITION_TOP_RIGHT, buf); - sprintf(buf, "%2d%02d%02d", state->data[pos].bit.hour, state->data[pos].bit.minute, 0); + sprintf(buf, "%2d%02d%02d", timestamp.unit.hour, timestamp.unit.minute, 0); watch_display_text(WATCH_POSITION_BOTTOM, buf); } else { // we are displaying the number of accelerometer wakeups and orientation changes watch_display_text(WATCH_POSITION_TOP, "AC"); sprintf(buf, "%2d", state->display_index); watch_display_text(WATCH_POSITION_TOP_RIGHT, buf); - sprintf(buf, "%-3u/%2d", state->data[pos].bit.orientation_changes > 999 ? 999 : state->data[pos].bit.orientation_changes, state->data[pos].bit.stationary_minutes); + sprintf(buf, "%-3u/%2d", data_points[pos].bit.orientation_changes > 999 ? 999 : data_points[pos].bit.orientation_changes, data_points[pos].bit.stationary_minutes); watch_display_text(WATCH_POSITION_BOTTOM, buf); } } @@ -118,10 +86,6 @@ void activity_logging_face_setup(uint8_t watch_face_index, void ** context_ptr) if (*context_ptr == NULL) { *context_ptr = malloc(sizeof(activity_logging_state_t)); memset(*context_ptr, 0, sizeof(activity_logging_state_t)); - // create file if it doesn't exist - if (!filesystem_file_exists("activity.dat")) { - filesystem_write_file("activity.dat", "", 0); - } } } @@ -157,9 +121,6 @@ bool activity_logging_face_loop(movement_event_t event, void *context) { _activity_logging_face_update_display(state, movement_clock_mode_24h()); } break; - case EVENT_BACKGROUND_TASK: - _activity_logging_face_log_data(state); - break; default: movement_default_loop_handler(event); break; @@ -172,14 +133,4 @@ void activity_logging_face_resign(void *context) { (void) context; } -movement_watch_face_advisory_t activity_logging_face_advise(void *context) { - (void) context; - movement_watch_face_advisory_t retval = { 0 }; - - // log data every 5 minutes - retval.wants_background_task = (movement_get_local_date_time().unit.minute % 5) == 0; - - return retval; -} - #endif diff --git a/watch-faces/sensor/activity_logging_face.h b/watch-faces/sensor/activity_logging_face.h index 489b4d53..90bf0093 100644 --- a/watch-faces/sensor/activity_logging_face.h +++ b/watch-faces/sensor/activity_logging_face.h @@ -41,23 +41,9 @@ #define ACTIVITY_LOGGING_NUM_DATA_POINTS (36) -typedef union { - struct { - uint32_t day: 5; - uint32_t month: 4; - uint32_t hour: 5; - uint32_t minute: 6; - uint32_t stationary_minutes: 3; - uint32_t orientation_changes: 9; - } bit; - uint32_t reg; -} activity_logging_data_point_t; - typedef struct { uint8_t display_index; // the index we are displaying on screen uint8_t ts_ticks; // when the user taps the LIGHT button, we show the timestamp for a few ticks. - int32_t data_points; // the absolute number of data points logged - activity_logging_data_point_t data[ACTIVITY_LOGGING_NUM_DATA_POINTS]; } activity_logging_state_t; void activity_logging_face_setup(uint8_t watch_face_index, void ** context_ptr); @@ -71,7 +57,7 @@ movement_watch_face_advisory_t activity_logging_face_advise(void *context); activity_logging_face_activate, \ activity_logging_face_loop, \ activity_logging_face_resign, \ - activity_logging_face_advise, \ + NULL, \ }) #endif // HAS_TEMPERATURE_SENSOR