rubenbe / comfospottwin40

Control your Zehnder ComfoSpot Twin 40
MIT License
9 stars 0 forks source link

Protocol Information / Decoding of Bus info #11

Open FStelzer opened 5 months ago

FStelzer commented 5 months ago

Hi, first of all thanks very much for sharing your work on this. I own some getair smartfans and this was pretty much the only reference i could find that gives some details into the protocol used. I agree that the getair smartfan seems to be the oem vendor used by all the mentioned ones (i ordered pollen filters from getair and got kermi x-well branded ones :D ). But I'm not sure if they are all based on the same hardware revision since mine seem to not match the protocol you decoded. Can you share some more information on how you worked it out? I've sniffed some data on my rs485 bus and see a different preamble (0x00, 0x55, 0x59) and then what seems to be different packet types or a mix of packet type & identifier next (either 0x69, 0x67, 0x6b, 0xd1, 0xd3). these packets usually have a static fixed length but i cannot find any field / or part of it that would indicate this length. So looking at the packet data i'm guessing in my case it's more like "$PREAMBLE $TYPE/$IDENT $DATA $XOR/CHECKSUM". Also there's quite a bit of traffic on the bus (no big packets, but consistent small ones - like heartbeats?) which i did not expect.

So i thought i would ask if you could elaborate a bit more on your understading of the protocol you reverse engineered. Even if the details changed between hardware or oem partner i don't think they will be totally different

rubenbe commented 5 months ago

Hi,

Thanks for your confirmation on the OEM branding! Information on these things is really scarce. I cannot comment on why the protocol would be different. If you find version numbers on the controller, you could share them? Is it also exactly the comfospot40 style fans? (there are some lookalikes IIRC )

So the protocol effectively timeshares between the controller and the sensors/fans. The controller sends the desired fan speed for a zone (1 to 6). And then waits a bit to receive the sensor value. Then it does the same for the next zone. When it has looped over all the zones, it will redo this. forever.

Strictly seen this it not necessary, since the fan will remember its speed until it is powered off. It is only required to fetch the sensor values. The 6 zones are actually the 3 on the DIP switches, and the "counter" dip switch adds the other 3. So protocol wise you could have more zones than 6 zones or run the "counter" spinning fans as a real dedicated zone.

There is a known issue in my code with multiple sensors in one zone. Unfortunately I broke my second sensor so I can't really investigate & solve.

I never fully reverse engineered the checksum protocol. It is a super simple sum, but there is a weird thing when there is a zero byte (since the checksum would not change if you only have 00 bytes in a packet). In the end I chose to ignore the checksum, since it is fairly stable. Maybe I should ask an AI these days if it can find the pattern :D (If you find the packet boundaries, feel free to dump the data here, it might be interesting)

I might do more posts, since it's been a while since I did the original reverse engineering. (And update the README with more info)

If you go back more in history, you'll find more of the scripts that I used to check my understanding of the protocol while reverse engineering. https://github.com/rubenbe/comfospottwin40/blob/9afbad88644a78e66a681fb7ff265994885ad127/reverser.py You might also want to look at an old version of reader.py (I removed it since I'm no longer using it and was broken) Feel free to revive the thing and re-add it in a PR.

This test contains my understanding of the protocol on a packet level. https://github.com/rubenbe/comfospottwin40/blob/master/tests/packet_test.py

FStelzer commented 5 months ago

Thanks a lot. I'll take a look at the code and do some more analysis on the data. Actually i ran the hexdump through chatgpt & claude and it gave some pretty useful info (found the packet preambles, made some guesses about packet types & fields). Often it makes a wrong assumption (for example it took FF0055 for the preamble because packets often end in FF but not always) and follows an obviously wrong trail, but if you correct it it was quite useful. I also had it try most of the common xor/crc/adler checksums on the packet endings using various byte ranges of the packet and it concluded it's probably none of these only xor gave some results but not consistent. I'll post some more info / packet dumps when i have time to work on it in the next few days.

FStelzer commented 4 months ago

I didn't have too much time yet to work more on it. But here's some packet data from my getair system. I think these are the controller packets: Fans Level = 0 0055596b7b6f9fc7fffd99d3d3abff 0055596b7b6f9dc7dffd99d7d3c9ff 0055596b7b6f9bc7dffbc3d3d3a7ff 0055596b7b6f99c7fffdc3d3d387ff

Fan Level = 25% 0055596b7b6f9fdcfd99d3d3ab 0055596b7b6f9ddcd799d7d3c9 0055596b7b6f9bdcb7c3d3d3a7 0055596b7b6f99dcfdc3d3d387

Fan Level = 50% 0055596b7b6f9fdcfd99d3d3ab 0055596b7b6f9ddcd799d7d3c9 0055596b7b6f9bdcb7c3d3d3a7 0055596b7b6f99dcfdc3d3d387

Fan Level = 75% 0055596b7b6f9f5cfd99d3d3b1 0055596b7b6f9ddcd799d7d3c9 0055596b7b6f9bdcb7c3d3d3a7 0055596b7b6f99dcfdc3d3d387

Fan Level = 100% 0055596b7b6f9fb8fd99d3d3b1 0055596b7b6f9ddcd799d7d3c9 0055596b7b6f9bdcb7c3d3d3a7 0055596b7b6f99dcfdc3d3d387

I've omitted any response packets in between. But you can clearly see the looping over 4 zones. It's very weird that i only see this high values (in the sensor responses as wel). So my guess is that they apply some transformation to their packet data. Probably to make the different oem partners devices incompatible to each other (They don't want you to use a Kermi controller in a Viesmann system). Since your preample was 00554D and mine is 005559 i would guess that the 59 represents some oem partner id and is probably also the key/offset for the obfuscation. I tried to have an AI apply various xor/offset/bitshift operations to it but witouth much success yet.

rubenbe commented 4 months ago

Thanks for the dump! Hmm, it would make sense from an OEM client's POV to make them incompatible. Have you tried flipping the wires? RS485 is valid in both "orientations". 0xFF become 0x00, but 0x55 remains 0x55, so that has confused me in the past too. I tried manually flipping some bits from your packet dump, but I couldn't immediately see the pattern (although I also didn't have a lot of time)

FStelzer commented 4 months ago

ok... you were right. i was fooled by seeing the 0x55 being the correct preamble when inverting the polarity produces the same result :D swapping the wires gives much more similar data to yours at first glance. i'll look more into it and see if there's anything different for the getair vendor

FStelzer commented 4 months ago

def calculate_checksum(packet): total = sum(packet[:-1]) total = ~total + 0x56 return bytes([total & 0xFF])

this checksum seems to work for me for every packet i've seen so far. the rest of the protocol seems fairly identical. i'll still have to try switching the various settings on the controller but most looks good.

with the 0x96 & 0x97 commands (i can see both with identical payload) i assume those are maybe an older hardware revision / protocol version?

i can also see different packets on the bus that loop just like the other controller packets. no idea what those are. 1720705636 SUM 554d00 ca090802b01e00021e1616bc {} 1720705636 SUM 554d00 ca090802b11c00011e1416c0 {} 1720705636 SUM 554d00 ca090802b21c00011e1616bd {} 1720705636 SUM 554d00 ca090802b31c00011e1616bc {} 1720705636 SUM 554d00 cb050803011f14a4 {} 1720705636 SUM 554d00 cb050803021f16a1 {} 1720705636 SUM 554d00 cb050803031f16a0 {} 1720705636 SUM 554d00 cc0908040301020000086b59 {}

those look like they are polling over some devices as well, but not really as straight forward as the others. maybe device/setting states to display on other controllers on the bus? at least i assume that 554D is the controller talking and 5553 are the fans.

rubenbe commented 4 months ago

Interesting! I had offset 0x57 (I don't remember why I chose that value). The rest of the calculation is pretty much the same. I'll try to add adjust my checksum function. There is the sensor data on the bus too (sent by the fan). The controller settings stay on the device itself IIRC.

FStelzer commented 4 months ago

i think the difference is the bitwise NOT (~total) that i don't see in your checksum. this might be what made you see the weird behaviour around some 0's in the sum.

i can see & read the sensor values just fine. they match the protocol you implemented :)

Do you know why the fans speeds for one zone always differ? Like 25% is one fan 25 and the othe 27, For 50% it's 44 & 47, 75% is 60 & 78 and 100% 93 & 99. Maybe the fans transport less volume running in one direction than the other and they compensate a bit for that?

rubenbe commented 4 months ago

When I have time, I'll update the checksumming code. (PRs are welcome if you would have time too ;) )

That's good news!

My guess is as good as yours, but I'm convinced that's the reason indeed. They probably ran some tests in a lab and hardcoded the values afterwards. I don't think I've implemented this actually.