# 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 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 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 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 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"};