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 的消息并做出相应反应。
通过这种架构,你可以让多个节点控制连接到某个特定节点的灯。
基础 CAN Bus 配置
Section titled “基础 CAN Bus 配置”每个 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 (必填, 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 变体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 (可选, Automation):在收到 CAN 帧时执行的自动化。参见
on_frame触发器。
on_frame 触发器
Section titled “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 帧运行。默认为不检查(两种情况下自动化都会运行)。
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 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 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 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]);Binary Sensor 示例
Section titled “Binary Sensor 示例”假设有一个连接到远程 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; } }Cover 示例
Section titled “Cover 示例”在此示例中,三个节点连接到 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