Files
ExoMy_Cuno/scripts/imu_rotate.py
T
EskimueandClaude Sonnet 4.6 93772ec3b7 HUD-Farbe konfigurierbar, Neigungskalibrierung, Auto-Rotate verbessert
- HUD-Farbe per Farbwähler im Admin einstellbar (localStorage)
- Schwarze Kontur auf HUD-Elementen via CSS drop-shadow und canvas shadowBlur
- Himmelsrichtungen auf Deutsch (O statt E, etc.)
- Roll/Pitch-Neigung kann im Admin genullt werden (imu_tilt_calibration.json)
- Neigungsoffset wird im Kamera-Overlay berücksichtigt
- Auto-Rotate läuft jetzt nativ als rospy-Script im Docker-Container
- Auto-Rotate erkennt aktuellen Locomotion-Mode und stellt ihn danach wieder her
- Auto-Rotate pausiert Web-GUI-Joystick via localStorage-Flag
- Feste 20%-Geschwindigkeit für Auto-Rotate (vel=4, Expo-äquivalent)
- Vignette entfernt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 10:26:11 +02:00

113 lines
3.1 KiB
Python

"""
Startet auto_rotate_ros.py via docker exec im ExoMy-Container.
Das Script läuft dort nativ mit rospy — kein WebSocket-Overhead.
Öffentliche API:
start_rotate(target_deg, heading_offset, restore_mode) -> dict
stop_rotate()
get_rotate_status() -> dict
"""
import subprocess
import threading
import time
_CONTAINER = 'exomy_autostart'
_SCRIPT_PATH = '/root/exomy_ws/src/exomy/scripts/auto_rotate_ros.py'
_ROS_SETUP = 'source /opt/ros/melodic/setup.bash && source /root/exomy_ws/devel/setup.bash'
_lock = threading.Lock()
_proc = None # subprocess.Popen (docker exec)
_monitor_thread = None
_status = {
'running': False,
'target_deg': None,
'result': 'idle',
'message': '',
}
def _monitor(proc, target_deg, restore_mode):
"""Wartet auf Prozessende und aktualisiert Status."""
global _proc
returncode = proc.wait()
with _lock:
if _proc is proc: # nicht überschrieben durch neuen Start
_proc = None
_status['running'] = False
if returncode == 0:
_status['result'] = 'done'
_status['message'] = 'Ziel erreicht'
elif returncode == -15 or returncode == 143:
_status['result'] = 'aborted'
_status['message'] = 'Abgebrochen'
else:
_status['result'] = 'error'
_status['message'] = 'Fehler (exit {})'.format(returncode)
def start_rotate(target_deg, heading_offset, restore_mode=1, done_deg=1.0, vel=None):
global _proc, _monitor_thread, _status
stop_rotate()
target_deg = float(target_deg) % 360
if vel is None:
# Fest 20% Geschwindigkeit, unabhängig vom Admin-Speed-Limit
# Entspricht dem Joystick-Expo: 100 * (20/100)^2 = 4
vel = 4
cmd = [
'docker', 'exec', _CONTAINER, 'bash', '-c',
'{setup} && python {script} {target} {mode} {offset} {done} {vel}'.format(
setup=_ROS_SETUP,
script=_SCRIPT_PATH,
target=target_deg,
mode=int(restore_mode),
offset=float(heading_offset),
done=float(done_deg),
vel=int(vel),
)
]
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
with _lock:
_proc = proc
_status = {
'running': True,
'target_deg': round(target_deg, 1),
'result': 'running',
'message': 'Drehe zu {:.0f}°'.format(target_deg),
}
_monitor_thread = threading.Thread(
target=_monitor,
args=(proc, target_deg, restore_mode),
daemon=True,
)
_monitor_thread.start()
return get_rotate_status()
def stop_rotate():
global _proc
with _lock:
proc = _proc
_proc = None
if proc is not None and proc.poll() is None:
# SIGTERM an den docker-exec-Prozess → Container-Prozess beendet sich sauber
proc.terminate()
try:
proc.wait(timeout=3.0)
except subprocess.TimeoutExpired:
proc.kill()
def get_rotate_status():
with _lock:
return dict(_status)