epaper29.tc¶
EPaper 2.9" Display Controller & Data Logger
// EPaper 2.9" Display Controller & Data Logger
// Sensors + EPaper display + 24h logging + WebChart + UDP globals
// 24h data logging arrays (96 x 15-min intervals)
// Data lives in [0..95] (the 15-min slot of the day, last_m15-1); the charts
// read arr[0..95] so all 96 slots show with no spurious leading 0. (Arrays sized
// [97] for back-compat with the older .bin layout; index 96 is now unused.)
// Stored in a dedicated .bin (NOT .pvs): .pvs is unreliable for chart arrays, so
// we own the file and fileWriteBin/fileReadBin it -- mirrors sml_chart_pv_save().
float aco2[97];
float atvc[97];
float tmpa[97];
float axhum[97];
#define CHART_FILE "/epaper29_chart.bin"
// Sensor readings — shared via UDP (auto-send on assignment)
global float wtemp = 0.0;
global float whumi = 0.0;
global float wtvoc = 0.0;
global float wco2 = 0.0;
// Local sensor readings (not shared)
float eco2 = 0;
float ahum = 0.0;
// Received via UDP from other devices
global float bpress = 0.0;
global float sedc = 0.0;
global float wrga = 0.0;
global float wrgh = 0.0;
global float wrgg = 0.0;
global float atmp = 0.0;
global float pwl = 0.0;
// Averaging accumulators
float tmps = 0.0;
float hums = 0.0;
float tvcs = 0.0;
float co2s = 0.0;
int mcnt = 0;
// State
int last_m15 = -1;
int cnt = 0;
char dt[128];
char lbl[32];
// Persist the four 24h chart rings to their own binary file (mirrors
// sml_chart_pv_save()). Called on the 15-min tick instead of saveVars().
void epaper_chart_save() {
int h = fileOpen(CHART_FILE, "w");
if (h < 0) return;
fileWriteBin(h, aco2, 97);
fileWriteBin(h, atvc, 97);
fileWriteBin(h, tmpa, 97);
fileWriteBin(h, axhum, 97);
fileClose(h);
}
void epaper_chart_load() {
int h = fileOpen(CHART_FILE, "r");
if (h < 0) return; // first run: arrays stay zero
fileReadBin(h, aco2, 97);
fileReadBin(h, atvc, 97);
fileReadBin(h, tmpa, 97);
fileReadBin(h, axhum, 97);
fileClose(h);
}
void read_sensors() {
wtemp = sensorGet("BME280#Temperature");
whumi = sensorGet("BME280#Humidity");
bpress = sensorGet("BME280#Pressure");
wtvoc = sensorGet("SGP30#TVOC");
eco2 = sensorGet("SGP30#eCO2");
ahum = sensorGet("BME280#AbsHumidity");
wco2 = sensorGet("SCD30#CarbonDioxide");
}
void update_display() {
// Row 1: Temperature, Humidity, Pressure, Time
sprintf(dt, "[f1p7x0y5]%.1f C", wtemp);
dspText(dt);
sprintf(dt, "[p7x70y5]%.1f %% [x250y5t] ", whumi);
dspText(dt);
//dspText("[x250y5t]");
sprintf(dt, "[p11x140y5]%.1f hPa", bpress);
dspText(dt);
// Row 2: TVOC, eCO2, Abs Humidity
sprintf(dt, "[p18x30y25]TVOC: %.1f ppb", wtvoc);
dspText(dt);
sprintf(dt, "[p18x160y25]eCO2: %.1f ppm", eco2);
dspText(dt);
sprintf(dt, "[p18c26l5]ahum: %.1f g^m3", ahum);
dspText(dt);
// solar inverters
sprintf(dt, "[p25c1l5]WR 1 (Dach) : %.1f W", sedc);
dspText(dt);
sprintf(dt, "[p25c1l6]WR 2 (Garage): %.1f W", -wrga);
dspText(dt);
sprintf(dt, "[p25c1l7]WR 3 (G-Haus): %.1f W", -wrgh);
dspText(dt);
sprintf(dt, "[p25c1l8]WR 4 (Garten): %.1f W", -wrgg);
dspText(dt);
// CO2
sprintf(dt, "[p25c1l10]CO2 : %.0f ppm", wco2);
dspText(dt);
sprintf(dt, "[x170y95r120:30f2p6x185y100] %.0f %", pwl);
dspText(dt);
sprintf(dt, "[f0s2p10x210y70] %.0f C", atmp);
dspText(dt);
// Flush to EPaper
dspText("[d]");
}
void EverySecond() {
read_sensors();
cnt = cnt + 1;
// Accumulate for averaging
tmps = tmps + wtemp;
hums = hums + whumi;
tvcs = tvcs + wtvoc;
co2s = co2s + wco2;
mcnt = mcnt + 1;
// 15-minute data logging
int m15 = (tasm_hour * 60 + tasm_minute) / 15 + 1;
if (m15 != last_m15 && last_m15 > 0) {
if (mcnt > 0) {
aco2[last_m15 - 1] = co2s / (float)mcnt; // store 0-based: slot 1..96 -> index 0..95
atvc[last_m15 - 1] = tvcs / (float)mcnt;
tmpa[last_m15 - 1] = tmps / (float)mcnt;
axhum[last_m15 - 1] = hums / (float)mcnt;
epaper_chart_save();
}
mcnt = 0;
co2s = 0.0;
tvcs = 0.0;
tmps = 0.0;
hums = 0.0;
}
last_m15 = m15;
// Sync graph position with time
int pos = m15;
if (pos >= 96) {
pos = 0;
}
// Update display every 10 seconds
int ups = tasm_uptime;
if (ups % 10 == 0) {
update_display();
}
// EPaper refresh every 5 min (prevent ghosting)
if (ups % 300 == 0) {
dspText("[Id]");
dspText("[id]");
}
// Sensor readings auto-send via UDP on assignment (global float)
}
void web_label(int idx) {
LGetString(idx, lbl);
dt = "{s}";
dt += lbl;
dt += "{m}";
webSend(dt);
}
void WebCall() {
web_label(0);
sprintf(dt, "%.1f C{e}", wtemp);
webSend(dt);
web_label(1);
sprintf(dt, "%.1f %{e}", whumi);
webSend(dt);
web_label(20);
sprintf(dt, "%.1f g/m3{e}", ahum);
webSend(dt);
web_label(4);
sprintf(dt, "%.0f ppm{e}", wco2);
webSend(dt);
web_label(2);
sprintf(dt, "%.1f hPa{e}", bpress);
webSend(dt);
sprintf(dt, "{s}Heap{m}%d kb{e}", tasm_heap/1024);
webSend(dt);
}
void WebPage() {
// Frame the charts in a card (WebPage is not auto-carded like WebCall slots).
webSend("<fieldset style='border:1px solid rgba(150,150,150,.4);border-radius:8px;margin:5px 2px;padding:2px 9px 8px'><legend style='font-size:11px;opacity:.55;padding:0 5px'>slot 4 charts</legend>");
webSend("<div style='margin-left:-30px'>");
int cpos = last_m15 - 1; // chart write-head: one past the newest WRITTEN slot
if (cpos < 0) { cpos = 0; } // (arr[last_m15-1] is the current, still-accumulating slot)
WebChartSize(640, 200);
// Dual Y-axis: TVOC (left 0-3000 ppb) + CO2 (right 0-2000 ppm)
WebChart(0, "Air Quality", "TVOC|ppb", 0xFF0000, cpos, 96,atvc, 0, 15, 0.0, 3000.0);
WebChart(0, "", "CO2|ppm", 0x0000FF, cpos, 96,aco2, 0, 15, 0.0, 2000.0);
// Dual Y-axis: Humidity (left 0-100 %) + Temperature (right 0-40 C)
WebChart(0, "Climate", "Humidity|%", 0xFF0000, cpos, 96,axhum, 1, 15, 0.0, 100.0);
WebChart(0, "", "Temperature|C", 0x0000FF, cpos, 96,tmpa, 1, 15, 0.0, 40.0);
webSend("</div>");
webSend("</fieldset>");
}
int main() {
epaper_chart_load(); // restore the chart rings from CHART_FILE
// Initial display setup: clear, draw separator lines
dspText("[zD0]");
dspText("[x0y20h296x0y40h296]");
dspText("[d]");
// Set initial time slot
last_m15 = (tasm_hour * 60 + tasm_minute) / 15 + 1;
print("EPaper display started\n");
return 0;
}