Firmware des Neigungsmessers ins Projekt übernehmen

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 <noreply@anthropic.com>
This commit is contained in:
BiasF
2026-08-30 15:12:30 +02:00
co-authored by Claude Opus 5
parent 1b9486aad4
commit 9406a749e5
6 changed files with 414 additions and 2 deletions
+79
View File
@@ -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)
+235
View File
@@ -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<uint8_t> 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<uint8_t> 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<uint8_t> 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"};
@@ -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
@@ -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