Dateien nach „wr“ hochladen
This commit is contained in:
parent
db82353769
commit
6d133de8aa
1 changed files with 933 additions and 0 deletions
933
wr/solarbat2-wr-unitV4.1.js
Normal file
933
wr/solarbat2-wr-unitV4.1.js
Normal file
|
|
@ -0,0 +1,933 @@
|
|||
// ============================================================
|
||||
// SolarBat WR Control
|
||||
// Version: 4.1
|
||||
//
|
||||
// HM400 Wechselrichtersteuerung
|
||||
//
|
||||
// Unterstützt:
|
||||
//
|
||||
// - AGM / GEL
|
||||
// - LiFePO4
|
||||
//
|
||||
// Eigenschaften:
|
||||
//
|
||||
// - FLOW basierte Architektur
|
||||
// - zentrale UBATT Nutzung
|
||||
// - Dashboard konfigurierbar
|
||||
// - WR SN dynamisch
|
||||
// - Emergency Mode
|
||||
// - Überschusslogik
|
||||
// - MQTT Spam Schutz
|
||||
// - sanfte Leistungsreduktion
|
||||
// - AC Netzteil Priorisierung
|
||||
//
|
||||
// ============================================================
|
||||
//
|
||||
// ARCHITEKTUR
|
||||
// ------------------------------------------------------------
|
||||
//
|
||||
// Verwendete zentrale Variablen:
|
||||
//
|
||||
// flow.UBATT
|
||||
// global.POWERSALDIERT
|
||||
//
|
||||
// Dadurch:
|
||||
//
|
||||
// - unabhängig von MQTT Reihenfolge
|
||||
// - unabhängig von Trigger Topics
|
||||
// - robust gegen Sensor Delays
|
||||
// - professioneller Aufbau
|
||||
//
|
||||
// EMPFOHLEN:
|
||||
//
|
||||
// Inject Node alle 10 Sekunden
|
||||
// triggert diese Function.
|
||||
//
|
||||
// ============================================================
|
||||
//
|
||||
// OUTPUT 1
|
||||
// HM400 LIMIT %
|
||||
//
|
||||
// OUTPUT 2
|
||||
// WR STATUS 0..2
|
||||
//
|
||||
// OUTPUT 3
|
||||
// WR POWER 0/1
|
||||
//
|
||||
// OUTPUT 4
|
||||
// CURRENT LIMIT
|
||||
//
|
||||
// ============================================================
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// BATTERY PROFILES
|
||||
// ============================================================
|
||||
|
||||
const BATTERY_PROFILES = {
|
||||
|
||||
// --------------------------------------------------------
|
||||
// AGM / GEL
|
||||
// --------------------------------------------------------
|
||||
|
||||
AGM: {
|
||||
|
||||
TYPE: "AGM",
|
||||
|
||||
UBATT_ON: 12.0,
|
||||
UBATT_OFF: 13.4,
|
||||
|
||||
UBATT_OVERVOLTAGE: 14.4,
|
||||
UBATT_EMERGENCY: 11.0,
|
||||
|
||||
LIMIT_MIN_ACTIVE: 5,
|
||||
LIMIT_MAX: 15,
|
||||
LIMIT_EMERGENCY: 1,
|
||||
|
||||
LIMIT_STEP: 5,
|
||||
|
||||
REDUCE_DELAY_SEC: 90
|
||||
},
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
// LIFEPO4
|
||||
// --------------------------------------------------------
|
||||
|
||||
LIFEPO4: {
|
||||
|
||||
TYPE: "LIFEPO4",
|
||||
|
||||
UBATT_ON: 12.8,
|
||||
UBATT_OFF: 13.6,
|
||||
|
||||
UBATT_OVERVOLTAGE: 14.3,
|
||||
UBATT_EMERGENCY: 11.9,
|
||||
|
||||
LIMIT_MIN_ACTIVE: 5,
|
||||
LIMIT_MAX: 35,
|
||||
LIMIT_EMERGENCY: 0,
|
||||
|
||||
LIMIT_STEP: 5,
|
||||
|
||||
REDUCE_DELAY_SEC: 30
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// RESET
|
||||
// ============================================================
|
||||
|
||||
if (!flow.get("WR_INIT_DONE")) {
|
||||
|
||||
flow.set("WR_LIMIT", 5);
|
||||
flow.set("WR_POWER", 1);
|
||||
flow.set("WR_LAST_REDUCE", 0);
|
||||
|
||||
flow.set("LAST_SENT_LIMIT", null);
|
||||
flow.set("LAST_SENT_POWER", null);
|
||||
|
||||
flow.set("WR_INIT_DONE", true);
|
||||
|
||||
node.warn("WR Context reset");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// BATTERY TYPE
|
||||
// ============================================================
|
||||
|
||||
let batteryType =
|
||||
|
||||
flow.get("CFG_BATTERY_TYPE")
|
||||
|
||||
||
|
||||
|
||||
"AGM";
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// BATTERY PROFILE
|
||||
// ============================================================
|
||||
|
||||
let batteryProfile =
|
||||
|
||||
BATTERY_PROFILES[batteryType]
|
||||
|
||||
||
|
||||
|
||||
BATTERY_PROFILES.AGM;
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// CONFIG
|
||||
// ============================================================
|
||||
|
||||
const cfg = {
|
||||
|
||||
// --------------------------------------------------------
|
||||
// WR SERIAL
|
||||
// --------------------------------------------------------
|
||||
|
||||
WR_SN:
|
||||
|
||||
flow.get("CFG_WR_SN")
|
||||
|
||||
??
|
||||
|
||||
"112185029661",
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
// BATTERY TYPE
|
||||
// --------------------------------------------------------
|
||||
|
||||
BATTERY_TYPE:
|
||||
batteryProfile.TYPE,
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Batterie
|
||||
// --------------------------------------------------------
|
||||
|
||||
UBATT_ON:
|
||||
|
||||
Number(
|
||||
flow.get("CFG_UBATT_ON")
|
||||
)
|
||||
|
||||
||
|
||||
|
||||
batteryProfile.UBATT_ON,
|
||||
|
||||
|
||||
|
||||
UBATT_OFF:
|
||||
|
||||
Number(
|
||||
flow.get("CFG_UBATT_OFF")
|
||||
)
|
||||
|
||||
||
|
||||
|
||||
batteryProfile.UBATT_OFF,
|
||||
|
||||
|
||||
|
||||
UBATT_OVERVOLTAGE:
|
||||
|
||||
Number(
|
||||
flow.get("CFG_UBATT_OVERVOLTAGE")
|
||||
)
|
||||
|
||||
||
|
||||
|
||||
batteryProfile.UBATT_OVERVOLTAGE,
|
||||
|
||||
|
||||
|
||||
UBATT_EMERGENCY:
|
||||
|
||||
Number(
|
||||
flow.get("CFG_UBATT_EMERGENCY")
|
||||
)
|
||||
|
||||
||
|
||||
|
||||
batteryProfile.UBATT_EMERGENCY,
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Leistung
|
||||
// --------------------------------------------------------
|
||||
|
||||
LIMIT_MIN_ACTIVE:
|
||||
|
||||
Number(
|
||||
flow.get("CFG_LIMIT_MIN_ACTIVE")
|
||||
)
|
||||
|
||||
||
|
||||
|
||||
batteryProfile.LIMIT_MIN_ACTIVE,
|
||||
|
||||
|
||||
|
||||
LIMIT_MAX:
|
||||
|
||||
Number(
|
||||
flow.get("CFG_LIMIT_MAX")
|
||||
)
|
||||
|
||||
||
|
||||
|
||||
batteryProfile.LIMIT_MAX,
|
||||
|
||||
|
||||
|
||||
LIMIT_STEP:
|
||||
|
||||
Number(
|
||||
flow.get("CFG_LIMIT_STEP")
|
||||
)
|
||||
|
||||
||
|
||||
|
||||
batteryProfile.LIMIT_STEP,
|
||||
|
||||
|
||||
|
||||
LIMIT_EMERGENCY:
|
||||
|
||||
Number(
|
||||
flow.get("CFG_LIMIT_EMERGENCY")
|
||||
)
|
||||
|
||||
||
|
||||
|
||||
batteryProfile.LIMIT_EMERGENCY,
|
||||
|
||||
|
||||
|
||||
REDUCE_DELAY_SEC:
|
||||
|
||||
Number(
|
||||
flow.get("CFG_REDUCE_DELAY_SEC")
|
||||
)
|
||||
|
||||
||
|
||||
|
||||
batteryProfile.REDUCE_DELAY_SEC
|
||||
};
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// UBATT
|
||||
// FLOW BASIERT
|
||||
// ============================================================
|
||||
|
||||
let ubatt =
|
||||
|
||||
Number(
|
||||
flow.get("UBATT")
|
||||
);
|
||||
|
||||
|
||||
|
||||
if (isNaN(ubatt)) {
|
||||
|
||||
node.warn("WR: UBATT invalid");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// GLOBALER NETZSALDO
|
||||
// ============================================================
|
||||
|
||||
let powersaldiert =
|
||||
|
||||
Number(
|
||||
global.get("POWERSALDIERT")
|
||||
);
|
||||
|
||||
|
||||
|
||||
if (isNaN(powersaldiert)) {
|
||||
|
||||
powersaldiert = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// STATES
|
||||
// ============================================================
|
||||
|
||||
let wrPower =
|
||||
Number(flow.get("WR_POWER"));
|
||||
|
||||
|
||||
|
||||
if (isNaN(wrPower)) {
|
||||
|
||||
wrPower = 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
let currentLimit =
|
||||
Number(flow.get("WR_LIMIT"));
|
||||
|
||||
|
||||
|
||||
if (isNaN(currentLimit)) {
|
||||
|
||||
currentLimit =
|
||||
cfg.LIMIT_MIN_ACTIVE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
let lastReduce =
|
||||
Number(flow.get("WR_LAST_REDUCE"));
|
||||
|
||||
|
||||
|
||||
if (isNaN(lastReduce)) {
|
||||
|
||||
lastReduce = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
let now =
|
||||
Date.now();
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// EMERGENCY MODE
|
||||
// ============================================================
|
||||
|
||||
let emergencyMode = false;
|
||||
|
||||
|
||||
|
||||
if (ubatt <= cfg.UBATT_EMERGENCY) {
|
||||
|
||||
emergencyMode = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// WR POWER
|
||||
// ============================================================
|
||||
|
||||
wrPower = 0;
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// ÜBERSPANNUNG
|
||||
// Batterie aktiv entlasten
|
||||
// ============================================================
|
||||
|
||||
if (ubatt >= cfg.UBATT_OVERVOLTAGE) {
|
||||
|
||||
currentLimit =
|
||||
cfg.LIMIT_MAX;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// NETZEINSPEISUNG
|
||||
// Keine zusätzliche Einspeisung
|
||||
// ============================================================
|
||||
|
||||
else if (powersaldiert < 0) {
|
||||
|
||||
currentLimit =
|
||||
cfg.LIMIT_MIN_ACTIVE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// REGELBEREICH
|
||||
// ============================================================
|
||||
|
||||
else {
|
||||
|
||||
let span =
|
||||
|
||||
cfg.UBATT_OFF
|
||||
-
|
||||
cfg.UBATT_ON;
|
||||
|
||||
|
||||
|
||||
if (span <= 0) {
|
||||
|
||||
span = 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
// FAKTOR
|
||||
// --------------------------------------------------------
|
||||
|
||||
let factor =
|
||||
|
||||
(
|
||||
ubatt
|
||||
-
|
||||
cfg.UBATT_ON
|
||||
)
|
||||
|
||||
/
|
||||
|
||||
span;
|
||||
|
||||
|
||||
|
||||
factor = Math.max(
|
||||
0,
|
||||
Math.min(1, factor)
|
||||
);
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
// exponentielle Kennlinie
|
||||
// --------------------------------------------------------
|
||||
|
||||
factor =
|
||||
Math.pow(factor, 0.7);
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Zielwert
|
||||
// --------------------------------------------------------
|
||||
|
||||
let targetLimit =
|
||||
|
||||
cfg.LIMIT_MIN_ACTIVE +
|
||||
|
||||
(
|
||||
|
||||
factor *
|
||||
|
||||
(
|
||||
|
||||
cfg.LIMIT_MAX
|
||||
-
|
||||
cfg.LIMIT_MIN_ACTIVE
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Schrittweite
|
||||
// --------------------------------------------------------
|
||||
|
||||
targetLimit =
|
||||
|
||||
Math.round(
|
||||
targetLimit
|
||||
/
|
||||
cfg.LIMIT_STEP
|
||||
)
|
||||
|
||||
*
|
||||
|
||||
cfg.LIMIT_STEP;
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Clamp
|
||||
// --------------------------------------------------------
|
||||
|
||||
targetLimit = Math.max(
|
||||
|
||||
cfg.LIMIT_MIN_ACTIVE,
|
||||
|
||||
Math.min(
|
||||
cfg.LIMIT_MAX,
|
||||
targetLimit
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
// sofort erhöhen
|
||||
// --------------------------------------------------------
|
||||
|
||||
if (targetLimit > currentLimit) {
|
||||
|
||||
currentLimit =
|
||||
targetLimit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
// verzögert reduzieren
|
||||
// --------------------------------------------------------
|
||||
|
||||
if (targetLimit < currentLimit) {
|
||||
|
||||
if (
|
||||
|
||||
(
|
||||
now - lastReduce
|
||||
)
|
||||
|
||||
>=
|
||||
|
||||
(
|
||||
cfg.REDUCE_DELAY_SEC
|
||||
* 1000
|
||||
)
|
||||
|
||||
) {
|
||||
|
||||
currentLimit =
|
||||
targetLimit;
|
||||
|
||||
flow.set(
|
||||
"WR_LAST_REDUCE",
|
||||
now
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// EMERGENCY MODE
|
||||
// ============================================================
|
||||
|
||||
if (emergencyMode) {
|
||||
|
||||
currentLimit =
|
||||
cfg.LIMIT_EMERGENCY;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// AC STATUS
|
||||
// ============================================================
|
||||
|
||||
let acOn = false;
|
||||
|
||||
|
||||
|
||||
try {
|
||||
|
||||
let root =
|
||||
flow.get("solarbat_002");
|
||||
|
||||
let p = null;
|
||||
|
||||
|
||||
|
||||
if (root?.AC?.POWER !== undefined) {
|
||||
|
||||
p = root.AC.POWER;
|
||||
}
|
||||
|
||||
else if (
|
||||
|
||||
root?.AC?.RESULT?.POWER
|
||||
!== undefined
|
||||
|
||||
) {
|
||||
|
||||
p =
|
||||
root.AC.RESULT.POWER;
|
||||
}
|
||||
|
||||
|
||||
|
||||
acOn = (p === "ON");
|
||||
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
acOn = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// AC aktiv
|
||||
// WR Minimum
|
||||
// ============================================================
|
||||
|
||||
if (acOn && !emergencyMode) {
|
||||
|
||||
currentLimit =
|
||||
cfg.LIMIT_MIN_ACTIVE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// ABSOLUTES MINIMUM
|
||||
// ============================================================
|
||||
|
||||
if (
|
||||
|
||||
currentLimit
|
||||
<
|
||||
cfg.LIMIT_EMERGENCY
|
||||
|
||||
) {
|
||||
|
||||
currentLimit =
|
||||
cfg.LIMIT_EMERGENCY;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// WR STATUS
|
||||
// ============================================================
|
||||
|
||||
let wrStatus = 0;
|
||||
|
||||
|
||||
|
||||
try {
|
||||
|
||||
let dtu =
|
||||
|
||||
flow.get("solarbat_002")
|
||||
?.DTU?.[cfg.WR_SN];
|
||||
|
||||
|
||||
|
||||
let reachable =
|
||||
|
||||
Number(
|
||||
dtu?.status?.reachable
|
||||
)
|
||||
|
||||
||
|
||||
|
||||
0;
|
||||
|
||||
|
||||
|
||||
let producing =
|
||||
|
||||
Number(
|
||||
dtu?.status?.producing
|
||||
)
|
||||
|
||||
||
|
||||
|
||||
0;
|
||||
|
||||
|
||||
|
||||
wrStatus =
|
||||
reachable + producing;
|
||||
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
wrStatus = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// SPEICHERN
|
||||
// ============================================================
|
||||
|
||||
flow.set(
|
||||
"WR_LIMIT",
|
||||
currentLimit
|
||||
);
|
||||
|
||||
flow.set(
|
||||
"WR_POWER",
|
||||
wrPower
|
||||
);
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// STATUS
|
||||
// ============================================================
|
||||
|
||||
node.status({
|
||||
|
||||
fill:
|
||||
|
||||
emergencyMode
|
||||
? "red"
|
||||
|
||||
:
|
||||
|
||||
wrPower
|
||||
? "green"
|
||||
: "grey",
|
||||
|
||||
|
||||
shape: "dot",
|
||||
|
||||
|
||||
text:
|
||||
|
||||
ubatt.toFixed(2)
|
||||
|
||||
+
|
||||
|
||||
"V "
|
||||
|
||||
+
|
||||
|
||||
cfg.BATTERY_TYPE
|
||||
|
||||
+
|
||||
|
||||
" WR="
|
||||
|
||||
+
|
||||
|
||||
wrStatus
|
||||
|
||||
+
|
||||
|
||||
" "
|
||||
|
||||
+
|
||||
|
||||
currentLimit
|
||||
|
||||
+
|
||||
|
||||
"%"
|
||||
|
||||
+
|
||||
|
||||
" "
|
||||
|
||||
+
|
||||
|
||||
powersaldiert
|
||||
|
||||
+
|
||||
|
||||
"W"
|
||||
});
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// MQTT SPAM SCHUTZ
|
||||
// ============================================================
|
||||
|
||||
let lastSentLimit =
|
||||
flow.get("LAST_SENT_LIMIT");
|
||||
|
||||
let lastSentPower =
|
||||
flow.get("LAST_SENT_POWER");
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// OUTPUT 1
|
||||
// LIMIT
|
||||
// ============================================================
|
||||
|
||||
let outLimit = null;
|
||||
|
||||
|
||||
|
||||
if (lastSentLimit !== currentLimit) {
|
||||
|
||||
outLimit = {
|
||||
|
||||
payload:
|
||||
currentLimit
|
||||
};
|
||||
|
||||
flow.set(
|
||||
"LAST_SENT_LIMIT",
|
||||
currentLimit
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// OUTPUT 2
|
||||
// WR STATUS
|
||||
// ============================================================
|
||||
|
||||
let outStatus = {
|
||||
|
||||
payload:
|
||||
wrStatus
|
||||
};
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// OUTPUT 3
|
||||
// WR POWER
|
||||
// ============================================================
|
||||
|
||||
let outPower = null;
|
||||
|
||||
|
||||
|
||||
if (lastSentPower !== wrPower) {
|
||||
|
||||
outPower = {
|
||||
|
||||
payload:
|
||||
wrPower
|
||||
};
|
||||
|
||||
flow.set(
|
||||
"LAST_SENT_POWER",
|
||||
wrPower
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// OUTPUT 4
|
||||
// CURRENT LIMIT
|
||||
// ============================================================
|
||||
|
||||
let outCurrentLimit = {
|
||||
|
||||
payload:
|
||||
currentLimit
|
||||
};
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
// RETURN
|
||||
// ============================================================
|
||||
|
||||
return [
|
||||
|
||||
outLimit,
|
||||
|
||||
outStatus,
|
||||
|
||||
outPower,
|
||||
|
||||
outCurrentLimit
|
||||
];
|
||||
Loading…
Add table
Reference in a new issue