Advertisement
Guest User

Rule 1.9.1f

a guest
Apr 13th, 2016
418
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Groovy 134.05 KB | None | 0 0
  1. /**
  2.  *  Rule
  3.  *
  4.  *  Copyright 2015, 2016 Bruce Ravenel
  5.  *
  6.  *  Version 1.9.1f   3 Apr 2016
  7.  *
  8.  *  Version History
  9.  *
  10.  *  1.9.1   26 Mar 2016     Added yearly period, and two periodic trigger events
  11.  *  1.9.0   24 Mar 2016     Added periodic trigger, bug fixes re ZWN buttons
  12.  *  1.8.6   19 Mar 2016     Bug fixes re private Boolean as trigger event
  13.  *  1.8.5   13 Mar 2016     Added support for single button device, more private Boolean options, emergency heat
  14.  *  1.8.4   11 Mar 2016     Strengthened code pertaining to evaluation of malformed rules
  15.  *  1.8.3   3 Mar 2016      Changed method of reporting version number to Rule Machine
  16.  *  1.8.2   2 Mar 2016      Reorganized UI for selecting Actions, group pages, for speed of mobile app
  17.  *  1.8.1   2 Mar 2016      Added NOT for rules: NOT Condition and NOT (sub-rule)
  18.  *  1.8.0   2 Mar 2016      Major code cleanup; added random color, decimal for energy & power, and door control
  19.  *  1.7.16  28 Feb 2016     Added cancel for delay set private, and delay for restore with cancel
  20.  *  1.7.15  24 Feb 2016     Minor UI cleanup, bug fixes
  21.  *  1.7.14  23 Feb 2016     Added adjust thermostat setpoints, delay Set Private Boolean
  22.  *  1.7.13  21 Feb 2016     Improved custom command selection
  23.  *  1.7.12  19 Feb 2016     Added Private Boolean enable/disable, capture/restore color hue and saturation
  24.  *  1.7.11  15 Feb 2016     Further UI redesign to better distinguish triggers, added seconds for delayed on/off
  25.  *  1.7.10  9 Feb 2016      Added Music player condition, fixed Days of Week schedule bug
  26.  *  1.7.9   8 Feb 2016      Added set Boolean for other Rules, and Send notification event
  27.  *  1.7.8   7 Feb 2016      Added Evaluate Rule after Delay (loop possible), and Private Boolean
  28.  *  1.7.7   6 Feb 2016      UI cleanup and organization, added capture/restore for switches/dimmers
  29.  *  1.7.6   5 Feb 2016      Added action to update rule(s) to fix broken schedules due to ST issues
  30.  *  1.7.5   3 Feb 2016      Removed use of unschedule() for delay cancel, to avoid ST issues
  31.  *  1.7.4   2 Feb 2016      Redesign of UI to make it clearer between Triggers and Rules
  32.  *  1.7.3   2 Feb 2016      Bug fix for multi-button device with more than 4 buttons
  33.  *  1.7.2   31 Jan 2016     Added mode based dimming action, and cause rule actions action
  34.  *  1.7.1   30 Jan 2016     Added support for more buttons than 4 on button device, now as many as 20
  35.  *  1.7.0   27 Jan 2016     Fixed thermostat mode trigger/condition, added thermostat operating state condition
  36.  *  1.6.13  17 Jan 2016     Added Text to speech support
  37.  *  1.6.12  10 Jan 2016     Bug fix re removing parts of a rule
  38.  *  1.6.11  8 Jan 2016      Added offset to compare to device, fixed bugs in compare to device
  39.  *  1.6.10  6 Jan 2016      Returned Delay on/off pending cancel per user request, further debug of rule evaluation
  40.  *  1.6.9   6 Jan 2016      Fixed bugs related to presence in triggers, added Off as disable option, fixed bug in rule evaluation
  41.  *  1.6.8   1 Jan 2016      Added version numbers to main Rule Machine page, multi SMS
  42.  *  1.6.7   31 Dec 2015     Added speak to send message
  43.  *  1.6.6   30 Dec 2015     Expert multi-commands added per Maxwell
  44.  *  1.6.5   29 Dec 2015     Added action to set dimmers from a track dimmer, restored turn on/off after delay action
  45.  *  1.6.4   29 Dec 2015     Added action to adjust dimmers +/-, fixed time bug for triggered rule, fixed dimmer level condition bug
  46.  *  1.6.3   26 Dec 2015     Added color temperature bulb set, per John-Paul Smith
  47.  *  1.6.2   26 Dec 2015     New delay selection, minor bug fixes, sub-rule input improvements
  48.  *  1.6.1   24 Dec 2015     Added ability to send device name with push or SMS, show rule truth on main page
  49.  *  1.6.0   23 Dec 2015     Added expert commands per Mike Maxwell, and actions for camera to take photo burst
  50.  *  1.5.11  23 Dec 2015     Fixed bug that prevented old triggers from running, minor UI change for rule display
  51.  *  1.5.10  22 Dec 2015     Require capability choice for all but last rule or trigger
  52.  *  1.5.9   21 Dec 2015     Fixed overlap of Days of Week selection
  53.  *  1.5.8   20 Dec 2015     More repair for that same mode bug; fixed so triggered-rule not tested at install
  54.  *  1.5.7   19 Dec 2015     Fixed bug re: selecting mode as condition/trigger, UI display
  55.  *  1.5.6   18 Dec 2015     Fixed bug re: old triggers not editable
  56.  *  1.5.5   17 Dec 2015     Added milliseconds to Delayed off, uses dev.off([delay: msec]) instead of runIn()
  57.  *
  58.  *  This software if free for Private Use. You may use and modify the software without distributing it.
  59.  *  
  60.  *  This software and derivatives may not be used for commercial purposes.
  61.  *  You may not modify, distribute or sublicense this software.
  62.  *  You may not grant a sublicense to modify and distribute this software to third parties not included in the license.
  63.  *
  64.  *  Software is provided without warranty and the software author/license owner cannot be held liable for damages.
  65.  *
  66.  */
  67.  
  68. definition(
  69.     name: "Rule",
  70.     namespace: "bravenel",
  71.     author: "Bruce Ravenel",
  72.     description: "Rule",
  73.     category: "Convenience",
  74.     parent: "bravenel:Rule Machine",
  75.     iconUrl: "https://raw.githubusercontent.com/bravenel/Rule-Trigger/master/smartapps/bravenel/RuleMachine.png",
  76.     iconX2Url: "https://raw.githubusercontent.com/bravenel/Rule-Trigger/master/smartapps/bravenel/RuleMachine%402x.png",
  77. )
  78.  
  79. preferences {
  80.     page(name: "mainPage")
  81.     page(name: "selectTrig")
  82.     page(name: "selectCTrig")
  83.     page(name: "selectRule")
  84.     page(name: "selectActions")
  85.     page(name: "selectTriggers")
  86.     page(name: "selectConditions")
  87.     page(name: "defineRule")
  88.     page(name: "certainTime")
  89.     page(name: "certainTimeX")
  90.     page(name: "atCertainTime")
  91.     page(name: "periodic")
  92.     page(name: "selectActionsTrue")
  93.     page(name: "selectActionsFalse")
  94.     page(name: "delayTruePage")
  95.     page(name: "delayFalsePage")
  96.     page(name: "switchTruePage")
  97.     page(name: "switchFalsePage")
  98.     page(name: "dimmerTruePage")
  99.     page(name: "dimmerFalsePage")
  100.     page(name: "doorTruePage")
  101.     page(name: "doorFalsePage")
  102.     page(name: "modeTruePage")
  103.     page(name: "modeFalsePage")
  104.     page(name: "ruleTruePage")
  105.     page(name: "ruleFalsePage")
  106.     page(name: "selectMsgTrue")
  107.     page(name: "selectMsgFalse")
  108.     page(name: "selectCustomActions")
  109. }
  110.  
  111. //
  112. // 
  113. //
  114.  
  115. def appVersion() {
  116.     return "1.9.1f"
  117. }
  118.  
  119. def mainPage() {
  120.     //expert settings for rule
  121.     try {
  122.         state.isExpert = parent.isExpert()
  123.         if (state.isExpert) state.cstCmds = parent.getCommands()
  124.         else state.cstCmds = []
  125.     }
  126.     catch (e) {log.error "Please update Rule Machine to V1.6 or later"}
  127.     if(state.private == null) state.private = "true"
  128.     def myTitle = "Define a Rule, Trigger or Actions\n"
  129.     if(state.howManyT > 1 || state.isTrig) myTitle = "Define a Trigger"
  130.     else if(state.howMany > 1) myTitle = "Define a Rule"
  131.     else if(app.label != null) myTitle = "Define Actions"
  132.     def myUninstall = state.isTrig || state.isRule || state.howManyT > 1 || state.howMany > 1 || (app.label != "Rule" && app.label != null)
  133.     dynamicPage(name: "mainPage", title: myTitle, uninstall: myUninstall, install: myUninstall) {
  134.         if(state.isTrig) {    // old Trigger
  135.             section() {    
  136.                 label title: "Name the Trigger", required: true
  137.                 def condLabel = conditionLabel()
  138.                 href "selectConditions", title: "Select Trigger Events", description: condLabel ? (condLabel) : "Tap to set", required: true, state: condLabel ? "complete" : null, submitOnChange: true
  139.                 href "selectActionsTrue", title: "Select Actions", description: state.actsTrue ? state.actsTrue : "Tap to set", state: state.actsTrue ? "complete" : null
  140.             }
  141.             section(title: "Restrictions", hidden: hideOptionsSection(), hideable: true) {
  142.                 def timeLabel = timeIntervalLabel()
  143.                 href "certainTime", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null
  144.                 input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
  145.                     options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
  146.                 input "modesY", "mode", title: "Only when mode is", multiple: true, required: false            
  147.                 input "disabled", "capability.switch", title: "Switch to disable trigger when ON", required: false, multiple: false
  148.             }    
  149.         } else if(state.isRule) {   // old Rule
  150.             section() {
  151.                 label title: "Name the Rule", required: true
  152.                 def condLabel = conditionLabel()
  153.                 href "selectConditions", title: "Select Conditions", description: condLabel ? (condLabel) : "Tap to set", required: true, state: condLabel ? "complete" : null, submitOnChange: true
  154.                 href "defineRule", title: "Define the Rule", description: state.str ? (state.str) : "Tap to set", state: state.str ? "complete" : null, submitOnChange: true
  155.                 href "selectActionsTrue", title: "Select Actions for True", description: state.actsTrue ? state.actsTrue : "Tap to set", state: state.actsTrue ? "complete" : null, submitOnChange: true
  156.                 href "selectActionsFalse", title: "Select Actions for False", description: state.actsFalse ? state.actsFalse : "Tap to set", state: state.actsFalse ? "complete" : null, submitOnChange: true
  157.             }
  158.             section(title: "Restrictions", hidden: hideOptionsSection(), hideable: true) {
  159.                 input "modesZ", "mode", title: "Evaluate only when mode is", multiple: true, required: false
  160.                 input "disabled", "capability.switch", title: "Switch to disable rule when ON", required: false, multiple: false
  161.             }  
  162.         }
  163.         else if(state.howMany > 1 && state.howManyT in [null, 1])   getRule()       // Existing Rule   
  164.         else if(state.howManyT > 1 && state.howMany in [null, 1])   getTrigger()    // Existing Trigger
  165.         else if(state.howManyT > 1)                                 getCTrigger()   // Existing Conditional Trigger
  166.         else if(app.label != "Rule" && app.label != null)           getActions()    // Existing Actions
  167.         else {                                                                      // New Rule, Trigger, Conditional Trigger or Actions
  168.             section("A Rule uses events for conditions and then\ntests a rule to run actions")                      {href "selectRule", title: "Define a Rule", description: "Tap to set"}
  169.             section("A Trigger uses events to run actions")                                                         {href "selectTrig", title: "Define a Trigger", description: "Tap to set"}
  170.             section("A Conditional Trigger uses events to run actions\nbased on conditions tested under a rule")    {href "selectCTrig", title: "Define a Conditional Trigger", description: "Tap to set"}
  171.             section("Other Rules can run these Actions")                                                            {href "selectActions", title: "Define Actions", description: "Tap to set"}
  172.         }
  173.     }
  174. }
  175.  
  176. def selectRule() {
  177.     dynamicPage(name: "selectRule", title: "Select Conditions, Rule and Actions", uninstall: true, install: true) {
  178.         getRule()
  179.     }
  180. }
  181.  
  182. def selectTrig() {
  183.     dynamicPage(name: "selectTrig", title: "Select Trigger Events and Actions", uninstall: true, install: true) {
  184.         getTrigger()
  185.     }
  186. }
  187.  
  188. def selectCTrig() {
  189.     dynamicPage(name: "selectCTrig", title: "Select Triggers, Conditions, Rule and Actions", uninstall: true, install: true) {
  190.         getCTrigger()
  191.     }
  192. }
  193.  
  194. def selectActions() {
  195.     dynamicPage(name: "selectActions", title: "Select Actions", uninstall: true, install: true) {
  196.         getActions()
  197.     }
  198. }
  199.  
  200. def getRule() {
  201.     section() {
  202.         label title: "Name the Rule", required: true
  203.         def condLabel = conditionLabel()
  204.         href "selectConditions", title: "Select Conditions ", description: condLabel ? (condLabel) : "Tap to set", state: condLabel ? "complete" : null, submitOnChange: true
  205.         def ruleLabel = rulLabl()
  206.         href "defineRule", title: "Define Rule", description: ruleLabel ? (ruleLabel) : "Tap to set", state: ruleLabel ? "complete" : null, submitOnChange: true
  207.         href "selectActionsTrue", title: "Select Actions for True", description: state.actsTrue ? state.actsTrue : "Tap to set", state: state.actsTrue ? "complete" : null, submitOnChange: true
  208.         href "selectActionsFalse", title: "Select Actions for False", description: state.actsFalse ? state.actsFalse : "Tap to set", state: state.actsFalse ? "complete" : null, submitOnChange: true
  209.     }
  210.     getMoreOptions()
  211. }
  212.  
  213. def getTrigger() {
  214.     section() {
  215.         label title: "Name the Trigger", required: true
  216.         def trigLabel = triggerLabel()
  217.         href "selectTriggers", title: "Select Trigger Events", description: trigLabel ? (trigLabel) : "Tap to set", state: trigLabel ? "complete" : null, submitOnChange: true
  218.         href "selectActionsTrue", title: "Select Actions", description: state.actsTrue ? state.actsTrue : "Tap to set", state: state.actsTrue ? "complete" : null, submitOnChange: true
  219.     }
  220.     getMoreOptions()
  221. }
  222.  
  223. def getCTrigger() {
  224.     section() {
  225.         label title: "Name the Conditional Trigger", required: true
  226.         def trigLabel = triggerLabel()
  227.         href "selectTriggers", title: "Select Trigger Events", description: trigLabel ? (trigLabel) : "Tap to set", state: trigLabel ? "complete" : null, submitOnChange: true
  228.         def condLabel = conditionLabel()
  229.         href "selectConditions", title: "Select Conditions ", description: condLabel ? (condLabel) : "Tap to set", state: condLabel ? "complete" : null, submitOnChange: true
  230.         def ruleLabel = rulLabl()
  231.         href "defineRule", title: "Define Rule", description: ruleLabel ? (ruleLabel) : "Tap to set", state: ruleLabel ? "complete" : null, submitOnChange: true
  232.         href "selectActionsTrue", title: "Select Actions for True", description: state.actsTrue ? state.actsTrue : "Tap to set", state: state.actsTrue ? "complete" : null, submitOnChange: true
  233.         href "selectActionsFalse", title: "Select Actions for False", description: state.actsFalse ? state.actsFalse : "Tap to set", state: state.actsFalse ? "complete" : null, submitOnChange: true
  234.     }
  235.     getMoreOptions()
  236. }
  237.  
  238. def getActions() {
  239.     section() {
  240.         label title: "Name the Actions", required: true
  241.         href "selectActionsTrue", title: "Select Actions", description: state.actsTrue ? state.actsTrue : "Tap to set", state: state.actsTrue ? "complete" : null, submitOnChange: true
  242.     }
  243.     getMoreOptions()
  244. }
  245.  
  246. def getMoreOptions() {
  247.     section(title: "Restrictions", hidden: hideOptionsSection(), hideable: true) {
  248.         def timeLabel = timeIntervalLabel()
  249.         href "certainTime", title: "Only between two times", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null
  250.         input "daysY", "enum", title: "Only on certain days of the week", multiple: true, required: false,
  251.             options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
  252.         input "modesY", "mode", title: "Only when mode is", multiple: true, required: false            
  253.         input "disabled", "capability.switch", title: "Switch to disable Rule", required: false, multiple: false, submitOnChange: true
  254.         if(disabled) input "disabledOff", "bool", title: "Disable when Off? On is default", required: false, defaultValue: false
  255.         def privy = state.private
  256.         input "usePrivateDisable", "bool", title: "Enable/Disable with private Boolean? [$privy]", required: false
  257.     }    
  258. }
  259.  
  260. def certainTime() {
  261.     dynamicPage(name: "certainTime", title: "Between two times", uninstall: false) {
  262.         section() {
  263.             input "startingX", "enum", title: "Starting at", options: ["A specific time", "Sunrise", "Sunset"], defaultValue: "A specific time", submitOnChange: true, required: false
  264.             if(startingX in [null, "A specific time"]) input "starting", "time", title: "Start time", required: false
  265.             else {
  266.                 if(startingX == "Sunrise") input "startSunriseOffset", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false
  267.                 else if(startingX == "Sunset") input "startSunsetOffset", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false
  268.             }
  269.         }
  270.         section() {
  271.             input "endingX", "enum", title: "Ending at", options: ["A specific time", "Sunrise", "Sunset"], defaultValue: "A specific time", submitOnChange: true, required: false
  272.             if(endingX in [null, "A specific time"]) input "ending", "time", title: "End time", required: false
  273.             else {
  274.                 if(endingX == "Sunrise") input "endSunriseOffset", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false
  275.                 else if(endingX == "Sunset") input "endSunsetOffset", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false
  276.             }
  277.         }
  278.     }
  279. }
  280.  
  281. // Trigger and Condition input code follows
  282.  
  283. def selectTriggers() {
  284.     selectTrigCond(true)
  285. }
  286.  
  287. def selectConditions() {
  288.     selectTrigCond(false)
  289. }
  290.  
  291. def selectTrigCond(isTrig) {
  292.     def ctStr = isTrig ? "tCapab" : "rCapab"
  293.     def ct = settings.findAll{it.key.startsWith(ctStr)}
  294.     def howMany = ct.size() + 1
  295.     if(isTrig) state.howManyT = howMany
  296.     else state.howMany = howMany
  297.     def excludes = (state.isTrig || isTrig) ? ["Certain Time", "Periodic", "Mode", "Routine", "Button", "Smart Home Monitor", "Private Boolean"] : ["Time of day", "Days of week", "Mode", "Smart Home Monitor", "Private Boolean"]
  298.     def pageName = isTrig ? "selectTriggers" : "selectConditions"
  299.     dynamicPage(name: pageName, title: (state.isTrig || isTrig) ? "Select Trigger Events (ANY will trigger)" : "Select Conditions", uninstall: false) {
  300.         for (int i = 1; i <= howMany; i++) {
  301.         def thisCapab = isTrig ? "tCapab$i" : "rCapab$i"
  302.         section((state.isTrig || isTrig) ? "Event Trigger #$i" : "Condition #$i") {            
  303.                 getCapab(thisCapab, isTrig, i < howMany)
  304.                 def myCapab = settings.find {it.key == thisCapab}
  305.                 if(myCapab) {
  306.                     def xCapab = myCapab.value
  307.                     if(!(xCapab in excludes)) {
  308.                         def thisDev = isTrig ? "tDev$i" : "rDev$i"
  309.                         getDevs(xCapab, thisDev, true)
  310.                         def myDev = settings.find {it.key == thisDev}
  311.                         if(myDev) if(myDev.value.size() > 1 && !isTrig) getAnyAll(thisDev)
  312.                         if(xCapab in ["Temperature", "Humidity", "Illuminance", "Dimmer level", "Energy meter", "Power meter", "Battery"]) getRelational(thisDev)
  313.                     } else if(xCapab == "Button") getButton(isTrig ? "tDev$i" : "rDev$i")
  314.                     getState(xCapab, i, isTrig)
  315.                 }
  316.             }
  317.         }
  318.     }
  319. }
  320.  
  321. def getCapab(myCapab, isTrig, isReq) {  
  322.     def myOptions = null
  323.     if(state.isRule || !isTrig) myOptions = ["Acceleration", "Battery", "Carbon monoxide detector", "Contact", "Days of week", "Dimmer level", "Energy meter", "Garage door", "Humidity", "Illuminance", "Lock",
  324.         "Mode", "Motion", "Power meter", "Presence", "Rule truth", "Smart Home Monitor", "Smoke detector", "Switch", "Temperature", "Private Boolean", "Door",
  325.         "Thermostat Mode", "Thermostat State", "Time of day", "Water sensor", "Music player"]
  326.     if(state.isTrig || isTrig) myOptions = ["Acceleration", "Battery", "Button", "Carbon monoxide detector", "Certain Time", "Periodic", "Contact", "Dimmer level", "Energy meter", "Garage door", "Humidity", "Illuminance",
  327.         "Lock", "Mode", "Motion", "Physical Switch", "Power meter", "Presence", "Routine", "Rule truth", "Smart Home Monitor", "Smoke detector", "Switch", "Temperature", "Door",
  328.         "Thermostat Mode", "Thermostat State", "Water sensor", "Private Boolean", "Music player"]
  329.     def result = input myCapab, "enum", title: "Select capability", required: isReq, options: myOptions.sort(), submitOnChange: true
  330. }
  331.  
  332. def getDevs(myCapab, dev, multi) {
  333.     def thisName = ""
  334.     def thisCapab = ""
  335.     switch(myCapab) {
  336.         case "Switch":
  337.             thisName = "Switches"
  338.             thisCapab = "switch"
  339.             break
  340.         case "Physical Switch":
  341.             thisName = "Switches"
  342.             thisCapab = "switch"
  343.             break
  344.         case "Motion":
  345.             thisName = "Motion sensors"
  346.             thisCapab = "motionSensor"
  347.             break
  348.         case "Acceleration":
  349.             thisName = "Acceleration sensors"
  350.             thisCapab = "accelerationSensor"
  351.             break        
  352.         case "Contact":
  353.             thisName = "Contact sensors"
  354.             thisCapab = "contactSensor"
  355.             break
  356.         case "Presence":
  357.             thisName = "Presence sensors"
  358.             thisCapab = "presenceSensor"
  359.             break
  360.         case "Garage door":
  361.             thisName = "Garage doors"
  362.             thisCapab = "garageDoorControl"
  363.             break
  364.         case "Door":
  365.             thisName = "Doors"
  366.             thisCapab = "doorControl"
  367.             break
  368.         case "Lock":
  369.             thisName = "Locks"
  370.             thisCapab = "lock"
  371.             break
  372.         case "Dimmer level":
  373.             thisName = "Dimmer" + (multi ? "s" : "")
  374.             thisCapab = "switchLevel"
  375.             break
  376.         case "Temperature":
  377.             thisName = "Temperature sensor" + (multi ? "s" : "")
  378.             thisCapab = "temperatureMeasurement"
  379.             break
  380.         case "Thermostat Mode":
  381.             thisName = "Thermostat" + (multi ? "s" : "")
  382.             thisCapab = "thermostat"
  383.             break
  384.         case "Thermostat State":
  385.             thisName = "Thermostat" + (multi ? "s" : "")
  386.             thisCapab = "thermostat"
  387.             break
  388.         case "Humidity":
  389.             thisName = "Humidity sensor" + (multi ? "s" : "")
  390.             thisCapab = "relativeHumidityMeasurement"
  391.             break
  392.         case "Illuminance":
  393.             thisName = "Illuminance sensor" + (multi ? "s" : "")
  394.             thisCapab = "illuminanceMeasurement"
  395.             break
  396.         case "Energy meter":
  397.             thisName = "Energy meter" + (multi ? "s" : "")
  398.             thisCapab = "energyMeter"
  399.             break
  400.         case "Power meter":
  401.             thisName = "Power meter" + (multi ? "s" : "")
  402.             thisCapab = "powerMeter"
  403.             break
  404.         case "Carbon monoxide detector":
  405.             thisName = "CO detector" + (multi ? "s" : "")
  406.             thisCapab = "carbonMonoxideDetector"
  407.             break
  408.         case "Smoke detector":
  409.             thisName = "Smoke detector" + (multi ? "s" : "")
  410.             thisCapab = "smokeDetector"
  411.             break
  412.         case "Water sensor":
  413.             thisName = "Water sensors"
  414.             thisCapab = "waterSensor"
  415.             break
  416.         case "Music player":
  417.             thisName = "Music player"
  418.             thisCapab = "musicPlayer"
  419.             break
  420.         case "Rule truth":
  421.             def theseRules = parent.ruleList(app.label)
  422.             def result = input dev, "enum", title: "Rules", required: true, multiple: multi, submitOnChange: true, options: theseRules.sort()
  423.             return result
  424.         case "Battery":
  425.             thisName = multi ? "Batteries" : "Battery"
  426.             thisCapab = "battery"
  427.     }
  428.     def result = input dev, "capability.$thisCapab", title: thisName, required: true, multiple: multi, submitOnChange: true
  429. }
  430.  
  431. def getAnyAll(myDev) {
  432.     def result = input "All$myDev", "bool", title: "All of these?", required: false
  433. }
  434.  
  435. def getRelational(myDev) {
  436.     def result = input "Rel$myDev", "enum", title: "Choose comparison", required: true, options: ["=", "!=", "<", ">", "<=", ">="]
  437. }
  438.  
  439. def getButton(dev) {
  440.     def numNames = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
  441.         "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty"]
  442.     def result = input "$dev", "capability.button", title: "Button Device", required: true, multiple: false, submitOnChange: true
  443.     def thisDev = settings.find{it.key == "$dev"}
  444.     if(thisDev) {
  445.         input "numButtons$dev", "number", title: "Number of buttons? (Default 4)", range: "1..20", required: false, submitOnChange: true, description: "4"
  446.         def numButtons = settings.find{it.key == "numButtons$dev"}
  447.         numButtons = numButtons ? numButtons.value : 4
  448.         def butOpts = ["one"]
  449.         if(numButtons > 1) {
  450.             for (int i = 1; i < numButtons; i++) butOpts[i] = numNames[i]
  451.             input "Button$dev", "enum", title: "Button number", required: true, multiple: false, submitOnChange: true, options: butOpts, defaultValue: "one"
  452.         }
  453.     }
  454. }
  455.  
  456. def getState(myCapab, n, isTrig) {
  457.     def result = null
  458.     def param = [n: n]
  459.     def myState = isTrig ? "tstate$n" : "state$n"
  460.     def myIsDev = isTrig ? "istDev$n" : "isDev$n"
  461.     def myRelDev = isTrig ? "reltDevice$n" : "relDevice$n"
  462.     def isRule = state.isRule || (state.howMany > 1 && !isTrig)
  463.     def phrase = isRule ? "state" : "becomes"
  464.     def presPhrase = isRule ? "state" : " ..."
  465.     def swphrase = isRule ? "state" : "turns"
  466.     def presoptions = isRule ? ["present", "not present"] : ["arrives", "leaves"]
  467.     def presdefault = isRule ? "present" : "arrives"
  468.     def lockphrase = isRule ? "state" : "is"
  469.     def days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
  470.     if     (myCapab == "Switch")                    result = input myState, "enum", title: "Switch $swphrase",          options: ["on", "off"],                     defaultValue: "on"
  471.     else if(myCapab == "Physical Switch")           result = input myState, "enum", title: "Switch turns ",             options: ["on", "off"],                     defaultValue: "on"
  472.     else if(myCapab == "Motion")                    result = input myState, "enum", title: "Motion $phrase",            options: ["active", "inactive"],            defaultValue: "active"
  473.     else if(myCapab == "Acceleration")              result = input myState, "enum", title: "Acceleration $phrase",      options: ["active", "inactive"],            defaultValue: "active"
  474.     else if(myCapab == "Contact")                   result = input myState, "enum", title: "Contact $phrase",           options: ["open", "closed"],                defaultValue: "open"
  475.     else if(myCapab == "Presence")                  result = input myState, "enum", title: "Presence $presPhrase",      options: presoptions,                       defaultValue: presdefault
  476.     else if(myCapab == "Garage door")               result = input myState, "enum", title: "Garage door $phrase",       options: ["closed", "open", "opening", "closing", "unknown"], defaultValue: "open"
  477.     else if(myCapab == "Door")                      result = input myState, "enum", title: "Door $phrase",              options: ["closed", "open", "opening", "closing", "unknown"], defaultValue: "open"
  478.     else if(myCapab == "Lock")                      result = input myState, "enum", title: "Lock $lockphrase",          options: ["locked", "unlocked"],            defaultValue: "unlocked"
  479.     else if(myCapab == "Thermostat Mode")           result = input myState, "enum", title: "Thermostat mode ",          options: ["heat", "cool", "auto", "off", "emergency heat"], defaultValue: "heat"
  480.     else if(myCapab == "Thermostat State")          result = input myState, "enum", title: "Thermostat state ",         options: ["heating", "idle", "pending cool", "vent economizer", "cooling", "pending heat", "fan only"], defaultValue: "heating"
  481.     else if(myCapab == "Carbon monoxide detector")  result = input myState, "enum", title: "CO $phrase ",               options: ["clear", ,"detected", "tested"],  defaultValue: "detected"
  482.     else if(myCapab == "Smoke detector")            result = input myState, "enum", title: "Smoke $phrase ",            options: ["clear", ,"detected", "tested"],  defaultValue: "detected"
  483.     else if(myCapab == "Water sensor")              result = input myState, "enum", title: "Water $phrase",             options: ["dry", "wet"],                    defaultValue: "wet"
  484.     else if(myCapab == "Button")                    result = input myState, "enum", title: "Button pushed or held ",    options: ["pushed", "held"],                defaultValue: "pushed"
  485.     else if(myCapab == "Rule truth")                result = input myState, "enum", title: "Rule truth $phrase ",       options: ["true", "false"],                 defaultValue: "true"
  486.     else if(myCapab == "Music player")              result = input myState, "enum", title: "Playing state",             options: ["playing", "paused","stopped"],   defaultValue: "playing"
  487.     else if(myCapab == "Private Boolean")           result = input myState, "enum", title: "Private Boolean $phrase ",  options: ["true", "false"],                 defaultValue: "true"
  488.     else if(myCapab == "Smart Home Monitor")        result = input myState, "enum", title: "SHM $phrase",               options: ["away" : "Arm (away)", "stay" : "Arm (stay)", "off" : "Disarm"]
  489.     else if(myCapab in ["Temperature", "Humidity", "Illuminance", "Energy meter", "Power meter", "Battery", "Dimmer level"]) {
  490.         input myIsDev, "bool", title: "Relative to another device?", multiple: false, required: false, submitOnChange: true, defaultValue: false
  491.         def myDev = settings.find {it.key == myIsDev}
  492.         if(myDev && myDev.value) {
  493.             getDevs(myCapab, myRelDev, false)
  494.             if     (myCapab == "Temperature")           result = input myState, "decimal",  title: "Temperature offset ",   range: "*..*",      defaultValue: 0
  495.             else if(myCapab == "Humidity")              result = input myState, "number",   title: "Humidity offset",       range: "-100..100", defaultValue: 0
  496.             else if(myCapab == "Illuminance")           result = input myState, "number",   title: "Illuminance offset",    range: "*..*",      defaultValue: 0
  497.             else if(myCapab == "Dimmer level")          result = input myState, "number",   title: "Dimmer offset",         range: "-100..100", defaultValue: 0
  498.             else if(myCapab == "Energy meter")          result = input myState, "decimal",  title: "Energy level offset",   range: "*..*",      defaultValue: 0
  499.             else if(myCapab == "Power meter")           result = input myState, "decimal",  title: "Power level offset",    range: "*..*",      defaultValue: 0
  500.             else if(myCapab == "Battery")               result = input myState, "number",   title: "Battery level offset",  range: "-100..100", defaultValue: 0
  501.         }
  502.         else if(myCapab == "Temperature")           result = input myState, "decimal",  title: "Temperature becomes ",  range: "*..*"
  503.         else if(myCapab == "Humidity")              result = input myState, "number",   title: "Humidity becomes",      range: "0..100"
  504.         else if(myCapab == "Illuminance")           result = input myState, "number",   title: "Illuminance becomes",   range: "0..*"
  505.         else if(myCapab == "Dimmer level")          result = input myState, "number",   title: "Dimmer level",          range: "0..100"
  506.         else if(myCapab == "Energy meter")          result = input myState, "decimal",  title: "Energy level becomes",  range: "0..*"
  507.         else if(myCapab == "Power meter")           result = input myState, "decimal",  title: "Power level becomes",   range: "*..*"
  508.         else if(myCapab == "Battery")               result = input myState, "number",   title: "Battery level becomes", range: "0..100"
  509.     } else if(myCapab == "Days of week")            result = input "days",  "enum",     title: "On certain days of the week", multiple: true, required: false, options: days
  510.     else if(myCapab == "Mode") {
  511.         def myModes = []
  512.         location.modes.each {myModes << "$it"}
  513.         def modeVar = (state.isRule || state.howMany > 1) ? "modes" : "modesX"
  514.         result = input modeVar, "enum", title: "Select mode(s)", multiple: true, required: false, options: myModes.sort()
  515.     } else if(myCapab == "Time of day") {
  516.         def timeLabel = timeIntervalLabelX()
  517.         href "certainTimeX", title: "Between two times", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null
  518.     } else if(myCapab == "Certain Time") {
  519.         def atTimeLabel = atTimeLabel()
  520.         href "atCertainTime", title: "At a certain time", description: atTimeLabel ?: "Tap to set", state: atTimeLabel ? "complete" : null
  521.     } else if(myCapab == "Periodic") {
  522.         state.thisN = n
  523.         def periodLabel = periodicLabel(n)
  524.         href "periodic", title: "Periodic schedule", description: periodLabel ?: "Tap to set", state: periodLabel ? "complete" : null, params: param
  525.     } else if(myCapab == "Routine") {
  526.         def phrases = location.helloHome?.getPhrases()*.label
  527.         result = input myState, "enum", title: "When this routine runs", multiple: false, required: false, options: phrases
  528.     }
  529.     def whatState = settings.find {it.key == myState}
  530. }
  531.  
  532. def certainTimeX() {
  533.     dynamicPage(name: "certainTimeX", title: "Between two times", uninstall: false) {
  534.         section() {
  535.             input "startingXX", "enum", title: "Starting at", options: ["A specific time", "Sunrise", "Sunset"], defaultValue: "A specific time", submitOnChange: true
  536.             if(startingXX in [null, "A specific time"]) input "startingA", "time", title: "Start time", required: false
  537.             else {
  538.                 if(startingXX == "Sunrise") input "startSunriseOffsetX", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false
  539.                 else if(startingXX == "Sunset") input "startSunsetOffsetX", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false
  540.             }
  541.         }
  542.         section() {
  543.             input "endingXX", "enum", title: "Ending at", options: ["A specific time", "Sunrise", "Sunset"], defaultValue: "A specific time", submitOnChange: true
  544.             if(endingXX in [null, "A specific time"]) input "endingA", "time", title: "End time", required: false
  545.             else {
  546.                 if(endingXX == "Sunrise") input "endSunriseOffsetX", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false
  547.                 else if(endingXX == "Sunset") input "endSunsetOffsetX", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false
  548.             }
  549.         }
  550.     }
  551. }
  552.  
  553. def atCertainTime() {
  554.     dynamicPage(name: "atCertainTime", title: "At a certain time", uninstall: false) {
  555.         section() {
  556.             input "timeX", "enum", title: "At time or sunrise/sunset?", options: ["A specific time", "Sunrise", "Sunset"], defaultValue: "A specific time", submitOnChange: true
  557.             if(timeX in [null, "A specific time"]) input "atTime", "time", title: "At this time", required: false
  558.             else {
  559.                 if(timeX == "Sunrise") input "atSunriseOffset", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false
  560.                 else if(timeX == "Sunset") input "atSunsetOffset", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false
  561.             }            
  562.         }
  563.     }
  564. }
  565.  
  566. def periodic(param) {
  567.     dynamicPage(name: "periodic", title: "Periodic schedule", uninstall: false) {
  568. //      def n = param.n
  569.         if(param.n != null) state.thisN = param.n
  570.         def n = state.thisN
  571.         section() {
  572.             input "whichPeriod$n", "enum", title: "Select periodic frequency", submitOnChange: true, required: true, options: ["Minutes", "Hourly", "Daily", "Weekly", "Monthly", "Yearly"]
  573. //            log.debug "periodic: ${settings["whichPeriod$n"]}"
  574.             switch(settings["whichPeriod$n"]) {
  575.                 case "Minutes":
  576.                     if(!settings["selectMinutesC$n"]) input "everyNMinutesC$n", "bool", title: " > Every n minutes?", submitOnChange: true, required: false
  577.                     if(settings["everyNMinutesC$n"]) input "everyNC$n", "number", title: " > number of minutes", range: "1..59", submitOnChange: true, required: false, defaultValue: 1
  578.                     if(!settings["everyNMinutesC$n"]) input "selectMinutesC$n", "enum", title: " > Each selected minute", submitOnChange: true, required: false, multiple: true,
  579.                         options: [   "0",  "1",  "2",  "3",  "4",  "5",  "6",  "7",  "8",  "9",
  580.                                     "10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
  581.                                     "20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
  582.                                     "30", "31", "32", "33", "34", "35", "36", "37", "38", "39",
  583.                                     "40", "41", "42", "43", "44", "45", "46", "47", "48", "49",
  584.                                     "50", "51", "52", "53", "54", "55", "56", "57", "58", "59"]
  585.                     break
  586.                 case "Hourly":
  587.                     if(!settings["selectHoursC$n"]) input "everyNHoursC$n", "bool", title: " > Every n hours?", submitOnChange: true, required: false
  588.                     if(settings["everyNHoursC$n"]) {
  589.                         input "everyNHC$n", "number", title: " > number of hours", range: "1..23", submitOnChange: true, required: false, defaultValue: 1
  590.                         input "startingHC$n", "time", title: " > Starting at", submitOnChange: true, required: false, defaultValue: "2016-03-23T12:00:00.000" + gmtOffset()
  591.                     }
  592.                     if(!settings["everyNHoursC$n"]) {
  593.                         input "selectHoursC$n", "enum", title: " > Each selected hour", submitOnChange: true, required: false, multiple: true,
  594.                             options: [   "0",  "1",  "2",  "3",  "4",  "5",  "6",  "7",  "8",  "9",
  595.                                         "10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
  596.                                         "20", "21", "22", "23"]
  597.                         input "startingHCX$n", "number", title: " > Starting minutes after the hour", submitOnChange: true, required: false, range: "0..59", defaultValue: 0
  598.                     }
  599.                     break
  600.                 case "Daily":
  601.                     if(!settings["selectDoMC$n"] && !settings["everyWeekDay$n"]) input "everyNDoMC$n", "bool", title: " > Every n days?", submitOnChange: true, required: false
  602.                     if(settings["everyNDoMC$n"]) input "everyNDC$n", "number", title: " > number of days", range: "1..31", submitOnChange: true, required: false, defaultValue: 1
  603.                     if(!settings["selectDoMC$n"] && !settings["everyNDoMC$n"]) input "everyWeekDay$n", "bool", title: " > Every weekday?", submitOnChange: true, required: false
  604.                     if(!settings["everyNDoMC$n"] && !settings["everyWeekDay$n"]) input "selectDoMC$n", "enum", title: " > Each selected day of the month", submitOnChange: true, required: false, multiple: true,
  605.                         options: [         "1",  "2",  "3",  "4",  "5",  "6",  "7",  "8",  "9",
  606.                                     "10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
  607.                                     "20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
  608.                                     "30", "31"]
  609.                     input "startingDC$n", "time", title: " > At time", submitOnChange: true, required: false, defaultValue: "2016-03-23T12:00:00.000" + gmtOffset()
  610.                     break
  611.                 case "Weekly":
  612.                     if(!settings["everyDoWC$n"]) input "selectDoWC$n", "enum", title: " > Each selected day of the week", submitOnChange: true, required: false, multiple: true,
  613.                         options: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
  614.                     input "startingWC$n", "time", title: " > Starting at", submitOnChange: true, required: false, defaultValue: "2016-03-23T12:00:00.000" + gmtOffset()
  615.                     break
  616.                 case "Monthly":
  617.                     if(!settings["weeklyMC$n"]) input "dayMC$n", "number", title: " > On day number", range: "1..31", submitOnChange: true, required: false
  618.                     if(!settings["selectMonthC$n"] && !settings["weeklyMC$n"]) input "everyNMC$n", "number", title: " > Of every n months", range: "1..12", submitOnChange: true, required: false
  619.                     if(!settings["everyNMC$n"] && !settings["weeklyMC$n"]) input "selectMonthC$n", "enum", title: " > Of each selected month", submitOnChange: true, required: false, multiple: true,
  620.                         options: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
  621.                     if(!settings["selectMonthC$n"] && !settings["everyNMC$n"]) {
  622.                         input "weeklyMC$n", "enum", title: " > In the week of month ...", submitOnChange: true, required: false, options: ["First", "Second", "Third", "Fourth"]
  623.                         if(settings["weeklyMC$n"]) {
  624.                             input "dailyMC$n", "enum", title: " > On day of week ...", submitOnChange: true, required: false, options: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
  625.                             input "everyNMCX$n", "number", title: " > Of every n months", range: "1..12", submitOnChange: true, required: false, defaultValue: 1
  626.                         }
  627.                     }
  628.                     input "startingMC$n", "time", title: " > Starting at", submitOnChange: true, required: false, defaultValue: "2016-03-23T12:00:00.000" + gmtOffset()
  629.                     break
  630.                 case "Yearly":
  631.                     if(!settings["weeklyYC$n"]) input "yearlyMonthC$n", "enum", title: " > In the month of ...", submitOnChange: true, required: false, multiple: false,
  632.                         options: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
  633.                     if(settings["yearlyMonthC$n"]) input "yearlyDayC$n", "number", title: " > On this day of month ...", description: "1..31", range: "1..31", submitOnChange: true, required: false, multiple: false
  634.                     if(!settings["yearlyMonthC$n"]) input "weeklyYC$n", "enum", title: " > In the week of month ...", submitOnChange: true, required: false, options: ["First", "Second", "Third", "Fourth"]
  635.                     if(settings["weeklyYC$n"]) {
  636.                         input "dailyYC$n", "enum", title: " > On day of week ...", submitOnChange: true, required: false, options: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
  637.                         input "yearlyMonthCX$n", "enum", title: " > In the month of ...", submitOnChange: true, required: false, multiple: false,
  638.                             options: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
  639.                     }
  640.                     input "startingYC$n", "time", title: " > Starting at", submitOnChange: true, required: false, defaultValue: "2016-03-23T12:00:00.000" + gmtOffset()
  641.                     break
  642.             }
  643.         }
  644.     }
  645. }
  646.  
  647. def periodicLabel(n) {
  648.     def result
  649.     switch(settings["whichPeriod$n"]) {
  650.         case "Minutes":
  651.             if(settings["everyNMinutesC$n"]) result = "Every ${settings["everyNC$n"]} minute" + (settings["everyNC$n"] > 1 ? "s" : "")
  652.             if(settings["selectMinutesC$n"]) {
  653.                 def str = stripBrackSpace("${settings["selectMinutesC$n"]}")
  654.                 result = "Each of these minutes\n   $str"
  655.             }
  656.             break
  657.         case "Hourly":
  658.             if(settings["everyNHoursC$n"]) result = "Every ${settings["everyNHC$n"]} hour" + (settings["everyNHC$n"] > 1 ? "s" : "") + " starting at ${hhmm(settings["startingHC$n"])}"
  659.             if(settings["selectHoursC$n"] != null) {
  660.                 def str = stripBrackSpace("${settings["selectHoursC$n"]}")
  661.                 def str2 = ""
  662.                 for(int i = 0; i < str.size(); i++) {
  663.                     if(str[i] == ",") str2 = str2 + ":00"
  664.                     str2 = str2 + str[i]
  665.                 }
  666.                 result = "Each of these hours\n   $str2:00\n   At ${settings["startingHCX$n"]} minutes after the hour"
  667.             }
  668.             break
  669.         case "Daily":
  670.             if(settings["everyNDoMC$n"]) result = "Every " + (settings["everyNDC$n"] == 1 ? "" : "${settings["everyNDC$n"]} ") + "day" + (settings["everyNDC$n"] > 1 ? "s" : "") + " at ${hhmm(settings["startingDC$n"])}"
  671.             if(settings["everyWeekDay$n"]) result = "Every weekday at ${hhmm(settings["startingDC$n"])}"
  672.             if(settings["selectDoMC$n"]) {
  673.                 def str = stripBrackSpace("${settings["selectDoMC$n"]}")
  674.                 result = "Each of these days of the month\n   $str: At ${hhmm(settings["startingDC$n"])}"
  675.             }
  676.             break
  677.         case "Weekly":
  678.             if(settings["selectDoWC$n"]) {
  679.                 def str = stripBrackSpace("${settings["selectDoWC$n"]}")
  680.                 result = "Each of these days of the week\n   $str: At ${hhmm(settings["startingWC$n"])}"
  681.             }
  682.             break
  683.         case "Monthly":  
  684.             if(settings["everyNMC$n"]) result = "On day ${settings["dayMC$n"]} of every ${settings["everyNMC$n"]} month" + (settings["everyNMC$n"] > 1 ? "s" : "") + " At ${hhmm(settings["startingMC$n"])}"
  685.             if(settings["selectMonthC$n"]) {
  686.                 def str = stripBrackSpace("${settings["selectMonthC$n"]}")
  687.                 result = "On day ${settings["dayMC$n"]} of each of these months\n   $str: At ${hhmm(settings["startingMC$n"])}"
  688.             }
  689.             if(settings["weeklyMC$n"]) result = "On the ${settings["weeklyMC$n"]} ${settings["dailyMC$n"]} of every " + (settings["everyNMCX$n"] > 1 ? "${settings["everyNMCX$n"]} month" : "month") +
  690.                 (settings["everyNMCX$n"] > 1 ? "s" : "") + " at ${hhmm(settings["startingMC$n"])}"
  691.             break
  692.         case "Yearly":  
  693.             if(settings["yearlyMonthC$n"]) result = "Every year on ${settings["yearlyDayC$n"]} of ${settings["yearlyMonthC$n"]}" + (settings["everyNMC$n"] > 1 ? "s" : "") + "\n  At ${hhmm(settings["startingYC$n"])}"
  694.             if(settings["weeklyYC$n"]) result = "On the ${settings["weeklyYC$n"]} ${settings["dailyYC$n"]} of ${settings["yearlyMonthCX$n"]} at ${hhmm(settings["startingYC$n"])}"
  695.             break
  696.     }
  697.     return result
  698. }
  699.  
  700. def cronString(n) {
  701.     def dayOrd = ["Sunday" : "SUN", "Monday" : "MON", "Tuesday" : "TUE", "Wednesday" : "WED", "Thursday" : "THU", "Friday" : "FRI", "Saturday" : "SAT"]
  702.     def monthOrd = ["January" : 1, "February" : 2, "March" : 3, "April" : 4, "May" : 5, "June" : 6, "July" : 7, "August" : 8, "September" : 9, "October" : 10, "November" : 11, "December" : 12]
  703.     def weekOrd = ["First" : 1, "Second" : 2, "Third" : 3, "Fourth" : 4]
  704.     def result
  705.     switch(settings["whichPeriod$n"]) {
  706.         case "Minutes":
  707.             if(settings["everyNMinutesC$n"]) result = "11 */${settings["everyNC$n"]} * * * ?"
  708.             if(settings["selectMinutesC$n"]) {
  709.                 def str = stripBrackSpace("${settings["selectMinutesC$n"]}")
  710.                 result = "11 $str * * * ?"
  711.             }
  712.             break
  713.         case "Hourly":
  714.             if(settings["everyNHoursC$n"]) result = "11 0 */${settings["everyNHC$n"]} * * ?"
  715.             if(settings["selectHoursC$n"]) {
  716.                 def str = stripBrackSpace("${settings["selectHoursC$n"]}")
  717.                 result = "11 ${settings["startingHCX$n"]} $str 1/1 * ?"
  718.             }
  719.             break
  720.         case "Daily":
  721.             def hrmn = hhmm(settings["startingDC$n"], "HH:mm")
  722.             def hr = hrmn[0..1]
  723.             def mn = hrmn[3..4]
  724.             if(settings["everyNDoMC$n"]) result = "11 $mn $hr */${settings["everyNDC$n"]} * ?"
  725.             if(settings["everyWeekDay$n"]) result = "11 $mn $hr ? * 1,2,3,4,5"
  726.             if(settings["selectDoMC$n"]) {
  727.                 def str = stripBrackSpace("${settings["selectDoMC$n"]}")
  728.                 result = "11 $mn $hr $str * ?"
  729.             }
  730.             break
  731.         case "Weekly":
  732.             def hrmn = hhmm(settings["startingWC$n"], "HH:mm")
  733.             def hr = hrmn[0..1]
  734.             def mn = hrmn[3..4]
  735.             if(settings["selectDoWC$n"]) {
  736.                 def str = ""
  737.                 settings["selectDoWC$n"].each {str = str + (str ? "," : "") + "${dayOrd["$it"]}"}
  738.                 result = "11 $mn $hr ? * $str"
  739.             }
  740.             break
  741.         case "Monthly":  
  742.             def hrmn = hhmm(settings["startingMC$n"], "HH:mm")
  743.             def hr = hrmn[0..1]
  744.             def mn = hrmn[3..4]
  745.             if(settings["everyNMC$n"]) result = "11 $mn $hr ${settings["dayMC$n"]} */${settings["everyNMC$n"]} ?"
  746.             if(settings["selectMonthC$n"]) {
  747.                 def str = ""
  748.                 settings["selectMonthC$n"].each {str = str + (str ? "," : "") + "${monthOrd["$it"]}"}
  749.                 result = "11 $mn $hr ${settings["dayMC$n"]} $str ?"
  750.             }
  751.             if(settings["weeklyMC$n"]) result = "11 $mn $hr ? */${settings["everyNMCX$n"]} ${dayOrd[settings["dailyMC$n"]]}#${weekOrd[settings["weeklyMC$n"]]}"
  752.             break
  753.         case "Yearly":
  754.             def hrmn = hhmm(settings["startingYC$n"], "HH:mm")
  755.             def hr = hrmn[0..1]
  756.             def mn = hrmn[3..4]
  757.             if(settings["yearlyMonthC$n"]) result = "11 $mn $hr ${settings["yearlyDayC$n"]} ${settings["yearlyMonthC$n"]} ?"
  758.             if(settings["weeklyYC$n"]) result = "11 $mn $hr ? ${monthOrd[settings["yearlyMonthCX$n"]]} ${dayOrd[settings["dailyYC$n"]]}#${weekOrd[settings["weeklyYC$n"]]}"
  759.             break
  760.     }
  761.     return result + " *"
  762. }
  763.  
  764. def triggerLabel() {
  765.     def howMany = state.howManyT
  766.     def result = ""
  767.     if(howMany) {
  768.         for (int i = 1; i < howMany; i++) {
  769.             def thisCapab = settings.find {it.key == "tCapab$i"}
  770.             if(!thisCapab) return result
  771.             result = result + (i > 1 ? "OR\n" : "") + conditionLabelN(i, true)
  772.             if(i < howMany - 1) result = result + "\n"
  773.         }
  774.     }
  775.     return result
  776. }
  777.  
  778. def conditionLabel() {
  779.     def howMany = state.howMany
  780.     def result = ""
  781.     if(howMany) {
  782.         for (int i = 1; i < howMany; i++) {
  783.             def thisCapab = settings.find {it.key == "rCapab$i"}
  784.             if(!thisCapab) return result
  785.             result = result + conditionLabelN(i, false) + ((state.isRule || state.isRule == null) ? (getOperand(i, true) ? " [TRUE]" : " [FALSE]") : "")
  786.             if(i < howMany - 1) result = result + "\n"
  787.         }
  788.         if((state.isRule || state.isRule == null) && howMany == 2 && state.eval in [null, [], [1]]) {
  789.             state.str = result[0..-8]
  790.             state.eval = [1]
  791.         }
  792.     }
  793.     return result
  794. }
  795.  
  796. def conditionLabelN(i, isTrig) {
  797.     def result = ""
  798.     def SHMphrase = isTrig ? "becomes" : ((state.isRule || state.howMany > 1) ? "is" : "becomes")
  799.     def phrase = isTrig ? "becomes" : ((state.isRule || state.howMany > 1) ? "of" : "becomes")
  800.     def thisCapab = settings.find {it.key == (isTrig ? "tCapab$i" : "rCapab$i")}
  801.     if(thisCapab.value == "Time of day") result = "Time between " + timeIntervalLabelX()
  802.     else if(thisCapab.value == "Certain Time")  result = "When time is " + atTimeLabel()
  803.     else if(thisCapab.value == "Periodic") result = periodicLabel(i)
  804.     else if(thisCapab.value == "Smart Home Monitor") {
  805.             def thisState = (settings.find {it.key == (isTrig ? "tstate$i" : "state$i")}).value
  806.             result = "SHM state $SHMphrase " + (thisState in ["away", "stay"] ? "Arm ($thisState)" : "Disarm")
  807.     } else if(thisCapab.value == "Days of week") result = "Day i" + (days.size() > 1 ? "n " + days : "s " + days[0])
  808.     else if(thisCapab.value == "Mode") {
  809.         if((state.isTrig || isTrig) && modesX) result = "Mode becomes " + (modesX.size() > 1 ? modesX : modesX[0])
  810.         else if((state.isRule || state.howMany > 1) && modes) result = "Mode i" + (modes.size() > 1 ? "n " + modes : "s " + modes[0])
  811.     } else if(thisCapab.value == "Routine") {
  812.         result = "Routine "
  813.         def thisState = settings.find {it.key == (isTrig ? "tstate$i" : "state$i")}
  814.         result = result + "'" + thisState.value + "' runs"
  815.     } else if(thisCapab.value == "Private Boolean") {
  816.         def thisState = settings.find {it.key == (isTrig ? "tstate$i" : "state$i")}
  817.         result = "Private Boolean $SHMphrase $thisState.value"
  818.     } else {
  819.         def thisDev = settings.find {it.key == (isTrig ? "tDev$i" : "rDev$i")}
  820.         if(!thisDev) return result
  821.         def thisAll = settings.find {it.key == (isTrig ? "AlltDev$i" : "AllrDev$i")}
  822. //      def myAny = thisAll && thisDev.value.size() > 1 ? "any " : ""
  823.         def myButton = settings.find {it.key == (isTrig ? "ButtontDev$i" : "ButtonrDev$i")}
  824.         def myAny = ""
  825. //        if((thisAll || !myButton) && thisDev.size() > 1) myAny = "any "
  826.         if((thisAll || !(thisCapab.value == "Button")) && thisDev.value.size() > 1) myAny = "any "
  827.         if     (thisCapab.value == "Temperature")   result = "Temperature $phrase "
  828.         else if(thisCapab.value == "Humidity")      result = "Humidity $phrase "
  829.         else if(thisCapab.value == "Illuminance")   result = "Illuminance $phrase "
  830.         else if(thisCapab.value == "Dimmer level")  result = "Dimmer level $phrase "
  831.         else if(thisCapab.value == "Energy meter")  result = "Energy level $phrase "
  832.         else if(thisCapab.value == "Power meter")   result = "Power level $phrase "
  833.         else if(thisCapab.value == "Battery")       result = "Battery level $phrase "
  834.         else if(thisCapab.value == "Rule truth")    result = "Rule truth $phrase "
  835.         else if(thisCapab.value == "Button") {
  836.             result = "$thisDev.value " + (myButton ? "button $myButton.value " : "")
  837.             def thisState = settings.find {it.key == (isTrig ? "tstate$i" : "state$i")}
  838.             result = result + thisState.value
  839.             return result
  840.         }
  841.         result = result + (myAny ? thisDev.value : thisDev.value[0]) + " " + ((thisAll ? thisAll.value : false) ? "all " : myAny)
  842.         def thisRel = settings.find {it.key == (isTrig ? "ReltDev$i" : "RelrDev$i")}
  843.         if(thisCapab.value in ["Temperature", "Humidity", "Illuminance", "Dimmer level", "Energy meter", "Power meter", "Battery"]) result = result + " " + thisRel.value + " "
  844.         if(thisCapab.value == "Physical Switch") result = result + "physical "
  845.         def thisState = settings.find {it.key == (isTrig ? "tstate$i" : "state$i")}
  846.         def thisRelDev = settings.find {it.key == (isTrig ? "reltDevice$i" : "relDevice$i")}
  847.         if(thisRelDev) {
  848.             result = result + thisRelDev.value
  849.             if(thisState) result = result + (thisState.value > 0 ? " +" : " ") + (thisState.value != 0 ? thisState.value : "")
  850.         }
  851.         else result = result + thisState.value
  852.         if(thisCapab.value == "Presence" && thisDev.value.size() > 1 && isTrig) result = result[0..-2]
  853.     }
  854.     return result
  855. }
  856.  
  857. // Rule definition code follows
  858.  
  859. def defineRule() {
  860.     dynamicPage(name: "defineRule", title: "Define the Rule", uninstall: false) {
  861.         section() {
  862.             paragraph "Turn on to enable parenthesized sub-rules"
  863.             input "advanced", "bool", title: "Complex Rule Input", required: false, submitOnChange: true
  864.         }
  865.         state.n = 0
  866.         state.str = ""
  867.         state.eval = []
  868.         section() {inputLeftAndRight(false)}
  869.     }
  870. }
  871.  
  872. def rulLabl() {
  873.     def result = state.str
  874.     if(state.eval && state.str) {
  875.         state.token = 0
  876.         def truth = eval()
  877.         result = result + "\n[" + (truth ? "TRUE" : "FALSE") + "]"
  878.     }
  879. }
  880.  
  881. def inputLeft(sub) {
  882.     def conds = []
  883.     for (int i = 1; i < state.howMany; i++) conds << conditionLabelN(i, false)
  884.     input "condNotL$state.n", "bool", title: "NOT ?", submitOnChange: true
  885.     if(settings["condNotL$state.n"]) {
  886.         state.str = state.str + "NOT "
  887.         state.eval << "NOT"
  888.         paragraph(state.str)
  889.     }
  890.     if(advanced) input "subCondL$state.n", "bool", title: "Enter subrule for left?", submitOnChange: true
  891.     if(settings["subCondL$state.n"]) {
  892.         state.str = state.str + "( "
  893.         state.eval << "("
  894.         paragraph(state.str)
  895.         inputLeftAndRight(true)
  896.     } else {
  897.         input "condL$state.n", "enum", title: "Which condition?", options: conds, submitOnChange: true
  898.         if(settings["condL$state.n"]) {
  899.             state.str = state.str + settings["condL$state.n"]
  900.             def myCond = 0
  901.             for (int i = 1; i < state.howMany; i++) if(conditionLabelN(i, false) == settings["condL$state.n"]) myCond = i
  902.             state.eval << myCond
  903.             paragraph(state.str)
  904.         }
  905.     }
  906. }
  907.  
  908. def inputRight(sub) {
  909.     if(sub) {
  910.         input "endOfSubL$state.n", "bool", title: "End of sub-rule?", submitOnChange: true
  911.         if(settings["endOfSubL$state.n"]) {
  912.             state.str = state.str + " )"
  913.             state.eval << ")"
  914.             paragraph(state.str)
  915.             return
  916.         }
  917.     }
  918.     state.n = state.n + 1
  919.     input "operator$state.n", "enum", title: "AND  or  OR", options: ["AND", "OR"], submitOnChange: true, required: false
  920.     if(settings["operator$state.n"]) {
  921.         state.str = state.str + "\n" + settings["operator$state.n"] + "\n"
  922.         state.eval << settings["operator$state.n"]
  923.         paragraph(state.str)
  924.         def conds = []
  925.         for (int i = 1; i < state.howMany; i++) conds << conditionLabelN(i, false)
  926.         input "condNotR$state.n", "bool", title: "NOT ?", submitOnChange: true
  927.         if(settings["condNotR$state.n"]) {
  928.             state.str = state.str + "NOT "
  929.             state.eval << "NOT"
  930.             paragraph(state.str)
  931.         }
  932.         if(advanced) input "subCondR$state.n", "bool", title: "Enter subrule for right?", submitOnChange: true
  933.         if(settings["subCondR$state.n"]) {
  934.             state.str = state.str + "( "
  935.             state.eval << "("
  936.             paragraph(state.str)
  937.             inputLeftAndRight(true)
  938.             inputRight(sub)
  939. //          if(sub) {
  940. //              input "endOfSub$state.n", "bool", title: "End of sub-rule?", submitOnChange: true
  941. //              if(settings["endOfSub$state.n"]) {
  942. //                  state.str = state.str + " )"
  943. //                  state.eval << ")"
  944. //                  paragraph(state.str)
  945. //                  return
  946. //              }
  947. //          }
  948.         } else {
  949.             input "condR$state.n", "enum", title: "Which condition?", options: conds, submitOnChange: true
  950.             if(settings["condR$state.n"]) {
  951.                 state.str = state.str + settings["condR$state.n"]
  952.                 def myCond = 0
  953.                 for (int i = 1; i < state.howMany; i++) if(conditionLabelN(i, false) == settings["condR$state.n"]) myCond = i
  954.                 state.eval << myCond
  955.                 paragraph(state.str)
  956.             }
  957.             if(sub) {
  958.                 input "endOfSub$state.n", "bool", title: "End of sub-rule?", submitOnChange: true
  959.                 if(settings["endOfSub$state.n"]) {
  960.                     state.str = state.str + " )"
  961.                     state.eval << ")"
  962.                     paragraph(state.str)
  963.                     return
  964.                 }
  965.             }
  966.             inputRight(sub)
  967.         }
  968.     }
  969. }
  970.  
  971. def inputLeftAndRight(sub) {
  972.     state.n = state.n + 1
  973.     inputLeft(sub)
  974.     inputRight(sub)
  975. }
  976.  
  977. // Action selection code follows
  978.  
  979. def selectActionsTrue() {
  980.     def isRule = state.isRule || state.howMany > 1
  981.     dynamicPage(name: "selectActionsTrue", title: "Select Actions" + (isRule ? " for True" : ""), uninstall: false) {
  982.         state.actsTrue = ""
  983.         getActions(true)
  984.         if(state.actsTrue) state.actsTrue = state.actsTrue[0..-2]
  985.     }
  986. }
  987.  
  988. def selectActionsFalse() {
  989.     dynamicPage(name: "selectActionsFalse", title: "Select Actions for False", uninstall: false) {
  990.         state.actsFalse = ""
  991.         getActions(false)
  992.         if(state.actsFalse) state.actsFalse = state.actsFalse[0..-2]
  993.     }
  994. }
  995.  
  996. def getActions(trufal) {
  997.     def thisStr = trufal ? "True" : "False"
  998.     section("") {
  999. // Delay These Actions
  1000.         def thisPage = "delay" + thisStr + "Page"
  1001.         def thisStateStr = trufal ? state.delayStrTrue : state.delayStrFalse
  1002.         href thisPage, title: "Delay These Actions", description: thisStateStr ? (thisStateStr) : "Tap to set", state: thisStateStr ? "complete" : null
  1003.         if(thisStateStr) setAct(trufal, thisStateStr)
  1004. // Control Switches, Capture/Restore
  1005.         def thisPage1 = "switch" + thisStr + "Page"
  1006.         def thisStateStr1 = trufal ? state.switchStrTrue : state.switchStrFalse
  1007.         href thisPage1, title: "Control Switches, Capture/Restore", description: thisStateStr1 ? (thisStateStr1) : "Tap to set", state: thisStateStr1 ? "complete" : null
  1008.         if(thisStateStr1) setAct(trufal, thisStateStr1)
  1009. // Set Dimmers and Bulbs
  1010.         def thisPage2 = "dimmer" + thisStr + "Page"
  1011.         def thisStateStr2 = trufal ? state.dimmerStrTrue : state.dimmerStrFalse
  1012.         href thisPage2, title: "Set Dimmers and Bulbs", description: thisStateStr2 ? (thisStateStr2) : "Tap to set", state: thisStateStr2 ? "complete" : null
  1013.         if(thisStateStr2) setAct(trufal, thisStateStr2)
  1014. // Control doors, locks, thermostats, fans, valves
  1015.         def thisPage3 = "door" + thisStr + "Page"
  1016.         def thisStateStr3 = trufal ? state.doorStrTrue : state.doorStrFalse
  1017.         href thisPage3, title: "Control doors, locks, thermostats, fans, valves", description: thisStateStr3 ? (thisStateStr3) : "Tap to set", state: thisStateStr3 ? "complete" : null
  1018.         if(thisStateStr3) setAct(trufal, thisStateStr3)
  1019. // Send or speak a message        
  1020.         def msgPage = "selectMsg" + thisStr
  1021.         def msgStr = trufal ? state.msgTrue : state.msgFalse
  1022.         href msgPage, title: "Send or speak a message", description: msgStr ? msgStr : "Tap to set", state: msgStr ? "complete" : null
  1023.         if(msgStr) setAct(trufal, msgStr)
  1024. // Set mode, alarm, Routine, photos
  1025.         def thisPage4 = "mode" + thisStr + "Page"
  1026.         def thisStateStr4 = trufal ? state.modeStrTrue : state.modeStrFalse
  1027.         href thisPage4, title: "Set mode, alarm, Routine, photos", description: thisStateStr4 ? (thisStateStr4) : "Tap to set", state: thisStateStr4 ? "complete" : null
  1028.         if(thisStateStr4) setAct(trufal, thisStateStr4)
  1029. // Run Rules, Actions, set Boolean
  1030.         def thisPage5 = "rule" + thisStr + "Page"
  1031.         def thisStateStr5 = trufal ? state.ruleStrTrue : state.ruleStrFalse
  1032.         href thisPage5, title: "Run Rules, Actions, set Boolean", description: thisStateStr5 ? (thisStateStr5) : "Tap to set", state: thisStateStr5 ? "complete" : null
  1033.         if(thisStateStr5) setAct(trufal, thisStateStr5)
  1034. // Run custom commands        
  1035.         if (state.isExpert){
  1036.             if (state.cstCmds){
  1037.                 state.ccTruth = trufal
  1038.                 def desc = trufal ? state.cmdActTrue : state.cmdActFalse
  1039.                 href( "selectCustomActions"
  1040.                     ,title      : "Run custom commands"
  1041.                     ,description: desc ?: "Tap to set"
  1042.                     ,state      : desc ? "complete" : null
  1043.                 )
  1044.                 if(desc) setAct(trufal, desc)
  1045.             }
  1046.         }
  1047.     }
  1048. }
  1049.  
  1050. def delayTruePage() {
  1051.     dynamicPage(name: "delayTruePage", title: "Select Delay for Actions", uninstall: false) {
  1052.         state.delayStrTrue = getDelay(true)
  1053.     }
  1054. }
  1055.  
  1056. def delayFalsePage() {
  1057.     dynamicPage(name: "delayFalsePage", title: "Select Delay for Actions", uninstall: false) {
  1058.         state.delayStrFalse = getDelay(false)
  1059.     }
  1060. }
  1061.  
  1062. def getDelay(trufal) {
  1063.     def thisStr = trufal ? "True" : "False"
  1064.     def delayStr = ""
  1065.     section() {
  1066.         def delayMin = "delayMin" + thisStr
  1067.         def delaySec = "delaySec" + thisStr
  1068.         def delayMil = "delayMil" + thisStr
  1069.         def cancel = "cancel" + thisStr
  1070.         def rand = "random" + thisStr
  1071.         if(!settings[delayMil] && !settings[delaySec]) {
  1072.             input delayMin, "number", title: "Minutes of delay", required: false, range: "1..*", submitOnChange: true
  1073.             if(settings[delayMin] > 0) {
  1074.                 if(state.isRule || state.howMany > 1) input cancel, "bool", title: "Cancel on truth change?", required: false, submitOnChange: true
  1075.                 paragraph "\n\n "
  1076.                 input rand, "bool", title: "Random delay?", required: false, submitOnChange: true
  1077.             }
  1078.         }
  1079.         if(!settings[delayMin] && !settings[delayMil]) {
  1080.             paragraph "\n"
  1081.             input delaySec, "number", title: "Seconds of delay", required: false, range: "1..*", submitOnChange: true
  1082.             if(settings[delaySec] > 0 && (state.isRule || state.howMany > 1)) input cancel, "bool", title: "Cancel on truth change?", required: false, submitOnChange: true
  1083.         }
  1084.         if(!settings[delayMin] && !settings[delaySec]) {
  1085.             paragraph "\n\n Milliseconds delay works only for \n on/off/dim/toggle, open/close, lock/unlock"
  1086.             input delayMil, "number", title: "Milliseconds of delay", required: false, range: "1..*", submitOnChange: true
  1087.         }
  1088.         if(settings[delayMin] > 0 || settings[delayMil] > 0 || settings[delaySec] > 0) {
  1089.             delayStr = "Delay by " + (settings[delayMil] ? "${settings[delayMil]} milliseconds" : (settings[delaySec] ? "${settings[delaySec]} seconds" : "${settings[delayMin]} minute"))
  1090.             if(settings[delayMin] > 1) delayStr = delayStr + "s"
  1091.             delayStr = delayStr + (settings[cancel] ? " [Cancel]" : "") + (settings[rand] ? " [Random]" : "")
  1092.         }
  1093.     }
  1094.     return delayStr
  1095. }
  1096.  
  1097. def switchTruePage() {
  1098.     dynamicPage(name: "switchTruePage", title: "Control Switches, Capture/Restore", uninstall: false) {
  1099.         state.switchStrTrue = getSwitch(true)
  1100.     }
  1101. }
  1102.  
  1103. def switchFalsePage() {
  1104.     dynamicPage(name: "switchFalsePage", title: "Control Switches, Capture/Restore", uninstall: false) {
  1105.         state.switchStrFalse = getSwitch(false)
  1106.     }
  1107. }
  1108.  
  1109. def getSwitch(trufal) {
  1110.     def thisStr = trufal ? "True" : "False"
  1111.     def prevStr = trufal ? state.actsTrue : state.actsFalse
  1112.     section() {
  1113. // Turn on these switches        
  1114.         def onSwitch = "onSwitch" + thisStr
  1115.         input onSwitch, "capability.switch", title: "Turn on these switches", multiple: true, required: false, submitOnChange: true
  1116.         checkAct(trufal, onSwitch, "On: ${settings[onSwitch]}")
  1117. // Turn off these switches        
  1118.         def offSwitch = "offSwitch" + thisStr
  1119.         input offSwitch, "capability.switch", title: "Turn off these switches", multiple: true, required: false, submitOnChange: true
  1120.         checkAct(trufal, offSwitch, "Off: ${settings[offSwitch]}")
  1121. // Toggle these switches        
  1122.         def toggleSwitch = "toggleSwitch" + thisStr
  1123.         input toggleSwitch, "capability.switch", title: "Toggle these switches", multiple: true, required: false, submitOnChange: true
  1124.         checkAct(trufal, toggleSwitch, "Toggle: ${settings[toggleSwitch]}")
  1125. // Turn on or off these switches after a delay (default is OFF)        
  1126.         def delayedOff = "delayedOff" + thisStr
  1127.         input delayedOff, "capability.switch", title: "Turn on or off these switches after a delay (default is OFF)", multiple: true, required: false, submitOnChange: true
  1128.         if(settings[delayedOff]) {
  1129.         def delayOnOff = "delayOnOff" + thisStr
  1130.         input delayOnOff, "bool", title: "> Turn ON after the delay?", multiple: false, required: false, defaultValue: false, submitOnChange: true
  1131.         def delayMinutes = "delayMinutes" + thisStr
  1132.         def delaySeconds = "delaySeconds" + thisStr
  1133.         def delayMillis = "delayMillis" + thisStr
  1134.         if(!settings[delayMillis] && !settings[delaySeconds]) input delayMinutes, "number", title: "> Minutes of delay", required: false, range: "1..*", submitOnChange: true
  1135.         if(!settings[delayMillis] && !settings[delayMinutes]) input delaySeconds, "number", title: "> Seconds of delay", required: false, range: "1..*", submitOnChange: true
  1136.         if(!settings[delayMinutes] && !settings[delaySeconds]) input delayMillis, "number", title: "> Milliseconds of delay", required: false, range: "1..*", submitOnChange: true
  1137.         if(settings[delayMinutes] || settings[delaySeconds] || settings[delayMillis]) {
  1138.             def delayStr = "Delayed " + (settings[delayOnOff] ? "On:" : "Off:") + " ${settings[delayedOff]}: "
  1139.             if(settings[delayMillis]) delayStr = delayStr + "${settings[delayMillis]} milliseconds"
  1140.             if(settings[delaySeconds]) delayStr = delayStr + "${settings[delaySeconds]} second"
  1141.             if(settings[delayMinutes]) delayStr = delayStr + "${settings[delayMinutes]} minute"
  1142.             if(settings[delayMinutes] > 1 || settings[delaySeconds] > 1) delayStr = delayStr + "s"
  1143.             setAct(trufal, delayStr)
  1144.         }
  1145.     }
  1146. // Turn on or off these switches after a delay, pending cancellation (default is OFF)        
  1147.         if(state.isRule || state.howMany > 1) {
  1148.             def pendedOff = "pendedOff" + thisStr
  1149.             input pendedOff, "capability.switch", title: "Turn on or off these switches after a delay, pending cancellation (default is OFF)", multiple: true, required: false, submitOnChange: true
  1150.             if(settings[pendedOff]) {
  1151.                 def pendOnOff = "pendOnOff" + thisStr
  1152.                 def pendMinutes = "pendMinutes" + thisStr
  1153.                 input pendOnOff, "bool", title: "> Turn ON after the delay?", multiple: false, required: false, defaultValue: false, submitOnChange: true
  1154.                 input pendMinutes, "number", title: "> Minutes of delay", required: true, range: "0..*", submitOnChange: true
  1155.                 if(settings[pendMinutes] != null) {
  1156.                     def pendStr = "Pending "+ (settings[pendOnOff] ? "On:" : "Off:") + " ${settings[pendedOff]}: ${settings[pendMinutes]} minute"
  1157.                     if(settings[pendMinutes] > 1 || settings[pendMinutes] == 0) pendStr = pendStr + "s"
  1158.                     setAct(trufal, pendStr)
  1159.                 }
  1160.             }
  1161.         }
  1162. // Capture the state of these switches        
  1163.         def capture = "capture"  + thisStr
  1164.         def captureNot = "capture"  + (trufal ? "False" : "True")
  1165.         if(settings[captureNot] == null) {
  1166.             input capture, "capability.switch", title: "Capture the state of these switches", multiple: true, required: false, submitOnChange: true
  1167.             checkAct(trufal, capture, "Capture: ${settings[capture]}")
  1168.         }
  1169. // Restore the state of captured switches        
  1170.         def restore = "restore" + thisStr
  1171.         def restoreDelay = "restoreDelay" + thisStr
  1172.         def restoreCancel = "restoreCancel" + thisStr
  1173.         if(settings[capture] || settings[captureNot]) input restore, "bool", title: "Restore the state of captured switches", required: false, submitOnChange: true
  1174.         if(settings[restore]) input restoreDelay, "number", title: "> Restore after a delay?", required: false, submitOnChange: true, description: "0 minutes"
  1175.         if(settings[restoreDelay] > 0 && (state.isRule || state.howMany > 1)) input restoreCancel, "bool", title: "> Cancel on truth change?", required: false, submitOnChange: true
  1176.         if(settings[restore] && settings[capture]) setAct(trufal, "Restore: ${settings[capture]}" + (settings[restoreDelay] > 0 ? " after ${settings[restoreDelay]} minutes" +
  1177.             (settings[restoreCancel] ? " [Cancel]" : "") : ""))
  1178.         else if(settings[restore] && settings[captureNot]) setAct(trufal, "Restore: ${settings[captureNot]}" + (settings[restoreDelay] > 0 ? " after ${settings[restoreDelay]} minutes"  +
  1179.             (settings[restoreCancel] ? " [Cancel]" : ""): ""))
  1180.     }
  1181.     def result = (trufal ? state.actsTrue : state.actsFalse) - prevStr
  1182.     if(result) result = result[0..-2]
  1183. }
  1184.  
  1185. def dimmerTruePage() {
  1186.     dynamicPage(name: "dimmerTruePage", title: "Set Dimmers and Bulbs", uninstall: false) {
  1187.         state.dimmerStrTrue = getDimmer(true)
  1188.     }
  1189. }
  1190.  
  1191. def dimmerFalsePage() {
  1192.     dynamicPage(name: "dimmerFalsePage", title: "Set Dimmers and Bulbs", uninstall: false) {
  1193.         state.dimmerStrFalse = getDimmer(false)
  1194.     }
  1195. }
  1196.  
  1197. def getDimmer(trufal) {
  1198.     def thisStr = trufal ? "True" : "False"
  1199.     def prevStr = trufal ? state.actsTrue : state.actsFalse
  1200.     section() {
  1201. // Set these dimmers        
  1202.         def dimA = "dimA" + thisStr
  1203.         input dimA, "capability.switchLevel", title: "Set these dimmers", multiple: true, submitOnChange: true, required: false
  1204.         if(settings[dimA]) {
  1205.             def dimTrack = "dimTrack" + thisStr
  1206.             def dimLA = "dimLA" + thisStr
  1207.             input dimTrack, "bool", title: "> Track event dimmer?", required: false, submitOnChange: true
  1208.             if(settings[dimTrack]) setAct(trufal, "Track Dim: ${settings[dimA]}")
  1209.             else input dimLA, "number", title: "> To this level", range: "0..100", required: true, submitOnChange: true
  1210.             if(settings[dimLA] != null) setAct(trufal, "Dim: ${settings[dimA]}: ${settings[dimLA]}")
  1211.         }
  1212. // Set these other dimmers        
  1213.         def dimB = "dimB" + thisStr
  1214.         input dimB, "capability.switchLevel", title: "Set these other dimmers", multiple: true, submitOnChange: true, required: false
  1215.         if(settings[dimB]) {
  1216.             def dimLB = "dimLB" + thisStr
  1217.             input dimLB, "number", title: "> To this level", range: "0..100", required: true, submitOnChange: true
  1218.             if(settings[dimLB] != null) setAct(trufal, "Dim: ${settings[dimB]}: ${settings[dimLB]}")
  1219.         }
  1220. // Toggle these dimmers        
  1221.         def toggleDimmer = "toggleDimmer" + thisStr
  1222.         def dimTog = "dimTog" + thisStr
  1223.         input toggleDimmer, "capability.switchLevel", title: "Toggle these dimmers", multiple: true, required: false, submitOnChange: true
  1224.         if(settings[toggleDimmer]) input dimTog, "number", title: "> To this level", range: "0..100", required: true, submitOnChange: true
  1225.         if(settings[dimTog] != null) checkAct(trufal, toggleDimmer, "Toggle Dim: ${settings[toggleDimmer]}: ${settings[dimTog]}")
  1226. // Adjust these dimmers        
  1227.         def adjustDimmer = "adjustDimmer" + thisStr
  1228.         def dimAdj = "dimAdj" + thisStr
  1229.         input adjustDimmer, "capability.switchLevel", title: "Adjust these dimmers", multiple: true, required: false, submitOnChange: true
  1230.         if(settings[adjustDimmer]) input dimAdj, "number", title: "> By this amount", range: "-100..100", required: true, submitOnChange: true
  1231.         if(settings[dimAdj]) checkAct(trufal, adjustDimmer, "Adjust: ${settings[adjustDimmer]}: ${settings[dimAdj]}")
  1232. // Set these dimmers per mode        
  1233.         def myModes = []
  1234.         location.modes.each {myModes << "$it"}
  1235.         def dimM = "dimM" + thisStr
  1236.         input dimM, "capability.switchLevel", title: "Set these dimmers per mode", multiple: true, submitOnChange: true, required: false
  1237.         if(settings[dimM]) {
  1238.             def dimmerModes = "dimmerModes" + thisStr
  1239.             input dimmerModes, "enum", title: "> Select dimmer level by mode", required: true, options: myModes.sort(), multiple: true, submitOnChange: true
  1240.             if(settings[dimmerModes]) {
  1241.                 def sortModes = settings[dimmerModes].sort()
  1242.                 checkAct(trufal, dimM, "Dimmers per mode: ${settings[dimM]}")
  1243.                 sortModes.each {getModeLevel(it, "level" + thisStr + "$it", trufal)}
  1244.             }
  1245.         }
  1246. // Set color temperature for these bulbs            
  1247.         def ct = "ct" + thisStr
  1248.         def ctL = "ctL" + thisStr
  1249.         input ct, "capability.colorTemperature", title: "Set color temperature for these bulbs", multiple: true, submitOnChange: true, required: false
  1250.         if(settings[ct]) input ctL, "number", title: "> To this color temperature", range: "2000..6500", required: true, submitOnChange: true, description: "2000..6500"
  1251.         if(settings[ctL]) checkAct(trufal, ct, "Color Temperature: ${settings[ct]}: ${settings[ctL]}")
  1252. // Set color for these bulbs        
  1253.         def bulbs = "bulbs" + thisStr
  1254.         input bulbs, "capability.colorControl", title: "Set color for these bulbs", multiple: true, required: false, submitOnChange: true
  1255.         if(settings[bulbs]) {
  1256.             def color = "color" + thisStr
  1257.             def colorLevel = "colorLevel" + thisStr
  1258.             input color, "enum", title: "> Bulb color?", required: true, multiple: false, submitOnChange: true,
  1259.                 options: ["Soft White", "White", "Daylight", "Warm White", "Red", "Green", "Blue", "Yellow", "Orange", "Purple", "Pink", "Custom color", "Random color"]
  1260.             input colorLevel, "number", title: "> Bulb level?", required: false, submitOnChange: true, range: "0..100", description: "0..100"
  1261.             setAct(trufal, "Color: ${settings[bulbs]} ")
  1262.             def colorHex = "colorHex" + thisStr
  1263.             def colorSat = "colorSat" + thisStr
  1264.             if(settings[color]) {
  1265.                 if(settings[color] == "Custom color") {
  1266.                     input colorHex, "number", title: "> Color value?", required: false, submitOnChange: true, range: "0..100", description: "0..100"
  1267.                     input colorSat, "number", title: "> Saturation value?", required: false, submitOnChange: true, range: "0..100", description: "0..100"
  1268.                 }
  1269.                 setAct(trufal, "   ${settings[color]} ")
  1270.                 if(settings[colorHex]) setAct(trufal, "   Hue:${settings[colorHex]} Sat:${settings[colorSat]} ")
  1271.             }
  1272.             checkAct(trufal, colorLevel, "   Level: ${settings[colorLevel]}")
  1273.         }
  1274.     }
  1275.     def result = (trufal ? state.actsTrue : state.actsFalse) - prevStr
  1276.     if(result) result = result[0..-2]
  1277. }
  1278.  
  1279. def doorTruePage() {
  1280.     dynamicPage(name: "doorTruePage", title: "Control doors, locks, thermostats, fans, valves", uninstall: false) {
  1281.         state.doorStrTrue = getDoor(true)
  1282.     }
  1283. }
  1284.  
  1285. def doorFalsePage() {
  1286.     dynamicPage(name: "doorFalsePage", title: "Control doors, locks, thermostats, fans, valves", uninstall: false) {
  1287.         state.doorStrFalse = getDoor(false)
  1288.     }
  1289. }
  1290.  
  1291. def getDoor(trufal) {
  1292.     def thisStr = trufal ? "True" : "False"
  1293.     def prevStr = trufal ? state.actsTrue : state.actsFalse
  1294.     section() {
  1295. // Open these garage doors        
  1296.         def garageOpen = "garageOpen" + thisStr
  1297.         input garageOpen, "capability.garageDoorControl", title: "Open these garage doors", multiple: true, required: false, submitOnChange: true
  1298.         checkAct(trufal, garageOpen, "Garage open: ${settings[garageOpen]}")
  1299. // Close these garage doors        
  1300.         def garageClose = "garageClose" + thisStr
  1301.         input garageClose, "capability.garageDoorControl", title: "Close these garage doors", multiple: true, required: false, submitOnChange: true
  1302.         checkAct(trufal, garageClose, "Garage close: ${settings[garageClose]}")
  1303. // Lock these locks        
  1304.         def lock = "lock" + thisStr
  1305.         input lock, "capability.lock", title: "Lock these locks", multiple: true, required: false, submitOnChange: true
  1306.         checkAct(trufal, lock, "Lock: ${settings[lock]}")
  1307. // Unlock these locks        
  1308.         def unlock = "unlock" + thisStr
  1309.         input unlock, "capability.lock", title: "Unlock these locks", multiple: true, required: false, submitOnChange: true
  1310.         checkAct(trufal, unlock, "Unlock: ${settings[unlock]}")
  1311. // Set these thermostats        
  1312.         def thermo = "thermo" + thisStr
  1313.         input thermo, "capability.thermostat", title: "Set these thermostats", multiple: true, required: false, submitOnChange: true
  1314.         if(settings[thermo]) {
  1315.             def thermoMode = "thermoMode" + thisStr
  1316.             def thermoSetHeat = "thermoSetHeat" + thisStr
  1317.             def thermoAdjHeat = "thermoAdjHeat" + thisStr
  1318.             def thermoSetCool = "thermoSetCool" + thisStr
  1319.             def thermoAdjCool = "thermoAdjCool" + thisStr
  1320.             def thermoFan = "thermoFan" + thisStr
  1321.             input thermoMode, "enum", title: "> Select thermostat mode", multiple: false, required: false, options: ["auto", "heat", "cool", "off", "emergency heat"], submitOnChange: true
  1322.             input thermoSetHeat, "decimal", title: "> Set heating point", multiple: false, required: false, submitOnChange: true
  1323.             input thermoAdjHeat, "decimal", title: "> Adjust heating point", multiple: false, required: false, submitOnChange: true, range: "*..*"
  1324.             input thermoSetCool, "decimal", title: "> Set cooling point", multiple: false, required: false, submitOnChange: true
  1325.             input thermoAdjCool, "decimal", title: "> Adjust cooling point", multiple: false, required: false, submitOnChange: true, range: "*..*"
  1326.             input thermoFan, "enum", title: "> Fan setting", multiple: false, required: false, submitOnChange: true, options: ["on", "auto"]
  1327.             setAct(trufal, "${settings[thermo]}: ")
  1328.             checkAct(trufal, thermoMode, "   Mode: " + settings[thermoMode] + " ")
  1329.             checkAct(trufal, thermoSetHeat, "   Heat to ${settings[thermoSetHeat]} ")
  1330.             checkAct(trufal, thermoAdjHeat, "   Adjust Heat by ${settings[thermoAdjHeat]} ")
  1331.             checkAct(trufal, thermoSetCool, "   Cool to ${settings[thermoSetCool]} ")
  1332.             checkAct(trufal, thermoAdjCool, "   Adjust Cool by ${settings[thermoAdjCool]} ")
  1333.             checkAct(trufal, thermoFan, "   Fan setting ${settings[thermoFan]}")
  1334.         }
  1335. // Adjust this fan - Low, Medium, High, Off    
  1336.         def fanAdjust = "fanAdjust" + thisStr
  1337.         input fanAdjust, "capability.switchLevel", title: "Adjust this fan - Low, Medium, High, Off", multiple: false, required: false, submitOnChange: true
  1338.         checkAct(trufal, fanAdjust, "Adjust Fan: ${settings[fanAdjust]}")
  1339. // Open these valves
  1340.         def openValve = "openValve" + thisStr
  1341.         input openValve, "capability.valve", title: "Open these valves", multiple: true, required: false, submitOnChange: true
  1342.         checkAct(trufal, openValve, "Open: ${settings[openValve]}")
  1343. // Close these valves
  1344.         def closeValve = "closeValve" + thisStr
  1345.         input closeValve, "capability.valve", title: "Close these valves", multiple: true, required: false, submitOnChange: true
  1346.         checkAct(trufal, closeValve, "Close: ${settings[closeValve]}")
  1347.     }
  1348.     def result = (trufal ? state.actsTrue : state.actsFalse) - prevStr
  1349.     if(result) result = result[0..-2]
  1350. }
  1351.  
  1352. def modeTruePage() {
  1353.     dynamicPage(name: "modeTruePage", title: "Set mode, alarm, Routine, photos", uninstall: false) {
  1354.         state.modeStrTrue = getMode(true)
  1355.     }
  1356. }
  1357.  
  1358. def modeFalsePage() {
  1359.     dynamicPage(name: "modeFalsePage", title: "Set mode, alarm, Routine, photos", uninstall: false) {
  1360.         state.modeStrFalse = getMode(false)
  1361.     }
  1362. }
  1363.  
  1364. def getMode(trufal) {
  1365.     def thisStr = trufal ? "True" : "False"
  1366.     def prevStr = trufal ? state.actsTrue : state.actsFalse
  1367.     section() {
  1368. // Set the mode        
  1369.         def myModes = []
  1370.         location.modes.each {myModes << "$it"}
  1371.         def modeV = "mode" + thisStr
  1372.         input modeV, "enum", title: "Set the mode", multiple: false, required: false, options: myModes.sort(), submitOnChange: true
  1373.         checkAct(trufal, modeV, "Mode: ${settings[modeV]}")
  1374. // Set the alarm state        
  1375.         def alarm = "alarm" + thisStr
  1376.         input alarm, "enum", title: "Set the alarm state", multiple: false, required: false, options: ["away" : "Arm (away)", "stay" : "Arm (stay)", "off" : "Disarm"], submitOnChange: true
  1377.         checkAct(trufal, alarm, "Alarm: " + (settings[alarm] in ["away", "stay"] ? "Arm (${settings[alarm]})" : "Disarm"))
  1378. // Run a Routine
  1379.         def phrases = location.helloHome?.getPhrases()*.label
  1380.         def myPhrase = "myPhrase" + thisStr
  1381.         input myPhrase, "enum", title: "Run a Routine", required: false, options: phrases.sort(), submitOnChange: true
  1382.         checkAct(trufal, myPhrase, "Routine: ${settings[myPhrase]}")  
  1383. // Take photos
  1384.         def camera = "camera" + thisStr
  1385.         input camera, "capability.imageCapture", title: "Take photos", required: false, multiple: false, submitOnChange: true
  1386.         if(settings[camera]) {
  1387.         def burstCount = "burstCount" + thisStr
  1388.             input burstCount, "number", title: "> How many? (default 5)", defaultValue:5
  1389.             setAct(trufal, "Photo: ${settings[camera]} " + (settings[burstCount] ?: ""))
  1390.         }
  1391.     }
  1392.     def result = (trufal ? state.actsTrue : state.actsFalse) - prevStr
  1393.     if(result) result = result[0..-2]
  1394. }
  1395.  
  1396. def ruleTruePage() {
  1397.     dynamicPage(name: "ruleTruePage", title: "Run Rules, Actions, set Boolean", uninstall: false) {
  1398.         state.ruleStrTrue = getRule(true)
  1399.     }
  1400. }
  1401.  
  1402. def ruleFalsePage() {
  1403.     dynamicPage(name: "ruleFalsePage", title: "Run Rules, Actions, set Boolean", uninstall: false) {
  1404.         state.ruleStrFalse = getRule(false)
  1405.     }
  1406. }
  1407.  
  1408. def getRule(trufal) {
  1409.     def thisStr = trufal ? "True" : "False"
  1410.     def prevStr = trufal ? state.actsTrue : state.actsFalse
  1411.     section() {
  1412. // Evaluate Rules        
  1413.         def theseRules = parent.ruleList(app.label)
  1414.         def rule = "rule" + thisStr
  1415.         if(theseRules != null) input rule, "enum", title: "Evaluate Rules", required: false, multiple: true, options: theseRules.sort(), submitOnChange: true
  1416.         checkAct(trufal, rule, "Rules: ${settings[rule]}")
  1417. // Run Rule Actions
  1418.         def ruleAct = "ruleAct" + thisStr
  1419.         if(theseRules != null) input ruleAct, "enum", title: "Run Rule Actions", required: false, multiple: true, options: theseRules.sort(), submitOnChange: true
  1420.         checkAct(trufal, ruleAct, "Rule Actions: ${settings[ruleAct]}")
  1421. // Update Rules
  1422.         def update = "update" + thisStr
  1423.         input update, "enum", title: "Update Rules", required: false, multiple: true, options: theseRules.sort(), submitOnChange: true
  1424.         checkAct(trufal, update, "Update Rules: ${settings[update]}")
  1425. // Evaluate Rules after delay
  1426.         def theseRules2 = parent.ruleList(app.label)
  1427.         theseRules2 << app.label
  1428.         def ruleEvalDelay = "ruleEvalDelay" + thisStr
  1429.         input ruleEvalDelay, "enum", title: "Evaluate Rules after delay", required: false, multiple: true, options: theseRules2.sort(), submitOnChange: true
  1430.         if(settings[ruleEvalDelay]) {
  1431.         def delayEvalMinutes = "delayEvalMinutes" + thisStr
  1432.             input delayEvalMinutes, "number", title: "> Minutes of delay", required: false, range: "1..*", submitOnChange: true
  1433.             if(settings[delayEvalMinutes] > 0) {
  1434.                 def delayStr = "Delay Rule Evaluations: ${settings[ruleEvalDelay]}: ${settings[delayEvalMinutes]} minute"
  1435.                 if(settings[delayEvalMinutes] > 1) delayStr = delayStr + "s"
  1436.                 setAct(trufal, delayStr)
  1437.             }
  1438.         }
  1439. // Set private Boolean
  1440.         def privateV = "private" + thisStr
  1441.         input privateV, "enum", title: "Set private Boolean", required: false, submitOnChange: true, options: ["true", "false"]
  1442.         if(settings[privateV]) {
  1443.             def other = "other" + thisStr
  1444.             def thisB = "thisB" + thisStr
  1445.             def otherPrivate = "otherPrivate" + thisStr
  1446.             def privateDelay = "privateDelay" + thisStr
  1447.             def privateDelayCancel = "privateDelayCancel" + thisStr
  1448.             input thisB, "bool", title: "> For this Rule (default)?", required: false, submitOnChange: true, defaultValue: true
  1449.             input other, "bool", title: "> And/Or for other Rules?", required: false, submitOnChange: true
  1450.             if(settings[other]) input otherPrivate, "enum", title: "> Select Rules to set Boolean", required: false, multiple: true, options: theseRules.sort(), submitOnChange: true
  1451.             input privateDelay, "number", title: "> Set after a delay?", required: false, submitOnChange: true, description: "0 minutes"
  1452.             if(settings[privateDelay] > 0 && (state.isRule || state.howMany > 1)) input privateDelayCancel, "bool", title: "> Cancel on truth change?", required: false, submitOnChange: true
  1453.             if(settings[thisB]) setAct(trufal, "Private Boolean: ${settings[privateV]}" + (settings[privateDelay] ? ": Delay ${settings[privateDelay]} minutes" + (settings[privateDelayCancel] ? " [Cancel]" : "") : ""))
  1454.             if(settings[otherPrivate]) setAct(trufal, "Rule Boolean: ${settings[otherPrivate]}: ${settings[privateV]}" + (settings[privateDelay] ? ": Delay ${settings[privateDelay]} minutes" + (settings[privateDelayCancel] ? " [Cancel]" : "") : ""))
  1455. //          else setAct(trufal, "Private Boolean: ${settings[privateV]}" + (settings[privateDelay] ? ": Delay ${settings[privateDelay]} minutes" + (settings[privateDelayCancel] ? " [Cancel]" : "") : ""))
  1456.         }
  1457.     }
  1458.     def result = (trufal ? state.actsTrue : state.actsFalse) - prevStr
  1459.     if(result) result = result[0..-2]
  1460. }
  1461.  
  1462. def getModeLevel(thisMode, modeVar, trufal) {
  1463.     def result = input modeVar, "number", range: "0..100", title: "> Level for $thisMode", required: true, submitOnChange: true
  1464.     def str = settings[modeVar]
  1465.     if(str) setAct(trufal, "   $thisMode: $str")
  1466. }
  1467.  
  1468. def selectMsgTrue() {
  1469.     dynamicPage(name: "selectMsgTrue", title: "Select Message and Destination", uninstall: false) {
  1470.         state.msgTrue = getMsg(true)
  1471.     }
  1472. }
  1473.  
  1474. def selectMsgFalse() {
  1475.     dynamicPage(name: "selectMsgFalse", title: "Select Message and Destination", uninstall: false) {
  1476.         state.msgFalse = getMsg(false)
  1477.     }
  1478. }
  1479.  
  1480. def getMsg(trufal) {
  1481.     def thisStr = trufal ? "True" : "False"
  1482.     def push = "push" + thisStr
  1483.     def notice = "notice" + thisStr
  1484.     def msg = "msg" + thisStr
  1485.     def refDev = "refDev" + thisStr
  1486.     def phone = "phone" + thisStr
  1487.     def speak = "speak" + thisStr
  1488.     def speakDevice = "speak" + thisStr + "Device"
  1489.     def mediaDevice = "media" + thisStr + "Device"
  1490.     def mediaVolume = "media" + thisStr + "Volume"
  1491.     section("") {
  1492.         if(settings[notice] in [null, false]) input push, "bool", title: "Send Push?", required: false, submitOnChange: true
  1493.         if(settings[push] in [null, false]) input notice, "bool", title: "Send Notification?", required: false, submitOnChange: true
  1494.         input msg, "text", title: "Custom message to send", required: false, submitOnChange: true
  1495.         input refDev, "bool", title: "Include device name?", required: false, submitOnChange: true
  1496.         input phone, "phone", title: "Phone number for SMS", required: false, submitOnChange: true
  1497.         input speak, "bool", title: "Speak this message?", required: false, submitOnChange: true
  1498.         if(settings[speak]){
  1499.             input speakDevice, title: "On this speech device", "capability.speechSynthesis", required: false, multiple: true, submitOnChange: true
  1500.             input mediaDevice, title: "On this music device", "capability.musicPlayer", required: false, multiple: true, submitOnChange: true
  1501.             if (settings[mediaDevice]) input mediaVolume, title: "At this volume", "number", required: false, multiple: false, defaultValue: "50", submitOnChange: true
  1502.         }
  1503.     }
  1504.     def str = (settings[push] ? "Push" : "") + (settings[notice] ? "Notify" : "") + (settings[msg] ? " '${settings[msg]}'" : "") + (settings[refDev] ? " [device]" : "") +
  1505.         (settings[phone] ? " to ${settings[phone]}" : "") + (settings[speak] ? " [speak]" : "") + (settings[mediaDevice] ? " ${settings[mediaDevice]}" : "")
  1506.     return str
  1507. }
  1508.  
  1509. def checkAct(trufal, dev, str) {
  1510.     if(settings[dev]) {
  1511.         if(trufal)  state.actsTrue = state.actsTrue + stripBrackets("$str") + "\n"
  1512.         else        state.actsFalse = state.actsFalse + stripBrackets("$str") + "\n"
  1513.     }
  1514. }
  1515.  
  1516. def setAct(trufal, str) {
  1517.     if(trufal)  state.actsTrue = state.actsTrue + stripBrackets("$str") + "\n"
  1518.     else        state.actsFalse = state.actsFalse + stripBrackets("$str") + "\n"
  1519. }
  1520.  
  1521. // initialization code follows
  1522.  
  1523. def scheduleTimeOfDay() {
  1524.     def start = null
  1525.     def stop = null
  1526.     def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: startSunriseOffsetX, sunsetOffset: startSunsetOffsetX)
  1527.     if(startingXX == "Sunrise") start = s.sunrise.time
  1528.     else if(startingXX == "Sunset") start = s.sunset.time
  1529.     else if(startingA) start = timeToday(startingA,location.timeZone).time
  1530.     s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: endSunriseOffsetX, sunsetOffset: endSunsetOffsetX)
  1531.     if(endingXX == "Sunrise") stop = s.sunrise.time
  1532.     else if(endingXX == "Sunset") stop = s.sunset.time
  1533.     else if(endingA) stop = timeToday(endingA,location.timeZone).time
  1534.     schedule(start, "startHandler")
  1535.     schedule(stop, "stopHandler")
  1536.     if(startingXX in ["Sunrise", "Sunset"] || endingXX in ["Sunrise", "Sunset"])
  1537.         schedule("2015-01-09T02:05:29.000" + gmtOffset(), "scheduleTimeOfDay") // in case sunset/sunrise; change daily
  1538. }
  1539.  
  1540. def scheduleAtTime() {
  1541.     def myTime = null
  1542.     def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: atSunriseOffset, sunsetOffset: atSunsetOffset)
  1543.     if(timeX == "Sunrise") myTime = s.sunrise.time
  1544.     else if(timeX == "Sunset") myTime = s.sunset.time
  1545.     else myTime = timeToday(atTime, location.timeZone).time
  1546.     schedule(myTime, "timeHandler")
  1547.     if(timeX in ["Sunrise", "Sunset"]) schedule("2015-01-09T02:05:29.000" + gmtOffset(), "scheduleAtTime") // in case sunset/sunrise; change daily
  1548. }
  1549.  
  1550. def installed() {
  1551.     initialize()
  1552. }
  1553.  
  1554. def updated() {
  1555.     //unschedule()
  1556.     unsubscribe()
  1557.     parent.unSubscribeRule(app.label)
  1558.     initialize()
  1559. }
  1560.  
  1561. def uninstalled() {
  1562. //  log.debug "uninstalled called"
  1563.     try { parent.removeChild(app.label) }
  1564.     catch (e) { log.error "No child app found" }
  1565. }
  1566.  
  1567. def gmtOffset() {
  1568.     def offset = location.timeZone.rawOffset
  1569.     def offsetAbs = offset < 0 ? -offset : offset
  1570.     def offsetSign = offset < 0 ? "-" : "+"
  1571.     int offsetHour = offsetAbs / 3600000
  1572.     int offsetMin = offsetAbs / 60000
  1573. //  int offsetM6 = offsetMin / 60
  1574. //  int offMin = offsetMin - (offsetM6.toInteger() * 60)
  1575.     int offMin = offsetMin % 60
  1576.     def result = String.format("%s%02d%02d", offsetSign, offsetHour, offMin);    
  1577. }
  1578.  
  1579. def startCron1() {
  1580.     schedule(cronString(1), "cronHandler")
  1581. }
  1582.  
  1583. def startCron2() {
  1584.     schedule(cronString(2), "cron2Handler")
  1585. }
  1586.  
  1587. def initialize() {
  1588.     def hasTrig = state.howManyT > 1
  1589.     def howMany = hasTrig ? state.howManyT : state.howMany
  1590.     for (int i = 1; i < howMany; i++) {
  1591.         def capab = (settings.find {it.key == (hasTrig ? "tCapab$i" : "rCapab$i")}).value
  1592.         def myState = settings.find {it.key == (hasTrig ? "tstate$i" : "state$i")}
  1593.         def myRelDev = settings.find {it.key == (hasTrig ? "reltDevice$i" : "relDevice$i")}
  1594.         def myDev = settings.find {it.key == (hasTrig ? "tDev$i" : "rDev$i")}
  1595.         if(myState) myState = myState.value
  1596.         switch(capab) {
  1597.             case "Mode":
  1598.                 subscribe(location, "mode", allHandler)
  1599.                 break
  1600.             case "Smart Home Monitor":
  1601.                 subscribe(location, "alarmSystemStatus" + ((state.isTrig || hasTrig) ? ".$myState" : ""), allHandler)
  1602.                 break
  1603.             case "Time of day":
  1604.                 scheduleTimeOfDay()
  1605.                 break
  1606.             case "Days of week":
  1607.                 schedule("2015-01-09T00:01:00.000" + gmtOffset(), "dayHandler")
  1608.                 break
  1609.             case "Certain Time":
  1610.                 scheduleAtTime()
  1611.                 break
  1612.             case "Periodic":
  1613.                 if(settings["whichPeriod$i"] == "Hourly" && settings["everyNHoursC$i"]) {
  1614.                     def strtcron = timeToday(settings["startingHC$i"], location.timeZone)
  1615.                     runOnce(strtcron, i == 1 ? startCron1 : startCron2)
  1616.                 } else schedule(cronString(i), i == 1 ? "cronHandler" : "cron2Handler")
  1617.                 break
  1618.             case "Dimmer level":
  1619.                 subscribe(myDev.value, "level", allHandler)
  1620.                 if(myRelDev) subscribe(myRelDev.value, "level", allHandler)
  1621.                 break
  1622.             case "Energy meter":
  1623.                 subscribe(myDev.value, "energy", allHandler)
  1624.                 if(myRelDev) subscribe(myRelDev.value, "energy", allHandler)
  1625.                 break
  1626.             case "Power meter":
  1627.                 subscribe(myDev.value, "power", allHandler)
  1628.                 if(myRelDev) subscribe(myRelDev.value, "power", allHandler)
  1629.                 break
  1630.             case "Temperature":
  1631.                 subscribe(myDev.value, "temperature", allHandler)
  1632.                 if(myRelDev) subscribe(myRelDev.value, "temperature", allHandler)
  1633.                 break
  1634.             case "Humidity":
  1635.                 subscribe(myDev.value, "humidity", allHandler)
  1636.                 if(myRelDev) subscribe(myRelDev.value, "humidity", allHandler)
  1637.                 break
  1638.             case "Battery":
  1639.                 subscribe(myDev.value, "battery", allHandler)
  1640.                 if(myRelDev) subscribe(myRelDev.value, "battery", allHandler)
  1641.                 break
  1642.             case "Illuminance":
  1643.                 subscribe(myDev.value, "illuminance", allHandler)
  1644.                 if(myRelDev) subscribe(myRelDev.value, "illuminance", allHandler)
  1645.                 break
  1646.             case "Carbon monoxide detector":
  1647.                 subscribe(myDev.value, "carbonMonoxide" + ((state.isTrig || hasTrig) ? ".$myState" : ""), allHandler)
  1648.                 break
  1649.             case "Smoke detector":
  1650.                 subscribe(myDev.value, "smoke" + ((state.isTrig || hasTrig) ? ".$myState" : ""), allHandler)
  1651.                 break
  1652.             case "Presence":
  1653.                 subscribe(myDev.value, "presence" + ((state.isTrig || hasTrig) ? (myState == "arrives" ? ".present" : ".not present") : ""), allHandler)
  1654.                 break
  1655.             case "Button":
  1656.                 subscribe(myDev.value, "button", allHandler)
  1657.                 break
  1658.             case "Rule truth":
  1659.                 parent.subscribeRule(app.label, myDev.value, (state.isTrig || hasTrig) ? myState : null, allHandler)
  1660.                 break
  1661.             case "Water sensor":
  1662.                 subscribe(myDev.value, "water" + ((state.isTrig || hasTrig) ? ".$myState" : ""), allHandler)
  1663.                 break
  1664.             case "Garage door":
  1665.                 subscribe(myDev.value, "door" + ((state.isTrig || hasTrig) ? ".$myState" : ""), allHandler)
  1666.                 break
  1667.             case "Door":
  1668.                 subscribe(myDev.value, "door" + ((state.isTrig || hasTrig) ? ".$myState" : ""), allHandler)
  1669.                 break
  1670.             case "Thermostat Mode":
  1671.                 subscribe(myDev.value, "thermostatMode" + ((state.isTrig || hasTrig) ? ".$myState" : ""), allHandler)
  1672.                 break
  1673.             case "Thermostat State":
  1674.                 subscribe(myDev.value, "thermostatOperatingState" + ((state.isTrig || hasTrig) ? ".$myState" : ""), allHandler)
  1675.                 break
  1676.             case "Physical Switch":
  1677.                 subscribe(myDev.value, "switch.$myState", physicalHandler)
  1678.                 break
  1679.             case "Routine":
  1680.                 subscribe(location, "routineExecuted", allHandler)
  1681.                 break
  1682.             case "Music player":
  1683.                 subscribe(myDev.value, "status" + ((state.isTrig || hasTrig) ? ".$myState" : ""), allHandler)
  1684.                 break
  1685.             case "Private Boolean":
  1686.                 break
  1687.             default:
  1688.                 subscribe(myDev.value, (capab.toLowerCase() + ((state.isTrig || hasTrig) ? ".$myState" : "")), allHandler)
  1689.         }
  1690.     }
  1691.     subscribe(disabled, "switch", disabledHandler)
  1692.     def disOnOff = disabledOff ? "off" : "on"
  1693.     if(disabled) state.disabled = disabled.currentSwitch == disOnOff
  1694.     else state.disabled = false
  1695. //  parent.setRuleTruth(app.label, true)
  1696.     if(hasTrig && state.howMany > 1) state.success = true
  1697.     else state.success = null
  1698.     if(state.isTrig || hasTrig) return
  1699.     if(state.isRule || state.howMany > 1) runRule(true)
  1700. }
  1701.  
  1702. // Main rule evaluation code follows
  1703.  
  1704. def compare(a, rel, b, relDev) {
  1705.     def result = true
  1706.     if     (rel == "=")     result = a == (relDev ? relDev + b : b)
  1707.     else if(rel == "!=")    result = a != (relDev ? relDev + b : b)
  1708.     else if(rel == ">")     result = a >  (relDev ? relDev + b : b)
  1709.     else if(rel == "<")     result = a <  (relDev ? relDev + b : b)
  1710.     else if(rel == ">=")    result = a >= (relDev ? relDev + b : b)
  1711.     else if(rel == "<=")    result = a <= (relDev ? relDev + b : b)
  1712.     return result
  1713. }
  1714.  
  1715. def checkCondAny(dev, stateX, cap, rel, relDev) {
  1716.     if(stateX == "leaves") stateX = "not present"
  1717.     else if(stateX == "arrives") stateX = "present"
  1718.     def result = false
  1719.     if     (cap == "Temperature")   dev.currentTemperature.each     {result = result || compare(it, rel, stateX, relDev ? relDev.currentTemperature : null)}
  1720.     else if(cap == "Humidity")      dev.currentHumidity.each        {result = result || compare(it, rel, stateX, relDev ? relDev.currentHumidity : null)}
  1721.     else if(cap == "Illuminance")   dev.currentIlluminance.each     {result = result || compare(it, rel, stateX, relDev ? relDev.currentIlluminance : null)}
  1722.     else if(cap == "Dimmer level")  dev.currentLevel.each           {result = result || compare(it, rel, stateX, relDev ? relDev.currentLevel : null)}
  1723.     else if(cap == "Energy meter")  dev.currentEnergy.each          {result = result || compare(it, rel, stateX, relDev ? relDev.currentEnergy : null)}
  1724.     else if(cap == "Power meter")   dev.currentPower.each           {result = result || compare(it, rel, stateX, relDev ? relDev.currentPower : null)}
  1725.     else if(cap == "Battery")       dev.currentBattery.each         {result = result || compare(it, rel, stateX, relDev ? relDev.currentBattery : null)}
  1726.     else if(cap == "Rule truth")    dev.each {
  1727.         def truth = parent.currentRule(it)
  1728.         result = result || stateX == "$truth"
  1729.     }
  1730.     else if(cap == "Water sensor")              result = stateX in dev.currentWater
  1731.     else if(cap == "Switch")                    result = stateX in dev.currentSwitch
  1732.     else if(cap == "Motion")                    result = stateX in dev.currentMotion
  1733.     else if(cap == "Acceleration")              result = stateX in dev.currentAcceleration
  1734.     else if(cap == "Contact")                   result = stateX in dev.currentContact
  1735.     else if(cap == "Presence")                  result = stateX in dev.currentPresence
  1736.     else if(cap == "Smoke detector")            result = stateX in dev.currentSmoke
  1737.     else if(cap == "Carbon monoxide detector")  result = stateX in dev.currentCarbonMonoxide
  1738.     else if(cap == "Lock")                      result = stateX in dev.currentLock
  1739.     else if(cap == "Garage door")               result = stateX in dev.currentDoor
  1740.     else if(cap == "Door")                      result = stateX in dev.currentDoor
  1741.     else if(cap == "Thermostat Mode")           result = stateX in dev.currentThermostatMode
  1742.     else if(cap == "Music player")              result = stateX in dev.currentStatus
  1743.     else if(cap == "Thermostat State")          result = stateX in dev.currentThermostatOperatingState
  1744. //  log.debug "CheckAny $cap $result"
  1745.     return result
  1746. }
  1747.  
  1748.  
  1749. def checkCondAll(dev, stateX, cap, rel, relDev) {
  1750.     def result = true
  1751.     if     (cap == "Temperature")       dev.currentTemperature.each {result = result && compare(it, rel, stateX, relDev ? relDev.currentTemperature : null)}
  1752.     else if(cap == "Humidity")          dev.currentHumidity.each    {result = result && compare(it, rel, stateX, relDev ? relDev.currentHumidity : null)}
  1753.     else if(cap == "Illuminance")       dev.currentIlluminance.each {result = result && compare(it, rel, stateX, relDev ? relDev.currentIlluminance : null)}
  1754.     else if(cap == "Dimmer level")      dev.currentLevel.each       {result = result && compare(it, rel, stateX, relDev ? relDev.currentLevel : null)}
  1755.     else if(cap == "Energy meter")      dev.currentEnergy.each      {result = result && compare(it, rel, stateX, relDev ? relDev.currentEnergy : null)}
  1756.     else if(cap == "Power meter")       dev.currentPower.each       {result = result && compare(it, rel, stateX, relDev ? relDev.currentPower : null)}
  1757.     else if(cap == "Battery")           dev.currentBattery.each     {result = result && compare(it, rel, stateX, relDev ? relDev.currentBattery : null)}
  1758.     else if(cap == "Rule truth")        dev.each {
  1759.         def rule = parent.currentRule(it)
  1760.         result = result && "$stateX" == "$rule"
  1761.     }
  1762.     else if(cap == "Water sensor")              dev.currentSwitch.each                      {result = result && stateX == it}
  1763.     else if(cap == "Switch")                    dev.currentSwitch.each                      {result = result && stateX == it}
  1764.     else if(cap == "Motion")                    dev.currentMotion.each                      {result = result && stateX == it}
  1765.     else if(cap == "Acceleration")              dev.currentAcceleration.each                {result = result && stateX == it}
  1766.     else if(cap == "Contact")                   dev.currentContact.each                     {result = result && stateX == it}
  1767.     else if(cap == "Presence")                  dev.currentPresence.each                    {result = result && stateX == it}
  1768.     else if(cap == "Smoke detector")            dev.currentSmoke.each                       {result = result && stateX == it}
  1769.     else if(cap == "Carbon monoxide detector")  dev.currentCarbonMonoxide.each              {result = result && stateX == it}
  1770.     else if(cap == "Lock")                      dev.currentLock.each                        {result = result && stateX == it}
  1771.     else if(cap == "Garage door")               dev.currentDoor.each                        {result = result && stateX == it}
  1772.     else if(cap == "Door")                      dev.currentDoor.each                        {result = result && stateX == it}
  1773.     else if(cap == "Music player")              dev.currentStatus.each                      {result = result && stateX == it}
  1774.     else if(cap == "Thermostat Mode")           dev.currentThermostatMode.each              {result = result && stateX == it}
  1775.     else if(cap == "Thermostat State")          dev.currentThermmostatOperatingState.each   {result = result && stateX == it}
  1776. //  log.debug "CheckAll $cap $result"
  1777.     return result
  1778. }
  1779.  
  1780.  
  1781. def getOperand(i, isR) {
  1782.     def result = true
  1783.     def foundItem = (settings.find {it.key == (isR ? "rCapab$i" : "tCapab$i")})
  1784.     if (foundItem == null) {
  1785. //        log.info "Cannot get operand for i: $i   isR: $isR"
  1786.         return null
  1787.     }
  1788.     def capab = (settings.find {it.key == (isR ? "rCapab$i" : "tCapab$i")}).value
  1789.     if     (capab == "Mode") result = modeOk
  1790.     else if(capab == "Time of day") result = timeOkX
  1791.     else if(capab == "Days of week") result = daysOk
  1792.     else if(capab == "Private Boolean") {
  1793.         def thisState = settings.find{it.key == (isR ? "state$i" : "tstate$i")}
  1794.         result = thisState.value == state.private //.toString()
  1795.     } else if(capab == "Smart Home Monitor") result = (settings.find {it.key == (isR ? "state$i" : "tstate$i")}).value == location.currentState("alarmSystemStatus")?.value
  1796.     else {
  1797.         def myDev =     settings.find {it.key == (isR ? "rDev$i" : "tDev$i")}
  1798.         def myState =   settings.find {it.key == (isR ? "state$i" : "tstate$i")}
  1799.         def myRel =     settings.find {it.key == (isR ? "RelrDev$i" : "ReltDev$i")}
  1800.         def myAll =     settings.find {it.key == (isR ? "AllrDev$i" : "AlltDev$i")}
  1801.         def myRelDev =  settings.find {it.key == (isR ? "relDevice$i" : "reltDevice$i")}
  1802.         if(!myDev) return false
  1803.         if(myAll) {
  1804.             if(myAll.value) result = checkCondAll(myDev.value, myState ? myState.value : 0, capab, myRel ? myRel.value : 0, myRelDev ? myRelDev.value : 0)
  1805.             else result = checkCondAny(myDev.value, myState ? myState.value : 0, capab, myRel ? myRel.value : 0, myRelDev ? myRelDev.value : 0)
  1806.         } else result = checkCondAny(myDev.value, myState ? myState.value : 0, capab, myRel ? myRel.value : 0, myRelDev ? myRelDev.value : 0)
  1807.     }
  1808. //    log.debug "operand $i is $result"
  1809.     return result
  1810. }
  1811.  
  1812. def findRParen() {
  1813.     def noMatch = true
  1814.     while(noMatch) {
  1815.         if(state.eval[state.token] == ")") {
  1816.             if(state.parenLev == 0) return
  1817.             else state.parenLev = state.parenLev - 1
  1818.         } else if(state.eval[state.token] == "(") state.parenLev = state.parenLev + 1
  1819.         state.token = state.token + 1
  1820.         if(state.token >= state.eval.size) return
  1821.     }
  1822. }
  1823.  
  1824. def disEval() {
  1825.     state.parenLev = 0
  1826.     findRParen()
  1827. }
  1828.  
  1829. def evalTerm() {
  1830.     def result = true
  1831.     def negate = false
  1832.     if(state.token >= state.eval.size) return false
  1833.     def thisTok = state.eval[state.token]
  1834.     if(thisTok == "NOT") {
  1835.         state.token = state.token + 1
  1836.         if(state.token >= state.eval.size) return false
  1837.         thisTok = state.eval[state.token]
  1838.         negate = true
  1839.     }
  1840.     if(thisTok == "(") {
  1841.         state.token = state.token + 1
  1842.         result = eval()
  1843.     } else result = getOperand(thisTok, true)
  1844.     state.token = state.token + 1
  1845.     return negate ? !result : result
  1846. }
  1847.  
  1848. def eval() {
  1849.     def result = evalTerm()
  1850.     while(true) {
  1851.         if(state.token >= state.eval.size) return result
  1852.         def thisTok = state.eval[state.token]
  1853.         if (thisTok == "OR") {
  1854.             if(result) {
  1855.                 disEval()
  1856.                 return true
  1857.             }
  1858.         } else if (thisTok == "AND") {
  1859.             if(!result) {
  1860.                 disEval()
  1861.                 return false
  1862.             }
  1863.         } else if (thisTok in [")", null]) return result
  1864.         state.token = state.token + 1
  1865.         result = evalTerm()
  1866.     }
  1867. }
  1868.  
  1869. // Run the evaluation and take action code follows
  1870.  
  1871. def adjustFan(device) {
  1872. //  log.debug "adjust: $device = ${device.currentLevel}"
  1873.     def currentLevel = device.currentLevel
  1874.     if(device.currentSwitch == 'off') device.setLevel(15)
  1875.     else if (currentLevel < 34) device.setLevel(50)
  1876.     else if (currentLevel < 67) device.setLevel(90)
  1877.     else device.setLevel(0)
  1878. }
  1879.  
  1880. def adjustShade(device) {
  1881. //  log.debug "shades: $device = ${device.currentMotor} state.lastUP = $state.lastshadesUp"
  1882.     if(device.currentMotor in ["up","down"]) {
  1883.         state.lastshadesUp = device.currentMotor == "up"
  1884.         device.stop()
  1885.     } else {
  1886.         state.lastshadesUp ? device.down() : device.up()
  1887. //      if(state.lastshadesUp) device.down()
  1888. //        else device.up()
  1889.         state.lastshadesUp = !state.lastshadesUp
  1890.     }
  1891. }
  1892.  
  1893. def toggle(devices, trufal) {
  1894. //  log.debug "toggle: $devices = ${devices*.currentValue('switch')}"
  1895.     def del = trufal ? (delayMilTrue ?: 0) : (delayMilFalse ?: 0)
  1896.     if (devices*.currentValue('switch').contains('on')) {
  1897.         if(del) devices.off([delay: del]) else devices.off()
  1898.     }
  1899.     else if (devices*.currentValue('switch').contains('off')) {
  1900.         if(del) devices.on([delay: del]) else devices.on()
  1901.     }
  1902. }
  1903.  
  1904. def dimToggle(devices, dimLevel, trufal) {
  1905. //  log.debug "dimToggle: $devices = ${devices*.currentValue('switch')}"
  1906.     def del = trufal ? (delayMilTrue ?: 0) : (delayMilFalse ?: 0)
  1907.     if (devices*.currentValue('switch').contains('on')) {if(del) devices.off([delay: del]) else devices.off()}
  1908.     else if(del) devices.setLevel(dimLevel, [delay: del]) else devices.setLevel(dimLevel)
  1909. }
  1910.  
  1911. def dimAdjust(devices, dimLevel, trufal) {
  1912. //  log.debug "dimAdjust: $devices = ${devices*.currentValue('level')}"
  1913.     def del = trufal ? (delayMilTrue ?: 0) : (delayMilFalse ?: 0)
  1914.     devices.each {
  1915.         def level = it.currentLevel + dimLevel
  1916.         if(level > 99) level = 99
  1917.         if(level < 0) level = 0
  1918.         if(del) it.setLevel(level, [delay: del]) else it.setLevel(level)
  1919.     }
  1920. }
  1921.  
  1922. def dimModes(trufal) {
  1923.     if(location.mode in (trufal ? dimmerModesTrue : dimmerModesFalse)) {
  1924.         def lev = settings.find{it.key == ("level" + (trufal ? "True" : "False") + "$location.mode")}.value
  1925.         def dev = trufal ? dimMTrue : dimMFalse
  1926.         dev.setLevel(lev)
  1927.     }
  1928. }
  1929.  
  1930. def sendSmsMulti(phone, msg) {
  1931.     def num = ""
  1932.     def i = phone.indexOf('*')
  1933.     num = i > 0 ? phone.substring(0, i) : phone
  1934.     sendSms(num, msg)
  1935.     num = i > 0 ? phone.substring(i + 1) : ""
  1936.     if(num) sendSmsMulti(num, msg)
  1937. }
  1938.  
  1939. def doDelay(time, rand, cancel, trufal) {
  1940.     def myTime = time
  1941.     if(rand) {
  1942.         myTime = Math.random()*time as Integer
  1943.         if(time > 60 && time % 60 == 0 && myTime < 60) myTime = 60
  1944.         else if(myTime < 10) myTime = 10
  1945.     }
  1946.     if(cancel) {
  1947.         runIn(myTime, trufal ? delayRuleTrue : delayRuleFalse)
  1948.         if(trufal) state.delayRuleTrue = true else state.delayRuleFalse = true
  1949.     } else runIn(myTime, trufal ? delayRuleTrueForce : delayRuleFalseForce)
  1950.     def isMins = myTime % 60 == 0
  1951.     if(isMins) myTime = myTime / 60
  1952.     def delayStr = isMins ? "minute" : "seconds"
  1953.     if(myTime > 1 && isMins) delayStr = delayStr + "s"
  1954.     if(state.isRule || state.howMany > 1) log.info ("$app.label is " + (trufal ? "True" : "False") + ", but " + (rand ? "random delay of $myTime $delayStr" : "delayed by $myTime $delayStr"))
  1955.     else log.info (rand ? "Random delay of $myTime $delayStr" : "Delayed by $myTime $delayStr")
  1956. }
  1957.  
  1958. def capture(dev) {
  1959.     state.lastDevState = []
  1960.     def i = 0
  1961.     def switchState = null
  1962.     def dimmerValue = null
  1963.     def hueValue = null
  1964.     def satValue = null
  1965.     dev.each {
  1966.         switchState = it.currentSwitch
  1967.         dimmerValue = it.currentLevel
  1968.         hueValue = it.currentHue
  1969.         satValue = it.currentSaturation
  1970.         state.lastDevState[i] = [switchState: switchState, dimmerValue: dimmerValue, hueValue: hueValue, satValue: satValue]
  1971.         i++        
  1972.     }
  1973. }
  1974.  
  1975. def restoreDev(switches, switchState, dimmerValue, hueValue, satValue) {
  1976.     int hueX = hueValue in ["null", null] ? 0 : hueValue.toInteger()
  1977.     int satX = satValue in ["null", null] ? 0 : satValue.toInteger()
  1978.     if(switchState == "off") switches.off()
  1979.     else if(hueX > 0 && satX > 0) {
  1980.         def newValue = [hue: hueX, saturation: satX, level: dimmerValue]
  1981.         switches.setColor(newValue)
  1982.     } else if(dimmerValue) switches.setLevel(dimmerValue)
  1983.     else switches.on()
  1984. }
  1985.  
  1986. def restore() {
  1987.     def i = 0
  1988.     def switchState = null
  1989.     def dimmerValue = null
  1990.     def hueValue = null
  1991.     def satValue = null
  1992.     state.lastDevState.each {
  1993.         switchState = it.switchState
  1994.         dimmerValue = it.dimmerValue
  1995.         hueValue = it.hueValue
  1996.         satValue = it.satValue
  1997.         if(captureTrue) restoreDev(captureTrue[i], switchState, dimmerValue, hueValue, satValue)
  1998.         if(captureFalse) restoreDev(captureFalse[i], switchState, dimmerValue, hueValue, satValue)
  1999.         i++
  2000.     }
  2001. }
  2002.  
  2003. def takeAction(success) {
  2004.     if(success) {
  2005.         if(captureTrue)         capture(captureTrue)
  2006.         if(onSwitchTrue)        if(delayMilTrue) onSwitchTrue.on([delay: delayMilTrue]) else onSwitchTrue.on()
  2007.         if(toggleSwitchTrue)    toggle(toggleSwitchTrue, true)
  2008.         if(delayedOffTrue)  {   if(delayMinutesTrue) runIn(delayMinutesTrue * 60, delayOffTrue)
  2009.                                 if(delaySecondsTrue) runIn(delaySecondsTrue, delayOffTrue)
  2010.                                 if(delayMillisTrue) {if(delayOnOffTrue) delayedOffTrue.on([delay: delayMillisTrue]) else delayedOffTrue.off([delay: delayMillisTrue])}   }
  2011.         if(pendedOffTrue)   {   state.pendingOffTrue = true
  2012.                                 if(pendMinutesTrue > 0) runIn(pendMinutesTrue * 60, pendingOffTrue) else pendingOffTrue()}
  2013.         if(pendedOffFalse)      state.pendingOffFalse = false  //unschedule(pendingOffFalse)}
  2014.         if(dimTrackTrue && dimATrue != null)        if(state.lastEvtLevel != null) {if(delayMilTrue) dimATrue.setLevel(state.lastEvtLevel, [delay: delayMilTrue]) else dimATrue.setLevel(state.lastEvtLevel)}
  2015.         if(dimATrue && dimLATrue != null)           if(delayMilTrue) dimATrue.setLevel(dimLATrue, [delay: delayMilTrue]) else dimATrue.setLevel(dimLATrue)
  2016.         if(dimBTrue && dimLBTrue != null)           if(delayMilTrue) dimBTrue.setLevel(dimLBTrue, [delay: delayMilTrue]) else dimBTrue.setLevel(dimLBTrue)
  2017.         if(toggleDimmerTrue && dimTogTrue != null)  dimToggle(toggleDimmerTrue, dimTogTrue, true)
  2018.         if(adjustDimmerTrue && dimAdjTrue != null)  dimAdjust(adjustDimmerTrue, dimAdjTrue, true)
  2019.         if(dimmerModesTrue && dimMTrue)             dimModes(true)
  2020.         if(ctTrue && ctLTrue)   ctTrue.setColorTemperature(ctLTrue)
  2021.         if(bulbsTrue)           setColor(true)
  2022.         if(garageOpenTrue)      if(delayMilTrue) garageOpenTrue.open([delay: delayMilTrue]) else garageOpenTrue.open()
  2023.         if(garageCloseTrue)     if(delayMilTrue) garageCloseTrue.close([delay: delayMilTrue]) else garageCloseTrue.close()
  2024.         if(lockTrue)            if(delayMilTrue) lockTrue.lock([delay: delayMilTrue]) else lockTrue.lock()
  2025.         if(unlockTrue)          if(delayMilTrue) unlockTrue.unlock([delay: delayMilTrue]) else unlockTrue.unlock()
  2026.         if(fanAdjustTrue)       adjustFan(fanAdjustTrue)
  2027.         if(openValveTrue)       if(delayMilTrue) openValveTrue.open([delay: delayMilTrue]) else openValveTrue.open()
  2028.         if(closeValveTrue)      if(delayMilTrue) closeValveTrue.close([delay: delayMilTrue]) else closeValveTrue.close()
  2029.         if(thermoTrue)      {   if(thermoModeTrue)  thermoTrue.setThermostatMode(thermoModeTrue)
  2030.                                 if(thermoSetHeatTrue)   thermoTrue.setHeatingSetpoint(thermoSetHeatTrue)
  2031.                                 if(thermoAdjHeatTrue)   thermoTrue.each{it.setHeatingSetpoint(it.currentHeatingSetpoint + thermoAdjHeatTrue)}
  2032.                                 if(thermoSetCoolTrue)   thermoTrue.setCoolingSetpoint(thermoSetCoolTrue)
  2033.                                 if(thermoAdjCoolTrue)   thermoTrue.each{it.setCoolingSetpoint(it.currentCoolingSetpoint + thermoAdjCoolTrue)}
  2034.                                 if(thermoFanTrue)   thermoTrue.setThermostatFanMode(thermoFanTrue)   }
  2035.         if(alarmTrue)           sendLocationEvent(name: "alarmSystemStatus", value: "$alarmTrue")
  2036.         if(modeTrue)            setLocationMode(modeTrue)
  2037.         if(privateTrue)         if(privateDelayTrue) {  if(privateDelayCancelTrue) { state.delayRuleTrue = true
  2038.                                                             runIn(privateDelayTrue * 60, delayPrivyTrueX) }
  2039.                                                         else runIn(privateDelayTrue * 60, delayPrivyTrue) }
  2040.                                 else if(otherTrue && otherPrivateTrue) parent.setRuleBoolean(otherPrivateTrue, privateTrue, app.label)
  2041.                                 if(thisBTrue) state.private = privateTrue // == "true"
  2042.         if(ruleTrue)            parent.runRule(ruleTrue, app.label)
  2043.         if(ruleActTrue)         parent.runRuleAct(ruleActTrue, app.label)
  2044.         if(ruleEvalDelayTrue)   if(delayEvalMinutesTrue) runIn(delayEvalMinutesTrue * 60, delayEvalTrue)
  2045.         if(updateTrue)          parent.runUpdate(updateTrue)
  2046.         if(myPhraseTrue)        location.helloHome.execute(myPhraseTrue)
  2047.         if(cameraTrue)      {   cameraTrue.take()
  2048.                                 (1..((burstCountTrue ?: 5) - 1)).each {cameraTrue.take(delay: (500 * it))}   }
  2049.         if(pushTrue)            sendPush((msgTrue ?: "Rule $app.label True") + (refDevTrue ? " $state.lastEvtName" : ""))
  2050.         if(noticeTrue)          sendNotificationEvent((msgTrue ?: "Rule $app.label True") + (refDevTrue ? " $state.lastEvtName" : ""))
  2051.         if(phoneTrue)           sendSmsMulti(phoneTrue, (msgTrue ?: "Rule $app.label True") + (refDevTrue ? " $state.lastEvtName" : ""))
  2052.         if(speakTrue)           speakTrueDevice?.speak((msgTrue ?: "Rule $app.label True") + (refDevTrue ? " $state.lastEvtName" : ""))
  2053.         if(mediaTrueDevice)     mediaTrueDevice.playTextAndRestore((msgTrue ?: "Rule $app.label True") + (refDevTrue ? " $state.lastEvtName" : ""), mediaTrueVolume)
  2054.         if(state.howManyCCtrue > 1)     execCommands(true)
  2055.         if(offSwitchTrue)       if(delayMilTrue) offSwitchTrue.off([delay: delayMilTrue]) else offSwitchTrue.off()
  2056.         if(restoreTrue)         if(restoreDelayTrue > 0) {  if(restoreCancelTrue) { state.delayRuleTrue = true
  2057.                                                                 runIn(restoreDelayTrue * 60, delayRestoreTrue) }
  2058.                                                             else runIn(restoreDelayTrue * 60, restore) }
  2059.                                 else restore()
  2060.     } else {
  2061.         if(captureFalse)        capture(captureFalse)
  2062.         if(onSwitchFalse)       if(delayMilFalse) onSwitchFalse.on([delay: delayMilFalse]) else onSwitchFalse.on()
  2063.         if(toggleSwitchFalse)   toggle(toggleSwitchFalse, false)
  2064.         if(delayedOffFalse) {   if(delayMinutesFalse) runIn(delayMinutesFalse * 60, delayOffFalse)
  2065.                                 if(delaySecondsFalse) runIn(delaySecondsFalse, delayOffFalse)
  2066.                                 if(delayMillisFalse) {if(delayOnOffFalse) delayedOffFalse.on([delay: delayMillisFalse]) else delayedOffFalse.off([delay: delayMillisFalse])}   }
  2067.         if(pendedOffFalse)  {   state.pendingOffFalse = true
  2068.                                 if(pendMinutesFalse > 0) runIn(pendMinutesFalse * 60, pendingOffFalse) else pendingOffFalse()}
  2069.         if(pendedOffTrue)       state.pendingOffTrue = false  //unschedule(pendingOffTrue)}
  2070.         if(dimTrackFalse && dimAFalse != null)          if(state.lastEvtLevel != null) {if(delayMilFalse) dimAFalse.setLevel(state.lastEvtLevel, [delay: delayMilFalse]) else dimAFalse.setLevel(state.lastEvtLevel)}
  2071.         if(dimAFalse && dimLAFalse != null)             if(delayMilFalse) dimAFalse.setLevel(dimLAFalse, [delay: delayMilFalse]) else dimAFalse.setLevel(dimLAFalse)
  2072.         if(dimBFalse && dimLBFalse != null)             if(delayMilFalse) dimBFalse.setLevel(dimLBFalse, [delay: delayMilFalse]) else dimBFalse.setLevel(dimLBFalse)
  2073.         if(toggleDimmerFalse && dimTogFalse != null)    dimToggle(toggleDimmerFalse, dimTogFalse, false)
  2074.         if(adjustDimmerFalse && dimAdjFalse != null)    dimAdjust(adjustDimmerFalse, dimAdjFalse, false)
  2075.         if(dimmerModesFalse && dimMFalse)               dimModes(false)
  2076.         if(ctFalse)             ctFalse.setColorTemperature(ctLFalse)
  2077.         if(bulbsFalse)          setColor(false)
  2078.         if(garageOpenFalse)     if(delayMilFalse) garageOpenFalse.open([delay: delayMilFalse]) else garageOpenFalse.open()
  2079.         if(garageCloseFalse)    if(delayMilFalse) garageCloseFalse.close([delay: delayMilFalse]) else garageCloseFalse.close()
  2080.         if(lockFalse)           if(delayMilFalse) lockFalse.lock([delay: delayMilFalse]) else lockFalse.lock()
  2081.         if(unlockFalse)         if(delayMilFalse) unlockFalse.unlock([delay: delayMilFalse]) else unlockFalse.unlock()
  2082.         if(fanAdjustFalse)      adjustFan(fanAdjustFalse)
  2083.         if(openValveFalse)      if(delayMilFalse) openValveFalse.open([delay: delayMilFalse]) else openValveFalse.open()
  2084.         if(closeValveFalse)     if(delayMilFalse) closeValveFalse.close([delay: delayMilFalse]) else closeValveFalse.close()
  2085.         if(thermoFalse)     {   if(thermoModeFalse)     thermoFalse.setThermostatMode(thermoModeFalse)
  2086.                                 if(thermoSetHeatFalse)  thermoFalse.setHeatingSetpoint(thermoSetHeatFalse)
  2087.                                 if(thermoAdjHeatFalse)  thermoFalse.each{it.setHeatingSetpoint(it.currentHeatingSetpoint + thermoAdjHeatFalse)}
  2088.                                 if(thermoSetCoolFalse)  thermoFalse.setCoolingSetpoint(thermoSetCoolFalse)  
  2089.                                 if(thermoAdjCoolFalse)  thermoFalse.each{it.setCoolingSetpoint(it.currentCoolingSetpoint + thermoAdjCoolFalse)}
  2090.                                 if(thermoFanFalse)  thermoFalse.setThermostatFanMode(thermoFanFalse)   }
  2091.         if(alarmFalse)          sendLocationEvent(name: "alarmSystemStatus", value: "$alarmFalse")
  2092.         if(modeFalse)           setLocationMode(modeFalse)
  2093.         if(privateFalse)        if(privateDelayFalse) { if(privateDelayCancelFalse) { state.delayRuleFalse = true
  2094.                                                             runIn(privateDelayFalse * 60, delayPrivyFalseX) }
  2095.                                                         else runIn(privateDelayFalse * 60, delayPrivyFalse) }
  2096.                                 else if(otherFalse && otherPrivateFalse) parent.setRuleBoolean(otherPrivateFalse, privateFalse, app.label)
  2097.                                 if(thisBFalse) state.private = privateFalse
  2098.         if(ruleFalse)           parent.runRule(ruleFalse, app.label)
  2099.         if(ruleActFalse)        parent.runRuleAct(ruleActFalse, app.label)
  2100.         if(ruleEvalDelayFalse)  if(delayEvalMinutesFalse) runIn(delayEvalMinutesFalse * 60, delayEvalFalse)
  2101.         if(updateFalse)         parent.runUpdate(updateFalse)
  2102.         if(myPhraseFalse)       location.helloHome.execute(myPhraseFalse)
  2103.         if(cameraFalse)     {   cameraFalse.take()
  2104.                                 (1..((burstCountFalse ?: 5) - 1)).each {cameraFalse.take(delay: (500 * it))}   }
  2105.         if(pushFalse)           sendPush((msgFalse ?: "Rule $app.label False") + (refDevFalse ? " $state.lastEvtName" : ""))
  2106.         if(noticeFalse)         sendNotificationEvent((msgFalse ?: "Rule $app.label False") + (refDevFalse ? " $state.lastEvtName" : ""))
  2107.         if(phoneFalse)          sendSmsMulti(phoneFalse, (msgFalse ?: "Rule $app.label False") + (refDevFalse ? " $state.lastEvtName" : ""))
  2108.         if(speakFalse)          speakFalseDevice?.speak((msgFalse ?: "Rule $app.label False") + (refDevFalse ? " $state.lastEvtName" : ""))
  2109.         if(mediaFalseDevice)    mediaFalseDevice.playTextAndRestore((msgFalse ?: "Rule $app.label False") + (refDevFalse ? " $state.lastEvtName" : ""), mediaFalseVolume)      
  2110.         if(state.howManyCCfalse > 1)    execCommands(false)
  2111.         if(offSwitchFalse)      if(delayMilFalse) offSwitchFalse.off([delay: delayMilFalse]) else offSwitchFalse.off()
  2112.         if(restoreFalse)        if(restoreDelayFalse > 0) { if(restoreCancelFalse) { state.delayRuleFalse = true
  2113.                                                                 runIn(restoreDelayFalse * 60, delayRestoreFalse) }
  2114.                                                             else runIn(restoreDelayFalse * 60, restore) }
  2115.                                 else restore()
  2116.     }
  2117. }
  2118.  
  2119. def runRule(force) {
  2120.     if(!allOk) return
  2121.     state.token = 0
  2122.     def success = eval()
  2123.     if((success != state.success) || force) {
  2124. //      unschedule(delayRuleTrue)
  2125. //      unschedule(delayRuleFalse)
  2126.         state.delayRuleTrue = false
  2127.         state.delayRuleFalse = false
  2128.         if     (delayMinTrue > 0 && success)    doDelay(delayMinTrue * 60, randomTrue, cancelTrue, true)
  2129.         else if(delayMinFalse > 0 && !success)  doDelay(delayMinFalse * 60, randomFalse, cancelFalse, false)
  2130.         else if(delaySecTrue > 0 && success)    doDelay(delaySecTrue, false, cancelTrue, true)
  2131.         else if(delaySecFalse > 0 && !success)  doDelay(delaySecFalse, false, cancelFalse, false)
  2132.         else takeAction(success)
  2133.         parent.setRuleTruth(app.label, success)
  2134.         state.success = success
  2135.         log.info (success ? "$app.label is now True" : "$app.label is now False")
  2136.     } // else log.info "$app.label evaluated " + (success ? "true" : "false")
  2137. }
  2138.  
  2139. def doTrigger() {
  2140.     if(!allOk) return
  2141.     if     (delayMinTrue > 0)   doDelay(delayMinTrue * 60, randomTrue, true, true)
  2142.     else if(delaySecTrue > 0)   doDelay(delaySecTrue, false, true, true)     
  2143.     else takeAction(true)
  2144.     log.info ("$app.label Triggered")
  2145. }
  2146.  
  2147. // Event Handler code follows
  2148.  
  2149. def getButton(dev, evt, i) {
  2150.     def numNames = ["", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
  2151.         "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty"]
  2152.     def buttonNumber = evt.jsonData.buttonNumber.toInteger()
  2153.     def value = evt.value
  2154.     def recentEvents = dev.eventsSince(new Date(now() - 3000)).findAll{it.value == evt.value && it.data == evt.data}
  2155.     def thisButton = 0
  2156.     def firstEventId = 0
  2157.     if (recentEvents.size() != 0) firstEventId = recentEvents[0].id
  2158.     if(firstEventId == evt.id) thisButton = numNames[buttonNumber]
  2159.     def myState = settings.find {it.key == (state.isTrig ? "state$i" : "tstate$i")}
  2160.     def myButton = settings.find {it.key == (state.isTrig ? "ButtonrDev$i" : "ButtontDev$i")}
  2161.     def result = true
  2162.     if(value in ["pushed", "held"]) result = (value == myState.value) && (thisButton == myButton.value)
  2163.     else if(value.startsWith("button")) result = thisButton == myButton.value // ZWN-SC7
  2164. }
  2165.  
  2166. def testEvt(evt) {
  2167.     def result = false
  2168.     def howMany = state.isTrig ? state.howMany : state.howManyT
  2169.     if(evt.name == "mode") return modeXOk
  2170.     if(evt.name == "routineExecuted") {
  2171.         for(int i = 1; i < howMany; i++) {
  2172.             def myCapab = (settings.find {it.key == (state.isTrig ? "rCapab$i" : "tCapab$i")}).value
  2173.             def state = settings.find {it.key == (state.isTrig ? "state$i" : "tstate$i")}
  2174.             if(myCapab == "Routine") result = result || evt.displayName == state.value
  2175.             if(result) return true
  2176.         }
  2177.         return false
  2178.     }
  2179.     for(int i = 1; i < howMany; i++) {
  2180.         def myDev = settings.find {it.key == (state.isTrig ? "rDev$i" : "tDev$i")}
  2181.         if(myDev) {
  2182.             myDev = myDev.value
  2183.             myDev.each {if(evt.displayName == it.displayName) {
  2184.                 if(evt.name == "button") result = getButton(myDev, evt, i)
  2185.                 else result = getOperand(i, state.isTrig)}
  2186.             }
  2187.         }
  2188.         if(result) return result
  2189.     }
  2190.     return result
  2191. }
  2192.  
  2193. def allHandler(evt) {
  2194.     if(!allOk) return
  2195.     log.info "$app.label: $evt.displayName $evt.name $evt.value"
  2196.     state.lastEvtName = evt.displayName
  2197.     if(evt.name == "level") state.lastEvtLevel = evt.value.toInteger()
  2198.     def hasTrig = state.howManyT > 1
  2199.     def hasCond = state.howMany > 1
  2200.     def doit = true
  2201.     if(state.isTrig) {
  2202.         if(evt.name in ["temperature", "humidity", "power", "energy", "battery", "illuminance", "mode", "button", "routineExecuted", "level", "presence"]) doit = testEvt(evt)
  2203.         if (doit) doTrigger() }
  2204.     else if(state.isRule) runRule(false)
  2205.     else {
  2206.         if(hasTrig) if(evt.name in ["temperature", "humidity", "power", "energy", "battery", "illuminance", "mode", "button", "routineExecuted", "level", "presence"]) doit = testEvt(evt)
  2207.         if(hasCond) {if(doit) runRule(hasTrig)}
  2208.         else if(doit) doTrigger()
  2209.     }
  2210. }
  2211.  
  2212. def startHandler() {
  2213.     runRule(false)
  2214. }
  2215.  
  2216. def stopHandler() {
  2217.     runRule(false)
  2218. }
  2219.  
  2220. def dayHandler() {
  2221.     runRule(false)
  2222. }
  2223.  
  2224. def timeHandler() {
  2225.     if(state.howMany > 1 && !state.isTrig) runRule(true)
  2226.     else if(state.isTrig || state.howManyT > 1) doTrigger()
  2227. }
  2228.  
  2229. def cronHandler() {
  2230.     if(state.howMany > 1 && !state.isTrig) runRule(true)
  2231.     else if(state.isTrig || state.howManyT > 1) doTrigger()
  2232. }
  2233.  
  2234. def cron2Handler() {
  2235.     if(state.howMany > 1 && !state.isTrig) runRule(true)
  2236.     else if(state.isTrig || state.howManyT > 1) doTrigger()
  2237. }
  2238.  
  2239. def physicalHandler(evt) {
  2240.     if(evt.isPhysical()) allHandler(evt)
  2241. }
  2242.  
  2243. // Delay Handlers follow
  2244.  
  2245. def delayOffTrue() {
  2246.     if(allOk) {if(delayOnOffTrue) delayedOffTrue.on() else delayedOffTrue.off()}
  2247. }
  2248.  
  2249. def pendingOffTrue() {
  2250.     if(allOk && state.pendingOffTrue) {if(pendOnOffTrue) pendedOffTrue.on() else pendedOffTrue.off()}
  2251. }
  2252.  
  2253. def delayEvalTrue() {
  2254.     if(allOk) parent.runRule(ruleEvalDelayTrue, app.label)
  2255. }
  2256.  
  2257. def delayOffFalse() {
  2258.     if(allOk) {if(delayOnOffFalse) delayedOffFalse.on() else delayedOffFalse.off()}
  2259. }
  2260.  
  2261. def pendingOffFalse() {
  2262.     if(allOk && state.pendingOffFalse) {if(pendOnOffFalse) pendedOffFalse.on() else pendedOffFalse.off()}
  2263. }
  2264.  
  2265. def delayEvalFalse() {
  2266.     if(allOk) parent.runRule(ruleEvalDelayFalse, app.label)
  2267. }
  2268.  
  2269. def delayRuleTrue() {
  2270.     if(allOk && state.delayRuleTrue) takeAction(true)
  2271. }
  2272.  
  2273. def delayRuleTrueForce() {
  2274.     if(allOk) takeAction(true)
  2275. }
  2276.  
  2277. def delayRuleFalse() {
  2278.     if(allOk && state.delayRuleFalse) takeAction(false)
  2279. }
  2280.  
  2281. def delayRuleFalseForce() {
  2282.     if(allOk) takeAction(false)
  2283. }
  2284.  
  2285. def delayPrivyTrue() {
  2286.     if(otherTrue && otherPrivateTrue) parent.setRuleBoolean(otherPrivateTrue, privateTrue, app.label)
  2287.     if(privateTrue != null) {
  2288.         state.private = privateTrue // == "true"
  2289.         if(state.isRule || (state.howMany > 1 && state.howManyT <= 1)) runRule(false)
  2290.         else doTrigger()
  2291.     }
  2292. }
  2293.  
  2294. def delayPrivyTrueX() {
  2295.     if(state.delayRuleTrue) delayPrivyTrue()
  2296. }
  2297.  
  2298. def delayPrivyFalse() {
  2299.     if(otherFalse && otherPrivateFalse) parent.setRuleBoolean(otherPrivateFalse, privateFalse, app.label)
  2300.     if(privateFalse != null) {
  2301.         state.private = privateFalse
  2302.         if(state.isRule || (state.howMany > 1 && state.howManyT <= 1)) runRule(false)
  2303.     }
  2304. }
  2305.  
  2306. def delayPrivyFalseX() {
  2307.     if(state.delayRuleFalse) delayPrivyFalse()
  2308. }
  2309.  
  2310. def delayRestoreTrue() {
  2311.     if(state.delayRuleTrue) restore()
  2312. }
  2313.  
  2314. def delayRestoreFalse() {
  2315.     if(state.delayRuleFalse) restore()
  2316. }
  2317.  
  2318. def disabledHandler(evt) {
  2319.     def disOnOff = disabledOff ? "off" : "on"
  2320.     state.disabled = evt.value == disOnOff
  2321. }
  2322.  
  2323. // Rule evaluation and action handlers follow
  2324.  
  2325. def ruleHandler(rule, truth) {
  2326.     if(!allOk) return
  2327.     log.info "$app.label: $rule is $truth"
  2328.     if(state.isRule || state.howMany > 1) runRule(false) else doTrigger()
  2329. }
  2330.  
  2331. def ruleEvaluator(rule) {
  2332.     if(!allOk) return
  2333.     log.info "$app.label: $rule evaluate"
  2334.     runRule(true)
  2335. }
  2336.  
  2337. def ruleActions(rule) {
  2338.     if(!allOk) return
  2339.     log.info "$app.label: $rule evaluate"
  2340.     if     (delayMinTrue > 0)   doDelay(delayMinTrue * 60, randomTrue, true, true)
  2341.     else if(delaySecTrue > 0)   doDelay(delaySecTrue, false, true, true)     
  2342.     else takeAction(true)
  2343. }
  2344.  
  2345. def revealSuccess() {
  2346.     def result = state.success
  2347. }
  2348.  
  2349. def setBoolean(truth, appLabel) {
  2350.     log.info "$app.label: Set Boolean from $appLabel: $truth"
  2351.     state.private = truth // == "true"
  2352.     if(state.isRule || (state.howMany > 1 && state.howManyT <= 1)) runRule(false)
  2353.     else for(int i = 1; i < state.howManyT; i++) {
  2354.         def myCap = settings.find {it.key == "tCapab$i"}
  2355.         if(myCap.value == "Private Boolean") if(getOperand(i, false)) {
  2356.             if(state.isRule || state.howMany > 1) runRule(true) else doTrigger()
  2357.             return
  2358.         }
  2359.     }
  2360. }
  2361.  
  2362. //  private execution filter methods follow
  2363.  
  2364. private atTimeLabel() {
  2365.     def result = ''
  2366.     if     (timeX == "Sunrise") result = "Sunrise" + offset(atSunriseOffset)
  2367.     else if(timeX == "Sunset")  result = "Sunset" + offset(atSunsetOffset)
  2368.     else if(atTime) result = hhmm(atTime)
  2369. }
  2370.  
  2371. private stripBrackSpace(str) {
  2372.     def result = ""
  2373.     def myStr = ""
  2374.     def i = str.indexOf('[')
  2375.     def j = str.indexOf(']')
  2376.     if(i >= 0 && j > i) myStr = str.substring(0, i) + str.substring(i + 1, j) + str.substring(j + 1)
  2377.     for(i = 0; i < myStr.size(); i++) {
  2378.         if(myStr[i] != " ") result = result + myStr[i]
  2379.     }
  2380.     return result
  2381. }
  2382.  
  2383. private hhmm(time, fmt = "h:mm a") {
  2384.     def t = timeToday(time, location.timeZone)
  2385.     def f = new java.text.SimpleDateFormat(fmt)
  2386.     f.setTimeZone(location.timeZone ?: timeZone(time))
  2387.     f.format(t)
  2388. }
  2389.  
  2390. private offset(value) {
  2391.     def result = value ? ((value > 0 ? "+" : "") + value + " min") : ""
  2392. }
  2393.  
  2394. private timeIntervalLabel() {
  2395.     def result = ""
  2396.     if (startingX == "Sunrise" && endingX == "Sunrise") result = "Sunrise" + offset(startSunriseOffset) + " and Sunrise" + offset(endSunriseOffset)
  2397.     else if (startingX == "Sunrise" && endingX == "Sunset") result = "Sunrise" + offset(startSunriseOffset) + " and Sunset" + offset(endSunsetOffset)
  2398.     else if (startingX == "Sunset" && endingX == "Sunrise") result = "Sunset" + offset(startSunsetOffset) + " and Sunrise" + offset(endSunriseOffset)
  2399.     else if (startingX == "Sunset" && endingX == "Sunset") result = "Sunset" + offset(startSunsetOffset) + " and Sunset" + offset(endSunsetOffset)
  2400.     else if (startingX == "Sunrise" && ending) result = "Sunrise" + offset(startSunriseOffset) + " and " + hhmm(ending, "h:mm a z")
  2401.     else if (startingX == "Sunset" && ending) result = "Sunset" + offset(startSunsetOffset) + " and " + hhmm(ending, "h:mm a z")
  2402.     else if (starting && endingX == "Sunrise") result = hhmm(starting) + " and Sunrise" + offset(endSunriseOffset)
  2403.     else if (starting && endingX == "Sunset") result = hhmm(starting) + " and Sunset" + offset(endSunsetOffset)
  2404.     else if (starting && ending) result = hhmm(starting) + " and " + hhmm(ending, "h:mm a z")
  2405. }
  2406.  
  2407. private timeIntervalLabelX() {
  2408.     def result = ""
  2409.     if (startingXX == "Sunrise" && endingXX == "Sunrise") result = "Sunrise" + offset(startSunriseOffsetX) + " and Sunrise" + offset(endSunriseOffsetX)
  2410.     else if (startingXX == "Sunrise" && endingXX == "Sunset") result = "Sunrise" + offset(startSunriseOffsetX) + " and Sunset" + offset(endSunsetOffsetX)
  2411.     else if (startingXX == "Sunset" && endingXX == "Sunrise") result = "Sunset" + offset(startSunsetOffsetX) + " and Sunrise" + offset(endSunriseOffsetX)
  2412.     else if (startingXX == "Sunset" && endingXX == "Sunset") result = "Sunset" + offset(startSunsetOffsetX) + " and Sunset" + offset(endSunsetOffsetX)
  2413.     else if (startingXX == "Sunrise" && endingA) result = "Sunrise" + offset(startSunriseOffsetX) + " and " + hhmm(endingA, "h:mm a z")
  2414.     else if (startingXX == "Sunset" && endingA) result = "Sunset" + offset(startSunsetOffsetX) + " and " + hhmm(endingA, "h:mm a z")
  2415.     else if (startingA && endingXX == "Sunrise") result = hhmm(startingA) + " and Sunrise" + offset(endSunriseOffsetX)
  2416.     else if (startingA && endingXX == "Sunset") result = hhmm(startingA) + " and Sunset" + offset(endSunsetOffsetX)
  2417.     else if (startingA && endingA) result = hhmm(startingA) + " and " + hhmm(endingA, "h:mm a z")
  2418. }
  2419.  
  2420. private getAllOk() {
  2421.     def okay = !(usePrivateDisable && state.private == "false")
  2422.     if(state.isRule) modeZOk && !state.disabled  && okay //&& daysOk && timeOk
  2423.     else if(state.isTrig) modeYOk && daysOk && timeOk && !state.disabled && okay
  2424.     else modeYOk && daysYOk && timeOk && !state.disabled && okay
  2425. }
  2426.  
  2427. private hideOptionsSection() {
  2428.     if(state.isRule || state.howMany > 1) (modesZ || daysY || modesY || disabled || starting || ending || startingX || endingX || usePrivateDisable) ? false : true
  2429.     else (starting || ending || daysY || modes || modesY || startingX || endingX || disabled || usePrivateDisable) ? false : true
  2430. }
  2431.  
  2432. private getModeZOk() {
  2433.     def result = !modesZ || modesZ.contains(location.mode)
  2434. //  log.trace "modeZOk = $result"
  2435.     return result
  2436. }
  2437.  
  2438. private getModeYOk() {
  2439.     def result = !modesY || modesY.contains(location.mode)
  2440. //  log.trace "modeYOk = $result"
  2441.     return result
  2442. }
  2443.  
  2444. private getModeOk() {
  2445.     def result = !modes || modes.contains(location.mode)
  2446. //  log.trace "modeOk = $result"
  2447.     return result
  2448. }
  2449.  
  2450. private getModeXOk() {
  2451.     def result = !modesX || modesX.contains(location.mode)
  2452. //  log.trace "modeXOk = $result"
  2453.     return result
  2454. }
  2455.  
  2456. private getDaysOk() {
  2457.     def result = true
  2458.     if (days) {
  2459.         def df = new java.text.SimpleDateFormat("EEEE")
  2460.         if (location.timeZone) df.setTimeZone(location.timeZone)
  2461.         else df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
  2462.         def day = df.format(new Date())
  2463.         result = days.contains(day)
  2464.     }
  2465. //  log.trace "daysOk = $result"
  2466.     return result
  2467. }
  2468.  
  2469. private getDaysYOk() {
  2470.     def result = true
  2471.     if (daysY) {
  2472.         def df = new java.text.SimpleDateFormat("EEEE")
  2473.         if (location.timeZone) df.setTimeZone(location.timeZone)
  2474.         else df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
  2475.         def day = df.format(new Date())
  2476.         result = daysY.contains(day)
  2477.     }
  2478. //  log.trace "daysYOk = $result"
  2479.     return result
  2480. }
  2481.  
  2482. private getTimeOk() {
  2483.     def result = true
  2484.     if((starting && ending) ||
  2485.     (starting && endingX in ["Sunrise", "Sunset"]) ||
  2486.     (startingX in ["Sunrise", "Sunset"] && ending) ||
  2487.     (startingX in ["Sunrise", "Sunset"] && endingX in ["Sunrise", "Sunset"])) {
  2488.         def currTime = now()
  2489.         def start = null
  2490.         def stop = null
  2491.         def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: startSunriseOffset, sunsetOffset: startSunsetOffset)
  2492.         if(startingX == "Sunrise") start = s.sunrise.time
  2493.         else if(startingX == "Sunset") start = s.sunset.time
  2494.         else if(starting) start = timeToday(starting, location.timeZone).time    //  Crash here means time zone not set!!
  2495.         s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: endSunriseOffset, sunsetOffset: endSunsetOffset)
  2496.         if(endingX == "Sunrise") stop = s.sunrise.time
  2497.         else if(endingX == "Sunset") stop = s.sunset.time
  2498.         else if(ending) stop = timeToday(ending,location.timeZone).time
  2499.         result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
  2500.     }
  2501. //  log.trace "getTimeOk = $result"
  2502.     return result
  2503. }
  2504.  
  2505. private getTimeOkX() {
  2506.     def result = true
  2507.     if((startingA && endingA) ||
  2508.     (startingA && endingXX in ["Sunrise", "Sunset"]) ||
  2509.     (startingXX in ["Sunrise", "Sunset"] && endingA) ||
  2510.     (startingXX in ["Sunrise", "Sunset"] && endingXX in ["Sunrise", "Sunset"])) {
  2511.         def currTime = now()
  2512.         def start = null
  2513.         def stop = null
  2514.         def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: startSunriseOffsetX, sunsetOffset: startSunsetOffsetX)
  2515.         if(startingXX == "Sunrise") start = s.sunrise.time
  2516.         else if(startingXX == "Sunset") start = s.sunset.time
  2517.         else if(startingA) start = timeToday(startingA, location.timeZone).time
  2518.         s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: endSunriseOffsetX, sunsetOffset: endSunsetOffsetX)
  2519.         if(endingXX == "Sunrise") stop = s.sunrise.time
  2520.         else if(endingXX == "Sunset") stop = s.sunset.time
  2521.         else if(endingA) stop = timeToday(endingA,location.timeZone).time
  2522.         result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
  2523.     }
  2524. //  log.trace "getTimeOkX = $result"
  2525.     return result
  2526. }
  2527.  
  2528. private stripBrackets(str) {
  2529.     def result = str
  2530.     def i = str.indexOf('[')
  2531.     def j = str.indexOf(']')
  2532.     if(i >= 0 && j > i) result = str.substring(0, i) + str.substring(i + 1, j) + str.substring(j + 1)
  2533.     return result
  2534. }
  2535.  
  2536. private setColor(trufal) {
  2537.     def hueColor = 0
  2538.     def saturation = 100
  2539.     switch(trufal ? colorTrue : colorFalse) {
  2540.         case "White":
  2541.             hueColor = 52
  2542.             saturation = 19
  2543.             break;
  2544.         case "Daylight":
  2545.             hueColor = 53
  2546.             saturation = 91
  2547.             break;
  2548.         case "Soft White":
  2549.             hueColor = 23
  2550.             saturation = 56
  2551.             break;
  2552.         case "Warm White":
  2553.             hueColor = 20
  2554.             saturation = 80 //83
  2555.             break;
  2556.         case "Blue":
  2557.             hueColor = 70
  2558.             break;
  2559.         case "Green":
  2560.             hueColor = 35
  2561.             break;
  2562.         case "Yellow":
  2563.             hueColor = 25
  2564.             break;
  2565.         case "Orange":
  2566.             hueColor = 10
  2567.             break;
  2568.         case "Purple":
  2569.             hueColor = 75
  2570.             break;
  2571.         case "Pink":
  2572.             hueColor = 83
  2573.             break;
  2574.         case "Red":
  2575.             hueColor = 100
  2576.             break;
  2577.         case "Custom color":
  2578.             hueColor = trufal ? colorHexTrue : colorHexFalse
  2579.             saturation = trufal ? colorSatTrue : colorSatFalse
  2580.             break;
  2581.         case "Random color":
  2582.             hueColor = Math.random()*100 as Integer
  2583.             saturation = Math.random()*100 as Integer
  2584.             if(saturation < 50) saturation += 50
  2585.             log.info "Random color: hue: $hueColor, saturation: $saturation"
  2586.             break;
  2587.     }
  2588.     def lightLevel = trufal ? colorLevelTrue : colorLevelFalse
  2589.     def newValue = [hue: hueColor, saturation: saturation, level: lightLevel as Integer ?: 100]
  2590.     if(trufal) bulbsTrue.setColor(newValue) else bulbsFalse.setColor(newValue)
  2591.     if(lightLevel == 0) {if(trufal) bulbsTrue.off() else bulbsFalse.off()}
  2592. }
  2593.  
  2594. //custom command execute method
  2595. def execCommand(devices,cmdID){
  2596.     def result = ""
  2597.     def pList = []
  2598.     def cmdMap = parent.getCommandMap(cmdID)
  2599.     if (cmdMap) {
  2600.         def params = cmdMap.params.sort()
  2601.         params.each{ p ->
  2602.             if (p.value.type == "string"){
  2603.                 pList << "${p.value.value}"
  2604.             } else if (p.value.type == "decimal"){
  2605.                 pList << p.value.value.toBigDecimal()
  2606.             } else {
  2607.                 pList << p.value.value.toInteger()
  2608.             }
  2609.         }
  2610.         def p = pList as Object[]
  2611.         devices.each { device ->
  2612.             try {
  2613.                 device."${cmdMap.cmd}"(p)
  2614.             }
  2615.             catch (IllegalArgumentException e){
  2616.                 def em = e as String
  2617.                 def ems = em.split(":")
  2618.                 ems = ems[2].replace(" [","").replace("]","")
  2619.                 ems = ems.replaceAll(", ","\n")
  2620.                 log.error "${device.displayName}, command failed, valid commands:\n${ems}"
  2621.             }
  2622.             catch (e) {
  2623.                 log.error "${device.displayName}, command failed:\n${e}"
  2624.             }
  2625.         }
  2626.     }
  2627. }
  2628.  
  2629. def execCommands(truth){
  2630.     def devicePrefix
  2631.     def commandPrefix
  2632.     def theseDevices
  2633.     def thisCommand
  2634.     def howMany
  2635.     if (truth) {
  2636.         devicePrefix = "customDeviceTrue"
  2637.         commandPrefix = "ccTrue"
  2638.         howMany = state.howManyCCtrue
  2639.     } else {
  2640.         devicePrefix = "customDeviceFalse"
  2641.         commandPrefix = "ccFalse"
  2642.         howMany = state.howManyCCfalse
  2643.     }  
  2644.     for (int i = 1; i < howMany; i++) {
  2645.         if (i == 1) theseDevices = devicePrefix
  2646.         else theseDevices = devicePrefix + "${i}"
  2647.         if (i == 1) thisCommand = commandPrefix
  2648.         else thisCommand = commandPrefix + "${i}"
  2649.         def devices = settings."${theseDevices}"
  2650.         def cmdID = settings."${thisCommand}"
  2651.         //log.debug "truth:${truth} devices:${devices} cmdID:${cmdID}"
  2652.          execCommand(devices,cmdID)
  2653.     }
  2654. }
  2655.  
  2656. def selectCustomActions(){
  2657.     def cstCmds = []
  2658.     def cstCapabs = []
  2659.     state.cstCmds.each {
  2660.         it.each {myT ->
  2661.             cstCmds << [(myT.key):myT.value[0]]
  2662.             cstCapabs << [(myT.key):myT.value[1]]
  2663.         }
  2664.     }
  2665.     def truth = state.ccTruth
  2666.     def devicePrefix
  2667.     def commandPrefix
  2668.     def theseDevices
  2669.     def thisCommand
  2670.     def allDevices
  2671.     def allCommands
  2672.     def howMany
  2673.     def capab
  2674.     if (truth) {
  2675.         devicePrefix = "customDeviceTrue"
  2676.         commandPrefix = "ccTrue"
  2677.         allDevices = settings.findAll{it.key.startsWith(devicePrefix)}
  2678.         allCommands = settings.findAll{it.key.startsWith(commandPrefix)}
  2679.         if (allDevices.size() <= allCommands.size()) state.howManyCCtrue = allDevices.size() + 1
  2680.         else state.howManyCCtrue = allDevices.size()
  2681.         howMany = state.howManyCCtrue
  2682.         state.cmdActTrue = ""
  2683.     } else {
  2684.         devicePrefix = "customDeviceFalse"
  2685.         commandPrefix = "ccFalse"
  2686.         allDevices = settings.findAll{it.key.startsWith(devicePrefix)}
  2687.         allCommands = settings.findAll{it.key.startsWith(commandPrefix)}
  2688.         if (allDevices.size() <= allCommands.size()) state.howManyCCfalse = allDevices.size() + 1
  2689.         else state.howManyCCfalse = allDevices.size()
  2690.         howMany = state.howManyCCfalse
  2691.         state.cmdActFalse = ""
  2692.     }
  2693.     dynamicPage(name: "selectCustomActions", title: "Select Custom Command Actions", uninstall: false) {
  2694.         for (int i = 1; i <= howMany; i++) {
  2695.             if (i == 1) theseDevices = devicePrefix
  2696.             else theseDevices = devicePrefix + "${i}"
  2697.             def crntDevices = settings."${theseDevices}"
  2698.             section("Custom command #${i}"){
  2699.                 if(i == 1) thisCommand = commandPrefix
  2700.                 else thisCommand = commandPrefix + "${i}"
  2701.                 def crntCommand = settings."${thisCommand}"
  2702.                 input( name: thisCommand, type: "enum", title: "Run this command", multiple: false, required: false, options: cstCmds, submitOnChange: true)
  2703.                 if (crntCommand) {
  2704.                     if (cstCmds.find{it[crntCommand]}) {
  2705.                         capab = cstCapabs.find{it[crntCommand]}[crntCommand]
  2706.                     }
  2707.                     input (name: theseDevices, type: "capability.$capab", title: "On these devices", multiple: true, required: false, submitOnChange: true)
  2708.                     if (crntDevices){
  2709.                         if (cstCmds.find{it[crntCommand]}) {
  2710.                             def c = cstCmds.find{it[crntCommand]}[crntCommand]
  2711.                             cmdActions("Command: ${c} on ${crntDevices}",truth)    
  2712.                         }
  2713.                     }
  2714.                 }
  2715.             }
  2716. //          if (truth) {
  2717. //              if (state.cmdActTrue) state.cmdActTrue = state.cmdActTrue[0..-2]
  2718. //          } else {
  2719. //              if (state.cmdActFalse) state.cmdActFalse = state.cmdActFalse[0..-2]
  2720. //            }
  2721.         }  
  2722.     }
  2723. }
  2724.  
  2725. def cmdActions(str, truth) {
  2726.     if (truth) state.cmdActTrue = state.cmdActTrue + stripBrackets("$str") + "\n"
  2727.     else state.cmdActFalse = state.cmdActFalse + stripBrackets("$str") + "\n"
  2728. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement