Skip to content

Instantly share code, notes, and snippets.

Created March 12, 2018 11:58
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 anonymous/4feb889186b6a2009ce0883255c55c47 to your computer and use it in GitHub Desktop.
Save anonymous/4feb889186b6a2009ce0883255c55c47 to your computer and use it in GitHub Desktop.
Aeon HEM DTHs
/**
* 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 Coding",
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 "energyDisp", "string"
attribute "energyOne", "string"
attribute "energyTwo", "string"
attribute "powerDisp", "string"
attribute "powerOne", "string"
attribute "powerTwo", "string"
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.powerOne") {
state(
"default",
label:'${currentValue}',
foregroundColors:[
[value: 1, color: "#000000"],
[value: 10000, color: "#ffffff"]
],
foregroundColor: "#000000"
)
}
valueTile("powerTwo", "device.powerTwo") {
state(
"default",
label:'${currentValue}',
foregroundColors:[
[value: 1, color: "#000000"],
[value: 10000, color: "#ffffff"]
],
foregroundColor: "#000000"
)
}
// Power row
valueTile("energyDisp", "device.energyDisp") {
state(
"default",
label: '${currentValue}', // shows kWh value with ' kWh' suffix
foregroundColor: "#000000",
backgroundColor: "#ffffff")
}
valueTile("energyOne", "device.energyOne") {
state(
"default",
label: '${currentValue}', // shows kWh value with ' kWh' suffix
foregroundColor: "#000000",
backgroundColor: "#ffffff")
}
valueTile("energyTwo", "device.energyTwo") {
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"
)
}
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
def cmd = zwave.parse(description, [0x31: 1, 0x32: 1, 0x60: 3])
if (cmd) {
result = createEvent(zwaveEvent(cmd))
}
log.debug "Parse returned ${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)
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
formattedValue = String.format("%5.1f", newValue)
//newValue = Math.round(cmd.scaledMeterValue) // really not worth the hassle to show decimals for Watts
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)
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
formattedValue = String.format("%5.1f", newValue)
dispValue = "${c1Name}\n${formattedValue}\nWatts" // L1 Watts Label
if (newValue != state.powerL1) {
state.powerL1 = newValue
[name: "powerOne", value: dispValue, unit: "", descriptionText: "L1 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: "energyOne", value: dispValue, unit: "", descriptionText: "L1 Energy: ${formattedValue} kWh"]
}
}
}
else if (cmd.sourceEndPoint == 2) {
if (encapsulatedCommand.scale == 2 ){
newValue = Math.round(encapsulatedCommand.scaledMeterValue*10)/10
formattedValue = String.format("%5.1f", newValue)
dispValue = "${c2Name}\n${formattedValue}\nWatts" // L2 Watts Label
if (newValue != state.powerL1) {
state.powerL2 = newValue
[name: "powerTwo", value: dispValue, unit: "", descriptionText: "L2 Power: ${formattedValue} Wa1tts"]
}
}
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
[name: "energyTwo", value: dispValue, unit: "", descriptionText: "L2 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: "W")
sendEvent(name: "energyDisp", value: "Total\n" + state.energyValue + "\nkWh", unit: "kWh")
sendEvent(name: "powerOne", value: c1Name + "\n" + state.powerL1 + "\nWatts", unit: "W")
sendEvent(name: "energyOne", value: c1Name + "\n" + state.energyL1 + "\nkWh", unit: "kWh")
sendEvent(name: "powerTwo", value: c2Name + "\n" + state.powerL2 + "\nWatts", unit: "W")
sendEvent(name: "energyTwo", value: c2Name + "\n" + state.energyL2 + "\nkWh", 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: 0).format(), // Disable (=0) selective reporting
zwave.configurationV1.configurationSet(parameterNumber: 4, size: 2, scaledConfigurationValue: 1).format(), // Don't send whole HEM unless watts have changed by 30
zwave.configurationV1.configurationSet(parameterNumber: 5, size: 2, scaledConfigurationValue: 1).format(), // Don't send L1 Data unless watts have changed by 15
zwave.configurationV1.configurationSet(parameterNumber: 6, size: 2, scaledConfigurationValue: 1).format(), // Don't send L2 Data unless watts have changed by 15
zwave.configurationV1.configurationSet(parameterNumber: 8, size: 1, scaledConfigurationValue: 5).format(), // Or by 5% (whole HEM)
zwave.configurationV1.configurationSet(parameterNumber: 9, size: 1, scaledConfigurationValue: 5).format(), // Or by 5% (L1)
zwave.configurationV1.configurationSet(parameterNumber: 10, size: 1, scaledConfigurationValue: 5).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: 10).format() // every hour
], 2000)
log.debug cmd
cmd
}
/**
* Aeon HEM1
*
* 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 gen-1 (US)
*
* Updates:
* -------
* 02-15-2016 : Removed posting to the Activity Feed in the phone app and event log.
* 02-17-2016 : Fixed preferences for kWh cost from string to number.
* 02-20-2016 : Enabled battery reporting (parameter 103, value 1), and documented the parameters better.
* 02-21-2016 : Made certain configuration parameters changeable via device preferences instead of having to tweak code all the time.
* 02-22-2016 : Fixed kWh cost entry in Preferences not allowing decimals.
* 02-27-2016 : Changed date formats to be MM-dd-yyyy h:mm a.
* 02-29-2016 : Changed reportType variable from 0 to 1.
* 03-11-2016 : Due to ST's v2.1.0 app totally hosing up SECONDARY_CONTROL, implemented a workaround to display that info in a separate tile.
* 03-19-2016 : Added clarity for preferences.
* 03-21-2016 : Fixed issue when resetting energy would also reset watts.
* 03-25-2016 : Removed the \n from the two tiles for resetting watts and energy due to rendering issues on iOS
* 07-07-2016 : Check for wildly large watts value coming from the HEM and do not process them. Firmware updates should have resolved this.
* 08-10-2016 : Check for 0 or negative watts value coming from the HEM and do not process them. Firmware updates should have resolved this.
* 08-21-2016 : Created separate tiles to reset min and max instead of having a single tile for both values. Changed many tiles to different sizes.
* 08-27-2016 : Modified the device handler for my liking, primarly for looks and feel.
* 09-16-2016 : During the check for 0 or negative values, use the last power value (state.powerValue) instead of just a hard coded value.
* 10-17-2016 : Cleaned up code.
* 10-19-2016 : Provided comments in the code for iOS users to edit so that the rendering of text for certain tiles to work right. Changed default icon.
* 10-19-2016 : Added a new parameter in Preferences so that a user can specify the high limit for a watts value instead of hard coding a value. Related to the change on 7-7-2016.
* 11-22-2016 : Added resetMeter section that calls the other resets (min, max, energy/cost). This is for a SmartApp that resets the meter automatically at the 1st day of month.
* 01-08-2017 : Added parameter 12 and set it to 1. Accumulates kWh energy when Battery Powered.
* 01-08-2017 : Cleaned up code in the resetMeter section.
* 01-08-2017 : Added code for Health Check capabilities/functions, and cleaned up code in the resetMeter section.
* 01-18-2017 : Removed code no longer needed, and added another parameter in Preference to enable or disable the display of values in the Recently tab and device's event log (not Live Logs). Enabling may be required for some SmartApps.
* 01-20-2017 : Removed the check for 0w, but still don't allow negative values. Also removed all rounding, which now displays 3 positions right of the decimal.
* 02-11-2017 : Removed commands no longer needed. Documented what each attribute is used for. Put battery info into the main tile instead of a separate tile.
* 02-12-2017 : Combined the battery and no-battery version into a single DTH, cleaned up code, and general improvements.
* 02-13-2017 : Cleaned up code for battery message being displayed. If someone decides to display battery % while not having batteries installed Health Check will catch that and push low battery notifications until the user disables the display.
* 03-11-2017 : Changed from valueTile to standardTile for a few tiles since ST's mobile app v2.3.x changed something between the two.
* 03-26-2017 : Added a new device Preference that allows for selecting how many decimal positions should be used to display for WATTS and kWh. What's stored for the actual meter reading that's seen in the IDE for Power, and what's sent to SmartApps, has not changed.
* 03-29-2017 : Made changes to account for ST v2.3.1 bugs with text rendering.
* 04-28-2017 : Cleaned up code, and some formatting/tile layout changes for my liking.
* 05-18-2017 : Changed valueTile to standardTile to resolve font/rendering issues.
* 05-28-2017 : Sometimes the HEM will send a super low reading, like 0.04672386; which in that case the decimal position setting would not get applied if you used 3. That's been fixed.
* 05-30-2017 : Thanks to @barkis for the following changes: fixed number of decimal points display on iPhone app; fixed problem with kVAh units display; changed refresh() to immediately update values; reworked decimal place selection to remove 'else' branch that never executes; removed some un-needed temporary variables; code cleanup (trailing whitespace/converted leading spaces to tabs to shrink file size)
* 06-05-2017 : Some tweaks for the 2.4.0 release of the mobile app.
* 06-12-2017 : Updated code to make sure kWh or kVAh readings from the reader are larger that the previous reading. There should never be a smaller reading from the previous reading.
* 06-14-2017 : Updated to fix battery reports showing up in the Recently tab when they shouldn't.
* 06-19-2017 : Cleaned up code in the resetkwh() section.
* 06-29-2017 : Rolled back change from 6-12-2017 until I find a better method of checking.
* 09-27-2017 : Changed tile format for device min/max history to look like my Aeon and Zooz DTH's.
* 09-28-2017 : Changed history tile from "standard" to "value", and reduced the number of dashes so it works better for iOS.
* 10-04-2017 : Fixed reset issues with energy/kWh not resetting properly. (more of a workaround for now)
* 10-07-2017 : Fixed code for battery reports still going to the Recently tab in the mobile app even though the option not to send messages was enabled.
* 10-26-2017 : Added 2 new attributes to capture kWh and cost data before they're reset in case someone needs to refer back to them for any reason. These can be seen in the IDE and in the Recently Tab in the mobile app.
*
*/
metadata {
definition (name: "My Aeon Home Energy Monitor Gen1", namespace: "jscgs350", author: "jscgs350")
{
capability "Energy Meter"
capability "Power Meter"
capability "Configuration"
capability "Sensor"
capability "Refresh"
capability "Polling"
capability "Battery"
// capability "Health Check"
attribute "currentKWH", "string" // Used to show current kWh since last reset
attribute "currentWATTS", "string" // Used to show current watts being used on the main tile
attribute "minWATTS", "string" // Used to store/display minimum watts used since last reset
attribute "maxWATTS", "string" // Used to store/display maximum watts used since last reset
attribute "resetMessage", "string" // Used for messages of what was reset (min, max, energy, or all values)
attribute "kwhCosts", "string" // Used to show energy costs since last reset
attribute "batteryStatus", "string"
attribute "kWhLastReset", "number"
attribute "CostLastReset", "number"
command "resetkwh"
command "resetmin"
command "resetmax"
command "resetMeter"
fingerprint deviceId: "0x2101", inClusters: " 0x70,0x31,0x72,0x86,0x32,0x80,0x85,0x60"
}
// tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"currentWATTS", type: "generic", width: 6, height: 4, decoration: "flat"){
tileAttribute ("device.currentWATTS", key: "PRIMARY_CONTROL") {
attributeState "default", label: '${currentValue}W', icon: "https://raw.githubusercontent.com/constjs/jcdevhandlers/master/img/device-activity-tile@2x.png", backgroundColor: "#79b821"
}
tileAttribute ("device.batteryStatus", key: "SECONDARY_CONTROL") {
attributeState "batteryStatus", label:'${currentValue}', icon:"https://raw.githubusercontent.com/constjs/jcdevhandlers/master/img/Battery-Charge-icon.png"
}
}
standardTile("iconTile", "iconTile", inactiveLabel: false, width: 1, height: 1) {
state "default", icon:"https://raw.githubusercontent.com/constjs/jcdevhandlers/master/img/device-activity-tile@2x.png"
}
valueTile("statusText", "statusText", inactiveLabel: false, decoration: "flat", width: 5, height: 1) {
state "statusText", label:'${currentValue}', backgroundColor:"#ffffff"
}
valueTile("resetMessage", "device.resetMessage", width: 5, height: 1, inactiveLabel: false, decoration: "flat") {
state("default", label: '${currentValue}', backgroundColor:"#ffffff")
}
valueTile("currentKWH", "device.currentKWH", width: 3, height: 1, inactiveLabel: false, decoration: "flat") {
state("default", action: "refresh", label: '${currentValue}', backgroundColor:"#ffffff")
}
valueTile("kwhCosts", "device.kwhCosts", width: 3, height: 1, inactiveLabel: false, decoration: "flat") {
state("default", label: 'Cost ${currentValue}', backgroundColor:"#ffffff")
}
standardTile("resetmin", "device.resetmin", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'Reset Min', action:"resetmin", icon:"st.secondary.refresh-icon"
}
standardTile("resetmax", "device.resetmax", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'Reset Max', action:"resetmax", icon:"st.secondary.refresh-icon"
}
standardTile("resetkwh", "device.resetkwh", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'Reset Energy', action:"resetkwh", icon:"st.secondary.refresh-icon"
}
standardTile("refresh", "device.refresh", width: 3, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'Refresh', action:"refresh", icon:"st.secondary.refresh-icon"
}
standardTile("configure", "device.configure", width: 3, height: 2, inactiveLabel: false, decoration: "flat") {
state "configure", label:'', action:"configure", icon:"st.secondary.configure"
}
valueTile("history", "device.history", decoration:"flat",width: 6, height: 2) {
state "history", label:'${currentValue}'
}
main (["currentWATTS"])
details(["currentWATTS", "currentKWH", "kwhCosts", "history", "resetmin", "resetmax", "resetkwh", "refresh", "configure"])
}
preferences {
input "displayEvents", "boolean", title: "Display all power and energy events in the Recently tab and the device's event log?", defaultValue: false, required: false, displayDuringSetup: true
input "displayBatteryLevel", "boolean", title: "Display battery level on main tile and Recently tab?", defaultValue: true, required: false, displayDuringSetup: true
input "kWhCost", "string", title: "Enter your cost per kWh (or just use the default, or use 0 to not calculate):", defaultValue: 0.16, required: false, displayDuringSetup: true
input "wattsLimit", "number", title: "Sometimes the HEM will send a wildly large watts value. What limit should be in place so that it's not processed? (in watts)", defaultValue: 20000, required: false, displayDuringSetup: true
input "reportType", "number", title: "ReportType: Send watt/kWh data on a time interval (0), or on a change in wattage (1)? Enter a 0 or 1:", defaultValue: 1, range: "0..1", required: false, displayDuringSetup: true
input "wattsChanged", "number", title: "For ReportType = 1, Don't send unless watts have changed by this many watts: (range 0 - 32,000W)", defaultValue: 50, range: "0..32000", required: false, displayDuringSetup: true
input "wattsPercent", "number", title: "For ReportType = 1, Don't send unless watts have changed by this percent: (range 0 - 99%)", defaultValue: 10, range: "0..99", required: false, displayDuringSetup: true
input "secondsWatts", "number", title: "For ReportType = 0, Send Watts data every how many seconds? (range 0 - 65,000 seconds)", defaultValue: 15, range: "0..65000", required: false, displayDuringSetup: true
input "secondsKwh", "number", title: "For ReportType = 0, Send kWh data every how many seconds? (range 0 - 65,000 seconds)", defaultValue: 60, range: "0..65000", required: false, displayDuringSetup: true
input "secondsBattery", "number", title: "If the HEM has batteries installed, send battery data every how many seconds? (range 0 - 65,000 seconds)", defaultValue: 900, range: "0..65000", required: false, displayDuringSetup: true
input "decimalPositions", "number", title: "How many decimal positions do you want watts AND kWh to display? (range 0 - 3)", defaultValue: 3, range: "0..3", required: false, displayDuringSetup: true
}
}
def updated() {
// Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
state.displayDisabled = ("true" == displayEvents)
state.displayBattery = ("true" == displayBatteryLevel)
log.debug "updated (kWhCost: ${kWhCost}, wattsLimit: ${wattsLimit}, reportType: ${reportType}, wattsChanged: ${wattsChanged}, wattsPercent: ${wattsPercent}, secondsWatts: ${secondsWatts}, secondsKwh: ${secondsKwh}, secondsBattery: ${secondsBattery}, decimalPositions: ${decimalPositions})"
response(configure())
}
def parse(String description) {
// log.debug "Parse received ${description}"
def result = null
def cmd = zwave.parse(description, [0x31: 1, 0x32: 1, 0x60: 3, 0x80: 1])
// log.debug "Parse returned ${cmd}"
if (cmd) {
result = createEvent(zwaveEvent(cmd))
}
// if (result) log.debug "Result returned ${result}"
if (state.displayBattery) {
def batteryStatusmsg = "USB power, batteries at ${device.currentState('battery')?.value}%"
sendEvent(name: "batteryStatus", value: batteryStatusmsg, displayed: false)
} else {
def batteryStatusmsg = "USB power"
sendEvent(name: "batteryStatus", value: batteryStatusmsg, displayed: false)
}
return result
}
def zwaveEvent(physicalgraph.zwave.commands.meterv1.MeterReport cmd) {
def dispValue
def newValue
def timeString = new Date().format("MM-dd-yy h:mm a", location.timeZone)
if (cmd.meterType == 33) {
if (cmd.scale == 0) {
newValue = cmd.scaledMeterValue
// log.debug "newValue is ${newValue} and prevValue is ${state.energyValue}"
if (newValue != state.energyValue) {
if (decimalPositions == 2) {
dispValue = String.format("%3.2f",newValue)
} else if (decimalPositions == 1) {
dispValue = String.format("%3.1f",newValue)
} else if (decimalPositions == 0) {
dispValue = Math.round(cmd.scaledMeterValue)
} else {
dispValue = String.format("%3.3f",newValue) // default
}
dispValue += " kWh"
sendEvent(name: "currentKWH", value: dispValue as String, unit: "", displayed: false)
state.energyValue = newValue
BigDecimal costDecimal = newValue * (kWhCost as BigDecimal)
def costDisplay = "\$"
costDisplay += String.format("%3.2f",costDecimal)
sendEvent(name: "kwhCosts", value: costDisplay as String, unit: "", displayed: false)
if (state.displayDisabled) {
[name: "energy", value: newValue, unit: "kWh", displayed: true]
} else {
[name: "energy", value: newValue, unit: "kWh", displayed: false]
}
}
} else if (cmd.scale == 1) {
newValue = cmd.scaledMeterValue
if (newValue != state.energyValue) {
dispValue = newValue + " kVAh"
sendEvent(name: "currentKWH", value: dispValue as String, unit: "", displayed: false)
state.energyValue = newValue
if (state.displayDisabled) {
[name: "energy", value: newValue, unit: "kVAh", displayed: true]
} else {
[name: "energy", value: newValue, unit: "kVAh", displayed: false]
}
}
}
else if (cmd.scale==2) {
newValue = cmd.scaledMeterValue // Remove all rounding
if (newValue < 0) {newValue = state.powerValue} // Don't want to see negative numbers as a valid minimum value (something isn't right with the meter) so use the last known good meter reading
if (newValue < wattsLimit) { // don't handle any wildly large readings due to firmware issues
if (newValue != state.powerValue) { // Only process a meter reading if it isn't the same as the last one
if (decimalPositions == 2) {
dispValue = String.format("%3.2f",newValue)
} else if (decimalPositions == 1) {
dispValue = String.format("%3.1f",newValue)
} else if (decimalPositions == 0) {
dispValue = Math.round(cmd.scaledMeterValue)
} else {
dispValue = String.format("%3.3f",newValue) // default
}
if (newValue < state.powerLow) {
def dispLowValue = dispValue+"w on "+timeString
sendEvent(name: "minWATTS", value: dispLowValue as String, unit: "", displayed: false)
state.powerLow = newValue
def historyDisp = ""
historyDisp = "Minimum/Maximum Readings as of ${timeString}\n------------------------------------------------------\nPower Low : ${device.currentState('minWATTS')?.value}\nPower High : ${device.currentState('maxWATTS')?.value}\nMessages : ${device.currentState('resetMessage')?.value}"
sendEvent(name: "history", value: historyDisp, displayed: false)
}
if (newValue > state.powerHigh) {
def dispHighValue = dispValue+"w on "+timeString
def historyDisp = ""
sendEvent(name: "maxWATTS", value: dispHighValue as String, unit: "", displayed: false)
state.powerHigh = newValue
historyDisp = "Minimum/Maximum Readings as of ${timeString}\n------------------------------------------------------\nPower Low : ${device.currentState('minWATTS')?.value}\nPower High : ${device.currentState('maxWATTS')?.value}\nMessages : ${device.currentState('resetMessage')?.value}"
sendEvent(name: "history", value: historyDisp, displayed: false)
}
sendEvent(name: "currentWATTS", value: dispValue as String, unit: "", displayed: false)
state.powerValue = newValue
if (state.displayDisabled) {
[name: "power", value: newValue, unit: "W", displayed: true]
} else {
[name: "power", value: newValue, unit: "W", displayed: false]
}
}
}
}
}
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
if (state.displayBattery) {
def map = [:]
map.name = "battery"
map.unit = "%"
map.displayed = true
if (cmd.batteryLevel == 0xFF) { // low battery message from device
map.value = 1
map.isStateChange = true
} else {
map.value = cmd.batteryLevel
map.isStateChange = true
}
sendEvent(name: "battery", value: map.value as String, displayed: true)
return map
} else {
def map = [:]
map.name = "battery"
map.unit = "%"
map.value = 99
map.displayed = false
map.isStateChange = true
sendEvent(name: "battery", value: map.value as String, displayed: false)
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() {
log.debug "Refreshed ${device.name}"
state.energyValue = -1 // force tile update
state.powerValue = -1
delayBetween([
zwave.meterV2.meterGet(scale: 0).format(),
zwave.meterV2.meterGet(scale: 2).format()
])
}
def poll() {
refresh()
}
// PING is used by Device-Watch in attempt to reach the Device
def ping() {
refresh()
}
def resetkwh() {
log.debug "${device.name} reset kWh/Cost values"
def timeString = new Date().format("MM-dd-yy h:mm a", location.timeZone)
def resetDisp = ""
resetDisp = "kWh value at time of last reset was ${device.currentState('currentKWH')?.value}"
sendEvent(name: "kWhLastReset", value: resetDisp, displayed: true)
resetDisp = "Costs at time of last reset was ${device.currentState('kwhCosts')?.value}"
sendEvent(name: "CostLastReset", value: resetDisp, displayed: true)
def historyDisp = ""
sendEvent(name: "resetMessage", value: "Energy Data (kWh/Cost) Reset On:\n"+timeString, unit: "", displayed: true)
sendEvent(name: "currentKWH", value: "", unit: "", displayed: false)
sendEvent(name: "kwhCosts", value: "", unit: "", displayed: false)
historyDisp = "Minimum/Maximum Readings as of ${timeString}\n------------------------------------------------------\nPower Low : ${device.currentState('minWATTS')?.value}\nPower High : ${device.currentState('maxWATTS')?.value}\nMessages : ${device.currentState('resetMessage')?.value}"
sendEvent(name: "history", value: historyDisp, displayed: false)
state.energyValue = 0
def cmd = delayBetween( [
zwave.meterV2.meterReset().format(),
zwave.meterV2.meterGet(scale: 0).format(),
zwave.meterV2.meterGet(scale: 2).format()
])
cmd
}
def resetmin() {
log.debug "${device.name} reset minimum watts value"
def historyDisp = ""
state.powerLow = 99999
def timeString = new Date().format("MM-dd-yy h:mm a", location.timeZone)
sendEvent(name: "resetMessage", value: "Watts Data Minimum Value Reset On:\n"+timeString, unit: "")
sendEvent(name: "minWATTS", value: "", unit: "", displayed: false)
historyDisp = "Minimum/Maximum Readings as of ${timeString}\n------------------------------------------------------\nPower Low : ${device.currentState('minWATTS')?.value}\nPower High : ${device.currentState('maxWATTS')?.value}\nMessages : ${device.currentState('resetMessage')?.value}"
sendEvent(name: "history", value: historyDisp, displayed: false)
def cmd = delayBetween( [
zwave.meterV2.meterGet(scale: 0).format(),
zwave.meterV2.meterGet(scale: 2).format()
])
cmd
}
def resetmax() {
log.debug "${device.name} reset maximum watts value"
def historyDisp = ""
state.powerHigh = 0
def timeString = new Date().format("MM-dd-yy h:mm a", location.timeZone)
sendEvent(name: "resetMessage", value: "Watts Data Maximum Value Reset On:\n"+timeString, unit: "")
sendEvent(name: "maxWATTS", value: "", unit: "", displayed: false)
historyDisp = "Minimum/Maximum Readings as of ${timeString}\n------------------------------------------------------\nPower Low : ${device.currentState('minWATTS')?.value}\nPower High : ${device.currentState('maxWATTS')?.value}\nMessages : ${device.currentState('resetMessage')?.value}"
sendEvent(name: "history", value: historyDisp, displayed: false)
def cmd = delayBetween( [
zwave.meterV2.meterGet(scale: 0).format(),
zwave.meterV2.meterGet(scale: 2).format()
])
cmd
}
def resetMeter() {
log.debug "Resetting all home energy meter values..."
def resetDisp = ""
resetDisp = "kWh value at time of last reset was ${device.currentState('currentKWH')?.value}"
sendEvent(name: "kWhLastReset", value: resetDisp, displayed: true)
resetDisp = "Costs at time of last reset was ${device.currentState('kwhCosts')?.value}"
sendEvent(name: "CostLastReset", value: resetDisp, displayed: true)
def historyDisp = ""
state.powerHigh = 0
state.powerLow = 99999
state.energyValue = 0
sendEvent(name: "minWATTS", value: "", unit: "", displayed: false)
sendEvent(name: "maxWATTS", value: "", unit: "", displayed: false)
sendEvent(name: "currentKWH", value: "", unit: "", displayed: false)
sendEvent(name: "kwhCosts", value: "Cost\n--", unit: "", displayed: false)
def timeString = new Date().format("MM-dd-yy h:mm a", location.timeZone)
sendEvent(name: "resetMessage", value: "HEM was reset on "+timeString, unit: "", displayed: true)
historyDisp = "Minimum/Maximum Readings as of ${timeString}\n------------------------------------------------------\nPower Low : ${device.currentState('minWATTS')?.value}\nPower High : ${device.currentState('maxWATTS')?.value}\nMessages : ${device.currentState('resetMessage')?.value}"
sendEvent(name: "history", value: historyDisp, displayed: false)
def cmd = delayBetween( [
zwave.meterV2.meterReset().format(),
zwave.meterV2.meterGet(scale: 0).format(),
zwave.meterV2.meterGet(scale: 2).format()
])
cmd
}
def configure() {
log.debug "${device.name} configuring..."
def cmd = delayBetween([
// Perform a complete factory reset. Use this all by itself and comment out all others below.
// Once reset, comment this line out and uncomment the others to go back to normal
// zwave.configurationV1.configurationSet(parameterNumber: 255, size: 4, scaledConfigurationValue: 1).format()
// Accumulate kWh energy when Battery Powered. By default this is disabled to assist saving battery power. (0 == disable, 1 == enable)
zwave.configurationV1.configurationSet(parameterNumber: 12, size: 1, scaledConfigurationValue: 1).format(),
// Send data based on a time interval (0), or based on a change in wattage (1). 0 is default and enables parameters 111, 112, and 113. 1 enables parameters 4 and 8.
zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, scaledConfigurationValue: reportType).format(),
// If parameter 3 is 1, don't send unless watts have changed by 50 <default> for the whole device.
zwave.configurationV1.configurationSet(parameterNumber: 4, size: 2, scaledConfigurationValue: wattsChanged).format(),
// If parameter 3 is 1, don't send unless watts have changed by 10% <default> for the whole device.
zwave.configurationV1.configurationSet(parameterNumber: 8, size: 1, scaledConfigurationValue: wattsPercent).format(),
// Defines the type of report sent for Reporting Group 1 for the whole device. 1->Battery Report, 4->Meter Report for Watt, 8->Meter Report for kWh
zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 4).format(), //watts
// If parameter 3 is 0, report every XX Seconds (for Watts) for Reporting Group 1 for the whole device.
zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: secondsWatts).format(),
// Defines the type of report sent for Reporting Group 2 for the whole device. 1->Battery Report, 4->Meter Report for Watt, 8->Meter Report for kWh
zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 8).format(), //kWh
// If parameter 3 is 0, report every XX seconds (for kWh) for Reporting Group 2 for the whole device.
zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: secondsKwh).format(),
// Defines the type of report sent for Reporting Group 3 for the whole device. 1->Battery Report, 4->Meter Report for Watt, 8->Meter Report for kWh
zwave.configurationV1.configurationSet(parameterNumber: 103, size: 4, scaledConfigurationValue: 1).format(), //battery
// If parameter 3 is 0, report every XX seconds (for battery) for Reporting Group 2 for the whole device.
zwave.configurationV1.configurationSet(parameterNumber: 113, size: 4, scaledConfigurationValue: secondsBattery).format()
])
cmd
}
/*
Dual Clamps: Update of Mike Maxwell's HEM Laundry monitoring device for Aeon HEM V1. Includes customizations
from Oglewon and MEarly.
SmartThings thread: https://community.smartthings.com/t/aeon-home-energy-meter-v1-read-clamps-separately/25480/100?u=danabw
Status:
1. Done: Oglewon - Fix reported number of buttons so both are announced/available in SmartApps
2. Done: MEarly - Set Washer/Dryer On=pushed event; Washer/Dryer Off=held event. Allows notifications at start/end of cycle, & start/stop logging under Recent Activity.
2. Done: Danabw - All Preferences entries labeled
3. Need help/later: 1) Add reporting frequency setting to preferences (currently only managed in Configuration section, not available in preferences
4. Need help/later: 2) ID what KWhDelay and detailDelay settings control, and remove them from Preferences if user control not necessary
*/
metadata {
definition (name: "Dual Clamps: Update of Maxwell's Aeon HEM V1 Laundry DTH", namespace: "MikeMaxwell", author: "Mike Maxwell")
{
capability "Configuration"
capability "Switch"
capability "Button"
//capability "Energy Meter"
capability "Actuator"
capability "Holdable Button"
capability "Sensor"
attribute "washerWatts", "string"
attribute "dryerWatts", "string"
attribute "washerState", "string"
attribute "dryerState", "string"
// command "configure"
fingerprint deviceId: "0x2101", inClusters: " 0x70,0x31,0x72,0x86,0x32,0x80,0x85,0x60"
}
preferences {
input name: "c1Name", type: "text", title: "Clamp 1 device (e.g., Washer)", description: "", required: true
input name: "washerRW", type: "number", title: "Minimum watts device draws when running:", description: "", required: true
input name: "c2Name", type: "text", title: "Clamp 2 device (e.g., Dryer)", description: "", required: true
input name: "dryerRW", type: "number", title: "Minimum watts device draws when running:", description: "", required: true
input name: "voltageValue", type: "number", title: "Line voltage: 120 or 240", description: "", required: true
input name: "kWhCost", type: "number", title: "Cost per kWh", description: "", required: true
// Not sure what kWhDelay is or does. Not sure how to remove it from the Preferences screen - it displaye w/no label before I created this entry.
input name: "kWhDelay", type: "number", title: "kWh Delay", description: "", required: true
// Not sure what detailDelay is or does. Not sure how to remove it from the Preferences screen - it displaye w/no label before I created this entry.
input name: "detailDelay", type: "number", title: "Detail Delay", description: "", required: true
// Would like to add an entry in Preferences to set the HEM reporting frequency (it is included in the Configuration
// section at the end of the code, but I don't know how to do that. :)
}
simulator {
}
tiles(scale: 2) {
multiAttributeTile(name:"laundryState", type: "generic", width: 6, height: 4, canChangeIcon: false){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
// attributeState "on", label:'Laundry Running', icon:"st.Appliances.appliances1", backgroundColor:"#53a7c0"
// attributeState "off", label:'Laundry Done', icon:"st.Appliances.appliances1", backgroundColor:"#ffffff"
attributeState "on", label:'', icon:"st.samsung.da.dryer_ic_dryer", backgroundColor:"#79b821"
attributeState "off", label:'', icon:"st.samsung.da.dryer_ic_dryer", backgroundColor:"#ffffff"
}
tileAttribute("device.switch", key: "SECONDARY_CONTROL") {
attributeState "on", label:'Laundry Running'
attributeState "off", label:'Laundry Done'
}
}
/*
valueTile("washerState", "device.washerState", width: 3, height: 2, canChangeIcon: true) {
state "default", label:'Washer\n${currentValue}'
}
valueTile("dryerState", "device.dryerState", width: 3, height: 2, canChangeIcon: true) {
state "default", label:'Dryer\n${currentValue}'
}
*/
standardTile("washerState", "device.washerState", width: 3, height: 3, canChangeIcon: true) {
state "off", label:'${name}', icon: "st.samsung.da.washer_ic_washer", backgroundColor:"#ffffff"
state "on", label:'${name}', icon: "st.samsung.da.washer_ic_washer", backgroundColor:"#79b821"
}
standardTile("dryerState", "device.dryerState", width: 3, height: 3, canChangeIcon: true) {
state "off", label:'${name}', icon: "st.samsung.da.dryer_ic_dryer", backgroundColor:"#ffffff"
state "on", label:'${name}', icon: "st.samsung.da.dryer_ic_dryer", backgroundColor:"#79b821"
}
valueTile("washer", "device.washerWatts", width: 3, height: 2, decoration: "flat") {
state("default", label:'Washer\n${currentValue} Watts', foregroundColor: "#000000")
}
valueTile("dryer", "device.dryerWatts", width: 3, height: 2, decoration: "flat") {
state("default", label:'Dryer\n${currentValue} Watts', foregroundColor: "#000000")
}
standardTile("configure", "device.configure", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
main "laundryState"
details(["laundryState","washerState","dryerState","washer","dryer","configure"])
}
}
def parse(String 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?.descriptionText}"
return result
} else {
}
}
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
//log.info "mc3v cmd: ${cmd}"
if (cmd.commandClass == 50) {
def encapsulatedCommand = cmd.encapsulatedCommand([0x30: 1, 0x31: 1])
if (encapsulatedCommand) {
def scale = encapsulatedCommand.scale
def value = encapsulatedCommand.scaledMeterValue
def source = cmd.sourceEndPoint
def str = ""
def name = ""
if (scale == 2 ){ //watts
str = "watts"
if (source == 1){
name = "washerWatts"
if (value >= settings.washerRW.toInteger()){
//washer is on
sendEvent(name: "washerState", value: "on", displayed: true)
//button event
if (!state.washerIsRunning)
sendEvent(name: "button", value: "pushed", data: [buttonNumber: 1], descriptionText: "Washer has started.", isStateChange: true)
state.washerIsRunning = true
} else {
//washer is off
if (state.washerIsRunning == true){
//button event
//
// Edited "Held" to send "Push" instead for clamp 1 (Washer clamp).
// Original sendEvent(name: "button", value: "held", data: [buttonNumber: 1], descriptionText: "Washer has finished.", isStateChange: true)
sendEvent(name: "button", value: "pushed", data: [buttonNumber: 1], descriptionText: "Washer has finished.", isStateChange: true)
}
sendEvent(name: "washerState", value: "off", displayed: false)
state.washerIsRunning = false
}
} else {
name = "dryerWatts"
if (value >= settings.dryerRW.toInteger()){
//dryer is on
sendEvent(name: "dryerState", value: "on", displayed: false)
//button event
if (!state.dryerIsRunning)
sendEvent(name: "button", value: "pushed", data: [buttonNumber: 2], descriptionText: "Dryer has started.", isStateChange: true)
state.dryerIsRunning = true
} else {
//dryer is off
if (state.dryerIsRunning == true){
//button event
sendEvent(name: "button", value: "held", data: [buttonNumber: 2], descriptionText: "Dryer has finished.", isStateChange: true)
}
sendEvent(name: "dryerState", value: "off", displayed: false)
state.dryerIsRunning = false
}
}
if (state.washerIsRunning || state.dryerIsRunning){
sendEvent(name: "switch", value: "on", descriptionText: "Washer and/or Dryer running...", displayed: true)
} else {
sendEvent(name: "switch", value: "off", displayed: false)
}
//log.debug "mc3v- name: ${name}, value: ${value}, unit: ${str}"
return [name: name, value: value.toInteger(), unit: str, displayed: false]
}
}
}
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Handles all Z-Wave commands we aren't interested in
//log.debug "Unhandled event ${cmd}"
[:]
}
def configure() {
log.debug "configure()"
initialize()
def cmd = delayBetween([
//zwave.configurationV1.configurationSet(parameterNumber: 100, size: 4, scaledConfigurationValue:1).format(), //reset if not 0
//zwave.configurationV1.configurationSet(parameterNumber: 110, size: 4, scaledConfigurationValue: 1).format(), //reset if not 0
zwave.configurationV1.configurationSet(parameterNumber: 1, size: 2, scaledConfigurationValue: 120).format(), // assumed voltage
zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, scaledConfigurationValue: 0).format(), // Disable (=0) selective reporting
zwave.configurationV1.configurationSet(parameterNumber: 9, size: 1, scaledConfigurationValue: 10).format(), // Or by 10% (L1)
zwave.configurationV1.configurationSet(parameterNumber: 10, size: 1, scaledConfigurationValue: 10).format(), // Or by 10% (L2)
zwave.configurationV1.configurationSet(parameterNumber: 20, size: 1, scaledConfigurationValue: 1).format(), //usb = 1
zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 6912).format(),
zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: 30).format() // Every 30 seconds
], 2000)
return cmd
}
def installed() {
configure()
}
def updated() {
configure()
}
def initialize() {
sendEvent(name: "numberOfButtons", value: 2)
}
/**
* 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: 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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment