# 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()