@enyo-energy/sunspec-sdk 0.0.56 → 0.0.57

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.
@@ -1206,10 +1206,14 @@ class SunspecMeter extends BaseSunspecDevice {
1206
1206
  async connect() {
1207
1207
  // Connect with specific unit ID for meter
1208
1208
  await this.ensureConnected();
1209
- // Check if meter models exist
1209
+ // Check if meter models exist (int+SF: 201/203/204, float: 211/212/213/214)
1210
1210
  const hasMeter = this.sunspecClient.findModel(this.unitId, sunspec_interfaces_js_1.SunspecModelId.Meter3Phase) !== undefined ||
1211
1211
  this.sunspecClient.findModel(this.unitId, sunspec_interfaces_js_1.SunspecModelId.MeterWye) !== undefined ||
1212
- this.sunspecClient.findModel(this.unitId, sunspec_interfaces_js_1.SunspecModelId.MeterSinglePhase) !== undefined;
1212
+ this.sunspecClient.findModel(this.unitId, sunspec_interfaces_js_1.SunspecModelId.MeterSinglePhase) !== undefined ||
1213
+ this.sunspecClient.findModel(this.unitId, sunspec_interfaces_js_1.SunspecModelId.Meter3PhaseWyeFloat) !== undefined ||
1214
+ this.sunspecClient.findModel(this.unitId, sunspec_interfaces_js_1.SunspecModelId.Meter3PhaseDeltaFloat) !== undefined ||
1215
+ this.sunspecClient.findModel(this.unitId, sunspec_interfaces_js_1.SunspecModelId.MeterSplitPhaseFloat) !== undefined ||
1216
+ this.sunspecClient.findModel(this.unitId, sunspec_interfaces_js_1.SunspecModelId.MeterSinglePhaseFloat) !== undefined;
1213
1217
  if (!hasMeter) {
1214
1218
  throw new Error('No meter model found in device');
1215
1219
  }
@@ -30,6 +30,10 @@ var SunspecModelId;
30
30
  SunspecModelId[SunspecModelId["MeterSinglePhase"] = 201] = "MeterSinglePhase";
31
31
  SunspecModelId[SunspecModelId["Meter3Phase"] = 203] = "Meter3Phase";
32
32
  SunspecModelId[SunspecModelId["MeterWye"] = 204] = "MeterWye";
33
+ SunspecModelId[SunspecModelId["MeterSinglePhaseFloat"] = 211] = "MeterSinglePhaseFloat";
34
+ SunspecModelId[SunspecModelId["MeterSplitPhaseFloat"] = 212] = "MeterSplitPhaseFloat";
35
+ SunspecModelId[SunspecModelId["Meter3PhaseWyeFloat"] = 213] = "Meter3PhaseWyeFloat";
36
+ SunspecModelId[SunspecModelId["Meter3PhaseDeltaFloat"] = 214] = "Meter3PhaseDeltaFloat";
33
37
  SunspecModelId[SunspecModelId["Nameplate"] = 120] = "Nameplate";
34
38
  SunspecModelId[SunspecModelId["Settings"] = 121] = "Settings";
35
39
  SunspecModelId[SunspecModelId["Status"] = 122] = "Status";
@@ -40,6 +40,10 @@ export declare enum SunspecModelId {
40
40
  MeterSinglePhase = 201,
41
41
  Meter3Phase = 203,
42
42
  MeterWye = 204,
43
+ MeterSinglePhaseFloat = 211,
44
+ MeterSplitPhaseFloat = 212,
45
+ Meter3PhaseWyeFloat = 213,
46
+ Meter3PhaseDeltaFloat = 214,
43
47
  Nameplate = 120,
44
48
  Settings = 121,
45
49
  Status = 122,
@@ -472,10 +476,12 @@ export interface SunspecBatteryBaseData extends SunspecBlock {
472
476
  setInvState?: number;
473
477
  }
474
478
  /**
475
- * Meter data structure
479
+ * Meter data structure. Covers SunSpec int+SF meter models 201/203/204 and float variants 211/212/213/214.
480
+ * The per-field offset comments below describe the int+SF (201/203/204) layout; float variants use
481
+ * 32-bit IEEE 754 floats (no scale factors) at different offsets — see the float reader implementation.
476
482
  */
477
483
  export interface SunspecMeterData extends SunspecBlock {
478
- blockNumber: 201 | 203 | 204;
484
+ blockNumber: 201 | 203 | 204 | 211 | 212 | 213 | 214;
479
485
  totalPower?: number;
480
486
  phaseAPower?: number;
481
487
  phaseBPower?: number;
@@ -1736,9 +1736,19 @@ class SunspecModbusClient {
1736
1736
  }
1737
1737
  }
1738
1738
  /**
1739
- * Read meter data from Model 201 (Single Phase) / Model 203 (Three Phase) / Model 204 (Split Phase)
1739
+ * Read meter data. Detects which SunSpec meter model the device exposes
1740
+ * int+SF (201/203/204) or float (211/212/213/214) — by checking the discovered
1741
+ * model directory, and dispatches to the appropriate decoder.
1740
1742
  */
1741
1743
  async readMeterData(unitId) {
1744
+ const floatIds = [213, 214, 212, 211];
1745
+ for (const id of floatIds) {
1746
+ const model = this.findModel(unitId, id);
1747
+ if (model) {
1748
+ console.debug(`Using meter Model ${id} at address ${model.address} (length ${model.length}) on unit ${unitId}`);
1749
+ return this.readFloatMeterData(unitId, model, id);
1750
+ }
1751
+ }
1742
1752
  let model = this.findModel(unitId, sunspec_interfaces_js_1.SunspecModelId.Meter3Phase);
1743
1753
  if (!model) {
1744
1754
  model = this.findModel(unitId, sunspec_interfaces_js_1.SunspecModelId.MeterWye);
@@ -1829,6 +1839,48 @@ class SunspecModbusClient {
1829
1839
  return null;
1830
1840
  }
1831
1841
  }
1842
+ /**
1843
+ * Read meter data from a float-variant model: 211 (single-phase), 212 (split-phase),
1844
+ * 213 (3-phase Wye), or 214 (3-phase Delta).
1845
+ *
1846
+ * All four models share the same SunSpec register layout — measurement fields are 32-bit IEEE 754
1847
+ * floats (no scale factors). Phase scope differs by model: 211 populates phase A only;
1848
+ * 212 phases A+B; 213/214 all three. Unpopulated phase fields read NaN and are returned as undefined.
1849
+ *
1850
+ * Per SunSpec spec, with Length = 124 registers:
1851
+ * 2-3 A (total current), 4-5 AphA, 6-7 AphB, 8-9 AphC
1852
+ * 10-11 PhV (L-N avg), 12-13 PhVphA, 14-15 PhVphB, 16-17 PhVphC
1853
+ * 18-19 PPV (L-L avg), 20-21 PPVphAB, 22-23 PPVphBC, 24-25 PPVphCA
1854
+ * 26-27 Hz
1855
+ * 28-29 W (total real power), 30-31 WphA, 32-33 WphB, 34-35 WphC
1856
+ * 60-61 TotWhExp, 68-69 TotWhImp
1857
+ */
1858
+ async readFloatMeterData(unitId, model, blockNumber) {
1859
+ try {
1860
+ console.debug(`Reading Float Meter Data from Model ${blockNumber} at base address: ${model.address} (unit ${unitId})`);
1861
+ const buffer = await this.readModelBlock(unitId, model);
1862
+ const data = {
1863
+ blockNumber,
1864
+ blockAddress: model.address,
1865
+ blockLength: model.length,
1866
+ current: this.extractFloat32OrUndefined(buffer, 2, blockNumber, 'AC Current'),
1867
+ voltage: this.extractFloat32OrUndefined(buffer, 10, blockNumber, 'Phase Voltage (L-N avg)'),
1868
+ frequency: this.extractFloat32OrUndefined(buffer, 26, blockNumber, 'Frequency'),
1869
+ totalPower: this.extractFloat32OrUndefined(buffer, 28, blockNumber, 'Total Real Power'),
1870
+ phaseAPower: this.extractFloat32OrUndefined(buffer, 30, blockNumber, 'Phase A Real Power'),
1871
+ phaseBPower: this.extractFloat32OrUndefined(buffer, 32, blockNumber, 'Phase B Real Power'),
1872
+ phaseCPower: this.extractFloat32OrUndefined(buffer, 34, blockNumber, 'Phase C Real Power'),
1873
+ exportedEnergy: this.extractFloat32OrUndefined(buffer, 60, blockNumber, 'Total Wh Exported'),
1874
+ importedEnergy: this.extractFloat32OrUndefined(buffer, 68, blockNumber, 'Total Wh Imported'),
1875
+ };
1876
+ console.debug(`[Model ${blockNumber}] Float Meter Data:`, data);
1877
+ return data;
1878
+ }
1879
+ catch (error) {
1880
+ console.error(`Error reading float meter data (Model ${blockNumber}): ${error}`);
1881
+ return null;
1882
+ }
1883
+ }
1832
1884
  /**
1833
1885
  * Read common block data (Model 1)
1834
1886
  */
@@ -314,9 +314,28 @@ export declare class SunspecModbusClient {
314
314
  */
315
315
  readBatteryControls(unitId: number): Promise<SunspecBatteryControls | null>;
316
316
  /**
317
- * Read meter data from Model 201 (Single Phase) / Model 203 (Three Phase) / Model 204 (Split Phase)
317
+ * Read meter data. Detects which SunSpec meter model the device exposes
318
+ * int+SF (201/203/204) or float (211/212/213/214) — by checking the discovered
319
+ * model directory, and dispatches to the appropriate decoder.
318
320
  */
319
321
  readMeterData(unitId: number): Promise<SunspecMeterData | null>;
322
+ /**
323
+ * Read meter data from a float-variant model: 211 (single-phase), 212 (split-phase),
324
+ * 213 (3-phase Wye), or 214 (3-phase Delta).
325
+ *
326
+ * All four models share the same SunSpec register layout — measurement fields are 32-bit IEEE 754
327
+ * floats (no scale factors). Phase scope differs by model: 211 populates phase A only;
328
+ * 212 phases A+B; 213/214 all three. Unpopulated phase fields read NaN and are returned as undefined.
329
+ *
330
+ * Per SunSpec spec, with Length = 124 registers:
331
+ * 2-3 A (total current), 4-5 AphA, 6-7 AphB, 8-9 AphC
332
+ * 10-11 PhV (L-N avg), 12-13 PhVphA, 14-15 PhVphB, 16-17 PhVphC
333
+ * 18-19 PPV (L-L avg), 20-21 PPVphAB, 22-23 PPVphBC, 24-25 PPVphCA
334
+ * 26-27 Hz
335
+ * 28-29 W (total real power), 30-31 WphA, 32-33 WphB, 34-35 WphC
336
+ * 60-61 TotWhExp, 68-69 TotWhImp
337
+ */
338
+ private readFloatMeterData;
320
339
  /**
321
340
  * Read common block data (Model 1)
322
341
  */
@@ -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.56';
12
+ exports.SDK_VERSION = '0.0.57';
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.56";
8
+ export declare const SDK_VERSION = "0.0.57";
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
@@ -1200,10 +1200,14 @@ export class SunspecMeter extends BaseSunspecDevice {
1200
1200
  async connect() {
1201
1201
  // Connect with specific unit ID for meter
1202
1202
  await this.ensureConnected();
1203
- // Check if meter models exist
1203
+ // Check if meter models exist (int+SF: 201/203/204, float: 211/212/213/214)
1204
1204
  const hasMeter = this.sunspecClient.findModel(this.unitId, SunspecModelId.Meter3Phase) !== undefined ||
1205
1205
  this.sunspecClient.findModel(this.unitId, SunspecModelId.MeterWye) !== undefined ||
1206
- this.sunspecClient.findModel(this.unitId, SunspecModelId.MeterSinglePhase) !== undefined;
1206
+ this.sunspecClient.findModel(this.unitId, SunspecModelId.MeterSinglePhase) !== undefined ||
1207
+ this.sunspecClient.findModel(this.unitId, SunspecModelId.Meter3PhaseWyeFloat) !== undefined ||
1208
+ this.sunspecClient.findModel(this.unitId, SunspecModelId.Meter3PhaseDeltaFloat) !== undefined ||
1209
+ this.sunspecClient.findModel(this.unitId, SunspecModelId.MeterSplitPhaseFloat) !== undefined ||
1210
+ this.sunspecClient.findModel(this.unitId, SunspecModelId.MeterSinglePhaseFloat) !== undefined;
1207
1211
  if (!hasMeter) {
1208
1212
  throw new Error('No meter model found in device');
1209
1213
  }
@@ -40,6 +40,10 @@ export declare enum SunspecModelId {
40
40
  MeterSinglePhase = 201,
41
41
  Meter3Phase = 203,
42
42
  MeterWye = 204,
43
+ MeterSinglePhaseFloat = 211,
44
+ MeterSplitPhaseFloat = 212,
45
+ Meter3PhaseWyeFloat = 213,
46
+ Meter3PhaseDeltaFloat = 214,
43
47
  Nameplate = 120,
44
48
  Settings = 121,
45
49
  Status = 122,
@@ -472,10 +476,12 @@ export interface SunspecBatteryBaseData extends SunspecBlock {
472
476
  setInvState?: number;
473
477
  }
474
478
  /**
475
- * Meter data structure
479
+ * Meter data structure. Covers SunSpec int+SF meter models 201/203/204 and float variants 211/212/213/214.
480
+ * The per-field offset comments below describe the int+SF (201/203/204) layout; float variants use
481
+ * 32-bit IEEE 754 floats (no scale factors) at different offsets — see the float reader implementation.
476
482
  */
477
483
  export interface SunspecMeterData extends SunspecBlock {
478
- blockNumber: 201 | 203 | 204;
484
+ blockNumber: 201 | 203 | 204 | 211 | 212 | 213 | 214;
479
485
  totalPower?: number;
480
486
  phaseAPower?: number;
481
487
  phaseBPower?: number;
@@ -27,6 +27,10 @@ export var SunspecModelId;
27
27
  SunspecModelId[SunspecModelId["MeterSinglePhase"] = 201] = "MeterSinglePhase";
28
28
  SunspecModelId[SunspecModelId["Meter3Phase"] = 203] = "Meter3Phase";
29
29
  SunspecModelId[SunspecModelId["MeterWye"] = 204] = "MeterWye";
30
+ SunspecModelId[SunspecModelId["MeterSinglePhaseFloat"] = 211] = "MeterSinglePhaseFloat";
31
+ SunspecModelId[SunspecModelId["MeterSplitPhaseFloat"] = 212] = "MeterSplitPhaseFloat";
32
+ SunspecModelId[SunspecModelId["Meter3PhaseWyeFloat"] = 213] = "Meter3PhaseWyeFloat";
33
+ SunspecModelId[SunspecModelId["Meter3PhaseDeltaFloat"] = 214] = "Meter3PhaseDeltaFloat";
30
34
  SunspecModelId[SunspecModelId["Nameplate"] = 120] = "Nameplate";
31
35
  SunspecModelId[SunspecModelId["Settings"] = 121] = "Settings";
32
36
  SunspecModelId[SunspecModelId["Status"] = 122] = "Status";
@@ -314,9 +314,28 @@ export declare class SunspecModbusClient {
314
314
  */
315
315
  readBatteryControls(unitId: number): Promise<SunspecBatteryControls | null>;
316
316
  /**
317
- * Read meter data from Model 201 (Single Phase) / Model 203 (Three Phase) / Model 204 (Split Phase)
317
+ * Read meter data. Detects which SunSpec meter model the device exposes
318
+ * int+SF (201/203/204) or float (211/212/213/214) — by checking the discovered
319
+ * model directory, and dispatches to the appropriate decoder.
318
320
  */
319
321
  readMeterData(unitId: number): Promise<SunspecMeterData | null>;
322
+ /**
323
+ * Read meter data from a float-variant model: 211 (single-phase), 212 (split-phase),
324
+ * 213 (3-phase Wye), or 214 (3-phase Delta).
325
+ *
326
+ * All four models share the same SunSpec register layout — measurement fields are 32-bit IEEE 754
327
+ * floats (no scale factors). Phase scope differs by model: 211 populates phase A only;
328
+ * 212 phases A+B; 213/214 all three. Unpopulated phase fields read NaN and are returned as undefined.
329
+ *
330
+ * Per SunSpec spec, with Length = 124 registers:
331
+ * 2-3 A (total current), 4-5 AphA, 6-7 AphB, 8-9 AphC
332
+ * 10-11 PhV (L-N avg), 12-13 PhVphA, 14-15 PhVphB, 16-17 PhVphC
333
+ * 18-19 PPV (L-L avg), 20-21 PPVphAB, 22-23 PPVphBC, 24-25 PPVphCA
334
+ * 26-27 Hz
335
+ * 28-29 W (total real power), 30-31 WphA, 32-33 WphB, 34-35 WphC
336
+ * 60-61 TotWhExp, 68-69 TotWhImp
337
+ */
338
+ private readFloatMeterData;
320
339
  /**
321
340
  * Read common block data (Model 1)
322
341
  */
@@ -1731,9 +1731,19 @@ export class SunspecModbusClient {
1731
1731
  }
1732
1732
  }
1733
1733
  /**
1734
- * Read meter data from Model 201 (Single Phase) / Model 203 (Three Phase) / Model 204 (Split Phase)
1734
+ * Read meter data. Detects which SunSpec meter model the device exposes
1735
+ * int+SF (201/203/204) or float (211/212/213/214) — by checking the discovered
1736
+ * model directory, and dispatches to the appropriate decoder.
1735
1737
  */
1736
1738
  async readMeterData(unitId) {
1739
+ const floatIds = [213, 214, 212, 211];
1740
+ for (const id of floatIds) {
1741
+ const model = this.findModel(unitId, id);
1742
+ if (model) {
1743
+ console.debug(`Using meter Model ${id} at address ${model.address} (length ${model.length}) on unit ${unitId}`);
1744
+ return this.readFloatMeterData(unitId, model, id);
1745
+ }
1746
+ }
1737
1747
  let model = this.findModel(unitId, SunspecModelId.Meter3Phase);
1738
1748
  if (!model) {
1739
1749
  model = this.findModel(unitId, SunspecModelId.MeterWye);
@@ -1824,6 +1834,48 @@ export class SunspecModbusClient {
1824
1834
  return null;
1825
1835
  }
1826
1836
  }
1837
+ /**
1838
+ * Read meter data from a float-variant model: 211 (single-phase), 212 (split-phase),
1839
+ * 213 (3-phase Wye), or 214 (3-phase Delta).
1840
+ *
1841
+ * All four models share the same SunSpec register layout — measurement fields are 32-bit IEEE 754
1842
+ * floats (no scale factors). Phase scope differs by model: 211 populates phase A only;
1843
+ * 212 phases A+B; 213/214 all three. Unpopulated phase fields read NaN and are returned as undefined.
1844
+ *
1845
+ * Per SunSpec spec, with Length = 124 registers:
1846
+ * 2-3 A (total current), 4-5 AphA, 6-7 AphB, 8-9 AphC
1847
+ * 10-11 PhV (L-N avg), 12-13 PhVphA, 14-15 PhVphB, 16-17 PhVphC
1848
+ * 18-19 PPV (L-L avg), 20-21 PPVphAB, 22-23 PPVphBC, 24-25 PPVphCA
1849
+ * 26-27 Hz
1850
+ * 28-29 W (total real power), 30-31 WphA, 32-33 WphB, 34-35 WphC
1851
+ * 60-61 TotWhExp, 68-69 TotWhImp
1852
+ */
1853
+ async readFloatMeterData(unitId, model, blockNumber) {
1854
+ try {
1855
+ console.debug(`Reading Float Meter Data from Model ${blockNumber} at base address: ${model.address} (unit ${unitId})`);
1856
+ const buffer = await this.readModelBlock(unitId, model);
1857
+ const data = {
1858
+ blockNumber,
1859
+ blockAddress: model.address,
1860
+ blockLength: model.length,
1861
+ current: this.extractFloat32OrUndefined(buffer, 2, blockNumber, 'AC Current'),
1862
+ voltage: this.extractFloat32OrUndefined(buffer, 10, blockNumber, 'Phase Voltage (L-N avg)'),
1863
+ frequency: this.extractFloat32OrUndefined(buffer, 26, blockNumber, 'Frequency'),
1864
+ totalPower: this.extractFloat32OrUndefined(buffer, 28, blockNumber, 'Total Real Power'),
1865
+ phaseAPower: this.extractFloat32OrUndefined(buffer, 30, blockNumber, 'Phase A Real Power'),
1866
+ phaseBPower: this.extractFloat32OrUndefined(buffer, 32, blockNumber, 'Phase B Real Power'),
1867
+ phaseCPower: this.extractFloat32OrUndefined(buffer, 34, blockNumber, 'Phase C Real Power'),
1868
+ exportedEnergy: this.extractFloat32OrUndefined(buffer, 60, blockNumber, 'Total Wh Exported'),
1869
+ importedEnergy: this.extractFloat32OrUndefined(buffer, 68, blockNumber, 'Total Wh Imported'),
1870
+ };
1871
+ console.debug(`[Model ${blockNumber}] Float Meter Data:`, data);
1872
+ return data;
1873
+ }
1874
+ catch (error) {
1875
+ console.error(`Error reading float meter data (Model ${blockNumber}): ${error}`);
1876
+ return null;
1877
+ }
1878
+ }
1827
1879
  /**
1828
1880
  * Read common block data (Model 1)
1829
1881
  */
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.56";
8
+ export declare const SDK_VERSION = "0.0.57";
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.56';
8
+ export const SDK_VERSION = '0.0.57';
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.56",
3
+ "version": "0.0.57",
4
4
  "description": "enyo Energy Sunspec SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",