Securifi Peanut Smart Plug

I understand. I'm not a real big fan myself, but the darn things work so well. They consistently switch on and off faster than my peanut plugs, never a delay and never go unresponsive. They appear to route for at least 5 devices, since that is how many I've seen associated with one in the zigbee routing table at the same time.

1 Like

Getting this error when trying to install this driver.

unable to resolve class physicalgraph.zigbee.zcl.DataType @ line 32, column 1. unable to resolve class physicalgraph.device.HubAction @ line 217, column 28.

1 Like

That's a SmartThings device handler, not a Hubitat driver. If you have Peanut plugs whose firmware has been updated to support power reporting, then this driver works with Hubitat:

/**
 *  Peanut Plug
 *
 *  Copyright 2017 pakmanw@sbcglobal.net
 *
 *  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.
 *
 *  Peanut Plug
 *
 *  Author: pakmanw@sbcglobal.net
 *
 *  Change Log
 *  2017-09-17 - v01.01 Created
 *  2018-03-01 - v01.02 fix power accuracy issue
 *  2018-12-23 - v01.03 merging jamesham change to get the calibrated attr from peanut plug,
 *                      add support for new smartthings app
 *  2019-01-17 - v01.04 merging jamesham retain state code
 *  2019-05-24 - V02.00 Converted to run on Hubitat.  Modified to display power correctly.
 *  2019-06-14 - V02.50 Added user variables Power Change Report Value, Power Reporting Interval,
 *                      Current Change Report Value, Current Reporting Interval, Voltage Reporting Interval, and
 *                      Debug Logging?
 */

import hubitat.zigbee.zcl.DataType

metadata {
	definition (name: "Peanut Plug", namespace: "pakmanwg", author: "pakmanw@sbcglobal.net", ocfDeviceType: "oic.d.switch",
		vid: "generic-switch-power-energy") {
		capability "Energy Meter"
		capability "Actuator"
		capability "Switch"
		capability "Power Meter"
		capability "Polling"
		capability "Refresh"
		capability "Configuration"
		capability "Sensor"
		capability "Light"
		capability "Health Check"
		capability "Voltage Measurement"
        
		attribute "current","number"

		command "reset"
       
		fingerprint profileId: "0104", inClusters: "0000, 0001, 0003, 0004, 0005, 0006, 0B04, 0B05",
			outClusters: "0000, 0001, 0003, 0004, 0005, 0006, 0019, 0B04, 0B05"
	}

	// tile definitions
	tiles {
		standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
			state "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00A0DC"
			state "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
		}
		valueTile("power", "device.power") {
			state "default", label:'${currentValue} W'
		}
		valueTile("energy", "device.energy") {
			state "default", label:'${currentValue} kWh'
		}
		valueTile("voltage", "device.voltage") {
			state "default", label:'${currentValue} V'
		}
		valueTile("current", "device.current") {
			state "default", label:'${currentValue} A'
		}
		standardTile("reset", "device.energy", inactiveLabel: false, decoration: "flat") {
			state "default", label:'reset kWh', action:"reset"
		}
		standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat") {
			state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
		}

		main(["switch","power","energy","voltage","current"])
		details(["switch","power","energy","voltage","current","refresh","reset"])
	}

	preferences {
        section {
		    input (
                name: "RetainState",
                type: "bool",
                title: "Retain State?",
                description: "Retain state on power loss?", 
                required: false, 
                displayDuringSetup: false, 
                defaultValue: true
            )
i/*		    input (
                name: "PowerReportValueChange",
                type: "enum",
                title: "Power Report Value Change",
                submitOnChange: true,
                options: ["No Selection","No Report",".1 Watt",".2 Watts",".3 Watts",".4 Watts",".5 Watts",
                            "1 Watt","2 Watts","3 Watts","4 Watts","5 Watts","10 Watts","25 Watts",
                            "50 Watts","75 Watts","100 Watts","150 Watts","200 Watts","250 Watts","300 Watts","400 Watts",
                            "500 Watts","750 Watts","1000 Watts"],
                required: true,
                Multiple: false
            )
		    input (
                name: "PowerReportPercentChange",
                type: "enum",
                title: "Power Report Percentage Change",
                submitOnChange: true,
                options: ["No Selection","No Report","1%","2%","3%","4%","5%","10%","15%","20","25%","30%","40%","50%","75%","100%"],
                required: true,
                Multiple: false
            )
		    input (
                name: "PowerReportingInterval",
                type: "enum",
                title: "Power Reporting Interval",
                submitOnChange: true,
                options: ["No Selection","No Report","5 Seconds","10 Seconds","15 Seconds","30 Seconds","45 Seconds","1 Minute",
                            "2 Minutes","3 Minutes","4 Minutes","5 Minutes","10 Minutes","15 Minutes","30 Minutes","45 Minutes",
                            "1 Hour","2 Hours","3 Hours","5 Hours"],
                required: true,
                Multiple: false
            ) */
		    input (
                name: "ReportablePowerChange",
                type: "number",
                title: "Power Change Report Value",
                description: "Report Power change greater than XXX watts. (.1 - 1000)",
                submitOnChange: true,
                required: true,
                range: "0..1000",
                defaultValue: 5
            )
		    input (
                name: "MinPowerReportTime",
                type: "number",
                title: "Power Reporting Interval",
                description: "Report Power no more than every XXX seconds. (1 - 7200)",
                submitOnChange: true,
                required: true,
                range: "1..7200",
                defaultValue: 60
            )
		    input (
                name: "ReportableCurrentChange",
                type: "number",
                title: "Current Change Report Value",
                description: "Report Current change greater than XXX milliamps. (1 - 1000)",
                submitOnChange: true,
                required: true,
                range: "1..1000",
                defaultValue: 1
            )
		    input (
                name: "MinCurrentReportTime",
                type: "number",
                title: "Current Reporting Interval",
                description: "Report Current no more than every XXX seconds. (1 - 7200)",
                submitOnChange: true,
                required: true,
                range: "1..7200",
                defaultValue: 60
            )
		    input (
                name: "MinVoltageReportTime",
                type: "number",
                title: "Voltage Reporting Interval",
                description: "Report Voltage no more than every XXX seconds. (1 - 7200)",
                submitOnChange: true,
                required: true,
                range: "1..7200",
                defaultValue: 60
            )
		    input (
                name: "DebugLogging",
                type: "bool",
                title: "Debug Logging",
//                description: "Enable Debug Logging", 
                required: true, 
                displayDuringSetup: false, 
                defaultValue: true
            )
        }
	}
}

def log(msg) {
	if (DebugLogging) {
		log.debug(msg)	
	}
}

// Parse incoming device messages to generate events
def parse(String description) {

//	zigbee.ELECTRICAL_MEASUREMENT_CLUSTER is 2820
	log "description is: $description"
	def event = zigbee.getEvent(description)
	if (event) {
	    log "event name is $event.name"
		if (event.name == "power") {
			def powerValue
			powerValue = (event.value as Integer) * getPowerMultiplier()
			sendEvent(name: "power", value: powerValue)
			def time = (now() - state.time) / 3600000 / 1000
			state.time = now()
			log "powerValues is $state.powerValue"
			state.energyValue = state.energyValue + (time * state.powerValue)
			state.powerValue = powerValue
			// log "energyValue is $state.energyValue"
			sendEvent(name: "energy", value: state.energyValue)
		} else {
			sendEvent(event)
		}
	} else if (description?.startsWith("read attr -")) {
		def descMap = zigbee.parseDescriptionAsMap(description)
		log "Desc Map: $descMap"
		if (descMap.clusterInt == zigbee.ELECTRICAL_MEASUREMENT_CLUSTER) {
			def intVal = Integer.parseInt(descMap.value,16)
			if (descMap.attrInt == 0x0600) {
				log.info "ACVoltageMultiplier $intVal"
				state.voltageMultiplier = intVal
			} else if (descMap.attrInt == 0x0601) {
				log.info "ACVoltageDivisor $intVal"
				state.voltageDivisor = intVal
			} else if (descMap.attrInt == 0x0602) {
				log.info "ACCurrentMultiplier $intVal"
				state.currentMultiplier = intVal
			} else if (descMap.attrInt == 0x0603) {
				log.info "ACCurrentDivisor $intVal"
				state.currentDivisor = intVal
			} else if (descMap.attrInt == 0x0604) {
				log.info "ACPowerMultiplier $intVal"
				state.powerMultiplier = intVal
			} else if (descMap.attrInt == 0x0605) {
				log.info "ACPowerDivisor $intVal"
				state.powerDivisor = intVal
			} else if (descMap.attrInt == 0x0505) {
				def voltageValue = intVal * getVoltageMultiplier()
				log "Voltage ${voltageValue}"
				state.voltage = $voltageValue
				sendEvent(name: "voltage", value: voltageValue)
			} else if (descMap.attrInt == 0x0508) {
				def currentValue = String.format("%.4f", (intVal * getCurrentMultiplier()))
				log "Current ${currentValue}"
				state.current = $currentValue
				sendEvent(name: "current", value: currentValue)
			} else if (descMap.attrInt == 0x050B) {
				def powerValue = String.format("%.4f", (intVal * getPowerMultiplier()))
//				log "Power ${intVal}, ${getPowerMultiplier()}"
				log "Power ${powerValue}"
				state.powerValue = $powerValue
				sendEvent(name: "power", value: powerValue)
			}
		} else {
			log.warn "Not an electrical measurement"
		}
	} else {
		log.warn "DID NOT PARSE MESSAGE for description : $description"
		log zigbee.parseDescriptionAsMap(description)
	}
}

def installed() {
	reset()
	configure()
	refresh()
}

def off() {
	zigbee.off()
}

def on() {
	zigbee.on()
}

def refresh() {
	Integer reportIntervalMinutes = 5
	setRetainState() +
	zigbee.onOffRefresh() +
//	zigbee.simpleMeteringPowerRefresh() +
//	simpleMeteringPowerRefresh() +
	zigbee.electricMeasurementPowerRefresh() +
	zigbee.onOffConfig(0, reportIntervalMinutes * 60) +
//	zigbee.simpleMeteringPowerConfig() +
//	simpleMeteringPowerConfig() +
//	zigbee.electricMeasurementPowerConfig() +
	electricMeasurementPowerConfig() +
	voltageMeasurementRefresh() +
	voltageMeasurementConfig() +
	currentMeasurementRefresh() +
	currentMeasurementConfig() +
	zigbee.readAttribute(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0600) +
	zigbee.readAttribute(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0601) +
	zigbee.readAttribute(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0602) +
	zigbee.readAttribute(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0603) +
	zigbee.readAttribute(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0604) +
	zigbee.readAttribute(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0605)
}

//def electricMeasurementPowerConfig(
//                                minReportTime=10,           // in seconds
//                                maxReportTime=600,          // in seconds
//                                reportableChange=0x0005)    // in .1 Watts 
def electricMeasurementPowerConfig()    // in .1 Watts 
{
    def MinPowerValueA
    MinPowerValue = (((ReportablePowerChange as float) * 10) as Integer)
	log.info "Power Report Time: $MinPowerReportTime, Power Report Value: $MinPowerValue"
	zigbee.configureReporting(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 
                            0x050B, 
                            DataType.INT16, 
                            MinPowerReportTime as Integer,              // Min Power reporting time in seconds
                            7200,                                       // Max Power reporting time in seconds
                            MinPowerValue as Integer)                   // Min Reportable Power Change in Tenths of Watts
}

//def currentMeasurementConfig(minReportTime=60, maxReportTime=600, reportableChange=0x0005) {
//	zigbee.configureReporting(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0508, DataType.UINT16, minReportTime, maxReportTime, reportableChange)
//}

def currentMeasurementConfig() {
	log.info "Current Report Time: $MinCurrentReportTime, Current Report Value: $ReportableCurrentChange"
	zigbee.configureReporting(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 
                            0x0508, 
                            DataType.INT16, 
                            MinCurrentReportTime as Integer,    // Min Current reporting time in seconds
                            7200,                               // Max Current reporting time in seconds
                            ReportableCurrentChange as Integer) // Min Reportable Current Change in Tenths of Watts
}

def currentMeasurementRefresh() {
	zigbee.readAttribute(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0508);
}

def voltageMeasurementConfig() {
	log.info "Voltage Report Time: $MinVoltageReportTime"
	zigbee.configureReporting(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 
                            0x0505, 
                            DataType.INT16, 
                            MinVoltageReportTime as Integer,    // Min Voltage reporting time in seconds
                            7200,                               // Max Voltage reporting time in seconds
                            0x0030)                             // Min Reportable Voltage Change in Tenths of Volts
}

def voltageMeasurementRefresh() {
	zigbee.readAttribute(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0505);
}

def getCurrentMultiplier() {
	if (state.currentMultiplier && state.currentDivisor) {
		return (state.currentMultiplier / state.currentDivisor)
	} else {
		return 0.001831
	}
}

def getVoltageMultiplier() {
	if (state.voltageMultiplier && state.voltageDivisor) {
		return (state.voltageMultiplier / state.voltageDivisor)
	} else {
		return 0.0045777
	}
}

def getPowerMultiplier() {
	if (state.powerMultiplier && state.powerDivisor) {
		return (state.powerMultiplier / state.powerDivisor)
	} else {
		return 0.277
	}
}

def configure() {
	log "in configure()"
	return configureHealthCheck() + setRetainState()
}

def configureHealthCheck() {
	Integer hcIntervalMinutes = 12
	sendEvent(name: "checkInterval", value: hcIntervalMinutes * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
	return refresh()
}

def updated() {
    log.info "${device.displayName}.updated()"
	log "in updated()"
	// updated() doesn't have it's return value processed as hub commands, so we have to send them explicitly
	def cmds = configureHealthCheck() + setRetainState()
	cmds.each{ sendHubCommand(new hubitat.device.HubAction(it)) }
}

def ping() {
	return zigbee.onOffRefresh()
}

def setRetainState() {
	log "Setting Retain State: $RetainState"
	if (RetainState == null || RetainState) {
		if (RetainState == null) {
			log.warn "RetainState is null, defaulting to 'true' behavior"
		}
		return zigbee.writeAttribute(0x0003, 0x0000, DataType.UINT16, 0x0000)
	} else {
		return zigbee.writeAttribute(0x0003, 0x0000, DataType.UINT16, 0x1111)
	}
}

def reset() {
	log "Reset"
	state.energyValue = 0.0
	state.powerValue = 0.0
	state.voltage = 0.0
	state.current = 0.0
	state.time = now()
	sendEvent(name: "energy", value: state.energyValue)
}
3 Likes

I have a couple of these and I’m trying to find the current firmware version. Where can I find that? Which version is required for power reporting to work?

You need to have a securifi router to update them.

1 Like

Has to be reported somewhere (how else could an Almond router know the Peanut Plug needs upgrade) but the drivers I have seen do not list it.

Have not done it yet in the one I am working on... Got that one to be "good enough" for my own needs and have been working on my other drivers and Christmas stuff so it has been put on the back burner.

Even after updating them with the Securifi router, it enables "power" monitoring but it does not enable "Energy" monitoring......just an FYI.

1 Like

Yeah, I am not really clear on the difference between the two. I could be wrong but I think Power is "at this moment" and Energy is "over X timeframe". Not sure if the DO energy or if the Almonds calculated it from the reported Power readings.

Yes that is the difference. With the firmware update the plugs will tell you the current at the moment electricity usage (Power). They won't tell you an accumulated over time energy used (Energy) like most energy monitoring outlets do.

3 Likes

Dumb question of the day. Does a specific model need to be used, or will any "securifi router" update the firmware on the plugs?

It needs to be a model with Zigbee support. I picked up the Securifi Almond+ off ebay without a power supply for $23.60 delivered ($15 + shipping). I already had a power supply that worked.

3 Likes

Basically an Almond+, Almond 2015, Almond 3, or Almond 3S. The very original Almond did not have ZigBee support.

Still not clear how it’s possible to know which firmware version I have and which one is required for reporting power usage. For those who have it working, how is it setup? Are you using the Power Meter template on the dashboard along with the device driver included in an earlier post on this thread? When I try this combination the tile shows “Unknown”.

The device details page doesn't list the firmware version before or after updating the plugs.

If you are using the standard generic zigbee outlet driver for the plug and you are getting an "unknown" for your dashboard power meter tile. Then you have the firmware that needs updating.

I can't remember if the Securfi Almond Hub states when updating the firmware version or not, what it does say is that there is a firmware update available once you pair the plug to the Securfi Almond Hub. Takes 5-10 minutes per plug to update. Then afterwards upon repairing to Hubitat that Power Meter Tile will show at the moment power usage.

1 Like

Anyone seen an issue with the Peanut not reporting its current state? That is, on or off? That seems to be a problem sometimes since when set as part of a group, it appears only to turn it off when it says it is on, and vice versa. If it has not reported itself on, turning a group off does not turn it off. Yes, I can hit "refresh" on the generic zigbee outlet driver and it updates, but that kind of defeats the purpose of a group!

I had the same problem.
I went into configure, and “enabled Automatic power reporting” and now it reports, the hub shows the on/off state now.

P.K.

Thank you!

Is there a newer version of the Hubitat driver that incorporates the latest changes of the samrtthings version? The ST version was updated a couple times in 2020.

attached is the 3-21-20 ST version. I edited 2 lines to get rid of errors. It should work fine

Summary

/**

  • Peanut Plug
  • Copyright 2020 pakmanw@sbcglobal.net
  • 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.
  • Peanut Plug
  • Author: pakmanw@sbcglobal.net
  • Change Log
  • 2017-09-17 - v01.01 Created
  • 2018-03-01 - v01.02 fix power accuracy issue
  • 2018-12-23 - v01.03 merging jamesham change to get the calibrated attr from peanut plug,
  •                  add support for new smartthings app
    
  • 2019-01-17 - v01.04 merging jamesham retain state code
  • 2019-09-27 - v01.05 update fingerprint from transman
  • 2019-09-30 - v01.06 new interface, add active power settings
  • 2019-10-01 - v01.07 add cost per kwh
  • 2019-10-02 - v01.08 add reset time
  • 2019-10-03 - v01.09 two decimal points for values
  • 2020-02-23 - v01.10 fix energy value not update issues
  • 2020-03-31 - v01.11 fix decimal place display problem in some devices
    */

import hubitat.zigbee.zcl.DataType

metadata {
definition (name: "Peanut Plug", namespace: "pakmanwg", author: "pakmanw@sbcglobal.net", ocfDeviceType: "oic.d.switch",
vid: "generic-switch-power-energy") {
capability "Energy Meter"
capability "Actuator"
capability "Switch"
capability "Power Meter"
capability "Polling"
capability "Refresh"
capability "Configuration"
capability "Sensor"
capability "Light"
capability "Health Check"
capability "Voltage Measurement"

attribute "current", "number"
attribute "switchStatus", "string"

command "reset"

fingerprint profileId: "0104", inClusters: "0000, 0001, 0003, 0004, 0005, 0006, 0B04, 0B05",
  outClusters: "0000, 0001, 0003, 0004, 0005, 0006, 0019, 0B04, 0B05",
  manufacturer: "Securifi Ltd."

}

// tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "generic", width: 6, height: 4, canChangeIcon: true) {
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00a0dc"
attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
}
tileAttribute ("device.switchStatus", key: "SECONDARY_CONTROL") {
attributeState "switchStatus", label:'${currentValue}'
}
}
standardTile("refresh", "device.refresh", width: 2, height: 2) {
state "refresh", label:'Refresh', action: "refresh", icon:"st.secondary.refresh-icon"
}
standardTile("reset", "device.reset", width: 2, height: 2) {
state "reset", label:'Reset', action: "reset", icon:"st.secondary.refresh-icon"
}
valueTile("power", "device.power", width: 2, height: 2) {
state "default", label:'${currentValue} W', backgroundColor: "#cccccc"
}
valueTile("energy", "device.energy", width: 2, height: 2) {
state "default", label:'${currentValue} kWh', backgroundColor: "#cccccc"
}
valueTile("voltage", "device.voltage", width: 2, height: 2) {
state "default", label:'${currentValue} V', backgroundColor: "#cccccc"
}
valueTile("current", "device.current", width: 2, height: 2) {
state "default", label:'${currentValue} A', backgroundColor: "#cccccc"
}
valueTile("history", "device.history", decoration:"flat", width: 6, height: 3) {
state "history", label:'${currentValue}'
}

main(["switch"])
details(["switch", "power", "energy", "refresh", "voltage", "current", "reset"])

}

preferences {
input name: "retainState", type: "bool", title: "Retain State?", description: "Retain state on power loss?", required: false, displayDuringSetup: false,
defaultValue: true
// configParams?.each {
// getOptionsInput(it)
// }
input "energyPrice", "number", title: "$/kWh Cost:", description: "Electric Cost per Kwh in cent", range: "0..", defaultValue: 15
input "inactivePower", "decimal", title: "Reporting inactive when power is less than or equal to:", range: "0..
", defaultValue: 0
// ["Power", "Energy", "Voltage", "Current"].each {
// getBoolInput("display${it}", "Display ${it} Activity", true)
// }
}
}

private getOptionsInput(param) {
if (param.prefName) {
input "${param.prefName}", "enum",
title: "${param.name}:",
defaultValue: "${param.val}",
required: false,
displayDuringSetup: true,
options: param.options?.collect { name, val -> name }
}
}

private getBoolInput(name, title, defaultVal) {
input "${name}", "bool",
title: "${title}?",
defaultValue: defaultVal,
required: false
}

def installed() {
reset()
configure()
refresh()
}

def off() {
zigbee.off()
}

def on() {
zigbee.on()
}

def refresh() {
Integer reportIntervalMinutes = 5
setRetainState() +
zigbee.onOffRefresh() +
zigbee.simpleMeteringPowerRefresh() +
zigbee.electricMeasurementPowerRefresh() +
zigbee.onOffConfig(1, 600) +
zigbee.simpleMeteringPowerConfig(1, 600) +
zigbee.electricMeasurementPowerConfig(1, 600) +
voltageMeasurementRefresh() +
voltageMeasurementConfig(reportIntervalMinutes * 60, 600) +
currentMeasurementRefresh() +
currentMeasurementConfig(reportIntervalMinutes * 60, 600) +
zigbee.readAttribute(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0600) +
zigbee.readAttribute(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0601) +
zigbee.readAttribute(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0602) +
zigbee.readAttribute(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0603) +
zigbee.readAttribute(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0604) +
zigbee.readAttribute(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0605)
}

def currentMeasurementConfig(minReportTime=1, maxReportTime=600, reportableChange=0x0030) {
zigbee.configureReporting(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0508, DataType.UINT16, minReportTime, maxReportTime, reportableChange)
}

def currentMeasurementRefresh() {
zigbee.readAttribute(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0508);
}

def voltageMeasurementConfig(minReportTime=1, maxReportTime=600, reportableChange=0x0018) {
zigbee.configureReporting(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0505, DataType.UINT16, minReportTime, maxReportTime, reportableChange)
}

def voltageMeasurementRefresh() {
zigbee.readAttribute(zigbee.ELECTRICAL_MEASUREMENT_CLUSTER, 0x0505);
}

def getCurrentMultiplier() {
if (state.currentMultiplier && state.currentDivisor) {
return (state.currentMultiplier / state.currentDivisor)
} else {
return 0.001831
}
}

def getVoltageMultiplier() {
if (state.voltageMultiplier && state.voltageDivisor) {
return (state.voltageMultiplier / state.voltageDivisor)
} else {
return 0.0045777
}
}

def getPowerMultiplier() {
if (state.powerMultiplier && state.powerDivisor) {
return (state.powerMultiplier / state.powerDivisor)
} else {
return 0.277
}
}

def configure() {
log.debug "in configure()"
return configureHealthCheck() + setRetainState()
}

def configureHealthCheck() {
Integer hcIntervalMinutes = 12
sendEvent(name: "checkInterval", value: hcIntervalMinutes * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
return refresh()
}

def updated() {
log.debug "in updated()"
// updated() doesn't have it's return value processed as hub commands, so we have to send them explicitly
def cmds = configureHealthCheck() + setRetainState()
cmds.each{ sendHubCommand(new hubitat.device.HubAction(it)) }
}

def ping() {
return zigbee.onOffRefresh()
}

def setRetainState() {
log.debug "Setting Retain State: $retainState"
if (retainState == null || retainState) {
if (retainState == null) {
log.warn "retainState is null, defaulting to 'true' behavior"
}
return zigbee.writeAttribute(0x0003, 0x0000, DataType.UINT16, 0x0000)
} else {
return zigbee.writeAttribute(0x0003, 0x0000, DataType.UINT16, 0x1111)
}
}

def reset() {
state.energyValue = 0.0
state.powerValue = 0.0
state.voltage = 0.0
state.current = 0.0
state.costValue = 0.0
state.time = now()
sendEvent(name: "energy", value: (String.format("%.2f", state.energyValue)))
sendEvent(name: "cost", value: (String.format("%.2f", state.costValue)))
state.resetTime = new Date().format('MM/dd/yy hh:mm a', location.timeZone)
}

def refreshHistory() {
def history = ""
def items = [:]

items["energyDuration"] = "Energy - Duration"
items["energyCost"] = "Energy - Cost"
["power", "voltage", "current"].each {
items["${it}Low"] = "${it.capitalize()} - Low"
items["${it}High"] = "${it.capitalize()} - High"
}
items.each { attrName, caption ->
def attr = device.currentState("${attrName}")
def val = attr?.value ?: ""
def unit = attr?.unit ?: ""
history += "${caption}: ${val} ${unit}\n"
}
sendEvent(createEventMap("history", history, false))
}

// Configuration Parameters
private getConfigParams() {
return [
powerValueChangeParam,
powerPercentageChangeParam,
powerReportIntervalParam,
energyReportIntervalParam,
voltageReportIntervalParam,
electricityReportIntervalParam
]
}

private getPowerValueChangeParam() {
return createConfigParamMap(151, "Power Report Value Change", 2, getPowerValueOptions(), "powerValueChange")
}

private getPowerPercentageChangeParam() {
return createConfigParamMap(152, "Power Report Percentage Change", 1, getPercentageOptions(10, "No Reports"), "powerPercentageChange")
}

private getPowerReportIntervalParam() {
return createConfigParamMap(171, "Power Reporting Interval", 4, getIntervalOptions(30, "No Reports"), "powerReportingInterval")
}

private getEnergyReportIntervalParam() {
return createConfigParamMap(172, "Energy Reporting Interval", 4, getIntervalOptions(300, "No Reports"), "energyReportingInterval")
}

private getVoltageReportIntervalParam() {
return createConfigParamMap(173, "Voltage Reporting Interval", 4, getIntervalOptions(0, "No Reports"), "voltageReportingInterval")
}

private getElectricityReportIntervalParam() {
return createConfigParamMap(174, "Electrical Current Reporting Interval", 4, getIntervalOptions(0, "No Reports"), "electricityReportingInterval")
}

private createConfigParamMap(num, name, size, options, prefName, val=null) {
if (val == null) {
val = (settings?."${prefName}" ?: findDefaultOptionName(options))
}
return [
num: num,
name: name,
size: size,
options: options,
prefName: prefName,
val: val
]
}

// Settings
private getEnergyPriceSetting() {
return safeToDec(settings?.energyPrice, 0.12)
}

private getInactivePowerSetting() {
return safeToDec(settings?.inactivePower, 0)
}

private getDebugOutputSetting() {
return settings?.debugOutput != false
}

private getMinimumReportingInterval() {
def minVal = (60 * 60 * 24 * 7)
[powerReportIntervalParam, energyReportIntervalParam, voltageReportIntervalParam, electricityReportIntervalParam].each {
def val = convertOptionSettingToInt(it.options, it.val)
if (val && val < minVal) {
minVal = val
}
}
return minVal
}

private getIntervalOptions(defaultVal=null, zeroName=null) {
def options = [:]
if (zeroName) {
options["${zeroName}"] = 0
}
options << getIntervalOptionsRange("Second", 1, [5,10,15,30,45])
options << getIntervalOptionsRange("Minute", 60, [1,2,3,4,5,10,15,30,45])
options << getIntervalOptionsRange("Hour", (60 * 60), [1,2,3,6,9,12,18])
options << getIntervalOptionsRange("Day", (60 * 60 * 24), [1,3,5])
options << getIntervalOptionsRange("Week", (60 * 60 * 24 * 7), [1,2])
return setDefaultOption(options, defaultVal)
}

private getIntervalOptionsRange(name, multiplier, range) {
def options = [:]
range?.each {
options["${it} ${name}${it == 1 ? '' : 's'}"] = (it * multiplier)
}
return options
}

private getPowerValueOptions() {
def options = [:]
[0,1,2,3,4,5,10,25,50,75,100,150,200,250,300,400,500,750,1000,1250,1500,1750,2000,2500,3000,3500,4000,4500,5000,6000,7000,8000,9000,10000,12500,15000].each {
if (it == 0) {
options["No Reports"] = it
}
else {
options["${it} Watts"] = it
}
}
return setDefaultOption(options, 50)
}

private getPercentageOptions(defaultVal=null, zeroName=null) {
def options = [:]
if (zeroName) {
options["${zeroName}"] = 0
}
for (int i = 1; i <= 5; i += 1) {
options["${i}%"] = i
}
for (int i = 10; i <= 100; i += 5) {
options["${i}%"] = i
}
return setDefaultOption(options, defaultVal)
}

private convertOptionSettingToInt(options, settingVal) {
return safeToInt(options?.find { name, val -> "${settingVal}" == name }?.value, 0)
}

private setDefaultOption(options, defaultVal) {
def name = options.find { key, val -> val == defaultVal }?.key
if (name != null) {
return changeOptionName(options, defaultVal, "${name}${defaultOptionSuffix}")
}
else {
return options
}
}

private changeOptionName(options, optionVal, newName) {
def result = [:]
options?.each { name, val ->
if (val == optionVal) {
name = "${newName}"
}
result["${name}"] = val
}
return result
}

private findDefaultOptionName(options) {
def option = options?.find { name, val ->
name?.contains("${defaultOptionSuffix}")
}
return option?.key ?: ""
}

private getDefaultOptionSuffix() {
return " (Default)"
}

private createEventMap(name, value, displayed=null, desc=null, unit=null) {
desc = desc ?: "${name} is ${value}"

def eventMap = [
name: name,
value: value,
displayed: (displayed == null ? ("${getAttrVal(name)}" != "${value}") : displayed)
]

if (unit) {
eventMap.unit = unit
desc = "${desc} ${unit}"
}

if (desc && eventMap.displayed) {
logDebug desc
eventMap.descriptionText = "${device.displayName} - ${desc}"
}
else {
logTrace "Creating Event: ${eventMap}"
}
return eventMap
}

private getAttrVal(attrName) {
try {
return device?.currentValue("${attrName}")
}
catch (ex) {
logTrace "$ex"
return null
}
}

private safeToInt(val, defaultVal=0) {
return "${val}"?.isInteger() ? "${val}".toInteger() : defaultVal
}

private safeToDec(val, defaultVal=0) {
return "${val}"?.isBigDecimal() ? "${val}".toBigDecimal() : defaultVal
}

private roundTwoPlaces(val) {
return Math.round(safeToDec(val) * 100) / 100
}

private convertToLocalTimeString(dt) {
def timeZoneId = location?.timeZone?.ID
if (timeZoneId) {
return dt.format("MM/dd/yyyy hh:mm:ss a", TimeZone.getTimeZone(timeZoneId))
}
else {
return "$dt"
}
}

private isDuplicateCommand(lastExecuted, allowedMil) {
!lastExecuted ? false : (lastExecuted + allowedMil > new Date().time)
}

def parse(String description) {

// log.debug "description is $description"
def event = zigbee.getEvent(description)
if (event) {
if (event.name == "power") {
def powerValue = (event.value as Integer) * getPowerMultiplier()
sendEvent(name: "power", value: (String.format("%.2f", powerValue)))
def time = (now() - state.time) / 3600000 / 1000
state.time = now()
// log.debug "powerValues is $state.powerValue"
state.energyValue = state.energyValue + (time * powerValue)
state.powerValue = powerValue
// log.debug "energyValue is $state.energyValue"
def localCostPerKwh = 15
if (energyPrice) {
localCostPerKwh = energyPrice as Integer
}
sendEvent(name: "energy", value: (String.format("%.2f", state.energyValue)))
state.costValue = roundTwoPlaces(state.energyValue * localCostPerKwh / 100)
sendEvent(name: "cost", value: (String.format("%.2f", state.costValue)))
if (inactivePowerSetting == null) {
inactivePowerSetting = 0
}
if (state.resetTime == null) {
state.resetTime = new Date().format('MM/dd/yy hh:mm a', location.timeZone)
}
def costStr = (String.format("%.2f", state.costValue))
def switchStatusS = (powerValue > inactivePowerSetting) ? "Active | Cost $$state.costValue since $state.resetTime" :
"Inactive | Cost $$costStr since $state.resetTime"
// log.debug "$switchStatusS"
sendEvent(name: "switchStatus", value: switchStatusS, displayed: false)
// refreshHistory
} else {
sendEvent(event)
}
} else if (description?.startsWith("read attr -")) {
def descMap = zigbee.parseDescriptionAsMap(description)
// log.debug "Desc Map: $descMap"
if (descMap.clusterInt == zigbee.ELECTRICAL_MEASUREMENT_CLUSTER) {
def intVal = Integer.parseInt(descMap.value,16)
if (descMap.attrInt == 0x0600) {
// log.debug "ACVoltageMultiplier $intVal"
state.voltageMultiplier = intVal
} else if (descMap.attrInt == 0x0601) {
// log.debug "ACVoltageDivisor $intVal"
state.voltageDivisor = intVal
} else if (descMap.attrInt == 0x0602) {
// log.debug "ACCurrentMultiplier $intVal"
state.currentMultiplier = intVal
} else if (descMap.attrInt == 0x0603) {
// log.debug "ACCurrentDivisor $intVal"
state.currentDivisor = intVal
} else if (descMap.attrInt == 0x0604) {
// log.debug "ACPowerMultiplier $intVal"
state.powerMultiplier = intVal
} else if (descMap.attrInt == 0x0605) {
// log.debug "ACPowerDivisor $intVal"
state.powerDivisor = intVal
} else if (descMap.attrInt == 0x0505) {
def voltageValue = roundTwoPlaces(intVal * getVoltageMultiplier())
// log.debug "Voltage ${voltageValue}"
state.voltage = $voltageValue
sendEvent(name: "voltage", value: (String.format("%.2f", voltageValue)))
} else if (descMap.attrInt == 0x0508) {
def currentValue = roundTwoPlaces(intVal * getCurrentMultiplier())
// log.debug "Current ${currentValue}"
state.current = $currentValue
sendEvent(name: "current", value: (String.format("%.2f", currentValue)))
}
} else {
log.warn "Not an electrical measurement"
}
} else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description)
}
}

Thanks for the updated code, I am getting this error when I try to save it.