19 Commits
Author SHA1 Message Date
FCS 16e2851696 neues Module SKP, Übertragung der Signalleuchte zustand von Winde an SKP
build_release.yaml / Build heltec_wifi_lora_32_V3_sailplane (push) Successful in 4m31s
build_release.yaml / Build heltec_wifi_lora_32_V3_skp (push) Successful in 2m53s
build_release.yaml / Build heltec_wifi_lora_32_V3_winde (push) Successful in 3m19s
build_release.yaml / Release (push) Successful in 11s
2026-08-30 12:40:52 +02:00
FCS f468f09ad2 Verbesserung Settings Menü 2026-08-30 12:18:17 +02:00
FCS 333d87af3e v0.0.8a
build_release.yaml / Build heltec_wifi_lora_32_V3_sailplane (push) Successful in 3m39s
build_release.yaml / Build heltec_wifi_lora_32_V3_winde (push) Successful in 3m21s
build_release.yaml / Release (push) Successful in 9s
2026-08-30 11:38:10 +02:00
FCS 13f1aa2148 fix: Endlosschleife Wifi auto off 2026-08-30 11:34:03 +02:00
FCS 5688f8d679 Verbesserung Redirect 2026-08-30 11:28:14 +02:00
FCS d8558b330a Build function 2026-08-30 11:25:03 +02:00
FCS 72d9923800 Changelog v0.0.8
build_release.yaml / Build heltec_wifi_lora_32_V3_sailplane (push) Failing after 3m22s
build_release.yaml / Build heltec_wifi_lora_32_V3_winde (push) Successful in 4m18s
build_release.yaml / Release (push) Skipped
2026-08-30 11:06:12 +02:00
FCS 57ccb75847 Build Version Anzeige, WiFi auto off, redirect to Settings Site 2026-08-30 11:04:03 +02:00
FCS e5453b29fa korrekture Release
build_release.yaml / Build heltec_wifi_lora_32_V3_sailplane (push) Successful in 2m16s
build_release.yaml / Build heltec_wifi_lora_32_V3_winde (push) Successful in 2m55s
build_release.yaml / Release (push) Successful in 9s
2026-08-28 19:08:14 +02:00
FCS ea79561bcf changelog ergänzt
build_release.yaml / Build heltec_wifi_lora_32_V3_sailplane (push) Successful in 4m1s
build_release.yaml / Build heltec_wifi_lora_32_V3_winde (push) Successful in 3m14s
build_release.yaml / Release (push) Successful in 18s
2026-08-28 18:54:06 +02:00
FCS cbceec1887 korrektur Checksum 2026-08-28 18:53:03 +02:00
FCS e1ee1c560e Korrektur Autobuild
build_release.yaml / Build heltec_wifi_lora_32_V3_sailplane (push) Failing after 4m7s
build_release.yaml / Build heltec_wifi_lora_32_V3_winde (push) Failing after 3m39s
build_release.yaml / Release (push) Skipped
2026-08-28 18:44:55 +02:00
FCS 97fb018d51 autobuild Remove tag Validation 2026-08-28 18:40:27 +02:00
FCS b987f7c21c Validate Tag 2026-08-28 18:38:00 +02:00
FCS fb2a0865ee Changelog 2026-08-28 18:33:17 +02:00
FCS 8ef86500db Verbesserung des AutoBuilds
build_release.yaml / Build heltec_wifi_lora_32_V3_sailplane (push) Failing after 13s
build_release.yaml / Build heltec_wifi_lora_32_V3_winde (push) Failing after 13s
build_release.yaml / Release (push) Skipped
2026-08-28 18:26:20 +02:00
FCS 0d2ff764ea Dokumentation um Webinterface, Einstellungen und OTA-Update erweitert
build_release.yaml / Build (push) Successful in 6m19s
build_release.yaml / Release (push) Successful in 19s
2026-08-28 18:07:57 +02:00
FCS 1014df8ec7 Change Versions Date
/ Build (push) Failing after 3m52s
/ Release (push) Has been skipped
build_release.yaml / Release (push) Canceled after 0s
build_release.yaml / Build (push) Canceled after 51s
2026-01-18 15:32:44 +01:00
FCS 9ee412126f send and Receive via NMEA, send time, non interrupt loop 2026-01-18 15:31:07 +01:00
14 changed files with 1200 additions and 157 deletions

No files matched your search

+90 -23
View File
@@ -1,46 +1,72 @@
on:
push:
tags:
- "*"
- "v*"
concurrency:
group: release-${{ gitea.ref }}
cancel-in-progress: true
jobs:
build:
env:
RUNNER_TOOL_CACHE: /toolcache
PLATFORMIO_BUILD_CACHE_DIR: ~/.platformio/.build_cache
PLATFORMIO_CACHE_DIR: ~/.platformio/.cachedir
name: Build
name: Build ${{ matrix.environment }}
strategy:
fail-fast: false
matrix:
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:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Cache
uses: actions/cache@v4
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: |
~/.cache/pip
~/.platformio/.cache
~/.platformio/.build_cache
~/.platformio/.cachedir
key: ${{ runner.os }}-pio
key: ${{ runner.os }}-pio-6.1.18-${{ hashFiles('platformio.ini', 'extra_script.py') }}
restore-keys: |
${{ runner.os }}-pio-6.1.18-
${{ runner.os }}-pio-
- name: Set up python
uses: actions/setup-python@v5
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.14.0"
architecture: "x64"
- name: Install PlatformIO
run: python3 -m pip install platformio==6.1.18
- name: Build firmwares
run: FIRMWARE_VERSION=${{gitea.ref_name}} platformio run
- name: Archive
uses: christopherhx/gitea-upload-artifact@v4
- name: Static analysis
run: python3 -m platformio check --environment "${{ matrix.environment }}"
- name: Build firmware
env:
FIRMWARE_VERSION: ${{ gitea.ref_name }}
run: python3 -m platformio run --environment "${{ matrix.environment }}"
- name: Verify firmware and create checksum
run: |
FIRMWARE_FILE=$(find ".pio/build/${{ matrix.environment }}" -maxdepth 1 -type f -name 'firmware_*.bin' -print -quit)
test -n "$FIRMWARE_FILE"
sha256sum "$FIRMWARE_FILE" > "firmware-${{ matrix.environment }}.sha256"
- name: Archive firmware
uses: christopherhx/gitea-upload-artifact@62ac910c5d3dfa85c7cb2df15afe2e342b2407c2 # v4
with:
name: firmware
path: .pio/build/*/firmware_*.bin
name: firmware-${{ matrix.environment }}
path: |
.pio/build/${{ matrix.environment }}/firmware_*.bin
firmware-${{ matrix.environment }}.sha256
retention-days: 1
release:
name: Release
# if: startsWith(github.event.ref, 'refs/tags/v')
if: startsWith(gitea.ref, 'refs/tags/v')
needs: build
permissions:
contents: write
@@ -48,17 +74,58 @@ jobs:
container: catthehacker/ubuntu:act-latest
steps:
- name: Checkout repo for CHANGELOG
uses: actions/checkout@v4
- name: Download artifacts
uses: christopherhx/gitea-download-artifact@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Download Sailplane artifact
uses: christopherhx/gitea-download-artifact@75635f32b4c1c41c4b3d64e8f85210112ed4c9c7 # v4
with:
name: firmware
path: firmware
# - name: Display structure of downloaded files
# run: ls -R
- name: release
uses: akkuman/gitea-release-action@v1
name: firmware-heltec_wifi_lora_32_V3_sailplane
path: firmware/sailplane
- name: Download Winde artifact
uses: christopherhx/gitea-download-artifact@75635f32b4c1c41c4b3d64e8f85210112ed4c9c7 # v4
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
uses: akkuman/gitea-release-action@5a83c90484ef4355121df5eb60558c8ce66e81ac # v1
with:
files: |-
**/firmware_*.bin
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"
+75 -1
View File
@@ -1,6 +1,80 @@
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
## 0.0.5 (30.11.2025)
### 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-<Callsign>` bzw. `Telemetry-Winch`)
+ Webinterface zeigt aktuell geladene Firmware-Version an
+ Speichern der Einstellungen springt nach dem Speichern auf die Startseite mit Erfolg/Fehlerstatus zurück
+ 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
+ Lizenzinformationen für Projektcode und Drittanbieterbibliotheken ergänzt
+ Firmware-Upload über das Webinterface für beide Firmwarevarianten ergänzt
### Segeflug Modul
+ Einstellbare minimale und maximale Schleppgeschwindigkeit
+ Einstellbare maximale Übertragungszeit, Standardwert 90 Sekunden
+ Geladene Einstellungen beim Programmstart verwendet
+ Sensorinitialisierung und Messwertverarbeitung robustifiziert
### Winden Modul
+ Einstellbare WLAN-Konfiguration über das Webinterface
+ Standard-WLAN-Name `Winde-Telemetry`
+ Initialisierung von Servo und LED-Ring korrigiert
+ NMEA-Nachrichtenprüfung und Fehlerbehandlung beim Empfang verbessert
---
## v0.0.6 (18.01.2026)
+ Anpassung Programmablaufplan
### Segeflug Modul
+ Umsetzen der Sender-Logik
+ senden im NMEA Format
### Winden Modul
+ empfangen im NMEA Format
---
## v0.0.5 (30.11.2025)
+ Formatierung Code
### Segeflug Modul
+ Luftdrucksensor (BMP180) hinzugefügt, für Höhe und Vario
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Winden-Telemetry contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+49 -16
View File
@@ -4,6 +4,14 @@
# 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
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md); der vollständige Lizenztext
für den Projektcode steht in [LICENSE](LICENSE).
Übertragung der IAS des Segelflugzeuges an die Winde
@@ -19,16 +27,38 @@
### Features:
- [x] LORA zum Übertragen
- [x] Airspeed via differenz Drucksensor
- [ ] Webinterface
- [ ] min/max speed einstellbar
- [ ] Kennzeichen einstellbar
- [x] Webinterface über eigenen Access Point
- [x] min/max speed per Webinterface einstellbar
- [x] Kennzeichen per Webinterface einstellbar
- [x] maximale Übertragungszeit per Webinterface einstellbar (Standard: 90 Sekunden)
- [x] Firmware-Upload per Webinterface für beide Firmwarevarianten
- [x] Analoge Anzeige in der Winde
- [x] Farbige LEDs für Limits
- [ ] Übertragungszeit Begrenzen
- [ ] Übertragungszeit einstellbar
- [ ] Übertragung Rundumleuchten Signal an Start
- [x] Übertragungszeit Begrenzen
- [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
Beide Firmwarevarianten starten einen eigenen WLAN-Access-Point. Der
voreingestellte WLAN-Name ist bei Sailplane `Sailplane-D1234` und bei der
Winde `Winde-Telemetry`.
- Passwort: `telemetry`
- Webadresse: `http://192.168.4.1/`
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 passende `.bin`-Datei aus `.pio/build/<environment>/`
auswählen. Nach einem erfolgreichen Upload startet das Gerät automatisch neu.
## Aufbau
Pin Layout Heltec WiFi LoRa (v3)
@@ -50,7 +80,7 @@ Pin Layout Heltec WiFi LoRa (v3)
## PCB
Die Platine ist so gestaltet das sie Induvidul bestückt werden kann, je nach Verwendung
Die Platine ist so gestaltet das sie individuell bestückt werden kann, je nach Verwendung
![Platine bestückt](img/Winden_Telemetry_PCB.png)
@@ -68,9 +98,9 @@ Die Platine ist so gestaltet das sie Induvidul bestückt werden kann, je nach Ve
| 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 |
@@ -81,21 +111,24 @@ flowchart TD
A(["Start"])
A --> B{"ist Senden an?"}
B --> | Ja | C{"Sende Timer <90 sec"}
B --> | Nein | E{"low speed timer <90 sec"}
I --> | Nein | E{"is airborn?"}
C --> | Nein | F["Sende Schleppen komplett"] -->G["Starte Low Speed Timer"]
C --> | Nein | F["Sende Schleppen komplett"] -->G["is airborn=true"] -->L["Lowspeedtimer = 0"]
C --> | Ja | D["Sende Speed"]
D --> Z["Warte 1 Sekunde"]
G ----> H["Senden aus"] -->Z
L ----> H["Senden aus"] -->Z
Z --> B
E --> | nein | I{"Speed < 30 km/h"}
I --> | nein |J["Senden an"] --> D
I --> | ja|Z
E --> | ja|Z
B --> | nein | I{"Speed < 30 km/h"}
E --> | nein |J["Senden an"] -->P["Start Sendetimer"] --> D
I --> | ja| K["Lowspeedtimer + 1"]
K --> M{"lowspeedtimer >90"}
M --> | ja | N["is airborn=false"] --> Z
M --> | nein | Z
E --> | ja |O["Lowspeedtimer = 0"] --> Z
```
+44
View File
@@ -0,0 +1,44 @@
# Third-party notices
This project contains or links to the following third-party software. The
listed versions are the versions resolved by PlatformIO on 2026-08-28.
Original license texts and copyright notices must be retained when the
corresponding software is redistributed.
## Direct and transitive library dependencies
| Component | Version | Used by | License | Source |
| --- | --- | --- | --- | --- |
| U8g2 | 2.36.18 | Sailplane, Winde | BSD-2-Clause; bundled fonts may have additional licenses | https://github.com/olikraus/u8g2 |
| RadioLib | 7.6.0 | Sailplane, Winde | MIT | https://github.com/jgromes/RadioLib |
| ArduinoJson | 7.4.3 | Sailplane, Winde | MIT | https://github.com/bblanchon/ArduinoJson |
| Bolder Flight Systems Airdata Calculations | 4.0.0 | Sailplane | MIT | https://github.com/bolderflight/airdata_calculations |
| Bolder Flight Systems MS4525 | 1.1.3 | Sailplane | MIT | https://github.com/bolderflight/ms4525 |
| Bolder Flight Systems Unit Conversions | 5.0.0 | Sailplane | MIT | https://github.com/bolderflight/units |
| Adafruit BMP085 Library | 1.2.4 | Sailplane | BSD | https://github.com/adafruit/Adafruit-BMP085-Library |
| Adafruit BusIO | 1.17.4 | Sailplane, transitive | MIT | https://github.com/adafruit/Adafruit_BusIO |
| Adafruit Unified Sensor | 1.1.15 | Sailplane, transitive | Apache-2.0 | https://github.com/adafruit/Adafruit_Sensor |
| Adafruit NeoPixel | 1.15.4 | Winde | LGPL-3.0-or-later | https://github.com/adafruit/Adafruit_NeoPixel |
| ESP32Servo | 3.1.3 | Winde | LGPL-2.1-or-later | https://github.com/madhephaestos/ESP32Servo |
## Build framework
| Component | Version | License | Source |
| --- | --- | --- | --- |
| Arduino core for ESP32 | 3.20017.241212+sha.dcc1105b | LGPL-2.1-or-later | https://github.com/espressif/arduino-esp32 |
The PlatformIO platform and build tools are development dependencies. Their
licenses are distributed with their respective PlatformIO packages and are
not relicensed by this project.
## License compatibility
The source code in this repository is licensed under the MIT License in
`LICENSE`. MIT is compatible with the MIT, BSD, Apache-2.0 and LGPL components
listed above. The LGPL components remain under their original licenses; this
project does not change or relicense them.
For a release containing compiled firmware, distribute this file together
with `LICENSE` and retain the copyright and license notices supplied by the
third-party components. U8g2 fonts must be checked separately when changing
fonts, because their licensing terms can differ from the U8g2 library code.
+22 -3
View File
@@ -1,8 +1,27 @@
import os
Import("env")
from typing import Any
# PlatformIO injects Import() and env at runtime. The editor does not know these
# names at file-load time, so we keep a short, explicit guard and silence the
# static-analysis warning for the SCons hook itself.
try:
Import("env") # type: ignore[name-defined]
except NameError:
env: Any = None
if env is None:
raise RuntimeError("This script must be run by PlatformIO's SCons environment.")
# Access to global construction environment
build_tag = env['PIOENV']
version_tag = os.getenv("FIRMWARE_VERSION")
build_tag = env["PIOENV"]
version_tag = os.getenv("FIRMWARE_VERSION", "dev").strip()
# CPPDEFINES ensures the macro expands to a valid C string literal instead of a bare identifier.
env.Append(CPPDEFINES=[("FIRMWARE_VERSION", f'\\"{version_tag}\\"')])
env.Replace(PROGNAME="firmware_%s_%s" % (build_tag, version_tag))
# include toolchain paths
env.Replace(COMPILATIONDB_INCLUDE_TOOLCHAIN=True)
# override compilation DB path
env.Replace(COMPILATIONDB_PATH=os.path.join("$BUILD_DIR", "compile_commands.json"))
+39 -14
View File
@@ -11,12 +11,19 @@ bfs::Ms4525do pres;
Adafruit_BMP085 bmp;
float pressure_avg[10];
float basicaltitude;
void init_2ndi2c(){
Wire1.begin(dp_sda, dp_scl);
float basicaltitude = 0.0f;
bool pressure_sensor_ready = false;
bool init_2ndi2c(){
if (!Wire1.begin(dp_sda, dp_scl))
{
Serial.println("Error starting I2C");
return false;
}
Wire1.setClock(400000);
return true;
}
void init_airspeed_sensor(void)
bool init_airspeed_sensor(void)
{
/*
@@ -27,10 +34,9 @@ void init_airspeed_sensor(void)
if (!pres.Begin())
{
Serial.println("Error communicating with sensor");
while (1)
{
}
return false;
}
return true;
}
int get_airspeed_kmh(void)
@@ -51,10 +57,22 @@ float get_airspeed(void)
return 0;
}
}
void init_pressure_sensor(void)
bool init_pressure_sensor(void)
{
bmp.begin(BMP085_ULTRAHIGHRES, &Wire1);
if (!bmp.begin(BMP085_ULTRAHIGHRES, &Wire1))
{
Serial.println("Error communicating with pressure sensor");
return false;
}
basicaltitude=bmp.readPressure();
if (basicaltitude <= 0.0f)
{
Serial.println("Invalid initial pressure");
return false;
}
for (float &sample : pressure_avg)
sample = basicaltitude;
pressure_sensor_ready = true;
Serial.print("Pressure = ");
Serial.print(basicaltitude);
Serial.println(" Pa");
@@ -64,21 +82,28 @@ void init_pressure_sensor(void)
Serial.print("Altitude = ");
Serial.print(get_altitude(basicaltitude));
Serial.println(" meters");
return true;
}
float get_pressure()
{
if (!pressure_sensor_ready)
return 0.0f;
float pressure;
pressure = bmp.readPressure();
memcpy(pressure_avg, &pressure_avg[1], sizeof(pressure_avg) - sizeof(int));
if (pressure <= 0.0f)
return pressure_avg[9];
memmove(pressure_avg, &pressure_avg[1], sizeof(pressure_avg) - sizeof(pressure_avg[0]));
pressure_avg[9] = pressure;
return pressure;
}
float get_pressure_avg(void)
{
float total = 0.0f;
int i;
float total;
for (i = 0; i <10; i++)
{
total = total + pressure_avg[i];
@@ -97,14 +122,14 @@ float get_vario_avg(void)
float vario;
int i;
float total;
float total = 0.0f;
for (i = 0; i <9; i++)
{
int n=i+1;
vario = get_altitude(pressure_avg[n]) - get_altitude(pressure_avg[i]);
total = total + vario;
}
return (total / 10);
return (total / 9);
}
float get_altitude(float pressure)
@@ -115,5 +140,5 @@ float get_altitude(float pressure)
}
float get_altitude_diff(void){
return bmp.readPressure() - basicaltitude;
return get_altitude(pressure_avg[9]) - get_altitude(basicaltitude);
}
+3 -3
View File
@@ -1,8 +1,8 @@
#ifndef WINDENTELEMETRY_AIRSPEED_H
#define WINDENTELEMETRY_AIRSPEED_H
void init_2ndi2c(void);
void init_airspeed_sensor(void);
bool init_2ndi2c(void);
bool init_airspeed_sensor(void);
int get_airspeed_kmh(void);
/*!
@@ -10,7 +10,7 @@ int get_airspeed_kmh(void);
*/
float get_airspeed(void);
void init_pressure_sensor(void);
bool init_pressure_sensor(void);
float get_pressure(void);
float get_pressure_adv(void);
float get_altitude(float pressure);
+430
View File
@@ -0,0 +1,430 @@
#include "webinterface.h"
#include <Arduino.h>
#include <DNSServer.h>
#include <Preferences.h>
#include <Update.h>
#include <WebServer.h>
#include <WiFi.h>
#include <cctype>
namespace
{
constexpr char default_access_point_password[] = "telemetry";
constexpr char settings_namespace[] = "telemetry";
constexpr char default_callsign[] = "D1234";
constexpr int default_min_speed = 80;
constexpr int default_max_speed = 150;
constexpr unsigned int default_active_time_seconds = 90;
constexpr unsigned long access_point_timeout_ms = 5UL * 60UL * 1000UL;
constexpr char telemetry_ap_prefix[] = "Telemetry-";
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
}
String generated_access_point_name(const String &callsign)
{
#ifdef DEVICE_SAILPLANE
String safe_callsign = 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
}
String url_encode(const String &value)
{
String encoded;
encoded.reserve(value.length() * 3);
for (unsigned int i = 0; i < value.length(); ++i)
{
const char c = value.charAt(i);
if (std::isalnum(static_cast<unsigned char>(c)) || c == '-' || c == '_' || c == '.' || c == '~')
encoded += c;
else
{
char buffer[4];
snprintf(buffer, sizeof(buffer), "%%%02X", static_cast<unsigned char>(c));
encoded += buffer;
}
}
return encoded;
}
WebServer server(80);
DNSServer dns_server;
Preferences preferences;
TelemetrySettings *active_settings = nullptr;
bool restart_after_update = false;
bool update_failed = false;
bool access_point_restart_pending = false;
bool access_point_active = false;
unsigned long access_point_started_ms = 0UL;
String html_escape(const String &value)
{
String escaped = value;
escaped.replace("&", "&amp;");
escaped.replace("<", "&lt;");
escaped.replace(">", "&gt;");
escaped.replace("\"", "&quot;");
escaped.replace("'", "&#39;");
return escaped;
}
bool is_captive_portal_target(const String &host, const String &uri)
{
String lowercase_host = host;
lowercase_host.toLowerCase();
String lowercase_uri = uri;
lowercase_uri.toLowerCase();
static const char *captive_hosts[] = {
"connectivitycheck.gstatic.com",
"clients3.google.com",
"google.com",
"www.google.com",
"msftconnecttest.com",
"msftncsi.com",
"captive.apple.com",
"www.apple.com",
"apple.com",
"connectivitycheck.android.com"};
for (const char *candidate : captive_hosts)
{
if (lowercase_host == candidate)
return true;
}
static const char *captive_paths[] = {
"/generate_204",
"/hotspot-detect.html",
"/library/test/success.html",
"/connecttest.txt",
"/redirect",
"/ncsi.txt",
"/success.txt",
"/wap"};
for (const char *candidate : captive_paths)
{
if (lowercase_uri == candidate)
return true;
}
return false;
}
void redirect_to_root(const String &message, bool success)
{
String location = String("/?status=") + (success ? "success" : "error") + "&msg=" + url_encode(message);
server.sendHeader("Location", location, true);
server.send(303, "text/plain; charset=utf-8", "");
}
void handle_captive_portal_redirect()
{
String host = server.hostHeader();
String uri = server.uri();
if (is_captive_portal_target(host, uri))
{
server.sendHeader("Location", "/", true);
server.send(302, "text/plain; charset=utf-8", "");
return;
}
server.send(404, "text/plain; charset=utf-8", "Not Found");
}
void send_page(const String &message = String())
{
String page;
page.reserve(2600);
page += F("<!doctype html><html lang='de'><head><meta charset='utf-8'>"
"<meta name='viewport' content='width=device-width,initial-scale=1'>"
"<title>Winden-Telemetry</title><style>"
"body{font-family:sans-serif;max-width:38rem;margin:2rem auto;padding:0 1rem}"
"label{display:block;margin-top:1rem}input{box-sizing:border-box;width:100%;padding:.6rem;font-size:1rem}"
"button{margin-top:1rem;padding:.65rem 1rem;font-size:1rem}"
".msg{padding:.6rem;background:#e5f4e8}.error{padding:.6rem;background:#fbe5e5}small{color:#555}"
"</style></head><body><h1>Winden-Telemetry</h1>");
if (message.length() > 0)
{
page += F("<p class='");
page += String(message.startsWith("Fehler") || message.startsWith("Ungueltig") || message.startsWith("Invalid") ? "error" : "msg");
page += F("'>");
page += html_escape(message);
page += F("</p>");
}
page += F("<p><small>Firmware: ");
page += html_escape(firmware_version_string());
page += F("</small></p><form method='post' action='/settings'>"
"<label>WLAN-Name<input name='ap_name' maxlength='32' required value='");
page += html_escape(active_settings->access_point_name);
page += F("'></label><label>WLAN-Passwort<input name='ap_password' type='password' minlength='8' maxlength='63' required value='");
page += html_escape(active_settings->access_point_password);
page += F("'></label>");
#ifdef DEVICE_SAILPLANE
page += F("<label>Callsign<input name='callsign' maxlength='16' required value='");
page += html_escape(active_settings->callsign);
page += F("'></label><label>Min Speed (km/h)<input name='min' type='number' min='0' max='300' required value='");
page += String(active_settings->min_speed);
page += F("'></label><label>Max Speed (km/h)<input name='max' type='number' min='1' max='300' required value='");
page += String(active_settings->max_speed);
page += F("'></label>");
#endif
#ifdef DEVICE_SAILPLANE
page += F("<label>Uebertragungszeit (Sekunden)<input name='active_time' type='number' min='1' max='600' required value='");
page += String(active_settings->active_time_seconds);
page += F("'></label>");
#endif
page += F("<button type='submit'>Speichern</button></form>"
"<h2>Firmware aktualisieren</h2><form method='post' action='/update' enctype='multipart/form-data'>"
"<input name='firmware' type='file' accept='.bin' required><button type='submit'>Upload starten</button>"
"</form><p><small>Access Point: ");
page += html_escape(active_settings->access_point_name);
page += F("</small></p></body></html>");
server.send(200, "text/html; charset=utf-8", page);
}
bool parse_settings(TelemetrySettings &settings)
{
#ifdef DEVICE_SAILPLANE
String callsign = server.arg("callsign");
int min_speed = server.arg("min").toInt();
int max_speed = server.arg("max").toInt();
int active_time_seconds = server.arg("active_time").toInt();
callsign.trim();
if (callsign.length() == 0 || callsign.length() > 16 || min_speed < 0 || max_speed > 300 || min_speed >= max_speed || active_time_seconds < 1 || active_time_seconds > 600)
return false;
#else
String callsign = settings.callsign;
int min_speed = settings.min_speed;
int max_speed = settings.max_speed;
int active_time_seconds = settings.active_time_seconds;
#endif
String access_point_name = server.arg("ap_name");
String access_point_password = server.arg("ap_password");
access_point_name.trim();
access_point_password.trim();
if (access_point_password.length() < 8 || access_point_password.length() > 63)
return false;
settings.callsign = callsign;
settings.access_point_name = generated_access_point_name(callsign);
settings.access_point_password = access_point_password;
settings.min_speed = min_speed;
settings.max_speed = max_speed;
settings.active_time_seconds = active_time_seconds;
return true;
}
void handle_root()
{
String message = server.arg("msg");
String status = server.arg("status");
if (status == "error" && message.length() == 0)
message = "Ungueltige Einstellungen";
else if (status == "success" && message.length() == 0)
message = "Einstellungen gespeichert.";
send_page(message);
}
void handle_settings()
{
TelemetrySettings updated = *active_settings;
if (!parse_settings(updated))
{
redirect_to_root("Ungueltige Einstellungen", false);
return;
}
const bool ap_name_changed = active_settings->access_point_name != updated.access_point_name;
const bool ap_password_changed = active_settings->access_point_password != updated.access_point_password;
active_settings->callsign = updated.callsign;
active_settings->access_point_name = updated.access_point_name;
active_settings->access_point_password = updated.access_point_password;
active_settings->min_speed = updated.min_speed;
active_settings->max_speed = updated.max_speed;
active_settings->active_time_seconds = updated.active_time_seconds;
preferences.putString("callsign", active_settings->callsign);
preferences.putString("ap_name", active_settings->access_point_name);
preferences.putString("ap_password", active_settings->access_point_password);
preferences.putInt("min", active_settings->min_speed);
preferences.putInt("max", active_settings->max_speed);
preferences.putUInt("active_time", active_settings->active_time_seconds);
if (ap_name_changed || ap_password_changed)
access_point_restart_pending = true;
redirect_to_root("Einstellungen gespeichert.", true);
}
void handle_update_upload()
{
HTTPUpload &upload = server.upload();
if (upload.status == UPLOAD_FILE_START)
{
restart_after_update = false;
update_failed = false;
Serial.printf("Firmware-Upload: %s\n", upload.filename.c_str());
if (!Update.begin(UPDATE_SIZE_UNKNOWN))
{
update_failed = true;
Update.printError(Serial);
}
}
else if (upload.status == UPLOAD_FILE_WRITE)
{
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize)
{
update_failed = true;
Update.printError(Serial);
}
}
else if (upload.status == UPLOAD_FILE_END)
{
if (Update.end(true))
{
restart_after_update = true;
Serial.printf("Firmware-Upload abgeschlossen: %u Bytes\n", upload.totalSize);
}
else
{
update_failed = true;
Update.printError(Serial);
}
}
}
void handle_update_result()
{
if (!update_failed && restart_after_update)
{
server.send(200, "text/plain; charset=utf-8", "Upload erfolgreich. Neustart...");
if (restart_after_update)
{
delay(500);
ESP.restart();
}
}
else
server.send(500, "text/plain; charset=utf-8", "Firmware-Upload fehlgeschlagen");
}
} // namespace
String firmware_version_string(void)
{
#ifdef FIRMWARE_VERSION
return String(FIRMWARE_VERSION);
#else
return "dev";
#endif
}
bool webinterface_begin(TelemetrySettings &settings)
{
active_settings = &settings;
preferences.begin(settings_namespace, false);
settings.callsign = preferences.getString("callsign", default_callsign);
settings.access_point_name = preferences.getString("ap_name", default_access_point_name());
settings.access_point_password = preferences.getString("ap_password", default_access_point_password);
settings.min_speed = preferences.getInt("min", default_min_speed);
settings.max_speed = preferences.getInt("max", default_max_speed);
settings.active_time_seconds = preferences.getUInt("active_time", default_active_time_seconds);
bool settings_valid = settings.access_point_name.length() > 0 && settings.access_point_name.length() <= 32 && settings.access_point_password.length() >= 8 && settings.access_point_password.length() <= 63;
#ifdef DEVICE_SAILPLANE
settings_valid = settings_valid && settings.callsign.length() > 0 && settings.min_speed >= 0 && settings.min_speed < settings.max_speed && settings.max_speed <= 300 && settings.active_time_seconds >= 1 && settings.active_time_seconds <= 600;
#endif
if (!settings_valid)
{
settings.callsign = default_callsign;
settings.access_point_name = default_access_point_name();
settings.access_point_password = default_access_point_password;
settings.min_speed = default_min_speed;
settings.max_speed = default_max_speed;
settings.active_time_seconds = default_active_time_seconds;
}
else
{
settings.access_point_name = generated_access_point_name(settings.callsign);
}
WiFi.mode(WIFI_AP);
bool ap_started = WiFi.softAP(settings.access_point_name, settings.access_point_password);
if (!ap_started)
return false;
access_point_active = true;
access_point_started_ms = millis();
dns_server.start(53, "connectivitycheck.gstatic.com", WiFi.softAPIP());
dns_server.start(53, "clients3.google.com", WiFi.softAPIP());
dns_server.start(53, "msftconnecttest.com", WiFi.softAPIP());
dns_server.start(53, "msftncsi.com", WiFi.softAPIP());
dns_server.start(53, "captive.apple.com", WiFi.softAPIP());
dns_server.start(53, "apple.com", WiFi.softAPIP());
dns_server.start(53, "*", WiFi.softAPIP());
server.on("/", HTTP_GET, handle_root);
server.on("/generate_204", HTTP_GET, handle_captive_portal_redirect);
server.on("/hotspot-detect.html", HTTP_GET, handle_captive_portal_redirect);
server.on("/connecttest.txt", HTTP_GET, handle_captive_portal_redirect);
server.on("/ncsi.txt", HTTP_GET, handle_captive_portal_redirect);
server.on("/success.txt", HTTP_GET, handle_captive_portal_redirect);
server.on("/settings", HTTP_POST, handle_settings);
server.on("/update", HTTP_POST, handle_update_result, handle_update_upload);
server.onNotFound(handle_captive_portal_redirect);
server.begin();
Serial.print("Firmware-Version: ");
Serial.println(firmware_version_string());
Serial.print("Webinterface: http://");
Serial.println(WiFi.softAPIP());
return true;
}
void webinterface_handle(void)
{
server.handleClient();
dns_server.processNextRequest();
if (access_point_restart_pending)
{
access_point_restart_pending = false;
WiFi.softAPdisconnect(true);
access_point_active = false;
delay(100);
if (!WiFi.softAP(active_settings->access_point_name, active_settings->access_point_password))
Serial.println("Access Point konnte nicht neu gestartet werden");
else
access_point_active = true;
access_point_started_ms = millis();
}
if (access_point_active && WiFi.softAPgetStationNum() == 0 && (millis() - access_point_started_ms) >= access_point_timeout_ms)
{
Serial.println("Access Point nach 5 Minuten ohne Client deaktiviert");
WiFi.softAPdisconnect(true);
access_point_active = false;
}
}
const TelemetrySettings &webinterface_settings(void)
{
return *active_settings;
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef WINDENTELEMETRY_WEBINTERFACE_H
#define WINDENTELEMETRY_WEBINTERFACE_H
#include <Arduino.h>
struct TelemetrySettings
{
String callsign;
String access_point_name;
String access_point_password;
int min_speed;
int max_speed;
unsigned int active_time_seconds;
};
String firmware_version_string(void);
bool webinterface_begin(TelemetrySettings &settings);
void webinterface_handle(void);
const TelemetrySettings &webinterface_settings(void);
#endif
+3 -10
View File
@@ -8,15 +8,14 @@ void init_servo(void)
{
myservo.setPeriodHertz(50);
myservo.attach(servoPin);
set_tacho(0, 70, 150);
}
void set_tacho(int speed, int speed_min, int speed_max)
{
int val = map(speed, -25, 220, 0, 155);
#ifdef DEBUG
Serial.print("Tacho Mapping: ")
Serial.println(val);
Serial.print("Tacho Mapping: ");
Serial.println(val);
#endif
myservo.write(val);
pixels.clear();
@@ -72,11 +71,5 @@ void init_pixel(void)
int setoffsetpixel(int pixel)
{
int newpixel;
newpixel = pixel + pixeloffset;
if (newpixel >= NUMPIXELS)
{
newpixel = newpixel - NUMPIXELS;
}
return newpixel;
return (pixel + pixeloffset + NUMPIXELS) % NUMPIXELS;
}
+15 -2
View File
@@ -2,6 +2,12 @@
#define WINDENTELEMETRY_CONFIG_H
#define LORA_FREQ 868.0
#define TELEMETRY_CALLSIGN "D1234"
#define TELEMETRY_SEND_INTERVAL_MS 1000UL
#define TELEMETRY_ACTIVE_TIME_MS 90000UL
#define TELEMETRY_LOW_SPEED_KMH 30
#define default_min_tow_speed 80
#define default_max_tow_speed 150
#ifdef HELTEC_WIFI_LORA_32_V3
// SX1262 on HELTEC_WIFI_LORA_32_V3 has the following connections:
// NSS pin: 8
@@ -27,8 +33,6 @@
/**difference pressure**/
#define dp_scl 20
#define dp_sda 19
#define default_min_tow_speed 80
#define default_max_tow_speed 150
#endif
#ifdef DEVICE_WINDE
@@ -42,6 +46,15 @@
// How many NeoPixels are attached
#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
+21 -8
View File
@@ -9,8 +9,8 @@
; https://docs.platformio.org/page/projectconf.html
[platformio]
build_cache_dir = ./.pio/.build_cache
cache_dir = ./.pio/.cache_dir
build_cache_dir = ~/.pio/.build_cache
cache_dir = ~/.pio/.cache_dir
[common]
platform = espressif32
@@ -25,6 +25,8 @@ build_flags =
-D CORE_DEBUG_LEVEL=5
-D SLOW_CLK_TPYE=0
-D REGION_EU868
-Ilib
-Isrc
lib_deps =
olikraus/U8g2@^2.36.15
jgromes/RadioLib@^7.4.0
@@ -41,12 +43,13 @@ lib_deps =
bolderflight/Bolder Flight Systems Unit Conversions@^5.0.0
bolderflight/Bolder Flight Systems Airdata Calculations@^4.0.0
adafruit/Adafruit BMP085 Library @ ^1.2.4
lib_ignore =
winde
build_flags = ${common.build_flags}
lib_ignore = winde
build_flags =
${common.build_flags}
-D DEVICE_SAILPLANE
upload_port = COM8
monitor_port = COM8
[env:heltec_wifi_lora_32_V3_winde]
extends = common
board = heltec_wifi_lora_32_V3
@@ -54,9 +57,19 @@ lib_deps =
${common.lib_deps}
adafruit/Adafruit NeoPixel@^1.15.2
madhephaestus/ESP32Servo@^3.0.9
lib_ignore =
sailplane
build_flags = ${common.build_flags}
lib_ignore = sailplane
build_flags =
${common.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
+367 -77
View File
@@ -3,87 +3,195 @@
#include <U8g2lib.h>
#include <RadioLib.h>
#include <ArduinoJson.h>
#include <webinterface.h>
#ifdef DEVICE_WINDE
#include <tacho.h>
#endif
TelemetrySettings telemetry_settings;
#ifdef DEVICE_SAILPLANE
#include <airspeed.h>
JsonDocument sailplane_data;
unsigned long previousMillisSend = 0;
unsigned long previousMillissendtimer = 0;
bool send_data = false;
bool is_airborn = false;
unsigned long lowspeedtimer = 0;
bool sensors_ready = false;
#endif
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;
// this function is called when a complete packet
// is received by the module
// IMPORTANT: this function MUST be 'void' type
// and MUST NOT have any arguments!
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);
#endif
#ifdef DEVICE_WINDE
String data_from;
int data_speed = 0;
int data_min = 0;
int data_max = 0;
#endif
void setup()
{
/* Serial to display data */
Serial.begin(115200);
Serial.println("start");
if (!webinterface_begin(telemetry_settings))
Serial.println("Webinterface konnte nicht gestartet werden");
int state = radio.begin(LORA_FREQ);
if (state == RADIOLIB_ERR_NONE)
{
radio_ready = true;
Serial.println(F("success!"));
}
else
{
Serial.print(F("failed, code "));
Serial.println(state);
while (true)
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)
{
delay(10);
Serial.print(F("SKP Receive failed, code "));
Serial.println(state);
radio_ready = false;
}
}
#endif
u8g2.begin();
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB14_tr);
#ifdef DEVICE_SAILPLANE
u8g2.drawStr(0, 20, "Start Plane");
init_2ndi2c();
init_airspeed_sensor();
init_pressure_sensor();
sensors_ready = init_2ndi2c() && init_airspeed_sensor() && init_pressure_sensor();
if (!sensors_ready)
{
u8g2.drawStr(0, 40, "Sensor error");
}
#endif
#ifdef DEVICE_WINDE
u8g2.drawStr(0, 20, "Start Winde");
init_servo();
init_pixel();
init_servo();
set_tacho(0, telemetry_settings.min_speed, telemetry_settings.max_speed);
radio.setPacketReceivedAction(setFlag);
// start listening for LoRa packets
state = radio.startReceive();
if (state == RADIOLIB_ERR_NONE)
if (radio_ready)
{
Serial.println(F("success!"));
}
else
{
Serial.print(F("failed, code "));
Serial.println(state);
while (true)
state = radio.startReceive();
if (state == RADIOLIB_ERR_NONE)
{
delay(10);
Serial.println(F("success!"));
}
else
{
Serial.print(F("failed, code "));
Serial.println(state);
radio_ready = false;
}
}
#endif
@@ -121,53 +229,245 @@ void draw_rssi(int Rssi){
}
void send_lora(String data)
{
if (!radio_ready)
return;
int state = radio.transmit(data);
if (state == RADIOLIB_ERR_NONE)
{
// the packet was successfully transmitted
Serial.println(F("success!"));
// print measured data rate
Serial.print(F("[SX1262] Datarate:\t"));
//Serial.print(radio.getDataRate());
Serial.println(F(" bps"));
}
else if (state == RADIOLIB_ERR_PACKET_TOO_LONG)
{
// the supplied packet was longer than 256 bytes
Serial.println(F("too long!"));
}
else if (state == RADIOLIB_ERR_TX_TIMEOUT)
{
// timeout occured while transmitting packet
Serial.println(F("timeout!"));
}
else
{
// some other error occurred
Serial.print(F("failed, code "));
Serial.println(state);
}
}
void send_speed(int vel_kmh)
{
String data;
// serializeJson(sailplane_data, data);
// sprintf(data,"$PWTLS,%s,%u,%u,%u","D-1234",vel_kmh,70,140);
data = "$PWTLS," + telemetry_settings.callsign + "," + String(vel_kmh) + "," + String(telemetry_settings.min_speed) + "," + String(telemetry_settings.max_speed);
data += nmea_checksum(data);
Serial.println(data);
send_lora(data);
}
void send_airborn()
{
String data;
// serializeJson(sailplane_data, data);
data = "$PWTLA," + telemetry_settings.callsign + ",1";
data += nmea_checksum(data);
Serial.println(data);
send_lora(data);
}
String getSplitValue(String data, char separator, int index)
{
int found = 0;
int strIndex[] = {0, -1};
int maxIndex = data.length() - 1;
for (int i = 0; i <= maxIndex && found <= index; i++)
{
if (data.charAt(i) == separator || i == maxIndex)
{
found++;
strIndex[0] = strIndex[1] + 1;
strIndex[1] = (i == maxIndex) ? i + 1 : i;
}
}
return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
}
bool valid_nmea(String data)
{
int checksum_start = data.indexOf('*');
if (checksum_start <= 1 || checksum_start + 2 >= data.length())
return false;
byte checksum = 0;
for (int i = 1; i < checksum_start; ++i)
checksum ^= data[i];
String received_checksum = data.substring(checksum_start + 1, checksum_start + 3);
received_checksum.toUpperCase();
char expected_checksum[3];
snprintf(expected_checksum, sizeof(expected_checksum), "%02X", checksum);
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);
if (pos1 == "$PWTLS")
{
if (getSplitValue(data, ',', 4).length() == 0)
return false;
decode_start(data);
return true;
}
else if (pos1 == "$PWTLA")
{
decode_airborn(data);
return true;
}
else
{
Serial.println("Nachricht nicht erkannt");
return false;
}
}
#endif
void loop()
{
#ifdef DEVICE_SAILPLANE
/* Read the sensor */
get_pressure();
int vel_kmh = get_airspeed_kmh();
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_logisoso42_tr);
u8g2.setCursor(0, 60);
u8g2.print(vel_kmh);
u8g2.sendBuffer();
if (vel_kmh > 30)
webinterface_handle();
#ifdef DEVICE_WINDE
bool current_warning_light_state = read_warning_light_voltage();
if (current_warning_light_state != last_warning_light_state)
{
sailplane_data["from"]= "D-1234";
sailplane_data["telemetry"]["speed"]=vel_kmh;
sailplane_data["telemetry"]["min"]=70;
sailplane_data["telemetry"]["max"]=150;
String data;
serializeJson(sailplane_data,data);
//String data = String(vel_kmh);
int state = radio.transmit(data);
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)
{
// the packet was successfully transmitted
Serial.println(F("success!"));
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)
{
delay(1000);
return;
}
// print measured data rate
Serial.print(F("[SX1262] Datarate:\t"));
Serial.print(radio.getDataRate());
Serial.println(F(" bps"));
}
else if (state == RADIOLIB_ERR_PACKET_TOO_LONG)
unsigned long currentMillis = millis();
unsigned long active_time_ms = telemetry_settings.active_time_seconds * 1000UL;
if (currentMillis - previousMillisSend >= TELEMETRY_SEND_INTERVAL_MS)
{
previousMillisSend = currentMillis;
/* Read the sensor */
get_pressure();
int vel_kmh = get_airspeed_kmh();
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_logisoso42_tr);
u8g2.setCursor(0, 60);
u8g2.print(vel_kmh);
u8g2.sendBuffer();
if (send_data == false)
{
// the supplied packet was longer than 256 bytes
Serial.println(F("too long!"));
}
else if (state == RADIOLIB_ERR_TX_TIMEOUT)
{
// timeout occured while transmitting packet
Serial.println(F("timeout!"));
if (vel_kmh < telemetry_settings.min_speed)
{
lowspeedtimer += TELEMETRY_SEND_INTERVAL_MS;
if (lowspeedtimer >= active_time_ms)
{
is_airborn = false;
lowspeedtimer = active_time_ms;
}
}
else
{
if (is_airborn)
{
lowspeedtimer = 0;
}
else
{
send_data = true;
send_speed(vel_kmh);
previousMillissendtimer = currentMillis;
}
}
}
else
{
// some other error occurred
Serial.print(F("failed, code "));
Serial.println(state);
if (currentMillis - previousMillissendtimer < active_time_ms)
{
send_speed(vel_kmh);
}
else
{
send_airborn();
is_airborn = true;
lowspeedtimer = 0;
send_data = false;
}
}
#ifdef DEBUG
@@ -176,18 +476,17 @@ void loop()
Serial.println(vel_kmh);
Serial.print(pres.pres_pa(), 6);
Serial.print("IAS (m/s): ");
Serial.println(ias);
Serial.println(get_airspeed());
Serial.println(vel_kmh);
Serial.print("\t");
Serial.print(pres.die_temp_c(), 6);
Serial.print("\n");
#endif
delay(1000);
}
#endif
#ifdef DEVICE_WINDE
if (receivedFlag)
if (radio_ready && receivedFlag)
{
// reset flag
receivedFlag = false;
@@ -195,22 +494,14 @@ void loop()
// you can read received data as an Arduino String
String str;
int state = radio.readData(str);
JsonDocument received;
deserializeJson(received,str);
// you can also read received data as byte array
/*
byte byteArr[8];
int numBytes = radio.getPacketLength();
int state = radio.readData(byteArr, numBytes);
*/
if (state == RADIOLIB_ERR_NONE)
{
String data_from=received["from"];
int data_speed=received["telemetry"]["speed"];
int data_min=received["telemetry"]["min"];
int data_max=received["telemetry"]["max"];
if (!valid_nmea(str) || !decode_msg(str))
{
Serial.println("Ungueltige Telemetrie-Nachricht");
return;
}
// packet was successfully received
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB10_tr);
@@ -226,7 +517,6 @@ void loop()
draw_rssi(radio.getRSSI());
u8g2.sendBuffer();
Serial.println(str);
int speed = str.toInt();
set_tacho(data_speed, data_min, data_max);
}
}