Skip to content

Commit

Permalink
Merge branch 'main' into goodwe
Browse files Browse the repository at this point in the history
  • Loading branch information
longzheng committed Dec 13, 2024
2 parents 570f659 + 20a27e8 commit b5725b5
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 11 deletions.
33 changes: 33 additions & 0 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,39 @@
"description": "MQTT meter configuration"
}
]
},
"publish": {
"type": "object",
"properties": {
"mqtt": {
"type": "object",
"properties": {
"host": {
"type": "string",
"description": "The host of the MQTT broker, including \"mqtt://\""
},
"username": {
"type": "string",
"description": "The username for the MQTT broker"
},
"password": {
"type": "string",
"description": "The password for the MQTT broker"
},
"topic": {
"type": "string",
"description": "The topic to publish limits"
}
},
"required": [
"host",
"topic"
],
"additionalProperties": false
}
},
"additionalProperties": false,
"description": "Publish active control limits"
}
},
"required": [
Expand Down
4 changes: 0 additions & 4 deletions config/config.example.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
{
"limiters": {
"sep2": {
"host": "https://sep2-test.energyq.com.au",
"dcapUri": "/api/v2/dcap"
}
},
"inverters": [
{
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export default withMermaid(
},
{ text: 'Site meter', link: '/configuration/meter' },
{ text: 'Limiters', link: '/configuration/limiters' },
{ text: 'Publish', link: '/configuration/publish' },
],
},
{
Expand Down
6 changes: 3 additions & 3 deletions docs/configuration/limiters.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ To use the CSIP-AUS limiter, add following property to `config.json`
"limiters": {
"sep2": {
"host": "https://sep2-test.energyq.com.au", // (string) required: the SEP2 server host
"dcapUri": "/api/v2/dcap" // (string) required: the device capability discovery URI
"dcapUri": "/api/v2/dcap", // (string) required: the device capability discovery URI
"nmi": "1234567890" // (string) optional: for utilities that require in-band registration, the NMI of the site
}
}
Expand All @@ -53,8 +53,8 @@ To set fixed limits (such as for fixed export limits), add the following propert
"fixed": {
"connect": true, // (true/false) optional: whether the inverters should be connected to the grid
"exportLimitWatts": 5000, // (number) optional: the maximum export limit in watts
"generationLimitWatts": 10000 // (number) optional: the maximum generation limit in watts
"importLimitWatts": 5000 // (number) optional: the maximum import limit in watts (not currently used)
"generationLimitWatts": 10000, // (number) optional: the maximum generation limit in watts
"importLimitWatts": 5000, // (number) optional: the maximum import limit in watts (not currently used)
"loadLimitWatts": 10000 // (number) optional: the maximum load limit in watts (not currently used)
}
}
Expand Down
105 changes: 105 additions & 0 deletions docs/configuration/publish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Publish

Optionally configure the active limits to be published to an external system.

[[toc]]

## MQTT

Write active limits to a MQTT topic.

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

```js
{
"publish": {
"mqtt": {
"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": "limits" // (string) required: the MQTT topic to write
}
}
...
}
```

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

```ts
const inverterControlTypesSchema = z.union([
z.literal("fixed"),
z.literal("mqtt"),
z.literal("sep2"),
z.literal("twoWayTariff"),
z.literal("negativeFeedIn")
])

const activeInverterControlLimitSchema = z.object({
opModEnergize: z.union([
z.object({
value: z.boolean(),
source: inverterControlTypesSchema
}),
z.undefined()
]),
opModConnect: z.union([
z.object({
value: z.boolean(),
source: inverterControlTypesSchema
}),
z.undefined()
]),
opModGenLimW: z.union([
z.object({
value: z.number(),
source: inverterControlTypesSchema
}),
z.undefined()
]),
opModExpLimW: z.union([
z.object({
value: z.number(),
source: inverterControlTypesSchema
}),
z.undefined()
]),
opModImpLimW: z.union([
z.object({
value: z.number(),
source: inverterControlTypesSchema
}),
z.undefined()
]),
opModLoadLimW: z.union([
z.object({
value: z.number(),
source: inverterControlTypesSchema
}),
z.undefined()
])
})
```

For example

```js
{
"opModEnergize": {
"source": "sep2",
"value": true
},
"opModConnect": {
"source": "sep2",
"value": true
},
"opModExpLimW": {
"source": "sep2",
"value": 1500
},
"opModImpLimW": {
"source": "sep2",
"value": 1500
}
}
```
7 changes: 7 additions & 0 deletions src/coordinator/helpers/inverterController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { CappedArrayStack } from '../../helpers/cappedArrayStack.js';
import { timeWeightedAverage } from '../../helpers/timeWeightedAverage.js';
import { differenceInSeconds } from 'date-fns';
import { type ControlsModel } from '../../connections/sunspec/models/controls.js';
import { Publish } from './publish.js';

export type SupportedControlTypes = Extract<
ControlType,
Expand Down Expand Up @@ -69,6 +70,7 @@ const defaultValues = {
} as const satisfies Record<ControlType, unknown>;

export class InverterController {
private activeLimitOutput: Publish;
private cachedDerSample = new CappedArrayStack<DerSample>({ limit: 100 });
private cachedSiteSample = new CappedArrayStack<SiteSample>({ limit: 100 });
private logger: Logger;
Expand Down Expand Up @@ -105,6 +107,7 @@ export class InverterController {
inverterConfiguration: InverterConfiguration,
) => Promise<void>;
}) {
this.activeLimitOutput = new Publish({ config });
this.secondsToSample = config.inverterControl.sampleSeconds;
this.controlFrequencyMinimumSeconds =
config.inverterControl.controlFrequencyMinimumSeconds;
Expand Down Expand Up @@ -172,6 +175,10 @@ export class InverterController {

writeActiveControlLimit({ limit: activeInverterControlLimit });

this.activeLimitOutput.onActiveInverterControlLimit({
limit: activeInverterControlLimit,
});

this.controlLimitsCache = {
controlLimitsByLimiter,
activeInverterControlLimit,
Expand Down
29 changes: 29 additions & 0 deletions src/coordinator/helpers/publish.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import mqtt from 'mqtt';
import { type Config } from '../../helpers/config.js';
import { type ActiveInverterControlLimit } from './inverterController.js';

export class Publish {
private mqttClient: mqtt.MqttClient | undefined;

constructor({ config }: { config: Config }) {
if (config.publish?.mqtt) {
this.mqttClient = mqtt.connect(config.publish.mqtt.host, {
username: config.publish.mqtt.username,
password: config.publish.mqtt.password,
});
}
}

onActiveInverterControlLimit({
limit,
}: {
limit: ActiveInverterControlLimit;
}) {
if (this.mqttClient) {
this.mqttClient.publish(
'inverterControlLimit',
JSON.stringify(limit),
);
}
}
}
23 changes: 23 additions & 0 deletions src/helpers/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,29 @@ A longer time will smooth out load changes but may result in overshoot.`,
})
.describe('MQTT meter configuration'),
]),
publish: z
.object({
mqtt: z
.object({
host: z
.string()
.describe(
'The host of the MQTT broker, including "mqtt://"',
),
username: z
.string()
.optional()
.describe('The username for the MQTT broker'),
password: z
.string()
.optional()
.describe('The password for the MQTT broker'),
topic: z.string().describe('The topic to publish limits'),
})
.optional(),
})
.describe('Publish active control limits')
.optional(),
});

export type Config = z.infer<typeof configSchema>;
Expand Down
8 changes: 4 additions & 4 deletions src/meters/mqtt/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import mqtt from 'mqtt';
import { type Config } from '../../helpers/config.js';
import { SiteSamplePollerBase } from '../siteSamplePollerBase.js';
import { type SiteSample } from '../siteSample.js';
import { type SiteSampleData, type SiteSample } from '../siteSample.js';
import { siteSampleDataSchema } from '../siteSample.js';

export class MqttSiteSamplePoller extends SiteSamplePollerBase {
private client: mqtt.MqttClient;
private cachedMessage: SiteSample | null = null;
private cachedMessage: SiteSampleData | null = null;

constructor({
mqttConfig,
Expand Down Expand Up @@ -37,7 +37,7 @@ export class MqttSiteSamplePoller extends SiteSamplePollerBase {
return;
}

this.cachedMessage = { date: new Date(), ...result.data };
this.cachedMessage = result.data;
});

void this.startPolling();
Expand All @@ -49,7 +49,7 @@ export class MqttSiteSamplePoller extends SiteSamplePollerBase {
throw new Error('No site sample data on MQTT');
}

return this.cachedMessage;
return { date: new Date(), ...this.cachedMessage };
}

override onDestroy() {
Expand Down

0 comments on commit b5725b5

Please sign in to comment.