/* * Daikin DKN Cloud Interface * * Licensed Virtual the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License * for the specific language governing permissions and limitations under the License. * * Change History: * * Date Who What * ---- --- ---- */ static String version() { return '0.0.0' } import groovy.transform.Field import java.net.URLEncoder import groovy.json.JsonOutput import groovy.json.JsonSlurper definition ( name: "Daikin DKN Cloud", namespace: "thebearmay", author: "Jean P. May, Jr.", description: "Daikin DKN Cloud interface - handles all communication with the Daikin Cloud.", category: "Utility", importUrl: "https://raw.githubusercontent.com/thebearmay/hubitat/main/daikin/daikinCloud/dknCloudApp.groovy", installOnOpen: true, oauth: true, iconUrl: "", iconX2Url: "" ) preferences { page name: "mainPage" page name: "localInfo" page name: "dknCredentials" } mappings { path("/dnkAuth") { action: [POST: "authRequest"] } } void installed() { if(debugEnabled) log.trace "installed()" state?.isInstalled = true initialize() } void updated(){ if(debugEnabled) log.trace "updated()" if(!state?.isInstalled) { state?.isInstalled = true } if(debugEnabled) runIn(1800,logsOff) } void initialize(){ } void logsOff(){ app.updateSetting("debugEnabled",[value:"false",type:"bool"]) } def mainPage(){ dynamicPage (name: "mainPage", title: "", install: true, uninstall: true) { if (app.getInstallationState() == 'COMPLETE') { section("Main") { href "localInfo", title: "Setup Server Information", required: false input "debugEnabled", "bool", title:"Enable Debug Logging:", submitOnChange:true, required:false, defaultValue:false if(debugEnabled) { unschedule() runIn(1800,logsOff) } } section("Reset Application Name", hideable: true, hidden: true){ input "nameOverride", "text", title: "New Name for Application", multiple: false, required: false, submitOnChange: true, defaultValue: app.getLabel() if(nameOverride != app.getLabel) app.updateLabel(nameOverride) } } else { section("") { paragraph title: "Click Done", "Please click Done to install app before continuing" } } } } def localInfo(){ dynamicPage (name: "localInfo", title: "", install: false, uninstall: false) { section("Local Hub Information", hideable: false, hidden: false){ //paragraph "Local Server API: ${getFullLocalApiServerUrl()}" paragraph "Cloud Server API: ${getFullApiServerUrl()}" if(state.accessToken == null) createAccessToken() paragraph "Access Token: ${state.accessToken}" input "resetToken", "button", title:"Reset Token" paragraph """From the Daikin DNK Cloud Setup

Before using Oauth, the third party client application must be registered with the Open API service, where the following details of the application must be provided: • Application Name • List of Redirect URI or Callback URL

The redirect URI is where the Open API will redirect the user after they authorize (or deny) the application, and therefore the part of the application that will handle authorization codes or access tokens. This must be seen as a list of valid URLs. As we will see next, on the authentication process using a web interface based flow, the application must specify a redirect URL. This URL must match one of the registered URLS. This is a safety measure used to ensure that the user will only be directed to appropriate locations.

As of now, this registration is done exclusively on demand, and requires manual interaction of the system administrator of the DKN Cloud NA ecosystem.


""" paragraph "Redirect URL path for Daikin: ${getFullApiServerUrl()}/dnkAuth?access_code=${state.accessToken}" href "dknCredentials",title: "AFTER you have registered this application with the Daikin DKN administrator go here to enter all credentials" } } } def dknCredentials(){ dynamicPage (name: "dknCredentials", title: "", install: false, uninstall: false, nextPage:"mainPage") { section("Application Credentials", hideable: false, hidden: false){ input "dknUserName", "text", title: "Daikin User Name:" input "dknPassword", "password", title: "Daikin Password:" input "dknAppName", "text", title: "App Name Registered with Daikin:" paragraph "These come from the Daikin DKN Administrator" input "dknCode", "text", title: "Daikin Client Code:" input "dknClientId", "text", title: "Daikin Client ID:" input "dknClientSecret", "text", title: "Daikin Client Secret:" input "authBtn", "button", title: "Create Initial Authorization" } } } //Begin App Authorization def authRequest() { log.warn "Unexpected endpoint access: ${request.data}" jsonText = JsonOutput.toJson([value: "AuthReq: Acknowledged"]) render contentType:'application/json', data: "$jsonText", status:200 } void intialAuth(){ command = "auth/login/dknUsa" bodyMap = [email:"$dknUserName", password:"$dknPassword"] def bodyText = JsonOutput.toJson(bodyMap) Map requestParams = [ uri: "https://www.dkncloudna.com/api/v1/$command", requestContentType: 'application/json', contentType: 'application/json', body: "$bodyText" ] if(debugEnabled) log.debug "$requestParams" asynchttpPost("getResp", requestParams, [cmd:"${command}"]) } void authReq2(command, bodyMap){ def bodyText = JsonOutput.toJson(bodyMap) Map requestParams = [ uri: "https://www.dkncloudna.com/api/v1/$command", requestContentType: 'application/json', contentType: 'application/json', Authorization: "Bearer $state.temp_token", body: "$bodyText" ] if(debugEnabled) log.debug "$requestParams" asynchttpPost("getResp", requestParams, [cmd:"${command}"]) } void tokenRequest(command, bodyMap){ def bodyText = "" bodyMap.each { if(bodyText !="") bodyText += "&" bodyText+=URLEncoder.encode(it.key, "UTF-8") bodyText+="=" bodyText+=URLEncoder.encode(it.value, "UTF-8") } Map requestParams = [ uri: "https://www.dkncloudna.com/api/v1/$command", requestContentType: 'application/json', contentType: 'application/x-www-form-urlencoded', body: "$bodyText" ] if(debugEnabled) log.debug "$requestParams" asynchttpPost("getResp", requestParams, [cmd:"${command}"]) } void tokenRefresh() { tokenRequest("open/oauth2/token",[client_id:"$dknClientId",client_secret:"$dknClientSecret",grant_type:"refresh_token",code:"$dknCode"]) } void getResp(resp, data) { try { if(debugEnabled) log.debug "$resp.properties - ${data['cmd']} - ${resp.getStatus()}" if(resp.getStatus() == 200 || resp.getStatus() == 207){ if(resp.data){ if(data.cmd == "auth/login/dknUsa"){ // response from initial auth request jsonData = (HashMap) resp.JSON state.temp_token = jsonData.token authReq2("users/oauth2/authorize",[entityName:"$dknAppName",client_id:"$dknClientId", scopes:"devices, installations"]) } else if (data.cmd =="users/oauth2/authorize"){ jsonData = (HashMap) resp.JSON state.dknAuthCode = jsonData.redirectUri.substring(jsonData.redirectUri.indexOf("=")+1) tokenRequest("open/oauth2/token",[client_id:"$dknClientId",client_secret:"$dknClientSecret",grant_type:"authorization_code",code:"$dknCode"]) } else if (data.cmd =="users/oauth2/token"){ jsonData = (HashMap) resp.JSON state.dknAccessToken = jsonData.access_token state.dknRefreshToken = jsonData.refresh_token state.dknTokenTimeStamp = new Date() // Now get the device list apiGet("devices",[]) } } else atomicState.returnString = "{\"status\":\"${resp.getStatus()}\"}" } } catch (Exception ex) { log.error "getResp - $ex.message" } } // End App Authorization // Begin API void apiGet (command, bodyMap){ def bodyText = JsonOutput.toJson(bodyMap) Map requestParams = [ uri: "https://dkncloudna.com/api/v1/open/$command", requestContentType: 'application/json', contentType: 'application/json', Authorization: "Bearer $state.dknAccessToken", body: "$bodyText" ] if(debugEnabled) log.debug "$requestParams" asynchttpGet("getApi", requestParams, [cmd:"${command}"]) } void getApi(resp, data){ try { if(debugEnabled) log.debug "$resp.properties - ${data['cmd']} - ${resp.getStatus()}" if(resp.getStatus() == 200 || resp.getStatus() == 207){ if(resp.data){ if(data.cmd == "devices"){ jsonData = (HashMap) resp.JSON state.siteId = jsonData._id state.siteName = jsonData.name unitTran = ['C', 'F'] state.siteUnit = unitTran[jsonData.Units.toInteger()] jsonData.devices.each{ createChildDev(it.name, it.mac) } } else if(data.cmd.indexOf("$state.siteId")!= -1) { mac = data.cmd.substring(data.cmd.lastIndexOf("/")+1) macStrip = mac.replace(":","") cd = getChildDevice("${device.deviceNetworkId}-${macStrip}") cd.updState("${resp.JSON}") } } } } catch (Exception e) { log.error "getApi - $e.message" } } void apiPut (command, bodyMap){ def bodyText = JsonOutput.toJson(bodyMap) Map requestParams = [ uri: "https://dkncloudna.com/api/v1/open/${state.siteId}/$command", requestContentType: 'application/json', contentType: 'application/json', Authorization: "Bearer $state.dknAccessToken", body: "$bodyText" ] if(debugEnabled) log.debug "$requestParams" httpPut(requestParams, [cmd:"${command}",bMap:bodyMap]) {resp -> if (resp.getStatus() == 401){ tokenRefresh() pauseExecution(500) apiPut(data.cmd, data.bMap) } else if (resp.getStatus == 400) { log.error "${resp.JSON}" }else { mac = data.cmd.substring(0,17) macStrip = mac.replace(":","") cd = getChildDevice("${device.deviceNetworkId}-${macStrip}") cd.updState("${resp.JSON}") } } } // End API void createChildDev(name, mac){ macStrip = mac.replace(":","") cd = addChildDevice("thebearmay", "Daikin Cloud Device", "${device.deviceNetworkId}-$macStrip", [name: "${name}", isComponent: true, mac:"$mac", label:"dcd$name"]) apiGet("${state.siteId}/${cd.properties.data["${mac}"]}") } void appButtonHandler(btn) { switch(btn) { case ("authBtn"): initialAuth() break default: if(debugEnabled) log.error "Undefined button $btn pushed" break } } void intialize() { } void uninstalled(){ chdList = getChildDevices() chdList.each{ deleteChildDevice(it.getDeviceNetworkId()) } }