Created
March 9, 2018 19:03
-
-
Save talz13/76b6ddc1fe3b12d6752702e5df2c34e3 to your computer and use it in GitHub Desktop.
SmartThings device handler for Aeon HEM v1 to read clamps separately
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* SmartThings Device Type for Aeon Home Energy Monitor v1 (HEM v1) | |
* (Goal of project is to) Displays individual values for each clamp (L1, L2) for granular monitoring | |
* Example: Individual circuits (breakers) of high-load devices, such as HVAC or clothes dryer | |
* | |
* Original Author: Copyright 2014 Barry A. Burke | |
* | |
**/ | |
metadata { | |
// Automatically generated. Make future change here. | |
definition ( | |
name: "Aeon HEM Split Reading 2", | |
namespace: "Green Living", | |
category: "Green Living", | |
author: "Barry A. Burke" | |
) | |
{ | |
capability "Energy Meter" | |
capability "Power Meter" | |
capability "Configuration" | |
capability "Sensor" | |
capability "Refresh" | |
capability "Polling" | |
//capability "Battery" | |
/*attribute "energy", "string" | |
attribute "power", "string" | |
attribute "volts", "string" | |
attribute "voltage", "string"*/ // We'll deliver both, since the correct one is not defined anywhere | |
attribute "energy", "number" | |
attribute "power", "number" | |
attribute "volts", "number" | |
attribute "voltage", "number" // We'll deliver both, since the correct one is not defined anywhere | |
attribute "energyDisp", "string" | |
attribute "energyOneDisp", "string" | |
attribute "energyTwoDisp", "string" | |
/*attribute "energyDisp", "number"*/ | |
attribute "energyOne", "number" | |
attribute "energyTwo", "number" | |
attribute "powerDisp", "string" | |
attribute "powerOneDisp", "string" | |
attribute "powerTwoDisp", "string" | |
/*attribute "powerDisp", "number"*/ | |
attribute "powerOne", "number" | |
attribute "powerTwo", "number" | |
command "reset" | |
command "configure" | |
command "refresh" | |
command "poll" | |
fingerprint deviceId: "0x2101", inClusters: " 0x70,0x31,0x72,0x86,0x32,0x80,0x85,0x60" | |
// fingerprint deviceId: "0x3101", inClusters: "0x70,0x32,0x60,0x85,0x56,0x72,0x86" | |
} | |
// simulator metadata | |
simulator { | |
for (int i = 0; i <= 10000; i += 1000) { | |
status "power ${i} W-ZZ": new physicalgraph.zwave.Zwave().meterV1.meterReport( | |
scaledMeterValue: i, precision: 3, meterType: 33, scale: 2, size: 4).incomingMessage() | |
} | |
for (int i = 0; i <= 100; i += 10) { | |
status "energy ${i} kWh-ZZ": new physicalgraph.zwave.Zwave().meterV1.meterReport( | |
scaledMeterValue: i, precision: 3, meterType: 33, scale: 0, size: 4).incomingMessage() | |
} | |
// TODO: Add data feeds for Volts and Amps | |
} | |
// tile definitions | |
tiles { | |
// Watts row | |
valueTile("powerDisp", "device.powerDisp") { | |
state ( | |
"default", | |
label:'${currentValue}', | |
foregroundColors:[ | |
[value: 1, color: "#000000"], | |
[value: 10000, color: "#ffffff"] | |
], | |
foregroundColor: "#000000" | |
) | |
} | |
valueTile("powerOne", "device.powerOneDisp") { | |
state( | |
"default", | |
label:'${currentValue}', | |
foregroundColors:[ | |
[value: 1, color: "#000000"], | |
[value: 10000, color: "#ffffff"] | |
], | |
foregroundColor: "#000000" | |
) | |
} | |
valueTile("powerTwo", "device.powerTwoDisp") { | |
state( | |
"default", | |
label:'${currentValue}', | |
foregroundColors:[ | |
[value: 1, color: "#000000"], | |
[value: 10000, color: "#ffffff"] | |
], | |
foregroundColor: "#000000" | |
) | |
} | |
// Energy row | |
valueTile("energyDisp", "device.energyDisp") { | |
state( | |
"default", | |
label: '${currentValue}', // shows kWh value with ' kWh' suffix | |
foregroundColor: "#000000", | |
backgroundColor: "#ffffff") | |
} | |
valueTile("energyOne", "device.energyOneDisp") { | |
state( | |
"default", | |
label: '${currentValue}', // shows kWh value with ' kWh' suffix | |
foregroundColor: "#000000", | |
backgroundColor: "#ffffff") | |
} | |
valueTile("energyTwo", "device.energyTwoDisp") { | |
state( | |
"default", | |
label: '${currentValue}', // shows kWh value with ' kWh' suffix | |
foregroundColor: "#000000", | |
backgroundColor: "#ffffff") | |
} | |
valueTile("power", "device.power") { | |
state ( | |
"default", | |
label:'${currentValue} W', | |
foregroundColor: "#000000", | |
/*backgroundColor:"#79b821"*/ | |
backgroundColor:"#ffffff" | |
) | |
} | |
/* | |
valueTile("energy", "device.energy") { | |
state ( | |
"default", | |
label:'${currentValue}kWh', | |
foregroundColor: "#000000", | |
backgroundColor:"#79b821" | |
) | |
} | |
*/ | |
// Controls row | |
standardTile("reset", "command.reset", inactiveLabel: false, decoration: "flat") { | |
state "default", label:'reset', action:"reset", icon: "st.Health & Wellness.health7" | |
} | |
standardTile("refresh", "command.refresh", inactiveLabel: false, decoration: "flat") { | |
state "default", label:'refresh', action:"refresh.refresh", icon:"st.secondary.refresh-icon" | |
} | |
standardTile("configure", "command.configure", inactiveLabel: false, decoration: "flat") { | |
state "configure", label:'', action: "configure", icon:"st.secondary.configure" | |
} | |
/* HEMv1 has a battery; v2 is line-powered */ | |
/* | |
valueTile("battery", "device.battery", decoration: "flat") { | |
state "battery", label:'${currentValue}% battery', unit:"" | |
} | |
*/ | |
main (["power"]) | |
details([ | |
"energyOne","energyTwo","energyDisp", | |
"powerOne","powerTwo","powerDisp", | |
"reset","refresh","configure"/*, | |
"battery"*/ | |
]) | |
} | |
preferences { | |
input "voltageValue", "number", title: "Voltage being monitored", /* description: "120", */ defaultValue: 120 | |
input "c1Name", "string", title: "Clamp 1 Name", description: "Name of appliance Clamp 1 is monitoring", defaultValue: "Clamp 1" as String | |
input "c2Name", "string", title: "Clamp 2 Name", description: "Name of appliance Clamp 2 is monitoring", defaultValue: "Clamp 2" as String | |
input "kWhCost", "string", title: "\$/kWh (0.16)", description: "0.16", defaultValue: "0.16" as String | |
input "kwhDelay", "number", title: "Energy report seconds (60)", /* description: "120", */ defaultValue: 120 | |
input "wDelay", "number", title: "Power report seconds (30)", /* description: "30", */ defaultValue: 30 | |
} | |
} | |
def installed() { | |
reset() // The order here is important | |
configure() // Since reports can start coming in even before we finish configure() | |
refresh() | |
} | |
def updated() { | |
configure() | |
resetDisplay() | |
refresh() | |
} | |
def parse(String description) { | |
def result = null | |
//log.debug "Parse input ${description}" | |
def cmd = zwave.parse(description, [0x31: 1, 0x32: 1, 0x60: 3]) | |
if (cmd) { | |
//log.debug "meter type: ${cmd.meterType}, scale: ${cmd.scale}" | |
//log.debug "cmd: ${cmd}" | |
result = createEvent(zwaveEvent(cmd)) | |
} | |
if (result?.descriptionText != null) { | |
log.debug "${result?.descriptionText}" | |
} | |
return result | |
} | |
def zwaveEvent(physicalgraph.zwave.commands.meterv1.MeterReport cmd) { | |
def dispValue | |
def newValue | |
def formattedValue | |
//def timeString = new Date().format("h:mm a", location.timeZone) | |
//log.debug "meter type: ${cmd.meterType}, scale: ${cmd.scale}, scaledMeterValue: ${cmd.scaledMeterValue}" | |
if (cmd.meterType == 33) { | |
if (cmd.scale == 0) { | |
newValue = Math.round(cmd.scaledMeterValue * 100) / 100 | |
if (newValue != state.energyValue) { | |
formattedValue = String.format("%5.2f", newValue) | |
dispValue = "Total\n${formattedValue}\nkWh" // total kWh label | |
sendEvent(name: "energyDisp", value: dispValue as String, unit: "", descriptionText: "Display Energy: ${newValue} kWh", displayed: false) | |
state.energyValue = newValue | |
[name: "energy", value: newValue, unit: "kWh", descriptionText: "Total Energy: ${formattedValue} kWh"] | |
} | |
} | |
else if (cmd.scale==2) { | |
//newValue = Math.round(cmd.scaledMeterValue*10)/10 | |
//log.debug "Total power? ${cmd.scaledMeterValue}" | |
newValue = Math.round(cmd.scaledMeterValue) // really not worth the hassle to show decimals for Watts | |
formattedValue = String.format("%d", newValue) | |
if (newValue != state.powerValue) { | |
dispValue = "Total\n"+newValue+"\nWatts" // Total watts label | |
//sendEvent(name: "powerDisp", value: dispValue as String, unit: "", descriptionText: "Display Power: ${newValue} Watts", displayed: false) | |
sendEvent(name: "powerDisp", value: dispValue as String, unit: "", descriptionText: "Display Power: ${newValue} Watts", displayed: false) | |
state.powerValue = newValue | |
[name: "power", value: newValue, unit: "W", descriptionText: "Total Power: ${formattedValue} Watts"] | |
} | |
} | |
} | |
} | |
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) { | |
def dispValue | |
def newValue | |
def formattedValue | |
if (cmd.commandClass == 50) { | |
def encapsulatedCommand = cmd.encapsulatedCommand([0x30: 1, 0x31: 1]) // can specify command class versions here like in zwave.parse | |
if (encapsulatedCommand) { | |
if (cmd.sourceEndPoint == 1) { | |
if (encapsulatedCommand.scale == 2 ) { | |
//newValue = Math.round(encapsulatedCommand.scaledMeterValue*10)/10 | |
newValue = Math.round(encapsulatedCommand.scaledMeterValue) | |
//formattedValue = String.format("%5.1f", newValue) | |
formattedValue = newValue | |
dispValue = "${c1Name}\n${formattedValue}\nWatts" // L1 Watts Label | |
if (newValue != state.powerL1) { | |
state.powerL1 = newValue | |
//[name: "powerOneDisp", value: dispValue, unit: "", descriptionText: "L1 Power: ${formattedValue} Watts"] | |
sendEvent(name: "powerOneDisp", value: dispValue as String, unit: "", descriptionText: "${c1Name} Power: ${newValue} Watts", displayed: false) | |
[name: "powerOne", value: newValue, unit: "W", descriptionText: "${c1Name} Power: ${newValue} Watts"] | |
//[name: "powerOne", value: formattedValue, unit: "W", descriptionText: "${c1Name} Power: ${formattedValue} Watts"] | |
} | |
} | |
else if (encapsulatedCommand.scale == 0 ){ | |
newValue = Math.round(encapsulatedCommand.scaledMeterValue * 100) / 100 | |
formattedValue = String.format("%5.2f", newValue) | |
dispValue = "${c1Name}\n${formattedValue}\nkWh" // L1 kWh label | |
if (newValue != state.energyL1) { | |
state.energyL1 = newValue | |
//[name: "energyOneDisp", value: dispValue, unit: "", descriptionText: "${c1Name} Energy: ${formattedValue} kWh"] | |
sendEvent(name: "energyOneDisp", value: dispValue as String, unit: "", descriptionText: "${c2Name} Energy: ${newValue} kWh", displayed: false) | |
[name: "energyOne", value: newValue, unit: "kWh", descriptionText: "${c1Name} Energy: ${newValue} kWh"] | |
//[name: "energyOne", value: formattedValue, unit: "kWh", descriptionText: "${c1Name} Energy: ${formattedValue} kWh"] | |
} | |
} | |
} | |
else if (cmd.sourceEndPoint == 2) { | |
if (encapsulatedCommand.scale == 2 ){ | |
//newValue = Math.round(encapsulatedCommand.scaledMeterValue*10)/10 | |
newValue = Math.round(encapsulatedCommand.scaledMeterValue) | |
//formattedValue = String.format("%5.1f", newValue) | |
formattedValue = newValue | |
dispValue = "${c2Name}\n${formattedValue}\nWatts" // L2 Watts Label | |
//if (newValue != state.powerL1) { | |
if (newValue != state.powerL2) { | |
state.powerL2 = newValue | |
sendEvent(name: "powerTwoDisp", value: dispValue as String, unit: "", descriptionText: "${c2Name} Power: ${newValue} Watts", displayed: false) | |
[name: "powerTwo", value: newValue, unit: "W", descriptionText: "${c2Name} Power: ${formattedValue} Watts"] | |
//[name: "powerTwo", value: formattedValue, unit: "W", descriptionText: "${c2Name} Power: ${formattedValue} Watts"] | |
} | |
} | |
else if (encapsulatedCommand.scale == 0 ){ | |
newValue = Math.round(encapsulatedCommand.scaledMeterValue * 100) / 100 | |
formattedValue = String.format("%5.2f", newValue) | |
dispValue = "${c2Name}\n${formattedValue}\nkWh" // L2 kWh label | |
if (newValue != state.energyL2) { | |
state.energyL2 = newValue | |
sendEvent(name: "energyTwoDisp", value: dispValue as String, unit: "", descriptionText: "${c2Name} Energy: ${newValue} kWh", displayed: false) | |
[name: "energyTwo", value: newValue, unit: "kWh", descriptionText: "${c2Name} Energy: ${formattedValue} kWh"] | |
//[name: "energyTwo", value: formattedValue, unit: "kWh", descriptionText: "${c2Name} Energy: ${formattedValue} kWh"] | |
} | |
} | |
} | |
} | |
} | |
} | |
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) { | |
def map = [:] | |
map.name = "battery" | |
map.unit = "%" | |
if (cmd.batteryLevel == 0xFF) { | |
map.value = 1 | |
map.descriptionText = "${device.displayName} battery is low" | |
map.isStateChange = true | |
} | |
else { | |
map.value = cmd.batteryLevel | |
} | |
//log.debug map | |
return map | |
} | |
def zwaveEvent(physicalgraph.zwave.Command cmd) { | |
// Handles all Z-Wave commands we aren't interested in | |
log.debug "Unhandled event ${cmd}" | |
[:] | |
} | |
def refresh() { // Request HEMv2 to send us the latest values for the 4 we are tracking | |
log.debug "refresh()" | |
delayBetween([ | |
zwave.meterV2.meterGet(scale: 0).format(), // Change 0 to 1 if international version | |
zwave.meterV2.meterGet(scale: 2).format(), | |
]) | |
resetDisplay() | |
} | |
def poll() { | |
log.debug "poll()" | |
refresh() | |
} | |
def resetDisplay() { | |
log.debug "resetDisplay()" | |
sendEvent(name: "powerDisp", value: "Total\n" + state.powerValue + "\nWatts", unit: "") | |
sendEvent(name: "energyDisp", value: "Total\n" + state.energyValue + "\nkWh", unit: "") | |
sendEvent(name: "powerOneDisp", value: c1Name + "\n" + state.powerL1 + "\nWatts", unit: "") | |
sendEvent(name: "powerOne", value: state.powerL1, unit: "W") | |
sendEvent(name: "energyOneDisp", value: c1Name + "\n" + state.energyL1 + "\nkWh", unit: "") | |
sendEvent(name: "energyOne", value: state.energyL1, unit: "kWh") | |
sendEvent(name: "powerTwoDisp", value: c2Name + "\n" + state.powerL2 + "\nWatts", unit: "") | |
sendEvent(name: "powerTwo", value: state.powerL2, unit: "W") | |
sendEvent(name: "energyTwoDisp", value: c2Name + "\n" + state.energyL2 + "\nkWh", unit: "") | |
sendEvent(name: "energyTwo", value: state.energyL2, unit: "kWh") | |
} | |
def reset() { | |
log.debug "reset()" | |
state.energyValue = "" | |
state.powerValue = "" | |
state.energyL1 = "" | |
state.energyL2 = "" | |
state.powerL1 = "" | |
state.powerL2 = "" | |
resetDisplay() | |
return [ | |
zwave.meterV2.meterReset().format(), | |
zwave.meterV2.meterGet(scale: 0).format() | |
] | |
configure() | |
} | |
def configure() { | |
log.debug "configure()" | |
Long kwhDelay = settings.kWhDelay as Long | |
Long wDelay = settings.wDelay as Long | |
if (kwhDelay == null) { // Shouldn't have to do this, but there seem to be initialization errors | |
kwhDelay = 15 | |
} | |
if (wDelay == null) { | |
wDelay = 15 | |
} | |
def cmd = delayBetween([ | |
zwave.configurationV1.configurationSet(parameterNumber: 1, size: 2, scaledConfigurationValue: voltageValue).format(), // assumed voltage | |
zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, scaledConfigurationValue: 1).format(), // Disable (=0) selective reporting | |
zwave.configurationV1.configurationSet(parameterNumber: 4, size: 2, scaledConfigurationValue: 10).format(), // Don't send whole HEM unless watts have changed by 30 | |
zwave.configurationV1.configurationSet(parameterNumber: 5, size: 2, scaledConfigurationValue: 10).format(), // Don't send L1 Data unless watts have changed by 15 | |
zwave.configurationV1.configurationSet(parameterNumber: 6, size: 2, scaledConfigurationValue: 10).format(), // Don't send L2 Data unless watts have changed by 15 | |
zwave.configurationV1.configurationSet(parameterNumber: 8, size: 1, scaledConfigurationValue: 20).format(), // Or by 5% (whole HEM) | |
zwave.configurationV1.configurationSet(parameterNumber: 9, size: 1, scaledConfigurationValue: 20).format(), // Or by 5% (L1) | |
zwave.configurationV1.configurationSet(parameterNumber: 10, size: 1, scaledConfigurationValue: 20).format(), // Or by 5% (L2) | |
zwave.configurationV1.configurationSet(parameterNumber: 100, size: 4, scaledConfigurationValue: 1).format(), // reset to defaults | |
zwave.configurationV1.configurationSet(parameterNumber: 110, size: 4, scaledConfigurationValue: 1).format(), // reset to defaults | |
zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 772).format(), // watt | |
zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: wDelay).format(), // every %Delay% seconds | |
zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 6152).format(), // kwh | |
zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: kwhDelay).format(), // Every %Delay% seconds | |
zwave.configurationV1.configurationSet(parameterNumber: 103, size: 4, scaledConfigurationValue: 1).format(), // battery | |
zwave.configurationV1.configurationSet(parameterNumber: 113, size: 4, scaledConfigurationValue: 3600).format() // every hour | |
], 2000) | |
log.debug cmd | |
cmd | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment