@enyo-energy/sunspec-sdk 0.0.5 → 0.0.7

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.
@@ -103,6 +103,15 @@ class SunspecInverter extends BaseSunspecDevice {
103
103
  const inverterData = await this.sunspecClient.readInverterData();
104
104
  const mpptDataList = await this.sunspecClient.readAllMPPTData();
105
105
  if (inverterData) {
106
+ // Log what we're sending
107
+ console.log('Preparing Inverter Message:', {
108
+ pvPowerW: inverterData.dcPower,
109
+ acPower: inverterData.acPower,
110
+ voltageL1: inverterData.voltageAN,
111
+ voltageL2: inverterData.voltageBN,
112
+ voltageL3: inverterData.voltageCN,
113
+ stringsCount: mpptDataList.length
114
+ });
106
115
  const inverterMessage = {
107
116
  id: (0, node_crypto_1.randomUUID)(),
108
117
  message: enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.InverterValuesUpdateV1,
@@ -113,12 +122,12 @@ class SunspecInverter extends BaseSunspecDevice {
113
122
  timestampIso: timestamp.toISOString(),
114
123
  resolution,
115
124
  data: {
116
- pvPowerW: inverterData.acPower || 0,
117
- activePowerLimitationW: inverterData.acPower || 0, // Using AC power as default
125
+ pvPowerW: inverterData.dcPower || 0, // Use DC power for PV power
126
+ activePowerLimitationW: inverterData.acPower || 0,
118
127
  state: this.mapOperatingState(inverterData.operatingState),
119
128
  voltageL1: inverterData.voltageAN || 0,
120
- voltageL2: inverterData.voltageBN,
121
- voltageL3: inverterData.voltageCN,
129
+ voltageL2: inverterData.voltageBN ?? undefined, // Use undefined if unimplemented phase
130
+ voltageL3: inverterData.voltageCN ?? undefined, // Use undefined if unimplemented phase
122
131
  strings: this.mapMPPTToStrings(mpptDataList)
123
132
  }
124
133
  };
@@ -152,11 +161,14 @@ class SunspecInverter extends BaseSunspecDevice {
152
161
  mapMPPTToStrings(mpptDataList) {
153
162
  const result = [];
154
163
  mpptDataList.forEach((mppt, index) => {
155
- result.push({
156
- index: index + 1,
157
- voltage: mppt.dcVoltage,
158
- powerW: mppt.dcPower
159
- });
164
+ // Only include strings with valid data
165
+ if (mppt.dcVoltage !== undefined || mppt.dcPower !== undefined) {
166
+ result.push({
167
+ index: index + 1,
168
+ voltage: mppt.dcVoltage ?? undefined, // Use null if undefined
169
+ powerW: mppt.dcPower ?? 0 // Default to 0 if undefined
170
+ });
171
+ }
160
172
  });
161
173
  return result;
162
174
  }
@@ -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
  */
@@ -261,19 +279,36 @@ class SunspecModbusClient {
261
279
  console.error('No inverter model found');
262
280
  return null;
263
281
  }
282
+ console.warn('IMPORTANT: Working with single-phase inverter, but 3-phase is expected!');
264
283
  return this.readSinglePhaseInverterData(singlePhaseModel);
265
284
  }
285
+ console.log(`Found 3-phase inverter model 103 at address ${model.address} with length ${model.length}`);
266
286
  const baseAddr = model.address + 2; // Skip ID and Length
267
287
  try {
268
288
  // Read all scale factors first using fault-tolerant reader
289
+ console.log(`Reading Inverter Data from Model 103 at base address: ${baseAddr}`);
269
290
  const scaleFactors = await this.readInverterScaleFactors(baseAddr);
270
291
  // Read values using fault-tolerant reader with proper data types
292
+ // Read raw voltage values to check for unimplemented phases
293
+ const voltageANRaw = await this.readRegisterValue(baseAddr + 10, 1, 'uint16');
294
+ const voltageBNRaw = await this.readRegisterValue(baseAddr + 11, 1, 'uint16');
295
+ const voltageCNRaw = await this.readRegisterValue(baseAddr + 12, 1, 'uint16');
296
+ console.log('Inverter Raw Voltage Values:', {
297
+ voltageANRaw: `0x${voltageANRaw.toString(16).toUpperCase()} (${voltageANRaw})`,
298
+ voltageBNRaw: `0x${voltageBNRaw.toString(16).toUpperCase()} (${voltageBNRaw})`,
299
+ voltageCNRaw: `0x${voltageCNRaw.toString(16).toUpperCase()} (${voltageCNRaw})`
300
+ });
271
301
  const data = {
272
302
  blockNumber: 103,
273
303
  blockAddress: model.address,
274
304
  blockLength: model.length,
275
305
  // AC Current values - Offsets 2-5
276
- acCurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 2, 1, 'uint16'), scaleFactors.A_SF),
306
+ acCurrent: this.applyScaleFactor(await (async () => {
307
+ const addr = baseAddr + 2;
308
+ const raw = await this.readRegisterValue(addr, 1, 'uint16');
309
+ console.log(`AC Current: address=${addr}, raw=0x${raw.toString(16).toUpperCase()} (${raw})`);
310
+ return raw;
311
+ })(), scaleFactors.A_SF),
277
312
  phaseACurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 3, 1, 'uint16'), scaleFactors.A_SF),
278
313
  phaseBCurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 4, 1, 'uint16'), scaleFactors.A_SF),
279
314
  phaseCCurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 5, 1, 'uint16'), scaleFactors.A_SF),
@@ -281,20 +316,41 @@ class SunspecModbusClient {
281
316
  voltageAB: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 7, 1, 'uint16'), scaleFactors.V_SF),
282
317
  voltageBC: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 8, 1, 'uint16'), scaleFactors.V_SF),
283
318
  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),
319
+ // Apply scale factor with unimplemented value checking
320
+ voltageAN: this.applyScaleFactor(voltageANRaw, scaleFactors.V_SF),
321
+ voltageBN: this.applyScaleFactor(voltageBNRaw, scaleFactors.V_SF),
322
+ voltageCN: this.applyScaleFactor(voltageCNRaw, scaleFactors.V_SF),
287
323
  // Power values - Offsets 14, 18, 20, 22
288
- acPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 14, 1, 'int16'), scaleFactors.W_SF),
324
+ acPower: this.applyScaleFactor(await (async () => {
325
+ const addr = baseAddr + 14;
326
+ const raw = await this.readRegisterValue(addr, 1, 'int16');
327
+ console.log(`AC Power (W): address=${addr}, raw=0x${raw.toString(16).toUpperCase()} (${raw}), SF=${scaleFactors.W_SF}`);
328
+ return raw;
329
+ })(), scaleFactors.W_SF, 'int16'),
289
330
  apparentPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 18, 1, 'uint16'), scaleFactors.VA_SF),
290
331
  reactivePower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 20, 1, 'int16'), scaleFactors.VAr_SF),
291
332
  powerFactor: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 22, 1, 'int16'), scaleFactors.PF_SF),
292
333
  // Frequency - Offset 16
293
334
  frequency: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 16, 1, 'uint16'), scaleFactors.Hz_SF),
294
335
  // DC values - Offsets 27, 28, 30
295
- dcCurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 27, 1, 'uint16'), scaleFactors.DCA_SF),
296
- dcVoltage: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 28, 1, 'uint16'), scaleFactors.DCV_SF),
297
- dcPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 30, 1, 'int16'), scaleFactors.DCW_SF),
336
+ dcCurrent: this.applyScaleFactor(await (async () => {
337
+ const addr = baseAddr + 27;
338
+ const raw = await this.readRegisterValue(addr, 1, 'uint16');
339
+ console.log(`DC Current: address=${addr}, raw=0x${raw.toString(16).toUpperCase()} (${raw})`);
340
+ return raw;
341
+ })(), scaleFactors.DCA_SF),
342
+ dcVoltage: this.applyScaleFactor(await (async () => {
343
+ const addr = baseAddr + 28;
344
+ const raw = await this.readRegisterValue(addr, 1, 'uint16');
345
+ console.log(`DC Voltage: address=${addr}, raw=0x${raw.toString(16).toUpperCase()} (${raw})`);
346
+ return raw;
347
+ })(), scaleFactors.DCV_SF),
348
+ dcPower: this.applyScaleFactor(await (async () => {
349
+ const addr = baseAddr + 30;
350
+ const raw = await this.readRegisterValue(addr, 1, 'int16');
351
+ console.log(`DC Power: address=${addr}, raw=0x${raw.toString(16).toUpperCase()} (${raw}), SF=${scaleFactors.DCW_SF}`);
352
+ return raw;
353
+ })(), scaleFactors.DCW_SF, 'int16'),
298
354
  // Temperature values - Offsets 32, 34, 35, 36
299
355
  cabinetTemperature: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 32, 1, 'int16'), scaleFactors.Tmp_SF),
300
356
  heatSinkTemperature: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 34, 1, 'int16'), scaleFactors.Tmp_SF),
@@ -312,8 +368,21 @@ class SunspecModbusClient {
312
368
  vendorEvents4: await this.readRegisterValue(baseAddr + 50, 2, 'uint32')
313
369
  };
314
370
  // Read AC Energy (32-bit accumulator) - Offset 24-25
315
- const acEnergy = await this.readRegisterValue(baseAddr + 24, 2, 'acc32');
371
+ const acEnergyAddr = baseAddr + 24;
372
+ const acEnergy = await this.readRegisterValue(acEnergyAddr, 2, 'acc32');
373
+ console.log(`AC Energy: address=${acEnergyAddr}, raw=0x${acEnergy.toString(16).toUpperCase()} (${acEnergy}), SF=${scaleFactors.WH_SF}`);
316
374
  data.acEnergy = BigInt(acEnergy) * BigInt(Math.pow(10, scaleFactors.WH_SF));
375
+ // Log final calculated values
376
+ console.log('Inverter Final Values:', {
377
+ acPower: data.acPower,
378
+ dcPower: data.dcPower,
379
+ voltageAN: data.voltageAN,
380
+ voltageBN: data.voltageBN,
381
+ voltageCN: data.voltageCN,
382
+ dcVoltage: data.dcVoltage,
383
+ dcCurrent: data.dcCurrent,
384
+ acCurrent: data.acCurrent
385
+ });
317
386
  return data;
318
387
  }
319
388
  catch (error) {
@@ -329,15 +398,53 @@ class SunspecModbusClient {
329
398
  // Implementation would be similar but simplified
330
399
  const baseAddr = model.address + 2;
331
400
  try {
332
- // Simplified implementation for single phase
401
+ console.log(`Reading Single-Phase Inverter Data from Model 101 at base address: ${baseAddr}`);
402
+ // Read scale factors for single phase model
403
+ const scaleFactors = {
404
+ A_SF: await this.readRegisterValue(baseAddr + 6, 1, 'int16'),
405
+ V_SF: await this.readRegisterValue(baseAddr + 13, 1, 'int16'),
406
+ W_SF: await this.readRegisterValue(baseAddr + 10, 1, 'int16'),
407
+ Hz_SF: await this.readRegisterValue(baseAddr + 12, 1, 'int16'),
408
+ DCA_SF: await this.readRegisterValue(baseAddr + 18, 1, 'int16'),
409
+ DCV_SF: await this.readRegisterValue(baseAddr + 19, 1, 'int16'),
410
+ DCW_SF: await this.readRegisterValue(baseAddr + 21, 1, 'int16')
411
+ };
412
+ console.log('Single-Phase Inverter Scale Factors:', JSON.stringify(scaleFactors, null, 2));
413
+ // Read raw values with logging
414
+ const acCurrentAddr = baseAddr + 2;
415
+ const acCurrentRaw = await this.readRegisterValue(acCurrentAddr, 1, 'uint16');
416
+ console.log(`AC Current: address=${acCurrentAddr}, raw=0x${acCurrentRaw.toString(16).toUpperCase()} (${acCurrentRaw})`);
417
+ const voltageAddr = baseAddr + 7;
418
+ const voltageRaw = await this.readRegisterValue(voltageAddr, 1, 'uint16');
419
+ console.log(`AC Voltage: address=${voltageAddr}, raw=0x${voltageRaw.toString(16).toUpperCase()} (${voltageRaw})`);
420
+ const acPowerAddr = baseAddr + 9;
421
+ const acPowerRaw = await this.readRegisterValue(acPowerAddr, 1, 'int16');
422
+ console.log(`AC Power: address=${acPowerAddr}, raw=0x${acPowerRaw.toString(16).toUpperCase()} (${acPowerRaw}), SF=${scaleFactors.W_SF}`);
423
+ const freqAddr = baseAddr + 11;
424
+ const freqRaw = await this.readRegisterValue(freqAddr, 1, 'uint16');
425
+ console.log(`Frequency: address=${freqAddr}, raw=0x${freqRaw.toString(16).toUpperCase()} (${freqRaw})`);
426
+ const dcCurrentAddr = baseAddr + 14;
427
+ const dcCurrentRaw = await this.readRegisterValue(dcCurrentAddr, 1, 'uint16');
428
+ console.log(`DC Current: address=${dcCurrentAddr}, raw=0x${dcCurrentRaw.toString(16).toUpperCase()} (${dcCurrentRaw})`);
429
+ const dcVoltageAddr = baseAddr + 15;
430
+ const dcVoltageRaw = await this.readRegisterValue(dcVoltageAddr, 1, 'uint16');
431
+ console.log(`DC Voltage: address=${dcVoltageAddr}, raw=0x${dcVoltageRaw.toString(16).toUpperCase()} (${dcVoltageRaw})`);
432
+ const dcPowerAddr = baseAddr + 20;
433
+ const dcPowerRaw = await this.readRegisterValue(dcPowerAddr, 1, 'int16');
434
+ console.log(`DC Power: address=${dcPowerAddr}, raw=0x${dcPowerRaw.toString(16).toUpperCase()} (${dcPowerRaw}), SF=${scaleFactors.DCW_SF}`);
435
+ const stateAddr = baseAddr + 24;
436
+ const stateRaw = await this.readRegisterValue(stateAddr, 1, 'uint16');
437
+ console.log(`Operating State: address=${stateAddr}, raw=0x${stateRaw.toString(16).toUpperCase()} (${stateRaw})`);
333
438
  return {
334
439
  blockNumber: 101,
335
- voltageAN: await this.readRegisterWithScaleFactor(baseAddr + 7, baseAddr + 13), // PhVphA with V_SF
336
- acCurrent: await this.readRegisterWithScaleFactor(baseAddr + 2, baseAddr + 6),
337
- acPower: await this.readRegisterWithScaleFactor(baseAddr + 9, baseAddr + 10),
338
- frequency: await this.readRegisterWithScaleFactor(baseAddr + 11, baseAddr + 12),
339
- dcPower: await this.readRegisterWithScaleFactor(baseAddr + 20, baseAddr + 21),
340
- operatingState: await this.readRegisterValue(baseAddr + 24, 1)
440
+ voltageAN: this.applyScaleFactor(voltageRaw, scaleFactors.V_SF),
441
+ acCurrent: this.applyScaleFactor(acCurrentRaw, scaleFactors.A_SF),
442
+ acPower: this.applyScaleFactor(acPowerRaw, scaleFactors.W_SF, 'int16'),
443
+ frequency: this.applyScaleFactor(freqRaw, scaleFactors.Hz_SF),
444
+ dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF),
445
+ dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF),
446
+ dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF, 'int16'),
447
+ operatingState: stateRaw
341
448
  };
342
449
  }
343
450
  catch (error) {
@@ -349,7 +456,7 @@ class SunspecModbusClient {
349
456
  * Read inverter scale factors
350
457
  */
351
458
  async readInverterScaleFactors(baseAddr) {
352
- return {
459
+ const scaleFactors = {
353
460
  A_SF: await this.readRegisterValue(baseAddr + 6, 1, 'int16'), // Offset 6
354
461
  V_SF: await this.readRegisterValue(baseAddr + 13, 1, 'int16'), // Offset 13
355
462
  W_SF: await this.readRegisterValue(baseAddr + 15, 1, 'int16'), // Offset 15
@@ -363,11 +470,18 @@ class SunspecModbusClient {
363
470
  DCW_SF: await this.readRegisterValue(baseAddr + 31, 1, 'int16'), // Offset 31
364
471
  Tmp_SF: await this.readRegisterValue(baseAddr + 36, 1, 'int16') // Offset 36
365
472
  };
473
+ console.log('Inverter Scale Factors:', JSON.stringify(scaleFactors, null, 2));
474
+ return scaleFactors;
366
475
  }
367
476
  /**
368
477
  * Apply scale factor to a value
478
+ * Returns undefined if the value is unimplemented or scale factor is out of range
369
479
  */
370
- applyScaleFactor(value, scaleFactor) {
480
+ applyScaleFactor(value, scaleFactor, dataType = 'uint16') {
481
+ // Check for unimplemented values
482
+ if (this.isUnimplementedValue(value, dataType)) {
483
+ return undefined;
484
+ }
371
485
  return value * Math.pow(10, scaleFactor);
372
486
  }
373
487
  /**
@@ -393,6 +507,28 @@ class SunspecModbusClient {
393
507
  DCWH_SF: await this.readRegisterValue(moduleAddr + 17, 1, 'int16'),
394
508
  Tmp_SF: await this.readRegisterValue(moduleAddr + 21, 1, 'int16')
395
509
  };
510
+ console.log(`MPPT Module ${moduleId} Scale Factors:`, JSON.stringify(scaleFactors, null, 2));
511
+ // Read raw values first
512
+ const dcCurrentRaw = await this.readRegisterValue(moduleAddr + 9, 1, 'uint16');
513
+ const dcVoltageRaw = await this.readRegisterValue(moduleAddr + 11, 1, 'uint16');
514
+ const dcPowerRaw = await this.readRegisterValue(moduleAddr + 13, 1, 'uint16');
515
+ const dcEnergyRaw = await this.readRegisterValue(moduleAddr + 15, 2, 'acc32');
516
+ const temperatureRaw = await this.readRegisterValue(moduleAddr + 20, 1, 'int16');
517
+ console.log(`MPPT Module ${moduleId} Raw Values:`, {
518
+ dcCurrentRaw: `0x${dcCurrentRaw.toString(16).toUpperCase()} (${dcCurrentRaw})`,
519
+ dcVoltageRaw: `0x${dcVoltageRaw.toString(16).toUpperCase()} (${dcVoltageRaw})`,
520
+ dcPowerRaw: `0x${dcPowerRaw.toString(16).toUpperCase()} (${dcPowerRaw})`,
521
+ dcEnergyRaw: `0x${dcEnergyRaw.toString(16).toUpperCase()} (${dcEnergyRaw})`,
522
+ temperatureRaw: `0x${temperatureRaw.toString(16).toUpperCase()} (${temperatureRaw})`
523
+ });
524
+ // Check if this module is actually implemented/connected
525
+ // If all key values are unimplemented, this module doesn't exist
526
+ if (this.isUnimplementedValue(dcCurrentRaw, 'uint16') &&
527
+ this.isUnimplementedValue(dcVoltageRaw, 'uint16') &&
528
+ this.isUnimplementedValue(dcPowerRaw, 'uint16')) {
529
+ console.log(`MPPT module ${moduleId} appears to be unconnected (all values are 0xFFFF)`);
530
+ return null;
531
+ }
396
532
  const data = {
397
533
  blockNumber: 160,
398
534
  blockAddress: model.address,
@@ -401,22 +537,24 @@ class SunspecModbusClient {
401
537
  // String ID - Offset 1 (8 registers for string)
402
538
  stringId: await this.readRegisterValue(moduleAddr + 1, 8, 'string'),
403
539
  // DC Current - Offset 9
404
- dcCurrent: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 9, 1, 'uint16'), scaleFactors.DCA_SF),
540
+ dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF),
405
541
  dcCurrentSF: scaleFactors.DCA_SF,
406
542
  // DC Voltage - Offset 11
407
- dcVoltage: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 11, 1, 'uint16'), scaleFactors.DCV_SF),
543
+ dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF),
408
544
  dcVoltageSF: scaleFactors.DCV_SF,
409
545
  // DC Power - Offset 13
410
- dcPower: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 13, 1, 'uint16'), scaleFactors.DCW_SF),
546
+ dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF),
411
547
  dcPowerSF: scaleFactors.DCW_SF,
412
548
  // 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)),
549
+ // Only calculate if value is not unimplemented
550
+ dcEnergy: !this.isUnimplementedValue(dcEnergyRaw, 'uint32')
551
+ ? BigInt(dcEnergyRaw) * BigInt(Math.pow(10, scaleFactors.DCWH_SF))
552
+ : undefined,
415
553
  dcEnergySF: scaleFactors.DCWH_SF,
416
554
  // Timestamp - Offset 18-19 (32-bit)
417
555
  timestamp: await this.readRegisterValue(moduleAddr + 18, 2, 'uint32'),
418
556
  // Temperature - Offset 20
419
- temperature: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 20, 1, 'int16'), scaleFactors.Tmp_SF),
557
+ temperature: this.applyScaleFactor(temperatureRaw, scaleFactors.Tmp_SF, 'int16'),
420
558
  temperatureSF: scaleFactors.Tmp_SF,
421
559
  // Operating State - Offset 22
422
560
  operatingState: await this.readRegisterValue(moduleAddr + 22, 1, 'uint16'),
@@ -439,9 +577,19 @@ class SunspecModbusClient {
439
577
  const mpptData = [];
440
578
  // Try to read up to 4 MPPT strings (typical maximum)
441
579
  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);
580
+ try {
581
+ const data = await this.readMPPTData(i);
582
+ // Only include if we got valid data (not null) and it has actual values
583
+ if (data &&
584
+ (data.dcCurrent !== undefined ||
585
+ data.dcVoltage !== undefined ||
586
+ data.dcPower !== undefined)) {
587
+ mpptData.push(data);
588
+ }
589
+ }
590
+ catch (error) {
591
+ console.debug(`Could not read MPPT module ${i}: ${error}`);
592
+ // Continue to try other modules
445
593
  }
446
594
  }
447
595
  return mpptData;
@@ -462,13 +610,47 @@ class SunspecModbusClient {
462
610
  return null;
463
611
  }
464
612
  const baseAddr = model.address + 2;
613
+ console.log(`Reading Meter Data from Model ${model.id} at base address: ${baseAddr}`);
465
614
  try {
466
- // This is a simplified implementation
467
- // Actual register offsets depend on specific meter model
615
+ // Model 201 (single phase) and 203 (3-phase) have different offsets
616
+ // Model 201: Power at offset 12, Frequency at offset 18
617
+ // Model 203: Power at offset 14, Frequency at offset 24
618
+ const isPowerMeter201 = model.id === 201;
619
+ const powerOffset = isPowerMeter201 ? 12 : 14; // Total Real Power offset
620
+ const powerSFOffset = isPowerMeter201 ? 15 : 17; // Power scale factor offset
621
+ const freqOffset = isPowerMeter201 ? 18 : 24; // Frequency offset
622
+ const freqSFOffset = isPowerMeter201 ? 21 : 27; // Frequency scale factor offset
623
+ const exportOffset = isPowerMeter201 ? 32 : 38; // Total Wh Exported offset
624
+ const importOffset = isPowerMeter201 ? 40 : 46; // Total Wh Imported offset
625
+ const energySFOffset = 4; // Energy scale factor is typically at offset 4
626
+ // Read scale factors
627
+ const powerSF = await this.readRegisterValue(baseAddr + powerSFOffset, 1, 'int16');
628
+ const freqSF = await this.readRegisterValue(baseAddr + freqSFOffset, 1, 'int16');
629
+ const energySF = await this.readRegisterValue(baseAddr + energySFOffset, 1, 'int16');
630
+ console.log(`Meter Scale Factors: Power_SF=${powerSF}, Freq_SF=${freqSF}, Energy_SF=${energySF}`);
631
+ // Read raw values
632
+ const powerAddr = baseAddr + powerOffset;
633
+ const powerRaw = await this.readRegisterValue(powerAddr, 1, 'int16');
634
+ console.log(`Meter Total Power: address=${powerAddr}, raw=0x${powerRaw.toString(16).toUpperCase()} (${powerRaw}), SF=${powerSF}`);
635
+ const freqAddr = baseAddr + freqOffset;
636
+ const freqRaw = await this.readRegisterValue(freqAddr, 1, 'uint16');
637
+ console.log(`Meter Frequency: address=${freqAddr}, raw=0x${freqRaw.toString(16).toUpperCase()} (${freqRaw}), SF=${freqSF}`);
638
+ const exportAddr = baseAddr + exportOffset;
639
+ const exportRaw = await this.readRegisterValue(exportAddr, 2, 'acc32');
640
+ console.log(`Meter Export Energy: address=${exportAddr}, raw=0x${exportRaw.toString(16).toUpperCase()} (${exportRaw}), SF=${energySF}`);
641
+ const importAddr = baseAddr + importOffset;
642
+ const importRaw = await this.readRegisterValue(importAddr, 2, 'acc32');
643
+ console.log(`Meter Import Energy: address=${importAddr}, raw=0x${importRaw.toString(16).toUpperCase()} (${importRaw}), SF=${energySF}`);
468
644
  return {
469
645
  blockNumber: model.id,
470
- totalPower: await this.readRegisterWithScaleFactor(baseAddr + 10, baseAddr + 11),
471
- frequency: await this.readRegisterWithScaleFactor(baseAddr + 20, baseAddr + 21)
646
+ totalPower: this.applyScaleFactor(powerRaw, powerSF, 'int16'),
647
+ frequency: this.applyScaleFactor(freqRaw, freqSF),
648
+ exportedEnergy: !this.isUnimplementedValue(exportRaw, 'uint32')
649
+ ? BigInt(exportRaw) * BigInt(Math.pow(10, energySF))
650
+ : undefined,
651
+ importedEnergy: !this.isUnimplementedValue(importRaw, 'uint32')
652
+ ? BigInt(importRaw) * BigInt(Math.pow(10, energySF))
653
+ : undefined
472
654
  };
473
655
  }
474
656
  catch (error) {
@@ -486,15 +668,31 @@ class SunspecModbusClient {
486
668
  return null;
487
669
  }
488
670
  const baseAddr = model.address + 2; // Skip ID and Length
671
+ console.log(`Reading Common Block - Model address: ${model.address}, Base address for data: ${baseAddr}`);
489
672
  try {
490
673
  // 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 {
674
+ // Common block offsets are relative to the start of the data (after ID and Length)
675
+ const manufacturerAddr = baseAddr; // Offset 0-15 (16 registers) from data start
676
+ const modelAddr = baseAddr + 16; // Offset 16-31 (16 registers) from data start
677
+ const optionsAddr = baseAddr + 32; // Offset 32-39 (8 registers) from data start
678
+ const versionAddr = baseAddr + 40; // Offset 40-47 (8 registers) from data start
679
+ const serialAddr = baseAddr + 48; // Offset 48-63 (16 registers) from data start
680
+ const deviceAddrAddr = baseAddr + 64; // Offset 64 from data start
681
+ console.log(`Reading manufacturer from address ${manufacturerAddr} (16 registers)`);
682
+ const manufacturer = await this.readRegisterValue(manufacturerAddr, 16, 'string');
683
+ console.log(`Manufacturer raw value: "${manufacturer}"`);
684
+ console.log(`Reading model from address ${modelAddr} (16 registers)`);
685
+ const modelName = await this.readRegisterValue(modelAddr, 16, 'string');
686
+ console.log(`Model raw value: "${modelName}"`);
687
+ console.log(`Reading options from address ${optionsAddr} (8 registers)`);
688
+ const options = await this.readRegisterValue(optionsAddr, 8, 'string');
689
+ console.log(`Reading version from address ${versionAddr} (8 registers)`);
690
+ const version = await this.readRegisterValue(versionAddr, 8, 'string');
691
+ console.log(`Reading serial from address ${serialAddr} (16 registers)`);
692
+ const serialNumber = await this.readRegisterValue(serialAddr, 16, 'string');
693
+ console.log(`Reading device address from address ${deviceAddrAddr}`);
694
+ const deviceAddress = await this.readRegisterValue(deviceAddrAddr, 1, 'uint16');
695
+ const result = {
498
696
  manufacturer: manufacturer,
499
697
  model: modelName,
500
698
  options: options,
@@ -502,6 +700,8 @@ class SunspecModbusClient {
502
700
  serialNumber: serialNumber,
503
701
  deviceAddress: deviceAddress
504
702
  };
703
+ console.log('Common Block Data:', JSON.stringify(result, null, 2));
704
+ return result;
505
705
  }
506
706
  catch (error) {
507
707
  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.7';
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.7";
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
  }
@@ -99,6 +99,15 @@ export class SunspecInverter extends BaseSunspecDevice {
99
99
  const inverterData = await this.sunspecClient.readInverterData();
100
100
  const mpptDataList = await this.sunspecClient.readAllMPPTData();
101
101
  if (inverterData) {
102
+ // Log what we're sending
103
+ console.log('Preparing Inverter Message:', {
104
+ pvPowerW: inverterData.dcPower,
105
+ acPower: inverterData.acPower,
106
+ voltageL1: inverterData.voltageAN,
107
+ voltageL2: inverterData.voltageBN,
108
+ voltageL3: inverterData.voltageCN,
109
+ stringsCount: mpptDataList.length
110
+ });
102
111
  const inverterMessage = {
103
112
  id: randomUUID(),
104
113
  message: EnyoDataBusMessageEnum.InverterValuesUpdateV1,
@@ -109,12 +118,12 @@ export class SunspecInverter extends BaseSunspecDevice {
109
118
  timestampIso: timestamp.toISOString(),
110
119
  resolution,
111
120
  data: {
112
- pvPowerW: inverterData.acPower || 0,
113
- activePowerLimitationW: inverterData.acPower || 0, // Using AC power as default
121
+ pvPowerW: inverterData.dcPower || 0, // Use DC power for PV power
122
+ activePowerLimitationW: inverterData.acPower || 0,
114
123
  state: this.mapOperatingState(inverterData.operatingState),
115
124
  voltageL1: inverterData.voltageAN || 0,
116
- voltageL2: inverterData.voltageBN,
117
- voltageL3: inverterData.voltageCN,
125
+ voltageL2: inverterData.voltageBN ?? undefined, // Use undefined if unimplemented phase
126
+ voltageL3: inverterData.voltageCN ?? undefined, // Use undefined if unimplemented phase
118
127
  strings: this.mapMPPTToStrings(mpptDataList)
119
128
  }
120
129
  };
@@ -148,11 +157,14 @@ export class SunspecInverter extends BaseSunspecDevice {
148
157
  mapMPPTToStrings(mpptDataList) {
149
158
  const result = [];
150
159
  mpptDataList.forEach((mppt, index) => {
151
- result.push({
152
- index: index + 1,
153
- voltage: mppt.dcVoltage,
154
- powerW: mppt.dcPower
155
- });
160
+ // Only include strings with valid data
161
+ if (mppt.dcVoltage !== undefined || mppt.dcPower !== undefined) {
162
+ result.push({
163
+ index: index + 1,
164
+ voltage: mppt.dcVoltage ?? undefined, // Use null if undefined
165
+ powerW: mppt.dcPower ?? 0 // Default to 0 if undefined
166
+ });
167
+ }
156
168
  });
157
169
  return result;
158
170
  }
@@ -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
  */
@@ -258,19 +276,36 @@ export class SunspecModbusClient {
258
276
  console.error('No inverter model found');
259
277
  return null;
260
278
  }
279
+ console.warn('IMPORTANT: Working with single-phase inverter, but 3-phase is expected!');
261
280
  return this.readSinglePhaseInverterData(singlePhaseModel);
262
281
  }
282
+ console.log(`Found 3-phase inverter model 103 at address ${model.address} with length ${model.length}`);
263
283
  const baseAddr = model.address + 2; // Skip ID and Length
264
284
  try {
265
285
  // Read all scale factors first using fault-tolerant reader
286
+ console.log(`Reading Inverter Data from Model 103 at base address: ${baseAddr}`);
266
287
  const scaleFactors = await this.readInverterScaleFactors(baseAddr);
267
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
+ });
268
298
  const data = {
269
299
  blockNumber: 103,
270
300
  blockAddress: model.address,
271
301
  blockLength: model.length,
272
302
  // AC Current values - Offsets 2-5
273
- acCurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 2, 1, 'uint16'), scaleFactors.A_SF),
303
+ acCurrent: this.applyScaleFactor(await (async () => {
304
+ const addr = baseAddr + 2;
305
+ const raw = await this.readRegisterValue(addr, 1, 'uint16');
306
+ console.log(`AC Current: address=${addr}, raw=0x${raw.toString(16).toUpperCase()} (${raw})`);
307
+ return raw;
308
+ })(), scaleFactors.A_SF),
274
309
  phaseACurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 3, 1, 'uint16'), scaleFactors.A_SF),
275
310
  phaseBCurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 4, 1, 'uint16'), scaleFactors.A_SF),
276
311
  phaseCCurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 5, 1, 'uint16'), scaleFactors.A_SF),
@@ -278,20 +313,41 @@ export class SunspecModbusClient {
278
313
  voltageAB: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 7, 1, 'uint16'), scaleFactors.V_SF),
279
314
  voltageBC: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 8, 1, 'uint16'), scaleFactors.V_SF),
280
315
  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),
316
+ // Apply scale factor with unimplemented value checking
317
+ voltageAN: this.applyScaleFactor(voltageANRaw, scaleFactors.V_SF),
318
+ voltageBN: this.applyScaleFactor(voltageBNRaw, scaleFactors.V_SF),
319
+ voltageCN: this.applyScaleFactor(voltageCNRaw, scaleFactors.V_SF),
284
320
  // Power values - Offsets 14, 18, 20, 22
285
- acPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 14, 1, 'int16'), scaleFactors.W_SF),
321
+ acPower: this.applyScaleFactor(await (async () => {
322
+ const addr = baseAddr + 14;
323
+ const raw = await this.readRegisterValue(addr, 1, 'int16');
324
+ console.log(`AC Power (W): address=${addr}, raw=0x${raw.toString(16).toUpperCase()} (${raw}), SF=${scaleFactors.W_SF}`);
325
+ return raw;
326
+ })(), scaleFactors.W_SF, 'int16'),
286
327
  apparentPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 18, 1, 'uint16'), scaleFactors.VA_SF),
287
328
  reactivePower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 20, 1, 'int16'), scaleFactors.VAr_SF),
288
329
  powerFactor: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 22, 1, 'int16'), scaleFactors.PF_SF),
289
330
  // Frequency - Offset 16
290
331
  frequency: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 16, 1, 'uint16'), scaleFactors.Hz_SF),
291
332
  // DC values - Offsets 27, 28, 30
292
- dcCurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 27, 1, 'uint16'), scaleFactors.DCA_SF),
293
- dcVoltage: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 28, 1, 'uint16'), scaleFactors.DCV_SF),
294
- dcPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 30, 1, 'int16'), scaleFactors.DCW_SF),
333
+ dcCurrent: this.applyScaleFactor(await (async () => {
334
+ const addr = baseAddr + 27;
335
+ const raw = await this.readRegisterValue(addr, 1, 'uint16');
336
+ console.log(`DC Current: address=${addr}, raw=0x${raw.toString(16).toUpperCase()} (${raw})`);
337
+ return raw;
338
+ })(), scaleFactors.DCA_SF),
339
+ dcVoltage: this.applyScaleFactor(await (async () => {
340
+ const addr = baseAddr + 28;
341
+ const raw = await this.readRegisterValue(addr, 1, 'uint16');
342
+ console.log(`DC Voltage: address=${addr}, raw=0x${raw.toString(16).toUpperCase()} (${raw})`);
343
+ return raw;
344
+ })(), scaleFactors.DCV_SF),
345
+ dcPower: this.applyScaleFactor(await (async () => {
346
+ const addr = baseAddr + 30;
347
+ const raw = await this.readRegisterValue(addr, 1, 'int16');
348
+ console.log(`DC Power: address=${addr}, raw=0x${raw.toString(16).toUpperCase()} (${raw}), SF=${scaleFactors.DCW_SF}`);
349
+ return raw;
350
+ })(), scaleFactors.DCW_SF, 'int16'),
295
351
  // Temperature values - Offsets 32, 34, 35, 36
296
352
  cabinetTemperature: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 32, 1, 'int16'), scaleFactors.Tmp_SF),
297
353
  heatSinkTemperature: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 34, 1, 'int16'), scaleFactors.Tmp_SF),
@@ -309,8 +365,21 @@ export class SunspecModbusClient {
309
365
  vendorEvents4: await this.readRegisterValue(baseAddr + 50, 2, 'uint32')
310
366
  };
311
367
  // Read AC Energy (32-bit accumulator) - Offset 24-25
312
- const acEnergy = await this.readRegisterValue(baseAddr + 24, 2, 'acc32');
368
+ const acEnergyAddr = baseAddr + 24;
369
+ const acEnergy = await this.readRegisterValue(acEnergyAddr, 2, 'acc32');
370
+ console.log(`AC Energy: address=${acEnergyAddr}, raw=0x${acEnergy.toString(16).toUpperCase()} (${acEnergy}), SF=${scaleFactors.WH_SF}`);
313
371
  data.acEnergy = BigInt(acEnergy) * BigInt(Math.pow(10, scaleFactors.WH_SF));
372
+ // Log final calculated values
373
+ console.log('Inverter Final Values:', {
374
+ acPower: data.acPower,
375
+ dcPower: data.dcPower,
376
+ voltageAN: data.voltageAN,
377
+ voltageBN: data.voltageBN,
378
+ voltageCN: data.voltageCN,
379
+ dcVoltage: data.dcVoltage,
380
+ dcCurrent: data.dcCurrent,
381
+ acCurrent: data.acCurrent
382
+ });
314
383
  return data;
315
384
  }
316
385
  catch (error) {
@@ -326,15 +395,53 @@ export class SunspecModbusClient {
326
395
  // Implementation would be similar but simplified
327
396
  const baseAddr = model.address + 2;
328
397
  try {
329
- // Simplified implementation for single phase
398
+ console.log(`Reading Single-Phase Inverter Data from Model 101 at base address: ${baseAddr}`);
399
+ // Read scale factors for single phase model
400
+ const scaleFactors = {
401
+ A_SF: await this.readRegisterValue(baseAddr + 6, 1, 'int16'),
402
+ V_SF: await this.readRegisterValue(baseAddr + 13, 1, 'int16'),
403
+ W_SF: await this.readRegisterValue(baseAddr + 10, 1, 'int16'),
404
+ Hz_SF: await this.readRegisterValue(baseAddr + 12, 1, 'int16'),
405
+ DCA_SF: await this.readRegisterValue(baseAddr + 18, 1, 'int16'),
406
+ DCV_SF: await this.readRegisterValue(baseAddr + 19, 1, 'int16'),
407
+ DCW_SF: await this.readRegisterValue(baseAddr + 21, 1, 'int16')
408
+ };
409
+ console.log('Single-Phase Inverter Scale Factors:', JSON.stringify(scaleFactors, null, 2));
410
+ // Read raw values with logging
411
+ const acCurrentAddr = baseAddr + 2;
412
+ const acCurrentRaw = await this.readRegisterValue(acCurrentAddr, 1, 'uint16');
413
+ console.log(`AC Current: address=${acCurrentAddr}, raw=0x${acCurrentRaw.toString(16).toUpperCase()} (${acCurrentRaw})`);
414
+ const voltageAddr = baseAddr + 7;
415
+ const voltageRaw = await this.readRegisterValue(voltageAddr, 1, 'uint16');
416
+ console.log(`AC Voltage: address=${voltageAddr}, raw=0x${voltageRaw.toString(16).toUpperCase()} (${voltageRaw})`);
417
+ const acPowerAddr = baseAddr + 9;
418
+ const acPowerRaw = await this.readRegisterValue(acPowerAddr, 1, 'int16');
419
+ console.log(`AC Power: address=${acPowerAddr}, raw=0x${acPowerRaw.toString(16).toUpperCase()} (${acPowerRaw}), SF=${scaleFactors.W_SF}`);
420
+ const freqAddr = baseAddr + 11;
421
+ const freqRaw = await this.readRegisterValue(freqAddr, 1, 'uint16');
422
+ console.log(`Frequency: address=${freqAddr}, raw=0x${freqRaw.toString(16).toUpperCase()} (${freqRaw})`);
423
+ const dcCurrentAddr = baseAddr + 14;
424
+ const dcCurrentRaw = await this.readRegisterValue(dcCurrentAddr, 1, 'uint16');
425
+ console.log(`DC Current: address=${dcCurrentAddr}, raw=0x${dcCurrentRaw.toString(16).toUpperCase()} (${dcCurrentRaw})`);
426
+ const dcVoltageAddr = baseAddr + 15;
427
+ const dcVoltageRaw = await this.readRegisterValue(dcVoltageAddr, 1, 'uint16');
428
+ console.log(`DC Voltage: address=${dcVoltageAddr}, raw=0x${dcVoltageRaw.toString(16).toUpperCase()} (${dcVoltageRaw})`);
429
+ const dcPowerAddr = baseAddr + 20;
430
+ const dcPowerRaw = await this.readRegisterValue(dcPowerAddr, 1, 'int16');
431
+ console.log(`DC Power: address=${dcPowerAddr}, raw=0x${dcPowerRaw.toString(16).toUpperCase()} (${dcPowerRaw}), SF=${scaleFactors.DCW_SF}`);
432
+ const stateAddr = baseAddr + 24;
433
+ const stateRaw = await this.readRegisterValue(stateAddr, 1, 'uint16');
434
+ console.log(`Operating State: address=${stateAddr}, raw=0x${stateRaw.toString(16).toUpperCase()} (${stateRaw})`);
330
435
  return {
331
436
  blockNumber: 101,
332
- voltageAN: await this.readRegisterWithScaleFactor(baseAddr + 7, baseAddr + 13), // PhVphA with V_SF
333
- acCurrent: await this.readRegisterWithScaleFactor(baseAddr + 2, baseAddr + 6),
334
- acPower: await this.readRegisterWithScaleFactor(baseAddr + 9, baseAddr + 10),
335
- frequency: await this.readRegisterWithScaleFactor(baseAddr + 11, baseAddr + 12),
336
- dcPower: await this.readRegisterWithScaleFactor(baseAddr + 20, baseAddr + 21),
337
- operatingState: await this.readRegisterValue(baseAddr + 24, 1)
437
+ voltageAN: this.applyScaleFactor(voltageRaw, scaleFactors.V_SF),
438
+ acCurrent: this.applyScaleFactor(acCurrentRaw, scaleFactors.A_SF),
439
+ acPower: this.applyScaleFactor(acPowerRaw, scaleFactors.W_SF, 'int16'),
440
+ frequency: this.applyScaleFactor(freqRaw, scaleFactors.Hz_SF),
441
+ dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF),
442
+ dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF),
443
+ dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF, 'int16'),
444
+ operatingState: stateRaw
338
445
  };
339
446
  }
340
447
  catch (error) {
@@ -346,7 +453,7 @@ export class SunspecModbusClient {
346
453
  * Read inverter scale factors
347
454
  */
348
455
  async readInverterScaleFactors(baseAddr) {
349
- return {
456
+ const scaleFactors = {
350
457
  A_SF: await this.readRegisterValue(baseAddr + 6, 1, 'int16'), // Offset 6
351
458
  V_SF: await this.readRegisterValue(baseAddr + 13, 1, 'int16'), // Offset 13
352
459
  W_SF: await this.readRegisterValue(baseAddr + 15, 1, 'int16'), // Offset 15
@@ -360,11 +467,18 @@ export class SunspecModbusClient {
360
467
  DCW_SF: await this.readRegisterValue(baseAddr + 31, 1, 'int16'), // Offset 31
361
468
  Tmp_SF: await this.readRegisterValue(baseAddr + 36, 1, 'int16') // Offset 36
362
469
  };
470
+ console.log('Inverter Scale Factors:', JSON.stringify(scaleFactors, null, 2));
471
+ return scaleFactors;
363
472
  }
364
473
  /**
365
474
  * Apply scale factor to a value
475
+ * Returns undefined if the value is unimplemented or scale factor is out of range
366
476
  */
367
- applyScaleFactor(value, scaleFactor) {
477
+ applyScaleFactor(value, scaleFactor, dataType = 'uint16') {
478
+ // Check for unimplemented values
479
+ if (this.isUnimplementedValue(value, dataType)) {
480
+ return undefined;
481
+ }
368
482
  return value * Math.pow(10, scaleFactor);
369
483
  }
370
484
  /**
@@ -390,6 +504,28 @@ export class SunspecModbusClient {
390
504
  DCWH_SF: await this.readRegisterValue(moduleAddr + 17, 1, 'int16'),
391
505
  Tmp_SF: await this.readRegisterValue(moduleAddr + 21, 1, 'int16')
392
506
  };
507
+ console.log(`MPPT Module ${moduleId} Scale Factors:`, JSON.stringify(scaleFactors, null, 2));
508
+ // Read raw values first
509
+ const dcCurrentRaw = await this.readRegisterValue(moduleAddr + 9, 1, 'uint16');
510
+ const dcVoltageRaw = await this.readRegisterValue(moduleAddr + 11, 1, 'uint16');
511
+ const dcPowerRaw = await this.readRegisterValue(moduleAddr + 13, 1, 'uint16');
512
+ const dcEnergyRaw = await this.readRegisterValue(moduleAddr + 15, 2, 'acc32');
513
+ const temperatureRaw = await this.readRegisterValue(moduleAddr + 20, 1, 'int16');
514
+ console.log(`MPPT Module ${moduleId} Raw Values:`, {
515
+ dcCurrentRaw: `0x${dcCurrentRaw.toString(16).toUpperCase()} (${dcCurrentRaw})`,
516
+ dcVoltageRaw: `0x${dcVoltageRaw.toString(16).toUpperCase()} (${dcVoltageRaw})`,
517
+ dcPowerRaw: `0x${dcPowerRaw.toString(16).toUpperCase()} (${dcPowerRaw})`,
518
+ dcEnergyRaw: `0x${dcEnergyRaw.toString(16).toUpperCase()} (${dcEnergyRaw})`,
519
+ temperatureRaw: `0x${temperatureRaw.toString(16).toUpperCase()} (${temperatureRaw})`
520
+ });
521
+ // Check if this module is actually implemented/connected
522
+ // If all key values are unimplemented, this module doesn't exist
523
+ if (this.isUnimplementedValue(dcCurrentRaw, 'uint16') &&
524
+ this.isUnimplementedValue(dcVoltageRaw, 'uint16') &&
525
+ this.isUnimplementedValue(dcPowerRaw, 'uint16')) {
526
+ console.log(`MPPT module ${moduleId} appears to be unconnected (all values are 0xFFFF)`);
527
+ return null;
528
+ }
393
529
  const data = {
394
530
  blockNumber: 160,
395
531
  blockAddress: model.address,
@@ -398,22 +534,24 @@ export class SunspecModbusClient {
398
534
  // String ID - Offset 1 (8 registers for string)
399
535
  stringId: await this.readRegisterValue(moduleAddr + 1, 8, 'string'),
400
536
  // DC Current - Offset 9
401
- dcCurrent: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 9, 1, 'uint16'), scaleFactors.DCA_SF),
537
+ dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF),
402
538
  dcCurrentSF: scaleFactors.DCA_SF,
403
539
  // DC Voltage - Offset 11
404
- dcVoltage: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 11, 1, 'uint16'), scaleFactors.DCV_SF),
540
+ dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF),
405
541
  dcVoltageSF: scaleFactors.DCV_SF,
406
542
  // DC Power - Offset 13
407
- dcPower: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 13, 1, 'uint16'), scaleFactors.DCW_SF),
543
+ dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF),
408
544
  dcPowerSF: scaleFactors.DCW_SF,
409
545
  // 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)),
546
+ // Only calculate if value is not unimplemented
547
+ dcEnergy: !this.isUnimplementedValue(dcEnergyRaw, 'uint32')
548
+ ? BigInt(dcEnergyRaw) * BigInt(Math.pow(10, scaleFactors.DCWH_SF))
549
+ : undefined,
412
550
  dcEnergySF: scaleFactors.DCWH_SF,
413
551
  // Timestamp - Offset 18-19 (32-bit)
414
552
  timestamp: await this.readRegisterValue(moduleAddr + 18, 2, 'uint32'),
415
553
  // Temperature - Offset 20
416
- temperature: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 20, 1, 'int16'), scaleFactors.Tmp_SF),
554
+ temperature: this.applyScaleFactor(temperatureRaw, scaleFactors.Tmp_SF, 'int16'),
417
555
  temperatureSF: scaleFactors.Tmp_SF,
418
556
  // Operating State - Offset 22
419
557
  operatingState: await this.readRegisterValue(moduleAddr + 22, 1, 'uint16'),
@@ -436,9 +574,19 @@ export class SunspecModbusClient {
436
574
  const mpptData = [];
437
575
  // Try to read up to 4 MPPT strings (typical maximum)
438
576
  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);
577
+ try {
578
+ const data = await this.readMPPTData(i);
579
+ // Only include if we got valid data (not null) and it has actual values
580
+ if (data &&
581
+ (data.dcCurrent !== undefined ||
582
+ data.dcVoltage !== undefined ||
583
+ data.dcPower !== undefined)) {
584
+ mpptData.push(data);
585
+ }
586
+ }
587
+ catch (error) {
588
+ console.debug(`Could not read MPPT module ${i}: ${error}`);
589
+ // Continue to try other modules
442
590
  }
443
591
  }
444
592
  return mpptData;
@@ -459,13 +607,47 @@ export class SunspecModbusClient {
459
607
  return null;
460
608
  }
461
609
  const baseAddr = model.address + 2;
610
+ console.log(`Reading Meter Data from Model ${model.id} at base address: ${baseAddr}`);
462
611
  try {
463
- // This is a simplified implementation
464
- // Actual register offsets depend on specific meter model
612
+ // Model 201 (single phase) and 203 (3-phase) have different offsets
613
+ // Model 201: Power at offset 12, Frequency at offset 18
614
+ // Model 203: Power at offset 14, Frequency at offset 24
615
+ const isPowerMeter201 = model.id === 201;
616
+ const powerOffset = isPowerMeter201 ? 12 : 14; // Total Real Power offset
617
+ const powerSFOffset = isPowerMeter201 ? 15 : 17; // Power scale factor offset
618
+ const freqOffset = isPowerMeter201 ? 18 : 24; // Frequency offset
619
+ const freqSFOffset = isPowerMeter201 ? 21 : 27; // Frequency scale factor offset
620
+ const exportOffset = isPowerMeter201 ? 32 : 38; // Total Wh Exported offset
621
+ const importOffset = isPowerMeter201 ? 40 : 46; // Total Wh Imported offset
622
+ const energySFOffset = 4; // Energy scale factor is typically at offset 4
623
+ // Read scale factors
624
+ const powerSF = await this.readRegisterValue(baseAddr + powerSFOffset, 1, 'int16');
625
+ const freqSF = await this.readRegisterValue(baseAddr + freqSFOffset, 1, 'int16');
626
+ const energySF = await this.readRegisterValue(baseAddr + energySFOffset, 1, 'int16');
627
+ console.log(`Meter Scale Factors: Power_SF=${powerSF}, Freq_SF=${freqSF}, Energy_SF=${energySF}`);
628
+ // Read raw values
629
+ const powerAddr = baseAddr + powerOffset;
630
+ const powerRaw = await this.readRegisterValue(powerAddr, 1, 'int16');
631
+ console.log(`Meter Total Power: address=${powerAddr}, raw=0x${powerRaw.toString(16).toUpperCase()} (${powerRaw}), SF=${powerSF}`);
632
+ const freqAddr = baseAddr + freqOffset;
633
+ const freqRaw = await this.readRegisterValue(freqAddr, 1, 'uint16');
634
+ console.log(`Meter Frequency: address=${freqAddr}, raw=0x${freqRaw.toString(16).toUpperCase()} (${freqRaw}), SF=${freqSF}`);
635
+ const exportAddr = baseAddr + exportOffset;
636
+ const exportRaw = await this.readRegisterValue(exportAddr, 2, 'acc32');
637
+ console.log(`Meter Export Energy: address=${exportAddr}, raw=0x${exportRaw.toString(16).toUpperCase()} (${exportRaw}), SF=${energySF}`);
638
+ const importAddr = baseAddr + importOffset;
639
+ const importRaw = await this.readRegisterValue(importAddr, 2, 'acc32');
640
+ console.log(`Meter Import Energy: address=${importAddr}, raw=0x${importRaw.toString(16).toUpperCase()} (${importRaw}), SF=${energySF}`);
465
641
  return {
466
642
  blockNumber: model.id,
467
- totalPower: await this.readRegisterWithScaleFactor(baseAddr + 10, baseAddr + 11),
468
- frequency: await this.readRegisterWithScaleFactor(baseAddr + 20, baseAddr + 21)
643
+ totalPower: this.applyScaleFactor(powerRaw, powerSF, 'int16'),
644
+ frequency: this.applyScaleFactor(freqRaw, freqSF),
645
+ exportedEnergy: !this.isUnimplementedValue(exportRaw, 'uint32')
646
+ ? BigInt(exportRaw) * BigInt(Math.pow(10, energySF))
647
+ : undefined,
648
+ importedEnergy: !this.isUnimplementedValue(importRaw, 'uint32')
649
+ ? BigInt(importRaw) * BigInt(Math.pow(10, energySF))
650
+ : undefined
469
651
  };
470
652
  }
471
653
  catch (error) {
@@ -483,15 +665,31 @@ export class SunspecModbusClient {
483
665
  return null;
484
666
  }
485
667
  const baseAddr = model.address + 2; // Skip ID and Length
668
+ console.log(`Reading Common Block - Model address: ${model.address}, Base address for data: ${baseAddr}`);
486
669
  try {
487
670
  // 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 {
671
+ // Common block offsets are relative to the start of the data (after ID and Length)
672
+ const manufacturerAddr = baseAddr; // Offset 0-15 (16 registers) from data start
673
+ const modelAddr = baseAddr + 16; // Offset 16-31 (16 registers) from data start
674
+ const optionsAddr = baseAddr + 32; // Offset 32-39 (8 registers) from data start
675
+ const versionAddr = baseAddr + 40; // Offset 40-47 (8 registers) from data start
676
+ const serialAddr = baseAddr + 48; // Offset 48-63 (16 registers) from data start
677
+ const deviceAddrAddr = baseAddr + 64; // Offset 64 from data start
678
+ console.log(`Reading manufacturer from address ${manufacturerAddr} (16 registers)`);
679
+ const manufacturer = await this.readRegisterValue(manufacturerAddr, 16, 'string');
680
+ console.log(`Manufacturer raw value: "${manufacturer}"`);
681
+ console.log(`Reading model from address ${modelAddr} (16 registers)`);
682
+ const modelName = await this.readRegisterValue(modelAddr, 16, 'string');
683
+ console.log(`Model raw value: "${modelName}"`);
684
+ console.log(`Reading options from address ${optionsAddr} (8 registers)`);
685
+ const options = await this.readRegisterValue(optionsAddr, 8, 'string');
686
+ console.log(`Reading version from address ${versionAddr} (8 registers)`);
687
+ const version = await this.readRegisterValue(versionAddr, 8, 'string');
688
+ console.log(`Reading serial from address ${serialAddr} (16 registers)`);
689
+ const serialNumber = await this.readRegisterValue(serialAddr, 16, 'string');
690
+ console.log(`Reading device address from address ${deviceAddrAddr}`);
691
+ const deviceAddress = await this.readRegisterValue(deviceAddrAddr, 1, 'uint16');
692
+ const result = {
495
693
  manufacturer: manufacturer,
496
694
  model: modelName,
497
695
  options: options,
@@ -499,6 +697,8 @@ export class SunspecModbusClient {
499
697
  serialNumber: serialNumber,
500
698
  deviceAddress: deviceAddress
501
699
  };
700
+ console.log('Common Block Data:', JSON.stringify(result, null, 2));
701
+ return result;
502
702
  }
503
703
  catch (error) {
504
704
  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.7";
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.7';
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.7",
4
4
  "description": "enyo Energy Sunspec SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",