@enyo-energy/sunspec-sdk 0.0.61 → 0.0.62

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.
@@ -6,11 +6,12 @@
6
6
  * Called on each readData() cycle; determines whether enough time has elapsed
7
7
  * to attempt a reconnection based on the current retry phase.
8
8
  *
9
- * Default schedule:
9
+ * Default schedule (infinite retry, max 15m backoff):
10
10
  * - Phase 1: every 10s for 1 minute
11
11
  * - Phase 2: every 30s for 2 minutes
12
12
  * - Phase 3: every 1m for 5 minutes
13
- * - Phase 4: every 5m forever
13
+ * - Phase 4: every 5m for 10 minutes
14
+ * - Phase 5: every 15m forever
14
15
  */
15
16
  Object.defineProperty(exports, "__esModule", { value: true });
16
17
  exports.ConnectionRetryManager = void 0;
@@ -5,11 +5,12 @@
5
5
  * Called on each readData() cycle; determines whether enough time has elapsed
6
6
  * to attempt a reconnection based on the current retry phase.
7
7
  *
8
- * Default schedule:
8
+ * Default schedule (infinite retry, max 15m backoff):
9
9
  * - Phase 1: every 10s for 1 minute
10
10
  * - Phase 2: every 30s for 2 minutes
11
11
  * - Phase 3: every 1m for 5 minutes
12
- * - Phase 4: every 5m forever
12
+ * - Phase 4: every 5m for 10 minutes
13
+ * - Phase 5: every 15m forever
13
14
  */
14
15
  import { IRetryConfig, IRetryPhase } from './sunspec-interfaces.cjs';
15
16
  export declare class ConnectionRetryManager {
@@ -187,6 +187,24 @@ class BaseSunspecDevice {
187
187
  async onConnectionFailure(_consecutiveFailures) {
188
188
  // Default: no-op. SunspecInverter overrides to publish a faulted status.
189
189
  }
190
+ /**
191
+ * Detect a silent read failure: the SDK's per-model readers swallow modbus errors and
192
+ * return null/[] rather than throwing, so a readData() cycle can complete "successfully"
193
+ * with no usable data while the underlying connection is broken. Once
194
+ * `connectionHealth.isHealthy()` flips to false, treat the device as offline so the
195
+ * appliance state is updated and the retry manager starts the backoff schedule.
196
+ *
197
+ * Returns true if the device was marked offline.
198
+ */
199
+ async markOfflineIfUnhealthy() {
200
+ if (this.sunspecClient.isHealthy(this.unitId)) {
201
+ return false;
202
+ }
203
+ console.warn(`${this.constructor.name} ${this.applianceId}: connection unhealthy after read cycle ` +
204
+ `(consecutiveFailures=${this.sunspecClient.getConnectionHealth().getConsecutiveFailures()}) — marking offline`);
205
+ await this.markOffline();
206
+ return true;
207
+ }
190
208
  /**
191
209
  * Mark the device as offline: close the underlying socket so the next readData()
192
210
  * cycle sees isConnected() === false and tryReconnect() can establish a fresh
@@ -338,6 +356,11 @@ class SunspecInverter extends BaseSunspecDevice {
338
356
  const mpptDataList = await this.sunspecClient.readAllMPPTData(this.unitId);
339
357
  const inverterSettings = await this.sunspecClient.readInverterSettings(this.unitId);
340
358
  const dcStrings = this.mapMPPTToStrings(mpptDataList);
359
+ // SDK readers swallow modbus errors and return null/[]; detect that here so the
360
+ // appliance is marked offline and the retry manager starts its backoff.
361
+ if (await this.markOfflineIfUnhealthy()) {
362
+ return messages;
363
+ }
341
364
  if (inverterData) {
342
365
  const pvPowerW = dcStrings.reduce((sum, s) => sum + (s.powerW || 0), 0);
343
366
  console.debug(`Got PV Power from DC strings: ${pvPowerW}W`);
@@ -806,6 +829,11 @@ class SunspecBattery extends BaseSunspecDevice {
806
829
  const batteryData = await this.sunspecClient.readBatteryData(this.unitId);
807
830
  const mpptDataList = await this.sunspecClient.readAllMPPTData(this.unitId);
808
831
  const mpptBatteryPowerW = this.extractBatteryPowerFromMPPT(mpptDataList);
832
+ // SDK readers swallow modbus errors and return null/[]; detect that here so the
833
+ // appliance is marked offline and the retry manager starts its backoff.
834
+ if (await this.markOfflineIfUnhealthy()) {
835
+ return messages;
836
+ }
809
837
  if (batteryData) {
810
838
  const advancedBatteryModel = this.sunspecClient.findModel(this.unitId, 801);
811
839
  const batteryBaseModel = this.sunspecClient.findModel(this.unitId, sunspec_interfaces_js_1.SunspecModelId.BatteryBase);
@@ -1291,6 +1319,11 @@ class SunspecMeter extends BaseSunspecDevice {
1291
1319
  try {
1292
1320
  // Read meter data
1293
1321
  const meterData = await this.sunspecClient.readMeterData(this.unitId);
1322
+ // SDK readers swallow modbus errors and return null; detect that here so the
1323
+ // appliance is marked offline and the retry manager starts its backoff.
1324
+ if (await this.markOfflineIfUnhealthy()) {
1325
+ return messages;
1326
+ }
1294
1327
  if (meterData) {
1295
1328
  const meterMessage = {
1296
1329
  id: (0, node_crypto_1.randomUUID)(),
@@ -60,6 +60,16 @@ export declare abstract class BaseSunspecDevice {
60
60
  * per failed attempt with the running consecutive-failure count.
61
61
  */
62
62
  protected onConnectionFailure(_consecutiveFailures: number): Promise<void>;
63
+ /**
64
+ * Detect a silent read failure: the SDK's per-model readers swallow modbus errors and
65
+ * return null/[] rather than throwing, so a readData() cycle can complete "successfully"
66
+ * with no usable data while the underlying connection is broken. Once
67
+ * `connectionHealth.isHealthy()` flips to false, treat the device as offline so the
68
+ * appliance state is updated and the retry manager starts the backoff schedule.
69
+ *
70
+ * Returns true if the device was marked offline.
71
+ */
72
+ protected markOfflineIfUnhealthy(): Promise<boolean>;
63
73
  /**
64
74
  * Mark the device as offline: close the underlying socket so the next readData()
65
75
  * cycle sees isConnected() === false and tryReconnect() can establish a fresh
@@ -9,7 +9,8 @@ exports.DEFAULT_RETRY_CONFIG = {
9
9
  { intervalMs: 10_000, durationMs: 60_000 }, // Phase 1: every 10s for 1 minute
10
10
  { intervalMs: 30_000, durationMs: 120_000 }, // Phase 2: every 30s for 2 minutes
11
11
  { intervalMs: 60_000, durationMs: 300_000 }, // Phase 3: every 1m for 5 minutes
12
- { intervalMs: 300_000, durationMs: 0 }, // Phase 4: every 5m forever
12
+ { intervalMs: 300_000, durationMs: 600_000 }, // Phase 4: every 5m for 10 minutes
13
+ { intervalMs: 900_000, durationMs: 0 }, // Phase 5: every 15m forever
13
14
  ]
14
15
  };
15
16
  /**
@@ -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.61';
12
+ exports.SDK_VERSION = '0.0.62';
13
13
  /**
14
14
  * Gets the current SDK version.
15
15
  * @returns The semantic version string of the SDK
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export declare const SDK_VERSION = "0.0.61";
8
+ export declare const SDK_VERSION = "0.0.62";
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
@@ -5,11 +5,12 @@
5
5
  * Called on each readData() cycle; determines whether enough time has elapsed
6
6
  * to attempt a reconnection based on the current retry phase.
7
7
  *
8
- * Default schedule:
8
+ * Default schedule (infinite retry, max 15m backoff):
9
9
  * - Phase 1: every 10s for 1 minute
10
10
  * - Phase 2: every 30s for 2 minutes
11
11
  * - Phase 3: every 1m for 5 minutes
12
- * - Phase 4: every 5m forever
12
+ * - Phase 4: every 5m for 10 minutes
13
+ * - Phase 5: every 15m forever
13
14
  */
14
15
  import { IRetryConfig, IRetryPhase } from './sunspec-interfaces.js';
15
16
  export declare class ConnectionRetryManager {
@@ -5,11 +5,12 @@
5
5
  * Called on each readData() cycle; determines whether enough time has elapsed
6
6
  * to attempt a reconnection based on the current retry phase.
7
7
  *
8
- * Default schedule:
8
+ * Default schedule (infinite retry, max 15m backoff):
9
9
  * - Phase 1: every 10s for 1 minute
10
10
  * - Phase 2: every 30s for 2 minutes
11
11
  * - Phase 3: every 1m for 5 minutes
12
- * - Phase 4: every 5m forever
12
+ * - Phase 4: every 5m for 10 minutes
13
+ * - Phase 5: every 15m forever
13
14
  */
14
15
  import { DEFAULT_RETRY_CONFIG } from './sunspec-interfaces.js';
15
16
  export class ConnectionRetryManager {
@@ -60,6 +60,16 @@ export declare abstract class BaseSunspecDevice {
60
60
  * per failed attempt with the running consecutive-failure count.
61
61
  */
62
62
  protected onConnectionFailure(_consecutiveFailures: number): Promise<void>;
63
+ /**
64
+ * Detect a silent read failure: the SDK's per-model readers swallow modbus errors and
65
+ * return null/[] rather than throwing, so a readData() cycle can complete "successfully"
66
+ * with no usable data while the underlying connection is broken. Once
67
+ * `connectionHealth.isHealthy()` flips to false, treat the device as offline so the
68
+ * appliance state is updated and the retry manager starts the backoff schedule.
69
+ *
70
+ * Returns true if the device was marked offline.
71
+ */
72
+ protected markOfflineIfUnhealthy(): Promise<boolean>;
63
73
  /**
64
74
  * Mark the device as offline: close the underlying socket so the next readData()
65
75
  * cycle sees isConnected() === false and tryReconnect() can establish a fresh
@@ -184,6 +184,24 @@ export class BaseSunspecDevice {
184
184
  async onConnectionFailure(_consecutiveFailures) {
185
185
  // Default: no-op. SunspecInverter overrides to publish a faulted status.
186
186
  }
187
+ /**
188
+ * Detect a silent read failure: the SDK's per-model readers swallow modbus errors and
189
+ * return null/[] rather than throwing, so a readData() cycle can complete "successfully"
190
+ * with no usable data while the underlying connection is broken. Once
191
+ * `connectionHealth.isHealthy()` flips to false, treat the device as offline so the
192
+ * appliance state is updated and the retry manager starts the backoff schedule.
193
+ *
194
+ * Returns true if the device was marked offline.
195
+ */
196
+ async markOfflineIfUnhealthy() {
197
+ if (this.sunspecClient.isHealthy(this.unitId)) {
198
+ return false;
199
+ }
200
+ console.warn(`${this.constructor.name} ${this.applianceId}: connection unhealthy after read cycle ` +
201
+ `(consecutiveFailures=${this.sunspecClient.getConnectionHealth().getConsecutiveFailures()}) — marking offline`);
202
+ await this.markOffline();
203
+ return true;
204
+ }
187
205
  /**
188
206
  * Mark the device as offline: close the underlying socket so the next readData()
189
207
  * cycle sees isConnected() === false and tryReconnect() can establish a fresh
@@ -334,6 +352,11 @@ export class SunspecInverter extends BaseSunspecDevice {
334
352
  const mpptDataList = await this.sunspecClient.readAllMPPTData(this.unitId);
335
353
  const inverterSettings = await this.sunspecClient.readInverterSettings(this.unitId);
336
354
  const dcStrings = this.mapMPPTToStrings(mpptDataList);
355
+ // SDK readers swallow modbus errors and return null/[]; detect that here so the
356
+ // appliance is marked offline and the retry manager starts its backoff.
357
+ if (await this.markOfflineIfUnhealthy()) {
358
+ return messages;
359
+ }
337
360
  if (inverterData) {
338
361
  const pvPowerW = dcStrings.reduce((sum, s) => sum + (s.powerW || 0), 0);
339
362
  console.debug(`Got PV Power from DC strings: ${pvPowerW}W`);
@@ -801,6 +824,11 @@ export class SunspecBattery extends BaseSunspecDevice {
801
824
  const batteryData = await this.sunspecClient.readBatteryData(this.unitId);
802
825
  const mpptDataList = await this.sunspecClient.readAllMPPTData(this.unitId);
803
826
  const mpptBatteryPowerW = this.extractBatteryPowerFromMPPT(mpptDataList);
827
+ // SDK readers swallow modbus errors and return null/[]; detect that here so the
828
+ // appliance is marked offline and the retry manager starts its backoff.
829
+ if (await this.markOfflineIfUnhealthy()) {
830
+ return messages;
831
+ }
804
832
  if (batteryData) {
805
833
  const advancedBatteryModel = this.sunspecClient.findModel(this.unitId, 801);
806
834
  const batteryBaseModel = this.sunspecClient.findModel(this.unitId, SunspecModelId.BatteryBase);
@@ -1285,6 +1313,11 @@ export class SunspecMeter extends BaseSunspecDevice {
1285
1313
  try {
1286
1314
  // Read meter data
1287
1315
  const meterData = await this.sunspecClient.readMeterData(this.unitId);
1316
+ // SDK readers swallow modbus errors and return null; detect that here so the
1317
+ // appliance is marked offline and the retry manager starts its backoff.
1318
+ if (await this.markOfflineIfUnhealthy()) {
1319
+ return messages;
1320
+ }
1288
1321
  if (meterData) {
1289
1322
  const meterMessage = {
1290
1323
  id: randomUUID(),
@@ -6,7 +6,8 @@ export const DEFAULT_RETRY_CONFIG = {
6
6
  { intervalMs: 10_000, durationMs: 60_000 }, // Phase 1: every 10s for 1 minute
7
7
  { intervalMs: 30_000, durationMs: 120_000 }, // Phase 2: every 30s for 2 minutes
8
8
  { intervalMs: 60_000, durationMs: 300_000 }, // Phase 3: every 1m for 5 minutes
9
- { intervalMs: 300_000, durationMs: 0 }, // Phase 4: every 5m forever
9
+ { intervalMs: 300_000, durationMs: 600_000 }, // Phase 4: every 5m for 10 minutes
10
+ { intervalMs: 900_000, durationMs: 0 }, // Phase 5: every 15m forever
10
11
  ]
11
12
  };
12
13
  /**
package/dist/version.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export declare const SDK_VERSION = "0.0.61";
8
+ export declare const SDK_VERSION = "0.0.62";
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
package/dist/version.js CHANGED
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export const SDK_VERSION = '0.0.61';
8
+ export const SDK_VERSION = '0.0.62';
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enyo-energy/sunspec-sdk",
3
- "version": "0.0.61",
3
+ "version": "0.0.62",
4
4
  "description": "enyo Energy Sunspec SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",