跳转到内容

Lambda 魔法

这里有一些在 ESPHome 中使用 Lambda 可以实现的各种有趣功能的配方。 这些不需要外部组件,展示了 Lambda 有多么强大。

某些显示屏如 lcd_pcf8574 组件 原生不支持页面,但您可以使用 Lambda 轻松实现:

display:
- platform: lcd_pcf8574
dimensions: 20x4
address: 0x27
id: lcd
lambda: |-
switch (id(page)){
case 1:
it.print(0, 1, "页面1");
break;
case 2:
it.print(0, 1, "页面2");
break;
case 3:
it.print(0, 1, "页面3");
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 数据包进行控制。 您可以使用脚本中的 Lambda 从 ESPHome 发送此类 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", "已发送 %s 到 %s:%d,共 %d 字节", 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

已在 arduinoesp-idf 平台上测试。

下面的解决方案解决了 RF Bridge(或 远程发射器)发送 RF 帧太快的问题,当操作无线电控制的窗帘时。窗帘电机似乎需要在各个代码传输之间至少 600-700 毫秒的静默时间才能识别它们。

这可以通过建立一个原始 RF 代码队列并逐个发送它们(之间有可配置的延迟)来解决。延迟只添加到需要从 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

下面的配置展示了如何使用单个按钮通过循环控制电动窗帘的运动:打开->停止->关闭->停止->…

在此示例中,使用 时间基础 配合 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

有时使用 模板 从用户界面更改一些数值可能更方便。 ESPHome 有一些不错的 辅助函数,其中有将文本转换为数字的函数。

在下面的示例中,我们有一个文本输入和一个模板传感器,可以从文本输入字段更新。Lambda 所做的是解析并将文本字符串转换为数字——只有当输入的字符串包含表示浮点数的字符(如数字、-.)时才会成功。如果输入的字符串包含任何其他字符,lambda 将返回 NaN,对应传感器的 unknown 状态。

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: "从文本转换的数字"