]> git.earman.xyz Git - sensor-watch.git/commitdiff
move stopwatch face into Second Movement
authorJoey Castillo <joeycastillo@utexas.edu>
Sat, 15 Mar 2025 22:41:07 +0000 (18:41 -0400)
committerJoey Castillo <joeycastillo@utexas.edu>
Sat, 15 Mar 2025 22:55:51 +0000 (18:55 -0400)
legacy/watch_faces/complication/stopwatch_face.c [deleted file]
legacy/watch_faces/complication/stopwatch_face.h [deleted file]
movement_config.h
movement_faces.h
watch-faces.mk
watch-faces/complication/stopwatch_face.c [new file with mode: 0644]
watch-faces/complication/stopwatch_face.h [new file with mode: 0644]

diff --git a/legacy/watch_faces/complication/stopwatch_face.c b/legacy/watch_faces/complication/stopwatch_face.c
deleted file mode 100644 (file)
index c2ef899..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * MIT License
- *
- * Copyright (c) 2022 Wesley Ellis
- * Copyright (c) 2022 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 <stdlib.h>
-#include <string.h>
-#include "stopwatch_face.h"
-#include "watch.h"
-#include "watch_utility.h"
-
-// distant future for background task: January 1, 2083
-// see stopwatch_face_activate for details
-static const watch_date_time_t distant_future = {
-    .unit = {0, 0, 0, 1, 1, 63}
-};
-
-void stopwatch_face_setup(uint8_t watch_face_index, void ** context_ptr) {
-    (void) watch_face_index;
-    if (*context_ptr == NULL) {
-        *context_ptr = malloc(sizeof(stopwatch_state_t));
-        memset(*context_ptr, 0, sizeof(stopwatch_state_t));
-    }
-}
-
-static void _stopwatch_face_update_display(stopwatch_state_t *stopwatch_state, bool show_seconds) {
-    if (stopwatch_state->running) {
-        watch_date_time_t now = watch_rtc_get_date_time();
-        uint32_t now_timestamp = watch_utility_date_time_to_unix_time(now, 0);
-        uint32_t start_timestamp = watch_utility_date_time_to_unix_time(stopwatch_state->start_time, 0);
-        stopwatch_state->seconds_counted = now_timestamp - start_timestamp;
-    }
-
-    if (stopwatch_state->seconds_counted >= 3456000) {
-        // display maxes out just shy of 40 days, thanks to the limit on the day digits (0-39)
-        stopwatch_state->running = false;
-        movement_cancel_background_task();
-        watch_display_string("st39235959", 0);
-        return;
-    }
-
-    watch_duration_t duration = watch_utility_seconds_to_duration(stopwatch_state->seconds_counted);
-    char buf[14];
-
-    sprintf(buf, "st  %02d%02d  ", duration.hours, duration.minutes);
-    watch_display_string(buf, 0);
-
-    if (duration.days != 0) {
-        sprintf(buf, "%2d", (uint8_t)duration.days);
-        watch_display_string(buf, 2);
-    }
-
-    if (show_seconds) {
-        sprintf(buf, "%02d", duration.seconds);
-        watch_display_string(buf, 8);
-    }
-}
-
-void stopwatch_face_activate(void *context) {
-    if (watch_sleep_animation_is_running()) watch_stop_sleep_animation();
-
-    stopwatch_state_t *stopwatch_state = (stopwatch_state_t *)context;
-    if (stopwatch_state->running) {
-        // because the low power update happens on the minute mark, and the wearer could start
-        // the stopwatch anytime, the low power update could fire up to 59 seconds later than
-        // we need it to, causing the stopwatch to display stale data.
-        // So let's schedule a background task that will never fire. This will keep the watch
-        // from entering low energy mode while the stopwatch is on screen. This background task
-        // will remain scheduled until the stopwatch stops OR this watch face resigns.
-        movement_schedule_background_task(distant_future);
-    }
-}
-
-bool stopwatch_face_loop(movement_event_t event, void *context) {
-    stopwatch_state_t *stopwatch_state = (stopwatch_state_t *)context;
-
-    switch (event.event_type) {
-        case EVENT_ACTIVATE:
-            watch_set_colon();
-            // fall through
-        case EVENT_TICK:
-            if (stopwatch_state->start_time.reg == 0) {
-                watch_display_string("st  000000", 0);
-            } else {
-                _stopwatch_face_update_display(stopwatch_state, true);
-            }
-            break;
-        case EVENT_LIGHT_BUTTON_DOWN:
-            movement_illuminate_led();
-            if (!stopwatch_state->running) {
-                stopwatch_state->start_time.reg = 0;
-                stopwatch_state->seconds_counted = 0;
-                watch_display_string("st  000000", 0);
-            }
-            break;
-        case EVENT_ALARM_BUTTON_DOWN:
-            if (movement_button_should_sound()) {
-                watch_buzzer_play_note(BUZZER_NOTE_C8, 50);
-            }
-            stopwatch_state->running = !stopwatch_state->running;
-            if (stopwatch_state->running) {
-                // we're running now, so we need to set the start_time.
-                if (stopwatch_state->start_time.reg == 0) {
-                    // if starting from the reset state, easy: we start now.
-                    stopwatch_state->start_time = watch_rtc_get_date_time();
-                } else {
-                    // if resuming with time already on the clock, the original start time isn't valid anymore!
-                    // so let's fetch the current time...
-                    uint32_t timestamp = watch_utility_date_time_to_unix_time(watch_rtc_get_date_time(), 0);
-                    // ...subtract the seconds we've already counted...
-                    timestamp -= stopwatch_state->seconds_counted;
-                    // and resume from the "virtual" start time that's that many seconds ago.
-                    stopwatch_state->start_time = watch_utility_date_time_from_unix_time(timestamp, 0);
-                }
-                // schedule our keepalive task when running...
-                movement_schedule_background_task(distant_future);
-            } else {
-                // and cancel it when stopped.
-                movement_cancel_background_task();
-            }
-            break;
-        case EVENT_TIMEOUT:
-            // explicitly ignore the timeout event so we stay on screen
-            break;
-        case EVENT_LOW_ENERGY_UPDATE:
-            if (!watch_sleep_animation_is_running()) watch_start_sleep_animation(1000);
-            if (!stopwatch_state->running) {
-                // since the tick animation is running, displaying the stopped time could be misleading,
-                // as it could imply that the stopwatch is running. instead, show a blank display to
-                // indicate that we are in sleep mode.
-                watch_display_string("st  ----  ", 0);
-            } else {
-                // this OTOH shouldn't happen anymore; if we're running, we shouldn't enter low energy mode
-                _stopwatch_face_update_display(stopwatch_state, false);
-                watch_set_indicator(WATCH_INDICATOR_BELL);
-            }
-            break;
-        default:
-            return movement_default_loop_handler(event);
-    }
-
-    return true;
-}
-
-void stopwatch_face_resign(void *context) {
-    (void) context;
-
-    // regardless of whether we're running or stopped, cancel the task
-    // that was keeping us awake while on screen.
-    movement_cancel_background_task();
-}
diff --git a/legacy/watch_faces/complication/stopwatch_face.h b/legacy/watch_faces/complication/stopwatch_face.h
deleted file mode 100644 (file)
index 5f9d66b..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * MIT License
- *
- * Copyright (c) 2022 Wesley Ellis
- * Copyright (c) 2022 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.
- */
-
-#ifndef STOPWATCH_FACE_H_
-#define STOPWATCH_FACE_H_
-
-/*
- * STOPWATCH FACE
- *
- * The Stopwatch face provides basic stopwatch functionality: you can start
- * and stop the stopwatch with the alarm button. Pressing the light button
- * when the timer is stopped resets it.
- *
- * This face does not count sub-seconds.
- * See also: "fast_stopwatch_face.h"
- */
-
-#include "movement.h"
-
-typedef struct {
-    bool running;
-    watch_date_time_t start_time; // while running, show the difference between this time and now
-    uint32_t seconds_counted;   // set this value when paused, and show that instead.
-} stopwatch_state_t;
-
-void stopwatch_face_setup(uint8_t watch_face_index, void ** context_ptr);
-void stopwatch_face_activate(void *context);
-bool stopwatch_face_loop(movement_event_t event, void *context);
-void stopwatch_face_resign(void *context);
-
-#define stopwatch_face ((const watch_face_t){ \
-    stopwatch_face_setup, \
-    stopwatch_face_activate, \
-    stopwatch_face_loop, \
-    stopwatch_face_resign, \
-    NULL, \
-})
-
-#endif // STOPWATCH_FACE_H_
index fd01eac59f8d49af45d8eab035237b7884dd5546..cd972ab9091246bbe3092970c3be10389726e84f 100644 (file)
@@ -31,6 +31,7 @@ const watch_face_t watch_faces[] = {
     clock_face,
     world_clock_face,
     sunrise_sunset_face,
+    stopwatch_face,
     countdown_face,
     beats_face,
     voltage_face,
index cda7e147b7d2d4018c49c9a4105dda0df85573a3..35e4f00db1113060a1e604accb710771a7301e90 100644 (file)
@@ -29,6 +29,7 @@
 #include "world_clock_face.h"
 #include "advanced_alarm_face.h"
 #include "countdown_face.h"
+#include "stopwatch_face.h"
 #include "fast_stopwatch_face.h"
 #include "sunrise_sunset_face.h"
 #include "character_set_face.h"
index 1813a18becbed45a15eb7b6efd68567fc12da4ac..5be1c78cd7abe35ed1fee60fa82e8e18ecebb99f 100644 (file)
@@ -4,6 +4,7 @@ SRCS += \
   ./watch-faces/clock/world_clock_face.c \
   ./watch-faces/complication/advanced_alarm_face.c \
   ./watch-faces/complication/countdown_face.c \
+  ./watch-faces/complication/stopwatch_face.c \
   ./watch-faces/complication/fast_stopwatch_face.c \
   ./watch-faces/complication/sunrise_sunset_face.c \
   ./watch-faces/demo/accel_interrupt_count_face.c \
diff --git a/watch-faces/complication/stopwatch_face.c b/watch-faces/complication/stopwatch_face.c
new file mode 100644 (file)
index 0000000..83293ef
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022 Wesley Ellis
+ * Copyright (c) 2022 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 <stdlib.h>
+#include <string.h>
+#include "stopwatch_face.h"
+#include "watch.h"
+#include "watch_utility.h"
+
+// distant future for background task: January 1, 2083
+// see stopwatch_face_activate for details
+static const watch_date_time_t distant_future = {
+    .unit = {0, 0, 0, 1, 1, 63}
+};
+
+void stopwatch_face_setup(uint8_t watch_face_index, void ** context_ptr) {
+    (void) watch_face_index;
+    if (*context_ptr == NULL) {
+        *context_ptr = malloc(sizeof(stopwatch_state_t));
+        memset(*context_ptr, 0, sizeof(stopwatch_state_t));
+    }
+}
+
+static void _stopwatch_face_update_display(stopwatch_state_t *stopwatch_state, bool show_seconds) {
+    if (stopwatch_state->running) {
+        watch_date_time_t now = watch_rtc_get_date_time();
+        uint32_t now_timestamp = watch_utility_date_time_to_unix_time(now, 0);
+        uint32_t start_timestamp = watch_utility_date_time_to_unix_time(stopwatch_state->start_time, 0);
+        stopwatch_state->seconds_counted = now_timestamp - start_timestamp;
+    }
+
+    if (stopwatch_state->seconds_counted >= 3456000) {
+        // display maxes out just shy of 40 days, thanks to the limit on the day digits (0-39)
+        stopwatch_state->running = false;
+        movement_cancel_background_task();
+        watch_display_text(WATCH_POSITION_TOP_RIGHT, "39");
+        watch_display_text(WATCH_POSITION_BOTTOM, "235959");
+        return;
+    }
+
+    watch_duration_t duration = watch_utility_seconds_to_duration(stopwatch_state->seconds_counted);
+    char buf[14];
+
+    sprintf(buf, "%02d%02d  ", duration.hours, duration.minutes);
+    watch_display_text(WATCH_POSITION_BOTTOM, buf);
+
+    if (duration.days != 0) {
+        sprintf(buf, "%2d", (uint8_t)duration.days);
+        watch_display_text(WATCH_POSITION_TOP_RIGHT, buf);
+    }
+
+    if (show_seconds) {
+        sprintf(buf, "%02d", duration.seconds);
+        watch_display_text(WATCH_POSITION_SECONDS, buf);
+    }
+}
+
+void stopwatch_face_activate(void *context) {
+    if (watch_sleep_animation_is_running()) watch_stop_sleep_animation();
+
+    stopwatch_state_t *stopwatch_state = (stopwatch_state_t *)context;
+    if (stopwatch_state->running) {
+        // because the low power update happens on the minute mark, and the wearer could start
+        // the stopwatch anytime, the low power update could fire up to 59 seconds later than
+        // we need it to, causing the stopwatch to display stale data.
+        // So let's schedule a background task that will never fire. This will keep the watch
+        // from entering low energy mode while the stopwatch is on screen. This background task
+        // will remain scheduled until the stopwatch stops OR this watch face resigns.
+        movement_schedule_background_task(distant_future);
+    }
+}
+
+bool stopwatch_face_loop(movement_event_t event, void *context) {
+    stopwatch_state_t *stopwatch_state = (stopwatch_state_t *)context;
+
+    switch (event.event_type) {
+        case EVENT_ACTIVATE:
+            watch_set_colon();
+            watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, "STW", "ST");
+            // fall through
+        case EVENT_TICK:
+            if (stopwatch_state->start_time.reg == 0) {
+                watch_display_text(WATCH_POSITION_TOP_RIGHT, "  ");
+                watch_display_text(WATCH_POSITION_BOTTOM, "000000");
+            } else {
+                _stopwatch_face_update_display(stopwatch_state, true);
+            }
+            break;
+        case EVENT_LIGHT_BUTTON_DOWN:
+            movement_illuminate_led();
+            if (!stopwatch_state->running) {
+                stopwatch_state->start_time.reg = 0;
+                stopwatch_state->seconds_counted = 0;
+                watch_display_text(WATCH_POSITION_BOTTOM, "000000");
+            }
+            break;
+        case EVENT_ALARM_BUTTON_DOWN:
+            if (movement_button_should_sound()) {
+                watch_buzzer_play_note(BUZZER_NOTE_C8, 50);
+            }
+            stopwatch_state->running = !stopwatch_state->running;
+            if (stopwatch_state->running) {
+                // we're running now, so we need to set the start_time.
+                if (stopwatch_state->start_time.reg == 0) {
+                    // if starting from the reset state, easy: we start now.
+                    stopwatch_state->start_time = watch_rtc_get_date_time();
+                } else {
+                    // if resuming with time already on the clock, the original start time isn't valid anymore!
+                    // so let's fetch the current time...
+                    uint32_t timestamp = watch_utility_date_time_to_unix_time(watch_rtc_get_date_time(), 0);
+                    // ...subtract the seconds we've already counted...
+                    timestamp -= stopwatch_state->seconds_counted;
+                    // and resume from the "virtual" start time that's that many seconds ago.
+                    stopwatch_state->start_time = watch_utility_date_time_from_unix_time(timestamp, 0);
+                }
+                // schedule our keepalive task when running...
+                movement_schedule_background_task(distant_future);
+            } else {
+                // and cancel it when stopped.
+                movement_cancel_background_task();
+            }
+            break;
+        case EVENT_TIMEOUT:
+            // explicitly ignore the timeout event so we stay on screen
+            break;
+        case EVENT_LOW_ENERGY_UPDATE:
+            if (!watch_sleep_animation_is_running()) watch_start_sleep_animation(1000);
+            if (!stopwatch_state->running) {
+                // since the tick animation is running, displaying the stopped time could be misleading,
+                // as it could imply that the stopwatch is running. instead, show a blank display to
+                // indicate that we are in sleep mode.
+                watch_display_text(WATCH_POSITION_BOTTOM, "----  ");
+            } else {
+                // this OTOH shouldn't happen anymore; if we're running, we shouldn't enter low energy mode
+                _stopwatch_face_update_display(stopwatch_state, false);
+                watch_set_indicator(WATCH_INDICATOR_BELL);
+            }
+            break;
+        default:
+            return movement_default_loop_handler(event);
+    }
+
+    return true;
+}
+
+void stopwatch_face_resign(void *context) {
+    (void) context;
+
+    // regardless of whether we're running or stopped, cancel the task
+    // that was keeping us awake while on screen.
+    movement_cancel_background_task();
+}
diff --git a/watch-faces/complication/stopwatch_face.h b/watch-faces/complication/stopwatch_face.h
new file mode 100644 (file)
index 0000000..5f9d66b
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022 Wesley Ellis
+ * Copyright (c) 2022 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.
+ */
+
+#ifndef STOPWATCH_FACE_H_
+#define STOPWATCH_FACE_H_
+
+/*
+ * STOPWATCH FACE
+ *
+ * The Stopwatch face provides basic stopwatch functionality: you can start
+ * and stop the stopwatch with the alarm button. Pressing the light button
+ * when the timer is stopped resets it.
+ *
+ * This face does not count sub-seconds.
+ * See also: "fast_stopwatch_face.h"
+ */
+
+#include "movement.h"
+
+typedef struct {
+    bool running;
+    watch_date_time_t start_time; // while running, show the difference between this time and now
+    uint32_t seconds_counted;   // set this value when paused, and show that instead.
+} stopwatch_state_t;
+
+void stopwatch_face_setup(uint8_t watch_face_index, void ** context_ptr);
+void stopwatch_face_activate(void *context);
+bool stopwatch_face_loop(movement_event_t event, void *context);
+void stopwatch_face_resign(void *context);
+
+#define stopwatch_face ((const watch_face_t){ \
+    stopwatch_face_setup, \
+    stopwatch_face_activate, \
+    stopwatch_face_loop, \
+    stopwatch_face_resign, \
+    NULL, \
+})
+
+#endif // STOPWATCH_FACE_H_