Code by Scott שאול בן ישוע
Inovelli-Scene-Creator-Child.groovy 34.6 KB
Newer Older
Stephan Hackett's avatar
Stephan Hackett committed
1
/*	
Sha'ul ben Yeshua's avatar
Sha'ul ben Yeshua committed
2
 *	Hubitat Import URL: https://raw.githubusercontent.com/sgrayban/ABC-Button/master/apps/Inovelli-Scene-Creator/Inovelli-Scene-Creator-Child.groovy
3
 *
Sha'ul ben Yeshua's avatar
Sha'ul ben Yeshua committed
4
 *	Inovelli Scene Creator Child
5
 *
Sha'ul ben Yeshua's avatar
Sha'ul ben Yeshua committed
6
7
8
 *	Original Authors: SmartThings, modified by Bruce Ravenel, Dale Coffing, Stephan Hackett
 *  
 *  Modified for Inovelli Scene control by Scott Grayban (Scott_Inovelli)
9
 *
10
 */
Sha'ul ben Yeshua's avatar
Sha'ul ben Yeshua committed
11
 
Stephan Hackett's avatar
Stephan Hackett committed
12
13
import hubitat.helper.RMUtils

Sha'ul ben Yeshua's avatar
Sha'ul ben Yeshua committed
14
def version(){"v1.0"}
15
16

definition(
Sha'ul ben Yeshua's avatar
Sha'ul ben Yeshua committed
17
18
19
20
    name: "Inovelli Scene Creator Child",
    namespace: "Inovelli",
    author: "Scott Grayban",
    description: "Create Scenes for your Inovelli Devices",
21
    category: "My Apps",
Sha'ul ben Yeshua's avatar
Sha'ul ben Yeshua committed
22
    parent: "Inovelli:Inovelli Scene Creator",
Stephan Hackett's avatar
Stephan Hackett committed
23
24
25
    iconUrl: "https://cdn.rawgit.com/stephack/ABC/master/resources/images/abc2.png",
    iconX2Url: "https://cdn.rawgit.com/stephack/ABC/master/resources/images/abc2.png",
    iconX3Url: "https://cdn.rawgit.com/stephack/ABC/master/resources/images/abc2.png",
26
27
28
)

preferences {
Stephan Hackett's avatar
Stephan Hackett committed
29
30
31
32
33
34
35
36
	page(name: "chooseButton")
	page(name: "configButtonsPage")
	page(name: "timeIntervalInput", title: "Only during a certain time") {
		section {
			input "starting", "time", title: "Starting", required: false
			input "ending", "time", title: "Ending", required: false
		}
	}
37
38
}

Stephan Hackett's avatar
Stephan Hackett committed
39
def chooseButton() {
40
	state.details=getPrefDetails()
Stephan Hackett's avatar
Stephan Hackett committed
41
42
	dynamicPage(name: "chooseButton", install: true, uninstall: true) {
		section(){
Sha'ul ben Yeshua's avatar
Sha'ul ben Yeshua committed
43
				def appHead = "<img src=https://raw.githubusercontent.com/sgrayban/ABC-Button/master/apps/Inovelli-Scene-Creator/resources/Inovelli-Scene-App.png height=80 width=80> \n${checkForUpdate()}"
Stephan Hackett's avatar
Stephan Hackett committed
44
				paragraph "<div style='text-align:center'>${appHead}</div>"
Stephan Hackett's avatar
Stephan Hackett committed
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
		}
		section(getFormat("header", "${getImage("Device", "45")}"+" Step 1: Select Your Button Device")) {
            input "buttonDevice", "capability.pushableButton", title: getFormat("section", "Button Device"), description: "Tap to Select", multiple: false, required: true, submitOnChange: true
		}
        if(buttonDevice){
        	state.buttonType =  buttonDevice.typeName
            if(state.buttonType.contains("Aeon Minimote")) state.buttonType =  "Aeon Minimote"
            if(logEnable) log.debug "Device Type is now set to: "+state.buttonType
            state.buttonCount = manualCount?: buttonDevice.currentValue('numberOfButtons')
            section(getFormat("header", "${getImage("Button", "45")}"+"  Step 2: Configure Your Buttons")) {
            	if(state.buttonCount<1) {
                	paragraph "The selected button device did not report the number of buttons it has. Please specify in the Advanced Config section below."
                }
                else {
                	for(i in 1..state.buttonCount){
                		href "configButtonsPage", title: getFormat("section", "${getImage("Button", "30")}" + " Button ${i}"), state: getDescription(i)!="Tap to configure"? "complete": null, description: getDescription(i), params: [pbutton: i]
                    }
            	}
            }
		}
        section(getFormat("header", "${getImage("Custom", "45")}"+"  Set Custom Name (Optional)")) {
        	label title: "Assign a name:", required: false
            paragraph getFormat("line")
        }
        section("Advanced Config:", hideable: true, hidden: hideOptionsSection()) {
            	input "manualCount", "number", title: "Set/Override # of Buttons?", required: false, description: "Only set if your driver does not report", submitOnChange: true
                input "collapseAll", "bool", title: "Collapse Unconfigured Sections?", defaultValue: true
				input "logEnable", "bool", title: "Enable Debug Logging?", required: false
			}
        section(title: "Only Execute When:", hideable: true, hidden: hideOptionsSection()) {
			def timeLabel = timeIntervalLabel()
			href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null
			input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
					options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
			input "modes", "mode", title: "Only when mode is", multiple: true, required: false
		}
	}
}

def configButtonsPage(params) {
	if (params.pbutton != null) state.currentButton = params.pbutton.toInteger()
	dynamicPage(name: "configButtonsPage", title: "CONFIGURE BUTTON ${state.currentButton}:\n${state.buttonType}", getButtonSections(state.currentButton))
}

def getButtonSections(buttonNumber) {
	return {    	
        def myDetail
        section(getFormat("header", "${getImage("Switches", "45")}"+" SWITCHES")){}
93
		//state.details=getPrefDetails()
94
        for(i in 1..30) {//Build 1st 30 Button Config Options
95
        	myDetail = state.details.find{it.sOrder==i}
Stephan Hackett's avatar
Stephan Hackett committed
96
97
98
        	//
    section(title: myDetail.secLabel, hideable: true, hidden: !(shallHide("${myDetail.id}${buttonNumber}"))) {
				if(showPush(myDetail.desc)) input "${myDetail.id}${buttonNumber}_pushed", myDetail.cap, title: "When Pushed", multiple: myDetail.mul, required: false, submitOnChange: collapseAll, options: myDetail.opt
99
100
101
				if(myDetail.sub && isReq("${myDetail.id}${buttonNumber}_pushed")) input "${myDetail.sub}${buttonNumber}_pushed", myDetail.subType, title: myDetail.sTitle, multiple: false, required: !myDetail.sNotReq, description: myDetail.sDesc, options: myDetail.subOpt
                if(myDetail.sub2 && isReq("${myDetail.id}${buttonNumber}_pushed")) input "${myDetail.sub2}${buttonNumber}_pushed", myDetail.subType, title: myDetail.s2Title, multiple: false, required: !myDetail.s2NotReq, description: myDetail.s2Desc, options: myDetail.subOpt
                if(myDetail.sub3 && isReq("${myDetail.id}${buttonNumber}_pushed")) input "${myDetail.sub3}${buttonNumber}_pushed", myDetail.subType, title: myDetail.s3Title, multiple: false, required: !myDetail.s3NotReq, description: myDetail.s3Desc, options: myDetail.subOpt
Stephan Hackett's avatar
Stephan Hackett committed
102
103
				
        		if(showHeld(myDetail.desc)) input "${myDetail.id}${buttonNumber}_held", myDetail.cap, title: "When Held", multiple: myDetail.mul, required: false, submitOnChange: collapseAll, options: myDetail.opt
104
105
106
107
108
109
110
111
112
                if(myDetail.sub && isReq("${myDetail.id}${buttonNumber}_held")) input "${myDetail.sub}${buttonNumber}_held", myDetail.subType, title: myDetail.sTitle, multiple: false, required: !myDetail.sNotReq, description: myDetail.sDesc, options: myDetail.subOpt
                if(myDetail.sub2 && isReq("${myDetail.id}${buttonNumber}_held")) input "${myDetail.sub2}${buttonNumber}_held", myDetail.subType, title: myDetail.s2Title, multiple: false, required: !myDetail.s2NotReq, description: myDetail.s2Desc, options: myDetail.subOpt
                if(myDetail.sub3 && isReq("${myDetail.id}${buttonNumber}_held")) input "${myDetail.sub3}${buttonNumber}_held", myDetail.subType, title: myDetail.s3Title, multiple: false, required: !myDetail.s3NotReq, description: myDetail.s3Desc, options: myDetail.subOpt
        		
				if(showDouble(myDetail.desc)) input "${myDetail.id}${buttonNumber}_doubleTapped", myDetail.cap, title: "When Double Tapped", multiple: myDetail.mul, required: false, submitOnChange: collapseAll, options: myDetail.opt
                if(myDetail.sub && isReq("${myDetail.id}${buttonNumber}_doubleTapped")) input "${myDetail.sub}${buttonNumber}_doubleTapped", myDetail.subType, title: myDetail.sTitle, multiple: false, required: !myDetail.sNotReq, description: myDetail.sDesc, options: myDetail.subOpt
                if(myDetail.sub2 && isReq("${myDetail.id}${buttonNumber}_doubleTapped")) input "${myDetail.sub2}${buttonNumber}_doubleTapped", myDetail.subType, title: myDetail.s2Title, multiple: false, required: !myDetail.s2NotReq, description: myDetail.s2Desc, options: myDetail.subOpt
                if(myDetail.sub3 && isReq("${myDetail.id}${buttonNumber}_doubleTapped")) input "${myDetail.sub3}${buttonNumber}_doubleTapped", myDetail.subType, title: myDetail.s3Title, multiple: false, required: !myDetail.s3NotReq, description: myDetail.s3Desc, options: myDetail.subOpt
		
Stephan Hackett's avatar
Stephan Hackett committed
113
        		if(showRelease(myDetail.desc)) input "${myDetail.id}${buttonNumber}_released", myDetail.cap, title: "When Released", multiple: myDetail.mul, required: false, submitOnChange: collapseAll, options: myDetail.opt
114
115
116
117
                if(myDetail.sub && isReq("${myDetail.id}${buttonNumber}_released")) input "${myDetail.sub}${buttonNumber}_released", myDetail.subType, title: myDetail.sTitle, multiple: false, required: !myDetail.sNotReq, description: myDetail.sDesc, options: myDetail.subOpt
                if(myDetail.sub2 && isReq("${myDetail.id}${buttonNumber}_released")) input "${myDetail.sub2}${buttonNumber}_released", myDetail.subType, title: myDetail.s2Title, multiple: false, required: !myDetail.s2NotReq, description: myDetail.s2Desc, options: myDetail.subOpt
                if(myDetail.sub3 && isReq("${myDetail.id}${buttonNumber}_released")) input "${myDetail.sub3}${buttonNumber}_released", myDetail.subType, title: myDetail.s3Title, multiple: false, required: !myDetail.s3NotReq, description: myDetail.s3Desc, options: myDetail.subOpt
		}
Stephan Hackett's avatar
Stephan Hackett committed
118
119
120
            if(i==3) section("\n"+getFormat("header", "${getImage("Dimmers", "45")}"+" DIMMERS")){}
            if(i==9) section("\n"+getFormat("header", "${getImage("Color", "45")}"+" COLOR LIGHTS")){}
            if(i==11) section("\n"+getFormat("header", "${getImage("Speakers", "45")}"+" SPEAKERS")){}
121
122
123
124
            if(i==18) section("\n"+getFormat("header", "${getImage("Fans", "45")}"+" FANS")){}
            if(i==21) section("\n"+getFormat("header", "${getImage("Mode", "45")}"+" MODES")){}
			if(i==23) section("\n"+getFormat("header", "${getImage("Rule", "45")}"+" RULE CONTROL")){}
            if(i==24) section("\n"+getFormat("header", "${getImage("Other", "45")}"+" OTHER")){}
125
        }
Stephan Hackett's avatar
Stephan Hackett committed
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
		
		section(getFormat("section", "Notifications (SMS):"), hideable:true , hidden: !shallHide("notifications_${buttonNumber}")) {
			input "notifications_${buttonNumber}_pushed", "text", title: "Message To Send When Pushed:", description: "Enter message to send", required: false, submitOnChange: collapseAll
            input "phone_${buttonNumber}_pushed","phone" ,title: "Send Text To:", description: "Enter phone number", required: false, submitOnChange: collapseAll
            if(showHeld()) {
            	paragraph getFormat("line")
				input "notifications_${buttonNumber}_held", "text", title: "Message To Send When Held:", description: "Enter message to send", required: false, submitOnChange: collapseAll
				input "phone_${buttonNumber}_held", "phone", title: "Send Text To:", description: "Enter phone number", required: false, submitOnChange: collapseAll
            }
            if(showDouble()) {
            	paragraph getFormat("line")
				input "notifications_${buttonNumber}_doubleTapped", "text", title: "Message To Send When Double Tapped:", description: "Enter message to send", required: false, submitOnChange: collapseAll
				input "phone_${buttonNumber}_doubleTapped", "phone", title: "Send Text To:", description: "Enter phone number", required: false, submitOnChange: collapseAll
            }
            if(showRelease()) {
            	paragraph getFormat("line")
				input "notifications_${buttonNumber}_released", "text", title: "Message To Send When Released:", description: "Enter message to send", required: false, submitOnChange: collapseAll
				input "phone_${buttonNumber}_released", "phone", title: "Send Text To:", description: "Enter phone number", required: false, submitOnChange: collapseAll
            }
		}
	}
}

def getImage(type, mySize) {
    def loc = "<img src=https://raw.githubusercontent.com/stephack/Hubitat/master/resources/images/"
    if(type == "Device") return "${loc}Device.png height=${mySize} width=${mySize}>   "
    if(type == "Button") return "${loc}Button.png height=${mySize} width=${mySize}>   "
    if(type == "Switches") return "${loc}Switches.png height=${mySize} width=${mySize}>   "
    if(type == "Color") return "${loc}Color.png height=${mySize} width=${mySize}>   "
    if(type == "Dimmers") return "${loc}Dimmers.png height=${mySize} width=${mySize}>   "
    if(type == "Speakers") return "${loc}Speakers.png height=${mySize} width=${mySize}>   "
    if(type == "Fans") return "${loc}Fans.png height=${mySize} width=${mySize}>   "
    if(type == "HSM") return "${loc}Mode.png height=${mySize} width=${mySize}>   "
    if(type == "Mode") return "${loc}Mode.png height=${mySize} width=${mySize}>   "
    if(type == "Other") return "${loc}Other.png height=${mySize} width=${mySize}>   "
    if(type == "Custom") return "${loc}Custom.png height=${mySize} width=${mySize}>   "
    if(type == "Locks") return "${loc}Locks.png height=30 width=30>   "
    if(type == "Sirens") return "${loc}Sirens.png height=30 width=30>   "
    if(type == "Scenes") return "${loc}Scenes.png height=30 width=30>   "
    if(type == "Shades") return "${loc}Shades.png height=30 width=30>   "
    if(type == "SMS") return "${loc}SMS.png height=30 width=30>   "
    if(type == "Speech") return "${loc}Audio.png height=30 width=30>   "
	if(type == "Rule") return "${loc}Rule.png height=${mySize} width=${mySize}>   "
}

def getFormat(type, myText=""){
    if(type == "section") return "<div style='color:#78bf35;font-weight: bold'>${myText}</div>"
    if(type == "command") return "<div style='color:red;font-weight: bold'>${myText}</div>"
    if(type == "header") return "<div style='color:#ffffff;background-color:#392F2E;text-align:center'>${myText}</div>"
    if(type == "line") return "\n<hr style='background-color:#78bf35; height: 2px; border: 0;'></hr>"
}

def shallHide(myFeature) {
	if(collapseAll) return (settings["${myFeature}_pushed"]||settings["${myFeature}_held"]||settings["${myFeature}_doubleTapped"]||settings["${myFeature}_released"]||settings["${myFeature}"])
	return true
}

def isReq(myFeature) {
    (settings[myFeature])? true : false
}

def showPush(desc) {
    if(buttonDevice.hasCapability("PushableButton")){ 	//is device pushable?
        if(desc.contains("Ramp")){									
            if(buttonDevice.hasCapability("HoldableButton")) return false	//if this is the Ramp section and device is also Holdable, then hide Pushed option
        }
        return true
193
    }
Stephan Hackett's avatar
Stephan Hackett committed
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
	return false
}

def showHeld(desc) {
    return buttonDevice.hasCapability("HoldableButton")
}

def showDouble(desc) {
    if(desc && desc.contains("Ramp")) return false //remove DoubleTapped option when setting smooth dimming button/devices
    return buttonDevice.hasCapability("DoubleTapableButton")
}

def showRelease(desc) {
    if(desc && desc.contains("Ramp")) return false //remove On Release option when setting smooth dimming button/devices
    return buttonDevice.hasCapability("ReleasableButton")
}

def getDescription(dNumber) {
    def descript = ""
    if(!(settings.find{it.key.contains("_${dNumber}_")})) return "Tap to configure"
    if(settings.find{it.key.contains("_${dNumber}_pushed")}) descript = "\nPUSHED:"+getDescDetails(dNumber,"_pushed")+"\n"
    if(settings.find{it.key.contains("_${dNumber}_held")}) descript = descript+"\nHELD:"+getDescDetails(dNumber,"_held")+"\n"
    if(settings.find{it.key.contains("_${dNumber}_doubleTapped")}) descript = descript+"\nTAPx2:"+getDescDetails(dNumber,"_doubleTapped")+"\n"
    if(settings.find{it.key.contains("_${dNumber}_released")}) descript = descript+"\nRELEASED:"+getDescDetails(dNumber,"_released")+"\n"
	return descript
}

def getDescDetails(bNum, type){
	def numType=bNum+type
	def preferenceNames = settings.findAll{it.key.contains("_${numType}")}.sort()		//get all configured settings that: match button# and type, AND are not false
    if(!preferenceNames){
    	return "  **Not Configured** "
    }
    else {
    	def formattedPage =""
    	preferenceNames.each {eachPref->
230
231
232
233
234
235
236
237
238
        	def prefDetail = state.details.find{eachPref.key.contains(it.id)}	//gets decription of action being performed(eg Turn On)
        						
			def prefDevice		//name of device the action is being performed on (eg Bedroom Fan)
			if(prefDetail.sub == "valRule"){
				prefDevice = " : " + getRuleName(eachPref.value)	//extracts rules name (instead if number) for button description
			}
			else {
				prefDevice = " : ${eachPref.value}"// was only needed to cleanup display in ST..not necessary in HE->           - "[" - "]"	
			}
239
240
			def thisSub = settings[prefDetail.sub + numType]
			def prefSubValue = thisSub != null? thisSub:"(!Missing!)"
Stephan Hackett's avatar
Stephan Hackett committed
241
242
243
244
245
246
247
248
249
250
251
252
253
            def sub2Value = settings[prefDetail.sub2 + numType]
            def sub3Value = settings[prefDetail.sub3 + numType]
            if(sub2Value) prefSubValue += ", S: ${sub2Value}"
            if(sub3Value) prefSubValue += ", L: ${sub3Value}"
            if(prefDetail.type=="normal") formattedPage += "\n- ${prefDetail.desc}${prefDevice}"
            if(prefDetail.type=="hasSub") formattedPage += "\n- ${prefDetail.desc}${prefSubValue}${prefDevice}"
            if(prefDetail.type=="bool") formattedPage += "\n- ${prefDetail.desc}"
    	}
		return formattedPage
    }
}

def getRules(){
254
255
256
257
258
259
260
261
262
263
264
265
266
	rules = RMUtils.getRuleList()
	//converts rules list to easily parsed format and stores in state.rules for easy access
	state.rules=[:] 
	rules.each{rule->
		rule.each{pair->
			state.rules[pair.key]=pair.value 
		}
	}
	////////////////////////////////////////////////////
	return rules
}

def getRuleName(num){	//allows button descriptions for RuleAPI controls to show Rule Name instead of Rule Number
Stephan Hackett's avatar
Stephan Hackett committed
267
	getRules()
268
269
	def holder=[]
	num.each{ruleNum->
Stephan Hackett's avatar
Stephan Hackett committed
270
		holder << state.rules.find{it.key==ruleNum.toInteger()}.value
271
272
	}
	return holder
273
274
}

275
276
277
278
279
280
281
282
283
284
def installed() {
	initialize()
}

def updated() {
	unsubscribe()
	initialize()
}

def initialize() {
Stephan Hackett's avatar
Stephan Hackett committed
285
286
287
288
289
290
291
292
    if(logEnable) log.debug "INITIALIZED with settings: ${settings}"
    if(logEnable) log.debug app.label
    if(!app.label || app.label == "default")app.updateLabel(defaultLabel())
	subscribe(buttonDevice, "pushed", buttonEvent)
	subscribe(buttonDevice, "held", buttonEvent)
	subscribe(buttonDevice, "doubleTapped", buttonEvent)
    subscribe(buttonDevice, "released", buttonEvent)
    state.lastshadesUp = true
293
	state.details=getPrefDetails()
Stephan Hackett's avatar
Stephan Hackett committed
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
}

def defaultLabel() {
	return "${buttonDevice} Mapping"
}

def getPrefDetails(){
	def detailMappings =
    	[[id:'lightOn_', sOrder:1, desc:'Turn On ', comm:turnOn, type:"normal", secLabel: getFormat("section", "Turn On"), cap: "capability.switch", mul: true],
     	 [id:"lightOff_", sOrder:2, desc:'Turn Off', comm:turnOff, type:"normal", secLabel: getFormat("section", "Turn Off"), cap: "capability.switch", mul: true],
         [id:'lights_', sOrder:3, desc:'Toggle On/Off', comm:toggle, type:"normal", secLabel: getFormat("section", "Toggle On/Off"), cap: "capability.switch", mul: true],
         
         [id:"lightDim_", sOrder:4, desc:'Dim to ', comm:turnDim, sub:"valLight", subType:"number", type:"hasSub", secLabel: getFormat("section", "On to Level - Group 1"), cap: "capability.switchLevel", sTitle: "Bright Level", sDesc:"0 to 100%", mul: true],
     	 [id:"lightD2m_", sOrder:5, desc:'Dim to ', comm:turnDim, sub:"valLight2", subType:"number", type:"hasSub", secLabel: getFormat("section", "On to Level - Group 2"), cap: "capability.switchLevel", sTitle: "Bright Level", sDesc:"0 to 100%", mul: true],
         [id:'dimPlus_', sOrder:6, desc:'Brightness +', comm:levelUp, sub:"valDimP", subType:"number", type:"hasSub", secLabel: getFormat("section", "Increase Level By"), cap: "capability.switchLevel", sTitle: "Increase by", sDesc:"0 to 15", mul: true],
     	 [id:'dimMinus_', sOrder:7, desc:'Brightness -', comm:levelDown, sub:"valDimM", subType:"number", type:"hasSub", secLabel: getFormat("section", "Decrease Level By"), cap: "capability.switchLevel", sTitle: "Decrease by", sDesc:"0 to 15", mul: true],
         [id:'lightsDT_', sOrder:8, desc:'Toggle Off/Dim to ', comm:dimToggle, sub:"valDT", subType:"number", type:"hasSub", secLabel: getFormat("section", "Toggle OnToLevel/Off"), cap: "capability.switchLevel", sTitle: "Bright Level", sDesc:"0 to 100%", mul: true],
         [id:'lightsRamp_', sOrder:9, desc:'Ramp ', comm:rampUp, sub:"valDir", subType:"enum", subOpt:['up','down'], type:"hasSub", secLabel: getFormat("section", "Ramp Up/Down"), cap: "capability.changeLevel", sTitle: "Ramp Direction (Up/Down)", sDesc:"Up or Down", mul: true],
         
         [id:'lightColorTemp_', sOrder:10, desc:'Set Light Color Temp to ', comm:colorSetT, sub:"valColorTemp", subType:"number", type:"hasSub", secLabel: getFormat("section", "Set Temperature"), cap: "capability.colorTemperature", sTitle: "Color Temp", sDesc:"2000 to 9000", mul: true],
314
         [id:'lightColor_', sOrder:11, desc:'Set Light Color H:', comm:colorSet, sub:"valHue", subType:"number", sub2:"valSat", sub3:"valLvl", type:"hasSub", secLabel: getFormat("section", "Set Color"), cap: "capability.colorControl", sTitle: "Hue", s2Title: "Saturation", s3Title: "Lvl", sDesc:"0 to 100", s2Desc:"0 to 100", s3Desc:"0 to 100", mul: true, s3NotReq:true],
Stephan Hackett's avatar
Stephan Hackett committed
315
316
317
318
319
     	          
         [id:"speakerpp_", sOrder:12, desc:'Toggle Play/Pause', comm:speakerplaystate, type:"normal", secLabel: getFormat("section", "Toggle Play/Pause"), cap: "capability.musicPlayer", mul: true],
     	 [id:'speakervu_', sOrder:13, desc:'Volume +', comm:levelUp, sub:"valSpeakU", subType:"number", type:"hasSub", secLabel: getFormat("section", "Increase Volume By"), cap: "capability.musicPlayer", sTitle: "Increase by", sDesc:"0 to 15", mul: true],
     	 [id:"speakervd_", sOrder:14, desc:'Volume -', comm:levelDown, sub:"valSpeakD", subType:"number", type:"hasSub", secLabel: getFormat("section", "Decrease Volume By"), cap: "capability.musicPlayer", sTitle: "Decrease by", sDesc:"0 to 15", mul: true],
         [id:'speakernt_', sOrder:15, desc:'Next Track', comm:speakernexttrack, type:"normal", secLabel: getFormat("section", "Go to Next Track"), cap: "capability.musicPlayer", mul: true],
320
321
322
         [id:'speakerpt_', sOrder:16, desc:'Previous Track', comm:speakerprevioustrack, type:"normal", secLabel: getFormat("section", "Go to Previous Track"), cap: "capability.musicPlayer", mul: true],
         [id:'speakermu_', sOrder:17, desc:'Mute', comm:speakermute, type:"normal", secLabel: getFormat("section", "Speakers Toggle Mute"), cap: "capability.musicPlayer", mul: true],
         [id:"musicPreset_", sOrder:18, desc:'Cycle Preset', comm:cyclePlaylist, type:"normal", secLabel: getFormat("section", "Preset to Cycle"), cap: "device.VirtualContainer", mul: true],         
Stephan Hackett's avatar
Stephan Hackett committed
323
         
324
325
326
         [id:'fanSet_', sOrder:19, desc:'Set Fan to ', comm:setFan, sub:"valSpeed", subType:"enum", subOpt:['off','low','medium-low','medium','high','auto'], type:"hasSub", secLabel: getFormat("section", "Set Speed"), cap: "capability.fanControl", sTitle: "Set Speed to", sDesc:"L/ML/M/H/A", mul: true],
         [id:"fanCycle_", sOrder:20, desc:'Cycle Fan Speed', comm:cycleFan, type:"normal", secLabel: getFormat("section", "Cycle Speed"), cap: "capability.fanControl", mul: true],         
         [id:"fanAdjust_", sOrder:21,desc:'Adjust', comm:adjustFan, type:"normal", secLabel: getFormat("section", "Cycle Speed (Legacy)"), cap: "capability.switchLevel", mul: true],
Stephan Hackett's avatar
Stephan Hackett committed
327
         
328
329
         [id:"mode_", sOrder:22, desc:'Set Mode', comm:changeMode, type:"normal", secLabel: getFormat("section", "Set Mode"), cap: "mode", mul: false],
     	 [id:"hsm_", sOrder:23, desc:'Set HSM', comm:setHSM, type:"normal", secLabel: getFormat("section", "Set HSM"), cap: "enum", opt:['armAway','armHome','disarm','armRules','disarmRules','disarmAll','armAll','cancelAlerts'], mul: false],
Stephan Hackett's avatar
Stephan Hackett committed
330

331
         [id:'rule_', sOrder:24, desc:'Rule To ', comm:ruleExec, sub:"valRule", subType:"enum", subOpt:['Run','Stop','Pause','Resume','Evaluate','Set Boolean True','Set Boolean False'], type:"hasSub", secLabel: getFormat("section", "Rule and Actions"), cap: "enum", opt: getRules(), sTitle: "Select Action Type", sDesc:"Choose Action", mul: true],
Stephan Hackett's avatar
Stephan Hackett committed
332
		 
333
334
335
336
337
338
         [id:"locks_", sOrder:25, desc:'Lock', comm:setUnlock, type:"normal", secLabel: getFormat("section", "Locks (Lock Only)"), cap: "capability.lock", mul: true],
		 [id:'cycleScenes_', sOrder:26, desc:'Cycle', comm:cycle, type:"normal", secLabel: getFormat("section", "Scenes (Cycle)"), cap: "device.SceneActivator", mul: true, isCycle: true],
         [id:"shadeAdjust_", sOrder:27,desc:'Adjust', comm:adjustShade, type:"normal", secLabel: getFormat("section", "Shades (Up/Down/Stop)"), cap: "capability.doorControl", mul: true],
         [id:'sirens_', sOrder:28, desc:'Toggle', comm:toggle, type:"normal", secLabel: getFormat("section", "Sirens (Toggle)"), cap: "capability.alarm", mul: true],
         [id:'httpRequest_', sOrder:29, desc:'Send: ', comm:hRequest, sub:"reqUrl", subType:"text", type:"hasSub", secLabel: getFormat("section", "Send Http Request"), cap: "enum", opt:['POST', 'GET'], sTitle:"HTTP URL", sDesc:"Enter complete url to send", mul: false],
         [id:"speechDevice_", sOrder:30, desc:'Send Msg To', comm:speechHandle, type:"normal", secLabel: getFormat("section", "Notifications (Speech):"), sub:"speechTxt", cap: "capability.speechSynthesis", subType:"text", sTitle: "Message To Speak:", sDesc:"Enter message to speak (Random messages: Use ; to separate choices)", mul: true],///set type to normal instead of sub so message text is not displayed
Stephan Hackett's avatar
Stephan Hackett committed
339
340
341
342
343
		 
		 [id:"notifications_", desc:'Send Push Notification', comm:messageHandle, sub:"valNotify", type:"bool"],
     	 [id:"phone_", desc:'Send SMS', comm:smsHandle, sub:"notifications_", type:"normal"],
        ]
    return detailMappings
344
345
}

Stephan Hackett's avatar
Stephan Hackett committed
346
def checkForUpdate(){
Sha'ul ben Yeshua's avatar
Sha'ul ben Yeshua committed
347
	def params = [uri: "https://raw.githubusercontent.com/sgrayban/ABC-Button/master/apps/Inovelli-Scene-Creator/child.json",
Stephan Hackett's avatar
Stephan Hackett committed
348
349
350
351
352
				   	contentType: "application/json"]
       	try {
			httpGet(params) { response ->
				def results = response.data
				def appStatus
Stephan Hackett's avatar
Stephan Hackett committed
353
354
				if(version() == results.currVersion) {
					appStatus = "<small>Child ${version()}</small><br>${results.noUpdateImg}"
Stephan Hackett's avatar
Stephan Hackett committed
355
356
				}
				else {
Stephan Hackett's avatar
Stephan Hackett committed
357
					appStatus = "<small>Child ${version()}</small><br>${results.updateImg}${results.changeLog}"
Sha'ul ben Yeshua's avatar
Sha'ul ben Yeshua committed
358
					log.warn "Inovelli Scene Creator Child App does not appear to be the latest version: Please update."
Stephan Hackett's avatar
Stephan Hackett committed
359
360
361
362
363
364
				}
				return appStatus
			}
		} 
        catch (e) {
        	log.error "Error:  $e"
365
    	}
366
367
}

Stephan Hackett's avatar
Stephan Hackett committed
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
def buttonEvent(evt) {
	if(allOk) {
    	def buttonNumber = evt.value
		def pressType = evt.name
		if(logEnable) log.debug "$buttonDevice: Button $buttonNumber was $pressType"
        
        //detects if button is used for graceful hold to dim function then calls stopLevelChange()
        if(pressType == "released" && settings["lightsRamp_${buttonNumber}_pushed"]){
        	rampEnd(settings["lightsRamp_${buttonNumber}_pushed"])
        }
        if(pressType == "released" && settings["lightsRamp_${buttonNumber}_held"]){
        	rampEnd(settings["lightsRamp_${buttonNumber}_held"])
        }        
        
    	def preferenceNames = settings.findAll{it.key.contains("_${buttonNumber}_${pressType}")}
    	preferenceNames.each{eachPref->
384
        	def prefDetail = state.details?.find{eachPref.key.contains(it.id)}		//returns the detail map of id,desc,comm,sub
Stephan Hackett's avatar
Stephan Hackett committed
385
386
387
388
389
390
391
392
393
        	def PrefSubValue = settings["${prefDetail.sub}${buttonNumber}_${pressType}"] //value of subsetting (eg 100)
            def PrefSub2Value = settings["${prefDetail.sub2}${buttonNumber}_${pressType}"] //value of subsetting (eg 100)
            def PrefSub3Value = settings["${prefDetail.sub3}${buttonNumber}_${pressType}"]	//value of subsetting (eg 100)
            if(prefDetail.sub3) "$prefDetail.comm"(eachPref.value,PrefSubValue, PrefSub2Value, PrefSub3Value)
            	else if(prefDetail.sub2) "$prefDetail.comm"(eachPref.value,PrefSubValue, PrefSub2Value)
        		else if(prefDetail.sub) "$prefDetail.comm"(eachPref.value,PrefSubValue)
                else if(prefDetail.isCycle) "$prefDetail.comm"(eachPref.value, "${eachPref.key}")
        	else "$prefDetail.comm"(eachPref.value)
    	}
Stephan Hackett's avatar
Stephan Hackett committed
394
	}
395
396
}

Stephan Hackett's avatar
Stephan Hackett committed
397
def speechHandle(devices, msg){
398
399
400
401
402
403
404
405
406
407
    log.info "Sending ${msg} to ${devices}"
	if(msg.contains(";")) {
		def myPool = msg.split(";")
		def poolSize = myPool.size()
		def randomItem = Math.abs(new Random().nextInt() % poolSize)
		devices.speak(myPool[randomItem-1])
	}
	else{
		devices.speak(msg)
	}
Stephan Hackett's avatar
Stephan Hackett committed
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
}

def turnOn(devices) {
	log.info "Turning On: $devices"
	devices.on()
}

def turnOff(devices) {
	log.info "Turning Off: $devices"
	devices.off()
}

def turnDim(devices, level) {
	log.info "Dimming (to $level): $devices"
	devices.setLevel(level)
}

def colorSet(devices,hueVal,satVal,lvlVal) {
    log.info "Setting Color (to H:$hueVal, S:$satVal, L:$lvlVal): $devices"
    def myColor = [:]
    myColor.hue = hueVal.toInteger()
    myColor.saturation = satVal.toInteger()
430
    if(lvlVal) myColor.level = lvlVal.toInteger()
Stephan Hackett's avatar
Stephan Hackett committed
431
432
433
434
435
436
437
438
439
440
    devices.setColor(myColor)//([hue:hueVal,saturation:satVal,level:50]) 
}

def colorSetT(devices, temp) {
    log.info "Setting Color Temp (to $temp): $devices"
    devices.setColorTemperature(temp)    
}

def adjustFan(device) {
	log.info "Adjusting: $device"
441
442
	def currentLevel = device.currentLevel[0]
	if(device.currentSwitch[0] == 'off') device.setLevel(15)
Stephan Hackett's avatar
Stephan Hackett committed
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
	else if (currentLevel < 34) device.setLevel(50)
  	else if (currentLevel < 67) device.setLevel(90)
	else device.off()
}

def adjustShade(device) {
	log.info "Shades: $device = ${device.currentMotor} state.lastUP = $state.lastshadesUp"
	if(device.currentMotor in ["up","down"]) {
    	state.lastshadesUp = device.currentMotor == "up"
    	device.stop()
    } else {
    	state.lastshadesUp ? device.down() : device.up()
        state.lastshadesUp = !state.lastshadesUp
    }
}

def setFan(devices, speed){
	log.info "Setting Speed to $speed: $devices"
    devices.setSpeed(speed)
}

def speakerplaystate(device) {
	log.info "Toggling Play/Pause: $device"
	device.currentStatus.contains('playing')? device.pause() : device.play()
}
   
def speakernexttrack(device) {
	log.info "Next Track Sent to: $device"
	device.nextTrack()
}   

474
475
476
477
478
def speakerprevioustrack(device) {
	log.info "Previous Track Sent to: $device"
	device.previousTrack()
} 

Stephan Hackett's avatar
Stephan Hackett committed
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
def speakermute(device) {
	log.info "Toggling Mute/Unmute: $device"
	device.currentMute.contains('unmuted')? device.mute() : device.unmute()
} 

def levelUp(device, inclevel) {
	log.info "Incrementing Level (by +$inclevel): $device"
	def currentVol = device.currentLevel[0]//device.currentValue('level')[0]	//currentlevel return a list...[0] is first item in list ie volume level
    def newVol = currentVol + inclevel
  	device.setLevel(newVol)
    if(logEnable) log.debug "Level increased by $inclevel to $newVol"
}

def levelDown(device, declevel) {
	log.info "Decrementing Level (by -$declevel): $device"
	def currentVol = device.currentLevel[0]//device.currentValue('level')[0]
    def newVol = currentVol - declevel
  	device.setLevel(newVol)
    if(logEnable) log.debug "Level decreased by $declevel to $newVol"
}

def rampUp(devices, dir){
    log.info "Ramping ${dir}: $devices"
    devices.startLevelChange(dir)
}

def rampEnd(device){
	log.info "Ending Ramp: $device"
    device.stopLevelChange()    
}

def setUnlock(devices) {
	log.info "Locking: $devices"
	devices.lock()
}

def toggle(devices) {
	log.info "Toggling: $devices"
	if (devices*.currentValue('switch').contains('on')) {
		devices.off()
519
	}
Stephan Hackett's avatar
Stephan Hackett committed
520
521
	else if (devices*.currentValue('switch').contains('off')) {
		devices.on()
522
	}
Stephan Hackett's avatar
Stephan Hackett committed
523
524
525
526
527
	else if (devices*.currentValue('alarm').contains('off')) {
        devices.siren()
    }
	else {
		devices.on()
528
	}
Stephan Hackett's avatar
Stephan Hackett committed
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
}

def dimToggle(devices, dimLevel) {
	log.info "Toggling On/Off | Dimming (to $dimLevel): $devices"
	if (devices*.currentValue('switch').contains('on')) devices.off()
	else devices.setLevel(dimLevel)
}

def runRout(rout){
	log.info "Running: $rout"
	location.helloHome.execute(rout)
}

def ruleExec(rules, action){
	log.info "Performing ${action} Action on Rules: ${rules}"
	def ruleAction
	if(action == "Run") ruleAction =  "runRuleAct"
	if(action == "Stop") ruleAction =  "stopRuleAct"
	if(action == "Pause") ruleAction =  "pauseRule"
	if(action == "Resume") ruleAction =  "resumeRule"
	if(action == "Evaluate") ruleAction =  "runRule"
	if(action == "Set Boolean True") ruleAction =  "setRuleBooleanTrue"
	if(action == "Set Boolean False") ruleAction =  "setRuleBooleanFalse"
	
	RMUtils.sendAction(rules, ruleAction, app.label)
}

def messageHandle(msg, inApp) {
	if(inApp==true) {
    	log.info "Push notification sent"
    	sendPush(msg)
560
561
562
	}
}

Stephan Hackett's avatar
Stephan Hackett committed
563
564
565
566
def smsHandle(phone, msg){
    log.info "SMS sent"
    sendSms(phone, msg ?:"No custom text entered on: $app.label")
}
567

Stephan Hackett's avatar
Stephan Hackett committed
568
569
570
def setHSM(hsmMode) {
    sendLocationEvent(name: "hsmSetArm", value: hsmMode)
}
571

Stephan Hackett's avatar
Stephan Hackett committed
572
573
574
575
def changeMode(mode) {
	log.info "Changing Mode to: $mode"
	if (location.mode != mode && location.modes?.find { it.name == mode}) setLocationMode(mode)
}
576

Stephan Hackett's avatar
Stephan Hackett committed
577
578
def cycleFan(devices) { //all fans will sync speeds with fisrt fan in the list
    log.info "Cycling: $devices"
579
580
581
582
583
584
    def mySpeed = devices[0].currentSpeed
    if(mySpeed == "off") devices.setSpeed("low") 
    if(mySpeed == "low") devices.setSpeed("medium-low") 
    if(mySpeed == "medium-low") devices.setSpeed("medium") 
    if(mySpeed == "medium") devices.setSpeed("high")
    if(mySpeed == "high") devices.setSpeed("off") 
Stephan Hackett's avatar
Stephan Hackett committed
585
}
586

Stephan Hackett's avatar
Stephan Hackett committed
587
588
589
590
591
592
593
def cycle(devices, devIndex) {
    log.info "Cycling: $devices"
    def mySize = devices.size() -1
    if(!state."${devIndex}" || state."${devIndex}" > mySize) state."${devIndex}" = 0
    devices[state."${devIndex}"].push()
    state."${devIndex}" ++
}
594

Stephan Hackett's avatar
Stephan Hackett committed
595
596
597
def cyclePlaylist(devices){
    devices.cycle()
}
Stephan Hackett's avatar
Stephan Hackett committed
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619

def hRequest(reqType, myUrl){
    def params = [
        uri: myUrl,
		contentType: 'application/x-www-form-urlencoded'
    ]
	
    if(logEnable) log.debug "${reqType} - ${params}"
	if(reqType == "POST") asynchttpPost('myPostResponse', params, [type: reqType])
    if(reqType == "GET") asynchttpGet('myPostResponse', params, [type: reqType])
  	  	
}

def myPostResponse(response,data){
	if(response.status != 200) {
		log.error "HTTP ${data["type"]} Request returned error ${response.status}. Check your URL!"
	}
    else {
        if(logEnable) log.debug "HTTP ${data["type"]} Request sent successfully"
    }
}

Stephan Hackett's avatar
Stephan Hackett committed
620
621
622
// execution filter methods
private getAllOk() {
	modeOk && daysOk && timeOk
623
624
}

Stephan Hackett's avatar
Stephan Hackett committed
625
626
627
628
629
private getModeOk() {
	def result = !modes || modes.contains(location.mode)
	if(logEnable) log.debug "modeOk = $result"
	result
}
630

Stephan Hackett's avatar
Stephan Hackett committed
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
private getDaysOk() {
	def result = true
	if (days) {
		def df = new java.text.SimpleDateFormat("EEEE")
		if (location.timeZone) {
			df.setTimeZone(location.timeZone)
		}
		else {
			df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
		}
		def day = df.format(new Date())
		result = days.contains(day)
	}
	if(logEnable) log.debug "daysOk = $result"
	result
}
647

Stephan Hackett's avatar
Stephan Hackett committed
648
649
650
651
652
653
654
655
656
657
658
private getTimeOk() {
	def result = true
	if (starting && ending) {
		def currTime = now()
		def start = timeToday(starting).time
		def stop = timeToday(ending).time
		result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
	}
	if(logEnable) log.debug "timeOk = $result"
	result
}
659

Stephan Hackett's avatar
Stephan Hackett committed
660
661
662
663
664
665
private hhmm(time, fmt = "h:mm a") {
	def t = timeToday(time, location.timeZone)
	def f = new java.text.SimpleDateFormat(fmt)
	f.setTimeZone(location.timeZone ?: timeZone(time))
	f.format(t)
}
666

Stephan Hackett's avatar
Stephan Hackett committed
667
668
669
private hideOptionsSection() {
	(starting || ending || days || modes || manualCount) ? false : true
}
670

Stephan Hackett's avatar
Stephan Hackett committed
671
672
673
private timeIntervalLabel() {
	(starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
}