跳转到内容

Lambda 魔法

以下是一些在 ESPHome 中使用 Lambdas 来做各种有趣事情的秘诀。 它们不需要外部组件,并展示出 Lambdas 的强大之处。

有些显示器(如 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 数据包进行控制。 您可以在 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());
// you can remove the next 4 lines if you don't want to set the source port for outgoing packets
source.sin_family = AF_INET;
source.sin_addr.s_addr = htonl(INADDR_ANY);
source.sin_port = htons(64998); // the source port number
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: "Send UDP Command"
on_press:
- script.execute:
id: send_udp
msg: "Hello World!"
host: "192.168.1.10"
port: 5000

已在 arduino 和 esp-idf 平台上测试通过。

以下方案解决了在操作无线电遥控窗帘时,RF 桥接器(或 远程发射器)一个接一个过于快速发送 RF 帧的问题。 窗帘电机似乎需要在每条代码传输之间有至少 600-700ms 的静默时间才能识别它们。

解决方案是建立一个原始 RF 代码队列,并按顺序逐个发送,两者之间带有(可配置的)延迟。 延迟只添加到来自窗帘列表中接下来的命令,这些命令需要从 Home Assistant 一次同时操作。 这对系统来说是透明的,系统看起来仍然像在同时操作。

rf_bridge:
number:
- platform: template
name: Delay commands
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:
# have multiple covers
- platform: time_based
name: 'My Room 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..the.closing.code..XXXXXXXXXX");
- script.execute: rf_transmitter_queue
close_duration: 26s
stop_action:
- lambda: id(rf_code_queue).push_back("AAB0YXXXX..the.stopping.code..XXXXXXXXXX");
- script.execute: rf_transmitter_queue
open_action:
- lambda: id(rf_code_queue).push_back("AAB0ZXXXX..the.opening.code..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:
# logic for cycling through movements: open->stop->close->stop->...
- lambda: |
if (id(my_cover).current_operation == COVER_OPERATION_IDLE) {
// Cover is idle, check current state and either open or close cover.
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 {
// Cover is opening/closing. Stop it.
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: "Cover"
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: "Number type in"
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: "Number from text"