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
16 * Copyright 2015 Michael Struck - Uses code from Lighting Director by Tim Slagle & Michael Struck
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:
21 * http://www.apache.org/licenses/LICENSE-2.0
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.
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"
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"
54 dynamicPage(name: "pageMain", install: true, uninstall: true) {
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
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
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
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
79 section([title:"Options", mobileOnly:true]) {
80 input "alarmSummary", "bool", title: "Enable Alarm Summary", defaultValue: "false", submitOnChange:true
82 href "pageAlarmSummary", title: "Alarm Summary Settings", description: "Tap to configure alarm summary settings", state: "complete"
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"
91 page(name: "pageAlarmSummary", title: "Alarm Summary Settings") {
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
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
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
115 if (A_alarmType == "2"){
116 input "A_secondAlarmMusic", "bool", title: "Play a track after voice greeting", defaultValue: "false", required: false, submitOnChange:true
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
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)
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)
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"
145 section ("Other actions at alarm time"){
146 def phrases = location.helloHome?.getPhrases()*.label
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"
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"
162 page(name: "pageDimmersA", title: "Dimmer Settings") {
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
169 page(name: "pageThermostatsA", title: "Thermostat Settings") {
171 input "A_thermostats", "capability.thermostat", title: "Thermostat to control...", multiple: false, required: false
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"
179 def pageWeatherSettingsA() {
180 dynamicPage(name: "pageWeatherSettingsA", title: "Weather Reporting Settings") {
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"
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
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
208 if (B_alarmType == "2"){
209 input "B_secondAlarmMusic", "bool", title: "Play a track after voice greeting", defaultValue: "false", required: false, submitOnChange:true
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
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)
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)
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"
238 section ("Other actions at alarm time"){
239 def phrases = location.helloHome?.getPhrases()*.label
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"
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"
255 page(name: "pageDimmersB", title: "Dimmer Settings") {
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
262 page(name: "pageThermostatsB", title: "Thermostat Settings") {
264 input "B_thermostats", "capability.thermostat", title: "Thermostat to control...", multiple: false, required: false
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"
272 def pageWeatherSettingsB() {
273 dynamicPage(name: "pageWeatherSettingsB", title: "Weather Reporting Settings") {
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"
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
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
301 if (C_alarmType == "2"){
302 input "C_secondAlarmMusic", "bool", title: "Play a track after voice greeting", defaultValue: "false", required: false, submitOnChange:true
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
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) }
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)
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"
332 section ("Other actions at alarm time"){
333 def phrases = location.helloHome?.getPhrases()*.label
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"
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"
349 page(name: "pageDimmersC", title: "Dimmer Settings") {
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
356 page(name: "pageThermostatsC", title: "Thermostat Settings") {
358 input "C_thermostats", "capability.thermostat", title: "Thermostat to control...", multiple: false, required: false
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"
366 def pageWeatherSettingsC() {
367 dynamicPage(name: "pageWeatherSettingsC", title: "Weather Reporting Settings") {
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"
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
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
395 if (D_alarmType == "2"){
396 input "D_secondAlarmMusic", "bool", title: "Play a track after voice greeting", defaultValue: "false", required: false, submitOnChange:true
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
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) }
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)
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"
426 section ("Other actions at alarm time"){
427 def phrases = location.helloHome?.getPhrases()*.label
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"
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"
443 page(name: "pageDimmersD", title: "Dimmer Settings") {
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
450 page(name: "pageThermostatsD", title: "Thermostat Settings") {
452 input "D_thermostats", "capability.thermostat", title: "Thermostat to control...", multiple: false, required: false
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"
460 def pageWeatherSettingsD() {
461 dynamicPage(name: "pageWeatherSettingsD", title: "Weather Reporting Settings") {
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"
473 page(name: "pageAbout", title: "About ${textAppName()}") {
475 paragraph "${textVersion()}\n${textCopyright()}\n\n${textLicense()}\n"
477 section("Instructions") {
482 //--------------------------------------
495 if (A_alarmType =="1"){
496 alarmSoundUri(A_soundAlarm, A_soundLength, 1)
498 if (B_alarmType =="1"){
499 alarmSoundUri(B_soundAlarm, B_soundLength, 2)
501 if (C_alarmType =="1"){
502 alarmSoundUri(C_soundAlarm, C_soundLength, 3)
504 if (D_alarmType =="1"){
505 alarmSoundUri(D_soundAlarm, D_soundLength, 4)
508 if (alarmSummary && summarySonos) {
509 subscribe(app, appTouchHandler)
511 if (ScenarioNameA && A_timeStart && A_sonos && A_alarmOn && A_alarmType){
512 schedule (A_timeStart, alarm_A)
514 saveSelectedSong(A_sonos, A_musicTrack, 1)
517 if (ScenarioNameB && B_timeStart && B_sonos &&B_alarmOn && B_alarmType){
518 schedule (B_timeStart, alarm_B)
520 saveSelectedSong(B_sonos, B_musicTrack, 2)
523 if (ScenarioNameC && C_timeStart && C_sonos && C_alarmOn && C_alarmType){
524 schedule (C_timeStart, alarm_C)
526 saveSelectedSong(C_sonos, C_musicTrack, 3)
529 if (ScenarioNameD && D_timeStart && D_sonos && D_alarmOn && D_alarmType){
530 schedule (D_timeStart, alarm_D)
532 saveSelectedSong(D_sonos, D_musicTrack, 4)
537 //--------------------------------------
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
544 A_dimmers?.setLevel(dimLevel)
546 def thermostatState = A_thermostats.currentThermostatMode
547 if (thermostatState == "auto") {
548 A_thermostats.setHeatingSetpoint(A_temperatureH)
549 A_thermostats.setCoolingSetpoint(A_temperatureC)
551 else if (thermostatState == "heat") {
552 A_thermostats.setHeatingSetpoint(A_temperatureH)
553 log.info "Set $A_thermostats Heat $A_temperatureH°"
556 A_thermostats.setCoolingSetpoint(A_temperatureC)
557 log.info "Set $A_thermostats Cool $A_temperatureC°"
562 location.helloHome.execute(A_phrase)
565 if (A_triggerMode && location.mode != A_triggerMode) {
566 if (location.modes?.find{it.name == A_triggerMode}) {
567 setLocationMode(A_triggerMode)
570 log.debug "Unable to change to undefined mode '${A_triggerMode}'"
575 A_sonos.setLevel(A_volume)
578 if (A_alarmType == "2" || (A_alarmType == "1" && A_secondAlarm =="1")) {
581 getGreeting(A_wakeMsg, 1)
584 if (A_weatherReport || A_humidity || A_includeTemp || A_localTemp) {
585 getWeatherReport(1, A_weatherReport, A_humidity, A_includeTemp, A_localTemp)
588 if (A_includeSunrise || A_includeSunset) {
589 getSunriseSunset(1, A_includeSunrise, A_includeSunset)
592 if ((A_switches || A_dimmers || A_thermostats) && A_confirmSwitches) {
593 getOnConfimation(A_switches, A_dimmers, A_thermostats, 1)
596 if (A_phrase && A_confirmPhrase) {
597 getPhraseConfirmation(1, A_phrase)
600 if (A_triggerMode && A_confirmMode){
601 getModeConfirmation(A_triggerMode, 1)
604 state.soundA = textToSpeech(state.fullMsgA, true)
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)
611 if (A_secondAlarm == "2" && state.selectedSongA && state.soundAlarmA){
612 A_sonos.playSoundAndTrack (state.soundAlarmA.uri, state.soundAlarmA.duration, state.selectedSongA)
615 A_sonos.playTrack(state.soundAlarmA.uri)
619 if (A_alarmType == "2") {
620 if (A_secondAlarmMusic && state.selectedSongA){
621 A_sonos.playSoundAndTrack (state.soundA.uri, state.soundA.duration, state.selectedSongA)
624 A_sonos.playTrack(state.soundA.uri)
628 if (A_alarmType == "3") {
629 A_sonos.playTrack(state.selectedSongA)
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
639 B_dimmers?.setLevel(dimLevel)
641 def thermostatState = B_thermostats.currentThermostatMode
642 if (thermostatState == "auto") {
643 B_thermostats.setHeatingSetpoint(B_temperatureH)
644 B_thermostats.setCoolingSetpoint(B_temperatureC)
646 else if (thermostatState == "heat") {
647 B_thermostats.setHeatingSetpoint(B_temperatureH)
648 log.info "Set $B_thermostats Heat $B_temperatureH°"
651 B_thermostats.setCoolingSetpoint(B_temperatureC)
652 log.info "Set $B_thermostats Cool $B_temperatureC°"
657 location.helloHome.execute(B_phrase)
660 if (B_triggerMode && location.mode != B_triggerMode) {
661 if (location.modes?.find{it.name == B_triggerMode}) {
662 setLocationMode(B_triggerMode)
665 log.debug "Unable to change to undefined mode '${B_triggerMode}'"
670 B_sonos.setLevel(B_volume)
673 if (B_alarmType == "2" || (B_alarmType == "1" && B_secondAlarm =="1")) {
676 getGreeting(B_wakeMsg, 2)
679 if (B_weatherReport || B_humidity || B_includeTemp || B_localTemp) {
680 getWeatherReport(2, B_weatherReport, B_humidity, B_includeTemp, B_localTemp)
683 if (B_includeSunrise || B_includeSunset) {
684 getSunriseSunset(2, B_includeSunrise, B_includeSunset)
687 if ((B_switches || B_dimmers || B_thermostats) && B_confirmSwitches) {
688 getOnConfimation(B_switches, B_dimmers, B_thermostats, 2)
691 if (B_phrase && B_confirmPhrase) {
692 getPhraseConfirmation(2, B_phrase)
695 if (B_triggerMode && B_confirmMode){
696 getModeConfirmation(B_triggerMode, 2)
699 state.soundB = textToSpeech(state.fullMsgB, true)
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)
706 if (B_secondAlarm == "2" && state.selectedSongB && state.soundAlarmB){
707 B_sonos.playSoundAndTrack (state.soundAlarmB.uri, state.soundAlarmB.duration, state.selectedSongB)
710 B_sonos.playTrack(state.soundAlarmB.uri)
714 if (B_alarmType == "2") {
715 if (B_secondAlarmMusic && state.selectedSongB){
716 B_sonos.playSoundAndTrack (state.soundB.uri, state.soundB.duration, state.selectedSongB)
719 B_sonos.playTrack(state.soundB.uri)
723 if (B_alarmType == "3") {
724 B_sonos.playTrack(state.selectedSongB)
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
734 C_dimmers?.setLevel(dimLevel)
736 def thermostatState = C_thermostats.currentThermostatMode
737 if (thermostatState == "auto") {
738 C_thermostats.setHeatingSetpoint(C_temperatureH)
739 C_thermostats.setCoolingSetpoint(C_temperatureC)
741 else if (thermostatState == "heat") {
742 C_thermostats.setHeatingSetpoint(C_temperatureH)
743 log.info "Set $C_thermostats Heat $C_temperatureH°"
746 C_thermostats.setCoolingSetpoint(C_temperatureC)
747 log.info "Set $C_thermostats Cool $C_temperatureC°"
752 location.helloHome.execute(C_phrase)
755 if (C_triggerMode && location.mode != C_triggerMode) {
756 if (location.modes?.find{it.name == C_triggerMode}) {
757 setLocationMode(C_triggerMode)
760 log.debug "Unable to change to undefined mode '${C_triggerMode}'"
765 C_sonos.setLevel(C_volume)
768 if (C_alarmType == "2" || (C_alarmType == "1" && C_secondAlarm =="1")) {
771 getGreeting(C_wakeMsg, 3)
774 if (C_weatherReport || C_humidity || C_includeTemp || C_localTemp) {
775 getWeatherReport(3, C_weatherReport, C_humidity, C_includeTemp, C_localTemp)
778 if (C_includeSunrise || C_includeSunset) {
779 getSunriseSunset(3, C_includeSunrise, C_includeSunset)
782 if ((C_switches || C_dimmers || C_thermostats) && C_confirmSwitches) {
783 getOnConfimation(C_switches, C_dimmers, C_thermostats, 3)
786 if (C_phrase && C_confirmPhrase) {
787 getPhraseConfirmation(3, C_phrase)
790 if (C_triggerMode && C_confirmMode){
791 getModeConfirmation(C_triggerMode, 3)
794 state.soundC = textToSpeech(state.fullMsgC, true)
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)
801 if (C_secondAlarm == "2" && state.selectedSongC && state.soundAlarmC){
802 C_sonos.playSoundAndTrack (state.soundAlarmC.uri, state.soundAlarmC.duration, state.selectedSongC)
805 C_sonos.playTrack(state.soundAlarmC.uri)
809 if (C_alarmType == "2") {
810 if (C_secondAlarmMusic && state.selectedSongC){
811 C_sonos.playSoundAndTrack (state.soundC.uri, state.soundC.duration, state.selectedSongC)
814 C_sonos.playTrack(state.soundC.uri)
818 if (C_alarmType == "3") {
819 C_sonos.playTrack(state.selectedSongC)
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
829 D_dimmers?.setLevel(dimLevel)
831 def thermostatState = D_thermostats.currentThermostatMode
832 if (thermostatState == "auto") {
833 D_thermostats.setHeatingSetpoint(D_temperatureH)
834 D_thermostats.setCoolingSetpoint(D_temperatureC)
836 else if (thermostatState == "heat") {
837 D_thermostats.setHeatingSetpoint(D_temperatureH)
838 log.info "Set $D_thermostats Heat $D_temperatureH°"
841 D_thermostats.setCoolingSetpoint(D_temperatureC)
842 log.info "Set $D_thermostats Cool $D_temperatureC°"
847 location.helloHome.execute(D_phrase)
850 if (D_triggerMode && location.mode != D_triggerMode) {
851 if (location.modes?.find{it.name == D_triggerMode}) {
852 setLocationMode(D_triggerMode)
855 log.debug "Unable to change to undefined mode '${D_triggerMode}'"
860 D_sonos.setLevel(D_volume)
863 if (D_alarmType == "2" || (D_alarmType == "1" && D_secondAlarm =="1")) {
866 getGreeting(D_wakeMsg, 4)
869 if (D_weatherReport || D_humidity || D_includeTemp || D_localTemp) {
870 getWeatherReport(4, D_weatherReport, D_humidity, D_includeTemp, D_localTemp)
873 if (D_includeSunrise || D_includeSunset) {
874 getSunriseSunset(4, D_includeSunrise, D_includeSunset)
877 if ((D_switches || D_dimmers || D_thermostats) && D_confirmSwitches) {
878 getOnConfimation(D_switches, D_dimmers, D_thermostats, 4)
881 if (D_phrase && D_confirmPhrase) {
882 getPhraseConfirmation(4, D_phrase)
885 if (D_triggerMode && D_confirmMode){
886 getModeConfirmation(D_triggerMode, 4)
889 state.soundD = textToSpeech(state.fullMsgD, true)
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)
896 if (D_secondAlarm == "2" && state.selectedSongD && state.soundAlarmD){
897 D_sonos.playSoundAndTrack (state.soundAlarmD.uri, state.soundAlarmD.duration, state.selectedSongD)
900 D_sonos.playTrack(state.soundAlarmD.uri)
904 if (D_alarmType == "2") {
905 if (D_secondAlarmMusic && state.selectedSongD){
906 D_sonos.playSoundAndTrack (state.soundD.uri, state.soundD.duration, state.selectedSongD)
909 D_sonos.playTrack(state.soundD.uri)
913 if (D_alarmType == "3") {
914 D_sonos.playTrack(state.selectedSongD)
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)
927 log.debug "Summary message = ${state.summaryMsg}"
928 def summarySound = textToSpeech(state.summaryMsg, true)
930 summarySonos.setLevel(summaryVolume)
932 summarySonos.playTrack(summarySound.uri)
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. "
940 else if (summaryDisabled && !alarmOn && scenarioName) {
941 state.summaryMsg = "${state.summaryMsg} Alarm ${num}, ${scenarioName}, set for ${parseDate(timeStart,"", "h:mm a")}, is disabled. "
943 else if (summaryDisabled && !scenarioName) {
944 state.summaryMsg = "${state.summaryMsg} Alarm ${num} is not configured. "
948 //--------------------------------------
950 def getDesc(timeStart, sonos, day, mode) {
951 def desc = "Tap to set alarm"
953 desc = "Alarm set to " + parseDate(timeStart,"", "h:mm a") +" on ${sonos}"
955 def dayListSize = day ? day.size() : 7
957 if (day && dayListSize < 7) {
959 for (dayName in day) {
960 desc = desc + " ${dayName}"
961 dayListSize = dayListSize -1
968 desc = desc + " every day"
972 def modeListSize = mode.size()
973 def modePrefix =" in the following modes: "
974 if (modeListSize == 1) {
975 modePrefix = " in the following mode: "
977 desc = desc + "${modePrefix}"
978 for (modeName in mode) {
979 desc = desc + "'${modeName}'"
980 modeListSize = modeListSize -1
990 desc = desc + " in all modes"
995 def greyOut(scenario, sonos, alarmTime, alarmOn, alarmType){
996 def result = scenario && sonos && alarmTime && alarmOn && alarmType ? "complete" : ""
999 def greyOut1(param1, param2, param3, param4, param5, param6){
1000 def result = param1 || param2 || param3 || param4 || param5 || param6 ? "complete" : ""
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"
1007 def greyOutOption(param){
1008 def result = param ? "complete" : ""
1011 def getTitle(scenario, num) {
1012 def title = scenario ? scenario : "Alarm ${num} not configured"
1015 def dimmerDesc(dimmer){
1016 def desc = dimmer ? "Tap to edit dimmer settings" : "Tap to set dimmer setting"
1019 def thermostatDesc(thermostat, heating, cooling){
1021 if (heating || cooling){
1023 tempText = "${heating} heat"
1026 tempText = "${cooling} cool"
1028 if (heating && cooling) {
1029 tempText ="${heating} heat / ${cooling} cool"
1033 tempText="Tap to edit thermostat settings"
1036 def desc = thermostat ? "${tempText}" : "Tap to set thermostat settings"
1040 private getDayOk(dayList) {
1043 result = dayList.contains(getDay())
1049 def df = new java.text.SimpleDateFormat("EEEE")
1050 if (location.timeZone) {
1051 df.setTimeZone(location.timeZone)
1054 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
1056 def day = df.format(new Date())
1059 private parseDate(date, epoch, type){
1062 long longDate = Long.valueOf(epoch).longValue()
1063 parseDate = new Date(longDate).format("yyyy-MM-dd'T'HH:mm:ss.SSSZ", location.timeZone)
1068 new Date().parse("yyyy-MM-dd'T'HH:mm:ss.SSSZ", parseDate).format("${type}", timeZone(parseDate))
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")
1078 def currTime = now()
1079 def verb1 = currTime >= s.sunrise.time ? "rose" : "will rise"
1080 def verb2 = currTime >= s.sunset.time ? "set" : "will set"
1082 if (includeSunrise && includeSunset) {
1083 msg = "The sun ${verb1} this morning at ${riseTime} and ${verb2} at ${setTime}. "
1085 else if (includeSunrise && !includeSunset) {
1086 msg = "The sun ${verb1} this morning at ${riseTime}. "
1088 else if (!includeSunrise && includeSunset) {
1089 msg = "The sun ${verb2} tonight at ${setTime}. "
1091 compileMsg(msg, scenario)
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)
1099 private getGreeting(msg, scenario) {
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}")
1109 compileMsg(msg, scenario)
1112 private getWeatherReport(scenario, weatherReport, humidity, includeTemp, localTemp) {
1113 if (location.timeZone || zipCode) {
1114 def isMetric = location.temperatureScale == "C"
1115 def sb = new StringBuilder()
1118 def current = getWeatherFeature("conditions", zipCode)
1120 sb << "The current temperature is ${Math.round(current.current_observation.temp_c)} degrees. "
1123 sb << "The current temperature is ${Math.round(current.current_observation.temp_f)} degrees. "
1128 sb << "The local temperature is ${Math.round(localTemp.currentTemperature)} degrees. "
1132 sb << "The local relative humidity is ${humidity.currentValue("humidity")}%. "
1135 if (weatherReport) {
1136 def weather = getWeatherFeature("forecast", zipCode)
1138 sb << "Today's forecast is "
1140 sb << weather.forecast.txt_forecast.forecastday[0].fcttext_metric
1143 sb << weather.forecast.txt_forecast.forecastday[0].fcttext
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)
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)
1158 private getOnConfimation(switches, dimmers, thermostats, scenario) {
1160 if ((switches || dimmers) && !thermostats) {
1161 msg = "All switches"
1163 if (!switches && !dimmers && thermostats) {
1164 msg = "All Thermostats"
1166 if ((switches || dimmers) && thermostats) {
1167 msg = "All switches and thermostats"
1169 msg = "${msg} are now on and set. "
1170 compileMsg(msg, scenario)
1173 private getPhraseConfirmation(scenario, phrase) {
1174 def msg="The Smart Things Hello Home phrase, ${phrase}, has been activated. "
1175 compileMsg(msg, scenario)
1178 private getModeConfirmation(mode, scenario) {
1179 def msg="The Smart Things mode is now being set to, ${mode}. "
1180 compileMsg(msg, scenario)
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}"}
1191 private alarmSoundUri(selection, length, scenario){
1193 def soundLength = ""
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}"]
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}"]
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}"]
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}"]
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}"]
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}"]
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}
1226 //Sonos Aquire Track from SmartThings code
1227 private songOptions(sonos, scenario) {
1229 // Make sure current selection is in the set
1230 def options = new LinkedHashSet()
1232 if (state.selectedSongA?.station) {
1233 options << state.selectedSongA.station
1235 else if (state.selectedSongA?.description) {
1236 options << state.selectedSongA.description
1240 if (state.selectedSongB?.station) {
1241 options << state.selectedSongB.station
1243 else if (state.selectedSongB?.description) {
1244 options << state.selectedSongB.description
1248 if (state.selectedSongC?.station) {
1249 options << state.selectedSongC.station
1251 else if (state.selectedSongC?.description) {
1252 options << state.selectedSongC.description
1256 if (state.selectedSongD?.station) {
1257 options << state.selectedSongD.station
1259 else if (state.selectedSongD?.description) {
1260 options << state.selectedSongD.description
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})
1268 log.trace "${options.size()} songs in list"
1269 options.take(20) as List
1273 private saveSelectedSong(sonos, song, scenario) {
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"
1280 def data = songs.find {s -> s.station == thisSong}
1281 log.info "Found ${data?.station}"
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}"
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"
1293 log.warn "Selected song '$song' not found"
1296 catch (Throwable t) {
1301 //Version/Copyright/Information/Help
1303 private def textAppName() {
1304 def text = "Talking Alarm Clock"
1307 private def textVersion() {
1308 def text = "Version 1.4.5 (06/17/2015)"
1311 private def textCopyright() {
1312 def text = "Copyright © 2015 Michael Struck"
1315 private def textLicense() {
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"+
1321 " http://www.apache.org/licenses/LICENSE-2.0"+
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."
1330 private def textHelp() {
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."