diff --git a/devicetypes/rachio/rachio-iro2-controller.src/rachio-iro2-controller.groovy b/devicetypes/rachio/rachio-iro2-controller.src/rachio-iro2-controller.groovy deleted file mode 100644 index ce10d780430..00000000000 --- a/devicetypes/rachio/rachio-iro2-controller.src/rachio-iro2-controller.groovy +++ /dev/null @@ -1,634 +0,0 @@ -/** - * Rachio Sprinkler Controller Device Handler - * - * Copyright\u00A9 2017, 2018 Franz Garsombke - * Written by Anthony Santilli (@tonesto7) - * - * 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. - * - */ - -import java.text.SimpleDateFormat - -def devVer() { return "2.0.0" } - -metadata { - definition (name: "Rachio Sprinkler Controller", namespace: "rachio", author: "Rachio") { - capability "Refresh" - capability "Switch" - capability "Actuator" - capability "Valve" - capability "Sensor" - capability "Health Check" - - attribute "hardwareModel", "string" - attribute "hardwareDesc", "string" - attribute "activeZoneCnt", "number" - attribute "controllerOn", "string" - - attribute "rainDelay","number" - attribute "watering", "string" - - //current_schedule data - attribute "scheduleType", "string" - attribute "curZoneRunStatus", "string" - - attribute "curZoneName", "string" - attribute "curZoneNumber", "number" - attribute "curZoneDuration", "number" - attribute "curZoneStartDate", "string" - attribute "curZoneIsCycling", "string" - attribute "curZoneCycleCount", "number" - attribute "curZoneWaterTime", "number" - attribute "rainDelayStr", "string" - attribute "standbyMode", "string" - - attribute "lastUpdatedDt", "string" - - command "stopWatering" - command "setRainDelay", ["number"] - - command "doSetRainDelay" - command "decreaseRainDelay" - command "increaseRainDelay" - command "setZoneWaterTime", ["number"] - command "decZoneWaterTime" - command "incZoneWaterTime" - command "runAllZones" - command "standbyOn" - command "standbyOff" - //command "pauseScheduleRun" - - command "open" - command "close" - //command "pause" - } - - simulator { - // TODO: define status and reply messages here - } - - tiles (scale: 2){ - multiAttributeTile(name: "valveTile", type: "generic", width: 6, height: 4) { - tileAttribute("device.watering", key: "PRIMARY_CONTROL" ) { - attributeState "off", label: 'Off', action: "runAllZones", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"on" - attributeState "offline", label: 'Offline', icon: "st.valves.water.closed", backgroundColor: "#cccccc" - attributeState "standby", label: 'Standby Mode', icon: "st.valves.water.closed", backgroundColor: "#cccccc" - attributeState "on", label: 'Watering', action: "close", icon: "st.valves.water.open", backgroundColor: "#00a0dc", nextState: "off" - } - tileAttribute("device.curZoneRunStatus", key: "SECONDARY_CONTROL") { - attributeState("default", label:'${currentValue}') - } - } - standardTile("hardwareModel", "device.hardwareModel", inactiveLabel: false, width: 2, height: 2, decoration: "flat") { - state "default", icon: "" - state "8ZoneV1", icon: "https://s3-us-west-2.amazonaws.com/rachio-media/smartthings/8zone_v1.png" - state "16ZoneV1", icon: "https://s3-us-west-2.amazonaws.com/rachio-media/smartthings/8zone_v1.png" - state "8ZoneV2", icon: "https://raw.githubusercontent.com/tonesto7/rachio-manager/master/images/rachio_gen2.png" - state "16ZoneV2", icon: "https://raw.githubusercontent.com/tonesto7/rachio-manager/master/images/rachio_gen2.png" - state "8ZoneV3", icon: "https://raw.githubusercontent.com/tonesto7/rachio-manager/master/images/rachio_gen3.png" - state "16ZoneV3", icon: "https://raw.githubusercontent.com/tonesto7/rachio-manager/master/images/rachio_gen3.png" - } - valueTile("hardwareDesc", "device.hardwareDesc", inactiveLabel: false, width: 4, height: 1, decoration: "flat") { - state "default", label: 'Model:\n${currentValue}' - } - valueTile("activeZoneCnt", "device.activeZoneCnt", inactiveLabel: true, width: 4, height: 1, decoration: "flat") { - state "default", label: 'Active Zones:\n${currentValue}' - } - valueTile("controllerOn", "device.controllerOn", inactiveLabel: true, width: 2, height: 1, decoration: "flat") { - state "default", label: 'Online Status:\n${currentValue}' - } - valueTile("controllerRunStatus", "device.controllerRunStatus", inactiveLabel: true, width: 4, height: 2, decoration: "flat") { - state "default", label: '${currentValue}' - } - valueTile("blank", "device.blank", width: 2, height: 1, decoration: "flat") { - state("default", label: '') - } - standardTile("switch", "device.switch", inactiveLabel: false, decoration: "flat") { - state "off", icon: "st.switch.off" - state "on", action: "stopWatering", icon: "st.switch.on" - } - valueTile("pauseScheduleRun", "device.scheduleTypeBtnDesc", inactiveLabel: false, decoration: "flat", width: 2, height: 1) { - state "default", label: '${currentValue}', action: "pauseScheduleRun" - } - - // Rain Delay Control - standardTile("leftButtonControl", "device.rainDelay", inactiveLabel: false, decoration: "flat") { - state "default", action:"decreaseRainDelay", icon:"st.thermostat.thermostat-left" - } - valueTile("rainDelay", "device.rainDelay", width: 2, height: 1, decoration: "flat") { - state "default", label:'Rain Delay:\n${currentValue} Days' - } - standardTile("rightButtonControl", "device.rainDelay", inactiveLabel: false, decoration: "flat") { - state "default", action:"increaseRainDelay", icon:"st.thermostat.thermostat-right" - } - valueTile("applyRainDelay", "device.rainDelayStr", width: 2, height: 1, inactiveLabel: false, decoration: "flat") { - state "default", label: '${currentValue}', action:'doSetRainDelay' - } - - //zone Water time control - valueTile("lastWateredDesc", "device.lastWateredDesc", width: 4, height: 1, decoration: "flat", wordWrap: true) { - state("default", label: 'Last Watered:\n${currentValue}') - } - standardTile("leftZoneTimeButton", "device.curZoneWaterTime", inactiveLabel: false, decoration: "flat") { - state "default", action:"decZoneWaterTime", icon:"st.thermostat.thermostat-left" - } - valueTile("curZoneWaterTime", "device.curZoneWaterTime", width: 2, height: 1, decoration: "flat") { - state "default", label:'Manual Zone Time:\n${currentValue} Minutes' - } - standardTile("rightZoneTimeButton", "device.curZoneWaterTime", inactiveLabel: false, decoration: "flat") { - state "default", action:"incZoneWaterTime", icon:"st.thermostat.thermostat-right" - } - valueTile("runAllZonesTile", "device.curZoneWaterTime", inactiveLabel: false, width: 2 , height: 1, decoration: "flat") { - state("default", label: 'Run All Zones\n${currentValue} Minutes', action:'runAllZones') - } - standardTile("standbyMode", "device.standbyMode", decoration: "flat", wordWrap: true, width: 2, height: 2) { - state "on", label:'Turn Standby Off', action:"standbyOff", nextState: "false", icon: "http://cdn.device-icons.smartthings.com/sonos/play-icon@2x.png" - state "off", label:'Turn Standby On', action:"standbyOn", nextState: "true", icon: "http://cdn.device-icons.smartthings.com/sonos/pause-icon@2x.png" - } - standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" - } - } - main "valveTile" - details(["valveTile", "hardwareModel", "hardwareDesc", "activeZoneCnt", "curZoneIsCyclingTile", "leftButtonControl", "rainDelay", "rightButtonControl", "applyRainDelay", - "leftZoneTimeButton", "curZoneWaterTime", "rightZoneTimeButton", "runAllZonesTile", "lastUpdatedDt", "standbyMode", "refresh"]) -} - -def getAppImg(imgName) { - return "https://raw.githubusercontent.com/tonesto7/rachio-manager/master/images/$imgName" -} - -// parse events into attributes -def parse(String description) { - log.debug "Parsing '${description}'" -} - -def initialize() { - sendEvent(name: "DeviceWatch-Enroll", value: groovy.json.JsonOutput.toJson(["protocol":"cloud", "scheme":"untracked"]), displayed: false) - - verifyDataAttr() -} - -def verifyDataAttr() { - updateDataValue("HealthEnrolled", "true") - updateDataValue("manufacturer", "Rachio") -// getDevGeneration is not defined in the connect app... -// def gen = state.deviceId ? parent?.getDevGeneration(state.deviceId) : null -// updateDataValue("model", "${device.name}${gen ? " ($gen)" : ""}") -} - -void installed() { - initialize() - state.isInstalled = true - sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true) -} - -void updated() { - initialize() -} - -// NOP implementation of ping as health check only calls this for tracked devices -// But as capability defines this method it's implemented to avoid MissingMethodException -def ping() { - log.info "unexpected ping call from health check" -} - -def generateEvent(Map results) { - if (!state.swVersion || state.swVersion != devVer()) { - initialize() - state.swVersion = devVer() - } - //log.warn "---------------START OF API RESULTS DATA----------------" - if (results) { - // log.debug results - state.deviceId = device.deviceNetworkId - state.pauseInStandby = (results.pauseInStandby == true) - hardwareModelEvent(results.data?.model) - activeZoneCntEvent(results.data?.zones) - controllerOnEvent(results.data?.on) - - if (results.status == "ONLINE") { - state.inStandby = results.standby - sendEvent(name: 'standbyMode', value: (results.standby?.toString() == "true" ? "on": "off"), displayed: true) - sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false) - if (results.standby == true && results.pauseInStandby == true) { - markStandby() - } else { - isWateringEvent(results.schedData?.status, results.schedData?.zoneId) - } - lastUpdatedEvent() - } else { - markOffLine() - } - if (!device.currentValue("curZoneWaterTime")) { - setZoneWaterTime(parent?.settings?.defaultZoneTime.toInteger()) - } - scheduleDataEvent(results.schedData, results.data.zones, results.rainDelay) - rainDelayValEvent(results.rainDelay) - } -} - -def getDurationDesc(long secondsCnt) { - int seconds = secondsCnt %60 - secondsCnt -= seconds - long minutesCnt = secondsCnt / 60 - long minutes = minutesCnt % 60 - minutesCnt -= minutes - long hoursCnt = minutesCnt / 60 - return "${minutes} min ${(seconds >= 0 && seconds < 10) ? "0${seconds}" : "${seconds}"} sec" -} - -def getDurationMinDesc(long secondsCnt) { - int seconds = secondsCnt %60 - secondsCnt -= seconds - long minutesCnt = secondsCnt / 60 - long minutes = minutesCnt % 60 - minutesCnt -= minutes - long hoursCnt = minutesCnt / 60 - return "${minutes}" -} - -def lastUpdatedEvent() { - state.lastUpdatedDt = formatDt(new Date())?.toString() - sendEvent(name: 'lastUpdatedDt', value: state.lastUpdatedDt, displayed: false) -} - -def markOffLine() { - log.debug("Watering is set to (Offline)") - sendEvent(name: 'watering', value: "offline", displayed: true) - sendEvent(name: 'valve', value: "closed", displayed: false) - sendEvent(name: 'switch', value: "off", displayed: false) - sendEvent(name: 'curZoneRunStatus', value: "Device is Offline", displayed: false) - sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false) -} - -def markStandby() { - log.debug("Watering set to (Standby Mode)") - sendEvent(name: 'watering', value: "standby", displayed: true) - sendEvent(name: 'valve', value: "closed", displayed: false) - sendEvent(name: 'switch', value: "off", displayed: false) - sendEvent(name: 'curZoneRunStatus', value: "Device in Standby Mode", displayed: false) -} - -def isWateringEvent(status, zoneId) { - //log.trace "isWateringEvent..." - def curState = device.currentValue("watering") - def isOn = (status == "PROCESSING") - def newState = isOn ? "on" : "off" - parent?.setWateringDeviceState(device.deviceNetworkId, isOn) - if(curState != newState) { - log.debug("UPDATED: Watering (${newState}) | Previous: (${curState})") - sendEvent(name: 'watering', value: newState, displayed: true) - sendEvent(name: 'switch', value: newState, displayed: false) - sendEvent(name: 'valve', value: (isOn ? "open" : "closed"), displayed: false) - if(curState != null) { - parent?.handleWateringSched(device.deviceNetworkId, isOn) - } - } -} - -def hardwareModelEvent(val) { - def newModel = null // Should these be assigned a defalt value e.g. 'Unknow' ? - def newDesc = null - switch(val) { - case "GENERATION1_8ZONE": - newModel = "8ZoneV1" - newDesc = "8-Zone (Gen 1)" - break - case "GENERATION1_16ZONE": - newModel = "16ZoneV1" - newDesc = "16-Zone (Gen 1)" - break - case "GENERATION2_8ZONE": - newModel = "8ZoneV2" - newDesc = "8-Zone (Gen 2)" - break - case "GENERATION2_16ZONE": - newModel = "16ZoneV2" - newDesc = "16-Zone (Gen 2)" - break - case "GENERATION3_8ZONE": - newModel = "8ZoneV3" - newDesc = "8-Zone (Gen 3)" - break - case "GENERATION3_16ZONE": - newModel = "16ZoneV3" - newDesc = "16-Zone (Gen 3)" - break - } - log.debug "Controller Model ${newModel}" - sendEvent(name: 'hardwareModel', value: newModel, displayed: true) - - log.debug "UPDATED: Controller Description ${newDesc}" - sendEvent(name: 'hardwareDesc', value: newDesc, displayed: true) -} - -def activeZoneCntEvent(zData) { - def zoneCnt = 0 - if (zData) { - zData.each { z -> if(z?.enabled.toString() == "true") { zoneCnt = zoneCnt+1 } } - } - log.debug "Active Zone Count ${zoneCnt}" - sendEvent(name: 'activeZoneCnt', value: zoneCnt, displayed: true) -} - -def controllerOnEvent(val) { - log.debug "Controller On Status ${newState}" - sendEvent(name: 'controllerOn', value: newState, displayed: true) -} - -def lastWateredDateEvent(val, dur) { - def newState = "${epochToDt(val)}" - def newDesc = "${epochToDt(val)}\nDuration: ${getDurationDesc(dur?.toLong())}" - log.debug "Last Watered Date ${newState}" - sendEvent(name: 'lastWateredDt', value: newState, displayed: true) - sendEvent(name: 'lastWateredDesc', value: newDesc, displayed: false) -} - -def rainDelayValEvent(val) { - def newState = val ? val : 0 - log.debug("Rain Delay Value ${newState}") - sendEvent(name: 'rainDelay', value: newState, displayed: true) - setRainDelayString(newState) -} - -def setZoneWaterTime(timeVal) { - def newVal = timeVal ? timeVal.toInteger() : parent?.settings?.defaultZoneTime.toInteger() - log.debug("Manual Zone Water Time (${newVal})") - sendEvent(name: 'curZoneWaterTime', value: newVal, displayed: true) -} - -def scheduleDataEvent(sData, zData, rainDelay) { - //log.trace "scheduleDataEvent($data)..." - state.schedData = sData - state.zoneData = zData - state.rainData = rainDelay - //def curSchedTypeBtnDesc = (!curSchedType || curSchedType in ["off", "manual"]) ? "Pause Disabled" : "Pause Schedule" - state.curSchedType = !sData?.type ? "Off" : sData?.type?.toString().capitalize() - state.curScheduleId = !sData?.scheduleId ? null : sData?.scheduleId - state.curScheduleRuleId = !sData?.scheduleRuleId ? null : sData?.scheduleRuleId - def zoneData = sData && zData ? getZoneData(zData, sData?.zoneId) : null - def zoneId = !zoneData ? null : sData?.zoneId - def zoneName = !zoneData ? null : zoneData?.name - def zoneNum = !zoneData ? null : zoneData?.zoneNumber - - def zoneStartDate = sData?.zoneStartDate ? sData?.zoneStartDate : null - def zoneDuration = sData?.zoneDuration ? sData?.zoneDuration : null - - def timeDiff = sData?.zoneStartDate ? GetTimeValDiff(sData?.zoneStartDate.toLong()) : 0 - def elapsedDuration = sData?.zoneStartDate ? getDurationMinDesc(Math.round(timeDiff)) : 0 - def wateringDuration = zoneDuration ? getDurationMinDesc(zoneDuration) : 0 - def zoneRunStatus = ((!zoneStartDate && !zoneDuration) || !zoneId ) ? "Status: Idle" : "${zoneName}: (${elapsedDuration} of ${wateringDuration} Minutes)" - - def zoneCycleCount = !sData?.totalCycleCount ? 0 : sData?.totalCycleCount - def zoneIsCycling = !sData?.cycling ? false : sData?.cycling - def wateringVal = device.currentValue("watering") - log.debug("ScheduleType ${state.curSchedType}") - sendEvent(name: 'scheduleType', value: state.curSchedType, displayed: true) - if(!state.inStandby && wateringVal != "offline" && isStateChange(device, "curZoneRunStatus", zoneRunStatus)) { - log.debug("UPDATED: ZoneRunStatus (${zoneRunStatus})") - sendEvent(name: 'curZoneRunStatus', value: zoneRunStatus, displayed: false) - } - log.debug("Active Zone Duration (${zoneDuration})") - sendEvent(name: 'curZoneDuration', value: zoneDuration?.toString(), displayed: true) - - log.debug("Current Zone Name (${zoneName})") - sendEvent(name: 'curZoneName', value: zoneName?.toString(), displayed: true) - - log.debug("Active Zone Number (${zoneNum})") - sendEvent(name: 'curZoneNumber', value: zoneNum, displayed: true) - log.debug("Zone Cycle Count (${zoneCycleCount})") - sendEvent(name: 'curZoneCycleCount', value: zoneCycleCount, displayed: true) - - sendEvent(name: 'curZoneIsCycling', value: zoneIsCycling?.toString().capitalize(), displayed: true) - - log.debug("Zone StartDate (${(zoneStartDate ? epochToDt(zoneStartDate).toString() : "Not Active")})") - sendEvent(name: 'curZoneStartDate', value: (zoneStartDate ? epochToDt(zoneStartDate).toString() : "Not Active"), displayed: true) -} - -def getZoneData(zData, zId) { - if (zData && zId) { - return zData.find { it?.id == zId } - } -} - -def incZoneWaterTime() { - // log.debug("Decrease Zone Runtime"); - def value = device.latestValue('curZoneWaterTime') - setZoneWaterTime(value + 1) -} - -def decZoneWaterTime() { - // log.debug("Increase Zone Runtime"); - def value = device.latestValue('curZoneWaterTime') - setZoneWaterTime(value - 1) -} - -def setRainDelayString( rainDelay) { - def rainDelayStr = "No Rain Delay"; - if( rainDelay > 0) { - rainDelayStr = "Rain Delayed"; - } - sendEvent(name: "rainDelayStr", value: rainDelayStr) -} - -def doSetRainDelay() { - def value = device.latestValue('rainDelay') - log.debug "Set Rain Delay ${value}" - if (parent?.setRainDelay(this, state.deviceId, value)) { - setRainDelayString(value) - } else { - markOffLine() - } - -} - -def updateRainDelay(value) { - log.debug "Update ${value}" - if (value > 7) { - value = 7; - } else if (value < 0) { - value = 0 - } - sendEvent(name: "rainDelayStr", value: "Set New Rain Delay") - sendEvent(name: 'rainDelay', value: value, displayed: true) -} - -def increaseRainDelay() { - log.debug "Increase Rain Delay" - def value = device.latestValue('rainDelay') - updateRainDelay(value + 1) -} - -def decreaseRainDelay() { - log.debug "Decrease Rain Delay" - def value = device.latestValue('rainDelay') - updateRainDelay(value - 1) -} - -def refresh() { - //log.trace "refresh..." - parent?.poll(this) -} - -def isCmdOk2Run() { - //log.trace "isCmdOk2Run..." - if (device.currentValue("DeviceWatch-DeviceStatus") == "online") { - if (!(state.pauseInStandby && state.inStandby)) { - return true - } - log.warn "Skipping the request... Because the controller is unable to send commands while it is in standby mode!!!" - } else { - log.warn "Skipping the request... Because the zone is unable to send commands while it's in an Offline State." - } - return false -} - -def runAllZones() { - log.trace "runAllZones..." - if (isCmdOk2Run()) { - def waterTime = device.latestValue('curZoneWaterTime') - log.debug "Sending Run All Zones for (${waterTime} Minutes)" - if (!parent?.runAllZones(this, state.deviceId, waterTime)) { - markOffLine() - } - } -} - -def pauseScheduleRun() { - log.trace "pauseScheduleRun... NOT AVAILABLE YET!!!" - if (state.curSchedType == "automatic") { - parent?.pauseScheduleRun(this) - } -} - -def standbyOn() { - log.trace "standbyOn..." - if (device.currentValue("watering") == "offline") { - log.debug "Device is currently Offline... Ignoring..." - } else if (device.currentValue("standbyMode") == "on") { - log.debug "Device is Already in Standby... Ignoring..." - } else { - if (parent?.standbyOn(this, state.deviceId)) { - sendEvent(name: 'standbyMode', value: "on", displayed: true) - } - } -} - -def standbyOff() { - log.trace "standbyOff..." - def inStandby = device.currentValue("standbyMode") == "on" ? true : false - if (device.currentValue("watering") == "offline") { - log.debug "Device is currently Offline... Ignoring..." - } else if (device.currentValue("standbyMode") == "on") { - if (parent?.standbyOff(this, state.deviceId)) { - sendEvent(name: 'standbyMode', value: "off", displayed: true) - } - } else { - log.debug "Device is Already out of Standby... Ignoring..." - } -} - -def on() { - log.trace "on..." - if (isCmdOk2Run()) { - if (device.currentValue("switch") == "off") { - open() - } else { - log.debug "Switch is Already ON... Ignoring..." - } - } -} - -def off() { - log.trace "off..." - if (device.currentValue("switch") == "on") { - close() - } else { - log.debug "Switch is Already OFF... Ignoring..." - } -} - -def open() { - log.debug "open command is not currently supported by the controller device..." -} - -def close() { - log.trace "close()..." - if (device.currentValue("valve") == "open") { - if (parent?.off(this, state.deviceId)) { - sendEvent(name:'watering', value: "off", displayed: true) - sendEvent(name:'switch', value: "off", displayed: false) - sendEvent(name:'valve', value: "closed", displayed: false) - } else { - log.trace "close(). marking offline" - markOffLine() - } - } else { - log.debug "Close command Ignored... The Valve is Already Closed" - } -} - -// To be used directly by smart apps -def stopWatering() { - log.trace "stopWatering" - close() -} - -def setRainDelay(rainDelay) { - sendEvent("name":"rainDelay", "value": value) - parent?.setRainDelay(this, value) -} - -def getDtNow() { - def now = new Date() - return formatDt(now, false) -} - -def epochToDt(val) { - return formatDt(new Date(val)) -} - -def formatDt(dt, mdy = true) { - def formatVal = mdy ? "MMM d, yyyy - h:mm:ss a" : "E MMM dd HH:mm:ss z yyyy" - def tf = new SimpleDateFormat(formatVal) - if (location?.timeZone) { - tf.setTimeZone(location?.timeZone) - } - return tf.format(dt) -} - -//Returns time differences is seconds -def GetTimeValDiff(timeVal) { - try { - def start = new Date(timeVal).getTime() - def now = new Date().getTime() - def diff = (int) (long) (now - start) / 1000 - return diff - } - catch (ex) { - log.error "GetTimeValDiff Exception: ${ex}" - return 1000 - } -} - -def getTimeDiffSeconds(strtDate, stpDate=null) { - if((strtDate && !stpDate) || (strtDate && stpDate)) { - def now = new Date() - def stopVal = stpDate ? stpDate.toString() : formatDt(now, false) - def start = Date.parse("E MMM dd HH:mm:ss z yyyy", strtDate).getTime() - def stop = Date.parse("E MMM dd HH:mm:ss z yyyy", stopVal).getTime() - def diff = (int) (long) (stop - start) / 1000 - return diff - } else { - return null - } -} diff --git a/devicetypes/rachio/rachio-iro2-zone.src/rachio-iro2-zone.groovy b/devicetypes/rachio/rachio-iro2-zone.src/rachio-iro2-zone.groovy deleted file mode 100644 index 3464acb15e0..00000000000 --- a/devicetypes/rachio/rachio-iro2-zone.src/rachio-iro2-zone.groovy +++ /dev/null @@ -1,167 +0,0 @@ -/** - * Rachio IRO2 Zone Device Handler - * - * Copyright\u00A9 2017, 2018 Franz Garsombke - * Written by Anthony Santilli (@tonesto7) - * - * 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. - * - */ -metadata { - definition (name: "Rachio Zone", namespace: "rachio", author: "Rachio") { - capability "Refresh" - capability "Switch" - capability "Actuator" - capability "Valve" - capability "Sensor" - capability "Health Check" - } - - simulator { - // TODO: define status and reply messages here - } - - tiles (scale: 2){ - multiAttributeTile(name: "valveTile", type: "generic", width: 6, height: 4) { - tileAttribute("device.switch", key: "PRIMARY_CONTROL" ) { - attributeState "off", label: 'Off', action: "open", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"on" - attributeState "on", label: 'Watering', action: "close", icon: "st.valves.water.open", backgroundColor: "#00a0dc", nextState: "off" - } - } - } - main "valveTile" - details(["valveTile"]) -} - -// parse events into attributes -def parse(String description) { - log.debug "Parsing '${description}'" -} - -def initialize() { - sendEvent(name: "DeviceWatch-Enroll", value: groovy.json.JsonOutput.toJson(["protocol":"cloud", "scheme":"untracked"]), displayed: false) -} - -void installed() { - state.isInstalled = true - initialize() - sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true) -} - -void updated() { - initialize() -} - -// NOP implementation of ping as health check only calls this for tracked devices -// But as capability defines this method it's implemented to avoid MissingMethodException -def ping() { - log.info "unexpected ping call from health check" -} - -def generateEvent(Map results) { - if (results) { - if (!results.data?.enabled) { - sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false) - return - } - // log.debug results - if (results.status == "ONLINE") { - sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false) - } else { - markOffLine() - } - } -} - -def refresh() { - parent?.poll(this) -} - -def on() { - log.trace "zone on..." - if (isCmdOk2Run()) { - if (device.currentValue("switch") == "off") { - open() - } else { - log.debug "Zone is Already ON... Ignoring.." - } - } -} - -def off() { - log.trace "zone off..." - if (device.currentValue("switch") == "on") { - close() - } else { - log.debug "Zone is Already OFF... Ignoring..." - } -} - -def open() { - log.trace "Zone open()..." - if (isCmdOk2Run()) { - if (device.currentValue("valve") == "closed") { - startZone() - } else { - log.debug "Valve is Already Open... Ignoring..." - } - } -} - -def close() { - log.trace "Zone close()..." - if (device.currentValue("valve") == "open") { - if (parent?.off(this, state.deviceId)) { - log.info "Zone was Stopped Successfully..." - sendEvent(name:'switch', value: "off", displayed: false) - sendEvent(name:'valve', value: "closed", displayed: false) - } - } else { - log.debug "Valve is Already Closed... Ignoring..." - } -} - -def markOffLine() { - log.trace "Watering (Offline)" - sendEvent(name: 'valve', value: "closed", displayed: false) - sendEvent(name: 'switch', value: "off", displayed: false) - sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false) -} - -def startZone() { - log.trace "startZone()..." - if (isCmdOk2Run()) { - def zoneNum = device.latestValue('zoneNumber') - def waterTime = 10; - log.debug("Starting Watering for Zone (${zoneNum}) for (${waterTime}) Minutes") - if (parent?.startZone(this, state.deviceId, zoneNum, waterTime)) { - log.debug "runThisZone was Sent Successfully" - sendEvent(name:'switch', value: "on", displayed: false) - sendEvent(name:'valve', value: "open", displayed: false) - } else { - markOffLine() - } - } -} - -// To be used directly by smart apps -def stopWatering() { - log.trace "stopWatering" - close() -} - -def isCmdOk2Run() { - //log.trace "isCmdOk2Run..." - if (device.currentValue("DeviceWatch-DeviceStatus") == "offline") { - log.warn "Skipping the request... Because the zone is unable to send commands while it's in an Offline State." - return false - } - return true -} diff --git a/smartapps/rachio/rachio-connect.src/rachio-connect.groovy b/smartapps/rachio/rachio-connect.src/rachio-connect.groovy deleted file mode 100644 index 087cd7bb0c1..00000000000 --- a/smartapps/rachio/rachio-connect.src/rachio-connect.groovy +++ /dev/null @@ -1,1360 +0,0 @@ -/** - * Rachio (Connect) Smart App - * - * Copyright\u00A9 2017, 2018 Franz Garsombke - * Written by Anthony Santilli (@tonesto7) - * - * 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. - * - */ - -import groovy.json.* -import java.text.SimpleDateFormat - -definition( - name: "Rachio (Connect)", - namespace: "rachio", - author: "Rachio", - description: "Connect your Rachio Sprinklers to SmartThings.", - category: "Green Living", - iconUrl: "https://s3-us-west-2.amazonaws.com/rachio-media/smartthings/Rachio-logo-100px.png", - iconX2Url: "https://s3-us-west-2.amazonaws.com/rachio-media/smartthings/Rachio-logo-200px.png", - iconX3Url: "https://s3-us-west-2.amazonaws.com/rachio-media/smartthings/Rachio-logo-300px.png", - singleInstance: true, - oauth: true, - usesThirdPartyAuthentication: true, - pausable: false -) - -{ - appSetting "clientId" - appSetting "clientSecret" - appSetting "serverUrl" - appSetting "apiUrl" - appSetting "appUrl" -} - -preferences { - page(name: "startPage") - page(name: "authPage") - page(name: "devicePage") - page(name: "devMigrationPage") - page(name: "supportPage") -} - -mappings { - path("/oauth/initialize") { action: [GET: "init"] } - path("/oauth/callback") { action: [ GET: "callback" ] } - path("/rachioReceiver") { action: [ POST: "rachioReceiveHandler" ] } -} - -def appVer() { return "2.0.0" } - -def appInfoSect() { - section() { - paragraph "Rachio (Connect)\n" + - "Copyright\u00A9 2017, 2018 Rachio, Inc.\n" + - "Version: ${appVer()}", - image: "https://s3-us-west-2.amazonaws.com/rachio-media/smartthings/Rachio-logo-100px.png" - } -} - -def startPage() { - if(atomicState.authToken) { - if(!settings.controllers && settings.sprinklers) { - devMigrationPage() - } else { - devicePage() - } - } else { - authPage() - } -} - -// Begin OAuth stuff -//Section2: page-related methods --------------------------------------------------------------------------------------- -def authPage() { - //log.debug "authPage()" - getAccessToken() - - def description = null - def uninstallAllowed = false - def oauthTokenProvided = false - //This is 3rd party cloud accessToken - if(atomicState.authToken) { - getRachioDeviceData(true) - def usrName = atomicState.userName ?: "" - description = usrName ? "You are signed in as $usrName" : "You are connected." - uninstallAllowed = true - oauthTokenProvided = true - } else { - description = "Login to Rachio..." - } - - //redirectUrl to be called back for code exchange - def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state?.accessToken}&apiServerUrl=${shardUrl}&client_name=smartthings" - - if (!oauthTokenProvided) { - log.debug "No Rachio AuthToken Found... Please Login..." - } - def authPara = !oauthTokenProvided ? "Tap to Login into Rachio service and authorize SmartThings access" : "Tap Next to setup your sprinklers." - return dynamicPage(name: "authPage", title: "Auth Page", nextPage: (oauthTokenProvided ? "devicePage" : null), uninstall: uninstallAllowed) { - appInfoSect() - section() { - paragraph authPara - href url: redirectUrl, style: "embedded", required: (!oauthTokenProvided), state: (oauthTokenProvided ? "complete" : ""), title: "Rachio", description: description - } - if(uninstallAllowed) { removeSect() } - } -} - -def removeSect() { - remove("Remove this App and Devices!", "WARNING!!!", "Last Chance to Stop!\nThis action is not reversible\n\nThis App and All Devices will be removed") -} - -def devMigrationPage() { - return dynamicPage(name: "devMigrationPage", title: "Migration Page", nextPage: "devicePage", install: false, uninstall: false) { - section() { - paragraph "This SmartApp was updated to support multiple controllers.\nYour previous controller and zone selections are being migrated to the new data structure.", required: true, state: null - } - section() { - log.debug "Migrating Controller and Zone Selections to New Data Structure..." - List devs = [] - String id = settings.sprinklers - devs.push(id as String) - app.updateSetting("controllers", [type: "enum", value: devs]) - log.debug "Controllers: ${settings.controllers}" - if(settings.selectedZones) { - List zones = settings.selectedZones?.collect { it as String } - if(zones) { - app.updateSetting("${id}_zones", [type: "enum", value: zones]) - } - log.debug "Controller($id) Zones: ${settings."${id}_zones"}" - } - paragraph "Setting Migration Complete...\n\nTap Next to Proceed to Device Configuration", state: "complete" - } - } -} - -// This method is called after "auth" page is done with Oauth2 authorization, then page "deviceList" with content of devicePage() -def devicePage() { - //log.trace "devicePage()..." - if(!atomicState.authToken) { - log.debug "No accesstoken" - return - } - // Step 1: get (list) of available devices associated with the login account. - def devData = getRachioDeviceData() - def devices = getDeviceInputEnum(devData) - // log.debug "rachioDeviceList(): ${devices}" - - //step2: render the page for user to select which device - return dynamicPage(name: "devicePage", title: "${(atomicState.authToken && atomicState.selectedDevices) ? "Select" : "Manage"} Your Devices", install: true, uninstall: true) { - appInfoSect() - section("Controller Configuration:"){ - input "controllers", "enum", title: "Select your controllers", description: "Tap to Select", required: true, multiple: true, options: devices, submitOnChange: true, image: (atomicState.modelInfo ? atomicState.modelInfo.img : "") - atomicState.controllerIds = settings.controllers - } - if(settings.controllers) { - updateHwInfoMap(devData?.devices) - devices?.sort { it?.value }?.each { cont-> - if(cont?.key in settings.controllers) { - section("${cont?.value} Zones:"){ - if(settings."${cont?.key}_zones") { - def dData = devData?.devices?.find { it?.id == cont?.key } - if(dData) { devDesc(dData) } - } - def zoneData = zoneSelections(devData, cont?.key) - input "${cont?.key}_zones", "enum", title: "Select your zones", description: "Tap to Select", required: true, multiple: true, options: zoneData, submitOnChange: true - } - } - } - section("Preferences:") { - input(name: "pauseInStandby", title: "Disable Actions while in Standby?", type: "bool", defaultValue: true, multiple: false, submitOnChange: true, - description: "Allow your device to be disabled in SmartThings when you place your controller in Standby Mode...") - paragraph "Select the Duration time to be used for manual Zone Runs (This can be changed under each zones device page)" - input(name: "defaultZoneTime", title: "Default Zone Runtime (Minutes)", type: "number", description: "Tap to Modify", required: false, defaultValue: 10, submitOnChange: true) - } - } - section() { - href "supportPage", title: "Rachio Support", description: "" - href "authPage", title: "Manage Login", description: "" - } - removeSect() - } -} - -void settingUpdate(name, value, type=null) { - log.trace "settingUpdate($name, $value, $type)..." - if(name && type) { - app.updateSetting("$name", [type: "$type", value: value]) - } - else if (name && type == null){ app.updateSetting(name.toString(), value) } -} - -void settingRemove(name) { - log.trace "settingRemove($name)..." - if(name) { - app.deleteSetting("$name") - } -} - -void appCleanup() { - log.trace "appCleanup()" - def stateItems = ["deviceId", "selectedDevice", "selectedZones", "inStandbyMode", "webhookId", "isWateringMap", "inStandbyModeMap"] - def setItems = ["sprinklers", "selectedZones"] - stateItems?.each { if(state.containsKey(it as String)) {state.remove(it)} } - setItems?.each { if(settings.containsKey(it as String)) {settingRemove(it)} } -} - -def devDesc(dev) { - if(dev) { - def zoneCnt = dev?.zones?.findAll { it?.id in settings."${dev?.id}_zones" }?.size() ?: 0 - def str = "${atomicState.installed ? "Installed" : "Installing"} Device:\n${atomicState.modelInfo[dev?.id]?.desc}\n" + - "\n($zoneCnt) Zone(s) ${atomicState.installed ? "are selected" : "will be installed"}" - paragraph str, state: "complete", image: (atomicState.modelInfo[dev?.id]?.img ?: "") - } -} - -def supportPage() { - return dynamicPage(name: "supportPage", title: "Rachio Support", install: false, uninstall: false) { - section() { - href url: getSupportUrl(), style:"embedded", title:"Rachio Support (Web)", description:"", state: "complete", - image: "http://rachio-media.s3.amazonaws.com/images/icons/icon-support.png" - href url: getCommunityUrl(), style:"embedded", title:"Rachio Community (Web)", description:"", state: "complete", - image: "http://d33v4339jhl8k0.cloudfront.net/docs/assets/5355b85be4b0d020874de960/images/58333550903360645bfa6cf8/file-Er3y7doeam.png" - } - } -} - -def zoneSelections(devData, devId=null) { - //log.debug "zoneSelections: $devData" - def res = [:] - if(!devData) { return res } - devData?.devices.sort {it?.name}.each { dev -> - if(dev?.id == devId) { - dev?.zones?.sort {it?.zoneNumber }.each { zone -> - def str = (zone?.enabled == true) ? "" : " (Disabled)" - //log.debug "zoneId: $zone.id" - def adni = [zone?.id].join('.') - res[adni] = "${zone?.name}$str" - } - } - } - return res -} - -// This was added to handle missing oauth on the smartapp and notifying the user of why it failed. -def getAccessToken() { - try { - if(!atomicState.accessToken) { - atomicState.accessToken = createAccessToken() - } - } - catch (ex) { - log.warn "Error: OAuth is not Enabled for the Rachio (Connect) application!!!. Please click remove and Enable Oauth under the SmartApp App Settings in the IDE..." - } -} - -//1. redirect SmartApp to prompt user to input his/her credentials on 3rd party cloud service -def init() { - //log.debug "init()" - def stcid = getClientId() - //log.debug "Rachio OAuth Client ID: ${stcid}" - - def oauthParams = [ - response_type: "code", - client_id: stcid, - redirect_uri: callbackUrl, - client_name: "smartthings" - ] - - def loc = "${appEndpoint}/oauth?${toQueryString(oauthParams)}" - //log.debug "OAuth Callback URL: ${loc}" - redirect(location: loc) - -} - -/* 2.0 Obtain authorization_code, access_token, refresh_token to be used with API calls - 2.1 get authorization_code from 3rd party cloud service - 2.2 use authorization_code to get access_token, refresh_token, and expire from 3rd party cloud service -*/ -def callback() { - //log.debug "callback()>> params.code ${params.code}" - def appKey = !appSettings?.clientId ? "smartthings" : appSettings.clientId - def tokenParams = [ - headers: ["Authorization": "Basic $appKey", "Content-Type": "application/x-www-form-urlencoded"], - uri: "${apiEndpoint}/1/oauth/token_2_0", - body: [ - grant_type:'authorization_code', - code:params.code, - redirect_uri: callbackUrl, - client_id : getClientId(), - client_secret: getClientSecret() - ] - ] - - try { - httpPost(tokenParams) { resp -> - atomicState.authToken = resp?.data.access_token.toString() - atomicState.refreshToken = resp?.data.refresh_token.toString() - atomicState.authTokenExpiresIn = resp?.data.expires_in.toString() - //log.debug "Response: ${resp?.data}" //Hiding from Release - } - } catch (groovyx.net.http.HttpResponseException e) { - log.error "Error: ${e?.statusCode}" - log.debug "Response headers: ${e?.response?.allHeaders}" - log.debug "Data: ${e?.response?.data}" - } - - if (atomicState.authToken) { - success() - } else { - fail() - } -} - -def success() { - def message = """ -
Your Rachio Account is now connected to SmartThings!
-Click 'Done' to finish setup.
- """ - connectionStatus(message) -} - -def fail() { - def message = """ -The connection could not be established!
-Click 'Done' to return to the menu.
- """ - connectionStatus(message) -} - -// End OAuth Stuff - -def connectionStatus(message, redirectUrl = null) { - def redirectHtml = "" - if (redirectUrl) { - redirectHtml = """ - - """ - } - - def html = """ - - - - -