diff --git a/config.py b/config.py index 1f4799f8f4f96f5e918c48944ab0c97aabdbb96f..a318d9cebe2210655aee510d8955cf2906a0720c 100644 --- a/config.py +++ b/config.py @@ -7,9 +7,10 @@ PROP_WINDOW_FULLSCREEN = False PROP_WINDOW_SCALE = 3 PROP_AVAILABLE_WIDGETS = [ + "IPShowWidget", + "TestWidget", "AnalogClockWidget", - "DigitalClockWidget", - "TestWidget" + "DigitalClockWidget" ] PROP_AVAILABLE_PATTERNS = [ diff --git a/main.py b/main.py index 4c94a397f4995a55573f607cccf9e47e1737bc14..83394fe7398a83d020a0814f2adb2c90b77edd7b 100644 --- a/main.py +++ b/main.py @@ -36,19 +36,24 @@ if os.path.exists("module_config.json"): with open("module_config.json", "w") as f: json.dump(module_config, f, indent=4) +mainthread_queue = queue.Queue() api_queue = queue.Queue() led_queue = queue.Queue() @app.route("/api/available_fonts") -def available_fonts(): +def wr_available_fonts(): return Response(json.dumps(pygame.font.get_fonts()), mimetype="application/json") @app.route("/api/available_widgets") -def available_widgets(): +def wr_available_widgets(): return Response(json.dumps(PROP_AVAILABLE_WIDGETS), mimetype="application/json") +@app.route("/api/available_patterns") +def wr_available_patterns(): + return Response(json.dumps(PROP_AVAILABLE_PATTERNS), mimetype="application/json") + @app.route("/api/set_widget", methods=["POST"]) -def set_widget(): +def wr_set_widget(): widget_name = request.args.get("widget") if widget_name is None: return "No widget provided", 400 @@ -60,6 +65,23 @@ def set_widget(): return "OK" +@app.route("/api/set_pattern", methods=["POST"]) +def wr_set_pattern(): + pattern_name = request.args.get("pattern") + if pattern_name is None: + return "No pattern provided", 400 + + if pattern_name not in PROP_AVAILABLE_PATTERNS: + return f"Pattern {pattern_name} is not available", 400 + + led_queue.put("SET_PATTERN:" + pattern_name) + + return "OK" + +@app.route("/api/get_config") +def wr_get_config(): + return Response(json.dumps(module_config), mimetype="application/json") + def load_widget(widget_name: str): # Get the widget properties from the config file widget_props = module_config.get(widget_name, {}).copy() @@ -122,6 +144,9 @@ def run_screen(): # Update the screen pygame.display.flip() + # Send exit message to main thread + mainthread_queue.put("EXIT") + # Quit Pygame pygame.quit() @@ -149,6 +174,8 @@ def run_leds(): current_pattern = load_pattern(pattern_name, leds) pattern_timer = module_config.get(pattern_name, {}).get("tick_rate", -1) current_pattern.tick() + elif message == "EXIT": + break except queue.Empty: pass @@ -175,7 +202,16 @@ if __name__ == "__main__": # Wait for the threads to finish try: while True: + try: + message = mainthread_queue.get_nowait() + if message == "EXIT": + api_queue.put("EXIT") + led_queue.put("EXIT") + break + except queue.Empty: + pass # Keep main thread alive - time.sleep(1000) + time.sleep(1) except KeyboardInterrupt: - exit() \ No newline at end of file + exit() + exit() \ No newline at end of file diff --git a/module_defaults.json b/module_defaults.json index de3b8f70d7d863e5e5d598b5347f5e40de692cf1..c25c3ba950faa9a18c7f7da670d72aa6bb6a82f1 100644 --- a/module_defaults.json +++ b/module_defaults.json @@ -197,6 +197,63 @@ "type": "filepath" } }, + "IPShowWidget": { + "scalable_font_size": { + "name": "Font size", + "description": "The font size of the clock.", + "default": 20, + "type": "integer" + }, + "font_color": { + "name": "Font colour", + "description": "The colour of the font.", + "default": "#cccccc", + "type": "color" + }, + "font_name": { + "name": "Font name", + "description": "The name of the font to use.", + "default": null, + "type": "font" + }, + "font_bold": { + "name": "Font bold", + "description": "Whether the font should be bold.", + "default": false, + "type": "boolean" + }, + "font_italic": { + "name": "Font italic", + "description": "Whether the font should be italic.", + "default": false, + "type": "boolean" + }, + "draw_background": { + "name": "Draw background", + "description": "Draw the background of the widget.", + "default": true, + "type": "boolean" + }, + "background_type": { + "name": "Background type", + "description": "The type of background to use (`color` or `image`).", + "default": "color", + "type": "enum", + "allowed_values": ["color", "image"] + }, + "background_color": { + "name": "Background colour", + "description": "The colour of the background.", + "default": "#000000", + "type": "color" + }, + "background_image": { + "name": "Background image", + "description": "The image to use as the background.", + "default": "", + "type": "filepath" + } + }, "TestWidget": { "color": { "name": "Colour", diff --git a/num_utils.py b/num_utils.py index 96699966e25bee7a3d9119976a9dbd05ec2327ff..cc177288631b36392789486ecfda2506ca3d73e9 100644 --- a/num_utils.py +++ b/num_utils.py @@ -1,5 +1,5 @@ from config import * - +import socket def get_screen_centre(): return (PROP_SCREEN_WIDTH * PROP_WINDOW_SCALE) / 2, (PROP_SCREEN_HEIGHT * PROP_WINDOW_SCALE) / 2 @@ -15,3 +15,17 @@ def color_hex_to_tuple(hx: str) -> tuple[int, int, int]: """ hx = hx.removeprefix("#") return tuple(int(hx[i:i + 2], 16) for i in (0, 2, 4)) + + +def get_ip(): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.settimeout(0) + try: + # doesn't even have to be reachable + s.connect(('10.254.254.254', 1)) + ip_addr = s.getsockname()[0] + except Exception: + ip_addr = '127.0.0.1' + finally: + s.close() + return ip_addr diff --git a/screenwidgets.py b/screenwidgets.py index a660703409395e4ab77c3d7e7f14775459096a72..212ba41a4d17c6dd29882388c67437ea1c6eee1a 100644 --- a/screenwidgets.py +++ b/screenwidgets.py @@ -2,7 +2,7 @@ import math from datetime import datetime from config import PROP_WINDOW_SCALE, PROP_SCREEN_WIDTH, PROP_SCREEN_HEIGHT -from num_utils import get_screen_centre +from num_utils import get_screen_centre, get_ip class IWidgetMeta(type): @@ -197,3 +197,51 @@ class AnalogClockWidget(IWidget): self.clock_hand(screen, get_screen_centre(), self.second_hand_length, second * 6, self.second_hand_thickness, self.second_hand_color) self.clock_hand(screen, get_screen_centre(), self.minute_hand_length, minute * 6, self.minute_hand_thickness, self.minute_hand_color) self.clock_hand(screen, get_screen_centre(), self.hour_hand_length, hour * 30, self.hour_hand_thickness, self.hour_hand_color) + +class IPShowWidget(IWidget): + def __init__(self, config: dict): + super().__init__(config) + self.pygame = config['pygame'] + + # Backgrounds + self.background_color = config.get("background_color", "black") + self.draw_background = config.get("draw_background", False) + self.draw_clock_background = config.get("draw_clock_background", True) + self.background_type = config.get("background_type", "color") + if self.background_type == "image" and config.get("background_image", "") != "": + self.bg_image = self.pygame.image.load(config.get("background_image", "")) + self.bg_image = self.pygame.transform.scale(self.bg_image, ( + PROP_SCREEN_WIDTH * PROP_WINDOW_SCALE, + PROP_SCREEN_HEIGHT * PROP_WINDOW_SCALE + )) + + + # Get the font path + if config.get("font_name", None) is not None: + self.font_path = self.pygame.font.match_font(config['font_name']) + else: + self.font_path = self.pygame.font.get_default_font() + + # Load the font + self.font = self.pygame.font.Font(self.font_path, config.get("font_size", 15 * PROP_WINDOW_SCALE)) + self.font.bold = config.get("font_bold", False) + self.font.italic = config.get("font_italic", False) + + # Set the colours + self.color = config.get("font_color", "white") + + def draw(self, screen): + if self.draw_background: + if self.background_type == "color": + screen.fill(self.background_color) + elif self.background_type == "image": + screen.blit(self.bg_image, (0, 0)) + + text_surface = self.font.render(f"IP Address:", True, self.color) + text_surface_2 = self.font.render(get_ip(), True, self.color) + text_rect = text_surface.get_rect(center=get_screen_centre()) + y = get_screen_centre()[1] + self.font.size("IP Address:")[1] + 10 + text_rect_2 = text_surface_2.get_rect(center=(get_screen_centre()[0], y)) + screen.blit(text_surface, text_rect) + screen.blit(text_surface_2, text_rect_2) +