Als Doku für andere die damit Probleme haben, dieser Thread hat mir jedenfalls sehr weitergeholfen, Danke! Hier mein Lösungsweg und Script:
Meine Fronius Einstellung bei Sicherheits- und Netzanforderungen:
Einspeisebegrenzung --> Dyn Einspeisebegrenzung auf xyWatt gesetzt (optional, sonst deaktivieren)
I/O-Leistungsmanagement --> Steuerungsprioritäten : hier Modbus Steuerung auf Platz 1 in der Liste (aber vielleicht auch nicht nötig, nicht überprüft)
Fronius Einstellung bei Energiemanagement:
Batteriemanagement --> Batterie Ladelimit Einstellungen von 5%-100% gesetzt,
wichtig: Ladung aus anderen Generatoren im Heimnetzwerk und aus dem öffentlichen Netz muss aktiviert sein (sonst nicht die volle Ladeleistung), Zeitabhängige Batteriesteuerung nicht aktiviert.
Eigenverbrauchs-Optimierung --> Manuell --> Betriebsmodus Einspeisung auf 5Watt (damit möglichst wenig Netzbezug ganz allgemein)
Fronius Einstellung bei Kommunikation
Modbus: Modbus Server über TCP, 502=Modbus-Port, int + SF=SunSpec Model, 200= Zähleradresse
SolarAPI aktiviert
Script gemeinsam mit Gemini 2.5. AI ist auch gut brauchbar falls man etwas ändern will, um es erklärt zu bekommen oder andere Funktionen haben möchte 🙂
wichtig: bei setStateAsync(testOID, testValue, false); // muss false sein
Probiere die Neuinstallation des Script adapters bei Fehlermeldung "...wurde nicht ausgeführt während der Debug-Modus aktiv ist". Alle anderen Lösungswege hatten bei mir nichts gebracht.
// === Skript zur nächtlichen Batterieladung basierend auf Solar-Forecast ===
//
// Ziel: Lädt die Batterie jeden Tag zwischen 4:00 und 6:00 Uhr morgens bzw. zur StartTime und StopTime
// aus dem Netz auf einen Ziel-SOC (%), der vom Solar-Forecast des Tages abhängt.
// - Niedriger Forecast -> Hoher Ziel-SOC (z.B. 100%)
// - Mittlerer Forecast -> Mittlerer Ziel-SOC (z.B. 50%)
// - Hoher Forecast -> Niedriger Ziel-SOC (z.B. 20%)
//
// Version: 1.0
// Datum: 2025-04-22
// --- KONFIGURATION ---
// Debug-Modus (true = mehr Log-Ausgaben, false = weniger)
const DEBUG = true;
// Objekt-ID des Solar-Forecasts (in kWh für den aktuellen Tag)
// WICHTIG: Sicherstellen, dass dieser Wert vor StopTime (z.B.4 Uhr morgens) aktuell ist!
const FORECAST_OID = "pvforecast.0.plants.mahue.energy.today"; //
// Schwellenwerte für den Forecast (in kWh)
const THRESHOLD_MEDIUM = 8; // Ab diesem Wert gilt der Forecast als "mittel"
const THRESHOLD_HIGH = 12; // Ab diesem Wert gilt der Forecast als "hoch"
// Darunter gilt er als "niedrig"
// Ziel-SOC (%) für die nächtliche Ladung je nach Forecast-Kategorie
const TARGET_SOC_LOW = 9000; // Ziel-SOC bei niedrigem Forecast
const TARGET_SOC_MEDIUM = 4000; // Ziel-SOC bei mittlerem Forecast
const TARGET_SOC_HIGH = 1500; // Ziel-SOC bei hohem Forecast
// Modbus Objekt-IDs (Platzhalter - BITTE DURCH KORREKTE IDs ERSETZEN!)
const MODBUS_SOC_OID = "modbus.0.holdingRegisters.40351_ChaState"; // Aktueller Batterie-SOC in % * 100
const MODBUS_ENABLE_GRID_CHARGE_OID = "modbus.0.holdingRegisters.40360_ChaGriSet"; // Register zum Aktivieren der Netzladung (z.B. Modus setzen) 40361 ChaGriSet enum16 1: GRID (Charging from grid enabled)
const MODBUS_SET_CHARGE_POWER_PERCENT_OID = "modbus.0.holdingRegisters.40356_InWRte"; // Register zum Setzen der Ladeleistung in % (BEISPIEL! 10000 = 100.00%) 40357 valid range in raw values is from -10000 to 10000.
const MODBUS_SET_DISCHARGE_POWER_PERCENT_OID = "modbus.0.holdingRegisters.40355_OutWRte"; // Percent of max discharge rate. laden erzwingen = "negatives Entladen" auf -10000 (minus 100.00 %) setzen
const MODBUS_STORAGE_CHARGE_CONTROL_OID = "modbus.0.holdingRegisters.40348_StorCtl_Mod"; // Register zum aktivieren der Register InWRte und OutWRte
// Wert für maximale Ladeleistung (oft 10000 für 100.00%)
const MAX_CHARGE_POWER_VALUE = 10000;
// Wert für maximale Entlade Ladeleistung (oft 10000 für 100.00%)
const MAX_DISCHARGE_POWER_VALUE = -10000;
// Wert, der in MODBUS_ENABLE_GRID_CHARGE_OID geschrieben werden muss, um Netzladung zu aktivieren
const ENABLE_GRID_CHARGE_VALUE = 1; //
const DISABLE_GRID_CHARGE_VALUE = 0;
// Wert, der in MODBUS_STORAGE_CHARGE_CONTROL_OID geschrieben werden muss, um Netzladung zu beenden / Normalbetrieb
const DISABLE_CHARGE_CONTROL_VALUE = 0; // SEN!
// Wert, der in MODBUS_STORAGE_CHARGE_CONTROL_OID geschrieben werden muss, um Netzladung zu aktivieren / Grid charge state
const ENABLE_CHARGE_CONTROL_VALUE = 3; //
// Prüfintervall während des Ladens (in Millisekunden), um SOC zu checken
const CHECK_INTERVAL_MS = 30000; // Alle 30 Sekunden
// Start time for schedule
const StartTime = 4; // at which time of day starts the grid charge
// Define the hour as a constant const scheduledHour = 4;
const cronScheduleStart = `0 ${StartTime} * * *`; // Build the string for use in scheduler function
// Stop Time
const StopTime = 6; // at which time of day stops the grid charge
const cronScheduleStop = `0 ${StopTime} * * *`; // Build the string for use in scheduler function
// --- Globale Variable für den Lade-Intervall ---
let chargeInterval = null;
let targetSOC = 0; // Wird zu Beginn des Ladevorgangs gesetzt
// --- HILFSFUNKTIONEN ---
// Log schreiben, wenn DEBUG == true
async function LOG(message) {
if (DEBUG) {
console.log(`[NightCharge] ${message}`);
}
}
// --- HAUPTFUNKTIONEN ---
// Funktion zum Stoppen des Ladevorgangs und Zurücksetzen der Modbus-Register
async function stopCharging(reason) {
if (chargeInterval) {
LOG(`Stoppe Ladevorgang: ${reason}`);
clearInterval(chargeInterval);
chargeInterval = null;
try {
// Entladeleistung explizit auf 10000 resetten von -10000
await setStateAsync(MODBUS_SET_DISCHARGE_POWER_PERCENT_OID, 10000, false);
LOG(`normal night charge stopp: Modbus-Register ${MODBUS_SET_DISCHARGE_POWER_PERCENT_OID} auf 10000 gesetzt.`);
// Netzladung deaktivieren
await setStateAsync(MODBUS_ENABLE_GRID_CHARGE_OID, DISABLE_GRID_CHARGE_VALUE, false);
LOG(`normal night charge stopp: Modbus-Register ${MODBUS_ENABLE_GRID_CHARGE_OID} auf ${DISABLE_GRID_CHARGE_VALUE} gesetzt (Netzladung deaktiviert).`);
// Storage Control wieder sperren / Normalbetrieb wiederherstellen
await setStateAsync(MODBUS_STORAGE_CHARGE_CONTROL_OID, DISABLE_CHARGE_CONTROL_VALUE, false); // acknowledge = true
LOG(`normal night charge stopp: Modbus-Register ${MODBUS_STORAGE_CHARGE_CONTROL_OID} auf ${DISABLE_CHARGE_CONTROL_VALUE} gesetzt.`);
} catch (e) {
console.error(`[NightCharge] Fehler beim Stoppen des Ladevorgangs via Modbus: ${e}`);
}
} else {
LOG(`Kein aktiver Ladevorgang zum Stoppen (${reason}). Stelle sicherheitshalber Normalbetrieb her.`);
try {
// Entladeleistung explizit auf 10000 resetten von -10000
await setStateAsync(MODBUS_SET_DISCHARGE_POWER_PERCENT_OID, 10000, false);
LOG(`just-in-case-stop: Modbus-Register ${MODBUS_SET_DISCHARGE_POWER_PERCENT_OID} auf 10000 gesetzt.`);
// Netzladung deaktivieren
await setStateAsync(MODBUS_ENABLE_GRID_CHARGE_OID, DISABLE_GRID_CHARGE_VALUE, false);
LOG(`just-in-case-stop: Modbus-Register ${MODBUS_ENABLE_GRID_CHARGE_OID} auf ${DISABLE_GRID_CHARGE_VALUE} gesetzt (Netzladung deaktiviert).`);
// Storage Control wieder sperren / Normalbetrieb wiederherstellen (Sicherheitsmaßnahme)
await setStateAsync(MODBUS_STORAGE_CHARGE_CONTROL_OID, DISABLE_CHARGE_CONTROL_VALUE, false); // acknowledge = true
LOG(`just-in-case-stop: Modbus-Register ${MODBUS_STORAGE_CHARGE_CONTROL_OID} auf ${DISABLE_CHARGE_CONTROL_VALUE} gesetzt.`);
} catch (e) {
console.error(`[NightCharge] Fehler beim Sicherheits-Stopp via Modbus: ${e}`);
}
}
}
// Funktion zum Prüfen des SOC während des Ladens
async function checkChargeStatus() {
try {
const currentSOCState = await getStateAsync(MODBUS_SOC_OID);
if (currentSOCState && currentSOCState.val !== null) {
const currentSOC = currentSOCState.val;
LOG(`Prüfe Ladestatus: Aktueller SOC = ${currentSOC}%, Ziel-SOC = ${targetSOC}%`);
if (currentSOC >= targetSOC) {
await stopCharging(`Ziel-SOC (${targetSOC}%) erreicht.`);
}
// Zusätzliche Sicherheitsprüfung für die Uhrzeit (obwohl der 6-Uhr-Trigger das auch abfängt)
const currentHour = new Date().getHours();
if (currentHour >= StopTime) {
await stopCharging(`Ladefenster-Ende (6:00 Uhr) erreicht.`);
}
} else {
LOG(`Fehler: Konnte aktuellen SOC nicht lesen (${MODBUS_SOC_OID}). Ladevorgang wird fortgesetzt.`);
}
} catch (e) {
console.error(`[NightCharge] Fehler beim Prüfen des Ladestatus: ${e}`);
// Hier eventuell nach mehreren Fehlern stoppen?
}
}
// Hauptfunktion, die um StartTime (z.B. 4) Uhr morgens getriggert wird
async function chargeBatteryBasedOnForecast() {
LOG(`Starte nächtlichen Lade-Check (${StartTime}:00 Uhr).`);
// Sicherstellen, dass kein alter Lade-Intervall läuft
if (chargeInterval) {
LOG("Warnung: Alter Lade-Intervall war noch aktiv. Stoppe ihn.");
await stopCharging(`Neuer Start um ${StartTime}:00 Uhr"`);
}
let forecast = 0;
let currentSOC = 0;
// 1. Forecast und aktuellen SOC lesen
try {
const forecastState = await getStateAsync(FORECAST_OID);
if (forecastState && forecastState.val !== null) {
forecast = forecastState.val;
} else {
LOG(`Fehler: Konnte Forecast-Wert nicht lesen (${FORECAST_OID}). Breche Ladevorgang ab.`);
return;
}
const currentSOCState = await getStateAsync(MODBUS_SOC_OID);
if (currentSOCState && currentSOCState.val !== null) {
currentSOC = currentSOCState.val;
} else {
LOG(`Fehler: Konnte aktuellen SOC nicht lesen (${MODBUS_SOC_OID}). Breche Ladevorgang ab.`);
return;
}
LOG(`Forecast = ${forecast} kWh, Aktueller SOC = ${currentSOC}%`);
} catch (e) {
console.error(`[NightCharge] Fehler beim Lesen der Initialwerte: ${e}. Breche ab.`);
return;
}
// 2. Ziel-SOC basierend auf Forecast bestimmen
if (forecast < THRESHOLD_MEDIUM) {
targetSOC = TARGET_SOC_LOW;
LOG(`Forecast niedrig (< ${THRESHOLD_MEDIUM} kWh). Ziel-SOC: ${targetSOC}%`);
} else if (forecast < THRESHOLD_HIGH) {
targetSOC = TARGET_SOC_MEDIUM;
LOG(`Forecast mittel (>= ${THRESHOLD_MEDIUM} kWh, < ${THRESHOLD_HIGH} kWh). Ziel-SOC: ${targetSOC}%`);
} else {
targetSOC = TARGET_SOC_HIGH;
LOG(`Forecast hoch (>= ${THRESHOLD_HIGH} kWh). Ziel-SOC: ${targetSOC}%`);
}
// 3. Prüfen, ob Ladung überhaupt notwendig ist
if (currentSOC >= targetSOC) {
LOG(`Batterie bereits ausreichend geladen (Aktuell: ${currentSOC}%, Ziel: ${targetSOC}%). Keine nächtliche Ladung nötig.`);
// Sicherstellen, dass der Wechselrichter im Normalmodus ist
await stopCharging(`Ziel bereits vor ${StartTime}:00 Uhr erreicht`);
return;
}
// 4. Ladung starten
LOG(`Starte nächtliche Ladung. Ziel-SOC: ${targetSOC}%.`);
try {
// Netzladung aktivieren / Storage Control Mode
await setStateAsync(MODBUS_STORAGE_CHARGE_CONTROL_OID, ENABLE_CHARGE_CONTROL_VALUE, false); // acknowledge = true
LOG(`Modbus-Register ${MODBUS_STORAGE_CHARGE_CONTROL_OID} auf ${ENABLE_CHARGE_CONTROL_VALUE} gesetzt.`);
// Netzladung aktivieren
await setStateAsync(MODBUS_ENABLE_GRID_CHARGE_OID, ENABLE_GRID_CHARGE_VALUE, false);
LOG(`Modbus-Register ${MODBUS_ENABLE_GRID_CHARGE_OID} auf ${ENABLE_GRID_CHARGE_VALUE} gesetzt (Netzladung aktiviert).`);
// Maximale Ladeleistung setzen
await setStateAsync(MODBUS_SET_CHARGE_POWER_PERCENT_OID, MAX_CHARGE_POWER_VALUE, false);
LOG(`Modbus-Register ${MODBUS_SET_CHARGE_POWER_PERCENT_OID} auf ${MAX_CHARGE_POWER_VALUE} gesetzt (Max. Ladeleistung).`);
// Maximale Entladeleistung setzen (entladeleistung erzwingen)
await setStateAsync(MODBUS_SET_DISCHARGE_POWER_PERCENT_OID, MAX_DISCHARGE_POWER_VALUE, false);
LOG(`Modbus-Register ${MODBUS_SET_DISCHARGE_POWER_PERCENT_OID} auf ${MAX_DISCHARGE_POWER_VALUE} gesetzt (Max. Entladeleistung erzwingen).`);
// Prüf-Intervall starten
chargeInterval = setInterval(checkChargeStatus, CHECK_INTERVAL_MS);
LOG(`Prüf-Intervall gestartet (alle ${CHECK_INTERVAL_MS / 1000} Sekunden).`);
// Erste Prüfung sofort ausführen
await checkChargeStatus();
} catch (e) {
console.error(`[NightCharge] Fehler beim Starten des Ladevorgangs via Modbus: ${e}. Breche ab.`);
// Versuch, den Normalzustand wiederherzustellen
await stopCharging("Fehler beim Starten der Ladung");
}
}
// --- ZEITPLANUNG (SCHEDULES) ---
// Trigger um StartTime Uhr morgens, um den Ladevorgang zu starten
schedule(cronScheduleStart, async () => {
await chargeBatteryBasedOnForecast();
});
LOG(`Schedule für Lade-Start um ${StartTime}:00 Uhr erstellt.`);
// Sicherheits-Trigger um 6:00 Uhr morgens, um sicherzustellen, dass die Ladung beendet wird
schedule(cronScheduleStop, async () => {
LOG(`Sicherheits-Check um ${StopTime}:00 Uhr: Beende eventuell laufenden Ladevorgang.`);
await stopCharging(`Planmäßiges Ende des Ladefensters (${StopTime}:00 Uhr)`);
});
LOG(`Schedule für Lade-Stopp um ${StopTime}:00 Uhr erstellt.`);
// --- SKRIPTSTART ---
LOG("Skript 'Nächtliche Batterieladung nach Forecast' gestartet.");
// Optional: Beim Skriptstart sicherstellen, dass der Normalzustand aktiv ist,
// falls das Skript zwischen StartTime (4:00) und StopTime (6:00) Uhr neu gestartet wird.
const currentHourNow = new Date().getHours();
if (currentHourNow < StartTime || currentHourNow >= StopTime) {
LOG("Skriptstart außerhalb des Ladefensters. Stelle Normalbetrieb sicher.");
stopCharging("Skriptstart außerhalb Ladefenster");
} else {
LOG(`Skriptstart *innerhalb* des Ladefensters (${StartTime}:00-${StopTime}:00 Uhr). Der ${StartTime}:00-Uhr-Trigger wird den Prozess starten/übernehmen.`);
// Hier könnte man überlegen, ob ein laufender Prozess wieder aufgenommen werden soll,
// aber das macht es komplexer. Der 4-Uhr-Trigger sollte ausreichen.
// !!! HIER ZUM TESTEN EINFÜGEN script start erzwingen !!!
//LOG("!!! TEST: Rufe chargeBatteryBasedOnForecast() jetzt manuell auf! !!!");
//chargeBatteryBasedOnForecast(); // ENDE script start erzwingen
}
und hier noch ein Testscript für register schreiben mit modbus:
// Nur zum Testen
const testOID = "modbus.0.holdingRegisters.40348_StorCtl_Mod";
const testValue = 3; // Wert
const DEBUG_MINIMAL = true; // Oder false zum Testen
async function minimalTest() {
if (DEBUG_MINIMAL) console.log(`[MinimalTest] Versuche ${testOID} auf ${testValue} zu setzen...`);
try {
await setStateAsync(testOID, testValue, false); // ack: false ist korrekt
if (DEBUG_MINIMAL) console.log(`[MinimalTest] setStateAsync für ${testOID} erfolgreich abgesetzt.`);
// Optional: Nach kurzer Zeit den Wert prüfen
setTimeout(async () => {
const state = await getStateAsync(testOID);
if (DEBUG_MINIMAL) console.log(`[MinimalTest] Aktueller Wert von ${testOID}: ${JSON.stringify(state)}`);
}, 3000); // 3 Sekunden warten
} catch (err) {
console.error(`[MinimalTest] Fehler beim Setzen von ${testOID}: ${err}`);
}
}
minimalTest();