How do I add a Device Handler for the Aeotec ZW116?

So for the Aeotec Dual Nano Switch Aeotec has suggested that I add in a new device handler. I have never done this before so I am not sure the method.

this is the hyper link

this is the steps they have told me to follow:

  1. Login to Web IDE and click on the “My Device Types” link on the top menu (login here: https://graph.api.smartthings.com/)
  2. Click on “Locations”
  3. Select your SmartThings Home Automation gateway that you want to put the device handler in
  4. You may need to login again, if not, continue to step 5.
  5. Create a new Device Handler by clicking on “New Device Handler” button in the upper-right corner.
  6. Click on “From Code.”
  7. Copy smartthings code from Github (https://raw.githubusercontent.com/Madj42/SmartThings-ZW116/master/Aeotec_Nano_ZW116.groovy ), and paste it into the code section.
  8. Click on “Save”, then wait for the spinning wheel to disappear before continuing.
  9. Click on “Publish” -> “Publish for me”
  10. Install it on your Nano Switch by going to “My Devices” page in the IDE
  11. Find your Nano Switch .
  12. Go to the bottom of the page for the current Nano Switch and click on “Edit.”
  13. Find the “Type” field and select your device handler. (should be located at the bottom of the list as
    Aeotec Nano Switch w/ Power Metering
    ).
  14. Click on “Update”
  15. Save Changes
  16. Repeat steps 10 - 15 for all Nano Switch devices you have on hand

My question is what is the code that I am suppose to paste in as per if I use the hyper link I get an error
or is it the lines in the hyper link that I paste in and if so then where do I actually start the copying from?

I am not sure if this make sense.

When you click on the link, it goes to GitHub. In there click “raw”. Then select all, and copy. Then paste that into the “code” section. Save, publish, for me, done

Use https://account.smartthings.com and then skip directly to 5 in the instructions above.

Much simpler.

Tried this but just received a huge error message. Is there part of the raw file that I don’t paste in there?

what was the error message?

Did you click on My Device Handlers in the menu and then select Create New Device Handler?

If it was a server error, then login to the ide from an incognito window and try again

This is the Error. Huge might be the wrong word

Org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: script_dth_metadata_1a935559_c6dc_4ea5_aa4e_5757f6b18738: 1: unexpected token: 2 @ line 1, column 1. 2) { multiAttributeTile(name:“switch”, type: “generic”, width: 6, height: 4, canChangeIcon: true){ tileAttribute (“device.switch”, key: “PRIMARY_CONTROL”) { attributeState “on”, label: ‘{name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00a0dc" attributeState "off", label: '{name}’, action: “switch.on”, icon: “st.switches.switch.off”, backgroundColor: “#ffffff” } tileAttribute (“device.acceleration”, key: “SECONDARY_CONTROL”) { attributeState “inactive”, label:‘INACTIVE’ attributeState “active”, label:‘ACTIVE’ } } standardTile(“refresh”, “device.refresh”, width: 2, height: 2) { state “refresh”, label:‘Refresh’, action: “refresh”, icon:“st.secondary.refresh-icon” } standardTile(“reset”, “device.reset”, width: 2, height: 2) { state “refresh”, label:‘Reset’, action: “reset”, icon:“st.secondary.refresh-icon” } valueTile(“energy”, “device.energy”, width: 2, height: 2) { state “energy”, label:’{currentValue} kWh', backgroundColor: "#cccccc" } valueTile("power", "device.power", width: 2, height: 2) { state "power", label:'{currentValue} W’, backgroundColor: “#cccccc” } valueTile(“voltage”, “device.voltage”, width: 2, height: 2) { state “voltage”, label:’{currentValue} V', backgroundColor: "#cccccc" } valueTile("current", "device.current", width: 2, height: 2) { state "current", label:'{currentValue} A’, backgroundColor: “#cccccc” } valueTile(“history”, “device.history”, decoration:“flat”,width: 6, height: 3) { state “history”, label:’{currentValue}' } valueTile("firmwareVersion", "device.firmwareVersion", decoration:"flat", width:3, height: 1) { state "firmwareVersion", label:'Firmware {currentValue}’ } main “switch” details([“switch”, “power”, “energy”, “refresh”, “voltage”, “current”, “reset”, “history”, “firmwareVersion”]) } } def clientVersion() { return “1.1” } private getOptionsInput(param) { if (param.prefName) { input “{param.prefName}", "enum", title: "{param.name}:”, defaultValue: “{param.val}", required: false, displayDuringSetup: true, options: param.options?.collect { name, val -> name } } } private getBoolInput(name, title, defaultVal) { input "{name}”, “bool”, title: “{title}?", defaultValue: defaultVal, required: false } // Meters private getMeterEnergy() { return getMeterMap("energy", 0, "kWh", null, settings?.displayEnergy != false) } private getMeterPower() { return getMeterMap("power", 2, "W", 2000, settings?.displayPower != false) } private getMeterVoltage() { return getMeterMap("voltage", 4, "V", 150, settings?.displayVoltage != false) } private getMeterCurrent() { return getMeterMap("current", 5, "A", 18, settings?.displayCurrent != false) } private getMeterMap(name, scale, unit, limit, displayed) { return [name:name, scale:scale, unit:unit, limit: limit, displayed:displayed] } def updated() { if (!isDuplicateCommand(state.lastUpdated, 3000)) { state.lastUpdated = new Date().time def cmds = configure() return cmds ? response(cmds) : [] } } def configure() { def result = [] updateHealthCheckInterval() def cmds = [] if (!device.currentValue("firmwareVersion")) { cmds << versionGetCmd() } configParams.each { param -> cmds += updateConfigVal(param) } result += delayBetweenCmds(cmds) if (!getAttrVal("energyTime")) { result += reset() } else { result += refresh() } return result } private updateConfigVal(param) { def cmds = [] if (hasPendingChange(param)) { def oldVal = getParamStoredIntVal(param) def newVal = getParamIntVal(param) logTrace "{param.name}(#{param.num}): changing {getParamStoredIntVal(param)} to {newVal}" cmds << configSetCmd(param, newVal) cmds << configGetCmd(param) } return cmds } private hasPendingChange(param) { return (getParamIntVal(param) != getParamStoredIntVal(param)) } void updateHealthCheckInterval() { def minReportingInterval = minimumReportingInterval if (state.minReportingInterval != minReportingInterval) { state.minReportingInterval = minReportingInterval // Set the Health Check interval so that it can be skipped twice plus 5 minutes. def checkInterval = ((minReportingInterval * 2) + (5 * 60)) def eventMap = createEventMap("checkInterval", checkInterval, false) eventMap.data = [protocol: "zwave", hubHardwareId: device.hub.hardwareID] sendEvent(eventMap) } } def ping() { logDebug "Pinging device because it has not checked in" return [switchBinaryGetCmd()] } def on() { logTrace "Turning On" return delayBetweenCmds([ switchBinarySetCmd(0xFF), switchBinaryGetCmd() ]) } def off() { if (disableSwitchSetting) { logTrace "Switch is disabled" } else { logTrace "Turning Off" return delayBetweenCmds([ switchBinarySetCmd(0x00), switchBinaryGetCmd() ]) } } def refresh() { logTrace "Refreshing" return delayBetweenCmds([ switchBinaryGetCmd(), meterGetCmd(meterEnergy), meterGetCmd(meterPower), meterGetCmd(meterVoltage), meterGetCmd(meterCurrent) ]) } def reset() { logTrace "Resetting" ["power", "voltage", "current"].each { sendEvent(createEventMap("{it}Low”, getAttrVal(it), false)) sendEvent(createEventMap("${it}High", getAttrVal(it), false)) } sendEvent(createEventMap(“energyTime”, new Date().time, false)) def result = result << meterResetCmd() result += refresh() return result } private meterGetCmd(meter) { return secureCmd(zwave.meterV3.meterGet(scale: meter.scale)) } private meterResetCmd() { return secureCmd(zwave.meterV3.meterReset()) } private versionGetCmd() { return secureCmd(zwave.versionV1.versionGet()) } private switchBinaryGetCmd() { return secureCmd(zwave.switchBinaryV1.switchBinaryGet()) } private switchBinarySetCmd(val) { return secureCmd(zwave.switchBinaryV1.switchBinarySet(switchValue: val)) } private configSetCmd(param, val) { return secureCmd(zwave.configurationV2.configurationSet(parameterNumber: param.num, size: param.size, scaledConfigurationValue: val)) } private configGetCmd(param) { return secureCmd(zwave.configurationV2.configurationGet(parameterNumber: param.num)) } private secureCmd(cmd) { if (zwaveInfo?.zw?.contains(“s”) || (“0x98” in device.rawDescription?.split(" “))) { return zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format() } else { return cmd.format() } } private delayBetweenCmds(cmds, delay=500) { return cmds ? delayBetween(cmds, delay) : } def parse(String description) { def result = def cmd = zwave.parse(description, commandClassVersions) if (cmd) { result += zwaveEvent(cmd) } else { log.warn “Unable to parse: $description” } if (!isDuplicateCommand(state.lastCheckinTime, 60000)) { state.lastCheckinTime = new Date().time result << createEvent(createEventMap(“lastCheckin”, convertToLocalTimeString(new Date()), false)) } return result } def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) { def encapsulatedCmd = cmd.encapsulatedCommand(commandClassVersions) def result = if (encapsulatedCmd) { result += zwaveEvent(encapsulatedCmd) } else { log.warn “Unable to extract encapsulated cmd from cmd" } return result } private getCommandClassVersions() { [ 0x20: 1, // Basic 0x25: 1, // Switch Binary 0x27: 1, // All Switch 0x2B: 1, // Scene Activation 0x2C: 1, // Scene Actuator Configuration 0x32: 3, // Meter v4 0x59: 1, // AssociationGrpInfo 0x5A: 1, // DeviceResetLocally 0x5E: 2, // ZwaveplusInfo 0x70: 2, // Configuration 0x72: 2, // ManufacturerSpecific 0x73: 1, // Powerlevel 0x7A: 2, // Firmware Update Md (3) 0x85: 2, // Association 0x86: 1, // Version (2) 0x98: 1 // Security ] } def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) { def val = cmd.scaledConfigurationValue def configParam = configParams.find { param -> param.num == cmd.parameterNumber } if (configParam) { def name = configParam.options?.find { it.value == val}?.key logDebug "{configParam.name}(#{configParam.num}) = {name != null ? name : val} ({val})" state["configVal{cmd.parameterNumber}”] = val } else { logDebug “Parameter {cmd.parameterNumber} = {val}” } return } def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) { logTrace “VersionReport: {cmd}" def version = "{cmd.applicationVersion}.{cmd.applicationSubVersion}" if (version != device.currentValue("firmwareVersion")) { logDebug "Firmware: {version}” sendEvent(name: “firmwareVersion”, value: version, displayed:false) } return } def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) { logTrace “SwitchBinaryReport: {cmd}" def result = [] result << createSwitchEvent(cmd.value, "digital") return result } def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) { logTrace "BasicReport: {cmd}” def result = result << createSwitchEvent(cmd.value, “physical”) return result } private createSwitchEvent(value, type) { def eventVal = (value == 0xFF) ? “on” : “off” def map = createEventMap(“switch”, eventVal, null, “Switch is ${eventVal}”) map.type = type return createEvent(map) } def zwaveEvent(physicalgraph.zwave.commands.meterv3.MeterReport cmd) { logTrace “MeterReport: $cmd” def result = def val = roundTwoPlaces(cmd.scaledMeterValue) def meter switch (cmd.scale) { case meterEnergy.scale: meter = meterEnergy break case meterPower.scale: createAccelerationEvent(val) meter = meterPower break case meterVoltage.scale: meter = meterVoltage break case meterCurrent.scale: meter = meterCurrent break default: logDebug “Unknown Meter Scale: cmd" } if (meter?.limit && val > meter.limit) { log.warn "Ignored {meter.name} value {val}{meter.unit} because the highest possible value is {meter.limit}{meter.unit}.” } else if (meter?.name && getAttrVal(”{[meter.name](http://meter.name)}") != val) { result << createEvent(createEventMap([meter.name](http://meter.name), val, meter.displayed, null, meter.unit)) if ([meter.name](http://meter.name) == [meterEnergy.name](http://meterEnergy.name)) { result += createEnergyEvents(val) } else { result += createHighLowEvents(meter, val) } runIn(5, refreshHistory) } return result } private createAccelerationEvent(val) { def deviceActive = (device.currentValue("acceleration") == "active") if (val > inactivePowerSetting && !deviceActive) { sendEvent(name:"acceleration", value:"active", displayed:false) } else if (val <= inactivePowerSetting && deviceActive){ sendEvent(name:"acceleration", value:"inactive", displayed:false) } } private createHighLowEvents(meter, val) { def result = [] def highLowNames = [] def highName = "{meter.name}High" def lowName = “${meter.name}Low” if (!getAttrVal(highName) || val > getAttrVal(highName)) { highLowNames << highName } if (!getAttrVal(lowName) || meter.value < getAttrVal(lowName)) { highLowNames << lowName } highLowNames.each { result << createEvent(createEventMap("it", val, false, null, meter.unit)) } return result } private createEnergyEvents(val) { def result = [] def cost = "\${roundTwoPlaces(val * energyPriceSetting)}" if (getAttrVal(“energyCost”) != cost) { result << createEvent(createEventMap(“energyCost”, cost, false)) } result << createEvent(createEventMap(“energyDuration”, calculateEnergyDuration(), false)) return result } private calculateEnergyDuration() { def energyTimeMS = getAttrVal(“energyTime”) if (!energyTimeMS) { return “Unknown” } else { def duration = roundTwoPlaces((new Date().time - energyTimeMS) / 60000) if (duration >= (24 * 60)) { return getFormattedDuration(duration, (24 * 60), “Day”) } else if (duration >= 60) { return getFormattedDuration(duration, 60, “Hour”) } else { return getFormattedDuration(duration, 0, “Minute”) } } } private getFormattedDuration(duration, divisor, name) { if (divisor) { duration = roundTwoPlaces(duration / divisor) } return “{duration} {name}{duration == 1 ? '' : 's'}" } def refreshHistory() { def history = "" def items = [:] items["energyDuration"] = "Energy - Duration" items["energyCost"] = "Energy - Cost" ["power", "voltage", "current"].each { items["{it}Low”] = “{it.capitalize()} - Low" items["{it}High”] = “{it.capitalize()} - High" } items.each { attrName, caption -> def attr = device.currentState("{attrName}”) def val = attr?.value ?: “” def unit = attr?.unit ?: “” history += “{caption}: {val} ${unit}\n” } sendEvent(createEventMap(“history”, history, false)) } def zwaveEvent(physicalgraph.zwave.Command cmd) { logDebug “Unhandled zwaveEvent: cmd" return [] } // Configuration Parameters private getConfigParams() { return [ instantPowerValuesParam, instantPowerFrequencyParam, ledIndicatorParam, ] } private getInstantPowerValuesParam() { return createConfigParamMap(101, "Instantaneous Power Report Values", 4, ["Current":8, "kWh/Watt":9, "Current/Watt":10, "Current/Watt/kWh":11, "Current/Voltage":12, "Current/Voltage/kWh":13, "Current/Voltage/Watt":14, "All{defaultOptionSuffix}”:15], “instantPowerValues”) } private getinstantPowerFrequencyParam() { return createConfigParamMap(111, “Instantaneous Power Report Frequency”, 4, [“10 Seconds”:10, “30 Seconds${defaultOptionSuffix}”:30, “1 Minute”:60, “2 Minutes”:120, “5 Minutes”:300], “instantPowerFrequency”) } private getLedIndicatorParam() { return createConfigParamMap(83, “LED Power Indicator”, 1, [“Follow the status of load (Energy mode){defaultOptionSuffix}":0, "Follow the status (on/off) of load. Red LED turns off after 5 sec if no change (Momentary indicate mode)":1], "ledIndicator") } private getParamStoredIntVal(param) { return state["configVal{param.num}”] } private getParamIntVal(param) { return param.options ? convertOptionSettingToInt(param.options, param.val) : param.val } private createConfigParamMap(num, name, size, options, prefName, val=null) { if (val == null) { val = (settings?."{prefName}" ?: findDefaultOptionName(options)) } return [ num: num, name: name, size: size, options: options, prefName: prefName, val: val ] } // Settings private getEnergyPriceSetting() { return safeToDec(settings?.energyPrice, 0.12) } private getInactivePowerSetting() { return safeToInt(settings?.inactivePower, 0) } private getDebugOutputSetting() { return settings?.debugOutput != false } private getTraceOutputSetting() { return settings?.traceOutput != false } private getDisableSwitchSetting() { return settings?.disableSwitch != false } private getMinimumReportingInterval() { def minVal = (60 * 60 * 24 * 7) return minVal } private getIntervalOptions(defaultVal=null, zeroName=null) { def options = [:] if (zeroName) { options["{zeroName}"] = 0 } options << getIntervalOptionsRange(“Second”, 1, [5,10,15,30,45]) options << getIntervalOptionsRange(“Minute”, 60, [1,2,3,4,5,10,15,30,45]) options << getIntervalOptionsRange(“Hour”, (60 * 60), [1,2,3,6,9,12,18]) options << getIntervalOptionsRange(“Day”, (60 * 60 * 24), [1,3,5]) options << getIntervalOptionsRange(“Week”, (60 * 60 * 24 * 7), [1,2]) return setDefaultOption(options, defaultVal) } private getIntervalOptionsRange(name, multiplier, range) { def options = [:] range?.each { options["{it} {name}{it == 1 ? '' : 's'}"] = (it * multiplier) } return options } private getPowerValueOptions() { def options = [:] [0,1,2,3,4,5,10,25,50,75,100,150,200,250,300,400,500,750,1000,1250,1500,1750,2000,2500,3000,3500,4000,4500,5000,6000,7000,8000,9000,10000,12500,15000].each { if (it == 0) { options["No Reports"] = it } else { options["{it} Watts"] = it } } return setDefaultOption(options, 50) } private getPercentageOptions(defaultVal=null, zeroName=null) { def options = [:] if (zeroName) { options["{zeroName}"] = 0 } for (int i = 1; i <= 5; i += 1) { options["{i}%"] = i } for (int i = 10; i <= 100; i += 5) { options["{i}%"] = i } return setDefaultOption(options, defaultVal) } private convertOptionSettingToInt(options, settingVal) { return safeToInt(options?.find { name, val -> "{settingVal}" == name }?.value, 0) } private setDefaultOption(options, defaultVal) { def name = options.find { key, val -> val == defaultVal }?.key if (name != null) { return changeOptionName(options, defaultVal, “{name}{defaultOptionSuffix}”) } else { return options } } private changeOptionName(options, optionVal, newName) { def result = [:] options?.each { name, val -> if (val == optionVal) { name = “{newName}" } result["{name}”] = val } return result } private findDefaultOptionName(options) { def option = options?.find { name, val -> name?.contains("{defaultOptionSuffix}") } return option?.key ?: "" } private getDefaultOptionSuffix() { return " (Default)" } private createEventMap(name, value, displayed=null, desc=null, unit=null) { desc = desc ?: "{name} is {value}" def eventMap = [ name: name, value: value, // isStateChange: true, displayed: (displayed == null ? ("{getAttrVal(name)}" != “{value}") : displayed) ] if (unit) { eventMap.unit = unit desc = "{desc} {unit}" } if (desc && eventMap.displayed) { logDebug desc eventMap.descriptionText = "{device.displayName} - {desc}" } else { logTrace "Creating Event: {eventMap}” } return eventMap } private getAttrVal(attrName) { try { return device?.currentValue("${attrName}") } catch (ex) { logTrace “ex" return null } } private safeToInt(val, defaultVal=0) { return "{val}”?.isInteger() ? “{val}".toInteger() : defaultVal } private safeToDec(val, defaultVal=0) { return "{val}”?.isBigDecimal() ? “${val}”.toBigDecimal() : defaultVal } private roundTwoPlaces(val) { return Math.round(safeToDec(val) * 100) / 100 } private convertToLocalTimeString(dt) { def timeZoneId = location?.timeZone?.ID if (timeZoneId) { return dt.format(“MM/dd/yyyy hh:mm:ss a”, TimeZone.getTimeZone(timeZoneId)) } else { return “$dt” } } private isDuplicateCommand(lastExecuted, allowedMil) { !lastExecuted ? false : (lastExecuted + allowedMil > new Date().time) } private logDebug(msg) { if (debugOutputSetting) { log.debug “$msg” } } private logTrace(msg) { if (traceOutputSetting) { log.trace “$msg” } } ^ 1 error

did you select all when you were copying the code?

the code starts with and includes /**

I think I managed to get it figured out. Seemed to work for one of the 3 way switches but didn’t do anything for the other 3 way switch.

Has anyone else had issues with wiring the Dual Nano switch so two 3 way switches?