Skip to content

swifty99/linbus

Repository files navigation

ESPHome LIN Bus Component

A LIN (Local Interconnect Network) bus component for ESPHome, for communicating with the many cheap LIN actuators, sensors, fans, flaps, motors, pumps and solenoids found in vehicles.

Platform: ESP32 family only (ESP-IDF framework). Validated on ESP32-C3.

Status

v2 — rewrite from scratch. Previous versions are discarded and not backward-compatible (YAML key, action names, and internal API all changed).

The implementation lives upstream on the lin_bus branch of swifty99/esphome and is mirrored here as an ESPHome external component so you can use it without building from a fork.

Installation

Add this to your ESPHome YAML:

external_components:
  - source: github://swifty99/linbus
    components: [linbus]

Quickstart

See example.yaml for a complete, runnable configuration. Minimum master-mode setup:

esp32:
  board: esp32-c3-devkitm-1
  framework:
    type: esp-idf

linbus:
  id: lin_master
  tx_pin: GPIO10
  rx_pin: GPIO6
  cs_pin: GPIO7
  baud_rate: 19200
  uart_port: UART_NUM_1
  mode: master
  schedule:
    - lin_id: 33
      interval: 200ms
  on_frame:
    - lin_id: 33
      then:
        - lambda: |-
            ESP_LOGD("linbus", "Got %d bytes for id 33", data_length);

Configuration Reference

linbus:
  id: lin_master                    # Optional
  tx_pin: GPIO10                    # Required
  rx_pin: GPIO6                     # Required
  cs_pin: GPIO7                     # Optional — LIN transceiver chip-select
  baud_rate: 19200                  # Optional (default 19200). Common: 9600, 19200, 20000
  uart_port: UART_NUM_1             # Optional (default UART_NUM_1)
  mode: master                      # Optional — master | slave | listener (default master)
  schedule:                         # Optional — master-only scheduling table (max 16)
    - lin_id: 34
      interval: 100ms
  responses:                        # Optional — pre-buffered publisher data (max 16)
    - lin_id: 33
      data: [0x00, 0x00, 0x00, 0x00]
      checksum: enhanced            # enhanced (LIN 2.x, default) | classic (LIN 1.x)
  on_frame:                         # Optional — automations per LIN ID
    - lin_id: 33
      then:
        - lambda: ...

Frame IDs are 6-bit decimal values (0–63). IDs 60–63 are reserved (diagnostic / future use) and not currently supported.

Modes

  • master — drives the bus. Sends frame headers on the configured schedule. Also publishes data for any lin_id in responses.
  • slave — listens passively for headers. When a header matches one of this node's responses, the node transmits the buffered data.
  • listener — receives only, never transmits. Useful for sniffing an existing bus or bench-testing.

Schedule & Responses — the one rule that matters

LIN has no collision detection. If two nodes respond to the same lin_id, the bus corrupts and stops working. You must design your bus so that each lin_id has exactly one publisher. The master's schedule simply sends headers — whoever is configured to respond to that ID puts the bytes on the wire.

Lay this out explicitly before you configure anything:

ID  Publisher  Purpose            Update Rate
--  ---------  -----------------  -----------
10  Slave A    Temperature        200ms
11  Master     Pump Command       100ms
12  Slave B    Status             100ms

Communication directions all use the same mechanism:

  • Slave → master: master schedules header for a slave's ID; slave publishes; master receives via on_frame.
  • Master → slave: master schedules header for an ID the master itself publishes (listed in its own responses); slaves receive via on_frame.
  • Slave → slave: master schedules the header; publisher slave transmits; listener slaves receive.

on_frame Trigger

Fires when a complete, valid frame with the given lin_id is received. Exposed variables:

  • data (std::vector<uint8_t>) — up to 8 bytes received.
  • data_length (uint8_t) — actual length.
on_frame:
  - lin_id: 33
    then:
      - lambda: |-
          if (data.size() >= 2) {
            float temp = ((data[0] << 8) | data[1]) / 10.0f;
            ESP_LOGD("linbus", "temp=%.1f", temp);
          }

High-frequency frames: use template sensors

At 10–50 ms intervals, on_frame runs thousands of times a minute. Heavy logic inside it will overwhelm ESPHome. Keep the automation trivial (copy into a global) and read at a controlled rate from a template sensor:

globals:
  - id: lin_frame_33
    type: std::vector<uint8_t>
    restore_value: no
    initial_value: '{}'

linbus:
  # ...
  on_frame:
    - lin_id: 33
      then:
        - lambda: id(lin_frame_33) = data;

sensor:
  - platform: template
    name: "LIN Temperature"
    unit_of_measurement: "°C"
    update_interval: 1s
    lambda: |-
      auto d = id(lin_frame_33);
      if (d.size() >= 2) return ((d[0] << 8) | d[1]) / 10.0f;
      return {};

Action: linbus.update_response

Updates the pre-buffered data this node publishes for a given lin_id. The new bytes are sent the next time that header appears on the bus.

number:
  - platform: template
    name: "Pump Speed"
    min_value: 0
    max_value: 100
    step: 1
    optimistic: true
    on_value:
      then:
        - linbus.update_response:
            id: lin_master
            lin_id: 48
            data: !lambda |-
              uint8_t speed = (uint8_t) x;
              return {speed, 0x01, 0x00, 0x00};

Parameters:

  • id — the linbus component ID (optional when only one is configured).
  • lin_id — required, 0–63, templatable.
  • data — required, 1–8 bytes, static list or lambda returning std::vector<uint8_t>.

Diagnostic Sensors

Expose bus-health counters as standard ESPHome sensors:

sensor:
  - platform: linbus
    linbus_id: lin_master
    master_requests:
      name: "LIN Master Requests"
    id_requests_answered:
      name: "LIN Requests Answered"
    data_bytes_received:
      name: "LIN Data Bytes"
    checksum_errors:
      name: "LIN Checksum Errors"
    send_failures:
      name: "LIN Send Failures"
    bus_load:
      name: "LIN Bus Load"        # percent, MEASUREMENT
    error_rate:
      name: "LIN Error Rate"      # percent, MEASUREMENT

All fields are optional — add only the ones you need.

Current Limitations

  • No event-triggered (spontaneous) frames.
  • No diagnostic frames (IDs 60–63) or NAD/SID services.
  • No sleep / wake-up signalling.
  • No node configuration services (LIN 2.x config).
  • ESP32 / ESP-IDF only. RP2040, ESP32 Arduino, and ESP8266 from the v1 codebase are not supported.

Migrating from v1

If you used the old lin: component from this repo, nothing carries over:

v1 v2
lin: YAML key linbus:
uart_id + separate uart: block tx_pin / rx_pin / uart_port / baud_rate on linbus: directly
linbus.send action linbus.update_response
linbus.request_pid action removed — use schedule: instead
Custom forked uart: component removed — uses ESP-IDF UART driver directly
ESP32 Arduino / RP2040 / ESP8266 ESP-IDF on ESP32 only

Credits & References

License

GPLv3 — see LICENSE.

About

Esphome LINbus

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors