# 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 import os from rgbw_colorspace_converter.colors.converters import HSV, RGB from hsluv import hsluv_to_rgb from kivy.config import Config def is_arm(): if (os.uname()[4][:3] == 'arm') or (os.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 #Config.set('graphics', 'width', '800') #Config.set('graphics', 'height', '480') Config.set('graphics', 'width', '667') Config.set('graphics', 'height', '400') import board import neopixel pixel_pin = board.D10 num_pixels = 144 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() else: Config.set('graphics', 'width', '1200') Config.set('graphics', 'height', '720') pixels = 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.clock import Clock from kivy.lang import Builder from kivy.graphics import Color, Line, Rectangle import pulsectl import vlc Builder.load_string(''' : on_pos: self.update_display() on_size: self.update_display() FloatLayout id: face size_hint: None, None pos_hint: {"center_x":0.5, "center_y":0.5} size: 0.9*min(root.size), 0.9*min(root.size) canvas: Color: rgb: 0.1, 0.1, 0.1 Ellipse: size: self.size pos: self.pos FloatLayout id: hands size_hint: None, None pos_hint: {"center_x":0.5, "center_y":0.5} size: 0.9*min(root.size), 0.9*min(root.size) FloatLayout id: play_button size_hint: None, None pos_hint: {"center_x":0.1, "center_y":0.1} size: 0.1*min(root.size), 0.1*min(root.size) FloatLayout id: set_alarm_button size_hint: None, None pos_hint: {"center_x":0.9, "center_y":0.1} size: 0.1*min(root.size), 0.1*min(root.size) FloatLayout id: light_button size_hint: None, None pos_hint: {"center_x":0.124, "center_y":0.9} size: 0.1*125/70*min(root.size), 0.1*min(root.size) FloatLayout id: settings_button size_hint: None, None pos_hint: {"center_x":0.9, "center_y":0.9} size: 0.1*min(root.size), 0.1*min(root.size) FloatLayout id: settings_menu size_hint: None, None pos_hint: {"center_x":0.5, "center_y":0.5} size: 0.7*min(root.size), 0.7*min(root.size) canvas: Color: rgb: 0.1, 0.1, 0.1 Rectangle: size: self.size pos: self.pos GridLayout: rows: 8 cols: 2 pos_hint: {"center_x":0.5, "center_y":0.5} Label: text: "Volume" halign: "left" valign: "middle" text_size: self.size Slider: id: volume_slider min: 0 max: 20 value: 15 on_value: root.volume_slider_value(*args) value_track: True value_track_color: [1, 0, 0, 1] Label: text:"Wake up sound" halign: "left" valign: "middle" text_size: self.size Button: id: settings_menu_wake_up_sound_select_button on_press: root.settings_menu_wake_up_sound_select_button_cb() text: str(app.alarm_settings.sound_selected) Label: text:"Wake up brightness" halign: "left" valign: "middle" text_size: self.size Slider: id: wake_up_bightness_slider min: 0 max: 20 value: 20 on_value: root.wake_up_brightness_slider_value(*args) value_track: True value_track_color: [1, 0, 0, 1] Label: text:"Reading light brightness" halign: "left" valign: "middle" text_size: self.size Slider: id: reading_light_brightness_slider min: 0 max: 20 value: 1 on_value: root.reading_light_brightness_slider_value(*args) value_track: True value_track_color: [1, 0, 0, 1] Label: text:"Display brightness" halign: "left" valign: "middle" text_size: self.size Slider: id: display_brightness_slider min: 0 max: 20 value: 10 on_value: root.display_brightness_slider_value(*args) value_track: True value_track_color: [1, 0, 0, 1] FloatLayout id: settings_menu_wake_up_sound size_hint: None, None pos_hint: {"center_x":0.5, "center_y":0.5} size: 0.7*min(root.size), 0.7*min(root.size) canvas: Color: rgb: 0.1, 0.1, 0.1 Rectangle: size: self.size pos: self.pos BoxLayout: orientation: "vertical" pos_hint: {"center_x":0.5, "center_y":0.5} Label: text: "Select wake up sound" GridLayout: cols: 2 CheckBox: group: "settings_menu_wake_up_sound" on_active: root.settings_menu_wake_up_sound_cb(self, self.active, "Birds") id: settings_menu_wake_up_sound_Birds Label: text: "Birds" halign: "left" valign: "middle" text_size: self.size active: True CheckBox: group: "settings_menu_wake_up_sound" on_active: root.settings_menu_wake_up_sound_cb(self, self.active, "NPO Radio 1") id: settings_menu_wake_up_sound_NpoRadio1 Label: text: "NPO Radio 1" halign: "left" valign: "middle" text_size: self.size Button: id: settings_menu_wake_up_sound_Ok_button on_press: root.settings_menu_wake_up_sound_Ok_button_cb() text: "Ok" ''') Position = collections.namedtuple('Position', 'x y') class AlarmSettings(): alarm_time = datetime.datetime(2022, 12, 10, 7, 30, 0, 0) alarm_activated = False alarm_playing = False alarm_modified = False led_color_rgbw = HSV(0, 0, 0).rgbw # sound_selected = "NPO Radio 1" # sound_source = "https://icecast.omroep.nl/radio1-bb-mp3" sound_selected = "Birds" sound_source = "Woodpecker Chirps - QuickSounds.com.mp3" seconds_to_sunrise = 1 * 60 # 30 minutes sunrise_state = "off" # "off", "active" or "completed" volume = 15 wake_up_brightness = 20 reading_light_brightness = 1 display_brightness = 10 class Touch(): def __init__(self, x=None): if x is None: self.pos = [] self.spos = [] self.is_empty = True self.angle = 0 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 def __repr__(self): return ".is_empty: " + str(self.is_empty) + ", .pos: " + str(self.pos) + ", .spos: " + str(self.spos) def copy(self, x): self = Touch(x) def clear(self): self = Touch() class MyClockWidget(FloatLayout): 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 led_color_rgbw = HSV(0, 0, 0).rgbw # view can be one of the following strings: # - "clock" # - "set_alarm" # - "settings_menu" # - "settings_menu_wake_up_sound" view = "clock" # 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 # 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() vlc = vlc.Instance() player = vlc.media_player_new() is_arm = is_arm() if is_arm: backlight = Backlight() else: backlight = None def play_sound(self, source): alarm_settings = App.get_running_app().alarm_settings 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) media = self.vlc.media_new(source) self.player.set_media(media) self.player.play() alarm_settings.alarm_playing = True def stop_sound(self): self.player.stop() alarm_settings = App.get_running_app().alarm_settings alarm_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)) self.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 draw_face(self): """ Add number labels when added in widget hierarchy """ alarm_settings = App.get_running_app().alarm_settings if self.view == "set_alarm": time = alarm_settings.alarm_time else: time = datetime.datetime.now() for i in range(1, 13): if time.hour < 12: offset = 0 else: offset = 12 self.face_numbers.append(Label( text=str(i + offset), font_size=float(Config.get('graphics', 'height'))*0.05, pos_hint={ # pos_hint is a fraction in range (0, 1) "center_x": 0.5 + 0.45*math.sin(2 * math.pi * i/12), "center_y": 0.5 + 0.45*math.cos(2 * math.pi * i/12), } )) self.ids["face"].add_widget(self.face_numbers[i - 1]) def update_face(self): alarm_settings = App.get_running_app().alarm_settings if self.view == "set_alarm": time = alarm_settings.alarm_time else: time = datetime.datetime.now() for i in range(0, 12): if time.hour < 12: offset = 0 else: offset = 12 self.face_numbers[i].text=str(i + 1 + offset) def on_parent(self, myclock, parent): self.draw_face() def position_on_clock(self, fraction, length): """ Calculate position in the clock using trygonometric functions """ center_x = self.size[0]/2 center_y = self.size[1]/2 return Position( center_x + round(length * math.sin(2 * math.pi * fraction)), center_y + round(length * math.cos(2 * math.pi * fraction)), ) def update_light_button(self): source = 'light_off.png' 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): alarm_settings = App.get_running_app().alarm_settings if alarm_settings.alarm_playing: source = 'playing.png' rgb = [0.9, 0.9, 0.9] else: source = 'stopped.png' 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): alarm_settings = App.get_running_app().alarm_settings if (self.view == "set_alarm") or alarm_settings.alarm_activated: source = 'alarm_on.png' rgb = [0.9, 0.9, 0.9] else: source = 'alarm_off.png' 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]]) # Rectangle(size=set_alarm_button.size, pos=set_alarm_button.pos, source=source) 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): alarm_settings = App.get_running_app().alarm_settings if (self.view.startswith("settings_menu")): source = 'settings_visible.png' rgb = [0.9, 0.9, 0.9] else: source = 'settings_not_visible.png' 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]]) # Rectangle(size=settings_button.size, pos=settings_button.pos, source=source) self.draw_list_curr_frame.append(["Rectangle", settings_button.canvas, settings_button.size, settings_button.pos, source]) def intensity_to_rgbw(self, intensity): if intensity < 0: intensity = 0 elif intensity > 1: intensity = 1 h = max(0, min(75, (75 - 12) * intensity + 12)) s = max(0, min(100, 250 - 250 * intensity)) # s = max(0, min(100, 100 - 100 * intensity)) l = 100 * intensity rgb = hsluv_to_rgb([h, s, l]) led_color = RGB(rgb[0] * 255, rgb[1] * 255, rgb[2] * 255) return led_color def sunrise(self): alarm_settings = App.get_running_app().alarm_settings if self.seconds_to_next_alarm < 0.1: App.get_running_app().alarm_settings.sunrise_state = "completed" intensity = (1.0 - self.seconds_to_next_alarm / alarm_settings.seconds_to_sunrise) led_color = self.intensity_to_rgbw(intensity) if self.led_color_rgbw != led_color.rgbw: print("t: " + str(self.seconds_to_next_alarm) + ", i: " + str(intensity) + ", rgbw: " + str(led_color.rgbw)) self.led_color_rgbw = led_color.rgbw pixels.fill(led_color.rgbw) pixels.show() def check_sun_rise(self): alarm_settings = App.get_running_app().alarm_settings if alarm_settings.alarm_activated == False: return if self.seconds_to_next_alarm < alarm_settings.seconds_to_sunrise: self.sunrise() else: led_color_off = RGB(0, 0, 0) if (alarm_settings.sunrise_state == "off") and (self.led_color_rgbw != led_color_off.rgbw): self.led_color_rgbw = led_color_off.rgbw pixels.fill(led_color_off.rgbw) pixels.show() def check_play_sound(self): alarm_settings = App.get_running_app().alarm_settings if alarm_settings.alarm_activated == False: return if (self.seconds_to_next_alarm < 0.1) or (alarm_settings.alarm_playing == True): if alarm_settings.sound_source != "": self.play_sound(alarm_settings.sound_source) def calc_seconds_to_next_alarm(self): alarm_settings = App.get_running_app().alarm_settings if alarm_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 = alarm_settings.alarm_time - now alarm_settings.alarm_time -= datetime.timedelta(days=d.days) # Calculate number of seconds until next alarm d = alarm_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.check_sun_rise() self.check_play_sound() def update_clock(self): 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]) alarm_settings = App.get_running_app().alarm_settings if self.view == "set_alarm": time = alarm_settings.alarm_time else: time = datetime.datetime.now() hands = self.ids["hands"] seconds_hand = self.position_on_clock(time.second/60, length=0.45*hands.size[0]) minutes_hand = self.position_on_clock(time.minute/60+time.second/3600, length=0.40*hands.size[0]) hours_hand = self.position_on_clock(time.hour/12 + time.minute/720, length=0.35*hands.size[0]) self.update_face() #hands.canvas.clear() 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 alarm_settings.alarm_modified == False: self.draw_list_curr_frame.append(["Color", hands.canvas, 0.9, 0.0, 0.0]) # Line(points=[hands.center_x, hands.center_y, hours_hand.x, hours_hand.y], width=3, cap="round") 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, 0.8, 0.0, 0.0]) #Line(points=[hands.center_x, hands.center_y, minutes_hand.x, minutes_hand.y], width=2, cap="round") 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.set_alarm_timeout_counter = 0 if alarm_settings.alarm_modified: alarm_settings.alarm_activated = True alarm_settings.alarm_modified = False else: self.draw_list_curr_frame.append(["Color", hands.canvas, 0.9, 0.9, 0.9]) # Line(points=[hands.center_x, hands.center_y, hours_hand.x, hours_hand.y], width=3, cap="round") 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, 0.8, 0.8, 0.8]) # Line(points=[hands.center_x, hands.center_y, minutes_hand.x, minutes_hand.y], width=2, cap="round") 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, 0.7, 0.7, 0.7]) # Line(points=[hands.center_x, hands.center_y, seconds_hand.x, seconds_hand.y], width=1, cap="round") self.draw_list_curr_frame.append(["Line", hands.canvas, [hands.center_x, hands.center_y, seconds_hand.x, seconds_hand.y], 1, "round"]) def update_settings(self): if self.view == "settings_menu": self.draw_list_curr_frame.append(["self.hide_widget", self.ids["settings_menu"], False]) elif self.view == "settings_menu_wake_up_sound": self.draw_list_curr_frame.append(["self.hide_widget", self.ids["settings_menu_wake_up_sound"], False]) def draw_display(self): 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]: Color(i[2], i[3], i[4]) elif i[0] == "Rectangle": with i[1]: Rectangle(size=i[2], pos=i[3], source=i[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 update_display(self, *args): 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"], 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"], True]) self.draw_list_curr_frame.append(["self.hide_widget", self.ids["settings_menu_wake_up_sound"], True]) 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 alarm_settings = App.get_running_app().alarm_settings self.ids["settings_menu_wake_up_sound_Birds"].active = False self.ids["settings_menu_wake_up_sound_NpoRadio1"].active = False print("sound selected: " + alarm_settings.sound_selected) if alarm_settings.sound_selected == "Birds": self.ids["settings_menu_wake_up_sound_Birds"].active = True elif alarm_settings.sound_selected == "NPO Radio 1": self.ids["settings_menu_wake_up_sound_NpoRadio1"].active = True self.view = "settings_menu_wake_up_sound" def settings_menu_wake_up_sound_Ok_button_cb(self): alarm_settings = App.get_running_app().alarm_settings self.ids["settings_menu_wake_up_sound_select_button"].text = alarm_settings.sound_selected self.view = "settings_menu" def settings_menu_wake_up_sound_cb(self, instance, value, sound): alarm_settings = App.get_running_app().alarm_settings if self.settings_menu_wake_up_sound_select_button_cb_hack: self.settings_menu_wake_up_sound_select_button_cb_hack = False if not (alarm_settings.sound_selected == "" and sound != ""): return if value == True: print("You selected " + sound) else: print("You deselected " + sound) alarm_settings.sound_source = "" alarm_settings.sound_selected = "" if self.ids["settings_menu_wake_up_sound_Birds"].active: alarm_settings.sound_selected = sound alarm_settings.sound_source = "Woodpecker Chirps - QuickSounds.com.mp3" elif self.ids["settings_menu_wake_up_sound_NpoRadio1"].active: alarm_settings.sound_selected = sound alarm_settings.sound_source = "https://icecast.omroep.nl/radio1-bb-mp3" def volume_slider_value(self, *args): alarm_settings = App.get_running_app().alarm_settings alarm_settings.volume = int(args[1]) print("Volume changed to " + str(alarm_settings.volume)) with pulsectl.Pulse('volume-increaser') as pulse: for sink in pulse.sink_list(): # Volume is usually in 0-1.0 range, with >1.0 being soft-boosted old_vol = pulse.volume_get_all_chans(sink) pulse.volume_set_all_chans(sink, alarm_settings.volume / 20.0) new_vol = pulse.volume_get_all_chans(sink) print("HW volume changed from " + str(old_vol) + " to " + str(new_vol)) def wake_up_brightness_slider_value(self, *args): alarm_settings = App.get_running_app().alarm_settings alarm_settings.wake_up_brightness = int(args[1]) print("Wake up brightness changed to " + str(alarm_settings.wake_up_brightness)) def reading_light_brightness_slider_value(self, *args): alarm_settings = App.get_running_app().alarm_settings alarm_settings.reading_light_brightness = int(args[1]) print("Reading light brightness changed to " + str(alarm_settings.reading_light_brightness)) def display_brightness_slider_value(self, *args): alarm_settings = App.get_running_app().alarm_settings alarm_settings.display_brightness = int(args[1]) if self.is_arm: self.set_backlight(alarm_settings.display_brightness) def on_light_button_pressed(self): alarm_settings = App.get_running_app().alarm_settings App.get_running_app().alarm_settings.sunrise_state = "off" if alarm_settings.alarm_playing: self.stop_sound() def on_play_button_pressed(self): alarm_settings = App.get_running_app().alarm_settings if alarm_settings.alarm_playing: self.stop_sound() else: self.play_sound(alarm_settings.sound_source) def on_alarm_button_pressed(self): alarm_settings = App.get_running_app().alarm_settings alarm_settings.alarm_modified = False self.set_alarm_timeout_counter = 0 if self.view == "set_alarm": self.view = "clock" alarm_settings.alarm_activated = False else: self.view = "set_alarm" alarm_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" else: self.view = "clock" print("view updated to " + self.view) def on_touch_up(self, touch): self.grabbed = "" alarm_settings = App.get_running_app().alarm_settings 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 on_touch_move(self, touch): if self.grabbed == "": return alarm_settings = App.get_running_app().alarm_settings 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): 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): while touch_curr.angle - self.touch_prev.angle <= -tol: touch_curr.angle += inc self.touch_prev = copy.deepcopy(touch_curr) if self.grabbed == "minute": alarm_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 - alarm_settings.alarm_time.minute) >= 15) and ((minute - alarm_settings.alarm_time.minute) <= 45)): minute = minute - 30 elif (((minute - alarm_settings.alarm_time.minute) <= -15) and ((minute - alarm_settings.alarm_time.minute) >= -45)): minute = minute + 30 while minute < 0: minute += 60 while minute >= 60: minute -= 60 # hour correction hour = alarm_settings.alarm_time.hour if alarm_settings.alarm_time.minute >= 55 and minute <= 5: hour += 1 elif alarm_settings.alarm_time.minute <= 5 and minute >= 55: hour -= 1 while hour < 0: hour += 24 while hour >= 24: hour -= 24 alarm_settings.alarm_time = datetime.datetime(alarm_settings.alarm_time.year, \ alarm_settings.alarm_time.month, alarm_settings.alarm_time.day, \ hour, minute, alarm_settings.alarm_time.second, 0) elif self.grabbed == "hour": alarm_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 - alarm_settings.alarm_time.hour) >= 3) and ((hour - alarm_settings.alarm_time.hour) <= 9)): hour = hour - 6 if (((hour - alarm_settings.alarm_time.hour) <= -3) and ((hour - alarm_settings.alarm_time.hour) >= -9)): hour = hour + 6 while hour < 0: hour += 12 while hour >= 12: hour -= 12 if alarm_settings.alarm_time.hour >= 12: hour += 12 # AM / PM correction if alarm_settings.alarm_time.hour == 11 and hour == 0: hour = 12 elif alarm_settings.alarm_time.hour == 23 and hour == 12: hour = 0 elif alarm_settings.alarm_time.hour == 0 and hour == 11: hour = 23 elif alarm_settings.alarm_time.hour == 12 and hour == 23: hour = 11 minute = alarm_settings.alarm_time.minute alarm_settings.alarm_time = datetime.datetime(alarm_settings.alarm_time.year, \ alarm_settings.alarm_time.month, alarm_settings.alarm_time.day, \ hour, alarm_settings.alarm_time.minute, alarm_settings.alarm_time.second, 0) super(MyClockWidget, self).on_touch_move(touch) def on_touch_down(self, touch): alarm_settings = App.get_running_app().alarm_settings time = alarm_settings.alarm_time hands = self.ids["hands"] minutes_hand = self.position_on_clock(time.minute/60+time.second/3600, length=0.40*hands.size[0]) hours_hand = self.position_on_clock(time.hour/12 + time.minute/720, length=0.35*hands.size[0]) if (0.05 <= touch.spos[0] <= 0.15) and (0.85 <= touch.spos[1] <= 0.95): self.on_light_button_pressed() elif (0.05 <= touch.spos[0] <= 0.15) and (0.05 <= touch.spos[1] <= 0.15): self.on_play_button_pressed() elif (0.85 <= touch.spos[0] <= 0.95) and (0.05 <= touch.spos[1] <= 0.15): self.on_alarm_button_pressed() elif (0.85 <= touch.spos[0] <= 0.95) and (0.85 <= touch.spos[1] <= 0.95): 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 = "" print("grabbed: " + self.grabbed) elif self.view == "settings_menu": pass elif self.view == "clock": self.stop_sound() super(MyClockWidget, self).on_touch_down(touch) class MyApp(App): alarm_settings = AlarmSettings() update_rate = 60.0 # apply volume setting with pulsectl.Pulse('volume-increaser') as pulse: for sink in pulse.sink_list(): # Volume is usually in 0-1.0 range, with >1.0 being soft-boosted old_vol = pulse.volume_get_all_chans(sink) pulse.volume_set_all_chans(sink, alarm_settings.volume / 20.0) new_vol = pulse.volume_get_all_chans(sink) print("HW volume changed from " + str(old_vol) + " to " + str(new_vol)) def build(self): clock_widget = MyClockWidget() 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) return clock_widget def except_hook(type, value, tb): if is_arm(): pixels.fill((0, 0, 0, 0)) pixels.show() return if __name__ == '__main__': #sys.excepthook = except_hook MyApp().run()