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,
}