Skip to content

Commit

Permalink
Add MQTT inverter documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
longzheng committed Jan 18, 2025
1 parent b6782a6 commit a7954d3
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 8 deletions.
125 changes: 125 additions & 0 deletions docs/configuration/inverters.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,128 @@ For SunSpec over RTU, you need to modify the `connection`
### Fronius

To enable SunSpec/Modbus on Fronius inverters, you'll need to access the inverter's local web UI and [enable the Modbus TCP option](https://github.com/longzheng/open-dynamic-export/wiki/Fronius-SunSpec-Modbus-configuration).

## MQTT

> [!WARNING]
> The MQTT inverter configuration does not support control. It is designed for systems which will monitor the API or "publish" active limit output to apply inverter control externally.
A MQTT topic can be read to get the inveter measurements.

To configure a MQTT inverter connection, add the following property to `config.json`

```js
{
"inverters": [
{
"type": "mqtt", // (string) required: the type of inverter
"host": "mqtt://192.168.1.2", // (string) required: the MQTT broker host
"username": "user", // (string) optional: the MQTT broker username
"password": "password", // (string) optional: the MQTT broker password
"topic": "inverters/1" // (string) required: the MQTT topic to read
"pollingIntervalMs": // (number) optional: the polling interval in milliseconds, default 200
}
]
...
}
```

The MQTT topic must contain a JSON message that meets the following schema

```js
z.object({
inverter: z.object({
/**
* Positive values = inverter export (produce) power
*
* Negative values = inverter import (consume) power
*
* Value is total (net across all phases) measurement
*/
realPower: z.number(),
/**
* Positive values = inverter export (produce) power
*
* Negative values = inverter import (consume) power
*
* Value is total (net across all phases) measurement
*/
reactivePower: z.number(),
// Voltage of phase A (null if not available)
voltagePhaseA: z.number().nullable(),
// Voltage of phase B (null if not available)
voltagePhaseB: z.number().nullable(),
// Voltage of phase C (null if not available)
voltagePhaseC: z.number().nullable(),
frequency: z.number(),
}),
nameplate: z.object({
/**
* Type of DER device Enumeration
*
* PV = 4,
* PV_STOR = 82,
*/
type: z.nativeEnum(DERTyp),
// Maximum active power output in W
maxW: z.number(),
// Maximum apparent power output in VA
maxVA: z.number(),
// Maximum reactive power output in var
maxVar: z.number(),
}),
settings: z.object({
// Currently set active power output in W
maxW: z.number(),
// Currently set apparent power output in VA
maxVA: z.number().nullable(),
// Currently set reactive power output in var
maxVar: z.number().nullable(),
}),
status: z.object({
// DER OperationalModeStatus value:
// 0 - Not applicable / Unknown
// 1 - Off
// 2 - Operational mode
// 3 - Test mode
operationalModeStatus: z.nativeEnum(OperationalModeStatusValue),
// DER ConnectStatus value (bitmap):
// 0 - Connected
// 1 - Available
// 2 - Operating
// 3 - Test
// 4 - Fault / Error
genConnectStatus: connectStatusValueSchema,
}),
})
```

For example

```json
{
"inverter": {
"realPower": 4500,
"reactivePower": 1500,
"voltagePhaseA": 230.5,
"voltagePhaseB": null,
"voltagePhaseC": null,
"frequency": 50.1
},
"nameplate": {
"type": 4,
"maxW": 5000,
"maxVA": 5000,
"maxVar": 5000
},
"settings": {
"maxW": 5000,
"maxVA": 5000,
"maxVar": 5000
},
"status": {
"operationalModeStatus": 2,
"genConnectStatus": 7
}
}
```
7 changes: 7 additions & 0 deletions src/inverter/inverterData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import { type SampleBase } from '../coordinator/helpers/sampleBase.js';

export const inverterDataSchema = z.object({
inverter: z.object({
/**
* Positive values = inverter export (produce) power
*
* Negative values = inverter import (consume) power
*
* Value is total (net across all phases) measurement
*/
realPower: z.number(),
reactivePower: z.number(),
voltagePhaseA: z.number().nullable(),
Expand Down
4 changes: 2 additions & 2 deletions src/meters/siteSample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { z } from 'zod';
// aligns with the CSIP-AUS requirements for site sample
export const siteSampleDataSchema = z.object({
/**
* Positive values = site import power
* Positive values = site import (consume) power
*
* Negative values = site export power
* Negative values = site export (produce) power
*/
realPower: z.union([
perPhaseNetMeasurementSchema,
Expand Down
12 changes: 6 additions & 6 deletions src/sep2/models/operationModeStatus.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { z } from 'zod';

/// DER OperationalModeStatus value:
/// 0 - Not applicable / Unknown
/// 1 - Off
/// 2 - Operational mode
/// 3 - Test mode
/// All other values reserved.
// DER OperationalModeStatus value:
// 0 - Not applicable / Unknown
// 1 - Off
// 2 - Operational mode
// 3 - Test mode
// All other values reserved.
export enum OperationalModeStatusValue {
NotApplicable = 0,
Off = 1,
Expand Down

0 comments on commit a7954d3

Please sign in to comment.