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);