diff --git a/CHANGELOG.md b/CHANGELOG.md index 79cda2b..d558190 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Important changes v0.4.0 and above!!! * Main control mode, buttons and presets need to be configured again!!! - ## [0.12.0] - (21.07.2023) + ## [0.13.0] - (30.07.2023) + ## Changes +- added RESTFul server +- code refactor and cleanup +- config.schema updated + +## [0.12.0] - (21.07.2023) ## Changes - added extra temperature sensor for Heater/Cooler control mode to use with automations - config.schema updeted diff --git a/README.md b/README.md index 7c1db1d..3d1202f 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,11 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation * Support direct device controll creating extra `Buttons`, appiled for all devices of same type in account. * Support identify all states of device creating `Sensors`, appiled for all devices of same type in account. * Support automations, shortcuts and siri. +* RESTful server: + * Request: `http//homebridge_ip_address:port/path`. + * Port is based on last 4 numbers of `device Id`, displayed in HB log during start. + * Path: `info`, `state`. + * Respone as JSON data. * MQTT client: * Topic: `Info`, `State`. * Publish as JSON data. @@ -191,6 +196,8 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation | `enableDebugMode` | This enable deep log in homebridge console. | | `disableLogInfo` | This disable display log values and states on every it change. | | `disableLogDeviceInfo` | This disable display log device info on plugin start. | +| `enableRestFul` | If enabled, RESTful server will start automatically and respond to any path request. | +| `restFulDebug` | If enabled, deep log will be present in homebridge console for RESTFul server. | | `enableMqtt` | This enabled MQTT Broker and publish to it all awailable data. | | `mqttDebug` | This enabled deep log in homebridge console for MQTT. | | `mqttHost` | Here set the `IP Address` or `Hostname` for MQTT Broker. | diff --git a/config.schema.json b/config.schema.json index 90f3511..e253d8a 100644 --- a/config.schema.json +++ b/config.schema.json @@ -1006,6 +1006,23 @@ "description": "This enable debug mode.", "required": false }, + "enableRestFul": { + "title": "Enable", + "type": "boolean", + "default": false, + "required": false, + "description": "This enable RESTful server." + }, + "restFulDebug": { + "title": "Debug", + "type": "boolean", + "default": false, + "required": false, + "description": "This enable debug mode for RESTFul.", + "condition": { + "functionBody": "return model.accounts[arrayIndices].enableRestFul === true;" + } + }, "enableMqtt": { "title": "Enable", "type": "boolean", @@ -1190,6 +1207,17 @@ "accounts[].disableLogDeviceInfo" ] }, + { + "key": "accounts[]", + "type": "section", + "title": "RESTful", + "expandable": true, + "expanded": false, + "items": [ + "accounts[].enableRestFul", + "accounts[].restFulDebug" + ] + }, { "key": "accounts[]", "type": "section", diff --git a/package.json b/package.json index 474aa66..2d48ce7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "displayName": "MELCloud Control", "name": "homebridge-melcloud-control", - "version": "0.12.6", + "version": "0.13.0", "description": "Homebridge plugin (https://github.com/homebridge/homebridge) - control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.", "license": "MIT", "author": "grzegorz914", @@ -29,7 +29,8 @@ }, "dependencies": { "async-mqtt": "^2.6.3", - "axios": "^1.4.0" + "axios": "^1.4.0", + "express": "^4.18.2" }, "keywords": [ "homebridge", diff --git a/sample-config.json b/sample-config.json index 5e038dd..4b07df2 100644 --- a/sample-config.json +++ b/sample-config.json @@ -60,6 +60,8 @@ "disableLogDeviceInfo": false, "enableDebugMode": false, "mqttDebug": false, + "enableRestFul": false, + "restFulDebug": false, "enableMqtt": false, "mqttHost": "192.168.1.33", "mqttPort": 1883, diff --git a/src/melcloudata.js b/src/melcloudata.js index ff379a0..1eeae11 100644 --- a/src/melcloudata.js +++ b/src/melcloudata.js @@ -15,6 +15,8 @@ class MelCloudAta extends EventEmitter { const buildingId = config.buildingId; const deviceId = config.deviceId; const debugLog = config.debugLog; + const restFulEnabled = config.restFulEnabled; + const mqttEnabled = config.mqttEnabled; const deviceInfoFile = `${prefDir}/${accountName}_Device_${deviceId}`; //set default values @@ -315,7 +317,12 @@ class MelCloudAta extends EventEmitter { const permissionCanDisableLocalController = deviceInfo.Permissions.CanDisableLocalController; this.emit('deviceInfo', manufacturer, modelIndoor, modelOutdoor, serialNumber, firmwareAppVersion, presets, presetsCount, hasAutomaticFanSpeed, airDirectionFunction, swingFunction, numberOfFanSpeeds, temperatureIncrement, minTempCoolDry, maxTempCoolDry, minTempHeat, maxTempHeat, minTempAutomatic, maxTempAutomatic, modelSupportsFanSpeed, modelSupportsAuto, modelSupportsHeat, modelSupportsDry); - this.emit('mqtt', `Info`, deviceInfo); + + //restFul + const restFul = restFulEnabled ? this.emit('restFul', 'info', deviceInfo) : false; + + //mqtt + const mqtt = mqttEnabled ? this.emit('mqtt', `Info`, deviceInfo) : false; //check device state await new Promise(resolve => setTimeout(resolve, 1000)); @@ -407,7 +414,13 @@ class MelCloudAta extends EventEmitter { this.offline = offline; this.emit('deviceState', deviceState, roomTemperature, setTemperature, setFanSpeed, operationMode, vaneHorizontal, vaneVertical, defaultHeatingSetTemperature, defaultCoolingSetTemperature, hideVaneControls, hideDryModeControl, inStandbyMode, prohibitSetTemperature, prohibitOperationMode, prohibitPower, power, offline); - this.emit('mqtt', `State`, deviceState); + + //restFul + const restFul = restFulEnabled ? this.emit('restFul', 'state', deviceState) : false; + + //mqtt + const mqtt = mqttEnabled ? this.emit('mqtt', `State`, deviceState) : false; + this.checkDeviceInfo(); } catch (error) { this.emit('error', `check device state error, ${error}, check again in 60s.`); diff --git a/src/melcloudatw.js b/src/melcloudatw.js index 2bdf1dc..2241378 100644 --- a/src/melcloudatw.js +++ b/src/melcloudatw.js @@ -15,6 +15,8 @@ class MelCloudAtw extends EventEmitter { const buildingId = config.buildingId; const deviceId = config.deviceId; const debugLog = config.debugLog; + const restFulEnabled = config.restFulEnabled; + const mqttEnabled = config.mqttEnabled; const deviceInfoFile = `${prefDir}/${accountName}_Device_${deviceId}`; this.axiosInstanceGet = axios.create({ @@ -405,7 +407,12 @@ class MelCloudAtw extends EventEmitter { }; this.emit('deviceInfo', manufacturer, modelIndoor, modelOutdoor, serialNumber, firmwareAppVersion, presets, presetsCount, zonesCount, heatPumpZoneName, hotWaterZoneName, hasHotWaterTank, temperatureIncrement, maxTankTemperature, hasZone2, zone1Name, zone2Name, heatCoolModes, caseHotWater, caseZone2); - this.emit('mqtt', `Info`, deviceInfo); + + //restFul + const restFul = restFulEnabled ? this.emit('restFul', 'info', deviceInfo) : false; + + //mqtt + const mqtt = mqttEnabled ? this.emit('mqtt', `Info`, deviceInfo) : false; //check device state await new Promise(resolve => setTimeout(resolve, 1000)); @@ -525,7 +532,13 @@ class MelCloudAtw extends EventEmitter { this.offline = offline; this.emit('deviceState', deviceState, setTemperatureZone1, setTemperatureZone2, roomTemperatureZone1, roomTemperatureZone2, operationMode, operationModeZone1, operationModeZone2, setHeatFlowTemperatureZone1, setHeatFlowTemperatureZone2, setCoolFlowTemperatureZone1, setCoolFlowTemperatureZone2, hcControlType, tankWaterTemperature, setTankWaterTemperature, forcedHotWaterMode, unitStatus, outdoorTemperature, ecoHotWater, holidayMode, prohibitZone1, prohibitZone2, prohibitHotWater, idleZone1, idleZone2, power, offline); - this.emit('mqtt', `State`, deviceState); + + //restFul + const restFul = restFulEnabled ? this.emit('restFul', 'state', deviceState) : false; + + //mqtt + const mqtt = mqttEnabled ? this.emit('mqtt', `State`, deviceState) : false; + this.checkDeviceInfo(); } catch (error) { this.emit('error', `check device state error, ${error}, check again in 60s.`); diff --git a/src/melclouddevice.js b/src/melclouddevice.js index 85f6ec4..ba33244 100644 --- a/src/melclouddevice.js +++ b/src/melclouddevice.js @@ -1,5 +1,6 @@ "use strict"; const EventEmitter = require('events'); +const RestFul = require('./restful.js'); const Mqtt = require('./mqtt.js'); const MelCloudAta = require('./melcloudata.js'); const MelCloudAtw = require('./melcloudatw.js'); @@ -50,7 +51,29 @@ class MelCloudDevice extends EventEmitter { this.startPrepareAccessory = true; this.displayDeviceInfo = true; - //mqtt client + //RESTFul server + const restFulEnabled = account.enableRestFul || false; + this.restFulConnected = false; + if (restFulEnabled) { + const restFulPort = deviceId.slice(-4); + this.restFul = new RestFul({ + port: restFulPort, + debug: account.restFulDebug || false + }); + + this.restFul.on('connected', (message) => { + this.emit('message', `${message}`); + this.restFulConnected = true; + }) + .on('error', (error) => { + this.emit('error', error); + }) + .on('debug', (debug) => { + this.emit('debug', debug); + }); + } + + //MQTT client const mqttEnabled = account.enableMqtt || false; this.mqttConnected = false; if (mqttEnabled) { @@ -95,7 +118,9 @@ class MelCloudDevice extends EventEmitter { contextKey: contextKey, buildingId: buildingId, deviceId: deviceId, - debugLog: this.enableDebugMode + debugLog: this.enableDebugMode, + restFulEnabled: account.enableRestFul, + mqttEnabled: account.enableMqtt }); this.melCloudAta.on('deviceInfo', (manufacturer, modelIndoor, modelOutdoor, serialNumber, firmwareAppVersion, presets, presetsCount, hasAutomaticFanSpeed, airDirectionFunction, swingFunction, numberOfFanSpeeds, temperatureIncrement, minTempCoolDry, maxTempCoolDry, minTempHeat, maxTempHeat, minTempAutomatic, maxTempAutomatic, modelSupportsFanSpeed, modelSupportsAuto, modelSupportsHeat, modelSupportsDry) => { @@ -516,6 +541,9 @@ class MelCloudDevice extends EventEmitter { .on('error', (error) => { this.emit('error', error); }) + .on('restFul', (path, data) => { + const restFul = this.restFulConnected ? this.restFul.update(path, data) : false; + }) .on('mqtt', (topic, message) => { const mqtt = this.mqttConnected ? this.mqtt.send(topic, message) : false; }); @@ -527,7 +555,9 @@ class MelCloudDevice extends EventEmitter { contextKey: contextKey, buildingId: buildingId, deviceId: deviceId, - debugLog: this.enableDebugMode + debugLog: this.enableDebugMode, + restFulEnabled: account.enableRestFul, + mqttEnabled: account.enableMqtt }); this.melCloudAtw.on('deviceInfo', (manufacturer, modelIndoor, modelOutdoor, serialNumber, firmwareAppVersion, presets, presetsCount, zonesCount, heatPumpZoneName, hotWaterZoneName, hasHotWaterTank, temperatureIncrement, maxTankTemperature, hasZone2, zone1Name, zone2Name, heatCoolModes, caseHotWater, caseZone2) => { @@ -972,6 +1002,9 @@ class MelCloudDevice extends EventEmitter { .on('error', (error) => { this.emit('error', error); }) + .on('restFul', (path, data) => { + const restFul = this.restFulConnected ? this.restFul.update(path, data) : false; + }) .on('mqtt', (topic, message) => { const mqtt = this.mqttConnected ? this.mqtt.send(topic, message) : false; }); @@ -983,7 +1016,9 @@ class MelCloudDevice extends EventEmitter { contextKey: contextKey, buildingId: buildingId, deviceId: deviceId, - debugLog: this.enableDebugMode + debugLog: this.enableDebugMode, + restFulEnabled: account.enableRestFul, + mqttEnabled: account.enableMqtt }); this.melCloudErv.on('deviceInfo', (manufacturer, modelIndoor, modelOutdoor, serialNumber, firmwareAppVersion, presets, presetsCount, hasCoolOperationMode, hasHeatOperationMode, hasAutoOperationMode, hasRoomTemperature, hasSupplyTemperature, hasOutdoorTemperature, hasCO2Sensor, hasPM25Sensor, pM25SensorStatus, pM25Level, hasAutoVentilationMode, hasBypassVentilationMode, hasAutomaticFanSpeed, coreMaintenanceRequired, filterMaintenanceRequired, roomCO2Level, actualVentilationMode, numberOfFanSpeeds, temperatureIncrement) => { @@ -1336,6 +1371,9 @@ class MelCloudDevice extends EventEmitter { .on('error', (error) => { this.emit('error', error); }) + .on('restFul', (path, data) => { + const restFul = this.restFulConnected ? this.restFul.update(path, data) : false; + }) .on('mqtt', (topic, message) => { const mqtt = this.mqttConnected ? this.mqtt.send(topic, message) : false; }); diff --git a/src/melclouderv.js b/src/melclouderv.js index 84f1c23..bec85f8 100644 --- a/src/melclouderv.js +++ b/src/melclouderv.js @@ -15,6 +15,8 @@ class MelCloudErv extends EventEmitter { const buildingId = config.buildingId; const deviceId = config.deviceId; const debugLog = config.debugLog; + const restFulEnabled = config.restFulEnabled; + const mqttEnabled = config.mqttEnabled; const deviceInfoFile = `${prefDir}/${accountName}_Device_${deviceId}`; this.axiosInstanceGet = axios.create({ @@ -306,7 +308,12 @@ class MelCloudErv extends EventEmitter { const permissionCanDisableLocalController = deviceInfo.Permissions.CanDisableLocalController; this.emit('deviceInfo', manufacturer, modelIndoor, modelOutdoor, serialNumber, firmwareAppVersion, presets, presetsCount, hasCoolOperationMode, hasHeatOperationMode, hasAutoOperationMode, hasRoomTemperature, hasSupplyTemperature, hasOutdoorTemperature, hasCO2Sensor, hasPM25Sensor, pM25SensorStatus, pM25Level, hasAutoVentilationMode, hasBypassVentilationMode, hasAutomaticFanSpeed, coreMaintenanceRequired, filterMaintenanceRequired, roomCO2Level, actualVentilationMode, numberOfFanSpeeds, temperatureIncrement); - this.emit('mqtt', `Info`, deviceInfo); + + //restFul + const restFul = restFulEnabled ? this.emit('restFul', 'info', deviceInfo) : false; + + //mqtt + const mqtt = mqttEnabled ? this.emit('mqtt', `Info`, deviceInfo) : false; //check device state await new Promise(resolve => setTimeout(resolve, 1000)); @@ -397,7 +404,13 @@ class MelCloudErv extends EventEmitter { this.offline = offline; this.emit('deviceState', deviceState, roomTemperature, supplyTemperature, outdoorTemperature, nightPurgeMode, setTemperature, setFanSpeed, operationMode, ventilationMode, defaultHeatingSetTemperature, defaultCoolingSetTemperature, hideRoomTemperature, hideSupplyTemperature, hideOutdoorTemperature, power, offline); - this.emit('mqtt', `State`, deviceState); + + //restFul + const restFul = restFulEnabled ? this.emit('restFul', 'state', deviceState) : false; + + //mqtt + const mqtt = mqttEnabled ? this.emit('mqtt', `State`, deviceState) : false; + this.checkDeviceInfo(); } catch (error) { this.emit('error', `check device state error, ${error}, check again in 60s.`); diff --git a/src/restful.js b/src/restful.js new file mode 100644 index 0000000..7599ad0 --- /dev/null +++ b/src/restful.js @@ -0,0 +1,49 @@ +"use strict"; +const express = require('express'); +const EventEmitter = require('events'); + +class RestFul extends EventEmitter { + constructor(config) { + super(); + this.restFulPort = config.port; + this.restFulDebug = config.debug; + + this.restFulData = { + info: 'This data is not available at this time.', + state: 'This data is not available at this time.' + }; + + this.connect(); + }; + + connect() { + try { + const restFul = express(); + restFul.set('json spaces', 2); + restFul.get('/info', (req, res) => { res.json(this.restFulData.info) }); + restFul.get('/state', (req, res) => { res.json(this.restFulData.state) }); + + restFul.listen(this.restFulPort, () => { + this.emit('connected', `RESTful started on port: ${this.restFulPort}`) + }); + + } catch (error) { + this.emit('error', `RESTful error: ${error}`) + } + }; + + update(path, data) { + switch (path) { + case 'info': + this.restFulData.info = data; + break; + case 'state': + this.restFulData.state = data; + break; + default: + break; + }; + const emitDebug = this.restFulDebug ? this.emit('debug', `RESTFul update path: ${path}, data: ${data}`) : false; + }; +}; +module.exports = RestFul; \ No newline at end of file