/** * Join API Device * * * Copyright 2018 Stephan Hackett * * 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. * * This driver uses sections of code derived from the original Pushover Driver that @ogiewon and I worked on. Thanks for you contributions Dan. * * 06/04/19 - added Notification category * - switch From GET requests to POST for added security * - switched to asynch calls for efficiency * - added multiple inline options (Title, Devices, Category, SMS#, Actions) * * 06/11/19 - fixed sms bug * - added Notification Icon (if Status Bar icon is not set, this will set the image to both notification and status bar icon) * * * * */ def version() {"v1.0.20190611a"} preferences { input("apikey", "text", title: "Join API Key: [api docs here]", description: "") if(getValidated()){ input("deviceNames", "enum", title: "Select Device [D]:", description: "", multiple: true, required: false, options: getValidated("deviceList")) input("title", "text", title: "Notification Title [T]:", description: "") input("icon", "text", title: "Notification Icon URI:", description: "(Notification image; and status bar icon if not set)") input("smallicon", "text", title: "Status Bar Icon URI:", description: "(Image to be displayed in the status bar)") input("url", "text", title: "URL:", description: "(URL to be opened when Notification is clicked)") input("category", "text", title: "Notification Category [C]:", description: "(Android 8+ allows custom notication handling)") input("sound", "text", title: "Sound URI:", description: "(URL of notification sound to be played)") input("image", "text", title: "Image URI:", description: "(URL of image to be displayed in the notification body)") input("myApp", "text", title: "Open App by Name:", description: "(Name of Android app to open)") input("appPackage", "text", title: "Open App by Package:", description: "(Name of Android Package to open)") input("smsnumber", "number", title: "Phone # to send SMS text TO [S]:", description: "(Text will be sent FROM the Join Device selected above)") input("actions", "text", title: "Actions for Notification [A]:", description: "(separate multiple actions with commas)") input("logEnable", "bool", title: "Enable Debug Logging?:", required: true) } } metadata { definition (name: "Join API Device", namespace: "stephack", author: "Stephan Hackett", importUrl: "https://raw.githubusercontent.com/stephack/Hubitat/master/drivers/Join%20API%20Driver/Join%20API%20Driver.groovy") { capability "Notification" capability "Actuator" capability "Speech Synthesis" } } def installed() { initialize() } def updated() { initialize() } def initialize() { state.version = version() state.devices = deviceNames //if(myPackage) device.updateSetting("appPackage","test") device.removeSetting("deviceName") device.removeSetting("myImage") device.removeSetting("myTitle") device.removeSetting("apiKey") device.removeSetting("myPackage") device.removeSetting("action") } def getValidated(type){ if(apikey){ if(type=="deviceList" && logEnable){log.debug "Generating Device List..."} else if(logEnable) log.debug "Validating Key..." def validated = false def params = [ uri: "https://joinjoaomgcd.appspot.com/_ah/api/registration/v1/listDevices?apikey=${apikey}", ] if(logEnable) log.debug "Validation params: ${params}" if ((apikey =~ /[A-Za-z0-9]{30}/)) { try{ httpGet(params){response -> if(response.status != 200) { log.error "Received HTTP error ${response.status}. Check your keys!" } else { if(type=="deviceList"){ if(logEnable) log.debug "Device list generated" deviceOptions = response.data.records.deviceName if(logEnable) log.debug "Device List: ${deviceOptions}" } else { if(logEnable) log.debug "Keys validated" validated = true } } } } catch (Exception e) { log.error "An invalid key was probably entered. Join API Server Returned: ${e}" } } else { log.error "API key '${apikey}' is not properly formatted!" } if(type=="deviceList") return deviceOptions return validated } } def speak(message) { if (deviceNames) { log.info "Sending Speech Request: '${message}' to Device: $deviceNames"} if(deviceNames && deviceNames instanceof List) { deviceNames = deviceNames.join(',') } def apiParams = ["apikey":apikey,"deviceNames":deviceNames, "say":message] def params = [ uri: "https://joinjoaomgcd.appspot.com/_ah/api/messaging/v1/sendPush?", requestContentType: 'application/json', contentType: 'application/json', body : apiParams ] if(logEnable) log.debug "Speak params: ${params}" if ((apikey =~ /[A-Za-z0-9]{30}/)) { asynchttpPost('myPostResponse', params) } else { log.error "API key '${apikey}' is not properly formatted!" } } def deviceNotification(message) { if(message.startsWith("[") || message.endsWith("]")|| message.contains("][")){ log.warn "Improperly formatted message!" return } def apiParams = buildMessage(message) if(logEnable) log.debug "Merged Settings: " + apiParams if (apiParams.deviceNames) { log.info "Sending Message: '${apiParams.text}' to Device: $apiParams.deviceNames"} if(apiParams.deviceNames && apiParams.deviceNames instanceof List) { //if multiple devices selected, convert list to string apiParams.deviceNames = apiParams.deviceNames.join(',') } def params = [ uri: "https://joinjoaomgcd.appspot.com/_ah/api/messaging/v1/sendPush?", requestContentType: 'application/json', contentType: 'application/json', body : apiParams ] if(logEnable) log.debug params if ((apikey =~ /[A-Za-z0-9]{30}/)) { asynchttpPost('myPostResponse', params) } else { log.error "API key '${apikey}' is not properly formatted!" } } def myPostResponse(response,data){ if(response.status != 200) { log.error "Received HTTP error ${response.status}. Check your keys!" } else { if(logEnable) log.debug "Message Received by Join API Server" } } def buildMessage(message){ def current = settings if(logEnable) log.debug "Settings: " + current def currentList = message.tokenize("[]") //separate complete message into a list with shortcut,value if(logEnable) log.debug "Current Split List: " + currentList def currentSize = currentList.size() def myMap = [:] myMap["text"] = currentList[0] if(currentSize > 1){ def count = 1 def totalPairs = currentSize / 2 //get the total number of key value pairs to create for (i in 1..totalPairs){ def myKey = getPrefName()[currentList[count]] //set key to actual preference name : eg. A=actions or D=deviceNames def myVal = currentList[count+1] myMap[myKey] = myVal count = count + 2 } } if(logEnable) log.debug "Custom Settings: " + myMap current << myMap //merge driver settings with custom setting if(current.smsnumber) current["smstext"] = myMap.text //when an smsnumber is included, set smstext to message current.remove("logEnable") //remove uneeded preferences if(current.actions){ //format actions using ||| delimiter as per api log.info current.actions log.info current.actions.replace(",", "|||") current["actions"] = current.actions.replace(",", "|||") } return current } def getPrefName(){ //convert inline shortcut to preference name return [ "D":"deviceNames", "T":"title", "C":"category", "A":"actions", "S":"smsnumber" ] }