Hello, Home Phrase Director - Control modes (through hello home phrases) based on occupancy and "sun state"

This is an awesome app idea. I wanted this exact setup; I did it manually with 6 separate smartapps in Mode Magic, which was a hassle. An off-the-shelf solution would have been easier. Although I do use the offset.

@storageanarchy - I wrote a similar app that can use a variable number of modes. I’ve been testing it to make sure it works before I posted the code. Here’s my code:

/**
 *  Better Sunrise/Sunset
 *
 *  Copyright 2014 Eric Roberts
 *
 *  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: "Better Sunrise/Sunset",
    namespace: "baldeagle072",
    author: "Eric Roberts",
    description: "Better sunrise/sunset with more options",
    category: "Mode Magic",
    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")

//setup

preferences {
	page name:"setupInit"
	page name:"setupConfigure"
	page name:"setupModeTypes"
}

def setupInit() {
	TRACE("setupInit()")
	if (state.installed) {
		//return setupModeTypes()
        return setupConfigure()
	} else {
		return setupConfigure()
	}
}

def setupConfigure() {
	TRACE("setupConfigure()")

	def textNumOfHelp = 
		"You can switch between two modes for each type. How many types " +
		"do you have?"

	def inputNumModeTypes = [
		name: 			"numModeTypes",
		type: 			"number",
		title: 			"How many mode types?",
		defaultValue: 	"3",
		required: 		true
	]


	def pageProperties = [
		name: 			"setupConfigure",
		title: 			"Number of Mode Types",
		nextPage: 		"setupModeTypes",
		uninstall: 		true
	]

/*
	def pageProperties = [
		name: 			"setupConfigure",
		title: 			"Number of Mode Types",
        install: 		true,
		uninstall: 		false
	]
    */
	return dynamicPage(pageProperties) {
		section("Number of Mode Types") {
			paragraph textNumOfHelp
			input inputNumModeTypes
		}

		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
		}
        /*
		section( "Notifications" ) {
			input "sendPushMessage", "enum", title: "Send a push notification?", options: ["Yes", "No"], required: false
			input "phoneNumber", "phone", title: "Send a text message?", required: false
		}
        */
	}
}

def setupModeTypes() {
	TRACE("setupModeTypes()")

	def textDayHelp =
		"This mode will be active during the day"

	def textNightHelp =
		"This mode will be active at night"

	def textOnSunriseHelp =
		"These switches will turn on at sunrise"

	def textOffSunriseHelp =
		"These switches will turn off at sunrise"

	def textOnSunsetHelp =
		"These switches will turn on at sunset"

	def textOffSunsetHelp =
		"These switches will turn off at sunset"
	
	def pageProperties = [
		name: 		"setupModeTypes",
		title: 		"Configure Mode Types",
		install: 	true,
		uninstall: 	state.installed
	]

	return dynamicPage(pageProperties) {
		for (int n = 1; n <= numModeTypes; n++) {
			section("Mode Type ${n}", hideable:true, hidden:true) {
				paragraph textDayHelp
				input "m${n}_dayMode", "mode", title: "Day Mode", required: true
				paragraph textNightHelp
				input "m${n}_nightMode", "mode", title: "Night Mode", required: true
				paragraph textOnSunriseHelp
				input "m${n}_sunriseOnSwitches", "capability.switch", title: "Sunrise On Switches", multiple: true, required: false
				paragraph textOffSunriseHelp
				input "m${n}_sunriseOffSwitches", "capability.switch", title: "Sunrise Off Switches", multiple: true, required: false
				paragraph textOnSunsetHelp
				input "m${n}_sunsetOnSwitches", "capability.switch", title: "Sunset On Switches", multiple: true, required: false
				paragraph textOffSunsetHelp
				input "m${n}_sunsetOffSwitches", "capability.switch", title: "Sunset Off Switches", multiple: true, required: false
			}
		}
	}
}

// installed/updated/init

def installed() {
	log.debug "Installed with settings: ${settings}"

	initialize()
}

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

	unsubscribe()
	initialize()
}

def initialize() {
	// TODO: subscribe to attributes, devices, locations, etc.
	TRACE("initialize()")
    
    state.installed = true
    
    if (settings.zipCode == null) {
    	settings.zipCode = location.zipCode
    }
    
    state.numModeTypes = numModeTypes
    
    state.sunriseArray = []
	state.sunsetArray = []
    state.modeTypes = []
    
    for (int n = 1; n <= numModeTypes; n++) {
    	setupModeType(n)
    }
    
    //log.debug("init modeTypes ${state.modeTypes}")

    scheduleSunriseSunset()
    TRACE("End init")
}

def setupModeType(n) {
	TRACE("setupModeType($n)")
	def modeType = [:]
	modeType.dayMode = settings."m${n}_dayMode"
	modeType.nightMode = settings."m${n}_nightMode"
    
	state.modeTypes.push(modeType)
    state.sunriseArray.push(modeType.nightMode)
    state.sunsetArray.push(modeType.dayMode)
}

def getModeTypeDevices(n) {
	if (n >= state.numModeTypes) {
    	return null
    }
    n++
    
    def devices = [:]
    
	devices.sunriseOnSwitches = settings."m${n}_sunriseOnSwitches"
	devices.sunriseOffSwitches = settings."m${n}_sunriseOffSwitches"
	devices.sunsetOnSwitches = settings."m${n}_sunsetOnSwitches"
	devices.sunsetOffSwitches = settings."m${n}_sunsetOffSwitches"
    
    return devices
}

// schedule

def scheduleSunriseSunset() {
	TRACE("scheduleSunriseSunset()")
    def srOff = sunriseOffset()
    def ssOff = sunsetOffset()
    log.debug("srOff: $srOff , ssOff: $ssOff")
	
	def sunriseSunset = getSunriseAndSunset(zipCode: settings.zipCode)
    
	def sunriseTime = sunriseSunset.sunrise
	def sunsetTime = sunriseSunset.sunset

	def sunriseScheduleTime = getSunriseWithOffset(srOff)
	def sunsetScheduleTime = getSunsetWithOffset(ssOff)

	log.debug("sunriseScheduleTime $sunriseScheduleTime , sunsetScheduleTime $sunsetScheduleTime")
    
    def localData = getWeatherFeature('geolookup', settings.zipCode as String)
    
    def timezone = TimeZone.getTimeZone(localData.location.tz_long)
    
    log.debug( "Sunset today is at $sunsetTime" )
    log.debug( "Sunrise today is at $sunriseTime" )
    
    unschedule()    
    schedule(sunriseScheduleTime, sunrise)
    schedule(sunsetScheduleTime, sunset)
    schedule(timeTodayAfter(new Date(), '01:00', timezone), scheduleSunriseSunset)
}

def getSunriseWithOffset(srOff) {
	def srOffTime = getSunriseAndSunset(zipCode: settings.zipCode, sunriseOffset:srOff)
    //log.debug(srOffTime)
    return srOffTime.sunrise
}

def getSunsetWithOffset(ssOff) {
	def ssOffTime = getSunriseAndSunset(zipCode: settings.zipCode, sunsetOffset:ssOff)
	return ssOffTime.sunset
}

def sunriseOffset() {
	//log.debug("settings.sunriseOffsetValue ${settings.sunriseOffsetValue}")
    //log.debug("settings.sunriseOffsetDir ${settings.sunriseOffsetDir}")
	if ((settings.sunriseOffsetValue != null) && (settings.sunriseOffsetDir != null)) {
		def offsetString = ""
		if (settings.sunriseOffsetDir == 'Before') {
			offsetString = "-"
		}
		offsetString += settings.sunriseOffsetValue
		return offsetString
	} else {
		return "00:00"
	}
}

def sunsetOffset() {
	//log.debug("settings.sunsetOffsetValue ${settings.sunsetOffsetValue}")
    //log.debug("settings.sunsetOffsetDir ${settings.sunsetOffsetDir}")
    //log.debug((settings.sunsetOffsetValue != null) && (settings.sunsetOffsetDir != null))
	if ((settings.sunsetOffsetValue != null) && (settings.sunsetOffsetDir != null)) {
		def offsetString = ""
		if (settings.sunsetOffsetDir == 'Before') {
			offsetString = "-"
		}
		offsetString += settings.sunsetOffsetValue
		return offsetString
	} else {
		return "00:00"
	}
}

// events

def sunrise() {
	TRACE("sunrise()")
	def currentMode = location.mode
	def n = state.sunriseArray.indexOf(currentMode)
    log.debug("currentMode $currentMode sunriseArray ${state.sunriseArray}")
	if (n >= 0) {
		def modeType = state.modeTypes[n]
        log.debug("sunrise modeType $modeType")
        def devices = getModeTypeDevices(n)
        def onSwitches = devices.sunriseOnSwitches
        def offSwitches = devices.sunriseOffSwitches
		if (onSwitches != null) {
        	onSwitches.on()
        }
        if (offSwitches != null) {
        	offSwitches.off()
        }
		changeMode(modeType.dayMode)
	}
}

def sunset() {
	TRACE("sunset()")
	def currentMode = location.mode
	def n = state.sunsetArray.indexOf(currentMode)
	if (n >= 0) {
		def modeType = state.modeTypes[n]
        def devices = getModeTypeDevices(n)
        def onSwitches = devices.sunsetOnSwitches
        def offSwitches = devices.sunsetOffSwitches
		if (onSwitches != null) {
        	onSwitches.on()
        }
        if (offSwitches != null) {
        	offSwitches.off()
        }
		changeMode(modeType.nightMode)
	}
}

def changeMode(newMode) {
	if (newMode && location.mode != newMode) {
		if (location.modes?.find{it.name == newMode}) {
			setLocationMode(newMode)
			log.debug("has changed the mode to '${newMode}'")
		}
		else {
			log.debug("tried to change to undefined mode '${newMode}'")
		}
	}
}

// debug

def TRACE(msg) {
	log.debug msg
    //log.debug("state $state")
}

Just so no one gets confused. @baldeagle072’s app will not change mode based on presence. Only at sunrise/sunset.

Two different but very useful apps depending on the application. Thanks for sharing @baldeagle072!!!

Tim,

Any idea why my Modes switched from Away Day to Home Night and skipped Away Night? Returned home to close to sunset maybe? Some kind of delay?
When using Magic Home is it normal to get the “Setting Sunset Mode” notification twice? Thanks!

Are your settings correct in the app? If they are I’m guessing you just returned home right around sunset. What time was sunset in your area today? If it was around 5:36 then that would be the correct assumption.

Used for debugging and has since been removed.

Sunset was at 5:27pm.
Some settings in my Magic Home:


What’s the false alarm threshold? Mode does not change to Away mode for X of minutes until after I leave home? Is this all it does?

Yep. Its meant to keep presence sensors from running amok.

Looks good. Guessing it was just a coincidence

How about this one?
Two Sunrise messages 3hrs apart? East Coast / West Coast maybe?

I think I had the same last night with Sunset.

@Dave Did you update to the latest code above? I get zero of these messages.

@tslagle13
Time, your Github says the last update was 14days ago, is the code in post #1 above more recent?

I just downloaded the code 3 or 4 days ago.
Thx

Yeah, i have not updated my github yet. #timfail

1 Like

This sounds great! When I try and create the SmartApp I get the following error however.

startup failed: script14166061520641161668770.groovy: 17: unexpected token: Home” @ line 17, column 18. name: “Magic Home”, ^ 1 error

Any thoughts?

Just tested on the IDE. Worked fine for me.

@tslagle13 Hey Tim, I seem to be getting a push notification whenever your app runs a mode transition. Any idea if that’s built in or if I can turn it off?

Push settings are built into the app already. If you don’t want them turn them off

Oops! I saw that option but didn’t realize I needed to select No. Assumed it was the default. Thanks Tim!

Turn off push notifications when your home… It’s an option in the app

Warning, rookie question here. How does a ST rookie install this from GitHub?

@digdug

Its a SmartApp, go to the IDE : https://graph.api.smartthings.com/login/auth

Then click on My SmartApps, + New SmartApp, From Code, then Paste Tim’s code in from his Github, Create, Save, Publish (for me).
Force close your ST App and when you restart it use the: + sign, MyApps, and select the Magic Home app.

It’s not the easiest thing to do but searching this forum gives lots of info.

Hey @tslagle13, any update on STs approval of your app yet?