Compare commits

...
Author SHA1 Message Date
raw-designsandClaude Fable 5 f43375e991 fix(P4): Heizleistungs-Kurven flackerten in großen Zeitfenstern — Bucket-Mittelung statt Einzelsample
Die Dezimierung im Verlauf-Chart pickte pro Punkt ein Einzelsample; das
Abtast-Raster hing an g_histPos und wanderte mit jedem Sekundentick um
eine Position. Die schnell schaltende Heizleistung (PWM) sprang dadurch
sichtbar zwischen zwei Kurvenbildern hin und her. Jetzt wird über den
gesamten stride-Bucket gemittelt — Bild steht ruhig, Leistungs-Kurve
zeigt die echte mittlere Heizleistung. Version 1.0.12.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 01:26:26 +02:00
thomas 92ef4fbfb6 Merge pull request 'fix(P4): Verlaufs-Historie ins PSRAM verlagern — interner RAM lief über' (#4) from fix/verlaufs-historie-ins-psram into main 2026-07-12 21:17:13 +02:00
raw-designsandClaude Fable 5 fd6ec5371b fix(P4): Verlaufs-Historie ins PSRAM verlagern — interner RAM lief über
Die 60-min-Ringpuffer der Verlauf-Seite (~29 KB) lagen als statische Arrays
im internen RAM und sprengten das Board-Limit (Linker: 107 % dynamischer
Speicher, Build brach ab). Historie jetzt als ein Block per heap_caps_malloc
im PSRAM (Fallback MALLOC_CAP_8BIT), wie die LVGL-Framebuffer; alle Zugriffe
gegen Allokationsfehler abgesichert (Chart bliebe dann leer).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 21:16:37 +02:00
thomas ea64e7de04 Merge pull request 'fix(P4): Shot-Zusammenfassung — größere Abstände zwischen Dauer/Gewicht/Flow' (#3) from fix/shot-zusammenfassung-abstände into main 2026-07-12 21:12:32 +02:00
3 changed files with 69 additions and 25 deletions
+11
View File
@@ -1,3 +1,12 @@
Version 1.0.12:
- Verlauf-Chart: Flackernde Heizleistungs-Kurven in den großen Zeitfenstern behoben. Die
Dezimierung pickte pro Chart-Punkt ein Einzelsample, dessen Abtast-Raster mit jedem
Sekundentick um eine Position wanderte — die schnell schaltende Heizleistung (PWM)
sprang dadurch sichtbar zwischen zwei Kurvenbildern hin und her. Jetzt wird über den
gesamten Zeitabschnitt eines Chart-Punkts gemittelt: das Bild steht ruhig, und die
Leistungs-Kurve zeigt die tatsächliche mittlere Heizleistung statt zufälliger
Momentwerte. Im 2-min-Fenster (1 Sample je Punkt) ändert sich nichts.
Version 1.0.11:
- Verlauf-Seite ausgebaut (nur P4, keine S3-/Protokolländerung nötig):
- Umschaltbares Zeitfenster: Buttons „2 min / 10 min / 30 min / 60 min" in der Kopfzeile.
@@ -14,6 +23,8 @@ Version 1.0.11:
ebenfalls P4-lokal im NVS.
- Feinere Kurven: Die Temperatur-Historie wird jetzt in Zehntelgrad geführt und gezeichnet
(vorher ganzzahlig gerundet) — PID-Schwingungen um ±1 °C sind damit sauber ablesbar.
- Die ~29 KB Historie liegen im PSRAM (heap_caps_malloc, wie die LVGL-Framebuffer) —
als statische Arrays sprengten sie den internen RAM (Linker: 107 % belegt).
- Shot-Zusammenfassung: Karte auf 560 px verbreitert und Spaltenabstand zwischen
Dauer/Gewicht/Flow deutlich vergrößert (28 px statt 10 px) — die drei Werte standen
zu nahe beisammen und waren schlecht lesbar.
+1 -1
View File
@@ -65,7 +65,7 @@
// -------------------------------------------------------------------------------------
// Firmware
// -------------------------------------------------------------------------------------
#define DISPLAY_FW_VERSION "1.0.11" // Firmware-Stand der P4-Display-Steuerung (Info-Seite)
#define DISPLAY_FW_VERSION "1.0.12" // Firmware-Stand der P4-Display-Steuerung (Info-Seite)
// -------------------------------------------------------------------------------------
// Protokoll
+52 -19
View File
@@ -17,6 +17,7 @@
#include <Arduino.h>
#include <ArduinoJson.h>
#include <Preferences.h> // Dashboard-Design P4-lokal merken (NVS)
#include <esp_heap_caps.h> // Verlaufs-Historie im PSRAM allozieren (interner RAM ist knapp)
#include "ui.h"
#include "theme.h"
#include "config.h"
@@ -124,11 +125,17 @@ static lv_obj_t* g_axHi = nullptr; static lv_obj_t* g_axMid = nullptr; static lv
static const uint16_t CHART_WIN_MIN[4] = { 2, 10, 30, 60 }; // waehlbare Fenster (Minuten)
#define CHART_HIST_MAX 3600 // Historie: 60 min à 1 s
#define CHART_HIST_NONE INT16_MIN // Markierung "keine Probe"
static int16_t g_histW[CHART_HIST_MAX]; // Zehntelgrad
static int16_t g_histD[CHART_HIST_MAX];
static int16_t g_histC[CHART_HIST_MAX];
static uint8_t g_histDutyW[CHART_HIST_MAX]; // Heizleistung (%)
static uint8_t g_histDutyD[CHART_HIST_MAX];
// Die ~29 KB Historie liegen im PSRAM (heap_caps_malloc in ui_init, wie die LVGL-
// Framebuffer) - als statische Arrays wuerden sie den knappen internen RAM sprengen
// (Board-Limit war vor diesem Feature bereits zu ~98 % belegt).
struct ChartHist {
int16_t w[CHART_HIST_MAX]; // Wasser (Zehntelgrad)
int16_t d[CHART_HIST_MAX]; // Dampf (Zehntelgrad)
int16_t c[CHART_HIST_MAX]; // Zusatzsensor (Zehntelgrad; CHART_HIST_NONE = aus)
uint8_t dutyW[CHART_HIST_MAX]; // Heizleistung Wasser (%)
uint8_t dutyD[CHART_HIST_MAX]; // Heizleistung Dampf (%)
};
static ChartHist* g_hist = nullptr;
static uint16_t g_histPos = 0, g_histFill = 0;
static uint8_t g_chartWin = 0; // Index in CHART_WIN_MIN (P4-lokal in NVS, "chartwin")
static bool g_chartDuty = false; // Heizleistung als Zusatzkurven (NVS "chartduty")
@@ -1471,26 +1478,45 @@ static void build_chart(lv_obj_t* p) {
// Y-Achse ueber das sichtbare Fenster (Werte < 30 Grad = kalter/inaktiver Sensor werden
// fuer die Skala ignoriert) und positioniert die gestrichelten Soll-Linien.
static void chart_redraw() {
if (!g_chart) return;
if (!g_chart || !g_hist) return;
const int stride = (int)CHART_WIN_MIN[g_chartWin] * 60 / 120; // Sekunden je Chart-Punkt
int32_t lo = INT32_MAX, hi = INT32_MIN; // Zehntelgrad
bool anyW = false, anyD = false;
for (int i = 0; i < 120; i++) {
int age = (119 - i) * stride; // Alter der Probe (Sekunden)
int ageStart = (119 - i) * stride; // juengstes Alter im Bucket (Sekunden)
int32_t vW = LV_CHART_POINT_NONE, vD = LV_CHART_POINT_NONE, vC = LV_CHART_POINT_NONE;
int32_t dW = LV_CHART_POINT_NONE, dD = LV_CHART_POINT_NONE;
if (age < (int)g_histFill) {
if (ageStart < (int)g_histFill) {
// Ueber den ganzen Bucket mitteln statt ein Einzelsample zu picken: das
// Abtast-Raster haengt an g_histPos und wandert sonst mit jedem Sekundentick,
// wodurch schnell schaltende Werte (Duty-PWM) sichtbar zwischen zwei
// Kurvenbildern hin- und herspringen. Der Mittelwert ist zudem bei der
// Heizleistung die ehrlichere Aussage als ein zufaelliger Momentwert.
int32_t sW = 0, sD = 0, sC = 0, sDW = 0, sDD = 0;
int nT = 0, nC = 0;
for (int s = 0; s < stride; s++) {
int age = ageStart + s;
if (age >= (int)g_histFill) break;
int idx = (int)g_histPos - 1 - age;
if (idx < 0) idx += CHART_HIST_MAX;
vW = g_histW[idx];
vD = g_histD[idx];
if (g_histC[idx] != CHART_HIST_NONE) vC = g_histC[idx];
dW = g_histDutyW[idx];
dD = g_histDutyD[idx];
sW += g_hist->w[idx];
sD += g_hist->d[idx];
sDW += g_hist->dutyW[idx];
sDD += g_hist->dutyD[idx];
nT++;
if (g_hist->c[idx] != CHART_HIST_NONE) { sC += g_hist->c[idx]; nC++; }
}
if (nT > 0) {
vW = (sW + nT / 2) / nT;
vD = (sD + nT / 2) / nT;
dW = (sDW + nT / 2) / nT;
dD = (sDD + nT / 2) / nT;
if (nC > 0) vC = (sC + nC / 2) / nC;
if (vW > 300) { anyW = true; if (vW < lo) lo = vW; if (vW > hi) hi = vW; }
if (vD > 300) { anyD = true; if (vD < lo) lo = vD; if (vD > hi) hi = vD; }
if (vC != LV_CHART_POINT_NONE && vC > 300) { if (vC < lo) lo = vC; if (vC > hi) hi = vC; }
}
}
lv_chart_set_value_by_id(g_chart, g_serW, i, vW);
lv_chart_set_value_by_id(g_chart, g_serD, i, vD);
lv_chart_set_value_by_id(g_chart, g_serC, i, vC);
@@ -1540,15 +1566,15 @@ static void chart_redraw() {
lv_chart_refresh(g_chart);
}
static void chart_sample_cb(lv_timer_t*) {
if (!g_chart) return;
if (!g_chart || !g_hist) return;
// 1-s-Probe in die 60-min-Historie (Ringpuffer); Anzeige zeichnet chart_redraw
bool caseOn = g_state.caseSensorEnabled && g_state.hasCaseTemp;
g_histW[g_histPos] = (int16_t)(g_state.tempW * 10.0f + 0.5f);
g_histD[g_histPos] = (int16_t)(g_state.tempD * 10.0f + 0.5f);
g_histC[g_histPos] = caseOn ? (int16_t)(g_state.caseTemp * 10.0f + 0.5f) : CHART_HIST_NONE;
g_hist->w[g_histPos] = (int16_t)(g_state.tempW * 10.0f + 0.5f);
g_hist->d[g_histPos] = (int16_t)(g_state.tempD * 10.0f + 0.5f);
g_hist->c[g_histPos] = caseOn ? (int16_t)(g_state.caseTemp * 10.0f + 0.5f) : CHART_HIST_NONE;
float dw = g_state.dutyW, dd = g_state.dutyD;
g_histDutyW[g_histPos] = (uint8_t)(dw < 0.0f ? 0 : (dw > 100.0f ? 100 : (int)(dw + 0.5f)));
g_histDutyD[g_histPos] = (uint8_t)(dd < 0.0f ? 0 : (dd > 100.0f ? 100 : (int)(dd + 0.5f)));
g_hist->dutyW[g_histPos] = (uint8_t)(dw < 0.0f ? 0 : (dw > 100.0f ? 100 : (int)(dw + 0.5f)));
g_hist->dutyD[g_histPos] = (uint8_t)(dd < 0.0f ? 0 : (dd > 100.0f ? 100 : (int)(dd + 0.5f)));
g_histPos = (uint16_t)((g_histPos + 1) % CHART_HIST_MAX);
if (g_histFill < CHART_HIST_MAX) g_histFill++;
@@ -2789,6 +2815,13 @@ void ui_init(ProtocolClient* client) {
g_client = client;
// Gespeicherte Darstellungs-Einstellungen laden (P4-lokal; Design-Standard: Rundinstrumente)
// Verlaufs-Historie (60 min à 1 s, ~29 KB) im PSRAM anlegen - wie die LVGL-Framebuffer.
// Schlaegt die Allokation fehl, bleibt das Chart schlicht leer (alle Zugriffe geprueft).
if (!g_hist) {
g_hist = (ChartHist*)heap_caps_malloc(sizeof(ChartHist), MALLOC_CAP_SPIRAM);
if (!g_hist) g_hist = (ChartHist*)heap_caps_malloc(sizeof(ChartHist), MALLOC_CAP_8BIT);
}
{ Preferences prefs; prefs.begin("ui", false);
g_tempDesign = prefs.getUChar("tempdsgn", TD_GAUGE);
g_brewLiveEnabled = prefs.getBool("brewlive", true);