sl-webui 45d456049a feat: MageDok 7in display setup for Jetson Orin (Issue #369)
Add complete display integration for MageDok 7" IPS touchscreen:

Configuration Files:
- X11 display config (xorg-magedok.conf) — 1024×600 @ 60Hz
- PulseAudio routing (pulseaudio-magedok.conf) — HDMI audio to speakers
- Udev rules (90-magedok-touch.rules) — USB touch device permissions
- Systemd service (magedok-display.service) — auto-start on boot

ROS2 Launch:
- magedok_display.launch.py — coordinate display/touch/audio setup

Helper Scripts:
- verify_display.py — validate 1024×600 resolution via xrandr
- touch_monitor.py — detect MageDok USB touch, publish status
- audio_router.py — configure PulseAudio HDMI sink routing

Documentation:
- MAGEDOK_DISPLAY_SETUP.md — complete installation and troubleshooting guide

Features:
✓ DisplayPort → HDMI video from Orin DP connector
✓ USB touch input as HID device (driver-free)
✓ HDMI audio routing to built-in speakers
✓ 1024×600 native resolution verification
✓ Systemd auto-launch on boot (no login prompt)
✓ Headless fallback when display disconnected
✓ ROS2 status monitoring (touch/audio/resolution)

Supports Salty Face UI (Issue #370) and accessibility features (Issue #371)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-03 15:44:03 -05:00

99 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
MageDok Display Verifier
Validates that the 7" display is running at 1024×600 resolution
"""
import os
import re
import subprocess
import rclpy
from rclpy.node import Node
class DisplayVerifier(Node):
def __init__(self):
super().__init__('display_verifier')
self.declare_parameter('target_width', 1024)
self.declare_parameter('target_height', 600)
self.declare_parameter('target_refresh', 60)
self.target_w = self.get_parameter('target_width').value
self.target_h = self.get_parameter('target_height').value
self.target_f = self.get_parameter('target_refresh').value
self.get_logger().info(f'Display Verifier: Target {self.target_w}×{self.target_h} @ {self.target_f}Hz')
self.verify_display()
def verify_display(self):
"""Check current display resolution via xdotool or xrandr"""
try:
# Try xrandr first
result = subprocess.run(['xrandr'], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
self.parse_xrandr(result.stdout)
else:
self.get_logger().warn('xrandr not available, checking edid-decode')
self.check_edid()
except Exception as e:
self.get_logger().error(f'Display verification failed: {e}')
def parse_xrandr(self, output):
"""Parse xrandr output to find active display resolution"""
lines = output.split('\n')
for line in lines:
# Look for connected display with resolution
if 'connected' in line and 'primary' in line:
# Example: "HDMI-1 connected primary 1024x600+0+0 (normal left inverted right)"
match = re.search(r'(\d+)x(\d+)', line)
if match:
width, height = int(match.group(1)), int(match.group(2))
self.verify_resolution(width, height)
return
self.get_logger().warn('Could not determine active display from xrandr')
def verify_resolution(self, current_w, current_h):
"""Validate resolution matches target"""
if current_w == self.target_w and current_h == self.target_h:
self.get_logger().info(f'✓ Display verified: {current_w}×{current_h} [OK]')
else:
self.get_logger().warn(f'⚠ Display mismatch: Expected {self.target_w}×{self.target_h}, got {current_w}×{current_h}')
self.attempt_set_resolution()
def attempt_set_resolution(self):
"""Try to set resolution via xrandr"""
try:
# Find HDMI output
result = subprocess.run(
['xrandr', '--output', 'HDMI-1', '--mode', f'{self.target_w}x{self.target_h}', '--rate', str(self.target_f)],
capture_output=True, text=True, timeout=5
)
if result.returncode == 0:
self.get_logger().info(f'✓ Resolution set to {self.target_w}×{self.target_h} @ {self.target_f}Hz')
else:
self.get_logger().warn(f'Resolution change failed: {result.stderr}')
except Exception as e:
self.get_logger().error(f'Could not set resolution: {e}')
def check_edid(self):
"""Fallback: check EDID (Extended Display ID) data"""
try:
result = subprocess.run(['edid-decode', '/sys/class/drm/card0-HDMI-A-1/edid'],
capture_output=True, text=True, timeout=5)
if 'Established timings' in result.stdout:
self.get_logger().info('Display EDID detected (MageDok 1024×600 display)')
except:
self.get_logger().warn('EDID check unavailable')
def main(args=None):
rclpy.init(args=args)
verifier = DisplayVerifier()
rclpy.shutdown()
if __name__ == '__main__':
main()