Created
March 12, 2018 11:58
-
-
Save anonymous/4feb889186b6a2009ce0883255c55c47 to your computer and use it in GitHub Desktop.
Aeon HEM DTHs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* SmartThings Device Type for Aeon Home Energy Monitor v1 (HEM v1) | |
* (Goal of project is to) Displays individual values for each clamp (L1, L2) for granular monitoring | |
* Example: Individual circuits (breakers) of high-load devices, such as HVAC or clothes dryer | |
* | |
* Original Author: Copyright 2014 Barry A. Burke | |
* | |
**/ | |
metadata { | |
// Automatically generated. Make future change here. | |
definition ( | |
name: "Aeon HEM 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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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