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 的消息并相应地做出反应。
通过这种架构,您可以有多个节点能够控制连接到单个特定节点的灯。
基本 CAN 总线配置
Section titled “基本 CAN 总线配置”每个 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] );配置变量:
-
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 变体25KBPS31K25BPS-esp32_can不支持33KBPS-esp32_can不支持40KBPS-esp32_can不支持50KBPS80KBPS-esp32_can不支持83K3BPS-esp32_can不支持95KBPS-esp32_can不支持100KBPS125KBPS- 默认200KBPS-esp32_can不支持250KBPS500KBPS1000KBPS
-
on_frame (可选, 自动化):接收到 CAN 帧时执行的自动化。请参阅
on_frame触发器。
on_frame 触发器
Section titled “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 帧运行。默认为不检查(两种情况都会运行自动化)。
canbus.send 动作
Section titled “canbus.send 动作”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 使用
0x000到0x7ff(十六进制)或0到2047(十进制) - 扩展 ID 使用
0x00000000到0x1fffffff(十六进制)或0到536870911(十进制)
此示例说明了如何在配置中使用不同的 ID 类型进行发送和接收。
# 每秒发送扩展和标准 ID 0x100time: - 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]);二进制传感器示例
Section titled “二进制传感器示例”假设我们有一个连接到远程 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