Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /**
- * Rule Machine
- *
- * Copyright 2015, 2016 Bruce Ravenel
- *
- * Version 1.9.0a 25 Mar 2016
- *
- * Version History
- *
- * 1.9.0 24 Mar 2016 Updates for Rule, small bug fixes
- * 1.8.2 9 Mar 2016 Changed startup page for installation
- * 1.8.1 3 Mar 2016 Changed method of getting Rule version
- * 1.8.0 2 Mar 2016 Clean up, added Door control
- * 1.7.6 24 Feb 2016 Added User Guide link, fixed Rule truth mechanism
- * 1.7.5 21 Feb 2016 Improved custom command selection
- * 1.7.4 20 Feb 2016 Added saved command display, UI improvements
- * 1.7.3 14 Feb 2016 Improved Rule Machine initialization, fixed Delete custom commands bug
- * 1.7.2 8 Feb 2016 Added set Boolean for Rules
- * 1.7.1 5 Feb 2016 Added update Rule
- * 1.7.0 31 Jan 2016 Added run Rule actions
- * 1.6.6 10 Jan 2016 Improved method of getting custom device commands
- * 1.6.5 1 Jan 2016 Added version numbers to main page
- * 1.6.4 30 Dec 2015 Multi-commands
- * 1.6.3 26 Dec 2015 UI improvements and icon per Michael Struck
- * 1.6.2 25 Dec 2015 null parameter value patch in expert, maxwell
- * 1.6.1 24 Dec 2015 UI improvement
- * 1.6.0 23 Dec 2015 Added expert commands per Mike Maxwell
- *
- * This software if free for Private Use. You may use and modify the software without distributing it.
- *
- * This software and derivatives may not be used for commercial purposes.
- * You may not modify, distribute or sublicense this software.
- * You may not grant a sublicense to modify and distribute this software to third parties not included in the license.
- *
- * Software is provided without warranty and the software author/license owner cannot be held liable for damages.
- *
- */
- definition(
- name: "Rule Machine",
- singleInstance: true,
- namespace: "bravenel",
- author: "Bruce Ravenel and Mike Maxwell",
- description: "Rule Machine",
- category: "My Apps",
- iconUrl: "https://raw.githubusercontent.com/bravenel/Rule-Trigger/master/smartapps/bravenel/RuleMachine.png",
- iconX2Url: "https://raw.githubusercontent.com/bravenel/Rule-Trigger/master/smartapps/bravenel/RuleMachine%402x.png",
- iconX3Url: "https://raw.githubusercontent.com/bravenel/Rule-Trigger/master/smartapps/bravenel/RuleMachine%402x.png"
- )
- preferences {
- page(name: "mainPage")
- page(name: "firstPage")
- page(name: "removePage")
- //expert pages
- page(name: "customCommandsPAGE")
- page(name: "generalApprovalPAGE")
- page(name: "addCustomCommandPAGE")
- page(name: "customParamsPAGE")
- }
- def mainPage() {
- if(!state.setup) firstRun()
- else {
- if(state.ruleState) state.ruleState = null // obsolete
- def nApps = childApps.size()
- dynamicPage(name: "mainPage", title: "Installed Rules, Triggers and Actions " + (nApps > 0 ? "[$nApps]" : ""), install: true, uninstall: false) {
- section {
- app(name: "childRules", appName: "Rule", namespace: "bravenel", title: "Create New Rule...", multiple: true)
- }
- section ("Expert Features") {
- href("customCommandsPAGE", title: null, description: anyCustom() ? "Custom Commands..." : "Tap to create Custom Commands", state: anyCustom())
- }
- section ("Rule Machine User Guide") {
- href url:"https://community.smartthings.com/t/rule-machine-user-guide/40176", style:"embedded", required:false, description:"Tap to view User Guide", title: ""
- }
- section ("Remove Rule Machine"){
- href "removePage", description: "Tap to remove Rule Machine and Rules", title: ""
- }
- section ("Version 1.9.0a/" + (nApps > 0 ? "${childApps[0].appVersion()}" : "---")) { }
- }
- }
- }
- def removePage() {
- dynamicPage(name: "removePage", title: "Remove Rule Machine And All Rules", install: false, uninstall: true) {
- section ("WARNING!\n\nRemoving Rule Machine also removes all Rules\n") {
- }
- }
- }
- def installed() {
- }
- def updated() {
- }
- def firstRun() {
- state.setup = true
- state.ruleSubscribers = [:]
- dynamicPage(name: "firstPage", title: "Hit Done to install Rule Machine", install: true, uninstall: false) { }
- }
- def childVersion() {
- def result = "---"
- if(childApps.size() > 0) result = childApps[0].appVersion()
- return result
- }
- def ruleList(appLabel) {
- def result = []
- childApps.each { child ->
- if(child.name == "Rule" && child.label != appLabel) result << child.label
- }
- return result
- }
- def subscribeRule(appLabel, ruleName, ruleTruth, childMethod) {
- // log.debug "subscribe: $appLabel, $ruleName, $ruleTruth, $childMethod"
- ruleName.each {name ->
- state.ruleSubscribers[name].each {if(it == appLabel) return}
- if(state.ruleSubscribers[name] == null) state.ruleSubscribers[name] = ["$appLabel":ruleTruth]
- else state.ruleSubscribers[name] << ["$appLabel":ruleTruth]
- }
- }
- def setRuleTruth(appLabel, ruleTruth) {
- // log.debug "setRuleTruth: $appLabel, $ruleTruth, ${state.ruleState[appLabel]}"
- def thisList = state.ruleSubscribers[appLabel]
- thisList.each {
- if(it.value == null || "$it.value" == "$ruleTruth") {
- childApps.each { child ->
- if(child.label == it.key) child.ruleHandler(appLabel, ruleTruth)
- }
- }
- }
- }
- def setRuleBoolean(rule, ruleBoolean, appLabel) {
- // log.debug "setRuleBoolean: $appLabel, $ruleBoolean"
- childApps.each { child ->
- rule.each {
- if(child.label == it) child.setBoolean(ruleBoolean, appLabel)
- }
- }
- }
- def currentRule(appLabel) {
- def result
- childApps.each { child ->
- if(child.label == appLabel) result = child.revealSuccess()
- }
- return result
- }
- def childUninstalled() {
- // log.debug "childUninstalled called"
- }
- def removeChild(appLabel) {
- // log.debug "removeChild: $appLabel"
- unSubscribeRule(appLabel)
- if(state.ruleSubscribers[appLabel] != null) state.ruleSubscribers.remove(appLabel)
- }
- def unSubscribeRule(appLabel) {
- // log.debug "unSubscribeRule: $appLabel"
- state.ruleSubscribers.each { rule ->
- def newList = [:]
- rule.value.each {list ->
- if(list.key != appLabel) newList << list
- }
- rule.value = newList
- }
- }
- def runRule(rule, appLabel) {
- // log.debug "runRule: $rule, $appLabel"
- childApps.each { child ->
- rule.each {
- if(child.label == it) child.ruleEvaluator(appLabel)
- }
- }
- }
- def runRuleAct(rule, appLabel) {
- // log.debug "runRuleAct: $rule, $appLabel"
- childApps.each { child ->
- rule.each {
- if(child.label == it) child.ruleActions(appLabel)
- }
- }
- }
- def runUpdate(rule) {
- // log.debug "runUpdate: $rule"
- childApps.each { child ->
- rule.each {
- if(child.label == it) child.updated()
- }
- }
- }
- /*****custom command specific pages *****/
- def generalApprovalPAGE(params){
- def title = params.title
- def method = params.method
- def result
- dynamicPage(name: "generalApprovalPAGE", title: title ){
- section() {
- if (method) {
- result = app."${method}"()
- paragraph "${result}"
- }
- }
- }
- }
- def customCommandsPAGE() {
- if (!state.lastCmdIDX) state.lastCmdIDX = 0
- def savedCommands = getMyCommands()
- dynamicPage(name: "customCommandsPAGE", title: "Custom Commands", uninstall: false, install: false) {
- def hasCommands = savedCommands.size() != 0
- def result = ""
- if (hasCommands){
- def cmdMaps = state.customCommands ?: []
- cmdMaps.each{ cmd ->
- def cont = cmd.value.text.size() > 30 ? "..." : ""
- result = result + "\n\t" + cmd.value.text.take(30) + cont
- }
- } else state.lastCmdIDX = 0
- section(hasCommands ? "Saved commands:\n" + result : "") {
- if (hasCommands) {
- input(
- name : "deleteCmds"
- ,title : "Delete saved commands"
- ,multiple : true
- ,required : false
- ,description : ""
- ,type : "enum"
- ,options : savedCommands
- ,submitOnChange : true
- )
- if (isValidCommand(deleteCmds)){
- href( "generalApprovalPAGE"
- ,title : "Delete commands now"
- ,description : ""
- ,state : null
- ,params : [method:"deleteCommands",title:"Delete commands"]
- ,submitOnChange : true
- )
- }
- paragraph("")
- }
- getCapab()
- if(myCapab) getDevs()
- if(devices && hasCommands) {
- input(
- name : "testCmd"
- ,title : "Test saved command on\n$devices"
- ,multiple : false
- ,required : false
- ,description : ""
- ,type : "enum"
- ,options : savedCommands
- ,submitOnChange : true
- )
- def res = execCommand(settings.testCmd)
- if (res) paragraph("${result}")
- }
- }
- section(){
- if (devices){
- href( "addCustomCommandPAGE"
- ,title : "New custom command..."
- ,description: ""
- ,state : null
- )
- }
- }
- }
- }
- def getCapab() {
- def myOptions = ["Acceleration", "Actuator", "Button", "Carbon monoxide detector", "Contact", "Dimmer", "Door", "Energy meter", "Garage door", "Humidity", "Illuminance",
- "Lock", "Motion", "Power meter", "Presence", "Smoke detector", "Switch", "Temperature", "Thermostat", "Water sensor", "Music player"]
- def result = input "myCapab", "enum", title: "Select capability for test device", required: false, options: myOptions.sort(), submitOnChange: true
- }
- def getDevs() {
- def multi = false
- def thisName = ""
- def thisCapab = ""
- switch(myCapab) {
- case "Switch":
- thisName = "switch"
- thisCapab = "switch"
- break
- case "Actuator":
- thisName = "actuator"
- thisCapab = "actuator"
- break
- case "Motion":
- thisName = "motion sensor"
- thisCapab = "motionSensor"
- break
- case "Button":
- thisName = "button device"
- thisCapab = "button"
- break
- case "Acceleration":
- thisName = "acceleration sensor"
- thisCapab = "accelerationSensor"
- break
- case "Contact":
- thisName = "contact sensor"
- thisCapab = "contactSensor"
- break
- case "Presence":
- thisName = "presence sensor"
- thisCapab = "presenceSensor"
- break
- case "Garage door":
- thisName = "garage door"
- thisCapab = "garageDoorControl"
- break
- case "Door":
- thisName = "door"
- thisCapab = "doorControl"
- break
- case "Lock":
- thisName = "lock"
- thisCapab = "lock"
- break
- case "Dimmer":
- thisName = "dimmer" + (multi ? "s" : "")
- thisCapab = "switchLevel"
- break
- case "Temperature":
- thisName = "temperature sensor" + (multi ? "s" : "")
- thisCapab = "temperatureMeasurement"
- break
- case "Thermostat":
- thisName = "thermostat" + (multi ? "s" : "")
- thisCapab = "thermostat"
- break
- case "Humidity":
- thisName = "humidity sensor" + (multi ? "s" : "")
- thisCapab = "relativeHumidityMeasurement"
- break
- case "Illuminance":
- thisName = "illuminance sensor" + (multi ? "s" : "")
- thisCapab = "illuminanceMeasurement"
- break
- case "Energy meter":
- thisName = "energy meter" + (multi ? "s" : "")
- thisCapab = "energyMeter"
- break
- case "Power meter":
- thisName = "power meter" + (multi ? "s" : "")
- thisCapab = "powerMeter"
- break
- case "Carbon monoxide detector":
- thisName = "CO detector" + (multi ? "s" : "")
- thisCapab = "carbonMonoxideDetector"
- break
- case "Smoke detector":
- thisName = "smoke detector" + (multi ? "s" : "")
- thisCapab = "smokeDetector"
- break
- case "Water sensor":
- thisName = "water sensor"
- thisCapab = "waterSensor"
- break
- case "Music player":
- thisName = "music player"
- thisCapab = "musicPlayer"
- break
- }
- def result = input "devices", "capability.$thisCapab", title: "Select $thisName to test for commands", required: false, multiple: multi, submitOnChange: true
- }
- def addCustomCommandPAGE(){
- def cmdLabel = getCmdLabel()
- def complete = ""
- def test = false
- def rest = getDeviceCommands()
- rest = "$rest"[1..-2]
- def pageTitle = "Create new custom command from\n\n${devices}\n\nAvailable commands:\n\n" + rest
- if (cmdLabel){
- complete = "complete"
- test = true
- }
- dynamicPage(name: "addCustomCommandPAGE", title: pageTitle, uninstall: false, install: false) {
- section(){
- input(
- name : "cCmd"
- ,title : "Select custom command"
- ,multiple : false
- ,required : false
- ,type : "enum"
- ,options : getDeviceCommands()
- ,submitOnChange : true
- )
- href( "customParamsPAGE"
- ,title: "Parameters"
- ,description: parameterLabel()
- ,state: null
- )
- }
- if (cCmd){
- def result = execTestCommand()
- section("Configured command: ${cmdLabel}\n${result}"){
- if (result == "succeeded"){
- if (!commandExists(cmdLabel)){
- href( "generalApprovalPAGE"
- ,title : "Save command now"
- ,description: ""
- ,state : null
- ,params : [method:"addCommand",title:"Add Command"]
- )
- }
- }
- }
- }
- }
- }
- def customParamsPAGE(p){
- def ct = settings.findAll{it.key.startsWith("cpType_")}
- state.howManyP = ct.size() + 1
- def howMany = state.howManyP
- dynamicPage(name: "customParamsPAGE", title: "Select parameters", uninstall: false) {
- if(howMany) {
- for (int i = 1; i <= howMany; i++) {
- def thisParam = "cpType_" + i
- def myParam = ct.find {it.key == thisParam}
- section("Parameter #${i}") {
- getParamType(thisParam, i != howMany)
- if(myParam) {
- def pType = myParam.value
- getPvalue(pType, i)
- }
- }
- }
- }
- }
- }
- /***** child specific methods *****/
- def getCommandMap(cmdID){
- return state.customCommands["${cmdID}"]
- }
- /***** local custom command specific methods *****/
- def anyCustom(){
- def result = null
- if (getCommands()) result = "complete"
- return result
- }
- def getParamType(myParam,isLast){
- def myOptions = ["string", "number", "decimal"]
- def result = input (
- name : myParam
- ,type : "enum"
- ,title : "parameter type"
- ,required : isLast
- ,options : myOptions
- ,submitOnChange : true
- )
- return result
- }
- def getPvalue(myPtype, n){
- def myVal = "cpVal_" + n
- def result = null
- if (myPtype == "string"){
- result = input(
- name : myVal
- ,title : "string value"
- ,type : "text"
- ,required : false
- )
- } else if (myPtype == "number"){
- result = input(
- name : myVal
- ,title : "integer value"
- ,type : "number"
- ,required : false
- )
- } else if (myPtype == "decimal"){
- result = input(
- name : myVal
- ,title : "decimal value"
- ,type : "decimal"
- ,required : false
- )
- }
- return result
- }
- def getCmdLabel(){
- def cmd
- if (settings.cCmd) cmd = settings.cCmd.value
- def cpTypes = settings.findAll{it.key.startsWith("cpType_")}.sort()
- def result = null
- if (cmd) {
- result = "${cmd}("
- if (cpTypes.size() == 0){
- result = result + ")"
- } else {
- def r = getParams(cpTypes)
- if (r == "") result = r
- else result = "${result}${r})"
- }
- }
- return result
- }
- def getParams(cpTypes){
- def result = ""
- def cpValue
- def badParam = false
- cpTypes.each{ cpType ->
- def i = cpType.key.replaceAll("cpType_","")
- def cpVal = settings.find{it.key == "cpVal_${i}"}
- if (cpVal){
- cpValue = cpVal.value
- if (cpType.value == "string"){
- result = result + "'${cpValue}',"
- } else {
- if (cpValue.isNumber()){
- result = result + "${cpValue},"
- } else {
- result = result + "[${cpValue}]: is not a number,"
- }
- }
- } else {
- badParam = true
- }
- }
- if (badParam) result = ""
- else result = result[0..-2]
- return result
- }
- def parameterLabel(){
- def howMany = (state.howManyP ?: 1) - 1
- def result = ""
- if (howMany) {
- for (int i = 1; i <= howMany; i++) {
- result = result + parameterLabelN(i) + "\n"
- }
- result = result[0..-2]
- }
- return result
- }
- def parameterLabelN(i){
- def result = ""
- def cpType = settings.find{it.key == "cpType_${i}"}
- def cpVal = settings.find{it.key == "cpVal_${i}"}
- def cpValue
- if (cpVal) cpValue = cpVal.value
- else cpValue = "missing value"
- if (cpType){
- result = "p${i} - type:${cpType.value}, value:${cpValue}"
- }
- return result
- }
- def getParamsAsList(cpTypes){
- def result = []
- cpTypes.each{ cpType ->
- def i = cpType.key.replaceAll("cpType_","")
- def cpVal = settings.find{it.key == "cpVal_${i}"}
- if (cpVal){
- if (cpType.value == "string"){
- result << "${cpVal.value}"
- } else if (cpType.value == "decimal"){
- result << cpVal.value.toBigDecimal()
- } else { // if (cpType.value == "number" && cpVal.value.isInteger()){
- result << cpVal.value.toInteger()
- }
- } else {
- result << "missing value"
- }
- }
- return result
- }
- def getCommands(){
- def result = []
- def cmdMaps = state.customCommands ?: []
- cmdMaps.each{ cmd ->
- def option = [(cmd.key):([cmd.value.text, cmd.value.capab == null ? "actuator" : cmd.value.capab])]
- result.push(option)
- }
- return result
- }
- def getMyCommands(){
- def result = []
- def cmdMaps = state.customCommands ?: []
- cmdMaps.each{ cmd ->
- def option = [(cmd.key):(cmd.value.text)]
- result.push(option)
- }
- return result
- }
- def isValidCommand(cmdIDS){
- def result = false
- cmdIDS.each{ cmdID ->
- def cmd = state.customCommands["${cmdID}"]
- if (cmd) result = true
- }
- return result
- }
- def deleteCommands(){
- def result
- def cmdMaps = state.customCommands
- if (deleteCmds.size() == 1) result = "Command removed"
- else result = "Commands removed"
- deleteCmds.each{ it ->
- cmdMaps.remove(it)
- }
- return result
- }
- def commandExists(cmd){
- def result = false
- if (state.customCommands){
- result = state.customCommands.find{ it.value.text == "${cmd}" }
- }
- return result
- }
- def addCommand(){
- def capabs = [ "Acceleration" : "accelerationSensor",
- "Button" : "button",
- "Carbon monoxide detector" : "carbonMonoxideDetector",
- "Contact" : "contactSensor",
- "Dimmer" : "switchLevel",
- "Door" : "doorControl",
- "Energy meter" : "energyMeter",
- "Garage door" : "garageDoorControl",
- "Humidity" : "humiditySensor",
- "Illuminance" : "illuminanceSensor",
- "Lock" : "lock",
- "Motion" : "motionSensor",
- "Power meter" : "powerMeter",
- "Presence" : "presenceSensor",
- "Smoke detector" : "smokeDetector",
- "Switch" : "switch",
- "Temperature" : "temperatureMeasurement",
- "Thermostat" : "thermostat",
- "Water sensor" : "waterSensor",
- "Music player" : "musicPlayer",
- "Actuator" : "actuator"]
- def result
- def newCmd = getCmdLabel()
- def found = commandExists(newCmd)
- def cmdMaps = state.customCommands
- //only update if not found...
- if (!found) {
- state.lastCmdIDX = state.lastCmdIDX + 1
- def nextIDX = state.lastCmdIDX
- def cmd = [text:"${newCmd}",cmd:"${cCmd}"]
- def params = [:]
- def cpTypes = settings.findAll{it.key.startsWith("cpType_")}.sort()
- cpTypes.each{ cpType ->
- def i = cpType.key.replaceAll("cpType_","")
- def cpVal = settings.find{it.key == "cpVal_${i}"}
- def param = ["type":"${cpType.value}","value":"${cpVal.value}"]
- params.put(i, param)
- }
- cmd.put("params",params)
- if(myCapab) cmd.put("capab", capabs[myCapab]) else cmd.put("capab", "actuator")
- if (cmdMaps) cmdMaps.put((nextIDX),cmd)
- else state.customCommands = [(nextIDX):cmd]
- result = "command: ${newCmd} was added"
- } else {
- result = "command: ${newCmd} was not added, it already exists."
- }
- return result
- }
- def execTestCommand(){
- def result
- def cTypes = settings.findAll{it.key.startsWith("cpType_")}
- def p = getParamsAsList(cTypes.sort()) as Object[]
- devices.each { device ->
- try {
- device."${cCmd}"(p)
- result = "succeeded"
- }
- catch (IllegalArgumentException e){
- def em = e as String
- def ems = em.split(":")
- ems = ems[2].replace(" [","").replace("]","")
- ems = ems.replaceAll(", ","\n")
- // result = "failed, valid commands:\n${ems}"
- result = "failed"
- }
- catch (e){
- result = "failed with:\n${e}"
- }
- }
- return result
- }
- def execCommand(cmdID){
- def result = ""
- def pList = []
- if (cmdID){
- def cmdMap = state.customCommands["${cmdID}"]
- if (testCmd && cmdMap) {
- def params = cmdMap.params.sort()
- params.each{ p ->
- if (p.value.type == "string"){
- pList << "${p.value.value}"
- } else if (p.value.type == "decimal"){
- pList << p.value.value.toBigDecimal()
- } else {
- pList << p.value.value.toInteger()
- }
- }
- def p = pList as Object[]
- devices.each { device ->
- try {
- device."${cmdMap.cmd}"(p)
- result = "Command succeeded"
- }
- catch (IllegalArgumentException e){
- def em = e as String
- def ems = em.split(":")
- ems = ems[2].replace(" [","").replace("]","")
- ems = ems.replaceAll(", ","\n")
- result = "Command failed, valid commands:\n${ems}"
- }
- catch (e){
- result = "failed with:\n${e}"
- }
- }
- return result
- }
- }
- }
- def getDeviceCommands(){
- def result = ""
- devices.each { device ->
- result = device.supportedCommands.collect{ it as String }
- //log.debug "supportedCommands:${result}"
- }
- return result
- }
- def isExpert(){
- return getCommands().size() > 0
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement