3

I'm trying to reverse engineer the RS485 protocol used by my home heater/AC.

This protocol handles communications between the wired remote control (WRC)/thermostat and the main heater/ac indoor unit (IU). My goal is to eventually be able to mock the indoor unit in software so that I can further reverse-engineer the remote control functions.

The RS485 bus runs at 2400 baud, 8N1. The packet framing format is quite simple. Packets can be either 7 (short packet) or 14 (long packet) bytes long. Packets always start with 0x32 and end with 0x34. The 2nd-last byte of the packet is a checksum of bytes 0 through (end - 3) xor'd.

Packets contain a source and destination address byte, and a command ID byte, followed by 1 (short packet) or 8 (long packet) data bytes.

Byte Comment
0 0x32
1 Source address
2 Destination address
3 Command ID
4...end - 3 Data (length = 1 or 8 bytes)
end - 2 Checksum
end - 1 0x34

The WRC seems to initiate all communications on the bus. The WRC will send a packet to an IU, and that IU will reply to the WRC.

The WRC is always address 0x84. An optional second WRC can be connected (not present in my tests), with address 0x85. The IU address can vary, as multiple units can be controlled from a single WRC. The base address is 0x20 (for unit ID 0). Other units will be 0x21, 0x22, etc. (though I believe this address is programmable).

If I connect an RS485 to USB (or IP) bridge to the bus I'm able to successfully decode the data packets sent between the WRC and IU during normal operation. For example:

        ┏━━ Source Address
        ┃  ┏━━ Destination Address
        ┃  ┃  ┏━━ Command ID
        ┃  ┃  ┃  ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━ Data
        ┃  ┃  ┃  ┃  ┃  ┃  ┃  ┃  ┃  ┃  ┃  ┏━━ Checksum
001: 32 84 20 52 00 00 00 00 00 00 00 00 f6 34
002: 32 20 84 52 49 4a 4b fd 81 00 00 4a 88 34
003: 32 84 20 53 00 00 00 00 00 00 00 00 f7 34
004: 32 20 84 53 00 00 00 00 1f 00 00 04 ec 34

Here, #1 is the WRC (0x84) sending command 0x52 to the IU (0x20). #2 is the IU responding to the WRC. #3 and #4 is similar, for command 0x53.

Where I'm struggling is with the initial startup/boot handshake/"training" sequence, which I need to emulate in-order to mock the IU to the WRC.

Here is a capture of the startup sequence, from when the IU, and by extension the WRC, is powered on (the WRC gets power from the IU).

Spacing added for clarity, showing "conversations".

      ┏━━ Monotonic timestamp (seconds)
      ┃           ┏━━ Delta timestamp (since last packet, seconds)
001: [000.000000, 000.000000] 00 33 af d1 d0 d5 ff ff ff ff ff ff 81 97 00 33 af d1 d0 d5 ff ff ff ff ff ff 81 97
002: [000.000021, 000.000021] 32 84 eb fb 02 00 00 00 00 00 00 00 96 34 [0x96 checksum valid]
003: [002.280395, 002.280374] 32 22 84 fc fa a0 34 [0xa0 checksum valid]
004: [008.991373, 006.710978] 32 84 eb f9 00 96 34 [0x96 checksum valid]

005: [011.731233, 002.739860] 00 33 af d1 d0 d5 ff ff ff ff ff ff 81 97 00 33 af d1 d0 d5 ff ff ff ff ff ff 81 97
006: [011.731254, 000.000021] 32 84 eb fb 02 00 00 00 00 00 00 00 96 34 [0x96 checksum valid]
007: [013.811966, 002.080712] 32 1f 84 fc b7 d0 34 [0xd0 checksum valid]
008: [020.722476, 006.910510] 32 84 eb f9 00 96 34 [0x96 checksum valid]

009: [023.468482, 002.746006] 00 33 af d1 d0 d5 ff ff ff ff ff ff 81 97 00 33 af d1 d0 d5 ff ff ff ff ff ff 81 97
010: [023.468515, 000.000033] 32 84 eb fb 01 00 00 00 00 00 00 00 95 34 [0x95 checksum valid]
011: [026.505220, 003.036705] 32 2e 84 fc 72 24 34 [0x24 checksum valid]
012: [032.443750, 005.938530] 32 84 2e fd 00 57 34 [0x57 checksum valid]
013: [032.494872, 000.051122] 32 2e 84 fe 00 54 34 [0x54 checksum valid]
014: [032.663648, 000.168776] 32 84 eb f9 00 96 34 [0x96 checksum valid]

015: [032.815016, 000.151368] 32 84 eb fb 00 00 00 00 00 00 00 00 94 34 [0x94 checksum valid]
016: [032.913647, 000.098631] 32 00 84 fc 71 09 34 [0x09 checksum valid]
017: [041.794678, 008.881031] 32 84 00 fd 00 79 34 [0x79 checksum valid]
018: [041.844613, 000.049935] 32 00 84 fe 00 7a 34 [0x7a checksum valid]
019: [042.014723, 000.170110] 32 84 eb f9 00 96 34 [0x96 checksum valid]

020: [044.364866, 002.350143] 00 33 af d1 d0 d5 ff ff ff ff ff ff 81 97 00 33 af d1 d0 d5 ff ff ff ff ff ff 81 97
021: [044.364886, 000.000020] 32 84 eb fb 01 00 00 00 00 00 00 00 95 34 [0x95 checksum valid]
022: [053.466376, 009.101490] 32 84 eb f9 00 96 34 [0x96 checksum valid]

023: [053.621133, 000.154757] 32 84 eb fb 00 00 00 00 00 00 00 00 94 34 [0x94 checksum valid]
024: [053.716330, 000.095197] 32 00 84 fc 2c 54 34 [0x54 checksum valid]
025: [062.596967, 008.880637] 32 84 00 fd 00 79 34 [0x79 checksum valid]
026: [062.650699, 000.053732] 32 00 84 fe 00 7a 34 [0x7a checksum valid]
027: [062.818408, 000.167709] 32 84 eb f9 00 96 34 [0x96 checksum valid]

028: [065.167061, 002.348653] 00 33 af d1 d0 d5 ff ff ff ff ff ff 81 97 00 33 af d1 d0 d5 ff ff ff ff ff ff 81 97
029: [065.167082, 000.000021] 32 84 eb fb 01 00 00 00 00 00 00 00 95 34 [0x95 checksum valid]
030: [074.268267, 009.101185] 32 84 eb f9 00 96 34 [0x96 checksum valid]

031: [074.408234, 000.139967] 32 84 eb fb 00 00 00 00 00 00 00 00 94 34 [0x94 checksum valid]
032: [074.518299, 000.110065] 32 00 84 fc b5 cd 34 [0xcd checksum valid]
033: [083.389394, 008.871095] 32 84 00 fd 00 79 34 [0x79 checksum valid]
034: [083.457872, 000.068478] 32 00 84 fe 00 7a 34 [0x7a checksum valid]
035: [083.619351, 000.161479] 32 84 eb f9 00 96 34 [0x96 checksum valid]

036: [083.759426, 000.140075] 32 84 20 52 00 00 00 00 00 00 00 00 f6 34 [0xf6 checksum valid]
037: [083.852692, 000.093266] 32 20 84 52 49 4a 4b fd 81 00 00 4a 88 34 [0x88 checksum valid]
038: [083.946021, 000.093329] 32 84 21 52 00 00 00 00 00 00 00 00 f7 34 [0xf7 checksum valid]
039: [084.121026, 000.175005] 32 84 22 52 00 00 00 00 00 00 00 00 f4 34 [0xf4 checksum valid]
040: [084.309237, 000.188211] 32 84 23 52 00 00 00 00 00 00 00 00 f5 34 [0xf5 checksum valid]
041: [084.489924, 000.180687] 32 84 24 52 00 00 00 00 00 00 00 00 f2 34 [0xf2 checksum valid]
<snip>
100: [095.130622, 000.174152] 32 84 6f 52 00 00 00 00 00 00 00 00 b9 34 [0xb9 checksum valid]

101: [095.319262, 000.188640] 32 84 85 c5 22 00 00 00 00 00 00 00 e6 34 [0xe6 checksum valid]
102: [095.505663, 000.186401] 32 84 85 c4 00 01 00 00 00 00 00 44 80 34 [0x80 checksum valid]
103: [095.670355, 000.164692] 32 84 c9 c6 22 01 20 b0 00 00 00 00 38 34 [0x38 checksum valid]
104: [095.857165, 000.186810] 32 84 c9 c4 00 01 00 00 00 00 00 44 cc 34 [0xcc checksum valid]

105: [096.223427, 000.366262] 32 84 20 54 00 00 00 00 00 00 00 00 f0 34 [0xf0 checksum valid]
106: [096.317298, 000.093871] 32 20 84 54 21 00 36 10 20 00 1f 00 c8 34 [0xc8 checksum valid]
107: [096.411133, 000.093835] 32 84 21 54 00 00 00 00 00 00 00 00 f1 34 [0xf1 checksum valid]
108: [096.570502, 000.159369] 32 84 22 54 00 00 00 00 00 00 00 00 f2 34 [0xf2 checksum valid]
109: [096.756579, 000.186077] 32 84 23 54 00 00 00 00 00 00 00 00 f3 34 [0xf3 checksum valid]
110: [096.930744, 000.174165] 32 84 24 54 00 00 00 00 00 00 00 00 f4 34 [0xf4 checksum valid]
111: [097.116138, 000.185394] 32 84 25 54 00 00 00 00 00 00 00 00 f5 34 [0xf5 checksum valid]
<snip>
169: [107.583592, 000.176422] 32 84 6f 54 00 00 00 00 00 00 00 00 bf 34 [0xbf checksum valid]

170: [107.761909, 000.178317] 32 84 85 c5 22 00 00 00 00 00 00 00 e6 34 [0xe6 checksum valid]
171: [107.945394, 000.183485] 32 84 85 c4 00 01 00 00 00 00 00 44 80 34 [0x80 checksum valid]
172: [108.121951, 000.176557] 32 84 c9 c6 22 01 20 b0 00 00 00 00 38 34 [0x38 checksum valid]
173: [108.301865, 000.179914] 32 84 c9 c4 00 01 00 00 00 00 00 44 cc 34 [0xcc checksum valid]

174: [108.661995, 000.360130] 32 84 20 52 00 00 00 00 00 00 00 00 f6 34 [0xf6 checksum valid]
175: [108.758394, 000.096399] 32 20 84 52 49 4a 4b fd 81 00 00 4a 88 34 [0x88 checksum valid]
176: [108.848289, 000.089895] 32 84 ad d1 01 00 00 00 00 00 00 00 f9 34 [0xf9 checksum valid]

177: [109.202152, 000.353863] 32 84 20 53 00 00 00 00 00 00 00 00 f7 34 [0xf7 checksum valid]
178: [109.308359, 000.106207] 32 20 84 53 00 00 00 00 1f 00 00 04 ec 34 [0xec checksum valid]
179: [109.401930, 000.093571] 32 84 ad d1 01 00 00 00 00 00 00 00 f9 34 [0xf9 checksum valid]
...

After power on the WRC begins sending sequences of packets, first to address 0xeb (which appears to be a "broadcast" address), starting with command ID 0xfb (#2). Then after about 2 seconds something responds (#3) from a seemingly random source address, with random data, yet the destination is correct (0x84), the command ID looks reasonable (0xfc is one-more than 0xfb, perhaps a "reply"?) and the checksum is valid.

These replies must come from the IU as the only two devices on the bus are the WRC and IU (and my RS485 tap, but that is currently passive). If I power on the WRC separately, without the IU connected, I can see the same packets from the WRC address being sent, but none of the "random" replies.

These sequences eventually end with a 0xf9 command from the WRC, and then repeat.

At #16 the 0xfc reply instead comes from address 0x00 and the WRC replies with command 0xfd (#17). 0x00 then replies with 0xfe (#18). This happens again at #23-#27 and #31-#35.

After a number of these longer sequences the WRC starts to scan the bus for known indoor unit addresses. It enumerates addresses 0x20 to 0x6f by sending command ID 0x52 (#36-#100). As only one indoor unit is connected in my scenario, a reply is only sent from 0x20 (#37). Other addresses receive no reply.

The WRC then sends commands 0xc5 and 0xc4 to the secondary WRC address 0x85 (#101, #102). As there is no secondary remote connected there is no reply.

Unsure yet what packets #103 and #104 are for.

Then another scan of addresses 0x20 to 0x6f starts, this time with command ID 0x54 (#105-#169).

Packets #170-#173 are second remote (0x85) comms again and unknown (0xc9).

At this point the WRC has discovered all IU's connected to the bus and begins normal operation: sequences of request-response (#174, #175), followed by a packet sent to 0xad (broadcast?) with command ID 0xd1 (#176).


These packets are also interspersed with data that I'm unable to decode, which on deeper investigation is valid data that has been inverted at the line/logic level:

001: 32 84 eb fa aa 00 00 00 00 00 00 00 3f 34
002: 32 84 eb fa aa 00 00 00 00 00 00 00 3f 34

I was able to locate the service manual for the WRC, which conveniently contains a schematic diagram. I've included the relevant cropped portion below.

WRC schematic, RS485 section

The main processor (micom) UART RX & TX pins (UART0_RX and UART0_TX) are connected to a Renesas ISL3175EIBZ RS485 Transceiver (IC04) via an Onsemi MM74HC86M Quad 2-Input Exclusive OR Gate (IC05). Only 2 of the 4 XOr gates are used, one for the RX line, the other for the TX. One half of each XOr gate is wired to a GPIO pin (INV_COM2) on the micom. My understanding is that this would allow the micom to invert all incoming and outgoing data by toggling this GPIO, presumably as a way to handle/detect incorrect wiring.

My (possibly incorrect) understanding is that this inverted data appears unreadable to me as not only the data bits are inverted, but also the start/stop bits. This results in the receiver in my bridge device interpreting data bits as stop bits or vice-versa.

Inverting the A/B connections to my RS485 bridge lets me decode these inverted packets, with the obvious side effect that now all other data is unreadable.


I've recorded the data from multiple boots of the WRC. The packets the WRC sends are always identical. Yet the reply packet source addresses and data values are, to my eye, random.

The inverted 0xfa packets are always:

001: 32 84 eb fa aa 00 00 00 00 00 00 00 3f 34

The 0xfb packets always follow this sequence:

001: 32 84 eb fb 02 00 00 00 00 00 00 00 96 34 [0x96 checksum valid]
002: 32 84 eb fb 02 00 00 00 00 00 00 00 96 34 [0x96 checksum valid]
003: 32 84 eb fb 01 00 00 00 00 00 00 00 95 34 [0x95 checksum valid]
004: 32 84 eb fb 00 00 00 00 00 00 00 00 94 34 [0x94 checksum valid]
005: 32 84 eb fb 01 00 00 00 00 00 00 00 95 34 [0x95 checksum valid]
006: 32 84 eb fb 00 00 00 00 00 00 00 00 94 34 [0x94 checksum valid]
007: 32 84 eb fb 01 00 00 00 00 00 00 00 95 34 [0x95 checksum valid]
008: 32 84 eb fb 00 00 00 00 00 00 00 00 94 34 [0x94 checksum valid]

Yet, the 0xfc packets that come as replies to the above 0xfb packets are always different. Here are the sequences for 4 separate boots of the WRC:

boot #1
001: 32 1a 84 fc ee 8c 34 [0x8c checksum valid]
002: 32 05 84 fc cd b0 34 [0xb0 checksum valid]
003: 32 5d 84 fc 71 54 34 [0x54 checksum valid]
004: 32 00 84 fc 42 3a 34 [0x3a checksum valid]
005: 32 00 84 fc 22 5a 34 [0x5a checksum valid]
006: 32 00 84 fc 41 39 34 [0x39 checksum valid]

boot #2
007: 32 21 84 fc 0d 54 34 [0x54 checksum valid]
008: 32 36 84 fc 26 68 34 [0x68 checksum valid]
009: 32 4d 84 fc 7d 48 34 [0x48 checksum valid]
010: 32 00 84 fc 7d 05 34 [0x05 checksum valid]
011: 32 00 84 fc 2c 54 34 [0x54 checksum valid]
012: 32 00 84 fc b8 c0 34 [0xc0 checksum valid]

boot #3
013: 32 1d 84 fc 69 0c 34 [0x0c checksum valid]
014: 32 1b 84 fc a7 c4 34 [0xc4 checksum valid]
015: 32 16 84 fc 26 48 34 [0x48 checksum valid]
016: 32 00 84 fc f4 8c 34 [0x8c checksum valid]
017: 32 00 84 fc 54 2c 34 [0x2c checksum valid]
018: 32 00 84 fc b7 cf 34 [0xcf checksum valid]

boot #4
019: 32 48 84 fc 08 38 34 [0x38 checksum valid]
020: 32 1f 84 fc 3b 5c 34 [0x5c checksum valid]
021: 32 21 84 fc 7d 24 34 [0x24 checksum valid]
022: 32 00 84 fc 7f 07 34 [0x07 checksum valid]
023: 32 00 84 fc 1c 64 34 [0x64 checksum valid]
024: 32 00 84 fc cc b4 34 [0xb4 checksum valid]

The first 3 reply packets always have seemingly random source addresses. Then the sender switches to replying from 0x00. The data byte in all cases also appears to be random.


Things I've tried, to no avail:

  • Replaying the captured data to the WRC. Unlike the live system, when I perform the replay, the WRC never replies with 0xfd to my 0xfc. It just continues to send 0xfa (inverted), 0xfb and 0xf9 in sequence, forever.

  • Recording the timings of the packets and sleeping before sending, to match.

  • Connecting two RS485 bridges together to verify that the data is being sent.

At this point I'm stumped. From my understanding there can't be any "out-of-band" comms, as everything happens over RS485. What's being sent doesn't look like challenge-response, as the WRC always sends the same data. I would have assumed a simple replay of this data would trick the WRC into thinking it's connected to a IU.

I'm open to any and all ideas!

I don't own a logic analyser, but might invest in one soon...

I hope that the EE stack exchange was the best place to post this. If not, sorry!


Update: Thanks to @frr's suggestion, I found a cheap USB DAC/ADC I had laying around and connected it's line-in to the RO pin of a MAX485 TTL board. The USB DAC/ADC is CM108 based, and can only do 48kHz mono line in, but that seems to suffice for the 2400 baud rate of the comms.

I've only tested this so far with just the WRC powered from a bench supply -- no indoor unit connected. But I'm able to record the signal in Audacity. With a bit of amplification I can decode the data.

Audactiy Logic Analyser

I discovered that the WRC actually talks 8E1, as there is a parity bit before the stop bit. Unfortunatly changing the port settings to match in my Python decoder & emulator scripts made no difference.

Next step is to capture the WRC talking to the IU.

adamz
  • 31
  • 2
  • you captured the data, but you did not capture the timing .... there may be required delays between packets – jsotola Sep 30 '21 at 16:24
  • @jsotola I've updated the post to include timestamps. I have tried sleeping for the recorded time before sending replies, but this doesn't work. – adamz Oct 01 '21 at 04:08
  • Maybe there is some "out of band" stuff occurring over the RS485 wires themselves. Yes all the signaling is on the RS485, but as you point out the system is already breaking the rules of UART (sometimes it's inverting the signals), so maybe it's doing other stuff that your (UART!) bridge isn't able to detect, for example a BREAK or some other pulse that isn't valid UART. I'd recommend you get a nice cheap logic analyzer -- here is [a computer-based one for $80](https://www.amazon.com/gp/product/B07D21GG6J) -- and figure out exactly what's going on over the wires. – Mr. Snrub Oct 01 '21 at 05:38
  • I've seen protocols on the serial lines doing weird stuff with parity bits. Good luck parsing the traffic bitwise to find out. – frr Oct 01 '21 at 06:18
  • The inverted sequences might serve as a way of detecting nodes with A+B wires flipped, like some PnP probe. Which would possibly be followed by some instruction, to the affected node, to invert its polarity for payload traffic. – frr Oct 01 '21 at 06:20
  • 1
    2400B is not very fast. Consider analyzing this in software :-) All you need is a suitable input. Hmm - audio line in? – frr Oct 01 '21 at 06:24
  • @Mr.Snrub thanks for the logic analyser link. I'll look into buying one. – adamz Oct 01 '21 at 06:55
  • @frr I've got a bunch of [MAX485 -> TTL breakout boards](https://www.aliexpress.com/item/32910597375.htm). I had the idea to use the GPIOs on a RPi and something like [piscope](http://abyz.me.uk/rpi/pigpio/piscope.html) to visualise/record them. But I blew up the MAX485 (and hopefully not the Pi) since silly me thought TTL means 3.3v. But for the MAX485 its 5V and the Pi is 3.3. Oops! – adamz Oct 01 '21 at 06:55
  • @adamz level-shifting a 5-Volt RS485 transceiver down to 3.3V for the RPi has been discussed here a short while ago: https://electronics.stackexchange.com/questions/575991/rs485-different-voltages/576007#576007 I'm keeping my fingers crossed for you that it's the MAX485 that blew up, rather than the respective RPi's GPIO pin :-( – frr Oct 01 '21 at 07:40
  • @adamz unless the GPIO has some support for HW-based isochronous sampling (like a general-purpose HW shift register), you won't get a very high sampling rate and accuracy/isochronicity. E.g. triggering an interrupt by a HW clock might work for something like 1 kHz of a sampling rate, maybe up to 10 kHz if the input is handled by a dedicated kernel-mode handler (which does not wake up the user space upon every clock edge). Compare that to plugging your RS485 TTL RX into the audio input, which can give you 48 kHz or maybe more, isochronous Xtal-clocked sampling rate with OS-backed buffering... – frr Oct 01 '21 at 07:58
  • @adamz after a quick Google search, there are I2s *output* modules for the RPi, but as a more flexible solution I'd recommend some USB audio dongle with the C-Media CM6533. The gain of the microphone input can be dialed down, basically to the level of a "line in" - and you can always use a resistive divider to limit the level even further, if desired. Some products with the CM6533 can only sample the mic in at 48 kHz, apparently some can do 96 kHz. The chip can support 96 kHz input, depending on firmware. Should be supported in Linux, including an HID driver for buttons (maybe a volume knob). – frr Oct 01 '21 at 08:38
  • @frr I may have one of those USB sound cards floating around in my big box o' cables. I'll give it a shot! – adamz Oct 01 '21 at 08:59
  • @adamz you'll see for yourself, what sampling rate you'll get offered - but even if it was just 11 kHz or so, it might as well suffice for the 2400 Baud. Obviously the higher the better, for the quality of postprocessing. – frr Oct 01 '21 at 11:00
  • @adamz And, you'll probably notice that the signal chain (the USB ADC, really) removes DC using a high-pass filter on the order of several Hz. You could probably get around this in post-processing - which would be *my* reason to avoid clipping (overdrive) of the analog audio at mike input. – frr Oct 01 '21 at 11:07
  • Do you know how the system behaves if two IUs have the same ID? Is the user supposed to assign unique IDs to the IUs? The WRC might perform some "arbitration magic", that's where randomness could play a role. – Sim Son Oct 01 '21 at 12:18
  • What's the make and model of your AC? – Sim Son Oct 01 '21 at 12:20
  • @SimSon Not sure what happens if there is two IUs with the same ID. Unfortunately my setup is a home install, with a single remote and a single IU. The AC IU is a Samsung AC120HBHFKH-SA and the remote is a MWR-WE10. The WRC is compatible with a wide range of Samsung IU's. I have a 2nd remote on order so I can test the main/sub remote capabilities. But buying a 2nd IU is going to be hard :) – adamz Oct 01 '21 at 13:01
  • 1
    @SimSon I've made significant progress on decoding the individual messages on the bus. But I'd like to emulate a IU to fully explore the capabilities of the WRC, including how it handles multiple IUs, if possible. Emulating a IU is the first step. My efforts so far: https://github.com/zegelin/samsung-hvac-mtqq – adamz Oct 01 '21 at 13:03
  • @frr I had success using a USB ADC to capture the output from one of those MAX485 to TTL boards. Any suggestions on software that can assist with decoding the signal? For the first few bytes I did it manually in Audacity, but its quite tedious :). I had a brief look into GNUradio, but I'm not at all familiar with it, or signal processing in general to know what to search for. – adamz Oct 02 '21 at 04:27
  • @adamz congratz about your progress. Unfortunately I don't have a cooked solution. A while ago I've written something crude in MinGW C for Win32 which might serve you as an example of how to read a WAV file. http://frantisek.rysanek.sweb.cz/shocks/integral.zip The proggie takes a WAV and performs integration on the signal. For your purposes, you could play with reverting the high-pass effect (DC removal) or you could just detect edges (high slew rate) and infer the steady signal levels inbetween based on those edges... – frr Oct 02 '21 at 23:22

0 Answers0