-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathHTTP-HostPingSmartApp.groovy
344 lines (294 loc) · 12.6 KB
/
HTTP-HostPingSmartApp.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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
193
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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
/**
* Host Pinger - SmartThings
*
* Copyright 2016 Jake Tebbett
*
* 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.
*
* Icon By: Maxim Basinski: https://www.iconfinder.com/vasabii
*
* VERSION CONTROL
* ###############
*
* 28/10/16 1.0 Release Version
* 29/10/16 1.1 Added child app and triggering of external switches
* 30/10/16 1.2 Removed direct triggering of child devices, Child app now includes delay on 'Offline'
* 01/12/16 1.3 Untested fix for null value logging error
* 19/01/17 1.4 Added setup instrucions in app and cosmetic changes
* 24/02/17 1.5 Added exact config details for EXE in to live logging
* 25/02/17 1.6 Fixed bug in last event logging
* 19/07/17 1.7 Fixed another bug in event logging
* 23/07/17 1.8 Fixed the bugs from the previous two releases and hopefully tested it this time!
* 12/01/18 1.9 Fixed error when creating new device
* 06/03/18 1.10 Fixed spelling of 'received' throughout
*
*/
definition(
name: "Host Pinger${parent ? " - Child" : ""}",
namespace: "castlecole",
author: "Jake Tebbett",
description: "Capture ping result events to control virtual switches",
category: "My Apps",
iconUrl: "https://raw.githubusercontent.com/jebbett/STHostPinger/master/icons/icon.png",
iconX2Url: "https://raw.githubusercontent.com/jebbett/STHostPinger/master/icons/icon.png",
iconX3Url: "https://raw.githubusercontent.com/jebbett/STHostPinger/master/icons/icon.png",
parent: parent ? "jebbett.Host Pinger" : null,
singleInstance: true,
oauth: [displayName: "HostPingState", displayLink: ""])
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
if(parent){
if(state.hostState == null){
state.hostState = "unknown"
}
app.updateLabel("${settings.appName} [${state.hostState}]")
}else{
generateAccessToken()
logWriter("URL FOR USE IN HOSTPINGER EXE:\n"+
"<!ENTITY accessToken '${state.accessToken}'>\n"+
"<!ENTITY appId '${app.id}'>\n"+
"<!ENTITY ide '${getApiServerUrl()}'>")
if(state.lastEvent == null){state.lastEvent = "No event received, please ensure that config.config is setup correctly"}
}
}
def generateAccessToken() {
if (!state.accessToken) {
try {
createAccessToken()
} catch(e) {
state.installedOK = "No"
log.error "Error: $e"
return false
}
}
state.installedOK = "Yes"
return true
}
preferences {
page(name: "pageMain")
page(name: "pageChild")
page(name: "mainMenu")
page(name: "lastEvt")
page(name: "EndPointInfo")
page(name: "pageDevice")
page(name: "pageDevDetails")
page(name: "pageDevDelete")
page(name: "pageDevAdd")
page(name: "pageTrigger")
}
// MAIN PAGE
def pageMain() {
parent ? pageChild() : mainMenu()
}
def mainMenu() {
dynamicPage(name: "mainMenu", title: "", install: true, uninstall: true, submitOnChange: true) {
section("Host Setup") {
href(name: "pageTrigger", title: "Hosts", required: false, page: "pageTrigger", description: "")
}
section("Virtual Devices (optional)"){
href(name: "pageDevice", title: "Create & Manage Virtual Devices", required: false, page: "pageDevice", description: "")
}
section("Instructions"){
paragraph "1. Either create a virtual device in this app using a link above or decide to control an existing switch"
paragraph "2. Add a new host under the hosts section and then set the trigger as either the virtual device you created or an existing switch"
}
section(title: "ADVANCED") {
href(name: "LastEvent", title: "Events Received", required: false, page: "lastEvt", description: "")
href(name: "Setup Details", title: "Endpoint Setup Details", required: false, page: "EndPointInfo", description: "")
input "debugLogging", "bool", title: "Debug Logging", required: false, defaultValue: false, submitOnChange: true
}
}
}
def lastEvt() {
dynamicPage(name: "lastEvt", title: "Last Event", install: false, uninstall: false) {
section(title: "Details of Last Event Received") {
input "evtLogNum", "number", title: "Number Of Rows To Log", required: true, defaultValue: 20, submitOnChange: false
paragraph "${updateLog("get", "Status", settings?.evtLogNum ?:0, null)}"
logWriter(updateLog("get", "Status", settings?.evtLogNum, null))
}
}
}
def EndPointInfo() {
dynamicPage(name: "EndPointInfo", title: "End Point Information", install: false, uninstall: false) {
section(title: "App ID") {
paragraph "$app.id"
}
section(title: "Access Token") {
if(!state.accessToken){
paragraph("You will need to enable OAuth in IDE, this can be found here:\n'My SmartApps' > App Settings", title: "OAuth Not Enabled", required: true, state: null)
}else{
paragraph "$state.accessToken"
}
}
}
}
def updateLog(command, name, len, event){
def logName = "log$name" as String // Add log prefix
def length = (len?:0).toInteger() // Make an integer or 0
if(length == 0){state.remove(logName); return "No Data, press done to start logging"} // If length set to 0, delete state
if(!state."$logName"){state."$logName" = []} // If list state new, create blank list
def tempList = state."$logName" // Create a temp List
// SET OR GET
switch(command) {
case "set":
if(!length || tempList.size() < length){length = tempList.size()+1} // Get valid trim length if short
tempList.add(0,"${new Date(now()).format("dd MMM HH:mm", location.timeZone)} - ${event}") // Add to top of tempList
state."$logName" = tempList.subList(0, length)
break;
case "get":
if(tempList.size() < length){length = tempList.size()} // Get valid trim length if short
def formattedList = ""
tempList = tempList.subList(0, length)
tempList.each { item ->
if(formattedList == ""){
formattedList = item
}else{
formattedList = formattedList + "\n" + item
}
}
return formattedList
break;
}
}
//// TRIGGER CHILD APP
def pageTrigger() {
dynamicPage(name: "pageTrigger", title: "Hosts", install: true, uninstall: false) {
section() {
app(name:"HostPingerChild", title:"Add Host..", appName: "Host Pinger", namespace: "jebbett", multiple: true, uninstall: true, image: "https://raw.githubusercontent.com/jebbett/STHostPinger/master/icons/add_48.png")
}
}
}
def pageChild() {
dynamicPage(name: "pageChild", title: "Host Details", install: true, uninstall: true) {
section() {
input "appName", type: "text", title: "Name", required:true, submitOnChange: true
input "hostName", type: "text", title: "IP, Host or URL", required:false
input "hostSwitch", "capability.switch", title:"Turn This Device On/Off With Status", multiple: true, required: false
input "hostDelay", type: "number", title: "Delay going offline (seconds)", required:true, defaultValue: 0
paragraph "Offline delay can help to avoid a false negative or where a device briefly disconnects from the network, this delay should either be 0 to report actual results or should exceed your polling interval to handle errors"
}
}
}
def AppCommandReceived(command, host){
if (settings?.hostName == host){
if(command == "online"){
hostSwitch?.on()
unschedule()
logWriter("Is Online")
}else{
if (settings?.hostDelay == "0"){
commandOffline()
}else{
def theDelay = settings.hostDelay as int
runIn(theDelay, commandOffline)
logWriter("Delayed Off Line Request Received")
}
}
state.hostState = command
app.updateLabel("${appName} [${command}]")
}
}
def commandOffline(){
hostSwitch?.off()
logWriter("Is Offline")
}
//// VIRTUAL DEVICE
def pageDevice() {
dynamicPage(name: "pageDevice", title: "Create Device", install: false, uninstall: false) {
section() {
def greenOrRed = ""
def i = 1 as int
getSortedDevices().each { dev ->
if(dev.switchState?.value == null){greenOrRed = "https://raw.githubusercontent.com/jebbett/STHostPinger/master/icons/unknown.png"}
else if(dev.switchState.value == "on"){greenOrRed = "https://raw.githubusercontent.com/jebbett/STHostPinger/master/icons/circle_green.png"}
else{greenOrRed = "https://raw.githubusercontent.com/jebbett/STHostPinger/master/icons/circle_red.png"}
href(name: "pageDevDetails$i", title:"$dev.label", description: "", params: [devi: dev.deviceNetworkId, devstate: dev.switchState?.value], page: "pageDevDetails", required: false, state: "complete", image: "$greenOrRed")
i++
}
}
section(title: "Please ensure that the custom device type is installed!"){
href(name: "pageDevDetails", title:"Create New Device", description: "", params: [devi: false], page: "pageDevDetails", required: false, state: "complete", image: "https://raw.githubusercontent.com/jebbett/STHostPinger/master/icons/add_48.png")
}
}
}
private getSortedDevices() {
return getChildDevices().sort{it.displayName}
}
def pageDevDetails(params) {
dynamicPage(name: "pageDevDetails", title: "Device Details", install: false, uninstall: false) {
if(params.devi){
section("Status") {
if(params.devstate == null){paragraph("Device Status: No Status Received - Check config.config", required: false)}
else if(params.devstate == "on"){paragraph("Device Status: Online", required: true, state: "complete")}
else{paragraph("Device Status: Offline", required: true)}
}
section("DELETE") {
href(name: "pageDevDelete", title:"DELETE DEVICE", description: "ONLY PRESS IF YOU ARE SURE!", params: [devi: "$params.devi"], page: "pageDevDelete", required: true, state: null, image: "https://raw.githubusercontent.com/jebbett/STHostPinger/master/icons/delete_48.png")
}
}else{
section() {
paragraph("Create a new Host Ping Device")
input "devName", type: "text", title: "Name", required:false, submitOnChange: true
href(name: "pageDevAdd", title:"Create Device", description: "", params: [devi: "$params.devi"], page: "pageDevAdd", required: true, state: null, image: "https://raw.githubusercontent.com/jebbett/STHostPinger/master/icons/add_48.png")
}
}
}
}
def pageDevAdd(params) {
if(settings.devName){
def DeviceID = "HostPingDev:"+settings.devName
def existingDevice = getChildDevice(DeviceID)
if(!existingDevice) {
def newTrigger = addChildDevice("jebbett", "Host Ping Device", DeviceID, null, [name: "PING: ${settings.devName}", label: "PING: ${settings.devName}"])
}
pageDevice()
}else{
dynamicPage(name: "pageDevAdd", title: "Device Details", install: false, uninstall: false) {
section() {
paragraph("Name not set")
}
}
}
}
def pageDevDelete(params) {
deleteChildDevice(params.devi)
pageDevice()
}
//// EVENT HANDLING
mappings {
path("/statechanged/:command") { action: [ GET: "OnCommandReceived" ] }
}
def OnCommandReceived() {
def command = params.command
def host = params.ipadd
logWriter("Event Received: ${command} ${host}")
updateLog("set", "Status", settings?.evtLogNum, "${host} [${command}]")
childApps.each { child ->
child.AppCommandReceived(command, host)
}
return
}
////GENERIC
// Debug Logging
private def logWriter(value) {
if(parent){
if(parent.debugLogging) {log.debug "${app.label} >> ${value}"}
}else{
if(debugLogging) {log.debug "${app.label} >> ${value}"}
}
}