-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathflip_clock.py
192 lines (158 loc) · 7.16 KB
/
flip_clock.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
"""
Display a "flip clock" as a prototype for the same on a LilyGo T-Display-S3.
(https://www.lilygo.cc/products/t-display-s3?variant=42585826558133).
The clock "flips" from one digit to the next by displaying a sequence of images
which have been previously generated by the "flip_digits.py" program. Files are
named as "xyz.png" where 'x' is the current digit, y is the same or the next
digit being flipped to, and 'z' is the flip state (0..MAX_STEP-1).
The display includes 2 buttons which toggle between display formats (24-hour,
12-hour and 12-hour with leading blank) as well as a "demo mode" that sets the
clock to a time just before a full 4-digit change and then runs as if a minute
was only 6 seconds long.
"""
import time
import PySimpleGUI as sg
from clock_digit import ClockFace, DispFmt, IMG_PATH
# PSG info
COLON_KEY = '-COLON-'
TICK_KEY = '-TICK-'
TOP_BTN = '-TOP-BTN-'
BOT_BTN = '-BOT-BTN-'
BABBLE_KEY = '-BABBLE-'
THE_FONT = ('Roboto-Regular', 14)
BG_COLOR = "black"
COLON_MS = 1000
BABBLE_MS = 4000
STEP_MS = 100
FAST_MSECS = 6000
DISP_FMT_LABELS = ['24-hour', '12-hour', '12-hour leading blank']
theClock = ClockFace(DispFmt.HR12) # container for individual "time" digits
def make_layout() -> list[list]:
""" Build our PSG screen layout """
# orange buttons to right of digits
top_btn = [sg.Image(filename=f'{IMG_PATH}/top_button.png', key=TOP_BTN, enable_events=True,
tooltip='Change display format', pad=(0, 0))]
bot_btn = [sg.Image(filename=f'{IMG_PATH}/bottom_button.png', key=BOT_BTN, enable_events=True,
tooltip='Toggle DEMO mode', pad=(0, 0))]
btn_col = sg.Column([top_btn, bot_btn])
# logo, digits in "major" to "minor" order with colon, and buttons column
digit0_path = f'{IMG_PATH}/000.png' # init all to '0'
digits_row = [
sg.Image(filename=f'{IMG_PATH}/logo.png', pad=((1, 8), (2, 2))),
sg.Image(digit0_path, key=theClock.hr10.key, pad=(1, 2)),
sg.Image(digit0_path, key=theClock.hr01.key, pad=(1, 2)),
sg.Image(filename=f'{IMG_PATH}/colon1.png', key=COLON_KEY, pad=(1, 2)),
sg.Image(digit0_path, key=theClock.min10.key, pad=(1, 2)),
sg.Image(digit0_path, key=theClock.min01.key, pad=(1, 2)),
btn_col
]
# general purpose message output area
babble_row = [sg.Text(text='', auto_size_text=True, justification='center', expand_x=True,
text_color='white', background_color=BG_COLOR, key=BABBLE_KEY)]
return [digits_row, babble_row]
def millis() -> int:
""" Emulate Arduino millis() function """
return time.time_ns() // (1000 * 1000) # nanoseconds -> microseconds
def babble(txt: str, window: sg.Window) -> bool:
""" Draw text at bottom of screen; '' to erase it """
window[BABBLE_KEY].update(value=txt)
return txt != '' # convenience to avoid "global" variable reference
def get_time(run_fast: bool, disp_fmt: DispFmt) -> tuple[int, int, int]:
""" Initialize our current time """
if run_fast: # demo mode
hour = 23 if disp_fmt == DispFmt.HR24 else 12
mins = 57
wait_ms = FAST_MSECS
else:
tm = time.localtime()
hour = tm.tm_hour
mins = tm.tm_min
wait_ms = (60 - tm.tm_sec) * 1000 # milliseconds until end of minute
return hour, mins, wait_ms
def update_time(hour: int, mins: int, run_fast: bool) -> tuple[int, int, int]:
""" Increment or reset current time & return msecs until next change """
if run_fast:
# always increment minutes in demo mode
mins += 1
if mins >= 60:
mins = 0
hour += 1
if hour >= 24:
hour = 0
wait_ms = FAST_MSECS
else:
tm = time.localtime() # get the "real" time
# Since we only have digit transformations for one unit at a time,
# ensure that we're only incrementing by 1 minute;
# else we need to reset the display completely.
prev_mins = hour * 60 + mins
curr_mins = tm.tm_hour * 60 + tm.tm_min
mins_diff = curr_mins - prev_mins
hour, mins, wait_ms = tm.tm_hour, tm.tm_min, (60 - tm.tm_sec) * 1000
if mins_diff not in [1, -(23 * 60 + 59)]:
wait_ms = -wait_ms # flag "re-draw" with neg. value
return hour, mins, wait_ms
def main():
layout = make_layout()
window = sg.Window(f'T-Display-S3', layout=layout, font=THE_FONT,
background_color=BG_COLOR, finalize=True)
run_fast = False
hours, mins, tick_ms = get_time(run_fast, theClock.get_disp_fmt())
theClock.set_time(hours, mins, start_step=False)
theClock.draw_all(window)
babble_on = babble('Starting...', window)
colon_on = True
step_start = 0
tick_start = colon_start = babble_start = millis()
while True:
# Emulate ESP32 Arduino framework "loop()" with a short timeout event
event, values = window.read(timeout=100, timeout_key=TICK_KEY)
if event in [sg.WIN_CLOSED]:
break
now = millis()
if event == TICK_KEY:
if theClock.is_stepping() and (now - step_start >= STEP_MS):
# flipping progress
theClock.do_step(window)
step_start = now
elif now - tick_start >= tick_ms:
# time to update clock display
hours, mins, tick_ms = update_time(hours, mins, run_fast)
if tick_ms >= 0:
# simple increment
theClock.set_time(hours, mins, start_step=True)
theClock.do_step(window)
else:
# major change: reset display
theClock.set_time(hours, mins, start_step=False)
theClock.draw_all(window)
babble_on = babble('Time re-sync', window)
tick_ms = -tick_ms
tick_start = now
if now - colon_start >= COLON_MS:
# toggle the colonON and OFF
colon_on = not colon_on
fn = 'colon1' if colon_on else 'colon0'
window[COLON_KEY].update(filename=f'{IMG_PATH}/{fn}.png')
colon_start = now
if babble_on and (now - babble_start >= BABBLE_MS):
# clear the text at bottom of screen
babble_on = babble('', window)
elif event == TOP_BTN:
# Toggle display format
fmt = (theClock.get_disp_fmt() + 1) % DispFmt.NUM_FMTS
theClock.set_disp_fmt(fmt)
theClock.set_time(hours, mins, start_step=False)
theClock.draw_all(window)
babble_on = babble(DISP_FMT_LABELS[fmt], window)
babble_start = now
elif event == BOT_BTN:
# Toggle "demo" mode where 1 minute elapses in 6 seconds
run_fast = not run_fast
hours, mins, tick_ms = get_time(run_fast, theClock.get_disp_fmt())
theClock.set_time(hours, mins, start_step=False)
theClock.draw_all(window)
babble_on = babble(f'Demo mode is {"ON" if run_fast else "OFF"}', window)
babble_start = tick_start = now
if __name__ == '__main__':
main()