Custom SmartApps after new SmartThings migration

I have written a SmartApp that I use in every room in my house. My concern is that after I migrate to the new SmartThings app that the SmartApp that I wrote will no longer work.

Does anyone know if this is the case?

Depends on what your app does, quite honestly.

Load up newapp and log in with your account without running migration and see what works and what doesn’t. It wont break Classic.

2 Likes

Hi Nathan,

Thanks for your quick response!

When I go to the new app I have the same amount of control of my SmartApp as in the classic app, so I guess that is good news for me.

My app doesn’t really do anything too fancy. The app I wrote, put simply, turns on the lights if there is motion, and turns them off when there is no motion for a set amount of time, but with the added twist that if you turn off the lights before they turn off from lack of motion, they won’t turn back on from motion until they are manually turned back on.

Here is the code. I am aware that it is pretty messy.
/**
* Motion Plus
*
* Copyright 2016 Matthew Williams
*
* 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.
*
*/
import groovy.time.TimeDuration
//import groovy.time.TimeCategory

definition(
    name: "Motion Plus",
    namespace: "MatthewKyleWilliams0",
    author: "Matthew Williams",
    description: "Turn on lights with motion sensor. Allow lights to be turned off without being turned back on by motion sensor.",
    category: "Convenience",
    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("Turn on when motion detected:") {
        input "themotion", "capability.motionSensor", required: true, title: "Where?"
    }
    section("Turn off when there's been no movement for") {
        input "minutes", "number", required: true, title: "Minutes?"
    }
    section("Turn on this light") {
        input "theswitch", "capability.switch", required: true, multiple: true
    }
    section("Enabled during daylight hours?") {
        input "duringdaylight", "boolean", required: true, multiple: false, title: "Daylight hours?"
    }
    section("Daylight offset (default 60 minutes)") {
    	input "daylightoffset", "number", required: false, title: "Offset minutes"
    }
    section("Light sensor"){
    	input "lightsensor", "capability.illuminanceMeasurement", required: false, title: "Light sensor", multiple: false
    }
    section("Light threshold"){
    	input "lux", "number", required: false, title: "Lux"
    }
    //section("Night time hours") {
    //	input "nightStart", "time", required: false, title: "Start time"
    //  input "nightEnd", "time", required: false, title: "End time"
    //}
}

def installed() {
    initialize()
}

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

def initialize() {
    subscribe(themotion, "motion.active", motionDetectedHandler)
    subscribe(themotion, "motion.inactive", motionStoppedHandler)
    subscribe(theswitch, "switch.on", lightOnHandler)
    subscribe(theswitch, "switch.off", lightOffHandler)
    subscribe(lightsensor, "illuminance", lightChangeHandler)
    sunsetHandler()
    //Backup schedule to prevent lights from staying on if the runOnce fails
	runEvery1Hour(checkMotion)
}

def sunsetHandler(){
    //log.debug "sunsetHandler"
    if(duringdaylight == "false"){
        //The room lighting can change below the threshold while there is motion. 
        //If there is motion call the motion detected handler so lights will turn on if the threshold has been met.
        if (themotion.currentState("motion").value == "active"){
            motionDetectedHandler()
        }
        def sunsetTime = getSunsetTime()
        def nextHour = now() + (1000*60*60)
        if(nextHour > sunsetTime){
            sunsetTime = nextHour
        }
        sunsetTime = new Date(sunsetTime)
        //log.debug(sunsetTime)
        runOnce(sunsetTime,sunsetHandler)
    }
}

def getSunsetTime(){
	use(groovy.time.TimeCategory){
		//Get the time of midnight the next day
        //For example if it is 3/2/2019 16:10 midnight time will be 3/3/2019 00:00 in epoch time
        def midnightTime = (new Date().clearTime() + 1.days)
        //Offset time is not included
        midnightTime = midnightTime + 5.hours
        midnightTime = midnightTime.time
        //Get sunrise and sunset for current day
        def sunsetTime = Date.parse("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", location.currentValue("sunsetTime"))
        if (sunsetTime.time > midnightTime){
        	//if sunrise time is for the current day change it to the next day
            sunsetTime = sunsetTime - 1.days
        }
        sunsetTime = sunsetTime.time
        //Only turn the light on if it is set to go on during daylight hours or it is not daylight hours
        def offsetMinutes = 60
        if(daylightoffset != null){
        	offsetMinutes = daylightoffset
        }
        def sunOffset = 60 * 1000 * offsetMinutes
        sunsetTime = (sunsetTime - sunOffset)
        return sunsetTime
	}
}

def getSunriseTime(){
	use(groovy.time.TimeCategory){
		//Get the time of midnight the next day
        //For example if it is 3/2/2019 16:10 midnight time will be 3/3/2019 00:00 in epoch time
        def midnightTime = (new Date().clearTime() + 1.days).time
        //Get sunrise and sunset for current day
        def sunriseTime = Date.parse("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", location.currentValue("sunriseTime"))
        if (sunriseTime.time > midnightTime){
        //if sunrise time is for the next day change it to the current day
        	sunriseTime = sunriseTime - 1.days
        }
        sunriseTime = sunriseTime.time
        //Only turn the light on if it is set to go on during daylight hours or it is not daylight hours
        def sunOffset = 60 * 1000 * 60
        sunriseTime = (sunriseTime + sunOffset)
        return sunriseTime
	}
}

def lightChangeHandler(evt){
	//The room lighting can change below the threshold while there is motion. 
    //If there is motion call the motion detected handler so lights will turn on if the threshold has been met.
    if (themotion.currentState("motion").value == "active"){
    	motionDetectedHandler()
    }
}

def lightOnHandler(evt) {
    //log.debug "lightOnHandler called: $evt"
    use(groovy.time.TimeCategory){
        def schedDate = new Date()
        for(def i = 0; i < minutes; i++){
            schedDate = schedDate + 61.seconds
        }
        runOnce(schedDate, checkMotion)
    }
    //runIn((61 * minutes), "checkMotion", [overwrite:true])
}

def lightOffHandler(evt) {
    //log.debug "lightOffHandler called: $evt"
}

def motionDetectedHandler(evt) {
	//use(groovy.time.TimeCategory){
        //def sunrise = getSunriseTime()
        //def sunset = getSunsetTime()
        //log.debug "rise " + sunrise
        //log.debug "set " + sunset
    //}
    //log.debug "motionDetectedHandler called: $evt"
    //log.debug atomicState
    int index = 0
    for(item in theswitch){
    	//def isOff = atomicState.isOff
        //def isTurningOff = atomicState.isTurningOff
        def onState = item.currentState("switch")
        //log.debug onState.value
        if(onState.value == "off") {
            //def switchStates 
            use(groovy.time.TimeCategory){
                def duration = 60.seconds//new TimeDuration(0, minutes, 0, 0)
                def exactDuration = 60.seconds
                //time the light turned off
                def start = onState.date
                def exactStart = onState.date
                for(def i = 0; i < minutes; i++){
                    start = start - duration
                    exactStart = exactStart - exactDuration
                }
                def end = onState.date //- 5.seconds
                //exactStart = exactStart - 10.seconds
                //log.debug "start"
                //log.debug start
                //log.debug "exactStart"
                //log.debug exactStart
                //log.debug "end"
                //log.debug end
                //def switchStates = item.eventsBetween(start, end)
                //def motionStates = themotion.eventsBetween(start, end)
                def switchStates = item.statesBetween("switch", start + 120.seconds, end - 120.seconds)
                def motionStates = themotion.statesBetween("motion", start + 120.seconds, end - 120.seconds)
                //log.debug "motionStates count"
                //log.debug motionStates.size()
                //log.debug switchStates.size()
                //Check to see if the light was last on for less than the set number of minutes. If this is true then the light must have turned off from another source
                def lastOnStatesBetween = item.statesBetween("switch", exactStart, end)
                //log.debug "statesCount"
                //log.debug lastOnStatesBetween.size()
                def lastOnState = lastOnStatesBetween[1]
                def isOnLessThanMinutes = false
                if(lastOnState != null){
                	//log.debug "lastOnState.value"
                	//log.debug lastOnState.value
                    def onStateDur = (onState.date.getTime() - lastOnState.date.getTime())/1000/60
                    //log.debug onStateDur
                    //subtract a minute from minutes in case motion sensor turns off lights a little before minutes
                    isOnLessThanMinutes = onStateDur < (minutes/2)
                }
                else{
                	//log.debug "lastOnState is null the state before that is"
                    //log.debug lastOnStatesBetween[0]
                    if(lastOnStatesBetween[0] != null){
                    	//log.debug lastOnStatesBetween[0].value
                    }
                }
                
                //Todo: check corner case of if motion has been active for longer than minutes when light is turned off
                //...look into statesBetween to see look for motion active as the latest state during the specified date range
                if(!isOnLessThanMinutes && ((switchStates.size() + motionStates.size()) < 3)){
                	def beforeSunrise = (getSunriseTime()) >= now()
                    def afterSunset = (getSunsetTime()) <= now()
                    if( (duringdaylight == "true") || beforeSunrise || afterSunset){
                    	if((beforeSunrise || afterSunset || lightsensor == null) || (lightsensor != null && lightsensor.currentState("illuminance").value.toInteger() <= lux.toInteger())){
                    		item.on()
                        }
                    }
                }
                else {
                    //log.debug "detected shut off from other source. Switch and Motion states count:"
                    //log.debug switchStates.size()
                    //log.debug motionStates.size()
                }
            }
        }
        else {
            //log.debug "light already on"
        }
        index++
    }
}

def motionStoppedHandler(evt) {
    //log.debug "motionStoppedHandler called: $evt"
    //log.debug "Starting motion stopped handler"
    use(groovy.time.TimeCategory){
        def schedDate = new Date()
        for(def i = 0; i < minutes; i++){
            schedDate = schedDate + 61.seconds
        }
        runOnce(schedDate, checkMotion)
    }
    //runIn((61 * minutes), "checkMotion", [overwrite:true])
}

def checkMotion() {
    log.debug "In checkMotion scheduled method"
    def motionState = themotion.currentState("motion")

	log.debug motionState.value
    if (motionState.value == "inactive") {
        // get the time elapsed between now and when the motion reported inactive
        def elapsed = now() - motionState.date.time
        log.debug "motion state"
        log.debug motionState.date.time
        //log.debug motionState.date
        log.debug "now"
        log.debug now()

        // elapsed time is in milliseconds, so the threshold must be converted to milliseconds too
        def threshold = 1000 * 60 * minutes
		
        log.debug "elapsed"
		log.debug elapsed
        log.debug "threshold"
        log.debug threshold
        if (elapsed >= threshold) {
            log.debug "Motion has stayed inactive long enough since last check ($elapsed ms):  turning switch off"
            int index = 0
            for(item in theswitch){
            	def lightState = item.currentState("switch")
            	log.debug "The light should be getting turned off... The state of the light is:"
                log.debug lightState.value
                if(lightState.value == "on"){
                    def lightOnElapsed = now() - lightState.date.time
                    if(lightOnElapsed >= threshold){
                    	//runIn((61 * minutes), "checkMotion", [overwrite:true])
                        use(groovy.time.TimeCategory){
                            def schedDate = new Date()
                            for(def i = 0; i < minutes; i++){
                                schedDate = schedDate + 61.seconds
                            }
                            runOnce(schedDate, checkMotion)
                        }
                    	item.off()
                        log.debug "The light should have been turned off... The state of the light is:"
                        log.debug lightState.value
                        //while (lightState.value == "on"){
                        	//log.debug "The light didn't turn off like it should have, attempting to turn off again..."
                        	//item.off()
                        //}
                    }
                }
                //else {
                //	item.off()
                //}
                index++
            }
            //theswitch.off()
        } else {
            //log.debug "Motion has not stayed inactive long enough since last check ($elapsed ms):  doing nothing"
            //log.debug "There is no other scheduled task to check the motion... scheduling check motion..."
            //runIn(61 * minutes, checkMotion)
        }
    } else {
        // Motion active; just log it and do nothing
        //log.debug "Motion is active, do nothing and wait for inactive"
    }
    //atomicState.isTurningOff = isTurningOff
}

That one will be fine… Well at least until the IDE is retired. As far a we’re hearing that wont come until sometime later next year.

Meanwhile study the new deveper docs to see how new SmartApps can work and see about porting yoir code beforehand.

1 Like

That’s a relief to hear! I’m fine with the rewriting it when the IDE is retired as long as there is some way of replicating my existing functionality.

Thanks for your insight!

I completed the migration and am happy to confirm that this answer was correct as expected. Everything works as always.

1 Like