21 Commits

Author SHA1 Message Date
e4770f2fa2 added flashscript 2024-06-03 01:34:30 +02:00
cbcdc34e6c fixed Bug leading to Stackoverflow 2024-06-02 23:37:38 +02:00
48774a42f4 improvement of webui-function 2024-06-02 23:37:04 +02:00
73c486be73 added some missing new lines in debug 2024-06-02 23:35:57 +02:00
6574947a80 hardened WiFi-String stuff 2024-06-02 23:35:20 +02:00
956cc49e6f reworked part of debugger 2024-06-02 22:38:52 +02:00
b1da9449ad sanitycheck autocorrect of EEPROM now default. 2024-06-02 21:59:38 +02:00
e54cadcc7c Bugfix - EE-Reset to defaults not pointing to HOST_NAME 2024-06-02 21:50:51 +02:00
0b2245d7f6 removed version file from flash, because auto generated during build 2024-05-31 18:56:28 +02:00
3ceab44a96 fixed Bug in Battery-Type-Setting and disabled WiFiClient 2024-05-31 18:51:05 +02:00
91de9f0785 incereased Version after Tag-creation 2024-05-31 14:27:06 +02:00
837fd7f558 updated Version before tag 2024-05-31 14:05:07 +02:00
6cacd8451f missed import in Buildscript 2024-05-31 14:04:57 +02:00
5ae054f314 improved UART Parser for LoRa 2024-05-31 13:59:11 +02:00
3e571a515d some Init-Stuff optimized and made Log nicer 2024-05-31 13:58:36 +02:00
a2aa302121 optimized dtc-table in WebUI 2024-05-31 12:50:01 +02:00
d04489819d highlite active Faction in WEbUI 2024-05-31 12:49:38 +02:00
52026296f2 updated DeviceName handling 2024-05-31 12:49:06 +02:00
a22f71649a updated Buildscripts to have central Versioning 2024-05-31 12:47:45 +02:00
ae8eef52ef missed to relplace some strings 2024-05-31 03:07:37 +02:00
d9ee193e26 missed to add function to websocket.js 2024-05-31 03:06:00 +02:00
22 changed files with 806 additions and 261 deletions

1
Software/.gitignore vendored
View File

@@ -1,4 +1,5 @@
data/ data/
data_src/version
.pio .pio
.vscode/.browse.c_cpp.db* .vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json .vscode/c_cpp_properties.json

View File

@@ -0,0 +1,97 @@
# Flash ESP8266 Script
## Version: 1.0.0
### Author: Marcel Peterkau
### Copyright: 2024
### License: MIT
---
## Beschreibung
Dieses Skript entpackt ein ZIP-Archiv, das Firmware- und Dateisystem-Dateien für einen ESP8266 enthält, und flasht diese auf das Gerät. Nach dem Flashen werden die entpackten Dateien automatisch gelöscht.
---
## Benutzung
```sh
python flash_esp8266.py <zip_file> [-v]
```
### Parameter
- `<zip_file>`: Der Name der ZIP-Datei, die die Firmware- und Dateisystem-Dateien enthält.
- `-v`: Aktiviert Debug-Ausgaben (optional).
### Beispiel
```sh
python flash_esp8266.py firmware_1.07_cbcdc34.zip -v
```
---
## Voraussetzungen
- Python 3.x
- Die folgenden Python-Bibliotheken müssen installiert sein:
- esptool
- pyserial
Installiere die benötigten Bibliotheken mit:
```sh
pip install esptool pyserial
```
---
## Funktionen
- **get_com_ports**: Ermittelt die verfügbaren COM-Ports.
- **find_new_com_port**: Findet den neuen COM-Port, wenn der ESP8266 angeschlossen wird.
- **extract_files**: Entpackt die ZIP-Datei und gibt die enthaltenen Dateien zurück.
- **decompress_gz**: Entpackt eine GZ-Datei.
- **clean_up**: Löscht die angegebenen Dateien nach dem Flashen.
---
## Ablauf
1. Das Skript überprüft, ob die angegebene ZIP-Datei existiert.
2. Die ZIP-Datei wird entpackt und die Firmware- und Dateisystem-Dateien werden identifiziert.
3. Die Dateisystem-Datei (GZ) wird entpackt.
4. Der Benutzer wird aufgefordert, den ESP8266 abzustecken und erneut anzustecken, um den neuen COM-Port zu ermitteln.
5. Die Firmware- und Dateisystem-Dateien werden auf den ESP8266 geflasht.
6. Die temporären Dateien werden nach dem erfolgreichen Flashen gelöscht.
---
## Fehlerbehebung
- **ZIP-Datei nicht gefunden**: Stelle sicher, dass der Pfad zur ZIP-Datei korrekt angegeben ist.
- **Erforderliche Dateien nicht im ZIP-Archiv**: Überprüfe, ob die ZIP-Datei die richtigen Dateien (`.fw.bin` und `.fs.gz`) enthält.
- **Kein neuer COM-Port gefunden**: Stelle sicher, dass der ESP8266 korrekt angeschlossen ist und warte, bis der neue COM-Port erkannt wird.
---
## Lizenz
Dieses Projekt ist unter der MIT-Lizenz lizenziert
---
## Versionshistorie
- **1.0.0** - Initiale Version
---
## Haftungsausschluss
Dieses Skript wird ohne Garantie bereitgestellt. Der Autor übernimmt keine Verantwortung für Schäden oder Datenverlust, die durch die Nutzung dieses Skripts entstehen könnten.
---

View File

@@ -0,0 +1,156 @@
"""
flash_esp8266.py
Version: 1.0.0
Author: Marcel Peterkau
Copyright: 2024
License: MIT
Beschreibung:
Dieses Skript entpackt ein ZIP-Archiv mit Firmware- und Dateisystem-Dateien und flasht diese auf einen ESP8266.
Die entpackten Dateien werden nach dem Flashen gelöscht.
Versionshistorie:
1.0.0 - Initiale Version
Benutzung:
python flash_esp8266.py <zip_file> [-v]
Optionen:
-v Aktiviert Debug-Ausgaben
"""
import esptool
import sys
import serial.tools.list_ports
import zipfile
import os
import gzip
import shutil
BAUD_RATE = 921600 # Erhöhte Baudrate
DEBUG = '-v' in sys.argv
def debug_print(message):
if DEBUG:
print(message)
def get_com_ports():
ports = [comport.device for comport in serial.tools.list_ports.comports()]
debug_print(f"Verfügbare COM-Ports: {ports}")
return ports
def find_new_com_port(old_ports, timeout=15):
debug_print("Suche nach neuen COM-Ports...")
import time
start_time = time.time()
while time.time() - start_time < timeout:
new_ports = get_com_ports()
added_ports = list(set(new_ports) - set(old_ports))
if added_ports:
new_port = added_ports[0]
print(f"Neuer COM-Port gefunden: {new_port}")
return new_port
time.sleep(1)
return None
def extract_files(zip_file):
with zipfile.ZipFile(zip_file, 'r') as zip_ref:
zip_ref.extractall()
debug_print(f"Entpackt: {zip_ref.namelist()}")
return zip_ref.namelist()
def decompress_gz(file_path):
output_file = file_path.replace('.gz', '')
with gzip.open(file_path, 'rb') as f_in:
with open(output_file, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
debug_print(f"Entpackt {file_path} zu {output_file}")
return output_file
def clean_up(files):
for file in files:
if os.path.isfile(file):
os.remove(file)
debug_print(f"Gelöscht: {file}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Verwendung: python flash_esp8266.py <zip_file> [-v]")
sys.exit(1)
ZIP_FILE = sys.argv[1]
# Überprüfe, ob das ZIP-Archiv existiert
if not os.path.isfile(ZIP_FILE):
print(f"ZIP-Archiv {ZIP_FILE} nicht gefunden.")
sys.exit(1)
# Entpacke das ZIP-Archiv
extracted_files = extract_files(ZIP_FILE)
# Finde die .fw.bin- und .fs.gz-Dateien
bin_file = None
fs_file = None
for file in extracted_files:
if file.endswith('.fw.bin'):
bin_file = file
elif file.endswith('.fs.gz'):
fs_file = file
if not bin_file or not fs_file:
print(f"Die erforderlichen Dateien (.fw.bin und .fs.gz) wurden nicht im ZIP-Archiv {ZIP_FILE} gefunden.")
sys.exit(1)
print(f"Gefundene Firmware-Datei: {bin_file}")
print(f"Gefundene Dateisystem-Datei: {fs_file}")
# Entpacke die .fs.gz-Datei
decompressed_fs_file = decompress_gz(fs_file)
print("Bitte stecke den ESP8266 ab, falls er angeschlossen ist, und drücke Enter.")
input()
print("Suche nach verfügbaren COM-Ports...")
old_ports = get_com_ports()
print("Bitte stecke den ESP8266 jetzt an und warte, bis der neue COM-Port erkannt wird...")
port = find_new_com_port(old_ports, timeout=15)
if port is None:
print("Kein neuer COM-Port gefunden. Bitte versuche es erneut.")
sys.exit(1)
print(f"Neuer COM-Port gefunden: {port}")
# Benutze esptool zum Flashen der Firmware und des Dateisystems
try:
# Flashen der Firmware
esptool_args_bin = [
'--port', port,
'--baud', str(BAUD_RATE),
'write_flash', '-fm', 'dout', '0x00000', bin_file
]
esptool.main(esptool_args_bin)
print("Firmware erfolgreich geflasht!")
# Flashen des Dateisystems
esptool_args_fs = [
'--port', port,
'--baud', str(BAUD_RATE),
'write_flash', '0x300000', decompressed_fs_file
]
esptool.main(esptool_args_fs)
print("Dateisystem erfolgreich geflasht!")
# Bereinigen der entpackten Dateien
clean_up(extracted_files)
clean_up([decompressed_fs_file])
except Exception as e:
print(f"Fehler beim Flashen: {e}")
sys.exit(1)
input("Drücke Enter, um das Fenster zu schließen...") # Hält das Fenster offen

View File

@@ -1,13 +1,10 @@
# SCRIPT TO GZIP CRITICAL FILES FOR ACCELERATED WEBSERVING
# see also https://community.platformio.org/t/question-esp32-compress-files-in-data-to-gzip-before-upload-possible-to-spiffs/6274/10
import glob import glob
import shutil import shutil
import gzip import gzip
import os import os
import subprocess import subprocess
import platform import platform
from os import popen
Import("env") Import("env")
Import("projenv") Import("projenv")
@@ -167,10 +164,15 @@ def gzip_webfiles(source, target, env):
shutil.rmtree(data_temp_dir_path) shutil.rmtree(data_temp_dir_path)
return return
def gzip_binffiles(source, target, env): def gzip_binffiles(source, target, env):
git_revision = popen('git rev-parse --short HEAD').read().strip()
custom_flash_version = env.GetProjectOption("custom_flash_version", "0.99")
# Format the target file name
target_filename = f"filesystem_{custom_flash_version}_{git_revision}.fs"
littlefsbin = target[0].get_abspath() littlefsbin = target[0].get_abspath()
targetbin = os.path.join(os.path.dirname(littlefsbin), 'filesystem.fs') targetbin = os.path.join(os.path.dirname(littlefsbin), target_filename)
shutil.copyfile(littlefsbin, targetbin) shutil.copyfile(littlefsbin, targetbin)
gzip_file(targetbin, os.path.join(str(targetbin) + '.gz')) gzip_file(targetbin, os.path.join(str(targetbin) + '.gz'))
os.remove(targetbin) os.remove(targetbin)

View File

@@ -7,8 +7,27 @@ from os import popen
git_revision = popen('git rev-parse --short HEAD').read().strip() git_revision = popen('git rev-parse --short HEAD').read().strip()
env.Replace(PROGNAME="firmware_%s.fw" % git_revision) # Versionsnummern aus platformio.ini holen
env.Append(CPPDEFINES=[('GIT_REV', '\\"{}\\"'.format(git_revision))]) custom_firmware_version = env.GetProjectOption("custom_firmware_version", "0.99")
custom_flash_version = env.GetProjectOption("custom_flash_version", "0.99")
# Versionsnummern aufteilen in Major und Minor
fw_major, fw_minor = custom_firmware_version.split('.')
fl_major, fl_minor = custom_flash_version.split('.')
# Version in Datei "version" im Ordner "data_src" überschreiben
with open('data_src/version', 'w') as version_file:
version_file.write(custom_flash_version)
# Build-Flags setzen
env.Replace(PROGNAME="firmware_%s_%s.fw" % (custom_firmware_version, git_revision))
env.Append(CPPDEFINES=[
('GIT_REV', '\\"{}\\"'.format(git_revision)),
('FW_MAJOR', fw_major),
('FW_MINOR', fw_minor),
('FL_MAJOR', fl_major),
('FL_MINOR', fl_minor)
])
struct2json.struct2json() struct2json.struct2json()
dtcs.build_dtcs() dtcs.build_dtcs()

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>KTM CAN Chain Oiler</title> <title>Dark Emergency Timer</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="static/css/bootstrap.min.css"> <link rel="stylesheet" href="static/css/bootstrap.min.css">
<link rel="stylesheet" href="static/css/custom.css"> <link rel="stylesheet" href="static/css/custom.css">
@@ -35,7 +35,7 @@
<nav class="navbar fixed-top navbar-dark bg-primary" id="navbar1"> <nav class="navbar fixed-top navbar-dark bg-primary" id="navbar1">
<a class="navbar-brand" href="#"> <a class="navbar-brand" href="#">
<img src="static/img/logo.png" width="30" height="30" class="d-inline-block align-top mr-1" alt=""> <img src="static/img/logo.png" width="30" height="30" class="d-inline-block align-top mr-1" alt="">
DE Airsoft Timer <span class="data-devicename">DE Airsoft Timer</span>
</a> </a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsingNavbar" <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsingNavbar"
aria-controls="collapsingNavbar" aria-expanded="false" aria-label="Toggle navigation"> aria-controls="collapsingNavbar" aria-expanded="false" aria-label="Toggle navigation">
@@ -63,7 +63,7 @@
<div class="col text-center"> <div class="col text-center">
<div class="jumbotron"> <div class="jumbotron">
<img src="static/img/logo.png" width="120" height="120" class="img-fluid" alt=""> <img src="static/img/logo.png" width="120" height="120" class="img-fluid" alt="">
<h3 class="pt-3">KTM CAN Chain Lube</h3> <h3 class="pt-3"><span class="data-devicename">DE Airsoft Timer</span></h3>
</div> </div>
</div> </div>
<!-- Div Group Battery remain --> <!-- Div Group Battery remain -->
@@ -96,14 +96,14 @@
<div id="header_faction3" class="col text-center data-name_faction3 text-white p-3">%NAME_FAC_3%</div> <div id="header_faction3" class="col text-center data-name_faction3 text-white p-3">%NAME_FAC_3%</div>
</div> </div>
<div class="row"> <div class="row">
<div class="col bg-dark text-white p-3"><img src="static/img/logo_fac1.png" <div class="col bg-dark text-white p-3 data-activefaction faction-logo faction1">
class="rounded mx-auto img-fluid d-block" alt="..."> <img src="static/img/logo_fac1.png" class="rounded mx-auto img-fluid d-block" alt="...">
</div> </div>
<div class="col bg-dark text-white p-3"><img src="static/img/logo_fac2.png" <div class="col bg-dark text-white p-3 data-activefaction faction-logo faction2">
class="rounded mx-auto img-fluid d-block" alt="..."> <img src="static/img/logo_fac2.png" class="rounded mx-auto img-fluid d-block" alt="...">
</div> </div>
<div class="col bg-dark text-white p-3"><img src="static/img/logo_fac3.png" <div class="col bg-dark text-white p-3 data-activefaction faction-logo faction3">
class="rounded mx-auto img-fluid d-block" alt="..."> <img src="static/img/logo_fac3.png" class="rounded mx-auto img-fluid d-block" alt="...">
</div> </div>
</div> </div>
<div class="row"> <div class="row">
@@ -190,8 +190,8 @@
<div class="col-8"> <div class="col-8">
<select id="batterytype" class="set-wsevent data-batterytype select form-control"> <select id="batterytype" class="set-wsevent data-batterytype select form-control">
<option value="Undefined">Undefined</option> <option value="Undefined">Undefined</option>
<option value="LiPo3S">LiPo 3S</option> <option value="LiPo 3S">LiPo 3S</option>
<option value="LiPo2S">LiPo 2S</option> <option value="LiPo 2S">LiPo 2S</option>
</select> </select>
</div> </div>
</div> </div>
@@ -215,28 +215,34 @@
<div class="form-group row"> <div class="form-group row">
<label for="name_faction1" class="control-label col-4">Faktion 1</label> <label for="name_faction1" class="control-label col-4">Faktion 1</label>
<div class="col-8"> <div class="col-8">
<input id="name_faction1" type="text" class="set-wsevent data-name_faction1 form-control" required="required"> <div class="input-group">
<input id="name_faction1" type="text" class="set-wsevent data-name_faction1 form-control" required="required">
<div class="input-group-append"> <div class="input-group-append">
<span class="input-group-text">max. 32 Zeichen</span> <span class="input-group-text">max. 32 Zeichen</span>
</div> </div>
</div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="name_faction2" class="control-label col-4">Faktion 2</label> <label for="name_faction2" class="control-label col-4">Faktion 2</label>
<div class="col-8"> <div class="col-8">
<input id="name_faction2" type="text" class="set-wsevent data-name_faction2 form-control" required="required"> <div class="input-group">
<input id="name_faction2" type="text" class="set-wsevent data-name_faction2 form-control" required="required">
<div class="input-group-append"> <div class="input-group-append">
<span class="input-group-text">max. 32 Zeichen</span> <span class="input-group-text">max. 32 Zeichen</span>
</div> </div>
</div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="name_faction3" class="control-label col-4">Faktion 3</label> <label for="name_faction3" class="control-label col-4">Faktion 3</label>
<div class="col-8"> <div class="col-8">
<input id="name_faction3" type="text" class="set-wsevent data-name_faction3 form-control" required="required"> <div class="input-group">
<input id="name_faction3" type="text" class="set-wsevent data-name_faction3 form-control" required="required">
<div class="input-group-append"> <div class="input-group-append">
<span class="input-group-text">max. 32 Zeichen</span> <span class="input-group-text">max. 32 Zeichen</span>
</div> </div>
</div>
</div> </div>
</div> </div>
<!-- Div Group Timer Settings --> <!-- Div Group Timer Settings -->
@@ -267,15 +273,15 @@
</tr> </tr>
<tr> <tr>
<td>Firmware Version</td> <td>Firmware Version</td>
<td>%SW_VERSION%</td> <td><span class="data-fw-version"></span></td>
</tr> </tr>
<tr> <tr>
<td>Flash Version</td> <td>Flash Version</td>
<td>%FS_VERSION%</td> <td><span class="data-flash-version"></span></td>
</tr> </tr>
<tr> <tr>
<td>Git Revision</td> <td>Git Revision</td>
<td>%GIT_REV%</td> <td><span class="data-git-revision"></span></td>
</tr> </tr>
</table> </table>
</p> </p>

View File

@@ -8438,4 +8438,23 @@ a.text-dark:hover {
.navbar-dark.bg-primary { .navbar-dark.bg-primary {
background-color: #111 !important background-color: #111 !important
} }
.glow-active-faction {
border: 3px solid #FFD700; /* Goldene Umrandung */
box-shadow: 0 0 20px #FFD700; /* Leuchtender Glüheffekt */
animation: glow 1.5s infinite alternate;
border-radius: 10px; /* Abgerundete Ecken */
margin-bottom: 10px; /* Abstand nach unten */
}
@keyframes glow {
from {
box-shadow: 0 0 10px #FFD700;
}
to {
box-shadow: 0 0 20px #FFD700;
}
}

View File

@@ -3,6 +3,13 @@ const jsonFilePath = "static/dtc_table.json";
var dtcState = {}; var dtcState = {};
async function processDTCNotifications(dtcArray) { async function processDTCNotifications(dtcArray) {
// Entferne DTCs aus dtcState, die nicht im dtcArray enthalten sind
for (var key in dtcState) {
if (!dtcArray.some((dtc) => parseInt(dtc.split(",")[1]) == key)) {
delete dtcState[key];
}
}
if (dtcArray.length === 0 || dtcArray[0] == "0") { if (dtcArray.length === 0 || dtcArray[0] == "0") {
dtcState = {}; dtcState = {};
return; return;
@@ -31,14 +38,14 @@ async function processDTCNotifications(dtcArray) {
if (dtcState[errorCode]) { if (dtcState[errorCode]) {
// Überprüfen, ob sich der Zustand von "previous" auf "active" geändert hat // Überprüfen, ob sich der Zustand von "previous" auf "active" geändert hat
if (activity !== dtcState[errorCode]) { if (activity !== dtcState[errorCode].activity) {
dtcState[errorCode] = activity; dtcState[errorCode].activity = activity;
if (activity === 1) showNotification(description, severity); if (activity === 1) showNotification(description, severity);
} }
} else { } else {
// DTC ist neu, Zustand speichern und Benachrichtigung anzeigen // DTC ist neu, Zustand speichern und wenn active, Benachrichtigung anzeigen
dtcState[errorCode] = activity; dtcState[errorCode] = { activity: activity };
showNotification(description, severity); if (activity === 1) showNotification(description, severity);
} }
} catch (error) { } catch (error) {
console.error("Error processing DTC:", error); console.error("Error processing DTC:", error);
@@ -89,14 +96,15 @@ async function showDTCModal(event) {
} catch (error) { } catch (error) {
console.error("Fehler beim Abrufen der Beschreibung:", error); console.error("Fehler beim Abrufen der Beschreibung:", error);
modal.find(".modal-title").text("Fehler"); modal.find(".modal-title").text("Fehler");
modal.find(".dtc-desc").text("DTC-Beschreibung konnte nicht geladen werden"); modal
.find(".dtc-desc")
.text("DTC-Beschreibung konnte nicht geladen werden");
} }
// Modal anzeigen // Modal anzeigen
modal.modal("show"); modal.modal("show");
} }
function fillDTCTable(dtcArray) { function fillDTCTable(dtcArray) {
// Referenz auf das Tabellen-Element // Referenz auf das Tabellen-Element
var table = document.getElementById("dtc_table"); var table = document.getElementById("dtc_table");
@@ -108,11 +116,28 @@ function fillDTCTable(dtcArray) {
tablediv.hidden = true; tablediv.hidden = true;
table.innerHTML = ""; table.innerHTML = "";
return; return;
} else {
// Zeige das Tabellen-Div, wenn DTC vorhanden sind
tablediv.hidden = false;
} }
// Prüfen, ob sich der Inhalt der Tabelle geändert hat
var tableContentChanged = false;
for (var i = 0; i < dtcArray.length; i++) {
var dtcInfo = dtcArray[i].split(",");
var errorCode = parseInt(dtcInfo[1]);
var activity = parseInt(dtcInfo[3]);
if (!dtcState[errorCode] || dtcState[errorCode].activity !== activity) {
tableContentChanged = true;
break;
}
}
if (!tableContentChanged) {
return;
}
// Zeige das Tabellen-Div, wenn DTC vorhanden sind
tablediv.hidden = false;
// Tabelle leeren, bevor sie neu gefüllt wird // Tabelle leeren, bevor sie neu gefüllt wird
table.innerHTML = ""; table.innerHTML = "";

View File

@@ -84,7 +84,6 @@ function onMessage(event) {
processDTCNotifications(dtcArray); processDTCNotifications(dtcArray);
fillDTCTable(dtcArray); fillDTCTable(dtcArray);
} else if (data.startsWith("MAPPING_STATUS:")) { } else if (data.startsWith("MAPPING_STATUS:")) {
const data_sliced = data.slice(15); const data_sliced = data.slice(15);
statusMapping = createMapping(data_sliced); statusMapping = createMapping(data_sliced);
@@ -179,9 +178,12 @@ function fillValuesToHTML(dataset) {
// Wenn das Element ein Settingsabschnitt-div ist // Wenn das Element ein Settingsabschnitt-div ist
if (dataset[key] == 0) element.style.display = "none"; if (dataset[key] == 0) element.style.display = "none";
else element.style.display = ""; else element.style.display = "";
} else if (element.tagName === "DIV") { } else if (element.tagName === "DIV" || element.tagName === "SPAN") {
if (element.classList.contains("format-time")) { if (element.classList.contains("format-time")) {
element.innerText = formatTime(dataset[key]); element.innerText = formatTime(dataset[key]);
} else if (element.classList.contains("faction-logo")) {
// Faction-Logo-Logik
updateFactionLogo(element, dataset[key]);
} else { } else {
element.innerText = dataset[key]; element.innerText = dataset[key];
} }
@@ -191,9 +193,42 @@ function fillValuesToHTML(dataset) {
} }
} }
} }
// Aktualisiere den <title>-Tag, wenn der Schlüssel 'devicename' im dataset vorhanden ist
if (key === "devicename") {
document.title = dataset[key];
}
} }
} }
function formatTime(seconds) {
var hrs = Math.floor(seconds / 3600);
var mins = Math.floor((seconds % 3600) / 60);
var secs = seconds % 60;
return (
String(hrs).padStart(2, "0") +
":" +
String(mins).padStart(2, "0") +
":" +
String(secs).padStart(2, "0")
);
}
function updateFactionLogo(element, faction) {
const glowClass = "glow-active-faction";
const factionClasses = ["faction1", "faction2", "faction3"];
factionClasses.forEach((factionClass) => {
if (
factionClass === "faction" + faction &&
element.classList.contains(factionClass)
) {
element.classList.add(glowClass);
} else {
element.classList.remove(glowClass);
}
});
}
// Funktion zum Setzen des ausgewählten Werts für Dropdowns // Funktion zum Setzen des ausgewählten Werts für Dropdowns
function setDropdownValue(selectElement, value) { function setDropdownValue(selectElement, value) {
@@ -219,15 +254,15 @@ function showNotification(message, type) {
// Erstellen Sie ein Bootstrap-Alert-Element // Erstellen Sie ein Bootstrap-Alert-Element
var alertElement = $( var alertElement = $(
'<div class="alert alert-' + '<div class="alert alert-' +
type + type +
' alert-dismissible fade show notification" role="alert">' + ' alert-dismissible fade show notification" role="alert">' +
"<strong>" + "<strong>" +
message + message +
"</strong>" + "</strong>" +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close">' + '<button type="button" class="close" data-dismiss="alert" aria-label="Close">' +
'<span aria-hidden="true">&times;</span>' + '<span aria-hidden="true">&times;</span>' +
"</button>" + "</button>" +
"</div>" "</div>"
); );
// Fügen Sie das Alert-Element dem Container hinzu // Fügen Sie das Alert-Element dem Container hinzu

View File

@@ -1 +0,0 @@
1.05

View File

@@ -12,8 +12,10 @@
#define TRUE 1 #define TRUE 1
#define FALSE 0 #define FALSE 0
#ifndef HOST_NAME #ifndef DEVICE_NAME
#define HOST_NAME "AirsoftTimer_%08X" #define HOST_NAME "AirsoftTimer"
#else
#define HOST_NAME DEVICE_NAME
#endif #endif
#define SHUTDOWN_DELAY_MS 5000 #define SHUTDOWN_DELAY_MS 5000

View File

@@ -82,7 +82,7 @@ const configData_t ConfigData_defaults = {
"FACTION 1", // Faction_1_Name "FACTION 1", // Faction_1_Name
"FACTION 2", // Faction_2_Name "FACTION 2", // Faction_2_Name
"FACTION 3", // Faction_3_Name "FACTION 3", // Faction_3_Name
"ArisoftTimer", HOST_NAME,
QUOTE(WIFI_AP_PASSWORD), QUOTE(WIFI_AP_PASSWORD),
QUOTE(WIFI_SSID_CLIENT), QUOTE(WIFI_SSID_CLIENT),
QUOTE(WIFI_PASSWORD_CLIENT), QUOTE(WIFI_PASSWORD_CLIENT),
@@ -90,7 +90,7 @@ const configData_t ConfigData_defaults = {
0 // checksum 0 // checksum
}; };
void InitEEPROM(); boolean InitEEPROM();
void EEPROM_Process(); void EEPROM_Process();
void StoreConfig_EEPROM(); void StoreConfig_EEPROM();
void GetConfig_EEPROM(); void GetConfig_EEPROM();
@@ -102,7 +102,6 @@ uint32_t Checksum_EEPROM(uint8_t const *data, size_t len);
void dumpEEPROM(uint16_t memoryAddress, uint16_t length); void dumpEEPROM(uint16_t memoryAddress, uint16_t length);
void MovePersistencePage_EEPROM(boolean reset); void MovePersistencePage_EEPROM(boolean reset);
uint32_t ConfigSanityCheck(bool autocorrect = false); uint32_t ConfigSanityCheck(bool autocorrect = false);
bool validateWiFiString(char *string, size_t size);
void writeSequentialToEEPROM(uint16_t memoryAddress, uint16_t length); void writeSequentialToEEPROM(uint16_t memoryAddress, uint16_t length);
void writeZeroToEEPROM(uint16_t memoryAddress, uint16_t length); void writeZeroToEEPROM(uint16_t memoryAddress, uint16_t length);

View File

@@ -10,7 +10,8 @@ typedef struct Globals_s
tSystem_Status resumeStatus = sysStat_Startup; /**< Status to resume after rain mode */ tSystem_Status resumeStatus = sysStat_Startup; /**< Status to resume after rain mode */
char systemStatustxt[16] = ""; /**< Text representation of system status */ char systemStatustxt[16] = ""; /**< Text representation of system status */
EERequest_t requestEEAction = EE_IDLE; /**< EEPROM-related request */ EERequest_t requestEEAction = EE_IDLE; /**< EEPROM-related request */
char DeviceName[33]; /**< Device name */ char DeviceName[25]; /**< Device name */
char DeviceNameId[sizeof(DeviceName) + 8]; /**< Device name plus 8 chars chipID */
char FlashVersion[10]; /**< Flash version */ char FlashVersion[10]; /**< Flash version */
uint16_t eePersistanceAdress; /**< EEPROM persistence address */ uint16_t eePersistanceAdress; /**< EEPROM persistence address */
bool hasDTC; bool hasDTC;
@@ -31,8 +32,8 @@ typedef struct Constants_s
} Constants_t; } Constants_t;
const Constants_t constants PROGMEM = { const Constants_t constants PROGMEM = {
1, 5, // Firmware_Version FW_MAJOR, FW_MINOR, // Firmware_Version
1, 5, // Required Flash Version FL_MAJOR, FL_MINOR, // Required Flash Version
GIT_REV // Git-Hash-String GIT_REV // Git-Hash-String
}; };

View File

@@ -0,0 +1,36 @@
#ifndef UTILITIES_H
#define UTILITIES_H
#include <Arduino.h>
/**
* @brief Validates whether a given string contains only characters allowed in WiFi SSIDs and passwords.
*
* This function checks each character in the provided string to ensure
* that it contains only characters allowed in WiFi SSIDs and passwords.
* It considers characters from 'A' to 'Z', 'a' to 'z', '0' to '9', as well as
* the following special characters: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
*
* @param string Pointer to the string to be validated.
* @param size Size of the string including the null-terminator.
* @return true if the string contains only allowed characters or is NULL,
* false otherwise.
*/
bool validateWiFiString(char *string, size_t size);
/**
* @brief Copies a string to a buffer, replacing invalid WiFi SSID characters with a placeholder.
*
* This function checks each character in the provided input string to ensure
* that it contains only characters allowed in WiFi SSIDs and passwords. If a character
* is invalid, it replaces it with a placeholder character (e.g., '_').
* It considers characters from 'A' to 'Z', 'a' to 'z', '0' to '9', as well as
* the following special characters: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
*
* @param input Pointer to the input string to be validated and copied.
* @param buffer Pointer to the buffer where the output string will be copied.
* @param bufferSize Size of the buffer including the null-terminator.
*/
void sanitizeWiFiString(const char *input, char *buffer, size_t bufferSize);
#endif // UTILITIES_H

View File

@@ -17,6 +17,9 @@ platform = espressif8266
framework = arduino framework = arduino
board = d1_mini board = d1_mini
custom_firmware_version = 1.07
custom_flash_version = 1.07
upload_protocol = esptool upload_protocol = esptool
upload_speed = 921600 upload_speed = 921600
;upload_port = 10.0.1.48 ;upload_port = 10.0.1.48
@@ -26,7 +29,7 @@ upload_speed = 921600
build_flags= build_flags=
-DATOMIC_FS_UPDATE -DATOMIC_FS_UPDATE
-DFEATURE_ENABLE_WIFI_CLIENT ;-DFEATURE_ENABLE_WIFI_CLIENT
;-DFEATURE_ENABLE_LORA ;-DFEATURE_ENABLE_LORA
-DFEATURE_ENABLE_UARTLORA -DFEATURE_ENABLE_UARTLORA
-DWIFI_AP_IP_GW=10,0,0,1 -DWIFI_AP_IP_GW=10,0,0,1

View File

@@ -3,6 +3,7 @@
const char *BatteryString[] = { const char *BatteryString[] = {
"Undefined", "Undefined",
"LiPo 2S", "LiPo 2S",
"LiPo 3S"}; "LiPo 3S"
};
const size_t BatteryString_Elements = sizeof(BatteryString) / sizeof(BatteryString[0]); const size_t BatteryString_Elements = sizeof(BatteryString) / sizeof(BatteryString[0]);

View File

@@ -15,7 +15,17 @@
DebugStatus_t DebuggerStatus[dbg_cntElements]; DebugStatus_t DebuggerStatus[dbg_cntElements];
void processCmdDebug(String command); // Funktionszeiger
typedef void (*CommandFunction)();
// Struktur zur Zuordnung von Commands zu Funktionen
struct CommandMapping
{
const char *command;
CommandFunction function;
};
void processCmdDebug(const char *command);
void Debug_formatCFG(); void Debug_formatCFG();
void Debug_formatPersistence(); void Debug_formatPersistence();
void Debug_printSystemInfo(); void Debug_printSystemInfo();
@@ -26,8 +36,97 @@ void Debug_dumpPersistance();
void Debug_ShowDTCs(); void Debug_ShowDTCs();
void Debug_dumpGlobals(); void Debug_dumpGlobals();
void Debug_printHelp(); void Debug_printHelp();
void Debug_Reboot();
const char *uint32_to_binary_string(uint32_t num); const char *uint32_to_binary_string(uint32_t num);
// Adapter-Functions for Debug-Commands
void adapterCheckEEPOM() { Debug_CheckEEPOM(false); }
void adapterCheckEEPOMFix() { Debug_CheckEEPOM(true); }
void adapterDumpEEPROM1k() { dumpEEPROM(0, 1024); }
void adapterDumpEEPROMAll() { dumpEEPROM(0, EEPROM_SIZE_BYTES); }
void adapterKillEEPROM() { writeSequentialToEEPROM(0, 1024); }
void adapterZeroEEPROM() { writeZeroToEEPROM(0, 1024); }
void adapterResetPageEEPROM() { MovePersistencePage_EEPROM(true); }
void adapterSaveEEPROM() { globals.requestEEAction = EE_ALL_SAVE; }
void adapterSetDebugPort() { SetDebugportStatus(dbg_Serial, enabled); }
void adapterCritDTC() { MaintainDTC(DTC_FAKE_DTC_CRIT, true, millis()); }
void adapterWarnDTC() { MaintainDTC(DTC_FAKE_DTC_WARN, true, millis()); }
void adapterInfoDTC() { MaintainDTC(DTC_FAKE_DTC_INFO, true, millis()); }
void adapterNotifyError() { Websocket_PushNotification("Debug Error Notification", error); }
void adapterNotifyWarning() { Websocket_PushNotification("Debug Warning Notification", warning); }
void adapterNotifySuccess() { Websocket_PushNotification("Debug Success Notification", success); }
void adapterNotifyInfo() { Websocket_PushNotification("Debug Info Notification", info); }
// Definition der Command-Mapping-Tabelle
const CommandMapping commandMappings[] = {
{"help", Debug_printHelp},
{"reboot", Debug_Reboot},
{"sysinfo", Debug_printSystemInfo},
{"netinfo", Debug_printWifiInfo},
{"formatCFG", Debug_formatCFG},
{"formatPDS", Debug_formatPersistence},
{"checkEE", adapterCheckEEPOM},
{"checkEEfix", adapterCheckEEPOMFix},
{"dumpEE1k", adapterDumpEEPROM1k},
{"dumpEE", adapterDumpEEPROMAll},
{"killEE", adapterKillEEPROM},
{"zeroEE", adapterZeroEEPROM},
{"resetPageEE", adapterResetPageEEPROM},
{"dumpCFG", Debug_dumpConfig},
{"dumpPDS", Debug_dumpPersistance},
{"saveEE", adapterSaveEEPROM},
{"dumpGlobals", Debug_dumpGlobals},
{"sdbg", adapterSetDebugPort},
{"dtc_show", Debug_ShowDTCs},
{"dtc_clear", ClearAllDTC},
{"dtc_crit", adapterCritDTC},
{"dtc_warn", adapterWarnDTC},
{"dtc_info", adapterInfoDTC},
{"notify_error", adapterNotifyError},
{"notify_warning", adapterNotifyWarning},
{"notify_success", adapterNotifySuccess},
{"notify_info", adapterNotifyInfo},
};
const size_t NUM_COMMANDS = sizeof(commandMappings) / sizeof(commandMappings[0]);
const char helpText[][64] PROGMEM = {
"help - Print this help text",
"sysinfo - System Info",
"reboot - System Reboot",
"netinfo - WiFi Info",
"formatPDS - Format Persistence EEPROM Data",
"formatCFG - Format Configuration EEPROM Data",
"checkEE - Check EEPROM with checksum",
"checkEEfix - Check and fix EEPROM with checksum",
"dumpEE1k - Dump the first 1kb of EEPROM to Serial",
"dumpEE - Dump the whole EEPROM to Serial",
"killEE - Kill the first 1024 bytes of EEPROM",
"zeroEE - Zero the first 1024 bytes of EEPROM",
"resetPageEE - Reset the PersistenceData Page",
"dumpCFG - Print Config struct",
"dumpPDS - Print PersistenceStruct",
"saveEE - Save EE-Data",
"dumpGlobals - Print globals",
"sdbg - Set debug port status",
"dtc_show - Show all DTCs",
"dtc_clear - Clear all DTCs",
"dtc_crit - Maintain critical DTC",
"dtc_warn - Maintain warning DTC",
"dtc_info - Maintain info DTC",
"notify_error - Send error notification",
"notify_warning - Send warning notification",
"notify_success - Send success notification",
"notify_info - Send info notification"
};
const size_t NUM_HELP_LINES = sizeof(helpText) / sizeof(helpText[0]);
// Überprüfen, ob die Anzahl der Commands und Hilfetext-Zeilen übereinstimmen
static_assert(NUM_COMMANDS == NUM_HELP_LINES, "Number of commands and help text lines do not match!");
/** /**
* @brief Initializes the debugger by setting the initial status for different debug ports. * @brief Initializes the debugger by setting the initial status for different debug ports.
* Serial debug output is turned off. * Serial debug output is turned off.
@@ -109,7 +208,7 @@ void Debug_Process()
break; break;
case CMD_COMPLETE: case CMD_COMPLETE:
processCmdDebug(String(inputBuffer)); processCmdDebug(inputBuffer);
break; break;
case CMD_OVERFLOW: case CMD_OVERFLOW:
@@ -184,65 +283,22 @@ void Debug_pushMessage(const char *format, ...)
* *
* @param command The debug command to be processed. * @param command The debug command to be processed.
*/ */
void processCmdDebug(String command) void processCmdDebug(const char *command)
{ {
// Check the received command and execute corresponding actions bool commandFound = false;
if (command == "help") for (size_t i = 0; i < NUM_COMMANDS; ++i)
Debug_printHelp(); {
else if (command == "reboot") if (strcmp(command, commandMappings[i].command) == 0)
globals.systemStatus = sysStat_Shutdown; {
else if (command == "sysinfo") commandMappings[i].function();
Debug_printSystemInfo(); commandFound = true;
else if (command == "netinfo") break;
Debug_printWifiInfo(); }
else if (command == "formatCFG") }
Debug_formatCFG(); if (!commandFound)
else if (command == "formatPDS") {
Debug_formatPersistence();
else if (command == "checkEE")
Debug_CheckEEPOM(false);
else if (command == "checkEEfix")
Debug_CheckEEPOM(true);
else if (command == "dumpEE1k")
dumpEEPROM(0, 1024);
else if (command == "dumpEE")
dumpEEPROM(0, EEPROM_SIZE_BYTES);
else if (command == "killEE")
writeSequentialToEEPROM(0, 1024);
else if (command == "zeroEE")
writeZeroToEEPROM(0, 1024);
else if (command == "resetPageEE")
MovePersistencePage_EEPROM(true);
else if (command == "dumpCFG")
Debug_dumpConfig();
else if (command == "dumpPDS")
Debug_dumpPersistance();
else if (command == "saveEE")
globals.requestEEAction = EE_ALL_SAVE;
else if (command == "dumpGlobals")
Debug_dumpGlobals();
else if (command == "sdbg")
SetDebugportStatus(dbg_Serial, enabled);
else if (command == "dtc_show")
Debug_ShowDTCs();
else if (command == "dtc_clear")
ClearAllDTC();
else if (command == "dtc_crit")
MaintainDTC(DTC_FAKE_DTC_CRIT, true, millis());
else if (command == "dtc_warn")
MaintainDTC(DTC_FAKE_DTC_WARN, true, millis());
else if (command == "dtc_info")
MaintainDTC(DTC_FAKE_DTC_INFO, true, millis());
else if (command == "notify_error")
Websocket_PushNotification("Debug Error Notification", error);
else if (command == "notify_warning")
Websocket_PushNotification("Debug Warning Notification", warning);
else if (command == "notify_success")
Websocket_PushNotification("Debug Success Notification", success);
else if (command == "notify_info")
Websocket_PushNotification("Debug Info Notification", info);
else
Debug_pushMessage("unknown Command\n"); Debug_pushMessage("unknown Command\n");
}
} }
/** /**
@@ -415,7 +471,7 @@ void Debug_ShowDTCs()
char buff_active[9]; char buff_active[9];
// Header for the DTC display // Header for the DTC display
Debug_pushMessage("\n timestamp | DTC-Nr. | status | severity\n"); Debug_pushMessage("\n timestamp | DTC-Nr. | status | debugval\n");
// Iterate through DTCStorage and display each entry // Iterate through DTCStorage and display each entry
for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++) for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++)
@@ -439,31 +495,37 @@ void Debug_ShowDTCs()
strcpy(buff_active, "none"); strcpy(buff_active, "none");
// Display DTC information // Display DTC information
Debug_pushMessage("%s %7d %8s %8d\n", buff_timestamp, DTCStorage[i].Number, buff_active); Debug_pushMessage("%s %7d %8s %8d\n", buff_timestamp, DTCStorage[i].Number, buff_active, DTCStorage[i].debugVal);
} }
} }
} }
/** /**
* @brief Displays the help commands for debugging through Serial or WebUI. * @brief Prints the help information stored in PROGMEM.
* Each command is printed individually in a formatted manner.
*/ */
void Debug_printHelp() void Debug_printHelp()
{ {
char buff[64]; char buffer[64];
for (size_t i = 0; i < NUM_HELP_LINES; ++i)
// Iterate through helpCmd and display each command
for (unsigned int i = 0; i < sizeof(helpCmd) / 63; i++)
{ {
// Copy a portion of helpCmd to buff for display strcpy_P(buffer, (PGM_P)pgm_read_word(&(helpText[i])));
memcpy_P(buff, (helpCmd + (i * 63)), 63);
buff[63] = 0;
// Display the help command // Display the help command
Debug_pushMessage(buff); Debug_pushMessage(buffer);
} }
} }
/**
* @brief Initiates a system reboot by setting the system status to shutdown.
*
* This function sets the global system status to `sysStat_Shutdown`,
* which will trigger a system reboot sequence.
*/
void Debug_Reboot()
{
globals.systemStatus = sysStat_Shutdown;
}
/** /**
* @brief Convert a uint32_t value to a binary string with nibbles separated by a space. * @brief Convert a uint32_t value to a binary string with nibbles separated by a space.
* *

View File

@@ -9,6 +9,7 @@
#include "eeprom.h" #include "eeprom.h"
#include "debugger.h" #include "debugger.h"
#include "globals.h" #include "globals.h"
#include "utilities.h"
// Instance of I2C_eeprom for EEPROM access // Instance of I2C_eeprom for EEPROM access
I2C_eeprom ee(I2C_EE_ADDRESS, EEPROM_SIZE_BYTES); I2C_eeprom ee(I2C_EE_ADDRESS, EEPROM_SIZE_BYTES);
@@ -35,12 +36,13 @@ boolean checkEEPROMavailable();
* *
* This function initializes the EEPROM using the I2C_eeprom instance and checks if it's available. * This function initializes the EEPROM using the I2C_eeprom instance and checks if it's available.
*/ */
void InitEEPROM() boolean InitEEPROM()
{ {
Wire.begin();
ConfigData = ConfigData_defaults; ConfigData = ConfigData_defaults;
PersistenceData = {0}; PersistenceData = {0};
ee.begin(); ee.begin();
checkEEPROMavailable(); return checkEEPROMavailable();
} }
/** /**
@@ -156,7 +158,7 @@ void GetConfig_EEPROM()
ConfigData.checksum = checksum; ConfigData.checksum = checksum;
uint32_t ConfigSanityCheckResult = ConfigSanityCheck(false); uint32_t ConfigSanityCheckResult = ConfigSanityCheck(true);
MaintainDTC(DTC_EEPROM_CFG_SANITY, (ConfigSanityCheckResult > 0), ConfigSanityCheckResult); MaintainDTC(DTC_EEPROM_CFG_SANITY, (ConfigSanityCheckResult > 0), ConfigSanityCheckResult);
} }
@@ -429,48 +431,6 @@ uint32_t ConfigSanityCheck(bool autocorrect)
return setting_reset_bits; return setting_reset_bits;
} }
/**
* @brief Validates whether a given string contains only characters allowed in WiFi SSIDs and passwords.
*
* This function checks each character in the provided string to ensure
* that it contains only characters allowed in WiFi SSIDs and passwords.
* It considers characters from 'A' to 'Z', 'a' to 'z', '0' to '9', as well as
* the following special characters: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
*
* @param string Pointer to the string to be validated.
* @param size Size of the string including the null-terminator.
* @return true if the string contains only allowed characters or is NULL,
* false otherwise.
*/
bool validateWiFiString(char *string, size_t size)
{
if (string == NULL)
return false;
for (size_t i = 0; i < size; i++)
{
char c = string[i];
if (c == '\0')
{
// Reached the end of the string, all characters were valid WiFi characters.
return true;
}
if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') || c == '!' || c == '"' || c == '#' ||
c == '$' || c == '%' || c == '&' || c == '\'' || c == '(' ||
c == ')' || c == '*' || c == '+' || c == ',' || c == '-' ||
c == '.' || c == '/' || c == ':' || c == ';' || c == '<' ||
c == '=' || c == '>' || c == '?' || c == '@' || c == '[' ||
c == '\\' || c == ']' || c == '^' || c == '_' || c == '`' ||
c == '{' || c == '|' || c == '}' || c == '~'))
{
// Found a character that is not a valid WiFi character.
return false;
}
}
// If the loop completes without finding a null terminator, the string is invalid.
return false;
}
/** /**
* @brief Write sequential numbers to a portion of EEPROM. * @brief Write sequential numbers to a portion of EEPROM.

View File

@@ -79,7 +79,7 @@ void LoRa_Process()
if (e220ttl.available() > 1) if (e220ttl.available() > 1)
{ {
ResponseContainer rc = e220ttl.receiveMessageRSSI(); ResponseContainer rc = e220ttl.receiveMessageRSSI();
// Is something goes wrong print error // If something goes wrong, print error
if (rc.status.code != 1) if (rc.status.code != 1)
{ {
Serial.println(rc.status.getResponseDescription()); Serial.println(rc.status.getResponseDescription());
@@ -96,34 +96,45 @@ void LoRa_Process()
#elif defined(FEATURE_ENABLE_UARTLORA) #elif defined(FEATURE_ENABLE_UARTLORA)
static char packageInput[32]; static char packageInput[32];
static bool packageRecieved = false; static bool packageReceived = false;
static unsigned int bufferPtr = 0; static unsigned int bufferPtr = 0;
int recievedSize = 0; int receivedSize = 0;
while (SerialLoRa.available() && packageRecieved == false) while (SerialLoRa.available() && !packageReceived)
{ {
if (bufferPtr < sizeof(packageInput) - 1) if (bufferPtr < sizeof(packageInput) - 1)
{ {
packageInput[bufferPtr] = SerialLoRa.read(); char c = SerialLoRa.read();
packageInput[bufferPtr + 1] = 0; // always terminate String packageInput[bufferPtr] = c;
packageInput[bufferPtr + 1] = '\0'; // always terminate string
if (packageInput[bufferPtr] == '\n') if (c == '\n')
{ {
packageRecieved = true; packageReceived = true;
recievedSize = bufferPtr; receivedSize = bufferPtr;
bufferPtr = 0; bufferPtr = 0;
Debug_pushMessage("Got LoRa UART: %s\n", packageInput); Debug_pushMessage("Got LoRa UART: %s\n", packageInput);
} }
else if ((packageInput[bufferPtr] >= 0x30) || (packageInput[bufferPtr] <= 0x5A)) // only accept Numbers, UpperCase-Letters and some special chars else if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c == ' ' || c == ',' || c == '.')) // Accept numbers, uppercase letters, and some special chars
{ {
bufferPtr++; bufferPtr++;
} }
else
{
Debug_pushMessage("Invalid character received: %c\n", c);
}
}
else
{
Debug_pushMessage("Buffer overflow. Resetting buffer.\n");
bufferPtr = 0;
} }
} }
if (packageRecieved) { if (packageReceived)
Parse_LoRa_UartCommand(packageInput, recievedSize); {
packageRecieved = false; Parse_LoRa_UartCommand(packageInput, receivedSize);
packageReceived = false;
} }
#endif #endif
@@ -238,7 +249,7 @@ void printParameters(struct Configuration configuration)
void Parse_LoRa_UartCommand(char input[], int size) void Parse_LoRa_UartCommand(char input[], int size)
{ {
Debug_pushMessage("Start parsing, size: %d", size); Debug_pushMessage("Start parsing, size: %d\n", size);
char delimiter[] = ";"; char delimiter[] = ";";
char *ptr; char *ptr;
char command[8]; char command[8];
@@ -251,14 +262,14 @@ void Parse_LoRa_UartCommand(char input[], int size)
while (ptr != NULL) while (ptr != NULL)
{ {
strncpy(command, ptr, sizeof(command) - 1); // Platz für Nullterminator lassen strncpy(command, ptr, sizeof(command) - 1); // Platz für Nullterminator lassen
command[sizeof(command) - 1] = '\0'; // Nullterminator setzen command[sizeof(command) - 1] = '\0'; // Nullterminator setzen
ptr = strtok(NULL, delimiter); ptr = strtok(NULL, delimiter);
if (ptr != NULL) if (ptr != NULL)
{ {
strncpy(value, ptr, sizeof(value) - 1); // Platz für Nullterminator lassen strncpy(value, ptr, sizeof(value) - 1); // Platz für Nullterminator lassen
value[sizeof(value) - 1] = '\0'; // Nullterminator setzen value[sizeof(value) - 1] = '\0'; // Nullterminator setzen
} }
else else
{ {
@@ -267,7 +278,7 @@ void Parse_LoRa_UartCommand(char input[], int size)
} }
// Hier kannst du den Wert und das Kommando verarbeiten // Hier kannst du den Wert und das Kommando verarbeiten
Debug_pushMessage("Command: %s, Value: %s", command, value); Debug_pushMessage("Command: %s, Value: %s\n", command, value);
} }
Debug_pushMessage("Parsed LoRa UART Command: %s Value: %s\n", command, value); Debug_pushMessage("Parsed LoRa UART Command: %s Value: %s\n", command, value);
@@ -275,12 +286,12 @@ void Parse_LoRa_UartCommand(char input[], int size)
if (!strcmp(command, "ENABLE")) if (!strcmp(command, "ENABLE"))
{ {
globals.timer_disabled = false; globals.timer_disabled = false;
Debug_pushMessage("Enabled by LoRa"); Debug_pushMessage("Enabled by LoRa\n");
} }
else if (!strcmp(command, "DISABLE")) else if (!strcmp(command, "DISABLE"))
{ {
globals.timer_disabled = true; globals.timer_disabled = true;
Debug_pushMessage("Disabled by LoRa"); Debug_pushMessage("Disabled by LoRa\n");
} }
else if (!strcmp(command, "RESET")) else if (!strcmp(command, "RESET"))
{ {
@@ -288,7 +299,7 @@ void Parse_LoRa_UartCommand(char input[], int size)
PersistenceData.faction_1_timer = 0; PersistenceData.faction_1_timer = 0;
PersistenceData.faction_2_timer = 0; PersistenceData.faction_2_timer = 0;
PersistenceData.faction_3_timer = 0; PersistenceData.faction_3_timer = 0;
Debug_pushMessage("Reset by LoRa"); Debug_pushMessage("Reset by LoRa\n");
} }
else if (!strcmp(command, "TMRSTP")) else if (!strcmp(command, "TMRSTP"))
{ {

View File

@@ -19,6 +19,7 @@
#include "globals.h" #include "globals.h"
#include "dtc.h" #include "dtc.h"
#include "debugger.h" #include "debugger.h"
#include "utilities.h"
#if defined(FEATURE_ENABLE_LORA) || defined(FEATURE_ENABLE_UARTLORA) #if defined(FEATURE_ENABLE_LORA) || defined(FEATURE_ENABLE_UARTLORA)
#include "lora_net.h" #include "lora_net.h"
#endif #endif
@@ -84,7 +85,8 @@ void setup()
system_update_cpu_freq(SYS_CPU_80MHZ); system_update_cpu_freq(SYS_CPU_80MHZ);
// Generate a unique device name based on ESP chip ID // Generate a unique device name based on ESP chip ID
snprintf(globals.DeviceName, 32, HOST_NAME, ESP.getChipId()); strncpy(globals.DeviceName, HOST_NAME, sizeof(globals.DeviceName));
snprintf(globals.DeviceNameId, sizeof(globals.DeviceNameId), "%s_%08X", globals.DeviceName, ESP.getChipId());
// Disable WiFi persistent storage // Disable WiFi persistent storage
WiFi.persistent(false); WiFi.persistent(false);
@@ -95,7 +97,7 @@ void setup()
#ifdef FEATURE_ENABLE_WIFI_CLIENT #ifdef FEATURE_ENABLE_WIFI_CLIENT
// Configure WiFi settings for client mode if enabled // Configure WiFi settings for client mode if enabled
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
WiFi.setHostname(globals.DeviceName); WiFi.setHostname(globals.DeviceNameId);
wifiMulti.addAP(QUOTE(WIFI_SSID_CLIENT), QUOTE(WIFI_PASSWORD_CLIENT)); wifiMulti.addAP(QUOTE(WIFI_SSID_CLIENT), QUOTE(WIFI_PASSWORD_CLIENT));
tmrWiFiMaintainConnection.start(); tmrWiFiMaintainConnection.start();
#else #else
@@ -108,14 +110,20 @@ void setup()
Serial.setDebugOutput(false); Serial.setDebugOutput(false);
Serial.print("\n\n-------------------START-------------------\n"); Serial.print("\n\n-------------------START-------------------\n");
Serial.print(globals.DeviceName); Serial.print(globals.DeviceNameId);
Serial.print("\nby Hiabuto Defense\n"); Serial.print("\nby Hiabuto Defense\n\n");
// Initialize EEPROM, load configuration, and persistence data from EEPROM // Initialize EEPROM, load configuration, and persistence data from EEPROM
InitEEPROM(); if (InitEEPROM())
GetConfig_EEPROM(); {
GetPersistence_EEPROM(); GetConfig_EEPROM();
Serial.print("\nEE-Init done"); GetPersistence_EEPROM();
Serial.printf("Initialized EEPROM at Address 0x%02X\n", I2C_EE_ADDRESS);
}
else
{
Serial.print("EEPROM not Initialized\n");
}
if (i2c_io.begin()) if (i2c_io.begin())
{ {
@@ -150,7 +158,7 @@ void setup()
// Set up OTA updates // Set up OTA updates
ArduinoOTA.setPort(8266); ArduinoOTA.setPort(8266);
ArduinoOTA.setHostname(globals.DeviceName); ArduinoOTA.setHostname(globals.DeviceNameId);
ArduinoOTA.setPassword(QUOTE(ADMIN_PASSWORD)); ArduinoOTA.setPassword(QUOTE(ADMIN_PASSWORD));
ArduinoOTA.onStart([]() ArduinoOTA.onStart([]()
@@ -208,7 +216,6 @@ void setup()
// Start cyclic EEPROM updates for Persistence Data Structure (PDS) // Start cyclic EEPROM updates for Persistence Data Structure (PDS)
tmrEEPROMCyclicPDS.start(); tmrEEPROMCyclicPDS.start();
Serial.print("\nSetup Done\n");
disp_FAC_1.init(); disp_FAC_1.init();
disp_FAC_1.setBrightness(5); disp_FAC_1.setBrightness(5);
@@ -217,7 +224,6 @@ void setup()
disp_FAC_3.init(); disp_FAC_3.init();
disp_FAC_3.setBrightness(5); disp_FAC_3.setBrightness(5);
tmrEEPROMCyclicPDS.start();
tmrFactionTicker.start(); tmrFactionTicker.start();
tmrInputGetter.start(); tmrInputGetter.start();
@@ -415,7 +421,7 @@ void tmrCallback_InputGetter()
if (keysPressed > 1) if (keysPressed > 1)
{ {
Debug_pushMessage("ERROR: More than one Flag active - setting no Faction active"); Debug_pushMessage("ERROR: More than one Flag active - setting no Faction active\n");
PersistenceData.activeFaction = NONE; PersistenceData.activeFaction = NONE;
return; return;
} }
@@ -424,7 +430,7 @@ void tmrCallback_InputGetter()
{ {
if (PersistenceData.activeFaction != FACTION_1) if (PersistenceData.activeFaction != FACTION_1)
{ {
Debug_pushMessage("Faction 1 captured !"); Debug_pushMessage("Faction 1 captured !\n");
globals.requestEEAction = EE_PDS_SAVE; globals.requestEEAction = EE_PDS_SAVE;
} }
PersistenceData.activeFaction = FACTION_1; PersistenceData.activeFaction = FACTION_1;
@@ -434,7 +440,7 @@ void tmrCallback_InputGetter()
{ {
if (PersistenceData.activeFaction != FACTION_2) if (PersistenceData.activeFaction != FACTION_2)
{ {
Debug_pushMessage("Faction 2 captured !"); Debug_pushMessage("Faction 2 captured !\n");
globals.requestEEAction = EE_PDS_SAVE; globals.requestEEAction = EE_PDS_SAVE;
} }
PersistenceData.activeFaction = FACTION_2; PersistenceData.activeFaction = FACTION_2;
@@ -444,7 +450,7 @@ void tmrCallback_InputGetter()
{ {
if (PersistenceData.activeFaction != FACTION_3) if (PersistenceData.activeFaction != FACTION_3)
{ {
Debug_pushMessage("Faction 3 captured !"); Debug_pushMessage("Faction 3 captured !\n");
globals.requestEEAction = EE_PDS_SAVE; globals.requestEEAction = EE_PDS_SAVE;
} }
PersistenceData.activeFaction = FACTION_3; PersistenceData.activeFaction = FACTION_3;
@@ -566,6 +572,7 @@ void wifiMaintainConnectionTicker_callback()
*/ */
void toggleWiFiAP(bool shutdown) void toggleWiFiAP(bool shutdown)
{ {
char buffer[33];
// Check if WiFi is currently active // Check if WiFi is currently active
if (WiFi.getMode() != WIFI_OFF) if (WiFi.getMode() != WIFI_OFF)
{ {
@@ -583,7 +590,8 @@ void toggleWiFiAP(bool shutdown)
// Start WiFi in Access Point (AP) mode // Start WiFi in Access Point (AP) mode
WiFi.mode(WIFI_AP); WiFi.mode(WIFI_AP);
WiFi.softAPConfig(IPAddress(WIFI_AP_IP_GW), IPAddress(WIFI_AP_IP_GW), IPAddress(255, 255, 255, 0)); WiFi.softAPConfig(IPAddress(WIFI_AP_IP_GW), IPAddress(WIFI_AP_IP_GW), IPAddress(255, 255, 255, 0));
WiFi.softAP(globals.DeviceName, QUOTE(WIFI_AP_PASSWORD)); sanitizeWiFiString(globals.DeviceNameId, buffer, sizeof(buffer));
WiFi.softAP(buffer, QUOTE(WIFI_AP_PASSWORD));
// Stop WiFi maintenance connection ticker if enabled and display debug messages // Stop WiFi maintenance connection ticker if enabled and display debug messages
#ifdef FEATURE_ENABLE_WIFI_CLIENT #ifdef FEATURE_ENABLE_WIFI_CLIENT
@@ -696,7 +704,7 @@ void ProcessKeyCombos(bool *btnState)
if (keyCount_Fac2 == 2 && keyCount_Fac3 == 0) if (keyCount_Fac2 == 2 && keyCount_Fac3 == 0)
{ {
Debug_pushMessage("KeyCombo: WiFi AP ON"); Debug_pushMessage("KeyCombo: WiFi AP ON\n");
OverrideDisplay(5000, "NET ", " ", " "); OverrideDisplay(5000, "NET ", " ", " ");
toggleWiFiAP(false); toggleWiFiAP(false);
} }

View File

@@ -0,0 +1,94 @@
#include "utilities.h"
/**
* @brief Validates whether a given string contains only characters allowed in WiFi SSIDs and passwords.
*
* This function checks each character in the provided string to ensure
* that it contains only characters allowed in WiFi SSIDs and passwords.
* It considers characters from 'A' to 'Z', 'a' to 'z', '0' to '9', as well as
* the following special characters: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
*
* @param string Pointer to the string to be validated.
* @param size Size of the string including the null-terminator.
* @return true if the string contains only allowed characters or is NULL,
* false otherwise.
*/
bool validateWiFiString(char *string, size_t size)
{
if (string == NULL)
return false;
for (size_t i = 0; i < size; i++)
{
char c = string[i];
if (c == '\0')
{
// Reached the end of the string, all characters were valid WiFi characters.
return true;
}
if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') || c == '!' || c == '"' || c == '#' ||
c == '$' || c == '%' || c == '&' || c == '\'' || c == '(' ||
c == ')' || c == '*' || c == '+' || c == ',' || c == '-' ||
c == '.' || c == '/' || c == ':' || c == ';' || c == '<' ||
c == '=' || c == '>' || c == '?' || c == '@' || c == '[' ||
c == '\\' || c == ']' || c == '^' || c == '_' || c == '`' ||
c == '{' || c == '|' || c == '}' || c == '~'))
{
// Found a character that is not a valid WiFi character.
return false;
}
}
// If the loop completes without finding a null terminator, the string is invalid.
return false;
}
/**
* @brief Copies a string to a buffer, replacing invalid WiFi SSID characters with a placeholder.
*
* This function checks each character in the provided input string to ensure
* that it contains only characters allowed in WiFi SSIDs and passwords. If a character
* is invalid, it replaces it with a placeholder character (e.g., '_').
* It considers characters from 'A' to 'Z', 'a' to 'z', '0' to '9', as well as
* the following special characters: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
*
* @param input Pointer to the input string to be validated and copied.
* @param buffer Pointer to the buffer where the output string will be copied.
* @param bufferSize Size of the buffer including the null-terminator.
*/
void sanitizeWiFiString(const char *input, char *buffer, size_t bufferSize)
{
if (input == NULL || buffer == NULL || bufferSize == 0)
return;
size_t i;
for (i = 0; i < bufferSize - 1; i++) // Leave space for null-terminator
{
char c = input[i];
if (c == '\0')
{
// Reached the end of the input string, terminate the buffer
buffer[i] = '\0';
return;
}
if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') || c == '!' || c == '"' || c == '#' ||
c == '$' || c == '%' || c == '&' || c == '\'' || c == '(' ||
c == ')' || c == '*' || c == '+' || c == ',' || c == '-' ||
c == '.' || c == '/' || c == ':' || c == ';' || c == '<' ||
c == '=' || c == '>' || c == '?' || c == '@' || c == '[' ||
c == '\\' || c == ']' || c == '^' || c == '_' || c == '`' ||
c == '{' || c == '|' || c == '}' || c == '~'))
{
// Replace invalid character with placeholder
buffer[i] = '_';
}
else
{
// Copy valid character to buffer
buffer[i] = c;
}
}
// Null-terminate the buffer
buffer[i] = '\0';
}

View File

@@ -71,7 +71,7 @@ void initWebUI()
} }
// Initialize mDNS and add service // Initialize mDNS and add service
MDNS.begin(globals.DeviceName); MDNS.begin(globals.DeviceNameId);
MDNS.addService("http", "tcp", 80); MDNS.addService("http", "tcp", 80);
// Set up WebSocket event handler and attach to web server // Set up WebSocket event handler and attach to web server
@@ -342,7 +342,7 @@ void WebServerEEJSON_Callback(AsyncWebServerRequest *request)
char buffer[16]; char buffer[16];
info["DeviceName"] = globals.DeviceName; info["DeviceNameId"] = globals.DeviceNameId;
sprintf(buffer, "%d.%02d", constants.Required_Flash_Version_major, constants.Required_Flash_Version_minor); sprintf(buffer, "%d.%02d", constants.Required_Flash_Version_major, constants.Required_Flash_Version_minor);
info["FW-Version"] = buffer; info["FW-Version"] = buffer;
info["FS-Version"] = globals.FlashVersion; info["FS-Version"] = globals.FlashVersion;
@@ -497,7 +497,6 @@ void Websocket_HandleSettings(uint8_t *data)
{ {
ConfigData.active_faction_on_reboot = value[0] == '1' ? true : false; ConfigData.active_faction_on_reboot = value[0] == '1' ? true : false;
} }
else if (strcmp(identifier, "name_faction1") == 0) else if (strcmp(identifier, "name_faction1") == 0)
{ {
strncpy(ConfigData.Faction_1_Name, value, sizeof(ConfigData.Faction_1_Name)); strncpy(ConfigData.Faction_1_Name, value, sizeof(ConfigData.Faction_1_Name));
@@ -511,10 +510,6 @@ void Websocket_HandleSettings(uint8_t *data)
strncpy(ConfigData.Faction_3_Name, value, sizeof(ConfigData.Faction_3_Name)); strncpy(ConfigData.Faction_3_Name, value, sizeof(ConfigData.Faction_3_Name));
} }
else if (strcmp(identifier, "batterytype") == 0) else if (strcmp(identifier, "batterytype") == 0)
{
strncpy(ConfigData.wifi_client_ssid, value, sizeof(ConfigData.wifi_client_ssid));
}
else if (strcmp(identifier, "batterytype") == 0)
{ {
int index = findIndexByString(value, BatteryString, BatteryString_Elements); int index = findIndexByString(value, BatteryString, BatteryString_Elements);
batterytypePreselect = (batteryType_t)index; batterytypePreselect = (batteryType_t)index;
@@ -599,44 +594,45 @@ void Websocket_RefreshClientData_DTCs(uint32_t client_id)
*/ */
void Websocket_RefreshClientData_Status(uint32_t client_id, bool send_mapping) void Websocket_RefreshClientData_Status(uint32_t client_id, bool send_mapping)
{ {
if (send_mapping)
{
const char mapping[] = "MAPPING_STATUS:"
"batterylevel;"
"systemstatus;"
"activefaction;"
"time_faction1;"
"time_faction2;"
"time_faction3;";
if (send_mapping) if (client_id > 0)
{ webSocket.text(client_id, mapping);
const char mapping[] = "MAPPING_STATUS:" else
"batterylevel;" webSocket.textAll(mapping);
"systemstatus;"
"activefaction;" Debug_pushMessage("send MAPPING_STATUS WS-Client Data\n");
"time_faction1;" }
"time_faction2;"
"time_faction3;"; char dataString[200] = {0}; // Maximal 200 Zeichen für den Data-String
sprintf(dataString, "STATUS:%d;%s;%d;%ld;%ld;%ld;",
globals.battery_level,
globals.systemStatustxt,
PersistenceData.activeFaction,
PersistenceData.faction_1_timer,
PersistenceData.faction_2_timer,
PersistenceData.faction_3_timer);
if (client_id > 0) if (client_id > 0)
webSocket.text(client_id, mapping); {
webSocket.text(client_id, dataString);
}
else else
webSocket.textAll(mapping); {
webSocket.textAll(dataString);
Debug_pushMessage("send MAPPING_STATUS WS-Client Data\n"); }
}
String temp = "STATUS:";
temp.concat(String(globals.battery_level) + ";");
temp.concat(String(globals.systemStatustxt) + ";");
temp.concat(String(PersistenceData.activeFaction) + ";");
temp.concat(String(PersistenceData.faction_1_timer) + ";");
temp.concat(String(PersistenceData.faction_2_timer) + ";");
temp.concat(String(PersistenceData.faction_3_timer) + ";");
if (client_id > 0)
{
webSocket.text(client_id, temp);
}
else
{
webSocket.textAll(temp);
}
} }
/** /**
* @brief Refreshes client data related to static configuration parameters on WebSocket clients. * @brief Refreshes client data related to static configuration parameters on WebSocket clients.
* *
@@ -650,40 +646,53 @@ void Websocket_RefreshClientData_Status(uint32_t client_id, bool send_mapping)
void Websocket_RefreshClientData_Static(uint32_t client_id, bool send_mapping) void Websocket_RefreshClientData_Static(uint32_t client_id, bool send_mapping)
{ {
Debug_pushMessage("send STATIC WS-Client Data\n");
if (send_mapping) if (send_mapping)
{ {
const char mapping[] = "MAPPING_STATIC:" const char mapping[] = "MAPPING_STATIC:"
"devicename;"
"active_faction_on_reboot;" "active_faction_on_reboot;"
"batteryType;" "batteryType;"
"name_faction1;" "name_faction1;"
"name_faction2;" "name_faction2;"
"name_faction3;" "name_faction3;"
"wifi-ssid;" "wifi-ssid;"
"wifi-pass;"; "wifi-pass;"
"fw-version;"
"flash-version;"
"git-revison;";
if (client_id > 0) if (client_id > 0)
webSocket.text(client_id, mapping); webSocket.text(client_id, mapping);
else else
webSocket.textAll(mapping); webSocket.textAll(mapping);
Debug_pushMessage("send MAPPING_STATIC WS-Client Data\n");
} }
String temp = "STATIC:"; char dataString[200] = {0}; // Maximal 200 Zeichen für den Data-String
temp.concat(String(ConfigData.active_faction_on_reboot) + ";"); sprintf(dataString, "STATIC:%s;%d;%d;%s;%s;%s;%s;%s;%d.%02d;%s;%s;",
temp.concat(String(ConfigData.batteryType) + ";"); globals.DeviceName,
temp.concat(String(ConfigData.Faction_1_Name) + ";"); ConfigData.active_faction_on_reboot,
temp.concat(String(ConfigData.Faction_2_Name) + ";"); ConfigData.batteryType,
temp.concat(String(ConfigData.Faction_3_Name) + ";"); ConfigData.Faction_1_Name,
temp.concat(String(ConfigData.wifi_client_ssid) + ";"); ConfigData.Faction_2_Name,
temp.concat(String(ConfigData.wifi_client_password) + ";"); ConfigData.Faction_3_Name,
ConfigData.wifi_client_ssid,
ConfigData.wifi_client_password,
constants.FW_Version_major,
constants.FW_Version_minor,
globals.FlashVersion,
constants.GitHash);
if (client_id > 0) if (client_id > 0)
{ {
webSocket.text(client_id, temp); webSocket.text(client_id, dataString);
} }
else else
{ {
webSocket.textAll(temp); webSocket.textAll(dataString);
} }
} }