4 * Author: brian@bevey.org
7 * Warn if doors or windows are open when inclement weather is approaching.
11 name: "Ready For Rain",
12 namespace: "imbrianj",
13 author: "brian@bevey.org",
14 description: "Warn if doors or windows are open when inclement weather is approaching.",
15 category: "Convenience",
16 iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
17 iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png"
22 if (!(location.zipCode || ( location.latitude && location.longitude )) && location.channelName == 'samsungtv') {
23 section { paragraph title: "Note:", "Location is required for this SmartApp. Go to 'Location Name' settings to setup your correct location." }
26 if (location.channelName != 'samsungtv') {
27 section( "Set your location" ) { input "zipCode", "text", title: "Zip code" }
30 section("Things to check?") {
31 input "sensors", "capability.contactSensor", multiple: true
34 section("Notifications?") {
35 input "sendPushMessage", "enum", title: "Send a push notification?", metadata: [values: ["Yes", "No"]], required: false
36 input "phone", "phone", title: "Send a Text Message?", required: false
39 section("Message interval?") {
40 input name: "messageDelay", type: "number", title: "Minutes (default to every message)", required: false
56 state.lastCheck = ["time": 0, "result": false]
57 schedule("0 0,30 * * * ?", scheduleCheck) // Check at top and half-past of every hour
58 subscribe(sensors, "contact.open", scheduleCheck)
61 def scheduleCheck(evt) {
62 def open = sensors.findAll { it?.latestValue("contact") == "open" }
63 def plural = open.size() > 1 ? "are" : "is"
65 // Only need to poll if we haven't checked in a while - and if something is left open.
66 if((now() - (30 * 60 * 1000) > state.lastCheck["time"]) && open) {
67 log.info("Something's open - let's check the weather.")
69 if (location.channelName != 'samsungtv')
70 response = getWeatherFeature("forecast", zipCode)
72 response = getWeatherFeature("forecast")
73 def weather = isStormy(response)
76 send("${open.join(', ')} ${plural} open and ${weather} coming.")
80 else if(((now() - (30 * 60 * 1000) <= state.lastCheck["time"]) && state.lastCheck["result"]) && open) {
81 log.info("We have fresh weather data, no need to poll.")
82 send("${open.join(', ')} ${plural} open and ${state.lastCheck["result"]} coming.")
86 log.info("Everything looks closed, no reason to check weather.")
91 def delay = (messageDelay != null && messageDelay != "") ? messageDelay * 60 * 1000 : 0
93 if(now() - delay > state.lastMessage) {
94 state.lastMessage = now()
95 if(sendPushMessage == "Yes") {
96 log.debug("Sending push message.")
101 log.debug("Sending text message.")
109 log.info("Have a message to send, but user requested to not get it.")
113 private isStormy(json) {
114 def types = ["rain", "snow", "showers", "sprinkles", "precipitation"]
115 def forecast = json?.forecast?.txt_forecast?.forecastday?.first()
119 def text = forecast?.fcttext?.toLowerCase()
124 for (int i = 0; i < types.size() && !result; i++) {
125 if(text.contains(types[i])) {
132 log.warn("Got forecast, couldn't parse.")
137 log.warn("Did not get a forecast: ${json}")
140 state.lastCheck = ["time": now(), "result": result]