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

Add basic Modbus support #57

Merged
merged 4 commits into from
May 10, 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ to any number of different targets, such as databases or MQTT.
* Supports _**multiple different power sensors**_
* [IotaWatt](http://iotawatt.com/)
* [Shelly](https://www.shelly.com/) (both Gen 1 and Gen 2)
* Generic Modbus sensors (limited support)
* Supports _**virtual power sensors**_
* A virtual power sensor gets its values from other configured sensors, enabling the user to calculate the total
power usage of three-phase devices or three-phase mains power
Expand Down
13 changes: 13 additions & 0 deletions examples/config.sample.full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ circuits:
- Vägg vardagsrum
- Vägg kök
- Vägg arbetsrum
clamp: positive # Don't allow negative values
# A IotaWatt main circuit, phase A/L1
- name: Main L1
type: main
Expand Down Expand Up @@ -133,6 +134,18 @@ circuits:
type: gen2-em
meter: 0
phase: a
# A circuit with a Modbus sensor
- name: Inverter/chargers
type: circuit
sensor:
type: modbus
modbus:
address: 10.112.4.250
port: 502
unit: 100
register: 866
type: int16
clamp: positive

#
# Characteristics. Characteristics mean voltage and frequency, and potentially other non-power related readings.
Expand Down
241 changes: 241 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"dependencies": {
"@influxdata/influxdb-client": "^1.33.2",
"axios": "^1.6.0",
"modbus-serial": "^8.0.16",
"mqtt": "^5.1.2",
"slugify": "^1.6.6",
"winston": "^3.11.0",
Expand Down
4 changes: 4 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getCharacteristicsSensorData as getIotawattCharacteristicsSensorData,
getSensorData as getIotawattSensorData,
} from './sensor/iotawatt'
import { getSensorData as getModbusSensorData } from './sensor/modbus'
import { getSensorData as getVirtualSensorData } from './sensor/virtual'
import { getSensorData as getUnmeteredSensorData } from './sensor/unmetered'
import {
Expand Down Expand Up @@ -153,6 +154,9 @@ export const resolveAndValidateConfig = (config: Config): Config => {
case SensorType.Iotawatt:
circuit.sensor.pollFunc = getIotawattSensorData
break
case SensorType.Modbus:
circuit.sensor.pollFunc = getModbusSensorData
break
case SensorType.Virtual:
circuit.sensor.pollFunc = getVirtualSensorData
break
Expand Down
17 changes: 13 additions & 4 deletions src/eachwatt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { WebSocketPublisherImpl } from './publisher/websocket'
import { PublisherType } from './publisher'
import { pollCharacteristicsSensors } from './characteristics'
import { createLogger } from './logger'
import { setRequestTimeout } from './http/client'
import { setRequestTimeout as setHttpRequestTimeout } from './http/client'
import { setRequestTimeout as setModbusRequestTimeout } from './modbus/client'

// Set up a signal handler, so we can exit on Ctrl + C when run from Docker
process.on('SIGINT', () => {
Expand Down Expand Up @@ -52,10 +53,16 @@ const mainPollerFunc = async (config: Config) => {
// Poll characteristics sensors
const characteristicsSensorData = await pollCharacteristicsSensors(now, config.characteristics)

// Round all numbers to one decimal point
// Post-process power sensor data
for (const data of powerSensorData) {
if (data.power !== undefined) {
// Round all numbers to one decimal point
data.power = Number(data.power.toFixed(1))

// Optionally clamp values
if (data.circuit.sensor.clamp === 'positive') {
data.power = Math.max(0, data.power)
}
}
}

Expand Down Expand Up @@ -101,10 +108,12 @@ const mainPollerFunc = async (config: Config) => {
publisherImpl: webSocketServer,
})

// Adjust the HTTP timeout to be half that of the polling interval
// Adjust request timeouts to be half that of the polling interval
const pollingInterval = config.settings.pollingInterval
logger.info(`Polling sensors with interval ${pollingInterval} milliseconds`)
setRequestTimeout((pollingInterval as number) / 2)
const timeoutMs = (pollingInterval as number) / 2
setHttpRequestTimeout(timeoutMs)
setModbusRequestTimeout(timeoutMs)

// Start polling sensors
await mainPollerFunc(config)
Expand Down
Loading
Loading