Created
December 11, 2017 05:21
-
-
Save natdm/d4dea47c4ef1f11892e465392124c49f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
definition( | |
name: "Smarter Nightlight", | |
author: "Nathan Hyland", | |
description: "Turns on lights when it's dark and motion is detected and lights are not already on. Turns lights off when it becomes light or some time after motion ceases.", | |
category: "Convenience", | |
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/light_motion-outlet-luminance.png", | |
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/[email protected]" | |
) | |
preferences { | |
section("Control this light..."){ | |
input "lights", "capability.switchLevel", multiple: true | |
} | |
section("And dim to this level"){ | |
input "level", "number", title: "Level?" | |
} | |
section("Turning on when it's dark and there's movement..."){ | |
input "motionSensor", "capability.motionSensor", title: "Where?" | |
} | |
section("And then off when it's light or there's been no movement for..."){ | |
input "delaySeconds", "number", title: "Seconds?" | |
} | |
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(motionSensor, "motion", motionHandler) | |
if (lightSensor) { | |
subscribe(lightSensor, "illuminance", illuminanceHandler, [filterEvents: false]) | |
} | |
else { | |
subscribe(location, "position", locationPositionChange) | |
subscribe(location, "sunriseTime", sunriseSunsetTimeHandler) | |
subscribe(location, "sunsetTime", sunriseSunsetTimeHandler) | |
astroCheck() | |
} | |
} | |
def locationPositionChange(evt) { | |
log.trace "locationChange()" | |
astroCheck() | |
} | |
def sunriseSunsetTimeHandler(evt) { | |
state.lastSunriseSunsetEvent = now() | |
log.debug "SmartNightlight.sunriseSunsetTimeHandler($app.id)" | |
astroCheck() | |
} | |
def motionHandler(evt) { | |
log.debug "$evt.name: $evt.value" | |
log.debug "lights.currentLevel = ${lights.currentSwitch}" | |
if (evt.value == "active" && lights.currentSwitch[0] == "off") { | |
if (enabled()) { | |
log.debug "turning on lights due to motion" | |
lights.setLevel(level) | |
state.lastStatus = "on" | |
} | |
state.motionStopTime = null | |
} | |
else { | |
state.motionStopTime = now() | |
if(delaySeconds) { | |
runIn(delaySeconds, turnOffMotionAfterDelay, [overwrite: true]) | |
} else { | |
turnOffMotionAfterDelay() | |
} | |
} | |
} | |
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.setLevel(0) | |
state.lastStatus = "off" | |
} | |
else if (state.motionStopTime) { | |
if (lastStatus != "off") { | |
def elapsed = now() - state.motionStopTime | |
if (elapsed >= ((delaySeconds ?: 0) * 1000L) - 2000) { | |
lights.setLevel(0) | |
state.lastStatus = "off" | |
} | |
} | |
} | |
else if (lastStatus != "on" && evt.integerValue < 30 && lights.currentSwitch[0] == "off"){ | |
lights.setLevel(level) | |
state.lastStatus = "on" | |
} | |
} | |
def turnOffMotionAfterDelay() { | |
log.trace "In turnOffMotionAfterDelay, state.motionStopTime = $state.motionStopTime, state.lastStatus = $state.lastStatus" | |
if (state.motionStopTime && state.lastStatus != "off") { | |
def elapsed = now() - state.motionStopTime | |
log.trace "elapsed = $elapsed" | |
if (elapsed >= ((delaySeconds ?: 0) * 1000L) - 2000) { | |
log.debug "Turning off lights" | |
lights.setLevel(0) | |
state.lastStatus = "off" | |
} | |
} | |
} | |
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?.toInteger() < 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 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment