Initial commit

This commit is contained in:
Admar Schoonen 2022-12-11 16:08:13 +01:00
parent d258702b29
commit 1cf155f327
4 changed files with 349 additions and 0 deletions

Binary file not shown.

BIN
alarm_off.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

BIN
alarm_on.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

349
clock.py Normal file
View File

@ -0,0 +1,349 @@
# 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):
playsound(source)
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
seconds_to_next_alarm = False
led_color = [0, 0, 0]
# sound_source = "https://icecast.omroep.nl/radio1-bb-mp3"
sound_source = "Woodpecker Chirps - QuickSounds.com.mp3"
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'
r = 0.9
g = 0.0
b = 0.0
else:
source = 'alarm_off.png'
r = 1.0
g = 1.0
b = 1.0
set_alarm_button = self.ids["set_alarm_button"]
set_alarm_button.canvas.clear()
with set_alarm_button.canvas:
Color(r, g, b)
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
intensity = round((1.0 - self.seconds_to_next_alarm / (30.0 * 60.0)) * 256.0)
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
if self.seconds_to_next_alarm < 30 * 60:
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:
print("beep beep!")
sound_process = Process(target=play_sound, args=(self.sound_source,))
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:
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()