From e308a9ec1134412f6194358790176d17b0724ef1 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Fri, 27 Nov 2020 16:02:17 -0500 Subject: [PATCH] working! PinAlarm not implemented yet. --- locale/circuitpython.pot | 25 +++- ports/esp32s2/common-hal/alarm/__init__.c | 71 +++++++++- .../common-hal/microcontroller/__init__.c | 3 - shared-bindings/alarm/__init__.c | 122 ++++++++++++------ shared-bindings/alarm/__init__.h | 1 + shared-bindings/alarm/pin/PinAlarm.c | 6 +- supervisor/shared/workflow.c | 4 +- 7 files changed, 175 insertions(+), 57 deletions(-) diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 1532a67b54..36fd5f647c 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -8,11 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -<<<<<<< HEAD -"POT-Creation-Date: 2020-11-25 15:08-0500\n" -======= -"POT-Creation-Date: 2020-11-11 16:30+0530\n" ->>>>>>> adafruit/main +"POT-Creation-Date: 2020-11-27 16:03-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -301,6 +297,7 @@ msgid "All I2C peripherals are in use" msgstr "" #: ports/esp32s2/common-hal/countio/Counter.c +#: ports/esp32s2/common-hal/frequencyio/FrequencyIn.c #: ports/esp32s2/common-hal/rotaryio/IncrementalEncoder.c msgid "All PCNT units in use" msgstr "" @@ -337,6 +334,7 @@ msgstr "" #: ports/atmel-samd/common-hal/pulseio/PulseIn.c #: ports/atmel-samd/common-hal/pulseio/PulseOut.c #: ports/cxd56/common-hal/pulseio/PulseOut.c +#: ports/esp32s2/common-hal/frequencyio/FrequencyIn.c #: ports/esp32s2/common-hal/neopixel_write/__init__.c #: ports/esp32s2/common-hal/pulseio/PulseIn.c #: ports/esp32s2/common-hal/pulseio/PulseOut.c @@ -1098,6 +1096,7 @@ msgid "Invalid byteorder string" msgstr "" #: ports/atmel-samd/common-hal/frequencyio/FrequencyIn.c +#: ports/esp32s2/common-hal/frequencyio/FrequencyIn.c msgid "Invalid capture period. Valid range: 1 - 500" msgstr "" @@ -1511,7 +1510,7 @@ msgid "Pin number already reserved by EXTI" msgstr "" #: ports/esp32s2/common-hal/alarm/__init__.c -msgid "PinAlarm deep sleep not yet implemented" +msgid "PinAlarm not yet implemented" msgstr "" #: shared-bindings/rgbmatrix/RGBMatrix.c @@ -1575,7 +1574,7 @@ msgstr "" msgid "RTC calibration is not supported on this board" msgstr "" -#: shared-bindings/time/__init__.c +#: shared-bindings/alarm/time/TimeAlarm.c shared-bindings/time/__init__.c msgid "RTC is not supported on this board" msgstr "" @@ -1721,6 +1720,10 @@ msgstr "" msgid "Supply at least one UART pin" msgstr "" +#: shared-bindings/alarm/time/TimeAlarm.c +msgid "Supply one of monotonic_time or epoch_time" +msgstr "" + #: shared-bindings/gnss/GNSS.c msgid "System entry must be gnss.SatelliteSystem" msgstr "" @@ -1784,6 +1787,10 @@ msgstr "" msgid "Tile width must exactly divide bitmap width" msgstr "" +#: shared-bindings/alarm/time/TimeAlarm.c +msgid "Time is in the past." +msgstr "" + #: ports/nrf/common-hal/_bleio/Adapter.c #, c-format msgid "Timeout is too long: Maximum timeout length is %d seconds" @@ -2498,6 +2505,10 @@ msgstr "" msgid "end_x should be an int" msgstr "" +#: shared-bindings/alarm/time/TimeAlarm.c +msgid "epoch_time not supported on this board" +msgstr "" + #: ports/nrf/common-hal/busio/UART.c #, c-format msgid "error = 0x%08lX" diff --git a/ports/esp32s2/common-hal/alarm/__init__.c b/ports/esp32s2/common-hal/alarm/__init__.c index 767b0de70e..e044103bce 100644 --- a/ports/esp32s2/common-hal/alarm/__init__.c +++ b/ports/esp32s2/common-hal/alarm/__init__.c @@ -37,6 +37,7 @@ #include "esp_log.h" #include "esp_sleep.h" +#include "esp_wifi.h" STATIC mp_obj_tuple_t *_deep_sleep_alarms; @@ -77,13 +78,14 @@ mp_obj_t common_hal_alarm_get_wake_alarm(void) { return mp_const_none; } -STATIC void setup_alarms(size_t n_alarms, const mp_obj_t *alarms) { +// Set up light sleep or deep sleep alarms. +STATIC void setup_sleep_alarms(size_t n_alarms, const mp_obj_t *alarms) { bool time_alarm_set = false; alarm_time_time_alarm_obj_t *time_alarm = MP_OBJ_NULL; for (size_t i = 0; i < n_alarms; i++) { if (MP_OBJ_IS_TYPE(alarms[i], &alarm_pin_pin_alarm_type)) { - mp_raise_NotImplementedError(translate("PinAlarm deep sleep not yet implemented")); + mp_raise_NotImplementedError(translate("PinAlarm not yet implemented")); } else if (MP_OBJ_IS_TYPE(alarms[i], &alarm_time_time_alarm_type)) { if (time_alarm_set) { @@ -98,23 +100,82 @@ STATIC void setup_alarms(size_t n_alarms, const mp_obj_t *alarms) { // Compute how long to actually sleep, considering the time now. mp_float_t now_secs = uint64_to_float(common_hal_time_monotonic_ms()) / 1000.0f; mp_float_t wakeup_in_secs = MAX(0.0f, time_alarm->monotonic_time - now_secs); - esp_sleep_enable_timer_wakeup((uint64_t) (wakeup_in_secs * 1000000)); + const uint64_t sleep_for_us = (uint64_t) (wakeup_in_secs * 1000000); + ESP_LOGI("ALARM", "Sleep for us: %lld", sleep_for_us); + esp_sleep_enable_timer_wakeup(sleep_for_us); } } +mp_obj_t common_hal_alarm_wait_until_alarms(size_t n_alarms, const mp_obj_t *alarms) { + if (n_alarms == 0) { + return mp_const_none; + } + + bool time_alarm_set = false; + alarm_time_time_alarm_obj_t *time_alarm = MP_OBJ_NULL; + + for (size_t i = 0; i < n_alarms; i++) { + if (MP_OBJ_IS_TYPE(alarms[i], &alarm_pin_pin_alarm_type)) { + mp_raise_NotImplementedError(translate("PinAlarm not yet implemented")); + } + else if (MP_OBJ_IS_TYPE(alarms[i], &alarm_time_time_alarm_type)) { + if (time_alarm_set) { + mp_raise_ValueError(translate("Only one alarm.time alarm can be set.")); + } + time_alarm = MP_OBJ_TO_PTR(alarms[i]); + time_alarm_set = true; + } + } + + ESP_LOGI("ALARM", "waiting for alarms"); + + if (time_alarm_set && n_alarms == 1) { + // If we're only checking time, avoid a polling loop, so maybe we can save some power. + const mp_float_t now_secs = uint64_to_float(common_hal_time_monotonic_ms()) / 1000.0f; + const mp_float_t wakeup_in_secs = MAX(0.0f, time_alarm->monotonic_time - now_secs); + const uint32_t delay_ms = (uint32_t) (wakeup_in_secs * 1000.0f); + ESP_LOGI("ALARM", "Delay for ms: %d", delay_ms); + common_hal_time_delay_ms((uint32_t) delay_ms); + } else { + // Poll for alarms. + while (true) { + RUN_BACKGROUND_TASKS; + // Allow ctrl-C interrupt. + if (mp_hal_is_interrupted()) { + return mp_const_none; + } + + // TODO: Check PinAlarms. + + if (time_alarm != MP_OBJ_NULL && + common_hal_time_monotonic_ms() * 1000.f >= time_alarm->monotonic_time) { + return time_alarm; + } + } + } + + return mp_const_none; +} + mp_obj_t common_hal_alarm_sleep_until_alarms(size_t n_alarms, const mp_obj_t *alarms) { - setup_alarms(n_alarms, alarms); + if (n_alarms == 0) { + return mp_const_none; + } + + setup_sleep_alarms(n_alarms, alarms); // Shut down wifi cleanly. esp_wifi_stop(); + ESP_LOGI("ALARM", "start light sleep"); esp_light_sleep_start(); return common_hal_alarm_get_wake_alarm(); } void common_hal_alarm_exit_and_deep_sleep_until_alarms(size_t n_alarms, const mp_obj_t *alarms) { - setup_alarms(n_alarms, alarms); + setup_sleep_alarms(n_alarms, alarms); // Shut down wifi cleanly. esp_wifi_stop(); + ESP_LOGI("ALARM", "start deep sleep"); esp_deep_sleep_start(); } diff --git a/ports/esp32s2/common-hal/microcontroller/__init__.c b/ports/esp32s2/common-hal/microcontroller/__init__.c index 184f5be696..b7bea4e6b8 100644 --- a/ports/esp32s2/common-hal/microcontroller/__init__.c +++ b/ports/esp32s2/common-hal/microcontroller/__init__.c @@ -42,9 +42,6 @@ #include "freertos/FreeRTOS.h" -#include "esp_sleep.h" -#include "esp_wifi.h" - void common_hal_mcu_delay_us(uint32_t delay) { mp_hal_delay_us(delay); } diff --git a/shared-bindings/alarm/__init__.c b/shared-bindings/alarm/__init__.c index 4c2189c0d0..195ec63745 100644 --- a/shared-bindings/alarm/__init__.c +++ b/shared-bindings/alarm/__init__.c @@ -25,40 +25,46 @@ */ #include "py/obj.h" +#include "py/reload.h" #include "py/runtime.h" #include "shared-bindings/alarm/__init__.h" #include "shared-bindings/alarm/pin/PinAlarm.h" #include "shared-bindings/alarm/time/TimeAlarm.h" +#include "shared-bindings/supervisor/Runtime.h" #include "shared-bindings/time/__init__.h" -#include "supervisor/shared/rgb_led_status.h" +#include "supervisor/shared/autoreload.h" #include "supervisor/shared/workflow.h" -// Wait this long to see if USB is being connected (enumeration starting). -#define CIRCUITPY_USB_CONNECTING_DELAY 1 -// Wait this long before going into deep sleep if connected. This -// allows the user to ctrl-C before deep sleep starts. -#define CIRCUITPY_USB_CONNECTED_DEEP_SLEEP_DELAY 5 +// Wait this long imediately after startup to see if we are connected to USB. +#define CIRCUITPY_USB_CONNECTED_SLEEP_DELAY 5 -//| """Power-saving light and deep sleep. Alarms can be set to wake up from sleep. +//| """Alarms and sleep //| -//| The `alarm` module provides sleep related functionality. There are two supported levels of -//| sleep, light and deep. +//| Provides alarms that trigger based on time intervals or on external events, such as pin +//| changes. +//| The program can simply wait for these alarms, or go into a sleep state and +//| and be awoken when they trigger. //| -//| Light sleep leaves the CPU and RAM powered so that CircuitPython can resume where it left off -//| after being woken up. CircuitPython automatically goes into a light sleep when `time.sleep()` is -//| called. To light sleep until a non-time alarm use `alarm.sleep_until_alarms()`. Any active -//| peripherals, such as I2C, are left on. +//| There are two supported levels of sleep: light sleep and deep sleep. //| -//| Deep sleep shuts down power to nearly all of the chip including the CPU and RAM. This can save -//| a more significant amount of power, but CircuitPython must start ``code.py`` from the beginning when +//| Light sleep leaves the CPU and RAM powered so the program can resume after sleeping. +//| +//| *However, note that on some platforms, light sleep will shut down some communications, including +//| WiFi and/or Bluetooth.* +//| +//| Deep sleep shuts down power to nearly all of the microcontroller including the CPU and RAM. This can save +//| a more significant amount of power, but CircuitPython must restart ``code.py`` from the beginning when //| awakened. //| """ //| //| wake_alarm: Alarm -//| """The most recent alarm to wake us up from a sleep (light or deep.)""" +//| """The most recently triggered alarm. If CircuitPython was sleeping, the alarm the woke it from sleep.""" //| + +// wake_alarm is implemented as a dictionary entry, so there's no code here. + void validate_objs_are_alarms(size_t n_args, const mp_obj_t *objs) { for (size_t i = 0; i < n_args; i++) { if (MP_OBJ_IS_TYPE(objs[i], &alarm_pin_pin_alarm_type) || @@ -69,9 +75,36 @@ void validate_objs_are_alarms(size_t n_args, const mp_obj_t *objs) { } } +//| def wait_until_alarms(*alarms: Alarm) -> Alarm: +//| """Wait for one of the alarms to trigger. The triggering alarm is returned. +//| is returned, and is also available as `alarm.wake_alarm`. Nothing is shut down +//| or interrupted. Power consumption will be reduced if possible. +//| +//| If no alarms are specified, return immediately. +//| """ +//| ... +//| +STATIC mp_obj_t alarm_wait_until_alarms(size_t n_args, const mp_obj_t *args) { + validate_objs_are_alarms(n_args, args); + common_hal_alarm_wait_until_alarms(n_args, args); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(alarm_wait_until_alarms_obj, 1, MP_OBJ_FUN_ARGS_MAX, alarm_wait_until_alarms); + //| def sleep_until_alarms(*alarms: Alarm) -> Alarm: //| """Go into a light sleep until awakened one of the alarms. The alarm causing the wake-up -//| is returned, and is also available as `alarm.wake_alarm`. +//| is returned, and is also available as `alarm.wake_alarm`. +//| +//| Some functionality may be shut down during sleep. On ESP32-S2, WiFi is turned off, +//| and existing connections are broken. +//| +//| If no alarms are specified, return immediately. +//| +//| **If CircuitPython is connected to a host computer,** `alarm.sleep_until_alarms()` +//| **does not go into light sleep.** +//| Instead, light sleep is simulated by doing `alarm.wait_until_alarms()`, +//| This allows the user to interrupt an existing program with ctrl-C, +//| and to edit the files in CIRCUITPY, which would not be possible in true light sleep //| """ //| ... //| @@ -84,47 +117,60 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(alarm_sleep_until_alarms_obj, 1, MP_OBJ_FUN_ //| def exit_and_deep_sleep_until_alarms(*alarms: Alarm) -> None: //| """Exit the program and go into a deep sleep, until awakened by one of the alarms. +//| This function does not return. //| //| When awakened, the microcontroller will restart and will run ``boot.py`` and ``code.py`` //| from the beginning. //| -//| An alarm equivalent to the one that caused the wake-up is available as `alarm.wake_alarm`. +//| After restart, an alarm *equivalent* to the one that caused the wake-up +//| will be available as `alarm.wake_alarm`. //| Its type and/or attributes may not correspond exactly to the original alarm. //| For time-base alarms, currently, an `alarm.time.TimeAlarm()` is created. //| //| If no alarms are specified, the microcontroller will deep sleep until reset. //| -//| If CircuitPython is unconnected to a host computer, go into deep sleep immediately. -//| But if it already connected or in the process of connecting to a host computer, wait at least -//| five seconds after starting code.py before entering deep sleep. -//| This allows interrupting a program that would otherwise go into deep sleep too quickly -//| to interrupt from the keyboard. +//| **If CircuitPython is connected to a host computer, `alarm.exit_and_deep_sleep_until_alarms()` +//| does not go into deep sleep.** +//| Instead, deep sleep is simulated by first doing `alarm.wait_until_alarms()`, +//| and then, when an alarm triggers, by restarting CircuitPython. +//| This allows the user to interrupt an existing program with ctrl-C, +//| and to edit the files in CIRCUITPY, which would not be possible in true deep sleep. +//| +//| Here is skeletal example that deep-sleeps and restarts every 60 seconds: +//| +//| .. code-block:: python +//| +//| import alarm +//| import time +//| +//| print("Waking up") +//| +//| # Set an alarm for 60 seconds from now. +//| time_alarm = alarm.time.TimeAlarm(monotonic_time=time.monotonic() + 60) +//| +//| # Deep sleep until the alarm goes off. Then restart the program. +//| alarm.exit_and_deep_sleep_until_alarms(time_alarm) //| """ //| ... //| STATIC mp_obj_t alarm_exit_and_deep_sleep_until_alarms(size_t n_args, const mp_obj_t *args) { validate_objs_are_alarms(n_args, args); - int64_t connecting_delay_msec = CIRCUITPY_USB_CONNECTING_DELAY * 1024 - supervisor_ticks_ms64(); + // Make sure we have been awake long enough for USB to connect (enumeration delay). + int64_t connecting_delay_msec = CIRCUITPY_USB_CONNECTED_SLEEP_DELAY * 1024 - supervisor_ticks_ms64(); if (connecting_delay_msec > 0) { common_hal_time_delay_ms(connecting_delay_msec * 1000 / 1024); } - // If connected, wait for the program to be running at least as long as - // CIRCUITPY_USB_CONNECTED_DEEP_SLEEP_DELAY. This allows a user to ctrl-C the running - // program in case it is in a tight deep sleep loop that would otherwise be difficult - // or impossible to interrupt. - // Indicate that we're delaying with the SAFE_MODE color. - int64_t delay_before_sleeping_msec = - supervisor_ticks_ms64() - CIRCUITPY_USB_CONNECTED_DEEP_SLEEP_DELAY * 1000; - if (supervisor_workflow_connecting() && delay_before_sleeping_msec > 0) { - temp_status_color(SAFE_MODE); - common_hal_time_delay_ms(delay_before_sleeping_msec); - clear_temp_status(); + if (supervisor_workflow_active()) { + common_hal_alarm_wait_until_alarms(n_args, args); + reload_requested = true; + supervisor_set_run_reason(RUN_REASON_STARTUP); + mp_raise_reload_exception(); + } else { + common_hal_alarm_exit_and_deep_sleep_until_alarms(n_args, args); + // Does not return. } - - common_hal_alarm_exit_and_deep_sleep_until_alarms(n_args, args); - // Does not return. return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(alarm_exit_and_deep_sleep_until_alarms_obj, 1, MP_OBJ_FUN_ARGS_MAX, alarm_exit_and_deep_sleep_until_alarms); diff --git a/shared-bindings/alarm/__init__.h b/shared-bindings/alarm/__init__.h index 968136345c..26dbb2897c 100644 --- a/shared-bindings/alarm/__init__.h +++ b/shared-bindings/alarm/__init__.h @@ -31,6 +31,7 @@ #include "common-hal/alarm/__init__.h" +extern mp_obj_t common_hal_alarm_wait_until_alarms(size_t n_alarms, const mp_obj_t *alarms); extern mp_obj_t common_hal_alarm_sleep_until_alarms(size_t n_alarms, const mp_obj_t *alarms); extern void common_hal_alarm_exit_and_deep_sleep_until_alarms(size_t n_alarms, const mp_obj_t *alarms); diff --git a/shared-bindings/alarm/pin/PinAlarm.c b/shared-bindings/alarm/pin/PinAlarm.c index fadd1b0d4a..a6497d4cde 100644 --- a/shared-bindings/alarm/pin/PinAlarm.c +++ b/shared-bindings/alarm/pin/PinAlarm.c @@ -56,7 +56,7 @@ //| to ``value`` to trigger the alarm. On some ports, edge-triggering may not be available, //| particularly for deep-sleep alarms. //| :param bool pull: Enable a pull-up or pull-down which pulls the pin to the level opposite -//| opposite that of ``value``. For instance, if ``value`` is set to ``True``, setting ``pull`` +//| that of ``value``. For instance, if ``value`` is set to ``True``, setting ``pull`` //| to ``True`` will enable a pull-down, to hold the pin low normally until an outside signal //| pulls it high. //| """ @@ -89,7 +89,7 @@ STATIC mp_obj_t alarm_pin_pin_alarm_make_new(const mp_obj_type_t *type, mp_uint_ return MP_OBJ_FROM_PTR(self); } -//| pins: Tuple[microcontroller.pin] +//| pins: Tuple[microcontroller.Pin] //| """The trigger pins.""" //| STATIC mp_obj_t alarm_pin_pin_alarm_obj_get_pins(mp_obj_t self_in) { @@ -105,7 +105,7 @@ const mp_obj_property_t alarm_pin_pin_alarm_pins_obj = { (mp_obj_t)&mp_const_none_obj}, }; -//| value: Tuple[microcontroller.pin] +//| value: Tuple[microcontroller.Pin] //| """The value on which to trigger.""" //| STATIC mp_obj_t alarm_pin_pin_alarm_obj_get_value(mp_obj_t self_in) { diff --git a/supervisor/shared/workflow.c b/supervisor/shared/workflow.c index 8e4ec16c0b..4986c09570 100644 --- a/supervisor/shared/workflow.c +++ b/supervisor/shared/workflow.c @@ -33,8 +33,10 @@ void supervisor_workflow_reset(void) { // Return true as soon as USB communication with host has started, // even before enumeration is done. +// Not that some chips don't notice when USB is unplugged after first being plugged in, +// so this is not perfect, but tud_suspended() check helps. bool supervisor_workflow_connecting(void) { - return tud_connected(); + return tud_connected() && !tud_suspended(); } // Return true if host has completed connection to us (such as USB enumeration).