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>
99 lines
3.8 KiB
Python
99 lines
3.8 KiB
Python
#!/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()
|