@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
package/dist/sunspec-devices.js
CHANGED
|
@@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import { EnyoApplianceConnectionType, EnyoApplianceStateEnum, EnyoApplianceStatusEnum, EnyoApplianceTopologyFeatureEnum, EnyoApplianceTypeEnum } from "@enyo-energy/energy-app-sdk/dist/types/enyo-appliance.js";
|
|
4
4
|
import { ConnectionRetryManager } from "./connection-retry-manager.js";
|
|
5
5
|
import { EnyoBatteryStateEnum, EnyoCommandAcknowledgeAnswerEnum, EnyoDataBusMessageEnum, EnyoInverterStateEnum, EnyoStringStateEnum } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
|
|
6
|
+
import { CalibrationSnapshotService } from "./calibration-snapshot-service.js";
|
|
6
7
|
import { EnyoSourceEnum } from "@enyo-energy/energy-app-sdk/dist/types/enyo-source.enum.js";
|
|
7
8
|
import { EnyoMeterApplianceAvailableFeaturesEnum } from "@enyo-energy/energy-app-sdk/dist/types/enyo-meter-appliance.js";
|
|
8
9
|
import { EnyoBatteryFeature, EnyoBatteryStorageMode } from "@enyo-energy/energy-app-sdk/dist/types/enyo-battery-appliance.js";
|
|
@@ -96,6 +97,7 @@ export class BaseSunspecDevice {
|
|
|
96
97
|
dataBus;
|
|
97
98
|
retryManager;
|
|
98
99
|
consecutiveReconnectFailures = 0;
|
|
100
|
+
calibrationService;
|
|
99
101
|
constructor(energyApp, name, networkDevice, sunspecClient, applianceManager, unitId = 1, port = 502, baseAddress = 40000, retryConfig, appliance, useTls) {
|
|
100
102
|
this.energyApp = energyApp;
|
|
101
103
|
this.name = name;
|
|
@@ -249,6 +251,27 @@ export class BaseSunspecDevice {
|
|
|
249
251
|
console.log(`${this.constructor.name} ${this.applianceId}: sending ${answer} for ${acknowledgeMessage} (messageId=${messageId}${rejectionReason ? `, reason=${rejectionReason}` : ''})`);
|
|
250
252
|
this.dataBus.sendMessage([ackMessage]);
|
|
251
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* Lazily construct and initialize the per-appliance CalibrationSnapshotService. Reloads
|
|
256
|
+
* any persisted snapshot from storage so an in-flight calibration survives restarts.
|
|
257
|
+
* Idempotent — subsequent calls are no-ops once the service exists.
|
|
258
|
+
*/
|
|
259
|
+
async initCalibrationService(deviceType) {
|
|
260
|
+
if (this.calibrationService || !this.applianceId) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
this.calibrationService = new CalibrationSnapshotService(this.energyApp.useStorage(), this.applianceId, (snapshot, reason) => this.restoreFromCalibrationSnapshot(snapshot, reason));
|
|
264
|
+
await this.calibrationService.initialize();
|
|
265
|
+
console.log(`${this.constructor.name} ${this.applianceId}: calibration service initialized (${deviceType})`);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Subclasses implement how to write the modified fields of a snapshot back to the device.
|
|
269
|
+
* Called both on explicit StopCalibrationV1 and when the 5-minute auto-stop fires.
|
|
270
|
+
*/
|
|
271
|
+
async restoreFromCalibrationSnapshot(_snapshot, _reason) {
|
|
272
|
+
// Default: subclasses override. Base implementation is a no-op so meter and other
|
|
273
|
+
// non-controllable devices don't need to implement it.
|
|
274
|
+
}
|
|
252
275
|
}
|
|
253
276
|
/**
|
|
254
277
|
* Sunspec Inverter implementation using dynamic model discovery
|
|
@@ -671,8 +694,16 @@ export class SunspecInverter extends BaseSunspecDevice {
|
|
|
671
694
|
if (this.dataBusListenerId) {
|
|
672
695
|
return;
|
|
673
696
|
}
|
|
697
|
+
// Fire and forget — calibration service hydration must not block listening setup.
|
|
698
|
+
// Listener registration before await ensures we don't miss messages that arrive
|
|
699
|
+
// during initial load.
|
|
700
|
+
void this.initCalibrationService('inverter');
|
|
674
701
|
this.dataBus = this.energyApp.useDataBus();
|
|
675
|
-
this.dataBusListenerId = this.dataBus.listenForMessages([
|
|
702
|
+
this.dataBusListenerId = this.dataBus.listenForMessages([
|
|
703
|
+
EnyoDataBusMessageEnum.SetInverterFeedInLimitV1,
|
|
704
|
+
EnyoDataBusMessageEnum.StartCalibrationV1,
|
|
705
|
+
EnyoDataBusMessageEnum.StopCalibrationV1,
|
|
706
|
+
], (entry) => this.handleInverterCommand(entry));
|
|
676
707
|
console.log(`Inverter ${this.applianceId}: started data bus listening (listener ${this.dataBusListenerId})`);
|
|
677
708
|
}
|
|
678
709
|
/**
|
|
@@ -692,8 +723,16 @@ export class SunspecInverter extends BaseSunspecDevice {
|
|
|
692
723
|
}
|
|
693
724
|
void (async () => {
|
|
694
725
|
try {
|
|
695
|
-
|
|
696
|
-
|
|
726
|
+
switch (entry.message) {
|
|
727
|
+
case EnyoDataBusMessageEnum.SetInverterFeedInLimitV1:
|
|
728
|
+
await this.handleSetFeedInLimit(entry);
|
|
729
|
+
break;
|
|
730
|
+
case EnyoDataBusMessageEnum.StartCalibrationV1:
|
|
731
|
+
await this.handleStartCalibration(entry);
|
|
732
|
+
break;
|
|
733
|
+
case EnyoDataBusMessageEnum.StopCalibrationV1:
|
|
734
|
+
await this.handleStopCalibration(entry);
|
|
735
|
+
break;
|
|
697
736
|
}
|
|
698
737
|
}
|
|
699
738
|
catch (error) {
|
|
@@ -717,8 +756,93 @@ export class SunspecInverter extends BaseSunspecDevice {
|
|
|
717
756
|
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to set feed-in limit');
|
|
718
757
|
return;
|
|
719
758
|
}
|
|
759
|
+
// setFeedInLimit writes WMaxLimPct + WMaxLim_Ena (see SunspecModbusClient.setFeedInLimit).
|
|
760
|
+
// Record both so an active calibration can roll them back on stop.
|
|
761
|
+
await this.calibrationService?.recordModification(['WMaxLimPct', 'WMaxLim_Ena']);
|
|
720
762
|
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
721
763
|
}
|
|
764
|
+
async handleStartCalibration(msg) {
|
|
765
|
+
if (!this.isConnected() || !this.applianceId) {
|
|
766
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Not connected');
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
if (!this.calibrationService) {
|
|
770
|
+
await this.initCalibrationService('inverter');
|
|
771
|
+
}
|
|
772
|
+
if (!this.calibrationService) {
|
|
773
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Calibration service unavailable');
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (this.calibrationService.isCalibrating()) {
|
|
777
|
+
console.log(`Inverter ${this.applianceId}: calibration already active — ack idempotently`);
|
|
778
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
console.log(`Inverter ${this.applianceId}: handling StartCalibrationV1`);
|
|
782
|
+
const controls = await this.sunspecClient.readInverterControls(this.unitId);
|
|
783
|
+
if (!controls) {
|
|
784
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to read inverter controls for snapshot');
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
try {
|
|
788
|
+
await this.calibrationService.startCalibration({
|
|
789
|
+
deviceType: 'inverter',
|
|
790
|
+
unitId: this.unitId,
|
|
791
|
+
inverterControls: controls,
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
catch (error) {
|
|
795
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, `Failed to persist snapshot: ${error}`);
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
799
|
+
}
|
|
800
|
+
async handleStopCalibration(msg) {
|
|
801
|
+
if (!this.applianceId) {
|
|
802
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Not initialized');
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
console.log(`Inverter ${this.applianceId}: handling StopCalibrationV1`);
|
|
806
|
+
const snapshot = await this.calibrationService?.stopCalibration();
|
|
807
|
+
if (!snapshot) {
|
|
808
|
+
// No active calibration — treat as a no-op success.
|
|
809
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
try {
|
|
813
|
+
await this.restoreFromCalibrationSnapshot(snapshot, 'stop');
|
|
814
|
+
}
|
|
815
|
+
catch (error) {
|
|
816
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, `Failed to restore registers: ${error}`);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
820
|
+
}
|
|
821
|
+
async restoreFromCalibrationSnapshot(snapshot, reason) {
|
|
822
|
+
if (snapshot.deviceType !== 'inverter' || !snapshot.inverterControls) {
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
const source = snapshot.inverterControls;
|
|
826
|
+
const partial = {};
|
|
827
|
+
// Whitelist of inverter fields that writeInverterControls actually writes (Model 123).
|
|
828
|
+
// Iterating a typed const list keeps the projection type-safe and bounds the rollback
|
|
829
|
+
// to fields the SDK knows how to restore.
|
|
830
|
+
const WRITABLE_INVERTER_FIELDS = ['Conn', 'WMaxLimPct', 'WMaxLim_Ena', 'OutPFSet', 'OutPFSet_Ena'];
|
|
831
|
+
for (const field of WRITABLE_INVERTER_FIELDS) {
|
|
832
|
+
if (snapshot.modifiedFields.includes(field) && source[field] !== undefined) {
|
|
833
|
+
partial[field] = source[field];
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
if (Object.keys(partial).length === 0) {
|
|
837
|
+
console.log(`Inverter ${this.applianceId}: calibration ${reason} — no modified fields to restore`);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
console.log(`Inverter ${this.applianceId}: calibration ${reason} — restoring [${Object.keys(partial).join(', ')}]`);
|
|
841
|
+
const ok = await this.sunspecClient.writeInverterControls(snapshot.unitId, partial);
|
|
842
|
+
if (!ok) {
|
|
843
|
+
throw new Error('writeInverterControls returned false');
|
|
844
|
+
}
|
|
845
|
+
}
|
|
722
846
|
}
|
|
723
847
|
/**
|
|
724
848
|
* Sunspec Battery implementation
|
|
@@ -1162,12 +1286,16 @@ export class SunspecBattery extends BaseSunspecDevice {
|
|
|
1162
1286
|
if (this.dataBusListenerId) {
|
|
1163
1287
|
return;
|
|
1164
1288
|
}
|
|
1289
|
+
// Fire and forget — calibration service hydration must not block listening setup.
|
|
1290
|
+
void this.initCalibrationService('battery');
|
|
1165
1291
|
this.dataBus = this.energyApp.useDataBus();
|
|
1166
1292
|
this.dataBusListenerId = this.dataBus.listenForMessages([
|
|
1167
1293
|
EnyoDataBusMessageEnum.StartStorageGridChargeV1,
|
|
1168
1294
|
EnyoDataBusMessageEnum.StopStorageGridChargeV1,
|
|
1169
1295
|
EnyoDataBusMessageEnum.SetStorageDischargeLimitV1,
|
|
1170
|
-
EnyoDataBusMessageEnum.SetStorageChargeLimitV1
|
|
1296
|
+
EnyoDataBusMessageEnum.SetStorageChargeLimitV1,
|
|
1297
|
+
EnyoDataBusMessageEnum.StartCalibrationV1,
|
|
1298
|
+
EnyoDataBusMessageEnum.StopCalibrationV1,
|
|
1171
1299
|
], (entry) => this.handleStorageCommand(entry));
|
|
1172
1300
|
console.log(`Battery ${this.applianceId}: started data bus listening (listener ${this.dataBusListenerId})`);
|
|
1173
1301
|
}
|
|
@@ -1201,6 +1329,12 @@ export class SunspecBattery extends BaseSunspecDevice {
|
|
|
1201
1329
|
case EnyoDataBusMessageEnum.SetStorageChargeLimitV1:
|
|
1202
1330
|
await this.handleSetChargeLimit(entry);
|
|
1203
1331
|
break;
|
|
1332
|
+
case EnyoDataBusMessageEnum.StartCalibrationV1:
|
|
1333
|
+
await this.handleStartCalibration(entry);
|
|
1334
|
+
break;
|
|
1335
|
+
case EnyoDataBusMessageEnum.StopCalibrationV1:
|
|
1336
|
+
await this.handleStopCalibration(entry);
|
|
1337
|
+
break;
|
|
1204
1338
|
}
|
|
1205
1339
|
}
|
|
1206
1340
|
catch (error) {
|
|
@@ -1241,6 +1375,7 @@ export class SunspecBattery extends BaseSunspecDevice {
|
|
|
1241
1375
|
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to set storage mode to charge');
|
|
1242
1376
|
return;
|
|
1243
1377
|
}
|
|
1378
|
+
await this.calibrationService?.recordModification(['chaGriSet', 'wChaMax', 'storCtlMod']);
|
|
1244
1379
|
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
1245
1380
|
}
|
|
1246
1381
|
async handleStopGridCharge(msg) {
|
|
@@ -1267,6 +1402,7 @@ export class SunspecBattery extends BaseSunspecDevice {
|
|
|
1267
1402
|
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to set storage mode to auto');
|
|
1268
1403
|
return;
|
|
1269
1404
|
}
|
|
1405
|
+
await this.calibrationService?.recordModification(['chaGriSet', 'storCtlMod']);
|
|
1270
1406
|
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
1271
1407
|
}
|
|
1272
1408
|
async handleSetDischargeLimit(msg) {
|
|
@@ -1291,6 +1427,7 @@ export class SunspecBattery extends BaseSunspecDevice {
|
|
|
1291
1427
|
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to set discharge limit');
|
|
1292
1428
|
return;
|
|
1293
1429
|
}
|
|
1430
|
+
await this.calibrationService?.recordModification(['outWRte']);
|
|
1294
1431
|
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
1295
1432
|
}
|
|
1296
1433
|
async handleSetChargeLimit(msg) {
|
|
@@ -1315,8 +1452,88 @@ export class SunspecBattery extends BaseSunspecDevice {
|
|
|
1315
1452
|
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to set charge limit');
|
|
1316
1453
|
return;
|
|
1317
1454
|
}
|
|
1455
|
+
await this.calibrationService?.recordModification(['inWRte']);
|
|
1318
1456
|
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
1319
1457
|
}
|
|
1458
|
+
async handleStartCalibration(msg) {
|
|
1459
|
+
if (!this.isConnected() || !this.applianceId) {
|
|
1460
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Not connected');
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
if (!this.calibrationService) {
|
|
1464
|
+
await this.initCalibrationService('battery');
|
|
1465
|
+
}
|
|
1466
|
+
if (!this.calibrationService) {
|
|
1467
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Calibration service unavailable');
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
if (this.calibrationService.isCalibrating()) {
|
|
1471
|
+
console.log(`Battery ${this.applianceId}: calibration already active — ack idempotently`);
|
|
1472
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
console.log(`Battery ${this.applianceId}: handling StartCalibrationV1`);
|
|
1476
|
+
const controls = await this.sunspecClient.readBatteryControls(this.unitId);
|
|
1477
|
+
if (!controls) {
|
|
1478
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to read battery controls for snapshot');
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
try {
|
|
1482
|
+
await this.calibrationService.startCalibration({
|
|
1483
|
+
deviceType: 'battery',
|
|
1484
|
+
unitId: this.unitId,
|
|
1485
|
+
batteryControls: controls,
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
catch (error) {
|
|
1489
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, `Failed to persist snapshot: ${error}`);
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
1493
|
+
}
|
|
1494
|
+
async handleStopCalibration(msg) {
|
|
1495
|
+
if (!this.applianceId) {
|
|
1496
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Not initialized');
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
console.log(`Battery ${this.applianceId}: handling StopCalibrationV1`);
|
|
1500
|
+
const snapshot = await this.calibrationService?.stopCalibration();
|
|
1501
|
+
if (!snapshot) {
|
|
1502
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
try {
|
|
1506
|
+
await this.restoreFromCalibrationSnapshot(snapshot, 'stop');
|
|
1507
|
+
}
|
|
1508
|
+
catch (error) {
|
|
1509
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, `Failed to restore registers: ${error}`);
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
1513
|
+
}
|
|
1514
|
+
async restoreFromCalibrationSnapshot(snapshot, reason) {
|
|
1515
|
+
if (snapshot.deviceType !== 'battery' || !snapshot.batteryControls) {
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
const source = snapshot.batteryControls;
|
|
1519
|
+
const partial = {};
|
|
1520
|
+
// Whitelist of battery fields that writeBatteryControls actually writes (Model 124).
|
|
1521
|
+
const WRITABLE_BATTERY_FIELDS = ['storCtlMod', 'chaGriSet', 'wChaMax', 'inWRte', 'outWRte', 'minRsvPct'];
|
|
1522
|
+
for (const field of WRITABLE_BATTERY_FIELDS) {
|
|
1523
|
+
if (snapshot.modifiedFields.includes(field) && source[field] !== undefined) {
|
|
1524
|
+
partial[field] = source[field];
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
if (Object.keys(partial).length === 0) {
|
|
1528
|
+
console.log(`Battery ${this.applianceId}: calibration ${reason} — no modified fields to restore`);
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
console.log(`Battery ${this.applianceId}: calibration ${reason} — restoring [${Object.keys(partial).join(', ')}]`);
|
|
1532
|
+
const ok = await this.sunspecClient.writeBatteryControls(snapshot.unitId, partial);
|
|
1533
|
+
if (!ok) {
|
|
1534
|
+
throw new Error('writeBatteryControls returned false');
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1320
1537
|
}
|
|
1321
1538
|
/**
|
|
1322
1539
|
* Sunspec Meter implementation
|
|
@@ -1391,11 +1608,13 @@ export class SunspecMeter extends BaseSunspecDevice {
|
|
|
1391
1608
|
console.error(`Failed to update meter appliance: ${error}`);
|
|
1392
1609
|
}
|
|
1393
1610
|
}
|
|
1611
|
+
this.startDataBusListening();
|
|
1394
1612
|
}
|
|
1395
1613
|
/**
|
|
1396
1614
|
* Disconnect from the meter and update appliance state
|
|
1397
1615
|
*/
|
|
1398
1616
|
async disconnect() {
|
|
1617
|
+
this.stopDataBusListening();
|
|
1399
1618
|
if (this.applianceId) {
|
|
1400
1619
|
try {
|
|
1401
1620
|
await this.applianceManager.updateApplianceState(this.applianceId, EnyoApplianceConnectionType.Connector, EnyoApplianceStateEnum.Offline);
|
|
@@ -1407,6 +1626,35 @@ export class SunspecMeter extends BaseSunspecDevice {
|
|
|
1407
1626
|
// Close just this meter's unit; other devices on the same network device stay open.
|
|
1408
1627
|
await this.sunspecClient.disconnectUnit(this.unitId);
|
|
1409
1628
|
}
|
|
1629
|
+
/**
|
|
1630
|
+
* Meter does not implement calibration; it only subscribes to Start/StopCalibrationV1 to
|
|
1631
|
+
* answer NotSupported (per the data-bus contract that every command must be acknowledged).
|
|
1632
|
+
*/
|
|
1633
|
+
startDataBusListening() {
|
|
1634
|
+
if (this.dataBusListenerId) {
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
this.dataBus = this.energyApp.useDataBus();
|
|
1638
|
+
this.dataBusListenerId = this.dataBus.listenForMessages([
|
|
1639
|
+
EnyoDataBusMessageEnum.StartCalibrationV1,
|
|
1640
|
+
EnyoDataBusMessageEnum.StopCalibrationV1,
|
|
1641
|
+
], (entry) => this.handleMeterCommand(entry));
|
|
1642
|
+
console.log(`Meter ${this.applianceId}: started data bus listening (listener ${this.dataBusListenerId})`);
|
|
1643
|
+
}
|
|
1644
|
+
stopDataBusListening() {
|
|
1645
|
+
if (this.dataBusListenerId && this.dataBus) {
|
|
1646
|
+
this.dataBus.unsubscribe(this.dataBusListenerId);
|
|
1647
|
+
console.log(`Meter ${this.applianceId}: stopped data bus listening (listener ${this.dataBusListenerId})`);
|
|
1648
|
+
}
|
|
1649
|
+
this.dataBusListenerId = undefined;
|
|
1650
|
+
this.dataBus = undefined;
|
|
1651
|
+
}
|
|
1652
|
+
handleMeterCommand(entry) {
|
|
1653
|
+
if (entry.applianceId !== this.applianceId) {
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
this.sendCommandAcknowledge(entry.id, entry.message, EnyoCommandAcknowledgeAnswerEnum.NotSupported, 'Meter does not support calibration');
|
|
1657
|
+
}
|
|
1410
1658
|
/**
|
|
1411
1659
|
* Update meter data and return data bus messages
|
|
1412
1660
|
*/
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enyo-energy/sunspec-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.71",
|
|
4
4
|
"description": "enyo Energy Sunspec SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"typescript": "^5.8.3"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@enyo-energy/energy-app-sdk": "^0.0.
|
|
40
|
+
"@enyo-energy/energy-app-sdk": "^0.0.137"
|
|
41
41
|
},
|
|
42
42
|
"volta": {
|
|
43
43
|
"node": "22.17.0"
|