Skip to content

Instantly share code, notes, and snippets.

@jemenake
Last active June 12, 2021 18:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jemenake/a25eb138dddf309c0eb13a3f9c6bd538 to your computer and use it in GitHub Desktop.
Save jemenake/a25eb138dddf309c0eb13a3f9c6bd538 to your computer and use it in GitHub Desktop.
Example of implementing child devices on Smartthings
/**
*
* Copyright 2021 Joseph Emenaker
*
* LOTS of credit to Eric Maycock for providing the starting code:
* https://community.smartthings.com/t/release-qubino-flush-1d-relay-flush-2-relays-flush-1-relay-flush-dimmer/79618
*
* 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: "TESTING123", namespace: "jemenake", author: "Joseph Emenaker", cstHandler: true
,ocfDeviceType: 'oic.d.switch', mnmn: 'SmartThingsCommunity', vid: 'generic-switch'
) {
capability "Actuator"
capability "Switch"
capability "Refresh"
// These define extra commands which are made available to the simulator and to other smartapps
command "on1"
command "on2"
command "deleteChildDevices"
command "createChildDevices"
command "getManufInfo"
fingerprint mfr: "0115", prod: "0110", model: "0001", deviceJoinName: "Device Join Name"
}
simulator {
// TODO: define status and reply messages here
}
tiles {
main(["switch"])
details(["switch",
childDeviceTiles("all")
])
// TODO: define your main and details tiles here
}
}
// parse events into attributes
def parse(String description) {
log.debug "parse description: $description"
def result = null
def cmd = zwave.parse(description)
if (cmd) {
result = zwaveEvent(cmd)
log.debug "Parsed ${cmd} to ${result.inspect()}"
} else {
log.debug "Non-parsed event: ${description}"
}
return result
}
// This gets called when you pull down on the details page in the mobile app
def refresh() {
log.debug("We got frefreshed... yay!")
def cmds = []
cmds << new physicalgraph.device.HubAction(command(encap(zwave.switchBinaryV1.switchBinaryGet(), 1)))
cmds << new physicalgraph.device.HubAction(command(encap(zwave.switchBinaryV1.switchBinaryGet(), 2)))
cmds << new physicalgraph.device.HubAction(command(encap(zwave.switchBinaryV1.switchBinaryGet(), 3)))
cmds << new physicalgraph.device.HubAction(command(encap(zwave.switchBinaryV1.switchBinaryGet(), 4)))
sendHubCommand(cmds)
}
def getManufInfo() {
def cmds = []
cmds << zwave.manufacturerSpecificV2.manufacturerSpecificGet()
sendHubCommand(cmds)
}
def installed() {
log.debug("parent installed");
createChildDevices()
}
def updated() {
log.debug("Parent updated");
}
void deleteChildDevices() {
log.debug("Before childDevices = $childDevices")
childDevices.each { deleteChildDevice(it.deviceNetworkId) }
log.debug("After childDevices = $childDevices")
}
void createChildDevices() {
for (i in 2..4) {
// When adding a child device from another namespace (i.e. developer), you need to add
// that namespace as the first parameter. If you just call:
// addChildDevice("Some Device Handler Name", ....)
// Smartthings will search for it in the namespace of the _parent_
try {
addChildDevice("smartthings", "Child Switch", ChildDNIFromZwaveChannel(i), null,
[completedSetup: true, label: "${device.displayName} (R${i})",
isComponent: false])
// TODO: Send a BasicReport or BasicGet to fetch the initial values
} catch (e) {
// runIn(2, "sendAlert")
log.debug("problem adding child device $i")
}
}
}
private String getChildDNIBase() {
return "${device.deviceNetworkId}-ep"
}
def zwaveChannelFromChildDNI(String dni) {
log.debug("Starting with DNI : $dni")
String remainder = dni.minus(getChildDNIBase())
log.debug("Remainder is $remainder")
int channel = Integer.parseInt(remainder)
log.debug("Channel is $channel")
return channel
}
def ChildDNIFromZwaveChannel(int channel) {
return getChildDNIBase() + channel
}
private encap(cmd, endpoint) {
if (endpoint) {
zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:endpoint).encapsulate(cmd)
} else {
cmd
}
}
private command(physicalgraph.zwave.Command cmd) {
if (state.sec || zwaveInfo?.zw?.contains("s") || ("0x98" in device.rawDescription?.split(" "))) {
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
} else {
cmd.format()
}
}
def childOn(String id) {
log.debug("childOn($id)")
int channel = zwaveChannelFromChildDNI(id)
log.debug(" Looks like that's channel $channel")
def cmds = []
cmds << new physicalgraph.device.HubAction(command(encap(zwave.switchBinaryV1.switchBinarySet(switchValue: 0xFF), channel)))
sendHubCommand(cmds)
log.debug("Commands are $cmds")
}
def childOff(String id) {
log.debug("childOff($id)")
int channel = zwaveChannelFromChildDNI(id)
log.debug(" Looks like that's channel $channel")
def cmds = []
cmds << new physicalgraph.device.HubAction(command(encap(zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00), channel)))
sendHubCommand(cmds)
log.debug("Commands are $cmds")
}
private sendCommands(cmds) {
def actions = []
cmds?.each {
actions << new physicalgraph.device.HubAction(it)
}
sendHubCommand(actions)
return []
}
// handle commands
def on() {
log.debug "Executing 'on'"
createChildDevices()
if(childDevices) {
childDevices.each { theDevice -> log.debug("There's a child known as $theDevice ") }
} else {
log.debug("NO CHILD DEVICES")
}
on1()
// TODO: handle 'on' command
}
def off() {
log.debug "Executing 'off'"
off1()
// TODO: handle 'off' command
}
def on1() {
log.debug "on1()"
delayBetween([
zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:1, commandClass:37, command:1, parameter:[255]).format(),
zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:1, commandClass:37, command:2).format()
], 1000)
}
def off1() {
log.debug "off1()"
delayBetween([
zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:1, commandClass:37, command:1, parameter:[0]).format(),
zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:1, commandClass:37, command:2).format()
], 1000)
}
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
log.debug "Got a multichannel command: $cmd"
def encapsulatedCommand = cmd.encapsulatedCommand()
def source = cmd.sourceEndPoint
log.debug (" Command from endpoint ${source}: ${encapsulatedCommand}")
if (encapsulatedCommand) {
return zwaveEvent(encapsulatedCommand, source)
}
}
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd, int channel) {
log.debug "Got a binary switch report (from channel $channel): $cmd"
log.debug "Value is ${cmd.value}"
if (channel == 1) {
return createEvent(name:"switch", value: cmd.value ? "on" : "off")
} else {
// All other channels are child devices, so we need to get the event to the child
def dni = ChildDNIFromZwaveChannel(channel)
log.debug("Hunting for DNI: $dni")
def child = childDevices.find { it.deviceNetworkId.equals(dni) }
if (child) {
child.sendEvent(name:"switch", value: cmd.value ? "on" : "off")
}
}
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport rpt) {
log.debug("ManufID : ${rpt.manufacturerId}")
log.debug("ProdTypeID : ${rpt.productTypeId}")
log.debug("ProdID : ${rpt.productId}")
log.debug("ManufName : ${rpt.manufacturerName}")
}
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
log.debug "Got a binary switch report: $cmd"
log.debug "Value is ${cmd.value}"
createEvent(name:"switch", value: cmd.value ? "on" : "off")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment