/**
* Spruce Controller wifi master *
* Copyright 2019 Plaid Systems
*
*
* 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.
*
-------------11-2019 update---------------
* Spruce Controller wifi master control tile
* Manual Schedule tiles
* port to Hubitat
*
--------------12-2019 update by BAB--------
* Store & use pause device's deviceNetworkId instead of using device.label ("Spruce Pause")
* (This allows users to change the name/label of the pause device without breaking things)
* Optimizations
--------------06-2020 update by BAB--------
* No longer assumes childred have been deleted before creating new ones. Instead, will
* rename existing children if they already exist with different names. Also added removeScheduleDevice()
* so that parent can delete/remove unused Pause and/or other schedules individually
-------------06-23-2020 update by BAB--------
* Force Hubitat to return the most current attribute values
*/
metadata {
definition (name: 'Spruce wifi master', namespace: 'plaidsystems', author: 'Plaid Systems') {
capability "Switch"
capability "Switch Level"
capability "Sensor"
capability "Actuator"
attribute "pause", "string"
attribute "contact", "string"
attribute "status", "string"
attribute "message", "string"
attribute "rainsensor", "string"
command "resume"
command "pause"
command "updateSettings"
}
preferences {
input (description: "Use Level to set zone watering time. This setting does not effect scheduled water times.",
displayDuringSetup: false, type: "paragraph", element: "paragraph", title: "Set Level")
input (description: "Use Update Settings to refresh the manual schedule child device list or Pause Control, update other zone settings from Spruce App.",
displayDuringSetup: false, type: "paragraph", element: "paragraph", title: "Update Settings")
}
}
def installed(){
setLevel(10)
setPause(off)
setRain(off)
sendEvent(name: "switch", value: "off", isStateChange: true) //initialize switch to off
sendEvent(name: "status", value: "idle", isStateChange: true)
sendEvent(name: "message", value: "controller initialized", isStateChange: true)
updateChildren()
}
//updateSettings command
void updateSettings(){
parent.getChildren()
sendEvent(name: "message", value: "controller settings updated", isStateChange: true)
}
def updateChildren(){
log.debug "updateChildren master"
//get and delete children avoids duplicate children - NO LONGER USED AS OF 06-2020
if (false) {
try {
def children = getChildDevices()
children.each{
log.debug it
//if ("${it}" == "Spruce Pause") log.debug "found it"
if (it.deviceNetworkId == state.pauseDeviceNetworkId) log.debug "found it"
deleteChildDevice(it.deviceNetworkId)
}
}
catch (e) {
log.debug "no children"
}
}
parent.child_schedules(device.deviceNetworkId)
}
//add schedule child devices
void createScheduleDevices(id, i, schedule, schName){
String childDNI = "${id}.${i}"
if (i == 99) state.pauseDeviceNetworkId = childDNI // The pause device is always schedule #99
//add children
def existingChild = getChildDevice(childDNI) // getChildDevices().find(it.deviceNetworkId == childDNI)
String name = "schedule${i}"
if (!existingChild) {
log.info "Adding schedule ${schName} / ${schedule} (${childDNI})"
addChildDevice("plaidsystems", "Spruce wifi schedule", childDNI, [completedSetup: true, label: "${schName}", isComponent: true, name: name])
} else {
boolean renamed = false
if (existingChild.name != name) {
existingChild.name = name
renamed = true
}
if (existingChild.label != schName) {
existingChild.label = schName
renamed = true
}
if (renamed) {
log.info "Renamed schedule ${schName} / ${schedule} (${childDNI})"
} else {
log.info "Schedule ${schName} / ${schedule} (${childDNI}) already exists"
}
}
}
//remove an unused schedule device
void removeScheduleDevice(id, i){
String childDNI = "${id}.${i}"
def existingChild = getChildDevice(childDNI) // getChildDevices().find(it.deviceNetworkId == childDNI)
if (existingChild) {
deleteChildDevice(childDNI)
if (i == 99) state.pauseDeviceNetworkId = null // The pause device is always schedule #99
log.debug "Removed schedule ${existingChild.label} / ${existingChild.name} (${childDNI})"
} else {
log.debug "Schedule ${childDNI} does not exist (removing)"
}
}
//removes ALL schedule devices
void removeScheduleDevices() {
childDevices?.each {
log.debug "Removed schedule ${it.label} / ${it.name} (${it.deviceNetworkId})"
deleteChildDevice(it.deviceNetworkId)
}
}
def generateEvent(Map results) {
//log.debug "master status: ${device.status}"
//log.debug "master switch: ${device.currentValue('switch', true)}"
log.debug "master results: ${results}"
if (results.value == 'on' && device.currentValue('switch', true) != "on"){
sendEvent(name: "switch", value: 'on', descriptionText: "${results.descriptionText}", displayed: true)
}
switch(results.name) {
//master switch
case "switch":
if (results.value == 'on') sendEvent(name: "status", value: 'schedule active', descriptionText: "${results.descriptionText}", displayed: false)
else if (results.value == 'off') off()
break
//zone status
case 'zone':
if (results.value == 'on' && device.currentValue('switch', true) != 'on') sendEvent(name: "status", value: 'active', descriptionText: "${results.descriptionText}", displayed: true)
else if (results.value == 'off' && device.currentValue('status', true) == 'active') off()
break
//zonehold status
case 'zonehold':
if (results.value == 'on' && device.currentValue('switch', true) != 'on') sendEvent(name: "status", value: 'active', descriptionText: "${results.descriptionText}", displayed: true)
break
//rain sensor
case "rainsensor":
sendEvent(name: "${results.name}", value: "${results.value}", descriptionText: "${results.descriptionText}", displayed: true)
break
//pause
case "pause":
sendEvent(name: "${results.name}", value: "${results.value}", descriptionText: "${results.descriptionText}", displayed: true)
if(results.value == 'on' && device.currentValue('status', true) == 'schedule active') sendEvent(name: "status", value: "pause", displayed: false)
else if(results.value == 'off') sendEvent(name: "status", value: "schedule active", displayed: false)
break
//contact
case "contact":
sendEvent(name: "${results.name}", value: "${results.value}", descriptionText: "${results.descriptionText}", displayed: true)
break
}
sendEvent(name: "message", value: "${results.descriptionText}", displayed: false)
}
//set minutes
def setLevel(percent) {
log.debug "setLevel: ${percent}"
sendEvent(name: "level", value: percent, displayed: false)
}
//set rainSensor
def setRain(value) {
log.debug "setRain: ${value}"
sendEvent(name: "rainsensor", value: value, displayed: false)
}
//set Pause
def setPause(value) {
log.debug "setPause: ${value}"
sendEvent(name: "pause", value: value, displayed: false)
}
//************* Commands to/from pause and schedule children *******************
def zoneon(dni) {
log.debug "zoneon ${dni}"
def childDevice = getChildDevice(dni) // childDevices.find{it.deviceNetworkId == dni}
if (childDevice?.currentValue('switch', true) != 'on'){
log.debug "master zoneon ${childDevice} ${dni} on"
def result = [name: 'switch', value: 'on', descriptionText: "zone is on", isStateChange: true, displayed: true]
childDevice.sendEvent(result)
//if("${childDevice}" != "Spruce Pause") parent.scheduleOnOff(childDevice, 1)
if(childDevice.deviceNetworkId != state.pauseDeviceNetworkId) parent.scheduleOnOff(childDevice, 1)
else pause()
}
}
def zoneoff(dni) {
log.debug "zoneoff ${dni}"
def childDevice = getChildDevice(dni) // childDevices.find{it.deviceNetworkId == dni}
if (childDevice?.currentValue('switch', true) != 'off'){
log.debug "master zoneoff ${childDevice} off"
def result = [name: 'switch', value: 'off', descriptionText: "zone is off", isStateChange: true, displayed: true]
childDevice.sendEvent(result)
//if("${childDevice}" != "Spruce Pause") parent.scheduleOnOff(childDevice, 0)
if(childDevice.deviceNetworkId != state.pauseDeviceNetworkId) parent.scheduleOnOff(childDevice, 0)
else resume()
}
}
void switchon(results){
///sendEvent(name: "status", value: 'active', descriptionText: "${results.descriptionText}", displayed: false)
}
//switch commands
void on(){
def runtime = device.latestValue('level') * 60
parent.runAll(runtime)
}
void off(){
log.debug "---------------switch off"
sendEvent(name: "switch", value: 'off', descriptionText: "${device.label} is off", displayed: false)
sendEvent(name: "status", value: 'idle', descriptionText: "${device.label} is off", displayed: true)
allSchedulesOff()
parent.send_stop()
}
void allSchedulesOff(){
def children = getChildDevices()
children.each { child ->
def result = [name: 'switch', value: 'off', descriptionText: "${child.displayName} is off", isStateChange: true, displayed: true]
child.sendEvent(result)
}
}
//pause command
void pause(){
//def childDevice = childDevices.find{"${it}" == "Spruce Pause"}
def childDevice = state.pauseDeviceNetworkId ? getChildDevice(state.pauseDeviceNetworkId) : null // childDevices.find{it.deviceNetworkId == state.pauseDeviceNetworkId}
//pause only allowed with schedules
if(device.currentValue('status', true).contains('active')){
childDevice?.sendEvent(name: "switch", value: 'on', descriptionText: "Pause", isStateChange: true, displayed: true)
parent.send_pause()
}
else childDevice?.sendEvent(name: "switch", value: 'off', descriptionText: "Pause", isStateChange: true, displayed: true)
}
//resume command
void resume(){
//def childDevice = childDevices.find{"${it}" == "Spruce Pause"}
def childDevice = state.pauseDeviceNetworkId ? getChildDevice(state.pauseDeviceNetworkId) : null // childDevices.find{it.deviceNetworkId == state.pauseDeviceNetworkId}
childDevice?.sendEvent(name: "switch", value: 'off', descriptionText: "Resume", isStateChange: true, displayed: true)
parent.send_resume()
}