I want to monitor an irrigation pump. I would like to know when it turns on and off and how long it runs. It’s a 240 volt motor that is out in the weather. The breaker box is located indoors so I would think that would be the best place to monitor. I would like to stay with zigbee. I’ve seen a couple of devices that have an amp meter type of clamp that you place around one or even both of the hot wires feeding the motor but I’m not sure what will work with Hubitat or if there is something else that might work better? Any ideas on what might work?
I use a Zigbee vibration sensor attached to the outside of a hardwired attic fan to monitor ON, OFF, and duration. Cheap. Even if you do a more robust indoor solution, if the pump is critical, you might want to try a local sensor as well for redundancy.
I would use a current transducer - there are zwave ones from Aeotec that work with Hubitat.
I’ve seen equivalent zigbee units, but you’d have to create a driver because I don’t know if one is already available. Recently, a number of community members have used AI engines to create drivers for a plethora of devices.
It would be around only one wire, and it can be in the building that powers the pump. In addition you will be able to tell by the current if the pump is primed or running dry.
and I had been using this driver, but it only worked for one clamp channel:
To monitor my Bedroom AC Split. There is a clamp on one leg of the 240, to know when it is off, cooling, or fan.
I've been meaning to have AI write a driver to get the other channel working, so now was a good time.
Gemini is just getting too good at writing these drivers. First iterations worked, and I did two more "tweak" iterations to use a custom child driver, and to add a poll refresh.
I now have both channels working! That was easy.
/**
* Tuya Zigbee Bidirectional Dual-Clamp Energy Monitor (PJ-1203A) - Parent Driver
* Features active Tuya reporting query frames to force live physical updates.
*/
metadata {
definition (name: "Tuya PJ-1203A Dual Clamp Monitor", namespace: "custom", author: "AI Collaborator") {
capability "EnergyMeter"
capability "PowerMeter"
capability "VoltageMeasurement"
capability "Refresh"
capability "Initialize"
attribute "frequency", "number"
fingerprint profileId: "0104", endpointId: "01", inClusters: "0000,0004,0005,EF00,ED00", outClusters: "0019,000A", model: "TS0601", manufacturer: "_TZE284_81yrt3lo"
}
preferences {
input name: "logEnable", type: "boolean", title: "Enable debug logging", defaultValue: true
input name: "txtEnable", type: "boolean", title: "Enable description text logging", defaultValue: true
}
}
private getDP_POWER_A() { 101 }
private getDP_DIRECTION_A() { 102 }
private getDP_POWER_B() { 105 }
private getDP_DIRECTION_B() { 104 }
private getDP_ENERGY_A() { 106 }
private getDP_ENERGY_B() { 108 }
private getDP_VOLTAGE() { 112 }
private getDP_CURRENT_A() { 113 }
private getDP_CURRENT_B() { 114 }
private getDP_FREQUENCY() { 111 }
private getDP_TOTAL_POWER() { 115 }
void parse(String description) {
if (logEnable) log.debug "Raw Description: ${description}"
Map descMap = zigbee.parseDescriptionAsMap(description)
if (descMap.clusterId == "EF00" && descMap.data) {
List<String> data = descMap.data
// Target index 2 for Tuya DP identification
int dp = Integer.parseInt(data[2], 16)
// Find exact segment lengths and loop bytes into continuous value integer
int dataLen = Integer.parseInt(data[4] + data[5], 16)
long rawValue = 0
for (int i = 0; i < dataLen; i++) {
rawValue = (rawValue << 8) + Integer.parseInt(data[6 + i], 16)
}
if (logEnable) log.debug "Tuya MCU Reported -> DP: ${dp}, Raw Value: ${rawValue}"
processClampData(dp, rawValue)
}
}
private void processClampData(int dp, long rawValue) {
def childA = fetchChildDevice("A")
def childB = fetchChildDevice("B")
switch (dp) {
case getDP_POWER_A():
float pA = rawValue / 10.0
if (childA) childA.sendEvent(name: "power", value: pA, unit: "W")
if (txtEnable) log.info "${device.displayName} (Clamp A): Power is ${pA} W"
break
case getDP_POWER_B():
float pB = rawValue / 10.0
if (childB) childB.sendEvent(name: "power", value: pB, unit: "W")
if (txtEnable) log.info "${device.displayName} (Clamp B): Power is ${pB} W"
break
case getDP_TOTAL_POWER():
float totalP = rawValue / 10.0
sendEvent(name: "power", value: totalP, unit: "W")
break
case getDP_ENERGY_A():
float eA = rawValue / 100.0
if (childA) childA.sendEvent(name: "energy", value: eA, unit: "kWh")
updateTotalEnergy()
break
case getDP_ENERGY_B():
float eB = rawValue / 100.0
if (childB) childB.sendEvent(name: "energy", value: eB, unit: "kWh")
updateTotalEnergy()
break
case getDP_CURRENT_A():
float cA = rawValue / 1000.0
if (childA) childA.sendEvent(name: "amperage", value: cA, unit: "A")
break
case getDP_CURRENT_B():
float cB = rawValue / 1000.0
if (childB) childB.sendEvent(name: "amperage", value: cB, unit: "A")
break
case getDP_VOLTAGE():
float volt = rawValue / 10.0
sendEvent(name: "voltage", value: volt, unit: "V")
if (childA) childA.sendEvent(name: "voltage", value: volt, unit: "V")
if (childB) childB.sendEvent(name: "voltage", value: volt, unit: "V")
break
case getDP_FREQUENCY():
sendEvent(name: "frequency", value: rawValue, unit: "Hz")
break
case getDP_DIRECTION_A():
String dirA = (rawValue == 1) ? "producing" : "consuming"
if (childA) childA.sendEvent(name: "powerDirection", value: dirA)
break
case getDP_DIRECTION_B():
String dirB = (rawValue == 1) ? "producing" : "consuming"
if (childB) childB.sendEvent(name: "powerDirection", value: dirB)
break
}
}
private void updateTotalEnergy() {
def childA = fetchChildDevice("A")
def childB = fetchChildDevice("B")
float eA = childA ? (childA.currentValue("energy") ?: 0.0) : 0.0
float eB = childB ? (childB.currentValue("energy") ?: 0.0) : 0.0
float combined = (eA + eB).round(2)
sendEvent(name: "energy", value: combined, unit: "kWh")
}
private fetchChildDevice(String clampIdentifier) {
String childNetworkId = "${device.deviceNetworkId}-clamp${clampIdentifier}"
def child = childDevices.find { it.deviceNetworkId == childNetworkId }
if (!child) {
try {
child = addChildDevice(
"custom",
"Tuya PJ-1203A Child Clamp",
childNetworkId,
[name: "${device.displayName} (Clamp ${clampIdentifier})", isComponent: false]
)
} catch (Exception e) {
log.error "Failed to deploy child component structure: ${e}"
}
}
return child
}
void initialize() {
childDevices.each { deleteChildDevice(it.deviceNetworkId) }
def childA = fetchChildDevice("A")
def childB = fetchChildDevice("B")
if (childA) childA.setInitialPlaceholderStates()
if (childB) childB.setInitialPlaceholderStates()
refresh()
}
void refresh() {
if (logEnable) log.debug "Sending explicit Tuya reporting query request (0x02)..."
// Command 0x02 requests a full physical status dump from Tuya MCU hardware devices
List<String> cmds = zigbee.command(0xEF00, 0x02)
sendHubCommand(new hubitat.device.HubMultiAction(cmds, hubitat.device.Protocol.ZIGBEE))
}
If you haven’t already thought about it, consider adding an automation that warns when the current is some percentage above typical running current for more than a few minutes to help identify problems.