Skip to content

Commit

Permalink
Merge pull request #18 from longzheng/sunspec-models
Browse files Browse the repository at this point in the history
Refactor SunSpec model types
  • Loading branch information
longzheng authored Sep 8, 2024
2 parents d507fad + d5b6bc2 commit c9f8947
Show file tree
Hide file tree
Showing 13 changed files with 88 additions and 65 deletions.
4 changes: 0 additions & 4 deletions src/sunspec/connection/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
20 changes: 0 additions & 20 deletions src/sunspec/connection/inverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
Expand All @@ -76,10 +68,6 @@ export class InverterSunSpecConnection extends SunSpecConnection {
address,
});

if (data.ID !== 121) {
throw new Error('Not a SunSpec settings model');
}

return data;
}

Expand All @@ -97,10 +85,6 @@ export class InverterSunSpecConnection extends SunSpecConnection {
address,
});

if (data.ID !== 122) {
throw new Error('Not a SunSpec status model');
}

return data;
}

Expand All @@ -118,10 +102,6 @@ export class InverterSunSpecConnection extends SunSpecConnection {
address,
});

if (data.ID !== 123) {
throw new Error('Not a SunSpec controls model');
}

return data;
}

Expand Down
4 changes: 0 additions & 4 deletions src/sunspec/connection/meter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
29 changes: 29 additions & 0 deletions src/sunspec/helpers/converters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
int16ToRegisters,
registersToAcc32,
registersToAcc64BigInt,
registersToId,
registersToInt16,
registersToInt16Nullable,
registersToString,
Expand Down Expand Up @@ -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',
);
});
});
24 changes: 24 additions & 0 deletions src/sunspec/helpers/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,27 @@ export function registersToAcc64BigInt(registers: number[]): bigint {
BigInt(registers[3]!)
);
}

export function registersToId<ID extends number>(
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}`,
);
}
32 changes: 14 additions & 18 deletions src/sunspec/helpers/sitePhases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
}
5 changes: 3 additions & 2 deletions src/sunspec/models/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
registersToId,
registersToString,
registersToStringNullable,
registersToUint16,
Expand All @@ -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
Expand All @@ -32,7 +33,7 @@ export const commonModel = sunSpecModelFactory<CommonModel>({
ID: {
start: 0,
end: 1,
readConverter: registersToUint16,
readConverter: (value) => registersToId(value, 1),
},
L: {
start: 1,
Expand Down
6 changes: 3 additions & 3 deletions src/sunspec/models/controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -103,7 +103,7 @@ export const controlsModel = sunSpecModelFactory<
ID: {
start: 0,
end: 1,
readConverter: registersToUint16,
readConverter: (value) => registersToId(value, 123),
},
L: {
start: 1,
Expand Down
5 changes: 3 additions & 2 deletions src/sunspec/models/inverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
registersToInt16Nullable,
registersToSunssfNullable,
registersToUint32Nullable,
registersToId,
} from '../helpers/converters';
import { sunSpecModelFactory } from './sunSpecModelFactory';

Expand All @@ -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
Expand Down Expand Up @@ -101,7 +102,7 @@ export const inverterModel = sunSpecModelFactory<InverterModel>({
ID: {
start: 0,
end: 1,
readConverter: registersToUint16,
readConverter: (value) => registersToId(value, [101, 102, 103]),
},
L: {
start: 1,
Expand Down
5 changes: 3 additions & 2 deletions src/sunspec/models/meter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import {
registersToInt16Nullable,
registersToSunssfNullable,
registersToAcc32,
registersToId,
} from '../helpers/converters';
import { sunSpecModelFactory } from './sunSpecModelFactory';

// https://sunspec.org/wp-content/uploads/2021/12/SunSpec_Information_Model_Reference_20211209.xlsx
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
Expand Down Expand Up @@ -157,7 +158,7 @@ export const meterModel = sunSpecModelFactory<MeterModel>({
ID: {
start: 0,
end: 1,
readConverter: registersToUint16,
readConverter: (value) => registersToId(value, [201, 202, 203]),
},
L: {
start: 1,
Expand Down
6 changes: 3 additions & 3 deletions src/sunspec/models/nameplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import {
registersToUint16Nullable,
registersToSunssfNullable,
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 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.
Expand Down Expand Up @@ -74,7 +74,7 @@ export const nameplateModel = sunSpecModelFactory<NameplateModel>({
ID: {
start: 0,
end: 1,
readConverter: registersToUint16,
readConverter: (value) => registersToId(value, 120),
},
L: {
start: 1,
Expand Down
7 changes: 3 additions & 4 deletions src/sunspec/models/settings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
registersToId,
registersToInt16,
registersToInt16Nullable,
registersToSunssf,
Expand All @@ -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.
Expand Down Expand Up @@ -85,7 +84,7 @@ export const settingsModel = sunSpecModelFactory<SettingsModel>({
ID: {
start: 0,
end: 1,
readConverter: registersToUint16,
readConverter: (value) => registersToId(value, 121),
},
L: {
start: 1,
Expand Down
6 changes: 3 additions & 3 deletions src/sunspec/models/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -64,7 +64,7 @@ export const statusModel = sunSpecModelFactory<StatusModel>({
ID: {
start: 0,
end: 1,
readConverter: registersToUint16,
readConverter: (value) => registersToId(value, 122),
},
L: {
start: 1,
Expand Down

0 comments on commit c9f8947

Please sign in to comment.