diff --git a/src/sunspec/connection/base.ts b/src/sunspec/connection/base.ts index 7b8e93a..f669661 100644 --- a/src/sunspec/connection/base.ts +++ b/src/sunspec/connection/base.ts @@ -174,10 +174,6 @@ export abstract class SunSpecConnection { address, }); - if (data.ID !== 1) { - throw new Error('Not a SunSpec common model'); - } - // cache common model // this is not expected to ever change so it can be persisted this.commonModelCache = data; diff --git a/src/sunspec/connection/inverter.ts b/src/sunspec/connection/inverter.ts index 9d3fa00..a92d09d 100644 --- a/src/sunspec/connection/inverter.ts +++ b/src/sunspec/connection/inverter.ts @@ -28,10 +28,6 @@ export class InverterSunSpecConnection extends SunSpecConnection { address, }); - if (data.ID !== 101 && data.ID !== 102 && data.ID !== 103) { - throw new Error('Not a SunSpec inverter monitoring model'); - } - return data; } @@ -53,10 +49,6 @@ export class InverterSunSpecConnection extends SunSpecConnection { address, }); - if (data.ID !== 120) { - throw new Error('Not a SunSpec nameplate model'); - } - this.nameplateModelCache = data; return data; @@ -76,10 +68,6 @@ export class InverterSunSpecConnection extends SunSpecConnection { address, }); - if (data.ID !== 121) { - throw new Error('Not a SunSpec settings model'); - } - return data; } @@ -97,10 +85,6 @@ export class InverterSunSpecConnection extends SunSpecConnection { address, }); - if (data.ID !== 122) { - throw new Error('Not a SunSpec status model'); - } - return data; } @@ -118,10 +102,6 @@ export class InverterSunSpecConnection extends SunSpecConnection { address, }); - if (data.ID !== 123) { - throw new Error('Not a SunSpec controls model'); - } - return data; } diff --git a/src/sunspec/connection/meter.ts b/src/sunspec/connection/meter.ts index 7be6afb..2d65b0b 100644 --- a/src/sunspec/connection/meter.ts +++ b/src/sunspec/connection/meter.ts @@ -19,10 +19,6 @@ export class MeterSunSpecConnection extends SunSpecConnection { address, }); - if (data.ID !== 201 && data.ID !== 202 && data.ID !== 203) { - throw new Error('Not a SunSpec meter model'); - } - return data; } } diff --git a/src/sunspec/helpers/converters.test.ts b/src/sunspec/helpers/converters.test.ts index cb2a999..9e4a92a 100644 --- a/src/sunspec/helpers/converters.test.ts +++ b/src/sunspec/helpers/converters.test.ts @@ -4,6 +4,7 @@ import { int16ToRegisters, registersToAcc32, registersToAcc64BigInt, + registersToId, registersToInt16, registersToInt16Nullable, registersToString, @@ -122,3 +123,31 @@ it('int16ToRegistersNullable should convert null to registers', () => { const result = int16NullableToRegisters(value); expect(result).toEqual([0x8000]); }); + +describe('registersToId', () => { + it('registersToId should convert register to a ID number', () => { + const registers = [0x0002]; + const result = registersToId(registers, 2); + expect(result).toBe(2); + }); + + it('registersToId should throw if register does not match ID number', () => { + const registers = [0x0004]; + expect(() => registersToId(registers, 2)).toThrowError( + 'Invalid model ID value', + ); + }); + + it('registersToId should convert register to a ID number array', () => { + const registers = [0x0003]; + const result = registersToId(registers, [2, 3, 4]); + expect(result).toBe(3); + }); + + it('registersToId should throw if register does not match ID number array', () => { + const registers = [0x0005]; + expect(() => registersToId(registers, [2, 3, 4])).toThrowError( + 'Invalid model ID value', + ); + }); +}); diff --git a/src/sunspec/helpers/converters.ts b/src/sunspec/helpers/converters.ts index 1d1f599..d80a414 100644 --- a/src/sunspec/helpers/converters.ts +++ b/src/sunspec/helpers/converters.ts @@ -141,3 +141,27 @@ export function registersToAcc64BigInt(registers: number[]): bigint { BigInt(registers[3]!) ); } + +export function registersToId( + registers: number[], + value: ID | ID[], +): ID { + const registerValue = registersToUint16(registers); + + if (typeof value === 'number') { + if (registerValue !== value) { + throw new Error( + `Invalid model ID value, expected ${value}, got ${registerValue}`, + ); + } + return value; + } + + if (value.some((v) => v === registerValue)) { + return registerValue as ID; + } + + throw new Error( + `Invalid model ID value, expected one of ${value.join('/')}, got ${registerValue}`, + ); +} diff --git a/src/sunspec/helpers/sitePhases.ts b/src/sunspec/helpers/sitePhases.ts index 2d6df02..c5fb7db 100644 --- a/src/sunspec/helpers/sitePhases.ts +++ b/src/sunspec/helpers/sitePhases.ts @@ -3,27 +3,23 @@ import type { InverterModel } from '../models/inverter'; import { type MeterModel } from '../models/meter'; export function getSitePhasesFromMeter(meter: MeterModel): SitePhases { - if (meter.ID === 201) { - return 'singlePhase'; + switch (meter.ID) { + case 201: + return 'singlePhase'; + case 202: + return 'splitPhase'; + case 203: + return 'threePhase'; } - if (meter.ID === 202) { - return 'splitPhase'; - } - if (meter.ID === 203) { - return 'threePhase'; - } - throw new Error(`Unknown meter SunSpec model ID ${meter.ID}`); } export function getSitePhasesFromInverter(inverter: InverterModel): SitePhases { - if (inverter.ID === 101) { - return 'singlePhase'; - } - if (inverter.ID === 102) { - return 'splitPhase'; - } - if (inverter.ID === 103) { - return 'threePhase'; + switch (inverter.ID) { + case 101: + return 'singlePhase'; + case 102: + return 'splitPhase'; + case 103: + return 'threePhase'; } - throw new Error(`Unknown inverter SunSpec model ID ${inverter.ID}`); } diff --git a/src/sunspec/models/common.ts b/src/sunspec/models/common.ts index 61568d9..7451744 100644 --- a/src/sunspec/models/common.ts +++ b/src/sunspec/models/common.ts @@ -1,4 +1,5 @@ import { + registersToId, registersToString, registersToStringNullable, registersToUint16, @@ -9,7 +10,7 @@ import { sunSpecModelFactory } from './sunSpecModelFactory'; // https://sunspec.org/wp-content/uploads/2021/12/SunSpec_Information_Model_Reference_20211209.xlsx export type CommonModel = { // Length of sunspec model common (1) - ID: number; + ID: 1; // Length of sunspec model common (1) L: number; // Manufacturer @@ -32,7 +33,7 @@ export const commonModel = sunSpecModelFactory({ ID: { start: 0, end: 1, - readConverter: registersToUint16, + readConverter: (value) => registersToId(value, 1), }, L: { start: 1, diff --git a/src/sunspec/models/controls.ts b/src/sunspec/models/controls.ts index 36a8600..15c47a5 100644 --- a/src/sunspec/models/controls.ts +++ b/src/sunspec/models/controls.ts @@ -9,14 +9,14 @@ import { registersToInt16Nullable, int16NullableToRegisters, registersToSunssfNullable, + registersToId, } from '../helpers/converters'; import { sunSpecModelFactory } from './sunSpecModelFactory'; // https://sunspec.org/wp-content/uploads/2021/12/SunSpec_Information_Model_Reference_20211209.xlsx export type ControlsModel = { // Model identifier - // 123 is Immediate Inverter Controls - ID: number; + ID: 123; // Model length L: number; // Time window for connect/disconnect @@ -103,7 +103,7 @@ export const controlsModel = sunSpecModelFactory< ID: { start: 0, end: 1, - readConverter: registersToUint16, + readConverter: (value) => registersToId(value, 123), }, L: { start: 1, diff --git a/src/sunspec/models/inverter.ts b/src/sunspec/models/inverter.ts index 46bfc4c..2b3f915 100644 --- a/src/sunspec/models/inverter.ts +++ b/src/sunspec/models/inverter.ts @@ -8,6 +8,7 @@ import { registersToInt16Nullable, registersToSunssfNullable, registersToUint32Nullable, + registersToId, } from '../helpers/converters'; import { sunSpecModelFactory } from './sunSpecModelFactory'; @@ -16,7 +17,7 @@ export type InverterModel = { // Model identifier // Well-known value. Uniquely identifies this as a sunspec model inverter monitoring // 101 is single phase, 102 is split phase, 103 is three phase - ID: number; + ID: 101 | 102 | 103; // Model length L: number; // AC Current @@ -101,7 +102,7 @@ export const inverterModel = sunSpecModelFactory({ ID: { start: 0, end: 1, - readConverter: registersToUint16, + readConverter: (value) => registersToId(value, [101, 102, 103]), }, L: { start: 1, diff --git a/src/sunspec/models/meter.ts b/src/sunspec/models/meter.ts index f47ea12..531ddcb 100644 --- a/src/sunspec/models/meter.ts +++ b/src/sunspec/models/meter.ts @@ -5,6 +5,7 @@ import { registersToInt16Nullable, registersToSunssfNullable, registersToAcc32, + registersToId, } from '../helpers/converters'; import { sunSpecModelFactory } from './sunSpecModelFactory'; @@ -12,7 +13,7 @@ import { sunSpecModelFactory } from './sunSpecModelFactory'; export type MeterModel = { // Model identifier // 201: single phase, 202: split phase, 203: three phases - ID: number; + ID: 201 | 202 | 203; // Model length L: number; // AC Current @@ -157,7 +158,7 @@ export const meterModel = sunSpecModelFactory({ ID: { start: 0, end: 1, - readConverter: registersToUint16, + readConverter: (value) => registersToId(value, [201, 202, 203]), }, L: { start: 1, diff --git a/src/sunspec/models/nameplate.ts b/src/sunspec/models/nameplate.ts index 643cd00..7d01559 100644 --- a/src/sunspec/models/nameplate.ts +++ b/src/sunspec/models/nameplate.ts @@ -5,6 +5,7 @@ import { registersToUint16Nullable, registersToSunssfNullable, registersToInt16Nullable, + registersToId, } from '../helpers/converters'; import { sunSpecModelFactory } from './sunSpecModelFactory'; @@ -12,8 +13,7 @@ import { sunSpecModelFactory } from './sunSpecModelFactory'; export type NameplateModel = { // Model identifier // Well-known value. Uniquely identifies this as a sunspec model nameplate - // 120 is nameplate - ID: number; + ID: 120; // Model length L: number; // Type of DER device. Default value is 4 to indicate PV device. @@ -74,7 +74,7 @@ export const nameplateModel = sunSpecModelFactory({ ID: { start: 0, end: 1, - readConverter: registersToUint16, + readConverter: (value) => registersToId(value, 120), }, L: { start: 1, diff --git a/src/sunspec/models/settings.ts b/src/sunspec/models/settings.ts index 564af69..930f8c7 100644 --- a/src/sunspec/models/settings.ts +++ b/src/sunspec/models/settings.ts @@ -1,4 +1,5 @@ import { + registersToId, registersToInt16, registersToInt16Nullable, registersToSunssf, @@ -10,11 +11,9 @@ import { sunSpecModelFactory } from './sunSpecModelFactory'; // https://sunspec.org/wp-content/uploads/2021/12/SunSpec_Information_Model_Reference_20211209.xlsx export type SettingsModel = { - // Address Offset // Model identifier // Well-known value. Uniquely identifies this as a sunspec model nameplate - // 121 is inverter controls basic settings - ID: number; + ID: 121; // Model length L: number; // Setting for maximum power output. Default to WRtg. @@ -85,7 +84,7 @@ export const settingsModel = sunSpecModelFactory({ ID: { start: 0, end: 1, - readConverter: registersToUint16, + readConverter: (value) => registersToId(value, 121), }, L: { start: 1, diff --git a/src/sunspec/models/status.ts b/src/sunspec/models/status.ts index 8dc8485..381147c 100644 --- a/src/sunspec/models/status.ts +++ b/src/sunspec/models/status.ts @@ -6,14 +6,14 @@ import { registersToUint32Nullable, registersToStringNullable, registersToInt16Nullable, + registersToId, } from '../helpers/converters'; import { sunSpecModelFactory } from './sunSpecModelFactory'; // https://sunspec.org/wp-content/uploads/2021/12/SunSpec_Information_Model_Reference_20211209.xlsx export type StatusModel = { // Model identifier - // 122 is Inverter Controls Extended Measurements and Status - ID: number; + ID: 122; // Model length L: number; // PV inverter present/available status. Enumerated value. @@ -64,7 +64,7 @@ export const statusModel = sunSpecModelFactory({ ID: { start: 0, end: 1, - readConverter: registersToUint16, + readConverter: (value) => registersToId(value, 122), }, L: { start: 1,