/** * V1.0.0 Init try * V1.0.1 Added Heat and Cool vent opening limits * V1.0.2 Added Multiple Vent control * V1.0.4 Fixed vent not setting issue with recursive call * V1.0.5 Added a pause control and added vent control for slow/normal and fast slope and intercepts * V1.0.6 Added local virtual thermostat control * V1.0.7 Added treat idle as Fan Only * V2.0.0 Added Digital Control and dashboard tile support * V2.1.0 Added auto refresh of temp sensor * * Copyright 2019 Craig Romei * * 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. * */ definition( name: "KeenectLiteZone", namespace: "Craig.Romei", author: "Craig Romei", description: "zone application for 'Keenect', do not install directly.", category: "My Apps", parent: "Craig.Romei:KeenectLiteMaster", iconUrl: "https://raw.githubusercontent.com/napalmcsr/SmartThingsStuff/master/smartapps/keenect/clipart-thermometer-thermometer-clipart-free-6-thermometer-clip-art-clipartix-free-clipart.jpg", iconX2Url: "https://raw.githubusercontent.com/napalmcsr/SmartThingsStuff/master/smartapps/keenect/clipart-thermometer-thermometer-clipart-free-6-thermometer-clip-art-clipartix-free-clipart.jpg", iconX3Url : "https://raw.githubusercontent.com/napalmcsr/SmartThingsStuff/master/smartapps/keenect/clipart-thermometer-thermometer-clipart-free-6-thermometer-clip-art-clipartix-free-clipart.jpg", importUrl: "https://raw.githubusercontent.com/napalmcsr/Hubitat_Napalmcsr/master/Apps/KeenectLite/KeenectLiteZone.src" ) preferences { page(name: "pageConfig") // Doing it this way elimiates the default app name/mode options. } def pageConfig() { dynamicPage(name: "", title: "", install: true, uninstall: true, refreshInterval:0) { section("Pause") { input "PauseZone", "bool", title: "Pause KeenectLite from controlling this zone.", required: true, defaultValue: false, submitOnChange: true } section("Devices") { input "tStat", "capability.thermostat", title: "Zone Virtual Thermostat for setting zone setpoints", required: true input name : "UseTstatMode", title : "Use Thermostat Mode to control vents", multiple : false ,required : true, type : "bool" ,submitOnChange : true, defaultValue : false input "vents", "capability.switchLevel", title: "Zone Vent",multiple: true, required: true input "tempSensor", "capability.temperatureMeasurement", title: "Zone Temperature Sensor", multiple: false, required: true input name: "tempSensorRefreshTime", title : "How often to refresh temp sensor when main system is running in minutes. Leave empty for no refresh." ,multiple : false ,required : false ,type : "number" ,defaultValue : "" input "DashboardTileUpdate", "capability.sensor", title: "Display Tile(If wanted):", required: false } section("Settings") { input "VentControlType", "enum", title: "How reactive do you want the vents? Aggressive keeps them open wider closer to target, Slow starts closing them further away from target. Binary is full open or close, based on limits.", submitOnChange: true,required : true,defaultValue : "Normal", options: ["Aggressive", "Normal", "Slow","Binary"] input "reverseActing", "bool", title: "Reverse acting vent?", required: true, defaultValue: false, submitOnChange: true input name: "heatMinVo", title : "Heating minimum vent opening" ,multiple : false ,required : true ,type : "number" ,defaultValue : "0" input name: "heatMaxVo", title : "Heating maximum vent opening" ,multiple : false ,required : true ,type : "number" ,defaultValue : "100" input name: "coldMinVo", title : "Cooling minimum vent opening" ,multiple : false ,required : true ,type : "number" ,defaultValue : "0" input name: "coldMaxVo", title : "Cooling maximum vent opening" ,multiple : false ,required : true ,type : "number" ,defaultValue : "100" input name: "FanVo", title : "Fan mode opening" ,multiple : false ,required : true ,type : "number" ,defaultValue : "0" input name : "TreatIdleAsFan", title : "Treat Idle as Fan Only?", multiple : false ,required : true, type : "bool" ,submitOnChange : true, defaultValue : false } section("Logging") { input "logLevel","enum",title: "IDE logging level",required: true,options: getLogLevels(),defaultValue : "2" } section() { label title: "Enter a name for this automation", required: false } } } def installed() { initialize() } def updated() { unsubscribe() initialize() } def initialize(){ infolog "Init" state.clear() debuglog "Subscribe to tempSensor" subscribe(tempSensor, "temperature", tempHandler) debuglog "Subscribe to vents" subscribe(vents, "level", ventHandler) debuglog "Subscribe to Tstat" subscribe(tStat, "heatingSetpoint", setTstatHSP) subscribe(tStat, "coolingSetpoint", setTstatCSP) if(UseTstatMode) { subscribe(tStat, "thermostatMode", setTstatMode) debuglog "Getting local virtual Thermostat thermostatMode" state.thermostatMode = tStat.currentValue("thermostatMode").toUpperCase() } debuglog "Getting Thermostat Heating Setpoint" state.zoneHeatSetpoint = tStat.currentValue("heatingSetpoint") debuglog "Getting Thermostat Cooling Setpoint" state.zoneColdSetpoint = tStat.currentValue("coolingSetpoint") debuglog "Getting current Zone Temperature" state.currentTemperature = tempSensor.currentValue("temperature") debuglog "Getting House Thermostat State and evaluating zone" state.mainTstatState = parent.ChildGetMainTstatState() debuglog "Getting All Vent Opening" state.VentOpeningMap = [:] vents.each{ vent -> debuglog "Getting Vent ${vent}" state.VentOpeningMap[vent.displayName] =vent.currentValue("level") } zoneEvaluate() state.remove("state.VentOpening") infolog "Done init" } def setTstatMode(evt) { infolog "Running setTstatMode" state.thermostatMode = evt.value.toUpperCase() zoneEvaluate() } def MainTstatStateChange(mainTStatState) { infolog "Running MainTstatStateChange - Param = ${mainTStatState}" state.mainTstatState = mainTStatState zoneEvaluate() if (((state.mainTstatState=="HEATING")||(state.mainTstatState=="COOLING"))&&(tempSensorRefreshTime!="")){ if (tempSensorRefreshTime){ TempSensorPolling(true) } } else{ TempSensorPolling(false) } } def TempSensorPolling(enable) { if (enable){ debuglog "Start refreshing temp sensor" runIn(180,refreshTempSensor) } else{ debuglog "Stop Refreshing Temp Sensor" unschedule(refreshTempSensor) } } def refreshTempSensor() { debuglog "Refreshing Temp Sensor" tempSensor.refresh() runIn(tempSensorRefreshTime*60, refreshTempSensor) } def setTstatHSP(evt) { infolog "Running setTstatHSP" state.zoneHeatSetpoint =evt.value.toFloat() debuglog "Hot setpoint set to ${state.zoneHeatSetpoint}" zoneEvaluate() } def setTstatCSP(evt) { infolog "Running setTstatCSP" state.zoneColdSetpoint = evt.value.toFloat() debuglog "Cold setpoint set to ${state.zoneColdSetpoint}" zoneEvaluate() } def tempHandler(evt) { infolog "Running tempHandler" state.currentTemperature = evt.value.toFloat() debuglog "Zone Temperature set to ${state.currentTemperature}" zoneEvaluate() } def ventHandler(evt) { infolog "Running ventHandler for ${evt.device} value: ${evt.value}" infolog "state.VentOpeningMap = ${state.VentOpeningMap}" state.VentOpeningMap = [:] vents.each{ vent -> debuglog "Getting Vent ${vent}" state.VentOpeningMap[vent.displayName] =vent.currentValue("level") } infolog "state.VentOpeningMap after = ${state.VentOpeningMap}" //state.VentOpeningMap[vent.displayName] =vent.currentValue("level") sendDisplayTile() debuglog "${evt.device} was set to ${evt.value}" } def zoneEvaluate(){ infolog "Running zoneEvaluate" Map VentParams = [:] debuglog "${state.mainTstatState=="COOLING"}" if ((TreatIdleAsFan&&(state.mainTstatState=="IDLE"))||(state.mainTstatState=="COOLING")||(state.mainTstatState=="HEATING")||(state.mainTstatState=="FAN ONLY")){ debuglog "here" switch (state.mainTstatState){ case "HEATING" : VentParams = SetHeatVentParams() break case "PENDING HEAT" : VentParams = SetHeatVentParams() break case "COOLING" : VentParams = SetCoolVentParams() break case "PENDING COOL" : VentParams = SetCoolVentParams() break case "FAN ONLY" : VentParams = SetFanVentParams() break case "IDLE" : VentParams = SetFanVentParams() break default : VentParams = SetErrorVentParams() break } infolog "zoneEvaluate:Check Tstat Mode" CheckTstatMode(VentParams) infolog "zoneEvaluate:Calculate Vent Opening" CalculteVent(VentParams) infolog "zoneEvaluate:Set The Vent" state.zoneVoLocal = VentParams.ventOpening setVents(VentParams.ventOpening) } sendDisplayTile() sendStatstoParent() infolog "zoneEvaluate: --EXIT" } def CheckTstatMode(VentParams) { if(UseTstatMode) { if (!(state.mainTstatState.contains(state.thermostatMode)||(state.thermostatMode=="AUTO"))) { debuglog "CheckTstatMode: Not in the tstat mode, state.mainTstatState= ${state.mainTstatState} - state.thermostatMode= ${state.thermostatMode}" VentParams.tempDelta = 0.1 VentParams.ventSlope = 0 VentParams.ventIntercept = VentParams.MinVo } } } def CalculteVent(Map VentParams){ VentParams.ventOpening = Math.round(VentParams.tempDelta*VentParams.ventSlope.toInteger()+VentParams.ventIntercept.toInteger()) if(reverseActing){ VentParams.ventOpening = 100-VentParams.ventOpening } debuglog "CalculteVent- VentParams.ventOpening before limit checks: ${VentParams.ventOpening}" if (VentParams.ventOpening>VentParams.MaxVo.toInteger()){ debuglog "CalculteVent- VentParams.ventOpening greater than Max" VentParams.ventOpening = VentParams.MaxVo.toInteger() } if (VentParams.ventOpening debuglog "${vent} : ${state.VentOpeningMap[vent.displayName]}" if (((state.VentOpeningMap[vent.displayName].toInteger()-newVo.toInteger())>7) || ((state.VentOpeningMap[vent.displayName].toInteger()-newVo.toInteger())<-7)){ debuglog "Vent was far enough away to move" vent.setLevel(newVo) runIn(60*1, ventcheck) } } } else{ debuglog "Zone Paused, not moving the Vents" } } def ventcheck(){ //if (state.enabled == true){ debuglog "ventcheck:enter- " setVents(state.zoneVoLocal) debuglog "ventcheck:exit- " //} } def sendDisplayTile() { if(DashboardTileUpdate) { def reportString = app.label +"
" reportString += "Mode: "+state.mainTstatState+"
" reportString += "Setpoint: "+state.activeSetPoint+"
" reportString += "Temp: "+state.currentTemperature+"
" vents.each{ vent -> reportString += vent.displayName+": "+vent.currentValue("level")+"
" } debuglog "zoneEvaluate: ${reportString}" DashboardTileUpdate.SetKeenectData(reportString) } } def sendStatstoParent(){ Map ChildMap = [:] ChildMap.title = app.label ChildMap.setpoint = state.activeSetPoint ChildMap.currentTemperature = state.currentTemperature ChildMap.vent = state.zoneVoLocal parent.SetChildStats(ChildMap) } def debuglog(statement) { def logL = 0 if (logLevel) logL = logLevel.toInteger() if (logL == 0) {return}//bail else if (logL >= 2) { log.debug(statement) } } def infolog(statement) { def logL = 0 if (logLevel) logL = logLevel.toInteger() if (logL == 0) {return}//bail else if (logL >= 1) { log.info(statement) } } def getLogLevels(){ return [["0":"None"],["1":"Running"],["2":"NeedHelp"]] } def setVersion(){ state.version = "2.3.1" // Version number of this app state.InternalName = "KeenectLiteZone" // this is the name used in the JSON file for this app }