From 16e2851696a7d0d26ae9d302c9d176970d3690d1 Mon Sep 17 00:00:00 2001 From: FCS Date: Sun, 30 Aug 2026 12:40:52 +0200 Subject: [PATCH] =?UTF-8?q?neues=20Module=20SKP,=20=C3=9Cbertragung=20der?= =?UTF-8?q?=20Signalleuchte=20zustand=20von=20Winde=20an=20SKP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/build_release.yaml | 15 +++ CHANGELOG.md | 22 ++++ README.md | 27 ++-- lib/webinterface/webinterface.cpp | 4 + lib/windentelemetry.h | 13 +- platformio.ini | 10 ++ src/main.cpp | 192 +++++++++++++++++++++++----- 7 files changed, 237 insertions(+), 46 deletions(-) diff --git a/.gitea/workflows/build_release.yaml b/.gitea/workflows/build_release.yaml index a92be0f..fc82ac2 100644 --- a/.gitea/workflows/build_release.yaml +++ b/.gitea/workflows/build_release.yaml @@ -20,6 +20,7 @@ jobs: environment: - heltec_wifi_lora_32_V3_sailplane - heltec_wifi_lora_32_V3_winde + - heltec_wifi_lora_32_V3_skp runs-on: ubuntu-latest container: catthehacker/ubuntu:act-latest steps: @@ -84,23 +85,35 @@ jobs: with: name: firmware-heltec_wifi_lora_32_V3_winde path: firmware/winde + - name: Download SKP artifact + uses: christopherhx/gitea-download-artifact@75635f32b4c1c41c4b3d64e8f85210112ed4c9c7 # v4 + with: + name: firmware-heltec_wifi_lora_32_V3_skp + path: firmware/skp - name: Verify release assets run: | RELEASE_VERSION="${{ gitea.ref_name }}" SAILPLANE_FILE=$(find firmware/sailplane -type f -name 'firmware_*.bin' -print -quit) WINDE_FILE=$(find firmware/winde -type f -name 'firmware_*.bin' -print -quit) + SKP_FILE=$(find firmware/skp -type f -name 'firmware_*.bin' -print -quit) test -n "$SAILPLANE_FILE" test -n "$WINDE_FILE" + test -n "$SKP_FILE" cp "$SAILPLANE_FILE" "firmware/firmware_sailplane_${RELEASE_VERSION}.bin" cp "$WINDE_FILE" "firmware/firmware_winde_${RELEASE_VERSION}.bin" + cp "$SKP_FILE" "firmware/firmware_skp_${RELEASE_VERSION}.bin" (cd firmware && sha256sum "firmware_sailplane_${RELEASE_VERSION}.bin" > "firmware_sailplane_${RELEASE_VERSION}.sha256") (cd firmware && sha256sum "firmware_winde_${RELEASE_VERSION}.bin" > "firmware_winde_${RELEASE_VERSION}.sha256") + (cd firmware && sha256sum "firmware_skp_${RELEASE_VERSION}.bin" > "firmware_skp_${RELEASE_VERSION}.sha256") test -s "firmware/firmware_sailplane_${RELEASE_VERSION}.bin" test -s "firmware/firmware_winde_${RELEASE_VERSION}.bin" + test -s "firmware/firmware_skp_${RELEASE_VERSION}.bin" test -s "firmware/firmware_sailplane_${RELEASE_VERSION}.sha256" test -s "firmware/firmware_winde_${RELEASE_VERSION}.sha256" + test -s "firmware/firmware_skp_${RELEASE_VERSION}.sha256" (cd firmware && sha256sum -c "firmware_sailplane_${RELEASE_VERSION}.sha256") (cd firmware && sha256sum -c "firmware_winde_${RELEASE_VERSION}.sha256") + (cd firmware && sha256sum -c "firmware_skp_${RELEASE_VERSION}.sha256") test -s LICENSE test -s THIRD_PARTY_NOTICES.md - name: Create release @@ -109,8 +122,10 @@ jobs: files: |- firmware/firmware_sailplane_${{ gitea.ref_name }}.bin firmware/firmware_winde_${{ gitea.ref_name }}.bin + firmware/firmware_skp_${{ gitea.ref_name }}.bin firmware/firmware_sailplane_${{ gitea.ref_name }}.sha256 firmware/firmware_winde_${{ gitea.ref_name }}.sha256 + firmware/firmware_skp_${{ gitea.ref_name }}.sha256 LICENSE THIRD_PARTY_NOTICES.md body_path: "CHANGELOG.md" diff --git a/CHANGELOG.md b/CHANGELOG.md index 73253bd..41d5089 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,24 @@ CHANGELOG +## v0.1.0 (30.08.2026) ++ modulabhängige Einstellungen im Webinterface nur noch für genutzte Werte sichtbar ++ gemeinsame AP-/OTA-Logik für die Module beibehalten + +### Winden Modul ++ Warnleuchten-Erkennung ergänzt ++ Statusmeldung via LoRa an das SKP-Modul senden + +### SKP Modul ++ neues Modul, ebenfalls mit Webinterface für Settings und OTA ++ empfangene Warnleuchten-Status via LoRa ausgewertet ++ Relais Steuerung + +--- ## v0.0.8a (30.08.2026) + fix: Wifi Auto off Nachrichten Schleife + Verbesserung Redirect to Einstellungs Seite + Verbesserung AutoBuild +--- ## v0.0.8 (30.08.2026) + Firmware-Version als Build-Tag im Code und im Webinterface eingebaut + WLAN-Name für Segelflug- und Winden-Module angepasst (`Telemetry-` bzw. `Telemetry-Winch`) @@ -12,18 +27,23 @@ CHANGELOG + Captive-Portal / DNS-Weiterleitung für das Webinterface ergänzt + Access Point bleibt nur 5 Minuten nach PowerOn aktiv, wenn kein Client verbunden ist, und wird dann automatisch deaktiviert +--- ## v0.0.7d (28.08.2026) + AutoBuild und Release-Prozess für Versionsnamen, Prüfsummen und Release-Artefakte korrigiert +--- ## v0.0.7c (28.08.2026) + korrektur AutoBuild (Checksum) +--- ## v0.0.7b (28.08.2026) + korrektur AutoBuild +--- ## v0.0.7a (28.08.2026) + verbessertes AutoBuild +--- ## v0.0.7 (28.08.2026) + Webinterface über eigenen WLAN-Access-Point ergänzt + Persistente Konfiguration für WLAN-Name, WLAN-Passwort und Callsign @@ -42,6 +62,7 @@ CHANGELOG + Initialisierung von Servo und LED-Ring korrigiert + NMEA-Nachrichtenprüfung und Fehlerbehandlung beim Empfang verbessert +--- ## v0.0.6 (18.01.2026) + Anpassung Programmablaufplan @@ -52,6 +73,7 @@ CHANGELOG ### Winden Modul + empfangen im NMEA Format +--- ## v0.0.5 (30.11.2025) + Formatierung Code ### Segeflug Modul diff --git a/README.md b/README.md index 320e1c3..bbb2f4b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ # Winden-Telemetry +Aktuelle Version: v0.1.0 + Der eigene Projektcode steht unter der MIT-Lizenz. Die verwendeten Drittanbieterbibliotheken bleiben unter ihren jeweiligen Original-Lizenzen. Eine Übersicht der Komponenten, Versionen und Lizenzbedingungen steht in @@ -33,7 +35,10 @@ für den Projektcode steht in [LICENSE](LICENSE). - [x] Analoge Anzeige in der Winde - [x] Farbige LEDs für Limits - [x] Übertragungszeit Begrenzen -- [ ] Übertragung Rundumleuchten Signal an Start +- [x] 12V-Warnleuchten-Erkennung am Wind-Modul via GPIO 5 +- [x] NMEA-Statusübertragung zum SKP-Modul mit Prefix $PWTLW +- [x] SKP-Modul mit Relaissteuerung über GPIO 7 und Timeout-Sicherung +- [x] eigenes SKP-Webinterface mit WLAN- und OTA-Funktionen - [x] Luftdrucksensor für Höhe und Vario ### Webinterface und Firmware-Update @@ -45,16 +50,14 @@ Winde `Winde-Telemetry`. - Passwort: `telemetry` - Webadresse: `http://192.168.4.1/` -Im Webinterface können WLAN-Name und WLAN-Passwort sowie Callsign, minimale und maximale Geschwindigkeit -gespeichert werden. Die Werte bleiben nach einem Neustart erhalten. Die -Sailplane-Firmware lädt sie beim Start und verwendet sie für die Telemetrie, -die Schlepp-Erkennung und die maximale Übertragungszeit. Die Winden-Firmware verwendet die gespeicherten -Grenzen für die Anzeige beim Start; empfangene Telemetriegrenzen werden danach -weiterhin aus dem LoRa-Paket übernommen. +Im Webinterface lassen sich die WLAN-Einstellungen verwalten. Je nach Modul +werden nur die tatsächlich genutzten Werte angezeigt. Die Werte bleiben nach +einem Neustart erhalten. Das Sailplane-Modul verwendet sie für Telemetrie und +Schlepp-Erkennung, während die Wind- und SKP-Module die passenden Betriebswerte +für ihren jeweiligen Betrieb übernehmen. -Für ein Firmware-Update die zur jeweiligen Hardware passende `.bin`-Datei aus -`.pio/build//` auswählen. Nach erfolgreichem Upload startet das -Gerät automatisch neu. +Für ein Firmware-Update die passende `.bin`-Datei aus `.pio/build//` + auswählen. Nach einem erfolgreichen Upload startet das Gerät automatisch neu. ## Aufbau @@ -95,9 +98,9 @@ Die Platine ist so gestaltet das sie individuell bestückt werden kann, je nach | I2C_4 | optional| optional| optional| | GPIO3 | optional| optional| optional| | GPIO4 | optional| optional| optional| -| GPIO5 | optional| Eingang Warnleuchte |optional| +| GPIO5 | optional| Eingang Warnleuchte | optional| | GPIO6 | optional| LED Ring | optional| -| GPIO7 | optional| Servo | für Relais| +| GPIO7 | optional| Servo | Relaissteuerung | diff --git a/lib/webinterface/webinterface.cpp b/lib/webinterface/webinterface.cpp index 7e4cc49..0d0193f 100644 --- a/lib/webinterface/webinterface.cpp +++ b/lib/webinterface/webinterface.cpp @@ -22,6 +22,8 @@ String default_access_point_name() { #ifdef DEVICE_SAILPLANE return String(telemetry_ap_prefix) + default_callsign; +#elif defined(DEVICE_SKP) + return String(telemetry_ap_prefix) + "SKP"; #else return String(telemetry_ap_prefix) + "Winch"; #endif @@ -34,6 +36,8 @@ String generated_access_point_name(const String &callsign) if (safe_callsign.length() == 0) safe_callsign = default_callsign; return String(telemetry_ap_prefix) + safe_callsign; +#elif defined(DEVICE_SKP) + return String(telemetry_ap_prefix) + "SKP"; #else return String(telemetry_ap_prefix) + "Winch"; #endif diff --git a/lib/windentelemetry.h b/lib/windentelemetry.h index fb2e4bd..f8040e9 100644 --- a/lib/windentelemetry.h +++ b/lib/windentelemetry.h @@ -41,11 +41,20 @@ #define servoPin 7 /**WS8212 **/ // Which pin on is connected to the WS8212? -#define NEOPIN 6 +#define NEOPIN 6 // How many NeoPixels are attached -#define NUMPIXELS 12 +#define NUMPIXELS 12 #define pixeloffset 6 +#define WARN_LIGHT_SENSE_PIN 5 +#define WARN_LIGHT_DETECT_THRESHOLD 1000 +#define WARN_LIGHT_TX_INTERVAL_MS 5000UL +#endif + +#ifdef DEVICE_SKP +#define RELAY_PIN 7 +#define RELAY_PULSE_MS 500UL +#define WARNING_TIMEOUT_MS 5000UL #endif diff --git a/platformio.ini b/platformio.ini index 83b33a7..4f62ff2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -63,3 +63,13 @@ build_flags = -D DEVICE_WINDE upload_port = COM9 monitor_port = COM9 + +[env:heltec_wifi_lora_32_V3_skp] +extends = common +board = heltec_wifi_lora_32_V3 +lib_deps = + ${common.lib_deps} +lib_ignore = sailplane, winde +build_flags = + ${common.build_flags} + -D DEVICE_SKP diff --git a/src/main.cpp b/src/main.cpp index 31d3a5d..34aac87 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,17 +22,87 @@ bool sensors_ready = false; SX1262 radio = new Module(LORA_NSS, LORA_DIO1, LORA_NRST, LORA_BUSY); bool radio_ready = false; + +String nmea_checksum(String str) +{ + byte cs = 0; + for (unsigned int n = 1; n < str.length(); n++) + { + if (str[n] == '*') + break; + cs ^= str[n]; + } + + if (cs < 0x10) + return "*0" + String(cs, HEX); + else + return "*" + String(cs, HEX); +} + #ifdef DEVICE_WINDE volatile bool receivedFlag = false; +bool warning_light_active = false; +bool last_warning_light_state = false; +unsigned long last_warning_light_tx_ms = 0UL; #if defined(ESP8266) || defined(ESP32) ICACHE_RAM_ATTR #endif void setFlag(void) { - // we got a packet, set the flag receivedFlag = true; } +bool read_warning_light_voltage() +{ + int raw = analogRead(WARN_LIGHT_SENSE_PIN); + return raw > WARN_LIGHT_DETECT_THRESHOLD; +} + +void send_warning_light_state(bool active) +{ + String payload = "$PWTLW," + String(active ? "1" : "0"); + payload += nmea_checksum(payload); + Serial.println(payload); + int state = radio.transmit(payload); + if (state != RADIOLIB_ERR_NONE) + { + Serial.print("Warnleuchte Status senden fehlgeschlagen, code "); + Serial.println(state); + } +} +#endif + +#ifdef DEVICE_SKP +volatile bool skpReceivedFlag = false; +bool relay_active = false; +unsigned long relay_off_deadline = 0UL; +unsigned long last_valid_warning_ms = 0UL; +#if defined(ESP8266) || defined(ESP32) +ICACHE_RAM_ATTR +#endif +void skpSetFlag(void) +{ + skpReceivedFlag = true; +} + +void set_relay_state(bool active) +{ + relay_active = active; + digitalWrite(RELAY_PIN, active ? HIGH : LOW); + if (active) + relay_off_deadline = millis() + RELAY_PULSE_MS; + else + relay_off_deadline = 0UL; +} + +void trigger_relay_pulse() +{ + last_valid_warning_ms = millis(); + if (relay_active) + digitalWrite(RELAY_PIN, LOW); + delay(20); + set_relay_state(true); +} #endif #ifdef HELTEC_WIFI_LORA_32_V3 U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, OLED_RST, OLED_CLK, OLED_DATA); @@ -66,6 +136,29 @@ void setup() Serial.println(state); Serial.println(F("Webinterface bleibt verfuegbar")); } +#ifdef DEVICE_WINDE + pinMode(WARN_LIGHT_SENSE_PIN, INPUT); + analogReadResolution(12); + analogSetPinAttenuation(WARN_LIGHT_SENSE_PIN, ADC_11db); + last_warning_light_state = read_warning_light_voltage(); +#endif +#ifdef DEVICE_SKP + pinMode(RELAY_PIN, OUTPUT); + digitalWrite(RELAY_PIN, LOW); + relay_active = false; + last_valid_warning_ms = millis(); + radio.setPacketReceivedAction(skpSetFlag); + if (radio_ready) + { + state = radio.startReceive(); + if (state != RADIOLIB_ERR_NONE) + { + Serial.print(F("SKP Receive failed, code ")); + Serial.println(state); + radio_ready = false; + } + } +#endif u8g2.begin(); u8g2.clearBuffer(); u8g2.setFont(u8g2_font_ncenB14_tr); @@ -105,21 +198,6 @@ void setup() u8g2.sendBuffer(); } -String nmea_checksum(String str) -{ - byte cs = 0; - for (unsigned int n = 1; n < str.length(); n++) - { - if (str[n] == '*') - break; - cs ^= str[n]; - } - - if (cs < 0x10) - return "*0" + String(cs, HEX); - else - return "*" + String(cs, HEX); -} void draw_rssi(int Rssi){ u8g2.drawFrame(102,8,4,3); u8g2.drawFrame(107,6,4,5); @@ -222,22 +300,6 @@ String getSplitValue(String data, char separator, int index) } return found > index ? data.substring(strIndex[0], strIndex[1]) : ""; } -#ifdef DEVICE_WINDE - -void decode_start(String data) -{ - data_from = getSplitValue(data, ',', 1); - data_speed = (getSplitValue(data, ',', 2)).toInt(); - data_min = (getSplitValue(data, ',', 3)).toInt(); - data_max = (getSplitValue(data, ',', 4)).toInt(); -} -void decode_airborn(String data) -{ - data_from = getSplitValue(data, ',', 1); - data_speed = 0; - data_min = 0; - data_max = 0; -} bool valid_nmea(String data) { @@ -256,6 +318,23 @@ bool valid_nmea(String data) return received_checksum == expected_checksum; } +#ifdef DEVICE_WINDE + +void decode_start(String data) +{ + data_from = getSplitValue(data, ',', 1); + data_speed = (getSplitValue(data, ',', 2)).toInt(); + data_min = (getSplitValue(data, ',', 3)).toInt(); + data_max = (getSplitValue(data, ',', 4)).toInt(); +} +void decode_airborn(String data) +{ + data_from = getSplitValue(data, ',', 1); + data_speed = 0; + data_min = 0; + data_max = 0; +} + bool decode_msg(String data) { String pos1 = getSplitValue(data, ',', 0); @@ -281,6 +360,55 @@ bool decode_msg(String data) void loop() { webinterface_handle(); +#ifdef DEVICE_WINDE + bool current_warning_light_state = read_warning_light_voltage(); + if (current_warning_light_state != last_warning_light_state) + { + last_warning_light_state = current_warning_light_state; + last_warning_light_tx_ms = millis(); + send_warning_light_state(current_warning_light_state); + } +#endif +#ifdef DEVICE_SKP + if (relay_active && millis() >= relay_off_deadline) + { + set_relay_state(false); + } + if (radio_ready && skpReceivedFlag) + { + skpReceivedFlag = false; + String str; + int state = radio.readData(str); + if (state == RADIOLIB_ERR_NONE) + { + if (!valid_nmea(str)) + { + Serial.println("Ungueltige SKP-Nachricht"); + } + else if (getSplitValue(str, ',', 0) == "$PWTLW") + { + String status_field = getSplitValue(str, ',', 1); + status_field.trim(); + bool warning_active = status_field == "1" || status_field == "ON" || status_field == "true"; + if (warning_active) + { + trigger_relay_pulse(); + Serial.println("Warnleuchte aktiv -> Relais an"); + } + else + { + set_relay_state(false); + last_valid_warning_ms = millis(); + Serial.println("Warnleuchte inaktiv -> Relais aus"); + } + } + } + } + if (millis() - last_valid_warning_ms > WARNING_TIMEOUT_MS) + { + set_relay_state(false); + } +#endif #ifdef DEVICE_SAILPLANE if (!sensors_ready) {