From 5a2b4ca6de3b048f1dd60180315223a55fd26c7c Mon Sep 17 00:00:00 2001 From: Grzegorz Date: Sat, 31 Aug 2024 13:51:50 +0200 Subject: [PATCH] release 3.1.0 --- CHANGELOG.md | 12 +- README.md | 15 +- config.schema.json | 362 ++++++++++++++++++++++++++++---- homebridge-ui/public/index.html | 296 ++++++++++++++++++-------- package-lock.json | 18 +- package.json | 4 +- src/deviceata.js | 203 +++++++++++------- src/deviceatw.js | 230 +++++++++++--------- src/deviceerv.js | 200 +++++++++++------- 9 files changed, 938 insertions(+), 402 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a05e1f..20c6035 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - v3 After update to v3 from previous versions the plugin need to be configured using Config UI X. - do not configure it manually, always using Config UI X +## [3.1.0] - (31.08.2024) + +## Changes + +- improvements in plugin config UI +- added possibility indyvidual presets control +- config schema update +- bump dependencies +- cleanup + ## [3.0.7] - (30.08.2024) ## Changes @@ -29,7 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - added homebridge UI server - whole new concept get all devices from melcloud usin config UI X -- added indyvidual configuration of every device +- added individual configuration of every device - config schema updated - cleanup diff --git a/README.md b/README.md index 19e64a3..0977174 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,10 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation | `ataDevices.autoDryFanMode` | Here select the operatiing mode for `Auto`, if this mode is not supported, it will be disabled.. | | `ataDevices.temperatureSensor` | This enable extra `Room` temperature sensors to use with automations in HomeKit app. | | `ataDevices.temperatureSensorOutdoor` | This enable extra `Outdoor` temperature sensors to use with automations in HomeKit app. | -| `ataDevices.presets` | This enable extra buttons for configured presets and display it in HomeKit app. | +| `ataDevices.presets` | Array of ATA device Presets created automatically after login to MELCloud from plugin config UI. | +| `ataDevices.presets.id` | Read only data, do not change it. | +| `ataDevices.presets.name` | Here You can schange the `Preset Name` which is exposed to the `Homebridge/HomeKit`. | +| `ataDevices.presets.displayType` | Here select display type in HomeKit, `0 - None/Disabled`, `1 - Outlet`, `2 - Switch`, `3 - Motion Sensor`, `4 - Occupancy Sensor`, `5 - Contact Sensor`. | | `buttonsSensors` | Array of buttons sensors. | | `buttonsSensors.name` | Here set `Button Name` which You want expose to the `Homebridge/HomeKit`. | | `buttonsSensors.mode` | Here select button mode, VH - Vane Horizontal, VV - Vane Horizontal. | @@ -206,7 +209,10 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation | `atwDevices.temperatureSensorReturnWaterTank` | This enable extra `Return Water Tank` temperature sensors to use with automations in HomeKit app. | | `atwDevices.temperatureSensorFlowZone2` | This enable extra `Flow Zone 2` temperature sensors to use with automations in HomeKit app. | | `atwDevices.temperatureSensorReturnZone2` | This enable extra `Return Zone 2` temperature sensors to use with automations in HomeKit app. | -| `atwDevices.presets` | This enable extra buttons for configured presets and display it in HomeKit app. | +| `atwDevices.presets` | Array of ATA device Presets created automatically after login to MELCloud from plugin config UI. | +| `atwDevices.presets.id` | Read only data, do not change it. | +| `atwDevices.presets.name` | Here You can schange the `Preset Name` which is exposed to the `Homebridge/HomeKit`. | +| `atwDevices.presets.displayType` | Here select display type in HomeKit, `0 - None/Disabled`, `1 - Outlet`, `2 - Switch`, `3 - Motion Sensor`, `4 - Occupancy Sensor`, `5 - Contact Sensor`. | | `buttonsSensors` | Array of buttons sensors. | | `buttonsSensors.name` | Here set `Button Name` which You want expose to the `Homebridge/HomeKit`. | | `buttonsSensors.mode` | Here select button mode. | @@ -221,7 +227,10 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation | `ervDevices.temperatureSensor` | This enable extra `Room` temperature sensors to use with automations in HomeKit app. | | `ervDevices.temperatureSensorOutdoor` | This enable extra `Outdoor` temperature sensors to use with automations in HomeKit app. | | `ervDevices.temperatureSensorSupply` | This enable extra `Supply` temperature sensors to use with automations in HomeKit app. | -| `ervDevices.presets` | This enable extra buttons for configured presets and display it in HomeKit app. | +| `ervDevices.presets` | Array of ATA device Presets created automatically after login to MELCloud from plugin config UI. | +| `ervDevices.presets.id` | Read only data, do not change it. | +| `ervDevices.presets.name` | Here You can schange the `Preset Name` which is exposed to the `Homebridge/HomeKit`. | +| `ervDevices.presets.displayType` | Here select display type in HomeKit, `0 - None/Disabled`, `1 - Outlet`, `2 - Switch`, `3 - Motion Sensor`, `4 - Occupancy Sensor`, `5 - Contact Sensor`. | | `buttonsSensors` | Array of buttons sensors. | | `buttonsSensors.name` | Here set `Button Name` which You want expose to the `Homebridge/HomeKit`. | | `buttonsSensors.mode` | Here select button mode. | diff --git a/config.schema.json b/config.schema.json index aa53aa8..8ec6d39 100644 --- a/config.schema.json +++ b/config.schema.json @@ -213,22 +213,25 @@ "id": { "title": "ID", "type": "integer", - "required": false + "required": true, + "readonly": true }, "type": { "title": "Type", "type": "integer", - "required": false + "required": true, + "readonly": true }, "typeString": { "title": "Type", "type": "string", - "required": false + "required": true, + "readonly": true }, "name": { "title": "Name", "type": "string", - "required": false + "required": true }, "displayMode": { "title": "Control", @@ -363,13 +366,6 @@ "description": "Select the operating mode for Auto, if this mode is not supported, it will be disabled.", "required": true }, - "presets": { - "title": "Presets", - "type": "boolean", - "default": false, - "description": "This enable extra buttons for configured presets and display it in HomeKit app.", - "required": false - }, "temperatureSensor": { "title": "Room", "type": "boolean", @@ -384,6 +380,81 @@ "description": "This enable extra outdoor temperature sensor to use with automations in HomeKit app.", "required": false }, + "presets": { + "title": "Presets", + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "title": "ID", + "type": "integer", + "required": true + }, + "name": { + "title": "Name", + "type": "string", + "placeholder": "Preset name", + "description": "Your own name displayed in Homebridge/HomeKit app.", + "required": true + }, + "displayType": { + "title": "Type", + "type": "integer", + "minimum": 0, + "maximum": 5, + "default": 0, + "oneOf": [ + { + "title": "None/Disabled", + "enum": [ + 0 + ] + }, + { + "title": "Outlet", + "enum": [ + 1 + ] + }, + { + "title": "Switch", + "enum": [ + 2 + ] + }, + { + "title": "Motion Sensor", + "enum": [ + 3 + ] + }, + { + "title": "Occupancy Sensor", + "enum": [ + 4 + ] + }, + { + "title": "Contact Sensor", + "enum": [ + 5 + ] + } + ], + "description": "Select the characteristic type to be displayed in HomeKit app.", + "required": true + }, + "namePrefix": { + "title": "Prefix", + "type": "boolean", + "default": false, + "description": "Enable/disable the accessory name as a prefix for button/sensor name.", + "required": false + } + } + } + }, "buttonsSensors": { "title": "Button / Sensor", "type": "array", @@ -677,7 +748,7 @@ } }, "condition": { - "functionBody": "const devices = model.accounts[arrayIndices]?.ataDevices || []; return devices.length > 0 && devices.every(device => device.id != null && device.id !== '');" + "functionBody": "const devices = model.accounts[arrayIndices]?.ataDevices || []; return devices.length > 0 && devices.every(device => device.id != null);" } }, "atwDevices": { @@ -689,22 +760,25 @@ "id": { "title": "Mode", "type": "integer", - "required": false + "required": true, + "readonly": true }, "type": { "title": "Type", "type": "integer", - "required": false + "required": true, + "readonly": true }, "typeString": { "title": "Type", "type": "string", - "required": false + "required": true, + "readonly": true }, "name": { "title": "Name", "type": "string", - "required": false + "required": true }, "displayMode": { "title": "Control", @@ -800,10 +874,78 @@ }, "presets": { "title": "Presets", - "type": "boolean", - "default": false, - "description": "This enable extra buttons for configured presets and display it in HomeKit app.", - "required": false + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "title": "ID", + "type": "integer", + "required": true + }, + "name": { + "title": "Name", + "type": "string", + "placeholder": "Preset name", + "description": "Your own name displayed in Homebridge/HomeKit app.", + "required": true + }, + "displayType": { + "title": "Type", + "type": "integer", + "minimum": 0, + "maximum": 5, + "default": 0, + "oneOf": [ + { + "title": "None/Disabled", + "enum": [ + 0 + ] + }, + { + "title": "Outlet", + "enum": [ + 1 + ] + }, + { + "title": "Switch", + "enum": [ + 2 + ] + }, + { + "title": "Motion Sensor", + "enum": [ + 3 + ] + }, + { + "title": "Occupancy Sensor", + "enum": [ + 4 + ] + }, + { + "title": "Contact Sensor", + "enum": [ + 5 + ] + } + ], + "description": "Select the characteristic type to be displayed in HomeKit app.", + "required": true + }, + "namePrefix": { + "title": "Prefix", + "type": "boolean", + "default": false, + "description": "Enable/disable the accessory name as a prefix for button/sensor name.", + "required": false + } + } + } }, "buttonsSensors": { "title": "Button", @@ -1020,7 +1162,7 @@ } }, "condition": { - "functionBody": "const devices = model.accounts[arrayIndices]?.atwDevices || []; return devices.length > 0 && devices.every(device => device.id != null && device.id !== '');" + "functionBody": "const devices = model.accounts[arrayIndices]?.atwDevices || []; return devices.length > 0 && devices.every(device => device.id != null);" } }, "ervDevices": { @@ -1032,29 +1174,32 @@ "id": { "title": "Mode", "type": "integer", - "required": false + "required": true, + "readonly": true }, "type": { "title": "Type", "type": "integer", - "required": false + "required": true, + "readonly": true }, "typeString": { "title": "Type", "type": "string", - "required": false + "required": true, + "readonly": true }, "name": { "title": "Name", "type": "string", - "required": false + "required": true }, "displayMode": { "title": "Control", "type": "integer", "minimum": 0, "maximum": 2, - "default": 0, + "default": 1, "oneOf": [ { "title": "None/Disabled", @@ -1101,13 +1246,82 @@ }, "presets": { "title": "Presets", - "type": "boolean", - "default": false, - "description": "This enable extra buttons for configured presets and display it in HomeKit app.", - "required": false + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "title": "ID", + "type": "integer", + "required": true, + "readonly": true + }, + "name": { + "title": "Name", + "type": "string", + "placeholder": "Preset name", + "description": "Your own name displayed in Homebridge/HomeKit app.", + "required": true + }, + "displayType": { + "title": "Type", + "type": "integer", + "minimum": 0, + "maximum": 5, + "default": 0, + "oneOf": [ + { + "title": "None/Disabled", + "enum": [ + 0 + ] + }, + { + "title": "Outlet", + "enum": [ + 1 + ] + }, + { + "title": "Switch", + "enum": [ + 2 + ] + }, + { + "title": "Motion Sensor", + "enum": [ + 3 + ] + }, + { + "title": "Occupancy Sensor", + "enum": [ + 4 + ] + }, + { + "title": "Contact Sensor", + "enum": [ + 5 + ] + } + ], + "description": "Select the characteristic type to be displayed in HomeKit app.", + "required": true + }, + "namePrefix": { + "title": "Prefix", + "type": "boolean", + "default": false, + "description": "Enable/disable the accessory name as a prefix for button/sensor name.", + "required": false + } + } + } }, "buttonsSensors": { - "title": "Button", + "title": "Buttons", "type": "array", "items": { "type": "object", @@ -1273,7 +1487,7 @@ } }, "condition": { - "functionBody": "const devices = model.accounts[arrayIndices]?.ervDevices || []; return devices.length > 0 && devices.every(device => device.id != null && device.id !== '');" + "functionBody": "const devices = model.accounts[arrayIndices]?.ervDevices || []; return devices.length > 0 && devices.every(device => device.id != null);" } }, "refreshInterval": { @@ -1484,7 +1698,7 @@ { "key": "accounts[].ataDevices[]", "type": "section", - "title": "Configuration", + "title": "Settings", "expandable": true, "expanded": false, "items": [ @@ -1492,8 +1706,30 @@ "accounts[].ataDevices[].displayMode", "accounts[].ataDevices[].heatDryFanMode", "accounts[].ataDevices[].coolDryFanMode", - "accounts[].ataDevices[].autoDryFanMode", - "accounts[].ataDevices[].presets" + "accounts[].ataDevices[].autoDryFanMode" + ] + }, + { + "key": "accounts[].ataDevices[]", + "type": "section", + "title": "Presets", + "expandable": true, + "expanded": false, + "items": [ + { + "key": "accounts[].ataDevices[].presets", + "type": "tabarray", + "title": "{{ value.name }}", + "items": [ + { + "key": "accounts[].ataDevices[].presets[].id", + "readonly": true + }, + "accounts[].ataDevices[].presets[].name", + "accounts[].ataDevices[].presets[].displayType", + "accounts[].ataDevices[].presets[].namePrefix" + ] + } ] }, { @@ -1551,13 +1787,35 @@ { "key": "accounts[].atwDevices[]", "type": "section", - "title": "Configuration", + "title": "Settings", "expandable": true, "expanded": false, "items": [ "accounts[].atwDevices[].name", - "accounts[].atwDevices[].displayMode", - "accounts[].atwDevices[].presets" + "accounts[].atwDevices[].displayMode" + ] + }, + { + "key": "accounts[].atwDevices[]", + "type": "section", + "title": "Presets", + "expandable": true, + "expanded": false, + "items": [ + { + "key": "accounts[].atwDevices[].presets", + "type": "tabarray", + "title": "{{ value.name }}", + "items": [ + { + "key": "accounts[].atwDevices[].presets[].id", + "readonly": true + }, + "accounts[].atwDevices[].presets[].name", + "accounts[].atwDevices[].presets[].displayType", + "accounts[].atwDevices[].presets[].namePrefix" + ] + } ] }, { @@ -1622,13 +1880,35 @@ { "key": "accounts[].ervDevices[]", "type": "section", - "title": "Configuration", + "title": "Settings", "expandable": true, "expanded": false, "items": [ "accounts[].ervDevices[].name", - "accounts[].ervDevices[].displayMode", - "accounts[].ervDevices[].presets" + "accounts[].ervDevices[].displayMode" + ] + }, + { + "key": "accounts[].ervDevices[]", + "type": "section", + "title": "Presets", + "expandable": true, + "expanded": false, + "items": [ + { + "key": "accounts[].ervDevices[].presets", + "type": "tabarray", + "title": "{{ value.name }}", + "items": [ + { + "key": "accounts[].ervDevices[].presets[].id", + "readonly": true + }, + "accounts[].ervDevices[].presets[].name", + "accounts[].ervDevices[].presets[].displayType", + "accounts[].ervDevices[].presets[].namePrefix" + ] + } ] }, { diff --git a/homebridge-ui/public/index.html b/homebridge-ui/public/index.html index c2cca59..ceacd94 100644 --- a/homebridge-ui/public/index.html +++ b/homebridge-ui/public/index.html @@ -8,6 +8,8 @@


+
+
@@ -130,11 +132,10 @@ //watch for click on the login button document.getElementById('logIn').addEventListener('click', async () => { homebridge.showSpinner(); + document.getElementById('info').innerHTML = 'Connecting...'; + await homebridge.updatePluginConfig(pluginConfig) try { - await homebridge.updatePluginConfig(pluginConfig) - document.getElementById('info').innerHTML = 'Connecting...'; - const accountName = pluginConfig[0].accounts[this.deviceIndex].name; const user = pluginConfig[0].accounts[this.deviceIndex].user; const passwd = pluginConfig[0].accounts[this.deviceIndex].passwd; @@ -150,113 +151,230 @@ const info = response.info ?? ''; const status = response.status; const devices = response.data ?? []; - const newDevices = { + const devicesByType = { ata: [], atw: [], erv: [] }; + + //new found devices + const newDevices = { + ata: [], + ataPresets: [], + atw: [], + atwPresets: [], + erv: [], + ervPresets: [], + }; + + //info text let textAta = 'ATA: 0'; + let textAtaPresets = 'ATA Presets: 0'; let textAtw = 'ATW: 0'; + let textAtwPresets = 'ATW Presets: 0'; let textErv = 'ERV: 0'; + let textErvPresets = 'ERV Presets: 0'; + //response status switch (status) { case 0: - //get device info fom devices - devices.forEach((device, index) => { + //get devices by type + for (const device of devices) { + const deviceType = device.Type; + const pusDeviceTypeAta = deviceType === 0 ? devicesByType.ata.push(device) : false; + const pusDeviceTypeAtw = deviceType === 1 ? devicesByType.atw.push(device) : false; + const pusDeviceTypeErv = deviceType === 3 ? devicesByType.erv.push(device) : false; + }; + + //get device info fom devices ata + devicesByType.ata.forEach((device, index) => { + const deviceId = device.DeviceID; + const deviceType = device.Type; + const deviceTypeText = "Air Conditioner"; + const deviceName = device.DeviceName; + const devicePresets = device.Presets ?? []; + + //device ata + const deviceAta = { + id: deviceId, + type: deviceType, + typeString: deviceTypeText, + name: deviceName, + displayMode: 1, + heatDryFanMode: 1, + coolDryFanMode: 1, + autoDryFanMode: 1, + temperatureSensor: false, + temperatureSensorOutdoor: false, + presets: [], + buttonsSensors: [] + }; + + const ataDevicesExist = pluginConfig[0].accounts[this.deviceIndex].ataDevices ?? false; + const ataDevices = ataDevicesExist ? pluginConfig[0].accounts[this.deviceIndex].ataDevices : pluginConfig[0].accounts[this.deviceIndex].ataDevices = []; + const ataDeviceIdExist = ataDevices.some(device => device.id === deviceId); + const pushTypeAta = ataDeviceIdExist ? false : ataDevices.push(deviceAta); + const pushNewDevice = ataDeviceIdExist ? false : newDevices.ata.push(device); + const devicesCount = newDevices.ata.length; + textAta = `ATA: ${devicesCount}` + + //presets + for (const preset of devicePresets) { + const presetId = preset.ID; + const presetName = preset.NumberDescription; + + preset.id = presetId; + preset.name = presetName; + preset.displayType == 0; + preset.namePrefix = false; + + const ataPresetsExist = pluginConfig[0].accounts[this.deviceIndex].ataDevices[index].presets ?? false; + const ataPresets = ataPresetsExist ? pluginConfig[0].accounts[this.deviceIndex].ataDevices[index].presets : pluginConfig[0].accounts[this.deviceIndex].ataDevices[index].presets = []; + const ataPresetIdExist = ataPresets.some(preset => preset.id === presetId); + const pushPresetAta = ataPresetIdExist ? false : ataPresets.push(preset); + const pushNewPreset = ataPresetIdExist ? false : newDevices.ataPresets.push(preset); + const presetsCount = newDevices.ataPresets.length; + textAtaPresets = `ATA Presets: ${presetsCount}` + }; + }); + + //get device info fom devices atw + devicesByType.atw.forEach((device, index) => { const deviceId = device.DeviceID; const deviceType = device.Type; - const deviceTypeText = ["Air Conditioner", "Heat Pump", "Unknown", "Energy Recovery Ventilation"][deviceType]; + const deviceTypeText = "Heat Pump"; const deviceName = device.DeviceName; + const devicePresets = device.Presets ?? []; - //config account keys - const accountKeys = Object.keys(pluginConfig[0].accounts[this.deviceIndex]); - - //devices ata - if (deviceType === 0) { - const deviceAta = { - id: deviceId, - type: deviceType, - typeString: deviceTypeText, - name: deviceName, - displayMode: 1, - heatDryFanMode: 1, - coolDryFanMode: 1, - autoDryFanMode: 1, - presets: false, - temperatureSensor: false, - temperatureSensorOutdoor: false, - buttonsSensors: [] - }; - - const ataDevicesExist = pluginConfig[0].accounts[this.deviceIndex].ataDevices ?? false; - const ataDevices = ataDevicesExist ? pluginConfig[0].accounts[this.deviceIndex].ataDevices : pluginConfig[0].accounts[this.deviceIndex].ataDevices = []; - const ataDeviceIdExist = ataDevices.some(device => device.id === deviceId); - const pushTypeAta = ataDeviceIdExist ? false : ataDevices.push(deviceAta); - const pushNewDevice = ataDeviceIdExist ? false : newDevices.ata.push(device); - const devicesCount = newDevices.ata.length; - textAta = `ATA: ${devicesCount}` + //device atw + const deviceAtw = { + id: deviceId, + type: deviceType, + typeString: deviceTypeText, + name: deviceName, + displayMode: 1, + temperatureSensor: false, + temperatureSensorFlow: false, + temperatureSensorReturn: false, + temperatureSensorFlowZone1: false, + temperatureSensorReturnZone1: false, + temperatureSensorFlowWaterTank: false, + temperatureSensorReturnWaterTank: false, + temperatureSensorFlowZone2: false, + temperatureSensorReturnZone2: false, + temperatureSensor: false, + temperatureSensorOutdoor: false, + presets: [], + buttonsSensors: [] }; + const atwDevicesExist = pluginConfig[0].accounts[this.deviceIndex].atwDevices ?? false; + const atwDevices = atwDevicesExist ? pluginConfig[0].accounts[this.deviceIndex].atwDevices : pluginConfig[0].accounts[this.deviceIndex].atwDevices = []; + const atwDeviceIdExist = atwDevices.some(device => device.id === deviceId); + const pushTypeAtw = atwDeviceIdExist ? false : atwDevices.push(deviceAtw); + const pushNewDevice = atwDeviceIdExist ? false : newDevices.atw.push(device); + const devicesCount = newDevices.atw.length; + textAtw = `ATW: ${devicesCount}` + + //presetzs + for (const preset of devicePresets) { + const presetId = preset.ID; + const presetName = preset.NumberDescription; + + preset.id = presetId; + preset.name = presetName; + preset.displayType == 0; + preset.namePrefix = false; + + const atwPresetsExist = pluginConfig[0].accounts[this.deviceIndex].atwDevices[index].presets ?? false; + const atwPresets = atwPresetsExist ? pluginConfig[0].accounts[this.deviceIndex].atwDevices[index].presets : pluginConfig[0].accounts[this.deviceIndex].atwDevices[index].presets = []; + const atwPresetIdExist = atwPresets.some(preset => preset.id === presetId); + const pushPresetAtw = atwPresetIdExist ? false : atwPresets.push(preset); + const pushNewPreset = atwPresetIdExist ? false : newDevices.atwPresets.push(preset); + const presetsCount = newDevices.atwPresets.length; + textAtwPresets = `ATW Presets: ${presetsCount}` + }; + }); - //devices atw - if (deviceType === 1) { - const deviceAtw = { - id: deviceId, - type: deviceType, - typeString: deviceTypeText, - name: deviceName, - displayMode: 1, - temperatureSensor: false, - temperatureSensorFlow: false, - temperatureSensorReturn: false, - temperatureSensorFlowZone1: false, - temperatureSensorReturnZone1: false, - temperatureSensorFlowWaterTank: false, - temperatureSensorReturnWaterTank: false, - temperatureSensorFlowZone2: false, - temperatureSensorReturnZone2: false, - presets: false, - temperatureSensor: false, - temperatureSensorOutdoor: false, - buttonsSensors: [] - }; - const atwDevicesExist = pluginConfig[0].accounts[this.deviceIndex].atwDevices ?? false; - const atwDevices = atwDevicesExist ? pluginConfig[0].accounts[this.deviceIndex].atwDevices : pluginConfig[0].accounts[this.deviceIndex].atwDevices = []; - const atwDeviceIdExist = atwDevices.some(device => device.id === deviceId); - const pushTypeatw = atwDeviceIdExist ? false : atwDevices.push(deviceAtw); - const pushNewDevice = atwDeviceIdExist ? false : newDevices.atw.push(device); - const devicesCount = newDevices.ata.length; - textAtw = `ATW: ${devicesCount}` + //get device info fom devices erv + devicesByType.erv.forEach((device, index) => { + const deviceId = device.DeviceID; + const deviceType = device.Type; + const deviceTypeText = "Energy Recovery Ventilation"; + const deviceName = device.DeviceName; + const devicePresets = device.Presets ?? []; + + //device erv + const deviceErv = { + id: deviceId, + type: deviceType, + typeString: deviceTypeText, + name: deviceName, + displayMode: 1, + temperatureSensor: false, + temperatureSensorOutdoor: false, + temperatureSensorSupply: false, + temperatureSensor: false, + temperatureSensorOutdoor: false, + presets: [], + buttonsSensors: [] }; + const ervDevicesExist = pluginConfig[0].accounts[this.deviceIndex].ervDevices ?? false; + const ervDevices = ervDevicesExist ? pluginConfig[0].accounts[this.deviceIndex].ervDevices : pluginConfig[0].accounts[this.deviceIndex].ervDevices = []; + const ervDeviceIdExist = ervDevices.some(device => device.id === deviceId); + const pushTypeerv = ervDeviceIdExist ? false : ervDevices.push(deviceErv); + const pushNewDevice = ervDeviceIdExist ? false : newDevices.erv.push(device); + const devicesCount = newDevices.erv.length; + textErv = `ERV: ${devicesCount}` + + //presets + for (const preset of devicePresets) { + const presetId = preset.ID; + const presetName = preset.NumberDescription; - //devices erv - if (deviceType === 3) { - const deviceErv = { - id: deviceId, - type: deviceType, - typeString: deviceTypeText, - name: deviceName, - displayMode: 1, - temperatureSensor: false, - temperatureSensorOutdoor: false, - temperatureSensorSupply: false, - presets: false, - temperatureSensor: false, - temperatureSensorOutdoor: false, - buttonsSensors: [] - }; - const ervDevicesExist = pluginConfig[0].accounts[this.deviceIndex].ervDevices ?? false; - const ervDevices = ervDevicesExist ? pluginConfig[0].accounts[this.deviceIndex].ervDevices : pluginConfig[0].accounts[this.deviceIndex].ervDevices = []; - const ervDeviceIdExist = ervDevices.some(device => device.id === deviceId); - const pushTypeerv = ervDeviceIdExist ? false : ervDevices.push(deviceErv); - const pushNewDevice = ervDeviceIdExist ? false : newDevices.erv.push(device); - const devicesCount = newDevices.ata.length; - textErv = `ERV: ${devicesCount}` + preset.id = presetId; + preset.name = presetName; + preset.displayType == 0; + preset.namePrefix = false; + + const ervPresetsExist = pluginConfig[0].accounts[this.deviceIndex].ervDevices[index].presets ?? false; + const ervPresets = ervPresetsExist ? pluginConfig[0].accounts[this.deviceIndex].ervDevices[index].presets : pluginConfig[0].accounts[this.deviceIndex].ervDevices[index].presets = []; + const ervPresetIdExist = ervPresets.some(preset => preset.id === presetId); + const pushPresetErv = ervPresetIdExist ? false : ervPresets.push(preset); + const pushNewPreset = ervPresetIdExist ? false : newDevices.ervPresets.push(preset); + const presetsCount = newDevices.ervPresets.length; + textErvPresets = `ERV Presets: ${presetsCount}` }; }); - const newDevicesCount = newDevices.ata.length + newDevices.atw.length + newDevices.erv.length; - document.getElementById('info').innerHTML = `Found in MELCloud, ${textAta}, ${textAtw}, ${textErv}. ${newDevicesCount === 0 ? '' : 'Now You can start to configure it.'}`; - document.getElementById('info').style.color = 'green'; + const newDevicesCount = newDevices.ata.length + newDevices.atw.length + newDevices.erv.length ?? 0; + const newPresetsCount = newDevices.ataPresets.length + newDevices.atwPresets.length + newDevices.ervPresets.length ?? 0; + if (newDevicesCount === 0 && newPresetsCount === 0) { + document.getElementById('info').innerHTML = 'No new devices found.'; + document.getElementById('info').style.color = 'white'; + }; + + if (newDevicesCount > 0 && newPresetsCount > 0) { + document.getElementById('info').innerHTML = `Found, ${textAta}, ${textAtw}, ${textErv}.`; + document.getElementById('info').style.color = 'green'; + document.getElementById('info1').innerHTML = `${textAtaPresets}, ${textAtwPresets}, ${textErvPresets}.`; + document.getElementById('info1').style.color = 'green'; + document.getElementById('info2').innerHTML = 'Now You can configure it.'; + document.getElementById('info2').style.color = 'green'; + }; + + if (newDevicesCount > 0 && newPresetsCount === 0) { + document.getElementById('info').innerHTML = `Found, ${textAta}, ${textAtw}, ${textErv}.`; + document.getElementById('info').style.color = 'green'; + document.getElementById('info1').innerHTML = 'Now You can configure it.'; + document.getElementById('info1').style.color = 'green'; + }; + + if (newDevicesCount === 0 && newPresetsCount > 0) { + document.getElementById('info').innerHTML = `Found, ${textAtaPresets}, ${textAtwPresets}, ${textErvPresets}.`; + document.getElementById('info').style.color = 'green'; + document.getElementById('info1').innerHTML = 'Now You can configure it.'; + document.getElementById('info1').style.color = 'green'; + } await homebridge.updatePluginConfig(pluginConfig); await homebridge.savePluginConfig(pluginConfig); diff --git a/package-lock.json b/package-lock.json index e218ef9..1b46031 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "homebridge-melcloud-control", - "version": "3.0.0", + "version": "3.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "homebridge-melcloud-control", - "version": "3.0.0", + "version": "3.1.0", "license": "MIT", "dependencies": { "@homebridge/plugin-ui-utils": "^1.0.3", "async-mqtt": "^2.6.3", - "axios": "^1.7.5", + "axios": "^1.7.6", "express": "^4.19.2" }, "engines": { @@ -60,9 +60,9 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "node_modules/axios": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", - "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.6.tgz", + "integrity": "sha512-Ekur6XDwhnJ5RgOCaxFnXyqlPALI3rVeukZMwOdfghW7/wGz784BYKiQq+QD8NPcr91KRo30KfHOchyijwWw7g==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -1274,9 +1274,9 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "axios": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", - "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.6.tgz", + "integrity": "sha512-Ekur6XDwhnJ5RgOCaxFnXyqlPALI3rVeukZMwOdfghW7/wGz784BYKiQq+QD8NPcr91KRo30KfHOchyijwWw7g==", "requires": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", diff --git a/package.json b/package.json index 34e8d9e..9038487 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "displayName": "MELCloud Control", "name": "homebridge-melcloud-control", - "version": "3.0.8", + "version": "3.1.0", "description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.", "license": "MIT", "author": "grzegorz914", @@ -34,7 +34,7 @@ "dependencies": { "@homebridge/plugin-ui-utils": "^1.0.3", "async-mqtt": "^2.6.3", - "axios": "^1.7.5", + "axios": "^1.7.6", "express": "^4.19.2" }, "keywords": [ diff --git a/src/deviceata.js b/src/deviceata.js index 96449ea..53baa9e 100644 --- a/src/deviceata.js +++ b/src/deviceata.js @@ -24,6 +24,7 @@ class DeviceAta extends EventEmitter { this.heatDryFanMode = device.heatDryFanMode || 1; //NONE, HEAT, DRY, FAN this.coolDryFanMode = device.coolDryFanMode || 1; //NONE, COOL, DRY, FAN this.autoDryFanMode = device.autoDryFanMode || 1; //NONE, AUTO, DRY, FAN + this.presets = device.presets || []; this.buttons = device.buttonsSensors || []; this.disableLogInfo = account.disableLogInfo || false; this.disableLogDeviceInfo = account.disableLogDeviceInfo || false; @@ -38,6 +39,26 @@ class DeviceAta extends EventEmitter { //function this.melCloud = melCloud; //function + //presets configured + this.presetsConfigured = []; + for (const preset of this.presets) { + const presetName = preset.name ?? false; + const presetDisplayType = preset.displayType ?? 0; + const presetNamePrefix = preset.namePrefix ?? false; + if (presetName && presetDisplayType > 0) { + const buttonServiceType = ['', Service.Outlet, Service.Switch, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][presetDisplayType]; + const buttonCharacteristicType = ['', Characteristic.On, Characteristic.On, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][presetDisplayType]; + preset.namePrefix = presetNamePrefix; + preset.serviceType = buttonServiceType; + preset.characteristicType = buttonCharacteristicType; + preset.state = false; + this.buttonsConfigured.push(preset); + } else { + const log = presetDisplayType === 0 ? false : this.emit('message', `Preset Name: ${preset ? preset : 'Missing'}.`); + }; + } + this.presetsConfiguredCount = this.presetsConfigured.length || 0; + //buttons configured this.buttonsConfigured = []; for (const button of this.buttons) { @@ -183,6 +204,9 @@ class DeviceAta extends EventEmitter { const outdoorTemperature = deviceData.Device.OutdoorTemperature; const temperatureUnit = CONSTANTS.TemperatureDisplayUnits[useFahrenheit]; + //presets + const presetsOnServer = deviceData.Presets ?? []; + //device state const roomTemperature = deviceState.RoomTemperature; const setTemperature = deviceState.SetTemperature; @@ -199,10 +223,8 @@ class DeviceAta extends EventEmitter { const power = deviceState.Power; const offline = deviceState.Offline; - //presets - const presets = this.presetsEnabled ? deviceData.Presets : []; - //accessory + this.accessory.presetsOnServer = presetsOnServer; this.accessory.power = power; this.accessory.offline = offline; this.accessory.roomTemperature = roomTemperature; @@ -223,7 +245,6 @@ class DeviceAta extends EventEmitter { this.accessory.numberOfFanSpeeds = numberOfFanSpeeds; this.accessory.modelSupportsFanSpeed = modelSupportsFanSpeed; this.accessory.modelSupportsDry = modelSupportsDry; - this.accessory.presets = presets; this.accessory.operationModeSetPropsMinValue = 0; this.accessory.operationModeSetPropsMaxValue = 3; this.accessory.operationModeSetPropsValidValues = [0]; @@ -408,6 +429,28 @@ class DeviceAta extends EventEmitter { .updateCharacteristic(Characteristic.CurrentTemperature, outdoorTemperature) }; + //update presets state + if (this.presetsConfigured.length > 0) { + for (let i = 0; i < this.presetsConfigured.length; i++) { + const preset = this.presetsConfigured[i]; + const index = presetsOnServer.findIndex(p => p.ID === preset.Id); + const presetData = presetsOnServer[index]; + + preset.state = presetData ? (presetData.Power === power + && presetData.SetTemperature === setTemperature + && presetData.OperationMode === operationMode + && presetData.VaneHorizontal === vaneHorizontal + && presetData.VaneVertical === vaneVertical + && presetData.FanSpeed === setFanSpeed) : false; + + if (this.presetsServices) { + const characteristicType = preset.characteristicType; + this.presetsServices[i] + .updateCharacteristic(characteristicType, preset.state) + }; + }; + }; + //update buttons state if (this.buttonsConfiguredCount > 0) { for (let i = 0; i < this.buttonsConfiguredCount; i++) { @@ -533,27 +576,6 @@ class DeviceAta extends EventEmitter { }; }; - //update presets state - if (presets.length > 0) { - this.presetsStates = []; - - for (let i = 0; i < presets.length; i++) { - const preset = presets[i]; - const state = preset.Power === power - && preset.SetTemperature === setTemperature - && preset.OperationMode === operationMode - && preset.VaneHorizontal === vaneHorizontal - && preset.VaneVertical === vaneVertical - && preset.FanSpeed === setFanSpeed; - this.presetsStates.push(state); - - if (this.presetsServices) { - this.presetsServices[i] - .updateCharacteristic(Characteristic.On, state) - }; - }; - }; - //log current state if (!this.disableLogInfo) { const operationModeText = !power ? CONSTANTS.AirConditioner.System[0] : CONSTANTS.AirConditioner.DriveMode[operationMode]; @@ -693,12 +715,14 @@ class DeviceAta extends EventEmitter { .setCharacteristic(Characteristic.FirmwareRevision, this.firmwareRevision); //melcloud services + const presetsOnServer = this.accessory.presetsOnServer; const displayMode = this.displayMode; const temperatureSensor = this.temperatureSensor; const temperatureSensorOutdoor = this.temperatureSensorOutdoor; + const presetsConfigured = this.presetsConfigured; + const presetsConfiguredCount = this.presetsConfiguredCount; const buttonsConfigured = this.buttonsConfigured; const buttonsConfiguredCount = this.buttonsConfiguredCount; - const presets = this.accessory.presets; const hasAutomaticFanSpeed = this.accessory.hasAutomaticFanSpeed; const modelSupportsFanSpeed = this.accessory.modelSupportsFanSpeed; const modelSupportsDry = this.accessory.modelSupportsDry; @@ -1057,9 +1081,74 @@ class DeviceAta extends EventEmitter { accessory.addService(this.outdoorTemperatureSensorService); }; + //presets services + if (presetsConfiguredCount > 0) { + const debug = this.enableDebugMode ? this.emit('debug', `Prepare presets services`) : false; + this.presetsServices = []; + const previousPresets = []; + + for (let i = 0; i < presetsConfiguredCount; i++) { + const preset = presetsConfigured[i]; + const index = presetsOnServer.findIndex(p => p.ID === preset.Id); + const presetData = presetsOnServer[index]; + + //get preset name + const presetName = preset.name; + + //get preset display type + const displayType = button.displayType; + + //get preset name prefix + const presetNamePrefix = preset.namePrefix; + + const serviceName = presetNamePrefix ? `${accessoryName} ${presetName}` : presetName; + const serviceType = preset.serviceType; + const characteristicType = preset.characteristicType; + const presetService = new serviceType(serviceName, `Preset ${deviceId} ${i}`); + presetService.addOptionalCharacteristic(Characteristic.ConfiguredName); + presetService.setCharacteristic(Characteristic.ConfiguredName, serviceName); + presetService.getCharacteristic(characteristicType) + .onGet(async () => { + const state = preset.state; + return state; + }) + .onSet(async (state) => { + if (displayType > 2) { + return; + }; + + try { + switch (state) { + case true: + previousPresets[i] = deviceState; + deviceState.SetTemperature = presetData.SetTemperature; + deviceState.Power = presetData.Power; + deviceState.OperationMode = presetData.OperationMode; + deviceState.VaneHorizontal = presetData.VaneHorizontal; + deviceState.VaneVertical = presetData.VaneVertical; + deviceState.SetFanSpeed = presetData.FanSpeed; + deviceState.EffectiveFlags = CONSTANTS.AirConditioner.EffectiveFlags.Power; + break; + case false: + deviceState = previousPresets[i]; + break; + }; + + await this.melCloudAta.send(deviceState); + const info = this.disableLogInfo ? false : this.emit('message', `Set: ${presetName}`); + } catch (error) { + this.emit('warn', `Set preset error: ${error}`); + }; + }); + previousPresets.push(deviceState); + this.presetsServices.push(presetService); + accessory.addService(presetService); + }; + }; + //buttons services if (buttonsConfiguredCount > 0) { - const debug = this.enableDebugMode ? this.emit('debug', `Prepare buttons/sensors service`) : false; + const debug = this.enableDebugMode ? this.emit('debug', `Prepare buttons/sensors services`) : false; this.buttonsServices = []; for (let i = 0; i < buttonsConfiguredCount; i++) { @@ -1077,13 +1166,13 @@ class DeviceAta extends EventEmitter { //get button name prefix const buttonNamePrefix = button.namePrefix; - const buttonServiceName = buttonNamePrefix ? `${accessoryName} ${buttonName}` : buttonName; - const buttonServiceType = button.serviceType; - const buttomCharacteristicType = button.characteristicType; - const buttonService = new buttonServiceType(buttonServiceName, `Button ${deviceId} ${i}`); + const serviceName = buttonNamePrefix ? `${accessoryName} ${buttonName}` : buttonName; + const serviceType = button.serviceType; + const characteristicType = button.characteristicType; + const buttonService = new serviceType(serviceName, `Button ${deviceId} ${i}`); buttonService.addOptionalCharacteristic(Characteristic.ConfiguredName); - buttonService.setCharacteristic(Characteristic.ConfiguredName, buttonServiceName); - buttonService.getCharacteristic(buttomCharacteristicType) + buttonService.setCharacteristic(Characteristic.ConfiguredName, serviceName); + buttonService.getCharacteristic(characteristicType) .onGet(async () => { const state = button.state; return state; @@ -1279,54 +1368,6 @@ class DeviceAta extends EventEmitter { }; }; - //presets services - if (presets.length > 0) { - const debug = this.enableDebugMode ? this.emit('debug', `Prepare presets service`) : false; - this.presetsServices = []; - const previousPresets = []; - - for (let i = 0; i < presets.length; i++) { - const preset = presets[i]; - const presetName = preset.NumberDescription; - - const presetService = new Service.Outlet(`${accessoryName} ${presetName}`, `Preset ${deviceId} ${i}`); - presetService.addOptionalCharacteristic(Characteristic.ConfiguredName); - presetService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} ${presetName}`); - presetService.getCharacteristic(Characteristic.On) - .onGet(async () => { - const state = this.presetsStates[i]; - return state; - }) - .onSet(async (state) => { - try { - switch (state) { - case true: - previousPresets[i] = deviceState; - deviceState.SetTemperature = preset.SetTemperature; - deviceState.Power = preset.Power; - deviceState.OperationMode = preset.OperationMode; - deviceState.VaneHorizontal = preset.VaneHorizontal; - deviceState.VaneVertical = preset.VaneVertical; - deviceState.SetFanSpeed = preset.FanSpeed; - deviceState.EffectiveFlags = CONSTANTS.AirConditioner.EffectiveFlags.Power; - break; - case false: - deviceState = previousPresets[i]; - break; - }; - - await this.melCloudAta.send(deviceState); - const info = this.disableLogInfo ? false : this.emit('message', `Set: ${presetName}`); - } catch (error) { - this.emit('warn', `Set preset error: ${error}`); - }; - }); - previousPresets.push(deviceState); - this.presetsServices.push(presetService); - accessory.addService(presetService); - }; - }; - return accessory; } catch (error) { throw new Error(error.message ?? error); diff --git a/src/deviceatw.js b/src/deviceatw.js index 5304de6..df4b58b 100644 --- a/src/deviceatw.js +++ b/src/deviceatw.js @@ -27,7 +27,7 @@ class DeviceAtw extends EventEmitter { this.temperatureSensorReturnWaterTank = device.temperatureSensorReturnWaterTank || false; this.temperatureSensorFlowZone2 = device.temperatureSensorFlowZone2 || false; this.temperatureSensorReturnZone2 = device.temperatureSensorReturnZone2 || false; - this.presetsEnabled = device.presets || false; + this.presets = device.presets || []; this.buttons = device.buttonsSensors || []; this.disableLogInfo = account.disableLogInfo || false; this.disableLogDeviceInfo = account.disableLogDeviceInfo || false; @@ -42,6 +42,26 @@ class DeviceAtw extends EventEmitter { //function this.melCloud = melCloud; //function + //presets configured + this.presetsConfigured = []; + for (const preset of this.presets) { + const presetName = preset.name ?? false; + const presetDisplayType = preset.displayType ?? 0; + const presetNamePrefix = preset.namePrefix ?? false; + if (presetName && presetDisplayType > 0) { + const buttonServiceType = ['', Service.Outlet, Service.Switch, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][presetDisplayType]; + const buttonCharacteristicType = ['', Characteristic.On, Characteristic.On, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][presetDisplayType]; + preset.namePrefix = presetNamePrefix; + preset.serviceType = buttonServiceType; + preset.characteristicType = buttonCharacteristicType; + preset.state = false; + this.buttonsConfigured.push(preset); + } else { + const log = presetDisplayType === 0 ? false : this.emit('message', `Preset Name: ${preset ? preset : 'Missing'}.`); + }; + } + this.presetsConfiguredCount = this.presetsConfigured.length || 0; + //buttons configured this.buttonsConfigured = []; for (const button of this.buttons) { @@ -194,6 +214,9 @@ class DeviceAtw extends EventEmitter { const returnTemperatureZone2 = deviceData.Device.ReturnTemperatureZone2; const returnTemperatureWaterTank = deviceData.Device.ReturnTemperatureBoiler; + //presets + const presetsOnServer = deviceData.Presets ?? []; + //zones const hotWater = hasHotWaterTank ? 1 : 0; const zone2 = hasZone2 ? 1 : 0; @@ -233,10 +256,8 @@ class DeviceAtw extends EventEmitter { const power = deviceState.Power; const offline = deviceState.Offline; - //presets - const presets = this.presetsEnabled ? deviceData.Presets : []; - //accessory + this.accessory.presetsOnServer = presetsOnServer; this.accessory.power = power; this.accessory.offline = offline; this.accessory.unitStatus = unitStatus; @@ -247,7 +268,6 @@ class DeviceAtw extends EventEmitter { this.accessory.temperatureIncrement = temperatureIncrement; this.accessory.hasHotWaterTank = hasHotWaterTank; this.accessory.hasZone2 = hasZone2; - this.accessory.presets = presets; //default values let name = 'Heat Pump' @@ -554,6 +574,34 @@ class DeviceAtw extends EventEmitter { }; }; + //update presets state + if (this.presetsConfigured.length > 0) { + for (let i = 0; i < this.presetsConfigured.length; i++) { + const preset = this.presetsConfigured[i]; + const index = presetsOnServer.findIndex(p => p.ID === preset.Id); + const presetData = presetsOnServer[index]; + + preset.state = presetData ? (presetData.Power === power + && presetData.EcoHotWater === ecoHotWater + && presetData.OperationModeZone1 === operationModeZone1 + && presetData.OperationModeZone2 === operationModeZone2 + && presetData.SetTankWaterTemperature === setTankWaterTemperature + && presetData.SetTemperatureZone1 === setTemperatureZone1 + && presetData.SetTemperatureZone2 === setTemperatureZone2 + && presetData.ForcedHotWaterMode === forcedHotWaterMode + && presetData.SetHeatFlowTemperatureZone1 === setHeatFlowTemperatureZone1 + && presetData.SetHeatFlowTemperatureZone2 === setHeatFlowTemperatureZone2 + && presetData.SetCoolFlowTemperatureZone1 === setCoolFlowTemperatureZone1 + && presetData.SetCoolFlowTemperatureZone2 === setCoolFlowTemperatureZone2) : false; + + if (this.presetsServices) { + const characteristicType = preset.characteristicType; + this.presetsServices[i] + .updateCharacteristic(characteristicType, preset.state) + }; + }; + }; + //update buttons state if (this.buttonsConfiguredCount > 0) { for (let i = 0; i < this.buttonsConfiguredCount; i++) { @@ -643,33 +691,6 @@ class DeviceAtw extends EventEmitter { }; }; - //update presets state - if (presets.length > 0) { - this.presetsStates = []; - - for (let i = 0; i < presets.length; i++) { - const preset = presets[i]; - const state = preset.Power === power - && preset.EcoHotWater === ecoHotWater - && preset.OperationModeZone1 === operationModeZone1 - && preset.OperationModeZone2 === operationModeZone2 - && preset.SetTankWaterTemperature === setTankWaterTemperature - && preset.SetTemperatureZone1 === setTemperatureZone1 - && preset.SetTemperatureZone2 === setTemperatureZone2 - && preset.ForcedHotWaterMode === forcedHotWaterMode - && preset.SetHeatFlowTemperatureZone1 === setHeatFlowTemperatureZone1 - && preset.SetHeatFlowTemperatureZone2 === setHeatFlowTemperatureZone2 - && preset.SetCoolFlowTemperatureZone1 === setCoolFlowTemperatureZone1 - && preset.SetCoolFlowTemperatureZone2 === setCoolFlowTemperatureZone2; - this.presetsStates.push(state); - - if (this.presetsServices) { - this.presetsServices[i] - .updateCharacteristic(Characteristic.On, state) - }; - }; - }; - //start prepare accessory if (this.startPrepareAccessory) { try { @@ -813,6 +834,7 @@ class DeviceAtw extends EventEmitter { //melcloud services const zonesCount = this.zonesCount; + const presetsOnServer = this.accessory.presetsOnServer; const temperatureSensor = this.temperatureSensor; const temperatureSensorFlow = this.temperatureSensorFlow; const temperatureSensorReturn = this.temperatureSensorReturn; @@ -822,9 +844,10 @@ class DeviceAtw extends EventEmitter { const temperatureSensorReturnWaterTank = this.temperatureSensorReturnWaterTank; const temperatureSensorFlowZone2 = this.temperatureSensorFlowZone2; const temperatureSensorReturnZone2 = this.temperatureSensorReturnZone2; + const presetsConfigured = this.presetsConfigured; + const presetsConfiguredCount = this.presetsConfiguredCount; const buttonsConfigured = this.buttonsConfigured; const buttonsConfiguredCount = this.buttonsConfiguredCount; - const presets = this.accessory.presets; const displayMode = this.displayMode; const caseHotWater = this.caseHotWater; const caseZone2 = this.caseZone2; @@ -1506,9 +1529,80 @@ class DeviceAtw extends EventEmitter { }; }; + //presets services + if (presetsConfiguredCount > 0) { + const debug = this.enableDebugMode ? this.emit('debug', `Prepare presets services`) : false; + this.presetsServices = []; + const previousPresets = []; + + for (let i = 0; i < presetsConfiguredCount; i++) { + const preset = presetsConfigured[i]; + const index = presetsOnServer.findIndex(p => p.ID === preset.Id); + const presetData = presetsOnServer[index]; + + //get preset name + const presetName = preset.name; + + //get preset display type + const displayType = button.displayType; + + //get preset name prefix + const presetNamePrefix = preset.namePrefix; + + const serviceName = presetNamePrefix ? `${accessoryName} ${presetName}` : presetName; + const serviceType = preset.serviceType; + const characteristicType = preset.characteristicType; + const presetService = new serviceType(serviceName, `Preset ${deviceId} ${i}`); + presetService.addOptionalCharacteristic(Characteristic.ConfiguredName); + presetService.setCharacteristic(Characteristic.ConfiguredName, serviceName); + presetService.getCharacteristic(characteristicType) + .onGet(async () => { + const state = this.presetsStates[i]; + return state; + }) + .onSet(async (state) => { + if (displayType > 2) { + return; + }; + + try { + switch (state) { + case true: + previousPresets[i] = deviceState; + deviceState.Power = presetData.Power; + deviceState.EcoHotWater = presetData.EcoHotWater; + deviceState.OperationModeZone1 = presetData.OperationModeZone1; + deviceState.OperationModeZone2 = presetData.OperationModeZone2; + deviceState.SetTankWaterTemperature = presetData.SetTankWaterTemperature; + deviceState.SetTemperatureZone1 = presetData.SetTemperatureZone1; + deviceState.SetTemperatureZone2 = presetData.SetTemperatureZone2; + deviceState.ForcedHotWaterMode = presetData.ForcedHotWaterMode; + deviceState.SetHeatFlowTemperatureZone1 = presetData.SetHeatFlowTemperatureZone1; + deviceState.SetHeatFlowTemperatureZone2 = presetData.SetHeatFlowTemperatureZone2; + deviceState.SetCoolFlowTemperatureZone1 = presetData.SetCoolFlowTemperatureZone1; + deviceState.SetCoolFlowTemperatureZone2 = presetData.SetCoolFlowTemperatureZone2; + deviceState.EffectiveFlags = CONSTANTS.HeatPump.EffectiveFlags.Power; + break; + case false: + deviceState = previousPresets[i]; + break; + }; + + await this.melCloudAtw.send(deviceState); + const info = this.disableLogInfo ? false : this.emit('message', `Set: ${presetName}`); + } catch (error) { + this.emit('warn', `Set preset error: ${error}`); + }; + }); + previousPresets.push(deviceState); + this.presetsServices.push(presetService); + accessory.addService(presetService); + }; + }; + //buttons services if (buttonsConfiguredCount > 0) { - const debug = this.enableDebugMode ? this.emit('debug', `Prepare buttons service`) : false; + const debug = this.enableDebugMode ? this.emit('debug', `Prepare buttons services`) : false; this.buttonsServices = []; for (let i = 0; i < buttonsConfiguredCount; i++) { @@ -1526,15 +1620,13 @@ class DeviceAtw extends EventEmitter { //get button name prefix const buttonNamePrefix = button.namePrefix; - const buttonServiceName = buttonNamePrefix ? `${accessoryName} ${buttonName}` : buttonName; - const buttonServiceType = button.serviceType; - const buttomCharacteristicType = button.characteristicType; - const buttonService = new buttonServiceType(buttonServiceName, `Button ${deviceId} ${i}`); - buttonService.addOptionalCharacteristic(Characteristic.ConfiguredName); - buttonService.setCharacteristic(Characteristic.ConfiguredName, buttonServiceName); + const serviceName = buttonNamePrefix ? `${accessoryName} ${buttonName}` : buttonName; + const serviceType = button.serviceType; + const characteristicType = button.characteristicType; + const buttonService = new serviceType(serviceName, `Button ${deviceId} ${i}`); buttonService.addOptionalCharacteristic(Characteristic.ConfiguredName); - buttonService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} ${buttonName}`); - buttonService.getCharacteristic(buttomCharacteristicType) + buttonService.setCharacteristic(Characteristic.ConfiguredName, serviceName); + buttonService.getCharacteristic(characteristicType) .onGet(async () => { const state = button.state; return state; @@ -1668,60 +1760,6 @@ class DeviceAtw extends EventEmitter { }; }; - //presets services - if (presets.length > 0) { - const debug = this.enableDebugMode ? this.emit('debug', `Prepare presets service`) : false; - this.presetsServices = []; - const previousPresets = []; - - for (let i = 0; i < presets.length; i++) { - const preset = presets[i]; - const presetName = preset.NumberDescription; - - const presetService = new Service.Outlet(`${accessoryName} ${presetName}`, `Preset ${deviceId} ${i}`); - presetService.addOptionalCharacteristic(Characteristic.ConfiguredName); - presetService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} ${presetName}`); - presetService.getCharacteristic(Characteristic.On) - .onGet(async () => { - const state = this.presetsStates[i]; - return state; - }) - .onSet(async (state) => { - try { - switch (state) { - case true: - previousPresets[i] = deviceState; - deviceState.Power = preset.Power; - deviceState.EcoHotWater = preset.EcoHotWater; - deviceState.OperationModeZone1 = preset.OperationModeZone1; - deviceState.OperationModeZone2 = preset.OperationModeZone2; - deviceState.SetTankWaterTemperature = preset.SetTankWaterTemperature; - deviceState.SetTemperatureZone1 = preset.SetTemperatureZone1; - deviceState.SetTemperatureZone2 = preset.SetTemperatureZone2; - deviceState.ForcedHotWaterMode = preset.ForcedHotWaterMode; - deviceState.SetHeatFlowTemperatureZone1 = preset.SetHeatFlowTemperatureZone1; - deviceState.SetHeatFlowTemperatureZone2 = preset.SetHeatFlowTemperatureZone2; - deviceState.SetCoolFlowTemperatureZone1 = preset.SetCoolFlowTemperatureZone1; - deviceState.SetCoolFlowTemperatureZone2 = preset.SetCoolFlowTemperatureZone2; - deviceState.EffectiveFlags = CONSTANTS.HeatPump.EffectiveFlags.Power; - break; - case false: - deviceState = previousPresets[i]; - break; - }; - - await this.melCloudAtw.send(deviceState); - const info = this.disableLogInfo ? false : this.emit('message', `Set: ${presetName}`); - } catch (error) { - this.emit('warn', `Set preset error: ${error}`); - }; - }); - previousPresets.push(deviceState); - this.presetsServices.push(presetService); - accessory.addService(presetService); - }; - }; - return accessory; } catch (error) { throw new Error(error.message ?? error); diff --git a/src/deviceerv.js b/src/deviceerv.js index 8290773..15ca979 100644 --- a/src/deviceerv.js +++ b/src/deviceerv.js @@ -21,7 +21,7 @@ class DeviceErv extends EventEmitter { this.temperatureSensor = device.temperatureSensor || false; this.temperatureSensorOutdoor = device.temperatureSensorOutdoor || false; this.temperatureSensorSupply = device.temperatureSensorSupply || false; - this.presetsEnabled = device.presets || false; + this.presets = device.presets || []; this.buttons = device.buttonsSensors || []; this.disableLogInfo = account.disableLogInfo || false; this.disableLogDeviceInfo = account.disableLogDeviceInfo || false; @@ -36,6 +36,26 @@ class DeviceErv extends EventEmitter { //function this.melCloud = melCloud; //function + //presets configured + this.presetsConfigured = []; + for (const preset of this.presets) { + const presetName = preset.name ?? false; + const presetDisplayType = preset.displayType ?? 0; + const presetNamePrefix = preset.namePrefix ?? false; + if (presetName && presetDisplayType > 0) { + const buttonServiceType = ['', Service.Outlet, Service.Switch, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][presetDisplayType]; + const buttonCharacteristicType = ['', Characteristic.On, Characteristic.On, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][presetDisplayType]; + preset.namePrefix = presetNamePrefix; + preset.serviceType = buttonServiceType; + preset.characteristicType = buttonCharacteristicType; + preset.state = false; + this.buttonsConfigured.push(preset); + } else { + const log = presetDisplayType === 0 ? false : this.emit('message', `Preset Name: ${preset ? preset : 'Missing'}.`); + }; + } + this.presetsConfiguredCount = this.presetsConfigured.length || 0; + //buttons configured this.buttonsConfigured = []; for (const button of this.buttons) { @@ -191,6 +211,9 @@ class DeviceErv extends EventEmitter { const numberOfFanSpeeds = deviceData.Device.NumberOfFanSpeeds ?? 0; const temperatureIncrement = deviceData.Device.TemperatureIncrement ?? 1; + //presets + const presetsOnServer = deviceData.Presets ?? []; + //device state const roomTemperature = deviceState.RoomTemperature; const supplyTemperature = deviceState.SupplyTemperature; @@ -207,13 +230,11 @@ class DeviceErv extends EventEmitter { const offline = deviceState.Offline; const temperatureUnit = CONSTANTS.TemperatureDisplayUnits[useFahrenheit]; - //presets - const presets = this.presetsEnabled ? deviceData.Presets : []; - //set temperature const targetTemperature = hasCoolOperationMode || hasHeatOperationMode ? setTemperature : 20; //accessory + this.accessory.presetsOnServer = presetsOnServer; this.accessory.power = power; this.accessory.offline = offline; this.accessory.roomTemperature = roomTemperature; @@ -245,7 +266,6 @@ class DeviceErv extends EventEmitter { this.accessory.coreMaintenanceRequired = coreMaintenanceRequired; this.accessory.filterMaintenanceRequired = filterMaintenanceRequired; this.accessory.actualVentilationMode = actualVentilationMode; - this.accessory.accessory.presets = presets; this.accessory.operationModeSetPropsMinValue = 0; this.accessory.operationModeSetPropsMaxValue = 3; this.accessory.operationModeSetPropsValidValues = [0]; @@ -418,6 +438,27 @@ class DeviceErv extends EventEmitter { .updateCharacteristic(Characteristic.PM2_5Density, pM25Level) } + //update presets state + if (this.presetsConfigured.length > 0) { + for (let i = 0; i < this.presetsConfigured.length; i++) { + const preset = this.presetsConfigured[i]; + const index = presetsOnServer.findIndex(p => p.ID === preset.Id); + const presetData = presetsOnServer[index]; + + preset.state = presetData ? (presetData.Power === power + && presetData.SetTemperature === targetTemperature + && presetData.OperationMode === operationMode + && presetData.VentilationMode === ventilationMode + && presetData.FanSpeed === setFanSpeed) : false; + + if (this.presetsServices) { + const characteristicType = preset.characteristicType; + this.presetsServices[i] + .updateCharacteristic(characteristicType, preset.state) + }; + }; + }; + //update buttons state if (this.buttonsConfiguredCount > 0) { for (let i = 0; i < this.buttonsConfiguredCount; i++) { @@ -480,26 +521,6 @@ class DeviceErv extends EventEmitter { }; }; - //update presets state - if (presets.length > 0) { - this.presetsStates = []; - - for (let i = 0; i < presets.length; i++) { - const preset = presets[i]; - const state = preset.Power === power - && preset.SetTemperature === targetTemperature - && preset.OperationMode === operationMode - && preset.VentilationMode === ventilationMode - && preset.FanSpeed === setFanSpeed; - this.presetsStates.push(state); - - if (this.presetsServices) { - this.presetsServices[i] - .updateCharacteristic(Characteristic.On, state) - }; - }; - }; - //log current state if (!this.disableLogInfo) { const operationModeText = !power ? CONSTANTS.Ventilation.System[0] : CONSTANTS.Ventilation.OperationMode[ventilationMode]; @@ -634,13 +655,15 @@ class DeviceErv extends EventEmitter { .setCharacteristic(Characteristic.FirmwareRevision, this.firmwareRevision); //melcloud services + const presetsOnServer = this.accessory.presetsOnServer; const displayMode = this.displayMode; const temperatureSensor = this.temperatureSensor; const temperatureSensorOutdoor = this.temperatureSensorOutdoor; const temperatureSensorSupply = this.temperatureSensorSupply; + const presetsConfigured = this.presetsConfigured; + const presetsConfiguredCount = this.presetsConfiguredCount; const buttonsConfigured = this.buttonsConfigured; const buttonsConfiguredCount = this.buttonsConfiguredCount; - const presets = this.accessory.presets; const hasCoolOperationMode = this.accessory.hasCoolOperationMode; const hasHeatOperationMode = this.accessory.hasHeatOperationMode; const hasCO2Sensor = this.accessory.hasCO2Sensor; @@ -1055,9 +1078,73 @@ class DeviceErv extends EventEmitter { accessory.addService(this.airQualitySensorService); } + //presets services + if (presetsConfiguredCount > 0) { + const debug = this.enableDebugMode ? this.emit('debug', `Prepare presets services`) : false; + this.presetsServices = []; + const previousPresets = []; + + for (let i = 0; i < presetsConfiguredCount; i++) { + const preset = presetsConfigured[i]; + const index = presetsOnServer.findIndex(p => p.ID === preset.Id); + const presetData = presetsOnServer[index] + + //get preset name + const presetName = preset.name; + + //get preset display type + const displayType = button.displayType; + + //get preset name prefix + const presetNamePrefix = preset.namePrefix; + + const serviceName = presetNamePrefix ? `${accessoryName} ${presetName}` : presetName; + const serviceType = preset.serviceType; + const characteristicType = preset.characteristicType; + const presetService = new serviceType(serviceName, `Preset ${deviceId} ${i}`); + presetService.addOptionalCharacteristic(Characteristic.ConfiguredName); + presetService.setCharacteristic(Characteristic.ConfiguredName, serviceName); + presetService.getCharacteristic(characteristicType) + .onGet(async () => { + const state = this.presetsStates[i]; + return state; + }) + .onSet(async (state) => { + if (displayType > 2) { + return; + }; + + try { + switch (state) { + case true: + previousPresets[i] = deviceState; + deviceState.SetTemperature = presetData.SetTemperature; + deviceState.Power = presetData.Power; + deviceState.OperationMode = presetData.OperationMode; + deviceState.VentilationMode = presetData.VentilationMode; + deviceState.SetFanSpeed = presetData.FanSpeed; + deviceState.EffectiveFlags = CONSTANTS.Ventilation.EffectiveFlags.Power; + break; + case false: + deviceState = previousPresets[i]; + break; + }; + + await this.melCloudErv.send(deviceState); + const info = this.disableLogInfo ? false : this.emit('message', `Set: ${presetName}`); + } catch (error) { + this.emit('warn', `Set preset error: ${error}`); + }; + }); + previousPresets.push(deviceState); + this.presetsServices.push(presetService); + accessory.addService(presetService); + }; + }; + //buttons services if (buttonsConfiguredCount > 0) { - const debug = this.enableDebugMode ? this.emit('debug', `Prepare buttons service`) : false; + const debug = this.enableDebugMode ? this.emit('debug', `Prepare buttons services`) : false; this.buttonsServices = []; for (let i = 0; i < buttonsConfiguredCount; i++) { @@ -1075,13 +1162,13 @@ class DeviceErv extends EventEmitter { //get button name prefix const buttonNamePrefix = button.namePrefix; - const buttonServiceName = buttonNamePrefix ? `${accessoryName} ${buttonName}` : buttonName; - const buttonServiceType = button.serviceType; - const buttomCharacteristicType = button.characteristicType; - const buttonService = new buttonServiceType(buttonServiceName, `Button ${deviceId} ${i}`); + const serviceName = buttonNamePrefix ? `${accessoryName} ${buttonName}` : buttonName; + const serviceType = button.serviceType; + const characteristicType = button.characteristicType; + const buttonService = new serviceType(serviceName, `Button ${deviceId} ${i}`); buttonService.addOptionalCharacteristic(Characteristic.ConfiguredName); - buttonService.setCharacteristic(Characteristic.ConfiguredName, buttonServiceName); - buttonService.getCharacteristic(buttomCharacteristicType) + buttonService.setCharacteristic(Characteristic.ConfiguredName, serviceName); + buttonService.getCharacteristic(characteristicType) .onGet(async () => { const state = button.state; return state; @@ -1171,53 +1258,6 @@ class DeviceErv extends EventEmitter { }; }; - //presets services - if (presets.length > 0) { - const debug = this.enableDebugMode ? this.emit('debug', `Prepare presets service`) : false; - this.presetsServices = []; - const previousPresets = []; - - for (let i = 0; i < presets.length; i++) { - const preset = presets[i]; - const presetName = preset.NumberDescription; - - const presetService = new Service.Outlet(`${accessoryName} ${presetName}`, `Preset ${deviceId} ${i}`); - presetService.addOptionalCharacteristic(Characteristic.ConfiguredName); - presetService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} ${presetName}`); - presetService.getCharacteristic(Characteristic.On) - .onGet(async () => { - const state = this.presetsStates[i]; - return state; - }) - .onSet(async (state) => { - try { - switch (state) { - case true: - previousPresets[i] = deviceState; - deviceState.SetTemperature = preset.SetTemperature; - deviceState.Power = preset.Power; - deviceState.OperationMode = preset.OperationMode; - deviceState.VentilationMode = preset.VentilationMode; - deviceState.SetFanSpeed = preset.FanSpeed; - deviceState.EffectiveFlags = CONSTANTS.Ventilation.EffectiveFlags.Power; - break; - case false: - deviceState = previousPresets[i]; - break; - }; - - await this.melCloudErv.send(deviceState); - const info = this.disableLogInfo ? false : this.emit('message', `Set: ${presetName}`); - } catch (error) { - this.emit('warn', `Set preset error: ${error}`); - }; - }); - previousPresets.push(deviceState); - this.presetsServices.push(presetService); - accessory.addService(presetService); - }; - }; - return accessory; } catch (error) { throw new Error(error.message ?? error);