Skip to content

Commit

Permalink
Merge pull request #115 from Jalle19/alarm-refact
Browse files Browse the repository at this point in the history
Implement support for acknowledging alarms
  • Loading branch information
Jalle19 authored Aug 16, 2024
2 parents 23c68fa + 83883ce commit ce918d0
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 50 deletions.
2 changes: 0 additions & 2 deletions app/enervent.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ export const AVAILABLE_SETTINGS = {
'defrostingAllowed': 55,
}

export const ALARM_REGISTERS_START = 385
export const ALARM_REGISTERS_END = 518
export const AVAILABLE_ALARMS = {
// Alarm number
// Name and descr based on Enervent EN EDA Modbus regirsters: 3x0385
Expand Down
20 changes: 20 additions & 0 deletions app/homeassistant.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -337,12 +337,22 @@ export const configureMqttDiscovery = async (modbusClient, mqttClient) => {
'defrosting': createDeviceStateConfiguration(configurationBase, 'defrosting', 'Defrosting'),
}

// Button for acknowledging alarms
const buttonConfigurationMap = {
'acknowledgeAlarm': createButtonConfiguration(
configurationBase,
'acknowledgeAlarm',
'Acknowledge newest alarm'
),
}

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

// Publish configurations
Expand Down Expand Up @@ -476,3 +486,13 @@ const createDeviceStateConfiguration = (configurationBase, stateName, entityName
'entity_category': 'diagnostic',
}
}

const createButtonConfiguration = (configurationBase, buttonName, entityName) => {
return {
...configurationBase,
'unique_id': `eda-button-${buttonName}`,
'name': entityName,
'object_id': `eda_button_${buttonName}`,
'command_topic': `${TOPIC_PREFIX_ALARM}/acknowledge`,
}
}
22 changes: 20 additions & 2 deletions app/http.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import {
getSettings,
setMode as modbusSetMode,
setSetting as modbusSetSetting,
getAlarmHistory,
acknowledgeAlarm as modbusAcknowledgeAlarm,
getDeviceState,
getNewestAlarm,
getAlarmSummary,
} from './modbus.mjs'
import { createLogger } from './logger.mjs'

Expand All @@ -20,15 +22,18 @@ const root = async (req, res) => {
const summary = async (modbusClient, req, res) => {
try {
let modeSummary = await getModeSummary(modbusClient)
const newestAlarm = await getNewestAlarm(modbusClient)

const summary = {
// TODO: Remove in next major version
'flags': modeSummary,
'modes': modeSummary,
'readings': await getReadings(modbusClient),
'settings': await getSettings(modbusClient),
'deviceInformation': await getDeviceInformation(modbusClient),
'alarmHistory': await getAlarmHistory(modbusClient),
'deviceState': await getDeviceState(modbusClient),
'alarmSummary': await getAlarmSummary(modbusClient),
'activeAlarm': newestAlarm?.state === 2 ? newestAlarm : null,
}

res.json(summary)
Expand Down Expand Up @@ -88,6 +93,16 @@ const setSetting = async (modbusClient, req, res) => {
}
}

const acknowledgeAlarm = async (modbusClient, req, res) => {
try {
logger.info('Acknowledging currently active alarm (if any)')

await modbusAcknowledgeAlarm(modbusClient)
} catch (e) {
handleError(e, res)
}
}

export const configureRoutes = (httpServer, modbusClient) => {
httpServer.get('/', root)
httpServer.get('/summary', (req, res) => {
Expand All @@ -102,6 +117,9 @@ export const configureRoutes = (httpServer, modbusClient) => {
httpServer.post('/setting/:setting/:value', (req, res) => {
return setSetting(modbusClient, req, res)
})
httpServer.post('/alarm/acknowledge', (req, res) => {
return acknowledgeAlarm(modbusClient, req, res)
})
}

const handleError = (e, res, statusCode = undefined) => {
Expand Down
80 changes: 40 additions & 40 deletions app/modbus.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Mutex } from 'async-mutex'
import { createLogger } from './logger.mjs'
import {
ALARM_REGISTERS_END,
ALARM_REGISTERS_START,
AUTOMATION_TYPE_LEGACY_EDA,
AUTOMATION_TYPE_MD,
AVAILABLE_ALARMS,
Expand Down Expand Up @@ -231,7 +229,8 @@ export const getSettings = async (modbusClient) => {
}

export const setSetting = async (modbusClient, setting, value) => {
if (AVAILABLE_SETTINGS[setting] === undefined) {
const dataAddress = AVAILABLE_SETTINGS[setting]
if (dataAddress === undefined) {
throw new Error('Unknown setting')
}

Expand Down Expand Up @@ -277,9 +276,9 @@ export const setSetting = async (modbusClient, setting, value) => {

// This isn't very nice, but it's good enough for now
if (coil) {
await mutex.runExclusive(async () => tryWriteCoil(modbusClient, AVAILABLE_SETTINGS[setting], value))
await mutex.runExclusive(async () => tryWriteCoil(modbusClient, dataAddress, value))
} else {
await mutex.runExclusive(async () => modbusClient.writeRegister(AVAILABLE_SETTINGS[setting], intValue))
await mutex.runExclusive(async () => tryWriteHoldingRegister(modbusClient, dataAddress, intValue))
}
}

Expand Down Expand Up @@ -339,53 +338,44 @@ export const getDeviceInformation = async (modbusClient) => {
return deviceInformation
}

export const getAlarmHistory = async (modbusClient) => {
let alarmHistory = []
export const getAlarmSummary = async (modbusClient) => {
let alarmSummary = { ...AVAILABLE_ALARMS }
const newestAlarm = await getNewestAlarm(modbusClient)

const startRegister = ALARM_REGISTERS_START
const endRegister = ALARM_REGISTERS_END
const alarmOffset = 7

for (let register = startRegister; register <= endRegister; register += alarmOffset) {
const result = await mutex.runExclusive(async () =>
tryReadHoldingRegisters(modbusClient, register, alarmOffset)
)
const code = result.data[0]
const state = result.data[1]
for (const type in alarmSummary) {
// Use "off" as the default alarm state, most likely to be true
alarmSummary[type].state = 0

// Skip unset alarm slots and unknown alarm types
if (AVAILABLE_ALARMS[code] === undefined) {
continue
// Use the state from the newest alarm
if (type === newestAlarm.type) {
alarmSummary[type].state = newestAlarm.state
}

let alarm = Object.assign({}, AVAILABLE_ALARMS[code])
alarm.state = state
alarm.date = parseAlarmTimestamp(result)

alarmHistory.push(alarm)
}

return alarmHistory
return alarmSummary
}

export const getAlarmStatuses = async (modbusClient) => {
let alarms = { ...AVAILABLE_ALARMS }
export const getNewestAlarm = async (modbusClient) => {
const result = await mutex.runExclusive(async () => tryReadHoldingRegisters(modbusClient, 385, 7))

// Use the alarm history to determine the state of each alarm
const alarmHistory = await getAlarmHistory(modbusClient)
const type = result.data[0]
const state = result.data[1]
const timestamp = parseAlarmTimestamp(result)

for (const code in alarms) {
// Use "off" as the default alarm state, most likely to be true
alarms[code].state = 0
if (AVAILABLE_ALARMS[type] === undefined) {
return null
}

for (const historicAlarm of alarmHistory) {
if (historicAlarm.name === alarms[code].name && historicAlarm.state > 0) {
alarms[code].state = historicAlarm.state
}
}
return {
...AVAILABLE_ALARMS[type],
type,
state,
timestamp,
}
}

return alarms
export const acknowledgeAlarm = async (modbusClient) => {
await tryWriteHoldingRegister(modbusClient, 386, 1)
}

export const getDeviceState = async (modbusClient) => {
Expand Down Expand Up @@ -445,3 +435,13 @@ const tryReadHoldingRegisters = async (modbusClient, dataAddress, length) => {
throw e
}
}

const tryWriteHoldingRegister = async (modbusClient, dataAddress, value) => {
try {
logger.debug(`Writing ${value} to holding register address ${dataAddress}`)
return await modbusClient.writeRegister(dataAddress, value)
} catch (e) {
logger.error(`Failed to write holding register address ${dataAddress}, value ${value}`)
throw e
}
}
24 changes: 18 additions & 6 deletions app/mqtt.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import {
setSetting,
getModeSummary,
setMode,
getAlarmStatuses,
getAlarmSummary,
getDeviceState,
acknowledgeAlarm,
} from './modbus.mjs'
import { createLogger } from './logger.mjs'

Expand Down Expand Up @@ -41,9 +42,10 @@ export const publishValues = async (modbusClient, mqttClient) => {
// Publish each setting
await publishSettings(modbusClient, mqttClient)

const alarmStatuses = await getAlarmStatuses(modbusClient)
// Publish alarm summary
const alarmSummary = await getAlarmSummary(modbusClient)

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

topicMap[topicName] = createBinaryValue(alarm.state === 2)
Expand Down Expand Up @@ -120,8 +122,12 @@ const publishTopics = async (mqttClient, topicMap, publishOptions = {}) => {
}

export const subscribeToChanges = async (modbusClient, mqttClient) => {
// Subscribe to settings and mode changes
const topicNames = [`${TOPIC_PREFIX_MODE}/+/set`, `${TOPIC_PREFIX_SETTINGS}/+/set`]
// Subscribe to writable topics
const topicNames = [
`${TOPIC_PREFIX_MODE}/+/set`,
`${TOPIC_PREFIX_SETTINGS}/+/set`,
`${TOPIC_PREFIX_ALARM}/acknowledge`,
]

for (const topicName of topicNames) {
logger.info(`Subscribing to topic(s) ${topicName}`)
Expand All @@ -135,21 +141,27 @@ export const handleMessage = async (modbusClient, mqttClient, topicName, rawPayl

const payload = parsePayload(rawPayload)

// Handle settings updates
if (topicName.startsWith(TOPIC_PREFIX_SETTINGS) && topicName.endsWith('/set')) {
// Handle settings updates
const settingName = topicName.substring(TOPIC_PREFIX_SETTINGS.length + 1, topicName.lastIndexOf('/'))

logger.info(`Updating setting ${settingName} to ${payload}`)

await setSetting(modbusClient, settingName, payload)
await publishSettings(modbusClient, mqttClient)
} else if (topicName.startsWith(TOPIC_PREFIX_MODE) && topicName.endsWith('/set')) {
// Handle mode changes
const mode = topicName.substring(TOPIC_PREFIX_MODE.length + 1, topicName.lastIndexOf('/'))

logger.info(`Updating mode ${mode} to ${payload}`)

await setMode(modbusClient, mode, payload)
await publishModeSummary(modbusClient, mqttClient)
} else if (topicName.startsWith(TOPIC_PREFIX_ALARM) && topicName.endsWith('/acknowledge')) {
// Acknowledge alarm
logger.info('Acknowledging currently active alarm (if any)')

await acknowledgeAlarm(modbusClient)
}
}

Expand Down

0 comments on commit ce918d0

Please sign in to comment.