@atelieruldetehnologii/node-red-custome-solutions 0.62.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +56 -0
- package/PROPRIETARY-LICENSE.txt +157 -0
- package/README.md +105 -0
- package/TERMS-AND-CONDITIONS.md +311 -0
- package/advantech-read/advantech-read.html +775 -0
- package/advantech-read/advantech-read.js +449 -0
- package/advantech-write/advantech-write.html +413 -0
- package/advantech-write/advantech-write.js +338 -0
- package/bess-control/bess-control.html +569 -0
- package/bess-control/bess-control.js +541 -0
- package/bess-control/lib/allocation.js +149 -0
- package/bess-control/lib/batteries.js +86 -0
- package/bess-control/lib/helpers.js +157 -0
- package/bess-control/lib/messages.js +131 -0
- package/bess-control/lib/runtime.js +156 -0
- package/carlo-gavazzi/carlo-gavazzi.html +4327 -0
- package/carlo-gavazzi/carlo-gavazzi.js +3081 -0
- package/control-export/control-export.html +3644 -0
- package/control-export/control-export.js +2192 -0
- package/control-reactive/control-reactive.html +3136 -0
- package/control-reactive/control-reactive.js +2124 -0
- package/fronius-emulator/README.md +258 -0
- package/fronius-emulator/fronius-emulator.html +170 -0
- package/fronius-emulator/fronius-emulator.js +520 -0
- package/fronius-emulator/lib/binary-codec.js +65 -0
- package/fronius-emulator/lib/fronius-register-map.js +185 -0
- package/fronius-emulator/lib/modbus-parser.js +61 -0
- package/fronius-emulator/lib/modbus-tcp-server.js +224 -0
- package/fronius-emulator/lib/validation.js +206 -0
- package/fronius-emulator/lib/virtual-meter.js +132 -0
- package/fronius-read/fronius-read.html +3944 -0
- package/fronius-read/fronius-read.js +1466 -0
- package/fronius-read/lib/inverter-profiles.js +711 -0
- package/fronius-read/lib/sunspec-core.js +303 -0
- package/fronius-read/lib/sunspec-model-parsers.js +845 -0
- package/fronius-write/fronius-write.html +1789 -0
- package/fronius-write/fronius-write.js +2531 -0
- package/influxdb/influxdb.html +1077 -0
- package/influxdb/influxdb.js +1487 -0
- package/janitza/janitza.html +2562 -0
- package/janitza/janitza.js +1500 -0
- package/janitza/lib/janitza-profiles.js +371 -0
- package/lib/a3-test-server.js +199 -0
- package/lib/adam-6000-profiles.js +173 -0
- package/lib/licensing/constants.js +103 -0
- package/lib/licensing/device-id.js +251 -0
- package/lib/licensing/errors.js +21 -0
- package/lib/licensing/license-client.js +377 -0
- package/lib/licensing/license-state.js +50 -0
- package/lib/licensing/node-guard.js +150 -0
- package/lib/licensing/package-integrity.js +167 -0
- package/lib/licensing/package-integrity.json +513 -0
- package/lib/licensing/public-key.pem +3 -0
- package/lib/licensing/public-key.pem.example +2 -0
- package/lib/licensing/public-keys.js +18 -0
- package/lib/licensing/storage.js +63 -0
- package/lib/licensing/token-verifier.js +84 -0
- package/lib/numbers.js +26 -0
- package/license/atelier-license.html +88 -0
- package/license/atelier-license.js +170 -0
- package/modbus-tcp-discover/modbus-tcp-discover.html +190 -0
- package/modbus-tcp-discover/modbus-tcp-discover.js +706 -0
- package/multi-battery/multi-battery.html +649 -0
- package/multi-battery/multi-battery.js +668 -0
- package/multi-grid/multi-grid.html +745 -0
- package/multi-grid/multi-grid.js +1392 -0
- package/multi-inverter/lib/engine-core.js +237 -0
- package/multi-inverter/multi-inverter.html +2674 -0
- package/multi-inverter/multi-inverter.js +1393 -0
- package/package.json +109 -0
- package/phoenix-contact/lib/eem-ma370-advanced.js +175 -0
- package/phoenix-contact/lib/eem-ma370.js +138 -0
- package/phoenix-contact/lib/integrations.js +149 -0
- package/phoenix-contact/lib/phoenix-contact-profile.js +35030 -0
- package/phoenix-contact/phoenix-contact.html +840 -0
- package/phoenix-contact/phoenix-contact.js +679 -0
- package/pv-generator/pv-generator.html +284 -0
- package/pv-generator/pv-generator.js +387 -0
- package/pylontech-read/pylontech-read.html +763 -0
- package/pylontech-read/pylontech-read.js +518 -0
- package/pylontech-write/pylontech-write.html +405 -0
- package/pylontech-write/pylontech-write.js +546 -0
- package/scada-aggregator/lib/aggregate.js +72 -0
- package/scada-aggregator/lib/groups.js +39 -0
- package/scada-aggregator/scada-aggregator.html +108 -0
- package/scada-aggregator/scada-aggregator.js +117 -0
- package/scada-server/lib/aggregator-map.js +58 -0
- package/scada-server/lib/encoding.js +235 -0
- package/scada-server/lib/modbus-server.js +492 -0
- package/scada-server/lib/object-path.js +18 -0
- package/scada-server/lib/register-map.js +288 -0
- package/scada-server/scada-server.html +1419 -0
- package/scada-server/scada-server.js +704 -0
- package/scripts/check-syntax.js +66 -0
- package/scripts/package-integrity.js +46 -0
- package/scripts/package-release.js +116 -0
- package/scripts/run-tests.js +2178 -0
- package/scripts/tgz-integrity.js +74 -0
- package/sma-read/lib/sunspec-core.js +83 -0
- package/sma-read/lib/sunspec-model-parsers.js +853 -0
- package/sma-read/sma-read.html +659 -0
- package/sma-read/sma-read.js +458 -0
- package/sma-write/sma-write.html +105 -0
- package/sma-write/sma-write.js +270 -0
- package/victron-read/lib/mqtt-state.js +176 -0
- package/victron-read/victron-read.html +1074 -0
- package/victron-read/victron-read.js +1222 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function normalizeBatteries(batteries, batteryCount) {
|
|
5
|
+
|
|
6
|
+
const list = Array.isArray(batteries) ? batteries : [];
|
|
7
|
+
|
|
8
|
+
const count = Math.max(1, Number(batteryCount || list.length || 1));
|
|
9
|
+
|
|
10
|
+
const defaultShare = 100 / count;
|
|
11
|
+
|
|
12
|
+
const normalized = [];
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
for (let index = 0; index < count; index += 1) {
|
|
16
|
+
|
|
17
|
+
const battery = list[index] || {};
|
|
18
|
+
|
|
19
|
+
const socMin = Number.isFinite(Number(battery.socMin)) ? Number(battery.socMin) : 30;
|
|
20
|
+
const rawSocHyst = Number.isFinite(Number(battery.socHystPct))
|
|
21
|
+
? Number(battery.socHystPct)
|
|
22
|
+
: (Number.isFinite(Number(battery.socHyst)) ? Number(battery.socHyst) : 5);
|
|
23
|
+
const socHyst = Math.max(0, rawSocHyst > socMin ? (rawSocHyst - socMin) : rawSocHyst);
|
|
24
|
+
|
|
25
|
+
normalized.push({
|
|
26
|
+
|
|
27
|
+
index: index,
|
|
28
|
+
|
|
29
|
+
id: String(battery.id || ("bat" + (index + 1))).trim(),
|
|
30
|
+
|
|
31
|
+
shareChargePct: Number.isFinite(Number(battery.shareChargePct)) ? Number(battery.shareChargePct) : defaultShare,
|
|
32
|
+
|
|
33
|
+
shareDischargePct: Number.isFinite(Number(battery.shareDischargePct)) ? Number(battery.shareDischargePct) : defaultShare,
|
|
34
|
+
|
|
35
|
+
maxChargeW: Math.max(0, Number(battery.maxChargeW || 150000)),
|
|
36
|
+
|
|
37
|
+
maxDischargeW: Math.max(0, Number(battery.maxDischargeW || 150000)),
|
|
38
|
+
|
|
39
|
+
socMin: socMin,
|
|
40
|
+
|
|
41
|
+
socHyst: socHyst,
|
|
42
|
+
|
|
43
|
+
rampUpW: Math.max(0, Number(battery.rampUpW || 1000)),
|
|
44
|
+
|
|
45
|
+
rampDownW: Math.max(0, Number(battery.rampDownW || 25000)),
|
|
46
|
+
|
|
47
|
+
priorityWeight: Number.isFinite(Number(battery.priorityWeight)) ? Math.max(0, Number(battery.priorityWeight)) : 1
|
|
48
|
+
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
return normalized;
|
|
55
|
+
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
function distributionIsValid(batteries) {
|
|
60
|
+
|
|
61
|
+
const chargeTotal = (Array.isArray(batteries) ? batteries : []).reduce(function (sum, battery) {
|
|
62
|
+
|
|
63
|
+
return sum + Number((battery && battery.shareChargePct) || 0);
|
|
64
|
+
|
|
65
|
+
}, 0);
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
const dischargeTotal = (Array.isArray(batteries) ? batteries : []).reduce(function (sum, battery) {
|
|
69
|
+
|
|
70
|
+
return sum + Number((battery && battery.shareDischargePct) || 0);
|
|
71
|
+
|
|
72
|
+
}, 0);
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
return Math.abs(chargeTotal - 100) <= 0.01 && Math.abs(dischargeTotal - 100) <= 0.01;
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
|
|
82
|
+
distributionIsValid,
|
|
83
|
+
|
|
84
|
+
normalizeBatteries
|
|
85
|
+
|
|
86
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function firstFinite() {
|
|
5
|
+
|
|
6
|
+
for (let i = 0; i < arguments.length; i += 1) {
|
|
7
|
+
|
|
8
|
+
const value = Number(arguments[i]);
|
|
9
|
+
|
|
10
|
+
if (Number.isFinite(value)) {
|
|
11
|
+
|
|
12
|
+
return value;
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return null;
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
function getByPath(source, path) {
|
|
24
|
+
|
|
25
|
+
if (!source || !path) return undefined;
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
return String(path).split(".").reduce(function (acc, key) {
|
|
29
|
+
|
|
30
|
+
return acc != null ? acc[key] : undefined;
|
|
31
|
+
|
|
32
|
+
}, source);
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
function clamp(value, min, max) {
|
|
38
|
+
|
|
39
|
+
return Math.min(Math.max(value, min), max);
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
function normalizeByConvention(value, signConvention) {
|
|
45
|
+
|
|
46
|
+
return Number.isFinite(value)
|
|
47
|
+
|
|
48
|
+
? (signConvention === "positive_export" ? -value : value)
|
|
49
|
+
|
|
50
|
+
: value;
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
function denormalizeByConvention(value, signConvention) {
|
|
56
|
+
|
|
57
|
+
return Number.isFinite(value)
|
|
58
|
+
|
|
59
|
+
? (signConvention === "positive_export" ? -value : value)
|
|
60
|
+
|
|
61
|
+
: value;
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
function parseBooleanLike(value) {
|
|
67
|
+
|
|
68
|
+
if (typeof value === "boolean") return value;
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if (typeof value === "number") {
|
|
72
|
+
|
|
73
|
+
if (value === 85 || value === 1) return true;
|
|
74
|
+
|
|
75
|
+
if (value === 170 || value === 0) return false;
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if (typeof value === "string") {
|
|
81
|
+
|
|
82
|
+
const normalized = value.trim().toLowerCase();
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if (["1", "true", "on", "enable", "enabled", "activ", "active", "auto"].includes(normalized)) {
|
|
86
|
+
|
|
87
|
+
return true;
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if (["0", "false", "off", "disable", "disabled", "dezactiv", "inactive", "manual"].includes(normalized)) {
|
|
93
|
+
|
|
94
|
+
return false;
|
|
95
|
+
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
if (normalized === "85") return true;
|
|
100
|
+
|
|
101
|
+
if (normalized === "170") return false;
|
|
102
|
+
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
return null;
|
|
107
|
+
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
function extractTimestampMs(msg) {
|
|
112
|
+
|
|
113
|
+
const ts = firstFinite(
|
|
114
|
+
|
|
115
|
+
msg && msg.timestamp_ms,
|
|
116
|
+
|
|
117
|
+
msg && msg.timestamp,
|
|
118
|
+
|
|
119
|
+
msg && msg.ts,
|
|
120
|
+
|
|
121
|
+
msg && msg.time,
|
|
122
|
+
|
|
123
|
+
msg && msg.payload && msg.payload.timestamp_ms,
|
|
124
|
+
|
|
125
|
+
msg && msg.payload && msg.payload.timestamp,
|
|
126
|
+
|
|
127
|
+
msg && msg.payload && msg.payload.ts,
|
|
128
|
+
|
|
129
|
+
msg && msg.payload && msg.payload.time
|
|
130
|
+
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
if (ts === null) return Date.now();
|
|
135
|
+
|
|
136
|
+
return ts < 1e11 ? Math.round(ts * 1000) : Math.round(ts);
|
|
137
|
+
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
module.exports = {
|
|
142
|
+
|
|
143
|
+
clamp,
|
|
144
|
+
|
|
145
|
+
denormalizeByConvention,
|
|
146
|
+
|
|
147
|
+
extractTimestampMs,
|
|
148
|
+
|
|
149
|
+
firstFinite,
|
|
150
|
+
|
|
151
|
+
getByPath,
|
|
152
|
+
|
|
153
|
+
normalizeByConvention,
|
|
154
|
+
|
|
155
|
+
parseBooleanLike
|
|
156
|
+
|
|
157
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function isFeedbackMessage(msg) {
|
|
5
|
+
|
|
6
|
+
const status = msg && msg.payload && typeof msg.payload === "object" ? msg.payload.status : null;
|
|
7
|
+
|
|
8
|
+
return Boolean(status && (msg.topic === "pylontech/write" || msg.topic === "pylontech/error"));
|
|
9
|
+
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
function extractGridReference(msg, options) {
|
|
14
|
+
|
|
15
|
+
if (typeof msg.payload === "number") {
|
|
16
|
+
|
|
17
|
+
return Number(msg.payload);
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if (typeof msg.payload === "string" && msg.payload.trim() !== "") {
|
|
23
|
+
|
|
24
|
+
const direct = Number(msg.payload);
|
|
25
|
+
|
|
26
|
+
if (Number.isFinite(direct)) return direct;
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
const payload = msg.payload;
|
|
32
|
+
|
|
33
|
+
if (!payload || typeof payload !== "object") {
|
|
34
|
+
|
|
35
|
+
return null;
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
const byPath = Number(options.getByPath(msg, options.gridPowerPath));
|
|
41
|
+
|
|
42
|
+
if (Number.isFinite(byPath)) {
|
|
43
|
+
|
|
44
|
+
return byPath;
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
return options.firstFinite(
|
|
50
|
+
|
|
51
|
+
payload.grid_reference_w,
|
|
52
|
+
|
|
53
|
+
payload.power_w,
|
|
54
|
+
|
|
55
|
+
payload.pcc_power,
|
|
56
|
+
|
|
57
|
+
payload.grid_power,
|
|
58
|
+
|
|
59
|
+
payload.power,
|
|
60
|
+
|
|
61
|
+
payload.value
|
|
62
|
+
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
function extractBatteryId(msg) {
|
|
69
|
+
|
|
70
|
+
return String(
|
|
71
|
+
|
|
72
|
+
msg.batteryId !== undefined
|
|
73
|
+
|
|
74
|
+
? msg.batteryId
|
|
75
|
+
|
|
76
|
+
: (msg.payload && (msg.payload.batteryId !== undefined ? msg.payload.batteryId : msg.payload.battery_id)) || ""
|
|
77
|
+
|
|
78
|
+
).trim();
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
function extractBatteryTelemetry(msg, options) {
|
|
84
|
+
|
|
85
|
+
const soc = Number(options.getByPath(msg, options.batterySocPath));
|
|
86
|
+
|
|
87
|
+
const externalControlStatus = options.parseBooleanLike(options.getByPath(msg, options.externalControlStatusPath));
|
|
88
|
+
|
|
89
|
+
const alarmLevel = options.getByPath(msg, options.alarmLevelPath);
|
|
90
|
+
|
|
91
|
+
const operatingStatus = options.getByPath(msg, options.operatingStatusPath);
|
|
92
|
+
|
|
93
|
+
const deratingReason = options.getByPath(msg, options.deratingReasonPath);
|
|
94
|
+
|
|
95
|
+
const availableChargeW = options.availableChargePath ? Number(options.getByPath(msg, options.availableChargePath)) : null;
|
|
96
|
+
|
|
97
|
+
const availableDischargeW = options.availableDischargePath ? Number(options.getByPath(msg, options.availableDischargePath)) : null;
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
|
|
102
|
+
soc: Number.isFinite(soc) ? soc : null,
|
|
103
|
+
|
|
104
|
+
externalControlStatus: externalControlStatus,
|
|
105
|
+
|
|
106
|
+
alarmLevel: alarmLevel,
|
|
107
|
+
|
|
108
|
+
operatingStatus: operatingStatus,
|
|
109
|
+
|
|
110
|
+
deratingReason: deratingReason,
|
|
111
|
+
|
|
112
|
+
availableChargeW: Number.isFinite(availableChargeW) ? availableChargeW : null,
|
|
113
|
+
|
|
114
|
+
availableDischargeW: Number.isFinite(availableDischargeW) ? availableDischargeW : null
|
|
115
|
+
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
module.exports = {
|
|
122
|
+
|
|
123
|
+
extractBatteryId,
|
|
124
|
+
|
|
125
|
+
extractBatteryTelemetry,
|
|
126
|
+
|
|
127
|
+
extractGridReference,
|
|
128
|
+
|
|
129
|
+
isFeedbackMessage
|
|
130
|
+
|
|
131
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function healthFactor(alarmLevel, operatingStatus, deratingReason) {
|
|
5
|
+
|
|
6
|
+
let factor = 1;
|
|
7
|
+
|
|
8
|
+
const alarm = Number(alarmLevel);
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if (Number.isFinite(alarm) && alarm > 0) {
|
|
12
|
+
|
|
13
|
+
factor *= Math.max(0.1, 1 - (alarm * 0.2));
|
|
14
|
+
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if (deratingReason !== undefined && deratingReason !== null && String(deratingReason).trim() !== "" && String(deratingReason) !== "0") {
|
|
19
|
+
|
|
20
|
+
factor *= 0.5;
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if (
|
|
26
|
+
|
|
27
|
+
operatingStatus !== undefined &&
|
|
28
|
+
|
|
29
|
+
operatingStatus !== null &&
|
|
30
|
+
|
|
31
|
+
["fault", "error", "offline", "off", "disabled"].includes(String(operatingStatus).trim().toLowerCase())
|
|
32
|
+
|
|
33
|
+
) {
|
|
34
|
+
|
|
35
|
+
factor = 0;
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
return factor;
|
|
41
|
+
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
function isStale(timestampMs, now, timeoutMs) {
|
|
46
|
+
|
|
47
|
+
const timeout = Number(timeoutMs || 0);
|
|
48
|
+
|
|
49
|
+
return Boolean(timeout) && (!Number.isFinite(timestampMs) || (now - timestampMs) > timeout);
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
function evaluateRuntimeBatteries(batteries, batteryState, options, now) {
|
|
55
|
+
|
|
56
|
+
return batteries.map(function (battery) {
|
|
57
|
+
|
|
58
|
+
const state = batteryState[battery.id] || {};
|
|
59
|
+
|
|
60
|
+
const telemetryStale = isStale(state.ts, now, options.staleTimeoutMs);
|
|
61
|
+
|
|
62
|
+
const feedbackStale = !state.writeFeedback || isStale(state.writeFeedback.ts, now, options.feedbackTimeoutMs);
|
|
63
|
+
|
|
64
|
+
const writeError = Boolean(state.writeFeedback && state.writeFeedback.status === "error" && !feedbackStale);
|
|
65
|
+
|
|
66
|
+
const health = healthFactor(state.alarmLevel, state.operatingStatus, state.deratingReason);
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
let dischargeAllowed = true;
|
|
70
|
+
|
|
71
|
+
if (Number.isFinite(state.soc)) {
|
|
72
|
+
|
|
73
|
+
const resumeSoc = Number(battery.socMin || 0) + Math.max(0, Number(battery.socHyst || 0));
|
|
74
|
+
|
|
75
|
+
if (state.soc <= battery.socMin) {
|
|
76
|
+
|
|
77
|
+
dischargeAllowed = false;
|
|
78
|
+
|
|
79
|
+
} else if (state.soc >= resumeSoc) {
|
|
80
|
+
|
|
81
|
+
dischargeAllowed = true;
|
|
82
|
+
|
|
83
|
+
} else if (state.dischargeAllowed !== undefined) {
|
|
84
|
+
|
|
85
|
+
dischargeAllowed = Boolean(state.dischargeAllowed);
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
state.dischargeAllowed = dischargeAllowed;
|
|
90
|
+
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
const excludedReasons = [];
|
|
95
|
+
|
|
96
|
+
if (!Number.isFinite(state.soc)) excludedReasons.push("missing_soc");
|
|
97
|
+
|
|
98
|
+
if (telemetryStale) excludedReasons.push("stale");
|
|
99
|
+
|
|
100
|
+
if (writeError) excludedReasons.push("write_error");
|
|
101
|
+
|
|
102
|
+
if (health <= 0) excludedReasons.push("health_block");
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
|
|
107
|
+
config: battery,
|
|
108
|
+
|
|
109
|
+
state: state,
|
|
110
|
+
|
|
111
|
+
telemetryStale: telemetryStale,
|
|
112
|
+
|
|
113
|
+
feedbackStale: feedbackStale,
|
|
114
|
+
|
|
115
|
+
writeError: writeError,
|
|
116
|
+
|
|
117
|
+
healthFactor: health,
|
|
118
|
+
|
|
119
|
+
dischargeAllowed: dischargeAllowed,
|
|
120
|
+
|
|
121
|
+
availableChargeW: Math.min(
|
|
122
|
+
|
|
123
|
+
battery.maxChargeW,
|
|
124
|
+
|
|
125
|
+
Number.isFinite(state.availableChargeW) ? Math.max(0, state.availableChargeW) : battery.maxChargeW
|
|
126
|
+
|
|
127
|
+
),
|
|
128
|
+
|
|
129
|
+
availableDischargeW: Math.min(
|
|
130
|
+
|
|
131
|
+
battery.maxDischargeW,
|
|
132
|
+
|
|
133
|
+
Number.isFinite(state.availableDischargeW) ? Math.max(0, state.availableDischargeW) : battery.maxDischargeW
|
|
134
|
+
|
|
135
|
+
),
|
|
136
|
+
|
|
137
|
+
excluded: excludedReasons.length > 0,
|
|
138
|
+
|
|
139
|
+
excludedReasons: excludedReasons
|
|
140
|
+
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
module.exports = {
|
|
149
|
+
|
|
150
|
+
evaluateRuntimeBatteries,
|
|
151
|
+
|
|
152
|
+
healthFactor,
|
|
153
|
+
|
|
154
|
+
isStale
|
|
155
|
+
|
|
156
|
+
};
|