I think I figured out @srj.little's notes and made one edit of my own - (Added off tmode in Thermostat mode to setThermostatMode function so that the dashboard could set off mode: line 409-410). See the full driver code below. Everything is working great for me! Thanks to @srj.little @MikeSas @woodard.thomas and everyone else who worked on this!
Reminder that you need to populate line 28,29,30 with APIkey, email address, and integrator token.
/*
* Daikin One+ Single Thermostat
* https://www.daikinone.com/openapi/documentation/index.html
*
*
* 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
* ---- --- ----
*/
import java.text.SimpleDateFormat
import groovy.json.JsonSlurper
import groovy.json.JsonOutput
import groovy.transform.Field
@SuppressWarnings('unused')
static String version() {return "0.0.9"}
@Field serverPath = "https://integrator-api.daikinskyport.com"
@Field daiApiKey = "Input API Key Here between Quotes"
@Field email = "Email address used for APIkey from Daikin between Quotes"
@Field integratorToken = "Really long integrator token goes here between quotes"
@Field static Map operatingModes = [
"0":"off"
,"1":"heat"
,"2":"cool"
,"3":"auto"
,"4":"emergency heat"
]
@Field static Map fanModes = [
"0":"auto"
,"1":"on"
]
@Field static Map fanCirculateModes = [
"0":"off"
,"1":"always on"
,"2":"on a schedule"
]
metadata {
definition (
name: "Daikin OnePlus Single Thermostat",
namespace: "SeriesOfUnlikelyExplanations",
author: "Tom Woodard",
importUrl:"https://gist.github.com/SeriesOfUnlikelyExplanations/50561c32a92364d220aa895e44dbbbb3#file-gistfile1-txt"
) {
capability "Actuator"
capability "Configuration"
capability "Initialize"
capability "Thermostat"
capability "ThermostatCoolingSetpoint"
capability "ThermostatFanMode"
capability "ThermostatHeatingSetpoint"
capability "ThermostatMode"
capability "ThermostatOperatingState"
attribute "equipmentStatus","number" //???HE - thermostatOperatingState ENUM ["heating", "pending cool", "pending heat", "vent economizer", "idle", "cooling", "fan only"]
attribute "fan","number"
attribute "fanCirculateSpeed","string"
attribute "setpointDelta","number"
attribute "setpointMinimum","number"
attribute "setpointMaximum","number"
attribute "thermostatModeNum","number"
attribute "heatingSetpoint","number"
attribute "coolingSetpoint","number"
attribute "tempOutdoor","number"
attribute "humidity", "number"
attribute "humidOutdoor", "number"
attribute "geofencingAway", "string"
//attribute "locationMap", "string"
command "refresh"
attribute "supportedThermostatFanModes", "JSON_OBJECT"
attribute "supportedThermostatModes", "JSON_OBJECT"
command "setThermostatMode", [[name: "Thermostat mode*",type:"ENUM", description:"Thermostat mode", constraints: operatingModes.collect {k,v -> v}]]
command "setThermostatFanMode", [[name: "Fan mode*",type:"ENUM", description:"Fan mode", constraints: fanModes.collect {k,v -> v}]]
}
}
preferences {
input("useFahrenheit", "bool", title: "Use Fahrenheit", defaultValue:false)
input("pollRate", "number", title: "Thermostat Polling Rate (minutes)\nZero for no polling:", defaultValue:5)
input("debugEnabled", "bool", title: "Enable debug logging?")
}
@SuppressWarnings('unused')
def installed() {
log.trace "${device.displayName} v${version()} installed()"
initialize()
}
def initialize(){
//setSupportedThermostatFanModes(JsonOutput.toJson(["auto","circulate","on"]))
//setSupportedThermostatModes(JsonOutput.toJson(["auto", "cool", "emergency heat", "heat", "off"]))
}
@SuppressWarnings('unused')
def updated(){
log.debug "test"
if(debugEnabled) {
log.debug "updated()"
runIn(1800,"logsOff")
} else
unschedule("logsOff")
log.debug "updating pollRate"
if(pollRate == null)
device.updateSetting("pollRate",[value:5,type:"number"])
if(pollRate > 0){
runIn(pollRate*60,"refresh")
} else
unschedule("refresh")
}
@SuppressWarnings('unused')
def configure() {
if(debugEnabled) log.debug "configure()"
getInitialAttributes()
}
void updateAttr(String aKey, aValue, String aUnit = ""){
sendEvent(name:aKey, value:aValue, unit:aUnit)
}
String getAuth() {
Map requestParams =
[
uri: "${serverPath}/v1/token",
headers: [
'Content-Type': 'application/json',
'x-api-key': "${daiApiKey}"
],
body: JsonOutput.toJson([
email: "${email}",
integratorToken: "${integratorToken}"])
]
authKey=""
if(debugEnabled) log.debug "$requestParams"
httpPost(requestParams) { resp ->
if(debugEnabled) log.debug "$resp.properties"
jsonData = (HashMap) resp.data
authKey = jsonData.accessToken
}
return authKey
}
HashMap getDeviceList(){
HashMap devMap = sendGet("/v1/devices")
return devMap
}
HashMap getDevDetail(id) {
HashMap devDetail = sendGet("/v1/devices/$id")
return devDetail
}
HashMap sendGet(command){
authToken = getAuth()
if(debugEnabled) log.debug "sendGet cmd: $command authToken:$authToken "
Map requestParams =
[
uri: "${serverPath}$command",
headers: [
'Accept': 'application/json',
'x-api-key': "${daiApiKey}",
"Authorization" : "Bearer $authToken"
]
]
if(debugEnabled) log.debug "get parameters $requestParams"
httpGet(requestParams) { resp ->
if(debugEnabled) log.debug "$resp.properties"
jsonData = (HashMap) resp.data
}
if(debugEnabled) log.debug "get JSON $jsonData"
return jsonData
}
void sendPut(command, bodyMap){
authToken = getAuth()
if(debugEnabled) log.debug "sendPut cmd: $command body: $bodyMap authToken:$authToken "
def bodyText = JsonOutput.toJson(bodyMap)
Map requestParams =
[
uri: "${serverPath}$command",
requestContentType: 'application/json',
contentType: 'application/json',
headers: [
'Accept': 'application/json',
'x-api-key': "${daiApiKey}",
"Authorization" : "Bearer $authToken"
],
body: "$bodyText"
]
if(debugEnabled) log.debug "$requestParams"
httpPut(requestParams) {resp ->
}
}
void getInitialAttributes(){
if(debugEnabled) log.debug "Configuring Intial Attributes"
HashMap devMap = getDeviceList()
if(debugEnabled) log.debug "Dev List $devMap"
if(debugEnabled) log.debug "${devMap.devices[0].id}"
if(debugEnabled) log.debug "get device properties ${devMap.devices[0].id}"
if(devMap.devices[0].id == null) {
log.error "Thermostat not Found"
return
}
devDetail = getDevDetail("${devMap.devices[0].id}")
device.updateDataValue("daiID", "${devMap.devices[0].id}")
device.updateDataValue("daiName", "${devMap.devices[0].name}")
device.updateDataValue("firmware", "${devMap.firmwareVersion}")
updateThermostat()
}
void updateThermostat() {
modeStr=["off","heat","cool","auto","emergency heat"]
circStr=["auto","on","circulate"]
fanSpd=["low","medium","high"]
id = device.properties.data["daiID"]
if(debugEnabled) log.debug "Using ID:$id"
if(id == null) {
log.error "Thermostat has not been properly configured"
return
}
devDetail = getDevDetail("$id")
if(debugEnabled) log.debug "Detail:$devDetail"
degUnit = "°C"
if(useFahrenheit) {
devDetail.setpointDelta = celsiusToFahrenheit(devDetail.setpointDelta.toFloat()).toFloat().round(0)
devDetail.setpointMinimum = celsiusToFahrenheit(devDetail.setpointMinimum.toFloat()).toFloat().round(0)
devDetail.heatSetpoint = celsiusToFahrenheit(devDetail.heatSetpoint.toFloat()).toFloat().round(0)
devDetail.coolSetpoint = celsiusToFahrenheit(devDetail.coolSetpoint.toFloat()).toFloat().round(0)
devDetail.setpointMaximum = celsiusToFahrenheit(devDetail.setpointMaximum.toFloat()).toFloat().round(0)
devDetail.tempIndoor = celsiusToFahrenheit(devDetail.tempIndoor.toFloat()).toFloat().round(1)
devDetail.tempOutdoor = celsiusToFahrenheit(devDetail.tempOutdoor.toFloat()).toFloat().round(1)
degUnit = "°F"
}
updateAttr("thermostatModeNum",devDetail.mode.toInteger())
updateAttr("thermostatMode",modeStr[devDetail.mode.toInteger()])
updateAttr("fan",devDetail.fan)
updateAttr("thermostatFanMode",circStr[devDetail.fanCirculate.toInteger()])
updateAttr("fanCirculateSpeed",fanSpd[devDetail.fanCirculateSpeed.toInteger()])
updateAttr("setpointDelta",devDetail.setpointDelta,degUnit)
updateAttr("setpointMinimum",devDetail.setpointMinimum,degUnit)
updateAttr("heatingSetpoint",devDetail.heatSetpoint,degUnit)
updateAttr("coolingSetpoint",devDetail.coolSetpoint,degUnit)
updateAttr("setpointMaximum",devDetail.setpointMaximum,degUnit)
updateAttr("temperature",devDetail.tempIndoor,degUnit)
updateAttr("tempOutdoor",devDetail.tempOutdoor,degUnit)
updateAttr("humidity",devDetail.humIndoor,"%")
updateAttr("humidOutdoor",devDetail.humOutdoor,"%")
updateAttr("geofencingEnabled",devDetail.geofencingEnabled)
}
/*****************************
* Begin Thermostat Methods **
****************************/
void refresh() {
updateThermostat()
if(pollRate > 0)
runIn(pollRate*60,"refresh")
}
void setMode(modeNum){
if(debugEnabled) log.debug "set mode $modeNum"
if(useFahrenheit){
coolset = checkForAdj(device.currentValue("coolingSetpoint"), fahrenheitToCelsius(device.currentValue("coolingSetpoint")).toFloat().round(1))
heatset = checkForAdj(device.currentValue("heatingSetpoint"), fahrenheitToCelsius(device.currentValue("heatingSetpoint")).toFloat().round(1))
} else {
coolset = device.currentValue("coolingSetpoint")
heatset = device.currentValue("heatingSetpoint")
}
sendPut("/v1/devices/${device.properties.data["daiID"]}/msp",[mode:modeNum, coolSetpoint:coolset, heatSetpoint:heatset ])
}
void auto(){
setMode(3)
updateAttr("thermostatMode","auto")
}
void cool(){
setMode(2)
updateAttr("thermostatMode","cool")
}
void emergencyHeat(){
setMode(4)
updateAttr("thermostatMode","emergency heat")
}
void fanAuto(){
sendPut("/v1/devices/${device.properties.data["daiID"]}/fan",[fanCirculate:0, fanCirculateSpeed:0])
updateAttr("thermostatFanMode","auto")
}
void fanCirculate(){
sendPut("/v1/devices/${device.properties.data["daiID"]}/fan",[fanCirculate:2, fanCirculateSpeed:0])
updateAttr("thermostatFanMode","circulate")
}
void fanOn(){
sendPut("/v1/devices/${device.properties.data["daiID"]}/fan",[fanCirculate:1, fanCirculateSpeed:0])
updateAttr("thermostatFanMode","on")
}
void heat(){
setMode(1)
updateAttr("thermostatMode","heat")
}
void off(){
setMode(0)
updateAttr("thermostatMode","off")
}
void setCoolingSetpoint(temp){
if(debugEnabled) log.debug "set coolSetpoint $temp"
if(device.currentValue("setpointMaximum")!= null && temp > device.currentValue("setpointMaximum")) temp = device.currentValue("setpointMaximum")
if(device.currentValue("setpointMinimum")!= null && temp < device.currentValue("setpointMinimum")) temp = device.currentValue("setpointMinimum")
if(useFahrenheit){
temp = checkForAdj(temp, fahrenheitToCelsius(temp).toFloat().round(1))
heatset = checkForAdj(device.currentValue("heatingSetpoint"), fahrenheitToCelsius(device.currentValue("heatingSetpoint")).toFloat().round(1))
updateAttr("coolingSetpoint",celsiusToFahrenheit(temp).toFloat().round(0).toInteger(),"°F")
} else {
temp = normalizeTemp(temp)
heatset = device.currentValue("heatingSetpoint")
updateAttr("coolingSetpoint",temp,"°C")
}
sendPut("/v1/devices/${device.properties.data["daiID"]}/msp",[mode: device.currentValue("thermostatModeNum"), coolSetpoint:temp, heatSetpoint: heatset ])
}
void setHeatingSetpoint(temp){
if(debugEnabled) log.debug "set heatSetpoint $temp"
if(device.currentValue("setpointMaximum")!= null && temp > device.currentValue("setpointMaximum")) temp = device.currentValue("setpointMaximum")
if(device.currentValue("setpointMinimum")!= null && temp < device.currentValue("setpointMinimum")) temp = device.currentValue("setpointMinimum")
if(useFahrenheit){
temp = checkForAdj(temp, fahrenheitToCelsius(temp).toFloat().round(1))
coolset = checkForAdj(device.currentValue("coolingSetpoint"), fahrenheitToCelsius(device.currentValue("coolingSetpoint")).toFloat().round(1))
updateAttr("heatingSetpoint",celsiusToFahrenheit(temp).toFloat().round(0).toInteger(),"°F")
} else {
temp = normalizeTemp(temp)
coolset = device.currentValue("coolingSetpoint")
updateAttr("heatingSetpoint",temp,"°C")
}
sendPut("/v1/devices/${device.properties.data["daiID"]}/msp",[mode: device.currentValue("thermostatModeNum"), coolSetpoint:coolset, heatSetpoint: temp ])
}
Float normalizeTemp(temp) { //limits to x.5 or x.0
Float nTemp = ((int) (temp*2 + 0.5))/2.0
return nTemp
}
Float checkForAdj(hold, temp) {
temp = normalizeTemp(temp)
if(celsiusToFahrenheit(temp).toFloat().round(0) < hold)
temp += 0.5
return temp
}
void setThermostatFanMode(fanmode){
if(fanmode=="on")
fanOn()
else if (fanmode == "circulate")
fanCirculate()
else
fanAuto()
}
void setThermostatMode(tmode){
if(tmode == "auto")
auto()
else if(tmode == "heat")
heat()
else if(tmode == "cool")
cool()
else if(tmode == "off")
off()
else
emergencyHeat()
}
/***************************
* End Thermostat Methods **
**************************/
@SuppressWarnings('unused')
void logsOff(){
device.updateSetting("debugEnabled",[value:"false",type:"bool"])
}```