跳转到内容

CAN 总线

控制器局域网 (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 总线以获取包含其特定 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 (必需, 平台):支持的 CAN 总线 平台 之一。

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

  • can_id (*必需, 整数):用于发送帧的默认 CAN ID

  • use_extended_id (可选, 布尔值):标识 can_id 的类型:

    • false:标准 11 位 ID (默认)
    • true:扩展 29 位 ID
  • bit_rate (可选, 枚举):支持的波特率之一。有关不同 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 (可选, 自动化):接收到 CAN 帧时执行的自动化。请参阅 on_frame 触发器

当接收到 CAN 帧时将触发此自动化。变量 x(类型为 std::vector<uint8_t>)包含帧数据、can_id(类型为 uint32_t)包含实际接收的 CAN ID 以及 remote_transmission_request(类型为 bool)包含 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;
// 继续...
}

配置变量:

  • can_id (*必需, 整数):接收时将触发此自动化的 CAN ID。

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

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

  • remote_transmission_request (可选, 布尔值):是否针对设置了或未设置”远程传输请求”位的 CAN 帧运行。默认为不检查(两种情况都会运行自动化)。

CAN 总线可以通过 canbus.send 动作发送帧。有几种使用方式:

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

配置变量:

  • data (*必需, 二进制数据, 可模板化):要发送的数据,CAN 总线每帧最多支持 8 字节/字符。

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

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

  • use_extended_id (可选, 布尔值):标识 can_id 的类型:

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

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

NOTE

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

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

  • 标准 ID 使用 0x0000x7ff(十六进制)或 02047(十进制)
  • 扩展 ID 使用 0x000000000x1fffffff(十六进制)或 0536870911(十进制)

此示例说明了如何在配置中使用不同的 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 扩展 ID 0x123", "%s", &b[0]);
- can_id: 0x123
then:
- lambda: |-
std::string b(x.begin(), x.end());
ESP_LOGD("CAN 标准 ID 0x123", "%s", &b[0]);

假设我们有一个连接到远程 CAN 节点的按钮,该节点将向 ID 0x100 发送消息,有效载荷 0x1 表示触点闭合,0x0 表示触点打开,此示例将查找此消息并相应地更新其 binary_sensor 的状态。

binary_sensor:
- platform: template
name: CAN 总线按钮
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 测试遮盖
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