Dateien nach „wr“ hochladen

This commit is contained in:
administrator 2026-05-30 09:27:07 +00:00
parent 46de0d82ed
commit 1a869d5fc4

746
wr/solarbat1-v3.3.5.js Normal file
View file

@ -0,0 +1,746 @@
// ============================================================================
// SolarController v3.3.5-LiFePO4-AGGRESSIVE
// Node-RED Function Node
//
// Änderungen zu v3.3.5
// ------------------------------------------------------------
// - Optimierte LiFePO4 Kennlinie
// - Deutlich praxisnähere Leistungsfreigabe
// - 12.8V erlaubt jetzt höhere WR Leistung
// - GRID EXPORT LOCK bleibt aktiv
// - Ramp-Up bleibt sanft
// - Step-Down bleibt sofort
// - Größere Totzone um 0W Netzfluss
// - Ruhigeres Verhalten bei kleinen Lastwechseln
// - Einspeisungsschutz weiterhin HARD LOCK
//
// Verhalten
// ------------------------------------------------------------
// NORMAL:
// 0 -> 5 -> 10 -> 15 ...
//
// AC AKTIV:
// LIMIT = 0%
//
// NETZEINSPEISUNG:
// LIMIT = 0%
//
// STEP DOWN:
// sofort
//
// ============================================================================
const VERSION = "3.3.5-LiFePO4-AGGRESSIVE";
// ============================================================================
// HILFSFUNKTIONEN
// ============================================================================
function num(v, def = 0) {
const n = Number(v);
return Number.isFinite(n) ? n : def;
}
function bool(v, def = false) {
if (typeof v === "boolean") return v;
if (typeof v === "string") {
const s = v.toLowerCase();
if (["true", "1", "on", "yes"].includes(s)) return true;
if (["false", "0", "off", "no"].includes(s)) return false;
}
if (typeof v === "number") {
return v !== 0;
}
return def;
}
function str(v, def = "") {
return (
v === undefined ||
v === null
)
? def
: String(v);
}
function clamp(v, min, max) {
return Math.max(min, Math.min(max, v));
}
function round1(v) {
return Math.round(v * 10) / 10;
}
function round0(v) {
return Math.round(v);
}
function roundToStep(v, step) {
if (!Number.isFinite(v)) {
return 0;
}
if (
!Number.isFinite(step)
||
step <= 0
) {
return round0(v);
}
return Math.round(v / step) * step;
}
function isNightTime(
now,
startHour,
endHour
) {
const hour =
now.getHours();
if (startHour === endHour) {
return true;
}
if (startHour < endHour) {
return (
hour >= startHour
&&
hour < endHour
);
}
return (
hour >= startHour
||
hour < endHour
);
}
// ============================================================================
// BATTERIEKENNLINIE
// ============================================================================
function getUbattLimitPct(
ubatt,
batteryType
) {
if (!Number.isFinite(ubatt)) {
return 100;
}
// =====================================================
// LIFEPO4
// AGGRESSIVE / PRAXISNAH
// =====================================================
if (batteryType === "LIFEPO4") {
if (ubatt < 12.0) return 0;
if (ubatt < 12.2) return 10;
if (ubatt < 12.4) return 20;
if (ubatt < 12.6) return 30;
if (ubatt < 12.8) return 40;
if (ubatt < 13.0) return 55;
if (ubatt < 13.1) return 70;
if (ubatt < 13.2) return 80;
if (ubatt < 13.3) return 90;
if (ubatt < 13.4) return 95;
return 100;
}
// =====================================================
// AGM / GEL
// =====================================================
if (ubatt < 11.8) return 0;
if (ubatt < 12.0) return 5;
if (ubatt < 12.2) return 10;
if (ubatt < 12.4) return 20;
if (ubatt < 12.6) return 35;
if (ubatt < 12.8) return 50;
if (ubatt < 13.0) return 65;
if (ubatt < 13.2) return 80;
if (ubatt < 13.4) return 90;
if (ubatt < 13.6) return 95;
return 100;
}
// ============================================================================
// ZEIT
// ============================================================================
const now = new Date();
// ============================================================================
// BATTERIETYP
// ============================================================================
const BATTERY_TYPE =
str(
msg.BATTERY_TYPE
??
msg.payload?.BATTERY_TYPE
??
global.get("BATTERY_TYPE"),
"LIFEPO4"
).toUpperCase();
// ============================================================================
// INPUTS
// ============================================================================
const UBATT =
num(
msg.UBATT
??
msg.payload?.UBATT
??
msg.payload
??
global.get("UBATT"),
NaN
);
const HM300MODE =
num(
msg.HM300MODE
??
msg.payload?.HM300MODE
??
global.get("HM300MODE"),
1
);
const POWERSALDIERT =
num(
msg.POWERSALDIERT
??
msg.payload?.POWERSALDIERT
??
global.get("POWERSALDIERT"),
0
);
// ============================================================================
// BATTERIEPARAMETER
// ============================================================================
let UNTERSPANNUNG_EIN = 12.0;
let UNTERSPANNUNG_AUS = 12.4;
let EINSPEISUNGLEVEL = -120;
// AGM/GEL
if (
BATTERY_TYPE === "AGM"
||
BATTERY_TYPE === "GEL"
) {
UNTERSPANNUNG_EIN = 11.8;
UNTERSPANNUNG_AUS = 12.0;
EINSPEISUNGLEVEL = -250;
}
// ============================================================================
// OVERRIDES
// ============================================================================
UNTERSPANNUNG_EIN =
num(
global.get("UNTERSPANNUNG_EIN"),
UNTERSPANNUNG_EIN
);
UNTERSPANNUNG_AUS =
num(
global.get("UNTERSPANNUNG_AUS"),
UNTERSPANNUNG_AUS
);
EINSPEISUNGLEVEL =
num(
global.get("EINSPEISUNGLEVEL"),
EINSPEISUNGLEVEL
);
// ============================================================================
// NACHTMODUS
// ============================================================================
const NIGHTMODE_START_HOUR =
num(
global.get("NIGHTMODE_START_HOUR"),
21
);
const NIGHTMODE_END_HOUR =
num(
global.get("NIGHTMODE_END_HOUR"),
6
);
const nightModeActive =
isNightTime(
now,
NIGHTMODE_START_HOUR,
NIGHTMODE_END_HOUR
);
// ============================================================================
// LIMIT PARAMETER
// ============================================================================
const LIMIT_MIN_PCT =
num(
global.get("LIMIT_MIN_PCT"),
0
);
const LIMIT_MAX_PCT =
num(
global.get("LIMIT_MAX_PCT"),
100
);
const LIMIT_STEP =
num(
global.get("LIMIT_STEP"),
5
);
const ManualPowerLimitPct =
num(
global.get("ManualPowerLimitPct"),
0
);
// ============================================================================
// AC STATUS
// ============================================================================
let SolarBatterieACLoad =
str(
global.get("SolarBatterieACLoad"),
"OFF"
);
// ============================================================================
// UNTERSPANNUNG
// ============================================================================
let UnterspannungAktiv =
bool(
global.get("UnterspannungAktiv"),
false
);
// ============================================================================
// UNTERSPANNUNGSLOGIK
// ============================================================================
if (Number.isFinite(UBATT)) {
if (
!UnterspannungAktiv
&&
UBATT <= UNTERSPANNUNG_EIN
) {
UnterspannungAktiv = true;
}
else if (
UnterspannungAktiv
&&
UBATT >= UNTERSPANNUNG_AUS
) {
UnterspannungAktiv = false;
}
}
// ============================================================================
// LIMIT BERECHNUNG
// ============================================================================
let LIMIT = 0;
let REGELFALL = "";
// UNTERSPANNUNG
if (UnterspannungAktiv) {
LIMIT = 0;
REGELFALL =
"Unterspannung";
}
// AC AKTIV
else if (
SolarBatterieACLoad === "ON"
) {
LIMIT = 0;
REGELFALL =
"ACLOAD aktiv";
}
// MANUELL
else if (HM300MODE === 0) {
LIMIT =
clamp(
roundToStep(
ManualPowerLimitPct,
LIMIT_STEP
),
LIMIT_MIN_PCT,
LIMIT_MAX_PCT
);
REGELFALL =
"Manuell";
}
// AUTOMATIK
else {
LIMIT =
clamp(
roundToStep(
getUbattLimitPct(
UBATT,
BATTERY_TYPE
),
LIMIT_STEP
),
LIMIT_MIN_PCT,
LIMIT_MAX_PCT
);
REGELFALL =
"Automatik";
}
// ============================================================================
// NACHTMODUS
// ============================================================================
if (nightModeActive) {
LIMIT =
Math.min(
LIMIT,
30
);
REGELFALL =
"Nachtmodus";
}
// ============================================================================
// GRID EXPORT PROTECTION
// ============================================================================
if (POWERSALDIERT < EINSPEISUNGLEVEL) {
LIMIT = 0;
REGELFALL =
"GRID EXPORT LOCK";
context.set(
"actualLimit",
0
);
context.set(
"lastRampTs",
Math.floor(Date.now() / 1000)
);
}
// ============================================================================
// SANFTER RAMP UP
// ============================================================================
const RAMP_UP_STEP = 5;
const RAMP_UP_INTERVAL_SEC = 60;
let actualLimit =
Number(
context.get("actualLimit")
);
if (isNaN(actualLimit)) {
actualLimit = LIMIT;
}
let lastRampTs =
Number(
context.get("lastRampTs")
);
if (isNaN(lastRampTs)) {
lastRampTs = 0;
}
let nowSec =
Math.floor(Date.now() / 1000);
// STEP DOWN SOFORT
if (LIMIT < actualLimit) {
actualLimit = LIMIT;
lastRampTs = nowSec;
}
// STEP UP LANGSAM
else if (LIMIT > actualLimit) {
if (
(
nowSec - lastRampTs
)
>=
RAMP_UP_INTERVAL_SEC
) {
actualLimit += RAMP_UP_STEP;
if (actualLimit > LIMIT) {
actualLimit = LIMIT;
}
lastRampTs = nowSec;
}
}
LIMIT = actualLimit;
// ============================================================================
// CONTEXT
// ============================================================================
context.set(
"actualLimit",
actualLimit
);
context.set(
"lastRampTs",
lastRampTs
);
// ============================================================================
// FINAL HARD LOCKS
// ============================================================================
if (SolarBatterieACLoad === "ON") {
LIMIT = 0;
REGELFALL =
"ACLOAD HARD LOCK";
}
if (POWERSALDIERT < EINSPEISUNGLEVEL) {
LIMIT = 0;
REGELFALL =
"GRID EXPORT HARD LOCK";
}
// ============================================================================
// GLOBALS
// ============================================================================
global.set(
"LIMIT",
LIMIT
);
global.set(
"REGELFALL",
REGELFALL
);
global.set(
"UnterspannungAktiv",
UnterspannungAktiv
);
// ============================================================================
// OUTPUTS
// ============================================================================
const controlPayload = {
version: VERSION,
timestamp:
new Date().toISOString(),
BATTERY_TYPE,
LIMIT,
REGELFALL,
UBATT,
POWERSALDIERT,
EINSPEISUNGLEVEL,
SolarBatterieACLoad,
UnterspannungAktiv
};
const debugPayload = {
version: VERSION,
timestamp:
new Date().toISOString(),
BATTERY_TYPE,
UBATT:
Number.isFinite(UBATT)
? round1(UBATT)
: "NaN",
POWERSALDIERT:
round1(POWERSALDIERT),
EINSPEISUNGLEVEL,
LIMIT,
REGELFALL,
SolarBatterieACLoad,
UnterspannungAktiv,
actualLimit,
lastRampTs
};
// ============================================================================
// STATUS
// ============================================================================
node.status({
fill:
LIMIT > 0
? "green"
:
(
SolarBatterieACLoad === "ON"
? "yellow"
: "grey"
),
shape: "dot",
text:
`${BATTERY_TYPE} | ` +
`${REGELFALL} | ` +
`UBATT ${round1(UBATT)}V | ` +
`GRID ${round1(POWERSALDIERT)}W | ` +
`LIMIT ${LIMIT}%`
});
// ============================================================================
// RETURN
// ============================================================================
return [
{
payload: controlPayload
},
{
payload: debugPayload
}
];