├── README.md ├── MimoLiteDoorbellDevice.groovy ├── Wackware-GE-Link-Bulb.groovy ├── better-virtual-dimmer.device.groovy ├── Better-Dimmer-Switch.device.groovy ├── Better-Dimmer2.device.groovy ├── fibaro-door-window-sensor.groovy ├── fibaro-flood-sensor.groovy ├── Better-Thermostat.device.groovy ├── fibaro-motion-sensor.groovy ├── fibaro-rgbw-controller.groovy ├── fibaro-rgbw-controller-test.groovy └── Fibaro-RGBW-3-beta.groovy /README.md: -------------------------------------------------------------------------------- 1 | smartthings-devices 2 | =================== 3 | 4 | Device Files 5 | -------------------------------------------------------------------------------- /MimoLiteDoorbellDevice.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * MimoLite Doorbell Device 3 | * 4 | * Copyright 2014 Todd Wackford 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | * Instructions: Once you install this device type into smartthings and have the mimolite hooked up to your 16 | * doorbell you will need to configure the mimolite. Go to the doorbell and hold the doorbell 17 | * button down. At the same time, you will need to tap the "configure" button on this device's 18 | * detail screen. After 5 seconds, you can release the doorbell button. 19 | * 20 | * Now press and release your doorbell button as you normally would. You should see the device 21 | * icon display a darker blue and the text "DING-DONG". This will display this way for 20 seconds. 22 | * 23 | * This device is best used with an app that can subscribe to the "button.pushed" event from 24 | * the device. 25 | */ 26 | metadata { 27 | // Automatically generated. Make future change here. 28 | definition (name: "MimoLite Doorbell Device", namespace: "wackford", author: "Todd Wackford") { 29 | capability "Configuration" 30 | capability "Button" 31 | capability "Polling" 32 | capability "Refresh" 33 | 34 | command "pushed" 35 | } 36 | 37 | simulator { 38 | // Simulator stuff 39 | 40 | } 41 | 42 | // UI tile definitions 43 | tiles { 44 | standardTile("button", "device.button", width: 2, height: 2) { 45 | state "default", label: ".....", action: "pushed", icon: "st.Home.home30", backgroundColor: "#B0E0E6" 46 | state "pushed", label: "Ding-Dong", icon: "st.Home.home30", backgroundColor: "#53a7c0" 47 | } 48 | standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") { 49 | state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure" 50 | } 51 | standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") { 52 | state "default", action:"refresh.refresh", icon:"st.secondary.refresh" 53 | } 54 | main (["button"]) 55 | details(["button", "configure", "refresh"]) 56 | } 57 | } 58 | 59 | def pushed() { 60 | 61 | //Uncomment the two lines below to simulate the doorbell being pushed. This is handy for connected app testing. 62 | 63 | //sendEvent( name : "button", value: "pushed", descriptionText: "$device.displayName was pressed", unit : "" ) 64 | //doorbellTimerEventHandler() 65 | } 66 | 67 | 68 | def parse(String description) { 69 | log.debug "description is: ${description}" 70 | 71 | def result = null 72 | def cmd = zwave.parse(description, [0x20: 1, 0x84: 1, 0x30: 1, 0x70: 1]) 73 | 74 | if (cmd.CMD == "7105") { //Mimo sent a power report lost power (doorbell released) 75 | //doorbellTimerEventHandler() // doesn't always fire for some users 76 | } else { 77 | sendEvent( name : "button", value : "pushed", descriptionText: "$device.displayName was pressed", unit : "" ) 78 | doorbellTimerEventHandler() 79 | } 80 | 81 | if (cmd) { 82 | result = createEvent(zwaveEvent(cmd)) 83 | } 84 | 85 | log.debug "Parse returned ${result?.descriptionText}" 86 | 87 | return result 88 | } 89 | 90 | def doorbellTimerEventHandler() { 91 | log.debug "in doorbellTimerEventHandler" 92 | runIn(15, "showButtonReleased") 93 | } 94 | 95 | def showButtonReleased() { 96 | sendEvent( name : "button", value: "default", descriptionText: "$device.displayName was released") 97 | refresh() 98 | log.debug "button is released" 99 | } 100 | 101 | def poll() { 102 | log.debug "state of button is: ${device.currentValue("button")}" 103 | sendEvent( name : "button", value: "${device.currentValue("button")}") 104 | } 105 | 106 | def refresh() { 107 | poll() 108 | } 109 | 110 | def configure() { 111 | log.debug "Configuring...." //setting up to monitor power alarm and actuator duration 112 | delayBetween([ 113 | zwave.associationV1.associationSet(groupingIdentifier:3, nodeId:[zwaveHubNodeId]).format(), 114 | zwave.configurationV1.configurationSet(configurationValue: [25], parameterNumber: 11, size: 1).format(), 115 | zwave.configurationV1.configurationGet(parameterNumber: 11).format() 116 | ]) 117 | } 118 | -------------------------------------------------------------------------------- /Wackware-GE-Link-Bulb.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * GE Link Bulb 3 | * 4 | * Copyright 2014 SmartThings 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | * Thanks to Chad Monroe @cmonroe and Patrick Stuart @pstuart 16 | * 17 | ****************************************************************************** 18 | * Changes 19 | ****************************************************************************** 20 | * 21 | * Change 1: 2014-10-10 (wackford) 22 | * Added setLevel event so subscriptions to the event will work 23 | * 24 | * 25 | */ 26 | metadata { 27 | definition (name: "Wackware GE Link Bulb", namespace: "smartthings", author: "smartthings") { 28 | 29 | capability "Actuator" 30 | capability "Configuration" 31 | capability "Refresh" 32 | capability "Sensor" 33 | capability "Switch" 34 | capability "Switch Level" 35 | 36 | fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019" 37 | } 38 | 39 | // simulator metadata 40 | simulator { 41 | // status messages 42 | status "on": "on/off: 1" 43 | status "off": "on/off: 0" 44 | 45 | // reply messages 46 | reply "zcl on-off on": "on/off: 1" 47 | reply "zcl on-off off": "on/off: 0" 48 | } 49 | 50 | // UI tile definitions 51 | tiles { 52 | standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { 53 | state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff" 54 | state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821" 55 | } 56 | standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") { 57 | state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" 58 | } 59 | controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false) { 60 | state "level", action:"switch level.setLevel" 61 | } 62 | valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") { 63 | state "level", label: 'Level ${currentValue}%' 64 | } 65 | 66 | 67 | main(["switch"]) 68 | details(["switch", "level", "levelSliderControl", "refresh"]) 69 | } 70 | } 71 | 72 | // Parse incoming device messages to generate events 73 | def parse(String description) { 74 | log.trace description 75 | if (description?.startsWith("catchall:")) { 76 | //def msg = zigbee.parse(description) 77 | //log.trace msg 78 | //log.trace "data: $msg.data" 79 | if(description?.endsWith("0100")) 80 | { 81 | def result = createEvent(name: "switch", value: "on") 82 | log.debug "Parse returned ${result?.descriptionText}" 83 | return result 84 | } 85 | if(description?.endsWith("0000")) 86 | { 87 | def result = createEvent(name: "switch", value: "off") 88 | log.debug "Parse returned ${result?.descriptionText}" 89 | return result 90 | } 91 | } 92 | if (description?.startsWith("read attr")) { 93 | log.debug description[-2..-1] 94 | def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 ) 95 | 96 | sendEvent( name: "level", value: i ) 97 | sendEvent( name: "switch.setLevel", value: i) //added to help subscribers 98 | } 99 | 100 | 101 | } 102 | 103 | def on() { 104 | log.debug "on()" 105 | sendEvent(name: "switch", value: "on") 106 | "st cmd 0x${device.deviceNetworkId} 1 6 1 {}" 107 | } 108 | 109 | def off() { 110 | log.debug "off()" 111 | sendEvent(name: "switch", value: "off") 112 | "st cmd 0x${device.deviceNetworkId} 1 6 0 {}" 113 | } 114 | 115 | def refresh() { 116 | [ 117 | "st rattr 0x${device.deviceNetworkId} 1 6 0", "delay 500", 118 | "st rattr 0x${device.deviceNetworkId} 1 8 0" 119 | ] 120 | } 121 | 122 | def setLevel(value) { 123 | log.trace "setLevel($value)" 124 | def cmds = [] 125 | 126 | if (value == 0) { 127 | sendEvent(name: "switch", value: "off") 128 | cmds << "st cmd 0x${device.deviceNetworkId} 1 8 0 {0000 0000}" 129 | } 130 | else if (device.latestValue("switch") == "off") { 131 | sendEvent(name: "switch", value: "on") 132 | } 133 | 134 | sendEvent(name: "level", value: value) 135 | def level = new BigInteger(Math.round(value * 255 / 100).toString()).toString(16) 136 | cmds << "st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} 0000}" 137 | 138 | //log.debug cmds 139 | cmds 140 | } 141 | 142 | def configure() { 143 | 144 | String zigbeeId = swapEndianHex(device.hub.zigbeeId) 145 | log.debug "Confuguring Reporting and Bindings." 146 | def configCmds = [ 147 | 148 | //Switch Reporting 149 | "zcl global send-me-a-report 6 0 0x10 0 3600 {01}", "delay 500", 150 | "send 0x${device.deviceNetworkId} 1 1", "delay 1000", 151 | 152 | //Level Control Reporting 153 | "zcl global send-me-a-report 8 0 0x20 5 3600 {0010}", "delay 200", 154 | "send 0x${device.deviceNetworkId} 1 1", "delay 1500", 155 | 156 | "zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 1000", 157 | "zdo bind 0x${device.deviceNetworkId} 1 1 8 {${device.zigbeeId}} {}", "delay 500", 158 | ] 159 | return configCmds + refresh() // send refresh cmds as part of config 160 | } 161 | 162 | 163 | 164 | private hex(value, width=2) { 165 | def s = new BigInteger(Math.round(value).toString()).toString(16) 166 | while (s.size() < width) { 167 | s = "0" + s 168 | } 169 | s 170 | } 171 | 172 | private Integer convertHexToInt(hex) { 173 | Integer.parseInt(hex,16) 174 | } 175 | 176 | private String swapEndianHex(String hex) { 177 | reverseArray(hex.decodeHex()).encodeHex() 178 | } 179 | 180 | private byte[] reverseArray(byte[] array) { 181 | int i = 0; 182 | int j = array.length - 1; 183 | byte tmp; 184 | while (j > i) { 185 | tmp = array[j]; 186 | array[j] = array[i]; 187 | array[i] = tmp; 188 | j--; 189 | i++; 190 | } 191 | return array 192 | } 193 | -------------------------------------------------------------------------------- /better-virtual-dimmer.device.groovy: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | Device: VirtualBetterDimmer.device.groovy 3 | Author: twack@wackware.net 4 | Version: 0.1 5 | Date: 2013-11-16 6 | Purpose: To implement horizontal slider and up/down toggle in virtual dimmer 7 | device. This is great device to use with "Dim With Me" app. This 8 | functionality can also be found in "BetterDimmer.app.groovy". 9 | 10 | Use License: Non-Profit Open Software License version 3.0 (NPOSL-3.0) 11 | http://opensource.org/licenses/NPOSL-3.0 12 | 13 | ****************************************************************************** 14 | * Changes 15 | ****************************************************************************** 16 | * 17 | * Change 1: 2013-11-16 (wackford) 18 | * Initial Build 19 | * 20 | * Change 2: 2014-10-10 (twackford) 21 | * Rebuilt to add metadata 22 | * 23 | * Change 3: 2014-10-12 (twackford) 24 | * Moved preferences section into metadata section 25 | * 26 | * Change 4: 2014-10-22 (twackford) 27 | * Fixed on/off override so it will work with other apps/devices 28 | * 29 | ****************************************************************************** 30 | 31 | Other Info: Special thanks to Danny Kleinman at ST for helping me get the 32 | state stuff figured out. The Android state filtering had me 33 | stumped. 34 | 35 | *****************************************************************************/ 36 | 37 | metadata { 38 | // Automatically generated. Make future change here. 39 | definition (name: "Better Virtual Dimmer", author: "todd@wackford.net") { 40 | capability "Switch" 41 | capability "Switch Level" 42 | capability "Refresh" 43 | capability "Polling" 44 | 45 | attribute "stepsize", "string" 46 | 47 | command "levelUp" 48 | command "levelDown" 49 | command "getLevel" 50 | command "dimmerOn" 51 | command "dimmerOff" 52 | } 53 | 54 | preferences { 55 | input "stepsize", "number", title: "Step Size", description: "Dimmer Step Size", defaultValue: 10, required: false, displayDuringSetup: true 56 | } 57 | 58 | tiles { 59 | 60 | standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { 61 | state "on", label:'${name}', action:"dimmerOff", icon:"st.switches.light.on", backgroundColor:"#79b821" 62 | state "off", label:'${name}', action:"dimmerOn", icon:"st.switches.light.off", backgroundColor:"#ffffff" 63 | 64 | } 65 | 66 | controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) { 67 | state "level", action:"switch level.setLevel", unit:"", backgroundColor:"#ffe71e" 68 | } 69 | 70 | valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") { 71 | state "level", label: 'Level ${currentValue}%' 72 | } 73 | 74 | standardTile("lUp", "device.switch", inactiveLabel: false,decoration: "flat", canChangeIcon: false) { 75 | state "up", label:'', action:"levelUp",icon:"st.illuminance.illuminance.bright" 76 | } 77 | 78 | standardTile("lDown", "device.switch", inactiveLabel: false,decoration: "flat", canChangeIcon: false) { 79 | state "down", label:'', action:"levelDown",icon:"st.illuminance.illuminance.light" 80 | } 81 | 82 | main(["switch"]) 83 | details(["switch","lUp","lDown","levelSliderControl","level" ,"Preferences"]) 84 | } 85 | } 86 | 87 | 88 | def initialize() { 89 | 90 | if ( !settings.stepsize ) 91 | state.stepsize = 10 92 | else 93 | state.stepsize = settings.stepsize 94 | 95 | if (!device.currentValue("level")) 96 | setLevel(100) 97 | } 98 | 99 | def parse(String description) {} 100 | 101 | def dimmerOn() { //made our own, since event was filtered by default on Android 102 | log.info "on" 103 | sendEvent(name:"switch",value:"on") 104 | } 105 | 106 | def dimmerOff() { //made our own, since event was filtered by default on Android 107 | log.info "off" 108 | sendEvent(name:"switch",value:"off") 109 | 110 | } 111 | 112 | def on() { 113 | log.info "on" 114 | sendEvent(name:"switch",value:"on") 115 | } 116 | 117 | def off() { 118 | log.info "off" 119 | sendEvent(name:"switch",value:"off") 120 | } 121 | 122 | def setLevel(val){ 123 | log.info "setLevel $val" 124 | log.info "Step Size: ${state.stepsize}" 125 | 126 | // make sure we don't drive switches past allowed values (command will hang device waiting for it to 127 | // execute. Never commes back) 128 | if (val < 0){ 129 | val = 0 130 | } 131 | 132 | if( val > 100){ 133 | val = 100 134 | } 135 | 136 | if (val == 0){ // I liked that 0 = off 137 | sendEvent(name:"level",value:val) 138 | dimmerOff() 139 | } 140 | else 141 | { 142 | dimmerOn() 143 | sendEvent(name:"level",value:val) 144 | sendEvent(name:"switch.setLevel",value:val) // had to add this to work if apps subscribed to 145 | // setLevel event. "Dim With Me" was one. 146 | } 147 | } 148 | 149 | def levelUp(){ 150 | if ( !state.stepsize ) { 151 | initialize() 152 | log.info "initialized on first up" 153 | } else { 154 | state.stepsize = settings.stepsize 155 | } 156 | 157 | def thisStep = state.stepsize as float 158 | int nextLevel = device.currentValue("level") + thisStep 159 | setLevel(nextLevel) 160 | log.info "level up $nextLevel" 161 | } 162 | 163 | def levelDown(){ 164 | if ( !state.stepsize ) { 165 | initialize() 166 | log.info "initialized on first down" 167 | } else { 168 | state.stepsize = settings.stepsize 169 | } 170 | 171 | def thisStep = state.stepsize as float 172 | int nextLevel = device.currentValue("level") - thisStep 173 | setLevel(nextLevel) 174 | log.info "level down $nextLevel" 175 | } 176 | 177 | def setLevel(val, dur){ //not called, but leave here for hue and other controllables 178 | log.info "setLevel $val, $dur" 179 | sendEvent(name:"setLevel",value:val) 180 | } 181 | 182 | def getLevel(){ //not called, dunno why but I'll leave it for later playing 183 | log.info device.currentValue("level") 184 | log.info device.currentValue("switch") 185 | } 186 | 187 | def poll() { 188 | log.info "poll" 189 | } 190 | 191 | def refresh() { 192 | log.info "refresh" 193 | } 194 | -------------------------------------------------------------------------------- /Better-Dimmer-Switch.device.groovy: -------------------------------------------------------------------------------- 1 | /* Better-Dimmer-Switch.device.groovy 2 | * 3 | * Variation of the stock SmartThings "Dimmer-Switch" 4 | * 5 | * Device type orients the slider to horizontal versus vertical and 6 | * adds increment up and down buttons. Hardcoded to increment by 10. 7 | * 8 | * I also added the ability to use photo/picture as background. 9 | * 10 | * To use you must have IDE access on your acount. Add a new device 11 | * type and add the custom commands: 12 | * levelUp 13 | * levelDown 14 | * Replace the starter code with this code and save the file. Go into 15 | * "My devices" and select the dimmer you want to change. Select "Edit" 16 | * and then change the "Type" to use this device type. 17 | * 18 | * Happy Hacking! 19 | * 20 | * twack@wackware.net 21 | * 20140208 22 | * 23 | */ 24 | 25 | 26 | metadata { 27 | simulator { 28 | status "on": "command: 2003, payload: FF" 29 | status "off": "command: 2003, payload: 00" 30 | status "09%": "command: 2003, payload: 09" 31 | status "10%": "command: 2003, payload: 0A" 32 | status "33%": "command: 2003, payload: 21" 33 | status "66%": "command: 2003, payload: 42" 34 | status "99%": "command: 2003, payload: 63" 35 | 36 | // reply messages 37 | reply "2001FF,delay 5000,2602": "command: 2603, payload: FF" 38 | reply "200100,delay 5000,2602": "command: 2603, payload: 00" 39 | reply "200119,delay 5000,2602": "command: 2603, payload: 19" 40 | reply "200132,delay 5000,2602": "command: 2603, payload: 32" 41 | reply "20014B,delay 5000,2602": "command: 2603, payload: 4B" 42 | reply "200163,delay 5000,2602": "command: 2603, payload: 63" 43 | } 44 | 45 | tiles { 46 | standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) { 47 | state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff" 48 | state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn" 49 | state "turningOn", label:'${name}', icon:"st.switches.switch.on", backgroundColor:"#79b821" 50 | state "turningOff", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#ffffff" 51 | } 52 | controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) { 53 | state "level", action:"switch level.setLevel" 54 | } 55 | standardTile("indicator", "device.indicatorStatus", inactiveLabel: false, decoration: "flat") { 56 | state "when off", action:"indicator.indicatorWhenOn", icon:"st.indicators.lit-when-off" 57 | state "when on", action:"indicator.indicatorNever", icon:"st.indicators.lit-when-on" 58 | state "never", action:"indicator.indicatorWhenOff", icon:"st.indicators.never-lit" 59 | } 60 | standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") { 61 | state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" 62 | } 63 | valueTile("lValue", "device.level", inactiveLabel: true, height:1, width:1, decoration: "flat") { 64 | state "levelValue", label:'${currentValue}%', unit:"", backgroundColor: "#53a7c0" 65 | } 66 | 67 | standardTile("lUp", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) { 68 | state "default", action:"levelUp", icon:"st.illuminance.illuminance.bright" 69 | } 70 | standardTile("lDown", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) { 71 | state "default", action:"levelDown", icon:"st.illuminance.illuminance.light" 72 | } 73 | 74 | main(["switch"]) 75 | details(["switch", "lUp", "lDown", "levelSliderControl", "lValue" , "refresh", "indicator"]) 76 | } 77 | } 78 | 79 | def levelUp(){ 80 | int nextLevel = device.currentValue("level") + 10.0 81 | 82 | if( nextLevel > 100){ 83 | nextLevel = 100 84 | } 85 | log.debug "Setting dimmer level up to: ${nextLevel}" 86 | delayBetween ([zwave.basicV1.basicSet(value: nextLevel).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 1500) 87 | } 88 | 89 | def levelDown(){ 90 | int nextLevel = device.currentValue("level") - 10.0 91 | 92 | if (nextLevel < 5){ 93 | nextLevel = 0 94 | } 95 | 96 | if (nextLevel == 0){ 97 | off() 98 | } 99 | else 100 | { 101 | log.debug "Setting dimmer level down to: ${nextLevel}" 102 | delayBetween ([zwave.basicV1.basicSet(value: nextLevel).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 1500) 103 | } 104 | } 105 | 106 | def parse(String description) { 107 | def item1 = [ 108 | canBeCurrentState: false, 109 | linkText: getLinkText(device), 110 | isStateChange: false, 111 | displayed: false, 112 | descriptionText: description, 113 | value: description 114 | ] 115 | def result 116 | def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1]) 117 | if (cmd) { 118 | result = createEvent(cmd, item1) 119 | } 120 | else { 121 | item1.displayed = displayed(description, item1.isStateChange) 122 | result = [item1] 123 | } 124 | log.debug "Parse returned ${result?.descriptionText}" 125 | result 126 | } 127 | 128 | def createEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd, Map item1) { 129 | def result = doCreateEvent(cmd, item1) 130 | for (int i = 0; i < result.size(); i++) { 131 | result[i].type = "physical" 132 | } 133 | result 134 | } 135 | 136 | def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd, Map item1) { 137 | def result = doCreateEvent(cmd, item1) 138 | result[0].descriptionText = "${item1.linkText} is ${item1.value}" 139 | result[0].handlerName = cmd.value ? "statusOn" : "statusOff" 140 | for (int i = 0; i < result.size(); i++) { 141 | result[i].type = "digital" 142 | } 143 | result 144 | } 145 | 146 | def doCreateEvent(physicalgraph.zwave.Command cmd, Map item1) { 147 | def result = [item1] 148 | item1.name = "switch" 149 | item1.value = cmd.value ? "on" : "off" 150 | item1.handlerName = item1.value 151 | item1.descriptionText = "${item1.linkText} was turned ${item1.value}" 152 | item1.canBeCurrentState = true 153 | item1.isStateChange = isStateChange(device, item1.name, item1.value) 154 | item1.displayed = item1.isStateChange 155 | 156 | if (cmd.value >= 5) { 157 | def item2 = new LinkedHashMap(item1) 158 | item2.name = "level" 159 | item2.value = cmd.value as String 160 | item2.unit = "%" 161 | item2.descriptionText = "${item1.linkText} dimmed ${item2.value} %" 162 | item2.canBeCurrentState = true 163 | item2.isStateChange = isStateChange(device, item2.name, item2.value) 164 | item2.displayed = false 165 | result << item2 166 | } 167 | result 168 | } 169 | 170 | def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) { 171 | def value = "when off" 172 | if (cmd.configurationValue[0] == 1) {value = "when on"} 173 | if (cmd.configurationValue[0] == 2) {value = "never"} 174 | [name: "indicatorStatus", value: value, display: false] 175 | } 176 | 177 | def createEvent(physicalgraph.zwave.Command cmd, Map map) { 178 | // Handles any Z-Wave commands we aren't interested in 179 | log.debug "UNHANDLED COMMAND $cmd" 180 | } 181 | 182 | def on() { 183 | log.info "on" 184 | delayBetween([zwave.basicV1.basicSet(value: 0xFF).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000) 185 | } 186 | 187 | def off() { 188 | delayBetween ([zwave.basicV1.basicSet(value: 0x00).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000) 189 | } 190 | 191 | def setLevel(value) { 192 | log.debug "Setting the level to: ${value}" 193 | delayBetween ([zwave.basicV1.basicSet(value: value).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000) 194 | } 195 | 196 | def setLevel(value, duration) { 197 | log.debug "Firing the multi arg version" 198 | def dimmingDuration = duration < 128 ? duration : 128 + Math.round(duration / 60) 199 | zwave.switchMultilevelV2.switchMultilevelSet(value: value, dimmingDuration: dimmingDuration).format() 200 | } 201 | 202 | def poll() { 203 | zwave.switchMultilevelV1.switchMultilevelGet().format() 204 | } 205 | 206 | def refresh() { 207 | poll() 208 | } 209 | 210 | def indicatorWhenOn() { 211 | sendEvent(name: "indicatorStatus", value: "when on", display: false) 212 | zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format() 213 | } 214 | 215 | def indicatorWhenOff() { 216 | sendEvent(name: "indicatorStatus", value: "when off", display: false) 217 | zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format() 218 | } 219 | 220 | def indicatorNever() { 221 | sendEvent(name: "indicatorStatus", value: "never", display: false) 222 | zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format() 223 | } 224 | 225 | def invertSwitch(invert=true) { 226 | if (invert) { 227 | zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 4, size: 1).format() 228 | } 229 | else { 230 | zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format() 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /Better-Dimmer2.device.groovy: -------------------------------------------------------------------------------- 1 | /* Better-Dimmer2.device.groovy 2 | * 3 | * Variation of the stock SmartThings "Dimmer-Switch" 4 | * 5 | * Device type orients the slider to horizontal versus vertical and 6 | * adds increment up and down buttons. Hardcoded to increment by 10. 7 | * 8 | * I also added the ability to use photo/picture as background. 9 | * 10 | * To use you must have IDE access on your acount to install this custom device. 11 | * 12 | * Replace the starter code with this code and save the file. Go into 13 | * "My devices" and select the dimmer you want to change. Select "Edit" 14 | * and then change the "Type" to use this device type. 15 | * 16 | * Happy Hacking! 17 | * 18 | * twack@wackware.net 19 | * 20140208 20 | * 21 | */ 22 | 23 | 24 | metadata { 25 | // Automatically generated. Make future change here. 26 | definition (name: "Better Dimmer", author: "todd@wackford.net") { 27 | capability "Refresh" 28 | capability "Switch" 29 | capability "Switch Level" 30 | capability "Polling" 31 | 32 | attribute "stepsize", "string" 33 | 34 | command "levelDown" 35 | command "levelUp" 36 | } 37 | 38 | simulator { 39 | status "on": "command: 2003, payload: FF" 40 | status "off": "command: 2003, payload: 00" 41 | status "09%": "command: 2003, payload: 09" 42 | status "10%": "command: 2003, payload: 0A" 43 | status "33%": "command: 2003, payload: 21" 44 | status "66%": "command: 2003, payload: 42" 45 | status "99%": "command: 2003, payload: 63" 46 | 47 | // reply messages 48 | reply "2001FF,delay 5000,2602": "command: 2603, payload: FF" 49 | reply "200100,delay 5000,2602": "command: 2603, payload: 00" 50 | reply "200119,delay 5000,2602": "command: 2603, payload: 19" 51 | reply "200132,delay 5000,2602": "command: 2603, payload: 32" 52 | reply "20014B,delay 5000,2602": "command: 2603, payload: 4B" 53 | reply "200163,delay 5000,2602": "command: 2603, payload: 63" 54 | } 55 | 56 | 57 | preferences { 58 | input "stepsize", "number", title: "Step Size", description: "Dimmer Step Size", defaultValue: 10, required: false, displayDuringSetup: true 59 | } 60 | 61 | tiles { 62 | standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) { 63 | state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff" 64 | state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn" 65 | state "turningOn", label:'${name}', icon:"st.switches.switch.on", backgroundColor:"#79b821" 66 | state "turningOff", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#ffffff" 67 | } 68 | controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) { 69 | state "level", action:"switch level.setLevel" 70 | } 71 | standardTile("indicator", "device.indicatorStatus", inactiveLabel: false, decoration: "flat") { 72 | state "when off", action:"indicator.indicatorWhenOn", icon:"st.indicators.lit-when-off" 73 | state "when on", action:"indicator.indicatorNever", icon:"st.indicators.lit-when-on" 74 | state "never", action:"indicator.indicatorWhenOff", icon:"st.indicators.never-lit" 75 | } 76 | standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") { 77 | state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" 78 | } 79 | valueTile("lValue", "device.level", inactiveLabel: true, height:1, width:1, decoration: "flat") { 80 | state "levelValue", label:'${currentValue}%', unit:"", backgroundColor: "#53a7c0" 81 | } 82 | 83 | standardTile("lUp", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) { 84 | state "default", action:"levelUp", icon:"st.illuminance.illuminance.bright" 85 | } 86 | standardTile("lDown", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) { 87 | state "default", action:"levelDown", icon:"st.illuminance.illuminance.light" 88 | } 89 | 90 | main(["switch"]) 91 | details(["switch", "lUp", "lDown", "levelSliderControl", "lValue" , "refresh", "indicator","preferences"]) 92 | } 93 | } 94 | 95 | def initialize() { 96 | if ( !settings.stepsize ) 97 | state.stepsize = 10 98 | else 99 | state.stepsize = settings.stepsize 100 | 101 | if (!device.currentValue("level")) 102 | setLevel(100) 103 | } 104 | 105 | def levelUp(){ 106 | if ( !state.stepsize ) { 107 | initialize() 108 | log.info "initialized on first up" 109 | } else { 110 | state.stepsize = settings.stepsize 111 | } 112 | 113 | 114 | def thisStep = state.stepsize as float 115 | int nextLevel = device.currentValue("level") + thisStep 116 | 117 | if( nextLevel > 100){ 118 | nextLevel = 100 119 | } 120 | 121 | log.debug "Setting dimmer level up to: ${nextLevel}" 122 | delayBetween ([zwave.basicV1.basicSet(value: nextLevel).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 1500) 123 | 124 | //send event since most zwave devices don't publish the level event until polled 125 | sendEvent(name:"level",value:nextLevel) 126 | sendEvent(name:"switch.setLevel",value:nextLevel) 127 | } 128 | 129 | def levelDown(){ 130 | if ( !state.stepsize ) { 131 | initialize() 132 | log.info "initialized on first up" 133 | } else { 134 | state.stepsize = settings.stepsize 135 | } 136 | 137 | def thisStep = state.stepsize as float 138 | int nextLevel = device.currentValue("level") - thisStep 139 | 140 | if (nextLevel < 1){ 141 | nextLevel = 0 142 | } 143 | 144 | if (nextLevel == 0){ 145 | off() 146 | } 147 | else 148 | { 149 | log.debug "Setting dimmer level down to: ${nextLevel}" 150 | delayBetween ([zwave.basicV1.basicSet(value: nextLevel).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 1500) 151 | 152 | //send event since most zwave devices don't publish the level event until polled 153 | sendEvent(name:"level",value:nextLevel) 154 | sendEvent(name:"switch.setLevel",value:nextLevel) 155 | } 156 | } 157 | 158 | def parse(String description) { 159 | def item1 = [ 160 | canBeCurrentState: false, 161 | linkText: getLinkText(device), 162 | isStateChange: false, 163 | displayed: false, 164 | descriptionText: description, 165 | value: description 166 | ] 167 | def result 168 | def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1]) 169 | if (cmd) { 170 | result = createEvent(cmd, item1) 171 | } 172 | else { 173 | item1.displayed = displayed(description, item1.isStateChange) 174 | result = [item1] 175 | } 176 | log.debug "Parse returned ${result?.descriptionText}" 177 | result 178 | } 179 | 180 | def createEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd, Map item1) { 181 | def result = doCreateEvent(cmd, item1) 182 | for (int i = 0; i < result.size(); i++) { 183 | result[i].type = "physical" 184 | } 185 | result 186 | } 187 | 188 | def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd, Map item1) { 189 | def result = doCreateEvent(cmd, item1) 190 | result[0].descriptionText = "${item1.linkText} is ${item1.value}" 191 | result[0].handlerName = cmd.value ? "statusOn" : "statusOff" 192 | for (int i = 0; i < result.size(); i++) { 193 | result[i].type = "digital" 194 | } 195 | result 196 | } 197 | 198 | def doCreateEvent(physicalgraph.zwave.Command cmd, Map item1) { 199 | def result = [item1] 200 | item1.name = "switch" 201 | item1.value = cmd.value ? "on" : "off" 202 | item1.handlerName = item1.value 203 | item1.descriptionText = "${item1.linkText} was turned ${item1.value}" 204 | item1.canBeCurrentState = true 205 | item1.isStateChange = isStateChange(device, item1.name, item1.value) 206 | item1.displayed = item1.isStateChange 207 | 208 | if (cmd.value >= 5) { 209 | def item2 = new LinkedHashMap(item1) 210 | item2.name = "level" 211 | item2.value = cmd.value as String 212 | item2.unit = "%" 213 | item2.descriptionText = "${item1.linkText} dimmed ${item2.value} %" 214 | item2.canBeCurrentState = true 215 | item2.isStateChange = isStateChange(device, item2.name, item2.value) 216 | item2.displayed = false 217 | result << item2 218 | } 219 | result 220 | } 221 | 222 | def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) { 223 | def value = "when off" 224 | if (cmd.configurationValue[0] == 1) {value = "when on"} 225 | if (cmd.configurationValue[0] == 2) {value = "never"} 226 | [name: "indicatorStatus", value: value, display: false] 227 | } 228 | 229 | def createEvent(physicalgraph.zwave.Command cmd, Map map) { 230 | // Handles any Z-Wave commands we aren't interested in 231 | log.debug "UNHANDLED COMMAND $cmd" 232 | } 233 | 234 | def on() { 235 | log.info "on" 236 | delayBetween([zwave.basicV1.basicSet(value: 0xFF).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000) 237 | } 238 | 239 | def off() { 240 | delayBetween ([zwave.basicV1.basicSet(value: 0x00).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000) 241 | } 242 | 243 | def setLevel(value) { 244 | log.debug "Setting the level to: ${value}" 245 | 246 | //send event since most zwave devices don't publish the level event until polled 247 | sendEvent(name:"level",value:value) 248 | sendEvent(name:"switch.setLevel",value:value) 249 | 250 | delayBetween ([zwave.basicV1.basicSet(value: value).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000) 251 | } 252 | 253 | def setLevel(value, duration) { 254 | log.debug "Firing the multi arg version" 255 | def dimmingDuration = duration < 128 ? duration : 128 + Math.round(duration / 60) 256 | zwave.switchMultilevelV2.switchMultilevelSet(value: value, dimmingDuration: dimmingDuration).format() 257 | } 258 | 259 | def poll() { 260 | zwave.switchMultilevelV1.switchMultilevelGet().format() 261 | } 262 | 263 | def refresh() { 264 | poll() 265 | } 266 | 267 | def indicatorWhenOn() { 268 | sendEvent(name: "indicatorStatus", value: "when on", display: false) 269 | zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format() 270 | } 271 | 272 | def indicatorWhenOff() { 273 | sendEvent(name: "indicatorStatus", value: "when off", display: false) 274 | zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format() 275 | } 276 | 277 | def indicatorNever() { 278 | sendEvent(name: "indicatorStatus", value: "never", display: false) 279 | zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format() 280 | } 281 | 282 | def invertSwitch(invert=true) { 283 | if (invert) { 284 | zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 4, size: 1).format() 285 | } 286 | else { 287 | zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format() 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /fibaro-door-window-sensor.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Device Type Definition File 3 | * 4 | * Device Type: Fibaro Door/Window Sensor 5 | * File Name: fibaro-door-window-sensor.groovy 6 | * Initial Release: 2014-12-10 7 | * @author: Todd Wackford 8 | * Email: todd@wackford.net 9 | * @version: 1.0 10 | * 11 | * Copyright 2014 SmartThings 12 | * 13 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 14 | * in compliance with the License. You may obtain a copy of the License at: 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 19 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 20 | * for the specific language governing permissions and limitations under the License. 21 | * 22 | */ 23 | 24 | /** 25 | * Sets up metadata, simulator info and tile definition. The tamper tile is setup, but 26 | * not displayed to the user. We do this so we can receive events and display on device 27 | * activity. If the user wants to display the tamper tile, adjust the tile display lines 28 | * with the following: 29 | * main(["contact", "temperature", "tamper"]) 30 | * details(["contact", "temperature", "battery", "tamper"]) 31 | * 32 | * @param none 33 | * 34 | * @return none 35 | */ 36 | metadata { 37 | definition (name: "Fibaro Door/Window Sensor", namespace: "smartthings", author: "Todd Wackford") { 38 | capability "Temperature Measurement" 39 | capability "Contact Sensor" 40 | capability "Sensor" 41 | capability "Battery" 42 | capability "Alarm" 43 | capability "Configuration" 44 | 45 | command "resetParams2StDefaults" 46 | command "listCurrentParams" 47 | command "updateZwaveParam" 48 | command "test" 49 | 50 | fingerprint deviceId: "0x2001", inClusters: "0x30,0x9C,0x85,0x72,0x70,0x86,0x80,0x56,0x84,0x7A,0xEF,0x2B" 51 | } 52 | 53 | simulator { 54 | // messages the device returns in response to commands it receives 55 | status "open" : "command: 2001, payload: FF" 56 | status "closed": "command: 2001, payload: 00" 57 | 58 | for (int i = 0; i <= 100; i += 20) { 59 | status "temperature ${i}F": new physicalgraph.zwave.Zwave().sensorMultilevelV2.sensorMultilevelReport( 60 | scaledSensorValue: i, precision: 1, sensorType: 1, scale: 1).incomingMessage() 61 | } 62 | 63 | for (int i = 0; i <= 100; i += 20) { 64 | status "battery ${i}%": new physicalgraph.zwave.Zwave().batteryV1.batteryReport( 65 | batteryLevel: i).incomingMessage() 66 | } 67 | } 68 | 69 | tiles { 70 | standardTile("contact", "device.contact", width: 2, height: 2) { 71 | state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#ffa81e" 72 | state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821" 73 | } 74 | valueTile("temperature", "device.temperature", inactiveLabel: false) { 75 | state "temperature", label:'${currentValue}°', 76 | backgroundColors:[ 77 | [value: "", color: "#ffffff"], 78 | [value: 31, color: "#153591"], 79 | [value: 44, color: "#1e9cbb"], 80 | [value: 59, color: "#90d2a7"], 81 | [value: 74, color: "#44b621"], 82 | [value: 84, color: "#f1d801"], 83 | [value: 95, color: "#d04e00"], 84 | [value: 96, color: "#bc2323"] 85 | ] 86 | } 87 | standardTile("tamper", "device.alarm") { 88 | state("secure", label:'secure', icon:"st.locks.lock.locked", backgroundColor:"#ffffff") 89 | state("tampered", label:'tampered', icon:"st.locks.lock.unlocked", backgroundColor:"#53a7c0") 90 | } 91 | valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") { 92 | state "battery", label:'${currentValue}% battery', unit:"" 93 | } 94 | standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") { 95 | state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure" 96 | } 97 | 98 | //this will display a temperature tile for the DS18B20 sensor 99 | main(["contact", "temperature"]) //COMMENT ME OUT IF NO TEMP INSTALLED 100 | details(["contact", "temperature", "battery"]) //COMMENT ME OUT IF NO TEMP INSTALLED 101 | 102 | //this will hide the temperature tile if the DS18B20 sensor is not installed 103 | //main(["contact"]) //UNCOMMENT ME IF NO TEMP INSTALLED 104 | //details(["contact", "battery"]) //UNCOMMENT ME IF NO TEMP INSTALLED 105 | } 106 | } 107 | 108 | // Parse incoming device messages to generate events 109 | def parse(String description) 110 | { 111 | def result = [] 112 | def cmd = zwave.parse(description, [0x30: 1, 0x84: 1, 0x9C: 1, 0x70: 2, 0x80: 1, 0x72: 2, 0x56: 2, 0x60: 3]) 113 | if (cmd) { 114 | if( cmd.CMD == "8407" ) { 115 | result << new physicalgraph.device.HubAction(zwave.wakeUpV1.wakeUpNoMoreInformation().format()) 116 | } 117 | result << createEvent(zwaveEvent(cmd)) 118 | } 119 | 120 | if (description == "updated") { 121 | if (!state.MSR) { 122 | result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds: 60*60, nodeid:zwaveHubNodeId)) 123 | result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet()) 124 | result << response(zwave.batteryV1.batteryGet().format()) 125 | } 126 | } 127 | 128 | result << response(zwave.batteryV1.batteryGet().format()) 129 | 130 | if ( result[0] != null ) { 131 | log.debug "Parse returned ${result}" 132 | result 133 | } 134 | } 135 | 136 | def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) { 137 | def encapsulatedCommand = cmd.encapsulatedCommand([0x30: 2, 0x31: 2]) // can specify command class versions here like in zwave.parse 138 | log.debug ("Command from endpoint ${cmd.sourceEndPoint}: ${encapsulatedCommand}") 139 | if (encapsulatedCommand) { 140 | return zwaveEvent(encapsulatedCommand) 141 | } 142 | } 143 | 144 | // Event Generation 145 | def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) 146 | { 147 | [descriptionText: "${device.displayName} woke up", isStateChange: false] 148 | 149 | } 150 | 151 | def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd) 152 | { 153 | def map = [:] 154 | switch (cmd.sensorType) { 155 | case 1: 156 | // temperature 157 | def cmdScale = cmd.scale == 1 ? "F" : "C" 158 | map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision) 159 | map.unit = getTemperatureScale() 160 | map.name = "temperature" 161 | break; 162 | } 163 | map 164 | } 165 | 166 | def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) { 167 | def map = [:] 168 | map.name = "battery" 169 | map.value = cmd.batteryLevel > 0 ? cmd.batteryLevel.toString() : 1 170 | map.unit = "%" 171 | map.displayed = false 172 | map 173 | } 174 | 175 | def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) { 176 | def map = [:] 177 | map.value = cmd.sensorValue ? "open" : "closed" 178 | map.name = "contact" 179 | if (map.value == "closed") { 180 | map.descriptionText = "$device.displayName is closed" 181 | } 182 | else { 183 | map.descriptionText = "$device.displayName is open" 184 | } 185 | 186 | response(zwave.batteryV1.batteryGet().format()) // update battery when we open/close 187 | map 188 | } 189 | 190 | def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) { 191 | //log.debug "in Zwave BasicSet" 192 | } 193 | 194 | 195 | def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) 196 | { 197 | def map = [:] 198 | map.value = cmd.sensorState ? "tampered" : "secure" 199 | map.name = "tamper" 200 | if (map.value == "tampered") { 201 | map.descriptionText = "$device.displayName has been tampered with" 202 | } 203 | else { 204 | map.descriptionText = "$device.displayName is secure" 205 | } 206 | map 207 | } 208 | 209 | def zwaveEvent(physicalgraph.zwave.Command cmd) { 210 | log.debug "Catchall reached for cmd: ${cmd.toString()}}" 211 | [:] 212 | } 213 | 214 | def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) { 215 | if ((cmd.parameterNumber == 15) && (cmd.configurationValue[0] == 1)) { //error in temp probe 216 | sendEvent(name:"temperature",value:"-99") 217 | } 218 | 219 | if ((cmd.parameterNumber == 15) && (cmd.configurationValue[0] == 255)) { //no temp probe 220 | sendEvent(name:"temperature",value:"") 221 | } 222 | log.debug "${device.displayName} parameter '${cmd.parameterNumber}' with a byte size of '${cmd.size}' is set to '${cmd.configurationValue}'" 223 | } 224 | 225 | def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { 226 | def result = [] 227 | 228 | def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId) 229 | log.debug "msr: $msr" 230 | device.updateDataValue(["MSR", msr]) 231 | 232 | if ( msr == "010F-0700-2000" ) { //this is the msr and device type for the fibaro motion sensor 233 | configure() 234 | } 235 | 236 | result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false) 237 | result 238 | } 239 | 240 | /** 241 | * Configures the device to settings needed by SmarthThings at device discovery time. 242 | * 243 | * @param none 244 | * 245 | * @return none 246 | */ 247 | def configure() { 248 | log.debug "Configuring Device..." 249 | def cmds = [] 250 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,0], parameterNumber: 1, size: 2).format() 251 | // send associate to group 3 to get sensor data reported only to hub 252 | cmds << zwave.associationV2.associationSet(groupingIdentifier:3, nodeId:[zwaveHubNodeId]).format() 253 | 254 | // send associate to group 2 to get tamper alarm data reported 255 | cmds << zwave.associationV2.associationSet(groupingIdentifier:2, nodeId:[zwaveHubNodeId]).format() 256 | 257 | // turn on the tamper alarm 258 | cmds << zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 10, size: 1).format() 259 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 10).format() 260 | 261 | // temperature change sensitivity 262 | cmds << zwave.configurationV1.configurationSet(configurationValue: [4], parameterNumber: 12, size: 1).format() 263 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 12).format() 264 | 265 | // see if there is a temp probe on board and is it working 266 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 15).format() 267 | 268 | delayBetween(cmds, 500) 269 | } 270 | 271 | //used to add "test" button for simulation of user changes to parameters 272 | def test() { 273 | def params = [paramNumber:10,value:1,size:1] 274 | updateZwaveParam(params) 275 | } 276 | 277 | /** 278 | * This method will allow the user to update device parameters (behavior) from an app. 279 | * A "Zwave Tweaker" app will be developed as an interface to do this. Or the user can 280 | * write his/her own app to envoke this method. No type or value checking is done to 281 | * compare to what device capability or reaction. It is up to user to read OEM 282 | * documentation prio to envoking this method. 283 | * 284 | *

THIS IS AN ADVANCED OPERATION. USE AT YOUR OWN RISK! READ OEM DOCUMENTATION! 285 | * 286 | * @param List[paramNumber:80,value:10,size:1] 287 | * 288 | * 289 | * @return none 290 | */ 291 | def updateZwaveParam(params) { 292 | if ( params ) { 293 | def pNumber = params.paramNumber 294 | def pSize = params.size 295 | def pValue = [params.value] 296 | log.debug "Make sure device is awake and in recieve mode" 297 | log.debug "Updating ${device.displayName} parameter number '${pNumber}' with value '${pValue}' with size of '${pSize}'" 298 | 299 | def cmds = [] 300 | cmds << zwave.configurationV1.configurationSet(configurationValue: pValue, parameterNumber: pNumber, size: pSize).format() 301 | cmds << zwave.configurationV1.configurationGet(parameterNumber: pNumber).format() 302 | delayBetween(cmds, 1000) 303 | } 304 | } 305 | 306 | /** 307 | * Sets all of available Fibaro parameters back to the device defaults except for what 308 | * SmartThings needs to support the stock functionality as released. This will be 309 | * called from the "Fibaro Tweaker" or user's app. 310 | * 311 | *

THIS IS AN ADVANCED OPERATION. USE AT YOUR OWN RISK! READ OEM DOCUMENTATION! 312 | * 313 | * @param none 314 | * 315 | * @return none 316 | */ 317 | def resetParams2StDefaults() { 318 | log.debug "Resetting ${device.displayName} parameters to SmartThings compatible defaults" 319 | def cmds = [] 320 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,0], parameterNumber: 1, size: 2).format() 321 | cmds << zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 2, size: 1).format() 322 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format() 323 | cmds << zwave.configurationV1.configurationSet(configurationValue: [255], parameterNumber: 5, size: 1).format() 324 | cmds << zwave.configurationV1.configurationSet(configurationValue: [255], parameterNumber: 7, size: 1).format() 325 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 9, size: 1).format() 326 | cmds << zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 10, size: 1).format() //ST Custom 327 | cmds << zwave.configurationV1.configurationSet(configurationValue: [4], parameterNumber: 12, size: 1).format() //St Custom 328 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 13, size: 1).format() 329 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 14, size: 1).format() 330 | 331 | delayBetween(cmds, 500) 332 | } 333 | 334 | /** 335 | * Lists all of available Fibaro parameters and thier current settings out to the 336 | * logging window in the IDE. This will be called from the "Fibaro Tweaker" or 337 | * user's own app. 338 | * 339 | *

THIS IS AN ADVANCED OPERATION. USE AT YOUR OWN RISK! READ OEM DOCUMENTATION! 340 | * 341 | * @param none 342 | * 343 | * @return none 344 | */ 345 | def listCurrentParams() { 346 | log.debug "Listing of current parameter settings of ${device.displayName}" 347 | def cmds = [] 348 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 1).format() 349 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 2).format() 350 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 3).format() 351 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 5).format() 352 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 7).format() 353 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 9).format() 354 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 10).format() 355 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 12).format() 356 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 13).format() 357 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 14).format() 358 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 15).format() 359 | 360 | delayBetween(cmds, 500) 361 | } 362 | -------------------------------------------------------------------------------- /fibaro-flood-sensor.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Device Type Definition File 3 | * 4 | * Device Type: Fibaro Flood Sensor 5 | * File Name: fibaro-flood-sensor.groovy 6 | * Initial Release: 2014-12-10 7 | * @author: Todd Wackford 8 | * Email: todd@wackford.net 9 | * @version: 1.0 10 | * 11 | * Copyright 2014 SmartThings 12 | * 13 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 14 | * in compliance with the License. You may obtain a copy of the License at: 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 19 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 20 | * for the specific language governing permissions and limitations under the License. 21 | * 22 | */ 23 | 24 | /** 25 | * Sets up metadata, simulator info and tile definition. The tamper tile is setup, but 26 | * not displayed to the user. We do this so we can receive events and display on device 27 | * activity. If the user wants to display the tamper tile, adjust the tile display lines 28 | * with the following: 29 | * main(["water", "temperature", "tamper"]) 30 | * details(["water", "temperature", "battery", "tamper"]) 31 | * 32 | * @param none 33 | * 34 | * @return none 35 | */ 36 | metadata { 37 | definition (name: "Fibaro Flood Sensor", namespace: "smartthings", author: "Todd Wackford") { 38 | capability "Water Sensor" 39 | capability "Temperature Measurement" 40 | capability "Acceleration Sensor" 41 | capability "Configuration" 42 | capability "Sensor" 43 | capability "Battery" 44 | 45 | command "resetParams2StDefaults" 46 | command "listCurrentParams" 47 | command "updateZwaveParam" 48 | command "test" 49 | 50 | fingerprint deviceId: "0xA102", inClusters: "0x30,0x9C,0x60,0x85,0x8E,0x72,0x70,0x86,0x80,0x84" 51 | } 52 | 53 | simulator { 54 | // messages the device returns in response to commands it receives 55 | status "motion (basic)" : "command: 2001, payload: FF" 56 | status "no motion (basic)" : "command: 2001, payload: 00" 57 | 58 | for (int i = 0; i <= 100; i += 20) { 59 | status "temperature ${i}F": new physicalgraph.zwave.Zwave().sensorMultilevelV2.sensorMultilevelReport( 60 | scaledSensorValue: i, precision: 1, sensorType: 1, scale: 1).incomingMessage() 61 | } 62 | 63 | for (int i = 200; i <= 1000; i += 200) { 64 | status "luminance ${i} lux": new physicalgraph.zwave.Zwave().sensorMultilevelV2.sensorMultilevelReport( 65 | scaledSensorValue: i, precision: 0, sensorType: 3).incomingMessage() 66 | } 67 | 68 | for (int i = 0; i <= 100; i += 20) { 69 | status "battery ${i}%": new physicalgraph.zwave.Zwave().batteryV1.batteryReport( 70 | batteryLevel: i).incomingMessage() 71 | } 72 | } 73 | 74 | tiles { 75 | standardTile("water", "device.waterSensor", width: 2, height: 2) { 76 | state "dry", icon:"st.alarm.water.dry", backgroundColor:"#ffffff" 77 | state "wet", icon:"st.alarm.water.wet", backgroundColor:"#53a7c0" 78 | } 79 | valueTile("temperature", "device.temperature", inactiveLabel: false) { 80 | state "temperature", label:'${currentValue}°', 81 | backgroundColors:[ 82 | [value: 31, color: "#153591"], 83 | [value: 44, color: "#1e9cbb"], 84 | [value: 59, color: "#90d2a7"], 85 | [value: 74, color: "#44b621"], 86 | [value: 84, color: "#f1d801"], 87 | [value: 95, color: "#d04e00"], 88 | [value: 96, color: "#bc2323"] 89 | ] 90 | } 91 | standardTile("tamper", "device.tamper") { 92 | state("secure", label:"secure", icon:"st.locks.lock.locked", backgroundColor:"#ffffff") 93 | state("tampered", label:"tampered", icon:"st.locks.lock.unlocked", backgroundColor:"#53a7c0") 94 | } 95 | valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") { 96 | state "battery", label:'${currentValue}% battery', unit:"" 97 | } 98 | standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") { 99 | state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure" 100 | } 101 | 102 | main(["water", "temperature"]) 103 | details(["water", "temperature", "battery"]) 104 | } 105 | } 106 | 107 | // Parse incoming device messages to generate events 108 | def parse(String description) 109 | { 110 | def result = [] 111 | def cmd = zwave.parse(description, [0x31: 2, 0x30: 1, 0x70: 2, 0x71: 1, 0x84: 1, 0x80: 1, 0x9C: 1, 0x72: 2, 0x56: 2, 0x60: 3]) 112 | 113 | if (cmd) { 114 | if( cmd.CMD == "8407" ) { 115 | result << new physicalgraph.device.HubAction(zwave.wakeUpV1.wakeUpNoMoreInformation().format()) 116 | } 117 | result << createEvent(zwaveEvent(cmd)) 118 | } 119 | 120 | if (description == "updated") { 121 | if (!state.MSR) { 122 | result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds: 60*60, nodeid:zwaveHubNodeId)) 123 | result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet()) 124 | } 125 | } 126 | 127 | result << response(zwave.batteryV1.batteryGet().format()) 128 | 129 | if ( result[0] != null ) { 130 | log.debug "Parse returned ${result}" 131 | result 132 | } 133 | } 134 | 135 | 136 | def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) { 137 | def encapsulatedCommand = cmd.encapsulatedCommand([0x30: 2, 0x31: 2]) // can specify command class versions here like in zwave.parse 138 | log.debug ("Command from endpoint ${cmd.sourceEndPoint}: ${encapsulatedCommand}") 139 | if (encapsulatedCommand) { 140 | return zwaveEvent(encapsulatedCommand) 141 | } 142 | } 143 | 144 | // Event Generation 145 | def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) 146 | { 147 | def result = [createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)] 148 | result << response(zwave.batteryV1.batteryGet()) 149 | 150 | result 151 | } 152 | 153 | def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd) 154 | { 155 | def map = [:] 156 | switch (cmd.sensorType) { 157 | case 1: 158 | // temperature 159 | def cmdScale = cmd.scale == 1 ? "F" : "C" 160 | map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision) 161 | map.unit = getTemperatureScale() 162 | map.name = "temperature" 163 | break; 164 | case 0: 165 | // here's our tamper alarm = acceleration 166 | map.value = cmd.sensorState == 255 ? "active" : "inactive" 167 | map.name = "acceleration" 168 | break; 169 | } 170 | map 171 | } 172 | 173 | def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) { 174 | def map = [:] 175 | map.name = "battery" 176 | map.value = cmd.batteryLevel > 0 ? cmd.batteryLevel.toString() : 1 177 | map.unit = "%" 178 | map.displayed = false 179 | map 180 | } 181 | 182 | def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) { 183 | def map = [:] 184 | map.value = cmd.sensorValue ? "active" : "inactive" 185 | map.name = "acceleration" 186 | if (map.value == "active") { 187 | map.descriptionText = "$device.displayName detected vibration" 188 | } 189 | else { 190 | map.descriptionText = "$device.displayName vibration has stopped" 191 | } 192 | map 193 | } 194 | 195 | def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) { 196 | log.debug "${device.displayName} parameter '${cmd.parameterNumber}' with a byte size of '${cmd.size}' is set to '${cmd.configurationValue}'" 197 | } 198 | 199 | def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) { 200 | log.debug "BasicSet with CMD = ${cmd}" 201 | } 202 | 203 | def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) 204 | { 205 | def map = [:] 206 | if (cmd.sensorType == 0x05) { 207 | map.name = "water" 208 | map.value = cmd.sensorState ? "wet" : "dry" 209 | map.descriptionText = "${device.displayName} is ${map.value}" 210 | 211 | } else if ( cmd.sensorType == 0) { 212 | map.name = "tamper" 213 | map.isStateChange = true 214 | map.value = cmd.sensorState ? "tampered" : "secure" 215 | map.descriptionText = "${device.displayName} has been tampered with" 216 | runIn(30, "resetTamper") //device does not send alarm cancelation 217 | 218 | } else if ( cmd.sensorType == 1) { 219 | map.name = "tamper" 220 | map.value = cmd.sensorState ? "tampered" : "secure" 221 | map.descriptionText = "${device.displayName} has been tampered with" 222 | runIn(30, "resetTamper") //device does not send alarm cancelation 223 | 224 | } else { 225 | map.descriptionText = "${device.displayName}: ${cmd}" 226 | } 227 | createEvent(map) 228 | } 229 | 230 | def resetTamper() { 231 | def map = [:] 232 | map.name = "tamper" 233 | map.value = "secure" 234 | map.descriptionText = "$device.displayName is secure" 235 | sendEvent(map) 236 | } 237 | 238 | def zwaveEvent(physicalgraph.zwave.Command cmd) { 239 | log.debug "Catchall reached for cmd: ${cmd.toString()}}" 240 | [:] 241 | } 242 | 243 | def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { 244 | def result = [] 245 | 246 | def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId) 247 | log.debug "msr: $msr" 248 | device.updateDataValue(["MSR", msr]) 249 | 250 | if ( msr == "010F-0B00-2001" ) { //this is the msr and device type for the fibaro motion sensor 251 | configure() 252 | } 253 | 254 | result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false) 255 | result 256 | } 257 | 258 | /** 259 | * Configures the device to settings needed by SmarthThings at device discovery time. 260 | * 261 | * @param none 262 | * 263 | * @return none 264 | */ 265 | def configure() { 266 | log.debug "Configuring Device..." 267 | def cmds = [] 268 | 269 | // send associate to group 2 to get alarm data 270 | cmds << zwave.associationV2.associationSet(groupingIdentifier:2, nodeId:[zwaveHubNodeId]).format() 271 | 272 | // send associate to group 3 to get sensor data reported only to hub 273 | cmds << zwave.associationV2.associationSet(groupingIdentifier:3, nodeId:[zwaveHubNodeId]).format() 274 | 275 | // temp hysteresis set to .5 degrees celcius 276 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,50], parameterNumber: 12, size: 2).format() 277 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 12).format() 278 | 279 | // reporting frequency of temps and battery set to one hour 280 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,60*60], parameterNumber: 10, size: 2).format() 281 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 10).format() 282 | 283 | delayBetween(cmds, 500) 284 | } 285 | 286 | 287 | //used to add "test" button for simulation of user changes to parameters 288 | def test() { 289 | def params = [paramNumber:12,value:4,size:1] 290 | updateZwaveParam(params) 291 | } 292 | 293 | /** 294 | * This method will allow the user to update device parameters (behavior) from an app. 295 | * A "Zwave Tweaker" app will be developed as an interface to do this. Or the user can 296 | * write his/her own app to envoke this method. No type or value checking is done to 297 | * compare to what device capability or reaction. It is up to user to read OEM 298 | * documentation prio to envoking this method. 299 | * 300 | *

THIS IS AN ADVANCED OPERATION. USE AT YOUR OWN RISK! READ OEM DOCUMENTATION! 301 | * 302 | * @param List[paramNumber:80,value:10,size:1] 303 | * 304 | * 305 | * @return none 306 | */ 307 | def updateZwaveParam(params) { 308 | if ( params ) { 309 | def pNumber = params.paramNumber 310 | def pSize = params.size 311 | def pValue = [params.value] 312 | log.debug "Make sure device is awake and in recieve mode (triple-click?)" 313 | log.debug "Updating ${device.displayName} parameter number '${pNumber}' with value '${pValue}' with size of '${pSize}'" 314 | 315 | def cmds = [] 316 | cmds << zwave.configurationV1.configurationSet(configurationValue: pValue, parameterNumber: pNumber, size: pSize).format() 317 | cmds << zwave.configurationV1.configurationGet(parameterNumber: pNumber).format() 318 | delayBetween(cmds, 1000) 319 | } 320 | } 321 | 322 | /** 323 | * Sets all of available Fibaro parameters back to the device defaults except for what 324 | * SmartThings needs to support the stock functionality as released. This will be 325 | * called from the "Fibaro Tweaker" or user's app. 326 | * 327 | *

THIS IS AN ADVANCED OPERATION. USE AT YOUR OWN RISK! READ OEM DOCUMENTATION! 328 | * 329 | * @param none 330 | * 331 | * @return none 332 | */ 333 | def resetParams2StDefaults() { 334 | log.debug "Resetting ${device.displayName} parameters to SmartThings compatible defaults" 335 | def cmds = [] 336 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,0], parameterNumber: 1, size: 2).format() 337 | cmds << zwave.configurationV1.configurationSet(configurationValue: [3], parameterNumber: 2, size: 1).format() 338 | cmds << zwave.configurationV1.configurationSet(configurationValue: [255], parameterNumber: 5, size: 1).format() 339 | cmds << zwave.configurationV1.configurationSet(configurationValue: [255], parameterNumber: 7, size: 1).format() 340 | cmds << zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 9, size: 1).format() 341 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,60*60], parameterNumber: 10, size: 2).format() 342 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,50], parameterNumber: 12, size: 2).format() 343 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 13, size: 1).format() 344 | cmds << zwave.configurationV1.configurationSet(configurationValue: [5,220], parameterNumber: 50, size: 2).format() 345 | cmds << zwave.configurationV1.configurationSet(configurationValue: [13,172], parameterNumber: 51, size: 2).format() 346 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,0,0,225], parameterNumber: 61, size: 4).format() 347 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,255,0,0], parameterNumber: 62, size: 4).format() 348 | cmds << zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 63, size: 1).format() 349 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,0], parameterNumber: 73, size: 2).format() 350 | cmds << zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 74, size: 1).format() 351 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,0], parameterNumber: 75, size: 2).format() 352 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,0], parameterNumber: 76, size: 2).format() 353 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 77, size: 1).format() 354 | 355 | delayBetween(cmds, 1200) 356 | } 357 | 358 | /** 359 | * Lists all of available Fibaro parameters and thier current settings out to the 360 | * logging window in the IDE This will be called from the "Fibaro Tweaker" or 361 | * user's own app. 362 | * 363 | *

THIS IS AN ADVANCED OPERATION. USE AT YOUR OWN RISK! READ OEM DOCUMENTATION! 364 | * 365 | * @param none 366 | * 367 | * @return none 368 | */ 369 | def listCurrentParams() { 370 | log.debug "Listing of current parameter settings of ${device.displayName}" 371 | def cmds = [] 372 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 1).format() 373 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 2).format() 374 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 5).format() 375 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 7).format() 376 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 9).format() 377 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 10).format() 378 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 12).format() 379 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 13).format() 380 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 50).format() 381 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 51).format() 382 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 61).format() 383 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 62).format() 384 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 63).format() 385 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 73).format() 386 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 74).format() 387 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 75).format() 388 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 76).format() 389 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 77).format() 390 | 391 | delayBetween(cmds, 1200) 392 | } 393 | -------------------------------------------------------------------------------- /Better-Thermostat.device.groovy: -------------------------------------------------------------------------------- 1 | /* Better-Thermostat.device.groovy 2 | * 3 | * Variation of the stock SmartThings "Zwave-Thermostat" 4 | * 5 | * Device type removes the sliders and replaces them with incremental 6 | * up and down buttons on each side of the heating and cooling setpoints. 7 | * 8 | * To use you must have IDE access on your acount. Add a new device 9 | * type and add the custom commands: 10 | * heatLevelUp 11 | * heatLevelDown 12 | * coolLevelUp 13 | * coolLevelDown 14 | * switchMode 15 | * switchFanMode 16 | * Replace the starter code with this code and save the file. Go into 17 | * "My devices" and select the thermostat you want to change. Select "Edit" 18 | * and then change the "Type" to use this device type. 19 | * 20 | * Happy Hacking! 21 | * 22 | * twack@wackware.net 23 | * 20140209 24 | * 25 | */ 26 | metadata { 27 | // Automatically generated. Make future change here. 28 | definition (name: "Better Thermostat", author: "todd@wackford.net") { 29 | capability "Temperature Measurement" 30 | capability "Refresh" 31 | capability "Thermostat" 32 | capability "Configuration" 33 | capability "Polling" 34 | 35 | command "heatLevelUp" 36 | command "heatLevelDown" 37 | command "coolLevelUp" 38 | command "coolLevelDown" 39 | command "switchMode" 40 | command "switchFanMode" 41 | } 42 | 43 | // simulator metadata 44 | simulator { 45 | status "off" : "command: 4003, payload: 00" 46 | status "heat" : "command: 4003, payload: 01" 47 | status "cool" : "command: 4003, payload: 02" 48 | status "auto" : "command: 4003, payload: 03" 49 | status "emergencyHeat" : "command: 4003, payload: 04" 50 | 51 | status "fanAuto" : "command: 4403, payload: 00" 52 | status "fanOn" : "command: 4403, payload: 01" 53 | status "fanCirculate" : "command: 4403, payload: 06" 54 | 55 | status "heat 60" : "command: 4303, payload: 01 01 3C" 56 | status "heat 68" : "command: 4303, payload: 01 01 44" 57 | status "heat 72" : "command: 4303, payload: 01 01 48" 58 | 59 | status "cool 72" : "command: 4303, payload: 02 01 48" 60 | status "cool 76" : "command: 4303, payload: 02 01 4C" 61 | status "cool 80" : "command: 4303, payload: 02 01 50" 62 | 63 | status "temp 58" : "command: 3105, payload: 01 22 02 44" 64 | status "temp 62" : "command: 3105, payload: 01 22 02 6C" 65 | status "temp 70" : "command: 3105, payload: 01 22 02 BC" 66 | status "temp 74" : "command: 3105, payload: 01 22 02 E4" 67 | status "temp 78" : "command: 3105, payload: 01 22 03 0C" 68 | status "temp 82" : "command: 3105, payload: 01 22 03 34" 69 | 70 | status "idle" : "command: 4203, payload: 00" 71 | status "heating" : "command: 4203, payload: 01" 72 | status "cooling" : "command: 4203, payload: 02" 73 | status "fan only" : "command: 4203, payload: 03" 74 | status "pending heat" : "command: 4203, payload: 04" 75 | status "pending cool" : "command: 4203, payload: 05" 76 | status "vent economizer": "command: 4203, payload: 06" 77 | 78 | // reply messages 79 | reply "2502": "command: 2503, payload: FF" 80 | } 81 | 82 | tiles { 83 | valueTile("temperature", "device.temperature", width: 2, height: 2) { 84 | state("temperature", label:'${currentValue}°', unit:'F', 85 | backgroundColors:[ 86 | [value: 31, color: "#153591"], 87 | [value: 44, color: "#1e9cbb"], 88 | [value: 59, color: "#90d2a7"], 89 | [value: 74, color: "#44b621"], 90 | [value: 84, color: "#f1d801"], 91 | [value: 95, color: "#d04e00"], 92 | [value: 96, color: "#bc2323"] 93 | ] 94 | ) 95 | } 96 | standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") { 97 | state "off", label:'', action:"switchMode", icon:"st.thermostat.heating-cooling-off" 98 | state "heat", label:'', action:"switchMode", icon:"st.thermostat.heat" 99 | state "emergencyHeat", label:'', action:"switchMode", icon:"st.thermostat.emergency-heat" 100 | state "cool", label:'', action:"switchMode", icon:"st.thermostat.cool" 101 | state "auto", label:'', action:"switchMode", icon:"st.thermostat.auto" 102 | } 103 | standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") { 104 | state "fanAuto", label:'', action:"switchFanMode", icon:"st.thermostat.fan-auto" 105 | state "fanOn", label:'', action:"switchFanMode", icon:"st.thermostat.fan-on" 106 | state "fanCirculate", label:' ', action:"switchFanMode", icon:"st.thermostat.fan-circulate" 107 | } 108 | valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, decoration: "flat") { 109 | state "heat", label:'${currentValue}° heat', unit:"F", backgroundColor:"#ffffff" 110 | } 111 | valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false, decoration: "flat") { 112 | state "cool", label:'${currentValue}° cool', unit:"F", backgroundColor:"#ffffff" 113 | } 114 | standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat") { 115 | state "default", action:"polling.poll", icon:"st.secondary.refresh" 116 | } 117 | standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") { 118 | state "configure", label:' ', action:"configuration.configure", icon:"st.secondary.configure" 119 | } 120 | standardTile("heatLevelUp", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false, decoration: "flat") { 121 | state "heatLevelUp", label:' ', action:"heatLevelUp", icon:"st.thermostat.thermostat-up" 122 | } 123 | standardTile("heatLevelDown", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false, decoration: "flat") { 124 | state "heatLevelDown", label:' ', action:"heatLevelDown", icon:"st.thermostat.thermostat-down" 125 | } 126 | standardTile("coolLevelUp", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false, decoration: "flat") { 127 | state "coolLevelUp", label:' ', action:"coolLevelUp", icon:"st.thermostat.thermostat-up" 128 | } 129 | standardTile("coolLevelDown", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false, decoration: "flat") { 130 | state "coolLevelDown", label:' ', action:"coolLevelDown", icon:"st.thermostat.thermostat-down" 131 | } 132 | 133 | main "temperature" 134 | details(["temperature", "mode", "fanMode", "heatLevelDown", "heatingSetpoint", "heatLevelUp", "coolLevelDown", "coolingSetpoint", "coolLevelUp", "refresh", "configure"]) 135 | } 136 | } 137 | 138 | def coolLevelUp(){ 139 | int nextLevel = device.currentValue("coolingSetpoint") + 1 140 | 141 | if( nextLevel > 99){ 142 | nextLevel = 99 143 | } 144 | log.debug "Setting cool set point up to: ${nextLevel}" 145 | setCoolingSetpoint(nextLevel) 146 | } 147 | 148 | def coolLevelDown(){ 149 | int nextLevel = device.currentValue("coolingSetpoint") - 1 150 | 151 | if( nextLevel < 50){ 152 | nextLevel = 50 153 | } 154 | log.debug "Setting cool set point down to: ${nextLevel}" 155 | setCoolingSetpoint(nextLevel) 156 | } 157 | 158 | def heatLevelUp(){ 159 | int nextLevel = device.currentValue("heatingSetpoint") + 1 160 | 161 | if( nextLevel > 90){ 162 | nextLevel = 90 163 | } 164 | log.debug "Setting heat set point up to: ${nextLevel}" 165 | setHeatingSetpoint(nextLevel) 166 | } 167 | 168 | def heatLevelDown(){ 169 | int nextLevel = device.currentValue("heatingSetpoint") - 1 170 | 171 | if( nextLevel < 40){ 172 | nextLevel = 40 173 | } 174 | log.debug "Setting heat set point down to: ${nextLevel}" 175 | setHeatingSetpoint(nextLevel) 176 | } 177 | 178 | def parse(String description) 179 | { 180 | def map = createEvent(zwaveEvent(zwave.parse(description, [0x42:1, 0x43:2, 0x31: 3]))) 181 | if (!map) { 182 | return null 183 | } 184 | 185 | def result = [map] 186 | if (map.isStateChange && map.name in ["heatingSetpoint","coolingSetpoint","thermostatMode"]) { 187 | def map2 = [ 188 | name: "thermostatSetpoint", 189 | unit: "F" 190 | ] 191 | if (map.name == "thermostatMode") { 192 | updateState("lastTriedMode", map.value) 193 | if (map.value == "cool") { 194 | map2.value = device.latestValue("coolingSetpoint") 195 | log.info "THERMOSTAT, latest cooling setpoint = ${map2.value}" 196 | } 197 | else { 198 | map2.value = device.latestValue("heatingSetpoint") 199 | log.info "THERMOSTAT, latest heating setpoint = ${map2.value}" 200 | } 201 | } 202 | else { 203 | def mode = device.latestValue("thermostatMode") 204 | log.info "THERMOSTAT, latest mode = ${mode}" 205 | if ((map.name == "heatingSetpoint" && mode == "heat") || (map.name == "coolingSetpoint" && mode == "cool")) { 206 | map2.value = map.value 207 | map2.unit = map.unit 208 | } 209 | } 210 | if (map2.value != null) { 211 | log.debug "THERMOSTAT, adding setpoint event: $map" 212 | result << createEvent(map2) 213 | } 214 | } else if (map.name == "thermostatFanMode" && map.isStateChange) { 215 | updateState("lastTriedFanMode", map.value) 216 | } 217 | log.debug "Parse returned $result" 218 | result 219 | } 220 | 221 | // Event Generation 222 | def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd) 223 | { 224 | def map = [:] 225 | map.value = cmd.scaledValue.toString() 226 | map.unit = cmd.scale == 1 ? "F" : "C" 227 | map.displayed = false 228 | switch (cmd.setpointType) { 229 | case 1: 230 | map.name = "heatingSetpoint" 231 | break; 232 | case 2: 233 | map.name = "coolingSetpoint" 234 | break; 235 | default: 236 | return [:] 237 | } 238 | // So we can respond with same format 239 | state.size = cmd.size 240 | state.scale = cmd.scale 241 | state.precision = cmd.precision 242 | map 243 | } 244 | 245 | def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv3.SensorMultilevelReport cmd) 246 | { 247 | def map = [:] 248 | map.value = cmd.scaledSensorValue.toString() 249 | map.unit = cmd.scale == 1 ? "F" : "C" 250 | map.name = "temperature" 251 | map 252 | } 253 | 254 | def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport cmd) 255 | { 256 | def map = [:] 257 | switch (cmd.operatingState) { 258 | case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_IDLE: 259 | map.value = "idle" 260 | break 261 | case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_HEATING: 262 | map.value = "heating" 263 | break 264 | case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_COOLING: 265 | map.value = "cooling" 266 | break 267 | case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_FAN_ONLY: 268 | map.value = "fan only" 269 | break 270 | case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_PENDING_HEAT: 271 | map.value = "pending heat" 272 | break 273 | case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_PENDING_COOL: 274 | map.value = "pending cool" 275 | break 276 | case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_VENT_ECONOMIZER: 277 | map.value = "vent economizer" 278 | break 279 | } 280 | map.name = "thermostatOperatingState" 281 | map 282 | } 283 | 284 | def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport cmd) { 285 | def map = [:] 286 | switch (cmd.mode) { 287 | case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_OFF: 288 | map.value = "off" 289 | break 290 | case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_HEAT: 291 | map.value = "heat" 292 | break 293 | case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUXILIARY_HEAT: 294 | map.value = "emergencyHeat" 295 | break 296 | case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_COOL: 297 | map.value = "cool" 298 | break 299 | case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUTO: 300 | map.value = "auto" 301 | break 302 | } 303 | map.name = "thermostatMode" 304 | map 305 | } 306 | 307 | def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport cmd) { 308 | def map = [:] 309 | switch (cmd.fanMode) { 310 | case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_AUTO_LOW: 311 | map.value = "fanAuto" 312 | break 313 | case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_LOW: 314 | map.value = "fanOn" 315 | break 316 | case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_CIRCULATION: 317 | map.value = "fanCirculate" 318 | break 319 | } 320 | map.name = "thermostatFanMode" 321 | map.displayed = false 322 | map 323 | } 324 | 325 | def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSupportedReport cmd) { 326 | def supportedModes = "" 327 | if(cmd.off) { supportedModes += "off " } 328 | if(cmd.heat) { supportedModes += "heat " } 329 | if(cmd.auxiliaryemergencyHeat) { supportedModes += "emergencyHeat " } 330 | if(cmd.cool) { supportedModes += "cool " } 331 | if(cmd.auto) { supportedModes += "auto " } 332 | 333 | updateState("supportedModes", supportedModes) 334 | } 335 | 336 | def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeSupportedReport cmd) { 337 | def supportedFanModes = "" 338 | if(cmd.auto) { supportedFanModes += "fanAuto " } 339 | if(cmd.low) { supportedFanModes += "fanOn " } 340 | if(cmd.circulation) { supportedFanModes += "fanCirculate " } 341 | 342 | updateState("supportedFanModes", supportedFanModes) 343 | } 344 | 345 | def updateState(String name, String value) { 346 | state[name] = value 347 | device.updateDataValue(name, value) 348 | } 349 | 350 | def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) { 351 | log.debug "Zwave event received: $cmd" 352 | } 353 | 354 | def zwaveEvent(physicalgraph.zwave.Command cmd) { 355 | log.warn "Unexpected zwave command $cmd" 356 | } 357 | 358 | // Command Implementations 359 | def poll() { 360 | delayBetween([ 361 | zwave.sensorMultilevelV3.sensorMultilevelGet().format(), // current temperature 362 | zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format(), 363 | zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format(), 364 | zwave.thermostatModeV2.thermostatModeGet().format(), 365 | zwave.thermostatFanModeV3.thermostatFanModeGet().format(), 366 | zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format() 367 | ], 2300) 368 | } 369 | 370 | def setHeatingSetpoint(degreesF) { 371 | setHeatingSetpoint(degreesF.toDouble()) 372 | } 373 | 374 | def setHeatingSetpoint(Double degreesF) { 375 | def p = (state.precision == null) ? 1 : state.precision 376 | delayBetween([ 377 | zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 1, scale: 1, precision: p, scaledValue: degreesF).format(), 378 | zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format() 379 | ]) 380 | } 381 | 382 | def setCoolingSetpoint(degreesF) { 383 | setCoolingSetpoint(degreesF.toDouble()) 384 | } 385 | 386 | def setCoolingSetpoint(Double degreesF) { 387 | def p = (state.precision == null) ? 1 : state.precision 388 | delayBetween([ 389 | zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 2, scale: 1, precision: p, scaledValue: degreesF).format(), 390 | zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format() 391 | ]) 392 | } 393 | 394 | def configure() { 395 | delayBetween([ 396 | zwave.thermostatModeV2.thermostatModeSupportedGet().format(), 397 | zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format(), 398 | zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format() 399 | ], 2300) 400 | } 401 | 402 | def modes() { 403 | ["off", "auto", "emergencyHeat", "heat", "cool"] 404 | } 405 | 406 | def switchMode() { 407 | def currentMode = device.currentState("thermostatMode")?.value 408 | def lastTriedMode = getDataByName("lastTriedMode") ?: currentMode ?: "off" 409 | def supportedModes = getDataByName("supportedModes") 410 | def modeOrder = modes() 411 | def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] } 412 | def nextMode = next(lastTriedMode) 413 | if (supportedModes?.contains(currentMode)) { 414 | while (!supportedModes.contains(nextMode) && nextMode != "off") { 415 | nextMode = next(nextMode) 416 | } 417 | } 418 | log.debug "Switching to mode: ${nextMode}" 419 | switchToMode(nextMode) 420 | } 421 | 422 | def switchToMode(nextMode) { 423 | def supportedModes = getDataByName("supportedModes") 424 | if(supportedModes && !supportedModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported" 425 | if (nextMode in modes()) { 426 | updateState("lastTriedMode", nextMode) 427 | return "$nextMode"() 428 | } else { 429 | log.debug("no mode method '$nextMode'") 430 | } 431 | } 432 | 433 | def switchFanMode() { 434 | def currentMode = device.currentState("thermostatFanMode")?.value 435 | def lastTriedMode = getDataByName("lastTriedFanMode") ?: currentMode ?: "off" 436 | def supportedModes = getDataByName("supportedFanModes") ?: "fanAuto fanOn" 437 | def modeOrder = ["fanAuto", "fanCirculate", "fanOn"] 438 | def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] } 439 | def nextMode = next(lastTriedMode) 440 | while (!supportedModes?.contains(nextMode) && nextMode != "fanAuto") { 441 | nextMode = next(nextMode) 442 | } 443 | switchToFanMode(nextMode) 444 | } 445 | 446 | def switchToFanMode(nextMode) { 447 | def supportedFanModes = getDataByName("supportedFanModes") 448 | if(supportedFanModes && !supportedFanModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported" 449 | 450 | def returnCommand 451 | if (nextMode == "fanAuto") { 452 | returnCommand = fanAuto() 453 | } else if (nextMode == "fanOn") { 454 | returnCommand = fanOn() 455 | } else if (nextMode == "fanCirculate") { 456 | returnCommand = fanCirculate() 457 | } else { 458 | log.debug("no fan mode '$nextMode'") 459 | } 460 | if(returnCommand) updateState("lastTriedFanMode", nextMode) 461 | returnCommand 462 | } 463 | 464 | def getDataByName(String name) { 465 | state[name] ?: device.getDataValue(name) 466 | } 467 | 468 | def getModeMap() { [ 469 | "off": 0, 470 | "heat": 1, 471 | "cool": 2, 472 | "emergency heat": 4 473 | ]} 474 | 475 | def setThermostatMode(String value) { 476 | delayBetween([ 477 | zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[value]).format(), 478 | zwave.thermostatModeV2.thermostatModeGet().format() 479 | ]) 480 | } 481 | 482 | def getFanModeMap() { [ 483 | "auto": 0, 484 | "on": 1, 485 | "circulate": 6 486 | ]} 487 | 488 | def setThermostatFanMode(String value) { 489 | delayBetween([ 490 | zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[value]).format(), 491 | zwave.thermostatFanModeV3.thermostatFanModeGet().format() 492 | ]) 493 | } 494 | 495 | def off() { 496 | delayBetween([ 497 | zwave.thermostatModeV2.thermostatModeSet(mode: 0).format(), 498 | zwave.thermostatModeV2.thermostatModeGet().format() 499 | ]) 500 | } 501 | 502 | def heat() { 503 | delayBetween([ 504 | zwave.thermostatModeV2.thermostatModeSet(mode: 1).format(), 505 | zwave.thermostatModeV2.thermostatModeGet().format() 506 | ]) 507 | } 508 | 509 | def emergencyHeat() { 510 | delayBetween([ 511 | zwave.thermostatModeV2.thermostatModeSet(mode: 4).format(), 512 | zwave.thermostatModeV2.thermostatModeGet().format() 513 | ]) 514 | } 515 | 516 | def cool() { 517 | delayBetween([ 518 | zwave.thermostatModeV2.thermostatModeSet(mode: 2).format(), 519 | zwave.thermostatModeV2.thermostatModeGet().format() 520 | ]) 521 | } 522 | 523 | def auto() { 524 | delayBetween([ 525 | zwave.thermostatModeV2.thermostatModeSet(mode: 3).format(), 526 | zwave.thermostatModeV2.thermostatModeGet().format() 527 | ]) 528 | } 529 | 530 | def fanOn() { 531 | delayBetween([ 532 | zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 1).format(), 533 | zwave.thermostatFanModeV3.thermostatFanModeGet().format() 534 | ]) 535 | } 536 | 537 | def fanAuto() { 538 | delayBetween([ 539 | zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 0).format(), 540 | zwave.thermostatFanModeV3.thermostatFanModeGet().format() 541 | ]) 542 | } 543 | 544 | def fanCirculate() { 545 | delayBetween([ 546 | zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 6).format(), 547 | zwave.thermostatFanModeV3.thermostatFanModeGet().format() 548 | ]) 549 | } 550 | -------------------------------------------------------------------------------- /fibaro-motion-sensor.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Device Type Definition File 3 | * 4 | * Device Type: Fibaro Motion Sensor 5 | * File Name: fibaro-motion-sensor.groovy 6 | * Initial Release: 2014-12-10 7 | * Author: Todd Wackford 8 | * Email: todd@wackford.net 9 | * 10 | * Copyright 2014 SmartThings 11 | * 12 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 13 | * in compliance with the License. You may obtain a copy of the License at: 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 18 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 19 | * for the specific language governing permissions and limitations under the License. 20 | * 21 | *************************************************************************************** 22 | * 23 | * Change Log: 24 | * 25 | * 1. 20150125 Todd Wackford 26 | * Incorporated Crc16Encap function to support core code changes. Duncan figured it 27 | * out as usual. 28 | * 29 | * 2. 20150125 Todd Wackford 30 | * Leaned out parse and moved most device info getting into configuration method. 31 | */ 32 | 33 | /** 34 | * Sets up metadata, simulator info and tile definition. 35 | * 36 | *

Bugs: There is a bug where it appears that ST is not checking the object name 37 | * for acceleration. So if you also have a motion object, they will step on each other 38 | * with thier 'active/inactive' state changes. "LOOK STBUG" is where future updates 39 | * need to be made once the bug is fixed. 40 | * 41 | * @param none 42 | * 43 | * @return none 44 | */ 45 | metadata { 46 | definition (name: "Fibaro Motion Sensor", namespace: "smartthings", author: "Todd Wackford") { 47 | capability "Motion Sensor" 48 | capability "Temperature Measurement" 49 | capability "Acceleration Sensor" 50 | capability "Configuration" 51 | capability "Illuminance Measurement" 52 | capability "Sensor" 53 | capability "Battery" 54 | 55 | command "resetParams2StDefaults" 56 | command "listCurrentParams" 57 | command "updateZwaveParam" 58 | command "test" 59 | command "configure" 60 | 61 | fingerprint deviceId: "0x2001", inClusters: "0x30,0x84,0x85,0x80,0x8F,0x56,0x72,0x86,0x70,0x8E,0x31,0x9C,0xEF,0x30,0x31,0x9C" 62 | } 63 | 64 | simulator { 65 | // messages the device returns in response to commands it receives 66 | status "motion (basic)" : "command: 2001, payload: FF" 67 | status "no motion (basic)" : "command: 2001, payload: 00" 68 | status "motion (binary)" : "command: 3003, payload: FF" 69 | status "no motion (binary)" : "command: 3003, payload: 00" 70 | 71 | for (int i = 0; i <= 100; i += 20) { 72 | status "temperature ${i}F": new physicalgraph.zwave.Zwave().sensorMultilevelV2.sensorMultilevelReport( 73 | scaledSensorValue: i, precision: 1, sensorType: 1, scale: 1).incomingMessage() 74 | } 75 | 76 | for (int i = 200; i <= 1000; i += 200) { 77 | status "luminance ${i} lux": new physicalgraph.zwave.Zwave().sensorMultilevelV2.sensorMultilevelReport( 78 | scaledSensorValue: i, precision: 0, sensorType: 3).incomingMessage() 79 | } 80 | 81 | for (int i = 0; i <= 100; i += 20) { 82 | status "battery ${i}%": new physicalgraph.zwave.Zwave().batteryV1.batteryReport( 83 | batteryLevel: i).incomingMessage() 84 | } 85 | } 86 | 87 | tiles { 88 | standardTile("motion", "device.motion", width: 2, height: 2) { 89 | state "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0" 90 | state "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff" 91 | } 92 | valueTile("temperature", "device.temperature", inactiveLabel: false) { 93 | state "temperature", label:'${currentValue}°', 94 | backgroundColors:[ 95 | [value: 31, color: "#153591"], 96 | [value: 44, color: "#1e9cbb"], 97 | [value: 59, color: "#90d2a7"], 98 | [value: 74, color: "#44b621"], 99 | [value: 84, color: "#f1d801"], 100 | [value: 95, color: "#d04e00"], 101 | [value: 96, color: "#bc2323"] 102 | ] 103 | } 104 | valueTile("illuminance", "device.illuminance", inactiveLabel: false) { 105 | state "luminosity", label:'${currentValue} ${unit}', unit:"lux" 106 | } 107 | valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") { 108 | state "battery", label:'${currentValue}% battery', unit:"" 109 | } 110 | standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") { 111 | state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure" 112 | } 113 | 114 | // There is a bug that does not look at the tile name. If you use 'active/inactive', it will step on 115 | // the motion sensor. Delete this tile below and uncomment the tile below this one. LOOK 4 'STBUG' to 116 | // see where else you need to update when bug is fixed. 117 | 118 | standardTile("acceleration", "device.acceleration") { 119 | state("vibration", label:'vibration', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0") 120 | state("still", label:'still', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff") 121 | } 122 | 123 | /* 124 | standardTile("acceleration", "device.acceleration") { 125 | state("active", label:'vibration', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0") 126 | state("inactive", label:'still', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff") 127 | } 128 | */ 129 | 130 | main(["motion", "temperature", "acceleration", "illuminance"]) 131 | details(["motion", "temperature", "acceleration", "battery", "illuminance", "configure"]) 132 | } 133 | } 134 | 135 | /** 136 | * Configures the device to settings needed by SmarthThings at device discovery time. 137 | * 138 | * @param none 139 | * 140 | * @return none 141 | */ 142 | def configure() { 143 | log.debug "Configuring Device For SmartThings Use" 144 | def cmds = [] 145 | 146 | // send associate to group 3 to get sensor data reported only to hub 147 | cmds << zwave.associationV2.associationSet(groupingIdentifier:3, nodeId:[zwaveHubNodeId]).format() 148 | 149 | // turn on tamper sensor with active/inactive reports (use it as an acceleration sensor) default is 0, or off 150 | cmds << zwave.configurationV1.configurationSet(configurationValue: [4], parameterNumber: 24, size: 1).format() 151 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 24).format() 152 | 153 | // temperature change report threshold (0-255 = 0.1 to 25.5C) default is 1.0 Celcius, setting to .5 Celcius 154 | cmds << zwave.configurationV1.configurationSet(configurationValue: [5], parameterNumber: 60, size: 1).format() 155 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 60).format() 156 | 157 | cmds << response(zwave.batteryV1.batteryGet()) 158 | cmds << response(zwave.versionV1.versionGet().format()) 159 | cmds << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()) 160 | cmds << response(zwave.firmwareUpdateMdV2.firmwareMdGet().format()) 161 | 162 | delayBetween(cmds, 500) 163 | } 164 | 165 | // Parse incoming device messages to generate events 166 | def parse(String description) 167 | { 168 | def result = [] 169 | def cmd = zwave.parse(description, [0x72: 2, 0x31: 2, 0x30: 1, 0x84: 1, 0x9C: 1, 0x70: 2, 0x80: 1, 0x86: 1, 0x7A: 1, 0x56: 1]) 170 | 171 | if (description == "updated") { 172 | result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds: 7200, nodeid:zwaveHubNodeId)) 173 | result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet()) 174 | } 175 | 176 | if (cmd) { 177 | if( cmd.CMD == "8407" ) { 178 | result << response(zwave.batteryV1.batteryGet().format()) 179 | result << new physicalgraph.device.HubAction(zwave.wakeUpV1.wakeUpNoMoreInformation().format()) 180 | } 181 | result << createEvent(zwaveEvent(cmd)) 182 | } 183 | 184 | if ( result[0] != null ) { 185 | log.debug "Parse returned ${result}" 186 | result 187 | } 188 | } 189 | 190 | def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) 191 | { 192 | def versions = [0x31: 2, 0x30: 1, 0x84: 1, 0x9C: 1, 0x70: 2] 193 | // def encapsulatedCommand = cmd.encapsulatedCommand(versions) 194 | def version = versions[cmd.commandClass as Integer] 195 | def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass) 196 | def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data) 197 | if (!encapsulatedCommand) { 198 | log.debug "Could not extract command from $cmd" 199 | } else { 200 | zwaveEvent(encapsulatedCommand) 201 | } 202 | } 203 | 204 | def createEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd, Map item1) { 205 | log.debug "manufacturerId: ${cmd.manufacturerId}" 206 | log.debug "manufacturerName: ${cmd.manufacturerName}" 207 | log.debug "productId: ${cmd.productId}" 208 | log.debug "productTypeId: ${cmd.productTypeId}" 209 | } 210 | 211 | def createEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd, Map item1) { 212 | updateDataValue("applicationVersion", "${cmd.applicationVersion}") 213 | log.debug "applicationVersion: ${cmd.applicationVersion}" 214 | log.debug "applicationSubVersion: ${cmd.applicationSubVersion}" 215 | log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}" 216 | log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}" 217 | log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}" 218 | } 219 | 220 | def createEvent(physicalgraph.zwave.commands.firmwareupdatemdv1.FirmwareMdReport cmd, Map item1) { 221 | log.debug "checksum: ${cmd.checksum}" 222 | log.debug "firmwareId: ${cmd.firmwareId}" 223 | log.debug "manufacturerId: ${cmd.manufacturerId}" 224 | } 225 | 226 | def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) 227 | { 228 | def map = [:] 229 | map.name = "acceleration" 230 | 231 | // LOOK STBUG 232 | // When thebug is fized (see tile definition above), uncomment the following two lines and delete the 233 | // two line below them. 234 | /* 235 | map.value = cmd.sensorState ? "active" : "inactive" //UNCOMMENT ME 236 | if (map.value == "active") { //UNCOMMENT ME 237 | */ 238 | map.value = cmd.sensorState ? "vibration" : "still" //DELETE ME 239 | if (map.value == "vibration") { //DELETE ME 240 | map.descriptionText = "$device.displayName detected vibration" 241 | } 242 | else { 243 | map.descriptionText = "$device.displayName vibration has stopped" 244 | } 245 | map 246 | } 247 | 248 | // Event Generation 249 | def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) 250 | { 251 | [descriptionText: "${device.displayName} woke up", isStateChange: false] 252 | } 253 | 254 | def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd) 255 | { 256 | def map = [:] 257 | switch (cmd.sensorType) { 258 | case 1: 259 | // temperature 260 | def cmdScale = cmd.scale == 1 ? "F" : "C" 261 | map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision) 262 | map.unit = getTemperatureScale() 263 | map.name = "temperature" 264 | break; 265 | case 3: 266 | // luminance 267 | map.value = cmd.scaledSensorValue.toInteger().toString() 268 | map.unit = "lux" 269 | map.name = "illuminance" 270 | break; 271 | } 272 | map 273 | } 274 | 275 | def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) { 276 | log.debug cmd 277 | def map = [:] 278 | map.name = "battery" 279 | map.value = cmd.batteryLevel > 0 ? cmd.batteryLevel.toString() : 1 280 | map.unit = "%" 281 | map.displayed = false 282 | map 283 | } 284 | 285 | def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) { 286 | def map = [:] 287 | map.value = cmd.sensorValue ? "active" : "inactive" 288 | map.name = "motion" 289 | if (map.value == "active") { 290 | map.descriptionText = "$device.displayName detected motion" 291 | } 292 | else { 293 | map.descriptionText = "$device.displayName motion has stopped" 294 | } 295 | map 296 | } 297 | 298 | def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) { 299 | def map = [:] 300 | map.value = cmd.value ? "active" : "inactive" 301 | map.name = "motion" 302 | if (map.value == "active") { 303 | map.descriptionText = "$device.displayName detected motion" 304 | } 305 | else { 306 | map.descriptionText = "$device.displayName motion has stopped" 307 | } 308 | map 309 | } 310 | 311 | def zwaveEvent(physicalgraph.zwave.Command cmd) { 312 | log.debug "Catchall reached for cmd: ${cmd.toString()}}" 313 | [:] 314 | } 315 | 316 | def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) { 317 | log.debug "${device.displayName} parameter '${cmd.parameterNumber}' with a byte size of '${cmd.size}' is set to '${cmd.configurationValue}'" 318 | } 319 | 320 | def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { 321 | def result = [] 322 | 323 | def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId) 324 | log.debug "msr: $msr" 325 | updateDataValue("MSR", msr) 326 | 327 | if ( msr == "010F-0800-2001" ) { //this is the msr and device type for the fibaro motion sensor 328 | configure() 329 | } 330 | 331 | result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false) 332 | result 333 | } 334 | 335 | //used to add "test" button for simulation of user changes to parameters 336 | def test() { 337 | def params = [paramNumber:80,value:10,size:1] 338 | updateZwaveParam(params) 339 | } 340 | 341 | /** 342 | * This method will allow the user to update device parameters (behavior) from an app. 343 | * A "Zwave Tweaker" app will be developed as an interface to do this. Or the user can 344 | * write his/her own app to envoke this method. No type or value checking is done to 345 | * compare to what device capability or reaction. It is up to user to read OEM 346 | * documentation prio to envoking this method. 347 | * 348 | *

THIS IS AN ADVANCED OPERATION. USE AT YOUR OWN RISK! READ OEM DOCUMENTATION! 349 | * 350 | * @param List[paramNumber:80,value:10,size:1] 351 | * 352 | * 353 | * @return none 354 | */ 355 | def updateZwaveParam(params) { 356 | if ( params ) { 357 | def pNumber = params.paramNumber 358 | def pSize = params.size 359 | def pValue = [params.value] 360 | log.debug "Make sure device is awake and in recieve mode" 361 | log.debug "Updating ${device.displayName} parameter number '${pNumber}' with value '${pValue}' with size of '${pSize}'" 362 | 363 | def cmds = [] 364 | cmds << zwave.configurationV1.configurationSet(configurationValue: pValue, parameterNumber: pNumber, size: pSize).format() 365 | cmds << zwave.configurationV1.configurationGet(parameterNumber: pNumber).format() 366 | delayBetween(cmds, 1000) 367 | } 368 | } 369 | 370 | /** 371 | * Sets all of available Fibaro parameters back to the device defaults except for what 372 | * SmartThings needs to support the stock functionality as released. This will be 373 | * called from the "Fibaro Tweaker" or user's app. 374 | * 375 | *

THIS IS AN ADVANCED OPERATION. USE AT YOUR OWN RISK! READ OEM DOCUMENTATION! 376 | * 377 | * @param none 378 | * 379 | * @return none 380 | */ 381 | def resetParams2StDefaults() { 382 | log.debug "Resetting Sensor Parameters to SmartThings Compatible Defaults" 383 | def cmds = [] 384 | cmds << zwave.configurationV1.configurationSet(configurationValue: [10], parameterNumber: 1, size: 1).format() 385 | cmds << zwave.configurationV1.configurationSet(configurationValue: [15], parameterNumber: 2, size: 1).format() 386 | cmds << zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format() 387 | cmds << zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 4, size: 1).format() 388 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,30], parameterNumber: 6, size: 2).format() 389 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 8, size: 1).format() 390 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,200], parameterNumber: 9, size: 2).format() 391 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 12, size: 1).format() 392 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 16, size: 1).format() 393 | cmds << zwave.configurationV1.configurationSet(configurationValue: [15], parameterNumber: 20, size: 1).format() 394 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,30], parameterNumber: 22, size: 2).format() 395 | cmds << zwave.configurationV1.configurationSet(configurationValue: [4], parameterNumber: 24, size: 1).format() 396 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 26, size: 1).format() 397 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,200], parameterNumber: 40, size: 2).format() 398 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,0], parameterNumber: 42, size: 2).format() 399 | cmds << zwave.configurationV1.configurationSet(configurationValue: [5], parameterNumber: 60, size: 1).format() 400 | cmds << zwave.configurationV1.configurationSet(configurationValue: [3,132], parameterNumber: 62, size: 2).format() 401 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,0], parameterNumber: 64, size: 2).format() 402 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,0], parameterNumber: 66, size: 2).format() 403 | cmds << zwave.configurationV1.configurationSet(configurationValue: [10], parameterNumber: 80, size: 1).format() 404 | cmds << zwave.configurationV1.configurationSet(configurationValue: [50], parameterNumber: 81, size: 1).format() 405 | cmds << zwave.configurationV1.configurationSet(configurationValue: [0,100], parameterNumber: 82, size: 2).format() 406 | cmds << zwave.configurationV1.configurationSet(configurationValue: [3,232], parameterNumber: 83, size: 2).format() 407 | cmds << zwave.configurationV1.configurationSet(configurationValue: [18], parameterNumber: 86, size: 1).format() 408 | cmds << zwave.configurationV1.configurationSet(configurationValue: [28], parameterNumber: 87, size: 1).format() 409 | cmds << zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 89, size: 1).format() 410 | 411 | delayBetween(cmds, 500) 412 | } 413 | 414 | /** 415 | * Lists all of available Fibaro parameters and thier current settings out to the 416 | * logging window in the IDE This will be called from the "Fibaro Tweaker" or 417 | * user's own app. 418 | * 419 | *

THIS IS AN ADVANCED OPERATION. USE AT YOUR OWN RISK! READ OEM DOCUMENTATION! 420 | * 421 | * @param none 422 | * 423 | * @return none 424 | */ 425 | def listCurrentParams() { 426 | log.debug "Listing of current parameter settings of ${device.displayName}" 427 | def cmds = [] 428 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 1).format() 429 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 2).format() 430 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 3).format() 431 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 4).format() 432 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 6).format() 433 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 8).format() 434 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 9).format() 435 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 12).format() 436 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 14).format() 437 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 16).format() 438 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 20).format() 439 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 22).format() 440 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 24).format() 441 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 26).format() 442 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 40).format() 443 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 42).format() 444 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 60).format() 445 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 62).format() 446 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 64).format() 447 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 66).format() 448 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 80).format() 449 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 81).format() 450 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 82).format() 451 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 83).format() 452 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 86).format() 453 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 87).format() 454 | cmds << zwave.configurationV1.configurationGet(parameterNumber: 89).format() 455 | 456 | delayBetween(cmds, 500) 457 | } 458 | -------------------------------------------------------------------------------- /fibaro-rgbw-controller.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Device Type Definition File 3 | * 4 | * Device Type: Fibaro RGBW Controller 5 | * File Name: fibaro-rgbw-controller.groovy 6 | * Initial Release: 2015-01-04 7 | * Author: Todd Wackford 8 | * Email: todd@wackford.net 9 | * 10 | * Copyright 2015 SmartThings 11 | * 12 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 13 | * in compliance with the License. You may obtain a copy of the License at: 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 18 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 19 | * for the specific language governing permissions and limitations under the License. 20 | * 21 | */ 22 | 23 | /* 24 | Todo's 25 | 1. Incorporate javadoc information and formatting 26 | */ 27 | 28 | metadata { 29 | 30 | definition (name: "Fibaro RGBW Controller", namespace: "smartthings", author: "Todd Wackford") { 31 | capability "Switch Level" 32 | capability "Actuator" 33 | capability "Switch" 34 | capability "Polling" 35 | capability "Refresh" 36 | capability "Sensor" 37 | capability "Configuration" 38 | capability "Color Control" 39 | capability "Power Meter" 40 | 41 | command "getDeviceData" 42 | command "softwhite" 43 | command "daylight" 44 | command "warmwhite" 45 | command "red" 46 | command "green" 47 | command "blue" 48 | command "cyan" 49 | command "magenta" 50 | command "orange" 51 | command "purple" 52 | command "yellow" 53 | command "white" 54 | command "fireplace" 55 | command "storm" 56 | command "deepfade" 57 | command "litefade" 58 | command "police" 59 | command "setAdjustedColor" 60 | command "setWhiteLevel" 61 | command "test" 62 | 63 | attribute "whiteLevel", "string" 64 | 65 | fingerprint deviceId: "0x1101", inClusters: "0x27,0x72,0x86,0x26,0x60,0x70,0x32,0x31,0x85,0x33" 66 | } 67 | 68 | simulator { 69 | status "on": "command: 2003, payload: FF" 70 | status "off": "command: 2003, payload: 00" 71 | status "09%": "command: 2003, payload: 09" 72 | status "10%": "command: 2003, payload: 0A" 73 | status "33%": "command: 2003, payload: 21" 74 | status "66%": "command: 2003, payload: 42" 75 | status "99%": "command: 2003, payload: 63" 76 | 77 | // reply messages 78 | reply "2001FF,delay 5000,2602": "command: 2603, payload: FF" 79 | reply "200100,delay 5000,2602": "command: 2603, payload: 00" 80 | reply "200119,delay 5000,2602": "command: 2603, payload: 19" 81 | reply "200132,delay 5000,2602": "command: 2603, payload: 32" 82 | reply "20014B,delay 5000,2602": "command: 2603, payload: 4B" 83 | reply "200163,delay 5000,2602": "command: 2603, payload: 63" 84 | } 85 | 86 | tiles { 87 | controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) { 88 | state "color", action:"setAdjustedColor" 89 | } 90 | controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) { 91 | state "level", action:"switch level.setLevel" 92 | } 93 | controlTile("whiteSliderControl", "device.whiteLevel", "slider", height: 1, width: 3, inactiveLabel: false) { 94 | state "whiteLevel", action:"setWhiteLevel", label:'White Level' 95 | } 96 | standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) { 97 | state "on", label:'${name}', action:"switch.off", icon:"st.illuminance.illuminance.bright", backgroundColor:"#79b821", nextState:"turningOff" 98 | state "off", label:'${name}', action:"switch.on", icon:"st.illuminance.illuminance.dark", backgroundColor:"#ffffff", nextState:"turningOn" 99 | state "turningOn", label:'${name}', icon:"st.illuminance.illuminance.bright", backgroundColor:"#79b821" 100 | state "turningOff", label:'${name}', icon:"st.illuminance.illuminance.dark", backgroundColor:"#ffffff" 101 | } 102 | valueTile("power", "device.power", decoration: "flat") { 103 | state "power", label:'${currentValue} W' 104 | } 105 | standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") { 106 | state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure" 107 | } 108 | standardTile("refresh", "device.switch", height: 1, inactiveLabel: false, decoration: "flat") { 109 | state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" 110 | } 111 | standardTile("softwhite", "device.softwhite", height: 1, inactiveLabel: false, canChangeIcon: false) { 112 | state "offsoftwhite", label:"soft white", action:"softwhite", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 113 | state "onsoftwhite", label:"soft white", action:"softwhite", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFF1E0" 114 | } 115 | standardTile("daylight", "device.daylight", height: 1, inactiveLabel: false, canChangeIcon: false) { 116 | state "offdaylight", label:"daylight", action:"daylight", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 117 | state "ondaylight", label:"daylight", action:"daylight", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFB" 118 | } 119 | standardTile("warmwhite", "device.warmwhite", height: 1, inactiveLabel: false, canChangeIcon: false) { 120 | state "offwarmwhite", label:"warm white", action:"warmwhite", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 121 | state "onwarmwhite", label:"warm white", action:"warmwhite", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFF4E5" 122 | } 123 | standardTile("red", "device.red", height: 1, inactiveLabel: false, canChangeIcon: false) { 124 | state "offred", label:"red", action:"red", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 125 | state "onred", label:"red", action:"red", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FF0000" 126 | } 127 | standardTile("green", "device.green", height: 1, inactiveLabel: false, canChangeIcon: false) { 128 | state "offgreen", label:"green", action:"green", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 129 | state "ongreen", label:"green", action:"green", icon:"st.illuminance.illuminance.bright", backgroundColor:"#00FF00" 130 | } 131 | standardTile("blue", "device.blue", height: 1, inactiveLabel: false, canChangeIcon: false) { 132 | state "offblue", label:"blue", action:"blue", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 133 | state "onblue", label:"blue", action:"blue", icon:"st.illuminance.illuminance.bright", backgroundColor:"#0000FF" 134 | } 135 | standardTile("cyan", "device.cyan", height: 1, inactiveLabel: false, canChangeIcon: false) { 136 | state "offcyan", label:"cyan", action:"cyan", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 137 | state "oncyan", label:"cyan", action:"cyan", icon:"st.illuminance.illuminance.bright", backgroundColor:"#00FFFF" 138 | } 139 | standardTile("magenta", "device.magenta", height: 1, inactiveLabel: false, canChangeIcon: false) { 140 | state "offmagenta", label:"magenta", action:"magenta", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 141 | state "onmagenta", label:"magenta", action:"magenta", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FF00FF" 142 | } 143 | standardTile("orange", "device.orange", height: 1, inactiveLabel: false, canChangeIcon: false) { 144 | state "offorange", label:"orange", action:"orange", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 145 | state "onorange", label:"orange", action:"orange", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FF6600" 146 | } 147 | standardTile("purple", "device.purple", height: 1, inactiveLabel: false, canChangeIcon: false) { 148 | state "offpurple", label:"purple", action:"purple", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 149 | state "onpurple", label:"purple", action:"purple", icon:"st.illuminance.illuminance.bright", backgroundColor:"#BF00FF" 150 | } 151 | standardTile("yellow", "device.yellow", height: 1, inactiveLabel: false, canChangeIcon: false) { 152 | state "offyellow", label:"yellow", action:"yellow", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 153 | state "onyellow", label:"yellow", action:"yellow", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFF00" 154 | } 155 | standardTile("white", "device.white", height: 1, inactiveLabel: false, canChangeIcon: false) { 156 | state "offwhite", label:"White", action:"white", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 157 | state "onwhite", label:"White", action:"white", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 158 | } 159 | standardTile("fireplace", "device.fireplace", height: 1, inactiveLabel: false, canChangeIcon: false) { 160 | state "offfireplace", label:"Fire Place", action:"fireplace", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 161 | state "onfireplace", label:"Fire Place", action:"fireplace", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 162 | } 163 | standardTile("storm", "device.storm", height: 1, inactiveLabel: false, canChangeIcon: false) { 164 | state "offstorm", label:"storm", action:"storm", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 165 | state "onstorm", label:"storm", action:"storm", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 166 | } 167 | standardTile("deepfade", "device.deepfade", height: 1, inactiveLabel: false, canChangeIcon: false) { 168 | state "offdeepfade", label:"deep fade", action:"deepfade", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 169 | state "ondeepfade", label:"deep fade", action:"deepfade", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 170 | } 171 | standardTile("litefade", "device.litefade", height: 1, inactiveLabel: false, canChangeIcon: false) { 172 | state "offlitefade", label:"lite fade", action:"litefade", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 173 | state "onlitefade", label:"lite fade", action:"litefade", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 174 | } 175 | standardTile("police", "device.police", height: 1, inactiveLabel: false, canChangeIcon: false) { 176 | state "offpolice", label:"police", action:"police", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 177 | state "onpolice", label:"police", action:"police", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 178 | } 179 | controlTile("saturationSliderControl", "device.saturation", "slider", height: 1, width: 2, inactiveLabel: false) { 180 | state "saturation", action:"color control.setSaturation" 181 | } 182 | valueTile("saturation", "device.saturation", inactiveLabel: false, decoration: "flat") { 183 | state "saturation", label: 'Sat ${currentValue} ' 184 | } 185 | controlTile("hueSliderControl", "device.hue", "slider", height: 1, width: 2, inactiveLabel: false) { 186 | state "hue", action:"color control.setHue" 187 | } 188 | valueTile("hue", "device.hue", inactiveLabel: false, decoration: "flat") { 189 | state "hue", label: 'Hue ${currentValue} ' 190 | } 191 | 192 | main(["switch"]) 193 | details(["switch", 194 | "levelSliderControl", 195 | "rgbSelector", 196 | "whiteSliderControl", 197 | "softwhite", 198 | "daylight", 199 | "warmwhite", 200 | "red", 201 | "green", 202 | "blue", 203 | "cyan", 204 | "magenta", 205 | "orange", 206 | "purple", 207 | "yellow", 208 | "white", 209 | "fireplace", 210 | "storm", 211 | "deepfade", 212 | "litefade", 213 | "police", 214 | //"power", 215 | //"configure", 216 | "refresh"]) 217 | } 218 | } 219 | 220 | def setAdjustedColor(value) { 221 | log.debug "setAdjustedColor: ${value}" 222 | 223 | toggleTiles("off") //turn off the hard color tiles 224 | 225 | def level = device.latestValue("level") 226 | value.level = level 227 | 228 | def c = hexToRgb(value.hex) 229 | value.rh = hex(c.r * (level/100)) 230 | value.gh = hex(c.g * (level/100)) 231 | value.bh = hex(c.b * (level/100)) 232 | 233 | setColor(value) 234 | } 235 | 236 | def setColor(value) { 237 | log.debug "setColor: ${value}" 238 | 239 | if (value.size() < 8) 240 | toggleTiles("off") 241 | 242 | if (( value.size() == 2) && (value.hue != null) && (value.saturation != null)) { //assuming we're being called from outside of device (App) 243 | def rgb = hslToRGB(value.hue, value.saturation, 0.5) 244 | value.hex = rgbToHex(rgb) 245 | value.rh = hex(rgb.r) 246 | value.gh = hex(rgb.g) 247 | value.bh = hex(rgb.b) 248 | } 249 | 250 | if ((value.size() == 3) && (value.hue != null) && (value.saturation != null) && (value.level)) { //user passed in a level value too from outside (App) 251 | def rgb = hslToRGB(value.hue, value.saturation, 0.5) 252 | value.hex = rgbToHex(rgb) 253 | value.rh = hex(rgb.r * value.level/100) 254 | value.gh = hex(rgb.g * value.level/100) 255 | value.bh = hex(rgb.b * value.level/100) 256 | } 257 | 258 | if (( value.size() == 1) && (value.hex)) { //being called from outside of device (App) with only hex 259 | def rgbInt = hexToRgb(value.hex) 260 | value.rh = hex(rgbInt.r) 261 | value.gh = hex(rgbInt.g) 262 | value.bh = hex(rgbInt.b) 263 | } 264 | 265 | if (( value.size() == 2) && (value.hex) && (value.level)) { //being called from outside of device (App) with only hex and level 266 | 267 | def rgbInt = hexToRgb(value.hex) 268 | value.rh = hex(rgbInt.r * value.level/100) 269 | value.gh = hex(rgbInt.g * value.level/100) 270 | value.bh = hex(rgbInt.b * value.level/100) 271 | } 272 | 273 | if (( value.size() == 1) && (value.colorName)) { //being called from outside of device (App) with only color name 274 | def colorData = getColorData(value.colorName) 275 | value.rh = colorData.rh 276 | value.gh = colorData.gh 277 | value.bh = colorData.bh 278 | value.hex = "#${value.rh}${value.gh}${value.bh}" 279 | } 280 | 281 | if (( value.size() == 2) && (value.colorName) && (value.level)) { //being called from outside of device (App) with only color name and level 282 | def colorData = getColorData(value.colorName) 283 | value.rh = hex(colorData.r * value.level/100) 284 | value.gh = hex(colorData.g * value.level/100) 285 | value.bh = hex(colorData.b * value.level/100) 286 | value.hex = "#${hex(colorData.r)}${hex(colorData.g)}${hex(colorData.b)}" 287 | } 288 | 289 | if (( value.size() == 3) && (value.red != null) && (value.green != null) && (value.blue != null)) { //being called from outside of device (App) with only color values (0-255) 290 | value.rh = hex(value.red) 291 | value.gh = hex(value.green) 292 | value.bh = hex(value.blue) 293 | value.hex = "#${value.rh}${value.gh}${value.bh}" 294 | } 295 | 296 | if (( value.size() == 4) && (value.red != null) && (value.green != null) && (value.blue != null) && (value.level)) { //being called from outside of device (App) with only color values (0-255) and level 297 | value.rh = hex(value.red * value.level/100) 298 | value.gh = hex(value.green * value.level/100) 299 | value.bh = hex(value.blue * value.level/100) 300 | value.hex = "#${hex(value.red)}${hex(value.green)}${hex(value.blue)}" 301 | } 302 | 303 | sendEvent(name: "hue", value: value.hue, displayed: false) 304 | sendEvent(name: "saturation", value: value.saturation, displayed: false) 305 | sendEvent(name: "color", value: value.hex, displayed: false) 306 | if (value.level) { 307 | sendEvent(name: "level", value: value.level) 308 | } 309 | if (value.switch) { 310 | sendEvent(name: "switch", value: value.switch) 311 | } 312 | 313 | sendRGB(value.rh, value.gh, value.bh) 314 | } 315 | 316 | def setLevel(level) { 317 | log.trace "setLevel($level)" 318 | 319 | if (level == 0) { off() } 320 | else if (device.latestValue("switch") == "off") { on() } 321 | 322 | def colorHex = device.latestValue("color") 323 | def c = hexToRgb(colorHex) 324 | 325 | def r = hex(c.r * (level/100)) 326 | def g = hex(c.g * (level/100)) 327 | def b = hex(c.b * (level/100)) 328 | 329 | sendEvent(name: "level", value: level) 330 | sendEvent(name: "setLevel", value: level, displayed: false) 331 | sendRGB(r, g, b) 332 | } 333 | 334 | 335 | def setWhiteLevel(value) { 336 | log.debug "setWhiteLevel: ${value}" 337 | def level = Math.min(value as Integer, 99) 338 | level = 255 * level/99 as Integer 339 | def channel = 0 340 | 341 | sendEvent(name: "whiteLevel", value: value) 342 | sendWhite(channel, value) 343 | } 344 | 345 | def sendWhite(channel, value) { 346 | def whiteLevel = hex(value) 347 | def cmd = [String.format("3305010${channel}${whiteLevel}%02X", 50)] 348 | cmd 349 | } 350 | 351 | def sendRGB(redHex, greenHex, blueHex) { 352 | def cmd = [String.format("33050302${redHex}03${greenHex}04${blueHex}%02X", 100),] 353 | cmd 354 | } 355 | 356 | 357 | def configure() { 358 | log.debug "Configuring Device For SmartThings Use" 359 | def cmds = [] 360 | 361 | // send associate to group 3 to get sensor data reported only to hub 362 | cmds << zwave.associationV2.associationSet(groupingIdentifier:5, nodeId:[zwaveHubNodeId]).format() 363 | 364 | delayBetween(cmds, 500) 365 | } 366 | 367 | def parse(String description) { 368 | //log.debug "description: ${description}" 369 | def item1 = [ 370 | canBeCurrentState: false, 371 | linkText: getLinkText(device), 372 | isStateChange: false, 373 | displayed: false, 374 | descriptionText: description, 375 | value: description 376 | ] 377 | def result 378 | def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 2, 0x72: 2, 0x60: 3, 0x33: 2, 0x32: 3, 0x31:2, 0x30: 2, 0x86: 1, 0x7A: 1]) 379 | 380 | if (cmd) { 381 | if ( cmd.CMD != "7006" ) { 382 | result = createEvent(cmd, item1) 383 | } 384 | } 385 | else { 386 | item1.displayed = displayed(description, item1.isStateChange) 387 | result = [item1] 388 | } 389 | //log.debug "Parse returned ${result?.descriptionText}" 390 | result 391 | } 392 | 393 | def getDeviceData() { 394 | def cmd = [] 395 | 396 | cmd << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet()) 397 | cmd << response(zwave.versionV1.versionGet()) 398 | cmd << response(zwave.firmwareUpdateMdV1.firmwareMdGet()) 399 | 400 | delayBetween(cmd, 500) 401 | } 402 | 403 | def createEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd, Map item1) { 404 | log.debug "manufacturerName: ${cmd.manufacturerName}" 405 | log.debug "manufacturerId: ${cmd.manufacturerId}" 406 | log.debug "productId: ${cmd.productId}" 407 | log.debug "productTypeId: ${cmd.productTypeId}" 408 | } 409 | 410 | def createEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd, Map item1) { 411 | updateDataValue("applicationVersion", "${cmd.applicationVersion}") 412 | log.debug "applicationVersion: ${cmd.applicationVersion}" 413 | log.debug "applicationSubVersion: ${cmd.applicationSubVersion}" 414 | log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}" 415 | log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}" 416 | log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}" 417 | } 418 | 419 | def createEvent(physicalgraph.zwave.commands.firmwareupdatemdv1.FirmwareMdReport cmd, Map item1) { 420 | log.debug "checksum: ${cmd.checksum}" 421 | log.debug "firmwareId: ${cmd.firmwareId}" 422 | log.debug "manufacturerId: ${cmd.manufacturerId}" 423 | } 424 | 425 | def zwaveEvent(physicalgraph.zwave.commands.colorcontrolv1.CapabilityReport cmd, Map item1) { 426 | 427 | log.debug "In CapabilityReport" 428 | } 429 | 430 | def createEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd, Map item1) { 431 | def encapsulatedCommand = cmd.encapsulatedCommand([0x26: 1, 0x30: 2, 0x32: 2, 0x33: 2]) // can specify command class versions here like in zwave.parse 432 | //log.debug ("Command from endpoint ${cmd.sourceEndPoint}: ${encapsulatedCommand}") 433 | if ((cmd.sourceEndPoint >= 1) && (cmd.sourceEndPoint <= 5)) { // we don't need color report 434 | //don't do anything 435 | } else { 436 | if (encapsulatedCommand) { 437 | zwaveEvent(encapsulatedCommand) 438 | } 439 | } 440 | } 441 | 442 | def createEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd, Map item1) { 443 | def result = doCreateEvent(cmd, item1) 444 | for (int i = 0; i < result.size(); i++) { 445 | result[i].type = "physical" 446 | } 447 | result 448 | } 449 | 450 | def createEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd, Map item1) { 451 | def result = doCreateEvent(cmd, item1) 452 | for (int i = 0; i < result.size(); i++) { 453 | result[i].type = "physical" 454 | } 455 | result 456 | } 457 | 458 | def createEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd, Map item1) { 459 | def result = [:] 460 | if ( cmd.sensorType == 4 ) { //power level comming in 461 | result.name = "power" 462 | result.value = cmd.scaledSensorValue 463 | result.descriptionText = "$device.displayName power usage is ${result.value} watt(s)" 464 | result.isStateChange 465 | sendEvent(name: result.name, value: result.value, displayed: false) 466 | } 467 | result 468 | } 469 | 470 | def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStartLevelChange cmd, Map item1) { 471 | [] 472 | } 473 | 474 | def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStopLevelChange cmd, Map item1) { 475 | [response(zwave.basicV1.basicGet())] 476 | } 477 | 478 | def createEvent(physicalgraph.zwave.commands.switchmultilevelv3.SwitchMultilevelSet cmd, Map item1) { 479 | def result = doCreateEvent(cmd, item1) 480 | for (int i = 0; i < result.size(); i++) { 481 | result[i].type = "physical" 482 | } 483 | result 484 | } 485 | 486 | def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd, Map item1) { 487 | def result = doCreateEvent(cmd, item1) 488 | result[0].descriptionText = "${item1.linkText} is ${item1.value}" 489 | result[0].handlerName = cmd.value ? "statusOn" : "statusOff" 490 | for (int i = 0; i < result.size(); i++) { 491 | result[i].type = "digital" 492 | } 493 | result 494 | } 495 | 496 | def doCreateEvent(physicalgraph.zwave.Command cmd, Map item1) { 497 | def result = [item1] 498 | 499 | item1.name = "switch" 500 | item1.value = cmd.value ? "on" : "off" 501 | item1.handlerName = item1.value 502 | item1.descriptionText = "${item1.linkText} was turned ${item1.value}" 503 | item1.canBeCurrentState = true 504 | item1.isStateChange = isStateChange(device, item1.name, item1.value) 505 | item1.displayed = item1.isStateChange 506 | 507 | if (cmd.value >= 5) { 508 | def item2 = new LinkedHashMap(item1) 509 | item2.name = "level" 510 | item2.value = cmd.value as String 511 | item2.unit = "%" 512 | item2.descriptionText = "${item1.linkText} dimmed ${item2.value} %" 513 | item2.canBeCurrentState = true 514 | item2.isStateChange = isStateChange(device, item2.name, item2.value) 515 | item2.displayed = false 516 | result << item2 517 | } 518 | result 519 | } 520 | 521 | def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd, item1) { 522 | log.debug "${device.displayName} parameter '${cmd.parameterNumber}' with a byte size of '${cmd.size}' is set to '${cmd.configurationValue}'" 523 | } 524 | /* 525 | def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) { 526 | log.debug "Report: $cmd" 527 | def value = "when off" 528 | if (cmd.configurationValue[0] == 1) {value = "when on"} 529 | if (cmd.configurationValue[0] == 2) {value = "never"} 530 | [name: "indicatorStatus", value: value, display: false] 531 | } 532 | */ 533 | def createEvent(physicalgraph.zwave.Command cmd, Map map) { 534 | // Handles any Z-Wave commands we aren't interested in 535 | log.debug "UNHANDLED COMMAND $cmd" 536 | } 537 | 538 | def on() { 539 | log.debug "on()" 540 | sendEvent(name: "switch", value: "on") 541 | delayBetween([zwave.basicV1.basicSet(value: 0xFF).format(), 542 | zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000) 543 | } 544 | 545 | def off() { 546 | log.debug "off()" 547 | sendEvent(name: "switch", value: "off") 548 | delayBetween ([zwave.basicV1.basicSet(value: 0x00).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000) 549 | } 550 | 551 | 552 | def poll() { 553 | zwave.switchMultilevelV1.switchMultilevelGet().format() 554 | } 555 | 556 | def refresh() { 557 | def cmd = [] 558 | cmd << response(zwave.switchMultilevelV1.switchMultilevelGet().format()) 559 | delayBetween(cmd, 500) 560 | } 561 | 562 | /** 563 | * This method will allow the user to update device parameters (behavior) from an app. 564 | * A "Zwave Tweaker" app will be developed as an interface to do this. Or the user can 565 | * write his/her own app to envoke this method. No type or value checking is done to 566 | * compare to what device capability or reaction. It is up to user to read OEM 567 | * documentation prio to envoking this method. 568 | * 569 | *

THIS IS AN ADVANCED OPERATION. USE AT YOUR OWN RISK! READ OEM DOCUMENTATION! 570 | * 571 | * @param List[paramNumber:80,value:10,size:1] 572 | * 573 | * 574 | * @return none 575 | */ 576 | def updateZwaveParam(params) { 577 | if ( params ) { 578 | def pNumber = params.paramNumber 579 | def pSize = params.size 580 | def pValue = [params.value] 581 | log.debug "Updating ${device.displayName} parameter number '${pNumber}' with value '${pValue}' with size of '${pSize}'" 582 | 583 | def cmds = [] 584 | cmds << zwave.configurationV1.configurationSet(configurationValue: pValue, parameterNumber: pNumber, size: pSize).format() 585 | 586 | cmds << zwave.configurationV1.configurationGet(parameterNumber: pNumber).format() 587 | delayBetween(cmds, 1500) 588 | } 589 | } 590 | 591 | def test() { 592 | //def value = [:] 593 | //value = [hue: 0, saturation: 100, level: 5] 594 | //value = [red: 255, green: 0, blue: 255, level: 60] 595 | //setColor(value) 596 | 597 | def cmd = [] 598 | 599 | if ( !state.cnt ) { 600 | state.cnt = 6 601 | } else { 602 | state.cnt = state.cnt + 1 603 | } 604 | 605 | if ( state.cnt > 10 ) 606 | state.cnt = 6 607 | 608 | // run programmed light show 609 | cmd << zwave.configurationV1.configurationSet(configurationValue: [state.cnt], parameterNumber: 72, size: 1).format() 610 | cmd << zwave.configurationV1.configurationGet(parameterNumber: 72).format() 611 | 612 | delayBetween(cmd, 500) 613 | 614 | } 615 | 616 | def colorNameToRgb(color) { 617 | 618 | final colors = [ 619 | [name:"Soft White", r: 255, g: 241, b: 224 ], 620 | [name:"Daylight", r: 255, g: 255, b: 251 ], 621 | [name:"Warm White", r: 255, g: 244, b: 229 ], 622 | 623 | [name:"Red", r: 255, g: 0, b: 0 ], 624 | [name:"Green", r: 0, g: 255, b: 0 ], 625 | [name:"Blue", r: 0, g: 0, b: 255 ], 626 | 627 | [name:"Cyan", r: 0, g: 255, b: 255 ], 628 | [name:"Magenta", r: 255, g: 0, b: 33 ], 629 | [name:"Orange", r: 255, g: 102, b: 0 ], 630 | 631 | [name:"Purple", r: 170, g: 0, b: 255 ], 632 | [name:"Yellow", r: 255, g: 255, b: 0 ], 633 | [name:"White", r: 255, g: 255, b: 255 ] 634 | ] 635 | 636 | def colorData = [:] 637 | colorData = colors.find { it.name == color } 638 | 639 | colorData 640 | } 641 | 642 | private hex(value, width=2) { 643 | def s = new BigInteger(Math.round(value).toString()).toString(16) 644 | while (s.size() < width) { 645 | s = "0" + s 646 | } 647 | s 648 | } 649 | 650 | def hexToRgb(colorHex) { 651 | def rrInt = Integer.parseInt(colorHex.substring(1,3),16) 652 | def ggInt = Integer.parseInt(colorHex.substring(3,5),16) 653 | def bbInt = Integer.parseInt(colorHex.substring(5,7),16) 654 | 655 | def colorData = [:] 656 | colorData = [r: rrInt, g: ggInt, b: bbInt] 657 | colorData 658 | } 659 | 660 | def rgbToHex(rgb) { 661 | def r = hex(rgb.r) 662 | def g = hex(rgb.g) 663 | def b = hex(rgb.b) 664 | def hexColor = "#${r}${g}${b}" 665 | 666 | hexColor 667 | } 668 | 669 | def hslToRGB(float var_h, float var_s, float var_l) { 670 | float h = var_h / 100 671 | float s = var_s / 100 672 | float l = var_l 673 | 674 | def r = 0 675 | def g = 0 676 | def b = 0 677 | 678 | if (s == 0) { 679 | r = l * 255 680 | g = l * 255 681 | b = l * 255 682 | } else { 683 | float var_2 = 0 684 | if (l < 0.5) { 685 | var_2 = l * (1 + s) 686 | } else { 687 | var_2 = (l + s) - (s * l) 688 | } 689 | 690 | float var_1 = 2 * l - var_2 691 | 692 | r = 255 * hueToRgb(var_1, var_2, h + (1 / 3)) 693 | g = 255 * hueToRgb(var_1, var_2, h) 694 | b = 255 * hueToRgb(var_1, var_2, h - (1 / 3)) 695 | } 696 | 697 | def rgb = [:] 698 | rgb = [r: r, g: g, b: b] 699 | 700 | rgb 701 | } 702 | 703 | def hueToRgb(v1, v2, vh) { 704 | if (vh < 0) { vh += 1 } 705 | if (vh > 1) { vh -= 1 } 706 | if ((6 * vh) < 1) { return (v1 + (v2 - v1) * 6 * vh) } 707 | if ((2 * vh) < 1) { return (v2) } 708 | if ((3 * vh) < 2) { return (v1 + (v2 - $v1) * ((2 / 3 - vh) * 6)) } 709 | return (v1) 710 | } 711 | 712 | def rgbToHSL(rgb) { 713 | def r = rgb.r / 255 714 | def g = rgb.g / 255 715 | def b = rgb.b / 255 716 | def h = 0 717 | def s = 0 718 | def l = 0 719 | 720 | def var_min = [r,g,b].min() 721 | def var_max = [r,g,b].max() 722 | def del_max = var_max - var_min 723 | 724 | l = (var_max + var_min) / 2 725 | 726 | if (del_max == 0) { 727 | h = 0 728 | s = 0 729 | } else { 730 | if (l < 0.5) { s = del_max / (var_max + var_min) } 731 | else { s = del_max / (2 - var_max - var_min) } 732 | 733 | def del_r = (((var_max - r) / 6) + (del_max / 2)) / del_max 734 | def del_g = (((var_max - g) / 6) + (del_max / 2)) / del_max 735 | def del_b = (((var_max - b) / 6) + (del_max / 2)) / del_max 736 | 737 | if (r == var_max) { h = del_b - del_g } 738 | else if (g == var_max) { h = (1 / 3) + del_r - del_b } 739 | else if (b == var_max) { h = (2 / 3) + del_g - del_r } 740 | 741 | if (h < 0) { h += 1 } 742 | if (h > 1) { h -= 1 } 743 | } 744 | def hsl = [:] 745 | hsl = [h: h * 100, s: s * 100, l: l] 746 | 747 | hsl 748 | } 749 | 750 | def getColorData(colorName) { 751 | log.debug "getColorData: ${colorName}" 752 | 753 | def colorRGB = colorNameToRgb(colorName) 754 | def colorHex = rgbToHex(colorRGB) 755 | def colorHSL = rgbToHSL(colorRGB) 756 | 757 | def colorData = [:] 758 | colorData = [h: colorHSL.h, 759 | s: colorHSL.s, 760 | l: device.latestValue("level"), 761 | r: colorRGB.r, 762 | g: colorRGB.g, 763 | b: colorRGB.b, 764 | rh: hex(colorRGB.r), 765 | gh: hex(colorRGB.g), 766 | bh: hex(colorRGB.b), 767 | hex: colorHex, 768 | alpha: 1] 769 | 770 | colorData 771 | } 772 | 773 | def doColorButton(colorName) { 774 | log.debug "doColorButton: '${colorName}()'" 775 | 776 | if (device.latestValue("switch") == "off") { on() } 777 | 778 | def level = device.latestValue("level") 779 | 780 | toggleTiles(colorName.toLowerCase().replaceAll("\\s","")) 781 | 782 | if ( colorName == "Fire Place" ) { updateZwaveParam([paramNumber:72, value:6, size:1]) } 783 | else if ( colorName == "Storm" ) { updateZwaveParam([paramNumber:72, value:7, size:1]) } 784 | else if ( colorName == "Deep Fade" ) { updateZwaveParam([paramNumber:72, value:8, size:1]) } 785 | else if ( colorName == "Lite Fade" ) { updateZwaveParam([paramNumber:72, value:9, size:1]) } 786 | else if ( colorName == "Police" ) { updateZwaveParam([paramNumber:72, value:10, size:1]) } 787 | else { 788 | def c = getColorData(colorName) 789 | def newValue = ["hue": c.h, "saturation": c.s, "level": level, "red": c.r, "green": c.g, "blue": c.b, "hex": c.hex, "alpha": c.alpha] 790 | setColor(newValue) 791 | def r = hex(c.r * (level/100)) 792 | def g = hex(c.g * (level/100)) 793 | def b = hex(c.b * (level/100)) 794 | sendRGB(r, g, b) 795 | } 796 | } 797 | 798 | def toggleTiles(color) { 799 | state.colorTiles = [] 800 | if ( !state.colorTiles ) { 801 | state.colorTiles = ["softwhite","daylight","warmwhite","red","green","blue","cyan","magenta","orange","purple","yellow","white","fireplace","storm","deepfade","litefade","police"] 802 | } 803 | 804 | def cmds = [] 805 | 806 | state.colorTiles.each({ 807 | if ( it == color ) { 808 | log.debug "Turning ${it} on" 809 | device.displayName + " was closed" 810 | cmds << sendEvent(name: it, value: "on${it}", display: True, descriptionText: "${device.displayName} ${color} is 'ON'", isStateChange: true) 811 | } else { 812 | //log.debug "Turning ${it} off" 813 | cmds << sendEvent(name: it, value: "off${it}", displayed: false) 814 | } 815 | }) 816 | 817 | delayBetween(cmds, 2500) 818 | } 819 | 820 | // rows of buttons 821 | def softwhite() { doColorButton("Soft White") } 822 | def daylight() { doColorButton("Daylight") } 823 | def warmwhite() { doColorButton("Warm White") } 824 | 825 | def red() { doColorButton("Red") } 826 | def green() { doColorButton("Green") } 827 | def blue() { doColorButton("Blue") } 828 | 829 | def cyan() { doColorButton("Cyan") } 830 | def magenta() { doColorButton("Magenta") } 831 | def orange() { doColorButton("Orange") } 832 | 833 | def purple() { doColorButton("Purple") } 834 | def yellow() { doColorButton("Yellow") } 835 | def white() { doColorButton("White") } 836 | 837 | def fireplace() { doColorButton("Fire Place") } 838 | def storm() { doColorButton("Storm") } 839 | def deepfade() { doColorButton("Deep Fade") } 840 | 841 | def litefade() { doColorButton("Lite Fade") } 842 | def police() { doColorButton("Police") } 843 | -------------------------------------------------------------------------------- /fibaro-rgbw-controller-test.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Device Type Definition File 3 | * 4 | * Device Type: Fibaro RGBW Controller 5 | * File Name: fibaro-rgbw-controller.groovy 6 | * Initial Release: 2015-01-04 7 | * Author: Todd Wackford 8 | * Email: todd@wackford.net 9 | * 10 | * Copyright 2015 SmartThings 11 | * 12 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 13 | * in compliance with the License. You may obtain a copy of the License at: 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 18 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 19 | * for the specific language governing permissions and limitations under the License. 20 | * 21 | */ 22 | 23 | /* 24 | Todo's 25 | 1. Incorporate javadoc information and formatting 26 | */ 27 | 28 | metadata { 29 | 30 | definition (name: "Fibaro RGBW Controller", namespace: "smartthings", author: "Todd Wackford") { 31 | capability "Switch Level" 32 | capability "Actuator" 33 | capability "Switch" 34 | capability "Polling" 35 | capability "Refresh" 36 | capability "Sensor" 37 | capability "Configuration" 38 | capability "Color Control" 39 | capability "Power Meter" 40 | 41 | command "getDeviceData" 42 | command "softwhite" 43 | command "daylight" 44 | command "warmwhite" 45 | command "red" 46 | command "green" 47 | command "blue" 48 | command "cyan" 49 | command "magenta" 50 | command "orange" 51 | command "purple" 52 | command "yellow" 53 | command "white" 54 | command "fireplace" 55 | command "storm" 56 | command "deepfade" 57 | command "litefade" 58 | command "police" 59 | command "setAdjustedColor" 60 | command "setWhiteLevel" 61 | command "test" 62 | 63 | attribute "whiteLevel", "string" 64 | 65 | fingerprint deviceId: "0x1101", inClusters: "0x27,0x72,0x86,0x26,0x60,0x70,0x32,0x31,0x85,0x33" 66 | } 67 | 68 | simulator { 69 | status "on": "command: 2003, payload: FF" 70 | status "off": "command: 2003, payload: 00" 71 | status "09%": "command: 2003, payload: 09" 72 | status "10%": "command: 2003, payload: 0A" 73 | status "33%": "command: 2003, payload: 21" 74 | status "66%": "command: 2003, payload: 42" 75 | status "99%": "command: 2003, payload: 63" 76 | 77 | // reply messages 78 | reply "2001FF,delay 5000,2602": "command: 2603, payload: FF" 79 | reply "200100,delay 5000,2602": "command: 2603, payload: 00" 80 | reply "200119,delay 5000,2602": "command: 2603, payload: 19" 81 | reply "200132,delay 5000,2602": "command: 2603, payload: 32" 82 | reply "20014B,delay 5000,2602": "command: 2603, payload: 4B" 83 | reply "200163,delay 5000,2602": "command: 2603, payload: 63" 84 | } 85 | 86 | tiles { 87 | controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) { 88 | state "color", action:"setAdjustedColor" 89 | } 90 | controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) { 91 | state "level", action:"switch level.setLevel" 92 | } 93 | controlTile("whiteSliderControl", "device.whiteLevel", "slider", height: 1, width: 3, inactiveLabel: false) { 94 | state "whiteLevel", action:"setWhiteLevel", label:'White Level' 95 | } 96 | standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) { 97 | state "on", label:'${name}', action:"switch.off", icon:"st.illuminance.illuminance.bright", backgroundColor:"#79b821", nextState:"turningOff" 98 | state "off", label:'${name}', action:"switch.on", icon:"st.illuminance.illuminance.dark", backgroundColor:"#ffffff", nextState:"turningOn" 99 | state "turningOn", label:'${name}', icon:"st.illuminance.illuminance.bright", backgroundColor:"#79b821" 100 | state "turningOff", label:'${name}', icon:"st.illuminance.illuminance.dark", backgroundColor:"#ffffff" 101 | } 102 | valueTile("power", "device.power", decoration: "flat") { 103 | state "power", label:'${currentValue} W' 104 | } 105 | standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") { 106 | state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure" 107 | } 108 | standardTile("refresh", "device.switch", height: 1, inactiveLabel: false, decoration: "flat") { 109 | state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" 110 | } 111 | standardTile("softwhite", "device.softwhite", height: 1, inactiveLabel: false, canChangeIcon: false) { 112 | state "offsoftwhite", label:"soft white", action:"softwhite", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 113 | state "onsoftwhite", label:"soft white", action:"softwhite", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFF1E0" 114 | } 115 | standardTile("daylight", "device.daylight", height: 1, inactiveLabel: false, canChangeIcon: false) { 116 | state "offdaylight", label:"daylight", action:"daylight", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 117 | state "ondaylight", label:"daylight", action:"daylight", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFB" 118 | } 119 | standardTile("warmwhite", "device.warmwhite", height: 1, inactiveLabel: false, canChangeIcon: false) { 120 | state "offwarmwhite", label:"warm white", action:"warmwhite", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 121 | state "onwarmwhite", label:"warm white", action:"warmwhite", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFF4E5" 122 | } 123 | standardTile("red", "device.red", height: 1, inactiveLabel: false, canChangeIcon: false) { 124 | state "offred", label:"red", action:"red", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 125 | state "onred", label:"red", action:"red", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FF0000" 126 | } 127 | standardTile("green", "device.green", height: 1, inactiveLabel: false, canChangeIcon: false) { 128 | state "offgreen", label:"green", action:"green", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 129 | state "ongreen", label:"green", action:"green", icon:"st.illuminance.illuminance.bright", backgroundColor:"#00FF00" 130 | } 131 | standardTile("blue", "device.blue", height: 1, inactiveLabel: false, canChangeIcon: false) { 132 | state "offblue", label:"blue", action:"blue", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 133 | state "onblue", label:"blue", action:"blue", icon:"st.illuminance.illuminance.bright", backgroundColor:"#0000FF" 134 | } 135 | standardTile("cyan", "device.cyan", height: 1, inactiveLabel: false, canChangeIcon: false) { 136 | state "offcyan", label:"cyan", action:"cyan", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 137 | state "oncyan", label:"cyan", action:"cyan", icon:"st.illuminance.illuminance.bright", backgroundColor:"#00FFFF" 138 | } 139 | standardTile("magenta", "device.magenta", height: 1, inactiveLabel: false, canChangeIcon: false) { 140 | state "offmagenta", label:"magenta", action:"magenta", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 141 | state "onmagenta", label:"magenta", action:"magenta", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FF00FF" 142 | } 143 | standardTile("orange", "device.orange", height: 1, inactiveLabel: false, canChangeIcon: false) { 144 | state "offorange", label:"orange", action:"orange", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 145 | state "onorange", label:"orange", action:"orange", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FF6600" 146 | } 147 | standardTile("purple", "device.purple", height: 1, inactiveLabel: false, canChangeIcon: false) { 148 | state "offpurple", label:"purple", action:"purple", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 149 | state "onpurple", label:"purple", action:"purple", icon:"st.illuminance.illuminance.bright", backgroundColor:"#BF00FF" 150 | } 151 | standardTile("yellow", "device.yellow", height: 1, inactiveLabel: false, canChangeIcon: false) { 152 | state "offyellow", label:"yellow", action:"yellow", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 153 | state "onyellow", label:"yellow", action:"yellow", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFF00" 154 | } 155 | standardTile("white", "device.white", height: 1, inactiveLabel: false, canChangeIcon: false) { 156 | state "offwhite", label:"White", action:"white", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 157 | state "onwhite", label:"White", action:"white", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 158 | } 159 | standardTile("fireplace", "device.fireplace", height: 1, inactiveLabel: false, canChangeIcon: false) { 160 | state "offfireplace", label:"Fire Place", action:"fireplace", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 161 | state "onfireplace", label:"Fire Place", action:"fireplace", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 162 | } 163 | standardTile("storm", "device.storm", height: 1, inactiveLabel: false, canChangeIcon: false) { 164 | state "offstorm", label:"storm", action:"storm", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 165 | state "onstorm", label:"storm", action:"storm", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 166 | } 167 | standardTile("deepfade", "device.deepfade", height: 1, inactiveLabel: false, canChangeIcon: false) { 168 | state "offdeepfade", label:"deep fade", action:"deepfade", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 169 | state "ondeepfade", label:"deep fade", action:"deepfade", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 170 | } 171 | standardTile("litefade", "device.litefade", height: 1, inactiveLabel: false, canChangeIcon: false) { 172 | state "offlitefade", label:"lite fade", action:"litefade", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 173 | state "onlitefade", label:"lite fade", action:"litefade", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 174 | } 175 | standardTile("police", "device.police", height: 1, inactiveLabel: false, canChangeIcon: false) { 176 | state "offpolice", label:"police", action:"police", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 177 | state "onpolice", label:"police", action:"police", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 178 | } 179 | controlTile("saturationSliderControl", "device.saturation", "slider", height: 1, width: 2, inactiveLabel: false) { 180 | state "saturation", action:"color control.setSaturation" 181 | } 182 | valueTile("saturation", "device.saturation", inactiveLabel: false, decoration: "flat") { 183 | state "saturation", label: 'Sat ${currentValue} ' 184 | } 185 | controlTile("hueSliderControl", "device.hue", "slider", height: 1, width: 2, inactiveLabel: false) { 186 | state "hue", action:"color control.setHue" 187 | } 188 | valueTile("hue", "device.hue", inactiveLabel: false, decoration: "flat") { 189 | state "hue", label: 'Hue ${currentValue} ' 190 | } 191 | 192 | main(["switch"]) 193 | details(["switch", 194 | "levelSliderControl", 195 | "rgbSelector", 196 | "whiteSliderControl", 197 | "softwhite", 198 | "daylight", 199 | "warmwhite", 200 | "red", 201 | "green", 202 | "blue", 203 | "white", 204 | "cyan", 205 | "magenta", 206 | "orange", 207 | "purple", 208 | "yellow", 209 | "fireplace", 210 | "storm", 211 | "deepfade", 212 | "litefade", 213 | "police", 214 | //"power", 215 | //"configure", 216 | "refresh"]) 217 | } 218 | } 219 | 220 | def setAdjustedColor(value) { 221 | log.debug "setAdjustedColor: ${value}" 222 | 223 | toggleTiles("off") //turn off the hard color tiles 224 | 225 | def level = device.latestValue("level") 226 | value.level = level 227 | 228 | def c = hexToRgb(value.hex) 229 | value.rh = hex(c.r * (level/100)) 230 | value.gh = hex(c.g * (level/100)) 231 | value.bh = hex(c.b * (level/100)) 232 | 233 | setColor(value) 234 | } 235 | 236 | def setColor(value) { 237 | log.debug "setColor: ${value}" 238 | 239 | if (value.size() < 8) 240 | toggleTiles("off") 241 | 242 | if (( value.size() == 2) && (value.hue != null) && (value.saturation != null)) { //assuming we're being called from outside of device (App) 243 | def rgb = hslToRGB(value.hue, value.saturation, 0.5) 244 | value.hex = rgbToHex(rgb) 245 | value.rh = hex(rgb.r) 246 | value.gh = hex(rgb.g) 247 | value.bh = hex(rgb.b) 248 | } 249 | 250 | if ((value.size() == 3) && (value.hue != null) && (value.saturation != null) && (value.level)) { //user passed in a level value too from outside (App) 251 | def rgb = hslToRGB(value.hue, value.saturation, 0.5) 252 | value.hex = rgbToHex(rgb) 253 | value.rh = hex(rgb.r * value.level/100) 254 | value.gh = hex(rgb.g * value.level/100) 255 | value.bh = hex(rgb.b * value.level/100) 256 | } 257 | 258 | if (( value.size() == 1) && (value.hex)) { //being called from outside of device (App) with only hex 259 | def rgbInt = hexToRgb(value.hex) 260 | value.rh = hex(rgbInt.r) 261 | value.gh = hex(rgbInt.g) 262 | value.bh = hex(rgbInt.b) 263 | } 264 | 265 | if (( value.size() == 2) && (value.hex) && (value.level)) { //being called from outside of device (App) with only hex and level 266 | 267 | def rgbInt = hexToRgb(value.hex) 268 | value.rh = hex(rgbInt.r * value.level/100) 269 | value.gh = hex(rgbInt.g * value.level/100) 270 | value.bh = hex(rgbInt.b * value.level/100) 271 | } 272 | 273 | if (( value.size() == 1) && (value.colorName)) { //being called from outside of device (App) with only color name 274 | def colorData = getColorData(value.colorName) 275 | value.rh = colorData.rh 276 | value.gh = colorData.gh 277 | value.bh = colorData.bh 278 | value.hex = "#${value.rh}${value.gh}${value.bh}" 279 | } 280 | 281 | if (( value.size() == 2) && (value.colorName) && (value.level)) { //being called from outside of device (App) with only color name and level 282 | def colorData = getColorData(value.colorName) 283 | value.rh = hex(colorData.r * value.level/100) 284 | value.gh = hex(colorData.g * value.level/100) 285 | value.bh = hex(colorData.b * value.level/100) 286 | value.hex = "#${hex(colorData.r)}${hex(colorData.g)}${hex(colorData.b)}" 287 | } 288 | 289 | if (( value.size() == 3) && (value.red != null) && (value.green != null) && (value.blue != null)) { //being called from outside of device (App) with only color values (0-255) 290 | value.rh = hex(value.red) 291 | value.gh = hex(value.green) 292 | value.bh = hex(value.blue) 293 | value.hex = "#${value.rh}${value.gh}${value.bh}" 294 | } 295 | 296 | if (( value.size() == 4) && (value.red != null) && (value.green != null) && (value.blue != null) && (value.level)) { //being called from outside of device (App) with only color values (0-255) and level 297 | value.rh = hex(value.red * value.level/100) 298 | value.gh = hex(value.green * value.level/100) 299 | value.bh = hex(value.blue * value.level/100) 300 | value.hex = "#${hex(value.red)}${hex(value.green)}${hex(value.blue)}" 301 | } 302 | 303 | sendEvent(name: "hue", value: value.hue, displayed: false) 304 | sendEvent(name: "saturation", value: value.saturation, displayed: false) 305 | sendEvent(name: "color", value: value.hex, displayed: false) 306 | if (value.level) { 307 | sendEvent(name: "level", value: value.level) 308 | } 309 | if (value.switch) { 310 | sendEvent(name: "switch", value: value.switch) 311 | } 312 | 313 | sendRGB(value.rh, value.gh, value.bh) 314 | } 315 | 316 | def setLevel(level) { 317 | log.debug "setLevel($level)" 318 | 319 | if (level == 0) { off() } 320 | else if (device.latestValue("switch") == "off") { on() } 321 | 322 | def colorHex = device.latestValue("color") 323 | def c = hexToRgb(colorHex) 324 | 325 | def r = hex(c.r * (level/100)) 326 | def g = hex(c.g * (level/100)) 327 | def b = hex(c.b * (level/100)) 328 | 329 | sendEvent(name: "level", value: level) 330 | sendEvent(name: "setLevel", value: level, displayed: false) 331 | sendRGB(r, g, b) 332 | } 333 | 334 | 335 | def setWhiteLevel(value) { 336 | log.debug "setWhiteLevel: ${value}" 337 | def level = Math.min(value as Integer, 99) 338 | level = 255 * level/99 as Integer 339 | def channel = 0 340 | 341 | if (device.latestValue("switch") == "off") { on() } 342 | 343 | sendEvent(name: "whiteLevel", value: value) 344 | sendWhite(channel, value) 345 | } 346 | 347 | def sendWhite(channel, value) { 348 | def whiteLevel = hex(value) 349 | def cmd = [String.format("3305010${channel}${whiteLevel}%02X", 50)] 350 | cmd 351 | } 352 | 353 | def sendRGB(redHex, greenHex, blueHex) { 354 | def cmd = [String.format("33050302${redHex}03${greenHex}04${blueHex}%02X", 100),] 355 | cmd 356 | } 357 | 358 | 359 | def sendRGBW(redHex, greenHex, blueHex, whiteHex) { 360 | def cmd = [String.format("33050400${whiteHex}02${redHex}03${greenHex}04${blueHex}%02X", 100),] 361 | cmd 362 | } 363 | 364 | 365 | def configure() { 366 | log.debug "Configuring Device For SmartThings Use" 367 | def cmds = [] 368 | 369 | // send associate to group 3 to get sensor data reported only to hub 370 | cmds << zwave.associationV2.associationSet(groupingIdentifier:5, nodeId:[zwaveHubNodeId]).format() 371 | 372 | delayBetween(cmds, 500) 373 | } 374 | 375 | def parse(String description) { 376 | //log.debug "description: ${description}" 377 | def item1 = [ 378 | canBeCurrentState: false, 379 | linkText: getLinkText(device), 380 | isStateChange: false, 381 | displayed: false, 382 | descriptionText: description, 383 | value: description 384 | ] 385 | def result 386 | def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 2, 0x72: 2, 0x60: 3, 0x33: 2, 0x32: 3, 0x31:2, 0x30: 2, 0x86: 1, 0x7A: 1]) 387 | 388 | if (cmd) { 389 | if ( cmd.CMD != "7006" ) { 390 | result = createEvent(cmd, item1) 391 | } 392 | } 393 | else { 394 | item1.displayed = displayed(description, item1.isStateChange) 395 | result = [item1] 396 | } 397 | //log.debug "Parse returned ${result?.descriptionText}" 398 | result 399 | } 400 | 401 | def getDeviceData() { 402 | def cmd = [] 403 | 404 | cmd << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet()) 405 | cmd << response(zwave.versionV1.versionGet()) 406 | cmd << response(zwave.firmwareUpdateMdV1.firmwareMdGet()) 407 | 408 | delayBetween(cmd, 500) 409 | } 410 | 411 | def createEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd, Map item1) { 412 | log.debug "manufacturerName: ${cmd.manufacturerName}" 413 | log.debug "manufacturerId: ${cmd.manufacturerId}" 414 | log.debug "productId: ${cmd.productId}" 415 | log.debug "productTypeId: ${cmd.productTypeId}" 416 | } 417 | 418 | def createEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd, Map item1) { 419 | updateDataValue("applicationVersion", "${cmd.applicationVersion}") 420 | log.debug "applicationVersion: ${cmd.applicationVersion}" 421 | log.debug "applicationSubVersion: ${cmd.applicationSubVersion}" 422 | log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}" 423 | log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}" 424 | log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}" 425 | } 426 | 427 | def createEvent(physicalgraph.zwave.commands.firmwareupdatemdv1.FirmwareMdReport cmd, Map item1) { 428 | log.debug "checksum: ${cmd.checksum}" 429 | log.debug "firmwareId: ${cmd.firmwareId}" 430 | log.debug "manufacturerId: ${cmd.manufacturerId}" 431 | } 432 | 433 | def zwaveEvent(physicalgraph.zwave.commands.colorcontrolv1.CapabilityReport cmd, Map item1) { 434 | 435 | log.debug "In CapabilityReport" 436 | } 437 | 438 | def createEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd, Map item1) { 439 | def encapsulatedCommand = cmd.encapsulatedCommand([0x26: 1, 0x30: 2, 0x32: 2, 0x33: 2]) // can specify command class versions here like in zwave.parse 440 | //log.debug ("Command from endpoint ${cmd.sourceEndPoint}: ${encapsulatedCommand}") 441 | if ((cmd.sourceEndPoint >= 1) && (cmd.sourceEndPoint <= 5)) { // we don't need color report 442 | //don't do anything 443 | } else { 444 | if (encapsulatedCommand) { 445 | zwaveEvent(encapsulatedCommand) 446 | } 447 | } 448 | } 449 | 450 | def createEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd, Map item1) { 451 | def result = doCreateEvent(cmd, item1) 452 | for (int i = 0; i < result.size(); i++) { 453 | result[i].type = "physical" 454 | } 455 | result 456 | } 457 | 458 | def createEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd, Map item1) { 459 | def result = doCreateEvent(cmd, item1) 460 | for (int i = 0; i < result.size(); i++) { 461 | result[i].type = "physical" 462 | } 463 | result 464 | } 465 | 466 | def createEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd, Map item1) { 467 | def result = [:] 468 | if ( cmd.sensorType == 4 ) { //power level comming in 469 | result.name = "power" 470 | result.value = cmd.scaledSensorValue 471 | result.descriptionText = "$device.displayName power usage is ${result.value} watt(s)" 472 | result.isStateChange 473 | sendEvent(name: result.name, value: result.value, displayed: false) 474 | } 475 | result 476 | } 477 | 478 | def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStartLevelChange cmd, Map item1) { 479 | [] 480 | } 481 | 482 | def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStopLevelChange cmd, Map item1) { 483 | [response(zwave.basicV1.basicGet())] 484 | } 485 | 486 | def createEvent(physicalgraph.zwave.commands.switchmultilevelv3.SwitchMultilevelSet cmd, Map item1) { 487 | def result = doCreateEvent(cmd, item1) 488 | for (int i = 0; i < result.size(); i++) { 489 | result[i].type = "physical" 490 | } 491 | result 492 | } 493 | 494 | def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd, Map item1) { 495 | def result = doCreateEvent(cmd, item1) 496 | result[0].descriptionText = "${item1.linkText} is ${item1.value}" 497 | result[0].handlerName = cmd.value ? "statusOn" : "statusOff" 498 | for (int i = 0; i < result.size(); i++) { 499 | result[i].type = "digital" 500 | } 501 | result 502 | } 503 | 504 | def doCreateEvent(physicalgraph.zwave.Command cmd, Map item1) { 505 | def result = [item1] 506 | 507 | item1.name = "switch" 508 | item1.value = cmd.value ? "on" : "off" 509 | item1.handlerName = item1.value 510 | item1.descriptionText = "${item1.linkText} was turned ${item1.value}" 511 | item1.canBeCurrentState = true 512 | item1.isStateChange = isStateChange(device, item1.name, item1.value) 513 | item1.displayed = item1.isStateChange 514 | 515 | if (cmd.value >= 5) { 516 | def item2 = new LinkedHashMap(item1) 517 | item2.name = "level" 518 | item2.value = cmd.value as String 519 | item2.unit = "%" 520 | item2.descriptionText = "${item1.linkText} dimmed ${item2.value} %" 521 | item2.canBeCurrentState = true 522 | item2.isStateChange = isStateChange(device, item2.name, item2.value) 523 | item2.displayed = false 524 | result << item2 525 | } 526 | result 527 | } 528 | 529 | def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd, item1) { 530 | log.debug "${device.displayName} parameter '${cmd.parameterNumber}' with a byte size of '${cmd.size}' is set to '${cmd.configurationValue}'" 531 | } 532 | /* 533 | def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) { 534 | log.debug "Report: $cmd" 535 | def value = "when off" 536 | if (cmd.configurationValue[0] == 1) {value = "when on"} 537 | if (cmd.configurationValue[0] == 2) {value = "never"} 538 | [name: "indicatorStatus", value: value, display: false] 539 | } 540 | */ 541 | def createEvent(physicalgraph.zwave.Command cmd, Map map) { 542 | // Handles any Z-Wave commands we aren't interested in 543 | log.debug "UNHANDLED COMMAND $cmd" 544 | } 545 | 546 | def on() { 547 | log.debug "on()" 548 | sendEvent(name: "switch", value: "on") 549 | delayBetween([zwave.basicV1.basicSet(value: 0xFF).format(), 550 | zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000) 551 | } 552 | 553 | def off() { 554 | log.debug "off()" 555 | sendEvent(name: "switch", value: "off") 556 | delayBetween ([zwave.basicV1.basicSet(value: 0x00).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000) 557 | } 558 | 559 | 560 | def poll() { 561 | zwave.switchMultilevelV1.switchMultilevelGet().format() 562 | } 563 | 564 | def refresh() { 565 | def cmd = [] 566 | cmd << response(zwave.switchMultilevelV1.switchMultilevelGet().format()) 567 | delayBetween(cmd, 500) 568 | } 569 | 570 | /** 571 | * This method will allow the user to update device parameters (behavior) from an app. 572 | * A "Zwave Tweaker" app will be developed as an interface to do this. Or the user can 573 | * write his/her own app to envoke this method. No type or value checking is done to 574 | * compare to what device capability or reaction. It is up to user to read OEM 575 | * documentation prio to envoking this method. 576 | * 577 | *

THIS IS AN ADVANCED OPERATION. USE AT YOUR OWN RISK! READ OEM DOCUMENTATION! 578 | * 579 | * @param List[paramNumber:80,value:10,size:1] 580 | * 581 | * 582 | * @return none 583 | */ 584 | def updateZwaveParam(params) { 585 | if ( params ) { 586 | def pNumber = params.paramNumber 587 | def pSize = params.size 588 | def pValue = [params.value] 589 | log.debug "Updating ${device.displayName} parameter number '${pNumber}' with value '${pValue}' with size of '${pSize}'" 590 | 591 | def cmds = [] 592 | cmds << zwave.configurationV1.configurationSet(configurationValue: pValue, parameterNumber: pNumber, size: pSize).format() 593 | 594 | cmds << zwave.configurationV1.configurationGet(parameterNumber: pNumber).format() 595 | delayBetween(cmds, 1500) 596 | } 597 | } 598 | 599 | def test() { 600 | //def value = [:] 601 | //value = [hue: 0, saturation: 100, level: 5] 602 | //value = [red: 255, green: 0, blue: 255, level: 60] 603 | //setColor(value) 604 | 605 | def cmd = [] 606 | 607 | if ( !state.cnt ) { 608 | state.cnt = 6 609 | } else { 610 | state.cnt = state.cnt + 1 611 | } 612 | 613 | if ( state.cnt > 10 ) 614 | state.cnt = 6 615 | 616 | // run programmed light show 617 | cmd << zwave.configurationV1.configurationSet(configurationValue: [state.cnt], parameterNumber: 72, size: 1).format() 618 | cmd << zwave.configurationV1.configurationGet(parameterNumber: 72).format() 619 | 620 | delayBetween(cmd, 500) 621 | 622 | } 623 | 624 | def colorNameToRgb(color) { 625 | 626 | final colors = [ 627 | [name:"Soft White", r: 255, g: 241, b: 224 ], 628 | [name:"Daylight", r: 255, g: 255, b: 251 ], 629 | [name:"Warm White", r: 255, g: 244, b: 229 ], 630 | 631 | [name:"Red", r: 255, g: 0, b: 0 ], 632 | [name:"Green", r: 0, g: 255, b: 0 ], 633 | [name:"Blue", r: 0, g: 0, b: 255 ], 634 | 635 | [name:"Cyan", r: 0, g: 255, b: 255 ], 636 | [name:"Magenta", r: 255, g: 0, b: 33 ], 637 | [name:"Orange", r: 255, g: 102, b: 0 ], 638 | 639 | [name:"Purple", r: 170, g: 0, b: 255 ], 640 | [name:"Yellow", r: 255, g: 255, b: 0 ], 641 | [name:"White", r: 255, g: 255, b: 255 ] 642 | ] 643 | 644 | def colorData = [:] 645 | colorData = colors.find { it.name == color } 646 | 647 | colorData 648 | } 649 | 650 | private hex(value, width=2) { 651 | def s = new BigInteger(Math.round(value).toString()).toString(16) 652 | while (s.size() < width) { 653 | s = "0" + s 654 | } 655 | s 656 | } 657 | 658 | def hexToRgb(colorHex) { 659 | def rrInt = Integer.parseInt(colorHex.substring(1,3),16) 660 | def ggInt = Integer.parseInt(colorHex.substring(3,5),16) 661 | def bbInt = Integer.parseInt(colorHex.substring(5,7),16) 662 | 663 | def colorData = [:] 664 | colorData = [r: rrInt, g: ggInt, b: bbInt] 665 | colorData 666 | } 667 | 668 | def rgbToHex(rgb) { 669 | def r = hex(rgb.r) 670 | def g = hex(rgb.g) 671 | def b = hex(rgb.b) 672 | def hexColor = "#${r}${g}${b}" 673 | 674 | hexColor 675 | } 676 | 677 | def hslToRGB(float var_h, float var_s, float var_l) { 678 | float h = var_h / 100 679 | float s = var_s / 100 680 | float l = var_l 681 | 682 | def r = 0 683 | def g = 0 684 | def b = 0 685 | 686 | if (s == 0) { 687 | r = l * 255 688 | g = l * 255 689 | b = l * 255 690 | } else { 691 | float var_2 = 0 692 | if (l < 0.5) { 693 | var_2 = l * (1 + s) 694 | } else { 695 | var_2 = (l + s) - (s * l) 696 | } 697 | 698 | float var_1 = 2 * l - var_2 699 | 700 | r = 255 * hueToRgb(var_1, var_2, h + (1 / 3)) 701 | g = 255 * hueToRgb(var_1, var_2, h) 702 | b = 255 * hueToRgb(var_1, var_2, h - (1 / 3)) 703 | } 704 | 705 | def rgb = [:] 706 | rgb = [r: r, g: g, b: b] 707 | 708 | rgb 709 | } 710 | 711 | def hueToRgb(v1, v2, vh) { 712 | if (vh < 0) { vh += 1 } 713 | if (vh > 1) { vh -= 1 } 714 | if ((6 * vh) < 1) { return (v1 + (v2 - v1) * 6 * vh) } 715 | if ((2 * vh) < 1) { return (v2) } 716 | if ((3 * vh) < 2) { return (v1 + (v2 - $v1) * ((2 / 3 - vh) * 6)) } 717 | return (v1) 718 | } 719 | 720 | def rgbToHSL(rgb) { 721 | def r = rgb.r / 255 722 | def g = rgb.g / 255 723 | def b = rgb.b / 255 724 | def h = 0 725 | def s = 0 726 | def l = 0 727 | 728 | def var_min = [r,g,b].min() 729 | def var_max = [r,g,b].max() 730 | def del_max = var_max - var_min 731 | 732 | l = (var_max + var_min) / 2 733 | 734 | if (del_max == 0) { 735 | h = 0 736 | s = 0 737 | } else { 738 | if (l < 0.5) { s = del_max / (var_max + var_min) } 739 | else { s = del_max / (2 - var_max - var_min) } 740 | 741 | def del_r = (((var_max - r) / 6) + (del_max / 2)) / del_max 742 | def del_g = (((var_max - g) / 6) + (del_max / 2)) / del_max 743 | def del_b = (((var_max - b) / 6) + (del_max / 2)) / del_max 744 | 745 | if (r == var_max) { h = del_b - del_g } 746 | else if (g == var_max) { h = (1 / 3) + del_r - del_b } 747 | else if (b == var_max) { h = (2 / 3) + del_g - del_r } 748 | 749 | if (h < 0) { h += 1 } 750 | if (h > 1) { h -= 1 } 751 | } 752 | def hsl = [:] 753 | hsl = [h: h * 100, s: s * 100, l: l] 754 | 755 | hsl 756 | } 757 | 758 | def getColorData(colorName) { 759 | log.debug "getColorData: ${colorName}" 760 | 761 | def colorRGB = colorNameToRgb(colorName) 762 | def colorHex = rgbToHex(colorRGB) 763 | def colorHSL = rgbToHSL(colorRGB) 764 | 765 | def colorData = [:] 766 | colorData = [h: colorHSL.h, 767 | s: colorHSL.s, 768 | l: device.latestValue("level"), 769 | r: colorRGB.r, 770 | g: colorRGB.g, 771 | b: colorRGB.b, 772 | rh: hex(colorRGB.r), 773 | gh: hex(colorRGB.g), 774 | bh: hex(colorRGB.b), 775 | hex: colorHex, 776 | alpha: 1] 777 | 778 | colorData 779 | } 780 | 781 | def doColorButton(colorName) { 782 | log.debug "doColorButton: '${colorName}()'" 783 | 784 | if (device.latestValue("switch") == "off") { on() } 785 | 786 | def level = device.latestValue("level") 787 | def maxLevel = hex(99) 788 | 789 | toggleTiles(colorName.toLowerCase().replaceAll("\\s","")) 790 | 791 | if ( colorName == "Fire Place" ) { updateZwaveParam([paramNumber:72, value:6, size:1]) } 792 | else if ( colorName == "Storm" ) { updateZwaveParam([paramNumber:72, value:7, size:1]) } 793 | else if ( colorName == "Deep Fade" ) { updateZwaveParam([paramNumber:72, value:8, size:1]) } 794 | else if ( colorName == "Lite Fade" ) { updateZwaveParam([paramNumber:72, value:9, size:1]) } 795 | else if ( colorName == "Police" ) { updateZwaveParam([paramNumber:72, value:10, size:1]) } 796 | else if ( colorName == "White" ) { String.format("33050400${maxLevel}02${hex(0)}03${hex(0)}04${hex(0)}%02X", 100) } 797 | else if ( colorName == "Daylight" ) { String.format("33050400${maxLevel}02${maxLevel}03${maxLevel}04${maxLevel}%02X", 100) } 798 | else { 799 | def c = getColorData(colorName) 800 | def newValue = ["hue": c.h, "saturation": c.s, "level": level, "red": c.r, "green": c.g, "blue": c.b, "hex": c.hex, "alpha": c.alpha] 801 | setColor(newValue) 802 | def r = hex(c.r * (level/100)) 803 | def g = hex(c.g * (level/100)) 804 | def b = hex(c.b * (level/100)) 805 | def w = hex(0) //to turn off white channel with toggling tiles 806 | sendRGBW(r, g, b, w) 807 | } 808 | } 809 | 810 | def toggleTiles(color) { 811 | state.colorTiles = [] 812 | if ( !state.colorTiles ) { 813 | state.colorTiles = ["softwhite","daylight","warmwhite","red","green","blue","cyan","magenta","orange","purple","yellow","white","fireplace","storm","deepfade","litefade","police"] 814 | } 815 | 816 | def cmds = [] 817 | 818 | state.colorTiles.each({ 819 | if ( it == color ) { 820 | log.debug "Turning ${it} on" 821 | cmds << sendEvent(name: it, value: "on${it}", display: True, descriptionText: "${device.displayName} ${color} is 'ON'", isStateChange: true) 822 | } else { 823 | //log.debug "Turning ${it} off" 824 | cmds << sendEvent(name: it, value: "off${it}", displayed: false) 825 | } 826 | }) 827 | 828 | delayBetween(cmds, 2500) 829 | } 830 | 831 | // rows of buttons 832 | def softwhite() { doColorButton("Soft White") } 833 | def daylight() { doColorButton("Daylight") } 834 | def warmwhite() { doColorButton("Warm White") } 835 | 836 | def red() { doColorButton("Red") } 837 | def green() { doColorButton("Green") } 838 | def blue() { doColorButton("Blue") } 839 | 840 | def cyan() { doColorButton("Cyan") } 841 | def magenta() { doColorButton("Magenta") } 842 | def orange() { doColorButton("Orange") } 843 | 844 | def purple() { doColorButton("Purple") } 845 | def yellow() { doColorButton("Yellow") } 846 | def white() { doColorButton("White") } 847 | 848 | def fireplace() { doColorButton("Fire Place") } 849 | def storm() { doColorButton("Storm") } 850 | def deepfade() { doColorButton("Deep Fade") } 851 | 852 | def litefade() { doColorButton("Lite Fade") } 853 | def police() { doColorButton("Police") } 854 | -------------------------------------------------------------------------------- /Fibaro-RGBW-3-beta.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 SmartThings 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at: 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing permissions and limitations under the License. 12 | * 13 | * Z-Wave RGBW Light 14 | * 15 | * Author: SmartThings 16 | * Date: 2015-7-12 17 | */ 18 | 19 | //Todo remove isTileCommand stuff 20 | // figure better way of config 21 | 22 | metadata { 23 | definition (name: "Fibaro RGBW 3 Beta", namespace: "twack", author: "Todd Wackford") { 24 | capability "Switch Level" 25 | capability "Actuator" 26 | capability "Color Control" 27 | capability "Color Temperature" 28 | capability "Switch" 29 | capability "Refresh" 30 | capability "Sensor" 31 | capability "Power Meter" 32 | 33 | command "redOn" 34 | command "redOff" 35 | command "greenOn" 36 | command "greenOff" 37 | command "blueOn" 38 | command "blueOff" 39 | command "whiteOn" 40 | command "whiteOff" 41 | 42 | command "setRedLevel" 43 | command "setGreenLevel" 44 | command "setBlueLevel" 45 | command "setWhiteLevel" 46 | 47 | command "fireplaceOn" 48 | command "fireplaceOff" 49 | command "stormOn" 50 | command "stormOff" 51 | command "deepfadeOn" 52 | command "deepfadeOff" 53 | command "litefadeOn" 54 | command "litefadeOff" 55 | command "policeOn" 56 | command "policeOff" 57 | 58 | command "red" 59 | command "green" 60 | command "blue" 61 | command "white" 62 | command "cyan" 63 | command "magenta" 64 | command "orange" 65 | command "purple" 66 | command "yellow" 67 | command "pink" 68 | command "coldWhite" 69 | command "warmWhite" 70 | command "fireplace" 71 | command "storm" 72 | command "deepfade" 73 | command "litefade" 74 | command "police" 75 | 76 | command "reset" 77 | 78 | fingerprint deviceId: "0x1101", inClusters: "0x27,0x72,0x86,0x26,0x60,0x70,0x32,0x31,0x85,0x33" 79 | } 80 | 81 | simulator { 82 | } 83 | 84 | tiles (scale: 2){ 85 | multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ 86 | tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { 87 | attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff" 88 | attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" 89 | attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff" 90 | attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" 91 | } 92 | tileAttribute ("device.level", key: "SLIDER_CONTROL") { 93 | attributeState "level", action:"switch level.setLevel" 94 | } 95 | tileAttribute ("device.color", key: "COLOR_CONTROL") { 96 | attributeState "color", action:"setColor" 97 | } 98 | tileAttribute ("power", key: "SECONDARY_CONTROL") { 99 | attributeState "power", label:'${currentValue} W' 100 | } 101 | } 102 | 103 | controlTile("colorTempControl", "device.colorTemperature", "slider", height: 1, width: 5, inactiveLabel: false) { 104 | state "colorTemperature", action:"setColorTemperature" 105 | } 106 | 107 | ////////////////////////// 108 | standardTile("red", "device.red", height: 1, width: 1, inactiveLabel: false, canChangeIcon: false) { 109 | state "off", label:"R", action:"redOn", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 110 | state "on", label:"R", action:"redOff", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FF0000" 111 | } 112 | controlTile("redSliderControl", "device.redLevel", "slider", height: 1, width: 4, inactiveLabel: false) { 113 | state "redLevel", action:"setRedLevel" 114 | } 115 | valueTile("redValueTile", "device.redLevel", decoration: "flat", height: 1, width: 1) { 116 | state "redLevel", label:'${currentValue}%' 117 | } 118 | 119 | standardTile("green", "device.green", height: 1, width: 1, inactiveLabel: false, canChangeIcon: false) { 120 | state "off", label:"G", action:"greenOn", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 121 | state "on", label:"G", action:"greenOff", icon:"st.illuminance.illuminance.bright", backgroundColor:"#00FF00" 122 | } 123 | controlTile("greenSliderControl", "device.greenLevel", "slider", height: 1, width: 4, inactiveLabel: false) { 124 | state "greenLevel", action:"setGreenLevel" 125 | } 126 | valueTile("greenValueTile", "device.greenLevel", decoration: "flat", height: 1, width: 1) { 127 | state "greenLevel", label:'${currentValue}%' 128 | } 129 | 130 | standardTile("blue", "device.blue", height: 1, width:1, inactiveLabel: false, canChangeIcon: false) { 131 | state "off", label:"B", action:"blueOn", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 132 | state "on", label:"B", action:"blueOff", icon:"st.illuminance.illuminance.bright", backgroundColor:"#0000FF" 133 | } 134 | controlTile("blueSliderControl", "device.blueLevel", "slider", height: 1, width: 4, inactiveLabel: false) { 135 | state "blueLevel", action:"setBlueLevel" 136 | } 137 | valueTile("blueValueTile", "device.blueLevel", decoration: "flat", height: 1, width: 1) { 138 | state "blueLevel", label:'${currentValue}%' 139 | } 140 | 141 | standardTile("white", "device.white", height: 1, width: 1, inactiveLabel: false, canChangeIcon: false) { 142 | state "off", label:"W", action:"whiteOn", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 143 | state "on", label:"W", action:"whiteOff", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 144 | } 145 | controlTile("whiteSliderControl", "device.whiteLevel", "slider", height: 1, width: 4, inactiveLabel: false) { 146 | state "whiteLevel", action:"setWhiteLevel" 147 | } 148 | valueTile("whiteValueTile", "device.whiteLevel", decoration: "flat", height: 1, width: 1) { 149 | state "whiteLevel", label:'${currentValue}%' 150 | } 151 | 152 | standardTile("fireplace", "device.fireplace", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) { 153 | state "off", label:"Fire Place", action:"fireplaceOn", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 154 | state "on", label:"Fire Place", action:"fireplaceOff", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 155 | } 156 | standardTile("storm", "device.storm", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) { 157 | state "off", label:"storm", action:"stormOn", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 158 | state "on", label:"storm", action:"stormOff", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 159 | } 160 | standardTile("deepfade", "device.deepfade", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) { 161 | state "off", label:"deep fade", action:"deepfadeOn", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 162 | state "on", label:"deep fade", action:"deepfadeOff", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 163 | } 164 | standardTile("litefade", "device.litefade", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) { 165 | state "off", label:"lite fade", action:"litefadeOn", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 166 | state "on", label:"lite fade", action:"litefadeOff", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 167 | } 168 | standardTile("police", "device.police", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) { 169 | state "off", label:"police", action:"policeOn", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 170 | state "on", label:"police", action:"policeOff", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" 171 | } 172 | 173 | 174 | /////////////////// 175 | standardTile("cyan", "device.cyan", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) { 176 | state "offcyan", label:"cyan", action:"cyan", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 177 | state "oncyan", label:"cyan", action:"cyan", icon:"st.illuminance.illuminance.bright", backgroundColor:"#00FFFF" 178 | } 179 | standardTile("magenta", "device.magenta", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) { 180 | state "offmagenta", label:"magenta", action:"magenta", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 181 | state "onmagenta", label:"magenta", action:"magenta", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FF00FF" 182 | } 183 | standardTile("orange", "device.orange", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) { 184 | state "offorange", label:"orange", action:"orange", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 185 | state "onorange", label:"orange", action:"orange", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FF6600" 186 | } 187 | standardTile("purple", "device.purple", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) { 188 | state "offpurple", label:"purple", action:"purple", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 189 | state "onpurple", label:"purple", action:"purple", icon:"st.illuminance.illuminance.bright", backgroundColor:"#BF00FF" 190 | } 191 | standardTile("yellow", "device.yellow", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) { 192 | state "offyellow", label:"yellow", action:"yellow", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" 193 | state "onyellow", label:"yellow", action:"yellow", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFF00" 194 | } 195 | standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { 196 | state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single" 197 | } 198 | standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { 199 | state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" 200 | } 201 | 202 | 203 | 204 | } 205 | 206 | main(["switch"]) 207 | details(["switch", "levelSliderControl", "rgbSelector", 208 | "red", "redSliderControl", "redValueTile", 209 | "green", "greenSliderControl", "greenValueTile", 210 | "blue", "blueSliderControl", "blueValueTile", 211 | "white", "whiteSliderControl", "whiteValueTile", 212 | "fireplace", "storm", "deepfade", 213 | "litefade", "police", 214 | "refresh" ]) 215 | } 216 | 217 | def installed() { 218 | configure() 219 | } 220 | def updated() { 221 | //response(refresh()) 222 | configure() 223 | //getDeviceData() 224 | } 225 | def configure() { 226 | log.debug "Configuring Device For SmartThings Use" 227 | 228 | sendEvent(name: "redLevel", value: 99) 229 | sendEvent(name: "greenLevel", value: 99) 230 | sendEvent(name: "blueLevel", value: 99) 231 | sendEvent(name: "whiteLevel", value: 99) 232 | 233 | def cmds = [] 234 | cmds << zwave.associationV2.associationSet(groupingIdentifier:5, nodeId:[zwaveHubNodeId]).format() 235 | 236 | delayBetween(cmds, 500) 237 | 238 | } 239 | def parse(description) { 240 | def result = null 241 | if (description != "updated") { 242 | def cmd = zwave.parse(description, [0x20: 1, 0x26: 2, 0x70: 2, 0x72: 2, 0x60: 3, 0x33: 2, 0x32: 2, 0x31:2, 0x30: 2, 0x86: 1, 0x7A: 1]) 243 | 244 | if (cmd) { 245 | result = zwaveEvent(cmd) 246 | log.debug("zwaveEvent parsed to $result") 247 | } else { 248 | log.debug("Couldn't parse '$description'") 249 | } 250 | } 251 | result 252 | } 253 | 254 | def on() { 255 | log.debug "on()" 256 | //sendEvent(name: "switch", value: "on") 257 | log.info "running program: ${state.runningProgram}" 258 | log.info "colors are zeros is: ${colorsAreZeros()}" 259 | if ( state.runningProgram ) { 260 | turnProgramOn(state.runningProgram.programName, state.runningProgram.programNumber) 261 | } else if ( state.previousHexLevels && !colorsAreZeros()) { 262 | log.info "Recalling previous color settings: ${state.previousHexLevels}" 263 | resetToPreviousLevels(state.previousHexLevels) 264 | } else { 265 | delayBetween([zwave.basicV1.basicSet(value: 0xFF).format(), 266 | zwave.switchMultilevelV1.switchMultilevelGet().format(), 267 | configure(), 268 | getDeviceData() 269 | ], 5000) 270 | } 271 | } 272 | def off() { 273 | log.debug "off()" 274 | sendEvent(name: "switch", value: "off", displayed: true, isStateChange: true) 275 | toggleOffProgramTiles("allOfThem") 276 | toggleOffColorTiles() 277 | delayBetween ([zwave.basicV1.basicSet(value: 0x00).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000) 278 | } 279 | def setLevel(level) { 280 | setLevel(level, 1) 281 | } 282 | def setLevel(level, duration) { 283 | log.debug "setLevel() level = ${level}" 284 | if(level > 99) level = 99 285 | commands([ 286 | zwave.switchMultilevelV3.switchMultilevelSet(value: level, dimmingDuration: duration), 287 | zwave.switchMultilevelV3.switchMultilevelGet(), 288 | ], (duration && duration < 12) ? (duration * 1000) : 3500) 289 | } 290 | def setSaturation(percent) { 291 | log.debug "setSaturation($percent)" 292 | setColor(saturation: percent) 293 | } 294 | def setHue(value) { 295 | log.debug "setHue($value)" 296 | setColor(hue: value) 297 | } 298 | def setColor(value) { 299 | def result = [] 300 | log.debug "setColor: ${value}" 301 | 302 | if (value.hex) { 303 | log.debug "setting color with hex" 304 | def c = value.hex.findAll(/[0-9a-fA-F]{2}/).collect { Integer.parseInt(it, 16) } 305 | result << zwave.switchColorV3.switchColorSet(red:c[0], green:c[1], blue:c[2]) 306 | } else { 307 | log.debug "setting color with hue & saturation" 308 | def hue = value.hue ?: device.currentValue("hue") 309 | def saturation = value.saturation ?: device.currentValue("saturation") 310 | if(hue == null) hue = 13 311 | if(saturation == null) saturation = 13 312 | def rgb = huesatToRGB(hue as Integer, saturation as Integer) 313 | def whiteValue = device.currentValue("colorTemperature") 314 | result << zwave.switchColorV3.switchColorSet(red: rgb[0], green: rgb[1], blue: rgb[2]) 315 | } 316 | 317 | if(value.hue) sendEvent(name: "hue", value: value.hue, displayed: false) 318 | if(value.hex) sendEvent(name: "color", value: value.hex, displayed: false) 319 | if(value.switch) sendEvent(name: "switch", value: value.switch, displayed: false) 320 | if(value.saturation) sendEvent(name: "saturation", value: value.saturation, displayed: false) 321 | 322 | commands(result) 323 | } 324 | def setColorTemperature(percent) { 325 | log.debug "setColorTemperature Percent: ${percent}" 326 | 327 | if ( percent >= 1 ) 328 | sendEvent(name: "white", value: "onwhite", descriptionText: "White Channel is 'ON'", isStateChange: true) 329 | else 330 | sendEvent(name: "white", value: "offwhite", descriptionText: "White Channel is 'OFF'", displayed: false, isStateChange: true) 331 | 332 | if(percent > 99) percent = 99 333 | int warmValue = percent * 255 / 99 334 | 335 | command(zwave.switchColorV3.switchColorSet(warmWhite:warmValue, coldWhite:(255 - warmValue))) 336 | } 337 | 338 | def reset() { 339 | log.debug "reset()" 340 | sendEvent(name: "color", value: "#ffffff") 341 | setColorTemperature(99) 342 | } 343 | def refresh() { 344 | commands([ 345 | zwave.switchMultilevelV3.switchMultilevelGet(), 346 | ], 1000) 347 | } 348 | 349 | def redOn() { 350 | def color = "red" 351 | def value = 99 352 | log.debug "setting ${color} to ${value}" 353 | sendEvent(name: color, value: "on") 354 | sendEvent(name: "${color}Level", value: value) 355 | setRedLevel(value) 356 | } 357 | def redOff() { 358 | def color = "red" 359 | def value = 0 360 | log.debug "setting ${color} to ${value}" 361 | sendEvent(name: color, value: "off") 362 | sendEvent(name: "${color}Level", value: value) 363 | setRedLevel(value) 364 | } 365 | def setRedLevel(value) { 366 | toggleOffProgramTiles(value) 367 | log.debug "setRedLevel: ${value}" 368 | def level = Math.min(value as Integer, 99) 369 | level = 255 * level/99 as Integer 370 | log.debug "level: ${level}" 371 | if ( value > 0 ) { 372 | if (device.latestValue("switch") == "off") { on() } 373 | sendEvent(name: "red", value: "on") 374 | } else { 375 | sendEvent(name: "red", value: "off") 376 | } 377 | def redLevelNew = Math.min(value as Integer, 99) 378 | redLevelNew = 255 * redLevelNew/99 as Integer 379 | def greenLevelNew = Math.min(device.latestValue("greenLevel") as Integer, 99) 380 | greenLevelNew = 255 * greenLevelNew/99 as Integer 381 | def blueLevelNew = Math.min(device.latestValue("blueLevel") as Integer, 99) 382 | blueLevelNew = 255 * blueLevelNew/99 as Integer 383 | def hexColorNew = "#${hex(redLevelNew)}${hex(greenLevelNew)}${hex(blueLevelNew)}" 384 | setColor([hex: hexColorNew.toUpperCase()]) 385 | } 386 | def greenOn() { 387 | def color = "green" 388 | def value = 99 389 | log.debug "setting ${color} to ${value}" 390 | sendEvent(name: color, value: "on") 391 | sendEvent(name: "${color}Level", value: value) 392 | setGreenLevel(value) 393 | } 394 | def greenOff() { 395 | def color = "green" 396 | def value = 0 397 | log.debug "setting ${color} to ${value}" 398 | sendEvent(name: color, value: "off") 399 | setGreenLevel(value) 400 | } 401 | def setGreenLevel(value) { 402 | toggleOffProgramTiles(value) 403 | log.debug "setGreenLevel: ${value}" 404 | def level = Math.min(value as Integer, 99) 405 | level = 255 * level/99 as Integer 406 | log.debug "level: ${level}" 407 | if ( value > 0 ) { 408 | if (device.latestValue("switch") == "off") { on() } 409 | sendEvent(name: "green", value: "on") 410 | } else { 411 | sendEvent(name: "green", value: "off") 412 | } 413 | def redLevelNew = Math.min(device.latestValue("redLevel") as Integer, 99) 414 | redLevelNew = 255 * redLevelNew/99 as Integer 415 | def greenLevelNew = Math.min(value as Integer, 99) 416 | greenLevelNew = 255 * greenLevelNew/99 as Integer 417 | def blueLevelNew = Math.min(device.latestValue("blueLevel") as Integer, 99) 418 | blueLevelNew = 255 * blueLevelNew/99 as Integer 419 | 420 | def hexColorNew = "#${hex(redLevelNew)}${hex(greenLevelNew)}${hex(blueLevelNew)}" 421 | log.info "New Hex Color Code: ${hexColorNew.toUpperCase()}" 422 | 423 | setColor([hex: hexColorNew.toUpperCase(), isTileCommand: true]) 424 | } 425 | def blueOn() { 426 | def color = "blue" 427 | def value = 99 428 | log.debug "setting ${color} to ${value}" 429 | sendEvent(name: color, value: "on") 430 | setBlueLevel(value) 431 | } 432 | def blueOff() { 433 | def color = "blue" 434 | def value = 0 435 | log.debug "setting ${color} to ${value}" 436 | sendEvent(name: color, value: "off") 437 | setBlueLevel(value) 438 | } 439 | def setBlueLevel(value) { 440 | toggleOffProgramTiles(value) 441 | log.debug "setBlueLevel: ${value}" 442 | def level = Math.min(value as Integer, 99) 443 | level = 255 * level/99 as Integer 444 | log.debug "level: ${level}" 445 | if ( value > 0 ) { 446 | if (device.latestValue("switch") == "off") { on() } 447 | sendEvent(name: "blue", value: "on") 448 | } else { 449 | sendEvent(name: "blue", value: "off") 450 | } 451 | def redLevelNew = Math.min(device.latestValue("redLevel") as Integer, 99) 452 | redLevelNew = 255 * redLevelNew/99 as Integer 453 | def greenLevelNew = Math.min(device.latestValue("greenLevel") as Integer, 99) 454 | greenLevelNew = 255 * greenLevelNew/99 as Integer 455 | def blueLevelNew = Math.min(value as Integer, 99) 456 | blueLevelNew = 255 * blueLevelNew/99 as Integer 457 | 458 | def hexColorNew = "#${hex(redLevelNew)}${hex(greenLevelNew)}${hex(blueLevelNew)}" 459 | log.info "New Hex Color Code: ${hexColorNew.toUpperCase()}" 460 | 461 | setColor([hex: hexColorNew.toUpperCase(), isTileCommand: true]) 462 | } 463 | def whiteOn() { 464 | log.debug "whiteOn()" 465 | sendEvent(name: "white", value: "on", displayed: true, descriptionText: "White Channel is 'ON'", isStateChange: true) 466 | def channel = 0 467 | def whiteLevel = hex(255) 468 | def cmd = [String.format("3305010${channel}${whiteLevel}%02X", 50)] 469 | cmd 470 | } 471 | def whiteOff() { 472 | log.debug "whiteOff()" 473 | sendEvent(name: "white", value: "off", displayed: true, descriptionText: "White Channel is 'OFF'", isStateChange: true) 474 | def channel = 0 475 | def whiteLevel = hex(0) 476 | setWhiteLevel(0) 477 | def cmd = [String.format("3305010${channel}${whiteLevel}%02X", 50)] 478 | cmd 479 | } 480 | def setWhiteLevel(value) { 481 | log.debug "setwhiteLevel: ${value}" 482 | def level = Math.min(value as Integer, 99) 483 | level = 255 * level/99 as Integer 484 | log.debug "level: ${level}" 485 | if ( value > 0 ) { 486 | if (device.latestValue("switch") == "off") { on() } 487 | sendEvent(name: "white", value: "on") 488 | } else { 489 | sendEvent(name: "white", value: "off") 490 | } 491 | def channel = 0 492 | def whiteLevel = hex(level) 493 | def cmd = [String.format("3305010${channel}${whiteLevel}%02X", 50)] 494 | cmd 495 | } 496 | 497 | def fireplaceOn() { 498 | log.debug "fireplaceOn()" 499 | turnProgramOn("fireplace", 6) 500 | } 501 | def fireplaceOff() { 502 | log.debug "fireplaceOff()" 503 | turnProgamOff("fireplace") 504 | } 505 | def stormOn() { 506 | log.debug "stormOn()" 507 | turnProgramOn("storm", 7) 508 | } 509 | def stormOff() { 510 | log.debug "stormOff()" 511 | turnProgamOff("storm") 512 | } 513 | def deepfadeOn() { 514 | log.debug "deepfadeOn()" 515 | turnProgramOn("deepfade", 8) 516 | } 517 | def deepfadeOff() { 518 | log.debug "deepfadeOff()" 519 | turnProgamOff("deepfade") 520 | } 521 | def litefadeOn() { 522 | log.debug "litefadeOn()" 523 | turnProgramOn("litefade", 9) 524 | } 525 | def litefadeOff() { 526 | log.debug "litefadeOff()" 527 | turnProgamOff("litefade") 528 | } 529 | def policeOn() { 530 | log.debug "policeOn()" 531 | turnProgramOn("police", 10) 532 | } 533 | def policeOff() { 534 | log.debug "policeOff()" 535 | turnProgamOff("police") 536 | } 537 | 538 | def red() { 539 | def color = "red" 540 | log.debug "turning on ${color}()" 541 | def rgbColor = colorNameToRgb(color) 542 | def hexColor = rgbToHex(rgbColor) 543 | setColor(hex: hexColor) 544 | sendRGBW(hex(rgbColor.r), hex(rgbColor.g), hex(rgbColor.b), hex(0)) 545 | } 546 | def green() { 547 | def color = "green" 548 | log.debug "turning on ${color}()" 549 | def rgbColor = colorNameToRgb(color) 550 | def hexColor = rgbToHex(rgbColor) 551 | setColor(hex: hexColor) 552 | sendRGBW(hex(rgbColor.r), hex(rgbColor.g), hex(rgbColor.b), hex(0)) 553 | } 554 | def blue() { 555 | def color = "blue" 556 | log.debug "turning on ${color}()" 557 | def rgbColor = colorNameToRgb(color) 558 | def hexColor = rgbToHex(rgbColor) 559 | setColor(hex: hexColor) 560 | sendRGBW(hex(rgbColor.r), hex(rgbColor.g), hex(rgbColor.b), hex(0)) 561 | } 562 | def white() { 563 | def color = "coldWhite" 564 | log.debug "turning on white()" 565 | def rgbColor = colorNameToRgb(color) 566 | def hexColor = rgbToHex(rgbColor) 567 | setColor(hex: hexColor) 568 | sendRGBW(hex(rgbColor.r), hex(rgbColor.g), hex(rgbColor.b), hex(255)) 569 | } 570 | def cyan() { 571 | def color = "cyan" 572 | log.debug "turning on ${color}()" 573 | def rgbColor = colorNameToRgb(color) 574 | def hexColor = rgbToHex(rgbColor) 575 | setColor(hex: hexColor) 576 | sendRGBW(hex(rgbColor.r), hex(rgbColor.g), hex(rgbColor.b), hex(0)) 577 | } 578 | def magenta() { 579 | def color = "magenta" 580 | log.debug "turning on ${color}()" 581 | def rgbColor = colorNameToRgb(color) 582 | def hexColor = rgbToHex(rgbColor) 583 | setColor(hex: hexColor) 584 | sendRGBW(hex(rgbColor.r), hex(rgbColor.g), hex(rgbColor.b), hex(0)) 585 | } 586 | def orange() { 587 | def color = "orange" 588 | log.debug "turning on ${color}()" 589 | def rgbColor = colorNameToRgb(color) 590 | def hexColor = rgbToHex(rgbColor) 591 | setColor(hex: hexColor) 592 | sendRGBW(hex(rgbColor.r), hex(rgbColor.g), hex(rgbColor.b), hex(0)) 593 | } 594 | def purple() { 595 | def color = "purple" 596 | log.debug "turning on ${color}()" 597 | def rgbColor = colorNameToRgb(color) 598 | def hexColor = rgbToHex(rgbColor) 599 | setColor(hex: hexColor) 600 | sendRGBW(hex(rgbColor.r), hex(rgbColor.g), hex(rgbColor.b), hex(0)) 601 | } 602 | def yellow() { 603 | def color = "yellow" 604 | log.debug "turning on ${color}()" 605 | def rgbColor = colorNameToRgb(color) 606 | def hexColor = rgbToHex(rgbColor) 607 | setColor(hex: hexColor) 608 | sendRGBW(hex(rgbColor.r), hex(rgbColor.g), hex(rgbColor.b), hex(0)) 609 | } 610 | def pink() { 611 | def color = "pink" 612 | log.debug "turning on ${color}()" 613 | def rgbColor = colorNameToRgb(color) 614 | def hexColor = rgbToHex(rgbColor) 615 | setColor(hex: hexColor) 616 | sendRGBW(hex(rgbColor.r), hex(rgbColor.g), hex(rgbColor.b), hex(0)) 617 | } 618 | def coldWhite() { 619 | def color = "coldWhite" 620 | log.debug "turning on ${color}()" 621 | def rgbColor = colorNameToRgb(color) 622 | def hexColor = rgbToHex(rgbColor) 623 | setColor(hex: hexColor) 624 | sendRGBW(hex(rgbColor.r), hex(rgbColor.g), hex(rgbColor.b), hex(0)) 625 | } 626 | def warmWhite() { 627 | def color = "warmWhite" 628 | log.debug "turning on ${color}()" 629 | def rgbColor = colorNameToRgb(color) 630 | def hexColor = rgbToHex(rgbColor) 631 | setColor(hex: hexColor) 632 | sendRGBW(hex(rgbColor.r), hex(rgbColor.g), hex(rgbColor.b), hex(0)) 633 | } 634 | def fireplace() { fireplaceOn() } 635 | def storm() { stormOn() } 636 | def deepfade() { deepfadeOn() } 637 | def litefade() { litefadeOn() } 638 | def police() { policeOn() } 639 | 640 | def getCurrentHexLevels() { 641 | def redLevelNew = Math.min(device.latestValue("redLevel") as Integer, 99) 642 | redLevelNew = 255 * redLevelNew/99 as Integer 643 | def greenLevelNew = Math.min(device.latestValue("greenLevel") as Integer, 99) 644 | greenLevelNew = 255 * greenLevelNew/99 as Integer 645 | def blueLevelNew = Math.min(device.latestValue("blueLevel") as Integer, 99) 646 | blueLevelNew = 255 * blueLevelNew/99 as Integer 647 | def whiteLevelNew = Math.min(device.latestValue("whiteLevel") as Integer, 99) 648 | whiteLevelNew = 255 * whiteLevelNew/99 as Integer 649 | def currentHexLevels = ["red": hex(redLevelNew), 650 | "green": hex(greenLevelNew), 651 | "blue": hex(blueLevelNew), 652 | "white": hex(whiteLevelNew)] 653 | return currentHexLevels 654 | } 655 | def resetToPreviousLevels(values) { 656 | log.debug "resetToPreviousLevels with ${values}" 657 | def hexColorPrevious = "#${values.red}${values.green}${values.blue}" 658 | def colorString = getColorDataFromHex(hexColorPrevious) 659 | 660 | if ( Integer.parseInt(values.red,16) > 0 ) 661 | sendEvent(name: "red", value: "on") 662 | 663 | if ( Integer.parseInt(values.green,16) > 0 ) 664 | sendEvent(name: "green", value: "on") 665 | 666 | if ( Integer.parseInt(values.blue,16) > 0 ) 667 | sendEvent(name: "blue", value: "on") 668 | 669 | if ( Integer.parseInt(values.white,16) > 0 ) 670 | sendEvent(name: "white", value: "on") 671 | 672 | //for some reason we have a race condition with setColor and setWhiteLevel. Can't have both 673 | //setColor(colorString) 674 | //set the white channel to what it was 675 | //setWhiteLevel(values.white) 676 | 677 | //using raw device command because of issue above. When device reports back it will set sliders 678 | sendRGBW(values.red,values.green,values.blue,values.white) 679 | } 680 | def turnProgramOn(programName, programNumber) { 681 | log.debug "Turning ${programName} On" 682 | state.runningProgram = ["programName": programName, "programNumber": programNumber] 683 | state.previousHexLevels = getCurrentHexLevels() 684 | sendEvent(name: programName, value: "on") 685 | toggleOffProgramTiles(programName) 686 | toggleOffColorTiles() 687 | if ( device.latestValue("switch") == "off" ) 688 | sendEvent(name: "switch", value: "on") 689 | updateZwaveParam([paramNumber:72, value:programNumber, size:1]) 690 | } 691 | def turnProgamOff(programName) { 692 | sendEvent(name: programName, value: "off") 693 | state.runningProgram = null 694 | log.info "previous hex levels: ${state.previousHexLevels}" 695 | if ( state.previousHexLevels ) { 696 | log.info "Previous levels in fireplaceOff is: ${state.previousHexLevels}" 697 | resetToPreviousLevels(state.previousHexLevels) 698 | } else { 699 | log.info "No previous levels saved. Setting to all On" 700 | resetToPreviousLevels(["red": "FF", "green": "FF", "blue": "FF", "white": "FF"]) 701 | } 702 | } 703 | def toggleOffProgramTiles(exceptThisTile) { 704 | def programTiles = ["fireplace", "deepfade", "litefade", "storm", "police"] 705 | programTiles.each() { 706 | if ( it != exceptThisTile ) 707 | if ( device.latestValue(it) == "on" ) 708 | sendEvent(name: it, value: "${it}Off", displayed: false, isStateChange: true) 709 | } 710 | } 711 | def toggleOffColorTiles() { 712 | sendEvent(name: "red", value: "redOff", displayed: false, isStateChange: true) 713 | sendEvent(name: "green", value: "greenOff", displayed: false, isStateChange: true) 714 | sendEvent(name: "blue", value: "blueOff", displayed: false, isStateChange: true) 715 | sendEvent(name: "white", value: "whiteOff", displayed: false, isStateChange: true) 716 | } 717 | def toggleTiles(pickedColor) { 718 | log.debug "toggleTiles(${pickedColor})" 719 | def colorTiles = ["white","red","green","blue","cyan","magenta","orange","purple","yellow"] 720 | def programTiles = ["fireplace", "deepfade", "litefade", "storm", "police"] 721 | def allTiles = colorTiles + programTiles 722 | def cmds = [] 723 | def description = "" 724 | 725 | for ( tile in allTiles ) { 726 | description = "RGB Channels set to ${pickedColor.capitalize()}" 727 | if ( tile == pickedColor ) { //turn on the tile picked 728 | if ( programTiles.any { it == pickedColor } ) { 729 | description = "Running Program ${pickedColor.capitalize()}" 730 | } 731 | if ( pickedColor == "white" ) { 732 | cmds << sendEvent(name: pickedColor, value: "on${pickedColor}", display: false, isStateChange: false ) 733 | } else { 734 | cmds << sendEvent(name: pickedColor, value: "on${pickedColor}", display: true, descriptionText: description, isStateChange: true) 735 | } 736 | } else if ( pickedColor == "white" ) { 737 | for ( program in programTiles ) { 738 | cmds << sendEvent(name: program, value: "off${program}", displayed: false, isStateChange: true) 739 | } 740 | } else { 741 | //if tile is a program, turn off all other tiles 742 | if ( programTiles.any { it == pickedColor } ) { 743 | cmds << sendEvent(name: tile, value: "off${tile}", displayed: false, isStateChange: true) 744 | } 745 | //if tile is a color, turn off all other color tiles but white 746 | /*if ( colorTiles.any { it == pickedColor } ) { 747 | if (( pickedColor != "white" ) && ( tile != "white" )) { 748 | cmds << sendEvent(name: tile, value: "off${tile}", displayed: false, isStateChange: true) 749 | } 750 | }*/ 751 | } 752 | } 753 | delayBetween(cmds, 2500) 754 | } 755 | def colorsAreZeros() { 756 | def result = true 757 | def redVal = device.latestValue("redLevel") as Integer 758 | def greenVal = device.latestValue("greenLevel") as Integer 759 | def blueVal = device.latestValue("blueLevel") as Integer 760 | def whiteVal = device.latestValue("whiteLevel") as Integer 761 | log.info "total colors = ${redVal + greenVal + blueVal + whiteVal}" 762 | if ( redVal + greenVal + blueVal + whiteVal ) 763 | result = false 764 | result 765 | } 766 | def getColorDataFromHex(colorHex) { 767 | log.debug "getColorDataFromHex: ${colorHex}" 768 | 769 | def colorRGB = hexToRgb(colorHex) 770 | def colorHSL = rgbToHSL(colorRGB) 771 | 772 | def c = [:] 773 | c = [h: colorHSL.h, 774 | s: colorHSL.s, 775 | l: device.latestValue("level"), 776 | r: colorRGB.r, 777 | g: colorRGB.g, 778 | b: colorRGB.b, 779 | rh: hex(colorRGB.r), 780 | gh: hex(colorRGB.g), 781 | bh: hex(colorRGB.b), 782 | hex: colorHex, 783 | alpha: 1] 784 | 785 | def newValue = ["hex": c.hex, "hue": c.h, "saturation": c.s, "level": c.l, "red": c.r, "green": c.g, "blue": c.b, "alpha": c.alpha] 786 | return newValue 787 | } 788 | def rgbToHSL(rgb) { 789 | def r = rgb.r / 255 790 | def g = rgb.g / 255 791 | def b = rgb.b / 255 792 | def h = 0 793 | def s = 0 794 | def l = 0 795 | 796 | def var_min = [r,g,b].min() 797 | def var_max = [r,g,b].max() 798 | def del_max = var_max - var_min 799 | 800 | l = (var_max + var_min) / 2 801 | 802 | if (del_max == 0) { 803 | h = 0 804 | s = 0 805 | } else { 806 | if (l < 0.5) { s = del_max / (var_max + var_min) } 807 | else { s = del_max / (2 - var_max - var_min) } 808 | 809 | def del_r = (((var_max - r) / 6) + (del_max / 2)) / del_max 810 | def del_g = (((var_max - g) / 6) + (del_max / 2)) / del_max 811 | def del_b = (((var_max - b) / 6) + (del_max / 2)) / del_max 812 | 813 | if (r == var_max) { h = del_b - del_g } 814 | else if (g == var_max) { h = (1 / 3) + del_r - del_b } 815 | else if (b == var_max) { h = (2 / 3) + del_g - del_r } 816 | 817 | if (h < 0) { h += 1 } 818 | if (h > 1) { h -= 1 } 819 | } 820 | def hsl = [:] 821 | hsl = [h: h * 100, s: s * 100, l: l] 822 | 823 | hsl 824 | } 825 | def hexToRgb(colorHex) { 826 | def rrInt = Integer.parseInt(colorHex.substring(1,3),16) 827 | def ggInt = Integer.parseInt(colorHex.substring(3,5),16) 828 | def bbInt = Integer.parseInt(colorHex.substring(5,7),16) 829 | 830 | def colorData = [:] 831 | colorData = [r: rrInt, g: ggInt, b: bbInt] 832 | colorData 833 | } 834 | def rgbToHSV(red, green, blue) { 835 | float r = red / 255f 836 | float g = green / 255f 837 | float b = blue / 255f 838 | float max = [r, g, b].max() 839 | float delta = max - [r, g, b].min() 840 | def hue = 13 841 | def saturation = 0 842 | if (max && delta) { 843 | saturation = 100 * delta / max 844 | if (r == max) { 845 | hue = ((g - b) / delta) * 100 / 6 846 | } else if (g == max) { 847 | hue = (2 + (b - r) / delta) * 100 / 6 848 | } else { 849 | hue = (4 + (r - g) / delta) * 100 / 6 850 | } 851 | } 852 | [hue: hue, saturation: saturation, value: max * 100] 853 | } 854 | def huesatToRGB(float hue, float sat) { 855 | while(hue >= 100) hue -= 100 856 | int h = (int)(hue / 100 * 6) 857 | float f = hue / 100 * 6 - h 858 | int p = Math.round(255 * (1 - (sat / 100))) 859 | int q = Math.round(255 * (1 - (sat / 100) * f)) 860 | int t = Math.round(255 * (1 - (sat / 100) * (1 - f))) 861 | switch (h) { 862 | case 0: return [255, t, p] 863 | case 1: return [q, 255, p] 864 | case 2: return [p, 255, t] 865 | case 3: return [p, q, 255] 866 | case 4: return [t, p, 255] 867 | case 5: return [255, p, q] 868 | } 869 | } 870 | def adjustOutgoingHue(percent) { 871 | def adjusted = percent 872 | if (percent > 31) { 873 | if (percent < 63.0) { 874 | adjusted = percent + (7 * (percent -30 ) / 32) 875 | } 876 | else if (percent < 73.0) { 877 | adjusted = 69 + (5 * (percent - 62) / 10) 878 | } 879 | else { 880 | adjusted = percent + (2 * (100 - percent) / 28) 881 | } 882 | } 883 | log.info "percent: $percent, adjusted: $adjusted" 884 | adjusted 885 | } 886 | def setAdjustedColor(value) { 887 | if (value) { 888 | log.trace "setAdjustedColor: ${value}" 889 | def adjusted = value + [:] 890 | adjusted.hue = adjustOutgoingHue(value.hue) 891 | // Needed because color picker always sends 100 892 | adjusted.level = null 893 | setColor(adjusted) 894 | } 895 | } 896 | def sendRGBW(redHex, greenHex, blueHex, whiteHex) { 897 | def cmd = [String.format("33050400${whiteHex}02${redHex}03${greenHex}04${blueHex}%02X", 100),] 898 | cmd 899 | } 900 | private hex(value, width=2) { 901 | def s = new BigInteger(Math.round(value).toString()).toString(16) 902 | while (s.size() < width) { 903 | s = "0" + s 904 | } 905 | s 906 | } 907 | def rgbToHex(rgb) { 908 | def r = hex(rgb.r) 909 | def g = hex(rgb.g) 910 | def b = hex(rgb.b) 911 | def hexColor = "#${r}${g}${b}" 912 | 913 | hexColor 914 | } 915 | def colorNameToRgb(color) { 916 | final colors = [ 917 | [name:"red", r: 255, g: 0, b: 0 ], 918 | [name:"green", r: 0, g: 255, b: 0 ], 919 | [name:"blue", r: 0, g: 0, b: 255 ], 920 | 921 | [name:"cyan", r: 0, g: 255, b: 255 ], 922 | [name:"magenta", r: 255, g: 0, b: 33 ], 923 | [name:"orange", r: 255, g: 102, b: 0 ], 924 | 925 | [name:"purple", r: 170, g: 0, b: 255 ], 926 | [name:"yellow", r: 255, g: 160, b: 0 ], 927 | [name:"pink", r: 255, g: 192, b: 203 ], 928 | 929 | [name:"coldWhite", r: 255, g: 255, b: 255 ], 930 | [name:"warmWhite", r: 255, g: 255, b: 185 ] 931 | ] 932 | 933 | def colorData = [:] 934 | colorData = colors.find { it.name == color } 935 | 936 | colorData 937 | } 938 | def getDeviceData() { 939 | def cmd = [] 940 | cmd << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet()) 941 | cmd << response(zwave.versionV1.versionGet()) 942 | cmd << response(zwave.firmwareUpdateMdV1.firmwareMdGet()) 943 | delayBetween(cmd, 2500) 944 | } 945 | 946 | private dimmerEvents(physicalgraph.zwave.Command cmd) { 947 | def value = (cmd.value ? "on" : "off") 948 | def result = [createEvent(name: "switch", value: value, displayed: false)] 949 | if (cmd.value) { 950 | result << createEvent(name: "level", value: cmd.value, unit: "%") 951 | } 952 | return result 953 | } 954 | private command(physicalgraph.zwave.Command cmd) { 955 | if (state.sec) { 956 | zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format() 957 | } else { 958 | cmd.format() 959 | } 960 | } 961 | private commands(commands, delay=200) { 962 | delayBetween(commands.collect{ command(it) }, delay) 963 | } 964 | 965 | def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) { 966 | def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x26: 2, 0x30: 2, 0x31: 2, 0x32: 2, 0x33: 2, 0x70: 2]) // can specify command class versions here like in zwave.parse 967 | log.info ("Command from endpoint ${cmd.sourceEndPoint}: ${encapsulatedCommand}") 968 | if ((cmd.sourceEndPoint >= 1) && (cmd.sourceEndPoint <= 5)) { // we don't need color report 969 | if ( cmd.sourceEndPoint == 2 ) { 970 | sendEvent(name: "redLevel", value: encapsulatedCommand.value) 971 | if ( encapsulatedCommand.value > 0 ) { 972 | sendEvent(name: "red", value: "on") 973 | } else { 974 | sendEvent(name: "red", value: "off") 975 | } 976 | } 977 | if ( cmd.sourceEndPoint == 3 ) { 978 | sendEvent(name: "greenLevel", value: encapsulatedCommand.value) 979 | if ( encapsulatedCommand.value > 0 ) { 980 | sendEvent(name: "green", value: "on") 981 | } else { 982 | sendEvent(name: "green", value: "off") 983 | } 984 | } 985 | if ( cmd.sourceEndPoint == 4 ) { 986 | sendEvent(name: "blueLevel", value: encapsulatedCommand.value) 987 | if ( encapsulatedCommand.value > 0 ) { 988 | sendEvent(name: "blue", value: "on") 989 | } else { 990 | sendEvent(name: "blue", value: "off") 991 | } 992 | } 993 | if ( cmd.sourceEndPoint == 5 ) { 994 | sendEvent(name: "whiteLevel", value: encapsulatedCommand.value) 995 | if ( encapsulatedCommand.value > 0 ) { 996 | sendEvent(name: "white", value: "on") 997 | }else { 998 | sendEvent(name: "white", value: "off") 999 | } 1000 | } 1001 | def result = "Setting Color Level Sliders from Device Color Level Report" 1002 | return result 1003 | } else { 1004 | if (encapsulatedCommand) { 1005 | zwaveEvent(encapsulatedCommand) 1006 | } 1007 | } 1008 | } 1009 | def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) { 1010 | return "${device.displayName} parameter '${cmd.parameterNumber}' with a byte size of '${cmd.size}' is set to '${cmd.configurationValue}'" 1011 | } 1012 | def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStartLevelChange cmd) { 1013 | log.debug " in multi start" 1014 | } 1015 | def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStopLevelChange cmd) { 1016 | //[response(zwave.basicV1.basicGet())] 1017 | log.debug " in multi stop" 1018 | } 1019 | def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd) { 1020 | def result = [:] 1021 | if ( cmd.sensorType == 4 ) { //power level comming in 1022 | result.name = "power" 1023 | result.value = cmd.scaledSensorValue 1024 | result.descriptionText = "$device.displayName power usage is ${result.value} watt(s)" 1025 | result.isStateChange 1026 | sendEvent(name: result.name, value: result.value, displayed: false) 1027 | } 1028 | result 1029 | } 1030 | def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) { 1031 | dimmerEvents(cmd) 1032 | } 1033 | def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) { 1034 | dimmerEvents(cmd) 1035 | } 1036 | def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv3.SwitchMultilevelReport cmd) { 1037 | dimmerEvents(cmd) 1038 | } 1039 | def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) { 1040 | response(command(zwave.switchMultilevelV1.switchMultilevelGet())) 1041 | } 1042 | def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) { 1043 | def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x84: 1]) 1044 | if (encapsulatedCommand) { 1045 | state.sec = 1 1046 | def result = zwaveEvent(encapsulatedCommand) 1047 | result = result.collect { 1048 | if (it instanceof physicalgraph.device.HubAction && !it.toString().startsWith("9881")) { 1049 | response(cmd.CMD + "00" + it.toString()) 1050 | } else { 1051 | it 1052 | } 1053 | } 1054 | result 1055 | } 1056 | } 1057 | def zwaveEvent(physicalgraph.zwave.Command cmd) { 1058 | def linkText = device.label ?: device.name 1059 | [linkText: linkText, descriptionText: "$linkText: $cmd", displayed: false] 1060 | } 1061 | def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { 1062 | updateDataValue("Manufacturer manufacturerName", "${cmd.manufacturerName}") 1063 | updateDataValue("Manufacturer manufacturerId", "${cmd.manufacturerId}") 1064 | updateDataValue("Manufacturer productId", "${cmd.productId}") 1065 | updateDataValue("Manufacturer productTypeId", "${cmd.productTypeId}") 1066 | return "Received ManufacturerSpecificReport" 1067 | } 1068 | def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) { 1069 | updateDataValue("Version applicationVersion", "${cmd.applicationVersion}") 1070 | updateDataValue("Version applicationSubVersion", "${cmd.applicationSubVersion}") 1071 | updateDataValue("Version zWaveLibraryType", "${cmd.zWaveLibraryType}") 1072 | updateDataValue("Version zWaveProtocolVersion", "${cmd.zWaveProtocolVersion}") 1073 | updateDataValue("Version zWaveProtocolSubVersion", "${cmd.zWaveProtocolSubVersion}") 1074 | return "Received VersionReport" 1075 | } 1076 | def zwaveEvent(physicalgraph.zwave.commands.firmwareupdatemdv1.FirmwareMdReport cmd) { 1077 | updateDataValue("Firmware checksum", "${cmd.checksum}") 1078 | updateDataValue("Firmware firmwareId", "${cmd.firmwareId}") 1079 | updateDataValue("Firmware manufacturerId", "${cmd.manufacturerId}") 1080 | return "Received FirmwareMdReport" 1081 | } 1082 | def updateZwaveParam(params) { 1083 | if ( params ) { 1084 | def pNumber = params.paramNumber 1085 | def pSize = params.size 1086 | def pValue = [params.value] 1087 | log.debug "Updating ${device.displayName} parameter number '${pNumber}' with value '${pValue}' with size of '${pSize}'" 1088 | def cmds = [] 1089 | cmds << zwave.configurationV1.configurationSet(configurationValue: pValue, parameterNumber: pNumber, size: pSize).format() 1090 | cmds << zwave.configurationV1.configurationGet(parameterNumber: pNumber).format() 1091 | delayBetween(cmds, 1500) 1092 | } 1093 | } 1094 | --------------------------------------------------------------------------------