]> git.earman.xyz Git - sensor-watch.git/commitdiff
early work on background tasks, documentation
authorJoey Castillo <jose.castillo@gmail.com>
Sat, 16 Oct 2021 20:03:27 +0000 (16:03 -0400)
committerJoey Castillo <jose.castillo@gmail.com>
Sat, 16 Oct 2021 20:03:52 +0000 (16:03 -0400)
movement/movement.c
movement/movement.h
movement/watch_faces/clock/simple_clock_face.c
movement/watch_faces/clock/simple_clock_face.h
movement/watch_faces/complications/pulseometer_face.h
movement/watch_faces/settings/preferences_face.h
movement/watch_faces/settings/set_time_face.h

index 03ddb24426817c4fe8e4e2298a37114f2d7e57a0..d43285e18bc739ff60360b98bd9353398e72256b 100644 (file)
@@ -144,7 +144,7 @@ bool app_loop() {
         // this is a little mini-runloop.
         // as long as le_mode_ticks is -1 (i.e. we are in low energy mode), we wake up here, update the screen, and go right back to sleep.
         while (movement_state.le_mode_ticks == -1) {
-            event.event_type = EVENT_LOW_POWER_TICK;
+            event.event_type = EVENT_LOW_ENERGY_UPDATE;
             watch_faces[movement_state.current_watch_face].loop(event, &movement_state.settings, watch_face_contexts[movement_state.current_watch_face]);
             watch_enter_shallow_sleep(true);
         }
@@ -201,7 +201,7 @@ void cb_alarm_btn_extwake() {
 }
 
 void cb_alarm_fired() {
-    event.event_type = EVENT_LOW_POWER_TICK;
+    event.event_type = EVENT_LOW_ENERGY_UPDATE;
 }
 
 void cb_tick() {
index 0ae0cc1336f952a13f400dbeb2abb9d1d5023221..3f3f0365a4e2ab2177a7f9654de9050c78226c6e 100644 (file)
@@ -21,7 +21,8 @@ typedef enum {
     EVENT_NONE = 0,             // There is no event to report.
     EVENT_ACTIVATE,             // Your watch face is entering the foreground.
     EVENT_TICK,                 // Most common event type. Your watch face is being called from the tick callback.
-    EVENT_LOW_POWER_TICK,       // The watch is in low energy mode, and you are getting the once-per-minute tick callback.
+    EVENT_LOW_ENERGY_UPDATE,    // If the watch is in low energy mode and you are in the foreground, you will get a chance to update the display once per minute.
+    EVENT_BACKGROUND_TASK,      // Your watch face is being invoked to perform a background task. Don't update the display here; you may not be in the foreground.
     EVENT_LIGHT_BUTTON_DOWN,    // The light button has been pressed, but not yet released.
     EVENT_LIGHT_BUTTON_UP,      // The light button was pressed and released.
     EVENT_LIGHT_LONG_PRESS,     // The light button was held for >2 seconds, and released.
@@ -38,16 +39,106 @@ typedef struct {
     uint8_t subsecond;
 } movement_event_t;
 
+/** @brief Perform setup for your watch face.
+  * @details It's tempting to say this is 'one-time' setup, but technically this function is called more than
+  *          once. When the watch first boots, this function is called with a NULL context_ptr, indicating
+  *          that it is the first run. At this time you should set context_ptr to something non-NULL if you
+  *          need to keep track of any state in your watch face. If your watch face requires any other setup,
+  *          like configuring a pin mode or a peripheral, you may want to do that here too.
+  *          This function will be called again after waking from sleep mode, since sleep mode disables all
+  *          of the device's pins and peripherals.
+  * @param settings A pointer to the global Movement settings. You can use this to inform how you present your
+  *                 display to the user (i.e. taking into account whether they have silenced the buttons, or if
+  *                 they prefer 12 or 24-hour mode). You can also change these settings if you like.
+  * @param context_ptr A pointer to a pointer; at first invocation, this value will be NULL, and you can set it
+  *                    to any value you like. Subsequent invocations will pass in whatever value you previously
+  *                    set. You may want to check if this is NULL and if so, allocate some space to store any
+  *                    data required for your watch face.
+  *
+  */
 typedef void (*watch_face_setup)(movement_settings_t *settings, void ** context_ptr);
+
+/** @brief Prepare to go on-screen.
+  * @details This function is called just before your watch enters the foreground. If your watch face has any
+  *          segments or text that is always displayed, you may want to set that here. In addition, if your
+  *          watch face depends on data from a peripheral (like an I2C sensor), you will likely want to enable
+  *          that peripheral here. In addition, if your watch face requires an update frequncy other than 1 Hz,
+  *          you may want to request that here using the movement_request_tick_frequency function.
+  * @param settings A pointer to the global Movement settings. @see watch_face_setup.
+  * @param context A pointer to your watch face's context. @see watch_face_setup.
+  *
+  */
 typedef void (*watch_face_activate)(movement_settings_t *settings, void *context);
+
+/** @brief Handle events and update the display.
+  * @details This function is called in response to an event. You should set up a switch statement that handles,
+  *          at the very least, the EVENT_TICK and EVENT_MODE_BUTTON_UP event types. The tick event happens once
+  *          per second (or more frequently if you asked for a faster tick with movement_request_tick_frequency).
+  *          The mode button up event occurs when the user presses the MODE button. **Your loop function SHOULD
+  *          call the movement_move_to_next_face function in response to this event.** If you have a good reason
+  *          to override this behavior (e.g. your user interface requires all three buttons), your watch face MUST
+  *          call the movement_move_to_next_face function in response to the EVENT_MODE_LONG_PRESS event. If you
+  *          fail to do this, the user will become stuck on your watch face.
+  * @param event A struct containing information about the event, including its type. @see movement_event_type_t
+  *              for a list of all possible event types.
+  * @param settings A pointer to the global Movement settings. @see watch_face_setup.
+  * @param context A pointer to your application's context. @see watch_face_setup.
+  * @return true if Movement can enter STANDBY mode; false to keep it awake. You should almost always return true.
+  * @note There are two event types that require some extra thought:
+          The EVENT_LOW_ENERGY_UPDATE event type is a special case. If you are in the foreground when the watch
+          goes into low energy mode, you will receive this tick once a minute (at the top of the minute) so that
+          you can update the screen. Great! But! When you receive this event, all pins and peripherals other than
+          the RTC will have been disabled to save energy. If your display is clock or calendar oriented, this is
+          fine. But if your display requires polling an I2C sensor or reading a value with the ADC, you won't be
+          able to do this. You should either display the name of the watch face in response to the low power tick,
+          or ensure that you resign before low power mode triggers, (e.g. by calling movement_move_to_face(0)).
+          **Your watch face MUST NOT wake up peripherals in response to a low power tick.** The purpose of this
+          mode is to consume as little energy as possible during the (potentially long) intervals when it's
+          unlikely the user is wearing or looking at the watch.
+          EVENT_BACKGROUND_TASK is also a special case. @see watch_face_wants_background_task for details.
+  */
 typedef bool (*watch_face_loop)(movement_event_t event, movement_settings_t *settings, void *context);
+
+/** @brief Prepare to go off-screen.
+  * @details This function is called before your watch face enters the background. If you requested a tick
+  *          frequency other than the standard 1 Hz, **you must call movement_request_tick_frequency(1) here**
+  *          to reset to 1 Hz. You should also disable any peripherals you enabled when you entered the foreground.
+  * @param settings A pointer to the global Movement settings. @see watch_face_setup.
+  * @param context A pointer to your application's context. @see watch_face_setup.
+  */
 typedef void (*watch_face_resign)(movement_settings_t *settings, void *context);
 
+/** @brief OPTIONAL. Request an opportunity to run a background task.
+  * @warning NOT YET IMPLEMENTED.
+  * @details Most apps will not need this function, but if you provide it, Movement will call it once per minute in
+  *          both active and low power modes, regardless of whether your app is in the foreground. You can check the
+  *          current time to determine whether you require a background task. If you return true here, Movement will
+  *          immediately call your loop function with an EVENT_BACKGROUND_TASK event. Note that it will not call your
+  *          activate or deactivate functions, since you are not going on screen.
+  *
+  *          Examples of background tasks:
+  *           - Wake and play a sound when an alarm or timer has been triggered.
+  *           - Check the state of an RTC interrupt pin or the timestamp of an RTC interrupt event.
+  *           - Log a data point from a sensor, and then return to sleep mode.
+  *
+  *          Guidelines for background tasks:
+  *           - Assume all peripherals and pins other than the RTC will be disabled when you get an EVENT_BACKGROUND_TASK.
+  *           - Even if your background task involves only the RTC peripheral, try to request background tasks sparingly.
+  *           - If your background task involves an external pin or peripheral, request background tasks no more than once per hour.
+  *           - If you need to enable a pin or a peripheral to perform your task, return it to its original state afterwards.
+  *
+  * @param settings A pointer to the global Movement settings. @see watch_face_setup.
+  * @param context A pointer to your application's context. @see watch_face_setup.
+  * @return true to request a background task; false otherwise.
+  */
+typedef bool (*watch_face_wants_background_task)(movement_settings_t *settings, void *context);
+
 typedef struct {
     watch_face_setup setup;
     watch_face_activate activate;
     watch_face_loop loop;
     watch_face_resign resign;
+    watch_face_wants_background_task wants_background_task;
 } watch_face_t;
 
 typedef struct {
index 6e58313a1154c74c39af62910ab710eaa324cd5a..351c7ebc1c687f1d75e87f0e0f8ddd1919bb10b4 100644 (file)
@@ -28,16 +28,16 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting
     switch (event.event_type) {
         case EVENT_ACTIVATE:
         case EVENT_TICK:
-        case EVENT_LOW_POWER_TICK:
+        case EVENT_LOW_ENERGY_UPDATE:
             date_time = watch_rtc_get_date_time();
             previous_date_time = *((uint32_t *)context);
             *((uint32_t *)context) = date_time.reg;
 
-            if (date_time.reg >> 6 == previous_date_time >> 6 && event.event_type != EVENT_LOW_POWER_TICK) {
+            if (date_time.reg >> 6 == previous_date_time >> 6 && event.event_type != EVENT_LOW_ENERGY_UPDATE) {
                 // everything before seconds is the same, don't waste cycles setting those segments.
                 pos = 8;
                 sprintf(buf, "%02d", date_time.unit.second);
-            } else if (date_time.reg >> 12 == previous_date_time >> 12 && event.event_type != EVENT_LOW_POWER_TICK) {
+            } else if (date_time.reg >> 12 == previous_date_time >> 12 && event.event_type != EVENT_LOW_ENERGY_UPDATE) {
                 // everything before minutes is the same.
                 pos = 6;
                 sprintf(buf, "%02d%02d", date_time.unit.minute, date_time.unit.second);
@@ -54,7 +54,7 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting
                     if (date_time.unit.hour == 0) date_time.unit.hour = 12;
                 }
                 pos = 0;
-                if (event.event_type == EVENT_LOW_POWER_TICK) {
+                if (event.event_type == EVENT_LOW_ENERGY_UPDATE) {
                     sprintf(buf, "%s%2d%2d%02d  ", weekdays[simple_clock_face_get_weekday(date_time.unit.year, date_time.unit.month, date_time.unit.day)], date_time.unit.day, date_time.unit.hour, date_time.unit.minute);
                 } else {
                     sprintf(buf, "%s%2d%2d%02d%02d", weekdays[simple_clock_face_get_weekday(date_time.unit.year, date_time.unit.month, date_time.unit.day)], date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second);
index 3cdc09e872f8e8fd76c65806a5735593699d761d..7daaba3e3093135e30948a6e3bedb9cc68a2337d 100644 (file)
@@ -15,6 +15,7 @@ uint8_t simple_clock_face_get_weekday(uint16_t day, uint16_t month, uint16_t yea
     simple_clock_face_activate, \
     simple_clock_face_loop, \
     simple_clock_face_resign, \
+    NULL, \
 }
 
 #endif // FAKE_FACE_H_
\ No newline at end of file
index f8e69d27c6332b2b03f7e5fae08c10fc6f350c21..5a9c66b0c85e1409b5302a08d0cb9db95d3cd9af 100644 (file)
@@ -19,6 +19,7 @@ void pulseometer_face_resign(movement_settings_t *settings, void *context);
     pulseometer_face_activate, \
     pulseometer_face_loop, \
     pulseometer_face_resign, \
+    NULL, \
 }
 
 #endif // PULSEOMETER_FACE_H_
\ No newline at end of file
index c8f1a14e61644e86fdcc3901af1ba59940efb900..5b735db978dc7f45365bac8d7c87166d0f4fd887 100644 (file)
@@ -13,6 +13,7 @@ void preferences_face_resign(movement_settings_t *settings, void *context);
     preferences_face_activate, \
     preferences_face_loop, \
     preferences_face_resign, \
+    NULL, \
 }
 
 #endif // PREFERENCES_FACE_H_
\ No newline at end of file
index b330d85217074582c981d4896b532437d2b160f8..06dbe2cec4411be6302e1eeb0449fffa9e3386b9 100644 (file)
@@ -13,6 +13,7 @@ void set_time_face_resign(movement_settings_t *settings, void *context);
     set_time_face_activate, \
     set_time_face_loop, \
     set_time_face_resign, \
+    NULL, \
 }
 
 #endif // SET_TIME_FACE_H_