| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 | """tested on uiflow for stickc v1.8.1"""# pylint: disable=import-errorimport jsonimport m5uiimport machineimport micropythonimport utilsfrom m5stack import axp, btnA, btnB, lcd_FONT = lcd.FONT_DejaVu40_DEFAULT_FONT_COLOR = lcd.WHITE_ALARM_TIME_PATH = "alarm.json"_SCREEN_WIDTH, _SCREEN_HEIGHT = lcd.winsize()class App:    # pylint: disable=too-few-public-methods,too-many-instance-attributes    def __init__(self) -> None:        self._rtc = machine.RTC()        self._clock_text_box = None        self._menu_position = 0        self._alarm_hour = None        self._alarm_minute = None        self._alarm_hour_text_box = None        self._alarm_minute_text_box = None        self._alarm_timer = None    @property    def _now_time(self) -> int:        # > [contradictory to] official micropython documentation, to set RTC,        # > use particular tuple (year , month, day, week=0, hour, minute, second, timestamp=0)        # https://community.m5stack.com/topic/3108/m5stack-core2-micropython-rtc-example        hour, minute, seconds = self._rtc.datetime()[4:7]        return (hour * 60 + minute) * 60 + seconds    @property    def _alarm_time(self) -> int:        return (self._alarm_hour * 60 + self._alarm_minute) * 60  # type: ignore    def _load_alarm_time(self) -> None:        with open(_ALARM_TIME_PATH, "r") as alarm_time_file:            alarm_time = json.load(alarm_time_file)        self._alarm_hour = alarm_time["hour"]        self._alarm_minute = alarm_time["minute"]    def _save_alarm_time(self) -> None:        with open(_ALARM_TIME_PATH, "w") as alarm_time_file:            json.dump(                {"hour": self._alarm_hour, "minute": self._alarm_minute},                alarm_time_file,            )    def _update_menu(self, event_arg: None) -> None:        # pylint: disable=unused-argument; callback        self._alarm_hour_text_box.setColor(  # type: ignore            lcd.GREEN            if self._menu_position == 1            else (lcd.RED if self._menu_position == 2 else _DEFAULT_FONT_COLOR)        )        self._alarm_minute_text_box.setColor(  # type: ignore            lcd.GREEN            if self._menu_position == 3            else (lcd.RED if self._menu_position == 4 else _DEFAULT_FONT_COLOR)        )    def _button_a_pressed(self) -> None:        self._menu_position = (self._menu_position + 1) % 5        # https://docs.micropython.org/en/latest/library/micropython.html#micropython.schedule        micropython.schedule(self._update_menu, None)    @staticmethod    def _alert() -> None:        print("ALARM")    def _alarm(self, timer: machine.Timer) -> None:        # pylint: disable=unused-argument; callback        micropython.schedule(lambda n: self._alert(), None)        micropython.schedule(lambda n: self._configure_alarm_timer(), None)    def _configure_alarm_timer(self) -> None:        seconds_until_alarm = (self._alarm_time - self._now_time - 1) % (            24 * 60 * 60        ) + 1        print("alarm in ", seconds_until_alarm / 60, " min")        self._alarm_timer.init(  # type: ignore            period=seconds_until_alarm * 1000,  # ms            mode=machine.Timer.ONE_SHOT,            callback=self._alarm,        )    def _update_alarm_time(self, event_arg: None) -> None:        # pylint: disable=unused-argument; callback        self._alarm_hour_text_box.setText(  # type: ignore            "{:02d}".format(self._alarm_hour)  # type: ignore        )        self._alarm_minute_text_box.setText(  # type: ignore            "{:02d}".format(self._alarm_minute)  # type: ignore        )        self._save_alarm_time()        self._configure_alarm_timer()    def _button_b_pressed(self) -> None:        if self._menu_position == 1:            self._alarm_hour += 1  # type: ignore        elif self._menu_position == 2:            self._alarm_hour -= 1  # type: ignore        elif self._menu_position == 3:            self._alarm_minute += 1  # type: ignore        elif self._menu_position == 4:            self._alarm_minute -= 1  # type: ignore        self._alarm_hour %= 24  # type: ignore        self._alarm_minute %= 60  # type: ignore        micropython.schedule(self._update_alarm_time, None)    def _format_time(self) -> str:        return "{:02d}:{:02d}".format(*self._rtc.datetime()[4:6])    def _setup_clock(self) -> None:        # https://github.com/m5stack/UIFlow-Code/wiki/M5UI#textbox        self._clock_text_box = m5ui.M5TextBox(            _SCREEN_WIDTH - 1,            0,            self._format_time(),            _FONT,            _DEFAULT_FONT_COLOR,            rotate=90,        )        machine.Timer(0).init(            period=10000,  # ms            mode=machine.Timer.PERIODIC,            callback=lambda t: self._clock_text_box.setText(self._format_time()),  # type: ignore        )    def _setup_alarm(self) -> None:        if not utils.exists(_ALARM_TIME_PATH):            self._alarm_hour = self._alarm_minute = 0  # type: ignore            self._save_alarm_time()        else:            self._load_alarm_time()        self._alarm_hour_text_box = m5ui.M5TextBox(            _SCREEN_WIDTH // 2,            0,            "{:02d}".format(self._alarm_hour),  # type: ignore            _FONT,            _DEFAULT_FONT_COLOR,            rotate=90,        )        m5ui.M5TextBox(            _SCREEN_WIDTH // 2, 53, ":", _FONT, _DEFAULT_FONT_COLOR, rotate=90        )        self._alarm_minute_text_box = m5ui.M5TextBox(            _SCREEN_WIDTH // 2,            66,            "{:02d}".format(self._alarm_minute),  # type: ignore            _FONT,            _DEFAULT_FONT_COLOR,            rotate=90,        )        # machine.Timer(1).init(...) breaks button handling        self._alarm_timer = machine.Timer(2)        self._configure_alarm_timer()    def run(self) -> None:        m5ui.setScreenColor(0x000000)  # clear screen        print("battery:", axp.getBatVoltage(), "V")        self._setup_clock()        self._setup_alarm()        btnA.wasPressed(self._button_a_pressed)        btnB.wasPressed(self._button_b_pressed)App().run()# TODO https://docs.micropython.org/en/latest/library/machine.WDT.html#machine-wdt# TODO https://docs.micropython.org/en/latest/esp8266/tutorial/powerctrl.html#deep-sleep-mode
 |