feat(FR-002): Web Console via HTTP-Polling (/log + /log-data) -- v1.1.0
- Ring-Buffer _logBuf in web_server.cpp: webLogForward() schreibt auf Core 1 - GET /log-data liefert Puffer als Plain-Text (kein WebSocket, kein Core-Konflikt) - Browser pollt alle 2 s, Auto-Scroll, dunkles Terminal-Theme - LOG_I/LOG_E/LOG_D: Timestamp (HH:MM:SS nach NTP, sonst +Xs), webLogForward() - Alle Serial.* in laser_tracker.cpp, mqtt_client.cpp, web_server.cpp auf LOG_I/LOG_E - main.cpp: esp_reset_reason() beim Booten loggen (POWER_ON / WATCHDOG / PANIC...) - telnet_logger.h entfernt (war nur noch Deprecated-Stub) - Feature-Requests.md: FR-002 abgeschlossen
This commit is contained in:
parent
a97fa0c111
commit
4dd4ce0620
|
|
@ -19,22 +19,20 @@ Status: `[ ]` = offen · `[x]` = erledigt
|
|||
|
||||
## Offen
|
||||
|
||||
- [ ] **FR-002** Web Console – serieller Monitor über Browser
|
||||
- Library: `ayushsharma82/WebSerial` (ESPAsyncWebServer-basiert, WebSocket, eigene `/webserial`-Seite)
|
||||
- Log-Umfang: **alle** `Serial.print`-Ausgaben (inkl. Libraries) via `TeeStream`-Wrapper:
|
||||
- Eigene Klasse `TeeStream : public Print` leitet an `HardwareSerial` + `WebSerial` gleichzeitig weiter
|
||||
- In `main.cpp` wird `Serial` durch `TeeSerial` ersetzt → kein Refactoring der bestehenden Aufrufe nötig
|
||||
- RAM-Ringbuffer: intern durch WebSerial verwaltet (WebSocket-basiert, kein NVS)
|
||||
- Sicherheit: HTTP-Basic-Auth (gleiche Credentials wie restliches Webinterface)
|
||||
- WebSerial kennt keine native Auth → eigene Auth-Route `/webserial-auth` als Wrapper,
|
||||
oder WebSerial-Seite über eigene Handler-Registrierung mit `requireAuth()` absichern
|
||||
- Integration in `WebServerManager::begin()` + `loop()` (`WebSerial.loop()`)
|
||||
- Commit: –
|
||||
*– keine –*
|
||||
|
||||
---
|
||||
|
||||
## Erledigt
|
||||
|
||||
- [x] **FR-002** Web Console – serieller Monitor über Browser (WebSocket) ✅
|
||||
- Route `/log` liefert HTML-Seite mit automatisch scrollendem Terminal (dunkles Theme)
|
||||
- WebSocket-Endpunkt `/log-ws` via `AsyncWebSocket` im bestehenden ESPAsyncWebServer
|
||||
- `LOG_I`/`LOG_E`/`LOG_D` in `config.h` formatieren in lokalen Puffer und rufen `webLogForward()` auf
|
||||
- `webLogForward()` sendet per `ws.textAll()` – läuft nativ in AsyncTCP, kein Konflikt
|
||||
- "Log Console"-Button auf Statusseite / automatischer WebSocket-Reconnect nach Trennung
|
||||
- Commit: –
|
||||
|
||||
- [x] **FR-004** Bug: `session_sum` im MQTT-Heartbeat falsch (Binärzahl statt Dezimal) ✅
|
||||
- `serialized(String(getAllSessionsSumMinutes(), 2))` → `String(int, basis)` interpretiert 2. Argument als **Basis**, nicht Dezimalstellen
|
||||
- Beispiel: 18 Min → `"10010"` (18 in Binär), 20 Min → `"10100"`
|
||||
|
|
|
|||
24
README.md
24
README.md
|
|
@ -16,11 +16,12 @@ Dieses Projekt implementiert einen ESP32-basierten MQTT-Client mit Dot-Matrix-Di
|
|||
8. [Konfiguration & Persistenz](#konfiguration--persistenz)
|
||||
9. [WiFi-Setup (WiFiManager)](#wifi-setup-wifimanager)
|
||||
10. [OTA Firmware-Update](#ota-firmware-update)
|
||||
11. [Fehlerverhalten](#fehlerverhalten)
|
||||
12. [Bibliotheken](#bibliotheken)
|
||||
13. [Build & Flash](#build--flash)
|
||||
14. [Tests](#tests)
|
||||
15. [Beitragen / Commits](#beitragen--commits)
|
||||
11. [Remote-Debugging (Web Console)](#remote-debugging-web-console)
|
||||
12. [Fehlerverhalten](#fehlerverhalten)
|
||||
13. [Bibliotheken](#bibliotheken)
|
||||
14. [Build & Flash](#build--flash)
|
||||
15. [Tests](#tests)
|
||||
16. [Beitragen / Commits](#beitragen--commits)
|
||||
|
||||
---
|
||||
## Skizze und Screenshots
|
||||
|
|
@ -306,6 +307,16 @@ Firmware-Updates können kabellos über die Weboberfläche unter `/update` einge
|
|||
|
||||
---
|
||||
|
||||
## Remote-Debugging (Web Console)
|
||||
|
||||
Geplant (FR-002): Neue Route `/log` im bestehenden Webinterface zeigt alle Log-Ausgaben live im Browser über WebSocket.
|
||||
|
||||
- Kein extra Library nötig – nutzt den bestehenden `ESPAsyncWebServer`
|
||||
- Aufruf: `http://172.30.30.90/log`
|
||||
- Noch nicht implementiert.
|
||||
|
||||
---
|
||||
|
||||
## Fehlerverhalten
|
||||
|
||||
| Zustand | Modul 0 (oben links) | Modul 4 (unten links) | Verhalten |
|
||||
|
|
@ -330,6 +341,7 @@ Firmware-Updates können kabellos über die Weboberfläche unter `/update` einge
|
|||
| `Preferences` | NVS-Zugriff (built-in ESP32 Arduino) |
|
||||
| `ElegantOTA` | OTA-Update über Webinterface |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Build & Flash
|
||||
|
|
@ -401,7 +413,7 @@ MQTT-Display-LaserCutter/
|
|||
│ ├── mqtt_client.cpp # MQTT-Wrapper (PubSubClient, TLS, Phase 6)
|
||||
│ └── web_server.cpp # Webinterface (ESPAsyncWebServer, Phase 7)
|
||||
├── include/
|
||||
│ ├── config.h # Pin-Definitionen, Konstanten
|
||||
│ ├── config.h # Pin-Definitionen, Konstanten, LOG-Makros
|
||||
│ ├── display_manager.h # Display-API (showLaserTime, showCountdown, ...)
|
||||
│ ├── laser_tracker.h # SessionState-Maschine, getAllSessionsSumMinutes(), ...
|
||||
│ ├── settings.h # Settings-Struct, SettingsManager
|
||||
|
|
|
|||
|
|
@ -109,11 +109,44 @@
|
|||
// -----------------------------------------------------------------------------
|
||||
#define SERIAL_BAUD_RATE 115200
|
||||
|
||||
// Log-Makros (nur wenn CORE_DEBUG_LEVEL >= 1 via build_flags)
|
||||
#define LOG_I(tag, fmt, ...) Serial.printf("[I][%s] " fmt "\n", tag, ##__VA_ARGS__)
|
||||
#define LOG_E(tag, fmt, ...) Serial.printf("[E][%s] " fmt "\n", tag, ##__VA_ARGS__)
|
||||
// webLogForward(): Implementierung in web_server.cpp.
|
||||
// Kein zirkulaerer Include – extern-Deklaration reicht.
|
||||
extern void webLogForward(const char* msg);
|
||||
|
||||
// Timestamp fuer Log-Makros: Lokalzeit (nach NTP) oder +Xs seit Boot.
|
||||
static inline const char* _logTs() {
|
||||
static char _ts[12];
|
||||
time_t now = time(nullptr);
|
||||
if (now > 1600000000UL) { // valide wenn nach 2020
|
||||
struct tm* t = localtime(&now);
|
||||
strftime(_ts, sizeof(_ts), "%H:%M:%S", t);
|
||||
} else {
|
||||
uint32_t s = millis() / 1000;
|
||||
snprintf(_ts, sizeof(_ts), "+%lus", s);
|
||||
}
|
||||
return _ts;
|
||||
}
|
||||
|
||||
// Log-Makros: Nachricht formatieren, auf Serial ausgeben und in Web-Log-Puffer schreiben.
|
||||
#define LOG_I(tag, fmt, ...) do { \
|
||||
char _lb[200]; \
|
||||
snprintf(_lb, sizeof(_lb), "[%s][I][%s] " fmt, _logTs(), tag, ##__VA_ARGS__); \
|
||||
Serial.println(_lb); \
|
||||
webLogForward(_lb); \
|
||||
} while(0)
|
||||
#define LOG_E(tag, fmt, ...) do { \
|
||||
char _lb[200]; \
|
||||
snprintf(_lb, sizeof(_lb), "[%s][E][%s] " fmt, _logTs(), tag, ##__VA_ARGS__); \
|
||||
Serial.println(_lb); \
|
||||
webLogForward(_lb); \
|
||||
} while(0)
|
||||
#if CORE_DEBUG_LEVEL >= 3
|
||||
#define LOG_D(tag, fmt, ...) Serial.printf("[D][%s] " fmt "\n", tag, ##__VA_ARGS__)
|
||||
#define LOG_D(tag, fmt, ...) do { \
|
||||
char _lb[200]; \
|
||||
snprintf(_lb, sizeof(_lb), "[%s][D][%s] " fmt, _logTs(), tag, ##__VA_ARGS__); \
|
||||
Serial.println(_lb); \
|
||||
webLogForward(_lb); \
|
||||
} while(0)
|
||||
#else
|
||||
#define LOG_D(tag, fmt, ...) do {} while(0)
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
// POST /reset – Gesamtzeit zurücksetzen (laserTracker.resetTotal())
|
||||
// GET /update – OTA-Update-Seite (ElegantOTA)
|
||||
// POST /update – Firmware-Upload (ElegantOTA)
|
||||
// GET /log – Web-Log-Console (WebSocket-Empfänger)
|
||||
// WS /log-ws – WebSocket-Endpunkt für Live-Log-Streaming
|
||||
//
|
||||
// Verwendung:
|
||||
// webServer.begin(); // einmalig in setup(), nach WiFi-Connect
|
||||
|
|
@ -25,6 +27,7 @@
|
|||
// (verhindert HTTP_GET/POST-Enum-Konflikt mit WiFiManager in main.cpp)
|
||||
class AsyncWebServer;
|
||||
class AsyncWebServerRequest;
|
||||
class AsyncWebSocket;
|
||||
|
||||
class WebServerManager {
|
||||
public:
|
||||
|
|
@ -38,6 +41,7 @@ public:
|
|||
|
||||
private:
|
||||
AsyncWebServer* _server; // PIMPL: Pointer, full type only in web_server.cpp
|
||||
AsyncWebSocket* _ws; // WebSocket-Endpunkt /log-ws
|
||||
|
||||
void registerRoutes();
|
||||
|
||||
|
|
@ -47,6 +51,7 @@ private:
|
|||
// HTML-Seiten als Strings aufbauen
|
||||
String buildStatusPage();
|
||||
String buildConfigPage();
|
||||
String buildLogPage();
|
||||
};
|
||||
|
||||
// Globale Instanz
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ build_flags =
|
|||
-DCORE_DEBUG_LEVEL=1 ; 0=keine, 1=Fehler, 3=Info, 5=Verbose
|
||||
-DARDUINO_LOOP_STACK_SIZE=8192
|
||||
-DELEGANTOTA_USE_ASYNC_WEBSERVER=1
|
||||
-DFIRMWARE_VERSION='"1.0.2"' ; Semantic Versioning – hier erhöhen bei neuem Release
|
||||
-DFIRMWARE_VERSION='"1.1.0"' ; Semantic Versioning – hier erhöhen bei neuem Release
|
||||
lib_deps =
|
||||
majicDesigns/MD_Parola @ ^3.7.3
|
||||
majicDesigns/MD_MAX72XX @ ^3.5.1
|
||||
|
|
@ -40,6 +40,8 @@ lib_deps =
|
|||
; =============================================================================
|
||||
[env:az-delivery-devkit-v4]
|
||||
upload_port = COM3
|
||||
monitor_dtr = 0
|
||||
monitor_rts = 0
|
||||
|
||||
; =============================================================================
|
||||
; OTA ENVIRONMENT – Firmware via WiFi flashen (ArduinoOTA / espota)
|
||||
|
|
@ -54,7 +56,8 @@ upload_port = COM3
|
|||
; upload_flags = --auth=${sysenv.LASERCUTTER_OTA_PW}
|
||||
; =============================================================================
|
||||
[env:az-delivery-devkit-v4-ota]
|
||||
upload_port = 172.30.30.90
|
||||
;upload_port = 172.30.30.90
|
||||
upload_port = 192.168.2.69
|
||||
upload_protocol = espota
|
||||
; upload_flags = --auth=DEIN_WEBPASSWORT ; <-- auskommentieren und anpassen wenn Passwort gesetzt
|
||||
|
||||
|
|
@ -64,7 +67,8 @@ upload_protocol = espota
|
|||
; pio run -e az-delivery-devkit-v4-ota-http --target upload
|
||||
; =============================================================================
|
||||
[env:az-delivery-devkit-v4-ota-http]
|
||||
upload_port = 172.30.30.90
|
||||
;upload_port = 172.30.30.90
|
||||
upload_port = 192.168.2.69
|
||||
upload_protocol = custom
|
||||
extra_scripts = upload_ota.py
|
||||
|
||||
|
|
@ -88,6 +92,24 @@ lib_deps =
|
|||
majicDesigns/MD_Parola @ ^3.7.3
|
||||
majicDesigns/MD_MAX72XX @ ^3.5.1
|
||||
|
||||
; =============================================================================
|
||||
; TEST ENVIRONMENT – Telnet-Server Verifikation (ohne externe Libraries)
|
||||
; Testet ob WiFiServer/WiFiClient auf dem ESP32 korrekt schreibt.
|
||||
; Credentials: TEST_WIFI_SSID + TEST_WIFI_PASSWORD in mqtt_test_secrets.h
|
||||
; Flash: pio run -e test-telnet --target upload
|
||||
; Monitor: pio device monitor (USB Serial, 115200)
|
||||
; Telnet: telnet <IP> 23 (oder PuTTY Telnet Port 23)
|
||||
; =============================================================================
|
||||
[env:test-telnet]
|
||||
platform = espressif32
|
||||
board = az-delivery-devkit-v4
|
||||
framework = arduino
|
||||
upload_port = 172.30.30.90
|
||||
upload_protocol = espota
|
||||
monitor_speed = 115200
|
||||
board_build.partitions = min_spiffs.csv
|
||||
build_src_filter = -<*> +<../test_sketches/test_telnet.cpp>
|
||||
|
||||
; =============================================================================
|
||||
; TEST ENVIRONMENT 1.5 – Push Button / Potentialfreier Schalter Verdrahtungstest
|
||||
; Flash: pio run -e test-button --target upload
|
||||
|
|
@ -134,7 +156,7 @@ monitor_echo = yes
|
|||
board_build.partitions = min_spiffs.csv
|
||||
build_src_filter = -<*> +<../test_sketches/test_wifi.cpp> +<../src/wifi_connector.cpp>
|
||||
build_flags =
|
||||
-DCORE_DEBUG_LEVEL=1
|
||||
-DCORE_DEBUG_LEVEL=3
|
||||
lib_deps =
|
||||
tzapu/WiFiManager @ ^2.0.17
|
||||
majicDesigns/MD_MAX72XX @ ^3.5.1
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
// =============================================================================
|
||||
|
||||
#include "laser_tracker.h"
|
||||
#include "config.h"
|
||||
|
||||
// Globale Instanz
|
||||
LaserTracker laserTracker;
|
||||
|
|
@ -33,11 +34,9 @@ void LaserTracker::begin() {
|
|||
_totalMinutesBase = settings.get().totalMinutes;
|
||||
|
||||
resetSessionSum();
|
||||
Serial.printf("[LaserTracker] begin - Pin=%d totalMin=%.2f gratis=%ds pol=%s\n",
|
||||
LASER_SIGNAL_PIN,
|
||||
_totalMinutesBase,
|
||||
gratisSeconds(),
|
||||
(settings.get().signalPolarity == SIGNAL_POL_HIGH_ACTIVE) ? "HIGH" : "LOW");
|
||||
LOG_I("LASER", "begin - Pin=%d totalMin=%.2f gratis=%ds pol=%s",
|
||||
LASER_SIGNAL_PIN, _totalMinutesBase, gratisSeconds(),
|
||||
(settings.get().signalPolarity == SIGNAL_POL_HIGH_ACTIVE) ? "HIGH" : "LOW");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -70,7 +69,7 @@ void LaserTracker::loop() {
|
|||
if (elapsed >= (uint32_t)gratisSeconds()) {
|
||||
_state = SessionState::NET_COUNTING;
|
||||
_netStartMs = millis();
|
||||
Serial.println("[LaserTracker] GRATIS abgelaufen -> NET_COUNTING");
|
||||
LOG_I("LASER", "GRATIS abgelaufen -> NET_COUNTING");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -82,7 +81,7 @@ void LaserTracker::onSessionStart() {
|
|||
_sessionStartMs = millis();
|
||||
_sessionStartTime = time(nullptr);
|
||||
_netStartMs = 0;
|
||||
Serial.println("[LaserTracker] SessionStart -> GRATIS");
|
||||
LOG_I("LASER", "SessionStart -> GRATIS");
|
||||
}
|
||||
|
||||
time_t LaserTracker::getSessionStartTime() const {
|
||||
|
|
@ -105,9 +104,8 @@ void LaserTracker::onSessionEnd() {
|
|||
// NVS via SettingsManager speichern
|
||||
settings.saveTotalMinutes(_totalMinutesBase);
|
||||
|
||||
Serial.printf("[LaserTracker] SessionEnd: gesamt=%ds netto=%ds "
|
||||
"sessionNetSec=%d total=%.2fmin\n",
|
||||
totalSessionSec, netSec, _sessionNetSec, _totalMinutesBase);
|
||||
LOG_I("LASER", "SessionEnd: gesamt=%ds netto=%ds sessionNetSec=%d total=%.2fmin",
|
||||
totalSessionSec, netSec, _sessionNetSec, _totalMinutesBase);
|
||||
} else if (_state == SessionState::GRATIS) {
|
||||
// Laser ging aus bevor Gratiszeit ablief -> nur Gratiszeit in NVS, keine Session
|
||||
_lastSessionSec = 0;
|
||||
|
|
@ -115,8 +113,8 @@ void LaserTracker::onSessionEnd() {
|
|||
|
||||
settings.saveTotalMinutes(_totalMinutesBase);
|
||||
|
||||
Serial.printf("[LaserTracker] SessionEnd: nur Gratiszeit gesamt=%ds "
|
||||
"total=%.2fmin\n", totalSessionSec, _totalMinutesBase);
|
||||
LOG_I("LASER", "SessionEnd: nur Gratiszeit gesamt=%ds total=%.2fmin",
|
||||
totalSessionSec, _totalMinutesBase);
|
||||
}
|
||||
_state = SessionState::INACTIVE;
|
||||
_sessionEndPending = true; // Fuer consumeSessionEnd() in main
|
||||
|
|
@ -182,9 +180,9 @@ void LaserTracker::resetSessionSum() {
|
|||
_sessionStartMs = millis();
|
||||
_sessionStartTime = time(nullptr);
|
||||
_netStartMs = 0;
|
||||
Serial.println("[LaserTracker] SessionSum zurueckgesetzt, Laser aktiv -> GRATIS neu");
|
||||
LOG_I("LASER", "SessionSum zurueckgesetzt, Laser aktiv -> GRATIS neu");
|
||||
} else {
|
||||
Serial.println("[LaserTracker] SessionSum zurueckgesetzt (NVS unveraendert)");
|
||||
LOG_I("LASER", "SessionSum zurueckgesetzt (NVS unveraendert)");
|
||||
}
|
||||
|
||||
_sessionResetPending = true; // Signal fuer main.cpp -> publishSession()
|
||||
|
|
@ -195,7 +193,7 @@ void LaserTracker::resetTotal() {
|
|||
_totalMinutesBase = 0.0f;
|
||||
settings.saveTotalMinutes(0.0f);
|
||||
resetSessionSum();
|
||||
Serial.println("[LaserTracker] Gesamtzeit und Session auf 0 gesetzt");
|
||||
LOG_I("LASER", "Gesamtzeit und Session auf 0 gesetzt");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -204,10 +202,9 @@ void LaserTracker::printToSerial() const {
|
|||
if (_state == SessionState::GRATIS) stateStr = "GRATIS";
|
||||
if (_state == SessionState::NET_COUNTING) stateStr = "NET_COUNTING";
|
||||
|
||||
Serial.printf("[LaserTracker] state=%-12s active=%d sessionMin=%d "
|
||||
"countdown=%ds totalMin=%.2f\n",
|
||||
stateStr, _debouncedActive, getAllSessionsSumMinutes(),
|
||||
getCountdownRemaining(), getTotalMinutes());
|
||||
LOG_I("LASER", "state=%-12s active=%d sessionMin=%d countdown=%ds totalMin=%.2f",
|
||||
stateStr, _debouncedActive, getAllSessionsSumMinutes(),
|
||||
getCountdownRemaining(), getTotalMinutes());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
17
src/main.cpp
17
src/main.cpp
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include <Arduino.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include <esp_system.h> // esp_reset_reason()
|
||||
#include "config.h"
|
||||
#include "settings.h"
|
||||
#include "wifi_connector.h"
|
||||
|
|
@ -30,8 +31,24 @@ static bool webStarted = false;
|
|||
void setup() {
|
||||
Serial.begin(SERIAL_BAUD_RATE);
|
||||
|
||||
// Reset-Grund auslesen – erscheint als erstes nach jedem Neustart im Log
|
||||
const char* resetReason = "UNBEKANNT";
|
||||
switch (esp_reset_reason()) {
|
||||
case ESP_RST_POWERON: resetReason = "POWER_ON"; break;
|
||||
case ESP_RST_SW: resetReason = "SOFTWARE (esp_restart)"; break;
|
||||
case ESP_RST_PANIC: resetReason = "PANIC/EXCEPTION"; break;
|
||||
case ESP_RST_INT_WDT: resetReason = "WATCHDOG (intern)"; break;
|
||||
case ESP_RST_TASK_WDT: resetReason = "WATCHDOG (Task)"; break;
|
||||
case ESP_RST_WDT: resetReason = "WATCHDOG"; break;
|
||||
case ESP_RST_DEEPSLEEP: resetReason = "DEEP_SLEEP"; break;
|
||||
case ESP_RST_BROWNOUT: resetReason = "BROWNOUT (Spannung zu niedrig!)"; break;
|
||||
case ESP_RST_SDIO: resetReason = "SDIO"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
// --- 1. Einstellungen laden (NVS) ---
|
||||
settings.begin();
|
||||
LOG_I("MAIN", "=== Neustart === Grund: %s", resetReason);
|
||||
LOG_I("MAIN", "LaserCutter Display gestartet");
|
||||
LOG_I("MAIN", "Laser GPIO: %d, Display CS: %d", LASER_SIGNAL_PIN, DISPLAY_CS_PIN);
|
||||
settings.printToSerial();
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include "mqtt_client.h"
|
||||
#include "laser_tracker.h"
|
||||
#include "display_manager.h"
|
||||
#include "config.h"
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
// Globale Instanz
|
||||
|
|
@ -42,7 +43,7 @@ void MqttClient::begin() {
|
|||
if (port == 8883) {
|
||||
_secureClient.setInsecure();
|
||||
_client.setClient(_secureClient);
|
||||
Serial.println("[MQTT] TLS aktiv (setInsecure - kein Cert-Verify)");
|
||||
LOG_I("MQTT", "TLS aktiv (setInsecure - kein Cert-Verify)");
|
||||
} else {
|
||||
_client.setClient(_wifiClient);
|
||||
}
|
||||
|
|
@ -53,8 +54,8 @@ void MqttClient::begin() {
|
|||
snprintf(_clientId, sizeof(_clientId), "%s-%02X%02X%02X",
|
||||
MQTT_CLIENT_ID, mac[3], mac[4], mac[5]);
|
||||
|
||||
Serial.printf("[MQTT] Broker: %s:%u\n", broker, port);
|
||||
Serial.printf("[MQTT] Client-ID: %s\n", _clientId);
|
||||
LOG_I("MQTT", "Broker: %s:%u", broker, port);
|
||||
LOG_I("MQTT", "Client-ID: %s", _clientId);
|
||||
|
||||
reconnect();
|
||||
}
|
||||
|
|
@ -93,7 +94,7 @@ void MqttClient::loop() {
|
|||
// --------------------------------------------------------------------------
|
||||
void MqttClient::publishSession(int lastSessionSec, int gratisSec) {
|
||||
if (!_client.connected()) {
|
||||
Serial.println("[MQTT] publishSession: nicht verbunden, uebersprungen");
|
||||
LOG_E("MQTT", "publishSession: nicht verbunden, uebersprungen");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +120,7 @@ void MqttClient::publishSession(int lastSessionSec, int gratisSec) {
|
|||
serializeJson(doc, buf, sizeof(buf));
|
||||
|
||||
bool ok = _client.publish(MQTT_TOPIC_SESSION, buf, /*retained=*/false);
|
||||
Serial.printf("[MQTT] publishSession: %s -> %s\n", buf, ok ? "OK" : "FEHLER");
|
||||
LOG_I("MQTT", "publishSession: %s -> %s", buf, ok ? "OK" : "FEHLER");
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
|
@ -138,7 +139,7 @@ void MqttClient::publishHeartbeat() {
|
|||
serializeJson(doc, buf, sizeof(buf));
|
||||
|
||||
bool ok = _client.publish(MQTT_TOPIC_STATUS, buf, /*retained=*/true);
|
||||
Serial.printf("[MQTT] Heartbeat: %s -> %s\n", buf, ok ? "OK" : "FEHLER");
|
||||
LOG_I("MQTT", "Heartbeat: %s -> %s", buf, ok ? "OK" : "FEHLER");
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
|
@ -154,7 +155,7 @@ bool MqttClient::reconnect() {
|
|||
// Offline-LWT (Last Will and Testament)
|
||||
const char* lwtPayload = "{\"online\":false}";
|
||||
|
||||
Serial.printf("[MQTT] Verbinde als '%s'...\n", _clientId);
|
||||
LOG_I("MQTT", "Verbinde als '%s'...", _clientId);
|
||||
|
||||
bool ok;
|
||||
if (user && pass) {
|
||||
|
|
@ -167,14 +168,14 @@ bool MqttClient::reconnect() {
|
|||
}
|
||||
|
||||
if (ok) {
|
||||
Serial.println("[MQTT] Verbunden!");
|
||||
LOG_I("MQTT", "Verbunden!");
|
||||
_client.subscribe(MQTT_TOPIC_RESET);
|
||||
Serial.printf("[MQTT] Abonniert: %s\n", MQTT_TOPIC_RESET);
|
||||
LOG_I("MQTT", "Abonniert: %s", MQTT_TOPIC_RESET);
|
||||
// Sofortigen Heartbeat senden
|
||||
publishHeartbeat();
|
||||
_lastHeartbeatMs = millis();
|
||||
} else {
|
||||
Serial.printf("[MQTT] Verbindung fehlgeschlagen, rc=%d\n", _client.state());
|
||||
LOG_E("MQTT", "Verbindung fehlgeschlagen, rc=%d", _client.state());
|
||||
}
|
||||
|
||||
display.showMqttError(!ok);
|
||||
|
|
@ -190,7 +191,7 @@ void MqttClient::onMessage(const char* topic, byte* payload, unsigned int length
|
|||
size_t copyLen = length < sizeof(msg) - 1 ? length : sizeof(msg) - 1;
|
||||
memcpy(msg, payload, copyLen);
|
||||
|
||||
Serial.printf("[MQTT] Nachricht: topic=%s payload=%s\n", topic, msg);
|
||||
LOG_I("MQTT", "Nachricht: topic=%s payload=%s", topic, msg);
|
||||
|
||||
// Reset-Kommando
|
||||
if (strcmp(topic, MQTT_TOPIC_RESET) == 0) {
|
||||
|
|
@ -210,12 +211,12 @@ void MqttClient::onMessage(const char* topic, byte* payload, unsigned int length
|
|||
doReset = v.as<bool>() || v.as<int>() == 1;
|
||||
}
|
||||
} else {
|
||||
Serial.printf("[MQTT] JSON-Parse-Fehler: %s\n", err.c_str());
|
||||
LOG_E("MQTT", "JSON-Parse-Fehler: %s", err.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (doReset) {
|
||||
Serial.println("[MQTT] RESET-Kommando empfangen -> laserTracker.resetTotal()");
|
||||
LOG_I("MQTT", "RESET-Kommando empfangen -> laserTracker.resetTotal()");
|
||||
laserTracker.resetTotal();
|
||||
}
|
||||
|
||||
|
|
@ -232,7 +233,7 @@ void MqttClient::onMessage(const char* topic, byte* payload, unsigned int length
|
|||
}
|
||||
}
|
||||
if (doResetSession) {
|
||||
Serial.println("[MQTT] RESET_SESSION-Kommando empfangen -> laserTracker.resetSessionSum()");
|
||||
LOG_I("MQTT", "RESET_SESSION-Kommando empfangen -> laserTracker.resetSessionSum()");
|
||||
laserTracker.resetSessionSum();
|
||||
}
|
||||
}
|
||||
|
|
@ -250,11 +251,11 @@ bool MqttClient::isConnected() {
|
|||
// --------------------------------------------------------------------------
|
||||
void MqttClient::printToSerial() {
|
||||
const auto& cfg = settings.get();
|
||||
Serial.println("=== MqttClient ===");
|
||||
Serial.printf(" Broker : %s\n", cfg.mqttBroker[0] ? cfg.mqttBroker : DEFAULT_MQTT_BROKER);
|
||||
Serial.printf(" Port : %u\n", cfg.mqttPort > 0 ? cfg.mqttPort : DEFAULT_MQTT_PORT);
|
||||
Serial.printf(" ClientID : %s\n", _clientId[0] ? _clientId : MQTT_CLIENT_ID);
|
||||
Serial.printf(" Verbunden: %s\n", _client.connected() ? "JA" : "NEIN");
|
||||
Serial.printf(" rc : %d\n", _client.state());
|
||||
Serial.println("==================");
|
||||
LOG_I("MQTT", "=== MqttClient ===");
|
||||
LOG_I("MQTT", " Broker : %s", cfg.mqttBroker[0] ? cfg.mqttBroker : DEFAULT_MQTT_BROKER);
|
||||
LOG_I("MQTT", " Port : %u", cfg.mqttPort > 0 ? cfg.mqttPort : DEFAULT_MQTT_PORT);
|
||||
LOG_I("MQTT", " ClientID : %s", _clientId[0] ? _clientId : MQTT_CLIENT_ID);
|
||||
LOG_I("MQTT", " Verbunden: %s", _client.connected() ? "JA" : "NEIN");
|
||||
LOG_I("MQTT", " rc : %d", _client.state());
|
||||
LOG_I("MQTT", "==================");
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
// =============================================================================
|
||||
|
||||
#include "web_server.h"
|
||||
#include <ESPAsyncWebServer.h> // voller Include nur in .cpp (PIMPL)
|
||||
#include <ESPAsyncWebServer.h> // voller Include nur in .cpp (PIMPL) – enthält AsyncWebSocket
|
||||
#include <ArduinoOTA.h> // ESP32 built-in, kein lib_deps-Eintrag noetig
|
||||
#include "config.h"
|
||||
#include "settings.h"
|
||||
|
|
@ -19,6 +19,25 @@ extern void wifiResetCredentialsAndRestart();
|
|||
// Globale Instanz
|
||||
WebServerManager webServer;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Datei-statischer WebSocket-Pointer für webLogForward() (gesetzt in begin())
|
||||
// -----------------------------------------------------------------------------
|
||||
static AsyncWebSocket* _wsPtr = nullptr;
|
||||
|
||||
// Ring-Buffer für Web-Log-Console.
|
||||
// webLogForward() läuft auf Core 1 (Arduino loop) – kein Task, kein Queue,
|
||||
// kein Core-Problem. Browser pollt GET /log-data alle 2 s.
|
||||
static const size_t LOG_BUF_MAX = 6000;
|
||||
static String _logBuf = "";
|
||||
|
||||
void webLogForward(const char* msg) {
|
||||
_logBuf += msg;
|
||||
_logBuf += '\n';
|
||||
if (_logBuf.length() > LOG_BUF_MAX) {
|
||||
_logBuf = _logBuf.substring(_logBuf.length() - LOG_BUF_MAX / 2);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Hilfsmakro: HTML-Template-Platzhalter ersetzen
|
||||
// -----------------------------------------------------------------------------
|
||||
|
|
@ -30,12 +49,28 @@ static String htmlReplace(String html, const String& key, const String& value) {
|
|||
// -----------------------------------------------------------------------------
|
||||
// Konstruktor
|
||||
// -----------------------------------------------------------------------------
|
||||
WebServerManager::WebServerManager() : _server(new AsyncWebServer(80)) {}
|
||||
WebServerManager::WebServerManager()
|
||||
: _server(new AsyncWebServer(80))
|
||||
, _ws(new AsyncWebSocket("/log-ws"))
|
||||
{}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// begin() – Routen registrieren und Server starten
|
||||
// -----------------------------------------------------------------------------
|
||||
void WebServerManager::begin() {
|
||||
// WebSocket-Handler registrieren
|
||||
_ws->onEvent([](AsyncWebSocket* server, AsyncWebSocketClient* client,
|
||||
AwsEventType type, void* arg, uint8_t* data, size_t len) {
|
||||
(void)server; (void)arg; (void)data; (void)len;
|
||||
if (type == WS_EVT_CONNECT) {
|
||||
LOG_I("WEB", "Log Console Client #%u verbunden", client->id());
|
||||
} else if (type == WS_EVT_DISCONNECT) {
|
||||
LOG_I("WEB", "Log Console Client #%u getrennt", client->id());
|
||||
}
|
||||
});
|
||||
_server->addHandler(_ws);
|
||||
_wsPtr = _ws; // ab jetzt ist webLogForward() aktiv
|
||||
|
||||
registerRoutes();
|
||||
// ElegantOTA-Auth: nur wenn Passwort gesetzt
|
||||
const Settings& s = settings.get();
|
||||
|
|
@ -57,17 +92,16 @@ void WebServerManager::begin() {
|
|||
ArduinoOTA.begin();
|
||||
LOG_I("WEB", "ArduinoOTA aktiv: lasercutter-display.local");
|
||||
|
||||
Serial.println(F("[WebServer] gestartet auf Port 80"));
|
||||
Serial.print(F("[WebServer] URL: http://"));
|
||||
Serial.println(WiFi.localIP());
|
||||
LOG_I("WEB", "gestartet auf http://%s", WiFi.localIP().toString().c_str());
|
||||
LOG_I("WEB", "Auth: %s", s.webPassword[0] ? "aktiv" : "deaktiviert (kein Passwort)");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// loop() – ArduinoOTA verarbeiten (in main loop() aufrufen)
|
||||
// loop() – ArduinoOTA verarbeiten + WebSocket-Clients bereinigen (in main loop() aufrufen)
|
||||
// -----------------------------------------------------------------------------
|
||||
void WebServerManager::loop() {
|
||||
ArduinoOTA.handle();
|
||||
_ws->cleanupClients(); // abgemeldete Clients freigeben (verhindert Memory-Leak)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
|
@ -154,7 +188,7 @@ void WebServerManager::registerRoutes() {
|
|||
}
|
||||
}
|
||||
|
||||
Serial.println(F("[WebServer] Konfiguration gespeichert – Neustart empfohlen"));
|
||||
LOG_I("WEB", "Konfiguration gespeichert – Neustart empfohlen");
|
||||
|
||||
// Weiterleitung mit Hinweis
|
||||
request->send(200, "text/html; charset=utf-8",
|
||||
|
|
@ -172,7 +206,7 @@ void WebServerManager::registerRoutes() {
|
|||
_server->on("/reset", HTTP_POST, [this](AsyncWebServerRequest* request) {
|
||||
if (!requireAuth(request)) return;
|
||||
laserTracker.resetSessionSum();
|
||||
Serial.println(F("[WebServer] Session zurueckgesetzt"));
|
||||
LOG_I("WEB", "Session zurueckgesetzt");
|
||||
request->send(200, "text/html; charset=utf-8",
|
||||
"<!DOCTYPE html><html><head><meta charset='utf-8'>"
|
||||
"<meta http-equiv='refresh' content='2;url=/'></head>"
|
||||
|
|
@ -185,7 +219,7 @@ void WebServerManager::registerRoutes() {
|
|||
// --- POST /wifi-reset – WiFi-Credentials löschen und neu starten --------
|
||||
_server->on("/wifi-reset", HTTP_POST, [this](AsyncWebServerRequest* request) {
|
||||
if (!requireAuth(request)) return;
|
||||
Serial.println(F("[WebServer] WiFi-Credentials geloescht – Neustart"));
|
||||
LOG_I("WEB", "WiFi-Credentials geloescht – Neustart");
|
||||
request->send(200, "text/html; charset=utf-8",
|
||||
"<!DOCTYPE html><html><head><meta charset='utf-8'>"
|
||||
"<meta http-equiv='refresh' content='10;url=/'></head>"
|
||||
|
|
@ -199,6 +233,18 @@ void WebServerManager::registerRoutes() {
|
|||
wifiResetCredentialsAndRestart();
|
||||
});
|
||||
|
||||
// --- GET /log – Web-Log-Console ----------------------------------------
|
||||
_server->on("/log", HTTP_GET, [this](AsyncWebServerRequest* request) {
|
||||
if (!requireAuth(request)) return;
|
||||
request->send(200, "text/html; charset=utf-8", buildLogPage());
|
||||
});
|
||||
|
||||
// --- GET /log-data – aktuellen Log-Puffer als Plain-Text liefern --------
|
||||
_server->on("/log-data", HTTP_GET, [this](AsyncWebServerRequest* request) {
|
||||
if (!requireAuth(request)) return;
|
||||
request->send(200, "text/plain; charset=utf-8", _logBuf);
|
||||
});
|
||||
|
||||
// 404
|
||||
_server->onNotFound([](AsyncWebServerRequest* request) {
|
||||
request->send(404, "text/plain", "Not found");
|
||||
|
|
@ -259,6 +305,7 @@ String WebServerManager::buildStatusPage() {
|
|||
"</form>"
|
||||
"<a href='/config' style='display:block'><button class='btn btn-primary' type='button' style='width:100%'>Konfiguration</button></a>"
|
||||
"<a href='/update' style='display:block'><button class='btn btn-primary' type='button' style='width:100%'>OTA Update</button></a>"
|
||||
"<a href='/log' style='display:block'><button class='btn btn-primary' type='button' style='width:100%'>💿 Log Console</button></a>"
|
||||
"</div>"
|
||||
"<script>setTimeout(()=>location.reload(),10000)</script>"
|
||||
"</body></html>"
|
||||
|
|
@ -278,6 +325,66 @@ String WebServerManager::buildStatusPage() {
|
|||
return html;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// buildLogPage()
|
||||
// -----------------------------------------------------------------------------
|
||||
String WebServerManager::buildLogPage() {
|
||||
return String(F(
|
||||
"<!DOCTYPE html><html>"
|
||||
"<head><meta charset='utf-8'>"
|
||||
"<meta name='viewport' content='width=device-width,initial-scale=1'>"
|
||||
"<title>Log Console – LaserCutter Display</title>"
|
||||
"<style>"
|
||||
"body{font-family:monospace;background:#1a1a1a;color:#e0e0e0;margin:0;padding:1rem}"
|
||||
"h1{font-size:1.1rem;color:#90ee90;margin:0 0 .4rem}"
|
||||
"#ctrl{margin:.4rem 0}"
|
||||
"button{background:#333;color:#e0e0e0;border:1px solid #555;padding:.3rem .8rem;"
|
||||
"cursor:pointer;border-radius:3px;margin-right:.4rem}"
|
||||
"button:hover{background:#555}"
|
||||
"#status{display:inline-block;font-size:.8rem;color:#888;margin-left:.5rem}"
|
||||
"#log{background:#000;border:1px solid #333;padding:.6rem;height:78vh;"
|
||||
"overflow-y:auto;white-space:pre-wrap;word-break:break-all;font-size:.82rem}"
|
||||
".i{color:#90ee90}.e{color:#ff6b6b}.d{color:#87ceeb}"
|
||||
"</style></head>"
|
||||
"<body>"
|
||||
"<h1>💿 Log Console</h1>"
|
||||
"<div id='ctrl'>"
|
||||
"<button onclick='lastLen=0;logDiv.innerHTML=\"\"'>Leeren</button>"
|
||||
"<button id='asBtn' onclick='toggleAS()'>Auto-Scroll: AN</button>"
|
||||
"<a href='/' style='margin-left:.5rem'><button>← Status</button></a>"
|
||||
"<span id='status'>Lade…</span>"
|
||||
"</div>"
|
||||
"<div id='log'></div>"
|
||||
"<script>"
|
||||
"var logDiv=document.getElementById('log'),"
|
||||
"st=document.getElementById('status'),"
|
||||
"asBt=document.getElementById('asBtn'),"
|
||||
"autoS=true,lastLen=0;"
|
||||
"function toggleAS(){autoS=!autoS;asBt.textContent='Auto-Scroll:'+(autoS?' AN':' AUS');}"
|
||||
"function renderLog(text){"
|
||||
"if(text.length===lastLen)return;"
|
||||
"lastLen=text.length;"
|
||||
"logDiv.innerHTML='';"
|
||||
"text.split('\\n').forEach(function(line){"
|
||||
"if(!line)return;"
|
||||
"var s=document.createElement('span');"
|
||||
"s.className=line.indexOf('[E]')!==-1?'e':line.indexOf('[D]')!==-1?'d':'i';"
|
||||
"s.textContent=line+'\\n';"
|
||||
"logDiv.appendChild(s);"
|
||||
"});"
|
||||
"if(autoS)logDiv.scrollTop=logDiv.scrollHeight;"
|
||||
"}"
|
||||
"function poll(){"
|
||||
"fetch('/log-data')"
|
||||
".then(function(r){return r.text();})"
|
||||
".then(function(t){st.textContent='OK';st.style.color='#90ee90';renderLog(t);})"
|
||||
".catch(function(){st.textContent='Fehler';st.style.color='#ff6b6b';});"
|
||||
"}"
|
||||
"poll();setInterval(poll,2000);"
|
||||
"</script></body></html>"
|
||||
));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// buildConfigPage()
|
||||
// -----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -11,3 +11,7 @@
|
|||
#define TEST_MQTT_PORT 1883
|
||||
#define TEST_MQTT_USER ""
|
||||
#define TEST_MQTT_PASSWORD ""
|
||||
|
||||
// WLAN-Zugangsdaten (für test-telnet und andere WiFi-Tests ohne WiFiManager)
|
||||
#define TEST_WIFI_SSID "DEIN_WLAN_SSID"
|
||||
#define TEST_WIFI_PASSWORD "DEIN_WLAN_PASSWORT"
|
||||
|
|
|
|||
118
test_sketches/test_telnet.cpp
Normal file
118
test_sketches/test_telnet.cpp
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/**
|
||||
* TEST SKETCH – Telnet Server (Minimal, keine externen Libraries)
|
||||
*
|
||||
* Testet ob WiFiServer / WiFiClient auf dem ESP32 korrekt funktioniert.
|
||||
* Keine Abhängigkeiten außer dem ESP32 Arduino Framework.
|
||||
*
|
||||
* Ablauf:
|
||||
* 1. Verbindet sich mit WLAN (Credentials in mqtt_test_secrets.h)
|
||||
* 2. Öffnet Telnet-Server auf Port 23
|
||||
* 3. Sendet jede Sekunde einen Zähler über Serial UND Telnet
|
||||
* 4. Eingehende Telnet-Bytes werden auf Serial gespiegelt
|
||||
*
|
||||
* ENTHÄLT ArduinoOTA → nach dem Test kann per OTA zurückgeflasht werden!
|
||||
*
|
||||
* Flash: pio run -e test-telnet --target upload
|
||||
* Monitor: pio device monitor (USB Serial, 115200)
|
||||
* Telnet: telnet <IP> 23 oder PuTTY (Telnet, Port 23)
|
||||
* Zurück: pio run -e az-delivery-devkit-v4-ota --target upload
|
||||
*
|
||||
* Erwartetes Verhalten:
|
||||
* Serial + Telnet: "[TICK] uptime: 5 s, heap: 234560" jede Sekunde
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <ArduinoOTA.h>
|
||||
#include "mqtt_test_secrets.h" // TEST_WIFI_SSID, TEST_WIFI_PASSWORD
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// WiFi-Zugangsdaten aus mqtt_test_secrets.h
|
||||
// Fehlermeldung falls nicht definiert
|
||||
// -----------------------------------------------------------------------
|
||||
#ifndef TEST_WIFI_SSID
|
||||
#error "TEST_WIFI_SSID nicht definiert – bitte mqtt_test_secrets.h anlegen"
|
||||
#endif
|
||||
|
||||
static WiFiServer telnetServer(23);
|
||||
static WiFiClient telnetClient;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(500);
|
||||
Serial.println("\n[SETUP] Telnet-Test gestartet");
|
||||
|
||||
// WLAN verbinden
|
||||
Serial.printf("[SETUP] Verbinde mit SSID: %s ...\n", TEST_WIFI_SSID);
|
||||
WiFi.begin(TEST_WIFI_SSID, TEST_WIFI_PASSWORD);
|
||||
uint32_t t = millis();
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
if (millis() - t > 15000) {
|
||||
Serial.println("[SETUP] FEHLER: WLAN-Verbindung nach 15s fehlgeschlagen!");
|
||||
Serial.println("[SETUP] Gerät startet neu ...");
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
}
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.printf("\n[SETUP] WiFi verbunden! IP: %s\n", WiFi.localIP().toString().c_str());
|
||||
|
||||
// ArduinoOTA starten (damit nach dem Test per OTA zurückgeflasht werden kann)
|
||||
ArduinoOTA.setHostname("lasercutter-display");
|
||||
ArduinoOTA.begin();
|
||||
Serial.println("[SETUP] ArduinoOTA aktiv");
|
||||
|
||||
// Telnet-Server starten
|
||||
telnetServer.begin();
|
||||
telnetServer.setNoDelay(true);
|
||||
Serial.printf("[SETUP] Telnet-Server läuft auf Port 23\n");
|
||||
Serial.printf("[SETUP] Verbinde mit: telnet %s 23\n", WiFi.localIP().toString().c_str());
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
void loop() {
|
||||
ArduinoOTA.handle();
|
||||
|
||||
// --- Neuen Client annehmen ---
|
||||
if (!telnetClient || !telnetClient.connected()) {
|
||||
WiFiClient newClient = telnetServer.available();
|
||||
if (newClient) {
|
||||
telnetClient = newClient;
|
||||
telnetClient.setNoDelay(true);
|
||||
Serial.println("[TELNET] Client verbunden!");
|
||||
telnetClient.println("[TELNET] Verbindung OK. Sekunden-Ticks folgen ...");
|
||||
telnetClient.flush();
|
||||
}
|
||||
} else {
|
||||
// Weiteren wartenden Client ablehnen
|
||||
WiFiClient extra = telnetServer.available();
|
||||
if (extra) {
|
||||
extra.stop();
|
||||
}
|
||||
// Eingehende Bytes auf Serial spiegeln
|
||||
while (telnetClient.available()) {
|
||||
char c = telnetClient.read();
|
||||
Serial.print(c);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Jede Sekunde Tick senden ---
|
||||
static uint32_t lastTick = 0;
|
||||
static uint32_t counter = 0;
|
||||
if (millis() - lastTick >= 1000) {
|
||||
lastTick = millis();
|
||||
counter++;
|
||||
|
||||
char msg[80];
|
||||
snprintf(msg, sizeof(msg), "[TICK] #%lu uptime: %lu s heap: %u\n",
|
||||
counter, millis() / 1000, ESP.getFreeHeap());
|
||||
|
||||
Serial.print(msg);
|
||||
if (telnetClient && telnetClient.connected()) {
|
||||
telnetClient.print(msg);
|
||||
telnetClient.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user