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.
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 my0xfc
. It just continues to send0xfa
(inverted),0xfb
and0xf9
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.
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.