Daikin One integration

Thanks for the suggestion. I am running v2.4.1.177

I still got the same error

errorgroovy.lang.MissingMethodException: No signature of method: user_driver_SeriesOfUnlikelyExplanations_Daikin_OnePlus_Single_Thermostat_1567.setSupportedThermostatFanModes() is applicable for argument types: (java.lang.String) values: [["auto","on"]] on line 83 (method installed)

Just to confirm I interpreted your requested edits to line 83 & 84 as follows:

def initialize(){
    setSupportedThermostatFanModes(JsonOutput.toJson(["auto","on"]))
    setSupportedThermostatModes(JsonOutput.toJson(["heat","cool","auto","off"]))
}

Sorry, not sure. You might try reaching out to @bravenel

I am wondering if the problem is that my Daikin One Home account has two thermostats (each minisplit shows up as a Thermostat in the Daikin One Home app. Does this driver handle multiple thermostats or was it only intended for s single thermostat in the account?

I can see in the API documentation there is a call to request the list of thermostats, but I am a bit over my head looking at the groovey and understanding it all, but I am thinking maybe that could be my problem?

I "fixed" a couple of errors with some modification to the code.

I couldn't get authenticated, similar issue to above but I found my own solution.
Then the old MissingMethodException

errorgroovy.lang.MissingMethodException: No signature of method: user_driver_SeriesOfUnlikelyExplanations_Daikin_OnePlus_Single_Thermostat_1567.setSupportedThermostatFanModes() is applicable for argument types: (java.lang.String) values: [["auto","on"]] on line 83 (method installed)

Solutions (or at least what I did)
I modified line 27,28
String serverPath = "https://integrator-api.daikinskyport.com"
String daiApiKey = "YOUR API KEY"

@Field serverPath = "https://integrator-api.daikinskyport.com"
@Field daiApiKey = "API Key... right here.. between the quotes"

Added email and integratorToken variables (is that what we call them?) this was just for ease of use.

@Field email = "email associated with token"
@Field integratorToken = "The Crazy Long Token... right here.. between the quotes"

My getAuth() is a mashup of bhan and original

String getAuth() {
Map requestParams =
[
uri: "${serverPath}/v1/token",
headers: [
'Content-Type': 'application/json',
'x-api-key': "${daiApiKey}"
],
body: JsonOutput.toJson([
email: "${email}",
integratorToken: "${integratorToken}"])
]

Then small change to sendGet

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"
]
]

and sendPut

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"
]

Those changes allowed the driver to work, up to the MissingMethodException

I added the following below my other "@Field" shenanigans

@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"
]

Modified 65/66 to the below 4 lines

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}]]

I commented out line 83/84

// setSupportedThermostatFanModes(JsonOutput.toJson(["auto","circulate","on"]))
// setSupportedThermostatModes(JsonOutput.toJson(["auto", "cool", "emergency heat", "heat", "off"]))

Conclusion
I am clearly floundering, but do have the thermostat reading with no errors. Can't say with any certainty that all my modifications are either required or smart, so you know, be warned.

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"])
}```
1 Like

Hi @charlieg , how do I get the API key etc. for my Daikin One unit in the USA? Sorry for the simple issue!

Found it. Note that the instructions in the website as posted in this thread are obsolete now that Daikin app has become SkyPort App. Now, in the app, you need to go to SkyportCare tab, then Home Integration, and request the API key.

@charlieg's code worked perfectly! Now I have a question:

I have 2 Thermostats upstairs and downstairs. Is it possible to get the second thermostat integrated?

Don't know if anyone has seen my post, so I will tag @thebearmay and see if he has any ideas.
I just installed a Daikin one+ stat into my communicating system.
All functions work great with the stat AND HE.
The ONLY thing missing is to be able to control fan speed from HE either by a dashboard switch / button or in RM.
I can on the stat and HE does indicate what the fan speed is, but there is NO WAY to control it.
I know the stat has"airquality" / "recirculate", but I haven't found a way to just run the fan other than setting the schedule for recirculate.
Unless I am missing something, how can I do this.
I would like to be able to set the speed in rule machine also, but I cannot find the parameter.
Thanks!

Driver has methods for the fan control:

void setThermostatFanMode(fanmode){
    if(fanmode=="on") 
       fanOn()
    else if (fanmode == "circulate")
       fanCirculate()
    else
       fanAuto()    
}

void fanAuto(){
    sendPut("/deviceData/${device.properties.data["daiID"]}",[fanCirculate:0])
    updateAttr("thermostatFanMode","auto")
}

void fanCirculate(){
    sendPut("/deviceData/${device.properties.data["daiID"]}",[fanCirculate:2])
    updateAttr("thermostatFanMode","circulate")
}

void fanOn(){
    sendPut("/deviceData/${device.properties.data["daiID"]}",[fanCirculate:1])
    updateAttr("thermostatFanMode","on")
}

These should be available in RM but might be listed under Custom

1 Like

Yes, thank you for the info.
I did find that I am able to change the fan mode, but what I would ALSO like to do is change the fan SPEED.
I am guessing that this is not a parameter that can be set, but only monitored as in the device page, fan speed is listed.

Also, why is Fan listed as False in current states?
image

Another thing....
When the stat is heating, the device page indicates IDLE and NOT heating.

Any ideas? @thebearmay

May be the poll timing, i.e. the heating is occurring between the polls for status.

Sorry, I wasn't notified of your response...
Anyway, would the status change on the next poll?
I have the poll set at 3 minutes right now.
If I changed to 1 minute would that trigger something on Daikin's servers?
I assume this integration is cloud based (YECH)!

OOPS... yes it does work and I can control the fan on/off/circulate.
I would also like to control speeds though, but haven't found a way to do that other than on the stat itself....