Ever since I installed a LEDA LUC2 fireplace, I wanted deeper insights into how it operates — particularly pressure difference, exhaust temperature, and ventilation state. Unfortunately, the vendor doesn’t provide any integration options, and the only external interface is a mysterious RJ12 port on the controller.
So I decided to reverse engineer it myself.
In this post, I’ll walk you through how I decoded the CAN bus communication of the LEDA LUC2, used an ESP32 with an MCP2515 module to read the values, and made them available in Home Assistant using ESPHome. Even better: the method works for any CAN-based system.

Step 1 – Sniffing the CAN Bus
I started by simply listening to the bus without sending anything. The goal was to see what kind of messages the LUC2 sends when it’s running.
The RJ12 port on the controller exposes a CAN High / CAN Low line, so I used an MCP2515 CAN transceiver paired with an ESP32 to tap into it passively.
Required Hardware
- ESP32 Dev Board
- MCP2515 CAN module (with TJA1050 transceiver)
- RJ12 breakout or RJ12 splitter (so the main unit can keep working)
- USB-C buck converter (12 V → 5 V to power ESP32 from the fireplace)
- A few jumper wires
Step 2 – Wiring it Up
Here’s how I wired the ESP32 to the MCP2515 over SPI:
MCP2515 Pin | ESP32 Pin |
---|---|
VCC | 3.3V |
GND | GND |
CS | GPIO16 |
SCK | GPIO22 |
MOSI | GPIO21 |
MISO | GPIO17 |
INT | Not used |

Then, I connected the CAN_H and CAN_L from the MCP2515 to the RJ12 port using a breakout cable. On the LEDA LUC2, the pinout of the RJ12 is:
RJ12 Pin | Signal |
---|---|
Pin 1 | 12V (used to power the ESP32 via buck converter) |
Pin 2 | unused |
Pin 3 | unused |
Pin 4 | CAN-L |
Pin 5 | CAN-H |
Pin 6 | GND |

⚠️ Note: Several users (including me, in early tests) accidentally swapped CAN-H and CAN-L, which results in no data being received. Make sure to get the polarity right.
Step 3 – Logging Raw CAN Frames
Once everything was connected, I flashed this minimal ESPHome YAML just to see what the fireplace was sending:
spi:
clk_pin: GPIO22
miso_pin: GPIO17
mosi_pin: GPIO21
canbus:
- platform: mcp2515
cs_pin: GPIO16
bit_rate: 125KBPS
on_frame:
- can_id: 0x28A
then:
- logger.log:
format: "Raw frame: %s"
args: [x]
When I powered up the fireplace, I immediately started seeing messages on CAN ID 0x28A. From here, I started dumping frames and inspecting the payloads over time.
Step 4 – Decoding the Messages
After collecting enough frames, I started to spot patterns:
- Messages always 8 bytes
- Byte 0 = message type
- Message Type 0x00 = sensor values
- Message Type 0x01 = status info
Example frame (Type 0x00):
00 03 E1 32 00 00 00 00
- Byte 0: 00 → Type 0
- Byte 1: 03 → Pressure value
- Byte 2: E1 → Modifier / condition byte
- Byte 3: 32 → Temperature (in °C)
I decoded pressure using this logic:
float pressure = x[1] * 0.1f;
if (x[2] == 0x81) {
pressure += 25.5f;
}
The result? Actual pressure difference values like 26.5 Pa or 29.1 Pa. Same with exhaust temperature — a direct value in °C from Byte 3.
Step 5 – Integrating Into Home Assistant
I extended the ESPHome config to publish proper sensors:
sensor:
- platform: template
name: "Pressure Difference"
id: pressure_difference_sensor
unit_of_measurement: "Pa"
- platform: template
name: "Exhaust Temperature"
id: exhaust_temperature_sensor
unit_of_measurement: "°C"
binary_sensor:
- platform: template
name: "Ventilation Active"
id: ventilation_status_sensor
And the full on_frame decoding logic:
on_frame:
- can_id: 0x28A
then:
- lambda: |-
if (x.size() == 8) {
uint8_t type = x[0];
if (type == 0x00) {
float pressure = x[1]*0.1f + (x[2]==0x81 ? 25.5f : 0);
float temp = x[3];
id(pressure_difference_sensor).publish_state(pressure);
id(exhaust_temperature_sensor).publish_state(temp);
} else if (type == 0x01) {
bool vent = (x[5] == 0x01);
id(ventilation_status_sensor).publish_state(vent);
}
}
As soon as ESPHome connects to Home Assistant, you’ll see these as regular sensors — ready for dashboards or automations.
Step 6 – Automation Ideas
Here are a few things I did once the data was available in Home Assistant:
- Low exhaust temperature alert: Push notification when fire needs reloading
- Auto-ventilation: Trigger fans based on pressure + ventilation status
- Trends: Long-term statistics and graphs of fireplace performance
Step 7 – Applying This to Other CAN Devices
This process works for any CAN device — not just LEDA:
- Sniff traffic using ESPHome and log raw frames
- Decode byte structure manually (you’ll get better with practice)
- Create sensors for useful values
Whether you’re working on solar inverters, EV chargers, or even cars, the approach is the same.
How Can You Discover Relevant CAN IDs on Other Devices
When applying this to other CAN systems, here’s a step-by-step process:
1. Log All CAN Traffic
Start with a generic logger in ESPHome or Arduino:
on_frame:
- then:
- logger.log:
format: "ID: 0x%03X -> %s"
args: [id, x]
Let it run for a few minutes and save the output.
2. Identify Repeating IDs
Look for:
- Periodic messages (same ID every second)
- Changing payloads (values that vary with time, temperature, input, etc.)
- Matching value changes to real-world actions
- Decoding payload bytes manually
- Iterating and testing
Final Thoughts
Reverse-engineering the LEDA LUC2 turned into a fun little weekend project — and now I have full visibility into my fireplace from anywhere. No cloud, no vendor lock-in, no guessing when to reload wood.
Everything I used is published here:
🧠 ESPHome config: 👉 https://gist.github.com/JavanXD/696d026ef202a7d6455ed4745df63e39
Enjoy the fire. 🔥