Power Circuit Monitoring for Power Loss


In a few weeks I am having a sump pump installed in my crawl space because of on going water issues. I have a receptacle in the area of the pump that is tied into a GFIC outlet. For obvious reasons I want to be notified if this outlet ever loses power.

I saw @Cobra and @Navat604 old posts on the ST community

Curious if these solutions are still working or if there are better ideas.

@Navat604 what relay did you use in your contact sensor? It is very small and appears like you have it installed within the sensor under the cover. I have also seen where some aren’t using a relay at all.

Some Iris Contact Sensors not working
Hacking into older wired Intercom system possible?
Hue Bulbs direct to Hubitat. Power on options?

I am doing this with an old 12v wall-wart (power supply) and a simple 12v Automotive relay from Pep-boys. I then read that with a simple contact sensor with external connections. I would be worried about using this setup as the sensor will lose power once the outlet does. How will it broadcast that it is disconnected? Isolating the sensors power supply from the measured supply makes sure that you will still have a signal once the power fails. Also, you have to make sure that you have your hub, router and internet modem on a UPS. If those lose power, you won't be getting any notifications. So, the sensor won't do you much good. :slight_smile:


Thanks. All my networking closet gear, includes HE, is on a UPS. I get notified when my alarm panel is on batttery power so I am covered for a total power loss. I just need to figure out how to monitor this one circuit.


I used a Monoprice Contact Sensor KFR-ZD2105US-5, 2N2222 transistor, 220 ohm resistor and a old USB cord. It uses less current than a relay.


I've been using @Navat604 solution (without a relay) for a few months now. I didn't use it for anything critcial... really just a way to reset my Hue bulbs to my custom temps and then turn them off (if they were off before the power outage). Fortunately, we haven't had as much flickers and outages as of late....but the ones that happened while I was home always reported the power outage correctly. There was only one instance where it did not detect that power returned and I had to unplug and replug the sensor. It always detected the outage.

Btw, with this solution, the sensor is battery powered so it is still able to speak to the hub and uses the reed switch to determine whether there is power or not.

If you are able to use the relay, it's probably the better option. Without the relay it can take a while for the power to the USB adapter to drain and delay the outage report. I plugged mine into a cheap USB hub and attached another USB device (old unused MagicJack) to help drain the power quickly. It reports the outage in under 3 seconds me.

EDIT: The pic above was actually the first way I set this up. Just remebered that I actuall switched to using the external contacts instead. Everything else was accurate.

Hue Power Loss Memory - Finally

I should add, the reason I am using a relay is that my "contact sensor" is actually a Raspberry Pi that is also running my email+text notification device as well. So, its on UPS but the input pins are more susceptible to voltage spikes than a simple contact sensor.


Still working perfectly.
Just now on HE instead of ST
So I know if there is a power loss it will not fail to notify because of the ST cloud going down.



I don't remember the relay part # but it's a small 5VDC relay I got at a local electronics shop.
You can use the relay or no relay depending on what you are monitoring. I use the relay for instant notification when power is post. Without the relay, depending on the USB adapter. It could take up to a minute for discharge of the USB for notification.


This was the reason I ended up using a USB hub and magicJack to quickly draw away any stored power from the usb adapter.


Thank you everyone for the replies!

@JBrown thank you so much for the pictures and part numbers. I went through a few Arduino kits I had bought for the kids and found the same. I had an unused Ecolink sensor that is now in use for this project. Working great and reports immediately with the transistor and resistor. I ended up using the screw terminals versus soldering to the circuit board.

Thanks again!


Hmmm. I have those parts in my workbench and I would love to get rid of my USB hub hack. New mini-project for this weekend...thanks for sharing your success.


I couldn't wait till this weekend to test it :smiley:

It worked perfectly with virtually no delay....best of all, NO usb hub and NO MagicJack required. Solder and shrink wrap will have to wait for this weekend though. Thanks again!



@stephack I was putting mine in its final location and noticed that it only reports open/closed events IF I pull the USB cable from the brick. The brick doesn't even have to be energized: when disconnected its open and when plugged in the brick its closed. In other words if I leave the USB in the brick and plug it into the wall or pull it out there is no difference. Are you experiencing the same? Maybe these screw terminals won't work and I need to solder it to the board.

@JBrown any thoughts on this issue?


Nope. It works connected to the brick for me. When you unplug the brick, wait at least 20 seconds to see if the status changes. The brick can hold a residual charge that takes a while to dissipate. This was the reason I used the USB hub and magicjack. With @JBrown's method, it works without the hub for me, but I'm still wondering if that's your issue. I'm also assuming your final location isn't next to the sensor's magnetic counterpart.


I also just remebered... I had something similar when I tried this the first time and it turned out that the USB cable I used had too thin a gauge (a really old iPhone cable). @Navat604 recommended trying a different cable and then it worked fine.


Hum that could be it because I used a really old UPS cable and the gauge was around 28 or so. Back to the drawing board :frowning: Hopefully I won't burn my finger again with the soldering iron. I will test with a bread board like you did first.


Stephan, is that a monoprice contact? I never got it working with my Ecolink and just received a few monoprice sensors. The external contact doesn’t appear to work. Did you have to do anything to enable it? I tried Zwave tweaked and maybe I am doing something wrong. :slightly_frowning_face:


I believe it goes under different brands. It's a GoControl but I believe it also goes under a Linear and Ecolink brands. I know there was an updated version from Ecolink (zwave plus I think) that had the external contacts in a different place. My contacts are near the top. Your pic looked different.


And I'm also using a modified version of the contact sensor driver. I took @Cobra's Contact/Switch driver and modified it to serve this purpose. I dont even remember what I changed at this point, but I can share once I get a chance to sit at my pc.


This is the driver I'm using. I think all I added was the on() and off() commands so I can manually change the settings for testing purposes....maybe a couple of other small modifications. Otherwise, it's @Cobra driver that can receive updates from the external contacts and also acts as an on/off switch. For my scenario I have the Reverse Switch Mode preference enabled so the following applies.

No power = contact open = switch off
Power = contact closed = switch on

This way it shows my device "Power Detector" as "On" when I have power.

 *  "Generic Z-Wave Contact/Switch Driver"
 *	Although originally I ported this from ST it now has the 'switch' capability added
 *  I've also added the ability to switch on/off when open or closed - You choose
 *  Copyright 2018 Cobra
 *  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.
 *  Z-Wave Door/Window Sensor
 *  Updated 10/08/2018
 *  V1.0.0 POC

metadata {
    definition(name: "Generic Z-Wave Contact/Switch Driver", namespace: "Cobra", author: "Cobra") { 
		capability "Contact Sensor"
		capability "Sensor"
		capability "Battery"
        	capability "Switch"
		attribute "DriverAuthor", "string"
        	attribute "DriverVersion", "string"
        	attribute "DriverStatus", "string"
		attribute "DriverUpdate", "string"

//		fingerprint deviceId: " inClusters:0x30,0x80,0x84,0x71,0x70,0x85,0x86,0x72
		fingerprint deviceId: "0x2001", inClusters: "0x30,0x80,0x84,0x85,0x86,0x72"
		fingerprint deviceId: "0x07", inClusters: "0x30"
		fingerprint deviceId: "0x0701", inClusters: "0x5E,0x98"
		fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x98", outClusters: "0x5A,0x82"
		fingerprint deviceId: "0x0701", inClusters: "0x5E,0x80,0x71,0x85,0x70,0x72,0x86,0x30,0x31,0x84,0x59,0x73,0x5A,0x8F,0x98,0x7A", outClusters: "0x20"
		fingerprint mfr: "0086", prod: "0002", model: "001D", deviceJoinName: "Aeotec Door/Window Sensor (Gen 5)"
		fingerprint mfr: "0086", prod: "0102", model: "0070", deviceJoinName: "Aeotec Door/Window Sensor 6"
		fingerprint mfr: "0086", prod: "0102", model: "0059", deviceJoinName: "Aeotec Recessed Door Sensor"
		fingerprint mfr: "014A", prod: "0001", model: "0002", deviceJoinName: "Ecolink Door/Window Sensor"
		fingerprint mfr: "014A", prod: "0001", model: "0003", deviceJoinName: "Ecolink Tilt Sensor"
		fingerprint mfr: "011A", prod: "0601", model: "0903", deviceJoinName: "Enerwave Magnetic Door/Window Sensor"
		fingerprint mfr: "014F", prod: "2001", model: "0102", deviceJoinName: "Nortek GoControl Door/Window Sensor"
		fingerprint mfr: "0063", prod: "4953", model: "3031", deviceJoinName: "Jasco Hinge Pin Door Sensor"
		fingerprint mfr: "019A", prod: "0003", model: "0003", deviceJoinName: "Sensative Strips"
		fingerprint mfr: "0258", prod: "0003", model: "0082", deviceJoinName: "NEO Coolcam Door/Window Sensor"
		fingerprint mfr: "021F", prod: "0003", model: "0101", deviceJoinName: "Dome Door/Window Sensor"
            section("Switch Mode"){

                input "mode", "bool", title: ("Reverse Switch Mode")    

def on(){
sendEvent(name: "switch", value: "on")    

def off(){
 sendEvent(name: "switch", value: "off")   

private getCommandClassVersions() {
	[0x20: 1, 0x25: 1, 0x30: 1, 0x31: 5, 0x80: 1, 0x84: 1, 0x71: 3, 0x9C: 1]

def parse(String description) {
	def result = null
	if (description.startsWith("Err 106")) {
		if ((zwaveInfo.zw == null && state.sec != 0) || zwaveInfo?.zw?.endsWith("s")) {
			log.debug description
		} else {
			result = createEvent(
				descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.",
				eventType: "ALERT",
				name: "secureInclusion",
				value: "failed",
				isStateChange: true,
	} else if (description != "updated") {
		def cmd = zwave.parse(description, commandClassVersions)
		if (cmd) {
			result = zwaveEvent(cmd)
//	log.debug "parsed '$description' to $result"
	return result

def installed() {
	sendEvent(name: "checkInterval", value: 2 * 4 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
	sendEvent(name: "battery", unit: "%", value: 100)

def updated() {
	sendEvent(name: "checkInterval", value: 2 * 4 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])

def sensorValueEvent(value) {
	if (value) {
		createEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open")
        if(mode == false){
                sendEvent(name: "switch", value: "on")}
        	sendEvent(name: "contact", value: "open")
        if(mode == true){
                sendEvent(name: "switch", value: "off")}
	} else {
		createEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed")
         if(mode == false){
             	sendEvent(name: "switch", value: "off")}
        	sendEvent(name: "contact", value: "closed")
        if(mode == true){
                sendEvent(name: "switch", value: "on")}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd) {

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicSet cmd) {

def zwaveEvent(hubitat.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {

def zwaveEvent(hubitat.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) {

def zwaveEvent(hubitat.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) {

def zwaveEvent(hubitat.zwave.commands.notificationv3.NotificationReport cmd) {
	def result = []
	if (cmd.notificationType == 0x06 && cmd.event == 0x16) {
		result << sensorValueEvent(1)
	} else if (cmd.notificationType == 0x06 && cmd.event == 0x17) {
		result << sensorValueEvent(0)
	} else if (cmd.notificationType == 0x07) {
		if (cmd.v1AlarmType == 0x07) {  // special case for nonstandard messages from Monoprice door/window sensors
			result << sensorValueEvent(cmd.v1AlarmLevel)
		} else if (cmd.event == 0x01 || cmd.event == 0x02) {
			result << sensorValueEvent(1)
		} else if (cmd.event == 0x03) {
			result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
			if (!state.MSR) result << response(command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
		} else if (cmd.event == 0x05 || cmd.event == 0x06) {
			result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
		} else if (cmd.event == 0x07) {
			if (!state.MSR) result << response(command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
			result << createEvent(name: "motion", value: "active", descriptionText: "$device.displayName detected motion")
	} else if (cmd.notificationType) {
		def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}"
		result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, displayed: false)
	} else {
		def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
		result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, displayed: false)

def zwaveEvent(hubitat.zwave.commands.wakeupv1.WakeUpNotification cmd) {
	def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)
	def cmds = []
	if (!state.MSR) {
		cmds << zwave.manufacturerSpecificV2.manufacturerSpecificGet()

	if (device.currentValue("contact") == null) {
		// In case our initial request didn't make it, initial state check no. 3
		cmds << zwave.sensorBinaryV2.sensorBinaryGet(sensorType: zwave.sensorBinaryV2.SENSOR_TYPE_DOOR_WINDOW)

	if (!state.lastbat || now() - state.lastbat > 53 * 60 * 60 * 1000) {
		cmds << zwave.batteryV1.batteryGet()

	def request = []
	if (cmds.size() > 0) {
		request = commands(cmds, 1000)
		request << "delay 20000"
	request << zwave.wakeUpV1.wakeUpNoMoreInformation().format()

	[event, response(request)]

def zwaveEvent(hubitat.zwave.commands.batteryv1.BatteryReport cmd) {
	def map = [name: "battery", unit: "%"]
	if (cmd.batteryLevel == 0xFF) {
		map.value = 1
		map.descriptionText = "${device.displayName} has a low battery"
		map.isStateChange = true
	} else {
		map.value = cmd.batteryLevel
	state.lastbat = now()

def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
	def result = []

	def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
	log.debug "msr: $msr"
	updateDataValue("MSR", msr)

	result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false)

	// change Driver if required based on MSR
	if (!retypeBasedOnMSR()) {
		if (msr == "011A-0601-0901") {
			// Enerwave motion doesn't always get the associationSet that the hub sends on join
			result << response(zwave.associationV1.associationSet(groupingIdentifier: 1, nodeId: zwaveHubNodeId))
	} else {
		// if this is door/window sensor check initial contact state no.2
		if (!device.currentState("contact")) {
			result << response(command(zwave.sensorBinaryV2.sensorBinaryGet(sensorType: zwave.sensorBinaryV2.SENSOR_TYPE_DOOR_WINDOW)))

	// every battery device can miss initial battery check. check initial battery state no.2
	if (!device.currentState("battery")) {
		result << response(command(zwave.batteryV1.batteryGet()))


def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
	def encapsulatedCommand = cmd.encapsulatedCommand(commandClassVersions)
	if (encapsulatedCommand) {

def zwaveEvent(hubitat.zwave.commands.crc16encapv1.Crc16Encap cmd) {
	def version = commandClassVersions[cmd.commandClass as Integer]
	def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
	def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
	if (encapsulatedCommand) {
		return zwaveEvent(encapsulatedCommand)

def zwaveEvent(hubitat.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
	def result = null
	def encapsulatedCommand = cmd.encapsulatedCommand(commandClassVersions)
	log.debug "Command from endpoint ${cmd.sourceEndPoint}: ${encapsulatedCommand}"
	if (encapsulatedCommand) {
		result = zwaveEvent(encapsulatedCommand)

def zwaveEvent(hubitat.zwave.commands.multicmdv1.MultiCmdEncap cmd) {
	log.debug "MultiCmd with $numberOfCommands inner commands"
	cmd.encapsulatedCommands(commandClassVersions).collect { encapsulatedCommand ->

def zwaveEvent(hubitat.zwave.Command cmd) {
	createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)

def initialPoll() {
	def request = []
	if (isEnerwave()) { // Enerwave motion doesn't always get the associationSet that the hub sends on join
		request << zwave.associationV1.associationSet(groupingIdentifier: 1, nodeId: zwaveHubNodeId)

	// check initial battery and contact state no.1
	request << zwave.batteryV1.batteryGet()
	request << zwave.sensorBinaryV2.sensorBinaryGet(sensorType: zwave.sensorBinaryV2.SENSOR_TYPE_DOOR_WINDOW)
	request << zwave.manufacturerSpecificV2.manufacturerSpecificGet()
	commands(request, 500) + ["delay 6000", command(zwave.wakeUpV1.wakeUpNoMoreInformation())]

private command(hubitat.zwave.Command cmd) {
//	if ((zwaveInfo.zw == null && state.sec != 0) || zwaveInfo?.zw?.endsWith("s")) {
//		zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
//	} else {
//	}

private commands(commands, delay = 200) {
	delayBetween(commands.collect { command(it) }, delay)

def retypeBasedOnMSR() {
	def dthChanged = true
	switch (state.MSR) {
		case "0086-0002-002D":
			log.debug "Changing device type to Z-Wave Water Sensor"
			setDeviceType("Z-Wave Water Sensor")
		case "011F-0001-0001":  // Schlage motion
		case "014A-0001-0001":  // Ecolink motion
		case "014A-0004-0001":  // Ecolink motion +
		case "0060-0001-0002":  // Everspring SP814
		case "0060-0001-0003":  // Everspring HSP02
		case "011A-0601-0901":  // Enerwave ZWN-BPC
			log.debug "Changing device type to Z-Wave Motion Sensor"
			setDeviceType("Z-Wave Motion Sensor")
		case "013C-0002-000D":  // Philio multi +
			log.debug "Changing device type to 3-in-1 Multisensor Plus (SG)"
			setDeviceType("3-in-1 Multisensor Plus (SG)")
		case "0109-2001-0106":  // Vision door/window
			log.debug "Changing device type to Z-Wave Plus Door/Window Sensor"
			setDeviceType("Z-Wave Plus Door/Window Sensor")
		case "0109-2002-0205": // Vision Motion
			log.debug "Changing device type to Z-Wave Plus Motion/Temp Sensor"
			setDeviceType("Z-Wave Plus Motion/Temp Sensor")
			dthChanged = false

private isEnerwave() {
	zwaveInfo?.mfr?.equals("011A") && zwaveInfo?.prod?.equals("0601") && zwaveInfo?.model?.equals("0901")

def version(){
    schedule("0 0 8 ? * FRI *", updateCheck)  

def updateCheck(){
	def paramsUD = [uri: "http://update.hubitat.uk/versions.json"]
       	try {
        httpGet(paramsUD) { respUD ->
 //  log.warn " Version Checking - Response Data: ${respUD.data}"   // Troubleshooting Debug Code 
       		def copyrightRead = (respUD.data.copyright)
       		state.Copyright = copyrightRead
            def newVerRaw = (respUD.data.versions.Driver.(state.InternalName))
            def newVer = (respUD.data.versions.Driver.(state.InternalName).replace(".", ""))
       		def currentVer = state.Version.replace(".", "")
      		state.UpdateInfo = (respUD.data.versions.UpdateInfo.Driver.(state.InternalName))
                state.author = (respUD.data.author)
		if(newVer == "NLS"){
            state.status = "<b>** This driver is no longer supported by $state.author  **</b>"       
            log.warn "** This driver is no longer supported by $state.author **"      
		else if(currentVer < newVer){
        	state.status = "<b>New Version Available (Version: $newVerRaw)</b>"
        	log.warn "** There is a newer version of this driver available  (Version: $newVerRaw) **"
        	log.warn "** $state.UpdateInfo **"
      		state.status = "Current"
      		log.info "You are using the current version of this driver"
        catch (e) {
        	log.error "Something went wrong: CHECK THE JSON FILE AND IT'S URI -  $e"
   		if(state.status == "Current"){
			state.UpdateInfo = "N/A"
		    sendEvent(name: "DriverUpdate", value: state.UpdateInfo, isStateChange: true)
	 	    sendEvent(name: "DriverStatus", value: state.Status, isStateChange: true)
	    	sendEvent(name: "DriverUpdate", value: state.UpdateInfo, isStateChange: true)
	     	sendEvent(name: "DriverStatus", value: state.Status, isStateChange: true)
 			sendEvent(name: "DriverAuthor", value: state.author, isStateChange: true)
    		sendEvent(name: "DriverVersion", value: state.Version, isStateChange: true)

def setVersion(){
		state.Version = "1.0.0"	 
		state.InternalName = "ContactSwitch"