Skip to content

energy_alarm.tc

energy_alarm.tc — die Ton-Alarme von core2_energy.tc (.76) auf dem P4 (.118).

Source on GitHub

// @name: Energie-Alarme + Daikin (vom Core2)
// ─────────────────────────────────────────────────────────────────────
// energy_alarm.tc — die Ton-Alarme von core2_energy.tc (.76) auf dem P4 (.118).
// Ausgabe ueber die Befehle I2SSay / I2SPlay wie hourly_chime.tc.
//
//   Heizungs-WP      eine Stunde unter 500 W  -> "heizung alarm" + /alarm.mp3,
//                    dann alle 30 s, bis sie wieder laeuft
//   Akku voll        Powerwall erreicht 100 %  -> "battery full" + /doorbell.mp3
//   Akku leer        unter 3 %                 -> /Done.mp3 alle 5 Minuten
//   Energy Manager   Fehlerwert gerr > 0       -> /Classic.mp3 alle 5 Minuten
//   Verbindung weg   5 min kein UDP-Wert       -> "powerwall lost" + /alarm.mp3,
//                    wieder da                 -> /Done.mp3
//
// Anders als auf dem Core2:
//  - Die HWP ist im SOMMER AUS. Der Heizungsalarm ist darum nur scharf, wenn sie
//    in den letzten 24 Stunden gelaufen ist (Modus AUTO); EALARMHWP AN/AUS
//    schaltet ihn fest. Auf dem Core2 haette er den ganzen Sommer getoent.
//  - "powerwall lost" konnte dort nie ausloesen: der Zaehler wurde im selben
//    Takt auf 60 zurueckgesetzt, in dem er auf 0 geprueft wurde.
//  - Der Scripter auf .61 sendet eine Variable nur, wenn sie sich AENDERT. Akku
//    auf 100 % oder HWP im Sommer auf 0 kommen darum stundenlang nicht. hwp und
//    pwl starten auf -1 = unbekannt; ihre Alarme warten auf den ersten Wert.
//  - Auf .118 kommen die UDP-Pakete lueckenhaft (die Firmware setzt den Empfang
//    nach 60 s Stille selbst zurueck, gemessen 23.09.2026 mehrmals je Stunde).
//    "Verbindung weg" darum erst nach 5 Minuten ohne jedes Paket.
//  - Die Toene spielt der TaskLoop nacheinander: I2SSay blockiert gut eine
//    Sekunde (nicht im Sekundentakt), und ein zweiter Ton waehrend des ersten
//    wird mit "I2S Audio busy" abgelehnt.
//
// DAIKIN: fragt alle 5 Minuten die Klimaanlage im Schlafzimmer
// (http://192.168.188.43/aircon/get_sensor_info, Feld htemp=) und schreibt
// den Wert in die globale Variable shtemp -- die Zuweisung verschickt ihn per
// UDP an andere Geraete, wie bisher der Core2 (.76). Das Dashboard auf DIESEM
// Geraet bekommt ihn ueber shareSetFloat("shtemp"), der eigene UDP-Versand
// kommt dort nicht an.
//
// Konsole (Praefix EALARM):
//   EALARMSTAT            Stand
//   EALARMTEST            alle Toene nacheinander
//   EALARMHWP AUTO|AN|AUS Heizungsalarm (bleibt ueber Neustart)
//   EALARMRUHE n          n Minuten alle Alarme stumm (0 = wieder an)
//   EALARMDAIKIN          Daikin sofort abfragen
// ─────────────────────────────────────────────────────────────────────

global float hwp = -1.0;   // Heizungs-WP, aktuelle Leistung (W); -1 = noch kein Wert
global float pwl = -1.0;   // Powerwall-Fuellstand (%); -1 = noch kein Wert
global float gerr;     // Fehlerwert des Energy Managers
global float shtemp;   // Schlafzimmer (Daikin), wird hier GESENDET

persist int hwpModus = 0;     // 0 AUTO, 1 AN, 2 AUS

int  cnt = 0;
#define UDP_WEG 300           // Sekunden ohne Paket bis "Verbindung weg"
int  udpTimer = UDP_WEG;
int  udpDa = 0;               // mindestens ein UDP-Wert seit dem Start
int  udpSeit = 0;             // Sekunden seit dem ersten UDP-Wert
int  verloren = 0;
int  hwpNiedrig = 0;          // Minuten in Folge unter 500 W
int  hwpLief = 0;             // Sekunden seit die HWP zuletzt >= 500 W hatte (0 = nie)
int  hwpLiefJe = 0;
float battVor = 0.0;
int  ruheBis = 0;             // Uptime, bis zu der alles stumm ist
char resp[64];
char letzter[80] = "-";

// Daikin
#define DAIKIN "http://192.168.188.43/aircon/get_sensor_info"
int  dkFaellig = 0;
int  dkZeit = -1;             // Uptime der letzten gueltigen Antwort
int  dkFehler = 0;            // Fehlversuche in Folge

// Tonwarteschlange: EverySecond reiht ein, der TaskLoop spielt ab
#define T_HWP   1
#define T_VOLL  2
#define T_LEER  3
#define T_GERR  4
#define T_WEG   5
#define T_DA    6
int tq[8]; int tqKopf = 0; int tqEnde = 0;
void ton(int t) {
    int n = (tqEnde + 1) % 8;
    if (n == tqKopf) { return; }          // voll: lieber einen verlieren als blockieren
    tq[tqEnde] = t; tqEnde = n;
}

void sag(char text[]) {
    char c[64];
    sprintf(c, "I2SSay %s", text);
    tasmCmd(c, resp);
}

void spiel(char datei[]) {
    char c[64];
    sprintf(c, "I2SPlay %s", datei);
    tasmCmd(c, resp);
}

int stumm() {
    return tasm_uptime < ruheBis;
}

void merke(char was[]) {
    char ts[24];
    timeStamp(ts);
    sprintf(letzter, "%s %s", ts, was);
    addLog("EALARM %s", was);
}

int hwpScharf() {
    if (hwpModus == 1) { return 1; }
    if (hwpModus == 2) { return 0; }
    // AUTO: nur in der Heizzeit, also wenn die HWP im letzten Tag gelaufen ist
    return hwpLiefJe == 1 && hwpLief < 86400;
}

void UdpCall() {
    udpTimer = UDP_WEG;
    if (udpDa == 0) { udpDa = 1; }
}

void EverySecond() {
    cnt = cnt + 1;
    // Daikin alle 5 Minuten (Versatz 15 s), abgefragt im TaskLoop
    if (cnt % 300 == 15 && tasm_wifi == 1) { dkFaellig = 1; }
    if (hwpLiefJe == 1) { hwpLief = hwpLief + 1; }

    // ── Verbindung: 60 s ohne UDP-Wert ──
    if (udpTimer > 0) { udpTimer = udpTimer - 1; }
    if (udpDa == 1 && udpTimer == 0 && verloren == 0) {
        verloren = 1;
        merke("Verbindung weg (5 min kein UDP)");
        if (!stumm()) { ton(T_WEG); }
    }
    if (udpTimer > 0 && verloren == 1) {
        verloren = 0;
        merke("Verbindung wieder da");
        if (!stumm()) { ton(T_DA); }
    }
    if (udpDa == 0) { return; }
    // Eine Minute Anlauf: die Werte kommen einzeln per UDP, ein erstes Paket
    // bringt nicht unbedingt schon pwl mit (sonst "Akku voll" beim Start)
    if (udpSeit < 60) { udpSeit = udpSeit + 1; battVor = pwl; return; }

    // ── Heizungs-WP: jede Minute pruefen ──
    if (hwp >= 500.0) { hwpLief = 0; hwpLiefJe = 1; }
    if (cnt % 60 == 0) {
        if (hwp >= 0.0 && hwp < 500.0) { hwpNiedrig = hwpNiedrig + 1; } else { hwpNiedrig = 0; }
    }
    if (hwpNiedrig >= 60 && cnt % 30 == 0 && hwpScharf()) {
        if (hwpNiedrig == 60 && cnt % 60 == 0) { merke("Heizungs-WP seit 1 h unter 500 W"); }
        if (!stumm()) { ton(T_HWP); }
    }

    // ── Akku voll: einmal beim Erreichen von 100 % ──
    if (pwl >= 100.0 && battVor >= 0.0 && battVor < 100.0) {
        merke("Akku voll");
        if (!stumm()) { ton(T_VOLL); }
    }
    // ── Akku leer: alle 5 Minuten unter 3 % (Versatz 5 s) ──
    if (pwl >= 0.0 && pwl < 3.0 && cnt % 300 == 5) {
        merke("Akku unter 3 %");
        if (!stumm()) { ton(T_LEER); }
    }
    battVor = pwl;

    // ── Energy Manager meldet Fehler: alle 5 Minuten (Versatz 10 s) ──
    if (gerr > 0.0 && cnt % 300 == 10) {
        merke("Energy Manager meldet Fehler");
        if (!stumm()) { ton(T_GERR); }
    }
}

// Die Toene nacheinander. Die Wartezeiten stehen DIREKT im TaskLoop: ein
// delay() eine Funktion tiefer sperrt den Slot fuer die anderen Rueckrufe
// (EverySecond, UdpCall), so lange er wartet.
void TaskLoop() {
    while (1) {
        if (tqKopf != tqEnde) {
            int t = tq[tqKopf];
            tqKopf = (tqKopf + 1) % 8;
            int ms = 1500;
            if (t == T_HWP)  { sag("heizung alarm");  spiel("/alarm.mp3");    ms = 6000; }
            if (t == T_VOLL) { sag("battery full");   spiel("/doorbell.mp3"); ms = 4000; }
            if (t == T_LEER) { spiel("/Done.mp3"); }
            if (t == T_GERR) { spiel("/Classic.mp3"); }
            if (t == T_WEG)  { sag("powerwall lost"); spiel("/alarm.mp3");    ms = 6000; }
            if (t == T_DA)   { spiel("/Done.mp3"); }
            delay(ms);
        }
        if (dkFaellig == 1) {
            dkFaellig = 0;
            char dk[160];
            int n = httpGet(DAIKIN, dk);
            int hp = -1;
            if (n > 0) { hp = strFind(dk, "htemp="); }
            if (hp >= 0) {
                char hv[12];
                strSub(hv, dk, hp + 6, 10);
                shtemp = atof(hv);           // Zuweisung = UDP-Versand an ANDERE Geraete
                // Slots auf DIESEM Geraet erreicht der eigene Versand nicht
                // (gemessen: das Dashboard blieb auf 0) -- darum der Share-Speicher
                shareSetFloat("shtemp", shtemp);
                dkZeit = tasm_uptime;
                dkFehler = 0;
            } else {
                dkFehler = dkFehler + 1;
            }
        }
        delay(200);
    }
}

// Statuszeilen auf der Hauptseite
void WebCall() {
    char b[200];
    char m[8];
    strcpy(m, "AUTO");
    if (hwpModus == 1) { strcpy(m, "AN"); }
    if (hwpModus == 2) { strcpy(m, "AUS"); }
    char sch[16]; strcpy(sch, "nicht scharf");
    if (hwpScharf()) { strcpy(sch, "scharf"); }
    char ak[16]; strcpy(ak, "unbekannt");
    if (pwl >= 0.0) { sprintf(ak, "%.0f %%", pwl); }
    char vb[24]; strcpy(vb, "ok");
    if (verloren) { strcpy(vb, "WEG"); }
    if (udpDa == 0) { strcpy(vb, "noch nichts"); }
    char ru[24]; strcpy(ru, "");
    if (stumm()) { sprintf(ru, ", stumm %d min", (ruheBis - tasm_uptime + 59) / 60); }
    sprintf(b, "{s}Energie-Alarme{m}Heizung %s (%s) &middot; Akku %s &middot; Daten %s%s{e}", m, sch, ak, vb, ru);
    webSend(b);
    sprintf(b, "{s}letzter Alarm{m}%s{e}", letzter);
    webSend(b);
    if (dkZeit >= 0) {
        int alt = (tasm_uptime - dkZeit) / 60;
        sprintf(b, "{s}Schlafzimmer (Daikin){m}%.1f &deg;C, vor %d min", shtemp, alt);
        webSend(b);
        if (dkFehler > 0) { sprintf(b, " &middot; %d Fehlversuche", dkFehler); webSend(b); }
        webSend("{e}");
    } else if (dkFehler > 0) {
        sprintf(b, "{s}Schlafzimmer (Daikin){m}keine Antwort (%d Versuche){e}", dkFehler);
        webSend(b);
    } else {
        webSend("{s}Schlafzimmer (Daikin){m}erste Abfrage folgt{e}");
    }
}

void Command(char cmd[]) {
    char r[200];
    if (strcmp(cmd, "STAT") == 0) {
        char m[8];
        strcpy(m, "AUTO");
        if (hwpModus == 1) { strcpy(m, "AN"); }
        if (hwpModus == 2) { strcpy(m, "AUS"); }
        int ruhe = 0;
        if (stumm()) { ruhe = (ruheBis - tasm_uptime + 59) / 60; }
        char u[4]; strcpy(u, "ok");
        if (verloren) { strcpy(u, "WEG"); }
        sprintf(r, "HWP %.0f W (%s, scharf %d, %d min niedrig) | Akku %.1f %% | gerr %.0f | UDP %s | Ruhe %d min | Daikin %.1f C (Fehler %d) | zuletzt: %s",
                hwp, m, hwpScharf(), hwpNiedrig, pwl, gerr, u, ruhe, shtemp, dkFehler, letzter);
        responseCmnd(r);
    } else if (strcmp(cmd, "TEST") == 0) {
        ton(T_HWP); ton(T_VOLL); ton(T_LEER); ton(T_GERR); ton(T_WEG); ton(T_DA);
        responseCmnd("alle sechs Toene nacheinander (etwa 25 s)");
    } else if (strcmp(cmd, "HWP AUTO") == 0) {
        hwpModus = 0; saveVars(); responseCmnd("Heizungsalarm AUTO (nur wenn die HWP im letzten Tag lief)");
    } else if (strcmp(cmd, "HWP AN") == 0) {
        hwpModus = 1; saveVars(); responseCmnd("Heizungsalarm immer AN");
    } else if (strcmp(cmd, "HWP AUS") == 0) {
        hwpModus = 2; saveVars(); responseCmnd("Heizungsalarm AUS");
    } else if (strcmp(cmd, "DAIKIN") == 0) {
        dkFaellig = 1;
        responseCmnd("Daikin wird abgefragt, Ergebnis mit EALARMSTAT");
    } else if (strStartsWith(cmd, "RUHE")) {
        char a[16]; strSub(a, cmd, 4, 0);
        int n = strToInt(a);
        if (n < 0) { n = 0; }
        ruheBis = tasm_uptime + n * 60;
        sprintf(r, "Alarme %d Minuten stumm", n);
        responseCmnd(r);
    } else {
        responseCmnd("EALARMSTAT | EALARMTEST | EALARMHWP AUTO|AN|AUS | EALARMRUHE n | EALARMDAIKIN");
    }
}

int main() {
    addLog("EALARM: Ton-Alarme gestartet");
    addCommand("EALARM");
    return 0;
}