-
Notifications
You must be signed in to change notification settings - Fork 2
/
alarm_clock.py
495 lines (402 loc) · 14.8 KB
/
alarm_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
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
#!/usr/bin/python
from time import sleep
from datetime import datetime, timedelta
from Adafruit_I2C import Adafruit_I2C
from Adafruit_MCP230xx import Adafruit_MCP230XX
from Adafruit_CharLCDPlate import Adafruit_CharLCDPlate
from apiclient.discovery import build
from oauth2client.file import Storage
from oauth2client.client import OAuth2WebServerFlow
from oauth2client.tools import run
from ConfigParser import SafeConfigParser
import gflags
import httplib2
import time
import subprocess
import smbus
import pygame
import pickle
import os
import sys
import random
import threading
# *************************************************************************************************************************
# initialize the LCD plate
# use busnum = 0 for raspi version 1 (256MB) and busnum = 1 for version 2
# *************************************************************************************************************************
lcd = Adafruit_CharLCDPlate(busnum = 1)
# *************************************************************************************************************************
# ******** global variables & constants
# *************************************************************************************************************************
# global variables
gcal_client_id = ""
gcal_client_secret = ""
gcal_developerKey = ""
gcal_storage = ""
data_file = ""
mp3_path = ""
service = -1
SHOW_CURRENT_TIME = 0
SHOW_ALARM_TIMES = 1
SHOW_ALARM_RUNNING = 2
SHOW_NOTHING = 3
pygame_status = False
ipaddr = "0.0.0.0"
menu_state = SHOW_CURRENT_TIME
alarm_times = []
alarm_index = 0
current_alarm = " --- "
hours = datetime.now().hour - 1
minutes = datetime.now().minute - 1
timestamp = time.time()
# *************************************************************************************************************************
# ******** threaded class for loading google calendar data
# *************************************************************************************************************************
class GoogleCalendarData(threading.Thread):
def __init__(self, service):
self.service = service
threading.Thread.__init__ (self)
def run(self):
self.alarm_times = ""
date = datetime.now().strftime("%Y-%m-%dT%H:%M:%S.000Z")
endDate = (datetime.now() + timedelta(days=14)).strftime("%Y-%m-%dT%H:%M:%S.000Z")
calendar = self.service.calendars().get(calendarId='primary').execute()
events = self.service.events().list(
calendarId=calendar['id'],
singleEvents=True,
#maxResults=20,
orderBy="startTime",
timeMin=date,
timeMax=endDate,
q="ALARM"
).execute()
new_alarm_times = []
while True:
for event in events.get('items', []):
# read in / parse time format
timedata = time.strptime(event['start']['dateTime'].split("+")[0], "%Y-%m-%dT%H:%M:%S")
# convert to unix epoch
timestamp = time.mktime(timedata)
# append new alarm date
new_alarm_times.append( { "date": timestamp, "status": True } )
page_token = events.get('nextPageToken')
if page_token:
events = self.service.events().list(
calendarId=calendar['id'],
singleEvents=True,
orderBy="startTime",
timeMin=date,
timeMax=endDate,
q="ALARM",
pageToken=page_token
).execute()
else:
break
self.alarm_times = new_alarm_times
def stop(self):
try:
self._Thread__stop()
except:
print(str(self.getName()) + " did not stop!")
# *************************************************************************************************************************
# ******** helpers
# *************************************************************************************************************************
def _read_config_file():
global gcal_client_id, gcal_client_secret, gcal_developerKey, gcal_storage, data_file, mp3_path
parser = SafeConfigParser()
parser.read('alarm_clock.cfg')
gcal_client_id = parser.get('google_calendar', 'client_id')
gcal_client_secret = parser.get('google_calendar', 'client_secret')
gcal_developerKey = parser.get('google_calendar', 'developerKey')
gcal_storage = parser.get('google_calendar', 'storage')
data_file = parser.get('alarm_clock', 'data_file')
mp3_path = parser.get('alarm_clock', 'mp3_path')
def _init_google_calendar():
global service
FLAGS = gflags.FLAGS
FLOW = OAuth2WebServerFlow(
client_id=gcal_client_id, client_secret=gcal_client_secret,
scope='https://www.googleapis.com/auth/calendar.readonly',
user_agent='alarm_clock.py/1.0.0')
FLAGS.auth_local_webserver = False
storage = Storage(gcal_storage)
credentials = storage.get()
if (credentials is None) or (credentials.invalid == True):
credentials = run(FLOW, storage)
http = httplib2.Http()
http = credentials.authorize(http)
service = build(serviceName='calendar', version='v3', http=http, developerKey=gcal_developerKey)
def _run_cmd_and_return(cmd):
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
output = p.communicate()[0]
return output
def _run_cmd_in_background(cmd):
subprocess.Popen(cmd, shell=True)
def _shutdown_pi():
#subprocess.call(["shutdown", "-h", "now"])
#subprocess.call("shutdown -h now", shell=True)
os.system("sudo shutdown -h now")
sys.exit(0)
def _play_mp3(mp3):
global pygame_status
if (not pygame_status):
pygame.init()
pygame.mixer.init()
pygame_status = True
if (pygame.mixer.music.get_busy()):
pygame.mixer.music.stop()
pygame.mixer.music.load(mp3)
pygame.mixer.music.play(-1)
def _stop_mp3():
global pygame_status
# unload pygame stuff to save cpu time
if (pygame_status):
if (pygame.mixer.music.get_busy()):
pygame.mixer.music.stop()
pygame.mixer.quit()
pygame.quit()
pygame_status = False
def _check_alarm_times():
global current_alarm
current_timestamp = time.time()
for index, alarm in enumerate(alarm_times):
date = alarm["date"]
status = alarm["status"]
# only if alarm is enabled
if (status):
# only during a 5 minute window
if (current_timestamp >= date) and (current_timestamp < date + 300):
current_alarm = datetime.fromtimestamp(date).strftime('%b %d --- %H:%M')
del alarm_times[index]
return True
return False
def _load_data():
global hours, alarm_times
alarm_times = pickle.load( open( data_file, "rb" ) )
hours = datetime.now().hour
_merge_alarm_data(_get_gcal_data())
_save_data()
def _save_data():
pickle.dump( alarm_times, open( data_file, "wb" ) )
def _get_gcal_data():
gcal = GoogleCalendarData(service)
gcal.start()
# timeout of 60 seconds
gcal.join(60)
# check if there was a timeout and the thread is still alive
new_alarm_times = alarm_times
if gcal.isAlive():
gcal.stop()
else:
new_alarm_times = gcal.alarm_times
return new_alarm_times
def _merge_alarm_data(new_alarm_times):
global alarm_times
for alarm in alarm_times:
# check if status == False, if so then check if alarm date exists inside new_alarm_times too,
# and update its status in there in order to not overwrite the status=False state from the current alarm data
if (not alarm["status"]):
for index, new_alarm in enumerate(new_alarm_times):
if (new_alarm["date"] == alarm["date"]):
new_alarm_times[index]["status"] = False
alarm_times = new_alarm_times
def _set_alarm_status(index, status):
global alarm_times
alarm_times[index]["status"] = status
_save_data()
def _add_mp3_path(file):
return mp3_path + file
def _get_mp3_files():
files = os.listdir(mp3_path)
return map(_add_mp3_path, files)
def _get_random_mp3_file():
return random.choice(_get_mp3_files())
def _get_ip():
cmd = "ip addr show eth0 | grep inet | awk '{print $2}' | cut -d/ -f1"
ip = _run_cmd_and_return(cmd)
return ip
# *************************************************************************************************************************
# ******** interact with LCD
# *************************************************************************************************************************
def init_display():
lcd.clear()
lcd.backlight(lcd.YELLOW)
lcd.message("JamesClonk's\nAlarm Clock!")
sleep(2)
lcd.backlight(lcd.VIOLET)
def show_time():
global minutes
lcd.clear()
lcd.message(datetime.now().strftime('%b %d --- %H:%M\n'))
lcd.message('IP %s' % ( ipaddr ) )
minutes = datetime.now().minute
def show_alarm():
global timestamp
alarm = alarm_times[alarm_index]
date = datetime.fromtimestamp(alarm["date"]).strftime('%b %d --- %H:%M')
status = "is enabled" if alarm["status"] else "is disabled"
lcd.clear()
lcd.message(date + "\n" + status)
# activity reset
timestamp = time.time()
def start_alarm():
switch_to_menu_alarm_running()
_play_mp3(_get_random_mp3_file())
lcd.message("ALARM !!!\n" + current_alarm)
def stop_alarm():
_stop_mp3()
def load_data():
# only display loading message in current time menu, as to not disturb the "nothing" menu
if (menu_state == SHOW_CURRENT_TIME):
lcd.clear()
lcd.backlight(lcd.BLUE)
lcd.message("loading data..")
_load_data()
switch_to_menu_time_display()
else:
_load_data()
def shutdown_pi():
lcd.clear()
lcd.backlight(lcd.RED)
lcd.message("shutting down..")
sleep(1)
_shutdown_pi()
def time_menu_up():
# force load of alarm data
load_data()
def time_menu_down():
global current_alarm
current_alarm = " ..forced"
# force start of alarm
start_alarm()
sleep(2)
def time_menu_left():
_run_cmd_in_background("sudo /etc/init.d/rain.sh stop")
lcd.clear()
lcd.message("stop rain.sh\nservice")
sleep(2)
show_time()
def time_menu_right():
_run_cmd_in_background("sudo /etc/init.d/rain.sh start")
lcd.clear()
lcd.message("start rain.sh\nservice")
sleep(2)
show_time()
def alarm_menu_up():
global alarm_index, timestamp
alarm_index = alarm_index - 1
if (alarm_index < 0):
alarm_index = len(alarm_times) - 1
show_alarm()
def alarm_menu_down():
global alarm_index, timestamp
alarm_index = alarm_index + 1
if (alarm_index >= len(alarm_times)):
alarm_index = 0
show_alarm()
def alarm_menu_left():
lcd.clear()
lcd.message("enabled!")
_set_alarm_status(alarm_index,True)
sleep(2)
show_alarm()
def alarm_menu_right():
lcd.clear()
lcd.message("disabled!")
_set_alarm_status(alarm_index,False)
sleep(2)
show_alarm()
def switch_to_menu_time_display():
global menu_state, minutes
lcd.clear()
lcd.backlight(lcd.VIOLET)
menu_state = SHOW_CURRENT_TIME
minutes = datetime.now().minute - 1
def switch_to_menu_alarm_times():
global menu_state, alarm_index
lcd.clear()
lcd.backlight(lcd.RED)
menu_state = SHOW_ALARM_TIMES
alarm_index = 0
show_alarm()
def switch_to_menu_alarm_running():
global menu_state, timestamp
lcd.clear()
lcd.backlight(lcd.YELLOW)
menu_state = SHOW_ALARM_RUNNING
timestamp = time.time()
def switch_to_menu_nothing():
global menu_state
lcd.clear()
lcd.backlight(lcd.OFF)
menu_state = SHOW_NOTHING
# *************************************************************************************************************************
# ******** menus
# *************************************************************************************************************************
def menu_time_display():
# reload alarm data every hour
if (datetime.now().hour != hours):
load_data()
# write current time to LCD every minute
elif (datetime.now().minute != minutes):
show_time()
elif (lcd.buttonPressed(lcd.SELECT)):
switch_to_menu_alarm_times()
elif (lcd.buttonPressed(lcd.UP)):
time_menu_up()
elif (lcd.buttonPressed(lcd.DOWN)):
time_menu_down()
elif (lcd.buttonPressed(lcd.LEFT)):
time_menu_left()
elif (lcd.buttonPressed(lcd.RIGHT)):
time_menu_right()
def menu_alarm_times():
# switch back to time display after 15 seconds of "inactivity"
if (time.time() >= timestamp + 15):
switch_to_menu_time_display()
elif (lcd.buttonPressed(lcd.SELECT)):
switch_to_menu_nothing()
elif (lcd.buttonPressed(lcd.UP)):
alarm_menu_up()
elif (lcd.buttonPressed(lcd.DOWN)):
alarm_menu_down()
elif (lcd.buttonPressed(lcd.LEFT)):
alarm_menu_left()
elif (lcd.buttonPressed(lcd.RIGHT)):
alarm_menu_right()
def menu_alarm_running():
# switch back to time display after 300 seconds of alarm, or if select is pressed
if (time.time() >= timestamp + 300) or (lcd.buttonPressed(lcd.SELECT)):
stop_alarm()
switch_to_menu_time_display()
elif (lcd.buttonPressed(lcd.LEFT)) and (lcd.buttonPressed(lcd.RIGHT)):
shutdown_pi()
def menu_nothing():
if (datetime.now().hour != hours):
load_data()
elif (lcd.buttonPressed(lcd.SELECT)):
switch_to_menu_time_display()
elif (lcd.buttonPressed(lcd.LEFT)) and (lcd.buttonPressed(lcd.RIGHT)):
shutdown_pi()
# *************************************************************************************************************************
# ******** main
# *************************************************************************************************************************
def main():
global ipaddr
ipaddr = _get_ip()
_read_config_file()
_init_google_calendar()
init_display()
while True:
if (menu_state == SHOW_ALARM_RUNNING):
menu_alarm_running()
elif (_check_alarm_times()):
start_alarm()
elif (menu_state == SHOW_CURRENT_TIME):
menu_time_display()
elif (menu_state == SHOW_ALARM_TIMES):
menu_alarm_times()
elif (menu_state == SHOW_NOTHING):
menu_nothing()
sleep(0.1)
main()