Danfoss Air Unit - Ethernet-Connection

Tags: #<Tag:0x00007f186448f5e8>

Hello,

at home I use a “Danfos Air Unit W2” ventillation. I only found users who have connected it via Z-Wave with openHAB (and it seems to work). But the Air Unit could also be connected via ethernet to the local network and with “Danfoss HRV PC-Tool” it is possible to read all values like temperatures, humidity and ventilation-speed from it and configure the complete unit.

Is there anyone who has managed to connect the Air Unit via ethernet to openHAB?

Sorry for my bad english… :wink:

Greetz,
BlackAlpha

Hi,

I did a lot analysis of tha traffic send between the HRV PC Tool and the Air unit CC. The protocol is very basic TCP. Only a few bytes for reading and also a few bytes for writing. Usually a 4 byte code has to be send to tell what you like to read. This also includes the statistics.

Unfortunately the HRV PC Tool is very talkative. So it read more or less all the data and that very frequently, even if you only view the main screen.

An integration into Loxone in Pico-C was partly successful. I can read data from the air unit. Unfortunately changing something doesn’t work stable. In fact it only works when I used the PC Tool some minutes ago. So it seems some kind of “ticket” or state activation has to be done, so that writing data is accepted by the air unit CC.

The discovery of the air unit is done using Simple Service Discovery Protocol (SSDP). In my implementation I skipped that part and directly accessed the know IP and port. However this could be the reason why changing data is not working.

I have an Excel at home with the investigations which bytes to send for which information and how to interprete the result (i.e. temperatures are returned as 2 byte Integers but with 2 decimals, i.e. 0x0998 = 2456 -> 24,56°C., humidity is a one byte integer).

I posted the results as well in the Loxone Forum and another Loxone user was able to fully integrate it using this information, he also added SSDP. Also changing values was working. Unfortunately some time ago Loxone close the forum and also the products get more and more closed, thus I also think about moving to OpenHab.

I will add the details of the excel in the evening. Remember me if I forget …

Regards,

Ralf

PS: I also contacted Danfoss to get official protocol information but they didn’t even answered.

1 Like

Hey @leaxi,

this sounds very nice to me. If you’re deciding to create a binding, a dream would come true. :wink:
I think I can’t help you with programing code (bad java skills :wink: ), but if you need someone for testing purposes, I would help you as good as I can. :slight_smile:

I’ve contacted Danfoss Germany, but they said they can’t provide any information. They referred me to Danfoss Denmark, but I didn’t contact them yet.

Regards,
Carsten

As written some days ago, here some details about the Danfoss Air Unit Ethernet protocol:

For the discovery of the air unit a UDP broadcast to 255.255.255.255 is send to port 30045. The content is
0x0c0030001100120013.

The air unit answers to the this broadcast with 0x0d0007000202.

The communication then happens by TCP usually using the port 30046.

All data pakets are 63 bytes as far as I found out. When reading data only the first 4 bytes are used:

0x00030000 to 0x0030014f the 24 hours 14 days statistics with temperatures and humidity. 0x00300000 is the current hour. 0x00300001 is one hour before.

You get 12 bytes back I.e.
dd 05 dc 08 1d 08 a4 06 3d 08 77 ba
0xdd = unknown
0x05dc = 15.0 out to air unit
0x081d = 22.1 room to air unit
0x08a4 = 20.8 air unit to room
0x063d = 16.0 air unit to out
0x0877 = 21.7 room temperature
0xba = 72.9% humidity

Starting with 0x0104 you can read a lot of settings
0x01040301 returns one byte with bypass activation room temperature
0x01041412 returns one byte with mode of the air unit
0x01041440 returns one byte with air in main setting in percent
0x01041441 returns one byte with air out main setting in percent
0x01041460 returns one byte with bypass on=0x01 off=0x00
0x01041462 returns one byte with hours of bypass activation
0x01041463 returns two bytes with bypass deactivation outside temperature
0x01041530 returns one byte with rush airing on=0x01 off=0x00
0x01041531 returns one byte with hours of rush airing
0x01041561 returns one byte with level in manual mode
0x01041571 returns one byte with night cooling on=0x01 off=0x00
0x01041702 returns one byte with automatic rush airing on=0x01 off=0x00
0x01041703 returns one byte with rush airing percent
0x01041706 returns one byte with automatic bypass on=0x01 off=0x00

The same setting can be written using 0x0106. So for example setting manual mode you need to send 0x0106141202.

Some more addresses to read:
0x04040025 the Model String I.e. Danfoss Air 4611
0x06041760 to 0x06041769 Alarm protocol:
First byte = Length of following data
2.+3. byte:
8220 = communication error
0201 = air filter
8 bytes 0 = no alarm entry
Following bytes depend on the type
Air filter:
4. byte = seconds
5. byte = minutes
6. byte & 0x1f = hours
9. byte = year
8. byte = month
7. byte & 0x1f = day

That’s all I tested, but except setting a program includes all major information.

2 Likes

Hello Lexi…
I have the danfoss service tool, where a lot of addresses can be read… there is many more than in the normal downloadable version… let me know if that’s usable for something

Hello Lexi… tried to get a newer service tool but with no success…
can you see if the registers in the tool I’ve send fits what you discovered? Maybe the rest also fits then… even since you can’t get online ewith the tool…

But how do we do the communication from openhab?
I can contact danfoss Denmark, but what is it exactly we need?

Hi,

Meanwhile I bought a z.wave.me razberry board and tried to connect to the Air Unit by z-wave.

Unfortunately I had to learn this is not as easy as I thought. First of all the Air Unit CCM is a Z-wave slave and the Air Dial is a controller. When used with another controller like the razberry then either the razberry or the Air Dial need to be a secondary controller. I was able to include the CCM in a network of the razberry, but then I couldn’t include the Air Dial anymore. I have no idea how to include the Air Dial as a secondary controller.
Even with the CCM included I didn’t saw a lot things to change.

So finally using Ethernet i’m much more near to a usable solution.

Hi @all,

are there any news about using openHAB to control the air unit? :slight_smile:

Greetings,
Carsten

Hi @leaxi,

it seems that no one is interested in integrating the Air Unit into openHAB. Above you told us, that you have integrated the Air Unit partly into Loxone. Are there any public code samples or is it possible to send me some?
Maybe a friend of mine tries to write a simple solution to connect the Air Unit via command line so that a simple “connection” via exec-binding would be possible.

It would be great if you have some more infos or code samples for us. :slight_smile:

Greetings,
Carsten

Hi Carsten,

I had some time to create small java code snipped and tried to against my air unit. It works, But really very basic.

I only reads three values: mode, input and output:

For each of the settings so far a new connection is opened, of course this doesn’t make sense. So more modularization necessary. But as a starting point for own investigation …

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

public class AirUnit {
    private static final byte[] MODE = { 0x1, 0x4, 0x14, 0x12 };
    private static final byte[] BASE_IN = { 0x1, 0x4, 0x14, 0x40 };
    private static final byte[] BASE_OUT = { 0x1, 0x4, 0x14, 0x41 };

    public static void main(String[] args) {
        System.out.println("Creating AirUnit object");
        AirUnit airUnit = new AirUnit();

        System.out.println(airUnit.getByte(MODE));
        System.out.println(airUnit.getByte(BASE_IN));
        System.out.println(airUnit.getByte(BASE_OUT));
    }

    private int getByte(byte[] valueAddress) {
        try {
            byte[] ipAddr = { (byte) 192, (byte) 168, (byte) 47, (byte) 11 };
            InetAddress inetAddr = InetAddress.getByAddress(ipAddr);
            Socket s = new Socket(inetAddr, 30046);

            OutputStream oStream = s.getOutputStream();
            InputStream iStream = s.getInputStream();

            oStream.write(valueAddress);
 
            byte result[] = new byte[63];
            iStream.read(result, 0, 63);

            s.close();

            return result[0];

        } catch (UnknownHostException exUh) {
            System.out.println("no address");
        } catch (IOException exIo) {
            System.out.println("port not open");
        }

        return 0;
    }
}
1 Like

Hi @leaxi,

thank you for the sample, it works fine! :slight_smile:
We did some tests and got some first results. :slight_smile:

Could you explain me, how humidity is calculated? If I’m converting hex to decimal my results don’t make any sense… :thinking:

Greetings,
Carsten

The humidity is stored in a one byte unsigned integer. 0xba in this case means 186 in decimal. To get the 72.9% you simply divide by the maximum possible value 255:

186/255 = 72.9

I also took some time in a few evenings this week and enhanced the example. So far I implemented a more robust reading (reconnect on error), reading boolean, byte and short (2 bytes) values. Writing to the registers works as well. Maybe we should start a GIT project. But here the latest state:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

public class AirUnit {
    private InetAddress inetAddr;
    private Socket socket;
    private OutputStream oStream;
    private InputStream iStream;
    private static final byte[] EMPTY = {};
    private static final byte[] GET_HISTORY = { 0x00, 0x30 };
    private static final byte[] GET_SETTING = { 0x01, 0x04 };
    private static final byte[] SET_SETTING = { 0x01, 0x06 };
    private static final byte[] MODE = { 0x14, 0x12 };
    private static final byte[] BASE_IN = { 0x14, 0x40 };
    private static final byte[] BASE_OUT = { 0x14, 0x41 };
    private static final byte[] BYPASS = { 0x14, 0x60 };
    private static final byte[] BYPASS_DEACTIVATION = { 0x14, 0x63 };
    private static final byte[] RUSH_AIRING = { 0x15, 0x30 };
    private static final byte[] NIGHT_COOLING = { 0x15, 0x71 };
    private static final byte[] AUTOMATIC_BYPASS = { 0x17, 0x06 };
    private static final byte[] AUTOMATIC_RUSH_AIRING = { 0x17, 0x02 };

    public static void main(String[] args) {
        System.out.println("Creating AirUnit object");
        try {
            InetAddress ip = InetAddress.getByName("192.168.47.11");
            AirUnit airUnit = new AirUnit(ip);
            System.out.format("Mode: %d\n", airUnit.getMode());
            System.out.format("Basic Input: %d\n", airUnit.getByteSetting(BASE_IN));
            System.out.format("Basic Output: %d\n", airUnit.getByteSetting(BASE_OUT));
            System.out.format("Bypass: %b\n", airUnit.getBooleanSetting(BYPASS));
            System.out.format("Bypass Deactivation: %d\n", airUnit.getShortSetting(BYPASS_DEACTIVATION));
            // airUnit.setSetting(BYPASS_DEACTIVATION, (short) 1450);
            // System.out.format("Bypass Deactivation: %d\n", airUnit.getShortSetting(BYPASS_DEACTIVATION));
            System.out.format("Automatic Bypass: %b\n", airUnit.getBooleanSetting(AUTOMATIC_BYPASS));
            System.out.format("Night Cooling: %b\n", airUnit.getBooleanSetting(NIGHT_COOLING));
            System.out.format("Rush Airing: %b\n", airUnit.getBooleanSetting(RUSH_AIRING));
            System.out.format("Automatic Rush Airing: %b\n", airUnit.getBooleanSetting(AUTOMATIC_RUSH_AIRING));
        } catch (UnknownHostException exUh) {
            System.out.println("unknown address");
        } catch (IOException exIO) {
            System.out.println("send data failed");
        }
    }

    public AirUnit(InetAddress inetAddr) throws IOException {
        this.inetAddr = inetAddr;
        connect();
    }

    public byte getMode() throws IOException {
        return getByteSetting(MODE);
    }

    public void setMode(byte mode) throws IOException {
        setSetting(MODE, mode);
    }

    private void connect() throws IOException {
        socket = new Socket(inetAddr, 30046);
        oStream = socket.getOutputStream();
        iStream = socket.getInputStream();
    }

    private boolean getBooleanSetting(byte[] register) throws IOException {
        return sendRobustRequest(GET_SETTING, register)[0] != 0;
    }

    private void setSetting(byte[] register, boolean value) throws IOException {
        setSetting(register, value ? (byte) 1 : (byte) 0);
    }

    private byte getByteSetting(byte[] register) throws IOException {
        return sendRobustRequest(GET_SETTING, register)[0];
    }

    private void setSetting(byte[] register, byte value) throws IOException {
        byte[] valueArray = { value };
        sendRobustRequest(SET_SETTING, register, valueArray);
    }

    private short getShortSetting(byte[] register) throws IOException {
        byte[] result = sendRobustRequest(GET_SETTING, register);
        return (short) ((result[0] << 8) + (result[1] & 0xff));
    }

    private void setSetting(byte[] register, short value) throws IOException {
        byte[] valueArray = new byte[2];
        valueArray[0] = (byte) (value >> 8);
        valueArray[1] = (byte) value;

        sendRobustRequest(SET_SETTING, register, valueArray);
    }

    private byte[] sendRobustRequest(byte[] operation, byte[] register) throws IOException {
        return sendRobustRequest(operation, register, EMPTY);
    }

    private byte[] sendRobustRequest(byte[] operation, byte[] register, byte[] value) throws IOException {
        byte[] request = new byte[4 + value.length];
        System.arraycopy(operation, 0, request, 0, 2);
        System.arraycopy(register, 0, request, 2, 2);
        System.arraycopy(value, 0, request, 4, value.length);

        try {
            return sendRequest(request);

        } catch (IOException exIo) {
            connect();
            return sendRequest(request);
        }
    }

    private byte[] sendRequest(byte[] request) throws IOException {

        oStream.write(request);
        oStream.flush();

        byte result[] = new byte[63];
        iStream.read(result, 0, 63);

        return result;
    }
}
2 Likes

Would be awesome to get this working :slight_smile:

Ok, so at least two are interested :slight_smile:

Here my next step a simple (and not very robust) discovery routine:

    private static final byte[] DISCOVER_SEND = { 0x0c, 0x00, 0x30, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13 };
    private static final byte[] DISCOVER_RECEIVE = { 0x0d, 0x00, 0x07, 0x00, 0x02, 0x02, 0x00 };

    public static AirUnit discover() {
        // broadcast to x.x.x.255:30045

        try {
            byte[] address;
            address = InetAddress.getLocalHost().getAddress();
            address[3] = (byte) 255;
            InetAddress broadcastAddress = InetAddress.getByAddress(address);
            byte[] sendBuffer = DISCOVER_SEND;
            DatagramPacket sendPacket = new DatagramPacket(sendBuffer, sendBuffer.length, broadcastAddress, 30045);
            DatagramSocket socket = new DatagramSocket();
            socket.send(sendPacket);

            byte[] receiveBuffer = new byte[7];
            DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
            socket.receive(receivePacket);
            socket.close();
            if (Arrays.equals(receiveBuffer, DISCOVER_RECEIVE)) {
                return new AirUnit(receivePacket.getAddress());
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
1 Like

Hi all,

does anyone of you live in scandinavia where Danfoss still sells the Air Unit? They don’t give any support anymore in Germany.

The HRV-PC-Tool allows to save a diagnostiacs file. The name is HRVdiagnostics4611.hrv in my case. This is no special file format but simplay a zip which contains one xml file. Unfortunately this ZIP is password protected.

There is a good chance that danfoss would tell the password of that diagnostics file when someone calls them where they still give support.I’m pretty sure the contained XML file would help to analyze the data which is send between the HRV-PC-TOOL and the CCM of the Air Unit. I think I already have 80% of the data that is visible in the tool or can be reviewed at the AIR DIAL, but for some values I’m still searching.

Regards Ralf

Hi @leaxi,

I’ve already tried to contact them and asked for some informations about the ethernet connection, the app or if an API is available a few months ago, but they were not interested in helping me. :frowning:

Unfortunately there is not so much time for developing our own app than we needed, but our app is growing and growing. :wink: At first we’re trying to develop a java command line app, so everybody is able to use it in openHAB or other home automation systems. If it’s ready, we will publish it and would try to make an openHAB-Binding for a more comfortable use in openHAB. :slight_smile:

Greetings,
Carsten

2 Likes

Hi all,

the last days and weeks I had a few evenings for getting a bit progress on the Danfoss Air Unit Binding.

I cloned the openhab binding repository, run the script to create the binding sceleton, and implemented the basic functions.

This means discovery is working very well now.
Also the binding appears with all it’s defined channels and channel groups in the Paper UI. Even querying the values for the different channels works, as I can see in the console output, but in the Paper UI I only got a value shown in the very early stages, when I had one 1 or 2 channels defined.

Furthermore when I reload the control site from the paper UI the Danfoss Air Unit Thing disappears completely and never comes back! Only removing the thing and adding it again helps.

So from the Danfoss Air Unit controlling point, it works. I get all data that would be interesting. But putting this into a binding is more complex than I thought. Until one channel is set up completely, inside a channel group, with some state options, etc. time passed by …

At the moment I don’t feel I can complete this alone and would need help from a binding developer.

Regards,

Ralf

I have a A3 also, very interested in getting this to work. I have done some work on the Z-Wave part of this. I have a sniffer set up, but it is not currently working. The HRV does support Manufacturer_Specific so some of this data should be able to pass through Z-Wave.

It is very intriguing to go about the TCP route. Fortunately the Danfoss app is a basic .Net executable that is not obfuscated in any way.

So here is what I have learned from that:
@leaxi The zip password is: danfossHRV7100

@Rasmus7700 the service tool and the consumer tool is the same file, all you have to do is edit the HRVPCTool.exe.config file, change the ProgramMode key between Installer {E4C3938B-9F3E-427e-85CF-A42FE350326D}, Service
{FC0CB02C-1695-4064-BCD9-FC0A5D77ED3D}, and enduser {47464213-F94A-495e-81A0-486E54CB4F64} which can actually be anything.

Additionally, throw this up on GitHub so we can have a look at it.

Edit: Fixed the installer key, stupid copy paste

lol

The zip contains simply such pairs ofr request and reponse data:

RequestResponseItem Timestamp=“2017-12-16T19:48:16.6444792+01:00” Request=“000400160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000” Response=“000119520000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000” />

This is the same I already have by sniffing the network traffic using wireshark. I tought the XML file would contain the data in a more readable format …

Much more interesting is the UID for the service tool. Changing this works well and a new “service” menu appears as additional tab strip.

In this tab there is a button called “show all parameters” which lists a description of each regeister with the datatype, writeable etc. So very valueable information if we miss something which we would like to add as a channel! Many many thanks!

It is definitly time to put this on GitHub, even if not very well working, but how to I do this exactly. So far it is part of the standard openhab2 addons project in my local eclipse, but I don’t know if it is the right way to upload it there? I think we can first make a working version in an own gibhub project? How to get it there? I’m really not very experienced in git.

With the help of @petero I forked the repository created a new branch and did a commit and push for the current development state. So feel free to review.

As written above, currently the binding is not working as expected, but at least discovery should do.