/** * Inovelli 1-Channel Outdoor Smart Plug NZW96 * Author: Eric Maycock (erocm123) * Date: 2018-06-13 * * Copyright 2018 Eric Maycock * * 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. * * 2018-06-13: Modified tile layout. Update firmware version reporting. * * 2018-05-02: Added support for Z-Wave Association Tool SmartApp. Associations require firmware 1.02+. * https://github.com/erocm123/SmartThingsPublic/tree/master/smartapps/erocm123/z-waveat */ metadata { definition (name: "Inovelli 1-Channel Outdoor Smart Plug NZW96", namespace: "InovelliUSA", author: "Eric Maycock", vid: "generic-switch") { capability "Switch" capability "Refresh" capability "Polling" capability "Actuator" capability "Sensor" //capability "Health Check" capability "Configuration" attribute "lastActivity", "String" attribute "firmware", "String" command "setAssociationGroup", ["number", "enum", "number", "number"] // group number, nodes, action (0 - remove, 1 - add), multi-channel endpoint (optional) fingerprint mfr: "015D", prod: "6000", model: "6000", deviceJoinName: "Inovelli Outdoor Smart Plug" fingerprint mfr: "0312", prod: "6000", model: "6000", deviceJoinName: "Inovelli Outdoor Smart Plug" fingerprint deviceId: "0x1001", inClusters: "0x5E,0x86,0x72,0x5A,0x85,0x59,0x73,0x25,0x27,0x70,0x71,0x8E,0x55,0x6C,0x7A" } simulator { } preferences { input "autoOff", "number", title: "Auto Off\n\nAutomatically turn switch off after this number of seconds\nRange: 0 to 32767", description: "Tap to set", required: false, range: "0..32767" input "ledIndicator", "enum", title: "LED Indicator\n\nTurn LED indicator on when switch is:\n", description: "Tap to set", required: false, options:[["0": "On"], ["1": "Off"], ["2": "Disable"]], defaultValue: "0" input description: "Use the \"Z-Wave Association Tool\" SmartApp to set device associations. (Firmware 1.02+)\n\nGroup 2: Sends on/off commands to associated devices when switch is pressed (BASIC_SET).", title: "Associations", displayDuringSetup: false, type: "paragraph", element: "paragraph" } tiles { multiAttributeTile(name: "switch", type: "lighting", width: 6, height: 4, canChangeIcon: true) { tileAttribute("device.switch", key: "PRIMARY_CONTROL") { attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn" attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00a0dc", nextState: "turningOff" attributeState "turningOff", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn" attributeState "turningOn", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00a0dc", nextState: "turningOff" } } valueTile("lastActivity", "device.lastActivity", inactiveLabel: false, decoration: "flat", width: 4, height: 1) { state "default", label: 'Last Activity: ${currentValue}',icon: "st.Health & Wellness.health9" } valueTile("firmware", "device.firmware", inactiveLabel: false, decoration: "flat", width: 2, height: 1) { state "default", label: 'fw: ${currentValue}', icon: "" } valueTile("icon", "device.icon", inactiveLabel: false, decoration: "flat", width: 5, height: 1) { state "default", label: '', icon: "https://inovelli.com/wp-content/uploads/Device-Handler/Inovelli-Device-Handler-Logo.png" } standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 1, height: 1) { state "default", label: "", action: "refresh.refresh", icon: "st.secondary.refresh" } } } def installed() { refresh() } def configure() { log.debug "configure()" def cmds = initialize() commands(cmds) } def updated() { if (!state.lastRan || now() >= state.lastRan + 2000) { log.debug "updated()" state.lastRan = now() def cmds = initialize() response(commands(cmds)) } else { log.debug "updated() ran within the last 2 seconds. Skipping execution." } } def initialize() { sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"]) sendEvent(name: "numberOfButtons", value: 1, displayed: true) def cmds = processAssociations() cmds << zwave.versionV1.versionGet() cmds << zwave.configurationV1.configurationSet(scaledConfigurationValue: ledIndicator!=null? ledIndicator.toInteger() : 0, parameterNumber: 1, size: 1) cmds << zwave.configurationV1.configurationGet(parameterNumber: 1) cmds << zwave.configurationV1.configurationSet(scaledConfigurationValue: autoOff!=null? autoOff.toInteger() : 0, parameterNumber: 2, size: 2) cmds << zwave.configurationV1.configurationGet(parameterNumber: 2) return cmds } def parse(description) { def result = null if (description.startsWith("Err 106")) { state.sec = 0 result = createEvent(descriptionText: description, isStateChange: true) } else if (description != "updated") { def cmd = zwave.parse(description, [0x20: 1, 0x25: 1, 0x70: 1, 0x98: 1]) if (cmd) { result = zwaveEvent(cmd) log.debug("'$description' parsed to $result") } else { log.debug("Couldn't zwave.parse '$description'") } } def now if(location.timeZone) now = new Date().format("yyyy MMM dd EEE h:mm:ss a", location.timeZone) else now = new Date().format("yyyy MMM dd EEE h:mm:ss a") sendEvent(name: "lastActivity", value: now, displayed:false) result } def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) { createEvent(name: "switch", value: cmd.value ? "on" : "off", type: "physical") } def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) { createEvent(name: "switch", value: cmd.value ? "on" : "off", type: "physical") } def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) { createEvent(name: "switch", value: cmd.value ? "on" : "off", type: "digital") } def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) { def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x25: 1]) if (encapsulatedCommand) { state.sec = 1 zwaveEvent(encapsulatedCommand) } } def zwaveEvent(physicalgraph.zwave.Command cmd) { log.debug "Unhandled: $cmd" null } def on() { commands([ zwave.basicV1.basicSet(value: 0xFF), zwave.switchBinaryV1.switchBinaryGet() ]) } def off() { commands([ zwave.basicV1.basicSet(value: 0x00), zwave.switchBinaryV1.switchBinaryGet() ]) } def ping() { refresh() } def poll() { refresh() } def refresh() { commands(zwave.switchBinaryV1.switchBinaryGet()) } private command(physicalgraph.zwave.Command cmd) { if (state.sec) { zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format() } else { cmd.format() } } private commands(commands, delay=500) { delayBetween(commands.collect{ command(it) }, delay) } def setDefaultAssociations() { def smartThingsHubID = zwaveHubNodeId.toString().format( '%02x', zwaveHubNodeId ) state.defaultG1 = [smartThingsHubID] state.defaultG2 = [] } def setAssociationGroup(group, nodes, action, endpoint = null){ if (!state."desiredAssociation${group}") { state."desiredAssociation${group}" = nodes } else { switch (action) { case 0: state."desiredAssociation${group}" = state."desiredAssociation${group}" - nodes break case 1: state."desiredAssociation${group}" = state."desiredAssociation${group}" + nodes break } } } def processAssociations(){ def cmds = [] setDefaultAssociations() def associationGroups = 5 if (state.associationGroups) { associationGroups = state.associationGroups } else { log.debug "Getting supported association groups from device" cmds << zwave.associationV2.associationGroupingsGet() } for (int i = 1; i <= associationGroups; i++){ if(state."actualAssociation${i}" != null){ if(state."desiredAssociation${i}" != null || state."defaultG${i}") { def refreshGroup = false ((state."desiredAssociation${i}"? state."desiredAssociation${i}" : [] + state."defaultG${i}") - state."actualAssociation${i}").each { log.debug "Adding node $it to group $i" cmds << zwave.associationV2.associationSet(groupingIdentifier:i, nodeId:Integer.parseInt(it,16)) refreshGroup = true } ((state."actualAssociation${i}" - state."defaultG${i}") - state."desiredAssociation${i}").each { log.debug "Removing node $it from group $i" cmds << zwave.associationV2.associationRemove(groupingIdentifier:i, nodeId:Integer.parseInt(it,16)) refreshGroup = true } if (refreshGroup == true) cmds << zwave.associationV2.associationGet(groupingIdentifier:i) else log.debug "There are no association actions to complete for group $i" } } else { log.debug "Association info not known for group $i. Requesting info from device." cmds << zwave.associationV2.associationGet(groupingIdentifier:i) } } return cmds } void zwaveEvent(physicalgraph.zwave.commands.associationv2.AssociationReport cmd) { def temp = [] if (cmd.nodeId != []) { cmd.nodeId.each { temp += it.toString().format( '%02x', it.toInteger() ).toUpperCase() } } state."actualAssociation${cmd.groupingIdentifier}" = temp log.debug "Associations for Group ${cmd.groupingIdentifier}: ${temp}" updateDataValue("associationGroup${cmd.groupingIdentifier}", "$temp") } def zwaveEvent(physicalgraph.zwave.commands.associationv2.AssociationGroupingsReport cmd) { log.debug "Supported association groups: ${cmd.supportedGroupings}" state.associationGroups = cmd.supportedGroupings createEvent(name: "groups", value: cmd.supportedGroupings) } def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) { log.debug cmd if(cmd.applicationVersion && cmd.applicationSubVersion) { def firmware = "${cmd.applicationVersion}.${cmd.applicationSubVersion.toString().padLeft(2,'0')}" state.needfwUpdate = "false" createEvent(name: "firmware", value: "${firmware}") } }