Zum Inhalt

guiton_display.tc

guiton_display.tc — LCD Energy Monitor

Source on GitHub

// guiton_display.tc — LCD Energy Monitor
// Converted from Tasmota Scripter for Guiton LCD board
// Device IP: 192.168.188.75

// ═══════════════════════════════════════════════════════════════════
// PERSISTENT VARIABLES (survive reboot)
// ═══════════════════════════════════════════════════════════════════
persist int loud;      // volume 0-100
persist int cpic;      // slideshow picture index

// ═══════════════════════════════════════════════════════════════════
// ENERGY VALUES (updated via UDP from collector)
// ═══════════════════════════════════════════════════════════════════
global float pwl;       // powerwall level %
global float atmp;      // ambient temperature
global float sip;       // grid import (Netz)
global float sop;       // solar production
global float bip;       // battery import/export
global float hip;       // home consumption
global float hwp;       // heat pump
global float sedc;      // solar roof (Dach)
global float wrgh;      // WR garden house (Gartenhaus)
global float wrga;      // WR garage
global float wrgg;      // WR garden (Garten)

// ═══════════════════════════════════════════════════════════════════
// STATE
// ═══════════════════════════════════════════════════════════════════
int lgrey;
int cnt;
int udp_timer;
watch int hr;        // hour change detection
watch int mn;        // minute change detection
watch int loud_w;    // volume slider change detection

char buf[160];
char tmp[80];
char path[80];
char fnam[64];

// ═══════════════════════════════════════════════════════════════════
// DISPLAY HELPERS
// ═══════════════════════════════════════════════════════════════════

// Draw value with auto-color: green (ci=2) if >0, red (ci=3) if <=0
// Returns new y position (y + 25)
int colval(float val, int x, int y) {
    int ci;
    if (val > 0.0) { ci = 2; } else { ci = 3; }
    sprintf(buf, "[Ci%dx%dy%dp-7]%.0f W", ci, x, y, val);
    dspText(buf);
    return y + 25;
}

// Draw value with fixed color index, returns new y
int wval(float val, int x, int y, int ci) {
    sprintf(buf, "[Ci%dx%dy%dp-7]%.0f W", ci, x, y, val);
    dspText(buf);
    return y + 25;
}

// Draw a column of labels starting at (x, y) with 25px spacing
void drawLabels(int x, int y, int col) {
    dspText("[f1s2Ci6]");
    if (col == 0) {
        // left column: H: N: S: B: HWP:
        sprintf(buf, "[x%dy%d]H:", x, y);
        dspText(buf);
        y = y + 25;
        sprintf(buf, "[x%dy%d]N:", x, y);
        dspText(buf);
        y = y + 25;
        sprintf(buf, "[x%dy%d]S:", x, y);
        dspText(buf);
        y = y + 25;
        sprintf(buf, "[x%dy%d]B:", x, y);
        dspText(buf);
        y = y + 25;
        sprintf(buf, "[x%dy%d]HWP:", x, y);
        dspText(buf);
    } else {
        // right column: DA: GA: GH: GA: SU:
        sprintf(buf, "[x%dy%d]DA:", x, y);
        dspText(buf);
        y = y + 25;
        sprintf(buf, "[x%dy%d]GA:", x, y);
        dspText(buf);
        y = y + 25;
        sprintf(buf, "[x%dy%d]GH:", x, y);
        dspText(buf);
        y = y + 25;
        sprintf(buf, "[x%dy%d]GA:", x, y);
        dspText(buf);
        y = y + 25;
        sprintf(buf, "[x%dy%d]SU:", x, y);
        dspText(buf);
    }
}

// Advance slideshow to next image in /RGB directory
void nextpic() {
    int dir;
    int found;
    int c;

    dir = fileOpenDir("/RGB");
    if (dir < 0) return;

    found = 0;
    c = 0;
    while (fileReadDir(dir, fnam)) {
        // Skip hidden files and non-image files
        if (fnam[0] == '.' || (strFind(fnam, ".jpg") < 0 && strFind(fnam, ".rgb") < 0)) {
            c = c;  // skip
        } else {
            c = c + 1;
            if (c == cpic) {
                // Build full path: /RGB/filename
                strcpy(path, "/RGB/");
                strcat(path, fnam);

                // Clear picture area with grey
                sprintf(buf, "[C%dx0y0R480:281]", lgrey);
                dspText(buf);

                // Draw scaled image
                strcpy(buf, "[x0y0P");
                strcat(buf, path);
                strcat(buf, ":1:480:281]");
                dspText(buf);

                audioPlay("/Correct.mp3");
                found = 1;
                break;
            }
        }
    }
    fileClose(dir);

    if (found > 0) {
        cpic = cpic + 1;
    } else {
        cpic = 1;  // loop back to start
    }
}

// Draw static elements (background, buttons, separators, labels)
void drawStatic() {
    // background + aerial photo
    sprintf(buf, "[B%dzf1s3x0y0P/Aerial.jpg:]", lgrey);
    dspText(buf);

    // touch power button
    dspText("[b1:420:420:50:50:2:11:4:2:/power:]");

    // separator lines
    dspText("[Ci1x0y285h480]");
    dspText("[Ci1x0y335h480]");

    // labels
    drawLabels(10, 345, 0);
    drawLabels(200, 345, 1);
}

// ═══════════════════════════════════════════════════════════════════
// UDP RECEIVE — parse energy values from collector
// ═══════════════════════════════════════════════════════════════════
void OnUDP() {
    udp_timer = 60;

    // TODO: Parse your UDP packet here.
    // Example pattern (adapt field names to your sender format):
    //   char pkt[512];
    //   udpGet(pkt);
    //   pwl  = udpFloat(pkt, "pwl");
    //   atmp = udpFloat(pkt, "atmp");
    //   sip  = udpFloat(pkt, "sip");
    //   sop  = udpFloat(pkt, "sop");
    //   bip  = udpFloat(pkt, "bip");
    //   hip  = udpFloat(pkt, "hip");
    //   hwp  = udpFloat(pkt, "hwp");
    //   sedc = udpFloat(pkt, "sedc");
    //   wrgh = udpFloat(pkt, "wrgh");
    //   wrga = udpFloat(pkt, "wrga");
    //   wrgg = udpFloat(pkt, "wrgg");
}

// ═══════════════════════════════════════════════════════════════════
// EVERY SECOND — display updates, change detection
// ═══════════════════════════════════════════════════════════════════
void EverySecond() {
    cnt = cnt + 1;

    // ─── UDP timeout ───
    if (udp_timer > 0) {
        udp_timer = udp_timer - 1;
    }
    if (udp_timer == 0) {
        addLog("TCC: udp timeout");
        udp_timer = -1;   // log once
    }

    // ─── Date / time ───
    dspText("[Ci5f4x10y300T]");
    dspText("[x160y300tS]");

    // ─── Temperature ───
    sprintf(buf, "[f2s1Ci3x350y300p-6]%.1f C", atmp);
    dspText(buf);

    // ─── Battery state ───
    sprintf(buf, "[f2s1x370y350p-6]%.1f %", pwl);
    dspText(buf);

    dspText("[f2s1]");

    // ─── Left column: Home / Netz / Solar / Battery / HWP ───
    int y;
    y = 345;
    y = wval(hip, 50, y, 2);      // Home — green
    y = colval(sip, 50, y);        // Netz — auto color
    y = wval(sop, 50, y, 7);      // Solar — cyan
    y = colval(bip, 50, y);        // Battery — auto color
    y = wval(hwp, 50, y, 2);      // Heat pump — green

    // ─── Right column: Dach / Gartenhaus / Garage / Garten / Sum ───
    y = 345;
    y = wval(sedc, 230, y, 7);                // Dach
    y = wval(0.0 - wrgh, 230, y, 7);          // Gartenhaus (negated)
    y = wval(0.0 - wrga, 230, y, 7);          // Garage (negated)
    y = wval(0.0 - wrgg, 230, y, 7);          // Garten (negated)
    float sum;
    sum = 0.0 - (wrga + wrgh + wrgg);
    y = wval(sum, 230, y, 7);                 // Sum

    // ─── Hour change: play chime ───
    hr = tasm_hour;
    if (tasm_year > 2023 && changed(hr)) {
        audioPlay("/Glass.mp3");
        snapshot(hr);
    }

    // ─── Volume change ───
    loud_w = loud;
    if (changed(loud_w)) {
        audioVol(loud);
        snapshot(loud_w);
    }

    // ─── Minute change: advance slideshow ───
    mn = tasm_minute;
    if (changed(mn)) {
        nextpic();
        snapshot(mn);
    }
}

// ═══════════════════════════════════════════════════════════════════
// WEB UI — slider on main Tasmota page
// ═══════════════════════════════════════════════════════════════════
void WebUI() {
    webSlider(loud, 0, 100, "Lautstaerke");
}

// ═══════════════════════════════════════════════════════════════════
// COMMAND — console volume control
// ═══════════════════════════════════════════════════════════════════
void Command(char cmd[]) {
    char arg[32];
    char resp[80];

    if (strFind(cmd, "Setvol") >= 0) {
        if (strlen(cmd) > 6) {
            strSub(arg, cmd, 6, 0);
            int vol;
            vol = atoi(arg);
            if (vol < 0) { vol = 0; }
            if (vol > 100) { vol = 100; }
            loud = vol;
            audioVol(loud);
            saveVars();
        }
        sprintf(resp, "{\"LCDSetvol\":\"%d\"}", loud);
        responseCmnd(resp);
    } else {
        strcpy(resp, "{\"LCD\":\"cmds: Setvol\"}");
        responseCmnd(resp);
    }
}

// ═══════════════════════════════════════════════════════════════════
// CALLBACKS
// ═══════════════════════════════════════════════════════════════════
void OnInit() {
    addLog("TCC: WiFi up");
}

void OnExit() {
    print("Guiton Display shutdown\n");
}

// ═══════════════════════════════════════════════════════════════════
// MAIN
// ═══════════════════════════════════════════════════════════════════
int main() {
    lgrey = 21130;
    cnt = 0;
    udp_timer = 30;
    hr = tasm_hour;
    mn = tasm_minute;
    loud_w = loud;
    snapshot(hr);
    snapshot(mn);
    snapshot(loud_w);

    // Audio init
    if (loud == 0) { loud = 80; }
    audioVol(loud);

    // Slideshow init
    if (cpic == 0) { cpic = 1; }

    // Draw static display
    drawStatic();

    // First slideshow image
    nextpic();

    // Startup sound
    audioPlay("/Startup.mp3");

    // Register console command prefix
    addCommand("LCD");

    return 0;
}