Swipe gesture controls, menu system, MQTT alert parameter check
This commit is contained in:
142
matrix.py
142
matrix.py
@@ -146,33 +146,83 @@ class Matrix64Display(SampleBase):
|
|||||||
self.mqtt = MQTTListener()
|
self.mqtt = MQTTListener()
|
||||||
self.mqtt.start()
|
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
|
# Buttons
|
||||||
self.button1 = Button(0, pull_up=True, hold_time=1.5)
|
self.button0 = Button(0, pull_up=True, hold_time=1.0)
|
||||||
self.button2 = Button(1, pull_up=True, hold_time=1.5)
|
self.button1 = Button(1, pull_up=True, hold_time=1.0)
|
||||||
self.button1.when_pressed = self.on_button1_press
|
|
||||||
self.button1.when_held = self.on_button_held
|
self.button0.when_pressed = lambda: self.on_button_press(0)
|
||||||
self.button2.when_pressed = self.on_button2_press
|
self.button1.when_pressed = lambda: self.on_button_press(1)
|
||||||
self.button2.when_held = self.on_button_held
|
self.button0.when_held = self.on_button0_held
|
||||||
|
self.button1.when_held = self.on_button1_held
|
||||||
|
|
||||||
def show_feedback(self, message, duration=2):
|
def show_feedback(self, message, duration=2):
|
||||||
self.feedback_message = message
|
self.feedback_message = message
|
||||||
self.feedback_until = time.time() + duration
|
self.feedback_until = time.time() + duration
|
||||||
|
|
||||||
def on_button1_press(self):
|
def on_button_press(self, button_id):
|
||||||
if not self.button1.is_held:
|
"""Handle button press for swipe detection."""
|
||||||
self.current_view = (self.current_view + 1) % NUM_VIEWS
|
now = time.time()
|
||||||
self.last_view_change = time.time()
|
|
||||||
self.show_feedback(VIEW_NAMES[self.current_view], 1.5)
|
|
||||||
|
|
||||||
def on_button2_press(self):
|
# Check for swipe gesture
|
||||||
if not self.button2.is_held:
|
if self.last_button is not None and (now - self.last_button_time) < self.swipe_timeout:
|
||||||
self.current_view = (self.current_view - 1) % NUM_VIEWS
|
if self.last_button == 0 and button_id == 1:
|
||||||
self.last_view_change = time.time()
|
# 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.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
|
||||||
|
|
||||||
def on_button_held(self):
|
# 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_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_button1_held(self):
|
||||||
|
"""Long press button 1 = toggle auto-cycle."""
|
||||||
|
if not self.menu_mode:
|
||||||
self.auto_cycle = not self.auto_cycle
|
self.auto_cycle = not self.auto_cycle
|
||||||
self.show_feedback("AUTO ON" if self.auto_cycle else "AUTO OFF", 2)
|
self.show_feedback("AUTO ON" if self.auto_cycle else "AUTO OFF", 2)
|
||||||
|
self.last_button = None
|
||||||
|
|
||||||
def update_data(self):
|
def update_data(self):
|
||||||
try:
|
try:
|
||||||
@@ -242,6 +292,57 @@ class Matrix64Display(SampleBase):
|
|||||||
x = (64 - msg_len) // 2
|
x = (64 - msg_len) // 2
|
||||||
graphics.DrawText(canvas, time_font, x, 38, msg_color, self.feedback_message)
|
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):
|
def draw_view_0(self, canvas, fonts, colors):
|
||||||
"""Vista Principal"""
|
"""Vista Principal"""
|
||||||
time_font, data_font, small_font = fonts
|
time_font, data_font, small_font = fonts
|
||||||
@@ -439,6 +540,11 @@ class Matrix64Display(SampleBase):
|
|||||||
|
|
||||||
canvas.Clear()
|
canvas.Clear()
|
||||||
|
|
||||||
|
# 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:
|
if self.current_view == 0:
|
||||||
self.draw_view_0(canvas, fonts, colors)
|
self.draw_view_0(canvas, fonts, colors)
|
||||||
elif self.current_view == 1:
|
elif self.current_view == 1:
|
||||||
@@ -451,8 +557,8 @@ class Matrix64Display(SampleBase):
|
|||||||
# Camera alert overlay (highest priority)
|
# Camera alert overlay (highest priority)
|
||||||
if self.mqtt.is_alert_active():
|
if self.mqtt.is_alert_active():
|
||||||
self.draw_camera_alert(canvas, fonts)
|
self.draw_camera_alert(canvas, fonts)
|
||||||
# Feedback overlay
|
# Feedback overlay (only when not in menu)
|
||||||
elif self.feedback_message and current_time < self.feedback_until:
|
elif not self.menu_mode and self.feedback_message and current_time < self.feedback_until:
|
||||||
self.draw_feedback(canvas, fonts, colors)
|
self.draw_feedback(canvas, fonts, colors)
|
||||||
else:
|
else:
|
||||||
self.feedback_message = None
|
self.feedback_message = None
|
||||||
|
|||||||
@@ -26,7 +26,20 @@ class MQTTListener:
|
|||||||
print(f"MQTT connection failed: {rc}")
|
print(f"MQTT connection failed: {rc}")
|
||||||
|
|
||||||
def on_message(self, client, userdata, msg):
|
def on_message(self, client, userdata, msg):
|
||||||
print(f"MQTT Alert received: {msg.topic}")
|
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_active = True
|
||||||
self.alert_until = time.time() + self.alert_duration
|
self.alert_until = time.time() + self.alert_duration
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user