diff --git a/matrix.py b/matrix.py index 6e8c5c6..b65890c 100644 --- a/matrix.py +++ b/matrix.py @@ -146,33 +146,83 @@ class Matrix64Display(SampleBase): self.mqtt = MQTTListener() self.mqtt.start() + # Menu state + self.menu_mode = False + self.menu_selection = 0 + + # Swipe gesture detection + self.last_button = None + self.last_button_time = 0 + self.swipe_timeout = 0.4 # seconds + # Buttons - self.button1 = Button(0, pull_up=True, hold_time=1.5) - self.button2 = Button(1, pull_up=True, hold_time=1.5) - self.button1.when_pressed = self.on_button1_press - self.button1.when_held = self.on_button_held - self.button2.when_pressed = self.on_button2_press - self.button2.when_held = self.on_button_held + self.button0 = Button(0, pull_up=True, hold_time=1.0) + self.button1 = Button(1, pull_up=True, hold_time=1.0) + + self.button0.when_pressed = lambda: self.on_button_press(0) + self.button1.when_pressed = lambda: self.on_button_press(1) + self.button0.when_held = self.on_button0_held + self.button1.when_held = self.on_button1_held def show_feedback(self, message, duration=2): self.feedback_message = message self.feedback_until = time.time() + duration - def on_button1_press(self): - if not self.button1.is_held: - self.current_view = (self.current_view + 1) % NUM_VIEWS - self.last_view_change = time.time() + def on_button_press(self, button_id): + """Handle button press for swipe detection.""" + now = time.time() + + # Check for swipe gesture + if self.last_button is not None and (now - self.last_button_time) < self.swipe_timeout: + if self.last_button == 0 and button_id == 1: + # Swipe 0→1 + if self.menu_mode: + self.menu_selection = (self.menu_selection - 1) % NUM_VIEWS # Up + else: + self.current_view = (self.current_view + 1) % NUM_VIEWS # Next + self.last_view_change = now + self.show_feedback(VIEW_NAMES[self.current_view], 1.5) + self.last_button = None + return + elif self.last_button == 1 and button_id == 0: + # Swipe 1→0 + if self.menu_mode: + self.menu_selection = (self.menu_selection + 1) % NUM_VIEWS # Down + else: + self.current_view = (self.current_view - 1) % NUM_VIEWS # Prev + self.last_view_change = now + self.show_feedback(VIEW_NAMES[self.current_view], 1.5) + self.last_button = None + return + + # Single button press (for menu select) + if self.menu_mode and button_id == 1: + # Select highlighted view + self.current_view = self.menu_selection + self.menu_mode = False self.show_feedback(VIEW_NAMES[self.current_view], 1.5) + self.last_button = None + return + + self.last_button = button_id + self.last_button_time = now - def on_button2_press(self): - if not self.button2.is_held: - self.current_view = (self.current_view - 1) % NUM_VIEWS - self.last_view_change = time.time() - self.show_feedback(VIEW_NAMES[self.current_view], 1.5) + def on_button0_held(self): + """Long press button 0 = open menu.""" + self.menu_mode = not self.menu_mode + if self.menu_mode: + self.menu_selection = self.current_view + self.show_feedback("Menú", 1) + else: + self.show_feedback("Cerrado", 1) + self.last_button = None - def on_button_held(self): - self.auto_cycle = not self.auto_cycle - self.show_feedback("AUTO ON" if self.auto_cycle else "AUTO OFF", 2) + def on_button1_held(self): + """Long press button 1 = toggle auto-cycle.""" + if not self.menu_mode: + self.auto_cycle = not self.auto_cycle + self.show_feedback("AUTO ON" if self.auto_cycle else "AUTO OFF", 2) + self.last_button = None def update_data(self): try: @@ -242,6 +292,57 @@ class Matrix64Display(SampleBase): x = (64 - msg_len) // 2 graphics.DrawText(canvas, time_font, x, 38, msg_color, self.feedback_message) + def draw_menu(self, canvas, fonts, colors): + """Draw view selection menu with scrolling.""" + time_font, data_font, small_font = fonts + time_color, humidity_color, hdd_color, label_color, line_color = colors + + # Full black background + for y in range(64): + graphics.DrawLine(canvas, 0, y, 63, y, graphics.Color(0, 0, 0)) + + # Title + title = "Vistas" + title_len = len(title) * 5 + title_x = (64 - title_len) // 2 + graphics.DrawText(canvas, data_font, title_x, 8, time_color, title) + graphics.DrawLine(canvas, 0, 10, 63, 10, line_color) + + # Menu items (3 visible at a time, selected centered) + visible_items = 4 + item_height = 12 + start_y = 22 + + # Calculate scroll offset to keep selection centered + center_slot = visible_items // 2 + scroll_offset = max(0, min(self.menu_selection - center_slot, NUM_VIEWS - visible_items)) + + for i in range(visible_items): + view_idx = scroll_offset + i + if view_idx >= NUM_VIEWS: + break + + y = start_y + i * item_height + name = VIEW_NAMES[view_idx] + + if view_idx == self.menu_selection: + # Highlight selected + highlight_color = graphics.Color(0, 40, 80) + for hy in range(y - 9, y + 2): + if 0 <= hy < 64: + graphics.DrawLine(canvas, 0, hy, 63, hy, highlight_color) + text_color = graphics.Color(100, 255, 200) + graphics.DrawText(canvas, data_font, 4, y, text_color, "> " + name) + else: + text_color = graphics.Color(100, 100, 100) + graphics.DrawText(canvas, data_font, 8, y, text_color, name) + + # Scroll indicators + if scroll_offset > 0: + graphics.DrawText(canvas, small_font, 58, 18, label_color, "^") + if scroll_offset + visible_items < NUM_VIEWS: + graphics.DrawText(canvas, small_font, 58, 62, label_color, "v") + def draw_view_0(self, canvas, fonts, colors): """Vista Principal""" time_font, data_font, small_font = fonts @@ -439,20 +540,25 @@ class Matrix64Display(SampleBase): canvas.Clear() - if self.current_view == 0: - self.draw_view_0(canvas, fonts, colors) - elif self.current_view == 1: - self.draw_view_1(canvas, fonts, colors) - elif self.current_view == 2: - self.draw_view_2(canvas, fonts, colors) - elif self.current_view == 3: - self.draw_view_3(canvas, fonts, colors) + # Menu mode takes priority over views + if self.menu_mode: + self.draw_menu(canvas, fonts, colors) + else: + # Draw current view + if self.current_view == 0: + self.draw_view_0(canvas, fonts, colors) + elif self.current_view == 1: + self.draw_view_1(canvas, fonts, colors) + elif self.current_view == 2: + self.draw_view_2(canvas, fonts, colors) + elif self.current_view == 3: + self.draw_view_3(canvas, fonts, colors) # Camera alert overlay (highest priority) if self.mqtt.is_alert_active(): self.draw_camera_alert(canvas, fonts) - # Feedback overlay - elif self.feedback_message and current_time < self.feedback_until: + # Feedback overlay (only when not in menu) + elif not self.menu_mode and self.feedback_message and current_time < self.feedback_until: self.draw_feedback(canvas, fonts, colors) else: self.feedback_message = None diff --git a/mqtt_listener.py b/mqtt_listener.py index 2fdb013..51ec75d 100644 --- a/mqtt_listener.py +++ b/mqtt_listener.py @@ -26,9 +26,22 @@ class MQTTListener: print(f"MQTT connection failed: {rc}") def on_message(self, client, userdata, msg): - print(f"MQTT Alert received: {msg.topic}") - self.alert_active = True - self.alert_until = time.time() + self.alert_duration + print(f"MQTT message received: {msg.topic}") + try: + import json + payload = json.loads(msg.payload.decode()) + if payload.get("alert") == True: + print("Camera alert triggered") + self.alert_active = True + self.alert_until = time.time() + self.alert_duration + elif payload.get("alert") == False: + print("Camera alert cleared") + # Don't immediately clear, let it timeout naturally + except: + # Fallback: if not JSON or parsing fails, still trigger alert + print("MQTT payload parse failed, triggering alert anyway") + self.alert_active = True + self.alert_until = time.time() + self.alert_duration def on_disconnect(self, client, userdata, rc): print("MQTT disconnected")