Dateien nach „AC“ hochladen

This commit is contained in:
administrator 2026-05-30 09:30:21 +00:00
parent 6d133de8aa
commit 8c0f201b32

View file

@ -0,0 +1,977 @@
// =======================================================
// SolarBat AC UNIT
// Version: 2.1
//
// Historie
// -------------------------------------------------------
// 2.1
// - AC_POWER Logging ergänzt
// - AC Leistung im Status integriert
// - AC Leistung im Status Output ergänzt
// - DB Logging erweitert
// - Mehrfach-INSERT vorbereitet
// - Split-Node kompatibel
//
// 2.0
// - AGM / GEL Unterstützung
// - LiFePO4 Unterstützung
// - AUTO / ON / OFF
// - Dashboard konfigurierbar
// - Überschusslogik
// - Emergency Mode
// - Mindestlaufzeit
// - MQTT Spam Schutz
// - DB Logging
//
// =======================================================
//
// FUNKTIONSÜBERSICHT
// -------------------------------------------------------
//
// Diese Steuerung regelt ein AC-Netzteil zum Laden
// einer Batterie abhängig von:
//
// - Batterietyp
// - Batteriespannung
// - Netzsaldo / Überschuss
// - Emergency Bedingungen
// - Betriebsmodus
//
// Zusätzlich werden Zustände und Messwerte
// in MariaDB protokolliert.
//
// =======================================================
//
// BATTERIEPROFILE
// -------------------------------------------------------
//
// Unterstützt:
//
// - AGM / GEL
// - LIFEPO4
//
// Profile können dynamisch per Dashboard
// überschrieben werden.
//
// =======================================================
//
// BETRIEBSMODI
// -------------------------------------------------------
//
// AUTO
// Automatische Regelung
//
// ON
// Netzteil dauerhaft EIN
//
// OFF
// Netzteil dauerhaft AUS
//
// =======================================================
//
// AUTO LOGIK
// -------------------------------------------------------
//
// Einschalten:
//
// - UBATT < UBATT_MIN
// - ausreichend Überschuss vorhanden
//
// Ausschalten:
//
// - UBATT >= UBATT_MAX
// - Mindestlaufzeit erreicht
//
// Emergency:
//
// - UBATT <= UBATT_EMERGENCY
// - Netzteil wird sofort aktiviert
//
// =======================================================
//
// ÜBERSCHUSSLOGIK
// -------------------------------------------------------
//
// POWERSALDIERT:
//
// Negativ
// -> Einspeisung / Überschuss
//
// Positiv
// -> Netzbezug
//
// Beispiel:
//
// -200W
// -> 200W Überschuss vorhanden
//
// =======================================================
//
// AC POWER LOGGING
// -------------------------------------------------------
//
// Zusätzlich wird die Leistungsaufnahme
// des AC Netzteils protokolliert.
//
// Erwartet:
//
// global.set("AC_POWER", value);
//
// Typischer Sensor:
//
// - Shelly Plug
// - Nous A8T
// - Tasmota ENERGY.Power
//
// =======================================================
//
// OUTPUTS
// -------------------------------------------------------
//
// OUTPUT 1
// MQTT POWER
//
// OUTPUT 2
// STATUS / DASHBOARD
//
// OUTPUT 3
// DB LOGGING
//
// =======================================================
//
// HINWEIS ZU OUTPUT 3
// -------------------------------------------------------
//
// OUTPUT 3 liefert ein ARRAY mit mehreren
// SQL INSERT Statements.
//
// Empfohlen:
//
// AC UNIT
// ↓
// split
// ↓
// mysql
//
// =======================================================
// =======================================================
// BATTERY PROFILES
// =======================================================
const BATTERY_PROFILES = {
// ---------------------------------------------------
// AGM / GEL
// ---------------------------------------------------
AGM: {
TYPE: "AGM",
AC_UBATT_MIN: 11.5,
AC_UBATT_MAX: 14.1,
AC_UBATT_EMERGENCY: 11.0,
AC_MIN_RUNTIME: 300,
AC_POWER_SURPLUS_THRESHOLD: -200
},
// ---------------------------------------------------
// LIFEPO4
// ---------------------------------------------------
LIFEPO4: {
TYPE: "LIFEPO4",
AC_UBATT_MIN: 12.4,
AC_UBATT_MAX: 14.0,
AC_UBATT_EMERGENCY: 11.9,
AC_MIN_RUNTIME: 180,
AC_POWER_SURPLUS_THRESHOLD: -250
}
};
// =======================================================
// BATTERY TYPE
// =======================================================
let batteryType =
flow.get("CFG_BATTERY_TYPE")
||
"AGM";
// =======================================================
// BATTERY PROFILE
// =======================================================
let batteryProfile =
BATTERY_PROFILES[batteryType]
||
BATTERY_PROFILES.AGM;
// =======================================================
// CONFIG
// =======================================================
var cfg = {
BATTERY_TYPE:
batteryProfile.TYPE,
// ---------------------------------------------------
// Spannung
// ---------------------------------------------------
UBATT_MIN:
Number(
flow.get("CFG_AC_UBATT_MIN")
)
||
batteryProfile.AC_UBATT_MIN,
UBATT_MAX:
Number(
flow.get("CFG_AC_UBATT_MAX")
)
||
batteryProfile.AC_UBATT_MAX,
UBATT_EMERGENCY:
Number(
flow.get("CFG_AC_UBATT_EMERGENCY")
)
||
batteryProfile.AC_UBATT_EMERGENCY,
// ---------------------------------------------------
// Laufzeit
// ---------------------------------------------------
MIN_RUNTIME:
Number(
flow.get("CFG_AC_MIN_RUNTIME")
)
||
batteryProfile.AC_MIN_RUNTIME,
// ---------------------------------------------------
// Überschuss
// ---------------------------------------------------
POWER_SURPLUS_THRESHOLD:
Number(
flow.get(
"CFG_AC_POWER_SURPLUS_THRESHOLD"
)
)
||
batteryProfile.AC_POWER_SURPLUS_THRESHOLD
};
// =======================================================
// INPUTS
// =======================================================
var ubatt = null;
var modeInput = null;
// -------------------------------------------------------
// Batteriespannung
// -------------------------------------------------------
if (typeof msg.payload === "number") {
ubatt = msg.payload;
}
// -------------------------------------------------------
// Mode
// -------------------------------------------------------
if (msg.topic === "mode") {
modeInput = msg.payload;
}
// =======================================================
// GLOBALER NETZSALDO
// =======================================================
var powersaldiert =
Number(
global.get("POWERSALDIERT")
);
if (isNaN(powersaldiert)) {
powersaldiert = 0;
}
// =======================================================
// AC POWER
// Leistung des AC Netzteils
// =======================================================
var acPower =
Number(
global.get("AC_POWER")
);
if (isNaN(acPower)) {
acPower = 0;
}
// =======================================================
// CONTEXT
// =======================================================
var state =
context.get("state")
||
"OFF";
var mode =
context.get("mode")
||
"auto";
var lastMode =
context.get("lastMode")
||
mode;
var lastChange =
context.get("lastChange")
||
0;
var now =
Math.floor(Date.now() / 1000);
// =======================================================
// MODE UMSCHALTUNG
// =======================================================
var forceOffOnAuto = false;
if (
modeInput === "auto"
||
modeInput === "on"
||
modeInput === "off"
) {
mode = modeInput;
context.set(
"mode",
mode
);
// ---------------------------------------------------
// AUTO
// ---------------------------------------------------
if (
mode === "auto"
&&
lastMode !== "auto"
) {
state = "OFF";
context.set(
"lastChange",
now
);
forceOffOnAuto = true;
}
// ---------------------------------------------------
// MANUELL ON
// ---------------------------------------------------
else if (mode === "on") {
state = "ON";
context.set(
"lastChange",
now
);
}
// ---------------------------------------------------
// MANUELL OFF
// ---------------------------------------------------
else if (mode === "off") {
state = "OFF";
context.set(
"lastChange",
now
);
}
context.set(
"lastMode",
mode
);
}
// =======================================================
// AUTO LOGIK
// =======================================================
if (
mode === "auto"
&&
ubatt !== null
&&
!forceOffOnAuto
) {
var targetState =
state;
// ===================================================
// EMERGENCY MODE
// Batterie MUSS geladen werden
// ===================================================
if (
ubatt <=
cfg.UBATT_EMERGENCY
) {
targetState = "ON";
}
// ===================================================
// NORMALE REGELUNG
// ===================================================
else {
// -----------------------------------------------
// Einschalten
// Nur wenn:
// - Batterie leer genug
// - Überschuss vorhanden
// -----------------------------------------------
if (
ubatt < cfg.UBATT_MIN
&&
powersaldiert
<=
cfg.POWER_SURPLUS_THRESHOLD
) {
targetState = "ON";
}
// -----------------------------------------------
// Ausschalten
// -----------------------------------------------
else if (
ubatt >= cfg.UBATT_MAX
) {
targetState = "OFF";
}
}
// ===================================================
// SCHALTLOGIK
// ===================================================
if (targetState !== state) {
// -----------------------------------------------
// Einschalten sofort
// -----------------------------------------------
if (targetState === "ON") {
state = "ON";
context.set(
"lastChange",
now
);
}
// -----------------------------------------------
// Ausschalten verzögert
// -----------------------------------------------
else if (
targetState === "OFF"
) {
if (
(
now - lastChange
)
>=
cfg.MIN_RUNTIME
) {
state = "OFF";
context.set(
"lastChange",
now
);
}
}
}
}
// =======================================================
// CONTEXT SPEICHERN
// =======================================================
context.set(
"state",
state
);
// =======================================================
// STATUS
// =======================================================
node.status({
fill:
state === "ON"
? "green"
: "grey",
shape: "dot",
text:
mode.toUpperCase()
+
" "
+
ubatt?.toFixed(2)
+
"V "
+
powersaldiert
+
"W AC:"
+
acPower
+
"W "
+
cfg.BATTERY_TYPE
});
// =======================================================
// OUTPUT 1
// MQTT
// =======================================================
var lastSentState =
context.get("lastSentState");
var out1 = null;
if (forceOffOnAuto) {
out1 = {
payload: "OFF"
};
context.set(
"lastSentState",
"OFF"
);
}
else if (
state !== lastSentState
) {
out1 = {
payload: state
};
context.set(
"lastSentState",
state
);
}
// =======================================================
// OUTPUT 2
// STATUS
// =======================================================
var out2 = {
payload: {
mode: mode,
state: state,
ubatt: ubatt,
powersaldiert: powersaldiert,
battery: cfg.BATTERY_TYPE,
acPower: acPower
}
};
// =======================================================
// OUTPUT 3
// DB LOGGING
// =======================================================
var out3 = null;
if (ubatt !== null) {
var lastUbatt =
context.get("lastUbatt");
var DELTA = 0.05;
var changed = false;
if (
lastUbatt === undefined
) {
changed = true;
}
else if (
Math.abs(
ubatt - lastUbatt
)
>=
DELTA
) {
changed = true;
}
context.set(
"lastUbatt",
ubatt
);
if (changed) {
var nowDate =
new Date();
var datum =
nowDate
.toISOString()
.slice(0, 10);
var zeit =
nowDate
.toTimeString()
.slice(0, 8);
out3 = [
// -------------------------------------------------
// BATTERIEVOLTAGE
// -------------------------------------------------
{
topic:
"INSERT INTO sensordata " +
"(DATUM, Zeit, SENSORID, SCHLUESSEL, WERT, AGG) " +
"VALUES (?, ?, ?, ?, ?, ?)",
payload: [
datum,
zeit,
"solarbatt2",
"BATTERIEVOLTAGE",
ubatt,
""
]
},
// -------------------------------------------------
// AC POWER
// -------------------------------------------------
{
topic:
"INSERT INTO sensordata " +
"(DATUM, Zeit, SENSORID, SCHLUESSEL, WERT, AGG) " +
"VALUES (?, ?, ?, ?, ?, ?)",
payload: [
datum,
zeit,
"solarbatt2",
"AC_POWER",
acPower,
""
]
}
];
}
}
// =======================================================
// RETURN
// =======================================================
return [
out1,
out2,
out3
];