11 Commits
Author SHA1 Message Date
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
OdomLinx 24e5b5afe6 README.md aktualisiert 2025-12-10 08:56:16 +00:00
FCS fa1c728187 Anpassung FlowChart 2025-12-08 13:08:41 +01:00
FCS e22e6e8ab3 Flowchart Segelflugzeug 2025-12-08 10:25:59 +01:00
FCS 7c979dbd01 PCB Layout 2025-12-05 13:36:46 +01:00
FCS 1c1eddc872 comports 2025-12-02 20:00:10 +01:00
FCS 3232c7e5c4 formating 2025-12-02 19:59:58 +01:00
FCS b4774d4b77 communicate via json, display rssi and from, dynamic min and max speed via json 2025-12-02 19:59:39 +01:00
FCS 54b89e7cb1 zusätzliche Bauteile und Anschluss Zuordnung 2025-12-02 18:47:57 +01:00
21 changed files with 812 additions and 98 deletions

No files matched your search

+28
View File
@@ -1,5 +1,33 @@
CHANGELOG CHANGELOG
## 0.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
## 0.0.6 (18.01.2025)
+ Anpassung Programmablaufplan
### Segeflug Modul
+ Umsetzen der Sender-Logik
+ senden im NMEA Format
### Winden Modul
+ empfangen im NMEA Format
## 0.0.5 (30.11.2025) ## 0.0.5 (30.11.2025)
+ Formatierung Code + Formatierung Code
### Segeflug Modul ### Segeflug Modul
+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.
+111 -8
View File
@@ -1,28 +1,131 @@
[![build status](https://gitea.rabe11.de/FCS/Winden-Telemetry/actions/workflows/build_release.yaml/badge.svg?event=push)](https://gitea.rabe11.de/FCS/Winden-Telemetry/releases) [![build status](https://gitea.rabe11.de/FCS/Winden-Telemetry/actions/workflows/build_release.yaml/badge.svg?event=push)](https://gitea.rabe11.de/FCS/Winden-Telemetry/releases)
![Gitea Release](https://img.shields.io/gitea/v/release/FCS/Winden-Telemetry?gitea_url=https%3A%2F%2Fgitea.rabe11.de)
# Winden-Telemetry # Winden-Telemetry
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 Übertragung der IAS des Segelflugzeuges an die Winde
Hardware: ### Hardware:
- [Heltec LoRa 32 (V3)](https://heltec.org/project/wifi-lora-32-v3/) - [Heltec LoRa 32 (V3)](https://heltec.org/project/wifi-lora-32-v3/)
- SG90 Servo - SG90 Servo
- MS4525 differenz Drucksensor - MS4525 differenz Drucksensor
- 12 LED Ring WS2812b - 12 LED Ring WS2812b 50mm
- BMP180 Temperatur und Luftdrucksensor - BMP180 Temperatur und Luftdrucksensor
- DC-DC Step Down Wandler 3A MP1584 - DC-DC Step Down Wandler 3A MP1584
- Relais - Relais
Features: ### Features:
- [x] LORA zum Übertragen - [x] LORA zum Übertragen
- [x] Airspeed via differenz Drucksensor - [x] Airspeed via differenz Drucksensor
- [ ] Webinterface - [x] Webinterface über eigenen Access Point
- [ ] min/max speed einstellbar - [x] min/max speed per Webinterface einstellbar
- [ ] Kennzeichen 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] Analoge Anzeige in der Winde
- [x] Farbige LEDs für Limits - [x] Farbige LEDs für Limits
- [ ] Übertragungszeit Begrenzen - [x] Übertragungszeit Begrenzen
- [ ] Übertragungszeit einstellbar
- [ ] Übertragung Rundumleuchten Signal an Start - [ ] Übertragung Rundumleuchten Signal an Start
- [x] Luftdrucksensor für Höhe und Vario - [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 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.
Für ein Firmware-Update die zur jeweiligen Hardware passende `.bin`-Datei aus
`.pio/build/<environment>/` auswählen. Nach erfolgreichem Upload startet das
Gerät automatisch neu.
## Aufbau
Pin Layout Heltec WiFi LoRa (v3)
![Heltec WiFi LoRa (v3)](https://heltec.org/wp-content/uploads/2023/09/pin.png)
### Anschlusszuordnung
| Heltec WiFi LoRa (v3) Pin | MP1584 |SG90 Pin | BMP180 Pin | MS4525 Pin | LED Ring Pin |
| --- | --- | --- | --- | --- | --- |
| GND | OUT -| GND | GND | GND | GND |
| 3.3 V| | | VCC | VCC | VCC |
| GPIO 6| | | | | IN |
| GPIO 7| | 1 PWM (Orange)| | |
| GPIO19| | | SDA| SDA |
| GPIO20| | | SCL| SCL|
| 5V | OUT + | VCC | | |
## PCB
Die Platine ist so gestaltet das sie individuell bestückt werden kann, je nach Verwendung
![Platine bestückt](img/Winden_Telemetry_PCB.png)
![Platinen Vorderseite](img/Winden_Telemetry_PCB_front.png)![Platinen Rückseite](img/Winden_Telemetry_PCB_back.png)
[Schaltplan](img/Winden_Telemetry_Schematic.pdf)
| Bauteil | Modul Segelflugzeug | Modul Winde | Modul SKP |
| --- |--- |--- |--- |
| J1 | x | x | x | optional|
| A1 | x | x | x | optional|
| I2C_1 | MS4525 | optional| optional|
| I2C_2 | BMP180 | optional| optional|
| I2C_3 | optional| optional| optional|
| I2C_4 | optional| optional| optional|
| GPIO3 | optional| optional| optional|
| GPIO4 | optional| optional| optional|
| GPIO5 | optional| Eingang Warnleuchte |optional|
| GPIO6 | optional| LED Ring | optional|
| GPIO7 | optional| Servo | für Relais|
## Programmablauf Segelflugzeug
```mermaid
flowchart TD
A(["Start"])
A --> B{"ist Senden an?"}
B --> | Ja | C{"Sende Timer <90 sec"}
I --> | Nein | E{"is airborn?"}
C --> | Nein | F["Sende Schleppen komplett"] -->G["is airborn=true"] -->L["Lowspeedtimer = 0"]
C --> | Ja | D["Sende Speed"]
D --> Z["Warte 1 Sekunde"]
L ----> H["Senden aus"] -->Z
Z --> B
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.
+10
View File
@@ -0,0 +1,10 @@
# Tacho mit LED Ring
Das Grundmodell ist von [alvaroalea auf Thingiverse](https://www.thingiverse.com/thing:2489322)
Erweitert wurde:
- Ziffernblatt 0 - 220 km/h
- Rückplatte mit Auslass für die Kabel des LED Rings [hier](back_with_hole.stl)
- Platte mit Vertiefung für LED Ring [hier](bezel_LED_Tacho.stl)
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.
Binary file not shown.
+6
View File
@@ -6,3 +6,9 @@ build_tag = env['PIOENV']
version_tag = os.getenv("FIRMWARE_VERSION") version_tag = os.getenv("FIRMWARE_VERSION")
env.Replace(PROGNAME="firmware_%s_%s" % (build_tag, 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"))
Binary file not shown.

After

Width:  |  Height:  |  Size: 485 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.
+39 -14
View File
@@ -11,12 +11,19 @@ bfs::Ms4525do pres;
Adafruit_BMP085 bmp; Adafruit_BMP085 bmp;
float pressure_avg[10]; float pressure_avg[10];
float basicaltitude; float basicaltitude = 0.0f;
void init_2ndi2c(){ bool pressure_sensor_ready = false;
Wire1.begin(dp_sda, dp_scl);
bool init_2ndi2c(){
if (!Wire1.begin(dp_sda, dp_scl))
{
Serial.println("Error starting I2C");
return false;
}
Wire1.setClock(400000); 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()) if (!pres.Begin())
{ {
Serial.println("Error communicating with sensor"); Serial.println("Error communicating with sensor");
while (1) return false;
{
}
} }
return true;
} }
int get_airspeed_kmh(void) int get_airspeed_kmh(void)
@@ -51,10 +57,22 @@ float get_airspeed(void)
return 0; 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(); 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("Pressure = ");
Serial.print(basicaltitude); Serial.print(basicaltitude);
Serial.println(" Pa"); Serial.println(" Pa");
@@ -64,21 +82,28 @@ void init_pressure_sensor(void)
Serial.print("Altitude = "); Serial.print("Altitude = ");
Serial.print(get_altitude(basicaltitude)); Serial.print(get_altitude(basicaltitude));
Serial.println(" meters"); Serial.println(" meters");
return true;
} }
float get_pressure() float get_pressure()
{ {
if (!pressure_sensor_ready)
return 0.0f;
float pressure; float pressure;
pressure = bmp.readPressure(); 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; pressure_avg[9] = pressure;
return pressure; return pressure;
} }
float get_pressure_avg(void) float get_pressure_avg(void)
{ {
float total = 0.0f;
int i; int i;
float total;
for (i = 0; i <10; i++) for (i = 0; i <10; i++)
{ {
total = total + pressure_avg[i]; total = total + pressure_avg[i];
@@ -97,14 +122,14 @@ float get_vario_avg(void)
float vario; float vario;
int i; int i;
float total; float total = 0.0f;
for (i = 0; i <9; i++) for (i = 0; i <9; i++)
{ {
int n=i+1; int n=i+1;
vario = get_altitude(pressure_avg[n]) - get_altitude(pressure_avg[i]); vario = get_altitude(pressure_avg[n]) - get_altitude(pressure_avg[i]);
total = total + vario; total = total + vario;
} }
return (total / 10); return (total / 9);
} }
float get_altitude(float pressure) float get_altitude(float pressure)
@@ -115,5 +140,5 @@ float get_altitude(float pressure)
} }
float get_altitude_diff(void){ 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 #ifndef WINDENTELEMETRY_AIRSPEED_H
#define WINDENTELEMETRY_AIRSPEED_H #define WINDENTELEMETRY_AIRSPEED_H
void init_2ndi2c(void); bool init_2ndi2c(void);
void init_airspeed_sensor(void); bool init_airspeed_sensor(void);
int get_airspeed_kmh(void); int get_airspeed_kmh(void);
/*! /*!
@@ -10,7 +10,7 @@ int get_airspeed_kmh(void);
*/ */
float get_airspeed(void); float get_airspeed(void);
void init_pressure_sensor(void); bool init_pressure_sensor(void);
float get_pressure(void); float get_pressure(void);
float get_pressure_adv(void); float get_pressure_adv(void);
float get_altitude(float pressure); float get_altitude(float pressure);
+242
View File
@@ -0,0 +1,242 @@
#include "webinterface.h"
#include <Arduino.h>
#include <Preferences.h>
#include <Update.h>
#include <WebServer.h>
#include <WiFi.h>
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;
String default_access_point_name()
{
#ifdef DEVICE_SAILPLANE
return String("Sailplane-") + default_callsign;
#else
return "Winde-Telemetry";
#endif
}
WebServer server(80);
Preferences preferences;
TelemetrySettings *active_settings = nullptr;
bool restart_after_update = false;
bool update_failed = false;
bool access_point_restart_pending = false;
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;
}
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}small{color:#555}"
"</style></head><body><h1>Winden-Telemetry</h1>");
if (message.length() > 0)
{
page += F("<p class='msg'>");
page += html_escape(message);
page += F("</p>");
}
page += F("<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><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><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><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)
{
String callsign = server.arg("callsign");
String access_point_name = server.arg("ap_name");
String access_point_password = server.arg("ap_password");
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();
access_point_name.trim();
access_point_password.trim();
if (access_point_name.length() == 0 || access_point_name.length() > 32 || access_point_password.length() < 8 || access_point_password.length() > 63 || 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;
settings.callsign = callsign;
settings.access_point_name = access_point_name;
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()
{
send_page();
}
void handle_settings()
{
TelemetrySettings updated = *active_settings;
if (!parse_settings(updated))
{
server.send(400, "text/plain; charset=utf-8", "Ungueltige Einstellungen");
return;
}
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);
send_page("Einstellungen gespeichert.");
access_point_restart_pending = 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
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);
if (settings.access_point_name.length() == 0 || settings.access_point_name.length() > 32 || settings.access_point_password.length() < 8 || settings.access_point_password.length() > 63 || 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)
{
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;
}
WiFi.mode(WIFI_AP);
bool ap_started = WiFi.softAP(settings.access_point_name, settings.access_point_password);
if (!ap_started)
return false;
server.on("/", HTTP_GET, handle_root);
server.on("/settings", HTTP_POST, handle_settings);
server.on("/update", HTTP_POST, handle_update_result, handle_update_upload);
server.begin();
Serial.print("Webinterface: http://");
Serial.println(WiFi.softAPIP());
return true;
}
void webinterface_handle(void)
{
server.handleClient();
if (access_point_restart_pending)
{
access_point_restart_pending = false;
WiFi.softAPdisconnect(true);
delay(100);
if (!WiFi.softAP(active_settings->access_point_name, active_settings->access_point_password))
Serial.println("Access Point konnte nicht neu gestartet werden");
}
}
const TelemetrySettings &webinterface_settings(void)
{
return *active_settings;
}
+20
View File
@@ -0,0 +1,20 @@
#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;
};
bool webinterface_begin(TelemetrySettings &settings);
void webinterface_handle(void);
const TelemetrySettings &webinterface_settings(void);
#endif
+2 -9
View File
@@ -8,14 +8,13 @@ void init_servo(void)
{ {
myservo.setPeriodHertz(50); myservo.setPeriodHertz(50);
myservo.attach(servoPin); myservo.attach(servoPin);
set_tacho(0, 70, 150);
} }
void set_tacho(int speed, int speed_min, int speed_max) void set_tacho(int speed, int speed_min, int speed_max)
{ {
int val = map(speed, -25, 220, 0, 155); int val = map(speed, -25, 220, 0, 155);
#ifdef DEBUG #ifdef DEBUG
Serial.print("Tacho Mapping: ") Serial.print("Tacho Mapping: ");
Serial.println(val); Serial.println(val);
#endif #endif
myservo.write(val); myservo.write(val);
@@ -72,11 +71,5 @@ void init_pixel(void)
int setoffsetpixel(int pixel) int setoffsetpixel(int pixel)
{ {
int newpixel; return (pixel + pixeloffset + NUMPIXELS) % NUMPIXELS;
newpixel = pixel + pixeloffset;
if (newpixel >= NUMPIXELS)
{
newpixel = newpixel - NUMPIXELS;
}
return newpixel;
} }
+6 -2
View File
@@ -2,6 +2,12 @@
#define WINDENTELEMETRY_CONFIG_H #define WINDENTELEMETRY_CONFIG_H
#define LORA_FREQ 868.0 #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 #ifdef HELTEC_WIFI_LORA_32_V3
// SX1262 on HELTEC_WIFI_LORA_32_V3 has the following connections: // SX1262 on HELTEC_WIFI_LORA_32_V3 has the following connections:
// NSS pin: 8 // NSS pin: 8
@@ -27,8 +33,6 @@
/**difference pressure**/ /**difference pressure**/
#define dp_scl 20 #define dp_scl 20
#define dp_sda 19 #define dp_sda 19
#define default_min_tow_speed 80
#define default_max_tow_speed 150
#endif #endif
#ifdef DEVICE_WINDE #ifdef DEVICE_WINDE
+19 -15
View File
@@ -9,8 +9,8 @@
; https://docs.platformio.org/page/projectconf.html ; https://docs.platformio.org/page/projectconf.html
[platformio] [platformio]
build_cache_dir=./.pio/.build_cache build_cache_dir = ~/.pio/.build_cache
cache_dir=./.pio/.cache_dir cache_dir = ~/.pio/.cache_dir
[common] [common]
platform = espressif32 platform = espressif32
@@ -25,37 +25,41 @@ build_flags =
-D CORE_DEBUG_LEVEL=5 -D CORE_DEBUG_LEVEL=5
-D SLOW_CLK_TPYE=0 -D SLOW_CLK_TPYE=0
-D REGION_EU868 -D REGION_EU868
-Ilib
-Isrc
lib_deps = lib_deps =
olikraus/U8g2@^2.36.15 olikraus/U8g2@^2.36.15
jgromes/RadioLib@^7.4.0 jgromes/RadioLib@^7.4.0
bblanchon/ArduinoJson@^7.4.2
monitor_speed = 115200 monitor_speed = 115200
extra_scripts = pre:extra_script.py extra_scripts = pre:extra_script.py
[env:heltec_wifi_lora_32_V3_sailplane] [env:heltec_wifi_lora_32_V3_sailplane]
extends = common extends = common
board = heltec_wifi_lora_32_V3 board = heltec_wifi_lora_32_V3
lib_deps = ${common.lib_deps} lib_deps =
${common.lib_deps}
bolderflight/Bolder Flight Systems MS4525@^1.1.3 bolderflight/Bolder Flight Systems MS4525@^1.1.3
bolderflight/Bolder Flight Systems Unit Conversions@^5.0.0 bolderflight/Bolder Flight Systems Unit Conversions@^5.0.0
bolderflight/Bolder Flight Systems Airdata Calculations@^4.0.0 bolderflight/Bolder Flight Systems Airdata Calculations@^4.0.0
adafruit/Adafruit BMP085 Library @ ^1.2.4 adafruit/Adafruit BMP085 Library @ ^1.2.4
lib_ignore = lib_ignore = winde
winde build_flags =
${common.build_flags}
build_flags = ${common.build_flags}
-D DEVICE_SAILPLANE -D DEVICE_SAILPLANE
upload_port = COM8
monitor_port = COM8
[env:heltec_wifi_lora_32_V3_winde] [env:heltec_wifi_lora_32_V3_winde]
extends = common extends = common
board = heltec_wifi_lora_32_V3 board = heltec_wifi_lora_32_V3
lib_deps = ${common.lib_deps} lib_deps =
${common.lib_deps}
adafruit/Adafruit NeoPixel@^1.15.2 adafruit/Adafruit NeoPixel@^1.15.2
madhephaestus/ESP32Servo@^3.0.9 madhephaestus/ESP32Servo@^3.0.9
lib_ignore = sailplane
lib_ignore = build_flags =
sailplane ${common.build_flags}
build_flags = ${common.build_flags}
-D DEVICE_WINDE -D DEVICE_WINDE
upload_port = COM9
monitor_port = COM9
+261 -47
View File
@@ -2,24 +2,28 @@
#include <Arduino.h> #include <Arduino.h>
#include <U8g2lib.h> #include <U8g2lib.h>
#include <RadioLib.h> #include <RadioLib.h>
#include <ArduinoJson.h>
#include <webinterface.h>
#ifdef DEVICE_WINDE #ifdef DEVICE_WINDE
#include <tacho.h> #include <tacho.h>
#endif #endif
TelemetrySettings telemetry_settings;
#ifdef DEVICE_SAILPLANE #ifdef DEVICE_SAILPLANE
#include <airspeed.h> #include <airspeed.h>
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 #endif
SX1262 radio = new Module(LORA_NSS, LORA_DIO1, LORA_NRST, LORA_BUSY); SX1262 radio = new Module(LORA_NSS, LORA_DIO1, LORA_NRST, LORA_BUSY);
bool radio_ready = false;
#ifdef DEVICE_WINDE #ifdef DEVICE_WINDE
volatile bool receivedFlag = false; 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!
#if defined(ESP8266) || defined(ESP32) #if defined(ESP8266) || defined(ESP32)
ICACHE_RAM_ATTR ICACHE_RAM_ATTR
#endif #endif
@@ -34,43 +38,57 @@ void setFlag(void)
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, OLED_RST, OLED_CLK, OLED_DATA); U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, OLED_RST, OLED_CLK, OLED_DATA);
#endif #endif
#ifdef DEVICE_WINDE
String data_from;
int data_speed = 0;
int data_min = 0;
int data_max = 0;
#endif
void setup() void setup()
{ {
/* Serial to display data */ /* Serial to display data */
Serial.begin(115200); Serial.begin(115200);
Serial.println("start"); Serial.println("start");
if (!webinterface_begin(telemetry_settings))
Serial.println("Webinterface konnte nicht gestartet werden");
int state = radio.begin(LORA_FREQ); int state = radio.begin(LORA_FREQ);
if (state == RADIOLIB_ERR_NONE) if (state == RADIOLIB_ERR_NONE)
{ {
radio_ready = true;
Serial.println(F("success!")); Serial.println(F("success!"));
} }
else else
{ {
Serial.print(F("failed, code ")); Serial.print(F("failed, code "));
Serial.println(state); Serial.println(state);
while (true) Serial.println(F("Webinterface bleibt verfuegbar"));
{
delay(10);
}
} }
u8g2.begin(); u8g2.begin();
u8g2.clearBuffer(); u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB14_tr); u8g2.setFont(u8g2_font_ncenB14_tr);
#ifdef DEVICE_SAILPLANE #ifdef DEVICE_SAILPLANE
u8g2.drawStr(0, 20, "Start Plane"); u8g2.drawStr(0, 20, "Start Plane");
init_2ndi2c(); sensors_ready = init_2ndi2c() && init_airspeed_sensor() && init_pressure_sensor();
init_airspeed_sensor(); if (!sensors_ready)
init_pressure_sensor(); {
u8g2.drawStr(0, 40, "Sensor error");
}
#endif #endif
#ifdef DEVICE_WINDE #ifdef DEVICE_WINDE
u8g2.drawStr(0, 20, "Start Winde"); u8g2.drawStr(0, 20, "Start Winde");
init_servo();
init_pixel(); init_pixel();
init_servo();
set_tacho(0, telemetry_settings.min_speed, telemetry_settings.max_speed);
radio.setPacketReceivedAction(setFlag); radio.setPacketReceivedAction(setFlag);
// start listening for LoRa packets // start listening for LoRa packets
if (radio_ready)
{
state = radio.startReceive(); state = radio.startReceive();
if (state == RADIOLIB_ERR_NONE) if (state == RADIOLIB_ERR_NONE)
{ {
@@ -80,30 +98,65 @@ void setup()
{ {
Serial.print(F("failed, code ")); Serial.print(F("failed, code "));
Serial.println(state); Serial.println(state);
while (true) radio_ready = false;
{
delay(10);
} }
} }
#endif #endif
u8g2.sendBuffer(); u8g2.sendBuffer();
} }
void loop() String nmea_checksum(String str)
{ {
#ifdef DEVICE_SAILPLANE byte cs = 0;
for (unsigned int n = 1; n < str.length(); n++)
/* 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)
{ {
String data = String(vel_kmh); 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);
u8g2.drawFrame(112,4,4,7);
u8g2.drawFrame(117,2,4,9);
u8g2.drawFrame(122,0,4,11);
if ((Rssi > -120) && Rssi < 0 )
{
u8g2.drawBox(102,8,4,3);
}
if ((Rssi > -100) && Rssi < 0 )
{
u8g2.drawBox(107,6,4,5);
}
if ((Rssi > -85) && Rssi < 0 )
{
u8g2.drawBox(112,4,4,7);
}
if ((Rssi > -70) && Rssi < 0 )
{
u8g2.drawBox(117,2,4,9);
}
if ((Rssi > -55) && Rssi < 0 )
{
u8g2.drawBox(122,0,4,11);
}
}
void send_lora(String data)
{
if (!radio_ready)
return;
int state = radio.transmit(data); int state = radio.transmit(data);
if (state == RADIOLIB_ERR_NONE) if (state == RADIOLIB_ERR_NONE)
{ {
@@ -112,7 +165,7 @@ void loop()
// print measured data rate // print measured data rate
Serial.print(F("[SX1262] Datarate:\t")); Serial.print(F("[SX1262] Datarate:\t"));
Serial.print(radio.getDataRate()); //Serial.print(radio.getDataRate());
Serial.println(F(" bps")); Serial.println(F(" bps"));
} }
else if (state == RADIOLIB_ERR_PACKET_TOO_LONG) else if (state == RADIOLIB_ERR_PACKET_TOO_LONG)
@@ -131,6 +184,163 @@ void loop()
Serial.print(F("failed, code ")); Serial.print(F("failed, code "));
Serial.println(state); 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]) : "";
}
#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)
{
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;
}
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()
{
webinterface_handle();
#ifdef DEVICE_SAILPLANE
if (!sensors_ready)
{
delay(1000);
return;
}
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)
{
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
{
if (currentMillis - previousMillissendtimer < active_time_ms)
{
send_speed(vel_kmh);
}
else
{
send_airborn();
is_airborn = true;
lowspeedtimer = 0;
send_data = false;
}
}
#ifdef DEBUG #ifdef DEBUG
/* Display the data */ /* Display the data */
@@ -138,18 +348,17 @@ void loop()
Serial.println(vel_kmh); Serial.println(vel_kmh);
Serial.print(pres.pres_pa(), 6); Serial.print(pres.pres_pa(), 6);
Serial.print("IAS (m/s): "); Serial.print("IAS (m/s): ");
Serial.println(ias); Serial.println(get_airspeed());
Serial.println(vel_kmh); Serial.println(vel_kmh);
Serial.print("\t"); Serial.print("\t");
Serial.print(pres.die_temp_c(), 6); Serial.print(pres.die_temp_c(), 6);
Serial.print("\n"); Serial.print("\n");
#endif #endif
delay(1000);
} }
#endif #endif
#ifdef DEVICE_WINDE #ifdef DEVICE_WINDE
if (receivedFlag) if (radio_ready && receivedFlag)
{ {
// reset flag // reset flag
receivedFlag = false; receivedFlag = false;
@@ -158,24 +367,29 @@ void loop()
String str; String str;
int state = radio.readData(str); int state = radio.readData(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) if (state == RADIOLIB_ERR_NONE)
{ {
if (!valid_nmea(str) || !decode_msg(str))
{
Serial.println("Ungueltige Telemetrie-Nachricht");
return;
}
// packet was successfully received // packet was successfully received
u8g2.clearBuffer(); u8g2.clearBuffer();
u8g2.setFont(u8g2_font_logisoso42_tr); u8g2.setFont(u8g2_font_ncenB10_tr);
u8g2.setCursor(0, 60); u8g2.setCursor(0, 10);
u8g2.print(str); u8g2.print(data_from);
u8g2.setCursor(0, 64);
u8g2.print(data_min);
u8g2.setCursor(64, 64);
u8g2.print(data_max);
u8g2.setFont(u8g2_font_logisoso32_tr);
u8g2.setCursor(0, 50);
u8g2.print(data_speed);
draw_rssi(radio.getRSSI());
u8g2.sendBuffer(); u8g2.sendBuffer();
Serial.println(str); Serial.println(str);
int speed = str.toInt(); set_tacho(data_speed, data_min, data_max);
set_tacho(speed, 70, 150);
} }
} }
#endif #endif