Compare commits

...

4 Commits

Author SHA1 Message Date
Ngoc Hoang
96e0995e43 Modifying 'Publication Requests for Lumi Touch Switch, Dimmer and Shade' 2015-11-19 02:33:05 -06:00
Ngoc Hoang
bf7a7830cc Modifying 'Publication Requests for Lumi Touch Switch, Dimmer and Shade' 2015-09-09 02:06:50 -05:00
Ngoc Hoang
7d90555006 Modifying 'Publication Requests for Lumi Touch Switch' 2015-09-08 00:06:37 -05:00
Ngoc Hoang
04c19990cf MSA-65: Hi Smartthings Team,
I'm from Lumi Vietnam, and these are device types for our Touch Switch devices. You can see images and info of our devices here:http://www.lumi.com.vn/Elite-Version-pd-4
Our switches have from 1 to 4 Touch Button with each one on different endpoint: 1, 3, 5, 7.
These Device Types are written based on GE Switch template, and support control, read attribute, config report
Sorry for don't have simulator code because I didn't know how to write. But we have real devices for your testing.  I named it Lumi Switch 3 in my devices list. And our hub will online from 5 a.m to 7 p.m GTM+7. You can try it.

Thank you,
Ngoc
2015-09-04 04:09:36 -05:00
6 changed files with 1668 additions and 0 deletions

View File

@@ -0,0 +1,313 @@
/**
* Lumi Dimmer
*
* Copyright 2015 Lumi Vietnam
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Lumi Dimmer", namespace: "lumivietnam", author: "Lumi Vietnam") {
capability "Switch"
capability "Switch Level"
//capability "Configuration"
capability "Refresh"
capability "Actuator"
capability "Sensor"
fingerprint profileId: "0104", endpointId: "9", deviceId: "0101", inClusters: "0000, 0003, 0006, 0008", outClusters: "0000", manufacturer: "Lumi Vietnam", model: "LM-DZ1"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "off", label: "Off", action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState: "on"
state "on", label: "On", action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821", nextState: "off"
}
standardTile("on", "device.onAll", decoration: "flat") {
state "default", label: 'On', action: "on", icon: "st.switches.light.on", backgroundColor: "#ffffff"
}
standardTile("off", "device.offAll", decoration: "flat") {
state "default", label: 'Off', action: "off", icon: "st.switches.light.off", backgroundColor: "#ffffff"
}
controlTile("levelControl", "device.levelControl", "slider", width: 2, height: 1) {
state "default", action:"switch level.setLevel", backgroundColor:"#79b821"
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Level ${currentValue}%'
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "switch"
details(["switch", "on", "off", "levelControl", "level", "refresh"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
def finalResult = isKnownDescription(description)
if (finalResult != "false") {
log.info finalResult
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
}
else {
if (finalResult.type == "switch") {
sendEvent(name: finalResult.type, value: finalResult.value)
}
else if (finalResult.type == "level") {
sendEvent(name: "level", value: finalResult.value)
sendEvent(name: "levelControl", value: finalResult.value)
if (finalResult.value == 0)
sendEvent(name: "switch", value: "off")
else
sendEvent(name: "switch", value: "on")
}
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug parseDescriptionAsMap(description)
}
}
def off() {
log.debug "0x${device.deviceNetworkId} Endpoint ${endpointId}"
sendEvent(name: "level", value: "0")
sendEvent(name: "levelControl", value: "0")
"st cmd 0x${device.deviceNetworkId} ${endpointId} 0x0006 0 {}"
}
def on() {
log.debug "0x${device.deviceNetworkId} Endpoint ${endpointId}"
sendEvent(name: "level", value: "50")
sendEvent(name: "levelControl", value: "50")
"st cmd 0x${device.deviceNetworkId} ${endpointId} 0x0006 1 {}"
}
def setLevel(value) {
value = value as Integer
sendEvent(name: "level", value: value)
sendEvent(name: "levelControl", value: value)
if (value == 0) {
sendEvent(name: "switch", value: "off")
"st cmd 0x${device.deviceNetworkId} ${endpointId} 0x0006 0 {}"
}
else {
sendEvent(name: "switch", value: "on")
setLevelWithRate(value, "0000")// + on()
}
}
def refresh() {
[
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
]
}
def configure() {
onOffConfig() + levelConfig() + refresh()
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
//Need to reverse array of size 2
private byte[] reverseArray(byte[] array) {
byte tmp;
tmp = array[1];
array[1] = array[0];
array[0] = tmp;
return array
}
def parseDescriptionAsMap(description) {
if (description?.startsWith("read attr -")) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()): nameAndValue[1].trim()]
}
}
else if (description?.startsWith("catchall: ")) {
def seg = (description - "catchall: ").split(" ")
def zigbeeMap = [:]
zigbeeMap += [raw: (description - "catchall: ")]
zigbeeMap += [profileId: seg[0]]
zigbeeMap += [clusterId: seg[1]]
zigbeeMap += [sourceEndpoint: seg[2]]
zigbeeMap += [destinationEndpoint: seg[3]]
zigbeeMap += [options: seg[4]]
zigbeeMap += [messageType: seg[5]]
zigbeeMap += [dni: seg[6]]
zigbeeMap += [isClusterSpecific: Short.valueOf(seg[7], 16) != 0]
zigbeeMap += [isManufacturerSpecific: Short.valueOf(seg[8], 16) != 0]
zigbeeMap += [manufacturerId: seg[9]]
zigbeeMap += [command: seg[10]]
zigbeeMap += [direction: seg[11]]
zigbeeMap += [data: seg.size() > 12 ? seg[12].split("").findAll { it }.collate(2).collect {
it.join('')
} : []]
zigbeeMap
}
}
def isKnownDescription(description) {
if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) {
def descMap = parseDescriptionAsMap(description)
if (descMap.cluster == "0006" || descMap.clusterId == "0006") {
isDescriptionOnOff(descMap)
}
else if (descMap.cluster == "0008" || descMap.clusterId == "0008"){
isDescriptionLevel(descMap)
}
else {
return "false"
}
}
else if(description?.startsWith("on/off:")) {
def switchValue = description?.endsWith("1") ? "on" : "off"
return [type: "switch", value : switchValue]
}
else {
return "false"
}
}
def isDescriptionOnOff(descMap) {
def switchValue = "undefined"
if (descMap.cluster == "0006") { //cluster info from read attr
value = descMap.value
if (value == "01"){
switchValue = "on"
}
else if (value == "00"){
switchValue = "off"
}
}
else if (descMap.clusterId == "0006") {
//cluster info from catch all
//command 0B is Default response and the last two bytes are [on/off][success]. on/off=00, success=00
//command 01 is Read attr response. the last two bytes are [datatype][value]. boolean datatype=10; on/off value = 01/00
if ((descMap.command=="0B" && descMap.raw.endsWith("0100")) || (descMap.command=="01" && descMap.raw.endsWith("1001"))){
switchValue = "on"
}
else if ((descMap.command=="0B" && descMap.raw.endsWith("0000")) || (descMap.command=="01" && descMap.raw.endsWith("1000"))){
switchValue = "off"
}
else if(descMap.command=="07"){
return [type: "update", value : "switch (0006) capability configured successfully"]
}
}
if (switchValue != "undefined"){
return [type: "switch", value : switchValue]
}
else {
return "false"
}
}
//@return - false or "success" or level [0-100]
def isDescriptionLevel(descMap) {
def dimmerValue = -1
if (descMap.cluster == "0008"){
//TODO: the message returned with catchall is command 0B with clusterId 0008. That is just a confirmation message
def value = convertHexToInt(descMap.value)
dimmerValue = Math.round(value * 100 / 255)
if(dimmerValue==0 && value > 0) {
dimmerValue = 1 //handling for non-zero hex value less than 3
}
}
else if(descMap.clusterId == "0008") {
if(descMap.command=="0B"){
return [type: "update", value : "level updated successfully"] //device updating the level change was successful. no value sent.
}
else if(descMap.command=="07"){
return [type: "update", value : "level (0008) capability configured successfully"]
}
}
if (dimmerValue != -1){
return [type: "level", value : dimmerValue]
}
else {
return "false"
}
}
def onOffConfig() {
[
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 6 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 6 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
//level config for devices with min reporting interval as 5 seconds and reporting interval if no activity as 1hour (3600s)
//min level change is 01
def levelConfig() {
[
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 8 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 8 0 0x20 1 3600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
def setLevelWithRate(level, rate) {
if(rate == null){
rate = "0000"
}
level = convertToHexString(level * 255 / 100) //Converting the 0-100 range to 0-FF range in hex
["st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {$level $rate}"]
}
String convertToHexString(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}

View File

@@ -0,0 +1,317 @@
/**
* Lumi Shade
*
* Copyright 2015 Lumi Vietnam
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Lumi Shade", namespace: "lumivietnam", author: "Lumi Vietnam") {
capability "Switch"
capability "Switch Level"
//capability "Configuration"
capability "Refresh"
capability "Actuator"
capability "Sensor"
fingerprint profileId: "0104", endpointId: "9", deviceId: "0200", inClusters: "0000, 0003, 0006, 0008", outClusters: "0000", manufacturer: "Lumi Vietnam", model: "LM-BZ1"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "off", label: "Closed", action: "switch.on", icon: "st.doors.garage.garage-closed", backgroundColor: "#ffffff", nextState: "turningOn"
state "turningOn", label: "Opening", action:" switch.on", icon: "st.doors.garage.garage-opening", backgroundColor: "#79b821"
state "on", label: "Opened", action: "switch.off", icon: "st.doors.garage.garage-open", backgroundColor: "#79b821", nextState: "turningOff"
state "turningOff", label: "Closing", action:" switch.off", icon: "st.doors.garage.garage-closing", backgroundColor: "#ffffff"
}
standardTile("on", "device.onAll", decoration: "flat") {
state "default", label: 'Open', action: "on", icon: "st.doors.garage.garage-opening", backgroundColor: "#ffffff"
}
standardTile("off", "device.offAll", decoration: "flat") {
state "default", label: 'Close', action: "off", icon: "st.doors.garage.garage-closing", backgroundColor: "#ffffff"
}
controlTile("levelControl", "device.levelControl", "slider", width: 2, height: 1) {
state "default", action:"switch level.setLevel", backgroundColor:"#79b821"
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Shade ${currentValue}%'
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "switch"
details(["switch", "on", "off", "levelControl", "level", "refresh"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
def finalResult = isKnownDescription(description)
if (finalResult != "false") {
log.info finalResult
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
}
else {
if (finalResult.type == "switch") {
sendEvent(name: finalResult.type, value: finalResult.value)
}
else if (finalResult.type == "level") {
sendEvent(name: "level", value: finalResult.value)
sendEvent(name: "levelControl", value: finalResult.value)
if (finalResult.value == 0) {
sendEvent(name: "switch", value: "off")
}
else {
sendEvent(name: "switch", value: "on")
}
}
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug parseDescriptionAsMap(description)
}
}
def off() {
log.debug "0x${device.deviceNetworkId} Endpoint ${endpointId}"
sendEvent(name: "level", value: "100")
sendEvent(name: "levelControl", value: "0")
"st cmd 0x${device.deviceNetworkId} ${endpointId} 0x0006 0 {}"
}
def on() {
log.debug "0x${device.deviceNetworkId} Endpoint ${endpointId}"
sendEvent(name: "level", value: "100")
sendEvent(name: "levelControl", value: "0")
"st cmd 0x${device.deviceNetworkId} ${endpointId} 0x0006 1 {}"
}
def setLevel(value) {
value = value as Integer
sendEvent(name: "level", value: value)
sendEvent(name: "levelControl", value: value)
if (value == 100) {
sendEvent(name: "switch", value: "off")
"st cmd 0x${device.deviceNetworkId} ${endpointId} 0x0006 0 {}"
}
else {
sendEvent(name: "switch", value: "on")
setLevelWithRate(value, "0000")// + on()
}
}
def refresh() {
[
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
]
}
def configure() {
onOffConfig() + levelConfig() + refresh()
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
//Need to reverse array of size 2
private byte[] reverseArray(byte[] array) {
byte tmp;
tmp = array[1];
array[1] = array[0];
array[0] = tmp;
return array
}
def parseDescriptionAsMap(description) {
if (description?.startsWith("read attr -")) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()): nameAndValue[1].trim()]
}
}
else if (description?.startsWith("catchall: ")) {
def seg = (description - "catchall: ").split(" ")
def zigbeeMap = [:]
zigbeeMap += [raw: (description - "catchall: ")]
zigbeeMap += [profileId: seg[0]]
zigbeeMap += [clusterId: seg[1]]
zigbeeMap += [sourceEndpoint: seg[2]]
zigbeeMap += [destinationEndpoint: seg[3]]
zigbeeMap += [options: seg[4]]
zigbeeMap += [messageType: seg[5]]
zigbeeMap += [dni: seg[6]]
zigbeeMap += [isClusterSpecific: Short.valueOf(seg[7], 16) != 0]
zigbeeMap += [isManufacturerSpecific: Short.valueOf(seg[8], 16) != 0]
zigbeeMap += [manufacturerId: seg[9]]
zigbeeMap += [command: seg[10]]
zigbeeMap += [direction: seg[11]]
zigbeeMap += [data: seg.size() > 12 ? seg[12].split("").findAll { it }.collate(2).collect {
it.join('')
} : []]
zigbeeMap
}
}
def isKnownDescription(description) {
if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) {
def descMap = parseDescriptionAsMap(description)
if (descMap.cluster == "0006" || descMap.clusterId == "0006") {
isDescriptionOnOff(descMap)
}
else if (descMap.cluster == "0008" || descMap.clusterId == "0008"){
isDescriptionLevel(descMap)
}
else {
return "false"
}
}
else if(description?.startsWith("on/off:")) {
def switchValue = description?.endsWith("1") ? "on" : "off"
return [type: "switch", value : switchValue]
}
else {
return "false"
}
}
def isDescriptionOnOff(descMap) {
def switchValue = "undefined"
if (descMap.cluster == "0006") { //cluster info from read attr
value = descMap.value
if (value == "01"){
switchValue = "on"
}
else if (value == "00"){
switchValue = "off"
}
}
else if (descMap.clusterId == "0006") {
//cluster info from catch all
//command 0B is Default response and the last two bytes are [on/off][success]. on/off=00, success=00
//command 01 is Read attr response. the last two bytes are [datatype][value]. boolean datatype=10; on/off value = 01/00
if ((descMap.command=="0B" && descMap.raw.endsWith("0100")) || (descMap.command=="01" && descMap.raw.endsWith("1001"))){
switchValue = "on"
}
else if ((descMap.command=="0B" && descMap.raw.endsWith("0000")) || (descMap.command=="01" && descMap.raw.endsWith("1000"))){
switchValue = "off"
}
else if(descMap.command=="07"){
return [type: "update", value : "switch (0006) capability configured successfully"]
}
}
if (switchValue != "undefined"){
return [type: "switch", value : switchValue]
}
else {
return "false"
}
}
//@return - false or "success" or level [0-100]
def isDescriptionLevel(descMap) {
def dimmerValue = -1
if (descMap.cluster == "0008"){
//TODO: the message returned with catchall is command 0B with clusterId 0008. That is just a confirmation message
def value = convertHexToInt(descMap.value)
dimmerValue = Math.round(value * 100 / 255)
if(dimmerValue==0 && value > 0) {
dimmerValue = 1 //handling for non-zero hex value less than 3
}
}
else if(descMap.clusterId == "0008") {
if(descMap.command=="0B"){
return [type: "update", value : "level updated successfully"] //device updating the level change was successful. no value sent.
}
else if(descMap.command=="07"){
return [type: "update", value : "level (0008) capability configured successfully"]
}
}
if (dimmerValue != -1){
return [type: "level", value : dimmerValue]
}
else {
return "false"
}
}
def onOffConfig() {
[
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 6 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 6 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
//level config for devices with min reporting interval as 5 seconds and reporting interval if no activity as 1hour (3600s)
//min level change is 01
def levelConfig() {
[
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 8 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 8 0 0x20 1 3600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
def setLevelWithRate(level, rate) {
if(rate == null){
rate = "0000"
}
level = convertToHexString(level * 255 / 100) //Converting the 0-100 range to 0-FF range in hex
["st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {$level $rate}"]
}
String convertToHexString(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}

View File

@@ -0,0 +1,195 @@
/**
* Lumi Switch 4
*
* Copyright 2015 Lumi Vietnam
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Lumi Switch 1", namespace: "lumivietnam", author: "Lumi Vietnam") {
capability "Actuator"
//capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Switch"
attribute "switch1", "string"
command "on1"
command "off1"
fingerprint profileId: "0104", deviceId: "0100", inClusters: "0000, 0003, 0006", outClusters: "0000", manufacturer: "Lumi Vietnam", model: "LM-SZ1"
}
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off 1": "on/off: 1"
reply "zcl on-off 0": "on/off: 0"
}
tiles {
standardTile("switch1", "device.switch1", width: 2, height: 2, canChangeIcon: true) {
state "on1", label: "SW1", action: "off1", icon: "st.switches.light.on", backgroundColor: "#79b821", nextState: "off1"
state "off1", label: "SW1", action: "on1", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState: "on1"
}
standardTile("onAll", "device.onAll", decoration: "flat") {
state "default", label: 'On', action: "on1", icon: "st.switches.light.on", backgroundColor: "#ffffff"
}
standardTile("offAll", "device.offAll", decoration: "flat") {
state "default", label: 'Off', action: "off1", icon: "st.switches.light.off", backgroundColor: "#ffffff"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 1, height: 1) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "switch1"
details(["switch1", "onAll", "offAll", "refresh" ])
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
def finalResult = isKnownDescription(description)
if (finalResult != "false") {
log.info finalResult
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
}
else if (finalResult.type == "switch") {
if (finalResult.srcEP == "01") {
state.sw1 = finalResult.value;
sendEvent(name: "switch1", value: finalResult.value=="on"?"on1":"off1")
}
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug parseDescriptionAsMap(description)
}
}
// handle commands
def configure() {
log.debug "Executing 'configure'"
//Config binding and report for each endpoint
[
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0006 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x0006 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 1"
]
}
def refresh() {
log.debug "Executing 'refresh'"
//Read Attribute On Off Value
[
"st rattr 0x${device.deviceNetworkId} 1 0x0006 0"
]
}
def on1() {
log.debug "Executing 'on1' 0x${device.deviceNetworkId} endpoint 1"
"st cmd 0x${device.deviceNetworkId} 1 0x0006 1 {}"
}
def off1() {
log.debug "Executing 'off1' 0x${device.deviceNetworkId} endpoint 1"
"st cmd 0x${device.deviceNetworkId} 1 0x0006 0 {}"
}
def isKnownDescription(description) {
if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) {
def descMap = parseDescriptionAsMap(description)
if (descMap.cluster == "0006" || descMap.clusterId == "0006") {
isDescriptionOnOff(descMap)
}
else {
return "false"
}
}
else if(description?.startsWith("on/off:")) {
def switchValue = description?.endsWith("1") ? "on" : "off"
return [type: "switch", value : switchValue]
}
else {
return "false"
}
}
def parseDescriptionAsMap(description) {
if (description?.startsWith("read attr -")) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()): nameAndValue[1].trim()]
}
}
else if (description?.startsWith("catchall: ")) {
def seg = (description - "catchall: ").split(" ")
def zigbeeMap = [:]
zigbeeMap += [raw: (description - "catchall: ")]
zigbeeMap += [profileId: seg[0]]
zigbeeMap += [clusterId: seg[1]]
zigbeeMap += [sourceEndpoint: seg[2]]
zigbeeMap += [destinationEndpoint: seg[3]]
zigbeeMap += [options: seg[4]]
zigbeeMap += [messageType: seg[5]]
zigbeeMap += [dni: seg[6]]
zigbeeMap += [isClusterSpecific: Short.valueOf(seg[7], 16) != 0]
zigbeeMap += [isManufacturerSpecific: Short.valueOf(seg[8], 16) != 0]
zigbeeMap += [manufacturerId: seg[9]]
zigbeeMap += [command: seg[10]]
zigbeeMap += [direction: seg[11]]
zigbeeMap += [data: seg.size() > 12 ? seg[12].split("").findAll { it }.collate(2).collect {
it.join('')
} : []]
zigbeeMap
}
}
def isDescriptionOnOff(descMap) {
def switchValue = "undefined"
if (descMap.cluster == "0006") { //cluster info from read attr
value = descMap.value
if (value == "01"){
switchValue = "on"
}
else if (value == "00"){
switchValue = "off"
}
}
else if (descMap.clusterId == "0006") {
//cluster info from catch all
//command 0B is Default response and the last two bytes are [on/off][success]. on/off=00, success=00
//command 01 is Read attr response. the last two bytes are [datatype][value]. boolean datatype=10; on/off value = 01/00
if ((descMap.command=="0B" && descMap.raw.endsWith("0100")) || (descMap.command=="01" && descMap.raw.endsWith("1001"))){
switchValue = "on"
}
else if ((descMap.command=="0B" && descMap.raw.endsWith("0000")) || (descMap.command=="01" && descMap.raw.endsWith("1000"))){
switchValue = "off"
}
else if(descMap.command=="07"){
return [type: "update", value : "switch (0006) capability configured successfully"]
}
}
if (switchValue != "undefined"){
return [type: "switch", value : switchValue, srcEP : descMap.sourceEndpoint]
}
else {
return "false"
}
}

View File

@@ -0,0 +1,253 @@
/**
* Lumi Switch 2
*
* Copyright 2015 Lumi Vietnam
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Lumi Switch 2", namespace: "lumivietnam", author: "Lumi Vietnam") {
capability "Actuator"
//capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Switch"
attribute "switch1", "string"
attribute "switch2", "string"
attribute "switchAll", "string"
command "on1"
command "off1"
command "on2"
command "off2"
command "onAll"
command "offAll"
fingerprint profileId: "0104", deviceId: "0100", inClusters: "0000, 0003, 0006", outClusters: "0000", manufacturer: "Lumi Vietnam", model: "LM-SZ2"
}
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off 1": "on/off: 1"
reply "zcl on-off 0": "on/off: 0"
}
tiles {
standardTile("switch1", "device.switch1", canChangeIcon: true) {
state "on1", label: "SW1", action: "off1", icon: "st.switches.light.on", backgroundColor: "#79b821", nextState: "off1"
state "off1", label: "SW1", action: "on1", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState: "on1"
}
standardTile("switch2", "device.switch2", canChangeIcon: true) {
state "on2", label: "SW2", action: "off2", icon: "st.switches.light.on", backgroundColor: "#79b821", nextState: "off2"
state "off2", label: "SW2", action: "on2", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState: "on2"
}
standardTile("switchAll", "device.switchAll", canChangeIcon: false) {
state "onAll", label: "All", action: "offAll", icon: "st.lights.multi-light-bulb-on", backgroundColor: "#79b821", nextState: "offAll"
state "offAll", label: "All", action: "onAll", icon: "st.lights.multi-light-bulb-off", backgroundColor: "#ffffff", nextState: "onAll"
}
standardTile("onAll", "device.onAll", decoration: "flat") {
state "default", label: 'On All', action: "onAll", icon: "st.lights.multi-light-bulb-on", backgroundColor: "#ffffff"
}
standardTile("offAll", "device.offAll", decoration: "flat") {
state "default", label: 'Off All', action: "offAll", icon: "st.lights.multi-light-bulb-off", backgroundColor: "#ffffff"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 1, height: 1) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main (["switchAll", "switch1", "switch2"])
details(["switch1", "switch2", "switchAll", "onAll", "offAll", "refresh" ])
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
def finalResult = isKnownDescription(description)
if (finalResult != "false") {
log.info finalResult
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
}
else if (finalResult.type == "switch") {
if (finalResult.srcEP == "01") {
state.sw1 = finalResult.value;
sendEvent(name: "switch1", value: finalResult.value=="on"?"on1":"off1")
}
else if (finalResult.srcEP == "03") {
state.sw2 = finalResult.value;
sendEvent(name: "switch2", value: finalResult.value=="on"?"on2":"off2")
}
//update state for switchAll Tile
if (state.sw1 == "off" && state.sw2 == "off") {
//log.debug "offalll"
sendEvent(name: "switchAll", value: "offAll")
}
else if (state.sw1 == "on" && state.sw2 == "on") {
//log.debug "onall"
sendEvent(name: "switchAll", value: "onAll")
}
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug parseDescriptionAsMap(description)
}
}
// handle commands
def configure() {
log.debug "Executing 'configure'"
//Config binding and report for each endpoint
[
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0006 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x0006 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 3 1 0x0006 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x0006 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 3"
]
}
def refresh() {
log.debug "Executing 'refresh'"
//Read Attribute On Off Value of each endpoint
[
"st rattr 0x${device.deviceNetworkId} 1 0x0006 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 3 0x0006 0"
]
}
def on1() {
log.debug "Executing 'on1' 0x${device.deviceNetworkId} endpoint 1"
"st cmd 0x${device.deviceNetworkId} 1 0x0006 1 {}"
}
def off1() {
log.debug "Executing 'off1' 0x${device.deviceNetworkId} endpoint 1"
"st cmd 0x${device.deviceNetworkId} 1 0x0006 0 {}"
}
def on2() {
log.debug "Executing 'on2' 0x${device.deviceNetworkId} endpoint 3"
"st cmd 0x${device.deviceNetworkId} 3 0x0006 1 {}"
}
def off2() {
log.debug "Executing 'off2' 0x${device.deviceNetworkId} endpoint 3"
"st cmd 0x${device.deviceNetworkId} 3 0x0006 0 {}"
}
def onAll() {
log.debug "Executing 'onAll' 0x${device.deviceNetworkId} endpoint 1 3"
[
"st cmd 0x${device.deviceNetworkId} 1 0x0006 1 {}", "delay 200",
"st cmd 0x${device.deviceNetworkId} 3 0x0006 1 {}"
]
}
def offAll() {
log.debug "Executing 'offAll' 0x${device.deviceNetworkId} endpoint 1 3"
[
"st cmd 0x${device.deviceNetworkId} 1 0x0006 0 {}", "delay 200",
"st cmd 0x${device.deviceNetworkId} 3 0x0006 0 {}"
]
}
def isKnownDescription(description) {
if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) {
def descMap = parseDescriptionAsMap(description)
if (descMap.cluster == "0006" || descMap.clusterId == "0006") {
isDescriptionOnOff(descMap)
}
else {
return "false"
}
}
else if(description?.startsWith("on/off:")) {
def switchValue = description?.endsWith("1") ? "on" : "off"
return [type: "switch", value : switchValue]
}
else {
return "false"
}
}
def parseDescriptionAsMap(description) {
if (description?.startsWith("read attr -")) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()): nameAndValue[1].trim()]
}
}
else if (description?.startsWith("catchall: ")) {
def seg = (description - "catchall: ").split(" ")
def zigbeeMap = [:]
zigbeeMap += [raw: (description - "catchall: ")]
zigbeeMap += [profileId: seg[0]]
zigbeeMap += [clusterId: seg[1]]
zigbeeMap += [sourceEndpoint: seg[2]]
zigbeeMap += [destinationEndpoint: seg[3]]
zigbeeMap += [options: seg[4]]
zigbeeMap += [messageType: seg[5]]
zigbeeMap += [dni: seg[6]]
zigbeeMap += [isClusterSpecific: Short.valueOf(seg[7], 16) != 0]
zigbeeMap += [isManufacturerSpecific: Short.valueOf(seg[8], 16) != 0]
zigbeeMap += [manufacturerId: seg[9]]
zigbeeMap += [command: seg[10]]
zigbeeMap += [direction: seg[11]]
zigbeeMap += [data: seg.size() > 12 ? seg[12].split("").findAll { it }.collate(2).collect {
it.join('')
} : []]
zigbeeMap
}
}
def isDescriptionOnOff(descMap) {
def switchValue = "undefined"
if (descMap.cluster == "0006") { //cluster info from read attr
value = descMap.value
if (value == "01"){
switchValue = "on"
}
else if (value == "00"){
switchValue = "off"
}
}
else if (descMap.clusterId == "0006") {
//cluster info from catch all
//command 0B is Default response and the last two bytes are [on/off][success]. on/off=00, success=00
//command 01 is Read attr response. the last two bytes are [datatype][value]. boolean datatype=10; on/off value = 01/00
if ((descMap.command=="0B" && descMap.raw.endsWith("0100")) || (descMap.command=="01" && descMap.raw.endsWith("1001"))){
switchValue = "on"
}
else if ((descMap.command=="0B" && descMap.raw.endsWith("0000")) || (descMap.command=="01" && descMap.raw.endsWith("1000"))){
switchValue = "off"
}
else if(descMap.command=="07"){
return [type: "update", value : "switch (0006) capability configured successfully"]
}
}
if (switchValue != "undefined"){
return [type: "switch", value : switchValue, srcEP : descMap.sourceEndpoint]
}
else {
return "false"
}
}

View File

@@ -0,0 +1,281 @@
/**
* Lumi Switch 3
*
* Copyright 2015 Lumi Vietnam
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Lumi Switch 3", namespace: "lumivietnam", author: "Lumi Vietnam") {
capability "Actuator"
//capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Switch"
attribute "switch1", "string"
attribute "switch2", "string"
attribute "switch3", "string"
attribute "switchAll", "string"
command "on1"
command "off1"
command "on2"
command "off2"
command "on3"
command "off3"
command "onAll"
command "offAll"
fingerprint profileId: "0104", deviceId: "0100", inClusters: "0000, 0003, 0006", outClusters: "0000", manufacturer: "Lumi Vietnam", model: "LM-SZ3"
}
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off 1": "on/off: 1"
reply "zcl on-off 0": "on/off: 0"
}
tiles {
standardTile("switch1", "device.switch1", canChangeIcon: true) {
state "on1", label: "SW1", action: "off1", icon: "st.switches.light.on", backgroundColor: "#79b821", nextState: "off1"
state "off1", label: "SW1", action: "on1", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState: "on1"
}
standardTile("switch2", "device.switch2", canChangeIcon: true) {
state "on2", label: "SW2", action: "off2", icon: "st.switches.light.on", backgroundColor: "#79b821", nextState: "off2"
state "off2", label: "SW2", action: "on2", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState: "on2"
}
standardTile("switch3", "device.switch3", canChangeIcon: true) {
state "on3", label: "SW3", action: "off3", icon: "st.switches.light.on", backgroundColor: "#79b821", nextState: "off3"
state "off3", label: "SW3", action:"on3", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState: "on3"
}
standardTile("switchAll", "device.switchAll", canChangeIcon: false) {
state "onAll", label: "All", action: "offAll", icon: "st.lights.multi-light-bulb-on", backgroundColor: "#79b821", nextState: "offAll"
state "offAll", label: "All", action: "onAll", icon: "st.lights.multi-light-bulb-off", backgroundColor: "#ffffff", nextState: "onAll"
}
standardTile("onAll", "device.onAll", decoration: "flat") {
state "default", label: 'On All', action: "onAll", icon: "st.lights.multi-light-bulb-on", backgroundColor: "#ffffff"
}
standardTile("offAll", "device.offAll", decoration: "flat") {
state "default", label: 'Off All', action: "offAll", icon: "st.lights.multi-light-bulb-off", backgroundColor: "#ffffff"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 1, height: 1) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main (["switchAll", "switch1", "switch2", "switch3"])
details(["switch1", "switch2", "switch3", "onAll", "offAll", "switchAll", "refresh" ])
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
def finalResult = isKnownDescription(description)
if (finalResult != "false") {
log.info finalResult
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
}
else if (finalResult.type == "switch") {
if (finalResult.srcEP == "01") {
state.sw1 = finalResult.value;
sendEvent(name: "switch1", value: finalResult.value=="on"?"on1":"off1")
}
else if (finalResult.srcEP == "03") {
state.sw2 = finalResult.value;
sendEvent(name: "switch2", value: finalResult.value=="on"?"on2":"off2")
}
else if (finalResult.srcEP == "05") {
state.sw3 = finalResult.value;
sendEvent(name: "switch3", value: finalResult.value=="on"?"on3":"off3")
}
//update state for switchAll Tile
if (state.sw1 == "off" && state.sw2 == "off" && state.sw3 == "off") {
//log.debug "offalll"
sendEvent(name: "switchAll", value: "offAll")
}
else if (state.sw1 == "on" && state.sw2 == "on" && state.sw3 == "on") {
//log.debug "onall"
sendEvent(name: "switchAll", value: "onAll")
}
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug parseDescriptionAsMap(description)
}
}
// handle commands
def configure() {
log.debug "Executing 'configure'"
//Config binding and report for each endpoint
[
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0006 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x0006 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 3 1 0x0006 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x0006 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 3", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 5 1 0x0006 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x0006 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 5"
]
}
def refresh() {
log.debug "Executing 'refresh'"
//Read Attribute On Off Value of each endpoint
[
"st rattr 0x${device.deviceNetworkId} 1 0x0006 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 3 0x0006 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 5 0x0006 0"
]
}
def on1() {
log.debug "Executing 'on1' 0x${device.deviceNetworkId} endpoint 1"
"st cmd 0x${device.deviceNetworkId} 1 0x0006 1 {}"
}
def off1() {
log.debug "Executing 'off1' 0x${device.deviceNetworkId} endpoint 1"
"st cmd 0x${device.deviceNetworkId} 1 0x0006 0 {}"
}
def on2() {
log.debug "Executing 'on2' 0x${device.deviceNetworkId} endpoint 3"
"st cmd 0x${device.deviceNetworkId} 3 0x0006 1 {}"
}
def off2() {
log.debug "Executing 'off2' 0x${device.deviceNetworkId} endpoint 3"
"st cmd 0x${device.deviceNetworkId} 3 0x0006 0 {}"
}
def on3() {
log.debug "Executing 'on3' 0x${device.deviceNetworkId} endpoint 5"
"st cmd 0x${device.deviceNetworkId} 5 0x0006 1 {}"
}
def off3() {
log.debug "Executing 'off3' 0x${device.deviceNetworkId} endpoint 5"
"st cmd 0x${device.deviceNetworkId} 5 0x0006 0 {}"
}
def onAll() {
log.debug "Executing 'onAll' 0x${device.deviceNetworkId} endpoint 1 3 5"
[
"st cmd 0x${device.deviceNetworkId} 1 0x0006 1 {}", "delay 200",
"st cmd 0x${device.deviceNetworkId} 3 0x0006 1 {}", "delay 200",
"st cmd 0x${device.deviceNetworkId} 5 0x0006 1 {}"
]
}
def offAll() {
log.debug "Executing 'offAll' 0x${device.deviceNetworkId} endpoint 1 3 5"
[
"st cmd 0x${device.deviceNetworkId} 1 0x0006 0 {}", "delay 200",
"st cmd 0x${device.deviceNetworkId} 3 0x0006 0 {}", "delay 200",
"st cmd 0x${device.deviceNetworkId} 5 0x0006 0 {}"
]
}
def isKnownDescription(description) {
if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) {
def descMap = parseDescriptionAsMap(description)
if (descMap.cluster == "0006" || descMap.clusterId == "0006") {
isDescriptionOnOff(descMap)
}
else {
return "false"
}
}
else if(description?.startsWith("on/off:")) {
def switchValue = description?.endsWith("1") ? "on" : "off"
return [type: "switch", value : switchValue]
}
else {
return "false"
}
}
def parseDescriptionAsMap(description) {
if (description?.startsWith("read attr -")) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()): nameAndValue[1].trim()]
}
}
else if (description?.startsWith("catchall: ")) {
def seg = (description - "catchall: ").split(" ")
def zigbeeMap = [:]
zigbeeMap += [raw: (description - "catchall: ")]
zigbeeMap += [profileId: seg[0]]
zigbeeMap += [clusterId: seg[1]]
zigbeeMap += [sourceEndpoint: seg[2]]
zigbeeMap += [destinationEndpoint: seg[3]]
zigbeeMap += [options: seg[4]]
zigbeeMap += [messageType: seg[5]]
zigbeeMap += [dni: seg[6]]
zigbeeMap += [isClusterSpecific: Short.valueOf(seg[7], 16) != 0]
zigbeeMap += [isManufacturerSpecific: Short.valueOf(seg[8], 16) != 0]
zigbeeMap += [manufacturerId: seg[9]]
zigbeeMap += [command: seg[10]]
zigbeeMap += [direction: seg[11]]
zigbeeMap += [data: seg.size() > 12 ? seg[12].split("").findAll { it }.collate(2).collect {
it.join('')
} : []]
zigbeeMap
}
}
def isDescriptionOnOff(descMap) {
def switchValue = "undefined"
if (descMap.cluster == "0006") { //cluster info from read attr
value = descMap.value
if (value == "01"){
switchValue = "on"
}
else if (value == "00"){
switchValue = "off"
}
}
else if (descMap.clusterId == "0006") {
//cluster info from catch all
//command 0B is Default response and the last two bytes are [on/off][success]. on/off=00, success=00
//command 01 is Read attr response. the last two bytes are [datatype][value]. boolean datatype=10; on/off value = 01/00
if ((descMap.command=="0B" && descMap.raw.endsWith("0100")) || (descMap.command=="01" && descMap.raw.endsWith("1001"))){
switchValue = "on"
}
else if ((descMap.command=="0B" && descMap.raw.endsWith("0000")) || (descMap.command=="01" && descMap.raw.endsWith("1000"))){
switchValue = "off"
}
else if(descMap.command=="07"){
return [type: "update", value : "switch (0006) capability configured successfully"]
}
}
if (switchValue != "undefined"){
return [type: "switch", value : switchValue, srcEP : descMap.sourceEndpoint]
}
else {
return "false"
}
}

View File

@@ -0,0 +1,309 @@
/**
* Lumi Switch 4
*
* Copyright 2015 Lumi Vietnam
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Lumi Switch 4", namespace: "lumivietnam", author: "Lumi Vietnam") {
capability "Actuator"
//capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Switch"
attribute "switch1", "string"
attribute "switch2", "string"
attribute "switch3", "string"
attribute "switch4", "string"
attribute "switchAll", "string"
command "on1"
command "off1"
command "on2"
command "off2"
command "on3"
command "off3"
command "on4"
command "off4"
command "onAll"
command "offAll"
fingerprint profileId: "0104", deviceId: "0100", inClusters: "0000, 0003, 0006", outClusters: "0000", manufacturer: "Lumi Vietnam", model: "LM-SZ4"
}
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off 1": "on/off: 1"
reply "zcl on-off 0": "on/off: 0"
}
tiles {
standardTile("switch1", "device.switch1", canChangeIcon: true) {
state "on1", label: "SW1", action: "off1", icon: "st.switches.light.on", backgroundColor: "#79b821", nextState: "off1"
state "off1", label: "SW1", action: "on1", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState: "on1"
}
standardTile("switch2", "device.switch2", canChangeIcon: true) {
state "on2", label: "SW2", action: "off2", icon: "st.switches.light.on", backgroundColor: "#79b821", nextState: "off2"
state "off2", label: "SW2", action: "on2", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState: "on2"
}
standardTile("switch3", "device.switch3", canChangeIcon: true) {
state "on3", label: "SW3", action: "off3", icon: "st.switches.light.on", backgroundColor: "#79b821", nextState: "off3"
state "off3", label: "SW3", action:"on3", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState: "on3"
}
standardTile("switch4", "device.switch4", canChangeIcon: true) {
state "on4", label: "SW4", action: "off4", icon: "st.switches.light.on", backgroundColor: "#79b821", nextState: "off4"
state "off4", label: "SW4", action:"on4", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState: "on4"
}
standardTile("switchAll", "device.switchAll", canChangeIcon: false) {
state "onAll", label: "All", action: "offAll", icon: "st.lights.multi-light-bulb-on", backgroundColor: "#79b821", nextState: "offAll"
state "offAll", label: "All", action: "onAll", icon: "st.lights.multi-light-bulb-off", backgroundColor: "#ffffff", nextState: "onAll"
}
standardTile("onAll", "device.onAll", decoration: "flat") {
state "default", label: 'On All', action: "onAll", icon: "st.lights.multi-light-bulb-on", backgroundColor: "#ffffff"
}
standardTile("offAll", "device.offAll", decoration: "flat") {
state "default", label: 'Off All', action: "offAll", icon: "st.lights.multi-light-bulb-off", backgroundColor: "#ffffff"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 1, height: 1) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main (["switchAll", "switch1", "switch2", "switch3", "switch4"])
details(["switch1", "switch2", "onAll", "switch3", "switch4", "offAll", "switchAll", "refresh" ])
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
def finalResult = isKnownDescription(description)
if (finalResult != "false") {
log.info finalResult
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
}
else if (finalResult.type == "switch") {
if (finalResult.srcEP == "01") {
state.sw1 = finalResult.value;
sendEvent(name: "switch1", value: finalResult.value=="on"?"on1":"off1")
}
else if (finalResult.srcEP == "03") {
state.sw2 = finalResult.value;
sendEvent(name: "switch2", value: finalResult.value=="on"?"on2":"off2")
}
else if (finalResult.srcEP == "05") {
state.sw3 = finalResult.value;
sendEvent(name: "switch3", value: finalResult.value=="on"?"on3":"off3")
}
else if (finalResult.srcEP == "07") {
state.sw4 = finalResult.value;
sendEvent(name: "switch4", value: finalResult.value=="on"?"on4":"off4")
}
//update state for switchAll Tile
if (state.sw1 == "off" && state.sw2 == "off" && state.sw3 == "off" && state.sw4 == "off") {
//log.debug "offalll"
sendEvent(name: "switchAll", value: "offAll")
}
else if (state.sw1 == "on" && state.sw2 == "on" && state.sw3 == "on" && state.sw4 == "on") {
//log.debug "onall"
sendEvent(name: "switchAll", value: "onAll")
}
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug parseDescriptionAsMap(description)
}
}
// handle commands
def configure() {
log.debug "Executing 'configure'"
//Config binding and report for each endpoint
[
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0006 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x0006 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 3 1 0x0006 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x0006 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 3", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 5 1 0x0006 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x0006 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 5", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 7 1 0x0006 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x0006 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 7"
]
}
def refresh() {
log.debug "Executing 'refresh'"
//Read Attribute On Off Value of each endpoint
[
"st rattr 0x${device.deviceNetworkId} 1 0x0006 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 3 0x0006 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 5 0x0006 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 7 0x0006 0"
]
}
def on1() {
log.debug "Executing 'on1' 0x${device.deviceNetworkId} endpoint 1"
"st cmd 0x${device.deviceNetworkId} 1 0x0006 1 {}"
}
def off1() {
log.debug "Executing 'off1' 0x${device.deviceNetworkId} endpoint 1"
"st cmd 0x${device.deviceNetworkId} 1 0x0006 0 {}"
}
def on2() {
log.debug "Executing 'on2' 0x${device.deviceNetworkId} endpoint 3"
"st cmd 0x${device.deviceNetworkId} 3 0x0006 1 {}"
}
def off2() {
log.debug "Executing 'off2' 0x${device.deviceNetworkId} endpoint 3"
"st cmd 0x${device.deviceNetworkId} 3 0x0006 0 {}"
}
def on3() {
log.debug "Executing 'on3' 0x${device.deviceNetworkId} endpoint 5"
"st cmd 0x${device.deviceNetworkId} 5 0x0006 1 {}"
}
def off3() {
log.debug "Executing 'off3' 0x${device.deviceNetworkId} endpoint 5"
"st cmd 0x${device.deviceNetworkId} 5 0x0006 0 {}"
}
def on4() {
log.debug "Executing 'on4' 0x${device.deviceNetworkId} endpoint 7"
"st cmd 0x${device.deviceNetworkId} 7 0x0006 1 {}"
}
def off4() {
log.debug "Executing 'off4' 0x${device.deviceNetworkId} endpoint 7"
"st cmd 0x${device.deviceNetworkId} 7 0x0006 0 {}"
}
def onAll() {
log.debug "Executing 'onAll' 0x${device.deviceNetworkId} endpoint 1 3 5 7"
[
"st cmd 0x${device.deviceNetworkId} 1 0x0006 1 {}", "delay 200",
"st cmd 0x${device.deviceNetworkId} 3 0x0006 1 {}", "delay 200",
"st cmd 0x${device.deviceNetworkId} 5 0x0006 1 {}", "delay 200",
"st cmd 0x${device.deviceNetworkId} 7 0x0006 1 {}"
]
}
def offAll() {
log.debug "Executing 'offAll' 0x${device.deviceNetworkId} endpoint 1 3 5 7"
[
"st cmd 0x${device.deviceNetworkId} 1 0x0006 0 {}", "delay 200",
"st cmd 0x${device.deviceNetworkId} 3 0x0006 0 {}", "delay 200",
"st cmd 0x${device.deviceNetworkId} 5 0x0006 0 {}", "delay 200",
"st cmd 0x${device.deviceNetworkId} 7 0x0006 0 {}"
]
}
def isKnownDescription(description) {
if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) {
def descMap = parseDescriptionAsMap(description)
if (descMap.cluster == "0006" || descMap.clusterId == "0006") {
isDescriptionOnOff(descMap)
}
else {
return "false"
}
}
else if(description?.startsWith("on/off:")) {
def switchValue = description?.endsWith("1") ? "on" : "off"
return [type: "switch", value : switchValue]
}
else {
return "false"
}
}
def parseDescriptionAsMap(description) {
if (description?.startsWith("read attr -")) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()): nameAndValue[1].trim()]
}
}
else if (description?.startsWith("catchall: ")) {
def seg = (description - "catchall: ").split(" ")
def zigbeeMap = [:]
zigbeeMap += [raw: (description - "catchall: ")]
zigbeeMap += [profileId: seg[0]]
zigbeeMap += [clusterId: seg[1]]
zigbeeMap += [sourceEndpoint: seg[2]]
zigbeeMap += [destinationEndpoint: seg[3]]
zigbeeMap += [options: seg[4]]
zigbeeMap += [messageType: seg[5]]
zigbeeMap += [dni: seg[6]]
zigbeeMap += [isClusterSpecific: Short.valueOf(seg[7], 16) != 0]
zigbeeMap += [isManufacturerSpecific: Short.valueOf(seg[8], 16) != 0]
zigbeeMap += [manufacturerId: seg[9]]
zigbeeMap += [command: seg[10]]
zigbeeMap += [direction: seg[11]]
zigbeeMap += [data: seg.size() > 12 ? seg[12].split("").findAll { it }.collate(2).collect {
it.join('')
} : []]
zigbeeMap
}
}
def isDescriptionOnOff(descMap) {
def switchValue = "undefined"
if (descMap.cluster == "0006") { //cluster info from read attr
value = descMap.value
if (value == "01"){
switchValue = "on"
}
else if (value == "00"){
switchValue = "off"
}
}
else if (descMap.clusterId == "0006") {
//cluster info from catch all
//command 0B is Default response and the last two bytes are [on/off][success]. on/off=00, success=00
//command 01 is Read attr response. the last two bytes are [datatype][value]. boolean datatype=10; on/off value = 01/00
if ((descMap.command=="0B" && descMap.raw.endsWith("0100")) || (descMap.command=="01" && descMap.raw.endsWith("1001"))){
switchValue = "on"
}
else if ((descMap.command=="0B" && descMap.raw.endsWith("0000")) || (descMap.command=="01" && descMap.raw.endsWith("1000"))){
switchValue = "off"
}
else if(descMap.command=="07"){
return [type: "update", value : "switch (0006) capability configured successfully"]
}
}
if (switchValue != "undefined"){
return [type: "switch", value : switchValue, srcEP : descMap.sourceEndpoint]
}
else {
return "false"
}
}