[RELEASE] You Got Mail

Hello all,

You Got Mail is a notification application that provides notifications when a contact sensor is open on a mailbox. This application supports Pushover, Music Speakers, Speech Speakers, Echo Speaks devices and Google devices for notifications. Application allows for custom notifications, random messages and mail delivery time window. Notification options if mailbox was left open.

Donations are always appreciated:

You Got Mail Changes:

  • 1.0.6 - enforced inputs required for the app to work, added ability to rename app for multiple instances
  • 1.0.5 - added additional AppWatchDogv2 support
  • 1.0.4 - added AppWatchDogv2 support
  • 1.0.3 - fixed logic for mailbox left open notification
  • 1.0.2 - Added notification option if mailbox was left open
  • 1.0.1 - Added a notification governor which is user defined (thank you Cobra for coding guidance!)
  • 1.0.0 Initial concept

Version 1.0.5 and above:
AppWatchDog2 Supported

Version 1.0.4 and below:
App Watchdog JSON file: https://raw.githubusercontent.com/PrayerfulDrop/Hubitat/master/AaronWardAppUpdate.json

4 Likes

1.0.1 - Added a notification governor which is user defined (thank you Cobra for coding guidance!)

1 Like

I plan to set up a contact sensor on my mailbox as soon as I get the time to replace my existing one. What differentiates this app from setting up notifications in RM? Genuinely curious.

Edit: just saw this....dont remember it being there yesterday when I read the OP.

1 Like

Yeah I updated this morning. I attempted to do this in RM at first. It soon became complicated and didn’t give me the results I wanted without maintaining multiple rules together. I wanted something easier with flexibility for my announcement needs. Great example is I want TTS notifications when mail is delivered in a time slot and only PushOver notifications with a different message outside of those hours. Also will be adding a mailbox left open notification option in next update.

1 Like

Nice work. I will add this app to my "to install" list once I get my mailbox situated. Great to see you diving in head first with Groovy.

1 Like

1.0.2 - Added notification option if mailbox was left open

1.0.3 - fixed logic for mailbox left open notification

1.0.4 - added AppWatchDogv2 support

@aaron Nice app. I wanted to know if this app would still work if the mailbox door sensor reads the opposite of the actual door status. For some reason the d/w on the mailbox sends the opposite of what the door actually is, don't know why. It is an ecolink d/w sensor.

How does your app detect the sensor? A change in status, open to close, close to open or does it have to show it is closed first and then open to work?

Also, at the bottom of the setup page where it shows the version, it says vnull.

Thanks for the notice on the null statement. :slight_smile:

Typically I am looking at for a closed sensor to become open and then closed again. Do you have a sensor that stays "open" and then is closed?

@aaron When the door is closed on the mailbox it shows Open on the device page. For some reason the connector block inside the d/w sensor reports the opposite when connected to a magnet switch. So it would report Open (door actually closed), Closed (door opened), Open (door back closed).

So I am looking at the Ecolink d/w sensor. Are you using a magnet or did you extend using the connector block?

I have an Ecolink that I use in conjunction with my wired alarm system. It shows closed when the magnet is close but if I use the relay block internally and place a different end-point (Kiddie relay as an example) it shows open even though the system is "closed." Are you doing the same? If so could you switch back to a magnet?

I am using an external magnet/reed switch on the mailbox door. I think it is a normally open. Wires run to external box that contains the ecolink and connects into internal green connector.

@razorwing

Try this custom device driver for your ECOLink. I swapped the open/closed.

/**
 *  Ecolink Contact Sensor Custom (modified from original Z-Wave Door/Window Sensor)
 * 
 *  importUrl: "https://raw.githubusercontent.com/PrayerfulDrop/Hubitat/master/drivers/ecolink-custom.groovy"
 *
 *  Copyright 2019 Aaron Ward
 *
 *  Original code ported and modified for hubitat from: https://github.com/luder888/SmartThingsPublic/blob/master/devicetypes/luder888/zwave-door-window-sensor-as-smoke-detector.src/zwave-door-window-sensor-as-smoke-detector.groovy
 *
 *  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.
 *
 * ------------------------------------------------------------------------------------------------------------------------------
 *
 *  Changes:
 *  1.0.0 - initial code port and modifications
 */

metadata {
	definition (
            name: "Ecolink Contact Sensor Custom", 
            namespace: "aaronward", 
            author: "Aaron Ward",
            importUrl: "https://raw.githubusercontent.com/PrayerfulDrop/Hubitat/master/drivers/ecolink-custom.groovy"
     ) {
		
        capability "Contact Sensor"
		capability "Battery"
		capability "Configuration"
	}
	preferences() {    	
            input("logEnable", "bool", title: "Enable logging", required: true, defaultValue: false)
    }    

}

def parse(String description) {
	def result = null
	if (description.startsWith("Err 106")) {
		if (state.sec) {
			if(logEnable) log.debug description
		} else {
			result = createEvent(
				descriptionText: "This sensor failed to complete the network security key exchange.",
				eventType: "ALERT",
				name: "secureInclusion",
				value: "failed",
				isStateChange: true,
			)
		}
	} else if (description != "updated") {
		def cmd = zwave.parse(description, [0x20: 1, 0x25: 1, 0x30: 1, 0x31: 5, 0x80: 1, 0x84: 1, 0x71: 3, 0x9C: 1])
		if (cmd) {
        	if(logEnable) log.debug "valid command"
			result = zwaveEvent(cmd)
		}
	}
	if(logEnable) log.debug "parsed '$description' to $result"
	return result
}

def updated() {
    if (logEnable) {
        log.debug "Logs enabled. Logs will be disabled in 15 minutes."
        runIn(900,logsOff)
    }
	def cmds = []
	if (!state.MSR) {
		cmds = [
			command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()),
			"delay 1200",
			zwave.wakeUpV1.wakeUpNoMoreInformation().format()
		]
	} else if (!state.lastbat) {
		cmds = []
	} else {
		cmds = [zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
	}
	response(cmds)
    
}

def configure() {
	commands([
		zwave.manufacturerSpecificV2.manufacturerSpecificGet(),
		zwave.batteryV1.batteryGet()
	], 6000)
}

def logsOff(){
    log.warn "Debug logging disabled."
    device.updateSetting("logEnable",[value:"false",type:"bool"])
}

def sensorValueEvent(value) {
	if (value) {
		createEvent(name: "contact", value: "closed")
	} else {
		createEvent(name: "contact", value: "open")
	}
}

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

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

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

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

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

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)
	}
	result
}

def zwaveEvent(hubitat.zwave.commands.wakeupv1.WakeUpNotification cmd)
{
	def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)
	def cmds = []
	if (!state.MSR) {
		cmds << command(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
		cmds << "delay 1200"
	}
	if (!state.lastbat || now() - state.lastbat > 53*60*60*1000) {
		cmds << command(zwave.batteryV1.batteryGet())
	} else {
		cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
	}
	[event, response(cmds)]
}

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()
	[createEvent(map), response(zwave.wakeUpV1.wakeUpNoMoreInformation())]
}

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)

	retypeBasedOnMSR()

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

	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 (!device.currentState("battery")) {
		if (msr == "0086-0102-0059") {
			result << response(zwave.securityV1.securityMessageEncapsulation().encapsulate(zwave.batteryV1.batteryGet()).format())
		} else {
			result << response(command(zwave.batteryV1.batteryGet()))
		}
	}

	result
}

def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
	def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x25: 1, 0x30: 1, 0x31: 5, 0x80: 1, 0x84: 1, 0x71: 3, 0x9C: 1])
	// log.debug "encapsulated: $encapsulatedCommand"
	if (encapsulatedCommand) {
		state.sec = 1
		zwaveEvent(encapsulatedCommand)
	}
}

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

private command(hubitat.zwave.Command cmd) {
	if (state.sec == 1) {
		zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
	} else {
		cmd.format()
	}
}

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

def retypeBasedOnMSR() {
	switch (state.MSR) {
		case "0086-0002-002D":
			if(logEnable) log.debug "Changing device type to Z-Wave Water Sensor"
			setDeviceType("Z-Wave Water Sensor")
			break
		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
			if(logEnable) log.debug "Changing device type to Z-Wave Motion Sensor"
			setDeviceType("Z-Wave Motion Sensor")
			break
		case "013C-0002-000D":  // Philio multi +
			if(logEnable) log.debug "Changing device type to 3-in-1 Multisensor Plus (SG)"
			setDeviceType("3-in-1 Multisensor Plus (SG)")
			break
		case "0109-2001-0106":  // Vision door/window
			if(logEnable) log.debug "Changing device type to Z-Wave Plus Door/Window Sensor"
			setDeviceType("Z-Wave Plus Door/Window Sensor")
			break
		case "0109-2002-0205": // Vision Motion
			if(logEnable) log.debug "Changing device type to Z-Wave Plus Motion/Temp Sensor"
			setDeviceType("Z-Wave Plus Motion/Temp Sensor")
			break
	}
}

@aaron I loaded the custom driver and the states for 'contact' still reads Open. Should it say Closed with the custom driver?
Maybe I will go open and close it to see if there is any change.

EDIT: That fixed it! Thanks Aaron. Much appreciated.

1 Like

1.0.5 - added additional AppWatchDogv2 support

Hi I get this error below, could you help

Can you show me the device information of your contact sensor?

Please see below I made a test with Aqara Sensor, same issue.

Click configure and then open/close your contact sensor.