@enyo-energy/sunspec-sdk 0.0.73 → 0.0.75
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 +5 -1
- package/dist/cjs/sunspec-battery-schedule-handler.cjs +72 -5
- package/dist/cjs/sunspec-battery-schedule-handler.d.cts +27 -0
- package/dist/cjs/sunspec-devices.cjs +4 -138
- package/dist/cjs/sunspec-devices.d.cts +0 -11
- package/dist/cjs/sunspec-modbus-client.cjs +64 -38
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/sunspec-battery-schedule-handler.d.ts +27 -0
- package/dist/sunspec-battery-schedule-handler.js +73 -6
- package/dist/sunspec-devices.d.ts +0 -11
- package/dist/sunspec-devices.js +4 -138
- package/dist/sunspec-modbus-client.js +64 -38
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
@@ -486,7 +486,10 @@ class SunspecModbusClient {
|
|
|
486
486
|
}
|
|
487
487
|
catch (error) {
|
|
488
488
|
console.debug(`No SunSpec device at unit ${unitId}: ${error}`);
|
|
489
|
-
|
|
489
|
+
// Discovery summary at line 594 below is the info-level outcome.
|
|
490
|
+
// This 0-models branch is the same outcome as a normal "no device found",
|
|
491
|
+
// covered already by the debug above. Keep at debug.
|
|
492
|
+
console.debug(`Discovery complete for unit ${unitId}. Found 0 models: []`);
|
|
490
493
|
return models;
|
|
491
494
|
}
|
|
492
495
|
currentAddress = addressInfo.nextAddress;
|
|
@@ -495,14 +498,14 @@ class SunspecModbusClient {
|
|
|
495
498
|
const buffer = await instance.readHoldingRegisters(currentAddress, 2);
|
|
496
499
|
const modelData = [buffer.readUInt16BE(0), buffer.readUInt16BE(2)];
|
|
497
500
|
if (!modelData || modelData.length < 2) {
|
|
498
|
-
console.
|
|
501
|
+
console.debug(`No data at address ${currentAddress}, ending discovery`);
|
|
499
502
|
break;
|
|
500
503
|
}
|
|
501
504
|
const modelId = modelData[0];
|
|
502
505
|
const modelLength = modelData[1];
|
|
503
506
|
// Check for end marker
|
|
504
507
|
if (modelId === 0xFFFF || modelId === 65535) {
|
|
505
|
-
console.
|
|
508
|
+
console.debug(`Found end marker at address ${currentAddress}`);
|
|
506
509
|
break;
|
|
507
510
|
}
|
|
508
511
|
// Store discovered model
|
|
@@ -512,7 +515,9 @@ class SunspecModbusClient {
|
|
|
512
515
|
length: modelLength
|
|
513
516
|
};
|
|
514
517
|
models.set(modelId, model);
|
|
515
|
-
|
|
518
|
+
// Per-model discovery step. The end-of-discovery summary below is the
|
|
519
|
+
// info-level outcome; the per-model walk is debug-only.
|
|
520
|
+
console.debug(`Discovered Model ${modelId} at address ${currentAddress} with length ${modelLength} (unit ${unitId})`);
|
|
516
521
|
// Jump to next model: current address + 2 (header) + model length
|
|
517
522
|
currentAddress = currentAddress + 2 + modelLength;
|
|
518
523
|
}
|
|
@@ -701,7 +706,9 @@ class SunspecModbusClient {
|
|
|
701
706
|
// Write to holding registers
|
|
702
707
|
await instance.writeMultipleRegisters(address, registerValues);
|
|
703
708
|
this.connectionHealth.recordSuccess();
|
|
704
|
-
|
|
709
|
+
// Per-register write success — fires for every parameter inside higher-level
|
|
710
|
+
// writers (writeBatteryControls / writeInverterControls). Demoted to debug.
|
|
711
|
+
console.debug(`Successfully wrote value ${value} to register ${address} (unit ${unitId})`);
|
|
705
712
|
return true;
|
|
706
713
|
}
|
|
707
714
|
catch (error) {
|
|
@@ -1599,7 +1606,10 @@ class SunspecModbusClient {
|
|
|
1599
1606
|
data.dischargePower = Math.abs((data.outWRte / 100) * data.wChaMax);
|
|
1600
1607
|
console.debug(`Calculated Discharge Power (inWRte: ${data.outWRte}, wChaMax: ${data.wChaMax}): ${data.dischargePower?.toFixed(2)} W`);
|
|
1601
1608
|
}
|
|
1602
|
-
|
|
1609
|
+
// Single-line JSON debug dump — Node's default formatter would split
|
|
1610
|
+
// the object across many lines; stringify keeps the whole snapshot
|
|
1611
|
+
// on one log entry so it stays grep-able and round-trippable.
|
|
1612
|
+
console.debug(`[Battery] unit=${unitId} model=124 data=${JSON.stringify(data)}`);
|
|
1603
1613
|
return data;
|
|
1604
1614
|
}
|
|
1605
1615
|
else if (model.id === 802) {
|
|
@@ -1636,17 +1646,18 @@ class SunspecModbusClient {
|
|
|
1636
1646
|
chargePower,
|
|
1637
1647
|
dischargePower,
|
|
1638
1648
|
};
|
|
1639
|
-
console.debug(
|
|
1649
|
+
console.debug(`[Battery] unit=${unitId} model=802 data=${JSON.stringify(result)}`);
|
|
1640
1650
|
return result;
|
|
1641
1651
|
}
|
|
1642
1652
|
else {
|
|
1643
|
-
// Handle other battery models (803) if needed
|
|
1644
|
-
|
|
1645
|
-
return {
|
|
1653
|
+
// Handle other battery models (803) if needed.
|
|
1654
|
+
const stub = {
|
|
1646
1655
|
blockNumber: model.id,
|
|
1647
1656
|
blockAddress: model.address,
|
|
1648
|
-
blockLength: model.length
|
|
1657
|
+
blockLength: model.length,
|
|
1649
1658
|
};
|
|
1659
|
+
console.debug(`[Battery] unit=${unitId} model=${model.id} (not yet implemented) data=${JSON.stringify(stub)}`);
|
|
1660
|
+
return stub;
|
|
1650
1661
|
}
|
|
1651
1662
|
}
|
|
1652
1663
|
catch (error) {
|
|
@@ -1664,17 +1675,21 @@ class SunspecModbusClient {
|
|
|
1664
1675
|
return false;
|
|
1665
1676
|
}
|
|
1666
1677
|
const baseAddr = model.address;
|
|
1667
|
-
|
|
1678
|
+
// Per-register write trace is high-volume (every schedule entry transition fires
|
|
1679
|
+
// a writeBatteryControls). Demoted to debug; consumers that need the trail can
|
|
1680
|
+
// raise their log level. High-level callers (setStorageMode, enableGridCharging,
|
|
1681
|
+
// setFeedInLimit, etc.) still emit one info-level line per action.
|
|
1682
|
+
console.debug(`Writing Battery Controls to Model 124 at base address: ${baseAddr} (unit ${unitId})`);
|
|
1668
1683
|
try {
|
|
1669
|
-
// Write
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1684
|
+
// Write order: source pin and parameter writes land BEFORE the
|
|
1685
|
+
// control mode so the device only "starts acting" once every
|
|
1686
|
+
// value that governs the action is fresh. The reverse order
|
|
1687
|
+
// leaks a Modbus RTT-sized window where storCtlMod sees stale
|
|
1688
|
+
// chaGriSet / wChaMax / inWRte / outWRte.
|
|
1674
1689
|
// Write charge source setting (Register 17)
|
|
1675
1690
|
if (controls.chaGriSet !== undefined) {
|
|
1676
1691
|
await this.writeRegisterValue(unitId, baseAddr + 17, controls.chaGriSet, 'uint16');
|
|
1677
|
-
console.
|
|
1692
|
+
console.debug(`Set charge source to ${controls.chaGriSet === sunspec_interfaces_js_1.SunspecChargeSource.GRID ? 'GRID' : 'PV'}`);
|
|
1678
1693
|
}
|
|
1679
1694
|
// Write maximum charge power (Register 2) - needs scale factor
|
|
1680
1695
|
if (controls.wChaMax !== undefined) {
|
|
@@ -1682,7 +1697,7 @@ class SunspecModbusClient {
|
|
|
1682
1697
|
const scaleFactor = await this.readRegisterValue(unitId, scaleFactorAddr, 1, 'int16');
|
|
1683
1698
|
const scaledValue = Math.round(controls.wChaMax / Math.pow(10, scaleFactor));
|
|
1684
1699
|
await this.writeRegisterValue(unitId, baseAddr + 2, scaledValue, 'uint16');
|
|
1685
|
-
console.
|
|
1700
|
+
console.debug(`Set max charge power to ${controls.wChaMax}W (scaled: ${scaledValue})`);
|
|
1686
1701
|
}
|
|
1687
1702
|
// Write charge rate (Register 13) - needs scale factor
|
|
1688
1703
|
if (controls.inWRte !== undefined) {
|
|
@@ -1690,7 +1705,7 @@ class SunspecModbusClient {
|
|
|
1690
1705
|
const scaleFactor = await this.readRegisterValue(unitId, scaleFactorAddr, 1, 'int16');
|
|
1691
1706
|
const scaledValue = Math.round(controls.inWRte / Math.pow(10, scaleFactor));
|
|
1692
1707
|
await this.writeRegisterValue(unitId, baseAddr + 13, scaledValue, 'int16');
|
|
1693
|
-
console.
|
|
1708
|
+
console.debug(`Set charge rate to ${controls.inWRte}% (scaled: ${scaledValue})`);
|
|
1694
1709
|
}
|
|
1695
1710
|
// Write discharge rate (Register 12) - needs scale factor
|
|
1696
1711
|
if (controls.outWRte !== undefined) {
|
|
@@ -1698,7 +1713,7 @@ class SunspecModbusClient {
|
|
|
1698
1713
|
const scaleFactor = await this.readRegisterValue(unitId, scaleFactorAddr, 1, 'int16');
|
|
1699
1714
|
const scaledValue = Math.round(controls.outWRte / Math.pow(10, scaleFactor));
|
|
1700
1715
|
await this.writeRegisterValue(unitId, baseAddr + 12, scaledValue, 'int16');
|
|
1701
|
-
console.
|
|
1716
|
+
console.debug(`Set discharge rate to ${controls.outWRte}% (scaled: ${scaledValue})`);
|
|
1702
1717
|
}
|
|
1703
1718
|
// Write minimum reserve percentage (Register 7) - needs scale factor
|
|
1704
1719
|
if (controls.minRsvPct !== undefined) {
|
|
@@ -1706,9 +1721,16 @@ class SunspecModbusClient {
|
|
|
1706
1721
|
const scaleFactor = await this.readRegisterValue(unitId, scaleFactorAddr, 1, 'int16');
|
|
1707
1722
|
const scaledValue = Math.round(controls.minRsvPct / Math.pow(10, scaleFactor));
|
|
1708
1723
|
await this.writeRegisterValue(unitId, baseAddr + 7, scaledValue, 'uint16');
|
|
1709
|
-
console.
|
|
1724
|
+
console.debug(`Set minimum reserve to ${controls.minRsvPct}% (scaled: ${scaledValue})`);
|
|
1725
|
+
}
|
|
1726
|
+
// Storage control mode (Register 5) — written LAST so all
|
|
1727
|
+
// governing parameters are already in place when the device
|
|
1728
|
+
// transitions into the new mode.
|
|
1729
|
+
if (controls.storCtlMod !== undefined) {
|
|
1730
|
+
await this.writeRegisterValue(unitId, baseAddr + 5, controls.storCtlMod, 'uint16');
|
|
1731
|
+
console.debug(`Set storage control mode to 0x${controls.storCtlMod.toString(16)}`);
|
|
1710
1732
|
}
|
|
1711
|
-
console.
|
|
1733
|
+
console.debug('Battery controls written successfully');
|
|
1712
1734
|
return true;
|
|
1713
1735
|
}
|
|
1714
1736
|
catch (error) {
|
|
@@ -1759,10 +1781,12 @@ class SunspecModbusClient {
|
|
|
1759
1781
|
async readBatteryControls(unitId) {
|
|
1760
1782
|
const model = this.findModel(unitId, sunspec_interfaces_js_1.SunspecModelId.Battery);
|
|
1761
1783
|
if (!model) {
|
|
1762
|
-
|
|
1784
|
+
// Fires per readBatteryControls attempt — noisy on non-battery units
|
|
1785
|
+
// that go through the same code path. Demoted to debug.
|
|
1786
|
+
console.debug(`Battery model 124 not found on unit ${unitId}`);
|
|
1763
1787
|
return null;
|
|
1764
1788
|
}
|
|
1765
|
-
console.
|
|
1789
|
+
console.debug(`Reading Battery Controls from Model 124 at base address: ${model.address} (unit ${unitId})`);
|
|
1766
1790
|
try {
|
|
1767
1791
|
// Read entire model block in a single Modbus call
|
|
1768
1792
|
const buffer = await this.readModelBlock(unitId, model);
|
|
@@ -1977,7 +2001,7 @@ class SunspecModbusClient {
|
|
|
1977
2001
|
console.debug(`Common block model not found on unit ${unitId}`);
|
|
1978
2002
|
return null;
|
|
1979
2003
|
}
|
|
1980
|
-
console.
|
|
2004
|
+
console.debug(`Reading Common Block - Model address: ${model.address} (unit ${unitId})`);
|
|
1981
2005
|
try {
|
|
1982
2006
|
// Read entire model block in a single Modbus call
|
|
1983
2007
|
const buffer = await this.readModelBlock(unitId, model);
|
|
@@ -2137,7 +2161,8 @@ class SunspecModbusClient {
|
|
|
2137
2161
|
async readInverterControls(unitId) {
|
|
2138
2162
|
const model = this.findModel(unitId, sunspec_interfaces_js_1.SunspecModelId.Controls);
|
|
2139
2163
|
if (!model) {
|
|
2140
|
-
|
|
2164
|
+
// Same trace-only rationale as the Model 124 not-found message above.
|
|
2165
|
+
console.debug(`Controls model 123 not found on unit ${unitId}`);
|
|
2141
2166
|
return null;
|
|
2142
2167
|
}
|
|
2143
2168
|
try {
|
|
@@ -2246,17 +2271,17 @@ class SunspecModbusClient {
|
|
|
2246
2271
|
const scaleFactor = sfBuffer.readInt16BE(0);
|
|
2247
2272
|
const scaledValue = Math.round(settings.WMax / Math.pow(10, scaleFactor));
|
|
2248
2273
|
// Writing registers needs to be implemented in EnergyAppModbusInstance
|
|
2249
|
-
// For now, log the write operation
|
|
2250
|
-
console.
|
|
2274
|
+
// For now, log the (would-be) write operation. Dry-run trace only — debug.
|
|
2275
|
+
console.debug(`Would write value ${scaledValue} to register ${baseAddr}`);
|
|
2251
2276
|
}
|
|
2252
2277
|
if (settings.VRef !== undefined) {
|
|
2253
2278
|
const sfBuffer = await instance.readHoldingRegisters(baseAddr + 23, 1);
|
|
2254
2279
|
const scaleFactor = sfBuffer.readInt16BE(0);
|
|
2255
2280
|
const scaledValue = Math.round(settings.VRef / Math.pow(10, scaleFactor));
|
|
2256
|
-
console.
|
|
2281
|
+
console.debug(`Would write value ${scaledValue} to register ${baseAddr + 1}`);
|
|
2257
2282
|
}
|
|
2258
2283
|
// Add more write operations for other settings as needed
|
|
2259
|
-
console.
|
|
2284
|
+
console.debug('Inverter settings written successfully');
|
|
2260
2285
|
return true;
|
|
2261
2286
|
}
|
|
2262
2287
|
catch (error) {
|
|
@@ -2275,36 +2300,37 @@ class SunspecModbusClient {
|
|
|
2275
2300
|
}
|
|
2276
2301
|
const baseAddr = model.address;
|
|
2277
2302
|
try {
|
|
2278
|
-
//
|
|
2303
|
+
// Per-field inverter-control writes are debug-only; the high-level callers
|
|
2304
|
+
// (`setFeedInLimit`, etc.) still log the action and outcome at info level.
|
|
2279
2305
|
if (controls.Conn !== undefined) {
|
|
2280
2306
|
await this.writeRegisterValue(unitId, baseAddr + 2, controls.Conn, 'uint16');
|
|
2281
|
-
console.
|
|
2307
|
+
console.debug(`Set connection control to ${controls.Conn}`);
|
|
2282
2308
|
}
|
|
2283
2309
|
// Power limit control (Register 3) - needs scale factor
|
|
2284
2310
|
if (controls.WMaxLimPct !== undefined) {
|
|
2285
2311
|
const scaleFactor = await this.readRegisterValue(unitId, baseAddr + 21, 1, 'int16');
|
|
2286
2312
|
const scaledValue = Math.round(controls.WMaxLimPct / Math.pow(10, scaleFactor));
|
|
2287
2313
|
await this.writeRegisterValue(unitId, baseAddr + 3, scaledValue, 'uint16');
|
|
2288
|
-
console.
|
|
2314
|
+
console.debug(`Set power limit to ${controls.WMaxLimPct}% (scaled: ${scaledValue})`);
|
|
2289
2315
|
}
|
|
2290
2316
|
// Throttle enable/disable (Register 7)
|
|
2291
2317
|
if (controls.WMaxLim_Ena !== undefined) {
|
|
2292
2318
|
await this.writeRegisterValue(unitId, baseAddr + 7, controls.WMaxLim_Ena, 'uint16');
|
|
2293
|
-
console.
|
|
2319
|
+
console.debug(`Set throttle enable to ${controls.WMaxLim_Ena}`);
|
|
2294
2320
|
}
|
|
2295
2321
|
// Power factor control (Register 8) - needs scale factor
|
|
2296
2322
|
if (controls.OutPFSet !== undefined) {
|
|
2297
2323
|
const scaleFactor = await this.readRegisterValue(unitId, baseAddr + 22, 1, 'int16');
|
|
2298
2324
|
const scaledValue = Math.round(controls.OutPFSet / Math.pow(10, scaleFactor));
|
|
2299
2325
|
await this.writeRegisterValue(unitId, baseAddr + 8, scaledValue, 'int16');
|
|
2300
|
-
console.
|
|
2326
|
+
console.debug(`Set power factor to ${controls.OutPFSet} (scaled: ${scaledValue})`);
|
|
2301
2327
|
}
|
|
2302
2328
|
// Power factor enable/disable (Register 12)
|
|
2303
2329
|
if (controls.OutPFSet_Ena !== undefined) {
|
|
2304
2330
|
await this.writeRegisterValue(unitId, baseAddr + 12, controls.OutPFSet_Ena, 'uint16');
|
|
2305
|
-
console.
|
|
2331
|
+
console.debug(`Set PF enable to ${controls.OutPFSet_Ena}`);
|
|
2306
2332
|
}
|
|
2307
|
-
console.
|
|
2333
|
+
console.debug('Inverter controls written successfully');
|
|
2308
2334
|
return true;
|
|
2309
2335
|
}
|
|
2310
2336
|
catch (error) {
|
package/dist/cjs/version.cjs
CHANGED
|
@@ -9,7 +9,7 @@ exports.getSdkVersion = getSdkVersion;
|
|
|
9
9
|
/**
|
|
10
10
|
* Current version of the enyo Energy App SDK.
|
|
11
11
|
*/
|
|
12
|
-
exports.SDK_VERSION = '0.0.
|
|
12
|
+
exports.SDK_VERSION = '0.0.75';
|
|
13
13
|
/**
|
|
14
14
|
* Gets the current SDK version.
|
|
15
15
|
* @returns The semantic version string of the SDK
|
package/dist/cjs/version.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StorageScheduleHandler, type ActiveStorageScheduleEntry, type StorageScheduleHandlerOptions } from "@enyo-energy/energy-app-sdk/dist/implementations/storage/storage-schedule-handler.js";
|
|
2
|
+
import { type EnyoDataBusSetStorageScheduleV1 } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
|
|
2
3
|
import { type SnapshotService } from "@enyo-energy/appliance-calibration";
|
|
3
4
|
import { type SunspecBatteryControls } from "./sunspec-interfaces.js";
|
|
4
5
|
/**
|
|
@@ -58,7 +59,33 @@ export declare class SunspecBatteryScheduleHandler extends StorageScheduleHandle
|
|
|
58
59
|
private readonly getSnapshotService;
|
|
59
60
|
private readonly onErrorCallback?;
|
|
60
61
|
private installedWChaMax;
|
|
62
|
+
/**
|
|
63
|
+
* Sticky pre-schedule snapshot, captured once on the first `onInit` and
|
|
64
|
+
* held across every subsequent schedule-to-schedule replacement. Cleared
|
|
65
|
+
* only when a real rollback fires (`mode: auto` from the data bus or
|
|
66
|
+
* `dispose`). Lets the eventual restore write the *true* baseline rather
|
|
67
|
+
* than the last-active-entry register set that the device happens to be
|
|
68
|
+
* in at the moment of replacement.
|
|
69
|
+
*/
|
|
70
|
+
private originalBaseline?;
|
|
71
|
+
/**
|
|
72
|
+
* Set inside the overridden `applyMessage` when an incoming
|
|
73
|
+
* `mode: schedule` message would replace an already-running schedule.
|
|
74
|
+
* Consumed (and reset) inside `onRollback` so the base library's
|
|
75
|
+
* automatic `doReleaseSchedule → onRollback` step does not actually
|
|
76
|
+
* write the snapshot back during a replacement.
|
|
77
|
+
*/
|
|
78
|
+
private suppressNextRollbackWrite;
|
|
61
79
|
constructor(opts: SunspecBatteryScheduleHandlerOptions);
|
|
80
|
+
/**
|
|
81
|
+
* Override the base-class data-bus router so a schedule-to-schedule
|
|
82
|
+
* replacement marks the next `onRollback` as "skip the write". The base
|
|
83
|
+
* library still owns the actual schedule lifecycle — we just steer one
|
|
84
|
+
* decision inside `onRollback`. `mode: auto` and any path that does not
|
|
85
|
+
* have an active schedule fall through unchanged, so the rollback fires
|
|
86
|
+
* normally there.
|
|
87
|
+
*/
|
|
88
|
+
applyMessage(msg: EnyoDataBusSetStorageScheduleV1): Promise<void>;
|
|
62
89
|
protected onInit(_active: ActiveStorageScheduleEntry): Promise<SunspecScheduleRegisters>;
|
|
63
90
|
protected onChange(active: ActiveStorageScheduleEntry, _previous: ActiveStorageScheduleEntry | undefined): void;
|
|
64
91
|
protected onRollback(registers: SunspecScheduleRegisters): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { StorageScheduleHandler, } from "@enyo-energy/energy-app-sdk/dist/implementations/storage/storage-schedule-handler.js";
|
|
2
|
-
import { EnyoStorageScheduleDirectionEnum } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
|
|
2
|
+
import { EnyoStorageScheduleDirectionEnum, EnyoStorageScheduleModeEnum, } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
|
|
3
3
|
import { SunspecChargeSource, SunspecStorageControlMode, } from "./sunspec-interfaces.js";
|
|
4
4
|
/**
|
|
5
5
|
* Concrete `StorageScheduleHandler` for SunSpec Model 124 batteries. The base
|
|
@@ -15,6 +15,23 @@ export class SunspecBatteryScheduleHandler extends StorageScheduleHandler {
|
|
|
15
15
|
getSnapshotService;
|
|
16
16
|
onErrorCallback;
|
|
17
17
|
installedWChaMax;
|
|
18
|
+
/**
|
|
19
|
+
* Sticky pre-schedule snapshot, captured once on the first `onInit` and
|
|
20
|
+
* held across every subsequent schedule-to-schedule replacement. Cleared
|
|
21
|
+
* only when a real rollback fires (`mode: auto` from the data bus or
|
|
22
|
+
* `dispose`). Lets the eventual restore write the *true* baseline rather
|
|
23
|
+
* than the last-active-entry register set that the device happens to be
|
|
24
|
+
* in at the moment of replacement.
|
|
25
|
+
*/
|
|
26
|
+
originalBaseline;
|
|
27
|
+
/**
|
|
28
|
+
* Set inside the overridden `applyMessage` when an incoming
|
|
29
|
+
* `mode: schedule` message would replace an already-running schedule.
|
|
30
|
+
* Consumed (and reset) inside `onRollback` so the base library's
|
|
31
|
+
* automatic `doReleaseSchedule → onRollback` step does not actually
|
|
32
|
+
* write the snapshot back during a replacement.
|
|
33
|
+
*/
|
|
34
|
+
suppressNextRollbackWrite = false;
|
|
18
35
|
constructor(opts) {
|
|
19
36
|
super(opts);
|
|
20
37
|
this.sunspecClient = opts.sunspecClient;
|
|
@@ -23,19 +40,53 @@ export class SunspecBatteryScheduleHandler extends StorageScheduleHandler {
|
|
|
23
40
|
this.getSnapshotService = opts.getSnapshotService;
|
|
24
41
|
this.onErrorCallback = opts.onErrorCallback;
|
|
25
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Override the base-class data-bus router so a schedule-to-schedule
|
|
45
|
+
* replacement marks the next `onRollback` as "skip the write". The base
|
|
46
|
+
* library still owns the actual schedule lifecycle — we just steer one
|
|
47
|
+
* decision inside `onRollback`. `mode: auto` and any path that does not
|
|
48
|
+
* have an active schedule fall through unchanged, so the rollback fires
|
|
49
|
+
* normally there.
|
|
50
|
+
*/
|
|
51
|
+
async applyMessage(msg) {
|
|
52
|
+
if (msg.applianceId === this.applianceIdForLog
|
|
53
|
+
&& msg.data.mode === EnyoStorageScheduleModeEnum.Schedule
|
|
54
|
+
&& this.getActiveEntry() !== undefined) {
|
|
55
|
+
this.suppressNextRollbackWrite = true;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
await super.applyMessage(msg);
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
// Defence in depth: if the base class skipped the rollback for
|
|
62
|
+
// any reason (validation error, disposed, etc.), the flag would
|
|
63
|
+
// otherwise stay set and silently swallow the next real rollback.
|
|
64
|
+
this.suppressNextRollbackWrite = false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
26
67
|
async onInit(_active) {
|
|
68
|
+
if (this.originalBaseline !== undefined) {
|
|
69
|
+
// Replacement install: keep handing the library the sticky
|
|
70
|
+
// baseline so the persisted snapshot continues to point at the
|
|
71
|
+
// original pre-schedule state. The library will overwrite its
|
|
72
|
+
// storage row with the same value — harmless.
|
|
73
|
+
this.installedWChaMax = this.originalBaseline.wChaMax;
|
|
74
|
+
return { ...this.originalBaseline };
|
|
75
|
+
}
|
|
27
76
|
const controls = await this.sunspecClient.readBatteryControls(this.unitId);
|
|
28
77
|
if (!controls) {
|
|
29
78
|
throw new Error(`SunspecBatteryScheduleHandler ${this.applianceIdForLog}: failed to read pre-schedule controls`);
|
|
30
79
|
}
|
|
31
|
-
|
|
32
|
-
return {
|
|
80
|
+
const baseline = {
|
|
33
81
|
storCtlMod: controls.storCtlMod,
|
|
34
82
|
chaGriSet: controls.chaGriSet,
|
|
35
83
|
wChaMax: controls.wChaMax,
|
|
36
84
|
inWRte: controls.inWRte,
|
|
37
85
|
outWRte: controls.outWRte,
|
|
38
86
|
};
|
|
87
|
+
this.originalBaseline = baseline;
|
|
88
|
+
this.installedWChaMax = baseline.wChaMax;
|
|
89
|
+
return { ...baseline };
|
|
39
90
|
}
|
|
40
91
|
onChange(active, _previous) {
|
|
41
92
|
void this.applyEntry(active).catch(err => {
|
|
@@ -43,6 +94,25 @@ export class SunspecBatteryScheduleHandler extends StorageScheduleHandler {
|
|
|
43
94
|
});
|
|
44
95
|
}
|
|
45
96
|
onRollback(registers) {
|
|
97
|
+
if (this.suppressNextRollbackWrite) {
|
|
98
|
+
// Schedule-to-schedule replacement. Do NOT write the snapshot
|
|
99
|
+
// back; the new schedule's first `onChange` will own the
|
|
100
|
+
// register state. `installedWChaMax` stays valid (set in
|
|
101
|
+
// onInit) so the very next applyEntry has a baseline to divide
|
|
102
|
+
// against — this is the fix for the
|
|
103
|
+
// "no usable wChaMax baseline (installedWChaMax=undefined)"
|
|
104
|
+
// race seen in production logs.
|
|
105
|
+
this.suppressNextRollbackWrite = false;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Real rollback (mode: auto, dispose, restart recovery). Clear the
|
|
109
|
+
// sticky baseline so the next `onInit` re-reads the device state.
|
|
110
|
+
// Important: clear synchronously here — NOT in the async finally
|
|
111
|
+
// that follows — so a concurrent `onInit` queued behind this one
|
|
112
|
+
// (impossible today, but defensive) cannot observe a half-cleared
|
|
113
|
+
// state.
|
|
114
|
+
this.originalBaseline = undefined;
|
|
115
|
+
this.installedWChaMax = undefined;
|
|
46
116
|
void (async () => {
|
|
47
117
|
try {
|
|
48
118
|
await this.sunspecClient.writeBatteryControls(this.unitId, registers);
|
|
@@ -53,9 +123,6 @@ export class SunspecBatteryScheduleHandler extends StorageScheduleHandler {
|
|
|
53
123
|
catch (err) {
|
|
54
124
|
console.error(`SunspecBatteryScheduleHandler ${this.applianceIdForLog}: onRollback failed: ${err}`);
|
|
55
125
|
}
|
|
56
|
-
finally {
|
|
57
|
-
this.installedWChaMax = undefined;
|
|
58
|
-
}
|
|
59
126
|
})();
|
|
60
127
|
}
|
|
61
128
|
onError(err) {
|
|
@@ -178,8 +178,6 @@ export declare class SunspecInverter extends BaseSunspecDevice {
|
|
|
178
178
|
* process restarts. Idempotent.
|
|
179
179
|
*/
|
|
180
180
|
private initSnapshotService;
|
|
181
|
-
private handleStartCalibration;
|
|
182
|
-
private handleStopCalibration;
|
|
183
181
|
/**
|
|
184
182
|
* `onRestore` callback for the inverter's {@link SnapshotService}. Writes only the
|
|
185
183
|
* subset of writable inverter fields that other commands actually touched during the
|
|
@@ -408,8 +406,6 @@ export declare class SunspecBattery extends BaseSunspecDevice {
|
|
|
408
406
|
* process restarts. Idempotent.
|
|
409
407
|
*/
|
|
410
408
|
private initSnapshotService;
|
|
411
|
-
private handleStartCalibration;
|
|
412
|
-
private handleStopCalibration;
|
|
413
409
|
/**
|
|
414
410
|
* `onRestore` callback for the battery's {@link SnapshotService}. Writes only the
|
|
415
411
|
* subset of writable battery fields touched during the calibration. Errors are
|
|
@@ -432,13 +428,6 @@ export declare class SunspecMeter extends BaseSunspecDevice {
|
|
|
432
428
|
* Disconnect from the meter and update appliance state
|
|
433
429
|
*/
|
|
434
430
|
disconnect(): Promise<void>;
|
|
435
|
-
/**
|
|
436
|
-
* Meter does not implement calibration; it only subscribes to Start/StopCalibrationV1 to
|
|
437
|
-
* answer NotSupported (per the data-bus contract that every command must be acknowledged).
|
|
438
|
-
*/
|
|
439
|
-
startDataBusListening(): void;
|
|
440
|
-
stopDataBusListening(): void;
|
|
441
|
-
private handleMeterCommand;
|
|
442
431
|
/**
|
|
443
432
|
* Update meter data and return data bus messages
|
|
444
433
|
*/
|