Hacking My Car's Climate Controls: LIN Reverse Engineering

It’s not a great feeling to walk out to your car in the morning and find that your windshield is completely covered in ice. Scraping it off is a pain, and it’s even worse when you’re in a hurry.

I used to drive a 20-year-old Honda Accord. It had a barebones climate control system, with buttons to choose where the air would blow, and dials to choose the temperature and fan speed. It was simple, but it worked. If I set the mode to defrost and then turned off the car, the defroster would still be on when I started the car again.

Not so in newer cars. My current car, a 5th generation Toyota RAV4, won’t keep the front or rear defrosters on after restarting the car. I installed a remote start system, but without being able to keep the defroster on, it’s not very useful. I could just as easily start the car myself and then turn on the defroster, but that would require me to go outside earlier than I want to.

I’ve been told this is a safety feature (and common on many newer cars, not just my Toyota). However, according to Toyota’s website, when you remote start the car, “if the outside temperature is less than 41°F, the front and rear defrosters will turn on.” Ah, so you can remotely defrost the windshield—if you bought a trim level that comes with Toyota’s remote start subscription service. I didn’t. (I did want the larger display though, so I bought and installed one from a junkyard Corolla.)

So I’ve been thinking about how I could hack my car’s climate control system to keep the defroster on after restarting the car.

The CAN Bus

With almost everything in modern cars being computer-controlled, CAN is the most common way to communicate between different systems. The CAN bus is a serial bus that uses a twisted pair of wires to carry data. It’s a multi-master bus, meaning that multiple devices can be connected to the bus and transmit data at the same time. Each device has a unique ID, and the bus uses arbitration to determine which device gets to transmit data at any given time.

CAN hacking is not new. There are many tools and a plethora of documentation and tutorials available for sniffing, intercepting, and modifying CAN traffic. Comma.ai, which I use every day, uses a CAN interceptor to modify communications between the car’s lane keeping camera and the car’s computer to send commands to drive the car semi-autonomously.

I figured that if CAN messages could drive the car, then they could probably control the climate control system, too. I started by connecting my computer to the panda that I’d installed already for openpilot and opening cabana to view the raw CAN traffic. Unfortunately, nothing changed when I messed with the climate control buttons. Cars have multiple CAN buses, so I tried sniffing other buses, including the one connected to the remote starter and the one on the back of the radio. Still nothing.

Simply put, CAN is not used for controlling the climate system in my car. And if there’s no CAN traffic, there’s nothing to sniff. I needed to find another way to control the climate system.

(Actually, there might be a way to control it through CAN. Newer Highlanders and Venzas with the 12-inch screen can control the climate system through the touchscreen, and some RAV4 Prime and Prius Prime models can control it through the app. If you have one of those, you’re welcome to try sniffing the CAN traffic and see if you can figure out how to control the climate system. I’d love to hear about it.)

The LIN Bus

I started looking for other ways to control the climate system. Studying the wiring diagrams for my car, I found that the control panel on the dash is connected to the climate control unit through a LIN bus. (I also found that there was a CAN line connected to the control unit directly to a CAN gateway, but I’d have to rip apart most of my car’s dash to get to it.)

LIN is a serial bus that uses a single wire to carry data. It is different from CAN in that it is a single-master bus, meaning that only one device can transmit data at a time. The master node sends data to up to 16 slave nodes, and the slave devices can respond to requests sent by the master. More information about LIN can be found here.

car connector panel connector
Circle color Wire color Description
Green Green LIN
Yellow White/Black 12V
White White Ground
Blue Blue Illumination

Because LIN is much newer than CAN, well-documented hardware is scarce and open-source utilities for sniffing and intercepting traffic are practically nonexistent. I decided to start by reverse engineering the LIN data transmitted between the control panel and the climate control unit. This was a lot easier than I expected. A LIN bus transciever with a TJA1020 chip converts LIN data to TTL serial data, which can be read by a USB-to-UART adapter. I ordered one and connected it to my computer, with the LIN terminal connected to the bus alongside both the car and the panel. (It’s a mess, but a functional mess.)

LIN bus connected to computer

Opening a serial connection using PuTTY gave me this: putty serial output

Looks ugly, but there’s a pattern. A hex editor was a bit more useful. This sequence of bytes was repeated over and over:

55 B1 00 40 00 00 38 38 00 00 9D 00 55 32 00 00 00 00 38 38 00 00 5D 00 55 39 40 00 00 00 10 90 00 00 E5 00 55 BA 00 00 00 00 00 00 00 00 45 00 55 F5 00 00 00 00 38 00 00 00 D1 00 55 76 00 55 78 90 00 00 00 00 00 00 00 F6 00

A LIN frame begins with a sync byte, followed by a 6-bit ID, a 2-bit parity bit, 8 data bytes, and a 1-byte checksum. The transceiver returns 0x55 to represent the sync byte and 0x00 at the end of the frame. Separating the frames gives us this:

55 B1 00 40 00 00 38 38 00 00 9D 00
55 32 00 00 00 00 38 38 00 00 5D 00
55 39 40 00 00 00 10 90 00 00 E5 00
55 BA 00 00 00 00 00 00 00 00 45 00
55 F5 00 00 00 00 38 00 00 00 D1 00
55 76 00
55 78 90 00 00 00 00 00 00 00 F6 00

Disconnecting the control panel from the bus changed the output:

55 B1 00 40 00 00 38 38 00 00 9D 00
55 32 00 00 00 00 38 38 00 00 5D 00
55 39 00
55 BA 00
55 F5 00 00 00 00 38 00 00 00 D1 00
55 76 00
55 78 00

Deciphering the Data

Immediately we can determine that 0xB1, 0x32, and 0xF5 are frames of data sent by the master node (the climate control unit) to the slave node (the control panel). 0x39, 0xBA, and 0x78 are requests, which are responded to by the control panel. I have no clue what 0x76 is. To figure out which frames do what, I simply pressed buttons and observed the changes in the data.

Status Messages

Frames with the ID 0xB1 contain the current status of the climate system.

Function Status Data (8 bytes + checksum) Bits (excluding checksum)
fan/power off 80 00 13 00 2c 2c 00 81 e0 >> 48 & 7
1 80 01 13 00 2c 2c 00 81 df
2 80 02 13 00 2c 2c 00 81 de
3 80 03 13 00 2c 2c 00 81 dd
4 80 04 13 00 2c 2c 00 81 dc
5 80 05 13 00 2c 2c 00 81 db
6 80 06 13 00 2c 2c 00 81 da
7 80 07 13 00 2c 2c 00 81 d9
temperature Low (0x00) 80 01 13 00 00 00 00 81 38 >> 24 & FF (driver)
>> 16 & FF (passenger)
60 - 64 °F (0x66 - 0x6a) 80 01 13 00 68 68 00 81 67
65 - 85 °F (0x22 - 0x36) 80 01 13 00 29 34 00 81 da
High (0x37) 80 01 13 00 37 37 00 81 c9
auto off 80 03 13 00 2c 2c 00 81 dd >> 53 & 1
on 80 23 13 00 2c 2c 00 81 bd
mode face 80 02 11 00 2c 2c 00 81 e0 >> 40 & F
face/feet 80 02 12 00 2c 2c 00 81 df
feet 80 02 13 00 2c 2c 00 81 de
feet/defrost 80 02 14 00 2c 2c 00 81 dd
defrost 80 02 19 00 2c 2c 00 81 d8
rear defrost off 80 03 13 00 2c 2c 00 81 dd >> 38 & 1
on 80 03 13 40 2c 2c 00 81 9d
eco off 80 03 13 00 2c 2c 00 81 dd >> 59 & 1
on 88 03 13 00 2c 2c 00 81 d5
recycle off 80 02 12 00 2c 2c 00 81 df >> 45 & 1
on 80 02 22 00 2c 2c 00 81 cf
s-mode off 00 02 12 00 2c 2c 00 81 60 >> 63 & F
on 80 02 12 00 2c 2c 00 81 df
sync off 00 02 12 00 2c 2c 00 81 60 >> 37 & 1
on 00 02 12 20 2c 2c 00 81 40
a/c off 00 02 12 00 2c 2c 00 80 61 & 1
on> 00 02 12 00 2c 2c 00 81 60
illumination off 00 06 14 00 35 36 00 81 47 >> 6 & 1
on 00 06 14 00 35 36 00 c1 07

Button Presses

Pressing buttons on the control panel to change the temperature, fan speed, or blower mode causes a change in the response to 0x39. Its data with nothing pressed is 40 00 00 00 10 90 00 00 e5. When a button is pressed, a bit or bits in the response change for a minimum of one frame before returning to the response. If the button is held down, the response continues to reflect the pressed button until it is released.

For the most part, multiple buttons can be pressed simultaneously. There are two exceptions.

  1. If the fan up/down buttons are pressed simultaneously, the panel will send the response for the fan up button and the fan speed will increase.
  2. Temperature up/down can be triggered multiple times per command. For example, if I quickly spin the driver temperature knob clockwise, the panel will respond to the next 0x39 request with 40 00 00 00 12 90 00 00 or 40 00 00 00 13 90 00 00. The temperature will increase by 2 or 3 degrees, respectively.
Button Data (8 bytes + checksum) Operation
none 40 00 00 00 10 90 00 00 e5  
off 42 00 00 00 10 90 00 00 e3 | 02 00 00 00 00 00 00 00
auto 48 00 00 00 10 90 00 00 dd | 08 00 00 00 00 00 00 00
driver temp down 40 00 00 00 0f 90 00 00 e6 - 00 00 00 00 01 00 00 00
driver temp up 40 00 00 00 11 90 00 00 e4 + 00 00 00 00 01 00 00 00
passenger temp down 40 00 00 00 10 8f 00 00 e6 - 00 00 00 00 00 01 00 00
passenger temp up 40 00 00 00 10 91 00 00 e6 + 00 00 00 00 00 01 00 00
sync 40 00 00 20 10 90 00 00 c5 | 00 00 00 20 00 00 00 00
a/c 40 80 00 00 10 90 00 00 65 | 80 00 00 00 00 00 00 00
front defrost 40 00 00 80 10 90 00 00 65 | 00 00 00 80 00 00 00 00
rear defrost 40 00 00 40 10 90 00 00 a5 | 00 00 00 40 00 00 00 00
eco 40 40 00 00 10 90 00 00 a5 | 00 40 00 00 00 00 00 00
fan down 40 3d 00 00 10 90 00 00 a8 | 00 3d 00 00 00 00 00 00
fan up 40 3c 00 00 10 90 00 00 a9 | 00 3c 00 00 00 00 00 00
mode 40 00 1c 00 10 90 00 00 c9 | 00 00 1c 00 00 00 00 00
recycle air 40 00 00 00 10 90 c0 00 25 | 00 00 00 00 00 00 c0 00
s-mode 40 00 80 00 10 90 00 00 65 | 00 00 80 00 00 00 00 00

Everything Else

During my testing, 0x32, 0xBA, 0xF5, and 0x78 messages never changed, so I’m not sure what they do. My car doesn’t have the winter package, which includes the heated seats, heated steering wheel, and wiper defroster. Those messages may be related to those features and would be used on cars with those options.

0x76 is still a mystery, as it is continually requested by the master node and never receives a response. Remember how I mentioned some cars, like the newest generation Venza and Highlander with the 12 inch display, can control the climate system from the touchscreen? They might use CAN, or they might be connected to the climate LIN bus and respond to 0x76 requests. I tried responding to 0x76 requests with the same data as 0x39 and nothing happened. I also tried sending messages with 1 bit changed at a time (0x0000000000000001, 0x0000000000000002, 0x0000000000000004 etc.) and observed no changes.

Sending LIN Data

Because the TJA1020 transceiver operates as a slave node, it can respond to requests sent by the master. Responses, as with data sent by the master, must end with a checksum byte. A Python script can be used to listen for requests and transmit responses.

import serial

ser = serial.Serial(
  port='COM9', # the serial port of the UART to USB adapter
  baudrate=19200,
  parity=serial.PARITY_NONE,
  stopbits=serial.STOPBITS_ONE,
  bytesize=serial.EIGHTBITS,
  timeout=None
)

def calculate_checksum(frame_id, bytes_data):
  checksum = frame_id
  for b in bytes_data:
    checksum += b
    if checksum > 255:
      checksum -= 255
  checksum = ~checksum & 0xFF  # invert; & 0xFF is unnecessary in C when using uint8_t
  return checksum.to_bytes(1, 'big', signed=False)

def respond():
  increase_fan_speed = b'\x40\x3c\x00\x00\x10\x90\x00\x00'
  while 1:
    b = ser.read()
    if b.hex() == '55':
      b = ser.read()
      if b.hex() == '39':
        ser.write(increase_fan_speed + calculate_checksum(0x39, increase_fan_speed))

respond()

Running this script will tell the car that the fan up button is being held down, causing the fan speed to increase. However, the control panel must be unplugged from the car for this to work. If the control panel is plugged in, both nodes will be transmitting responses at the same time, resulting in a collision. The car sees this as invalid data and ignores it.

Operating as a master node is a bit more complex. To start, most LIN chips require another resistor placed between two pins or something of the sort to enable them to transmit in master mode. When I tried sending messages to the panel as a master, nothing happened. I think Python with the USB-UART adapter might just be too slow to send messages fast enough to be recognized by the transceiver as a single message. SoftwareSerial on an Arduino also did not send messages quickly enough. I was finally able to get it to work with an Arduino Mega, which has 3 hardware serial interfaces in addition to its USB serial interface. More detail on this will have to wait for another post.

Next Steps

My goal is to send messages to turn on the defrosters if they were on before shutting off the car. This is going to require building an interceptor with 2 LIN transceivers, one to act as a slave on the car’s network and another to act as a master for the panel. I bought some parts to build a prototype, but turning this into something that reliably works in my car is going to take a while.

prototype LIN interceptor