Lambda 魔法
这里有一些使用 Lambda 在 ESPHome 中实现各种有趣事情的做法。 这些不需要外部组件,并展示了 Lambda 的强大功能。
显示页面替代方案
一些显示设备如 lcd_pcf8574 组件 本身不支持页面,但你可以使用 Lambda 轻松实现:
display:
- platform: lcd_pcf8574
dimensions: 20x4
address: 0x27
id: lcd
lambda: |-
switch (id(page)){
case 1:
it.print(0, 1, "Page1");
break;
case 2:
it.print(0, 1, "Page2");
break;
case 3:
it.print(0, 1, "Page3");
break;
}
globals:
- id: page
type: int
initial_value: "1"
interval:
- interval: 5s
then:
- lambda: |-
id(page) = (id(page) + 1);
if (id(page) > 3) {
id(page) = 1;
}
发送 UDP 命令
有多种网络设备可以通过包含命令字符串的 UDP 数据包进行控制。 你可以使用 ESPHome 中的脚本中的 Lambda 发送这样的 UDP 命令。
script:
- id: send_udp
parameters:
msg: string
host: string
port: int
then:
- lambda: |-
int sock = ::socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in destination, source;
destination.sin_family = AF_INET;
destination.sin_port = htons(port);
destination.sin_addr.s_addr = inet_addr(host.c_str());
// 如果你不需要为发出的数据包设置源端口,可以移除接下来的 4 行
source.sin_family = AF_INET;
source.sin_addr.s_addr = htonl(INADDR_ANY);
source.sin_port = htons(64998); // 源端口编号
bind(sock, (struct sockaddr*)&source, sizeof(source));
int n_bytes = ::sendto(sock, msg.c_str(), msg.length(), 0, reinterpret_cast<sockaddr*>(&destination), sizeof(destination));
ESP_LOGD("lambda", "Sent %s to %s:%d in %d bytes", msg.c_str(), host.c_str(), port, n_bytes);
::close(sock);
button:
- platform: template
id: button_udp_sender
name: "发送 UDP 命令"
on_press:
- script.execute:
id: send_udp
msg: "Hello World!"
host: "192.168.1.10"
port: 5000
在 arduino
和 esp-idf
平台上都经过测试。
延迟远程传输
下面的解决方案处理了在操作无线遥控窗帘时,由 RF Bridge Component(或 Remote Transmitter)发送的射频帧过于频繁的问题。窗帘电机似乎需要在单独的代码传输之间至少有 600-700ms 的静默时间才能识别它们。
这个问题可以通过构建一个原始射频代码队列,并使用(可配置的)延迟将它们一个接一个地发送来解决。延迟仅添加到接下来从需要同时从 Home Assistant 操作的窗帘列表中发送的命令。这对系统是透明的,系统仍然看起来像它们同时操作。
rf_bridge:
number:
- platform: template
name: 延迟命令
icon: mdi:clock-fast
entity_category: config
optimistic: true
restore_value: true
initial_value: 750
unit_of_measurement: "ms"
id: queue_delay
min_value: 10
max_value: 1000
step: 50
mode: box
globals:
- id: rf_code_queue
type: 'std::vector<std::string>'
script:
- id: rf_transmitter_queue
mode: single
then:
while:
condition:
lambda: 'return !id(rf_code_queue).empty();'
then:
- rf_bridge.send_raw:
raw: !lambda |-
std::string rf_code = id(rf_code_queue).front();
id(rf_code_queue).erase(id(rf_code_queue).begin());
return rf_code;
- delay: !lambda 'return id(queue_delay).state;'
cover:
# 有多个窗帘
- platform: time_based
name: '我的房间 1'
disabled_by_default: false
device_class: shutter
assumed_state: true
has_built_in_endstop: true
close_action:
- lambda: id(rf_code_queue).push_back("AAB0XXXXX..关闭的代码..XXXXXXXXXX");
- script.execute: rf_transmitter_queue
close_duration: 26s
stop_action:
- lambda: id(rf_code_queue).push_back("AAB0YXXXX..停止的代码..XXXXXXXXXX");
- script.execute: rf_transmitter_queue
open_action:
- lambda: id(rf_code_queue).push_back("AAB0ZXXXX..开启的代码..XXXXXXXXXX");
- script.execute: rf_transmitter_queue
open_duration: 27s
单按钮窗帘控制
下面的配置展示了如何通过一个按钮控制电动窗帘的运动,通过循环切换:开启->停止->关闭->停止->…
在这个例子中,使用了 Time Based Cover,并配置了 Sonoff Dual R2 的 GPIO。
ℹ️ Note
快速控制窗帘(在之前的命令后一分钟内发送新的开启/关闭命令)可能会导致意外行为(例如:窗帘在中间停止)。这是因为延迟继电器关闭功能使用异步自动化实现的。因此,每次发送开启/关闭命令时,都会添加一个延迟继电器关闭命令,并且旧的命令不会被删除。
esp8266:
board: esp01_1m
binary_sensor:
- platform: gpio
pin:
number: GPIO10
inverted: true
id: button
on_press:
then:
# 循环切换运动的逻辑:开启->停止->关闭->停止->...
- lambda: |
if (id(my_cover).current_operation == COVER_OPERATION_IDLE) {
// 窗帘处于空闲状态,检查当前状态,要么开启要么关闭窗帘。
if (id(my_cover).is_fully_closed()) {
auto call = id(my_cover).make_call();
call.set_command_open();
call.perform();
} else {
auto call = id(my_cover).make_call();
call.set_command_close();
call.perform();
}
} else {
// 窗帘正在开启/关闭。停止它。
auto call = id(my_cover).make_call();
call.set_command_stop();
call.perform();
}
switch:
- platform: gpio
pin: GPIO12
interlock: &interlock [open_cover, close_cover]
id: open_cover
- platform: gpio
pin: GPIO5
interlock: *interlock
id: close_cover
cover:
- platform: time_based
name: "窗帘"
id: my_cover
open_action:
- switch.turn_on: open_cover
open_duration: 60s
close_action:
- switch.turn_on: close_cover
close_duration: 60s
stop_action:
- switch.turn_off: open_cover
- switch.turn_off: close_cover
从文本输入更新数值
有时使用 Template Text 从用户界面更改一些数值可能更方便。 ESPHome 有一些很棒的 辅助函数,其中包括一个将文本转换为数字的函数。
在下面的示例中,我们有一个文本输入和一个模板传感器,它们可以从文本输入字段更新。Lambda 做的事情是将文本字符串解析并转换为数字——这只有在输入的字符串包含表示浮点数的字符(如数字、-
和 .
)时才会成功。如果输入的字符串包含任何其他字符,Lambda 将返回 NaN
,这对应于 未知
传感器状态。
text:
- platform: template
name: "数字类型输入"
optimistic: true
min_length: 0
max_length: 16
mode: text
on_value:
then:
- sensor.template.publish:
id: num_from_text
state: !lambda |-
auto n = parse_number<float>(x);
return n.has_value() ? n.value() : NAN;
sensor:
- platform: template
id: num_from_text
name: "从文本获取的数字"