[OBSOLETE] Color Changing Smart Weather lamp app

If you’re like me, you’ve had your share of weather this year. I decided to use SmartThings to better visualize the weather - and to know whether I need an umbrella, a coat, a snow shovel, some tissues (for high pollen) or sandals and shorts when I head out the door.

Enter the color changing smart weather lamp app. It uses a motion sensor and one or more Phillips Hue or LifX LED bulbs to visually display the weather forecast. Use your favorite lamp fixture.

Here is a quick video that shows how it works. Color Changing Smart Weather Lamp - YouTube The video doesn’t sufficiently capture the cool and vivid colors created by the Hue bulb, but you get the idea.
Color Changing Smart Weather Lamp - YouTube
Weather Lamp Colors

  • Blinking Red – A weather watch, warning, or advisory is in effect
    in your area.
  • Purple – Rain: It’s raining outside. Triggers
    when there is greater than 15% chance of rain in this or the next
    hour
  • Blue – Cold: It’s freezing outside
  • Pink – Snow: Snow is
    forecast for the day
  • Orange – Hot: It’s hot outside – above
    80F
  • Green – Sneeze: Pollen is in the air - above 7 (pollen
    requires integration with IFTTT that sets a virtual switch in ST)
  • White – All clear

To get accurate weather information it, for example, uses the specific latitude and longitude from your SmartThings Hub to take advantage of weather micro-targeting API at forecast.io that powers the App DarkSky. They believe they can predict when it will rain — down to the minute — at your exact location.

If there is a weather emergency, the app can send you a text message to warn you.
If you are an allergy sufferer and want the lamp to turn green when there is a high pollen count outside, then there is a bit of a hack. Create a virtual switch in SmartThings: How to create a Virtual Switch in SmartThings | Think Make Lab Then create an account at IFTTT.com if you don’t already have one, add a SmartThings channel to your account, and then add this recipe and link it to the virtual switch you created in ST. Log in - IFTTT Then select the Virtual Switch in the settings of the SmartLantern App.

Here’s to good weather!

17 Likes

Now I need to get a hue!

Where did you get that little light? Nice little thing perfect for beside the door like you have.

It’s a very cool Japanese style Shoji lantern made by an artisan I found on Etsy: https://www.etsy.com/shop/BarnKatDesigns

Mine would be orange all the time all summer here in Las Vegas. :weary:

3 Likes

Change the orange hue to something you like instead, and everyday will be awesome! Winning!

If you are in Las Vegas, this is a neat feature in winter/spring!

Did I just step into the Lego Movie?

5 Likes

I would just have to change the Orange to Red to remind myself that I am going out into a furnace. :grinning: But then I would never see the Blue or Pink colors here either (No snow or freezing cold) and would very seldom see the purple as we don’t get much rain.

Thinking about it, we have pretty dull weather here compared with some areas. And I think I just like it that way compared with some of the areas I have lived at before.

1 Like

I’m glad someone got that reference.

Las Vegas has like…one week of snow! Haha. It’s okay, I’m in San Diego, where everyday is 75, but I’m not complaining. You should make it blink red when it’s over 107. Las Vegas makes my hair very angry at me.

Oh look! This is now on the blog!

This is great. I’m not sure how to find this app though (still pretty new to smartthings). I looked in the IDE and tried to create one from template but didn’t see it there. Am I doing it wrong?

Where can I find this SmartApp?

1 Like

You can install the app manually this way.

Start by going to the SmartThings developer tools page at https://graph.api.smartthings.com. Login to the developer tools, creating an account if needed.

Then copy all of the code from GitHub here: https://github.com/jkohlen/Smart-Weather-Lamp/blob/master/Smart-Weather-Lamp.groovy or from the code pasted below. On the developer tools page, click “My SmartApps” and then “New SmartApp”. Select the tab “From Code” and paste the copied code into the box. Click the “Create” button at the bottom of the page. Finally click “Publish” -> “For Me” to publish this app to your account.

Then open your SmartThings mobile app, click the big plus sign at the bottom of the dashboard, Scroll across the top of the screen as far as you can. The last entry on the right should be “My Apps.” You’re self published apps should be there – including the Color Changing Smart Weather Light. Select it to install, choose a motion sensor, a color changing light (like a Phillips Hue Bulb or LifX), enter your zipcode, select an icon for the app and press “done.” You should be enjoying better weather in no time!

Alternatively, copy the code from here:

/**
 *  Color Changing Smart Weather lamp
 *
 *	
 *	This weather lantern app turns on with motion and turns a Phillips hue (or LifX) lamp different colors based on the weather.  
 *	It uses dark sky's weather API to micro-target weather. 
 *
 *	Weather Lamp Colors
 *
 *	Blinking Red 	Weather Watch, Warning or Advisory for your location
 *	Purple 		Rain: It’s raining outside. Triggers when there is greater than 15% chance of rain in this or the next hour
 *	Blue 		Cold: It’s freezing outside
 *	Pink		Snow: There is a chance of snow
 *	Orange 		Hot:  It’s hot outside -- above 90F
 *	Green  		Sneeze: Pollen is in the air - above 7 (polin requires integration with IFTTT that sets a virtual switch in ST 
 *	White		All clear
 *
 *  With special thanks to insights from the Flasher script by Bob, the SmartThings Hue mood lighting script, the light on motion script by kennyyork@centralite.com, and the Smartthings severe weather alert script 
 *
 *  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: "Color Changing Smart Weather Light",
    namespace: "",
    author: "Jim Kohlenberger",
    description: "A color changing smart weather lantern app that turns on with motion and turns a hue lamp different colors based on the weather.",
    category: "",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Developers/smart-light-timer.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Developers/smart-light-timer@2x.png",
    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Developers/smart-light-timer@2x.png")



preferences
{
    section("Select Motion Detector") {
        input "motion_detector", "capability.motionSensor", title: "Where?"
    }
    section("Control these bulbs...") {
		input "hues", "capability.colorControl", title: "Which Hue Bulbs?", required:true, multiple:true
		}
    section("Set brightness level for lights (100 is max representing 100%, default is 60)") {
        input "brightnessLevel", "number", title: "Brightess level (without %)?", required: false
    }
    section ("Zip code (optional, defaults to location coordinates)...") {
		input "zipcode", "text", title: "Zip Code", required: false
	}
    section ("In addition to push notifications, for emergency weather send text alerts to...") {
		input "phone1", "phone", title: "Phone Number 1", required: false
		input "phone2", "phone", title: "Phone Number 2", required: false
		input "phone3", "phone", title: "Phone Number 3", required: false
	}
    section ("Optionally set lantern to turn green once, if this switch is turned on.") {
    	input "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: false
    }
    section("Icon") {
        icon(title: "Select icon for app:", required: true)
    }
}

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

def updated() {
	log.debug "Updated with settings: ${settings}"
    log.debug "Weather Light: oldKeys: $oldKeys"
 
    unsubscribe()
    unschedule()
    scheduleJob()
	initialize()
}

def scheduleJob() {
	def sec = Math.round(Math.floor(Math.random() * 60))
	def min = 4 
    def cron = "$sec $min * * * ?"
    schedule(cron, "turnOff")
}

def checkForWeather() {
	def alerts
    def conditions
    def color ="Warm White"
    def statustest
    def value
	if(locationIsDefined()) {
		if(zipcodeIsValid()) {
			alerts = getWeatherFeature("alerts", zipcode)?.alerts
            conditions = getWeatherFeature("conditions", zipcode )
                       
		} else {
			log.warn "Severe Weather Alert: Invalid zipcode entered, defaulting to location's zipcode"
			alerts = getWeatherFeature("alerts")?.alerts
            conditions = getWeatherFeature("conditions") }
	} else {
		log.warn "Severe Weather Alert: Location is not defined"
	}

	log.debug "Weather conditions temp_f: ${conditions.current_observation.temp_f.toInteger()}"

	//  FREEZING BLUE	
    if (conditions.current_observation.temp_f <= 34 ) {
        	color = "Blue"
        log.debug "Weather temp below 34F, its freezing out so setting light to blue."
    }
   	
    //  HOT ORANGE
	if (conditions.current_observation.temp_f >= 80 )  {
        color = "Orange"
        log.debug "Weather temp above 80F, setting light to orange."
    }
    
 
    //	PURPLE RAIN FORCAST
	log.debug "Checking Forecast.io Weather"
    httpGet("https://api.forecast.io/forecast/8b533da63e8dc7e74a1aa20acaf8ac13/$location.latitude,$location.longitude") {response -> 
            
		if (response.data) {
       		def precipprob = response.data.currently.precipProbability.floatValue() // A numerical value between 0 and 1 (inclusive) representing the probability of precipitation occurring at the given time. 
			def tempFar = response.data.currently.temperature.floatValue()
			def thisHour = response.data.minutely.data[0].precipProbability.floatValue() //this top of hour  	
			def nextHour = response.data.minutely.data[1].precipProbability.floatValue() //next top of hour
			log.debug "Actual current temp: ${tempFar}, Precipitation probability now: ${precipprob}, thisHour: ${thisHour}, nextHour ${nextHour}"
    		if ((thisHour >15) || (nextHour >15)) {
	    		color = "Purple" 
    	    	log.debug "Greater than 15% chance of rain in this or the next hour, setting light to Purple."
    		}
       	}   else {
        	log.debug "HttpGet Response data unsuccesful."
        }
    }
    
    //  PINK SNOW
    def f = getWeatherFeature("forecast", zipcode) //get("forecast")
	def f1= f?.forecast?.simpleforecast?.forecastday
	if (f1) {
		value = f1[0].snow_day 
	}
	else {
		log.warn "Forecast not found"
	}
  	
  	log.debug "The chance of snow = ${value}"
    if (!value.toString().contains("0.0")) {
    	if (!value.toString().contains("null")) {
    		color = "Pink"
    		log.debug "Weather shows chance of snow, setting light to Pink."
        }
    }
   
	sendcolor(color)
      
	def newKeys = alerts?.collect{it.type + it.date_epoch} ?: []
	log.debug "Severe Weather Alert: newKeys: $newKeys"

	def oldKeys = state.alertKeys ?: []
	log.debug "Severe Weather Alert: oldKeys: $oldKeys"

	if (newKeys != oldKeys) {

		state.alertKeys = newKeys

		alerts.each {alert ->
			if (!oldKeys.contains(alert.type + alert.date_epoch) && descriptionFilter(alert.description)) {
                color = "Red"
                sendcolor(color)
				flashLights()

			}
		}
	}
}

def descriptionFilter(String description) {
	def filterList = ["special", "statement", "test"]
	def passesFilter = true
	filterList.each() { word ->
		if(description.toLowerCase().contains(word)) { passesFilter = false }
	}
	passesFilter
}


def locationIsDefined() {
	zipcodeIsValid() || location.zipCode || ( location.latitude && location.longitude )
}

def zipcodeIsValid() {
	zipcode && zipcode.isNumber() && zipcode.size() == 5
}

private send(message) {
	sendPush message
	if (settings.phone1) {
		sendSms phone1, message
	}
	if (settings.phone2) {
		sendSms phone2, message
	}
	if (settings.phone3) {
		sendSms phone3, message
	}
}

def sendcolor(color) {
	log.debug "Sendcolor = $color"
    def hueColor = 0
    def saturation = 100

	switch(color) {
		case "White":
			hueColor = 52
			saturation = 19
			break;
		case "Daylight":
			hueColor = 53
			saturation = 91
			break;
		case "Soft White":
			hueColor = 23
			saturation = 56
			break;
		case "Warm White":
			hueColor = 20
			saturation = 80 //83
			break;
		case "Blue":
			hueColor = 70
			break;
		case "Green":
			hueColor = 39
			break;
		case "Yellow":
			hueColor = 25
			break;
		case "Orange":
			hueColor = 10
			break;
		case "Purple":
			hueColor = 75
			break;
		case "Pink":
			hueColor = 83
			break;
		case "Red":
			hueColor = 100
			break;
	}

	state.previous = [:]

	hues.each {
		state.previous[it.id] = [
			"switch": it.currentValue("switch"),
			"level" : it.currentValue("level"),
			"hue": it.currentValue("hue"),
			"saturation": it.currentValue("saturation")
           
		]
	}
	
	log.debug "current values = $state.previous"
    
    // CHECK for GREEN button on
    if (mySwitch != null) {
    	if (mySwitch.latestValue("switch") == "on" ) {   
    		log.debug "mySwitch is on so setting light to GREEN and closing switch"
        	if (color != "Red") { //If its red, then override green
        		hueColor = 39
            	mySwitch.off()  
        	}
    	}
    }
    
  	def lightLevel = 60
    if (brightnessLevel != null) {
    	lightLevel = brightnessLevel 
    }
     
	def newValue = [hue: hueColor, saturation: saturation, level: lightLevel]  
	log.debug "new value = $newValue"

	hues*.setColor(newValue)
}

/// HANDLE MOTION

def turnOff() {
	log.debug "Timer fired, turning off light(s)"
    hues.off()
}

def motionHandler(evt) {
	if (evt.value == "active") {                // If there is movement then...
        log.debug "Motion detected, turning on light and killing timer"
        checkForWeather()
        unschedule( turnOff )                   // ...we don't need to turn it off.
    }
    else {                                      // If there is no movement then...
        def delay = 100 				
        log.debug "Motion cleared, turning off switches in (${delay})."
        pause(delay)
        hues.off()
    }
}

def initialize() {
	log.info "Initializing, subscribing to motion event at ${motionHandler} on ${motion_detector}"
    subscribe(motion_detector, "motion", motionHandler)
	subscribe(app, appTouchHandler)
}
def appTouchHandler(evt) {
	checkForWeather()
    def delay = 4000 				
    log.debug "App triggered with button press, turning off switches in (${delay})."
    pause(delay)
    hues.off()
}

private flashLights() {
	def doFlash = true
	def onFor = onFor ?: 1000
	def offFor = offFor ?: 1000
	def numFlashes = numFlashes ?: 3

	log.debug "LAST ACTIVATED IS: ${state.lastActivated}"
	if (state.lastActivated) {
		def elapsed = now() - state.lastActivated
		def sequenceTime = (numFlashes + 1) * (onFor + offFor)
		doFlash = elapsed > sequenceTime
		log.debug "DO FLASH: $doFlash, ELAPSED: $elapsed, LAST ACTIVATED: ${state.lastActivated}"
	}

	if (doFlash) {
		log.debug "FLASHING $numFlashes times"
		state.lastActivated = now()
		log.debug "LAST ACTIVATED SET TO: ${state.lastActivated}"
		def initialActionOn = switches.collect{it.currentSwitch != "on"}
		def delay = 1L
		numFlashes.times {
			log.trace "Switch on after  $delay msec"
            hues.eachWithIndex {s, i ->
				if (initialActionOn[i]) {
					s.on(delay: delay)
				}
				else {
					s.off(delay:delay)
				}
			}
			delay += onFor
			log.trace "Switch off after $delay msec"
            hues.eachWithIndex {s, i ->
				if (initialActionOn[i]) {
					s.off(delay: delay)
				}
				else {
					s.on(delay:delay)
				}
			}
			delay += offFor
		}
        //delay += offFor
        //s.off(delay:delay)
	}
}
3 Likes

Done and done! Thanks!

Question: Having the high pollen count turned on introduces the possibility for overlap. For example, if it’s hot outside and the pollen count is above 7, does it flash orange and green or does the green override it?

2 Likes

Thanks! Will be testing this out tonight…I may have to move one of the hue bulbs closer to the front door however. I like your lantern!

Great question. So that app treats things in a hierarchical manner. So for example, snow (Pink) trumps blue (freezing cold). Similarly, a weather alert (blinking red) trumps everything.

For your question on what happens when its both high pollen (green) and hot outside (orange), it is set so that it only turns green the first time there is motion – then resets the virtual switch. This way, the next time it senses motion, it would glow Orange.

Thanks for clarifying. Correct me if I’m wrong: If I trigger the first motion and it glows green, and if my roommate is the one who has allergies, he would not know there is a high pollen count for the day.

My hue only glows orange for now, even though it’s 38F outside. I’ll have to test more to see why its doing that. If you have any suggestions let me know.

Great work!

Thanks for this. This is the best step-by-step on how to get a custom app installed that I’ve seen so far! I’m not really a developer and have been wondering how to do this.

1 Like

Pete - That’s correct. If it glows green once for pollen, and your roommate who has allergies misses it, he’s unfortunately going to be sneezing and sniffling all day. Unfortunately, I couldn’t find an easy way to show two colors for the green pollen because of the reliance on data from IFTTT. There is a pollen API from Calritin I found at http://www.claritin.com/weatherpollenservice/weatherpollenservice.svc/getforecast/[zipcode] but the JSON is malformed and produces errors.

As an alternative, you could create an IFTTT script to always turn it green when the pollen is high.

Very cool app! I have a problem though… Every color works except purple, anyone have this issue?

Great app @jkohlen. I just finished setting it up but had to make a few changes. Where you’re checking for rain for the next 2 hours, you’re using the minutely data instead of the hourly data so you’re actually checking the next 2 minutes instead of the next 2 hours. Also, you may want to consider removing your forecast.io API key and instructing anyone that uses your code to create their own account. I registered my own developer account and saw that only 1,000 free calls to the API are allowed daily. If enough people copy your code as is that limit could be reached and access would be lost for the remainder of the day for everyone using that API key.

1 Like

I just noticed the issue too and found the problem. The code is meant to check if there is a 15%+ probability of rain. There is an error in the check. The forecast data uses a scale from 0-1 for probabilty, 0 meaning 0% and 1 meaning 100%, but the code is checking to see if the probabilty is >15, which it could never be because that would be greater than 100%. Change the code to look for thisHour>0.15. Also make sure you change minutely data to hourly data as I mentioned in my other post.