From 39f3136d752cdd5b1bf4e23b44a27c9b5c071a72 Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Tue, 29 Sep 2020 18:29:25 +0200 Subject: [PATCH 01/20] WWST-6898 - New DTH for Viconics Schneider Room Controller --- .../child-setpoints.groovy | 57 +++ .../viconics-schneider-room-controller.groovy | 456 ++++++++++++++++++ 2 files changed, 513 insertions(+) create mode 100644 devicetypes/smartthings/child-setpoints.src/child-setpoints.groovy create mode 100644 devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy diff --git a/devicetypes/smartthings/child-setpoints.src/child-setpoints.groovy b/devicetypes/smartthings/child-setpoints.src/child-setpoints.groovy new file mode 100644 index 00000000000..f7a792e041e --- /dev/null +++ b/devicetypes/smartthings/child-setpoints.src/child-setpoints.groovy @@ -0,0 +1,57 @@ +/** + * Child Thermostat Setpoints + * + * Copyright 2020 SmartThings + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License + * for the specific language governing permissions and limitations under the License. + * + */ +metadata { + definition(name: "Child Setpoints", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "oic.d.thermostat") { + capability "Actuator" + capability "Configuration" + capability "Health Check" + capability "Refresh" + capability "Thermostat Cooling Setpoint" + capability "Thermostat Heating Setpoint" + } +} + +def setCoolingSetpoint(setpoint) { + log.debug "setCoolingSetpoint: ${setpoint}" + parent.setChildCoolingSetpoint(device.deviceNetworkId, setpoint) +} + +def setHeatingSetpoint(setpoint) { + log.debug "setHeatingSetpoint: ${setpoint}" + parent.setChildHeatingSetpoint(device.deviceNetworkId, setpoint) +} + +def installed() { + configure() +} + +def updated() { + configure() +} + +def configure() { + parent.configureChild() + refresh() +} + +def ping() { + refresh() +} + +def refresh() { + parent.refreshChild() +} \ No newline at end of file diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy new file mode 100644 index 00000000000..a55a3047ff2 --- /dev/null +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -0,0 +1,456 @@ +/** + * Viconics/Schneider Room Controller + * + * Copyright 2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License + * for the specific language governing permissions and limitations under the License. + * + */ +import groovy.json.JsonOutput +import physicalgraph.zigbee.zcl.DataType +metadata { + definition(name: "Viconics Schneider Room Controller", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "oic.d.thermostat", mcdSync: true) { + + capability "Actuator" + capability "Sensor" + capability "Temperature Measurement" + capability "Relative Humidity Measurement" + capability "Thermostat" + capability "Thermostat Mode" + capability "Fan Speed" + capability "Thermostat Fan Mode" + capability "Thermostat Cooling Setpoint" + capability "Thermostat Heating Setpoint" + capability "Thermostat Operating State" + capability "Configuration" + capability "Health Check" + capability "Refresh" + + //Viconics VT8350 + fingerprint manufacturer: "Viconics", model: "254-143", deviceJoinName: "Viconics Thermostat", mnmn: "SmartThings", vid: "SmartThings-smartthings-Viconics_Schneider_Room_Controller_Fan" // VT8350 Low Voltage Fan Coil Controller and Zone Controller, Raw Description 0A 0104 0301 00 0A 0201 0202 0405 0402 0406 0204 0000 0004 0003 0005 0B 0201 0202 0405 0402 0406 0204 0000 0004 0003 0005 0500 + //fingerprint profileId: "0104", inClusters: "0000,0003,0201,0202,0204,0405", outClusters: "0402,0405", manufacturer: "Viconics", model: "254-143", deviceJoinName: "VT8350" + } +} + +private getTHERMOSTAT_CLUSTER() { 0x0201 } +private getTHERMOSTAT_UI_CONFIGURATION_CLUSTER() { 0x0204 } +private getRELATIVE_HUMIDITY_CLUSTER() {0x405} +private getRELATIVE_HUMIDITY_MEASURED_VALUE() {0x0000} +private getTEMPERATURE_DISPLAY_MODE() {0x0000} +private getLOCAL_TEMPERATURE() {0x0000} +private getCOOLING_SETPOINT() { 0x0011 } +private getHEATING_SETPOINT() { 0x0012 } +private getCOOLING_SETPOINT_UNOCCUPIED() { 0x0013 } +private getHEATING_SETPOINT_UNOCCUPIED() { 0x0014 } +private getCUSTOM_HUMIDITY() { 0x07a6 } +private getCUSTOM_THERMOSTAT_MODE() { 0x0687 } +private getCUSTOM_FAN_SPEED() { 0x0688 } +private getCUSTOM_FAN_MODE() { 0x0698 } +private getCUSTOM_OCCUPANCY() { 0x0c50 } +private getCUSTOM_THERMOSTAT_OPERATING_STATE() { 0x06BF } +private getUNOCCUPIED_SETPOINT_CHILD_DEVICE_ID() {1} +private getTHERMOSTAT_MODE_OFF() { 0x00 } +private getTHERMOSTAT_MODE_AUTO() { 0x01 } +private getTHERMOSTAT_MODE_COOL() { 0x03 } +private getTHERMOSTAT_MODE_HEAT() { 0x04 } + +private getFAN_MODE_MAP() { + [ + "04":"on", + "05":"auto" + ] +} + +private getTHERMOSTAT_MODE_MAP() { + [ + "00":"off", + "01":"auto", + "02":"cool", + "03":"heat", + "04":"heat" + ] +} + +private getTHERMOSTAT_OPERATING_STATE_MAP() { + [ + "00":"idle", + "01":"cooling", + "02":"heating" + ] +} + +def installed() { + log.debug "installed" + + sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"]) + state.supportedFanModes = ["on", "auto"] + state.supportedThermostatModes = ["off", "auto", "cool", "heat"] + + sendEvent(name: "supportedThermostatFanModes", value: JsonOutput.toJson(state.supportedFanModes), displayed: false) + sendEvent(name: "supportedThermostatModes", value: JsonOutput.toJson(state.supportedThermostatModes), displayed: false) + sendEvent(name: "coolingSetpointRange", value: coolingSetpointRange, displayed: false) + sendEvent(name: "heatingSetpointRange", value: heatingSetpointRange, displayed: false) +} + +private void createChildThermostat() { + log.debug "Creating child thermostat to handle unoccupied cooling/heating setpoints" + def label = "Unoccupied setpoints" + def childName = "${device.displayName} " + label + + def child = addChildDevice("Child Setpoints", "${device.deviceNetworkId}:1", device.hubId, + [completedSetup: true, label: childName, isComponent: true, componentName: "childSetpoints", componentLabel: label] + ) + + child.sendEvent(name: "coolingSetpoint", value: 20.0, unit: "C") + child.sendEvent(name: "heatingSetpoint", value: 21.0, unit: "C") + log.debug "child.inspect() ${child}" +} + + +def parse(String description) { + def eventMap = zigbee.getEvent(description) + def result = [] + + if (description?.startsWith("humidity: ")){ + // Viconics VT8350 humidity reports are parsed as floating point numbers (range 0 - 1%) + def humidityVal = (description - "humidity: " - "%").trim() + if (humidityVal.isNumber()) { + humidityVal = new BigDecimal(humidityVal) * 100 + } + eventMap.name = "humidity" + eventMap.value = humidityVal + eventMap.unit = "%" + } + /*} else if (eventMap) { + log.debug "eventMap: ${eventMap.inspect()}" + // Viconics VT8350 humidity reports are parsed as floating point numbers (range 0 - 1%) + if(eventMap.name == "humidity") { + eventMap.value = eventMap.value * 100 + } + result = createEvent(eventMap) + }*/ else { + eventMap = [:] + def descMap = zigbee.parseDescriptionAsMap(description) + + if((descMap.clusterInt == THERMOSTAT_CLUSTER && descMap.attrId)){ + def attributeInt = zigbee.convertHexToInt(descMap.attrId) + + if (attributeInt == COOLING_SETPOINT) { + log.debug "COOLING SETPOINT OCCUPIED, descMap.value: ${descMap.value}" + eventMap.name = "coolingSetpoint" + eventMap.value = getTemperature(descMap.value) + eventMap.unit = temperatureScale + } else if (attributeInt == HEATING_SETPOINT) { + log.debug "HEATING SETPOINT OCCUPIED, descMap.value: ${descMap.value}" + eventMap.name = "heatingSetpoint" + eventMap.value = getTemperature(descMap.value) + eventMap.unit = temperatureScale + } else if(attributeInt == COOLING_SETPOINT_UNOCCUPIED) { + log.debug "COOLING SETPOINT UNOCCUPIED, descMap.value: ${descMap.value}" + def childEvent = [:] + childEvent.name = "coolingSetpoint" + childEvent.value = getTemperature(descMap.value) + childEvent.unit = temperatureScale + sendEventToChild(UNOCCUPIED_SETPOINT_CHILD_DEVICE_ID, childEvent) + } else if (attributeInt == HEATING_SETPOINT_UNOCCUPIED) { + log.debug "HEATING SETPOINT UNOCCUPIED, descMap.value: ${descMap.value}" + def childEvent = [:] + childEvent.name = "heatingSetpoint" + childEvent.value = getTemperature(descMap.value) + childEvent.unit = temperatureScale + sendEventToChild(UNOCCUPIED_SETPOINT_CHILD_DEVICE_ID, childEvent) + } else if (attributeInt == LOCAL_TEMPERATURE) { + log.debug "LOCAL TEMPERATURE, descMap.value: ${descMap.value}" + eventMap.name = "temperature" + eventMap.value = getTemperature(descMap.value) + eventMap.unit = temperatureScale + } else if (attributeInt == CUSTOM_HUMIDITY) { + log.debug "CUSTOM HUMIDITY, descMap.value: ${descMap.value}" + eventMap.name = "humidity" + eventMap.value = Integer.parseInt(descMap.value, 16) + eventMap.unit = "%" + } else if (attributeInt == CUSTOM_FAN_MODE) { + // this device doesn't report fan mode concrete values (it responds for query, but it didn't send any values) + log.debug "CUSTOM FAN MODE, descMap.value: ${descMap.value}" + } else if (attributeInt == CUSTOM_FAN_SPEED) { + // VT8350 reports fan speed 3 as AUTO + log.debug "CUSTOM FAN SPEED, descMap.value: ${descMap.value}" + def sliderValue = mapFanSpeedSliderValue(descMap.value) + if (sliderValue < 4) { + eventMap.name = "fanSpeed" + eventMap.value = sliderValue + result << createEvent([name:"thermostatFanMode", value: "on", data: [supportedThermostatFanModes: state.supportedFanModes]]) + } else { + result << createEvent([name:"thermostatFanMode", value: "auto", data:[supportedThermostatFanModes: state.supportedFanModes]]) + } + } else if (attributeInt == CUSTOM_THERMOSTAT_MODE) { + log.debug "CUSTOM THERMOSTAT MODE, descMap.value: ${descMap.value}" + eventMap.name = "thermostatMode" + eventMap.value = THERMOSTAT_MODE_MAP[descMap.value] + eventMap.data = [supportedThermostatModes: state.supportedThermostatModes] + } else if (attributeInt == CUSTOM_THERMOSTAT_OPERATING_STATE) { + log.debug "CUSTOM THERMOSTAT OPERATING STATE, descMap.value: ${descMap.value}" + eventMap.name = "thermostatOperatingState" + eventMap.value = THERMOSTAT_OPERATING_STATE_MAP[descMap.value] + } else if(attributeInt == CUSTOM_OCCUPANCY) { + log.debug "CUSTOM OCCUPANCY, descMap.value: ${descMap.value}" + } else { + log.debug "descMap.inspect(): ${descMap.inspect()}" + } + } + } + result << createEvent(eventMap) + //log.debug "Description ${description} parsed to ${result}" + return result +} + +private sendEventToChild(childNumber, event) { + def child = childDevices?.find { getChildId(it.deviceNetworkId) == childNumber } + + if (child) { + log.debug "Sending ${event.name} event to $child.displayName" + child?.sendEvent(event) + } else { + log.debug "Child device $childNumber not found!" + } +} + +def setCoolingSetpoint(degrees) { + setSetpoint(degrees, COOLING_SETPOINT) +} + +def setHeatingSetpoint(degrees) { + setSetpoint(degrees, HEATING_SETPOINT) +} + +def setChildCoolingSetpoint(deviceNetworkId, degrees) { + log.debug "deviceNetworkId: ${deviceNetworkId} degrees: ${degrees}" + def switchId = getChildId(deviceNetworkId) + if (switchId != null) { + setSetpoint(degrees, COOLING_SETPOINT_UNOCCUPIED) + } +} + +def setChildHeatingSetpoint(deviceNetworkId, degrees) { + log.debug "deviceNetworkId: ${deviceNetworkId} degrees: ${degrees}" + + def switchId = getChildId(deviceNetworkId) + if (switchId != null) { + setSetpoint(degrees, HEATING_SETPOINT_UNOCCUPIED) + } +} + +def getChildId(deviceNetworkId) { + def split = deviceNetworkId?.split(":") + return (split.length > 1) ? split[1] as Integer : null +} + +def setSetpoint(degrees, setpointAttr) { + log.debug "degrees: ${degrees}, setpointAttr: ${setpointAttr}" + if (degrees != null && setpointAttr != null) { + log.debug "temperatureScale: ${temperatureScale}" + def celsius = (temperatureScale == "C") ? degrees : fahrenheitToCelsius(degrees) + celsius = (celsius as Double).round(2) + + delayBetween([ + zigbee.writeAttribute(THERMOSTAT_CLUSTER, setpointAttr, DataType.INT16, zigbee.convertToHexString(celsius * 100)), + zigbee.readAttribute(THERMOSTAT_CLUSTER, setpointAttr) + ], 500) + } +} + +def setThermostatFanMode(mode) { + if (state.supportedFanModes?.contains(mode)) { + switch (mode) { + case "on": + setFanSpeed(1) + break + case "auto": + setFanSpeed(4) + break + } + } else { + log.debug "Unsupported fan mode $mode" + } +} + +def setFanSpeed(speed) { + log.debug "setFanSpeed: ${speed}" + + if (speed == 0 || speed >= 4) { //if by any chance user selects 0 or a value higher than 3, it fan will be set to AUTO + speed = 3 + } else { + speed = speed - 1 + } + delayBetween([ + zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED, DataType.ENUM8, speed), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED) + ], 500) +} + +def setThermostatMode(mode) { + log.debug "set mode $mode (supported ${state.supportedThermostatModes})" + if (state.supportedThermostatModes?.contains(mode)) { + switch (mode) { + case "auto": + auto() + break + case "cool": + cool() + break + case "heat": + heat() + break + case "off": + off() + break + } + } else { + log.debug "Unsupported mode $mode" + } +} + +def off() { + delayBetween([ + zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, THERMOSTAT_MODE_OFF), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE) + ], 500) +} + +def auto() { + delayBetween([ + zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, THERMOSTAT_MODE_AUTO), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE) + ], 500) +} + +def cool() { + delayBetween([ + zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, THERMOSTAT_MODE_COOL), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE) + ], 500) +} + +def heat() { + delayBetween([ + zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, THERMOSTAT_MODE_HEAT), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE) + ], 500) +} + +def ping() { + log.debug "ping" + refresh() +} + +def refresh() { + log.debug "refresh" + getRefreshCommands() +} + +def getRefreshCommands() { + def refreshCommands = [] + + refreshCommands += zigbee.readAttribute(RELATIVE_HUMIDITY_CLUSTER, RELATIVE_HUMIDITY_MEASURED_VALUE) + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_HUMIDITY) + + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, LOCAL_TEMPERATURE) + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, COOLING_SETPOINT) + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, HEATING_SETPOINT) + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, COOLING_SETPOINT_UNOCCUPIED) + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, HEATING_SETPOINT_UNOCCUPIED) + + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED) + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE) + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE) // formerly THERMOSTAT MODE: 0x001C + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) + + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, 0x0650) //occupancy command + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_OCCUPANCY) + refreshCommands += zigbee.readAttribute(THERMOSTAT_UI_CONFIGURATION_CLUSTER, TEMPERATURE_DISPLAY_MODE) + + refreshCommands +} + +def configure() { + log.debug "Configuration" + + if(!childDevices) { + createChildThermostat() + } + + def configurationCommands = [] + // todo: check if following binding is necessary here + configurationCommands += "zdo bind 0x${device.deviceNetworkId} 1 0xA 0x201 {${device.zigbeeId}} {}" + configurationCommands += "zdo bind 0x${device.deviceNetworkId} 1 0xA 0x405 {${device.zigbeeId}} {}" + + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, 1, 300, 1) //formerly THERMOSTAT MODE: 0x001C + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, LOCAL_TEMPERATURE, DataType.INT16, 10, 60, 50) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, COOLING_SETPOINT, DataType.INT16, 1, 300, 10) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, HEATING_SETPOINT, DataType.INT16, 1, 300, 10) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, COOLING_SETPOINT_UNOCCUPIED, DataType.INT16, 1, 300, 10) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, HEATING_SETPOINT_UNOCCUPIED, DataType.INT16, 1, 300, 10) + //configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, 0x0A58, 0x10, 1, 300, 1) //GFan + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE, DataType.ENUM8, 1, 300, 1) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED, DataType.ENUM8, 1, 300, 1) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE, DataType.ENUM8, 1, 300, 1) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_OCCUPANCY, DataType.ENUM8, 1, 300, 1) + configurationCommands += zigbee.configureReporting(THERMOSTAT_UI_CONFIGURATION_CLUSTER, TEMPERATURE_DISPLAY_MODE, DataType.ENUM8, 1, 300, 1) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_HUMIDITY, DataType.UINT16, 60, 300, 5) + configurationCommands += zigbee.configureReporting(RELATIVE_HUMIDITY_CLUSTER, RELATIVE_HUMIDITY_MEASURED_VALUE, DataType.UINT16, 60, 300, 5) + + delayBetween(getRefreshCommands()+configurationCommands) +} + +def isViconicsVT8350() { + device.getDataValue("model") == "254-143" // Viconics VT8350 Low Voltage Fan Coil Controller and Zone Controller +} + +def getCoolingSetpointRange() { + (getTemperatureScale() == "C") ? [12, 37.5] : [54, 100] +} +def getHeatingSetpointRange() { + (getTemperatureScale() == "C") ? [4.5, 32] : [40, 90] +} + +def getTemperature(value) { + if (value != null) { + def celsius = Integer.parseInt(value, 16) / 100 + if (temperatureScale == "C") { + return celsius//Math.round(celsius) + } else { + return celsiusToFahrenheit(celsius)//Math.round(celsiusToFahrenheit(celsius)) + } + } +} + +def mapFanSpeedSliderValue(rawValue) { + log.debug "mapFanSpeedSliderValue: ${rawValue}" + //Map current fan value to the Fan Speed slider + def resultValue + + switch(rawValue) { + case "00": // low + resultValue = 1 + break + case "01": // medium + resultValue = 2 + break + case "02": // high + resultValue = 3 + break + case "03": // auto + default: + resultValue = 4 + break + } + resultValue +} \ No newline at end of file From 4fa9d05891f92efab269b0fcf3e0f2c7e28788a3 Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Thu, 1 Oct 2020 15:26:37 +0200 Subject: [PATCH 02/20] Support for capabilities: occupancy, fan mode. Support for Viconics VT8650, Schneider Electric: SE8350 and SE8650. Reading more attributes after thermostat mode and fan speed changes. Changes in configuration. --- .../viconics-schneider-room-controller.groovy | 186 ++++++++++++++---- 1 file changed, 143 insertions(+), 43 deletions(-) diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy index a55a3047ff2..da4bc85301b 100644 --- a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -20,6 +20,7 @@ metadata { capability "Actuator" capability "Sensor" + capability "Occupancy Sensor" capability "Temperature Measurement" capability "Relative Humidity Measurement" capability "Thermostat" @@ -33,9 +34,22 @@ metadata { capability "Health Check" capability "Refresh" - //Viconics VT8350 - fingerprint manufacturer: "Viconics", model: "254-143", deviceJoinName: "Viconics Thermostat", mnmn: "SmartThings", vid: "SmartThings-smartthings-Viconics_Schneider_Room_Controller_Fan" // VT8350 Low Voltage Fan Coil Controller and Zone Controller, Raw Description 0A 0104 0301 00 0A 0201 0202 0405 0402 0406 0204 0000 0004 0003 0005 0B 0201 0202 0405 0402 0406 0204 0000 0004 0003 0005 0500 - //fingerprint profileId: "0104", inClusters: "0000,0003,0201,0202,0204,0405", outClusters: "0402,0405", manufacturer: "Viconics", model: "254-143", deviceJoinName: "VT8350" + // Viconics VT8350 Low Voltage Fan Coil Controller and Zone Controller + // Raw Description 0A 0104 0301 00 0A 0201 0202 0405 0402 0406 0204 0000 0004 0003 0005 0B 0201 0202 0405 0402 0406 0204 0000 0004 0003 0005 0500 + fingerprint manufacturer: "Viconics", model: "254-143", deviceJoinName: "Viconics Room Controller", mnmn: "SmartThings", vid: "SmartThings-smartthings-Viconics_Schneider_Room_Controller_Fan" + + // Viconics VT8650 Heat Pump and Indoor Air Quality Controller + // Raw Description 0A 0104 0301 00 09 0201 0405 0402 0406 0204 0000 0004 0003 0005 0A 0201 0405 0402 0406 0204 0000 0004 0003 0005 0500 + fingerprint manufacturer: "Viconics", model: "254-162", deviceJoinName: "Viconics Room Controller", mnmn: "SmartThings", vid: "SmartThings-smartthings-Viconics_Schneider_Room_Controller" + //fingerprint profileId: "0104", inClusters: "0000,0003,0201,0204,0405", outClusters: "0402,0405", manufacturer: "Viconics", model: "254-162", deviceJoinName: "VT8650xx" + + // Schneider Electric SE8350 Low Voltage Fan Coil Unit (FCU) and Zone Control + // Raw Description 0A 0104 0301 00 0A 0201 0202 0405 0402 0406 0204 0000 0004 0003 0005 0B 0201 0202 0405 0402 0406 0204 0000 0004 0003 0005 0500 + fingerprint manufacturer: "Schneider Electric", model: "254-145", deviceJoinName: "Schneider Electric Room Controller", vid: "SmartThings-smartthings-Viconics_Schneider_Room_Controller_Fan" + + // Schneider Electric SE8650 Roof Top Unit Controller + // Raw Description 0A 0104 0301 00 09 0201 0405 0402 0406 0204 0000 0004 0003 0005 0A 0201 0405 0402 0406 0204 0000 0004 0003 0005 0500 + fingerprint manufacturer: "Schneider Electric", model: "254-163", deviceJoinName: "Schneider Electric Room Controller", vid: "SmartThings-smartthings-Viconics_Schneider_Room_Controller" } } @@ -49,22 +63,28 @@ private getCOOLING_SETPOINT() { 0x0011 } private getHEATING_SETPOINT() { 0x0012 } private getCOOLING_SETPOINT_UNOCCUPIED() { 0x0013 } private getHEATING_SETPOINT_UNOCCUPIED() { 0x0014 } +private getOCCUPANCY() { 0x002 } private getCUSTOM_HUMIDITY() { 0x07a6 } private getCUSTOM_THERMOSTAT_MODE() { 0x0687 } private getCUSTOM_FAN_SPEED() { 0x0688 } private getCUSTOM_FAN_MODE() { 0x0698 } -private getCUSTOM_OCCUPANCY() { 0x0c50 } +private getCUSTOM_EFFECTIVE_OCCUPANCY() { 0x0c50 } private getCUSTOM_THERMOSTAT_OPERATING_STATE() { 0x06BF } private getUNOCCUPIED_SETPOINT_CHILD_DEVICE_ID() {1} private getTHERMOSTAT_MODE_OFF() { 0x00 } private getTHERMOSTAT_MODE_AUTO() { 0x01 } -private getTHERMOSTAT_MODE_COOL() { 0x03 } -private getTHERMOSTAT_MODE_HEAT() { 0x04 } +private getTHERMOSTAT_MODE_COOL() { 0x02 } +private getTHERMOSTAT_MODE_HEAT() { 0x03 } +private getCUSTOM_FAN_MODE_ON() { 0x00 } +private getCUSTOM_FAN_MODE_AUTO() { 0x01 } +private getCUSTOM_FAN_MODE_CIRCULATE() { 0x02 } + private getFAN_MODE_MAP() { [ - "04":"on", - "05":"auto" + "00":"on", + "01":"auto", + "02":"circulate" ] } @@ -86,11 +106,27 @@ private getTHERMOSTAT_OPERATING_STATE_MAP() { ] } +private getEFFECTIVE_OCCUPANCY_MAP() { + [ + "00":"Occupied", + "01":"Unoccupied", + "02":"Override", + "03":"Standby" + ] +} + def installed() { log.debug "installed" sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"]) - state.supportedFanModes = ["on", "auto"] + + + if(isViconicsVT8350()|| isSchneiderSE8350()) { + state.supportedFanModes = ["on", "auto"] + } else { + state.supportedFanModes = ["on", "auto", "circulate"] + } + state.supportedThermostatModes = ["off", "auto", "cool", "heat"] sendEvent(name: "supportedThermostatFanModes", value: JsonOutput.toJson(state.supportedFanModes), displayed: false) @@ -142,7 +178,11 @@ def parse(String description) { if((descMap.clusterInt == THERMOSTAT_CLUSTER && descMap.attrId)){ def attributeInt = zigbee.convertHexToInt(descMap.attrId) - if (attributeInt == COOLING_SETPOINT) { + if(attributeInt == OCCUPANCY) { + log.debug "OCCUPANCY, descMap.value: ${descMap.value}" + eventMap.name = "occupancy" + eventMap.value = EFFECTIVE_OCCUPANCY_MAP[descMap.value] + } else if (attributeInt == COOLING_SETPOINT) { log.debug "COOLING SETPOINT OCCUPIED, descMap.value: ${descMap.value}" eventMap.name = "coolingSetpoint" eventMap.value = getTemperature(descMap.value) @@ -177,8 +217,12 @@ def parse(String description) { eventMap.value = Integer.parseInt(descMap.value, 16) eventMap.unit = "%" } else if (attributeInt == CUSTOM_FAN_MODE) { - // this device doesn't report fan mode concrete values (it responds for query, but it didn't send any values) log.debug "CUSTOM FAN MODE, descMap.value: ${descMap.value}" + if (isViconicsVT8650() || isSchneiderSE8650()) { + eventMap.name = "thermostatFanMode" + eventMap.value = FAN_MODE_MAP[descMap.value] + eventMap.data = [supportedThermostatFanModes: state.supportedFanModes] + } } else if (attributeInt == CUSTOM_FAN_SPEED) { // VT8350 reports fan speed 3 as AUTO log.debug "CUSTOM FAN SPEED, descMap.value: ${descMap.value}" @@ -199,8 +243,10 @@ def parse(String description) { log.debug "CUSTOM THERMOSTAT OPERATING STATE, descMap.value: ${descMap.value}" eventMap.name = "thermostatOperatingState" eventMap.value = THERMOSTAT_OPERATING_STATE_MAP[descMap.value] - } else if(attributeInt == CUSTOM_OCCUPANCY) { - log.debug "CUSTOM OCCUPANCY, descMap.value: ${descMap.value}" + } else if(attributeInt == CUSTOM_EFFECTIVE_OCCUPANCY) { + log.debug "EFFECTIVE OCCUPANCY, descMap.value: ${descMap.value}" + eventMap.name = "effectiveOccupancy" + eventMap.value = EFFECTIVE_OCCUPANCY_MAP[descMap.value] } else { log.debug "descMap.inspect(): ${descMap.inspect()}" } @@ -268,19 +314,55 @@ def setSetpoint(degrees, setpointAttr) { def setThermostatFanMode(mode) { if (state.supportedFanModes?.contains(mode)) { - switch (mode) { - case "on": - setFanSpeed(1) - break - case "auto": - setFanSpeed(4) - break + if(isViconicsVT8350() || isSchneiderSE8350()) { + switch (mode) { + case "on": + setFanSpeed(1) + break + case "auto": + setFanSpeed(4) + break + } + } else if(isViconicsVT8650() || isSchneiderSE8650()) { + switch (mode) { + case "on": + getThermostatFanModeCommands(CUSTOM_FAN_MODE_ON) + break + case "auto": + getThermostatFanModeCommands(CUSTOM_FAN_MODE_AUTO) + break + case "circulate": + getThermostatFanModeCommands(CUSTOM_FAN_MODE_CIRCULATE) + break + } } } else { log.debug "Unsupported fan mode $mode" } } +def fanOn() { + getThermostatFanModeCommands(CUSTOM_FAN_MODE_ON) +} + +def fanAuto() { + getThermostatFanModeCommands(CUSTOM_FAN_MODE_AUTO) +} + +def fanCirculate() { + getThermostatFanModeCommands(CUSTOM_FAN_MODE_CIRCULATE) +} + +def getThermostatFanModeCommands(mode) { + if(mode) { + delayBetween([ + zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE, DataType.ENUM8, mode), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE) + ], 500) + } +} + + def setFanSpeed(speed) { log.debug "setFanSpeed: ${speed}" @@ -291,8 +373,9 @@ def setFanSpeed(speed) { } delayBetween([ zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED, DataType.ENUM8, speed), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED), zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED) + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) ], 500) } @@ -321,28 +404,32 @@ def setThermostatMode(mode) { def off() { delayBetween([ zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, THERMOSTAT_MODE_OFF), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE) + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) ], 500) } def auto() { delayBetween([ zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, THERMOSTAT_MODE_AUTO), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE) + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) ], 500) } def cool() { delayBetween([ zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, THERMOSTAT_MODE_COOL), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE) + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) ], 500) } def heat() { delayBetween([ zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, THERMOSTAT_MODE_HEAT), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE) + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) ], 500) } @@ -374,7 +461,8 @@ def getRefreshCommands() { refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, 0x0650) //occupancy command - refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_OCCUPANCY) + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_EFFECTIVE_OCCUPANCY) + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, OCCUPANCY) refreshCommands += zigbee.readAttribute(THERMOSTAT_UI_CONFIGURATION_CLUSTER, TEMPERATURE_DISPLAY_MODE) refreshCommands @@ -392,28 +480,24 @@ def configure() { configurationCommands += "zdo bind 0x${device.deviceNetworkId} 1 0xA 0x201 {${device.zigbeeId}} {}" configurationCommands += "zdo bind 0x${device.deviceNetworkId} 1 0xA 0x405 {${device.zigbeeId}} {}" - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, 1, 300, 1) //formerly THERMOSTAT MODE: 0x001C - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, LOCAL_TEMPERATURE, DataType.INT16, 10, 60, 50) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, COOLING_SETPOINT, DataType.INT16, 1, 300, 10) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, HEATING_SETPOINT, DataType.INT16, 1, 300, 10) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, COOLING_SETPOINT_UNOCCUPIED, DataType.INT16, 1, 300, 10) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, HEATING_SETPOINT_UNOCCUPIED, DataType.INT16, 1, 300, 10) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, 1, 60, 1) //formerly THERMOSTAT MODE: 0x001C + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, LOCAL_TEMPERATURE, DataType.INT16, 10, 60, 10) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, COOLING_SETPOINT, DataType.INT16, 1, 60, 10) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, HEATING_SETPOINT, DataType.INT16, 1, 60, 10) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, COOLING_SETPOINT_UNOCCUPIED, DataType.INT16, 1, 60, 10) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, HEATING_SETPOINT_UNOCCUPIED, DataType.INT16, 1, 60, 10) //configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, 0x0A58, 0x10, 1, 300, 1) //GFan - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE, DataType.ENUM8, 1, 300, 1) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED, DataType.ENUM8, 1, 300, 1) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE, DataType.ENUM8, 1, 300, 1) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_OCCUPANCY, DataType.ENUM8, 1, 300, 1) - configurationCommands += zigbee.configureReporting(THERMOSTAT_UI_CONFIGURATION_CLUSTER, TEMPERATURE_DISPLAY_MODE, DataType.ENUM8, 1, 300, 1) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_HUMIDITY, DataType.UINT16, 60, 300, 5) - configurationCommands += zigbee.configureReporting(RELATIVE_HUMIDITY_CLUSTER, RELATIVE_HUMIDITY_MEASURED_VALUE, DataType.UINT16, 60, 300, 5) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE, DataType.ENUM8, 1, 60, 1) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED, DataType.ENUM8, 1, 60, 1) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE, DataType.ENUM8, 1, 60, 1) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_EFFECTIVE_OCCUPANCY, DataType.ENUM8, 1, 60, 1) + configurationCommands += zigbee.configureReporting(THERMOSTAT_UI_CONFIGURATION_CLUSTER, TEMPERATURE_DISPLAY_MODE, DataType.ENUM8, 1, 60, 1) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_HUMIDITY, DataType.UINT16, 1, 60, 10) + configurationCommands += zigbee.configureReporting(RELATIVE_HUMIDITY_CLUSTER, RELATIVE_HUMIDITY_MEASURED_VALUE, DataType.UINT16, 1, 60, 5) delayBetween(getRefreshCommands()+configurationCommands) } -def isViconicsVT8350() { - device.getDataValue("model") == "254-143" // Viconics VT8350 Low Voltage Fan Coil Controller and Zone Controller -} - def getCoolingSetpointRange() { (getTemperatureScale() == "C") ? [12, 37.5] : [54, 100] } @@ -453,4 +537,20 @@ def mapFanSpeedSliderValue(rawValue) { break } resultValue +} + +def isViconicsVT8350() { + device.getDataValue("model") == "254-143" // Viconics VT8350 Low Voltage Fan Coil Controller and Zone Controller +} + +def isViconicsVT8650() { + device.getDataValue("model") == "254-162" // Viconics VT8650 Heat Pump and Indoor Air Quality Controller +} + +def isSchneiderSE8350() { + device.getDataValue("model") == "254-145" // SE8350 Low Voltage Fan Coil Unit (FCU) and Zone Control +} + +def isSchneiderSE8650() { + device.getDataValue("model") == "254-163" // SE8650 Roof Top Unit Controller } \ No newline at end of file From 088f78b22f4c672679df6ca464a67b107d632c55 Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Thu, 1 Oct 2020 15:52:02 +0200 Subject: [PATCH 03/20] Fixes spacing --- .../viconics-schneider-room-controller.groovy | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy index da4bc85301b..7519a14d8e9 100644 --- a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -117,16 +117,13 @@ private getEFFECTIVE_OCCUPANCY_MAP() { def installed() { log.debug "installed" - sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"]) - - if(isViconicsVT8350()|| isSchneiderSE8350()) { + if (isViconicsVT8350()|| isSchneiderSE8350()) { state.supportedFanModes = ["on", "auto"] } else { state.supportedFanModes = ["on", "auto", "circulate"] } - state.supportedThermostatModes = ["off", "auto", "cool", "heat"] sendEvent(name: "supportedThermostatFanModes", value: JsonOutput.toJson(state.supportedFanModes), displayed: false) @@ -149,12 +146,11 @@ private void createChildThermostat() { log.debug "child.inspect() ${child}" } - def parse(String description) { def eventMap = zigbee.getEvent(description) def result = [] - if (description?.startsWith("humidity: ")){ + if (description?.startsWith("humidity: ")) { // Viconics VT8350 humidity reports are parsed as floating point numbers (range 0 - 1%) def humidityVal = (description - "humidity: " - "%").trim() if (humidityVal.isNumber()) { @@ -167,7 +163,7 @@ def parse(String description) { /*} else if (eventMap) { log.debug "eventMap: ${eventMap.inspect()}" // Viconics VT8350 humidity reports are parsed as floating point numbers (range 0 - 1%) - if(eventMap.name == "humidity") { + if (eventMap.name == "humidity") { eventMap.value = eventMap.value * 100 } result = createEvent(eventMap) @@ -175,10 +171,10 @@ def parse(String description) { eventMap = [:] def descMap = zigbee.parseDescriptionAsMap(description) - if((descMap.clusterInt == THERMOSTAT_CLUSTER && descMap.attrId)){ + if ((descMap.clusterInt == THERMOSTAT_CLUSTER && descMap.attrId)) { def attributeInt = zigbee.convertHexToInt(descMap.attrId) - if(attributeInt == OCCUPANCY) { + if (attributeInt == OCCUPANCY) { log.debug "OCCUPANCY, descMap.value: ${descMap.value}" eventMap.name = "occupancy" eventMap.value = EFFECTIVE_OCCUPANCY_MAP[descMap.value] @@ -192,7 +188,7 @@ def parse(String description) { eventMap.name = "heatingSetpoint" eventMap.value = getTemperature(descMap.value) eventMap.unit = temperatureScale - } else if(attributeInt == COOLING_SETPOINT_UNOCCUPIED) { + } else if (attributeInt == COOLING_SETPOINT_UNOCCUPIED) { log.debug "COOLING SETPOINT UNOCCUPIED, descMap.value: ${descMap.value}" def childEvent = [:] childEvent.name = "coolingSetpoint" @@ -243,7 +239,7 @@ def parse(String description) { log.debug "CUSTOM THERMOSTAT OPERATING STATE, descMap.value: ${descMap.value}" eventMap.name = "thermostatOperatingState" eventMap.value = THERMOSTAT_OPERATING_STATE_MAP[descMap.value] - } else if(attributeInt == CUSTOM_EFFECTIVE_OCCUPANCY) { + } else if (attributeInt == CUSTOM_EFFECTIVE_OCCUPANCY) { log.debug "EFFECTIVE OCCUPANCY, descMap.value: ${descMap.value}" eventMap.name = "effectiveOccupancy" eventMap.value = EFFECTIVE_OCCUPANCY_MAP[descMap.value] @@ -314,7 +310,7 @@ def setSetpoint(degrees, setpointAttr) { def setThermostatFanMode(mode) { if (state.supportedFanModes?.contains(mode)) { - if(isViconicsVT8350() || isSchneiderSE8350()) { + if (isViconicsVT8350() || isSchneiderSE8350()) { switch (mode) { case "on": setFanSpeed(1) @@ -323,7 +319,7 @@ def setThermostatFanMode(mode) { setFanSpeed(4) break } - } else if(isViconicsVT8650() || isSchneiderSE8650()) { + } else if (isViconicsVT8650() || isSchneiderSE8650()) { switch (mode) { case "on": getThermostatFanModeCommands(CUSTOM_FAN_MODE_ON) @@ -354,7 +350,7 @@ def fanCirculate() { } def getThermostatFanModeCommands(mode) { - if(mode) { + if (mode) { delayBetween([ zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE, DataType.ENUM8, mode), zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE) @@ -362,7 +358,6 @@ def getThermostatFanModeCommands(mode) { } } - def setFanSpeed(speed) { log.debug "setFanSpeed: ${speed}" @@ -471,7 +466,7 @@ def getRefreshCommands() { def configure() { log.debug "Configuration" - if(!childDevices) { + if (!childDevices) { createChildThermostat() } @@ -518,7 +513,7 @@ def getTemperature(value) { def mapFanSpeedSliderValue(rawValue) { log.debug "mapFanSpeedSliderValue: ${rawValue}" - //Map current fan value to the Fan Speed slider + //maps current fan value to the Fan Speed slider def resultValue switch(rawValue) { From cf40900556181cd933b17d725b21040c32c98772 Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Fri, 2 Oct 2020 11:27:28 +0200 Subject: [PATCH 04/20] Renames child device to Child Thermostat Setpoints --- .../child-setpoints.groovy | 57 ------------------- .../viconics-schneider-room-controller.groovy | 2 +- 2 files changed, 1 insertion(+), 58 deletions(-) delete mode 100644 devicetypes/smartthings/child-setpoints.src/child-setpoints.groovy diff --git a/devicetypes/smartthings/child-setpoints.src/child-setpoints.groovy b/devicetypes/smartthings/child-setpoints.src/child-setpoints.groovy deleted file mode 100644 index f7a792e041e..00000000000 --- a/devicetypes/smartthings/child-setpoints.src/child-setpoints.groovy +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Child Thermostat Setpoints - * - * Copyright 2020 SmartThings - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed - - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License - * for the specific language governing permissions and limitations under the License. - * - */ -metadata { - definition(name: "Child Setpoints", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "oic.d.thermostat") { - capability "Actuator" - capability "Configuration" - capability "Health Check" - capability "Refresh" - capability "Thermostat Cooling Setpoint" - capability "Thermostat Heating Setpoint" - } -} - -def setCoolingSetpoint(setpoint) { - log.debug "setCoolingSetpoint: ${setpoint}" - parent.setChildCoolingSetpoint(device.deviceNetworkId, setpoint) -} - -def setHeatingSetpoint(setpoint) { - log.debug "setHeatingSetpoint: ${setpoint}" - parent.setChildHeatingSetpoint(device.deviceNetworkId, setpoint) -} - -def installed() { - configure() -} - -def updated() { - configure() -} - -def configure() { - parent.configureChild() - refresh() -} - -def ping() { - refresh() -} - -def refresh() { - parent.refreshChild() -} \ No newline at end of file diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy index 7519a14d8e9..b383946e900 100644 --- a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -137,7 +137,7 @@ private void createChildThermostat() { def label = "Unoccupied setpoints" def childName = "${device.displayName} " + label - def child = addChildDevice("Child Setpoints", "${device.deviceNetworkId}:1", device.hubId, + def child = addChildDevice("Child Thermostat Setpoints", "${device.deviceNetworkId}:1", device.hubId, [completedSetup: true, label: childName, isComponent: true, componentName: "childSetpoints", componentLabel: label] ) From 1eb23fd253886ceca107d0dfdc65ab6ae978b44a Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Fri, 2 Oct 2020 11:48:59 +0200 Subject: [PATCH 05/20] Renames child device to Child Thermostat Setpoints --- .../child-thermostat-setpoints.groovy | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 devicetypes/smartthings/child-thermostat-setpoints.src/child-thermostat-setpoints.groovy diff --git a/devicetypes/smartthings/child-thermostat-setpoints.src/child-thermostat-setpoints.groovy b/devicetypes/smartthings/child-thermostat-setpoints.src/child-thermostat-setpoints.groovy new file mode 100644 index 00000000000..baad415689c --- /dev/null +++ b/devicetypes/smartthings/child-thermostat-setpoints.src/child-thermostat-setpoints.groovy @@ -0,0 +1,57 @@ +/** + * Child Thermostat Setpoints + * + * Copyright 2020 SmartThings + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License + * for the specific language governing permissions and limitations under the License. + * + */ +metadata { + definition(name: "Child Thermostat Setpoints", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "oic.d.thermostat") { + capability "Actuator" + capability "Configuration" + capability "Health Check" + capability "Refresh" + capability "Thermostat Cooling Setpoint" + capability "Thermostat Heating Setpoint" + } +} + +def setCoolingSetpoint(setpoint) { + log.debug "setCoolingSetpoint: ${setpoint}" + parent.setChildCoolingSetpoint(device.deviceNetworkId, setpoint) +} + +def setHeatingSetpoint(setpoint) { + log.debug "setHeatingSetpoint: ${setpoint}" + parent.setChildHeatingSetpoint(device.deviceNetworkId, setpoint) +} + +def installed() { + configure() +} + +def updated() { + configure() +} + +def configure() { + parent.configureChild() + refresh() +} + +def ping() { + refresh() +} + +def refresh() { + parent.refreshChild() +} \ No newline at end of file From 9a6d2d6664a980e41fe5df7b67a8e6df8d715614 Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Fri, 2 Oct 2020 12:24:00 +0200 Subject: [PATCH 06/20] Fixes processing occupancy reports. --- .../viconics-schneider-room-controller.groovy | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy index b383946e900..3ee62d1820b 100644 --- a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -64,11 +64,12 @@ private getHEATING_SETPOINT() { 0x0012 } private getCOOLING_SETPOINT_UNOCCUPIED() { 0x0013 } private getHEATING_SETPOINT_UNOCCUPIED() { 0x0014 } private getOCCUPANCY() { 0x002 } +private getCUSTOM_OCCUPANCY() {0x0650} +private getCUSTOM_EFFECTIVE_OCCUPANCY() { 0x0c50 } private getCUSTOM_HUMIDITY() { 0x07a6 } private getCUSTOM_THERMOSTAT_MODE() { 0x0687 } private getCUSTOM_FAN_SPEED() { 0x0688 } private getCUSTOM_FAN_MODE() { 0x0698 } -private getCUSTOM_EFFECTIVE_OCCUPANCY() { 0x0c50 } private getCUSTOM_THERMOSTAT_OPERATING_STATE() { 0x06BF } private getUNOCCUPIED_SETPOINT_CHILD_DEVICE_ID() {1} private getTHERMOSTAT_MODE_OFF() { 0x00 } @@ -174,8 +175,8 @@ def parse(String description) { if ((descMap.clusterInt == THERMOSTAT_CLUSTER && descMap.attrId)) { def attributeInt = zigbee.convertHexToInt(descMap.attrId) - if (attributeInt == OCCUPANCY) { - log.debug "OCCUPANCY, descMap.value: ${descMap.value}" + if (attributeInt == OCCUPANCY || attributeInt == CUSTOM_EFFECTIVE_OCCUPANCY) { + log.debug "${attributeInt == OCCUPANCY ? "OCCUPANCY" : "EFFECTIVE OCCUPANCY"}, descMap.value: ${descMap.value}, attrId: ${attributeInt}" eventMap.name = "occupancy" eventMap.value = EFFECTIVE_OCCUPANCY_MAP[descMap.value] } else if (attributeInt == COOLING_SETPOINT) { @@ -239,10 +240,6 @@ def parse(String description) { log.debug "CUSTOM THERMOSTAT OPERATING STATE, descMap.value: ${descMap.value}" eventMap.name = "thermostatOperatingState" eventMap.value = THERMOSTAT_OPERATING_STATE_MAP[descMap.value] - } else if (attributeInt == CUSTOM_EFFECTIVE_OCCUPANCY) { - log.debug "EFFECTIVE OCCUPANCY, descMap.value: ${descMap.value}" - eventMap.name = "effectiveOccupancy" - eventMap.value = EFFECTIVE_OCCUPANCY_MAP[descMap.value] } else { log.debug "descMap.inspect(): ${descMap.inspect()}" } @@ -455,9 +452,9 @@ def getRefreshCommands() { refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE) // formerly THERMOSTAT MODE: 0x001C refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) - refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, 0x0650) //occupancy command - refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_EFFECTIVE_OCCUPANCY) refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, OCCUPANCY) + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_OCCUPANCY) + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_EFFECTIVE_OCCUPANCY) refreshCommands += zigbee.readAttribute(THERMOSTAT_UI_CONFIGURATION_CLUSTER, TEMPERATURE_DISPLAY_MODE) refreshCommands @@ -485,7 +482,9 @@ def configure() { configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE, DataType.ENUM8, 1, 60, 1) configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED, DataType.ENUM8, 1, 60, 1) configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE, DataType.ENUM8, 1, 60, 1) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_EFFECTIVE_OCCUPANCY, DataType.ENUM8, 1, 60, 1) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, OCCUPANCY, DataType.ENUM8, 1, 60, null) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_OCCUPANCY, DataType.ENUM8, 1, 60, null) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_EFFECTIVE_OCCUPANCY, DataType.ENUM8, 1, 60, null) configurationCommands += zigbee.configureReporting(THERMOSTAT_UI_CONFIGURATION_CLUSTER, TEMPERATURE_DISPLAY_MODE, DataType.ENUM8, 1, 60, 1) configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_HUMIDITY, DataType.UINT16, 1, 60, 10) configurationCommands += zigbee.configureReporting(RELATIVE_HUMIDITY_CLUSTER, RELATIVE_HUMIDITY_MEASURED_VALUE, DataType.UINT16, 1, 60, 5) From c18a1b363cb7c09315868841ddf561eee5da2b06 Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Fri, 2 Oct 2020 12:32:10 +0200 Subject: [PATCH 07/20] small refactoring --- .../viconics-schneider-room-controller.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy index 3ee62d1820b..ab4c60a3c15 100644 --- a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -136,7 +136,7 @@ def installed() { private void createChildThermostat() { log.debug "Creating child thermostat to handle unoccupied cooling/heating setpoints" def label = "Unoccupied setpoints" - def childName = "${device.displayName} " + label + def childName = "${device.displayName} ${label}" def child = addChildDevice("Child Thermostat Setpoints", "${device.deviceNetworkId}:1", device.hubId, [completedSetup: true, label: childName, isComponent: true, componentName: "childSetpoints", componentLabel: label] @@ -172,7 +172,7 @@ def parse(String description) { eventMap = [:] def descMap = zigbee.parseDescriptionAsMap(description) - if ((descMap.clusterInt == THERMOSTAT_CLUSTER && descMap.attrId)) { + if (descMap.clusterInt == THERMOSTAT_CLUSTER && descMap.attrId) { def attributeInt = zigbee.convertHexToInt(descMap.attrId) if (attributeInt == OCCUPANCY || attributeInt == CUSTOM_EFFECTIVE_OCCUPANCY) { From 0d6a791cb7800916ac9fb122d72d44569ba243dd Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Mon, 5 Oct 2020 15:07:20 +0200 Subject: [PATCH 08/20] code refactoring --- .../viconics-schneider-room-controller.groovy | 347 +++++++++--------- 1 file changed, 164 insertions(+), 183 deletions(-) diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy index ab4c60a3c15..092b73a2707 100644 --- a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -23,7 +23,6 @@ metadata { capability "Occupancy Sensor" capability "Temperature Measurement" capability "Relative Humidity Measurement" - capability "Thermostat" capability "Thermostat Mode" capability "Fan Speed" capability "Thermostat Fan Mode" @@ -83,44 +82,59 @@ private getCUSTOM_FAN_MODE_CIRCULATE() { 0x02 } private getFAN_MODE_MAP() { [ - "00":"on", - "01":"auto", - "02":"circulate" + "00":"on", + "01":"auto", + "02":"circulate" + ] +} + +private getTHERMOSTAT_FAN_MODE_ATTRIBUTE_ID_MAP() { + [ + "on": 0x00, // CUSTOM_FAN_MODE_ON + "auto": 0x01, // CUSTOM_FAN_MODE_AUTO + "circulate": 0x02 // CUSTOM_FAN_MODE_CIRCULATE ] } private getTHERMOSTAT_MODE_MAP() { [ - "00":"off", - "01":"auto", - "02":"cool", - "03":"heat", - "04":"heat" + "00":"off", + "01":"auto", + "02":"cool", + "03":"heat", + "04":"heat" ] } private getTHERMOSTAT_OPERATING_STATE_MAP() { [ - "00":"idle", - "01":"cooling", - "02":"heating" + "00":"idle", + "01":"cooling", + "02":"heating" ] } private getEFFECTIVE_OCCUPANCY_MAP() { [ - "00":"Occupied", - "01":"Unoccupied", - "02":"Override", - "03":"Standby" + "00":"Occupied", + "01":"Unoccupied" + ] +} + +private getFAN_SPEED_SLIDER_MAP() { + [ + "00": 1, // low + "01": 2, // medium + "02": 3, // high + "03": 4 // auto ] } def installed() { log.debug "installed" - sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"]) + sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) - if (isViconicsVT8350()|| isSchneiderSE8350()) { + if (isViconicsVT8350() || isSchneiderSE8350()) { state.supportedFanModes = ["on", "auto"] } else { state.supportedFanModes = ["on", "auto", "circulate"] @@ -148,106 +162,99 @@ private void createChildThermostat() { } def parse(String description) { - def eventMap = zigbee.getEvent(description) - def result = [] - - if (description?.startsWith("humidity: ")) { - // Viconics VT8350 humidity reports are parsed as floating point numbers (range 0 - 1%) - def humidityVal = (description - "humidity: " - "%").trim() - if (humidityVal.isNumber()) { - humidityVal = new BigDecimal(humidityVal) * 100 - } - eventMap.name = "humidity" - eventMap.value = humidityVal - eventMap.unit = "%" - } - /*} else if (eventMap) { - log.debug "eventMap: ${eventMap.inspect()}" - // Viconics VT8350 humidity reports are parsed as floating point numbers (range 0 - 1%) - if (eventMap.name == "humidity") { - eventMap.value = eventMap.value * 100 - } - result = createEvent(eventMap) - }*/ else { - eventMap = [:] - def descMap = zigbee.parseDescriptionAsMap(description) - - if (descMap.clusterInt == THERMOSTAT_CLUSTER && descMap.attrId) { - def attributeInt = zigbee.convertHexToInt(descMap.attrId) - - if (attributeInt == OCCUPANCY || attributeInt == CUSTOM_EFFECTIVE_OCCUPANCY) { - log.debug "${attributeInt == OCCUPANCY ? "OCCUPANCY" : "EFFECTIVE OCCUPANCY"}, descMap.value: ${descMap.value}, attrId: ${attributeInt}" - eventMap.name = "occupancy" - eventMap.value = EFFECTIVE_OCCUPANCY_MAP[descMap.value] - } else if (attributeInt == COOLING_SETPOINT) { - log.debug "COOLING SETPOINT OCCUPIED, descMap.value: ${descMap.value}" - eventMap.name = "coolingSetpoint" - eventMap.value = getTemperature(descMap.value) - eventMap.unit = temperatureScale - } else if (attributeInt == HEATING_SETPOINT) { - log.debug "HEATING SETPOINT OCCUPIED, descMap.value: ${descMap.value}" - eventMap.name = "heatingSetpoint" - eventMap.value = getTemperature(descMap.value) - eventMap.unit = temperatureScale - } else if (attributeInt == COOLING_SETPOINT_UNOCCUPIED) { - log.debug "COOLING SETPOINT UNOCCUPIED, descMap.value: ${descMap.value}" - def childEvent = [:] - childEvent.name = "coolingSetpoint" - childEvent.value = getTemperature(descMap.value) - childEvent.unit = temperatureScale - sendEventToChild(UNOCCUPIED_SETPOINT_CHILD_DEVICE_ID, childEvent) - } else if (attributeInt == HEATING_SETPOINT_UNOCCUPIED) { - log.debug "HEATING SETPOINT UNOCCUPIED, descMap.value: ${descMap.value}" - def childEvent = [:] - childEvent.name = "heatingSetpoint" - childEvent.value = getTemperature(descMap.value) - childEvent.unit = temperatureScale - sendEventToChild(UNOCCUPIED_SETPOINT_CHILD_DEVICE_ID, childEvent) - } else if (attributeInt == LOCAL_TEMPERATURE) { - log.debug "LOCAL TEMPERATURE, descMap.value: ${descMap.value}" - eventMap.name = "temperature" - eventMap.value = getTemperature(descMap.value) - eventMap.unit = temperatureScale - } else if (attributeInt == CUSTOM_HUMIDITY) { - log.debug "CUSTOM HUMIDITY, descMap.value: ${descMap.value}" - eventMap.name = "humidity" - eventMap.value = Integer.parseInt(descMap.value, 16) - eventMap.unit = "%" - } else if (attributeInt == CUSTOM_FAN_MODE) { - log.debug "CUSTOM FAN MODE, descMap.value: ${descMap.value}" - if (isViconicsVT8650() || isSchneiderSE8650()) { - eventMap.name = "thermostatFanMode" - eventMap.value = FAN_MODE_MAP[descMap.value] - eventMap.data = [supportedThermostatFanModes: state.supportedFanModes] - } - } else if (attributeInt == CUSTOM_FAN_SPEED) { - // VT8350 reports fan speed 3 as AUTO - log.debug "CUSTOM FAN SPEED, descMap.value: ${descMap.value}" - def sliderValue = mapFanSpeedSliderValue(descMap.value) - if (sliderValue < 4) { - eventMap.name = "fanSpeed" - eventMap.value = sliderValue - result << createEvent([name:"thermostatFanMode", value: "on", data: [supportedThermostatFanModes: state.supportedFanModes]]) - } else { - result << createEvent([name:"thermostatFanMode", value: "auto", data:[supportedThermostatFanModes: state.supportedFanModes]]) - } - } else if (attributeInt == CUSTOM_THERMOSTAT_MODE) { - log.debug "CUSTOM THERMOSTAT MODE, descMap.value: ${descMap.value}" - eventMap.name = "thermostatMode" - eventMap.value = THERMOSTAT_MODE_MAP[descMap.value] - eventMap.data = [supportedThermostatModes: state.supportedThermostatModes] - } else if (attributeInt == CUSTOM_THERMOSTAT_OPERATING_STATE) { - log.debug "CUSTOM THERMOSTAT OPERATING STATE, descMap.value: ${descMap.value}" - eventMap.name = "thermostatOperatingState" - eventMap.value = THERMOSTAT_OPERATING_STATE_MAP[descMap.value] - } else { - log.debug "descMap.inspect(): ${descMap.inspect()}" - } - } - } + def result = [] + def eventMap = [:] + def descMap = zigbee.parseDescriptionAsMap(description) + + if (descMap.clusterInt == THERMOSTAT_CLUSTER && descMap.attrId) { + def attributeInt = zigbee.convertHexToInt(descMap.attrId) + + switch (attributeInt) { + case OCCUPANCY: + case CUSTOM_EFFECTIVE_OCCUPANCY: + log.debug "${attributeInt == OCCUPANCY ? "OCCUPANCY" : "EFFECTIVE OCCUPANCY"}, descMap.value: ${descMap.value}, attrId: ${attributeInt}" + eventMap.name = "occupancy" + eventMap.value = EFFECTIVE_OCCUPANCY_MAP[descMap.value] + break + case COOLING_SETPOINT: + log.debug "COOLING SETPOINT OCCUPIED, descMap.value: ${descMap.value}" + eventMap.name = "coolingSetpoint" + eventMap.value = getTemperature(descMap.value) + eventMap.unit = temperatureScale + break + case HEATING_SETPOINT: + log.debug "HEATING SETPOINT OCCUPIED, descMap.value: ${descMap.value}" + eventMap.name = "heatingSetpoint" + eventMap.value = getTemperature(descMap.value) + eventMap.unit = temperatureScale + break + case COOLING_SETPOINT_UNOCCUPIED: + log.debug "COOLING SETPOINT UNOCCUPIED, descMap.value: ${descMap.value}" + def childEvent = [:] + childEvent.name = "coolingSetpoint" + childEvent.value = getTemperature(descMap.value) + childEvent.unit = temperatureScale + sendEventToChild(UNOCCUPIED_SETPOINT_CHILD_DEVICE_ID, childEvent) + break + case HEATING_SETPOINT_UNOCCUPIED: + log.debug "HEATING SETPOINT UNOCCUPIED, descMap.value: ${descMap.value}" + def childEvent = [:] + childEvent.name = "heatingSetpoint" + childEvent.value = getTemperature(descMap.value) + childEvent.unit = temperatureScale + sendEventToChild(UNOCCUPIED_SETPOINT_CHILD_DEVICE_ID, childEvent) + break + case LOCAL_TEMPERATURE: + log.debug "LOCAL TEMPERATURE, descMap.value: ${descMap.value}" + eventMap.name = "temperature" + eventMap.value = getTemperature(descMap.value) + eventMap.unit = temperatureScale + break + case CUSTOM_HUMIDITY: + log.debug "CUSTOM HUMIDITY, descMap.value: ${descMap.value}" + eventMap.name = "humidity" + eventMap.value = Integer.parseInt(descMap.value, 16) + eventMap.unit = "%" + break + case CUSTOM_FAN_MODE: + log.debug "CUSTOM FAN MODE, descMap.value: ${descMap.value}" + if (isViconicsVT8650() || isSchneiderSE8650()) { + eventMap.name = "thermostatFanMode" + eventMap.value = FAN_MODE_MAP[descMap.value] + eventMap.data = [supportedThermostatFanModes: state.supportedFanModes] + } + break + case CUSTOM_FAN_SPEED: + // VT8350 reports fan speed 3 as AUTO + log.debug "CUSTOM FAN SPEED, descMap.value: ${descMap.value}" + def sliderValue = FAN_SPEED_SLIDER_MAP[descMap.value] + if (sliderValue < 4) { + eventMap.name = "fanSpeed" + eventMap.value = sliderValue + result << createEvent([name:"thermostatFanMode", value: "on", data: [supportedThermostatFanModes: state.supportedFanModes]]) + } else { + result << createEvent([name:"thermostatFanMode", value: "auto", data:[supportedThermostatFanModes: state.supportedFanModes]]) + } + break + case CUSTOM_THERMOSTAT_MODE: + log.debug "CUSTOM THERMOSTAT MODE, descMap.value: ${descMap.value}" + eventMap.name = "thermostatMode" + eventMap.value = THERMOSTAT_MODE_MAP[descMap.value] + eventMap.data = [supportedThermostatModes: state.supportedThermostatModes] + break + case CUSTOM_THERMOSTAT_OPERATING_STATE: + log.debug "CUSTOM THERMOSTAT OPERATING STATE, descMap.value: ${descMap.value}" + eventMap.name = "thermostatOperatingState" + eventMap.value = THERMOSTAT_OPERATING_STATE_MAP[descMap.value] + break + default: + log.debug "UNHANDLED ATTRIBUTE, descMap.inspect(): ${descMap.inspect()}" + + } + } result << createEvent(eventMap) //log.debug "Description ${description} parsed to ${result}" - return result + result } private sendEventToChild(childNumber, event) { @@ -288,7 +295,7 @@ def setChildHeatingSetpoint(deviceNetworkId, degrees) { def getChildId(deviceNetworkId) { def split = deviceNetworkId?.split(":") - return (split.length > 1) ? split[1] as Integer : null + (split.length > 1) ? split[1] as Integer : null } def setSetpoint(degrees, setpointAttr) { @@ -317,17 +324,7 @@ def setThermostatFanMode(mode) { break } } else if (isViconicsVT8650() || isSchneiderSE8650()) { - switch (mode) { - case "on": - getThermostatFanModeCommands(CUSTOM_FAN_MODE_ON) - break - case "auto": - getThermostatFanModeCommands(CUSTOM_FAN_MODE_AUTO) - break - case "circulate": - getThermostatFanModeCommands(CUSTOM_FAN_MODE_CIRCULATE) - break - } + getThermostatFanModeCommands(THERMOSTAT_FAN_MODE_ATTRIBUTE_ID_MAP[mode]) } } else { log.debug "Unsupported fan mode $mode" @@ -393,41 +390,35 @@ def setThermostatMode(mode) { } } -def off() { - delayBetween([ - zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, THERMOSTAT_MODE_OFF), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) - ], 500) -} - def auto() { - delayBetween([ - zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, THERMOSTAT_MODE_AUTO), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) - ], 500) + getThermostatModeCommands(THERMOSTAT_MODE_AUTO) } def cool() { - delayBetween([ - zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, THERMOSTAT_MODE_COOL), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) - ], 500) + getThermostatModeCommands(THERMOSTAT_MODE_COOL) } def heat() { - delayBetween([ - zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, THERMOSTAT_MODE_HEAT), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) - ], 500) + getThermostatModeCommands(THERMOSTAT_MODE_HEAT) +} + +def off() { + getThermostatModeCommands(THERMOSTAT_MODE_OFF) +} + +def getThermostatModeCommands(mode) { + if (mode) { + delayBetween([ + zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, mode), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) + ], 500) + } } def ping() { log.debug "ping" - refresh() + zigbee.readAttribute(THERMOSTAT_CLUSTER, LOCAL_TEMPERATURE) } def refresh() { @@ -444,8 +435,6 @@ def getRefreshCommands() { refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, LOCAL_TEMPERATURE) refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, COOLING_SETPOINT) refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, HEATING_SETPOINT) - refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, COOLING_SETPOINT_UNOCCUPIED) - refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, HEATING_SETPOINT_UNOCCUPIED) refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED) refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE) @@ -457,15 +446,24 @@ def getRefreshCommands() { refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_EFFECTIVE_OCCUPANCY) refreshCommands += zigbee.readAttribute(THERMOSTAT_UI_CONFIGURATION_CLUSTER, TEMPERATURE_DISPLAY_MODE) + refreshCommands += refreshChild() + + refreshCommands +} + +def refreshChild() { + log.debug "refresh child device" + def refreshCommands = [] + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, COOLING_SETPOINT_UNOCCUPIED) + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, HEATING_SETPOINT_UNOCCUPIED) + refreshCommands } def configure() { log.debug "Configuration" - if (!childDevices) { - createChildThermostat() - } + configureChild() def configurationCommands = [] // todo: check if following binding is necessary here @@ -482,7 +480,7 @@ def configure() { configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE, DataType.ENUM8, 1, 60, 1) configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED, DataType.ENUM8, 1, 60, 1) configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE, DataType.ENUM8, 1, 60, 1) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, OCCUPANCY, DataType.ENUM8, 1, 60, null) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, OCCUPANCY, DataType.ENUM8, 1, 60, null) configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_OCCUPANCY, DataType.ENUM8, 1, 60, null) configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_EFFECTIVE_OCCUPANCY, DataType.ENUM8, 1, 60, null) configurationCommands += zigbee.configureReporting(THERMOSTAT_UI_CONFIGURATION_CLUSTER, TEMPERATURE_DISPLAY_MODE, DataType.ENUM8, 1, 60, 1) @@ -492,6 +490,12 @@ def configure() { delayBetween(getRefreshCommands()+configurationCommands) } +def configureChild() { + if (!childDevices) { + createChildThermostat() + } +} + def getCoolingSetpointRange() { (getTemperatureScale() == "C") ? [12, 37.5] : [54, 100] } @@ -503,36 +507,13 @@ def getTemperature(value) { if (value != null) { def celsius = Integer.parseInt(value, 16) / 100 if (temperatureScale == "C") { - return celsius//Math.round(celsius) + celsius//Math.round(celsius) } else { - return celsiusToFahrenheit(celsius)//Math.round(celsiusToFahrenheit(celsius)) + celsiusToFahrenheit(celsius)//Math.round(celsiusToFahrenheit(celsius)) } } } -def mapFanSpeedSliderValue(rawValue) { - log.debug "mapFanSpeedSliderValue: ${rawValue}" - //maps current fan value to the Fan Speed slider - def resultValue - - switch(rawValue) { - case "00": // low - resultValue = 1 - break - case "01": // medium - resultValue = 2 - break - case "02": // high - resultValue = 3 - break - case "03": // auto - default: - resultValue = 4 - break - } - resultValue -} - def isViconicsVT8350() { device.getDataValue("model") == "254-143" // Viconics VT8350 Low Voltage Fan Coil Controller and Zone Controller } From 43f4505bdc31846f5dc463bc93cdfb2c50e19002 Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Mon, 5 Oct 2020 15:34:05 +0200 Subject: [PATCH 09/20] reverted capability Thermostat --- .../viconics-schneider-room-controller.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy index 092b73a2707..b2ab5395938 100644 --- a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -23,6 +23,7 @@ metadata { capability "Occupancy Sensor" capability "Temperature Measurement" capability "Relative Humidity Measurement" + capability "Thermostat" capability "Thermostat Mode" capability "Fan Speed" capability "Thermostat Fan Mode" From 1d3df1cdc5817ef9b5e3be964dcc6c795648437a Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Fri, 9 Oct 2020 14:33:41 +0200 Subject: [PATCH 10/20] Removed configuration from child dth. --- .../child-thermostat-setpoints.groovy | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/devicetypes/smartthings/child-thermostat-setpoints.src/child-thermostat-setpoints.groovy b/devicetypes/smartthings/child-thermostat-setpoints.src/child-thermostat-setpoints.groovy index baad415689c..b8838d6cf34 100644 --- a/devicetypes/smartthings/child-thermostat-setpoints.src/child-thermostat-setpoints.groovy +++ b/devicetypes/smartthings/child-thermostat-setpoints.src/child-thermostat-setpoints.groovy @@ -17,7 +17,6 @@ metadata { definition(name: "Child Thermostat Setpoints", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "oic.d.thermostat") { capability "Actuator" - capability "Configuration" capability "Health Check" capability "Refresh" capability "Thermostat Cooling Setpoint" @@ -35,19 +34,6 @@ def setHeatingSetpoint(setpoint) { parent.setChildHeatingSetpoint(device.deviceNetworkId, setpoint) } -def installed() { - configure() -} - -def updated() { - configure() -} - -def configure() { - parent.configureChild() - refresh() -} - def ping() { refresh() } From 73bddc7a87b1e7d82eed532867ab6d9637b15c52 Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Mon, 12 Oct 2020 14:36:02 +0200 Subject: [PATCH 11/20] Configure setpoint values initially. Round incoming setpoint values to nearest half. Code refactoring. --- .../viconics-schneider-room-controller.groovy | 44 +++++++++++++------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy index b2ab5395938..519f579d37a 100644 --- a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -23,7 +23,6 @@ metadata { capability "Occupancy Sensor" capability "Temperature Measurement" capability "Relative Humidity Measurement" - capability "Thermostat" capability "Thermostat Mode" capability "Fan Speed" capability "Thermostat Fan Mode" @@ -180,20 +179,20 @@ def parse(String description) { case COOLING_SETPOINT: log.debug "COOLING SETPOINT OCCUPIED, descMap.value: ${descMap.value}" eventMap.name = "coolingSetpoint" - eventMap.value = getTemperature(descMap.value) + eventMap.value = getTemperature(descMap.value, true) eventMap.unit = temperatureScale break case HEATING_SETPOINT: log.debug "HEATING SETPOINT OCCUPIED, descMap.value: ${descMap.value}" eventMap.name = "heatingSetpoint" - eventMap.value = getTemperature(descMap.value) + eventMap.value = getTemperature(descMap.value, true) eventMap.unit = temperatureScale break case COOLING_SETPOINT_UNOCCUPIED: log.debug "COOLING SETPOINT UNOCCUPIED, descMap.value: ${descMap.value}" def childEvent = [:] childEvent.name = "coolingSetpoint" - childEvent.value = getTemperature(descMap.value) + childEvent.value = getTemperature(descMap.value, true) childEvent.unit = temperatureScale sendEventToChild(UNOCCUPIED_SETPOINT_CHILD_DEVICE_ID, childEvent) break @@ -201,7 +200,7 @@ def parse(String description) { log.debug "HEATING SETPOINT UNOCCUPIED, descMap.value: ${descMap.value}" def childEvent = [:] childEvent.name = "heatingSetpoint" - childEvent.value = getTemperature(descMap.value) + childEvent.value = getTemperature(descMap.value, true) childEvent.unit = temperatureScale sendEventToChild(UNOCCUPIED_SETPOINT_CHILD_DEVICE_ID, childEvent) break @@ -279,8 +278,8 @@ def setHeatingSetpoint(degrees) { def setChildCoolingSetpoint(deviceNetworkId, degrees) { log.debug "deviceNetworkId: ${deviceNetworkId} degrees: ${degrees}" - def switchId = getChildId(deviceNetworkId) - if (switchId != null) { + def childId = getChildId(deviceNetworkId) + if (childId != null) { setSetpoint(degrees, COOLING_SETPOINT_UNOCCUPIED) } } @@ -288,8 +287,8 @@ def setChildCoolingSetpoint(deviceNetworkId, degrees) { def setChildHeatingSetpoint(deviceNetworkId, degrees) { log.debug "deviceNetworkId: ${deviceNetworkId} degrees: ${degrees}" - def switchId = getChildId(deviceNetworkId) - if (switchId != null) { + def childId = getChildId(deviceNetworkId) + if (childId != null) { setSetpoint(degrees, HEATING_SETPOINT_UNOCCUPIED) } } @@ -467,9 +466,12 @@ def configure() { configureChild() def configurationCommands = [] - // todo: check if following binding is necessary here - configurationCommands += "zdo bind 0x${device.deviceNetworkId} 1 0xA 0x201 {${device.zigbeeId}} {}" - configurationCommands += "zdo bind 0x${device.deviceNetworkId} 1 0xA 0x405 {${device.zigbeeId}} {}" + + // set initial values + configurationCommands += setSetpoint(initialCoolingSetpoint, COOLING_SETPOINT) + configurationCommands += setSetpoint(initialHeatingSetpoint, HEATING_SETPOINT) + configurationCommands += setSetpoint(initialCoolingSetpoint, COOLING_SETPOINT_UNOCCUPIED) + configurationCommands += setSetpoint(initialHeatingSetpoint, HEATING_SETPOINT_UNOCCUPIED) configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, 1, 60, 1) //formerly THERMOSTAT MODE: 0x001C configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, LOCAL_TEMPERATURE, DataType.INT16, 10, 60, 10) @@ -504,9 +506,21 @@ def getHeatingSetpointRange() { (getTemperatureScale() == "C") ? [4.5, 32] : [40, 90] } -def getTemperature(value) { +def getInitialCoolingSetpoint() { + (getTemperatureScale() == "C") ? 28 : 86 +} + +def getInitialHeatingSetpoint() { + (getTemperatureScale() == "C") ? 20 : 68 +} + +def getTemperature(value, roundValue = false) { if (value != null) { def celsius = Integer.parseInt(value, 16) / 100 + if(roundValue) { + celsius = roundToTheNearestHalf(value) + } + if (temperatureScale == "C") { celsius//Math.round(celsius) } else { @@ -515,6 +529,10 @@ def getTemperature(value) { } } +def roundToTheNearestHalf(value) { + Math.round(value * 2) / 2 +} + def isViconicsVT8350() { device.getDataValue("model") == "254-143" // Viconics VT8350 Low Voltage Fan Coil Controller and Zone Controller } From 00e9e08526bfcdd459704c085f3f1a46361ef641 Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Mon, 12 Oct 2020 14:44:45 +0200 Subject: [PATCH 12/20] Remove blank line --- .../viconics-schneider-room-controller.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy index 519f579d37a..9e0f07a2d17 100644 --- a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -249,7 +249,6 @@ def parse(String description) { break default: log.debug "UNHANDLED ATTRIBUTE, descMap.inspect(): ${descMap.inspect()}" - } } result << createEvent(eventMap) From 0ac614ddaa8975f43bd425d5bff9f97c9760f29c Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Mon, 12 Oct 2020 14:48:10 +0200 Subject: [PATCH 13/20] fix --- .../viconics-schneider-room-controller.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy index 9e0f07a2d17..b8c0e73e7b4 100644 --- a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -517,7 +517,7 @@ def getTemperature(value, roundValue = false) { if (value != null) { def celsius = Integer.parseInt(value, 16) / 100 if(roundValue) { - celsius = roundToTheNearestHalf(value) + celsius = roundToTheNearestHalf(celsius) } if (temperatureScale == "C") { From f480e0b8a3a5f8bc0611b505aac20a61db206326 Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Mon, 12 Oct 2020 15:01:55 +0200 Subject: [PATCH 14/20] fix --- .../viconics-schneider-room-controller.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy index b8c0e73e7b4..44429a64d8b 100644 --- a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -489,7 +489,7 @@ def configure() { configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_HUMIDITY, DataType.UINT16, 1, 60, 10) configurationCommands += zigbee.configureReporting(RELATIVE_HUMIDITY_CLUSTER, RELATIVE_HUMIDITY_MEASURED_VALUE, DataType.UINT16, 1, 60, 5) - delayBetween(getRefreshCommands()+configurationCommands) + delayBetween(getRefreshCommands() + configurationCommands) } def configureChild() { From 1f8bcc3dd9e640c5050d5a5a1f1bfe7bf8c02eb3 Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Tue, 13 Oct 2020 11:50:57 +0200 Subject: [PATCH 15/20] Change max reporting interval to 3600 seconds. Clean up the code. --- .../viconics-schneider-room-controller.groovy | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy index 44429a64d8b..e3141ba26e4 100644 --- a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -305,8 +305,8 @@ def setSetpoint(degrees, setpointAttr) { celsius = (celsius as Double).round(2) delayBetween([ - zigbee.writeAttribute(THERMOSTAT_CLUSTER, setpointAttr, DataType.INT16, zigbee.convertToHexString(celsius * 100)), - zigbee.readAttribute(THERMOSTAT_CLUSTER, setpointAttr) + zigbee.writeAttribute(THERMOSTAT_CLUSTER, setpointAttr, DataType.INT16, zigbee.convertToHexString(celsius * 100)), + zigbee.readAttribute(THERMOSTAT_CLUSTER, setpointAttr) ], 500) } } @@ -345,8 +345,8 @@ def fanCirculate() { def getThermostatFanModeCommands(mode) { if (mode) { delayBetween([ - zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE, DataType.ENUM8, mode), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE) + zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE, DataType.ENUM8, mode), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE) ], 500) } } @@ -360,10 +360,10 @@ def setFanSpeed(speed) { speed = speed - 1 } delayBetween([ - zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED, DataType.ENUM8, speed), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) + zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED, DataType.ENUM8, speed), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) ], 500) } @@ -408,9 +408,9 @@ def off() { def getThermostatModeCommands(mode) { if (mode) { delayBetween([ - zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, mode), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE), - zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) + zigbee.writeAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, mode), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE), + zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) ], 500) } } @@ -472,22 +472,22 @@ def configure() { configurationCommands += setSetpoint(initialCoolingSetpoint, COOLING_SETPOINT_UNOCCUPIED) configurationCommands += setSetpoint(initialHeatingSetpoint, HEATING_SETPOINT_UNOCCUPIED) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, 1, 60, 1) //formerly THERMOSTAT MODE: 0x001C - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, LOCAL_TEMPERATURE, DataType.INT16, 10, 60, 10) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, COOLING_SETPOINT, DataType.INT16, 1, 60, 10) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, HEATING_SETPOINT, DataType.INT16, 1, 60, 10) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, COOLING_SETPOINT_UNOCCUPIED, DataType.INT16, 1, 60, 10) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, HEATING_SETPOINT_UNOCCUPIED, DataType.INT16, 1, 60, 10) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE, DataType.ENUM8, 1, 3600, 1) //formerly THERMOSTAT MODE: 0x001C + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, LOCAL_TEMPERATURE, DataType.INT16, 10, 3600, 10) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, COOLING_SETPOINT, DataType.INT16, 1, 3600, 10) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, HEATING_SETPOINT, DataType.INT16, 1, 3600, 10) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, COOLING_SETPOINT_UNOCCUPIED, DataType.INT16, 1, 3600, 10) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, HEATING_SETPOINT_UNOCCUPIED, DataType.INT16, 1, 3600, 10) //configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, 0x0A58, 0x10, 1, 300, 1) //GFan - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE, DataType.ENUM8, 1, 60, 1) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED, DataType.ENUM8, 1, 60, 1) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE, DataType.ENUM8, 1, 60, 1) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, OCCUPANCY, DataType.ENUM8, 1, 60, null) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_OCCUPANCY, DataType.ENUM8, 1, 60, null) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_EFFECTIVE_OCCUPANCY, DataType.ENUM8, 1, 60, null) - configurationCommands += zigbee.configureReporting(THERMOSTAT_UI_CONFIGURATION_CLUSTER, TEMPERATURE_DISPLAY_MODE, DataType.ENUM8, 1, 60, 1) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_HUMIDITY, DataType.UINT16, 1, 60, 10) - configurationCommands += zigbee.configureReporting(RELATIVE_HUMIDITY_CLUSTER, RELATIVE_HUMIDITY_MEASURED_VALUE, DataType.UINT16, 1, 60, 5) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE, DataType.ENUM8, 1, 3600, 1) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED, DataType.ENUM8, 1, 3600, 1) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE, DataType.ENUM8, 1, 3600, 1) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, OCCUPANCY, DataType.ENUM8, 1, 3600, null) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_OCCUPANCY, DataType.ENUM8, 1, 3600, null) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_EFFECTIVE_OCCUPANCY, DataType.ENUM8, 1, 3600, null) + configurationCommands += zigbee.configureReporting(THERMOSTAT_UI_CONFIGURATION_CLUSTER, TEMPERATURE_DISPLAY_MODE, DataType.ENUM8, 1, 3600, 1) + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_HUMIDITY, DataType.UINT16, 1, 3600, 10) + configurationCommands += zigbee.configureReporting(RELATIVE_HUMIDITY_CLUSTER, RELATIVE_HUMIDITY_MEASURED_VALUE, DataType.UINT16, 1, 3600, 5) delayBetween(getRefreshCommands() + configurationCommands) } @@ -521,9 +521,9 @@ def getTemperature(value, roundValue = false) { } if (temperatureScale == "C") { - celsius//Math.round(celsius) + celsius } else { - celsiusToFahrenheit(celsius)//Math.round(celsiusToFahrenheit(celsius)) + celsiusToFahrenheit(celsius) } } } From e730a833df814b23bcc83ce8c590c684feb80c58 Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Tue, 13 Oct 2020 15:09:46 +0200 Subject: [PATCH 16/20] Code refactoring. Removed unnecessary configuration commands. --- .../viconics-schneider-room-controller.groovy | 61 ++++++++++--------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy index e3141ba26e4..52d788f1d24 100644 --- a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -54,8 +54,6 @@ metadata { private getTHERMOSTAT_CLUSTER() { 0x0201 } private getTHERMOSTAT_UI_CONFIGURATION_CLUSTER() { 0x0204 } -private getRELATIVE_HUMIDITY_CLUSTER() {0x405} -private getRELATIVE_HUMIDITY_MEASURED_VALUE() {0x0000} private getTEMPERATURE_DISPLAY_MODE() {0x0000} private getLOCAL_TEMPERATURE() {0x0000} private getCOOLING_SETPOINT() { 0x0011 } @@ -78,55 +76,64 @@ private getTHERMOSTAT_MODE_HEAT() { 0x03 } private getCUSTOM_FAN_MODE_ON() { 0x00 } private getCUSTOM_FAN_MODE_AUTO() { 0x01 } private getCUSTOM_FAN_MODE_CIRCULATE() { 0x02 } +private getOPERATING_STATE_IDLE() { 0x00 } +private getOPERATING_STATE_COOLING() { 0x01 } +private getOPERATING_STATE_HEATING() { 0x02 } +private getOCCUPANCY_OCCUPIED() { 0x00 } +private getOCCUPANCY_UNOCCUPIED() { 0x01 } +private getFAN_SPEED_LOW() { 0x00 } +private getFAN_SPEED_MEDIUM() { 0x01 } +private getFAN_SPEED_HIGH() { 0x02 } +private getFAN_SPEED_AUTO() { 0x03 } private getFAN_MODE_MAP() { [ - "00":"on", - "01":"auto", - "02":"circulate" + (CUSTOM_FAN_MODE_ON):"on", + (CUSTOM_FAN_MODE_AUTO):"auto", + (CUSTOM_FAN_MODE_CIRCULATE):"circulate" ] } private getTHERMOSTAT_FAN_MODE_ATTRIBUTE_ID_MAP() { [ - "on": 0x00, // CUSTOM_FAN_MODE_ON - "auto": 0x01, // CUSTOM_FAN_MODE_AUTO - "circulate": 0x02 // CUSTOM_FAN_MODE_CIRCULATE + "on": (CUSTOM_FAN_MODE_ON), + "auto": (CUSTOM_FAN_MODE_AUTO), + "circulate": (CUSTOM_FAN_MODE_CIRCULATE) ] } private getTHERMOSTAT_MODE_MAP() { [ - "00":"off", - "01":"auto", - "02":"cool", - "03":"heat", + (THERMOSTAT_MODE_OFF):"off", + (THERMOSTAT_MODE_AUTO):"auto", + (THERMOSTAT_MODE_COOL):"cool", + (THERMOSTAT_MODE_HEAT):"heat", "04":"heat" ] } private getTHERMOSTAT_OPERATING_STATE_MAP() { [ - "00":"idle", - "01":"cooling", - "02":"heating" + (OPERATING_STATE_IDLE):"idle", + (OPERATING_STATE_COOLING):"cooling", + (OPERATING_STATE_HEATING):"heating" ] } private getEFFECTIVE_OCCUPANCY_MAP() { [ - "00":"Occupied", - "01":"Unoccupied" + (OCCUPANCY_OCCUPIED):"Occupied", + (OCCUPANCY_UNOCCUPIED):"Unoccupied" ] } private getFAN_SPEED_SLIDER_MAP() { [ - "00": 1, // low - "01": 2, // medium - "02": 3, // high - "03": 4 // auto + (FAN_SPEED_LOW): 1, + (FAN_SPEED_MEDIUM): 2, + (FAN_SPEED_HIGH): 3, + (FAN_SPEED_AUTO): 4 ] } @@ -174,7 +181,7 @@ def parse(String description) { case CUSTOM_EFFECTIVE_OCCUPANCY: log.debug "${attributeInt == OCCUPANCY ? "OCCUPANCY" : "EFFECTIVE OCCUPANCY"}, descMap.value: ${descMap.value}, attrId: ${attributeInt}" eventMap.name = "occupancy" - eventMap.value = EFFECTIVE_OCCUPANCY_MAP[descMap.value] + eventMap.value = EFFECTIVE_OCCUPANCY_MAP[Integer.parseInt(descMap.value, 16)] break case COOLING_SETPOINT: log.debug "COOLING SETPOINT OCCUPIED, descMap.value: ${descMap.value}" @@ -220,14 +227,14 @@ def parse(String description) { log.debug "CUSTOM FAN MODE, descMap.value: ${descMap.value}" if (isViconicsVT8650() || isSchneiderSE8650()) { eventMap.name = "thermostatFanMode" - eventMap.value = FAN_MODE_MAP[descMap.value] + eventMap.value = FAN_MODE_MAP[Integer.parseInt(descMap.value, 16)] eventMap.data = [supportedThermostatFanModes: state.supportedFanModes] } break case CUSTOM_FAN_SPEED: // VT8350 reports fan speed 3 as AUTO log.debug "CUSTOM FAN SPEED, descMap.value: ${descMap.value}" - def sliderValue = FAN_SPEED_SLIDER_MAP[descMap.value] + def sliderValue = FAN_SPEED_SLIDER_MAP[Integer.parseInt(descMap.value, 16)] if (sliderValue < 4) { eventMap.name = "fanSpeed" eventMap.value = sliderValue @@ -239,13 +246,13 @@ def parse(String description) { case CUSTOM_THERMOSTAT_MODE: log.debug "CUSTOM THERMOSTAT MODE, descMap.value: ${descMap.value}" eventMap.name = "thermostatMode" - eventMap.value = THERMOSTAT_MODE_MAP[descMap.value] + eventMap.value = THERMOSTAT_MODE_MAP[Integer.parseInt(descMap.value, 16)] eventMap.data = [supportedThermostatModes: state.supportedThermostatModes] break case CUSTOM_THERMOSTAT_OPERATING_STATE: log.debug "CUSTOM THERMOSTAT OPERATING STATE, descMap.value: ${descMap.value}" eventMap.name = "thermostatOperatingState" - eventMap.value = THERMOSTAT_OPERATING_STATE_MAP[descMap.value] + eventMap.value = THERMOSTAT_OPERATING_STATE_MAP[Integer.parseInt(descMap.value, 16)] break default: log.debug "UNHANDLED ATTRIBUTE, descMap.inspect(): ${descMap.inspect()}" @@ -428,7 +435,6 @@ def refresh() { def getRefreshCommands() { def refreshCommands = [] - refreshCommands += zigbee.readAttribute(RELATIVE_HUMIDITY_CLUSTER, RELATIVE_HUMIDITY_MEASURED_VALUE) refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_HUMIDITY) refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, LOCAL_TEMPERATURE) @@ -487,7 +493,6 @@ def configure() { configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_EFFECTIVE_OCCUPANCY, DataType.ENUM8, 1, 3600, null) configurationCommands += zigbee.configureReporting(THERMOSTAT_UI_CONFIGURATION_CLUSTER, TEMPERATURE_DISPLAY_MODE, DataType.ENUM8, 1, 3600, 1) configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_HUMIDITY, DataType.UINT16, 1, 3600, 10) - configurationCommands += zigbee.configureReporting(RELATIVE_HUMIDITY_CLUSTER, RELATIVE_HUMIDITY_MEASURED_VALUE, DataType.UINT16, 1, 3600, 5) delayBetween(getRefreshCommands() + configurationCommands) } From 65175198ba488c662f95fbd16788b76aa8e60e8f Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Wed, 14 Oct 2020 15:18:47 +0200 Subject: [PATCH 17/20] Fix, code refactoring. --- .../viconics-schneider-room-controller.groovy | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy index 52d788f1d24..380625df2c4 100644 --- a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -81,11 +81,6 @@ private getOPERATING_STATE_COOLING() { 0x01 } private getOPERATING_STATE_HEATING() { 0x02 } private getOCCUPANCY_OCCUPIED() { 0x00 } private getOCCUPANCY_UNOCCUPIED() { 0x01 } -private getFAN_SPEED_LOW() { 0x00 } -private getFAN_SPEED_MEDIUM() { 0x01 } -private getFAN_SPEED_HIGH() { 0x02 } -private getFAN_SPEED_AUTO() { 0x03 } - private getFAN_MODE_MAP() { [ @@ -123,17 +118,8 @@ private getTHERMOSTAT_OPERATING_STATE_MAP() { private getEFFECTIVE_OCCUPANCY_MAP() { [ - (OCCUPANCY_OCCUPIED):"Occupied", - (OCCUPANCY_UNOCCUPIED):"Unoccupied" - ] -} - -private getFAN_SPEED_SLIDER_MAP() { - [ - (FAN_SPEED_LOW): 1, - (FAN_SPEED_MEDIUM): 2, - (FAN_SPEED_HIGH): 3, - (FAN_SPEED_AUTO): 4 + (OCCUPANCY_OCCUPIED):"occupied", + (OCCUPANCY_UNOCCUPIED):"unoccupied" ] } @@ -173,13 +159,11 @@ def parse(String description) { def eventMap = [:] def descMap = zigbee.parseDescriptionAsMap(description) - if (descMap.clusterInt == THERMOSTAT_CLUSTER && descMap.attrId) { - def attributeInt = zigbee.convertHexToInt(descMap.attrId) - - switch (attributeInt) { + if (descMap.clusterInt == THERMOSTAT_CLUSTER && descMap.attrInt) { + switch (descMap.attrInt) { case OCCUPANCY: case CUSTOM_EFFECTIVE_OCCUPANCY: - log.debug "${attributeInt == OCCUPANCY ? "OCCUPANCY" : "EFFECTIVE OCCUPANCY"}, descMap.value: ${descMap.value}, attrId: ${attributeInt}" + log.debug "${descMap.attrInt == OCCUPANCY ? "OCCUPANCY" : "EFFECTIVE OCCUPANCY"}, descMap.value: ${descMap.value}, descMap.attrInt: ${descMap.attrInt}" eventMap.name = "occupancy" eventMap.value = EFFECTIVE_OCCUPANCY_MAP[Integer.parseInt(descMap.value, 16)] break @@ -234,7 +218,8 @@ def parse(String description) { case CUSTOM_FAN_SPEED: // VT8350 reports fan speed 3 as AUTO log.debug "CUSTOM FAN SPEED, descMap.value: ${descMap.value}" - def sliderValue = FAN_SPEED_SLIDER_MAP[Integer.parseInt(descMap.value, 16)] + // the device reports values of range 0-3 (0 is LOW) + def sliderValue = Integer.parseInt(descMap.value, 16) + 1 if (sliderValue < 4) { eventMap.name = "fanSpeed" eventMap.value = sliderValue @@ -441,7 +426,9 @@ def getRefreshCommands() { refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, COOLING_SETPOINT) refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, HEATING_SETPOINT) - refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED) + if (supportsFanSpeed()) { + refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED) + } refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE) refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_MODE) // formerly THERMOSTAT MODE: 0x001C refreshCommands += zigbee.readAttribute(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE) @@ -485,8 +472,10 @@ def configure() { configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, COOLING_SETPOINT_UNOCCUPIED, DataType.INT16, 1, 3600, 10) configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, HEATING_SETPOINT_UNOCCUPIED, DataType.INT16, 1, 3600, 10) //configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, 0x0A58, 0x10, 1, 300, 1) //GFan + if (supportsFanSpeed()) { + configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED, DataType.ENUM8, 1, 3600, 1) + } configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_FAN_MODE, DataType.ENUM8, 1, 3600, 1) - configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_FAN_SPEED, DataType.ENUM8, 1, 3600, 1) configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_THERMOSTAT_OPERATING_STATE, DataType.ENUM8, 1, 3600, 1) configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, OCCUPANCY, DataType.ENUM8, 1, 3600, null) configurationCommands += zigbee.configureReporting(THERMOSTAT_CLUSTER, CUSTOM_OCCUPANCY, DataType.ENUM8, 1, 3600, null) @@ -537,6 +526,10 @@ def roundToTheNearestHalf(value) { Math.round(value * 2) / 2 } +def supportsFanSpeed() { + isViconicsVT8350() || isSchneiderSE8350() +} + def isViconicsVT8350() { device.getDataValue("model") == "254-143" // Viconics VT8350 Low Voltage Fan Coil Controller and Zone Controller } From 80f1d63f519e472fbfa95f1dceece4f116fe8f6b Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Wed, 14 Oct 2020 15:54:57 +0200 Subject: [PATCH 18/20] Fix --- .../viconics-schneider-room-controller.groovy | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy index 380625df2c4..654bfadc264 100644 --- a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -159,11 +159,13 @@ def parse(String description) { def eventMap = [:] def descMap = zigbee.parseDescriptionAsMap(description) - if (descMap.clusterInt == THERMOSTAT_CLUSTER && descMap.attrInt) { - switch (descMap.attrInt) { + if (descMap.clusterInt == THERMOSTAT_CLUSTER && descMap.attrId) { + def attributeInt = zigbee.convertHexToInt(descMap.attrId) + + switch (attributeInt) { case OCCUPANCY: case CUSTOM_EFFECTIVE_OCCUPANCY: - log.debug "${descMap.attrInt == OCCUPANCY ? "OCCUPANCY" : "EFFECTIVE OCCUPANCY"}, descMap.value: ${descMap.value}, descMap.attrInt: ${descMap.attrInt}" + log.debug "${attributeInt == OCCUPANCY ? "OCCUPANCY" : "EFFECTIVE OCCUPANCY"}, descMap.value: ${descMap.value}, attrId: ${attributeInt}" eventMap.name = "occupancy" eventMap.value = EFFECTIVE_OCCUPANCY_MAP[Integer.parseInt(descMap.value, 16)] break From 1fcf4d19bd75d29589c8680803d47628012a3c87 Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Thu, 15 Oct 2020 11:49:45 +0200 Subject: [PATCH 19/20] Fix --- .../viconics-schneider-room-controller.groovy | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy index 654bfadc264..380625df2c4 100644 --- a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -159,13 +159,11 @@ def parse(String description) { def eventMap = [:] def descMap = zigbee.parseDescriptionAsMap(description) - if (descMap.clusterInt == THERMOSTAT_CLUSTER && descMap.attrId) { - def attributeInt = zigbee.convertHexToInt(descMap.attrId) - - switch (attributeInt) { + if (descMap.clusterInt == THERMOSTAT_CLUSTER && descMap.attrInt) { + switch (descMap.attrInt) { case OCCUPANCY: case CUSTOM_EFFECTIVE_OCCUPANCY: - log.debug "${attributeInt == OCCUPANCY ? "OCCUPANCY" : "EFFECTIVE OCCUPANCY"}, descMap.value: ${descMap.value}, attrId: ${attributeInt}" + log.debug "${descMap.attrInt == OCCUPANCY ? "OCCUPANCY" : "EFFECTIVE OCCUPANCY"}, descMap.value: ${descMap.value}, descMap.attrInt: ${descMap.attrInt}" eventMap.name = "occupancy" eventMap.value = EFFECTIVE_OCCUPANCY_MAP[Integer.parseInt(descMap.value, 16)] break From 3b03cd576e772ff2b4e135ee1cdd6a3b5ee2b761 Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Wed, 21 Oct 2020 16:40:41 +0200 Subject: [PATCH 20/20] Removes unnecessary value from thermostat mode map. Fixes temperature reporting. --- .../viconics-schneider-room-controller.groovy | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy index 380625df2c4..479e2ea4985 100644 --- a/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy +++ b/devicetypes/smartthings/viconics-schneider-room-controller.src/viconics-schneider-room-controller.groovy @@ -103,8 +103,7 @@ private getTHERMOSTAT_MODE_MAP() { (THERMOSTAT_MODE_OFF):"off", (THERMOSTAT_MODE_AUTO):"auto", (THERMOSTAT_MODE_COOL):"cool", - (THERMOSTAT_MODE_HEAT):"heat", - "04":"heat" + (THERMOSTAT_MODE_HEAT):"heat" ] } @@ -159,7 +158,7 @@ def parse(String description) { def eventMap = [:] def descMap = zigbee.parseDescriptionAsMap(description) - if (descMap.clusterInt == THERMOSTAT_CLUSTER && descMap.attrInt) { + if (descMap.clusterInt == THERMOSTAT_CLUSTER && descMap.attrInt != null) { switch (descMap.attrInt) { case OCCUPANCY: case CUSTOM_EFFECTIVE_OCCUPANCY: