Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor SunSpec model types #18

Merged
merged 1 commit into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading