├── README.md ├── zemismart zigbee switch DTH (touch).groovy └── zemismart zigbee switch DTH.groovy /README.md: -------------------------------------------------------------------------------- 1 | # zigbee-device -------------------------------------------------------------------------------- /zemismart zigbee switch DTH (touch).groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Zemi ZigBee Switch - touch 3 | * 4 | * Copyright 2020 SmartThings 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | * use this file except in compliance with the License. You may obtain a copy 8 | * of the License at: 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | * License for the specific language governing permissions and limitations 16 | * under the License. 17 | * 18 | * author : fuls@naver.com 19 | */ 20 | public static String version() { return "v0.0.2.20200426" } 21 | /* 22 | * 2020/04/26 >>> v0.0.2.20200426 - Support to old Jemi swtich version 23 | * 2020/04/25 >>> v0.0.1.20200425 - Initialize 24 | */ 25 | 26 | import java.lang.Math 27 | 28 | metadata { 29 | definition(name: "Zemi ZigBee Switch", namespace: "zemismart", author: "Onaldo", ocfDeviceType: "oic.d.switch", vid: "generic-switch") { 30 | capability "Actuator" 31 | capability "Configuration" 32 | capability "Refresh" 33 | capability "Health Check" 34 | capability "Switch" 35 | 36 | command "childOn", ["string"] 37 | command "childOff", ["string"] 38 | 39 | // Zemi ZigBee Multi Switch 40 | fingerprint endpointId: "10", profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006", manufacturer: "Feibit Inc co.", model: "FB56+ZSW1GKJ2.7", deviceJoinName: "Zemi Zigbee Switch" 41 | fingerprint endpointId: "10", profileId: "0104", deviceId: "0002", inClusters: "0000, 0005, 0004, 0006", outClusters: "0000", manufacturer: "Feibit Inc co.", model: "FB56+ZSW1HKJ2.5", deviceJoinName: "Zemi Zigbee Switch 1" 42 | fingerprint endpointId: "10", profileId: "0104", deviceId: "0002", inClusters: "0000, 0003, 0004, 0005, 0006", manufacturer: "Feibit Inc co.", model: "FB56+ZSW1IKJ2.7", deviceJoinName: "Zemi Zigbee Switch 1" 43 | fingerprint endpointId: "0B", profileId: "C05E", inClusters: "0000, 0004, 0003, 0006, 0005, 1000, 0008", outClusters: "0019", manufacturer: "FeiBit", model: "FNB56-ZSW02LX2.0", deviceJoinName: "Zemi Zigbee Switch 1" 44 | fingerprint endpointId: "01", profileId: "C05E", inClusters: "0000, 0004, 0003, 0006, 0005, 1000, 0008", outClusters: "0019", manufacturer: "FeiBit", model: "FNB56-ZSW03LX2.0", deviceJoinName: "Zemi Zigbee Switch 1" 45 | } 46 | 47 | preferences { 48 | input type: "paragraph", element: "paragraph", title: "Version", description: version(), displayDuringSetup: false 49 | } 50 | 51 | // simulator metadata 52 | simulator { 53 | // status messages 54 | status "on": "on/off: 1" 55 | status "off": "on/off: 0" 56 | 57 | // reply messages 58 | reply "zcl on-off on": "on/off: 1" 59 | reply "zcl on-off off": "on/off: 0" 60 | } 61 | 62 | tiles(scale: 2) { 63 | multiAttributeTile(name: "switch", type: "lighting", width: 6, height: 4, canChangeIcon: true) { 64 | tileAttribute("device.switch", key: "PRIMARY_CONTROL") { 65 | attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#00A0DC", nextState: "turningOff" 66 | attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState: "turningOn" 67 | attributeState "turningOn", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#00A0DC", nextState: "turningOff" 68 | attributeState "turningOff", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState: "turningOn" 69 | } 70 | } 71 | standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { 72 | state "default", label: "", action: "refresh.refresh", icon: "st.secondary.refresh" 73 | } 74 | main "switch" 75 | details(["switch", "refresh"]) 76 | } 77 | } 78 | 79 | def installed() { 80 | log.debug "installed()" 81 | def endpointCount = getEndpointCount() 82 | if (endpointCount == 1) { 83 | // for 1 gang switch - ST Official local dth 84 | setDeviceType("ZigBee Switch") 85 | } else if (endpointCount > 1){ 86 | def model = device.getDataValue("model") 87 | if (model == 'FB56+ZSW1HKJ2.5' || model == 'FB56+ZSW1IKJ2.7') { 88 | device.updateDataValue("endpointId", "10") 89 | } 90 | // for multi switch, cloud device 91 | createChildDevices() 92 | } 93 | updateDataValue("onOff", "catchall") 94 | refresh() 95 | } 96 | 97 | def updated() { 98 | log.debug "updated()" 99 | updateDataValue("onOff", "catchall") 100 | refresh() 101 | } 102 | 103 | def parse(String description) { 104 | Map eventMap = zigbee.getEvent(description) 105 | Map eventDescMap = zigbee.parseDescriptionAsMap(description) 106 | 107 | if (!eventMap && eventDescMap) { 108 | eventMap = [:] 109 | if (eventDescMap?.clusterId == zigbee.ONOFF_CLUSTER) { 110 | eventMap[name] = "switch" 111 | eventMap[value] = eventDescMap?.value 112 | } 113 | } 114 | 115 | if (eventMap) { 116 | def endpointId = device.getDataValue("endpointId") 117 | log.debug "eventMap $eventMap | eventDescMap $eventDescMap" 118 | 119 | if (eventDescMap?.sourceEndpoint == endpointId) { 120 | log.debug "parse - sendEvent parent $eventDescMap.sourceEndpoint" 121 | sendEvent(eventMap) 122 | } else { 123 | log.debug "parse - sendEvent child $eventDescMap.sourceEndpoint" 124 | def childDevice = childDevices.find { 125 | it.deviceNetworkId == "$device.deviceNetworkId:${eventDescMap.sourceEndpoint}" 126 | } 127 | if (childDevice) { 128 | childDevice.sendEvent(eventMap) 129 | } else { 130 | log.debug "Child device: $device.deviceNetworkId:${eventDescMap.sourceEndpoint} was not found" 131 | def parentEndpointInt = zigbee.convertHexToInt(endpointId) 132 | def childEndpointInt = zigbee.convertHexToInt(eventDescMap?.sourceEndpoint) 133 | def childEndpointHexString = zigbee.convertToHexString(childEndpointInt, 2).toUpperCase() 134 | def deviceLabel = "${device.displayName[0..-2]}" 135 | def deviceIndex = Math.abs(childEndpointInt - parentEndpointInt) + 1 136 | createChildDevice("$deviceLabel$deviceIndex", childEndpointHexString) 137 | } 138 | } 139 | } 140 | } 141 | 142 | private getEndpointCount() { 143 | def model = device.getDataValue("model") 144 | 145 | switch (model) { 146 | case 'FNB56-ZSW02LX2.0' : return 2 147 | case 'FNB56-ZSW03LX2.0' : return 3 148 | case 'FB56+ZSW1GKJ2.7' : return 1 149 | case 'FB56+ZSW1HKJ2.5' : return 2 150 | case 'FB56+ZSW1IKJ2.7' : return 3 151 | default : return 0 152 | } 153 | } 154 | 155 | private void createChildDevices() { 156 | log.debug("createChildDevices of $device.deviceNetworkId") 157 | def endpointCount = getEndpointCount() 158 | def endpointId = device.getDataValue("endpointId") 159 | def endpointInt = zigbee.convertHexToInt(endpointId) 160 | def deviceLabel = "${device.displayName[0..-2]}" 161 | 162 | for (i in 1..endpointCount - 1) { 163 | def endpointHexString = zigbee.convertToHexString(endpointInt + i, 2).toUpperCase() 164 | createChildDevice("$deviceLabel${i + 1}", endpointHexString) 165 | } 166 | } 167 | 168 | private void createChildDevice(String deviceLabel, String endpointHexString) { 169 | def childDevice = childDevices.find { 170 | it.deviceNetworkId == "$device.deviceNetworkId:$endpointHexString" 171 | } 172 | if (!childDevice) { 173 | log.debug("Need to createChildDevice: $device.deviceNetworkId:$endpointHexString") 174 | addChildDevice("smartthings", "Child Switch Health", "$device.deviceNetworkId:$endpointHexString", device.hubId, 175 | [completedSetup: true, label: deviceLabel, isComponent: false]) 176 | } else { 177 | log.debug("createChildDevice: SKIP - $device.deviceNetworkId:${endpointHexString}") 178 | } 179 | } 180 | 181 | private getChildEndpoint(String dni) { 182 | dni.split(":")[-1] as String 183 | } 184 | 185 | def on() { 186 | log.debug("on") 187 | zigbee.on() 188 | } 189 | 190 | def off() { 191 | log.debug("off") 192 | zigbee.off() 193 | } 194 | 195 | def childOn(String dni) { 196 | log.debug("child on ${dni}") 197 | def childEndpoint = getChildEndpoint(dni) 198 | def endpointInt = zigbee.convertHexToInt(childEndpoint) 199 | zigbee.command(zigbee.ONOFF_CLUSTER, 0x01, "", [destEndpoint: endpointInt]) 200 | } 201 | 202 | def childOff(String dni) { 203 | log.debug("child off ${dni}") 204 | def childEndpoint = getChildEndpoint(dni) 205 | def endpointInt = zigbee.convertHexToInt(childEndpoint) 206 | zigbee.command(zigbee.ONOFF_CLUSTER, 0x00, "", [destEndpoint: endpointInt]) 207 | } 208 | 209 | /** 210 | * PING is used by Device-Watch in attempt to reach the Device 211 | * */ 212 | def ping() { 213 | return refresh() 214 | } 215 | 216 | def refresh() { 217 | def cmds = zigbee.onOffRefresh() 218 | def endpointCount = getEndpointCount() 219 | 220 | if (endpointCount > 1) { 221 | def endpointId = device.getDataValue("endpointId") 222 | def endpointInt = zigbee.convertHexToInt(endpointId) 223 | 224 | for (i in 1..endpointCount - 1) { 225 | def endpointValue = endpointInt + i 226 | cmds += zigbee.readAttribute(zigbee.ONOFF_CLUSTER, 0x0000, [destEndpoint: endpointValue]) 227 | } 228 | } else { 229 | cmds += zigbee.readAttribute(zigbee.ONOFF_CLUSTER, 0x0000, [destEndpoint: 0xFF]) 230 | } 231 | 232 | return cmds 233 | } 234 | 235 | def poll() { 236 | refresh() 237 | } 238 | 239 | def healthPoll() { 240 | log.debug "healthPoll()" 241 | def cmds = refresh() 242 | cmds.each { sendHubCommand(new physicalgraph.device.HubAction(it)) } 243 | } 244 | 245 | def configureHealthCheck() { 246 | Integer hcIntervalMinutes = 12 247 | if (!state.hasConfiguredHealthCheck) { 248 | log.debug "Configuring Health Check, Reporting" 249 | unschedule("healthPoll") 250 | runEvery5Minutes("healthPoll") 251 | def healthEvent = [name: "checkInterval", value: hcIntervalMinutes * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]] 252 | // Device-Watch allows 2 check-in misses from device 253 | sendEvent(healthEvent) 254 | childDevices.each { 255 | it.sendEvent(healthEvent) 256 | } 257 | state.hasConfiguredHealthCheck = true 258 | } 259 | } 260 | 261 | def configure() { 262 | log.debug "configure()" 263 | configureHealthCheck() 264 | 265 | //other devices supported by this DTH in the future 266 | def cmds = zigbee.onOffConfig(0, 120) 267 | def endpointCount = getEndpointCount() 268 | 269 | if (endpointCount > 1) { 270 | def endpointId = device.getDataValue("endpointId") 271 | def endpointInt = zigbee.convertHexToInt(endpointId) 272 | 273 | for (i in 1..endpointCount - 1) { 274 | def endpointValue = endpointInt + i 275 | cmds += zigbee.configureReporting(zigbee.ONOFF_CLUSTER, 0x0000, 0x10, 0, 120, null, [destEndpoint: endpointValue]) 276 | } 277 | } else { 278 | cmds += zigbee.configureReporting(zigbee.ONOFF_CLUSTER, 0x0000, 0x10, 0, 120, null, [destEndpoint: 0xFF]) 279 | } 280 | cmds += refresh() 281 | return cmds 282 | } -------------------------------------------------------------------------------- /zemismart zigbee switch DTH.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 SmartThings 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | * author : fuls@naver.com 17 | */ 18 | public static String version() { return "v0.0.1.20200424" } 19 | /* 20 | * 2020/04/24 >>> v0.0.1.20200424 - Initialize 21 | */ 22 | metadata { 23 | definition(name: "Zemi ZigBee Switch", namespace: "zemismart", author: "Onaldo", ocfDeviceType: "oic.d.switch", vid: "generic-switch") { 24 | capability "Actuator" 25 | capability "Configuration" 26 | capability "Refresh" 27 | capability "Health Check" 28 | capability "Switch" 29 | 30 | command "childOn", ["string"] 31 | command "childOff", ["string"] 32 | 33 | // Zemi ZigBee Multi Switch 34 | fingerprint endpointId: "0x10", profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006", manufacturer: "Feibit Inc co.", model: "FB56+ZSW1GKJ2.7", deviceJoinName: "Zemi Zigbee Switch" 35 | fingerprint endpointId: "0x0B", profileId: "C05E", inClusters: "0000, 0004, 0003, 0006, 0005, 1000, 0008", outClusters: "0019", manufacturer: "FeiBit", model: "FNB56-ZSW02LX2.0", deviceJoinName: "Zemi Zigbee Switch 1" 36 | fingerprint endpointId: "0x01", profileId: "C05E", inClusters: "0000, 0004, 0003, 0006, 0005, 1000, 0008", outClusters: "0019", manufacturer: "FeiBit", model: "FNB56-ZSW03LX2.0", deviceJoinName: "Zemi Zigbee Switch 1" 37 | } 38 | 39 | preferences { 40 | input type: "paragraph", element: "paragraph", title: "Version", description: version(), displayDuringSetup: false 41 | } 42 | 43 | // simulator metadata 44 | simulator { 45 | // status messages 46 | status "on": "on/off: 1" 47 | status "off": "on/off: 0" 48 | 49 | // reply messages 50 | reply "zcl on-off on": "on/off: 1" 51 | reply "zcl on-off off": "on/off: 0" 52 | } 53 | 54 | tiles(scale: 2) { 55 | multiAttributeTile(name: "switch", type: "lighting", width: 6, height: 4, canChangeIcon: true) { 56 | tileAttribute("device.switch", key: "PRIMARY_CONTROL") { 57 | attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#00A0DC", nextState: "turningOff" 58 | attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState: "turningOn" 59 | attributeState "turningOn", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#00A0DC", nextState: "turningOff" 60 | attributeState "turningOff", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState: "turningOn" 61 | } 62 | } 63 | standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { 64 | state "default", label: "", action: "refresh.refresh", icon: "st.secondary.refresh" 65 | } 66 | main "switch" 67 | details(["switch", "refresh"]) 68 | } 69 | } 70 | 71 | def installed() { 72 | log.debug "installed()" 73 | if (parent) { 74 | setDeviceType("Child Switch Health") 75 | } else { 76 | if (device.getDataValue("model") == "FB56+ZSW1GKJ2.7") { 77 | setDeviceType("ZigBee Switch") 78 | } else { 79 | createChildDevices() 80 | } 81 | 82 | updateDataValue("onOff", "catchall") 83 | refresh() 84 | } 85 | } 86 | 87 | def updated() { 88 | log.debug "updated()" 89 | updateDataValue("onOff", "catchall") 90 | refresh() 91 | } 92 | 93 | def parse(String description) { 94 | log.debug "description $description" 95 | Map eventMap = zigbee.getEvent(description) 96 | Map eventDescMap = zigbee.parseDescriptionAsMap(description) 97 | 98 | if (!eventMap && eventDescMap) { 99 | eventMap = [:] 100 | if (eventDescMap?.clusterId == zigbee.ONOFF_CLUSTER) { 101 | eventMap[name] = "switch" 102 | eventMap[value] = eventDescMap?.value 103 | } 104 | } 105 | 106 | if (eventMap) { 107 | log.debug "eventMap $eventMap" 108 | log.debug "eventDescMap $eventDescMap" 109 | if (eventDescMap?.sourceEndpoint == "0B" || eventDescMap?.sourceEndpoint == "01") { 110 | sendEvent(eventMap) 111 | } else { 112 | def childDevice = childDevices.find { 113 | it.deviceNetworkId == "$device.deviceNetworkId:${eventDescMap.sourceEndpoint}" 114 | } 115 | if (childDevice) { 116 | childDevice.sendEvent(eventMap) 117 | } else { 118 | log.debug "Child device: $device.deviceNetworkId:${eventDescMap.sourceEndpoint} was not found" 119 | } 120 | } 121 | } 122 | } 123 | 124 | private void createChildDevices() { 125 | log.debug("dni: $device.deviceNetworkId") 126 | def endPointMap = [] 127 | def x = getChildCount() 128 | if (x == 2 && device.getDataValue("model") == "FNB56-ZSW02LX2.0") { 129 | endPointMap = ["C"] 130 | } else { 131 | endPointMap = 2..x 132 | } 133 | 134 | def switchNum = 1 135 | for (i in endPointMap) { 136 | switchNum += 1 137 | addChildDevice("Zemi ZigBee Switch", "$device.deviceNetworkId:0${i}", device.hubId, 138 | [completedSetup: true, label: "Zemi ZigBee Switch ${switchNum}", isComponent: false]) 139 | } 140 | } 141 | 142 | private getChildEndpoint(String dni) { 143 | dni.split(":")[-1] as String 144 | } 145 | 146 | def on() { 147 | log.debug("on") 148 | zigbee.on() 149 | } 150 | 151 | def off() { 152 | log.debug("off") 153 | zigbee.off() 154 | } 155 | 156 | def childOn(String dni) { 157 | log.debug("child on ${dni}") 158 | def childEndpoint = getChildEndpoint(dni) 159 | zigbee.command(zigbee.ONOFF_CLUSTER, 0x01, "", [destEndpoint: childEndpoint]) 160 | } 161 | 162 | def childOff(String dni) { 163 | log.debug("child off ${dni}") 164 | def childEndpoint = getChildEndpoint(dni) 165 | zigbee.command(zigbee.ONOFF_CLUSTER, 0x00, "", [destEndpoint: childEndpoint]) 166 | } 167 | 168 | /** 169 | * PING is used by Device-Watch in attempt to reach the Device 170 | * */ 171 | def ping() { 172 | return refresh() 173 | } 174 | 175 | def refresh() { 176 | def cmds = zigbee.onOffRefresh() 177 | def x = getChildCount() 178 | 179 | def endPointMap = 2..x 180 | 181 | if (x == 2 && device.getDataValue("model") == "FNB56-ZSW02LX2.0") { 182 | endPointMap = [0x0C] 183 | } 184 | 185 | for (i in endPointMap) { 186 | cmds += zigbee.readAttribute(zigbee.ONOFF_CLUSTER, 0x0000, [destEndpoint: i]) 187 | } 188 | 189 | return cmds 190 | } 191 | 192 | def poll() { 193 | refresh() 194 | } 195 | 196 | def healthPoll() { 197 | log.debug "healthPoll()" 198 | def cmds = refresh() 199 | cmds.each { sendHubCommand(new physicalgraph.device.HubAction(it)) } 200 | } 201 | 202 | def configureHealthCheck() { 203 | Integer hcIntervalMinutes = 12 204 | if (!state.hasConfiguredHealthCheck) { 205 | log.debug "Configuring Health Check, Reporting" 206 | unschedule("healthPoll") 207 | runEvery5Minutes("healthPoll") 208 | def healthEvent = [name: "checkInterval", value: hcIntervalMinutes * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]] 209 | // Device-Watch allows 2 check-in misses from device 210 | sendEvent(healthEvent) 211 | childDevices.each { 212 | it.sendEvent(healthEvent) 213 | } 214 | state.hasConfiguredHealthCheck = true 215 | } 216 | } 217 | 218 | def configure() { 219 | log.debug "configure()" 220 | configureHealthCheck() 221 | 222 | //other devices supported by this DTH in the future 223 | def cmds = zigbee.onOffConfig(0, 120) 224 | def x = getChildCount() 225 | 226 | def endPointMap = 2..x 227 | 228 | if (x == 2 && device.getDataValue("model") == "FNB56-ZSW02LX2.0") { 229 | endPointMap = [0x0C] 230 | } 231 | 232 | for (i in endPointMap) { 233 | cmds += zigbee.configureReporting(zigbee.ONOFF_CLUSTER, 0x0000, 0x10, 0, 120, null, [destEndpoint: i]) 234 | } 235 | cmds += refresh() 236 | return cmds 237 | } 238 | 239 | private getChildCount() { 240 | if (device.getDataValue("model") == "FNB56-ZSW02LX2.0") { 241 | return 2 242 | } else if (device.getDataValue("model") == "FNB56-ZSW03LX2.0") { 243 | return 3 244 | } else { 245 | return 2 246 | } 247 | } 248 | --------------------------------------------------------------------------------