Skip to content

Instantly share code, notes, and snippets.

@JavanXD
Last active September 17, 2025 15:58
Show Gist options
  • Save JavanXD/696d026ef202a7d6455ed4745df63e39 to your computer and use it in GitHub Desktop.
Save JavanXD/696d026ef202a7d6455ed4745df63e39 to your computer and use it in GitHub Desktop.
ESPHome/Homeassistant - Sniff CAN-Bus (MCP2515) from LEDA LUC2
substitutions:
name: esphome-ledaluc2
friendly_name: ESPHome LEDA LUC2
esphome:
name: ${name}
friendly_name: ${friendly_name}
min_version: 2023.6.0 # Use a stable ESPHome version for compatibility
name_add_mac_suffix: false # Prevent adding MAC suffix to the device name
project:
name: esphome.web
version: dev # Version of the project
esp32:
board: esp32dev # Specify the ESP32 development board
framework:
type: arduino # Use the Arduino framework
# Enable logging for debugging purposes
logger:
# Enable Home Assistant API with encryption key from secrets.yaml
api:
encryption:
key: !secret api_key
# Enable over-the-air updates for firmware
ota:
platform: esphome
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Fallback hotspot in case Wi-Fi connection fails
ap:
ssid: "ESPHome-LEDALUC2"
password: !secret ap_password
# Allow Wi-Fi provisioning via serial connection
improv_serial:
# Enable captive portal for Wi-Fi provisioning via the fallback hotspot
captive_portal:
# Import specific components from an example configuration without overwriting local settings
dashboard_import:
package_import_url: github://esphome/example-configs/esphome-web/esp32.yaml@main
import_full_config: false
# Host a simple web server (e.g., for Improv Wi-Fi)
web_server:
# Configure the SPI interface for the MCP2515 CAN bus module
spi:
clk_pin: GPIO22 # Clock pin
miso_pin: GPIO17 # Master In Slave Out pin
mosi_pin: GPIO21 # Master Out Slave In pin
# Configure the CAN bus using the MCP2515 module
canbus:
- platform: mcp2515
cs_pin: GPIO16
can_id: 0x28A
bit_rate: 125KBPS
on_frame:
- can_id: 0x28A
then:
- lambda: |-
// Log all received CAN frames for debugging
if (x.size() > 0 && x.size() < 8) {
// Log the received frame data safely
std::string frame_data;
for (size_t i = 0; i < x.size(); i++) {
char byte_str[5];
snprintf(byte_str, sizeof(byte_str), "0x%02X ", x[i]);
frame_data += byte_str;
}
// Log the frame data in a single line
ESP_LOGD("CAN", "Received CAN Frame (Size: %d bytes): %s", x.size(), frame_data.c_str());
// Check for specific sizes and log states
if (x.size() == 2) {
ESP_LOGI("CAN", "Interpreted State: Ventilation turned OFF via Display");
} else if (x.size() == 1) {
ESP_LOGI("CAN", "Interpreted State: Ventilation turned ON via Display");
} else {
ESP_LOGW("CAN", "Interpreted State: Unknown or Additional Data");
}
}
else if (x.size() == 8) {
uint8_t frame_type = x[0]; // First byte determines the frame type
// Frame type 0x00: Pressure difference and exhaust temperature
if (frame_type == 0x00) {
// Extract pressure difference from Byte 2
float pressure_difference = x[1] * 0.1f; // Convert to Pascals
// Check for adjustment flag in Byte 3
if (x[2] == 0x81) {
const float PRESSURE_ADJUSTMENT_VALUE = 25.5f;
pressure_difference += PRESSURE_ADJUSTMENT_VALUE;
ESP_LOGI("CAN", "Adjustment Applied: +%.1f Pa", PRESSURE_ADJUSTMENT_VALUE);
}
// Extract exhaust temperature from Byte 4
float exhaust_temperature = static_cast<float>(x[3]); // °C
// Log decoded values
ESP_LOGI("CAN", "Pressure: %.1f Pa, Temperature: %.1f °C", pressure_difference, exhaust_temperature);
// Publish to sensors
id(pressure_difference_sensor).publish_state(pressure_difference);
id(exhaust_temperature_sensor).publish_state(exhaust_temperature);
}
// Frame type 0x01: Ventilation status
else if (frame_type == 0x01) {
// Extract ventilation status from Byte 6
bool ventilation_active = (x[5] == 0x01);
// Log ventilation status
ESP_LOGI("CAN", "Ventilation Active: %s", ventilation_active ? "Yes" : "No");
ESP_LOGD("CAN", "Ventilation Bytes: Data=%02X %02X %02X %02X %02X %02X %02X %02X",
x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7]);
// Publish to binary sensor
id(ventilation_status_sensor).publish_state(ventilation_active);
}
// Frame type 0x09: Heartbeat signal
else if (frame_type == 0x09) {
// Log loop and counter for reference
ESP_LOGD("CAN", "Heartbeat (0x09): Loop=%02X, Counter=%02X", x[2], x[1]);
}
// Frame type 0x55: Heartbeat signal
else if (frame_type == 0x55) {
// Log loop and counter for reference
ESP_LOGD("CAN", "Heartbeat (0x55): Loop=%02X, Counter=%02X", x[2], x[1]);
}
else if (frame_type == 0x80) {
ESP_LOGW("CAN", "Error Frame (0x80): Data=%02X %02X %02X %02X %02X %02X %02X %02X",
x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7]);
}
else if (frame_type == 0x81) {
ESP_LOGW("CAN", "Error Frame (0x81): Data=%02X %02X %02X %02X %02X %02X %02X %02X",
x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7]);
}
// Handle unknown frame types
else {
ESP_LOGW("CAN", "Unknown Frame Type. Data=%02X %02X %02X %02X %02X %02X %02X %02X",
x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7]);
}
} else {
ESP_LOGW("CAN", "Unexpected frame size: %d bytes", x.size());
}
# Define template sensors to hold the values published from the CAN bus data
sensor:
- platform: template
name: "Pressure Difference"
id: pressure_difference_sensor
unit_of_measurement: "Pa"
accuracy_decimals: 1
device_class: pressure # Device class for pressure sensors
icon: "mdi:air-filter" # Optional custom icon for visual clarity
filters:
- throttle_average: 3s
- platform: template
name: "Exhaust Temperature"
id: exhaust_temperature_sensor
unit_of_measurement: "°C"
accuracy_decimals: 1
device_class: temperature # Device class for temperature sensors
icon: "mdi:thermometer"
filters:
- throttle_average: 3s
binary_sensor:
- platform: template
name: "Ventilation Status"
id: ventilation_status_sensor
device_class: running # Device class for indicating system activity
icon: "mdi:fan" # Optional custom icon for ventilation
@moritzj29
Copy link

Vielen Dank @JavanXD für dieses super How-To und Code Beispiel! Ich konnte ebenfalls meinen LEDA LUC2 erfolgreich in HomeAssistant einbinden.

Auf dem Weg dahin hatte ich auch einige Schwierigkeiten mit defekten MCP2515 Modulen, daher hier kurz meine Learnings:

  • Component canbus is marked FAILED: beim start deutet darauf hin, dass irgendwas mit der SPI Kommunikation oder dem Modul nicht passt, hat nichts mit dem CAN Bus selbst zu tun
  • auch ohne Fehlermeldung hatte ich 2 Module die keinen CAN Traffic gezeigt haben -> hier vermute ich defekte Module (ESD oder sonst was)
  • der LEDA nutzt den CAN Bus ja selber zur Kommunikation, damit sind die beiden Enden des Buses bereits definiert und vermutlich auch mit 120 Ohm terminiert. Abzweige sollten daher möglichst kurz sein. Nach einigen erfolglosen Versuchen habe ich dann den Bus Abzweig deutlich verkürzt auf <10cm. Ich kann es aufgrund der 2 defekten Module nicht zu 100% sagen, ob das jetzt einen Einfluss hatte oder nicht. Ist aber einen Versuch Wert wenn nichts geht...

Bzgl. ESPHome Version: bei mir läuft aktuell 2025.6.2

@failbit
Copy link

failbit commented Sep 5, 2025

@moritzj29: danke für deine Erfahrungswerte. Könntest du nochmal sagen welche MCP2515-Module du jetzt genau genommen hast? Ggf. gibt es da ja noch Herstellerunterschiede!

@moritzj29
Copy link

puh… 5er pack von AliExpress für 5€ 😂 kann natürlich gut sein, dass es da auch fake chips gibt, aber das wird schwer vorher zu erkennen sein.

@sisamiwe
Copy link

@JavanXD
Hallo,
Ich möchte meine Ledatronic LT3 auch auslesen.
Das Grundsetup funktioniert, aber die Messages sind anders aufgebaut. Danke für die Anleitung.

Könntest du mal einen CAN Mitschnitt posten, wenn du die Ebene Fachbetrieb per Displayeingabe aktivierst?
Ich möchte versuchen, ob man auch ohne Display (ist bei mir nicht verbaut) auf die Werte dort zugreifen bzw auslesen kann.
Danke dir.

@failbit
Copy link

failbit commented Sep 17, 2025

Bei mir war es tatsächlich ein defekter MCP2515. Ausgetauscht und alles klappt auf anhieb!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment