Skip to content

epaper29.tc

EPaper 2.9" Display Controller & Data Logger

Source on GitHub

// 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;
}