/* Multi-TP-Link Product Integration Application Copyright Dave Gutheinz License: https://github.com/DaveGut/HubitatActive/blob/master/KasaDevices/License.md ===== Link to Documentation ===== https://github.com/DaveGut/HubitatActive/blob/master/KasaDevices/Documentation.pdf ========================================*/ import groovy.json.JsonBuilder import groovy.json.JsonSlurper import org.json.JSONObject definition( name: "Tapo Integration", namespace: nameSpace(), author: "Dave Gutheinz", description: "Application to install Tapo protocol TP-Link bulbs, plugs, and switches.", category: "Convenience", iconUrl: "", iconX2Url: "", installOnOpen: true, singleInstance: true, documentationLink: "https://github.com/DaveGut/tpLink_Hubitat", importUrl: "https://raw.githubusercontent.com/DaveGut/tpLink_Hubitat/refs/heads/main/App/tapo_device_install.groovy" ) preferences { page(name: "startPage") page(name: "enterCredentialsPage") page(name: "addDevicesPage") page(name: "removeDevicesPage") } def installed() { app?.updateSetting("logEnable", false) app?.updateSetting("infoLog", true) def hub = location.hubs[0] def hubIpArray = hub.localIP.split('\\.') def segments = [hubIpArray[0],hubIpArray[1],hubIpArray[2]].join(".") app?.updateSetting("lanSegment", [type:"string", value: segments]) app?.updateSetting("hostLimits", [type:"string", value: "2, 254"]) app?.updateSetting("encPassword", "INVALID") app?.updateSetting("encUsername", "INVALID") app?.updateSetting("localHash", "INVALID") atomicState.devices = [:] atomicState.unsupported = [:] logInfo([method: "installed", status: "Initialized settings"]) } def updated() { Map logData = [method: "updated", status: "setting updated for new session"] app?.removeSetting("selectedAddDevices") app?.removeSetting("selectedRemoveDevices") app?.updateSetting("logEnable", false) app?.updateSetting("appSetup", false) app?.updateSetting("spInst", false) state.needCreds = false runIn(30, scheduleItems) logInfo(logData) } def scheduleItems() { Map logData = [method: "scheduleItems"] unschedule() runIn(570, resetTpLinkChecked) logData << setLogsOff() Map newDevices = [:] atomicState.devices.each { def isChild = getChildDevice(it.key) if (isChild) { newDevices << it } } atomicState.devices = newDevices atomicState.unsupported = [:] logData << [devData: "Purged non-children"] logDebug(logData) } def uninstalled() { getAllChildDevices().each { deleteChildDevice(it.deviceNetworkId) } logInfo([method: "uninstalled", status: "Devices and App uninstalled"]) } def initInstance() { Map logData = [method: "initInstance"] if (!state.needCreds) { state.needCreds = false } if (!atomicState.unsupported) { atomicState.unsupported = [:] } state.tpLinkChecked = false logData << [setSegments: setSegments()] if (state.appVersion != version()) { state.appVersion = version() app.removeSetting("scheduled") // ver 2.4.1 only app.removeSetting("appVer") // ver 2.4.1 only app.removeSetting("ports") // ver 2.4.1 only state.remove("portArray") // ver 2.4.1 only app.removeSetting("showFound") // ver 2.4.1 only app.removeSetting("startApp") // ver 2.4.1 only app.removeSetting("finding") // ver 2.4.1 only logData << [versionUpdates: "update to appVersion ${version()}"] } logInfo(logData) return } def setSegments() { try { state.segArray = lanSegment.split('\\,') def rangeArray = hostLimits.split('\\,') def array0 = rangeArray[0].toInteger() def array1 = array0 + 2 if (rangeArray.size() > 1) { array1 = rangeArray[1].toInteger() } state.hostArray = [array0, array1] return "segmentsUpdated" } catch (e) { logWarn("startPage: Invalid entry for Lan Segements or Host Array Range. Resetting to default!") def hub = location.hubs[0] def hubIpArray = hub.localIP.split('\\.') def segments = [hubIpArray[0],hubIpArray[1],hubIpArray[2]].join(".") app?.updateSetting("lanSegment", [type:"string", value: segments]) app?.updateSetting("hostLimits", [type:"string", value: "1, 254"]) return "error updating segments" } } def startPage() { logInfo([method: "startPage", status: "Starting ${app.getLabel()} Setup"]) def action = initInstance() if (selectedRemoveDevices) { removeDevices() } else if (selectedAddDevices) { addDevices() } return dynamicPage(name:"startPage", uninstall: true, install: true) { section() { input "spInst", "bool", title: "Display Quick Instructions", submitOnChange: true, defaultValue: true if (spInst) { paragraph quickStartPg() } Map lanParams = [LanSegments: state.segArray, hostRange: state.hostArray] String params = "Application Setup Parameters" params += "\n\tlanDiscoveryParams: ${lanParams}" paragraph params input "appSetup", "bool", title: "Modify Application Setup (LanDiscParams)", submitOnChange: true, defaultValue: false if (appSetup) { input "lanSegment", "string", title: "Lan Segments (ex: 192.168.50, 192,168.01)", submitOnChange: true input "hostLimits", "string", title: "Host Address Range (ex: 5, 100)", submitOnChange: true } def credDesc = "Credentials: userName: ${userName}, password set/redacted." if (!userName || !userPassword) { credDesc = "Credentials not set. Enter credentials to proceed." state.needCreds = true } else { def wait = createTpLinkCreds() logDebug(wait) credDesc += "\nEncoded password and username set based on credentials." state.needCreds = false } href "enterCredentialsPage", title: "Enter/Update Username and Password", description: credDesc if (!state.needCreds) { href "addDevicesPage", title: "Scan for devices and add", description: "It will take 30+ seconds to find devices." } else { paragraph "Credentials are required to scan for to find devices." } href "removeDevicesPage", title: "Remove Devices", description: "Select to remove selected Device from Hubitat." input "logEnable", "bool", title: "Debug logging", submitOnChange: true } } } def enterCredentialsPage() { Map credData = [:] return dynamicPage (name: "enterCredentialsPage", title: "Enter Credentials", nextPage: startPage, install: false) { section() { input "hidePassword", "bool", title: "Hide Password", submitOnChange: true, defaultValue: false paragraph "Password and Username are both case sensitive." def pwdType = "string" if (hidePassword) { pwdType = "password" } input ("userName", "string", title: "Email Address", required: false, submitOnChange: false) input ("userPassword", pwdType, title: "Account Password", required: false, submitOnChange: false) } } } // ===== Add selected newdevices ===== def addDevicesPage() { logDebug("addDevicesPage") app?.removeSetting("selectedAddDevices") findTpLinkDevices("getTpLinkLanData", 10) atomicState.finding = true int i for(i = 0; i < 60; i+=5) { pauseExecution(5000) if (atomicState.finding == false) { pauseExecution(5000) i = 61 break } } def addDevicesData = atomicState.devices Map uninstalledDevices = [:] List installedDrivers = getInstalledDrivers() Map foundDevices = [:] addDevicesData.each { def isChild = getChildDevice(it.key) if (!isChild) { uninstalledDevices["${it.key}"] = "${it.value.alias}, ${it.value.type}" def driver = "TpLink ${it.value.type}" if (!installedDrivers.find{ it == driver }) { foundDevices << ["${it.value.alias}": "Not installed. Needs driver ${driver}"] } else { foundDevices << ["${it.value.alias}": "Not installed. Driver found."] } } else { foundDevices << ["${it.value.alias}": "Installed."] } } foundDevices = foundDevices.sort() String devicesFound = "Found Devices" foundDevices.each{ devicesFound += "\n\t${it}" } String missingDevices = "Exercise missing devices through the Tapo Phone " missingDevices += "App. Then select this function." return dynamicPage(name:"addDevicesPage", title: "Add Devices to Hubitat", nextPage: startPage, install: false) { section() { input ("selectedAddDevices", "enum", required: false, multiple: true, title: "Devices to add (${uninstalledDevices.size() ?: 0} available).\n\t" + "Total Devices: ${addDevicesData.size()}", description: "Use the dropdown to select devices. Then select 'Done'.", options: uninstalledDevices) if (atomicState.unsupported.size() > 0) { def unsupNote = "Found Unsupported Devices: ${atomicState.unsupported}" paragraph unsupNote } paragraph devicesFound href "addDevicesPage", title: "Rescan for Additional Devices", description: missingDevices } } } def getInstalledDrivers() { List installedDrivers = [] Map params = [ uri: "https://127.0.0.1:8443", ignoreSSLIssues: true, path: "/hub2/userDeviceTypes", ] try { httpGet(params) { resp -> resp.data.each { if (it.namespace == nameSpace()) { installedDrivers << it.name } } } logDebug([method: "getInstalledDrivers", drivers: installedDrivers]) } catch (err) { logWarn([method: "getInstalledDrivers", err: err, message: "Unable to get installed driver list"]) } return installedDrivers } def supportedProducts() { return ["SMART.TAPOBULB", "SMART.TAPOPLUG", "SMART.TAPOSWITCH", "SMART.TAPOHUB", "SMART.KASAHUB", "SMART.KASAPLUG", "SMART.KASASWITCH", "SMART.TAPOROBOVAC", "SMART.IPCAMERA", "SMART.TAPODOORBELL", // "IOT.SMARTPLUGSWITCH", "SMART.MATTERBULB", "SMART.MATTERPLUG", ] } // ===== Add Devices ===== def addDevices() { Map logData = [method: "addDevices", selectedAddDevices: selectedAddDevices] def hub = location.hubs[0] def devicesData = atomicState.devices selectedAddDevices.each { dni -> def isChild = getChildDevice(dni) if (!isChild) { def device = devicesData.find { it.key == dni } addDevice(device, dni) } pauseExecution(3000) } logInfo(logData) app?.removeSetting("selectedAddDevices") } def addDevice(device, dni) { Map logData = [method: "addDevice", dni: dni, alias: device.value.alias] try { Map deviceData = [devIp: device.value.devIp, protocol: device.value.protocol, baseUrl: device.value.baseUrl, type: device.value.type] if (device.value.ledVer) { deviceData << [ledVer: device.value.ledVer] } if (device.value.isEm) { deviceData << [isEm: device.value.isEm] } if (device.value.gradOnOff) { deviceData << [gradOnOff: device.value.gradOnOff] } if (device.value.ctLow) { deviceData << [ctLow: device.value.ctLow] } if (device.value.ctHigh) { deviceData << [ctHigh: device.value.ctHigh] } if (device.value.alert) { deviceData << [alert: device.value.alert] } if (device.value.power) { deviceData << [power: device.value.power] } if (device.value.isDoorbell) { deviceData << [isDoorbell: device.value.isDoorbell] } addChildDevice( nameSpace(), "TpLink ${device.value.type}", dni, [ "label": device.value.alias, "name" : device.value.model, "data" : deviceData ] ) logData << [status: "added"] logInfo(logData) } catch (err) { logData << [status: "failedToAdd", device: device, errorMsg: err] logWarn(logData) } return } // ===== Remove Devices ===== def removeDevicesPage() { Map logData = [method: "removeDevicesPage"] Map installedDevices = [:] getChildDevices().each { installedDevices << ["${it.device.deviceNetworkId}": it.device.label] } logData << [installedDevices: installedDevices] logInfo(logData) return dynamicPage(name:"removedDevicesPage", title:"Remove Devices from Hubitat", nextPage: startPage, install: false) { section() { input ("selectedRemoveDevices", "enum", required: false, multiple: true, title: "Devices to remove (${installedDevices.size() ?: 0} available)", description: "Use the dropdown to select devices. Then select 'Done'.", options: installedDevices) } } } def removeDevices() { Map logData = [method: "removeDevices", selectedRemoveDevices: selectedRemoveDevices] selectedRemoveDevices.each { dni -> deleteChildDevice(dni) logData << ["${dni}": [status: "deleted"]] } app?.removeSetting("selectedRemoveDevices") logInfo(logData) } def getDeviceData(dni) { Map devices = atomicState.devices def device = devices.find { it.key == dni } Map devData = device.value return devData } // ===== Common UDP Communications ===== private sendLanCmd(ip, port, cmdData, action, commsTo = 5, ignore = false) { def myHubAction = new hubitat.device.HubAction( cmdData, hubitat.device.Protocol.LAN, [type: hubitat.device.HubAction.Type.LAN_TYPE_UDPCLIENT, destinationAddress: "${ip}:${port}", encoding: hubitat.device.HubAction.Encoding.HEX_STRING, ignoreResponse: ignore, parseWarning: true, timeout: commsTo, callback: action]) try { sendHubCommand(myHubAction) } catch (error) { logWarn("sendLanCmd: command to ${ip}:${port} failed. Error = ${error}") } return } def pingTest() { Map devices = atomicState.devices devices.each {device -> def baseUrl = device.value.baseUrl ping(baseUrl) } } def quickStartPg() { String quickSP = "" quickSP += "a. Tapo Phone App: Install device.\n" quickSP += "b. Tapo Phone App: Turn on Third Party Compatibility.\n" quickSP += "c. Tapo Phone App: Exercise device to be installed.\n" quickSP += "d. Here: Enter/Update Username/Pwd, as required.\n" quickSP += "e. Here: Scan for devices and add.\n" quickSP += "Detailed information: use the ? icon above.\n" return quickSP } // ~~~~~ start include (375) davegut.appTpLinkSmart ~~~~~ library ( // library marker davegut.appTpLinkSmart, line 1 name: "appTpLinkSmart", // library marker davegut.appTpLinkSmart, line 2 namespace: "davegut", // library marker davegut.appTpLinkSmart, line 3 author: "Dave Gutheinz", // library marker davegut.appTpLinkSmart, line 4 description: "Discovery library for Application support the Tapo protocol devices.", // library marker davegut.appTpLinkSmart, line 5 category: "utilities", // library marker davegut.appTpLinkSmart, line 6 documentationLink: "" // library marker davegut.appTpLinkSmart, line 7 ) // library marker davegut.appTpLinkSmart, line 8 import org.json.JSONObject // library marker davegut.appTpLinkSmart, line 9 import groovy.json.JsonOutput // library marker davegut.appTpLinkSmart, line 10 import groovy.json.JsonBuilder // library marker davegut.appTpLinkSmart, line 11 import groovy.json.JsonSlurper // library marker davegut.appTpLinkSmart, line 12 def createTpLinkCreds() { // library marker davegut.appTpLinkSmart, line 14 Map SMARTCredData = [u: userName, p: userPassword] // library marker davegut.appTpLinkSmart, line 15 // User Creds (username/password hashed) // library marker davegut.appTpLinkSmart, line 16 String encUsername = mdEncode("SHA-1", userName.bytes).encodeHex().encodeAsBase64().toString() // library marker davegut.appTpLinkSmart, line 17 app?.updateSetting("encUsername", [type: "string", value: encUsername]) // library marker davegut.appTpLinkSmart, line 18 SMARTCredData << [encUsername: encUsername] // library marker davegut.appTpLinkSmart, line 19 String encPassword = userPassword.trim().bytes.encodeBase64().toString() // library marker davegut.appTpLinkSmart, line 20 app?.updateSetting("encPassword", [type: "string", value: encPassword]) // library marker davegut.appTpLinkSmart, line 21 SMARTCredData << [encPassword: encPassword] // library marker davegut.appTpLinkSmart, line 22 // vacAes Creds (password only) // library marker davegut.appTpLinkSmart, line 23 String encPasswordVac = mdEncode("MD5", userPassword.trim().bytes).encodeHex().toString().toUpperCase() // library marker davegut.appTpLinkSmart, line 24 app?.updateSetting("encPasswordVac", [type: "string", value: encPasswordVac]) // library marker davegut.appTpLinkSmart, line 25 SMARTCredData << [encPasswordVac: encPasswordVac] // library marker davegut.appTpLinkSmart, line 26 // Camera Creds (password only) // library marker davegut.appTpLinkSmart, line 27 String encPasswordCam = mdEncode("SHA-256", userPassword.trim().bytes).encodeHex().toString().toUpperCase() // library marker davegut.appTpLinkSmart, line 28 app?.updateSetting("encPasswordCam", [type: "string", value: encPasswordCam]) // library marker davegut.appTpLinkSmart, line 29 SMARTCredData << [encPasswordCam: encPasswordCam] // library marker davegut.appTpLinkSmart, line 30 // KLAP Local Hash // library marker davegut.appTpLinkSmart, line 31 byte[] userHashByte = mdEncode("SHA-1", encodeUtf8(userName).getBytes()) // library marker davegut.appTpLinkSmart, line 32 byte[] passwordHashByte = mdEncode("SHA-1", encodeUtf8(userPassword.trim()).getBytes()) // library marker davegut.appTpLinkSmart, line 33 byte[] authHashByte = [userHashByte, passwordHashByte].flatten() // library marker davegut.appTpLinkSmart, line 34 String authHash = mdEncode("SHA-256", authHashByte).encodeBase64().toString() // library marker davegut.appTpLinkSmart, line 35 app?.updateSetting("localHash", [type: "string", value: authHash]) // library marker davegut.appTpLinkSmart, line 36 SMARTCredData << [localHash: localHash] // library marker davegut.appTpLinkSmart, line 37 logDebug(SMARTCredData) // library marker davegut.appTpLinkSmart, line 38 return [SMARTDevCreds: SMARTCredData] // library marker davegut.appTpLinkSmart, line 39 } // library marker davegut.appTpLinkSmart, line 40 def findTpLinkDevices(action, timeout = 10) { // library marker davegut.appTpLinkSmart, line 42 Map logData = [method: "findTpLinkDevices", action: action, timeOut: timeout] // library marker davegut.appTpLinkSmart, line 43 def start = state.hostArray.min().toInteger() // library marker davegut.appTpLinkSmart, line 44 def finish = state.hostArray.max().toInteger() + 1 // library marker davegut.appTpLinkSmart, line 45 logData << [hostArray: state.hostArray, pollSegments: state.segArray] // library marker davegut.appTpLinkSmart, line 46 List deviceIPs = [] // library marker davegut.appTpLinkSmart, line 47 state.segArray.each { // library marker davegut.appTpLinkSmart, line 48 def pollSegment = it.trim() // library marker davegut.appTpLinkSmart, line 49 logData << [pollSegment: pollSegment] // library marker davegut.appTpLinkSmart, line 50 for(int i = start; i < finish; i++) { // library marker davegut.appTpLinkSmart, line 51 deviceIPs.add("${pollSegment}.${i.toString()}") // library marker davegut.appTpLinkSmart, line 52 } // library marker davegut.appTpLinkSmart, line 53 def cmdData = "0200000101e51100095c11706d6f58577b22706172616d73223a7b227273615f6b6579223a222d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d5c6e4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b43415145416d684655445279687367797073467936576c4d385c6e54646154397a61586133586a3042712f4d6f484971696d586e2b736b4e48584d525a6550564134627532416257386d79744a5033445073665173795679536e355c6e6f425841674d303149674d4f46736350316258367679784d523871614b33746e466361665a4653684d79536e31752f564f2f47474f795436507459716f384e315c6e44714d77373563334b5a4952387a4c71516f744657747239543337536e50754a7051555a7055376679574b676377716e7338785a657a78734e6a6465534171765c6e3167574e75436a5356686d437931564d49514942576d616a37414c47544971596a5442376d645348562f2b614a32564467424c6d7770344c7131664c4f6a466f5c6e33737241683144744a6b537376376a624f584d51695666453873764b6877586177717661546b5658382f7a4f44592b2f64684f5374694a4e6c466556636c35585c6e4a514944415141425c6e2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d5c6e227d7d" // library marker davegut.appTpLinkSmart, line 54 logData << [pass1: "port 20002"] // library marker davegut.appTpLinkSmart, line 55 sendLanCmd(deviceIPs.join(','), "20002", cmdData, action, timeout) // library marker davegut.appTpLinkSmart, line 56 runIn(timeout + 5, udpTimeout) // library marker davegut.appTpLinkSmart, line 57 pauseExecution(10000) // library marker davegut.appTpLinkSmart, line 58 // Port 20004 datas may not respond to firs poll (battery saving). Poll twice // library marker davegut.appTpLinkSmart, line 59 logData << [pass2: "port 20004"] // library marker davegut.appTpLinkSmart, line 60 sendLanCmd(deviceIPs.join(','), "20004", cmdData, "nullParse", 2) // library marker davegut.appTpLinkSmart, line 61 pauseExecution(7000) // library marker davegut.appTpLinkSmart, line 62 sendLanCmd(deviceIPs.join(','), "20004", cmdData, action, timeout) // library marker davegut.appTpLinkSmart, line 63 runIn(timeout + 5, udpTimeout) // library marker davegut.appTpLinkSmart, line 64 } // library marker davegut.appTpLinkSmart, line 65 logInfo(logData) // library marker davegut.appTpLinkSmart, line 66 state.tpLinkChecked = true // library marker davegut.appTpLinkSmart, line 67 runIn(570, resetTpLinkChecked) // library marker davegut.appTpLinkSmart, line 68 return logData // library marker davegut.appTpLinkSmart, line 69 } // library marker davegut.appTpLinkSmart, line 70 def udpTimeout() { // library marker davegut.appTpLinkSmart, line 72 Map logData = [method: "udpTimeout", status: "no devices found", error: "udpTimeout"] // library marker davegut.appTpLinkSmart, line 73 logDebug(logData) // library marker davegut.appTpLinkSmart, line 74 atomicState.finding = false // library marker davegut.appTpLinkSmart, line 75 } // library marker davegut.appTpLinkSmart, line 76 def nullParse(response) {} // library marker davegut.appTpLinkSmart, line 78 def getTpLinkLanData(response) { // library marker davegut.appTpLinkSmart, line 80 Map logData = [method: "getTpLinkLanData", // library marker davegut.appTpLinkSmart, line 81 action: "Completed LAN Discovery", // library marker davegut.appTpLinkSmart, line 82 smartDevicesFound: response.size()] // library marker davegut.appTpLinkSmart, line 83 logInfo(logData) // library marker davegut.appTpLinkSmart, line 84 unschedule("udpTimeout") // library marker davegut.appTpLinkSmart, line 85 List discData = [] // library marker davegut.appTpLinkSmart, line 86 if (response instanceof Map) { // library marker davegut.appTpLinkSmart, line 87 Map devData = getDiscData(response) // library marker davegut.appTpLinkSmart, line 88 if (devData.status == "OK") { // library marker davegut.appTpLinkSmart, line 89 discData << devData // library marker davegut.appTpLinkSmart, line 90 } // library marker davegut.appTpLinkSmart, line 91 } else { // library marker davegut.appTpLinkSmart, line 92 response.each { // library marker davegut.appTpLinkSmart, line 93 Map devData = getDiscData(it) // library marker davegut.appTpLinkSmart, line 94 if (devData.status == "OK" && !discData.toString().contains(devData.dni)) { // library marker davegut.appTpLinkSmart, line 95 discData << devData // library marker davegut.appTpLinkSmart, line 96 } // library marker davegut.appTpLinkSmart, line 97 } // library marker davegut.appTpLinkSmart, line 98 } // library marker davegut.appTpLinkSmart, line 99 getAllTpLinkDeviceData(discData) // library marker davegut.appTpLinkSmart, line 100 } // library marker davegut.appTpLinkSmart, line 101 def getDiscData(response) { // library marker davegut.appTpLinkSmart, line 103 Map devData = [:] // library marker davegut.appTpLinkSmart, line 104 Map unsupDev = atomicState.unsupported // library marker davegut.appTpLinkSmart, line 105 try { // library marker davegut.appTpLinkSmart, line 106 def respData = parseLanMessage(response.description) // library marker davegut.appTpLinkSmart, line 107 if (respData.type == "LAN_TYPE_UDPCLIENT") { // library marker davegut.appTpLinkSmart, line 108 byte[] payloadByte = hubitat.helper.HexUtils.hexStringToByteArray(respData.payload.drop(32)) // library marker davegut.appTpLinkSmart, line 109 String payloadString = new String(payloadByte) // library marker davegut.appTpLinkSmart, line 110 // Handle Jumbo packets // library marker davegut.appTpLinkSmart, line 111 if (payloadString.length() > 1007) { // library marker davegut.appTpLinkSmart, line 112 if (payloadString.contains(""":"H200",""")) { // library marker davegut.appTpLinkSmart, line 113 // H200. Keep data up to, but not including "key" // library marker davegut.appTpLinkSmart, line 114 payloadString = payloadString.substring(0,payloadString.indexOf(""","key":""")) + "}}}" // library marker davegut.appTpLinkSmart, line 115 } else { // library marker davegut.appTpLinkSmart, line 116 // Unknown cases with fingers crossed. (Note: I could go through a lot of processing // library marker davegut.appTpLinkSmart, line 117 // here to determine the break point; however, I am lazy and it is a currently undiscovered case..) // library marker davegut.appTpLinkSmart, line 118 payloadString = payloadString + """"}}}""" // library marker davegut.appTpLinkSmart, line 119 } // library marker davegut.appTpLinkSmart, line 120 } // library marker davegut.appTpLinkSmart, line 121 Map payload = new JsonSlurper().parseText(payloadString).result // library marker davegut.appTpLinkSmart, line 122 List supported = supportedProducts() // library marker davegut.appTpLinkSmart, line 123 String devType = payload.device_type // library marker davegut.appTpLinkSmart, line 124 String model = payload.device_model // library marker davegut.appTpLinkSmart, line 125 String devIp = payload.ip // library marker davegut.appTpLinkSmart, line 126 String protocol = payload.mgt_encrypt_schm.encrypt_type // library marker davegut.appTpLinkSmart, line 127 String status = "true" // library marker davegut.appTpLinkSmart, line 128 if (protocol == "TPAP") { // library marker davegut.appTpLinkSmart, line 129 status = "Protocol not supported" // library marker davegut.appTpLinkSmart, line 130 unsupDev << ["${model}": "Protocol ${protocol} not supported"] // library marker davegut.appTpLinkSmart, line 131 } else if (model == "HS200") { // library marker davegut.appTpLinkSmart, line 132 status = "Device model not supported" // library marker davegut.appTpLinkSmart, line 133 unsupDev << ["${model}": "Model ${model} not supported"] // library marker davegut.appTpLinkSmart, line 134 } else if (!supported.contains(devType)) { // library marker davegut.appTpLinkSmart, line 135 status = "Device type not supported" // library marker davegut.appTpLinkSmart, line 136 unsupDev << ["${model}": "DevType ${devType} not supported"] // library marker davegut.appTpLinkSmart, line 137 } // library marker davegut.appTpLinkSmart, line 138 if (status == "true") { // library marker davegut.appTpLinkSmart, line 139 String dni = payload.mac.replaceAll("-", "") // library marker davegut.appTpLinkSmart, line 140 String port = payload.mgt_encrypt_schm.http_port // library marker davegut.appTpLinkSmart, line 141 String httpStr = "http://" // library marker davegut.appTpLinkSmart, line 142 String httpPath = "/app" // library marker davegut.appTpLinkSmart, line 143 if (payload.mgt_encrypt_schm.is_support_https) { // library marker davegut.appTpLinkSmart, line 144 httpStr = "https://" // library marker davegut.appTpLinkSmart, line 145 } // library marker davegut.appTpLinkSmart, line 146 if (devType == "SMART.IPCAMERA" || devType == "SMART.TAPODOORBELL") { // library marker davegut.appTpLinkSmart, line 147 protocol = "camera" // library marker davegut.appTpLinkSmart, line 148 port = "443" // library marker davegut.appTpLinkSmart, line 149 } else if (devType == "SMART.TAPOROBOVAC" && protocol == "AES") { // library marker davegut.appTpLinkSmart, line 150 protocol = "vacAes" // library marker davegut.appTpLinkSmart, line 151 httpPath = "" // library marker davegut.appTpLinkSmart, line 152 } // library marker davegut.appTpLinkSmart, line 153 String baseUrl = httpStr + devIp + ":" + port + httpPath // library marker davegut.appTpLinkSmart, line 154 devData << [udpPort: respData.port, type: devType, model: model, baseUrl: baseUrl, // library marker davegut.appTpLinkSmart, line 155 dni: dni, ip: devIp, port: port, protocol: protocol, status: "OK"] // library marker davegut.appTpLinkSmart, line 156 if (payload.power) { devData << [power: payload.power] } // library marker davegut.appTpLinkSmart, line 157 logDebug(devData) // library marker davegut.appTpLinkSmart, line 158 } else { // library marker davegut.appTpLinkSmart, line 159 Map errResp = [method: "getDiscData", payload: payload, status: status] // library marker davegut.appTpLinkSmart, line 160 logDebug(errResp) // library marker davegut.appTpLinkSmart, line 161 } // library marker davegut.appTpLinkSmart, line 162 } // library marker davegut.appTpLinkSmart, line 163 } catch (err) { // library marker davegut.appTpLinkSmart, line 164 devData << [status: "INVALID", respData: repsData, error: err] // library marker davegut.appTpLinkSmart, line 165 logWarn(devData) // library marker davegut.appTpLinkSmart, line 166 } // library marker davegut.appTpLinkSmart, line 167 atomicState.unsupported = unsupDev // library marker davegut.appTpLinkSmart, line 168 return devData // library marker davegut.appTpLinkSmart, line 169 } // library marker davegut.appTpLinkSmart, line 170 def getAllTpLinkDeviceData(List discData) { // library marker davegut.appTpLinkSmart, line 172 Map logData = [method: "getAllTpLinkDeviceData", discData: discData.size()] // library marker davegut.appTpLinkSmart, line 173 discData.each { Map devData -> // library marker davegut.appTpLinkSmart, line 174 if (devData.protocol == "KLAP") { // library marker davegut.appTpLinkSmart, line 175 klapHandshake(devData.baseUrl, localHash, devData) // library marker davegut.appTpLinkSmart, line 176 } else if (devData.protocol == "AES") { // library marker davegut.appTpLinkSmart, line 177 aesHandshake(devData.baseUrl, devData) // library marker davegut.appTpLinkSmart, line 178 } else if (devData.protocol == "vacAes") { // library marker davegut.appTpLinkSmart, line 179 vacAesHandshake(devData.baseUrl, userName, encPasswordVac, devData) // library marker davegut.appTpLinkSmart, line 180 } else if (devData.protocol == "camera") { // library marker davegut.appTpLinkSmart, line 181 Map hsInput = [url: devData.baseUrl, user: userName, pwd: encPasswordCam] // library marker davegut.appTpLinkSmart, line 182 cameraHandshake(hsInput, devData) // library marker davegut.appTpLinkSmart, line 183 } else { // library marker davegut.appTpLinkSmart, line 184 unknownProt(devData) // library marker davegut.appTpLinkSmart, line 185 } // library marker davegut.appTpLinkSmart, line 186 pauseExecution(1000) // library marker davegut.appTpLinkSmart, line 187 } // library marker davegut.appTpLinkSmart, line 188 atomicState.finding = false // library marker davegut.appTpLinkSmart, line 189 logDebug(logData) // library marker davegut.appTpLinkSmart, line 190 } // library marker davegut.appTpLinkSmart, line 191 def getDataCmd() { // library marker davegut.appTpLinkSmart, line 193 List requests = [[method: "get_device_info"]] // library marker davegut.appTpLinkSmart, line 194 requests << [method: "component_nego"] // library marker davegut.appTpLinkSmart, line 195 Map cmdBody = [ // library marker davegut.appTpLinkSmart, line 196 method: "multipleRequest", // library marker davegut.appTpLinkSmart, line 197 params: [requests: requests]] // library marker davegut.appTpLinkSmart, line 198 return cmdBody // library marker davegut.appTpLinkSmart, line 199 } // library marker davegut.appTpLinkSmart, line 200 def addToDevices(devData, cmdData) { // library marker davegut.appTpLinkSmart, line 202 Map logData = [method: "addToDevices"] // library marker davegut.appTpLinkSmart, line 203 String dni = devData.dni // library marker davegut.appTpLinkSmart, line 204 def devicesData = atomicState.devices // library marker davegut.appTpLinkSmart, line 205 devicesData.remove(dni) // library marker davegut.appTpLinkSmart, line 206 def comps // library marker davegut.appTpLinkSmart, line 207 def cmdResp // library marker davegut.appTpLinkSmart, line 208 String alias // library marker davegut.appTpLinkSmart, line 209 String tpType = devData.type // library marker davegut.appTpLinkSmart, line 210 String model = devData.model // library marker davegut.appTpLinkSmart, line 211 if (devData.protocol != "camera") { // library marker davegut.appTpLinkSmart, line 212 comps = cmdData.find { it.method == "component_nego" } // library marker davegut.appTpLinkSmart, line 213 comps = comps.result.component_list // library marker davegut.appTpLinkSmart, line 214 cmdResp = cmdData.find { it.method == "get_device_info" } // library marker davegut.appTpLinkSmart, line 215 cmdResp = cmdResp.result // library marker davegut.appTpLinkSmart, line 216 byte[] plainBytes = cmdResp.nickname.decodeBase64() // library marker davegut.appTpLinkSmart, line 217 alias = new String(plainBytes) // library marker davegut.appTpLinkSmart, line 218 if (alias == "") { alias = model } // library marker davegut.appTpLinkSmart, line 219 } else { // library marker davegut.appTpLinkSmart, line 220 comps = cmdData.find { it.method == "getAppComponentList" } // library marker davegut.appTpLinkSmart, line 221 comps = comps.result.app_component.app_component_list // library marker davegut.appTpLinkSmart, line 222 cmdResp = cmdData.find { it.method == "getDeviceInfo" } // library marker davegut.appTpLinkSmart, line 223 cmdResp = cmdResp.result.device_info.basic_info // library marker davegut.appTpLinkSmart, line 224 alias = cmdResp.device_alias // library marker davegut.appTpLinkSmart, line 225 if (alias == "") { alias = model } // library marker davegut.appTpLinkSmart, line 226 } // library marker davegut.appTpLinkSmart, line 227 def type = "Unknown" // library marker davegut.appTpLinkSmart, line 228 def ctHigh // library marker davegut.appTpLinkSmart, line 229 def ctLow // library marker davegut.appTpLinkSmart, line 230 Map deviceData = [devIp: devData.ip, deviceType: tpType, protocol: devData.protocol, // library marker davegut.appTpLinkSmart, line 231 model: model, baseUrl: devData.baseUrl, alias: alias] // library marker davegut.appTpLinkSmart, line 232 // Determine Driver to Load // library marker davegut.appTpLinkSmart, line 233 if (tpType.contains("PLUG") || tpType.contains("SWITCH")) { // library marker davegut.appTpLinkSmart, line 234 type = "Plug" // library marker davegut.appTpLinkSmart, line 235 if (comps.find { it.id == "control_child" }) { // library marker davegut.appTpLinkSmart, line 236 type = "Parent" // library marker davegut.appTpLinkSmart, line 237 } else if (comps.find{it.id=="dimmer"} || comps.find{it.id=="brightness"}) { // library marker davegut.appTpLinkSmart, line 238 type = "Dimmer" // library marker davegut.appTpLinkSmart, line 239 } // library marker davegut.appTpLinkSmart, line 240 } else if (tpType.contains("HUB")) { // library marker davegut.appTpLinkSmart, line 241 type = "Hub" // library marker davegut.appTpLinkSmart, line 242 } else if (tpType.contains("BULB")) { // library marker davegut.appTpLinkSmart, line 243 type = "Dimmer" // library marker davegut.appTpLinkSmart, line 244 if (comps.find { it.id == "light_strip" }) { // library marker davegut.appTpLinkSmart, line 245 type = "Lightstrip" // library marker davegut.appTpLinkSmart, line 246 } else if (comps.find { it.id == "color" }) { // library marker davegut.appTpLinkSmart, line 247 type = "Color Bulb" // library marker davegut.appTpLinkSmart, line 248 } // library marker davegut.appTpLinkSmart, line 249 if (type != "Dimmer" && comps.find { it.id == "color_temperature" } ) { // library marker davegut.appTpLinkSmart, line 250 ctHigh = cmdResp.color_temp_range[1] // library marker davegut.appTpLinkSmart, line 251 ctLow = cmdResp.color_temp_range[0] // library marker davegut.appTpLinkSmart, line 252 deviceData << [ctHigh: ctHigh, ctLow: ctLow] // library marker davegut.appTpLinkSmart, line 253 } // library marker davegut.appTpLinkSmart, line 254 } else if (tpType.contains("ROBOVAC")) { // library marker davegut.appTpLinkSmart, line 255 type = "Robovac" // library marker davegut.appTpLinkSmart, line 256 } else if (tpType.contains("CAMERA")) { // library marker davegut.appTpLinkSmart, line 257 type = "Camera" // library marker davegut.appTpLinkSmart, line 258 if (comps.find { it.name == "ptz" } ) { // library marker davegut.appTpLinkSmart, line 259 type = "Cam Ptz" // library marker davegut.appTpLinkSmart, line 260 } // library marker davegut.appTpLinkSmart, line 261 } else if (tpType.contains("DOORBELL")) { // library marker davegut.appTpLinkSmart, line 262 type = "Camera" // library marker davegut.appTpLinkSmart, line 263 deviceData << [isDoorbell: "true"] // library marker davegut.appTpLinkSmart, line 264 } else if (tpType.contains("CHIME")) { // library marker davegut.appTpLinkSmart, line 265 type = "Chime" // library marker davegut.appTpLinkSmart, line 266 } // library marker davegut.appTpLinkSmart, line 267 deviceData << [type: type] // library marker davegut.appTpLinkSmart, line 268 if (comps.find {it.id == "led"} ) { // library marker davegut.appTpLinkSmart, line 269 String ledVer = comps.find {it.id == "led"}.ver_code // library marker davegut.appTpLinkSmart, line 270 deviceData << [ledVer: ledVer] // library marker davegut.appTpLinkSmart, line 271 } // library marker davegut.appTpLinkSmart, line 272 if (comps.find {it.id == "energy_monitoring"}) { deviceData << [isEm: "true"] } // library marker davegut.appTpLinkSmart, line 273 if (comps.find {it.id == "on_off_gradually"}) { deviceData << [gradOnOff: "true"] } // library marker davegut.appTpLinkSmart, line 274 if (comps.find { it.name == "led"}) { // library marker davegut.appTpLinkSmart, line 275 String ledVer = comps.find { it.name == "led" }.version // library marker davegut.appTpLinkSmart, line 276 deviceData << [ledVer: ledVer] // library marker davegut.appTpLinkSmart, line 277 } // library marker davegut.appTpLinkSmart, line 278 if (comps.find {it.name == "alert"}) { deviceData << [alert: "true"] } // library marker davegut.appTpLinkSmart, line 279 if (devData.power) { deviceData << [power: devData.power] } // library marker davegut.appTpLinkSmart, line 280 devicesData << ["${dni}": deviceData] // library marker davegut.appTpLinkSmart, line 281 atomicState.devices = devicesData // library marker davegut.appTpLinkSmart, line 282 logData << ["${deviceData.alias}": deviceData, dni: dni] // library marker davegut.appTpLinkSmart, line 283 Map InfoData = ["${deviceData.alias}": "added to device data"] // library marker davegut.appTpLinkSmart, line 284 logInfo("${deviceData.alias}: added to device data") // library marker davegut.appTpLinkSmart, line 285 updateChild(dni, deviceData) // library marker davegut.appTpLinkSmart, line 286 logDebug(logData) // library marker davegut.appTpLinkSmart, line 287 } // library marker davegut.appTpLinkSmart, line 288 def updateChild(dni, deviceData) { // library marker davegut.appTpLinkSmart, line 290 def child = getChildDevice(dni) // library marker davegut.appTpLinkSmart, line 291 if (child) { // library marker davegut.appTpLinkSmart, line 292 child.updateChild(deviceData) // library marker davegut.appTpLinkSmart, line 293 } // library marker davegut.appTpLinkSmart, line 294 } // library marker davegut.appTpLinkSmart, line 295 // ===== get Smart KLAP Protocol Data ===== // library marker davegut.appTpLinkSmart, line 297 def sendKlapDataCmd(handshakeData, data) { // library marker davegut.appTpLinkSmart, line 298 if (handshakeData.respStatus != "Login OK") { // library marker davegut.appTpLinkSmart, line 299 Map logData = [method: "sendKlapDataCmd", handshake: handshakeData, data: data] // library marker davegut.appTpLinkSmart, line 300 logWarn(logData) // library marker davegut.appTpLinkSmart, line 301 } else { // library marker davegut.appTpLinkSmart, line 302 Map reqParams = [timeout: 10, headers: ["Cookie": data.data.cookie]] // library marker davegut.appTpLinkSmart, line 303 def seqNo = data.data.seqNo + 1 // library marker davegut.appTpLinkSmart, line 304 String cmdBodyJson = new groovy.json.JsonBuilder(getDataCmd()).toString() // library marker davegut.appTpLinkSmart, line 305 Map encryptedData = klapEncrypt(cmdBodyJson.getBytes(), data.data.encKey, // library marker davegut.appTpLinkSmart, line 306 data.data.encIv, data.data.encSig, seqNo) // library marker davegut.appTpLinkSmart, line 307 reqParams << [ // library marker davegut.appTpLinkSmart, line 308 uri: "${data.data.devData.baseUrl}/request?seq=${encryptedData.seqNumber}", // library marker davegut.appTpLinkSmart, line 309 body: encryptedData.cipherData, // library marker davegut.appTpLinkSmart, line 310 ignoreSSLIssues: true, // library marker davegut.appTpLinkSmart, line 311 timeout:10, // library marker davegut.appTpLinkSmart, line 312 contentType: "application/octet-stream", // library marker davegut.appTpLinkSmart, line 313 requestContentType: "application/octet-stream"] // library marker davegut.appTpLinkSmart, line 314 asynchttpPost("parseKlapResp", reqParams, [data: data]) // library marker davegut.appTpLinkSmart, line 315 } // library marker davegut.appTpLinkSmart, line 316 } // library marker davegut.appTpLinkSmart, line 317 def parseKlapResp(resp, respData) { // library marker davegut.appTpLinkSmart, line 319 Map data = respData.data // library marker davegut.appTpLinkSmart, line 320 Map logData = [method: "parseKlapResp", ip: data.data.devData.ip, model: data.data.devData.model] // library marker davegut.appTpLinkSmart, line 321 if (resp.status == 200) { // library marker davegut.appTpLinkSmart, line 322 try { // library marker davegut.appTpLinkSmart, line 323 byte[] cipherResponse = resp.data.decodeBase64()[32..-1] // library marker davegut.appTpLinkSmart, line 324 def clearResp = klapDecrypt(cipherResponse, data.data.encKey, // library marker davegut.appTpLinkSmart, line 325 data.data.encIv, data.data.seqNo + 1) // library marker davegut.appTpLinkSmart, line 326 Map cmdResp = new JsonSlurper().parseText(clearResp) // library marker davegut.appTpLinkSmart, line 327 logData << [status: "OK"] // library marker davegut.appTpLinkSmart, line 328 if (cmdResp.error_code == 0) { // library marker davegut.appTpLinkSmart, line 329 addToDevices(data.data.devData, cmdResp.result.responses) // library marker davegut.appTpLinkSmart, line 330 logDebug(logData) // library marker davegut.appTpLinkSmart, line 331 } else { // library marker davegut.appTpLinkSmart, line 332 logData << [status: "errorInCmdResp", cmdResp: cmdResp] // library marker davegut.appTpLinkSmart, line 333 logWarn(logData) // library marker davegut.appTpLinkSmart, line 334 } // library marker davegut.appTpLinkSmart, line 335 } catch (err) { // library marker davegut.appTpLinkSmart, line 336 logData << [status: "deviceDataParseError", error: err, dataLength: resp.data.length()] // library marker davegut.appTpLinkSmart, line 337 logWarn(logData) // library marker davegut.appTpLinkSmart, line 338 } // library marker davegut.appTpLinkSmart, line 339 } else { // library marker davegut.appTpLinkSmart, line 340 logData << [status: "httpFailure", data: resp.properties] // library marker davegut.appTpLinkSmart, line 341 logWarn(logData) // library marker davegut.appTpLinkSmart, line 342 } // library marker davegut.appTpLinkSmart, line 343 } // library marker davegut.appTpLinkSmart, line 344 // ===== get Smart Camera Protocol Data ===== // library marker davegut.appTpLinkSmart, line 346 def sendCameraDataCmd(devData, camCmdIn) { // library marker davegut.appTpLinkSmart, line 347 List requests = [[method:"getDeviceInfo", params:[device_info:[name:["basic_info"]]]], // library marker davegut.appTpLinkSmart, line 348 [method:"getAppComponentList", params:[app_component:[name:"app_component_list"]]]] // library marker davegut.appTpLinkSmart, line 349 Map cmdBody = [method: "multipleRequest", params: [requests: requests]] // library marker davegut.appTpLinkSmart, line 350 def cmdStr = JsonOutput.toJson(cmdBody) // library marker davegut.appTpLinkSmart, line 351 Map reqBody = [method: "securePassthrough", // library marker davegut.appTpLinkSmart, line 352 params: [request: aesEncrypt(cmdStr, camCmdIn.lsk, camCmdIn.ivb)]] // library marker davegut.appTpLinkSmart, line 353 String cmdData = new groovy.json.JsonBuilder(reqBody).toString() // library marker davegut.appTpLinkSmart, line 354 String initTagHex = camCmdIn.encPwd + camCmdIn.cnonce // library marker davegut.appTpLinkSmart, line 355 String initTag = mdEncode("SHA-256", initTagHex.getBytes()).encodeHex().toString().toUpperCase() // library marker davegut.appTpLinkSmart, line 356 String tagString = initTag + cmdData + camCmdIn.seqNo // library marker davegut.appTpLinkSmart, line 357 String tag = mdEncode("SHA-256", tagString.getBytes()).encodeHex().toString().toUpperCase() // library marker davegut.appTpLinkSmart, line 358 Map heads = getCamHeaders() // library marker davegut.appTpLinkSmart, line 359 heads << ["Tapo_tag": tag, Seq: camCmdIn.seqNo] // library marker davegut.appTpLinkSmart, line 360 Map reqParams = [uri: camCmdIn.apiUrl, // library marker davegut.appTpLinkSmart, line 361 body: cmdData, // library marker davegut.appTpLinkSmart, line 362 contentType: "application/json", // library marker davegut.appTpLinkSmart, line 363 requestContentType: "application/json", // library marker davegut.appTpLinkSmart, line 364 timeout: 10, // library marker davegut.appTpLinkSmart, line 365 ignoreSSLIssues: true, // library marker davegut.appTpLinkSmart, line 366 headers: heads // library marker davegut.appTpLinkSmart, line 367 ] // library marker davegut.appTpLinkSmart, line 368 asynchttpPost("parseCameraResp", reqParams, [data: [devData: devData, camCmdIn: camCmdIn]]) // library marker davegut.appTpLinkSmart, line 369 } // library marker davegut.appTpLinkSmart, line 370 def parseCameraResp(resp, data) { // library marker davegut.appTpLinkSmart, line 372 Map logData = [method: "parseCameraResp", ip: data.data.devData.ip] // library marker davegut.appTpLinkSmart, line 373 if (resp.json.error_code == 0) { // library marker davegut.appTpLinkSmart, line 374 resp = resp.json // library marker davegut.appTpLinkSmart, line 375 try { // library marker davegut.appTpLinkSmart, line 376 def clearResp = aesDecrypt(resp.result.response, data.data.camCmdIn.lsk, // library marker davegut.appTpLinkSmart, line 377 data.data.camCmdIn.ivb) // library marker davegut.appTpLinkSmart, line 378 Map cmdResp = new JsonSlurper().parseText(clearResp) // library marker davegut.appTpLinkSmart, line 379 logData << [status: "OK"] // library marker davegut.appTpLinkSmart, line 380 if (cmdResp.error_code == 0) { // library marker davegut.appTpLinkSmart, line 381 addToDevices(data.data.devData, cmdResp.result.responses) // library marker davegut.appTpLinkSmart, line 382 logDebug(logData) // library marker davegut.appTpLinkSmart, line 383 } else { // library marker davegut.appTpLinkSmart, line 384 logData << [status: "errorInCmdResp", cmdResp: cmdResp] // library marker davegut.appTpLinkSmart, line 385 logWarn(logData) // library marker davegut.appTpLinkSmart, line 386 } // library marker davegut.appTpLinkSmart, line 387 } catch (err) { // library marker davegut.appTpLinkSmart, line 388 logData << [status: "decryptDataError", error: err] // library marker davegut.appTpLinkSmart, line 389 logWarn(logData) // library marker davegut.appTpLinkSmart, line 390 } // library marker davegut.appTpLinkSmart, line 391 } else { // library marker davegut.appTpLinkSmart, line 392 logData << [status: "rerurnDataErrorCode", resp: resp] // library marker davegut.appTpLinkSmart, line 393 logWarn(logData) // library marker davegut.appTpLinkSmart, line 394 } // library marker davegut.appTpLinkSmart, line 395 } // library marker davegut.appTpLinkSmart, line 396 // ===== get Smart AES Protocol Data ===== // library marker davegut.appTpLinkSmart, line 398 def getAesToken(resp, data) { // library marker davegut.appTpLinkSmart, line 399 Map logData = [method: "getAesToken"] // library marker davegut.appTpLinkSmart, line 400 if (resp.status == 200) { // library marker davegut.appTpLinkSmart, line 401 if (resp.json.error_code == 0) { // library marker davegut.appTpLinkSmart, line 402 try { // library marker davegut.appTpLinkSmart, line 403 def clearResp = aesDecrypt(resp.json.result.response, data.encKey, data.encIv) // library marker davegut.appTpLinkSmart, line 404 Map cmdResp = new JsonSlurper().parseText(clearResp) // library marker davegut.appTpLinkSmart, line 405 if (cmdResp.error_code == 0) { // library marker davegut.appTpLinkSmart, line 406 def token = cmdResp.result.token // library marker davegut.appTpLinkSmart, line 407 logData << [respStatus: "OK", token: token] // library marker davegut.appTpLinkSmart, line 408 logDebug(logData) // library marker davegut.appTpLinkSmart, line 409 sendAesDataCmd(token, data) // library marker davegut.appTpLinkSmart, line 410 } else { // library marker davegut.appTpLinkSmart, line 411 logData << [respStatus: "ERROR code in cmdResp", // library marker davegut.appTpLinkSmart, line 412 error_code: cmdResp.error_code, // library marker davegut.appTpLinkSmart, line 413 check: "cryptoArray, credentials", data: cmdResp] // library marker davegut.appTpLinkSmart, line 414 logWarn(logData) // library marker davegut.appTpLinkSmart, line 415 } // library marker davegut.appTpLinkSmart, line 416 } catch (err) { // library marker davegut.appTpLinkSmart, line 417 logData << [respStatus: "ERROR parsing respJson", respJson: resp.json, // library marker davegut.appTpLinkSmart, line 418 error: err] // library marker davegut.appTpLinkSmart, line 419 logWarn(logData) // library marker davegut.appTpLinkSmart, line 420 } // library marker davegut.appTpLinkSmart, line 421 } else { // library marker davegut.appTpLinkSmart, line 422 logData << [respStatus: "ERROR code in resp.json", errorCode: resp.json.error_code, // library marker davegut.appTpLinkSmart, line 423 respJson: resp.json] // library marker davegut.appTpLinkSmart, line 424 logWarn(logData) // library marker davegut.appTpLinkSmart, line 425 } // library marker davegut.appTpLinkSmart, line 426 } else { // library marker davegut.appTpLinkSmart, line 427 logData << [respStatus: "ERROR in HTTP response", respStatus: resp.status, data: resp.properties] // library marker davegut.appTpLinkSmart, line 428 logWarn(logData) // library marker davegut.appTpLinkSmart, line 429 } // library marker davegut.appTpLinkSmart, line 430 } // library marker davegut.appTpLinkSmart, line 431 def sendAesDataCmd(token, data) { // library marker davegut.appTpLinkSmart, line 433 def cmdStr = JsonOutput.toJson(getDataCmd()).toString() // library marker davegut.appTpLinkSmart, line 434 Map reqBody = [method: "securePassthrough", // library marker davegut.appTpLinkSmart, line 435 params: [request: aesEncrypt(cmdStr, data.encKey, data.encIv)]] // library marker davegut.appTpLinkSmart, line 436 Map reqParams = [uri: "${data.baseUrl}?token=${token}", // library marker davegut.appTpLinkSmart, line 437 body: new groovy.json.JsonBuilder(reqBody).toString(), // library marker davegut.appTpLinkSmart, line 438 contentType: "application/json", // library marker davegut.appTpLinkSmart, line 439 requestContentType: "application/json", // library marker davegut.appTpLinkSmart, line 440 timeout: 10, // library marker davegut.appTpLinkSmart, line 441 headers: ["Cookie": data.cookie]] // library marker davegut.appTpLinkSmart, line 442 asynchttpPost("parseAesResp", reqParams, [data: data]) // library marker davegut.appTpLinkSmart, line 443 } // library marker davegut.appTpLinkSmart, line 444 def parseAesResp(resp, data) { // library marker davegut.appTpLinkSmart, line 446 Map logData = [method: "parseAesResp"] // library marker davegut.appTpLinkSmart, line 447 if (resp.status == 200) { // library marker davegut.appTpLinkSmart, line 448 try { // library marker davegut.appTpLinkSmart, line 449 Map cmdResp = new JsonSlurper().parseText(aesDecrypt(resp.json.result.response, // library marker davegut.appTpLinkSmart, line 450 data.data.encKey, data.data.encIv)) // library marker davegut.appTpLinkSmart, line 451 logData << [status: "OK", cmdResp: cmdResp] // library marker davegut.appTpLinkSmart, line 452 if (cmdResp.error_code == 0) { // library marker davegut.appTpLinkSmart, line 453 addToDevices(data.data.devData, cmdResp.result.responses) // library marker davegut.appTpLinkSmart, line 454 logDebug(logData) // library marker davegut.appTpLinkSmart, line 455 } else { // library marker davegut.appTpLinkSmart, line 456 logData << [status: "errorInCmdResp"] // library marker davegut.appTpLinkSmart, line 457 logWarn(logData) // library marker davegut.appTpLinkSmart, line 458 } // library marker davegut.appTpLinkSmart, line 459 } catch (err) { // library marker davegut.appTpLinkSmart, line 460 logData << [status: "deviceDataParseError", error: err, dataLength: resp.data.length()] // library marker davegut.appTpLinkSmart, line 461 logWarn(logData) // library marker davegut.appTpLinkSmart, line 462 } // library marker davegut.appTpLinkSmart, line 463 } else { // library marker davegut.appTpLinkSmart, line 464 logData << [status: "httpFailure", data: resp.properties] // library marker davegut.appTpLinkSmart, line 465 logWarn(logData) // library marker davegut.appTpLinkSmart, line 466 } // library marker davegut.appTpLinkSmart, line 467 } // library marker davegut.appTpLinkSmart, line 468 // ===== get Smart vacAes Protocol Data ===== // library marker davegut.appTpLinkSmart, line 470 def sendVacAesDataCmd(token, data) { // library marker davegut.appTpLinkSmart, line 471 Map devData = data.data.devData // library marker davegut.appTpLinkSmart, line 472 Map reqParams = getVacAesParams(getDataCmd(), "${data.data.baseUrl}/?token=${token}") // library marker davegut.appTpLinkSmart, line 473 asynchttpPost("parseVacAesResp", reqParams, [data: devData]) // library marker davegut.appTpLinkSmart, line 474 } // library marker davegut.appTpLinkSmart, line 475 def parseVacAesResp(resp, devData) { // library marker davegut.appTpLinkSmart, line 477 Map logData = [parseMethod: "parseVacAesResp"] // library marker davegut.appTpLinkSmart, line 478 try { // library marker davegut.appTpLinkSmart, line 479 Map cmdResp = resp.json // library marker davegut.appTpLinkSmart, line 480 logData << [status: "OK", cmdResp: cmdResp] // library marker davegut.appTpLinkSmart, line 481 if (cmdResp.error_code == 0) { // library marker davegut.appTpLinkSmart, line 482 addToDevices(devData.data, cmdResp.result.responses) // library marker davegut.appTpLinkSmart, line 483 logDebug(logData) // library marker davegut.appTpLinkSmart, line 484 } else { // library marker davegut.appTpLinkSmart, line 485 logData << [status: "errorInCmdResp"] // library marker davegut.appTpLinkSmart, line 486 logWarn(logData) // library marker davegut.appTpLinkSmart, line 487 } // library marker davegut.appTpLinkSmart, line 488 } catch (err) { // library marker davegut.appTpLinkSmart, line 489 logData << [status: "deviceDataParseError", error: err, dataLength: resp.data.length()] // library marker davegut.appTpLinkSmart, line 490 logWarn(logData) // library marker davegut.appTpLinkSmart, line 491 } // library marker davegut.appTpLinkSmart, line 492 return parseData // library marker davegut.appTpLinkSmart, line 493 } // library marker davegut.appTpLinkSmart, line 494 // ===== Support device data update request ===== // library marker davegut.appTpLinkSmart, line 496 def tpLinkCheckForDevices(timeout = 5) { // library marker davegut.appTpLinkSmart, line 497 Map logData = [method: "tpLinkCheckForDevices"] // library marker davegut.appTpLinkSmart, line 498 def checked = true // library marker davegut.appTpLinkSmart, line 499 if (state.tpLinkChecked == true) { // library marker davegut.appTpLinkSmart, line 500 checked = false // library marker davegut.appTpLinkSmart, line 501 logData << [status: "noCheck", reason: "Completed within last 10 minutes"] // library marker davegut.appTpLinkSmart, line 502 } else { // library marker davegut.appTpLinkSmart, line 503 def findData = findTpLinkDevices("parseTpLinkCheck", timeout) // library marker davegut.appTpLinkSmart, line 504 logData << [status: "checking"] // library marker davegut.appTpLinkSmart, line 505 pauseExecution((timeout+2)*1000) // library marker davegut.appTpLinkSmart, line 506 } // library marker davegut.appTpLinkSmart, line 507 logDebug(logData) // library marker davegut.appTpLinkSmart, line 508 return checked // library marker davegut.appTpLinkSmart, line 509 } // library marker davegut.appTpLinkSmart, line 510 def resetTpLinkChecked() { state.tpLinkChecked = false } // library marker davegut.appTpLinkSmart, line 512 def parseTpLinkCheck(response) { // library marker davegut.appTpLinkSmart, line 514 List discData = [] // library marker davegut.appTpLinkSmart, line 515 if (response instanceof Map) { // library marker davegut.appTpLinkSmart, line 516 Map devdata = getDiscData(response) // library marker davegut.appTpLinkSmart, line 517 if (devData.status != "INVALID") { // library marker davegut.appTpLinkSmart, line 518 discData << devData // library marker davegut.appTpLinkSmart, line 519 } // library marker davegut.appTpLinkSmart, line 520 } else { // library marker davegut.appTpLinkSmart, line 521 response.each { // library marker davegut.appTpLinkSmart, line 522 Map devData = getDiscData(it) // library marker davegut.appTpLinkSmart, line 523 if (devData.status == "OK") { // library marker davegut.appTpLinkSmart, line 524 discData << devData // library marker davegut.appTpLinkSmart, line 525 } // library marker davegut.appTpLinkSmart, line 526 } // library marker davegut.appTpLinkSmart, line 527 } // library marker davegut.appTpLinkSmart, line 528 updateTpLinkDevices(discData) // library marker davegut.appTpLinkSmart, line 529 } // library marker davegut.appTpLinkSmart, line 530 def updateTpLinkDevices(discData) { // library marker davegut.appTpLinkSmart, line 532 Map logData = [method: "updateTpLinkDevices"] // library marker davegut.appTpLinkSmart, line 533 state.tpLinkChecked = true // library marker davegut.appTpLinkSmart, line 534 runIn(570, resetTpLinkChecked) // library marker davegut.appTpLinkSmart, line 535 List children = getChildDevices() // library marker davegut.appTpLinkSmart, line 536 children.each { childDev -> // library marker davegut.appTpLinkSmart, line 537 Map childData = [:] // library marker davegut.appTpLinkSmart, line 538 def dni = childDev.deviceNetworkId // library marker davegut.appTpLinkSmart, line 539 def connected = "false" // library marker davegut.appTpLinkSmart, line 540 Map devData = discData.find{ it.dni == dni } // library marker davegut.appTpLinkSmart, line 541 if (childDev.getDataValue("baseUrl")) { // library marker davegut.appTpLinkSmart, line 542 if (devData != null) { // library marker davegut.appTpLinkSmart, line 543 if (childDev.getDataValue("baseUrl") == devData.baseUrl && // library marker davegut.appTpLinkSmart, line 544 childDev.getDataValue("protocol") == devData.protocol) { // library marker davegut.appTpLinkSmart, line 545 childData << [status: "noChanges"] // library marker davegut.appTpLinkSmart, line 546 } else { // library marker davegut.appTpLinkSmart, line 547 childDev.updateDataValue("baseUrl", devData.baseUrl) // library marker davegut.appTpLinkSmart, line 548 childDev.updateDataValue("protocol", devData.protocol) // library marker davegut.appTpLinkSmart, line 549 childData << ["baseUrl": devData.baseUrl, // library marker davegut.appTpLinkSmart, line 550 "protocol": devData.protocol, // library marker davegut.appTpLinkSmart, line 551 "connected": "true"] // library marker davegut.appTpLinkSmart, line 552 } // library marker davegut.appTpLinkSmart, line 553 } // library marker davegut.appTpLinkSmart, line 554 pauseExecution(500) // library marker davegut.appTpLinkSmart, line 555 } // library marker davegut.appTpLinkSmart, line 556 logData << ["${childDev}": childData] // library marker davegut.appTpLinkSmart, line 557 } // library marker davegut.appTpLinkSmart, line 558 logDebug(logData) // library marker davegut.appTpLinkSmart, line 559 } // library marker davegut.appTpLinkSmart, line 560 // ~~~~~ end include (375) davegut.appTpLinkSmart ~~~~~ // ~~~~~ start include (385) davegut.tpLinkComms ~~~~~ library ( // library marker davegut.tpLinkComms, line 1 name: "tpLinkComms", // library marker davegut.tpLinkComms, line 2 namespace: "davegut", // library marker davegut.tpLinkComms, line 3 author: "Compiled by Dave Gutheinz", // library marker davegut.tpLinkComms, line 4 description: "Communication methods for TP-Link Integration", // library marker davegut.tpLinkComms, line 5 category: "utilities", // library marker davegut.tpLinkComms, line 6 documentationLink: "" // library marker davegut.tpLinkComms, line 7 ) // library marker davegut.tpLinkComms, line 8 import org.json.JSONObject // library marker davegut.tpLinkComms, line 9 import groovy.json.JsonOutput // library marker davegut.tpLinkComms, line 10 import groovy.json.JsonBuilder // library marker davegut.tpLinkComms, line 11 import groovy.json.JsonSlurper // library marker davegut.tpLinkComms, line 12 // ===== Communications Methods ===== // library marker davegut.tpLinkComms, line 14 def asyncSend(cmdBody, reqData, action) { // library marker davegut.tpLinkComms, line 15 Map cmdData = [cmdBody: cmdBody, reqData: reqData, action: action] // library marker davegut.tpLinkComms, line 16 def protocol = getDataValue("protocol") // library marker davegut.tpLinkComms, line 17 Map reqParams = [:] // library marker davegut.tpLinkComms, line 18 if (protocol == "KLAP") { // library marker davegut.tpLinkComms, line 19 reqParams = getKlapParams(cmdBody) // library marker davegut.tpLinkComms, line 20 } else if (protocol == "camera") { // library marker davegut.tpLinkComms, line 21 reqParams = getCameraParams(cmdBody, reqData) // library marker davegut.tpLinkComms, line 22 } else if (protocol == "AES") { // library marker davegut.tpLinkComms, line 23 reqParams = getAesParams(cmdBody) // library marker davegut.tpLinkComms, line 24 } else if (protocol == "vacAes") { // library marker davegut.tpLinkComms, line 25 reqParams = getVacAesParams(cmdBody, "${getDataValue("baseUrl")}/?token=${token}") // library marker davegut.tpLinkComms, line 26 } // library marker davegut.tpLinkComms, line 27 if (reqParams != [:]) { // library marker davegut.tpLinkComms, line 28 if (state.errorCount == 0) { state.lastCommand = cmdData } // library marker davegut.tpLinkComms, line 29 asynchttpPost(action, reqParams, [data: reqData]) // library marker davegut.tpLinkComms, line 30 logDebug([method: "asyncSend", reqData: reqData]) // library marker davegut.tpLinkComms, line 31 } else { // library marker davegut.tpLinkComms, line 32 unknownProt(reqData) // library marker davegut.tpLinkComms, line 33 } // library marker davegut.tpLinkComms, line 34 } // library marker davegut.tpLinkComms, line 35 def unknownProt(reqData) { // library marker davegut.tpLinkComms, line 37 Map warnData = ["UnknownProtocol": [data: reqData, // library marker davegut.tpLinkComms, line 38 msg: "Device will not install or if installed will not work"]] // library marker davegut.tpLinkComms, line 39 logWarn(warnData) // library marker davegut.tpLinkComms, line 40 } // library marker davegut.tpLinkComms, line 41 def parseData(resp, protocol = getDataValue("protocol"), data = null) { // library marker davegut.tpLinkComms, line 43 Map logData = [method: "parseData", status: resp.status, protocol: protocol, // library marker davegut.tpLinkComms, line 44 sourceMethod: data.data] // library marker davegut.tpLinkComms, line 45 def message = "OK" // library marker davegut.tpLinkComms, line 46 if (resp.status == 200) { // library marker davegut.tpLinkComms, line 47 if (protocol == "KLAP") { // library marker davegut.tpLinkComms, line 48 logData << parseKlapData(resp, data) // library marker davegut.tpLinkComms, line 49 } else if (protocol == "AES") { // library marker davegut.tpLinkComms, line 50 logData << parseAesData(resp, data) // library marker davegut.tpLinkComms, line 51 } else if (protocol == "vacAes") { // library marker davegut.tpLinkComms, line 52 logData << parseVacAesData(resp, data) // library marker davegut.tpLinkComms, line 53 } else if (protocol == "camera") { // library marker davegut.tpLinkComms, line 54 logData << parseCameraData(resp, data) // library marker davegut.tpLinkComms, line 55 } // library marker davegut.tpLinkComms, line 56 } else { // library marker davegut.tpLinkComms, line 57 message = resp.errorMessage // library marker davegut.tpLinkComms, line 58 String userMessage = "unspecified" // library marker davegut.tpLinkComms, line 59 if (resp.status == 403) { // library marker davegut.tpLinkComms, line 60 userMessage = "Try again. If error persists, check your credentials" // library marker davegut.tpLinkComms, line 61 } else if (resp.status == 408) { // library marker davegut.tpLinkComms, line 62 userMessage = "Your router connection to ${getDataValue("baseUrl")} failed. Run Configure." // library marker davegut.tpLinkComms, line 63 } else { // library marker davegut.tpLinkComms, line 64 userMessage = "Unhandled error Lan return" // library marker davegut.tpLinkComms, line 65 } // library marker davegut.tpLinkComms, line 66 logData << [respMessage: message, userMessage: userMessage] // library marker davegut.tpLinkComms, line 67 logDebug(logData) // library marker davegut.tpLinkComms, line 68 } // library marker davegut.tpLinkComms, line 69 handleCommsError(resp.status, message) // library marker davegut.tpLinkComms, line 70 return logData // library marker davegut.tpLinkComms, line 71 } // library marker davegut.tpLinkComms, line 72 private sendFindCmd(ip, port, cmdData, action, commsTo = 5, ignore = false) { // library marker davegut.tpLinkComms, line 74 def myHubAction = new hubitat.device.HubAction( // library marker davegut.tpLinkComms, line 75 cmdData, // library marker davegut.tpLinkComms, line 76 hubitat.device.Protocol.LAN, // library marker davegut.tpLinkComms, line 77 [type: hubitat.device.HubAction.Type.LAN_TYPE_UDPCLIENT, // library marker davegut.tpLinkComms, line 78 destinationAddress: "${ip}:${port}", // library marker davegut.tpLinkComms, line 79 encoding: hubitat.device.HubAction.Encoding.HEX_STRING, // library marker davegut.tpLinkComms, line 80 ignoreResponse: ignore, // library marker davegut.tpLinkComms, line 81 parseWarning: true, // library marker davegut.tpLinkComms, line 82 timeout: commsTo, // library marker davegut.tpLinkComms, line 83 callback: action]) // library marker davegut.tpLinkComms, line 84 try { // library marker davegut.tpLinkComms, line 85 sendHubCommand(myHubAction) // library marker davegut.tpLinkComms, line 86 } catch (error) { // library marker davegut.tpLinkComms, line 87 logWarn("sendLanCmd: command to ${ip}:${port} failed. Error = ${error}") // library marker davegut.tpLinkComms, line 88 } // library marker davegut.tpLinkComms, line 89 return // library marker davegut.tpLinkComms, line 90 } // library marker davegut.tpLinkComms, line 91 // Unknown Protocol method // library marker davegut.tpLinkComms, line 93 // ===== Communications Error Handling ===== // library marker davegut.tpLinkComms, line 94 def handleCommsError(status, msg = "") { // library marker davegut.tpLinkComms, line 95 // Retransmit all comms error except Switch and Level related (Hub retries for these). // library marker davegut.tpLinkComms, line 96 // This is determined by state.digital // library marker davegut.tpLinkComms, line 97 if (status == 200) { // library marker davegut.tpLinkComms, line 98 setCommsError(status, "OK") // library marker davegut.tpLinkComms, line 99 } else { // library marker davegut.tpLinkComms, line 100 Map logData = [method: "handleCommsError", status: code, msg: msg] // library marker davegut.tpLinkComms, line 101 def count = state.errorCount + 1 // library marker davegut.tpLinkComms, line 102 logData << [count: count, status: status, msg: msg] // library marker davegut.tpLinkComms, line 103 switch(count) { // library marker davegut.tpLinkComms, line 104 case 1: // library marker davegut.tpLinkComms, line 105 case 2: // library marker davegut.tpLinkComms, line 106 // errors 1 and 2, retry immediately // library marker davegut.tpLinkComms, line 107 runIn(1, delayedPassThrough) // library marker davegut.tpLinkComms, line 108 break // library marker davegut.tpLinkComms, line 109 case 3: // library marker davegut.tpLinkComms, line 110 // error 3, login or scan find device on the lan // library marker davegut.tpLinkComms, line 111 // then retry // library marker davegut.tpLinkComms, line 112 if (status == 403) { // library marker davegut.tpLinkComms, line 113 logData << [action: "attemptLogin"] // library marker davegut.tpLinkComms, line 114 // await device handshake result???? // library marker davegut.tpLinkComms, line 115 deviceHandshake() // library marker davegut.tpLinkComms, line 116 runIn(4, delayedPassThrough) // library marker davegut.tpLinkComms, line 117 } else { // library marker davegut.tpLinkComms, line 118 logData << [action: "Find on LAN then login"] // library marker davegut.tpLinkComms, line 119 configure() // library marker davegut.tpLinkComms, line 120 // await configure result????? // library marker davegut.tpLinkComms, line 121 runIn(10, delayedPassThrough) // library marker davegut.tpLinkComms, line 122 } // library marker davegut.tpLinkComms, line 123 break // library marker davegut.tpLinkComms, line 124 case 4: // library marker davegut.tpLinkComms, line 125 runIn(1, delayedPassThrough) // library marker davegut.tpLinkComms, line 126 break // library marker davegut.tpLinkComms, line 127 default: // library marker davegut.tpLinkComms, line 128 // Set comms error first time errros are 5 or more. // library marker davegut.tpLinkComms, line 129 logData << [action: "SetCommsErrorTrue"] // library marker davegut.tpLinkComms, line 130 setCommsError(status, msg, 5) // library marker davegut.tpLinkComms, line 131 } // library marker davegut.tpLinkComms, line 132 state.errorCount = count // library marker davegut.tpLinkComms, line 133 logInfo(logData) // library marker davegut.tpLinkComms, line 134 } // library marker davegut.tpLinkComms, line 135 } // library marker davegut.tpLinkComms, line 136 def delayedPassThrough() { // library marker davegut.tpLinkComms, line 138 def cmdData = new JSONObject(state.lastCommand) // library marker davegut.tpLinkComms, line 139 def cmdBody = parseJson(cmdData.cmdBody.toString()) // library marker davegut.tpLinkComms, line 140 asyncSend(cmdBody, cmdData.reqData, cmdData.action) // library marker davegut.tpLinkComms, line 141 } // library marker davegut.tpLinkComms, line 142 def setCommsError(status, msg = "OK", count = state.commsError) { // library marker davegut.tpLinkComms, line 144 Map logData = [method: "setCommsError", status: status, errorMsg: msg, count: count] // library marker davegut.tpLinkComms, line 145 if (device && status == 200) { // library marker davegut.tpLinkComms, line 146 state.errorCount = 0 // library marker davegut.tpLinkComms, line 147 if (device.currentValue("commsError") == "true") { // library marker davegut.tpLinkComms, line 148 sendEvent(name: "commsError", value: "false") // library marker davegut.tpLinkComms, line 149 setPollInterval() // library marker davegut.tpLinkComms, line 150 unschedule("errorConfigure") // library marker davegut.tpLinkComms, line 151 logInfo(logData) // library marker davegut.tpLinkComms, line 152 } // library marker davegut.tpLinkComms, line 153 } else if (device) { // library marker davegut.tpLinkComms, line 154 if (device.currentValue("commsError") == "false" && count > 4) { // library marker davegut.tpLinkComms, line 155 updateAttr("commsError", "true") // library marker davegut.tpLinkComms, line 156 setPollInterval("30 min") // library marker davegut.tpLinkComms, line 157 runEvery10Minutes(errorConfigure) // library marker davegut.tpLinkComms, line 158 logData << [pollInterval: "30 Min", errorConfigure: "ever 10 min"] // library marker davegut.tpLinkComms, line 159 logWarn(logData) // library marker davegut.tpLinkComms, line 160 if (status == 403) { // library marker davegut.tpLinkComms, line 161 logWarn(logInErrorAction()) // library marker davegut.tpLinkComms, line 162 } else { // library marker davegut.tpLinkComms, line 163 logWarn(lanErrorAction()) // library marker davegut.tpLinkComms, line 164 } // library marker davegut.tpLinkComms, line 165 } else { // library marker davegut.tpLinkComms, line 166 logData << [error: "Unspecified Error"] // library marker davegut.tpLinkComms, line 167 logWarn(logData) // library marker davegut.tpLinkComms, line 168 } // library marker davegut.tpLinkComms, line 169 } // library marker davegut.tpLinkComms, line 170 } // library marker davegut.tpLinkComms, line 171 def errorConfigure() { // library marker davegut.tpLinkComms, line 173 logDebug([method: "errorConfigure"]) // library marker davegut.tpLinkComms, line 174 if (device.currentValue("commsError") == "true") { // library marker davegut.tpLinkComms, line 175 configure() // library marker davegut.tpLinkComms, line 176 } else { // library marker davegut.tpLinkComms, line 177 unschedule("errorConfigure") // library marker davegut.tpLinkComms, line 178 } // library marker davegut.tpLinkComms, line 179 } // library marker davegut.tpLinkComms, line 180 def lanErrorAction() { // library marker davegut.tpLinkComms, line 182 def action = "Likely cause of this error is YOUR LAN device configuration: " // library marker davegut.tpLinkComms, line 183 action += "a. VERIFY your device is on the DHCP list in your router, " // library marker davegut.tpLinkComms, line 184 action += "b. VERIFY your device is in the active device list in your router, and " // library marker davegut.tpLinkComms, line 185 action += "c. TRY controlling your device from the TAPO phone app." // library marker davegut.tpLinkComms, line 186 return action // library marker davegut.tpLinkComms, line 187 } // library marker davegut.tpLinkComms, line 188 def logInErrorAction() { // library marker davegut.tpLinkComms, line 190 def action = "Likely cause is your login credentials are incorrect or the login has expired. " // library marker davegut.tpLinkComms, line 191 action += "a. RUN command Configure. b. If error persists, check your credentials in the App" // library marker davegut.tpLinkComms, line 192 return action // library marker davegut.tpLinkComms, line 193 } // library marker davegut.tpLinkComms, line 194 // ~~~~~ end include (385) davegut.tpLinkComms ~~~~~ // ~~~~~ start include (386) davegut.tpLinkCrypto ~~~~~ library ( // library marker davegut.tpLinkCrypto, line 1 name: "tpLinkCrypto", // library marker davegut.tpLinkCrypto, line 2 namespace: "davegut", // library marker davegut.tpLinkCrypto, line 3 author: "Compiled by Dave Gutheinz", // library marker davegut.tpLinkCrypto, line 4 description: "Handshake methods for TP-Link Integration", // library marker davegut.tpLinkCrypto, line 5 category: "utilities", // library marker davegut.tpLinkCrypto, line 6 documentationLink: "" // library marker davegut.tpLinkCrypto, line 7 ) // library marker davegut.tpLinkCrypto, line 8 import java.security.spec.PKCS8EncodedKeySpec // library marker davegut.tpLinkCrypto, line 9 import javax.crypto.Cipher // library marker davegut.tpLinkCrypto, line 10 import java.security.KeyFactory // library marker davegut.tpLinkCrypto, line 11 import java.util.Random // library marker davegut.tpLinkCrypto, line 12 import javax.crypto.spec.SecretKeySpec // library marker davegut.tpLinkCrypto, line 13 import javax.crypto.spec.IvParameterSpec // library marker davegut.tpLinkCrypto, line 14 import java.security.MessageDigest // library marker davegut.tpLinkCrypto, line 15 // ===== Crypto Methods ===== // library marker davegut.tpLinkCrypto, line 17 def klapEncrypt(byte[] request, encKey, encIv, encSig, seqNo) { // library marker davegut.tpLinkCrypto, line 18 byte[] encSeqNo = integerToByteArray(seqNo) // library marker davegut.tpLinkCrypto, line 19 byte[] ivEnc = [encIv, encSeqNo].flatten() // library marker davegut.tpLinkCrypto, line 20 def cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") // library marker davegut.tpLinkCrypto, line 21 SecretKeySpec key = new SecretKeySpec(encKey, "AES") // library marker davegut.tpLinkCrypto, line 22 IvParameterSpec iv = new IvParameterSpec(ivEnc) // library marker davegut.tpLinkCrypto, line 23 cipher.init(Cipher.ENCRYPT_MODE, key, iv) // library marker davegut.tpLinkCrypto, line 24 byte[] cipherRequest = cipher.doFinal(request) // library marker davegut.tpLinkCrypto, line 25 byte[] payload = [encSig, encSeqNo, cipherRequest].flatten() // library marker davegut.tpLinkCrypto, line 27 byte[] signature = mdEncode("SHA-256", payload) // library marker davegut.tpLinkCrypto, line 28 cipherRequest = [signature, cipherRequest].flatten() // library marker davegut.tpLinkCrypto, line 29 return [cipherData: cipherRequest, seqNumber: seqNo] // library marker davegut.tpLinkCrypto, line 30 } // library marker davegut.tpLinkCrypto, line 31 def klapDecrypt(cipherResponse, encKey, encIv, seqNo) { // library marker davegut.tpLinkCrypto, line 33 byte[] encSeqNo = integerToByteArray(seqNo) // library marker davegut.tpLinkCrypto, line 34 byte[] ivEnc = [encIv, encSeqNo].flatten() // library marker davegut.tpLinkCrypto, line 35 def cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") // library marker davegut.tpLinkCrypto, line 36 SecretKeySpec key = new SecretKeySpec(encKey, "AES") // library marker davegut.tpLinkCrypto, line 37 IvParameterSpec iv = new IvParameterSpec(ivEnc) // library marker davegut.tpLinkCrypto, line 38 cipher.init(Cipher.DECRYPT_MODE, key, iv) // library marker davegut.tpLinkCrypto, line 39 byte[] byteResponse = cipher.doFinal(cipherResponse) // library marker davegut.tpLinkCrypto, line 40 return new String(byteResponse, "UTF-8") // library marker davegut.tpLinkCrypto, line 41 } // library marker davegut.tpLinkCrypto, line 42 def aesEncrypt(request, encKey, encIv) { // library marker davegut.tpLinkCrypto, line 44 def cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") // library marker davegut.tpLinkCrypto, line 45 SecretKeySpec key = new SecretKeySpec(encKey, "AES") // library marker davegut.tpLinkCrypto, line 46 IvParameterSpec iv = new IvParameterSpec(encIv) // library marker davegut.tpLinkCrypto, line 47 cipher.init(Cipher.ENCRYPT_MODE, key, iv) // library marker davegut.tpLinkCrypto, line 48 String result = cipher.doFinal(request.getBytes("UTF-8")).encodeBase64().toString() // library marker davegut.tpLinkCrypto, line 49 return result.replace("\r\n","") // library marker davegut.tpLinkCrypto, line 50 } // library marker davegut.tpLinkCrypto, line 51 def aesDecrypt(cipherResponse, encKey, encIv) { // library marker davegut.tpLinkCrypto, line 53 byte[] decodedBytes = cipherResponse.decodeBase64() // library marker davegut.tpLinkCrypto, line 54 def cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") // library marker davegut.tpLinkCrypto, line 55 SecretKeySpec key = new SecretKeySpec(encKey, "AES") // library marker davegut.tpLinkCrypto, line 56 IvParameterSpec iv = new IvParameterSpec(encIv) // library marker davegut.tpLinkCrypto, line 57 cipher.init(Cipher.DECRYPT_MODE, key, iv) // library marker davegut.tpLinkCrypto, line 58 return new String(cipher.doFinal(decodedBytes), "UTF-8") // library marker davegut.tpLinkCrypto, line 59 } // library marker davegut.tpLinkCrypto, line 60 // ===== Encoding Methods ===== // library marker davegut.tpLinkCrypto, line 62 def mdEncode(hashMethod, byte[] data) { // library marker davegut.tpLinkCrypto, line 63 MessageDigest md = MessageDigest.getInstance(hashMethod) // library marker davegut.tpLinkCrypto, line 64 md.update(data) // library marker davegut.tpLinkCrypto, line 65 return md.digest() // library marker davegut.tpLinkCrypto, line 66 } // library marker davegut.tpLinkCrypto, line 67 String encodeUtf8(String message) { // library marker davegut.tpLinkCrypto, line 69 byte[] arr = message.getBytes("UTF8") // library marker davegut.tpLinkCrypto, line 70 return new String(arr) // library marker davegut.tpLinkCrypto, line 71 } // library marker davegut.tpLinkCrypto, line 72 int byteArrayToInteger(byte[] byteArr) { // library marker davegut.tpLinkCrypto, line 74 int arrayASInteger // library marker davegut.tpLinkCrypto, line 75 try { // library marker davegut.tpLinkCrypto, line 76 arrayAsInteger = ((byteArr[0] & 0xFF) << 24) + ((byteArr[1] & 0xFF) << 16) + // library marker davegut.tpLinkCrypto, line 77 ((byteArr[2] & 0xFF) << 8) + (byteArr[3] & 0xFF) // library marker davegut.tpLinkCrypto, line 78 } catch (error) { // library marker davegut.tpLinkCrypto, line 79 Map errLog = [byteArr: byteArr, ERROR: error] // library marker davegut.tpLinkCrypto, line 80 logWarn("byteArrayToInteger: ${errLog}") // library marker davegut.tpLinkCrypto, line 81 } // library marker davegut.tpLinkCrypto, line 82 return arrayAsInteger // library marker davegut.tpLinkCrypto, line 83 } // library marker davegut.tpLinkCrypto, line 84 byte[] integerToByteArray(value) { // library marker davegut.tpLinkCrypto, line 86 String hexValue = hubitat.helper.HexUtils.integerToHexString(value, 4) // library marker davegut.tpLinkCrypto, line 87 byte[] byteValue = hubitat.helper.HexUtils.hexStringToByteArray(hexValue) // library marker davegut.tpLinkCrypto, line 88 return byteValue // library marker davegut.tpLinkCrypto, line 89 } // library marker davegut.tpLinkCrypto, line 90 def getSeed(size) { // library marker davegut.tpLinkCrypto, line 92 byte[] temp = new byte[size] // library marker davegut.tpLinkCrypto, line 93 new Random().nextBytes(temp) // library marker davegut.tpLinkCrypto, line 94 return temp // library marker davegut.tpLinkCrypto, line 95 } // library marker davegut.tpLinkCrypto, line 96 // ~~~~~ end include (386) davegut.tpLinkCrypto ~~~~~ // ~~~~~ start include (388) davegut.tpLinkTransAes ~~~~~ library ( // library marker davegut.tpLinkTransAes, line 1 name: "tpLinkTransAes", // library marker davegut.tpLinkTransAes, line 2 namespace: "davegut", // library marker davegut.tpLinkTransAes, line 3 author: "Compiled by Dave Gutheinz", // library marker davegut.tpLinkTransAes, line 4 description: "Handshake methods for TP-Link Integration", // library marker davegut.tpLinkTransAes, line 5 category: "utilities", // library marker davegut.tpLinkTransAes, line 6 documentationLink: "" // library marker davegut.tpLinkTransAes, line 7 ) // library marker davegut.tpLinkTransAes, line 8 def aesHandshake(baseUrl = getDataValue("baseUrl"), devData = null) { // library marker davegut.tpLinkTransAes, line 10 Map reqData = [baseUrl: baseUrl, devData: devData] // library marker davegut.tpLinkTransAes, line 11 Map rsaKey = getRsaKey() // library marker davegut.tpLinkTransAes, line 12 def pubPem = "-----BEGIN PUBLIC KEY-----\n${rsaKey.public}-----END PUBLIC KEY-----\n" // library marker davegut.tpLinkTransAes, line 13 Map cmdBody = [ method: "handshake", params: [ key: pubPem]] // library marker davegut.tpLinkTransAes, line 14 Map reqParams = [uri: baseUrl, // library marker davegut.tpLinkTransAes, line 15 body: new groovy.json.JsonBuilder(cmdBody).toString(), // library marker davegut.tpLinkTransAes, line 16 requestContentType: "application/json", // library marker davegut.tpLinkTransAes, line 17 timeout: 10] // library marker davegut.tpLinkTransAes, line 18 asynchttpPost("parseAesHandshake", reqParams, [data: reqData]) // library marker davegut.tpLinkTransAes, line 19 } // library marker davegut.tpLinkTransAes, line 20 def parseAesHandshake(resp, data){ // library marker davegut.tpLinkTransAes, line 22 Map logData = [method: "parseAesHandshake"] // library marker davegut.tpLinkTransAes, line 23 if (resp.status == 200 && resp.data != null) { // library marker davegut.tpLinkTransAes, line 24 try { // library marker davegut.tpLinkTransAes, line 25 Map reqData = [devData: data.data.devData, baseUrl: data.data.baseUrl] // library marker davegut.tpLinkTransAes, line 26 Map cmdResp = new JsonSlurper().parseText(resp.data) // library marker davegut.tpLinkTransAes, line 27 // cookie // library marker davegut.tpLinkTransAes, line 28 def cookieHeader = resp.headers["Set-Cookie"].toString() // library marker davegut.tpLinkTransAes, line 29 def cookie = cookieHeader.substring(cookieHeader.indexOf(":") +1, cookieHeader.indexOf(";")) // library marker davegut.tpLinkTransAes, line 30 // keys // library marker davegut.tpLinkTransAes, line 31 byte[] privateKeyBytes = getRsaKey().private.decodeBase64() // library marker davegut.tpLinkTransAes, line 32 byte[] deviceKeyBytes = cmdResp.result.key.getBytes("UTF-8").decodeBase64() // library marker davegut.tpLinkTransAes, line 33 Cipher instance = Cipher.getInstance("RSA/ECB/PKCS1Padding") // library marker davegut.tpLinkTransAes, line 34 instance.init(2, KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes))) // library marker davegut.tpLinkTransAes, line 35 byte[] cryptoArray = instance.doFinal(deviceKeyBytes) // library marker davegut.tpLinkTransAes, line 36 byte[] encKey = cryptoArray[0..15] // library marker davegut.tpLinkTransAes, line 37 byte[] encIv = cryptoArray[16..31] // library marker davegut.tpLinkTransAes, line 38 logData << [respStatus: "Cookies/Keys Updated", cookie: cookie, // library marker davegut.tpLinkTransAes, line 39 encKey: encKey, encIv: encIv] // library marker davegut.tpLinkTransAes, line 40 String password = encPassword // library marker davegut.tpLinkTransAes, line 41 String username = encUsername // library marker davegut.tpLinkTransAes, line 42 if (device) { // library marker davegut.tpLinkTransAes, line 43 password = parent.encPassword // library marker davegut.tpLinkTransAes, line 44 username = parent.encUsername // library marker davegut.tpLinkTransAes, line 45 device.updateSetting("cookie",[type:"password", value: cookie]) // library marker davegut.tpLinkTransAes, line 46 device.updateSetting("encKey",[type:"password", value: encKey]) // library marker davegut.tpLinkTransAes, line 47 device.updateSetting("encIv",[type:"password", value: encIv]) // library marker davegut.tpLinkTransAes, line 48 } else { // library marker davegut.tpLinkTransAes, line 49 reqData << [cookie: cookie, encIv: encIv, encKey: encKey] // library marker davegut.tpLinkTransAes, line 50 } // library marker davegut.tpLinkTransAes, line 51 Map cmdBody = [method: "login_device", // library marker davegut.tpLinkTransAes, line 52 params: [password: password, // library marker davegut.tpLinkTransAes, line 53 username: username], // library marker davegut.tpLinkTransAes, line 54 requestTimeMils: 0] // library marker davegut.tpLinkTransAes, line 55 def cmdStr = JsonOutput.toJson(cmdBody).toString() // library marker davegut.tpLinkTransAes, line 56 Map reqBody = [method: "securePassthrough", // library marker davegut.tpLinkTransAes, line 57 params: [request: aesEncrypt(cmdStr, encKey, encIv)]] // library marker davegut.tpLinkTransAes, line 58 Map reqParams = [uri: reqData.baseUrl, // library marker davegut.tpLinkTransAes, line 59 body: reqBody, // library marker davegut.tpLinkTransAes, line 60 timeout:10, // library marker davegut.tpLinkTransAes, line 61 headers: ["Cookie": cookie], // library marker davegut.tpLinkTransAes, line 62 contentType: "application/json", // library marker davegut.tpLinkTransAes, line 63 requestContentType: "application/json"] // library marker davegut.tpLinkTransAes, line 64 asynchttpPost("parseAesLogin", reqParams, [data: reqData]) // library marker davegut.tpLinkTransAes, line 65 logDebug(logData) // library marker davegut.tpLinkTransAes, line 66 } catch (err) { // library marker davegut.tpLinkTransAes, line 67 logData << [respStatus: "ERROR parsing HTTP resp.data", // library marker davegut.tpLinkTransAes, line 68 respData: resp.data, error: err] // library marker davegut.tpLinkTransAes, line 69 logWarn(logData) // library marker davegut.tpLinkTransAes, line 70 } // library marker davegut.tpLinkTransAes, line 71 } else { // library marker davegut.tpLinkTransAes, line 72 logData << [respStatus: "ERROR in HTTP response", resp: resp.properties] // library marker davegut.tpLinkTransAes, line 73 logWarn(logData) // library marker davegut.tpLinkTransAes, line 74 } // library marker davegut.tpLinkTransAes, line 75 } // library marker davegut.tpLinkTransAes, line 76 def parseAesLogin(resp, data) { // library marker davegut.tpLinkTransAes, line 78 if (device) { // library marker davegut.tpLinkTransAes, line 79 Map logData = [method: "parseAesLogin"] // library marker davegut.tpLinkTransAes, line 80 if (resp.status == 200) { // library marker davegut.tpLinkTransAes, line 81 if (resp.json.error_code == 0) { // library marker davegut.tpLinkTransAes, line 82 try { // library marker davegut.tpLinkTransAes, line 83 byte[] encKey = new JsonSlurper().parseText(encKey) // library marker davegut.tpLinkTransAes, line 84 byte[] encIv = new JsonSlurper().parseText(encIv) // library marker davegut.tpLinkTransAes, line 85 def clearResp = aesDecrypt(resp.json.result.response, encKey, encIv) // library marker davegut.tpLinkTransAes, line 86 Map cmdResp = new JsonSlurper().parseText(clearResp) // library marker davegut.tpLinkTransAes, line 87 if (cmdResp.error_code == 0) { // library marker davegut.tpLinkTransAes, line 88 def token = cmdResp.result.token // library marker davegut.tpLinkTransAes, line 89 logData << [respStatus: "OK", token: token] // library marker davegut.tpLinkTransAes, line 90 device.updateSetting("token",[type:"password", value: token]) // library marker davegut.tpLinkTransAes, line 91 setCommsError(200) // library marker davegut.tpLinkTransAes, line 92 logDebug(logData) // library marker davegut.tpLinkTransAes, line 93 } else { // library marker davegut.tpLinkTransAes, line 94 logData << [respStatus: "ERROR code in cmdResp", // library marker davegut.tpLinkTransAes, line 95 error_code: cmdResp.error_code, // library marker davegut.tpLinkTransAes, line 96 check: "cryptoArray, credentials", data: cmdResp] // library marker davegut.tpLinkTransAes, line 97 logInfo(logData) // library marker davegut.tpLinkTransAes, line 98 } // library marker davegut.tpLinkTransAes, line 99 } catch (err) { // library marker davegut.tpLinkTransAes, line 100 logData << [respStatus: "ERROR parsing respJson", respJson: resp.json, // library marker davegut.tpLinkTransAes, line 101 error: err] // library marker davegut.tpLinkTransAes, line 102 logInfo(logData) // library marker davegut.tpLinkTransAes, line 103 } // library marker davegut.tpLinkTransAes, line 104 } else { // library marker davegut.tpLinkTransAes, line 105 logData << [respStatus: "ERROR code in resp.json", errorCode: resp.json.error_code, // library marker davegut.tpLinkTransAes, line 106 respJson: resp.json] // library marker davegut.tpLinkTransAes, line 107 logInfo(logData) // library marker davegut.tpLinkTransAes, line 108 } // library marker davegut.tpLinkTransAes, line 109 } else { // library marker davegut.tpLinkTransAes, line 110 logData << [respStatus: "ERROR in HTTP response", respStatus: resp.status, data: resp.properties] // library marker davegut.tpLinkTransAes, line 111 logInfo(logData) // library marker davegut.tpLinkTransAes, line 112 } // library marker davegut.tpLinkTransAes, line 113 } else { // library marker davegut.tpLinkTransAes, line 114 // Code used in application only. // library marker davegut.tpLinkTransAes, line 115 getAesToken(resp, data.data) // library marker davegut.tpLinkTransAes, line 116 } // library marker davegut.tpLinkTransAes, line 117 } // library marker davegut.tpLinkTransAes, line 118 def getAesParams(cmdBody) { // library marker davegut.tpLinkTransAes, line 120 byte[] encKey = new JsonSlurper().parseText(encKey) // library marker davegut.tpLinkTransAes, line 121 byte[] encIv = new JsonSlurper().parseText(encIv) // library marker davegut.tpLinkTransAes, line 122 def cmdStr = JsonOutput.toJson(cmdBody).toString() // library marker davegut.tpLinkTransAes, line 123 Map reqBody = [method: "securePassthrough", // library marker davegut.tpLinkTransAes, line 124 params: [request: aesEncrypt(cmdStr, encKey, encIv)]] // library marker davegut.tpLinkTransAes, line 125 Map reqParams = [uri: "${getDataValue("baseUrl")}?token=${token}", // library marker davegut.tpLinkTransAes, line 126 body: new groovy.json.JsonBuilder(reqBody).toString(), // library marker davegut.tpLinkTransAes, line 127 contentType: "application/json", // library marker davegut.tpLinkTransAes, line 128 requestContentType: "application/json", // library marker davegut.tpLinkTransAes, line 129 timeout: 10, // library marker davegut.tpLinkTransAes, line 130 ignoreSSLIssues: true, // library marker davegut.tpLinkTransAes, line 131 headers: ["Cookie": cookie]] // library marker davegut.tpLinkTransAes, line 132 return reqParams // library marker davegut.tpLinkTransAes, line 133 } // library marker davegut.tpLinkTransAes, line 134 def parseAesData(resp, data) { // library marker davegut.tpLinkTransAes, line 136 Map parseData = [parseMethod: "parseAesData", sourceMethod: data.data] // library marker davegut.tpLinkTransAes, line 137 try { // library marker davegut.tpLinkTransAes, line 138 byte[] encKey = new JsonSlurper().parseText(encKey) // library marker davegut.tpLinkTransAes, line 139 byte[] encIv = new JsonSlurper().parseText(encIv) // library marker davegut.tpLinkTransAes, line 140 Map cmdResp = new JsonSlurper().parseText(aesDecrypt(resp.json.result.response, // library marker davegut.tpLinkTransAes, line 141 encKey, encIv)) // library marker davegut.tpLinkTransAes, line 142 parseData << [cryptoStatus: "OK", cmdResp: cmdResp] // library marker davegut.tpLinkTransAes, line 143 } catch (err) { // library marker davegut.tpLinkTransAes, line 144 parseData << [cryptoStatus: "decryptDataError", error: err, dataLength: resp.data.length()] // library marker davegut.tpLinkTransAes, line 145 } // library marker davegut.tpLinkTransAes, line 146 return parseData // library marker davegut.tpLinkTransAes, line 147 } // library marker davegut.tpLinkTransAes, line 148 def getRsaKey() { // library marker davegut.tpLinkTransAes, line 150 return [public: "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGr/mHBK8aqx7UAS+g+TuAvE3J2DdwsqRn9MmAkjPGNon1ZlwM6nLQHfJHebdohyVqkNWaCECGXnftnlC8CM2c/RujvCrStRA0lVD+jixO9QJ9PcYTa07Z1FuEze7Q5OIa6pEoPxomrjxzVlUWLDXt901qCdn3/zRZpBdpXzVZtQIDAQAB", // library marker davegut.tpLinkTransAes, line 151 private: "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMav+YcErxqrHtQBL6D5O4C8TcnYN3CypGf0yYCSM8Y2ifVmXAzqctAd8kd5t2iHJWqQ1ZoIQIZed+2eULwIzZz9G6O8KtK1EDSVUP6OLE71An09xhNrTtnUW4TN7tDk4hrqkSg/GiauPHNWVRYsNe33TWoJ2ff/NFmkF2lfNVm1AgMBAAECgYEAocxCHmKBGe2KAEkq+SKdAxvVGO77TsobOhDMWug0Q1C8jduaUGZHsxT/7JbA9d1AagSh/XqE2Sdq8FUBF+7vSFzozBHyGkrX1iKURpQFEQM2j9JgUCucEavnxvCqDYpscyNRAgqz9jdh+BjEMcKAG7o68bOw41ZC+JyYR41xSe0CQQD1os71NcZiMVqYcBud6fTYFHZz3HBNcbzOk+RpIHyi8aF3zIqPKIAh2pO4s7vJgrMZTc2wkIe0ZnUrm0oaC//jAkEAzxIPW1mWd3+KE3gpgyX0cFkZsDmlIbWojUIbyz8NgeUglr+BczARG4ITrTV4fxkGwNI4EZxBT8vXDSIXJ8NDhwJBAIiKndx0rfg7Uw7VkqRvPqk2hrnU2aBTDw8N6rP9WQsCoi0DyCnX65Hl/KN5VXOocYIpW6NAVA8VvSAmTES6Ut0CQQCX20jD13mPfUsHaDIZafZPhiheoofFpvFLVtYHQeBoCF7T7vHCRdfl8oj3l6UcoH/hXMmdsJf9KyI1EXElyf91AkAvLfmAS2UvUnhX4qyFioitjxwWawSnf+CewN8LDbH7m5JVXJEh3hqp+aLHg1EaW4wJtkoKLCF+DeVIgbSvOLJw"] // library marker davegut.tpLinkTransAes, line 152 } // library marker davegut.tpLinkTransAes, line 153 // ~~~~~ end include (388) davegut.tpLinkTransAes ~~~~~ // ~~~~~ start include (389) davegut.tpLinkCamTransport ~~~~~ library ( // library marker davegut.tpLinkCamTransport, line 1 name: "tpLinkCamTransport", // library marker davegut.tpLinkCamTransport, line 2 namespace: "davegut", // library marker davegut.tpLinkCamTransport, line 3 author: "Compiled by Dave Gutheinz", // library marker davegut.tpLinkCamTransport, line 4 description: "TP-Link Camera Protocol Implementation", // library marker davegut.tpLinkCamTransport, line 5 category: "utilities", // library marker davegut.tpLinkCamTransport, line 6 documentationLink: "" // library marker davegut.tpLinkCamTransport, line 7 ) // library marker davegut.tpLinkCamTransport, line 8 import java.util.Random // library marker davegut.tpLinkCamTransport, line 9 import java.security.MessageDigest // library marker davegut.tpLinkCamTransport, line 10 import java.security.spec.PKCS8EncodedKeySpec // library marker davegut.tpLinkCamTransport, line 11 import javax.crypto.Cipher // library marker davegut.tpLinkCamTransport, line 12 import java.security.KeyFactory // library marker davegut.tpLinkCamTransport, line 13 import javax.crypto.spec.SecretKeySpec // library marker davegut.tpLinkCamTransport, line 14 import javax.crypto.spec.IvParameterSpec // library marker davegut.tpLinkCamTransport, line 15 def cameraHandshake(hsInput, devData = [:]) { // library marker davegut.tpLinkCamTransport, line 17 //log.debug devData // library marker davegut.tpLinkCamTransport, line 18 Map logData = [url: hsInput.url] // library marker davegut.tpLinkCamTransport, line 19 if (devData != [:]) { // library marker davegut.tpLinkCamTransport, line 20 logData << [model: devData.model, ip: devData.ip] // library marker davegut.tpLinkCamTransport, line 21 } // library marker davegut.tpLinkCamTransport, line 22 def warn = true // library marker davegut.tpLinkCamTransport, line 23 // Step 1. Determine secureUser (userName or "admin"). // library marker davegut.tpLinkCamTransport, line 24 def isSecure = state.isSecure // library marker davegut.tpLinkCamTransport, line 25 if (isSecure != true) { // library marker davegut.tpLinkCamTransport, line 26 Map secResp = checkSecure(hsInput) // library marker davegut.tpLinkCamTransport, line 27 isSecure = secResp.secure // library marker davegut.tpLinkCamTransport, line 28 // isSecure = checkSecure(hsInput) // library marker davegut.tpLinkCamTransport, line 29 state.isSecure = isSecure // library marker davegut.tpLinkCamTransport, line 30 logData << [checkSecure: secResp] // library marker davegut.tpLinkCamTransport, line 31 } // library marker davegut.tpLinkCamTransport, line 32 if (isSecure == true) { // library marker davegut.tpLinkCamTransport, line 33 // Step 2. Confirm Device. // library marker davegut.tpLinkCamTransport, line 34 Map confData = confirmDevice(hsInput) // library marker davegut.tpLinkCamTransport, line 35 logData << [confirmDevice: confData] // library marker davegut.tpLinkCamTransport, line 36 if (confData.confirmed == true) { // library marker davegut.tpLinkCamTransport, line 37 // Step 3. Get token data. // library marker davegut.tpLinkCamTransport, line 38 Map tokenData = getToken(hsInput, confData) // library marker davegut.tpLinkCamTransport, line 39 logData << [tokenData: tokenData] // library marker davegut.tpLinkCamTransport, line 40 if (tokenData.status == "OK") { // library marker davegut.tpLinkCamTransport, line 41 // Step 4. next action. // library marker davegut.tpLinkCamTransport, line 42 if (app) { // library marker davegut.tpLinkCamTransport, line 43 camCmdIn = [lsk: tokenData.lsk, ivb: tokenData.ivb, // library marker davegut.tpLinkCamTransport, line 44 seqNo: tokenData.seqNo, apiUrl: tokenData.apiUrl, // library marker davegut.tpLinkCamTransport, line 45 encPwd: hsInput.pwd, cnonce: confData.cnonce] // library marker davegut.tpLinkCamTransport, line 46 sendCameraDataCmd(devData, camCmdIn) // library marker davegut.tpLinkCamTransport, line 47 } else if (device) { // library marker davegut.tpLinkCamTransport, line 48 device.updateSetting("nonce", [type:"password", value: confData.nonce]) // library marker davegut.tpLinkCamTransport, line 49 device.updateSetting("cnonce", [type:"password", value: confData.cnonce]) // library marker davegut.tpLinkCamTransport, line 50 device.updateSetting("lsk",[type:"password", value: tokenData.lsk]) // library marker davegut.tpLinkCamTransport, line 51 device.updateSetting("ivb",[type:"password", value: tokenData.ivb]) // library marker davegut.tpLinkCamTransport, line 52 device.updateSetting("encPwd",[type:"password", value: hsInput.encPwd]) // library marker davegut.tpLinkCamTransport, line 53 device.updateSetting("apiUrl",[type:"password", value: tokenData.apiUrl]) // library marker davegut.tpLinkCamTransport, line 54 state.seqNo = tokenData.seqNo // library marker davegut.tpLinkCamTransport, line 55 } // library marker davegut.tpLinkCamTransport, line 56 warn = false // library marker davegut.tpLinkCamTransport, line 57 } // library marker davegut.tpLinkCamTransport, line 58 } // library marker davegut.tpLinkCamTransport, line 59 } // library marker davegut.tpLinkCamTransport, line 60 if (warn == true) { // library marker davegut.tpLinkCamTransport, line 61 logWarn([cameraHandshake: logData]) // library marker davegut.tpLinkCamTransport, line 62 } // library marker davegut.tpLinkCamTransport, line 63 else { // library marker davegut.tpLinkCamTransport, line 64 logDebug([cameraHandshake: logData]) // library marker davegut.tpLinkCamTransport, line 65 } // library marker davegut.tpLinkCamTransport, line 66 return logData // library marker davegut.tpLinkCamTransport, line 67 } // library marker davegut.tpLinkCamTransport, line 68 def checkSecure(hsInput) { // library marker davegut.tpLinkCamTransport, line 70 Map secResp = [:] // library marker davegut.tpLinkCamTransport, line 71 secure = false // library marker davegut.tpLinkCamTransport, line 72 Map cmdBody = [method: "login", params: [encrypt_type: "3", username: hsInput.user]] // library marker davegut.tpLinkCamTransport, line 73 Map respData = postSync(cmdBody, hsInput.url) // library marker davegut.tpLinkCamTransport, line 74 if (respData.error_code == -40413 && respData.result && respData.result.data // library marker davegut.tpLinkCamTransport, line 75 && respData.result.data.encrypt_type.contains("3")) { // library marker davegut.tpLinkCamTransport, line 76 secure = true // library marker davegut.tpLinkCamTransport, line 77 } else { // library marker davegut.tpLinkCamTransport, line 78 secResp << [invalidUser: getNote("checkUser")] // library marker davegut.tpLinkCamTransport, line 79 } // library marker davegut.tpLinkCamTransport, line 80 secResp << [secure: secure] // library marker davegut.tpLinkCamTransport, line 81 return secResp // library marker davegut.tpLinkCamTransport, line 82 } // library marker davegut.tpLinkCamTransport, line 83 def confirmDevice(hsInput) { // library marker davegut.tpLinkCamTransport, line 85 String cnonce = getSeed(8).encodeHex().toString().toUpperCase() // library marker davegut.tpLinkCamTransport, line 86 Map cmdBody = [method: "login", // library marker davegut.tpLinkCamTransport, line 87 params: [cnonce: cnonce, encrypt_type: "3", username: hsInput.user]] // library marker davegut.tpLinkCamTransport, line 88 Map respData = postSync(cmdBody, hsInput.url) // library marker davegut.tpLinkCamTransport, line 89 def confirmed = false // library marker davegut.tpLinkCamTransport, line 90 Map confData = [:] // library marker davegut.tpLinkCamTransport, line 91 if (respData.result) { // library marker davegut.tpLinkCamTransport, line 92 Map results = respData.result.data // library marker davegut.tpLinkCamTransport, line 93 if (respData.error_code == -40413 && results.code == -40401 && // library marker davegut.tpLinkCamTransport, line 94 results.encrypt_type.toString().contains("3")) { // library marker davegut.tpLinkCamTransport, line 95 String noncesPwdHash = cnonce + hsInput.pwd + results.nonce // library marker davegut.tpLinkCamTransport, line 96 String testHash = mdEncode("SHA-256", noncesPwdHash.getBytes()).encodeHex().toString().toUpperCase() // library marker davegut.tpLinkCamTransport, line 97 String checkData = testHash + results.nonce + cnonce // library marker davegut.tpLinkCamTransport, line 98 if (checkData == results.device_confirm) { // library marker davegut.tpLinkCamTransport, line 99 confData << [nonce: results.nonce, cnonce: cnonce] // library marker davegut.tpLinkCamTransport, line 100 confirmed = true // library marker davegut.tpLinkCamTransport, line 101 } else { // library marker davegut.tpLinkCamTransport, line 102 confData << [error: "checkData and deviceData mismatch"] // library marker davegut.tpLinkCamTransport, line 103 } // library marker davegut.tpLinkCamTransport, line 104 } else { // library marker davegut.tpLinkCamTransport, line 105 confData << [error_code: respData.error_code, code: results.code, // library marker davegut.tpLinkCamTransport, line 106 encrypt_type: results.encrypt_type, error: "invalid data to continue"] // library marker davegut.tpLinkCamTransport, line 107 } // library marker davegut.tpLinkCamTransport, line 108 } else { // library marker davegut.tpLinkCamTransport, line 109 confData << [respData: respData, error: "no respData.results in return."] // library marker davegut.tpLinkCamTransport, line 110 } // library marker davegut.tpLinkCamTransport, line 111 confData << [confirmed: confirmed] // library marker davegut.tpLinkCamTransport, line 112 if (confirmed == false) { // library marker davegut.tpLinkCamTransport, line 113 confData << [checkPwd: getNote("checkPwd"), thirdParty: getNote("thirdParty")] // library marker davegut.tpLinkCamTransport, line 114 } // library marker davegut.tpLinkCamTransport, line 115 return confData // library marker davegut.tpLinkCamTransport, line 116 } // library marker davegut.tpLinkCamTransport, line 117 def getToken(hsInput, confData) { // library marker davegut.tpLinkCamTransport, line 119 Map tokenData = [:] // library marker davegut.tpLinkCamTransport, line 120 String digestPwdHex = hsInput.pwd + confData.cnonce + confData.nonce // library marker davegut.tpLinkCamTransport, line 121 String digestPwd = mdEncode("SHA-256", digestPwdHex.getBytes()).encodeHex().toString().toUpperCase() // library marker davegut.tpLinkCamTransport, line 122 String fullDigestPwdHex = digestPwd + confData.cnonce + confData.nonce // library marker davegut.tpLinkCamTransport, line 123 String fullDigestPwd = new String(fullDigestPwdHex.getBytes(), "UTF-8") // library marker davegut.tpLinkCamTransport, line 124 Map cmdBody = [ // library marker davegut.tpLinkCamTransport, line 125 method: "login", // library marker davegut.tpLinkCamTransport, line 126 params: [cnonce: confData.cnonce, // library marker davegut.tpLinkCamTransport, line 127 encrypt_type: "3", // library marker davegut.tpLinkCamTransport, line 128 digest_passwd: fullDigestPwd, // library marker davegut.tpLinkCamTransport, line 129 username: hsInput.user // library marker davegut.tpLinkCamTransport, line 130 ]] // library marker davegut.tpLinkCamTransport, line 131 Map respData = postSync(cmdBody, hsInput.url) // library marker davegut.tpLinkCamTransport, line 132 Map logData = [errorCode: respData.error_code] // library marker davegut.tpLinkCamTransport, line 133 if (respData.error_code == 0) { // library marker davegut.tpLinkCamTransport, line 134 Map result = respData.result // library marker davegut.tpLinkCamTransport, line 135 if (result != null) { // library marker davegut.tpLinkCamTransport, line 136 if (result.start_seq != null) { // library marker davegut.tpLinkCamTransport, line 137 if (result.user_group == "root") { // library marker davegut.tpLinkCamTransport, line 138 byte[] lsk = genEncryptToken("lsk", hsInput.pwd, confData.nonce, confData.cnonce) // library marker davegut.tpLinkCamTransport, line 139 byte[] ivb = genEncryptToken("ivb", hsInput.pwd, confData.nonce, confData.cnonce) // library marker davegut.tpLinkCamTransport, line 140 String apiUrl = "${hsInput.url}/stok=${result.stok}/ds" // library marker davegut.tpLinkCamTransport, line 141 tokenData << [seqNo: result.start_seq, lsk: lsk, ivb: ivb, // library marker davegut.tpLinkCamTransport, line 142 apiUrl: apiUrl, status: "OK"] // library marker davegut.tpLinkCamTransport, line 143 } else { // library marker davegut.tpLinkCamTransport, line 144 tokenData << [status: "invalidUserGroup"] // library marker davegut.tpLinkCamTransport, line 145 } // library marker davegut.tpLinkCamTransport, line 146 } else { // library marker davegut.tpLinkCamTransport, line 147 tokenData << [status: "nullStartSeq"] // library marker davegut.tpLinkCamTransport, line 148 } // library marker davegut.tpLinkCamTransport, line 149 } else { // library marker davegut.tpLinkCamTransport, line 150 tokenData << [status: "nullDataFrom Device", respData: respData] // library marker davegut.tpLinkCamTransport, line 151 } // library marker davegut.tpLinkCamTransport, line 152 } else { // library marker davegut.tpLinkCamTransport, line 153 tokenData << [status: "credentialError"] // library marker davegut.tpLinkCamTransport, line 154 } // library marker davegut.tpLinkCamTransport, line 155 if (tokenData.status != "OK") { // library marker davegut.tpLinkCamTransport, line 156 tokenData << [respData: respData, tokenErr: getNote("tokenErr")] // library marker davegut.tpLinkCamTransport, line 157 // logData << [tokenData: tokenData, respData: respData] // library marker davegut.tpLinkCamTransport, line 158 // logWarn([getToken: logData]) // library marker davegut.tpLinkCamTransport, line 159 } // library marker davegut.tpLinkCamTransport, line 160 return tokenData // library marker davegut.tpLinkCamTransport, line 161 } // library marker davegut.tpLinkCamTransport, line 162 def genEncryptToken(tokenType, pwd, nonce, cnonce) { // library marker davegut.tpLinkCamTransport, line 164 String hashedKeyHex = cnonce + pwd + nonce // library marker davegut.tpLinkCamTransport, line 165 String hashedKey = mdEncode("SHA-256", hashedKeyHex.getBytes()).encodeHex().toString().toUpperCase() // library marker davegut.tpLinkCamTransport, line 166 String tokenHex = tokenType + cnonce + nonce + hashedKey // library marker davegut.tpLinkCamTransport, line 167 byte[] tokenFull = mdEncode("SHA-256", tokenHex.getBytes()) // library marker davegut.tpLinkCamTransport, line 168 return tokenFull[0..15] // library marker davegut.tpLinkCamTransport, line 169 } // library marker davegut.tpLinkCamTransport, line 170 def getNote(noteId) { // library marker davegut.tpLinkCamTransport, line 172 String note = "Undefined note." // library marker davegut.tpLinkCamTransport, line 173 if (noteId == "checkPwd") { // library marker davegut.tpLinkCamTransport, line 174 note = "Check password. Must not have spaces. Certain special characters also cause failure." // library marker davegut.tpLinkCamTransport, line 175 } else if (noteId == "thirdParty") { // library marker davegut.tpLinkCamTransport, line 176 note = "Check Tapo app setting Third-Party Services for on. If on toggle off then on." // library marker davegut.tpLinkCamTransport, line 177 } else if (noteId == "checkUser") { // library marker davegut.tpLinkCamTransport, line 178 note = "Check username. No spaces. Alternate username admin may also work." // library marker davegut.tpLinkCamTransport, line 179 } else if (noteId == "tokenErr") { // library marker davegut.tpLinkCamTransport, line 180 note = "Try again in 10 minutes. If error persists, contact developer." // library marker davegut.tpLinkCamTransport, line 181 } // library marker davegut.tpLinkCamTransport, line 182 return note // library marker davegut.tpLinkCamTransport, line 183 } // library marker davegut.tpLinkCamTransport, line 184 def shortHandshake() { // library marker davegut.tpLinkCamTransport, line 186 String pwd = parent.encPasswordCam // library marker davegut.tpLinkCamTransport, line 187 String url = getDataValue("baseUrl") // library marker davegut.tpLinkCamTransport, line 188 Map logData = [:] // library marker davegut.tpLinkCamTransport, line 189 String digestPwdHex = pwd + cnonce + nonce // library marker davegut.tpLinkCamTransport, line 190 String digestPwd = mdEncode("SHA-256", digestPwdHex.getBytes()).encodeHex().toString().toUpperCase() // library marker davegut.tpLinkCamTransport, line 191 String fullDigestPwdHex = digestPwd + cnonce + nonce // library marker davegut.tpLinkCamTransport, line 192 String fullDigestPwd = new String(fullDigestPwdHex.getBytes(), "UTF-8") // library marker davegut.tpLinkCamTransport, line 193 Map cmdBody = [ // library marker davegut.tpLinkCamTransport, line 194 method: "login", // library marker davegut.tpLinkCamTransport, line 195 params: [cnonce: cnonce, // library marker davegut.tpLinkCamTransport, line 196 encrypt_type: "3", // library marker davegut.tpLinkCamTransport, line 197 digest_passwd: fullDigestPwd, // library marker davegut.tpLinkCamTransport, line 198 username: parent.userName // library marker davegut.tpLinkCamTransport, line 199 ]] // library marker davegut.tpLinkCamTransport, line 200 Map respData = postSync(cmdBody, url) // library marker davegut.tpLinkCamTransport, line 201 logData << [errorCode: respData.error_code] // library marker davegut.tpLinkCamTransport, line 203 String tokenStatus = "OK" // library marker davegut.tpLinkCamTransport, line 204 if (respData.error_code == 0) { // library marker davegut.tpLinkCamTransport, line 205 Map result = respData.result // library marker davegut.tpLinkCamTransport, line 206 if (result != null) { // library marker davegut.tpLinkCamTransport, line 207 if (result.start_seq != null) { // library marker davegut.tpLinkCamTransport, line 208 if (result.user_group == "root") { // library marker davegut.tpLinkCamTransport, line 209 byte[] lsk = genEncryptToken("lsk", pwd, nonce, cnonce) // library marker davegut.tpLinkCamTransport, line 210 byte[] ivb = genEncryptToken("ivb", pwd, nonce, cnonce) // library marker davegut.tpLinkCamTransport, line 211 String apiUrl = "${url}/stok=${result.stok}/ds" // library marker davegut.tpLinkCamTransport, line 212 logData << [seqNo: result.start_seq, lsk: lsk, ivb: ivb, apiUrl: apiUrl] // library marker davegut.tpLinkCamTransport, line 213 device.updateSetting("lsk",[type:"password", value: lsk]) // library marker davegut.tpLinkCamTransport, line 214 device.updateSetting("ivb",[type:"password", value: ivb]) // library marker davegut.tpLinkCamTransport, line 215 device.updateSetting("apiUrl",[type:"password", value: apiUrl]) // library marker davegut.tpLinkCamTransport, line 216 state.seqNo = result.start_seq // library marker davegut.tpLinkCamTransport, line 217 } else { // library marker davegut.tpLinkCamTransport, line 218 tokenStatus = "invalidUserGroup" // library marker davegut.tpLinkCamTransport, line 219 } // library marker davegut.tpLinkCamTransport, line 220 } else { // library marker davegut.tpLinkCamTransport, line 221 tokenStatus = "nullStartSeq" // library marker davegut.tpLinkCamTransport, line 222 } // library marker davegut.tpLinkCamTransport, line 223 } else { // library marker davegut.tpLinkCamTransport, line 224 tokenStatus = "nullDataFrom Device" // library marker davegut.tpLinkCamTransport, line 225 } // library marker davegut.tpLinkCamTransport, line 226 } else { // library marker davegut.tpLinkCamTransport, line 227 tokenStatus ="credentialError" // library marker davegut.tpLinkCamTransport, line 228 } // library marker davegut.tpLinkCamTransport, line 229 if (tokenStatus != "OK") { // library marker davegut.tpLinkCamTransport, line 230 Map hsInput = [url: url, user: parent.userName, pwd: pwd] // library marker davegut.tpLinkCamTransport, line 231 logData << [respData: respData, cameraHandshake: cameraHandshake(hsInput)] // library marker davegut.tpLinkCamTransport, line 232 logWarn([shortHandshake: logData]) // library marker davegut.tpLinkCamTransport, line 233 } // library marker davegut.tpLinkCamTransport, line 234 return logData // library marker davegut.tpLinkCamTransport, line 235 } // library marker davegut.tpLinkCamTransport, line 236 // ===== Sync Communications ===== // library marker davegut.tpLinkCamTransport, line 238 def getCamHeaders() { // library marker davegut.tpLinkCamTransport, line 239 Map headers = [ // library marker davegut.tpLinkCamTransport, line 240 "Accept": "application/json", // library marker davegut.tpLinkCamTransport, line 241 "Accept-Encoding": "gzip, deflate", // library marker davegut.tpLinkCamTransport, line 242 "User-Agent": "Tapo CameraClient Android", // library marker davegut.tpLinkCamTransport, line 243 "Connection": "close", // library marker davegut.tpLinkCamTransport, line 244 "requestByApp": "true", // library marker davegut.tpLinkCamTransport, line 245 "Content-Type": "application/json; charset=UTF-8" // library marker davegut.tpLinkCamTransport, line 246 ] // library marker davegut.tpLinkCamTransport, line 247 return headers // library marker davegut.tpLinkCamTransport, line 248 } // library marker davegut.tpLinkCamTransport, line 249 def postSync(cmdBody, baseUrl) { // library marker davegut.tpLinkCamTransport, line 251 Map respData = [:] // library marker davegut.tpLinkCamTransport, line 252 Map heads = getCamHeaders() // library marker davegut.tpLinkCamTransport, line 253 Map httpParams = [uri: baseUrl, // library marker davegut.tpLinkCamTransport, line 254 body: JsonOutput.toJson(cmdBody), // library marker davegut.tpLinkCamTransport, line 255 contentType: "application/json", // library marker davegut.tpLinkCamTransport, line 256 requestContentType: "application/json", // library marker davegut.tpLinkCamTransport, line 257 timeout: 10, // library marker davegut.tpLinkCamTransport, line 258 ignoreSSLIssues: true, // library marker davegut.tpLinkCamTransport, line 259 headers: heads // library marker davegut.tpLinkCamTransport, line 260 ] // library marker davegut.tpLinkCamTransport, line 261 try { // library marker davegut.tpLinkCamTransport, line 262 httpPostJson(httpParams) { resp -> // library marker davegut.tpLinkCamTransport, line 263 if (resp.status == 200) { // library marker davegut.tpLinkCamTransport, line 264 respData << resp.data // library marker davegut.tpLinkCamTransport, line 265 } else { // library marker davegut.tpLinkCamTransport, line 266 respData << [status: resp.status, errorData: resp.properties, // library marker davegut.tpLinkCamTransport, line 267 action: "Check IP Address"] // library marker davegut.tpLinkCamTransport, line 268 logWarn(respData) // library marker davegut.tpLinkCamTransport, line 269 } // library marker davegut.tpLinkCamTransport, line 270 } // library marker davegut.tpLinkCamTransport, line 271 } catch (err) { // library marker davegut.tpLinkCamTransport, line 272 respData << [status: "httpPostJson error", error: err] // library marker davegut.tpLinkCamTransport, line 273 logWarn(respData) // library marker davegut.tpLinkCamTransport, line 274 } // library marker davegut.tpLinkCamTransport, line 275 return respData // library marker davegut.tpLinkCamTransport, line 276 } // library marker davegut.tpLinkCamTransport, line 277 def getCameraParams(cmdBody, reqData) { // library marker davegut.tpLinkCamTransport, line 279 byte[] encKey = new JsonSlurper().parseText(lsk) // library marker davegut.tpLinkCamTransport, line 280 byte[] encIv = new JsonSlurper().parseText(ivb) // library marker davegut.tpLinkCamTransport, line 281 def cmdStr = JsonOutput.toJson(cmdBody) // library marker davegut.tpLinkCamTransport, line 282 Map reqBody = [method: "securePassthrough", // library marker davegut.tpLinkCamTransport, line 283 params: [request: aesEncrypt(cmdStr, encKey, encIv)]] // library marker davegut.tpLinkCamTransport, line 284 String cmdData = new groovy.json.JsonBuilder(reqBody).toString() // library marker davegut.tpLinkCamTransport, line 285 Integer seqNumber = state.seqNo // library marker davegut.tpLinkCamTransport, line 286 String initTagHex = parent.encPasswordCam + cnonce // library marker davegut.tpLinkCamTransport, line 287 String initTag = mdEncode("SHA-256", initTagHex.getBytes()).encodeHex().toString().toUpperCase() // library marker davegut.tpLinkCamTransport, line 288 String tagString = initTag + cmdData + seqNumber // library marker davegut.tpLinkCamTransport, line 289 String tag = mdEncode("SHA-256", tagString.getBytes()).encodeHex().toString().toUpperCase() // library marker davegut.tpLinkCamTransport, line 290 Map heads = getCamHeaders() // library marker davegut.tpLinkCamTransport, line 291 heads << ["Tapo_tag": tag, Seq: seqNumber] // library marker davegut.tpLinkCamTransport, line 292 Map reqParams = [uri: apiUrl, // library marker davegut.tpLinkCamTransport, line 293 body: cmdData, // library marker davegut.tpLinkCamTransport, line 294 contentType: "application/json", // library marker davegut.tpLinkCamTransport, line 295 requestContentType: "application/json", // library marker davegut.tpLinkCamTransport, line 296 timeout: 15, // library marker davegut.tpLinkCamTransport, line 297 ignoreSSLIssues: true, // library marker davegut.tpLinkCamTransport, line 298 headers: heads // library marker davegut.tpLinkCamTransport, line 299 ] // library marker davegut.tpLinkCamTransport, line 300 return reqParams // library marker davegut.tpLinkCamTransport, line 301 } // library marker davegut.tpLinkCamTransport, line 302 def parseCameraData(resp, data) { // library marker davegut.tpLinkCamTransport, line 304 Map parseData = [sourceMethod: data.data, jsonErrCode: resp.json.error_code] // library marker davegut.tpLinkCamTransport, line 305 state.seqNo += 1 // library marker davegut.tpLinkCamTransport, line 306 if (resp.json.error_code == 0) { // library marker davegut.tpLinkCamTransport, line 307 try { // library marker davegut.tpLinkCamTransport, line 308 byte[] encKey = new JsonSlurper().parseText(lsk) // library marker davegut.tpLinkCamTransport, line 309 byte[] encIv = new JsonSlurper().parseText(ivb) // library marker davegut.tpLinkCamTransport, line 310 Map cmdResp = new JsonSlurper().parseText(aesDecrypt(resp.json.result.response, // library marker davegut.tpLinkCamTransport, line 311 encKey, encIv)) // library marker davegut.tpLinkCamTransport, line 312 parseData << [parseStatus: "OK", cmdResp: cmdResp] // library marker davegut.tpLinkCamTransport, line 313 state.protoError = false // library marker davegut.tpLinkCamTransport, line 314 } catch (err) { // library marker davegut.tpLinkCamTransport, line 315 parseData << [parseStatus: "Decrypt Error", error: err] // library marker davegut.tpLinkCamTransport, line 316 } // library marker davegut.tpLinkCamTransport, line 317 } else { // library marker davegut.tpLinkCamTransport, line 318 parseData << [parseStatus: "Protocol Error"] // library marker davegut.tpLinkCamTransport, line 319 } // library marker davegut.tpLinkCamTransport, line 320 if (parseData.parseStatus != "OK") { // library marker davegut.tpLinkCamTransport, line 321 if (state.protoError == false) { // library marker davegut.tpLinkCamTransport, line 322 parseData << [nextMeth: "resolveProtocolError"] // library marker davegut.tpLinkCamTransport, line 323 resolveProtocolError() // library marker davegut.tpLinkCamTransport, line 324 } // library marker davegut.tpLinkCamTransport, line 325 } // library marker davegut.tpLinkCamTransport, line 326 return parseData // library marker davegut.tpLinkCamTransport, line 327 } // library marker davegut.tpLinkCamTransport, line 328 // Run deviceHandshake then retry command // library marker davegut.tpLinkCamTransport, line 330 def resolveProtocolError() { // library marker davegut.tpLinkCamTransport, line 331 Map logData = [method: "resolveProtocolError", lastCmd: state.lastCommand] // library marker davegut.tpLinkCamTransport, line 332 state.protoError = true // library marker davegut.tpLinkCamTransport, line 333 deviceHandshake() // library marker davegut.tpLinkCamTransport, line 334 runIn(4, delayedPassThrough) // library marker davegut.tpLinkCamTransport, line 335 logDebug(logData) // library marker davegut.tpLinkCamTransport, line 336 } // library marker davegut.tpLinkCamTransport, line 337 // ~~~~~ end include (389) davegut.tpLinkCamTransport ~~~~~ // ~~~~~ start include (390) davegut.tpLinkTransKlap ~~~~~ library ( // library marker davegut.tpLinkTransKlap, line 1 name: "tpLinkTransKlap", // library marker davegut.tpLinkTransKlap, line 2 namespace: "davegut", // library marker davegut.tpLinkTransKlap, line 3 author: "Compiled by Dave Gutheinz", // library marker davegut.tpLinkTransKlap, line 4 description: "Handshake methods for TP-Link Integration", // library marker davegut.tpLinkTransKlap, line 5 category: "utilities", // library marker davegut.tpLinkTransKlap, line 6 documentationLink: "" // library marker davegut.tpLinkTransKlap, line 7 ) // library marker davegut.tpLinkTransKlap, line 8 def klapHandshake(baseUrl, localHash, devData = null) { // library marker davegut.tpLinkTransKlap, line 10 byte[] localSeed = getSeed(16) // library marker davegut.tpLinkTransKlap, line 11 Map reqData = [localSeed: localSeed, baseUrl: baseUrl, localHash: localHash, devData:devData] // library marker davegut.tpLinkTransKlap, line 12 Map reqParams = [uri: "${baseUrl}/handshake1", // library marker davegut.tpLinkTransKlap, line 13 body: localSeed, // library marker davegut.tpLinkTransKlap, line 14 contentType: "application/octet-stream", // library marker davegut.tpLinkTransKlap, line 15 requestContentType: "application/octet-stream", // library marker davegut.tpLinkTransKlap, line 16 timeout:10, // library marker davegut.tpLinkTransKlap, line 17 ignoreSSLIssues: true] // library marker davegut.tpLinkTransKlap, line 18 asynchttpPost("parseKlapHandshake", reqParams, [data: reqData]) // library marker davegut.tpLinkTransKlap, line 19 } // library marker davegut.tpLinkTransKlap, line 20 def parseKlapHandshake(resp, data) { // library marker davegut.tpLinkTransKlap, line 22 Map reqData = [devData: data.data.devData] // library marker davegut.tpLinkTransKlap, line 23 hs1Success = false // library marker davegut.tpLinkTransKlap, line 24 if (resp.status == 200 && resp.data != null) { // library marker davegut.tpLinkTransKlap, line 25 try { // library marker davegut.tpLinkTransKlap, line 26 byte[] localSeed = data.data.localSeed // library marker davegut.tpLinkTransKlap, line 27 byte[] seedData = resp.data.decodeBase64() // library marker davegut.tpLinkTransKlap, line 28 byte[] remoteSeed = seedData[0 .. 15] // library marker davegut.tpLinkTransKlap, line 29 byte[] serverHash = seedData[16 .. 47] // library marker davegut.tpLinkTransKlap, line 30 byte[] localHash = data.data.localHash.decodeBase64() // library marker davegut.tpLinkTransKlap, line 31 byte[] authHash = [localSeed, remoteSeed, localHash].flatten() // library marker davegut.tpLinkTransKlap, line 32 byte[] localAuthHash = mdEncode("SHA-256", authHash) // library marker davegut.tpLinkTransKlap, line 33 if (localAuthHash == serverHash) { // library marker davegut.tpLinkTransKlap, line 34 // cookie // library marker davegut.tpLinkTransKlap, line 35 def cookieHeader = resp.headers["Set-Cookie"].toString() // library marker davegut.tpLinkTransKlap, line 36 def cookie = cookieHeader.substring(cookieHeader.indexOf(":") +1, cookieHeader.indexOf(";")) // library marker davegut.tpLinkTransKlap, line 37 // seqNo and encIv // library marker davegut.tpLinkTransKlap, line 38 byte[] payload = ["iv".getBytes(), localSeed, remoteSeed, localHash].flatten() // library marker davegut.tpLinkTransKlap, line 39 byte[] fullIv = mdEncode("SHA-256", payload) // library marker davegut.tpLinkTransKlap, line 40 byte[] byteSeqNo = fullIv[-4..-1] // library marker davegut.tpLinkTransKlap, line 41 int seqNo = byteArrayToInteger(byteSeqNo) // library marker davegut.tpLinkTransKlap, line 42 if (device) { // library marker davegut.tpLinkTransKlap, line 43 state.seqNo = seqNo // library marker davegut.tpLinkTransKlap, line 44 } else { // library marker davegut.tpLinkTransKlap, line 45 atomicState.seqNo = seqNo // library marker davegut.tpLinkTransKlap, line 46 } // library marker davegut.tpLinkTransKlap, line 47 // encKey // library marker davegut.tpLinkTransKlap, line 48 payload = ["lsk".getBytes(), localSeed, remoteSeed, localHash].flatten() // library marker davegut.tpLinkTransKlap, line 49 byte[] encKey = mdEncode("SHA-256", payload)[0..15] // library marker davegut.tpLinkTransKlap, line 50 // encSig // library marker davegut.tpLinkTransKlap, line 51 payload = ["ldk".getBytes(), localSeed, remoteSeed, localHash].flatten() // library marker davegut.tpLinkTransKlap, line 52 byte[] encSig = mdEncode("SHA-256", payload)[0..27] // library marker davegut.tpLinkTransKlap, line 53 hs1Success = true // library marker davegut.tpLinkTransKlap, line 54 logDebug([parseKlapHandshake: reqData]) // library marker davegut.tpLinkTransKlap, line 55 if (device) { // library marker davegut.tpLinkTransKlap, line 56 device.updateSetting("cookie",[type:"password", value: cookie]) // library marker davegut.tpLinkTransKlap, line 57 device.updateSetting("encKey",[type:"password", value: encKey]) // library marker davegut.tpLinkTransKlap, line 58 device.updateSetting("encIv",[type:"password", value: fullIv[0..11]]) // library marker davegut.tpLinkTransKlap, line 59 device.updateSetting("encSig",[type:"password", value: encSig]) // library marker davegut.tpLinkTransKlap, line 60 } else { // library marker davegut.tpLinkTransKlap, line 61 reqData << [cookie: cookie, seqNo: seqNo, encIv: fullIv[0..11], // library marker davegut.tpLinkTransKlap, line 62 encSig: encSig, encKey: encKey] // library marker davegut.tpLinkTransKlap, line 63 } // library marker davegut.tpLinkTransKlap, line 64 byte[] loginHash = [remoteSeed, localSeed, localHash].flatten() // library marker davegut.tpLinkTransKlap, line 65 byte[] body = mdEncode("SHA-256", loginHash) // library marker davegut.tpLinkTransKlap, line 66 Map reqParams = [uri: "${data.data.baseUrl}/handshake2", // library marker davegut.tpLinkTransKlap, line 67 body: body, // library marker davegut.tpLinkTransKlap, line 68 timeout:10, // library marker davegut.tpLinkTransKlap, line 69 ignoreSSLIssues: true, // library marker davegut.tpLinkTransKlap, line 70 headers: ["Cookie": cookie], // library marker davegut.tpLinkTransKlap, line 71 contentType: "application/octet-stream", // library marker davegut.tpLinkTransKlap, line 72 requestContentType: "application/octet-stream"] // library marker davegut.tpLinkTransKlap, line 73 asynchttpPost("parseKlapHandshake2", reqParams, [data: reqData]) // library marker davegut.tpLinkTransKlap, line 74 } else { // library marker davegut.tpLinkTransKlap, line 75 reqData << [respStatus: "ERROR: localAuthHash != serverHash", // library marker davegut.tpLinkTransKlap, line 76 action: "Check credentials and try again"] // library marker davegut.tpLinkTransKlap, line 77 } // library marker davegut.tpLinkTransKlap, line 78 } catch (err) { // library marker davegut.tpLinkTransKlap, line 79 reqData << [respStatus: "ERROR parsing 200 response", resp: resp.properties, error: err, // library marker davegut.tpLinkTransKlap, line 80 action: "Try Configure command"] // library marker davegut.tpLinkTransKlap, line 81 } // library marker davegut.tpLinkTransKlap, line 82 } else { // library marker davegut.tpLinkTransKlap, line 83 reqData << [respStatus: resp.status, message: resp.errorMessage, // library marker davegut.tpLinkTransKlap, line 84 action: "Try Configure command"] // library marker davegut.tpLinkTransKlap, line 85 } // library marker davegut.tpLinkTransKlap, line 86 reqData << [hs1Success: hs1Success] // library marker davegut.tpLinkTransKlap, line 87 if (hs1Success == false) { // library marker davegut.tpLinkTransKlap, line 88 logWarn([parseKlapHandshake: reqData]) // library marker davegut.tpLinkTransKlap, line 89 } // library marker davegut.tpLinkTransKlap, line 90 } // library marker davegut.tpLinkTransKlap, line 91 def parseKlapHandshake2(resp, data) { // library marker davegut.tpLinkTransKlap, line 93 Map logData = [method: "parseKlapHandshake2"] // library marker davegut.tpLinkTransKlap, line 94 if (resp.status == 200 && resp.data == null) { // library marker davegut.tpLinkTransKlap, line 95 logData << [respStatus: "Login OK"] // library marker davegut.tpLinkTransKlap, line 96 setCommsError(200) // library marker davegut.tpLinkTransKlap, line 97 logDebug(logData) // library marker davegut.tpLinkTransKlap, line 98 } else { // library marker davegut.tpLinkTransKlap, line 99 logData << [respStatus: "LOGIN FAILED", reason: "ERROR in HTTP response", // library marker davegut.tpLinkTransKlap, line 100 resp: resp.properties] // library marker davegut.tpLinkTransKlap, line 101 logWarn(logData) // library marker davegut.tpLinkTransKlap, line 102 } // library marker davegut.tpLinkTransKlap, line 103 if (!device) { sendKlapDataCmd(logData, data) } // library marker davegut.tpLinkTransKlap, line 104 } // library marker davegut.tpLinkTransKlap, line 105 def getKlapParams(cmdBody) { // library marker davegut.tpLinkTransKlap, line 107 int seqNo = state.seqNo + 1 // library marker davegut.tpLinkTransKlap, line 108 state.seqNo = seqNo // library marker davegut.tpLinkTransKlap, line 109 byte[] encKey = new JsonSlurper().parseText(encKey) // library marker davegut.tpLinkTransKlap, line 110 byte[] encIv = new JsonSlurper().parseText(encIv) // library marker davegut.tpLinkTransKlap, line 111 byte[] encSig = new JsonSlurper().parseText(encSig) // library marker davegut.tpLinkTransKlap, line 112 String cmdBodyJson = new groovy.json.JsonBuilder(cmdBody).toString() // library marker davegut.tpLinkTransKlap, line 113 Map encryptedData = klapEncrypt(cmdBodyJson.getBytes(), encKey, encIv, // library marker davegut.tpLinkTransKlap, line 115 encSig, seqNo) // library marker davegut.tpLinkTransKlap, line 116 Map reqParams = [ // library marker davegut.tpLinkTransKlap, line 117 uri: "${getDataValue("baseUrl")}/request?seq=${seqNo}", // library marker davegut.tpLinkTransKlap, line 118 body: encryptedData.cipherData, // library marker davegut.tpLinkTransKlap, line 119 headers: ["Cookie": cookie], // library marker davegut.tpLinkTransKlap, line 120 contentType: "application/octet-stream", // library marker davegut.tpLinkTransKlap, line 121 requestContentType: "application/octet-stream", // library marker davegut.tpLinkTransKlap, line 122 timeout: 10, // library marker davegut.tpLinkTransKlap, line 123 ignoreSSLIssues: true] // library marker davegut.tpLinkTransKlap, line 124 return reqParams // library marker davegut.tpLinkTransKlap, line 125 } // library marker davegut.tpLinkTransKlap, line 126 def parseKlapData(resp, data) { // library marker davegut.tpLinkTransKlap, line 128 Map parseData = [Method: "parseKlapData", sourceMethod: data.data] // library marker davegut.tpLinkTransKlap, line 129 try { // library marker davegut.tpLinkTransKlap, line 130 byte[] encKey = new JsonSlurper().parseText(encKey) // library marker davegut.tpLinkTransKlap, line 131 byte[] encIv = new JsonSlurper().parseText(encIv) // library marker davegut.tpLinkTransKlap, line 132 int seqNo = state.seqNo // library marker davegut.tpLinkTransKlap, line 133 byte[] cipherResponse = resp.data.decodeBase64()[32..-1] // library marker davegut.tpLinkTransKlap, line 134 Map cmdResp = new JsonSlurper().parseText(klapDecrypt(cipherResponse, encKey, // library marker davegut.tpLinkTransKlap, line 135 encIv, seqNo)) // library marker davegut.tpLinkTransKlap, line 136 parseData << [cryptoStatus: "OK", cmdResp: cmdResp] // library marker davegut.tpLinkTransKlap, line 137 } catch (err) { // library marker davegut.tpLinkTransKlap, line 138 parseData << [cryptoStatus: "decryptDataError", error: err] // library marker davegut.tpLinkTransKlap, line 139 } // library marker davegut.tpLinkTransKlap, line 140 return parseData // library marker davegut.tpLinkTransKlap, line 141 } // library marker davegut.tpLinkTransKlap, line 142 // ~~~~~ end include (390) davegut.tpLinkTransKlap ~~~~~ // ~~~~~ start include (391) davegut.tpLinkTransVacAes ~~~~~ library ( // library marker davegut.tpLinkTransVacAes, line 1 name: "tpLinkTransVacAes", // library marker davegut.tpLinkTransVacAes, line 2 namespace: "davegut", // library marker davegut.tpLinkTransVacAes, line 3 author: "Compiled by Dave Gutheinz", // library marker davegut.tpLinkTransVacAes, line 4 description: "Handshake methods for TP-Link Integration", // library marker davegut.tpLinkTransVacAes, line 5 category: "utilities", // library marker davegut.tpLinkTransVacAes, line 6 documentationLink: "" // library marker davegut.tpLinkTransVacAes, line 7 ) // library marker davegut.tpLinkTransVacAes, line 8 // ===== Login ===== // library marker davegut.tpLinkTransVacAes, line 10 def vacAesHandshake(baseUrl, userName, encPasswordVac, devData = null) { // library marker davegut.tpLinkTransVacAes, line 11 Map reqData = [baseUrl: baseUrl, devData: devData] // library marker davegut.tpLinkTransVacAes, line 12 if (device) { // library marker davegut.tpLinkTransVacAes, line 13 userName = parent.userName // library marker davegut.tpLinkTransVacAes, line 14 encPasswordVac = parent.encPasswordVac // library marker davegut.tpLinkTransVacAes, line 15 } // library marker davegut.tpLinkTransVacAes, line 16 Map cmdBody = [method: "login", // library marker davegut.tpLinkTransVacAes, line 17 params: [hashed: true, // library marker davegut.tpLinkTransVacAes, line 18 password: encPasswordVac, // library marker davegut.tpLinkTransVacAes, line 19 username: userName]] // library marker davegut.tpLinkTransVacAes, line 20 Map reqParams = getVacAesParams(cmdBody, baseUrl) // library marker davegut.tpLinkTransVacAes, line 21 asynchttpPost("parseVacAesLogin", reqParams, [data: reqData]) // library marker davegut.tpLinkTransVacAes, line 22 } // library marker davegut.tpLinkTransVacAes, line 23 def parseVacAesLogin(resp, data) { // library marker davegut.tpLinkTransVacAes, line 25 Map logData = [method: "parseVacAesLogin", oldToken: token] // library marker davegut.tpLinkTransVacAes, line 26 if (resp.status == 200 && resp.json != null) { // library marker davegut.tpLinkTransVacAes, line 27 def newToken = resp.json.result.token // library marker davegut.tpLinkTransVacAes, line 28 logData << [status: "OK", token: newToken] // library marker davegut.tpLinkTransVacAes, line 29 if (device) { // library marker davegut.tpLinkTransVacAes, line 30 device.updateSetting("token", [type: "string", value: newToken]) // library marker davegut.tpLinkTransVacAes, line 31 setCommsError(200) // library marker davegut.tpLinkTransVacAes, line 32 } else { // library marker davegut.tpLinkTransVacAes, line 33 sendVacAesDataCmd(newToken, data) // library marker davegut.tpLinkTransVacAes, line 34 } // library marker davegut.tpLinkTransVacAes, line 35 logDebug(logData) // library marker davegut.tpLinkTransVacAes, line 36 } else { // library marker davegut.tpLinkTransVacAes, line 37 logData << [respStatus: "ERROR in HTTP response", resp: resp.properties] // library marker davegut.tpLinkTransVacAes, line 38 logWarn(logData) // library marker davegut.tpLinkTransVacAes, line 39 } // library marker davegut.tpLinkTransVacAes, line 40 } // library marker davegut.tpLinkTransVacAes, line 41 def getVacAesParams(cmdBody, url) { // library marker davegut.tpLinkTransVacAes, line 43 Map reqParams = [uri: url, // library marker davegut.tpLinkTransVacAes, line 44 body: cmdBody, // library marker davegut.tpLinkTransVacAes, line 45 contentType: "application/json", // library marker davegut.tpLinkTransVacAes, line 46 requestContentType: "application/json", // library marker davegut.tpLinkTransVacAes, line 47 ignoreSSLIssues: true, // library marker davegut.tpLinkTransVacAes, line 48 timeout: 10] // library marker davegut.tpLinkTransVacAes, line 49 return reqParams // library marker davegut.tpLinkTransVacAes, line 50 } // library marker davegut.tpLinkTransVacAes, line 51 def parseVacAesData(resp, data) { // library marker davegut.tpLinkTransVacAes, line 53 Map parseData = [parseMethod: "parseVacAesData", sourceMethod: data.data] // library marker davegut.tpLinkTransVacAes, line 54 try { // library marker davegut.tpLinkTransVacAes, line 55 parseData << [cryptoStatus: "OK", cmdResp: resp.json] // library marker davegut.tpLinkTransVacAes, line 56 logDebug(parseData) // library marker davegut.tpLinkTransVacAes, line 57 } catch (err) { // library marker davegut.tpLinkTransVacAes, line 58 parseData << [cryptoStatus: "deviceDataParseError", error: err, dataLength: resp.data.length()] // library marker davegut.tpLinkTransVacAes, line 59 logWarn(parseData) // library marker davegut.tpLinkTransVacAes, line 60 handleCommsError() // library marker davegut.tpLinkTransVacAes, line 61 } // library marker davegut.tpLinkTransVacAes, line 62 return parseData // library marker davegut.tpLinkTransVacAes, line 63 } // library marker davegut.tpLinkTransVacAes, line 64 // ~~~~~ end include (391) davegut.tpLinkTransVacAes ~~~~~ // ~~~~~ start include (376) davegut.Logging ~~~~~ library ( // library marker davegut.Logging, line 1 name: "Logging", // library marker davegut.Logging, line 2 namespace: "davegut", // library marker davegut.Logging, line 3 author: "Dave Gutheinz", // library marker davegut.Logging, line 4 description: "Common Logging and info gathering Methods", // library marker davegut.Logging, line 5 category: "utilities", // library marker davegut.Logging, line 6 documentationLink: "" // library marker davegut.Logging, line 7 ) // library marker davegut.Logging, line 8 def nameSpace() { return "davegut" } // library marker davegut.Logging, line 10 def version() { return "2.4.2a" } // library marker davegut.Logging, line 12 def label() { // library marker davegut.Logging, line 14 if (device) { // library marker davegut.Logging, line 15 return device.displayName + "-${version()}" // library marker davegut.Logging, line 16 } else { // library marker davegut.Logging, line 17 return app.getLabel() + "-${version()}" // library marker davegut.Logging, line 18 } // library marker davegut.Logging, line 19 } // library marker davegut.Logging, line 20 def updateAttr(attr, value) { // library marker davegut.Logging, line 22 if (device.currentValue(attr) != value) { // library marker davegut.Logging, line 23 sendEvent(name: attr, value: value) // library marker davegut.Logging, line 24 } // library marker davegut.Logging, line 25 } // library marker davegut.Logging, line 26 def listAttributes() { // library marker davegut.Logging, line 28 def attrData = device.getCurrentStates() // library marker davegut.Logging, line 29 Map attrs = [:] // library marker davegut.Logging, line 30 attrData.each { // library marker davegut.Logging, line 31 attrs << ["${it.name}": it.value] // library marker davegut.Logging, line 32 } // library marker davegut.Logging, line 33 return attrs // library marker davegut.Logging, line 34 } // library marker davegut.Logging, line 35 def setLogsOff() { // library marker davegut.Logging, line 37 def logData = [infoLog: infoLog, logEnable: logEnable] // library marker davegut.Logging, line 38 if (logEnable) { // library marker davegut.Logging, line 39 runIn(1800, debugLogOff) // library marker davegut.Logging, line 40 logData << [debugLogOff: "scheduled"] // library marker davegut.Logging, line 41 } // library marker davegut.Logging, line 42 return logData // library marker davegut.Logging, line 43 } // library marker davegut.Logging, line 44 def logTrace(msg){ log.trace "${label()}: ${msg}" } // library marker davegut.Logging, line 46 def logInfo(msg) { // library marker davegut.Logging, line 48 if (infoLog) { log.info "${label()}: ${msg}" } // library marker davegut.Logging, line 49 } // library marker davegut.Logging, line 50 def debugLogOff() { // library marker davegut.Logging, line 52 if (device) { // library marker davegut.Logging, line 53 device.updateSetting("logEnable", [type:"bool", value: false]) // library marker davegut.Logging, line 54 } else { // library marker davegut.Logging, line 55 app.updateSetting("logEnable", false) // library marker davegut.Logging, line 56 } // library marker davegut.Logging, line 57 logInfo("debugLogOff") // library marker davegut.Logging, line 58 } // library marker davegut.Logging, line 59 def logDebug(msg) { // library marker davegut.Logging, line 61 if (logEnable) { log.debug "${label()}: ${msg}" } // library marker davegut.Logging, line 62 } // library marker davegut.Logging, line 63 def logWarn(msg) { log.warn "${label()}: ${msg}" } // library marker davegut.Logging, line 65 def logError(msg) { log.error "${label()}: ${msg}" } // library marker davegut.Logging, line 67 // ~~~~~ end include (376) davegut.Logging ~~~~~