Files
SmartThingsPublic/smartapps/michaelstruck/google-home-helper.src/google-home-helper.groovy

1185 lines
73 KiB
Groovy

/**
* Google Home Helper
*
* Copyright © 2016 Michael Struck
* Version 1.0.0 12/1/16
*
* Version 1.0.0 (12/1/16) - Initial release
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*/
definition(
name: "Google Home Helper${parent ? " - Scenario " : ""}",
singleInstance: true,
namespace: "MichaelStruck",
author: "Michael Struck",
parent: parent ? "MichaelStruck.Google Home Helper" : null,
description: "Allows for various SmartThings devices to be tied to switches controlled by Google Home.",
category: "My Apps",
iconUrl: "https://raw.githubusercontent.com/MichaelStruck/SmartThingsPublic/master/smartapps/michaelstruck/google-home-helper.src/GoogleHome.png",
iconX2Url: "https://raw.githubusercontent.com/MichaelStruck/SmartThingsPublic/master/smartapps/michaelstruck/google-home-helper.src/GoogleHome@2x.png",
iconX3Url: "https://raw.githubusercontent.com/MichaelStruck/SmartThingsPublic/master/smartapps/michaelstruck/google-home-helper.src/GoogleHome@2x.png")
preferences {
page name:"pageMain"
//Parent menu pages
page name:"mainPageParent"
page name:"pageAbout"
page name:"pageSettings"
page name:"pageSwitches"
page name:"pageAddSwitch"
//Child menu pages
page name: "mainPageChild"
page name: "pageControl"
page name: "onPageSTDevices"
page name: "offPageSTDevices"
page name: "pageSpeaker"
page name: "pagePanic"
page name: "pageThermostat"
page name: "pageVoice"
page name: "pageTempReport"
page name: "pageHomeReport"
page name: "onPageHTTP"
page name: "offPageHTTP"
}
def pageMain() { if (!parent) mainPageParent() else mainPageChild() }
//Show main page
def mainPageParent() {
dynamicPage(name: "mainPageParent", title: "Google Home Helper Scenarios", install: true, uninstall: false) {
section {
app(name: "childScenarios", appName: "Google Home Helper", namespace: "MichaelStruck", title: "Create New Google Home Scenario...", multiple: true)
}
section("Options") {
href "pageSettings", title: "Configure Settings", description: "Tap to configure app settings", image: imgURL() + "settings.png"
if (showAddSwitches) {
def titleTxt = getChildDevices().size() > 0 ? "Add/View Virtual Switches" : "Add Virtual Switches"
def descTxt = getChildDevices().size() > 1 ? "${getChildDevices().size()} virtual switches created" : getChildDevices().size() == 1 ? "One virtual switch created" : "Tap to add virtual switches"
href "pageSwitches", title: "${titleTxt}", description: "${descTxt}", image: imgURL() + "add.png"
}
href "pageAbout", title: "About ${textAppName()}", description: "Tap to get application version, license, instructions or to remove the application", image: imgURL() + "info.png"
}
}
}
def pageAbout(){
dynamicPage(name: "pageAbout", uninstall: true) {
section {
paragraph "${textAppName()}\n${textCopyright()}", image: "https://raw.githubusercontent.com/MichaelStruck/SmartThingsPublic/master/smartapps/michaelstruck/google-home-helper.src/GoogleHome@2x.png"
}
section ("SmartApp/Switch Versions") { paragraph "${textVersion()}" }
section ("Apache License") { paragraph "${textLicense()}"}
section("Instructions") { paragraph textHelp()}
section("Tap below to remove all scenarios, switches and application"){
}
}
}
def pageSettings(){
dynamicPage(name: "pageSettings", install: false, uninstall: false) {
section { paragraph "Configure Settings", image: imgURL() + "settings.png" }
section (" ") {
input "speakerSonos", "bool", title: "Show Sonos options", defaultValue: false, submitOnChange:true
if (speakerSonos) input "memoryCount", "enum", title: "Maximum number of Sonos memory slots", options: [2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8"], defaultValue: 2, required: false
input "tstatNest", "bool", title: "Show Nest options", defaultValue: false
input "showRestrictions", "bool", title: "Show scenario restrictions", defaultValue: true
input "showAddSwitches", "bool", title: "Allow in-app virtual switch creation", defaultValue: false
input "showNotifyFeed", "bool", title: "Post activity to notification feed" , defaultValue: false
}
}
}
def pageSwitches() {
dynamicPage(name: "pageSwitches", install: false, uninstall: false) {
section { paragraph "Add/View Virtual Switches", image: imgURL() + "add.png" }
section("New switch information"){
input "addSwitchName", "text", title: "Switch Label", description: "Enter a unique label name for the virtual switch", required: false, submitOnChange:true
input "addSwitchType", "enum", title: "Switch Type...", description: "Choose a switch type", options:["Google Switch","Momentary Button Tile-Google"], required: false, submitOnChange:true
if (addSwitchType && addSwitchName) href "pageAddSwitch",title: "Add Switch", description: "Tap to add this switch", image: imgURL() + "add.png"
}
def switchList = ""
state.sw1Ver = ""
state.sw2Ver = ""
def count= getChildDevices().size(), noun = "${count} switches"
if (count > 0) {
if (count == 1) noun = "One switch"
getChildDevices().each {
if (it.typeName=="Google Switch" && state.sw1Ver == "") state.sw1Ver = "Google Switch Version: ${it.versionNum()}"
if (it.typeName=="Momentary Button Tile" && state.sw2Ver == "") state.sw2Ver = "Momentary Button Tile Version: ${it.versionNum()}"
switchList += "${it.label} (${it.typeName})"
count --
if (count>0) switchList +="\n"
}
section ("${noun} created within Google Home Helper"){paragraph switchList}
}
}
}
// Show "pageAddSwitch" page
def pageAddSwitch() {
dynamicPage(name: "pageAddSwitch", title: "Add Switch", install: false, uninstall: false) {
def repsonse
if (getChildDevices().find{it.label == addSwitchName}) repsonse="There is already a switch labled '${addSwitchName}'.\n\nTap Done to go back and change the switch label name."
else repsonse = !addSwitchName || !addSwitchType ? "Switch label name or type not specified.\n\nTap Done to go back and enter the switch information" : addChildSwitches()
section {paragraph repsonse}
}
}
//Child Menu Items-------------------------------------------
def mainPageChild() {
dynamicPage(name: "mainPageChild", title: "Scenario Settings", uninstall: true, install: true) {
section {
label title:"Scenario Name", required:true
input "scenarioType", "enum", title: "Scenario Type...", options: [["Baseboard":"Baseboard Heater Control"],["Thermostat":"Heating/Cooling Thermostat Control"],["Control":"Modes/Routines/Devices/HTTP/SHM Control"],["Panic":"Panic Commands"],["Speaker":"Speaker Control"],["Voice":"Voice Reporting"]], required: false, multiple: false, submitOnChange:true
def fullScenarioName = [Baseboard:"Baseboard Heater",Thermostat:"Heating/Cooling Thermostat",Control:"Control Scenario",Panic:"Panic Commands",Speaker:"Speaker",Voice:"Voice Reporting"][scenarioType] ?: scenarioType
if (scenarioType) href "page${scenarioType}", title: "${fullScenarioName} Settings", description: scenarioDesc(), state: greyOutScen()
}
if (scenarioType && parent.showRestrictions){
section("Restrictions", hideable: true, hidden: !(runDay || timeIntervalInput || runMode)) {
input "runDay", "enum", options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], title: "Only Certain Days Of The Week...", multiple: true, required: false, image: imgURL() + "calendar.png"
href "timeIntervalInput", title: "Only During Certain Times...", description: getTimeLabel(timeStart, timeEnd), state: greyOutState(timeStart, timeEnd,""), image: imgURL() + "clock.png"
input "runMode", "mode", title: "Only In The Following Modes...", multiple: true, required: false, image: imgURL() + "modes.png"
}
}
section("Tap below to remove this scenario"){}
}
}
page(name: "timeIntervalInput", title: "Only during a certain time") {
section {
input "timeStart", "time", title: "Starting", required: false
input "timeEnd", "time", title: "Ending", required: false
}
}
// Show "pageControl" page
def pageControl() {
dynamicPage(name: "pageControl", install: false, uninstall: false) {
section { paragraph "Control Scenario Settings", image: imgURL() + "control.png" }
section ("Switch Selection"){
input "GoogleSwitch", "capability.switch", title: "Control Switch (On/Off, Momentary)", multiple: false, required: true, image: imgURL() + "dimmer.png"
input "showOptions", "enum", title: "Switch States To React To...", options: ["":"On/Off", "1":"On Only", "2":"Off Only"] , required: false, submitOnChange:true, defaultValue: ""
}
if (!showOptions || showOptions == "1") controlOnOff("on")
if (!showOptions || showOptions == "2") controlOnOff("off")
}
}
def controlOnOff(type){
def phrases = location.helloHome?.getPhrases()*.label
if (phrases) phrases.sort()
section ("When switch is ${type}..."){
if (phrases) input "${type}Phrase", "enum", title: "Perform This Routine", options: phrases, required: false, image: imgURL() + "routine.png"
input "${type}Mode", "mode", title: "Set Mode To...", required: false, image: imgURL() + "modes.png"
input "${type}SHM", "enum",title: "Set Smart Home Monitor To...", options: ["away":"Arm(Away)", "stay":"Arm(Stay)", "off":"Disarm"], required: false, image: imgURL() + "SHM.png"
href "${type}PageSTDevices", title: "SmartThings Device Control...", description: getDeviceDesc("${type}"), state: getDeviceState("${type}"), image: imgURL() + "smartthings.png"
href "${type}PageHTTP", title: "HTTP Request...", description: getHTTPDesc("${type}"), state: greyOutStateHTTP("${type}"), image: imgURL() + "network.png"
input "${type}Delay", "number", title: "Delay (Minutes) To Activate After Trigger", defaultValue: 0, required: false, image: imgURL() + "stopwatch.png"
input ("${type}Contacts", "contact", title: "Send Notifications To...", required: false, image: imgURL() + "sms.png") {
input "${type}SMSNum", "phone", title: "Send SMS Message (Phone Number)...", required: false, image: imgURL() + "sms.png"
input "${type}PushMsg", "bool", title: "Send Push Message", defaultValue: false
}
input "${type}SMSMsg", "text", title: "Message To Send...", required: false
}
}
//Show "onPageHTTP" page
def onPageHTTP (){
dynamicPage(name: "onPageHTTP", title: "HTTP Request", install: false, uninstall: false){
pageHTTPOnOff("on")
}
}
//Show "offPageHTTP" page
def offPageHTTP (){
dynamicPage(name: "offPageHTTP", title: "HTTP Request", install: false, uninstall: false){
pageHTTPOnOff("off")
}
}
def pageHTTPOnOff(type){
section{
input "${type}ExtInt", "enum", title: "Choose HTTP Command Type", options:[0:"External REST",1:"Internal (IP, port, command)"], submitOnChange:true , required: false
if (settings."${type}ExtInt" == "0") input "${type}HTTP", "text", title:"HTTP Address...", required: false
else if (settings."${type}ExtInt" == "1"){
input "${type}IP", "text", title: "Internal IP Address", description: "IPv4 address xx.xx.xx.xx format", required: false
input "${type}Port", "number", title: "Internal Port", description: "Enter a port number 0 to 65536", required: false
input "${type}Command", "text", title: "Command", description: "Enter REST commands", required: false
}
}
}
// Show "onPageSTDevices" page
def onPageSTDevices(){
dynamicPage (name: "onPageSTDevices", install: false, uninstall: false) {
pageSTDevicesOnOff("on")
}
}
// Show "offPageSTDevices" page
def offPageSTDevices(){
dynamicPage (name: "offPageSTDevices", install: false, uninstall: false) {
pageSTDevicesOnOff("off")
}
}
def pageSTDevicesOnOff(type){
section { paragraph "SmartThings Device Control", image: imgURL() + "smartthings.png"}
section ("Switches"){
input "${type}Switches", "capability.switch", title: "Control These Switches...", multiple: true, required: false, submitOnChange:true
if (settings."${type}Switches") input "${type}SwitchesCMD", "enum", title: "Command To Send To Switches", options:["on":"Turn on","off":"Turn off", "toggle":"Toggle the switches' on/off state"], multiple: false, required: false
}
section ("Dimmers"){
input "${type}Dimmers", "capability.switchLevel", title: "Control These Dimmers...", multiple: true, required: false , submitOnChange:true
if (settings."${type}Dimmers") input "${type}DimmersCMD", "enum", title: "Command To Send To Dimmers", options:["on":"Turn on","off":"Turn off","set":"Set level", "toggle":"Toggle the dimmers' on/off state"], multiple: false, required: false, submitOnChange:true
if (settings."${type}DimmersCMD" == "set" && settings."${type}Dimmers") input "${type}DimmersLVL", "number", title: "Dimmers Level", description: "Set dimmer level", required: false, defaultValue: 0
}
section ("Colored Lights", hideWhenEmpty: true){
input "${type}ColoredLights", "capability.colorControl", title: "Control These Colored Lights...", multiple: true, required: false, submitOnChange:true
if (settings."${type}ColoredLights") input "${type}ColoredLightsCMD", "enum", title: "Command To Send To Colored Lights", options:["on":"Turn on","off":"Turn off","set":"Set color and level", "toggle":"Toggle the lights' on/off state"], multiple: false, required: false, submitOnChange:true
if (settings."${type}ColoredLightsCMD" == "set" && settings."${type}ColoredLights"){
input "${type}ColoredLightsCLR", "enum", title: "Choose A Color...", required: false, multiple:false, options: fillColorSettings().name, submitOnChange:true
if (settings."${type}ColoredLightsCLR" == "Custom-User Defined"){
input "${type}HueUserDefined", "number", title: "Colored Lights Hue", description: "Set colored light hue (0 to 100)", required: false, defaultValue: 0
input "${type}SatUserDefined", "number", title: "Colored Lights Saturation", description: "Set colored lights saturation (0 to 100)", required: false, defaultValue: 0
}
input "${type}ColoredLightsLVL", "number", title: "Colored Light Level", description: "Set colored lights level", required: false, defaultValue: 0
}
}
section ("Thermostats"){
input "${type}Tstats", "capability.thermostat", title: "Control These Thermostats...", multiple: true, required: false, submitOnChange:true
if (settings."${type}Tstats") input "${type}TstatsCMD", "enum", title: "Command To Send To Thermostats", options:["heat":"Set heating temperature","cool":"Set cooling temperature"], multiple: false, required: false, submitOnChange:true
if (settings."${type}TstatsCMD") input "${type}TstatLVL", "number", title: "Temperature Level", description: "Set temperature level", required: false
}
section ("Locks"){
input "${type}Locks","capability.lock", title: "Control These Locks...", multiple: true, required: false, submitOnChange:true
if (settings."${type}Locks") input "${type}LocksCMD", "enum", title: "Command To Send To Locks", options:["lock":"Lock","unlock":"Unlock"], multiple: false, required: false
}
section("Garage Doors"){
input "${type}Garages","capability.garageDoorControl", title: "Control These Garage Doors...", multiple: true, required: false, submitOnChange:true
if (settings."${type}Garages") input "${type}GaragesCMD", "enum", title: "Command To Send To Garage Doors", options:["open":"Open","close":"Close"], multiple: false, required: false
}
}
// Show "Panic" page
def pagePanic() {
dynamicPage(name: "pagePanic", install: false, uninstall: false) {
section {paragraph "Panic Commands Settings", image: imgURL() + "emergency.png"}
section ("Switch Selection") {
input "panicSwitchOn", "capability.momentary", title: "ON Control Switch (Momentary)", multiple: false, required: true, submitOnChange:true,image: imgURL() + "button.png"
if (panicSwitchOn) input "panicSwitchOff", "capability.momentary", title: "OFF Control Switch (Momentary)", multiple: false, required: false, submitOnChange:true,image: imgURL() + "button.png"
}
section ("When panic is activated...", hideWhenEmpty: true){
input "alarm", "capability.alarm", title: "Activate Alarms...", multiple: true, required: false, submitOnChange:true
if (parent.speakerSonos) input "alarmSonos", "capability.musicPlayer", title: "Use Sonos As Alarm...", multiple: false , required: false , submitOnChange:true,image: imgURL() + "speaker.png"
if (alarm){
input "alarmType", "enum", title: "Select Alarm Type", options: ["strobe":"Strobe light", "siren":"Siren", "both":"Both stobe and siren"], multiple: false, required: false
input "alarmTimer", "number", title:"Alarm Turns Off Automatically After (Minutes)", required: false,image: imgURL() + "stopwatch.png"
}
if (alarmSonos && parent.speakerSonos && alarmSonos.name.contains("Sonos")){
input "alarmSonosVolume", "number", title:"Sonos Alarm Volume", required: false, image: imgURL() + "volume.png"
input "alarmSonosSound", "enum", title:"Sonos Alarm Sound", options: [1:"Alarm 1-European Siren", 2:"Alarm 2-Sci-Fi Siren", 3:"Alarm 3-Police Car Siren", 4:"Alarm 4-Red Alert",5:"Custom-User Defined"], multiple: false, required: false, submitOnChange:true
if (alarmSonosSound == "5") input "alarmSonosCustom", "text", title:"URL/Location Of Custom Sound...", required: false
input "alarmSonosTimer", "number", title:"Alarm Turns Off Automatically After (Seconds)", required: false,image: imgURL() + "stopwatch.png"
}
if (alarmSonos && parent.speakerSonos && !alarmSonos.name.contains("Sonos")){
paragraph "You have chosen a speaker for your alarm that is not supported. Currently, only Sonos speakers can be used as alarms. Please choose a Sonos speaker."
}
input ("panicContactsOn", "contact", title: "Send Notifications To...", required: false,image: imgURL() + "sms.png") {
input "panicSMSnumberOn", "phone", title: "Send SMS Message To (Phone Number)...", required: false,image: imgURL() + "sms.png"
input "panicPushOn", "bool", title: "Send Push Message", defaultValue: false
}
input "panicSMSMsgOn","text",title: "Message To Send...", required: false
}
if (panicSwitchOn && panicSwitchOff){
section ("When panic is deactivated..."){
if (alarm || alarmSonos) input "alarmOff", "bool", title: "Turn Off Alarm?", defaultValue: false
input ("panicContactsOff", "contact", title: "Send Notifications To...", required: false,image: imgURL() + "sms.png") {
input "panicSMSnumberOff", "phone", title: "Send SMS Message To (Phone Number)...", required: false,image: imgURL() + "sms.png"
input "panicPushOff", "bool", title: "Send Push Message", defaultValue: false
}
input "panicSMSMsgOff","text",title: "Message To Send...", required: false
}
}
}
}
// Show "pageSpeaker" page
def pageSpeaker(){
dynamicPage(name: "pageSpeaker", install: false, uninstall: false) {
section { paragraph "Speaker Settings", image: imgURL() + "speaker.png" }
section ("Switch/Speaker Selection"){
input "vDimmerSpeaker", "capability.switchLevel", title: "Control Switch (Dimmer)", multiple: false, required:false, submitOnChange:true,image: imgURL() + "dimmer.png"
input "speaker", "capability.musicPlayer", title: "Speaker To Control", multiple: false , required: false, submitOnChange:true,image: imgURL() + "speaker.png"
}
section ("Speaker Volume Limits", hideable: true, hidden: !(upLimitSpeaker || lowLimitSpeaker || speakerInitial)) {
input "upLimitSpeaker", "number", title: "Volume Upper Limit", required: false
input "lowLimitSpeaker", "number", title: "Volume Lower Limit", required: false
input "speakerInitial", "number", title: "Volume When Speaker Turned On", required: false
}
section ("Speaker Track Controls", hideable: true, hidden: !(nextSwitch || prevSwitch)) {
input "nextSwitch", "capability.momentary", title: "Next Track Switch (Momentary)", multiple: false, required: false,image: imgURL() + "button.png"
input "prevSwitch", "capability.momentary", title: "Previous Track Switch (Momentary)", multiple: false, required: false,image: imgURL() + "button.png"
}
if (vDimmerSpeaker){
section ("Other Functions/Controls", hideable: true, hidden: !(speakerOnSwitches || speakerOffSwitches || speakerOffFunction)){
input "speakerOnSwitches", "capability.switch", title: "When Control Switch On, Turn On...", multiple: true, required: false
input "speakerOffSwitches", "capability.switch", title: "When Control Switch Off, Turn off...", multiple: true, required: false
input "speakerOffFunction", "bool", title: "Control Switch off action: Pause/Stop Playback", defaultValue: false
}
}
if (speaker && songOptions(1) && parent.speakerSonos && speaker.name.contains("Sonos")){
for (int i = 1; i <=sonosSlots(); i++) {
section ("Sonos Saved Station ${i}", hideable: true, hidden: !(settings."song${i}Switch" || settings."song${i}Station")){
input "song${i}Switch", "capability.momentary", title: "Saved Station Switch #${i} (Momentary)", multiple: false, required: false, submitOnChange:true,image: imgURL() + "button.png"
if (settings."song${i}Switch") input "song${i}Station", "enum", title: "Song/Station #${i}", description: "Tap to select recently played song/station", multiple: false,
required: false, options: songOptions("${i}"), submitOnChange:true
if (settings."song${i}Station") input "announce${i}Song", "bool", title: "Announce Song Name Prior To Playing", defaultValue: false
}
}
}
if (speaker && !songOptions(1) && parent.speakerSonos && speaker.name.contains("Sonos")){
section ("Sonos Saved Stations", hideable: true, hidden: false) {
paragraph "There are currently no songs available in the Sonos memory. Play a "+
"song or station through SmartThings, then come back to this app and the station list should be available"
}
}
}
}
// Show "pageThermostat" page
def pageThermostat(){
dynamicPage(name: "pageThermostat", install: false, uninstall: false) {
section {paragraph "Heating/Cooling Thermostat Settings", image: imgURL() + "temp.png"}
section ("Switch/Thermostat Selection"){
input "vDimmerTstat", "capability.switchLevel", title: "Control Switch (Dimmer)", multiple: false, required:false, image: imgURL() + "dimmer.png"
input "tstat", "capability.thermostat", title: "Thermostat To Control", multiple: false , required: false, image: imgURL() + "temp.png"
input "autoControlTstat", "bool", title: "Control Thermostat In 'Auto' Mode", defaultValue: false
}
section ("Thermostat Temperature Limits", hideable: true, hidden:!(upLimitTstat ||lowLimitTstat)) {
input "upLimitTstat", "number", title: "Thermostat Upper Limit", required: false
input "lowLimitTstat", "number", title: "Thermostat Lower Limit", required: false
}
section ("Thermostat Setpoints\n(When controls turned on below)", hideable: true, hidden:!(heatingSetpoint ||coolingSetpoint)){
input "heatingSetpoint", "number", title: "Heating Setpoint", required: false
input "coolingSetpoint", "number", title: "Cooling Setpoint", required: false
}
section ("Thermostat Mode Controls", hideable: true, hidden:!(heatingSwitch || coolingSwitch || autoSwitch)) {
input "heatingSwitch", "capability.momentary", title: "Heating Mode Switch (Momentary)", multiple: false, required: false,image: imgURL() + "button.png"
input "coolingSwitch", "capability.momentary", title: "Cooling Mode Switch (Momentary)", multiple: false, required: false,image: imgURL() + "button.png"
input "autoSwitch", "capability.momentary", title: "Auto Mode Switch (Momentary)", multiple: false, required: false,image: imgURL() + "button.png"
}
if (parent.tstatNest){
section ("Nest Home/Away Controls", hideable: true, hidden:!(nestHome || nestAway)){
input "nestHome", "capability.momentary", title: "Home Mode Switch (Momentary)", multiple: false, required: false,image: imgURL() + "button.png"
input "nestAway", "capability.momentary", title: "Away Mode Switch (Momentary)", multiple: false, required: false,image: imgURL() + "button.png"
}
}
}
}
// Show "pageBaseboard" page
page(name: "pageBaseboard", title: "Baseboard Heater Settings", install: false, uninstall: false) {
section {paragraph "Baseboard Heater Settings", image: imgURL() + "heating.png"}
section ("Switch/Baseboard Selection") {
input "vDimmerBB", "capability.switchLevel", title: "Control Switch (Dimmer)", multiple: false, required:false,image: imgURL() + "dimmer.png"
input "tstatBB", "capability.thermostat", title: "Thermostat To Control", multiple: true, required: false, image: imgURL() + "temp.png"
}
section ("Baseboard Temperature Limits", hideable: true, hidden:!(upLimitTstatBB ||lowLimitTstatBB)) {
input "upLimitTstatBB", "number", title: "Thermostat Upper Limit", required: false
input "lowLimitTstatBB", "number", title: "Thermostat Lower Limit", required: false
}
section ("Baseboard On/Off Setpoints", hideable: true, hidden:!(setpointBBon ||setpointBBoff)){
input "setpointBBon", "number", title: "Setpoint When Control Switch Turned On", required: false
input "setpointBBoff", "number", title: "Setpoint When Control Switch Turned Off", required: false
}
}
//Show "pageVoice" page
def pageVoice(){
dynamicPage(name: "pageVoice", install: false, uninstall: false){
section { paragraph "Voice Reporting Settings", image: imgURL() + "voice.png" }
section ("Switch/Speaker Selection") {
input "voiceControl", "capability.momentary", title: "Voice Report Control Switch (Momentary)", multiple: false, required: true,image: imgURL() + "button.png"
input "voiceSpeaker", "capability.musicPlayer", title: "Voice Report Speaker", multiple: false, required: false, submitOnChange:true,image: imgURL() + "speaker.png"
if (voiceSpeaker) input "voiceVolume", "number", title: "Speaker Volume", required: false, image: imgURL() + "volume.png"
input "voiceDevice", "capability.speechSynthesis", title: "Voice Report Speech Synthesis Device", multiple: false, required: false, hideWhenEmpty: true
input "voiceDelay", "number", title: "Delay (Minutes) After Trigger To Report", defaultValue: 0, required: false,image: imgURL() + "stopwatch.png"
if (voiceSpeaker) input "voiceResume", "bool", title: "Resume Music/Track After Voice Report", defaultValue: false
input "voiceNotification", "bool", title: "Push/SMS Notification Of Report", defaultValue: false, submitOnChange:true
if (voiceNotification){
input ("voiceContacts", "contact", title: "Send Notifications To...", required: false) {
input "voiceSMSnumber", "phone", title: "Send SMS Message To (Phone Number)...", required: false
input "voicePush", "bool", title: "Send Push Message", defaultValue: false
}
}
}
section ("Report Types"){
input "voicePre", "text", title: "Pre Message Before Device Report", description: "Enter a message to play before the device report", defaultValue: "This is your SmartThings voice report for %time%, %day%, %date%.", required: false, capitalization: "sentences"
href "pageSwitchReport", title: "Switch/Dimmer Report", description: reportDesc(voiceSwitch, voiceDimmer, ""), state: greyOutState(voiceSwitch, voiceDimmer, ""), image: imgURL() + "power.png"
href "pagePresenceReport", title: "Presence Report", description: reportDesc(voicePresence, "", ""), state: greyOutState(voicePresence, "", ""), image : imgURL() + "people.png"
href "pageDoorReport", title: "Door/Window Report", description: reportDesc(voiceDoorSensors, voiceDoorControls, voiceDoorLocks), state: greyOutState(voiceDoorSensors, voiceDoorControls, voiceDoorLocks), image: imgURL() + "lock.png"
href "pageTempReport", title: "Temperature/Thermostat Report", description: reportDesc(voiceTemperature, voiceTempSettings, voiceTempVar), state: greyOutState(voiceTemperature, voiceTempSettings, voiceTempVar),image: imgURL() + "temp.png"
href "pageHomeReport", title: "Mode and Smart Home Monitor Report", description: reportDescMSHM(), state: greyOutState(voiceMode, voiceSHM, ""), image: imgURL() + "modes.png"
input "voicePost", "text", title: "Post Message After Device Report", description: "Enter a message to play after the device report", required: false, capitalization: "sentences"
}
}
}
page(name: "pagePresenceReport", title: "Presence Report", install: false, uninstall: false){
section {
input "voicePresence", "capability.presenceSensor", title: "Presence Sensors To Report Their Status...", multiple: true, required: false
input "voicePresentOnly", "bool", title: "Report Only Sensors That Are 'Not Present'", defaultValue: false
}
}
page(name: "pageSwitchReport", title: "Switch/Dimmer Report", install: false, uninstall: false){
section {
input "voiceSwitch", "capability.switch", title: "Switches To Report Their Status...", multiple: true, required: false
input "voiceOnSwitchOnly", "bool", title: "Report Only Switches That Are On", defaultValue: false
input "voiceDimmer", "capability.switchLevel", title: "Dimmers To Report Their Status...", multiple: true, required: false
input "voiceOnDimmerOnly", "bool", title: "Report Only Dimmers That Are On", defaultValue: false
}
}
page(name: "pageDoorReport", title: "Door/Window Report", install: false, uninstall: false){
section {
input "voiceDoorSensors", "capability.contactSensor", title: "Doors/Windows Sensors To Report Their Status...", multiple: true, required: false
input "voiceDoorControls", "capability.doorControl", title: "Door Controls To Report Their Status...", multiple: true, required: false
input "voiceDoorLocks", "capability.lock", title: "Locks To Report Their Status...", multiple: true, required: false
input "voiceDoorAll", "bool", title: "Report Door/Window Summary Even When All Are Closed And Locked", defaultValue: false
}
}
def pageTempReport(){
dynamicPage(name: "pageTempReport", title: "Temperature/Thermostat Report", install: false, uninstall: false){
section {
input "voiceTempVar", "capability.temperatureMeasurement", title: "Temperature Device Variable (%temp%)",multiple: false, required: false
input "voiceTemperature", "capability.temperatureMeasurement", title: "Devices To Report Temperatures...",multiple: true, required: false
}
section ("Thermostat Setpoint Reporting") {
input "voiceTempSettings", "capability.thermostat", title: "Thermostats To Report Their Setpoints...",multiple: true, required: false, submitOnChange:true
if (voiceTempSettings) {
input "voiceTempSettingsType", "enum", title: "Which Setpoint To Report", defaultValue: "heatingSetpoint",
options: ["heatingSetpoint": "Heating Setpoint","coolingSetpoint":"Cooling Setpoint","thermostatSetpoint":"Single Setpoint (Not compatible with all thermostats)"]
input "voiceTempSettingSummary", "bool", title: "Consolidate Thermostat Report", defaultValue: false, submitOnChange:true
}
if (voiceTempSettingSummary && voiceTempSettings) input "voiceTempTarget", "number", title: "Thermostat Setpoint Target", required: false, defaultValue: 50
}
}
}
def pageHomeReport(){
dynamicPage(name: "pageHomeReport", title: "Mode And Security Report", install: false, uninstall: false){
section {
input "voiceMode", "bool", title: "Report SmartThings Mode Status", defaultValue: false
input "voiceSHM", "bool", title: "Report Smart Home Monitor Status", defaultValue: false
}
}
}
//-----------------------------------------------------------
def installed() { initialize() }
def updated() {
unschedule()
unsubscribe()
initialize()
}
def uninstalled(){ deleteChildSwitches() }
def initialize() { parent ? initializeChild() : initializeParent() }
private initializeParent(){
childApps.each {child ->log.info "Installed Scenario: ${child.label}"}
}
private initializeChild(){
if (scenarioType == "Control" && GoogleSwitch) subscribe(GoogleSwitch, "switch", switchHandler)
if (scenarioType == "Thermostat"){
if (vDimmerTstat && tstat){
subscribe (vDimmerTstat, "switch.off", thermoOffHandler)
subscribe (vDimmerTstat, "level", thermoHandler)
if (heatingSwitch) subscribe (heatingSwitch, "switch.on", heatHandler)
if (coolingSwitch) subscribe (coolingSwitch, "switch.on", coolHandler)
if (autoSwitch) subscribe (autoSwitch, "switch.on", autoHandler)
}
if (parent.tstatNest){
if (nestHome) subscribe(nestHome, "switch.on", nestHomeHandler)
if (nestAway) subscribe(nestAway, "switch.on", nestAwayHandler)
}
}
if (scenarioType == "Baseboard" && vDimmerBB && tstatBB){
subscribe (vDimmerBB, "switch", BBOnOffHandler)
subscribe (vDimmerBB, "level", BBHandler)
}
if (scenarioType == "Speaker" && vDimmerSpeaker && speaker) {
subscribe (vDimmerSpeaker, "level", speakerVolHandler)
subscribe (vDimmerSpeaker, "switch", speakerOnHandler)
if (nextSwitch) subscribe (nextSwitch, "switch.on", controlNextHandler)
if (prevSwitch) subscribe (prevSwitch, "switch.on", controlPrevHandler)
if (parent.speakerSonos && speaker.name.contains("Sonos")){
for (int i = 1; i <= sonosSlots(); i++) {
if (settings."song${i}Switch" && settings."song${i}Station"){
saveSelectedSong(i,settings."song${i}Station")
subscribe (settings."song${i}Switch", "switch.on", controlSong)
}
}
}
}
if (scenarioType == "Panic"){
if (panicSwitchOn) subscribe (panicSwitchOn, "switch.on", panicOn)
if (panicSwitchOff) subscribe (panicSwitchOff, "switch.on", panicOff)
if (alarmSonos && parent.speakerSonos && alarmSonos.name.contains("Sonos") && alarmSonosSound) getAlarmSound()
}
if (scenarioType == "Voice" && (voiceControl && (voiceDevice || voiceSpeaker)) || (voiceNotification && (voiceContacts || voiceSMSnumber || voicePush))) {
subscribe(voiceControl, "switch.on", voiceHandler)
}
}
//Common modules (for adding switches)
def addChildSwitches(){
def deviceID = "GHH_${app.id}_${getChildDevices().size()}"
def nameSpace = "MichaelStruck"
def result
try {
def childDevice = addChildDevice(nameSpace, addSwitchType, deviceID, null, [name: deviceID, label: addSwitchName, completedSetup: true])
log.debug "Created Switch ${addSwitchName}: ${deviceID}"
result ="The ${addSwitchType} named '${addSwitchName}' has been created.\n\nBe sure to include the switch in your Google Home app."
} catch (e) {
log.debug "Error creating switch: ${e}"
result ="The ${addSwitchType} named '${addSwitchName}' could NOT be created.\n\nEnsure you have the correct device code installed and published within the IDE."
}
result + "\n\nTap Done to return to the switches page."
}
def deleteChildSwitches() {
getChildDevices().each {
log.debug "Deleting switch ID: " + it.deviceNetworkId
try {
deleteChildDevice(it.deviceNetworkId)
} catch (e) {
log.debug "Fatal exception ${e}"
}
}
}
//Child Code------------------------------------------------------------------------------------
//Mode/Routine/Devices/HTTP/SHM-----------------------------------------------------------------
def switchHandler(evt) {
if (getOkToRun("Control Scenario on/off")) {
if (evt.value == "on" && getOkOnOptions()) {
if (!onDelay || onDelay == 0) turnOnOff("on")
else {
runIn(onDelay*60, turnOn, [overwrite: true])
if (parent.showNotifyFeed) sendNotificationEvent("Google Home Helper Scenario: '${app.label}' ON triggered. Will activate in ${onDelay} minutes.")
}
}
else if (evt.value == "off" && getOkOffOptions()) {
if (!offDelay || offDelay == 0) turnOnOff("off")
else {
runIn(offDelay*60, turnOff , [overwrite: true])
if (parent.showNotifyFeed) sendNotificationEvent("Google Home Helper Scenario: '${app.label}' OFF triggered. Will activate in ${offDelay} minutes.")
}
}
}
}
def turnOn() {turnOnOff("on")}
def turnOff() {turnOnOff("off")}
def turnOnOff(type){
if (parent.showNotifyFeed) sendNotificationEvent("Google Home Helper Scenario: '${app.label}' ${type.toUpperCase()} activated.")
def cmd = [switch: settings."${type}SwitchesCMD", dimmer: settings."${type}DimmersCMD", cLight: settings."${type}ColoredLightsCMD", tstat: settings."${type}TstatsCMD", lock: settings."${type}LocksCMD", garage: settings."${type}GaragesCMD"]
if (settings."${type}Phrase") location.helloHome.execute(settings."${type}Phrase")
if (settings."${type}Mode") changeMode(settings."${type}Mode")
if (settings."${type}Switches" && cmd.switch) cmd.switch == "toggle" ? toggleState(settings."${type}Switches") : settings."${type}Switches"?."${cmd.switch}"()
if (settings."${type}Dimmers" && cmd.dimmer){
if (cmd.dimmer == "set"){
def level = settings."${type}DimmersLVL" < 0 || !settings."${type}DimmersLVL" ? 0 : settings."${type}DimmersLVL" >100 ? 100 : settings."${type}DimmersLVL" as int
settings."${type}Dimmers"?.setLevel(level)
}
else cmd.dimmer == "toggle" ? toggleState(settings."${type}Dimmers") : settings."${type}Dimmers"?."${cmd.dimmer}"()
}
if (settings."${type}ColoredLights" && cmd.cLight){
if (cmd.cLight == "set"){
def level = !settings."${type}ColoredLightsLVL" || settings."${type}ColoredLightsLVL" < 0 ? 0 : settings."${type}ColoredLightsLVL" >100 ? 100 : settings."${type}ColoredLightsLVL" as int
settings."${type}ColoredLightsCLR" ? setColoredLights(settings."${type}ColoredLights", settings."${type}ColoredLightsCLR", level, type) : settings."${type}ColoredLights"?.setLevel(level)
}
else if (cmd.cLight == "toggle") toggleState(settings."${type}ColoredLights")
else settings."${type}ColoredLights"?."${cmd.cLight}"()
}
if (settings."${type}Locks" && cmd.lock) settings."${type}Locks"?."${cmd.lock}"()
if (settings."${type}Tstats" && settings."${type}TstatLVL"){
def tLevel = settings."${type}TstatLVL" < 0 ? 0 : settings."${type}TstatLVL" >100 ? 100 : settings."${type}TstatLVL" as int
cmd.tstat == "heat" ? settings."${type}Tstats"?.setHeatingSetpoint(tLevel) : settings."${type}Tstats"?.setCoolingSetpoint(tLevel)
}
def param = [http:settings."${type}HTTP", ip:settings."${type}IP", port:settings."${type}Port", cmd:settings."${type}Command"]
if (settings."${type}ExtInt" == "0" && param.http){
log.info "Attempting to run: ${param.http}"
httpGet(param.http)
}
if (settings."${type}ExtInt" == "1" && param.ip && param.port && param.cmd){
def deviceHexID = convertToHex (param.ip, param.port)
log.info "Device Network Id set to ${deviceHexID}"
sendHubCommand(new physicalgraph.device.HubAction("""GET /${param.cmd} HTTP/1.1\r\nHOST: ${param.ip}:${param.port}\r\n\r\n""", physicalgraph.device.Protocol.LAN, "${deviceHexID}"))
}
if (settings."${type}SHM"){
log.info "Setting Smart Home Monitor to " + settings."${type}SHM"
sendLocationEvent(name: "alarmSystemStatus", value: settings."${type}SHM")
}
if (settings."${type}Garages" && cmd.garage) settings."${type}Garages"?."${cmd.garage}"()
if ((settings."${type}PushMsg" || settings."${type}SMSNum" || settings."${type}Contacts") && settings."${type}SMSMsg") sendMSG(settings."${type}SMSNum", settings."${type}SMSMsg", settings."${type}PushMsg", settings."${type}Contacts")
}
//Panic Handlers-----------------------------------------------------------------
def panicOn(evt){
if (getOkToRun("Panic actions activated")) {
if (alarm && alarmType){
alarmTurnOn()
if (alarmTimer && alarmTimer > 0){
def delayOff = alarmTimer as int
runIn(delayOff*60, alarmTurnOff, [overwrite: true])
}
}
if (alarmSonos && parent.speakerSonos && alarmSonos.name.contains("Sonos") && alarmSonosSound) {
if (alarmSonosVolume) alarmSonos.setLevel(alarmSonosVolume as int)
alarmSonos.playSoundAndTrack (state.alarmSound.uri, state.alarmSound.duration,"")
}
if (panicSMSnumberOn || panicPushOn || panicContactsOn){
def smsTxt = panicSMSMsgOn ? panicSMSMsgOn : "Panic was activated without message text input. Please investigate."
sendMSG(panicSMSnumberOn, smsTxt, panicPushOn, panicContactsOn)
}
if (parent.showNotifyFeed) sendNotificationEvent("Google Home Helper Scenario: '${app.label}' PANIC ON activated.")
}
}
def panicOff(evt){
if (getOkToRun("Panic actions deactivated")) {
if (alarmOff){
alarmTurnOff()
if (alarmSonos && parent.speakerSonos && alarmSonos.name.contains("Sonos") && alarmSonosSound) alarmSonos.stop()
}
if (panicSMSnumberOff || panicPushOff || panicContactsOff){
def smsTxt = panicSMSMsgOff ? panicSMSMsgOff : "Panic was deactivated without message text input. Please investigate"
sendMSG(panicSMSnumberOff, smsTxt, panicPushOff, panicContactsOff)
}
if (parent.showNotifyFeed) sendNotificationEvent ("Google Helper Scenario: '${app.label}' PANIC OFF activated.")
}
}
def alarmTurnOn(){alarm?."$alarmType"()}
def alarmTurnOff(){alarm?.off()}
//Speaker Handlers-----------------------------------------------------------------
def speakerControl(cmd, song, songName, announce){
if (cmd=="station" || cmd=="on") {
//Google Switch should be used to prevent looping to this point when switch in turned on
if (speakerInitial){
def speakerLevel = speakerInitial as int
vDimmerSpeaker.setLevel(speakerLevel)
}
else {
try { vDimmerSpeaker.setLevel(speaker.currentValue("level") as int) }
catch(e) { log.debug "Can't get current speaker level...may not be a true Sonos." }
}
if (cmd=="station"){
log.debug "Playing: ${song}"
def text = textToSpeech(announce ? "Now playing: ${songName}." : " ", true)
speaker.playSoundAndTrack(text.uri, text.duration, song)
}
if (cmd=="on"){
speaker.play()
speakerOnSwitches?.on()
}
}
if (cmd=="off"){
speakerOffFunction ? speaker.stop() : speaker.pause()
speakerOffSwitches?.off()
}
}
//Volume Handler
def speakerVolHandler(evt){
if (getOkToRun("Speaker volume change")) {
def speakerLevel = vDimmerSpeaker.currentValue("level") as int
if (speakerLevel == 0) vDimmerSpeaker.off()
else {
speakerLevel = upLimitSpeaker && (vDimmerSpeaker.currentValue("level") > upLimitSpeaker) ? upLimitSpeaker : lowLimitSpeaker && (vDimmerSpeaker.currentValue("level") < lowLimitSpeaker) ? lowLimitSpeaker : speakerLevel
speaker.setLevel(speakerLevel)
}
}
}
//Speaker on/off
def speakerOnHandler(evt) {if (getOkToRun("Speaker on/off")) {if (evt.value == "on" || evt.value == "off" ) speakerControl(evt.value,"","","")}}
def controlNextHandler(evt) {if (getOkToRun("Speaker next track")) speaker.nextTrack()}
def controlPrevHandler(evt) {if (getOkToRun("Speaker previous track")) speaker.previousTrack()}
def controlSong(evt){
def trigger = evt.displayName
if (getOkToRun("Speaker Saved Song/Station Trigger: ${trigger}")) {
for (int i = 1; i <= sonosSlots(); i++) if (settings."song${i}Switch" && trigger == settings."song${i}Switch".label){speakerControl("station", state."selectedSong${i}", settings."song${i}Station", settings."announce${i}Song")}
}
}
//Thermostat Handlers-----------------------------------------------------------------
def thermoOffHandler(evt){if (getOkToRun("Thermostat turned off")) tstat.off()}
def heatHandler(evt){
if (getOkToRun("Thermostat mode:Heating")) {
tstat.heat()
def setpoint = heatingSetpoint ? heatingSetpoint : tstat.currentValue("heatingSetpoint")
vDimmerTstat.setLevel(setpoint)
}
}
def coolHandler(evt){
if (getOkToRun("Thermostat mode:Cooling")) {
tstat.cool()
def setpoint = coolingSetpointSetpoint ? coolingSetpoint: tstat.currentValue("coolingSetpoint")
vDimmerTstat.setLevel(setpoint)
}
}
def autoHandler(evt){
if (getOkToRun("Thermostat mode:Auto")) {
tstat.auto()
def setpointH = heatingSetpoint ? heatingSetpoint : tstat.currentValue("heatingSetpoint")
def setpointC = coolingSetpoint ? coolingSetpoint: tstat.currentValue("coolingSetpoint")
vDimmerTstat?.on()
tstat.setHeatingSetpoint(setpointH)
tstat.setCoolingSetpoint(setpointC)
}
}
def nestHomeHandler(evt){if (getOkToRun("Thermostat mode:Home")) tstat.present()}
def nestAwayHandler(evt){if (getOkToRun("Thermostat mode:Away")) tstat.away()}
//Thermostat Temp Handler
def thermoHandler(evt){
if (getOkToRun("Temperature change")) {
def tstatMode=tstat.currentValue("thermostatMode")
if (tstatMode != "auto" || (tstatMode == "auto" && autoControlTstat)){
def tstatLevel = vDimmerTstat.currentValue("level") as int
tstatLevel = upLimitTstat && vDimmerTstat.currentValue("level") > upLimitTstat ? upLimitTstat : lowLimitTstat && vDimmerTstat.currentValue("level") < lowLimitTstat ? lowLimitTstat : tstatLevel
if (tstatMode == "heat" || tstatMode == "auto") tstat.setHeatingSetpoint(tstatLevel)
if (tstatMode == "cool" || tstatMode == "auto") tstat.setCoolingSetpoint(tstatLevel)
log.info "Thermostat set to ${tstatLevel}"
}
}
}
//Baseboard Handlers-----------------------------------------------------------------
def BBOnOffHandler(evt) {if (getOkToRun("Baseboard(s) turned ${evt.value}")) BBOnOff(evt.value)}
def BBHandler(evt){
if (getOkToRun("Baseboard Temperature change")) {
def tstatBBLevel = vDimmerBB.currentValue("level") as int
tstatBBLevel = upLimitTstatBB && vDimmerBB.currentValue("level") > upLimitTstatBB ? upLimitTstatBB : lowLimitTstatBB && vDimmerBB.currentValue("level") < lowLimitTstatBB ? lowLimitTstatBB : tstatBBLevel
tstatBB.each {
it.setHeatingSetpoint(tstatBBLevel)
log.info "${it} set to '${tstatBBLevel}'"
if (it.name.contains("Stelpro")) {
log.info "Applying ${tstatBBLevel} setpoint to StelPro thermostat:'${it}'"
it.applyNow()
}
}
}
}
def BBOnOff(status) {if (settings."setpointBB${status}") vDimmerBB.setLevel(settings."setpointBB${status}")}
//Voice Reporting Handlers-----------------------------------------------------------------
def voiceHandler(evt){
if (!voiceDelay || voiceDelay == 0) voiceReport()
else runIn(voiceDelay*60, voiceReport, [overwrite: true])
}
def voiceReport(){
if (getOkToRun("Voice Reporting")) {
def fullMsg = voicePre ? "${replaceVoiceVar(voicePre)} " : ""
if (voiceOnSwitchOnly) fullMsg += voiceSwitch ? switchReport(voiceSwitch, "switches") : ""
else fullMsg += voiceSwitch ? reportStatus(voiceSwitch, "switch") : ""
if (voiceOnDimmerOnly) fullMsg += voiceDimmer ? switchReport(voiceDimmer, "dimmers") : ""
else fullMsg += voiceDimmer ? reportStatus(voiceDimmer, "level") : ""
fullMsg += voicePresence ? presenceReport() : ""
fullMsg += (voiceTemperature) ? reportStatus(voiceTemperature, "temperature") : ""
if (voiceTempSettingSummary && voiceTempSettingsType) fullMsg += (voiceTempSettings) ? thermostatSummary(): ""
else fullMsg += (voiceTempSettings && voiceTempSettingsType) ? reportStatus(voiceTempSettings, voiceTempSettingsType) : ""
fullMsg += voiceDoorSensors || voiceDoorControls || voiceDoorLocks ? doorWindowReport() : ""
fullMsg += voiceMode ? "The current SmartThings mode is set to, '${location.currentMode}'. " : ""
fullMsg += voiceSHM ? "The current Smart Home Monitor status is '${location.currentState("alarmSystemStatus")?.value}'. " : ""
fullMsg += voicePost ? "${replaceVoiceVar(voicePost)} " : ""
log.info fullMsg
def reportVol = voiceSpeaker ? voiceSpeaker.currentValue("level") : 0
if (voiceVolume && voiceSpeaker) reportVol = voiceVolume as int
if (voiceSpeaker){
voiceSpeaker.refresh()
if (voiceResume) voiceSpeaker.playTextAndResume(fullMsg, reportVol)
else {
voiceSpeaker.setLevel(reportVol)
voiceSpeaker.playText(fullMsg)
}
}
if (voiceDevice) voiceDevice?.speak("${fullMsg}")
if (voiceNotification && (voiceContacts || voiceSMSnumber || voicePush)) sendMSG(voiceSMSnumber, fullMsg, voicePush, voiceContacts)
}
}
//Common Methods-------------
private String convertToHex(ipAddress, port){
String hexIP = ipAddress.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join()
String hexPort = port.toString().format( '%04x', port.toInteger() )
return "${hexIP}:${hexPort}"
}
def getOkToRun(module){
def result = true
if (parent.showRestrictions) result = (!runMode || runMode.contains(location.mode)) && getDayOk(runDay) && getTimeOk(timeStart,timeEnd)
if (result) log.info "Google Home Helper scenario '${app.label}', '${module}' triggered"
else log.warn "Google Home Helper scenario '${app.label}', '${module}' not triggered due to scenario restrictions"
result
}
def getOkOnOptions(){def result = (!showOptions || showOptions == "1") && (onPhrase || onMode || onSwitches || onDimmers || onColoredLights || onLocks || onGarages || onTstats || onHTTP || onIP || onSHM || onSMSMsg)}
def getOkOffOptions(){def result = (!showOptions || showOptions == "2") && (offPhrase || offMode || offSwitches || offDimmers || offColoredLights || offLocks || offGarages || offTstats || offHTTP || offIP || offSHM || offSMSMsg)}
def changeMode(newMode) {
if (location.mode != newMode) {
if (location.modes?.find{it.name == newMode}) setLocationMode(newMode)
else log.warn "Unable to change to undefined mode '${newMode}'"
}
}
def getTimeLabel(start, end){
def timeLabel = "Tap to set"
if(start && end) timeLabel = "Between " + parseDate("${start}", "h:mm a") + " and " + parseDate("${end}", "h:mm a")
else if (start) timeLabel = "Start at " + parseDate("${start}", "h:mm a")
else if (end) timeLabel = "End at " + parseDate("${end}", "h:mm a")
timeLabel
}
def scenarioDesc(){
def desc = ""
if (scenarioType=="Control" && GoogleSwitch){
def onOff = !showOptions ? "On and Off" : showOptions && showOptions == "1" ? "On" : "Off"
def delayTimeOn = onDelay && onDelay>1 ? "${onDelay} minutes" : onDelay && onDelay==1 ? "${onDelay} minute" : "immediately"
def delayTimeOff = offDelay && offDelay>1 ? "${offDelay} minutes" : offDelay && offDelay==1 ? "${offDelay} minute" : "immediately"
def timing = (!showOptions || showOptions == "1") ? "On scenario activates ${delayTimeOn} after triggered. " : ""
timing += (!showOptions || showOptions == "2") ? "Off scenario activates ${delayTimeOff} after triggered." : ""
desc = "'${GoogleSwitch}' switch (${onOff}) controls the scenario. ${timing}"
}
if (scenarioType=="Speaker"){
desc = vDimmerSpeaker && speaker ? "'${vDimmerSpeaker}' dimmer controls '${speaker}' speaker.": ""
desc += nextSwitch && desc ? "\n'${nextSwitch}' switch activates Next Track." : ""
desc += prevSwitch && desc ? "\n'${prevSwitch}' switch activates Previous Track." : ""
for (int i = 1; i <= sonosSlots(); i++) {
desc += parent.speakerSonos && (speaker && speaker.name.contains("Sonos")) && settings."song${i}Switch" && settings."song${i}Station" && desc ? "\n'" + settings."song${i}Switch" + "' switch activates '" + settings."song${i}Station" + "'." : ""
}
}
if (scenarioType=="Thermostat"){
desc = vDimmerTstat && tstat ? "'${vDimmerTstat}' dimmer controls '${tstat}' thermostat." : ""
desc += heatingSwitch && desc ? "\n'${heatingSwitch}' switch activates 'Heat' mode." : ""
desc += coolingSwitch && desc ? "\n'${coolingSwitch}' switch activates 'Cool' mode." : ""
desc += autoSwitch && desc ? "\n'${autoSwitch}' switch activates 'Auto' mode." : ""
desc += parent.tstatNest && nestHome && desc ? "\n'${nestHome}' switch activates 'Home' mode." : ""
desc += parent.tstatNest && nestAway && desc? "\n'${nestAway}' switch activates 'Away' mode." : ""
}
if (scenarioType=="Panic"){
desc = panicSwitchOn ? "'${panicSwitchOn}' switch activates panic actions." : ""
desc += panicSwitchOn && panicSwitchOff && desc ?"\n'${panicSwitchOff}' switch deactivates panic actions." : ""
}
if (scenarioType=="Baseboard") {
def noun = tstatBB && tstatBB.size() == 1 ? "baseboard heater: " : "baseboard heaters: "
desc = vDimmerBB && tstatBB ? "'${vDimmerBB}' dimmer controls ${noun}${tstatBB}." : ""
}
if (scenarioType=="Voice"){
def pushTxt = voiceNotification && (voiceContacts || voiceSMSnumber || voicePush)
def noun = pushTxt ? "Push/SMS Notification" : ""
noun += pushTxt && (voiceSpeaker || voiceDevice) ? ", " : ""
noun += voiceSpeaker && !voiceDevice ? "'${voiceSpeaker}'" : ""
noun += !voiceSpeaker && voiceDevice ? "'${voiceDevice}'" : ""
noun += voiceSpeaker && voiceDevice ? "'${voiceSpeaker}' and '${voiceDevice}'}" : ""
def delayTime = voiceDelay && voiceDelay>1 ? "${voiceDelay} minutes" : voiceDelay && voiceDelay==1 ? "${voiceDelay} minute" : "immediately"
desc = voiceControl && noun ? "'${voiceControl}' controls voice reports via ${noun}. Report generated ${delayTime} after triggered." : ""
}
desc = desc ? desc : "Status: UNCONFIGURED - Tap to configure scenario"
}
def reportDesc(param1, param2, param3) {def result = param1 || param2 || param3 ? "Status: CONFIGURED - Tap to edit" : "Status: UNCONFIGURED - Tap to configure"}
def reportDescMSHM() {
def result= "Status: "
result += voiceMode ? "Report Mode: On" : "Report Mode: Off"
result += voiceSHM ? ", Report SHM: On" : ", Report SHM: Off"
}
def getDeviceDesc(type){
def result, switches, dimmers, cLights, locks, garages, tstats, lvl, cLvl, clr, tLvl
def cmd = [switch: settings."${type}SwitchesCMD", dimmer: settings."${type}DimmersCMD", cLight: settings."${type}ColoredLightsCMD", tstat: settings."${type}TstatsCMD", lock: settings."${type}LocksCMD", garage: settings."${type}GaragesCMD"]
switches = settings."${type}Switches" && cmd.switch ? settings."${type}Switches" : ""
lvl = cmd.dimmer == "set" && settings."${type}DimmersLVL" ? settings."${type}DimmersLVL" as int : 0
dimmers = settings."${type}Dimmers" && cmd.dimmer ? settings."${type}Dimmers" : ""
cLvl = cmd.cLight == "set" && settings."${type}ColoredLightsLVL" ? settings."${type}ColoredLightsLVL" as int : 0
clr = cmd.cLight == "set" && settings."${type}ColoredLightsCLR" ? settings."${type}ColoredLightsCLR" : ""
cLights = settings."${type}ColoredLights" && cmd.cLight ? settings."${type}ColoredLights" : ""
tstats = settings."${type}Tstats" && cmd.tstat && settings."${type}TstatLVL" ? settings."${type}Tstats" : ""
tLvl = tstats ? settings."${type}TstatLVL" : 0
locks = settings."${type}Locks" && cmd.lock ? settings."${type}Locks" : ""
garages = settings."${type}Garages" && cmd.garage ? settings."${type}Garages" : ""
lvl = lvl < 0 ? lvl = 0 : lvl >100 ? lvl=100 : lvl
tLvl = tLvl < 0 ? tLvl = 0 : tLvl >100 ? tLvl=100 : tLvl
cLvl = cLvl < 0 ? cLvl = 0 : cLvl >100 ? cLvl=100 : cLvl
if (switches || dimmers || cLights || tstats || locks || garages) {
result = switches ? "${switches} set to ${cmd.switch}" : ""
result += result && dimmers ? "\n" : ""
result += dimmers && cmd.dimmer != "set" ? "${dimmers} set to ${cmd.dimmer}" : ""
result += dimmers && cmd.dimmer == "set" ? "${dimmers} set to ${lvl}%" : ""
result += result && cLights ? "\n" : ""
result += cLights && cmd.cLight != "set" ? "${cLights} set to ${cmd.cLight}":""
result += cLights && cmd.cLight == "set" ? "${cLights} set to " : ""
result += cLights && cmd.cLight == "set" && clr ? "${clr} and " : ""
result += cLights && cmd.cLight == "set" ? "${cLvl}%" : ""
result += result && tstats ? "\n" : ""
result += tstats && cmd.tstat && tLvl ? "${tstats} set to ${cmd.tstat} : ${tLvl}" : ""
result += result && locks ? "\n":""
result += locks ? "${locks} set to ${cmd.lock}" : ""
result += result && garages ? "\n" : ""
result += garages ? "${garages} set to ${cmd.garage}" : ""
}
result = result ? result : "Status: UNCONFIGURED - Tap to configure"
}
def getDeviceState(type){
def result = getDeviceDesc("${type}") == "Status: UNCONFIGURED - Tap to configure" ? "" : "complete"
}
def getHTTPDesc(type){
def result = ""
def param = [http:settings."${type}HTTP", ip:settings."${type}IP", port:settings."${type}Port", cmd:settings."${type}Command"]
if (settings."${type}ExtInt" == "0" && param.http) result += param.http
else if (settings."${type}ExtInt" == "1" && param.ip && param.port && param.cmd) result += "http://${param.ip}:${param.port}/${param.cmd}"
result = result ? result : "Status: UNCONFIGURED - Tap to configure"
}
def greyOutState(param1, param2, param3){def result = param1 || param2 || param3 ? "complete" : ""}
def greyOutStateHTTP(type){
def param = [http:settings."${type}HTTP", ip:settings."${type}IP", port:settings."${type}Port", cmd:settings."${type}Command"]
def result = (settings."${type}ExtInt" == "0" && param.http) || (settings."${type}ExtInt" == "1" && param.ip && param.port && param.cmd) ? "complete" : ""
}
def greyOutScen(){def result = (scenarioType=="Control" && GoogleSwitch) || (scenarioType=="Speaker" && vDimmerSpeaker && speaker) || (scenarioType=="Thermostat" && vDimmerTstat && tstat) || (scenarioType=="Panic" && panicSwitchOn && panicSwitchOff) ? "complete" : ""}
private getDayOk(dayList) {
def result = true
if (dayList) {
def df = new java.text.SimpleDateFormat("EEEE")
location.timeZone ? df.setTimeZone(location.timeZone) : df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
def day = df.format(new Date())
result = dayList.contains(day)
}
result
}
private getTimeOk(startTime, endTime) {
def result = true, currTime = now()
def start = startTime ? timeToday(startTime).time : null
def stop = endTime ? timeToday(endTime).time : null
if (startTime && endTime) result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
else if (startTime) result = currTime >= start
else if (endTime) result = currTime <= stop
result
}
def fillColorSettings(){
def colorData = []
colorData << [name: "White", hue: 0, sat: 0] << [name: "Orange", hue: 11, sat: 100] << [name: "Red", hue: 100, sat: 100] << [name: "Purple", hue: 77, sat: 100]
colorData << [name: "Green", hue: 30, sat: 100] << [name: "Blue", hue: 66, sat: 100] << [name: "Yellow", hue: 16, sat: 100] << [name: "Pink", hue: 95, sat: 100]
colorData << [name: "Cyan", hue: 50, sat: 100] << [name: "Chartreuse", hue: 25, sat: 100] << [name: "Teal", hue: 44, sat: 100] << [name: "Magenta", hue: 92, sat: 100]
colorData << [name: "Violet", hue: 83, sat: 100] << [name: "Indigo", hue: 70, sat: 100]<< [name: "Marigold", hue: 16, sat: 75]<< [name: "Raspberry", hue: 99, sat: 75]
colorData << [name: "Fuchsia", hue: 92, sat: 75] << [name: "Lavender", hue: 83, sat: 75]<< [name: "Aqua", hue: 44, sat: 75]<< [name: "Amber", hue: 11, sat: 75]
colorData << [name: "Carnation", hue: 99, sat: 50] << [name: "Periwinkle", hue: 70, sat: 50]<< [name: "Pistachio", hue: 30, sat: 50]
colorData << [name: "Vanilla", hue: 16, sat: 50] << [name: "Custom-User Defined", hue: 0, sat: 0]
}
private setColoredLights(switches, color, level, type){
def getColorData = fillColorSettings().find {it.name==color}
def hueColor = getColorData.hue
def satLevel = getColorData.sat
if (color == "Custom-User Defined"){
hueColor = settings."${type}HueUserDefined" ? settings."${type}HueUserDefined" : 0
satLevel = settings."${type}SatUserDefined" ? settings."${type}SatUserDefined" : 0
hueColor = hueColor > 100 ? 100 : hueColor < 0 ? 0 : hueColor
satLevel = satLevel > 100 ? 100 : satLevel < 0 ? 0 : satLevel
}
def newValue = [hue: hueColor as int, saturation: satLevel as int, level: level as int]
switches?.setColor(newValue)
}
def songOptions(slot) {
if (speaker) {
def options = new LinkedHashSet()
if (state."selectedSong${slot}"?.station) options << state."selectedSong${slot}".station
else if (state."selectedSong${slot}"?.description) options << state."selectedSong${slot}".description
def states = speaker.statesSince("trackData", new Date(0), [max:30])
def dataMaps = states.collect{it.jsonValue}
options.addAll(dataMaps.collect{it.station})
log.trace "${options.size()} song(s) in list"
options.take(20) as List
}
}
def saveSelectedSong(slot, song) {
try {
def thisSong = song
log.info "Looking for $thisSong"
def songs = speaker.statesSince("trackData", new Date(0), [max:30]).collect{it.jsonValue}
log.info "Searching ${songs.size()} records"
def data = songs.find {s -> s.station == thisSong}
log.info "Found ${data?.station}"
if (data) state."selectedSong${slot}"=data
else log.warn "'${song}' not found"
}
catch (Throwable t) { log.error t }
}
//Voice report---------------------------------------------------
def switchReport(devices, type){
def result = ""
if (devices.latestValue("switch").contains("on")) devices.each { deviceName->
if (deviceName.latestValue("switch")=="on") {
result += "${deviceName} is on"
result += type == "dimmers" ? " and set to ${deviceName.latestValue("level")}%. " : ". "
}
}
else result += "All of the monitored ${type} are off. "
result
}
def thermostatSummary(){
def result = "", monitorCount = voiceTempSettings.size(), matchCount = 0, err = false
for (device in voiceTempSettings) {
try{ if (device.latestValue(voiceTempSettingsType) as int == voiceTempTarget as int) matchCount ++ }
catch (e) { err=true }
}
if (!err){
def difCount = monitorCount - matchCount
if (monitorCount == 1 && difCount==1) result +="The monitored thermostat, ${voiceTempSettings}, is not set to ${voiceTempTarget} degrees. "
else if (monitorCount == 1 && !difCount) result +="The monitored thermostat, ${voiceTempSettings}, is set to ${voiceTempTarget} degrees. "
if (monitorCount > 1) {
if (!difCount) result += "All thermostats are set to ${voiceTempTarget} degrees. "
else if (difCount==monitorCount) result += "None of the thermostats are set to ${voiceTempTarget} degrees. "
else if (matchCount==1) {
for (device in voiceTempSettings){
if (device.latestValue(voiceTempSettingsType) as int == voiceTempTarget as int){
result += "Of the ${monitorCount} monitored thermostats, only ${device} is set to ${voiceTempTarget} degrees. "
}
}
}
else if (difCount && matchCount>1) {
result += "Some of the thermostats are set to ${voiceTempTarget} degrees except"
for (device in voiceTempSettings){
if (device.latestValue(voiceTempSettingsType) as int != voiceTempTarget as int){
result += " ${device}"
difCount = difCount -1
result += difCount && difCount == 1 ? " and" : difCount && difCount > 1 ? ", " : ". "
}
}
}
}
}
else result="Some of your thermostats are not able to provide their setpoint. Please choose another setpoint type to report on. "
result
}
def reportStatus(deviceList, type){
def result = ""
def appd = type=="temperature" || type=="thermostatSetpoint" || type == "heatingSetpoint" || type=="coolingSetpoint" ? "degrees" : ""
if (type != "thermostatSetpoint" && type != "heatingSetpoint" && type !="coolingSetpoint") deviceList.each {deviceName->result += "${deviceName} is ${deviceName.latestValue(type)} ${appd}. " }
else deviceList.each { deviceName->
try { result += "${deviceName} is set to ${deviceName.latestValue(type) as int} ${appd}. " }
catch (e) { result = "${deviceName} is not able to provide its setpoint. Please choose another setpoint type to report on. " }
}
result
}
def presenceReport(){
def result = ""
if (voicePresentOnly){
if (voicePresence.latestValue("presence").contains("not present")) voicePresence.each { deviceName->
if (deviceName.latestValue("presence")=="not present") result += "${deviceName} is not present"
}
else result += "All of the monitored presence sensors are present. "
}
else voicePresence.each {deviceName->result += "${deviceName} is " + deviceName.latestValue("presence") + ". " }
result
}
def doorWindowReport(){
def countOpened = 0, countOpenedDoor = 0, countUnlocked = 0
def result = "", listOpened = "", listUnlocked = ""
if (voiceDoorSensors && voiceDoorSensors.latestValue("contact").contains("open")){
for (sensor in voiceDoorSensors) if (sensor.latestValue("contact")=="open") countOpened ++
listOpened = listDevices(voiceDoorSensors, "contact", "open", countOpened )
}
if (voiceDoorControls && voiceDoorControls.latestValue("door").contains("open")){
for (door in voiceDoorControls) if (door.latestValue("door") == "open") countOpenedDoor ++
listOpened += listDevices(voiceDoorControls, "door", "open", countOpenedDoor)
}
if (voiceDoorLocks && voiceDoorLocks.latestValue("lock").contains("unlocked")){
for (doorLock in voiceDoorLocks) if (doorLock.latestValue("lock")=="unlocked") countUnlocked ++
listUnlocked = listDevices(voiceDoorLocks, "lock", "unlocked", countUnlocked)
}
def totalCount = countOpenedDoor + countOpened
if (voiceDoorAll){
if (!totalCount && !countUnlocked) {
result += "All of the doors and windows are closed"
result += voiceDoorLocks ? " and locked. " : ". "
}
if (!countOpened && !countOpenedDoor && countUnlocked){
result += "All of the doors and windows are closed, but the "
result += countUnlocked > 1 ? "following are unlocked: ${listUnlocked}. " :"${listUnlocked} is unlocked. "
}
if ((countOpened || countOpenedDoor) && !countUnlocked){
result += "All of the doors are locked, but the "
result += totalCount > 1 ? "following doors or windows are open: ${listOpened}. " : "${listOpened} is open. "
}
}
else {
if ((countOpened || countOpenedDoor) && !countUnlocked) result += totalCount > 1 ? "The following doors or windows are currently open: ${listOpened}. " : "${listOpened} is open. "
if (!countOpened && !countOpenedDoor && countUnlocked) result += countUnlocked > 1 ? "The following doors are unlocked: ${listUnlocked}. " : "The ${listUnlocked} is unlocked. "
}
if ((countOpened || countOpenedDoor) && countUnlocked){
def verb = totalCount > 1 ? "following doors or windows are currently open: ${listOpened}" : "${listOpened} is open"
def verb1 = countUnlocked > 1 ? "following are unlocked: ${listUnlocked}" : "${listUnlocked} is unlocked"
result += "The ${verb}. Also, the ${verb1}. "
}
result
}
def listDevices(devices, type, condition, count){
def result = ""
for (deviceName in devices){
if (deviceName.latestValue("${type}") == "${condition}"){
result += " ${deviceName}"
count = count - 1
if (count == 1) result += " and the "
else if (count> 1) result += ", "
}
}
result
}
//Send Messages
def sendMSG(num, msg, push, recipients){
def logText =""
if (num) {logText = "SMS Message '${msg}' sent to ${num}"}
if (push) {logText = "Message '${msg}' pushed to SmartApp"}
if (num && push) {logText = "Message '${msg}' sent to ${num} and pushed to SmartApp"}
if (location.contactBookEnabled && recipients) {logText = "Message '${msg}' sent to ${recipients}"}
if (parent.showNotifyFeed) sendNotificationEvent(logText)
log.info logText
if (location.contactBookEnabled && recipients) sendNotificationToContacts(msg, recipients)
else {
if (num) {sendSmsMessage(num,"${msg}")}
if (push) {sendPushMessage("${msg}")}
}
}
//Toggle states (off -> on, on -> off)
def toggleState(swDevices){
swDevices.each{
def currState = it.currentValue("switch")
def newstate = currState == "off" ? "on" : "off"
it?."$newstate"()
}
}
//Get Sonos Alarm Sound uri
def getAlarmSound(){
def soundLength = alarmSonosTimer && alarmSonosTimer < 60 ? alarmSonosTimer : 60
def soundUri = [uri: "https://raw.githubusercontent.com/MichaelStruck/SmartThingsPublic/master/smartapps/michaelstruck/alexa-helper-scenario.src/AlarmSirens/AlarmSiren${alarmSonosSound}.mp3", duration: "${soundLength}"]
if (alarmSonosSound == "5") soundUri =[uri: "${alarmSonosCustom}", duration: "${soundLength}"]
state.alarmSound = soundUri
}
def sonosSlots(){def slots = parent.getMemCount() as int}
private replaceVoiceVar(msg) {
def df = new java.text.SimpleDateFormat("EEEE")
location.timeZone ? df.setTimeZone(location.timeZone) : df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
def day = df.format(new Date()), time = parseDate("","h:mm a"), month = parseDate("","MMMM"), year = parseDate("","yyyy"), dayNum = parseDate("","d")
def temp = voiceTempVar ? "${voiceTempVar.latestValue("temperature")} degrees" : "undefined device"
msg = msg.replace('%day%', day)
msg = msg.replace('%date%', "${month} ${dayNum}, ${year}")
msg = msg.replace('%time%', "${time}")
msg = msg.replace('%temp%', "${temp}")
msg
}
private parseDate(time, type){
def formattedDate = time ? time : new Date(now()).format("yyyy-MM-dd'T'HH:mm:ss.SSSZ", location.timeZone)
new Date().parse("yyyy-MM-dd'T'HH:mm:ss.SSSZ", formattedDate).format("${type}", timeZone(formattedDate))
}
//Common modules
def imgURL() { return "https://raw.githubusercontent.com/MichaelStruck/SmartThingsPublic/master/img/" }
def getMemCount(){ return memoryCount ? memoryCount : 2 }
def getSwitchAbout(){ return "Created by Google Home Helper SmartApp" }
//Version/Copyright/Information/Help
private def textAppName() { return "Google Home Helper" }
private def textVersion() {
def version = "SmartApp Version: 1.0.0 (12/01/2016)"
def deviceCount= getChildDevices().size()
def deviceVersion = state.sw1Ver && deviceCount ? "\n${state.sw1Ver}": ""
deviceVersion += state.sw2Ver && deviceCount ? "\n${state.sw2Ver}": ""
return "${version}${deviceVersion}"
}
private def versionInt(){return 100}
private def textCopyright() {return "Copyright © 2016 Michael Struck"}
private def textLicense() {
def text =
"Licensed under the Apache License, Version 2.0 (the 'License'); "+
"you may not use this file except in compliance with the License. "+
"You may obtain a copy of the License at"+
"\n\n"+
" http://www.apache.org/licenses/LICENSE-2.0"+
"\n\n"+
"Unless required by applicable law or agreed to in writing, software "+
"distributed under the License is distributed on an 'AS IS' BASIS, "+
"WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. "+
"See the License for the specific language governing permissions and "+
"limitations under the License."
}
private def textHelp() {
def text =
"Ties various SmartThings functions to the on/off state of specifc switches. You may also control a thermostat, baseboard heaters, "+
"the volume of a wireless speakers, define a panic command or report on the status of various devices using " +
"either a dimmer control or momentary button tile. Perfect for use with the Google Home.\n\n" +
"To use, first create the required momentary button tiles or 'Google Switch' (custom switch/dimmer) from the SmartThings IDE or the SmartApp. "+
"You may also use any physical switches already associated with SmartThings. Include these switches within the Google Home app. Then, create a "+
"new scenario that best fits your needs, associating the switches with the various controls within the scenario.\n\n" +
"For more information, go to http://thingsthataresmart.wiki/index.php?title=Google_Home_Helper"
}