跳转到内容

CAN Bus

控制器局域网(Controller Area Network,CAN)总线是一种串行总线协议,用于将各个系统和传感器连接起来, 作为传统多芯线束的替代方案。它允许汽车零部件在单根或双根数据线总线上以高达 1Mbps 的速度进行通信。

CAN 是国际标准化组织(ISO)定义的串行通信总线,最初 开发用于汽车行业,以两线制总线替代复杂的线束。该 规范要求对电气干扰具有高抗干扰能力,并且能够自我诊断和修复 数据错误。这些特性使 CAN 在楼宇 自动化、医疗和制造等多种行业中获得了广泛应用。

ESPHome 的当前实现支持单帧数据传输。这样你可以发送和 接收最多 8 字节的数据帧。 借此你可以在总线上传输按钮按下事件或传感器反馈信号。 总线上所有其他设备都能获取这些数据,用于开关灯或显示 传输的数据。

CAN 总线本身只有两根线,分别为 Can High 和 Can Low,简称 CanH 和 CanL。要让 ESPHome 的 CAN 总线工作,你需要选择已实现物理 CAN 总线的设备。 你可以配置多个总线。

任何 CAN 总线节点随时都可以传输数据;任何节点都可以发送和/或接收任意 can_id 值。 你需要决定如何组织 can_id 值;例如,你可以建立一个 CAN 总线网络, 让每个节点使用一个 can_id 来广播关于自身的数据。如果某个节点应该(例如)打开一盏灯, 它可以监听总线中包含其特定 can_id 的消息并做出相应反应。 通过这种架构,你可以让多个节点控制连接到某个特定节点的灯。

每个 canbus 平台都扩展了以下配置模式:

# 示例配置条目
canbus:
- platform: ...
can_id: 4
on_frame:
- can_id: 500
use_extended_id: false
then:
- lambda: |-
std::string b(x.begin(), x.end());
ESP_LOGD("can id 500", "%s", &b[0] );

配置变量:

  • platform (必填, platform):受支持的 CAN 总线 平台 之一。

  • id (可选, ID):手动指定用于代码生成的 ID。

  • can_id (必填, int):传输帧时默认使用的 CAN ID。

  • use_extended_id (可选, boolean):标识 can_id 的类型:

    • false:标准 11 位 ID (默认)
    • true:扩展 29 位 ID
  • bit_rate (可选, enum):受支持的波特率之一。参见 此表格, 查看不同 ESP32 变体内部 CAN(TWAI)控制器支持的波特率列表。默认为 125KBPS。

    • 1KBPS - esp32_can 的支持情况取决于 ESP32 变体
    • 5KBPS - esp32_can 的支持情况取决于 ESP32 变体
    • 10KBPS - esp32_can 的支持情况取决于 ESP32 变体
    • 12K5BPS - esp32_can 的支持情况取决于 ESP32 变体
    • 16KBPS - esp32_can 的支持情况取决于 ESP32 变体
    • 20KBPS - esp32_can 的支持情况取决于 ESP32 变体
    • 25KBPS
    • 31K25BPS - esp32_can 不支持
    • 33KBPS - esp32_can 不支持
    • 40KBPS - esp32_can 不支持
    • 50KBPS
    • 80KBPS - esp32_can 不支持
    • 83K3BPS - esp32_can 不支持
    • 95KBPS - esp32_can 不支持
    • 100KBPS
    • 125KBPS - 默认
    • 200KBPS - esp32_can 不支持
    • 250KBPS
    • 500KBPS
    • 1000KBPS
  • on_frame (可选, Automation):在收到 CAN 帧时执行的自动化。参见 on_frame 触发器。

此自动化在收到 CAN 帧时触发。类型为 std::vector<uint8_t> 的变量 x(包含帧数据)、类型为 uint32_t 的 can_id(包含实际 收到的 CAN ID)以及类型为 bool 的 remote_transmission_request(包含来自 CAN 帧的相应字段)会传递给自动化,以便在 lambda 中使用。

NOTE

此节点发送到相同 ID 的消息不会显示为已接收的消息。

canbus:
- platform: ...
on_frame:
- can_id: 43 # 收到的 can_id
then:
- if:
condition:
lambda: 'return (x.size() > 0) ? x[0] == 0x11 : false;'
then:
light.toggle: light1
- can_id: 0b00000000000000000000001000000
can_id_mask: 0b11111000000000011111111000000
use_extended_id: true
remote_transmission_request: false
then:
- lambda: |-
auto pdo_id = can_id >> 14;
switch (pdo_id)
{
case 117:
ESP_LOGD("canbus", "exhaust_fan_duty");
break;
case 118:
ESP_LOGD("canbus", "supply_fan_duty");
break;
case 119:
ESP_LOGD("canbus", "supply_fan_flow");
break;
// to be continued...
}

配置变量:

  • can_id (必填, int):收到此 CAN ID 时将触发该自动化。

  • can_id_mask (可选, int):在尝试与 can_id 匹配之前应用于收到的 CAN ID 的位掩码。默认为 0x1fffffff(收到的 CAN ID 的所有位都与 can_id 进行比较)。

  • use_extended_id (可选, boolean):标识要匹配的 can_id 类型。默认为 false。

  • remote_transmission_request (可选, boolean):是否对带有”remote transmission request”位设置或未设置的 CAN 帧运行。默认为不检查(两种情况下自动化都会运行)。

CAN 总线可以通过 canbus.send 操作来传输帧。有几种使用方式:

on_...:
- canbus.send:
data: [ 0x10, 0x20, 0x30 ]
canbus_id: my_mcp2515 # 如果只有一个 canbus 设备则可选
can_id: 23 # 覆盖 can bus 中配置的 can_id
on_...:
- canbus.send: [ 0x11, 0x22, 0x33 ]
- canbus.send: 'hello'
# 模板化;返回值类型必须是 std::vector<uint8_t>
- canbus.send: !lambda return {0x00, 0x20, 0x42};

配置变量:

  • data (必填, binary data, templatable):要传输的数据,CAN 总线每帧最多支持 8 个 字节/字符。

  • canbus_id (可选):设置传输帧要使用的 CAN 总线 ID。如果配置中定义了多个 CAN 总线平台,则此项为必需。

  • can_id (可选, int):允许覆盖为 CAN 总线设备配置的 can_id。

  • use_extended_id (可选, boolean):标识 can_id 的类型:

    • false:标准 11 位 ID (默认)
    • true:扩展 29 位 ID
  • remote_transmission_request (可选, boolean):设置为发送 CAN 总线帧以请求其他节点的数据。 如果需要发送特定的数据长度码,请在 data 中包含必要的(虚拟)字节。默认为 false。

标准 ID 和扩展 ID 可以在同一网段上共存。

NOTE

重要的是要知道”标准”和”扩展”地址表示不同的地址。例如, 标准 0x123 和扩展 0x123 实际上是不同的地址。

ID 可以使用十进制或十六进制表示法:

  • 标准 ID 使用 0x000 到 0x7ff(十六进制)或 0 到 2047(十进制)
  • 扩展 ID 使用 0x00000000 到 0x1fffffff(十六进制)或 0 到 536870911(十进制)

此示例说明了不同 ID 类型如何在配置中用于发送和接收。

# 每秒钟传输扩展 ID 和标准 ID 0x100
time:
- platform: sntp
on_time:
- seconds: /1
then:
- canbus.send:
# 显式扩展 ID
use_extended_id: true
can_id: 0x100
data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
- canbus.send:
# 默认标准 ID
can_id: 0x100
data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
canbus:
- platform: ...
can_id: 0x1fff
use_extended_id: true
bit_rate: 125kbps
on_frame:
- can_id: 0x123
use_extended_id: true
then:
- lambda: |-
std::string b(x.begin(), x.end());
ESP_LOGD("CAN extended ID 0x123", "%s", &b[0]);
- can_id: 0x123
then:
- lambda: |-
std::string b(x.begin(), x.end());
ESP_LOGD("CAN standard ID 0x123", "%s", &b[0]);

假设有一个连接到远程 CAN 节点的按钮,它将对 ID 0x100 发送一条消息,载荷 为 0x1(表示开关闭合)和 0x0(表示开关断开)。此示例将查找该消息并相应地更新 其 binary_sensor 的状态。

binary_sensor:
- platform: template
name: CAN Bus Button
id: can_bus_button
canbus:
- platform: ...
can_id: 4
bit_rate: 125kbps
on_frame:
- can_id: ${0x100}
then:
- lambda: |-
if(x.size() > 0) {
switch(x[0]) {
case 0x0: // 按钮释放
id(can_bus_button).publish_state(false);
break;
case 0x1: // 按钮按下
id(can_bus_button).publish_state(true);
break;
}
}

在此示例中,三个节点连接到 CAN 总线:

  • 节点 1 向 ID 0x50B 发送单字节载荷

  • 节点 2 向 ID 0x50C 发送单字节载荷

    这些节点发送基于连接到它们的按钮状态的单字节载荷:

    • 0: 按钮释放
    • 1: 按钮按下
    • 2: 长按
    • 3: 长释放
    • 4: 双击
  • 节点 3 控制连接到它的电机。它期望一条发送到 ID 0x51A 的消息,其单字节载荷为:

    • 0: 关闭
    • 1: 打开
    • 2: 关闭
canbus:
- platform: ...
id: my_canbus
can_id: 4
bit_rate: 125kbps
on_frame:
- can_id: 0x50c
then:
- lambda: |-
if(x.size() > 0) {
auto call = id(TestCover).make_call();
switch(x[0]) {
case 0x2: call.set_command_open(); call.perform(); break; // 长按
case 0x1: // 按钮按下
case 0x3: call.set_command_stop(); call.perform(); break; // 长释放
case 0x4: call.set_position(1.0); call.perform(); break; // 双击
}
}
- can_id: 0x50b
then:
- lambda: |-
if(x.size() > 0) {
auto call = id(TestCover).make_call();
switch(x[0]) {
case 0x2: call.set_command_close(); call.perform(); break; // 长按
case 0x1: // 按钮按下
case 0x3: call.set_command_stop(); call.perform(); break; // 长释放
case 0x4: call.set_position(0.0); call.perform(); break; // 双击
}
}
cover:
- platform: time_based
name: Canbus Test Cover
id: TestCover
device_class: shutter
has_built_in_endstop: true
open_action:
- canbus.send:
data: [ 0x01 ]
canbus_id: my_canbus
can_id: 0x51A
open_duration: 2min
close_action:
- canbus.send:
data: [ 0x02 ]
canbus_id: my_canbus
can_id: 0x51A
close_duration: 2min
stop_action:
- canbus.send:
data: [ 0x00 ]
canbus_id: my_canbus
can_id: 0x51A