Skip to content

Commit

Permalink
Merge pull request #70 from longzheng/polling-rate
Browse files Browse the repository at this point in the history
Allow configuring polling rate via config
  • Loading branch information
longzheng authored Dec 20, 2024
2 parents c60a3dd + f676e76 commit 2e26d16
Show file tree
Hide file tree
Showing 12 changed files with 79 additions and 24 deletions.
28 changes: 26 additions & 2 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@
"maximum": 255,
"default": 1,
"description": "The unit/slave ID of the Modbus device. Defaults to 1."
},
"pollingIntervalMs": {
"type": "number",
"description": "The minimum number of seconds between polling, subject to the latency of the polling loop.",
"default": 200
}
},
"required": [
Expand All @@ -253,6 +258,9 @@
},
"unitId": {
"$ref": "#/definitions/config/properties/inverters/items/anyOf/0/properties/unitId"
},
"pollingIntervalMs": {
"$ref": "#/definitions/config/properties/inverters/items/anyOf/0/properties/pollingIntervalMs"
}
},
"required": [
Expand Down Expand Up @@ -280,10 +288,10 @@
"description": "How many seconds of inverter and site data to sample to make control decisions.\nA shorter time will increase responsiveness to load changes but may introduce oscillations.\nA longer time will smooth out load changes but may result in overshoot.",
"default": 5
},
"controlFrequencyMinimumSeconds": {
"intervalSeconds": {
"type": "number",
"minimum": 0,
"description": "The number of seconds between control commands",
"description": "The minimum number of seconds between control commands, subject to the latency of the control loop.",
"default": 1
}
},
Expand Down Expand Up @@ -313,6 +321,9 @@
},
"unitId": {
"$ref": "#/definitions/config/properties/inverters/items/anyOf/0/properties/unitId"
},
"pollingIntervalMs": {
"$ref": "#/definitions/config/properties/inverters/items/anyOf/0/properties/pollingIntervalMs"
}
},
"required": [
Expand All @@ -339,6 +350,9 @@
},
"unitId": {
"$ref": "#/definitions/config/properties/inverters/items/anyOf/0/properties/unitId"
},
"pollingIntervalMs": {
"$ref": "#/definitions/config/properties/inverters/items/anyOf/0/properties/pollingIntervalMs"
}
},
"required": [
Expand Down Expand Up @@ -369,6 +383,11 @@
"type": "number",
"description": "Request timeout in seconds",
"default": 2
},
"pollingIntervalMs": {
"type": "number",
"description": "The minimum number of seconds between polling, subject to the latency of the polling loop.",
"default": 200
}
},
"required": [
Expand Down Expand Up @@ -401,6 +420,11 @@
"topic": {
"type": "string",
"description": "The topic to pull meter readings from"
},
"pollingIntervalMs": {
"type": "number",
"description": "The minimum number of seconds between polling, subject to the latency of the polling loop.",
"default": 200
}
},
"required": [
Expand Down
4 changes: 2 additions & 2 deletions docs/configuration/inverter-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ The inverter control loop will attempt to aggregate and average a window of meas

## Frequency

The inverter control loop will attempt to control the inverter with a rate limit to prevent over-correcting and leading to osscilations. The `config.json` option `inverterControl.controlFrequencyMinimumSeconds` can be used to adjust the frequency in seconds.
The inverter control loop will attempt to control the inverter with a rate limit to prevent over-correcting and leading to osscilations. The `config.json` option `inverterControl.intervalSeconds` can be used to adjust the frequency in seconds.

The rate limit will be influenced by the latency of sending commands to the inverter (e.g. over WiFi) and the inverter's response time, so the frequency configuration is a minimum and not a guaranteed rate.

```js
{
"inverterControl": {
"controlFrequencyMinimumSeconds": 5 // (number) optional: the frequency in seconds to control the inverter
"intervalSeconds": 5 // (number) optional: the frequency in seconds to control the inverter
},
...
}
Expand Down
6 changes: 4 additions & 2 deletions docs/configuration/inverters.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ To configure a SMA inverter connection, add the following property to `config.js
"ip": "192.168.1.6", // (string) required: the IP address of the inverter
"port": 502 // (number) required: the Modbus TCP port of the inverter
},
"unitId": 1 // (number) required: the Modbus unit ID of the inverter
"unitId": 1 // (number) required: the Modbus unit ID of the inverter,
"pollingIntervalMs": // (number) optional: the polling interval in milliseconds, default 200
}
],
...
Expand All @@ -50,7 +51,8 @@ To configure a SunSpec inverter connection over TCP, add the following property
"ip": "192.168.1.6", // (string) required: the IP address of the inverter
"port": 502 // (number) required: the Modbus TCP port of the inverter
},
"unitId": 1 // (number) required: the Modbus unit ID of the inverter
"unitId": 1 // (number) required: the Modbus unit ID of the inverter,
"pollingIntervalMs": // (number) optional: the polling interval in milliseconds, default 200
}
],
...
Expand Down
10 changes: 7 additions & 3 deletions docs/configuration/meter.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ To configure a MQTT meter connection, add the following property to `config.json
"username": "user", // (string) optional: the MQTT broker username
"password": "password", // (string) optional: the MQTT broker password
"topic": "site" // (string) required: the MQTT topic to read
"pollingIntervalMs": // (number) optional: the polling interval in milliseconds, default 200
}
...
}
Expand Down Expand Up @@ -147,7 +148,8 @@ To configure a SMA inverter with meter connection, add the following property to
"port": 502 // (number) required: the Modbus TCP port of the inverter
},
"unitId": 240 // (number) required: the SunSpec unit ID of the meter
"location": "feedin" // (string) optional: the location of the meter (feedin or consumption)
"location": "feedin" // (string) optional: the location of the meter (feedin or consumption),
"pollingIntervalMs": // (number) optional: the polling interval in milliseconds, default 200
}
...
}
Expand Down Expand Up @@ -175,7 +177,8 @@ To configure a SunSpec meter connection over TCP, add the following property to
"port": 502 // (number) required: the Modbus TCP port of the inverter
},
"unitId": 240 // (number) required: the SunSpec unit ID of the meter
"location": "feedin" // (string) optional: the location of the meter (feedin or consumption)
"location": "feedin" // (string) optional: the location of the meter (feedin or consumption),
"pollingIntervalMs": // (number) optional: the polling interval in milliseconds, default 200
}
...
}
Expand Down Expand Up @@ -208,7 +211,8 @@ Sites with a Tesla Powerwall 2 can use the Backup Gateway 2's meter and local AP
"meter": {
"type": "powerwall2", // (string) required: the type of meter
"ip": "192.168.1.68", // (string) required: the IP address of the Powerwall 2 Gateway
"password": "ABCDE" // (string) required: the password to access the Powerwall 2 API (the last 5 characters of the password sticker)
"password": "ABCDE" // (string) required: the password to access the Powerwall 2 API (the last 5 characters of the password sticker),
"pollingIntervalMs": // (number) optional: the polling interval in milliseconds, default 200
}
...
```
10 changes: 3 additions & 7 deletions src/coordinator/helpers/inverterController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export class InverterController {
// we don't want to rely on the latest readings to make decisions since it will lead to oscillating control values
// we take a time weighted average of the last few seconds to smooth out the control values
private secondsToSample: number;
private controlFrequencyMinimumSeconds: number;
private intervalSeconds: number;

constructor({
config,
Expand All @@ -109,8 +109,7 @@ export class InverterController {
}) {
this.publish = new Publish({ config });
this.secondsToSample = config.inverterControl.sampleSeconds;
this.controlFrequencyMinimumSeconds =
config.inverterControl.controlFrequencyMinimumSeconds;
this.intervalSeconds = config.inverterControl.intervalSeconds;
this.limiters = limiters;
this.logger = pinoLogger.child({ module: 'InverterController' });
this.onControl = onControl;
Expand Down Expand Up @@ -234,10 +233,7 @@ export class InverterController {

writeLatency({ field: 'applyControlLoop', duration });

const delay = Math.max(
this.controlFrequencyMinimumSeconds * 1000 - duration,
0,
);
const delay = Math.max(this.intervalSeconds * 1000 - duration, 0);

setTimeout(() => {
void this.applyControlLoop();
Expand Down
27 changes: 25 additions & 2 deletions src/helpers/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ const modbusSchema = z.object({
.max(255)
.default(1)
.describe('The unit/slave ID of the Modbus device. Defaults to 1.'),
pollingIntervalMs: z
.number()
.optional()
.describe(
'The minimum number of seconds between polling, subject to the latency of the polling loop.',
)
.default(200),
});

export type ModbusSchema = z.infer<typeof modbusSchema>;
Expand Down Expand Up @@ -171,10 +178,12 @@ A longer time will smooth out load changes but may result in overshoot.`,
)
.optional()
.default(5),
controlFrequencyMinimumSeconds: z
intervalSeconds: z
.number()
.min(0)
.describe(`The number of seconds between control commands`)
.describe(
`The minimum number of seconds between control commands, subject to the latency of the control loop.`,
)
.optional()
.default(1),
}),
Expand Down Expand Up @@ -213,6 +222,13 @@ A longer time will smooth out load changes but may result in overshoot.`,
.optional()
.describe('Request timeout in seconds')
.default(2),
pollingIntervalMs: z
.number()
.optional()
.describe(
'The minimum number of seconds between polling, subject to the latency of the polling loop.',
)
.default(200),
})
.describe('Powerwall 2 meter configuration'),
z
Expand All @@ -234,6 +250,13 @@ A longer time will smooth out load changes but may result in overshoot.`,
topic: z
.string()
.describe('The topic to pull meter readings from'),
pollingIntervalMs: z
.number()
.optional()
.describe(
'The minimum number of seconds between polling, subject to the latency of the polling loop.',
)
.default(200),
})
.describe('MQTT meter configuration'),
]),
Expand Down
2 changes: 1 addition & 1 deletion src/inverter/sma/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class SmaInverterDataPoller extends InverterDataPollerBase {
}) {
super({
name: 'SmaInverterDataPoller',
pollingIntervalMs: 200,
pollingIntervalMs: smaInverterConfig.pollingIntervalMs,
applyControl,
inverterIndex,
});
Expand Down
2 changes: 1 addition & 1 deletion src/inverter/sunspec/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class SunSpecInverterDataPoller extends InverterDataPollerBase {
}) {
super({
name: 'SunSpecInverterDataPoller',
pollingIntervalMs: 200,
pollingIntervalMs: sunspecInverterConfig.pollingIntervalMs,
applyControl,
inverterIndex,
});
Expand Down
2 changes: 1 addition & 1 deletion src/meters/mqtt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class MqttSiteSamplePoller extends SiteSamplePollerBase {
}) {
super({
name: 'mqtt',
pollingIntervalMs: 200,
pollingIntervalMs: mqttConfig.pollingIntervalMs,
});

this.client = mqtt.connect(mqttConfig.host, {
Expand Down
2 changes: 1 addition & 1 deletion src/meters/powerwall2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class Powerwall2SiteSamplePoller extends SiteSamplePollerBase {
}) {
super({
name: 'powerwall2',
pollingIntervalMs: 200,
pollingIntervalMs: powerwall2Config.pollingIntervalMs,
});

this.client = getPowerwall2Client({
Expand Down
5 changes: 4 additions & 1 deletion src/meters/sma/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ export class SmaMeterSiteSamplePoller extends SiteSamplePollerBase {
private model: SmaMeterConfig['model'];

constructor({ smaMeterConfig }: { smaMeterConfig: SmaMeterConfig }) {
super({ name: 'sma', pollingIntervalMs: 200 });
super({
name: 'sma',
pollingIntervalMs: smaMeterConfig.pollingIntervalMs,
});

this.smaConnection = new SmaConnection(smaMeterConfig);
this.model = smaMeterConfig.model;
Expand Down
5 changes: 4 additions & 1 deletion src/meters/sunspec/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ export class SunSpecMeterSiteSamplePoller extends SiteSamplePollerBase {
sunspecMeterConfig: SunSpecMeterConfig;
invertersPoller: InvertersPoller;
}) {
super({ name: 'sunspec', pollingIntervalMs: 200 });
super({
name: 'sunspec',
pollingIntervalMs: sunspecMeterConfig.pollingIntervalMs,
});

this.meterConnection = new MeterSunSpecConnection(sunspecMeterConfig);
this.location = sunspecMeterConfig.location;
Expand Down

0 comments on commit 2e26d16

Please sign in to comment.