Skip to content

Commit

Permalink
[WWST-626] Add DTH for ZigBee Danalock V3 (SmartThingsCommunity#3254)
Browse files Browse the repository at this point in the history
* Add DTH for ZigBee Danalock V3

* Changing checkInterval value and responseMap.data

* changing folder name

* changing deviceJoinName

* Remove redundant files
  • Loading branch information
MMateusiakS authored and greens committed Jul 18, 2018
1 parent ed8dfcb commit e08c080
Show file tree
Hide file tree
Showing 2 changed files with 282 additions and 0 deletions.
26 changes: 26 additions & 0 deletions devicetypes/smartthings/zigbee-lock-without-codes.src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Danalock ZigBee

Local Execution

Works with:

* [Danalock V3 858125000074](https://danalock.com/products/danalock-v3-smart-lock/)

## Table of contents

* [Capabilities](#capabilities)
* [Device Health](#device-health)

## Capabilities

* **Configuration**
* **Health Check**
* **Sensor**
* **Battery**
* **Actuator**
* **Lock**
* **Refresh**

## Device Health
* __122 min__ checkInterval

Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
/**
*
* Copyright 2018 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/

import physicalgraph.zigbee.zcl.DataType

metadata {
definition (name:"ZigBee Lock Without Codes", namespace:"smartthings", author:"SmartThings", vid:"generic-lock-2", mnmn:"SmartThings", runLocally:true, minHubCoreVersion:'000.022.00013', executeCommandsLocally:true) {
capability "Actuator"
capability "Lock"
capability "Refresh"
capability "Sensor"
capability "Battery"
capability "Configuration"
capability "Health Check"

fingerprint profileId:"0104, 000A", inClusters:"0000, 0001, 0003, 0009, 0020,0101, 0B05", outclusters:"000A, 0019, 0B05", manufacturer:"Danalock", model:"V3-BTZB", deviceJoinName:"Danalock V3 Smart Lock"

}

tiles(scale:2) {
multiAttributeTile(name:"toggle", type:"generic", width:6, height:4) {
tileAttribute("device.lock", key:"PRIMARY_CONTROL"){
attributeState "locked", label:'locked', action:"lock.unlock", icon:"st.locks.lock.locked", backgroundColor:"#00A0DC", nextState:"unlocking"
attributeState "unlocked", label:'unlocked', action:"lock.lock", icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff", nextState:"locking"
attributeState "unknown", label:"unknown", action:"lock.lock", icon:"st.locks.lock.unknown", backgroundColor:"#ffffff", nextState:"locking"
attributeState "locking", label:'locking', icon:"st.locks.lock.locked", backgroundColor:"#00A0DC"
attributeState "unlocking", label:'unlocking', icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff"
}
}
standardTile("lock", "device.lock", inactiveLabel:false, decoration:"flat", width:2, height:2) {
state "default", label:'lock', action:"lock.lock", icon:"st.locks.lock.locked", nextState:"locking"
}
standardTile("unlock", "device.lock", inactiveLabel:false, decoration:"flat", width:2, height:2) {
state "default", label:'unlock', action:"lock.unlock", icon:"st.locks.lock.unlocked", nextState:"unlocking"

}
valueTile("battery", "device.battery", inactiveLabel:false, decoration:"flat", width:2, height:2) {
state "battery", label:'${currentValue}% battery', unit:""
}
standardTile("refresh", "device.refresh", inactiveLabel:false, decoration:"flat", width:2, height:2) {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}

main "toggle"
details(["toggle", "lock", "unlock", "battery", "refresh"])
}
}

private getCLUSTER_POWER() { 0x0001 }
private getCLUSTER_DOORLOCK() { 0x0101 }
private getDOORLOCK_CMD_LOCK_DOOR() { 0x00 }
private getDOORLOCK_CMD_UNLOCK_DOOR() { 0x01 }
private getDOORLOCK_RESPONSE_OPERATION_EVENT() { 0x20 }
private getDOORLOCK_RESPONSE_PROGRAMMING_EVENT() { 0x21 }
private getPOWER_ATTR_BATTERY_PERCENTAGE_REMAINING() { 0x0021 }
private getDOORLOCK_ATTR_LOCKSTATE() { 0x0000 }

def installed() {
log.debug "Executing installed()"
initialize()
}

def uninstalled() {
log.debug "Executing uninstalled()"
sendEvent(name:"lockRemoved", value:device.id, isStateChange:true, displayed:false)
}

def updated() {
try {
if (!state.init || !state.configured) {
state.init = true
def cmds = []
if (!state.configured) {
cmds << initialize()
} else {
cmds << refresh()
}

return response(cmds.flatten())
}
} catch (e) {
log.warn "ZigBee DTH - updated() threw exception:- $e"
}
return null
}

def ping() {
refresh()
}

def refresh() {
def cmds = zigbee.readAttribute(CLUSTER_DOORLOCK, DOORLOCK_ATTR_LOCKSTATE)
return cmds
}

def configure() {
def cmds = initialize()
return cmds
}

def initialize() {
log.debug "Executing initialize()"
state.configured = true
sendEvent(name:"checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed:false, data: [protocol:"zigbee", hubHardwareId:device.hub.hardwareID, offlinePingable:"1"])

def cmds = zigbee.configureReporting(CLUSTER_DOORLOCK, DOORLOCK_ATTR_LOCKSTATE,
DataType.ENUM8, 0, 3600, null) +
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING,DataType.UINT8, 600, 21600, 0x01) +
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING)

def allCmds = cmds + refresh()
return allCmds
}

def lock() {
def cmds = zigbee.command(CLUSTER_DOORLOCK, DOORLOCK_CMD_LOCK_DOOR) +
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING)

return cmds
}

def unlock() {
def cmds = zigbee.command(CLUSTER_DOORLOCK, DOORLOCK_CMD_UNLOCK_DOOR) +
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING)
return cmds
}

def parse(String description) {
def result = null
if (description) {
if (description.startsWith('read attr -')) {
result = parseAttributeResponse(description)
} else {
result = parseCommandResponse(description)
}
}
return result
}

private def parseAttributeResponse(String description) {
Map descMap = zigbee.parseDescriptionAsMap(description)
log.debug "Executing parseAttributeResponse() with description map:- $descMap"
def result = []
Map responseMap = [:]
def clusterInt = descMap.clusterInt
def attrInt = descMap.attrInt
def deviceName = device.displayName
responseMap.data = deviceName

if (clusterInt == CLUSTER_POWER && attrInt == POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) {
responseMap.name = "battery"

if (Integer.parseInt(descMap.value, 16) != 255) {
responseMap.value = Math.round(Integer.parseInt(descMap.value, 16) / 2)
responseMap.descriptionText = "Battery is at ${responseMap.value}%"
}

} else if (clusterInt == CLUSTER_DOORLOCK && attrInt == DOORLOCK_ATTR_LOCKSTATE) {
def value = Integer.parseInt(descMap.value, 16)
responseMap.name = "lock"
if (value == 0) {
responseMap.value = "unknown"
responseMap.descriptionText = "Unknown state"
} else if (value == 1) {
log.debug "locked"
responseMap.value = "locked"
responseMap.descriptionText = "Locked"
} else if (value == 2) {
log.debug "unlocked"
responseMap.value = "unlocked"
responseMap.descriptionText = "Unlocked"
} else {
responseMap.value = "unknown"
responseMap.descriptionText = "Unknown state"
}
} else {
return null
}
result << createEvent(responseMap)
return result
}

private def parseCommandResponse(String description) {
Map descMap = zigbee.parseDescriptionAsMap(description)
log.debug "Executing parseCommandResponse() with description map:- $descMap"

def deviceName = device.displayName
def result = []
Map responseMap = [:]
def data = descMap.data
def cmd = descMap.commandInt
def clusterInt = descMap.clusterInt
responseMap.data = deviceName

if (clusterInt == CLUSTER_DOORLOCK && (cmd == DOORLOCK_CMD_LOCK_DOOR || cmd == DOORLOCK_CMD_UNLOCK_DOOR)) {
def cmdList = []
cmdList << "delay 4200"
cmdList << zigbee.readAttribute(CLUSTER_DOORLOCK, DOORLOCK_ATTR_LOCKSTATE).first()
result << response(cmdList)
} else if (clusterInt == CLUSTER_DOORLOCK && cmd == DOORLOCK_RESPONSE_OPERATION_EVENT) {
def eventSource = Integer.parseInt(data[0], 16)
def eventCode = Integer.parseInt(data[1], 16)

responseMap.name = "lock"
responseMap.displayed = true
responseMap.isStateChange = true

if (eventSource == 1) {
responseMap.data = [method: "command"]
} else if (eventSource == 2) {
def desc = "manually"
responseMap.data = [method: "manual"]
}

switch (eventCode) {
case 1:
responseMap.value = "locked"
responseMap.descriptionText = "Locked ${desc}"
break
case 2:
responseMap.value = "unlocked"
responseMap.descriptionText = "Unlocked ${desc}"
break
default:
break
}
}

result << createEvent(responseMap)
return result
}

private Boolean secondsPast(timestamp, seconds) {
if (!(timestamp instanceof Number)) {
if (timestamp instanceof Date) {
timestamp = timestamp.time
} else if ((timestamp instanceof String) && timestamp.isNumber()) {
timestamp = timestamp.toLong()
} else {
return true
}
}
return (now() - timestamp) > (seconds * 1000)
}

0 comments on commit e08c080

Please sign in to comment.