Skip to content

Instantly share code, notes, and snippets.

@talz13
Created March 9, 2018 19:03
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 talz13/76b6ddc1fe3b12d6752702e5df2c34e3 to your computer and use it in GitHub Desktop.
Save talz13/76b6ddc1fe3b12d6752702e5df2c34e3 to your computer and use it in GitHub Desktop.
SmartThings device handler for Aeon HEM v1 to read clamps separately
/**
* 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