Update lighting-director.groovy
[smartapps.git] / official / talking-alarm-clock.groovy
1 /**
2  *  Talking Alarm Clock
3  *
4  *  Version - 1.0.0 5/23/15
5  *  Version - 1.1.0 5/24/15 - A song can now be selected to play after the voice greeting and bug fixes
6  *  Version - 1.2.0 5/27/15 - Added About screen and misc code clean up and GUI revisions
7  *  Version - 1.3.0 5/29/15 - Further code optimizations and addition of alarm summary action
8  *  Version - 1.3.1 5/30/15 - Fixed one small code syntax issue in Scenario D
9  *  Version - 1.4.0 6/7/15 -  Revised About screen, enhanced the weather forecast voice summary, added a mode change option with alarm, and added secondary alarm options
10  *  Version - 1.4.1 6/9/15 - Changed the mode change speech to make it clear when the mode change is taking place  
11  *  Version - 1.4.2 6/10/15 - To prevent accidental triggering of summary, put in a mode switch restriction
12  *  Version - 1.4.3 6/12/15 - Syntax issues and minor GUI fixes
13  *  Version - 1.4.4 6/15/15 - Fixed a bug with Phrase change at alarm time
14  *  Version - 1.4.5 6/17/15 - Code optimization, implemented the new submitOnChange option and a new license agreement change
15  *
16  *  Copyright 2015 Michael Struck - Uses code from Lighting Director by Tim Slagle & Michael Struck
17  *
18  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
19  *  in compliance with the License. You may obtain a copy of the License at:
20  *
21  *      http://www.apache.org/licenses/LICENSE-2.0
22  *
23  *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
24  *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
25  *  for the specific language governing permissions and limitations under the License.
26  *
27  */
28  
29 definition(
30     name: "Talking Alarm Clock",
31     namespace: "MichaelStruck",
32     author: "Michael Struck",
33     description: "Control up to 4 waking schedules using a Sonos speaker as an alarm.",
34     category: "Convenience",
35     iconUrl: "https://raw.githubusercontent.com/MichaelStruck/SmartThings/master/Other-SmartApps/Talking-Alarm-Clock/Talkingclock.png",
36     iconX2Url: "https://raw.githubusercontent.com/MichaelStruck/SmartThings/master/Other-SmartApps/Talking-Alarm-Clock/Talkingclock@2x.png",
37         iconX3Url: "https://raw.githubusercontent.com/MichaelStruck/SmartThings/master/Other-SmartApps/Talking-Alarm-Clock/Talkingclock@2x.png"
38     )
39
40 preferences {
41         page name:"pageMain"
42         page name:"pageSetupScenarioA"
43         page name:"pageSetupScenarioB"
44         page name:"pageSetupScenarioC"
45         page name:"pageSetupScenarioD"
46         page name:"pageWeatherSettingsA" //technically, these 4 pages should not be dynamic, but are here to work around a crash on the Andriod app
47         page name:"pageWeatherSettingsB"
48         page name:"pageWeatherSettingsC"
49         page name:"pageWeatherSettingsD"
50 }
51
52 // Show setup page
53 def pageMain() {
54         dynamicPage(name: "pageMain", install: true, uninstall: true) {
55         section ("Alarms") {
56             href "pageSetupScenarioA", title: getTitle(ScenarioNameA, 1), description: getDesc(A_timeStart, A_sonos, A_day, A_mode), state: greyOut(ScenarioNameA, A_sonos, A_timeStart, A_alarmOn, A_alarmType)
57             if (ScenarioNameA && A_sonos && A_timeStart && A_alarmType){
58                 input "A_alarmOn", "bool", title: "Enable this alarm?", defaultValue: "true", submitOnChange:true
59             }
60         }
61         section {
62             href "pageSetupScenarioB", title: getTitle(ScenarioNameB, 2), description: getDesc(B_timeStart, B_sonos, B_day, B_mode), state: greyOut(ScenarioNameB, B_sonos, B_timeStart, B_alarmOn, B_alarmType)
63             if (ScenarioNameB && B_sonos && B_timeStart  && B_alarmType){
64                 input "B_alarmOn", "bool", title: "Enable this alarm?", defaultValue: "false", submitOnChange:true
65                 }
66         }
67         section {
68             href "pageSetupScenarioC", title: getTitle(ScenarioNameC, 3), description: getDesc(C_timeStart, C_sonos, C_day, C_mode), state: greyOut(ScenarioNameC, C_sonos, C_timeStart, C_alarmOn, C_alarmType)
69             if (ScenarioNameC && C_sonos && C_timeStart && C_alarmType){
70                 input "C_alarmOn", "bool", title: "Enable this alarm?", defaultValue: "false", submitOnChange:true
71                 }
72         }
73         section {
74             href "pageSetupScenarioD", title: getTitle(ScenarioNameD, 4), description: getDesc(D_timeStart, D_sonos, D_day, D_mode), state: greyOut(ScenarioNameD, D_sonos, D_timeStart, D_alarmOn, D_alarmType)
75             if (ScenarioNameD && D_sonos && D_timeStart && D_alarmType){
76                 input "D_alarmOn", "bool", title: "Enable this alarm?", defaultValue: "false", submitOnChange:true
77             }
78         }
79         section([title:"Options", mobileOnly:true]) {
80             input "alarmSummary", "bool", title: "Enable Alarm Summary", defaultValue: "false", submitOnChange:true
81             if (alarmSummary) {
82                 href "pageAlarmSummary", title: "Alarm Summary Settings", description: "Tap to configure alarm summary settings", state: "complete"
83             }
84             input "zipCode", "text", title: "Zip Code", required: false
85             label title:"Assign a name", required: false
86             href "pageAbout", title: "About ${textAppName()}", description: "Tap to get application version, license and instructions"
87         }
88     }
89 }
90
91 page(name: "pageAlarmSummary", title: "Alarm Summary Settings") {
92         section {
93         input "summarySonos", "capability.musicPlayer", title: "Choose a Sonos speaker", required: false
94         input "summaryVolume", "number", title: "Set the summary volume", description: "0-100%", required: false
95         input "summaryDisabled", "bool", title: "Include disabled or unconfigured alarms in summary", defaultValue: "false"
96         input "summaryMode", "mode", title: "Speak summary only during the following modes...", multiple: true, required: false
97         }
98 }
99 //Show "pageSetupScenarioA" page
100 def pageSetupScenarioA() {
101     dynamicPage(name: "pageSetupScenarioA") {
102                 section("Alarm settings") {
103                 input "ScenarioNameA", "text", title: "Scenario Name", multiple: false, required: true
104                         input "A_sonos", "capability.musicPlayer", title: "Choose a Sonos speaker", required: true, submitOnChange:true
105             input "A_volume", "number", title: "Alarm volume", description: "0-100%", required: false
106                 input "A_timeStart", "time", title: "Time to trigger alarm", required: true
107                 input "A_day", "enum", options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], title: "Alarm on certain days of the week...", multiple: true, required: false
108                 input "A_mode", "mode", title: "Alarm only during the following modes...", multiple: true, required: false
109                 input "A_alarmType", "enum", title: "Select a primary alarm type...", multiple: false, required: true, options: [[1:"Alarm sound (up to 20 seconds)"],[2:"Voice Greeting"],[3:"Music track/Internet Radio"]], submitOnChange:true
110             
111             if (A_alarmType != "3") {
112                 if (A_alarmType == "1"){
113                         input "A_secondAlarm", "enum", title: "Select a second alarm after the first is completed", multiple: false, required: false, options: [[1:"Voice Greeting"],[2:"Music track/Internet Radio"]], submitOnChange:true
114                 }
115                 if (A_alarmType == "2"){
116                         input "A_secondAlarmMusic", "bool", title: "Play a track after voice greeting", defaultValue: "false", required: false, submitOnChange:true
117                 }
118                 }
119         }
120         if (A_alarmType == "1"){
121                 section ("Alarm sound options"){
122                                 input "A_soundAlarm", "enum", title: "Play this sound...", required:false, multiple: false, options: [[1:"Alien-8 seconds"],[2:"Bell-12 seconds"], [3:"Buzzer-20 seconds"], [4:"Fire-20 seconds"], [5:"Rooster-2 seconds"], [6:"Siren-20 seconds"]]
123                                 input "A_soundLength", "number", title: "Maximum time to play sound (empty=use sound default)", description: "1-20", required: false        
124                 }
125                 }
126         if (A_alarmType == "2" || (A_alarmType == "1" && A_secondAlarm =="1")) {
127                 section ("Voice greeting options") {
128                 input "A_wakeMsg", "text", title: "Wake voice message", defaultValue: "Good morning! It is %time% on %day%, %date%.", required: false
129                                 href "pageWeatherSettingsA", title: "Weather Reporting Settings", description: getWeatherDesc(A_weatherReport, A_includeSunrise, A_includeSunset, A_includeTemp, A_humidity, A_localTemp), state: greyOut1(A_weatherReport, A_includeSunrise, A_includeSunset, A_includeTemp, A_humidity, A_localTemp)
130                         }
131         }
132         if (A_alarmType == "3" || (A_alarmType == "1" && A_secondAlarm =="2") || (A_alarmType == "2" && A_secondAlarmMusic)){
133                 section ("Music track/internet radio options"){
134                 input "A_musicTrack", "enum", title: "Play this track/internet radio station", required:false, multiple: false, options: songOptions(A_sonos, 1)
135                 }
136         }
137         section("Devices to control in this alarm scenario") {
138                         input "A_switches", "capability.switch",title: "Control the following switches...", multiple: true, required: false, submitOnChange:true
139                         href "pageDimmersA", title: "Dimmer Settings", description: dimmerDesc(A_dimmers), state: greyOutOption(A_dimmers), submitOnChange:true
140             href "pageThermostatsA", title: "Thermostat Settings", description: thermostatDesc(A_thermostats, A_temperatureH, A_temperatureC), state: greyOutOption(A_thermostats), submitOnChange:true
141                 if ((A_switches || A_dimmers || A_thermostats) && (A_alarmType == "2" || (A_alarmType == "1" && A_secondAlarm =="1"))){
142                 input "A_confirmSwitches", "bool", title: "Confirm switches/thermostats status in voice message", defaultValue: "false"
143             }
144         }
145                 section ("Other actions at alarm time"){
146             def phrases = location.helloHome?.getPhrases()*.label
147                         if (phrases) {
148                                 phrases.sort()
149                                 input "A_phrase", "enum", title: "Alarm triggers the following phrase", required: false, options: phrases, multiple: false, submitOnChange:true
150                                 if (A_phrase  && (A_alarmType == "2" || (A_alarmType == "1" && A_secondAlarm =="1"))){
151                         input "A_confirmPhrase", "bool", title: "Confirm Hello, Home phrase in voice message", defaultValue: "false"
152                 }
153             }
154             input "A_triggerMode", "mode", title: "Alarm triggers the following mode", required: false, submitOnChange:true
155                 if (A_triggerMode  && (A_alarmType == "2" || (A_alarmType == "1" && A_secondAlarm =="1"))){
156                 input "A_confirmMode", "bool", title: "Confirm mode in voice message", defaultValue: "false"
157             }
158         }
159     } 
160 }
161
162 page(name: "pageDimmersA", title: "Dimmer Settings") {
163         section {
164         input "A_dimmers", "capability.switchLevel", title: "Dim the following...", multiple: true, required: false     
165                 input "A_level", "enum", options: [[10:"10%"],[20:"20%"],[30:"30%"],[40:"40%"],[50:"50%"],[60:"60%"],[70:"70%"],[80:"80%"],[90:"90%"],[100:"100%"]],title: "Set dimmers to this level", multiple: false, required: false
166         }
167 }
168
169 page(name: "pageThermostatsA", title: "Thermostat Settings") {
170         section {
171         input "A_thermostats", "capability.thermostat", title: "Thermostat to control...", multiple: false, required: false
172         }
173     section {
174         input "A_temperatureH", "number", title: "Heating setpoint", required: false, description: "Temperature when in heat mode"
175                 input "A_temperatureC", "number", title: "Cooling setpoint", required: false, description: "Temperature when in cool mode"
176         }
177 }
178
179 def pageWeatherSettingsA() {
180         dynamicPage(name: "pageWeatherSettingsA", title: "Weather Reporting Settings") {
181                 section {
182                         input "A_includeTemp", "bool", title: "Speak current temperature (from local forecast)", defaultValue: "false"
183                 input "A_localTemp", "capability.temperatureMeasurement", title: "Speak local temperature (from device)", required: false, multiple: false
184                 input "A_humidity", "capability.relativeHumidityMeasurement", title: "Speak local humidity (from device)", required: false, multiple: false
185                 input "A_weatherReport", "bool", title: "Speak today's weather forecast", defaultValue: "false"
186                 input "A_includeSunrise", "bool", title: "Speak today's sunrise", defaultValue: "false"
187                 input "A_includeSunset", "bool", title: "Speak today's sunset", defaultValue: "false"
188                 }
189         }
190 }
191
192 //Show "pageSetupScenarioB" page
193 def pageSetupScenarioB() {
194     dynamicPage(name: "pageSetupScenarioB") {
195                 section("Alarm settings") {
196                 input "ScenarioNameB", "text", title: "Scenario Name", multiple: false, required: true
197                         input "B_sonos", "capability.musicPlayer", title: "Choose a Sonos speaker", required: true, submitOnChange:true
198             input "B_volume", "number", title: "Alarm volume", description: "0-100%", required: false
199                 input "B_timeStart", "time", title: "Time to trigger alarm", required: true
200                 input "B_day", "enum", options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], title: "Alarm on certain days of the week...", multiple: true, required: false
201                 input "B_mode", "mode", title: "Alarm only during the following modes...", multiple: true, required: false
202                 input "B_alarmType", "enum", title: "Select a primary alarm type...", multiple: false, required: true, options: [[1:"Alarm sound (up to 20 seconds)"],[2:"Voice Greeting"],[3:"Music track/Internet Radio"]], submitOnChange:true
203             
204             if (B_alarmType != "3") {
205                 if (B_alarmType == "1"){
206                         input "B_secondAlarm", "enum", title: "Select a second alarm after the first is completed", multiple: false, required: false, options: [[1:"Voice Greeting"],[2:"Music track/Internet Radio"]], submitOnChange:true
207                 }
208                 if (B_alarmType == "2"){
209                         input "B_secondAlarmMusic", "bool", title: "Play a track after voice greeting", defaultValue: "false", required: false, submitOnChange:true
210                 }
211                 }
212         }
213         if (B_alarmType == "1"){
214                 section ("Alarm sound options"){
215                                 input "B_soundAlarm", "enum", title: "Play this sound...", required:false, multiple: false, options: [[1:"Alien-8 seconds"],[2:"Bell-12 seconds"], [3:"Buzzer-20 seconds"], [4:"Fire-20 seconds"], [5:"Rooster-2 seconds"], [6:"Siren-20 seconds"]]
216                                 input "B_soundLength", "number", title: "Maximum time to play sound (empty=use sound default)", description: "1-20", required: false        
217                 }
218                 }
219                 if (B_alarmType == "2" || (B_alarmType == "1" && B_secondAlarm =="1")){
220                 section ("Voice greeting options") {
221                 input "B_wakeMsg", "text", title: "Wake voice message", defaultValue: "Good morning! It is %time% on %day%, %date%.", required: false
222                 href "pageWeatherSettingsB", title: "Weather Reporting Settings", description: getWeatherDesc(B_weatherReport, B_includeSunrise, B_includeSunset, B_includeTemp, B_humidity, B_localTemp), state: greyOut1(B_weatherReport, B_includeSunrise, B_includeSunset, B_includeTemp, B_humidity, B_localTemp)
223                 }
224         }        
225         if (B_alarmType == "3" || (B_alarmType == "1" && B_secondAlarm =="2") || (B_alarmType == "2" && B_secondAlarmMusic)){
226                 section ("Music track/internet radio options"){
227                 input "B_musicTrack", "enum", title: "Play this track/internet radio station", required:false, multiple: false, options: songOptions(B_sonos, 1)
228                 }
229         }
230         section("Devices to control in this alarm scenario") {
231                         input "B_switches", "capability.switch",title: "Control the following switches...", multiple: true, required: false, submitOnChange:true
232                         href "pageDimmersB", title: "Dimmer Settings", description: dimmerDesc(B_dimmers), state: greyOutOption(B_dimmers), submitOnChange:true
233             href "pageThermostatsB", title: "Thermostat Settings", description: thermostatDesc(B_thermostats, B_temperatureH, B_temperatureC), state: greyOutOption(B_thermostats), submitOnChange:true
234                 if ((B_switches || B_dimmers || B_thermostats) && (B_alarmType == "2" || (B_alarmType == "1" && B_secondAlarm =="1"))){
235                 input "B_confirmSwitches", "bool", title: "Confirm switches/thermostats status in voice message", defaultValue: "false"
236             }
237         }
238         section ("Other actions at alarm time"){
239             def phrases = location.helloHome?.getPhrases()*.label
240                         if (phrases) {
241                                 phrases.sort()
242                                 input "B_phrase", "enum", title: "Alarm triggers the following phrase", required: false, options: phrases, multiple: false, submitOnChange:true
243                                 if (B_phrase  && (B_alarmType == "2" || (B_alarmType == "1" && B_secondAlarm =="1"))){
244                         input "B_confirmPhrase", "bool", title: "Confirm Hello, Home phrase in voice message", defaultValue: "false"
245                 }
246             }
247             input "B_triggerMode", "mode", title: "Alarm triggers the following mode", required: false, submitOnChange:true
248                 if (B_triggerMode  && (B_alarmType == "2" || (B_alarmType == "1" && B_secondAlarm =="1"))){
249                 input "B_confirmMode", "bool", title: "Confirm mode in voice message", defaultValue: "false"
250             }
251         }
252     } 
253 }
254
255 page(name: "pageDimmersB", title: "Dimmer Settings") {
256         section {
257         input "B_dimmers", "capability.switchLevel", title: "Dim the following...", multiple: true, required: false     
258                 input "B_level", "enum", options: [[10:"10%"],[20:"20%"],[30:"30%"],[40:"40%"],[50:"50%"],[60:"60%"],[70:"70%"],[80:"80%"],[90:"90%"],[100:"100%"]],title: "Set dimmers to this level", multiple: false, required: false
259         }
260 }
261
262 page(name: "pageThermostatsB", title: "Thermostat Settings") {
263         section {
264         input "B_thermostats", "capability.thermostat", title: "Thermostat to control...", multiple: false, required: false
265         }
266     section {
267         input "B_temperatureH", "number", title: "Heating setpoint", required: false, description: "Temperature when in heat mode"
268                 input "B_temperatureC", "number", title: "Cooling setpoint", required: false, description: "Temperature when in cool mode"
269         }
270 }
271
272 def pageWeatherSettingsB() {
273         dynamicPage(name: "pageWeatherSettingsB", title: "Weather Reporting Settings") {
274                 section {
275                 input "B_includeTemp", "bool", title: "Speak current temperature (from local forecast)", defaultValue: "false"
276                 input "B_localTemp", "capability.temperatureMeasurement", title: "Speak local temperature (from device)", required: false, multiple: false
277                 input "B_humidity", "capability.relativeHumidityMeasurement", title: "Speak local humidity (from device)", required: false, multiple: false
278                 input "B_weatherReport", "bool", title: "Speak today's weather forecast", defaultValue: "false"
279                         input "B_includeSunrise", "bool", title: "Speak today's sunrise", defaultValue: "false"
280                         input "B_includeSunset", "bool", title: "Speak today's sunset", defaultValue: "false"
281                 }
282         }
283 }
284
285 //Show "pageSetupScenarioC" page
286 def pageSetupScenarioC() {
287     dynamicPage(name: "pageSetupScenarioC") {
288                 section("Alarm settings") {
289                 input "ScenarioNameC", "text", title: "Scenario Name", multiple: false, required: true
290                         input "C_sonos", "capability.musicPlayer", title: "Choose a Sonos speaker", required: true, submitOnChange:true
291             input "C_volume", "number", title: "Alarm volume", description: "0-100%", required: false
292                 input "C_timeStart", "time", title: "Time to trigger alarm", required: true
293                 input "C_day", "enum", options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], title: "Alarm on certain days of the week...", multiple: true, required: false
294                 input "C_mode", "mode", title: "Alarm only during the following modes...", multiple: true, required: false
295                 input "C_alarmType", "enum", title: "Select a primary alarm type...", multiple: false, required: true, options: [[1:"Alarm sound (up to 20 seconds)"],[2:"Voice Greeting"],[3:"Music track/Internet Radio"]], submitOnChange:true
296             
297             if (C_alarmType != "3") {
298                 if (C_alarmType == "1"){
299                         input "C_secondAlarm", "enum", title: "Select a second alarm after the first is completed", multiple: false, required: false, options: [[1:"Voice Greeting"],[2:"Music track/Internet Radio"]], submitOnChange:true
300                 }
301                 if (C_alarmType == "2"){
302                         input "C_secondAlarmMusic", "bool", title: "Play a track after voice greeting", defaultValue: "false", required: false, submitOnChange:true
303                 }
304                 }
305         }
306         if (C_alarmType == "1"){
307                 section ("Alarm sound options"){
308                                 input "C_soundAlarm", "enum", title: "Play this sound...", required:false, multiple: false, options: [[1:"Alien-8 seconds"],[2:"Bell-12 seconds"], [3:"Buzzer-20 seconds"], [4:"Fire-20 seconds"], [5:"Rooster-2 seconds"], [6:"Siren-20 seconds"]]
309                                 input "C_soundLength", "number", title: "Maximum time to play sound (empty=use sound default)", description: "1-20", required: false        
310                 }
311                 }
312         
313         if (C_alarmType == "2" || (C_alarmType == "1" && C_secondAlarm =="1")) {
314                 section ("Voice greeting options") {
315                 input "C_wakeMsg", "text", title: "Wake voice message", defaultValue: "Good morning! It is %time% on %day%, %date%.", required: false
316                                 href "pageWeatherSettingsC", title: "Weather Reporting Settings", description: getWeatherDesc(C_weatherReport, C_includeSunrise, C_includeSunset, C_includeTemp, A_humidity, C_localTemp), state: greyOut1(C_weatherReport, C_includeSunrise, C_includeSunset, C_includeTemp, C_humidity, C_localTemp)          }
317         }
318         
319         if (C_alarmType == "3" || (C_alarmType == "1" && C_secondAlarm =="2") || (C_alarmType == "2" && C_secondAlarmMusic)){
320                 section ("Music track/internet radio options"){
321                 input "C_musicTrack", "enum", title: "Play this track/internet radio station", required:false, multiple: false, options: songOptions(C_sonos, 1)
322                 }
323         }
324         section("Devices to control in this alarm scenario") {
325                         input "C_switches", "capability.switch",title: "Control the following switches...", multiple: true, required: false, submitOnChange:true
326                         href "pageDimmersC", title: "Dimmer Settings", description: dimmerDesc(C_dimmers), state: greyOutOption(C_dimmers), submitOnChange:true
327             href "pageThermostatsC", title: "Thermostat Settings", description: thermostatDesc(C_thermostats, C_temperatureH, C_temperatureC), state: greyOutOption(C_thermostats), submitOnChange:true
328                 if ((C_switches || C_dimmers || C_thermostats) && (C_alarmType == "2" || (C_alarmType == "1" && C_secondAlarm =="1"))){
329                 input "C_confirmSwitches", "bool", title: "Confirm switches/thermostats status in voice message", defaultValue: "false"
330             }
331         }
332         section ("Other actions at alarm time"){
333             def phrases = location.helloHome?.getPhrases()*.label
334                         if (phrases) {
335                                 phrases.sort()
336                                 input "C_phrase", "enum", title: "Alarm triggers the following phrase", required: false, options: phrases, multiple: false, submitOnChange:true
337                                 if (C_phrase  && (C_alarmType == "2" || (C_alarmType == "1" && C_secondAlarm =="1"))){
338                         input "C_confirmPhrase", "bool", title: "Confirm Hello, Home phrase in voice message", defaultValue: "false"
339                 }
340             }
341             input "C_triggerMode", "mode", title: "Alarm triggers the following mode", required: false, submitOnChange:true
342                 if (C_triggerMode  && (C_alarmType == "2" || (C_alarmType == "1" && C_secondAlarm =="1"))){
343                 input "C_confirmMode", "bool", title: "Confirm mode in voice message", defaultValue: "false"
344             }
345         }
346     } 
347 }
348
349 page(name: "pageDimmersC", title: "Dimmer Settings") {
350         section {
351         input "C_dimmers", "capability.switchLevel", title: "Dim the following...", multiple: true, required: false     
352                 input "C_level", "enum", options: [[10:"10%"],[20:"20%"],[30:"30%"],[40:"40%"],[50:"50%"],[60:"60%"],[70:"70%"],[80:"80%"],[90:"90%"],[100:"100%"]],title: "Set dimmers to this level", multiple: false, required: false
353         }
354 }
355
356 page(name: "pageThermostatsC", title: "Thermostat Settings") {
357         section {
358         input "C_thermostats", "capability.thermostat", title: "Thermostat to control...", multiple: false, required: false
359         }
360     section {
361         input "C_temperatureH", "number", title: "Heating setpoint", required: false, description: "Temperature when in heat mode"
362                 input "C_temperatureC", "number", title: "Cooling setpoint", required: false, description: "Temperature when in cool mode"
363         }
364 }
365
366 def pageWeatherSettingsC() {
367         dynamicPage(name: "pageWeatherSettingsC", title: "Weather Reporting Settings") {
368                 section {
369                 input "C_includeTemp", "bool", title: "Speak current temperature (from local forecast)", defaultValue: "false"
370                 input "C_localTemp", "capability.temperatureMeasurement", title: "Speak local temperature (from device)", required: false, multiple: false
371                 input "C_humidity", "capability.relativeHumidityMeasurement", title: "Speak local humidity (from device)", required: false, multiple: false
372                 input "C_weatherReport", "bool", title: "Speak today's weather forecast", defaultValue: "false"
373                 input "C_includeSunrise", "bool", title: "Speak today's sunrise", defaultValue: "false"
374                 input "C_includeSunset", "bool", title: "Speak today's sunset", defaultValue: "false"
375                 }
376         }
377 }
378
379 //Show "pageSetupScenarioD" page
380 def pageSetupScenarioD() {
381     dynamicPage(name: "pageSetupScenarioD") {
382                 section("Alarm settings") {
383                 input "ScenarioNameD", "text", title: "Scenario Name", multiple: false, required: true
384                         input "D_sonos", "capability.musicPlayer", title: "Choose a Sonos speaker", required: true, submitOnChange:true
385             input "D_volume", "number", title: "Alarm volume", description: "0-100%", required: false
386                 input "D_timeStart", "time", title: "Time to trigger alarm", required: true
387                 input "D_day", "enum", options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], title: "Alarm on certain days of the week...", multiple: true, required: false
388                 input "D_mode", "mode", title: "Alarm only during the following modes...", multiple: true, required: false
389                 input "D_alarmType", "enum", title: "Select a primary alarm type...", multiple: false, required: true, options: [[1:"Alarm sound (up to 20 seconds)"],[2:"Voice Greeting"],[3:"Music track/Internet Radio"]], submitOnChange:true
390             
391             if (D_alarmType != "3") {
392                 if (D_alarmType == "1"){
393                         input "D_secondAlarm", "enum", title: "Select a second alarm after the first is completed", multiple: false, required: false, options: [[1:"Voice Greeting"],[2:"Music track/Internet Radio"]], submitOnChange:true
394                 }
395                 if (D_alarmType == "2"){
396                         input "D_secondAlarmMusic", "bool", title: "Play a track after voice greeting", defaultValue: "false", required: false, submitOnChange:true
397                 }
398                 }
399         }
400         if (D_alarmType == "1"){
401                 section ("Alarm sound options"){
402                                 input "D_soundAlarm", "enum", title: "Play this sound...", required:false, multiple: false, options: [[1:"Alien-8 seconds"],[2:"Bell-12 seconds"], [3:"Buzzer-20 seconds"], [4:"Fire-20 seconds"], [5:"Rooster-2 seconds"], [6:"Siren-20 seconds"]]
403                                 input "D_soundLength", "number", title: "Maximum time to play sound (empty=use sound default)", description: "1-20", required: false        
404                 }
405                 }
406         
407         if (D_alarmType == "2" || (D_alarmType == "1" && D_secondAlarm =="1")) {
408                 section ("Voice greeting options") {
409                 input "D_wakeMsg", "text", title: "Wake voice message", defaultValue: "Good morning! It is %time% on %day%, %date%.", required: false
410                                 href "pageWeatherSettingsD", title: "Weather Reporting Settings", description: getWeatherDesc(D_weatherReport, D_includeSunrise, D_includeSunset, D_includeTemp, D_humidity, D_localTemp), state: greyOut1(D_weatherReport, D_includeSunrise, D_includeSunset, D_includeTemp, D_humidity, D_localTemp)          }
411         }
412         
413         if (D_alarmType == "3" || (D_alarmType == "1" && D_secondAlarm =="2") || (D_alarmType == "2" && D_secondAlarmMusic)){
414                 section ("Music track/internet radio options"){
415                 input "D_musicTrack", "enum", title: "Play this track/internet radio station", required:false, multiple: false, options: songOptions(D_sonos, 1)
416                 }
417         }
418         section("Devices to control in this alarm scenario") {
419                         input "D_switches", "capability.switch",title: "Control the following switches...", multiple: true, required: false, submitOnChange:true
420                         href "pageDimmersD", title: "Dimmer Settings", description: dimmerDesc(D_dimmers), state: greyOutOption(D_dimmers), submitOnChange:true
421             href "pageThermostatsD", title: "Thermostat Settings", description: thermostatDesc(D_thermostats, D_temperatureH, D_temperatureC), state: greyOutOption(D_thermostats), submitOnChange:true
422                 if ((D_switches || D_dimmers || D_thermostats) && (D_alarmType == "2" || (D_alarmType == "1" && D_secondAlarm =="1"))){
423                 input "D_confirmSwitches", "bool", title: "Confirm switches/thermostats status in voice message", defaultValue: "false"
424             }
425         }
426         section ("Other actions at alarm time"){
427             def phrases = location.helloHome?.getPhrases()*.label
428                         if (phrases) {
429                                 phrases.sort()
430                                 input "D_phrase", "enum", title: "Alarm triggers the following phrase", required: false, options: phrases, multiple: false, submitOnChange:true
431                                 if (D_phrase  && (D_alarmType == "2" || (D_alarmType == "1" && D_secondAlarm =="1"))){
432                         input "D_confirmPhrase", "bool", title: "Confirm Hello, Home phrase in voice message", defaultValue: "false"
433                 }
434             }
435             input "D_triggerMode", "mode", title: "Alarm triggers the following mode", required: false, submitOnChange:true
436                 if (D_triggerMode  && (D_alarmType == "2" || (D_alarmType == "1" && D_secondAlarm =="1"))){
437                 input "D_confirmMode", "bool", title: "Confirm mode in voice message", defaultValue: "false"
438             }
439         }
440     } 
441 }
442
443 page(name: "pageDimmersD", title: "Dimmer Settings") {
444         section {
445         input "D_dimmers", "capability.switchLevel", title: "Dim the following...", multiple: true, required: false     
446                 input "D_level", "enum", options: [[10:"10%"],[20:"20%"],[30:"30%"],[40:"40%"],[50:"50%"],[60:"60%"],[70:"70%"],[80:"80%"],[90:"90%"],[100:"100%"]],title: "Set dimmers to this level", multiple: false, required: false
447         }
448 }
449
450 page(name: "pageThermostatsD", title: "Thermostat Settings") {
451         section {
452         input "D_thermostats", "capability.thermostat", title: "Thermostat to control...", multiple: false, required: false
453         }
454     section {
455         input "D_temperatureH", "number", title: "Heating setpoint", required: false, description: "Temperature when in heat mode"
456                 input "D_temperatureC", "number", title: "Cooling setpoint", required: false, description: "Temperature when in cool mode"
457         }
458 }
459
460 def pageWeatherSettingsD() {
461         dynamicPage(name: "pageWeatherSettingsD", title: "Weather Reporting Settings") {
462                 section {
463                 input "D_includeTemp", "bool", title: "Speak current temperature (from local forecast)", defaultValue: "false"
464                         input "D_localTemp", "capability.temperatureMeasurement", title: "Speak local temperature (from device)", required: false, multiple: false
465                 input "D_humidity", "capability.relativeHumidityMeasurement", title: "Speak local humidity (from device)", required: false, multiple: false
466                 input "D_weatherReport", "bool", title: "Speak today's weather forecast", defaultValue: "false"
467                 input "D_includeSunrise", "bool", title: "Speak today's sunrise", defaultValue: "false"
468                 input "D_includeSunset", "bool", title: "Speak today's sunset", defaultValue: "false"
469                 }
470         }
471 }
472
473 page(name: "pageAbout", title: "About ${textAppName()}") {
474         section {
475             paragraph "${textVersion()}\n${textCopyright()}\n\n${textLicense()}\n"
476         }
477         section("Instructions") {
478             paragraph textHelp()
479         }
480 }
481
482 //--------------------------------------
483
484 def installed() {
485     initialize()
486 }
487
488 def updated() {
489     unschedule()
490     unsubscribe()
491     initialize()
492 }
493
494 def initialize() {
495         if (A_alarmType =="1"){
496         alarmSoundUri(A_soundAlarm, A_soundLength, 1)
497     }
498     if (B_alarmType =="1"){
499         alarmSoundUri(B_soundAlarm, B_soundLength, 2)
500     }
501     if (C_alarmType =="1"){
502         alarmSoundUri(C_soundAlarm, C_soundLength, 3)
503     }
504     if (D_alarmType =="1"){
505         alarmSoundUri(D_soundAlarm, D_soundLength, 4)
506     }
507     
508     if (alarmSummary && summarySonos) {
509                 subscribe(app, appTouchHandler)
510     }
511     if (ScenarioNameA && A_timeStart && A_sonos && A_alarmOn && A_alarmType){
512                 schedule (A_timeStart, alarm_A)
513         if (A_musicTrack){
514                 saveSelectedSong(A_sonos, A_musicTrack, 1)
515         }
516         }
517     if (ScenarioNameB && B_timeStart && B_sonos &&B_alarmOn && B_alarmType){
518                 schedule (B_timeStart, alarm_B)
519         if (B_musicTrack){
520                 saveSelectedSong(B_sonos, B_musicTrack, 2)
521         }
522         }
523     if (ScenarioNameC && C_timeStart && C_sonos && C_alarmOn && C_alarmType){
524                 schedule (C_timeStart, alarm_C)
525         if (C_musicTrack){
526                 saveSelectedSong(C_sonos, C_musicTrack, 3)
527         }
528         }
529         if (ScenarioNameD && D_timeStart && D_sonos && D_alarmOn && D_alarmType){
530                 schedule (D_timeStart, alarm_D)
531         if (D_musicTrack){
532                 saveSelectedSong(D_sonos, D_musicTrack, 4)
533         }
534         }    
535 }
536
537 //--------------------------------------
538
539 def alarm_A() {
540         if ((!A_mode || A_mode.contains(location.mode)) && getDayOk(A_day)) {   
541         if (A_switches || A_dimmers || A_thermostats) {
542                 def dimLevel = A_level as Integer
543             A_switches?.on()
544                 A_dimmers?.setLevel(dimLevel)
545             if (A_thermostats) {
546                         def thermostatState = A_thermostats.currentThermostatMode
547                                 if (thermostatState == "auto") {
548                                         A_thermostats.setHeatingSetpoint(A_temperatureH)
549                                         A_thermostats.setCoolingSetpoint(A_temperatureC)
550                                 }
551                                 else if (thermostatState == "heat") {
552                                         A_thermostats.setHeatingSetpoint(A_temperatureH)
553                                 log.info "Set $A_thermostats Heat $A_temperatureH°"
554                                 }
555                                 else {
556                                         A_thermostats.setCoolingSetpoint(A_temperatureC)
557                                 log.info "Set $A_thermostats Cool $A_temperatureC°"
558                 }         
559                 }
560         }
561         if (A_phrase) {
562                 location.helloHome.execute(A_phrase)
563         }
564         
565         if (A_triggerMode && location.mode != A_triggerMode) {
566                         if (location.modes?.find{it.name == A_triggerMode}) {
567                                 setLocationMode(A_triggerMode)
568                         } 
569             else {
570                                 log.debug "Unable to change to undefined mode '${A_triggerMode}'"
571                         }
572                 }
573         
574         if (A_volume) {
575                         A_sonos.setLevel(A_volume)
576                 }
577         
578         if (A_alarmType == "2" || (A_alarmType == "1" && A_secondAlarm =="1")) {
579                 state.fullMsgA = ""
580                 if (A_wakeMsg) {
581                         getGreeting(A_wakeMsg, 1)
582                 }
583             
584             if (A_weatherReport || A_humidity || A_includeTemp || A_localTemp) {
585                                 getWeatherReport(1, A_weatherReport, A_humidity, A_includeTemp, A_localTemp)
586                         }
587         
588                 if (A_includeSunrise || A_includeSunset) {
589                         getSunriseSunset(1, A_includeSunrise, A_includeSunset)
590                 }
591             
592             if ((A_switches || A_dimmers || A_thermostats) && A_confirmSwitches) {
593                                 getOnConfimation(A_switches, A_dimmers, A_thermostats, 1)
594                 }
595             
596             if (A_phrase && A_confirmPhrase) {
597                         getPhraseConfirmation(1, A_phrase)
598                 }
599             
600             if (A_triggerMode && A_confirmMode){
601                 getModeConfirmation(A_triggerMode, 1)
602             }
603           
604             state.soundA = textToSpeech(state.fullMsgA, true)
605                 }
606         
607         if (A_alarmType == "1"){
608                 if (A_secondAlarm == "1" && state.soundAlarmA){
609                 A_sonos.playSoundAndTrack (state.soundAlarmA.uri, state.soundAlarmA.duration, state.soundA.uri) 
610                 }
611             if (A_secondAlarm == "2" && state.selectedSongA && state.soundAlarmA){
612                 A_sonos.playSoundAndTrack (state.soundAlarmA.uri, state.soundAlarmA.duration, state.selectedSongA)
613             }
614             if (!A_secondAlarm){
615                 A_sonos.playTrack(state.soundAlarmA.uri)
616             }
617         }
618         
619         if (A_alarmType == "2") {
620                 if (A_secondAlarmMusic && state.selectedSongA){
621                 A_sonos.playSoundAndTrack (state.soundA.uri, state.soundA.duration, state.selectedSongA)
622             }
623             else {
624                 A_sonos.playTrack(state.soundA.uri)
625             }
626         }
627         
628         if (A_alarmType == "3") {
629                 A_sonos.playTrack(state.selectedSongA)
630         }
631         }
632 }
633
634 def alarm_B() {
635         if ((!B_mode || B_mode.contains(location.mode)) && getDayOk(B_day)) {   
636         if (B_switches || B_dimmers || B_thermostats) {
637                 def dimLevel = B_level as Integer
638             B_switches?.on()
639                 B_dimmers?.setLevel(dimLevel)
640             if (B_thermostats) {
641                         def thermostatState = B_thermostats.currentThermostatMode
642                                 if (thermostatState == "auto") {
643                                         B_thermostats.setHeatingSetpoint(B_temperatureH)
644                                         B_thermostats.setCoolingSetpoint(B_temperatureC)
645                                 }
646                                 else if (thermostatState == "heat") {
647                                         B_thermostats.setHeatingSetpoint(B_temperatureH)
648                                 log.info "Set $B_thermostats Heat $B_temperatureH°"
649                                 }
650                                 else {
651                                         B_thermostats.setCoolingSetpoint(B_temperatureC)
652                                 log.info "Set $B_thermostats Cool $B_temperatureC°"
653                 }         
654                 }
655         }
656         if (B_phrase) {
657                 location.helloHome.execute(B_phrase)
658         }
659         
660         if (B_triggerMode && location.mode != B_triggerMode) {
661                         if (location.modes?.find{it.name == B_triggerMode}) {
662                                 setLocationMode(B_triggerMode)
663                         } 
664             else {
665                                 log.debug "Unable to change to undefined mode '${B_triggerMode}'"
666                         }
667                 }
668         
669         if (B_volume) {
670                         B_sonos.setLevel(B_volume)
671                 }
672         
673         if (B_alarmType == "2" || (B_alarmType == "1" && B_secondAlarm =="1")) {
674                 state.fullMsgB = ""
675                 if (B_wakeMsg) {
676                         getGreeting(B_wakeMsg, 2)
677                 }
678             
679             if (B_weatherReport || B_humidity || B_includeTemp || B_localTemp) {
680                                 getWeatherReport(2, B_weatherReport, B_humidity, B_includeTemp, B_localTemp)
681                         }
682         
683                 if (B_includeSunrise || B_includeSunset) {
684                         getSunriseSunset(2, B_includeSunrise, B_includeSunset)
685                 }
686             
687             if ((B_switches || B_dimmers || B_thermostats) && B_confirmSwitches) {
688                                 getOnConfimation(B_switches, B_dimmers, B_thermostats, 2)
689                 }
690             
691             if (B_phrase && B_confirmPhrase) {
692                         getPhraseConfirmation(2, B_phrase)
693                 }
694             
695             if (B_triggerMode && B_confirmMode){
696                 getModeConfirmation(B_triggerMode, 2)
697             }
698           
699             state.soundB = textToSpeech(state.fullMsgB, true)
700                 }
701         
702         if (B_alarmType == "1"){
703                 if (B_secondAlarm == "1" && state.soundAlarmB) {
704                 B_sonos.playSoundAndTrack (state.soundAlarmB.uri, state.soundAlarmB.duration, state.soundB.uri) 
705                 }
706             if (B_secondAlarm == "2" && state.selectedSongB && state.soundAlarmB){
707                 B_sonos.playSoundAndTrack (state.soundAlarmB.uri, state.soundAlarmB.duration, state.selectedSongB)
708             }
709             if (!B_secondAlarm){
710                 B_sonos.playTrack(state.soundAlarmB.uri)
711             }
712         }
713         
714         if (B_alarmType == "2") {
715                 if (B_secondAlarmMusic && state.selectedSongB){
716                 B_sonos.playSoundAndTrack (state.soundB.uri, state.soundB.duration, state.selectedSongB)
717             }
718             else {
719                 B_sonos.playTrack(state.soundB.uri)
720             }
721         }
722         
723         if (B_alarmType == "3") {
724                 B_sonos.playTrack(state.selectedSongB)
725         }
726         }
727 }
728
729 def alarm_C() {
730         if ((!C_mode || C_mode.contains(location.mode)) && getDayOk(C_day)) {   
731         if (C_switches || C_dimmers || C_thermostats) {
732                 def dimLevel = C_level as Integer
733             C_switches?.on()
734                 C_dimmers?.setLevel(dimLevel)
735             if (C_thermostats) {
736                         def thermostatState = C_thermostats.currentThermostatMode
737                                 if (thermostatState == "auto") {
738                                         C_thermostats.setHeatingSetpoint(C_temperatureH)
739                                         C_thermostats.setCoolingSetpoint(C_temperatureC)
740                                 }
741                                 else if (thermostatState == "heat") {
742                                         C_thermostats.setHeatingSetpoint(C_temperatureH)
743                                 log.info "Set $C_thermostats Heat $C_temperatureH°"
744                                 }
745                                 else {
746                                         C_thermostats.setCoolingSetpoint(C_temperatureC)
747                                 log.info "Set $C_thermostats Cool $C_temperatureC°"
748                 }         
749                 }
750         }
751         if (C_phrase) {
752                 location.helloHome.execute(C_phrase)
753         }
754         
755         if (C_triggerMode && location.mode != C_triggerMode) {
756                         if (location.modes?.find{it.name == C_triggerMode}) {
757                                 setLocationMode(C_triggerMode)
758                         } 
759             else {
760                                 log.debug "Unable to change to undefined mode '${C_triggerMode}'"
761                         }
762                 }
763         
764         if (C_volume) {
765                         C_sonos.setLevel(C_volume)
766                 }
767         
768         if (C_alarmType == "2" || (C_alarmType == "1" && C_secondAlarm =="1")) {
769                 state.fullMsgC = ""
770                 if (C_wakeMsg) {
771                         getGreeting(C_wakeMsg, 3)
772                 }
773             
774             if (C_weatherReport || C_humidity || C_includeTemp || C_localTemp) {
775                                 getWeatherReport(3, C_weatherReport, C_humidity, C_includeTemp, C_localTemp)
776                         }
777         
778                 if (C_includeSunrise || C_includeSunset) {
779                         getSunriseSunset(3, C_includeSunrise, C_includeSunset)
780                 }
781             
782             if ((C_switches || C_dimmers || C_thermostats) && C_confirmSwitches) {
783                                 getOnConfimation(C_switches, C_dimmers, C_thermostats, 3)
784                 }
785             
786             if (C_phrase && C_confirmPhrase) {
787                         getPhraseConfirmation(3, C_phrase)
788                 }
789             
790             if (C_triggerMode && C_confirmMode){
791                 getModeConfirmation(C_triggerMode, 3)
792             }
793           
794             state.soundC = textToSpeech(state.fullMsgC, true)
795                 }
796         
797         if (C_alarmType == "1"){
798                 if (C_secondAlarm == "1" && state.soundAlarmC){
799                 C_sonos.playSoundAndTrack (state.soundAlarmC.uri, state.soundAlarmC.duration, state.soundC.uri) 
800                 }
801             if (C_secondAlarm == "2" && state.selectedSongC && state.soundAlarmC){
802                 C_sonos.playSoundAndTrack (state.soundAlarmC.uri, state.soundAlarmC.duration, state.selectedSongC)
803             }
804             if (!C_secondAlarm){
805                 C_sonos.playTrack(state.soundAlarmC.uri)
806             }
807         }
808         
809         if (C_alarmType == "2") {
810                 if (C_secondAlarmMusic && state.selectedSongC){
811                 C_sonos.playSoundAndTrack (state.soundC.uri, state.soundC.duration, state.selectedSongC)
812             }
813             else {
814                 C_sonos.playTrack(state.soundC.uri)
815             }
816         }
817         
818         if (C_alarmType == "3") {
819                 C_sonos.playTrack(state.selectedSongC)
820         }
821         }
822 }
823
824 def alarm_D() {
825         if ((!D_mode || D_mode.contains(location.mode)) && getDayOk(D_day)) {   
826         if (D_switches || D_dimmers || D_thermostats) {
827                 def dimLevel = D_level as Integer
828             D_switches?.on()
829                 D_dimmers?.setLevel(dimLevel)
830             if (D_thermostats) {
831                         def thermostatState = D_thermostats.currentThermostatMode
832                                 if (thermostatState == "auto") {
833                                         D_thermostats.setHeatingSetpoint(D_temperatureH)
834                                         D_thermostats.setCoolingSetpoint(D_temperatureC)
835                                 }
836                                 else if (thermostatState == "heat") {
837                                         D_thermostats.setHeatingSetpoint(D_temperatureH)
838                                 log.info "Set $D_thermostats Heat $D_temperatureH°"
839                                 }
840                                 else {
841                                         D_thermostats.setCoolingSetpoint(D_temperatureC)
842                                 log.info "Set $D_thermostats Cool $D_temperatureC°"
843                 }         
844                 }
845         }
846         if (D_phrase) {
847                 location.helloHome.execute(D_phrase)
848         }
849         
850         if (D_triggerMode && location.mode != D_triggerMode) {
851                         if (location.modes?.find{it.name == D_triggerMode}) {
852                                 setLocationMode(D_triggerMode)
853                         } 
854             else {
855                                 log.debug "Unable to change to undefined mode '${D_triggerMode}'"
856                         }
857                 }
858         
859         if (D_volume) {
860                         D_sonos.setLevel(D_volume)
861                 }
862         
863         if (D_alarmType == "2" || (D_alarmType == "1" && D_secondAlarm =="1")) {
864                 state.fullMsgD = ""
865                 if (D_wakeMsg) {
866                         getGreeting(D_wakeMsg, 4)
867                 }
868             
869             if (D_weatherReport || D_humidity || D_includeTemp || D_localTemp) {
870                                 getWeatherReport(4, D_weatherReport, D_humidity, D_includeTemp, D_localTemp)
871                         }
872         
873                 if (D_includeSunrise || D_includeSunset) {
874                         getSunriseSunset(4, D_includeSunrise, D_includeSunset)
875                 }
876             
877             if ((D_switches || D_dimmers || D_thermostats) && D_confirmSwitches) {
878                                 getOnConfimation(D_switches, D_dimmers, D_thermostats, 4)
879                 }
880             
881             if (D_phrase && D_confirmPhrase) {
882                         getPhraseConfirmation(4, D_phrase)
883                 }
884             
885             if (D_triggerMode && D_confirmMode){
886                 getModeConfirmation(D_triggerMode, 4)
887             }
888           
889             state.soundD = textToSpeech(state.fullMsgD, true)
890                 }
891         
892         if (D_alarmType == "1"){
893                 if (D_secondAlarm == "1" && state.soundAlarmD){
894                 D_sonos.playSoundAndTrack (state.soundAlarmD.uri, state.soundAlarmD.duration, state.soundD.uri) 
895                 }
896             if (D_secondAlarm == "2" && state.selectedSongD && state.soundAlarmD){
897                 D_sonos.playSoundAndTrack (state.soundAlarmD.uri, state.soundAlarmD.duration, state.selectedSongD)
898             }
899             if (!D_secondAlarm){
900                 D_sonos.playTrack(state.soundAlarmD.uri)
901             }
902         }
903         
904         if (D_alarmType == "2") {
905                 if (D_secondAlarmMusic && state.selectedSongD){
906                 D_sonos.playSoundAndTrack (state.soundD.uri, state.soundD.duration, state.selectedSongD)
907             }
908             else {
909                 D_sonos.playTrack(state.soundD.uri)
910             }
911         }
912         
913         if (D_alarmType == "3") {
914                 D_sonos.playTrack(state.selectedSongD)
915         }
916         }
917 }
918
919 def appTouchHandler(evt){
920         if (!summaryMode || summaryMode.contains(location.mode)) {
921         state.summaryMsg = "The following is a summary of the alarm settings. "
922                 getSummary (A_alarmOn, ScenarioNameA, A_timeStart, 1)
923         getSummary (B_alarmOn, ScenarioNameB, B_timeStart, 2)
924         getSummary (C_alarmOn, ScenarioNameC, C_timeStart, 3)
925         getSummary (D_alarmOn, ScenarioNameD, D_timeStart, 4)
926         
927         log.debug "Summary message = ${state.summaryMsg}"
928                 def summarySound = textToSpeech(state.summaryMsg, true)
929         if (summaryVolume) {
930                 summarySonos.setLevel(summaryVolume)
931                 }
932         summarySonos.playTrack(summarySound.uri)
933         }
934 }
935
936 def getSummary (alarmOn, scenarioName, timeStart, num){
937     if (alarmOn && scenarioName) {
938         state.summaryMsg = "${state.summaryMsg} Alarm ${num}, ${scenarioName}, set for ${parseDate(timeStart,"", "h:mm a" )}, is enabled. "
939     }
940     else if (summaryDisabled && !alarmOn && scenarioName) {
941         state.summaryMsg = "${state.summaryMsg} Alarm ${num}, ${scenarioName}, set for ${parseDate(timeStart,"", "h:mm a")}, is disabled. "
942     }
943     else if (summaryDisabled && !scenarioName) {
944         state.summaryMsg = "${state.summaryMsg} Alarm ${num} is not configured. "
945     }
946 }
947
948 //--------------------------------------
949
950 def getDesc(timeStart, sonos, day, mode) {
951         def desc = "Tap to set alarm"
952         if (timeStart) {
953         desc = "Alarm set to " + parseDate(timeStart,"", "h:mm a") +" on ${sonos}"
954                 
955         def dayListSize = day ? day.size() : 7
956              
957         if (day && dayListSize < 7) {
958                 desc = desc + " on"
959             for (dayName in day) {
960                                 desc = desc + " ${dayName}"
961                         dayListSize = dayListSize -1
962                 if (dayListSize) {
963                         desc = "${desc}, "
964                         }
965                 }
966         }
967         else {
968                 desc = desc + " every day"
969         }
970         
971         if (mode) {
972                 def modeListSize = mode.size()
973                 def modePrefix =" in the following modes: "
974                 if (modeListSize == 1) {
975                         modePrefix = " in the following mode: "
976                 }
977             desc = desc + "${modePrefix}" 
978                 for (modeName in mode) {
979                         desc = desc + "'${modeName}'"
980                         modeListSize = modeListSize -1
981                 if (modeListSize) {
982                         desc = "${desc}, "
983                 }
984                 else {
985                         desc = "${desc}"
986                         }
987                 }
988                 }
989         else {
990                 desc = desc + " in all modes"
991         }
992     }
993         desc    
994 }
995 def greyOut(scenario, sonos, alarmTime, alarmOn, alarmType){
996         def result = scenario && sonos  && alarmTime && alarmOn && alarmType ? "complete" : ""
997 }
998
999 def greyOut1(param1, param2, param3, param4, param5, param6){
1000         def result = param1 || param2 || param3 || param4 || param5 || param6 ? "complete" : ""
1001 }
1002
1003 def getWeatherDesc(param1, param2, param3, param4, param5, param6) {
1004         def title = param1 || param2 || param3 || param4 || param5 || param6 ? "Tap to edit weather reporting options" : "Tap to setup weather reporting options"
1005 }
1006
1007 def greyOutOption(param){
1008         def result = param ? "complete" : ""
1009 }
1010
1011 def getTitle(scenario, num) {
1012         def title = scenario ? scenario : "Alarm ${num} not configured"
1013 }
1014
1015 def dimmerDesc(dimmer){
1016         def desc = dimmer ? "Tap to edit dimmer settings" : "Tap to set dimmer setting"
1017 }
1018
1019 def thermostatDesc(thermostat, heating, cooling){
1020         def tempText 
1021     if (heating || cooling){
1022         if (heating){
1023                 tempText = "${heating} heat"
1024         }
1025         if (cooling){
1026                 tempText = "${cooling} cool"
1027         }
1028                 if (heating && cooling) {
1029                 tempText ="${heating} heat / ${cooling} cool"
1030         }
1031     }
1032     else {
1033         tempText="Tap to edit thermostat settings"
1034     }
1035     
1036     def desc = thermostat ? "${tempText}" : "Tap to set thermostat settings"
1037         return desc
1038 }
1039
1040 private getDayOk(dayList) {
1041         def result = true
1042         if (dayList) {
1043                 result = dayList.contains(getDay())
1044         }
1045         result
1046 }
1047
1048 private getDay(){
1049         def df = new java.text.SimpleDateFormat("EEEE")
1050         if (location.timeZone) {
1051                 df.setTimeZone(location.timeZone)
1052         }
1053         else {
1054                 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
1055         }
1056         def day = df.format(new Date())
1057 }
1058
1059 private parseDate(date, epoch, type){
1060     def parseDate = ""
1061     if (epoch){
1062         long longDate = Long.valueOf(epoch).longValue()
1063         parseDate = new Date(longDate).format("yyyy-MM-dd'T'HH:mm:ss.SSSZ", location.timeZone)
1064     }
1065     else {
1066         parseDate = date
1067     }
1068     new Date().parse("yyyy-MM-dd'T'HH:mm:ss.SSSZ", parseDate).format("${type}", timeZone(parseDate))
1069 }
1070
1071 private getSunriseSunset(scenario, includeSunrise, includeSunset){
1072         if (location.timeZone || zipCode) {
1073         def todayDate = new Date()
1074         def s = getSunriseAndSunset(zipcode: zipCode, date: todayDate)  
1075                 def riseTime = parseDate("", s.sunrise.time, "h:mm a")
1076                 def setTime = parseDate ("", s.sunset.time, "h:mm a")
1077                 def msg = ""
1078         def currTime = now()
1079         def verb1 = currTime >= s.sunrise.time ? "rose" : "will rise"
1080         def verb2 = currTime >= s.sunset.time ? "set" : "will set"
1081        
1082         if (includeSunrise && includeSunset) {
1083                         msg = "The sun ${verb1} this morning at ${riseTime} and ${verb2} at ${setTime}. "
1084         }
1085         else if (includeSunrise && !includeSunset) {
1086                 msg = "The sun ${verb1} this morning at ${riseTime}. "
1087         }
1088         else if (!includeSunrise && includeSunset) {
1089                 msg = "The sun ${verb2} tonight at ${setTime}. "
1090         }
1091         compileMsg(msg, scenario)
1092         }
1093         else {
1094                 msg = "Please set the location of your hub with the SmartThings mobile app, or enter a zip code to receive sunset and sunrise information. "
1095                 compileMsg(msg, scenario)
1096         }
1097 }
1098
1099 private getGreeting(msg, scenario) {
1100         def day = getDay()
1101     def time = parseDate("", now(), "h:mm a")
1102     def month = parseDate("", now(), "MMMM")
1103     def year = parseDate("", now(), "yyyy")
1104     def dayNum = parseDate("", now(), "dd")
1105         msg = msg.replace('%day%', day)
1106     msg = msg.replace('%date%', "${month} ${dayNum}, ${year}")
1107     msg = msg.replace('%time%', "${time}")
1108     msg = "${msg} "
1109     compileMsg(msg, scenario)
1110 }
1111
1112 private getWeatherReport(scenario, weatherReport, humidity, includeTemp, localTemp) {
1113         if (location.timeZone || zipCode) {
1114                 def isMetric = location.temperatureScale == "C"
1115         def sb = new StringBuilder()
1116                 
1117         if (includeTemp){
1118                 def current = getWeatherFeature("conditions", zipCode)
1119                 if (isMetric) {
1120                         sb << "The current temperature is ${Math.round(current.current_observation.temp_c)} degrees. "
1121                 }
1122                 else {
1123                         sb << "The current temperature is ${Math.round(current.current_observation.temp_f)} degrees. "
1124                 }
1125         }
1126         
1127         if (localTemp){
1128                         sb << "The local temperature is ${Math.round(localTemp.currentTemperature)} degrees. "
1129         }
1130
1131         if (humidity) {
1132                 sb << "The local relative humidity is ${humidity.currentValue("humidity")}%. "
1133         }
1134         
1135         if (weatherReport) {
1136                 def weather = getWeatherFeature("forecast", zipCode)
1137             
1138             sb << "Today's forecast is "
1139                         if (isMetric) {
1140                         sb << weather.forecast.txt_forecast.forecastday[0].fcttext_metric 
1141                 }
1142                 else {
1143                         sb << weather.forecast.txt_forecast.forecastday[0].fcttext
1144                 }
1145         }
1146         
1147                 def msg = sb.toString()
1148         msg = msg.replaceAll(/([0-9]+)C/,'$1 degrees')
1149         msg = msg.replaceAll(/([0-9]+)F/,'$1 degrees')
1150         compileMsg(msg, scenario)               
1151         }
1152         else {
1153                 msg = "Please set the location of your hub with the SmartThings mobile app, or enter a zip code to receive weather forecasts."
1154                 compileMsg(msg, scenario)
1155     }
1156 }
1157
1158 private getOnConfimation(switches, dimmers, thermostats, scenario) {
1159         def msg = ""
1160     if ((switches || dimmers) && !thermostats) {
1161         msg = "All switches"    
1162     }
1163     if (!switches && !dimmers && thermostats) {
1164         msg = "All Thermostats"
1165     }
1166     if ((switches || dimmers) && thermostats) {
1167         msg = "All switches and thermostats"
1168     } 
1169     msg = "${msg} are now on and set. "
1170     compileMsg(msg, scenario)
1171 }
1172
1173 private getPhraseConfirmation(scenario, phrase) {
1174         def msg="The Smart Things Hello Home phrase, ${phrase}, has been activated. "
1175         compileMsg(msg, scenario)
1176 }
1177
1178 private getModeConfirmation(mode, scenario) {
1179         def msg="The Smart Things mode is now being set to, ${mode}. "
1180         compileMsg(msg, scenario)
1181 }
1182
1183 private compileMsg(msg, scenario) {
1184         log.debug "msg = ${msg}"
1185         if (scenario == 1) {state.fullMsgA = state.fullMsgA + "${msg}"}
1186         if (scenario == 2) {state.fullMsgB = state.fullMsgB + "${msg}"}
1187         if (scenario == 3) {state.fullMsgC = state.fullMsgC + "${msg}"}
1188         if (scenario == 4) {state.fullMsgD = state.fullMsgD + "${msg}"}
1189 }
1190
1191 private alarmSoundUri(selection, length, scenario){
1192         def soundUri = ""
1193         def soundLength = ""
1194     switch(selection) {
1195         case "1":
1196                 soundLength = length >0 && length < 8 ? length : 8
1197             soundUri = [uri: "https://raw.githubusercontent.com/MichaelStruck/SmartThings/master/Other-SmartApps/Talking-Alarm-Clock/AlarmSounds/AlarmAlien.mp3", duration: "${soundLength}"]
1198                 break
1199         case "2":
1200                 soundLength = length >0 && length < 12 ? length : 12
1201             soundUri = [uri: "https://raw.githubusercontent.com/MichaelStruck/SmartThings/master/Other-SmartApps/Talking-Alarm-Clock/AlarmSounds/AlarmBell.mp3", duration: "${soundLength}"]
1202                 break
1203         case "3":
1204                 soundLength = length >0 && length < 20 ? length : 20
1205             soundUri = [uri: "https://raw.githubusercontent.com/MichaelStruck/SmartThings/master/Other-SmartApps/Talking-Alarm-Clock/AlarmSounds/AlarmBuzzer.mp3", duration: "${soundLength}"]
1206                 break
1207         case "4":
1208                 soundLength = length >0 && length < 20 ? length : 20
1209             soundUri = [uri: "https://raw.githubusercontent.com/MichaelStruck/SmartThings/master/Other-SmartApps/Talking-Alarm-Clock/AlarmSounds/AlarmFire.mp3", duration: "${soundLength}"]
1210                 break
1211         case "5":
1212                 soundLength = length >0 && length < 2 ? length : 2
1213             soundUri = [uri: "https://raw.githubusercontent.com/MichaelStruck/SmartThings/master/Other-SmartApps/Talking-Alarm-Clock/AlarmSounds/AlarmRooster.mp3", duration: "${soundLength}"]
1214                 break
1215         case "6":
1216                 soundLength = length >0 && length < 20 ? length : 20
1217             soundUri = [uri: "https://raw.githubusercontent.com/MichaelStruck/SmartThings/master/Other-SmartApps/Talking-Alarm-Clock/AlarmSounds/AlarmSiren.mp3", duration: "${soundLength}"]
1218                         break
1219     }
1220         if (scenario == 1) {state.soundAlarmA = soundUri}
1221         if (scenario == 2) {state.soundAlarmB = soundUri}
1222         if (scenario == 3) {state.soundAlarmC = soundUri}
1223         if (scenario == 4) {state.soundAlarmD = soundUri}               
1224 }       
1225
1226 //Sonos Aquire Track from SmartThings code
1227 private songOptions(sonos, scenario) {
1228         if (sonos){
1229         // Make sure current selection is in the set
1230                 def options = new LinkedHashSet()
1231                 if (scenario == 1){
1232                 if (state.selectedSongA?.station) {
1233                                 options << state.selectedSongA.station
1234                         }
1235                         else if (state.selectedSongA?.description) {
1236                                 options << state.selectedSongA.description
1237                         }
1238         }
1239         if (scenario == 2){
1240                 if (state.selectedSongB?.station) {
1241                                 options << state.selectedSongB.station
1242                         }
1243                         else if (state.selectedSongB?.description) {
1244                                 options << state.selectedSongB.description
1245                         }
1246         }
1247         if (scenario == 3){
1248                 if (state.selectedSongC?.station) {
1249                                 options << state.selectedSongC.station
1250                         }
1251                         else if (state.selectedSongC?.description) {
1252                                 options << state.selectedSongC.description
1253                         }
1254         }
1255         if (scenario == 4){
1256                 if (state.selectedSongD?.station) {
1257                                 options << state.selectedSongD.station
1258                         }
1259                         else if (state.selectedSongD?.description) {
1260                                 options << state.selectedSongD.description
1261                         }
1262         }
1263                 // Query for recent tracks
1264                 def states = sonos.statesSince("trackData", new Date(0), [max:30])
1265                 def dataMaps = states.collect{it.jsonValue}
1266                 options.addAll(dataMaps.collect{it.station})
1267
1268                 log.trace "${options.size()} songs in list"
1269                 options.take(20) as List
1270         }
1271 }
1272
1273 private saveSelectedSong(sonos, song, scenario) {
1274         try {
1275                 def thisSong = song
1276                 log.info "Looking for $thisSong"
1277                 def songs = sonos.statesSince("trackData", new Date(0), [max:30]).collect{it.jsonValue}
1278                 log.info "Searching ${songs.size()} records"
1279
1280                 def data = songs.find {s -> s.station == thisSong}
1281                 log.info "Found ${data?.station}"
1282                 if (data) {
1283                         if (scenario == 1) {state.selectedSongA = data}
1284             if (scenario == 2) {state.selectedSongB = data}
1285             if (scenario == 3) {state.selectedSongC = data}
1286             if (scenario == 4) {state.selectedSongD = data}
1287                         log.debug "Selected song for Scenario ${scenario} = ${data}"
1288                 }
1289                 else if (song == state.selectedSongA?.station || song == state.selectedSongB?.station || song == state.selectedSongC?.station || song == state.selectedSongD?.station) {
1290                         log.debug "Selected existing entry '$song', which is no longer in the last 20 list"
1291                 }
1292                 else {
1293                         log.warn "Selected song '$song' not found"
1294                 }
1295         }
1296         catch (Throwable t) {
1297                 log.error t
1298         }
1299 }
1300
1301 //Version/Copyright/Information/Help
1302
1303 private def textAppName() {
1304         def text = "Talking Alarm Clock"
1305 }       
1306
1307 private def textVersion() {
1308     def text = "Version 1.4.5 (06/17/2015)"
1309 }
1310
1311 private def textCopyright() {
1312     def text = "Copyright © 2015 Michael Struck"
1313 }
1314
1315 private def textLicense() {
1316     def text =
1317                 "Licensed under the Apache License, Version 2.0 (the 'License'); "+
1318                 "you may not use this file except in compliance with the License. "+
1319                 "You may obtain a copy of the License at"+
1320                 "\n\n"+
1321                 "    http://www.apache.org/licenses/LICENSE-2.0"+
1322                 "\n\n"+
1323                 "Unless required by applicable law or agreed to in writing, software "+
1324                 "distributed under the License is distributed on an 'AS IS' BASIS, "+
1325                 "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. "+
1326                 "See the License for the specific language governing permissions and "+
1327                 "limitations under the License."
1328 }
1329
1330 private def textHelp() {
1331         def text =
1332         "Within each alarm scenario, choose a Sonos speaker, an alarm time and alarm type along with " +
1333         "switches, dimmers and thermostat to control when the alarm is triggered. Hello, Home phrases and modes can be triggered at alarm time. "+
1334         "You also have the option of setting up different alarm sounds, tracks and a personalized spoken greeting that can include a weather report. " +
1335         "Variables that can be used in the voice greeting include %day%, %time% and %date%.\n\n"+
1336         "From the main SmartApp convenience page, tapping the 'Talking Alarm Clock' icon (if enabled within the app) will "+
1337         "speak a summary of the alarms enabled or disabled without having to go into the application itself. This " +
1338         "functionality is optional and can be configured from the main setup page."
1339 }
1340