forked from imbrianj/ready_for_rain
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ready_for_rain.groovy
141 lines (120 loc) · 4.11 KB
/
ready_for_rain.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/**
* Ready for Rain
*
* Author: [email protected]
* Date: 9/10/13
*
* Warn if doors or windows are open when inclement weather is approaching.
*
* Changelog:
*
* 4/15/2016 by motley74 ([email protected])
* Added ability to set delay before attempting to send message,
* will cancel alert if contacts closed within delay.
*
*/
definition(
name: "Ready For Rain",
namespace: "imbrianj",
author: "[email protected]",
description: "Warn if doors or windows are open when inclement weather is approaching.",
category: "Convenience",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png"
)
preferences {
section("Zip code?") {
input "zipcode", "text", title: "Zipcode?", required: false
}
section("Things to check?") {
input "sensors", "capability.contactSensor", multiple: true
}
section("Notifications?") {
input "sendPushMessage", "enum", title: "Send a push notification?", metadata: [values: ["Yes", "No"]], required: false
input "phone", "phone", title: "Send a Text Message?", required: false
}
section("Message options?") {
input name: "messageDelay", type: "number", title: "Delay before sending initial message? Minutes (default to no delay)", required: false
input name: "messageReset", type: "number", title: "Delay before sending secondary messages? Minutes (default to every message)", required: false
}
}
def installed() {
init()
}
def updated() {
unsubscribe()
unschedule()
init()
}
def init() {
state.lastMessage = 0
state.lastCheck = ["time": 0, "result": false]
schedule("0 0,30 * * * ?", scheduleCheck) // Check at top and half-past of every hour
subscribe(sensors, "contact.open", scheduleCheck)
}
def scheduleCheck(evt) {
def open = sensors.findAll { it?.latestValue("contact") == "open" }
def waitTime = messageDelay ? messageDelay * 60 : 0
def expireWeather = (now() - (30 * 60 * 1000))
// Only need to poll if we haven't checked in a while - and if something is left open.
if((now() - (30 * 60 * 1000) > state.lastCheck["time"]) && open) {
log.info("Something's open - let's check the weather.")
state.weatherForecast = getWeatherFeature("forecast", zipcode)
def weather = isStormy(state.weatherForecast)
if(weather) {
runIn(waitTime, "send", [overwrite: false])
}
} else if(((now() - (30 * 60 * 1000) <= state.lastCheck["time"]) && state.lastCheck["result"]) && open) {
log.info("We have fresh weather data, no need to poll.")
runIn(waitTime, "send", [overwrite: false])
} else {
log.info("Everything looks closed, no reason to check weather.")
}
}
def send() {
def delay = (messageReset != null && messageReset != "") ? messageReset * 60 * 1000 : 0
def open = sensors.findAll { it?.latestValue("contact") == "open" }
def plural = open.size() > 1 ? "are" : "is"
def weather = isStormy(state.weatherForecast)
def msg = "${open.join(', ')} ${plural} open and ${weather} coming."
if(open) {
if(now() - delay > state.lastMessage) {
state.lastMessage = now()
if(sendPushMessage == "Yes") {
log.debug("Sending push message.")
sendPush(msg)
}
if(phone) {
log.debug("Sending text message.")
sendSms(phone, msg)
}
log.debug(msg)
} else {
log.info("Have a message to send, but user requested to not get it.")
}
} else {
log.info("Everything closed before timeout.")
}
}
private isStormy(json) {
def types = ["rain", "snow", "showers", "sprinkles", "precipitation"]
def forecast = json?.forecast?.txt_forecast?.forecastday?.first()
def result = false
if(forecast) {
def text = forecast?.fcttext?.toLowerCase()
log.debug(text)
if(text) {
for (int i = 0; i < types.size() && !result; i++) {
if(text.contains(types[i])) {
result = types[i]
}
}
} else {
log.warn("Got forecast, couldn't parse.")
}
} else {
log.warn("Did not get a forecast: ${json}")
}
state.lastCheck = ["time": now(), "result": result]
return result
}