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

Growatt support #56

Open
wants to merge 36 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5c95e7b
Fix names
longzheng Oct 26, 2024
8f7faf4
Add Growatt meter
longzheng Oct 26, 2024
26d59e4
Fix typo
longzheng Oct 26, 2024
bf05b7b
Add Growatt meter
longzheng Oct 26, 2024
85ced2c
Fix input registers
longzheng Oct 26, 2024
23e6353
Merge branch 'main' into growatt
longzheng Oct 26, 2024
a4fadc9
Fix helpers
longzheng Oct 26, 2024
4903ee7
Merge branch 'main' into growatt
longzheng Oct 26, 2024
cc9c64d
Initial Growatt inverter support
longzheng Oct 26, 2024
0b66a3e
Update config.schema.json
longzheng Oct 26, 2024
75ad8d7
Merge branch 'modbus-connection' into growatt
longzheng Oct 26, 2024
b1927cf
Refactor GrowattConnection to use new ModbusConnection
longzheng Oct 26, 2024
b12c4e0
Fix growatt inverter
longzheng Oct 26, 2024
5afe4d9
Merge branch 'modbus-connection' into growatt
longzheng Oct 26, 2024
3fd84c7
Merge fixes
longzheng Oct 26, 2024
2d9590d
Add async to function
longzheng Oct 27, 2024
6c4d648
Merge branch 'main' into growatt
longzheng Oct 28, 2024
5b95b39
Return target solar watts for inverter configuration
longzheng Oct 28, 2024
a95077a
Typo
longzheng Oct 28, 2024
5cd25ae
Rename file
longzheng Oct 28, 2024
176cf4a
Merge branch 'main' into growatt
longzheng Oct 30, 2024
dec0b96
Merge branch 'main' into growatt
longzheng Nov 1, 2024
1b94c68
Merge branch 'main' into growatt
longzheng Nov 3, 2024
378907e
Growatt inverter control experiment
longzheng Nov 3, 2024
374a8e6
Clamp value
longzheng Nov 3, 2024
f0e7a93
Merge branch 'main' into growatt
longzheng Nov 7, 2024
086ad7d
Merge branch 'main' into growatt
longzheng Nov 9, 2024
f1f0c5d
Lint
longzheng Nov 9, 2024
49b03a7
Merge branch 'main' into growatt
longzheng Nov 11, 2024
5a2b884
Merge branch 'main' into growatt
longzheng Nov 11, 2024
3ab7bdd
Merge fixes
longzheng Nov 11, 2024
11f032b
Merge branch 'main' into growatt
longzheng Nov 12, 2024
ff14dd5
Fix types
longzheng Nov 12, 2024
e1c1f77
Merge branch 'main' into growatt
longzheng Nov 18, 2024
d564c2a
Fix return types
longzheng Nov 18, 2024
0f555f8
Merge branch 'main' into growatt
longzheng Dec 4, 2024
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
42 changes: 42 additions & 0 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,27 @@
],
"additionalProperties": false,
"description": "SMA inverter configuration"
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "growatt"
},
"connection": {
"$ref": "#/definitions/config/properties/inverters/items/anyOf/0/properties/connection"
},
"unitId": {
"$ref": "#/definitions/config/properties/inverters/items/anyOf/0/properties/unitId"
}
},
"required": [
"type",
"connection"
],
"additionalProperties": false,
"description": "Growatt inverter configuration"
}
]
},
Expand Down Expand Up @@ -349,6 +370,27 @@
"additionalProperties": false,
"description": "SMA meter configuration"
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "growatt"
},
"connection": {
"$ref": "#/definitions/config/properties/inverters/items/anyOf/0/properties/connection"
},
"unitId": {
"$ref": "#/definitions/config/properties/inverters/items/anyOf/0/properties/unitId"
}
},
"required": [
"type",
"connection"
],
"additionalProperties": false,
"description": "Growatt meter configuration"
},
{
"type": "object",
"properties": {
Expand Down
81 changes: 81 additions & 0 deletions src/connections/modbus/connection/growatt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { type ModbusSchema } from '../../../helpers/config.js';
import { getModbusConnection } from '../connections.js';
import { type GrowattInverterModels } from '../models/growatt/inverter.js';
import { GrowattInveter1Model } from '../models/growatt/inverter.js';
import { type GrowattInverterControl } from '../models/growatt/inverterControl.js';
import { GrowattInverterControl1Model } from '../models/growatt/inverterControl.js';
import { type GrowattMeterModels } from '../models/growatt/meter.js';
import {
GrowattMeter1Model,
GrowattMeter2Model,
} from '../models/growatt/meter.js';
import { type ModbusConnection } from './base.js';
import { type Logger } from 'pino';

export class GrowattConnection {
protected readonly modbusConnection: ModbusConnection;
protected readonly unitId: number;
private logger: Logger;

constructor({ connection, unitId }: ModbusSchema) {
this.modbusConnection = getModbusConnection(connection);
this.unitId = unitId;
this.logger = this.modbusConnection.logger.child({
module: 'GrowattConnection',
unitId,
});
}

async getInverterModel(): Promise<GrowattInverterModels> {
const model1 = await GrowattInveter1Model.read({
modbusConnection: this.modbusConnection,
address: {
start: 0,
length: 3,
},
unitId: this.unitId,
});

return model1;
}

async getMeterModel(): Promise<GrowattMeterModels> {
const model1 = await GrowattMeter1Model.read({
modbusConnection: this.modbusConnection,
address: {
start: 37,
length: 10,
},
unitId: this.unitId,
});

const model2 = await GrowattMeter2Model.read({
modbusConnection: this.modbusConnection,
address: {
start: 1015,
length: 24,
},
unitId: this.unitId,
});

const data = { ...model1, ...model2 };

return data;
}

async writeInverterControlModel(values: GrowattInverterControl) {
return await GrowattInverterControl1Model.write({
modbusConnection: this.modbusConnection,
address: {
start: 3,
length: 1,
},
unitId: this.unitId,
values,
});
}

public onDestroy(): void {
this.modbusConnection.close();
}
}
28 changes: 18 additions & 10 deletions src/connections/modbus/modbusModelFactory.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { writeLatency } from '../../helpers/influxdb.js';
import { objectEntriesWithType } from '../../helpers/object.js';
import { type ModelAddress } from '../sunspec/connection/base.js';
import { type ModbusConnection } from './connection/base.js';
import {
type ModbusConnection,
type ModbusRegisterType,
} from './connection/base.js';

export type Mapping<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -22,8 +25,13 @@ export function modbusModelFactory<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Model extends Record<string, any>,
WriteableKeys extends keyof Model = never,
>(config: {
>({
name,
type = 'holding',
mapping,
}: {
name: string;
type?: ModbusRegisterType;
mapping: Mapping<Model, WriteableKeys>;
}): {
read(params: {
Expand All @@ -42,7 +50,7 @@ export function modbusModelFactory<
read: async ({ modbusConnection, address, unitId }) => {
const logger = modbusConnection.logger.child({
module: 'modbusModelFactory',
model: config.name,
model: name,
type: 'read',
});

Expand All @@ -51,7 +59,7 @@ export function modbusModelFactory<
await modbusConnection.connect();

const registers = await modbusConnection.readRegisters({
type: 'holding',
type,
unitId,
start: address.start,
length: address.length,
Expand All @@ -77,7 +85,7 @@ export function modbusModelFactory<
}
})(),
unitId: unitId.toString(),
model: config.name,
model: name,
addressStart: address.start.toString(),
addressLength: address.length.toString(),
},
Expand All @@ -90,28 +98,28 @@ export function modbusModelFactory<

return convertReadRegisters({
registers: registers.data,
mapping: config.mapping,
mapping,
});
},
write: async ({ modbusConnection, address, unitId, values }) => {
const logger = modbusConnection.logger.child({
module: 'modbusModelFactory',
model: config.name,
model: name,
type: 'write',
});

const start = performance.now();

const registerValues = convertWriteRegisters({
values,
mapping: config.mapping,
mapping,
length: address.length,
});

await modbusConnection.connect();

await modbusConnection.writeRegisters({
type: 'holding',
type,
unitId,
start: address.start,
data: registerValues,
Expand All @@ -137,7 +145,7 @@ export function modbusModelFactory<
}
})(),
unitId: unitId.toString(),
model: config.name,
model: name,
addressStart: address.start.toString(),
addressLength: address.length.toString(),
},
Expand Down
32 changes: 32 additions & 0 deletions src/connections/modbus/models/growatt/inverter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
registersToUint16,
registersToUint32,
} from '../../helpers/converters.js';
import { modbusModelFactory } from '../../modbusModelFactory.js';

export type GrowattInverterModels = GrowattInverter1;

type GrowattInverter1 = {
// Inverter run state
// 0:waiting, 1:normal, 3:fault
InverterStatus: number;
// Input power
Ppv: number;
};

export const GrowattInveter1Model = modbusModelFactory<GrowattInverter1>({
name: 'GrowattInveter1Model',
type: 'input',
mapping: {
InverterStatus: {
start: 0,
end: 1,
readConverter: registersToUint16,
},
Ppv: {
start: 1,
end: 3,
readConverter: (value) => registersToUint32(value, -1),
},
},
});
29 changes: 29 additions & 0 deletions src/connections/modbus/models/growatt/inverterControl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
registersToUint16,
uint16ToRegisters,
} from '../../helpers/converters.js';
import { modbusModelFactory } from '../../modbusModelFactory.js';

export type GrowattInverterControl = GrowattInverterControl1;

export type GrowattInverterControl1 = {
// Inverter Max output active power percent
// 0-100 or 255
// 255: power is not belimited
ActivePRate: number;
};

export const GrowattInverterControl1Model = modbusModelFactory<
GrowattInverterControl1,
keyof GrowattInverterControl1
>({
name: 'GrowattInverterControl1Model',
mapping: {
ActivePRate: {
start: 0,
end: 1,
readConverter: registersToUint16,
writeConverter: uint16ToRegisters,
},
},
});
Loading
Loading