GoodMorningSunshine/clock.py

387 lines
13 KiB
Python
Raw Normal View History

2022-12-11 16:08:13 +01:00
# Many thanks to https://stackoverflow.com/questions/18923321/making-a-clock-in-kivy
import collections
import datetime
import math
import sys
import traceback
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.graphics import Color, Line, Rectangle
from multiprocessing import Process
from playsound import playsound
Builder.load_string('''
<MyClockWidget>:
on_pos: self.update_clock()
on_size: self.update_clock()
FloatLayout
id: face
size_hint: None, None
pos_hint: {"center_x":0.5, "center_y":0.5}
size: 0.9*min(root.size), 0.9*min(root.size)
canvas:
Color:
rgb: 0.1, 0.1, 0.1
Ellipse:
size: self.size
pos: self.pos
FloatLayout
id: hands
size_hint: None, None
pos_hint: {"center_x":0.5, "center_y":0.5}
size: 0.9*min(root.size), 0.9*min(root.size)
FloatLayout
id: set_alarm_button
size_hint: None, None
pos_hint: {"center_x":0.9, "center_y":0.1}
size: 0.1*min(root.size), 0.1*min(root.size)
''')
Position = collections.namedtuple('Position', 'x y')
global sound_process
sound_process = None
def play_sound(source):
2022-12-12 22:21:57 +01:00
while True:
print("beep beep!")
2022-12-12 22:21:57 +01:00
playsound(source)
2022-12-11 16:08:13 +01:00
2023-01-05 22:02:22 +01:00
class AlarmSettings():
2022-12-11 16:08:13 +01:00
alarm_time = datetime.datetime(2022, 12, 10, 7, 30, 0, 0)
alarm_activated = False
alarm_modified = False
led_color = [0, 0, 0]
# sound_source = "https://icecast.omroep.nl/radio1-bb-mp3"
sound_source = "Woodpecker Chirps - QuickSounds.com.mp3"
2022-12-17 21:00:10 +01:00
seconds_to_sunrise = 30 * 60
2022-12-11 16:08:13 +01:00
2023-01-05 22:02:22 +01:00
class MyClockWidget(FloatLayout):
grabbed = ""
face_numbers = []
# if not grabbed, set_alarm_timeout_counter is incremented at every update call (ie: 60 Hz);
# this is used to blink the hands at 1 Hz when setting the alarm and releasing the hand
set_alarm_timeout_counter = 0
seconds_to_next_alarm = 0
led_color = [0, 0, 0]
2023-01-06 20:18:22 +01:00
# view can be "clock" "set_alarm" "settings" or "calendar"
view = "clock"
2022-12-11 16:08:13 +01:00
def draw_face(self):
"""
Add number labels when added in widget hierarchy
"""
2023-01-05 22:02:22 +01:00
alarm_settings = App.get_running_app().alarm_settings
2023-01-06 20:18:22 +01:00
if self.view == "set_alarm":
2023-01-05 22:02:22 +01:00
time = alarm_settings.alarm_time
2022-12-11 16:08:13 +01:00
else:
time = datetime.datetime.now()
for i in range(1, 13):
if time.hour < 12:
offset = 0
else:
offset = 12
self.face_numbers.append(Label(
text=str(i + offset),
pos_hint={
# pos_hint is a fraction in range (0, 1)
"center_x": 0.5 + 0.45*math.sin(2 * math.pi * i/12),
"center_y": 0.5 + 0.45*math.cos(2 * math.pi * i/12),
}
))
self.ids["face"].add_widget(self.face_numbers[i - 1])
def update_face(self):
2023-01-05 22:02:22 +01:00
alarm_settings = App.get_running_app().alarm_settings
2023-01-06 20:18:22 +01:00
if self.view == "set_alarm":
2023-01-05 22:02:22 +01:00
time = alarm_settings.alarm_time
2022-12-11 16:08:13 +01:00
else:
time = datetime.datetime.now()
for i in range(0, 12):
if time.hour < 12:
offset = 0
else:
offset = 12
self.face_numbers[i].text=str(i + 1 + offset)
def on_parent(self, myclock, parent):
self.draw_face()
def position_on_clock(self, fraction, length):
"""
Calculate position in the clock using trygonometric functions
"""
center_x = self.size[0]/2
center_y = self.size[1]/2
return Position(
center_x + length * math.sin(2 * math.pi * fraction),
center_y + length * math.cos(2 * math.pi * fraction),
)
def update_set_alarm_button(self):
set_alarm_button = self.ids["set_alarm_button"]
2023-01-05 22:02:22 +01:00
app = App.get_running_app()
alarm_settings = app.alarm_settings
2023-01-06 20:18:22 +01:00
if (self.view == "set_alarm") or alarm_settings.alarm_activated:
2022-12-11 16:08:13 +01:00
source = 'alarm_on.png'
2022-12-17 20:50:47 +01:00
rgb = [0.9, 0.0, 0.0]
2022-12-11 16:08:13 +01:00
else:
source = 'alarm_off.png'
2022-12-17 20:50:47 +01:00
rgb = [1.0, 1.0, 1.0]
2022-12-11 16:08:13 +01:00
set_alarm_button = self.ids["set_alarm_button"]
set_alarm_button.canvas.clear()
with set_alarm_button.canvas:
2022-12-17 20:50:47 +01:00
Color(rgb[0], rgb[1], rgb[2])
2022-12-11 16:08:13 +01:00
Rectangle(size=set_alarm_button.size, pos=set_alarm_button.pos, source=source)
def sun_rise(self):
2023-01-05 22:02:22 +01:00
alarm_settings = App.get_running_app().alarm_settings
2022-12-11 16:08:13 +01:00
# to do: calculate brightness and color according to sun rise instead of linear increment
2023-01-05 22:02:22 +01:00
intensity = math.floor((1.0 - self.seconds_to_next_alarm / alarm_settings.seconds_to_sunrise) * 256.0)
2022-12-17 20:50:47 +01:00
if intensity < 0:
intensity = 0
2022-12-11 16:08:13 +01:00
led_color = [0, 0, 0]
for i in range(3):
led_color[i] = intensity
if self.led_color != led_color:
self.led_color = led_color
def check_sun_rise(self):
2023-01-05 22:02:22 +01:00
alarm_settings = App.get_running_app().alarm_settings
if alarm_settings.alarm_activated == False:
2022-12-11 16:08:13 +01:00
return
2023-01-05 22:02:22 +01:00
if self.seconds_to_next_alarm < alarm_settings.seconds_to_sunrise:
2022-12-11 16:08:13 +01:00
self.sun_rise()
def check_play_sound(self):
global sound_process
2023-01-05 22:02:22 +01:00
alarm_settings = App.get_running_app().alarm_settings
2022-12-11 16:08:13 +01:00
2023-01-05 22:02:22 +01:00
if alarm_settings.alarm_activated == False:
2022-12-11 16:08:13 +01:00
return
if self.seconds_to_next_alarm < 0.1 and sound_process is None:
2023-01-05 22:02:22 +01:00
sound_process = Process(target=play_sound, args=(alarm_settings.sound_source,))
2022-12-11 16:08:13 +01:00
sound_process.start()
def calc_seconds_to_next_alarm(self):
2023-01-05 22:02:22 +01:00
alarm_settings = App.get_running_app().alarm_settings
if alarm_settings.alarm_activated == False:
2022-12-11 16:08:13 +01:00
return
# Make sure alarm_time is in the future but not more than 24 h from now
now = datetime.datetime.now()
2023-01-05 22:02:22 +01:00
d = alarm_settings.alarm_time - now
alarm_settings.alarm_time -= datetime.timedelta(days=d.days)
2022-12-11 16:08:13 +01:00
# Calculate number of seconds until next alarm
2023-01-05 22:02:22 +01:00
d = alarm_settings.alarm_time - now
2022-12-11 16:08:13 +01:00
self.seconds_to_next_alarm = d.days * 24 * 3600 + d.seconds + d.microseconds / 1000000.0
def check_alarm(self):
self.calc_seconds_to_next_alarm()
self.check_sun_rise()
self.check_play_sound()
def update_clock(self, *args):
self.check_alarm()
"""
Redraw clock hands
"""
2023-01-05 22:02:22 +01:00
alarm_settings = App.get_running_app().alarm_settings
2023-01-06 20:18:22 +01:00
if self.view == "set_alarm":
2023-01-05 22:02:22 +01:00
time = alarm_settings.alarm_time
2022-12-11 16:08:13 +01:00
else:
time = datetime.datetime.now()
hands = self.ids["hands"]
seconds_hand = self.position_on_clock(time.second/60, length=0.45*hands.size[0])
minutes_hand = self.position_on_clock(time.minute/60+time.second/3600, length=0.40*hands.size[0])
hours_hand = self.position_on_clock(time.hour/12 + time.minute/720, length=0.35*hands.size[0])
self.update_face()
self.update_set_alarm_button()
hands.canvas.clear()
with hands.canvas:
2023-01-06 20:18:22 +01:00
if self.view == "set_alarm":
2023-01-05 22:02:22 +01:00
if self.grabbed != "" or self.set_alarm_timeout_counter < 1 * 60 or \
self.set_alarm_timeout_counter % 60 <= 30 or alarm_settings.alarm_modified == False:
2022-12-11 16:08:13 +01:00
Color(0.9, 0.0, 0.0)
Line(points=[hands.center_x, hands.center_y, hours_hand.x, hours_hand.y], width=3, cap="round")
Color(0.8, 0.0, 0.0)
Line(points=[hands.center_x, hands.center_y, minutes_hand.x, minutes_hand.y], width=2, cap="round")
if self.grabbed == "":
self.set_alarm_timeout_counter += 1
if self.set_alarm_timeout_counter >= 4 * 60 + 30:
2023-01-06 20:18:22 +01:00
self.view = "clock"
2022-12-11 16:08:13 +01:00
self.set_alarm_timeout_counter = 0
2023-01-05 22:02:22 +01:00
if alarm_settings.alarm_modified:
alarm_settings.alarm_activated = True
alarm_settings.alarm_modified = False
2022-12-11 16:08:13 +01:00
else:
Color(0.9, 0.9, 0.9)
Line(points=[hands.center_x, hands.center_y, hours_hand.x, hours_hand.y], width=3, cap="round")
Color(0.8, 0.8, 0.8)
Line(points=[hands.center_x, hands.center_y, minutes_hand.x, minutes_hand.y], width=2, cap="round")
Color(0.7, 0.7, 0.7)
Line(points=[hands.center_x, hands.center_y, seconds_hand.x, seconds_hand.y], width=1, cap="round")
def on_alarm_button_pressed(self):
2023-01-05 22:02:22 +01:00
alarm_settings = App.get_running_app().alarm_settings
alarm_settings.alarm_modified = False
2022-12-11 16:08:13 +01:00
self.set_alarm_timeout_counter = 0
2023-01-06 20:18:22 +01:00
if self.view == "set_alarm":
self.view = "clock"
2023-01-05 22:02:22 +01:00
alarm_settings.alarm_activated = False
2022-12-11 16:08:13 +01:00
else:
2023-01-06 20:18:22 +01:00
self.view = "set_alarm"
2023-01-05 22:02:22 +01:00
alarm_settings.alarm_activated = True
2022-12-11 16:08:13 +01:00
def on_touch_up(self, touch):
self.grabbed = ""
2023-01-05 22:02:22 +01:00
alarm_settings = App.get_running_app().alarm_settings
2023-01-06 20:18:22 +01:00
if (self.view == "set_alarm") and (self.grabbed == "hour" or self.grabbed == "minute"):
2022-12-11 16:08:13 +01:00
self.set_alarm_timeout_counter = 0
def on_touch_move(self, touch):
2023-01-05 22:02:22 +01:00
alarm_settings = App.get_running_app().alarm_settings
2022-12-11 16:08:13 +01:00
self.alarm_set_timeout = 0
x = touch.pos[0] - self.size[0]/2
y = touch.pos[1] - self.size[1]/2
angle = math.atan2(y, x) / math.pi; # angle is between -1 and 1
if self.grabbed == "minute":
2023-01-05 22:02:22 +01:00
alarm_settings.alarm_modified = True
2022-12-11 16:08:13 +01:00
self.set_alarm_timeout_counter = 0
minute = round(-angle * 30 + 15)
if minute < 0:
minute += 60
if minute == 60:
minute = 59
# hour correction
2023-01-05 22:02:22 +01:00
hour = alarm_settings.alarm_time.hour
if alarm_settings.alarm_time.minute >= 55 and minute <= 5:
2022-12-11 16:08:13 +01:00
hour += 1
2023-01-05 22:02:22 +01:00
elif alarm_settings.alarm_time.minute <= 5 and minute >= 55:
2022-12-11 16:08:13 +01:00
hour -= 1
if hour == 24:
hour = 0
elif hour == -1:
hour = 23
2023-01-05 22:02:22 +01:00
alarm_settings.alarm_time = datetime.datetime(alarm_settings.alarm_time.year, \
alarm_settings.alarm_time.month, alarm_settings.alarm_time.day, \
hour, minute, alarm_settings.alarm_time.second, 0)
2022-12-11 16:08:13 +01:00
elif self.grabbed == "hour":
2023-01-05 22:02:22 +01:00
alarm_settings.alarm_modified = True
2022-12-11 16:08:13 +01:00
self.set_alarm_timeout_counter = 0
hour = round(-angle * 6 + 3)
if hour < 0:
hour += 12
if hour == 12:
hour = 0
2023-01-05 22:02:22 +01:00
if alarm_settings.alarm_time.hour >= 12:
2022-12-11 16:08:13 +01:00
hour += 12
# AM / PM correction
2023-01-05 22:02:22 +01:00
if alarm_settings.alarm_time.hour == 11 and hour == 0:
2022-12-11 16:08:13 +01:00
hour = 12
2023-01-05 22:02:22 +01:00
elif alarm_settings.alarm_time.hour == 23 and hour == 12:
2022-12-11 16:08:13 +01:00
hour = 0
2023-01-05 22:02:22 +01:00
elif alarm_settings.alarm_time.hour == 0 and hour == 11:
2022-12-11 16:08:13 +01:00
hour = 23
2023-01-05 22:02:22 +01:00
elif alarm_settings.alarm_time.hour == 12 and hour == 23:
2022-12-11 16:08:13 +01:00
hour = 11
# AM / PM boundary
2023-01-05 22:02:22 +01:00
alarm_settings.alarm_time = datetime.datetime(alarm_settings.alarm_time.year, \
alarm_settings.alarm_time.month, alarm_settings.alarm_time.day, \
hour, alarm_settings.alarm_time.minute, alarm_settings.alarm_time.second, 0)
2022-12-11 16:08:13 +01:00
def on_touch_down(self, touch):
global sound_process
2023-01-05 22:02:22 +01:00
alarm_settings = App.get_running_app().alarm_settings
time = alarm_settings.alarm_time
2022-12-11 16:08:13 +01:00
hands = self.ids["hands"]
minutes_hand = self.position_on_clock(time.minute/60+time.second/3600, length=0.40*hands.size[0])
hours_hand = self.position_on_clock(time.hour/12 + time.minute/720, length=0.35*hands.size[0])
self.grabbed = ""
2023-01-05 22:02:22 +01:00
app = App.get_running_app()
alarm_settings = app.alarm_settings
2022-12-11 16:08:13 +01:00
if (0.85 <= touch.spos[0] <= 0.95) and (0.05 <= touch.spos[1] <= 0.15):
self.on_alarm_button_pressed()
elif sound_process is not None:
kill_sound_process()
2023-01-06 20:18:22 +01:00
elif self.view == "set_alarm":
2022-12-11 16:08:13 +01:00
self.set_alarm_timeout_counter = 0
if (minutes_hand.x - 0.1 * self.size[0] <= touch.pos[0] <= minutes_hand.x + 0.1 * self.size[0]) and \
(minutes_hand.y - 0.1 * self.size[1] <= touch.pos[1] <= minutes_hand.y + 0.1 * self.size[1]):
self.grabbed = "minute"
elif (hours_hand.x - 0.1 * self.size[0] <= touch.pos[0] <= hours_hand.x + 0.1 * self.size[0]) and \
(hours_hand.y - 0.1 * self.size[1] <= touch.pos[1] <= hours_hand.y + 0.1 * self.size[1]):
self.grabbed = "hour"
class MyApp(App):
2023-01-05 22:02:22 +01:00
alarm_settings = AlarmSettings()
2022-12-11 16:08:13 +01:00
def build(self):
clock_widget = MyClockWidget()
# update initially, just after construction of the widget is complete
Clock.schedule_once(clock_widget.update_clock, 0)
# then update 60 times per second
Clock.schedule_interval(clock_widget.update_clock, 1.0/60.0)
return clock_widget
def kill_sound_process():
global sound_process
if sound_process is not None:
sound_process.kill()
sound_process = None
def except_hook(type, value, tb):
kill_sound_process()
if __name__ == '__main__':
# sys.excepthook = except_hook
MyApp().run()