From 580029730a660769e280a7f4fa3b979f17a43f1d Mon Sep 17 00:00:00 2001 From: Eskimue Date: Thu, 28 May 2026 14:52:28 +0200 Subject: [PATCH] IMU eingebaut --- README.md | 11 ++- gui/admin.html | 68 +++++++++++++++++++ scripts/exomy_admin_api.py | 134 ++++++++++++++++++++++++++++++++++++- 3 files changed, 210 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 91f268a..d99ab37 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,17 @@ Die Software liegt in diesem Repository direkt an der Wurzel. Nicht zur Software | **Hostname** | `cuno` | | **Benutzer** | `pi` | | **SSH-Passwort** | `Sonneberg` | -| **LAN-IP** | `192.168.1.83` | -| **WLAN-IP** | `192.168.1.9` | +| **LAN-IP (`eskimue.de`)** | `192.168.1.83` | +| **WLAN-IP (`eskimue.de`)** | `192.168.1.9` | +| **WLAN-IP (`4pi`, SSH)** | `10.42.44.150` | | **Fallback-AP** | SSID `CUNO`, IP `192.168.50.1`, Passwort `astr0cun042` | ```bash +# im WLAN "eskimue.de" ssh pi@192.168.1.9 + +# im WLAN "4pi" +ssh pi@10.42.44.150 ``` --- @@ -134,6 +139,8 @@ ssh pi@192.168.1.9 "docker cp /home/pi/ExoMy_Software/src/meine_datei.py exomy_a ssh pi@192.168.1.9 "docker restart exomy_autostart" ``` +Im WLAN `4pi` dieselben Befehle jeweils mit `10.42.44.150` statt `192.168.1.9` verwenden. + GUI-Dateien: ```bash diff --git a/gui/admin.html b/gui/admin.html index 4a8a78e..966e7e8 100644 --- a/gui/admin.html +++ b/gui/admin.html @@ -96,6 +96,49 @@ - + +
+
+
+ IMU BNO085 + I2C 0x4A +
+
+ + Status + - +
+
+ + Adresse + - +
+
+ + Accel + - +
+
+ + Gyro + - +
+
+ + Mag + - +
+
+ + Quaternion + - +
+
+ + Hinweis + - +
+
@@ -934,6 +977,13 @@ setServiceState('camera_service_state', '-'); setServiceState('service_admin_api', '-'); setServiceState('service_exomy', '-'); + setStateText('imu_state', 'unknown', '-'); + setText('imu_address', '--'); + setText('imu_acceleration', '--'); + setText('imu_gyro', '--'); + setText('imu_magnetic', '--'); + setText('imu_quaternion', '--'); + setText('imu_error', '--'); var bar = document.getElementById('bar-temp-adm'); if (bar) bar.style.width = '0%'; } @@ -1147,6 +1197,16 @@ node.classList.add('state_unknown'); } + function setStateText(id, state, text) { + var node = document.getElementById(id); + if (!node) return; + node.textContent = text || '-'; + node.classList.remove('state_active', 'state_inactive', 'state_unknown'); + if (state === 'active' || state === 'running') { node.classList.add('state_active'); return; } + if (state === 'inactive' || state === 'failed' || state === 'exited') { node.classList.add('state_inactive'); return; } + node.classList.add('state_unknown'); + } + function formatNumber(value, digits) { return Number(value || 0).toLocaleString('de-DE', { minimumFractionDigits: digits, @@ -2428,6 +2488,7 @@ function renderStatus(data) { var system = data.system || {}; var services = data.services || {}; + var imu = data.imu || {}; setAdminStatus(data.status || 'Bereit'); setText('info_ips', system.ips); setText('info_wifi', system.wifi); @@ -2463,6 +2524,13 @@ setServiceState('camera_service_state', services.camera); setServiceState('service_admin_api', services.admin_api); setServiceState('service_exomy', services.exomy); + setStateText('imu_state', imu.state || 'unknown', imu.status || '-'); + setText('imu_address', imu.address); + setText('imu_acceleration', imu.acceleration); + setText('imu_gyro', imu.gyro); + setText('imu_magnetic', imu.magnetic); + setText('imu_quaternion', imu.quaternion); + setText('imu_error', imu.error || '-'); } async function fetchStatus() { diff --git a/scripts/exomy_admin_api.py b/scripts/exomy_admin_api.py index 7cbf13b..29899cb 100644 --- a/scripts/exomy_admin_api.py +++ b/scripts/exomy_admin_api.py @@ -14,6 +14,7 @@ import subprocess import termios import threading import time +import warnings from zoneinfo import ZoneInfo PORT = 8082 @@ -66,6 +67,11 @@ CAMERA_PROFILE_MAP = {profile['id']: profile for profile in CAMERA_PROFILES} DEFAULT_CAMERA_PROFILE_ID = '1296x972' CAMERA_FPS_OPTIONS = [1, 2, 3, 4, 5, 7, 10, 20] DEFAULT_CAMERA_FPS = 10 +IMU_I2C_ADDRESS = '0x4A' +IMU_LOCK = threading.Lock() +IMU_SENSOR = None +IMU_LAST_ERROR = None +IMU_WORKAROUND_APPLIED = False class ReusableThreadingTCPServer(socketserver.ThreadingTCPServer): @@ -77,6 +83,130 @@ def run_command(command): return result.stdout.strip(), result.stderr.strip(), result.returncode +def _format_vector(values, unit, decimals=2): + if not values: + return 'unbekannt' + return 'X {0:.{d}f} | Y {1:.{d}f} | Z {2:.{d}f} {u}'.format( + values[0], values[1], values[2], d=decimals, u=unit + ) + + +def _format_quaternion(values): + if not values: + return 'unbekannt' + return 'I {0:.3f} | J {1:.3f} | K {2:.3f} | R {3:.3f}'.format( + values[0], values[1], values[2], values[3] + ) + + +def _apply_bno08x_workaround(module): + global IMU_WORKAROUND_APPLIED + + if IMU_WORKAROUND_APPLIED: + return + + original_handle_packet = module.BNO08X._handle_packet + + def safe_handle_packet(self, packet): + try: + return original_handle_packet(self, packet) + except KeyError as exc: + # Some BNO085 firmwares emit undocumented reports that the Adafruit + # library cannot decode yet. Ignore those packets and keep known + # sensor reports flowing. + if len(exc.args) == 1 and isinstance(exc.args[0], int): + return + raise + + module.BNO08X._handle_packet = safe_handle_packet + IMU_WORKAROUND_APPLIED = True + + +def _create_imu_sensor(): + global IMU_LAST_ERROR + + try: + with warnings.catch_warnings(): + warnings.filterwarnings( + 'ignore', + message='I2C frequency is not settable in python, ignoring!', + category=RuntimeWarning, + ) + import adafruit_bno08x + import board + import busio + from adafruit_bno08x import ( + BNO_REPORT_ACCELEROMETER, + BNO_REPORT_GYROSCOPE, + BNO_REPORT_MAGNETOMETER, + BNO_REPORT_ROTATION_VECTOR, + ) + from adafruit_bno08x.i2c import BNO08X_I2C + + _apply_bno08x_workaround(adafruit_bno08x) + + i2c = busio.I2C(board.SCL, board.SDA, frequency=400000) + sensor = BNO08X_I2C(i2c) + for feature in ( + BNO_REPORT_ACCELEROMETER, + BNO_REPORT_GYROSCOPE, + BNO_REPORT_MAGNETOMETER, + BNO_REPORT_ROTATION_VECTOR, + ): + sensor.enable_feature(feature) + time.sleep(0.2) + IMU_LAST_ERROR = None + return sensor + except Exception as exc: + IMU_LAST_ERROR = str(exc) + return None + + +def read_imu_status(): + global IMU_SENSOR, IMU_LAST_ERROR + + with IMU_LOCK: + if IMU_SENSOR is None: + IMU_SENSOR = _create_imu_sensor() + + if IMU_SENSOR is None: + return { + 'state': 'unknown', + 'status': 'Nicht bereit', + 'address': IMU_I2C_ADDRESS, + 'acceleration': 'unbekannt', + 'gyro': 'unbekannt', + 'magnetic': 'unbekannt', + 'quaternion': 'unbekannt', + 'error': IMU_LAST_ERROR or 'BNO085 nicht verfugbar', + } + + try: + return { + 'state': 'active', + 'status': 'Verbunden', + 'address': IMU_I2C_ADDRESS, + 'acceleration': _format_vector(IMU_SENSOR.acceleration, 'm/s^2'), + 'gyro': _format_vector(IMU_SENSOR.gyro, 'rad/s', 3), + 'magnetic': _format_vector(IMU_SENSOR.magnetic, 'uT'), + 'quaternion': _format_quaternion(IMU_SENSOR.quaternion), + 'error': '', + } + except Exception as exc: + IMU_SENSOR = None + IMU_LAST_ERROR = str(exc) + return { + 'state': 'unknown', + 'status': 'Lesefehler', + 'address': IMU_I2C_ADDRESS, + 'acceleration': 'unbekannt', + 'gyro': 'unbekannt', + 'magnetic': 'unbekannt', + 'quaternion': 'unbekannt', + 'error': IMU_LAST_ERROR, + } + + def read_cpu_temperature(): try: with open('/sys/class/thermal/thermal_zone0/temp', 'r', encoding='utf-8') as handle: @@ -523,6 +653,7 @@ def collect_status(): wifi_details = get_wifi_details() time_details = get_system_time_details() container_details = get_container_details(EXOMY_CONTAINER) + imu_details = read_imu_status() return { 'status': 'Bereit', 'system': { @@ -563,7 +694,8 @@ def collect_status(): 'exomy': get_container_state(EXOMY_CONTAINER), 'exomy_uptime': container_details['uptime'], 'exomy_restarts': container_details['restarts'], - } + }, + 'imu': imu_details, }