My version of entry and exit delay for SHM (Classic App)

e2SHM

This is my version of the eNTRY and eXIT delay for SHM (e2SHM).

eSHM

  • this app can run in multi-instances so multiple profiles can be created. For example, 60 seconds delay for the front door, and 90 second delay for the back door.
  • Delay can be defined separately for armed/away and/or armed/stay

Requirements:

  • one simulated contact
  • one simulated pushbutton

those can be created in the IDE. If desired, a seperate simulated contact can be paired with each real contact for “coolness.”

This app uses the full features of the built-in SHM so there is no risk!

This is my first cut at it and I am sharing so I can learn from everyone input.

Have fun,

=======

eSHM version 2.1

  • Add iris keypad support (single code for home version)
  • Add appTouch for easier app identification in automation/smartapp screen
  •  Delay exit and entry beep
    
  • Add Alexa annoucements via simulated contacts
  •  Revised delay loop timimg.  RunOnce is used instead of runIn. 
    
  • Remove simulation pushbutton in pingpong timeloop (not needed since RunOnce is used)
  •  The pingpong timeloop is used only for notification, avoiding "race condition"
    
  • Remove icon selection (not needed)
  • general code cleanup
/**
 *  e2SHM
 * 
 *  Version 2.1.0 3/20/19
 *
 *	3/10/19		Initial release
 *	
 *	3/20/19  	Add iris keypad support (single code for home version)
 *				Delay exit and entry beep
 *				Add Alexa annoucements via simulated contacts
 *				Revised delay loop timimg.  RunOnce is used instead of runIn.
 *				Remove simulation pushbutton in pingpong timeloop (not needed since RunOnce is used)
 *				The pingpong timeloop is used only for notification, avoiding "race condition"
 *				Remove icon selection (not needed) 
 *				general code cleanup
 *
 *  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.
 *
 */
definition(
	name: "eSHMv2.1",
    namespace: "smartthings",
    author: "4Dad",
    description: "Allow entry/exit alarm delay to SHM",
    category: "Safety & Security",
    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",
	singleInstance: false
)

import groovy.json.JsonSlurper
import groovy.json.JsonBuilder

preferences { 
    page(name: "pageOne")    
    page(name: "pageTwo")
    page(name: "pageThree")
    page(name: "pageFour")
}

def pageOne() {
	dynamicPage(name: "pageOne", title: "e2SHM App Settings", uninstall: true) {
		section {
			input(name: "delaymode", type: "enum", title: "Select SHM Mode to Delay", options: ["Armed/Away", "Armed/Stay"], required: true, multiple: false)
			input(name: "exitentryduration", type: "number", title: "SHM Delay Duration in seconds", required: true)
		}
		section ("e2SHM Delay contact sensor settings") {
			input(name: "realdoorcontact", type: "capability.contactSensor", title: "Actual Door Contact", required: true, multiple: true)
 			input(name: "simulateddoorcontact", type: "capability.contactSensor", title: "Simulated Door Contact", required: true, multiple: false)
		} 
        section {
	       	href(name: "topageTwo", title: "Keypad option", page: "pageTwo")
        }
	}
}

def pageTwo() {
	dynamicPage(name: "pageTwo", title: "Keypad option", uninstall: true) {
        section {
        	input(name: "keypad", type: "capability.tone", title: "Select keypad", required: false, multiple: true, refreshAfterSelection: true, submitOnChange: true)
		}
        if (keypad != null) {        
    		section("Keypad user code setting") {
      			input(name: "userCode", type: "number", title: "Select 4 digits pincode", required: true, refreshAfterSelection: true)      			
                input(name: "userSlot", type: "number", title: "Use default or... Enter slot (1 through 30)", required: false, defaultValue: getRandomNumberInRange())
           	}
        } 
        section {
	       	href(name: "topageThree", title: "Notification option", page: "pageThree")
        }
	}
}

def pageThree() { 
	dynamicPage(name: "pageThree", title: "Push notification option...", install: true, uninstall: true) {

        section {
        	input(name: "eSHMPushMessage", type: "bool", title: "eSHM progress notification", required:false, defaultValue: false)
		}
        if (keypad != null) {
            section {
       			input(name: "keypadPushMessage", type: "bool", title: "Keypad event notification", required:false, defaultValue: false)
       			input(name: "keypadDelayBeep", type: "bool", title: "Keypad entry and exit beep", required:false, defaultValue: false)
			}
		}      
  		section {
        	input(name: "AlexaNotification", type: "bool", title: "Alexa says eSHM notification", required: false, defaultValue: false, submitOnChange: true)
		}
        if (AlexaNotification) {
            section {
                input(name: "AlexaSayEntry", type: "capability.contactSensor", title: "Alexa Entry virtual contact", required: true, multiple: false)
                input(name: "AlexaSayExit", type: "capability.contactSensor", title: "Alexa Exit virtual contact", required: true, multiple: false)
            }
		}
        section([mobileOnly:true]) {
            label title: "Assign a name", required: false
		}
        section {
        	href(name: "topageFour", title: "To app info page", page: "pageFour")
            href(name: "topageOne", title: "Back to start page", page: "pageOne")
        }
    }
}

def pageFour() {
	dynamicPage(name: "pageFour", title: "Information", install: true, uninstall: true) {
        section ("About this App") { 
            paragraph "${textAppName()}\n${textVersion()}\n${textCopyright()}\n\n${textLicense()}\n"
        }
        section ("Help & Instructions") { 
            paragraph "${textHelp()}"
        }
	}
}

def installed() {
	initialize()
}

def updated() {
	unsubscribe()
	initialize()
}

def initialize() {
    subscribe(app, appTouch)
    // keypad initialization
    if (keypad != null) {
    	subscribe(keypad, "tone", donothingHandler)
    	subscribe(keypad,"codeEntered", codeEntryHandler)
			senduserCodetokeypad()
	}
	subscribe(location, "alarmSystemStatus", SHMStatusHandler)	
    subscribe(realdoorcontact, "contact.open", realcontactHandler)
    subscribe(realdoorcontact, "contact.closed", realcontactHandler)
    subscribe(simulateddoorcontact, "contact.open", donothingHandler)
    subscribe(simulateddoorcontact, "contact.closed", donothingHandler)
    // optional alexa says notification
    if (AlexaNotification) {
    	subscribe(AlexaSayEntry, "contactSensor", donothingHandler)
    	subscribe(AlexaSayExit, "contactSensor", donothingHandler)
	}
	// App settings
    atomicState.exitentrydelay = "off"
    atomicState.pingpongloopis = 0
    if (exitentryduration != 0) {
    	atomicState.alarmwaitstep = exitentryduration/10  
    } else {
    	atomicState.alarmwaitstep = 1
    }    
    atomicState.alarmcounter = 1
	atomicState.adoorwasopened = 0
    
    // use the same SHM panel terms on smartthings app for input
    if (delaymode == "Armed/Away") {
    	atomicState.delaySHMmode = "away"
    } else {
        atomicState.delaySHMmode = "stay"
    }
    // eSHM settings
	simulateddoorcontact.close()
    if (keypad != null) {
    	keypadSHMStatusHandler()
	}
	if (atomicState.delaySHMmode == location.currentState("alarmSystemStatus")?.value) {
   		if (atomicState.SHMalarmModeis != 1) {
     	    if (eSHMPushMessage) {
	        	sendPush ("SHM is already armed/${atomicState.delaySHMmode} mode")
			}
            atomicState.SHMalarmModeis = 1
            atomicState.exitentrydelay = "entry"
		}
    }
 }   

// Send pincode to keypad(s)
def senduserCodetokeypad() {
  	def array = []
    if (userCode != null) {
      	array << ["code${userSlot}", "${userCode}"]
    } else {
      	array << ["code${userSlot}", "0000"]
        if (keypadPushMessage) {
        	sendPush("Pincode reset to 0000 on ${keypad}")}
    }
  	def json = new groovy.json.JsonBuilder(array).toString()
  	if (json != '[]') {
    	keypad.updateCodes(json)
  	}
}

// randomize slot for user code
// might be useful in future... but this is a cool way to assign slot
private int getRandomNumberInRange() {
	def min = 1
    def max = 30
    Random r = new Random();
		return r.nextInt((max - min) + 1) + min;
}

// keypad pincode handler
// verify pincode and set SHM mode
// SHM mode change either via keypad is handled by SHMStatusHandler
def codeEntryHandler(evt) {
  	def codeEntered = evt.value as String
  	def data = evt.data as String
  	def armMode = ''
	switch (data) {
		case "0" :
    		armMode = "off"
  		break
  		case "1" :
    		armMode = "stay"
  		break
  		case "2" :
    		armMode = "stay" // for night mode in DTH
    	break
  		case "3" :
    		armMode = "away"
  		break
    	default :
    		return []
    	break
  	}
    def correctCode = userCode as String
    if (codeEntered == correctCode) {
        switch (data) {
        	case "0" :
         		sendLocationEvent(name: "alarmSystemStatus", value: "off")
                keypad?.each() { it.setDisarmed() }
          		if (keypadPushMessage) { sendPush("SHM was disarmed with keypad") }
        	break
        	case "1" :
				sendLocationEvent(name: "alarmSystemStatus", value: "stay")
           		if (keypadPushMessage) { sendPush("SHM was set to 'Armed/Stay' with keypad") }
        	break    
        	case "2" :
          		sendLocationEvent(name: "alarmSystemStatus", value: "stay")
          		if (keypadPushMessage) { sendPush("SHM was set to 'Armed/Stay' with keypad") }
        	break
        	case "3" :
          		sendLocationEvent(name: "alarmSystemStatus", value: "away")
          		if (keypadPushMessage) { sendPush("SHM was set to 'Armed/Away' with keypad") }
       		break
        	default :
        		// nothing to do here
			break
    	}

	} else {
    	if (keypadPushMessage) { sendPush("Incorrect Code Entered: ${codeEntered}") }
    }
}

def donothingHandler(evt) {
// nothing to do here
}

// select a keypad will activate this routine
// synch keypad indicator with SHM
// this routine catches SHM events even when eSHM is deactivated
def keypadSHMStatusHandler() {
	def armmode = location.currentState("alarmSystemStatus")?.value
	switch (armmode) {
    	case "stay" :
            keypad?.each() { it.acknowledgeArmRequest(1) }
        	keypad?.each() { it.setArmedStay() }
		break
        case "away" :
            keypad?.each() { it.acknowledgeArmRequest(3) }
        	keypad?.each() { it.setArmedAway() }
        break
        case "off" :
            keypad?.each() { it.acknowledgeArmRequest(0) }
         	keypad?.each() { it.setDisarmed() }
        break
        default :
        		// nothing to do here
		break
  	}
}

// Do SHM mode and exit alarm
// eSHM is activated when the SHM mode is the same as set eSHM mode
// deactivated all eSHM routines when SHM mode is not the same as eSHM mode
// deactivated means no action will be taken from subscribed events when they occurred
def SHMStatusHandler(evt) { 
    if (atomicState.delaySHMmode == location.currentState("alarmSystemStatus")?.value) {
        atomicState.exitentrydelay = "exit"
		atomicState.adoorwasopened = 0
		atomicState.SHMalarmModeis = 1
        atomicState.pingpongloopis = 1
		atomicState.alarmcounter = 1
        if (eSHMPushMessage) {
	        sendPush ("SHM is starting the armed/${atomicState.delaySHMmode} mode")
		}
        if (keypad != null) {
            if (keypadDelayBeep) {
            	keypad?.each() { it.setExitDelay(exitentryduration) }
            } 
		}
        if (exitentryduration > 0) {
        	def date = new Date(now() +  exitentryduration*1000)
        	runOnce(date, exitdelayalarmHandler)
        	def pingdata = true
				pinpongtimeloopHandler(pingdata)
        } else {
        	exitdelayalarmHandler()
        }    
    } else {
        atomicState.exitentrydelay = "off"
        atomicState.adoorwasopened = 0
        atomicState.pingpongloopis = 0
        atomicState.SHMalarmModeis = 0
		simulateddoorcontact.close()
        if (eSHMPushMessage) {
        	sendPush ("eSHM is DEACTIVATED")
        }
        if (keypad != null) {
        	keypadSHMStatusHandler()
		}
        if (AlexaNotification) { 
        	AlexaSayExit.close() 
        	AlexaSayEntry.close() 
		}
    }
}  

// Monitor physical door contact for OPEN event
// Do SHM entry and exit conditions on selected door_contact
// only need one event to trigger the exit or entry handler
//  - on entry: disarm SHM is the only option
//  - on exit: door must be closed by the end of delay period to set alarm mode 
def realcontactHandler(evt) {
    if (atomicState.SHMalarmModeis == 1) {
    	if (evt.value == "open") {
        	if (atomicState.adoorwasopened == 0) {
            	if (atomicState.exitentrydelay == "exit") {
                    atomicState.adoorwasopened = 1
                    if (eSHMPushMessage) { sendPush("SHM Exit delay was triggered!") }

				} else {
            		atomicState.exitentrydelay = "entry"
        			atomicState.pingpongloopis = 1
					atomicState.alarmcounter = 1
            		atomicState.adoorwasopened = 1
                    if (eSHMPushMessage) { sendPush("SHM Entry delay was triggered!") }
                    if (keypad != null) {
                    	if (keypadDelayBeep) {
                        	keypad?.each() { it.setEntryDelay(exitentryduration) }
						}
                    }
                    if (exitentryduration > 0) {
                    	def date = new Date(now() +  exitentryduration*1000)
        				runOnce(date, entrydelayalarmHandler)
						def pingdata = true
							pinpongtimeloopHandler(pingdata) 
                    } else {
                    	entrydelayalarmHandler()
                    }    
            	}
			}
		}
	}
}

// Do delay using a "pingpong loop" (current option)
// another option is to schedule the delay timeout and use this loop for push notification
// both options, a SHM mode change will stop this loop
def pinpongtimeloopHandler(pingdata) {
    if (atomicState.pingpongloopis == 1) {
		if (atomicState.alarmwaitstep > atomicState.alarmcounter) {
			def timeleft = (atomicState.alarmwaitstep - atomicState.alarmcounter) * 10
            switch (atomicState.exitentrydelay) {
            	case "exit" :
                   if (atomicState.alarmcounter % 2 == 0) { 
            			if (eSHMPushMessage) {sendPush("SHM will be armed in ${timeleft} seconds") }
                        	if (AlexaNotification) { AlexaSayExit.close() }
            		}
            	break
            	case "entry" :
                    if (atomicState.alarmcounter % 2 == 0) {
           				if (eSHMPushMessage) {sendPush("${timeleft} seconds left to DISARM SHM")}
                    		if (AlexaNotification) { AlexaSayEntry.close() }
            		}
          		break  
            	default :
                	// nothing to do here
            	break
            }
			atomicState.alarmcounter = atomicState.alarmcounter + 1
            runIn(10, pingpongloop)
    	}
	}  
}

// pingpong loop back.  
// SHM mode change will stop this loop
def pingpongloop() {
	if (atomicState.SHMalarmModeis == 1) {
    	if (AlexaNotification) { 
        	if (atomicState.exitentrydelay == "exit") {
        		AlexaSayExit.open() }
        	if (atomicState.exitentrydelay == "entry") {
        		AlexaSayEntry.open() }
		}
    	def pingdata = true
		pinpongtimeloopHandler(pingdata)
    }
}

// Set SHM mode after exit delay duration expired: armed if door is closed; disarmed if door is opened
// SHM mode change during the delay period will stop this routine
// SHM mode change either via keypad or smartthings app is handled by SHMStatusHandler
def exitdelayalarmHandler() {
	if (atomicState.SHMalarmModeis == 1 && atomicState.exitentrydelay == "exit") { 
    	def open = realdoorcontact.findAll { it?.latestValue("contact") == "open" }
        def list = open.size() > 1 ? "are" : "is"
        atomicState.pingpongloopis = 0
    	atomicState.adoorwasopened = 0
    		if (open) {
				if (eSHMPushMessage) {
	    			sendPush("SHM activation was CANCELLED.  ${open.join(', ')} ${list} open")
        		}
				sendLocationEvent(name: "alarmSystemStatus", value: "off")
                atomicState.exitentrydelay = "off"
 			} else {
            	if (eSHMPushMessage) {
	    			sendPush("SHM was set to armed/${atomicState.delaySHMmode} mode")
        		}
              	atomicState.exitentrydelay = "entry"
			}
        keypadSHMStatusHandler()
        if (AlexaNotification) { 
        	AlexaSayExit.close() 
         	AlexaSayEntry.close() 
		}
	}  
}

// Set SHM alarm condition after enter delay duration expired: alarm if SHM was not disarmed; 
// SHM mode change during the delay period will stop this routine
// SHM mode change either via keypad or smartthings app is handled by SHMStatusHandler
def entrydelayalarmHandler () {
	if (atomicState.SHMalarmModeis == 1 && atomicState.exitentrydelay == "entry") {
		if (eSHMPushMessage) {
    		sendPush ("CAUTION..Intruder Alert...Intruder Alert...")
    	}
    	simulateddoorcontact.open()
        atomicState.pingpongloopis = 0
        atomicState.adoorwasopened = 0
    }
    if (AlexaNotification) { 
        AlexaSayExit.close() 
        AlexaSayEntry.close() 
	}
}


//Version/Copyright/Information/Help
private def textAppName() {
	def text = "SHM delay for entry and exit"
}	

private def textVersion() {
    def text = "Version 2.1.0 3/20/19"
}

private def textCopyright() {
    def text = "Copyright © 2019 illumatti"
}

private def textLicense() {
    def text =
		"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"+
		"\n\n"+
		"    http://www.apache.org/licenses/LICENSE-2.0"+
		"\n\n"+
		"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."
}

private def textHelp() {
	def text =
		"e2SHM provides the missing delay alarm features in the built-in SHM.\n"+
        "Requirements: \n"+
        "  . One simnulated contact to allow exit/entry delay to SHM. \n"+ 
		"This contact is easy to make in the IDE/create new device/simulated devices. \n"+
 		"SHM settings: \n"+  
        "- Run this app in two instances: one for armed/away and one for armed/stay. \n"+
        "- Remove door(s) need entry/exit delay from the SHM setting in each Armed/Stay"+
        "and Armed/Away mode. \n"+
        "- Add the simulated contact to the SHM settings for each mode. \n"+
        "Options \n"+
        "- Keypad with User code and entry and exit beep. (Tested with Iris keypad.) \n"+
        "- Push notification on eSHM and keypad events \n"+
        "- AlexaSays option for exit and entry /n"+
        "  . AlexaSays needs two simulated contacts.  Use Alexa app to ask Alexa to do thing "+
        "during the exit and entry duration.  This app control those contacts and Alexa takes "+
        "the set actions when contacts open in Alexa app."
}


help me how to put it in block code coz it is not working…:anguished:

it is ok now

Just for clarity, this works only with the SHM feature in the classic app, correct?

not really, i say it because the groovy say it is for classic.
i haven’t tested yet coz the current samsung smartthings connect app is buggy.

Their new revision is scheduled for end of mar, they said

SHM in the new app and SHM in the Classic app are two completely different features which just happened to have the same name. :scream:

The features for the new app’s SHM have not been exposed for use by custom smartapps. So this creates a lot of confusion.

On the other hand, The new app’s SHM feature has a built-in entry delay, so people are less likely to be looking for one if that’s the app they are using.

2 Likes

It will not allow “Virtual pushbutton”. also can you send how to create simulated contact?

Also in the SHM how do I configure the settings? sensors open/close?

sign in your account
go to device list

  • new device
    in the " type" section select simulated contact

yes, the contact open\close is normally for door/windows