/** * Aeon HEMv2 * * Copyright 2014 Barry A. Burke * * 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. * * * Aeon Home Energy Meter v2 (US) * * Author: Barry A. Burke * Contributors: Brock Haymond: UI updates * * Genesys: Based off of Aeon Smart Meter Code sample provided by SmartThings (2013-05-30). Built on US model * may also work on international versions (currently reports total values only) * * History: * * 2014-06-13: Massive OverHaul * - Fixed Configuration (original had byte order of bitstrings backwards * - Increased reporting frequency to 10s - note that values won't report unless they change * (they will also report if they exceed limits defined in the settings - currently just using * the defaults). * - Added support for Volts & Amps monitoring (was only Power and Energy) * - Added flexible tile display. Currently only used to show High and Low values since last * reset (with time stamps). * - All tiles are attributes, so that their values are preserved when you're not 'watching' the * meter display * - Values are formatted to Strings in zwaveEvent parser so that we don't lose decimal values * in the tile label display conversion * - Updated fingerprint to match Aeon Home Energy Monitor v2 deviceId & clusters * - Added colors for Watts and Amps display * - Changed time format to 24 hour * 2014-06-17: Tile Tweaks * - Reworked "decorations:" - current values are no longer "flat" * - Added colors to current Watts (0-18000) & Amps (0-150) * - Changed all colors to use same blue-green-orange-red as standard ST temperature guages * 2014-06-18: Cost calculations * - Added $/kWh preference * 2014-09-07: Bug fix & Cleanup * - Fixed "Unexpected Error" on Refresh tile - (added Refresh Capability) * - Cleaned up low values - reset to ridiculously high value instead of null * - Added poll() command/capability (just does a refresh) * * 2014-09-19 GUI Tweaks, HEM v1 alterations (from Brock Haymond) * - Reworked all tiles for look, color, text formatting, & readability */ metadata { // Automatically generated. Make future change here. definition ( name: "Aeon HEMv2", namespace: "smartthings", 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 "amps", "string" attribute "energyDisp", "string" attribute "energyOne", "string" attribute "energyTwo", "string" attribute "powerDisp", "string" attribute "powerOne", "string" attribute "powerTwo", "string" attribute "voltsDisp", "string" attribute "voltsOne", "string" attribute "voltsTwo", "string" attribute "ampsDisp", "string" attribute "ampsOne", "string" attribute "ampsTwo", "string" command "reset" command "configure" // v1 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": 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": 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", backgroundColors:[ [value: "0 Watts", color: "#153591"], [value: "500 Watts", color: "#1e9cbb"], [value: "1000 Watts", color: "#90d2a7"], [value: "1500 Watts", color: "#44b621"], [value: "2000 Watts", color: "#f1d801"], [value: "2500 Watts", color: "#d04e00"], [value: "3000 Watts", color: "#bc2323"] /* [value: "0 Watts", color: "#153591"], [value: "3000 Watts", color: "#1e9cbb"], [value: "6000 Watts", color: "#90d2a7"], [value: "9000 Watts", color: "#44b621"], [value: "12000 Watts", color: "#f1d801"], [value: "15000 Watts", color: "#d04e00"], [value: "18000 Watts", color: "#bc2323"] */ ] ) } valueTile("powerOne", "device.powerOne", decoration: "flat") { state("default", label:'${currentValue}') } valueTile("powerTwo", "device.powerTwo", decoration: "flat") { state("default", label:'${currentValue}') } // Power row valueTile("energyDisp", "device.energyDisp") { state("default", label: '${currentValue}', backgroundColor:"#ffffff") } valueTile("energyOne", "device.energyOne") { state("default", label: '${currentValue}', backgroundColor:"#ffffff") } valueTile("energyTwo", "device.energyTwo") { state("default", label: '${currentValue}', backgroundColor:"#ffffff") } // Volts row valueTile("voltsDisp", "device.voltsDisp") { state "default", label: '${currentValue}', backgroundColors:[ [value: "115.6 Volts", color: "#bc2323"], [value: "117.8 Volts", color: "#D04E00"], [value: "120.0 Volts", color: "#44B621"], [value: "122.2 Volts", color: "#D04E00"], [value: "124.4 Volts", color: "#bc2323"] ] } valueTile("voltsOne", "device.voltsOne", decoration: "flat") { state "default", label:'${currentValue}' } valueTile("voltsTwo", "device.voltsTwo", decoration: "flat") { state "default", label:'${currentValue}' } // Amps row valueTile("ampsDisp", "device.ampsDisp") { state "default", label: '${currentValue}' , foregroundColor: "#000000", color: "#000000", backgroundColors:[ [value: "0 Amps", color: "#153591"], [value: "25 Amps", color: "#1e9cbb"], [value: "50 Amps", color: "#90d2a7"], [value: "75 Amps", color: "#44b621"], [value: "100 Amps", color: "#f1d801"], [value: "125 Amps", color: "#d04e00"], [value: "150 Amps", color: "#bc2323"] ] } valueTile("ampsOne", "device.ampsOne", decoration: "flat") { state "default", label:'${currentValue}' } valueTile("ampsTwo", "device.ampsTwo", decoration: "flat") { state "default", label:'${currentValue}' } // Controls row standardTile("reset", "device.energy", inactiveLabel: false) { state "default", label:'reset', action:"reset", icon: "st.Health & Wellness.health7" } standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat" ) { state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" } standardTile("configure", "device.power", inactiveLabel: false, decoration: "flat") { state "configure", label:'', action:"configure", icon:"st.secondary.configure" } valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") { state "battery", label:'${currentValue}% battery', unit:"" } // TODO: Add configurable delay button - Cycle through 10s, 30s, 1m, 5m, 60m, off? main (["powerDisp","energyDisp","ampsDisp","voltsDisp"]) details([ "energyOne","energyDisp","energyTwo", "powerOne","powerDisp","powerTwo", //"ampsOne","ampsDisp","ampsTwo", // Comment out these two lines for HEMv! //"voltsOne","voltsDisp","voltsTwo", // Comment out these two lines for HEMv1 "reset","refresh", "battery", "configure" ]) } preferences { input "kWhCost", "string", title: "\$/kWh (0.16)", defaultValue: "0.16" as String } } def parse(String description) { // log.debug "Parse received ${description}" def result = null def cmd = zwave.parse(description, [0x31: 1, 0x32: 1, 0x60: 3]) if (cmd) { result = createEvent(zwaveEvent(cmd)) } if (result) log.debug "Parse returned ${result}" return result } def zwaveEvent(physicalgraph.zwave.commands.meterv1.MeterReport cmd) { // log.debug "zwaveEvent received ${cmd}" def dispValue def newValue def timeString = new Date().format("h:mm a", location.timeZone) if (cmd.meterType == 33) { if (cmd.scale == 0) { newValue = cmd.scaledMeterValue if (newValue != state.energyValue) { dispValue = String.format("%5.2f",newValue)+"\nkWh" sendEvent(name: "energyDisp", value: dispValue as String, unit: "") state.energyValue = newValue BigDecimal costDecimal = newValue * ( kWhCost as BigDecimal) def costDisplay = String.format("%3.2f",costDecimal) sendEvent(name: "energyTwo", value: "Cost\n\$${costDisplay}", unit: "") [name: "energy", value: newValue, unit: "kWh"] } } else if (cmd.scale == 1) { newValue = cmd.scaledMeterValue if (newValue != state.energyValue) { dispValue = String.format("%5.2f",newValue)+"\nkVAh" sendEvent(name: "energyDisp", value: dispValue as String, unit: "") state.energyValue = newValue [name: "energy", value: newValue, unit: "kVAh"] } } else if (cmd.scale==2) { newValue = Math.round( cmd.scaledMeterValue ) // really not worth the hassle to show decimals for Watts if (newValue != state.powerValue) { dispValue = newValue+"\nWatts" sendEvent(name: "powerDisp", value: dispValue as String, unit: "") if (newValue < state.powerLow) { dispValue = "Low\n"+newValue+" W\n"+timeString sendEvent(name: "powerOne", value: dispValue as String, unit: "") state.powerLow = newValue } if (newValue > state.powerHigh) { dispValue = "High\n"+newValue+" W\n"+timeString sendEvent(name: "powerTwo", value: dispValue as String, unit: "") state.powerHigh = newValue } state.powerValue = newValue [name: "power", value: newValue, unit: "W"] } } } else if (cmd.meterType == 161) { if (cmd.scale == 0) { newValue = cmd.scaledMeterValue if (newValue != state.voltsValue) { dispValue = String.format("%5.2f", newValue)+"\nVolts" sendEvent(name: "voltsDisp", value: dispValue as String, unit: "") if (newValue < state.voltsLow) { dispValue = "Low\n"+String.format("%5.2f", newValue)+" V\n"+timeString sendEvent(name: "voltsOne", value: dispValue as String, unit: "") state.voltsLow = newValue } if (newValue > state.voltsHigh) { dispValue = "High\n"+String.format("%5.2f", newValue)+" V\n"+timeString sendEvent(name: "voltsTwo", value: dispValue as String, unit: "") state.voltsHigh = newValue } state.voltsValue = newValue [name: "volts", value: newValue, unit: "V"] } } else if (cmd.scale==1) { newValue = cmd.scaledMeterValue if (newValue != state.ampsValue) { dispValue = String.format("%5.2f", newValue)+"\nAmps" sendEvent(name: "ampsDisp", value: dispValue as String, unit: "") if (newValue < state.ampsLow) { dispValue = "Low\n"+String.format("%5.2f", newValue)+" A\n"+timeString sendEvent(name: "ampsOne", value: dispValue as String, unit: "") state.ampsLow = newValue } if (newValue > state.ampsHigh) { dispValue = "High\n"+String.format("%5.2f", newValue)+" A\n"+timeString sendEvent(name: "ampsTwo", value: dispValue as String, unit: "") state.ampsHigh = newValue } state.ampsValue = newValue [name: "amps", value: newValue, unit: "A"] } } } } 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} has a low battery" 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() { delayBetween([ zwave.meterV2.meterGet(scale: 0).format(), zwave.meterV2.meterGet(scale: 2).format() ]) } def poll() { refresh() } def reset() { log.debug "${device.name} reset" state.powerHigh = 0 state.powerLow = 99999 state.ampsHigh = 0 state.ampsLow = 999 state.voltsHigh = 0 state.voltsLow = 999 def dateString = new Date().format("m/d/YY", location.timeZone) def timeString = new Date().format("h:mm a", location.timeZone) sendEvent(name: "energyOne", value: "Since\n"+dateString+"\n"+timeString, unit: "") sendEvent(name: "powerOne", value: "", unit: "") sendEvent(name: "voltsOne", value: "", unit: "") sendEvent(name: "ampsOne", value: "", unit: "") sendEvent(name: "ampsDisp", value: "", unit: "") sendEvent(name: "voltsDisp", value: "", unit: "") sendEvent(name: "powerDisp", value: "", unit: "") sendEvent(name: "energyDisp", value: "", unit: "") sendEvent(name: "energyTwo", value: "Cost\n--", unit: "") sendEvent(name: "powerTwo", value: "", unit: "") sendEvent(name: "voltsTwo", value: "", unit: "") sendEvent(name: "ampsTwo", value: "", unit: "") // No V1 available def cmd = delayBetween( [ zwave.meterV2.meterReset().format(), zwave.meterV2.meterGet(scale: 0).format() ]) cmd } def configure() { // TODO: Turn on reporting for each leg of power - display as alternate view (Currently those values are // returned as zwaveEvents...they probably aren't implemented in the core Meter device yet. def cmd = delayBetween([ zwave.configurationV1.configurationSet(parameterNumber: 255, size: 4, scaledConfigurationValue: 1).format(), // Reset All Params to Factory Default zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, scaledConfigurationValue: 1).format(), // Enable selective reporting zwave.configurationV1.configurationSet(parameterNumber: 4, size: 2, scaledConfigurationValue: 50).format(), // Don't send unless watts have increased by 50 zwave.configurationV1.configurationSet(parameterNumber: 8, size: 2, scaledConfigurationValue: 10).format(), // Or by 10% (these 3 are the default values zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 10).format(), // Average Watts & Amps zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: 30).format(), // Every 30 Seconds zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 4).format(), // Average Voltage zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 150).format(), // every 2.5 minute zwave.configurationV1.configurationSet(parameterNumber: 103, size: 4, scaledConfigurationValue: 1).format(), // Total kWh (cumulative) zwave.configurationV1.configurationSet(parameterNumber: 113, size: 4, scaledConfigurationValue: 300).format() // every 5 minutes ]) log.debug cmd cmd }