Skip to main content

🖥️ Pi System Monitor – JavaScript

// V26.06.008
// Linux Pi-System Monitor
// Bessere Fehlerbehandlung und optimierter Code verwendet nun async/await.
// Das Skript überwacht die Spannungswarnung, CPU-Temperatur und die Uptime eines Raspberry Pi-Systems,
// und speichert diese Informationen regelmäßig in ioBroker-Datenpunkten,
// wobei die Temperatur auf eine Stelle und die Uptime auf zwei Stellen hinter dem Komma begrenzt wird.

// Node exec
const { exec } = require("child_process");
const util = require("util");
const execAsync = util.promisify(exec);

// Pfad für die Datenpunkte
const dataPath = "javascript.0.linux-pi-system";

// --------------------------------------------------
// State erstellen / updaten
// --------------------------------------------------
async function updateOrCreateState(path, value, type = null) {
    try {
        const exists = await existsStateAsync(path);

        if (!exists) {

            const finalType = type || (
                value === null || value === undefined ? "mixed" :
                Array.isArray(value) ? "array" :
                typeof value
            );

            await createStateAsync(path, value, {
                name: path.split('.').pop(),
                role: finalType === "string" ? "text" : "value",
                type: finalType,
                read: true,
                write: false
            });
        }

        await setStateAsync(path, value, true);

    } catch (err) {
        log(`Fehler (${path}): ${err.message || err}`, "error");
    }
}

// --------------------------------------------------
// CPU Temperatur
// --------------------------------------------------
async function saveCPUTemperature() {
    try {
        const { stdout } = await execAsync("cat /sys/class/thermal/thermal_zone0/temp");

        const raw = stdout.trim();
        const milli = Number(raw);

        if (isNaN(milli)) throw new Error(`ungültiger Wert: ${raw}`);

        const celsius = Number((milli / 1000).toFixed(1));

        await updateOrCreateState(`${dataPath}.temperature`, celsius);

    } catch (error) {
        log(`CPU Temp Fehler: ${error.message || error}`, "error");
    }
}

// --------------------------------------------------
// Uptime
// --------------------------------------------------
async function saveUptime() {
    try {
        const { stdout } = await execAsync("cat /proc/uptime");

        const raw = stdout.trim().split(' ')[0];
        const seconds = Number(raw);

        if (isNaN(seconds)) throw new Error(`ungültiger Wert: ${raw}`);

        const hours = Number((seconds / 3600).toFixed(2));

        await updateOrCreateState(`${dataPath}.uptime`, hours);

    } catch (error) {
        log(`Uptime Fehler: ${error.message || error}`, "error");
    }
}

// --------------------------------------------------
// Voltage / Throttling Check via vcgencmd
//
// Voraussetzung:
// - Der ausführende Benutzer muss Mitglied der Gruppe "video" sein,
//   damit Zugriff auf /dev/vcio (vcgencmd) ohne sudo möglich ist.
//
// Hintergrund:
// - vcgencmd greift auf /dev/vcio zu.
// - Ohne passende Rechte schlägt der Befehl fehl (Permission denied).
// - Betrifft u.a. ioBroker, EOS und iob diag.
//
// Einrichtung für ioBroker:
//   sudo usermod -aG video iobroker
//
// Optional (für eigenen Login-User):
//   sudo usermod -aG video $USER
//
// Hinweis:
// - Danach ist ein Logout, Neustart oder Service-Neustart erforderlich,
//   damit die Gruppenänderung wirksam wird.
//
// Prüfung:
// - Alle Mitglieder der Gruppe anzeigen:
//   getent group video
// --------------------------------------------------

async function checkVoltageWarning() {
    try {
        const { stdout } = await execAsync("vcgencmd get_throttled");

        const raw = stdout.trim();

        // Beispiel: throttled=0x50000
        const match = raw.match(/0x([0-9a-fA-F]+)/);
        if (!match) throw new Error(`Ungültige Antwort: ${raw}`);

        const value = parseInt(match[1], 16);

        // Bitmasken laut Raspberry Pi Doku
        const UNDER_VOLTAGE_NOW      = 0x1;
        const THROTTLED_NOW          = 0x2;
        const FREQ_CAPPED_NOW        = 0x4;
        const UNDER_VOLTAGE_PAST     = 0x10000;
        const THROTTLED_PAST         = 0x20000;
        const FREQ_CAPPED_PAST       = 0x40000;

        let status = [];

        // Aktuelle Probleme
        if (value & UNDER_VOLTAGE_NOW) status.push("⚠️ Under-voltage NOW");
        if (value & THROTTLED_NOW) status.push("🔥 Throttled NOW");
        if (value & FREQ_CAPPED_NOW) status.push("📉 Frequency capped NOW");

        // Historische Probleme
        if (value & UNDER_VOLTAGE_PAST) status.push("Under-voltage occurred");
        if (value & THROTTLED_PAST) status.push("Throttling occurred");
        if (value & FREQ_CAPPED_PAST) status.push("Frequency capped occurred");

        const text = status.length ? status.join(" | ") : "✅ Voltage OK";

        await updateOrCreateState(`${dataPath}.powerWarning`, text);

        // Optional: Rohwert extra speichern (für Debug / VIS)
        await updateOrCreateState(`${dataPath}.powerWarningRaw`, value);

    } catch (error) {
        await updateOrCreateState(`${dataPath}.powerWarning`, "ERROR");
        log(`Voltage Fehler: ${error.message || error}`, "error");
    }
}

// --------------------------------------------------
// INITIALER LAUF
// --------------------------------------------------
(async () => {
    await saveCPUTemperature();
    await saveUptime();
    await checkVoltageWarning();
})();

// --------------------------------------------------
// ZEITPLAN
// --------------------------------------------------
schedule("*/5 * * * *", saveCPUTemperature);   // alle 5 Minuten
schedule("*/10 * * * *", saveUptime);          // alle 10 Minuten
schedule("*/1 * * * *", checkVoltageWarning);  // jede Minute