Compare commits

..

1 Commits

Author SHA1 Message Date
Peter Alvmo
1b2baf1244 MSA-1970: lassicGOD:Fibaro Wall Plug ZW5 2017-05-12 07:07:29 -07:00
2 changed files with 735 additions and 212 deletions

View File

@@ -0,0 +1,735 @@
/**
* Fibaro Wall Plug ZW5
* Requires: Fibaro Double Switch 2 Child Device
*
* Copyright 2017 Artur Draga
*
* 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: "ClassicGOD", author: "Artur Draga") {
capability "Switch"
capability "Energy Meter"
capability "Power Meter"
capability "Configuration"
capability "Health Check"
command "reset"
command "refresh"
fingerprint deviceId: "0x1001", inClusters:"0x5E,0x22,0x59,0x56,0x7A,0x32,0x71,0x73,0x98,0x31,0x85,0x70,0x72,0x5A,0x8E,0x25,0x86"
fingerprint deviceId: "0x1001", inClusters:"0x5E,0x22,0x59,0x56,0x7A,0x32,0x71,0x73,0x31,0x85,0x70,0x72,0x5A,0x8E,0x25,0x86"
}
tiles (scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 3, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "off", label: 'Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState:"turningOn"
attributeState "on", label: 'On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00a0dc", nextState:"turningOff"
attributeState "turningOn", label:'Turning On', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00a0dc", nextState:"turningOff"
attributeState "turningOff", label:'Turning Off', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute("device.combinedMeter", key:"SECONDARY_CONTROL") {
attributeState("combinedMeter", label:'${currentValue}')
}
}
valueTile("power", "device.power", decoration: "flat", width: 2, height: 2) {
state "power", label:'${currentValue}\nW', action:"refresh"
}
valueTile("energy", "device.energy", decoration: "flat", width: 2, height: 2) {
state "energy", label:'${currentValue}\nkWh', action:"refresh"
}
valueTile("reset", "device.energy", decoration: "flat", width: 2, height: 2) {
state "reset", label:'reset\nkWh', action:"reset"
}
}
preferences {
input ( name: "logging", title: "Logging", type: "boolean", required: false )
parameterMap().each {
input (
name: it.key,
title: "${it.num}. ${it.title}",
description: it.descr,
type: it.type,
options: it.options,
range: (it.min != null && it.max != null) ? "${it.min}..${it.max}" : null,
defaultValue: it.def,
required: false
)
}
}
}
//UI and tile functions
def on() {
encap(zwave.basicV1.basicSet(value: 255))
}
def off() {
encap(zwave.basicV1.basicSet(value: 0))
}
def reset() {
def cmds = []
cmds << zwave.meterV3.meterReset()
cmds << zwave.meterV3.meterGet(scale: 0)
cmds << zwave.meterV3.meterGet(scale: 2)
encapSequence(cmds,1000)
}
def refresh() {
def cmds = []
cmds << zwave.meterV3.meterGet(scale: 0)
cmds << zwave.meterV3.meterGet(scale: 2)
cmds << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 4)
encapSequence(cmds,1000)
}
//Configuration and synchronization
def updated() {
if ( state.lastUpdated && (now() - state.lastUpdated) < 500 ) return
def cmds = []
logging("${device.displayName} - Executing updated()","info")
def Integer cmdCount = 0
parameterMap().each {
if(settings."$it.key" != null) {
if (state."$it.key" == null) { state."$it.key" = [value: null, state: "synced"] }
if (state."$it.key".value != settings."$it.key" as Integer || state."$it.key".state == "notSynced") {
state."$it.key".value = settings."$it.key" as Integer
state."$it.key".state = "notSynced"
cmds << zwave.configurationV2.configurationSet(configurationValue: intToParam(state."$it.key".value, it.size), parameterNumber: it.num, size: it.size)
cmds << zwave.configurationV2.configurationGet(parameterNumber: it.num)
cmdCount = cmdCount + 1
}
}
}
if ( cmdCount > 0 ) {
logging("${device.displayName} - sending config.", "info")
sendEvent(name: "combinedMeter", value: "SYNC IN PROGRESS.", displayed: false)
runIn((5+cmdCount*2), syncCheck)
}
state.lastUpdated = now()
if (cmds) { response(encapSequence(cmds,1000)) }
}
def syncCheck() {
logging("${device.displayName} - Executing syncCheck()","info")
def Integer count = 0
if (device.currentValue("combinedMeter")?.contains("SYNC") && device.currentValue("combinedMeter") != "SYNC OK.") {
parameterMap().each {
if (state."$it.key".state == "notSynced" ) {
count = count + 1
}
}
}
if (count == 0) {
logging("${device.displayName} - Sync Complete","info")
sendEvent(name: "combinedMeter", value: "SYNC OK.", displayed: false)
} else {
logging("${device.displayName} Sync Incomplete","info")
if (device.currentValue("combinedMeter") != "SYNC FAILED!") {
sendEvent(name: "combinedMeter", value: "SYNC INCOMPLETE.", displayed: false)
}
}
}
//event handlers related to configuration and sync
def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) {
def paramKey = parameterMap().find( {it.num == cmd.parameterNumber } ).key
logging("${device.displayName} - Parameter ${paramKey} value is ${cmd.scaledConfigurationValue} expected " + state."$paramKey".value, "info")
if (state."$paramKey".value == cmd.scaledConfigurationValue) {
state."$paramKey".state = "synced"
}
}
def zwaveEvent(physicalgraph.zwave.commands.applicationstatusv1.ApplicationRejectedRequest cmd) {
logging("${device.displayName} - rejected request!","warn")
if (device.currentValue("combinedMeter") == "SYNC IN PROGRESS.") {
sendEvent(name: "combinedMeter", value: "SYNC FAILED!", displayed: false)
}
}
//event handlers
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
//ignore
}
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
logging("${device.displayName} - SwitchBinaryReport received, value: ${cmd.value}","info")
sendEvent([name: "switch", value: (cmd.value == 0 ) ? "off": "on"])
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
logging("${device.displayName} - SensorMultilevelReport received, value: ${cmd.scaledSensorValue} scale: ${cmd.scale}","info")
if (cmd.sensorType == 4) {
sendEvent([name: "power", value: cmd.scaledSensorValue, unit: "W"])
updateCombinedMeter()
}
}
def zwaveEvent(physicalgraph.zwave.commands.meterv3.MeterReport cmd) {
logging("${device.displayName} - MeterReport received, value: ${cmd.scaledMeterValue} scale: ${cmd.scale}","info")
switch (cmd.scale) {
case 0:
sendEvent([name: "energy", value: cmd.scaledMeterValue, unit: "kWh"])
break
case 2:
sendEvent([name: "power", value: cmd.scaledMeterValue, unit: "W"])
break
}
updateCombinedMeter()
}
//other
private updateCombinedMeter() {
if (!device.currentValue("combinedMeter")?.contains("SYNC") || device.currentValue("combinedMeter") == "SYNC OK." || device.currentValue("combinedMeter") == null ) {
sendEvent([name: "combinedMeter", value: "${device.currentValue("power")} W / ${device.currentValue("energy")} kWh", displayed: false])
}
}
/*
####################
## Z-Wave Toolkit ##
####################
*/
def parse(String description) {
def result = []
logging("${device.displayName} - Parsing: ${description}")
if (description.startsWith("Err 106")) {
result = createEvent(
descriptionText: "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, cmdVersions())
if (cmd) {
logging("${device.displayName} - Parsed: ${cmd}")
zwaveEvent(cmd)
}
}
}
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand(cmdVersions())
if (encapsulatedCommand) {
logging("${device.displayName} - Parsed SecurityMessageEncapsulation into: ${encapsulatedCommand}")
zwaveEvent(encapsulatedCommand)
} else {
log.warn "Unable to extract secure cmd from $cmd"
createEvent(descriptionText: cmd.toString())
}
}
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) {
def version = cmdVersions()[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.warn "Could not extract crc16 command from $cmd"
} else {
logging("${device.displayName} - Parsed Crc16Encap into: ${encapsulatedCommand}")
zwaveEvent(encapsulatedCommand)
}
}
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand(cmdVersions())
if (encapsulatedCommand) {
logging("${device.displayName} - Parsed MultiChannelCmdEncap ${encapsulatedCommand}")
zwaveEvent(encapsulatedCommand, cmd.sourceEndPoint as Integer)
}
}
private logging(text, type = "debug") {
if (settings.logging == "true") {
log."$type" text
}
}
private secEncap(physicalgraph.zwave.Command cmd) {
logging("${device.displayName} - encapsulating command using Secure Encapsulation, command: $cmd","info")
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
}
private crcEncap(physicalgraph.zwave.Command cmd) {
logging("${device.displayName} - encapsulating command using CRC16 Encapsulation, command: $cmd","info")
zwave.crc16EncapV1.crc16Encap().encapsulate(cmd).format() // doesn't work righ now because SmartThings...
//"5601${cmd.format()}0000"
}
private multiEncap(physicalgraph.zwave.Command cmd, Integer ep) {
logging("${device.displayName} - encapsulating command using Multi Channel Encapsulation, ep: $ep command: $cmd","info")
zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:ep).encapsulate(cmd)
}
private encap(physicalgraph.zwave.Command cmd) {
if (zwaveInfo.zw.contains("s")) {
secEncap(cmd)
} else if (zwaveInfo.cc.contains("56")){
crcEncap(cmd)
} else {
logging("${device.displayName} - no encapsulation supported for command: $cmd","info")
cmd.format()
}
}
private encap(physicalgraph.zwave.Command cmd, Integer ep) {
encap(multiEncap(cmd, ep))
}
private encap(List encapList) {
encap(encapList[0], encapList[1])
}
private encap(Map encapMap) {
encap(encapMap.cmd, encapMap.ep)
}
private encapSequence(cmds, Integer delay=250) {
delayBetween(cmds.collect{ encap(it) }, delay)
}
private encapSequence(cmds, Integer delay, Integer ep) {
delayBetween(cmds.collect{ encap(it, ep) }, delay)
}
private List intToParam(Long value, Integer size = 1) {
def result = []
size.times {
result = result.plus(0, (value & 0xFF) as Short)
value = (value >> 8)
}
return result
}
/*
##########################
## Device Configuration ##
##########################
*/
private Map cmdVersions() {
//[0x5E: 2, 0x59: 1, 0x80: 1, 0x56: 1, 0x7A: 3, 0x73: 1, 0x98: 1, 0x22: 1, 0x85: 2, 0x5B: 1, 0x70: 2, 0x8E: 2, 0x86: 2, 0x84: 2, 0x75: 2, 0x72: 2] //Fibaro KeyFob
//[0x5E: 2, 0x86: 1, 0x72: 2, 0x59: 1, 0x80: 1, 0x73: 1, 0x56: 1, 0x22: 1, 0x31: 5, 0x98: 1, 0x7A: 3, 0x20: 1, 0x5A: 1, 0x85: 2, 0x84: 2, 0x71: 3, 0x8E: 2, 0x70: 2, 0x30: 1, 0x9C: 1] //Fibaro Motion Sensor ZW5
//[0x5E: 2, 0x86: 1, 0x72: 1, 0x59: 1, 0x73: 1, 0x22: 1, 0x56: 1, 0x32: 3, 0x71: 1, 0x98: 1, 0x7A: 1, 0x25: 1, 0x5A: 1, 0x85: 2, 0x70: 2, 0x8E: 2, 0x60: 3, 0x75: 1, 0x5B: 1] //Fibaro Double Switch 2
[0x5E: 2, 0x22: 1, 0x59: 1, 0x56: 1, 0x7A: 1, 0x32: 3, 0x71: 1, 0x73: 1, 0x98: 1, 0x31: 5, 0x85: 2, 0x70: 2, 0x72: 2, 0x5A: 1, 0x8E: 2, 0x25: 1, 0x86: 2] //Fibaro Wall Plug ZW5
}
private parameterMap() {[
[key: "alwaysActive", num: 1, size: 1, type: "enum", options: [0: "0 - inactive", 1: "1 - activated"], def: "0", title: "Always on function", descr: null],
[key: "restoreState", num: 2, size: 1, type: "enum", options: [0: "0 - power off after power failure", 1: "1 - restore state"], def: "1", title: "Restore state after power failure", descr: null],
[key: "overloadSafety", num: 3, size: 2, type: "number", def: 0, min: 0, max: 30000 , title: "Overload safety switch", descr: null],
[key: "immediatePowerReports", num: 10, size: 1, type: "number", def: 80, min: 1, max: 100, title: "Immediate power reports", descr: null],
[key: "standardPowerReports", num: 11, size: 1, type: "number", def: 15, min: 1, max: 100, title: "Standard power reports", descr: null],
[key: "powerReportFrequency", num: 12, size: 2, type: "number", def: 30, min: 5, max: 600, title: "Power reporting interval", descr: null],
[key: "energyReport", num: 13, size: 2, type: "number", def: 10, min: 0, max: 500, title: "Energy reports", descr: null],
[key: "periodicReports", num: 14, size: 2, type: "number", def: 3600, min: 0, max: 32400, title: "Periodic power and energy reports", descr: null],
[key: "deviceEnergyConsumed", num: 15, size: 1, type: "enum", options: [0: "0 - don't measure", 1: "1 - measure"], def: "0", title: "Energy consumed by the device itself", descr: null],
[key: "powerLoad", num: 40, size: 2, type: "number", def: 25000, min: 1000, max: 30000, title: "Power load which makes the LED ring flash violet", descr: null],
[key: "ringColorOn", num: 41, size: 1, type: "enum", options: [
0: "0 - Off",
1: "1 - Load based - continuous",
2: "2 - Load based - steps",
3: "3 - White",
4: "4 - Red",
5: "5 - Green",
6: "6 - Blue",
7: "7 - Yellow",
8: "8 - Cyan",
9: "9 - Magenta"
], def: "1", title: "Ring LED color when on", descr: null],
[key: "ringColorOff", num: 42, size: 1, type: "enum", options: [
0: "0 - Off",
1: "1 - Last measured power",
3: "3 - White",
4: "4 - Red",
5: "5 - Green",
6: "6 - Blue",
7: "7 - Yellow",
8: "8 - Cyan",
9: "9 - Magenta"
], def: "0", title: "Ring LED color when off", descr: null]
]}

View File

@@ -1,212 +0,0 @@
/**
* Copyright 2017 Ian Lindsay
*
* Code snippets taken from Tim Slagle
* https://community.smartthings.com/u/tslagle13
* here:
* https://community.smartthings.com/t/generic-camera-device-using-local-connection-new-version-now-available/3269/75?u=l0kiscot
*
*
* 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.
*
*/
preferences {
input("devicekey", "text", title: "Device Key", description: "Your OpenGarage.io device key")
input("ipadd", "text", title: "IP address", description: "The IP address of your OpenGarage.io unit")
input("port", "text", title: "Port", description: "The port of your OpenGarage.io unit")
}
metadata {
definition (name: "OpenGarage.io Handler", namespace: "littlegumSmartHome", author: "Ian Lindsay") {
capability "Door Control"
capability "Garage Door Control"
capability "Refresh"
}
tiles (scale: 2){
standardTile("garagedoor", "device.garagedoor", width: 6, height: 4) {
state "open", label: '${name}', action: "close", icon: "st.doors.garage.garage-open", backgroundColor: "#e54444"
state "closed", label: '${name}', action: "open", icon: "st.doors.garage.garage-closed", backgroundColor: "#79b821"
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 6, height: 2) {
state "default", action: "refresh.refresh", icon: "st.secondary.refresh"
}
main("garagedoor")
details(["garagedoor", "refresh"])
}
simulator {
// simulator metadata
}
}
def installed() {
initialize()
}
def initialize() {
log.debug "initialize triggered"
// initialize state
state.doorStatus = 1 // 1 means open, 0 means closed
api("getstatus", [])
}
def open() {
log.debug "Executing 'close'"
api("openclose", [])
}
def close() {
log.debug "Executing 'close'"
api("openclose", [])
}
def refresh() {
log.debug "Refreshing Values "
api("getstatus", [])
}
def api(method, args = [], success = {}) {
def methods = [
"getstatus": [gdipadd: "${ipadd}", gdport:"${port}", gdpath:"/jc", gdtype: "get"],
"openclose": [gdipadd: "${ipadd}", gdport:"${port}", gdpath:"/cc?dkey=${devicekey}&click=1", gdtype: "get"]
]
def request = methods.getAt(method)
doRequest(request.gdipadd, request.gdport, request.gdpath, request.gdtype, success)
}
private doRequest(gdipadd, gdport, gdpath, gdtype, success) {
log.debug(gdipadd)
//if(type == "post") {
// httpPost(uri , "", success)
//}
//else if(type == "get") {
// httpGet(uri, success)
//}
def host = gdipadd
def hosthex = convertIPToHex(host)
def porthex = Long.toHexString(Long.parseLong((gdport)))
if (porthex.length() < 4) { porthex = "00" + porthex }
//log.debug "Port in Hex is $porthex"
//log.debug "Hosthex is : $hosthex"
device.deviceNetworkId = "$hosthex:$porthex"
//log.debug "The device id configured is: $device.deviceNetworkId"
//def path = gdpath //"/SnapshotJPEG?Resolution=640x480&Quality=Clarity"
log.debug "path is: $gdpath"
def headers = [:] //"HOST:" + getHostAddress() + ""
headers.put("HOST", "$host:$gdport")
try {
def hubAction = new physicalgraph.device.HubAction(
method: method,
path: gdpath,
headers: headers
)
}
catch (Exception e)
{
log.debug "Hit Exception on $hubAction"
log.debug e
}
}
def parse(description) {
def msg = parseLanMessage(description)
log.debug msg
def headersAsString = msg.header // => headers as a string
def headerMap = msg.headers // => headers as a Map
def body = msg.body // => request body as a string
def status = msg.status // => http status code of the response
//def json = msg.json // => any JSON included in response body, as a data structure of lists and maps
//def xml = msg.xml // => any XML included in response body, as a document tree structure
//def data = msg.data // => either JSON or XML in response body (whichever is specified by content-type header in response)
def slurper = new groovy.json.JsonSlurper()
def json = slurper.parseText(msg.body)
log.debug json
def result
log.debug "before state.doorStatus: $state.doorStatus"
// open / close event
if(json.result){
if(state.doorStatus){
log.debug "door open - so closing"
state.doorStatus = 0
result = createEvent(name: "garagedoor", value: "closed")
} else {
log.debug "door closed - so opening"
state.doorStatus = 1
result = createEvent(name: "garagedoor", value: "open")
}
}
//status update request
if(json.mac){
if(json.door){
log.debug "door is open - refreshing setting"
state.doorStatus = 1
result = createEvent(name: "garagedoor", value: "open")
} else {
log.debug "door is closed - refreshing setting"
state.doorStatus = 0
result = createEvent(name: "garagedoor", value: "closed")
}
}
log.debug "after state.doorStatus: $state.doorStatus"
return result
}
private Long converIntToLong(ipAddress) {
long result = 0
def parts = ipAddress.split("\\.")
for (int i = 3; i >= 0; i--) {
result |= (Long.parseLong(parts[3 - i]) << (i * 8));
}
return result & 0xFFFFFFFF;
}
private String convertIPToHex(ipAddress) {
return Long.toHexString(converIntToLong(ipAddress));
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
log.debug("Convert hex to ip: $hex") // a0 00 01 6
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
private getHostAddress() {
def parts = device.deviceNetworkId.split(":")
log.debug device.deviceNetworkId
def ip = convertHexToIP(parts[0])
def port = convertHexToInt(parts[1])
return ip + ":" + port
}