RTCClockApp.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. from Debug import Debug
  2. from machine import Pin
  3. from machine import RTC
  4. import utime as time
  5. from tm1637 import TM1637
  6. from TimeSource import TimeSource
  7. from TimeSyncer import TimeSyncer
  8. from micropython import schedule
  9. from machine import Timer
  10. from machine import idle
  11. MODE_TIME = 0
  12. MODE_DATE = 1
  13. MODE_WDAY = 2
  14. MODE_SEC = 3
  15. WEEKDAYS=("non","tuE","uEd","tHu","Fri","SAt","Sun")
  16. class RTCClockApp(Debug):
  17. """RTCを用いた時計アプリケーションクラス"""
  18. def __init__(self, display_dev: TM1637, time_source: TimeSource, time_sync: TimeSyncer, mode_select_pin: int, force_sync_pin: int):
  19. """
  20. コンストラクタ
  21. Args:
  22. display_dev: TM1637のインスタンス
  23. time_source: TimeSourceのインスタンス
  24. time_sync: TimeSyncerのインスタンス
  25. mode_select_pin: 表示モード切替スイッチが接続されているGPIO番号
  26. force_sync_pin: 強制受信スイッチが接続されているGPIO番号
  27. """
  28. self.disp = display_dev
  29. self.timesrc = time_source
  30. self.timesync = time_sync
  31. # タイマー10ms
  32. self.tm = Timer()
  33. # 表示更新処理中フラグ(キュー溢れ防止用)
  34. self._updating = False
  35. # 1つ前の秒
  36. self.prev_sec = -1
  37. # 初期表示
  38. self.disp.show_str("----")
  39. # RTCに現在日時が設定されているか?
  40. self.setup_rtc = False
  41. # TimeSourceにコールバック関数を登録
  42. self.timesrc.add_callback(self.adjust_rtc)
  43. # 表示モード
  44. self.mode = MODE_TIME
  45. # 1つ前の表示モード
  46. self.prev_mode = self.mode
  47. # キースイッチ設定
  48. self._last_keydown_times = {} # キー押下時間を格納する辞書
  49. self.mode_select = Pin(mode_select_pin, Pin.IN, pull=Pin.PULL_UP)
  50. self.force_sync = Pin(force_sync_pin, mode=Pin.IN, pull=Pin.PULL_UP)
  51. self.mode_select.irq(self._key_event_handler,trigger=Pin.IRQ_FALLING)
  52. self.force_sync.irq(self._key_event_handler,trigger=Pin.IRQ_FALLING)
  53. def _key_event_handler(self, p):
  54. """スイッチ押下時の割り込みハンドラ"""
  55. key_id = id(p)
  56. now = time.ticks_ms()
  57. last_time = self._last_keydown_times.get(key_id, 0)
  58. if time.ticks_diff(now, last_time) > 500: # チャタリング防止: 500ms以上経過していたら
  59. self._last_keydown_times[key_id] = now
  60. schedule(self._key_down, p)
  61. def _key_down(self, p):
  62. """キーダウンイベント処理(非同期コンテキストで実行)"""
  63. if p == self.mode_select: # 表示モード選択
  64. self.mode = (self.mode + 1) % 4
  65. elif p == self.force_sync: # 強制受信
  66. self.timesync.sync_start()
  67. def adjust_rtc(self, args):
  68. """
  69. RTCに現在日時を設定するコールバック関数
  70. Args:
  71. args: (jjy_time, ticks_ms)
  72. jjy_time: RTCに書き込むUNIXエポックタイム
  73. ticks_ms: この時間を受信したtime.ticks_ms()
  74. """
  75. jjy_time, ticks_ms = (args)
  76. rtc = RTC()
  77. elapsed_ms = time.ticks_diff(time.ticks_ms(), ticks_ms)
  78. rtc_time = jjy_time + (elapsed_ms // 1000) + 1
  79. now = time.localtime(rtc_time)
  80. # 次の秒が来るまでスリープしてタイミングを合わせる
  81. time.sleep_ms(1000 - (elapsed_ms % 1000))
  82. rtc.datetime((now[0], now[1], now[2], now[6], now[3], now[4], now[5], 0))
  83. self.dprint("Set RTC to %d/%d/%d, %d:%d:%d" % (now[0], now[1], now[2], now[3], now[4], now[5]))
  84. self.setup_rtc = True
  85. @micropython.native
  86. def _timer_handler(self,t):
  87. """
  88. 10msタイマー割り込みハンドラ
  89. 実際の処理はschedule()に委譲
  90. """
  91. # 前回の処理が終わっていない、またはキューが一杯の場合はスキップする
  92. if not self._updating:
  93. try:
  94. schedule(self._update_display, None)
  95. self._updating = True
  96. except RuntimeError:
  97. self.dprint("--- schedule() queue full ---")
  98. @micropython.native
  99. def _update_display(self, _):
  100. """
  101. 表示の更新を行う(メインスレッドで実行)
  102. """
  103. try:
  104. now = time.localtime()
  105. sec = now[5]
  106. if self.prev_sec != sec or self.prev_mode != self.mode:
  107. self.prev_sec = sec
  108. self.prev_mode = self.mode
  109. # 表示更新
  110. disp_str = "----"
  111. if self.setup_rtc:
  112. if self.mode == MODE_TIME:
  113. if now[5] % 2 == 0:
  114. disp_str = "%2d:%02d" % (now[3],now[4])
  115. else:
  116. disp_str = "%2d%02d" % (now[3],now[4])
  117. elif self.mode == MODE_DATE:
  118. disp_str = "%2d%2d" % (now[1], now[2])
  119. elif self.mode == MODE_WDAY:
  120. disp_str = " %s" % (WEEKDAYS[now[6]])
  121. elif self.mode == MODE_SEC:
  122. disp_str = "%2d%2d" % (now[4], now[5])
  123. self.disp.show_str(disp_str)
  124. finally:
  125. # 処理完了(またはエラー発生)時にフラグを下ろす
  126. self._updating = False
  127. def run(self):
  128. """
  129. アプリケーションメインループ
  130. """
  131. self.tm.init(mode=Timer.PERIODIC, period=10, callback=self._timer_handler)
  132. try:
  133. while True:
  134. idle()
  135. except Exception as e:
  136. self.dprint(e)
  137. finally:
  138. self.tm.deinit()