@enyo-energy/sunspec-sdk 0.0.5 → 0.0.6

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.
@@ -113,12 +113,12 @@ class SunspecInverter extends BaseSunspecDevice {
113
113
  timestampIso: timestamp.toISOString(),
114
114
  resolution,
115
115
  data: {
116
- pvPowerW: inverterData.acPower || 0,
117
- activePowerLimitationW: inverterData.acPower || 0, // Using AC power as default
116
+ pvPowerW: inverterData.dcPower || 0, // Use DC power for PV power
117
+ activePowerLimitationW: inverterData.acPower || 0,
118
118
  state: this.mapOperatingState(inverterData.operatingState),
119
119
  voltageL1: inverterData.voltageAN || 0,
120
- voltageL2: inverterData.voltageBN,
121
- voltageL3: inverterData.voltageCN,
120
+ voltageL2: inverterData.voltageBN ?? undefined, // Use null if undefined (unimplemented phase)
121
+ voltageL3: inverterData.voltageCN ?? undefined, // Use null if undefined (unimplemented phase)
122
122
  strings: this.mapMPPTToStrings(mpptDataList)
123
123
  }
124
124
  };
@@ -152,11 +152,14 @@ class SunspecInverter extends BaseSunspecDevice {
152
152
  mapMPPTToStrings(mpptDataList) {
153
153
  const result = [];
154
154
  mpptDataList.forEach((mppt, index) => {
155
- result.push({
156
- index: index + 1,
157
- voltage: mppt.dcVoltage,
158
- powerW: mppt.dcPower
159
- });
155
+ // Only include strings with valid data
156
+ if (mppt.dcVoltage !== undefined || mppt.dcPower !== undefined) {
157
+ result.push({
158
+ index: index + 1,
159
+ voltage: mppt.dcVoltage ?? undefined, // Use null if undefined
160
+ powerW: mppt.dcPower ?? 0 // Default to 0 if undefined
161
+ });
162
+ }
160
163
  });
161
164
  return result;
162
165
  }
@@ -2,7 +2,7 @@ import { ApplianceManager, EnergyApp } from "@enyo-energy/energy-app-sdk";
2
2
  import { EnyoApplianceName } from "@enyo-energy/energy-app-sdk/dist/types/enyo-appliance.js";
3
3
  import { EnyoNetworkDevice } from "@enyo-energy/energy-app-sdk/dist/types/enyo-network-device.js";
4
4
  import { SunspecModbusClient } from "./sunspec-modbus-client.cjs";
5
- import { EnyoDataBusMessage } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
5
+ import { EnyoDataBusMessage, EnyoDataBusMessageResolution } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
6
6
  /**
7
7
  * Base abstract class for all Sunspec devices
8
8
  */
@@ -27,7 +27,7 @@ export declare abstract class BaseSunspecDevice {
27
27
  /**
28
28
  * Update device data and return data bus messages
29
29
  */
30
- abstract readData(clockId: string, resolution: '10s' | '30s' | '1m' | '15m'): Promise<EnyoDataBusMessage[]>;
30
+ abstract readData(clockId: string, resolution: EnyoDataBusMessageResolution): Promise<EnyoDataBusMessage[]>;
31
31
  /**
32
32
  * Check if the device is connected
33
33
  */
@@ -83,5 +83,5 @@ export declare class SunspecMeter extends BaseSunspecDevice {
83
83
  /**
84
84
  * Update meter data and return data bus messages
85
85
  */
86
- readData(clockId: string, resolution: '10s' | '30s' | '1m' | '15m'): Promise<EnyoDataBusMessage[]>;
86
+ readData(clockId: string, resolution: EnyoDataBusMessageResolution): Promise<EnyoDataBusMessage[]>;
87
87
  }
@@ -189,6 +189,24 @@ class SunspecModbusClient {
189
189
  }
190
190
  return value;
191
191
  }
192
+ /**
193
+ * Check if a value is "unimplemented" according to Sunspec
194
+ * Returns true if the value represents an unimplemented/not applicable register
195
+ */
196
+ isUnimplementedValue(value, dataType = 'uint16') {
197
+ switch (dataType) {
198
+ case 'uint16':
199
+ return value === 0xFFFF || value === 65535;
200
+ case 'int16':
201
+ return value === 0x7FFF || value === 32767;
202
+ case 'uint32':
203
+ return value === 0xFFFFFFFF;
204
+ case 'int32':
205
+ return value === 0x7FFFFFFF;
206
+ default:
207
+ return false;
208
+ }
209
+ }
192
210
  /**
193
211
  * Helper to clean string values by removing null characters
194
212
  */
@@ -268,6 +286,15 @@ class SunspecModbusClient {
268
286
  // Read all scale factors first using fault-tolerant reader
269
287
  const scaleFactors = await this.readInverterScaleFactors(baseAddr);
270
288
  // Read values using fault-tolerant reader with proper data types
289
+ // Read raw voltage values to check for unimplemented phases
290
+ const voltageANRaw = await this.readRegisterValue(baseAddr + 10, 1, 'uint16');
291
+ const voltageBNRaw = await this.readRegisterValue(baseAddr + 11, 1, 'uint16');
292
+ const voltageCNRaw = await this.readRegisterValue(baseAddr + 12, 1, 'uint16');
293
+ console.log('Inverter Raw Voltage Values:', {
294
+ voltageANRaw: `0x${voltageANRaw.toString(16).toUpperCase()} (${voltageANRaw})`,
295
+ voltageBNRaw: `0x${voltageBNRaw.toString(16).toUpperCase()} (${voltageBNRaw})`,
296
+ voltageCNRaw: `0x${voltageCNRaw.toString(16).toUpperCase()} (${voltageCNRaw})`
297
+ });
271
298
  const data = {
272
299
  blockNumber: 103,
273
300
  blockAddress: model.address,
@@ -281,9 +308,10 @@ class SunspecModbusClient {
281
308
  voltageAB: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 7, 1, 'uint16'), scaleFactors.V_SF),
282
309
  voltageBC: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 8, 1, 'uint16'), scaleFactors.V_SF),
283
310
  voltageCA: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 9, 1, 'uint16'), scaleFactors.V_SF),
284
- voltageAN: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 10, 1, 'uint16'), scaleFactors.V_SF),
285
- voltageBN: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 11, 1, 'uint16'), scaleFactors.V_SF),
286
- voltageCN: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 12, 1, 'uint16'), scaleFactors.V_SF),
311
+ // Apply scale factor with unimplemented value checking
312
+ voltageAN: this.applyScaleFactor(voltageANRaw, scaleFactors.V_SF),
313
+ voltageBN: this.applyScaleFactor(voltageBNRaw, scaleFactors.V_SF),
314
+ voltageCN: this.applyScaleFactor(voltageCNRaw, scaleFactors.V_SF),
287
315
  // Power values - Offsets 14, 18, 20, 22
288
316
  acPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 14, 1, 'int16'), scaleFactors.W_SF),
289
317
  apparentPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 18, 1, 'uint16'), scaleFactors.VA_SF),
@@ -349,7 +377,7 @@ class SunspecModbusClient {
349
377
  * Read inverter scale factors
350
378
  */
351
379
  async readInverterScaleFactors(baseAddr) {
352
- return {
380
+ const scaleFactors = {
353
381
  A_SF: await this.readRegisterValue(baseAddr + 6, 1, 'int16'), // Offset 6
354
382
  V_SF: await this.readRegisterValue(baseAddr + 13, 1, 'int16'), // Offset 13
355
383
  W_SF: await this.readRegisterValue(baseAddr + 15, 1, 'int16'), // Offset 15
@@ -363,11 +391,23 @@ class SunspecModbusClient {
363
391
  DCW_SF: await this.readRegisterValue(baseAddr + 31, 1, 'int16'), // Offset 31
364
392
  Tmp_SF: await this.readRegisterValue(baseAddr + 36, 1, 'int16') // Offset 36
365
393
  };
394
+ console.log('Inverter Scale Factors:', JSON.stringify(scaleFactors, null, 2));
395
+ return scaleFactors;
366
396
  }
367
397
  /**
368
398
  * Apply scale factor to a value
399
+ * Returns undefined if the value is unimplemented or scale factor is out of range
369
400
  */
370
- applyScaleFactor(value, scaleFactor) {
401
+ applyScaleFactor(value, scaleFactor, dataType = 'uint16') {
402
+ // Check for unimplemented values
403
+ if (this.isUnimplementedValue(value, dataType)) {
404
+ return undefined;
405
+ }
406
+ // Validate scale factor is within reasonable range (-10 to +10)
407
+ if (Math.abs(scaleFactor) > 10) {
408
+ console.warn(`Scale factor ${scaleFactor} is outside reasonable range, clamping to ±10`);
409
+ scaleFactor = Math.max(-10, Math.min(10, scaleFactor));
410
+ }
371
411
  return value * Math.pow(10, scaleFactor);
372
412
  }
373
413
  /**
@@ -393,6 +433,28 @@ class SunspecModbusClient {
393
433
  DCWH_SF: await this.readRegisterValue(moduleAddr + 17, 1, 'int16'),
394
434
  Tmp_SF: await this.readRegisterValue(moduleAddr + 21, 1, 'int16')
395
435
  };
436
+ console.log(`MPPT Module ${moduleId} Scale Factors:`, JSON.stringify(scaleFactors, null, 2));
437
+ // Read raw values first
438
+ const dcCurrentRaw = await this.readRegisterValue(moduleAddr + 9, 1, 'uint16');
439
+ const dcVoltageRaw = await this.readRegisterValue(moduleAddr + 11, 1, 'uint16');
440
+ const dcPowerRaw = await this.readRegisterValue(moduleAddr + 13, 1, 'uint16');
441
+ const dcEnergyRaw = await this.readRegisterValue(moduleAddr + 15, 2, 'acc32');
442
+ const temperatureRaw = await this.readRegisterValue(moduleAddr + 20, 1, 'int16');
443
+ console.log(`MPPT Module ${moduleId} Raw Values:`, {
444
+ dcCurrentRaw: `0x${dcCurrentRaw.toString(16).toUpperCase()} (${dcCurrentRaw})`,
445
+ dcVoltageRaw: `0x${dcVoltageRaw.toString(16).toUpperCase()} (${dcVoltageRaw})`,
446
+ dcPowerRaw: `0x${dcPowerRaw.toString(16).toUpperCase()} (${dcPowerRaw})`,
447
+ dcEnergyRaw: `0x${dcEnergyRaw.toString(16).toUpperCase()} (${dcEnergyRaw})`,
448
+ temperatureRaw: `0x${temperatureRaw.toString(16).toUpperCase()} (${temperatureRaw})`
449
+ });
450
+ // Check if this module is actually implemented/connected
451
+ // If all key values are unimplemented, this module doesn't exist
452
+ if (this.isUnimplementedValue(dcCurrentRaw, 'uint16') &&
453
+ this.isUnimplementedValue(dcVoltageRaw, 'uint16') &&
454
+ this.isUnimplementedValue(dcPowerRaw, 'uint16')) {
455
+ console.log(`MPPT module ${moduleId} appears to be unconnected (all values are 0xFFFF)`);
456
+ return null;
457
+ }
396
458
  const data = {
397
459
  blockNumber: 160,
398
460
  blockAddress: model.address,
@@ -401,22 +463,24 @@ class SunspecModbusClient {
401
463
  // String ID - Offset 1 (8 registers for string)
402
464
  stringId: await this.readRegisterValue(moduleAddr + 1, 8, 'string'),
403
465
  // DC Current - Offset 9
404
- dcCurrent: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 9, 1, 'uint16'), scaleFactors.DCA_SF),
466
+ dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF),
405
467
  dcCurrentSF: scaleFactors.DCA_SF,
406
468
  // DC Voltage - Offset 11
407
- dcVoltage: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 11, 1, 'uint16'), scaleFactors.DCV_SF),
469
+ dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF),
408
470
  dcVoltageSF: scaleFactors.DCV_SF,
409
471
  // DC Power - Offset 13
410
- dcPower: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 13, 1, 'uint16'), scaleFactors.DCW_SF),
472
+ dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF),
411
473
  dcPowerSF: scaleFactors.DCW_SF,
412
474
  // DC Energy - Offset 15-16 (32-bit accumulator)
413
- dcEnergy: BigInt(await this.readRegisterValue(moduleAddr + 15, 2, 'acc32')) *
414
- BigInt(Math.pow(10, scaleFactors.DCWH_SF)),
475
+ // Only calculate if value is not unimplemented
476
+ dcEnergy: !this.isUnimplementedValue(dcEnergyRaw, 'uint32')
477
+ ? BigInt(dcEnergyRaw) * BigInt(Math.pow(10, scaleFactors.DCWH_SF))
478
+ : undefined,
415
479
  dcEnergySF: scaleFactors.DCWH_SF,
416
480
  // Timestamp - Offset 18-19 (32-bit)
417
481
  timestamp: await this.readRegisterValue(moduleAddr + 18, 2, 'uint32'),
418
482
  // Temperature - Offset 20
419
- temperature: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 20, 1, 'int16'), scaleFactors.Tmp_SF),
483
+ temperature: this.applyScaleFactor(temperatureRaw, scaleFactors.Tmp_SF, 'int16'),
420
484
  temperatureSF: scaleFactors.Tmp_SF,
421
485
  // Operating State - Offset 22
422
486
  operatingState: await this.readRegisterValue(moduleAddr + 22, 1, 'uint16'),
@@ -439,9 +503,19 @@ class SunspecModbusClient {
439
503
  const mpptData = [];
440
504
  // Try to read up to 4 MPPT strings (typical maximum)
441
505
  for (let i = 1; i <= 4; i++) {
442
- const data = await this.readMPPTData(i);
443
- if (data && data.dcCurrent !== undefined && data.dcCurrent > 0) {
444
- mpptData.push(data);
506
+ try {
507
+ const data = await this.readMPPTData(i);
508
+ // Only include if we got valid data (not null) and it has actual values
509
+ if (data &&
510
+ (data.dcCurrent !== undefined ||
511
+ data.dcVoltage !== undefined ||
512
+ data.dcPower !== undefined)) {
513
+ mpptData.push(data);
514
+ }
515
+ }
516
+ catch (error) {
517
+ console.debug(`Could not read MPPT module ${i}: ${error}`);
518
+ // Continue to try other modules
445
519
  }
446
520
  }
447
521
  return mpptData;
@@ -486,15 +560,31 @@ class SunspecModbusClient {
486
560
  return null;
487
561
  }
488
562
  const baseAddr = model.address + 2; // Skip ID and Length
563
+ console.log(`Reading Common Block - Model address: ${model.address}, Base address for data: ${baseAddr}`);
489
564
  try {
490
565
  // Read all strings using fault-tolerant reader with proper data type conversion
491
- const manufacturer = await this.readRegisterValue(baseAddr + 2, 16, 'string'); // Offset 2-17
492
- const modelName = await this.readRegisterValue(baseAddr + 18, 16, 'string'); // Offset 18-33
493
- const options = await this.readRegisterValue(baseAddr + 34, 8, 'string'); // Offset 34-41
494
- const version = await this.readRegisterValue(baseAddr + 42, 8, 'string'); // Offset 42-49
495
- const serialNumber = await this.readRegisterValue(baseAddr + 50, 16, 'string'); // Offset 50-65
496
- const deviceAddress = await this.readRegisterValue(baseAddr + 66, 1, 'uint16'); // Offset 66
497
- return {
566
+ // Common block offsets are relative to the start of the data (after ID and Length)
567
+ const manufacturerAddr = baseAddr; // Offset 0-15 (16 registers) from data start
568
+ const modelAddr = baseAddr + 16; // Offset 16-31 (16 registers) from data start
569
+ const optionsAddr = baseAddr + 32; // Offset 32-39 (8 registers) from data start
570
+ const versionAddr = baseAddr + 40; // Offset 40-47 (8 registers) from data start
571
+ const serialAddr = baseAddr + 48; // Offset 48-63 (16 registers) from data start
572
+ const deviceAddrAddr = baseAddr + 64; // Offset 64 from data start
573
+ console.log(`Reading manufacturer from address ${manufacturerAddr} (16 registers)`);
574
+ const manufacturer = await this.readRegisterValue(manufacturerAddr, 16, 'string');
575
+ console.log(`Manufacturer raw value: "${manufacturer}"`);
576
+ console.log(`Reading model from address ${modelAddr} (16 registers)`);
577
+ const modelName = await this.readRegisterValue(modelAddr, 16, 'string');
578
+ console.log(`Model raw value: "${modelName}"`);
579
+ console.log(`Reading options from address ${optionsAddr} (8 registers)`);
580
+ const options = await this.readRegisterValue(optionsAddr, 8, 'string');
581
+ console.log(`Reading version from address ${versionAddr} (8 registers)`);
582
+ const version = await this.readRegisterValue(versionAddr, 8, 'string');
583
+ console.log(`Reading serial from address ${serialAddr} (16 registers)`);
584
+ const serialNumber = await this.readRegisterValue(serialAddr, 16, 'string');
585
+ console.log(`Reading device address from address ${deviceAddrAddr}`);
586
+ const deviceAddress = await this.readRegisterValue(deviceAddrAddr, 1, 'uint16');
587
+ const result = {
498
588
  manufacturer: manufacturer,
499
589
  model: modelName,
500
590
  options: options,
@@ -502,6 +592,8 @@ class SunspecModbusClient {
502
592
  serialNumber: serialNumber,
503
593
  deviceAddress: deviceAddress
504
594
  };
595
+ console.log('Common Block Data:', JSON.stringify(result, null, 2));
596
+ return result;
505
597
  }
506
598
  catch (error) {
507
599
  console.error(`Error reading common block: ${error}`);
@@ -44,6 +44,11 @@ export declare class SunspecModbusClient {
44
44
  * Convert unsigned 16-bit value to signed
45
45
  */
46
46
  private convertToSigned16;
47
+ /**
48
+ * Check if a value is "unimplemented" according to Sunspec
49
+ * Returns true if the value represents an unimplemented/not applicable register
50
+ */
51
+ private isUnimplementedValue;
47
52
  /**
48
53
  * Helper to clean string values by removing null characters
49
54
  */
@@ -66,6 +71,7 @@ export declare class SunspecModbusClient {
66
71
  private readInverterScaleFactors;
67
72
  /**
68
73
  * Apply scale factor to a value
74
+ * Returns undefined if the value is unimplemented or scale factor is out of range
69
75
  */
70
76
  private applyScaleFactor;
71
77
  /**
@@ -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.5';
12
+ exports.SDK_VERSION = '0.0.6';
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.5";
8
+ export declare const SDK_VERSION = "0.0.6";
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
@@ -2,7 +2,7 @@ import { ApplianceManager, EnergyApp } from "@enyo-energy/energy-app-sdk";
2
2
  import { EnyoApplianceName } from "@enyo-energy/energy-app-sdk/dist/types/enyo-appliance.js";
3
3
  import { EnyoNetworkDevice } from "@enyo-energy/energy-app-sdk/dist/types/enyo-network-device.js";
4
4
  import { SunspecModbusClient } from "./sunspec-modbus-client.js";
5
- import { EnyoDataBusMessage } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
5
+ import { EnyoDataBusMessage, EnyoDataBusMessageResolution } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
6
6
  /**
7
7
  * Base abstract class for all Sunspec devices
8
8
  */
@@ -27,7 +27,7 @@ export declare abstract class BaseSunspecDevice {
27
27
  /**
28
28
  * Update device data and return data bus messages
29
29
  */
30
- abstract readData(clockId: string, resolution: '10s' | '30s' | '1m' | '15m'): Promise<EnyoDataBusMessage[]>;
30
+ abstract readData(clockId: string, resolution: EnyoDataBusMessageResolution): Promise<EnyoDataBusMessage[]>;
31
31
  /**
32
32
  * Check if the device is connected
33
33
  */
@@ -83,5 +83,5 @@ export declare class SunspecMeter extends BaseSunspecDevice {
83
83
  /**
84
84
  * Update meter data and return data bus messages
85
85
  */
86
- readData(clockId: string, resolution: '10s' | '30s' | '1m' | '15m'): Promise<EnyoDataBusMessage[]>;
86
+ readData(clockId: string, resolution: EnyoDataBusMessageResolution): Promise<EnyoDataBusMessage[]>;
87
87
  }
@@ -109,12 +109,12 @@ export class SunspecInverter extends BaseSunspecDevice {
109
109
  timestampIso: timestamp.toISOString(),
110
110
  resolution,
111
111
  data: {
112
- pvPowerW: inverterData.acPower || 0,
113
- activePowerLimitationW: inverterData.acPower || 0, // Using AC power as default
112
+ pvPowerW: inverterData.dcPower || 0, // Use DC power for PV power
113
+ activePowerLimitationW: inverterData.acPower || 0,
114
114
  state: this.mapOperatingState(inverterData.operatingState),
115
115
  voltageL1: inverterData.voltageAN || 0,
116
- voltageL2: inverterData.voltageBN,
117
- voltageL3: inverterData.voltageCN,
116
+ voltageL2: inverterData.voltageBN ?? undefined, // Use null if undefined (unimplemented phase)
117
+ voltageL3: inverterData.voltageCN ?? undefined, // Use null if undefined (unimplemented phase)
118
118
  strings: this.mapMPPTToStrings(mpptDataList)
119
119
  }
120
120
  };
@@ -148,11 +148,14 @@ export class SunspecInverter extends BaseSunspecDevice {
148
148
  mapMPPTToStrings(mpptDataList) {
149
149
  const result = [];
150
150
  mpptDataList.forEach((mppt, index) => {
151
- result.push({
152
- index: index + 1,
153
- voltage: mppt.dcVoltage,
154
- powerW: mppt.dcPower
155
- });
151
+ // Only include strings with valid data
152
+ if (mppt.dcVoltage !== undefined || mppt.dcPower !== undefined) {
153
+ result.push({
154
+ index: index + 1,
155
+ voltage: mppt.dcVoltage ?? undefined, // Use null if undefined
156
+ powerW: mppt.dcPower ?? 0 // Default to 0 if undefined
157
+ });
158
+ }
156
159
  });
157
160
  return result;
158
161
  }
@@ -44,6 +44,11 @@ export declare class SunspecModbusClient {
44
44
  * Convert unsigned 16-bit value to signed
45
45
  */
46
46
  private convertToSigned16;
47
+ /**
48
+ * Check if a value is "unimplemented" according to Sunspec
49
+ * Returns true if the value represents an unimplemented/not applicable register
50
+ */
51
+ private isUnimplementedValue;
47
52
  /**
48
53
  * Helper to clean string values by removing null characters
49
54
  */
@@ -66,6 +71,7 @@ export declare class SunspecModbusClient {
66
71
  private readInverterScaleFactors;
67
72
  /**
68
73
  * Apply scale factor to a value
74
+ * Returns undefined if the value is unimplemented or scale factor is out of range
69
75
  */
70
76
  private applyScaleFactor;
71
77
  /**
@@ -186,6 +186,24 @@ export class SunspecModbusClient {
186
186
  }
187
187
  return value;
188
188
  }
189
+ /**
190
+ * Check if a value is "unimplemented" according to Sunspec
191
+ * Returns true if the value represents an unimplemented/not applicable register
192
+ */
193
+ isUnimplementedValue(value, dataType = 'uint16') {
194
+ switch (dataType) {
195
+ case 'uint16':
196
+ return value === 0xFFFF || value === 65535;
197
+ case 'int16':
198
+ return value === 0x7FFF || value === 32767;
199
+ case 'uint32':
200
+ return value === 0xFFFFFFFF;
201
+ case 'int32':
202
+ return value === 0x7FFFFFFF;
203
+ default:
204
+ return false;
205
+ }
206
+ }
189
207
  /**
190
208
  * Helper to clean string values by removing null characters
191
209
  */
@@ -265,6 +283,15 @@ export class SunspecModbusClient {
265
283
  // Read all scale factors first using fault-tolerant reader
266
284
  const scaleFactors = await this.readInverterScaleFactors(baseAddr);
267
285
  // Read values using fault-tolerant reader with proper data types
286
+ // Read raw voltage values to check for unimplemented phases
287
+ const voltageANRaw = await this.readRegisterValue(baseAddr + 10, 1, 'uint16');
288
+ const voltageBNRaw = await this.readRegisterValue(baseAddr + 11, 1, 'uint16');
289
+ const voltageCNRaw = await this.readRegisterValue(baseAddr + 12, 1, 'uint16');
290
+ console.log('Inverter Raw Voltage Values:', {
291
+ voltageANRaw: `0x${voltageANRaw.toString(16).toUpperCase()} (${voltageANRaw})`,
292
+ voltageBNRaw: `0x${voltageBNRaw.toString(16).toUpperCase()} (${voltageBNRaw})`,
293
+ voltageCNRaw: `0x${voltageCNRaw.toString(16).toUpperCase()} (${voltageCNRaw})`
294
+ });
268
295
  const data = {
269
296
  blockNumber: 103,
270
297
  blockAddress: model.address,
@@ -278,9 +305,10 @@ export class SunspecModbusClient {
278
305
  voltageAB: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 7, 1, 'uint16'), scaleFactors.V_SF),
279
306
  voltageBC: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 8, 1, 'uint16'), scaleFactors.V_SF),
280
307
  voltageCA: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 9, 1, 'uint16'), scaleFactors.V_SF),
281
- voltageAN: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 10, 1, 'uint16'), scaleFactors.V_SF),
282
- voltageBN: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 11, 1, 'uint16'), scaleFactors.V_SF),
283
- voltageCN: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 12, 1, 'uint16'), scaleFactors.V_SF),
308
+ // Apply scale factor with unimplemented value checking
309
+ voltageAN: this.applyScaleFactor(voltageANRaw, scaleFactors.V_SF),
310
+ voltageBN: this.applyScaleFactor(voltageBNRaw, scaleFactors.V_SF),
311
+ voltageCN: this.applyScaleFactor(voltageCNRaw, scaleFactors.V_SF),
284
312
  // Power values - Offsets 14, 18, 20, 22
285
313
  acPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 14, 1, 'int16'), scaleFactors.W_SF),
286
314
  apparentPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 18, 1, 'uint16'), scaleFactors.VA_SF),
@@ -346,7 +374,7 @@ export class SunspecModbusClient {
346
374
  * Read inverter scale factors
347
375
  */
348
376
  async readInverterScaleFactors(baseAddr) {
349
- return {
377
+ const scaleFactors = {
350
378
  A_SF: await this.readRegisterValue(baseAddr + 6, 1, 'int16'), // Offset 6
351
379
  V_SF: await this.readRegisterValue(baseAddr + 13, 1, 'int16'), // Offset 13
352
380
  W_SF: await this.readRegisterValue(baseAddr + 15, 1, 'int16'), // Offset 15
@@ -360,11 +388,23 @@ export class SunspecModbusClient {
360
388
  DCW_SF: await this.readRegisterValue(baseAddr + 31, 1, 'int16'), // Offset 31
361
389
  Tmp_SF: await this.readRegisterValue(baseAddr + 36, 1, 'int16') // Offset 36
362
390
  };
391
+ console.log('Inverter Scale Factors:', JSON.stringify(scaleFactors, null, 2));
392
+ return scaleFactors;
363
393
  }
364
394
  /**
365
395
  * Apply scale factor to a value
396
+ * Returns undefined if the value is unimplemented or scale factor is out of range
366
397
  */
367
- applyScaleFactor(value, scaleFactor) {
398
+ applyScaleFactor(value, scaleFactor, dataType = 'uint16') {
399
+ // Check for unimplemented values
400
+ if (this.isUnimplementedValue(value, dataType)) {
401
+ return undefined;
402
+ }
403
+ // Validate scale factor is within reasonable range (-10 to +10)
404
+ if (Math.abs(scaleFactor) > 10) {
405
+ console.warn(`Scale factor ${scaleFactor} is outside reasonable range, clamping to ±10`);
406
+ scaleFactor = Math.max(-10, Math.min(10, scaleFactor));
407
+ }
368
408
  return value * Math.pow(10, scaleFactor);
369
409
  }
370
410
  /**
@@ -390,6 +430,28 @@ export class SunspecModbusClient {
390
430
  DCWH_SF: await this.readRegisterValue(moduleAddr + 17, 1, 'int16'),
391
431
  Tmp_SF: await this.readRegisterValue(moduleAddr + 21, 1, 'int16')
392
432
  };
433
+ console.log(`MPPT Module ${moduleId} Scale Factors:`, JSON.stringify(scaleFactors, null, 2));
434
+ // Read raw values first
435
+ const dcCurrentRaw = await this.readRegisterValue(moduleAddr + 9, 1, 'uint16');
436
+ const dcVoltageRaw = await this.readRegisterValue(moduleAddr + 11, 1, 'uint16');
437
+ const dcPowerRaw = await this.readRegisterValue(moduleAddr + 13, 1, 'uint16');
438
+ const dcEnergyRaw = await this.readRegisterValue(moduleAddr + 15, 2, 'acc32');
439
+ const temperatureRaw = await this.readRegisterValue(moduleAddr + 20, 1, 'int16');
440
+ console.log(`MPPT Module ${moduleId} Raw Values:`, {
441
+ dcCurrentRaw: `0x${dcCurrentRaw.toString(16).toUpperCase()} (${dcCurrentRaw})`,
442
+ dcVoltageRaw: `0x${dcVoltageRaw.toString(16).toUpperCase()} (${dcVoltageRaw})`,
443
+ dcPowerRaw: `0x${dcPowerRaw.toString(16).toUpperCase()} (${dcPowerRaw})`,
444
+ dcEnergyRaw: `0x${dcEnergyRaw.toString(16).toUpperCase()} (${dcEnergyRaw})`,
445
+ temperatureRaw: `0x${temperatureRaw.toString(16).toUpperCase()} (${temperatureRaw})`
446
+ });
447
+ // Check if this module is actually implemented/connected
448
+ // If all key values are unimplemented, this module doesn't exist
449
+ if (this.isUnimplementedValue(dcCurrentRaw, 'uint16') &&
450
+ this.isUnimplementedValue(dcVoltageRaw, 'uint16') &&
451
+ this.isUnimplementedValue(dcPowerRaw, 'uint16')) {
452
+ console.log(`MPPT module ${moduleId} appears to be unconnected (all values are 0xFFFF)`);
453
+ return null;
454
+ }
393
455
  const data = {
394
456
  blockNumber: 160,
395
457
  blockAddress: model.address,
@@ -398,22 +460,24 @@ export class SunspecModbusClient {
398
460
  // String ID - Offset 1 (8 registers for string)
399
461
  stringId: await this.readRegisterValue(moduleAddr + 1, 8, 'string'),
400
462
  // DC Current - Offset 9
401
- dcCurrent: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 9, 1, 'uint16'), scaleFactors.DCA_SF),
463
+ dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF),
402
464
  dcCurrentSF: scaleFactors.DCA_SF,
403
465
  // DC Voltage - Offset 11
404
- dcVoltage: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 11, 1, 'uint16'), scaleFactors.DCV_SF),
466
+ dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF),
405
467
  dcVoltageSF: scaleFactors.DCV_SF,
406
468
  // DC Power - Offset 13
407
- dcPower: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 13, 1, 'uint16'), scaleFactors.DCW_SF),
469
+ dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF),
408
470
  dcPowerSF: scaleFactors.DCW_SF,
409
471
  // DC Energy - Offset 15-16 (32-bit accumulator)
410
- dcEnergy: BigInt(await this.readRegisterValue(moduleAddr + 15, 2, 'acc32')) *
411
- BigInt(Math.pow(10, scaleFactors.DCWH_SF)),
472
+ // Only calculate if value is not unimplemented
473
+ dcEnergy: !this.isUnimplementedValue(dcEnergyRaw, 'uint32')
474
+ ? BigInt(dcEnergyRaw) * BigInt(Math.pow(10, scaleFactors.DCWH_SF))
475
+ : undefined,
412
476
  dcEnergySF: scaleFactors.DCWH_SF,
413
477
  // Timestamp - Offset 18-19 (32-bit)
414
478
  timestamp: await this.readRegisterValue(moduleAddr + 18, 2, 'uint32'),
415
479
  // Temperature - Offset 20
416
- temperature: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 20, 1, 'int16'), scaleFactors.Tmp_SF),
480
+ temperature: this.applyScaleFactor(temperatureRaw, scaleFactors.Tmp_SF, 'int16'),
417
481
  temperatureSF: scaleFactors.Tmp_SF,
418
482
  // Operating State - Offset 22
419
483
  operatingState: await this.readRegisterValue(moduleAddr + 22, 1, 'uint16'),
@@ -436,9 +500,19 @@ export class SunspecModbusClient {
436
500
  const mpptData = [];
437
501
  // Try to read up to 4 MPPT strings (typical maximum)
438
502
  for (let i = 1; i <= 4; i++) {
439
- const data = await this.readMPPTData(i);
440
- if (data && data.dcCurrent !== undefined && data.dcCurrent > 0) {
441
- mpptData.push(data);
503
+ try {
504
+ const data = await this.readMPPTData(i);
505
+ // Only include if we got valid data (not null) and it has actual values
506
+ if (data &&
507
+ (data.dcCurrent !== undefined ||
508
+ data.dcVoltage !== undefined ||
509
+ data.dcPower !== undefined)) {
510
+ mpptData.push(data);
511
+ }
512
+ }
513
+ catch (error) {
514
+ console.debug(`Could not read MPPT module ${i}: ${error}`);
515
+ // Continue to try other modules
442
516
  }
443
517
  }
444
518
  return mpptData;
@@ -483,15 +557,31 @@ export class SunspecModbusClient {
483
557
  return null;
484
558
  }
485
559
  const baseAddr = model.address + 2; // Skip ID and Length
560
+ console.log(`Reading Common Block - Model address: ${model.address}, Base address for data: ${baseAddr}`);
486
561
  try {
487
562
  // Read all strings using fault-tolerant reader with proper data type conversion
488
- const manufacturer = await this.readRegisterValue(baseAddr + 2, 16, 'string'); // Offset 2-17
489
- const modelName = await this.readRegisterValue(baseAddr + 18, 16, 'string'); // Offset 18-33
490
- const options = await this.readRegisterValue(baseAddr + 34, 8, 'string'); // Offset 34-41
491
- const version = await this.readRegisterValue(baseAddr + 42, 8, 'string'); // Offset 42-49
492
- const serialNumber = await this.readRegisterValue(baseAddr + 50, 16, 'string'); // Offset 50-65
493
- const deviceAddress = await this.readRegisterValue(baseAddr + 66, 1, 'uint16'); // Offset 66
494
- return {
563
+ // Common block offsets are relative to the start of the data (after ID and Length)
564
+ const manufacturerAddr = baseAddr; // Offset 0-15 (16 registers) from data start
565
+ const modelAddr = baseAddr + 16; // Offset 16-31 (16 registers) from data start
566
+ const optionsAddr = baseAddr + 32; // Offset 32-39 (8 registers) from data start
567
+ const versionAddr = baseAddr + 40; // Offset 40-47 (8 registers) from data start
568
+ const serialAddr = baseAddr + 48; // Offset 48-63 (16 registers) from data start
569
+ const deviceAddrAddr = baseAddr + 64; // Offset 64 from data start
570
+ console.log(`Reading manufacturer from address ${manufacturerAddr} (16 registers)`);
571
+ const manufacturer = await this.readRegisterValue(manufacturerAddr, 16, 'string');
572
+ console.log(`Manufacturer raw value: "${manufacturer}"`);
573
+ console.log(`Reading model from address ${modelAddr} (16 registers)`);
574
+ const modelName = await this.readRegisterValue(modelAddr, 16, 'string');
575
+ console.log(`Model raw value: "${modelName}"`);
576
+ console.log(`Reading options from address ${optionsAddr} (8 registers)`);
577
+ const options = await this.readRegisterValue(optionsAddr, 8, 'string');
578
+ console.log(`Reading version from address ${versionAddr} (8 registers)`);
579
+ const version = await this.readRegisterValue(versionAddr, 8, 'string');
580
+ console.log(`Reading serial from address ${serialAddr} (16 registers)`);
581
+ const serialNumber = await this.readRegisterValue(serialAddr, 16, 'string');
582
+ console.log(`Reading device address from address ${deviceAddrAddr}`);
583
+ const deviceAddress = await this.readRegisterValue(deviceAddrAddr, 1, 'uint16');
584
+ const result = {
495
585
  manufacturer: manufacturer,
496
586
  model: modelName,
497
587
  options: options,
@@ -499,6 +589,8 @@ export class SunspecModbusClient {
499
589
  serialNumber: serialNumber,
500
590
  deviceAddress: deviceAddress
501
591
  };
592
+ console.log('Common Block Data:', JSON.stringify(result, null, 2));
593
+ return result;
502
594
  }
503
595
  catch (error) {
504
596
  console.error(`Error reading common block: ${error}`);
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.5";
8
+ export declare const SDK_VERSION = "0.0.6";
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.5';
8
+ export const SDK_VERSION = '0.0.6';
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.5",
3
+ "version": "0.0.6",
4
4
  "description": "enyo Energy Sunspec SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",