A sometimes-silent doorbell

Continuing the discussion from Working MIMOlite Device Type with Sensor:

I integrated my doorbell today using a MIMOlite with the device type linked above. I have a dog who loves to bark at the front door whenever the doorbell rings, so I’d really rather get a push notification and have enough time to get her leash on, and then calmly go answer the door. If I don’t hear my phone, I still want the doorbell to ring on repeated presses.

So I wrote this SmartApp. Perhaps it will be helpful to others. It can be easily modified to use other hardware.

I’ll add pictures of the wiring sometime soon…

First, disconnect the wire that leads from your doorbell button to the doorbell transformer and the wire that leads from your doorbell button to the doorbell chime, and connect those to the contact input on the MIMOlite.

Second, connect the NO and COM ports of the MIMOlite to the doorbell transformer and the doorbell chime, respectively.

Third, plug in the MIMOlite, and include it in your network.

Fourth, set the device type for the MIMOlite to use the above device to give it the capabilities “Contact Sensor” and “Momentary”. Set the delay of the momentary switch to something that works for you… I’m using 500ms.

Fifth, install the SmartApp:

/**
 *  Sometimes Silent Doorbell
 *
 *  Copyright 2015 Paul Toben
 *
 *  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.
 *
 *	
 *
 *
 *  This app is designed to work with the MIMOlite using a device type that exposes the input relay as a contact
 *  sensor, and the output relay as a momentary switch (using the Momentary capability).  To find a device type
 *  for your MIMOlite, see: https://community.smartthings.com/t/working-mimolite-device-type-with-sensor/1468/4
 *
 *  You must modify the linked device type with a momentary delay that works for your dorbell.  I am using 500ms.
 *
 *  This app can, however, be used with any contact sensor as an input and any momentary switch as an output. YMMV.
 *
 */
definition(
    name: "Sometimes Silent Doorbell",
    namespace: "",
    author: "Paul Toben",
    description: "A doorbell controller that first sends a push notification, and only rings the doorbell after some delay. Great for people with sleeping children or reactive dogs.",
    category: "My Apps",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")


preferences {
	section ("When this doorbell button is pushed...") {
		input "contact1", "capability.contactSensor", title: "Button", multiple: false, required: true
	}
    section ("Send this message") {
    	input "message", "text", title: "Notification message", description: "Doorbell!", required: true
	}
	section ("Ring this doorbell...") {
		input "switch1", "capability.momentary", title: "Momentary Switch", multiple: false, required: true
	}
	section("But only allow the bell to ring after how many seconds?") {
		input "silentSeconds", "decimal", title: "Seconds", required: true
	}
    section("Reset to silent mode after how many minutes?") {
	input "resetMinutes", "decimal", title: "Minutes", required: true
	}
}

def installed() {
	log.debug "Installed with settings: ${settings}"
	initialize()
}

def updated() {
	log.debug "Updated with settings: ${settings}"
	unsubscribe()
	initialize()
}

def initialize() {
	subscribe(contact1, "contact.closed", contactClosedHandler)
    unschedule(stateReset)
	state.running = false
	log.debug "Initialized, reset scheduled calls, and state.running is ${state.running}"
}

def contactClosedHandler(evt) {
	log.debug "Doorbell button push detected"

    runIn(resetMinutes * 60, stateReset)					//schedule the reset back to silent mode
    
    if (state.running == false) {							//if this is the first press of the button,
    	state.running = true								//mark that the button has been pressed recently
    	state.firstPush = now()								//mark the time that the button was pressed this first time
        log.debug "state.firstPush is: ${state.firstPush}"
        silentBell()										//call silentBell to send a push notification.
        }
        
    else {													//if this is NOT the first press of the button
    	def millisSinceFirst = now() - state.firstPush		//calculate how long ago the first press was
        log.debug "seconds since first push is ${millisSinceFirst / 1000}"
        if (millisSinceFirst <= (silentSeconds * 1000)) {	//if this press should still be silent,
        	log.debug "Not enough time has passed. silentSeconds is greater. calling silentBell"
        	silentBell()									//call silentBell to send a push notification.
			}
		else if (millisSinceFirst > (silentSeconds * 1000)){	//if this press should be loud,
        	log.debug "millisSinceFirst is greater, calling loudBell"
            loudBell()											//call loudBell and ring my bell l l, ring my bell.
            }
		}
}

def silentBell() {											//method for sending push notifications when the button is pushed
	log.debug "silentBell called, sending push message"
    sendPush(message)
}

def loudBell() {											//method for ringing the doorbell
	log.debug "loudBell called... Ding Dong."
    switch1.push
}

def stateReset() {											//method for resetting to silent mode
    state.running = false
    log.debug "stateReset called. state.running is now: ${state.running}"
}

The complete device type for the MIMOlite is below if it’s easier to copy/paste.

/**
 *  MIMO Doorbell
 *
 *  Copyright 2015 Paul Toben, based on Jit Jack and SmartThings
 *
 *  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.
 *
 */
metadata {
	definition (name: "MIMO Doorbell", author: "Paul Toben") {
		capability "Contact Sensor"
		capability "Momentary"
		capability "Polling"
		capability "Refresh"
	}

	simulator {
		status "on":  "command: 2003, payload: FF"
		status "off": "command: 2003, payload: 00"

        // reply messages
		reply "2001FF,delay 100,2502": "command: 2503, payload: FF"
		reply "200100,delay 100,2502": "command: 2503, payload: 00"

		// status messages
		status "open":  "command: 2001, payload: FF"
		status "closed": "command: 2001, payload: 00"	}

	tiles {
		standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
			state "off", label: '${name}', action: "momentary.push", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
			state "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
		}
        standardTile("contact", "device.contact", inactiveLabel: false) {
			state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#ffa81e"
			state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821"
		}
        standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
			state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
		}
        valueTile("alarm", "device.alarm", inactiveLabel: false) {
			state "alarm", label:'${currentValue}'
		}

		main (["switch", "contact"])
		details(["switch", "contact", "refresh", "alarm"])
	}	
}

def parse(String description) {
	def result = null
	def cmd = zwave.parse(description, [0x20: 1, 0x84: 1, 0x30: 1, 0x70: 1])
	if (cmd) {
		result = createEvent(zwaveEvent(cmd))
	}
	log.debug "Parse returned ${result?.descriptionText}"
	return result
}

def sensorValueEvent(Short value) {
	if (value) {
		createEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open")
	} else {
		createEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed")
	}
}

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

def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
	[name: "switch", value: cmd.value ? "on" : "off", type: "physical"]
}

def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
	[name: "switch", value: cmd.value ? "on" : "off", type: "digital"]
}

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

def zwaveEvent(physicalgraph.zwave.commands.alarmv1.AlarmReport cmd)
{
	sensorValueEvent(cmd.sensorState)
}

def zwaveEvent(physicalgraph.zwave.Command cmd) {
	// Handles all Z-Wave commands we aren't interested in
	[:]
}

def push() {
	def cmds = [
		zwave.basicV1.basicSet(value: 0xFF).format(),
		zwave.switchBinaryV1.switchBinaryGet().format(),
		"delay 500",
		zwave.basicV1.basicSet(value: 0x00).format(),
		zwave.switchBinaryV1.switchBinaryGet().format()
	]
}

def on() {
	push()
}

def off() {
	[
		zwave.basicV1.basicSet(value: 0x00).format(),
		zwave.switchBinaryV1.switchBinaryGet().format()
	]
}

def poll() {
	zwave.switchBinaryV1.switchBinaryGet().format()
}

def refresh() {
	zwave.switchBinaryV1.switchBinaryGet().format()
}
3 Likes