How to integrate Daikin Altherma LT heat pump?

Hello all,

I had originally intended to use and rework the Daikin binding for use with my new Daikin Altherma low-temperature split air-to-water heat pump, but on further inspection it seems as if that binding is only for air conditioners. That binding integrates to either the KKRP01A or BRP072A42 units, which only seem compatible with a list of Daikin’s AC units.

Does anyone have experience or advice on the most sound way to integrate Daikin Altherma LT heat pumps? I’ve seen a somewhat expensive KNX module which I would rather avoid, even though the point at which it ties into the unit seems sensible (in the place of an additional controller using the P1P2 interface). There is also the CoolLinkNet unit, but only one of my indoor/outdoor models are listed as supported, and I have had no reply to my enquiry.

My specific Daikin Altherma models are:
EHVH11S26CB9W (indoor unit)
ERLQ011CV3 (outdoor unit)

Thanks for any suggestions!

John

Dear John,

The only quick solution (although seems expensive, is cheap compared to the other options - the full official list of interfaces for controlling Daikin products is here) you can have is the KNX interface.
I would go for the KNX interface only if I had a KNX bus already installed. There is also a Modbus option for Altherma here.

Best regards,

George

1 Like

Thank you very much, George. If I had a KNX bus already in place, I would buy that unit since it seems well executed. The Modbus option is unfortunately only for HT (high temperature) units.

Is there any way you can enquire Daikin if it works for the HT also? It seems that both are using the same P1P2 interface. I don’t think that there is a difference in terms of communication. The objects should be the same. I will ask someone from Daikin and will come back. To my knowledge the HT is different only in terms of water temperature supplied (additional compressor).

I wish I knew a good path to get the question to someone knowledgeable at Daikin. My local Daikin installer is unlikely to know more than me on that subject. I see in the installation manual for RTD-W that its setpoint range is 25-80C, so it makes me wonder if it would be “dangerous” to try it. But you might be right in your theory that it would work, since the comms are the same. I also don’t see Modbus registers for reading back the sensor values like leaving water temperature, outside temperature, etc. I hope my only option isn’t setting up a KNX network…

Thanks for being incredibly helpful, George!

Dear John,

In the installation manual of the Modbus interface at page 10 are the registers you require. Manual here.
I just hang up with a Daikin Technician (service guy from Daikin directly, not dealer), he said it should work and promised he will further investigate at Daikin’s HQ. I’ll keep you updated.
It’s definitely easier to have rs485 installed! Forget about KNX just for one interface (been there done that! :wink:)
LATER EDIT: the guy called me back. The answer is that for older CA models the modbus interface works. For the one that you have EHVH11S26CB9W it does not. The only option they support is KNX. But in 2017 they plan to issue a LAN adapter for your model. Sorry for the bad news!

BR,
George

Sorry; I missed that!

That’s actually great news! I would rather wait until next year for a proper TCP/IP-based solution than either spend on a KNX setup I would under-use, or think that there is only a dead end.

Thank you again, George, for your incredible assistance.

Regards,
John

1 Like

John, how does it go with that TCP/IP solution? Does it exist? I have Altherma HTemp and want to integrate it to OH2. Any idea? Thanks

Hi Jiří, I located in March and still have it on my to-do list to purchase and install a BRP069A62 for my low-temp Daikin Altherma split system heat pump. This might not be the correct device for your heat pump, not sure. IIRC the interfacing looked achievable with the HTTP binding and a REGEX or JSONPATH transformation filter. I would have to actually purchase and install the device first in order to give useful advice, but in any case it looked simple to integrate with.

John

So, it’s finally out on the market (glad to hear that)!
I have already integrated BRP069BXX and BRP069AXX. No JSON needed, just plain REGEX.
Unfortunately, Daikin does not provide support for the API.
I have managed to discover 2 main areas of these interfaces:

http://DAIKIN_IP/aircon/get_sensor_info

and

http://DAIKIN_IP/aircon/get_control_info

For both, you will need get requests. What is important is that you will need to figure out the MANDATORY order and the minimum number of the parameters to be sent in case of trying to control.
The output of such a GET request is something like:

ret=OK,htemp=26.0,hhum=-,otemp=23.0,err=0,cmpfreq=0

Good luck!
George

2 Likes

Hello George,

I have a BRP069A61 recently installed in my Altherma 3. I have contacted Daikin and they have replied that they only support the APP, and that they do not give any kind of information, since it is a proprietary protocol.
I have tried with
http: // DAIKIN_IP / aircon / get_sensor_info
and

http: // DAIKIN_IP / aircon / get_control_info

But the BRP069A61 replies: Sorry, the requested file does not exist on this server.

I suppose there must be differences between each type of module …
How did you find out how the module works? I say it to be able to do with mine …

Thanks and best regards
Luis

“Man in the middle method”, use a packet sniffer and a port mirror! With Altherma the aircon section might not exist.
Sniff the packets sent between the app and the interface!

Hello,
I have an Altherma LT CB series and a BRP069A2 and want to monitor it like you.

Have you suceeded to make some GET request ? I sniff the packet, but nothing obvious about the sensors values appear…the area of Georges are not accessible too.

Regards

If HTTP GET does not work for you, you need to use websocket. See:

So I found some Pyhthon code in the emoncms code for the BRP069A2 and combined it with the REST API of OpenHab. I wanted to do it as a regular script, but couldn’t figure out how to properly do web sockets. The idea behind the script is that it runs as cron job. I feel like this is a little bit of a hack, but it should do the job and may help some people here.

from websocket import create_connection
import json, urllib, random, string
from urllib import request, parse

def randomString(stringLength=5):
    """Generate a random string of fixed length """
    letters = string.ascii_lowercase
    return ''.join(random.choice(letters) for i in range(stringLength))

def postUdpate(item, value):
#Adjust your IP of the OpenHab part here.
    req =  request.Request("http://openhab:8080/rest/items/"+item+"/state", data=str(value).encode('utf-8')) 
    req.add_header('Content-Type', 'text/plain')
    req.get_method = lambda: 'PUT'
    resp = request.urlopen(req)
    if resp.status!=202 :
        print("Response was %s %s" % (resp.status, resp.reason))

def requestValue(ws, item):
    ws.send("{\"m2m:rqp\":{\"op\":2,\"to\":\"/[0]/MNAE/"+item+"/la\",\"fr\":\"/OpenHab\",\"rqi\":\""+randomString()+"\"}}")
    result1 =  json.loads(ws.recv())
    #print("Response was: %s" % result1)
    value = result1["m2m:rsp"]["pc"]["m2m:cin"]["con"]
    return value

#You need to adjust your IP Address here
ws = create_connection("ws://192.168.188.20/mca")

items =	{
  "Heating_TankTemperature": "2/Sensor/TankTemperature",
  "Heating_IndoorTemperature": "1/Sensor/IndoorTemperature",
  "Heating_OutdoorTemperature": "1/Sensor/OutdoorTemperature",
  "Heating_OperationPower": "1/Operation/Power",
  "Heating_OperationMode": "1/Operation/OperationMode",
  "Heating_OperationTargetTemperature": "1/Operation/TargetTemperature",
  "Heating_TankOperationPower": "2/Operation/Power",
  "Heating_TankOperationMode": "2/Operation/OperationMode",
  "Heating_TankOperationTargetTemperature": "2/Operation/TargetTemperature",
  "Heating_TankOperationPowerful": "2/Operation/Powerful",
  "Heating_ErrorState": "0/UnitStatus/ErrorState"
}

for x in items:
  value = requestValue(ws, items[x])
  #print("Received value '%s' for %s" % (value, x))
  postUdpate(x, value)

ws.close()

And of course some items:

Group heating  "Heating"
Number:Temperature     Heating_TankTemperature            "Tank temperature"                             (heating)
Number:Temperature     Heating_IndoorTemperature            "Indoor temperature"                             (heating)
Number:Temperature     Heating_OutdoorTemperature            "Outdoor temperature"                             (heating)
Number:Temperature     Heating_OperationTargetTemperature            "Target temperature"                             (heating)
Number:Temperature     Heating_TankOperationTargetTemperature            "Target tank temperature"                             (heating)
String     Heating_ErrorState            "Error state"                             (heating)
String     Heating_OperationPower            "Operation Power"                             (heating)
String     Heating_OperationMode            "Operation Mode"                             (heating)
String     Heating_TankOperationPowerful            "Operation Powerful"                             (heating)
String     Heating_TankOperationPower            "Operation Power"                             (heating)
String     Heating_TankOperationMode            "Operation Mode"                             (heating)

You may need to run pip3 install websocket websocket-client before it actually works and execute it with python3.

Enjoy!

1 Like

I did a man in the middle capture and found the following end points:

/[0]
/[0]/MNAE/0
/[0]/MNAE/0/DateTime/la
/[0]/MNAE/0/Error/la
/[0]/MNAE/0/UnitProfile/la
/[0]/MNAE/1
/[0]/MNAE/1/ChildLock/LockedState/la
/[0]/MNAE/1/ChildLock/PinCode/la
/[0]/MNAE/1/Consumption/la
/[0]/MNAE/1/Holiday/EndDate/la
/[0]/MNAE/1/Holiday/HolidayState/la
/[0]/MNAE/1/Holiday/StartDate/la
/[0]/MNAE/1/Operation/OperationMode/la
/[0]/MNAE/1/Operation/Power/la
/[0]/MNAE/1/Operation/TargetTemperature/la
/[0]/MNAE/1/Schedule/Next/la
/[0]/MNAE/1/Sensor/IndoorTemperature/la
/[0]/MNAE/1/Sensor/OutdoorTemperature/la
/[0]/MNAE/1/UnitIdentifier/Icon/la
/[0]/MNAE/1/UnitIdentifier/Name/la
/[0]/MNAE/1/UnitProfile/la
/[0]/MNAE/1/UnitStatus/EmergencyState/la
/[0]/MNAE/1/UnitStatus/ErrorState/la
/[0]/MNAE/1/UnitStatus/InstallerState/la
/[0]/MNAE/1/UnitStatus/TargetTemperatureOverruledState/la
/[0]/MNAE/1/UnitStatus/WarningState/la
/[0]/MNAE/2
/[0]/MNAE/2/ChildLock/LockedState/la
/[0]/MNAE/2/ChildLock/PinCode/la
/[0]/MNAE/2/Consumption/la
/[0]/MNAE/2/Holiday/EndDate/la
/[0]/MNAE/2/Holiday/HolidayState/la
/[0]/MNAE/2/Holiday/StartDate/la
/[0]/MNAE/2/Operation/OperationMode/la
/[0]/MNAE/2/Operation/Power/la
/[0]/MNAE/2/Operation/Powerful/la
/[0]/MNAE/2/Operation/TargetTemperature/la
/[0]/MNAE/2/Schedule/Next/la
/[0]/MNAE/2/Sensor/TankTemperature/la
/[0]/MNAE/2/UnitIdentifier/Icon/la
/[0]/MNAE/2/UnitIdentifier/Name/la
/[0]/MNAE/2/UnitInfo/UnitType/la
/[0]/MNAE/2/UnitProfile/la
/[0]/MNAE/2/UnitStatus/EmergencyState/la
/[0]/MNAE/2/UnitStatus/ErrorState/la
/[0]/MNAE/2/UnitStatus/InstallerState/la
/[0]/MNAE/2/UnitStatus/ReheatState/la
/[0]/MNAE/2/UnitStatus/TargetTemperatureOverruledState/la
/[0]/MNAE/2/UnitStatus/WarningState/la
/[0]/MNAE/2/UnitStatus/WeatherDependentState/la
/[0]/MNAE/3
/[0]/MNCSE-node/deviceInfo
/[0]/MNCSE-node/firmware

You can do a discovery of your device by asking for the [0]/MNAE/ and then a number. This will give you a response of what unit you are talking to:

{"m2m:rsp":{"rsc":2000,"rqi":"lcmsd","to":"/S","fr":"/[0]/MNAE/0","pc":{"m2m:cnt":{"rn":"0","ri":"006a","pi":"C0003","ty":3,"ct":"20000000T000000Z","lt":"20000000T000000Z","st":11,"lbl":"function/Adapter"}}}}
{"m2m:rsp":{"rsc":2000,"rqi":"lnjnp","to":"/S","fr":"/[0]/MNAE/2","pc":{"m2m:cnt":{"rn":"2","ri":"003c","pi":"C0003","ty":3,"ct":"20000000T000000Z","lt":"20000000T000000Z","st":13,"lbl":"function/DomesticHotWaterTank"}}}}

A non existing unit will give you a rsc:4004. The next step would then be to request the unit profile: /[0]/MNAE/2/UnitProfile/la which gives you a map of the end points that can be talked to and some information about what answers to expect (only the con part shown here):

{  
   "Sensor":[  
      "IndoorTemperature",
      "OutdoorTemperature"
   ],
   "UnitStatus":[  
      "ErrorState",
      "InstallerState",
      "WarningState",
      "EmergencyState",
      "TargetTemperatureOverruledState"
   ],
   "Operation":{  
      "Power":[  
         "on",
         "standby"
      ],
      "OperationMode":[  
         "heating"
      ],
      "TargetTemperature":{  
         "heating":{  
            "maxValue":30.0000000000000000,
            "minValue":12.0000000000000000,
            "stepValue":1.0000000000000000
         }
      }
   },
   "Schedule":{  
      "Base":"action",
      "defaultScheduleAvailable":"true",
      "NameAdjustable":"false",
      "List":{  
         "heating":[  
            {  
               "StartTime":{  
                  "stepValue":10.0000000000000000,
                  "unit":"minutes"
               },
               "TargetTemperature":{  
                  "heating":{  
                     "maxValue":30.0000000000000000,
                     "minValue":12.0000000000000000,
                     "stepValue":1.0000000000000000
                  }
               },
               "Actions":[  
                  "StartTime",
                  "TargetTemperature"
               ],
               "maxActionsAllowed":6
            },
            [  
               "monday",
               "tuesday",
               "wednesday",
               "thursday",
               "friday",
               "saturday",
               "sunday"
            ],
            [  
               "monday",
               "tuesday",
               "wednesday",
               "thursday",
               "friday",
               "saturday",
               "sunday"
            ],
            [  
               "monday",
               "tuesday",
               "wednesday",
               "thursday",
               "friday",
               "saturday",
               "sunday"
            ],
            [  

            ]
         ]
      }
   },
   "Consumption":{  
      "Electrical":{  
         "unit":"kWh",
         "Heating":{  
            "Daily":{  
               "contentCount":24,
               "resolution":2
            },
            "Weekly":{  
               "contentCount":14,
               "resolution":1
            },
            "Monthly":{  
               "contentCount":24,
               "resolution":1
            }
         }
      }
   }
}{  
   "Sensor":[  
      "TankTemperature"
   ],
   "UnitStatus":[  
      "ErrorState",
      "InstallerState",
      "WeatherDependentState",
      "WarningState",
      "EmergencyState"
   ],
   "Operation":{  
      "Power":[  
         "on",
         "standby"
      ],
      "OperationMode":[  
         "reheat_only"
      ],
      "TargetTemperature":{  
         "reheat_only":{  
            "maxValue":60.0000000000000000,
            "minValue":30.0000000000000000,
            "stepValue":1.0000000000000000
         }
      },
      "powerful":[  
         "0",
         "1"
      ]
   },
   "Schedule":{  

   },
   "Consumption":{  
      "Electrical":{  
         "unit":"kWh",
         "Heating":{  
            "Daily":{  
               "contentCount":24,
               "resolution":2
            },
            "Weekly":{  
               "contentCount":14,
               "resolution":1
            },
            "Monthly":{  
               "contentCount":24,
               "resolution":1
            }
         }
      }
   }
}

Notably interesting is the response to consumption:

{"Electrical":{"Heating":{"D":[0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,3,null],"W":[1,1,2,2,1,0,1,1,2,null,null,null,null,null],"M":[null,null,null,null,null,null,null,null,null,null,null,null,null,0,82,23,100,null,null,null,null,null,null,null]}}}

Unfortunately I don’t have my heating on long enough to get some more interesting data. But I consider writing a proper Daikin adapter bundle as there is some interesting data on these.

1 Like

(Owning a Daikin Altherma LT 16 kW ( EHVH16S26CB9W, ERLQ016CAW1)

I was Googling to find information about how to connect a Daikin Altherma LT with a modbus interface and found this thread; at first I was disappointed to read that only the HT versions were suited for the RTD-W:

But now I found the RTD-LT/CA. Maybe that one was not available in 2016.

https://www.daikin.eu/en_us/products/RTD-LT-CA.html
http://www.alphabms.com/wp-content/uploads/2018/04/Daikin-RTD-Range.pdf

Product Features

RTD-LT

  • Modbus interface for monitoring and control of Daikin Altherma low temperature (EHVH(X)-C / EHBH(X)-C)
  • Voltage and resistance control
  • Photovoltaic operation signal for energy saving

Using the RTD-LT seems to be the most flexible way of remotely managing low temperature heat pumps.

For reference, here is a detailed installation manual that documents the device quite well.
http://www.dacnw.ru/extranet/01.%20Daikin/06.%20СИСТЕМЫ%20УПРАВЛЕНИЯ%20И%20ОПЦИИ/ЦЕНТРАЛЬНЫЕ%20ПУЛЬТЫ%20И%20ШЛЮЗЫ/MODBUS%20GATEWAY%20(EKMBDXA_RTD)/RTD/(IM-OM)%20RTD-LT-CA_rus.pdf

Hey, I continued to investigate this API a bit and the consumption works as following:
For the daily row every even hour a new value is provided for the past 2 hours. Here is an example at 23h:

[0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,null]

One hour later at 0:01

[0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,null,null,null,null,null,null,null,null,null]

So the last value is essentially never written.
For the weekly this is the same on Sunday it looks like this:

"W":[7,8,4,9,1,3,3,1,3,3,8,1,4,null]

on Monday then:

"W":[1,3,3,8,1,4,2,null,null,null,null,null,null,null]

What I am not quite sure yet is what this consumption entails. I have 2 smartmeter (one for heating, the other for the rest of the house) and the numbers that I collect there during the heating phases are smaller, but not as much as I would expect for an air heat pump. I guess I should check the front panel of my unit to see as it reports both consumed and generated energy.

Date Measured Reported
2019-06-17 2,6 7
2019-06-18 2,8 8
2019-06-19 2 4
2019-06-20 3,2 9
2019-06-21 1 1
2019-06-22 1,6 3
2019-06-23 1,2 3
2019-06-24 1,3 1
2019-06-25 1,5 3
2019-06-26 1,3 3
2019-06-27 3,1 8
2019-06-28 0,8 1
2019-06-29 1,4 4

So here is my updated script now which works quite well:

from websocket import create_connection
import json, datetime, time, urllib
from urllib import request, parse
import random
import string

def randomString(stringLength=5):
    """Generate a random string of fixed length """
    letters = string.ascii_lowercase
    return ''.join(random.choice(letters) for i in range(stringLength))

def postUdpate(item, value):
    if value is None:
        print("Not posting None value for "+item)
        return
    req =  request.Request("http://openhab:8080/rest/items/"+item+"/state", data=str(value).encode('utf-8')) # this will make the method "POST"
    req.add_header('Content-Type', 'text/plain')
    req.get_method = lambda: 'PUT'
    resp = request.urlopen(req)
    if resp.status!=202 :
        print("Response was %s %s" % (resp.status, resp.reason))

def requestValue(ws, item):
    ws.send("{\"m2m:rqp\":{\"op\":2,\"to\":\"/[0]/MNAE/"+item+"/la\",\"fr\":\"/OpenHab\",\"rqi\":\""+randomString()+"\"}}")
    result1 =  json.loads(ws.recv())
    #print("Response was: %s" % result1)
    value = result1["m2m:rsp"]["pc"]["m2m:cin"]["con"]
    return value

def requestYesterdaysConsumption(unitNumber):
    consumption=requestValue(ws, str(unitNumber)+"/Consumption")
    cj = json.loads(consumption)
    now = datetime.datetime.now()
    weeklyIndex=(now.weekday()-1)+7
    dailyUsage=cj["Electrical"]["Heating"]["W"][weeklyIndex]
    print("Received electrical weekly usage value '%s' for unit %s" % (dailyUsage, unitNumber))
    if dailyUsage is not None: #This may occur if your local time is more advanced
        return dailyUsage*1000 # than the heating unit.
    return None

def requestLast2HoursConsumption(unitNumber):
    consumption=requestValue(ws, str(unitNumber)+"/Consumption")
    cj = json.loads(consumption)
    now = datetime.datetime.now()
    hourlyIndex=((now.hour-2)//2)+12
    biHourly=cj["Electrical"]["Heating"]["D"][hourlyIndex]
    print("Received electrical daily usage value '%s' for unit %s" % (biHourly, unitNumber))
    if biHourly is not None:
        return biHourly*1000
    return None

ts = time.time()
timestamp = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')

ws = create_connection("ws://192.168.188.20/mca")

items =	{
  "Heating_TankTemperature": "2/Sensor/TankTemperature",
  "Heating_IndoorTemperature": "1/Sensor/IndoorTemperature",
  "Heating_OutdoorTemperature": "1/Sensor/OutdoorTemperature",
  "Heating_OperationPower": "1/Operation/Power",
  "Heating_OperationMode": "1/Operation/OperationMode",
  "Heating_OperationTargetTemperature": "1/Operation/TargetTemperature",
  "Heating_TankOperationPower": "2/Operation/Power",
  "Heating_TankOperationMode": "2/Operation/OperationMode",
  "Heating_TankOperationTargetTemperature": "2/Operation/TargetTemperature",
  "Heating_TankOperationPowerful": "2/Operation/Powerful",
  "Heating_ErrorState": "0/UnitStatus/ErrorState"
}

for x in items:
  value = requestValue(ws, items[x])
  print("Received value '%s' for %s" % (value, x))
  postUdpate(x, value)

postUdpate("Heating_BiHourlyEnergyHeat", requestLast2HoursConsumption(1))
postUdpate("Heating_BiHourlyEnergyWater", requestLast2HoursConsumption(2))
postUdpate("Heating_DailyEnergyHeat", requestYesterdaysConsumption(1))
postUdpate("Heating_DailyEnergyWater", requestYesterdaysConsumption(2))
ws.close()

I also captured some write packages, but apparently forgot to store them :wink: But what I recall is that OP is set to 1 for writing. I will re-check when I am back at home.

Ah, I did find the responses for switching something:

{"m2m:rqp":{"op":1,"to":"/[0]/MNAE/1/Operation/Power","fr":"/S","rqi":"qwxxl","ty":4,"pc":{"m2m:cin":{"con":"on","cnf":"text/plain:0"}}}}
{"m2m:rsp":{"rsc":2001,"rqi":"qwxxl","to":"/S","fr":"/[0]/MNAE/1/Operation/Power"}}
{"m2m:rqp":{"op":1,"to":"/[0]/MNAE/2/Operation/Powerful","fr":"/S","rqi":"spcxt","ty":4,"pc":{"m2m:cin":{"con":1,"cnf":"text/plain:0"}}}}
{"m2m:rsp":{"rsc":2001,"rqi":"spcxt","to":"/S","fr":"/[0]/MNAE/2/Operation/Powerful"}}
{"m2m:rqp":{"op":1,"to":"/[0]/MNAE/2/Operation/Powerful","fr":"/S","rqi":"olpcx","ty":4,"pc":{"m2m:cin":{"con":0,"cnf":"text/plain:0"}}}}
{"m2m:rsp":{"rsc":2001,"rqi":"olpcx","to":"/S","fr":"/[0]/MNAE/2/Operation/Powerful"}}