vibrating_alarm_m5stickc.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. """
  2. tested on uiflow for stickc v1.8.1
  3. """
  4. # pylint: disable=import-error
  5. import json
  6. import m5ui
  7. import machine
  8. import micropython
  9. import utils
  10. from m5stack import axp, btnA, btnB, lcd
  11. _FONT = lcd.FONT_DejaVu40
  12. _DEFAULT_FONT_COLOR = lcd.WHITE
  13. _ALARM_TIME_PATH = "alarm.json"
  14. _SCREEN_WIDTH, _SCREEN_HEIGHT = lcd.winsize()
  15. class App:
  16. # pylint: disable=too-few-public-methods,too-many-instance-attributes
  17. def __init__(self) -> None:
  18. self._rtc = machine.RTC()
  19. self._clock_text_box = None
  20. self._menu_position = 0
  21. self._alarm_hour = None
  22. self._alarm_minute = None
  23. self._alarm_hour_text_box = None
  24. self._alarm_minute_text_box = None
  25. self._alarm_timer = None
  26. @property
  27. def _now_time(self) -> int:
  28. # > [contradictory to] official micropython documentation, to set RTC,
  29. # > use particular tuple (year , month, day, week=0, hour, minute, second, timestamp=0)
  30. # https://community.m5stack.com/topic/3108/m5stack-core2-micropython-rtc-example
  31. hour, minute, seconds = self._rtc.datetime()[4:7]
  32. return (hour * 60 + minute) * 60 + seconds
  33. @property
  34. def _alarm_time(self) -> int:
  35. return (self._alarm_hour * 60 + self._alarm_minute) * 60 # type: ignore
  36. def _load_alarm_time(self) -> None:
  37. with open(_ALARM_TIME_PATH, "r") as alarm_time_file:
  38. alarm_time = json.load(alarm_time_file)
  39. self._alarm_hour = alarm_time["hour"]
  40. self._alarm_minute = alarm_time["minute"]
  41. def _save_alarm_time(self) -> None:
  42. with open(_ALARM_TIME_PATH, "w") as alarm_time_file:
  43. json.dump(
  44. {"hour": self._alarm_hour, "minute": self._alarm_minute},
  45. alarm_time_file,
  46. )
  47. def _update_menu(self, event_arg: None) -> None:
  48. # pylint: disable=unused-argument; callback
  49. self._alarm_hour_text_box.setColor( # type: ignore
  50. lcd.GREEN
  51. if self._menu_position == 1
  52. else (lcd.RED if self._menu_position == 2 else _DEFAULT_FONT_COLOR)
  53. )
  54. self._alarm_minute_text_box.setColor( # type: ignore
  55. lcd.GREEN
  56. if self._menu_position == 3
  57. else (lcd.RED if self._menu_position == 4 else _DEFAULT_FONT_COLOR)
  58. )
  59. def _button_a_pressed(self) -> None:
  60. self._menu_position = (self._menu_position + 1) % 5
  61. # https://docs.micropython.org/en/latest/library/micropython.html#micropython.schedule
  62. micropython.schedule(self._update_menu, None)
  63. @staticmethod
  64. def _alert() -> None:
  65. print("ALARM")
  66. def _alarm(self, timer: machine.Timer) -> None:
  67. # pylint: disable=unused-argument; callback
  68. micropython.schedule(lambda n: self._alert(), None)
  69. micropython.schedule(lambda n: self._configure_alarm_timer(), None)
  70. def _configure_alarm_timer(self) -> None:
  71. seconds_until_alarm = (self._alarm_time - self._now_time - 1) % (
  72. 24 * 60 * 60
  73. ) + 1
  74. print("alarm in ", seconds_until_alarm / 60, " min")
  75. self._alarm_timer.init( # type: ignore
  76. period=seconds_until_alarm * 1000, # ms
  77. mode=machine.Timer.ONE_SHOT,
  78. callback=self._alarm,
  79. )
  80. def _update_alarm_time(self, event_arg: None) -> None:
  81. # pylint: disable=unused-argument; callback
  82. self._alarm_hour_text_box.setText( # type: ignore
  83. "{:02d}".format(self._alarm_hour) # type: ignore
  84. )
  85. self._alarm_minute_text_box.setText( # type: ignore
  86. "{:02d}".format(self._alarm_minute) # type: ignore
  87. )
  88. self._save_alarm_time()
  89. self._configure_alarm_timer()
  90. def _button_b_pressed(self) -> None:
  91. if self._menu_position == 1:
  92. self._alarm_hour += 1 # type: ignore
  93. elif self._menu_position == 2:
  94. self._alarm_hour -= 1 # type: ignore
  95. elif self._menu_position == 3:
  96. self._alarm_minute += 1 # type: ignore
  97. elif self._menu_position == 4:
  98. self._alarm_minute -= 1 # type: ignore
  99. self._alarm_hour %= 24 # type: ignore
  100. self._alarm_minute %= 60 # type: ignore
  101. micropython.schedule(self._update_alarm_time, None)
  102. def _format_time(self) -> str:
  103. return "{:02d}:{:02d}".format(*self._rtc.datetime()[4:6])
  104. def _setup_clock(self) -> None:
  105. # https://github.com/m5stack/UIFlow-Code/wiki/M5UI#textbox
  106. self._clock_text_box = m5ui.M5TextBox(
  107. _SCREEN_WIDTH - 1,
  108. 0,
  109. self._format_time(),
  110. _FONT,
  111. _DEFAULT_FONT_COLOR,
  112. rotate=90,
  113. )
  114. machine.Timer(0).init(
  115. period=10000, # ms
  116. mode=machine.Timer.PERIODIC,
  117. callback=lambda t: self._clock_text_box.setText(self._format_time()), # type: ignore
  118. )
  119. def _setup_alarm(self) -> None:
  120. if not utils.exists(_ALARM_TIME_PATH):
  121. self._alarm_hour = self._alarm_minute = 0 # type: ignore
  122. self._save_alarm_time()
  123. else:
  124. self._load_alarm_time()
  125. self._alarm_hour_text_box = m5ui.M5TextBox(
  126. _SCREEN_WIDTH // 2,
  127. 0,
  128. "{:02d}".format(self._alarm_hour), # type: ignore
  129. _FONT,
  130. _DEFAULT_FONT_COLOR,
  131. rotate=90,
  132. )
  133. m5ui.M5TextBox(
  134. _SCREEN_WIDTH // 2, 53, ":", _FONT, _DEFAULT_FONT_COLOR, rotate=90
  135. )
  136. self._alarm_minute_text_box = m5ui.M5TextBox(
  137. _SCREEN_WIDTH // 2,
  138. 66,
  139. "{:02d}".format(self._alarm_minute), # type: ignore
  140. _FONT,
  141. _DEFAULT_FONT_COLOR,
  142. rotate=90,
  143. )
  144. # machine.Timer(1).init(...) breaks button handling
  145. self._alarm_timer = machine.Timer(2)
  146. self._configure_alarm_timer()
  147. def run(self) -> None:
  148. m5ui.setScreenColor(0x000000) # clear screen
  149. print("battery:", axp.getBatVoltage(), "V")
  150. self._setup_clock()
  151. self._setup_alarm()
  152. btnA.wasPressed(self._button_a_pressed)
  153. btnB.wasPressed(self._button_b_pressed)
  154. App().run()
  155. # TODO https://docs.micropython.org/en/latest/library/machine.WDT.html#machine-wdt
  156. # TODO https://docs.micropython.org/en/latest/esp8266/tutorial/powerctrl.html#deep-sleep-mode