@enyo-energy/sunspec-sdk 0.0.70 → 0.0.71
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/dist/calibration-snapshot-service.d.ts +67 -0
- package/dist/calibration-snapshot-service.js +160 -0
- package/dist/cjs/calibration-snapshot-service.cjs +164 -0
- package/dist/cjs/calibration-snapshot-service.d.cts +67 -0
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.d.cts +1 -0
- package/dist/cjs/sunspec-devices.cjs +252 -4
- package/dist/cjs/sunspec-devices.d.cts +26 -0
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/sunspec-devices.d.ts +26 -0
- package/dist/sunspec-devices.js +252 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
@@ -6,6 +6,7 @@ const node_crypto_1 = require("node:crypto");
|
|
|
6
6
|
const enyo_appliance_js_1 = require("@enyo-energy/energy-app-sdk/dist/types/enyo-appliance.js");
|
|
7
7
|
const connection_retry_manager_js_1 = require("./connection-retry-manager.cjs");
|
|
8
8
|
const enyo_data_bus_value_js_1 = require("@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js");
|
|
9
|
+
const calibration_snapshot_service_js_1 = require("./calibration-snapshot-service.cjs");
|
|
9
10
|
const enyo_source_enum_js_1 = require("@enyo-energy/energy-app-sdk/dist/types/enyo-source.enum.js");
|
|
10
11
|
const enyo_meter_appliance_js_1 = require("@enyo-energy/energy-app-sdk/dist/types/enyo-meter-appliance.js");
|
|
11
12
|
const enyo_battery_appliance_js_1 = require("@enyo-energy/energy-app-sdk/dist/types/enyo-battery-appliance.js");
|
|
@@ -99,6 +100,7 @@ class BaseSunspecDevice {
|
|
|
99
100
|
dataBus;
|
|
100
101
|
retryManager;
|
|
101
102
|
consecutiveReconnectFailures = 0;
|
|
103
|
+
calibrationService;
|
|
102
104
|
constructor(energyApp, name, networkDevice, sunspecClient, applianceManager, unitId = 1, port = 502, baseAddress = 40000, retryConfig, appliance, useTls) {
|
|
103
105
|
this.energyApp = energyApp;
|
|
104
106
|
this.name = name;
|
|
@@ -252,6 +254,27 @@ class BaseSunspecDevice {
|
|
|
252
254
|
console.log(`${this.constructor.name} ${this.applianceId}: sending ${answer} for ${acknowledgeMessage} (messageId=${messageId}${rejectionReason ? `, reason=${rejectionReason}` : ''})`);
|
|
253
255
|
this.dataBus.sendMessage([ackMessage]);
|
|
254
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* Lazily construct and initialize the per-appliance CalibrationSnapshotService. Reloads
|
|
259
|
+
* any persisted snapshot from storage so an in-flight calibration survives restarts.
|
|
260
|
+
* Idempotent — subsequent calls are no-ops once the service exists.
|
|
261
|
+
*/
|
|
262
|
+
async initCalibrationService(deviceType) {
|
|
263
|
+
if (this.calibrationService || !this.applianceId) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
this.calibrationService = new calibration_snapshot_service_js_1.CalibrationSnapshotService(this.energyApp.useStorage(), this.applianceId, (snapshot, reason) => this.restoreFromCalibrationSnapshot(snapshot, reason));
|
|
267
|
+
await this.calibrationService.initialize();
|
|
268
|
+
console.log(`${this.constructor.name} ${this.applianceId}: calibration service initialized (${deviceType})`);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Subclasses implement how to write the modified fields of a snapshot back to the device.
|
|
272
|
+
* Called both on explicit StopCalibrationV1 and when the 5-minute auto-stop fires.
|
|
273
|
+
*/
|
|
274
|
+
async restoreFromCalibrationSnapshot(_snapshot, _reason) {
|
|
275
|
+
// Default: subclasses override. Base implementation is a no-op so meter and other
|
|
276
|
+
// non-controllable devices don't need to implement it.
|
|
277
|
+
}
|
|
255
278
|
}
|
|
256
279
|
exports.BaseSunspecDevice = BaseSunspecDevice;
|
|
257
280
|
/**
|
|
@@ -675,8 +698,16 @@ class SunspecInverter extends BaseSunspecDevice {
|
|
|
675
698
|
if (this.dataBusListenerId) {
|
|
676
699
|
return;
|
|
677
700
|
}
|
|
701
|
+
// Fire and forget — calibration service hydration must not block listening setup.
|
|
702
|
+
// Listener registration before await ensures we don't miss messages that arrive
|
|
703
|
+
// during initial load.
|
|
704
|
+
void this.initCalibrationService('inverter');
|
|
678
705
|
this.dataBus = this.energyApp.useDataBus();
|
|
679
|
-
this.dataBusListenerId = this.dataBus.listenForMessages([
|
|
706
|
+
this.dataBusListenerId = this.dataBus.listenForMessages([
|
|
707
|
+
enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.SetInverterFeedInLimitV1,
|
|
708
|
+
enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StartCalibrationV1,
|
|
709
|
+
enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StopCalibrationV1,
|
|
710
|
+
], (entry) => this.handleInverterCommand(entry));
|
|
680
711
|
console.log(`Inverter ${this.applianceId}: started data bus listening (listener ${this.dataBusListenerId})`);
|
|
681
712
|
}
|
|
682
713
|
/**
|
|
@@ -696,8 +727,16 @@ class SunspecInverter extends BaseSunspecDevice {
|
|
|
696
727
|
}
|
|
697
728
|
void (async () => {
|
|
698
729
|
try {
|
|
699
|
-
|
|
700
|
-
|
|
730
|
+
switch (entry.message) {
|
|
731
|
+
case enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.SetInverterFeedInLimitV1:
|
|
732
|
+
await this.handleSetFeedInLimit(entry);
|
|
733
|
+
break;
|
|
734
|
+
case enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StartCalibrationV1:
|
|
735
|
+
await this.handleStartCalibration(entry);
|
|
736
|
+
break;
|
|
737
|
+
case enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StopCalibrationV1:
|
|
738
|
+
await this.handleStopCalibration(entry);
|
|
739
|
+
break;
|
|
701
740
|
}
|
|
702
741
|
}
|
|
703
742
|
catch (error) {
|
|
@@ -721,8 +760,93 @@ class SunspecInverter extends BaseSunspecDevice {
|
|
|
721
760
|
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to set feed-in limit');
|
|
722
761
|
return;
|
|
723
762
|
}
|
|
763
|
+
// setFeedInLimit writes WMaxLimPct + WMaxLim_Ena (see SunspecModbusClient.setFeedInLimit).
|
|
764
|
+
// Record both so an active calibration can roll them back on stop.
|
|
765
|
+
await this.calibrationService?.recordModification(['WMaxLimPct', 'WMaxLim_Ena']);
|
|
724
766
|
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
725
767
|
}
|
|
768
|
+
async handleStartCalibration(msg) {
|
|
769
|
+
if (!this.isConnected() || !this.applianceId) {
|
|
770
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Not connected');
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
if (!this.calibrationService) {
|
|
774
|
+
await this.initCalibrationService('inverter');
|
|
775
|
+
}
|
|
776
|
+
if (!this.calibrationService) {
|
|
777
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Calibration service unavailable');
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
if (this.calibrationService.isCalibrating()) {
|
|
781
|
+
console.log(`Inverter ${this.applianceId}: calibration already active — ack idempotently`);
|
|
782
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
console.log(`Inverter ${this.applianceId}: handling StartCalibrationV1`);
|
|
786
|
+
const controls = await this.sunspecClient.readInverterControls(this.unitId);
|
|
787
|
+
if (!controls) {
|
|
788
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to read inverter controls for snapshot');
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
try {
|
|
792
|
+
await this.calibrationService.startCalibration({
|
|
793
|
+
deviceType: 'inverter',
|
|
794
|
+
unitId: this.unitId,
|
|
795
|
+
inverterControls: controls,
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
catch (error) {
|
|
799
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, `Failed to persist snapshot: ${error}`);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
803
|
+
}
|
|
804
|
+
async handleStopCalibration(msg) {
|
|
805
|
+
if (!this.applianceId) {
|
|
806
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Not initialized');
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
console.log(`Inverter ${this.applianceId}: handling StopCalibrationV1`);
|
|
810
|
+
const snapshot = await this.calibrationService?.stopCalibration();
|
|
811
|
+
if (!snapshot) {
|
|
812
|
+
// No active calibration — treat as a no-op success.
|
|
813
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
try {
|
|
817
|
+
await this.restoreFromCalibrationSnapshot(snapshot, 'stop');
|
|
818
|
+
}
|
|
819
|
+
catch (error) {
|
|
820
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, `Failed to restore registers: ${error}`);
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
824
|
+
}
|
|
825
|
+
async restoreFromCalibrationSnapshot(snapshot, reason) {
|
|
826
|
+
if (snapshot.deviceType !== 'inverter' || !snapshot.inverterControls) {
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
const source = snapshot.inverterControls;
|
|
830
|
+
const partial = {};
|
|
831
|
+
// Whitelist of inverter fields that writeInverterControls actually writes (Model 123).
|
|
832
|
+
// Iterating a typed const list keeps the projection type-safe and bounds the rollback
|
|
833
|
+
// to fields the SDK knows how to restore.
|
|
834
|
+
const WRITABLE_INVERTER_FIELDS = ['Conn', 'WMaxLimPct', 'WMaxLim_Ena', 'OutPFSet', 'OutPFSet_Ena'];
|
|
835
|
+
for (const field of WRITABLE_INVERTER_FIELDS) {
|
|
836
|
+
if (snapshot.modifiedFields.includes(field) && source[field] !== undefined) {
|
|
837
|
+
partial[field] = source[field];
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (Object.keys(partial).length === 0) {
|
|
841
|
+
console.log(`Inverter ${this.applianceId}: calibration ${reason} — no modified fields to restore`);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
console.log(`Inverter ${this.applianceId}: calibration ${reason} — restoring [${Object.keys(partial).join(', ')}]`);
|
|
845
|
+
const ok = await this.sunspecClient.writeInverterControls(snapshot.unitId, partial);
|
|
846
|
+
if (!ok) {
|
|
847
|
+
throw new Error('writeInverterControls returned false');
|
|
848
|
+
}
|
|
849
|
+
}
|
|
726
850
|
}
|
|
727
851
|
exports.SunspecInverter = SunspecInverter;
|
|
728
852
|
/**
|
|
@@ -1167,12 +1291,16 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
1167
1291
|
if (this.dataBusListenerId) {
|
|
1168
1292
|
return;
|
|
1169
1293
|
}
|
|
1294
|
+
// Fire and forget — calibration service hydration must not block listening setup.
|
|
1295
|
+
void this.initCalibrationService('battery');
|
|
1170
1296
|
this.dataBus = this.energyApp.useDataBus();
|
|
1171
1297
|
this.dataBusListenerId = this.dataBus.listenForMessages([
|
|
1172
1298
|
enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StartStorageGridChargeV1,
|
|
1173
1299
|
enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StopStorageGridChargeV1,
|
|
1174
1300
|
enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.SetStorageDischargeLimitV1,
|
|
1175
|
-
enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.SetStorageChargeLimitV1
|
|
1301
|
+
enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.SetStorageChargeLimitV1,
|
|
1302
|
+
enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StartCalibrationV1,
|
|
1303
|
+
enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StopCalibrationV1,
|
|
1176
1304
|
], (entry) => this.handleStorageCommand(entry));
|
|
1177
1305
|
console.log(`Battery ${this.applianceId}: started data bus listening (listener ${this.dataBusListenerId})`);
|
|
1178
1306
|
}
|
|
@@ -1206,6 +1334,12 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
1206
1334
|
case enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.SetStorageChargeLimitV1:
|
|
1207
1335
|
await this.handleSetChargeLimit(entry);
|
|
1208
1336
|
break;
|
|
1337
|
+
case enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StartCalibrationV1:
|
|
1338
|
+
await this.handleStartCalibration(entry);
|
|
1339
|
+
break;
|
|
1340
|
+
case enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StopCalibrationV1:
|
|
1341
|
+
await this.handleStopCalibration(entry);
|
|
1342
|
+
break;
|
|
1209
1343
|
}
|
|
1210
1344
|
}
|
|
1211
1345
|
catch (error) {
|
|
@@ -1246,6 +1380,7 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
1246
1380
|
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to set storage mode to charge');
|
|
1247
1381
|
return;
|
|
1248
1382
|
}
|
|
1383
|
+
await this.calibrationService?.recordModification(['chaGriSet', 'wChaMax', 'storCtlMod']);
|
|
1249
1384
|
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
1250
1385
|
}
|
|
1251
1386
|
async handleStopGridCharge(msg) {
|
|
@@ -1272,6 +1407,7 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
1272
1407
|
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to set storage mode to auto');
|
|
1273
1408
|
return;
|
|
1274
1409
|
}
|
|
1410
|
+
await this.calibrationService?.recordModification(['chaGriSet', 'storCtlMod']);
|
|
1275
1411
|
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
1276
1412
|
}
|
|
1277
1413
|
async handleSetDischargeLimit(msg) {
|
|
@@ -1296,6 +1432,7 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
1296
1432
|
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to set discharge limit');
|
|
1297
1433
|
return;
|
|
1298
1434
|
}
|
|
1435
|
+
await this.calibrationService?.recordModification(['outWRte']);
|
|
1299
1436
|
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
1300
1437
|
}
|
|
1301
1438
|
async handleSetChargeLimit(msg) {
|
|
@@ -1320,8 +1457,88 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
1320
1457
|
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to set charge limit');
|
|
1321
1458
|
return;
|
|
1322
1459
|
}
|
|
1460
|
+
await this.calibrationService?.recordModification(['inWRte']);
|
|
1323
1461
|
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
1324
1462
|
}
|
|
1463
|
+
async handleStartCalibration(msg) {
|
|
1464
|
+
if (!this.isConnected() || !this.applianceId) {
|
|
1465
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Not connected');
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
if (!this.calibrationService) {
|
|
1469
|
+
await this.initCalibrationService('battery');
|
|
1470
|
+
}
|
|
1471
|
+
if (!this.calibrationService) {
|
|
1472
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Calibration service unavailable');
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
if (this.calibrationService.isCalibrating()) {
|
|
1476
|
+
console.log(`Battery ${this.applianceId}: calibration already active — ack idempotently`);
|
|
1477
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
console.log(`Battery ${this.applianceId}: handling StartCalibrationV1`);
|
|
1481
|
+
const controls = await this.sunspecClient.readBatteryControls(this.unitId);
|
|
1482
|
+
if (!controls) {
|
|
1483
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to read battery controls for snapshot');
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
try {
|
|
1487
|
+
await this.calibrationService.startCalibration({
|
|
1488
|
+
deviceType: 'battery',
|
|
1489
|
+
unitId: this.unitId,
|
|
1490
|
+
batteryControls: controls,
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
catch (error) {
|
|
1494
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, `Failed to persist snapshot: ${error}`);
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
1498
|
+
}
|
|
1499
|
+
async handleStopCalibration(msg) {
|
|
1500
|
+
if (!this.applianceId) {
|
|
1501
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Not initialized');
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
console.log(`Battery ${this.applianceId}: handling StopCalibrationV1`);
|
|
1505
|
+
const snapshot = await this.calibrationService?.stopCalibration();
|
|
1506
|
+
if (!snapshot) {
|
|
1507
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
1508
|
+
return;
|
|
1509
|
+
}
|
|
1510
|
+
try {
|
|
1511
|
+
await this.restoreFromCalibrationSnapshot(snapshot, 'stop');
|
|
1512
|
+
}
|
|
1513
|
+
catch (error) {
|
|
1514
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, `Failed to restore registers: ${error}`);
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
1518
|
+
}
|
|
1519
|
+
async restoreFromCalibrationSnapshot(snapshot, reason) {
|
|
1520
|
+
if (snapshot.deviceType !== 'battery' || !snapshot.batteryControls) {
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
const source = snapshot.batteryControls;
|
|
1524
|
+
const partial = {};
|
|
1525
|
+
// Whitelist of battery fields that writeBatteryControls actually writes (Model 124).
|
|
1526
|
+
const WRITABLE_BATTERY_FIELDS = ['storCtlMod', 'chaGriSet', 'wChaMax', 'inWRte', 'outWRte', 'minRsvPct'];
|
|
1527
|
+
for (const field of WRITABLE_BATTERY_FIELDS) {
|
|
1528
|
+
if (snapshot.modifiedFields.includes(field) && source[field] !== undefined) {
|
|
1529
|
+
partial[field] = source[field];
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
if (Object.keys(partial).length === 0) {
|
|
1533
|
+
console.log(`Battery ${this.applianceId}: calibration ${reason} — no modified fields to restore`);
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
console.log(`Battery ${this.applianceId}: calibration ${reason} — restoring [${Object.keys(partial).join(', ')}]`);
|
|
1537
|
+
const ok = await this.sunspecClient.writeBatteryControls(snapshot.unitId, partial);
|
|
1538
|
+
if (!ok) {
|
|
1539
|
+
throw new Error('writeBatteryControls returned false');
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1325
1542
|
}
|
|
1326
1543
|
exports.SunspecBattery = SunspecBattery;
|
|
1327
1544
|
/**
|
|
@@ -1397,11 +1614,13 @@ class SunspecMeter extends BaseSunspecDevice {
|
|
|
1397
1614
|
console.error(`Failed to update meter appliance: ${error}`);
|
|
1398
1615
|
}
|
|
1399
1616
|
}
|
|
1617
|
+
this.startDataBusListening();
|
|
1400
1618
|
}
|
|
1401
1619
|
/**
|
|
1402
1620
|
* Disconnect from the meter and update appliance state
|
|
1403
1621
|
*/
|
|
1404
1622
|
async disconnect() {
|
|
1623
|
+
this.stopDataBusListening();
|
|
1405
1624
|
if (this.applianceId) {
|
|
1406
1625
|
try {
|
|
1407
1626
|
await this.applianceManager.updateApplianceState(this.applianceId, enyo_appliance_js_1.EnyoApplianceConnectionType.Connector, enyo_appliance_js_1.EnyoApplianceStateEnum.Offline);
|
|
@@ -1413,6 +1632,35 @@ class SunspecMeter extends BaseSunspecDevice {
|
|
|
1413
1632
|
// Close just this meter's unit; other devices on the same network device stay open.
|
|
1414
1633
|
await this.sunspecClient.disconnectUnit(this.unitId);
|
|
1415
1634
|
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Meter does not implement calibration; it only subscribes to Start/StopCalibrationV1 to
|
|
1637
|
+
* answer NotSupported (per the data-bus contract that every command must be acknowledged).
|
|
1638
|
+
*/
|
|
1639
|
+
startDataBusListening() {
|
|
1640
|
+
if (this.dataBusListenerId) {
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
this.dataBus = this.energyApp.useDataBus();
|
|
1644
|
+
this.dataBusListenerId = this.dataBus.listenForMessages([
|
|
1645
|
+
enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StartCalibrationV1,
|
|
1646
|
+
enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StopCalibrationV1,
|
|
1647
|
+
], (entry) => this.handleMeterCommand(entry));
|
|
1648
|
+
console.log(`Meter ${this.applianceId}: started data bus listening (listener ${this.dataBusListenerId})`);
|
|
1649
|
+
}
|
|
1650
|
+
stopDataBusListening() {
|
|
1651
|
+
if (this.dataBusListenerId && this.dataBus) {
|
|
1652
|
+
this.dataBus.unsubscribe(this.dataBusListenerId);
|
|
1653
|
+
console.log(`Meter ${this.applianceId}: stopped data bus listening (listener ${this.dataBusListenerId})`);
|
|
1654
|
+
}
|
|
1655
|
+
this.dataBusListenerId = undefined;
|
|
1656
|
+
this.dataBus = undefined;
|
|
1657
|
+
}
|
|
1658
|
+
handleMeterCommand(entry) {
|
|
1659
|
+
if (entry.applianceId !== this.applianceId) {
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
this.sendCommandAcknowledge(entry.id, entry.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.NotSupported, 'Meter does not support calibration');
|
|
1663
|
+
}
|
|
1416
1664
|
/**
|
|
1417
1665
|
* Update meter data and return data bus messages
|
|
1418
1666
|
*/
|
|
@@ -5,6 +5,7 @@ import { EnyoNetworkDevice } from "@enyo-energy/energy-app-sdk/dist/types/enyo-n
|
|
|
5
5
|
import { SunspecModbusClient } from "./sunspec-modbus-client.cjs";
|
|
6
6
|
import { ConnectionRetryManager } from "./connection-retry-manager.cjs";
|
|
7
7
|
import { EnyoCommandAcknowledgeAnswerEnum, EnyoDataBusMessage, EnyoDataBusMessageEnum, EnyoDataBusMessageResolution } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
|
|
8
|
+
import { type CalibrationDeviceType, type CalibrationRestoreReason, type CalibrationSnapshot, CalibrationSnapshotService } from "./calibration-snapshot-service.cjs";
|
|
8
9
|
import { EnergyAppDataBus } from "@enyo-energy/energy-app-sdk/dist/packages/energy-app-data-bus.js";
|
|
9
10
|
/**
|
|
10
11
|
* Base abstract class for all Sunspec devices
|
|
@@ -25,6 +26,7 @@ export declare abstract class BaseSunspecDevice {
|
|
|
25
26
|
protected dataBus?: EnergyAppDataBus;
|
|
26
27
|
protected retryManager: ConnectionRetryManager;
|
|
27
28
|
protected consecutiveReconnectFailures: number;
|
|
29
|
+
protected calibrationService?: CalibrationSnapshotService;
|
|
28
30
|
constructor(energyApp: EnergyApp, name: EnyoApplianceName[], networkDevice: EnyoNetworkDevice, sunspecClient: SunspecModbusClient, applianceManager: ApplianceManager, unitId?: number, port?: number, baseAddress?: number, retryConfig?: IRetryConfig, appliance?: EnyoAppliance, useTls?: boolean | undefined);
|
|
29
31
|
/**
|
|
30
32
|
* Connect to the device and create/update the appliance
|
|
@@ -77,6 +79,17 @@ export declare abstract class BaseSunspecDevice {
|
|
|
77
79
|
*/
|
|
78
80
|
protected markOffline(): Promise<void>;
|
|
79
81
|
protected sendCommandAcknowledge(messageId: string, acknowledgeMessage: EnyoDataBusMessageEnum | string, answer: EnyoCommandAcknowledgeAnswerEnum, rejectionReason?: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* Lazily construct and initialize the per-appliance CalibrationSnapshotService. Reloads
|
|
84
|
+
* any persisted snapshot from storage so an in-flight calibration survives restarts.
|
|
85
|
+
* Idempotent — subsequent calls are no-ops once the service exists.
|
|
86
|
+
*/
|
|
87
|
+
protected initCalibrationService(deviceType: CalibrationDeviceType): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Subclasses implement how to write the modified fields of a snapshot back to the device.
|
|
90
|
+
* Called both on explicit StopCalibrationV1 and when the 5-minute auto-stop fires.
|
|
91
|
+
*/
|
|
92
|
+
protected restoreFromCalibrationSnapshot(_snapshot: CalibrationSnapshot, _reason: CalibrationRestoreReason): Promise<void>;
|
|
80
93
|
}
|
|
81
94
|
/**
|
|
82
95
|
* Sunspec Inverter implementation using dynamic model discovery
|
|
@@ -156,6 +169,9 @@ export declare class SunspecInverter extends BaseSunspecDevice {
|
|
|
156
169
|
stopDataBusListening(): void;
|
|
157
170
|
private handleInverterCommand;
|
|
158
171
|
private handleSetFeedInLimit;
|
|
172
|
+
private handleStartCalibration;
|
|
173
|
+
private handleStopCalibration;
|
|
174
|
+
protected restoreFromCalibrationSnapshot(snapshot: CalibrationSnapshot, reason: CalibrationRestoreReason): Promise<void>;
|
|
159
175
|
}
|
|
160
176
|
/**
|
|
161
177
|
* Sunspec Battery implementation
|
|
@@ -288,6 +304,9 @@ export declare class SunspecBattery extends BaseSunspecDevice {
|
|
|
288
304
|
private handleStopGridCharge;
|
|
289
305
|
private handleSetDischargeLimit;
|
|
290
306
|
private handleSetChargeLimit;
|
|
307
|
+
private handleStartCalibration;
|
|
308
|
+
private handleStopCalibration;
|
|
309
|
+
protected restoreFromCalibrationSnapshot(snapshot: CalibrationSnapshot, reason: CalibrationRestoreReason): Promise<void>;
|
|
291
310
|
}
|
|
292
311
|
/**
|
|
293
312
|
* Sunspec Meter implementation
|
|
@@ -303,6 +322,13 @@ export declare class SunspecMeter extends BaseSunspecDevice {
|
|
|
303
322
|
* Disconnect from the meter and update appliance state
|
|
304
323
|
*/
|
|
305
324
|
disconnect(): Promise<void>;
|
|
325
|
+
/**
|
|
326
|
+
* Meter does not implement calibration; it only subscribes to Start/StopCalibrationV1 to
|
|
327
|
+
* answer NotSupported (per the data-bus contract that every command must be acknowledged).
|
|
328
|
+
*/
|
|
329
|
+
startDataBusListening(): void;
|
|
330
|
+
stopDataBusListening(): void;
|
|
331
|
+
private handleMeterCommand;
|
|
306
332
|
/**
|
|
307
333
|
* Update meter data and return data bus messages
|
|
308
334
|
*/
|
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.71';
|
|
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
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './sunspec-interfaces.js';
|
|
2
2
|
export * from './sunspec-devices.js';
|
|
3
3
|
export * from './sunspec-modbus-client.js';
|
|
4
|
+
export * from './calibration-snapshot-service.js';
|
|
4
5
|
export { ConnectionRetryManager } from './connection-retry-manager.js';
|
|
5
6
|
export { SDK_VERSION, getSdkVersion } from './version.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './sunspec-interfaces.js';
|
|
2
2
|
export * from './sunspec-devices.js';
|
|
3
3
|
export * from './sunspec-modbus-client.js';
|
|
4
|
+
export * from './calibration-snapshot-service.js';
|
|
4
5
|
export { ConnectionRetryManager } from './connection-retry-manager.js';
|
|
5
6
|
export { SDK_VERSION, getSdkVersion } from './version.js';
|
|
@@ -5,6 +5,7 @@ import { EnyoNetworkDevice } from "@enyo-energy/energy-app-sdk/dist/types/enyo-n
|
|
|
5
5
|
import { SunspecModbusClient } from "./sunspec-modbus-client.js";
|
|
6
6
|
import { ConnectionRetryManager } from "./connection-retry-manager.js";
|
|
7
7
|
import { EnyoCommandAcknowledgeAnswerEnum, EnyoDataBusMessage, EnyoDataBusMessageEnum, EnyoDataBusMessageResolution } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
|
|
8
|
+
import { type CalibrationDeviceType, type CalibrationRestoreReason, type CalibrationSnapshot, CalibrationSnapshotService } from "./calibration-snapshot-service.js";
|
|
8
9
|
import { EnergyAppDataBus } from "@enyo-energy/energy-app-sdk/dist/packages/energy-app-data-bus.js";
|
|
9
10
|
/**
|
|
10
11
|
* Base abstract class for all Sunspec devices
|
|
@@ -25,6 +26,7 @@ export declare abstract class BaseSunspecDevice {
|
|
|
25
26
|
protected dataBus?: EnergyAppDataBus;
|
|
26
27
|
protected retryManager: ConnectionRetryManager;
|
|
27
28
|
protected consecutiveReconnectFailures: number;
|
|
29
|
+
protected calibrationService?: CalibrationSnapshotService;
|
|
28
30
|
constructor(energyApp: EnergyApp, name: EnyoApplianceName[], networkDevice: EnyoNetworkDevice, sunspecClient: SunspecModbusClient, applianceManager: ApplianceManager, unitId?: number, port?: number, baseAddress?: number, retryConfig?: IRetryConfig, appliance?: EnyoAppliance, useTls?: boolean | undefined);
|
|
29
31
|
/**
|
|
30
32
|
* Connect to the device and create/update the appliance
|
|
@@ -77,6 +79,17 @@ export declare abstract class BaseSunspecDevice {
|
|
|
77
79
|
*/
|
|
78
80
|
protected markOffline(): Promise<void>;
|
|
79
81
|
protected sendCommandAcknowledge(messageId: string, acknowledgeMessage: EnyoDataBusMessageEnum | string, answer: EnyoCommandAcknowledgeAnswerEnum, rejectionReason?: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* Lazily construct and initialize the per-appliance CalibrationSnapshotService. Reloads
|
|
84
|
+
* any persisted snapshot from storage so an in-flight calibration survives restarts.
|
|
85
|
+
* Idempotent — subsequent calls are no-ops once the service exists.
|
|
86
|
+
*/
|
|
87
|
+
protected initCalibrationService(deviceType: CalibrationDeviceType): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Subclasses implement how to write the modified fields of a snapshot back to the device.
|
|
90
|
+
* Called both on explicit StopCalibrationV1 and when the 5-minute auto-stop fires.
|
|
91
|
+
*/
|
|
92
|
+
protected restoreFromCalibrationSnapshot(_snapshot: CalibrationSnapshot, _reason: CalibrationRestoreReason): Promise<void>;
|
|
80
93
|
}
|
|
81
94
|
/**
|
|
82
95
|
* Sunspec Inverter implementation using dynamic model discovery
|
|
@@ -156,6 +169,9 @@ export declare class SunspecInverter extends BaseSunspecDevice {
|
|
|
156
169
|
stopDataBusListening(): void;
|
|
157
170
|
private handleInverterCommand;
|
|
158
171
|
private handleSetFeedInLimit;
|
|
172
|
+
private handleStartCalibration;
|
|
173
|
+
private handleStopCalibration;
|
|
174
|
+
protected restoreFromCalibrationSnapshot(snapshot: CalibrationSnapshot, reason: CalibrationRestoreReason): Promise<void>;
|
|
159
175
|
}
|
|
160
176
|
/**
|
|
161
177
|
* Sunspec Battery implementation
|
|
@@ -288,6 +304,9 @@ export declare class SunspecBattery extends BaseSunspecDevice {
|
|
|
288
304
|
private handleStopGridCharge;
|
|
289
305
|
private handleSetDischargeLimit;
|
|
290
306
|
private handleSetChargeLimit;
|
|
307
|
+
private handleStartCalibration;
|
|
308
|
+
private handleStopCalibration;
|
|
309
|
+
protected restoreFromCalibrationSnapshot(snapshot: CalibrationSnapshot, reason: CalibrationRestoreReason): Promise<void>;
|
|
291
310
|
}
|
|
292
311
|
/**
|
|
293
312
|
* Sunspec Meter implementation
|
|
@@ -303,6 +322,13 @@ export declare class SunspecMeter extends BaseSunspecDevice {
|
|
|
303
322
|
* Disconnect from the meter and update appliance state
|
|
304
323
|
*/
|
|
305
324
|
disconnect(): Promise<void>;
|
|
325
|
+
/**
|
|
326
|
+
* Meter does not implement calibration; it only subscribes to Start/StopCalibrationV1 to
|
|
327
|
+
* answer NotSupported (per the data-bus contract that every command must be acknowledged).
|
|
328
|
+
*/
|
|
329
|
+
startDataBusListening(): void;
|
|
330
|
+
stopDataBusListening(): void;
|
|
331
|
+
private handleMeterCommand;
|
|
306
332
|
/**
|
|
307
333
|
* Update meter data and return data bus messages
|
|
308
334
|
*/
|