Anybody else having problems with "Unschedule()"?


(Deives) #1

Hi guys, I wrote and app and had it running for months with no issue, the app controls a light based on a motion sensor and other stuff. Since last week I noticed that my lights go crazy, on and off if the room is occupied for longer the timer.
I ran the app today on the IDE and noticed that my runin() is not showing as scheduled, although it does run and the timer does work, but the problem I’m finding is that if there is still motion in the room, it suppose to unschedule, but since it is theoretically not scheduled, then there is nothing to unschedule.

Anybody else seen the same problem?

Thanks!


(sidjohn1) #2

I’ve been playing with runin() and unschedule() all day and they have been both working fine. Its highly probable you have a issue with your code logic. If you want other eyes on it posting a link to the code your having a issue with would help.


(Deives) #3

Ok, thanks for replying! I will contact support then. Like I said before, this code has been working for months.

Thanks again!


(Tuffcalc) #4

@twisty, can you update us on what support says? I’m finding the same issue.

All of a sudden lights going on/off before the timer. I think it is related to runin or unschedule.


(Deives) #5

Here is the code:

/**
 *  Smart Timer
 *  Loosely based on "Light Follows Me"
 *
 *  This prevent them from turning off when the timer expires, if they were already turned on
 *
 *  If the switch is already on, it won't be affected by the timer  (Must be turned of manually)
 *  If the switch is toggled while in timeout-mode, it will remain on and ignore the timer (Must be turned of manually)
 *
 *  The timeout perid begins when the contact is closed, or motion stops, so leaving a door open won't start the timer until it's closed.
 *
 *  Author: Twisty81
 *  Date: 2015-02-20
 *  
 *  Modified by Twisty81 to include interlocking between motion and contact, if motion stops while the contact is open, or contact closes while there is motion, it won't start the timer.
 *	Yet one doesn't require the other, it can be just contact or just motion, or both.
 * 	Also if timer is running but contact was re-opened and/or motion went to active again, it will stop the timer, till the contact is closed and/or motion stops.
 *	Added Sunset and Sunrise functionality, this will trigger the light only between sunset and sunrise when a contact is open and/or motion was detected.
 *  
 */

definition(
    name: "Smarter Light Timer, with check",
    namespace:"",
    author: "Twisty81",
    description: "Turns ON a switch for X minutes, then turns it OFF. Unless, the switch is already ON in which case it stays ON. If the switch is toggled while the timer is running, the timer is canceled",
    category: "Convenience",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/light_contact-outlet-luminance.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/light_contact-outlet-luminance@2x.png"
)

preferences {
	section("Turn on when..."){
		input "motions", "capability.motionSensor", multiple: true, title: "...there is Motion", required: false
		input "contacts", "capability.contactSensor", multiple: true, title: "...this Opens", required: false
	}
	section("Turn on this light(s)..."){
		input "lights", "capability.switch", multiple: true, title: "Select Lights"
	}
	section("When contact is closed and/or motion stops, Turn off after how many seconds? (60s minimum)") {
        input "time", "number", title: "Enter 0 to not auto-off", defaultValue: "60"
    }
    section("And it's dark...(optional)") {
		input "luminance1", "capability.illuminanceMeasurement", title: "Where?", required: false
		input "lux", "number", title: "Less than? (Lux 0 - 1000)", defaultValue: "10", required: false
        input "luxBool", "bool", title: "Should lux be a priority over Sunset/Sunrise?" 
	}
	section("Based on Sunset/Sunrise for the location? (optional)") {
    	input "sunBool", "bool", title: "No/Yes"
    }
    	section ("Sunrise offset (optional)...") {
			input "sunriseOffsetValue", "text", title: "HH:MM", required: false
			input "sunriseOffsetDir", "enum", title: "Before or After", required: false, options: ["Before","After"]
		}
		section ("Sunset offset (optional)...") {
			input "sunsetOffsetValue", "text", title: "HH:MM", required: false
			input "sunsetOffsetDir", "enum", title: "Before or After", required: false, options: ["Before","After"]
		}
		section ("Zip code (optional, defaults to location coordinates)...") {
			input "zipCode", "text", required: false
		}    
}

def installed() {
	initialize()
}

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


def initialize() {
	subscribe(lights, "switch", switchChange)
	subscribe(motions, "motion", motionHandler)
	subscribe(contacts, "contact", contactHandler)
    
	state.myState = "ready"
    log.debug "state: " + state.myState
    
	if(sunBool == true) {
    	log.debug "RiseSet?: " + sunBool
        subscribe(location, "position", locationPositionChange)
		subscribe(location, "sunriseTime", sunriseSunsetTimeHandler)
		subscribe(location, "sunsetTime", sunriseSunsetTimeHandler)
	   	astroCheck()
//		schedule("0 1 * * * ?", astroCheck) // check every hour since location can change without event?
	}        
        
}

def locationPositionChange(evt) {
	log.trace "locationChange()"
	astroCheck()
}

def sunriseSunsetTimeHandler(evt) {
	log.trace "sunriseSunsetTimeHandler()"
	astroCheck()
}

def astroCheck() {
	def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: sunriseOffset, sunsetOffset: sunsetOffset)
    state.riseTime = s.sunrise.time
	state.setTime = s.sunset.time
	log.debug "rise: ${new Date(state.riseTime)}($state.riseTime), set: ${new Date(state.setTime)}($state.setTime)"
    
    if(state.riseTime < now()) { //often I found the system returning the past sunrise, which for us is not important anymore. Therefore we are checking if sunrise is in the past, if so, add one day.
    	log.debug "adding one day"
        state.riseTime = s.sunrise.time + 86400000
    	log.debug "New rise: ${new Date(state.riseTime)}($state.riseTime), set: ${new Date(state.setTime)}($state.setTime)"
    }    
}    

def switchChange(evt) {
	log.debug "SwitchChange: $evt.name: $evt.value"
/*	def currSwitches = lights.currentSwitch
	def onSwitches = currSwitches.findAll {switchVal -> switchVal == "on" ? true : false}

    if(onSwitches.size() == lights.size()) {
		log.debug "${onSwitches.size()} out of ${lights.size()} switches are on"
        log.debug "all already on"
	}
    else if(onSwitches.size() << lights.size()) {
		log.debug "${onSwitches.size()} out of ${lights.size()} switches are on"
        log.debug "some are on"
    }
    else if(onSwitches.size() == 0) {
		log.debug "${onSwitches.size()} out of ${lights.size()} switches are on"
        log.debug "ready"
    }
*/        

    if(evt.value == "on") {
        // Slight change of Race condition between motion or contact turning the switch on,
        // versus user turning the switch on. Since we can't pass event parameters :-(, we rely
        // on the state and hope for the best.
        if(state.myState == "activating") {
            // Activating from the Motion or Contact handlers and not the switch itself. Go to Active mode.
            state.myState = "active"
        } else if(state.myState != "active") {
	  		state.myState = "already on"
            // If the switch went to "ON" and it wasn't this app, then set "already on" to inform the app not to
            // run throught the timer when the contact closes and/or motion stops.
        }
    } else {
    	// If active and switch is turned off manually, then stop the schedule and go to "ready" state
    	if(state.myState == "active" || state.myState == "activating") {
    		unschedule(turnOffLight)
        }
  		state.myState = "ready"
    }
    log.debug "state: " + state.myState
}

def contactHandler(evt) {
	log.debug "contactHandler: $evt.name: $evt.value"
	def currContacts = contacts.currentContact
    def openContacts = currContacts.findAll {contactVal -> contactVal == "open" ? true : false}
    log.debug "${openContacts.size()} out of ${contacts.size()} contacts are open"
//    log.debug "openContacts: $openContacts"
    
    if(openContacts) {
    	log.debug "Some contact(s) just opened"
        state.myContact = "open" 			// to interlock with motion handler, this way if the contact is open and the motion goes inactive it won't start the timer.
    	logicCheck()
	}  
	else {
    	state.myContact = "closed"			// to interlock with motion handler
        log.debug "contact(s) closed, checking condition"
        	if (state.myState == "active" && state.myMotion == "inactive" || state.myState == "active" && !state.myMotion) {
			// when the contact closes, it checks if motion is inactive which will allow the timer to run OR in case
            // the motion was not set up by the user, the state will be null and the timer will run since nothing is impeding.
            log.debug "condition passed"
            setCheckAndSchedule()
        	}
   		}
    
    log.debug "state: " + state.myState
}

def motionHandler(evt) {
	log.debug "motionHandler: $evt.name: $evt.value"
	def currMotions = motions.currentMotion
    def activeMotions = currMotions.findAll {motionVal -> motionVal == "active" ? true : false}
    log.debug "${activeMotions.size()} out of ${motions.size()} motion sensors are active"
//    log.debug "active Motions: $activeMotions"

    if(activeMotions) {
        state.myMotion = "active"			// to interlock with contact handler, this way if the contact is closed
    	logicCheck()  									// while the motion is active, it won't start the timer
    }
	else {
		state.myMotion = "inactive"		// to interlock with contact handler
        log.debug "motion inactive, checking condition"
		if (state.myState == "active" && state.myContact == "closed" || state.myState == "active" && !state.myContact) {
			// when motion goes inactive, it checks if the contact is closed which will allow the timer to run OR in
            // case the contact was not set up by the user, the state will be null and the timer will run since nothing is impeding.
			log.debug "condition passed"
           	setCheckAndSchedule()
		}
	}
	log.debug "state: " + state.myState
}

def logicCheck() {
	
        if(luminance1 && state.myState == "ready") {	// if a lux sensor was selected on preferences and the app is ready...
        def lightSensorState = luminance1.currentIlluminance  //...then, get the illuminance from the sensor
       		log.debug "Illuminance: $lightSensorState < $lux"
       		if(lightSensorState < lux) {				// compare the illuminance with user lux setting, if the condition passes, turn on the lights
				log.debug "Turning on lights by contact opening and lux"
            	lightOn()
            }
            if(sunBool == true && luxBool == false && state.myState == "ready") { //this is used when sunSet/Rise was selected by the user when he/she also selected a lux sensor, in this case the lux has NO priority and whichever is true, lux or sunset/rise, then turn on the lights
        		if(now() > state.setTime && now() < state.riseTime) {
        		log.debug "Turning on lights by contact opening and RiseSet non priority"
           		lightOn()
                }
            }
        }            
		else if(sunBool == true && state.myState == "ready") { //if the user selected to use surRise/Set and the app is ready...
        	if(now() > state.setTime && now() < state.riseTime) { //checks if now is within dark period SET to RISE, if so, turn on the lights
        		log.debug "Turning on lights by contact opening and RiseSet"
           		lightOn()
			}    
        }
        else if(state.myState == "ready") { //if no lux and no sunset was selected then it will fall here and turn on the light
        		log.debug "Turning on lights by contact opening only"
            	lightOn()
        }   
        else if(state.myState == "active") {		// if the app was already active, by motion or contact, and the contact re-opened
        	log.debug "unscheduling"		// stop the timer
            unschedule()
        }   
}

def lightOn() {
	state.myState = "activating"
    lights.on()
//	runIn(60, checkmyState)
}

/*
def checkmyState() {
	log.debug "checking myState"
	if(state.myState == "activating") {
    	state.myState = "ready"
        log.debug "lights failed to turn on, setting state to ready again"
    }
    log.debug "state: " + state.myState
}    
*/

def setCheckAndSchedule() {
    if(time == 0)
    	log.debug "user set lights to not turn off"		// self-explanatory
    else if(state.myState != "already on") {			// checking if switch state did not change
		runIn(time, turnOffLight)
		log.debug "Timer Counting"
	}        	
}

def turnOffLight() {
	log.debug "double checking if light was not turned Off/On by user"
    if (state.myState == "active") {		// after the timer is done, it double check to see if nothing changed
    										// before commanding the lights to off
		log.debug "turning off lights"
	    lights.off()
		state.myState = "ready"				// set the state to "ready" again
	    log.debug "state: " + state.myState
    }    
}

private getSunriseOffset() {
	sunriseOffsetValue ? (sunriseOffsetDir == "Before" ? "-$sunriseOffsetValue" : sunriseOffsetValue) : null
}

private getSunsetOffset() {
	sunsetOffsetValue ? (sunsetOffsetDir == "Before" ? "-$sunsetOffsetValue" : sunsetOffsetValue) : null
}

(Brandon) #6

I didn’t look at any of the code, but if you enclose your code in [ code] and [ /code] brackets, you’ll get something much more readable for everyone else. I had to add a space in between the left ‘[’ and the next char, so make sure you delete that when you go to use it.

It will show up like this:

if (this) {
     var that = do
} 

#7

I’d been having runIn() and unschedule() over the past several days, now() values have been strange, lots of issues. Support told me several people had complained about this.

As of this morning things seem to be running better for me.


(Deives) #8

Edited the code post! Thanks for the tip!


(Deives) #9

The problem I’m having is that I don’t see on the IDE the job being scheduled after runin(), or being unscheduled after unschedule().
See the attached picture, after the log debug “timer counting” It suppose to show the job scheduled, but it doesn’t, same for the unscheduling.


(Nick) #10

Yeah, I had problems with unschedule not actually unscheduling. I put debug code in every time I did a “runIn” and a “unschedule”, and I know I called “unschedule”. But it didn’t actually unschedule it, given like 60 seconds before the function was to be called.

I worked around this by adding a variable to state called: alowed_to_run. And set it to true or false, based on if I called runIn or unschedule. Then when my function was called, if state.allowed_to_run was false (meaning a “unschedule” was called last), I just exited.