@enyo-energy/sunspec-sdk 0.0.80 → 0.0.82
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/sunspec-devices.cjs +56 -0
- package/dist/cjs/sunspec-devices.d.cts +23 -0
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/sunspec-devices.d.ts +23 -0
- package/dist/sunspec-devices.js +56 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
@@ -107,6 +107,15 @@ class BaseSunspecDevice {
|
|
|
107
107
|
dataBus;
|
|
108
108
|
retryManager;
|
|
109
109
|
consecutiveReconnectFailures = 0;
|
|
110
|
+
/**
|
|
111
|
+
* True once {@link markOffline} has set the appliance state to Offline and
|
|
112
|
+
* it has not yet been re-asserted Connected. Used by
|
|
113
|
+
* {@link reassertConnectedIfRecovered} to heal the shared-socket case: when
|
|
114
|
+
* a sibling device on the same unit wins the reconnect race, this device's
|
|
115
|
+
* readData() sees isConnected() === true and skips tryReconnect(), so it
|
|
116
|
+
* would otherwise stay Offline forever despite reading data fine.
|
|
117
|
+
*/
|
|
118
|
+
appliedOffline = false;
|
|
110
119
|
/**
|
|
111
120
|
* Prefix used when persisting calibration snapshots via the library's
|
|
112
121
|
* {@link SnapshotService}. Kept identical to the key the SDK used before
|
|
@@ -181,6 +190,7 @@ class BaseSunspecDevice {
|
|
|
181
190
|
if (this.applianceId) {
|
|
182
191
|
await this.applianceManager.updateApplianceState(this.applianceId, enyo_appliance_js_1.EnyoApplianceConnectionType.Connector, enyo_appliance_js_1.EnyoApplianceStateEnum.Connected);
|
|
183
192
|
}
|
|
193
|
+
this.appliedOffline = false;
|
|
184
194
|
await this.onConnectionRestored();
|
|
185
195
|
const postStats = this.sunspecClient.getConnectionStats();
|
|
186
196
|
console.log(`${this.constructor.name} ${this.applianceId}: Reconnection successful after ${attempt} attempt(s) (opens=${postStats.opens}, closes=${postStats.closes}, openUnits=${postStats.openUnits})`);
|
|
@@ -249,12 +259,46 @@ class BaseSunspecDevice {
|
|
|
249
259
|
if (this.applianceId) {
|
|
250
260
|
try {
|
|
251
261
|
await this.applianceManager.updateApplianceState(this.applianceId, enyo_appliance_js_1.EnyoApplianceConnectionType.Connector, enyo_appliance_js_1.EnyoApplianceStateEnum.Offline);
|
|
262
|
+
this.appliedOffline = true;
|
|
252
263
|
}
|
|
253
264
|
catch (error) {
|
|
254
265
|
console.error(`${this.constructor.name} ${this.applianceId}: Failed to mark appliance offline: ${error}`);
|
|
255
266
|
}
|
|
256
267
|
}
|
|
257
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Heal the shared-socket recovery gap. When several devices share one
|
|
271
|
+
* SunspecModbusClient on the same unit (e.g. a Fronius GEN24 inverter and
|
|
272
|
+
* its battery), only the device that detects !isConnected() first runs
|
|
273
|
+
* tryReconnect() — and that is the only path that flips the appliance back
|
|
274
|
+
* to Connected. Siblings then see isConnected() === true on their next
|
|
275
|
+
* readData() and skip tryReconnect() entirely, so without this they would
|
|
276
|
+
* stay Offline forever despite reading data fine.
|
|
277
|
+
*
|
|
278
|
+
* Call this on the success path of readData() (after the
|
|
279
|
+
* markOfflineIfUnhealthy() guard has confirmed the unit is healthy). It is
|
|
280
|
+
* a cheap no-op unless this device was previously marked offline.
|
|
281
|
+
*/
|
|
282
|
+
async reassertConnectedIfRecovered() {
|
|
283
|
+
if (!this.appliedOffline || !this.applianceId) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (!this.sunspecClient.isHealthy(this.unitId)) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
console.log(`${this.constructor.name} ${this.applianceId}: connection recovered via shared socket — re-asserting Connected`);
|
|
290
|
+
try {
|
|
291
|
+
await this.applianceManager.updateApplianceState(this.applianceId, enyo_appliance_js_1.EnyoApplianceConnectionType.Connector, enyo_appliance_js_1.EnyoApplianceStateEnum.Connected);
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
console.error(`${this.constructor.name} ${this.applianceId}: Failed to re-assert Connected: ${error}`);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
this.appliedOffline = false;
|
|
298
|
+
this.retryManager.reset();
|
|
299
|
+
this.consecutiveReconnectFailures = 0;
|
|
300
|
+
await this.onConnectionRestored();
|
|
301
|
+
}
|
|
258
302
|
sendCommandAcknowledge(messageId, acknowledgeMessage, answer, rejectionReason) {
|
|
259
303
|
if (!this.dataBus || !this.applianceId) {
|
|
260
304
|
return;
|
|
@@ -420,6 +464,10 @@ class SunspecInverter extends BaseSunspecDevice {
|
|
|
420
464
|
if (await this.markOfflineIfUnhealthy()) {
|
|
421
465
|
return messages;
|
|
422
466
|
}
|
|
467
|
+
// Healthy read: if a sibling on the same shared socket reconnected us
|
|
468
|
+
// (so our own tryReconnect() never ran), flip our appliance state back
|
|
469
|
+
// to Connected. No-op unless this device is still marked offline.
|
|
470
|
+
await this.reassertConnectedIfRecovered();
|
|
423
471
|
if (inverterData) {
|
|
424
472
|
const pvPowerW = dcStrings.reduce((sum, s) => sum + (s.powerW || 0), 0);
|
|
425
473
|
console.debug(`Got PV Power from DC strings: ${pvPowerW}W`);
|
|
@@ -1183,6 +1231,10 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
1183
1231
|
if (await this.markOfflineIfUnhealthy()) {
|
|
1184
1232
|
return messages;
|
|
1185
1233
|
}
|
|
1234
|
+
// Healthy read: if a sibling on the same shared socket reconnected us
|
|
1235
|
+
// (so our own tryReconnect() never ran), flip our appliance state back
|
|
1236
|
+
// to Connected. No-op unless this device is still marked offline.
|
|
1237
|
+
await this.reassertConnectedIfRecovered();
|
|
1186
1238
|
if (batteryData) {
|
|
1187
1239
|
// See `selectLiveBatteryPowerW` for the source-preference rationale.
|
|
1188
1240
|
const batteryPowerW = selectLiveBatteryPowerW(mpptBatteryPowerW, batteryData);
|
|
@@ -1696,6 +1748,10 @@ class SunspecMeter extends BaseSunspecDevice {
|
|
|
1696
1748
|
if (await this.markOfflineIfUnhealthy()) {
|
|
1697
1749
|
return messages;
|
|
1698
1750
|
}
|
|
1751
|
+
// Healthy read: if a sibling on the same shared socket reconnected us
|
|
1752
|
+
// (so our own tryReconnect() never ran), flip our appliance state back
|
|
1753
|
+
// to Connected. No-op unless this device is still marked offline.
|
|
1754
|
+
await this.reassertConnectedIfRecovered();
|
|
1699
1755
|
if (meterData) {
|
|
1700
1756
|
const meterMessage = {
|
|
1701
1757
|
id: (0, node_crypto_1.randomUUID)(),
|
|
@@ -27,6 +27,15 @@ export declare abstract class BaseSunspecDevice {
|
|
|
27
27
|
protected dataBus?: EnergyAppDataBus;
|
|
28
28
|
protected retryManager: ConnectionRetryManager;
|
|
29
29
|
protected consecutiveReconnectFailures: number;
|
|
30
|
+
/**
|
|
31
|
+
* True once {@link markOffline} has set the appliance state to Offline and
|
|
32
|
+
* it has not yet been re-asserted Connected. Used by
|
|
33
|
+
* {@link reassertConnectedIfRecovered} to heal the shared-socket case: when
|
|
34
|
+
* a sibling device on the same unit wins the reconnect race, this device's
|
|
35
|
+
* readData() sees isConnected() === true and skips tryReconnect(), so it
|
|
36
|
+
* would otherwise stay Offline forever despite reading data fine.
|
|
37
|
+
*/
|
|
38
|
+
private appliedOffline;
|
|
30
39
|
/**
|
|
31
40
|
* Prefix used when persisting calibration snapshots via the library's
|
|
32
41
|
* {@link SnapshotService}. Kept identical to the key the SDK used before
|
|
@@ -90,6 +99,20 @@ export declare abstract class BaseSunspecDevice {
|
|
|
90
99
|
* connection after the backoff interval. Then update appliance state.
|
|
91
100
|
*/
|
|
92
101
|
protected markOffline(): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Heal the shared-socket recovery gap. When several devices share one
|
|
104
|
+
* SunspecModbusClient on the same unit (e.g. a Fronius GEN24 inverter and
|
|
105
|
+
* its battery), only the device that detects !isConnected() first runs
|
|
106
|
+
* tryReconnect() — and that is the only path that flips the appliance back
|
|
107
|
+
* to Connected. Siblings then see isConnected() === true on their next
|
|
108
|
+
* readData() and skip tryReconnect() entirely, so without this they would
|
|
109
|
+
* stay Offline forever despite reading data fine.
|
|
110
|
+
*
|
|
111
|
+
* Call this on the success path of readData() (after the
|
|
112
|
+
* markOfflineIfUnhealthy() guard has confirmed the unit is healthy). It is
|
|
113
|
+
* a cheap no-op unless this device was previously marked offline.
|
|
114
|
+
*/
|
|
115
|
+
protected reassertConnectedIfRecovered(): Promise<void>;
|
|
93
116
|
protected sendCommandAcknowledge(messageId: string, acknowledgeMessage: EnyoDataBusMessageEnum | string, answer: EnyoCommandAcknowledgeAnswerEnum, rejectionReason?: string): void;
|
|
94
117
|
/**
|
|
95
118
|
* Build a typed {@link SnapshotService} bound to this appliance's calibration storage,
|
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.82';
|
|
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
|
@@ -27,6 +27,15 @@ export declare abstract class BaseSunspecDevice {
|
|
|
27
27
|
protected dataBus?: EnergyAppDataBus;
|
|
28
28
|
protected retryManager: ConnectionRetryManager;
|
|
29
29
|
protected consecutiveReconnectFailures: number;
|
|
30
|
+
/**
|
|
31
|
+
* True once {@link markOffline} has set the appliance state to Offline and
|
|
32
|
+
* it has not yet been re-asserted Connected. Used by
|
|
33
|
+
* {@link reassertConnectedIfRecovered} to heal the shared-socket case: when
|
|
34
|
+
* a sibling device on the same unit wins the reconnect race, this device's
|
|
35
|
+
* readData() sees isConnected() === true and skips tryReconnect(), so it
|
|
36
|
+
* would otherwise stay Offline forever despite reading data fine.
|
|
37
|
+
*/
|
|
38
|
+
private appliedOffline;
|
|
30
39
|
/**
|
|
31
40
|
* Prefix used when persisting calibration snapshots via the library's
|
|
32
41
|
* {@link SnapshotService}. Kept identical to the key the SDK used before
|
|
@@ -90,6 +99,20 @@ export declare abstract class BaseSunspecDevice {
|
|
|
90
99
|
* connection after the backoff interval. Then update appliance state.
|
|
91
100
|
*/
|
|
92
101
|
protected markOffline(): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Heal the shared-socket recovery gap. When several devices share one
|
|
104
|
+
* SunspecModbusClient on the same unit (e.g. a Fronius GEN24 inverter and
|
|
105
|
+
* its battery), only the device that detects !isConnected() first runs
|
|
106
|
+
* tryReconnect() — and that is the only path that flips the appliance back
|
|
107
|
+
* to Connected. Siblings then see isConnected() === true on their next
|
|
108
|
+
* readData() and skip tryReconnect() entirely, so without this they would
|
|
109
|
+
* stay Offline forever despite reading data fine.
|
|
110
|
+
*
|
|
111
|
+
* Call this on the success path of readData() (after the
|
|
112
|
+
* markOfflineIfUnhealthy() guard has confirmed the unit is healthy). It is
|
|
113
|
+
* a cheap no-op unless this device was previously marked offline.
|
|
114
|
+
*/
|
|
115
|
+
protected reassertConnectedIfRecovered(): Promise<void>;
|
|
93
116
|
protected sendCommandAcknowledge(messageId: string, acknowledgeMessage: EnyoDataBusMessageEnum | string, answer: EnyoCommandAcknowledgeAnswerEnum, rejectionReason?: string): void;
|
|
94
117
|
/**
|
|
95
118
|
* Build a typed {@link SnapshotService} bound to this appliance's calibration storage,
|
package/dist/sunspec-devices.js
CHANGED
|
@@ -101,6 +101,15 @@ export class BaseSunspecDevice {
|
|
|
101
101
|
dataBus;
|
|
102
102
|
retryManager;
|
|
103
103
|
consecutiveReconnectFailures = 0;
|
|
104
|
+
/**
|
|
105
|
+
* True once {@link markOffline} has set the appliance state to Offline and
|
|
106
|
+
* it has not yet been re-asserted Connected. Used by
|
|
107
|
+
* {@link reassertConnectedIfRecovered} to heal the shared-socket case: when
|
|
108
|
+
* a sibling device on the same unit wins the reconnect race, this device's
|
|
109
|
+
* readData() sees isConnected() === true and skips tryReconnect(), so it
|
|
110
|
+
* would otherwise stay Offline forever despite reading data fine.
|
|
111
|
+
*/
|
|
112
|
+
appliedOffline = false;
|
|
104
113
|
/**
|
|
105
114
|
* Prefix used when persisting calibration snapshots via the library's
|
|
106
115
|
* {@link SnapshotService}. Kept identical to the key the SDK used before
|
|
@@ -175,6 +184,7 @@ export class BaseSunspecDevice {
|
|
|
175
184
|
if (this.applianceId) {
|
|
176
185
|
await this.applianceManager.updateApplianceState(this.applianceId, EnyoApplianceConnectionType.Connector, EnyoApplianceStateEnum.Connected);
|
|
177
186
|
}
|
|
187
|
+
this.appliedOffline = false;
|
|
178
188
|
await this.onConnectionRestored();
|
|
179
189
|
const postStats = this.sunspecClient.getConnectionStats();
|
|
180
190
|
console.log(`${this.constructor.name} ${this.applianceId}: Reconnection successful after ${attempt} attempt(s) (opens=${postStats.opens}, closes=${postStats.closes}, openUnits=${postStats.openUnits})`);
|
|
@@ -243,12 +253,46 @@ export class BaseSunspecDevice {
|
|
|
243
253
|
if (this.applianceId) {
|
|
244
254
|
try {
|
|
245
255
|
await this.applianceManager.updateApplianceState(this.applianceId, EnyoApplianceConnectionType.Connector, EnyoApplianceStateEnum.Offline);
|
|
256
|
+
this.appliedOffline = true;
|
|
246
257
|
}
|
|
247
258
|
catch (error) {
|
|
248
259
|
console.error(`${this.constructor.name} ${this.applianceId}: Failed to mark appliance offline: ${error}`);
|
|
249
260
|
}
|
|
250
261
|
}
|
|
251
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* Heal the shared-socket recovery gap. When several devices share one
|
|
265
|
+
* SunspecModbusClient on the same unit (e.g. a Fronius GEN24 inverter and
|
|
266
|
+
* its battery), only the device that detects !isConnected() first runs
|
|
267
|
+
* tryReconnect() — and that is the only path that flips the appliance back
|
|
268
|
+
* to Connected. Siblings then see isConnected() === true on their next
|
|
269
|
+
* readData() and skip tryReconnect() entirely, so without this they would
|
|
270
|
+
* stay Offline forever despite reading data fine.
|
|
271
|
+
*
|
|
272
|
+
* Call this on the success path of readData() (after the
|
|
273
|
+
* markOfflineIfUnhealthy() guard has confirmed the unit is healthy). It is
|
|
274
|
+
* a cheap no-op unless this device was previously marked offline.
|
|
275
|
+
*/
|
|
276
|
+
async reassertConnectedIfRecovered() {
|
|
277
|
+
if (!this.appliedOffline || !this.applianceId) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (!this.sunspecClient.isHealthy(this.unitId)) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
console.log(`${this.constructor.name} ${this.applianceId}: connection recovered via shared socket — re-asserting Connected`);
|
|
284
|
+
try {
|
|
285
|
+
await this.applianceManager.updateApplianceState(this.applianceId, EnyoApplianceConnectionType.Connector, EnyoApplianceStateEnum.Connected);
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
console.error(`${this.constructor.name} ${this.applianceId}: Failed to re-assert Connected: ${error}`);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
this.appliedOffline = false;
|
|
292
|
+
this.retryManager.reset();
|
|
293
|
+
this.consecutiveReconnectFailures = 0;
|
|
294
|
+
await this.onConnectionRestored();
|
|
295
|
+
}
|
|
252
296
|
sendCommandAcknowledge(messageId, acknowledgeMessage, answer, rejectionReason) {
|
|
253
297
|
if (!this.dataBus || !this.applianceId) {
|
|
254
298
|
return;
|
|
@@ -413,6 +457,10 @@ export class SunspecInverter extends BaseSunspecDevice {
|
|
|
413
457
|
if (await this.markOfflineIfUnhealthy()) {
|
|
414
458
|
return messages;
|
|
415
459
|
}
|
|
460
|
+
// Healthy read: if a sibling on the same shared socket reconnected us
|
|
461
|
+
// (so our own tryReconnect() never ran), flip our appliance state back
|
|
462
|
+
// to Connected. No-op unless this device is still marked offline.
|
|
463
|
+
await this.reassertConnectedIfRecovered();
|
|
416
464
|
if (inverterData) {
|
|
417
465
|
const pvPowerW = dcStrings.reduce((sum, s) => sum + (s.powerW || 0), 0);
|
|
418
466
|
console.debug(`Got PV Power from DC strings: ${pvPowerW}W`);
|
|
@@ -1175,6 +1223,10 @@ export class SunspecBattery extends BaseSunspecDevice {
|
|
|
1175
1223
|
if (await this.markOfflineIfUnhealthy()) {
|
|
1176
1224
|
return messages;
|
|
1177
1225
|
}
|
|
1226
|
+
// Healthy read: if a sibling on the same shared socket reconnected us
|
|
1227
|
+
// (so our own tryReconnect() never ran), flip our appliance state back
|
|
1228
|
+
// to Connected. No-op unless this device is still marked offline.
|
|
1229
|
+
await this.reassertConnectedIfRecovered();
|
|
1178
1230
|
if (batteryData) {
|
|
1179
1231
|
// See `selectLiveBatteryPowerW` for the source-preference rationale.
|
|
1180
1232
|
const batteryPowerW = selectLiveBatteryPowerW(mpptBatteryPowerW, batteryData);
|
|
@@ -1687,6 +1739,10 @@ export class SunspecMeter extends BaseSunspecDevice {
|
|
|
1687
1739
|
if (await this.markOfflineIfUnhealthy()) {
|
|
1688
1740
|
return messages;
|
|
1689
1741
|
}
|
|
1742
|
+
// Healthy read: if a sibling on the same shared socket reconnected us
|
|
1743
|
+
// (so our own tryReconnect() never ran), flip our appliance state back
|
|
1744
|
+
// to Connected. No-op unless this device is still marked offline.
|
|
1745
|
+
await this.reassertConnectedIfRecovered();
|
|
1690
1746
|
if (meterData) {
|
|
1691
1747
|
const meterMessage = {
|
|
1692
1748
|
id: randomUUID(),
|
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.82",
|
|
4
4
|
"description": "enyo Energy Sunspec SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@enyo-energy/appliance-calibration": "0.0.2",
|
|
44
|
-
"@enyo-energy/energy-app-sdk": "^0.0.
|
|
44
|
+
"@enyo-energy/energy-app-sdk": "^0.0.160"
|
|
45
45
|
},
|
|
46
46
|
"volta": {
|
|
47
47
|
"node": "22.17.0"
|