Dateien nach „wr“ hochladen
This commit is contained in:
parent
46de0d82ed
commit
1a869d5fc4
1 changed files with 746 additions and 0 deletions
746
wr/solarbat1-v3.3.5.js
Normal file
746
wr/solarbat1-v3.3.5.js
Normal 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
|
||||
}
|
||||
];
|
||||
Loading…
Add table
Reference in a new issue