diff --git a/devicetypes/johnrucker/coopboss-h3vx.src/coopboss-h3vx.groovy b/devicetypes/johnrucker/coopboss-h3vx.src/coopboss-h3vx.groovy new file mode 100644 index 0000000..510c4ef --- /dev/null +++ b/devicetypes/johnrucker/coopboss-h3vx.src/coopboss-h3vx.groovy @@ -0,0 +1,795 @@ +/** + * CoopBoss H3Vx + * 02/29/16 Fixed app crash with Android by changing the syntax of default state in tile definition. + * Fixed null value errors during join process. Added 3 new commands to refresh data. + * + * 01/18/16 Masked invalid temperature reporting when TempProbe1 is below 0C + * Added setBaseCurrentNE, readBaseCurrentNE, commands as well as baseCurrentNE attribute. + * + * Copyright 2016 John Rucker + * + * 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. + * Icon location = http://scripts.3dgo.net/smartthings/icons/ + */ +metadata { + definition (name: "CoopBoss H3Vx", namespace: "JohnRucker", author: "John.Rucker@Solar-Current.com") { + capability "Refresh" + capability "Polling" + capability "Sensor" + capability "Actuator" + capability "Configuration" + capability "Temperature Measurement" + capability "Door Control" + capability "Switch" + + command "closeDoor" + command "closeDoorHiI" + command "openDoor" + command "autoCloseOn" + command "autoCloseOff" + command "autoOpenOn" + command "autoOpenOff" + command "setCloseLevelTo" + command "setOpenLevelTo" + command "setSensitivityLevel" + command "Aux1On" + command "Aux1Off" + command "Aux2On" + command "Aux2Off" + command "updateTemp1" + command "updateTemp2" + command "updateSun" + command "setNewBaseCurrent" + command "setNewPhotoCalibration" + command "readNewPhotoCalibration" + command "readBaseCurrentNE" + command "setBaseCurrentNE" + command "updateSensitivity" + command "updateCloseLightLevel" + command "updateOpenLightLevel" + + attribute "doorState","string" + attribute "currentLightLevel","number" + attribute "closeLightLevel","number" + attribute "openLightLevel","number" + attribute "autoCloseEnable","string" + attribute "autoOpenEnable","string" + attribute "TempProb1","number" + attribute "TempProb2","number" + attribute "dayOrNight","string" + attribute "doorSensitivity","number" + attribute "doorCurrent","number" + attribute "doorVoltage","number" + attribute "Aux1","string" + attribute "Aux2","string" + attribute "coopStatus","string" + attribute "baseDoorCurrent","number" + attribute "photoCalibration","number" + attribute "baseCurrentNE","string" + + fingerprint profileId: "0104", inClusters: "0000,0101,0402", manufacturer: "Solar-Current", model: "Coop Boss" + + } + + // simulator metadata + simulator { + } + + + preferences { + input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph" + input "tempOffsetCoop", "number", title: "Coop Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false + input "tempOffsetOutside", "number", title: "Outside Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false + } + + + // UI tile definitions + tiles(scale: 2){ + multiAttributeTile(name:"doorCtrl", type:"generic", width:6, height:4) {tileAttribute("device.doorState", key: "PRIMARY_CONTROL") + { + attributeState "unknown", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", nextState:"Sent" + attributeState "open", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#0000ff" , nextState:"Sent" + attributeState "opening", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffa81e" + attributeState "closed", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#79b821", nextState:"Sent" + attributeState "closing", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffa81e" + attributeState "jammed", label: '${name}', action:"closeDoorHiI", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent" + attributeState "forced close", label: 'forced\rclose', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff8000", nextState:"Sent" + attributeState "fault", label: 'FAULT', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent" + attributeState "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e" + } + tileAttribute ("device.coopStatus", key: "SECONDARY_CONTROL") { + attributeState "device.coopStatus", label:'${currentValue}' + } + } + + multiAttributeTile(name:"dtlsDoorCtrl", type:"generic", width:6, height:4) {tileAttribute("device.doorState", key: "PRIMARY_CONTROL") + { + attributeState "unknown", label: '${name}', action:"openDoor", icon: "st.secondary.tools", nextState:"Sent" + attributeState "open", label: '${name}', action:"closeDoor", icon: "st.doors.garage.garage-open", backgroundColor: "#0000ff", nextState:"Sent" + attributeState "opening", label: '${name}', action:"closeDoor", icon: "st.doors.garage.garage-opening", backgroundColor: "#ffa81e" + attributeState "closed", label: '${name}', action:"openDoor", icon: "st.doors.garage.garage-closed", backgroundColor: "#79b821", nextState:"Sent" + attributeState "closing", label: '${name}', action:"openDoor", icon: "st.doors.garage.garage-closing", backgroundColor: "#ffa81e" + attributeState "jammed", label: '${name}', action:"closeDoorHiI", icon: "st.doors.garage.garage-open", backgroundColor: "#ff0000", nextState:"Sent" + attributeState "forced close", label: "forced", action:"openDoor", icon: "st.doors.garage.garage-closed", backgroundColor: "#ff8000", nextState:"Sent" + attributeState "fault", label: 'FAULT', action:"openDoor", icon: "st.secondary.tools", backgroundColor: "#ff0000", nextState:"Sent" + attributeState "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e" + } + tileAttribute ("device.doorState", key: "SECONDARY_CONTROL") { + attributeState "unknown", label: 'Door is in unknown state. Push to open.' + attributeState "open", label: 'Coop door is open. Push to close.' + attributeState "opening", label: 'Caution, door is opening!' + attributeState "closed", label: 'Coop door is closed. Push to open.' + attributeState "closing", label: 'Caution, door is closing!' + attributeState "jammed", label: 'Door open! Push for high-force close' + attributeState "forced close", label: "Door is closed. Push to open." + attributeState "fault", label: 'Door fault check electrical connection.' + attributeState "Sent", label: 'Command sent to CoopBoss...' + } + } + + standardTile("autoClose", "device.autoCloseEnable", width: 2, height: 2){ + state "on", label: 'Auto', action:"autoCloseOff", icon: "st.doors.garage.garage-closing", backgroundColor: "#79b821", nextState:"Sent" + state "off", label: 'Auto', action:"autoCloseOn", icon: "st.doors.garage.garage-closing", nextState:"Sent" + state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e" + } + + standardTile("autoOpen", "device.autoOpenEnable", width: 2, height: 2){ + state "on", label: 'Auto', action:"autoOpenOff", icon: "st.doors.garage.garage-opening", backgroundColor: "#79b821", nextState:"Sent" + state "off", label: 'Auto', action:"autoOpenOn", icon: "st.doors.garage.garage-opening", nextState:"Sent" + state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e" + } + + valueTile("TempProb1", "device.TempProb1", width: 2, height: 2, decoration: "flat"){ + state "default", label:'Coop\r${currentValue}°', unit:"F", action:"updateTemp1"} + + valueTile("TempProb2", "device.TempProb2", width: 2, height: 2, decoration: "flat"){ + state "default", label:'Outside\r${currentValue}°', unit:"F", action:"updateTemp2"} + + valueTile("currentLevel", "device.currentLightLevel", width: 2, height: 2, decoration: "flat") { + state "default", label:'Sun\r${currentValue}', action:"updateSun"} + + valueTile("dayOrNight", "device.dayOrNight", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { + state "default", label:'${currentValue}.' + } + + controlTile("SetClSlider", "device.closeLightLevel", "slider", height: 2, width: 4, inactiveLabel: false, range:"(1..100)") { + state "closeLightLevel", action:"setCloseLevelTo", backgroundColor:"#d04e00" + } + + valueTile("SetClValue", "device.closeLightLevel", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { + state "default", label:'Close\nSunlight\n${currentValue}', action:'updateCloseLightLevel' + } + + controlTile("SetOpSlider", "device.openLightLevel", "slider", height: 2, width: 4, inactiveLabel: false, range:"(1..100)") { + state "openLightLevel", action:"setOpenLevelTo", backgroundColor:"#d04e00" + } + + valueTile("SetOpValue", "device.openLightLevel", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { + state "default", label:'Open\nSunlight\n${currentValue}', action:'updateOpenLightLevel' + } + + controlTile("SetSensitivitySlider", "device.doorSensitivity", "slider", height: 2, width: 4, inactiveLabel: false, range:"(1..100)") { + state "openLightLevel", action:"setSensitivityLevel", backgroundColor:"#d04e00" + } + + valueTile("SetSensitivityValue", "device.doorSensitivity", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { + state "default", label:'Door\nSensitivity\n${currentValue}', action:'updateSensitivity' + } + + standardTile("refresh", "device.refresh", width: 2, height: 2, decoration: "flat", inactiveLabel: false) { + state "default", label:'All', action:"refresh.refresh", icon:"st.secondary.refresh-icon" + } + + standardTile("aux1", "device.Aux1", width: 2, height: 2, canChangeIcon: true) { + state "off", label:'Aux 1', action:"Aux1On", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"Sent" + state "on", label:'Aux 1', action:"Aux1Off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"Sent" + state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e" + } + + standardTile("aux2", "device.Aux2", width: 2, height: 2, canChangeIcon: true) { + state "off", label:'Aux 2', action:"Aux2On", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"Sent" + state "on", label:'Aux 2', action:"Aux2Off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"Sent" + state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e" + } + + main "doorCtrl" + details (["dtlsDoorCtrl", "TempProb1", "TempProb2", "currentLevel", "autoClose", "autoOpen", "dayOrNight", + "SetClSlider", "SetClValue", "SetOpSlider", "SetOpValue", "SetSensitivitySlider", "SetSensitivityValue", + "aux1", "aux2", "refresh"]) + } +} + +// Parse incoming device messages to generate events def parse(String description) { +def parse(String description) { + log.debug "description: $description" + Map map = [:] + if (description?.startsWith('catchall:')) { + map = parseCatchAllMessage(description) + } + else if (description?.startsWith('read attr -')) { + map = parseReportAttributeMessage(description) + } + else if (description?.startsWith('temperature: ') || description?.startsWith('humidity: ')) { + map = parseCustomMessage(description) + } + log.debug map + //return map ? createEvent(map) : null + sendEvent(map) + callUpdateStatusTxt() +} + +private Map parseCatchAllMessage(String description) { + Map resultMap = [:] + def cluster = zigbee.parse(description) + log.debug cluster + if (cluster.clusterId == 0x0402) { + switch(cluster.sourceEndpoint) { + + case 0x39: // Endpoint 0x39 is the temperature of probe 1 + String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join() + resultMap.name = "TempProb1" + def celsius = Integer.valueOf(temp,16).shortValue() + if (celsius == -32768){ // This number is used to indicate an error in the temperature reading + resultMap.value = "---" + }else{ + celsius = celsius / 100 // Temperature value is sent X 100. + resultMap.value = celsiusToFahrenheit(celsius) + if (tempOffsetOutside) { + def offset = tempOffsetOutside as int + resultMap.value = resultMap.value + offset + } + } + sendEvent(name: "temperature", value: resultMap.value, displayed: false) // set the temperatureMeasurment capability to temperature + break + + case 0x40: // Endpoint 0x40 is the temperature of probe 2 + String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join() + resultMap.name = "TempProb2" + def celsius = Integer.valueOf(temp,16).shortValue() + //resultMap.descriptionText = "Prob2 celsius value = ${celsius}" + if (celsius == -32768){ // This number is used to indicate an error in the temperature reading + resultMap.value = "---" + }else{ + celsius = celsius / 100 // Temperature value is sent X 100. + resultMap.value = celsiusToFahrenheit(celsius) + if (tempOffsetCoop) { + def offset = tempOffsetCoop as int + resultMap.value = resultMap.value + offset + } + } + break + } + } + + if (cluster.clusterId == 0x0101 && cluster.command == 0x0b) { // This is a default response to a command sent to cluster 0x0101 door control + //log.debug "Default Response Data = $cluster.data" + switch(cluster.data) { + + case "[10, 0]": // 0x0a turn auto close on command verified + resultMap.name = "autoCloseEnable" + resultMap.value = "on" + break + + case "[11, 0]": // 0x0b turn auto close off command verified + resultMap.name = "autoCloseEnable" + resultMap.value = "off" + break + + case "[12, 0]": // 0x0C turn auto open on command verified + resultMap.name = "autoOpenEnable" + resultMap.value = "on" + break + + case "[13, 0]": // 0x0d turn auto open off command verified + resultMap.name = "autoOpenEnable" + resultMap.value = "off" + break + + + case "[20, 0]": // 0x14 Aux1 On command verified + log.info "verified Aux1 On" + sendEvent(name: "switch", value: "on", displayed: false) + resultMap.name = "Aux1" + resultMap.value = "on" + break + + case "[21, 0]": // 0x15 Aux1 Off command verified + log.info "verified Aux1 Off" + sendEvent(name: "switch", value: "off", displayed: false) + resultMap.name = "Aux1" + resultMap.value = "off" + break + + case "[22, 0]": // 0x16 Aux2 On command verified + log.info "verified Aux2 On" + resultMap.name = "Aux2" + resultMap.value = "on" + break + + case "[23, 0]": // 0x17 Aux2 Off command verified + log.info "verified Aux2 Off" + resultMap.name = "Aux2" + resultMap.value = "off" + break + + } + } + return resultMap +} + +private Map parseReportAttributeMessage(String description) { + Map resultMap = [:] + def descMap = parseDescriptionAsMap(description) + //log.debug "read attr descMap --> $descMap" + if (descMap.cluster == "0101" && descMap.attrId == "0003") { + resultMap.name = "doorState" + if (descMap.value == "00"){ + resultMap.value = "unknown" + sendEvent(name: "door", value: "unknown", displayed: false) + }else if(descMap.value == "01"){ + resultMap.value = "closed" + sendEvent(name: "door", value: "closed", displayed: false) + }else if(descMap.value == "02"){ + resultMap.value = "open" + sendEvent(name: "door", value: "open", displayed: false) + }else if(descMap.value == "03"){ + resultMap.value = "jammed" + }else if(descMap.value == "04"){ + resultMap.value = "forced close" + }else if(descMap.value == "05"){ + resultMap.value = "forced close" + }else if(descMap.value == "06"){ + resultMap.value = "closing" + sendEvent(name: "door", value: "closing", displayed: false) + }else if(descMap.value == "07"){ + resultMap.value = "opening" + sendEvent(name: "door", value: "opening", displayed: false) + }else if(descMap.value == "08"){ + resultMap.value = "fault" + }else { + resultMap.value = "unknown" + } + resultMap.descriptionText = "Door State Changed to ${resultMap.value}" + + } else if (descMap.cluster == "0101" && descMap.attrId == "0400") { + resultMap.name = "currentLightLevel" + resultMap.value = (Integer.parseInt(descMap.value, 16)) + resultMap.displayed = false + + } else if (descMap.cluster == "0101" && descMap.attrId == "0401") { + resultMap.name = "closeLightLevel" + resultMap.value = (Integer.parseInt(descMap.value, 16)) + + } else if (descMap.cluster == "0101" && descMap.attrId == "0402") { + resultMap.name = "openLightLevel" + resultMap.value = (Integer.parseInt(descMap.value, 16)) + + } else if (descMap.cluster == "0101" && descMap.attrId == "0403") { + resultMap.name = "autoCloseEnable" + if (descMap.value == "01"){resultMap.value = "on"} + else{resultMap.value = "off"} + + } else if (descMap.cluster == "0101" && descMap.attrId == "0404") { + resultMap.name = "autoOpenEnable" + if (descMap.value == "01"){resultMap.value = "on"} + else{resultMap.value = "off"} + + } else if (descMap.cluster == "0101" && descMap.attrId == "0405") { + resultMap.name = "doorCurrent" + resultMap.value = (Integer.parseInt(descMap.value, 16)) + resultMap.value = resultMap.value * 0.001 + + } else if (descMap.cluster == "0101" && descMap.attrId == "0408") { + resultMap.name = "doorSensitivity" + resultMap.value = (100 - Integer.parseInt(descMap.value, 16)) + + } else if (descMap.cluster == "0101" && descMap.attrId == "0409") { + resultMap.name = "baseDoorCurrent" + resultMap.value = (Integer.parseInt(descMap.value, 16)) + resultMap.value = resultMap.value * 0.001 + + } else if (descMap.cluster == "0101" && descMap.attrId == "040a") { + resultMap.name = "doorVoltage" + resultMap.value = (Integer.parseInt(descMap.value, 16)) + resultMap.value = resultMap.value * 0.001 + + } else if (descMap.cluster == "0101" && descMap.attrId == "040b") { + resultMap.name = "Aux1" + if(descMap.value == "01"){ + resultMap.value = "on" + sendEvent(name: "switch", value: "on", displayed: false) + }else{ + resultMap.value = "off" + sendEvent(name: "switch", value: "off", displayed: false) + } + + } else if (descMap.cluster == "0101" && descMap.attrId == "040c") { + resultMap.name = "Aux2" + if(descMap.value == "01"){ + resultMap.value = "on" + }else{ + resultMap.value = "off" + } + } else if (descMap.cluster == "0101" && descMap.attrId == "040d") { + resultMap.name = "photoCalibration" + resultMap.value = (Integer.parseInt(descMap.value, 16)) + } else if (descMap.cluster == "0101" && descMap.attrId == "040e") { + resultMap.name = "baseCurrentNE" + resultMap.value = (Integer.parseInt(descMap.value, 16)) + } + return resultMap +} + +private Map parseCustomMessage(String description) { + //log.info "ParseCustomMessage called with ${description}" + Map resultMap = [:] + if (description?.startsWith('temperature: ')) { + resultMap.name = "temperature" + def rawT = (description - "temperature: ").trim() + resultMap.descriptionText = "Temperature celsius value = ${rawT}" + def rawTint = Float.parseFloat(rawT) + if (rawTint > 65){ + resultMap.name = null + resultMap.value = null + resultMap.descriptionText = "Temperature celsius value = ${rawT} is invalid not updating" + log.warn "Invalid temperature value detected! rawT = ${rawT}, description = ${description}" + }else if (rawT == -32768){ // This number is used to indicate an error in the temperature reading + resultMap.value = "ERR" + }else{ + resultMap.value = celsiusToFahrenheit(rawT.toFloat()) as Float + sendEvent(name: "TempProb1", value: resultMap.value, displayed: false) // Workaround for lack of access to endpoint information for Temperature report + } + } + resultMap.displayed = false + log.info "Temperature reported = ${resultMap.value}" + return resultMap +} + +def parseDescriptionAsMap(description) { + (description - "read attr - ").split(",").inject([:]) { map, param -> + def nameAndValue = param.split(":") + map += [(nameAndValue[0].trim()):nameAndValue[1].trim()] + } +} + +// Added for Temeperature parse +def getFahrenheit(value) { + def celsius = Integer.parseInt(value, 16) + return celsiusToFahrenheit(celsius) as Integer +} + +// Private methods +def callUpdateStatusTxt(){ + def cTemp = device.currentState("TempProb1")?.value + def cLight = 0 + def testNull = device.currentState("currentLightLevel")?.value + if (testNull != null){ + cLight = device.currentState("currentLightLevel")?.value as int + } + updateStatusTxt(cTemp, cLight) +} + +def updateStatusTxt(currentTemp, currentLight){ + //log.info "called updateStatusTxt with ${currentTemp}, ${currentLight}" + def cTmp = currentTemp + def cLL = 10 + def oLL = 10 + + def testNull = device.currentState("closeLightLevel")?.value + if (testNull != null){ + cLL = device.currentState("closeLightLevel")?.value as int + } + + testNull = device.currentState("openLightLevel")?.value + if (testNull != null){ + oLL = device.currentState("openLightLevel")?.value as int + } + + def aOpnEn = device.currentState("autoOpenEnable")?.value + def aClsEn = device.currentState("autoCloseEnable")?.value + + if (currentLight < cLL){ + if (aOpnEn == "on"){ + sendEvent(name: "dayOrNight", value: "Sun must be > ${oLL} to auto open", displayed: false) + sendEvent(name: "coopStatus", value: "Sunlight ${currentLight} open at ${oLL}. Coop ${cTmp}°", displayed: false) + }else{ + sendEvent(name: "dayOrNight", value: "Auto Open is turned off.", displayed: false) + sendEvent(name: "coopStatus", value: "Sunlight ${currentLight} auto open off. Coop ${cTmp}°", displayed: false) + } + }else { + if (aClsEn == "on"){ + sendEvent(name: "dayOrNight", value: "Sun must be < ${cLL} to auto close", displayed: false) + sendEvent(name: "coopStatus", value: "Sunlight ${currentLight} close at ${cLL}. Coop ${cTmp}°", displayed: false) + }else{ + sendEvent(name: "dayOrNight", value: "Auto Close is turned off.", displayed: false) + sendEvent(name: "coopStatus", value: "Sunlight ${currentLight} auto close off. Coop ${cTmp}°", displayed: false) + } + } +} + +// Commands to device +def on() { + log.debug "on calling Aux1On" + Aux1On() +} + +def off() { + log.debug "off calling Aux1Off" + Aux1Off() +} + +def close() { + log.debug "close calling closeDoor" + closeDoor() +} + +def open() { + log.debug "open calling openDoor" + openDoor() +} + +def Aux1On(){ + log.debug "Sending Aux1 = on command" + "st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x14 {}" +} + +def Aux1Off(){ + log.debug "Sending Aux1 = off command" + "st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x15 {}" +} + +def Aux2On(){ + log.debug "Sending Aux2 = on command" + "st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x16 {}" +} + +def Aux2Off(){ + log.debug "Sending Aux2 = off command" + "st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x17 {}" +} + +def openDoor() { + log.debug "Sending Open command" + "st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x1 {}" +} + +def closeDoor() { + log.debug "Sending Close command" + "st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0 {}" +} + +def closeDoorHiI() { + log.debug "Sending High Current Close command" + "st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x4 {}" +} + +def autoOpenOn() { + log.debug "Setting Auto Open On" + "st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0C {}" +} + +def autoOpenOff() { + log.debug "Setting Auto Open Off" + "st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0D {}" +} + +def autoCloseOn() { + log.debug "Setting Auto Close On" + "st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0A {}" +} + +def autoCloseOff() { + log.debug "Setting Auto Close Off" + "st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0B {}" +} + +def setOpenLevelTo(cValue) { + def cX = cValue + log.debug "Setting Open Light Level to ${cX} Hex = 0x${Integer.toHexString(cX)}" + + def cmd = [] + cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x402 0x23 {${Integer.toHexString(cX)}}" + cmd << "delay 150" + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x402" // Read light value + cmd +} + +def setCloseLevelTo(cValue) { + def cX = cValue + log.debug "Setting Close Light Level to ${cX} Hex = 0x${Integer.toHexString(cX)}" + + def cmd = [] + cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x401 0x23 {${Integer.toHexString(cX)}}" + cmd << "delay 150" + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x401" // Read light value + cmd + +} + +def setSensitivityLevel(cValue) { + def cX = 100 - cValue + log.debug "Setting Door sensitivity level to ${cX} Hex = 0x${Integer.toHexString(cX)}" + + def cmd = [] + cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x408 0x23 {${Integer.toHexString(cX)}}" // Write attribute. 0x23 is a 32 bit integer value. + cmd << "delay 150" + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x408" // Read attribute + cmd +} + +def setNewBaseCurrent(cValue) { + def cX = cValue as int + log.info "Setting new BaseCurrent to ${cX} Hex = 0x${Integer.toHexString(cX)}" + + def cmd = [] + cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x409 0x23 {${Integer.toHexString(cX)}}" // Write attribute. 0x23 is a 32 bit integer value. + cmd << "delay 150" + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x409" // Read attribute + cmd +} + +def setNewPhotoCalibration(cValue) { + def cX = cValue as int + log.info "Setting new Photoresister calibration to ${cX} Hex = 0x${Integer.toHexString(cX)}" + + def cmd = [] + cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40D 0x2B {${Integer.toHexString(cX)}}" // Write attribute. 0x2B is a 32 bit signed integer value. + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40D" // Read attribute + cmd +} + +def readNewPhotoCalibration() { + log.info "Requesting current Photoresister calibration " + + def cmd = [] + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40D" // Read attribute + cmd +} + +def readBaseCurrentNE() { + log.info "Requesting base current never exceed setting " + + def cmd = [] + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40E" // Read attribute + cmd +} + +def setBaseCurrentNE(cValue) { + def cX = cValue as int + log.info "Setting new base Current Never Exceed to ${cX} Hex = 0x${Integer.toHexString(cX)}" + + def cmd = [] + cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40E 0x23 {${Integer.toHexString(cX)}}" // Write attribute. 0x23 is a 32 bit unsigned integer value. + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40E" // Read attribute + cmd +} + +def poll(){ + log.debug "Polling Device" + def cmd = [] + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0003" // Read Door State + cmd << "delay 150" + + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0400" // Read Current Light Level + cmd << "delay 150" + + cmd << "st rattr 0x${device.deviceNetworkId} 0x39 0x0402 0x0000" // Read probe 1 Temperature + cmd << "delay 150" + + cmd << "st rattr 0x${device.deviceNetworkId} 0x40 0x0402 0x0000" // Read probe 2 Temperature + + cmd +} + +def updateTemp1() { + log.debug "Sending attribute read request for Temperature Probe1" + def cmd = [] + cmd << "st rattr 0x${device.deviceNetworkId} 0x39 0x0402 0x0000" // Read Current Temperature from Coop Probe 1 + cmd +} + +def updateTemp2() { + log.debug "Sending attribute read request for Temperature Probe2" + def cmd = [] + cmd << "st rattr 0x${device.deviceNetworkId} 0x40 0x0402 0x0000" // Read Current Temperature from Coop Probe 2 + cmd +} + + +def updateSun() { + log.debug "Sending attribute read request for Sun Light Level" + def cmd = [] + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0400" // Read Current Light Level + cmd +} + +def updateSensitivity() { + log.debug "Sending attribute read request for door sensitivity" + def cmd = [] + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0408" // Read Door sensitivity + cmd +} + +def updateCloseLightLevel() { + log.debug "Sending attribute read close light level" + def cmd = [] + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0401" + cmd +} + +def updateOpenLightLevel() { + log.debug "Sending attribute read open light level" + def cmd = [] + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0402" + cmd +} + +def refresh() { + log.debug "sending refresh command" + def cmd = [] + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0003" // Read Door State + cmd << "delay 150" + + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0400" // Read Current Light Level + cmd << "delay 150" + + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0401" // Read Door Close Light Level + cmd << "delay 150" + + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0402" // Read Door Open Light Level + cmd << "delay 150" + + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0403" // Read Auto Door Close Settings + cmd << "delay 150" + + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0404" // Read Auto Door Open Settings + cmd << "delay 150" + + cmd << "st rattr 0x${device.deviceNetworkId} 0x39 0x0402 0x0000" // Read Current Temperature from Coop Probe 1 + cmd << "delay 150" + + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0408" // Object detection sensitivity + cmd << "delay 150" + + cmd << "st rattr 0x${device.deviceNetworkId} 0x40 0x0402 0x0000" // Read Current Temperature from Coop Probe 2 + cmd << "delay 150" + + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0405" // Current required to close door + cmd << "delay 150" + + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x040B" // Aux1 Status + cmd << "delay 150" + + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x040C" // Aux2 Status + cmd << "delay 150" + + cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x409" // Read Base current + + cmd +} + +def configure() { + log.debug "Binding SEP 0x38 DEP 0x01 Cluster 0x0101 Lock cluster to hub" + log.debug "Binding SEP 0x39 DEP 0x01 Cluster 0x0402 Temperature cluster to hub" + log.debug "Binding SEP 0x40 DEP 0x01 Cluster 0x0402 Temperature cluster to hub" + + def cmd = [] + cmd << "zdo bind 0x${device.deviceNetworkId} 0x38 0x01 0x0101 {${device.zigbeeId}} {}" // Bind to end point 0x38 and the lock cluster + cmd << "delay 150" + cmd << "zdo bind 0x${device.deviceNetworkId} 0x39 0x01 0x0402 {${device.zigbeeId}} {}" // Bind to end point 0x39 and the temperature cluster + cmd << "delay 150" + cmd << "zdo bind 0x${device.deviceNetworkId} 0x40 0x01 0x0402 {${device.zigbeeId}} {}" // Bind to end point 0x40 and the temperature cluster + cmd << "delay 1500" + + log.info "Sending ZigBee Configuration Commands to Coop Control" + return cmd + refresh() +} + + diff --git a/smartapps/johnrucker/door-jammed-notification.src/door-jammed-notification.groovy b/smartapps/johnrucker/door-jammed-notification.src/door-jammed-notification.groovy new file mode 100644 index 0000000..b44ddcf --- /dev/null +++ b/smartapps/johnrucker/door-jammed-notification.src/door-jammed-notification.groovy @@ -0,0 +1,66 @@ +/** + * Door Jammed Notification + * + * Copyright 2015 John Rucker + * + * 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: "Door Jammed Notification", + namespace: "JohnRucker", + author: "John.Rucker@Solar-current.com", + description: "Sends a SmartThings notification and text messages when your CoopBoss detects a door jam.", + category: "My Apps", + iconUrl: "http://coopboss.com/images/SmartThingsIcons/coopbossLogo.png", + iconX2Url: "http://coopboss.com/images/SmartThingsIcons/coopbossLogo2x.png", + iconX3Url: "http://coopboss.com/images/SmartThingsIcons/coopbossLogo3x.png") + +preferences { + section("When the door state changes") { + paragraph "Send a SmartThings notification when the coop's door jammed and did not close." + input "doorSensor", "capability.doorControl", title: "Select CoopBoss", required: true, multiple: false + input("recipients", "contact", title: "Recipients", description: "Send notifications to") { + input "phone", "phone", title: "Phone number?", required: true} + } +} + +def installed() { + log.debug "Installed with settings: ${settings}" + initialize() +} + +def updated() { + log.debug "Updated with settings: ${settings}" + unsubscribe() + initialize() +} + +def initialize() { + subscribe(doorSensor, "doorState", coopDoorStateHandler) +} + +def coopDoorStateHandler(evt) { + if (evt.value == "jammed"){ + def msg = "WARNING ${doorSensor.displayName} door is jammed and did not close!" + log.debug "WARNING ${doorSensor.displayName} door is jammed and did not close, texting $phone" + + if (location.contactBookEnabled) { + sendNotificationToContacts(msg, recipients) + } + else { + sendPush(msg) + if (phone) { + sendSms(phone, msg) + } + } + } +} \ No newline at end of file diff --git a/smartapps/johnrucker/door-state-to-color-light-hue-bulb.src/door-state-to-color-light-hue-bulb.groovy b/smartapps/johnrucker/door-state-to-color-light-hue-bulb.src/door-state-to-color-light-hue-bulb.groovy new file mode 100644 index 0000000..db42954 --- /dev/null +++ b/smartapps/johnrucker/door-state-to-color-light-hue-bulb.src/door-state-to-color-light-hue-bulb.groovy @@ -0,0 +1,141 @@ +/** + * CoopBoss Door Status to color + * + * Copyright 2015 John Rucker + * + * 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: "Door State to Color Light (Hue Bulb)", + namespace: "JohnRucker", + author: "John Rucker", + description: "Change the color of your Hue bulbs based on your coop's door status.", + category: "My Apps", + iconUrl: "http://coopboss.com/images/SmartThingsIcons/coopbossLogo.png", + iconX2Url: "http://coopboss.com/images/SmartThingsIcons/coopbossLogo2x.png", + iconX3Url: "http://coopboss.com/images/SmartThingsIcons/coopbossLogo3x.png") + + +preferences { + section("When the door opens/closese...") { + paragraph "Sets a Hue bulb or bulbs to a color based on your coop's door status:\r unknown = white\r open = blue\r opening = purple\r closed = green\r closing = pink\r jammed = red\r forced close = orange." + input "doorSensor", "capability.doorControl", title: "Select CoopBoss", required: true, multiple: false + input "bulbs", "capability.colorControl", title: "pick a bulb", required: true, multiple: true + } +} + +def installed() { + log.debug "Installed with settings: ${settings}" + initialize() +} + +def updated() { + log.debug "Updated with settings: ${settings}" + unsubscribe() + initialize() +} + +def initialize() { + subscribe(doorSensor, "doorState", coopDoorStateHandler) +} + +def coopDoorStateHandler(evt) { + log.debug "${evt.descriptionText}, $evt.value" + def color = "White" + def hueColor = 100 + def saturation = 100 + Map hClr = [:] + hClr.hex = "#FFFFFF" + + switch(evt.value) { + case "open": + color = "Blue" + break; + case "opening": + color = "Purple" + break; + case "closed": + color = "Green" + break; + case "closing": + color = "Pink" + break; + case "jammed": + color = "Red" + break; + case "forced close": + color = "Orange" + break; + case "unknown": + color = "White" + break; + } + + switch(color) { + case "White": + hueColor = 52 + saturation = 19 + break; + case "Daylight": + hueColor = 53 + saturation = 91 + break; + case "Soft White": + hueColor = 23 + saturation = 56 + break; + case "Warm White": + hueColor = 20 + saturation = 80 //83 + break; + case "Blue": + hueColor = 70 + hClr.hex = "#0000FF" + break; + case "Green": + hueColor = 39 + hClr.hex = "#00FF00" + break; + case "Yellow": + hueColor = 25 + hClr.hex = "#FFFF00" + break; + case "Orange": + hueColor = 10 + hClr.hex = "#FF6000" + break; + case "Purple": + hueColor = 75 + hClr.hex = "#BF7FBF" + break; + case "Pink": + hueColor = 83 + hClr.hex = "#FF5F5F" + break; + case "Red": + hueColor = 100 + hClr.hex = "#FF0000" + break; + } + + //bulbs*.on() + bulbs*.setHue(hueColor) + bulbs*.setSaturation(saturation) + bulbs*.setColor(hClr) + + //bulbs.each{ + //it.on() // Turn the bulb on when open (this method does not come directly from the colorControl capability) + //it.setLevel(100) // Make sure the light brightness is 100% + //it.setHue(hueColor) + //it.setSaturation(saturation) + //} +} \ No newline at end of file