Sunrise and sunset calculation not working


(Brian B) #1

I wrote a function a while back that would turn my outside lights on when I arrived home if it was after sunset and before sunrise. About two weeks ago, this app stopped working correctly, and the lights now come on regardless of the time. Has anyone else experienced problems recently with the sunrise and sunset calculations?


(Aaron) #2

I wrestled with this for an app I am working on. I think I have it working well after some iteration. If you post your code I will take a look and see if I notice any of the issues I encountered.

EDIT: here is the code I used to determine if the sun is down at the time the code runs:

def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: state.sunriseOffset, sunsetOffset: state.sunsetOffset)
    def now = new Date()
    def riseTime = s.sunrise
    def setTime = s.sunset
    if(setTime.before(now) || riseTime.after(now)) {   //before midnight/after sunset or after midnight/before sunset
	  	log.info "Sun is down" }

(Gary D) #3

I haven’t noticed any problems with my apps… I’ve included the code I use for detecting if “now” is between sunrise and sunset…

First, a function for setting the sunrise/sunset times in the smartapp “state.” This code also prevents the computation from being done more than once every 12 hours (unless it’s forced.) This was done on the assumption that getSunriseAndSunset() has significant overhead (which might be a bad assumption.) ALL time work is done using UTC instead of playing games with varying time zones.

[code]
def retrieveSunData(forceIt)
{
if ((true == forceIt) || (now() > state.nextSunCheck))
{
state.nextSunCheck = now() + (1000 * (60 * 60 *12)) // every 12 hours
log.debug “Updating sunrise/sunset data”

/* instead of absolute timedate stamps for sunrise and sunset, use just hours/minutes.	The reason
   is that if we miss updating the sunrise/sunset data for a day or two, at least the times will be
   within a few minutes */


	def sunData = getSunriseAndSunset()

	state.sunsetTime = sunData.sunset.hours + ':' + sunData.sunset.minutes
	state.sunriseTime = sunData.sunrise.hours + ':' + sunData.sunrise.minutes

	log.debug "Sunrise time: ${state.sunriseTime} UTC"
	log.debug "Sunset time: ${state.sunsetTime} UTC"
}

}[/code]

… and this is how that function (and the state variables it creates) is used:

	def timeNow = now()
	def tz = TimeZone.getTimeZone("UTC")

	// possibly update the sunrise/sunset data. (don't force the update)
	retrieveSunData(false)
    	def bIsValidTime = ((now() < timeToday(state.sunriseTime, tz).time) || (now() > timeToday(state.sunsetTime, tz).time))

	// if bIsValidTime then it's currently between sunset and sunrise.

#4

There has been some recent discussion that the sunset trigger had stopped working in some cities, possibly due to too much ST cloud traffic all at the same time.

If the code used to work great and suddenly stopped working, the first thing I’d do is put in a support ticket and see if there’s a known problem in your area.


(Brian B) #5

Here is the entirety of my code. It worked fine for months: turning lights on when someone arrived home or when an exterior door was opened (someone leaving). And that all still happens, but now the lights come on even if I arrive home in the middle of the day. I’ll also look at the code you and others posted here to see what differences may exist.

/**
 *  External Entry Light Control
 *
 *  Copyright 2014 Brian Battaglia
 *
 *  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: "External Entry Light Control",
    namespace: "woobles",
    author: "Brian Battaglia",
    description: "Turn on lights (if it is dark) if someone comes home or the front door opens and turn them off if any person leaves or after a specified time. ",
    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")


preferences {
    section("Control these lights..."){
        input "lights", "capability.switch", multiple: true
    }
    section("When these doors open..."){
        input "contact1", "capability.contactSensor", title: "Where?", multiple: true
    }
    section("Or when I arrive and leave..."){
        input "presence1", "capability.presenceSensor", title: "Who?", multiple: true
    }
    section("And then off when it's light or after..."){
        input "delayMinutes", "number", title: "Minutes?"
    }
    section("Using either on this light sensor (optional) or the local sunrise and sunset"){
        input "lightSensor", "capability.illuminanceMeasurement", 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"]
    }
    section ("Zip code (optional, defaults to location coordinates when location services are enabled)...") {
        input "zipCode", "text", title: "Zip code", required: false
    }
}

def installed() {
    initialize()
}

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

def initialize() {
    subscribe(contact1, "contact.open", contactOpenHandler)
    subscribe(presence1, "presence", presenceHandler)
    subscribe(lights, "switch", switchChange)
    
    if (lightSensor) {
        subscribe(lightSensor, "illuminance", illuminanceHandler, [filterEvents: false])
    }
    else {
        subscribe(location, "position", locationPositionChange)
        subscribe(location, "sunriseTime", sunriseSunsetTimeHandler)
        subscribe(location, "sunsetTime", sunriseSunsetTimeHandler)
        astroCheck()
    }
}



def switchChange(evt) {
    log.debug "SwitchChange: $evt.name: $evt.value"
    
    if(evt.value == "on") {
        def delayTime = 60 * delayMinutes
        runIn(delayTime, turnOffLights)
    } else {
    }

}


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

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

def contactOpenHandler(evt) {
    log.debug "$evt.name: $evt.value"
    if (evt.value == "open") {
        if (enabled()) {
            log.debug "turning on lights due to door opening"
            lights.on()
            state.lastStatus = "on"
            def delayTime = 60 * delayMinutes
            runIn(delayTime, turnOffLights)
        }
    }
}

def presenceHandler(evt) {
    if (evt.value == "present") {
        log.debug "${presence1.label ?: presence1.name} has arrived at the ${location}"
        if (enabled()) {
            log.debug "turning on lights due to someone arriving"
            lights.on()
            state.lastStatus = "on"
            def delayTime = 60 * delayMinutes
            runIn(delayTime, turnOffLights)
        }
    } else if (evt.value == "not present") {
        log.debug "${presence1.label ?: presence1.name} has left the ${location}"
        log.debug "turning off lights due to someone leaving"
        lights.off()
        state.lastStatus = "off"

    }
}

def turnOffLights() {
    log.debug "turning off lights due to time out"
    lights.off()
    state.lastStatus = "off"
}

def illuminanceHandler(evt) {
    log.debug "$evt.name: $evt.value, lastStatus: $state.lastStatus, motionStopTime: $state.motionStopTime"
    def lastStatus = state.lastStatus
    if (lastStatus != "off" && evt.integerValue > 50) {
        lights.off()
        state.lastStatus = "off"
    }
    else if (state.motionStopTime) {
        if (lastStatus != "off") {
            def elapsed = now() - state.motionStopTime
            if (elapsed >= (delayMinutes ?: 0) * 60000L) {
                lights.off()
                state.lastStatus = "off"
            }
        }
    }
    else if (lastStatus != "on" && evt.value < 30){
        lights.on()
        state.lastStatus = "on"
    }
}


def scheduleCheck() {
    log.debug "In scheduleCheck - skipping"
    //turnOffMotionAfterDelay()
}

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)"
}

private enabled() {
    def result
    if (lightSensor) {
        result = lightSensor.currentIlluminance < 30
    }
    else {
        def t = now()
        result = t < state.riseTime || t > state.setTime
    }
    result
}

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

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

(Brian B) #6

I put in a support ticket before posting here thinking that it wasn’t a software issue and was instead a system issue. They ultimately said I should come to the forum since my question is about a custom app.


#7

Ok, set up a new Hello Home Action that does nothing but turn on one light as a sunset test. No custom code. So you create a new Home Home Action, have it turn on one light, and use the Additional Settings to Automatically initiate the action at Sunset.

If that Hello Home Action triggers OK and your custom SmartApp doesn’t, it’s your code.

If the Hello Home Action doesn’t trigger, go back to support.


(Aaron) #8

I didn’t see anything that jumped out at me (but I’m no expert) . . . .

One thought: subscribe to sunrise and sunset instead of sunriseTime and sunsetTime. According to @matthewnohr on this thread, a sunriseTime event won’t be posted if tomorrow’s is the same time as today’s (rounded to the nearest minute, I assume). This could explain a failure on a particular day but not every day. Also, you don’t need the payload that sunriseTime and sunsetTime bring.


(Rob DeMillo) #9

It is just apps that we are rolling on our own, it’s the sunset / sunrise mechanism in smartthings. Around the same time frame the original poster stated, by out-of-the-box sunrise / sunset timers just stopped functioning. I had to move to specific turn on / off times.


#10

I think you meant “NOT just apps that we are rolling on our own,” right?


(Rob DeMillo) #11

Yep - thanks JD, I wrote that too quickly.

At any rate, this problem went away with the last release.


(April Wong) #12

Yay! Glad to hear that.