Compare commits
No commits in common. "b04fd916ff9b467669ade6e47b91cd392e145b3f" and "a8a9771ec701ee0d7653a9c242497075c9e09853" have entirely different histories.
b04fd916ff
...
a8a9771ec7
@ -1,20 +0,0 @@
|
|||||||
# PulseAudio Configuration for MageDok HDMI Audio
|
|
||||||
# Routes HDMI audio from DisplayPort adapter to internal speaker output
|
|
||||||
|
|
||||||
# Detect and load HDMI output module
|
|
||||||
load-module module-alsa-sink device=hw:0,3 sink_name=hdmi_stereo sink_properties="device.description='HDMI Audio'"
|
|
||||||
|
|
||||||
# Detect and configure internal speaker (fallback)
|
|
||||||
load-module module-alsa-sink device=hw:0,0 sink_name=speaker_mono sink_properties="device.description='Speaker'"
|
|
||||||
|
|
||||||
# Set HDMI as default output sink
|
|
||||||
set-default-sink hdmi_stereo
|
|
||||||
|
|
||||||
# Enable volume control
|
|
||||||
load-module module-volume-restore
|
|
||||||
|
|
||||||
# Auto-switch to HDMI when connected
|
|
||||||
load-module module-switch-on-connect
|
|
||||||
|
|
||||||
# Log sink configuration
|
|
||||||
.load-if-exists /etc/pulse/magedok-routing.conf
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
# X11 Configuration for MageDok 7" Display
|
|
||||||
# Resolution: 1024×600 @ 60Hz
|
|
||||||
# Output: HDMI via DisplayPort adapter
|
|
||||||
|
|
||||||
Section "Monitor"
|
|
||||||
Identifier "MageDok"
|
|
||||||
Option "PreferredMode" "1024x600_60.00"
|
|
||||||
Option "Position" "0 0"
|
|
||||||
Option "Primary" "true"
|
|
||||||
EndSection
|
|
||||||
|
|
||||||
Section "Screen"
|
|
||||||
Identifier "Screen0"
|
|
||||||
Monitor "MageDok"
|
|
||||||
DefaultDepth 24
|
|
||||||
SubSection "Display"
|
|
||||||
Depth 24
|
|
||||||
Modes "1024x600" "1024x768" "800x600" "640x480"
|
|
||||||
EndSubSection
|
|
||||||
EndSection
|
|
||||||
|
|
||||||
Section "Device"
|
|
||||||
Identifier "NVIDIA Tegra"
|
|
||||||
Driver "nvidia"
|
|
||||||
BusID "PCI:0:0:0"
|
|
||||||
Option "RegistryDwords" "EnableBrightnessControl=1"
|
|
||||||
Option "ConnectedMonitor" "HDMI-0"
|
|
||||||
EndSection
|
|
||||||
|
|
||||||
Section "ServerLayout"
|
|
||||||
Identifier "Default"
|
|
||||||
Screen "Screen0"
|
|
||||||
EndSection
|
|
||||||
@ -1,218 +0,0 @@
|
|||||||
# MageDok 7" Touchscreen Display Setup
|
|
||||||
|
|
||||||
Issue #369: Display setup for MageDok 7" IPS touchscreen on Jetson Orin Nano.
|
|
||||||
|
|
||||||
## Hardware Setup
|
|
||||||
|
|
||||||
### Connections
|
|
||||||
- **Video**: DisplayPort → HDMI cable from Orin DP 1.2 connector to MageDok HDMI input
|
|
||||||
- **Touch**: USB 3.0 cable from Orin USB-A to MageDok USB-C connector
|
|
||||||
- **Audio**: HDMI carries embedded audio from DisplayPort (no separate audio cable needed)
|
|
||||||
|
|
||||||
### Display Specs
|
|
||||||
- **Resolution**: 1024×600 @ 60Hz
|
|
||||||
- **Panel Type**: 7" IPS (In-Plane Switching) - wide viewing angles
|
|
||||||
- **Sunlight Readable**: Yes, with high brightness
|
|
||||||
- **Built-in Speakers**: Yes (via HDMI audio)
|
|
||||||
|
|
||||||
## Installation Steps
|
|
||||||
|
|
||||||
### 1. Kernel and Display Driver Configuration
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Update display mode database (if needed)
|
|
||||||
sudo apt-get update && sudo apt-get install -y xrandr x11-utils edid-decode
|
|
||||||
|
|
||||||
# Verify X11 is running
|
|
||||||
echo $DISPLAY # Should show :0 or :1
|
|
||||||
|
|
||||||
# Check connected displays
|
|
||||||
xrandr --query
|
|
||||||
```
|
|
||||||
|
|
||||||
**Expected output**: HDMI-1 connected at 1024x600 resolution
|
|
||||||
|
|
||||||
### 2. Install udev Rules for Touch Input
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Copy udev rules
|
|
||||||
sudo cp jetson/ros2_ws/src/saltybot_bringup/udev/90-magedok-touch.rules \
|
|
||||||
/etc/udev/rules.d/
|
|
||||||
|
|
||||||
# Reload udev
|
|
||||||
sudo udevadm control --reload-rules
|
|
||||||
sudo udevadm trigger
|
|
||||||
|
|
||||||
# Verify touch device
|
|
||||||
ls -l /dev/magedok-touch
|
|
||||||
# Or check input devices
|
|
||||||
cat /proc/bus/input/devices | grep -i "eGTouch\|EETI"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. X11 Display Configuration
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Backup original X11 config
|
|
||||||
sudo cp /etc/X11/xorg.conf /etc/X11/xorg.conf.backup
|
|
||||||
|
|
||||||
# Apply MageDok X11 config
|
|
||||||
sudo cp jetson/ros2_ws/src/saltybot_bringup/config/xorg-magedok.conf \
|
|
||||||
/etc/X11/xorg.conf
|
|
||||||
|
|
||||||
# Restart X11 (or reboot)
|
|
||||||
sudo systemctl restart gdm3 # or startx if using console
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. PulseAudio Audio Routing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check current audio sinks
|
|
||||||
pactl list sinks | grep Name
|
|
||||||
|
|
||||||
# Find HDMI sink (typically contains "hdmi" in name)
|
|
||||||
pactl set-default-sink <hdmi-sink-name>
|
|
||||||
|
|
||||||
# Verify routing
|
|
||||||
pactl get-default-sink
|
|
||||||
|
|
||||||
# Optional: Set volume
|
|
||||||
pactl set-sink-volume <sink-name> 70%
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. ROS2 Launch Configuration
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build the saltybot_bringup package
|
|
||||||
cd jetson/ros2_ws
|
|
||||||
colcon build --packages-select saltybot_bringup
|
|
||||||
|
|
||||||
# Source workspace
|
|
||||||
source install/setup.bash
|
|
||||||
|
|
||||||
# Launch display setup
|
|
||||||
ros2 launch saltybot_bringup magedok_display.launch.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. Enable Auto-Start on Boot
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Copy systemd service
|
|
||||||
sudo cp jetson/ros2_ws/src/saltybot_bringup/systemd/magedok-display.service \
|
|
||||||
/etc/systemd/system/
|
|
||||||
|
|
||||||
# Enable service
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
sudo systemctl enable magedok-display.service
|
|
||||||
|
|
||||||
# Start service
|
|
||||||
sudo systemctl start magedok-display.service
|
|
||||||
|
|
||||||
# Check status
|
|
||||||
sudo systemctl status magedok-display.service
|
|
||||||
sudo journalctl -u magedok-display -f # Follow logs
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
### Display Resolution
|
|
||||||
```bash
|
|
||||||
# Check actual resolution
|
|
||||||
xdotool getactivewindow getwindowgeometry
|
|
||||||
|
|
||||||
# Verify with xrandr
|
|
||||||
xrandr | grep "1024x600"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Expected**: `1024x600_60.00 +0+0` or similar
|
|
||||||
|
|
||||||
### Touch Input
|
|
||||||
```bash
|
|
||||||
# List input devices
|
|
||||||
xinput list
|
|
||||||
|
|
||||||
# Should show "MageDok Touch" or "eGTouch Controller"
|
|
||||||
# Test touch by clicking on display - cursor should move
|
|
||||||
```
|
|
||||||
|
|
||||||
### Audio
|
|
||||||
```bash
|
|
||||||
# Test HDMI audio
|
|
||||||
speaker-test -c 2 -l 1 -s 1 -t sine
|
|
||||||
|
|
||||||
# Verify volume level
|
|
||||||
pactl list sinks | grep -A 10 RUNNING
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Display Not Detected
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check EDID data
|
|
||||||
edid-decode /sys/class/drm/card0-HDMI-A-1/edid
|
|
||||||
|
|
||||||
# Force resolution
|
|
||||||
xrandr --output HDMI-1 --mode 1024x600 --rate 60
|
|
||||||
|
|
||||||
# Check kernel logs
|
|
||||||
dmesg | grep -i "drm\|HDMI\|dp"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Touch Not Working
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check USB connection
|
|
||||||
lsusb | grep -i "eGTouch\|EETI"
|
|
||||||
|
|
||||||
# Verify udev rules applied
|
|
||||||
cat /etc/udev/rules.d/90-magedok-touch.rules
|
|
||||||
|
|
||||||
# Test touch device directly
|
|
||||||
evtest /dev/magedok-touch # Or /dev/input/eventX
|
|
||||||
```
|
|
||||||
|
|
||||||
### Audio Not Routing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check PulseAudio daemon
|
|
||||||
pulseaudio --version
|
|
||||||
systemctl status pulseaudio
|
|
||||||
|
|
||||||
# Restart PulseAudio
|
|
||||||
systemctl --user restart pulseaudio
|
|
||||||
|
|
||||||
# Monitor audio stream
|
|
||||||
pactl list sink-inputs
|
|
||||||
```
|
|
||||||
|
|
||||||
### Display Disconnection (Headless Fallback)
|
|
||||||
|
|
||||||
The system should continue operating normally with display disconnected:
|
|
||||||
- ROS2 services remain accessible via network
|
|
||||||
- Robot commands via `/cmd_vel` continue working
|
|
||||||
- Data logging and telemetry unaffected
|
|
||||||
- Dashboard accessible via SSH/webui from other machine
|
|
||||||
|
|
||||||
## Testing Checklist
|
|
||||||
|
|
||||||
- [ ] Display shows 1024×600 resolution
|
|
||||||
- [ ] Touch input registers in xinput (test by moving cursor)
|
|
||||||
- [ ] Audio plays through display speakers
|
|
||||||
- [ ] System boots without login prompt (if using auto-start)
|
|
||||||
- [ ] All ROS2 nodes launch correctly with display
|
|
||||||
- [ ] System operates normally when display is disconnected
|
|
||||||
- [ ] `/magedok/touch_status` topic shows true (ROS2 verify script)
|
|
||||||
- [ ] `/magedok/audio_status` topic shows HDMI sink (ROS2 audio router)
|
|
||||||
|
|
||||||
## Related Issues
|
|
||||||
|
|
||||||
- **#368**: Salty Face UI (depends on this display setup)
|
|
||||||
- **#370**: Animated expression UI
|
|
||||||
- **#371**: Deaf/accessibility mode with touch keyboard
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- MageDok 7" Specs: [HDMI, 1024×600, USB Touch, Built-in Speakers]
|
|
||||||
- Jetson Orin Nano DisplayPort Output: Requires active adapter (no DP Alt Mode on USB-C)
|
|
||||||
- PulseAudio: HDMI audio sink routing via ALSA
|
|
||||||
- X11/Xrandr: Display mode configuration
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
MageDok 7" Display Launch Configuration
|
|
||||||
- Video: DisplayPort → HDMI (1024×600)
|
|
||||||
- Touch: USB HID
|
|
||||||
- Audio: HDMI → internal speakers via PulseAudio
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
from launch import LaunchDescription
|
|
||||||
from launch_ros.actions import Node
|
|
||||||
from launch.actions import ExecuteProcess
|
|
||||||
|
|
||||||
|
|
||||||
def generate_launch_description():
|
|
||||||
return LaunchDescription([
|
|
||||||
# Log startup
|
|
||||||
ExecuteProcess(
|
|
||||||
cmd=['echo', '[MageDok] Display setup starting...'],
|
|
||||||
shell=True,
|
|
||||||
),
|
|
||||||
|
|
||||||
# Verify display resolution
|
|
||||||
Node(
|
|
||||||
package='saltybot_bringup',
|
|
||||||
executable='verify_display.py',
|
|
||||||
name='display_verifier',
|
|
||||||
parameters=[
|
|
||||||
{'target_width': 1024},
|
|
||||||
{'target_height': 600},
|
|
||||||
{'target_refresh': 60},
|
|
||||||
],
|
|
||||||
output='screen',
|
|
||||||
),
|
|
||||||
|
|
||||||
# Monitor touch input
|
|
||||||
Node(
|
|
||||||
package='saltybot_bringup',
|
|
||||||
executable='touch_monitor.py',
|
|
||||||
name='touch_monitor',
|
|
||||||
parameters=[
|
|
||||||
{'device_name': 'MageDok Touch'},
|
|
||||||
{'poll_interval': 0.1},
|
|
||||||
],
|
|
||||||
output='screen',
|
|
||||||
),
|
|
||||||
|
|
||||||
# Audio routing (PulseAudio sink redirection)
|
|
||||||
Node(
|
|
||||||
package='saltybot_bringup',
|
|
||||||
executable='audio_router.py',
|
|
||||||
name='audio_router',
|
|
||||||
parameters=[
|
|
||||||
{'hdmi_sink': 'alsa_output.pci-0000_00_1d.0.hdmi-stereo'},
|
|
||||||
{'default_sink': True},
|
|
||||||
],
|
|
||||||
output='screen',
|
|
||||||
),
|
|
||||||
])
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
#!/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()
|
|
||||||
@ -1,88 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
MageDok Touch Input Monitor
|
|
||||||
Verifies USB touch device is recognized and functional
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import rclpy
|
|
||||||
from rclpy.node import Node
|
|
||||||
from std_msgs.msg import String, Bool
|
|
||||||
|
|
||||||
|
|
||||||
class TouchMonitor(Node):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__('touch_monitor')
|
|
||||||
|
|
||||||
self.declare_parameter('device_name', 'MageDok Touch')
|
|
||||||
self.declare_parameter('poll_interval', 0.1)
|
|
||||||
|
|
||||||
self.device_name = self.get_parameter('device_name').value
|
|
||||||
self.poll_interval = self.get_parameter('poll_interval').value
|
|
||||||
|
|
||||||
self.touch_status_pub = self.create_publisher(Bool, '/magedok/touch_status', 10)
|
|
||||||
self.device_info_pub = self.create_publisher(String, '/magedok/device_info', 10)
|
|
||||||
|
|
||||||
self.get_logger().info(f'Touch Monitor: Scanning for {self.device_name}...')
|
|
||||||
self.detect_touch_device()
|
|
||||||
|
|
||||||
# Publish status every 2 seconds
|
|
||||||
self.create_timer(2.0, self.publish_status)
|
|
||||||
|
|
||||||
def detect_touch_device(self):
|
|
||||||
"""Detect MageDok touch device via USB"""
|
|
||||||
try:
|
|
||||||
# Check lsusb for MageDok or eGTouch device
|
|
||||||
result = subprocess.run(['lsusb'], capture_output=True, text=True, timeout=5)
|
|
||||||
lines = result.stdout.split('\n')
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
if 'eGTouch' in line or 'EETI' in line or 'MageDok' in line or 'touch' in line.lower():
|
|
||||||
self.get_logger().info(f'✓ Touch device found: {line.strip()}')
|
|
||||||
msg = String()
|
|
||||||
msg.data = line.strip()
|
|
||||||
self.device_info_pub.publish(msg)
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Fallback: check input devices
|
|
||||||
result = subprocess.run(['grep', '-l', 'eGTouch\|EETI\|MageDok', '/proc/bus/input/devices'],
|
|
||||||
capture_output=True, text=True, timeout=5)
|
|
||||||
if result.returncode == 0:
|
|
||||||
self.get_logger().info('✓ Touch device registered in /proc/bus/input/devices')
|
|
||||||
return True
|
|
||||||
|
|
||||||
self.get_logger().warn('⚠ Touch device not detected — ensure USB connection is secure')
|
|
||||||
return False
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.get_logger().error(f'Device detection failed: {e}')
|
|
||||||
return False
|
|
||||||
|
|
||||||
def publish_status(self):
|
|
||||||
"""Publish current touch device status"""
|
|
||||||
try:
|
|
||||||
result = subprocess.run(['ls', '/dev/magedok-touch'], capture_output=True, timeout=2)
|
|
||||||
status = Bool()
|
|
||||||
status.data = (result.returncode == 0)
|
|
||||||
self.touch_status_pub.publish(status)
|
|
||||||
|
|
||||||
if status.data:
|
|
||||||
self.get_logger().debug('Touch device: ACTIVE')
|
|
||||||
else:
|
|
||||||
self.get_logger().warn('Touch device: NOT DETECTED')
|
|
||||||
except Exception as e:
|
|
||||||
status = Bool()
|
|
||||||
status.data = False
|
|
||||||
self.touch_status_pub.publish(status)
|
|
||||||
|
|
||||||
|
|
||||||
def main(args=None):
|
|
||||||
rclpy.init(args=args)
|
|
||||||
monitor = TouchMonitor()
|
|
||||||
rclpy.spin(monitor)
|
|
||||||
rclpy.shutdown()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
#!/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()
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=MageDok 7" Display Setup and Auto-Launch
|
|
||||||
Documentation=https://gitea.vayrette.com/seb/saltylab-firmware/issues/369
|
|
||||||
After=network-online.target
|
|
||||||
Wants=network-online.target
|
|
||||||
ConditionPathExists=/dev/pts/0
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStartPre=/bin/sleep 2
|
|
||||||
ExecStart=/usr/bin/env bash -c 'source /opt/ros/jazzy/setup.bash && ros2 launch saltybot_bringup magedok_display.launch.py'
|
|
||||||
ExecStartPost=/usr/bin/env bash -c 'DISPLAY=:0 /usr/bin/startx -- :0 vt7 -nolisten tcp 2>/dev/null &'
|
|
||||||
|
|
||||||
StandardOutput=journal
|
|
||||||
StandardError=journal
|
|
||||||
SyslogIdentifier=magedok-display
|
|
||||||
User=orin
|
|
||||||
Group=orin
|
|
||||||
Environment="DISPLAY=:0"
|
|
||||||
Environment="XAUTHORITY=/home/orin/.Xauthority"
|
|
||||||
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=5
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
# MageDok 7" Touchscreen USB Device Rules
|
|
||||||
# Ensure touch device is recognized and accessible
|
|
||||||
|
|
||||||
# Generic USB touch input device (MageDok)
|
|
||||||
# Manufacturer typically reports as: EETI eGTouch Controller
|
|
||||||
SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="*eGTouch*", TAG="uaccess"
|
|
||||||
SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="*EETI*", TAG="uaccess"
|
|
||||||
SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="*MageDok*", TAG="uaccess"
|
|
||||||
|
|
||||||
# Fallback: Any USB device with touch capability (VID/PID may vary by batch)
|
|
||||||
SUBSYSTEM=="usb", ATTRS{bInterfaceClass}=="03", ATTRS{bInterfaceSubClass}=="01", TAG="uaccess"
|
|
||||||
|
|
||||||
# Create /dev/magedok-touch symlink for consistent reference
|
|
||||||
SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="*eGTouch*", SYMLINK="magedok-touch"
|
|
||||||
SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="*EETI*", SYMLINK="magedok-touch"
|
|
||||||
|
|
||||||
# Permissions: 0666 (rw for all users)
|
|
||||||
SUBSYSTEM=="input", KERNEL=="event*", MODE="0666"
|
|
||||||
SUBSYSTEM=="input", KERNEL=="mouse*", MODE="0666"
|
|
||||||
Loading…
x
Reference in New Issue
Block a user