Skip to content

Commit

Permalink
Merge pull request #108 from Jalle19/minor-refact
Browse files Browse the repository at this point in the history
Minor refactoring and cleanups
  • Loading branch information
Jalle19 authored Feb 10, 2024
2 parents 78982f6 + 4b7ec13 commit 040a5fb
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 67 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
* Disable "heating/cooling allowed" on legacy EDA units, fixes crash (https://github.com/Jalle19/eda-modbus-bridge/issues/105)
* Disable "eco" mode switch on older units (https://github.com/Jalle19/eda-modbus-bridge/issues/104)
* Add optional sensors for control panel temperature, supply fan speed and exhaust fan speed (https://github.com/Jalle19/eda-modbus-bridge/issues/96)
* Remove references to "flags", use "modes" everywhere instead
* Use HTTP 500 instead of HTTP 400 for generic errors

## 2.7.1

Expand Down
27 changes: 19 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ https://www.home-assistant.io/integrations/switch.rest/ with minimal effort. See
* [Usage](#usage)
* [HTTP endpoints](#http-endpoints)
* [GET /summary](#get-summary)
* [GET /mode/{flag}](#get-modeflag)
* [GET /mode/{mode}](#get-modemode)
* [GET /alarms](#get-alarms)
* [POST /mode/{flag}](#post-modeflag)
* [POST /mode/{mode}](#post-modemode)
* [POST /setting/{setting}/{value}](#post-settingsettingvalue)
* [MQTT support](#mqtt-support)
* [Home Assistant MQTT discovery](#home-assistant-mqtt-discovery)
Expand Down Expand Up @@ -138,6 +138,17 @@ Returns a JSON object like this:
"manualBoost": false,
"summerNightCooling": false
},
"modes": {
"away": false,
"longAway": false,
"overPressure": false,
"cookerHood": false,
"centralVacuumCleaner": false,
"maxHeating": false,
"maxCooling": false,
"manualBoost": false,
"summerNightCooling": false
},
"readings": {
"freshAirTemperature": -2.9,
"supplyAirTemperatureAfterHeatRecovery": 16.8,
Expand Down Expand Up @@ -215,9 +226,9 @@ Returns a JSON object like this:

```

### GET /mode/{flag}
### GET /mode/{mode}

Returns the status of the specified mode/flag. The response looks like this:
Returns the status of the specified mode. The response looks like this:

```json
{"active":false}
Expand All @@ -240,19 +251,19 @@ Returns the active or dismissed alarms. The response looks like this:
}
```

### POST /mode/{flag}
### POST /mode/{mode}

Enables/disables the specified mode/flag depending on the boolean value in the following request body:
Enables/disables the specified mode depending on the boolean value in the following request body:

```json
{"active":false}
```

The response is identical to that of `GET /mode/{flag}`.
The response is identical to that of `GET /mode/{mode}`.

### POST /setting/{setting}/{value}

Changes the setting to the specified value. HTTP 400 is thrown if the value specified is out of range or invalid.
Changes the setting to the specified value. HTTP 400 is returned if the value specified is out of range or invalid.

Returns the new setting values, like this:

Expand Down
4 changes: 3 additions & 1 deletion app/enervent.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const AVAILABLE_FLAGS = {
export const AVAILABLE_MODES = {
'away': 1,
'longAway': 2,
'overPressure': 3,
Expand Down Expand Up @@ -37,6 +37,8 @@ export const AVAILABLE_SETTINGS = {
'longAwayHeatingAllowed': 20,
}

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
65 changes: 46 additions & 19 deletions app/http.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {
getDeviceInformation,
getFlag,
getFlagSummary,
getMode as modbusGetMode,
getModeSummary,
getReadings,
getSettings,
setFlag,
setMode as modbusSetMode,
setSetting as modbusSetSetting,
getAlarmHistory,
getDeviceState,
Expand All @@ -13,14 +13,17 @@ import { createLogger } from './logger.mjs'

const logger = createLogger('http')

export const root = async (req, res) => {
const root = async (req, res) => {
res.send('eda-modbus-bridge')
}

export const summary = async (modbusClient, req, res) => {
const summary = async (modbusClient, req, res) => {
try {
let modeSummary = await getModeSummary(modbusClient)
const summary = {
'flags': await getFlagSummary(modbusClient),
// TODO: Remove in next major version
'flags': modeSummary,
'modes': modeSummary,
'readings': await getReadings(modbusClient),
'settings': await getSettings(modbusClient),
'deviceInformation': await getDeviceInformation(modbusClient),
Expand All @@ -34,10 +37,10 @@ export const summary = async (modbusClient, req, res) => {
}
}

export const getFlagStatus = async (modbusClient, req, res) => {
const getMode = async (modbusClient, req, res) => {
try {
const flag = req.params['flag']
const status = await getFlag(modbusClient, flag)
const mode = req.params['mode']
const status = await modbusGetMode(modbusClient, mode)

res.json({
'active': status,
Expand All @@ -47,24 +50,24 @@ export const getFlagStatus = async (modbusClient, req, res) => {
}
}

export const setFlagStatus = async (modbusClient, req, res) => {
const setMode = async (modbusClient, req, res) => {
try {
const flag = req.params['flag']
const mode = req.params['mode']
const status = !!req.body['active']

logger.info(`Setting flag ${flag} to ${status}`)
logger.info(`Setting mode ${mode} to ${status}`)

await setFlag(modbusClient, flag, status)
await modbusSetMode(modbusClient, mode, status)

res.json({
'active': await getFlag(modbusClient, flag),
'active': await modbusGetMode(modbusClient, mode),
})
} catch (e) {
handleError(e, res)
}
}

export const setSetting = async (modbusClient, req, res) => {
const setSetting = async (modbusClient, req, res) => {
try {
const setting = req.params['setting']
const value = req.params['value']
Expand All @@ -77,12 +80,36 @@ export const setSetting = async (modbusClient, req, res) => {
'settings': await getSettings(modbusClient),
})
} catch (e) {
handleError(e, res)
if (e instanceof RangeError) {
handleError(e, res, 400)
} else {
handleError(e, res)
}
}
}

const handleError = (e, res) => {
export const configureRoutes = (httpServer, modbusClient) => {
httpServer.get('/', root)
httpServer.get('/summary', (req, res) => {
return summary(modbusClient, req, res)
})
httpServer.get('/mode/:mode', (req, res) => {
return getMode(modbusClient, req, res)
})
httpServer.post('/mode/:mode', (req, res) => {
return setMode(modbusClient, req, res)
})
httpServer.post('/setting/:setting/:value', (req, res) => {
return setSetting(modbusClient, req, res)
})
}

const handleError = (e, res, statusCode = undefined) => {
logger.error(`An exception occurred: ${e.name}: ${e.message}`, e.stack)
res.status(400)
res.json(e)
// Use HTTP 500 if no status code has been set
res.status(statusCode ?? 500)
res.json({
error: e.name,
message: e.message,
})
}
36 changes: 19 additions & 17 deletions app/modbus.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
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,
AVAILABLE_FLAGS,
AVAILABLE_MODES,
AVAILABLE_SETTINGS,
createModelNameString,
determineAutomationType,
Expand All @@ -30,7 +32,7 @@ const logger = createLogger('modbus')
// Runtime cache for device information (retrieved once per run only since the information doesn't change)
let CACHED_DEVICE_INFORMATION

export const getFlagSummary = async (modbusClient) => {
export const getModeSummary = async (modbusClient) => {
let result = await mutex.runExclusive(async () => tryReadCoils(modbusClient, 0, 13))
let summary = {
// 'stop': result.data[0], // - Can not return value if stopped.
Expand Down Expand Up @@ -58,26 +60,26 @@ export const getFlagSummary = async (modbusClient) => {
return summary
}

export const getFlag = async (modbusClient, flag) => {
if (AVAILABLE_FLAGS[flag] === undefined) {
throw new Error('Unknown flag')
export const getMode = async (modbusClient, mode) => {
if (AVAILABLE_MODES[mode] === undefined) {
throw new Error('Unknown mode')
}

const result = await mutex.runExclusive(async () => tryReadCoils(modbusClient, AVAILABLE_FLAGS[flag], 1))
const result = await mutex.runExclusive(async () => tryReadCoils(modbusClient, AVAILABLE_MODES[mode], 1))

return result.data[0]
}

export const setFlag = async (modbusClient, flag, value) => {
if (AVAILABLE_FLAGS[flag] === undefined) {
throw new Error('Unknown flag')
export const setMode = async (modbusClient, mode, value) => {
if (AVAILABLE_MODES[mode] === undefined) {
throw new Error('Unknown mode')
}

await mutex.runExclusive(async () => tryWriteCoils(modbusClient, AVAILABLE_FLAGS[flag], value))
await mutex.runExclusive(async () => tryWriteCoil(modbusClient, AVAILABLE_MODES[mode], value))

// Flags are mutually exclusive, disable all others when enabling one
// Modes are mutually exclusive, disable all others when enabling one
if (value) {
await disableAllModesExcept(modbusClient, flag)
await disableAllModesExcept(modbusClient, mode)
}
}

Expand All @@ -87,7 +89,7 @@ const disableAllModesExcept = async (modbusClient, exceptedMode) => {
continue
}

await mutex.runExclusive(async () => tryWriteCoils(modbusClient, AVAILABLE_FLAGS[mode], false))
await mutex.runExclusive(async () => tryWriteCoil(modbusClient, AVAILABLE_MODES[mode], false))
}
}

Expand Down Expand Up @@ -267,7 +269,7 @@ 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 () => modbusClient.writeCoil(AVAILABLE_SETTINGS[setting], value))
await mutex.runExclusive(async () => tryWriteCoil(modbusClient, AVAILABLE_SETTINGS[setting], value))
} else {
await mutex.runExclusive(async () => modbusClient.writeRegister(AVAILABLE_SETTINGS[setting], intValue))
}
Expand Down Expand Up @@ -332,8 +334,8 @@ export const getDeviceInformation = async (modbusClient) => {
export const getAlarmHistory = async (modbusClient) => {
let alarmHistory = []

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

for (let register = startRegister; register <= endRegister; register += alarmOffset) {
Expand Down Expand Up @@ -416,7 +418,7 @@ const tryReadCoils = async (modbusClient, dataAddress, length) => {
}
}

const tryWriteCoils = async (modbusClient, dataAddress, value) => {
const tryWriteCoil = async (modbusClient, dataAddress, value) => {
try {
logger.debug(`Writing ${value} to coil address ${dataAddress}`)
return await modbusClient.writeCoil(dataAddress, value)
Expand Down
16 changes: 8 additions & 8 deletions app/mqtt.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import {
getDeviceInformation,
getSettings,
setSetting,
getFlagSummary,
setFlag,
getModeSummary,
setMode,
getAlarmStatuses,
getDeviceState,
} from './modbus.mjs'
Expand All @@ -27,8 +27,8 @@ export const publishValues = async (modbusClient, mqttClient) => {
[TOPIC_NAME_STATUS]: 'online',
}

// Publish state for each mode.
await publishFlags(modbusClient, mqttClient)
// Publish mode summary
await publishModeSummary(modbusClient, mqttClient)

// Publish each reading
const readings = await getReadings(modbusClient)
Expand Down Expand Up @@ -79,12 +79,12 @@ export const publishDeviceInformation = async (modbusClient, mqttClient) => {
})
}

const publishFlags = async (modbusClient, mqttClient) => {
const publishModeSummary = async (modbusClient, mqttClient) => {
// Create a map from topic name to value that should be published
let topicMap = {}

// Publish state for each mode.
const modeSummary = await getFlagSummary(modbusClient)
const modeSummary = await getModeSummary(modbusClient)

for (const [mode, state] of Object.entries(modeSummary)) {
const topicName = `${TOPIC_PREFIX_MODE}/${mode}`
Expand Down Expand Up @@ -148,8 +148,8 @@ export const handleMessage = async (modbusClient, mqttClient, topicName, rawPayl

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

await setFlag(modbusClient, mode, payload)
await publishFlags(modbusClient, mqttClient)
await setMode(modbusClient, mode, payload)
await publishModeSummary(modbusClient, mqttClient)
}
}

Expand Down
16 changes: 2 additions & 14 deletions eda-modbus-bridge.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import expressWinston from 'express-winston'
import mqtt from 'mqtt'
import yargs from 'yargs'
import ModbusRTU from 'modbus-serial'
import { getFlagStatus, root, setFlagStatus, setSetting, summary } from './app/http.mjs'
import { configureRoutes } from './app/http.mjs'
import {
publishValues,
subscribeToChanges,
Expand Down Expand Up @@ -121,19 +121,7 @@ const argv = yargs(process.argv.slice(2))
httpServer.use(express.json())

// Define routes
httpServer.get('/', root)
httpServer.get('/summary', (req, res) => {
return summary(modbusClient, req, res)
})
httpServer.get('/mode/:flag', (req, res) => {
return getFlagStatus(modbusClient, req, res)
})
httpServer.post('/mode/:flag', (req, res) => {
return setFlagStatus(modbusClient, req, res)
})
httpServer.post('/setting/:setting/:value', (req, res) => {
return setSetting(modbusClient, req, res)
})
configureRoutes(httpServer, modbusClient)

httpServer.listen(argv.httpPort, argv.httpListenAddress, () => {
httpLogger.info(`Listening on http://${argv.httpListenAddress}:${argv.httpPort}`)
Expand Down

0 comments on commit 040a5fb

Please sign in to comment.