From 8a4d55247b1d00f00f215d4ba259eb041db61f94 Mon Sep 17 00:00:00 2001 From: Seyd <34152761+SeydX@users.noreply.github.com> Date: Mon, 22 Mar 2021 08:07:38 +0100 Subject: [PATCH] Bugfix & Improvements - Added "Dummy Switch" option to Central Switch - Added new accessory type option (LightSensor) to Solar Intensity Accessory - Added new mode (CUSTOM) to HOT_WATER devices - Fixed current state of HeaterCooler if temperature is reached - Fixed AUTO mode for HOT_WATER devices - Bugfixes --- config.schema.json | 46 +++++++++- homebridge-ui/public/js/main.js | 12 ++- homebridge-ui/public/js/schema.js | 46 +++++++++- src/accessories/lightbulb.js | 10 ++- src/accessories/lightsensor.js | 42 +++++++++ src/helper/handler.js | 141 +++++++++++++++++------------- src/platform.js | 8 +- src/tado/tado-config.js | 18 +++- 8 files changed, 241 insertions(+), 82 deletions(-) create mode 100644 src/accessories/lightsensor.js diff --git a/config.schema.json b/config.schema.json index d6ec4d1..788bf44 100644 --- a/config.schema.json +++ b/config.schema.json @@ -177,6 +177,28 @@ "type": "boolean", "description": "If enabled, a lightbulb accessory for the solar intensity will be exposed to HomeKit." }, + "accTypeSolarIntensity": { + "title": "Solar Intensity Accessory Type", + "type": "string", + "condition": { + "functionBody": "try { return model.homes[arrayIndices[0]].weather.solarIntensity } catch(e){ return false }" + }, + "oneOf": [ + { + "title": "Lightbulb", + "enum": [ + "LIGHTBULB" + ] + }, + { + "title": "Sensor", + "enum": [ + "SENSOR" + ] + } + ], + "description": "The accessory type of the device." + }, "airQuality": { "title": "Air Quality", "type": "boolean", @@ -249,6 +271,14 @@ }, "description": "If enabled, a turn off heat switch will be added to the central switch." }, + "dummySwitch": { + "title": "Dummy Switch", + "type": "boolean", + "condition": { + "functionBody": "try { return model.homes[arrayIndices[0]].extras.centralSwitch } catch(e){ return false }" + }, + "description": "If enabled, a dummy switch (for eg automation purposes) will be added to the central switch." + }, "childLockSwitches": { "title": "Child Lock Switches", "type": "array", @@ -382,11 +412,17 @@ "title": "Timer", "enum": [ "TIMER" + ] + }, + { + "title": "Custom", + "enum": [ + "CUSTOM" ] } - ], - "description": "Mode for the commands to be sent with. can be 'MANUAL' for manual control until ended by the user, 'AUTO' for manual control until next schedule change in tado° app OR 'TIMER' for manual control until timer ends." - }, + ], + "description": "Mode for the commands to be sent with. can be 'MANUAL' for manual control until ended by the user, 'AUTO' for manual control until next schedule change in tado° app, 'TIMER' for manual control until timer ends or 'CUSTOM' for a mixed use of MANUAL (off) and AUTO (on). (CUSTOM is only for HEATING types with easyMode enabled and HOT_WATER types with temperature support)." + }, "modeTimer": { "title": "Timer", "description": "Timer for the manual mode in minutes.", @@ -714,6 +750,7 @@ "items": [ "homes[].weather.temperatureSensor", "homes[].weather.solarIntensity", + "homes[].weather.accTypeSolarIntensity", "homes[].weather.airQuality", { "title": "Location", @@ -747,7 +784,8 @@ "homes[].extras.runningInformation", "homes[].extras.boostSwitch", "homes[].extras.sheduleSwitch", - "homes[].extras.turnoffSwitch" + "homes[].extras.turnoffSwitch", + "homes[].extras.dummySwitch" ] }, { diff --git a/homebridge-ui/public/js/main.js b/homebridge-ui/public/js/main.js index f1a0bb8..ab7f035 100644 --- a/homebridge-ui/public/js/main.js +++ b/homebridge-ui/public/js/main.js @@ -127,6 +127,8 @@ function goBack(index) { async function createCustomSchema(home){ + //schema.layout.homes.forEach() + customSchemaActive = homebridge.createForm(schema, { name: pluginConfig[0].name, debug: pluginConfig[0].debug, @@ -137,11 +139,11 @@ async function createCustomSchema(home){ pluginConfig[0].name = config.name; pluginConfig[0].debug = config.debug; - pluginConfig[0].homes = pluginConfig[0].homes.map(home => { - if(home.name === config.homes.name){ - home = config.homes; + pluginConfig[0].homes = pluginConfig[0].homes.map(myHome => { + if(myHome.name === config.homes.name){ + myHome = config.homes; } - return home; + return myHome; }); try { @@ -995,6 +997,7 @@ async function fetchDevices(credentials, refresh, resync){ weather: { temperatureSensor: false, solarIntensity: false, + accTypeSolarIntensity: 'LIGHTBULB', airQuality: false }, extras: { @@ -1129,6 +1132,7 @@ async function fetchDevices(credentials, refresh, resync){ weather: { temperatureSensor: false, solarIntensity: false, + accTypeSolarIntensity: 'LIGHTBULB', airQuality: false }, extras: { diff --git a/homebridge-ui/public/js/schema.js b/homebridge-ui/public/js/schema.js index 391495c..0d05957 100644 --- a/homebridge-ui/public/js/schema.js +++ b/homebridge-ui/public/js/schema.js @@ -172,6 +172,28 @@ const schema = { 'type': 'boolean', 'description': 'If enabled, a lightbulb accessory for the solar intensity will be exposed to HomeKit.' }, + 'accTypeSolarIntensity': { + 'title': 'Solar Intensity Accessory Type', + 'type': 'string', + 'condition': { + 'functionBody': 'try { return model.homes.weather.solarIntensity } catch(e){ return false }' + }, + 'oneOf': [ + { + 'title': 'Lightbulb', + 'enum': [ + 'LIGHTBULB' + ] + }, + { + 'title': 'Sensor', + 'enum': [ + 'SENSOR' + ] + } + ], + 'description': 'The accessory type of the device.' + }, 'airQuality': { 'title': 'Air Quality', 'type': 'boolean', @@ -244,6 +266,14 @@ const schema = { }, 'description': 'If enabled, a turn off heat switch will be added to the central switch.' }, + 'dummySwitch': { + 'title': 'Dummy Switch', + 'type': 'boolean', + 'condition': { + 'functionBody': 'try { return model.homes.extras.centralSwitch } catch(e){ return false }' + }, + 'description': 'If enabled, a dummy switch (for eg automation purposes) will be added to the central switch.' + }, 'childLockSwitches': { 'title': 'Child Lock Switches', 'type': 'array', @@ -355,7 +385,7 @@ const schema = { 'title': 'Maximum Temperature', 'type': 'integer', 'description': 'Maximum adjustable temperature value (in celsius/fahrenheit). HEATING devices also this plugin, supports a maxValue of 25° Celsius / 77° Fahrenheit by default. HOT WATER devices, also this plugin, supports a maxValue of of 65° Celsius / 149° Fahrenheit by default. If your device has a different maxValue, you can set it up here. (Incorrect maxValue may cause problems!)' - }, + }, 'mode': { 'title': 'Termination Mode', 'type': 'string', @@ -378,10 +408,16 @@ const schema = { 'enum': [ 'TIMER' ] + }, + { + 'title': 'Custom', + 'enum': [ + 'CUSTOM' + ] } ], - 'description': 'Mode for the commands to be sent with. can be \'MANUAL\' for manual control until ended by the user, \'AUTO\' for manual control until next schedule change in tado° app OR \'TIMER\' for manual control until timer ends.' - }, + 'description': 'Mode for the commands to be sent with. can be \'MANUAL\' for manual control until ended by the user, \'AUTO\' for manual control until next schedule change in tado° app, \'TIMER\' for manual control until timer ends or \'CUSTOM\' for a mixed use of MANUAL (off) and AUTO (on). (CUSTOM is only for HEATING types with easyMode enabled and HOT_WATER types with temperature support).' + }, 'modeTimer': { 'title': 'Timer', 'description': 'Timer for the manual mode in minutes.', @@ -702,6 +738,7 @@ const schema = { 'items': [ 'homes.weather.temperatureSensor', 'homes.weather.solarIntensity', + 'homes.weather.accTypeSolarIntensity', 'homes.weather.airQuality', { 'title': 'Location', @@ -735,7 +772,8 @@ const schema = { 'homes.extras.runningInformation', 'homes.extras.boostSwitch', 'homes.extras.sheduleSwitch', - 'homes.extras.turnoffSwitch' + 'homes.extras.turnoffSwitch', + 'homes.extras.dummySwitch' ] }, { diff --git a/src/accessories/lightbulb.js b/src/accessories/lightbulb.js index 068b6d8..275507e 100644 --- a/src/accessories/lightbulb.js +++ b/src/accessories/lightbulb.js @@ -2,7 +2,7 @@ const Logger = require('../helper/logger.js'); -class SolarlightAccessory { +class SolarLightbulbAccessory { constructor (api, accessory, accessories, tado) { @@ -23,6 +23,12 @@ class SolarlightAccessory { getService () { let service = this.accessory.getService(this.api.hap.Service.Lightbulb); + let serviceOld = this.accessory.getService(this.api.hap.Service.LightSensor); + + if(serviceOld){ + Logger.info('Removing LightSensor service', this.accessory.displayName); + this.accessory.removeService(serviceOld); + } if(!service){ Logger.info('Adding Lightbulb service', this.accessory.displayName); @@ -52,4 +58,4 @@ class SolarlightAccessory { } -module.exports = SolarlightAccessory; \ No newline at end of file +module.exports = SolarLightbulbAccessory; \ No newline at end of file diff --git a/src/accessories/lightsensor.js b/src/accessories/lightsensor.js new file mode 100644 index 0000000..3a2f056 --- /dev/null +++ b/src/accessories/lightsensor.js @@ -0,0 +1,42 @@ +'use strict'; + +const Logger = require('../helper/logger.js'); + +class SolarLightsensorAccessory { + + constructor (api, accessory, accessories, tado) { + + this.api = api; + this.accessory = accessory; + this.accessories = accessories; + + this.tado = tado; + + this.getService(); + + } + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// + // Services + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// + + getService () { + + let service = this.accessory.getService(this.api.hap.Service.LightSensor); + let serviceOld = this.accessory.getService(this.api.hap.Service.Lightbulb); + + if(serviceOld){ + Logger.info('Removing Lightbulb service', this.accessory.displayName); + this.accessory.removeService(serviceOld); + } + + if(!service){ + Logger.info('Adding LightSensor service', this.accessory.displayName); + service = this.accessory.addService(this.api.hap.Service.LightSensor, this.accessory.displayName, this.accessory.context.config.subtype); + } + + } + +} + +module.exports = SolarLightsensorAccessory; \ No newline at end of file diff --git a/src/helper/handler.js b/src/helper/handler.js index 37f4d5c..26befc9 100644 --- a/src/helper/handler.js +++ b/src/helper/handler.js @@ -129,12 +129,12 @@ module.exports = (api, accessories, config, tado, telegram) => { .getCharacteristic(targetTempCharacteristic) .value.toFixed(2)); - if(clear || (value && accessory.context.config.mode === 'AUTO' && accessory.context.config.subtype.includes('heatercooler'))){ + if(clear || (value && accessory.context.config.mode === 'CUSTOM' && accessory.context.config.subtype.includes('heatercooler'))){ await tado.clearZoneOverlay(config.homeId, accessory.context.config.zoneId); return; } - mode = !value && accessory.context.config.mode === 'AUTO' && accessory.context.config.subtype.includes('heatercooler') + mode = !value && accessory.context.config.mode === 'CUSTOM' && accessory.context.config.subtype.includes('heatercooler') ? 'MANUAL' : mode; @@ -258,11 +258,11 @@ module.exports = (api, accessories, config, tado, telegram) => { case 'zone-window-switch': { - let id = target.split('-'); - id = id[id.length-1]; - - await tado.setWindowDetection(config.homeId, id, value, 3600); - await tado.setOpenWindowMode(config.homeId, id, value); + let zoneId = target.split('-'); + zoneId = zoneId[zoneId.length-1]; + + await tado.setWindowDetection(config.homeId, zoneId, value, 3600); + await tado.setOpenWindowMode(config.homeId, zoneId, value); break; @@ -277,6 +277,9 @@ module.exports = (api, accessories, config, tado, telegram) => { } case 'extra-cntrlswitch': { + + if(target === 'Dummy') + return; const heatAccessories = accessories.filter(acc => acc && acc.context.config.type === 'HEATING'); @@ -840,7 +843,7 @@ module.exports = (api, accessories, config, tado, telegram) => { const zoneState = await tado.getZoneState(config.homeId, zone.id); - let currentState, targetState, currentTemp, targetTemp, humidity, active, battery; + let currentState, targetState, currentTemp, targetTemp, humidity, active, battery, tempEqual; if(zoneState.setting.type === 'HEATING'){ @@ -863,25 +866,29 @@ module.exports = (api, accessories, config, tado, telegram) => { targetTemp = config.temperatureUnit === 'CELSIUS' ? zoneState.setting.temperature.celsius : zoneState.setting.temperature.fahrenheit; - + + tempEqual = Math.round(currentTemp) === Math.round(targetTemp); + currentState = currentTemp <= targetTemp ? 1 : 2; - + targetState = 1; - + active = 1; - } + } else { - if(zoneState.setting.power === 'OFF'){ currentState = 0; targetState = 0; active = 0; + } - if(zoneState.overlayType === null) + if(zoneState.overlayType === null){ + currentState = 0; targetState = 3; + } } @@ -971,7 +978,7 @@ module.exports = (api, accessories, config, tado, telegram) => { let characteristicActive = api.hap.Characteristic.Active; currentState = active - ? targetState === 3 + ? targetState === 3 || tempEqual ? 1 : currentState + 1 : 0; @@ -1229,45 +1236,39 @@ module.exports = (api, accessories, config, tado, telegram) => { const windowContactAccessory = accessories.filter(acc => acc && acc.context.config.subtype === 'zone-window-contact'); const windowSwitchAccessory = accessories.filter(acc => acc && acc.displayName === acc.context.config.homeName + ' Open Window'); - windowContactAccessory.forEach(acc => { - - if(acc.displayName.includes(zone.name)){ + if(windowContactAccessory.length){ - let serviceBattery = acc.getService(api.hap.Service.BatteryService); - let characteristicBattery = api.hap.Characteristic.BatteryLevel; - - if(serviceBattery && !isNaN(battery)){ + windowContactAccessory.forEach(acc => { - serviceBattery - .getCharacteristic(characteristicBattery) - .updateValue(battery); - - } - - let serviceSwitch = acc.getService(api.hap.Service.Switch); - let serviceContact = acc.getService(api.hap.Service.ContactSensor); - - let service = serviceSwitch || serviceContact; - - let characteristic = serviceSwitch - ? api.hap.Characteristic.On - : api.hap.Characteristic.ContactSensorState; + if(acc.displayName.includes(zone.name)){ - let state = serviceSwitch - ? zone.openWindowEnabled - ? true - : false - : zoneState.openWindow === null - ? 0 - : 1; + let serviceBattery = acc.getService(api.hap.Service.BatteryService); + let characteristicBattery = api.hap.Characteristic.BatteryLevel; - service - .getCharacteristic(characteristic) - .updateValue(state); + if(serviceBattery && !isNaN(battery)){ + + serviceBattery + .getCharacteristic(characteristicBattery) + .updateValue(battery); + + } + + let service = acc.getService(api.hap.Service.ContactSensor); + let characteristic = api.hap.Characteristic.ContactSensorState; + + let state = zoneState.openWindow || zoneState.openWindowDetected + ? 1 + : 0; + + service + .getCharacteristic(characteristic) + .updateValue(state); + + } - } - - }); + }); + + } if(windowSwitchAccessory.length){ @@ -1275,14 +1276,14 @@ module.exports = (api, accessories, config, tado, telegram) => { if(service.subtype && service.subtype.includes(zone.name)){ - let serviceSwitch = windowSwitchAccessory[0].getServiceById(api.hap.Service.Switch, service.subtype); + let service = windowSwitchAccessory[0].getServiceById(api.hap.Service.Switch, service.subtype); let characteristic = api.hap.Characteristic.On; let state = zone.openWindowEnabled ? true : false; - serviceSwitch + service .getCharacteristic(characteristic) .updateValue(state); @@ -1448,17 +1449,31 @@ module.exports = (api, accessories, config, tado, telegram) => { solarIntensityAccessory[0].context.lightBulbState = state; solarIntensityAccessory[0].context.lightBulbBrightness = brightness; - let service = solarIntensityAccessory[0].getService(api.hap.Service.Lightbulb); - let characteristicOn = api.hap.Characteristic.On; - let characteristicBrightness = api.hap.Characteristic.Brightness; + let serviceLightbulb = solarIntensityAccessory[0].getService(api.hap.Service.Lightbulb); + let serviceLightsensor = solarIntensityAccessory[0].getService(api.hap.Service.LightSensor); - service - .getCharacteristic(characteristicOn) - .updateValue(state); - - service - .getCharacteristic(characteristicBrightness) - .updateValue(brightness); + if(serviceLightbulb){ + + let characteristicOn = api.hap.Characteristic.On; + let characteristicBrightness = api.hap.Characteristic.Brightness; + + serviceLightbulb + .getCharacteristic(characteristicOn) + .updateValue(state); + + serviceLightbulb + .getCharacteristic(characteristicBrightness) + .updateValue(brightness); + + } else { + + let characteristicLux = api.hap.Characteristic.CurrentAmbientLightLevel; + + serviceLightsensor + .getCharacteristic(characteristicLux) + .updateValue(brightness * 1000); + + } } @@ -1773,7 +1788,7 @@ module.exports = (api, accessories, config, tado, telegram) => { let error; - console.log(err) + console.log(err); if(err.options) Logger.debug('API request ' + err.options.method + ' ' + err.options.url.pathname + ' ' + err.message, config.homeName); @@ -1809,7 +1824,7 @@ module.exports = (api, accessories, config, tado, telegram) => { } else if(err.output && err.output.payload && Object.keys(err.output.payload).length) { //simple-oauth2 boom error - error = err.output.payload + error = err.output.payload; } else { diff --git a/src/platform.js b/src/platform.js index c21ed90..7217d02 100644 --- a/src/platform.js +++ b/src/platform.js @@ -14,7 +14,8 @@ const HumidityAccessory = require('./accessories/humidity.js'); const MotionAccessory = require('./accessories/motion.js'); const OccupancyAccessory = require('./accessories/occupancy.js'); const SecurityAccessory = require('./accessories/security.js'); -const SolarlightAccessory = require('./accessories/lightbulb.js'); +const SolarLightbulbAccessory = require('./accessories/lightbulb.js'); +const SolarLightsensorAccessory = require('./accessories/lightsensor.js'); const SwitchAccessory = require('./accessories/switch.js'); const TemperatureAccessory = require('./accessories/temperature.js'); const ThermostatAccessory = require('./accessories/thermostat.js'); @@ -341,7 +342,10 @@ TadoPlatform.prototype = { new TemperatureAccessory(this.api, accessory, this.accessories, tado, deviceHandler, FakeGatoHistoryService); break; case 'weather-lightbulb': - new SolarlightAccessory(this.api, accessory, this.accessories, tado); + new SolarLightbulbAccessory(this.api, accessory, this.accessories, tado); + break; + case 'weather-lightsensor': + new SolarLightsensorAccessory(this.api, accessory, this.accessories, tado); break; case 'weather-airquality': new AirqualityAccessory(this.api, accessory, this.accessories, tado); diff --git a/src/tado/tado-config.js b/src/tado/tado-config.js index f4fd43d..5528e79 100644 --- a/src/tado/tado-config.js +++ b/src/tado/tado-config.js @@ -55,6 +55,7 @@ module.exports = { weather: { temperatureSensor: false, solarIntensity: false, + accTypeSolarIntensity: 'LIGHTBULB', airQuality: false }, extras: { @@ -548,7 +549,7 @@ module.exports = { let valid_boilerTypes = ['SWITCH', 'FAUCET']; let valid_zoneTypes = ['HEATING', 'HOT_WATER']; - let valid_modes = ['MANUAL', 'AUTO', 'TIMER']; + let valid_modes = ['MANUAL', 'AUTO', 'TIMER', 'CUSTOM']; if(!zone.name) { Logger.warn('There is no name configured for this zone. This zone will be skipped.', home.name); @@ -861,8 +862,9 @@ module.exports = { let config = { ...accessoryConfig }; config.name = name; - config.subtype = 'weather-lightbulb'; - + config.subtype = home.weather.accTypeSolarIntensity === 'SENSOR' + ? 'weather-lightsensor' + : 'weather-lightbulb'; config.model = 'Solar Intensity'; config.serialNumber = hashCode(name); @@ -948,6 +950,16 @@ module.exports = { }); } + + //Configure Turnoff Switch + if(home.extras.dummySwitch){ + + validSwitches.push({ + name: 'Dummy', + sub: 'CentralDummy' + }); + + } }