Outdoor Lights - turn on dimmed at dusk, full brightness for X minutes when motion detected

Super! Thanks @madtnotn

I searched for and did not find guidelines for best practices for how to handle code collaboration in the ST community. For now, I’m just going to wait for you to post your edited code before I do anything.

I’m thinking about limits for dim level since I know there can be issues with values that are too low. Maybe just a list: 20, 35, 50, 75

I had not considered an adjustment for bright level . . . just assumed you would always want 100%.

I was also thinking about a default configuration and hiding some adjustment behind ‘advanced settings’. I think I saw a SmartApp with a button for advanced settings and revealed some additional options.

Anyway . . .thanks! Looking forward to seeing what you add.

I tried a couple of different version, but below is the one that I have been using. Changing some of the variable such as the number to pre-set values caused this to stop working. My level of experience with coding is pretty low so I went back to what worked. I’m sure there are other ways to make it work. Change the header info a bit as I had made other changes that I backed out.

The bright level is something I find useful as I don’t want the light blasting at 100% for all motion or while I test. For me a subtle change is enough to let someone know, they have been seen and the “house” is reacting.

/**

  • Dusk-to-Dawn Motion-Dimming Light
  • Copyright 2014 Michael Madsen
  • 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: “Dusk-to-Dawn Light”,
namespace: “”,
author: “Michael Madsen”,
description: “Dusk to dawn light with dim/bright motion sensing. Based on work by pstaurt and AaronZon.”,
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”)

preferences {
section(“Select Motion Sensor(s) you want to Use”) {
input “motions”, “capability.motionSensor”, title: “Motions”, required: false, multiple: false
}
section(“Select Dimmers you want to Use”) {
input “switches”, “capability.switchLevel”, title: “Switches”, required: false, multiple: true
}
section (“Dim Level?”) {
input “dnumber”, “number”, required: true
}
section (“Bright Level?”) {
input “bnumber”, “number”, required: true
}
section (“Delay in Seconds?”) {
input “delay”, “number”, required: true
}
section (“Zip code”) {
input “zipCode”, “text”, required: true
}
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”]
}
}
def installed() {
log.debug “Installed with settings: ${settings}”

initialize()
}

def updated() {
log.debug “Updated with settings: ${settings}”

unsubscribe()
//unschedule handled in astroCheck method
initialize()
}

def initialize() {
subscribe(location, “position”, locationPositionChange)
subscribe(location, “sunriseTime”, sunriseSunsetTimeHandler)
subscribe(location, “sunsetTime”, sunriseSunsetTimeHandler)
subscribe(motions, “motion.active”, handleMotionEvent)
initialSunPosition()
astroCheck()
}

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

def sunriseSunsetTimeHandler(evt) {
log.trace “sunriseSunsetTimeHandler()”
astroCheck()
}

def initialSunPosition() {
//Determine if sun is down at time of initializtion and run sunsetHandler() if so

def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: sunriseOffset, sunsetOffset: sunsetOffset)
def now = new Date()
def riseTime = s.sunrise
def setTime = s.sunset
log.debug “riseTime: $riseTime”
log.debug “setTime: $setTime”
log.debug “Now: $now”

	if(setTime.before(now)) {
      sunsetHandler()
        log.info "Sun is already down, run sunsetHandler"
  }
  else 
    {	if (riseTime.after(now)) {
    	sunsetHandler()
        log.info "Sun is already down, run sunsetHandler"
    	}
  	} 

}

def astroCheck() {
//query sunset and sunrise times with offsets applied, schedule handlers for sun events
//this method lifted from Sunrise/Sunset with some mods and error corrections

def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: sunriseOffset, sunsetOffset: sunsetOffset)
def now = new Date()
def riseTime = s.sunrise
def setTime = s.sunset
log.debug “riseTime: $riseTime”
log.debug “setTime: $setTime”
log.debug “Now: $now”

if (state.riseTime != riseTime.time) {
state.riseTime = riseTime.time

  unschedule("sunriseHandler")
  if(riseTime.before(now)) {
  	state.riseTime = riseTime.next()
  }
    log.info "scheduling sunrise handler for $state.riseTime"  
    //todo: resolve issue with date formatted as Epoch sometimes in log
  schedule(state.riseTime, sunriseHandler)

}

if (state.setTime != setTime.time) {
state.setTime = setTime.time
unschedule(“sunsetHandler”)

  if(setTime.before(now)) {
      state.setTime = setTime.next()
  }
    log.info "scheduling sunset handler for $state.setTime"
    //todo: resolve issue with date formatted as Epoch sometimes in log
  schedule(state.setTime, sunsetHandler)

}
}

def sunriseHandler() {
log.info “Executing sunrise handler”
switches?.setLevel(0)
state.sunPosition = “up”
}

def sunsetHandler() {
log.info “Executing sunset handler”
switches?.setLevel(dnumber)
log.debug (dnumber)
state.sunPosition = “down”
}

def handleMotionEvent(evt) {
log.debug “Motion detected”
log.debug state

if (state.sunPosition == "down") {
	switches?.setLevel(bnumber)
    state.Level = "99"
	log.debug "set the switches to level"
    log.debug (bnumber)
    log.debug state
    runIn((delay), dimOrOffafterBright)
}
else 
{
	log.debug "but sun is up, so do nothing"
}

}

def dimOrOffafterBright() {
//Handles the case where the sun comes up during bright time

log.debug “Bright delay is complete, decide to turn off or dim based on sun position and offsets”

if (state.sunPosition == "down") {
	switches?.setLevel(dnumber) 
    state.Level = "20"
	log.debug "Return to dimmed states since sun is down"
    log.debug state
}
else 
{
	switches?.setLevel(0)
    state.Level = "0"
    log.debug "Turned off lights since sun came up during bright time"
    log.debug state
}

}

private getLabel() {
app.label ?: “SmartThings”
}

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

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

Thanks for posting your code, @madtnotn

I haven’t done anything with it yet, however.

My lights did not turn off this morning. According to the logs, they did turn off for an instant and then immediately turned back on to 20%. Not sure what happened unless there was some sort of weird timing issue.

As I’ve learned more about the newer sunrise/set methods available in the platform, I am seeing that there is probably a better way to do this. As it is, the code utilizing a mixture of the old and new sunrise/set methods . . . maybe this is causing the timing issue. It looks like there is a simpler, cleaner way to do this leveraging the newest built-in sunrise/set capabilities in ST. So, rather the beating my head against the wall trying to figure out what’s going on (it would be nice if the debug log statements from the code appeared somewhere outside IDE), I might just take a shot at re-writing. In the meantime, I will see what happens at sunset today and then again at sunrise tomorrow (since I know I won’t have time to write for at least a few days).

Since my last post, the SmartApp has worked perfectly. Maybe I’ll blame the platform for the error. when I get time, I will continue work on this.

This app is REALLY cool. Is it possible to edit the code so it dims the lights only after motion stops? I love how you can set the delay in seconds, but to be able to set the delay time amount AFTER motion stops would makes this even cooler.

@kgofswg1973 -Glad you like it! Did you try the original code or the enhanced version @madtnotn posted? Yes, I’m sure it’s possible to make the lights stay on a certain amount of time after motion stops. Not doing so was an intentional simplification. In my application, the sensor is watching the front entrance and there is really no reason to linger there so triggering only on the start of motion works for me. I can, however, see why it might be better to time the delay after motion stops. If I make a revision to do this will you (or someone) test it?

It was the last version @madtnotn posted. I’m happy to test the revision! Thanks.

OK, I had some problems today that, apparently, are just problems when simulating in the IDE. Yikes. Anyway, things went a lot better when I started working on the metal here at home. Here’s what I’ve done:

Taking cues from @madtnotn, I’ve made the dim level and bright levels adjustable. Rather than making them entries, I made them selectable from a list of what seems like (to me) reasonable options. Also, I made the delay (bright time) adjustable. I set up the input as minutes and multiplied the value x 60 in the code where I schedule the end of bright time with a runIn()

I also handled the issue of certain lights (at least, mine) that will not start at a low dim level but operate just fine there once lit. At sunset, the lights turn on a 30%, then 3 seconds later I run the handler I use for the end of the delay which sets it back to the selected dim setting. This happens only once at sunset.

What’s not done: I have not implemented @kgofswg1973 suggestion to key the delay after the motion stops. I think I know how I want to do that and will when I get a chance. Also, I would like the delay (bright time) setting to be from a list but I haven’t been able to get that to work. Some issue with data type handling but I don’t have the skills right now to really understand this.

Here is the code:

/**
 *  Dusk-to-Dawn Motion-Dimming Light - AaronZON - Dev Branch
 *
 *  Copyright 2014 Aaron Herzon
 *
 *  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: "Dusk-to-Dawn Light - AaronZON - Dev Branch",
    namespace: "AaronZON",
    author: "Aaron Herzon",
    description: "Dusk to dawn light with dim/bright motion sensing ",
    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")


preferences {
	section("Select Motion Sensor(s) you want to Use") {
        input "motions", "capability.motionSensor", title: "Motions", required: false, multiple: true
	}
    section("Select Dimmers you want to Use") {
        input "switches", "capability.switchLevel", title: "Switches", required: false, multiple: true
	}
    section ("Set Bright and Dim Levels") {
		input "DimLevel", "enum", title: "Dimmed Level", required: true, options: [10,15,20,30,50,75]
        input "BrightLevel", "enum", title: "Bright Level", required: true, options: [100,75,50]
        input "DelaySec", "number", title: "Bright time, minutes", required: true  //todo: figure out how to make this a selectable list
        	}
    section ("Zip code (optional, defaults to location coordinates)...") {
		input "zipCode", "text", required: false
	}
    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"]
	}
}
def installed() {
	log.debug "Installed with settings: ${settings} DelayMin: $DelayMin"

	initialize()
}

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

	unsubscribe()
	//unschedule handled in astroCheck method
	initialize()
}

def initialize() {
	subscribe(location, "position", locationPositionChange)
	subscribe(location, "sunriseTime", sunriseSunsetTimeHandler)
	subscribe(location, "sunsetTime", sunriseSunsetTimeHandler)
    subscribe(motions, "motion.active", handleMotionEvent)
	initialSunPosition()
	astroCheck()
}

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

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

def initialSunPosition() {  
	//Determine if sun is down at time of initializtion and run sunsetHandler() if so
    
	def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: sunriseOffset, sunsetOffset: sunsetOffset)
	def now = new Date()
    def riseTime = s.sunrise
	def setTime = s.sunset
	log.debug "riseTime: $riseTime"
    log.debug "setTime: $setTime"
    log.debug "Now: $now"
	
    	if(setTime.before(now)) {
	        sunsetHandler()
            log.info "Sun is already down, run sunsetHandler"
	    }
	    else 
        {	if (riseTime.after(now)) {
        	sunsetHandler()
            log.info "Sun is already down, run sunsetHandler"
        	}
      	} 
}

def astroCheck() {
	//query sunset and sunrise times with offsets applied, schedule handlers for sun events
    //this method lifted from Sunrise/Sunset with some mods and error corrections
    
	def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: sunriseOffset, sunsetOffset: sunsetOffset)
	def now = new Date()
	def riseTime = s.sunrise
	def setTime = s.sunset
	log.debug "riseTime: $riseTime"
	log.debug "setTime: $setTime"
    log.debug "Now: $now"
	
	if (state.riseTime != riseTime.time) {
		state.riseTime = riseTime.time
		
		unschedule("sunriseHandler")
		if(riseTime.before(now)) {
			state.riseTime = riseTime.next()
		}
		
        log.info "scheduling sunrise handler for $state.riseTime"
        //todo: resolve issue with date formated as Epoch sometimes in log
		schedule(state.riseTime, sunriseHandler)
	}
   
	if (state.setTime != setTime.time) {
		state.setTime = setTime.time
		unschedule("sunsetHandler")

	    if(setTime.before(now)) {
	        state.setTime = setTime.next()
	    }
	
        log.info "scheduling sunset handler for $state.setTime"
        //todo: resolve issue with date formated as Epoch sometimes in log
	    schedule(state.setTime, sunsetHandler)
	}
}

def sunriseHandler() {
	log.info "Executing sunrise handler"
    switches?.setLevel(0)
    state.sunPosition = "up"
}

def sunsetHandler() {
	log.info "Executing sunset handler"
	switches?.setLevel(30)  //starts at 30 (arbitrary) to make sure lights start (my LEDs won't start at below 20% but run fine)
    state.sunPosition = "down"
    runIn(3, dimOrOffafterBright)  //after initial light-off, runs handler to set to selected dim level after 3 seconds
}


def handleMotionEvent(evt) {
	log.debug "Motion detected . . . ."
    log.debug state
    
    if (state.sunPosition == "down") {
    	switches?.setLevel(BrightLevel)
        state.Level = BrightLevel
    	log.debug ". . . set the switches to level $BrightLevel"
        runIn((DelaySec*60), dimOrOffafterBright)  //delay is number of minutes entered in preferences x 60 to get seconds
    }
    else 
    {
    	log.debug ". . . but sun is up, so do nothing"
        log.debug state
    }
}

def dimOrOffafterBright() {   
	//Handles the case where the sun comes up during bright time
    
	log.debug "Bright delay is complete, decide to turn off or dim based on sun position and offsets"
       
    if (state.sunPosition == "down") {
    	switches?.setLevel(DimLevel)
        state.Level = DimLevel
    	log.debug "Return to dimmed states since sun is down"
        log.debug state
    }
    else 
    {
    	switches?.setLevel(0)
        state.Level = 0
        log.debug "Turned off lights since sun came up during bright time"
        log.debug state
    }
}

private getLabel() {
	app.label ?: "SmartThings"
}

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

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

OK @kgofswg1973, I created a version with your suggestion. This version keys the delay off the ‘inactive’ motion event (motion ends) rather than the ‘active’ event, as suggested. I think this is less efficient since I am now subscribed to both ‘active’ and ‘inactive’ motion events and running handlers for both. Maybe not a big deal in the whole scheme of things. I have this running at my house right now after a cursory test in the IDE.

What’s not done:

  • Make the delay time input a selection from a list rather than a type-in field.
  • Revise the code to leverage the newer sunriseTime and sunsetTime events built into ST

The code:

/**
 *  Dusk-to-Dawn Motion/Dimming Light - AaronZON - Dev Branch V2
 *
 *  Copyright 2014 Aaron Herzon
 *
 *  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: "Dusk-to-Dawn Motion/Dimming Light - AaronZON - Dev Branch V2",
    namespace: "AaronZON",
    author: "Aaron Herzon",
    description: "Dusk to dawn light with dim/bright motion sensing ",
    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")


preferences {
	section("Select Motion Sensor(s) you want to Use") {
        input "motions", "capability.motionSensor", title: "Motions", required: false, multiple: true
	}
    section("Select Dimmers you want to Use") {
        input "switches", "capability.switchLevel", title: "Switches", required: false, multiple: true
	}
    section ("Set Bright and Dim Levels") {
		input "DimLevel", "enum", title: "Dimmed Level", required: true, options: [10,15,20,30,50,75]
        input "BrightLevel", "enum", title: "Bright Level", required: true, options: [100,75,50]
        input "DelaySec", "number", title: "Bright time, minutes", required: true  //todo: figure out how to make this a selectable list
        	}
    section ("Zip code (optional, defaults to location coordinates)...") {
		input "zipCode", "text", required: false
	}
    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"]
	}
}
def installed() {
	log.debug "Installed with settings: ${settings} DelayMin: $DelayMin"

	initialize()
}

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

	unsubscribe()
	//unschedule handled in astroCheck method
	initialize()
}

def initialize() {
	subscribe(location, "position", locationPositionChange)
	subscribe(location, "sunriseTime", sunriseSunsetTimeHandler)
	subscribe(location, "sunsetTime", sunriseSunsetTimeHandler)
    subscribe(motions, "motion.active", handleMotionEvent)
    subscribe(motions, "motion.inactive", handleEndMotionEvent)
	initialSunPosition()
	astroCheck()
}

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

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

def initialSunPosition() {  
	//Determine if sun is down at time of initializtion and run sunsetHandler() if so
    
	def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: sunriseOffset, sunsetOffset: sunsetOffset)
	def now = new Date()
    def riseTime = s.sunrise
	def setTime = s.sunset
	log.debug "riseTime: $riseTime"
    log.debug "setTime: $setTime"
    log.debug "Now: $now"
	
    	if(setTime.before(now)) {
	        sunsetHandler()
            log.info "Sun is already down, run sunsetHandler"
	    }
	    else 
        {	if (riseTime.after(now)) {
        	sunsetHandler()
            log.info "Sun is already down, run sunsetHandler"
        	}
      	} 
}

def astroCheck() {
	//query sunset and sunrise times with offsets applied, schedule handlers for sun events
    //this method lifted from Sunrise/Sunset with some mods and error corrections
    
	def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: sunriseOffset, sunsetOffset: sunsetOffset)
	def now = new Date()
	def riseTime = s.sunrise
	def setTime = s.sunset
	log.debug "riseTime: $riseTime"
	log.debug "setTime: $setTime"
    log.debug "Now: $now"
	
	if (state.riseTime != riseTime.time) {
		state.riseTime = riseTime.time
		
		unschedule("sunriseHandler")
		if(riseTime.before(now)) {
			state.riseTime = riseTime.next()
		}
		
        log.info "scheduling sunrise handler for $state.riseTime"
        //todo: resolve issue with date formated as Epoch sometimes in log
		schedule(state.riseTime, sunriseHandler)
	}
   
	if (state.setTime != setTime.time) {
		state.setTime = setTime.time
		unschedule("sunsetHandler")

	    if(setTime.before(now)) {
	        state.setTime = setTime.next()
	    }
	
        log.info "scheduling sunset handler for $state.setTime"
        //todo: resolve issue with date formated as Epoch sometimes in log
	    schedule(state.setTime, sunsetHandler)
	}
}

def sunriseHandler() {
	log.info "Executing sunrise handler"
    switches?.setLevel(0)
    state.sunPosition = "up"
}

def sunsetHandler() {
	log.info "Executing sunset handler"
	switches?.setLevel(30)  //starts at 30 (arbitrary) to make sure lights start (my LEDs won't start at below 20% but run fine)
    state.sunPosition = "down"
    runIn(3, dimOrOffafterBright)  //after initial light-off, runs handler to set to selected dim level after 3 seconds
}


def handleMotionEvent(evt) {
	log.debug "Motion detected . . . ."
    log.debug state
    
    if (state.sunPosition == "down") {
    	switches?.setLevel(BrightLevel)
        state.Level = BrightLevel
    	log.debug ". . . set the dimmers to level $BrightLevel"
    }
    else 
    {
    	log.debug ". . . but sun is up, so do nothing"
        log.debug state
    }
}

def handleEndMotionEvent(evt) {
	log.debug "Motion stopped . . . ."
    log.debug state
    
    if (state.sunPosition == "down") {
    	switches?.setLevel(BrightLevel)  //does nothing unless sun went down during active motion
        state.Level = BrightLevel
    	log.debug ". . . set the dimmers to level $BrightLevel if not already there"
        runIn((DelaySec*60), dimOrOffafterBright)  //delay is number of minutes entered in preferences x 60 to get seconds
    }
    else 
    {
    	log.debug ". . . but sun is up, so do nothing"
        log.debug state
    }
}

def dimOrOffafterBright() {   
	//Handles the case where the sun comes up during bright time
    
	log.debug "Bright delay is complete, decide to turn off or dim based on sun position and offsets"
       
    if (state.sunPosition == "down") {
    	switches?.setLevel(DimLevel)
        state.Level = DimLevel
    	log.debug "Return to dimmed states since sun is down"
        log.debug state
    }
    else 
    {
    	switches?.setLevel(0)
        state.Level = 0
        log.debug "Turned off lights since sun came up during bright time"
        log.debug state
    }
}

private getLabel() {
	app.label ?: "SmartThings"
}

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

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

@AaronZON, the DIMMED and BRIGHT level fields are giving me this strange page that doesn’t allow me to select anything or input a value.

Weird. iOS?
This is what I get on my Samsung Phone:

I have a 4S at home. I can try to reproduce.

For now, you can change:

section (“Set Bright and Dim Levels”) {
input “DimLevel”, “enum”, title: “Dimmed Level”, required: true, options: [10,15,20,30,50,75]
input “BrightLevel”, “enum”, title: “Bright Level”, required: true, options: [100,75,50]

to:

section (“Set Bright and Dim Levels”) {
input “DimLevel”, “number”, title: “Dimmed Level”, required: true
input “BrightLevel”, “number”, title: “Bright Level”, required: true

, , , which should just change these into normal numeric entry fields. I like the list selection since it eliminates out-of-range entries.

Yeah, iOS. Your edit fixed the issue and it works PERFECTLY! I can’t thank you enough for making these revisions!

OK. Super. Let me know if you see any weird behavior. Thanks for trying this and for your suggestions.

If I can reproduce the bug you found in the iOS app, I will report it.

I was able to reproduce this issue on a 4s running iOS 8 with fresh ST installation. Seems like a bug. I will try to report.

So, I reported this late last night and received 2 responses this morning. Way to go, ST support! Charlie in ST support confirms that this might be a bug in the iOS app and suggested that, as a work-around, I take the values in as strings and convert to ints. I am a few weeks into my coding career and don’t know how to do this yet :slight_smile: I’m still very confused about how data types are handled. It would be fantastic if someone would suggest the code to make this change. Also, I’m still looking for that ‘for Dummies’ reference that would have this and other basic concepts concisely explained.

In the meantime, the edit I suggested to @kgofswg1973 seems to be working for him, so, if you want to use this and are on iOS you can use my latest code and make those edits (look a few post up for those edits) and you should be good.

To change your input to use strings you’d just have to do options: ["10","15","20","30","50","75"] and then to convert that back to an int you can just do DimLevel as int. SmartApps are written in the groovy language so when in doubt take a look at their docs. The conversion back to an int might not be necessary, because groovy might just do it for you.

I’ve taken the liberty of doing the conversion for you, but haven’t tested it (I figured I’d let you do it since you know the intricacies of your SmartApp).

    /**
 *  Dusk-to-Dawn Motion/Dimming Light - AaronZON - Dev Branch V2
 *
 *  Copyright 2014 Aaron Herzon
 *
 *  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: "Dusk-to-Dawn Motion/Dimming Light - AaronZON - Dev Branch V2",
    namespace: "MikeDave",
    author: "Aaron Herzon",
    description: "Dusk to dawn light with dim/bright motion sensing ",
    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")


preferences {
  section("Select Motion Sensor(s) you want to Use") {
        input "motions", "capability.motionSensor", title: "Motions", required: false, multiple: true
  }
  section("Select Dimmers you want to Use") {
        input "switches", "capability.switchLevel", title: "Switches", required: false, multiple: true
  }
  section ("Set Bright and Dim Levels") {
    input "DimLevel", "enum", title: "Dimmed Level", required: true, options: ["10","15","20","30","50","75"]
    input "BrightLevel", "enum", title: "Bright Level", required: true, options: ["100","75","50"]
    input "DelaySec", "number", title: "Bright time, minutes", required: true  //todo: figure out how to make this a selectable list
  }
  section ("Zip code (optional, defaults to location coordinates)...") {
    input "zipCode", "text", required: false
  }
  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"]
  }
}
def installed() {
  log.debug "Installed with settings: ${settings} DelayMin: $DelayMin"

  initialize()
}

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

  unsubscribe()
  //unschedule handled in astroCheck method
  initialize()
}

def initialize() {
  subscribe(location, "position", locationPositionChange)
  subscribe(location, "sunriseTime", sunriseSunsetTimeHandler)
  subscribe(location, "sunsetTime", sunriseSunsetTimeHandler)
  subscribe(motions, "motion.active", handleMotionEvent)
  subscribe(motions, "motion.inactive", handleEndMotionEvent)
  initialSunPosition()
  astroCheck()
}

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

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

def initialSunPosition() {  
  //Determine if sun is down at time of initializtion and run sunsetHandler() if so

  def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: sunriseOffset, sunsetOffset: sunsetOffset)
  def now = new Date()
  def riseTime = s.sunrise
  def setTime = s.sunset
  log.debug "riseTime: $riseTime"
  log.debug "setTime: $setTime"
  log.debug "Now: $now"

  if(setTime.before(now)) {
    sunsetHandler()
    log.info "Sun is already down, run sunsetHandler"
  } else if (riseTime.after(now)) {
    sunsetHandler()
    log.info "Sun is already down, run sunsetHandler"
  } 
}

def astroCheck() {
  //query sunset and sunrise times with offsets applied, schedule handlers for sun events
    //this method lifted from Sunrise/Sunset with some mods and error corrections

  def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: sunriseOffset, sunsetOffset: sunsetOffset)
  def now = new Date()
  def riseTime = s.sunrise
  def setTime = s.sunset
  log.debug "riseTime: $riseTime"
  log.debug "setTime: $setTime"
  log.debug "Now: $now"

  if (state.riseTime != riseTime.time) {
    state.riseTime = riseTime.time

    unschedule("sunriseHandler")
    if(riseTime.before(now)) {
      state.riseTime = riseTime.next()
    }

    log.info "scheduling sunrise handler for $state.riseTime"
    //todo: resolve issue with date formated as Epoch sometimes in log
    schedule(state.riseTime, sunriseHandler)
  }

  if (state.setTime != setTime.time) {
    state.setTime = setTime.time
    unschedule("sunsetHandler")

    if(setTime.before(now)) {
      state.setTime = setTime.next()
    }

    log.info "scheduling sunset handler for $state.setTime"
    //todo: resolve issue with date formated as Epoch sometimes in log
    schedule(state.setTime, sunsetHandler)
  }
}

def sunriseHandler() {
    log.info "Executing sunrise handler"
    switches?.setLevel(0)
    state.sunPosition = "up"
}

def sunsetHandler() {
  log.info "Executing sunset handler"
  switches?.setLevel(30)  //starts at 30 (arbitrary) to make sure lights start (my LEDs won't start at below 20% but run fine)
  state.sunPosition = "down"
  runIn(3, dimOrOffafterBright)  //after initial light-off, runs handler to set to selected dim level after 3 seconds
}


def handleMotionEvent(evt) {
    log.debug "Motion detected . . . ."
    log.debug state

    if (state.sunPosition == "down") {
      int newLevel = BrightLevel as int
      switches?.setLevel(newLevel)
      state.Level = newLevel
      log.debug ". . . set the dimmers to level $newLevel"
    }
    else 
    {
      log.debug ". . . but sun is up, so do nothing"
      log.debug state
    }
}

def handleEndMotionEvent(evt) {
  log.debug "Motion stopped . . . ."
    log.debug state

    if (state.sunPosition == "down") {
      int newLevel = BrightLevel as int
      switches?.setLevel(newLevel)  //does nothing unless sun went down during active motion
      state.Level = newLevel
      log.debug ". . . set the dimmers to level $newLevel if not already there"
      runIn((DelaySec*60), dimOrOffafterBright)  //delay is number of minutes entered in preferences x 60 to get seconds
    }
    else 
    {
      log.debug ". . . but sun is up, so do nothing"
      log.debug state
    }
}

def dimOrOffafterBright() {   
  //Handles the case where the sun comes up during bright time

  log.debug "Bright delay is complete, decide to turn off or dim based on sun position and offsets"

    if (state.sunPosition == "down") {
      int newLevel = DimLevel as int
      switches?.setLevel(newLevel)
      state.Level = newLevel
      log.debug "Return to dimmed states since sun is down"
      log.debug state
    }
    else 
    {
      switches?.setLevel(0)
      state.Level = 0
      log.debug "Turned off lights since sun came up during bright time"
      log.debug state
    }
}

private getLabel() {
  app.label ?: "SmartThings"
}

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

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

Much appreciated @steve_vlaminck

i was doing just this when I saw the notification of your post and was right at the section describing ‘as Integer’. I also found the toInteger() method. I’m running out of time today but when I get a chance, I think I’m going to try to do the conversion right in the preferences section. That way, it only runs once rather than every time I set the lights. So, maybe I will input DimLevelSTR as a string, then:

int DimLevel = DimLevelSTR.toInteger()

and put that right after the input statement in the preferences handler.

I’m pretty sure I tried this and it didn’t work. It’s cool that Groovy just takes care of certain things but frustrating not to necessarily know exactly what Groovy will handle and what will need to be handled in the code.

Thanks again

EDIT: Looks like ST doesn’t like other code in the preferences section. Looks like putting it in the initialize section will work.

EDIT: I started a new thread proposing a ‘final’ version of this. Link

There’s got to be a more efficient way to do this. . . . well, anyway, here is the latest code that should work around the iOS bug. I also applied what I learned from this to make the selection of the delay time from a list instead of a numeric entry.

The code. Version 3:

/**
 *  Dusk-to-Dawn Motion/Dimming Light - AaronZON - Dev Branch V3
 *
 *  Copyright 2014 Aaron Herzon
 *
 *  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: "Dusk-to-Dawn Motion/Dimming Light - AaronZON - Dev Branch V3",
    namespace: "AaronZON",
    author: "Aaron Herzon",
    description: "Dusk to dawn light with dim/bright motion sensing ",
    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")


preferences {
	section("Select Motion Sensor(s) you want to Use") {
        input "motions", "capability.motionSensor", title: "Motions", required: false, multiple: true
	}
    section("Select Dimmers you want to Use") {
        input "switches", "capability.switchLevel", title: "Switches", required: false, multiple: true
	}
    section ("Set Bright and Dim Levels") {
		input "DimLevelStr", "enum", title: "Dimmed Level", required: true, options: ["10","15","20","30","50","75"]
        
        input "BrightLevelStr", "enum", title: "Bright Level", required: true, options: ["100","75","50"]
        
        input "DelayMinStr", "enum", title: "Bright time, minutes", required: true, options: ["1","3","5","10","15","30","60"]
        	}
    section ("Zip code (optional, defaults to location coordinates)...") {
		input "zipCode", "text", required: false
	}
    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"]
	}
}
def installed() {
	log.debug "Installed with settings: ${settings} DelayMin: $DelayMin"

	initialize()
}

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

	unsubscribe()
	//unschedule handled in astroCheck method
	initialize()
}

def initialize() {
	state.DimLevel = DimLevelStr as Integer
    state.BrightLevel = BrightLevelStr as Integer
    state.DelayMin = DelayMinStr as Integer
    
    subscribe(location, "position", locationPositionChange)
	subscribe(location, "sunriseTime", sunriseSunsetTimeHandler)
	subscribe(location, "sunsetTime", sunriseSunsetTimeHandler)
    subscribe(motions, "motion.active", handleMotionEvent)
    subscribe(motions, "motion.inactive", handleEndMotionEvent)
	initialSunPosition()
	astroCheck()
}

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

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

def initialSunPosition() {  
	//Determine if sun is down at time of initializtion and run sunsetHandler() if so
    
	def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: sunriseOffset, sunsetOffset: sunsetOffset)
	def now = new Date()
    def riseTime = s.sunrise
	def setTime = s.sunset
	log.debug "riseTime: $riseTime"
    log.debug "setTime: $setTime"
    log.debug "Now: $now"
	
    	if(setTime.before(now)) {
	        sunsetHandler()
            log.info "Sun is already down, run sunsetHandler"
	    }
	    else 
        {	if (riseTime.after(now)) {
        	sunsetHandler()
            log.info "Sun is already down, run sunsetHandler"
        	}
      	} 
}

def astroCheck() {
	//query sunset and sunrise times with offsets applied, schedule handlers for sun events
    //this method lifted from Sunrise/Sunset with some mods and error corrections
    
	def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: sunriseOffset, sunsetOffset: sunsetOffset)
	def now = new Date()
	def riseTime = s.sunrise
	def setTime = s.sunset
	log.debug "riseTime: $riseTime"
	log.debug "setTime: $setTime"
    log.debug "Now: $now"
	
	if (state.riseTime != riseTime.time) {
		state.riseTime = riseTime.time
		
		unschedule("sunriseHandler")
		if(riseTime.before(now)) {
			state.riseTime = riseTime.next()
		}
		
        log.info "scheduling sunrise handler for $state.riseTime"
        //todo: resolve issue with date formated as Epoch sometimes in log
		schedule(state.riseTime, sunriseHandler)
	}
   
	if (state.setTime != setTime.time) {
		state.setTime = setTime.time
		unschedule("sunsetHandler")

	    if(setTime.before(now)) {
	        state.setTime = setTime.next()
	    }
	
        log.info "scheduling sunset handler for $state.setTime"
        //todo: resolve issue with date formated as Epoch sometimes in log
	    schedule(state.setTime, sunsetHandler)
	}
}

def sunriseHandler() {
	log.info "Executing sunrise handler"
    switches?.setLevel(0)
    state.sunPosition = "up"
}

def sunsetHandler() {
	log.info "Executing sunset handler"
	switches?.setLevel(30)  //starts at 30 (arbitrary) to make sure lights start (my LEDs won't start at below 20% but run fine)
    state.sunPosition = "down"
    runIn(3, dimOrOffafterBright)  //after initial light-off, runs handler to set to selected dim level after 3 seconds
}


def handleMotionEvent(evt) {
	log.debug "Motion detected . . . ."
    
    if (state.sunPosition == "down") {
    	switches?.setLevel(state.BrightLevel)
        state.Level = state.BrightLevel
    	log.debug ". . . set the dimmers to level $state.BrightLevel"
    }
    else 
    {
    	log.debug ". . . but sun is up, so do nothing"
        log.debug state
    }
}

def handleEndMotionEvent(evt) {
	log.debug "Motion stopped . . . ."

    if (state.sunPosition == "down") {
    	switches?.setLevel(state.BrightLevel)  //does nothing unless sun went down during active motion
        state.Level = state.BrightLevel
    	log.debug ". . . set the dimmers to level $state.BrightLevel if not already there"
        runIn((state.DelayMin*60), dimOrOffafterBright)  //delay is number of minutes entered in preferences x 60 to get seconds
    }
    else 
    {
    	log.debug ". . . but sun is up, so do nothing"
        log.debug state
    }
}

def dimOrOffafterBright() {   
	//Handles the case where the sun comes up during bright time
    
	log.debug "Bright delay is complete, decide to turn off or dim based on sun position and offsets"
       
    if (state.sunPosition == "down") {
    	switches?.setLevel(state.DimLevel)
        state.Level = state.DimLevel
    	log.debug "Return to dimmed states since sun is down"
        log.debug state
    }
    else 
    {
    	switches?.setLevel(0)
        state.Level = 0
        log.debug "Turned off lights since sun came up during bright time"
        log.debug state
    }
}

private getLabel() {
	app.label ?: "SmartThings"
}

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

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

I have been using this for a few weeks now and have had problems with it running my lights during the day. I didn’t modify any of the location settings of sunrise/sunset offsets. Any thoughts?

Hey @FileMan44. This is an old thread. Please go here for the latest a greatest.