Compare commits

...

1 Commits

Author SHA1 Message Date
Ko Sang Ju
8570c84d37 MSA-835: Navien Room Controller 2016-01-21 23:10:55 -06:00
2 changed files with 1016 additions and 0 deletions

View File

@@ -0,0 +1,348 @@
/**
* Navien Thermostat
*
* Author: Navien Within
* Date: 2015-11-02
*/
metadata {
definition (name: "Navien Room Controller", namespace: "smartthings", author: "Navien") {
capability "Thermostat"
capability "Polling"
capability "Refresh"
command "generateEvent"
command "powerONOFF"
command "setRoomTemp"
command "setOndolTemp"
command "controlMode1"
command "controlMode2"
attribute "setRoomSlider", "NUMBER"
attribute "setOndolSlider", "NUMBER"
}
// simulator metadata
simulator { }
//tiles(scale: 2) {
tiles {
/*
multiAttributeTile(name: "temperature", type: "thermostat", canChangeIcon: true, canChangeBackground: true) {
tileAttribute("temperature", key: "PRIMARY_CONTROL") {
attributeState "temperature", label: '${currentValue}°', icon:"st.Home.home1", backgroundColor:"#FFA81E"
}
tileAttribute ("statusText", key: "SECONDARY_CONTROL") {
attributeState "statusText", label: 'statusText'
}
tileAttribute("button", key: "VALUE_CONTROL"){
attributeState "setUp"
attributeState "setDown"
}
}
*/
valueTile("thermostatStatus", "device.thermostatStatus", width: 3, height: 2) {
state "전원 OFF", label:'${currentValue}', icon:"st.Navien.bgs_power_off", backgroundColor:"#BDBDBD"
state "외출 ON", label:'${currentValue}', icon:"st.Navien.bgs_out", backgroundColor:"#FF8C17"
state "실내난방", label:'${currentValue}', icon:"st.Navien.bgs_indoor", backgroundColor:"#FF8C17"
state "온돌난방", label:'${currentValue}', icon:"st.Navien.bgs_ondol", backgroundColor:"#FF8C17"
state "반복예약난방", label:'${currentValue}', icon:"st.Navien.bgs_heating_again", backgroundColor:"#FF8C17"
state "24시간예약난방", label:'${currentValue}', icon:"st.Navien.bgs_24heat", backgroundColor:"#FF8C17"
state "간편예약난방", label:'${currentValue}', icon:"st.Navien.bgs_heat", backgroundColor:"#FF8C17"
state "온수전용", label:'${currentValue}', icon:"st.Navien.bgs_water", backgroundColor:"#FF8C17"
state "빠른온수", label:'${currentValue}', icon:"st.Navien.bgs_water_fast", backgroundColor:"#FF8C17"
}
valueTile("temperature", "device.temperature", width: 1, height: 1, inactiveLabel: false) {
state "OFF", label:'', unit:"C", icon:"st.Navien.bg_recent_off"
state "default", label:'${currentValue}°', unit:"C", icon:"st.Navien.bg_recent"
}
valueTile("hotWater", "device.hotWater", width: 1, height: 1) {
state "OFF", label:'', unit:"C", icon:"st.Navien.bg_water_off"
state "default", label:'${currentValue}°', unit:"C", icon:"st.Navien.bg_water"
}
standardTile("refresh", "device.refresh", , width: 1, height: 1, inactiveLabel: false) {
state "default", action:"refresh.refresh", icon:"st.Navien.but_refresh"
}
valueTile("setRoomTemp", "device.setRoomTemp", width: 1, height:1) {
state "OFF", label:'', icon:"st.Navien.bg_indoor_off"
state "default", label:'${currentValue}°', icon:"st.Navien.bg_indoor"
}
controlTile("setRoomSlider", "device.setRoomSlider", "slider", height: 1, width: 2, inactiveLabel: false, range:"(10..40)") {
state "default", action:"setRoomTemp", backgroundColor:"#F08C00"
}
valueTile("setOndolTemp", "device.setOndolTemp", width: 1, height:1) {
state "OFF", label:'', icon:"st.Navien.bg_ondol"
state "default", label:'${currentValue}°', icon:"st.Navien.bg_ondol_off"
}
controlTile("setOndolSlider", "device.setOndolSlider", "slider", height: 1, width: 2, inactiveLabel: false, range:"(40..83)") {
state "default", action:"setOndolTemp", backgroundColor:"#F08C00"
}
standardTile("power", "device.power", width: 1, height: 1, inactiveLabel: false) {
state "ON", label:'', action:"powerONOFF", icon:"st.Navien.but_power"
state "default", label:'', action:"powerONOFF", icon:"st.Navien.but_power_off"
}
standardTile("controlMode1", "device.controlMode1", width: 1, height: 1, inactiveLabel: false) {
state "실내난방 ON", label:'', action:"controlMode1", icon:"st.Navien.but_indoor"
state "온돌난방 ON", label:'', action:"controlMode1", icon:"st.Navien.but_ondol"
state "default", label:'', icon:"st.Navien.but_indoor_off"
}
standardTile("controlMode2", "device.controlMode2", width: 1, height: 1, inactiveLabel: false) {
state "외출해제", label:'', action:"controlMode2", icon:"st.Navien.but_out_off"
state "외출설정", label:'', action:"controlMode2", icon:"st.Navien.but_out"
state "default", label:'', icon:"st.Navien.but_out_dis"
}
valueTile("herotile", "device.herotile", width: 3, height:1) {
state "default", label:'', icon:"st.Navien.bg_herotile"
}
main "thermostatStatus"
details(["thermostatStatus", "temperature", "hotWater", "refresh", "setRoomTemp", "setRoomSlider", "setOndolTemp", "setOndolSlider", "power", "controlMode1", "controlMode2", "herotile"])
}
}
def powerONOFF()
{
log.debug "powerONOFF called"
def powerStatus = device.currentValue("power")
def results
if(powerStatus == "ON")
{
results = parent.childRequest(this, "1", "33", "0")
}
else if(powerStatus == "전원 OFF")
{
results = parent.childRequest(this, "1", "34", "0")
}
generateEvent(results)
log.debug "powerONOFF ended"
}
def setRoomTemp(degrees)
{
def degreesInteger = degrees as Integer
sendEvent("name":"setRoomSlider", "value":degreesInteger)
def status = device.currentValue("thermostatStatus")
if(status == "실내난방")
{
def results = parent.childRequest(this, "1", "44", degreesInteger*2)
generateEvent(results)
}
}
def setOndolTemp(degrees)
{
def degreesInteger = degrees as Integer
sendEvent("name":"setOndolSlider", "value":degreesInteger)
def status = device.currentValue("thermostatStatus")
if(status == "온돌난방")
{
def results = parent.childRequest(this, "1", "36", degreesInteger*2)
generateEvent(results)
}
}
def controlMode1()
{
log.debug "controlMode1"
def control = device.currentValue("controlMode1")
controlMode(control)
}
def controlMode2()
{
log.debug "controlMode2"
def control = device.currentValue("controlMode2")
controlMode(control)
}
def controlMode(control)
{
if(control == "실내난방 ON")
{
log.debug "실내난방 ON : 제어"
def value = device.currentValue("setRoomSlider")*2
def results = parent.childRequest(this, "1", "43", value)
generateEvent(results)
}
else if(control == "온돌난방 ON")
{
log.debug "온돌난방 ON : 제어"
def value = device.currentValue("setOndolSlider")*2
def results = parent.childRequest(this, "1", "35", value)
generateEvent(results)
}
else if(control == "외출해제")
{
log.debug "외출해제 : 제어"
def results = parent.childRequest(this, "1", "46", "0")
generateEvent(results)
}
else if(control == "외출설정")
{
log.debug "외출설정 : 제어"
def results = parent.childRequest(this, "1", "46", "1")
generateEvent(results)
}
}
def refresh()
{
log.debug "refresh called"
poll()
log.debug "refresh ended"
}
void poll() {
log.debug "Executing 'poll' using parent SmartApp"
def results = parent.pollChild(this)
generateEvent(results)
}
void generateEvent(Map results)
{
log.debug "parsing data $results"
if(results)
{
results.each { name, value ->
def linkText = getLinkText(device)
def isChange = true
def isDisplayed = true
if (name=="temperature" || name=="hotWater")
{
//isChange = isTemperatureStateChange(device, name, value.toString())
isDisplayed = isChange
sendEvent(
name: name,
value: value,
unit: "C",
linkText: linkText,
descriptionText: getThermostatDescriptionText(name, value, linkText),
handlerName: name,
isStateChange: isChange,
displayed: isDisplayed)
}
else if (name=="thermostatStatus")
{
isChange = isStateChange(device, name, value.toString())
isDisplayed = isChange
sendEvent(
name: name,
value: value.toString(),
linkText: linkText,
descriptionText: getThermostatDescriptionText(name, value, linkText),
handlerName: name,
isStateChange: isChange,
displayed: isDisplayed)
}
}
generateStatusEvent(results)
}
}
private getThermostatDescriptionText(name, value, linkText)
{
if(name == "temperature")
{
return "$linkText was $value°C"
}
else if(name == "roomTemp")
{
return "latest roomTemp setpoint was $value°C"
}
else if(name == "ondolTemp")
{
return "latest ondolTemp setpoint was $value°C"
}
else if (name == "thermostatMode")
{
return "thermostat mode is ${value}"
}
else
{
return "${name} = ${value}"
}
}
def generateStatusEvent(Map results) {
log.debug "generateStatusEvent"
def status = results.thermostatStatus
def setRoomTemp = results.roomTemp
def setOndolTemp = results.ondolTemp
log.debug "status ===> ${status}"
if (status == "전원 OFF") {
sendEvent("name":"temperature", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
sendEvent("name":"hotWater", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
sendEvent("name":"setRoomTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
sendEvent("name":"setRoomSlider", "value":"0", displayed: true, isStateChange: true)
sendEvent("name":"setOndolTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
sendEvent("name":"setOndolSlider", "value":"0", displayed: true, isStateChange: true)
sendEvent("name":"power", "value":"ON", "description":"전원 ON", displayed: true, isStateChange: true)
sendEvent("name":"controlMode1", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
sendEvent("name":"controlMode2", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
}else if(status == "온수전용"){
sendEvent("name":"setRoomTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
sendEvent("name":"setRoomSlider", "value":"0", displayed: true, isStateChange: true)
sendEvent("name":"setOndolTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
sendEvent("name":"setOndolSlider", "value":"0", displayed: true, isStateChange: true)
sendEvent("name":"power", "value":"전원 OFF", "description":"OFF", displayed: true, isStateChange: true)
sendEvent("name":"controlMode1", "value":"실내 ON", "description":"ON", displayed: true, isStateChange: true)
sendEvent("name":"controlMode2", "value":"온돌 ON", "description":"ON", displayed: true, isStateChange: true)
}else if(status == "외출 ON"){
sendEvent("name":"setRoomTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
sendEvent("name":"setRoomSlider", "value":"0", displayed: true, isStateChange: true)
sendEvent("name":"setOndolTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
sendEvent("name":"setOndolSlider", "value":"0", displayed: true, isStateChange: true)
sendEvent("name":"power", "value":"전원 OFF", "description":"OFF", displayed: true, isStateChange: true)
sendEvent("name":"controlMode1", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
sendEvent("name":"controlMode2", "value":"외출해제", "description":"외출해제", displayed: true, isStateChange: true)
}else if(status == "실내난방"){
sendEvent("name":"setRoomTemp", "value":"${setRoomTemp}", "description":"설정온도", displayed: true, isStateChange: true)
sendEvent("name":"setRoomSlider", "value":setRoomTemp, displayed: true, isStateChange: true)
sendEvent("name":"setOndolTemp", "value":"${setOndolTemp}", "description":"설정온도", displayed: true, isStateChange: true)
sendEvent("name":"setOndolSlider", "value":setOndolTemp, displayed: true, isStateChange: true)
sendEvent("name":"power", "value":"전원 OFF", "description":"OFF", displayed: true, isStateChange: true)
sendEvent("name":"controlMode1", "value":"온돌난방 ON", "description":"온돌난방 ON", displayed: true, isStateChange: true)
sendEvent("name":"controlMode2", "value":"외출설정", "description":"외출설정", displayed: true, isStateChange: true)
}
else if(status == "온돌난방"){
sendEvent("name":"setRoomTemp", "value":"${setRoomTemp}", "description":"설정온도", displayed: true, isStateChange: true)
sendEvent("name":"setRoomSlider", "value":setRoomTemp, displayed: true, isStateChange: true)
sendEvent("name":"setOndolTemp", "value":"${setOndolTemp}", "description":"설정온도", displayed: true, isStateChange: true)
sendEvent("name":"setOndolSlider", "value":setOndolTemp, displayed: true, isStateChange: true)
sendEvent("name":"power", "value":"전원 OFF", "description":"OFF", displayed: true, isStateChange: true)
sendEvent("name":"controlMode1", "value":"실내난방 ON", "description":"실내난방 ON", displayed: true, isStateChange: true)
sendEvent("name":"controlMode2", "value":"외출설정", "description":"외출설정", displayed: true, isStateChange: true)
}
log.debug "Generate Status Event = ${status}"
}

View File

@@ -0,0 +1,668 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* Navien Service Manager
*
* Author: sangju
* Date: 2015-11-01
*
*/
definition(
name: "나비엔 스마트톡 연동",
namespace: "smartthings",
author: "나비엔 스마트톡",
description: "SmartThings에서 나비엔 스마트톡의 온도조절기를 연결합니다.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Navien/navien.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Navien/navien@2x.png",
singleInstance: true
) {
appSetting "clientId"
appSetting "serverUrl"
appSetting "bcd"
}
preferences {
page(name: "loginPage", title: "나비엔 스마트톡 등록")
page(name: "navienAuth", title: "나비엔 스마트톡 등록")
page(name: "navienDeviceList", title: "나비엔 스마트톡 등록", install: true )
}
def refreshToken
def authToken
def userName
def loginPage(){
log.debug "authPage()"
def showUninstall = username != null && password != null
return dynamicPage(name: "loginPage", title: "경동나비엔", nextPage:"navienAuth", uninstall:false) {
section("나비엔 스마트톡 등록"){
input "username", "text", title: "나비엔 스마트톡 아이디", required: true, autoCorrect:false
input "password", "password", title: "나비엔 스마트톡 패스워드", required: true, autoCorrect:false
}
//section("To use Navien, SmartThings encrypts and securely stores your Navien credentials.") {}
}
}
def navienAuth(){
log.debug "navienAuth()"
def loginResult = forceLogin()
if(loginResult.success)
{
return dynamicPage(name: "navienAuth", title: "나비엔 스마트톡 인증", nextPage:"navienDeviceList", uninstall:false) {
section(){
paragraph "나비엔 스마트톡 인증 성공"
}
}
}
else
{
return dynamicPage(name: "navienAuth", title: "나비엔 스마트톡 인증", nextPage:null, uninstall:false) {
section("Login failed"){
paragraph "나비엔 스마트톡 인증 실패"
}
}
}
}
def navienDeviceList(){
log.debug "navienDeviceList()"
def connectResult = navienConnecting()
def p
if(connectResult.success)
{
statusSetting(state.status)
def stats = getNavienThermostats()
log.debug "device list: $stats"
p = dynamicPage(name: "navienDeviceList", title: "나비엔 스마트톡 선택", install:true) {
section(""){
paragraph "나비엔 스마트톡 계정에서 사용할 수 있는 온도 조절 장치의 목록을 확인하고 SmartTings에 연결하려는 목록을 선택하려면 아래에서 선택해 주세요."
input(name: "thermostats", title:"", type: "enum", required:true, multiple:true, description: "Tap to choose", metadata:[values:stats])
}
}
}
else
{
p = dynamicPage(name: "navienDeviceList", title: "나비엔 스마트톡 선택", nextPage:null, uninstall:false) {
section("나비엔 스마트톡 온도 조절 장치의 연결 상태를 확인하시기 바랍니다."){
paragraph ""
}
}
}
log.debug "list p: $p"
return p
}
def getNavienThermostats() {
def stats = [:]
def dni = [getChildName()].join('.')
if(state.boilerType == "01") stats[dni] = "스마트톡"
else if(state.boilerType == "02") stats[dni] = "콘덴싱톡"
else stats[dni] = "----"
return stats
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def uninstalled() {
def devices = getChildDevices()
if(devices != null) log.trace "deleting ${devices.size()} device"
}
def initialize() {
log.debug "initialize"
def d = getChildDevice(getChildName())
if(!d)
{
d = addChildDevice(getChildNamespace(), getChildName(), getChildName())
log.debug "created ${d.displayName} with id $dni"
}
else
{
log.debug "found ${d.displayName} with id $dni already exists"
}
def devices = d
log.debug "created ${devices.size()} thermostats"
def delete
// Delete any that are no longer in settings
if(!thermostats)
{
log.debug "If delete thermostats"
delete = getAllChildDevices()
}
else
{
log.debug "Else delete thermostats"
if(it != null) delete = getChildDevices().findAll { !thermostats.contains(it.deviceNetworkId) }
}
if(delete != null) log.debug "deleting ${delete.size()} thermostats"
if(it != null) delete.each { deleteChildDevice(it.deviceNetworkId) }
atomicState.thermostatData = [:]
pollHandler()
}
def pollHandler() {
log.debug ("pollHandler.")
pollChildren()
atomicState.thermostats.each {stat ->
def dni = stat.key
log.debug ("DNI = ${dni}")
debugEvent ("DNI = ${dni}")
def d = getChildDevice(dni)
if(d)
{
log.debug ("Found Child Device.")
debugEvent ("Found Child Device.")
debugEvent("Event Data before generate event call = ${stat}")
d.generateEvent(atomicState.thermostats[dni].data)
}
}
}
def pollChildren()
{
log.trace "polling children"
def pollParams = [
uri: getServerUrl()+"/api/SmartTokApi?bcd="+getBCD()+"&mid=${state.mid}&did=36&subid=1&cmd=01&data=0",
headers: ["Authorization": "Bearer ${state.authToken}"]
]
log.trace "Before HTTPGET to navien."
def jsonData
try{
httpGet(pollParams) { resp ->
if (resp.status == 200)
{
log.debug "poll results returned"
//atomicState.thermostats = resp.data.Status.inject([:]) { collector, stat ->
atomicState.thermostats = "1".inject([:]) { collector, stat ->
def dni = [getChildName()].join('.')
log.debug "updating dni $dni"
def data = statusSetting(resp.data.Status)
log.debug ("Event Data = ${data}")
collector[dni] = [data:data]
return collector
}
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
}
else
{
log.error "polling children & got http status ${resp.status}"
//refresh the auth token
if(resp.status == 400)
{
log.debug "Bad Request Description"
}
else if(resp.status == 401)
{
log.debug "Unauthorized Description"
}
else if(resp.status == 500)
{
log.debug "InternalServerError Description"
atomicState.action = "pollChildren";
refreshAuthToken()
}
else
{
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
}
}
}
}
catch(all)
{
log.debug "___exception polling children: "
//refreshAuthToken()
}
}
def getPollRateMillis() { return 2 * 60 * 1000 }
def pollChild( child )
{
log.debug "poll child"
debugEvent ("poll child")
def now = new Date().time
log.debug "now ====> ${now}"
debugEvent ("Last Poll Millis = ${atomicState.lastPollMillis}")
def last = atomicState.lastPollMillis ?: 0
def next = last + pollRateMillis
log.debug "pollChild( ${child.device.deviceNetworkId} ): $now > $next ?? w/ current state: ${atomicState.thermostats}"
debugEvent ("pollChild( ${child.device.deviceNetworkId} ): $now > $next ?? w/ current state: ${atomicState.thermostats}")
// if( now > next )
if( true ) // for now let's always poll/refresh
{
log.debug "polling children because $now > $next"
debugEvent("polling children because $now > $next")
pollChildren()
log.debug "polled children and looking for ${child.device.deviceNetworkId} from ${atomicState.thermostats}"
debugEvent ("polled children and looking for ${child.device.deviceNetworkId} from ${atomicState.thermostats}")
def currentTime = new Date().time
debugEvent ("Current Time = ${currentTime}")
atomicState.lastPollMillis = currentTime
def tData = atomicState.thermostats[child.device.deviceNetworkId]
if(!tData)
{
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
// TODO: flag device as in error state
// child.errorState = true
return null
}
tData.updated = currentTime
return tData.data
}
else if(atomicState.thermostats[child.device.deviceNetworkId] != null)
{
log.debug "not polling children, found child ${child.device.deviceNetworkId} "
def tData = atomicState.thermostats[child.device.deviceNetworkId]
if(!tData.updated)
{
// we have pulled new data for this thermostat, but it has not asked us for it
// track it and return the data
tData.updated = new Date().time
return tData.data
}
return null
}
else if(atomicState.thermostats[child.device.deviceNetworkId] == null)
{
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
// TODO: flag device as in error state
// child.errorState = true
return null
}
else
{
// it's not time to poll again and this thermostat already has its latest values
}
return null
}
def childRequest( child, subid, cmd, data )
{
getControlSend(subid, cmd, data)
def tData = atomicState.thermostats[child.device.deviceNetworkId]
if(!tData)
{
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
return null
}
tData.updated = currentTime
return tData.data
}
def getControlSend(subid, cmd, data)
{
log.trace "getParams"
def pollParams = [
uri: getServerUrl()+"/api/SmartTokApi?bcd="+getBCD()+"&mid=${state.mid}&did=36&subid=${subid}&cmd=${cmd}&data=${data}",
headers: ["Authorization": "Bearer ${state.authToken}"]
]
log.trace "Before Control HTTPGET to navien."
def jsonData
try{
httpGet(pollParams) { resp ->
debugEvent ("Response (resp.data.Staus) : = ${resp.data.Staus}", true)
if (resp.status == 200)
{
log.debug "poll results returned"
//atomicState.thermostats = resp.data.Status.inject([:]) { collector, stat ->
atomicState.thermostats = "1".inject([:]) { collector, stat ->
def dni = [getChildName()].join('.')
log.debug "updating dni $dni"
def response = statusSetting(resp.data.Status)
log.debug ("Event Data = ${response}")
collector[dni] = [data:response]
return collector
}
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
}
else
{
log.error "polling children & got http status ${resp.status}"
//refresh the auth token
if(resp.status == 400)
{
log.debug "Bad Request Description"
}
else if(resp.status == 401)
{
log.debug "Unauthorized Description"
}
else if(resp.status == 500)
{
log.debug "InternalServerError Description"
atomicState.action = "pollChildren";
refreshAuthToken()
}
else
{
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
}
}
}
}
catch(all)
{
log.debug "___exception polling children: "
//refreshAuthToken()
}
}
private refreshAuthToken() {
log.trace "refreshing auth token"
if(!atomicState.refreshToken) {
log.warn "Can not refresh OAuth token since there is no refreshToken stored"
} else {
def refreshParams = [
uri: getServerUrl(),
path: "/Token",
headers: ['Content-Type': "application/x-www-form-urlencoded"],
body: [grant_type: "refresh_token", refresh_token: "${state.refreshToken}"]
]
log.debug refreshParams
try {
def jsonMap
httpPost(refreshParams) { resp ->
if(resp.status == 200) {
log.debug "Token refreshed...calling saved RestAction now! ${resp}"
if(resp.data)
{
jsonMap = resp.data
state.refreshToken = jsonMap.refresh_token
state.authToken = jsonMap.access_token
if(atomicState.action && atomicState.action != "") {
log.debug "Executing next action: ${atomicState.action}"
"{atomicState.action}"()
//remove saved action
atomicState.action = ""
}
}
atomicState.action = ""
}
else
{
log.debug "refresh failed ${resp.status} : ${resp.status.code}"
}
}
}
catch(Exception e) {
log.debug "caught exception refreshing auth token: " + e.getStackTrace()
}
}
}
def statusSetting(status){
/*
log.debug "state.status ====> ${state.status}"
log.debug "제품아이디 ====> ${state.status.substring(0, 16)}" // 제품아이디
log.debug "보일러모델타입 ====> ${state.status.substring(26, 28)}" // 보일러모델타입
log.debug "에러코드 ====> ${state.status.substring(32, 36)}" // 에러코드
log.debug "온수설정온도 ====> ${state.status.substring(36, 38)}" // 온수설정온도
log.debug "state.status ====> ${state.status.substring(38, 40)}" // 난방세기
log.debug "state.status ====> ${state.status.substring(40, 42)}" // 옵션기능
log.debug "작동모드 ====> ${state.status.substring(42, 44)}" // 작동모드
log.debug "현재실내온도 ====> ${state.status.substring(44, 46)}" // 현재실내온도
log.debug "실내난방설정온도 ====> ${state.status.substring(46, 48)}" // 실내난방설정온도
log.debug "온돌난방설정온도 ====> ${state.status.substring(48, 50)}" // 온돌난방설정온도
*/
state.mid = status.substring(0, 16)
state.boilerType = status.substring(26, 28)
state.errorCode = status.substring(32, 36)
state.hotWater = convertHexToInt(status.substring(36, 38))
def s = status.substring(42, 44)
if(s == "01") state.thermostatStatus = "전원 OFF"
else if(s == "02") state.thermostatStatus = "외출 ON"
else if(s == "03") state.thermostatStatus = "실내난방"
else if(s == "04") state.thermostatStatus = "온돌난방"
else if(s == "05") state.thermostatStatus = "반복예약난방"
else if(s == "06") state.thermostatStatus = "24시간예약난방"
else if(s == "07") state.thermostatStatus = "간편예약난방"
else if(s == "08") state.thermostatStatus = "온수전용"
else if(s == "09") state.thermostatStatus = "빠른온수"
else state.thermostatStatus = "---"
state.temperature = convertHexToInt(status.substring(44, 46))
state.roomTemp = convertHexToInt(status.substring(46, 48))
state.ondolTemp = convertHexToInt(status.substring(48, 50))
def data = [
mid: state.mid,
boilerType: state.boilerType,
errorCode: state.errorCode,
hotWater: state.hotWater,
thermostatStatus: state.thermostatStatus,
temperature: state.temperature,
roomTemp: state.roomTemp,
ondolTemp: state.ondolTemp
]
return data
}
def navienConnecting(){
log.debug "navienConnecting()"
def connectParams = [
uri: getServerUrl()+"/api/SmartTokApi?bcd="+getBCD()+"&uid=${state.userName}&scd=2",
headers: ["Authorization": "Bearer ${state.authToken}"]
]
def result = [success:false]
def jsonData
httpGet(connectParams) { resp ->
if (resp.status == 200)
{
jsonData = resp.data
result.success = true
state.status = jsonData.Status
}
else if(resp.status == 400)
{
result.reason = "Bad Request"
}
else if(resp.status == 401)
{
result.reason = "Unauthorized"
}
else if(resp.status == 500)
{
result.reason = "Internal ServerError"
}
else
{
result.reason = "Bad Connect"
}
}
return result
}
private forceLogin(){
log.debug "forceLogin()"
updateCookie(null)
login()
}
private updateCookie(String cookie){
atomicState.cookie = cookie
state.cookie = cookie
}
private login(){
if(getCookieValueIsValid())
{
return [success:true]
}
return doLogin()
}
private doLogin() {
log.debug "doLogin()"
def loginParams = [
uri: getServerUrl(),
path: "/Token",
headers: ['Content-Type': "application/x-www-form-urlencoded"],
body: [grant_type: "password", username: username, password: password]
]
def result = [success:false]
def jsonMap
try
{
httpPost(loginParams) { resp ->
if (resp.status == 200 && resp.headers.'Content-Type'.contains("application/json"))
{
log.debug "login 200 json headers: " + resp.headers.collect { "${it.name}:${it.value}" }
def cookie = resp?.headers?.'Set-Cookie'?.split(";")?.getAt(0)
if (cookie) {
log.debug "login setting cookie to $cookie"
updateCookie(cookie)
result.success = true
jsonMap = resp.data
state.refreshToken = jsonMap.refresh_token
state.authToken = jsonMap.access_token
state.userName = jsonMap.userName
}
else
{
// ERROR: any more information we can give?
result.reason = "Bad login"
}
}
else
{
result.reason = "Bad login"
}
}
}
catch(groovyx.net.http.HttpResponseException hre)
{
result.reason = "Exception"
}
return result
}
private Boolean getCookieValueIsValid()
{
// TODO: make a call with the cookie to verify that it works
return getCookieValue()
}
private getCookieValue(){
state.cookie
}
def getChildNamespace() { "smartthings" }
def getChildName() { "Navien Room Controller" }
def getChildDeviceIdsString()
{
log.debug "thermostats ====> ${thermostats}"
return thermostats.collect { it.split(/\./).last() }.join(',')
}
def getServerUrl() { return appSettings.serverUrl }
def getSmartThingsClientId() { return appSettings.clientId }
def getBCD() { return appSettings.bcd }
def debugEvent(message, displayEvent = true) {
def results = [
name: "appdebug",
descriptionText: message,
displayed: displayEvent
]
log.debug "Generating AppDebug Event: ${results}"
sendEvent (results)
}
private convertHexToInt(hex) {
return (Integer.parseInt(hex,16) / 2)
}