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
|
|
|
|
|
2022-12-17 18:12:13 +01:00
|
|
|
def play_sound(source):
|
2022-12-12 22:21:57 +01:00
|
|
|
while True:
|
2022-12-17 18:12:13 +01:00
|
|
|
print("beep beep!")
|
2022-12-12 22:21:57 +01:00
|
|
|
playsound(source)
|
2022-12-11 16:08:13 +01:00
|
|
|
|
|
|
|
class MyClockWidget(FloatLayout):
|
|
|
|
set_alarm_mode = False
|
|
|
|
alarm_time = datetime.datetime(2022, 12, 10, 7, 30, 0, 0)
|
|
|
|
alarm_activated = False
|
|
|
|
|
|
|
|
grabbed = ""
|
|
|
|
face_numbers = []
|
|
|
|
set_alarm_timeout_counter = 0
|
|
|
|
alarm_modified = False
|
2022-12-17 21:00:10 +01:00
|
|
|
seconds_to_next_alarm = 0
|
2022-12-11 16:08:13 +01:00
|
|
|
led_color = [0, 0, 0]
|
2022-12-17 18:12:13 +01:00
|
|
|
# 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
|
|
|
|
|
|
|
def draw_face(self):
|
|
|
|
"""
|
|
|
|
Add number labels when added in widget hierarchy
|
|
|
|
"""
|
|
|
|
if self.set_alarm_mode:
|
|
|
|
time = self.alarm_time
|
|
|
|
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):
|
|
|
|
if self.set_alarm_mode:
|
|
|
|
time = self.alarm_time
|
|
|
|
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"]
|
|
|
|
|
|
|
|
if self.set_alarm_mode or self.alarm_activated:
|
|
|
|
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):
|
|
|
|
# to do: calculate brightness and color according to sun rise instead of linear increment
|
2022-12-17 21:00:10 +01:00
|
|
|
intensity = math.floor((1.0 - self.seconds_to_next_alarm / self.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):
|
|
|
|
if self.alarm_activated == False:
|
|
|
|
return
|
|
|
|
|
2022-12-17 21:00:10 +01:00
|
|
|
if self.seconds_to_next_alarm < self.seconds_to_sunrise:
|
2022-12-11 16:08:13 +01:00
|
|
|
self.sun_rise()
|
|
|
|
|
|
|
|
def check_play_sound(self):
|
|
|
|
global sound_process
|
|
|
|
|
|
|
|
if self.alarm_activated == False:
|
|
|
|
return
|
|
|
|
|
|
|
|
if self.seconds_to_next_alarm < 0.1 and sound_process is None:
|
2022-12-17 18:12:13 +01:00
|
|
|
sound_process = Process(target=play_sound, args=(self.sound_source,))
|
2022-12-11 16:08:13 +01:00
|
|
|
sound_process.start()
|
|
|
|
|
|
|
|
def calc_seconds_to_next_alarm(self):
|
|
|
|
if self.alarm_activated == False:
|
|
|
|
return
|
|
|
|
|
|
|
|
# Make sure alarm_time is in the future but not more than 24 h from now
|
|
|
|
now = datetime.datetime.now()
|
|
|
|
d = self.alarm_time - now
|
|
|
|
self.alarm_time -= datetime.timedelta(days=d.days)
|
|
|
|
|
|
|
|
# Calculate number of seconds until next alarm
|
|
|
|
d = self.alarm_time - now
|
|
|
|
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
|
|
|
|
"""
|
|
|
|
if self.set_alarm_mode:
|
|
|
|
time = self.alarm_time
|
|
|
|
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:
|
|
|
|
if self.set_alarm_mode:
|
2022-12-17 21:00:10 +01:00
|
|
|
# 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
|
2022-12-11 16:08:13 +01:00
|
|
|
if self.grabbed != "" or self.set_alarm_timeout_counter < 1 * 60 or self.set_alarm_timeout_counter % 60 <= 30 or self.alarm_modified == False:
|
|
|
|
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:
|
|
|
|
self.set_alarm_mode = False
|
|
|
|
self.set_alarm_timeout_counter = 0
|
|
|
|
|
|
|
|
if self.alarm_modified:
|
|
|
|
self.alarm_activated = True
|
|
|
|
self.alarm_modified = False
|
|
|
|
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):
|
|
|
|
self.alarm_modified = False
|
|
|
|
self.set_alarm_timeout_counter = 0
|
|
|
|
|
|
|
|
if self.set_alarm_mode:
|
|
|
|
self.set_alarm_mode = False
|
|
|
|
self.alarm_activated = False
|
|
|
|
else:
|
|
|
|
self.set_alarm_mode = True
|
|
|
|
self.alarm_activated = True
|
|
|
|
|
|
|
|
def on_touch_up(self, touch):
|
|
|
|
self.grabbed = ""
|
|
|
|
if self.set_alarm_mode and (self.grabbed == "hour" or self.grabbed == "minute"):
|
|
|
|
self.set_alarm_timeout_counter = 0
|
|
|
|
|
|
|
|
def on_touch_move(self, touch):
|
|
|
|
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":
|
|
|
|
self.alarm_modified = True
|
|
|
|
self.set_alarm_timeout_counter = 0
|
|
|
|
minute = round(-angle * 30 + 15)
|
|
|
|
if minute < 0:
|
|
|
|
minute += 60
|
|
|
|
if minute == 60:
|
|
|
|
minute = 59
|
|
|
|
|
|
|
|
# hour correction
|
|
|
|
hour = self.alarm_time.hour
|
|
|
|
if self.alarm_time.minute >= 55 and minute <= 5:
|
|
|
|
hour += 1
|
|
|
|
elif self.alarm_time.minute <= 5 and minute >= 55:
|
|
|
|
hour -= 1
|
|
|
|
|
|
|
|
if hour == 24:
|
|
|
|
hour = 0
|
|
|
|
elif hour == -1:
|
|
|
|
hour = 23
|
|
|
|
|
|
|
|
self.alarm_time = datetime.datetime(self.alarm_time.year, self.alarm_time.month, self.alarm_time.day, \
|
|
|
|
hour, minute, self.alarm_time.second, 0)
|
|
|
|
elif self.grabbed == "hour":
|
|
|
|
self.alarm_modified = True
|
|
|
|
self.set_alarm_timeout_counter = 0
|
|
|
|
hour = round(-angle * 6 + 3)
|
|
|
|
if hour < 0:
|
|
|
|
hour += 12
|
|
|
|
if hour == 12:
|
|
|
|
hour = 0
|
|
|
|
|
|
|
|
if self.alarm_time.hour >= 12:
|
|
|
|
hour += 12
|
|
|
|
|
|
|
|
# AM / PM correction
|
|
|
|
if self.alarm_time.hour == 11 and hour == 0:
|
|
|
|
hour = 12
|
|
|
|
elif self.alarm_time.hour == 23 and hour == 12:
|
|
|
|
hour = 0
|
|
|
|
elif self.alarm_time.hour == 0 and hour == 11:
|
|
|
|
hour = 23
|
|
|
|
elif self.alarm_time.hour == 12 and hour == 23:
|
|
|
|
hour = 11
|
|
|
|
|
|
|
|
# AM / PM boundary
|
|
|
|
self.alarm_time = datetime.datetime(self.alarm_time.year, self.alarm_time.month, self.alarm_time.day, \
|
|
|
|
hour, self.alarm_time.minute, self.alarm_time.second, 0)
|
|
|
|
|
|
|
|
def on_touch_down(self, touch):
|
|
|
|
global sound_process
|
|
|
|
|
|
|
|
time = self.alarm_time
|
|
|
|
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 = ""
|
|
|
|
|
|
|
|
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()
|
|
|
|
elif self.set_alarm_mode:
|
|
|
|
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):
|
|
|
|
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()
|
|
|
|
|