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

98 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""
MageDok Audio Router
Routes HDMI audio from DisplayPort adapter to internal speakers via PulseAudio
"""
import subprocess
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class AudioRouter(Node):
def __init__(self):
super().__init__('audio_router')
self.declare_parameter('hdmi_sink', 'alsa_output.pci-0000_00_1d.0.hdmi-stereo')
self.declare_parameter('default_sink', True)
self.hdmi_sink = self.get_parameter('hdmi_sink').value
self.set_default = self.get_parameter('default_sink').value
self.audio_status_pub = self.create_publisher(String, '/magedok/audio_status', 10)
self.get_logger().info('Audio Router: Configuring HDMI audio routing...')
self.setup_pulseaudio()
# Check status every 5 seconds
self.create_timer(5.0, self.check_audio_status)
def setup_pulseaudio(self):
"""Configure PulseAudio to route HDMI audio"""
try:
# List available sinks
result = subprocess.run(['pactl', 'list', 'sinks'], capture_output=True, text=True, timeout=5)
sinks = self._parse_pa_sinks(result.stdout)
if not sinks:
self.get_logger().warn('No PulseAudio sinks detected')
return
self.get_logger().info(f'Available sinks: {", ".join(sinks.keys())}')
# Find HDMI or use first available
hdmi_sink = None
for name in sinks.keys():
if 'hdmi' in name.lower() or 'HDMI' in name:
hdmi_sink = name
break
if not hdmi_sink:
hdmi_sink = list(sinks.keys())[0] # Fallback to first sink
self.get_logger().warn(f'HDMI sink not found, using: {hdmi_sink}')
else:
self.get_logger().info(f'✓ HDMI sink identified: {hdmi_sink}')
# Set as default if requested
if self.set_default:
subprocess.run(['pactl', 'set-default-sink', hdmi_sink], timeout=5)
self.get_logger().info(f'✓ Audio routed to: {hdmi_sink}')
except Exception as e:
self.get_logger().error(f'PulseAudio setup failed: {e}')
def _parse_pa_sinks(self, pactl_output):
"""Parse 'pactl list sinks' output"""
sinks = {}
current_sink = None
for line in pactl_output.split('\n'):
if line.startswith('Sink #'):
current_sink = line.split('#')[1].strip()
elif '\tName: ' in line and current_sink:
name = line.split('Name: ')[1].strip()
sinks[name] = current_sink
return sinks
def check_audio_status(self):
"""Verify audio is properly routed"""
try:
result = subprocess.run(['pactl', 'get-default-sink'], capture_output=True, text=True, timeout=5)
status = String()
status.data = result.stdout.strip()
self.audio_status_pub.publish(status)
self.get_logger().debug(f'Current audio sink: {status.data}')
except Exception as e:
self.get_logger().warn(f'Audio status check failed: {e}')
def main(args=None):
rclpy.init(args=args)
router = AudioRouter()
rclpy.spin(router)
rclpy.shutdown()
if __name__ == '__main__':
main()