launcher: first crack at low power 'screensaver' mode
This commit is contained in:
		
							parent
							
								
									64485b4255
								
							
						
					
					
						commit
						4300dff616
					
				| @ -157,7 +157,7 @@ bool app_loop() { | |||||||
|         delay_ms(250); |         delay_ms(250); | ||||||
| 
 | 
 | ||||||
|         // nap time :)
 |         // nap time :)
 | ||||||
|         watch_enter_shallow_sleep(NULL); |         watch_enter_shallow_sleep(false); | ||||||
| 
 | 
 | ||||||
|         // we just woke up; wait a moment again for the user's finger to be off the button...
 |         // we just woke up; wait a moment again for the user's finger to be off the button...
 | ||||||
|         delay_ms(250); |         delay_ms(250); | ||||||
|  | |||||||
| @ -1,19 +1,27 @@ | |||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
| #include <string.h> | #include <string.h> | ||||||
|  | #include <limits.h> | ||||||
| #include "watch.h" | #include "watch.h" | ||||||
| #include "launcher.h" | #include "launcher.h" | ||||||
| #include "launcher_config.h" | #include "launcher_config.h" | ||||||
| 
 | 
 | ||||||
| LauncherState launcher_state; | LauncherState launcher_state; | ||||||
| void * widget_contexts[LAUNCHER_NUM_WIDGETS]; | void * widget_contexts[LAUNCHER_NUM_WIDGETS]; | ||||||
|  | const int32_t launcher_screensaver_deadlines[8] = {INT_MAX, 3600, 7200, 21600, 43200, 86400, 172800, 604800}; | ||||||
| 
 | 
 | ||||||
| void cb_mode_pressed(); | void cb_mode_btn_interrupt(); | ||||||
| void cb_light_pressed(); | void cb_light_btn_interrupt(); | ||||||
| void cb_alarm_pressed(); | void cb_alarm_btn_interrupt(); | ||||||
|  | void cb_alarm_btn_extwake(); | ||||||
|  | void cb_alarm_fired(); | ||||||
| void cb_tick(); | void cb_tick(); | ||||||
| 
 | 
 | ||||||
|  | static inline void _launcher_reset_screensaver_countdown() { | ||||||
|  |     // for testing, make the timeout happen 60x faster.
 | ||||||
|  |     launcher_state.screensaver_ticks = launcher_screensaver_deadlines[launcher_state.launcher_settings.bit.screensaver_interval] / 60; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void launcher_request_tick_frequency(uint8_t freq) { | void launcher_request_tick_frequency(uint8_t freq) { | ||||||
|     // FIXME: there is an issue where after changing tick frequencies on a widget switch, something glitchy happens on the next one.
 |  | ||||||
|     watch_rtc_disable_all_periodic_callbacks(); |     watch_rtc_disable_all_periodic_callbacks(); | ||||||
|     launcher_state.subsecond = 0; |     launcher_state.subsecond = 0; | ||||||
|     launcher_state.tick_frequency = freq; |     launcher_state.tick_frequency = freq; | ||||||
| @ -39,10 +47,11 @@ void launcher_move_to_next_widget() { | |||||||
| 
 | 
 | ||||||
| void app_init() { | void app_init() { | ||||||
|     memset(&launcher_state, 0, sizeof(launcher_state)); |     memset(&launcher_state, 0, sizeof(launcher_state)); | ||||||
|  | 
 | ||||||
|     launcher_state.launcher_settings.bit.led_green_color = 0xF; |     launcher_state.launcher_settings.bit.led_green_color = 0xF; | ||||||
|     launcher_state.launcher_settings.bit.button_should_sound = true; |     launcher_state.launcher_settings.bit.button_should_sound = true; | ||||||
|     watch_date_time date_time = watch_rtc_get_date_time(); |     launcher_state.launcher_settings.bit.screensaver_interval = 1; | ||||||
|     watch_rtc_set_date_time(date_time); |     _launcher_reset_screensaver_countdown(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void app_wake_from_deep_sleep() { | void app_wake_from_deep_sleep() { | ||||||
| @ -50,10 +59,13 @@ void app_wake_from_deep_sleep() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void app_setup() { | void app_setup() { | ||||||
|  |     if (launcher_state.screensaver_ticks != -1) { | ||||||
|  |         watch_disable_extwake_interrupt(BTN_ALARM); | ||||||
|  | 
 | ||||||
|         watch_enable_external_interrupts(); |         watch_enable_external_interrupts(); | ||||||
|     watch_register_interrupt_callback(BTN_MODE, cb_mode_pressed, INTERRUPT_TRIGGER_BOTH); |         watch_register_interrupt_callback(BTN_MODE, cb_mode_btn_interrupt, INTERRUPT_TRIGGER_BOTH); | ||||||
|     watch_register_interrupt_callback(BTN_LIGHT, cb_light_pressed, INTERRUPT_TRIGGER_BOTH); |         watch_register_interrupt_callback(BTN_LIGHT, cb_light_btn_interrupt, INTERRUPT_TRIGGER_BOTH); | ||||||
|     watch_register_interrupt_callback(BTN_ALARM, cb_alarm_pressed, INTERRUPT_TRIGGER_BOTH); |         watch_register_interrupt_callback(BTN_ALARM, cb_alarm_btn_interrupt, INTERRUPT_TRIGGER_BOTH); | ||||||
| 
 | 
 | ||||||
|         watch_enable_buzzer(); |         watch_enable_buzzer(); | ||||||
|         watch_enable_leds(); |         watch_enable_leds(); | ||||||
| @ -68,6 +80,7 @@ void app_setup() { | |||||||
|         widgets[0].activate(&launcher_state.launcher_settings, widget_contexts[launcher_state.current_widget]); |         widgets[0].activate(&launcher_state.launcher_settings, widget_contexts[launcher_state.current_widget]); | ||||||
|         widgets[0].loop(EVENT_ACTIVATE, &launcher_state.launcher_settings, 0, widget_contexts[launcher_state.current_widget]); |         widgets[0].loop(EVENT_ACTIVATE, &launcher_state.launcher_settings, 0, widget_contexts[launcher_state.current_widget]); | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| void app_prepare_for_sleep() { | void app_prepare_for_sleep() { | ||||||
| } | } | ||||||
| @ -106,6 +119,19 @@ bool app_loop() { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // if we have timed out of our screensaver countdown, enter screensaver mode.
 | ||||||
|  |     if (launcher_state.screensaver_ticks == 0) { | ||||||
|  |         launcher_state.screensaver_ticks = -1; | ||||||
|  |         watch_date_time alarm_time; | ||||||
|  |         alarm_time.reg = 0; | ||||||
|  |         alarm_time.unit.second = 59; // after a match, the alarm fires at the next rising edge of CLK_RTC_CNT, so 59 seconds lets us update at :00
 | ||||||
|  |         watch_rtc_register_alarm_callback(cb_alarm_fired, alarm_time, ALARM_MATCH_SS); | ||||||
|  |         watch_register_extwake_callback(BTN_ALARM, cb_alarm_btn_extwake, true); | ||||||
|  |         widgets[launcher_state.current_widget].loop(EVENT_SCREENSAVER, &launcher_state.launcher_settings, 0, widget_contexts[launcher_state.current_widget]); | ||||||
|  |         event = EVENT_SCREENSAVER; | ||||||
|  |         watch_enter_shallow_sleep(true); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     if (event) { |     if (event) { | ||||||
|         widgets[launcher_state.current_widget].loop(event, &launcher_state.launcher_settings, launcher_state.subsecond, widget_contexts[launcher_state.current_widget]); |         widgets[launcher_state.current_widget].loop(event, &launcher_state.launcher_settings, launcher_state.subsecond, widget_contexts[launcher_state.current_widget]); | ||||||
|         event = 0; |         event = 0; | ||||||
| @ -130,23 +156,38 @@ LauncherEvent _figure_out_button_event(LauncherEvent button_down_event, uint8_t | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void cb_light_pressed() { | void cb_light_btn_interrupt() { | ||||||
|  |     _launcher_reset_screensaver_countdown(); | ||||||
|     event = _figure_out_button_event(EVENT_LIGHT_BUTTON_DOWN, &launcher_state.light_down_timestamp); |     event = _figure_out_button_event(EVENT_LIGHT_BUTTON_DOWN, &launcher_state.light_down_timestamp); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void cb_mode_pressed() { | void cb_mode_btn_interrupt() { | ||||||
|  |     _launcher_reset_screensaver_countdown(); | ||||||
|     event = _figure_out_button_event(EVENT_MODE_BUTTON_DOWN, &launcher_state.mode_down_timestamp); |     event = _figure_out_button_event(EVENT_MODE_BUTTON_DOWN, &launcher_state.mode_down_timestamp); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void cb_alarm_pressed() { | void cb_alarm_btn_interrupt() { | ||||||
|  |     _launcher_reset_screensaver_countdown(); | ||||||
|     event = _figure_out_button_event(EVENT_ALARM_BUTTON_DOWN, &launcher_state.alarm_down_timestamp); |     event = _figure_out_button_event(EVENT_ALARM_BUTTON_DOWN, &launcher_state.alarm_down_timestamp); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void cb_alarm_btn_extwake() { | ||||||
|  |     _launcher_reset_screensaver_countdown(); | ||||||
|  |     // this is a hack: waking from shallow sleep, app_setup does get called, but it happens before we reset our ticks.
 | ||||||
|  |     // need to figure out if there's a better heuristic for determining how we woke up.
 | ||||||
|  |     app_setup(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void cb_alarm_fired() { | ||||||
|  |     event = EVENT_SCREENSAVER; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void cb_tick() { | void cb_tick() { | ||||||
|     event = EVENT_TICK; |     event = EVENT_TICK; | ||||||
|     watch_date_time date_time = watch_rtc_get_date_time(); |     watch_date_time date_time = watch_rtc_get_date_time(); | ||||||
|     if (date_time.unit.second != launcher_state.last_second) { |     if (date_time.unit.second != launcher_state.last_second) { | ||||||
|         if (launcher_state.light_ticks) launcher_state.light_ticks--; |         if (launcher_state.light_ticks) launcher_state.light_ticks--; | ||||||
|  |         if (launcher_state.launcher_settings.bit.screensaver_interval && launcher_state.screensaver_ticks > 0) launcher_state.screensaver_ticks--; | ||||||
| 
 | 
 | ||||||
|         launcher_state.last_second = date_time.unit.second; |         launcher_state.last_second = date_time.unit.second; | ||||||
|         launcher_state.subsecond = 0; |         launcher_state.subsecond = 0; | ||||||
|  | |||||||
| @ -25,6 +25,7 @@ typedef enum LauncherEvent { | |||||||
|     EVENT_NONE = 0,             // There is no event to report.
 |     EVENT_NONE = 0,             // There is no event to report.
 | ||||||
|     EVENT_ACTIVATE,             // Your widget is entering the foreground.
 |     EVENT_ACTIVATE,             // Your widget is entering the foreground.
 | ||||||
|     EVENT_TICK,                 // Most common event type. Your widget is being called from the tick callback.
 |     EVENT_TICK,                 // Most common event type. Your widget is being called from the tick callback.
 | ||||||
|  |     EVENT_SCREENSAVER,          // Your widget is being asked to display its output for screensaver mode.
 | ||||||
|     EVENT_LIGHT_BUTTON_DOWN,    // The light button has been pressed, but not yet released.
 |     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_BUTTON_UP,      // The light button was pressed and released.
 | ||||||
|     EVENT_LIGHT_LONG_PRESS,     // The light button was held for >2 seconds, and released.
 |     EVENT_LIGHT_LONG_PRESS,     // The light button was held for >2 seconds, and released.
 | ||||||
| @ -65,6 +66,9 @@ typedef struct LauncherState { | |||||||
|     uint8_t mode_down_timestamp; |     uint8_t mode_down_timestamp; | ||||||
|     uint8_t alarm_down_timestamp; |     uint8_t alarm_down_timestamp; | ||||||
| 
 | 
 | ||||||
|  |     // screensaver countdown
 | ||||||
|  |     int32_t screensaver_ticks; | ||||||
|  | 
 | ||||||
|     // stuff for subsecond tracking
 |     // stuff for subsecond tracking
 | ||||||
|     uint8_t tick_frequency; |     uint8_t tick_frequency; | ||||||
|     uint8_t last_second; |     uint8_t last_second; | ||||||
|  | |||||||
| @ -27,17 +27,18 @@ void simple_clock_widget_loop(LauncherEvent event, LauncherSettings *settings, u | |||||||
|     watch_date_time date_time; |     watch_date_time date_time; | ||||||
|     uint32_t previous_date_time; |     uint32_t previous_date_time; | ||||||
|     switch (event) { |     switch (event) { | ||||||
|         case EVENT_TICK: |  | ||||||
|         case EVENT_ACTIVATE: |         case EVENT_ACTIVATE: | ||||||
|  |         case EVENT_TICK: | ||||||
|  |         case EVENT_SCREENSAVER: | ||||||
|             date_time = watch_rtc_get_date_time(); |             date_time = watch_rtc_get_date_time(); | ||||||
|             previous_date_time = *((uint32_t *)context); |             previous_date_time = *((uint32_t *)context); | ||||||
|             *((uint32_t *)context) = date_time.reg; |             *((uint32_t *)context) = date_time.reg; | ||||||
| 
 | 
 | ||||||
|             if (date_time.reg >> 6 == previous_date_time >> 6) { |             if (date_time.reg >> 6 == previous_date_time >> 6 && event != EVENT_SCREENSAVER) { | ||||||
|                 // everything before seconds is the same, don't waste cycles setting those segments.
 |                 // everything before seconds is the same, don't waste cycles setting those segments.
 | ||||||
|                 pos = 8; |                 pos = 8; | ||||||
|                 sprintf(buf, "%02d", date_time.unit.second); |                 sprintf(buf, "%02d", date_time.unit.second); | ||||||
|             } else if (date_time.reg >> 12 == previous_date_time >> 12) { |             } else if (date_time.reg >> 12 == previous_date_time >> 12 && event != EVENT_SCREENSAVER) { | ||||||
|                 // everything before minutes is the same.
 |                 // everything before minutes is the same.
 | ||||||
|                 pos = 6; |                 pos = 6; | ||||||
|                 sprintf(buf, "%02d%02d", date_time.unit.minute, date_time.unit.second); |                 sprintf(buf, "%02d%02d", date_time.unit.minute, date_time.unit.second); | ||||||
| @ -54,8 +55,12 @@ void simple_clock_widget_loop(LauncherEvent event, LauncherSettings *settings, u | |||||||
|                     if (date_time.unit.hour == 0) date_time.unit.hour = 12; |                     if (date_time.unit.hour == 0) date_time.unit.hour = 12; | ||||||
|                 } |                 } | ||||||
|                 pos = 0; |                 pos = 0; | ||||||
|  |                 if (event == EVENT_SCREENSAVER) { | ||||||
|  |                     sprintf(buf, "%s%2d%2d%02d  ", weekdays[simple_clock_widget_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_widget_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); |                     sprintf(buf, "%s%2d%2d%02d%02d", weekdays[simple_clock_widget_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); | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|             watch_display_string(buf, pos); |             watch_display_string(buf, pos); | ||||||
|             break; |             break; | ||||||
|         case EVENT_MODE_BUTTON_UP: |         case EVENT_MODE_BUTTON_UP: | ||||||
|  | |||||||
| @ -151,11 +151,8 @@ void _watch_disable_all_peripherals_except_slcd() { | |||||||
|     MCLK->APBCMASK.reg &= ~MCLK_APBCMASK_SERCOM3; |     MCLK->APBCMASK.reg &= ~MCLK_APBCMASK_SERCOM3; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void watch_enter_shallow_sleep(char *message) { | void watch_enter_shallow_sleep(bool display_on) { | ||||||
|     if (message != NULL) { |     if (!display_on) { | ||||||
|         watch_display_string("          ", 0); |  | ||||||
|         watch_display_string(message, 0); |  | ||||||
|     } else { |  | ||||||
|         slcd_sync_deinit(&SEGMENT_LCD_0); |         slcd_sync_deinit(&SEGMENT_LCD_0); | ||||||
|         hri_mclk_clear_APBCMASK_SLCD_bit(SLCD); |         hri_mclk_clear_APBCMASK_SLCD_bit(SLCD); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -81,13 +81,8 @@ uint32_t watch_get_backup_data(uint8_t reg); | |||||||
|   *        the LCD. You can wake from this mode by pressing the ALARM button, if you have an registered an |   *        the LCD. You can wake from this mode by pressing the ALARM button, if you have an registered an | ||||||
|   *        external wake callback on the ALARM button. When your app wakes from this shallow sleep mode, your |   *        external wake callback on the ALARM button. When your app wakes from this shallow sleep mode, your | ||||||
|   *        app_setup method will be called, since this function will have disabled things you set up. |   *        app_setup method will be called, since this function will have disabled things you set up. | ||||||
|   * @param message Either NULL, or a string representing a message to display while in shallow sleep mode. If |   * @param display_on if true, leaves the LCD on to display whatever content was on-screen. If false, disables | ||||||
|   *                this parameter is NULL, the screen will be blanked out, and this function will disable the |   *                   the segment LCD controller for additional power savings. | ||||||
|   *                SLCD peripheral for additional power savings. If the message is non-NULL, it will replace |  | ||||||
|   *                any text on the screen, and will be displayed at position 0 (so you should pad out the beginning |  | ||||||
|   *                of the string with spaces if you wish for the message to appear on line 2, i.e. "    SLEEP"). |  | ||||||
|   *                Also note that this function will NOT clear any indicator segments that you have set. This is |  | ||||||
|   *                by design, in case you wish to leave an indicator lit in sleep mode. |  | ||||||
|   * @details This shallow sleep mode is not the lowest power mode available (see watch_enter_deep_sleep), but |   * @details This shallow sleep mode is not the lowest power mode available (see watch_enter_deep_sleep), but | ||||||
|   *          it has the benefit of retaining your application state and being able to wake from the ALARM button. |   *          it has the benefit of retaining your application state and being able to wake from the ALARM button. | ||||||
|   *          It also provides an option for displaying a message to the user when asleep. Note that whether you |   *          It also provides an option for displaying a message to the user when asleep. Note that whether you | ||||||
| @ -96,10 +91,10 @@ uint32_t watch_get_backup_data(uint8_t reg); | |||||||
|   * |   * | ||||||
|   *          Power consumption in shallow sleep mode varies a bit with the battery voltage and the temperature, |   *          Power consumption in shallow sleep mode varies a bit with the battery voltage and the temperature, | ||||||
|   *          but at 3 V and 25~30° C you can roughly estimate: |   *          but at 3 V and 25~30° C you can roughly estimate: | ||||||
|   *           * < 12µA current draw with the LCD controller on (message != NULL) |   *           * < 12µA current draw with the LCD controller on | ||||||
|   *           * < 6µA current draw with the LCD controller off (message == NULL) |   *           * < 6µA current draw with the LCD controller off | ||||||
|   */ |   */ | ||||||
| void watch_enter_shallow_sleep(char *message); | void watch_enter_shallow_sleep(bool display_on); | ||||||
| 
 | 
 | ||||||
| /** @brief Enters the SAM L22's lowest-power mode, BACKUP.
 | /** @brief Enters the SAM L22's lowest-power mode, BACKUP.
 | ||||||
|   * @details This function does some housekeeping before entering BACKUP mode. It first disables all |   * @details This function does some housekeeping before entering BACKUP mode. It first disables all | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user