/** * 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() }