iobroker.anker-solix 0.10.13 → 0.10.15
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/README.md +1 -1
- package/admin/i18n/de.json +1 -1
- package/admin/i18n/en.json +1 -1
- package/build/lib/curtailmentPower.js +49 -6
- package/build/lib/curtailmentPower.js.map +2 -2
- package/build/lib/curtailmentRunner.js +31 -99
- package/build/lib/curtailmentRunner.js.map +2 -2
- package/build/lib/curtailmentStates.js +16 -1
- package/build/lib/curtailmentStates.js.map +2 -2
- package/build/lib/types.js.map +1 -1
- package/build/main.js +4 -3
- package/build/main.js.map +2 -2
- package/io-package.json +26 -13
- package/package.json +1 -1
- package/python/bridge.py +21 -13
package/README.md
CHANGED
|
@@ -288,7 +288,7 @@ German guides/videos linked from the [HA README](https://github.com/thomluther/h
|
|
|
288
288
|
|
|
289
289
|
## Curtailment avoidance (optional)
|
|
290
290
|
|
|
291
|
-
Tab **Abregelungsvermeidung** / **Curtailment avoidance**: [solarprognose](https://github.com/ioBroker/ioBroker.solarprognose) detects overproduction days. **
|
|
291
|
+
Tab **Abregelungsvermeidung** / **Curtailment avoidance**: [solarprognose](https://github.com/ioBroker/ioBroker.solarprognose) detects overproduction days. **Controls only:** **manual** mode + **`ac_output_limit`** (AC output / export). **Does not** change station base settings (grid export cap, `allow_grid_export`, home load preset, AC charge limit). **Before:** `ac_output_limit` = live PV. **Active:** `missing_charge_wh`, `max_charge_w` = `missing_charge_wh` ÷ `remaining_hours`, `export_w` = `live_pv_w` − `max_charge_w`, `ac_output_limit` = `export_w`. **After:** restore selected mode. States: `curtailment.live_pv_w`, `missing_charge_wh`, `max_charge_w`, `export_w`, `remaining_hours`.
|
|
292
292
|
|
|
293
293
|
**Admin:** checkbox *Combiner box present* — without combiner: device ID + solarbank type + battery Wh; with combiner: combiner ID + up to **4** solarbank slots (each slot can be *none*). **Combiner:** total AC limit = **sum** of per-unit limits (SB2 **1000** W, SB3 Pro **1200** W, SB4 Pro **2500** W). **Standalone:** always **800** W.
|
|
294
294
|
|
package/admin/i18n/de.json
CHANGED
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"cloud_state": "Cloud-Status",
|
|
75
75
|
"wifi_state": "WLAN-Status",
|
|
76
76
|
"CurtailmentAvoidance": "Abregelungsvermeidung",
|
|
77
|
-
"curtailment_hint": "
|
|
77
|
+
"curtailment_hint": "Nur Benutzerdefiniert + AC-Ausgangsleistung (ac_output_limit). Keine Grundeinstellungen (Einspeise-Limit, allow_grid_export, Hauslast). Active: missing_charge_wh, max_charge_w = missing_wh ÷ remaining_hours, export_w = live_pv_w − max_charge_w. Vorher: export = live_pv_w.",
|
|
78
78
|
"enableCurtailmentAvoidance": "Abregelungsvermeidung aktivieren",
|
|
79
79
|
"curtailmentForecastPath": "Objektpfad Prognose (Basis hourly, z. B. solarprognose.0.forecast.00.hourly)",
|
|
80
80
|
"curtailmentModeAfter": "Nutzungsmodus nach Abregelungsfenster",
|
package/admin/i18n/en.json
CHANGED
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"cloud_state": "Cloud state",
|
|
75
75
|
"wifi_state": "WiFi state",
|
|
76
76
|
"CurtailmentAvoidance": "Curtailment avoidance",
|
|
77
|
-
"curtailment_hint": "
|
|
77
|
+
"curtailment_hint": "Manual mode + ac_output_limit only (no grid export cap / allow_grid_export / home load). Active: missing_charge_wh, max_charge_w = missing_wh ÷ remaining_hours, export_w = live_pv_w − max_charge_w. Before: export = live_pv_w. Requires MQTT.",
|
|
78
78
|
"enableCurtailmentAvoidance": "Enable curtailment avoidance",
|
|
79
79
|
"curtailmentForecastPath": "Forecast base path (hourly, e.g. solarprognose.0.forecast.00.hourly)",
|
|
80
80
|
"curtailmentModeAfter": "Usage mode after curtailment window",
|
|
@@ -21,6 +21,7 @@ __export(curtailmentPower_exports, {
|
|
|
21
21
|
COMBINER_MAX_AC_OUTPUT_W: () => COMBINER_MAX_AC_OUTPUT_W,
|
|
22
22
|
PV_SENSOR_IDS: () => PV_SENSOR_IDS,
|
|
23
23
|
calcMaxChargeW: () => calcMaxChargeW,
|
|
24
|
+
calcMissingChargeWh: () => calcMissingChargeWh,
|
|
24
25
|
isPvGenerationSensor: () => isPvGenerationSensor,
|
|
25
26
|
isPvSensorEntity: () => isPvSensorEntity,
|
|
26
27
|
parsePvSensorStateId: () => parsePvSensorStateId,
|
|
@@ -28,6 +29,7 @@ __export(curtailmentPower_exports, {
|
|
|
28
29
|
pvSensorStatePaths: () => pvSensorStatePaths,
|
|
29
30
|
readLivePvPowerW: () => readLivePvPowerW,
|
|
30
31
|
readPvFromEntities: () => readPvFromEntities,
|
|
32
|
+
readSocPercentForCurtailment: () => readSocPercentForCurtailment,
|
|
31
33
|
resolveActiveExportW: () => resolveActiveExportW,
|
|
32
34
|
resolveBeforeExportW: () => resolveBeforeExportW,
|
|
33
35
|
resolveCurtailmentSetpoints: () => resolveCurtailmentSetpoints,
|
|
@@ -142,20 +144,59 @@ function resolveBeforeExportW(livePvW, forecast, nowHour, window) {
|
|
|
142
144
|
return (0, import_curtailmentForecast.forecastExportTargetW)(forecast, nowHour, window);
|
|
143
145
|
}
|
|
144
146
|
const COMBINER_MAX_AC_OUTPUT_W = 4800;
|
|
145
|
-
function
|
|
146
|
-
if (
|
|
147
|
+
function calcMissingChargeWh(batteryCapacityWh, socPercent) {
|
|
148
|
+
if (batteryCapacityWh <= 0) {
|
|
147
149
|
return 0;
|
|
148
150
|
}
|
|
149
|
-
|
|
151
|
+
const soc = Math.min(100, Math.max(0, socPercent));
|
|
152
|
+
return Math.max(0, Math.round((100 - soc) / 100 * batteryCapacityWh));
|
|
150
153
|
}
|
|
151
|
-
function calcMaxChargeW(
|
|
154
|
+
function calcMaxChargeW(missingWh, hoursRemaining) {
|
|
152
155
|
const hours = Math.max(1, hoursRemaining);
|
|
153
|
-
if (
|
|
156
|
+
if (missingWh <= 0) {
|
|
154
157
|
return 0;
|
|
155
158
|
}
|
|
156
|
-
const missingWh = (100 - socPercent) / 100 * batteryCapacityWh;
|
|
157
159
|
return Math.max(0, Math.round(missingWh / hours));
|
|
158
160
|
}
|
|
161
|
+
async function readSocPercentForCurtailment(host, deviceId) {
|
|
162
|
+
var _a, _b, _c;
|
|
163
|
+
const fromEntities = (_a = host.getDeviceEntities) == null ? void 0 : _a.call(host, deviceId);
|
|
164
|
+
if (fromEntities) {
|
|
165
|
+
for (const key of ["state_of_charge", "battery_soc", "total_soc"]) {
|
|
166
|
+
const n = Number(fromEntities[key]);
|
|
167
|
+
if (Number.isFinite(n) && n >= 0 && n <= 100) {
|
|
168
|
+
return Math.round(n);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const siteId = (_c = (_b = host.getDeviceSiteId) == null ? void 0 : _b.call(host, deviceId)) == null ? void 0 : _c.trim();
|
|
173
|
+
if (siteId) {
|
|
174
|
+
const systemSoc = await host.getStateAsync(`${host.namespace}.system.${siteId}.sensors.state_of_charge`);
|
|
175
|
+
const n = Number(systemSoc == null ? void 0 : systemSoc.val);
|
|
176
|
+
if (Number.isFinite(n) && n >= 0 && n <= 100) {
|
|
177
|
+
return Math.round(n);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const candidates = [
|
|
181
|
+
`${host.namespace}.combiner_box.${deviceId}.sensors.state_of_charge`,
|
|
182
|
+
`${host.namespace}.combiner_box.${deviceId}.sensors.battery_soc`,
|
|
183
|
+
`${host.namespace}.solarbank.${deviceId}.sensors.state_of_charge`
|
|
184
|
+
];
|
|
185
|
+
for (const id of candidates) {
|
|
186
|
+
const st = await host.getStateAsync(id);
|
|
187
|
+
const n = Number(st == null ? void 0 : st.val);
|
|
188
|
+
if (Number.isFinite(n) && n >= 0 && n <= 100) {
|
|
189
|
+
return Math.round(n);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return void 0;
|
|
193
|
+
}
|
|
194
|
+
function resolveActiveExportW(livePvW, maxChargeW) {
|
|
195
|
+
if (livePvW <= 0) {
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
return Math.max(0, Math.round(livePvW - Math.max(0, maxChargeW)));
|
|
199
|
+
}
|
|
159
200
|
function resolveCurtailmentSetpoints(phase, livePvW, maxChargeW, forecast, nowHour, window) {
|
|
160
201
|
if (phase === "before") {
|
|
161
202
|
return { exportW: resolveBeforeExportW(livePvW, forecast, nowHour, window), chargeW: 0 };
|
|
@@ -170,6 +211,7 @@ function resolveCurtailmentSetpoints(phase, livePvW, maxChargeW, forecast, nowHo
|
|
|
170
211
|
COMBINER_MAX_AC_OUTPUT_W,
|
|
171
212
|
PV_SENSOR_IDS,
|
|
172
213
|
calcMaxChargeW,
|
|
214
|
+
calcMissingChargeWh,
|
|
173
215
|
isPvGenerationSensor,
|
|
174
216
|
isPvSensorEntity,
|
|
175
217
|
parsePvSensorStateId,
|
|
@@ -177,6 +219,7 @@ function resolveCurtailmentSetpoints(phase, livePvW, maxChargeW, forecast, nowHo
|
|
|
177
219
|
pvSensorStatePaths,
|
|
178
220
|
readLivePvPowerW,
|
|
179
221
|
readPvFromEntities,
|
|
222
|
+
readSocPercentForCurtailment,
|
|
180
223
|
resolveActiveExportW,
|
|
181
224
|
resolveBeforeExportW,
|
|
182
225
|
resolveCurtailmentSetpoints,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/curtailmentPower.ts"],
|
|
4
|
-
"sourcesContent": ["import { forecastExportTargetW } from \"./curtailmentForecast\";\nimport type { CurtailmentPhase, CurtailmentWindow, HourlyForecast } from \"./curtailmentTypes\";\n\n/** Sensors that reflect current PV generation (W). */\nexport const PV_SENSOR_IDS = [\"total_pv_power\", \"input_power\", \"solar_power_total\"] as const;\n\n/** Optional power-flow sensors: sum \u2248 total PV when direct sensors are missing. */\nconst PV_FLOW_SUM_IDS = [\"pv_to_home_power\", \"pv_to_battery_power\", \"photovoltaic_to_grid_power\"] as const;\n\nexport type PvSensorId = (typeof PV_SENSOR_IDS)[number];\n\nexport interface CurtailmentPowerHost {\n\tnamespace: string;\n\tgetStateAsync: (id: string) => Promise<ioBroker.State | null | undefined>;\n\tgetDeviceEntities?: (deviceId: string) => Record<string, unknown> | undefined;\n\t/** Anker site UUID for system.*.sensors.total_pv_power (preferred PV source). */\n\tgetDeviceSiteId?: (deviceId: string) => string | undefined;\n}\n\nexport function systemTotalPvStatePath(namespace: string, siteId: string): string {\n\treturn `${namespace}.system.${siteId}.sensors.total_pv_power`;\n}\n\nexport function pvSensorStatePaths(namespace: string, deviceId: string): string[] {\n\tconst paths: string[] = [];\n\tfor (const channel of [\"solarbank\", \"combiner_box\"] as const) {\n\t\tfor (const sensor of [...PV_SENSOR_IDS, ...PV_FLOW_SUM_IDS]) {\n\t\t\tpaths.push(`${namespace}.${channel}.${deviceId}.sensors.${sensor}`);\n\t\t}\n\t}\n\treturn paths;\n}\n\nexport function parseSystemPvStateId(namespace: string, stateId: string): { siteId: string } | undefined {\n\tconst prefix = `${namespace}.`;\n\tif (!stateId.startsWith(prefix)) {\n\t\treturn undefined;\n\t}\n\tconst rest = stateId.slice(prefix.length);\n\tconst match = /^system\\.([^.]+)\\.sensors\\.total_pv_power$/.exec(rest);\n\tif (!match) {\n\t\treturn undefined;\n\t}\n\treturn { siteId: match[1] ?? \"\" };\n}\n\nexport function parsePvSensorStateId(\n\tnamespace: string,\n\tstateId: string,\n): { deviceId: string; sensor: PvSensorId } | undefined {\n\tconst prefix = `${namespace}.`;\n\tif (!stateId.startsWith(prefix) || !stateId.includes(\".sensors.\")) {\n\t\treturn undefined;\n\t}\n\tconst rest = stateId.slice(prefix.length);\n\tconst match = /^(?:solarbank|combiner_box)\\.([^.]+)\\.sensors\\.(total_pv_power|input_power)$/.exec(rest);\n\tif (!match) {\n\t\treturn undefined;\n\t}\n\treturn { deviceId: match[1] ?? \"\", sensor: match[2] as PvSensorId };\n}\n\nexport function isPvSensorEntity(entityId: string): entityId is PvSensorId {\n\treturn (PV_SENSOR_IDS as readonly string[]).includes(entityId);\n}\n\nexport function isPvGenerationSensor(entityId: string): boolean {\n\treturn isPvSensorEntity(entityId) || (PV_FLOW_SUM_IDS as readonly string[]).includes(entityId);\n}\n\n/** Best estimate of current PV generation (W) from poll entity map. */\nexport function readPvFromEntities(entities: Record<string, unknown> | undefined): number {\n\tif (!entities) {\n\t\treturn 0;\n\t}\n\tlet max = 0;\n\tfor (const key of PV_SENSOR_IDS) {\n\t\tconst n = Number(entities[key]);\n\t\tif (Number.isFinite(n) && n > max) {\n\t\t\tmax = n;\n\t\t}\n\t}\n\tif (max > 0) {\n\t\treturn Math.round(max);\n\t}\n\tlet flowSum = 0;\n\tfor (const key of PV_FLOW_SUM_IDS) {\n\t\tconst n = Number(entities[key]);\n\t\tif (Number.isFinite(n) && n > 0) {\n\t\t\tflowSum += n;\n\t\t}\n\t}\n\tif (flowSum > 0) {\n\t\treturn Math.round(flowSum);\n\t}\n\treturn 0;\n}\n\nasync function readSystemTotalPvW(host: CurtailmentPowerHost, siteId: string): Promise<number> {\n\tconst st = await host.getStateAsync(systemTotalPvStatePath(host.namespace, siteId));\n\tconst n = Number(st?.val);\n\treturn Number.isFinite(n) && n > 0 ? Math.round(n) : 0;\n}\n\n/** Read live PV generation (W): system.total_pv_power first, then device sensors. */\nexport async function readLivePvPowerW(host: CurtailmentPowerHost, deviceId: string): Promise<number> {\n\tconst siteId = host.getDeviceSiteId?.(deviceId)?.trim();\n\tif (siteId) {\n\t\tconst fromSystem = await readSystemTotalPvW(host, siteId);\n\t\tif (fromSystem > 0) {\n\t\t\treturn fromSystem;\n\t\t}\n\t}\n\tconst fromPoll = readPvFromEntities(host.getDeviceEntities?.(deviceId));\n\tif (fromPoll > 0) {\n\t\treturn fromPoll;\n\t}\n\tlet max = 0;\n\tfor (const id of pvSensorStatePaths(host.namespace, deviceId)) {\n\t\tconst st = await host.getStateAsync(id);\n\t\tconst n = Number(st?.val);\n\t\tif (Number.isFinite(n) && n > max) {\n\t\t\tmax = n;\n\t\t}\n\t}\n\treturn max > 0 ? Math.round(max) : 0;\n}\n\n/** Before window: export all live generation, no battery charging. */\nexport function resolveBeforeExportW(\n\tlivePvW: number,\n\tforecast: HourlyForecast,\n\tnowHour: number,\n\twindow: CurtailmentWindow,\n): number {\n\tif (livePvW > 0) {\n\t\treturn livePvW;\n\t}\n\treturn forecastExportTargetW(forecast, nowHour, window);\n}\n\n/** Combiner / multisystem AC output (max_load_parallel MQTT steps up to 4800 W). */\nexport const COMBINER_MAX_AC_OUTPUT_W = 4800;\n\n
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { forecastExportTargetW } from \"./curtailmentForecast\";\nimport type { CurtailmentPhase, CurtailmentWindow, HourlyForecast } from \"./curtailmentTypes\";\n\n/** Sensors that reflect current PV generation (W). */\nexport const PV_SENSOR_IDS = [\"total_pv_power\", \"input_power\", \"solar_power_total\"] as const;\n\n/** Optional power-flow sensors: sum \u2248 total PV when direct sensors are missing. */\nconst PV_FLOW_SUM_IDS = [\"pv_to_home_power\", \"pv_to_battery_power\", \"photovoltaic_to_grid_power\"] as const;\n\nexport type PvSensorId = (typeof PV_SENSOR_IDS)[number];\n\nexport interface CurtailmentPowerHost {\n\tnamespace: string;\n\tgetStateAsync: (id: string) => Promise<ioBroker.State | null | undefined>;\n\tgetDeviceEntities?: (deviceId: string) => Record<string, unknown> | undefined;\n\t/** Anker site UUID for system.*.sensors.total_pv_power (preferred PV source). */\n\tgetDeviceSiteId?: (deviceId: string) => string | undefined;\n}\n\nexport function systemTotalPvStatePath(namespace: string, siteId: string): string {\n\treturn `${namespace}.system.${siteId}.sensors.total_pv_power`;\n}\n\nexport function pvSensorStatePaths(namespace: string, deviceId: string): string[] {\n\tconst paths: string[] = [];\n\tfor (const channel of [\"solarbank\", \"combiner_box\"] as const) {\n\t\tfor (const sensor of [...PV_SENSOR_IDS, ...PV_FLOW_SUM_IDS]) {\n\t\t\tpaths.push(`${namespace}.${channel}.${deviceId}.sensors.${sensor}`);\n\t\t}\n\t}\n\treturn paths;\n}\n\nexport function parseSystemPvStateId(namespace: string, stateId: string): { siteId: string } | undefined {\n\tconst prefix = `${namespace}.`;\n\tif (!stateId.startsWith(prefix)) {\n\t\treturn undefined;\n\t}\n\tconst rest = stateId.slice(prefix.length);\n\tconst match = /^system\\.([^.]+)\\.sensors\\.total_pv_power$/.exec(rest);\n\tif (!match) {\n\t\treturn undefined;\n\t}\n\treturn { siteId: match[1] ?? \"\" };\n}\n\nexport function parsePvSensorStateId(\n\tnamespace: string,\n\tstateId: string,\n): { deviceId: string; sensor: PvSensorId } | undefined {\n\tconst prefix = `${namespace}.`;\n\tif (!stateId.startsWith(prefix) || !stateId.includes(\".sensors.\")) {\n\t\treturn undefined;\n\t}\n\tconst rest = stateId.slice(prefix.length);\n\tconst match = /^(?:solarbank|combiner_box)\\.([^.]+)\\.sensors\\.(total_pv_power|input_power)$/.exec(rest);\n\tif (!match) {\n\t\treturn undefined;\n\t}\n\treturn { deviceId: match[1] ?? \"\", sensor: match[2] as PvSensorId };\n}\n\nexport function isPvSensorEntity(entityId: string): entityId is PvSensorId {\n\treturn (PV_SENSOR_IDS as readonly string[]).includes(entityId);\n}\n\nexport function isPvGenerationSensor(entityId: string): boolean {\n\treturn isPvSensorEntity(entityId) || (PV_FLOW_SUM_IDS as readonly string[]).includes(entityId);\n}\n\n/** Best estimate of current PV generation (W) from poll entity map. */\nexport function readPvFromEntities(entities: Record<string, unknown> | undefined): number {\n\tif (!entities) {\n\t\treturn 0;\n\t}\n\tlet max = 0;\n\tfor (const key of PV_SENSOR_IDS) {\n\t\tconst n = Number(entities[key]);\n\t\tif (Number.isFinite(n) && n > max) {\n\t\t\tmax = n;\n\t\t}\n\t}\n\tif (max > 0) {\n\t\treturn Math.round(max);\n\t}\n\tlet flowSum = 0;\n\tfor (const key of PV_FLOW_SUM_IDS) {\n\t\tconst n = Number(entities[key]);\n\t\tif (Number.isFinite(n) && n > 0) {\n\t\t\tflowSum += n;\n\t\t}\n\t}\n\tif (flowSum > 0) {\n\t\treturn Math.round(flowSum);\n\t}\n\treturn 0;\n}\n\nasync function readSystemTotalPvW(host: CurtailmentPowerHost, siteId: string): Promise<number> {\n\tconst st = await host.getStateAsync(systemTotalPvStatePath(host.namespace, siteId));\n\tconst n = Number(st?.val);\n\treturn Number.isFinite(n) && n > 0 ? Math.round(n) : 0;\n}\n\n/** Read live PV generation (W): system.total_pv_power first, then device sensors. */\nexport async function readLivePvPowerW(host: CurtailmentPowerHost, deviceId: string): Promise<number> {\n\tconst siteId = host.getDeviceSiteId?.(deviceId)?.trim();\n\tif (siteId) {\n\t\tconst fromSystem = await readSystemTotalPvW(host, siteId);\n\t\tif (fromSystem > 0) {\n\t\t\treturn fromSystem;\n\t\t}\n\t}\n\tconst fromPoll = readPvFromEntities(host.getDeviceEntities?.(deviceId));\n\tif (fromPoll > 0) {\n\t\treturn fromPoll;\n\t}\n\tlet max = 0;\n\tfor (const id of pvSensorStatePaths(host.namespace, deviceId)) {\n\t\tconst st = await host.getStateAsync(id);\n\t\tconst n = Number(st?.val);\n\t\tif (Number.isFinite(n) && n > max) {\n\t\t\tmax = n;\n\t\t}\n\t}\n\treturn max > 0 ? Math.round(max) : 0;\n}\n\n/** Before window: export all live generation, no battery charging. */\nexport function resolveBeforeExportW(\n\tlivePvW: number,\n\tforecast: HourlyForecast,\n\tnowHour: number,\n\twindow: CurtailmentWindow,\n): number {\n\tif (livePvW > 0) {\n\t\treturn livePvW;\n\t}\n\treturn forecastExportTargetW(forecast, nowHour, window);\n}\n\n/** Combiner / multisystem AC output (max_load_parallel MQTT steps up to 4800 W). */\nexport const COMBINER_MAX_AC_OUTPUT_W = 4800;\n\n/** Wh still required to reach 100 % SOC (active phase). */\nexport function calcMissingChargeWh(batteryCapacityWh: number, socPercent: number): number {\n\tif (batteryCapacityWh <= 0) {\n\t\treturn 0;\n\t}\n\tconst soc = Math.min(100, Math.max(0, socPercent));\n\treturn Math.max(0, Math.round(((100 - soc) / 100) * batteryCapacityWh));\n}\n\n/** Max AC charge power (W) = missing Wh \u00F7 remaining curtailment hours. */\nexport function calcMaxChargeW(missingWh: number, hoursRemaining: number): number {\n\tconst hours = Math.max(1, hoursRemaining);\n\tif (missingWh <= 0) {\n\t\treturn 0;\n\t}\n\treturn Math.max(0, Math.round(missingWh / hours));\n}\n\n/** Read battery SOC (%) for curtailment; undefined if no trustworthy sensor value. */\nexport async function readSocPercentForCurtailment(\n\thost: CurtailmentPowerHost,\n\tdeviceId: string,\n): Promise<number | undefined> {\n\tconst fromEntities = host.getDeviceEntities?.(deviceId);\n\tif (fromEntities) {\n\t\tfor (const key of [\"state_of_charge\", \"battery_soc\", \"total_soc\"] as const) {\n\t\t\tconst n = Number(fromEntities[key]);\n\t\t\tif (Number.isFinite(n) && n >= 0 && n <= 100) {\n\t\t\t\treturn Math.round(n);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst siteId = host.getDeviceSiteId?.(deviceId)?.trim();\n\tif (siteId) {\n\t\tconst systemSoc = await host.getStateAsync(`${host.namespace}.system.${siteId}.sensors.state_of_charge`);\n\t\tconst n = Number(systemSoc?.val);\n\t\tif (Number.isFinite(n) && n >= 0 && n <= 100) {\n\t\t\treturn Math.round(n);\n\t\t}\n\t}\n\n\tconst candidates = [\n\t\t`${host.namespace}.combiner_box.${deviceId}.sensors.state_of_charge`,\n\t\t`${host.namespace}.combiner_box.${deviceId}.sensors.battery_soc`,\n\t\t`${host.namespace}.solarbank.${deviceId}.sensors.state_of_charge`,\n\t];\n\tfor (const id of candidates) {\n\t\tconst st = await host.getStateAsync(id);\n\t\tconst n = Number(st?.val);\n\t\tif (Number.isFinite(n) && n >= 0 && n <= 100) {\n\t\t\treturn Math.round(n);\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n/** Active window: AC output (export) = live PV \u2212 max charge power. */\nexport function resolveActiveExportW(livePvW: number, maxChargeW: number): number {\n\tif (livePvW <= 0) {\n\t\treturn 0;\n\t}\n\treturn Math.max(0, Math.round(livePvW - Math.max(0, maxChargeW)));\n}\n\nexport function resolveCurtailmentSetpoints(\n\tphase: CurtailmentPhase,\n\tlivePvW: number,\n\tmaxChargeW: number,\n\tforecast: HourlyForecast,\n\tnowHour: number,\n\twindow: CurtailmentWindow,\n): { exportW: number; chargeW: number } {\n\tif (phase === \"before\") {\n\t\treturn { exportW: resolveBeforeExportW(livePvW, forecast, nowHour, window), chargeW: 0 };\n\t}\n\tif (phase === \"active\") {\n\t\treturn { exportW: resolveActiveExportW(livePvW, maxChargeW), chargeW: maxChargeW };\n\t}\n\treturn { exportW: 0, chargeW: 0 };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -18,7 +18,6 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var curtailmentRunner_exports = {};
|
|
20
20
|
__export(curtailmentRunner_exports, {
|
|
21
|
-
COMBINER_MIN_HOME_LOAD_W: () => COMBINER_MIN_HOME_LOAD_W,
|
|
22
21
|
runCurtailmentAvoidance: () => runCurtailmentAvoidance,
|
|
23
22
|
runCurtailmentOnPvChange: () => runCurtailmentOnPvChange
|
|
24
23
|
});
|
|
@@ -28,10 +27,7 @@ var import_curtailmentConfig = require("./curtailmentConfig");
|
|
|
28
27
|
var import_curtailmentForecast = require("./curtailmentForecast");
|
|
29
28
|
var import_curtailmentPower = require("./curtailmentPower");
|
|
30
29
|
var import_curtailmentStates = require("./curtailmentStates");
|
|
31
|
-
const COMBINER_MIN_HOME_LOAD_W = 0;
|
|
32
30
|
const lastAppliedExportW = /* @__PURE__ */ new Map();
|
|
33
|
-
const lastAppliedHomeLoadW = /* @__PURE__ */ new Map();
|
|
34
|
-
const lastAppliedChargeW = /* @__PURE__ */ new Map();
|
|
35
31
|
const lastAppliedPhase = /* @__PURE__ */ new Map();
|
|
36
32
|
function berlinHour() {
|
|
37
33
|
var _a;
|
|
@@ -43,106 +39,34 @@ function berlinHour() {
|
|
|
43
39
|
const h = (_a = parts.find((p) => p.type === "hour")) == null ? void 0 : _a.value;
|
|
44
40
|
return Math.min(23, Math.max(0, Number(h) || 0));
|
|
45
41
|
}
|
|
46
|
-
function
|
|
42
|
+
function clampAcOutputW(powerW, role) {
|
|
47
43
|
if (powerW <= 0) {
|
|
48
44
|
return 0;
|
|
49
45
|
}
|
|
50
|
-
const hardwareMax = role === "combiner" ?
|
|
51
|
-
return Math.min(hardwareMax, Math.max(
|
|
46
|
+
const hardwareMax = role === "combiner" ? 4800 : 1e5;
|
|
47
|
+
return Math.min(hardwareMax, Math.max(0, Math.round(powerW)));
|
|
52
48
|
}
|
|
53
|
-
async function
|
|
54
|
-
const candidates = [
|
|
55
|
-
`${host.namespace}.solarbank.${deviceId}.sensors.state_of_charge`,
|
|
56
|
-
`${host.namespace}.combiner_box.${deviceId}.sensors.state_of_charge`,
|
|
57
|
-
`${host.namespace}.combiner_box.${deviceId}.sensors.battery_soc`
|
|
58
|
-
];
|
|
59
|
-
for (const id of candidates) {
|
|
60
|
-
const st = await host.getStateAsync(id);
|
|
61
|
-
if ((st == null ? void 0 : st.val) !== null && (st == null ? void 0 : st.val) !== void 0) {
|
|
62
|
-
const n = Number(st.val);
|
|
63
|
-
if (!Number.isNaN(n)) {
|
|
64
|
-
return Math.min(100, Math.max(0, n));
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return 0;
|
|
69
|
-
}
|
|
70
|
-
async function applyOptionalControl(host, deviceId, control, value, ctx) {
|
|
71
|
-
try {
|
|
72
|
-
await host.applyControl(deviceId, control, value, ctx);
|
|
73
|
-
} catch (err) {
|
|
74
|
-
host.log.debug(`Curtailment optional control ${control} skipped: ${err.message}`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
async function applyManualAndExportSwitches(host, device) {
|
|
49
|
+
async function applyManualMode(host, device) {
|
|
78
50
|
const ctx = host.getDeviceContext(device.deviceId);
|
|
79
51
|
await host.applyControl(device.deviceId, "preset_usage_mode", "manual", ctx);
|
|
80
|
-
await applyOptionalControl(host, device.deviceId, "preset_allow_export", true, ctx);
|
|
81
|
-
await applyOptionalControl(host, device.deviceId, "allow_grid_export", true, ctx);
|
|
82
|
-
}
|
|
83
|
-
async function applyChargeLimit(host, device, chargeW) {
|
|
84
|
-
const rounded = Math.max(0, Math.round(chargeW));
|
|
85
|
-
const last = lastAppliedChargeW.get(device.deviceId);
|
|
86
|
-
if (last === rounded) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
const ctx = host.getDeviceContext(device.deviceId);
|
|
90
|
-
await host.applyControl(device.deviceId, "ac_charge_limit", rounded, ctx);
|
|
91
|
-
lastAppliedChargeW.set(device.deviceId, rounded);
|
|
92
|
-
}
|
|
93
|
-
function deviceHasControl(host, deviceId, control) {
|
|
94
|
-
var _a;
|
|
95
|
-
const writable = (_a = host.getDeviceWritable) == null ? void 0 : _a.call(host, deviceId);
|
|
96
|
-
if (!(writable == null ? void 0 : writable.length)) {
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
99
|
-
return writable.includes(control);
|
|
100
|
-
}
|
|
101
|
-
async function applyCombinerExportLimit(host, deviceId, exportW, ctx) {
|
|
102
|
-
const lastAc = lastAppliedExportW.get(deviceId);
|
|
103
|
-
if (lastAc !== exportW) {
|
|
104
|
-
await host.applyControl(deviceId, "ac_output_limit", exportW, ctx);
|
|
105
|
-
lastAppliedExportW.set(deviceId, exportW);
|
|
106
|
-
}
|
|
107
|
-
const homeW = COMBINER_MIN_HOME_LOAD_W;
|
|
108
|
-
const lastHome = lastAppliedHomeLoadW.get(deviceId);
|
|
109
|
-
if (lastHome !== homeW) {
|
|
110
|
-
await applyOptionalControl(host, deviceId, "set_output_power", homeW, ctx);
|
|
111
|
-
lastAppliedHomeLoadW.set(deviceId, homeW);
|
|
112
|
-
}
|
|
113
52
|
}
|
|
114
|
-
async function
|
|
115
|
-
const
|
|
116
|
-
|
|
53
|
+
async function applyAcOutputLimit(host, device, targetW) {
|
|
54
|
+
const acOutputW = clampAcOutputW(targetW, device.role);
|
|
55
|
+
const last = lastAppliedExportW.get(device.deviceId);
|
|
56
|
+
if (last === acOutputW) {
|
|
117
57
|
return;
|
|
118
58
|
}
|
|
119
59
|
const ctx = host.getDeviceContext(device.deviceId);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
await applyCombinerExportLimit(host, id, exportW, ctx);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const last = lastAppliedExportW.get(id);
|
|
126
|
-
if (last === exportW) {
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (deviceHasControl(host, id, "ac_output_limit")) {
|
|
130
|
-
await host.applyControl(id, "ac_output_limit", exportW, ctx);
|
|
131
|
-
}
|
|
132
|
-
if (deviceHasControl(host, id, "set_output_power")) {
|
|
133
|
-
await host.applyControl(id, "set_output_power", exportW, ctx);
|
|
134
|
-
}
|
|
135
|
-
lastAppliedExportW.set(id, exportW);
|
|
60
|
+
await host.applyControl(device.deviceId, "ac_output_limit", acOutputW, ctx, { acOutputApiOnly: true });
|
|
61
|
+
lastAppliedExportW.set(device.deviceId, acOutputW);
|
|
136
62
|
}
|
|
137
63
|
async function applyAfterPhase(host, device, modeAfter) {
|
|
138
64
|
lastAppliedExportW.delete(device.deviceId);
|
|
139
|
-
lastAppliedHomeLoadW.delete(device.deviceId);
|
|
140
|
-
lastAppliedChargeW.delete(device.deviceId);
|
|
141
65
|
lastAppliedPhase.delete(device.deviceId);
|
|
142
66
|
const ctx = host.getDeviceContext(device.deviceId);
|
|
143
67
|
await host.applyControl(device.deviceId, "preset_usage_mode", modeAfter, ctx);
|
|
144
68
|
}
|
|
145
|
-
async function applyCurtailmentSetpoints(host, device, phase, exportW,
|
|
69
|
+
async function applyCurtailmentSetpoints(host, device, phase, exportW, modeAfter, opts) {
|
|
146
70
|
const prevPhase = lastAppliedPhase.get(device.deviceId);
|
|
147
71
|
const phaseChanged = prevPhase !== phase;
|
|
148
72
|
if (phase === "after" || phase === "idle") {
|
|
@@ -150,14 +74,13 @@ async function applyCurtailmentSetpoints(host, device, phase, exportW, chargeW,
|
|
|
150
74
|
return;
|
|
151
75
|
}
|
|
152
76
|
if (phaseChanged || !(opts == null ? void 0 : opts.modeOnly)) {
|
|
153
|
-
await
|
|
77
|
+
await applyManualMode(host, device);
|
|
154
78
|
lastAppliedPhase.set(device.deviceId, phase);
|
|
155
79
|
}
|
|
156
80
|
if (opts == null ? void 0 : opts.modeOnly) {
|
|
157
81
|
return;
|
|
158
82
|
}
|
|
159
|
-
await
|
|
160
|
-
await applyExportLimit(host, device, exportW);
|
|
83
|
+
await applyAcOutputLimit(host, device, exportW);
|
|
161
84
|
}
|
|
162
85
|
async function buildDeviceContext(host, device, forecast, nowHour, livePvOverride) {
|
|
163
86
|
const limit = (0, import_curtailmentProfiles.acExportLimitW)(device);
|
|
@@ -165,12 +88,14 @@ async function buildDeviceContext(host, device, forecast, nowHour, livePvOverrid
|
|
|
165
88
|
const phase = (0, import_curtailmentForecast.currentPhase)(window, nowHour);
|
|
166
89
|
const livePvW = livePvOverride !== void 0 && livePvOverride >= 0 ? Math.round(livePvOverride) : window.today ? await (0, import_curtailmentPower.readLivePvPowerW)(host, device.deviceId) : 0;
|
|
167
90
|
const remaining = (0, import_curtailmentForecast.remainingCurtailmentHours)(window, nowHour);
|
|
168
|
-
const soc = window.today && phase === "active" ? await
|
|
169
|
-
const
|
|
91
|
+
const soc = window.today && phase === "active" ? await (0, import_curtailmentPower.readSocPercentForCurtailment)(host, device.deviceId) : void 0;
|
|
92
|
+
const missingChargeWh = window.today && phase === "active" && soc !== void 0 ? (0, import_curtailmentPower.calcMissingChargeWh)(device.batteryCapacityWh, soc) : 0;
|
|
93
|
+
const maxChargeW = window.today && phase === "active" && soc !== void 0 ? (0, import_curtailmentPower.calcMaxChargeW)(missingChargeWh, remaining) : 0;
|
|
170
94
|
const { exportW, chargeW } = (0, import_curtailmentPower.resolveCurtailmentSetpoints)(phase, livePvW, maxChargeW, forecast, nowHour, window);
|
|
171
|
-
return { limit, window, phase, livePvW, maxChargeW, exportW, chargeW, remaining, soc };
|
|
95
|
+
return { limit, window, phase, livePvW, missingChargeWh, maxChargeW, exportW, chargeW, remaining, soc };
|
|
172
96
|
}
|
|
173
97
|
async function publishDeviceStates(host, ctx) {
|
|
98
|
+
var _a;
|
|
174
99
|
await host.setState(import_curtailmentStates.CURTAILMENT_STATE_IDS.today, ctx.window.today, true);
|
|
175
100
|
await host.setState(
|
|
176
101
|
import_curtailmentStates.CURTAILMENT_STATE_IDS.start,
|
|
@@ -182,6 +107,8 @@ async function publishDeviceStates(host, ctx) {
|
|
|
182
107
|
ctx.window.today ? `${ctx.window.endHour.toString().padStart(2, "0")}:00` : "",
|
|
183
108
|
true
|
|
184
109
|
);
|
|
110
|
+
await host.setState(import_curtailmentStates.CURTAILMENT_STATE_IDS.missingChargeWh, ctx.missingChargeWh, true);
|
|
111
|
+
await host.setState(import_curtailmentStates.CURTAILMENT_STATE_IDS.socPercent, (_a = ctx.soc) != null ? _a : 0, true);
|
|
185
112
|
await host.setState(import_curtailmentStates.CURTAILMENT_STATE_IDS.maxChargeW, ctx.maxChargeW, true);
|
|
186
113
|
await host.setState(import_curtailmentStates.CURTAILMENT_STATE_IDS.exportW, ctx.exportW, true);
|
|
187
114
|
await host.setState(import_curtailmentStates.CURTAILMENT_STATE_IDS.remainingHours, ctx.remaining, true);
|
|
@@ -198,18 +125,24 @@ async function runDeviceCurtailment(host, device, config, forecast, nowHour, opt
|
|
|
198
125
|
}
|
|
199
126
|
if (ctx.phase !== "before" && ctx.phase !== "active") {
|
|
200
127
|
if (!(opts == null ? void 0 : opts.setpointsOnly)) {
|
|
201
|
-
await applyCurtailmentSetpoints(host, device, ctx.phase, ctx.exportW,
|
|
128
|
+
await applyCurtailmentSetpoints(host, device, ctx.phase, ctx.exportW, config.modeAfter);
|
|
202
129
|
}
|
|
203
130
|
return;
|
|
204
131
|
}
|
|
205
132
|
if (!(opts == null ? void 0 : opts.setpointsOnly)) {
|
|
206
133
|
const unitsHint = device.role === "combiner" && ((_a = device.units) == null ? void 0 : _a.length) ? `, units=${device.units.join("+")} (${device.units.length} banks)` : "";
|
|
134
|
+
const socHint = ctx.soc === void 0 ? "SOC=n/a" : `SOC=${ctx.soc}%`;
|
|
135
|
+
if (ctx.phase === "active" && ctx.soc === void 0) {
|
|
136
|
+
host.log.warn(
|
|
137
|
+
`Curtailment [${device.deviceId}]: no SOC sensor \u2014 missing_charge_wh and max_charge_w stay 0; check state_of_charge`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
207
140
|
host.log.info(
|
|
208
|
-
`Curtailment [${device.deviceId}]: phase=${ctx.phase}, limit=${ctx.limit}W${unitsHint}, window ${ctx.window.startHour}-${ctx.window.endHour}h, livePv=${ctx.livePvW}W,
|
|
141
|
+
`Curtailment [${device.deviceId}]: phase=${ctx.phase}, limit=${ctx.limit}W${unitsHint}, window ${ctx.window.startHour}-${ctx.window.endHour}h, livePv=${ctx.livePvW}W, missingWh=${ctx.missingChargeWh}, maxCharge=${ctx.maxChargeW}W, export=${ctx.exportW}W, ${socHint}`
|
|
209
142
|
);
|
|
210
143
|
}
|
|
211
144
|
try {
|
|
212
|
-
await applyCurtailmentSetpoints(host, device, ctx.phase, ctx.exportW,
|
|
145
|
+
await applyCurtailmentSetpoints(host, device, ctx.phase, ctx.exportW, config.modeAfter, {
|
|
213
146
|
modeOnly: (opts == null ? void 0 : opts.setpointsOnly) && lastAppliedPhase.get(device.deviceId) === ctx.phase
|
|
214
147
|
});
|
|
215
148
|
} catch (err) {
|
|
@@ -238,11 +171,11 @@ async function runCurtailmentOnPvChange(host, config, deviceId, livePvW) {
|
|
|
238
171
|
}
|
|
239
172
|
await publishDeviceStates(host, ctx);
|
|
240
173
|
try {
|
|
241
|
-
await applyCurtailmentSetpoints(host, device, ctx.phase, ctx.exportW,
|
|
174
|
+
await applyCurtailmentSetpoints(host, device, ctx.phase, ctx.exportW, config.modeAfter, {
|
|
242
175
|
modeOnly: lastAppliedPhase.get(device.deviceId) === ctx.phase
|
|
243
176
|
});
|
|
244
177
|
host.log.debug(
|
|
245
|
-
`Curtailment PV follow [${device.deviceId}]: phase=${ctx.phase}, livePv=${ctx.livePvW}W,
|
|
178
|
+
`Curtailment PV follow [${device.deviceId}]: phase=${ctx.phase}, livePv=${ctx.livePvW}W, missingWh=${ctx.missingChargeWh}, maxCharge=${ctx.maxChargeW}W, export=${ctx.exportW}W`
|
|
246
179
|
);
|
|
247
180
|
} catch (err) {
|
|
248
181
|
host.log.warn(`Curtailment PV follow failed for ${device.deviceId}: ${err.message}`);
|
|
@@ -273,7 +206,6 @@ async function runCurtailmentAvoidance(host, config) {
|
|
|
273
206
|
}
|
|
274
207
|
// Annotate the CommonJS export names for ESM import in node:
|
|
275
208
|
0 && (module.exports = {
|
|
276
|
-
COMBINER_MIN_HOME_LOAD_W,
|
|
277
209
|
runCurtailmentAvoidance,
|
|
278
210
|
runCurtailmentOnPvChange
|
|
279
211
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/curtailmentRunner.ts"],
|
|
4
|
-
"sourcesContent": ["import { acExportLimitW } from \"./curtailmentProfiles\";\nimport { resolveCurtailmentDevices, type CurtailmentStructuredNative } from \"./curtailmentConfig\";\nimport {\n\tcurrentPhase,\n\tdetectCurtailmentWindow,\n\treadHourlyForecast,\n\tremainingCurtailmentHours,\n} from \"./curtailmentForecast\";\nimport {\n\tCOMBINER_MAX_AC_OUTPUT_W,\n\tcalcMaxChargeW,\n\treadLivePvPowerW,\n\tresolveCurtailmentSetpoints,\n\ttype CurtailmentPowerHost,\n} from \"./curtailmentPower\";\nimport { CURTAILMENT_STATE_IDS } from \"./curtailmentStates\";\nimport type { CurtailmentDeviceConfig, CurtailmentDeviceRole, CurtailmentPhase } from \"./curtailmentTypes\";\nimport type { DeviceControlContext } from \"./types\";\n\nexport interface CurtailmentRunnerConfig extends CurtailmentStructuredNative {\n\tenabled: boolean;\n\tforecastBasePath: string;\n\tmodeAfter: \"smartmeter\" | \"smart\";\n}\n\nexport interface CurtailmentRunnerHost extends CurtailmentPowerHost {\n\t/** Writable control ids from last poll (e.g. set_output_power on combiner). */\n\tgetDeviceWritable?: (deviceId: string) => string[] | undefined;\n\tlog: {\n\t\tdebug: (msg: string) => void;\n\t\tinfo: (msg: string) => void;\n\t\twarn: (msg: string) => void;\n\t};\n\tgetForeignStateAsync: (id: string) => Promise<ioBroker.State | null | undefined>;\n\tgetForeignObjectAsync?: (id: string) => Promise<ioBroker.Object | null | undefined>;\n\tsetState: (id: string, val: unknown, ack?: boolean) => Promise<void>;\n\tgetDeviceContext: (deviceId: string) => DeviceControlContext | undefined;\n\tapplyControl: (\n\t\tdeviceId: string,\n\t\tcontrol: string,\n\t\tvalue: string | number | boolean,\n\t\tdeviceContext?: DeviceControlContext,\n\t) => Promise<void>;\n}\n\n/** Combiner manual schedule: home load preset (W), not grid export \u2014 keep minimal so surplus goes to grid. */\nexport const COMBINER_MIN_HOME_LOAD_W = 0;\n\nconst lastAppliedExportW = new Map<string, number>();\nconst lastAppliedHomeLoadW = new Map<string, number>();\nconst lastAppliedChargeW = new Map<string, number>();\nconst lastAppliedPhase = new Map<string, CurtailmentPhase>();\n\nfunction berlinHour(): number {\n\tconst parts = new Intl.DateTimeFormat(\"de-DE\", {\n\t\ttimeZone: \"Europe/Berlin\",\n\t\thour: \"numeric\",\n\t\thour12: false,\n\t}).formatToParts(new Date());\n\tconst h = parts.find(p => p.type === \"hour\")?.value;\n\treturn Math.min(23, Math.max(0, Number(h) || 0));\n}\n\nfunction clampExportW(powerW: number, role: CurtailmentDeviceRole): number {\n\tif (powerW <= 0) {\n\t\treturn 0;\n\t}\n\tconst hardwareMax = role === \"combiner\" ? COMBINER_MAX_AC_OUTPUT_W : 100_000;\n\treturn Math.min(hardwareMax, Math.max(100, Math.round(powerW)));\n}\n\nasync function readSocPercent(host: CurtailmentRunnerHost, deviceId: string): Promise<number> {\n\tconst candidates = [\n\t\t`${host.namespace}.solarbank.${deviceId}.sensors.state_of_charge`,\n\t\t`${host.namespace}.combiner_box.${deviceId}.sensors.state_of_charge`,\n\t\t`${host.namespace}.combiner_box.${deviceId}.sensors.battery_soc`,\n\t];\n\tfor (const id of candidates) {\n\t\tconst st = await host.getStateAsync(id);\n\t\tif (st?.val !== null && st?.val !== undefined) {\n\t\t\tconst n = Number(st.val);\n\t\t\tif (!Number.isNaN(n)) {\n\t\t\t\treturn Math.min(100, Math.max(0, n));\n\t\t\t}\n\t\t}\n\t}\n\treturn 0;\n}\n\nasync function applyOptionalControl(\n\thost: CurtailmentRunnerHost,\n\tdeviceId: string,\n\tcontrol: string,\n\tvalue: string | number | boolean,\n\tctx: DeviceControlContext | undefined,\n): Promise<void> {\n\ttry {\n\t\tawait host.applyControl(deviceId, control, value, ctx);\n\t} catch (err) {\n\t\thost.log.debug(`Curtailment optional control ${control} skipped: ${(err as Error).message}`);\n\t}\n}\n\nasync function applyManualAndExportSwitches(\n\thost: CurtailmentRunnerHost,\n\tdevice: CurtailmentDeviceConfig,\n): Promise<void> {\n\tconst ctx = host.getDeviceContext(device.deviceId);\n\tawait host.applyControl(device.deviceId, \"preset_usage_mode\", \"manual\", ctx);\n\tawait applyOptionalControl(host, device.deviceId, \"preset_allow_export\", true, ctx);\n\tawait applyOptionalControl(host, device.deviceId, \"allow_grid_export\", true, ctx);\n}\n\nasync function applyChargeLimit(\n\thost: CurtailmentRunnerHost,\n\tdevice: CurtailmentDeviceConfig,\n\tchargeW: number,\n): Promise<void> {\n\tconst rounded = Math.max(0, Math.round(chargeW));\n\tconst last = lastAppliedChargeW.get(device.deviceId);\n\tif (last === rounded) {\n\t\treturn;\n\t}\n\tconst ctx = host.getDeviceContext(device.deviceId);\n\tawait host.applyControl(device.deviceId, \"ac_charge_limit\", rounded, ctx);\n\tlastAppliedChargeW.set(device.deviceId, rounded);\n}\n\nfunction deviceHasControl(host: CurtailmentRunnerHost, deviceId: string, control: string): boolean {\n\tconst writable = host.getDeviceWritable?.(deviceId);\n\tif (!writable?.length) {\n\t\treturn true;\n\t}\n\treturn writable.includes(control);\n}\n\nasync function applyCombinerExportLimit(\n\thost: CurtailmentRunnerHost,\n\tdeviceId: string,\n\texportW: number,\n\tctx: DeviceControlContext | undefined,\n): Promise<void> {\n\tconst lastAc = lastAppliedExportW.get(deviceId);\n\tif (lastAc !== exportW) {\n\t\t// max_load_parallel (MQTT) \u2014 AC output cap only; station feed-in cap stays user/app controlled.\n\t\tawait host.applyControl(deviceId, \"ac_output_limit\", exportW, ctx);\n\t\tlastAppliedExportW.set(deviceId, exportW);\n\t}\n\n\tconst homeW = COMBINER_MIN_HOME_LOAD_W;\n\tconst lastHome = lastAppliedHomeLoadW.get(deviceId);\n\tif (lastHome !== homeW) {\n\t\t// set_output_power = manual home load preset, not export.\n\t\tawait applyOptionalControl(host, deviceId, \"set_output_power\", homeW, ctx);\n\t\tlastAppliedHomeLoadW.set(deviceId, homeW);\n\t}\n}\n\nasync function applyExportLimit(\n\thost: CurtailmentRunnerHost,\n\tdevice: CurtailmentDeviceConfig,\n\texportTargetW: number,\n): Promise<void> {\n\tconst exportW = clampExportW(exportTargetW, device.role);\n\tif (exportW <= 0) {\n\t\treturn;\n\t}\n\tconst ctx = host.getDeviceContext(device.deviceId);\n\tconst id = device.deviceId;\n\n\tif (device.role === \"combiner\") {\n\t\tawait applyCombinerExportLimit(host, id, exportW, ctx);\n\t\treturn;\n\t}\n\n\tconst last = lastAppliedExportW.get(id);\n\tif (last === exportW) {\n\t\treturn;\n\t}\n\tif (deviceHasControl(host, id, \"ac_output_limit\")) {\n\t\tawait host.applyControl(id, \"ac_output_limit\", exportW, ctx);\n\t}\n\tif (deviceHasControl(host, id, \"set_output_power\")) {\n\t\tawait host.applyControl(id, \"set_output_power\", exportW, ctx);\n\t}\n\tlastAppliedExportW.set(id, exportW);\n}\n\nasync function applyAfterPhase(\n\thost: CurtailmentRunnerHost,\n\tdevice: CurtailmentDeviceConfig,\n\tmodeAfter: \"smartmeter\" | \"smart\",\n): Promise<void> {\n\tlastAppliedExportW.delete(device.deviceId);\n\tlastAppliedHomeLoadW.delete(device.deviceId);\n\tlastAppliedChargeW.delete(device.deviceId);\n\tlastAppliedPhase.delete(device.deviceId);\n\tconst ctx = host.getDeviceContext(device.deviceId);\n\tawait host.applyControl(device.deviceId, \"preset_usage_mode\", modeAfter, ctx);\n}\n\nasync function applyCurtailmentSetpoints(\n\thost: CurtailmentRunnerHost,\n\tdevice: CurtailmentDeviceConfig,\n\tphase: CurtailmentPhase,\n\texportW: number,\n\tchargeW: number,\n\tmodeAfter: \"smartmeter\" | \"smart\",\n\topts?: { modeOnly?: boolean },\n): Promise<void> {\n\tconst prevPhase = lastAppliedPhase.get(device.deviceId);\n\tconst phaseChanged = prevPhase !== phase;\n\n\tif (phase === \"after\" || phase === \"idle\") {\n\t\tawait applyAfterPhase(host, device, modeAfter);\n\t\treturn;\n\t}\n\n\tif (phaseChanged || !opts?.modeOnly) {\n\t\tawait applyManualAndExportSwitches(host, device);\n\t\tlastAppliedPhase.set(device.deviceId, phase);\n\t}\n\n\tif (opts?.modeOnly) {\n\t\treturn;\n\t}\n\n\tawait applyChargeLimit(host, device, chargeW);\n\tawait applyExportLimit(host, device, exportW);\n}\n\ninterface DeviceRunContext {\n\tlimit: number;\n\twindow: ReturnType<typeof detectCurtailmentWindow>;\n\tphase: CurtailmentPhase;\n\tlivePvW: number;\n\tmaxChargeW: number;\n\texportW: number;\n\tchargeW: number;\n\tremaining: number;\n\tsoc: number;\n}\n\nasync function buildDeviceContext(\n\thost: CurtailmentRunnerHost,\n\tdevice: CurtailmentDeviceConfig,\n\tforecast: Awaited<ReturnType<typeof readHourlyForecast>>,\n\tnowHour: number,\n\tlivePvOverride?: number,\n): Promise<DeviceRunContext> {\n\tconst limit = acExportLimitW(device);\n\tconst window = detectCurtailmentWindow(forecast, limit);\n\tconst phase = currentPhase(window, nowHour);\n\tconst livePvW =\n\t\tlivePvOverride !== undefined && livePvOverride >= 0\n\t\t\t? Math.round(livePvOverride)\n\t\t\t: window.today\n\t\t\t\t? await readLivePvPowerW(host, device.deviceId)\n\t\t\t\t: 0;\n\tconst remaining = remainingCurtailmentHours(window, nowHour);\n\tconst soc = window.today && phase === \"active\" ? await readSocPercent(host, device.deviceId) : 0;\n\tconst maxChargeW =\n\t\twindow.today && phase === \"active\" ? calcMaxChargeW(device.batteryCapacityWh, soc, remaining) : 0;\n\tconst { exportW, chargeW } = resolveCurtailmentSetpoints(phase, livePvW, maxChargeW, forecast, nowHour, window);\n\treturn { limit, window, phase, livePvW, maxChargeW, exportW, chargeW, remaining, soc };\n}\n\nasync function publishDeviceStates(host: CurtailmentRunnerHost, ctx: DeviceRunContext): Promise<void> {\n\tawait host.setState(CURTAILMENT_STATE_IDS.today, ctx.window.today, true);\n\tawait host.setState(\n\t\tCURTAILMENT_STATE_IDS.start,\n\t\tctx.window.today ? `${ctx.window.startHour.toString().padStart(2, \"0\")}:00` : \"\",\n\t\ttrue,\n\t);\n\tawait host.setState(\n\t\tCURTAILMENT_STATE_IDS.end,\n\t\tctx.window.today ? `${ctx.window.endHour.toString().padStart(2, \"0\")}:00` : \"\",\n\t\ttrue,\n\t);\n\tawait host.setState(CURTAILMENT_STATE_IDS.maxChargeW, ctx.maxChargeW, true);\n\tawait host.setState(CURTAILMENT_STATE_IDS.exportW, ctx.exportW, true);\n\tawait host.setState(CURTAILMENT_STATE_IDS.remainingHours, ctx.remaining, true);\n\tawait host.setState(CURTAILMENT_STATE_IDS.phase, ctx.phase, true);\n\tawait host.setState(CURTAILMENT_STATE_IDS.acLimitW, ctx.limit, true);\n\tawait host.setState(CURTAILMENT_STATE_IDS.livePvW, ctx.livePvW, true);\n}\n\nasync function runDeviceCurtailment(\n\thost: CurtailmentRunnerHost,\n\tdevice: CurtailmentDeviceConfig,\n\tconfig: CurtailmentRunnerConfig,\n\tforecast: Awaited<ReturnType<typeof readHourlyForecast>>,\n\tnowHour: number,\n\topts?: { livePvOverride?: number; setpointsOnly?: boolean },\n): Promise<void> {\n\tconst ctx = await buildDeviceContext(host, device, forecast, nowHour, opts?.livePvOverride);\n\tawait publishDeviceStates(host, ctx);\n\n\tif (!ctx.window.today) {\n\t\treturn;\n\t}\n\n\tif (ctx.phase !== \"before\" && ctx.phase !== \"active\") {\n\t\tif (!opts?.setpointsOnly) {\n\t\t\tawait applyCurtailmentSetpoints(host, device, ctx.phase, ctx.exportW, ctx.chargeW, config.modeAfter);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (!opts?.setpointsOnly) {\n\t\tconst unitsHint =\n\t\t\tdevice.role === \"combiner\" && device.units?.length\n\t\t\t\t? `, units=${device.units.join(\"+\")} (${device.units.length} banks)`\n\t\t\t\t: \"\";\n\t\thost.log.info(\n\t\t\t`Curtailment [${device.deviceId}]: phase=${ctx.phase}, limit=${ctx.limit}W${unitsHint}, ` +\n\t\t\t\t`window ${ctx.window.startHour}-${ctx.window.endHour}h, livePv=${ctx.livePvW}W, ` +\n\t\t\t\t`charge=${ctx.chargeW}W, export=${ctx.exportW}W, SOC=${ctx.soc}%`,\n\t\t);\n\t}\n\n\ttry {\n\t\tawait applyCurtailmentSetpoints(host, device, ctx.phase, ctx.exportW, ctx.chargeW, config.modeAfter, {\n\t\t\tmodeOnly: opts?.setpointsOnly && lastAppliedPhase.get(device.deviceId) === ctx.phase,\n\t\t});\n\t} catch (err) {\n\t\thost.log.warn(`Curtailment control failed for ${device.deviceId}: ${(err as Error).message}`);\n\t}\n}\n\n/** Immediate follow-up when live PV changes (during sync / MQTT). */\nexport async function runCurtailmentOnPvChange(\n\thost: CurtailmentRunnerHost,\n\tconfig: CurtailmentRunnerConfig,\n\tdeviceId: string,\n\tlivePvW: number,\n): Promise<void> {\n\tif (!config.enabled || livePvW < 0) {\n\t\treturn;\n\t}\n\tconst devices = resolveCurtailmentDevices(config).filter(d => d.enabled && d.deviceId === deviceId);\n\tif (!devices.length) {\n\t\treturn;\n\t}\n\tconst basePath = (config.forecastBasePath || \"solarprognose.0.forecast.00.hourly\").trim();\n\tconst forecast = await readHourlyForecast(\n\t\tbasePath,\n\t\tid => host.getForeignStateAsync(id),\n\t\thost.getForeignObjectAsync ? id => host.getForeignObjectAsync!(id) : undefined,\n\t);\n\tconst nowHour = berlinHour();\n\tfor (const device of devices) {\n\t\tconst ctx = await buildDeviceContext(host, device, forecast, nowHour, livePvW);\n\t\tif (!ctx.window.today || (ctx.phase !== \"before\" && ctx.phase !== \"active\")) {\n\t\t\tcontinue;\n\t\t}\n\t\tawait publishDeviceStates(host, ctx);\n\t\ttry {\n\t\t\tawait applyCurtailmentSetpoints(host, device, ctx.phase, ctx.exportW, ctx.chargeW, config.modeAfter, {\n\t\t\t\tmodeOnly: lastAppliedPhase.get(device.deviceId) === ctx.phase,\n\t\t\t});\n\t\t\thost.log.debug(\n\t\t\t\t`Curtailment PV follow [${device.deviceId}]: phase=${ctx.phase}, livePv=${ctx.livePvW}W, ` +\n\t\t\t\t\t`charge=${ctx.chargeW}W, export=${ctx.exportW}W`,\n\t\t\t);\n\t\t} catch (err) {\n\t\t\thost.log.warn(`Curtailment PV follow failed for ${device.deviceId}: ${(err as Error).message}`);\n\t\t}\n\t}\n}\n\nexport async function runCurtailmentAvoidance(\n\thost: CurtailmentRunnerHost,\n\tconfig: CurtailmentRunnerConfig,\n): Promise<void> {\n\tif (!config.enabled) {\n\t\tawait host.setState(CURTAILMENT_STATE_IDS.phase, \"disabled\", true);\n\t\treturn;\n\t}\n\n\tconst devices = resolveCurtailmentDevices(config).filter(d => d.enabled);\n\tif (!devices.length) {\n\t\thost.log.debug(\"Curtailment avoidance: no enabled devices configured\");\n\t\tawait host.setState(CURTAILMENT_STATE_IDS.phase, \"no_devices\", true);\n\t\treturn;\n\t}\n\n\tconst basePath = (config.forecastBasePath || \"solarprognose.0.forecast.00.hourly\").trim();\n\tconst forecast = await readHourlyForecast(\n\t\tbasePath,\n\t\tid => host.getForeignStateAsync(id),\n\t\thost.getForeignObjectAsync ? id => host.getForeignObjectAsync!(id) : undefined,\n\t);\n\tconst nowHour = berlinHour();\n\n\tfor (const device of devices) {\n\t\tawait runDeviceCurtailment(host, device, config, forecast, nowHour);\n\t}\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA
|
|
4
|
+
"sourcesContent": ["import { acExportLimitW } from \"./curtailmentProfiles\";\nimport { resolveCurtailmentDevices, type CurtailmentStructuredNative } from \"./curtailmentConfig\";\nimport {\n\tcurrentPhase,\n\tdetectCurtailmentWindow,\n\treadHourlyForecast,\n\tremainingCurtailmentHours,\n} from \"./curtailmentForecast\";\nimport {\n\tcalcMaxChargeW,\n\tcalcMissingChargeWh,\n\treadLivePvPowerW,\n\treadSocPercentForCurtailment,\n\tresolveCurtailmentSetpoints,\n\ttype CurtailmentPowerHost,\n} from \"./curtailmentPower\";\nimport { CURTAILMENT_STATE_IDS } from \"./curtailmentStates\";\nimport type { CurtailmentDeviceConfig, CurtailmentDeviceRole, CurtailmentPhase } from \"./curtailmentTypes\";\nimport type { DeviceControlContext } from \"./types\";\n\nexport interface CurtailmentRunnerConfig extends CurtailmentStructuredNative {\n\tenabled: boolean;\n\tforecastBasePath: string;\n\tmodeAfter: \"smartmeter\" | \"smart\";\n}\n\nexport interface CurtailmentRunnerHost extends CurtailmentPowerHost {\n\t/** Writable control ids from last poll (e.g. ac_output_limit on combiner). */\n\tgetDeviceWritable?: (deviceId: string) => string[] | undefined;\n\tlog: {\n\t\tdebug: (msg: string) => void;\n\t\tinfo: (msg: string) => void;\n\t\twarn: (msg: string) => void;\n\t};\n\tgetForeignStateAsync: (id: string) => Promise<ioBroker.State | null | undefined>;\n\tgetForeignObjectAsync?: (id: string) => Promise<ioBroker.Object | null | undefined>;\n\tsetState: (id: string, val: unknown, ack?: boolean) => Promise<void>;\n\tgetDeviceContext: (deviceId: string) => DeviceControlContext | undefined;\n\tapplyControl: (\n\t\tdeviceId: string,\n\t\tcontrol: string,\n\t\tvalue: string | number | boolean,\n\t\tdeviceContext?: DeviceControlContext,\n\t\topts?: { acOutputApiOnly?: boolean },\n\t) => Promise<void>;\n}\n\nconst lastAppliedExportW = new Map<string, number>();\nconst lastAppliedPhase = new Map<string, CurtailmentPhase>();\n\nfunction berlinHour(): number {\n\tconst parts = new Intl.DateTimeFormat(\"de-DE\", {\n\t\ttimeZone: \"Europe/Berlin\",\n\t\thour: \"numeric\",\n\t\thour12: false,\n\t}).formatToParts(new Date());\n\tconst h = parts.find(p => p.type === \"hour\")?.value;\n\treturn Math.min(23, Math.max(0, Number(h) || 0));\n}\n\nfunction clampAcOutputW(powerW: number, role: CurtailmentDeviceRole): number {\n\tif (powerW <= 0) {\n\t\treturn 0;\n\t}\n\tconst hardwareMax = role === \"combiner\" ? 4800 : 100_000;\n\treturn Math.min(hardwareMax, Math.max(0, Math.round(powerW)));\n}\n\nasync function applyManualMode(host: CurtailmentRunnerHost, device: CurtailmentDeviceConfig): Promise<void> {\n\tconst ctx = host.getDeviceContext(device.deviceId);\n\tawait host.applyControl(device.deviceId, \"preset_usage_mode\", \"manual\", ctx);\n}\n\nasync function applyAcOutputLimit(\n\thost: CurtailmentRunnerHost,\n\tdevice: CurtailmentDeviceConfig,\n\ttargetW: number,\n): Promise<void> {\n\tconst acOutputW = clampAcOutputW(targetW, device.role);\n\tconst last = lastAppliedExportW.get(device.deviceId);\n\tif (last === acOutputW) {\n\t\treturn;\n\t}\n\tconst ctx = host.getDeviceContext(device.deviceId);\n\tawait host.applyControl(device.deviceId, \"ac_output_limit\", acOutputW, ctx, { acOutputApiOnly: true });\n\tlastAppliedExportW.set(device.deviceId, acOutputW);\n}\n\nasync function applyAfterPhase(\n\thost: CurtailmentRunnerHost,\n\tdevice: CurtailmentDeviceConfig,\n\tmodeAfter: \"smartmeter\" | \"smart\",\n): Promise<void> {\n\tlastAppliedExportW.delete(device.deviceId);\n\tlastAppliedPhase.delete(device.deviceId);\n\tconst ctx = host.getDeviceContext(device.deviceId);\n\tawait host.applyControl(device.deviceId, \"preset_usage_mode\", modeAfter, ctx);\n}\n\nasync function applyCurtailmentSetpoints(\n\thost: CurtailmentRunnerHost,\n\tdevice: CurtailmentDeviceConfig,\n\tphase: CurtailmentPhase,\n\texportW: number,\n\tmodeAfter: \"smartmeter\" | \"smart\",\n\topts?: { modeOnly?: boolean },\n): Promise<void> {\n\tconst prevPhase = lastAppliedPhase.get(device.deviceId);\n\tconst phaseChanged = prevPhase !== phase;\n\n\tif (phase === \"after\" || phase === \"idle\") {\n\t\tawait applyAfterPhase(host, device, modeAfter);\n\t\treturn;\n\t}\n\n\tif (phaseChanged || !opts?.modeOnly) {\n\t\tawait applyManualMode(host, device);\n\t\tlastAppliedPhase.set(device.deviceId, phase);\n\t}\n\n\tif (opts?.modeOnly) {\n\t\treturn;\n\t}\n\n\tawait applyAcOutputLimit(host, device, exportW);\n}\n\ninterface DeviceRunContext {\n\tlimit: number;\n\twindow: ReturnType<typeof detectCurtailmentWindow>;\n\tphase: CurtailmentPhase;\n\tlivePvW: number;\n\tmissingChargeWh: number;\n\tmaxChargeW: number;\n\texportW: number;\n\tchargeW: number;\n\tremaining: number;\n\tsoc?: number;\n}\n\nasync function buildDeviceContext(\n\thost: CurtailmentRunnerHost,\n\tdevice: CurtailmentDeviceConfig,\n\tforecast: Awaited<ReturnType<typeof readHourlyForecast>>,\n\tnowHour: number,\n\tlivePvOverride?: number,\n): Promise<DeviceRunContext> {\n\tconst limit = acExportLimitW(device);\n\tconst window = detectCurtailmentWindow(forecast, limit);\n\tconst phase = currentPhase(window, nowHour);\n\tconst livePvW =\n\t\tlivePvOverride !== undefined && livePvOverride >= 0\n\t\t\t? Math.round(livePvOverride)\n\t\t\t: window.today\n\t\t\t\t? await readLivePvPowerW(host, device.deviceId)\n\t\t\t\t: 0;\n\tconst remaining = remainingCurtailmentHours(window, nowHour);\n\tconst soc =\n\t\twindow.today && phase === \"active\" ? await readSocPercentForCurtailment(host, device.deviceId) : undefined;\n\tconst missingChargeWh =\n\t\twindow.today && phase === \"active\" && soc !== undefined\n\t\t\t? calcMissingChargeWh(device.batteryCapacityWh, soc)\n\t\t\t: 0;\n\tconst maxChargeW =\n\t\twindow.today && phase === \"active\" && soc !== undefined ? calcMaxChargeW(missingChargeWh, remaining) : 0;\n\tconst { exportW, chargeW } = resolveCurtailmentSetpoints(phase, livePvW, maxChargeW, forecast, nowHour, window);\n\treturn { limit, window, phase, livePvW, missingChargeWh, maxChargeW, exportW, chargeW, remaining, soc };\n}\n\nasync function publishDeviceStates(host: CurtailmentRunnerHost, ctx: DeviceRunContext): Promise<void> {\n\tawait host.setState(CURTAILMENT_STATE_IDS.today, ctx.window.today, true);\n\tawait host.setState(\n\t\tCURTAILMENT_STATE_IDS.start,\n\t\tctx.window.today ? `${ctx.window.startHour.toString().padStart(2, \"0\")}:00` : \"\",\n\t\ttrue,\n\t);\n\tawait host.setState(\n\t\tCURTAILMENT_STATE_IDS.end,\n\t\tctx.window.today ? `${ctx.window.endHour.toString().padStart(2, \"0\")}:00` : \"\",\n\t\ttrue,\n\t);\n\tawait host.setState(CURTAILMENT_STATE_IDS.missingChargeWh, ctx.missingChargeWh, true);\n\tawait host.setState(CURTAILMENT_STATE_IDS.socPercent, ctx.soc ?? 0, true);\n\tawait host.setState(CURTAILMENT_STATE_IDS.maxChargeW, ctx.maxChargeW, true);\n\tawait host.setState(CURTAILMENT_STATE_IDS.exportW, ctx.exportW, true);\n\tawait host.setState(CURTAILMENT_STATE_IDS.remainingHours, ctx.remaining, true);\n\tawait host.setState(CURTAILMENT_STATE_IDS.phase, ctx.phase, true);\n\tawait host.setState(CURTAILMENT_STATE_IDS.acLimitW, ctx.limit, true);\n\tawait host.setState(CURTAILMENT_STATE_IDS.livePvW, ctx.livePvW, true);\n}\n\nasync function runDeviceCurtailment(\n\thost: CurtailmentRunnerHost,\n\tdevice: CurtailmentDeviceConfig,\n\tconfig: CurtailmentRunnerConfig,\n\tforecast: Awaited<ReturnType<typeof readHourlyForecast>>,\n\tnowHour: number,\n\topts?: { livePvOverride?: number; setpointsOnly?: boolean },\n): Promise<void> {\n\tconst ctx = await buildDeviceContext(host, device, forecast, nowHour, opts?.livePvOverride);\n\tawait publishDeviceStates(host, ctx);\n\n\tif (!ctx.window.today) {\n\t\treturn;\n\t}\n\n\tif (ctx.phase !== \"before\" && ctx.phase !== \"active\") {\n\t\tif (!opts?.setpointsOnly) {\n\t\t\tawait applyCurtailmentSetpoints(host, device, ctx.phase, ctx.exportW, config.modeAfter);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (!opts?.setpointsOnly) {\n\t\tconst unitsHint =\n\t\t\tdevice.role === \"combiner\" && device.units?.length\n\t\t\t\t? `, units=${device.units.join(\"+\")} (${device.units.length} banks)`\n\t\t\t\t: \"\";\n\t\tconst socHint = ctx.soc === undefined ? \"SOC=n/a\" : `SOC=${ctx.soc}%`;\n\t\tif (ctx.phase === \"active\" && ctx.soc === undefined) {\n\t\t\thost.log.warn(\n\t\t\t\t`Curtailment [${device.deviceId}]: no SOC sensor \u2014 missing_charge_wh and max_charge_w stay 0; check state_of_charge`,\n\t\t\t);\n\t\t}\n\t\thost.log.info(\n\t\t\t`Curtailment [${device.deviceId}]: phase=${ctx.phase}, limit=${ctx.limit}W${unitsHint}, ` +\n\t\t\t\t`window ${ctx.window.startHour}-${ctx.window.endHour}h, livePv=${ctx.livePvW}W, ` +\n\t\t\t\t`missingWh=${ctx.missingChargeWh}, maxCharge=${ctx.maxChargeW}W, export=${ctx.exportW}W, ${socHint}`,\n\t\t);\n\t}\n\n\ttry {\n\t\tawait applyCurtailmentSetpoints(host, device, ctx.phase, ctx.exportW, config.modeAfter, {\n\t\t\tmodeOnly: opts?.setpointsOnly && lastAppliedPhase.get(device.deviceId) === ctx.phase,\n\t\t});\n\t} catch (err) {\n\t\thost.log.warn(`Curtailment control failed for ${device.deviceId}: ${(err as Error).message}`);\n\t}\n}\n\n/** Immediate follow-up when live PV changes (during sync / MQTT). */\nexport async function runCurtailmentOnPvChange(\n\thost: CurtailmentRunnerHost,\n\tconfig: CurtailmentRunnerConfig,\n\tdeviceId: string,\n\tlivePvW: number,\n): Promise<void> {\n\tif (!config.enabled || livePvW < 0) {\n\t\treturn;\n\t}\n\tconst devices = resolveCurtailmentDevices(config).filter(d => d.enabled && d.deviceId === deviceId);\n\tif (!devices.length) {\n\t\treturn;\n\t}\n\tconst basePath = (config.forecastBasePath || \"solarprognose.0.forecast.00.hourly\").trim();\n\tconst forecast = await readHourlyForecast(\n\t\tbasePath,\n\t\tid => host.getForeignStateAsync(id),\n\t\thost.getForeignObjectAsync ? id => host.getForeignObjectAsync!(id) : undefined,\n\t);\n\tconst nowHour = berlinHour();\n\tfor (const device of devices) {\n\t\tconst ctx = await buildDeviceContext(host, device, forecast, nowHour, livePvW);\n\t\tif (!ctx.window.today || (ctx.phase !== \"before\" && ctx.phase !== \"active\")) {\n\t\t\tcontinue;\n\t\t}\n\t\tawait publishDeviceStates(host, ctx);\n\t\ttry {\n\t\t\tawait applyCurtailmentSetpoints(host, device, ctx.phase, ctx.exportW, config.modeAfter, {\n\t\t\t\tmodeOnly: lastAppliedPhase.get(device.deviceId) === ctx.phase,\n\t\t\t});\n\t\t\thost.log.debug(\n\t\t\t\t`Curtailment PV follow [${device.deviceId}]: phase=${ctx.phase}, livePv=${ctx.livePvW}W, ` +\n\t\t\t\t\t`missingWh=${ctx.missingChargeWh}, maxCharge=${ctx.maxChargeW}W, export=${ctx.exportW}W`,\n\t\t\t);\n\t\t} catch (err) {\n\t\t\thost.log.warn(`Curtailment PV follow failed for ${device.deviceId}: ${(err as Error).message}`);\n\t\t}\n\t}\n}\n\nexport async function runCurtailmentAvoidance(\n\thost: CurtailmentRunnerHost,\n\tconfig: CurtailmentRunnerConfig,\n): Promise<void> {\n\tif (!config.enabled) {\n\t\tawait host.setState(CURTAILMENT_STATE_IDS.phase, \"disabled\", true);\n\t\treturn;\n\t}\n\n\tconst devices = resolveCurtailmentDevices(config).filter(d => d.enabled);\n\tif (!devices.length) {\n\t\thost.log.debug(\"Curtailment avoidance: no enabled devices configured\");\n\t\tawait host.setState(CURTAILMENT_STATE_IDS.phase, \"no_devices\", true);\n\t\treturn;\n\t}\n\n\tconst basePath = (config.forecastBasePath || \"solarprognose.0.forecast.00.hourly\").trim();\n\tconst forecast = await readHourlyForecast(\n\t\tbasePath,\n\t\tid => host.getForeignStateAsync(id),\n\t\thost.getForeignObjectAsync ? id => host.getForeignObjectAsync!(id) : undefined,\n\t);\n\tconst nowHour = berlinHour();\n\n\tfor (const device of devices) {\n\t\tawait runDeviceCurtailment(host, device, config, forecast, nowHour);\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -28,6 +28,8 @@ const CURTAILMENT_STATE_IDS = {
|
|
|
28
28
|
today: `${CURTAILMENT_CHANNEL}.today`,
|
|
29
29
|
start: `${CURTAILMENT_CHANNEL}.curtailment_start`,
|
|
30
30
|
end: `${CURTAILMENT_CHANNEL}.curtailment_end`,
|
|
31
|
+
missingChargeWh: `${CURTAILMENT_CHANNEL}.missing_charge_wh`,
|
|
32
|
+
socPercent: `${CURTAILMENT_CHANNEL}.soc_percent`,
|
|
31
33
|
maxChargeW: `${CURTAILMENT_CHANNEL}.max_charge_w`,
|
|
32
34
|
exportW: `${CURTAILMENT_CHANNEL}.export_w`,
|
|
33
35
|
livePvW: `${CURTAILMENT_CHANNEL}.live_pv_w`,
|
|
@@ -75,10 +77,22 @@ async function setupCurtailmentStates(adapter) {
|
|
|
75
77
|
def: ""
|
|
76
78
|
}
|
|
77
79
|
},
|
|
80
|
+
{
|
|
81
|
+
id: CURTAILMENT_STATE_IDS.missingChargeWh,
|
|
82
|
+
common: {
|
|
83
|
+
name: "Missing charge energy to full SOC (active phase, Wh)",
|
|
84
|
+
type: "number",
|
|
85
|
+
role: "value.energy",
|
|
86
|
+
unit: "Wh",
|
|
87
|
+
read: true,
|
|
88
|
+
write: false,
|
|
89
|
+
def: 0
|
|
90
|
+
}
|
|
91
|
+
},
|
|
78
92
|
{
|
|
79
93
|
id: CURTAILMENT_STATE_IDS.maxChargeW,
|
|
80
94
|
common: {
|
|
81
|
-
name: "Max AC charge power (
|
|
95
|
+
name: "Max AC charge power (missing Wh \xF7 remaining hours, W)",
|
|
82
96
|
type: "number",
|
|
83
97
|
role: "value.power",
|
|
84
98
|
unit: "W",
|
|
@@ -153,6 +167,7 @@ async function setupCurtailmentStates(adapter) {
|
|
|
153
167
|
common: st.common,
|
|
154
168
|
native: {}
|
|
155
169
|
});
|
|
170
|
+
await adapter.extendObject(st.id, { common: st.common });
|
|
156
171
|
}
|
|
157
172
|
}
|
|
158
173
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/curtailmentStates.ts"],
|
|
4
|
-
"sourcesContent": ["export const CURTAILMENT_CHANNEL = \"curtailment\";\n\nexport const CURTAILMENT_STATE_IDS = {\n\ttoday: `${CURTAILMENT_CHANNEL}.today`,\n\tstart: `${CURTAILMENT_CHANNEL}.curtailment_start`,\n\tend: `${CURTAILMENT_CHANNEL}.curtailment_end`,\n\tmaxChargeW: `${CURTAILMENT_CHANNEL}.max_charge_w`,\n\texportW: `${CURTAILMENT_CHANNEL}.export_w`,\n\tlivePvW: `${CURTAILMENT_CHANNEL}.live_pv_w`,\n\tremainingHours: `${CURTAILMENT_CHANNEL}.remaining_hours`,\n\tphase: `${CURTAILMENT_CHANNEL}.phase`,\n\tacLimitW: `${CURTAILMENT_CHANNEL}.ac_limit_w`,\n} as const;\n\nexport async function setupCurtailmentStates(adapter: ioBroker.Adapter): Promise<void> {\n\tawait adapter.setObjectNotExistsAsync(CURTAILMENT_CHANNEL, {\n\t\ttype: \"channel\",\n\t\tcommon: { name: \"Curtailment avoidance\" },\n\t\tnative: {},\n\t});\n\n\tconst states: Array<{\n\t\tid: string;\n\t\tcommon: ioBroker.StateCommon;\n\t}> = [\n\t\t{\n\t\t\tid: CURTAILMENT_STATE_IDS.today,\n\t\t\tcommon: {\n\t\t\t\tname: \"Curtailment expected today\",\n\t\t\t\ttype: \"boolean\",\n\t\t\t\trole: \"indicator\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: false,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: CURTAILMENT_STATE_IDS.start,\n\t\t\tcommon: {\n\t\t\t\tname: \"Curtailment window start (hour)\",\n\t\t\t\ttype: \"string\",\n\t\t\t\trole: \"text\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: \"\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: CURTAILMENT_STATE_IDS.end,\n\t\t\tcommon: {\n\t\t\t\tname: \"Curtailment window end (hour)\",\n\t\t\t\ttype: \"string\",\n\t\t\t\trole: \"text\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: \"\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: CURTAILMENT_STATE_IDS.maxChargeW,\n\t\t\tcommon: {\n\t\t\t\tname: \"Max AC charge power (
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["export const CURTAILMENT_CHANNEL = \"curtailment\";\n\nexport const CURTAILMENT_STATE_IDS = {\n\ttoday: `${CURTAILMENT_CHANNEL}.today`,\n\tstart: `${CURTAILMENT_CHANNEL}.curtailment_start`,\n\tend: `${CURTAILMENT_CHANNEL}.curtailment_end`,\n\tmissingChargeWh: `${CURTAILMENT_CHANNEL}.missing_charge_wh`,\n\tsocPercent: `${CURTAILMENT_CHANNEL}.soc_percent`,\n\tmaxChargeW: `${CURTAILMENT_CHANNEL}.max_charge_w`,\n\texportW: `${CURTAILMENT_CHANNEL}.export_w`,\n\tlivePvW: `${CURTAILMENT_CHANNEL}.live_pv_w`,\n\tremainingHours: `${CURTAILMENT_CHANNEL}.remaining_hours`,\n\tphase: `${CURTAILMENT_CHANNEL}.phase`,\n\tacLimitW: `${CURTAILMENT_CHANNEL}.ac_limit_w`,\n} as const;\n\nexport async function setupCurtailmentStates(adapter: ioBroker.Adapter): Promise<void> {\n\tawait adapter.setObjectNotExistsAsync(CURTAILMENT_CHANNEL, {\n\t\ttype: \"channel\",\n\t\tcommon: { name: \"Curtailment avoidance\" },\n\t\tnative: {},\n\t});\n\n\tconst states: Array<{\n\t\tid: string;\n\t\tcommon: ioBroker.StateCommon;\n\t}> = [\n\t\t{\n\t\t\tid: CURTAILMENT_STATE_IDS.today,\n\t\t\tcommon: {\n\t\t\t\tname: \"Curtailment expected today\",\n\t\t\t\ttype: \"boolean\",\n\t\t\t\trole: \"indicator\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: false,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: CURTAILMENT_STATE_IDS.start,\n\t\t\tcommon: {\n\t\t\t\tname: \"Curtailment window start (hour)\",\n\t\t\t\ttype: \"string\",\n\t\t\t\trole: \"text\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: \"\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: CURTAILMENT_STATE_IDS.end,\n\t\t\tcommon: {\n\t\t\t\tname: \"Curtailment window end (hour)\",\n\t\t\t\ttype: \"string\",\n\t\t\t\trole: \"text\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: \"\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: CURTAILMENT_STATE_IDS.missingChargeWh,\n\t\t\tcommon: {\n\t\t\t\tname: \"Missing charge energy to full SOC (active phase, Wh)\",\n\t\t\t\ttype: \"number\",\n\t\t\t\trole: \"value.energy\",\n\t\t\t\tunit: \"Wh\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: 0,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: CURTAILMENT_STATE_IDS.maxChargeW,\n\t\t\tcommon: {\n\t\t\t\tname: \"Max AC charge power (missing Wh \u00F7 remaining hours, W)\",\n\t\t\t\ttype: \"number\",\n\t\t\t\trole: \"value.power\",\n\t\t\t\tunit: \"W\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: 0,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: CURTAILMENT_STATE_IDS.exportW,\n\t\t\tcommon: {\n\t\t\t\tname: \"AC/grid export target (W)\",\n\t\t\t\ttype: \"number\",\n\t\t\t\trole: \"value.power\",\n\t\t\t\tunit: \"W\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: 0,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: CURTAILMENT_STATE_IDS.livePvW,\n\t\t\tcommon: {\n\t\t\t\tname: \"Live PV generation (W)\",\n\t\t\t\ttype: \"number\",\n\t\t\t\trole: \"value.power\",\n\t\t\t\tunit: \"W\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: 0,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: CURTAILMENT_STATE_IDS.remainingHours,\n\t\t\tcommon: {\n\t\t\t\tname: \"Remaining curtailment hours\",\n\t\t\t\ttype: \"number\",\n\t\t\t\trole: \"value\",\n\t\t\t\tunit: \"h\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: 0,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: CURTAILMENT_STATE_IDS.phase,\n\t\t\tcommon: {\n\t\t\t\tname: \"Curtailment phase\",\n\t\t\t\ttype: \"string\",\n\t\t\t\trole: \"text\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: \"idle\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: CURTAILMENT_STATE_IDS.acLimitW,\n\t\t\tcommon: {\n\t\t\t\tname: \"AC export limit (active group)\",\n\t\t\t\ttype: \"number\",\n\t\t\t\trole: \"value.power\",\n\t\t\t\tunit: \"W\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: 0,\n\t\t\t},\n\t\t},\n\t];\n\n\tfor (const st of states) {\n\t\tawait adapter.setObjectNotExistsAsync(st.id, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: st.common,\n\t\t\tnative: {},\n\t\t});\n\t\t// Ensure new states appear after adapter upgrades (setObjectNotExists alone does not update).\n\t\tawait adapter.extendObject(st.id, { common: st.common });\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/build/lib/types.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/types.ts"],
|
|
4
|
-
"sourcesContent": ["export interface DeviceInfo {\n\tid: string;\n\ttype: string;\n\tname: string;\n\tsite_id: string;\n\tmodel: string;\n\tdevice_pn?: string;\n\tstation_sn?: string;\n\tgeneration?: number;\n}\n\n/** Cached from last poll so set-bridge can use MQTT without extra site refresh. */\nexport interface DeviceControlContext {\n\ttype: string;\n\tsite_id: string;\n\tdevice_pn: string;\n\tstation_sn: string;\n\tgeneration: number;\n}\n\nexport interface BridgeDevice {\n\tinfo: DeviceInfo;\n\tentities: Record<string, string | number | boolean | null>;\n\twritable: string[];\n\t/** Valid SolarbankUsageMode names for preset_usage_mode (HA solarbank_usage_mode_options). */\n\tusage_mode_options?: string[];\n\t/** Site/device has energy_details from API (statistics channel). */\n\thasStatistics?: boolean;\n}\n\nexport interface DeviceListEntry {\n\tid: string;\n\tname: string;\n\ttype?: string;\n\tsite_id?: string;\n\tmodel?: string;\n}\n\nexport interface BridgePollResult {\n\tok: boolean;\n\terror?: string;\n\tnickname?: string;\n\tdevices?: BridgeDevice[] | DeviceListEntry[];\n\tsites?: Array<{ id: string; name: string }>;\n\tschedule?: unknown;\n\tpath?: string;\n\tsystem?: unknown;\n\trefreshDetails?: boolean;\n\tintervalcount?: number;\n\tdeviceintervals?: number;\n}\n\nexport interface BridgeConfig {\n\tusername: string;\n\tpassword: string;\n\tcountry: string;\n\tmqttUsage: boolean;\n\tcacheDir: string;\n\texclude?: string[];\n\tenableAllDevices?: boolean;\n\tselectedSiteId?: string;\n\tselectedDeviceIds?: string[];\n\t/** HA dev_interval_mult: device details every N site polls (default 10). */\n\tdeviceDetailMultiplier?: number;\n\trequestDelay?: number;\n\trequestTimeout?: number;\n\tendpointLimit?: number;\n\tenableCoreEntities?: boolean;\n\tenableEnergyStatistics?: boolean;\n\tenableEnergyDetail?: boolean;\n\tenablePowerFlows?: boolean;\n\tenableDiagnostics?: boolean;\n\tenableBinaryIndicators?: boolean;\n\tenableAdvancedControls?: boolean;\n\tenableSystemOverview?: boolean;\n\tenableSitePrice?: boolean;\n\tenableAccountInfo?: boolean;\n\tenableSolarbankMeta?: boolean;\n\tenableSmartplug?: boolean;\n\tenablePps?: boolean;\n\tenableEvCharger?: boolean;\n\tenableVehicle?: boolean;\n\tenableHes?: boolean;\n\tenablePowerPanel?: boolean;\n\tenableInverter?: boolean;\n}\n\nexport interface BridgeSetConfig extends BridgeConfig {\n\tdeviceId: string;\n\tcontrol: string;\n\tvalue: string | number | boolean;\n\tdeviceContext?: DeviceControlContext;\n}\n\nexport interface BridgeServiceConfig extends BridgeConfig {\n\tservice: string;\n\tparams: Record<string, unknown>;\n}\n"],
|
|
4
|
+
"sourcesContent": ["export interface DeviceInfo {\n\tid: string;\n\ttype: string;\n\tname: string;\n\tsite_id: string;\n\tmodel: string;\n\tdevice_pn?: string;\n\tstation_sn?: string;\n\tgeneration?: number;\n}\n\n/** Cached from last poll so set-bridge can use MQTT without extra site refresh. */\nexport interface DeviceControlContext {\n\ttype: string;\n\tsite_id: string;\n\tdevice_pn: string;\n\tstation_sn: string;\n\tgeneration: number;\n}\n\nexport interface BridgeDevice {\n\tinfo: DeviceInfo;\n\tentities: Record<string, string | number | boolean | null>;\n\twritable: string[];\n\t/** Valid SolarbankUsageMode names for preset_usage_mode (HA solarbank_usage_mode_options). */\n\tusage_mode_options?: string[];\n\t/** Site/device has energy_details from API (statistics channel). */\n\thasStatistics?: boolean;\n}\n\nexport interface DeviceListEntry {\n\tid: string;\n\tname: string;\n\ttype?: string;\n\tsite_id?: string;\n\tmodel?: string;\n}\n\nexport interface BridgePollResult {\n\tok: boolean;\n\terror?: string;\n\tnickname?: string;\n\tdevices?: BridgeDevice[] | DeviceListEntry[];\n\tsites?: Array<{ id: string; name: string }>;\n\tschedule?: unknown;\n\tpath?: string;\n\tsystem?: unknown;\n\trefreshDetails?: boolean;\n\tintervalcount?: number;\n\tdeviceintervals?: number;\n}\n\nexport interface BridgeConfig {\n\tusername: string;\n\tpassword: string;\n\tcountry: string;\n\tmqttUsage: boolean;\n\tcacheDir: string;\n\texclude?: string[];\n\tenableAllDevices?: boolean;\n\tselectedSiteId?: string;\n\tselectedDeviceIds?: string[];\n\t/** HA dev_interval_mult: device details every N site polls (default 10). */\n\tdeviceDetailMultiplier?: number;\n\trequestDelay?: number;\n\trequestTimeout?: number;\n\tendpointLimit?: number;\n\tenableCoreEntities?: boolean;\n\tenableEnergyStatistics?: boolean;\n\tenableEnergyDetail?: boolean;\n\tenablePowerFlows?: boolean;\n\tenableDiagnostics?: boolean;\n\tenableBinaryIndicators?: boolean;\n\tenableAdvancedControls?: boolean;\n\tenableSystemOverview?: boolean;\n\tenableSitePrice?: boolean;\n\tenableAccountInfo?: boolean;\n\tenableSolarbankMeta?: boolean;\n\tenableSmartplug?: boolean;\n\tenablePps?: boolean;\n\tenableEvCharger?: boolean;\n\tenableVehicle?: boolean;\n\tenableHes?: boolean;\n\tenablePowerPanel?: boolean;\n\tenableInverter?: boolean;\n}\n\nexport interface BridgeSetConfig extends BridgeConfig {\n\tdeviceId: string;\n\tcontrol: string;\n\tvalue: string | number | boolean;\n\tdeviceContext?: DeviceControlContext;\n\t/** Curtailment: set AC output via API only (no MQTT / station feed-in side effects). */\n\tacOutputApiOnly?: boolean;\n}\n\nexport interface BridgeServiceConfig extends BridgeConfig {\n\tservice: string;\n\tparams: Record<string, unknown>;\n}\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;;AAAA;AAAA;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/build/main.js
CHANGED
|
@@ -285,7 +285,7 @@ class AnkerSolix extends utils.Adapter {
|
|
|
285
285
|
});
|
|
286
286
|
}
|
|
287
287
|
}
|
|
288
|
-
async applyAdapterControl(deviceId, control, value, deviceContext) {
|
|
288
|
+
async applyAdapterControl(deviceId, control, value, deviceContext, opts) {
|
|
289
289
|
await (0, import_pythonBridge.runBridge)(
|
|
290
290
|
"set",
|
|
291
291
|
{
|
|
@@ -293,7 +293,8 @@ class AnkerSolix extends utils.Adapter {
|
|
|
293
293
|
deviceId,
|
|
294
294
|
control,
|
|
295
295
|
value,
|
|
296
|
-
deviceContext
|
|
296
|
+
deviceContext,
|
|
297
|
+
acOutputApiOnly: opts == null ? void 0 : opts.acOutputApiOnly
|
|
297
298
|
},
|
|
298
299
|
this.config.pythonPath || "",
|
|
299
300
|
this.log
|
|
@@ -340,7 +341,7 @@ class AnkerSolix extends utils.Adapter {
|
|
|
340
341
|
await this.setState(id, val, ack != null ? ack : true);
|
|
341
342
|
},
|
|
342
343
|
getDeviceContext: (deviceId) => this.deviceContexts.get(deviceId),
|
|
343
|
-
applyControl: (deviceId, control, value, deviceContext) => this.applyAdapterControl(deviceId, control, value, deviceContext)
|
|
344
|
+
applyControl: (deviceId, control, value, deviceContext, opts) => this.applyAdapterControl(deviceId, control, value, deviceContext, opts)
|
|
344
345
|
};
|
|
345
346
|
}
|
|
346
347
|
refreshCurtailmentDeviceIds() {
|
package/build/main.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/main.ts"],
|
|
4
|
-
"sourcesContent": ["/*\n * ioBroker Anker Solix adapter\n * Based on https://github.com/thomluther/ha-anker-solix (Home Assistant integration)\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nimport * as utils from \"@iobroker/adapter-core\";\n\nimport { parseSelectedDeviceIds } from \"./lib/configHelpers\";\nimport { ControlQueue } from \"./lib/controlQueue\";\nimport { parsePvSensorStateId, parseSystemPvStateId } from \"./lib/curtailmentPower\";\nimport {\n\trunCurtailmentAvoidance,\n\trunCurtailmentOnPvChange,\n\ttype CurtailmentRunnerConfig,\n\ttype CurtailmentRunnerHost,\n} from \"./lib/curtailmentRunner\";\nimport { setupCurtailmentStates } from \"./lib/curtailmentStates\";\nimport { runPythonInstaller } from \"./lib/ensurePython\";\nimport { ensureBridgeDaemon, runBridge, stopBridgeDaemon } from \"./lib/pythonBridge\";\nimport { SERVICE_STATES, setupServiceStates } from \"./lib/services\";\nimport { resolveCurtailmentDevices } from \"./lib/curtailmentConfig\";\nimport { parseControlStateId, syncDevices, type CurtailmentPvSyncHost } from \"./lib/stateSync\";\nimport type { BridgeConfig, BridgeDevice, BridgeServiceConfig, DeviceControlContext } from \"./lib/types\";\n\nclass AnkerSolix extends utils.Adapter {\n\tprivate pollTimer: ioBroker.Interval | undefined;\n\tprivate readonly controlQueue = new ControlQueue();\n\tprivate readonly deviceContexts = new Map<string, DeviceControlContext>();\n\tprivate readonly deviceEntities = new Map<string, Record<string, unknown>>();\n\tprivate readonly deviceWritable = new Map<string, string[]>();\n\tprivate readonly lastNotifiedPvW = new Map<string, number>();\n\tprivate readonly curtailmentDeviceIds = new Set<string>();\n\tprivate pollAfterControlTimer: NodeJS.Timeout | undefined;\n\n\tpublic constructor(options: Partial<utils.AdapterOptions> = {}) {\n\t\tsuper({\n\t\t\t...options,\n\t\t\tname: \"anker-solix\",\n\t\t});\n\t\tthis.on(\"ready\", this.onReady.bind(this));\n\t\tthis.on(\"stateChange\", this.onStateChange.bind(this));\n\t\tthis.on(\"message\", this.onMessage.bind(this));\n\t\tthis.on(\"unload\", this.onUnload.bind(this));\n\t}\n\n\tprivate getAuthCacheDir(): string {\n\t\treturn path.join(utils.getAbsoluteInstanceDataDir(this), \"authcache\");\n\t}\n\n\tprivate getAuthCacheFile(): string {\n\t\tconst email = (this.config.username || \"\").trim();\n\t\treturn path.join(this.getAuthCacheDir(), `${email}.json`);\n\t}\n\n\tprivate logAuthCacheStatus(): void {\n\t\tconst cacheDir = this.getAuthCacheDir();\n\t\tconst cacheFile = this.getAuthCacheFile();\n\t\tconst email = (this.config.username || \"\").trim();\n\t\tif (!email) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tfs.mkdirSync(cacheDir, { recursive: true });\n\t\t} catch (err) {\n\t\t\tthis.log.warn(`Cannot create authcache folder ${cacheDir}: ${(err as Error).message}`);\n\t\t\treturn;\n\t\t}\n\t\tif (fs.existsSync(cacheFile)) {\n\t\t\tthis.log.debug(`Anker login cache present: ${cacheFile}`);\n\t\t\treturn;\n\t\t}\n\t\tlet other = \"\";\n\t\ttry {\n\t\t\tconst names = fs.readdirSync(cacheDir).filter(f => f.endsWith(\".json\"));\n\t\t\tif (names.length) {\n\t\t\t\tother = ` Found other file(s) in folder: ${names.join(\", \")} (username must match filename).`;\n\t\t\t}\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t\tthis.log.warn(\n\t\t\t`No Anker login cache at ${cacheFile}.${other} ` +\n\t\t\t\t\"Without this file every adapter restart triggers a new API login (often captcha 100032). \" +\n\t\t\t\t\"Copy <email>.json from a working Anker/Solix integration (e.g. ha-anker-solix) into that folder, then restart.\",\n\t\t);\n\t}\n\n\tprivate getBridgeConfig(): BridgeConfig {\n\t\tconst cacheDir = this.getAuthCacheDir();\n\t\tconst selectedIds = parseSelectedDeviceIds(this.config.selectedDeviceIds);\n\n\t\treturn {\n\t\t\tusername: this.config.username,\n\t\t\tpassword: this.config.password,\n\t\t\tcountry: this.config.country || \"DE\",\n\t\t\tmqttUsage: this.config.mqttUsage !== false,\n\t\t\tcacheDir,\n\t\t\tenableAllDevices: this.config.enableAllDevices !== false,\n\t\t\tselectedSiteId: this.config.selectedSiteId || \"\",\n\t\t\tselectedDeviceIds: selectedIds,\n\t\t\tdeviceDetailMultiplier: Math.max(1, Number(this.config.deviceDetailMultiplier) || 10),\n\t\t\trequestDelay: Number(this.config.requestDelay) || 0.3,\n\t\t\trequestTimeout: Number(this.config.requestTimeout) || 10,\n\t\t\tendpointLimit: Number(this.config.endpointLimit) || 10,\n\t\t\tenableCoreEntities: this.config.enableCoreEntities !== false,\n\t\t\tenableEnergyStatistics: !!this.config.enableEnergyStatistics,\n\t\t\tenableEnergyDetail: !!this.config.enableEnergyDetail,\n\t\t\tenablePowerFlows: !!this.config.enablePowerFlows,\n\t\t\tenableDiagnostics: !!this.config.enableDiagnostics,\n\t\t\tenableBinaryIndicators: !!this.config.enableBinaryIndicators,\n\t\t\tenableAdvancedControls: !!this.config.enableAdvancedControls,\n\t\t\tenableSystemOverview: !!this.config.enableSystemOverview,\n\t\t\tenableSitePrice: !!this.config.enableSitePrice,\n\t\t\tenableAccountInfo: !!this.config.enableAccountInfo,\n\t\t\tenableSolarbankMeta: !!this.config.enableSolarbankMeta,\n\t\t\tenableSmartplug: !!this.config.enableSmartplug,\n\t\t\tenablePps: !!this.config.enablePps,\n\t\t\tenableEvCharger: !!this.config.enableEvCharger,\n\t\t\tenableVehicle: !!this.config.enableVehicle,\n\t\t\tenableHes: !!this.config.enableHes,\n\t\t\tenablePowerPanel: !!this.config.enablePowerPanel,\n\t\t\tenableInverter: !!this.config.enableInverter,\n\t\t};\n\t}\n\n\t/** Remove legacy install symlink from old GitHub repo name \"AnkerSolix\". */\n\tprivate cleanupLegacyInstallSymlink(): void {\n\t\tconst alias = path.join(this.adapterDir, \"..\", \"iobroker.AnkerSolix\");\n\t\ttry {\n\t\t\tif (fs.existsSync(alias) && fs.lstatSync(alias).isSymbolicLink()) {\n\t\t\t\tfs.unlinkSync(alias);\n\t\t\t\tthis.log.info(\"Removed legacy symlink iobroker.AnkerSolix\");\n\t\t\t}\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}\n\n\tprivate async ensurePythonDeps(force = false): Promise<boolean> {\n\t\tif (!force && this.config.autoInstallPython === false) {\n\t\t\treturn true;\n\t\t}\n\t\tconst result = await runPythonInstaller(this.config.pythonPath || \"\", this.log);\n\t\tawait this.setState(\"info.pythonReady\", result.ok, true);\n\t\tif (!result.ok) {\n\t\t\tthis.log.warn(`Python setup: ${result.message}`);\n\t\t}\n\t\treturn result.ok;\n\t}\n\n\tprivate async pollOnce(): Promise<void> {\n\t\tif (!this.config.acceptTerms) {\n\t\t\tthis.log.warn(\"Please accept the usage terms in the adapter configuration.\");\n\t\t\tawait this.setState(\"info.connection\", false, true);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.config.username?.trim()) {\n\t\t\tthis.log.warn(\"Anker e-mail (username) is required in adapter settings.\");\n\t\t\tawait this.setState(\"info.connection\", false, true);\n\t\t\treturn;\n\t\t}\n\t\tif (!this.config.password?.trim()) {\n\t\t\tthis.log.warn(\"Password missing \u2013 open instance config in Admin, re-enter Anker password and save.\");\n\t\t\tawait this.setState(\"info.connection\", false, true);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!(await this.ensurePythonDeps())) {\n\t\t\tawait this.setState(\"info.connection\", false, true);\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await runBridge(\"poll\", this.getBridgeConfig(), this.config.pythonPath || \"\", this.log);\n\n\t\t\tif (this.config.enableCurtailmentAvoidance) {\n\t\t\t\tthis.refreshCurtailmentDeviceIds();\n\t\t\t}\n\n\t\t\tconst pollDevices = result.devices as BridgeDevice[] | undefined;\n\t\t\tif (pollDevices?.length) {\n\t\t\t\tthis.rememberDeviceContexts(pollDevices);\n\t\t\t\tthis.rememberDeviceEntities(pollDevices);\n\t\t\t\tawait syncDevices(this, pollDevices);\n\t\t\t}\n\n\t\t\tif (result.nickname) {\n\t\t\t\tawait this.setState(\"account.nickname\", result.nickname, true);\n\t\t\t}\n\n\t\t\tawait this.setState(\"info.connection\", true, true);\n\t\t\tconst detailHint = result.refreshDetails ? \"devices+mqtt\" : \"sites\";\n\t\t\tconst intervalHint =\n\t\t\t\tresult.intervalcount !== undefined && result.deviceintervals !== undefined\n\t\t\t\t\t? `, next detail in ~${result.intervalcount} polls`\n\t\t\t\t\t: \"\";\n\t\t\tthis.log.debug(`Poll OK (${pollDevices?.length ?? 0} devices, ${detailHint}${intervalHint})`);\n\n\t\t\tawait this.runCurtailmentAvoidanceIfEnabled();\n\t\t} catch (error) {\n\t\t\tawait this.setState(\"info.connection\", false, true);\n\t\t\tconst msg = (error as Error).message || String(error);\n\t\t\tif (msg.includes(\"CaptchaRequired\") || msg.includes(\"100032\") || msg.toLowerCase().includes(\"captcha\")) {\n\t\t\t\tconst cacheFile = this.getAuthCacheFile();\n\t\t\t\tconst missing = !fs.existsSync(cacheFile);\n\t\t\t\tconst hint = missing\n\t\t\t\t\t? `Erwartete Datei: ${cacheFile} \u2013 von funktionierender Anker/Solix-Integration (z. B. ha-anker-solix) dorthin kopieren, Ordner anlegen falls n\u00F6tig, Adapter neu starten.`\n\t\t\t\t\t: `Cache vorhanden aber ung\u00FCltig: ${cacheFile} \u2013 frische Datei von HA kopieren oder Passwort in Admin neu speichern.`;\n\t\t\t\tthis.log.error(\n\t\t\t\t\t`Poll failed: ${msg} \u2013 API-Neulogin n\u00F6tig${missing ? \" (kein Login-Cache)\" : \"\"}. ${hint}`,\n\t\t\t\t);\n\t\t\t\tif (missing) {\n\t\t\t\t\tthis.logAuthCacheStatus();\n\t\t\t\t}\n\t\t\t} else if (msg.includes(\"Cached Anker login is invalid\") || msg.includes(\"invalidated by the mobile app\")) {\n\t\t\t\tthis.log.error(\n\t\t\t\t\t`Poll failed: ${msg} \u2013 Gespeicherter API-Token ung\u00FCltig (abgelaufen oder durch App ersetzt). ` +\n\t\t\t\t\t\t\"Nicht \u201ECache l\u00F6schen\u201C \u2013 stattdessen frische authcache-Datei von HA kopieren oder App kurz abmelden, dann neu starten.\",\n\t\t\t\t);\n\t\t\t} else if (msg.includes(\"InvalidCredentials\") || msg.includes(\"Authentication failed\")) {\n\t\t\t\tthis.log.error(\n\t\t\t\t\t`Poll failed: ${msg} \u2013 Check e-mail, password and country (${this.config.country || \"DE\"}). ` +\n\t\t\t\t\t\t\"In Admin use \u201CInstall Python dependencies\u201D tab or restart after saving config; \" +\n\t\t\t\t\t\t\"try country matching your Anker account region.\",\n\t\t\t\t);\n\t\t\t} else if (\n\t\t\t\tmsg.includes(\"26161\") ||\n\t\t\t\tmsg.includes(\"429\") ||\n\t\t\t\tmsg.includes(\"Too Many Requests\") ||\n\t\t\t\tmsg.includes(\"Failed to request\")\n\t\t\t) {\n\t\t\t\tthis.log.warn(\n\t\t\t\t\t`Poll failed (Anker API limit or temporary error): ${msg} \u2013 ` +\n\t\t\t\t\t\t\"adapter will retry; increase scan interval (e.g. 120 s) if this persists.\",\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tthis.log.error(`Poll failed: ${msg}`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getPrimaryDeviceId(): string {\n\t\tconst selected = parseSelectedDeviceIds(this.config.selectedDeviceIds);\n\t\tif (selected[0]) {\n\t\t\treturn selected[0];\n\t\t}\n\t\ttry {\n\t\t\tconst list = JSON.parse(this.config.deviceListJson || '{\"devices\":[]}') as {\n\t\t\t\tdevices?: Array<{ id: string }>;\n\t\t\t};\n\t\t\treturn list.devices?.[0]?.id || \"\";\n\t\t} catch {\n\t\t\treturn \"\";\n\t\t}\n\t}\n\n\tprivate async handleServiceTrigger(stateId: string): Promise<void> {\n\t\tconst ns = `${this.namespace}.services.`;\n\t\tif (!stateId.startsWith(ns)) {\n\t\t\treturn;\n\t\t}\n\t\tconst action = stateId.slice(ns.length);\n\n\t\tif (action === \"refresh_devices\") {\n\t\t\tawait this.pollOnce();\n\t\t\tawait this.setState(stateId, false, true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst serviceActions = [\"get_schedule\", \"clear_schedule\", \"export_systems\", \"get_system_info\"];\n\t\tif (!serviceActions.includes(action)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst params: Record<string, unknown> = {\n\t\t\tdeviceId: this.getPrimaryDeviceId(),\n\t\t\tsiteId: this.config.selectedSiteId || \"\",\n\t\t\tincludeMqtt: this.config.mqttUsage !== false,\n\t\t};\n\n\t\ttry {\n\t\t\tconst serviceConfig: BridgeServiceConfig = {\n\t\t\t\t...this.getBridgeConfig(),\n\t\t\t\tservice: action,\n\t\t\t\tparams,\n\t\t\t};\n\t\t\tconst result = await runBridge(\"service\", serviceConfig, this.config.pythonPath || \"\", this.log);\n\n\t\t\tif (action === \"get_schedule\" && result.schedule !== undefined) {\n\t\t\t\tawait this.setState(SERVICE_STATES.scheduleJson, JSON.stringify(result.schedule, null, 2), true);\n\t\t\t}\n\t\t\tif (action === \"export_systems\" && result.path) {\n\t\t\t\tawait this.setState(SERVICE_STATES.exportResult, String(result.path), true);\n\t\t\t}\n\t\t\tif (action === \"get_system_info\" && result.system !== undefined) {\n\t\t\t\tawait this.setState(SERVICE_STATES.systemInfo, JSON.stringify(result.system, null, 2), true);\n\t\t\t}\n\n\t\t\tawait this.setState(stateId, false, true);\n\t\t} catch (error) {\n\t\t\tthis.log.error(`Service ${action} failed: ${(error as Error).message}`);\n\t\t\tawait this.setState(stateId, false, true);\n\t\t}\n\t}\n\n\tprivate rememberDeviceContexts(devices: BridgeDevice[]): void {\n\t\tfor (const device of devices) {\n\t\t\tconst info = device.info;\n\t\t\tthis.deviceContexts.set(info.id, {\n\t\t\t\ttype: info.type,\n\t\t\t\tsite_id: info.site_id,\n\t\t\t\tdevice_pn: info.device_pn || info.model || \"\",\n\t\t\t\tstation_sn: info.station_sn || \"\",\n\t\t\t\tgeneration: info.generation ?? 0,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate async applyAdapterControl(\n\t\tdeviceId: string,\n\t\tcontrol: string,\n\t\tvalue: string | number | boolean,\n\t\tdeviceContext?: DeviceControlContext,\n\t): Promise<void> {\n\t\tawait runBridge(\n\t\t\t\"set\",\n\t\t\t{\n\t\t\t\t...this.getBridgeConfig(),\n\t\t\t\tdeviceId,\n\t\t\t\tcontrol,\n\t\t\t\tvalue,\n\t\t\t\tdeviceContext,\n\t\t\t},\n\t\t\tthis.config.pythonPath || \"\",\n\t\t\tthis.log,\n\t\t);\n\t}\n\n\tprivate rememberDeviceEntities(devices: BridgeDevice[]): void {\n\t\tfor (const device of devices) {\n\t\t\tthis.deviceEntities.set(device.info.id, device.entities);\n\t\t}\n\t}\n\n\tprivate getCurtailmentConfig(): CurtailmentRunnerConfig {\n\t\tconst modeAfter = this.config.curtailmentModeAfter === \"smart\" ? \"smart\" : \"smartmeter\";\n\t\treturn {\n\t\t\tenabled: true,\n\t\t\tforecastBasePath: (this.config.curtailmentForecastPath || \"solarprognose.0.forecast.00.hourly\").trim(),\n\t\t\tmodeAfter,\n\t\t\tcurtailmentHasCombiner: this.config.curtailmentHasCombiner,\n\t\t\tcurtailmentStandaloneDeviceId: this.config.curtailmentStandaloneDeviceId,\n\t\t\tcurtailmentStandaloneProfile: this.config.curtailmentStandaloneProfile,\n\t\t\tcurtailmentStandaloneBatteryWh: this.config.curtailmentStandaloneBatteryWh,\n\t\t\tcurtailmentCombinerDeviceId: this.config.curtailmentCombinerDeviceId,\n\t\t\tcurtailmentCombinerBatteryWh: this.config.curtailmentCombinerBatteryWh,\n\t\t\tcurtailmentCombinerUnit1: this.config.curtailmentCombinerUnit1,\n\t\t\tcurtailmentCombinerUnit2: this.config.curtailmentCombinerUnit2,\n\t\t\tcurtailmentCombinerUnit3: this.config.curtailmentCombinerUnit3,\n\t\t\tcurtailmentCombinerUnit4: this.config.curtailmentCombinerUnit4,\n\t\t\tcurtailmentDevicesJson: this.config.curtailmentDevicesJson,\n\t\t};\n\t}\n\n\tprivate getCurtailmentHost(): CurtailmentRunnerHost {\n\t\treturn {\n\t\t\tnamespace: this.namespace,\n\t\t\tlog: this.log,\n\t\t\tgetForeignStateAsync: id => this.getForeignStateAsync(id),\n\t\t\tgetForeignObjectAsync: id => this.getForeignObjectAsync(id),\n\t\t\tgetStateAsync: id => this.getStateAsync(id),\n\t\t\tgetDeviceEntities: deviceId => this.deviceEntities.get(deviceId),\n\t\t\tgetDeviceSiteId: deviceId => this.deviceContexts.get(deviceId)?.site_id,\n\t\t\tgetDeviceWritable: deviceId => this.deviceWritable.get(deviceId),\n\t\t\tsetState: async (id, val, ack) => {\n\t\t\t\tawait this.setState(id, val as ioBroker.StateValue, ack ?? true);\n\t\t\t},\n\t\t\tgetDeviceContext: deviceId => this.deviceContexts.get(deviceId),\n\t\t\tapplyControl: (deviceId, control, value, deviceContext) =>\n\t\t\t\tthis.applyAdapterControl(deviceId, control, value, deviceContext),\n\t\t};\n\t}\n\n\tprivate refreshCurtailmentDeviceIds(): void {\n\t\tthis.curtailmentDeviceIds.clear();\n\t\tif (!this.config.enableCurtailmentAvoidance) {\n\t\t\treturn;\n\t\t}\n\t\tfor (const d of resolveCurtailmentDevices(this.getCurtailmentConfig())) {\n\t\t\tif (d.enabled) {\n\t\t\t\tthis.curtailmentDeviceIds.add(d.deviceId);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handleCurtailmentPvUpdated(deviceId: string, livePvW: number): void {\n\t\tif (!this.config.enableCurtailmentAvoidance || !this.curtailmentDeviceIds.has(deviceId)) {\n\t\t\treturn;\n\t\t}\n\t\tconst rounded = Math.round(livePvW);\n\t\tif (this.lastNotifiedPvW.get(deviceId) === rounded) {\n\t\t\treturn;\n\t\t}\n\t\tthis.lastNotifiedPvW.set(deviceId, rounded);\n\t\tvoid this.runCurtailmentExportOnPvChange(deviceId, rounded);\n\t}\n\n\tprivate handleCurtailmentSystemPvUpdated(siteId: string, livePvW: number): void {\n\t\tif (!this.config.enableCurtailmentAvoidance) {\n\t\t\treturn;\n\t\t}\n\t\tconst rounded = Math.round(livePvW);\n\t\tfor (const deviceId of this.curtailmentDeviceIds) {\n\t\t\tconst ctx = this.deviceContexts.get(deviceId);\n\t\t\tif (ctx?.site_id !== siteId) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (this.lastNotifiedPvW.get(deviceId) === rounded) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.lastNotifiedPvW.set(deviceId, rounded);\n\t\t\tvoid this.runCurtailmentExportOnPvChange(deviceId, rounded);\n\t\t}\n\t}\n\n\tprivate async runCurtailmentExportOnPvChange(deviceId: string, livePvW: number): Promise<void> {\n\t\tif (!this.config.enableCurtailmentAvoidance) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait runCurtailmentOnPvChange(this.getCurtailmentHost(), this.getCurtailmentConfig(), deviceId, livePvW);\n\t\t} catch (err) {\n\t\t\tthis.log.debug(`Curtailment PV follow: ${(err as Error).message}`);\n\t\t}\n\t}\n\n\tprivate subscribeCurtailmentPvStates(): void {\n\t\tif (!this.config.enableCurtailmentAvoidance) {\n\t\t\treturn;\n\t\t}\n\t\tconst ns = this.namespace;\n\t\tthis.subscribeStates(`${ns}.system.*.sensors.total_pv_power`);\n\t\tfor (const channel of [\"solarbank\", \"combiner_box\"]) {\n\t\t\tthis.subscribeStates(`${ns}.${channel}.*.sensors.total_pv_power`);\n\t\t\tthis.subscribeStates(`${ns}.${channel}.*.sensors.input_power`);\n\t\t}\n\t}\n\n\tprivate async runCurtailmentAvoidanceIfEnabled(): Promise<void> {\n\t\tif (!this.config.enableCurtailmentAvoidance) {\n\t\t\treturn;\n\t\t}\n\t\tthis.refreshCurtailmentDeviceIds();\n\t\ttry {\n\t\t\tawait runCurtailmentAvoidance(this.getCurtailmentHost(), this.getCurtailmentConfig());\n\t\t} catch (err) {\n\t\t\tthis.log.warn(`Curtailment avoidance: ${(err as Error).message}`);\n\t\t}\n\t}\n\n\tprivate schedulePollAfterControl(): void {\n\t\tif (this.pollAfterControlTimer) {\n\t\t\tclearTimeout(this.pollAfterControlTimer);\n\t\t}\n\t\tthis.pollAfterControlTimer = setTimeout(() => {\n\t\t\tthis.pollAfterControlTimer = undefined;\n\t\t\tvoid this.pollOnce();\n\t\t}, 12_000);\n\t}\n\n\tprivate async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise<void> {\n\t\tif (!state) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.config.enableCurtailmentAvoidance) {\n\t\t\tconst n = Number(state.val);\n\t\t\tif (Number.isFinite(n) && n >= 0) {\n\t\t\t\tconst systemPv = parseSystemPvStateId(this.namespace, id);\n\t\t\t\tif (systemPv) {\n\t\t\t\t\tthis.handleCurtailmentSystemPvUpdated(systemPv.siteId, n);\n\t\t\t\t} else {\n\t\t\t\t\tconst pv = parsePvSensorStateId(this.namespace, id);\n\t\t\t\t\tif (pv) {\n\t\t\t\t\t\tthis.handleCurtailmentPvUpdated(pv.deviceId, n);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (state.ack) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (id.startsWith(`${this.namespace}.services.`) && state.val === true) {\n\t\t\tawait this.handleServiceTrigger(id);\n\t\t\treturn;\n\t\t}\n\n\t\tconst control = parseControlStateId(this.namespace, id);\n\t\tif (!control) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst current = await this.getStateAsync(id);\n\t\tif (\n\t\t\tcurrent?.ack &&\n\t\t\tcurrent.val !== null &&\n\t\t\tcurrent.val !== undefined &&\n\t\t\tString(current.val) === String(state.val)\n\t\t) {\n\t\t\tawait this.setState(id, { val: state.val, ack: true });\n\t\t\treturn;\n\t\t}\n\n\t\tconst value = state.val as string | number | boolean;\n\t\tconst deviceContext = this.deviceContexts.get(control.deviceId);\n\n\t\tthis.controlQueue.enqueue({\n\t\t\tstateId: id,\n\t\t\texecute: async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait runBridge(\n\t\t\t\t\t\t\"set\",\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t...this.getBridgeConfig(),\n\t\t\t\t\t\t\tdeviceId: control.deviceId,\n\t\t\t\t\t\t\tcontrol: control.control,\n\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t\tdeviceContext,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tthis.config.pythonPath || \"\",\n\t\t\t\t\t\tthis.log,\n\t\t\t\t\t);\n\t\t\t\t\tawait this.setState(id, { val: value, ack: true });\n\t\t\t\t\tthis.log.info(`Applied ${control.control} on ${control.deviceId}`);\n\t\t\t\t\tthis.schedulePollAfterControl();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = (error as Error).message;\n\t\t\t\t\tif (message.includes(\"429\") || message.includes(\"Too Many Requests\")) {\n\t\t\t\t\t\tthis.log.warn(\n\t\t\t\t\t\t\t`Control rate-limited for ${id} \u2013 wait ~1 minute before retrying (Anker API limit).`,\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.log.error(`Control failed for ${id}: ${message}`);\n\t\t\t\t\t}\n\t\t\t\t\tawait this.setState(id, { val: value, ack: false });\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t}\n\n\tprivate async onMessage(obj: ioBroker.Message): Promise<void> {\n\t\tif (!obj?.command) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst respond = (response: unknown): void => {\n\t\t\tif (obj.callback) {\n\t\t\t\tthis.sendTo(obj.from, obj.command, response, obj.callback);\n\t\t\t}\n\t\t};\n\n\t\ttry {\n\t\t\tif (obj.command === \"clearAuthCache\") {\n\t\t\t\tconst cacheDir = this.getAuthCacheDir();\n\t\t\t\tconst fs = await import(\"node:fs/promises\");\n\t\t\t\ttry {\n\t\t\t\t\tconst files = await fs.readdir(cacheDir);\n\t\t\t\t\tawait Promise.all(files.map(f => fs.unlink(path.join(cacheDir, f)).catch(() => undefined)));\n\t\t\t\t\tawait stopBridgeDaemon();\n\t\t\t\t\tawait ensureBridgeDaemon(this.getBridgeConfig(), this.config.pythonPath || \"\", this.log);\n\t\t\t\t\tthis.log.warn(\n\t\t\t\t\t\t`Anker login cache cleared (${files.length} file(s) in ${cacheDir}). ` +\n\t\t\t\t\t\t\t\"Next poll requires a new API login; on many hosts Anker returns captcha (100032). \" +\n\t\t\t\t\t\t\t`Restore authcache/${(this.config.username || \"\").trim()}.json from HA or retry login when cloud allows it.`,\n\t\t\t\t\t);\n\t\t\t\t\trespond({ ok: true, cleared: files.length });\n\t\t\t\t} catch {\n\t\t\t\t\tthis.log.warn(\n\t\t\t\t\t\t`Anker login cache clear requested but folder empty or missing (${cacheDir}). ` +\n\t\t\t\t\t\t\t\"Adapter must complete a successful API login to create authcache/<email>.json.\",\n\t\t\t\t\t);\n\t\t\t\t\trespond({ ok: true, cleared: 0 });\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (obj.command === \"installPython\") {\n\t\t\t\tconst ok = await this.ensurePythonDeps(true);\n\t\t\t\trespond({ ok });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (obj.command === \"loadDevices\") {\n\t\t\t\tif (!this.config.username || !this.config.password) {\n\t\t\t\t\trespond({ error: \"Credentials required\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tawait this.ensurePythonDeps();\n\t\t\t\tconst result = await runBridge(\n\t\t\t\t\t\"list_devices\",\n\t\t\t\t\tthis.getBridgeConfig(),\n\t\t\t\t\tthis.config.pythonPath || \"\",\n\t\t\t\t\tthis.log,\n\t\t\t\t);\n\t\t\t\tconst payload = {\n\t\t\t\t\tsites: result.sites || [],\n\t\t\t\t\tdevices: result.devices || [],\n\t\t\t\t};\n\t\t\t\trespond({ ok: true, deviceListJson: JSON.stringify(payload, null, 2), ...payload });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\trespond({ error: `Unknown command ${obj.command}` });\n\t\t} catch (error) {\n\t\t\trespond({ error: (error as Error).message });\n\t\t}\n\t}\n\n\tprivate async onReady(): Promise<void> {\n\t\tthis.cleanupLegacyInstallSymlink();\n\n\t\tawait this.setObjectNotExistsAsync(\"account\", {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: \"Account\" },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(\"account.nickname\", {\n\t\t\ttype: \"state\",\n\t\t\tcommon: {\n\t\t\t\tname: \"Account nickname\",\n\t\t\t\ttype: \"string\",\n\t\t\t\trole: \"info\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(\"info.pythonReady\", {\n\t\t\ttype: \"state\",\n\t\t\tcommon: {\n\t\t\t\tname: \"Python dependencies ready\",\n\t\t\t\ttype: \"boolean\",\n\t\t\t\trole: \"indicator\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: false,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\n\t\tawait setupServiceStates(this);\n\t\tawait setupCurtailmentStates(this);\n\t\t(this as CurtailmentPvSyncHost).onCurtailmentPvUpdated = (deviceId, livePvW) =>\n\t\t\tthis.handleCurtailmentPvUpdated(deviceId, livePvW);\n\t\t(this as CurtailmentPvSyncHost).onCurtailmentSystemPvUpdated = (siteId, livePvW) =>\n\t\t\tthis.handleCurtailmentSystemPvUpdated(siteId, livePvW);\n\t\tthis.refreshCurtailmentDeviceIds();\n\t\tawait this.setState(\"info.connection\", false, true);\n\n\t\tconst intervalSec = Math.max(30, Number(this.config.scanInterval) || 60);\n\t\tthis.log.info(\n\t\t\t`Anker Solix adapter started (poll every ${intervalSec}s, MQTT: ${this.config.mqttUsage !== false})`,\n\t\t);\n\n\t\tawait this.ensurePythonDeps();\n\n\t\tthis.logAuthCacheStatus();\n\n\t\tawait ensureBridgeDaemon(this.getBridgeConfig(), this.config.pythonPath || \"\", this.log);\n\n\t\tthis.subscribeStates(`${this.namespace}.*.control.*`);\n\t\tthis.subscribeStates(`${this.namespace}.services.*`);\n\t\tthis.subscribeCurtailmentPvStates();\n\n\t\tawait this.pollOnce();\n\t\tthis.pollTimer = this.setInterval(() => {\n\t\t\tvoid this.pollOnce();\n\t\t}, intervalSec * 1000);\n\t}\n\n\tprivate onUnload(callback: () => void): void {\n\t\tif (this.pollTimer) {\n\t\t\tthis.clearInterval(this.pollTimer);\n\t\t\tthis.pollTimer = undefined;\n\t\t}\n\t\tif (this.pollAfterControlTimer) {\n\t\t\tclearTimeout(this.pollAfterControlTimer);\n\t\t\tthis.pollAfterControlTimer = undefined;\n\t\t}\n\t\tthis.lastNotifiedPvW.clear();\n\t\t(this as CurtailmentPvSyncHost).onCurtailmentPvUpdated = undefined;\n\t\t(this as CurtailmentPvSyncHost).onCurtailmentSystemPvUpdated = undefined;\n\t\tvoid stopBridgeDaemon().finally(() => callback());\n\t}\n}\n\nif (require.main !== module) {\n\tmodule.exports = (options: Partial<utils.AdapterOptions> | undefined) => new AnkerSolix(options);\n} else {\n\t(() => new AnkerSolix())();\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/*\n * ioBroker Anker Solix adapter\n * Based on https://github.com/thomluther/ha-anker-solix (Home Assistant integration)\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nimport * as utils from \"@iobroker/adapter-core\";\n\nimport { parseSelectedDeviceIds } from \"./lib/configHelpers\";\nimport { ControlQueue } from \"./lib/controlQueue\";\nimport { parsePvSensorStateId, parseSystemPvStateId } from \"./lib/curtailmentPower\";\nimport {\n\trunCurtailmentAvoidance,\n\trunCurtailmentOnPvChange,\n\ttype CurtailmentRunnerConfig,\n\ttype CurtailmentRunnerHost,\n} from \"./lib/curtailmentRunner\";\nimport { setupCurtailmentStates } from \"./lib/curtailmentStates\";\nimport { runPythonInstaller } from \"./lib/ensurePython\";\nimport { ensureBridgeDaemon, runBridge, stopBridgeDaemon } from \"./lib/pythonBridge\";\nimport { SERVICE_STATES, setupServiceStates } from \"./lib/services\";\nimport { resolveCurtailmentDevices } from \"./lib/curtailmentConfig\";\nimport { parseControlStateId, syncDevices, type CurtailmentPvSyncHost } from \"./lib/stateSync\";\nimport type { BridgeConfig, BridgeDevice, BridgeServiceConfig, DeviceControlContext } from \"./lib/types\";\n\nclass AnkerSolix extends utils.Adapter {\n\tprivate pollTimer: ioBroker.Interval | undefined;\n\tprivate readonly controlQueue = new ControlQueue();\n\tprivate readonly deviceContexts = new Map<string, DeviceControlContext>();\n\tprivate readonly deviceEntities = new Map<string, Record<string, unknown>>();\n\tprivate readonly deviceWritable = new Map<string, string[]>();\n\tprivate readonly lastNotifiedPvW = new Map<string, number>();\n\tprivate readonly curtailmentDeviceIds = new Set<string>();\n\tprivate pollAfterControlTimer: NodeJS.Timeout | undefined;\n\n\tpublic constructor(options: Partial<utils.AdapterOptions> = {}) {\n\t\tsuper({\n\t\t\t...options,\n\t\t\tname: \"anker-solix\",\n\t\t});\n\t\tthis.on(\"ready\", this.onReady.bind(this));\n\t\tthis.on(\"stateChange\", this.onStateChange.bind(this));\n\t\tthis.on(\"message\", this.onMessage.bind(this));\n\t\tthis.on(\"unload\", this.onUnload.bind(this));\n\t}\n\n\tprivate getAuthCacheDir(): string {\n\t\treturn path.join(utils.getAbsoluteInstanceDataDir(this), \"authcache\");\n\t}\n\n\tprivate getAuthCacheFile(): string {\n\t\tconst email = (this.config.username || \"\").trim();\n\t\treturn path.join(this.getAuthCacheDir(), `${email}.json`);\n\t}\n\n\tprivate logAuthCacheStatus(): void {\n\t\tconst cacheDir = this.getAuthCacheDir();\n\t\tconst cacheFile = this.getAuthCacheFile();\n\t\tconst email = (this.config.username || \"\").trim();\n\t\tif (!email) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tfs.mkdirSync(cacheDir, { recursive: true });\n\t\t} catch (err) {\n\t\t\tthis.log.warn(`Cannot create authcache folder ${cacheDir}: ${(err as Error).message}`);\n\t\t\treturn;\n\t\t}\n\t\tif (fs.existsSync(cacheFile)) {\n\t\t\tthis.log.debug(`Anker login cache present: ${cacheFile}`);\n\t\t\treturn;\n\t\t}\n\t\tlet other = \"\";\n\t\ttry {\n\t\t\tconst names = fs.readdirSync(cacheDir).filter(f => f.endsWith(\".json\"));\n\t\t\tif (names.length) {\n\t\t\t\tother = ` Found other file(s) in folder: ${names.join(\", \")} (username must match filename).`;\n\t\t\t}\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t\tthis.log.warn(\n\t\t\t`No Anker login cache at ${cacheFile}.${other} ` +\n\t\t\t\t\"Without this file every adapter restart triggers a new API login (often captcha 100032). \" +\n\t\t\t\t\"Copy <email>.json from a working Anker/Solix integration (e.g. ha-anker-solix) into that folder, then restart.\",\n\t\t);\n\t}\n\n\tprivate getBridgeConfig(): BridgeConfig {\n\t\tconst cacheDir = this.getAuthCacheDir();\n\t\tconst selectedIds = parseSelectedDeviceIds(this.config.selectedDeviceIds);\n\n\t\treturn {\n\t\t\tusername: this.config.username,\n\t\t\tpassword: this.config.password,\n\t\t\tcountry: this.config.country || \"DE\",\n\t\t\tmqttUsage: this.config.mqttUsage !== false,\n\t\t\tcacheDir,\n\t\t\tenableAllDevices: this.config.enableAllDevices !== false,\n\t\t\tselectedSiteId: this.config.selectedSiteId || \"\",\n\t\t\tselectedDeviceIds: selectedIds,\n\t\t\tdeviceDetailMultiplier: Math.max(1, Number(this.config.deviceDetailMultiplier) || 10),\n\t\t\trequestDelay: Number(this.config.requestDelay) || 0.3,\n\t\t\trequestTimeout: Number(this.config.requestTimeout) || 10,\n\t\t\tendpointLimit: Number(this.config.endpointLimit) || 10,\n\t\t\tenableCoreEntities: this.config.enableCoreEntities !== false,\n\t\t\tenableEnergyStatistics: !!this.config.enableEnergyStatistics,\n\t\t\tenableEnergyDetail: !!this.config.enableEnergyDetail,\n\t\t\tenablePowerFlows: !!this.config.enablePowerFlows,\n\t\t\tenableDiagnostics: !!this.config.enableDiagnostics,\n\t\t\tenableBinaryIndicators: !!this.config.enableBinaryIndicators,\n\t\t\tenableAdvancedControls: !!this.config.enableAdvancedControls,\n\t\t\tenableSystemOverview: !!this.config.enableSystemOverview,\n\t\t\tenableSitePrice: !!this.config.enableSitePrice,\n\t\t\tenableAccountInfo: !!this.config.enableAccountInfo,\n\t\t\tenableSolarbankMeta: !!this.config.enableSolarbankMeta,\n\t\t\tenableSmartplug: !!this.config.enableSmartplug,\n\t\t\tenablePps: !!this.config.enablePps,\n\t\t\tenableEvCharger: !!this.config.enableEvCharger,\n\t\t\tenableVehicle: !!this.config.enableVehicle,\n\t\t\tenableHes: !!this.config.enableHes,\n\t\t\tenablePowerPanel: !!this.config.enablePowerPanel,\n\t\t\tenableInverter: !!this.config.enableInverter,\n\t\t};\n\t}\n\n\t/** Remove legacy install symlink from old GitHub repo name \"AnkerSolix\". */\n\tprivate cleanupLegacyInstallSymlink(): void {\n\t\tconst alias = path.join(this.adapterDir, \"..\", \"iobroker.AnkerSolix\");\n\t\ttry {\n\t\t\tif (fs.existsSync(alias) && fs.lstatSync(alias).isSymbolicLink()) {\n\t\t\t\tfs.unlinkSync(alias);\n\t\t\t\tthis.log.info(\"Removed legacy symlink iobroker.AnkerSolix\");\n\t\t\t}\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}\n\n\tprivate async ensurePythonDeps(force = false): Promise<boolean> {\n\t\tif (!force && this.config.autoInstallPython === false) {\n\t\t\treturn true;\n\t\t}\n\t\tconst result = await runPythonInstaller(this.config.pythonPath || \"\", this.log);\n\t\tawait this.setState(\"info.pythonReady\", result.ok, true);\n\t\tif (!result.ok) {\n\t\t\tthis.log.warn(`Python setup: ${result.message}`);\n\t\t}\n\t\treturn result.ok;\n\t}\n\n\tprivate async pollOnce(): Promise<void> {\n\t\tif (!this.config.acceptTerms) {\n\t\t\tthis.log.warn(\"Please accept the usage terms in the adapter configuration.\");\n\t\t\tawait this.setState(\"info.connection\", false, true);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.config.username?.trim()) {\n\t\t\tthis.log.warn(\"Anker e-mail (username) is required in adapter settings.\");\n\t\t\tawait this.setState(\"info.connection\", false, true);\n\t\t\treturn;\n\t\t}\n\t\tif (!this.config.password?.trim()) {\n\t\t\tthis.log.warn(\"Password missing \u2013 open instance config in Admin, re-enter Anker password and save.\");\n\t\t\tawait this.setState(\"info.connection\", false, true);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!(await this.ensurePythonDeps())) {\n\t\t\tawait this.setState(\"info.connection\", false, true);\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await runBridge(\"poll\", this.getBridgeConfig(), this.config.pythonPath || \"\", this.log);\n\n\t\t\tif (this.config.enableCurtailmentAvoidance) {\n\t\t\t\tthis.refreshCurtailmentDeviceIds();\n\t\t\t}\n\n\t\t\tconst pollDevices = result.devices as BridgeDevice[] | undefined;\n\t\t\tif (pollDevices?.length) {\n\t\t\t\tthis.rememberDeviceContexts(pollDevices);\n\t\t\t\tthis.rememberDeviceEntities(pollDevices);\n\t\t\t\tawait syncDevices(this, pollDevices);\n\t\t\t}\n\n\t\t\tif (result.nickname) {\n\t\t\t\tawait this.setState(\"account.nickname\", result.nickname, true);\n\t\t\t}\n\n\t\t\tawait this.setState(\"info.connection\", true, true);\n\t\t\tconst detailHint = result.refreshDetails ? \"devices+mqtt\" : \"sites\";\n\t\t\tconst intervalHint =\n\t\t\t\tresult.intervalcount !== undefined && result.deviceintervals !== undefined\n\t\t\t\t\t? `, next detail in ~${result.intervalcount} polls`\n\t\t\t\t\t: \"\";\n\t\t\tthis.log.debug(`Poll OK (${pollDevices?.length ?? 0} devices, ${detailHint}${intervalHint})`);\n\n\t\t\tawait this.runCurtailmentAvoidanceIfEnabled();\n\t\t} catch (error) {\n\t\t\tawait this.setState(\"info.connection\", false, true);\n\t\t\tconst msg = (error as Error).message || String(error);\n\t\t\tif (msg.includes(\"CaptchaRequired\") || msg.includes(\"100032\") || msg.toLowerCase().includes(\"captcha\")) {\n\t\t\t\tconst cacheFile = this.getAuthCacheFile();\n\t\t\t\tconst missing = !fs.existsSync(cacheFile);\n\t\t\t\tconst hint = missing\n\t\t\t\t\t? `Erwartete Datei: ${cacheFile} \u2013 von funktionierender Anker/Solix-Integration (z. B. ha-anker-solix) dorthin kopieren, Ordner anlegen falls n\u00F6tig, Adapter neu starten.`\n\t\t\t\t\t: `Cache vorhanden aber ung\u00FCltig: ${cacheFile} \u2013 frische Datei von HA kopieren oder Passwort in Admin neu speichern.`;\n\t\t\t\tthis.log.error(\n\t\t\t\t\t`Poll failed: ${msg} \u2013 API-Neulogin n\u00F6tig${missing ? \" (kein Login-Cache)\" : \"\"}. ${hint}`,\n\t\t\t\t);\n\t\t\t\tif (missing) {\n\t\t\t\t\tthis.logAuthCacheStatus();\n\t\t\t\t}\n\t\t\t} else if (msg.includes(\"Cached Anker login is invalid\") || msg.includes(\"invalidated by the mobile app\")) {\n\t\t\t\tthis.log.error(\n\t\t\t\t\t`Poll failed: ${msg} \u2013 Gespeicherter API-Token ung\u00FCltig (abgelaufen oder durch App ersetzt). ` +\n\t\t\t\t\t\t\"Nicht \u201ECache l\u00F6schen\u201C \u2013 stattdessen frische authcache-Datei von HA kopieren oder App kurz abmelden, dann neu starten.\",\n\t\t\t\t);\n\t\t\t} else if (msg.includes(\"InvalidCredentials\") || msg.includes(\"Authentication failed\")) {\n\t\t\t\tthis.log.error(\n\t\t\t\t\t`Poll failed: ${msg} \u2013 Check e-mail, password and country (${this.config.country || \"DE\"}). ` +\n\t\t\t\t\t\t\"In Admin use \u201CInstall Python dependencies\u201D tab or restart after saving config; \" +\n\t\t\t\t\t\t\"try country matching your Anker account region.\",\n\t\t\t\t);\n\t\t\t} else if (\n\t\t\t\tmsg.includes(\"26161\") ||\n\t\t\t\tmsg.includes(\"429\") ||\n\t\t\t\tmsg.includes(\"Too Many Requests\") ||\n\t\t\t\tmsg.includes(\"Failed to request\")\n\t\t\t) {\n\t\t\t\tthis.log.warn(\n\t\t\t\t\t`Poll failed (Anker API limit or temporary error): ${msg} \u2013 ` +\n\t\t\t\t\t\t\"adapter will retry; increase scan interval (e.g. 120 s) if this persists.\",\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tthis.log.error(`Poll failed: ${msg}`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getPrimaryDeviceId(): string {\n\t\tconst selected = parseSelectedDeviceIds(this.config.selectedDeviceIds);\n\t\tif (selected[0]) {\n\t\t\treturn selected[0];\n\t\t}\n\t\ttry {\n\t\t\tconst list = JSON.parse(this.config.deviceListJson || '{\"devices\":[]}') as {\n\t\t\t\tdevices?: Array<{ id: string }>;\n\t\t\t};\n\t\t\treturn list.devices?.[0]?.id || \"\";\n\t\t} catch {\n\t\t\treturn \"\";\n\t\t}\n\t}\n\n\tprivate async handleServiceTrigger(stateId: string): Promise<void> {\n\t\tconst ns = `${this.namespace}.services.`;\n\t\tif (!stateId.startsWith(ns)) {\n\t\t\treturn;\n\t\t}\n\t\tconst action = stateId.slice(ns.length);\n\n\t\tif (action === \"refresh_devices\") {\n\t\t\tawait this.pollOnce();\n\t\t\tawait this.setState(stateId, false, true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst serviceActions = [\"get_schedule\", \"clear_schedule\", \"export_systems\", \"get_system_info\"];\n\t\tif (!serviceActions.includes(action)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst params: Record<string, unknown> = {\n\t\t\tdeviceId: this.getPrimaryDeviceId(),\n\t\t\tsiteId: this.config.selectedSiteId || \"\",\n\t\t\tincludeMqtt: this.config.mqttUsage !== false,\n\t\t};\n\n\t\ttry {\n\t\t\tconst serviceConfig: BridgeServiceConfig = {\n\t\t\t\t...this.getBridgeConfig(),\n\t\t\t\tservice: action,\n\t\t\t\tparams,\n\t\t\t};\n\t\t\tconst result = await runBridge(\"service\", serviceConfig, this.config.pythonPath || \"\", this.log);\n\n\t\t\tif (action === \"get_schedule\" && result.schedule !== undefined) {\n\t\t\t\tawait this.setState(SERVICE_STATES.scheduleJson, JSON.stringify(result.schedule, null, 2), true);\n\t\t\t}\n\t\t\tif (action === \"export_systems\" && result.path) {\n\t\t\t\tawait this.setState(SERVICE_STATES.exportResult, String(result.path), true);\n\t\t\t}\n\t\t\tif (action === \"get_system_info\" && result.system !== undefined) {\n\t\t\t\tawait this.setState(SERVICE_STATES.systemInfo, JSON.stringify(result.system, null, 2), true);\n\t\t\t}\n\n\t\t\tawait this.setState(stateId, false, true);\n\t\t} catch (error) {\n\t\t\tthis.log.error(`Service ${action} failed: ${(error as Error).message}`);\n\t\t\tawait this.setState(stateId, false, true);\n\t\t}\n\t}\n\n\tprivate rememberDeviceContexts(devices: BridgeDevice[]): void {\n\t\tfor (const device of devices) {\n\t\t\tconst info = device.info;\n\t\t\tthis.deviceContexts.set(info.id, {\n\t\t\t\ttype: info.type,\n\t\t\t\tsite_id: info.site_id,\n\t\t\t\tdevice_pn: info.device_pn || info.model || \"\",\n\t\t\t\tstation_sn: info.station_sn || \"\",\n\t\t\t\tgeneration: info.generation ?? 0,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate async applyAdapterControl(\n\t\tdeviceId: string,\n\t\tcontrol: string,\n\t\tvalue: string | number | boolean,\n\t\tdeviceContext?: DeviceControlContext,\n\t\topts?: { acOutputApiOnly?: boolean },\n\t): Promise<void> {\n\t\tawait runBridge(\n\t\t\t\"set\",\n\t\t\t{\n\t\t\t\t...this.getBridgeConfig(),\n\t\t\t\tdeviceId,\n\t\t\t\tcontrol,\n\t\t\t\tvalue,\n\t\t\t\tdeviceContext,\n\t\t\t\tacOutputApiOnly: opts?.acOutputApiOnly,\n\t\t\t},\n\t\t\tthis.config.pythonPath || \"\",\n\t\t\tthis.log,\n\t\t);\n\t}\n\n\tprivate rememberDeviceEntities(devices: BridgeDevice[]): void {\n\t\tfor (const device of devices) {\n\t\t\tthis.deviceEntities.set(device.info.id, device.entities);\n\t\t}\n\t}\n\n\tprivate getCurtailmentConfig(): CurtailmentRunnerConfig {\n\t\tconst modeAfter = this.config.curtailmentModeAfter === \"smart\" ? \"smart\" : \"smartmeter\";\n\t\treturn {\n\t\t\tenabled: true,\n\t\t\tforecastBasePath: (this.config.curtailmentForecastPath || \"solarprognose.0.forecast.00.hourly\").trim(),\n\t\t\tmodeAfter,\n\t\t\tcurtailmentHasCombiner: this.config.curtailmentHasCombiner,\n\t\t\tcurtailmentStandaloneDeviceId: this.config.curtailmentStandaloneDeviceId,\n\t\t\tcurtailmentStandaloneProfile: this.config.curtailmentStandaloneProfile,\n\t\t\tcurtailmentStandaloneBatteryWh: this.config.curtailmentStandaloneBatteryWh,\n\t\t\tcurtailmentCombinerDeviceId: this.config.curtailmentCombinerDeviceId,\n\t\t\tcurtailmentCombinerBatteryWh: this.config.curtailmentCombinerBatteryWh,\n\t\t\tcurtailmentCombinerUnit1: this.config.curtailmentCombinerUnit1,\n\t\t\tcurtailmentCombinerUnit2: this.config.curtailmentCombinerUnit2,\n\t\t\tcurtailmentCombinerUnit3: this.config.curtailmentCombinerUnit3,\n\t\t\tcurtailmentCombinerUnit4: this.config.curtailmentCombinerUnit4,\n\t\t\tcurtailmentDevicesJson: this.config.curtailmentDevicesJson,\n\t\t};\n\t}\n\n\tprivate getCurtailmentHost(): CurtailmentRunnerHost {\n\t\treturn {\n\t\t\tnamespace: this.namespace,\n\t\t\tlog: this.log,\n\t\t\tgetForeignStateAsync: id => this.getForeignStateAsync(id),\n\t\t\tgetForeignObjectAsync: id => this.getForeignObjectAsync(id),\n\t\t\tgetStateAsync: id => this.getStateAsync(id),\n\t\t\tgetDeviceEntities: deviceId => this.deviceEntities.get(deviceId),\n\t\t\tgetDeviceSiteId: deviceId => this.deviceContexts.get(deviceId)?.site_id,\n\t\t\tgetDeviceWritable: deviceId => this.deviceWritable.get(deviceId),\n\t\t\tsetState: async (id, val, ack) => {\n\t\t\t\tawait this.setState(id, val as ioBroker.StateValue, ack ?? true);\n\t\t\t},\n\t\t\tgetDeviceContext: deviceId => this.deviceContexts.get(deviceId),\n\t\t\tapplyControl: (deviceId, control, value, deviceContext, opts) =>\n\t\t\t\tthis.applyAdapterControl(deviceId, control, value, deviceContext, opts),\n\t\t};\n\t}\n\n\tprivate refreshCurtailmentDeviceIds(): void {\n\t\tthis.curtailmentDeviceIds.clear();\n\t\tif (!this.config.enableCurtailmentAvoidance) {\n\t\t\treturn;\n\t\t}\n\t\tfor (const d of resolveCurtailmentDevices(this.getCurtailmentConfig())) {\n\t\t\tif (d.enabled) {\n\t\t\t\tthis.curtailmentDeviceIds.add(d.deviceId);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handleCurtailmentPvUpdated(deviceId: string, livePvW: number): void {\n\t\tif (!this.config.enableCurtailmentAvoidance || !this.curtailmentDeviceIds.has(deviceId)) {\n\t\t\treturn;\n\t\t}\n\t\tconst rounded = Math.round(livePvW);\n\t\tif (this.lastNotifiedPvW.get(deviceId) === rounded) {\n\t\t\treturn;\n\t\t}\n\t\tthis.lastNotifiedPvW.set(deviceId, rounded);\n\t\tvoid this.runCurtailmentExportOnPvChange(deviceId, rounded);\n\t}\n\n\tprivate handleCurtailmentSystemPvUpdated(siteId: string, livePvW: number): void {\n\t\tif (!this.config.enableCurtailmentAvoidance) {\n\t\t\treturn;\n\t\t}\n\t\tconst rounded = Math.round(livePvW);\n\t\tfor (const deviceId of this.curtailmentDeviceIds) {\n\t\t\tconst ctx = this.deviceContexts.get(deviceId);\n\t\t\tif (ctx?.site_id !== siteId) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (this.lastNotifiedPvW.get(deviceId) === rounded) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.lastNotifiedPvW.set(deviceId, rounded);\n\t\t\tvoid this.runCurtailmentExportOnPvChange(deviceId, rounded);\n\t\t}\n\t}\n\n\tprivate async runCurtailmentExportOnPvChange(deviceId: string, livePvW: number): Promise<void> {\n\t\tif (!this.config.enableCurtailmentAvoidance) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait runCurtailmentOnPvChange(this.getCurtailmentHost(), this.getCurtailmentConfig(), deviceId, livePvW);\n\t\t} catch (err) {\n\t\t\tthis.log.debug(`Curtailment PV follow: ${(err as Error).message}`);\n\t\t}\n\t}\n\n\tprivate subscribeCurtailmentPvStates(): void {\n\t\tif (!this.config.enableCurtailmentAvoidance) {\n\t\t\treturn;\n\t\t}\n\t\tconst ns = this.namespace;\n\t\tthis.subscribeStates(`${ns}.system.*.sensors.total_pv_power`);\n\t\tfor (const channel of [\"solarbank\", \"combiner_box\"]) {\n\t\t\tthis.subscribeStates(`${ns}.${channel}.*.sensors.total_pv_power`);\n\t\t\tthis.subscribeStates(`${ns}.${channel}.*.sensors.input_power`);\n\t\t}\n\t}\n\n\tprivate async runCurtailmentAvoidanceIfEnabled(): Promise<void> {\n\t\tif (!this.config.enableCurtailmentAvoidance) {\n\t\t\treturn;\n\t\t}\n\t\tthis.refreshCurtailmentDeviceIds();\n\t\ttry {\n\t\t\tawait runCurtailmentAvoidance(this.getCurtailmentHost(), this.getCurtailmentConfig());\n\t\t} catch (err) {\n\t\t\tthis.log.warn(`Curtailment avoidance: ${(err as Error).message}`);\n\t\t}\n\t}\n\n\tprivate schedulePollAfterControl(): void {\n\t\tif (this.pollAfterControlTimer) {\n\t\t\tclearTimeout(this.pollAfterControlTimer);\n\t\t}\n\t\tthis.pollAfterControlTimer = setTimeout(() => {\n\t\t\tthis.pollAfterControlTimer = undefined;\n\t\t\tvoid this.pollOnce();\n\t\t}, 12_000);\n\t}\n\n\tprivate async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise<void> {\n\t\tif (!state) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.config.enableCurtailmentAvoidance) {\n\t\t\tconst n = Number(state.val);\n\t\t\tif (Number.isFinite(n) && n >= 0) {\n\t\t\t\tconst systemPv = parseSystemPvStateId(this.namespace, id);\n\t\t\t\tif (systemPv) {\n\t\t\t\t\tthis.handleCurtailmentSystemPvUpdated(systemPv.siteId, n);\n\t\t\t\t} else {\n\t\t\t\t\tconst pv = parsePvSensorStateId(this.namespace, id);\n\t\t\t\t\tif (pv) {\n\t\t\t\t\t\tthis.handleCurtailmentPvUpdated(pv.deviceId, n);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (state.ack) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (id.startsWith(`${this.namespace}.services.`) && state.val === true) {\n\t\t\tawait this.handleServiceTrigger(id);\n\t\t\treturn;\n\t\t}\n\n\t\tconst control = parseControlStateId(this.namespace, id);\n\t\tif (!control) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst current = await this.getStateAsync(id);\n\t\tif (\n\t\t\tcurrent?.ack &&\n\t\t\tcurrent.val !== null &&\n\t\t\tcurrent.val !== undefined &&\n\t\t\tString(current.val) === String(state.val)\n\t\t) {\n\t\t\tawait this.setState(id, { val: state.val, ack: true });\n\t\t\treturn;\n\t\t}\n\n\t\tconst value = state.val as string | number | boolean;\n\t\tconst deviceContext = this.deviceContexts.get(control.deviceId);\n\n\t\tthis.controlQueue.enqueue({\n\t\t\tstateId: id,\n\t\t\texecute: async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait runBridge(\n\t\t\t\t\t\t\"set\",\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t...this.getBridgeConfig(),\n\t\t\t\t\t\t\tdeviceId: control.deviceId,\n\t\t\t\t\t\t\tcontrol: control.control,\n\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t\tdeviceContext,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tthis.config.pythonPath || \"\",\n\t\t\t\t\t\tthis.log,\n\t\t\t\t\t);\n\t\t\t\t\tawait this.setState(id, { val: value, ack: true });\n\t\t\t\t\tthis.log.info(`Applied ${control.control} on ${control.deviceId}`);\n\t\t\t\t\tthis.schedulePollAfterControl();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = (error as Error).message;\n\t\t\t\t\tif (message.includes(\"429\") || message.includes(\"Too Many Requests\")) {\n\t\t\t\t\t\tthis.log.warn(\n\t\t\t\t\t\t\t`Control rate-limited for ${id} \u2013 wait ~1 minute before retrying (Anker API limit).`,\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.log.error(`Control failed for ${id}: ${message}`);\n\t\t\t\t\t}\n\t\t\t\t\tawait this.setState(id, { val: value, ack: false });\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t}\n\n\tprivate async onMessage(obj: ioBroker.Message): Promise<void> {\n\t\tif (!obj?.command) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst respond = (response: unknown): void => {\n\t\t\tif (obj.callback) {\n\t\t\t\tthis.sendTo(obj.from, obj.command, response, obj.callback);\n\t\t\t}\n\t\t};\n\n\t\ttry {\n\t\t\tif (obj.command === \"clearAuthCache\") {\n\t\t\t\tconst cacheDir = this.getAuthCacheDir();\n\t\t\t\tconst fs = await import(\"node:fs/promises\");\n\t\t\t\ttry {\n\t\t\t\t\tconst files = await fs.readdir(cacheDir);\n\t\t\t\t\tawait Promise.all(files.map(f => fs.unlink(path.join(cacheDir, f)).catch(() => undefined)));\n\t\t\t\t\tawait stopBridgeDaemon();\n\t\t\t\t\tawait ensureBridgeDaemon(this.getBridgeConfig(), this.config.pythonPath || \"\", this.log);\n\t\t\t\t\tthis.log.warn(\n\t\t\t\t\t\t`Anker login cache cleared (${files.length} file(s) in ${cacheDir}). ` +\n\t\t\t\t\t\t\t\"Next poll requires a new API login; on many hosts Anker returns captcha (100032). \" +\n\t\t\t\t\t\t\t`Restore authcache/${(this.config.username || \"\").trim()}.json from HA or retry login when cloud allows it.`,\n\t\t\t\t\t);\n\t\t\t\t\trespond({ ok: true, cleared: files.length });\n\t\t\t\t} catch {\n\t\t\t\t\tthis.log.warn(\n\t\t\t\t\t\t`Anker login cache clear requested but folder empty or missing (${cacheDir}). ` +\n\t\t\t\t\t\t\t\"Adapter must complete a successful API login to create authcache/<email>.json.\",\n\t\t\t\t\t);\n\t\t\t\t\trespond({ ok: true, cleared: 0 });\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (obj.command === \"installPython\") {\n\t\t\t\tconst ok = await this.ensurePythonDeps(true);\n\t\t\t\trespond({ ok });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (obj.command === \"loadDevices\") {\n\t\t\t\tif (!this.config.username || !this.config.password) {\n\t\t\t\t\trespond({ error: \"Credentials required\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tawait this.ensurePythonDeps();\n\t\t\t\tconst result = await runBridge(\n\t\t\t\t\t\"list_devices\",\n\t\t\t\t\tthis.getBridgeConfig(),\n\t\t\t\t\tthis.config.pythonPath || \"\",\n\t\t\t\t\tthis.log,\n\t\t\t\t);\n\t\t\t\tconst payload = {\n\t\t\t\t\tsites: result.sites || [],\n\t\t\t\t\tdevices: result.devices || [],\n\t\t\t\t};\n\t\t\t\trespond({ ok: true, deviceListJson: JSON.stringify(payload, null, 2), ...payload });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\trespond({ error: `Unknown command ${obj.command}` });\n\t\t} catch (error) {\n\t\t\trespond({ error: (error as Error).message });\n\t\t}\n\t}\n\n\tprivate async onReady(): Promise<void> {\n\t\tthis.cleanupLegacyInstallSymlink();\n\n\t\tawait this.setObjectNotExistsAsync(\"account\", {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: \"Account\" },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(\"account.nickname\", {\n\t\t\ttype: \"state\",\n\t\t\tcommon: {\n\t\t\t\tname: \"Account nickname\",\n\t\t\t\ttype: \"string\",\n\t\t\t\trole: \"info\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(\"info.pythonReady\", {\n\t\t\ttype: \"state\",\n\t\t\tcommon: {\n\t\t\t\tname: \"Python dependencies ready\",\n\t\t\t\ttype: \"boolean\",\n\t\t\t\trole: \"indicator\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: false,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\n\t\tawait setupServiceStates(this);\n\t\tawait setupCurtailmentStates(this);\n\t\t(this as CurtailmentPvSyncHost).onCurtailmentPvUpdated = (deviceId, livePvW) =>\n\t\t\tthis.handleCurtailmentPvUpdated(deviceId, livePvW);\n\t\t(this as CurtailmentPvSyncHost).onCurtailmentSystemPvUpdated = (siteId, livePvW) =>\n\t\t\tthis.handleCurtailmentSystemPvUpdated(siteId, livePvW);\n\t\tthis.refreshCurtailmentDeviceIds();\n\t\tawait this.setState(\"info.connection\", false, true);\n\n\t\tconst intervalSec = Math.max(30, Number(this.config.scanInterval) || 60);\n\t\tthis.log.info(\n\t\t\t`Anker Solix adapter started (poll every ${intervalSec}s, MQTT: ${this.config.mqttUsage !== false})`,\n\t\t);\n\n\t\tawait this.ensurePythonDeps();\n\n\t\tthis.logAuthCacheStatus();\n\n\t\tawait ensureBridgeDaemon(this.getBridgeConfig(), this.config.pythonPath || \"\", this.log);\n\n\t\tthis.subscribeStates(`${this.namespace}.*.control.*`);\n\t\tthis.subscribeStates(`${this.namespace}.services.*`);\n\t\tthis.subscribeCurtailmentPvStates();\n\n\t\tawait this.pollOnce();\n\t\tthis.pollTimer = this.setInterval(() => {\n\t\t\tvoid this.pollOnce();\n\t\t}, intervalSec * 1000);\n\t}\n\n\tprivate onUnload(callback: () => void): void {\n\t\tif (this.pollTimer) {\n\t\t\tthis.clearInterval(this.pollTimer);\n\t\t\tthis.pollTimer = undefined;\n\t\t}\n\t\tif (this.pollAfterControlTimer) {\n\t\t\tclearTimeout(this.pollAfterControlTimer);\n\t\t\tthis.pollAfterControlTimer = undefined;\n\t\t}\n\t\tthis.lastNotifiedPvW.clear();\n\t\t(this as CurtailmentPvSyncHost).onCurtailmentPvUpdated = undefined;\n\t\t(this as CurtailmentPvSyncHost).onCurtailmentSystemPvUpdated = undefined;\n\t\tvoid stopBridgeDaemon().finally(() => callback());\n\t}\n}\n\nif (require.main !== module) {\n\tmodule.exports = (options: Partial<utils.AdapterOptions> | undefined) => new AnkerSolix(options);\n} else {\n\t(() => new AnkerSolix())();\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["fs"]
|
|
7
7
|
}
|
package/io-package.json
CHANGED
|
@@ -1,21 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "anker-solix",
|
|
4
|
-
"version": "0.10.
|
|
4
|
+
"version": "0.10.15",
|
|
5
5
|
"messagebox": true,
|
|
6
6
|
"news": {
|
|
7
|
-
"0.10.
|
|
8
|
-
"en": "Curtailment:
|
|
9
|
-
"de": "Abregelung:
|
|
10
|
-
"ru": "Abregelung:
|
|
11
|
-
"pt": "Abregelung:
|
|
12
|
-
"nl": "Abregelung:
|
|
13
|
-
"fr": "Abregelung:
|
|
14
|
-
"it": "Abregelung:
|
|
15
|
-
"es": "Abregelung:
|
|
16
|
-
"pl": "Abregelung:
|
|
17
|
-
"uk": "Abregelung:
|
|
18
|
-
"zh-cn": "
|
|
7
|
+
"0.10.15": {
|
|
8
|
+
"en": "Curtailment: API-only ac_output_limit; fix SOC/missing_charge_wh; no station feed-in changes",
|
|
9
|
+
"de": "Abregelung: ac_output_limit nur API; SOC/missing_charge_wh korrigiert; kein Einspeise-Limit",
|
|
10
|
+
"ru": "Abregelung: API ac_output_limit",
|
|
11
|
+
"pt": "Abregelung: API ac_output_limit",
|
|
12
|
+
"nl": "Abregelung: API ac_output_limit",
|
|
13
|
+
"fr": "Abregelung: API ac_output_limit",
|
|
14
|
+
"it": "Abregelung: API ac_output_limit",
|
|
15
|
+
"es": "Abregelung: API ac_output_limit",
|
|
16
|
+
"pl": "Abregelung: API ac_output_limit",
|
|
17
|
+
"uk": "Abregelung: API ac_output_limit",
|
|
18
|
+
"zh-cn": "限电 API ac_output_limit"
|
|
19
|
+
},
|
|
20
|
+
"0.10.14": {
|
|
21
|
+
"en": "Curtailment: manual + ac_output_limit only; missing_charge_wh; active export = PV − charge",
|
|
22
|
+
"de": "Abregelung: nur Benutzerdefiniert + ac_output_limit; missing_charge_wh; active: Einspeisung = PV − Laden",
|
|
23
|
+
"ru": "Abregelung: manual + ac_output_limit",
|
|
24
|
+
"pt": "Abregelung: manual + ac_output_limit",
|
|
25
|
+
"nl": "Abregelung: manual + ac_output_limit",
|
|
26
|
+
"fr": "Abregelung: manual + ac_output_limit",
|
|
27
|
+
"it": "Abregelung: manual + ac_output_limit",
|
|
28
|
+
"es": "Abregelung: manual + ac_output_limit",
|
|
29
|
+
"pl": "Abregelung: manual + ac_output_limit",
|
|
30
|
+
"uk": "Abregelung: manual + ac_output_limit",
|
|
31
|
+
"zh-cn": "限电仅 manual + ac_output_limit"
|
|
19
32
|
},
|
|
20
33
|
"0.10.12": {
|
|
21
34
|
"en": "Curtailment combiner: ac_output_limit (max_load) for export; home load preset 0 W",
|
package/package.json
CHANGED
package/python/bridge.py
CHANGED
|
@@ -318,6 +318,7 @@ async def _set_ac_output_limit(
|
|
|
318
318
|
device: dict,
|
|
319
319
|
limit: int,
|
|
320
320
|
ha_client: IoBrokerAnkerApiClient | None = None,
|
|
321
|
+
api_only: bool = False,
|
|
321
322
|
) -> Any:
|
|
322
323
|
"""HA max_load / max_load_total: MQTT first, optional API cache sync without get_power_limit."""
|
|
323
324
|
dev_type = str(device.get("type") or "").lower()
|
|
@@ -325,19 +326,20 @@ async def _set_ac_output_limit(
|
|
|
325
326
|
multisystem = dev_type == COMBINER or "all_power_limit" in device
|
|
326
327
|
mqtt_ok = False
|
|
327
328
|
|
|
328
|
-
if
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
329
|
+
if not api_only:
|
|
330
|
+
if multisystem:
|
|
331
|
+
mqtt_ok = await _mqtt_max_load_parallel(
|
|
332
|
+
api, site_id, limit, control_sn, ha_client=ha_client
|
|
333
|
+
)
|
|
334
|
+
elif dev_type == SOLARBANK and not station_sn:
|
|
335
|
+
mqtt_ok = await _mqtt_command(
|
|
336
|
+
api,
|
|
337
|
+
device_id,
|
|
338
|
+
SolixMqttCommands.sb_max_load,
|
|
339
|
+
limit,
|
|
340
|
+
"set_max_load",
|
|
341
|
+
ha_client=ha_client,
|
|
342
|
+
)
|
|
341
343
|
|
|
342
344
|
api_sn = (
|
|
343
345
|
control_sn
|
|
@@ -346,6 +348,8 @@ async def _set_ac_output_limit(
|
|
|
346
348
|
)
|
|
347
349
|
api_ok = await _api_set_ac_output_only(api, site_id, api_sn, limit, device)
|
|
348
350
|
|
|
351
|
+
if api_only and api_ok:
|
|
352
|
+
return {"api": True, "curtailment_api_only": True}
|
|
349
353
|
if mqtt_ok:
|
|
350
354
|
return {"mqtt": True, "api": api_ok}
|
|
351
355
|
if api_ok:
|
|
@@ -497,7 +501,9 @@ async def apply_control(
|
|
|
497
501
|
control: str,
|
|
498
502
|
value: Any,
|
|
499
503
|
ha_client: IoBrokerAnkerApiClient | None = None,
|
|
504
|
+
config: dict | None = None,
|
|
500
505
|
) -> None:
|
|
506
|
+
config = config or {}
|
|
501
507
|
site_id, control_sn, device_id, device = _control_context(api, device_id)
|
|
502
508
|
dev_type = str(device.get("type") or "").lower()
|
|
503
509
|
|
|
@@ -537,6 +543,7 @@ async def apply_control(
|
|
|
537
543
|
device,
|
|
538
544
|
int(value),
|
|
539
545
|
ha_client=ha_client,
|
|
546
|
+
api_only=bool(config.get("acOutputApiOnly")),
|
|
540
547
|
)
|
|
541
548
|
elif control == "pv_input_limit":
|
|
542
549
|
result = await api.set_power_limit(
|
|
@@ -803,6 +810,7 @@ async def run_set_with_client(client: IoBrokerAnkerApiClient, config: dict) -> d
|
|
|
803
810
|
str(config["control"]),
|
|
804
811
|
config["value"],
|
|
805
812
|
ha_client=client,
|
|
813
|
+
config=config,
|
|
806
814
|
)
|
|
807
815
|
return {"ok": True, "persistent": True}
|
|
808
816
|
|