@enyo-energy/sunspec-sdk 0.0.72 → 0.0.73

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.
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SunspecMeter = exports.SunspecBattery = exports.SunspecInverter = exports.BaseSunspecDevice = void 0;
4
+ exports.detectFeaturesFromRegisters = detectFeaturesFromRegisters;
5
+ exports.filterFeaturesByCalibrationResult = filterFeaturesByCalibrationResult;
4
6
  const sunspec_interfaces_js_1 = require("./sunspec-interfaces.cjs");
5
7
  const node_crypto_1 = require("node:crypto");
6
8
  const enyo_appliance_js_1 = require("@enyo-energy/energy-app-sdk/dist/types/enyo-appliance.js");
@@ -864,6 +866,54 @@ class SunspecInverter extends BaseSunspecDevice {
864
866
  }
865
867
  }
866
868
  exports.SunspecInverter = SunspecInverter;
869
+ /**
870
+ * Pure register-presence detection — exported so it can be unit-tested
871
+ * without the full `SunspecBattery` scaffold. Maps each Model 124 writable
872
+ * register to the `EnyoBatteryFeature` it represents:
873
+ *
874
+ * - `chaGriSet` → `GridCharging` (charge source PV/GRID)
875
+ * - `wChaMax` → `ChargeLimitation` (max charge power)
876
+ * - `outWRte` → `DischargeLimitation` (discharge rate %)
877
+ * - `storCtlMod` → `GridDischarging` (closest signal that external discharge
878
+ * control is available; the inverter decides where it goes)
879
+ */
880
+ function detectFeaturesFromRegisters(batteryData) {
881
+ const features = [];
882
+ if (batteryData?.chaGriSet !== undefined)
883
+ features.push(enyo_battery_appliance_js_1.EnyoBatteryFeature.GridCharging);
884
+ if (batteryData?.wChaMax !== undefined)
885
+ features.push(enyo_battery_appliance_js_1.EnyoBatteryFeature.ChargeLimitation);
886
+ if (batteryData?.outWRte !== undefined)
887
+ features.push(enyo_battery_appliance_js_1.EnyoBatteryFeature.DischargeLimitation);
888
+ if (batteryData?.storCtlMod !== undefined)
889
+ features.push(enyo_battery_appliance_js_1.EnyoBatteryFeature.GridDischarging);
890
+ return features;
891
+ }
892
+ /**
893
+ * Apply the per-feature calibration filter to a register-detected set.
894
+ * Exported for unit-test access to the legacy-result fallback branch.
895
+ *
896
+ * The contract:
897
+ * - No `result` or `result.state !== 'calibrated'` → strip every controllable
898
+ * feature (safe fallback while waiting for calibration).
899
+ * - `result.state === 'calibrated'` with a decodable per-feature payload →
900
+ * publish only the controllable features whose probes passed.
901
+ * - `result.state === 'calibrated'` with **no** decodable payload → legacy
902
+ * data (pre-feature-calibrator SDK). The new calibrator only marks
903
+ * `state=calibrated` when at least one probe passes, so an empty decoded
904
+ * set with `state=calibrated` unambiguously signals legacy results. Publish
905
+ * the full detected set to preserve the old all-or-nothing semantics on
906
+ * upgrade.
907
+ */
908
+ function filterFeaturesByCalibrationResult(detected, result, controllable) {
909
+ if (!result || result.state !== 'calibrated') {
910
+ return detected.filter(f => !controllable.includes(f));
911
+ }
912
+ const passed = (0, sunspec_battery_feature_calibrator_js_1.decodeFeatureResults)(result.notes);
913
+ if (passed.size === 0)
914
+ return detected;
915
+ return detected.filter(f => !controllable.includes(f) || passed.has(f));
916
+ }
867
917
  /**
868
918
  * Sunspec Battery implementation
869
919
  */
@@ -939,14 +989,7 @@ class SunspecBattery extends BaseSunspecDevice {
939
989
  * {@link resolveAdvertisedFeatures}.
940
990
  */
941
991
  detectFromRegisters(batteryData) {
942
- const features = [];
943
- if (batteryData?.chaGriSet !== undefined) {
944
- features.push(enyo_battery_appliance_js_1.EnyoBatteryFeature.GridCharging);
945
- }
946
- if (batteryData?.wChaMax !== undefined) {
947
- features.push(enyo_battery_appliance_js_1.EnyoBatteryFeature.ChargeLimitation);
948
- }
949
- return features;
992
+ return detectFeaturesFromRegisters(batteryData);
950
993
  }
951
994
  /**
952
995
  * Resolve `appliance.battery.features` for the configured mode. Called on
@@ -964,18 +1007,10 @@ class SunspecBattery extends BaseSunspecDevice {
964
1007
  return intersect(this.detectFromRegisters(batteryData));
965
1008
  case sunspec_interfaces_js_1.SunspecBatteryFeatureModeKind.CalibrationBased: {
966
1009
  const detected = intersect(this.detectFromRegisters(batteryData));
967
- if (!this.applianceId || !this.calibrationResultStore) {
968
- return detected.filter(f => !SunspecBattery.CONTROLLABLE_FEATURES.includes(f));
969
- }
970
- const result = this.calibrationResultStore.getResult(this.applianceId);
971
- if (!result || result.state !== 'calibrated') {
972
- return detected.filter(f => !SunspecBattery.CONTROLLABLE_FEATURES.includes(f));
973
- }
974
- // Per-feature gating: only publish controllable features whose
975
- // probe passed during the last calibration. See
976
- // `decodeFeatureResults` and `SunspecBatteryFeatureCalibrator`.
977
- const passed = (0, sunspec_battery_feature_calibrator_js_1.decodeFeatureResults)(result.notes);
978
- return detected.filter(f => !SunspecBattery.CONTROLLABLE_FEATURES.includes(f) || passed.has(f));
1010
+ const result = this.applianceId
1011
+ ? this.calibrationResultStore?.getResult(this.applianceId)
1012
+ : undefined;
1013
+ return filterFeaturesByCalibrationResult(detected, result, SunspecBattery.CONTROLLABLE_FEATURES);
979
1014
  }
980
1015
  }
981
1016
  }
@@ -1,11 +1,12 @@
1
- import { type IRetryConfig, type SunspecBatteryBaseData, SunspecBatteryCapability, type SunspecBatteryControls, type SunspecBatteryFeatureMode, type SunspecInverterData, SunspecInverterCapability, SunspecMeterCapability, SunspecStorageMode } from "./sunspec-interfaces.cjs";
1
+ import { type IRetryConfig, type SunspecBatteryBaseData, SunspecBatteryCapability, type SunspecBatteryControls, type SunspecBatteryData, type SunspecBatteryFeatureMode, type SunspecInverterData, SunspecInverterCapability, SunspecMeterCapability, SunspecStorageMode } from "./sunspec-interfaces.cjs";
2
2
  import { ApplianceManager, EnergyApp } from "@enyo-energy/energy-app-sdk";
3
3
  import { type EnyoAppliance, type EnyoApplianceErrorCode, EnyoApplianceName } from "@enyo-energy/energy-app-sdk/dist/types/enyo-appliance.js";
4
4
  import { EnyoNetworkDevice } from "@enyo-energy/energy-app-sdk/dist/types/enyo-network-device.js";
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 { BatteryCalibrator, type BatteryCalibratorConfig, type CalibrationResultStore, type CalibrationSnapshot, type RestoreReason, SnapshotService } from "@enyo-energy/appliance-calibration";
8
+ import { BatteryCalibrator, type BatteryCalibratorConfig, type CalibrationResult, type CalibrationResultStore, type CalibrationSnapshot, type RestoreReason, SnapshotService } from "@enyo-energy/appliance-calibration";
9
+ import { EnyoBatteryFeature } from "@enyo-energy/energy-app-sdk/dist/types/enyo-battery-appliance.js";
9
10
  import { EnergyAppDataBus } from "@enyo-energy/energy-app-sdk/dist/packages/energy-app-data-bus.js";
10
11
  /**
11
12
  * Base abstract class for all Sunspec devices
@@ -188,6 +189,35 @@ export declare class SunspecInverter extends BaseSunspecDevice {
188
189
  */
189
190
  private restoreInverterSnapshot;
190
191
  }
192
+ /**
193
+ * Pure register-presence detection — exported so it can be unit-tested
194
+ * without the full `SunspecBattery` scaffold. Maps each Model 124 writable
195
+ * register to the `EnyoBatteryFeature` it represents:
196
+ *
197
+ * - `chaGriSet` → `GridCharging` (charge source PV/GRID)
198
+ * - `wChaMax` → `ChargeLimitation` (max charge power)
199
+ * - `outWRte` → `DischargeLimitation` (discharge rate %)
200
+ * - `storCtlMod` → `GridDischarging` (closest signal that external discharge
201
+ * control is available; the inverter decides where it goes)
202
+ */
203
+ export declare function detectFeaturesFromRegisters(batteryData: SunspecBatteryData | null): EnyoBatteryFeature[];
204
+ /**
205
+ * Apply the per-feature calibration filter to a register-detected set.
206
+ * Exported for unit-test access to the legacy-result fallback branch.
207
+ *
208
+ * The contract:
209
+ * - No `result` or `result.state !== 'calibrated'` → strip every controllable
210
+ * feature (safe fallback while waiting for calibration).
211
+ * - `result.state === 'calibrated'` with a decodable per-feature payload →
212
+ * publish only the controllable features whose probes passed.
213
+ * - `result.state === 'calibrated'` with **no** decodable payload → legacy
214
+ * data (pre-feature-calibrator SDK). The new calibrator only marks
215
+ * `state=calibrated` when at least one probe passes, so an empty decoded
216
+ * set with `state=calibrated` unambiguously signals legacy results. Publish
217
+ * the full detected set to preserve the old all-or-nothing semantics on
218
+ * upgrade.
219
+ */
220
+ export declare function filterFeaturesByCalibrationResult(detected: EnyoBatteryFeature[], result: CalibrationResult | undefined, controllable: readonly EnyoBatteryFeature[]): EnyoBatteryFeature[];
191
221
  /**
192
222
  * Sunspec Battery implementation
193
223
  */
@@ -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.72';
12
+ exports.SDK_VERSION = '0.0.73';
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.72";
8
+ export declare const SDK_VERSION = "0.0.73";
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
@@ -1,11 +1,12 @@
1
- import { type IRetryConfig, type SunspecBatteryBaseData, SunspecBatteryCapability, type SunspecBatteryControls, type SunspecBatteryFeatureMode, type SunspecInverterData, SunspecInverterCapability, SunspecMeterCapability, SunspecStorageMode } from "./sunspec-interfaces.js";
1
+ import { type IRetryConfig, type SunspecBatteryBaseData, SunspecBatteryCapability, type SunspecBatteryControls, type SunspecBatteryData, type SunspecBatteryFeatureMode, type SunspecInverterData, SunspecInverterCapability, SunspecMeterCapability, SunspecStorageMode } from "./sunspec-interfaces.js";
2
2
  import { ApplianceManager, EnergyApp } from "@enyo-energy/energy-app-sdk";
3
3
  import { type EnyoAppliance, type EnyoApplianceErrorCode, EnyoApplianceName } from "@enyo-energy/energy-app-sdk/dist/types/enyo-appliance.js";
4
4
  import { EnyoNetworkDevice } from "@enyo-energy/energy-app-sdk/dist/types/enyo-network-device.js";
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 { BatteryCalibrator, type BatteryCalibratorConfig, type CalibrationResultStore, type CalibrationSnapshot, type RestoreReason, SnapshotService } from "@enyo-energy/appliance-calibration";
8
+ import { BatteryCalibrator, type BatteryCalibratorConfig, type CalibrationResult, type CalibrationResultStore, type CalibrationSnapshot, type RestoreReason, SnapshotService } from "@enyo-energy/appliance-calibration";
9
+ import { EnyoBatteryFeature } from "@enyo-energy/energy-app-sdk/dist/types/enyo-battery-appliance.js";
9
10
  import { EnergyAppDataBus } from "@enyo-energy/energy-app-sdk/dist/packages/energy-app-data-bus.js";
10
11
  /**
11
12
  * Base abstract class for all Sunspec devices
@@ -188,6 +189,35 @@ export declare class SunspecInverter extends BaseSunspecDevice {
188
189
  */
189
190
  private restoreInverterSnapshot;
190
191
  }
192
+ /**
193
+ * Pure register-presence detection — exported so it can be unit-tested
194
+ * without the full `SunspecBattery` scaffold. Maps each Model 124 writable
195
+ * register to the `EnyoBatteryFeature` it represents:
196
+ *
197
+ * - `chaGriSet` → `GridCharging` (charge source PV/GRID)
198
+ * - `wChaMax` → `ChargeLimitation` (max charge power)
199
+ * - `outWRte` → `DischargeLimitation` (discharge rate %)
200
+ * - `storCtlMod` → `GridDischarging` (closest signal that external discharge
201
+ * control is available; the inverter decides where it goes)
202
+ */
203
+ export declare function detectFeaturesFromRegisters(batteryData: SunspecBatteryData | null): EnyoBatteryFeature[];
204
+ /**
205
+ * Apply the per-feature calibration filter to a register-detected set.
206
+ * Exported for unit-test access to the legacy-result fallback branch.
207
+ *
208
+ * The contract:
209
+ * - No `result` or `result.state !== 'calibrated'` → strip every controllable
210
+ * feature (safe fallback while waiting for calibration).
211
+ * - `result.state === 'calibrated'` with a decodable per-feature payload →
212
+ * publish only the controllable features whose probes passed.
213
+ * - `result.state === 'calibrated'` with **no** decodable payload → legacy
214
+ * data (pre-feature-calibrator SDK). The new calibrator only marks
215
+ * `state=calibrated` when at least one probe passes, so an empty decoded
216
+ * set with `state=calibrated` unambiguously signals legacy results. Publish
217
+ * the full detected set to preserve the old all-or-nothing semantics on
218
+ * upgrade.
219
+ */
220
+ export declare function filterFeaturesByCalibrationResult(detected: EnyoBatteryFeature[], result: CalibrationResult | undefined, controllable: readonly EnyoBatteryFeature[]): EnyoBatteryFeature[];
191
221
  /**
192
222
  * Sunspec Battery implementation
193
223
  */
@@ -859,6 +859,54 @@ export class SunspecInverter extends BaseSunspecDevice {
859
859
  }
860
860
  }
861
861
  }
862
+ /**
863
+ * Pure register-presence detection — exported so it can be unit-tested
864
+ * without the full `SunspecBattery` scaffold. Maps each Model 124 writable
865
+ * register to the `EnyoBatteryFeature` it represents:
866
+ *
867
+ * - `chaGriSet` → `GridCharging` (charge source PV/GRID)
868
+ * - `wChaMax` → `ChargeLimitation` (max charge power)
869
+ * - `outWRte` → `DischargeLimitation` (discharge rate %)
870
+ * - `storCtlMod` → `GridDischarging` (closest signal that external discharge
871
+ * control is available; the inverter decides where it goes)
872
+ */
873
+ export function detectFeaturesFromRegisters(batteryData) {
874
+ const features = [];
875
+ if (batteryData?.chaGriSet !== undefined)
876
+ features.push(EnyoBatteryFeature.GridCharging);
877
+ if (batteryData?.wChaMax !== undefined)
878
+ features.push(EnyoBatteryFeature.ChargeLimitation);
879
+ if (batteryData?.outWRte !== undefined)
880
+ features.push(EnyoBatteryFeature.DischargeLimitation);
881
+ if (batteryData?.storCtlMod !== undefined)
882
+ features.push(EnyoBatteryFeature.GridDischarging);
883
+ return features;
884
+ }
885
+ /**
886
+ * Apply the per-feature calibration filter to a register-detected set.
887
+ * Exported for unit-test access to the legacy-result fallback branch.
888
+ *
889
+ * The contract:
890
+ * - No `result` or `result.state !== 'calibrated'` → strip every controllable
891
+ * feature (safe fallback while waiting for calibration).
892
+ * - `result.state === 'calibrated'` with a decodable per-feature payload →
893
+ * publish only the controllable features whose probes passed.
894
+ * - `result.state === 'calibrated'` with **no** decodable payload → legacy
895
+ * data (pre-feature-calibrator SDK). The new calibrator only marks
896
+ * `state=calibrated` when at least one probe passes, so an empty decoded
897
+ * set with `state=calibrated` unambiguously signals legacy results. Publish
898
+ * the full detected set to preserve the old all-or-nothing semantics on
899
+ * upgrade.
900
+ */
901
+ export function filterFeaturesByCalibrationResult(detected, result, controllable) {
902
+ if (!result || result.state !== 'calibrated') {
903
+ return detected.filter(f => !controllable.includes(f));
904
+ }
905
+ const passed = decodeFeatureResults(result.notes);
906
+ if (passed.size === 0)
907
+ return detected;
908
+ return detected.filter(f => !controllable.includes(f) || passed.has(f));
909
+ }
862
910
  /**
863
911
  * Sunspec Battery implementation
864
912
  */
@@ -934,14 +982,7 @@ export class SunspecBattery extends BaseSunspecDevice {
934
982
  * {@link resolveAdvertisedFeatures}.
935
983
  */
936
984
  detectFromRegisters(batteryData) {
937
- const features = [];
938
- if (batteryData?.chaGriSet !== undefined) {
939
- features.push(EnyoBatteryFeature.GridCharging);
940
- }
941
- if (batteryData?.wChaMax !== undefined) {
942
- features.push(EnyoBatteryFeature.ChargeLimitation);
943
- }
944
- return features;
985
+ return detectFeaturesFromRegisters(batteryData);
945
986
  }
946
987
  /**
947
988
  * Resolve `appliance.battery.features` for the configured mode. Called on
@@ -959,18 +1000,10 @@ export class SunspecBattery extends BaseSunspecDevice {
959
1000
  return intersect(this.detectFromRegisters(batteryData));
960
1001
  case SunspecBatteryFeatureModeKind.CalibrationBased: {
961
1002
  const detected = intersect(this.detectFromRegisters(batteryData));
962
- if (!this.applianceId || !this.calibrationResultStore) {
963
- return detected.filter(f => !SunspecBattery.CONTROLLABLE_FEATURES.includes(f));
964
- }
965
- const result = this.calibrationResultStore.getResult(this.applianceId);
966
- if (!result || result.state !== 'calibrated') {
967
- return detected.filter(f => !SunspecBattery.CONTROLLABLE_FEATURES.includes(f));
968
- }
969
- // Per-feature gating: only publish controllable features whose
970
- // probe passed during the last calibration. See
971
- // `decodeFeatureResults` and `SunspecBatteryFeatureCalibrator`.
972
- const passed = decodeFeatureResults(result.notes);
973
- return detected.filter(f => !SunspecBattery.CONTROLLABLE_FEATURES.includes(f) || passed.has(f));
1003
+ const result = this.applianceId
1004
+ ? this.calibrationResultStore?.getResult(this.applianceId)
1005
+ : undefined;
1006
+ return filterFeaturesByCalibrationResult(detected, result, SunspecBattery.CONTROLLABLE_FEATURES);
974
1007
  }
975
1008
  }
976
1009
  }
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.72";
8
+ export declare const SDK_VERSION = "0.0.73";
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.72';
8
+ export const SDK_VERSION = '0.0.73';
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.72",
3
+ "version": "0.0.73",
4
4
  "description": "enyo Energy Sunspec SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",