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 alarm support #32

Merged
merged 2 commits into from
Jan 21, 2022
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

* Add basic test suite
* Retain MQTT discovery configuration messages (fixes https://github.com/Jalle19/eda-modbus-bridge/issues/29)
* Publish only changed settings or modes in MQTT callback
* Publish only changed settings or modes in MQTT callback (fixes https://github.com/Jalle19/eda-modbus-bridge/issues/33)
* Add alarms support (fixes https://github.com/Jalle19/eda-modbus-bridge/issues/31)

## 2.0.0

Expand Down
47 changes: 46 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,15 @@ Returns a JSON object like this:
"familyType": 0,
"serialNumber": 0,
"softwareVersion": 217
}
},
"alarms": [
{
"name": "ServiceReminder",
"description": "Service reminder",
"state": 1, // 2 = Active, 1 = Dismissed
"date": "2021-10-24T11:28:00.000Z"
},...
]
}
```

Expand All @@ -116,6 +124,23 @@ Returns the status of the specified mode/flag. The response looks like this:
{"active":false}
```

### GET /alarms

Returns the active or dismissed alarms. The response looks like this:

```json
{
"alarms": [
{
"name": "ServiceReminder",
"description": "Service reminder",
"state": 1, // 2 = Active, 1 = Dismissed
"date": "2021-10-24T11:28:00.000Z"
},...
]
}
```

### POST /mode/{flag}

Enables/disables the specified mode/flag depending on the boolean value in the following request body:
Expand Down Expand Up @@ -191,6 +216,25 @@ eda/deviceInformation/heatingTypeInstalled
eda/deviceInformation/familyType
eda/deviceInformation/serialNumber
eda/deviceInformation/softwareVersion
eda/alarm/TE5InletAfterHeatExchangerCold
eda/alarm/TE10InletAfterHeaterCold
eda/alarm/TE10InletAfterHeaterHot
eda/alarm/TE20RoomHot
eda/alarm/TE30OutletCold
eda/alarm/TE30OutletHot
eda/alarm/HPFault
eda/alarm/HeaterFault
eda/alarm/ReturnWaterCold
eda/alarm/LTOFault
eda/alarm/CoolingFault
eda/alarm/EmergencyStop
eda/alarm/FireRisk
eda/alarm/ServiceReminder
eda/alarm/HeaterPressureSwitch
eda/alarm/InletFilterDirty
eda/alarm/OutletFilterDirty
eda/alarm/InletFanPressureAbnomaly
eda/alarm/OutletFanPressureAbnomaly
```

The following topics can be written to in order to control the operation of the ventilation unit:
Expand Down Expand Up @@ -222,6 +266,7 @@ in Home Assistant automatically through the MQTT integration. The following enti
* sensors for all readings
* numbers (configurable) for settings
* switches for the ventilation modes
* binary sensors for the alarms

![](https://raw.githubusercontent.com/Jalle19/eda-modbus-bridge/master/docs/readme_ha1.png "Home Assistant device info")
![](https://raw.githubusercontent.com/Jalle19/eda-modbus-bridge/master/docs/readme_ha2.png "Home Assistant controls")
Expand Down
17 changes: 16 additions & 1 deletion app/handlers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
getReadings,
getSettings,
setFlag,
setSetting as modbusSetSetting
setSetting as modbusSetSetting,
getAlarmStatuses
} from './modbus.mjs'

export const root = async (req, res) => {
Expand All @@ -18,6 +19,7 @@ export const summary = async (modbusClient, req, res) => {
'readings': await getReadings(modbusClient),
'settings': await getSettings(modbusClient),
'deviceInformation': await getDeviceInformation(modbusClient),
'alarms': await getAlarmStatuses(modbusClient, true, false),
}

res.json(summary)
Expand Down Expand Up @@ -71,4 +73,17 @@ export const setSetting = async (modbusClient, req, res) => {
res.status(400)
res.send(e.message)
}
}

export const getAlarms = async (modbusClient, req, res) => {
try {
const alarms = await getAlarmStatuses(modbusClient, true, false)

res.json({
'alarms': alarms,
})
} catch (e) {
res.status(400)
res.send(e.message)
}
}
61 changes: 61 additions & 0 deletions app/modbus.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,28 @@ const AVAILABLE_SETTINGS = {
'temperatureTarget': 135,
}

export let AVAILABLE_ALARMS = {
1: { name: 'TE5InletAfterHeatExchangerCold', description: 'TE5 inlet after heatexchanger cold' }, // 1=TE5 Tulo LTOn jälkeen kylmä
2: { name: 'TE10InletAfterHeaterCold', description: 'TE10 inlet after heater cold' }, // 2=TE10 Tulo lämmityspatterin jälkeen kylmä
3: { name: 'TE10InletAfterHeaterHot', description: 'TE10 inlet after heater hot' }, // 3=TE10 Tulo lämmityspatterin jälkeen kuuma
4: { name: 'TE20RoomHot', description: 'TE20 room hot' }, // 4=TE20 Huone kuuma
5: { name: 'TE30OutletCold', description: 'TE30 outlet cold' }, // 5=TE30 Poisto kylmä
6: { name: 'TE30OutletHot', description: 'TE30 outlet hot' }, // 6=TE30 Poisto kuuma
7: { name: 'HPFault', description: 'HP fault' }, // 7=HP vika
8: { name: 'HeaterFault', description: 'Heater fault' }, // 8=SLP vika
9: { name: 'ReturnWaterCold', description: 'Return water cold' }, // 9=Paluuvesi kylmää
10: { name: 'LTOFault', description: 'Heatexchanger fault' }, // 10=LTO vika
11: { name: 'CoolingFault', description: 'Cooling fault' }, // 11=Jäähdytys vika
12: { name: 'EmergencyStop', description: 'Emergency stop' }, // 12=Hätäseis
13: { name: 'FireRisk', description: 'Fire risk' }, // 13=Palovaara
14: { name: 'ServiceReminder', description: 'Service reminder' }, // 14=Huoltomuistutus
15: { name: 'HeaterPressureSwitch', description: 'Heater pressure switch' }, // 15=SLP painevahti
16: { name: 'InletFilterDirty', description: 'Inlet filter dirty' }, // 16=Tulosuodatin likainen,
17: { name: 'OutletFilterDirty', description: 'Outlet filter dirty' }, // 17=Poistosuodatin likainen
20: { name: 'InletFanPressureAbnomaly', description: 'Inlet fan pressure abnomaly' }, // 20=Tulopuhallin painepoikkeama
21: { name: 'OutletFanPressureAbnomaly', description: 'Outlet fan pressure abnomaly' }, // 21=Poistopuhallin painepoikkeama
}

const mutex = new Mutex()

export const parseTemperature = (temperature) => {
Expand Down Expand Up @@ -230,6 +252,45 @@ export const getDeviceInformation = async (modbusClient) => {
return deviceInformation
}

export const getAlarmStatuses = async (modbusClient, onlyActive = true, distinct = true) => {
let alarms = []

if (distinct === true && onlyActive === false) {
alarms = Object.assign([], AVAILABLE_ALARMS);
}

const startRegister = 385
const endRegister = 518
const alarmOffset = 7;

for (let register = startRegister; register <= endRegister; register += alarmOffset) {
let result = await mutex.runExclusive(async () => modbusClient.readHoldingRegisters(register, 7))
let code = result.data[0]
let state = result.data[1]

if (AVAILABLE_ALARMS[code] !== undefined && (onlyActive && state > 0 || onlyActive === false)) {
let alarm = Object.assign({}, AVAILABLE_ALARMS[code])

alarm.state = state
alarm.date = new Date(`${result.data[2] + 2000}-${result.data[3]}-${result.data[4]} ${result.data[5]}:${result.data[6]}:00`)

if (distinct === true) {
if (alarms[code] !== undefined) {
if (alarm.date > alarms[code].date) {
alarms[code].date = alarm.date
}
} else {
alarms[code] = Object.assign({}, alarm)
}
} else {
alarms.push(alarm)
}
}
}

return alarms
}

const getDeviceFamilyName = (familyTypeInt) => {
return [
'Pingvin',
Expand Down
34 changes: 33 additions & 1 deletion app/mqtt.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import {
setSetting,
getFlagSummary,
setFlag,
createModelNameString
createModelNameString,
getAlarmStatuses,
AVAILABLE_ALARMS
} from './modbus.mjs'

const TOPIC_PREFIX = 'eda'
const TOPIC_PREFIX_MODE = `${TOPIC_PREFIX}/mode`
const TOPIC_PREFIX_READINGS = `${TOPIC_PREFIX}/readings`
const TOPIC_PREFIX_SETTINGS = `${TOPIC_PREFIX}/settings`
const TOPIC_PREFIX_ALARM = `${TOPIC_PREFIX}/alarm`
const TOPIC_PREFIX_DEVICE_INFORMATION = `${TOPIC_PREFIX}/deviceInformation`
const TOPIC_NAME_STATUS = `${TOPIC_PREFIX}/status`

Expand Down Expand Up @@ -43,6 +46,16 @@ export const publishValues = async (modbusClient, mqttClient) => {
topicMap[topicName] = JSON.stringify(value)
}

const alarmStatuses = await getAlarmStatuses(modbusClient, false, true)

for (const [index, alarm] of Object.entries(alarmStatuses)) {
const topicName = `${TOPIC_PREFIX_ALARM}/${alarm.name}`

// Boolean values are changed to "ON" and "OFF" respectively since those are the
// defaults for MQTT binary sensors in Home Assistant
topicMap[topicName] = alarm.state == 2 ? 'ON' : 'OFF'
}

await publishTopics(mqttClient, topicMap)
}

Expand Down Expand Up @@ -216,11 +229,19 @@ export const configureMqttDiscovery = async (modbusClient, mqttClient) => {
'summerNightCooling': createSwitchConfiguration(configurationBase, 'summerNightCooling', 'Summer night cooling'),
}

const alarmConfigurationMap = {
}

for (const [code, alarm] of Object.entries(AVAILABLE_ALARMS)) {
alarmConfigurationMap[alarm.name] = createAlarmConfiguration(configurationBase, alarm)
}

// Final map that describes everything we want to be auto-discovered
const configurationMap = {
'sensor': sensorConfigurationMap,
'number': numberConfigurationMap,
'switch': switchConfigurationMap,
'binary_sensor': alarmConfigurationMap,
}

// Publish configurations
Expand Down Expand Up @@ -295,3 +316,14 @@ const createSwitchConfiguration = (configurationBase, modeName, entityName) => {
'command_topic': `${TOPIC_PREFIX_MODE}/${modeName}/set`,
}
}

const createAlarmConfiguration = (configurationBase, alarm) => {
return {
...configurationBase,
'unique_id': `eda-${alarm.name}`,
'name': alarm.description,
'object_id': `eda_${alarm.name}`,
'state_topic': `${TOPIC_PREFIX_ALARM}/${alarm.name}`,
'entity_category': 'diagnostic'
}
}
7 changes: 5 additions & 2 deletions eda-modbus-bridge.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import morgan from 'morgan'
import MQTT from 'async-mqtt'
import yargs from 'yargs'
import ModbusRTU from 'modbus-serial'
import { getFlagStatus, root, setFlagStatus, setSetting, summary } from './app/handlers.mjs'
import { getFlagStatus, root, setFlagStatus, setSetting, summary, getAlarms } from './app/handlers.mjs'
import { publishValues, configureMqttDiscovery, subscribeToChanges, handleMessage } from './app/mqtt.mjs'

const argv = yargs(process.argv.slice(2))
Expand Down Expand Up @@ -83,6 +83,9 @@ const argv = yargs(process.argv.slice(2))
httpServer.get('/summary', (req, res) => {
return summary(modbusClient, req, res)
})
httpServer.get('/alarms', (req, res) => {
return getAlarms(modbusClient, req, res)
})
httpServer.get('/mode/:flag', (req, res) => {
return getFlagStatus(modbusClient, req, res)
})
Expand Down Expand Up @@ -117,7 +120,7 @@ const argv = yargs(process.argv.slice(2))

const mqttClient = await MQTT.connectAsync(argv.mqttBrokerUrl, clientOptions)

// Publish readings/settings/modes regularly
// Publish readings/settings/modes/alarms regularly
setInterval(async () => {
await publishValues(modbusClient, mqttClient)
}, argv.mqttPublishInterval * 1000)
Expand Down