feat(P4): Cold Extraction am Touch-Display + RX-Zeilenlimit-Fix (1.1.0 / S3 5.3.2)
Display-Seite der kalten Extraktion: Schalter unter "Modi" auf der Temperatur-Seite mit Erklärzeile, eigener Slot in der Statuszeile direkt hinter der Sicherheitsmeldung, "Cold Extraction - Heizen aus" in der Wasser-Kachel statt eines unerreichbaren Sollwerts, unterdrückter Aufheiz-Countdown, "Vorbenetzung" als erste Phase des kalten Bezugs, Parameter-Anzeige auf der Brew-Seite und "(kalt)"-Markierung in der Statistik (kalte Bezüge zählen nicht in die mittlere Dauer). Bug-Fix P4: PROTO_RX_LINE_MAX lag bei 2048 Bytes, während die State-Zeile der S3 bereits rund 2,1 KB erreicht. Mit langem Status-, Profil- oder SSID-Text lag sie darüber — dann wurde die ganze Zeile verworfen und das Display fror auf dem letzten Stand ein, ohne dass die Verbindung als tot erkannt wurde. Limit jetzt 4096 Bytes; der Puffer bleibt bei 2 KB reserviert, weil der interne RAM knapp ist und eine Arduino-String nicht ins PSRAM alloziert werden kann. S3 5.3.2: Sperrgrund und transiente Meldung gehen als Codes (cxBlock/cxNotice) statt als Klartext ans Display, dazu cxPreInf für die Phasenleiste. Der bis zu 110 Byte lange Freitext hätte die State-Zeile über das Zeilenlimit gedrückt; die Texte rendert jetzt das Display, analog zur Trennung statusKey/statusText. Auf dem OLED und in den ack-Antworten bleibt der Klartext unverändert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
57b7969f8b
commit
e8edc8eb30
+176
-10
@@ -65,7 +65,9 @@ static lv_obj_t* g_errIcon = nullptr; // Fehlersymbol im Header (rot, auf
|
||||
// Konsolidierte Statuszeile: EINE feste einzeilige Meldung statt gestapelter Einzelbanner.
|
||||
// Jeder Hinweis belegt einen Slot (Reihenfolge = Prioritaet, Rot vor Gelb vor Blau);
|
||||
// mehrere aktive Meldungen rotieren alle 4 s weich durch (siehe status_line_tick).
|
||||
enum StatusSlot { ST_SAFETY = 0, ST_DAMPF, ST_CLEAN, ST_TUNE, ST_MAINT, ST_HEAT, ST_COUNT };
|
||||
// ST_COLDEX steht direkt hinter der Sicherheitsmeldung: Solange der Modus laeuft, heizt der
|
||||
// Wasserkreis nicht - das muss der Benutzer sehen, sonst wartet er auf Temperatur, die nicht kommt.
|
||||
enum StatusSlot { ST_SAFETY = 0, ST_COLDEX, ST_DAMPF, ST_CLEAN, ST_TUNE, ST_MAINT, ST_HEAT, ST_COUNT };
|
||||
struct StatusMsg {
|
||||
bool active = false;
|
||||
char text[120] = "";
|
||||
@@ -205,6 +207,7 @@ static lv_obj_t* g_piRow = nullptr; // Containe
|
||||
static lv_obj_t* g_piChip[3] = { nullptr, nullptr, nullptr }; // Chip je Schritt
|
||||
static lv_obj_t* g_piChipLbl[3] = { nullptr, nullptr, nullptr };
|
||||
static bool g_piShotMode = false; // laufender Bezug nutzt Pre-Infusion
|
||||
static bool g_piColdMode = false; // laufender Bezug ist eine kalte Extraktion (andere Phasennamen)
|
||||
static uint8_t g_piStateS3 = 0; // Phasenstatus der S3 (0=inaktiv,1=PI,2=Pause,3=Extraktion)
|
||||
static uint32_t g_piDurMs = 0, g_piPauseMs = 0; // beim Bezug gelatchte Phasendauern
|
||||
static int g_piLastPhase = -1; // zuletzt gestylte Phase (Styles nur bei Wechsel anfassen)
|
||||
@@ -219,6 +222,8 @@ static float g_sbtTarget = 0.0f; // Dampf-Zielzeit (s)
|
||||
|
||||
// Temperatur
|
||||
static lv_obj_t* lblTargW; static lv_obj_t* lblTargD; static lv_obj_t* swEco; static lv_obj_t* swMaint;
|
||||
static lv_obj_t* swColdEx = nullptr; // Cold Extraction (nur bei S3-FW >= 5.3.0 sichtbar)
|
||||
static lv_obj_t* lblColdExHint = nullptr; // Erklaerung/Sperrgrund unter dem Schalter
|
||||
static float g_targW = 0, g_targD = 0; static unsigned long g_targEditMs = 0;
|
||||
static lv_obj_t* lblHeatUp = nullptr; static int g_heatUp = 0; static unsigned long g_heatUpEditMs = 0;
|
||||
static bool g_heatUpDirty = false; // ungesendete Aufheizzeit-Aenderung (entprellt senden)
|
||||
@@ -245,6 +250,7 @@ static lv_obj_t* swPI; static lv_obj_t* taPiDur; static lv_obj_t* taPiPause;
|
||||
static lv_obj_t* swBBT; static lv_obj_t* taBbtSecs;
|
||||
static lv_obj_t* swBBW; static lv_obj_t* taBbwTarget; static lv_obj_t* taBbwOffset;
|
||||
static lv_obj_t* swSBT; static lv_obj_t* taSbtSecs;
|
||||
static lv_obj_t* lblBrewColdEx = nullptr; // Cold-Extraction-Parameter (nur Anzeige)
|
||||
|
||||
// WiFi
|
||||
static lv_obj_t* wifiStatusLbl; static lv_obj_t* wifiHintLbl; static lv_obj_t* wifiList;
|
||||
@@ -1195,6 +1201,32 @@ static lv_obj_t* build_temp_arc(lv_obj_t* parent, const char* title, lv_color_t
|
||||
|
||||
static void eco_cb(lv_event_t* e) { if (g_client) g_client->sendAction(sw_get((lv_obj_t*)lv_event_get_target(e)) ? "startEco" : "stopEco"); }
|
||||
static void maint_cb(lv_event_t* e) { if (g_client) g_client->sendAction("toggleMaintenance", sw_get((lv_obj_t*)lv_event_get_target(e)) ? "true" : "false"); }
|
||||
// Sperrgrund-Code der S3 (cxBlock) in Klartext. Die S3 sendet bewusst nur den Code, damit
|
||||
// die State-Zeile kurz bleibt - die Texte gehoeren ins Display (analog statusKey).
|
||||
static String coldex_block_text(uint8_t code, float maxTemp, float tempW) {
|
||||
switch (code) {
|
||||
case 1: return "nicht freigeschaltet";
|
||||
case 2: return "Standby aktiv";
|
||||
case 3: return "Wartungsmodus aktiv";
|
||||
case 4: return "Reinigungsassistent aktiv";
|
||||
case 5: return "PID-Tuning läuft";
|
||||
case 6: return "Wasser-Sensor liefert keinen gültigen Wert";
|
||||
case 7: {
|
||||
char b[72];
|
||||
snprintf(b, sizeof(b), "Wasserkessel zu warm (%.1f °C, benötigt < %.1f °C)",
|
||||
(double)tempW, (double)maxTemp);
|
||||
return String(b);
|
||||
}
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Cold Extraction: gezielt start/stop senden (nicht toggle), damit ein abgewiesener Befehl
|
||||
// den Schalter nicht in einen Zustand bringt, den die S3 nie bestaetigt.
|
||||
static void coldex_cb(lv_event_t* e) {
|
||||
bool want = sw_get((lv_obj_t*)lv_event_get_target(e));
|
||||
if (g_client) g_client->sendAction(want ? "startColdExtraction" : "stopColdExtraction");
|
||||
}
|
||||
|
||||
// Stepper-Zeile: Beschriftung | [-] Wert [+]
|
||||
static lv_obj_t* build_stepper(lv_obj_t* parent, const char* label, lv_event_cb_t minus, lv_event_cb_t plus, int dec) {
|
||||
@@ -1283,6 +1315,16 @@ static void build_temp(lv_obj_t* p) {
|
||||
lv_obj_add_event_cb(swEco, eco_cb, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
swMaint = add_switch(p, "Wartungsmodus");
|
||||
lv_obj_add_event_cb(swMaint, maint_cb, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
swColdEx = add_switch(p, "Cold Extraction");
|
||||
lv_obj_add_event_cb(swColdEx, coldex_cb, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
lblColdExHint = lv_label_create(p);
|
||||
lv_label_set_long_mode(lblColdExHint, LV_LABEL_LONG_WRAP);
|
||||
lv_obj_set_width(lblColdExHint, LV_PCT(100));
|
||||
lv_obj_set_style_text_color(lblColdExHint, COL_TEXT_DIM, 0);
|
||||
lv_label_set_text(lblColdExHint, "");
|
||||
// Beide Zeilen bleiben versteckt, solange die S3 die Funktion nicht freigeschaltet meldet
|
||||
lv_obj_add_flag(lv_obj_get_parent(swColdEx), LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(lblColdExHint, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
// =====================================================================================
|
||||
@@ -1607,7 +1649,10 @@ static void chart_sample_cb(lv_timer_t*) {
|
||||
// Countdown voll bis zum eingestellten Wert durch - auch wenn das Wasser dabei >40 Grad wird.
|
||||
{
|
||||
long rs = g_state.heatUpRemainSec;
|
||||
if (g_state.standbyActive || rs <= 0) g_heatUpLatched = false; // Ende/Standby -> zuruecksetzen
|
||||
// Bei aktiver Cold Extraction heizt der Wasserkreis nicht - ein Aufheiz-Countdown
|
||||
// waere schlicht falsch (er liefe ab, ohne dass die Maschine warm wird).
|
||||
if (g_state.cxActive) g_heatUpLatched = false;
|
||||
else if (g_state.standbyActive || rs <= 0) g_heatUpLatched = false; // Ende/Standby -> zuruecksetzen
|
||||
else if (g_state.tempW < 40.0f) g_heatUpLatched = true; // Kaltstart erkannt -> ganzen Countdown zeigen
|
||||
if (g_heatUpLatched) {
|
||||
char b[56];
|
||||
@@ -1761,8 +1806,11 @@ static void brew_live_tick() {
|
||||
if (phase <= 2) {
|
||||
uint32_t endMs = (phase == 1) ? g_piDurMs : (g_piDurMs + g_piPauseMs);
|
||||
uint32_t remainMs = (g_shotFrozenMs < endMs) ? (endMs - g_shotFrozenMs) : 0;
|
||||
snprintf(b, sizeof(b), "%s - noch %.1f s",
|
||||
(phase == 1) ? "Pre-Infusion" : "Pause", remainMs / 1000.0f);
|
||||
const char* pname = (phase == 1) ? (g_piColdMode ? "Vorbenetzung" : "Pre-Infusion")
|
||||
: "Pause";
|
||||
snprintf(b, sizeof(b), "%s - noch %.1f s", pname, remainMs / 1000.0f);
|
||||
} else if (g_piColdMode && g_state.cxResting) {
|
||||
snprintf(b, sizeof(b), "Extraktion - Pumpenpause");
|
||||
} else {
|
||||
snprintf(b, sizeof(b), "Extraktion");
|
||||
}
|
||||
@@ -1963,6 +2011,15 @@ static void build_brew(lv_obj_t* p) {
|
||||
swSBT = add_switch(p, "Steam-by-Time aktiv");
|
||||
taSbtSecs = add_num(p, "Dampf-Zielzeit (s)");
|
||||
add_save_button(p, "Brühen speichern", send_save_brew);
|
||||
// Cold Extraction: nur Anzeige. Die Parameter liegen bewusst nur in der Web-UI der S3
|
||||
// (maschinenabhaengig, nicht ueber saveBrew erreichbar); geschaltet wird der Modus
|
||||
// auf der Temperatur-Seite unter "Modi".
|
||||
lblBrewColdEx = lv_label_create(p);
|
||||
lv_label_set_long_mode(lblBrewColdEx, LV_LABEL_LONG_WRAP);
|
||||
lv_obj_set_width(lblBrewColdEx, LV_PCT(100));
|
||||
lv_obj_set_style_text_color(lblBrewColdEx, COL_TEXT_DIM, 0);
|
||||
lv_label_set_text(lblBrewColdEx, "");
|
||||
lv_obj_add_flag(lblBrewColdEx, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
// =====================================================================================
|
||||
@@ -2761,7 +2818,9 @@ static void pi_bar_update() {
|
||||
}
|
||||
|
||||
// Texte: erledigte Schritte mit Haken, aktive zeitgesteuerte Phase mit Restzeit
|
||||
static const char* piNames[3] = { "Pre-Infusion", "Pause", "Extraktion" };
|
||||
static const char* piNamesHot[3] = { "Pre-Infusion", "Pause", "Extraktion" };
|
||||
static const char* piNamesCold[3] = { "Vorbenetzung", "Pause", "Extraktion" };
|
||||
const char** piNames = g_piColdMode ? piNamesCold : piNamesHot;
|
||||
char pbuf[40];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (!g_piChipLbl[i]) continue;
|
||||
@@ -3385,6 +3444,32 @@ void ui_update(const MachineState& st) {
|
||||
status_set(ST_SAFETY, false);
|
||||
}
|
||||
|
||||
// Cold-Extraction-Hinweis: erklaert dauerhaft, warum der Wasserkreis nicht heizt.
|
||||
// Transiente Meldung (abgewiesener Start / Abbruch) hat Vorrang vor dem Dauerzustand.
|
||||
if (st.cxNotice == 2) {
|
||||
status_set(ST_COLDEX, true, LV_SYMBOL_WARNING " Cold Extraction beendet: kein Zulauf",
|
||||
COL_WARN, lv_color_hex(0x000000));
|
||||
} else if (st.cxNotice == 3) {
|
||||
status_set(ST_COLDEX, true, "Cold Extraction: erst nach dem Bezug umschaltbar",
|
||||
COL_WARN, lv_color_hex(0x000000));
|
||||
} else if (st.cxNotice == 1) {
|
||||
String t = "Cold Extraction nicht möglich - ";
|
||||
t += coldex_block_text(st.cxBlock, st.cxMaxTemp, st.tempW);
|
||||
status_set(ST_COLDEX, true, t.c_str(), COL_WARN, lv_color_hex(0x000000));
|
||||
} else if (st.cxActive && st.cxResting) {
|
||||
status_set(ST_COLDEX, true, "Cold Extraction: Pumpenpause (Pumpenschutz)",
|
||||
COL_ACCENT, lv_color_hex(0x000000));
|
||||
} else if (st.cxActive && !st.cxAllowed) {
|
||||
String t = "Cold Extraction aktiv, Bezug gesperrt - ";
|
||||
t += coldex_block_text(st.cxBlock, st.cxMaxTemp, st.tempW);
|
||||
status_set(ST_COLDEX, true, t.c_str(), COL_WARN, lv_color_hex(0x000000));
|
||||
} else if (st.cxActive) {
|
||||
status_set(ST_COLDEX, true, "Cold Extraction aktiv - Wasserkreis heizt nicht",
|
||||
lv_color_hex(0x1976d2), COL_TEXT);
|
||||
} else {
|
||||
status_set(ST_COLDEX, false);
|
||||
}
|
||||
|
||||
// Dampf-Hinweis (Heizen aus hat Vorrang vor Startverzoegerung)
|
||||
if (st.steamHeatDisabled) {
|
||||
status_set(ST_DAMPF, true, "Dampf: Heizen deaktiviert", COL_DANGER, COL_TEXT);
|
||||
@@ -3523,6 +3608,14 @@ void ui_update(const MachineState& st) {
|
||||
temp_progress_set(barDutyW, arcTempW, bulbTempW, 0, COL_DANGER);
|
||||
} else {
|
||||
lv_label_set_text(lblTempW, tempStr(st.tempW, 1).c_str());
|
||||
if (st.cxActive) {
|
||||
// Cold Extraction: der Sollwert ist bedeutungslos, es wird nicht geheizt.
|
||||
// Statt eines Zielbands, das nie erreicht wird, den Grund anzeigen.
|
||||
lv_obj_set_style_text_color(lblTempW, lv_color_hex(0x1976d2), 0);
|
||||
label_set_if_changed(lblSetW, "Cold Extraction - Heizen aus");
|
||||
lv_obj_set_style_text_color(lblSetW, lv_color_hex(0x1976d2), 0);
|
||||
temp_progress_set(barDutyW, arcTempW, bulbTempW, 0, lv_color_hex(0x1976d2));
|
||||
} else {
|
||||
// Ist-Wert faerbt sich weich Richtung Soll (Blau -> Gold -> Gruen im Zielband)
|
||||
lv_color_t colW = temp_state_color(st.tempW, st.setW);
|
||||
lv_obj_set_style_text_color(lblTempW, colW, 0);
|
||||
@@ -3531,6 +3624,7 @@ void ui_update(const MachineState& st) {
|
||||
// Fortschritt = Ist/Soll in %, gedeckelt auf 100 (Ist >= Soll -> voll)
|
||||
int pctW = (st.setW > 0.0f) ? (int)(st.tempW / st.setW * 100.0f + 0.5f) : 0;
|
||||
temp_progress_set(barDutyW, arcTempW, bulbTempW, pctW, colW);
|
||||
}
|
||||
}
|
||||
if (st.dampfSafetyShutdown || st.dampfSensorError) {
|
||||
lv_label_set_text(lblTempD, LV_SYMBOL_WARNING);
|
||||
@@ -3596,10 +3690,19 @@ void ui_update(const MachineState& st) {
|
||||
g_shotBbtMode = st.bbtEnabled && st.bbtSecs > 0.0f; // Bezug mit Brew-by-Time?
|
||||
if (g_shotBbtMode) g_bbtTarget = st.bbtSecs;
|
||||
// Pre-Infusion-Phasenleiste: Modus, S3-Phase und Phasendauern latchen (pi_bar_update)
|
||||
g_piShotMode = st.piEnabled;
|
||||
// Ein kalter Bezug hat seine eigene Phasenfolge (Vorbenetzung -> Extraktion, keine
|
||||
// Pause) und ignoriert die Pre-Infusion-Einstellungen komplett.
|
||||
g_piColdMode = st.cxShot;
|
||||
if (g_piColdMode) {
|
||||
g_piShotMode = (st.cxPreInfSec > 0.0f);
|
||||
g_piDurMs = (uint32_t)(st.cxPreInfSec * 1000.0f);
|
||||
g_piPauseMs = 0;
|
||||
} else {
|
||||
g_piShotMode = st.piEnabled;
|
||||
g_piDurMs = (uint32_t)(st.piDurSecs * 1000.0f);
|
||||
g_piPauseMs = (uint32_t)(st.piPauseSecs * 1000.0f);
|
||||
}
|
||||
g_piStateS3 = st.piState;
|
||||
g_piDurMs = (uint32_t)(st.piDurSecs * 1000.0f);
|
||||
g_piPauseMs = (uint32_t)(st.piPauseSecs * 1000.0f);
|
||||
// Live-Bezugsschirm einblenden (nur echte Bezuege; nicht im Reinigungsassistenten,
|
||||
// nicht im Standby und nicht, wenn der Nutzer ihn fuer diesen Bezug weggetippt hat)
|
||||
if (g_brewLiveEnabled && !g_brewLiveClosed && !g_brewLiveShown &&
|
||||
@@ -3623,6 +3726,7 @@ void ui_update(const MachineState& st) {
|
||||
}
|
||||
g_shotActive = false;
|
||||
g_piShotMode = false; // Phasenleiste ausblenden (pi_bar_update)
|
||||
g_piColdMode = false;
|
||||
g_piStateS3 = 0;
|
||||
brew_live_hide(); // Bezug vorbei -> Overlay weich ausblenden
|
||||
g_brewLiveClosed = false; // Wegtipp-Sperre gilt nur fuer den laufenden Bezug
|
||||
@@ -3725,6 +3829,60 @@ void ui_update(const MachineState& st) {
|
||||
if (swEco) sw_set(swEco, st.ecoActive);
|
||||
if (swMaint) sw_set(swMaint, st.maintenanceActive);
|
||||
if (swMaint2) sw_set(swMaint2, st.maintenanceActive);
|
||||
if (swColdEx) {
|
||||
// Zeile nur zeigen, wenn die S3 die Funktion freigeschaltet meldet (und sie kennt)
|
||||
lv_obj_t* rowCx = lv_obj_get_parent(swColdEx);
|
||||
if (st.cxEnabled) {
|
||||
lv_obj_remove_flag(rowCx, LV_OBJ_FLAG_HIDDEN);
|
||||
if (lblColdExHint) lv_obj_remove_flag(lblColdExHint, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
lv_obj_add_flag(rowCx, LV_OBJ_FLAG_HIDDEN);
|
||||
if (lblColdExHint) lv_obj_add_flag(lblColdExHint, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
sw_set(swColdEx, st.cxActive);
|
||||
// Einschalten nur bei Freigabe; Ausschalten immer - ausser waehrend eines kalten Bezugs
|
||||
bool usable = st.cxEnabled && (st.cxActive ? !st.cxShot : st.cxAllowed);
|
||||
if (usable) lv_obj_remove_state(swColdEx, LV_STATE_DISABLED);
|
||||
else lv_obj_add_state(swColdEx, LV_STATE_DISABLED);
|
||||
if (lblColdExHint && st.cxEnabled) {
|
||||
String hint;
|
||||
if (st.cxActive) {
|
||||
hint = "Wasserkreis heizt nicht (bleibt über Standby und Neustart aktiv). ";
|
||||
if (st.cxAllowed) {
|
||||
char b[96];
|
||||
snprintf(b, sizeof(b), "Bezug regulär starten - Ziel %.0f g, max %.0f s.",
|
||||
(double)st.cxTarget, (double)st.cxMaxSecs);
|
||||
hint += b;
|
||||
} else {
|
||||
hint += coldex_block_text(st.cxBlock, st.cxMaxTemp, st.tempW);
|
||||
}
|
||||
} else if (!st.cxAllowed) {
|
||||
hint = coldex_block_text(st.cxBlock, st.cxMaxTemp, st.tempW);
|
||||
} else {
|
||||
char b[112];
|
||||
snprintf(b, sizeof(b), "Kalte Extraktion möglich (Wasser %.1f °C < %.1f °C). "
|
||||
"Solange aktiv, heizt der Wasserkreis nicht.",
|
||||
(double)st.tempW, (double)st.cxMaxTemp);
|
||||
hint = b;
|
||||
}
|
||||
label_set_if_changed(lblColdExHint, hint.c_str());
|
||||
}
|
||||
}
|
||||
if (lblBrewColdEx) {
|
||||
if (st.cxEnabled) {
|
||||
char b[192];
|
||||
snprintf(b, sizeof(b),
|
||||
"Cold Extraction (nur Anzeige, Einstellung in der Web-UI):\n"
|
||||
"Freigabe < %.1f °C | Vorbenetzung %.0f s | Ziel %.0f g | max. %.0f s\n"
|
||||
"Geschaltet wird der Modus auf der Seite \"Temperaturen\" unter \"Modi\".",
|
||||
(double)st.cxMaxTemp, (double)st.cxPreInfSec,
|
||||
(double)st.cxTarget, (double)st.cxMaxSecs);
|
||||
label_set_if_changed(lblBrewColdEx, b);
|
||||
lv_obj_remove_flag(lblBrewColdEx, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
lv_obj_add_flag(lblBrewColdEx, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
// Licht-Button spiegeln (gold = an)
|
||||
if (btnLight) {
|
||||
@@ -3857,14 +4015,19 @@ void ui_set_usage_stats(const String& json) {
|
||||
String l;
|
||||
int n = 0;
|
||||
float durSum = 0.0f;
|
||||
int nDur = 0; // Anzahl der Bezuege, die in den Mittelwert eingehen (ohne kalte)
|
||||
for (JsonVariant v : doc["shots"].as<JsonArray>()) {
|
||||
const char* dt = v["dt"] | "";
|
||||
float d = v["d"] | 0.0f;
|
||||
float w = v["w"] | -1.0f;
|
||||
bool cold = v["cx"] | false; // kalte Extraktion (fehlt bei S3-FW < 5.3.0)
|
||||
if (n > 0) l += "\n";
|
||||
l += String(dt) + " " + String(d, 1) + " s";
|
||||
if (w >= 0.0f) l += " " + String(w, 1) + " g";
|
||||
durSum += d;
|
||||
if (cold) l += " (kalt)";
|
||||
// Minutenlange kalte Bezuege wuerden den Mittelwert unbrauchbar machen -> raus
|
||||
// (gleiche Regel wie in der S3-Statistik)
|
||||
if (!cold) { durSum += d; nDur++; }
|
||||
n++;
|
||||
}
|
||||
|
||||
@@ -3873,7 +4036,10 @@ void ui_set_usage_stats(const String& json) {
|
||||
s += "Diese Woche: " + String(week) + "\n";
|
||||
// total ist exakt, solange die ganze Datei ins Fenster passte (sonst Fenster-Untergrenze)
|
||||
s += "Gesamt: " + (partial ? String("min. ") : String("")) + String(total) + " Bezüge";
|
||||
if (n > 0) s += "\nMittl. Dauer (letzte " + String(n) + "): " + String(durSum / n, 1) + " s";
|
||||
if (nDur > 0) {
|
||||
s += "\nMittl. Dauer (letzte " + String(nDur) + "): " + String(durSum / nDur, 1) + " s";
|
||||
if (nDur < n) s += " (ohne kalte)";
|
||||
}
|
||||
label_set_if_changed(g_statSummary, s.c_str());
|
||||
|
||||
if (g_statList) {
|
||||
|
||||
Reference in New Issue
Block a user