Bouni / python-luxtronik

python-luxtronik is a library that allow you to interact with a Luxtronik heatpump controller.
MIT License
37 stars 20 forks source link

Push value changes / callbacks #38

Closed BenPru closed 1 year ago

BenPru commented 1 year ago

Sockets and websockets can push messages / data from server to client. I looked at the luxtronik web interface in the firmware and recreated it as a python test. Unfortunately they poll the data every second instead a websocket event new data or something else.

Do you know if the socket api (8889) can server events?


Btw.: Have you an idea how to match socket api and websocket api values? For example the flow in and out temperatures are calculations (3004) 10 and 11 in the socket. In the websocket the flow in and out temperatures are in group Temperaturen (@id=0x0x594240) with @id 0x0x598308 and 0x0x58d5e0. Do you know how to match them? 3004.10 <--> 0x0x598308

Bouni commented 1 year ago

As far as I can tell from my reverse engeneering they only poll data, the heatpump never sends data on its own. The only case where that maybe happens is the data thet gets sent to heatpump24.com but thats useless to us.

I never used the Websockets API (only in my node red integration https://github.com/Bouni/node-red-contrib-luxtronik2-ws) because I never figured out how to work with the IDs properly as the change on every request.

And the socket api works very well for me ...

BenPru commented 1 year ago

Yes, the websocket seems to be very stable but slower as the sockets. I'm currently brute forcing it. Perhaps I find another login as 999999 which provides more informations.

kbabioch commented 1 year ago

I'm still trying to understand what the issue is that you're trying to solve with the websockets. Why would we need to match the information from the websocket to the information of the old TCP socket API? Isn't it an "either or", so either we're going to use the reverse engineered TCP socket, or the websocket. Or what am I missing here?

BenPru commented 1 year ago

It is possible to crash the heatpump using sockets. Websockets seems to be stable. E.g. firmware revision is only provided by websocket not via socket. --> Need for the update sensor for firmware version check.

gerw commented 1 year ago

I am currently parsing the websocket interface to log some data of my heat pump. I can conform that it is also possible to crash the heat pump via the websocket interface. If I recall correctly, one has to send "GET;$id" with a wrong "$id".

The websocket interface provides some more information if you log in as "Installateur" or as "Kundendienst". As Installateur, you see additionally the (current and desired) "Spreizung" of the heat circuit and of the heat source. As "Kundendienst", you also get more temperatures: "Vorlauf Soll", "Verdampfungstemp. d" and "Verflüssigungstemp. b"; and "Freq. min." ,"Freq. max." under "Ausgänge". It would be interesting to locate these temperatures in the calculations of the socket interface.

As "Kundendienst", you also see some Logs: An "Info Log" (which for me only contains timestamps with the message "Eltwin Log" and the id 32) and an "EventLog" in which changes of parameters are logged (but not if the parameter was changed via the socket interface).

I doubt that there is another password than "999999" (which is configurable) to log into the websocket interface. I can try to check whether I can confirm this by looking into the firmware.

BenPru commented 1 year ago

It would be interesting to locate these temperatures in the calculations of the socket interface.

Yes. I think the socket interfaces provides the same informations but the websocket has for each value the name of the value.

The websocket interface provides some more information if you log in as "Installateur" or as "Kundendienst"

Do you mean heatpump24.com or the local web interface (which uses the websocket)? For now I have tried 0-999999 and were was no other login as the customer (999999). Every unknown login gets the default readonly data. I check every login and check which gets more data like 999999. Now I try > 999999 but the websocket is slow....

I doubt that there is another password than "999999" (which is configurable) to log into the websocket interface.

Do you know how an "Installateur"/"Kundendienst" logins? There is only one password input without a user name.

I can try to check whether I can confirm this by looking into the firmware.

That would be awesome.

gerw commented 1 year ago

I mean the local web interface (websocket on my heat pump). You first have to log in with 999999 (or your custom password) and then under "Zugang: Benutzer" you can enter the Installateur- or Kundendienst-Passwort.

In the firmware, I have found the location at which the login request (on the websocket) is evaluated, by I did not understand the assembler, yet.

kbabioch commented 1 year ago

In the firmware, I have found the location at which the login request (on the websocket) is evaluated, by I did not understand the assembler, yet.

Which tool are you using? ghidra is super powerful and able to de-compile most of the assembly listing into readable C code.

I was able to find / verify some interesting aspects in the code (passwords for login on heat pump itself). Haven't looked at the Websocket aspect yet, though. Do you have a location where the password is evaluated?

BenPru commented 1 year ago

I was able to find / verify some interesting aspects in the code (passwords for login on heat pump itself). Haven't looked at the Websocket aspect yet, though. Do you have a location where the password is evaluated?

Do you have the ssh login?

kbabioch commented 1 year ago

Had a (quick) look. Could find the following passages related to password handling and the webserver:

Somewhere at around 00098d18:

void Init_Webserver_Password(void)

{
  if ((999999 < WebserverPassword) || (G_werte_neu != '\0')) {
    storeParameter(0x2f3,9,(int *)0x0);
    storeParameter(0x2f4,9,(int *)0x0);
    storeParameter(0x2f5,9,(int *)0x0);
    storeParameter(0x2f6,9,(int *)0x0);
    storeParameter(0x2f7,9,(int *)0x0);
    storeParameter(0x2f8,9,(int *)0x0);
  }
  return;
}

Somewhere around 001e740c:


/* WARNING: Could not reconcile some variable overlaps */
/* WSS_Client::processTraffic(char*, unsigned int) */

undefined4 WSS_Client::processTraffic(char *param_1,uint param_2)

{
  int iVar1;
  undefined4 uVar2;
  char cVar3;
  undefined uVar4;
  bool bVar5;
  void *local_10;
  int local_c;

  iVar1 = sscanf((char *)param_2,"LOGIN;%d",&local_c);
  if (iVar1 != 0) {
    iVar1 = WebserverPassword;
    if (local_c != WebserverPassword) {
      iVar1 = 0;
    }
    cVar3 = (char)iVar1;
    if (local_c == WebserverPassword) {
      cVar3 = '\x01';
    }
    param_1[0x14] = cVar3;
    uVar2 = sendPage((WSS_Client *)param_1,(void *)0x0,4);
    return uVar2;
  }
  iVar1 = sscanf((char *)param_2,"GET;0x%p",&local_10);
  if (iVar1 != 0) {
    uVar2 = sendPage((WSS_Client *)param_1,local_10,4);
    return uVar2;
  }
  iVar1 = strcmp((char *)param_2,"REFRESH");
  if (iVar1 == 0) {
    uVar2 = sendRefresh((WSS_Client *)param_1);
    return uVar2;
  }
  if (param_1[0x14] == '\0') {
    return 0;
  }
  iVar1 = sscanf((char *)param_2,"SET;set_0x%p;%d",&local_10,&local_c);
  if (iVar1 == 2) {
    uVar2 = setParam((WSS_Client *)param_1,local_10,local_c);
    return uVar2;
  }
  iVar1 = sscanf((char *)param_2,"SAVE;%d",&local_c);
  if (iVar1 == 1) {
    bVar5 = local_c != 1;
    if (bVar5) {
      local_c = 0;
    }
    uVar4 = (undefined)local_c;
    if (!bVar5) {
      uVar4 = true;
    }
    uVar2 = saveParams((WSS_Client *)param_1,(bool)uVar4);
    return uVar2;
  }
  if (Einst_RemoteControl != 0) {
    iVar1 = sscanf((char *)param_2,"MOVE;%d",&local_c);
    if (iVar1 == 1) {
      bVar5 = true;
      goto LAB_001e7674;
    }
  }
  bVar5 = false;
LAB_001e7674:
  if (!bVar5) {
    return 0;
  }
  uVar2 = performRemoteMove((WSS_Client *)param_1,(uchar)local_c);
  return uVar2;
}

This is basically the main function that deals with processing the websocket commands. Essentially there is only a few commands:

The format for this command relates to format strings that are used by sscanf.

Based on this, it seems to be that there is only one password, which is initialized to 999999. Every digit of this password is saved parameter (0x2f3 - 0x2f8, e.g. 755 - 760).


When I login with 999999 to the websocket interface, it looks like this:

grafik

As you can see, I'm logged in as Benutzer.

I can than log in as Installateur by using 9445 as password in the Zugang tab (9445 is the "secret" password to become Installateur that can also be used locally on the Luxtronik device. It's hard-coded into the firmware):

Screenshot 2023-01-15 at 14-30-25 Heatpump controller

Afterwards the main page looks like this:

Screenshot 2023-01-15 at 14-31-57 Heatpump controller

There are some additional data points when using Installateur (e.g. Abtaubedarf), but overall it looks quite similar.

kbabioch commented 1 year ago

Do you have the ssh login?

Yes, although I haven't originally discovered it. Details can be found here: https://github.com/Bouni/luxtronik-firmware-analysis

To be honest: The SSH login wasn't too helpful so far. The application itself can be extracted from the downloadable firmware package. Being root on the heat pump is cool, of course, but essentially its a minimalistic Linux environment running - more or less - one binary that exposes all of the services to the network.

gerw commented 1 year ago

To be honest: The SSH login wasn't too helpful so far. The application itself can be extracted from the downloadable firmware package. Being root on the heat pump is cool, of course, but essentially its a minimalistic Linux environment running - more or less - one binary that exposes all of the services to the network.

The firmware can be even fetched from the heat pump itself: it is served by the webserver under http://my_heatpump/appl...

kbabioch commented 1 year ago

The firmware can be even fetched from the heat pump itself: it is served by the webserver under http://my_heatpump/appl...

Nice finding, haven't even tried that. Essentially you can download all files from within /home, including the passwd and shadow:

http://my_heatpump/share/shadow and http://my_heatpump/share/passwd.

ls dump:

# pwd
/home
# ls -R
.:
10Min_1                  LWD.lin                  ParamArchive_1673222400  SWPH291.lin              index.html               lang_it                  lang_slo
10Min_2                  LWD45.lin                ParamArchive_1673308800  Webserver                lang_CR                  lang_lt                  lang_suo
ASB.bin                  LWD90.lin                ParamArchive_1673395200  appl                     lang_cz                  lang_lv                  lang_sve
ASB_BL_Switch.bin        LWDRev.lin               ParamArchive_1673481600  appl.cfg                 lang_dan                 lang_mag                 lang_tr
ASB_bootloader.bin       LuxConst.sqlite          ParamArchive_1673568000  appl_param1              lang_de                  lang_ned                 share
Defines.txt              MSW_15.lin               ParamArchive_1673654400  appl_param2              lang_ee                  lang_nor                 timezone
GLT.conf                 MSW_Inverter.lin         ParamArchive_1673740800  bootloader.lin           lang_en                  lang_p                   udhcpc.script
HZIO.lin                 NewProc                  SEC.bin                  default.sqlite           lang_es                  lang_pol
Info.dti                 ParamArchive_1672617600  SWP.lin                  default.sqlite-wal       lang_fr                  lang_ro
LD2AG.lin                ParamArchive_1673136000  SWPH.lin                 firmware                 lang_hr                  lang_sk

./Webserver:
Lux.js        LuxSim.jpg    base.css      index.html    jquery.js     saveIcon.png

./share:
localtime  passwd     shadow

All of this are interesting findings, I'm currently documenting them. Probably not too relevant for this topic, though.

gerw commented 1 year ago

Yes, I also found the passwd and the shadow file... It is scary that the heat pump exposes its own root password...

kbabioch commented 1 year ago

Yes, I also found the passwd and the shadow file... It is scary that the heat pump exposes its own root password...

Well, security-wise this thing is a good demonstration on how to not to it. Basically they are doing every mistake you can make. Feels like its the 90's again in terms of IT security.

On the other hand: For our purpose this is very good, as the official API (GLT Lizenz) does cost money and is not really useful/powerful.

kbabioch commented 1 year ago

By the way: Coming back to password and access codes:

This is the passage (after some de-compiling) that deals with access codes and contains the secrets:

void CheckPassword(ushort password)
{
  int access_level;

  if (password == 7311) {
    access_level = 1;
  }
  else if (password == 9445) {
    access_level = 3;
  }
  else {
    access_level = 0;
  }
  storeParameter(107, access_level, (int *)0x0);
  return;
}

At least in terms of entering codes there is only those: 9445 and 7311.

There is another way by using USB sticks. That way you can get Kundendienst and Werksdienst access levels, but that's not too helpful here.

kbabioch commented 1 year ago

Following up on those access codes:

Here you have additional options:

grafik

As you can see there are Kurzprogramme. That way you can heat up the water or activate Abtauen.

Also there are is way more information in the web interface:

grafik

@BenPru Maybe this is helpful to you?

kbabioch commented 1 year ago

I've also tried this with access level manufacturer (i.e. Werk):

grafik

There is some additional data compared to after sales service (= Kundendienst), e.g. Target flow or Defrost End.

Also, since I figured out how the access level is managed internally, I've prepared a pull request to set this remotely very easily without the need to enter any codes and/or inserting specially crafted USB sticks. You'll find the details in #42.

BenPru commented 1 year ago

@BenPru Maybe this is helpful to you?

Thanks a lot. Very interesting. Unfortunately, this doesn't make a big difference with my Novelan LAD 7. Only the values ​​Verdampfungstemp., Verflüssigungstemp.​​ and ​​Abtauwunsch​​ have been added. I will include this in my integration. Then I covered (at least for my LAD 7) all readable and the most important writable values ​​of the Luxtronik web interface in the integration. The categories BMS, Smart and Info Log doesn't exist in my case.

Should the integration have a toggle for param 107?

The download http://my_heatpump/appl is the raw firmware and not the same as the download package from the ait download page? I can create a link in the integration to this and allow a backup. But a raw file is not useful for a normal user.

kbabioch commented 1 year ago

I will include this in my integration. Then I covered (at least for my LAD 7) all readable and the most important writable values ​​of the Luxtronik web interface in the integration.

Are you already using information from the web socket interface in your integration? Thought you're still using the old API and are only experimenting with the new websocket stuff.

The categories BMS, Smart and Info Log doesn't exist in my case.

Interesting, no idea, I didn't do anything special to get them activated.

Should the integration have a toggle for param 107?

Well, I'm not sure about this. At least the highest level (manufacturer = Werk) gives you a lot of options (locally). Apparently you can even change some (serial) numbers, etc. Probably you can seriously break something, if you don't know what you're doing.

For the sake of security and robustness I would leave it out to be honest for now. You could mention it in the documentation and/or wait for people to find out and ask for it. But giving them full privileges without them asking for it, might go wrong.

The download http://my_heatpump/appl is the raw firmware and not the same as the download package from the ait download page?

The download package contains many files, including the appl binary. The appl is just the binary that is being run. You can easily extract the firmware yourself. This is well described here: https://github.com/Bouni/luxtronik-firmware-analysis

I don't think that a download / backup is needed for normal operation. Nothing to gain from it, so I would not put it into your integration. There are some other (configuration) file within /home that might be more relevant, but to be honest, I don't see a benefit for the HA integration to add links to those, etc.

BenPru commented 1 year ago

Are you already using information from the web socket interface in your integration? Thought you're still using the old API and are only experimenting with the new websocket stuff.

No, I don't use the websocket in the integration. Just playing and looking into it.