first commit
This commit is contained in:
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
# Gitignore settings for ESPHome
|
||||
# This is an example and may include too much for your use-case.
|
||||
# You can modify this file to suit your needs.
|
||||
/.esphome/
|
||||
/secrets.yaml
|
||||
@@ -0,0 +1,95 @@
|
||||
# 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 | Längsneigung, Float32 little-endian, Grad |
|
||||
| Roll | `…3425` | lesen | Querneigung, Float32 little-endian, Grad |
|
||||
| Kalibrieren | `…3427` | schreiben | ein Byte: `0` setzt zurück, alles andere kalibriert |
|
||||
|
||||
Der Client fragt Pitch und Roll im Takt ab; VanControl Pro tut das zweimal je
|
||||
Sekunde.
|
||||
|
||||
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 VanControl Pro auf *Auf aktuelle Lage kalibrieren* tippen.
|
||||
Die Offsets werden dauerhaft gespeichert und überstehen einen Neustart.
|
||||
|
||||
## Was sich gegenüber 1.0 geändert hat
|
||||
|
||||
Bewusst wenig – siehe unten.
|
||||
|
||||
* **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.
|
||||
* **Der ESP-NOW-Rest ist entfernt.** Er schickte bei jedem einzelnen Messwert
|
||||
ein `"hallo"` an eine fest eingetragene MAC-Adresse.
|
||||
|
||||
## Was bewusst nicht geändert wurde
|
||||
|
||||
Eine weitergehende Fassung hatte `notify` auf Pitch und Roll, setzte die Werte
|
||||
aktiv bei jeder Messung und stellte die Kalibrier-Offsets zum Auslesen bereit.
|
||||
Sie liess sich mit `esphome config` prüfen und fehlerfrei übersetzen – auf dem
|
||||
Gerät blieb sie danach jedoch stumm, ohne jede Ausgabe über die serielle
|
||||
Schnittstelle.
|
||||
|
||||
Woran genau es lag, ist offen. Verdächtig sind das Setzen der Werte aus dem
|
||||
`on_boot`-Ablauf heraus, bevor der BLE-Server bereit ist, sowie zwei
|
||||
Benachrichtigungen zehnmal je Sekunde. Weil sich das nur am Gerät klären lässt,
|
||||
bleibt es beim bewährten Aufbau: Pitch und Roll sind reine Lesewerte mit
|
||||
hinterlegtem Ausdruck, der Client fragt ab.
|
||||
|
||||
Die verworfene Fassung ist in der Projektgeschichte unter *Neigungsmesser
|
||||
VanAlign einbinden* nachlesbar, falls jemand daran weiterarbeiten will.
|
||||
|
||||
Die UUIDs sind unverändert, die vorhandene WebApp läuft weiter.
|
||||
|
||||
## Clients
|
||||
|
||||
* **VanControl Pro** (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)
|
||||
@@ -0,0 +1,295 @@
|
||||
# VanAlign Pro - Neigungsmessung über BLE
|
||||
#
|
||||
# Bewusst nah am bewährten Stand 1.0 gehalten. Gegenüber diesem geändert:
|
||||
#
|
||||
# * Die Kalibrierung funktioniert. 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.
|
||||
# * Der ESP-NOW-Rest ist entfernt. Er schickte bei jedem einzelnen
|
||||
# Messwert ein "hallo" an eine fest eingetragene MAC-Adresse.
|
||||
#
|
||||
# Bewusst NICHT geändert: Pitch und Roll bleiben reine Lesewerte mit
|
||||
# hinterlegtem Ausdruck. Eine Fassung mit notify und aktiv gesetzten Werten
|
||||
# liess das Gerät verstummen - sie ist in der Projektgeschichte unter
|
||||
# "Neigungsmesser VanAlign einbinden" nachlesbar, aber nicht in Betrieb.
|
||||
# Der Client fragt die Werte stattdessen im Takt ab.
|
||||
|
||||
esphome:
|
||||
name: vanalign
|
||||
friendly_name: "VanAlign Pro"
|
||||
|
||||
|
||||
esp32:
|
||||
board: esp32-s3-devkitc-1
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
|
||||
logger:
|
||||
level: WARN
|
||||
|
||||
espnow:
|
||||
channel: 1
|
||||
sensor:
|
||||
- platform: mpu6050
|
||||
#address: 0x69
|
||||
gyro_x:
|
||||
name: "MPU6050 Gyro X-Achse"
|
||||
id: mpu_gyro_x
|
||||
gyro_y:
|
||||
name: "MPU6050 Gyro Y-Achse"
|
||||
id: mpu_gyro_y
|
||||
gyro_z:
|
||||
name: "MPU6050 Gyro Z-Achse"
|
||||
id: mpu_gyro_z
|
||||
accel_x:
|
||||
name: "MPU6050 Accel X"
|
||||
id: accel_x
|
||||
internal: true
|
||||
accel_y:
|
||||
name: "MPU6050 Accel Y"
|
||||
id: accel_y
|
||||
internal: true
|
||||
accel_z:
|
||||
name: "MPU6050 Accel Z"
|
||||
id: accel_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); // Offset wird hier subtrahiert
|
||||
filters:
|
||||
- sliding_window_moving_average:
|
||||
window_size: 8
|
||||
send_every: 1
|
||||
- exponential_moving_average:
|
||||
alpha: 0.2
|
||||
|
||||
- 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); // Offset wird hier subtrahiert
|
||||
filters:
|
||||
- sliding_window_moving_average:
|
||||
window_size: 8
|
||||
send_every: 1
|
||||
- exponential_moving_average:
|
||||
alpha: 0.2
|
||||
|
||||
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'
|
||||
# Die Einbaulage liegt im Gerät, nicht in den Apps: Sie beschreibt, wie der
|
||||
# Sensor im Fahrzeug sitzt – eine Eigenschaft des Einbaus, nicht des Telefons.
|
||||
# Damit sehen iPhone, Uhr und Android dasselbe, ohne sie je einzeln zu
|
||||
# bestimmen. Angewandt wird sie weiterhin in den Apps; das Gerät verwahrt sie
|
||||
# nur, sonst rechneten ältere Clients die Korrektur ein zweites Mal.
|
||||
- id: orientation_version
|
||||
type: uint8_t
|
||||
restore_value: yes
|
||||
initial_value: '0'
|
||||
- id: orientation_source
|
||||
type: uint8_t
|
||||
restore_value: yes
|
||||
initial_value: '0'
|
||||
- id: orientation_invert_long
|
||||
type: bool
|
||||
restore_value: yes
|
||||
initial_value: 'false'
|
||||
- id: orientation_invert_lat
|
||||
type: bool
|
||||
restore_value: yes
|
||||
initial_value: 'false'
|
||||
- id: orientation_twist
|
||||
type: float
|
||||
restore_value: yes
|
||||
initial_value: '0.0'
|
||||
|
||||
- id: enable_captive
|
||||
type: bool
|
||||
restore_value: yes
|
||||
initial_value: 'false'
|
||||
|
||||
esp32_ble_server:
|
||||
services:
|
||||
- uuid: 2a24b789-7aab-4535-af3e-ee76a35cc42d
|
||||
advertise: true
|
||||
characteristics:
|
||||
- id: pitch_ble
|
||||
uuid: cad48e28-7fbe-41cf-bae9-d77a6c233424
|
||||
description: "Pitch"
|
||||
read: true
|
||||
value: !lambda |-
|
||||
std::vector<unsigned char> v(sizeof(float));
|
||||
float val = id(pitch).state;
|
||||
memcpy(v.data(), &val, sizeof(float));
|
||||
return v;
|
||||
- id: roll_ble
|
||||
uuid: cad48e28-7fbe-41cf-bae9-d77a6c233425
|
||||
description: "Roll"
|
||||
read: true
|
||||
value: !lambda |-
|
||||
std::vector<unsigned char> v(sizeof(float));
|
||||
float val = id(roll).state;
|
||||
memcpy(v.data(), &val, sizeof(float));
|
||||
return v;
|
||||
# Die gespeicherten Nullpunkte, zwei Floats. Daran erkennen die Apps,
|
||||
# ob überhaupt schon kalibriert wurde.
|
||||
- id: offsets_ble
|
||||
uuid: cad48e28-7fbe-41cf-bae9-d77a6c233426
|
||||
description: "Kalibrier-Offsets"
|
||||
read: true
|
||||
value: !lambda |-
|
||||
std::vector<unsigned char> v(2 * sizeof(float));
|
||||
float p = id(pitch_offset);
|
||||
float r = id(roll_offset);
|
||||
memcpy(v.data(), &p, sizeof(float));
|
||||
memcpy(v.data() + sizeof(float), &r, sizeof(float));
|
||||
return v;
|
||||
|
||||
# Die Einbaulage, acht Byte:
|
||||
#
|
||||
# 0 Version, 1 = gültig gesetzt, 0 = nie geschrieben
|
||||
# 1 Längsachse: 0 = Pitch des Sensors, 1 = Roll des Sensors
|
||||
# 2 längs umgekehrt (0/1)
|
||||
# 3 quer umgekehrt (0/1)
|
||||
# 4..7 Verdrehung um die Hochachse, float32, Grad
|
||||
- id: orientation_ble
|
||||
uuid: cad48e28-7fbe-41cf-bae9-d77a6c233428
|
||||
description: "Einbaulage"
|
||||
read: true
|
||||
write: true
|
||||
value: !lambda |-
|
||||
std::vector<unsigned char> v(8, 0);
|
||||
v[0] = id(orientation_version);
|
||||
v[1] = id(orientation_source);
|
||||
v[2] = id(orientation_invert_long) ? 1 : 0;
|
||||
v[3] = id(orientation_invert_lat) ? 1 : 0;
|
||||
float t = id(orientation_twist);
|
||||
memcpy(v.data() + 4, &t, sizeof(float));
|
||||
return v;
|
||||
on_write:
|
||||
then:
|
||||
- lambda: |-
|
||||
if (x.size() < 8 || x[0] != 1) {
|
||||
ESP_LOGW("vanalign", "Einbaulage verworfen: %d Byte, Version %d",
|
||||
(int) x.size(), x.empty() ? -1 : (int) x[0]);
|
||||
return;
|
||||
}
|
||||
float t;
|
||||
memcpy(&t, x.data() + 4, sizeof(float));
|
||||
if (!std::isfinite(t) || fabsf(t) > 180.0f) {
|
||||
ESP_LOGW("vanalign", "Einbaulage verworfen: Verdrehung %.1f", t);
|
||||
return;
|
||||
}
|
||||
id(orientation_version) = 1;
|
||||
id(orientation_source) = x[1];
|
||||
id(orientation_invert_long) = x[2] != 0;
|
||||
id(orientation_invert_lat) = x[3] != 0;
|
||||
id(orientation_twist) = t;
|
||||
ESP_LOGI("vanalign", "Einbaulage gespeichert: Quelle=%d laengs=%d quer=%d verdreht=%.1f",
|
||||
(int) x[1], (int) x[2], (int) x[3], t);
|
||||
|
||||
- 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();
|
||||
}
|
||||
|
||||
i2c:
|
||||
sda: GPIO8
|
||||
scl: GPIO9
|
||||
scan: true
|
||||
|
||||
#web_server:
|
||||
# port: 80
|
||||
|
||||
|
||||
#ota:
|
||||
# platform: web_server
|
||||
|
||||
|
||||
#wifi:
|
||||
# ap:
|
||||
# ssid: "VanAlign-Setup"
|
||||
# password: "kalibrierung"
|
||||
|
||||
|
||||
|
||||
script:
|
||||
# Die aktuelle Lage wird zur neuen Null. 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));
|
||||
|
||||
- id: reset_calibration
|
||||
then:
|
||||
- lambda: |-
|
||||
id(pitch_offset) = 0.0f;
|
||||
id(roll_offset) = 0.0f;
|
||||
ESP_LOGI("vanalign", "Kalibrierung zurückgesetzt");
|
||||
|
||||
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.0.2"};
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user