From fafa4cb01f847a3a145e427b9bf3e0b2e8e1e5f1 Mon Sep 17 00:00:00 2001 From: Nowak Date: Mon, 21 Mar 2016 03:51:54 -0500 Subject: [PATCH] MSA-971: Added Device Handler for Fibaro Wall Plug ZW5 --- .../fibaro-wall-plug-zw5.groovy | 254 ++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 devicetypes/fibargroup/fibaro-wall-plug-zw5.src/fibaro-wall-plug-zw5.groovy diff --git a/devicetypes/fibargroup/fibaro-wall-plug-zw5.src/fibaro-wall-plug-zw5.groovy b/devicetypes/fibargroup/fibaro-wall-plug-zw5.src/fibaro-wall-plug-zw5.groovy new file mode 100644 index 0000000..94d8efc --- /dev/null +++ b/devicetypes/fibargroup/fibaro-wall-plug-zw5.src/fibaro-wall-plug-zw5.groovy @@ -0,0 +1,254 @@ +/** + * Fibaro Wall Plug ZW5 + * + * Copyright 2016 Fibar Group S.A. + * + * 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: "Fibaro Wall Plug ZW5", namespace: "fibargroup", author: "Fibar Group S.A.") { + capability "Actuator" + capability "Configuration" + capability "Energy Meter" + capability "Power Meter" + capability "Refresh" + capability "Sensor" + capability "Switch" + + command "reset" + + fingerprint deviceId: "0x1001", inClusters: "0x5E, 0x22, 0x85, 0x59, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x32, 0x8E, 0x71, 0x73, 0x98, 0x31, 0x25, 0x86", outClusters: "" + } + + simulator { + + } + + tiles(scale: 2) { + multiAttributeTile(name:"FGWP", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app + tileAttribute("device.switch", key:"PRIMARY_CONTROL") { + attributeState("on", label: '${name}', action: "switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821") + attributeState("off", label: '${name}', action: "switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff") + } + + tileAttribute("device.power", key:"SECONDARY_CONTROL") { + attributeState("default", label:'${currentValue} W', backgroundColor:"#ffffff") + } + } + + valueTile("energy", "device.energy", width: 2, height: 2) { + state "default", label:'${currentValue} kWh' + } + standardTile("reset", "device.energy", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", label:'reset kWh', action:"reset" + } + standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" + } + + main "FGWP" + details(["FGWP", "energy", "reset", "refresh"]) + } +} + +// parse events into attributes +def parse(String description) { + log.debug "Parsing '${description}'" + + if (description.startsWith("Err 106")) { + if (state.sec) { + result = createEvent(descriptionText:description, displayed:false) + } else { + result = createEvent( + descriptionText: "FGK failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.", + eventType: "ALERT", + name: "secureInclusion", + value: "failed", + displayed: true, + ) + } + } else if (description == "updated") { + return null + } else { + def cmd = zwave.parse(description, [0x25: 1, 0x31: 5, 0x32: 1, 0x5A: 1, 0x71: 3, 0x72: 2, 0x86: 1]) + + if (cmd) { + log.debug "Parsed '${cmd}'" + zwaveEvent(cmd) + } + } +} + +//security +def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) { + def encapsulatedCommand = cmd.encapsulatedCommand([0x25: 1, 0x5A: 1]) + if (encapsulatedCommand) { + return zwaveEvent(encapsulatedCommand) + } else { + log.warn "Unable to extract encapsulated cmd from $cmd" + createEvent(descriptionText: cmd.toString()) + } +} + +//crc16 +def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) +{ + def versions = [0x31: 5, 0x32: 1, 0x71: 3, 0x72: 2, 0x86: 1] + def version = versions[cmd.commandClass as Integer] + def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass) + def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data) + if (!encapsulatedCommand) { + log.debug "Could not extract command from $cmd" + } else { + zwaveEvent(encapsulatedCommand) + } +} + +def zwaveEvent(physicalgraph.zwave.commands.meterv1.MeterReport cmd) { + if (cmd.scale == 0) { + createEvent(name: "energy", value: cmd.scaledMeterValue, unit: "kWh") + } else if (cmd.scale == 2) { + createEvent(name: "power", value: Math.round(cmd.scaledMeterValue), unit: "W") + } +} + +def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { + log.debug "manufacturerId: ${cmd.manufacturerId}" + log.debug "manufacturerName: ${cmd.manufacturerName}" + log.debug "productId: ${cmd.productId}" + log.debug "productTypeId: ${cmd.productTypeId}" +} + +def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) { + log.debug "deviceIdData: ${cmd.deviceIdData}" + log.debug "deviceIdDataFormat: ${cmd.deviceIdDataFormat}" + log.debug "deviceIdDataLengthIndicator: ${cmd.deviceIdDataLengthIndicator}" + log.debug "deviceIdType: ${cmd.deviceIdType}" + + if (cmd.deviceIdType == 1 && cmd.deviceIdDataFormat == 1) {//serial number in binary format + String serialNumber = "h'" + + cmd.deviceIdData.each{ data -> + serialNumber += "${String.format("%02X", data)}" + } + + updateDataValue("serialNumber", serialNumber) + log.debug "${device.displayName} - serial number: ${serialNumber}" + } +} + +def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) { + updateDataValue("version", "${cmd.applicationVersion}.${cmd.applicationSubVersion}") + log.debug "applicationVersion: ${cmd.applicationVersion}" + log.debug "applicationSubVersion: ${cmd.applicationSubVersion}" + log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}" + log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}" + log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}" +} + +def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) { + def map = [ displayed: true ] + if (cmd.sensorType == 4) { + createEvent(name: "power", value: Math.round(cmd.scaledSensorValue), unit: "W") + } +} + +def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) { + createEvent(name: "switch", value: cmd.value ? "on" : "off", type: "digital") +} + +def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) { + log.info "${device.displayName}: received command: $cmd - device has reset itself" +} + +def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) +{ + if (cmd.notificationType == 0x08) { + if (cmd.event == 0x06) { + createEvent(descriptionText: "Warning: $device.displayName detected over-current", isStateChange: true) + } else if (cmd.event == 0x08) { + createEvent(descriptionText: "Warning: $device.displayName detected over-load", isStateChange: true) + } + } +} + +def zwaveEvent(physicalgraph.zwave.Command cmd) { + log.debug "$device.displayName: Unhandled: $cmd" + [:] +} + +// handle commands +def configure() { + log.debug "Executing 'configure'" + + def cmds = [] + + cmds += zwave.manufacturerSpecificV1.manufacturerSpecificGet() + cmds += zwave.manufacturerSpecificV2.deviceSpecificGet() + cmds += zwave.versionV1.versionGet() + cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]) + cmds += zwave.meterV2.meterGet(scale:0) + cmds += zwave.meterV2.meterGet(scale:2) + cmds += zwave.switchBinaryV1.switchBinaryGet() + + encapSequence(cmds, 500) +} + +def refresh() { + log.debug "Executing 'refresh'" + + def cmds = [] + cmds += zwave.meterV2.meterGet(scale:0) + cmds += zwave.meterV2.meterGet(scale:2) + cmds += zwave.switchBinaryV1.switchBinaryGet() + + encapSequence(cmds, 500) +} + +def on() { + log.debug "Executing 'on'" + encap(zwave.switchBinaryV1.switchBinarySet(switchValue: 0xFF)) +} + +def off() { + log.debug "Executing 'off'" + encap(zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00)) +} + +def reset() { + log.debug "Executing 'reset'" + encap(zwave.meterV2.meterReset()) +} + +private secure(physicalgraph.zwave.Command cmd) { + zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format() +} + +private crc16(physicalgraph.zwave.Command cmd) { + //zwave.crc16encapV1.crc16Encap().encapsulate(cmd).format() + "5601${cmd.format()}0000" +} + +private encapSequence(commands, delay=200) { + delayBetween(commands.collect{ encap(it) }, delay) +} + +private encap(physicalgraph.zwave.Command cmd) { + def secureClasses = [0x25, 0x5A, 0x70, 0x85, 0x8E] + + //todo: check if secure inclusion was successful + //if not do not send security-encapsulated command + if (secureClasses.find{ it == cmd.commandClassId }) { + secure(cmd) + } else { + crc16(cmd) + } +} \ No newline at end of file