Compare commits

..

6 Commits

Author SHA1 Message Date
PASCI
93219abcc8 Modifying 'Slightly smarter use of motion sensor' 2017-01-10 09:55:02 -08:00
PASCI
4b8e94b7ca MSA-1693: It control a SWITCH combining a MOTION SENSOR and a TIMEFRAME interval.
The MOTION works as expected outside the TIMEFRAME interval.
The SWITCH is always ON during the TIMEFRAME interval.
If there is no MOTION the SWITCH is turned OFF at the end of the TIMEFRAME interval.
2017-01-09 10:18:57 -08:00
Jack Chi
c028515fcd Merge pull request #1546 from skt123/dimmer_switch
[CHF-487] Health Check for Z-Wave Dimmer Switch
2016-12-29 10:29:43 -08:00
Jack Chi
751c98d123 Merge pull request #1550 from parijatdas/enerwave
[CHF-488] [CHF-489] [CHF-490] Implementation of HealthCheck for Enerwave Duplex Receptacle ZW15R, Enerwave On/Off Switch ZW15S and Leviton 15A Switch VRS15-1LZ
2016-12-29 10:27:18 -08:00
Parijat Das
9e10405527 Added fingerprints for the following devices:
1. Enerwave Duplex Receptacle ZW15R
2. Enerwave On/Off Switch ZW15S
3. Leviton 15A Switch VRS15-1LZ
2016-12-20 18:33:50 +05:30
sushant.k1
32ceaff54d [CHF-487]
Added Health Check Implementation for:
1. 1,000-Watt In-Wall Smart Dimmer Switch (GE 12725)
2. In-Wall Smart Fan Control (GE 12730)
2016-12-20 12:08:16 +05:30
4 changed files with 132 additions and 626 deletions

View File

@@ -22,9 +22,10 @@ metadata {
capability "Sensor"
capability "Health Check"
fingerprint mfr:"0063", prod:"4457", deviceJoinName: "Z-Wave Wall Dimmer"
fingerprint mfr:"0063", prod:"4944", deviceJoinName: "Z-Wave Wall Dimmer"
fingerprint mfr:"0063", prod:"5044", deviceJoinName: "Z-Wave Plug-In Dimmer"
fingerprint mfr:"0063", prod:"4457", deviceJoinName: "GE In-Wall Smart Dimmer "
fingerprint mfr:"0063", prod:"4944", deviceJoinName: "GE In-Wall Smart Dimmer "
fingerprint mfr:"0063", prod:"5044", deviceJoinName: "GE Plug-In Smart Dimmer "
fingerprint mfr:"0063", prod:"4944", model:"3034", deviceJoinName: "GE In-Wall Smart Fan Control"
}
simulator {

View File

@@ -25,6 +25,9 @@ metadata {
fingerprint mfr:"0063", prod:"4F50", model:"3031", deviceJoinName: "GE Plug-in Outdoor Switch"
fingerprint mfr:"001D", prod:"1D04", model:"0334", deviceJoinName: "Leviton Outlet"
fingerprint mfr:"001D", prod:"1C02", model:"0334", deviceJoinName: "Leviton Switch"
fingerprint mfr:"001D", prod:"0301", model:"0334", deviceJoinName: "Leviton 15A Switch"
fingerprint mfr:"011A", prod:"0101", model:"0102", deviceJoinName: "Enerwave On/Off Switch"
fingerprint mfr:"011A", prod:"0101", model:"0603", deviceJoinName: "Enerwave Duplex Receptacle"
}
// simulator metadata

View File

@@ -1,623 +0,0 @@
/**
* Rule Machine
*
* Copyright 2015 Bruce Ravenel and Mike Maxwell
*
* Version 1.6.5 1 Jan 2016
*
* Version History
*
* 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 23 Dec 2015 Added expert commands per Mike Maxwell
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
definition(
name: "Rule 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: "removePage")
//expert pages
page(name: "expert")
page(name: "generalApprovalPAGE")
page(name: "customCommandsPAGE")
page(name: "addCustomCommandPAGE")
page(name: "customParamsPAGE")
}
def mainPage() {
dynamicPage(name: "mainPage", title: "Installed Rules", install: true, uninstall: false, submitOnChange: true) {
if(!state.setup) initialize(true)
section {
app(name: "childRules", appName: "Rule", namespace: "bravenel", title: "Create New Rule...", multiple: true)
}
section ("Expert Features") {
href( "expert", title: "", description: "Tap to create custom commands", state: "")
}
section ("Remove Rule Machine"){
href "removePage", description: "Tap to remove Rule Machine ", title: ""
}
if(state.ver) section ("Version 1.6.5/" + state.ver) { }
}
}
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() {
if(!state.setup) initialize(true) else initialize(false)
}
def updated() {
initialize(false)
}
def initialize(first) {
if(first) {
state.ruleState = [:]
state.ruleSubscribers = [:]
}
childApps.each {child ->
if(child.name == "Rule") {
log.info "Installed Rules and Triggers: ${child.label}"
if(first) {
state.ruleState[child.label] = null
state.ruleSubscribers[child.label] = [:]
}
}
}
state.setup = true
}
def ruleList(appLabel) {
def result = []
childApps.each { child ->
if(child.name == "Rule" && child.label != appLabel && state.ruleState[child.label] != null) 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 "setRuleTruth1: $appLabel, $ruleTruth"
state.ruleState[appLabel] = ruleTruth
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 currentRule(appLabel) {
// log.debug "currentRule: $appLabel, ${state.ruleState[appLabel]}"
def result = state.ruleState[appLabel]
}
def childUninstalled() {
// log.debug "childUninstalled called"
}
def removeChild(appLabel) {
// log.debug "removeChild: $appLabel"
unSubscribeRule(appLabel)
if(state.ruleState[appLabel] != null) state.ruleState.remove(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)
}
}
}
/*****custom command specific pages *****/
def expert(){
dynamicPage(name: "expert", title: "Expert Features", uninstall: false, install: false) {
section(){
paragraph "${expertText()}"
//expert hrefs...
href( "customCommandsPAGE"
,title : "Configure Custom Commands..."
,description: ""
,state : anyCustom()
)
}
}
}
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 = getCommands()
dynamicPage(name: "customCommandsPAGE", title: "Custom Commands", uninstall: false, install: false) {
section(){
input(
name : "devices"
,title : "Test device"
,multiple : false
,required : false
,type : "capability.actuator"
,submitOnChange : true
)
if (settings.devices && savedCommands.size() != 0){
input(
name : "testCmd"
,title : "Select saved command to test"
,multiple : false
,required : false
,type : "enum"
,options : savedCommands
,submitOnChange : true
)
}
}
def result = execCommand(settings.testCmd)
if (result) {
section("${result}"){
}
}
section(){
if (devices){
href( "addCustomCommandPAGE"
,title : "New custom command..."
,description: ""
,state : null
)
}
}
if (getCommands()){
section(){
input(
name : "deleteCmds"
,title : "Delete custom commands..."
,multiple : true
,required : false
,description : ""
,type : "enum"
,options : getCommands()
,submitOnChange : true
)
if (isValidCommand(deleteCmds)){
href( "generalApprovalPAGE"
,title : "Delete command(s) now"
,description : ""
,state : null
,params : [method:"deleteCommands",title:"Delete Command"]
,submitOnChange : true
)
}
}
}
}
}
def addCustomCommandPAGE(){
def cmdLabel = getCmdLabel()
def complete = ""
def test = false
def pageTitle = "Create new custom command for:\n${devices}"
if (cmdLabel){
complete = "complete"
test = true
}
dynamicPage(name: "addCustomCommandPAGE", title: pageTitle, uninstall: false, install: false) {
section(){
input(
name : "cCmd"
,title : "Available device commands"
,multiple : false
,required : false
,type : "enum"
,options : getDeviceCommands()
,submitOnChange : true
)
href( "customParamsPAGE"
,title: "Parameters"
,description: parameterLabel()
,state: null
)
}
if (test){
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 {
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)]
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 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 (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}"
}
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 ->
try {
device."xxx"()
result = "Command succeeded"
}
catch (IllegalArgumentException e){
def em = e as String
def ems = em.split(":")
ems = ems[2].replace(" [","").replace("]","")
result = ems.split(", ").collect{it as String}
}
}
return result
}
def isExpert(ver){
state.ver = ver
return getCommands().size() > 0
}
def expertText() {
def text =
"Custom commands allows Rule Machine to control devices with custom capabilities. " +
"This includes dual dimmers and switches, ThingShields, FGBW controllers or any device you might build a " +
"custom SmartApp to utilize.\n\nCustom commands that are created and saved here will become available for use " +
"in any new or existing rules.\n\nAfter saving at least one command, look for 'Run custom device command' in your " +
"'Select Actions' sections."
}

View File

@@ -0,0 +1,125 @@
//==================================================================================
/**
* Motion-Switch-Sched SmartApp
*
* Copyright 2017 PASCI Ciro Ippolito 2017
***********************************************************************************
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
//==================================================================================
definition(
name: "Motion Switch SmartTimeframe",
namespace: "PASCI",
author: "PASCI",
description: "Motion Activated switch + Override timeframe.",
category: "Convenience",
iconUrl: "http://cdn.device-icons.smartthings.com/Home/home30-icn@2x.png",
iconX2Url: "http://cdn.device-icons.smartthings.com/Home/home30-icn@2x.png",
iconX3Url: "http://cdn.device-icons.smartthings.com/Home/home30-icn@2x.png"
)
//iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
//iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
//iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
//==================================================================================
preferences {
section("When MOTION is detected") {
input "themotion", "capability.motionSensor", required: true, title: "WHERE"
}
section("Turn ON this SWITCH") {
input "OUTLE", "capability.switch", required: true, title: "WHICH"
}
section("ON for how long") {
input "minutes", "number", required: true, title: "MINUTES)"
}
section("ON OVERRIDE time frame (if there is no motion)") {
input "FROMTime", "time", title: "From", required: true
input "TOTime", "time", title: "To", required: true
}
}
//==================================================================================
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
//==================================================================================
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
//==================================================================================
def initialize() {
log.debug "Installata at: " + now()
schedule(FROMTime, handlerFROMTIME)
schedule(TOTime, handlerTOTime)
subscribe(themotion, "motion.active", handler_Motion___Active)
subscribe(themotion, "motion.inactive", handler_motion_inactive)
}
// HANDLERS
//==================================================================================
def handlerFROMTIME() {
log.debug "FROM TIME at ${new Date()}"
OUTLE.on()
}
//==================================================================================
def handlerTOTime() {
log.debug "TO TIME at ${new Date()}"
checkMotion();
/*
def motionState = themotion.currentState("motion")
log.debug "state of motion sensor is: " + motionState.value
log.debug "TURN OFF at ${new Date()}"
log.debug "state of motion sensor is: " + themotion.motion
if (motionState.value == "active"){
log.debug "state of motion sensor is ACTIVE"
}else{
OUTLE.off()
log.debug "state of motion sensor is INACTIVE"
}
*/
}
//==================================================================================
def handler_Motion___Active(evt) {
log.debug("MOT Active")
OUTLE.on()
}
//==================================================================================
def handler_motion_inactive(evt) {
log.debug("MOT Inactive")
def between = timeOfDayIsBetween(FROMTime, TOTime, new Date(), location.timeZone)
if (between) {
log.debug("It dasan't mattar Christopha")
OUTLE.on()
} else {
runIn(60 * minutes, checkMotion)
}
}
//==================================================================================
def checkMotion() {
log.debug "In checkMotion scheduled method"
// get the current state object for the motion sensor
def motionState = themotion.currentState("motion")
if (motionState.value == "inactive") {
def elapsed = now() - motionState.date.time // time elapsed between now and when the motion reported inactive
def threshold = 1000 * 60 * minutes // elapsed time is in milliseconds
if (elapsed >= threshold) {
log.debug "Motion has stayed inactive long enough since last check ($elapsed ms): turning switch off"
OUTLE.off()
} else {
log.debug "Motion has not stayed inactive long enough since last check ($elapsed ms): doing nothing"
}
} else {
// Motion active; just log it and do nothing
log.debug "Motion is active, do nothing and wait for inactive"
}
}
//==================================================================================
//==================================================================================