From 9406a749e53a681a8d70de5d64dc658b6ce27ca9 Mon Sep 17 00:00:00 2001 From: BiasF Date: Sun, 30 Aug 2026 15:12:30 +0200 Subject: [PATCH] =?UTF-8?q?Firmware=20des=20Neigungsmessers=20ins=20Projek?= =?UTF-8?q?t=20=C3=BCbernehmen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die ESPHome-Firmware lag bisher im eigenständigen Projekt VanAllign-Pro. Da die App der einzige Client ist, gehören Firmware und App zusammen: ändert sich die Bluetooth-Schnittstelle, muss beides gemeinsam angefasst werden. Übernommen sind die Firmware selbst und die beiden Experimente zu WLAN/BLE-Umschaltung und ESP-NOW, letztere nach experimente/ einsortiert und als nicht betriebsnotwendig gekennzeichnet. Die WebBLE-Oberfläche bleibt im Ursprungsprojekt - sie ist durch die App abgelöst und bringt 2 MB Bilder mit, die in einem iOS-Projekt nichts zu suchen haben. Die .gitignore schliesst jetzt die ESPHome-Build-Verzeichnisse aus; im Ursprungsprojekt fehlte das, dort liegen inzwischen 233 MB Bauartefakte neben den Quellen. Am neuen Ort mit esphome config gegengeprüft. Co-Authored-By: Claude Opus 5 --- .gitignore | 10 +- README.md | 7 + firmware/vanalign/README.md | 79 ++++++ firmware/vanalign/esp32_ble.yaml | 235 ++++++++++++++++++ .../experimente/esp32_EspNow-rec.yaml | 22 ++ .../vanalign/experimente/esp32_switch.yaml | 63 +++++ 6 files changed, 414 insertions(+), 2 deletions(-) create mode 100644 firmware/vanalign/README.md create mode 100644 firmware/vanalign/esp32_ble.yaml create mode 100644 firmware/vanalign/experimente/esp32_EspNow-rec.yaml create mode 100644 firmware/vanalign/experimente/esp32_switch.yaml diff --git a/.gitignore b/.gitignore index 7affa43..931948f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,8 @@ ._* # Custom folder icons -Icon +Icon + # Volume root files .DocumentRevisions-V100 @@ -62,4 +63,9 @@ timeline.xctimeline playground.xcworkspace # Swift Package Manager -# Package.resolved \ No newline at end of file +# Package.resolved +### ESPHome +# Build-Verzeichnis und Zustandsdaten; das Abbild wird aus der YAML erzeugt. +.esphome/ +firmware/**/.esphome/ +firmware/**/.pioenvs/ diff --git a/README.md b/README.md index dc21404..8b53dc9 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ iOS-App, die per Bluetooth LE die Energieanlage im Wohnmobil ausliest: Ein Victron SmartShunt/BMV wird ebenfalls unterstützt, falls später einer dazukommt. +Die Firmware des Neigungsmessers liegt mit im Projekt, siehe +[firmware/vanalign](firmware/vanalign/README.md). + ## Bauen und installieren ```bash @@ -170,6 +173,10 @@ und die Prüfsummen beider Daly-Dialekte. ## Aufbau ``` +firmware/vanalign/ Firmware des Neigungsmessers (ESPHome) +├── esp32_ble.yaml Neigungsmessung und Bluetooth-Schnittstelle +└── experimente/ Nicht für den Betrieb nötig + CamperMonitor/ ├── Models/ │ ├── Profile.swift Fahrzeug diff --git a/firmware/vanalign/README.md b/firmware/vanalign/README.md new file mode 100644 index 0000000..df64cb2 --- /dev/null +++ b/firmware/vanalign/README.md @@ -0,0 +1,79 @@ +# VanAlign Pro – Neigungsmessung für den Camper + +ESP32-S3 mit MPU6050, der Längs- und Querneigung des Fahrzeugs misst und über +Bluetooth bereitstellt. Zum Ausrichten beim Parken: die Anzeige zeigt, welche +Seite höher steht und wie weit es noch ist. + +> Teile dieses Projekts wurden mit KI-Unterstützung erstellt. + +## Firmware + +`esp32_ble.yaml` – die einzige Datei, die für den Betrieb gebraucht wird. + +```bash +cd firmware/vanalign +esphome run esp32_ble.yaml +``` + +### Bluetooth-Schnittstelle + +Dienst `2a24b789-7aab-4535-af3e-ee76a35cc42d` (wird beworben, das Gerät ist +darüber auffindbar). + +| Charakteristik | UUID (Ende) | Zugriff | Inhalt | +|---|---|---|---| +| Pitch | `…3424` | lesen, benachrichtigen | Längsneigung, Float32 little-endian, Grad | +| Roll | `…3425` | lesen, benachrichtigen | Querneigung, Float32 little-endian, Grad | +| Offsets | `…3426` | lesen, benachrichtigen | zwei Float32: die gespeicherten Kalibrierwerte | +| Kalibrieren | `…3427` | schreiben | ein Byte: `0` setzt zurück, alles andere kalibriert | + +Positiver Pitch heißt: das Heck steht höher. Positiver Roll: die rechte Seite +steht höher. + +### Kalibrierung + +Fahrzeug eben stellen, dann ein beliebiges Byte ungleich `0` auf `…3427` +schreiben – oder in Camper Monitor auf *Auf aktuelle Lage kalibrieren* tippen. +Die Offsets werden dauerhaft gespeichert und überstehen einen Neustart. + +## Was sich gegenüber 1.0 geändert hat + +* **Kalibrierung funktioniert jetzt überhaupt.** Die Charakteristik `…3427` war + zwar beschreibbar, hatte aber keine Aktion hinterlegt und tat nichts. Der + Kalibrier-Knopf war nur über den Webserver erreichbar, der auskommentiert + ist – kalibrieren war damit auf keinem Weg möglich. +* **Die Werte werden gesendet, statt abgefragt.** Pitch und Roll haben jetzt + `notify`. Vorher musste der Client im Sekundentakt lesen, was für eine + Wasserwaage zu träge ist. +* **Die gespeicherten Offsets sind auslesbar**, sodass ein Client zeigen kann, + ob überhaupt schon kalibriert wurde. +* **Der ESP-NOW-Rest ist entfernt.** Er schickte bei jedem einzelnen Messwert + ein `"hallo"` an eine fest eingetragene MAC-Adresse. + +Die UUIDs sind unverändert, die vorhandene WebApp läuft weiter. + +## Clients + +* **Camper Monitor** (iOS) – die App in diesem Projekt. Geräteart + *Nivellierung*: grafische Libelle, Kalibrierung aus der App, zusammen mit + den übrigen Geräten im Fahrzeug. +* Die ursprüngliche WebBLE-Oberfläche liegt weiterhin im Projekt + `VanAllign-Pro` unter `WebAPP/`. + +## Weitere Dateien + +Unter `experimente/` liegen `esp32_switch.yaml` (Umschalten zwischen WLAN und +BLE) und `esp32_EspNow-rec.yaml` (ESP-NOW-Empfänger). Beide gehören nicht zum +Neigungsmesser und werden für den Betrieb nicht gebraucht – sie sind nur +aufgehoben, weil sie Ideen aus der Entstehungszeit festhalten. + +## Herkunft + +Übernommen aus dem eigenständigen Projekt `VanAllign-Pro`. Dort liegen +weiterhin die WebBLE-Oberfläche (`WebAPP/`, durch die iOS-App abgelöst) und +unter `bin/` die gebauten Binärdateien der Version 1.0. + +## Voraussetzungen + +* ESP32-S3, MPU6050 an I²C (SDA GPIO8, SCL GPIO9) +* ESPHome (geprüft mit 2026.6.5) diff --git a/firmware/vanalign/esp32_ble.yaml b/firmware/vanalign/esp32_ble.yaml new file mode 100644 index 0000000..a8c1fb6 --- /dev/null +++ b/firmware/vanalign/esp32_ble.yaml @@ -0,0 +1,235 @@ +# VanAlign Pro – Neigungsmessung über BLE +# +# ESP32-S3 mit MPU6050. Liefert Pitch und Roll als 32-Bit-Float +# (little-endian) und lässt sich über Bluetooth auf die aktuelle Lage +# kalibrieren. +# +# Die UUIDs sind gegenüber Version 1.0 unverändert, damit die vorhandene +# WebApp weiterläuft. Neu sind: +# * notify auf Pitch und Roll – die Werte werden gesendet, sobald sie sich +# ändern, statt vom Client im Sekundentakt abgefragt zu werden. +# * eine funktionierende Kalibrierung über Bluetooth. Bisher war die +# Charakteristik zwar beschreibbar, hatte aber keine Aktion hinterlegt, +# und der Kalibrier-Knopf war nur über den Webserver erreichbar, der +# auskommentiert ist. Kalibrieren war damit gar nicht möglich. +# * die gespeicherten Offsets sind auslesbar, sodass ein Client zeigen +# kann, ob überhaupt schon kalibriert wurde. + +esphome: + name: vanalign + friendly_name: "VanAlign Pro" + on_boot: + priority: -100 + then: + - script.execute: publish_offsets + +esp32: + board: esp32-s3-devkitc-1 + framework: + type: esp-idf + +logger: + level: WARN + +i2c: + sda: GPIO8 + scl: GPIO9 + scan: true + +globals: + - id: pitch_offset + type: float + restore_value: yes + initial_value: '0.0' + - id: roll_offset + type: float + restore_value: yes + initial_value: '0.0' + +sensor: + - platform: mpu6050 + #address: 0x69 + accel_x: + id: accel_x + internal: true + accel_y: + id: accel_y + internal: true + accel_z: + id: accel_z + internal: true + gyro_x: + id: mpu_gyro_x + internal: true + gyro_y: + id: mpu_gyro_y + internal: true + gyro_z: + id: mpu_gyro_z + internal: true + update_interval: 0.1s + + - platform: template + name: "Neigung Pitch" + id: pitch + icon: mdi:caravan + unit_of_measurement: "°" + accuracy_decimals: 1 + update_interval: 0.1s + lambda: |- + if (isnan(id(accel_x).state) || isnan(id(accel_y).state) || isnan(id(accel_z).state)) { + return NAN; + } + float raw = atan2(id(accel_y).state, + sqrt(pow(id(accel_x).state, 2) + pow(id(accel_z).state, 2))) + * (180.0 / 3.14159265); + return raw - id(pitch_offset); + filters: + - sliding_window_moving_average: + window_size: 8 + send_every: 1 + - exponential_moving_average: + alpha: 0.2 + on_value: + # Ohne dieses Senden müsste der Client die Werte abfragen; die Anzeige + # ruckelt dann im Takt der Abfrage statt der Messung. + - ble_server.characteristic.set_value: + id: pitch_ble + value: !lambda |- + std::vector v(sizeof(float)); + float val = x; + memcpy(v.data(), &val, sizeof(float)); + return v; + + - platform: template + name: "Neigung Roll" + id: roll + icon: mdi:axis-x-rotate-clockwise + unit_of_measurement: "°" + accuracy_decimals: 1 + update_interval: 0.1s + lambda: |- + if (isnan(id(accel_x).state) || isnan(id(accel_z).state)) { + return NAN; + } + float raw = atan2(-id(accel_x).state, id(accel_z).state) * (180.0 / 3.14159265); + return raw - id(roll_offset); + filters: + - sliding_window_moving_average: + window_size: 8 + send_every: 1 + - exponential_moving_average: + alpha: 0.2 + on_value: + - ble_server.characteristic.set_value: + id: roll_ble + value: !lambda |- + std::vector v(sizeof(float)); + float val = x; + memcpy(v.data(), &val, sizeof(float)); + return v; + +script: + # Die aktuelle Lage wird zur neuen Null. Beide Wege – Knopf und Bluetooth – + # laufen hier zusammen, damit sie nicht auseinanderdriften. + - id: calibrate_level + then: + - lambda: |- + if (isnan(id(accel_x).state) || isnan(id(accel_y).state) || isnan(id(accel_z).state)) { + ESP_LOGW("vanalign", "Kalibrierung abgebrochen: keine Sensorwerte"); + return; + } + id(pitch_offset) = atan2(id(accel_y).state, + sqrt(pow(id(accel_x).state, 2) + pow(id(accel_z).state, 2))) + * (180.0 / 3.14159265); + id(roll_offset) = atan2(-id(accel_x).state, id(accel_z).state) * (180.0 / 3.14159265); + ESP_LOGI("vanalign", "Kalibriert: pitch_offset=%.2f roll_offset=%.2f", + id(pitch_offset), id(roll_offset)); + + - script.execute: publish_offsets + + - id: reset_calibration + then: + - lambda: |- + id(pitch_offset) = 0.0f; + id(roll_offset) = 0.0f; + ESP_LOGI("vanalign", "Kalibrierung zurückgesetzt"); + - script.execute: publish_offsets + + # Die gespeicherten Offsets für lesende Clients bereitstellen. + - id: publish_offsets + then: + - ble_server.characteristic.set_value: + id: offsets_ble + value: !lambda |- + std::vector v(2 * sizeof(float)); + float values[2] = { id(pitch_offset), id(roll_offset) }; + memcpy(v.data(), values, sizeof(values)); + return v; + +esp32_ble_server: + manufacturer: "VanAlign" + model: "Pro" + services: + - uuid: 2a24b789-7aab-4535-af3e-ee76a35cc42d + advertise: true + characteristics: + - id: pitch_ble + uuid: cad48e28-7fbe-41cf-bae9-d77a6c233424 + description: "Pitch" + read: true + notify: true + + - id: roll_ble + uuid: cad48e28-7fbe-41cf-bae9-d77a6c233425 + description: "Roll" + read: true + notify: true + + # Zwei Floats: die gespeicherten Offsets. Damit kann ein Client + # anzeigen, ob und worauf kalibriert wurde. + - id: offsets_ble + uuid: cad48e28-7fbe-41cf-bae9-d77a6c233426 + description: "Kalibrier-Offsets" + read: true + notify: true + + # Ein Byte: 0 setzt die Kalibrierung zurück, alles andere kalibriert + # auf die aktuelle Lage. + - id: calib_ble + uuid: cad48e28-7fbe-41cf-bae9-d77a6c233427 + description: "Kalibriere Neigung" + write: true + on_write: + then: + - lambda: |- + bool reset = !x.empty() && (x[0] == 0x00 || x[0] == '0'); + if (reset) { + id(reset_calibration).execute(); + } else { + id(calibrate_level).execute(); + } + +button: + - platform: template + name: "Kalibriere Neigung" + id: calib_button + on_press: + - script.execute: calibrate_level + + - platform: template + name: "Kalibrierung zurücksetzen" + id: calib_reset_button + on_press: + - script.execute: reset_calibration + + - platform: restart + name: "ESP Restart" + +text_sensor: + - platform: template + name: "Firmware Version" + id: firmware_version + icon: mdi:tag + lambda: |- + return {"v1.1.0"}; diff --git a/firmware/vanalign/experimente/esp32_EspNow-rec.yaml b/firmware/vanalign/experimente/esp32_EspNow-rec.yaml new file mode 100644 index 0000000..951b447 --- /dev/null +++ b/firmware/vanalign/experimente/esp32_EspNow-rec.yaml @@ -0,0 +1,22 @@ +esphome: + name: vanalign + friendly_name: "VanAlign Pro" + + +esp32: + board: esp32-s3-devkitc-1 + +logger: + level: WARN + +espnow: + channel: 1 + on_broadcast: + - logger.log: + format: "Sent to %s from %s: %s RSSI: %ddBm" + args: + - format_mac_address_pretty(info.des_addr).c_str() + - format_mac_address_pretty(info.src_addr).c_str() + - format_hex_pretty(data, size).c_str() + - info.rx_ctrl->rssi + diff --git a/firmware/vanalign/experimente/esp32_switch.yaml b/firmware/vanalign/experimente/esp32_switch.yaml new file mode 100644 index 0000000..2e1f76d --- /dev/null +++ b/firmware/vanalign/experimente/esp32_switch.yaml @@ -0,0 +1,63 @@ +esphome: + name: dual-net-webble + +esp32: + board: esp32-s3-devkitc-1 + framework: + type: esp-idf + +logger: + baud_rate: 0 + level: WARN + +wifi: + ssid: "DEIN_SSID" + password: "DEIN_PASSWORT" + enable_on_boot: false + +api: +web_server: + port: 80 + # ota: true <-- entfernen! + +ota: + - platform: esphome # klassisches OTA (benötigt 'api:') + - platform: web_server # OTA im Web-Interface + + +# BLE-Stack nur für eigenen GATT-Server +esp32_ble: + enable_on_boot: false + +esp32_ble_server: + manufacturer: "MyLab" + model: "WebBLE" + services: + - uuid: "12345678-1234-5678-1234-56789abcdef0" + characteristics: + # Write "1" -> WLAN-Fenster starten/verlängern + - uuid: "12345678-1234-5678-1234-56789abcdef1" + name: "StartMaintenance" + value: "0" + on_write: + then: + - lambda: |- + if (x.get_value().find("1") != std::string::npos) { + id(start_wifi_window).execute(); + } + # Status bei Bedarf lesbar (z. B. "WIFI,14" oder "BLE,0") + - uuid: "12345678-1234-5678-1234-56789abcdef2" + name: "Status" + value: "BLE,0" + on_read: + then: + - lambda: |- + char buf[16]; + snprintf(buf, sizeof(buf), "%s,%d", + id(_netmode_is_wifi) ? "WIFI" : "BLE", + (int) id(_wifi_minutes_left)); + x.set_value(std::string(buf)); + +number: + - platform: template + id: wifi_window_minu