@enyo-energy/sunspec-sdk 0.0.64 → 0.0.66

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.
@@ -723,6 +723,9 @@ class SunspecBattery extends BaseSunspecDevice {
723
723
  if (batteryData?.chaGriSet !== undefined) {
724
724
  features.push(enyo_battery_appliance_js_1.EnyoBatteryFeature.GridCharging);
725
725
  }
726
+ if (batteryData?.wChaMax !== undefined) {
727
+ features.push(enyo_battery_appliance_js_1.EnyoBatteryFeature.ChargeLimitation);
728
+ }
726
729
  // Create or update appliance (skip if an existing appliance was provided)
727
730
  if (!this.applianceId) {
728
731
  try {
@@ -1096,7 +1099,8 @@ class SunspecBattery extends BaseSunspecDevice {
1096
1099
  this.dataBusListenerId = this.dataBus.listenForMessages([
1097
1100
  enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StartStorageGridChargeV1,
1098
1101
  enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.StopStorageGridChargeV1,
1099
- enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.SetStorageDischargeLimitV1
1102
+ enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.SetStorageDischargeLimitV1,
1103
+ enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.SetStorageChargeLimitV1
1100
1104
  ], (entry) => this.handleStorageCommand(entry));
1101
1105
  console.log(`Battery ${this.applianceId}: started data bus listening (listener ${this.dataBusListenerId})`);
1102
1106
  }
@@ -1127,6 +1131,9 @@ class SunspecBattery extends BaseSunspecDevice {
1127
1131
  case enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.SetStorageDischargeLimitV1:
1128
1132
  await this.handleSetDischargeLimit(entry);
1129
1133
  break;
1134
+ case enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.SetStorageChargeLimitV1:
1135
+ await this.handleSetChargeLimit(entry);
1136
+ break;
1130
1137
  }
1131
1138
  }
1132
1139
  catch (error) {
@@ -1219,6 +1226,30 @@ class SunspecBattery extends BaseSunspecDevice {
1219
1226
  }
1220
1227
  this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
1221
1228
  }
1229
+ async handleSetChargeLimit(msg) {
1230
+ if (!this.isConnected() || !this.applianceId) {
1231
+ this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.NotSupported);
1232
+ return;
1233
+ }
1234
+ console.log(`Battery ${this.applianceId}: handling SetStorageChargeLimitV1 (chargeLimitW=${msg.data.chargeLimitW})`);
1235
+ // Read current state to get wChaMax for percentage conversion
1236
+ const controls = await this.getBatteryControls();
1237
+ console.log(`Battery ${this.applianceId}: current state - inWRte=${controls?.inWRte}, wChaMax=${controls?.wChaMax}`);
1238
+ if (!controls?.wChaMax) {
1239
+ this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to read wChaMax for charge limit conversion');
1240
+ return;
1241
+ }
1242
+ // Convert watts to percentage of WChaMax, clamped to 0-100%
1243
+ const chargeLimitPercent = Math.min(100, Math.max(0, (msg.data.chargeLimitW / controls.wChaMax) * 100));
1244
+ console.log(`Battery ${this.applianceId}: calculated charge limit: ${chargeLimitPercent.toFixed(1)}% (${msg.data.chargeLimitW}W / ${controls.wChaMax}W)`);
1245
+ // Set charge limit (Register 13: inWRte)
1246
+ const success = await this.writeBatteryControls({ inWRte: chargeLimitPercent });
1247
+ if (!success) {
1248
+ this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to set charge limit');
1249
+ return;
1250
+ }
1251
+ this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted);
1252
+ }
1222
1253
  }
1223
1254
  exports.SunspecBattery = SunspecBattery;
1224
1255
  /**
@@ -265,6 +265,7 @@ export declare class SunspecBattery extends BaseSunspecDevice {
265
265
  private handleStartGridCharge;
266
266
  private handleStopGridCharge;
267
267
  private handleSetDischargeLimit;
268
+ private handleSetChargeLimit;
268
269
  }
269
270
  /**
270
271
  * Sunspec Meter implementation
@@ -255,6 +255,7 @@ var SunspecBatteryCapability;
255
255
  (function (SunspecBatteryCapability) {
256
256
  SunspecBatteryCapability["GridCharging"] = "grid-charging";
257
257
  SunspecBatteryCapability["DischargeLimit"] = "discharge-limit";
258
+ SunspecBatteryCapability["ChargeLimit"] = "charge-limit";
258
259
  })(SunspecBatteryCapability || (exports.SunspecBatteryCapability = SunspecBatteryCapability = {}));
259
260
  var SunspecMeterCapability;
260
261
  (function (SunspecMeterCapability) {
@@ -613,7 +613,8 @@ export declare enum SunspecInverterCapability {
613
613
  }
614
614
  export declare enum SunspecBatteryCapability {
615
615
  GridCharging = "grid-charging",
616
- DischargeLimit = "discharge-limit"
616
+ DischargeLimit = "discharge-limit",
617
+ ChargeLimit = "charge-limit"
617
618
  }
618
619
  export declare enum SunspecMeterCapability {
619
620
  }
@@ -476,10 +476,21 @@ class SunspecModbusClient {
476
476
  const maxAddress = 50000; // Safety limit
477
477
  let currentAddress = 0;
478
478
  console.log(`Starting Sunspec model discovery for unit ${unitId}...`);
479
+ // Probe for the SunS identifier first. A missing identifier means the unit ID isn't a
480
+ // SunSpec device — common during unit-ID scans — and is logged at debug level so probes
481
+ // don't surface as errors. Mid-scan failures below remain at error level since they
482
+ // indicate a partial / malformed SunSpec map.
483
+ let addressInfo;
484
+ try {
485
+ addressInfo = await this.detectSunspecBaseAddress(unitId, customBaseAddress);
486
+ }
487
+ catch (error) {
488
+ console.debug(`No SunSpec device at unit ${unitId}: ${error}`);
489
+ console.log(`Discovery complete for unit ${unitId}. Found 0 models: []`);
490
+ return models;
491
+ }
492
+ currentAddress = addressInfo.nextAddress;
479
493
  try {
480
- // Detect the base address and addressing mode
481
- const addressInfo = await this.detectSunspecBaseAddress(unitId, customBaseAddress);
482
- currentAddress = addressInfo.nextAddress;
483
494
  while (currentAddress < maxAddress) {
484
495
  const buffer = await instance.readHoldingRegisters(currentAddress, 2);
485
496
  const modelData = [buffer.readUInt16BE(0), buffer.readUInt16BE(2)];
@@ -1961,7 +1972,9 @@ class SunspecModbusClient {
1961
1972
  async readCommonBlock(unitId) {
1962
1973
  const model = this.findModel(unitId, sunspec_interfaces_js_1.SunspecModelId.Common);
1963
1974
  if (!model) {
1964
- console.error(`Common block model not found on unit ${unitId}`);
1975
+ // Common (model 1) is the SunSpec discovery anchor — its absence simply means this
1976
+ // unit isn't SunSpec. Callers iterating unit IDs already check the null return.
1977
+ console.debug(`Common block model not found on unit ${unitId}`);
1965
1978
  return null;
1966
1979
  }
1967
1980
  console.log(`Reading Common Block - Model address: ${model.address} (unit ${unitId})`);
@@ -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.64';
12
+ exports.SDK_VERSION = '0.0.66';
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.64";
8
+ export declare const SDK_VERSION = "0.0.66";
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
@@ -265,6 +265,7 @@ export declare class SunspecBattery extends BaseSunspecDevice {
265
265
  private handleStartGridCharge;
266
266
  private handleStopGridCharge;
267
267
  private handleSetDischargeLimit;
268
+ private handleSetChargeLimit;
268
269
  }
269
270
  /**
270
271
  * Sunspec Meter implementation
@@ -718,6 +718,9 @@ export class SunspecBattery extends BaseSunspecDevice {
718
718
  if (batteryData?.chaGriSet !== undefined) {
719
719
  features.push(EnyoBatteryFeature.GridCharging);
720
720
  }
721
+ if (batteryData?.wChaMax !== undefined) {
722
+ features.push(EnyoBatteryFeature.ChargeLimitation);
723
+ }
721
724
  // Create or update appliance (skip if an existing appliance was provided)
722
725
  if (!this.applianceId) {
723
726
  try {
@@ -1091,7 +1094,8 @@ export class SunspecBattery extends BaseSunspecDevice {
1091
1094
  this.dataBusListenerId = this.dataBus.listenForMessages([
1092
1095
  EnyoDataBusMessageEnum.StartStorageGridChargeV1,
1093
1096
  EnyoDataBusMessageEnum.StopStorageGridChargeV1,
1094
- EnyoDataBusMessageEnum.SetStorageDischargeLimitV1
1097
+ EnyoDataBusMessageEnum.SetStorageDischargeLimitV1,
1098
+ EnyoDataBusMessageEnum.SetStorageChargeLimitV1
1095
1099
  ], (entry) => this.handleStorageCommand(entry));
1096
1100
  console.log(`Battery ${this.applianceId}: started data bus listening (listener ${this.dataBusListenerId})`);
1097
1101
  }
@@ -1122,6 +1126,9 @@ export class SunspecBattery extends BaseSunspecDevice {
1122
1126
  case EnyoDataBusMessageEnum.SetStorageDischargeLimitV1:
1123
1127
  await this.handleSetDischargeLimit(entry);
1124
1128
  break;
1129
+ case EnyoDataBusMessageEnum.SetStorageChargeLimitV1:
1130
+ await this.handleSetChargeLimit(entry);
1131
+ break;
1125
1132
  }
1126
1133
  }
1127
1134
  catch (error) {
@@ -1214,6 +1221,30 @@ export class SunspecBattery extends BaseSunspecDevice {
1214
1221
  }
1215
1222
  this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
1216
1223
  }
1224
+ async handleSetChargeLimit(msg) {
1225
+ if (!this.isConnected() || !this.applianceId) {
1226
+ this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.NotSupported);
1227
+ return;
1228
+ }
1229
+ console.log(`Battery ${this.applianceId}: handling SetStorageChargeLimitV1 (chargeLimitW=${msg.data.chargeLimitW})`);
1230
+ // Read current state to get wChaMax for percentage conversion
1231
+ const controls = await this.getBatteryControls();
1232
+ console.log(`Battery ${this.applianceId}: current state - inWRte=${controls?.inWRte}, wChaMax=${controls?.wChaMax}`);
1233
+ if (!controls?.wChaMax) {
1234
+ this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to read wChaMax for charge limit conversion');
1235
+ return;
1236
+ }
1237
+ // Convert watts to percentage of WChaMax, clamped to 0-100%
1238
+ const chargeLimitPercent = Math.min(100, Math.max(0, (msg.data.chargeLimitW / controls.wChaMax) * 100));
1239
+ console.log(`Battery ${this.applianceId}: calculated charge limit: ${chargeLimitPercent.toFixed(1)}% (${msg.data.chargeLimitW}W / ${controls.wChaMax}W)`);
1240
+ // Set charge limit (Register 13: inWRte)
1241
+ const success = await this.writeBatteryControls({ inWRte: chargeLimitPercent });
1242
+ if (!success) {
1243
+ this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to set charge limit');
1244
+ return;
1245
+ }
1246
+ this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
1247
+ }
1217
1248
  }
1218
1249
  /**
1219
1250
  * Sunspec Meter implementation
@@ -613,7 +613,8 @@ export declare enum SunspecInverterCapability {
613
613
  }
614
614
  export declare enum SunspecBatteryCapability {
615
615
  GridCharging = "grid-charging",
616
- DischargeLimit = "discharge-limit"
616
+ DischargeLimit = "discharge-limit",
617
+ ChargeLimit = "charge-limit"
617
618
  }
618
619
  export declare enum SunspecMeterCapability {
619
620
  }
@@ -252,6 +252,7 @@ export var SunspecBatteryCapability;
252
252
  (function (SunspecBatteryCapability) {
253
253
  SunspecBatteryCapability["GridCharging"] = "grid-charging";
254
254
  SunspecBatteryCapability["DischargeLimit"] = "discharge-limit";
255
+ SunspecBatteryCapability["ChargeLimit"] = "charge-limit";
255
256
  })(SunspecBatteryCapability || (SunspecBatteryCapability = {}));
256
257
  export var SunspecMeterCapability;
257
258
  (function (SunspecMeterCapability) {
@@ -471,10 +471,21 @@ export class SunspecModbusClient {
471
471
  const maxAddress = 50000; // Safety limit
472
472
  let currentAddress = 0;
473
473
  console.log(`Starting Sunspec model discovery for unit ${unitId}...`);
474
+ // Probe for the SunS identifier first. A missing identifier means the unit ID isn't a
475
+ // SunSpec device — common during unit-ID scans — and is logged at debug level so probes
476
+ // don't surface as errors. Mid-scan failures below remain at error level since they
477
+ // indicate a partial / malformed SunSpec map.
478
+ let addressInfo;
479
+ try {
480
+ addressInfo = await this.detectSunspecBaseAddress(unitId, customBaseAddress);
481
+ }
482
+ catch (error) {
483
+ console.debug(`No SunSpec device at unit ${unitId}: ${error}`);
484
+ console.log(`Discovery complete for unit ${unitId}. Found 0 models: []`);
485
+ return models;
486
+ }
487
+ currentAddress = addressInfo.nextAddress;
474
488
  try {
475
- // Detect the base address and addressing mode
476
- const addressInfo = await this.detectSunspecBaseAddress(unitId, customBaseAddress);
477
- currentAddress = addressInfo.nextAddress;
478
489
  while (currentAddress < maxAddress) {
479
490
  const buffer = await instance.readHoldingRegisters(currentAddress, 2);
480
491
  const modelData = [buffer.readUInt16BE(0), buffer.readUInt16BE(2)];
@@ -1956,7 +1967,9 @@ export class SunspecModbusClient {
1956
1967
  async readCommonBlock(unitId) {
1957
1968
  const model = this.findModel(unitId, SunspecModelId.Common);
1958
1969
  if (!model) {
1959
- console.error(`Common block model not found on unit ${unitId}`);
1970
+ // Common (model 1) is the SunSpec discovery anchor — its absence simply means this
1971
+ // unit isn't SunSpec. Callers iterating unit IDs already check the null return.
1972
+ console.debug(`Common block model not found on unit ${unitId}`);
1960
1973
  return null;
1961
1974
  }
1962
1975
  console.log(`Reading Common Block - Model address: ${model.address} (unit ${unitId})`);
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.64";
8
+ export declare const SDK_VERSION = "0.0.66";
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.64';
8
+ export const SDK_VERSION = '0.0.66';
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.64",
3
+ "version": "0.0.66",
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.125"
40
+ "@enyo-energy/energy-app-sdk": "^0.0.131"
41
41
  },
42
42
  "volta": {
43
43
  "node": "22.17.0"