MPII 5000 - ESS-Modus per modbus ändern

Ganz herzlichen Dank!

Ich brauch wohl noch etwas um wirklich aktiv schalten/steuern zu können, da meine SOC Berechnungsversuche aktuell übelst grausige Ergebnisse liefern....

/*
  Selbstlernende SOC-Kurve für JK BMS mit ioBroker
  ------------------------------------------------
  Funktionen:
  1. Adaptive Spannungskorrektur / selbstlernende Kurve
  2. Stromintegration ab ±2 A (korrigierter Strom)
  3. Automatisch lernender CurrOffset für kleine Ströme
  4. Adaptive CurrFactor-Lernfunktion
  5. Spannungsglättung
  6. Temperaturabhängige SOC-Anpassung
  7. Initial-SOC aus Victron beim ersten Start
  8. Gesamt-SOC aller aktiven Batterien (gewichteter Durchschnitt)
  9. Jede Batterie aktivierbar/deaktivierbar
 10. Persistente Speicherung von CurrFactor, CurrOffset, LearnVoltageCurve
 11. Intervall: 5 Sekunden
*/

const interval = 5000; // Intervall in ms

// ----------------------------
// Konfiguration der Batterien
// ----------------------------
let batteries = [
    { active: true,  name: 'JKBMS0', modbusVoltage: 'modbus.1.holdingRegisters.4749_Spannung', modbusCurrent: 'modbus.1.holdingRegisters.4753_Strom', modbusSOC: 'modbus.1.holdingRegisters.4760_SOC', dataPath: '0_userdata.0.Stromspeicher.JKBMS0', capacity: 314 },
    { active: true,  name: 'JKBMS1', modbusVoltage: 'modbus.2.holdingRegisters.4749_BatteryVoltage', modbusCurrent: 'modbus.2.holdingRegisters.4753_BatteryCurrent', modbusSOC: 'modbus.2.holdingRegisters.4760_SOC', dataPath: '0_userdata.0.Stromspeicher.JKBMS1', capacity: 314 },
    { active: false, name: 'JKBMS2', modbusVoltage: 'modbus.3.holdingRegisters.4749_BatteryVoltage', modbusCurrent: 'modbus.3.holdingRegisters.4753_BatteryCurrent', modbusSOC: 'modbus.3.holdingRegisters.4760_SOC', dataPath: '0_userdata.0.Stromspeicher.JKBMS2', capacity: 314 }
];

// ----------------------------
// Lern- und Anpassungsfaktoren
// ----------------------------
const learningFactor      = 0.05;   // Spannungslernkurve
const offsetLearningRate  = 0.05;   // CurrOffset Lernrate
const factorLearningRate  = 0.001;  // CurrFactor Lernrate
const voltageSmoothFactor = 0.1;    // Glättung der Spannung
const minCurrentA         = 2;      // Minimale Stromstärke für Integration
const tempRef             = 25;     // Referenztemperatur °C
const tempFactor          = 0.1;    // %SOC pro °C

// ----------------------------
// Hilfsfunktion: fehlende Datenpunkte erstellen
// ----------------------------
function ensureStateExists(id, value) {
    if (!existsState(id)) {
        createState(id, value, { type: 'number', read: true, write: true });
    }
}

// ----------------------------
// Hauptintervall
// ----------------------------
setInterval(() => {

    let totalSOC = 0;
    let totalCapacity = 0;

    batteries.forEach(batt => {
        if (!batt.active) return;

        // ----------------------------
        // 1. Sicherstellen, dass alle benötigten Datenpunkte existieren
        // ----------------------------
        ensureStateExists(`${batt.dataPath}.SOC_Initial`, 0);
        ensureStateExists(`${batt.dataPath}.CurrFactor`, 1);
        ensureStateExists(`${batt.dataPath}.CurrOffset`, 0);
        ensureStateExists(`${batt.dataPath}.PrevVoltage`, getState(batt.modbusVoltage)?.val || 0);
        ensureStateExists(`${batt.dataPath}.SOC_Current`, 0);
        ensureStateExists(`${batt.dataPath}.SOC_Final`, 0);
        ensureStateExists(`${batt.dataPath}.Current_Smoothed`, 0);
        ensureStateExists(`${batt.dataPath}.Ah_Remaining`, 0);
        ensureStateExists(`${batt.dataPath}.LearnVoltageCurve`, '{}');

        // ----------------------------
        // 2. Modbus-Daten
        // ----------------------------
        let socRaw   = getState(batt.modbusSOC)?.val || 0;
        let voltage  = getState(batt.modbusVoltage)?.val || 0;
        let current  = getState(batt.modbusCurrent)?.val || 0;
        let temp     = getState(`${batt.dataPath}.Temp`)?.val || tempRef;

        // ----------------------------
        // 3. CurrFactor & CurrOffset
        // ----------------------------
        let currFactor = getState(`${batt.dataPath}.CurrFactor`).val;
        let currOffset = getState(`${batt.dataPath}.CurrOffset`).val;

        if (Math.abs(current) < minCurrentA) {
            currOffset += offsetLearningRate * (0 - current);
            setState(`${batt.dataPath}.CurrOffset`, currOffset, true);
        }

        let currentCorrected = current * currFactor + currOffset;

        // ----------------------------
        // 4. Initial SOC aus Victron beim ersten Start
        // ----------------------------
        let socStart = getState(`${batt.dataPath}.SOC_Initial`).val;
        if (socStart === 0) {
            setState(`${batt.dataPath}.SOC_Initial`, socRaw, true);
            socStart = socRaw;
        }

        let socCurrent = getState(`${batt.dataPath}.SOC_Current`).val || socStart;

        // ----------------------------
        // 5. Spannung glätten
        // ----------------------------
        let prevVoltage = getState(`${batt.dataPath}.PrevVoltage`).val;
        let voltageSmoothed = prevVoltage * (1 - voltageSmoothFactor) + voltage * voltageSmoothFactor;
        setState(`${batt.dataPath}.PrevVoltage`, voltageSmoothed, true);

        // ----------------------------
        // 6. Temperaturabhängige SOC-Anpassung
        // ----------------------------
        let deltaT = temp - tempRef;
        socCurrent += deltaT * tempFactor;

        // ----------------------------
        // 7. Stromintegration
        // ----------------------------
        if (Math.abs(currentCorrected) > minCurrentA) {
            let socDelta = (currentCorrected * (interval / 1000) / 3600) / batt.capacity * 100;
            socCurrent += socDelta;
        }

        // ----------------------------
        // 8. Adaptive Lernkurve
        // ----------------------------
        let learnCurve = JSON.parse(getState(`${batt.dataPath}.LearnVoltageCurve`).val);
        let voltageKey = voltageSmoothed.toFixed(2);

        if (learnCurve[voltageKey]) {
            socCurrent += (learnCurve[voltageKey] - socCurrent) * learningFactor;
        } else {
            learnCurve[voltageKey] = socCurrent;
            setState(`${batt.dataPath}.LearnVoltageCurve`, JSON.stringify(learnCurve), true);
        }

        // ----------------------------
        // 9. Adaptive CurrFactor-Lernung
        // ----------------------------
        if (Math.abs(currentCorrected) > minCurrentA) {
            let socDeltaIntegrated = (currentCorrected * (interval / 1000) / 3600) / batt.capacity * 100;
            let socDeltaObserved = socCurrent - socStart;
            if (Math.abs(socDeltaIntegrated) > 0.01) {
                currFactor += (socDeltaObserved / socDeltaIntegrated - currFactor) * factorLearningRate;
                setState(`${batt.dataPath}.CurrFactor`, currFactor, true);
            }
        }

        // ----------------------------
        // 10. Datenpunkte aktualisieren
        // ----------------------------
        setState(`${batt.dataPath}.SOC_Current`, socCurrent, true);
        setState(`${batt.dataPath}.SOC_Final`, socCurrent, true);
        setState(`${batt.dataPath}.SOC_Voltage`, socRaw, true);
        setState(`${batt.dataPath}.Current_Smoothed`, currentCorrected, true);

        let AhRemaining = batt.capacity * socCurrent / 100;
        setState(`${batt.dataPath}.Ah_Remaining`, AhRemaining, true);

        // ----------------------------
        // 11. Gesamt-SOC berechnen
        // ----------------------------
        totalSOC += socCurrent * batt.capacity;
        totalCapacity += batt.capacity;
    });

    if (totalCapacity > 0) {
        let socGesamt = totalSOC / totalCapacity;
        setState('0_userdata.0.Stromspeicher.SOC_Gesamt', socGesamt, true);

        // ----------------------------
        // 12. Vergleich Victron (nur Logging)
        // ----------------------------
        let victronSOC = getState('modbus.0.inputRegisters.100.843_Battery_State_of_Charge')?.val;
        if (victronSOC !== undefined && victronSOC !== null) {
            let abweichung = socGesamt - victronSOC;
            setState('0_userdata.0.Stromspeicher.SOC_Abweichung', abweichung, true);
        }
    }

}, interval);

Puh, ganz schön aufwenige Rechnerei. Respekt.

Das JK BMS misst bei niedrigen Strömen ungenau. Da ist also ein große Fehlerabweichung. Als im Oktober die WP noch kaum lief, war der Standby-Verbrauch eher gering, 100-200 Watt über die Nacht. Die Batterie wurde an den nebligen Tagen nicht mehr richtig voll, so dass ich das erste Mal SOC 10% und damit Entlade-Ende erreicht habe.

Die Batterie wurde am Tag ein wenig aufgeladen, abends wurde dann SOC 12% angezeigt, die SOC-0% Voltage (siehe hier) von 3V wurde aber erreicht. Damit fiel der SOC direkt von 12% auf 0%.
Daher habe ich als Trigger 3,1V eingestellt.

Ich nehme also stumpf den SOC des JK BMS, wohl wissend, dass der eine grobe Schätzung ist.

1 „Gefällt mir“