class Clock: PAGE_CLOCK = 0 PAGE_SETUP = 1 def __init__(self, screen_resolution=(320, 240)): log.info(f"Init {type(self).__name__}") if HAS_GPIO: # Backlight GPIO.setmode(GPIO.BCM) GPIO.setup(18, GPIO.OUT) self.backlight = GPIO.PWM(18, 1000) self.backlight_value = 100 self.backlight.start(self.backlight_value) self.button_1 = 17 self.button_2 = 22 self.button_3 = 23 self.button_4 = 27 GPIO.setup(self.button_1, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(self.button_2, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(self.button_3, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(self.button_4, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.add_event_detect(self.button_1, GPIO.FALLING, callback=self.handle_button_1, bouncetime=200) GPIO.add_event_detect(self.button_2, GPIO.FALLING, callback=self.handle_button_2, bouncetime=200) GPIO.add_event_detect(self.button_3, GPIO.FALLING, callback=self.handle_button_3, bouncetime=200) GPIO.add_event_detect(self.button_4, GPIO.FALLING, callback=self.handle_button_4, bouncetime=200) os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1' # os.environ["SDL_FBDEV"] = "/dev/fb1" # os.environ["SDL_VIDEODRIVER"] = "fbcon" status = pygame.init() info = pygame.display.Info() log.info(f"SDL init status: {status}") log.info("Available display modes:") log.info(pygame.display.list_modes()) log.info(f"Display resolution: ({info.current_w}, {info.current_h})") log.info(f"Display driver: {pygame.display.get_driver()}") log.info(f"Display bitsize: {info.bitsize}, bytesize: {info.bytesize}") if screen_resolution is None: self.screen_resolution = np.array([pygame.display.Info().current_w, pygame.display.Info().current_h]) else: self.screen_resolution = np.array(screen_resolution) self.surface = pygame.display.set_mode(self.screen_resolution) pygame.display.set_caption("Treblig Clock") if HAS_GPIO: pygame.mouse.set_visible(False) else: pygame.mouse.set_visible(True) pygame.font.init() self.pyg_clock = pygame.time.Clock() self.clock_style = ClockStyleSimple(surface=self.surface, screen_resolution=screen_resolution) self.alarm = Alarm() self.angles = [0, 0, 0] self.now = datetime.datetime.now() # todo: remove hard coded wake-up time. self.alarm.add(7, 0) self.is_running = False self.page = Clock.PAGE_CLOCK self.mouse = None log.debug(f"Info:") log.debug("\n" + str(pygame.display.Info())) # def __del__(self): # log.info(f"Delete {type(self).__name__}") def cleanup(self): if HAS_GPIO: self.backlight_value = 100 self.backlight.ChangeDutyCycle(self.backlight_value) self.backlight.stop() GPIO.cleanup() pygame.font.quit() pygame.display.quit() pygame.quit() def run(self): try: self.is_running = True while self.is_running: self.on_event() self.update() self.draw() self.pyg_clock.tick(30) except KeyboardInterrupt: log.info("Keyboard interrupt.") pass except Exception: log.exception("ERROR") finally: log.info("Shutting down gracefully.") self.cleanup() log.info("Bye!") def update(self): self.now = datetime.datetime.now() self.alarm.update(self.now) self.clock_style.update(self.now, self.alarm) self.mouse = pygame.mouse.get_pos() def draw(self): if self.page == Clock.PAGE_CLOCK: self.draw_page_clock() elif self.page == Clock.PAGE_SETUP: self.draw_page_set_alarm() pygame.display.update() def update_theme(self): if self.now.hour == 8 and self.now.minute == 0 and 0 < self.now.second < 2: self.set_theme_day() elif self.now.hour == 22 and self.now.minute == 0 and 0 < self.now.second < 2: self.set_theme_night() def draw_page_clock(self): self.clock_style.draw() def draw_page_set_alarm(self): params = self.theme.get("set_alarm") self.surface.fill(self.theme.get("background").get("color2")) for k, v in self.alarm.alarms.items(): params["label"] = f"{self.alarm.alarms[k]['hour']}:{self.alarm.alarms[k]['minute']}" self.draw_label(params) # todo: extend to support multiple alarms break def on_event(self): for event in pygame.event.get(): if event.type is pygame.QUIT: self.is_running = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: self.is_running = False if event.key == pygame.K_3: log.info("Button 3 pressed.") if self.page == Clock.PAGE_CLOCK: self.page = Clock.PAGE_SETUP else: self.page = Clock.PAGE_CLOCK if event.key == pygame.K_z: log.info("Button 4 pressed.") log.info(self.alarm.alarms) if event.type == pygame.MOUSEBUTTONDOWN: log.info("click detected") params = {"rect": np.array([0.8, 0.8, 0.95, 0.95])} _rect = self.to_pygrect(params["rect"]) if _rect[0] < self.mouse[0] < _rect[0] + _rect[2] and _rect[1] < self.mouse[1] < _rect[1] + _rect[3]: log.info("click inside button detected") # def draw_button(self, params): # params = {} # params["rect"] = np.array([0.8, 0.8, 0.95, 0.95]) # r = params["rect"] # _rect = self.to_pygrect(params["rect"]) # pygame.draw.rect(self.surface, (220, 220, 220), _rect) # font_size = self.to_pixels(24 / 240) # font_big = pygame.font.Font(None, font_size) # text_surface = font_big.render("test", True, (0, 0, 0)) # center = self.to_pixels(r[:2] + 0.5 * (r[2:] - r[:2])) # rect = text_surface.get_rect(center=center) # self.surface.blit(text_surface, rect) def handle_button_1(self, *args): if self.page == self.PAGE_CLOCK: if self.alarm.is_beeping(): self.alarm.snooze() else: self.brightness_up() elif self.page == self.PAGE_SETUP: for k, v in self.alarm.alarms.items(): self.alarm.alarms[k]["hour"] += 1 if self.alarm.alarms[k]["hour"] > 23: self.alarm.alarms[k]["hour"] = 0 break def handle_button_2(self, *args): if self.page == self.PAGE_CLOCK: if self.alarm.is_beeping(): self.alarm.stop_beeping() self.alarm.stop_snoozing() else: self.brightness_down() elif self.page == self.PAGE_SETUP: for k, v in self.alarm.alarms.items(): self.alarm.alarms[k]["hour"] -= 1 if self.alarm.alarms[k]["hour"] < 0: self.alarm.alarms[k]["hour"] = 23 break def handle_button_3(self, *args): self.toggle_day_night() def handle_button_4(self, *args): if self.page == self.PAGE_CLOCK: self.page = self.PAGE_SETUP else: self.page = self.PAGE_CLOCK def brightness_up(self, *args): self.backlight_value = min(100, self.backlight_value + 10) self.backlight.ChangeDutyCycle(self.backlight_value) log.info(f"Brightness up: {self.backlight_value}") def brightness_down(self, *args): self.backlight_value = max(0, self.backlight_value - 10) self.backlight.ChangeDutyCycle(self.backlight_value) log.info(f"Brightness down: {self.backlight_value}") def set_theme_day(self): self.theme = self.theme_day self.backlight_value = 70 self.backlight.ChangeDutyCycle(70) def set_theme_night(self): self.theme = self.theme_night self.backlight_value = 10 self.backlight.ChangeDutyCycle(self.backlight_value) def toggle_day_night(self, *args): if self.theme is self.theme_day: self.set_theme_night() else: self.set_theme_day()