GoodMorningSunshine/clock.py

1766 lines
69 KiB
Python

# Many thanks to https://stackoverflow.com/questions/18923321/making-a-clock-in-kivy
import collections
import datetime
import math
import sys
import traceback
import copy
from os import uname, walk
from kivy.config import Config
from kivy.properties import ObjectProperty
import time
from sounds import WakeUpSounds
import json
from hsluv import hsluv_to_rgb, rgb_to_hsluv
import requests
import urllib.parse
from kivy.core.image import Image as CoreImage
from kivy.uix.image import Image as kiImage
from PIL import Image, ImageChops
from io import BytesIO
def is_arm():
if (uname()[4][:3] == 'arm') or (uname()[4][:7] == 'aarch64'):
return True
else:
return False
if is_arm():
# Do not forget to create udev rule:
# echo 'SUBSYSTEM=="backlight",RUN+="/bin/chmod 666 /sys/class/backlight/%k/brightness /sys/class/backlight/%k/bl_power"' | sudo tee -a /etc/udev/rules.d/backlight-permissions.rules
from rpi_backlight import Backlight
from rgbw_colorspace_converter.colors.converters import HSV, RGB
Config.set('graphics', 'width', '800')
Config.set('graphics', 'height', '480')
#Config.set('graphics', 'width', '750')
#Config.set('graphics', 'height', '450')
import board
import neopixel
pixel_pin = board.D10
num_pixels = 300
ORDER = neopixel.GRBW
pixels = neopixel.NeoPixel(
pixel_pin, num_pixels, brightness=0.2, auto_write=False, pixel_order=ORDER
)
pixels.fill((0, 0, 0, 0))
pixels.show()
backlight = Backlight()
else:
Config.set('graphics', 'width', '1200')
Config.set('graphics', 'height', '720')
pixels = None
backlight = None
Config.set('graphics', 'maxfps', '60')
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.slider import Slider
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.checkbox import CheckBox
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.graphics import Color, Line, Rectangle, Ellipse
from kivy.core.window import Window
if is_arm():
Window.show_cursor = False
import alsaaudio
import vlc
Builder.load_file('clock.kv')
Position = collections.namedtuple('Position', 'x y')
class Theme():
def __init__(self, x="Dark"):
print("reading themes/" + x + "/theme.json")
f = open("themes/" + x + "/theme.json")
j = json.load(f)
f.close()
self.name = x
self.color_background = j["color_background"]
self.color_shade = j["color_shade"]
self.color_clock_hands_hours = j["color_clock_hands_hours"]
self.color_clock_hands_minutes = j["color_clock_hands_minutes"]
self.color_clock_hands_seconds = j["color_clock_hands_seconds"]
self.color_alarm_hands_hours = j["color_alarm_hands_hours"]
self.color_alarm_hands_minutes = j["color_alarm_hands_minutes"]
self.color_alarm_hands_seconds = j["color_alarm_hands_seconds"]
self.color_font = j["color_font"]
self.color_numbers = j["color_numbers"]
self.color_button = j["color_button"]
self.color_funky = j["color_funky"]
self.icon_light_off = "themes/" + x + "/light_off.png"
self.icon_light_reading = "themes/" + x + "/light_reading.png"
self.icon_light_sunrise = "themes/" + x + "/light_sunrise.png"
self.icon_light_on = "themes/" + x + "/light_on.png"
self.icon_media_playing = "themes/" + x + "/media_playing.png"
self.icon_media_stopped = "themes/" + x + "/media_stopped.png"
self.icon_alarm_on = "themes/" + x + "/alarm_on.png"
self.icon_alarm_off = "themes/" + x + "/alarm_off.png"
self.icon_settings_visible = "themes/" + x + "/settings_visible.png"
self.icon_settings_not_visible = "themes/" + x + "/settings_not_visible.png"
class Touch():
def __init__(self, x=None):
if x is None:
self.pos = []
self.spos = []
self.is_empty = True
self.angle = 0
self.corrected = False
else:
self.pos = [i for i in x.pos]
self.spos = [i for i in x.spos]
self.is_empty = False
x = self.spos[0] - 0.5
y = self.spos[1] - 0.5
self.angle = math.atan2(y, x) / math.pi; # angle is between -1 and 1
self.corrected = False
def __repr__(self):
return ".is_empty: " + str(self.is_empty) + ", .pos: " + str(self.pos) + ", .spos: " + str(self.spos), ", .corrected: " + str(self.corrected)
def copy(self, x):
self = Touch(x)
def clear(self):
self = Touch()
class TouchEvent():
def __init__(self, x=None):
self.time = time.time()
self.touch = copy.copy(x)
self.processed = False
class ClockSettings():
def __init__(self):
f = open("settings.json")
j = json.load(f)
f.close()
self.theme_selected = j["theme"]
self.wake_up_volume = j["volume"]
self.sound_selected = j["wake_up_sound"]
self.wake_up_brightness = j["wake_up_brightness"]
self.reading_light_brightness = j["reading_light_brightness"]
self.display_brightness = j["display_brightness"]
self.alarm_activated = j["alarm_activated"]
try:
self.address = j["address"]
except:
pass
hour = j["alarm_time_hour"]
minute = j["alarm_time_minute"]
t = datetime.datetime.now()
self.alarm_time = datetime.datetime(t.year, \
t.month, t.day, hour, minute, 0, 0)
self.alarm_activated = False
self.alarm_playing = False
self.alarm_modified = False
self.seconds_to_sunrise = 30 * 60 # 30 minutes
def read(self):
ClockSettings()
def write(self):
j = {}
j["theme"] = self.theme_selected
j["volume"] = self.wake_up_volume
j["wake_up_sound"] = self.sound_selected
j["wake_up_brightness"] = self.wake_up_brightness
j["reading_light_brightness"] = self.reading_light_brightness
j["display_brightness"] = self.display_brightness
j["alarm_activated"] = self.alarm_activated
j["alarm_time_hour"] = self.alarm_time.hour
j["alarm_time_minute"] = self.alarm_time.minute
if hasattr(self, "address"):
j["address"] = self.address
with open("settings.json", "w") as f:
json.dump(j, f, indent=4)
print("settings saved")
class MyClockWidget(FloatLayout):
settings = ClockSettings()
if settings.theme_selected == "Automatic":
theme_name = "Light"
else:
theme_name = settings.theme_selected
theme = ObjectProperty(Theme(theme_name))
for x in ('', 'PCM'):
try:
mixer = alsaaudio.Mixer()
break
except:
pass
else:
raise OSError('could not open mixer ' + x)
grabbed = ""
face_numbers = []
# if not grabbed, set_alarm_timeout_counter is incremented at every update call;
# 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
light_state = "off" # "off", "reading", "sunrise" or "on"
intensity_target = 0
intensity_prev = 0
intensity_curr = 0
intensity_target_prev = None
rgbw_prev = None
volume_target = 0
volume_prev = 0
volume_curr = 0
volume_target_prev = None
# Ugly workaround for issue with Kivy and Raspberry Pi 3 + touch screen
# For each type of touch event, handling of the event is ignored until the last event is more than touch_delay_time seconds old
touch_delay_time = 0.1
touch_down_event_prev = None
touch_move_event_prev = None
touch_up_event_prev = None
touch_down_event_curr = None
touch_move_event_curr = None
touch_up_event_curr = None
# view can be one of the following strings:
# - "clock"
# - "set_alarm"
# - "settings_menu"
# - "settings_menu_wake_up_sound"
# - "settings_menu_theme"
view = "clock"
view_prev = ""
view_active_since = time.time()
# ugly workaround for issue where input is processed twice (specifically:
# pressing Ok in wake up sound view switches to settings view but also
# adjusts display brightness slider)
time_to_ignore_inputs_after_view_change = 0.5
# ugly workaround for issue where graphics are not drawn correctly after view change
time_to_force_redraws_after_view_change = 0.5
# we need a dirty hack to sensure that radio button is always in sync with selected setting
settings_menu_wake_up_sound_select_button_cb_hack = False
settings_menu_theme_select_button_cb_hack = False
# defer drawing to improve application speed: if list of draw calls for
# this frame is identical to list of previous frame then nothing needs to
# be drawed
draw_list_last_frame = []
draw_list_curr_frame = []
touch_prev = Touch()
light_button_move_init = []
vlc = vlc.Instance()
player = vlc.media_player_new()
is_arm = is_arm()
aqi_colors = [
[0.0, 0.0, 0.0], # x < 1.0 black (good)
[0.3, 0.3, 255.0/255.0], # 1.0 <= x < 2.0 blue (good)
[0.0, 160.0/255.0, 160.0/255.0], # 2.0 <= x < 3.0 cyan (good)
[1.0, 1.0, 1.0], # 3.0 <= x < 4.0 white (mediocre)
[1.0, 1.0, 150.0/255.0], # 4.0 <= < x 5.0 light yellow (mediocre)
[245.0/255.0, 245.0/255.0, 0.0], # 5.0 <= x < 6.0 yellow (mediocre)
[1.0, 175.0/255.0, 0.0], # 6.0 <= x < 7.0 orange (inadequate)
[1.0, 120.0/255.0, 0.0], # 7.0 <= x < 8.0 red orange (inadequate)
[200.0/255.0, 0.0, 0.0], # 8.0 <= x < 9.0 red (bad)
[200.0/255.0, 0.0, 200.0/255.0], # 9.0 <= x < 10.0 magenta (bad)
[100.0/255.0, 0.0, 200.0/255.0], # 10.0 <= x purple (terrible)
]
precipitation = []
precipitation_thresholds = [0.1, 0.22, 0.47, 1.0, 2.2, 4.7, 10, 22, 47, 100]
pollen = []
pollen_thresholds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
aqi = []
aqi_thresholds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
paqi = []
paqi_thresholds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
uvi = []
uvi_thresholds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
iaqi = []
iaqi_thresholds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
def get_air_quality_metric(self, j, t, metric, periodicity):
x = []
try:
for i in j[metric]:
if i["time"]>= t - periodicity:
x.append(i["value"])
except:
print("Couldn't find metric " + metric)
return x
def get_air_quality(self, *args):
if hasattr(self.settings, "address") == False:
print("No address specified")
return
url = "https://sinoptik.luon.net/forecast?address=" + urllib.parse.quote(self.settings.address, safe="") + "&metrics=precipitation&metrics=UVI&metrics=AQI&metrics=pollen&metrics=PAQI"
response = requests.get(url)
self.precipitation = []
self.pollen = []
self.aqi = []
self.paqi = []
self.uvi = []
self.iaqi = []
if response.status_code == 200:
t = datetime.datetime.now().timestamp()
j = json.loads(response.text)
self.precipitation = self.get_air_quality_metric(j, t, "precipitation", 300)
# self.precipitation = [0, 0.1, 0.22, 0.47, 1, 2.2, 4.7, 10, 22, 47, 100]
self.pollen = self.get_air_quality_metric(j, t, "pollen", 3600)
self.aqi = self.get_air_quality_metric(j, t, "AQI", 3600)
self.paqi = self.get_air_quality_metric(j, t, "PAQI", 3600)
self.uvi = self.get_air_quality_metric(j, t, "UVI", 3600 * 24)
else:
print("Error retrieving air quality; got response " + str(response))
def sync_air_quality(self, *args):
Clock.schedule_interval(self.get_air_quality, 60)
def update_background_automatic_sunrise(self):
background = self.ids["background"]
self.draw_list_curr_frame.append(["canvas.clear()", background])
with background.canvas:
color = self.intensity_to_rgb(self.intensity_curr)
self.draw_list_curr_frame.append(["Color", background.canvas, color[0], color[1], color[2]])
self.draw_list_curr_frame.append(["Rectangle", background.canvas, background.size, background.pos, None])
def update_background_automatic_no_sunrise(self):
background = self.ids["background"]
self.draw_list_curr_frame.append(["canvas.clear()", background])
with background.canvas:
color = self.theme.color_background
self.draw_list_curr_frame.append(["Color", background.canvas, color[0], color[1], color[2]])
self.draw_list_curr_frame.append(["Rectangle", background.canvas, background.size, background.pos, None])
def apply_theme(self):
if self.settings.theme_selected == "Automatic":
if (self.light_state != "sunrise") and (self.light_state != "on"):
# switch to light theme after 7:00 AM, to dark theme after 19:00
t = datetime.datetime.now()
h = t.hour + t.minute / 60
if (h >= 7) and (h < 19):
if self.theme.name != "Light":
self.theme = Theme("Light")
else:
if self.theme.name != "Dark":
self.theme = Theme("Dark")
self.update_background_automatic_no_sunrise()
else:
if self.intensity_curr >= 0.5:
if self.theme.name != "Light":
self.theme = Theme("Light")
else:
if self.theme.name != "Dark":
self.theme = Theme("Dark")
self.update_background_automatic_sunrise()
else:
if self.theme.name != self.settings.theme_selected:
self.theme = Theme(self.settings.theme_selected)
self.update_background_automatic_no_sunrise()
self.ids["settings_menu_wake_up_sound_label_top"].color = self.theme.color_font
self.settings_menu_wake_up_sound_Ok_button.color = self.theme.color_font
self.ids["settings_menu_theme_label_top"].color = self.theme.color_font
self.settings_menu_theme_Ok_button.color = self.theme.color_font
def find_themes(self):
themes = next(walk('themes/'))[1]
if ('Dark' in themes) and ('Light' in themes):
themes.append('Automatic')
print('Found the following themes: ' + str(themes))
return themes
def add_themes(self):
self.themes = self.find_themes()
x = self.ids["settings_menu_theme_boxlayout"]
gl = GridLayout(
cols=2,
)
self.ids["settings_menu_theme_select_button"].text = self.settings.theme_selected
self.theme_checkboxes = []
self.theme_labels = []
i = 0
for w in self.themes:
c = CheckBox(
group = "settings_menu_theme",
size = [gl.size[0] * 0.1, gl.size[1]],
)
c.bind(active=self.settings_menu_theme_cb)
gl.add_widget(c)
self.theme_checkboxes.append(c)
if i == 0:
a = True
else:
a = False
l = Label(
text = w,
halign = "left",
valign = "middle",
size = [Window.size[0] * 0.3, Window.size[1]],
text_size = Window.size,
font_size = Window.height*0.05,
color = self.theme.color_font,
)
l.text_size = l.size
gl.add_widget(l)
self.theme_labels.append(l)
i = i + 1
x.add_widget(gl)
b = Button(
text = "Ok",
font_size = self.height*0.3,
color = self.theme.color_font,
background_normal = '',
background_color = self.theme.color_button
)
b.bind(on_press = self.settings_menu_theme_Ok_button_cb)
x.add_widget(b)
self.settings_menu_theme_Ok_button = b
self.apply_theme()
def apply_settings(self):
self.ids["volume_slider"].value = self.settings.wake_up_volume
self.ids["settings_menu_wake_up_sound_select_button"].text = self.settings.sound_selected
self.ids["wake_up_brightness_slider"].value = self.settings.wake_up_brightness
self.ids["reading_light_brightness_slider"].value = self.settings.reading_light_brightness
self.ids["display_brightness_slider"].value = self.settings.display_brightness
def add_wake_up_sounds(self):
x = self.ids["settings_menu_wake_up_sound_boxlayout"]
gl = GridLayout(
cols=2,
)
self.wake_up_sounds = WakeUpSounds
self.settings.sound_source = self.wake_up_sounds[self.settings.sound_selected]
self.wake_up_sound_checkboxes = []
self.wake_up_sound_labels = []
i = 0
for w in self.wake_up_sounds:
c = CheckBox(
group = "settings_menu_wake_up_sound",
size = [gl.size[0] * 0.1, gl.size[1]],
)
c.bind(active=self.settings_menu_wake_up_sound_cb)
gl.add_widget(c)
self.wake_up_sound_checkboxes.append(c)
if i == 0:
a = True
else:
a = False
l = Label(
text = w,
halign = "left",
valign = "middle",
size = [Window.size[0] * 0.3, Window.size[1]],
text_size = Window.size,
font_size = Window.height*0.05,
color = self.theme.color_font,
)
l.text_size = l.size
gl.add_widget(l)
self.wake_up_sound_labels.append(l)
i = i + 1
x.add_widget(gl)
b = Button(
text = "Ok",
font_size = self.height*0.3,
color = self.theme.color_font,
background_normal = '',
background_color = self.theme.color_button
)
b.bind(on_press = self.settings_menu_wake_up_sound_Ok_button_cb)
x.add_widget(b)
self.settings_menu_wake_up_sound_Ok_button = b
def set_volume(self, x):
self.mixer.setvolume(int(x * 100))
self.volume_prev = self.volume_curr
self.volume_curr = x
def play_sound(self, source):
if (self.player.get_state() == vlc.State.NothingSpecial) or \
(self.player.get_state() == vlc.State.Paused) or \
(self.player.get_state() == vlc.State.Stopped) or \
(self.player.get_state() == vlc.State.Ended) or \
(self.player.get_state() == vlc.State.Error):
print("beep beep! " + source)
self.volume_target = self.settings.wake_up_volume / 20.0
media = self.vlc.media_new(source)
self.player.set_media(media)
self.player.play()
self.settings.alarm_playing = True
def stop_sound(self):
self.player.stop()
self.settings.alarm_playing = False
def set_backlight(self, x):
offset = 3
# 20 steps to from 1 to 100 - offset
c = (100 - offset)**(1/20)
y = c**x + offset
z = max(1, round(y))
if self.is_arm:
if backlight.brightness != z:
backlight.brightness = z
def hide_widget(self, widget, hide=True):
if hasattr(widget, 'saved_attrs'):
if not hide:
widget.height, widget.size_hint_y, widget.opacity, widget.disabled = widget.saved_attrs
del widget.saved_attrs
elif hide:
widget.saved_attrs = widget.height, widget.size_hint_y, widget.opacity, widget.disabled
widget.height, widget.size_hint_y, widget.opacity, widget.disabled = 0, None, 0, True
def is_widget_hidden(self, widget):
return hasattr(widget, 'saved_attrs')
def draw_numbers(self):
"""
Add number labels when added in widget hierarchy
"""
if self.view == "set_alarm":
t = self.settings.alarm_time
else:
t = datetime.datetime.now()
for i in range(1, 13):
if t.hour < 12:
offset = 0
else:
offset = 12
self.face_numbers.append(Label(
text=str(i + offset),
font_size=float(Config.get('graphics', 'height'))*0.05,
color=self.theme.color_numbers,
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 draw_colored_circle(self, reference, x, y, r, color):
if color != [0.0, 0.0, 0.0]:
if color == [1, 1, 1]:
# Draw black circle
p = [x - r, y - r]
self.draw_list_curr_frame.append(["Color", reference.canvas, 0, 0, 0])
self.draw_list_curr_frame.append(["Ellipse", reference.canvas, [2 * r, 2 * r], p])
# Draw slightly smaller white circle
p = [x - r * 0.95, y - r * 0.95]
self.draw_list_curr_frame.append(["Color", reference.canvas, color[0], color[1], color[2]])
self.draw_list_curr_frame.append(["Ellipse", reference.canvas, [2 * r * 0.95, 2 * r * 0.95], p])
else:
# Draw slightly smaller colored circle
p = [x - r, y - r]
self.draw_list_curr_frame.append(["Color", reference.canvas, color[0], color[1], color[2]])
self.draw_list_curr_frame.append(["Ellipse", reference.canvas, [2 * r, 2 * r], p])
def calculate_colors(self, x, x_thresholds):
colors = []
for i in range(0, len(x)):
idx = 0
for l in x_thresholds:
if l > x[i]:
break
idx = idx + 1
if idx >= len(self.aqi_colors):
idx = len(self.aqi_colors) - 1
colors.append(self.aqi_colors[idx])
return colors
def draw_precipitation_paqi_expectation(self, x, x_thresholds, location):
if len(x) == 0:
return
face_plate = self.ids["face_plate"]
with face_plate.canvas:
N = min(12, len(x))
colors = self.calculate_colors(x[:N], x_thresholds)
for i in range(0, N):
color = colors[i]
R = face_plate.size[0] / 2
r = R / 10
tmp_x = 0.5 + location*math.sin(2 * math.pi * i/12)
tmp_y = 0.5 + location*math.cos(2 * math.pi * i/12)
coord_x = face_plate.pos[0] + 2 * R * tmp_x
coord_y = face_plate.pos[1] + 2 * R * tmp_y
self.draw_colored_circle(face_plate, coord_x, coord_y, r, color)
def draw_precipitation_expectation(self):
self.draw_precipitation_paqi_expectation(self.precipitation, self.precipitation_thresholds, 0.45)
def draw_paqi_expectation(self):
self.draw_precipitation_paqi_expectation(self.paqi, self.paqi_thresholds, 0.32)
def paint_icon(self, color, img_source, label):
c = (int(256 * color[0]), int(256 * color[1]), int(256 * color[2]), int(256))
tmp = Image.new('RGBA', img_source.size, color = c)
img_modified = ImageChops.multiply(tmp, img_source)
data = BytesIO()
img_modified.save(data, format='png')
data.seek(0)
im = CoreImage(BytesIO(data.read()), ext='png')
icon = self.ids[label]
icon.texture = im.texture
def draw_icon_helper(self, x, x_thresholds, x_label, L, color_prev, img):
N = min(L, len(x))
colors = self.calculate_colors(x[:N], x_thresholds)
color = colors[x.index(max(x[:N]))]
if color_prev != color:
# Color has changed
if color == [1, 1, 1]:
img = Image.open("icons/" + x_label + "_outline.png")
else:
if color_prev == [1, 1, 1]:
img = Image.open("icons/" + x_label + ".png")
if self.is_arm:
img = img.resize((int(img.width * 0.67), int(img.height * 0.67)))
self.paint_icon(color, img, "icon_" + x_label)
return color
def draw_icon_iaqi(self):
if hasattr(self, 'image_iaqi') == False:
self.image_iaqi = Image.open("icons/iaqi.png")
if len(self.iaqi) == 0:
if self.is_widget_hidden(self.ids["icon_iaqi"]) == False:
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["icon_iaqi"], True])
return
if self.is_widget_hidden(self.ids["icon_iaqi"]):
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["icon_iaqi"], False])
if hasattr(self, 'iaqi_color_prev') == False:
self.iaqi_color_prev = []
if hasattr(self, 'iaqi_color') == False:
self.iaqi_color = []
color = self.draw_icon_helper(self.iaqi, self.iaqi_thresholds, "iaqi", 1, self.iaqi_color_prev, self.image_iaqi)
self.iaqi_color_prev = self.iaqi_color
self.iaqi_color = color
def draw_icon_uvi(self):
if hasattr(self, 'image_uvi') == False:
self.image_uvi = Image.open("icons/uvi.png")
if len(self.uvi) == 0:
if self.is_widget_hidden(self.ids["icon_uvi"]) == False:
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["icon_uvi"], True])
return
if self.is_widget_hidden(self.ids["icon_uvi"]):
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["icon_uvi"], False])
if hasattr(self, 'uvi_color_prev') == False:
self.uvi_color_prev = []
if hasattr(self, 'uvi_color') == False:
self.uvi_color = []
color = self.draw_icon_helper(self.uvi, self.uvi_thresholds, "uvi", 1, self.uvi_color_prev, self.image_uvi)
self.uvi_color_prev = self.uvi_color
self.uvi_color = color
def draw_icon_pollen(self):
if hasattr(self, 'image_pollen') == False:
self.image_pollen = Image.open("icons/pollen.png")
if len(self.pollen) == 0:
if self.is_widget_hidden(self.ids["icon_pollen"]) == False:
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["icon_pollen"], True])
return
if self.is_widget_hidden(self.ids["icon_pollen"]):
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["icon_pollen"], False])
if hasattr(self, 'pollen_color_prev') == False:
self.pollen_color_prev = []
if hasattr(self, 'pollen_color') == False:
self.pollen_color = []
color = self.draw_icon_helper(self.pollen, self.pollen_thresholds, "pollen", 12, self.pollen_color_prev, self.image_pollen)
self.pollen_color_prev = self.pollen_color
self.pollen_color = color
def draw_icon_aqi(self):
if hasattr(self, 'image_aqi') == False:
self.image_aqi = Image.open("icons/aqi.png")
if len(self.aqi) == 0:
if self.is_widget_hidden(self.ids["icon_aqi"]) == False:
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["icon_aqi"], True])
return
if self.is_widget_hidden(self.ids["icon_aqi"]):
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["icon_aqi"], False])
if hasattr(self, 'aqi_color_prev') == False:
self.aqi_color_prev = []
if hasattr(self, 'aqi_color') == False:
self.aqi_color = []
color = self.draw_icon_helper(self.aqi, self.aqi_thresholds, "aqi", 12, self.aqi_color_prev, self.image_aqi)
self.aqi_color_prev = self.aqi_color
self.aqi_color = color
def draw_icons(self):
self.draw_icon_uvi()
self.draw_icon_pollen()
self.draw_icon_aqi()
self.draw_icon_iaqi()
def draw_face(self):
self.draw_numbers()
def update_theme(self):
if self.settings.theme_selected == "Automatic":
self.apply_theme()
def update_face(self):
face_plate = self.ids["face_plate"]
self.draw_list_curr_frame.append(["canvas.clear()", face_plate])
if self.view == "set_alarm":
t = self.settings.alarm_time
else:
t = datetime.datetime.now()
if (self.view == "clock") and (self.precipitation != []) and (self.paqi != []):
for i in range(0, 12):
self.face_numbers[i].text = ""
self.draw_precipitation_expectation()
self.draw_paqi_expectation()
self.draw_icons()
else:
with face_plate.canvas:
color = self.theme.color_numbers
self.draw_list_curr_frame.append(["Color", face_plate.canvas, color[0], color[1], color[2]])
r = face_plate.size[0] / 2
p = [face_plate.pos[0] + r, face_plate.pos[1] + r]
self.draw_list_curr_frame.append(["Circle", face_plate.canvas, p, r])
for i in range(0, 12):
if t.hour < 12:
offset = 0
else:
offset = 12
self.face_numbers[i].color = self.theme.color_numbers
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 + round(length * math.sin(2 * math.pi * fraction)),
center_y + round(length * math.cos(2 * math.pi * fraction)),
)
def update_light_button(self):
if (self.light_state == "off"):
source = self.theme.icon_light_off
rgb = [1.0, 1.0, 1.0]
elif (self.light_state == "reading"):
source = self.theme.icon_light_reading
rgb = [1.0, 1.0, 1.0]
elif (self.light_state == "sunrise"):
source = self.theme.icon_light_sunrise
rgb = [1.0, 1.0, 1.0]
elif (self.light_state == "on"):
source = self.theme.icon_light_on
rgb = [1.0, 1.0, 1.0]
light_button = self.ids["light_button"]
self.draw_list_curr_frame.append(["canvas.clear()", light_button])
with light_button.canvas:
self.draw_list_curr_frame.append(["Color", light_button.canvas, rgb[0], rgb[1], rgb[2]])
self.draw_list_curr_frame.append(["Rectangle", light_button.canvas, light_button.size, light_button.pos, source])
def update_play_button(self):
if self.settings.alarm_playing:
source = self.theme.icon_media_playing
rgb = [1.0, 1.0, 1.0]
else:
source = self.theme.icon_media_stopped
rgb = [1.0, 1.0, 1.0]
play_button = self.ids["play_button"]
self.draw_list_curr_frame.append(["canvas.clear()", play_button])
with play_button.canvas:
self.draw_list_curr_frame.append(["Color", play_button.canvas, rgb[0], rgb[1], rgb[2]])
self.draw_list_curr_frame.append(["Rectangle", play_button.canvas, play_button.size, play_button.pos, source])
def update_set_alarm_button(self):
if (self.view == "set_alarm") or self.settings.alarm_activated:
source = self.theme.icon_alarm_on
rgb = [1.0, 1.0, 1.0]
else:
source = self.theme.icon_alarm_off
rgb = [1.0, 1.0, 1.0]
set_alarm_button = self.ids["set_alarm_button"]
self.draw_list_curr_frame.append(["canvas.clear()", set_alarm_button])
with set_alarm_button.canvas:
self.draw_list_curr_frame.append(["Color", set_alarm_button.canvas, rgb[0], rgb[1], rgb[2]])
self.draw_list_curr_frame.append(["Rectangle", set_alarm_button.canvas, set_alarm_button.size, set_alarm_button.pos, source])
def update_settings_button(self):
if (self.view.startswith("settings_menu")):
source = self.theme.icon_settings_visible
rgb = [1.0, 1.0, 1.0]
else:
source = self.theme.icon_settings_not_visible
rgb = [1.0, 1.0, 1.0]
settings_button = self.ids["settings_button"]
self.draw_list_curr_frame.append(["canvas.clear()", settings_button])
with settings_button.canvas:
self.draw_list_curr_frame.append(["Color", settings_button.canvas, rgb[0], rgb[1], rgb[2]])
self.draw_list_curr_frame.append(["Rectangle", settings_button.canvas, settings_button.size, settings_button.pos, source])
def intensity_to_rgb(self, intensity):
if intensity < 0:
intensity = 0
elif intensity > 1:
intensity = 1
# sunlight intensity and color temperature vary with sine function
intensity = math.sin(2 * math.pi * intensity / 4)
# from https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html
# usable between 1000 and 40000 K
# we only want color temperatures between 2000 and 6500 K
CCT = 2000 + (6500 - 2000) * intensity
if CCT < 1000:
CCT = 1000
if CCT > 40000:
CCT = 40000
Temperature = CCT / 100
if Temperature <= 66:
Red = 255
else:
Red = Temperature - 60
Red = 329.698727446 * pow(Red, -0.1332047592)
if Red < 0:
Red = 0
if Red > 255:
Red = 255
if Temperature <= 66:
Green = Temperature
Green = 99.4708025861 * math.log(Green) - 161.1195681661
if Green < 0:
Green = 0
if Green > 255:
Green = 255
else:
Green = Temperature - 60
Green = 288.1221695283 * pow(Green, -0.0755148492)
if Green < 0:
Green = 0
if Green > 255:
Green = 255
if Temperature >= 66:
Blue = 255
else:
if Temperature <= 19:
Blue = 0
else:
Blue = Temperature - 10
Blue = 138.5177312231 * math.log(Blue) - 305.0447927307
if Blue < 0:
Blue = 0
if Blue > 255:
Blue = 255
rgb_cct = [Red / 255, Green / 255, Blue / 255]
# adjust intensity
[h, s, l]= rgb_to_hsluv(rgb_cct)
l = l * intensity
rgb = hsluv_to_rgb([h, s, l])
return rgb
def intensity_to_rgbw(self, intensity):
rgb = self.intensity_to_rgb(intensity)
led_color = RGB(rgb[0] * 255, rgb[1] * 255, rgb[2] * 255)
return led_color
def update_backlight(self):
# make sure brightness of display is never lower than intensity of leds
brightness_min = 20 * self.intensity_curr
if self.settings.display_brightness < brightness_min:
self.set_backlight(brightness_min)
else:
self.set_backlight(self.settings.display_brightness)
def set_leds(self):
if self.intensity_prev != self.intensity_curr:
self.intensity_prev = self.intensity_curr
if self.is_arm:
led_color = self.intensity_to_rgbw(self.intensity_curr)
if self.rgbw_prev != led_color.rgbw:
self.rgbw_prev = led_color.rgbw
# print(self.light_state + ", t: " + str(self.seconds_to_next_alarm) + ", i: " + str(self.intensity_curr) + ", rgbw: " + str(led_color.rgbw))
pixels.fill(led_color.rgbw)
pixels.show()
else:
# On non-arm rgbw_colorspace_converter and hsluv are not available; fake rgbw by setting w component to scaled value of intensity_curr
rgbw = [0, 0, 0, round(self.intensity_curr * 255)]
if self.rgbw_prev != rgbw:
self.rgbw_prev = rgbw
# print(self.light_state + ", t: " + str(self.seconds_to_next_alarm) + ", i: " + str(self.intensity_curr) + ", rgbw: " + str(rgbw))
def sunrise(self):
if self.view == "set_alarm":
# Do not simulate sunrise when adjusting alarm time
return
if self.seconds_to_next_alarm < 0.1:
intensity_target = (self.settings.wake_up_brightness / 20.0)
new_state = "on"
else:
intensity_target = (1.0 - self.seconds_to_next_alarm / self.settings.seconds_to_sunrise) * (self.settings.wake_up_brightness / 20.0)
new_state = "sunrise"
# only adjust intensity_target if new intensity_target is higher than current intesity, to avoid dimming light when reading mode is on
if self.intensity_target < intensity_target:
self.intensity_target = intensity_target
self.light_state = new_state
def process_led_state(self):
if (self.settings.alarm_activated) and (self.seconds_to_next_alarm < self.settings.seconds_to_sunrise):
self.sunrise()
else:
if (self.light_state == "off"):
self.intensity_target = 0
elif (self.light_state == "reading"):
self.intensity_target = self.settings.reading_light_brightness / 20.0
elif (self.light_state == "on"):
self.intensity_target = self.settings.wake_up_brightness / 20.0
weight = 0.05
intensity_next = weight * self.intensity_target + (1 - weight) * self.intensity_curr
step = intensity_next - self.intensity_curr
if abs(step) < 0.001:
step = self.intensity_target - self.intensity_curr
self.intensity_curr = self.intensity_curr + step
def process_volume_state(self):
if (self.settings.alarm_playing) and (self.player.get_state() == vlc.State.Playing):
step = 0.005
if (self.volume_curr <= self.volume_target):
if (self.volume_curr + step > self.volume_target):
step = self.volume_target - self.volume_curr
else:
step = -step
if (self.volume_curr + step < self.volume_target):
step = self.volume_target - self.volume_curr
if step != 0:
volume_next = self.volume_curr + step
self.set_volume(volume_next)
if volume_next == 0:
self.stop_sound()
def check_play_sound(self):
if self.settings.alarm_activated == False:
return
if (self.seconds_to_next_alarm < 0.1) or (self.settings.alarm_playing == True):
if self.settings.sound_source != "":
self.play_sound(self.settings.sound_source)
def calc_seconds_to_next_alarm(self):
if self.settings.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.settings.alarm_time - now
self.settings.alarm_time -= datetime.timedelta(days=d.days)
# Calculate number of seconds until next alarm
d = self.settings.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.process_led_state()
self.set_leds()
self.update_backlight()
self.check_play_sound()
self.process_volume_state()
def update_clock(self):
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["face_plate"], False])
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["face"], False])
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["hands"], False])
if self.view == "clock":
if len(self.uvi) > 0:
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["icon_uvi"], False])
if len(self.pollen) > 0:
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["icon_pollen"], False])
if len(self.aqi) > 0:
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["icon_aqi"], False])
if self.view == "set_alarm":
t = self.settings.alarm_time
else:
t = datetime.datetime.now()
hands = self.ids["hands"]
seconds_hand = self.position_on_clock(t.second/60, length=0.45*hands.size[0])
minutes_hand = self.position_on_clock(t.minute/60+t.second/3600, length=0.38*hands.size[0])
hours_hand = self.position_on_clock(t.hour/12 + t.minute/720, length=0.32*hands.size[0])
self.draw_list_curr_frame.append(["canvas.clear()", hands])
update_rate = App.get_running_app().update_rate
with hands.canvas:
if self.view == "set_alarm":
if self.grabbed != "" or self.set_alarm_timeout_counter < 1 * update_rate or \
self.set_alarm_timeout_counter % update_rate <= update_rate / 2 or self.settings.alarm_modified == False:
self.draw_list_curr_frame.append(["Color", hands.canvas, self.theme.color_alarm_hands_hours[0], self.theme.color_alarm_hands_hours[1], self.theme.color_alarm_hands_hours[2]])
self.draw_list_curr_frame.append(["Line", hands.canvas, [hands.center_x, hands.center_y, hours_hand.x, hours_hand.y], 3, "round"])
self.draw_list_curr_frame.append(["Color", hands.canvas, self.theme.color_alarm_hands_minutes[0], self.theme.color_alarm_hands_minutes[1], self.theme.color_alarm_hands_minutes[2]])
self.draw_list_curr_frame.append(["Line", hands.canvas, [hands.center_x, hands.center_y, minutes_hand.x, minutes_hand.y], 2, "round"])
if self.grabbed == "":
self.set_alarm_timeout_counter += 1
if self.set_alarm_timeout_counter >= 4.5 * update_rate:
self.view = "clock"
self.settings.write()
self.set_alarm_timeout_counter = 0
self.view_active_since = time.time()
if self.settings.alarm_modified:
self.settings.alarm_activated = True
self.settings.alarm_modified = False
else:
self.draw_list_curr_frame.append(["Color", hands.canvas, self.theme.color_clock_hands_hours[0], self.theme.color_clock_hands_hours[1], self.theme.color_clock_hands_hours[2]])
self.draw_list_curr_frame.append(["Line", hands.canvas, [hands.center_x, hands.center_y, hours_hand.x, hours_hand.y], 3, "round"])
self.draw_list_curr_frame.append(["Color", hands.canvas, self.theme.color_clock_hands_minutes[0], self.theme.color_clock_hands_minutes[1], self.theme.color_clock_hands_minutes[2]])
self.draw_list_curr_frame.append(["Line", hands.canvas, [hands.center_x, hands.center_y, minutes_hand.x, minutes_hand.y], 2, "round"])
self.draw_list_curr_frame.append(["Color", hands.canvas, self.theme.color_clock_hands_seconds[0], self.theme.color_clock_hands_seconds[1], self.theme.color_clock_hands_seconds[2]])
self.draw_list_curr_frame.append(["Line", hands.canvas, [hands.center_x, hands.center_y, seconds_hand.x, seconds_hand.y], 1, "round"])
self.update_face()
def update_settings_menu(self):
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["settings_menu_background"], False])
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["settings_menu"], False])
background = self.ids["settings_menu_background"]
self.draw_list_curr_frame.append(["canvas.clear()", background])
with background.canvas:
rgb = self.theme.color_shade
self.draw_list_curr_frame.append(["Color", background.canvas, rgb[0], rgb[1], rgb[2]])
self.draw_list_curr_frame.append(["Rectangle", background.canvas, background.size, background.pos, ""])
for s in ["label_0", "label_1", "label_2", "label_3", "label_4", "label_5", "theme_select_button", "wake_up_sound_select_button"]:
i = self.ids["settings_menu_" + s]
i.color = self.theme.color_font
i = self.ids["settings_menu_theme_select_button"]
i.background_color = self.theme.color_button
i = self.ids["settings_menu_wake_up_sound_select_button"]
i.background_color = self.theme.color_button
def update_settings_menu_wake_up_sound(self):
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["settings_menu_wake_up_sound_background"], False])
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["settings_menu_wake_up_sound"], False])
background = self.ids["settings_menu_wake_up_sound_background"]
self.draw_list_curr_frame.append(["canvas.clear()", background])
with background.canvas:
rgb = self.theme.color_shade
self.draw_list_curr_frame.append(["Color", background.canvas, rgb[0], rgb[1], rgb[2]])
self.draw_list_curr_frame.append(["Rectangle", background.canvas, background.size, background.pos, ""])
for i in self.wake_up_sound_labels:
i.color = self.theme.color_font
self.settings_menu_wake_up_sound_Ok_button.background_color = self.theme.color_button
def update_settings_menu_theme(self):
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["settings_menu_theme_background"], False])
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["settings_menu_theme"], False])
background = self.ids["settings_menu_theme_background"]
self.draw_list_curr_frame.append(["canvas.clear()", background])
with background.canvas:
rgb = self.theme.color_shade
self.draw_list_curr_frame.append(["Color", background.canvas, rgb[0], rgb[1], rgb[2]])
self.draw_list_curr_frame.append(["Rectangle", background.canvas, background.size, background.pos, ""])
for i in self.theme_labels:
i.color = self.theme.color_font
self.settings_menu_theme_Ok_button.background_color = self.theme.color_button
def update_settings(self):
if self.view == "settings_menu":
self.update_settings_menu()
elif self.view == "settings_menu_wake_up_sound":
self.update_settings_menu_wake_up_sound()
elif self.view == "settings_menu_theme":
self.update_settings_menu_theme()
def draw_display(self):
t = time.time()
if (self.view != self.view_prev) or (t - self.view_active_since < self.time_to_force_redraws_after_view_change):
self.draw_list_last_frame = [] # force redraw when view has changed
self.view_prev = self.view
if self.draw_list_curr_frame != self.draw_list_last_frame:
for i in self.draw_list_curr_frame:
if i[0] == "self.hide_widget":
self.hide_widget(i[1], i[2])
elif i[0] == "canvas.clear()":
i[1].canvas.clear()
elif i[0] == "Color":
with i[1]:
if len(i) == 5:
Color(i[2], i[3], i[4])
else:
Color(i[2], i[3], i[4], i[5])
elif i[0] == "Rectangle":
with i[1]:
Rectangle(size=i[2], pos=i[3], source=i[4])
elif i[0] == "Ellipse":
with i[1]:
Ellipse(size=i[2], pos=i[3])
elif i[0] == "Circle":
with i[1]:
Line(circle=(i[2][0], i[2][1], i[3]), width=4)
elif i[0] == "Line":
with i[1]:
Line(points=i[2], width=i[3], cap=i[4])
else:
print("Unknown draw command: " + i[0])
self.draw_list_last_frame = self.draw_list_curr_frame
self.draw_list_curr_frame = []
def process_touch_events(self):
t = time.time()
if (self.touch_down_event_curr is not None) and (self.touch_down_event_curr.processed == False) and \
(t - self.touch_down_event_curr.time > self.touch_delay_time):
self.touch_down_function(self.touch_down_event_curr.touch)
self.touch_down_event_curr.processed = True
self.touch_down_event_prev = copy.copy(self.touch_down_event_curr)
self.touch_down_event_curr = None
if (self.touch_move_event_curr is not None) and (self.touch_move_event_curr.processed == False) and \
(t - self.touch_move_event_curr.time > self.touch_delay_time):
self.touch_move_function(self.touch_move_event_curr.touch)
self.touch_move_event_curr.processed = True
self.touch_move_event_prev = copy.copy(self.touch_move_event_curr)
self.touch_move_event_curr = None
if (self.touch_up_event_curr is not None) and (self.touch_up_event_curr.processed == False) and \
(t - self.touch_up_event_curr.time > self.touch_delay_time):
self.touch_up_function(self.touch_up_event_curr.touch)
self.touch_up_event_curr.processed = True
self.touch_up_event_prev = copy.copy(self.touch_up_event_curr)
self.touch_up_event_curr = None
def update_display(self, *args):
self.process_touch_events()
self.check_alarm()
# Hide all dynamic widgets; will be enabled when updating respecive view
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["face_plate"], True])
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["face"], True])
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["hands"], True])
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["settings_menu_background"], True])
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["settings_menu"], True])
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["settings_menu_wake_up_sound_background"], True])
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["settings_menu_wake_up_sound"], True])
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["settings_menu_theme_background"], True])
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["settings_menu_theme"], True])
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["icon_uvi"], True])
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["icon_pollen"], True])
self.draw_list_curr_frame.append(["self.hide_widget", self.ids["icon_aqi"], True])
self.update_theme()
self.update_light_button()
self.update_play_button()
self.update_set_alarm_button()
self.update_settings_button()
if self.view == "clock" or self.view == "set_alarm":
self.update_clock()
elif self.view.startswith("settings_menu"):
self.update_settings()
else:
print("unknown view: " + self.view)
self.draw_display()
def settings_menu_wake_up_sound_select_button_cb(self):
self.settings_menu_wake_up_sound_select_button_cb_hack = True
for c in self.wake_up_sound_checkboxes:
c.active = False
print("sound selected: " + self.settings.sound_selected)
n = 0
for i in self.wake_up_sounds:
if i == self.settings.sound_selected:
self.wake_up_sound_checkboxes[n].active = True
n = n + 1
self.view = "settings_menu_wake_up_sound"
self.view_active_since = time.time()
def settings_menu_wake_up_sound_Ok_button_cb(self, event):
self.ids["settings_menu_wake_up_sound_select_button"].text = self.settings.sound_selected
self.view = "settings_menu"
self.view_active_since = time.time()
def settings_menu_wake_up_sound_cb(self, instance, value):
n = 0
for c in self.wake_up_sound_checkboxes:
if c == instance:
break
n = n + 1
k = 0
for sound in self.wake_up_sounds:
if k == n:
break
k = k + 1
if self.settings_menu_wake_up_sound_select_button_cb_hack:
self.settings_menu_wake_up_sound_select_button_cb_hack = False
if not (self.settings.sound_selected == "" and sound != ""):
return
if value == True:
print("You selected " + sound)
else:
print("You deselected " + sound)
self.settings.sound_source = self.wake_up_sounds[sound]
self.settings.sound_selected = sound
def settings_menu_theme_select_button_cb(self):
self.settings_menu_theme_select_button_cb_hack = True
for c in self.theme_checkboxes:
c.active = False
print("theme selected: " + self.settings.theme_selected)
n = 0
print("self.themes: " + str(self.themes))
for i in self.themes:
if i == self.settings.theme_selected:
self.theme_checkboxes[n].active = True
n = n + 1
self.view = "settings_menu_theme"
self.view_active_since = time.time()
def settings_menu_theme_Ok_button_cb(self, event):
self.ids["settings_menu_theme_select_button"].text = self.settings.theme_selected
self.view = "settings_menu"
self.view_active_since = time.time()
def settings_menu_theme_cb(self, instance, value):
n = 0
for c in self.theme_checkboxes:
if c == instance:
break
n = n + 1
k = 0
for theme in self.themes:
if k == n:
break
k = k + 1
if self.settings_menu_theme_select_button_cb_hack:
self.settings_menu_theme_select_button_cb_hack = False
if not (self.settings.theme_selected == "" and theme != ""):
return
if value == True:
print("You selected " + theme)
else:
print("You deselected " + theme)
self.settings.theme_selected = theme
self.apply_theme()
self.settings.write()
def volume_slider_value(self, *args):
self.settings.wake_up_volume = int(args[1])
old_vol = self.mixer.getvolume()
if (self.settings.alarm_playing) and (self.player.get_state() == vlc.State.Playing):
# immediately set volume when audio is playing
self.volume_target = self.settings.wake_up_volume / 20.0
self.set_volume(self.volume_target)
new_vol = self.mixer.getvolume()
def wake_up_brightness_slider_value(self, *args):
self.settings.wake_up_brightness = int(args[1])
print("Wake up brightness changed to " + str(self.settings.wake_up_brightness))
def reading_light_brightness_slider_value(self, *args):
self.settings.reading_light_brightness = int(args[1])
if (self.light_state == "reading"):
self.intensity_target = self.settings.reading_light_brightness / 20.0
print("Reading light brightness changed to " + str(self.settings.reading_light_brightness))
def display_brightness_slider_value(self, *args):
self.settings.display_brightness = int(args[1])
def on_light_button_pressed(self):
print("light button pressed from view " + self.view)
if self.light_state == "off":
self.light_state = "reading"
self.intensity_target = self.settings.reading_light_brightness / 20.0
elif self.light_state == "reading":
self.light_state = "off"
self.intensity_target = 0
if self.settings.alarm_playing:
self.volume_target = 0
elif self.light_state == "sunrise":
# allow enabling reading mode when sunrise has not yet reached that level
if self.intensity_target < self.settings.reading_light_brightness / 20.0:
self.light_state = "reading"
self.intensity_target = self.settings.reading_light_brightness / 20.0
else:
self.light_state = "off"
elif self.light_state == "on":
self.light_state = "reading"
self.intensity_target = 0
def on_play_button_pressed(self):
print("play button pressed from view " + self.view)
if self.settings.alarm_playing:
self.volume_target = 0
else:
self.play_sound(self.settings.sound_source)
def on_alarm_button_pressed(self):
print("alarm button pressed from view " + self.view)
self.settings.alarm_modified = False
self.set_alarm_timeout_counter = 0
if self.view == "set_alarm":
self.view = "clock"
self.view_active_since = time.time()
self.settings.alarm_activated = False
self.settings.write()
else:
if (self.view.startswith("settings_menu")):
self.settings.write()
self.view = "set_alarm"
self.view_active_since = time.time()
self.settings.alarm_activated = True
def on_settings_button_pressed(self):
print("settings button pressed from view " + self.view)
if self.view != "settings_menu":
self.view = "settings_menu"
self.view_active_since = time.time()
else:
self.view = "clock"
self.view_active_since = time.time()
self.settings.write()
print("view updated to " + self.view)
def touch_up_function(self, touch):
self.grabbed = ""
self.light_button_move_init = []
if (self.view == "set_alarm") and (self.grabbed == "hour" or self.grabbed == "minute"):
self.set_alarm_timeout_counter = 0
super(MyClockWidget, self).on_touch_up(touch)
def touch_move_function(self, touch):
if self.grabbed == "":
return
self.alarm_set_timeout = 0
touch_curr = Touch(touch)
# Ugly workaround for issue with Kivy and Raspberry Pi 3 + touch screen
tol = 0.5 # 0.5 is equal to 90 degrees
inc = 1 # 1 is equal to 180 degrees
if (self.touch_prev.is_empty == False) and (touch_curr.angle - self.touch_prev.angle >= tol):
touch_curr.spos[0] = 1 - touch_curr.spos[0]
touch_curr.spos[1] = 1 - touch_curr.spos[1]
touch_curr.corrected = True
while touch_curr.angle - self.touch_prev.angle >= tol:
touch_curr.angle -= inc
elif (self.touch_prev.is_empty == False) and (touch_curr.angle - self.touch_prev.angle <= -tol):
touch_curr.spos[0] = 1 - touch_curr.spos[0]
touch_curr.spos[1] = 1 - touch_curr.spos[1]
touch_curr.corrected = True
while touch_curr.angle - self.touch_prev.angle <= -tol:
touch_curr.angle += inc
self.touch_prev = copy.deepcopy(touch_curr)
if self.grabbed == "minute":
self.settings.alarm_modified = True
self.set_alarm_timeout_counter = 0
minute = round(-touch_curr.angle * 30 + 15)
while minute < 0:
minute += 60
while minute >= 60:
minute -= 60
# Sometimes the hand is 30 minutes ahead / behind the place where the user touches the screen --> correct for this behavior
if (((minute - self.settings.alarm_time.minute) >= 15) and ((minute - self.settings.alarm_time.minute) <= 45)):
minute = minute - 30
elif (((minute - self.settings.alarm_time.minute) <= -15) and ((minute - self.settings.alarm_time.minute) >= -45)):
minute = minute + 30
while minute < 0:
minute += 60
while minute >= 60:
minute -= 60
# hour correction
hour = self.settings.alarm_time.hour
if self.settings.alarm_time.minute >= 55 and minute <= 5:
hour += 1
elif self.settings.alarm_time.minute <= 5 and minute >= 55:
hour -= 1
while hour < 0:
hour += 24
while hour >= 24:
hour -= 24
self.settings.alarm_time = datetime.datetime(self.settings.alarm_time.year, \
self.settings.alarm_time.month, self.settings.alarm_time.day, \
hour, minute, self.settings.alarm_time.second, 0)
elif self.grabbed == "hour":
self.settings.alarm_modified = True
self.set_alarm_timeout_counter = 0
hour = round(-touch_curr.angle * 6 + 3)
while hour < 0:
hour += 12
while hour >= 12:
hour -= 12
# Sometimes the hand is 6 hours ahead / behind the place where the user touches the screen --> correct for this behavior
if (((hour - self.settings.alarm_time.hour) >= 3) and ((hour - self.settings.alarm_time.hour) <= 9)):
hour = hour - 6
if (((hour - self.settings.alarm_time.hour) <= -3) and ((hour - self.settings.alarm_time.hour) >= -9)):
hour = hour + 6
while hour < 0:
hour += 12
while hour >= 12:
hour -= 12
if self.settings.alarm_time.hour >= 12:
hour += 12
# AM / PM correction
if self.settings.alarm_time.hour == 11 and hour == 0:
hour = 12
elif self.settings.alarm_time.hour == 23 and hour == 12:
hour = 0
elif self.settings.alarm_time.hour == 0 and hour == 11:
hour = 23
elif self.settings.alarm_time.hour == 12 and hour == 23:
hour = 11
minute = self.settings.alarm_time.minute
self.settings.alarm_time = datetime.datetime(self.settings.alarm_time.year, \
self.settings.alarm_time.month, self.settings.alarm_time.day, \
hour, self.settings.alarm_time.minute, self.settings.alarm_time.second, 0)
elif self.grabbed == "light_button":
if len(self.light_button_move_init) == 0:
self.light_button_move_init = touch_curr.spos
d = touch_curr.spos[0] - self.light_button_move_init[0]
threshold = 0.05
# Ugly workaround for issue with Kivy and Raspberry Pi 3 + touch screen: mirror d if position is on other side
if self.light_button_move_init[0] > 0.5:
d = -d
if d > 0.05:
# move to the right: set light to wake up level
self.light_state = "on"
elif d < -0.05:
# move to the left: set light off
self.light_state = "off"
super(MyClockWidget, self).on_touch_move(touch)
def touch_down_function(self, touch):
t = self.settings.alarm_time
hands = self.ids["hands"]
minutes_hand = self.position_on_clock(t.minute/60+t.second/3600, length=0.40*hands.size[0])
hours_hand = self.position_on_clock(t.hour/12 + t.minute/720, length=0.35*hands.size[0])
if (0.02 <= touch.spos[0] <= 0.28) and (0.82 <= touch.spos[1] <= 0.98):
if self.grabbed == "":
self.grabbed = "light_button"
self.on_light_button_pressed()
elif (0.02 <= touch.spos[0] <= 0.18) and (0.02 <= touch.spos[1] <= 0.18):
self.on_play_button_pressed()
elif (0.82 <= touch.spos[0] <= 0.98) and (0.02 <= touch.spos[1] <= 0.18):
self.on_alarm_button_pressed()
elif (0.82 <= touch.spos[0] <= 0.98) and (0.82 <= touch.spos[1] <= 0.98):
self.on_settings_button_pressed()
elif self.view == "set_alarm":
self.set_alarm_timeout_counter = 0
if self.grabbed == "":
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"
self.touch_prev.clear()
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"
self.touch_prev.clear()
else:
self.grabbed = ""
elif self.view == "settings_menu":
pass
elif self.view == "settings_menu_wake_up_sound":
pass
elif self.view == "settings_menu_theme_select":
pass
elif self.view == "clock":
self.volume_target = 0
super(MyClockWidget, self).on_touch_down(touch)
def on_touch_down(self, touch, after=False):
t = time.time()
if t - self.view_active_since <= self.time_to_ignore_inputs_after_view_change:
return True
"""
if self.touch_down_event_prev is None:
print("touch down at " + str(t) + " on " + str(touch.spos) + ", after: " + str(after) + ", prev event: None")
else:
print("touch down at " + str(t) + " on " + str(touch.spos) + ", after: " + str(after) + ", prev event at " + str(self.touch_down_event_prev.time) + " on " + str(self.touch_down_event_prev.touch.spos))
"""
if after:
self.touch_down_event_curr = TouchEvent(touch)
return True
else:
Clock.schedule_once(lambda dt: self.on_touch_down(touch, True))
return super(MyClockWidget, self).on_touch_down(touch)
def on_touch_move(self, touch, after=False):
t = time.time()
if t - self.view_active_since <= self.time_to_ignore_inputs_after_view_change:
return True
"""
if self.touch_move_event_prev is None:
print("touch move at " + str(t) + " on " + str(touch.spos) + ", after: " + str(after) + ", prev event: None")
else:
print("touch move at " + str(t) + " on " + str(touch.spos) + ", after: " + str(after) + ", prev event at " + str(self.touch_move_event_prev.time) + " on " + str(self.touch_move_event_prev.touch.spos))
"""
if after:
# Do not delay processing of move events
self.touch_move_function(touch)
return True
else:
Clock.schedule_once(lambda dt: self.on_touch_move(touch, True))
return super(MyClockWidget, self).on_touch_move(touch)
def on_touch_up(self, touch, after=False):
t = time.time()
if t - self.view_active_since <= self.time_to_ignore_inputs_after_view_change:
return True
"""
if self.touch_up_event_prev is None:
print("touch up at " + str(t) + " on " + str(touch.spos) + ", after: " + str(after) + ", prev event: None")
else:
print("touch up at " + str(t) + " on " + str(touch.spos) + ", after: " + str(after) + ", prev event at " + str(self.touch_up_event_prev.time) + " on " + str(self.touch_up_event_prev.touch.spos))
"""
if after:
self.touch_up_event_curr = TouchEvent(touch)
return True
else:
Clock.schedule_once(lambda dt: self.on_touch_up(touch, True))
return super(MyClockWidget, self).on_touch_up(touch)
class MyApp(App):
update_rate = 60.0
def build(self):
clock_widget = MyClockWidget()
clock_widget.add_wake_up_sounds()
clock_widget.add_themes()
update_rate = App.get_running_app().update_rate
# update initially, just after construction of the widget is complete
Clock.schedule_once(clock_widget.update_display, 0)
# then update at update_rate times per second
Clock.schedule_interval(clock_widget.update_display, 1.0/update_rate)
Clock.schedule_once(clock_widget.get_air_quality, 0)
t = datetime.datetime.now()
delay = 58 - (t.second + t.microsecond / 1000000)
if delay < 0:
delay = delay + 60
Clock.schedule_once(clock_widget.sync_air_quality, delay)
if is_arm():
Window.borderless = True
# Volume is in 0-1.0 range
old_vol = clock_widget.mixer.getvolume()
clock_widget.volume_target = clock_widget.settings.wake_up_volume / 20.0
new_vol = clock_widget.mixer.getvolume()
print("HW volume changed from " + str(old_vol) + " to " + str(new_vol))
clock_widget.apply_settings()
return clock_widget
def except_hook(type, value, tb):
if is_arm():
pixels.fill((0, 0, 0, 0))
pixels.show()
backlight.brightness = 50
return
if __name__ == '__main__':
if is_arm():
sys.excepthook = except_hook
MyApp().run()