helloworld
yet another personal website on the interwebs

Hacking the Autoterm Air / Planar 2D

Van Hacking
Reverse Engineering
Heater

As part of my effort to smart up my van, I've looked into how to make my heater controllable with an API. It is a Autoterm Planar 2D (now rebranded as Autoterm Air 2D) diesel heater, and it came with the PU-27 control panel.

Heater and panel are connected with a 4-wired cable. The wires are colored red, blue, white and green. Which made me think: Red and blue is probably the power supply, as that's quite common for car components. And the rest might be RS232? They probably didn't make up their own protocol, because, after all, why should they?

A quick google research led me to Grimoire's Blog, which supported my assumption. Grimoire also dug into the serial protocol used and explains how to build a proxy that sits between panel and heater. Everything received from the panel will be passed on to the heater, and vice versa. If you're interested in that, I suggest you head over to his blog and check it out. For the sake of completeness, I'll repeat the key points here as well:

The wiring is as follows:

JST SM 5 pin schematics
Looking at the plug from the side where the cables go in.
  • 1: unused
  • 2: heater (TX) to panel (RX), 5V, green
  • 3: panel (TX) to heater (RX), 5V, white
  • 4: ground, blue
  • 5: +12V, red

The comm lines are operated at 5V, and using 3.3V signal levels won't work for the heater. So you either need to shift the logic levels (e.g. the TXS0108E should do the trick), or you use some serial to USB converter that can operate on 5V levels out of the box.

Also, all communication is initiated by the panel. The heater responds and never sends messages the panel did not ask for in the first place. After power up, the panel tries a couple of baud settings until it gets a valid response from the heater.

At first I was thinking about building the proxy with my weapon of choice, the ESP8266. But as I'm using a Raspberry Pi in my van anyway, I was fine with attaching two FTDI serial-to-USB converters, just as Grimoire did. That also shortened iteration cycles when I reverse engineered the protocol. Compiling and uploading sketches just takes more time than running some code on my laptop.

The Proxy

Before being able to analyze the protocol, I needed to build that proxy device that can be plugged between heater and panel. I wanted to be able to restore the original connection in case everything fails, so I've used the same connectors as Autoterm did (5-pin JST SM. Yes, 5 pin.).

The enclosure in Tinkercad
The enclosure in Tinkercad.

I've also 3d printed a case that holds the two serial-USB adapters, provides space for the necessary wiring and can be wall-mounted next to the Raspberry. Nothing fancy, but does the job.

Print your own if you feel like it: tinkercad stl

Once wired up, I've added some code that passes everything it receives on one serial port to the other, and vice versa. Now I could play with the heater and see which bytes changed.

Image of the heater proxy
The finished heater proxy on a messy table.

Protocol

This is what I found out about the protocol. At the end I was able to replicate all functionality of the PU-27.

Basic message structure

The basic message structure is as follows:

  • 0xAA: Preamble. Each sentence starts with this.
  • Sender
    • 0x03: Message originates from the panel
    • 0x04: Response from the heater
  • Payload length in bytes
  • 0x00, probably some padding
  • Command ID, see below
  • Payload: Depends on the command or response
  • Checksum: CRC16-MODBUS, calculated for the entire message. An online tool for calculating these can be found here: https://www.modbustools.com/modbus_crc16.html

Messages are idempotent, so it does not make a difference if you send a command once or multiple times. In fact, the PU-27 controller usually repeats commands several times.

During regular operation, the panel sends commands in a loop:

  • 0x02: Get settings
  • 0x11: Report panel temperature
  • 0x0f: Get status
  • 0x11: Report panel temperature again

After that, this sequence repeats. Commands are emitted once every ~0.8s.

0x01: Start heater

The payload is organized as follows:

OffsetContent
00xFF 0xFF Preamble
2Mode, see below
3Temperature setpoint in °C as a single byte
4Ventilation: 0x01 = On, 0x02 = Off
5Power level, 0-9
ModeDescription
0x01By T Heater
0x02By T Panel
0x03By T Air
0x04By Power

Obviously, the temperature setpoint is only relevant for modes 1-3, and the power level for mode 4.

Example:

Turn on the heater with ventilation and power level 3:

P >> aa 03 06 00 01 ff ff 04 10 01 03 2e af
H << aa 04 06 00 01 ff ff 04 10 01 03 f4 1e

0x02: Get/Set settings

If this command is sent without payload, the heater responds with the current settings. Alternatively, the panel can send new settings. In that case, the payload is identical to the 0x01 (Start) command.

This command is also used to change the settings when the heater is running.

The panel polls the settings periodically.

Example

Set setpoint to 16°C, controlled by heater's sensor, with ventilation.

P >> aa 03 06 00 02 ff ff 01 10 01 03 e2 9c
H << aa 04 06 00 02 ff ff 01 10 01 03 38 2d

0x03: Shutdown

Stops the heater. Stopping the heater can take a while, and you can check the status so see when that's finished.

This sequence seems to be always the same, regardless of the mode used.

Example

C >> aa 03 00 00 03 5d 7c
H << aa 04 00 00 03 29 7d

0x04: Initialization

Sent by the panel when it tries to establish a connection with the heater. Once it receives a valid reply, it sticks to the current connection settings. This command is never used again during regular operation.

Example

C >> aa 03 00 00 04 9f 3d
H << aa 04 05 00 04 10 82 00 15 11 f9 82

0x11: Report panel temperature

This one is used by the controller panel to report the temperature to the heater. This is relevant when the heater is operating in By T Panel mode.

Payload is just one byte (the temperature in °C), and the heater repeats whatever value the panel sends. This command is used during the regular polling cycle, and it is polled twice as often as e.g. the status request.

Example

It's 10°C:

C >> aa 03 01 00 11 0a ba d1
H << aa 04 01 00 11 0a 7a 64

0x0F: Status

The panel polls this one periodically. There is more in this response payload than I figured out, but this is what I know so far:

OffsetContent
6Battery voltage * 10, e.g. 0x84 equals 13.2V
9Operating mode, see below
14 and 16Ventilation power. During startup this value goes up to 0x96, and then decreases slowly down to 0x46 during regular operation (status 0x04). Also, this value seems to appear twice, god knows, why.
Operating ModeMeaning
0x00Heater off
0x01Starting
0x04Running
0x05Shutting down

Example

Heater is running, Battery voltage is 13.2V, ventilation is at 109.

P >> aa 03 00 00 0f 58 7c
H << aa 04 13 00 0f 03 00 00 0a 7f 00 84 01 b2 04 00 37 37 00 6d 00 6d 00 64 c3 0a

Implementation

I've written some typescript/node js code that does the interfacing. The software I'm running on the van is a mess to be honest, but I've cleaned up the relevant classes, so you can grab them here.

Some notes on the implementation:

  • You can totally inject commands, as long as forward all responses from the heater to the display. That way, the PU-27 is notified about e.g. changed setpoints or heater states. It nicely updates it's views.
  • When injecting commands, pretend to be the panel and use it's sender ID. This is fine.
  • I've added some code that tracks when the heater state was polled last by the panel. If it's disconnected, a timeout will be triggered and the code takes over polling.

Installation / Deployment

The FT232RL serial-to-usb adapters work pretty much right out of the box on both Windows- and Linux systems. On my Raspberry PI they just popped up as e.g. /dev/ttyUSB0. However, there is no guarantee that one converter will have the same name after a reboot as it did before. So to be sure your setup keeps working you should assign an alias and work with that.

To do so, first check the port names the devices are connected to:

dmesg | grep ttyUSB

[    7.805124] usb 1-1.2: FTDI USB Serial Device converter now attached to ttyUSB0
[    7.813654] usb 1-1.3: FTDI USB Serial Device converter now attached to ttyUSB1

Then check the details for each of the devices found:

udevadm info --name=/dev/ttyUSB0 --attribute-walk
udevadm info --name=/dev/ttyUSB1 --attribute-walk

Armed with that information you can finally set up symlinks by creating a file /etc/udev/rules.d/10-usb-serial.rules. As I'm using identical converters, there is no way to distinguish them other than the USB ports they are attached to. Pick some attributes that describe each converter. I've picked then vendor- and product id, as well as the serial. But that's the same for both devices, so it could just as well be ignored.

My file looks like this:

SUBSYSTEM=="tty", KERNELS=="1-1.3", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", ATTRS{serial}=="A50285BI", SYMLINK+="ttyUSB_panel"
SUBSYSTEM=="tty", KERNELS=="1-1.2", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", ATTRS{serial}=="A50285BI", SYMLINK+="ttyUSB_heater"

KERNELS is the USB port as listed by dmesg, and the other attributes stem from the udevadm command.

After that we have symlinks /dev/ttyUSB_panel and /dev/ttyUSB_heater, which will map to the corresponding devices.

UI

With that information at hand, you should be able to easily integrate the heater into your system. I've built a nice UI for controlling and checking my heater. This is how it looks:

Screenshot of the heater control UI Screenshot of the heater status UI
Heater controls and status

References

Parts

  • Raspberry PI 3
  • 2x FT232RL USB to serial adapter that supports 5V signal levels
  • JST SM 5 pin connectors (1x plug, 1x socket)
  • 2x Mini USB patch cables

Links