@enyo-energy/sunspec-sdk 0.0.31 → 0.0.33
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/cjs/connection-retry-manager.cjs +63 -80
- package/dist/cjs/connection-retry-manager.d.cts +32 -27
- package/dist/cjs/sunspec-devices.cjs +201 -33
- package/dist/cjs/sunspec-devices.d.cts +56 -6
- package/dist/cjs/sunspec-interfaces.cjs +109 -5
- package/dist/cjs/sunspec-interfaces.d.cts +191 -5
- package/dist/cjs/sunspec-modbus-client.cjs +599 -290
- package/dist/cjs/sunspec-modbus-client.d.cts +43 -27
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/connection-retry-manager.d.ts +32 -27
- package/dist/connection-retry-manager.js +63 -80
- package/dist/sunspec-devices.d.ts +56 -6
- package/dist/sunspec-devices.js +201 -33
- package/dist/sunspec-interfaces.d.ts +191 -5
- package/dist/sunspec-interfaces.js +108 -4
- package/dist/sunspec-modbus-client.d.ts +43 -27
- package/dist/sunspec-modbus-client.js +600 -291
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/sunspec-devices.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { SunspecBatteryChargeState, SunspecModelId, SunspecMPPTOperatingState, SunspecStorageMode } from "./sunspec-interfaces.js";
|
|
1
|
+
import { SunspecBatteryChargeState, SunspecModelId, SunspecMPPTOperatingState, SunspecStorageMode, SunspecInverterCapability } from "./sunspec-interfaces.js";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { EnyoApplianceConnectionType, EnyoApplianceStateEnum, EnyoApplianceTopologyFeatureEnum, EnyoApplianceTypeEnum } from "@enyo-energy/energy-app-sdk/dist/types/enyo-appliance.js";
|
|
4
|
+
import { ConnectionRetryManager } from "./connection-retry-manager.js";
|
|
4
5
|
import { EnyoBatteryStateEnum, EnyoCommandAcknowledgeAnswerEnum, EnyoDataBusMessageEnum, EnyoInverterStateEnum, EnyoStringStateEnum } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
|
|
5
6
|
import { EnyoSourceEnum } from "@enyo-energy/energy-app-sdk/dist/types/enyo-source.enum.js";
|
|
6
7
|
import { EnyoMeterApplianceAvailableFeaturesEnum } from "@enyo-energy/energy-app-sdk/dist/types/enyo-meter-appliance.js";
|
|
7
8
|
import { EnyoBatteryFeature, EnyoBatteryStorageMode } from "@enyo-energy/energy-app-sdk/dist/types/enyo-battery-appliance.js";
|
|
9
|
+
// TODO: Remove once added to @enyo-energy/energy-app-sdk EnyoDataBusMessageEnum
|
|
10
|
+
export const ENYO_DATA_BUS_SET_INVERTER_FEED_IN_LIMIT_V1 = 'SetInverterFeedInLimitV1';
|
|
8
11
|
/**
|
|
9
12
|
* Extract battery discharge power from MPPT data.
|
|
10
13
|
* Returns the discharge power in Watts (positive value), or 0 if no discharge.
|
|
@@ -32,7 +35,10 @@ export class BaseSunspecDevice {
|
|
|
32
35
|
baseAddress;
|
|
33
36
|
applianceId;
|
|
34
37
|
lastUpdateTime = 0;
|
|
35
|
-
|
|
38
|
+
dataBusListenerId;
|
|
39
|
+
dataBus;
|
|
40
|
+
retryManager;
|
|
41
|
+
constructor(energyApp, name, networkDevice, sunspecClient, applianceManager, unitId = 1, port = 502, baseAddress = 40000, retryConfig) {
|
|
36
42
|
this.energyApp = energyApp;
|
|
37
43
|
this.name = name;
|
|
38
44
|
this.networkDevice = networkDevice;
|
|
@@ -41,6 +47,7 @@ export class BaseSunspecDevice {
|
|
|
41
47
|
this.unitId = unitId;
|
|
42
48
|
this.port = port;
|
|
43
49
|
this.baseAddress = baseAddress;
|
|
50
|
+
this.retryManager = new ConnectionRetryManager(retryConfig);
|
|
44
51
|
}
|
|
45
52
|
/**
|
|
46
53
|
* Check if the device is connected
|
|
@@ -62,11 +69,86 @@ export class BaseSunspecDevice {
|
|
|
62
69
|
await this.sunspecClient.discoverModels(this.baseAddress);
|
|
63
70
|
}
|
|
64
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Attempt a reconnection if the tiered retry schedule allows it.
|
|
74
|
+
* Called from readData() when the device is disconnected.
|
|
75
|
+
* Returns true if reconnection succeeded.
|
|
76
|
+
*/
|
|
77
|
+
async tryReconnect() {
|
|
78
|
+
this.retryManager.markDisconnected();
|
|
79
|
+
if (!this.retryManager.shouldAttemptNow()) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
this.retryManager.recordAttempt();
|
|
83
|
+
const phase = this.retryManager.getCurrentPhase();
|
|
84
|
+
const attempt = this.retryManager.getAttemptCount();
|
|
85
|
+
const elapsed = Math.round(this.retryManager.getElapsedMs() / 1000);
|
|
86
|
+
console.log(`${this.constructor.name} ${this.applianceId}: Reconnect attempt #${attempt} ` +
|
|
87
|
+
`(phase: ${phase.intervalMs / 1000}s interval, elapsed: ${elapsed}s)`);
|
|
88
|
+
try {
|
|
89
|
+
const success = await this.sunspecClient.reconnect();
|
|
90
|
+
if (success) {
|
|
91
|
+
// Re-discover models after reconnect
|
|
92
|
+
await this.sunspecClient.discoverModels(this.baseAddress);
|
|
93
|
+
this.retryManager.reset();
|
|
94
|
+
// Update appliance state to Connected
|
|
95
|
+
if (this.applianceId) {
|
|
96
|
+
await this.applianceManager.updateApplianceState(this.applianceId, EnyoApplianceConnectionType.Connector, EnyoApplianceStateEnum.Connected);
|
|
97
|
+
}
|
|
98
|
+
console.log(`${this.constructor.name} ${this.applianceId}: Reconnection successful after ${attempt} attempt(s)`);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.error(`${this.constructor.name} ${this.applianceId}: Reconnect attempt #${attempt} failed: ${error}`);
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Mark the device as offline: update appliance state and start tracking disconnection.
|
|
109
|
+
*/
|
|
110
|
+
async markOffline() {
|
|
111
|
+
this.retryManager.markDisconnected();
|
|
112
|
+
if (this.applianceId) {
|
|
113
|
+
try {
|
|
114
|
+
await this.applianceManager.updateApplianceState(this.applianceId, EnyoApplianceConnectionType.Connector, EnyoApplianceStateEnum.Offline);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.error(`${this.constructor.name} ${this.applianceId}: Failed to mark appliance offline: ${error}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
sendCommandAcknowledge(messageId, acknowledgeMessage, answer, rejectionReason) {
|
|
122
|
+
if (!this.dataBus || !this.applianceId) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const ackMessage = {
|
|
126
|
+
id: randomUUID(),
|
|
127
|
+
message: EnyoDataBusMessageEnum.CommandAcknowledgeV1,
|
|
128
|
+
type: 'answer',
|
|
129
|
+
source: EnyoSourceEnum.Device,
|
|
130
|
+
applianceId: this.applianceId,
|
|
131
|
+
timestampIso: new Date().toISOString(),
|
|
132
|
+
data: {
|
|
133
|
+
messageId,
|
|
134
|
+
acknowledgeMessage: acknowledgeMessage,
|
|
135
|
+
answer,
|
|
136
|
+
rejectionReason
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
console.log(`${this.constructor.name} ${this.applianceId}: sending ${answer} for ${acknowledgeMessage} (messageId=${messageId}${rejectionReason ? `, reason=${rejectionReason}` : ''})`);
|
|
140
|
+
this.dataBus.sendMessage([ackMessage]);
|
|
141
|
+
}
|
|
65
142
|
}
|
|
66
143
|
/**
|
|
67
144
|
* Sunspec Inverter implementation using dynamic model discovery
|
|
68
145
|
*/
|
|
69
146
|
export class SunspecInverter extends BaseSunspecDevice {
|
|
147
|
+
capabilities;
|
|
148
|
+
constructor(energyApp, name, networkDevice, sunspecClient, applianceManager, unitId = 1, port = 502, baseAddress = 40000, capabilities = [], retryConfig) {
|
|
149
|
+
super(energyApp, name, networkDevice, sunspecClient, applianceManager, unitId, port, baseAddress, retryConfig);
|
|
150
|
+
this.capabilities = capabilities;
|
|
151
|
+
}
|
|
70
152
|
async connect() {
|
|
71
153
|
// Ensure Sunspec client is connected
|
|
72
154
|
if (!this.sunspecClient.isConnected()) {
|
|
@@ -107,8 +189,10 @@ export class SunspecInverter extends BaseSunspecDevice {
|
|
|
107
189
|
if (mpptModel) {
|
|
108
190
|
console.log(`MPPT model found for inverter ${this.networkDevice.hostname}`);
|
|
109
191
|
}
|
|
192
|
+
this.startDataBusListening();
|
|
110
193
|
}
|
|
111
194
|
async disconnect() {
|
|
195
|
+
this.stopDataBusListening();
|
|
112
196
|
if (this.applianceId) {
|
|
113
197
|
await this.applianceManager.updateApplianceState(this.applianceId, EnyoApplianceConnectionType.Connector, EnyoApplianceStateEnum.Offline);
|
|
114
198
|
}
|
|
@@ -119,7 +203,9 @@ export class SunspecInverter extends BaseSunspecDevice {
|
|
|
119
203
|
}
|
|
120
204
|
async readData(clockId, resolution) {
|
|
121
205
|
if (!this.isConnected()) {
|
|
122
|
-
|
|
206
|
+
await this.tryReconnect();
|
|
207
|
+
if (!this.isConnected())
|
|
208
|
+
return [];
|
|
123
209
|
}
|
|
124
210
|
const messages = [];
|
|
125
211
|
const timestamp = new Date();
|
|
@@ -169,6 +255,7 @@ export class SunspecInverter extends BaseSunspecDevice {
|
|
|
169
255
|
}
|
|
170
256
|
catch (error) {
|
|
171
257
|
console.error(`Error updating inverter data: ${error}`);
|
|
258
|
+
await this.markOffline();
|
|
172
259
|
}
|
|
173
260
|
return messages;
|
|
174
261
|
}
|
|
@@ -239,13 +326,72 @@ export class SunspecInverter extends BaseSunspecDevice {
|
|
|
239
326
|
mapDcStringToApplianceMetadata(mpptDataList) {
|
|
240
327
|
return mpptDataList.map(s => ({ index: s.index, name: s.name }));
|
|
241
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Start listening for inverter commands on the data bus.
|
|
331
|
+
* Idempotent — does nothing if already listening.
|
|
332
|
+
*/
|
|
333
|
+
startDataBusListening() {
|
|
334
|
+
if (this.dataBusListenerId) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
this.dataBus = this.energyApp.useDataBus();
|
|
338
|
+
this.dataBusListenerId = this.dataBus.listenForMessages([ENYO_DATA_BUS_SET_INVERTER_FEED_IN_LIMIT_V1], (entry) => this.handleInverterCommand(entry));
|
|
339
|
+
console.log(`Inverter ${this.applianceId}: started data bus listening (listener ${this.dataBusListenerId})`);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Stop listening for inverter commands on the data bus.
|
|
343
|
+
*/
|
|
344
|
+
stopDataBusListening() {
|
|
345
|
+
if (this.dataBusListenerId && this.dataBus) {
|
|
346
|
+
this.dataBus.unsubscribe(this.dataBusListenerId);
|
|
347
|
+
console.log(`Inverter ${this.applianceId}: stopped data bus listening (listener ${this.dataBusListenerId})`);
|
|
348
|
+
}
|
|
349
|
+
this.dataBusListenerId = undefined;
|
|
350
|
+
this.dataBus = undefined;
|
|
351
|
+
}
|
|
352
|
+
handleInverterCommand(entry) {
|
|
353
|
+
if (entry.applianceId !== this.applianceId) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
void (async () => {
|
|
357
|
+
try {
|
|
358
|
+
if (entry.message === ENYO_DATA_BUS_SET_INVERTER_FEED_IN_LIMIT_V1) {
|
|
359
|
+
await this.handleSetFeedInLimit(entry);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
console.error(`Inverter ${this.applianceId}: error handling ${entry.message}:`, error);
|
|
364
|
+
}
|
|
365
|
+
})();
|
|
366
|
+
}
|
|
367
|
+
async handleSetFeedInLimit(msg) {
|
|
368
|
+
// Check capability
|
|
369
|
+
if (!this.capabilities.includes(SunspecInverterCapability.FeedInLimit)) {
|
|
370
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.NotSupported, 'FeedInLimit capability not enabled');
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (!this.isConnected() || !this.applianceId) {
|
|
374
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Not connected');
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
console.log(`Inverter ${this.applianceId}: handling SetInverterFeedInLimitV1 (feedInLimitW=${msg.data.feedInLimitW})`);
|
|
378
|
+
const success = await this.sunspecClient.setFeedInLimit(msg.data.feedInLimitW);
|
|
379
|
+
if (!success) {
|
|
380
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to set feed-in limit');
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
384
|
+
}
|
|
242
385
|
}
|
|
243
386
|
/**
|
|
244
387
|
* Sunspec Battery implementation
|
|
245
388
|
*/
|
|
246
389
|
export class SunspecBattery extends BaseSunspecDevice {
|
|
247
|
-
|
|
248
|
-
|
|
390
|
+
capabilities;
|
|
391
|
+
constructor(energyApp, name, networkDevice, sunspecClient, applianceManager, unitId = 1, port = 502, baseAddress = 40000, capabilities = [], retryConfig) {
|
|
392
|
+
super(energyApp, name, networkDevice, sunspecClient, applianceManager, unitId, port, baseAddress, retryConfig);
|
|
393
|
+
this.capabilities = capabilities;
|
|
394
|
+
}
|
|
249
395
|
/**
|
|
250
396
|
* Connect to the battery and create/update the appliance
|
|
251
397
|
*/
|
|
@@ -332,7 +478,9 @@ export class SunspecBattery extends BaseSunspecDevice {
|
|
|
332
478
|
*/
|
|
333
479
|
async readData(clockId, resolution) {
|
|
334
480
|
if (!this.isConnected()) {
|
|
335
|
-
|
|
481
|
+
await this.tryReconnect();
|
|
482
|
+
if (!this.isConnected())
|
|
483
|
+
return [];
|
|
336
484
|
}
|
|
337
485
|
const messages = [];
|
|
338
486
|
const timestamp = new Date();
|
|
@@ -343,6 +491,16 @@ export class SunspecBattery extends BaseSunspecDevice {
|
|
|
343
491
|
const mpptBatteryPowerW = this.extractBatteryPowerFromMPPT(mpptDataList);
|
|
344
492
|
if (batteryData) {
|
|
345
493
|
const advancedBatteryModel = this.sunspecClient.findModel(801);
|
|
494
|
+
const batteryBaseModel = this.sunspecClient.findModel(SunspecModelId.BatteryBase);
|
|
495
|
+
// Determine battery power: prefer model 802 w field, then MPPT extraction, then undefined
|
|
496
|
+
let batteryPowerW;
|
|
497
|
+
if (batteryBaseModel && (batteryData.chargePower !== undefined || batteryData.dischargePower !== undefined)) {
|
|
498
|
+
// Model 802 provides power directly: positive = charge, negative = discharge
|
|
499
|
+
batteryPowerW = (batteryData.chargePower || 0) - (batteryData.dischargePower || 0);
|
|
500
|
+
}
|
|
501
|
+
else if (!advancedBatteryModel) {
|
|
502
|
+
batteryPowerW = mpptBatteryPowerW;
|
|
503
|
+
}
|
|
346
504
|
const batteryMessage = {
|
|
347
505
|
id: randomUUID(),
|
|
348
506
|
message: EnyoDataBusMessageEnum.BatteryValuesUpdateV1,
|
|
@@ -354,7 +512,7 @@ export class SunspecBattery extends BaseSunspecDevice {
|
|
|
354
512
|
resolution,
|
|
355
513
|
data: {
|
|
356
514
|
batterySoC: batteryData.soc || batteryData.chaState || 0,
|
|
357
|
-
batteryPowerW
|
|
515
|
+
batteryPowerW,
|
|
358
516
|
state: this.mapToEnyoBatteryState(batteryData.chaSt),
|
|
359
517
|
}
|
|
360
518
|
};
|
|
@@ -380,6 +538,7 @@ export class SunspecBattery extends BaseSunspecDevice {
|
|
|
380
538
|
}
|
|
381
539
|
catch (error) {
|
|
382
540
|
console.error(`Error updating battery data: ${error}`);
|
|
541
|
+
await this.markOffline();
|
|
383
542
|
}
|
|
384
543
|
return messages;
|
|
385
544
|
}
|
|
@@ -494,6 +653,21 @@ export class SunspecBattery extends BaseSunspecDevice {
|
|
|
494
653
|
}
|
|
495
654
|
return this.sunspecClient.readBatteryControls();
|
|
496
655
|
}
|
|
656
|
+
/**
|
|
657
|
+
* Read full battery base data from Model 802
|
|
658
|
+
*
|
|
659
|
+
* Returns the complete Model 802 data structure with all fields,
|
|
660
|
+
* including nameplate, SoC/health, status, events, voltage, current, and power.
|
|
661
|
+
*
|
|
662
|
+
* @returns Promise<SunspecBatteryBaseData | null> - Full model 802 data or null if not available
|
|
663
|
+
*/
|
|
664
|
+
async readBatteryBaseData() {
|
|
665
|
+
if (!this.isConnected()) {
|
|
666
|
+
console.error('Battery not connected');
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
return this.sunspecClient.readBatteryBaseData();
|
|
670
|
+
}
|
|
497
671
|
/**
|
|
498
672
|
* Write custom battery control settings
|
|
499
673
|
*
|
|
@@ -681,44 +855,35 @@ export class SunspecBattery extends BaseSunspecDevice {
|
|
|
681
855
|
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.NotSupported);
|
|
682
856
|
return;
|
|
683
857
|
}
|
|
684
|
-
console.log(`Battery ${this.applianceId}: handling SetStorageDischargeLimitV1 (
|
|
685
|
-
// Read current state for
|
|
858
|
+
console.log(`Battery ${this.applianceId}: handling SetStorageDischargeLimitV1 (dischargeLimitW=${msg.data.dischargeLimitW})`);
|
|
859
|
+
// Read current state to get wChaMax for percentage conversion
|
|
686
860
|
const controls = await this.getBatteryControls();
|
|
687
|
-
console.log(`Battery ${this.applianceId}: current state - outWRte=${controls?.outWRte}`);
|
|
861
|
+
console.log(`Battery ${this.applianceId}: current state - outWRte=${controls?.outWRte}, wChaMax=${controls?.wChaMax}`);
|
|
862
|
+
if (!controls?.wChaMax) {
|
|
863
|
+
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to read wChaMax for discharge limit conversion');
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
// Convert watts to percentage of WDisChaMax (using wChaMax), clamped to 0-100%
|
|
867
|
+
const dischargeLimitPercent = Math.min(100, Math.max(0, (msg.data.dischargeLimitW / controls.wChaMax) * 100));
|
|
868
|
+
console.log(`Battery ${this.applianceId}: calculated discharge limit: ${dischargeLimitPercent.toFixed(1)}% (${msg.data.dischargeLimitW}W / ${controls.wChaMax}W)`);
|
|
688
869
|
// Set discharge limit (Register 12: outWRte)
|
|
689
|
-
const success = await this.writeBatteryControls({ outWRte:
|
|
870
|
+
const success = await this.writeBatteryControls({ outWRte: dischargeLimitPercent });
|
|
690
871
|
if (!success) {
|
|
691
872
|
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to set discharge limit');
|
|
692
873
|
return;
|
|
693
874
|
}
|
|
694
875
|
this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
|
|
695
876
|
}
|
|
696
|
-
sendCommandAcknowledge(messageId, acknowledgeMessage, answer, rejectionReason) {
|
|
697
|
-
if (!this.dataBus || !this.applianceId) {
|
|
698
|
-
return;
|
|
699
|
-
}
|
|
700
|
-
const ackMessage = {
|
|
701
|
-
id: randomUUID(),
|
|
702
|
-
message: EnyoDataBusMessageEnum.CommandAcknowledgeV1,
|
|
703
|
-
type: 'answer',
|
|
704
|
-
source: EnyoSourceEnum.Device,
|
|
705
|
-
applianceId: this.applianceId,
|
|
706
|
-
timestampIso: new Date().toISOString(),
|
|
707
|
-
data: {
|
|
708
|
-
messageId,
|
|
709
|
-
acknowledgeMessage,
|
|
710
|
-
answer,
|
|
711
|
-
rejectionReason
|
|
712
|
-
}
|
|
713
|
-
};
|
|
714
|
-
console.log(`Battery ${this.applianceId}: sending ${answer} for ${acknowledgeMessage} (messageId=${messageId}${rejectionReason ? `, reason=${rejectionReason}` : ''})`);
|
|
715
|
-
this.dataBus.sendMessage([ackMessage]);
|
|
716
|
-
}
|
|
717
877
|
}
|
|
718
878
|
/**
|
|
719
879
|
* Sunspec Meter implementation
|
|
720
880
|
*/
|
|
721
881
|
export class SunspecMeter extends BaseSunspecDevice {
|
|
882
|
+
capabilities;
|
|
883
|
+
constructor(energyApp, name, networkDevice, sunspecClient, applianceManager, unitId = 1, port = 502, baseAddress = 40000, capabilities = [], retryConfig) {
|
|
884
|
+
super(energyApp, name, networkDevice, sunspecClient, applianceManager, unitId, port, baseAddress, retryConfig);
|
|
885
|
+
this.capabilities = capabilities;
|
|
886
|
+
}
|
|
722
887
|
/**
|
|
723
888
|
* Connect to the meter and create/update the appliance
|
|
724
889
|
*/
|
|
@@ -775,7 +940,9 @@ export class SunspecMeter extends BaseSunspecDevice {
|
|
|
775
940
|
*/
|
|
776
941
|
async readData(clockId, resolution) {
|
|
777
942
|
if (!this.isConnected()) {
|
|
778
|
-
|
|
943
|
+
await this.tryReconnect();
|
|
944
|
+
if (!this.isConnected())
|
|
945
|
+
return [];
|
|
779
946
|
}
|
|
780
947
|
const messages = [];
|
|
781
948
|
const timestamp = new Date();
|
|
@@ -806,6 +973,7 @@ export class SunspecMeter extends BaseSunspecDevice {
|
|
|
806
973
|
}
|
|
807
974
|
catch (error) {
|
|
808
975
|
console.error(`Error updating meter data: ${error}`);
|
|
976
|
+
await this.markOffline();
|
|
809
977
|
}
|
|
810
978
|
return messages;
|
|
811
979
|
}
|
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
* SunSpec block interfaces with block numbers
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* A single phase in the tiered retry schedule
|
|
6
|
+
*/
|
|
7
|
+
export interface IRetryPhase {
|
|
8
|
+
intervalMs: number;
|
|
9
|
+
durationMs: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Configuration for connection retry with tiered schedule
|
|
6
13
|
*/
|
|
7
14
|
export interface IRetryConfig {
|
|
8
|
-
|
|
9
|
-
maxDelayMs: number;
|
|
10
|
-
backoffFactor: number;
|
|
11
|
-
maxAttempts: number;
|
|
15
|
+
phases: IRetryPhase[];
|
|
12
16
|
}
|
|
13
17
|
export declare const DEFAULT_RETRY_CONFIG: IRetryConfig;
|
|
14
18
|
/**
|
|
@@ -178,6 +182,83 @@ export declare enum SunspecBatteryChargeState {
|
|
|
178
182
|
HOLDING = 6,
|
|
179
183
|
TESTING = 7
|
|
180
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Battery Control Mode values for Model 802
|
|
187
|
+
* Offset 17: LocRemCtl - Local/Remote control mode
|
|
188
|
+
*/
|
|
189
|
+
export declare enum SunspecBatteryControlMode {
|
|
190
|
+
REMOTE = 0,
|
|
191
|
+
LOCAL = 1
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Battery Type values for Model 802
|
|
195
|
+
* Offset 21: Typ - Battery type
|
|
196
|
+
*/
|
|
197
|
+
export declare enum SunspecBatteryType {
|
|
198
|
+
NOT_APPLICABLE_UNKNOWN = 0,
|
|
199
|
+
LEAD_ACID = 1,
|
|
200
|
+
NICKEL_METAL_HYDRIDE = 2,
|
|
201
|
+
NICKEL_CADMIUM = 3,
|
|
202
|
+
LITHIUM_ION = 4,
|
|
203
|
+
CARBON_ZINC = 5,
|
|
204
|
+
ZINC_CHLORIDE = 6,
|
|
205
|
+
ALKALINE = 7,
|
|
206
|
+
RECHARGEABLE_ALKALINE = 8,
|
|
207
|
+
SODIUM_SULFUR = 9,
|
|
208
|
+
FLOW = 10,
|
|
209
|
+
SUPER_CAPACITOR = 11,
|
|
210
|
+
OTHER = 99
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Battery Bank State values for Model 802
|
|
214
|
+
* Offset 22: State - Battery bank state
|
|
215
|
+
*/
|
|
216
|
+
export declare enum SunspecBatteryBankState {
|
|
217
|
+
DISCONNECTED = 1,
|
|
218
|
+
INITIALIZING = 2,
|
|
219
|
+
CONNECTED = 3,
|
|
220
|
+
STANDBY = 4,
|
|
221
|
+
SOC_PROTECTION = 5,
|
|
222
|
+
SUSPENDING = 6,
|
|
223
|
+
FAULT = 99
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Battery Event 1 bit positions for Model 802
|
|
227
|
+
* Offset 26-27: Evt1 - Battery event bitfield
|
|
228
|
+
*/
|
|
229
|
+
export declare enum SunspecBatteryEvent1 {
|
|
230
|
+
COMMUNICATION_ERROR = 0,
|
|
231
|
+
OVER_TEMP_ALARM = 1,
|
|
232
|
+
OVER_TEMP_WARNING = 2,
|
|
233
|
+
UNDER_TEMP_ALARM = 3,
|
|
234
|
+
UNDER_TEMP_WARNING = 4,
|
|
235
|
+
OVER_CHARGE_CURRENT_ALARM = 5,
|
|
236
|
+
OVER_CHARGE_CURRENT_WARNING = 6,
|
|
237
|
+
OVER_DISCHARGE_CURRENT_ALARM = 7,
|
|
238
|
+
OVER_DISCHARGE_CURRENT_WARNING = 8,
|
|
239
|
+
OVER_VOLT_ALARM = 9,
|
|
240
|
+
OVER_VOLT_WARNING = 10,
|
|
241
|
+
UNDER_VOLT_ALARM = 11,
|
|
242
|
+
UNDER_VOLT_WARNING = 12,
|
|
243
|
+
UNDER_SOC_MIN_ALARM = 13,
|
|
244
|
+
UNDER_SOC_MIN_WARNING = 14,
|
|
245
|
+
OVER_SOC_MAX_ALARM = 15,
|
|
246
|
+
OVER_SOC_MAX_WARNING = 16,
|
|
247
|
+
VOLTAGE_IMBALANCE_WARNING = 17,
|
|
248
|
+
TEMPERATURE_IMBALANCE_ALARM = 18,
|
|
249
|
+
TEMPERATURE_IMBALANCE_WARNING = 19,
|
|
250
|
+
CONTACTOR_ERROR = 20,
|
|
251
|
+
FAN_ERROR = 21,
|
|
252
|
+
FUSE_ERROR = 22,
|
|
253
|
+
GROUND_FAULT = 23,
|
|
254
|
+
OPEN_DOOR_ERROR = 24,
|
|
255
|
+
CURRENT_IMBALANCE_WARNING = 25,
|
|
256
|
+
OTHER_ALARM = 26,
|
|
257
|
+
OTHER_WARNING = 27,
|
|
258
|
+
RESERVED_1 = 28,
|
|
259
|
+
CONFIGURATION_ALARM = 29,
|
|
260
|
+
CONFIGURATION_WARNING = 30
|
|
261
|
+
}
|
|
181
262
|
/**
|
|
182
263
|
* Storage Control Mode bitfield values for Model 124
|
|
183
264
|
*
|
|
@@ -247,6 +328,102 @@ export interface SunspecBatteryData extends SunspecBlock {
|
|
|
247
328
|
temperature?: number;
|
|
248
329
|
status?: number;
|
|
249
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* Battery Base data structure based on Model 802 (Battery Base Model)
|
|
333
|
+
*
|
|
334
|
+
* SunSpec Model 802 Register Map (offsets relative to model start):
|
|
335
|
+
* - 0-1: ID and Length
|
|
336
|
+
* - 2: AHRtg - Nameplate charge capacity (AH) (uint16)
|
|
337
|
+
* - 3: WHRtg - Nameplate energy capacity (WH) (uint16)
|
|
338
|
+
* - 4: WChaRteMax - Maximum rate of charge (W) (uint16)
|
|
339
|
+
* - 5: WDisChaRteMax - Maximum rate of discharge (W) (uint16)
|
|
340
|
+
* - 6: DisChaRte - Self discharge rate (%) (uint16)
|
|
341
|
+
* - 7: SoCMax - Maximum state of charge (%) (uint16)
|
|
342
|
+
* - 8: SoCMin - Minimum state of charge (%) (uint16)
|
|
343
|
+
* - 9: SoCRsvMax - Maximum reserve SOC (%) (uint16)
|
|
344
|
+
* - 10: SoCRsvMin - Minimum reserve SOC (%) (uint16)
|
|
345
|
+
* - 11: SoC - State of charge (%) (uint16)
|
|
346
|
+
* - 12: DoD - Depth of discharge (%) (uint16)
|
|
347
|
+
* - 13: SoH - State of health (%) (uint16)
|
|
348
|
+
* - 14-15: NCyc - Cycle count (uint32)
|
|
349
|
+
* - 16: ChaSt - Charge status (enum16)
|
|
350
|
+
* - 17: LocRemCtl - Local/Remote control (enum16)
|
|
351
|
+
* - 18: Hb - Heartbeat (uint16)
|
|
352
|
+
* - 19: CtrlHb - Controller heartbeat (uint16)
|
|
353
|
+
* - 20: AlmRst - Alarm reset (uint16)
|
|
354
|
+
* - 21: Typ - Battery type (enum16)
|
|
355
|
+
* - 22: State - Battery bank state (enum16)
|
|
356
|
+
* - 23: StateVnd - Vendor-specific state (enum16)
|
|
357
|
+
* - 24-25: WarrDt - Warranty date (uint32)
|
|
358
|
+
* - 26-27: Evt1 - Event bitfield 1 (bitfield32)
|
|
359
|
+
* - 28-29: Evt2 - Event bitfield 2 (bitfield32)
|
|
360
|
+
* - 30-31: EvtVnd1 - Vendor event bitfield 1 (bitfield32)
|
|
361
|
+
* - 32-33: EvtVnd2 - Vendor event bitfield 2 (bitfield32)
|
|
362
|
+
* - 34: V - Battery voltage (V) (uint16)
|
|
363
|
+
* - 35: VMax - Maximum battery voltage (V) (uint16)
|
|
364
|
+
* - 36: VMin - Minimum battery voltage (V) (uint16)
|
|
365
|
+
* - 37: CellVMax - Maximum cell voltage (V) (uint16)
|
|
366
|
+
* - 38: CellVMaxStr - String containing max cell voltage (uint16)
|
|
367
|
+
* - 39: CellVMaxMod - Module containing max cell voltage (uint16)
|
|
368
|
+
* - 40: CellVMin - Minimum cell voltage (V) (uint16)
|
|
369
|
+
* - 41: CellVMinStr - String containing min cell voltage (uint16)
|
|
370
|
+
* - 42: CellVMinMod - Module containing min cell voltage (uint16)
|
|
371
|
+
* - 43: CellVAvg - Average cell voltage (V) (uint16)
|
|
372
|
+
* - 44: A - Battery current (A) (int16)
|
|
373
|
+
* - 45: AChaMax - Maximum charge current (A) (uint16)
|
|
374
|
+
* - 46: ADisChaMax - Maximum discharge current (A) (uint16)
|
|
375
|
+
* - 47: W - Battery power (W) (int16)
|
|
376
|
+
* - 48: ReqInvState - Requested inverter state (enum16)
|
|
377
|
+
* - 49: ReqW - Requested power (W) (int16)
|
|
378
|
+
* - 50: SetOp - Set operation (enum16)
|
|
379
|
+
* - 51: SetInvState - Set inverter state (enum16)
|
|
380
|
+
* Scale factors at offsets 52-63
|
|
381
|
+
*/
|
|
382
|
+
export interface SunspecBatteryBaseData extends SunspecBlock {
|
|
383
|
+
blockNumber: 802;
|
|
384
|
+
ahRtg?: number;
|
|
385
|
+
whRtg?: number;
|
|
386
|
+
wChaRteMax?: number;
|
|
387
|
+
wDisChaRteMax?: number;
|
|
388
|
+
disChaRte?: number;
|
|
389
|
+
soCMax?: number;
|
|
390
|
+
soCMin?: number;
|
|
391
|
+
soCRsvMax?: number;
|
|
392
|
+
soCRsvMin?: number;
|
|
393
|
+
soC?: number;
|
|
394
|
+
doD?: number;
|
|
395
|
+
soH?: number;
|
|
396
|
+
nCyc?: number;
|
|
397
|
+
chaSt?: number;
|
|
398
|
+
chaStName?: string;
|
|
399
|
+
locRemCtl?: number;
|
|
400
|
+
typ?: number;
|
|
401
|
+
typName?: string;
|
|
402
|
+
state?: number;
|
|
403
|
+
stateName?: string;
|
|
404
|
+
evt1?: number;
|
|
405
|
+
evt2?: number;
|
|
406
|
+
evtVnd1?: number;
|
|
407
|
+
evtVnd2?: number;
|
|
408
|
+
v?: number;
|
|
409
|
+
vMax?: number;
|
|
410
|
+
vMin?: number;
|
|
411
|
+
cellVMax?: number;
|
|
412
|
+
cellVMaxStr?: number;
|
|
413
|
+
cellVMaxMod?: number;
|
|
414
|
+
cellVMin?: number;
|
|
415
|
+
cellVMinStr?: number;
|
|
416
|
+
cellVMinMod?: number;
|
|
417
|
+
cellVAvg?: number;
|
|
418
|
+
a?: number;
|
|
419
|
+
aChaMax?: number;
|
|
420
|
+
aDisChaMax?: number;
|
|
421
|
+
w?: number;
|
|
422
|
+
reqInvState?: number;
|
|
423
|
+
reqW?: number;
|
|
424
|
+
setOp?: number;
|
|
425
|
+
setInvState?: number;
|
|
426
|
+
}
|
|
250
427
|
/**
|
|
251
428
|
* Meter data structure
|
|
252
429
|
*/
|
|
@@ -378,6 +555,15 @@ export declare enum SunspecStorageMode {
|
|
|
378
555
|
* 2. Set chaGriSet = 1 to allow grid charging
|
|
379
556
|
* 3. Set wChaMax to the desired charging power in Watts
|
|
380
557
|
*/
|
|
558
|
+
export declare enum SunspecInverterCapability {
|
|
559
|
+
FeedInLimit = "feed-in-limit"
|
|
560
|
+
}
|
|
561
|
+
export declare enum SunspecBatteryCapability {
|
|
562
|
+
GridCharging = "grid-charging",
|
|
563
|
+
DischargeLimit = "discharge-limit"
|
|
564
|
+
}
|
|
565
|
+
export declare enum SunspecMeterCapability {
|
|
566
|
+
}
|
|
381
567
|
export interface SunspecBatteryControls {
|
|
382
568
|
storCtlMod?: number;
|
|
383
569
|
chaGriSet?: number;
|