-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.py
572 lines (464 loc) · 18.4 KB
/
app.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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
"""
author: Jerry Wu
all tasks to run are included in this file
"""
import os
import time
import threading
from cv2 import cuda_BufferPool
import RPi.GPIO as GPIO
from flask import Flask, render_template, redirect
import go_to_bed
### constants ###
MIN_DELAY = 10 # delay for tasks need to update within minute precision
FAST_DELAY = 0.01 # delay for tasks need to update immediately
SNOOZE_TIME = 1 # TODO: 10 # snooze time in minutes
SOUND_PATH = "sound/Let Her Go.mp3" # path to sound file
# FUTURE scan for available alarm music in the sound folder
# available_files = []
# for (dirpath, dirnames, filenames) in os.walk("./sound"):
# available_files.extend(filenames)
BED_TIME_THRESHOLD = 5 # minutes
SETTING_ITEM = ['bed time', 'wake up time']
LED_ON = GPIO.LOW
LED_OFF = GPIO.HIGH
# MAIN_STATUS: 0: wakeup, 1: sleep, 2: alarm
MAIN_STATUS = 'main status'
MAIN_STATUS_WAKEUP = 0
MAIN_STATUS_NEED_SLEEP = 1
MAIN_STATUS_SLEEP = 2
MAIN_STATUS_ALARM = 3
# ALARM_SWITCH: 0: on, 1: off
ALARM_STATUS = 'alarm status'
ALARM_ON = 0
ALARM_OFF = 1
# OLED_STATUS
OLED_STATUS = 'oled status'
OLED_DISPLAY = 0
OLED_SETTING = 1
OLED_SET_HOUR = 2
OLED_SET_MINUTE = 3
# setting status TODO: when oled timeout or confirm, update status
# indicate which option is currently selected
SETTING_SELECTION = 0
# display time on oled
SETTING_TIME = 1
# global variables
current_status = {MAIN_STATUS: MAIN_STATUS_WAKEUP,
ALARM_STATUS: ALARM_OFF,
OLED_STATUS: OLED_DISPLAY}
bed_time = [11, 10] # TODO: [22, 30] # time to sleep (hour, minute)
today_bed_time = 0 # today's bed time (time.time())
up_time = [11, 15] # TODO: [7, 0] # time to wake up (hour, minute)
alarm_time = up_time # time to play alarm clock sound (hour, minute)
sleep_info = [("05/6", "10:30", True), # list to store sleep info (date, time, follow schedule)
("05/7", "11:53", False),
("05/8", "10:30", True),
("05/9", "10:30", True)] # TODO: make empty []
light_threshold = 1.5 # threshold voltage for light sensor, user tunable
time_12_hour = False # 12 hour mode or 24 hour mode
setting_status = {SETTING_SELECTION: 0,
SETTING_TIME: bed_time}
settings_info = [['sleep time', f'{bed_time[0]}:{bed_time[1]}'],
['wake time', f"{up_time[0]}:{up_time[1]}"],
['volume', '100%'],
['brightness', '100%'],
['light sensitivity', light_threshold],
['12 hour format', time_12_hour]]
friends_sleep_info = [('Jerry', '83%'),
('Tom', '75%'),
('George', '72%'),
('Mary', '65%'),
('Bob', '60%'),
('Alice', '55%'),
('Jack', '50%'),
('Linda', '45%'),
('John', '40%'),
('Jane', '35%')]
# GPIO pins
SNOOZE_BUT = 24
STOP_BUT = 23
RED_LED = 25
GREEN_LED = 26
ALARM_SWITCH = 22
ENCODER_L = 14
ENCODER_R = 15
ENCODER_BUT = 16
### onetime tasks ###
def simple_GPIO_setup():
"""
setup some devices that only need input or output
devices: red/green LEDs, snooze button, stop button, alarm switch
"""
GPIO.setmode(GPIO.BCM)
# setup stop/pause button pull up by default
GPIO.setup(SNOOZE_BUT, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.add_event_detect(SNOOZE_BUT, GPIO.RISING, callback=pause_alarm)
GPIO.setup(STOP_BUT, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.add_event_detect(STOP_BUT, GPIO.RISING, callback=stop_alarm)
# setup alarm switch pull up by default
GPIO.setup(ALARM_SWITCH, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
current_status[ALARM_STATUS] = GPIO.input(ALARM_SWITCH)
GPIO.add_event_detect(ALARM_SWITCH, GPIO.BOTH, callback=alarm_switch)
# setup red/green LED
GPIO.setup(RED_LED, GPIO.OUT)
GPIO.output(RED_LED, LED_OFF)
GPIO.setup(GREEN_LED, GPIO.OUT)
if current_status[ALARM_STATUS] == ALARM_ON:
GPIO.output(GREEN_LED, LED_ON)
else:
GPIO.output(GREEN_LED, LED_OFF)
# setup encoder
# default to ground
GPIO.setup(ENCODER_L, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(ENCODER_R, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(ENCODER_BUT, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# add event detect
GPIO.add_event_detect(ENCODER_L, GPIO.FALLING, callback=encoder_rotate)
GPIO.add_event_detect(ENCODER_BUT, GPIO.FALLING, callback=encoder_but)
# add timer
global encoder_ccw_time, encoder_cw_time
encoder_ccw_time = time.time()
encoder_cw_time = time.time()
def peripheral_setup():
"""
setup all the peripherals
peripherals: rfid, oled, clock, speaker
"""
global rfid, oled, clock, speaker, light_sensor
# setup RFID
rfid = go_to_bed.RFID()
# setup OLED (I2C)
oled = go_to_bed.OLED()
# setup led
clock = go_to_bed.Clock()
# setup speaker
speaker = go_to_bed.Speaker()
speaker.set_sound(SOUND_PATH) # FUTURE: let user choose sound
# setup light sensor
light_sensor = go_to_bed.ADC()
# setup webpage
webpage_flask = Flask(__name__, static_folder='assets')
### interrupt ###
def alarm_switch(channel):
"""
callback function to determine alarm switch state
if switch is on, turn off the alarm, green LED off
otherwise, turn on the alarm, green LED on
"""
print("switch interrupt") # TODO
# debounce, wait for 20 milliseconds
time.sleep(0.020)
if GPIO.input(channel) == ALARM_ON:
current_status[ALARM_STATUS] = ALARM_ON
GPIO.output(GREEN_LED, LED_ON)
else:
current_status[ALARM_STATUS] = ALARM_OFF
GPIO.output(GREEN_LED, LED_OFF)
def pause_alarm(channel):
"""
callback function to pause the alarm
"""
# debounce, wait for 20 milliseconds
time.sleep(0.020)
if GPIO.input(channel):
# stop sound
if not speaker.is_stopped():
speaker.stop_sound()
if current_status[MAIN_STATUS] == MAIN_STATUS_ALARM:
# snooze alarm
hour, minute, _ = get_time()
set_time(alarm_time, hour, (minute + SNOOZE_TIME))
# act as back button
elif current_status[OLED_STATUS] == OLED_SETTING:
setting_status[SETTING_SELECTION] = 0 # set selection back to 0
current_status[OLED_STATUS] = OLED_DISPLAY
oled_update_display()
elif current_status[OLED_STATUS] == OLED_SET_HOUR:
current_status[OLED_STATUS] = OLED_SETTING
oled_update_display()
elif current_status[OLED_STATUS] == OLED_SET_MINUTE:
current_status[OLED_STATUS] = OLED_SET_HOUR
oled_update_display()
def stop_alarm(channel):
"""
callback function to stop alarm clock. If button pushed, alarm is stopped
"""
global alarm_time
# debounce, wait for 20 milliseconds
time.sleep(0.020)
if GPIO.input(channel):
# turn off alarm
if not speaker.is_stopped():
speaker.stop_sound()
if current_status[MAIN_STATUS] == MAIN_STATUS_ALARM:
# set MAIN_STATUS to wakeup
current_status[MAIN_STATUS] = MAIN_STATUS_WAKEUP
oled_update_display()
# set alarm_time to up_time
set_time(alarm_time, *up_time)
def encoder_rotate(channel):
assert channel == ENCODER_L
global encoder_ccw_time, encoder_cw_time
if GPIO.input(ENCODER_R) == GPIO.HIGH:
if time.time() - encoder_cw_time < 0.1:
pass # still clockwise
else:
if current_status[OLED_STATUS] == OLED_SETTING:
setting_status[SETTING_SELECTION] += 1
elif current_status[OLED_STATUS] == OLED_SET_HOUR:
setting_status[SETTING_TIME][0] = (
setting_status[SETTING_TIME][0] + 1) % 24
elif current_status[OLED_STATUS] == OLED_SET_MINUTE:
setting_status[SETTING_TIME][1] = (
setting_status[SETTING_TIME][1] + 1) % 60
oled_update_display()
encoder_ccw_time = time.time()
elif GPIO.input(ENCODER_R) == GPIO.LOW:
if time.time() - encoder_ccw_time < 0.1:
pass # still counter clockwise
else:
if current_status[OLED_STATUS] == OLED_SETTING:
setting_status[SETTING_SELECTION] -= 1
elif current_status[OLED_STATUS] == OLED_SET_HOUR:
setting_status[SETTING_TIME][0] = (
setting_status[SETTING_TIME][0] - 1) % 24
elif current_status[OLED_STATUS] == OLED_SET_MINUTE:
setting_status[SETTING_TIME][1] = (
setting_status[SETTING_TIME][1] - 1) % 60
oled_update_display()
encoder_cw_time = time.time()
def encoder_but(channel):
global bed_time, up_time
time.sleep(0.020)
if not GPIO.input(channel):
if current_status[OLED_STATUS] == OLED_DISPLAY:
current_status[OLED_STATUS] = OLED_SETTING
elif current_status[OLED_STATUS] == OLED_SETTING:
# determine whether to set bed time or up time
if setting_status[SETTING_SELECTION] == 0:
setting_status[SETTING_TIME] = bed_time
else:
setting_status[SETTING_TIME] = up_time
current_status[OLED_STATUS] = OLED_SET_HOUR
elif current_status[OLED_STATUS] == OLED_SET_HOUR:
current_status[OLED_STATUS] = OLED_SET_MINUTE
elif current_status[OLED_STATUS] == OLED_SET_MINUTE:
# store current setting
if setting_status[SETTING_SELECTION] == 0:
bed_time[0] = setting_status[SETTING_TIME][0]
bed_time[1] = setting_status[SETTING_TIME][1]
print('update bed time:', bed_time) # TODO: test
else:
up_time[0] = setting_status[SETTING_TIME][0]
up_time[1] = setting_status[SETTING_TIME][1]
print('update up time:', up_time) # TODO: test
setting_status[SETTING_SELECTION] = 0
current_status[OLED_STATUS] = OLED_DISPLAY
oled_update_display()
### helper functions ###
def get_time():
"""
get current time
@return: hour, min, sec
"""
current_time = time.localtime()
hour = current_time.tm_hour
minute = current_time.tm_min
sec = current_time.tm_sec
return hour, minute, sec
def get_date():
"""
get today's date
@return: month, day
"""
current_time = time.localtime()
month = current_time.tm_mon
day = current_time.tm_mday
return month, day
def set_time(time_object, hour, minute):
"""
set time given hour and min in 24hr format
@param time_object: time object to set
@param hour: hour to set
@param min: minute to set
"""
time_object[1] = minute % 60
time_object[0] = (hour + minute // 60) % 24
def inc_time(time_object, hour=0, minute=0):
"""
increment
@param time_object: time object to increase
@param hour: hour increment
@param min: minute to increment
"""
set_time(time_object, time_object[0] + hour, time_object[1] + minute)
def oled_update_display():
"""
change the oled display according to different status
should be manual called everytime the current_status is changed
FUTURE: separate process to check for state change and call oled_display
automatically?
"""
oled.clear_display()
if current_status[OLED_STATUS] == OLED_DISPLAY:
if current_status[MAIN_STATUS] == MAIN_STATUS_WAKEUP:
oled.add_text('wake up') # TODO: change to picture
elif current_status[MAIN_STATUS] == MAIN_STATUS_NEED_SLEEP:
oled.add_text('need sleep') # TODO: change to picture
oled.add_text('40% slept')
elif current_status[MAIN_STATUS] == MAIN_STATUS_SLEEP:
oled.add_text('sleep') # TODO: change to picture
elif current_status[MAIN_STATUS] == MAIN_STATUS_ALARM:
oled.add_text('alarm') # TODO: change to picture
elif current_status[OLED_STATUS] == OLED_SETTING:
for i in range(len(SETTING_ITEM)):
if i == (setting_status[SETTING_SELECTION]//2 % len(SETTING_ITEM)):
oled.add_text('> ' + SETTING_ITEM[i])
else:
oled.add_text(SETTING_ITEM[i])
elif current_status[OLED_STATUS] == OLED_SET_HOUR:
h, m = setting_status[SETTING_TIME]
oled.add_text(f'-{h:02d}-:{m:02d}')
elif current_status[OLED_STATUS] == OLED_SET_MINUTE:
h, m = setting_status[SETTING_TIME]
oled.add_text(f'{h:02d}:-{m:02d}-')
oled.update_display()
@webpage_flask.route("/")
def home():
return redirect("/index")
@webpage_flask.route("/index")
def home_template():
status = 'wakeup'
if current_status[MAIN_STATUS] == MAIN_STATUS_WAKEUP:
pass
elif current_status[MAIN_STATUS] == MAIN_STATUS_NEED_SLEEP:
status = 'need sleep'
elif current_status[MAIN_STATUS] == MAIN_STATUS_SLEEP:
status = 'sleep'
elif current_status[MAIN_STATUS] == MAIN_STATUS_ALARM:
status = 'alarm'
return render_template("index.html",
sleep_info=sleep_info,
up_time=f"{up_time[0]}:{up_time[1]}",
bed_time=f"{bed_time[0]}:{bed_time[1]}",
other_info=friends_sleep_info,
status=status)
@webpage_flask.route("/settings")
def settings_template():
global settings_info
settings_info = [['sleep time', f'{bed_time[0]:02d}:{bed_time[1]:02d}'],
['wake time', f"{up_time[0]:02d}:{up_time[1]:02d}"],
['volume', '50%'],
['brightness', '50%'],
['light sensitivity', light_threshold],
['12 hour format', time_12_hour]]
return render_template("settings.html",
settings=settings_info)
### background tasks ###
def run_webpage():
"""
process that runs the webpage continuously
"""
# TODO
pass
def update_time():
"""
update the time shown on LED every 1 seconds, the ':' will blink
"""
while True:
hour, minute, _ = get_time()
if time_12_hour:
hour %= 12
if hour == 0:
hour = 12
clock.set_display(f'{hour:02d}:{minute:02d}')
time.sleep(1)
clock.set_display(f'{hour:02d}{minute:02d}')
time.sleep(1)
def check_sleeping():
"""
process that check whether light turns off and phone is nearby RFID
"""
global today_bed_time, bed_time
while True:
if current_status[MAIN_STATUS] == MAIN_STATUS_WAKEUP:
h, m, _ = get_time()
if h == bed_time[0] and m == bed_time[1]:
current_status[MAIN_STATUS] = MAIN_STATUS_NEED_SLEEP
oled_update_display()
today_bed_time = time.time()
GPIO.output(RED_LED, LED_ON)
if current_status[MAIN_STATUS] == MAIN_STATUS_NEED_SLEEP:
# check phone
rfid.read() # will block until RFID is read
voltage = light_sensor.read()
# check light sensor
if voltage <= light_threshold:
current_status[MAIN_STATUS] = MAIN_STATUS_SLEEP
oled_update_display()
# if sleep within BED_TIME_THRESHOLD, count as follow schedule
month, day = get_date()
if (time.time() - today_bed_time)/60 <= BED_TIME_THRESHOLD:
sleep_info.append((f'{month:02d}/{day:02d}',
f'{bed_time[0]:02d}:{bed_time[1]:02d}',
True))
else:
h, m, _ = get_time()
sleep_info.append((f'{month:02d}/{day:02d}',
f'{h:02d}:{m:02d}',
False))
GPIO.output(RED_LED, LED_OFF)
elif current_status[MAIN_STATUS] == MAIN_STATUS_SLEEP:
# check phone
id, _ = rfid.read_no_block()
voltage = light_sensor.read()
# check light sensor
if not id or voltage > light_threshold:
current_status[MAIN_STATUS] = MAIN_STATUS_NEED_SLEEP
oled_update_display()
time.sleep(1)
def alarm_clock():
"""
process for alarm clock
"""
while True:
h, m, _ = get_time()
if current_status[MAIN_STATUS] == MAIN_STATUS_SLEEP:
if h == up_time[0] and m == up_time[1]:
if current_status[ALARM_STATUS] == ALARM_ON:
# set status to alarm if sleep before
current_status[MAIN_STATUS] = MAIN_STATUS_ALARM
else:
current_status[MAIN_STATUS] = MAIN_STATUS_WAKEUP
oled_update_display()
if current_status[MAIN_STATUS] == MAIN_STATUS_ALARM:
if h == alarm_time[0] and m == alarm_time[1]:
# move next alarm to SNOOZE_TIME minutes later
inc_time(alarm_time, minute=SNOOZE_TIME)
speaker.play_sound()
time.sleep(MIN_DELAY)
if __name__ == "__main__":
# one time tasks
simple_GPIO_setup()
peripheral_setup()
oled_update_display()
# background tasks
background_tasks = [alarm_clock, update_time, check_sleeping]
# start background tasks
for task in background_tasks:
thread = threading.Thread(target=task, daemon=True)
thread.start()
# TODO
# # turn on webpage
# webpage_flask.run(host='0.0.0.0', port=80) #, debug=True, threaded=True)
# TODO: test only
try:
print("program started")
ex = input('type exit to exit: ')
while ex != 'exit':
ex = input('type exit to exit: ')
except KeyboardInterrupt:
pass
print("program finished, perform GPIO cleanup")
GPIO.cleanup()