Compare commits

..
Author SHA1 Message Date
J. Nick Koston 4ea7546980 [graph] Drop the unused vector include 2026-11-14 10:27:14 -06:00
J. Nick Koston c08e2bedab Merge remote-tracking branch 'origin/dev' into HEAD 2026-11-14 08:37:27 -06:00
J. Nick Koston d314e7435e Drop the wiegand test; its triggers are converted on dev already 2026-11-12 09:48:19 -06:00
J. Nick Koston 2b62b202af Merge remote-tracking branch 'origin/dev' into hub-lists-static-vector
# Conflicts:
#	esphome/components/sprinkler/__init__.py
2026-11-12 09:39:34 -06:00
J. Nick Koston b02bfbba14 Merge remote-tracking branch 'upstream/dev' into hub-lists-static-vector
# Conflicts:
#	esphome/components/nextion/nextion.h
2026-11-09 08:50:18 -08:00
J. Nick Koston f9ad2a0dd1 Merge branch 'dev' into hub-lists-static-vector 2026-11-04 21:20:55 -06:00
J. Nick Koston d25370da30 Merge dev into hub-lists-static-vector 2026-11-04 21:15:16 -06:00
J. Nick Koston d950e8672e Merge dev into hub-lists-static-vector 2026-11-04 21:10:43 -06:00
J. Nick Koston ff98b5404b Merge branch 'slot-list-migrate' into hub-lists-static-vector
# Conflicts:
#	esphome/components/canbus/__init__.py
#	esphome/components/canbus/canbus.h
#	esphome/components/usb_uart/usb_uart.h
2026-11-04 18:38:39 -06:00
J. Nick Koston 83c52100a9 Merge branch 'slot-list-helper' into slot-list-migrate 2026-11-04 18:35:20 -06:00
J. Nick Koston 0891a605c2 Merge remote-tracking branch 'origin/dev' into slot-list-helper
# Conflicts:
#	.agents/skills/component-codegen/SKILL.md
#	.agents/skills/embedded-memory/SKILL.md
#	esphome/cpp_helpers.py
#	tests/unit_tests/test_cpp_helpers.py
2026-11-04 18:35:15 -06:00
J. Nick Koston 6a90377541 Trim the slot list comments 2026-11-01 09:00:21 -10:00
J. Nick Koston a8cce79d4d Write the slot list skill text as instructions 2026-11-01 08:59:01 -10:00
J. Nick Koston 6c42ffe363 Merge branch 'slot-list-migrate' into hub-lists-static-vector 2026-10-31 21:46:59 -10:00
J. Nick Koston f7d81810cf Replace the pn532 and duco count guards with an empty list check 2026-10-31 21:46:55 -10:00
J. Nick Koston 9a10880bea Merge branch 'slot-list-helper' into slot-list-migrate 2026-10-31 21:44:38 -10:00
J. Nick Koston 05a7ac5070 Drop a redundant set from the empty slot list check 2026-10-31 21:44:29 -10:00
J. Nick Koston c946a78af0 Merge remote-tracking branch 'origin/slot-list-migrate' into hub-lists-static-vector 2026-10-31 21:36:46 -10:00
J. Nick Koston 12be180106 Merge remote-tracking branch 'origin/slot-list-helper' into slot-list-migrate 2026-10-31 21:36:30 -10:00
J. Nick Koston 2b8283b769 Trim slot list comments and docstrings 2026-10-31 21:36:21 -10:00
J. Nick Koston 020f7518e4 Merge remote-tracking branch 'origin/slot-list-migrate' into hub-lists-static-vector 2026-10-31 21:28:16 -10:00
J. Nick Koston e768b3ad38 Merge remote-tracking branch 'origin/slot-list-helper' into slot-list-migrate 2026-10-31 21:28:13 -10:00
J. Nick Koston a895299fbc Say where cg.slot_list must be called 2026-10-31 21:28:10 -10:00
J. Nick Koston fd8c321deb Merge remote-tracking branch 'origin/slot-list-migrate' into hub-lists-static-vector 2026-10-31 21:07:07 -10:00
J. Nick Koston 6bc65977c8 Merge remote-tracking branch 'origin/slot-list-helper' into slot-list-migrate 2026-10-31 21:07:04 -10:00
J. Nick Koston e2c3183e72 Wrap the slot_list line in the AI guide 2026-10-31 21:07:00 -10:00
J. Nick Koston 6bd95ae68b Mention cg.slot_list and SLOT_LIST in the AI guide 2026-10-31 21:06:44 -10:00
J. Nick Koston 12a1f94325 Drop doc comments that would now attach to the slot list members 2026-10-31 21:04:54 -10:00
J. Nick Koston a290033ada Merge branch 'slot-list-helper' into slot-list-migrate 2026-10-31 21:04:46 -10:00
J. Nick Koston 186ba0c9fd Merge branch 'slot-list-migrate' into hub-lists-static-vector 2026-10-31 21:04:46 -10:00
J. Nick Koston 25864f8fca Tidy the slot list test comments, skill text and emitter note 2026-10-31 21:04:40 -10:00
J. Nick Koston 2d06ba192a Guard kamstrup's add_custom_sensor; include helpers.h outside USE_TIME in bedjet; drop unused vector includes 2026-10-31 20:55:16 -10:00
J. Nick Koston 76d429e9c5 Merge branch 'slot-list-migrate' into hub-lists-static-vector 2026-10-31 20:54:58 -10:00
J. Nick Koston a38940d114 Guard gps and duco register methods with SLOT_LIST_REQUIRES; leave uart binary_sensor on slot_counter 2026-10-31 20:54:46 -10:00
J. Nick Koston fab2486c0f Merge branch 'slot-list-helper' into slot-list-migrate 2026-10-31 20:54:39 -10:00
J. Nick Koston 2feb806357 Emit empty slot lists for the domains the build copies; add SLOT_LIST_REQUIRES for hand-written register methods 2026-10-31 20:54:31 -10:00
J. Nick Koston b1c400a9ce Pass each hub's DOMAIN to cg.slot_list 2026-10-31 20:45:50 -10:00
J. Nick Koston 216a936f8f Merge branch 'slot-list-migrate' into hub-lists-static-vector 2026-10-31 20:45:35 -10:00
J. Nick Koston 23ed987da5 Pass each hub's DOMAIN to cg.slot_list 2026-10-31 20:44:55 -10:00
J. Nick Koston 2a600801ec Merge branch 'slot-list-helper' into slot-list-migrate 2026-10-31 20:44:45 -10:00
J. Nick Koston aebef214bc Take the hub's DOMAIN in cg.slot_list instead of reading the caller's module 2026-10-31 20:44:34 -10:00
J. Nick Koston 7fbedf5276 canbus triggers in SLOT_LIST without a Component base; usb_uart get_channels returns the list 2026-10-31 20:28:59 -10:00
J. Nick Koston 9e679e620c Merge branch 'slot-list-migrate' into hub-lists-static-vector 2026-10-31 20:28:02 -10:00
J. Nick Koston f5882a19ca Merge branch 'slot-list-helper' into slot-list-migrate 2026-10-31 20:27:54 -10:00
J. Nick Koston 7df1e8ddfc Register ecocomfort2 children with SLOT_LIST and set their parent from codegen; trim repeated comments 2026-10-31 20:27:54 -10:00
J. Nick Koston 7cde55341d Fail to compile a SLOT_LIST registration that code generation did not count; reject a define declared twice 2026-10-31 20:27:29 -10:00
J. Nick Koston dc1bbe089e Merge branch 'slot-list-migrate' into hub-lists-static-vector 2026-10-31 20:21:41 -10:00
J. Nick Koston 8c50dca737 Merge branch 'slot-list-helper' into slot-list-migrate 2026-10-31 20:21:40 -10:00
J. Nick Koston 42fb1b00e5 Leave a slot_list define alone when something already set it 2026-10-31 20:21:40 -10:00
J. Nick Koston 144fe66e36 Merge branch 'slot-list-migrate' into hub-lists-static-vector 2026-10-31 20:20:30 -10:00
J. Nick Koston 7f79868b92 Merge branch 'slot-list-helper' into slot-list-migrate 2026-10-31 20:20:30 -10:00
J. Nick Koston 548d5041ca Include helpers.h directly in every header that declares a slot list 2026-10-31 20:20:30 -10:00
J. Nick Koston 2e342a469e Emit the empty slot_list defines from code generation for every component whose sources are built 2026-10-31 20:20:20 -10:00
J. Nick Koston 77cf5df19c Merge branch 'slot-list-helper' into slot-list-migrate 2026-10-31 20:17:58 -10:00
J. Nick Koston 50acc2095f Merge branch 'slot-list-migrate' into hub-lists-static-vector 2026-10-31 20:17:58 -10:00
J. Nick Koston 7d99802baa A BLE controller without trackers now gets a scan listener count of 0 2026-10-31 20:17:58 -10:00
J. Nick Koston c8d1a5dc24 Find slot_list's calling module through inspect; keep the empty-list test off static members via an instance 2026-10-31 20:17:57 -10:00
J. Nick Koston 15f45ecc9b Prove multi-reader sizing with three Wiegand readers 2026-10-31 20:05:02 -10:00
J. Nick Koston 9d8ab61d20 Merge branch 'slot-list-migrate' into hub-lists-static-vector 2026-10-31 20:04:47 -10:00
J. Nick Koston d4b723813d Make each sweep list one SLOT_LIST or SlotList member, declared in its hub's __init__.py 2026-10-31 20:04:47 -10:00
J. Nick Koston b51c5d752f Leave the ld2450 polygon zones on slot_counter: the define also gates their text and binary_sensor code 2026-10-31 20:04:37 -10:00
J. Nick Koston 32b2601019 Leave the hubs that HubFunction will replace on std::vector 2026-10-31 20:03:09 -10:00
J. Nick Koston 6204a2e4bf Merge branch 'slot-list-migrate' into hub-lists-static-vector 2026-10-31 20:02:53 -10:00
J. Nick Koston 1b01363dee Make each converted slot list one SLOT_LIST or SlotList member without #ifdef 2026-10-31 20:02:45 -10:00
J. Nick Koston 9148aae3cb Leave the hubs that HubFunction will replace, and the lists whose define gates more than the list, on slot_counter 2026-10-31 20:00:25 -10:00
J. Nick Koston 4c321183c4 Merge branch 'slot-list-helper' into slot-list-migrate 2026-10-31 19:57:56 -10:00
J. Nick Koston f8271968c1 Emit slot_list defines as 0 for hubs in the build so SLOT_LIST needs no #ifdef 2026-10-31 19:57:44 -10:00
J. Nick Koston b06d6cecad Merge branch 'slot-list-migrate' into hub-lists-static-vector 2026-10-31 19:47:56 -10:00
J. Nick Koston 9a91bf8d00 Merge branch 'slot-list-helper' into slot-list-migrate 2026-10-31 19:47:53 -10:00
J. Nick Koston 623962dfef Silence bugprone-macro-parentheses on the SLOT_LIST type uses 2026-10-31 19:47:44 -10:00
J. Nick Koston 1a6f2f6dc5 [components] Keep codegen-registered hub child lists in StaticVector 2026-10-31 19:36:01 -10:00
J. Nick Koston 50f50956b9 [components] Use cg.slot_list and SLOT_LIST for existing codegen-sized registration lists 2026-10-31 19:34:42 -10:00
J. Nick Koston 7ca31423ec [core] Add cg.slot_list and SLOT_LIST for codegen-sized registration lists 2026-10-31 19:33:08 -10:00
52 changed files with 215 additions and 517 deletions

No files matched your search

+1 -1
View File
@@ -741,7 +741,7 @@ esphome/components/web_server_idf/* @dentra
esphome/components/weikai/* @DrCoolZic
esphome/components/weikai_i2c/* @DrCoolZic
esphome/components/weikai_spi/* @DrCoolZic
esphome/components/whirlpool/* @arpena @glmnet @Rodrigoah
esphome/components/whirlpool/* @arpena @glmnet
esphome/components/whynter/* @aeonsablaze
esphome/components/wiegand/* @ssieb
esphome/components/wireguard/* @droscy @lhoracek @thomas0bernard
+5 -1
View File
@@ -35,9 +35,13 @@ BEDJET_CLIENT_SCHEMA = cv.Schema(
)
_register_child = cg.slot_list("BEDJET_CHILD_COUNT", "register_child", DOMAIN)
async def register_bedjet_child(var: MockObj, config: ConfigType) -> None:
parent = await cg.get_variable(config[CONF_BEDJET_ID])
cg.add(parent.register_child(var))
await cg.register_parented(var, parent)
cg.add(_register_child(parent, var))
async def to_code(config: ConfigType) -> None:
-5
View File
@@ -547,11 +547,6 @@ void BedJetHub::dispatch_status_() {
}
}
void BedJetHub::register_child(BedJetClient *obj) {
this->children_.push_back(obj);
obj->set_parent(this);
}
} // namespace esphome::bedjet
#endif
+2 -5
View File
@@ -6,11 +6,10 @@
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "bedjet_child.h"
#include "bedjet_codec.h"
#include <vector>
#ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h"
#include "esphome/core/time.h"
@@ -92,8 +91,7 @@ class BedJetHub final : public esphome::ble_client::BLEClientNode, public Pollin
bool has_status() { return this->codec_->has_status(); }
const BedjetStatusPacket *get_status_packet() const { return this->codec_->get_status_packet(); }
/** Register a `BedJetClient` child component. */
void register_child(BedJetClient *obj);
SLOT_LIST(BedJetClient, children_, register_child, BEDJET_CHILD_COUNT)
/** Set the status timeout.
*
@@ -127,7 +125,6 @@ class BedJetHub final : public esphome::ble_client::BLEClientNode, public Pollin
esp_ble_gattc_cb_param_t *param) override;
protected:
std::vector<BedJetClient *> children_;
void dispatch_status_();
void dispatch_state_(bool is_ready);
@@ -185,6 +185,9 @@ EFFECTIVE_HIGH_VOLTAGE = {
}
register_touch_pad = cg.slot_list("ESP32_TOUCH_PAD_COUNT", "register_touch_pad", DOMAIN)
def validate_touch_pad(value: Any) -> int:
value = gpio.gpio_pin_number_validator(value)
variant = get_esp32_variant()
@@ -4,7 +4,12 @@ import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_PIN, CONF_THRESHOLD
from esphome.types import ConfigType
from . import ESP32TouchComponent, esp32_touch_ns, validate_touch_pad
from . import (
ESP32TouchComponent,
esp32_touch_ns,
register_touch_pad,
validate_touch_pad,
)
DEPENDENCIES = ["esp32_touch", "esp32"]
@@ -34,4 +39,4 @@ async def to_code(config: ConfigType) -> None:
config[CONF_WAKEUP_THRESHOLD],
)
await binary_sensor.register_binary_sensor(var, config)
cg.add(hub.register_touch_pad(var))
cg.add(register_touch_pad(hub, var))
+2 -4
View File
@@ -3,10 +3,9 @@
#ifdef USE_ESP32
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include <vector>
#include <driver/touch_sens.h>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
@@ -28,7 +27,7 @@ class ESP32TouchBinarySensor;
class ESP32TouchComponent final : public Component {
public:
void register_touch_pad(ESP32TouchBinarySensor *pad) { this->children_.push_back(pad); }
SLOT_LIST(ESP32TouchBinarySensor, children_, register_touch_pad, ESP32_TOUCH_PAD_COUNT)
void set_setup_mode(bool setup_mode) { this->setup_mode_ = setup_mode; }
void set_meas_interval_us(float meas_interval_us) { this->meas_interval_us_ = meas_interval_us; }
@@ -118,7 +117,6 @@ class ESP32TouchComponent final : public Component {
static bool on_inactive_cb(touch_sensor_handle_t handle, const touch_inactive_event_data_t *event, void *ctx);
// Common members
std::vector<ESP32TouchBinarySensor *> children_;
bool setup_mode_{false};
uint32_t setup_mode_last_log_print_{0};
@@ -7,8 +7,6 @@
#include "esphome/core/automation.h"
#include "esphome/components/network/ip_address.h"
#include <atomic>
#ifdef USE_ETHERNET
#ifdef USE_ESP32
@@ -368,9 +366,6 @@ class EthernetComponent final : public Component {
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
StaticVector<EthernetIPStateListener *, ESPHOME_ETHERNET_IP_STATE_LISTENERS> ip_state_listeners_;
#ifdef USE_ESP32
std::atomic<uint8_t> ip_state_pending_{0};
#endif
#endif
#ifdef USE_ETHERNET_CONNECT_TRIGGER
@@ -129,14 +129,6 @@ void EthernetComponent::log_error_and_mark_failed_(esp_err_t err, const char *me
void EthernetComponent::loop() {
const uint32_t now = App.get_loop_component_start_time();
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
// The IP events arrive on the event task; the listeners publish states, so notify them here
if (this->ip_state_pending_.load(std::memory_order_acquire) != 0) {
this->ip_state_pending_.store(0, std::memory_order_relaxed);
this->notify_ip_state_listeners_();
}
#endif
switch (this->state_) {
case EthernetComponentState::STOPPED:
if (this->started_) {
@@ -778,8 +770,7 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base
event_name = "ETH connected";
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
#if defined(USE_ETHERNET_IP_STATE_LISTENERS) && defined(USE_ETHERNET_MANUAL_IP)
global_eth_component->ip_state_pending_.store(1, std::memory_order_release);
global_eth_component->enable_loop_soon_any_context();
global_eth_component->notify_ip_state_listeners_();
#endif
#if USE_NETWORK_IPV6
// Start SLAAC on link-up, not after the DHCPv4 lease. This also restores the
@@ -807,9 +798,6 @@ void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_b
const esp_netif_ip_info_t *ip_info = &event->ip_info;
ESP_LOGV(TAG, "[Ethernet event] ETH Got IP " IPSTR, IP2STR(&ip_info->ip));
global_eth_component->got_ipv4_address_ = true;
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
global_eth_component->ip_state_pending_.store(1, std::memory_order_release);
#endif
#if USE_NETWORK_IPV6 && (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0)
global_eth_component->connected_ = global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT;
global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
@@ -817,6 +805,9 @@ void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_b
global_eth_component->connected_ = true;
global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
#endif /* USE_NETWORK_IPV6 */
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
global_eth_component->notify_ip_state_listeners_();
#endif
}
#if USE_NETWORK_IPV6
@@ -828,9 +819,6 @@ void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_
// after a link flap fires another event for the same address.
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
global_eth_component->ipv6_count_ = esp_netif_get_all_ip6(global_eth_component->eth_netif_, if_ip6s);
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
global_eth_component->ip_state_pending_.store(1, std::memory_order_release);
#endif
#if (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0)
global_eth_component->connected_ =
global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT);
@@ -839,6 +827,9 @@ void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_
global_eth_component->connected_ = global_eth_component->got_ipv4_address_;
global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
#endif
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
global_eth_component->notify_ip_state_listeners_();
#endif
}
#endif /* USE_NETWORK_IPV6 */
+4 -1
View File
@@ -143,6 +143,9 @@ CONFIG_SCHEMA = cv.All(
)
_add_trace = cg.slot_list("GRAPH_TRACE_COUNT", "add_trace", DOMAIN)
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_duration(config[CONF_DURATION]))
@@ -179,7 +182,7 @@ async def to_code(config: ConfigType) -> None:
await cg.add_variable(tr.set_line_color, trace.get(CONF_COLOR))
if CONF_CONTINUOUS in trace:
cg.add(tr.set_continuous(trace[CONF_CONTINUOUS]))
cg.add(var.add_trace(tr))
cg.add(_add_trace(var, tr))
# Add legend
if CONF_LEGEND in config:
lgd = config[CONF_LEGEND]
+2 -3
View File
@@ -1,9 +1,9 @@
#pragma once
#include <cstdint>
#include <span>
#include <vector>
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/color.h"
#include "esphome/core/helpers.h"
#include "esphome/core/string_ref.h"
#include "esphome/core/component.h"
@@ -157,7 +157,7 @@ class Graph final : public Component {
void set_grid_x(float val) { this->gridspacing_x_ = val; }
void set_grid_y(float val) { this->gridspacing_y_ = val; }
void set_border(bool val) { this->border_ = val; }
void add_trace(GraphTrace *trace) { traces_.push_back(trace); }
SLOT_LIST(GraphTrace, traces_, add_trace, GRAPH_TRACE_COUNT)
void add_legend(GraphLegend *legend) {
this->legend_ = legend;
legend->init(this);
@@ -181,7 +181,6 @@ class Graph final : public Component {
float gridspacing_x_{NAN};
float gridspacing_y_{NAN};
bool border_{true};
std::vector<GraphTrace *> traces_;
GraphLegend *legend_{nullptr};
friend GraphLegend;
@@ -1 +1,7 @@
import esphome.codegen as cg
DOMAIN = "kamstrup_kmp"
add_custom_sensor = cg.slot_list(
"KAMSTRUP_KMP_CUSTOM_SENSOR_COUNT", "add_custom_sensor", DOMAIN
)
@@ -28,9 +28,9 @@ void KamstrupKMPComponent::dump_config() {
LOG_SENSOR(" ", "Flow", this->flow_sensor_);
LOG_SENSOR(" ", "Volume", this->volume_sensor_);
for (size_t i = 0; i < this->custom_sensors_.size(); i++) {
LOG_SENSOR(" ", "Custom Sensor", this->custom_sensors_[i]);
ESP_LOGCONFIG(TAG, " Command: 0x%04X", this->custom_commands_[i]);
for (const auto &custom : this->custom_sensors_) {
LOG_SENSOR(" ", "Custom Sensor", custom.sensor);
ESP_LOGCONFIG(TAG, " Command: 0x%04X", custom.command);
}
}
@@ -63,8 +63,8 @@ void KamstrupKMPComponent::update() {
this->command_queue_.push(CMD_VOLUME);
}
for (uint16_t custom_command : this->custom_commands_) {
this->command_queue_.push(custom_command);
for (const auto &custom : this->custom_sensors_) {
this->command_queue_.push(custom.command);
}
}
@@ -261,9 +261,9 @@ void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint
}
// Custom sensors
for (size_t i = 0; i < this->custom_commands_.size(); i++) {
if (command == this->custom_commands_[i]) {
this->custom_sensors_[i]->publish_state(value);
for (const auto &custom : this->custom_sensors_) {
if (command == custom.command) {
custom.sensor->publish_state(value);
}
}
@@ -4,6 +4,7 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
namespace esphome::kamstrup_kmp {
@@ -75,15 +76,17 @@ class KamstrupKMPComponent final : public PollingComponent, public uart::UARTDev
void dump_config() override;
void update() override;
void loop() override;
SLOT_LIST_REQUIRES(KAMSTRUP_KMP_CUSTOM_SENSOR_COUNT)
void add_custom_sensor(sensor::Sensor *sensor, uint16_t command) {
this->custom_sensors_.push_back(sensor);
this->custom_commands_.push_back(command);
this->custom_sensors_.push_back({sensor, command});
}
protected:
// Custom sensors and commands
std::vector<sensor::Sensor *> custom_sensors_;
std::vector<uint16_t> custom_commands_;
struct CustomSensor {
sensor::Sensor *sensor;
uint16_t command;
};
[[no_unique_address]] SlotList<CustomSensor, KAMSTRUP_KMP_CUSTOM_SENSOR_COUNT> custom_sensors_;
// Command queue
std::queue<uint16_t> command_queue_;
+3 -1
View File
@@ -24,6 +24,8 @@ from esphome.const import (
)
from esphome.types import ConfigType
from . import add_custom_sensor
CODEOWNERS = ["@cfeenstra1024"]
DEPENDENCIES = ["uart"]
@@ -134,4 +136,4 @@ async def to_code(config: ConfigType) -> None:
if CONF_CUSTOM in config:
for conf in config[CONF_CUSTOM]:
sens = await sensor.new_sensor(conf)
cg.add(var.add_custom_sensor(sens, conf[CONF_COMMAND]))
cg.add(add_custom_sensor(var, sens, conf[CONF_COMMAND]))
+8 -2
View File
@@ -152,6 +152,12 @@ def _final_validate(config: ConfigType) -> None:
FINAL_VALIDATE_SCHEMA = _final_validate
_add_server_register = cg.slot_list(
"MODBUS_SERVER_REGISTER_COUNT", "add_server_register", DOMAIN
)
_add_server_bit = cg.slot_list("MODBUS_SERVER_BIT_COUNT", "add_server_bit", DOMAIN)
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
if server_courtesy_response := config.get(CONF_COURTESY_RESPONSE):
@@ -200,7 +206,7 @@ async def to_code(config: ConfigType) -> None:
)
if server_register[CONF_ALLOW_PARTIAL_READ]:
cg.add(server_register_var.set_allow_partial_read(True))
cg.add(var.add_server_register(server_register_var))
cg.add(_add_server_register(var, server_register_var))
for server_bit in config.get(CONF_BITS, []):
server_bit_var = cg.new_Pvariable(server_bit[CONF_ID], server_bit[CONF_ADDRESS])
cg.add(
@@ -222,6 +228,6 @@ async def to_code(config: ConfigType) -> None:
)
)
)
cg.add(var.add_server_bit(server_bit_var))
cg.add(_add_server_bit(var, server_bit_var))
await cg.register_component(var, config)
return await modbus.register_modbus_server_device(var, config)
@@ -5,9 +5,9 @@
#include "esphome/components/modbus/modbus.h"
#include "esphome/components/modbus/modbus_helpers.h"
#include "esphome/core/automation.h"
#include "esphome/core/helpers.h"
#include <utility>
#include <vector>
namespace esphome::modbus_server {
@@ -125,10 +125,8 @@ class ModbusServer final : public Component, public modbus::ModbusServerDevice {
public:
void dump_config() override;
/// Registers a server register with the controller. Called by esphomes code generator
void add_server_register(ServerRegister *server_register) { server_registers_.push_back(server_register); }
/// Registers a server bit with the controller. Called by esphomes code generator
void add_server_bit(ServerBit *server_bit) { server_bits_.push_back(server_bit); }
SLOT_LIST(ServerRegister, server_registers_, add_server_register, MODBUS_SERVER_REGISTER_COUNT)
SLOT_LIST(ServerBit, server_bits_, add_server_bit, MODBUS_SERVER_BIT_COUNT)
/// called when a modbus request (function code 0x03 or 0x04) was parsed without errors
modbus::ResponseStatus on_read_registers(uint16_t start_address, uint16_t number_of_registers,
modbus::RegisterValues &registers) final;
@@ -151,10 +149,6 @@ class ModbusServer final : public Component, public modbus::ModbusServerDevice {
ServerRegister *find_containing_register_(uint32_t address) const;
/// Find the registered bit at address, or nullptr if none is.
ServerBit *find_bit_(uint16_t address) const;
/// Collection of all server registers for this component
std::vector<ServerRegister *> server_registers_{};
/// Collection of all server bits (coils/discrete inputs) for this component
std::vector<ServerBit *> server_bits_{};
/// Server courtesy response
ServerCourtesyResponse server_courtesy_response_{
.enabled = false, .register_last_address = 0xFFFF, .register_value = 0};
+16
View File
@@ -23,3 +23,19 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
},
}
)
register_sensor_component = cg.slot_list(
"NEXTION_SENSOR_COUNT", "register_sensor_component", DOMAIN
)
register_textsensor_component = cg.slot_list(
"NEXTION_TEXT_SENSOR_COUNT", "register_textsensor_component", DOMAIN
)
register_switch_component = cg.slot_list(
"NEXTION_SWITCH_COUNT", "register_switch_component", DOMAIN
)
register_touch_component = cg.slot_list(
"NEXTION_TOUCH_COUNT", "register_touch_component", DOMAIN
)
register_binarysensor_component = cg.slot_list(
"NEXTION_BINARY_SENSOR_COUNT", "register_binarysensor_component", DOMAIN
)
@@ -4,7 +4,14 @@ from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import CONF_COMPONENT_ID, CONF_ID, CONF_PAGE_ID, CONF_STATE
from .. import CONF_NEXTION_ID, CONF_PUBLISH_STATE, CONF_SEND_TO_NEXTION, nextion_ns
from .. import (
CONF_NEXTION_ID,
CONF_PUBLISH_STATE,
CONF_SEND_TO_NEXTION,
nextion_ns,
register_binarysensor_component,
register_touch_component,
)
from ..base_component import (
CONF_COMPONENT_NAME,
CONF_VARIABLE_NAME,
@@ -44,13 +51,13 @@ async def to_code(config):
await cg.register_component(var, config)
if config.keys() >= {CONF_PAGE_ID, CONF_COMPONENT_ID}:
cg.add(hub.register_touch_component(var))
cg.add(register_touch_component(hub, var))
cg.add(var.set_component_id(config[CONF_COMPONENT_ID]))
cg.add(var.set_page_id(config[CONF_PAGE_ID]))
if CONF_COMPONENT_NAME in config or CONF_VARIABLE_NAME in config:
await setup_component_core_(var, config, ".val")
cg.add(hub.register_binarysensor_component(var))
cg.add(register_binarysensor_component(hub, var))
automation.register_apply_action(
+6 -15
View File
@@ -2,19 +2,15 @@
#include <list>
#include <utility>
#include <vector>
#include "esphome/components/display/display.h"
#include "esphome/components/display/display_color_utils.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/string_ref.h"
#include "esphome/core/time.h"
#ifdef USE_NEXTION_WAVEFORM
#include "esphome/core/helpers.h"
#endif // USE_NEXTION_WAVEFORM
#include "nextion_base.h"
#include "nextion_component.h"
@@ -1050,11 +1046,11 @@ class Nextion final : public NextionBase, public PollingComponent, public uart::
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
void register_touch_component(NextionComponentBase *obj) { this->touch_.push_back(obj); }
void register_switch_component(NextionComponentBase *obj) { this->switchtype_.push_back(obj); }
void register_binarysensor_component(NextionComponentBase *obj) { this->binarysensortype_.push_back(obj); }
void register_sensor_component(NextionComponentBase *obj) { this->sensortype_.push_back(obj); }
void register_textsensor_component(NextionComponentBase *obj) { this->textsensortype_.push_back(obj); }
SLOT_LIST(NextionComponentBase, touch_, register_touch_component, NEXTION_TOUCH_COUNT)
SLOT_LIST(NextionComponentBase, switchtype_, register_switch_component, NEXTION_SWITCH_COUNT)
SLOT_LIST(NextionComponentBase, binarysensortype_, register_binarysensor_component, NEXTION_BINARY_SENSOR_COUNT)
SLOT_LIST(NextionComponentBase, sensortype_, register_sensor_component, NEXTION_SENSOR_COUNT)
SLOT_LIST(NextionComponentBase, textsensortype_, register_textsensor_component, NEXTION_TEXT_SENSOR_COUNT)
void setup() override;
void set_brightness(float brightness) { this->brightness_ = brightness; }
@@ -1631,11 +1627,6 @@ class Nextion final : public NextionBase, public PollingComponent, public uart::
bool check_connect_();
std::vector<NextionComponentBase *> touch_;
std::vector<NextionComponentBase *> switchtype_;
std::vector<NextionComponentBase *> sensortype_;
std::vector<NextionComponentBase *> textsensortype_;
std::vector<NextionComponentBase *> binarysensortype_;
CallbackManager<void()> setup_callback_{};
CallbackManager<void()> sleep_callback_{};
CallbackManager<void()> wake_callback_{};
@@ -4,7 +4,13 @@ from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import CONF_COMPONENT_ID, CONF_ID, CONF_STATE
from .. import CONF_NEXTION_ID, CONF_PUBLISH_STATE, CONF_SEND_TO_NEXTION, nextion_ns
from .. import (
CONF_NEXTION_ID,
CONF_PUBLISH_STATE,
CONF_SEND_TO_NEXTION,
nextion_ns,
register_sensor_component,
)
from ..base_component import (
CONF_COMPONENT_NAME,
CONF_PRECISION,
@@ -61,7 +67,7 @@ async def to_code(config):
await cg.register_component(var, config)
await sensor.register_sensor(var, config)
cg.add(hub.register_sensor_component(var))
cg.add(register_sensor_component(hub, var))
await setup_component_core_(var, config, ".val")
@@ -4,7 +4,13 @@ from esphome.components import switch
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_STATE
from .. import CONF_NEXTION_ID, CONF_PUBLISH_STATE, CONF_SEND_TO_NEXTION, nextion_ns
from .. import (
CONF_NEXTION_ID,
CONF_PUBLISH_STATE,
CONF_SEND_TO_NEXTION,
nextion_ns,
register_switch_component,
)
from ..base_component import (
CONF_COMPONENT_NAME,
CONF_VARIABLE_NAME,
@@ -30,7 +36,7 @@ async def to_code(config):
await cg.register_component(var, config)
await switch.register_switch(var, config)
cg.add(hub.register_switch_component(var))
cg.add(register_switch_component(hub, var))
await setup_component_core_(var, config, ".val")
@@ -4,7 +4,13 @@ from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_STATE
from .. import CONF_NEXTION_ID, CONF_PUBLISH_STATE, CONF_SEND_TO_NEXTION, nextion_ns
from .. import (
CONF_NEXTION_ID,
CONF_PUBLISH_STATE,
CONF_SEND_TO_NEXTION,
nextion_ns,
register_textsensor_component,
)
from ..base_component import CONFIG_TEXT_COMPONENT_SCHEMA, setup_component_core_
CODEOWNERS = ["@senexcrenshaw"]
@@ -26,7 +32,7 @@ async def to_code(config):
await text_sensor.register_text_sensor(var, config)
await cg.register_component(var, config)
cg.add(hub.register_textsensor_component(var))
cg.add(register_textsensor_component(hub, var))
await setup_component_core_(var, config, ".txt")
+3
View File
@@ -34,6 +34,9 @@ RC522_SCHEMA = cv.Schema(
).extend(cv.polling_component_schema("1s"))
register_tag = cg.slot_list("RC522_BINARY_SENSOR_COUNT", "register_tag", DOMAIN)
async def setup_rc522(var: MockObj, config: ConfigType) -> None:
await cg.register_component(var, config)
+2 -2
View File
@@ -7,7 +7,7 @@ from esphome.const import CONF_UID
from esphome.core import HexInt
from esphome.types import ConfigType
from . import CONF_RC522_ID, RC522, rc522_ns
from . import CONF_RC522_ID, RC522, rc522_ns, register_tag
DEPENDENCIES = ["rc522"]
@@ -46,6 +46,6 @@ async def to_code(config: ConfigType) -> None:
var = await binary_sensor.new_binary_sensor(config)
hub = await cg.get_variable(config[CONF_RC522_ID])
cg.add(hub.register_tag(var))
cg.add(register_tag(hub, var))
addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split("-")]
cg.add(var.set_uid(addr))
+2 -2
View File
@@ -3,6 +3,7 @@
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/automation.h"
#include "esphome/core/helpers.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include <vector>
@@ -20,7 +21,7 @@ class RC522 : public PollingComponent {
void loop() override;
void register_tag(RC522BinarySensor *tag) { this->binary_sensors_.push_back(tag); }
SLOT_LIST(RC522BinarySensor, binary_sensors_, register_tag, RC522_BINARY_SENSOR_COUNT)
#ifdef RC522_ON_TAG_COUNT
void add_on_tag_automation(Automation<std::string> *automation) { this->tag_automations_.add(automation); }
#endif
@@ -245,7 +246,6 @@ class RC522 : public PollingComponent {
GPIOPin *reset_pin_{nullptr};
uint8_t reset_count_{0};
uint32_t reset_timeout_{0};
std::vector<RC522BinarySensor *> binary_sensors_;
#ifdef RC522_ON_TAG_COUNT
AutomationList<RC522_ON_TAG_COUNT, std::string> tag_automations_;
#endif
+7 -1
View File
@@ -473,6 +473,11 @@ for _name, _schema, _apply in (
automation.register_apply_action(_name, _schema, _apply)
_add_controller = cg.slot_list(
"SPRINKLER_OTHER_CONTROLLER_COUNT", "add_controller", DOMAIN
)
async def to_code(config):
for sprinkler_controller in config:
if len(sprinkler_controller[CONF_VALVES]) > 1:
@@ -714,4 +719,5 @@ async def to_code(config):
var = await cg.get_variable(sprinkler_controller[CONF_ID])
for controller_to_add in config:
if sprinkler_controller[CONF_ID] != controller_to_add[CONF_ID]:
await cg.add_variable(var.add_controller, controller_to_add[CONF_ID])
other = await cg.get_variable(controller_to_add[CONF_ID])
cg.add(_add_controller(var, other))
@@ -377,8 +377,6 @@ void Sprinkler::add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControll
}
}
void Sprinkler::add_controller(Sprinkler *other_controller) { this->other_controllers_.push_back(other_controller); }
void Sprinkler::set_controller_main_switch(SprinklerControllerSwitch *controller_switch) {
this->controller_sw_ = controller_switch;
controller_switch->set_state_lambda([this]() -> optional<bool> {
+2 -5
View File
@@ -184,8 +184,8 @@ class Sprinkler final : public Component {
/// add a valve to the controller
void add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControllerSwitch *enable_sw = nullptr);
/// add another controller to the controller so it can check if pumps/main valves are in use
void add_controller(Sprinkler *other_controller);
// Other controllers, to check if pumps/main valves are in use
SLOT_LIST(Sprinkler, other_controllers_, add_controller, SPRINKLER_OTHER_CONTROLLER_COUNT)
/// configure important controller switches
void set_controller_main_switch(SprinklerControllerSwitch *controller_switch);
@@ -568,9 +568,6 @@ class Sprinkler final : public Component {
/// Valve control timers, indexed by SprinklerTimerIndex (also used as the scheduler id)
std::array<SprinklerTimer, 2> timer_;
/// Other Sprinkler instances we should be aware of (used to check if pumps are in use)
std::vector<Sprinkler *> other_controllers_;
/// Switches we'll present to the front end
SprinklerControllerSwitch *auto_adv_sw_{nullptr};
SprinklerControllerSwitch *controller_sw_{nullptr};
+4 -1
View File
@@ -239,6 +239,9 @@ CONFIG_SCHEMA = cv.ensure_list(
)
_add_channel = cg.slot_list("USB_UART_CHANNEL_COUNT", "add_channel", DOMAIN)
async def to_code(config: list[ConfigType]) -> None:
# The output chunk pool/queue are compile-time-sized templates shared by all
# USBUartChannel instances, so use the largest buffer_size across every channel
@@ -271,6 +274,6 @@ async def to_code(config: list[ConfigType]) -> None:
cg.add(chvar.set_debug(channel[CONF_DEBUG]))
if channel[CONF_DEBUG_PREFIX]:
cg.add(chvar.set_debug_prefix(channel[CONF_DEBUG_PREFIX]))
cg.add(var.add_channel(chvar))
cg.add(_add_channel(var, chvar))
if channel[CONF_DEBUG]:
cg.add_define("USE_UART_DEBUGGER")
+2 -4
View File
@@ -243,9 +243,9 @@ class USBUartComponent : public usb_host::USBClient {
void setup() override;
void loop() override;
void dump_config() override;
std::vector<USBUartChannelBase *> get_channels() { return this->channels_; }
const auto &get_channels() const { return this->channels_; }
void add_channel(USBUartChannelBase *channel) { this->channels_.push_back(channel); }
SLOT_LIST(USBUartChannelBase, channels_, add_channel, USB_UART_CHANNEL_COUNT)
virtual void start_input(USBUartChannelBase *channel);
void start_output(USBUartChannelBase *channel);
@@ -306,8 +306,6 @@ class USBUartComponent : public usb_host::USBClient {
void read_interface_descriptions_();
void on_interface_string(uint8_t interface_number, const usb_str_desc_t *desc) override;
std::vector<USBUartChannelBase *> channels_{};
// Config state machine
USBUartChannelBase *cfg_single_{nullptr}; // non-null: single-channel run (RELOAD or MODEM)
uint8_t *cfg_read_dest_{nullptr}; // where the step in flight stores its reply, if not cfg_response_
+4
View File
@@ -74,6 +74,9 @@ WKBASE_SCHEMA = cv.Schema(
).extend(cv.COMPONENT_SCHEMA)
_register_channel = cg.slot_list("WEIKAI_CHANNEL_COUNT", "register_channel", DOMAIN)
async def register_weikai(var: MockObj, config: ConfigType) -> None:
"""Register an weikai device with the given config."""
cg.add(var.set_crystal(config[CONF_CRYSTAL]))
@@ -83,6 +86,7 @@ async def register_weikai(var: MockObj, config: ConfigType) -> None:
chan = cg.new_Pvariable(uart_elem[CONF_ID])
cg.add(chan.set_channel_name(str(uart_elem[CONF_ID])))
cg.add(chan.set_parent(var))
cg.add(_register_channel(var, chan))
cg.add(chan.set_channel(uart_elem[CONF_CHANNEL]))
cg.add(chan.set_baud_rate(uart_elem[CONF_BAUD_RATE]))
cg.add(chan.set_stop_bits(uart_elem[CONF_STOP_BITS]))
+10 -12
View File
@@ -147,6 +147,8 @@ class WeikaiComponent : public Component {
bool page1() { return page1_; }
SLOT_LIST(WeikaiChannel, children_, register_channel, WEIKAI_CHANNEL_COUNT)
/// @brief Factory method to create a Register object
/// @param reg address of the register
/// @param channel channel associated with this register
@@ -182,14 +184,13 @@ class WeikaiComponent : public Component {
/// @}
#endif
uint8_t pin_config_{0x00}; ///< pin config mask: 1 means OUTPUT, 0 means INPUT
uint8_t output_state_{0x00}; ///< output state: 1 means HIGH, 0 means LOW
uint8_t input_state_{0x00}; ///< input pin states: 1 means HIGH, 0 means LOW
uint32_t crystal_; ///< crystal value;
int test_mode_; ///< test mode value (0 -> no tests)
bool page1_{false}; ///< set to true when in "page1 mode"
std::vector<WeikaiChannel *> children_{}; ///< the list of WeikaiChannel UART children
std::string name_; ///< name of entity
uint8_t pin_config_{0x00}; ///< pin config mask: 1 means OUTPUT, 0 means INPUT
uint8_t output_state_{0x00}; ///< output state: 1 means HIGH, 0 means LOW
uint8_t input_state_{0x00}; ///< input pin states: 1 means HIGH, 0 means LOW
uint32_t crystal_; ///< crystal value;
int test_mode_; ///< test mode value (0 -> no tests)
bool page1_{false}; ///< set to true when in "page1 mode"
std::string name_; ///< name of entity
};
///////////////////////////////////////////////////////////////////////////////
@@ -226,10 +227,7 @@ class WeikaiChannel final : public uart::UARTComponent {
public:
/// @brief We belongs to this WeikaiComponent
/// @param parent pointer to the component we belongs to
void set_parent(WeikaiComponent *parent) {
this->parent_ = parent;
this->parent_->children_.push_back(this); // add ourself to the list (vector)
}
void set_parent(WeikaiComponent *parent) { this->parent_ = parent; }
/// @brief Sets the channel number
/// @param channel number
+2 -6
View File
@@ -5,15 +5,13 @@ from esphome.const import CONF_MODEL
from esphome.types import ConfigType
AUTO_LOAD = ["climate_ir"]
CODEOWNERS = ["@glmnet", "@arpena", "@Rodrigoah"]
CODEOWNERS = ["@glmnet", "@arpena"]
whirlpool_ns = cg.esphome_ns.namespace("whirlpool")
WhirlpoolClimate = whirlpool_ns.class_("WhirlpoolClimate", climate_ir.ClimateIR)
Model = whirlpool_ns.enum("Model")
MODELS = Model.options(
"DG11J1-3A", "DG11J1-91", "HISENSE_ARG", "HITACHI_HCRA31NEWH", prefix="MODEL_"
)
MODELS = Model.options("DG11J1-3A", "DG11J1-91", "HISENSE_ARG", prefix="MODEL_")
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(WhirlpoolClimate).extend(
{
@@ -28,5 +26,3 @@ async def to_code(config: ConfigType) -> None:
# cv.enum keeps the YAML key as a str subclass; the MockObj value cannot be compared
if config[CONF_MODEL] == "HISENSE_ARG":
cg.add_define("USE_WHIRLPOOL_HISENSE_ARG")
elif config[CONF_MODEL] == "HITACHI_HCRA31NEWH":
cg.add_define("USE_WHIRLPOOL_HITACHI_HCRA31NEWH")
+15 -61
View File
@@ -2,8 +2,6 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <algorithm>
namespace esphome::whirlpool {
ESPHOME_LOG_TAG(TAG, "whirlpool.climate");
@@ -46,17 +44,6 @@ static constexpr uint8_t HISENSE_ARG_POWER_TOGGLE = 0x01; // byte 15
static constexpr uint8_t HISENSE_ARG_SETTINGS_ONLY = 0x02; // byte 15
static constexpr uint8_t HISENSE_ARG_FIXED_TEMP_STEP = 7; // auto and fan-only send 23 C
// Hitachi HCRA31NEWH model: power is a state in every frame
static constexpr uint8_t HITACHI_BYTE10 = 0x20; // byte 10
static constexpr uint8_t HITACHI_COOL = 0x02; // byte 15
static constexpr uint8_t HITACHI_OFF = 0x01; // byte 15
static constexpr uint8_t HITACHI_BYTE18_ON = 0x38; // byte 18
static constexpr uint8_t HITACHI_BYTE18_OFF = 0x28; // byte 18
static constexpr uint8_t HITACHI_BYTE19 = 0x08; // byte 19
// Header, 21 bytes of 8 bits, two dividers and the footer mark
static constexpr uint32_t WHIRLPOOL_FRAME_TIMINGS = 2 + WHIRLPOOL_STATE_LENGTH * 16 + 2 * 2 + 1;
WhirlpoolClimate::WhirlpoolClimate()
: climate_ir::ClimateIR(
WHIRLPOOL_DG11J1_3A_TEMP_MIN, WHIRLPOOL_DG11J1_3A_TEMP_MAX, 1.0f, true, true,
@@ -68,19 +55,19 @@ void WhirlpoolClimate::transmit_state() {
this->last_transmit_time_ = millis(); // setting the time of the last transmission.
uint8_t remote_state[WHIRLPOOL_STATE_LENGTH];
memset(remote_state, 0, sizeof(remote_state));
put_be16(remote_state, this->remote_id_());
remote_state[0] = 0x83;
remote_state[1] = 0x06;
remote_state[6] = 0x80;
remote_state[18] = 0x08;
remote_state[18] = 0x08; // the same on every supported model
const bool powered_on = this->mode != climate::CLIMATE_MODE_OFF;
// Hitachi sends power as a state instead of a toggle
const bool power_toggle = !this->is_hitachi_hcra31newh_() && powered_on != this->powered_on_assumed;
auto powered_on = this->mode != climate::CLIMATE_MODE_OFF;
const bool power_toggle = powered_on != this->powered_on_assumed;
if (power_toggle) {
// Set power toggle command
remote_state[2] = 4;
remote_state[15] = 1;
this->powered_on_assumed = powered_on;
}
this->powered_on_assumed = powered_on;
switch (this->mode) {
case climate::CLIMATE_MODE_HEAT_COOL:
// set fan auto
@@ -106,10 +93,6 @@ void WhirlpoolClimate::transmit_state() {
remote_state[15] = 6;
break;
case climate::CLIMATE_MODE_OFF:
// These remotes keep the cool code in the power-off frame
if (this->is_hisense_arg_() || this->is_hitachi_hcra31newh_())
remote_state[3] = WHIRLPOOL_COOL;
break;
default:
break;
}
@@ -119,7 +102,6 @@ void WhirlpoolClimate::transmit_state() {
// Temperature
auto temp = (uint8_t) roundf(clamp(this->target_temperature, this->temperature_min_(), this->temperature_max_()));
const auto temp_step = (uint8_t) (temp - this->temperature_min_());
this->target_temperature = temp; // publish what the unit was sent
if (hisense_arg && this->fixed_temp_mode_()) {
remote_state[3] |= HISENSE_ARG_FIXED_TEMP_STEP << 4;
} else {
@@ -149,6 +131,7 @@ void WhirlpoolClimate::transmit_state() {
remote_state[2] |= WHIRLPOOL_SWING_MASK;
remote_state[8] = WHIRLPOOL_SWING_BYTE8;
}
this->encode_hisense_arg_(remote_state, power_toggle, temp_step);
} else if (this->send_swing_cmd_) {
if (this->swing_mode == climate::CLIMATE_SWING_VERTICAL || this->swing_mode == climate::CLIMATE_SWING_OFF) {
remote_state[2] |= WHIRLPOOL_SWING_MASK;
@@ -156,11 +139,6 @@ void WhirlpoolClimate::transmit_state() {
}
}
if (hisense_arg)
this->encode_hisense_arg_(remote_state, power_toggle, temp_step);
if (this->is_hitachi_hcra31newh_())
this->encode_hitachi_hcra31newh_(remote_state);
// Checksum
remote_state[13] = xor_bytes(&remote_state[2], 11, remote_state[13]);
remote_state[20] = xor_bytes(&remote_state[14], 6, remote_state[20]);
@@ -170,7 +148,6 @@ void WhirlpoolClimate::transmit_state() {
// Send code
auto transmit = this->transmitter_->transmit();
auto *data = transmit.get_data();
data->reserve(WHIRLPOOL_FRAME_TIMINGS);
data->set_carrier_frequency(38000);
@@ -203,6 +180,10 @@ void WhirlpoolClimate::encode_hisense_arg_(uint8_t *remote_state, bool power_tog
remote_state[15] = power_toggle ? HISENSE_ARG_POWER_TOGGLE : HISENSE_ARG_SETTINGS_ONLY;
switch (this->mode) {
case climate::CLIMATE_MODE_OFF:
// The remote keeps the cool code in the power-off frame
remote_state[3] = (remote_state[3] & 0xF0) | WHIRLPOOL_COOL;
break;
case climate::CLIMATE_MODE_DRY:
// The unit runs these fan speeds itself; the user's choice is kept for the next mode change
remote_state[2] &= ~WHIRLPOOL_FAN_MASK;
@@ -231,27 +212,6 @@ void WhirlpoolClimate::encode_hisense_arg_(uint8_t *remote_state, bool power_tog
#endif
}
void WhirlpoolClimate::encode_hitachi_hcra31newh_(uint8_t *remote_state) const {
#ifdef USE_WHIRLPOOL_HITACHI_HCRA31NEWH
remote_state[10] = HITACHI_BYTE10;
remote_state[18] = HITACHI_BYTE18_ON;
remote_state[19] = HITACHI_BYTE19;
// The unit takes the whole state from every frame: only the off frame carries the power bit
switch (this->mode) {
case climate::CLIMATE_MODE_COOL:
remote_state[15] = HITACHI_COOL;
break;
case climate::CLIMATE_MODE_OFF:
remote_state[2] |= WHIRLPOOL_POWER;
remote_state[15] = HITACHI_OFF;
remote_state[18] = HITACHI_BYTE18_OFF;
break;
default:
break;
}
#endif
}
bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) {
// Check if the esp isn't currently transmitting.
if (millis() - this->last_transmit_time_ < 500) {
@@ -274,8 +234,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) {
if (!data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_GAP))
return false;
}
// The Hitachi remote always sends all 21 bytes; byte 18 holds its power state
if (i == WHIRLPOOL_SECOND_DIVIDER && !data.is_valid() && !this->is_hitachi_hcra31newh_()) {
if (i == WHIRLPOOL_SECOND_DIVIDER && !data.is_valid()) {
// Remote control only sent 14 bytes, nothing more to read, not even the footer
ESP_LOGV(TAG, "Remote control only sent %d bytes", i);
skip_footer = true;
@@ -302,18 +261,13 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) {
ESP_LOGV_HEX(TAG, remote_state, WHIRLPOOL_STATE_LENGTH, WHIRLPOOL_STATE_LENGTH, "Received: %s");
// verify header remote code
if (encode_uint16(remote_state[0], remote_state[1]) != this->remote_id_())
if (remote_state[0] != 0x83 || remote_state[1] != 0x06)
return false;
// powr on/off button
ESP_LOGV(TAG, "Power: %02X", (remote_state[2] & WHIRLPOOL_POWER));
if (this->is_hitachi_hcra31newh_()) {
// Power is a state: byte 18 says whether the unit is off
this->powered_on_assumed = remote_state[18] != HITACHI_BYTE18_OFF;
if (!this->powered_on_assumed)
this->mode = climate::CLIMATE_MODE_OFF;
} else if ((remote_state[2] & WHIRLPOOL_POWER) == WHIRLPOOL_POWER) {
if ((remote_state[2] & WHIRLPOOL_POWER) == WHIRLPOOL_POWER) {
auto powered_on = this->mode != climate::CLIMATE_MODE_OFF;
if (powered_on) {
@@ -355,7 +309,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) {
int temp = remote_state[3] & 0xF0;
ESP_LOGVV(TAG, "Temperature Raw: %02X", temp);
temp = (uint8_t) temp >> 4;
temp = std::min(temp + static_cast<int>(this->temperature_min_()), static_cast<int>(this->temperature_max_()));
temp += static_cast<int>(this->temperature_min_());
ESP_LOGVV(TAG, "Temperature Climate: %u", temp);
this->target_temperature = temp;
}
+4 -19
View File
@@ -6,10 +6,9 @@ namespace esphome::whirlpool {
/// Simple enum to represent models.
enum Model {
MODEL_DG11J1_3A = 0, /// Temperature range is from 18 to 32
MODEL_DG11J1_91 = 1, /// Temperature range is from 16 to 30
MODEL_HISENSE_ARG = 2, /// Hisense/BGH (Argentina), 16 to 30
MODEL_HITACHI_HCRA31NEWH = 3, /// Hitachi with the HCRA31NEWH remote, 16 to 30
MODEL_DG11J1_3A = 0, /// Temperature range is from 18 to 32
MODEL_DG11J1_91 = 1, /// Temperature range is from 16 to 30
MODEL_HISENSE_ARG = 2, /// Hisense/BGH (Argentina), 16 to 30
};
// Temperature
@@ -30,9 +29,7 @@ class WhirlpoolClimate final : public climate_ir::ClimateIR {
/// Override control to change settings of the climate device.
void control(const climate::ClimateCall &call) override {
// The toggle remotes flip swing on every press, so only send one when the mode really changes
auto swing = call.get_swing_mode();
this->send_swing_cmd_ = swing.has_value() && *swing != this->swing_mode;
send_swing_cmd_ = call.get_swing_mode().has_value();
climate_ir::ClimateIR::control(call);
}
@@ -58,22 +55,10 @@ class WhirlpoolClimate final : public climate_ir::ClimateIR {
return false;
#endif
}
/// False when the model is compiled out, so the compiler drops the Hitachi branches.
bool is_hitachi_hcra31newh_() const {
#ifdef USE_WHIRLPOOL_HITACHI_HCRA31NEWH
return this->model_ == MODEL_HITACHI_HCRA31NEWH;
#else
return false;
#endif
}
/// The remote ID sent in bytes 0 and 1.
uint16_t remote_id_() const { return this->is_hitachi_hcra31newh_() ? 0xA959 : 0x8306; }
/// Modes in which the Hisense ARG model sends a fixed temperature.
bool fixed_temp_mode_() const;
/// Add the Hisense ARG bytes on top of the common whirlpool frame.
void encode_hisense_arg_(uint8_t *remote_state, bool power_toggle, uint8_t temp_step) const;
/// Add the Hitachi HCRA31NEWH bytes on top of the common whirlpool frame.
void encode_hitachi_hcra31newh_(uint8_t *remote_state) const;
/// Set the time of the last transmission.
uint32_t last_transmit_time_{};
+1 -1
View File
@@ -563,7 +563,7 @@ class WiFiComponent final : public Component {
// (In most use cases you won't need these)
/// Setup WiFi interface.
void setup() override;
#if defined(USE_ESP32) || defined(USE_LN882X) || defined(USE_BK72XX)
#if defined(USE_LN882X) || defined(USE_BK72XX)
void on_powerdown() override;
#endif
void start();
@@ -1635,21 +1635,6 @@ bool WiFiComponent::wifi_dpp_start_listen_() {
void WiFiComponent::wifi_dpp_stop_listen_() { esp_supp_dpp_stop_listen(); }
#endif
void WiFiComponent::on_powerdown() {
// Leave the AP before esp_restart() or deep sleep stops Wi-Fi. esp_wifi_stop() while associated
// makes the ESP32 retransmit its Disassociation up to the retry limit although the AP ACKs it
// (32 copies with protected management frames), which some APs handle badly.
// Upstream bug: https://github.com/espressif/esp-idf/issues/19443 - remove this once it is fixed
// in the ESP-IDF versions we support.
// Ask the driver: s_sta_connected lags until the main loop has handled the connect event.
wifi_ap_record_t ap_info;
if (esp_wifi_sta_get_ap_info(&ap_info) != ESP_OK)
return;
ESP_LOGD(TAG, "Disconnecting before powerdown");
esp_wifi_disconnect();
delay(100); // NOLINT: once per reboot or deep sleep, lets the disassociation go out
}
bool WiFiComponent::wifi_disconnect_() { return esp_wifi_disconnect(); }
bssid_t WiFiComponent::wifi_bssid() {
-2
View File
@@ -294,8 +294,6 @@ void Application::reboot() {
for (auto &component : std::ranges::reverse_view(this->components_)) {
component->on_shutdown();
}
// No teardown on a forced reboot, but still power hardware down (Wi-Fi leaves the AP)
this->run_powerdown_hooks();
arch_restart();
}
void Application::safe_reboot() {
+2 -4
View File
@@ -185,12 +185,10 @@ class Component {
*/
virtual bool teardown() { return true; }
/** Called last before a reboot or deep sleep to power down hardware.
/** Called after teardown is complete to power down hardware.
*
* On a safe shutdown this is called after all components have finished their teardown process,
* This is called after all components have finished their teardown process,
* making it safe to power down hardware like ethernet PHY.
* On a forced reboot (Application::reboot()) it is called right after on_shutdown() with no
* teardown, so implementations must not assume teardown() has run.
*/
virtual void on_powerdown() {}
+15 -1
View File
@@ -292,6 +292,21 @@
#define SYSTA_BUS_LISTENER_COUNT 2
#define SERIAL_PROXY_COUNT 2
#define SNTP_SERVER_COUNT 3
#define BEDJET_CHILD_COUNT 1
#define ESP32_TOUCH_PAD_COUNT 1
#define GRAPH_TRACE_COUNT 1
#define KAMSTRUP_KMP_CUSTOM_SENSOR_COUNT 1
#define MODBUS_SERVER_BIT_COUNT 1
#define MODBUS_SERVER_REGISTER_COUNT 1
#define NEXTION_BINARY_SENSOR_COUNT 1
#define NEXTION_SENSOR_COUNT 1
#define NEXTION_SWITCH_COUNT 1
#define NEXTION_TEXT_SENSOR_COUNT 1
#define NEXTION_TOUCH_COUNT 1
#define RC522_BINARY_SENSOR_COUNT 1
#define SPRINKLER_OTHER_CONTROLLER_COUNT 1
#define USB_UART_CHANNEL_COUNT 1
#define WEIKAI_CHANNEL_COUNT 1
#define USE_MEDIA_PLAYER
#define USE_MEDIA_SOURCE
#define NETWORK_ACTIVE_INTERFACE_LISTENER_COUNT 1
@@ -387,7 +402,6 @@
#define USE_WATER_HEATER
#define USE_WATER_HEATER_VISUAL_OVERRIDES
#define USE_WHIRLPOOL_HISENSE_ARG
#define USE_WHIRLPOOL_HITACHI_HCRA31NEWH
#define USE_ZWAVE_PROXY
// Feature flags which do not work for zephyr
@@ -2,7 +2,3 @@ packages:
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
<<: !include common.yaml
# IPv6 with an IP sensor: compiles the IPv6 event handler's listener notification
network:
enable_ipv6: true
@@ -0,0 +1,14 @@
import esphome.codegen as cg
from esphome.types import ConfigType
from tests.testing_helpers import ComponentManifestOverride
def override_manifest(manifest: ComponentManifestOverride) -> None:
async def to_code_testing(config: ConfigType) -> None:
# Register and bit lists are sized by code generation; a gtest server holds at most three of each.
cg.add_define("MODBUS_SERVER_REGISTER_COUNT", 3)
cg.add_define("MODBUS_SERVER_BIT_COUNT", 3)
# A MULTI_CONF component gets no entry in the host build, so its to_code would never run.
manifest.multi_conf = False
manifest.to_code = to_code_testing
+1 -2
View File
@@ -10,8 +10,7 @@ def override_manifest(manifest: ComponentManifestOverride) -> None:
manifest.auto_load = ["climate_ir", "climate"]
async def to_code_testing(config: ConfigType) -> None:
# Compiles in the Hisense ARG and Hitachi models, which climate.py adds only when configured
# Compiles in the Hisense ARG model, which climate.py adds only when it is configured
cg.add_define("USE_WHIRLPOOL_HISENSE_ARG")
cg.add_define("USE_WHIRLPOOL_HITACHI_HCRA31NEWH")
manifest.to_code = to_code_testing
-5
View File
@@ -8,8 +8,3 @@ climate:
model: HISENSE_ARG
transmitter_id: xmitr
receiver_id: rcvr
- platform: whirlpool
name: Whirlpool Hitachi HCRA31NEWH Climate
model: HITACHI_HCRA31NEWH
transmitter_id: xmitr
receiver_id: rcvr
@@ -252,9 +252,6 @@ TEST(WhirlpoolHisenseArg, SwingIsSentOnEveryTransmit) {
TEST(WhirlpoolDG11J13A, SwingIsSentOnlyWhenItChanges) {
Fixture f(MODEL_DG11J1_3A);
f.command(climate::CLIMATE_MODE_COOL, 24, climate::CLIMATE_FAN_LOW, climate::CLIMATE_SWING_OFF);
EXPECT_EQ(f.last_frame()[2] & 0x80, 0) << "swing is already off";
f.command(climate::CLIMATE_MODE_COOL, 24, climate::CLIMATE_FAN_LOW, climate::CLIMATE_SWING_VERTICAL);
Frame frame = f.last_frame();
EXPECT_EQ(frame[2] & 0x80, 0x80);
@@ -264,9 +261,6 @@ TEST(WhirlpoolDG11J13A, SwingIsSentOnlyWhenItChanges) {
frame = f.last_frame();
EXPECT_EQ(frame[2] & 0x80, 0);
EXPECT_EQ(frame[8], 0x00);
f.command(climate::CLIMATE_MODE_COOL, 25, climate::CLIMATE_FAN_LOW, climate::CLIMATE_SWING_VERTICAL);
EXPECT_EQ(f.last_frame()[2] & 0x80, 0) << "swing is already vertical";
}
// Cool 24 C (step 8), fan low, powered on assumed
@@ -391,90 +385,4 @@ TEST(WhirlpoolHisenseArg, EveryTransmitHasValidChecksums) {
}
}
// Cool 24 C (step 8), fan low, unit on
static Frame hitachi_cool_frame() {
return with_checksums({0xA9, 0x59, 0x03, 0x82, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x20,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x38, 0x08, 0x00});
}
// The off frame: power bit, cool code, byte 15 0x01 and byte 18 0x28
static Frame hitachi_off_frame() {
return with_checksums({0xA9, 0x59, 0x07, 0x82, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x20,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x28, 0x08, 0x00});
}
TEST(WhirlpoolHitachiHCRA31NEWH, CoolAndOffMatchKnownFrames) {
Fixture f(MODEL_HITACHI_HCRA31NEWH);
f.command(climate::CLIMATE_MODE_COOL, 24, climate::CLIMATE_FAN_LOW);
// Turning on sends the full state with no power bit
expect_frame(f.last_frame(), hitachi_cool_frame());
f.command(climate::CLIMATE_MODE_OFF, 24, climate::CLIMATE_FAN_LOW);
expect_frame(f.last_frame(), hitachi_off_frame());
// A second off command still sends the power bit
f.command(climate::CLIMATE_MODE_OFF, 24, climate::CLIMATE_FAN_LOW);
EXPECT_EQ(f.last_frame()[2] & 0x04, 0x04);
f.command(climate::CLIMATE_MODE_HEAT, 24, climate::CLIMATE_FAN_LOW);
const Frame frame = f.last_frame();
EXPECT_EQ(frame[2] & 0x04, 0);
EXPECT_EQ(frame[3], 0x80);
EXPECT_EQ(frame[15], 0x06);
EXPECT_EQ(frame[18], 0x38);
}
TEST(WhirlpoolHitachiHCRA31NEWH, ReceivePowerIsAState) {
Fixture f(MODEL_HITACHI_HCRA31NEWH);
ASSERT_EQ(f.climate.mode, climate::CLIMATE_MODE_OFF);
// An on frame restores the mode even though it has no power bit
EXPECT_TRUE(receive(f.climate, hitachi_cool_frame()));
EXPECT_EQ(f.climate.mode, climate::CLIMATE_MODE_COOL);
EXPECT_FLOAT_EQ(f.climate.target_temperature, 24);
EXPECT_EQ(f.climate.fan_mode, climate::CLIMATE_FAN_LOW);
// The same frame again keeps it on
EXPECT_TRUE(receive(f.climate, hitachi_cool_frame()));
EXPECT_EQ(f.climate.mode, climate::CLIMATE_MODE_COOL);
EXPECT_TRUE(receive(f.climate, hitachi_off_frame()));
EXPECT_EQ(f.climate.mode, climate::CLIMATE_MODE_OFF);
// A repeated off frame does not toggle
EXPECT_TRUE(receive(f.climate, hitachi_off_frame()));
EXPECT_EQ(f.climate.mode, climate::CLIMATE_MODE_OFF);
}
TEST(WhirlpoolHitachiHCRA31NEWH, ReceiveChecksRemoteId) {
Fixture hitachi(MODEL_HITACHI_HCRA31NEWH);
EXPECT_FALSE(receive(hitachi.climate, hisense_cool_frame()));
Fixture whirlpool(MODEL_DG11J1_91);
EXPECT_FALSE(receive(whirlpool.climate, hitachi_cool_frame()));
}
TEST(WhirlpoolHitachiHCRA31NEWH, ReceivedTemperatureIsClamped) {
Fixture f(MODEL_HITACHI_HCRA31NEWH);
Frame frame = hitachi_cool_frame();
frame[3] = 0xF2; // step 15 would be 31 C
EXPECT_TRUE(receive(f.climate, with_checksums(frame)));
EXPECT_FLOAT_EQ(f.climate.target_temperature, 30);
}
TEST(WhirlpoolDG11J13A, TargetIsClampedAndRoundedToWhatWasSent) {
Fixture f(MODEL_DG11J1_3A);
f.command(climate::CLIMATE_MODE_COOL, 35, climate::CLIMATE_FAN_LOW);
EXPECT_FLOAT_EQ(f.climate.target_temperature, 32);
f.command(climate::CLIMATE_MODE_COOL, 24.4f, climate::CLIMATE_FAN_LOW);
EXPECT_FLOAT_EQ(f.climate.target_temperature, 24);
}
TEST(WhirlpoolHitachiHCRA31NEWH, ReceiveRejectsShortFrame) {
Fixture f(MODEL_HITACHI_HCRA31NEWH);
remote_base::RawTimings raw = waveform(hitachi_cool_frame());
// Cut after the second divider: 14 bytes, no footer
raw.resize(2 + 14 * 16 + 2 * 2);
remote_base::RemoteReceiveData data(raw, 25, remote_base::TOLERANCE_MODE_PERCENTAGE);
EXPECT_FALSE(static_cast<remote_base::RemoteReceiverListener &>(f.climate).on_receive(data));
EXPECT_EQ(f.climate.mode, climate::CLIMATE_MODE_OFF);
}
} // namespace esphome::whirlpool::testing
@@ -1,23 +0,0 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_NAME
from esphome.types import ConfigType
CODEOWNERS = ["@esphome/tests"]
MULTI_CONF = True
reboot_hook_recorder_ns = cg.esphome_ns.namespace("reboot_hook_recorder")
RebootHookRecorder = reboot_hook_recorder_ns.class_("RebootHookRecorder", cg.Component)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(RebootHookRecorder),
cv.Required(CONF_NAME): cv.string,
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
cg.add(var.set_name(config[CONF_NAME]))
@@ -1,10 +0,0 @@
#include "reboot_hook_recorder.h"
#include "esphome/core/log.h"
namespace esphome::reboot_hook_recorder {
ESPHOME_LOG_TAG(TAG, "reboot_hook_recorder");
void RebootHookRecorder::record_(const char *hook) { ESP_LOGI(TAG, "HOOK %s %s", this->name_, hook); }
} // namespace esphome::reboot_hook_recorder
@@ -1,33 +0,0 @@
#pragma once
#include "esphome/core/component.h"
namespace esphome::reboot_hook_recorder {
/// Logs every shutdown lifecycle hook it receives, so a test can check their order.
class RebootHookRecorder : public Component {
public:
void set_name(const char *name) { this->name_ = name; }
void on_safe_shutdown() override { this->record_("on_safe_shutdown"); }
void on_shutdown() override { this->record_("on_shutdown"); }
// Needs a second call to finish, like a component waiting for a connection to close
bool teardown() override {
if (++this->teardown_calls_ < 2) {
this->record_("teardown_pending");
return false;
}
if (this->teardown_calls_ == 2)
this->record_("teardown_done");
return true;
}
void on_powerdown() override { this->record_("on_powerdown"); }
protected:
void record_(const char *hook);
const char *name_{""};
uint8_t teardown_calls_{0};
};
} // namespace esphome::reboot_hook_recorder
@@ -1,19 +0,0 @@
esphome:
name: reboot-hooks-forced
host:
logger:
level: DEBUG
api:
# Reboot once the test has connected, so that it sees every hook
on_client_connected:
- delay: 100ms
- lambda: App.reboot();
external_components:
- source:
type: local
path: EXTERNAL_COMPONENT_PATH
reboot_hook_recorder:
- name: first
- name: second
@@ -1,19 +0,0 @@
esphome:
name: reboot-hooks-safe
host:
logger:
level: DEBUG
api:
# Reboot once the test has connected, so that it sees every hook
on_client_connected:
- delay: 100ms
- lambda: App.safe_reboot();
external_components:
- source:
type: local
path: EXTERNAL_COMPONENT_PATH
reboot_hook_recorder:
- name: first
- name: second
@@ -1,80 +0,0 @@
"""Test the order of the shutdown lifecycle hooks on a forced and on a safe reboot."""
import re
import pytest
from .log_utils import LineWaiter
from .types import APIClientConnectedFactory, RunCompiledFunction
from .wait_utils import wait_or_fail
HOOK_PATTERN = re.compile(r"HOOK (\w+) (\w+)")
# Hooks run in reverse component order, so "first" gets each hook last.
LAST_HOOK = "HOOK first on_powerdown"
async def _record_reboot_hooks(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> list[tuple[str, str]]:
"""Connect, which makes the device reboot, and return its (component, hook) calls in order."""
waiter = LineWaiter()
hooks: list[tuple[str, str]] = []
def on_line(line: str) -> None:
if match := HOOK_PATTERN.search(line):
hooks.append((match[1], match[2]))
waiter.callback(line)
async with (
run_compiled(yaml_config, line_callback=on_line),
api_client_connected(),
):
await wait_or_fail(
waiter.wait_for(LAST_HOOK),
5.0,
"Device did not run the powerdown hooks",
hooks=hooks,
)
return hooks
@pytest.mark.asyncio
async def test_reboot_hook_order_forced(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""A forced reboot runs on_shutdown then on_powerdown, with no safe shutdown or teardown."""
hooks = await _record_reboot_hooks(yaml_config, run_compiled, api_client_connected)
assert hooks == [
("second", "on_shutdown"),
("first", "on_shutdown"),
("second", "on_powerdown"),
("first", "on_powerdown"),
]
@pytest.mark.asyncio
async def test_reboot_hook_order_safe(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""A safe reboot powers down only after every component has finished its teardown."""
hooks = await _record_reboot_hooks(yaml_config, run_compiled, api_client_connected)
assert hooks == [
("second", "on_safe_shutdown"),
("first", "on_safe_shutdown"),
("second", "on_shutdown"),
("first", "on_shutdown"),
("second", "teardown_pending"),
("first", "teardown_pending"),
("second", "teardown_done"),
("first", "teardown_done"),
("second", "on_powerdown"),
("first", "on_powerdown"),
]