diff --git a/boards/manifest-common.py b/boards/manifest-common.py index ac951f8..0b06bcc 100644 --- a/boards/manifest-common.py +++ b/boards/manifest-common.py @@ -10,4 +10,5 @@ require("aioble") freeze("../../pimoroni-pico/micropython/modules_py", "pimoroni.py") -freeze("../../pimoroni-pico/micropython/modules_py", "boot.py") \ No newline at end of file +freeze("../../pimoroni-pico/micropython/modules_py", "boot.py") +freeze("../modules", "inky_frame.py") \ No newline at end of file diff --git a/boards/usermod-common.cmake b/boards/usermod-common.cmake index a0b0a72..6f58348 100644 --- a/boards/usermod-common.cmake +++ b/boards/usermod-common.cmake @@ -56,10 +56,9 @@ include(servo/micropython) include(encoder/micropython) include(motor/micropython) +# Still required for version.py include(modules_py/modules_py) -copy_module(inky_frame.py) - # C++ Magic Memory include(cppmem/micropython) diff --git a/modules/inky_frame.py b/modules/inky_frame.py new file mode 100644 index 0000000..589a1a7 --- /dev/null +++ b/modules/inky_frame.py @@ -0,0 +1,181 @@ +from pimoroni import ShiftRegister, PWMLED +from machine import Pin, I2C, RTC +from wakeup import get_shift_state, reset_shift_state +from micropython import const +import pcf85063a +import ntptime +import time + +BLACK = const(0) +WHITE = const(1) + +GREEN = const(2) +BLUE = const(3) +RED = const(4) +YELLOW = const(5) +ORANGE = const(6) +TAUPE = const(7) + +SR_CLOCK = const(8) +SR_LATCH = const(9) +SR_OUT = const(10) + +LED_A = const(11) +LED_B = const(12) +LED_C = const(13) +LED_D = const(14) +LED_E = const(15) + +LED_BUSY = const(6) +LED_WIFI = const(7) + +HOLD_VSYS_EN = const(2) + +RTC_ALARM = const(2) +EXTERNAL_TRIGGER = const(1) +EINK_BUSY = const(0) + +SHIFT_STATE = get_shift_state() + +reset_shift_state() + +i2c = I2C(0) +rtc = pcf85063a.PCF85063A(i2c) +i2c.writeto_mem(0x51, 0x00, b'\x00') # ensure rtc is running (this should be default?) +rtc.enable_timer_interrupt(False) + +vsys = Pin(HOLD_VSYS_EN) +vsys.on() + + +def woken_by_rtc(): + mask = (1 << RTC_ALARM) + return bool(sr.read() & mask) or bool(SHIFT_STATE & mask) + + +def woken_by_ext_trigger(): + mask = (1 << EXTERNAL_TRIGGER) + return bool(sr.read() & mask) or bool(SHIFT_STATE & mask) + + +def woken_by_button(): + return bool(sr.read() & 0b11111000) or bool(SHIFT_STATE & 0b11111000) + + +def pico_rtc_to_pcf(): + # Set the PCF85063A to the time stored by Pico W's RTC + year, month, day, dow, hour, minute, second, _ = RTC().datetime() + rtc.datetime((year, month, day, hour, minute, second, dow)) + + +def pcf_to_pico_rtc(): + # Set Pico W's RTC to the time stored by the PCF85063A + t = rtc.datetime() + # BUG ERRNO 22, EINVAL, when date read from RTC is invalid for the Pico's RTC. + try: + RTC().datetime((t[0], t[1], t[2], t[6], t[3], t[4], t[5], 0)) + return True + except OSError: + return False + + +def sleep_for(minutes): + year, month, day, hour, minute, second, dow = rtc.datetime() + + # if the time is very close to the end of the minute, advance to the next minute + # this aims to fix the edge case where the board goes to sleep right as the RTC triggers, thus never waking up + if second >= 55: + minute += 1 + + # Can't sleep beyond a month, so clamp the sleep to a 28 day maximum + minutes = min(minutes, 40320) + + # Calculate the future alarm date; first, turn the current time into seconds since epoch + sec_since_epoch = time.mktime((year, month, day, hour, minute, second, dow, 0)) + + # Add the required minutes to this + sec_since_epoch += minutes * 60 + + # And convert it back into a more useful tuple + (ayear, amonth, aday, ahour, aminute, asecond, adow, adoy) = time.localtime(sec_since_epoch) + + # And now set the alarm as before, now including the day + rtc.clear_alarm_flag() + rtc.set_alarm(0, aminute, ahour, aday) + rtc.enable_alarm_interrupt(True) + + turn_off() + + # Simulate sleep while on USB power + while minutes > 0: + time.sleep(60) + minutes -= 1 + + +def turn_off(): + time.sleep(0.1) + vsys.off() + + +def set_time(): + # Set both Pico W's RTC and PCF85063A to NTP time + ntptime.settime() + pico_rtc_to_pcf() + + +class Button: + def __init__(self, sr, idx, led, debounce=50): + self.sr = sr + self.startup_state = bool(SHIFT_STATE & (1 << idx)) + self.led = PWMLED(led) + self.led.off() + self._idx = idx + self._debounce_time = debounce + self._changed = time.ticks_ms() + self._last_value = None + + def led_on(self): + self.led.on() + + def led_off(self): + self.led.off() + + def led_brightness(self, brightness): + self.led.brightness(brightness) + + def led_toggle(self): + self.led.toggle() + + def read(self): + if self.startup_state: + self.startup_state = False + return True + + value = self.raw() + if value != self._last_value and time.ticks_ms() - self._changed > self._debounce_time: + self._last_value = value + self._changed = time.ticks_ms() + return value + return False + + def raw(self): + if self.startup_state: + self.startup_state = False + return True + return self.sr[self._idx] == 1 + + @property + def is_pressed(self): + return self.raw() + + +sr = ShiftRegister(SR_CLOCK, SR_LATCH, SR_OUT) + +button_a = Button(sr, 7, LED_A) +button_b = Button(sr, 6, LED_B) +button_c = Button(sr, 5, LED_C) +button_d = Button(sr, 4, LED_D) +button_e = Button(sr, 3, LED_E) + +led_busy = PWMLED(LED_BUSY) +led_wifi = PWMLED(LED_WIFI)