/** * Rule * * Copyright 2015 Bruce Ravenel * * Version 1.2.3a 25 Nov 2015 * * 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: "Rule", namespace: "bravenel", author: "Bruce Ravenel", description: "Rule", category: "Convenience", parent: "bravenel:Rule Machine", iconUrl: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps.png", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps@2x.png" ) preferences { page(name: "selectRule") page(name: "selectConditions") page(name: "defineRule") page(name: "certainTime") page(name: "atCertainTime") page(name: "selectActionsTrue") page(name: "selectActionsFalse") page(name: "selectMsgTrue") page(name: "selectMsgFalse") } def selectRule() { dynamicPage(name: "selectRule", title: "Select Conditions, Rule and Results", uninstall: true, install: true) { section() { label title: "Name the Rule", required: true def condLabel = conditionLabel() if (condLabel) condLabel = condLabel[0..-2] href "selectConditions", title: "Define Conditions", description: condLabel ? (condLabel) : "Tap to set", required: true, state: condLabel ? "complete" : null, submitOnChange: true href "defineRule", title: "Define the Rule", description: state.str ? (state.str) : "Tap to set", state: state.str ? "complete" : null href "selectActionsTrue", title: "Select the Actions for True", description: state.actsTrue ? state.actsTrue : "Tap to set", state: state.actsTrue ? "complete" : null href "selectActionsFalse", title: "Select the Actions for False", description: state.actsFalse ? state.actsFalse : "Tap to set", state: state.actsFalse ? "complete" : null } section(title: "More options", hidden: hideOptionsSection(), hideable: true) { input "modesZ", "mode", title: "Evaluate only when mode is", multiple: true, required: false paragraph "Advanced Rule Input allows for parenthesized sub-rules." input "advanced", "bool", title: "Advanced Rule Input", required: false input "disabled", "capability.switch", title: "Switch to disable rule when ON", required: false, multiple: false } } } // Condition input code follows def selectConditions() { def ct = settings.findAll{it.key.startsWith("rCapab")} state.howMany = ct.size() + 1 def howMany = state.howMany dynamicPage(name: "selectConditions", title: "Select Conditions", uninstall: false) { if(howMany) { for (int i = 1; i <= howMany; i++) { def thisCapab = "rCapab$i" section("Condition #$i") { getCapab(thisCapab) def myCapab = settings.find {it.key == thisCapab} if(myCapab) { def xCapab = myCapab.value // removed , "Certain Time" if(!(xCapab in ["Time of day", "Days of week", "Mode"])) { def thisDev = "rDev$i" getDevs(xCapab, thisDev) def myDev = settings.find {it.key == thisDev} if(myDev) if(myDev.value.size() > 1) getAnyAll(thisDev) if(xCapab in ["Temperature", "Humidity", "Illuminance", "Dimmer level", "Energy meter", "Power meter", "Battery"]) getRelational(thisDev) } getState(xCapab, i) } } } } } } def getDevs(myCapab, dev) { def thisName = "" def thisCapab = "" switch(myCapab) { case "Switch": thisName = "Switches" thisCapab = "switch" break case "Motion": thisName = "Motion sensors" thisCapab = "motionSensor" break case "Acceleration": thisName = "Acceleration sensors" thisCapab = "accelerationSensor" break case "Contact": thisName = "Contact sensors" thisCapab = "contactSensor" break case "Presence": thisName = "Presence sensors" thisCapab = "presenceSensor" break case "Lock": thisName = "Locks" thisCapab = "lock" break case "Dimmer level": thisName = "Dimmers" thisCapab = "switchLevel" break case "Temperature": thisName = "Temperature sensors" thisCapab = "temperatureMeasurement" break case "Humidity": thisName = "Humidity sensors" thisCapab = "relativeHumidityMeasurement" break case "Illuminance": thisName = "Illuminance sensors" thisCapab = "illuminanceMeasurement" break case "Energy meter": thisName = "Energy meters" thisCapab = "energyMeter" break case "Power meter": thisName = "Power meters" thisCapab = "powerMeter" break case "Water sensor": thisName = "Water sensors" thisCapab = "waterSensor" break case "Battery": thisName = "Batteries" thisCapab = "battery" } def result = input dev, "capability.$thisCapab", title: thisName, required: true, multiple: true, submitOnChange: true } def getAnyAll(myDev) { def result = input "All$myDev", "bool", title: "All of these?", defaultValue: false } def getRelational(myDev) { def result = input "Rel$myDev", "enum", title: "Choose comparison", required: true, options: ["=", "!=", "<", ">", "<=", ">="] } def getCapab(myCapab) { // removed , "Valve" to avoid confusion, and , "Certain Time" def myOptions = ["Switch", "Motion", "Acceleration", "Contact", "Presence", "Lock", "Temperature", "Humidity", "Illuminance", "Time of day", "Days of week", "Mode", "Dimmer level", "Energy meter", "Power meter", "Water sensor", "Battery"] def result = input myCapab, "enum", title: "Select capability", required: false, options: myOptions.sort(), submitOnChange: true } def getState(myCapab, n) { def result = null def days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] if (myCapab == "Switch") result = input "state$n", "enum", title: "Switch state", options: ["on", "off"] else if(myCapab == "Motion") result = input "state$n", "enum", title: "Motion state", options: ["active", "inactive"], defaultValue: "active" else if(myCapab == "Acceleration") result = input "state$n", "enum", title: "Acceleration state", options: ["active", "inactive"] else if(myCapab == "Contact") result = input "state$n", "enum", title: "Contact state", options: ["open", "closed"] else if(myCapab == "Presence") result = input "state$n", "enum", title: "Presence state", options: ["present", "not present"], defaultValue: "present" else if(myCapab == "Lock") result = input "state$n", "enum", title: "Lock state", options: ["locked", "unlocked"] else if(myCapab == "Water sensor") result = input "state$n", "enum", title: "Water state", options: ["dry", "wet"] else if(myCapab == "Dimmer level") result = input "state$n", "number", title: "Dimmer level", range: "0..100" else if(myCapab == "Temperature") result = input "state$n", "decimal", title: "Temperature", range: "*..*" else if(myCapab == "Humidity") result = input "state$n", "number", title: "Humidity", range: "0..100" else if(myCapab == "Illuminance") result = input "state$n", "number", title: "Illuminance" else if(myCapab == "Energy meter") result = input "state$n", "number", title: "Energy level" else if(myCapab == "Power meter") result = input "state$n", "number", title: "Power level", range: "*..*" else if(myCapab == "Battery") result = input "state$n", "number", title: "Battery level" else if(myCapab == "Days of week") result = input "days", "enum", title: "On certain days of the week", multiple: true, required: false, options: days else if(myCapab == "Mode") { def myModes = [] location.modes.each {myModes << "$it"} result = input "modes", "enum", title: "Select mode(s)", multiple: true, required: false, options: myModes.sort() } else if(myCapab == "Time of day") { def timeLabel = timeIntervalLabel() href "certainTime", title: "During a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null } } def certainTime() { dynamicPage(name: "certainTime", title: "Only during a certain time", uninstall: false) { section() { input "startingX", "enum", title: "Starting at", options: ["A specific time", "Sunrise", "Sunset"], defaultValue: "A specific time", submitOnChange: true if(startingX in [null, "A specific time"]) input "starting", "time", title: "Start time", required: false else { if(startingX == "Sunrise") input "startSunriseOffset", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false else if(startingX == "Sunset") input "startSunsetOffset", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false } } section() { input "endingX", "enum", title: "Ending at", options: ["A specific time", "Sunrise", "Sunset"], defaultValue: "A specific time", submitOnChange: true if(endingX in [null, "A specific time"]) input "ending", "time", title: "End time", required: false else { if(endingX == "Sunrise") input "endSunriseOffset", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false else if(endingX == "Sunset") input "endSunsetOffset", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false } } } } def conditionLabel() { def howMany = state.howMany def result = "" if(howMany) { for (int i = 1; i <= howMany; i++) { result = result + conditionLabelN(i) if((i + 1) <= howMany) result = result + "\n" } if(howMany == 2) { state.str = result[0..-2] state.eval = [1] } } return result } def conditionLabelN(i) { def result = "" def thisCapab = settings.find {it.key == "rCapab$i"} if(!thisCapab) return result if(thisCapab.value == "Time of day") result = "Time between " + timeIntervalLabel() else if(thisCapab.value == "Days of week") result = "Day i" + (days.size() > 1 ? "n " + days : "s " + days[0]) else if(thisCapab.value == "Mode") result = "Mode i" + (modes.size() > 1 ? "n " + modes : "s " + modes[0]) else { def thisDev = settings.find {it.key == "rDev$i"} if(!thisDev) return result def thisAll = settings.find {it.key == "AllrDev$i"} def myAny = thisAll ? "any " : "" if (thisCapab.value == "Temperature") result = "Temperature of " else if(thisCapab.value == "Humidity") result = "Humidity of " else if(thisCapab.value == "Illuminance") result = "Illuminance of " else if(thisCapab.value == "Dimmer level") result = "Dimmer level of " else if(thisCapab.value == "Energy meter") result = "Energy level of " else if(thisCapab.value == "Power meter") result = "Power level of " else if(thisCapab.value == "Battery") result = "Battery level of " result = result + (myAny ? thisDev.value : thisDev.value[0]) + " " + ((thisAll ? thisAll.value : false) ? "all " : myAny) def thisRel = settings.find {it.key == "RelrDev$i"} if(thisCapab.value in ["Temperature", "Humidity", "Illuminance", "Dimmer level", "Energy meter", "Power meter", "Battery"]) result = result + " " + thisRel.value + " " def thisState = settings.find {it.key == "state$i"} result = result + thisState.value } return result } // Rule definition code follows def defineRule() { dynamicPage(name: "defineRule", title: "Define the Rule", uninstall: false) { state.n = 0 state.str = "" state.eval = [] section() {inputLeftAndRight(false)} } } def inputLeft(sub) { def howMany = state.howMany - 1 def conds = [] for (int i = 1; i <= howMany; i++) conds << conditionLabelN(i) if(advanced) input "subCondL$state.n", "bool", title: "Enter subrule for left?", submitOnChange: true if(settings["subCondL$state.n"]) { state.str = state.str + "(" state.eval << "(" paragraph(state.str) inputLeftAndRight(true) input "moreConds$state.n", "bool", title: "More conditions on left?", submitOnChange: true if(settings["moreConds$state.n"]) inputRight(sub) } else { input "condL$state.n", "enum", title: "Which condition?", options: conds, submitOnChange: true if(settings["condL$state.n"]) { state.str = state.str + settings["condL$state.n"] def myCond = 0 for (int i = 1; i <= howMany; i++) if(conditionLabelN(i) == settings["condL$state.n"]) myCond = i state.eval << myCond paragraph(state.str) } } } def inputRight(sub) { def howMany = state.howMany - 1 state.n = state.n + 1 input "operator$state.n", "enum", title: "AND or OR", options: ["AND", "OR"], submitOnChange: true, required: false if(settings["operator$state.n"]) { state.str = state.str + " " + settings["operator$state.n"] + " " state.eval << settings["operator$state.n"] paragraph(state.str) def conds = [] for (int i = 1; i <= howMany; i++) conds << conditionLabelN(i) if(advanced) input "subCondR$state.n", "bool", title: "Enter subrule for right?", submitOnChange: true if(settings["subCondR$state.n"]) { state.str = state.str + "(" state.eval << "(" paragraph(state.str) inputLeftAndRight(true) inputRight(sub) } else { input "condR$state.n", "enum", title: "Which condition?", options: conds, submitOnChange: true if(settings["condR$state.n"]) { state.str = state.str + settings["condR$state.n"] def myCond = 0 for (int i = 1; i <= howMany; i++) if(conditionLabelN(i) == settings["condR$state.n"]) myCond = i state.eval << myCond paragraph(state.str) } if(sub) { input "endOfSub$state.n", "bool", title: "End of sub-rule?", submitOnChange: true if(settings["endOfSub$state.n"]) { state.str = state.str + ")" state.eval << ")" paragraph(state.str) return } } inputRight(sub) } } } def inputLeftAndRight(sub) { state.n = state.n + 1 inputLeft(sub) inputRight(sub) } def stripBrackets(str) { def i = str.indexOf('[') def j = str.indexOf(']') def result = str.substring(0, i) + str.substring(i + 1, j) + str.substring(j + 1) return result } // Action selection code follows def setActTrue(dev, str) { if(dev) state.actsTrue = state.actsTrue + stripBrackets("$str") + "\n" } def addToActTrue(str) { state.actsTrue = state.actsTrue + str + "\n" } def buildActTrue(str, brackets) { state.actsTrue = state.actsTrue + (brackets ? stripBrackets("$str") : str) } def setActFalse(dev, str) { if(dev) state.actsFalse = state.actsFalse + stripBrackets("$str") + "\n" } def addToActFalse(str) { state.actsFalse = state.actsFalse + str + "\n" } def buildActFalse(str, brackets) { state.actsFalse = state.actsFalse + (brackets ? stripBrackets("$str") : str) } def selectActionsTrue() { dynamicPage(name: "selectActionsTrue", title: "Select Actions for True", uninstall: false) { state.actsTrue = "" section("") { input "onSwitchTrue", "capability.switch", title: "Turn on these switches", multiple: true, required: false, submitOnChange: true setActTrue(onSwitchTrue, "On: $onSwitchTrue") input "offSwitchTrue", "capability.switch", title: "Turn off these switches", multiple: true, required: false, submitOnChange: true setActTrue(offSwitchTrue, "Off: $offSwitchTrue") input "delayedOffTrue", "capability.switch", title: "Turn on/off these switches after a delay (default is OFF)", multiple: true, required: false, submitOnChange: true if(delayedOffTrue) { input "delayOnOffTrue", "bool", title: "Turn ON after the delay?", multiple: false, required: false, defaultValue: false, submitOnChange: true input "delayMinutesTrue", "number", title: "Minutes of delay", required: true, range: "1..*", submitOnChange: true if(delayMinutesTrue) { def delayStrTrue = "Delayed " + (delayOnOffTrue ? "On:" : "Off:") + " $delayedOffTrue: $delayMinutesTrue minute" if(delayMinutesTrue > 1) delayStrTrue = delayStrTrue + "s" setActTrue(delayedOffTrue, delayStrTrue) } } input "pendedOffTrue", "capability.switch", title: "Turn on/off these switches after a delay, pending cancellation (default is OFF)", multiple: true, required: false, submitOnChange: true if(pendedOffTrue) { input "pendOnOffTrue", "bool", title: "Turn ON after the delay?", multiple: false, required: false, defaultValue: false, submitOnChange: true input "pendMinutesTrue", "number", title: "Minutes of delay", required: true, range: "1..*", submitOnChange: true if(pendMinutesTrue) { def pendStrTrue = "Pending "+ (pendOnOffTrue ? "On:" : "Off:") + " $pendedOffTrue: $pendMinutesTrue minute" if(pendMinutesTrue > 1) pendStrTrue = pendStrTrue + "s" setActTrue(pendedOffTrue, pendStrTrue) } } input "dimATrue", "capability.switchLevel", title: "Set these dimmers", multiple: true, submitOnChange: true, required: false if(dimATrue) input "dimLATrue", "number", title: "To this level", range: "0..100", required: true, submitOnChange: true if(dimLATrue) setActTrue(dimATrue, "Dim: $dimATrue: $dimLATrue") input "dimBTrue", "capability.switchLevel", title: "Set these other dimmers", multiple: true, submitOnChange: true, required: false if(dimBTrue) input "dimLBTrue", "number", title: "To this level", range: "0..100", required: true, submitOnChange: true if(dimLBTrue) setActTrue(dimBTrue, "Dim: $dimBTrue: $dimLBTrue") input "bulbsTrue", "capability.colorControl", title: "Set color for these bulbs", multiple: true, required: false, submitOnChange: true if(bulbsTrue) { input "colorTrue", "enum", title: "Bulb color?", required: true, multiple: false, submitOnChange: true, options: ["Soft White", "White", "Daylight", "Warm White", "Red", "Green", "Blue", "Yellow", "Orange", "Purple", "Pink"] input "colorLevelTrue", "number", title: "Bulb level?", required: false, submitOnChange: true, range: "0..100" buildActTrue("Color: $bulbsTrue ", true) if(colorTrue) buildActTrue("$colorTrue ", false) if(colorLevelTrue) addToActTrue("Level: $colorLevelTrue") } input "lockTrue", "capability.lock", title: "Lock these locks", multiple: true, required: false, submitOnChange: true setActTrue(lockTrue, "Lock: $lockTrue") input "unlockTrue", "capability.lock", title: "Unlock these locks", multiple: true, required: false, submitOnChange: true setActTrue(unlockTrue, "Unlock: $unlockTrue") input "openValveTrue", "capability.valve", title: "Open these valves", multiple: true, required: false, submitOnChange: true setActTrue(openValveTrue, "Open: $openValveTrue") input "closeValveTrue", "capability.valve", title: "Close these valves", multiple: true, required: false, submitOnChange: true setActTrue(closeValveTrue, "Close: $closeValveTrue") input "thermoTrue", "capability.thermostat", title: "Set these thermostats", multiple: true, required: false, submitOnChange: true if(thermoTrue) { input "thermoModeTrue", "enum", title: "Select thermostate mode", multiple: false, required: false, options: ["auto", "heat", "cool", "off"], submitOnChange: true input "thermoSetHeatTrue", "decimal", title: "Set heating point", multiple: false, required: false, submitOnChange: true input "thermoSetCoolTrue", "decimal", title: "Set cooling point", multiple: false, required: false, submitOnChange: true input "thermoFanTrue", "enum", title: "Fan setting", multiple: false, required: false, submitOnChange: true, options: ["fanOn", "fanAuto"] buildActTrue("$thermoTrue: ", true) if(thermoModeTrue) buildActTrue("Mode: " + thermoModeTrue + " ", false) if(thermoSetHeatTrue) buildActTrue("Heat to $thermoSetHeatTrue ", false) if(thermoSetCoolTrue) buildActTrue("Cool to $thermoSetCoolTrue ", false) if(thermoFanTrue) buildActTrue("Fan setting $thermoFanTrue", false) addToActTrue("") } input "modeTrue", "mode", title: "Set the mode", multiple: false, required: false, submitOnChange: true if(modeTrue) addToActTrue("Mode: $modeTrue") def phrases = location.helloHome?.getPhrases()*.label input "myPhraseTrue", "enum", title: "Routine to run", required: false, options: phrases.sort(), submitOnChange: true if(myPhraseTrue) addToActTrue("Routine: $myPhraseTrue") href "selectMsgTrue", title: "Send message", description: state.msgTrue ? state.msgTrue : "Tap to set", state: state.msgTrue ? "complete" : null if(state.msgTrue) addToActTrue(state.msgTrue) input "delayTrue", "number", title: "Delay the effect of this rule by this many minutes", required: false, submitOnChange: true if(delayTrue) { def delayStr = "Delay Rule: $delayTrue minute" if(delayTrue > 1) delayStr = delayStr + "s" addToActTrue(delayStr) } } if(state.actsTrue) state.actsTrue = state.actsTrue[0..-2] } } def selectActionsFalse() { dynamicPage(name: "selectActionsFalse", title: "Select Actions for False", uninstall: false) { state.actsFalse = "" section("") { input "onSwitchFalse", "capability.switch", title: "Turn on these switches", multiple: true, required: false, submitOnChange: true setActFalse(onSwitchFalse, "On: $onSwitchFalse") input "offSwitchFalse", "capability.switch", title: "Turn off these switches", multiple: true, required: false, submitOnChange: true setActFalse(offSwitchFalse, "Off: $offSwitchFalse") input "delayedOffFalse", "capability.switch", title: "Turn on/off these switches after a delay (default is OFF)", multiple: true, required: false, submitOnChange: true if(delayedOffFalse) { input "delayOnOffFalse", "bool", title: "Turn ON after the delay?", multiple: false, required: false, defaultValue: false, submitOnChange: true input "delayMinutesFalse", "number", title: "Minutes of delay", required: true, range: "1..*", submitOnChange: true if(delayMinutesFalse) { def delayStrFalse = "Delayed " + (delayOnOffFalse ? "On:" : "Off:") + " $delayedOffFalse: $delayMinutesFalse minute" if(delayMinutesFalse > 1) delayStrFalse = delayStrFalse + "s" setActFalse(delayedOffFalse, delayStrFalse) } } input "pendedOffFalse", "capability.switch", title: "Turn on/off these switches after a delay, pending cancellation (default is OFF)", multiple: true, required: false, submitOnChange: true if(pendedOffFalse) { input "pendOnOffFalse", "bool", title: "Turn ON after the delay?", multiple: false, required: false, defaultValue: false, submitOnChange: true input "pendMinutesFalse", "number", title: "Minutes of delay", required: true, range: "1..*", submitOnChange: true if(pendMinutesFalse) { def pendStrFalse = "Pending "+ (pendOnOffFalse ? "On:" : "Off:") + " $pendedOffFalse: $pendMinutesFalse minute" if(pendMinutesFalse > 1) pendStrFalse = pendStrFalse + "s" setActFalse(pendedOffFalse, pendStrFalse) } } input "dimAFalse", "capability.switchLevel", title: "Set these dimmers", multiple: true, submitOnChange: true, required: false if(dimAFalse) input "dimLAFalse", "number", title: "To this level", range: "0..100", required: true, submitOnChange: true if(dimLAFalse) setActFalse(dimAFalse, "Dim: $dimAFalse: $dimLAFalse") input "dimBFalse", "capability.switchLevel", title: "Set these other dimmers", multiple: true, submitOnChange: true, required: false if(dimBFalse) input "dimLBFalse", "number", title: "To this level", range: "0..100", required: true, submitOnChange: true if(dimLBFalse) setActFalse(dimBFalse, "Dim: $dimBFalse: $dimLBFalse") input "bulbsFalse", "capability.colorControl", title: "Set color for these bulbs", multiple: true, required: false, submitOnChange: true if(bulbsFalse) { input "colorFalse", "enum", title: "Bulb color?", required: true, multiple: false, submitOnChange: true, options: ["Soft White", "White", "Daylight", "Warm White", "Red", "Green", "Blue", "Yellow", "Orange", "Purple", "Pink"] input "colorLevelFalse", "number", title: "Bulb level?", required: false, submitOnChange: true, range: "0..100" buildActFalse("Color: $bulbsFalse ", true) if(colorFalse) buildActFalse("$colorFalse ", false) if(colorLevelFalse) addToActFalse("Level: $colorLevelFalse") } input "lockFalse", "capability.lock", title: "Lock these locks", multiple: true, required: false, submitOnChange: true setActFalse(lockFalse, "Lock: $lockFalse") input "unlockFalse", "capability.lock", title: "Unlock these locks", multiple: true, required: false, submitOnChange: true setActFalse(unlockFalse, "Unlock: $unlockFalse") input "openValveFalse", "capability.valve", title: "Open these valves", multiple: true, required: false, submitOnChange: true setActTrue(openValveFalse, "Open: $openValveFalse") input "closeValveFalse", "capability.valve", title: "Close these valves", multiple: true, required: false, submitOnChange: true setActTrue(closeValveFalse, "Close: $closeValveFalse") input "thermoFalse", "capability.thermostat", title: "Set these thermostats", multiple: true, required: false, submitOnChange: true if(thermoFalse) { input "thermoModeFalse", "enum", title: "Select thermostate mode", multiple: false, required: false, options: ["auto", "heat", "cool", "off"], submitOnChange: true input "thermoSetHeatFalse", "decimal", title: "Set heating point", multiple: false, required: false, submitOnChange: true input "thermoSetCoolFalse", "decimal", title: "Set cooling point", multiple: false, required: false, submitOnChange: true input "thermoFanFalse", "enum", title: "Fan setting", multiple: false, required: false, submitOnChange: true, options: ["fanOn", "fanAuto"] buildActFalse("$thermoFalse: ", true) if(thermoModeFalse) buildActFalse("Mode: " + thermoModeFalse + " ", false) if(thermoSetHeatFalse) buildActFalse("Heat to $thermoSetHeatFalse ", false) if(thermoSetCoolFalse) buildActFalse("Cool to $thermoSetCoolFalse ", false) if(thermoFanFalse) buildActFalse("Fan setting $thermoFanFalse", false) addToActFalse("") } input "modeFalse", "mode", title: "Set the mode", multiple: false, required: false, submitOnChange: true if(modeFalse) addToActFalse("Mode: $modeFalse") def phrases = location.helloHome?.getPhrases()*.label input "myPhraseFalse", "enum", title: "Routine to run", required: false, options: phrases.sort(), submitOnChange: true if(myPhraseFalse) addToActFalse("Routine: $myPhraseFalse") href "selectMsgFalse", title: "Send message", description: state.msgFalse ? state.msgFalse : "Tap to set", state: state.msgFalse ? "complete" : null if(state.msgFalse) addToActFalse(state.msgFalse) input "delayFalse", "number", title: "Delay the effect of this rule by this many minutes", required: false, submitOnChange: true if(delayFalse) { def delayStr = "Delay Rule: $delayFalse minute" if(delayFalse > 1) delayStr = delayStr + "s" addToActFalse(delayStr) } } if(state.actsFalse) state.actsFalse = state.actsFalse[0..-2] } } def selectMsgTrue() { dynamicPage(name: "selectMsgTrue", title: "Select Message and Destination", uninstall: false) { section("") { input "pushTrue", "bool", title: "Send Push Notification?", required: false, submitOnChange: true input "msgTrue", "text", title: "Custom message to send", required: false, submitOnChange: true input "phoneTrue", "phone", title: "Phone number for SMS", required: false, submitOnChange: true } state.msgTrue = (pushTrue ? "Push" : "") + (msgTrue ? " '$msgTrue'" : "") + (phoneTrue ? " to $phoneTrue" : "") } } def selectMsgFalse() { dynamicPage(name: "selectMsgFalse", title: "Select Message and Destination", uninstall: false) { section("") { input "pushFalse", "bool", title: "Send Push Notification?", required: false, submitOnChange: true input "msgFalse", "text", title: "Custom message to send", required: false, submitOnChange: true input "phoneFalse", "phone", title: "Phone number for SMS", required: false, submitOnChange: true } state.msgFalse = (pushFalse ? "Push" : "") + (msgFalse ? " '$msgFalse'" : "") + (phoneFalse ? " to $phoneFalse" : "") } } // initialization code follows def scheduleTimeOfDay() { def start = null def stop = null def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: startSunriseOffset, sunsetOffset: startSunsetOffset) if(startingX == "Sunrise") start = s.sunrise.time else if(startingX == "Sunset") start = s.sunset.time else if(starting) start = timeToday(starting,location.timeZone).time s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: endSunriseOffset, sunsetOffset: endSunsetOffset) if(endingX == "Sunrise") stop = s.sunrise.time else if(endingX == "Sunset") stop = s.sunset.time else if(ending) stop = timeToday(ending,location.timeZone).time schedule(start, "startHandler") schedule(stop, "stopHandler") if(startingX in ["Sunrise", "Sunset"] || endingX in ["Sunrise", "Sunset"]) schedule("2015-01-09T00:00:29.000-0700", "scheduleTimeOfDay") // in case sunset/sunrise; change daily } def installed() { initialize() } def updated() { unschedule() unsubscribe() initialize() } def initialize() { def howMany = state.howMany - 1 for (int i = 1; i <= howMany; i++) { def capab = (settings.find {it.key == "rCapab$i"}).value if (capab == "Mode") subscribe(location, "mode", allHandler) else if(capab == "Time of day") scheduleTimeOfDay() else if(capab == "Days of week") schedule("2015-01-09T00:00:10.000-0700", "runRule") else if(capab == "Dimmer level") subscribe((settings.find{it.key == "rDev$i"}).value, "level", allHandler) else if(capab == "Energy meter") subscribe((settings.find{it.key == "rDev$i"}).value, "energy", allHandler) else if(capab == "Power meter") subscribe((settings.find{it.key == "rDev$i"}).value, "power", allHandler) else if(capab == "Water sensor") subscribe((settings.find{it.key == "rDev$i"}).value, "water", allHandler) else subscribe((settings.find{it.key == "rDev$i"}).value, capab.toLowerCase(), allHandler) } state.success = null subscribe(disabled, "switch", disabledHandler) if(disabled) state.disabled = disabled.currentSwitch == "on" else state.disabled = false runRule(false) } // Main rule evaluation code follows def compare(a, rel, b) { def result = true if (rel == "=") result = a == b else if(rel == "!=") result = a != b else if(rel == ">") result = a > b else if(rel == "<") result = a < b else if(rel == ">=") result = a >= b else if(rel == "<=") result = a <= b return result } def checkCondAny(dev, state, cap, rel) { def result = false if (cap == "Temperature") dev.currentTemperature.each {result = result || compare(it, rel, state)} else if(cap == "Humidity") dev.currentHumidity.each {result = result || compare(it, rel, state)} else if(cap == "Illuminance") dev.currentIlluminance.each {result = result || compare(it, rel, state)} else if(cap == "Dimmer level") dev.currentLevel.each {result = result || compare(it, rel, state)} else if(cap == "Energy meter") dev.currentEnergy.each {result = result || compare(it, rel, state)} else if(cap == "Power meter") dev.currentPower.each {result = result || compare(it, rel, state)} else if(cap == "Battery") dev.currentBattery.each {result = result || compare(it, rel, state)} else if(cap == "Water sensor") result = state in dev.currentWater else if(cap == "Switch") result = state in dev.currentSwitch else if(cap == "Motion") result = state in dev.currentMotion else if(cap == "Acceleration") result = state in dev.currentAcceleration else if(cap == "Contact") result = state in dev.currentContact else if(cap == "Presence") result = state in dev.currentPresence else if(cap == "Lock") result = state in dev.currentLock // log.debug "CheckAny $cap $result" return result } def checkCondAll(dev, state, cap, rel) { def flip = ["on": "off", "off": "on", "active": "inactive", "inactive": "active", "open": "closed", "closed": "open", "wet": "dry", "dry": "wet", "present": "not present", "not present": "present", "locked": "unlocked", "unlocked": "locked"] def result = true if (cap == "Temperature") dev.currentTemperature.each {result = result && compare(it, rel, state)} else if(cap == "Humidity") dev.currentHumidity.each {result = result && compare(it, rel, state)} else if(cap == "Illuminance") dev.currentIlluminance.each {result = result && compare(it, rel, state)} else if(cap == "Dimmer level") dev.currentLevel.each {result = result && compare(it, rel, state)} else if(cap == "Energy meter") dev.currentEnergy.each {result = result && compare(it, rel, state)} else if(cap == "Power meter") dev.currentPower.each {result = result && compare(it, rel, state)} else if(cap == "Battery") dev.currentBattery.each {result = result && compare(it, rel, state)} else if(cap == "Water sensor") result = !(flip[state] in dev.currentSwitch) else if(cap == "Switch") result = !(flip[state] in dev.currentSwitch) else if(cap == "Motion") result = !(flip[state] in dev.currentMotion) else if(cap == "Acceleration") result = !(flip[state] in dev.currentAcceleration) else if(cap == "Contact") result = !(flip[state] in dev.currentContact) else if(cap == "Presence") result = !(flip[state] in dev.currentPresence) else if(cap == "Lock") result = !(flip[state] in dev.currentLock) // log.debug "CheckAll $cap $result" return result } def getOperand(i) { def result = true def capab = (settings.find {it.key == "rCapab$i"}).value if (capab == "Mode") result = modeOk else if(capab == "Time of day") result = timeOk else if(capab == "Days of week") result = daysOk else { def myDev = settings.find {it.key == "rDev$i"} def myState = settings.find {it.key == "state$i"} def myRel = settings.find {it.key == "RelrDev$i"} def myAll = settings.find {it.key == "AllrDev$i"} if(myAll) { if(myAll.value) result = checkCondAll(myDev.value, myState.value, capab, myRel ? myRel.value : 0) else result = checkCondAny(myDev.value, myState.value, capab, myRel ? myRel.value : 0) } else result = checkCondAny(myDev.value, myState.value, capab, myRel ? myRel.value : 0) } // log.debug "operand is $result" return result } def findRParen() { def noMatch = true while(noMatch) { if(state.eval[state.token] == ")") { if(state.parenLev == 0) return else state.parenLev = state.parenLev - 1 } else if(state.eval[state.token] == "(") state.parenLev = state.parenLev + 1 state.token = state.token + 1 if(state.token >= state.eval.size) return } } def disEval() { if(state.eval[state.token] == "(") { state.parenLev = 0 findRParen() } if(state.token >= state.eval.size) return state.token = state.token + 1 } def evalTerm() { def result = true def thisTok = state.eval[state.token] if (thisTok == "(") { state.token = state.token + 1 result = eval() } else result = getOperand(thisTok) state.token = state.token + 1 return result } def eval() { def result = evalTerm() while(true) { if(state.token >= state.eval.size) return result def thisTok = state.eval[state.token] if (thisTok == "OR") { if(result) { disEval() return true } } else if (thisTok == "AND") { if(!result) { disEval() return false } } else if (thisTok == ")") return result state.token = state.token + 1 result = evalTerm() } } // Run the evaluation and take action code follows def doDelayTrue(time) { runIn(time * 60, delayRuleTrue) def delayStr = "minute" if(time > 1) delayStr = delayStr + "s" log.info ("$app.label is True, but delayed by $time $delayStr") state.success = success } def doDelayFalse(time) { runIn(time * 60, delayRuleFalse) def delayStr = "minute" if(time > 1) delayStr = delayStr + "s" log.info ("$app.label is False, but delayed by $time $delayStr") state.success = success } def runRule(delay) { if(!allOk) return state.token = 0 def success = eval() if((success != state.success) || delay) { unschedule(delayRuleTrue) unschedule(delayRuleFalse) if (delayTrue > 0 && !delay && success) doDelayTrue(delayTrue) else if(delayFalse > 0 && !delay && !success) doDelayFalse(delayFalse) else { if(success) { if(onSwitchTrue) onSwitchTrue.on() if(offSwitchTrue) offSwitchTrue.off() if(delayedOffTrue) runIn(delayMinutesTrue * 60, delayOffTrue) if(pendedOffTrue) runIn(pendMinutesTrue * 60, pendingOffTrue) if(pendedOffFalse) unschedule(pendingOffFalse) if(dimATrue) dimATrue.setLevel(dimLATrue) if(dimBTrue) dimBTrue.setLevel(dimLBTrue) if(bulbsTrue) setColor(true) if(lockTrue) lockTrue.lock() if(unlockTrue) unlockTrue.unlock() if(openValveTrue) openValveTrue.open() if(closeValveTrue) closeValveTrue.close() if(thermoTrue) { if(thermoModeTrue) thermoTrue.setThermostatMode(thermoModeTrue) if(thermoSetHeatTrue) thermoTrue.setHeatingSetpoint(thermoSetHeatTrue) if(thermoSetCoolTrue) thermoTrue.setCoolingSetpoint(thermoSetCoolTrue) if(thermoFanTrue) thermoTrue.setThermostatFanMode(thermoFanTrue) } if(modeTrue) setLocationMode(modeTrue) if(myPhraseTrue) location.helloHome.execute(myPhraseTrue) if(pushTrue) sendPush(msgTrue ?: "Rule $app.label True") if(phoneTrue) sendSms(phoneTrue, msgTrue ?: "Rule $app.label True") } else { if(onSwitchFalse) onSwitchFalse.on() if(offSwitchFalse) offSwitchFalse.off() if(delayedOffFalse) runIn(delayMinutesFalse * 60, delayOffFalse) if(pendedOffFalse) runIn(pendMinutesFalse * 60, pendingOffFalse) if(pendedOffTrue) unschedule(pendingOffTrue) if(dimAFalse) dimAFalse.setLevel(dimLAFalse) if(dimBFalse) dimBFalse.setLevel(dimLBFalse) if(bulbsFalse) setColor(false) if(lockFalse) lockFalse.lock() if(unlockFalse) unlockFalse.unlock() if(openValveFalse) openValveFalse.open() if(closeValveFalse) closeValveFalse.close() if(thermoFalse) { if(thermoModeFalse) thermoFalse.setThermostatMode(thermoModeFalse) if(thermoSetHeatFalse) thermoFalse.setHeatingSetpoint(thermoSetHeatFalse) if(thermoSetCoolFalse) thermoFalse.setCoolingSetpoint(thermoSetCoolFalse) if(thermoFanFalse) thermoFalse.setThermostatFanMode(thermoFanFalse) } if(modeFalse) setLocationMode(modeFalse) if(myPhraseFalse) location.helloHome.execute(myPhraseFalse) if(pushFalse) sendPush(msgFalse ?: "Rule $app.label False") if(phoneFalse) sendSms(phoneFalse, msgFalse ?: "Rule $app.label False") } state.success = success log.info (success ? "$app.label is True" : "$app.label is False") } } } def allHandler(evt) { log.info "$app.label: $evt.displayName $evt.name $evt.value" runRule(false) } def startHandler() { runRule(false) } def stopHandler() { runRule(false) } def timeHandler() { runRule(false) } def delayOffTrue() { if(delayOnOffTrue && allOk) delayedOffTrue.on() else delayedOffTrue.off() } def pendingOffTrue() { if(pendOnOffTrue && allOk) pendedOffTrue.on() else pendedOffTrue.off() } def delayOffFalse() { if(delayOnOffFalse && allOk) delayedOffFalse.on() else delayedOffFalse.off() } def pendingOffFalse() { if(pendOnOffFalse && allOk) pendedOffFalse.on() else pendedOffFalse.off() } def delayRuleTrue() { runRule(true) } def delayRuleFalse() { runRule(true) } def disabledHandler(evt) { state.disabled = evt.value == "on" } // private execution filter methods follow private atTimeLabel() { def result = '' if (timeX == "Sunrise") result = "Sunrise" + offset(atSunriseOffset) else if(timeX == "Sunset") result = "Sunset" + offset(atSunsetOffset) else if(atTime) result = hhmm(atTime) } private hhmm(time, fmt = "h:mm a") { def t = timeToday(time, location.timeZone) def f = new java.text.SimpleDateFormat(fmt) f.setTimeZone(location.timeZone ?: timeZone(time)) f.format(t) } private offset(value) { def result = value ? ((value > 0 ? "+" : "") + value + " min") : "" } private timeIntervalLabel() { def result = "" if (startingX == "Sunrise" && endingX == "Sunrise") result = "Sunrise" + offset(startSunriseOffset) + " and Sunrise" + offset(endSunriseOffset) else if (startingX == "Sunrise" && endingX == "Sunset") result = "Sunrise" + offset(startSunriseOffset) + " and Sunset" + offset(endSunsetOffset) else if (startingX == "Sunset" && endingX == "Sunrise") result = "Sunset" + offset(startSunsetOffset) + " and Sunrise" + offset(endSunriseOffset) else if (startingX == "Sunset" && endingX == "Sunset") result = "Sunset" + offset(startSunsetOffset) + " and Sunset" + offset(endSunsetOffset) else if (startingX == "Sunrise" && ending) result = "Sunrise" + offset(startSunriseOffset) + " and " + hhmm(ending, "h:mm a z") else if (startingX == "Sunset" && ending) result = "Sunset" + offset(startSunsetOffset) + " and " + hhmm(ending, "h:mm a z") else if (starting && endingX == "Sunrise") result = hhmm(starting) + " and Sunrise" + offset(endSunriseOffset) else if (starting && endingX == "Sunset") result = hhmm(starting) + " and Sunset" + offset(endSunsetOffset) else if (starting && ending) result = hhmm(starting) + " and " + hhmm(ending, "h:mm a z") } private getAllOk() { modeZOk && !state.disabled //&& daysOk && timeOk } private hideOptionsSection() { (modesZ || physicalOverride || advanced) ? false : true } private getModeZOk() { def result = !modesZ || modesZ.contains(location.mode) // log.trace "modeZOk = $result" return result } private getModeOk() { def result = !modes || modes.contains(location.mode) // log.trace "modeOk = $result" return result } private getDaysOk() { def result = true if (days) { def df = new java.text.SimpleDateFormat("EEEE") if (location.timeZone) df.setTimeZone(location.timeZone) else df.setTimeZone(TimeZone.getTimeZone("America/New_York")) def day = df.format(new Date()) result = days.contains(day) } // log.trace "daysOk = $result" return result } private getTimeOk() { def result = true if((starting && ending) || (starting && endingX in ["Sunrise", "Sunset"]) || (startingX in ["Sunrise", "Sunset"] && ending) || (startingX in ["Sunrise", "Sunset"] && endingX in ["Sunrise", "Sunset"])) { def currTime = now() def start = null def stop = null def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: startSunriseOffset, sunsetOffset: startSunsetOffset) if(startingX == "Sunrise") start = s.sunrise.time else if(startingX == "Sunset") start = s.sunset.time else if(starting) start = timeToday(starting, location.timeZone).time s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: endSunriseOffset, sunsetOffset: endSunsetOffset) if(endingX == "Sunrise") stop = s.sunrise.time else if(endingX == "Sunset") stop = s.sunset.time else if(ending) stop = timeToday(ending,location.timeZone).time result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start } // log.trace "getTimeOk = $result" return result } private setColor(trufal) { def hueColor = 0 def saturation = 100 switch(trufal ? colorTrue : colorFalse) { case "White": hueColor = 52 saturation = 19 break; case "Daylight": hueColor = 53 saturation = 91 break; case "Soft White": hueColor = 23 saturation = 56 break; case "Warm White": hueColor = 20 saturation = 80 //83 break; case "Blue": hueColor = 70 break; case "Green": hueColor = 39 break; case "Yellow": hueColor = 25 break; case "Orange": hueColor = 10 break; case "Purple": hueColor = 75 break; case "Pink": hueColor = 83 break; case "Red": hueColor = 100 break; } def lightLevel = trufal ? colorLevelTrue : colorLevelFalse def newValue = [hue: hueColor, saturation: saturation, level: lightLevel as Integer ?: 100] if(trufal) bulbsTrue.setColor(newValue) else bulbsFalse.setColor(newValue) if(lightLevel == 0) {if(trufal) bulbsTrue.off() else bulbsFalse.off()} }