Compare commits

...

66 Commits

Author SHA1 Message Date
J. Nick Koston
f42f222e82 Merge branch 'dev' into scanf_bloat 2026-02-03 03:14:23 +01:00
J. Nick Koston
ccf5c1f7e9 [esp32] Exclude additional unused IDF components (driver, dac, mcpwm, twai, openthread, ulp) (#13664) 2026-02-03 03:12:12 +01:00
dependabot[bot]
efecea9450 Bump github/codeql-action from 4.32.0 to 4.32.1 (#13726)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-03 02:27:34 +01:00
J. Nick Koston
26e4cda610 [logger] Use vsnprintf_P directly for ESP8266 flash format strings (#13716) 2026-02-03 02:25:54 +01:00
Jan Kundrát
a6543d32bd [sx126x] fix maximal payload_length (#13723) 2026-02-02 20:15:18 -05:00
Jonathan Swoboda
da947d060f [wizard] Use API encryption key instead of deprecated password (#13634)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 19:20:24 -05:00
J. Nick Koston
1119003eb5 [core] Add missing uint32_t ID overloads for defer() and cancel_defer() (#13720) 2026-02-02 22:22:11 +01:00
J. Nick Koston
c089d9aeac [esp32_hosted] Replace sscanf with strtol for version parsing (#13658) 2026-02-02 22:21:52 +01:00
J. Nick Koston
4f0894e970 [analyze-memory] Add top 30 largest symbols to report (#13673) 2026-02-02 22:05:39 +01:00
J. Nick Koston
848c237159 [time] Use lazy callback for time sync to save 8 bytes (#13652) 2026-02-02 22:05:27 +01:00
J. Nick Koston
6892805094 [api] Align water_heater_command with standard entity command pattern (#13655) 2026-02-02 22:00:46 +01:00
Roger Fachini
aa8ccfc32b [ethernet] Add on_connect and on_disconnect triggers (#13677)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-02-02 17:00:11 +01:00
rwrozelle
18991686ab [mqtt] resolve warnings related to use of ip.str() (#13719) 2026-02-02 16:48:08 +01:00
J. Nick Koston
62f34bea83 [template.output] Avoid heap allocation for triggers (#13709) 2026-02-02 07:36:27 +01:00
J. Nick Koston
6114005952 [template.water_heater] Avoid heap allocation for trigger (#13712) 2026-02-02 07:36:08 +01:00
J. Nick Koston
c0e5ae4298 [template.text] Avoid heap allocation for trigger (#13711) 2026-02-02 07:35:21 +01:00
J. Nick Koston
420de987bc [micro_wake_word] Avoid heap allocation for trigger (#13714) 2026-02-02 07:35:03 +01:00
J. Nick Koston
61e33217cd [cc1101] Avoid heap allocation for trigger (#13715) 2026-02-02 07:34:50 +01:00
J. Nick Koston
b5b9a89561 [light] Avoid heap allocation for AutomationLightEffect trigger (#13713) 2026-02-02 07:34:34 +01:00
J. Nick Koston
bc9fc66225 [template.datetime] Avoid heap allocation for triggers (#13710) 2026-02-02 04:30:46 +00:00
J. Nick Koston
6727fe9040 [remote_transmitter] Avoid heap allocation for triggers (#13708) 2026-02-02 04:18:17 +00:00
J. Nick Koston
56110d4495 [time_based] Avoid heap allocation for cover triggers (#13703) 2026-02-02 05:15:50 +01:00
J. Nick Koston
1362ff6cba [speaker.media_player] Avoid heap allocation for triggers (#13707) 2026-02-02 05:15:33 +01:00
J. Nick Koston
dbd7401721 [feedback] Avoid heap allocation for cover triggers (#13693) 2026-02-02 05:15:13 +01:00
J. Nick Koston
f0801ecac0 [template.lock] Avoid heap allocation for triggers (#13704) 2026-02-02 05:14:11 +01:00
J. Nick Koston
379652f631 [thermostat] Remove dead null checks for triggers (#13706) 2026-02-02 04:10:08 +00:00
J. Nick Koston
18c152723c [sprinkler] Avoid heap allocation for triggers (#13705) 2026-02-02 04:53:46 +01:00
J. Nick Koston
09b76d5e4a [voice_assistant] Avoid heap allocation for triggers (#13689) 2026-02-02 04:50:16 +01:00
J. Nick Koston
8791c24072 [api] Avoid heap allocation for client connected/disconnected triggers (#13688) 2026-02-02 04:50:01 +01:00
J. Nick Koston
652c02b9ab [bang_bang] Avoid heap allocation for climate triggers (#13701) 2026-02-02 04:49:46 +01:00
J. Nick Koston
4ab552d750 [http_request] Avoid heap allocation for triggers (#13690) 2026-02-02 04:47:49 +01:00
J. Nick Koston
e420964b93 [template.switch] Avoid heap allocation for triggers (#13691) 2026-02-02 04:47:34 +01:00
J. Nick Koston
7d717a78dc [template] Avoid heap allocation for number set trigger (#13694) 2026-02-02 04:47:21 +01:00
J. Nick Koston
2f0abd5c3f [template] Avoid heap allocation for cover triggers (#13696) 2026-02-02 04:46:55 +01:00
J. Nick Koston
d49d8095df [template] Avoid heap allocation for valve triggers (#13697) 2026-02-02 04:46:41 +01:00
J. Nick Koston
8a8c1290db [endstop] Avoid heap allocation for cover triggers (#13702) 2026-02-02 04:45:01 +01:00
J. Nick Koston
01ffeba2c2 [api] Avoid heap allocation for homeassistant action triggers (#13695) 2026-02-02 04:44:08 +01:00
J. Nick Koston
78ed898f0b [current_based] Avoid heap allocation for cover triggers (#13700) 2026-02-02 04:43:52 +01:00
J. Nick Koston
75ee9a718a [sx126x] Avoid heap allocation for packet trigger (#13699) 2026-02-02 04:43:30 +01:00
J. Nick Koston
bfeb447178 [sx127x] Avoid heap allocation for packet trigger (#13698) 2026-02-02 04:43:16 +01:00
J. Nick Koston
29f8d70b35 [thermostat] Avoid heap allocation for triggers (#13692) 2026-02-02 04:41:08 +01:00
Simon Fischer
1ff2f3b6a3 [dlms_meter] Add dlms smart meter component (#8009)
Co-authored-by: Thomas Rupprecht <rupprecht.thomas@gmail.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 10:48:27 -05:00
Jonathan Swoboda
891382a32e [max7219] Allocate buffer in constructor (#13660)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 09:59:13 -05:00
J. Nick Koston
0fd50b2381 [esp32] Disable unused per-tag log filtering, saving ~536 bytes RAM (#13662) 2026-01-31 01:21:52 -06:00
J. Nick Koston
e68b302bba [ci] Block new scanf() usage to prevent ~9.8KB flash bloat 2026-01-30 20:21:43 -06:00
J. Nick Koston
3e11a9d8a5 [ci] Block new scanf() usage to prevent ~9.8KB flash bloat 2026-01-30 20:20:24 -06:00
Clyde Stubbs
9dcb469460 [core] Simplify generation of Lambda during to_code() (#13533) 2026-01-31 12:18:30 +11:00
J0k3r2k1
5e3561d60b [mipi_spi] Fix log_pin() FlashStringHelper compatibility (#13624)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-01-30 14:33:45 -06:00
Thomas Rupprecht
ca9ed369f9 [pmsx003] support device-types PMS1003, PMS3003, PMS9003M (#13640) 2026-01-30 14:59:47 -05:00
J. Nick Koston
4e96b20b46 [mqtt] Restore ESP8266 on_message defer to prevent stack overflow (#13648) 2026-01-30 12:49:14 -06:00
J. Nick Koston
a1a60c44da [web_server_base] Update ESPAsyncWebServer to 3.9.6 (#13639) 2026-01-30 12:48:34 -06:00
Shivam Maurya
898c8a5836 [core] ESP32 chip revision text (#13647) 2026-01-30 11:01:00 -05:00
Thomas Rupprecht
20edd11ca7 [pmsx003] Improvements (#13626) 2026-01-29 22:48:16 -05:00
J. Nick Koston
9a8c71a58b [logger] Fix USB Serial JTAG VFS linker errors when using UART on IDF (#13628)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-29 21:31:01 -06:00
Jonathan Swoboda
1a7435250e Merge branch 'release' into dev 2026-01-29 22:22:23 -05:00
Jonathan Swoboda
3c91d72403 Merge pull request #13632 from esphome/bump-2026.1.3
2026.1.3
2026-01-29 22:22:10 -05:00
Jonathan Swoboda
0a63fc6f05 Bump version to 2026.1.3 2026-01-29 21:11:09 -05:00
J. Nick Koston
50e739ee8e [http_request] Fix empty body for chunked transfer encoding responses (#13599) 2026-01-29 21:11:09 -05:00
J. Nick Koston
6c84f20491 [wifi] Fix ESP8266 yield panic when WiFi scan fails (#13603) 2026-01-29 21:11:09 -05:00
Cody Cutrer
a68506f924 [ld2450] preserve precision of angle (#13600) 2026-01-29 21:11:08 -05:00
esphomebot
a20d42ca0b Update webserver local assets to 20260127-190637 (#13573)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-01-29 21:11:08 -05:00
J. Nick Koston
4ec8846198 [web_server] Add name_id to SSE for entity ID format migration (#13535) 2026-01-29 21:11:08 -05:00
J. Nick Koston
40ea65b1c0 [socket] ESP8266: call delay(0) instead of esp_delay(0, cb) for zero timeout (#13530) 2026-01-29 21:11:08 -05:00
J. Nick Koston
f7937ef952 [ota] Improve error message when device closes connection without responding (#13562) 2026-01-29 21:11:08 -05:00
sebcaps
d6bf137026 [mhz19] Fix Uninitialized var warning message (#13526) 2026-01-29 21:11:08 -05:00
esphomebot
ed9a672f44 Update webserver local assets to 20260122-204614 (#13455)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-01-29 21:11:08 -05:00
137 changed files with 2701 additions and 894 deletions

View File

@@ -1 +1 @@
cf3d341206b4184ec8b7fe85141aef4fe4696aa720c3f8a06d4e57930574bdab 069fa9526c52f7c580a9ec17c7678d12f142221387e9b561c18f95394d4629a3

View File

@@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0 uses: github/codeql-action/init@6bc82e05fd0ea64601dd4b465378bbcf57de0314 # v4.32.1
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }} build-mode: ${{ matrix.build-mode }}
@@ -86,6 +86,6 @@ jobs:
exit 1 exit 1
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0 uses: github/codeql-action/analyze@6bc82e05fd0ea64601dd4b465378bbcf57de0314 # v4.32.1
with: with:
category: "/language:${{matrix.language}}" category: "/language:${{matrix.language}}"

View File

@@ -134,6 +134,7 @@ esphome/components/dfplayer/* @glmnet
esphome/components/dfrobot_sen0395/* @niklasweber esphome/components/dfrobot_sen0395/* @niklasweber
esphome/components/dht/* @OttoWinter esphome/components/dht/* @OttoWinter
esphome/components/display_menu_base/* @numo68 esphome/components/display_menu_base/* @numo68
esphome/components/dlms_meter/* @SimonFischer04
esphome/components/dps310/* @kbx81 esphome/components/dps310/* @kbx81
esphome/components/ds1307/* @badbadc0ffee esphome/components/ds1307/* @badbadc0ffee
esphome/components/ds2484/* @mrk-its esphome/components/ds2484/* @mrk-its

View File

@@ -4,6 +4,8 @@ from __future__ import annotations
from collections import defaultdict from collections import defaultdict
from collections.abc import Callable from collections.abc import Callable
import heapq
from operator import itemgetter
import sys import sys
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@@ -29,6 +31,10 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
) )
# Lower threshold for RAM symbols (RAM is more constrained) # Lower threshold for RAM symbols (RAM is more constrained)
RAM_SYMBOL_SIZE_THRESHOLD: int = 24 RAM_SYMBOL_SIZE_THRESHOLD: int = 24
# Number of top symbols to show in the largest symbols report
TOP_SYMBOLS_LIMIT: int = 30
# Width for symbol name display in top symbols report
COL_TOP_SYMBOL_NAME: int = 55
# Column width constants # Column width constants
COL_COMPONENT: int = 29 COL_COMPONENT: int = 29
@@ -147,6 +153,37 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
section_label = f" [{section[1:]}]" # .data -> [data], .bss -> [bss] section_label = f" [{section[1:]}]" # .data -> [data], .bss -> [bss]
return f"{demangled} ({size:,} B){section_label}" return f"{demangled} ({size:,} B){section_label}"
def _add_top_symbols(self, lines: list[str]) -> None:
"""Add a section showing the top largest symbols in the binary."""
# Collect all symbols from all components: (symbol, demangled, size, section, component)
all_symbols = [
(symbol, demangled, size, section, component)
for component, symbols in self._component_symbols.items()
for symbol, demangled, size, section in symbols
]
# Get top N symbols by size using heapq for efficiency
top_symbols = heapq.nlargest(
self.TOP_SYMBOLS_LIMIT, all_symbols, key=itemgetter(2)
)
lines.append("")
lines.append(f"Top {self.TOP_SYMBOLS_LIMIT} Largest Symbols:")
# Calculate truncation limit from column width (leaving room for "...")
truncate_limit = self.COL_TOP_SYMBOL_NAME - 3
for i, (_, demangled, size, section, component) in enumerate(top_symbols):
# Format section label
section_label = f"[{section[1:]}]" if section else ""
# Truncate demangled name if too long
demangled_display = (
f"{demangled[:truncate_limit]}..."
if len(demangled) > self.COL_TOP_SYMBOL_NAME
else demangled
)
lines.append(
f"{i + 1:>2}. {size:>7,} B {section_label:<8} {demangled_display:<{self.COL_TOP_SYMBOL_NAME}} {component}"
)
def generate_report(self, detailed: bool = False) -> str: def generate_report(self, detailed: bool = False) -> str:
"""Generate a formatted memory report.""" """Generate a formatted memory report."""
components = sorted( components = sorted(
@@ -248,6 +285,9 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
"RAM", "RAM",
) )
# Top largest symbols in the binary
self._add_top_symbols(lines)
# Add ESPHome core detailed analysis if there are core symbols # Add ESPHome core detailed analysis if there are core symbols
if self._esphome_core_symbols: if self._esphome_core_symbols:
self._add_section_header(lines, f"{_COMPONENT_CORE} Detailed Analysis") self._add_section_header(lines, f"{_COMPONENT_CORE} Detailed Analysis")

View File

@@ -45,6 +45,7 @@ service APIConnection {
rpc time_command (TimeCommandRequest) returns (void) {} rpc time_command (TimeCommandRequest) returns (void) {}
rpc update_command (UpdateCommandRequest) returns (void) {} rpc update_command (UpdateCommandRequest) returns (void) {}
rpc valve_command (ValveCommandRequest) returns (void) {} rpc valve_command (ValveCommandRequest) returns (void) {}
rpc water_heater_command (WaterHeaterCommandRequest) returns (void) {}
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {} rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {} rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}

View File

@@ -1385,7 +1385,7 @@ uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnec
is_single); is_single);
} }
void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) { void APIConnection::water_heater_command(const WaterHeaterCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(water_heater::WaterHeater, water_heater, water_heater) ENTITY_COMMAND_MAKE_CALL(water_heater::WaterHeater, water_heater, water_heater)
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_MODE) if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_MODE)
call.set_mode(static_cast<water_heater::WaterHeaterMode>(msg.mode)); call.set_mode(static_cast<water_heater::WaterHeaterMode>(msg.mode));

View File

@@ -170,7 +170,7 @@ class APIConnection final : public APIServerConnection {
#ifdef USE_WATER_HEATER #ifdef USE_WATER_HEATER
bool send_water_heater_state(water_heater::WaterHeater *water_heater); bool send_water_heater_state(water_heater::WaterHeater *water_heater);
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override; void water_heater_command(const WaterHeaterCommandRequest &msg) override;
#endif #endif
#ifdef USE_IR_RF #ifdef USE_IR_RF

View File

@@ -746,6 +746,11 @@ void APIServerConnection::on_update_command_request(const UpdateCommandRequest &
#ifdef USE_VALVE #ifdef USE_VALVE
void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { this->valve_command(msg); } void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { this->valve_command(msg); }
#endif #endif
#ifdef USE_WATER_HEATER
void APIServerConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) {
this->water_heater_command(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
const SubscribeBluetoothLEAdvertisementsRequest &msg) { const SubscribeBluetoothLEAdvertisementsRequest &msg) {

View File

@@ -303,6 +303,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_VALVE #ifdef USE_VALVE
virtual void valve_command(const ValveCommandRequest &msg) = 0; virtual void valve_command(const ValveCommandRequest &msg) = 0;
#endif #endif
#ifdef USE_WATER_HEATER
virtual void water_heater_command(const WaterHeaterCommandRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
#endif #endif
@@ -432,6 +435,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_VALVE #ifdef USE_VALVE
void on_valve_command_request(const ValveCommandRequest &msg) override; void on_valve_command_request(const ValveCommandRequest &msg) override;
#endif #endif
#ifdef USE_WATER_HEATER
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
#endif #endif

View File

@@ -211,7 +211,7 @@ void APIServer::loop() {
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER #ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
// Fire trigger after client is removed so api.connected reflects the true state // Fire trigger after client is removed so api.connected reflects the true state
this->client_disconnected_trigger_->trigger(client_name, client_peername); this->client_disconnected_trigger_.trigger(client_name, client_peername);
#endif #endif
// Don't increment client_index since we need to process the swapped element // Don't increment client_index since we need to process the swapped element
} }

View File

@@ -227,12 +227,10 @@ class APIServer : public Component,
#endif #endif
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER #ifdef USE_API_CLIENT_CONNECTED_TRIGGER
Trigger<std::string, std::string> *get_client_connected_trigger() const { return this->client_connected_trigger_; } Trigger<std::string, std::string> *get_client_connected_trigger() { return &this->client_connected_trigger_; }
#endif #endif
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER #ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
Trigger<std::string, std::string> *get_client_disconnected_trigger() const { Trigger<std::string, std::string> *get_client_disconnected_trigger() { return &this->client_disconnected_trigger_; }
return this->client_disconnected_trigger_;
}
#endif #endif
protected: protected:
@@ -253,10 +251,10 @@ class APIServer : public Component,
// Pointers and pointer-like types first (4 bytes each) // Pointers and pointer-like types first (4 bytes each)
std::unique_ptr<socket::Socket> socket_ = nullptr; std::unique_ptr<socket::Socket> socket_ = nullptr;
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER #ifdef USE_API_CLIENT_CONNECTED_TRIGGER
Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>(); Trigger<std::string, std::string> client_connected_trigger_;
#endif #endif
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER #ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
Trigger<std::string, std::string> *client_disconnected_trigger_ = new Trigger<std::string, std::string>(); Trigger<std::string, std::string> client_disconnected_trigger_;
#endif #endif
// 4-byte aligned types // 4-byte aligned types

View File

@@ -136,12 +136,10 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
void set_wants_response() { this->flags_.wants_response = true; } void set_wants_response() { this->flags_.wants_response = true; }
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
Trigger<JsonObjectConst, Ts...> *get_success_trigger_with_response() const { Trigger<JsonObjectConst, Ts...> *get_success_trigger_with_response() { return &this->success_trigger_with_response_; }
return this->success_trigger_with_response_;
}
#endif #endif
Trigger<Ts...> *get_success_trigger() const { return this->success_trigger_; } Trigger<Ts...> *get_success_trigger() { return &this->success_trigger_; }
Trigger<std::string, Ts...> *get_error_trigger() const { return this->error_trigger_; } Trigger<std::string, Ts...> *get_error_trigger() { return &this->error_trigger_; }
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
void play(const Ts &...x) override { void play(const Ts &...x) override {
@@ -187,14 +185,14 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
if (response.is_success()) { if (response.is_success()) {
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
if (this->flags_.wants_response) { if (this->flags_.wants_response) {
this->success_trigger_with_response_->trigger(response.get_json(), args...); this->success_trigger_with_response_.trigger(response.get_json(), args...);
} else } else
#endif #endif
{ {
this->success_trigger_->trigger(args...); this->success_trigger_.trigger(args...);
} }
} else { } else {
this->error_trigger_->trigger(response.get_error_message(), args...); this->error_trigger_.trigger(response.get_error_message(), args...);
} }
}, },
captured_args); captured_args);
@@ -251,10 +249,10 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
TemplatableStringValue<Ts...> response_template_{""}; TemplatableStringValue<Ts...> response_template_{""};
Trigger<JsonObjectConst, Ts...> *success_trigger_with_response_ = new Trigger<JsonObjectConst, Ts...>(); Trigger<JsonObjectConst, Ts...> success_trigger_with_response_;
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
Trigger<Ts...> *success_trigger_ = new Trigger<Ts...>(); Trigger<Ts...> success_trigger_;
Trigger<std::string, Ts...> *error_trigger_ = new Trigger<std::string, Ts...>(); Trigger<std::string, Ts...> error_trigger_;
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
struct Flags { struct Flags {

View File

@@ -6,8 +6,7 @@ namespace bang_bang {
static const char *const TAG = "bang_bang.climate"; static const char *const TAG = "bang_bang.climate";
BangBangClimate::BangBangClimate() BangBangClimate::BangBangClimate() = default;
: idle_trigger_(new Trigger<>()), cool_trigger_(new Trigger<>()), heat_trigger_(new Trigger<>()) {}
void BangBangClimate::setup() { void BangBangClimate::setup() {
this->sensor_->add_on_state_callback([this](float state) { this->sensor_->add_on_state_callback([this](float state) {
@@ -160,13 +159,13 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
switch (action) { switch (action) {
case climate::CLIMATE_ACTION_OFF: case climate::CLIMATE_ACTION_OFF:
case climate::CLIMATE_ACTION_IDLE: case climate::CLIMATE_ACTION_IDLE:
trig = this->idle_trigger_; trig = &this->idle_trigger_;
break; break;
case climate::CLIMATE_ACTION_COOLING: case climate::CLIMATE_ACTION_COOLING:
trig = this->cool_trigger_; trig = &this->cool_trigger_;
break; break;
case climate::CLIMATE_ACTION_HEATING: case climate::CLIMATE_ACTION_HEATING:
trig = this->heat_trigger_; trig = &this->heat_trigger_;
break; break;
default: default:
trig = nullptr; trig = nullptr;
@@ -204,9 +203,9 @@ void BangBangClimate::set_away_config(const BangBangClimateTargetTempConfig &awa
void BangBangClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } void BangBangClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
void BangBangClimate::set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; } void BangBangClimate::set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
Trigger<> *BangBangClimate::get_idle_trigger() const { return this->idle_trigger_; } Trigger<> *BangBangClimate::get_idle_trigger() { return &this->idle_trigger_; }
Trigger<> *BangBangClimate::get_cool_trigger() const { return this->cool_trigger_; } Trigger<> *BangBangClimate::get_cool_trigger() { return &this->cool_trigger_; }
Trigger<> *BangBangClimate::get_heat_trigger() const { return this->heat_trigger_; } Trigger<> *BangBangClimate::get_heat_trigger() { return &this->heat_trigger_; }
void BangBangClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } void BangBangClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
void BangBangClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } void BangBangClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }

View File

@@ -30,9 +30,9 @@ class BangBangClimate : public climate::Climate, public Component {
void set_normal_config(const BangBangClimateTargetTempConfig &normal_config); void set_normal_config(const BangBangClimateTargetTempConfig &normal_config);
void set_away_config(const BangBangClimateTargetTempConfig &away_config); void set_away_config(const BangBangClimateTargetTempConfig &away_config);
Trigger<> *get_idle_trigger() const; Trigger<> *get_idle_trigger();
Trigger<> *get_cool_trigger() const; Trigger<> *get_cool_trigger();
Trigger<> *get_heat_trigger() const; Trigger<> *get_heat_trigger();
protected: protected:
/// Override control to change settings of the climate device. /// Override control to change settings of the climate device.
@@ -57,17 +57,13 @@ class BangBangClimate : public climate::Climate, public Component {
* *
* In idle mode, the controller is assumed to have both heating and cooling disabled. * In idle mode, the controller is assumed to have both heating and cooling disabled.
*/ */
Trigger<> *idle_trigger_{nullptr}; Trigger<> idle_trigger_;
/** The trigger to call when the controller should switch to cooling mode. /** The trigger to call when the controller should switch to cooling mode.
*/ */
Trigger<> *cool_trigger_{nullptr}; Trigger<> cool_trigger_;
/** The trigger to call when the controller should switch to heating mode. /** The trigger to call when the controller should switch to heating mode.
*
* A null value for this attribute means that the controller has no heating action
* For example window blinds, where only cooling (blinds closed) and not-cooling
* (blinds open) is possible.
*/ */
Trigger<> *heat_trigger_{nullptr}; Trigger<> heat_trigger_;
/** A reference to the trigger that was previously active. /** A reference to the trigger that was previously active.
* *
* This is so that the previous trigger can be stopped before enabling a new one. * This is so that the previous trigger can be stopped before enabling a new one.

View File

@@ -156,7 +156,7 @@ void CC1101Component::call_listeners_(const std::vector<uint8_t> &packet, float
for (auto &listener : this->listeners_) { for (auto &listener : this->listeners_) {
listener->on_packet(packet, freq_offset, rssi, lqi); listener->on_packet(packet, freq_offset, rssi, lqi);
} }
this->packet_trigger_->trigger(packet, freq_offset, rssi, lqi); this->packet_trigger_.trigger(packet, freq_offset, rssi, lqi);
} }
void CC1101Component::loop() { void CC1101Component::loop() {

View File

@@ -79,7 +79,7 @@ class CC1101Component : public Component,
// Packet mode operations // Packet mode operations
CC1101Error transmit_packet(const std::vector<uint8_t> &packet); CC1101Error transmit_packet(const std::vector<uint8_t> &packet);
void register_listener(CC1101Listener *listener) { this->listeners_.push_back(listener); } void register_listener(CC1101Listener *listener) { this->listeners_.push_back(listener); }
Trigger<std::vector<uint8_t>, float, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; } Trigger<std::vector<uint8_t>, float, float, uint8_t> *get_packet_trigger() { return &this->packet_trigger_; }
protected: protected:
uint16_t chip_id_{0}; uint16_t chip_id_{0};
@@ -96,8 +96,7 @@ class CC1101Component : public Component,
// Packet handling // Packet handling
void call_listeners_(const std::vector<uint8_t> &packet, float freq_offset, float rssi, uint8_t lqi); void call_listeners_(const std::vector<uint8_t> &packet, float freq_offset, float rssi, uint8_t lqi);
Trigger<std::vector<uint8_t>, float, float, uint8_t> *packet_trigger_{ Trigger<std::vector<uint8_t>, float, float, uint8_t> packet_trigger_;
new Trigger<std::vector<uint8_t>, float, float, uint8_t>()};
std::vector<uint8_t> packet_; std::vector<uint8_t> packet_;
std::vector<CC1101Listener *> listeners_; std::vector<CC1101Listener *> listeners_;

View File

@@ -66,7 +66,7 @@ void CurrentBasedCover::loop() {
if (this->current_operation == COVER_OPERATION_OPENING) { if (this->current_operation == COVER_OPERATION_OPENING) {
if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction
this->direction_idle_(); this->direction_idle_();
this->malfunction_trigger_->trigger(); this->malfunction_trigger_.trigger();
ESP_LOGI(TAG, "'%s' - Malfunction detected during opening. Current flow detected in close circuit", ESP_LOGI(TAG, "'%s' - Malfunction detected during opening. Current flow detected in close circuit",
this->name_.c_str()); this->name_.c_str());
} else if (this->is_opening_blocked_()) { // Blocked } else if (this->is_opening_blocked_()) { // Blocked
@@ -87,7 +87,7 @@ void CurrentBasedCover::loop() {
} else if (this->current_operation == COVER_OPERATION_CLOSING) { } else if (this->current_operation == COVER_OPERATION_CLOSING) {
if (this->malfunction_detection_ && this->is_opening_()) { // Malfunction if (this->malfunction_detection_ && this->is_opening_()) { // Malfunction
this->direction_idle_(); this->direction_idle_();
this->malfunction_trigger_->trigger(); this->malfunction_trigger_.trigger();
ESP_LOGI(TAG, "'%s' - Malfunction detected during closing. Current flow detected in open circuit", ESP_LOGI(TAG, "'%s' - Malfunction detected during closing. Current flow detected in open circuit",
this->name_.c_str()); this->name_.c_str());
} else if (this->is_closing_blocked_()) { // Blocked } else if (this->is_closing_blocked_()) { // Blocked
@@ -221,15 +221,15 @@ void CurrentBasedCover::start_direction_(CoverOperation dir) {
Trigger<> *trig; Trigger<> *trig;
switch (dir) { switch (dir) {
case COVER_OPERATION_IDLE: case COVER_OPERATION_IDLE:
trig = this->stop_trigger_; trig = &this->stop_trigger_;
break; break;
case COVER_OPERATION_OPENING: case COVER_OPERATION_OPENING:
this->last_operation_ = dir; this->last_operation_ = dir;
trig = this->open_trigger_; trig = &this->open_trigger_;
break; break;
case COVER_OPERATION_CLOSING: case COVER_OPERATION_CLOSING:
this->last_operation_ = dir; this->last_operation_ = dir;
trig = this->close_trigger_; trig = &this->close_trigger_;
break; break;
default: default:
return; return;

View File

@@ -16,9 +16,9 @@ class CurrentBasedCover : public cover::Cover, public Component {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
Trigger<> *get_stop_trigger() const { return this->stop_trigger_; } Trigger<> *get_stop_trigger() { return &this->stop_trigger_; }
Trigger<> *get_open_trigger() const { return this->open_trigger_; } Trigger<> *get_open_trigger() { return &this->open_trigger_; }
void set_open_sensor(sensor::Sensor *open_sensor) { this->open_sensor_ = open_sensor; } void set_open_sensor(sensor::Sensor *open_sensor) { this->open_sensor_ = open_sensor; }
void set_open_moving_current_threshold(float open_moving_current_threshold) { void set_open_moving_current_threshold(float open_moving_current_threshold) {
this->open_moving_current_threshold_ = open_moving_current_threshold; this->open_moving_current_threshold_ = open_moving_current_threshold;
@@ -28,7 +28,7 @@ class CurrentBasedCover : public cover::Cover, public Component {
} }
void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; } void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; }
Trigger<> *get_close_trigger() const { return this->close_trigger_; } Trigger<> *get_close_trigger() { return &this->close_trigger_; }
void set_close_sensor(sensor::Sensor *close_sensor) { this->close_sensor_ = close_sensor; } void set_close_sensor(sensor::Sensor *close_sensor) { this->close_sensor_ = close_sensor; }
void set_close_moving_current_threshold(float close_moving_current_threshold) { void set_close_moving_current_threshold(float close_moving_current_threshold) {
this->close_moving_current_threshold_ = close_moving_current_threshold; this->close_moving_current_threshold_ = close_moving_current_threshold;
@@ -44,7 +44,7 @@ class CurrentBasedCover : public cover::Cover, public Component {
void set_malfunction_detection(bool malfunction_detection) { this->malfunction_detection_ = malfunction_detection; } void set_malfunction_detection(bool malfunction_detection) { this->malfunction_detection_ = malfunction_detection; }
void set_start_sensing_delay(uint32_t start_sensing_delay) { this->start_sensing_delay_ = start_sensing_delay; } void set_start_sensing_delay(uint32_t start_sensing_delay) { this->start_sensing_delay_ = start_sensing_delay; }
Trigger<> *get_malfunction_trigger() const { return this->malfunction_trigger_; } Trigger<> *get_malfunction_trigger() { return &this->malfunction_trigger_; }
cover::CoverTraits get_traits() override; cover::CoverTraits get_traits() override;
@@ -64,23 +64,23 @@ class CurrentBasedCover : public cover::Cover, public Component {
void recompute_position_(); void recompute_position_();
Trigger<> *stop_trigger_{new Trigger<>()}; Trigger<> stop_trigger_;
sensor::Sensor *open_sensor_{nullptr}; sensor::Sensor *open_sensor_{nullptr};
Trigger<> *open_trigger_{new Trigger<>()}; Trigger<> open_trigger_;
float open_moving_current_threshold_; float open_moving_current_threshold_;
float open_obstacle_current_threshold_{FLT_MAX}; float open_obstacle_current_threshold_{FLT_MAX};
uint32_t open_duration_; uint32_t open_duration_;
sensor::Sensor *close_sensor_{nullptr}; sensor::Sensor *close_sensor_{nullptr};
Trigger<> *close_trigger_{new Trigger<>()}; Trigger<> close_trigger_;
float close_moving_current_threshold_; float close_moving_current_threshold_;
float close_obstacle_current_threshold_{FLT_MAX}; float close_obstacle_current_threshold_{FLT_MAX};
uint32_t close_duration_; uint32_t close_duration_;
uint32_t max_duration_{UINT32_MAX}; uint32_t max_duration_{UINT32_MAX};
bool malfunction_detection_{true}; bool malfunction_detection_{true};
Trigger<> *malfunction_trigger_{new Trigger<>()}; Trigger<> malfunction_trigger_;
uint32_t start_sensing_delay_; uint32_t start_sensing_delay_;
float obstacle_rollback_; float obstacle_rollback_;

View File

@@ -0,0 +1,57 @@
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266
CODEOWNERS = ["@SimonFischer04"]
DEPENDENCIES = ["uart"]
CONF_DLMS_METER_ID = "dlms_meter_id"
CONF_DECRYPTION_KEY = "decryption_key"
CONF_PROVIDER = "provider"
PROVIDERS = {"generic": 0, "netznoe": 1}
dlms_meter_component_ns = cg.esphome_ns.namespace("dlms_meter")
DlmsMeterComponent = dlms_meter_component_ns.class_(
"DlmsMeterComponent", cg.Component, uart.UARTDevice
)
def validate_key(value):
value = cv.string_strict(value)
if len(value) != 32:
raise cv.Invalid("Decryption key must be 32 hex characters (16 bytes)")
try:
return [int(value[i : i + 2], 16) for i in range(0, 32, 2)]
except ValueError as exc:
raise cv.Invalid("Decryption key must be hex values from 00 to FF") from exc
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(DlmsMeterComponent),
cv.Required(CONF_DECRYPTION_KEY): validate_key,
cv.Optional(CONF_PROVIDER, default="generic"): cv.enum(
PROVIDERS, lower=True
),
}
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA),
cv.only_on([PLATFORM_ESP8266, PLATFORM_ESP32]),
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"dlms_meter", baud_rate=2400, require_rx=True
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
key = ", ".join(str(b) for b in config[CONF_DECRYPTION_KEY])
cg.add(var.set_decryption_key(cg.RawExpression(f"{{{key}}}")))
cg.add(var.set_provider(PROVIDERS[config[CONF_PROVIDER]]))

View File

@@ -0,0 +1,71 @@
#pragma once
#include <cstdint>
namespace esphome::dlms_meter {
/*
+-------------------------------+
| Ciphering Service |
+-------------------------------+
| System Title Length |
+-------------------------------+
| |
| |
| |
| System |
| Title |
| |
| |
| |
+-------------------------------+
| Length | (1 or 3 Bytes)
+-------------------------------+
| Security Control Byte |
+-------------------------------+
| |
| Frame |
| Counter |
| |
+-------------------------------+
| |
~ ~
Encrypted Payload
~ ~
| |
+-------------------------------+
Ciphering Service: 0xDB (General-Glo-Ciphering)
System Title Length: 0x08
System Title: Unique ID of meter
Length: 1 Byte=Length <= 127, 3 Bytes=Length > 127 (0x82 & 2 Bytes length)
Security Control Byte:
- Bit 3…0: Security_Suite_Id
- Bit 4: "A" subfield: indicates that authentication is applied
- Bit 5: "E" subfield: indicates that encryption is applied
- Bit 6: Key_Set subfield: 0 = Unicast, 1 = Broadcast
- Bit 7: Indicates the use of compression.
*/
static constexpr uint8_t DLMS_HEADER_LENGTH = 16;
static constexpr uint8_t DLMS_HEADER_EXT_OFFSET = 2; // Extra offset for extended length header
static constexpr uint8_t DLMS_CIPHER_OFFSET = 0;
static constexpr uint8_t DLMS_SYST_OFFSET = 1;
static constexpr uint8_t DLMS_LENGTH_OFFSET = 10;
static constexpr uint8_t TWO_BYTE_LENGTH = 0x82;
static constexpr uint8_t DLMS_LENGTH_CORRECTION = 5; // Header bytes included in length field
static constexpr uint8_t DLMS_SECBYTE_OFFSET = 11;
static constexpr uint8_t DLMS_FRAMECOUNTER_OFFSET = 12;
static constexpr uint8_t DLMS_FRAMECOUNTER_LENGTH = 4;
static constexpr uint8_t DLMS_PAYLOAD_OFFSET = 16;
static constexpr uint8_t GLO_CIPHERING = 0xDB;
static constexpr uint8_t DATA_NOTIFICATION = 0x0F;
static constexpr uint8_t TIMESTAMP_DATETIME = 0x0C;
static constexpr uint16_t MAX_MESSAGE_LENGTH = 512; // Maximum size of message (when having 2 bytes length in header).
// Provider specific quirks
static constexpr uint8_t NETZ_NOE_MAGIC_BYTE = 0x81; // Magic length byte used by Netz NOE
static constexpr uint8_t NETZ_NOE_EXPECTED_MESSAGE_LENGTH = 0xF8;
static constexpr uint8_t NETZ_NOE_EXPECTED_SECURITY_CONTROL_BYTE = 0x20;
} // namespace esphome::dlms_meter

View File

@@ -0,0 +1,468 @@
#include "dlms_meter.h"
#include <cmath>
#if defined(USE_ESP8266_FRAMEWORK_ARDUINO)
#include <bearssl/bearssl.h>
#elif defined(USE_ESP32)
#include "mbedtls/esp_config.h"
#include "mbedtls/gcm.h"
#endif
namespace esphome::dlms_meter {
static constexpr const char *TAG = "dlms_meter";
void DlmsMeterComponent::dump_config() {
const char *provider_name = this->provider_ == PROVIDER_NETZNOE ? "Netz NOE" : "Generic";
ESP_LOGCONFIG(TAG,
"DLMS Meter:\n"
" Provider: %s\n"
" Read Timeout: %u ms",
provider_name, this->read_timeout_);
#define DLMS_METER_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s##_sensor_);
DLMS_METER_SENSOR_LIST(DLMS_METER_LOG_SENSOR, )
#define DLMS_METER_LOG_TEXT_SENSOR(s) LOG_TEXT_SENSOR(" ", #s, this->s##_text_sensor_);
DLMS_METER_TEXT_SENSOR_LIST(DLMS_METER_LOG_TEXT_SENSOR, )
}
void DlmsMeterComponent::loop() {
// Read while data is available, netznoe uses two frames so allow 2x max frame length
while (this->available()) {
if (this->receive_buffer_.size() >= MBUS_MAX_FRAME_LENGTH * 2) {
ESP_LOGW(TAG, "Receive buffer full, dropping remaining bytes");
break;
}
uint8_t c;
this->read_byte(&c);
this->receive_buffer_.push_back(c);
this->last_read_ = millis();
}
if (!this->receive_buffer_.empty() && millis() - this->last_read_ > this->read_timeout_) {
this->mbus_payload_.clear();
if (!this->parse_mbus_(this->mbus_payload_))
return;
uint16_t message_length;
uint8_t systitle_length;
uint16_t header_offset;
if (!this->parse_dlms_(this->mbus_payload_, message_length, systitle_length, header_offset))
return;
if (message_length < DECODER_START_OFFSET || message_length > MAX_MESSAGE_LENGTH) {
ESP_LOGE(TAG, "DLMS: Message length invalid: %u", message_length);
this->receive_buffer_.clear();
return;
}
// Decrypt in place and then decode the OBIS codes
if (!this->decrypt_(this->mbus_payload_, message_length, systitle_length, header_offset))
return;
this->decode_obis_(&this->mbus_payload_[header_offset + DLMS_PAYLOAD_OFFSET], message_length);
}
}
bool DlmsMeterComponent::parse_mbus_(std::vector<uint8_t> &mbus_payload) {
ESP_LOGV(TAG, "Parsing M-Bus frames");
uint16_t frame_offset = 0; // Offset is used if the M-Bus message is split into multiple frames
while (frame_offset < this->receive_buffer_.size()) {
// Ensure enough bytes remain for the minimal intro header before accessing indices
if (this->receive_buffer_.size() - frame_offset < MBUS_HEADER_INTRO_LENGTH) {
ESP_LOGE(TAG, "MBUS: Not enough data for frame header (need %d, have %d)", MBUS_HEADER_INTRO_LENGTH,
(this->receive_buffer_.size() - frame_offset));
this->receive_buffer_.clear();
return false;
}
// Check start bytes
if (this->receive_buffer_[frame_offset + MBUS_START1_OFFSET] != START_BYTE_LONG_FRAME ||
this->receive_buffer_[frame_offset + MBUS_START2_OFFSET] != START_BYTE_LONG_FRAME) {
ESP_LOGE(TAG, "MBUS: Start bytes do not match");
this->receive_buffer_.clear();
return false;
}
// Both length bytes must be identical
if (this->receive_buffer_[frame_offset + MBUS_LENGTH1_OFFSET] !=
this->receive_buffer_[frame_offset + MBUS_LENGTH2_OFFSET]) {
ESP_LOGE(TAG, "MBUS: Length bytes do not match");
this->receive_buffer_.clear();
return false;
}
uint8_t frame_length = this->receive_buffer_[frame_offset + MBUS_LENGTH1_OFFSET]; // Get length of this frame
// Check if received data is enough for the given frame length
if (this->receive_buffer_.size() - frame_offset <
frame_length + 3) { // length field inside packet does not account for second start- + checksum- + stop- byte
ESP_LOGE(TAG, "MBUS: Frame too big for received data");
this->receive_buffer_.clear();
return false;
}
// Ensure we have full frame (header + payload + checksum + stop byte) before accessing stop byte
size_t required_total =
frame_length + MBUS_HEADER_INTRO_LENGTH + MBUS_FOOTER_LENGTH; // payload + header + 2 footer bytes
if (this->receive_buffer_.size() - frame_offset < required_total) {
ESP_LOGE(TAG, "MBUS: Incomplete frame (need %d, have %d)", (unsigned int) required_total,
this->receive_buffer_.size() - frame_offset);
this->receive_buffer_.clear();
return false;
}
if (this->receive_buffer_[frame_offset + frame_length + MBUS_HEADER_INTRO_LENGTH + MBUS_FOOTER_LENGTH - 1] !=
STOP_BYTE) {
ESP_LOGE(TAG, "MBUS: Invalid stop byte");
this->receive_buffer_.clear();
return false;
}
// Verify checksum: sum of all bytes starting at MBUS_HEADER_INTRO_LENGTH, take last byte
uint8_t checksum = 0; // use uint8_t so only the 8 least significant bits are stored
for (uint16_t i = 0; i < frame_length; i++) {
checksum += this->receive_buffer_[frame_offset + MBUS_HEADER_INTRO_LENGTH + i];
}
if (checksum != this->receive_buffer_[frame_offset + frame_length + MBUS_HEADER_INTRO_LENGTH]) {
ESP_LOGE(TAG, "MBUS: Invalid checksum: %x != %x", checksum,
this->receive_buffer_[frame_offset + frame_length + MBUS_HEADER_INTRO_LENGTH]);
this->receive_buffer_.clear();
return false;
}
mbus_payload.insert(mbus_payload.end(), &this->receive_buffer_[frame_offset + MBUS_FULL_HEADER_LENGTH],
&this->receive_buffer_[frame_offset + MBUS_HEADER_INTRO_LENGTH + frame_length]);
frame_offset += MBUS_HEADER_INTRO_LENGTH + frame_length + MBUS_FOOTER_LENGTH;
}
return true;
}
bool DlmsMeterComponent::parse_dlms_(const std::vector<uint8_t> &mbus_payload, uint16_t &message_length,
uint8_t &systitle_length, uint16_t &header_offset) {
ESP_LOGV(TAG, "Parsing DLMS header");
if (mbus_payload.size() < DLMS_HEADER_LENGTH + DLMS_HEADER_EXT_OFFSET) {
ESP_LOGE(TAG, "DLMS: Payload too short");
this->receive_buffer_.clear();
return false;
}
if (mbus_payload[DLMS_CIPHER_OFFSET] != GLO_CIPHERING) { // Only general-glo-ciphering is supported (0xDB)
ESP_LOGE(TAG, "DLMS: Unsupported cipher");
this->receive_buffer_.clear();
return false;
}
systitle_length = mbus_payload[DLMS_SYST_OFFSET];
if (systitle_length != 0x08) { // Only system titles with length of 8 are supported
ESP_LOGE(TAG, "DLMS: Unsupported system title length");
this->receive_buffer_.clear();
return false;
}
message_length = mbus_payload[DLMS_LENGTH_OFFSET];
header_offset = 0;
if (this->provider_ == PROVIDER_NETZNOE) {
// for some reason EVN seems to set the standard "length" field to 0x81 and then the actual length is in the next
// byte. Check some bytes to see if received data still matches expectation
if (message_length == NETZ_NOE_MAGIC_BYTE &&
mbus_payload[DLMS_LENGTH_OFFSET + 1] == NETZ_NOE_EXPECTED_MESSAGE_LENGTH &&
mbus_payload[DLMS_LENGTH_OFFSET + 2] == NETZ_NOE_EXPECTED_SECURITY_CONTROL_BYTE) {
message_length = mbus_payload[DLMS_LENGTH_OFFSET + 1];
header_offset = 1;
} else {
ESP_LOGE(TAG, "Wrong Length - Security Control Byte sequence detected for provider EVN");
}
} else {
if (message_length == TWO_BYTE_LENGTH) {
message_length = encode_uint16(mbus_payload[DLMS_LENGTH_OFFSET + 1], mbus_payload[DLMS_LENGTH_OFFSET + 2]);
header_offset = DLMS_HEADER_EXT_OFFSET;
}
}
if (message_length < DLMS_LENGTH_CORRECTION) {
ESP_LOGE(TAG, "DLMS: Message length too short: %u", message_length);
this->receive_buffer_.clear();
return false;
}
message_length -= DLMS_LENGTH_CORRECTION; // Correct message length due to part of header being included in length
if (mbus_payload.size() - DLMS_HEADER_LENGTH - header_offset != message_length) {
ESP_LOGV(TAG, "DLMS: Length mismatch - payload=%d, header=%d, offset=%d, message=%d", mbus_payload.size(),
DLMS_HEADER_LENGTH, header_offset, message_length);
ESP_LOGE(TAG, "DLMS: Message has invalid length");
this->receive_buffer_.clear();
return false;
}
if (mbus_payload[header_offset + DLMS_SECBYTE_OFFSET] != 0x21 &&
mbus_payload[header_offset + DLMS_SECBYTE_OFFSET] !=
0x20) { // Only certain security suite is supported (0x21 || 0x20)
ESP_LOGE(TAG, "DLMS: Unsupported security control byte");
this->receive_buffer_.clear();
return false;
}
return true;
}
bool DlmsMeterComponent::decrypt_(std::vector<uint8_t> &mbus_payload, uint16_t message_length, uint8_t systitle_length,
uint16_t header_offset) {
ESP_LOGV(TAG, "Decrypting payload");
uint8_t iv[12]; // Reserve space for the IV, always 12 bytes
// Copy system title to IV (System title is before length; no header offset needed!)
// Add 1 to the offset in order to skip the system title length byte
memcpy(&iv[0], &mbus_payload[DLMS_SYST_OFFSET + 1], systitle_length);
memcpy(&iv[8], &mbus_payload[header_offset + DLMS_FRAMECOUNTER_OFFSET],
DLMS_FRAMECOUNTER_LENGTH); // Copy frame counter to IV
uint8_t *payload_ptr = &mbus_payload[header_offset + DLMS_PAYLOAD_OFFSET];
#if defined(USE_ESP8266_FRAMEWORK_ARDUINO)
br_gcm_context gcm_ctx;
br_aes_ct_ctr_keys bc;
br_aes_ct_ctr_init(&bc, this->decryption_key_.data(), this->decryption_key_.size());
br_gcm_init(&gcm_ctx, &bc.vtable, br_ghash_ctmul32);
br_gcm_reset(&gcm_ctx, iv, sizeof(iv));
br_gcm_flip(&gcm_ctx);
br_gcm_run(&gcm_ctx, 0, payload_ptr, message_length);
#elif defined(USE_ESP32)
size_t outlen = 0;
mbedtls_gcm_context gcm_ctx;
mbedtls_gcm_init(&gcm_ctx);
mbedtls_gcm_setkey(&gcm_ctx, MBEDTLS_CIPHER_ID_AES, this->decryption_key_.data(), this->decryption_key_.size() * 8);
mbedtls_gcm_starts(&gcm_ctx, MBEDTLS_GCM_DECRYPT, iv, sizeof(iv));
auto ret = mbedtls_gcm_update(&gcm_ctx, payload_ptr, message_length, payload_ptr, message_length, &outlen);
mbedtls_gcm_free(&gcm_ctx);
if (ret != 0) {
ESP_LOGE(TAG, "Decryption failed with error: %d", ret);
this->receive_buffer_.clear();
return false;
}
#else
#error "Invalid Platform"
#endif
if (payload_ptr[0] != DATA_NOTIFICATION || payload_ptr[5] != TIMESTAMP_DATETIME) {
ESP_LOGE(TAG, "OBIS: Packet was decrypted but data is invalid");
this->receive_buffer_.clear();
return false;
}
ESP_LOGV(TAG, "Decrypted payload: %d bytes", message_length);
return true;
}
void DlmsMeterComponent::decode_obis_(uint8_t *plaintext, uint16_t message_length) {
ESP_LOGV(TAG, "Decoding payload");
MeterData data{};
uint16_t current_position = DECODER_START_OFFSET;
bool power_factor_found = false;
while (current_position + OBIS_CODE_OFFSET <= message_length) {
if (plaintext[current_position + OBIS_TYPE_OFFSET] != DataType::OCTET_STRING) {
ESP_LOGE(TAG, "OBIS: Unsupported OBIS header type: %x", plaintext[current_position + OBIS_TYPE_OFFSET]);
this->receive_buffer_.clear();
return;
}
uint8_t obis_code_length = plaintext[current_position + OBIS_LENGTH_OFFSET];
if (obis_code_length != OBIS_CODE_LENGTH_STANDARD && obis_code_length != OBIS_CODE_LENGTH_EXTENDED) {
ESP_LOGE(TAG, "OBIS: Unsupported OBIS header length: %x", obis_code_length);
this->receive_buffer_.clear();
return;
}
if (current_position + OBIS_CODE_OFFSET + obis_code_length > message_length) {
ESP_LOGE(TAG, "OBIS: Buffer too short for OBIS code");
this->receive_buffer_.clear();
return;
}
uint8_t *obis_code = &plaintext[current_position + OBIS_CODE_OFFSET];
uint8_t obis_medium = obis_code[OBIS_A];
uint16_t obis_cd = encode_uint16(obis_code[OBIS_C], obis_code[OBIS_D]);
bool timestamp_found = false;
bool meter_number_found = false;
if (this->provider_ == PROVIDER_NETZNOE) {
// Do not advance Position when reading the Timestamp at DECODER_START_OFFSET
if ((obis_code_length == OBIS_CODE_LENGTH_EXTENDED) && (current_position == DECODER_START_OFFSET)) {
timestamp_found = true;
} else if (power_factor_found) {
meter_number_found = true;
power_factor_found = false;
} else {
current_position += obis_code_length + OBIS_CODE_OFFSET; // Advance past code and position
}
} else {
current_position += obis_code_length + OBIS_CODE_OFFSET; // Advance past code, position and type
}
if (!timestamp_found && !meter_number_found && obis_medium != Medium::ELECTRICITY &&
obis_medium != Medium::ABSTRACT) {
ESP_LOGE(TAG, "OBIS: Unsupported OBIS medium: %x", obis_medium);
this->receive_buffer_.clear();
return;
}
if (current_position >= message_length) {
ESP_LOGE(TAG, "OBIS: Buffer too short for data type");
this->receive_buffer_.clear();
return;
}
float value = 0.0f;
uint8_t value_size = 0;
uint8_t data_type = plaintext[current_position];
current_position++;
switch (data_type) {
case DataType::DOUBLE_LONG_UNSIGNED: {
value_size = 4;
if (current_position + value_size > message_length) {
ESP_LOGE(TAG, "OBIS: Buffer too short for DOUBLE_LONG_UNSIGNED");
this->receive_buffer_.clear();
return;
}
value = encode_uint32(plaintext[current_position + 0], plaintext[current_position + 1],
plaintext[current_position + 2], plaintext[current_position + 3]);
current_position += value_size;
break;
}
case DataType::LONG_UNSIGNED: {
value_size = 2;
if (current_position + value_size > message_length) {
ESP_LOGE(TAG, "OBIS: Buffer too short for LONG_UNSIGNED");
this->receive_buffer_.clear();
return;
}
value = encode_uint16(plaintext[current_position + 0], plaintext[current_position + 1]);
current_position += value_size;
break;
}
case DataType::OCTET_STRING: {
uint8_t data_length = plaintext[current_position];
current_position++; // Advance past string length
if (current_position + data_length > message_length) {
ESP_LOGE(TAG, "OBIS: Buffer too short for OCTET_STRING");
this->receive_buffer_.clear();
return;
}
// Handle timestamp (normal OBIS code or NETZNOE special case)
if (obis_cd == OBIS_TIMESTAMP || timestamp_found) {
if (data_length < 8) {
ESP_LOGE(TAG, "OBIS: Timestamp data too short: %u", data_length);
this->receive_buffer_.clear();
return;
}
uint16_t year = encode_uint16(plaintext[current_position + 0], plaintext[current_position + 1]);
uint8_t month = plaintext[current_position + 2];
uint8_t day = plaintext[current_position + 3];
uint8_t hour = plaintext[current_position + 5];
uint8_t minute = plaintext[current_position + 6];
uint8_t second = plaintext[current_position + 7];
if (year > 9999 || month > 12 || day > 31 || hour > 23 || minute > 59 || second > 59) {
ESP_LOGE(TAG, "Invalid timestamp values: %04u-%02u-%02uT%02u:%02u:%02uZ", year, month, day, hour, minute,
second);
this->receive_buffer_.clear();
return;
}
snprintf(data.timestamp, sizeof(data.timestamp), "%04u-%02u-%02uT%02u:%02u:%02uZ", year, month, day, hour,
minute, second);
} else if (meter_number_found) {
snprintf(data.meternumber, sizeof(data.meternumber), "%.*s", data_length, &plaintext[current_position]);
}
current_position += data_length;
break;
}
default:
ESP_LOGE(TAG, "OBIS: Unsupported OBIS data type: %x", data_type);
this->receive_buffer_.clear();
return;
}
// Skip break after data
if (this->provider_ == PROVIDER_NETZNOE) {
// Don't skip the break on the first timestamp, as there's none
if (!timestamp_found) {
current_position += 2;
}
} else {
current_position += 2;
}
// Check for additional data (scaler-unit structure)
if (current_position < message_length && plaintext[current_position] == DataType::INTEGER) {
// Apply scaler: real_value = raw_value × 10^scaler
if (current_position + 1 < message_length) {
int8_t scaler = static_cast<int8_t>(plaintext[current_position + 1]);
if (scaler != 0) {
value *= powf(10.0f, scaler);
}
}
// on EVN Meters there is no additional break
if (this->provider_ == PROVIDER_NETZNOE) {
current_position += 4;
} else {
current_position += 6;
}
}
// Handle numeric values (LONG_UNSIGNED and DOUBLE_LONG_UNSIGNED)
if (value_size > 0) {
switch (obis_cd) {
case OBIS_VOLTAGE_L1:
data.voltage_l1 = value;
break;
case OBIS_VOLTAGE_L2:
data.voltage_l2 = value;
break;
case OBIS_VOLTAGE_L3:
data.voltage_l3 = value;
break;
case OBIS_CURRENT_L1:
data.current_l1 = value;
break;
case OBIS_CURRENT_L2:
data.current_l2 = value;
break;
case OBIS_CURRENT_L3:
data.current_l3 = value;
break;
case OBIS_ACTIVE_POWER_PLUS:
data.active_power_plus = value;
break;
case OBIS_ACTIVE_POWER_MINUS:
data.active_power_minus = value;
break;
case OBIS_ACTIVE_ENERGY_PLUS:
data.active_energy_plus = value;
break;
case OBIS_ACTIVE_ENERGY_MINUS:
data.active_energy_minus = value;
break;
case OBIS_REACTIVE_ENERGY_PLUS:
data.reactive_energy_plus = value;
break;
case OBIS_REACTIVE_ENERGY_MINUS:
data.reactive_energy_minus = value;
break;
case OBIS_POWER_FACTOR:
data.power_factor = value;
power_factor_found = true;
break;
default:
ESP_LOGW(TAG, "Unsupported OBIS code 0x%04X", obis_cd);
}
}
}
this->receive_buffer_.clear();
ESP_LOGI(TAG, "Received valid data");
this->publish_sensors(data);
this->status_clear_warning();
}
} // namespace esphome::dlms_meter

View File

@@ -0,0 +1,96 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
#include "esphome/components/uart/uart.h"
#include "mbus.h"
#include "dlms.h"
#include "obis.h"
#include <array>
#include <vector>
namespace esphome::dlms_meter {
#ifndef DLMS_METER_SENSOR_LIST
#define DLMS_METER_SENSOR_LIST(F, SEP)
#endif
#ifndef DLMS_METER_TEXT_SENSOR_LIST
#define DLMS_METER_TEXT_SENSOR_LIST(F, SEP)
#endif
struct MeterData {
float voltage_l1 = 0.0f; // Voltage L1
float voltage_l2 = 0.0f; // Voltage L2
float voltage_l3 = 0.0f; // Voltage L3
float current_l1 = 0.0f; // Current L1
float current_l2 = 0.0f; // Current L2
float current_l3 = 0.0f; // Current L3
float active_power_plus = 0.0f; // Active power taken from grid
float active_power_minus = 0.0f; // Active power put into grid
float active_energy_plus = 0.0f; // Active energy taken from grid
float active_energy_minus = 0.0f; // Active energy put into grid
float reactive_energy_plus = 0.0f; // Reactive energy taken from grid
float reactive_energy_minus = 0.0f; // Reactive energy put into grid
char timestamp[27]{}; // Text sensor for the timestamp value
// Netz NOE
float power_factor = 0.0f; // Power Factor
char meternumber[13]{}; // Text sensor for the meterNumber value
};
// Provider constants
enum Providers : uint32_t { PROVIDER_GENERIC = 0x00, PROVIDER_NETZNOE = 0x01 };
class DlmsMeterComponent : public Component, public uart::UARTDevice {
public:
DlmsMeterComponent() = default;
void dump_config() override;
void loop() override;
void set_decryption_key(const std::array<uint8_t, 16> &key) { this->decryption_key_ = key; }
void set_provider(uint32_t provider) { this->provider_ = provider; }
void publish_sensors(MeterData &data) {
#define DLMS_METER_PUBLISH_SENSOR(s) \
if (this->s##_sensor_ != nullptr) \
s##_sensor_->publish_state(data.s);
DLMS_METER_SENSOR_LIST(DLMS_METER_PUBLISH_SENSOR, )
#define DLMS_METER_PUBLISH_TEXT_SENSOR(s) \
if (this->s##_text_sensor_ != nullptr) \
s##_text_sensor_->publish_state(data.s);
DLMS_METER_TEXT_SENSOR_LIST(DLMS_METER_PUBLISH_TEXT_SENSOR, )
}
DLMS_METER_SENSOR_LIST(SUB_SENSOR, )
DLMS_METER_TEXT_SENSOR_LIST(SUB_TEXT_SENSOR, )
protected:
bool parse_mbus_(std::vector<uint8_t> &mbus_payload);
bool parse_dlms_(const std::vector<uint8_t> &mbus_payload, uint16_t &message_length, uint8_t &systitle_length,
uint16_t &header_offset);
bool decrypt_(std::vector<uint8_t> &mbus_payload, uint16_t message_length, uint8_t systitle_length,
uint16_t header_offset);
void decode_obis_(uint8_t *plaintext, uint16_t message_length);
std::vector<uint8_t> receive_buffer_; // Stores the packet currently being received
std::vector<uint8_t> mbus_payload_; // Parsed M-Bus payload, reused to avoid heap churn
uint32_t last_read_ = 0; // Timestamp when data was last read
uint32_t read_timeout_ = 1000; // Time to wait after last byte before considering data complete
uint32_t provider_ = PROVIDER_GENERIC; // Provider of the meter / your grid operator
std::array<uint8_t, 16> decryption_key_;
};
} // namespace esphome::dlms_meter

View File

@@ -0,0 +1,69 @@
#pragma once
#include <cstdint>
namespace esphome::dlms_meter {
/*
+----------------------------------------------------+ -
| Start Character [0x68] | \
+----------------------------------------------------+ |
| Data Length (L) | |
+----------------------------------------------------+ |
| Data Length Repeat (L) | |
+----------------------------------------------------+ > M-Bus Data link layer
| Start Character Repeat [0x68] | |
+----------------------------------------------------+ |
| Control/Function Field (C) | |
+----------------------------------------------------+ |
| Address Field (A) | /
+----------------------------------------------------+ -
| Control Information Field (CI) | \
+----------------------------------------------------+ |
| Source Transport Service Access Point (STSAP) | > DLMS/COSEM M-Bus transport layer
+----------------------------------------------------+ |
| Destination Transport Service Access Point (DTSAP) | /
+----------------------------------------------------+ -
| | \
~ ~ |
Data > DLMS/COSEM Application Layer
~ ~ |
| | /
+----------------------------------------------------+ -
| Checksum | \
+----------------------------------------------------+ > M-Bus Data link layer
| Stop Character [0x16] | /
+----------------------------------------------------+ -
Data_Length = L - C - A - CI
Each line (except Data) is one Byte
Possible Values found in publicly available docs:
- C: 0x53/0x73 (SND_UD)
- A: FF (Broadcast)
- CI: 0x00-0x1F/0x60/0x61/0x7C/0x7D
- STSAP: 0x01 (Management Logical Device ID 1 of the meter)
- DTSAP: 0x67 (Consumer Information Push Client ID 103)
*/
// MBUS start bytes for different telegram formats:
// - Single Character: 0xE5 (length=1)
// - Short Frame: 0x10 (length=5)
// - Control Frame: 0x68 (length=9)
// - Long Frame: 0x68 (length=9+data_length)
// This component currently only uses Long Frame.
static constexpr uint8_t START_BYTE_SINGLE_CHARACTER = 0xE5;
static constexpr uint8_t START_BYTE_SHORT_FRAME = 0x10;
static constexpr uint8_t START_BYTE_CONTROL_FRAME = 0x68;
static constexpr uint8_t START_BYTE_LONG_FRAME = 0x68;
static constexpr uint8_t MBUS_HEADER_INTRO_LENGTH = 4; // Header length for the intro (0x68, length, length, 0x68)
static constexpr uint8_t MBUS_FULL_HEADER_LENGTH = 9; // Total header length
static constexpr uint8_t MBUS_FOOTER_LENGTH = 2; // Footer after frame
static constexpr uint8_t MBUS_MAX_FRAME_LENGTH = 250; // Maximum size of frame
static constexpr uint8_t MBUS_START1_OFFSET = 0; // Offset of first start byte
static constexpr uint8_t MBUS_LENGTH1_OFFSET = 1; // Offset of first length byte
static constexpr uint8_t MBUS_LENGTH2_OFFSET = 2; // Offset of (duplicated) second length byte
static constexpr uint8_t MBUS_START2_OFFSET = 3; // Offset of (duplicated) second start byte
static constexpr uint8_t STOP_BYTE = 0x16;
} // namespace esphome::dlms_meter

View File

@@ -0,0 +1,94 @@
#pragma once
#include <cstdint>
namespace esphome::dlms_meter {
// Data types as per specification
enum DataType {
NULL_DATA = 0x00,
BOOLEAN = 0x03,
BIT_STRING = 0x04,
DOUBLE_LONG = 0x05,
DOUBLE_LONG_UNSIGNED = 0x06,
OCTET_STRING = 0x09,
VISIBLE_STRING = 0x0A,
UTF8_STRING = 0x0C,
BINARY_CODED_DECIMAL = 0x0D,
INTEGER = 0x0F,
LONG = 0x10,
UNSIGNED = 0x11,
LONG_UNSIGNED = 0x12,
LONG64 = 0x14,
LONG64_UNSIGNED = 0x15,
ENUM = 0x16,
FLOAT32 = 0x17,
FLOAT64 = 0x18,
DATE_TIME = 0x19,
DATE = 0x1A,
TIME = 0x1B,
ARRAY = 0x01,
STRUCTURE = 0x02,
COMPACT_ARRAY = 0x13
};
enum Medium {
ABSTRACT = 0x00,
ELECTRICITY = 0x01,
HEAT_COST_ALLOCATOR = 0x04,
COOLING = 0x05,
HEAT = 0x06,
GAS = 0x07,
COLD_WATER = 0x08,
HOT_WATER = 0x09,
OIL = 0x10,
COMPRESSED_AIR = 0x11,
NITROGEN = 0x12
};
// Data structure
static constexpr uint8_t DECODER_START_OFFSET = 20; // Skip header, timestamp and break block
static constexpr uint8_t OBIS_TYPE_OFFSET = 0;
static constexpr uint8_t OBIS_LENGTH_OFFSET = 1;
static constexpr uint8_t OBIS_CODE_OFFSET = 2;
static constexpr uint8_t OBIS_CODE_LENGTH_STANDARD = 0x06; // 6-byte OBIS code (A.B.C.D.E.F)
static constexpr uint8_t OBIS_CODE_LENGTH_EXTENDED = 0x0C; // 12-byte extended OBIS code
static constexpr uint8_t OBIS_A = 0;
static constexpr uint8_t OBIS_B = 1;
static constexpr uint8_t OBIS_C = 2;
static constexpr uint8_t OBIS_D = 3;
static constexpr uint8_t OBIS_E = 4;
static constexpr uint8_t OBIS_F = 5;
// Metadata
static constexpr uint16_t OBIS_TIMESTAMP = 0x0100;
static constexpr uint16_t OBIS_SERIAL_NUMBER = 0x6001;
static constexpr uint16_t OBIS_DEVICE_NAME = 0x2A00;
// Voltage
static constexpr uint16_t OBIS_VOLTAGE_L1 = 0x2007;
static constexpr uint16_t OBIS_VOLTAGE_L2 = 0x3407;
static constexpr uint16_t OBIS_VOLTAGE_L3 = 0x4807;
// Current
static constexpr uint16_t OBIS_CURRENT_L1 = 0x1F07;
static constexpr uint16_t OBIS_CURRENT_L2 = 0x3307;
static constexpr uint16_t OBIS_CURRENT_L3 = 0x4707;
// Power
static constexpr uint16_t OBIS_ACTIVE_POWER_PLUS = 0x0107;
static constexpr uint16_t OBIS_ACTIVE_POWER_MINUS = 0x0207;
// Active energy
static constexpr uint16_t OBIS_ACTIVE_ENERGY_PLUS = 0x0108;
static constexpr uint16_t OBIS_ACTIVE_ENERGY_MINUS = 0x0208;
// Reactive energy
static constexpr uint16_t OBIS_REACTIVE_ENERGY_PLUS = 0x0308;
static constexpr uint16_t OBIS_REACTIVE_ENERGY_MINUS = 0x0408;
// Netz NOE specific
static constexpr uint16_t OBIS_POWER_FACTOR = 0x0D07;
} // namespace esphome::dlms_meter

View File

@@ -0,0 +1,124 @@
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE,
UNIT_VOLT,
UNIT_WATT,
UNIT_WATT_HOURS,
)
from .. import CONF_DLMS_METER_ID, DlmsMeterComponent
AUTO_LOAD = ["dlms_meter"]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_DLMS_METER_ID): cv.use_id(DlmsMeterComponent),
cv.Optional("voltage_l1"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_l2"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_l3"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("current_l1"): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("current_l2"): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("current_l3"): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("active_power_plus"): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("active_power_minus"): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("active_energy_plus"): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("active_energy_minus"): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("reactive_energy_plus"): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("reactive_energy_minus"): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
# Netz NOE
cv.Optional("power_factor"): sensor.sensor_schema(
accuracy_decimals=3,
device_class=DEVICE_CLASS_POWER_FACTOR,
state_class=STATE_CLASS_MEASUREMENT,
),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
hub = await cg.get_variable(config[CONF_DLMS_METER_ID])
sensors = []
for key, conf in config.items():
if not isinstance(conf, dict):
continue
id = conf[CONF_ID]
if id and id.type == sensor.Sensor:
sens = await sensor.new_sensor(conf)
cg.add(getattr(hub, f"set_{key}_sensor")(sens))
sensors.append(f"F({key})")
if sensors:
cg.add_define(
"DLMS_METER_SENSOR_LIST(F, sep)", cg.RawExpression(" sep ".join(sensors))
)

View File

@@ -0,0 +1,37 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import CONF_ID
from .. import CONF_DLMS_METER_ID, DlmsMeterComponent
AUTO_LOAD = ["dlms_meter"]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_DLMS_METER_ID): cv.use_id(DlmsMeterComponent),
cv.Optional("timestamp"): text_sensor.text_sensor_schema(),
# Netz NOE
cv.Optional("meternumber"): text_sensor.text_sensor_schema(),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
hub = await cg.get_variable(config[CONF_DLMS_METER_ID])
text_sensors = []
for key, conf in config.items():
if not isinstance(conf, dict):
continue
id = conf[CONF_ID]
if id and id.type == text_sensor.TextSensor:
sens = await text_sensor.new_text_sensor(conf)
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))
text_sensors.append(f"F({key})")
if text_sensors:
cg.add_define(
"DLMS_METER_TEXT_SENSOR_LIST(F, sep)",
cg.RawExpression(" sep ".join(text_sensors)),
)

View File

@@ -141,15 +141,15 @@ void EndstopCover::start_direction_(CoverOperation dir) {
Trigger<> *trig; Trigger<> *trig;
switch (dir) { switch (dir) {
case COVER_OPERATION_IDLE: case COVER_OPERATION_IDLE:
trig = this->stop_trigger_; trig = &this->stop_trigger_;
break; break;
case COVER_OPERATION_OPENING: case COVER_OPERATION_OPENING:
this->last_operation_ = dir; this->last_operation_ = dir;
trig = this->open_trigger_; trig = &this->open_trigger_;
break; break;
case COVER_OPERATION_CLOSING: case COVER_OPERATION_CLOSING:
this->last_operation_ = dir; this->last_operation_ = dir;
trig = this->close_trigger_; trig = &this->close_trigger_;
break; break;
default: default:
return; return;

View File

@@ -15,9 +15,9 @@ class EndstopCover : public cover::Cover, public Component {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
Trigger<> *get_open_trigger() const { return this->open_trigger_; } Trigger<> *get_open_trigger() { return &this->open_trigger_; }
Trigger<> *get_close_trigger() const { return this->close_trigger_; } Trigger<> *get_close_trigger() { return &this->close_trigger_; }
Trigger<> *get_stop_trigger() const { return this->stop_trigger_; } Trigger<> *get_stop_trigger() { return &this->stop_trigger_; }
void set_open_endstop(binary_sensor::BinarySensor *open_endstop) { this->open_endstop_ = open_endstop; } void set_open_endstop(binary_sensor::BinarySensor *open_endstop) { this->open_endstop_ = open_endstop; }
void set_close_endstop(binary_sensor::BinarySensor *close_endstop) { this->close_endstop_ = close_endstop; } void set_close_endstop(binary_sensor::BinarySensor *close_endstop) { this->close_endstop_ = close_endstop; }
void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; } void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; }
@@ -39,11 +39,11 @@ class EndstopCover : public cover::Cover, public Component {
binary_sensor::BinarySensor *open_endstop_; binary_sensor::BinarySensor *open_endstop_;
binary_sensor::BinarySensor *close_endstop_; binary_sensor::BinarySensor *close_endstop_;
Trigger<> *open_trigger_{new Trigger<>()}; Trigger<> open_trigger_;
uint32_t open_duration_; uint32_t open_duration_;
Trigger<> *close_trigger_{new Trigger<>()}; Trigger<> close_trigger_;
uint32_t close_duration_; uint32_t close_duration_;
Trigger<> *stop_trigger_{new Trigger<>()}; Trigger<> stop_trigger_;
uint32_t max_duration_{UINT32_MAX}; uint32_t max_duration_{UINT32_MAX};
Trigger<> *prev_command_trigger_{nullptr}; Trigger<> *prev_command_trigger_{nullptr};

View File

@@ -124,10 +124,14 @@ COMPILER_OPTIMIZATIONS = {
# - "sdmmc": driver -> esp_driver_sdmmc -> sdmmc dependency chain # - "sdmmc": driver -> esp_driver_sdmmc -> sdmmc dependency chain
DEFAULT_EXCLUDED_IDF_COMPONENTS = ( DEFAULT_EXCLUDED_IDF_COMPONENTS = (
"cmock", # Unit testing mock framework - ESPHome doesn't use IDF's testing "cmock", # Unit testing mock framework - ESPHome doesn't use IDF's testing
"driver", # Legacy driver shim - only needed by esp32_touch, esp32_can for legacy headers
"esp_adc", # ADC driver - only needed by adc component "esp_adc", # ADC driver - only needed by adc component
"esp_driver_dac", # DAC driver - only needed by esp32_dac component
"esp_driver_i2s", # I2S driver - only needed by i2s_audio component "esp_driver_i2s", # I2S driver - only needed by i2s_audio component
"esp_driver_mcpwm", # MCPWM driver - ESPHome doesn't use motor control PWM
"esp_driver_rmt", # RMT driver - only needed by remote_transmitter/receiver, neopixelbus "esp_driver_rmt", # RMT driver - only needed by remote_transmitter/receiver, neopixelbus
"esp_driver_touch_sens", # Touch sensor driver - only needed by esp32_touch "esp_driver_touch_sens", # Touch sensor driver - only needed by esp32_touch
"esp_driver_twai", # TWAI/CAN driver - only needed by esp32_can component
"esp_eth", # Ethernet driver - only needed by ethernet component "esp_eth", # Ethernet driver - only needed by ethernet component
"esp_hid", # HID host/device support - ESPHome doesn't implement HID functionality "esp_hid", # HID host/device support - ESPHome doesn't implement HID functionality
"esp_http_client", # HTTP client - only needed by http_request component "esp_http_client", # HTTP client - only needed by http_request component
@@ -138,9 +142,11 @@ DEFAULT_EXCLUDED_IDF_COMPONENTS = (
"espcoredump", # Core dump support - ESPHome has its own debug component "espcoredump", # Core dump support - ESPHome has its own debug component
"fatfs", # FAT filesystem - ESPHome doesn't use filesystem storage "fatfs", # FAT filesystem - ESPHome doesn't use filesystem storage
"mqtt", # ESP-IDF MQTT library - ESPHome has its own MQTT implementation "mqtt", # ESP-IDF MQTT library - ESPHome has its own MQTT implementation
"openthread", # Thread protocol - only needed by openthread component
"perfmon", # Xtensa performance monitor - ESPHome has its own debug component "perfmon", # Xtensa performance monitor - ESPHome has its own debug component
"protocomm", # Protocol communication for provisioning - unused by ESPHome "protocomm", # Protocol communication for provisioning - unused by ESPHome
"spiffs", # SPIFFS filesystem - ESPHome doesn't use filesystem storage (IDF only) "spiffs", # SPIFFS filesystem - ESPHome doesn't use filesystem storage (IDF only)
"ulp", # ULP coprocessor - not currently used by any ESPHome component
"unity", # Unit testing framework - ESPHome doesn't use IDF's testing "unity", # Unit testing framework - ESPHome doesn't use IDF's testing
"wear_levelling", # Flash wear levelling for fatfs - unused since fatfs unused "wear_levelling", # Flash wear levelling for fatfs - unused since fatfs unused
"wifi_provisioning", # WiFi provisioning - ESPHome uses its own improv implementation "wifi_provisioning", # WiFi provisioning - ESPHome uses its own improv implementation
@@ -739,9 +745,10 @@ CONF_DISABLE_REGI2C_IN_IRAM = "disable_regi2c_in_iram"
CONF_DISABLE_FATFS = "disable_fatfs" CONF_DISABLE_FATFS = "disable_fatfs"
# VFS requirement tracking # VFS requirement tracking
# Components that need VFS features can call require_vfs_select() or require_vfs_dir() # Components that need VFS features can call require_vfs_*() functions
KEY_VFS_SELECT_REQUIRED = "vfs_select_required" KEY_VFS_SELECT_REQUIRED = "vfs_select_required"
KEY_VFS_DIR_REQUIRED = "vfs_dir_required" KEY_VFS_DIR_REQUIRED = "vfs_dir_required"
KEY_VFS_TERMIOS_REQUIRED = "vfs_termios_required"
# Feature requirement tracking - components can call require_* functions to re-enable # Feature requirement tracking - components can call require_* functions to re-enable
# These are stored in CORE.data[KEY_ESP32] dict # These are stored in CORE.data[KEY_ESP32] dict
KEY_USB_SERIAL_JTAG_SECONDARY_REQUIRED = "usb_serial_jtag_secondary_required" KEY_USB_SERIAL_JTAG_SECONDARY_REQUIRED = "usb_serial_jtag_secondary_required"
@@ -768,6 +775,15 @@ def require_vfs_dir() -> None:
CORE.data[KEY_VFS_DIR_REQUIRED] = True CORE.data[KEY_VFS_DIR_REQUIRED] = True
def require_vfs_termios() -> None:
"""Mark that VFS termios support is required by a component.
Call this from components that use terminal I/O functions (usb_serial_jtag_vfs_*, etc.).
This prevents CONFIG_VFS_SUPPORT_TERMIOS from being disabled.
"""
CORE.data[KEY_VFS_TERMIOS_REQUIRED] = True
def require_full_certificate_bundle() -> None: def require_full_certificate_bundle() -> None:
"""Request the full certificate bundle instead of the common-CAs-only bundle. """Request the full certificate bundle instead of the common-CAs-only bundle.
@@ -1319,6 +1335,10 @@ async def to_code(config):
# Disable dynamic log level control to save memory # Disable dynamic log level control to save memory
add_idf_sdkconfig_option("CONFIG_LOG_DYNAMIC_LEVEL_CONTROL", False) add_idf_sdkconfig_option("CONFIG_LOG_DYNAMIC_LEVEL_CONTROL", False)
# Disable per-tag log level filtering since dynamic level control is disabled above
# This saves ~250 bytes of RAM (tag cache) and associated code
add_idf_sdkconfig_option("CONFIG_LOG_TAG_LEVEL_IMPL_NONE", True)
# Reduce PHY TX power in the event of a brownout # Reduce PHY TX power in the event of a brownout
add_idf_sdkconfig_option("CONFIG_ESP_PHY_REDUCE_TX_POWER", True) add_idf_sdkconfig_option("CONFIG_ESP_PHY_REDUCE_TX_POWER", True)
@@ -1372,11 +1392,18 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_LIBC_LOCKS_PLACE_IN_IRAM", False) add_idf_sdkconfig_option("CONFIG_LIBC_LOCKS_PLACE_IN_IRAM", False)
# Disable VFS support for termios (terminal I/O functions) # Disable VFS support for termios (terminal I/O functions)
# ESPHome doesn't use termios functions on ESP32 (only used in host UART driver). # USB Serial JTAG VFS functions require termios support.
# Components that need it (e.g., logger when USB_SERIAL_JTAG is supported but not selected
# as the logger output) call require_vfs_termios().
# Saves approximately 1.8KB of flash when disabled (default). # Saves approximately 1.8KB of flash when disabled (default).
add_idf_sdkconfig_option( if CORE.data.get(KEY_VFS_TERMIOS_REQUIRED, False):
"CONFIG_VFS_SUPPORT_TERMIOS", not advanced[CONF_DISABLE_VFS_SUPPORT_TERMIOS] # Component requires VFS termios - force enable regardless of user setting
) add_idf_sdkconfig_option("CONFIG_VFS_SUPPORT_TERMIOS", True)
else:
# No component needs it - allow user to control (default: disabled)
add_idf_sdkconfig_option(
"CONFIG_VFS_SUPPORT_TERMIOS", not advanced[CONF_DISABLE_VFS_SUPPORT_TERMIOS]
)
# Disable VFS support for select() with file descriptors # Disable VFS support for select() with file descriptors
# ESPHome only uses select() with sockets via lwip_select(), which still works. # ESPHome only uses select() with sockets via lwip_select(), which still works.

View File

@@ -15,6 +15,7 @@ from esphome.components.esp32 import (
VARIANT_ESP32S2, VARIANT_ESP32S2,
VARIANT_ESP32S3, VARIANT_ESP32S3,
get_esp32_variant, get_esp32_variant,
include_builtin_idf_component,
) )
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
@@ -121,6 +122,10 @@ def get_default_tx_enqueue_timeout(bit_rate):
async def to_code(config): async def to_code(config):
# Legacy driver component provides driver/twai.h header
include_builtin_idf_component("driver")
# Also enable esp_driver_twai for future migration to new API
include_builtin_idf_component("esp_driver_twai")
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await canbus.register_canbus(var, config) await canbus.register_canbus(var, config)

View File

@@ -1,7 +1,12 @@
from esphome import pins from esphome import pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import output from esphome.components import output
from esphome.components.esp32 import VARIANT_ESP32, VARIANT_ESP32S2, get_esp32_variant from esphome.components.esp32 import (
VARIANT_ESP32,
VARIANT_ESP32S2,
get_esp32_variant,
include_builtin_idf_component,
)
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_NUMBER, CONF_PIN from esphome.const import CONF_ID, CONF_NUMBER, CONF_PIN
@@ -38,6 +43,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
async def to_code(config): async def to_code(config):
include_builtin_idf_component("esp_driver_dac")
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)
await output.register_output(var, config) await output.register_output(var, config)

View File

@@ -34,14 +34,29 @@ static const char *const ESP_HOSTED_VERSION_STR = STRINGIFY(ESP_HOSTED_VERSION_M
ESP_HOSTED_VERSION_MINOR_1) "." STRINGIFY(ESP_HOSTED_VERSION_PATCH_1); ESP_HOSTED_VERSION_MINOR_1) "." STRINGIFY(ESP_HOSTED_VERSION_PATCH_1);
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE #ifdef USE_ESP32_HOSTED_HTTP_UPDATE
// Parse an integer from str, advancing ptr past the number
// Returns false if no digits were parsed
static bool parse_int(const char *&ptr, int &value) {
char *end;
value = static_cast<int>(strtol(ptr, &end, 10));
if (end == ptr)
return false;
ptr = end;
return true;
}
// Parse version string "major.minor.patch" into components // Parse version string "major.minor.patch" into components
// Returns true if parsing succeeded // Returns true if at least major.minor was parsed
static bool parse_version(const std::string &version_str, int &major, int &minor, int &patch) { static bool parse_version(const std::string &version_str, int &major, int &minor, int &patch) {
major = minor = patch = 0; major = minor = patch = 0;
if (sscanf(version_str.c_str(), "%d.%d.%d", &major, &minor, &patch) >= 2) { const char *ptr = version_str.c_str();
return true;
} if (!parse_int(ptr, major) || *ptr++ != '.' || !parse_int(ptr, minor))
return false; return false;
if (*ptr == '.')
parse_int(++ptr, patch);
return true;
} }
// Compare two versions, returns: // Compare two versions, returns:

View File

@@ -269,6 +269,8 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config): async def to_code(config):
# Re-enable ESP-IDF's touch sensor driver (excluded by default to save compile time) # Re-enable ESP-IDF's touch sensor driver (excluded by default to save compile time)
include_builtin_idf_component("esp_driver_touch_sens") include_builtin_idf_component("esp_driver_touch_sens")
# Legacy driver component provides driver/touch_sensor.h header
include_builtin_idf_component("driver")
touch = cg.new_Pvariable(config[CONF_ID]) touch = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(touch, config) await cg.register_component(touch, config)

View File

@@ -1,6 +1,6 @@
import logging import logging
from esphome import pins from esphome import automation, pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.esp32 import ( from esphome.components.esp32 import (
VARIANT_ESP32, VARIANT_ESP32,
@@ -35,6 +35,8 @@ from esphome.const import (
CONF_MODE, CONF_MODE,
CONF_MOSI_PIN, CONF_MOSI_PIN,
CONF_NUMBER, CONF_NUMBER,
CONF_ON_CONNECT,
CONF_ON_DISCONNECT,
CONF_PAGE_ID, CONF_PAGE_ID,
CONF_PIN, CONF_PIN,
CONF_POLLING_INTERVAL, CONF_POLLING_INTERVAL,
@@ -237,6 +239,8 @@ BASE_SCHEMA = cv.Schema(
cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name,
cv.Optional(CONF_USE_ADDRESS): cv.string_strict, cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
cv.Optional(CONF_ON_CONNECT): automation.validate_automation(single=True),
cv.Optional(CONF_ON_DISCONNECT): automation.validate_automation(single=True),
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
@@ -430,6 +434,18 @@ async def to_code(config):
if CORE.using_arduino: if CORE.using_arduino:
cg.add_library("WiFi", None) cg.add_library("WiFi", None)
if on_connect_config := config.get(CONF_ON_CONNECT):
cg.add_define("USE_ETHERNET_CONNECT_TRIGGER")
await automation.build_automation(
var.get_connect_trigger(), [], on_connect_config
)
if on_disconnect_config := config.get(CONF_ON_DISCONNECT):
cg.add_define("USE_ETHERNET_DISCONNECT_TRIGGER")
await automation.build_automation(
var.get_disconnect_trigger(), [], on_disconnect_config
)
CORE.add_job(final_step) CORE.add_job(final_step)

View File

@@ -309,6 +309,9 @@ void EthernetComponent::loop() {
this->dump_connect_params_(); this->dump_connect_params_();
this->status_clear_warning(); this->status_clear_warning();
#ifdef USE_ETHERNET_CONNECT_TRIGGER
this->connect_trigger_.trigger();
#endif
} else if (now - this->connect_begin_ > 15000) { } else if (now - this->connect_begin_ > 15000) {
ESP_LOGW(TAG, "Connecting failed; reconnecting"); ESP_LOGW(TAG, "Connecting failed; reconnecting");
this->start_connect_(); this->start_connect_();
@@ -318,10 +321,16 @@ void EthernetComponent::loop() {
if (!this->started_) { if (!this->started_) {
ESP_LOGI(TAG, "Stopped connection"); ESP_LOGI(TAG, "Stopped connection");
this->state_ = EthernetComponentState::STOPPED; this->state_ = EthernetComponentState::STOPPED;
#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
this->disconnect_trigger_.trigger();
#endif
} else if (!this->connected_) { } else if (!this->connected_) {
ESP_LOGW(TAG, "Connection lost; reconnecting"); ESP_LOGW(TAG, "Connection lost; reconnecting");
this->state_ = EthernetComponentState::CONNECTING; this->state_ = EthernetComponentState::CONNECTING;
this->start_connect_(); this->start_connect_();
#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
this->disconnect_trigger_.trigger();
#endif
} else { } else {
this->finish_connect_(); this->finish_connect_();
// When connected and stable, disable the loop to save CPU cycles // When connected and stable, disable the loop to save CPU cycles

View File

@@ -4,6 +4,7 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/automation.h"
#include "esphome/components/network/ip_address.h" #include "esphome/components/network/ip_address.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
@@ -119,6 +120,12 @@ class EthernetComponent : public Component {
void add_ip_state_listener(EthernetIPStateListener *listener) { this->ip_state_listeners_.push_back(listener); } void add_ip_state_listener(EthernetIPStateListener *listener) { this->ip_state_listeners_.push_back(listener); }
#endif #endif
#ifdef USE_ETHERNET_CONNECT_TRIGGER
Trigger<> *get_connect_trigger() { return &this->connect_trigger_; }
#endif
#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
Trigger<> *get_disconnect_trigger() { return &this->disconnect_trigger_; }
#endif
protected: protected:
static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
static void got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); static void got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
@@ -190,6 +197,12 @@ class EthernetComponent : public Component {
StaticVector<EthernetIPStateListener *, ESPHOME_ETHERNET_IP_STATE_LISTENERS> ip_state_listeners_; StaticVector<EthernetIPStateListener *, ESPHOME_ETHERNET_IP_STATE_LISTENERS> ip_state_listeners_;
#endif #endif
#ifdef USE_ETHERNET_CONNECT_TRIGGER
Trigger<> connect_trigger_;
#endif
#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
Trigger<> disconnect_trigger_;
#endif
private: private:
// Stores a pointer to a string literal (static storage duration). // Stores a pointer to a string literal (static storage duration).
// ONLY set from Python-generated code with string literals - never dynamic strings. // ONLY set from Python-generated code with string literals - never dynamic strings.

View File

@@ -335,18 +335,18 @@ void FeedbackCover::start_direction_(CoverOperation dir) {
switch (dir) { switch (dir) {
case COVER_OPERATION_IDLE: case COVER_OPERATION_IDLE:
trig = this->stop_trigger_; trig = &this->stop_trigger_;
break; break;
case COVER_OPERATION_OPENING: case COVER_OPERATION_OPENING:
this->last_operation_ = dir; this->last_operation_ = dir;
trig = this->open_trigger_; trig = &this->open_trigger_;
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
obstacle = this->open_obstacle_; obstacle = this->open_obstacle_;
#endif #endif
break; break;
case COVER_OPERATION_CLOSING: case COVER_OPERATION_CLOSING:
this->last_operation_ = dir; this->last_operation_ = dir;
trig = this->close_trigger_; trig = &this->close_trigger_;
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
obstacle = this->close_obstacle_; obstacle = this->close_obstacle_;
#endif #endif

View File

@@ -17,9 +17,9 @@ class FeedbackCover : public cover::Cover, public Component {
void loop() override; void loop() override;
void dump_config() override; void dump_config() override;
Trigger<> *get_open_trigger() const { return this->open_trigger_; } Trigger<> *get_open_trigger() { return &this->open_trigger_; }
Trigger<> *get_close_trigger() const { return this->close_trigger_; } Trigger<> *get_close_trigger() { return &this->close_trigger_; }
Trigger<> *get_stop_trigger() const { return this->stop_trigger_; } Trigger<> *get_stop_trigger() { return &this->stop_trigger_; }
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
void set_open_endstop(binary_sensor::BinarySensor *open_endstop); void set_open_endstop(binary_sensor::BinarySensor *open_endstop);
@@ -61,9 +61,9 @@ class FeedbackCover : public cover::Cover, public Component {
binary_sensor::BinarySensor *close_obstacle_{nullptr}; binary_sensor::BinarySensor *close_obstacle_{nullptr};
#endif #endif
Trigger<> *open_trigger_{new Trigger<>()}; Trigger<> open_trigger_;
Trigger<> *close_trigger_{new Trigger<>()}; Trigger<> close_trigger_;
Trigger<> *stop_trigger_{new Trigger<>()}; Trigger<> stop_trigger_;
uint32_t open_duration_{0}; uint32_t open_duration_{0};
uint32_t close_duration_{0}; uint32_t close_duration_{0};

View File

@@ -332,13 +332,13 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
void set_json(std::function<void(Ts..., JsonObject)> json_func) { this->json_func_ = json_func; } void set_json(std::function<void(Ts..., JsonObject)> json_func) { this->json_func_ = json_func; }
#ifdef USE_HTTP_REQUEST_RESPONSE #ifdef USE_HTTP_REQUEST_RESPONSE
Trigger<std::shared_ptr<HttpContainer>, std::string &, Ts...> *get_success_trigger_with_response() const { Trigger<std::shared_ptr<HttpContainer>, std::string &, Ts...> *get_success_trigger_with_response() {
return this->success_trigger_with_response_; return &this->success_trigger_with_response_;
} }
#endif #endif
Trigger<std::shared_ptr<HttpContainer>, Ts...> *get_success_trigger() const { return this->success_trigger_; } Trigger<std::shared_ptr<HttpContainer>, Ts...> *get_success_trigger() { return &this->success_trigger_; }
Trigger<Ts...> *get_error_trigger() const { return this->error_trigger_; } Trigger<Ts...> *get_error_trigger() { return &this->error_trigger_; }
void set_max_response_buffer_size(size_t max_response_buffer_size) { void set_max_response_buffer_size(size_t max_response_buffer_size) {
this->max_response_buffer_size_ = max_response_buffer_size; this->max_response_buffer_size_ = max_response_buffer_size;
@@ -372,7 +372,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
auto captured_args = std::make_tuple(x...); auto captured_args = std::make_tuple(x...);
if (container == nullptr) { if (container == nullptr) {
std::apply([this](Ts... captured_args_inner) { this->error_trigger_->trigger(captured_args_inner...); }, std::apply([this](Ts... captured_args_inner) { this->error_trigger_.trigger(captured_args_inner...); },
captured_args); captured_args);
return; return;
} }
@@ -406,14 +406,14 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
} }
std::apply( std::apply(
[this, &container, &response_body](Ts... captured_args_inner) { [this, &container, &response_body](Ts... captured_args_inner) {
this->success_trigger_with_response_->trigger(container, response_body, captured_args_inner...); this->success_trigger_with_response_.trigger(container, response_body, captured_args_inner...);
}, },
captured_args); captured_args);
} else } else
#endif #endif
{ {
std::apply([this, &container]( std::apply([this, &container](
Ts... captured_args_inner) { this->success_trigger_->trigger(container, captured_args_inner...); }, Ts... captured_args_inner) { this->success_trigger_.trigger(container, captured_args_inner...); },
captured_args); captured_args);
} }
container->end(); container->end();
@@ -433,12 +433,10 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
std::map<const char *, TemplatableValue<std::string, Ts...>> json_{}; std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
std::function<void(Ts..., JsonObject)> json_func_{nullptr}; std::function<void(Ts..., JsonObject)> json_func_{nullptr};
#ifdef USE_HTTP_REQUEST_RESPONSE #ifdef USE_HTTP_REQUEST_RESPONSE
Trigger<std::shared_ptr<HttpContainer>, std::string &, Ts...> *success_trigger_with_response_ = Trigger<std::shared_ptr<HttpContainer>, std::string &, Ts...> success_trigger_with_response_;
new Trigger<std::shared_ptr<HttpContainer>, std::string &, Ts...>();
#endif #endif
Trigger<std::shared_ptr<HttpContainer>, Ts...> *success_trigger_ = Trigger<std::shared_ptr<HttpContainer>, Ts...> success_trigger_;
new Trigger<std::shared_ptr<HttpContainer>, Ts...>(); Trigger<Ts...> error_trigger_;
Trigger<Ts...> *error_trigger_ = new Trigger<Ts...>();
size_t max_response_buffer_size_{SIZE_MAX}; size_t max_response_buffer_size_{SIZE_MAX};
}; };

View File

@@ -11,12 +11,6 @@ namespace i2c {
static const char *const TAG = "i2c"; static const char *const TAG = "i2c";
void I2CBus::i2c_scan_() { void I2CBus::i2c_scan_() {
// suppress logs from the IDF I2C library during the scan
#if defined(USE_ESP32) && defined(USE_LOGGER)
auto previous = esp_log_level_get("*");
esp_log_level_set("*", ESP_LOG_NONE);
#endif
for (uint8_t address = 8; address != 120; address++) { for (uint8_t address = 8; address != 120; address++) {
auto err = write_readv(address, nullptr, 0, nullptr, 0); auto err = write_readv(address, nullptr, 0, nullptr, 0);
if (err == ERROR_OK) { if (err == ERROR_OK) {
@@ -27,9 +21,6 @@ void I2CBus::i2c_scan_() {
// it takes 16sec to scan on nrf52. It prevents board reset. // it takes 16sec to scan on nrf52. It prevents board reset.
arch_feed_wdt(); arch_feed_wdt();
} }
#if defined(USE_ESP32) && defined(USE_LOGGER)
esp_log_level_set("*", previous);
#endif
} }
ErrorCode I2CDevice::read_register(uint8_t a_register, uint8_t *data, size_t len) { ErrorCode I2CDevice::read_register(uint8_t a_register, uint8_t *data, size_t len) {

View File

@@ -138,20 +138,20 @@ class LambdaLightEffect : public LightEffect {
class AutomationLightEffect : public LightEffect { class AutomationLightEffect : public LightEffect {
public: public:
AutomationLightEffect(const char *name) : LightEffect(name) {} AutomationLightEffect(const char *name) : LightEffect(name) {}
void stop() override { this->trig_->stop_action(); } void stop() override { this->trig_.stop_action(); }
void apply() override { void apply() override {
if (!this->trig_->is_action_running()) { if (!this->trig_.is_action_running()) {
this->trig_->trigger(); this->trig_.trigger();
} }
} }
Trigger<> *get_trig() const { return trig_; } Trigger<> *get_trig() { return &this->trig_; }
/// Get the current effect index for use in automations. /// Get the current effect index for use in automations.
/// Useful for automations that need to know which effect is running. /// Useful for automations that need to know which effect is running.
uint32_t get_current_index() const { return this->get_index(); } uint32_t get_current_index() const { return this->get_index(); }
protected: protected:
Trigger<> *trig_{new Trigger<>}; Trigger<> trig_;
}; };
struct StrobeLightEffectColor { struct StrobeLightEffectColor {

View File

@@ -16,6 +16,8 @@ from esphome.components.esp32 import (
VARIANT_ESP32S3, VARIANT_ESP32S3,
add_idf_sdkconfig_option, add_idf_sdkconfig_option,
get_esp32_variant, get_esp32_variant,
require_usb_serial_jtag_secondary,
require_vfs_termios,
) )
from esphome.components.libretiny import get_libretiny_component, get_libretiny_family from esphome.components.libretiny import get_libretiny_component, get_libretiny_family
from esphome.components.libretiny.const import ( from esphome.components.libretiny.const import (
@@ -397,9 +399,15 @@ async def to_code(config):
elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG: elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG:
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG", True) add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG", True)
cg.add_define("USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG") cg.add_define("USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG")
# Define platform support flags for components that need auto-detection
try: try:
uart_selection(USB_SERIAL_JTAG) uart_selection(USB_SERIAL_JTAG)
cg.add_define("USE_LOGGER_USB_SERIAL_JTAG") cg.add_define("USE_LOGGER_USB_SERIAL_JTAG")
# USB Serial JTAG code is compiled when platform supports it.
# Enable secondary USB serial JTAG console so the VFS functions are available.
if CORE.is_esp32 and config[CONF_HARDWARE_UART] != USB_SERIAL_JTAG:
require_usb_serial_jtag_secondary()
require_vfs_termios()
except cv.Invalid: except cv.Invalid:
pass pass
try: try:

View File

@@ -128,22 +128,7 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266. // Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
// //
// This function handles format strings stored in flash memory (PROGMEM) to save RAM. // This function handles format strings stored in flash memory (PROGMEM) to save RAM.
// The buffer is used in a special way to avoid allocating extra memory: // Uses vsnprintf_P to read the format string directly from flash without copying to RAM.
//
// Memory layout during execution:
// Step 1: Copy format string from flash to buffer
// tx_buffer_: [format_string][null][.....................]
// tx_buffer_at_: ------------------^
// msg_start: saved here -----------^
//
// Step 2: format_log_to_buffer_with_terminator_ reads format string from beginning
// and writes formatted output starting at msg_start position
// tx_buffer_: [format_string][null][formatted_message][null]
// tx_buffer_at_: -------------------------------------^
//
// Step 3: Output the formatted message (starting at msg_start)
// write_msg_ and callbacks receive: this->tx_buffer_ + msg_start
// which points to: [formatted_message][null]
// //
void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format, void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
va_list args) { // NOLINT va_list args) { // NOLINT
@@ -153,35 +138,25 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
RecursionGuard guard(global_recursion_guard_); RecursionGuard guard(global_recursion_guard_);
this->tx_buffer_at_ = 0; this->tx_buffer_at_ = 0;
// Copy format string from progmem // Write header, format body directly from flash, and write footer
auto *format_pgm_p = reinterpret_cast<const uint8_t *>(format); this->write_header_to_buffer_(level, tag, line, nullptr, this->tx_buffer_, &this->tx_buffer_at_,
char ch = '.'; this->tx_buffer_size_);
while (this->tx_buffer_at_ < this->tx_buffer_size_ && ch != '\0') { this->format_body_to_buffer_P_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_,
this->tx_buffer_[this->tx_buffer_at_++] = ch = (char) progmem_read_byte(format_pgm_p++); reinterpret_cast<PGM_P>(format), args);
} this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_);
// Buffer full from copying format - RAII guard handles cleanup on return // Ensure null termination
if (this->tx_buffer_at_ >= this->tx_buffer_size_) { uint16_t null_pos = this->tx_buffer_at_ >= this->tx_buffer_size_ ? this->tx_buffer_size_ - 1 : this->tx_buffer_at_;
return; this->tx_buffer_[null_pos] = '\0';
}
// Save the offset before calling format_log_to_buffer_with_terminator_
// since it will increment tx_buffer_at_ to the end of the formatted string
uint16_t msg_start = this->tx_buffer_at_;
this->format_log_to_buffer_with_terminator_(level, tag, line, this->tx_buffer_, args, this->tx_buffer_,
&this->tx_buffer_at_, this->tx_buffer_size_);
uint16_t msg_length =
this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position
// Listeners get message first (before console write) // Listeners get message first (before console write)
#ifdef USE_LOG_LISTENERS #ifdef USE_LOG_LISTENERS
for (auto *listener : this->log_listeners_) for (auto *listener : this->log_listeners_)
listener->on_log(level, tag, this->tx_buffer_ + msg_start, msg_length); listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_);
#endif #endif
// Write to console starting at the msg_start // Write to console
this->write_tx_buffer_to_console_(msg_start, &msg_length); this->write_tx_buffer_to_console_();
} }
#endif // USE_STORE_LOG_STR_IN_FLASH #endif // USE_STORE_LOG_STR_IN_FLASH

View File

@@ -597,31 +597,40 @@ class Logger : public Component {
*buffer_at = pos; *buffer_at = pos;
} }
// Helper to process vsnprintf return value and strip trailing newlines.
// Updates buffer_at with the formatted length, handling truncation:
// - When vsnprintf truncates (ret >= remaining), it writes (remaining - 1) chars + null terminator
// - When it doesn't truncate (ret < remaining), it writes ret chars + null terminator
__attribute__((always_inline)) static inline void process_vsnprintf_result(const char *buffer, uint16_t *buffer_at,
uint16_t remaining, int ret) {
if (ret < 0)
return; // Encoding error, do not increment buffer_at
*buffer_at += (ret >= remaining) ? (remaining - 1) : static_cast<uint16_t>(ret);
// Remove all trailing newlines right after formatting
while (*buffer_at > 0 && buffer[*buffer_at - 1] == '\n')
(*buffer_at)--;
}
inline void HOT format_body_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format, inline void HOT format_body_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format,
va_list args) { va_list args) {
// Get remaining capacity in the buffer // Check remaining capacity in the buffer
if (*buffer_at >= buffer_size) if (*buffer_at >= buffer_size)
return; return;
const uint16_t remaining = buffer_size - *buffer_at; const uint16_t remaining = buffer_size - *buffer_at;
process_vsnprintf_result(buffer, buffer_at, remaining, vsnprintf(buffer + *buffer_at, remaining, format, args));
const int ret = vsnprintf(buffer + *buffer_at, remaining, format, args);
if (ret < 0) {
return; // Encoding error, do not increment buffer_at
}
// Update buffer_at with the formatted length (handle truncation)
// When vsnprintf truncates (ret >= remaining), it writes (remaining - 1) chars + null terminator
// When it doesn't truncate (ret < remaining), it writes ret chars + null terminator
uint16_t formatted_len = (ret >= remaining) ? (remaining - 1) : ret;
*buffer_at += formatted_len;
// Remove all trailing newlines right after formatting
while (*buffer_at > 0 && buffer[*buffer_at - 1] == '\n') {
(*buffer_at)--;
}
} }
#ifdef USE_STORE_LOG_STR_IN_FLASH
// ESP8266 variant that reads format string directly from flash using vsnprintf_P
inline void HOT format_body_to_buffer_P_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, PGM_P format,
va_list args) {
if (*buffer_at >= buffer_size)
return;
const uint16_t remaining = buffer_size - *buffer_at;
process_vsnprintf_result(buffer, buffer_at, remaining, vsnprintf_P(buffer + *buffer_at, remaining, format, args));
}
#endif
inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1; static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1;
this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size); this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size);

View File

@@ -114,9 +114,6 @@ void Logger::pre_setup() {
global_logger = this; global_logger = this;
esp_log_set_vprintf(esp_idf_log_vprintf_); esp_log_set_vprintf(esp_idf_log_vprintf_);
if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) {
esp_log_level_set("*", ESP_LOG_VERBOSE);
}
ESP_LOGI(TAG, "Log initialized"); ESP_LOGI(TAG, "Log initialized");
} }

View File

@@ -28,11 +28,10 @@ CONFIG_SCHEMA = (
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID], config[CONF_NUM_CHIPS])
await spi.register_spi_device(var, config, write_only=True) await spi.register_spi_device(var, config, write_only=True)
await display.register_display(var, config) await display.register_display(var, config)
cg.add(var.set_num_chips(config[CONF_NUM_CHIPS]))
cg.add(var.set_intensity(config[CONF_INTENSITY])) cg.add(var.set_intensity(config[CONF_INTENSITY]))
cg.add(var.set_reverse(config[CONF_REVERSE_ENABLE])) cg.add(var.set_reverse(config[CONF_REVERSE_ENABLE]))

View File

@@ -3,8 +3,7 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome { namespace esphome::max7219 {
namespace max7219 {
static const char *const TAG = "max7219"; static const char *const TAG = "max7219";
@@ -115,12 +114,14 @@ const uint8_t MAX7219_ASCII_TO_RAW[95] PROGMEM = {
}; };
float MAX7219Component::get_setup_priority() const { return setup_priority::PROCESSOR; } float MAX7219Component::get_setup_priority() const { return setup_priority::PROCESSOR; }
MAX7219Component::MAX7219Component(uint8_t num_chips) : num_chips_(num_chips) {
this->buffer_ = new uint8_t[this->num_chips_ * 8]; // NOLINT
memset(this->buffer_, 0, this->num_chips_ * 8);
}
void MAX7219Component::setup() { void MAX7219Component::setup() {
this->spi_setup(); this->spi_setup();
this->buffer_ = new uint8_t[this->num_chips_ * 8]; // NOLINT
for (uint8_t i = 0; i < this->num_chips_ * 8; i++)
this->buffer_[i] = 0;
// let's assume the user has all 8 digits connected, only important in daisy chained setups anyway // let's assume the user has all 8 digits connected, only important in daisy chained setups anyway
this->send_to_all_(MAX7219_REGISTER_SCAN_LIMIT, 7); this->send_to_all_(MAX7219_REGISTER_SCAN_LIMIT, 7);
// let's use our own ASCII -> led pattern encoding // let's use our own ASCII -> led pattern encoding
@@ -229,7 +230,6 @@ void MAX7219Component::set_intensity(uint8_t intensity) {
this->intensity_ = intensity; this->intensity_ = intensity;
} }
} }
void MAX7219Component::set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; }
uint8_t MAX7219Component::strftime(uint8_t pos, const char *format, ESPTime time) { uint8_t MAX7219Component::strftime(uint8_t pos, const char *format, ESPTime time) {
char buffer[64]; char buffer[64];
@@ -240,5 +240,4 @@ uint8_t MAX7219Component::strftime(uint8_t pos, const char *format, ESPTime time
} }
uint8_t MAX7219Component::strftime(const char *format, ESPTime time) { return this->strftime(0, format, time); } uint8_t MAX7219Component::strftime(const char *format, ESPTime time) { return this->strftime(0, format, time); }
} // namespace max7219 } // namespace esphome::max7219
} // namespace esphome

View File

@@ -6,8 +6,7 @@
#include "esphome/components/spi/spi.h" #include "esphome/components/spi/spi.h"
#include "esphome/components/display/display.h" #include "esphome/components/display/display.h"
namespace esphome { namespace esphome::max7219 {
namespace max7219 {
class MAX7219Component; class MAX7219Component;
@@ -17,6 +16,8 @@ class MAX7219Component : public PollingComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> { spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
public: public:
explicit MAX7219Component(uint8_t num_chips);
void set_writer(max7219_writer_t &&writer); void set_writer(max7219_writer_t &&writer);
void setup() override; void setup() override;
@@ -30,7 +31,6 @@ class MAX7219Component : public PollingComponent,
void display(); void display();
void set_intensity(uint8_t intensity); void set_intensity(uint8_t intensity);
void set_num_chips(uint8_t num_chips);
void set_reverse(bool reverse) { this->reverse_ = reverse; }; void set_reverse(bool reverse) { this->reverse_ = reverse; };
/// Evaluate the printf-format and print the result at the given position. /// Evaluate the printf-format and print the result at the given position.
@@ -56,10 +56,9 @@ class MAX7219Component : public PollingComponent,
uint8_t intensity_{15}; // Intensity of the display from 0 to 15 (most) uint8_t intensity_{15}; // Intensity of the display from 0 to 15 (most)
bool intensity_changed_{}; // True if we need to re-send the intensity bool intensity_changed_{}; // True if we need to re-send the intensity
uint8_t num_chips_{1}; uint8_t num_chips_{1};
uint8_t *buffer_; uint8_t *buffer_{nullptr};
bool reverse_{false}; bool reverse_{false};
max7219_writer_t writer_{}; max7219_writer_t writer_{};
}; };
} // namespace max7219 } // namespace esphome::max7219
} // namespace esphome

View File

@@ -325,7 +325,7 @@ void MicroWakeWord::loop() {
ESP_LOGD(TAG, "Detected '%s' with sliding average probability is %.2f and max probability is %.2f", ESP_LOGD(TAG, "Detected '%s' with sliding average probability is %.2f and max probability is %.2f",
detection_event.wake_word->c_str(), (detection_event.average_probability / uint8_to_float_divisor), detection_event.wake_word->c_str(), (detection_event.average_probability / uint8_to_float_divisor),
(detection_event.max_probability / uint8_to_float_divisor)); (detection_event.max_probability / uint8_to_float_divisor));
this->wake_word_detected_trigger_->trigger(*detection_event.wake_word); this->wake_word_detected_trigger_.trigger(*detection_event.wake_word);
if (this->stop_after_detection_) { if (this->stop_after_detection_) {
this->stop(); this->stop();
} }

View File

@@ -60,7 +60,7 @@ class MicroWakeWord : public Component
void set_stop_after_detection(bool stop_after_detection) { this->stop_after_detection_ = stop_after_detection; } void set_stop_after_detection(bool stop_after_detection) { this->stop_after_detection_ = stop_after_detection; }
Trigger<std::string> *get_wake_word_detected_trigger() const { return this->wake_word_detected_trigger_; } Trigger<std::string> *get_wake_word_detected_trigger() { return &this->wake_word_detected_trigger_; }
void add_wake_word_model(WakeWordModel *model); void add_wake_word_model(WakeWordModel *model);
@@ -78,7 +78,7 @@ class MicroWakeWord : public Component
protected: protected:
microphone::MicrophoneSource *microphone_source_{nullptr}; microphone::MicrophoneSource *microphone_source_{nullptr};
Trigger<std::string> *wake_word_detected_trigger_ = new Trigger<std::string>(); Trigger<std::string> wake_word_detected_trigger_;
State state_{State::STOPPED}; State state_{State::STOPPED};
std::weak_ptr<RingBuffer> ring_buffer_; std::weak_ptr<RingBuffer> ring_buffer_;

View File

@@ -1,6 +1,39 @@
#include "mipi_spi.h" #include "mipi_spi.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome { namespace esphome::mipi_spi {
namespace mipi_spi {} // namespace mipi_spi
} // namespace esphome void internal_dump_config(const char *model, int width, int height, int offset_width, int offset_height, uint8_t madctl,
bool invert_colors, int display_bits, bool is_big_endian, const optional<uint8_t> &brightness,
GPIOPin *cs, GPIOPin *reset, GPIOPin *dc, int spi_mode, uint32_t data_rate, int bus_width) {
ESP_LOGCONFIG(TAG,
"MIPI_SPI Display\n"
" Model: %s\n"
" Width: %d\n"
" Height: %d\n"
" Swap X/Y: %s\n"
" Mirror X: %s\n"
" Mirror Y: %s\n"
" Invert colors: %s\n"
" Color order: %s\n"
" Display pixels: %d bits\n"
" Endianness: %s\n"
" SPI Mode: %d\n"
" SPI Data rate: %uMHz\n"
" SPI Bus width: %d",
model, width, height, YESNO(madctl & MADCTL_MV), YESNO(madctl & (MADCTL_MX | MADCTL_XFLIP)),
YESNO(madctl & (MADCTL_MY | MADCTL_YFLIP)), YESNO(invert_colors), (madctl & MADCTL_BGR) ? "BGR" : "RGB",
display_bits, is_big_endian ? "Big" : "Little", spi_mode, static_cast<unsigned>(data_rate / 1000000),
bus_width);
LOG_PIN(" CS Pin: ", cs);
LOG_PIN(" Reset Pin: ", reset);
LOG_PIN(" DC Pin: ", dc);
if (offset_width != 0)
ESP_LOGCONFIG(TAG, " Offset width: %d", offset_width);
if (offset_height != 0)
ESP_LOGCONFIG(TAG, " Offset height: %d", offset_height);
if (brightness.has_value())
ESP_LOGCONFIG(TAG, " Brightness: %u", brightness.value());
}
} // namespace esphome::mipi_spi

View File

@@ -63,6 +63,11 @@ enum BusType {
BUS_TYPE_SINGLE_16 = 16, // Single bit bus, but 16 bits per transfer BUS_TYPE_SINGLE_16 = 16, // Single bit bus, but 16 bits per transfer
}; };
// Helper function for dump_config - defined in mipi_spi.cpp to allow use of LOG_PIN macro
void internal_dump_config(const char *model, int width, int height, int offset_width, int offset_height, uint8_t madctl,
bool invert_colors, int display_bits, bool is_big_endian, const optional<uint8_t> &brightness,
GPIOPin *cs, GPIOPin *reset, GPIOPin *dc, int spi_mode, uint32_t data_rate, int bus_width);
/** /**
* Base class for MIPI SPI displays. * Base class for MIPI SPI displays.
* All the methods are defined here in the header file, as it is not possible to define templated methods in a cpp file. * All the methods are defined here in the header file, as it is not possible to define templated methods in a cpp file.
@@ -201,37 +206,9 @@ class MipiSpi : public display::Display,
} }
void dump_config() override { void dump_config() override {
esph_log_config(TAG, internal_dump_config(this->model_, WIDTH, HEIGHT, OFFSET_WIDTH, OFFSET_HEIGHT, this->madctl_, this->invert_colors_,
"MIPI_SPI Display\n" DISPLAYPIXEL * 8, IS_BIG_ENDIAN, this->brightness_, this->cs_, this->reset_pin_, this->dc_pin_,
" Model: %s\n" this->mode_, this->data_rate_, BUS_TYPE);
" Width: %u\n"
" Height: %u",
this->model_, WIDTH, HEIGHT);
if constexpr (OFFSET_WIDTH != 0)
esph_log_config(TAG, " Offset width: %u", OFFSET_WIDTH);
if constexpr (OFFSET_HEIGHT != 0)
esph_log_config(TAG, " Offset height: %u", OFFSET_HEIGHT);
esph_log_config(TAG,
" Swap X/Y: %s\n"
" Mirror X: %s\n"
" Mirror Y: %s\n"
" Invert colors: %s\n"
" Color order: %s\n"
" Display pixels: %d bits\n"
" Endianness: %s\n",
YESNO(this->madctl_ & MADCTL_MV), YESNO(this->madctl_ & (MADCTL_MX | MADCTL_XFLIP)),
YESNO(this->madctl_ & (MADCTL_MY | MADCTL_YFLIP)), YESNO(this->invert_colors_),
this->madctl_ & MADCTL_BGR ? "BGR" : "RGB", DISPLAYPIXEL * 8, IS_BIG_ENDIAN ? "Big" : "Little");
if (this->brightness_.has_value())
esph_log_config(TAG, " Brightness: %u", this->brightness_.value());
log_pin(TAG, " CS Pin: ", this->cs_);
log_pin(TAG, " Reset Pin: ", this->reset_pin_);
log_pin(TAG, " DC Pin: ", this->dc_pin_);
esph_log_config(TAG,
" SPI Mode: %d\n"
" SPI Data rate: %dMHz\n"
" SPI Bus width: %d",
this->mode_, static_cast<unsigned>(this->data_rate_ / 1000000), BUS_TYPE);
} }
protected: protected:

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "esphome/core/automation.h"
#include "mixer_speaker.h" #include "mixer_speaker.h"
#ifdef USE_ESP32 #ifdef USE_ESP32

View File

@@ -139,7 +139,8 @@ class MQTTBackendESP32 final : public MQTTBackend {
this->lwt_retain_ = retain; this->lwt_retain_ = retain;
} }
void set_server(network::IPAddress ip, uint16_t port) final { void set_server(network::IPAddress ip, uint16_t port) final {
this->host_ = ip.str(); char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
this->host_ = ip.str_to(ip_buf);
this->port_ = port; this->port_ = port;
} }
void set_server(const char *host, uint16_t port) final { void set_server(const char *host, uint16_t port) final {

View File

@@ -643,10 +643,34 @@ static bool topic_match(const char *message, const char *subscription) {
} }
void MQTTClientComponent::on_message(const std::string &topic, const std::string &payload) { void MQTTClientComponent::on_message(const std::string &topic, const std::string &payload) {
for (auto &subscription : this->subscriptions_) { #ifdef USE_ESP8266
if (topic_match(topic.c_str(), subscription.topic.c_str())) // IMPORTANT: This defer is REQUIRED to prevent stack overflow crashes on ESP8266.
subscription.callback(topic, payload); //
} // On ESP8266, this callback is invoked directly from the lwIP/AsyncTCP network stack
// which runs in the "sys" context with a very limited stack (~4KB). By the time we
// reach this function, the stack is already partially consumed by the network
// processing chain: tcp_input -> AsyncClient::_recv -> AsyncMqttClient::_onMessage -> here.
//
// MQTT subscription callbacks can trigger arbitrary user actions (automations, HTTP
// requests, sensor updates, etc.) which may have deep call stacks of their own.
// For example, an HTTP request action requires: DNS lookup -> TCP connect -> TLS
// handshake (if HTTPS) -> request formatting. This easily overflows the remaining
// system stack space, causing a LoadStoreAlignmentCause exception or silent corruption.
//
// By deferring to the main loop, we ensure callbacks execute with a fresh, full-size
// stack in the normal application context rather than the constrained network task.
//
// DO NOT REMOVE THIS DEFER without understanding the above. It may appear to work
// in simple tests but will cause crashes with complex automations.
this->defer([this, topic, payload]() {
#endif
for (auto &subscription : this->subscriptions_) {
if (topic_match(topic.c_str(), subscription.topic.c_str()))
subscription.callback(topic, payload);
}
#ifdef USE_ESP8266
});
#endif
} }
// Setters // Setters

View File

@@ -4,6 +4,7 @@ from esphome.components.esp32 import (
VARIANT_ESP32C6, VARIANT_ESP32C6,
VARIANT_ESP32H2, VARIANT_ESP32H2,
add_idf_sdkconfig_option, add_idf_sdkconfig_option,
include_builtin_idf_component,
only_on_variant, only_on_variant,
require_vfs_select, require_vfs_select,
) )
@@ -172,6 +173,9 @@ FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config): async def to_code(config):
# Re-enable openthread IDF component (excluded by default)
include_builtin_idf_component("openthread")
cg.add_define("USE_OPENTHREAD") cg.add_define("USE_OPENTHREAD")
# OpenThread SRP needs access to mDNS services after setup # OpenThread SRP needs access to mDNS services after setup

View File

@@ -2,21 +2,20 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
namespace esphome { namespace esphome::pmsx003 {
namespace pmsx003 {
static const char *const TAG = "pmsx003"; static const char *const TAG = "pmsx003";
static const uint8_t START_CHARACTER_1 = 0x42; static const uint8_t START_CHARACTER_1 = 0x42;
static const uint8_t START_CHARACTER_2 = 0x4D; static const uint8_t START_CHARACTER_2 = 0x4D;
static const uint16_t PMS_STABILISING_MS = 30000; // time taken for the sensor to become stable after power on in ms static const uint16_t STABILISING_MS = 30000; // time taken for the sensor to become stable after power on in ms
static const uint16_t PMS_CMD_MEASUREMENT_MODE_PASSIVE = static const uint16_t CMD_MEASUREMENT_MODE_PASSIVE =
0x0000; // use `PMS_CMD_MANUAL_MEASUREMENT` to trigger a measurement 0x0000; // use `Command::MANUAL_MEASUREMENT` to trigger a measurement
static const uint16_t PMS_CMD_MEASUREMENT_MODE_ACTIVE = 0x0001; // automatically perform measurements static const uint16_t CMD_MEASUREMENT_MODE_ACTIVE = 0x0001; // automatically perform measurements
static const uint16_t PMS_CMD_SLEEP_MODE_SLEEP = 0x0000; // go to sleep mode static const uint16_t CMD_SLEEP_MODE_SLEEP = 0x0000; // go to sleep mode
static const uint16_t PMS_CMD_SLEEP_MODE_WAKEUP = 0x0001; // wake up from sleep mode static const uint16_t CMD_SLEEP_MODE_WAKEUP = 0x0001; // wake up from sleep mode
void PMSX003Component::setup() {} void PMSX003Component::setup() {}
@@ -42,7 +41,7 @@ void PMSX003Component::dump_config() {
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
if (this->update_interval_ <= PMS_STABILISING_MS) { if (this->update_interval_ <= STABILISING_MS) {
ESP_LOGCONFIG(TAG, " Mode: active continuous (sensor default)"); ESP_LOGCONFIG(TAG, " Mode: active continuous (sensor default)");
} else { } else {
ESP_LOGCONFIG(TAG, " Mode: passive with sleep/wake cycles"); ESP_LOGCONFIG(TAG, " Mode: passive with sleep/wake cycles");
@@ -55,44 +54,44 @@ void PMSX003Component::loop() {
const uint32_t now = App.get_loop_component_start_time(); const uint32_t now = App.get_loop_component_start_time();
// Initialize sensor mode on first loop // Initialize sensor mode on first loop
if (this->initialised_ == 0) { if (!this->initialised_) {
if (this->update_interval_ > PMS_STABILISING_MS) { if (this->update_interval_ > STABILISING_MS) {
// Long update interval: use passive mode with sleep/wake cycles // Long update interval: use passive mode with sleep/wake cycles
this->send_command_(PMS_CMD_MEASUREMENT_MODE, PMS_CMD_MEASUREMENT_MODE_PASSIVE); this->send_command_(Command::MEASUREMENT_MODE, CMD_MEASUREMENT_MODE_PASSIVE);
this->send_command_(PMS_CMD_SLEEP_MODE, PMS_CMD_SLEEP_MODE_WAKEUP); this->send_command_(Command::SLEEP_MODE, CMD_SLEEP_MODE_WAKEUP);
} else { } else {
// Short/zero update interval: use active continuous mode // Short/zero update interval: use active continuous mode
this->send_command_(PMS_CMD_MEASUREMENT_MODE, PMS_CMD_MEASUREMENT_MODE_ACTIVE); this->send_command_(Command::MEASUREMENT_MODE, CMD_MEASUREMENT_MODE_ACTIVE);
} }
this->initialised_ = 1; this->initialised_ = true;
} }
// If we update less often than it takes the device to stabilise, spin the fan down // If we update less often than it takes the device to stabilise, spin the fan down
// rather than running it constantly. It does take some time to stabilise, so we // rather than running it constantly. It does take some time to stabilise, so we
// need to keep track of what state we're in. // need to keep track of what state we're in.
if (this->update_interval_ > PMS_STABILISING_MS) { if (this->update_interval_ > STABILISING_MS) {
switch (this->state_) { switch (this->state_) {
case PMSX003_STATE_IDLE: case State::IDLE:
// Power on the sensor now so it'll be ready when we hit the update time // Power on the sensor now so it'll be ready when we hit the update time
if (now - this->last_update_ < (this->update_interval_ - PMS_STABILISING_MS)) if (now - this->last_update_ < (this->update_interval_ - STABILISING_MS))
return; return;
this->state_ = PMSX003_STATE_STABILISING; this->state_ = State::STABILISING;
this->send_command_(PMS_CMD_SLEEP_MODE, PMS_CMD_SLEEP_MODE_WAKEUP); this->send_command_(Command::SLEEP_MODE, CMD_SLEEP_MODE_WAKEUP);
this->fan_on_time_ = now; this->fan_on_time_ = now;
return; return;
case PMSX003_STATE_STABILISING: case State::STABILISING:
// wait for the sensor to be stable // wait for the sensor to be stable
if (now - this->fan_on_time_ < PMS_STABILISING_MS) if (now - this->fan_on_time_ < STABILISING_MS)
return; return;
// consume any command responses that are in the serial buffer // consume any command responses that are in the serial buffer
while (this->available()) while (this->available())
this->read_byte(&this->data_[0]); this->read_byte(&this->data_[0]);
// Trigger a new read // Trigger a new read
this->send_command_(PMS_CMD_MANUAL_MEASUREMENT, 0); this->send_command_(Command::MANUAL_MEASUREMENT, 0);
this->state_ = PMSX003_STATE_WAITING; this->state_ = State::WAITING;
break; break;
case PMSX003_STATE_WAITING: case State::WAITING:
// Just go ahead and read stuff // Just go ahead and read stuff
break; break;
} }
@@ -180,27 +179,31 @@ optional<bool> PMSX003Component::check_byte_() {
} }
bool PMSX003Component::check_payload_length_(uint16_t payload_length) { bool PMSX003Component::check_payload_length_(uint16_t payload_length) {
// https://avaldebe.github.io/PyPMS/sensors/Plantower/
switch (this->type_) { switch (this->type_) {
case PMSX003_TYPE_X003: case Type::PMS1003:
// The expected payload length is typically 28 bytes. return payload_length == 28; // 2*13+2
// However, a 20-byte payload check was already present in the code. case Type::PMS3003: // Data 7/8/9 not set/reserved
// No official documentation was found confirming this. return payload_length == 20; // 2*9+2
// Retaining this check to avoid breaking existing behavior. case Type::PMSX003: // Data 13 not set/reserved
// Deprecated: Length 20 is for PMS3003 backwards compatibility
return payload_length == 28 || payload_length == 20; // 2*13+2 return payload_length == 28 || payload_length == 20; // 2*13+2
case PMSX003_TYPE_5003T: case Type::PMS5003S:
case PMSX003_TYPE_5003S: case Type::PMS5003T: // Data 13 not set/reserved
return payload_length == 28; // 2*13+2 (Data 13 not set/reserved) return payload_length == 28; // 2*13+2
case PMSX003_TYPE_5003ST: case Type::PMS5003ST: // Data 16 not set/reserved
return payload_length == 36; // 2*17+2 (Data 16 not set/reserved) return payload_length == 36; // 2*17+2
case Type::PMS9003M:
return payload_length == 28; // 2*13+2
} }
return false; return false;
} }
void PMSX003Component::send_command_(PMSX0003Command cmd, uint16_t data) { void PMSX003Component::send_command_(Command cmd, uint16_t data) {
uint8_t send_data[7] = { uint8_t send_data[7] = {
START_CHARACTER_1, // Start Byte 1 START_CHARACTER_1, // Start Byte 1
START_CHARACTER_2, // Start Byte 2 START_CHARACTER_2, // Start Byte 2
cmd, // Command static_cast<uint8_t>(cmd), // Command
uint8_t((data >> 8) & 0xFF), // Data 1 uint8_t((data >> 8) & 0xFF), // Data 1
uint8_t((data >> 0) & 0xFF), // Data 2 uint8_t((data >> 0) & 0xFF), // Data 2
0, // Verify Byte 1 0, // Verify Byte 1
@@ -265,7 +268,7 @@ void PMSX003Component::parse_data_() {
if (this->pm_particles_25um_sensor_ != nullptr) if (this->pm_particles_25um_sensor_ != nullptr)
this->pm_particles_25um_sensor_->publish_state(pm_particles_25um); this->pm_particles_25um_sensor_->publish_state(pm_particles_25um);
if (this->type_ == PMSX003_TYPE_5003T) { if (this->type_ == Type::PMS5003T) {
ESP_LOGD(TAG, ESP_LOGD(TAG,
"Got PM0.3 Particles: %u Count/0.1L, PM0.5 Particles: %u Count/0.1L, PM1.0 Particles: %u Count/0.1L, " "Got PM0.3 Particles: %u Count/0.1L, PM0.5 Particles: %u Count/0.1L, PM1.0 Particles: %u Count/0.1L, "
"PM2.5 Particles %u Count/0.1L", "PM2.5 Particles %u Count/0.1L",
@@ -289,7 +292,7 @@ void PMSX003Component::parse_data_() {
} }
// Formaldehyde // Formaldehyde
if (this->type_ == PMSX003_TYPE_5003ST || this->type_ == PMSX003_TYPE_5003S) { if (this->type_ == Type::PMS5003S || this->type_ == Type::PMS5003ST) {
const uint16_t formaldehyde = this->get_16_bit_uint_(28); const uint16_t formaldehyde = this->get_16_bit_uint_(28);
ESP_LOGD(TAG, "Got Formaldehyde: %u µg/m^3", formaldehyde); ESP_LOGD(TAG, "Got Formaldehyde: %u µg/m^3", formaldehyde);
@@ -299,8 +302,8 @@ void PMSX003Component::parse_data_() {
} }
// Temperature and Humidity // Temperature and Humidity
if (this->type_ == PMSX003_TYPE_5003ST || this->type_ == PMSX003_TYPE_5003T) { if (this->type_ == Type::PMS5003T || this->type_ == Type::PMS5003ST) {
const uint8_t temperature_offset = (this->type_ == PMSX003_TYPE_5003T) ? 24 : 30; const uint8_t temperature_offset = (this->type_ == Type::PMS5003T) ? 24 : 30;
const float temperature = static_cast<int16_t>(this->get_16_bit_uint_(temperature_offset)) / 10.0f; const float temperature = static_cast<int16_t>(this->get_16_bit_uint_(temperature_offset)) / 10.0f;
const float humidity = this->get_16_bit_uint_(temperature_offset + 2) / 10.0f; const float humidity = this->get_16_bit_uint_(temperature_offset + 2) / 10.0f;
@@ -314,22 +317,22 @@ void PMSX003Component::parse_data_() {
} }
// Firmware Version and Error Code // Firmware Version and Error Code
if (this->type_ == PMSX003_TYPE_5003ST) { if (this->type_ == Type::PMS1003 || this->type_ == Type::PMS5003ST || this->type_ == Type::PMS9003M) {
const uint8_t firmware_version = this->data_[36]; const uint8_t firmware_error_code_offset = (this->type_ == Type::PMS5003ST) ? 36 : 28;
const uint8_t error_code = this->data_[37]; const uint8_t firmware_version = this->data_[firmware_error_code_offset];
const uint8_t error_code = this->data_[firmware_error_code_offset + 1];
ESP_LOGD(TAG, "Got Firmware Version: 0x%02X, Error Code: 0x%02X", firmware_version, error_code); ESP_LOGD(TAG, "Got Firmware Version: 0x%02X, Error Code: 0x%02X", firmware_version, error_code);
} }
// Spin down the sensor again if we aren't going to need it until more time has // Spin down the sensor again if we aren't going to need it until more time has
// passed than it takes to stabilise // passed than it takes to stabilise
if (this->update_interval_ > PMS_STABILISING_MS) { if (this->update_interval_ > STABILISING_MS) {
this->send_command_(PMS_CMD_SLEEP_MODE, PMS_CMD_SLEEP_MODE_SLEEP); this->send_command_(Command::SLEEP_MODE, CMD_SLEEP_MODE_SLEEP);
this->state_ = PMSX003_STATE_IDLE; this->state_ = State::IDLE;
} }
this->status_clear_warning(); this->status_clear_warning();
} }
} // namespace pmsx003 } // namespace esphome::pmsx003
} // namespace esphome

View File

@@ -5,27 +5,28 @@
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h" #include "esphome/components/uart/uart.h"
namespace esphome { namespace esphome::pmsx003 {
namespace pmsx003 {
enum PMSX0003Command : uint8_t { enum class Type : uint8_t {
PMS_CMD_MEASUREMENT_MODE = PMS1003 = 0,
0xE1, // Data Options: `PMS_CMD_MEASUREMENT_MODE_PASSIVE`, `PMS_CMD_MEASUREMENT_MODE_ACTIVE` PMS3003,
PMS_CMD_MANUAL_MEASUREMENT = 0xE2, PMSX003, // PMS5003, PMS6003, PMS7003, PMSA003 (NOT PMSA003I - see `pmsa003i` component)
PMS_CMD_SLEEP_MODE = 0xE4, // Data Options: `PMS_CMD_SLEEP_MODE_SLEEP`, `PMS_CMD_SLEEP_MODE_WAKEUP` PMS5003S,
PMS5003T,
PMS5003ST,
PMS9003M,
}; };
enum PMSX003Type { enum class Command : uint8_t {
PMSX003_TYPE_X003 = 0, MEASUREMENT_MODE = 0xE1, // Data Options: `CMD_MEASUREMENT_MODE_PASSIVE`, `CMD_MEASUREMENT_MODE_ACTIVE`
PMSX003_TYPE_5003T, MANUAL_MEASUREMENT = 0xE2,
PMSX003_TYPE_5003ST, SLEEP_MODE = 0xE4, // Data Options: `CMD_SLEEP_MODE_SLEEP`, `CMD_SLEEP_MODE_WAKEUP`
PMSX003_TYPE_5003S,
}; };
enum PMSX003State { enum class State : uint8_t {
PMSX003_STATE_IDLE = 0, IDLE = 0,
PMSX003_STATE_STABILISING, STABILISING,
PMSX003_STATE_WAITING, WAITING,
}; };
class PMSX003Component : public uart::UARTDevice, public Component { class PMSX003Component : public uart::UARTDevice, public Component {
@@ -37,7 +38,7 @@ class PMSX003Component : public uart::UARTDevice, public Component {
void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
void set_type(PMSX003Type type) { this->type_ = type; } void set_type(Type type) { this->type_ = type; }
void set_pm_1_0_std_sensor(sensor::Sensor *pm_1_0_std_sensor) { this->pm_1_0_std_sensor_ = pm_1_0_std_sensor; } void set_pm_1_0_std_sensor(sensor::Sensor *pm_1_0_std_sensor) { this->pm_1_0_std_sensor_ = pm_1_0_std_sensor; }
void set_pm_2_5_std_sensor(sensor::Sensor *pm_2_5_std_sensor) { this->pm_2_5_std_sensor_ = pm_2_5_std_sensor; } void set_pm_2_5_std_sensor(sensor::Sensor *pm_2_5_std_sensor) { this->pm_2_5_std_sensor_ = pm_2_5_std_sensor; }
@@ -77,20 +78,20 @@ class PMSX003Component : public uart::UARTDevice, public Component {
optional<bool> check_byte_(); optional<bool> check_byte_();
void parse_data_(); void parse_data_();
bool check_payload_length_(uint16_t payload_length); bool check_payload_length_(uint16_t payload_length);
void send_command_(PMSX0003Command cmd, uint16_t data); void send_command_(Command cmd, uint16_t data);
uint16_t get_16_bit_uint_(uint8_t start_index) const { uint16_t get_16_bit_uint_(uint8_t start_index) const {
return encode_uint16(this->data_[start_index], this->data_[start_index + 1]); return encode_uint16(this->data_[start_index], this->data_[start_index + 1]);
} }
Type type_;
State state_{State::IDLE};
bool initialised_{false};
uint8_t data_[64]; uint8_t data_[64];
uint8_t data_index_{0}; uint8_t data_index_{0};
uint8_t initialised_{0};
uint32_t fan_on_time_{0}; uint32_t fan_on_time_{0};
uint32_t last_update_{0}; uint32_t last_update_{0};
uint32_t last_transmission_{0}; uint32_t last_transmission_{0};
uint32_t update_interval_{0}; uint32_t update_interval_{0};
PMSX003State state_{PMSX003_STATE_IDLE};
PMSX003Type type_;
// "Standard Particle" // "Standard Particle"
sensor::Sensor *pm_1_0_std_sensor_{nullptr}; sensor::Sensor *pm_1_0_std_sensor_{nullptr};
@@ -118,5 +119,4 @@ class PMSX003Component : public uart::UARTDevice, public Component {
sensor::Sensor *humidity_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr};
}; };
} // namespace pmsx003 } // namespace esphome::pmsx003
} // namespace esphome

View File

@@ -40,34 +40,128 @@ pmsx003_ns = cg.esphome_ns.namespace("pmsx003")
PMSX003Component = pmsx003_ns.class_("PMSX003Component", uart.UARTDevice, cg.Component) PMSX003Component = pmsx003_ns.class_("PMSX003Component", uart.UARTDevice, cg.Component)
PMSX003Sensor = pmsx003_ns.class_("PMSX003Sensor", sensor.Sensor) PMSX003Sensor = pmsx003_ns.class_("PMSX003Sensor", sensor.Sensor)
TYPE_PMSX003 = "PMSX003" TYPE_PMS1003 = "PMS1003"
TYPE_PMS3003 = "PMS3003"
TYPE_PMSX003 = "PMSX003" # PMS5003, PMS6003, PMS7003, PMSA003 (NOT PMSA003I - see `pmsa003i` component)
TYPE_PMS5003S = "PMS5003S"
TYPE_PMS5003T = "PMS5003T" TYPE_PMS5003T = "PMS5003T"
TYPE_PMS5003ST = "PMS5003ST" TYPE_PMS5003ST = "PMS5003ST"
TYPE_PMS5003S = "PMS5003S" TYPE_PMS9003M = "PMS9003M"
PMSX003Type = pmsx003_ns.enum("PMSX003Type") Type = pmsx003_ns.enum("Type", is_class=True)
PMSX003_TYPES = { PMSX003_TYPES = {
TYPE_PMSX003: PMSX003Type.PMSX003_TYPE_X003, TYPE_PMS1003: Type.PMS1003,
TYPE_PMS5003T: PMSX003Type.PMSX003_TYPE_5003T, TYPE_PMS3003: Type.PMS3003,
TYPE_PMS5003ST: PMSX003Type.PMSX003_TYPE_5003ST, TYPE_PMSX003: Type.PMSX003,
TYPE_PMS5003S: PMSX003Type.PMSX003_TYPE_5003S, TYPE_PMS5003S: Type.PMS5003S,
TYPE_PMS5003T: Type.PMS5003T,
TYPE_PMS5003ST: Type.PMS5003ST,
TYPE_PMS9003M: Type.PMS9003M,
} }
SENSORS_TO_TYPE = { SENSORS_TO_TYPE = {
CONF_PM_1_0: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], CONF_PM_1_0_STD: [
CONF_PM_2_5: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], TYPE_PMS1003,
CONF_PM_10_0: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], TYPE_PMS3003,
CONF_PM_1_0_STD: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], TYPE_PMSX003,
CONF_PM_2_5_STD: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], TYPE_PMS5003S,
CONF_PM_10_0_STD: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], TYPE_PMS5003T,
CONF_PM_0_3UM: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], TYPE_PMS5003ST,
CONF_PM_0_5UM: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], TYPE_PMS9003M,
CONF_PM_1_0UM: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], ],
CONF_PM_2_5UM: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], CONF_PM_2_5_STD: [
CONF_PM_5_0UM: [TYPE_PMSX003, TYPE_PMS5003ST, TYPE_PMS5003S], TYPE_PMS1003,
CONF_PM_10_0UM: [TYPE_PMSX003, TYPE_PMS5003ST, TYPE_PMS5003S], TYPE_PMS3003,
CONF_FORMALDEHYDE: [TYPE_PMS5003ST, TYPE_PMS5003S], TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_10_0_STD: [
TYPE_PMS1003,
TYPE_PMS3003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_1_0: [
TYPE_PMS1003,
TYPE_PMS3003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_2_5: [
TYPE_PMS1003,
TYPE_PMS3003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_10_0: [
TYPE_PMS1003,
TYPE_PMS3003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_0_3UM: [
TYPE_PMS1003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_0_5UM: [
TYPE_PMS1003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_1_0UM: [
TYPE_PMS1003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_2_5UM: [
TYPE_PMS1003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_5_0UM: [
TYPE_PMS1003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_10_0UM: [
TYPE_PMS1003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_FORMALDEHYDE: [TYPE_PMS5003S, TYPE_PMS5003ST],
CONF_TEMPERATURE: [TYPE_PMS5003T, TYPE_PMS5003ST], CONF_TEMPERATURE: [TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_HUMIDITY: [TYPE_PMS5003T, TYPE_PMS5003ST], CONF_HUMIDITY: [TYPE_PMS5003T, TYPE_PMS5003ST],
} }

View File

@@ -83,7 +83,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
uint32_t on_time, off_time; uint32_t on_time, off_time;
this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time); this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time);
this->target_time_ = 0; this->target_time_ = 0;
this->transmit_trigger_->trigger(); this->transmit_trigger_.trigger();
for (uint32_t i = 0; i < send_times; i++) { for (uint32_t i = 0; i < send_times; i++) {
InterruptLock lock; InterruptLock lock;
for (int32_t item : this->temp_.get_data()) { for (int32_t item : this->temp_.get_data()) {
@@ -102,7 +102,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
if (i + 1 < send_times) if (i + 1 < send_times)
this->target_time_ += send_wait; this->target_time_ += send_wait;
} }
this->complete_trigger_->trigger(); this->complete_trigger_.trigger();
} }
} // namespace remote_transmitter } // namespace remote_transmitter

View File

@@ -57,8 +57,8 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
void set_non_blocking(bool non_blocking) { this->non_blocking_ = non_blocking; } void set_non_blocking(bool non_blocking) { this->non_blocking_ = non_blocking; }
#endif #endif
Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; }; Trigger<> *get_transmit_trigger() { return &this->transmit_trigger_; }
Trigger<> *get_complete_trigger() const { return this->complete_trigger_; }; Trigger<> *get_complete_trigger() { return &this->complete_trigger_; }
protected: protected:
void send_internal(uint32_t send_times, uint32_t send_wait) override; void send_internal(uint32_t send_times, uint32_t send_wait) override;
@@ -96,8 +96,8 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
#endif #endif
uint8_t carrier_duty_percent_; uint8_t carrier_duty_percent_;
Trigger<> *transmit_trigger_{new Trigger<>()}; Trigger<> transmit_trigger_;
Trigger<> *complete_trigger_{new Trigger<>()}; Trigger<> complete_trigger_;
}; };
} // namespace remote_transmitter } // namespace remote_transmitter

View File

@@ -203,7 +203,7 @@ void RemoteTransmitterComponent::wait_for_rmt_() {
this->status_set_warning(); this->status_set_warning();
} }
this->complete_trigger_->trigger(); this->complete_trigger_.trigger();
} }
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
@@ -264,7 +264,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
return; return;
} }
this->transmit_trigger_->trigger(); this->transmit_trigger_.trigger();
rmt_transmit_config_t config; rmt_transmit_config_t config;
memset(&config, 0, sizeof(config)); memset(&config, 0, sizeof(config));
@@ -333,7 +333,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
ESP_LOGE(TAG, "Empty data"); ESP_LOGE(TAG, "Empty data");
return; return;
} }
this->transmit_trigger_->trigger(); this->transmit_trigger_.trigger();
for (uint32_t i = 0; i < send_times; i++) { for (uint32_t i = 0; i < send_times; i++) {
rmt_transmit_config_t config; rmt_transmit_config_t config;
memset(&config, 0, sizeof(config)); memset(&config, 0, sizeof(config));
@@ -354,7 +354,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
if (i + 1 < send_times) if (i + 1 < send_times)
delayMicroseconds(send_wait); delayMicroseconds(send_wait);
} }
this->complete_trigger_->trigger(); this->complete_trigger_.trigger();
} }
#endif #endif

View File

@@ -519,9 +519,9 @@ void SpeakerMediaPlayer::set_mute_state_(bool mute_state) {
if (old_mute_state != mute_state) { if (old_mute_state != mute_state) {
if (mute_state) { if (mute_state) {
this->defer([this]() { this->mute_trigger_->trigger(); }); this->defer([this]() { this->mute_trigger_.trigger(); });
} else { } else {
this->defer([this]() { this->unmute_trigger_->trigger(); }); this->defer([this]() { this->unmute_trigger_.trigger(); });
} }
} }
} }
@@ -550,7 +550,7 @@ void SpeakerMediaPlayer::set_volume_(float volume, bool publish) {
this->set_mute_state_(false); this->set_mute_state_(false);
} }
this->defer([this, volume]() { this->volume_trigger_->trigger(volume); }); this->defer([this, volume]() { this->volume_trigger_.trigger(volume); });
} }
} // namespace speaker } // namespace speaker

View File

@@ -84,9 +84,9 @@ class SpeakerMediaPlayer : public Component,
this->media_format_ = media_format; this->media_format_ = media_format;
} }
Trigger<> *get_mute_trigger() const { return this->mute_trigger_; } Trigger<> *get_mute_trigger() { return &this->mute_trigger_; }
Trigger<> *get_unmute_trigger() const { return this->unmute_trigger_; } Trigger<> *get_unmute_trigger() { return &this->unmute_trigger_; }
Trigger<float> *get_volume_trigger() const { return this->volume_trigger_; } Trigger<float> *get_volume_trigger() { return &this->volume_trigger_; }
void play_file(audio::AudioFile *media_file, bool announcement, bool enqueue); void play_file(audio::AudioFile *media_file, bool announcement, bool enqueue);
@@ -154,9 +154,9 @@ class SpeakerMediaPlayer : public Component,
// Used to save volume/mute state for restoration on reboot // Used to save volume/mute state for restoration on reboot
ESPPreferenceObject pref_; ESPPreferenceObject pref_;
Trigger<> *mute_trigger_ = new Trigger<>(); Trigger<> mute_trigger_;
Trigger<> *unmute_trigger_ = new Trigger<>(); Trigger<> unmute_trigger_;
Trigger<float> *volume_trigger_ = new Trigger<float>(); Trigger<float> volume_trigger_;
}; };
} // namespace speaker } // namespace speaker

View File

@@ -29,7 +29,7 @@ void SprinklerControllerNumber::setup() {
} }
void SprinklerControllerNumber::control(float value) { void SprinklerControllerNumber::control(float value) {
this->set_trigger_->trigger(value); this->set_trigger_.trigger(value);
this->publish_state(value); this->publish_state(value);
@@ -39,8 +39,7 @@ void SprinklerControllerNumber::control(float value) {
void SprinklerControllerNumber::dump_config() { LOG_NUMBER("", "Sprinkler Controller Number", this); } void SprinklerControllerNumber::dump_config() { LOG_NUMBER("", "Sprinkler Controller Number", this); }
SprinklerControllerSwitch::SprinklerControllerSwitch() SprinklerControllerSwitch::SprinklerControllerSwitch() = default;
: turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {}
void SprinklerControllerSwitch::loop() { void SprinklerControllerSwitch::loop() {
// Loop is only enabled when f_ has a value (see setup()) // Loop is only enabled when f_ has a value (see setup())
@@ -56,11 +55,11 @@ void SprinklerControllerSwitch::write_state(bool state) {
} }
if (state) { if (state) {
this->prev_trigger_ = this->turn_on_trigger_; this->prev_trigger_ = &this->turn_on_trigger_;
this->turn_on_trigger_->trigger(); this->turn_on_trigger_.trigger();
} else { } else {
this->prev_trigger_ = this->turn_off_trigger_; this->prev_trigger_ = &this->turn_off_trigger_;
this->turn_off_trigger_->trigger(); this->turn_off_trigger_.trigger();
} }
this->publish_state(state); this->publish_state(state);
@@ -69,9 +68,6 @@ void SprinklerControllerSwitch::write_state(bool state) {
void SprinklerControllerSwitch::set_state_lambda(std::function<optional<bool>()> &&f) { this->f_ = f; } void SprinklerControllerSwitch::set_state_lambda(std::function<optional<bool>()> &&f) { this->f_ = f; }
float SprinklerControllerSwitch::get_setup_priority() const { return setup_priority::HARDWARE; } float SprinklerControllerSwitch::get_setup_priority() const { return setup_priority::HARDWARE; }
Trigger<> *SprinklerControllerSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; }
Trigger<> *SprinklerControllerSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; }
void SprinklerControllerSwitch::setup() { void SprinklerControllerSwitch::setup() {
this->state = this->get_initial_state_with_restore_mode().value_or(false); this->state = this->get_initial_state_with_restore_mode().value_or(false);
// Disable loop if no state lambda is set - nothing to poll // Disable loop if no state lambda is set - nothing to poll

View File

@@ -76,7 +76,7 @@ class SprinklerControllerNumber : public number::Number, public Component {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::PROCESSOR; } float get_setup_priority() const override { return setup_priority::PROCESSOR; }
Trigger<float> *get_set_trigger() const { return set_trigger_; } Trigger<float> *get_set_trigger() { return &this->set_trigger_; }
void set_initial_value(float initial_value) { initial_value_ = initial_value; } void set_initial_value(float initial_value) { initial_value_ = initial_value; }
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
@@ -84,7 +84,7 @@ class SprinklerControllerNumber : public number::Number, public Component {
void control(float value) override; void control(float value) override;
float initial_value_{NAN}; float initial_value_{NAN};
bool restore_value_{true}; bool restore_value_{true};
Trigger<float> *set_trigger_ = new Trigger<float>(); Trigger<float> set_trigger_;
ESPPreferenceObject pref_; ESPPreferenceObject pref_;
}; };
@@ -97,8 +97,8 @@ class SprinklerControllerSwitch : public switch_::Switch, public Component {
void dump_config() override; void dump_config() override;
void set_state_lambda(std::function<optional<bool>()> &&f); void set_state_lambda(std::function<optional<bool>()> &&f);
Trigger<> *get_turn_on_trigger() const; Trigger<> *get_turn_on_trigger() { return &this->turn_on_trigger_; }
Trigger<> *get_turn_off_trigger() const; Trigger<> *get_turn_off_trigger() { return &this->turn_off_trigger_; }
void loop() override; void loop() override;
float get_setup_priority() const override; float get_setup_priority() const override;
@@ -107,8 +107,8 @@ class SprinklerControllerSwitch : public switch_::Switch, public Component {
void write_state(bool state) override; void write_state(bool state) override;
optional<std::function<optional<bool>()>> f_; optional<std::function<optional<bool>()>> f_;
Trigger<> *turn_on_trigger_; Trigger<> turn_on_trigger_;
Trigger<> *turn_off_trigger_; Trigger<> turn_off_trigger_;
Trigger<> *prev_trigger_{nullptr}; Trigger<> *prev_trigger_{nullptr};
}; };

View File

@@ -213,7 +213,7 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True), cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True),
cv.Optional(CONF_PA_POWER, default=17): cv.int_range(min=-3, max=22), cv.Optional(CONF_PA_POWER, default=17): cv.int_range(min=-3, max=22),
cv.Optional(CONF_PA_RAMP, default="40us"): cv.enum(RAMP), cv.Optional(CONF_PA_RAMP, default="40us"): cv.enum(RAMP),
cv.Optional(CONF_PAYLOAD_LENGTH, default=0): cv.int_range(min=0, max=256), cv.Optional(CONF_PAYLOAD_LENGTH, default=0): cv.int_range(min=0, max=255),
cv.Optional(CONF_PREAMBLE_DETECT, default=2): cv.int_range(min=0, max=4), cv.Optional(CONF_PREAMBLE_DETECT, default=2): cv.int_range(min=0, max=4),
cv.Optional(CONF_PREAMBLE_SIZE, default=8): cv.int_range(min=1, max=65535), cv.Optional(CONF_PREAMBLE_SIZE, default=8): cv.int_range(min=1, max=65535),
cv.Required(CONF_RST_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_RST_PIN): pins.gpio_output_pin_schema,

View File

@@ -343,7 +343,7 @@ void SX126x::call_listeners_(const std::vector<uint8_t> &packet, float rssi, flo
for (auto &listener : this->listeners_) { for (auto &listener : this->listeners_) {
listener->on_packet(packet, rssi, snr); listener->on_packet(packet, rssi, snr);
} }
this->packet_trigger_->trigger(packet, rssi, snr); this->packet_trigger_.trigger(packet, rssi, snr);
} }
void SX126x::loop() { void SX126x::loop() {

View File

@@ -97,7 +97,7 @@ class SX126x : public Component,
void configure(); void configure();
SX126xError transmit_packet(const std::vector<uint8_t> &packet); SX126xError transmit_packet(const std::vector<uint8_t> &packet);
void register_listener(SX126xListener *listener) { this->listeners_.push_back(listener); } void register_listener(SX126xListener *listener) { this->listeners_.push_back(listener); }
Trigger<std::vector<uint8_t>, float, float> *get_packet_trigger() const { return this->packet_trigger_; }; Trigger<std::vector<uint8_t>, float, float> *get_packet_trigger() { return &this->packet_trigger_; }
protected: protected:
void configure_fsk_ook_(); void configure_fsk_ook_();
@@ -111,7 +111,7 @@ class SX126x : public Component,
void read_register_(uint16_t reg, uint8_t *data, uint8_t size); void read_register_(uint16_t reg, uint8_t *data, uint8_t size);
void call_listeners_(const std::vector<uint8_t> &packet, float rssi, float snr); void call_listeners_(const std::vector<uint8_t> &packet, float rssi, float snr);
void wait_busy_(); void wait_busy_();
Trigger<std::vector<uint8_t>, float, float> *packet_trigger_{new Trigger<std::vector<uint8_t>, float, float>()}; Trigger<std::vector<uint8_t>, float, float> packet_trigger_;
std::vector<SX126xListener *> listeners_; std::vector<SX126xListener *> listeners_;
std::vector<uint8_t> packet_; std::vector<uint8_t> packet_;
std::vector<uint8_t> sync_value_; std::vector<uint8_t> sync_value_;

View File

@@ -300,7 +300,7 @@ void SX127x::call_listeners_(const std::vector<uint8_t> &packet, float rssi, flo
for (auto &listener : this->listeners_) { for (auto &listener : this->listeners_) {
listener->on_packet(packet, rssi, snr); listener->on_packet(packet, rssi, snr);
} }
this->packet_trigger_->trigger(packet, rssi, snr); this->packet_trigger_.trigger(packet, rssi, snr);
} }
void SX127x::loop() { void SX127x::loop() {

View File

@@ -83,7 +83,7 @@ class SX127x : public Component,
void configure(); void configure();
SX127xError transmit_packet(const std::vector<uint8_t> &packet); SX127xError transmit_packet(const std::vector<uint8_t> &packet);
void register_listener(SX127xListener *listener) { this->listeners_.push_back(listener); } void register_listener(SX127xListener *listener) { this->listeners_.push_back(listener); }
Trigger<std::vector<uint8_t>, float, float> *get_packet_trigger() const { return this->packet_trigger_; }; Trigger<std::vector<uint8_t>, float, float> *get_packet_trigger() { return &this->packet_trigger_; }
protected: protected:
void configure_fsk_ook_(); void configure_fsk_ook_();
@@ -94,7 +94,7 @@ class SX127x : public Component,
void write_register_(uint8_t reg, uint8_t value); void write_register_(uint8_t reg, uint8_t value);
void call_listeners_(const std::vector<uint8_t> &packet, float rssi, float snr); void call_listeners_(const std::vector<uint8_t> &packet, float rssi, float snr);
uint8_t read_register_(uint8_t reg); uint8_t read_register_(uint8_t reg);
Trigger<std::vector<uint8_t>, float, float> *packet_trigger_{new Trigger<std::vector<uint8_t>, float, float>()}; Trigger<std::vector<uint8_t>, float, float> packet_trigger_;
std::vector<SX127xListener *> listeners_; std::vector<SX127xListener *> listeners_;
std::vector<uint8_t> packet_; std::vector<uint8_t> packet_;
std::vector<uint8_t> sync_value_; std::vector<uint8_t> sync_value_;

View File

@@ -7,13 +7,7 @@ using namespace esphome::cover;
static const char *const TAG = "template.cover"; static const char *const TAG = "template.cover";
TemplateCover::TemplateCover() TemplateCover::TemplateCover() = default;
: open_trigger_(new Trigger<>()),
close_trigger_(new Trigger<>),
stop_trigger_(new Trigger<>()),
toggle_trigger_(new Trigger<>()),
position_trigger_(new Trigger<float>()),
tilt_trigger_(new Trigger<float>()) {}
void TemplateCover::setup() { void TemplateCover::setup() {
switch (this->restore_mode_) { switch (this->restore_mode_) {
case COVER_NO_RESTORE: case COVER_NO_RESTORE:
@@ -62,22 +56,22 @@ void TemplateCover::loop() {
void TemplateCover::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void TemplateCover::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void TemplateCover::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } void TemplateCover::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
float TemplateCover::get_setup_priority() const { return setup_priority::HARDWARE; } float TemplateCover::get_setup_priority() const { return setup_priority::HARDWARE; }
Trigger<> *TemplateCover::get_open_trigger() const { return this->open_trigger_; } Trigger<> *TemplateCover::get_open_trigger() { return &this->open_trigger_; }
Trigger<> *TemplateCover::get_close_trigger() const { return this->close_trigger_; } Trigger<> *TemplateCover::get_close_trigger() { return &this->close_trigger_; }
Trigger<> *TemplateCover::get_stop_trigger() const { return this->stop_trigger_; } Trigger<> *TemplateCover::get_stop_trigger() { return &this->stop_trigger_; }
Trigger<> *TemplateCover::get_toggle_trigger() const { return this->toggle_trigger_; } Trigger<> *TemplateCover::get_toggle_trigger() { return &this->toggle_trigger_; }
void TemplateCover::dump_config() { LOG_COVER("", "Template Cover", this); } void TemplateCover::dump_config() { LOG_COVER("", "Template Cover", this); }
void TemplateCover::control(const CoverCall &call) { void TemplateCover::control(const CoverCall &call) {
if (call.get_stop()) { if (call.get_stop()) {
this->stop_prev_trigger_(); this->stop_prev_trigger_();
this->stop_trigger_->trigger(); this->stop_trigger_.trigger();
this->prev_command_trigger_ = this->stop_trigger_; this->prev_command_trigger_ = &this->stop_trigger_;
this->publish_state(); this->publish_state();
} }
if (call.get_toggle().has_value()) { if (call.get_toggle().has_value()) {
this->stop_prev_trigger_(); this->stop_prev_trigger_();
this->toggle_trigger_->trigger(); this->toggle_trigger_.trigger();
this->prev_command_trigger_ = this->toggle_trigger_; this->prev_command_trigger_ = &this->toggle_trigger_;
this->publish_state(); this->publish_state();
} }
if (call.get_position().has_value()) { if (call.get_position().has_value()) {
@@ -85,13 +79,13 @@ void TemplateCover::control(const CoverCall &call) {
this->stop_prev_trigger_(); this->stop_prev_trigger_();
if (pos == COVER_OPEN) { if (pos == COVER_OPEN) {
this->open_trigger_->trigger(); this->open_trigger_.trigger();
this->prev_command_trigger_ = this->open_trigger_; this->prev_command_trigger_ = &this->open_trigger_;
} else if (pos == COVER_CLOSED) { } else if (pos == COVER_CLOSED) {
this->close_trigger_->trigger(); this->close_trigger_.trigger();
this->prev_command_trigger_ = this->close_trigger_; this->prev_command_trigger_ = &this->close_trigger_;
} else { } else {
this->position_trigger_->trigger(pos); this->position_trigger_.trigger(pos);
} }
if (this->optimistic_) { if (this->optimistic_) {
@@ -101,7 +95,7 @@ void TemplateCover::control(const CoverCall &call) {
if (call.get_tilt().has_value()) { if (call.get_tilt().has_value()) {
auto tilt = *call.get_tilt(); auto tilt = *call.get_tilt();
this->tilt_trigger_->trigger(tilt); this->tilt_trigger_.trigger(tilt);
if (this->optimistic_) { if (this->optimistic_) {
this->tilt = tilt; this->tilt = tilt;
@@ -119,8 +113,8 @@ CoverTraits TemplateCover::get_traits() {
traits.set_supports_tilt(this->has_tilt_); traits.set_supports_tilt(this->has_tilt_);
return traits; return traits;
} }
Trigger<float> *TemplateCover::get_position_trigger() const { return this->position_trigger_; } Trigger<float> *TemplateCover::get_position_trigger() { return &this->position_trigger_; }
Trigger<float> *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; } Trigger<float> *TemplateCover::get_tilt_trigger() { return &this->tilt_trigger_; }
void TemplateCover::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; } void TemplateCover::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; }
void TemplateCover::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; } void TemplateCover::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; }
void TemplateCover::set_has_position(bool has_position) { this->has_position_ = has_position; } void TemplateCover::set_has_position(bool has_position) { this->has_position_ = has_position; }

View File

@@ -19,12 +19,12 @@ class TemplateCover final : public cover::Cover, public Component {
template<typename F> void set_state_lambda(F &&f) { this->state_f_.set(std::forward<F>(f)); } template<typename F> void set_state_lambda(F &&f) { this->state_f_.set(std::forward<F>(f)); }
template<typename F> void set_tilt_lambda(F &&f) { this->tilt_f_.set(std::forward<F>(f)); } template<typename F> void set_tilt_lambda(F &&f) { this->tilt_f_.set(std::forward<F>(f)); }
Trigger<> *get_open_trigger() const; Trigger<> *get_open_trigger();
Trigger<> *get_close_trigger() const; Trigger<> *get_close_trigger();
Trigger<> *get_stop_trigger() const; Trigger<> *get_stop_trigger();
Trigger<> *get_toggle_trigger() const; Trigger<> *get_toggle_trigger();
Trigger<float> *get_position_trigger() const; Trigger<float> *get_position_trigger();
Trigger<float> *get_tilt_trigger() const; Trigger<float> *get_tilt_trigger();
void set_optimistic(bool optimistic); void set_optimistic(bool optimistic);
void set_assumed_state(bool assumed_state); void set_assumed_state(bool assumed_state);
void set_has_stop(bool has_stop); void set_has_stop(bool has_stop);
@@ -49,16 +49,16 @@ class TemplateCover final : public cover::Cover, public Component {
TemplateLambda<float> tilt_f_; TemplateLambda<float> tilt_f_;
bool assumed_state_{false}; bool assumed_state_{false};
bool optimistic_{false}; bool optimistic_{false};
Trigger<> *open_trigger_; Trigger<> open_trigger_;
Trigger<> *close_trigger_; Trigger<> close_trigger_;
bool has_stop_{false}; bool has_stop_{false};
bool has_toggle_{false}; bool has_toggle_{false};
Trigger<> *stop_trigger_; Trigger<> stop_trigger_;
Trigger<> *toggle_trigger_; Trigger<> toggle_trigger_;
Trigger<> *prev_command_trigger_{nullptr}; Trigger<> *prev_command_trigger_{nullptr};
Trigger<float> *position_trigger_; Trigger<float> position_trigger_;
bool has_position_{false}; bool has_position_{false};
Trigger<float> *tilt_trigger_; Trigger<float> tilt_trigger_;
bool has_tilt_{false}; bool has_tilt_{false};
}; };

View File

@@ -62,7 +62,7 @@ void TemplateDate::control(const datetime::DateCall &call) {
if (has_day) if (has_day)
value.day_of_month = *call.get_day(); value.day_of_month = *call.get_day();
this->set_trigger_->trigger(value); this->set_trigger_.trigger(value);
if (this->optimistic_) { if (this->optimistic_) {
if (has_year) if (has_year)

View File

@@ -22,7 +22,7 @@ class TemplateDate final : public datetime::DateEntity, public PollingComponent
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; } float get_setup_priority() const override { return setup_priority::HARDWARE; }
Trigger<ESPTime> *get_set_trigger() const { return this->set_trigger_; } Trigger<ESPTime> *get_set_trigger() { return &this->set_trigger_; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; } void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; }
@@ -34,7 +34,7 @@ class TemplateDate final : public datetime::DateEntity, public PollingComponent
bool optimistic_{false}; bool optimistic_{false};
ESPTime initial_value_{}; ESPTime initial_value_{};
bool restore_value_{false}; bool restore_value_{false};
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>(); Trigger<ESPTime> set_trigger_;
TemplateLambda<ESPTime> f_; TemplateLambda<ESPTime> f_;
ESPPreferenceObject pref_; ESPPreferenceObject pref_;

View File

@@ -80,7 +80,7 @@ void TemplateDateTime::control(const datetime::DateTimeCall &call) {
if (has_second) if (has_second)
value.second = *call.get_second(); value.second = *call.get_second();
this->set_trigger_->trigger(value); this->set_trigger_.trigger(value);
if (this->optimistic_) { if (this->optimistic_) {
if (has_year) if (has_year)

View File

@@ -22,7 +22,7 @@ class TemplateDateTime final : public datetime::DateTimeEntity, public PollingCo
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; } float get_setup_priority() const override { return setup_priority::HARDWARE; }
Trigger<ESPTime> *get_set_trigger() const { return this->set_trigger_; } Trigger<ESPTime> *get_set_trigger() { return &this->set_trigger_; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; } void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; }
@@ -34,7 +34,7 @@ class TemplateDateTime final : public datetime::DateTimeEntity, public PollingCo
bool optimistic_{false}; bool optimistic_{false};
ESPTime initial_value_{}; ESPTime initial_value_{};
bool restore_value_{false}; bool restore_value_{false};
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>(); Trigger<ESPTime> set_trigger_;
TemplateLambda<ESPTime> f_; TemplateLambda<ESPTime> f_;
ESPPreferenceObject pref_; ESPPreferenceObject pref_;

View File

@@ -62,7 +62,7 @@ void TemplateTime::control(const datetime::TimeCall &call) {
if (has_second) if (has_second)
value.second = *call.get_second(); value.second = *call.get_second();
this->set_trigger_->trigger(value); this->set_trigger_.trigger(value);
if (this->optimistic_) { if (this->optimistic_) {
if (has_hour) if (has_hour)

View File

@@ -22,7 +22,7 @@ class TemplateTime final : public datetime::TimeEntity, public PollingComponent
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; } float get_setup_priority() const override { return setup_priority::HARDWARE; }
Trigger<ESPTime> *get_set_trigger() const { return this->set_trigger_; } Trigger<ESPTime> *get_set_trigger() { return &this->set_trigger_; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; } void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; }
@@ -34,7 +34,7 @@ class TemplateTime final : public datetime::TimeEntity, public PollingComponent
bool optimistic_{false}; bool optimistic_{false};
ESPTime initial_value_{}; ESPTime initial_value_{};
bool restore_value_{false}; bool restore_value_{false};
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>(); Trigger<ESPTime> set_trigger_;
TemplateLambda<ESPTime> f_; TemplateLambda<ESPTime> f_;
ESPPreferenceObject pref_; ESPPreferenceObject pref_;

View File

@@ -7,8 +7,7 @@ using namespace esphome::lock;
static const char *const TAG = "template.lock"; static const char *const TAG = "template.lock";
TemplateLock::TemplateLock() TemplateLock::TemplateLock() = default;
: lock_trigger_(new Trigger<>()), unlock_trigger_(new Trigger<>()), open_trigger_(new Trigger<>()) {}
void TemplateLock::setup() { void TemplateLock::setup() {
if (!this->f_.has_value()) if (!this->f_.has_value())
@@ -28,11 +27,11 @@ void TemplateLock::control(const lock::LockCall &call) {
auto state = *call.get_state(); auto state = *call.get_state();
if (state == LOCK_STATE_LOCKED) { if (state == LOCK_STATE_LOCKED) {
this->prev_trigger_ = this->lock_trigger_; this->prev_trigger_ = &this->lock_trigger_;
this->lock_trigger_->trigger(); this->lock_trigger_.trigger();
} else if (state == LOCK_STATE_UNLOCKED) { } else if (state == LOCK_STATE_UNLOCKED) {
this->prev_trigger_ = this->unlock_trigger_; this->prev_trigger_ = &this->unlock_trigger_;
this->unlock_trigger_->trigger(); this->unlock_trigger_.trigger();
} }
if (this->optimistic_) if (this->optimistic_)
@@ -42,14 +41,11 @@ void TemplateLock::open_latch() {
if (this->prev_trigger_ != nullptr) { if (this->prev_trigger_ != nullptr) {
this->prev_trigger_->stop_action(); this->prev_trigger_->stop_action();
} }
this->prev_trigger_ = this->open_trigger_; this->prev_trigger_ = &this->open_trigger_;
this->open_trigger_->trigger(); this->open_trigger_.trigger();
} }
void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; } float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; }
Trigger<> *TemplateLock::get_lock_trigger() const { return this->lock_trigger_; }
Trigger<> *TemplateLock::get_unlock_trigger() const { return this->unlock_trigger_; }
Trigger<> *TemplateLock::get_open_trigger() const { return this->open_trigger_; }
void TemplateLock::dump_config() { void TemplateLock::dump_config() {
LOG_LOCK("", "Template Lock", this); LOG_LOCK("", "Template Lock", this);
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));

View File

@@ -15,9 +15,9 @@ class TemplateLock final : public lock::Lock, public Component {
void dump_config() override; void dump_config() override;
template<typename F> void set_state_lambda(F &&f) { this->f_.set(std::forward<F>(f)); } template<typename F> void set_state_lambda(F &&f) { this->f_.set(std::forward<F>(f)); }
Trigger<> *get_lock_trigger() const; Trigger<> *get_lock_trigger() { return &this->lock_trigger_; }
Trigger<> *get_unlock_trigger() const; Trigger<> *get_unlock_trigger() { return &this->unlock_trigger_; }
Trigger<> *get_open_trigger() const; Trigger<> *get_open_trigger() { return &this->open_trigger_; }
void set_optimistic(bool optimistic); void set_optimistic(bool optimistic);
void loop() override; void loop() override;
@@ -29,9 +29,9 @@ class TemplateLock final : public lock::Lock, public Component {
TemplateLambda<lock::LockState> f_; TemplateLambda<lock::LockState> f_;
bool optimistic_{false}; bool optimistic_{false};
Trigger<> *lock_trigger_; Trigger<> lock_trigger_;
Trigger<> *unlock_trigger_; Trigger<> unlock_trigger_;
Trigger<> *open_trigger_; Trigger<> open_trigger_;
Trigger<> *prev_trigger_{nullptr}; Trigger<> *prev_trigger_{nullptr};
}; };

View File

@@ -36,7 +36,7 @@ void TemplateNumber::update() {
} }
void TemplateNumber::control(float value) { void TemplateNumber::control(float value) {
this->set_trigger_->trigger(value); this->set_trigger_.trigger(value);
if (this->optimistic_) if (this->optimistic_)
this->publish_state(value); this->publish_state(value);

View File

@@ -17,7 +17,7 @@ class TemplateNumber final : public number::Number, public PollingComponent {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; } float get_setup_priority() const override { return setup_priority::HARDWARE; }
Trigger<float> *get_set_trigger() const { return set_trigger_; } Trigger<float> *get_set_trigger() { return &this->set_trigger_; }
void set_optimistic(bool optimistic) { optimistic_ = optimistic; } void set_optimistic(bool optimistic) { optimistic_ = optimistic; }
void set_initial_value(float initial_value) { initial_value_ = initial_value; } void set_initial_value(float initial_value) { initial_value_ = initial_value; }
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
@@ -27,7 +27,7 @@ class TemplateNumber final : public number::Number, public PollingComponent {
bool optimistic_{false}; bool optimistic_{false};
float initial_value_{NAN}; float initial_value_{NAN};
bool restore_value_{false}; bool restore_value_{false};
Trigger<float> *set_trigger_ = new Trigger<float>(); Trigger<float> set_trigger_;
TemplateLambda<float> f_; TemplateLambda<float> f_;
ESPPreferenceObject pref_; ESPPreferenceObject pref_;

View File

@@ -8,22 +8,22 @@ namespace esphome::template_ {
class TemplateBinaryOutput final : public output::BinaryOutput { class TemplateBinaryOutput final : public output::BinaryOutput {
public: public:
Trigger<bool> *get_trigger() const { return trigger_; } Trigger<bool> *get_trigger() { return &this->trigger_; }
protected: protected:
void write_state(bool state) override { this->trigger_->trigger(state); } void write_state(bool state) override { this->trigger_.trigger(state); }
Trigger<bool> *trigger_ = new Trigger<bool>(); Trigger<bool> trigger_;
}; };
class TemplateFloatOutput final : public output::FloatOutput { class TemplateFloatOutput final : public output::FloatOutput {
public: public:
Trigger<float> *get_trigger() const { return trigger_; } Trigger<float> *get_trigger() { return &this->trigger_; }
protected: protected:
void write_state(float state) override { this->trigger_->trigger(state); } void write_state(float state) override { this->trigger_.trigger(state); }
Trigger<float> *trigger_ = new Trigger<float>(); Trigger<float> trigger_;
}; };
} // namespace esphome::template_ } // namespace esphome::template_

View File

@@ -5,7 +5,7 @@ namespace esphome::template_ {
static const char *const TAG = "template.switch"; static const char *const TAG = "template.switch";
TemplateSwitch::TemplateSwitch() : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {} TemplateSwitch::TemplateSwitch() = default;
void TemplateSwitch::loop() { void TemplateSwitch::loop() {
auto s = this->f_(); auto s = this->f_();
@@ -19,11 +19,11 @@ void TemplateSwitch::write_state(bool state) {
} }
if (state) { if (state) {
this->prev_trigger_ = this->turn_on_trigger_; this->prev_trigger_ = &this->turn_on_trigger_;
this->turn_on_trigger_->trigger(); this->turn_on_trigger_.trigger();
} else { } else {
this->prev_trigger_ = this->turn_off_trigger_; this->prev_trigger_ = &this->turn_off_trigger_;
this->turn_off_trigger_->trigger(); this->turn_off_trigger_.trigger();
} }
if (this->optimistic_) if (this->optimistic_)
@@ -32,8 +32,8 @@ void TemplateSwitch::write_state(bool state) {
void TemplateSwitch::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void TemplateSwitch::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
bool TemplateSwitch::assumed_state() { return this->assumed_state_; } bool TemplateSwitch::assumed_state() { return this->assumed_state_; }
float TemplateSwitch::get_setup_priority() const { return setup_priority::HARDWARE - 2.0f; } float TemplateSwitch::get_setup_priority() const { return setup_priority::HARDWARE - 2.0f; }
Trigger<> *TemplateSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; } Trigger<> *TemplateSwitch::get_turn_on_trigger() { return &this->turn_on_trigger_; }
Trigger<> *TemplateSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; } Trigger<> *TemplateSwitch::get_turn_off_trigger() { return &this->turn_off_trigger_; }
void TemplateSwitch::setup() { void TemplateSwitch::setup() {
if (!this->f_.has_value()) if (!this->f_.has_value())
this->disable_loop(); this->disable_loop();

View File

@@ -15,8 +15,8 @@ class TemplateSwitch final : public switch_::Switch, public Component {
void dump_config() override; void dump_config() override;
template<typename F> void set_state_lambda(F &&f) { this->f_.set(std::forward<F>(f)); } template<typename F> void set_state_lambda(F &&f) { this->f_.set(std::forward<F>(f)); }
Trigger<> *get_turn_on_trigger() const; Trigger<> *get_turn_on_trigger();
Trigger<> *get_turn_off_trigger() const; Trigger<> *get_turn_off_trigger();
void set_optimistic(bool optimistic); void set_optimistic(bool optimistic);
void set_assumed_state(bool assumed_state); void set_assumed_state(bool assumed_state);
void loop() override; void loop() override;
@@ -31,9 +31,9 @@ class TemplateSwitch final : public switch_::Switch, public Component {
TemplateLambda<bool> f_; TemplateLambda<bool> f_;
bool optimistic_{false}; bool optimistic_{false};
bool assumed_state_{false}; bool assumed_state_{false};
Trigger<> *turn_on_trigger_; Trigger<> turn_on_trigger_;
Trigger<> *turn_off_trigger_; Trigger<> turn_off_trigger_;
Trigger<> *prev_trigger_{nullptr}; Trigger<> *prev_trigger_{nullptr}; // Points to one of the above
}; };
} // namespace esphome::template_ } // namespace esphome::template_

View File

@@ -47,7 +47,7 @@ void TemplateText::update() {
} }
void TemplateText::control(const std::string &value) { void TemplateText::control(const std::string &value) {
this->set_trigger_->trigger(value); this->set_trigger_.trigger(value);
if (this->optimistic_) if (this->optimistic_)
this->publish_state(value); this->publish_state(value);

View File

@@ -68,7 +68,7 @@ class TemplateText final : public text::Text, public PollingComponent {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; } float get_setup_priority() const override { return setup_priority::HARDWARE; }
Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; } Trigger<std::string> *get_set_trigger() { return &this->set_trigger_; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_initial_value(const char *initial_value) { this->initial_value_ = initial_value; } void set_initial_value(const char *initial_value) { this->initial_value_ = initial_value; }
/// Prevent accidental use of std::string which would dangle /// Prevent accidental use of std::string which would dangle
@@ -79,7 +79,7 @@ class TemplateText final : public text::Text, public PollingComponent {
void control(const std::string &value) override; void control(const std::string &value) override;
bool optimistic_ = false; bool optimistic_ = false;
const char *initial_value_{nullptr}; const char *initial_value_{nullptr};
Trigger<std::string> *set_trigger_ = new Trigger<std::string>(); Trigger<std::string> set_trigger_;
TemplateLambda<std::string> f_{}; TemplateLambda<std::string> f_{};
TemplateTextSaverBase *pref_ = nullptr; TemplateTextSaverBase *pref_ = nullptr;

View File

@@ -7,12 +7,7 @@ using namespace esphome::valve;
static const char *const TAG = "template.valve"; static const char *const TAG = "template.valve";
TemplateValve::TemplateValve() TemplateValve::TemplateValve() = default;
: open_trigger_(new Trigger<>()),
close_trigger_(new Trigger<>),
stop_trigger_(new Trigger<>()),
toggle_trigger_(new Trigger<>()),
position_trigger_(new Trigger<float>()) {}
void TemplateValve::setup() { void TemplateValve::setup() {
switch (this->restore_mode_) { switch (this->restore_mode_) {
@@ -56,10 +51,10 @@ void TemplateValve::set_optimistic(bool optimistic) { this->optimistic_ = optimi
void TemplateValve::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } void TemplateValve::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
float TemplateValve::get_setup_priority() const { return setup_priority::HARDWARE; } float TemplateValve::get_setup_priority() const { return setup_priority::HARDWARE; }
Trigger<> *TemplateValve::get_open_trigger() const { return this->open_trigger_; } Trigger<> *TemplateValve::get_open_trigger() { return &this->open_trigger_; }
Trigger<> *TemplateValve::get_close_trigger() const { return this->close_trigger_; } Trigger<> *TemplateValve::get_close_trigger() { return &this->close_trigger_; }
Trigger<> *TemplateValve::get_stop_trigger() const { return this->stop_trigger_; } Trigger<> *TemplateValve::get_stop_trigger() { return &this->stop_trigger_; }
Trigger<> *TemplateValve::get_toggle_trigger() const { return this->toggle_trigger_; } Trigger<> *TemplateValve::get_toggle_trigger() { return &this->toggle_trigger_; }
void TemplateValve::dump_config() { void TemplateValve::dump_config() {
LOG_VALVE("", "Template Valve", this); LOG_VALVE("", "Template Valve", this);
@@ -72,14 +67,14 @@ void TemplateValve::dump_config() {
void TemplateValve::control(const ValveCall &call) { void TemplateValve::control(const ValveCall &call) {
if (call.get_stop()) { if (call.get_stop()) {
this->stop_prev_trigger_(); this->stop_prev_trigger_();
this->stop_trigger_->trigger(); this->stop_trigger_.trigger();
this->prev_command_trigger_ = this->stop_trigger_; this->prev_command_trigger_ = &this->stop_trigger_;
this->publish_state(); this->publish_state();
} }
if (call.get_toggle().has_value()) { if (call.get_toggle().has_value()) {
this->stop_prev_trigger_(); this->stop_prev_trigger_();
this->toggle_trigger_->trigger(); this->toggle_trigger_.trigger();
this->prev_command_trigger_ = this->toggle_trigger_; this->prev_command_trigger_ = &this->toggle_trigger_;
this->publish_state(); this->publish_state();
} }
if (call.get_position().has_value()) { if (call.get_position().has_value()) {
@@ -87,13 +82,13 @@ void TemplateValve::control(const ValveCall &call) {
this->stop_prev_trigger_(); this->stop_prev_trigger_();
if (pos == VALVE_OPEN) { if (pos == VALVE_OPEN) {
this->open_trigger_->trigger(); this->open_trigger_.trigger();
this->prev_command_trigger_ = this->open_trigger_; this->prev_command_trigger_ = &this->open_trigger_;
} else if (pos == VALVE_CLOSED) { } else if (pos == VALVE_CLOSED) {
this->close_trigger_->trigger(); this->close_trigger_.trigger();
this->prev_command_trigger_ = this->close_trigger_; this->prev_command_trigger_ = &this->close_trigger_;
} else { } else {
this->position_trigger_->trigger(pos); this->position_trigger_.trigger(pos);
} }
if (this->optimistic_) { if (this->optimistic_) {
@@ -113,7 +108,7 @@ ValveTraits TemplateValve::get_traits() {
return traits; return traits;
} }
Trigger<float> *TemplateValve::get_position_trigger() const { return this->position_trigger_; } Trigger<float> *TemplateValve::get_position_trigger() { return &this->position_trigger_; }
void TemplateValve::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; } void TemplateValve::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; }
void TemplateValve::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; } void TemplateValve::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; }

View File

@@ -18,11 +18,11 @@ class TemplateValve final : public valve::Valve, public Component {
TemplateValve(); TemplateValve();
template<typename F> void set_state_lambda(F &&f) { this->state_f_.set(std::forward<F>(f)); } template<typename F> void set_state_lambda(F &&f) { this->state_f_.set(std::forward<F>(f)); }
Trigger<> *get_open_trigger() const; Trigger<> *get_open_trigger();
Trigger<> *get_close_trigger() const; Trigger<> *get_close_trigger();
Trigger<> *get_stop_trigger() const; Trigger<> *get_stop_trigger();
Trigger<> *get_toggle_trigger() const; Trigger<> *get_toggle_trigger();
Trigger<float> *get_position_trigger() const; Trigger<float> *get_position_trigger();
void set_optimistic(bool optimistic); void set_optimistic(bool optimistic);
void set_assumed_state(bool assumed_state); void set_assumed_state(bool assumed_state);
void set_has_stop(bool has_stop); void set_has_stop(bool has_stop);
@@ -45,14 +45,14 @@ class TemplateValve final : public valve::Valve, public Component {
TemplateLambda<float> state_f_; TemplateLambda<float> state_f_;
bool assumed_state_{false}; bool assumed_state_{false};
bool optimistic_{false}; bool optimistic_{false};
Trigger<> *open_trigger_; Trigger<> open_trigger_;
Trigger<> *close_trigger_; Trigger<> close_trigger_;
bool has_stop_{false}; bool has_stop_{false};
bool has_toggle_{false}; bool has_toggle_{false};
Trigger<> *stop_trigger_; Trigger<> stop_trigger_;
Trigger<> *toggle_trigger_; Trigger<> toggle_trigger_;
Trigger<> *prev_command_trigger_{nullptr}; Trigger<> *prev_command_trigger_{nullptr};
Trigger<float> *position_trigger_; Trigger<float> position_trigger_;
bool has_position_{false}; bool has_position_{false};
}; };

View File

@@ -5,7 +5,7 @@ namespace esphome::template_ {
static const char *const TAG = "template.water_heater"; static const char *const TAG = "template.water_heater";
TemplateWaterHeater::TemplateWaterHeater() : set_trigger_(new Trigger<>()) {} TemplateWaterHeater::TemplateWaterHeater() = default;
void TemplateWaterHeater::setup() { void TemplateWaterHeater::setup() {
if (this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE || if (this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE ||
@@ -78,7 +78,7 @@ void TemplateWaterHeater::control(const water_heater::WaterHeaterCall &call) {
} }
} }
this->set_trigger_->trigger(); this->set_trigger_.trigger();
if (this->optimistic_) { if (this->optimistic_) {
this->publish_state(); this->publish_state();

View File

@@ -28,7 +28,7 @@ class TemplateWaterHeater : public Component, public water_heater::WaterHeater {
this->supported_modes_ = modes; this->supported_modes_ = modes;
} }
Trigger<> *get_set_trigger() const { return this->set_trigger_; } Trigger<> *get_set_trigger() { return &this->set_trigger_; }
void setup() override; void setup() override;
void loop() override; void loop() override;
@@ -42,7 +42,7 @@ class TemplateWaterHeater : public Component, public water_heater::WaterHeater {
water_heater::WaterHeaterTraits traits() override; water_heater::WaterHeaterTraits traits() override;
// Ordered to minimize padding on 32-bit: 4-byte members first, then smaller // Ordered to minimize padding on 32-bit: 4-byte members first, then smaller
Trigger<> *set_trigger_; Trigger<> set_trigger_;
TemplateLambda<float> current_temperature_f_; TemplateLambda<float> current_temperature_f_;
TemplateLambda<water_heater::WaterHeaterMode> mode_f_; TemplateLambda<water_heater::WaterHeaterMode> mode_f_;
TemplateWaterHeaterRestoreMode restore_mode_{WATER_HEATER_NO_RESTORE}; TemplateWaterHeaterRestoreMode restore_mode_{WATER_HEATER_NO_RESTORE};

View File

@@ -499,7 +499,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
} }
bool action_ready = false; bool action_ready = false;
Trigger<> *trig = this->idle_action_trigger_, *trig_fan = nullptr; Trigger<> *trig = &this->idle_action_trigger_, *trig_fan = nullptr;
switch (action) { switch (action) {
case climate::CLIMATE_ACTION_OFF: case climate::CLIMATE_ACTION_OFF:
case climate::CLIMATE_ACTION_IDLE: case climate::CLIMATE_ACTION_IDLE:
@@ -529,10 +529,10 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME); this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME);
if (this->supports_fan_with_cooling_) { if (this->supports_fan_with_cooling_) {
this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON); this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON);
trig_fan = this->fan_only_action_trigger_; trig_fan = &this->fan_only_action_trigger_;
} }
this->cooling_max_runtime_exceeded_ = false; this->cooling_max_runtime_exceeded_ = false;
trig = this->cool_action_trigger_; trig = &this->cool_action_trigger_;
ESP_LOGVV(TAG, "Switching to COOLING action"); ESP_LOGVV(TAG, "Switching to COOLING action");
action_ready = true; action_ready = true;
} }
@@ -543,10 +543,10 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
this->start_timer_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME); this->start_timer_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME);
if (this->supports_fan_with_heating_) { if (this->supports_fan_with_heating_) {
this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON); this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON);
trig_fan = this->fan_only_action_trigger_; trig_fan = &this->fan_only_action_trigger_;
} }
this->heating_max_runtime_exceeded_ = false; this->heating_max_runtime_exceeded_ = false;
trig = this->heat_action_trigger_; trig = &this->heat_action_trigger_;
ESP_LOGVV(TAG, "Switching to HEATING action"); ESP_LOGVV(TAG, "Switching to HEATING action");
action_ready = true; action_ready = true;
} }
@@ -558,7 +558,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
} else { } else {
this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON); this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON);
} }
trig = this->fan_only_action_trigger_; trig = &this->fan_only_action_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_ONLY action"); ESP_LOGVV(TAG, "Switching to FAN_ONLY action");
action_ready = true; action_ready = true;
} }
@@ -567,7 +567,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
if (this->drying_action_ready_()) { if (this->drying_action_ready_()) {
this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_ON); this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_ON);
this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON); this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON);
trig = this->dry_action_trigger_; trig = &this->dry_action_trigger_;
ESP_LOGVV(TAG, "Switching to DRYING action"); ESP_LOGVV(TAG, "Switching to DRYING action");
action_ready = true; action_ready = true;
} }
@@ -586,9 +586,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
} }
this->action = action; this->action = action;
this->prev_action_trigger_ = trig; this->prev_action_trigger_ = trig;
if (trig != nullptr) { trig->trigger();
trig->trigger();
}
// if enabled, call the fan_only action with cooling/heating actions // if enabled, call the fan_only action with cooling/heating actions
if (trig_fan != nullptr) { if (trig_fan != nullptr) {
ESP_LOGVV(TAG, "Calling FAN_ONLY action with HEATING/COOLING action"); ESP_LOGVV(TAG, "Calling FAN_ONLY action with HEATING/COOLING action");
@@ -634,14 +632,14 @@ void ThermostatClimate::trigger_supplemental_action_() {
if (!this->timer_active_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME)) { if (!this->timer_active_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME)) {
this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME); this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME);
} }
trig = this->supplemental_cool_action_trigger_; trig = &this->supplemental_cool_action_trigger_;
ESP_LOGVV(TAG, "Calling supplemental COOLING action"); ESP_LOGVV(TAG, "Calling supplemental COOLING action");
break; break;
case climate::CLIMATE_ACTION_HEATING: case climate::CLIMATE_ACTION_HEATING:
if (!this->timer_active_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME)) { if (!this->timer_active_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME)) {
this->start_timer_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME); this->start_timer_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME);
} }
trig = this->supplemental_heat_action_trigger_; trig = &this->supplemental_heat_action_trigger_;
ESP_LOGVV(TAG, "Calling supplemental HEATING action"); ESP_LOGVV(TAG, "Calling supplemental HEATING action");
break; break;
default: default:
@@ -660,24 +658,24 @@ void ThermostatClimate::switch_to_humidity_control_action_(HumidificationAction
return; return;
} }
Trigger<> *trig = this->humidity_control_off_action_trigger_; Trigger<> *trig = &this->humidity_control_off_action_trigger_;
switch (action) { switch (action) {
case THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF: case THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF:
// trig = this->humidity_control_off_action_trigger_; // trig = &this->humidity_control_off_action_trigger_;
ESP_LOGVV(TAG, "Switching to HUMIDIFICATION_OFF action"); ESP_LOGVV(TAG, "Switching to HUMIDIFICATION_OFF action");
break; break;
case THERMOSTAT_HUMIDITY_CONTROL_ACTION_DEHUMIDIFY: case THERMOSTAT_HUMIDITY_CONTROL_ACTION_DEHUMIDIFY:
trig = this->humidity_control_dehumidify_action_trigger_; trig = &this->humidity_control_dehumidify_action_trigger_;
ESP_LOGVV(TAG, "Switching to DEHUMIDIFY action"); ESP_LOGVV(TAG, "Switching to DEHUMIDIFY action");
break; break;
case THERMOSTAT_HUMIDITY_CONTROL_ACTION_HUMIDIFY: case THERMOSTAT_HUMIDITY_CONTROL_ACTION_HUMIDIFY:
trig = this->humidity_control_humidify_action_trigger_; trig = &this->humidity_control_humidify_action_trigger_;
ESP_LOGVV(TAG, "Switching to HUMIDIFY action"); ESP_LOGVV(TAG, "Switching to HUMIDIFY action");
break; break;
case THERMOSTAT_HUMIDITY_CONTROL_ACTION_NONE: case THERMOSTAT_HUMIDITY_CONTROL_ACTION_NONE:
default: default:
action = THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF; action = THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF;
// trig = this->humidity_control_off_action_trigger_; // trig = &this->humidity_control_off_action_trigger_;
} }
if (this->prev_humidity_control_trigger_ != nullptr) { if (this->prev_humidity_control_trigger_ != nullptr) {
@@ -686,9 +684,7 @@ void ThermostatClimate::switch_to_humidity_control_action_(HumidificationAction
} }
this->humidification_action = action; this->humidification_action = action;
this->prev_humidity_control_trigger_ = trig; this->prev_humidity_control_trigger_ = trig;
if (trig != nullptr) { trig->trigger();
trig->trigger();
}
} }
void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bool publish_state) { void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bool publish_state) {
@@ -703,62 +699,60 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bo
this->publish_state(); this->publish_state();
if (this->fan_mode_ready_()) { if (this->fan_mode_ready_()) {
Trigger<> *trig = this->fan_mode_auto_trigger_; Trigger<> *trig = &this->fan_mode_auto_trigger_;
switch (fan_mode) { switch (fan_mode) {
case climate::CLIMATE_FAN_ON: case climate::CLIMATE_FAN_ON:
trig = this->fan_mode_on_trigger_; trig = &this->fan_mode_on_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_ON mode"); ESP_LOGVV(TAG, "Switching to FAN_ON mode");
break; break;
case climate::CLIMATE_FAN_OFF: case climate::CLIMATE_FAN_OFF:
trig = this->fan_mode_off_trigger_; trig = &this->fan_mode_off_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_OFF mode"); ESP_LOGVV(TAG, "Switching to FAN_OFF mode");
break; break;
case climate::CLIMATE_FAN_AUTO: case climate::CLIMATE_FAN_AUTO:
// trig = this->fan_mode_auto_trigger_; // trig = &this->fan_mode_auto_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_AUTO mode"); ESP_LOGVV(TAG, "Switching to FAN_AUTO mode");
break; break;
case climate::CLIMATE_FAN_LOW: case climate::CLIMATE_FAN_LOW:
trig = this->fan_mode_low_trigger_; trig = &this->fan_mode_low_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_LOW mode"); ESP_LOGVV(TAG, "Switching to FAN_LOW mode");
break; break;
case climate::CLIMATE_FAN_MEDIUM: case climate::CLIMATE_FAN_MEDIUM:
trig = this->fan_mode_medium_trigger_; trig = &this->fan_mode_medium_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_MEDIUM mode"); ESP_LOGVV(TAG, "Switching to FAN_MEDIUM mode");
break; break;
case climate::CLIMATE_FAN_HIGH: case climate::CLIMATE_FAN_HIGH:
trig = this->fan_mode_high_trigger_; trig = &this->fan_mode_high_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_HIGH mode"); ESP_LOGVV(TAG, "Switching to FAN_HIGH mode");
break; break;
case climate::CLIMATE_FAN_MIDDLE: case climate::CLIMATE_FAN_MIDDLE:
trig = this->fan_mode_middle_trigger_; trig = &this->fan_mode_middle_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_MIDDLE mode"); ESP_LOGVV(TAG, "Switching to FAN_MIDDLE mode");
break; break;
case climate::CLIMATE_FAN_FOCUS: case climate::CLIMATE_FAN_FOCUS:
trig = this->fan_mode_focus_trigger_; trig = &this->fan_mode_focus_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_FOCUS mode"); ESP_LOGVV(TAG, "Switching to FAN_FOCUS mode");
break; break;
case climate::CLIMATE_FAN_DIFFUSE: case climate::CLIMATE_FAN_DIFFUSE:
trig = this->fan_mode_diffuse_trigger_; trig = &this->fan_mode_diffuse_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_DIFFUSE mode"); ESP_LOGVV(TAG, "Switching to FAN_DIFFUSE mode");
break; break;
case climate::CLIMATE_FAN_QUIET: case climate::CLIMATE_FAN_QUIET:
trig = this->fan_mode_quiet_trigger_; trig = &this->fan_mode_quiet_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_QUIET mode"); ESP_LOGVV(TAG, "Switching to FAN_QUIET mode");
break; break;
default: default:
// we cannot report an invalid mode back to HA (even if it asked for one) // we cannot report an invalid mode back to HA (even if it asked for one)
// and must assume some valid value // and must assume some valid value
fan_mode = climate::CLIMATE_FAN_AUTO; fan_mode = climate::CLIMATE_FAN_AUTO;
// trig = this->fan_mode_auto_trigger_; // trig = &this->fan_mode_auto_trigger_;
} }
if (this->prev_fan_mode_trigger_ != nullptr) { if (this->prev_fan_mode_trigger_ != nullptr) {
this->prev_fan_mode_trigger_->stop_action(); this->prev_fan_mode_trigger_->stop_action();
this->prev_fan_mode_trigger_ = nullptr; this->prev_fan_mode_trigger_ = nullptr;
} }
this->start_timer_(thermostat::THERMOSTAT_TIMER_FAN_MODE); this->start_timer_(thermostat::THERMOSTAT_TIMER_FAN_MODE);
if (trig != nullptr) { trig->trigger();
trig->trigger();
}
this->prev_fan_mode_ = fan_mode; this->prev_fan_mode_ = fan_mode;
this->prev_fan_mode_trigger_ = trig; this->prev_fan_mode_trigger_ = trig;
} }
@@ -775,25 +769,25 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_
this->prev_mode_trigger_->stop_action(); this->prev_mode_trigger_->stop_action();
this->prev_mode_trigger_ = nullptr; this->prev_mode_trigger_ = nullptr;
} }
Trigger<> *trig = this->off_mode_trigger_; Trigger<> *trig = &this->off_mode_trigger_;
switch (mode) { switch (mode) {
case climate::CLIMATE_MODE_AUTO: case climate::CLIMATE_MODE_AUTO:
trig = this->auto_mode_trigger_; trig = &this->auto_mode_trigger_;
break; break;
case climate::CLIMATE_MODE_HEAT_COOL: case climate::CLIMATE_MODE_HEAT_COOL:
trig = this->heat_cool_mode_trigger_; trig = &this->heat_cool_mode_trigger_;
break; break;
case climate::CLIMATE_MODE_COOL: case climate::CLIMATE_MODE_COOL:
trig = this->cool_mode_trigger_; trig = &this->cool_mode_trigger_;
break; break;
case climate::CLIMATE_MODE_HEAT: case climate::CLIMATE_MODE_HEAT:
trig = this->heat_mode_trigger_; trig = &this->heat_mode_trigger_;
break; break;
case climate::CLIMATE_MODE_FAN_ONLY: case climate::CLIMATE_MODE_FAN_ONLY:
trig = this->fan_only_mode_trigger_; trig = &this->fan_only_mode_trigger_;
break; break;
case climate::CLIMATE_MODE_DRY: case climate::CLIMATE_MODE_DRY:
trig = this->dry_mode_trigger_; trig = &this->dry_mode_trigger_;
break; break;
case climate::CLIMATE_MODE_OFF: case climate::CLIMATE_MODE_OFF:
default: default:
@@ -802,9 +796,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_
mode = climate::CLIMATE_MODE_OFF; mode = climate::CLIMATE_MODE_OFF;
// trig = this->off_mode_trigger_; // trig = this->off_mode_trigger_;
} }
if (trig != nullptr) { trig->trigger();
trig->trigger();
}
this->mode = mode; this->mode = mode;
this->prev_mode_ = mode; this->prev_mode_ = mode;
this->prev_mode_trigger_ = trig; this->prev_mode_trigger_ = trig;
@@ -824,29 +816,27 @@ void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mo
this->prev_swing_mode_trigger_->stop_action(); this->prev_swing_mode_trigger_->stop_action();
this->prev_swing_mode_trigger_ = nullptr; this->prev_swing_mode_trigger_ = nullptr;
} }
Trigger<> *trig = this->swing_mode_off_trigger_; Trigger<> *trig = &this->swing_mode_off_trigger_;
switch (swing_mode) { switch (swing_mode) {
case climate::CLIMATE_SWING_BOTH: case climate::CLIMATE_SWING_BOTH:
trig = this->swing_mode_both_trigger_; trig = &this->swing_mode_both_trigger_;
break; break;
case climate::CLIMATE_SWING_HORIZONTAL: case climate::CLIMATE_SWING_HORIZONTAL:
trig = this->swing_mode_horizontal_trigger_; trig = &this->swing_mode_horizontal_trigger_;
break; break;
case climate::CLIMATE_SWING_OFF: case climate::CLIMATE_SWING_OFF:
// trig = this->swing_mode_off_trigger_; // trig = &this->swing_mode_off_trigger_;
break; break;
case climate::CLIMATE_SWING_VERTICAL: case climate::CLIMATE_SWING_VERTICAL:
trig = this->swing_mode_vertical_trigger_; trig = &this->swing_mode_vertical_trigger_;
break; break;
default: default:
// we cannot report an invalid mode back to HA (even if it asked for one) // we cannot report an invalid mode back to HA (even if it asked for one)
// and must assume some valid value // and must assume some valid value
swing_mode = climate::CLIMATE_SWING_OFF; swing_mode = climate::CLIMATE_SWING_OFF;
// trig = this->swing_mode_off_trigger_; // trig = &this->swing_mode_off_trigger_;
}
if (trig != nullptr) {
trig->trigger();
} }
trig->trigger();
this->swing_mode = swing_mode; this->swing_mode = swing_mode;
this->prev_swing_mode_ = swing_mode; this->prev_swing_mode_ = swing_mode;
this->prev_swing_mode_trigger_ = trig; this->prev_swing_mode_trigger_ = trig;
@@ -1024,10 +1014,8 @@ void ThermostatClimate::check_humidity_change_trigger_() {
this->prev_target_humidity_ = this->target_humidity; this->prev_target_humidity_ = this->target_humidity;
} }
// trigger the action // trigger the action
Trigger<> *trig = this->humidity_change_trigger_; Trigger<> *trig = &this->humidity_change_trigger_;
if (trig != nullptr) { trig->trigger();
trig->trigger();
}
} }
void ThermostatClimate::check_temperature_change_trigger_() { void ThermostatClimate::check_temperature_change_trigger_() {
@@ -1050,10 +1038,8 @@ void ThermostatClimate::check_temperature_change_trigger_() {
} }
} }
// trigger the action // trigger the action
Trigger<> *trig = this->temperature_change_trigger_; Trigger<> *trig = &this->temperature_change_trigger_;
if (trig != nullptr) { trig->trigger();
trig->trigger();
}
} }
bool ThermostatClimate::cooling_required_() { bool ThermostatClimate::cooling_required_() {
@@ -1202,12 +1188,10 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) {
if (config != nullptr) { if (config != nullptr) {
ESP_LOGV(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset))); ESP_LOGV(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
if (this->change_preset_internal_(*config) || (!this->preset.has_value()) || this->preset.value() != preset) { if (this->change_preset_internal_(*config) || (!this->preset.has_value()) || this->preset.value() != preset) {
// Fire any preset changed trigger if defined // Fire preset changed trigger
Trigger<> *trig = this->preset_change_trigger_; Trigger<> *trig = &this->preset_change_trigger_;
this->set_preset_(preset); this->set_preset_(preset);
if (trig != nullptr) { trig->trigger();
trig->trigger();
}
this->refresh(); this->refresh();
ESP_LOGI(TAG, "Preset %s applied", LOG_STR_ARG(climate::climate_preset_to_string(preset))); ESP_LOGI(TAG, "Preset %s applied", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
@@ -1234,13 +1218,11 @@ void ThermostatClimate::change_custom_preset_(const char *custom_preset, size_t
ESP_LOGV(TAG, "Custom preset %s requested", custom_preset); ESP_LOGV(TAG, "Custom preset %s requested", custom_preset);
if (this->change_preset_internal_(*config) || !this->has_custom_preset() || if (this->change_preset_internal_(*config) || !this->has_custom_preset() ||
this->get_custom_preset() != custom_preset) { this->get_custom_preset() != custom_preset) {
// Fire any preset changed trigger if defined // Fire preset changed trigger
Trigger<> *trig = this->preset_change_trigger_; Trigger<> *trig = &this->preset_change_trigger_;
// Use the base class method which handles pointer lookup and preset reset internally // Use the base class method which handles pointer lookup and preset reset internally
this->set_custom_preset_(custom_preset); this->set_custom_preset_(custom_preset);
if (trig != nullptr) { trig->trigger();
trig->trigger();
}
this->refresh(); this->refresh();
ESP_LOGI(TAG, "Custom preset %s applied", custom_preset); ESP_LOGI(TAG, "Custom preset %s applied", custom_preset);
@@ -1305,41 +1287,7 @@ void ThermostatClimate::set_custom_preset_config(std::initializer_list<CustomPre
this->custom_preset_config_ = presets; this->custom_preset_config_ = presets;
} }
ThermostatClimate::ThermostatClimate() ThermostatClimate::ThermostatClimate() = default;
: cool_action_trigger_(new Trigger<>()),
supplemental_cool_action_trigger_(new Trigger<>()),
cool_mode_trigger_(new Trigger<>()),
dry_action_trigger_(new Trigger<>()),
dry_mode_trigger_(new Trigger<>()),
heat_action_trigger_(new Trigger<>()),
supplemental_heat_action_trigger_(new Trigger<>()),
heat_mode_trigger_(new Trigger<>()),
heat_cool_mode_trigger_(new Trigger<>()),
auto_mode_trigger_(new Trigger<>()),
idle_action_trigger_(new Trigger<>()),
off_mode_trigger_(new Trigger<>()),
fan_only_action_trigger_(new Trigger<>()),
fan_only_mode_trigger_(new Trigger<>()),
fan_mode_on_trigger_(new Trigger<>()),
fan_mode_off_trigger_(new Trigger<>()),
fan_mode_auto_trigger_(new Trigger<>()),
fan_mode_low_trigger_(new Trigger<>()),
fan_mode_medium_trigger_(new Trigger<>()),
fan_mode_high_trigger_(new Trigger<>()),
fan_mode_middle_trigger_(new Trigger<>()),
fan_mode_focus_trigger_(new Trigger<>()),
fan_mode_diffuse_trigger_(new Trigger<>()),
fan_mode_quiet_trigger_(new Trigger<>()),
swing_mode_both_trigger_(new Trigger<>()),
swing_mode_off_trigger_(new Trigger<>()),
swing_mode_horizontal_trigger_(new Trigger<>()),
swing_mode_vertical_trigger_(new Trigger<>()),
humidity_change_trigger_(new Trigger<>()),
temperature_change_trigger_(new Trigger<>()),
preset_change_trigger_(new Trigger<>()),
humidity_control_dehumidify_action_trigger_(new Trigger<>()),
humidity_control_humidify_action_trigger_(new Trigger<>()),
humidity_control_off_action_trigger_(new Trigger<>()) {}
void ThermostatClimate::set_default_preset(const char *custom_preset) { void ThermostatClimate::set_default_preset(const char *custom_preset) {
// Find the preset in custom_preset_config_ and store pointer from there // Find the preset in custom_preset_config_ and store pointer from there
@@ -1513,49 +1461,49 @@ void ThermostatClimate::set_supports_humidification(bool supports_humidification
} }
} }
Trigger<> *ThermostatClimate::get_cool_action_trigger() const { return this->cool_action_trigger_; } Trigger<> *ThermostatClimate::get_cool_action_trigger() { return &this->cool_action_trigger_; }
Trigger<> *ThermostatClimate::get_supplemental_cool_action_trigger() const { Trigger<> *ThermostatClimate::get_supplemental_cool_action_trigger() {
return this->supplemental_cool_action_trigger_; return &this->supplemental_cool_action_trigger_;
} }
Trigger<> *ThermostatClimate::get_dry_action_trigger() const { return this->dry_action_trigger_; } Trigger<> *ThermostatClimate::get_dry_action_trigger() { return &this->dry_action_trigger_; }
Trigger<> *ThermostatClimate::get_fan_only_action_trigger() const { return this->fan_only_action_trigger_; } Trigger<> *ThermostatClimate::get_fan_only_action_trigger() { return &this->fan_only_action_trigger_; }
Trigger<> *ThermostatClimate::get_heat_action_trigger() const { return this->heat_action_trigger_; } Trigger<> *ThermostatClimate::get_heat_action_trigger() { return &this->heat_action_trigger_; }
Trigger<> *ThermostatClimate::get_supplemental_heat_action_trigger() const { Trigger<> *ThermostatClimate::get_supplemental_heat_action_trigger() {
return this->supplemental_heat_action_trigger_; return &this->supplemental_heat_action_trigger_;
} }
Trigger<> *ThermostatClimate::get_idle_action_trigger() const { return this->idle_action_trigger_; } Trigger<> *ThermostatClimate::get_idle_action_trigger() { return &this->idle_action_trigger_; }
Trigger<> *ThermostatClimate::get_auto_mode_trigger() const { return this->auto_mode_trigger_; } Trigger<> *ThermostatClimate::get_auto_mode_trigger() { return &this->auto_mode_trigger_; }
Trigger<> *ThermostatClimate::get_cool_mode_trigger() const { return this->cool_mode_trigger_; } Trigger<> *ThermostatClimate::get_cool_mode_trigger() { return &this->cool_mode_trigger_; }
Trigger<> *ThermostatClimate::get_dry_mode_trigger() const { return this->dry_mode_trigger_; } Trigger<> *ThermostatClimate::get_dry_mode_trigger() { return &this->dry_mode_trigger_; }
Trigger<> *ThermostatClimate::get_fan_only_mode_trigger() const { return this->fan_only_mode_trigger_; } Trigger<> *ThermostatClimate::get_fan_only_mode_trigger() { return &this->fan_only_mode_trigger_; }
Trigger<> *ThermostatClimate::get_heat_mode_trigger() const { return this->heat_mode_trigger_; } Trigger<> *ThermostatClimate::get_heat_mode_trigger() { return &this->heat_mode_trigger_; }
Trigger<> *ThermostatClimate::get_heat_cool_mode_trigger() const { return this->heat_cool_mode_trigger_; } Trigger<> *ThermostatClimate::get_heat_cool_mode_trigger() { return &this->heat_cool_mode_trigger_; }
Trigger<> *ThermostatClimate::get_off_mode_trigger() const { return this->off_mode_trigger_; } Trigger<> *ThermostatClimate::get_off_mode_trigger() { return &this->off_mode_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_on_trigger() const { return this->fan_mode_on_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_on_trigger() { return &this->fan_mode_on_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_off_trigger() const { return this->fan_mode_off_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_off_trigger() { return &this->fan_mode_off_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_auto_trigger() const { return this->fan_mode_auto_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_auto_trigger() { return &this->fan_mode_auto_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_low_trigger() const { return this->fan_mode_low_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_low_trigger() { return &this->fan_mode_low_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_medium_trigger() const { return this->fan_mode_medium_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_medium_trigger() { return &this->fan_mode_medium_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_high_trigger() const { return this->fan_mode_high_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_high_trigger() { return &this->fan_mode_high_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_middle_trigger() const { return this->fan_mode_middle_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_middle_trigger() { return &this->fan_mode_middle_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_focus_trigger() const { return this->fan_mode_focus_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_focus_trigger() { return &this->fan_mode_focus_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_diffuse_trigger() const { return this->fan_mode_diffuse_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_diffuse_trigger() { return &this->fan_mode_diffuse_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_quiet_trigger() const { return this->fan_mode_quiet_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_quiet_trigger() { return &this->fan_mode_quiet_trigger_; }
Trigger<> *ThermostatClimate::get_swing_mode_both_trigger() const { return this->swing_mode_both_trigger_; } Trigger<> *ThermostatClimate::get_swing_mode_both_trigger() { return &this->swing_mode_both_trigger_; }
Trigger<> *ThermostatClimate::get_swing_mode_off_trigger() const { return this->swing_mode_off_trigger_; } Trigger<> *ThermostatClimate::get_swing_mode_off_trigger() { return &this->swing_mode_off_trigger_; }
Trigger<> *ThermostatClimate::get_swing_mode_horizontal_trigger() const { return this->swing_mode_horizontal_trigger_; } Trigger<> *ThermostatClimate::get_swing_mode_horizontal_trigger() { return &this->swing_mode_horizontal_trigger_; }
Trigger<> *ThermostatClimate::get_swing_mode_vertical_trigger() const { return this->swing_mode_vertical_trigger_; } Trigger<> *ThermostatClimate::get_swing_mode_vertical_trigger() { return &this->swing_mode_vertical_trigger_; }
Trigger<> *ThermostatClimate::get_humidity_change_trigger() const { return this->humidity_change_trigger_; } Trigger<> *ThermostatClimate::get_humidity_change_trigger() { return &this->humidity_change_trigger_; }
Trigger<> *ThermostatClimate::get_temperature_change_trigger() const { return this->temperature_change_trigger_; } Trigger<> *ThermostatClimate::get_temperature_change_trigger() { return &this->temperature_change_trigger_; }
Trigger<> *ThermostatClimate::get_preset_change_trigger() const { return this->preset_change_trigger_; } Trigger<> *ThermostatClimate::get_preset_change_trigger() { return &this->preset_change_trigger_; }
Trigger<> *ThermostatClimate::get_humidity_control_dehumidify_action_trigger() const { Trigger<> *ThermostatClimate::get_humidity_control_dehumidify_action_trigger() {
return this->humidity_control_dehumidify_action_trigger_; return &this->humidity_control_dehumidify_action_trigger_;
} }
Trigger<> *ThermostatClimate::get_humidity_control_humidify_action_trigger() const { Trigger<> *ThermostatClimate::get_humidity_control_humidify_action_trigger() {
return this->humidity_control_humidify_action_trigger_; return &this->humidity_control_humidify_action_trigger_;
} }
Trigger<> *ThermostatClimate::get_humidity_control_off_action_trigger() const { Trigger<> *ThermostatClimate::get_humidity_control_off_action_trigger() {
return this->humidity_control_off_action_trigger_; return &this->humidity_control_off_action_trigger_;
} }
void ThermostatClimate::dump_config() { void ThermostatClimate::dump_config() {

View File

@@ -146,40 +146,40 @@ class ThermostatClimate : public climate::Climate, public Component {
void set_preset_config(std::initializer_list<PresetEntry> presets); void set_preset_config(std::initializer_list<PresetEntry> presets);
void set_custom_preset_config(std::initializer_list<CustomPresetEntry> presets); void set_custom_preset_config(std::initializer_list<CustomPresetEntry> presets);
Trigger<> *get_cool_action_trigger() const; Trigger<> *get_cool_action_trigger();
Trigger<> *get_supplemental_cool_action_trigger() const; Trigger<> *get_supplemental_cool_action_trigger();
Trigger<> *get_dry_action_trigger() const; Trigger<> *get_dry_action_trigger();
Trigger<> *get_fan_only_action_trigger() const; Trigger<> *get_fan_only_action_trigger();
Trigger<> *get_heat_action_trigger() const; Trigger<> *get_heat_action_trigger();
Trigger<> *get_supplemental_heat_action_trigger() const; Trigger<> *get_supplemental_heat_action_trigger();
Trigger<> *get_idle_action_trigger() const; Trigger<> *get_idle_action_trigger();
Trigger<> *get_auto_mode_trigger() const; Trigger<> *get_auto_mode_trigger();
Trigger<> *get_cool_mode_trigger() const; Trigger<> *get_cool_mode_trigger();
Trigger<> *get_dry_mode_trigger() const; Trigger<> *get_dry_mode_trigger();
Trigger<> *get_fan_only_mode_trigger() const; Trigger<> *get_fan_only_mode_trigger();
Trigger<> *get_heat_mode_trigger() const; Trigger<> *get_heat_mode_trigger();
Trigger<> *get_heat_cool_mode_trigger() const; Trigger<> *get_heat_cool_mode_trigger();
Trigger<> *get_off_mode_trigger() const; Trigger<> *get_off_mode_trigger();
Trigger<> *get_fan_mode_on_trigger() const; Trigger<> *get_fan_mode_on_trigger();
Trigger<> *get_fan_mode_off_trigger() const; Trigger<> *get_fan_mode_off_trigger();
Trigger<> *get_fan_mode_auto_trigger() const; Trigger<> *get_fan_mode_auto_trigger();
Trigger<> *get_fan_mode_low_trigger() const; Trigger<> *get_fan_mode_low_trigger();
Trigger<> *get_fan_mode_medium_trigger() const; Trigger<> *get_fan_mode_medium_trigger();
Trigger<> *get_fan_mode_high_trigger() const; Trigger<> *get_fan_mode_high_trigger();
Trigger<> *get_fan_mode_middle_trigger() const; Trigger<> *get_fan_mode_middle_trigger();
Trigger<> *get_fan_mode_focus_trigger() const; Trigger<> *get_fan_mode_focus_trigger();
Trigger<> *get_fan_mode_diffuse_trigger() const; Trigger<> *get_fan_mode_diffuse_trigger();
Trigger<> *get_fan_mode_quiet_trigger() const; Trigger<> *get_fan_mode_quiet_trigger();
Trigger<> *get_swing_mode_both_trigger() const; Trigger<> *get_swing_mode_both_trigger();
Trigger<> *get_swing_mode_horizontal_trigger() const; Trigger<> *get_swing_mode_horizontal_trigger();
Trigger<> *get_swing_mode_off_trigger() const; Trigger<> *get_swing_mode_off_trigger();
Trigger<> *get_swing_mode_vertical_trigger() const; Trigger<> *get_swing_mode_vertical_trigger();
Trigger<> *get_humidity_change_trigger() const; Trigger<> *get_humidity_change_trigger();
Trigger<> *get_temperature_change_trigger() const; Trigger<> *get_temperature_change_trigger();
Trigger<> *get_preset_change_trigger() const; Trigger<> *get_preset_change_trigger();
Trigger<> *get_humidity_control_dehumidify_action_trigger() const; Trigger<> *get_humidity_control_dehumidify_action_trigger();
Trigger<> *get_humidity_control_humidify_action_trigger() const; Trigger<> *get_humidity_control_humidify_action_trigger();
Trigger<> *get_humidity_control_off_action_trigger() const; Trigger<> *get_humidity_control_off_action_trigger();
/// Get current hysteresis values /// Get current hysteresis values
float cool_deadband(); float cool_deadband();
float cool_overrun(); float cool_overrun();
@@ -417,115 +417,65 @@ class ThermostatClimate : public climate::Climate, public Component {
/// The sensor used for getting the current humidity /// The sensor used for getting the current humidity
sensor::Sensor *humidity_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr};
/// The trigger to call when the controller should switch to cooling action/mode. /// Trigger for cooling action/mode
/// Trigger<> cool_action_trigger_;
/// A null value for this attribute means that the controller has no cooling action Trigger<> supplemental_cool_action_trigger_;
/// For example electric heat, where only heating (power on) and not-heating Trigger<> cool_mode_trigger_;
/// (power off) is possible.
Trigger<> *cool_action_trigger_{nullptr};
Trigger<> *supplemental_cool_action_trigger_{nullptr};
Trigger<> *cool_mode_trigger_{nullptr};
/// The trigger to call when the controller should switch to dry (dehumidification) mode. /// Trigger for dry (dehumidification) mode
/// Trigger<> dry_action_trigger_;
/// In dry mode, the controller is assumed to have both heating and cooling disabled, Trigger<> dry_mode_trigger_;
/// although the system may use its cooling mechanism to achieve drying.
Trigger<> *dry_action_trigger_{nullptr};
Trigger<> *dry_mode_trigger_{nullptr};
/// The trigger to call when the controller should switch to heating action/mode. /// Trigger for heating action/mode
/// Trigger<> heat_action_trigger_;
/// A null value for this attribute means that the controller has no heating action Trigger<> supplemental_heat_action_trigger_;
/// For example window blinds, where only cooling (blinds closed) and not-cooling Trigger<> heat_mode_trigger_;
/// (blinds open) is possible.
Trigger<> *heat_action_trigger_{nullptr};
Trigger<> *supplemental_heat_action_trigger_{nullptr};
Trigger<> *heat_mode_trigger_{nullptr};
/// The trigger to call when the controller should switch to heat/cool mode. /// Trigger for heat/cool mode
/// Trigger<> heat_cool_mode_trigger_;
/// In heat/cool mode, the controller will enable heating/cooling as necessary and switch
/// to idle when the temperature is within the thresholds/set points.
Trigger<> *heat_cool_mode_trigger_{nullptr};
/// The trigger to call when the controller should switch to auto mode. /// Trigger for auto mode
/// Trigger<> auto_mode_trigger_;
/// In auto mode, the controller will enable heating/cooling as supported/necessary and switch
/// to idle when the temperature is within the thresholds/set points.
Trigger<> *auto_mode_trigger_{nullptr};
/// The trigger to call when the controller should switch to idle action/off mode. /// Trigger for idle action/off mode
/// Trigger<> idle_action_trigger_;
/// In these actions/modes, the controller is assumed to have both heating and cooling disabled. Trigger<> off_mode_trigger_;
Trigger<> *idle_action_trigger_{nullptr};
Trigger<> *off_mode_trigger_{nullptr};
/// The trigger to call when the controller should switch to fan-only action/mode. /// Trigger for fan-only action/mode
/// Trigger<> fan_only_action_trigger_;
/// In fan-only mode, the controller is assumed to have both heating and cooling disabled. Trigger<> fan_only_mode_trigger_;
/// The system should activate the fan only.
Trigger<> *fan_only_action_trigger_{nullptr};
Trigger<> *fan_only_mode_trigger_{nullptr};
/// The trigger to call when the controller should switch on the fan. /// Fan mode triggers
Trigger<> *fan_mode_on_trigger_{nullptr}; Trigger<> fan_mode_on_trigger_;
Trigger<> fan_mode_off_trigger_;
Trigger<> fan_mode_auto_trigger_;
Trigger<> fan_mode_low_trigger_;
Trigger<> fan_mode_medium_trigger_;
Trigger<> fan_mode_high_trigger_;
Trigger<> fan_mode_middle_trigger_;
Trigger<> fan_mode_focus_trigger_;
Trigger<> fan_mode_diffuse_trigger_;
Trigger<> fan_mode_quiet_trigger_;
/// The trigger to call when the controller should switch off the fan. /// Swing mode triggers
Trigger<> *fan_mode_off_trigger_{nullptr}; Trigger<> swing_mode_both_trigger_;
Trigger<> swing_mode_off_trigger_;
Trigger<> swing_mode_horizontal_trigger_;
Trigger<> swing_mode_vertical_trigger_;
/// The trigger to call when the controller should switch the fan to "auto" mode. /// Trigger for target humidity changes
Trigger<> *fan_mode_auto_trigger_{nullptr}; Trigger<> humidity_change_trigger_;
/// The trigger to call when the controller should switch the fan to "low" speed. /// Trigger for target temperature changes
Trigger<> *fan_mode_low_trigger_{nullptr}; Trigger<> temperature_change_trigger_;
/// The trigger to call when the controller should switch the fan to "medium" speed. /// Trigger for preset mode changes
Trigger<> *fan_mode_medium_trigger_{nullptr}; Trigger<> preset_change_trigger_;
/// The trigger to call when the controller should switch the fan to "high" speed. /// Humidity control triggers
Trigger<> *fan_mode_high_trigger_{nullptr}; Trigger<> humidity_control_dehumidify_action_trigger_;
Trigger<> humidity_control_humidify_action_trigger_;
/// The trigger to call when the controller should switch the fan to "middle" position. Trigger<> humidity_control_off_action_trigger_;
Trigger<> *fan_mode_middle_trigger_{nullptr};
/// The trigger to call when the controller should switch the fan to "focus" position.
Trigger<> *fan_mode_focus_trigger_{nullptr};
/// The trigger to call when the controller should switch the fan to "diffuse" position.
Trigger<> *fan_mode_diffuse_trigger_{nullptr};
/// The trigger to call when the controller should switch the fan to "quiet" position.
Trigger<> *fan_mode_quiet_trigger_{nullptr};
/// The trigger to call when the controller should switch the swing mode to "both".
Trigger<> *swing_mode_both_trigger_{nullptr};
/// The trigger to call when the controller should switch the swing mode to "off".
Trigger<> *swing_mode_off_trigger_{nullptr};
/// The trigger to call when the controller should switch the swing mode to "horizontal".
Trigger<> *swing_mode_horizontal_trigger_{nullptr};
/// The trigger to call when the controller should switch the swing mode to "vertical".
Trigger<> *swing_mode_vertical_trigger_{nullptr};
/// The trigger to call when the target humidity changes.
Trigger<> *humidity_change_trigger_{nullptr};
/// The trigger to call when the target temperature(s) change(es).
Trigger<> *temperature_change_trigger_{nullptr};
/// The trigger to call when the preset mode changes
Trigger<> *preset_change_trigger_{nullptr};
/// The trigger to call when dehumidification is required
Trigger<> *humidity_control_dehumidify_action_trigger_{nullptr};
/// The trigger to call when humidification is required
Trigger<> *humidity_control_humidify_action_trigger_{nullptr};
/// The trigger to call when (de)humidification should stop
Trigger<> *humidity_control_off_action_trigger_{nullptr};
/// A reference to the trigger that was previously active. /// A reference to the trigger that was previously active.
/// ///

View File

@@ -62,7 +62,7 @@ class RealTimeClock : public PollingComponent {
void apply_timezone_(); void apply_timezone_();
#endif #endif
CallbackManager<void()> time_sync_callback_; LazyCallbackManager<void()> time_sync_callback_;
}; };
template<typename... Ts> class TimeHasTimeCondition : public Condition<Ts...> { template<typename... Ts> class TimeHasTimeCondition : public Condition<Ts...> {

View File

@@ -132,15 +132,15 @@ void TimeBasedCover::start_direction_(CoverOperation dir) {
Trigger<> *trig; Trigger<> *trig;
switch (dir) { switch (dir) {
case COVER_OPERATION_IDLE: case COVER_OPERATION_IDLE:
trig = this->stop_trigger_; trig = &this->stop_trigger_;
break; break;
case COVER_OPERATION_OPENING: case COVER_OPERATION_OPENING:
this->last_operation_ = dir; this->last_operation_ = dir;
trig = this->open_trigger_; trig = &this->open_trigger_;
break; break;
case COVER_OPERATION_CLOSING: case COVER_OPERATION_CLOSING:
this->last_operation_ = dir; this->last_operation_ = dir;
trig = this->close_trigger_; trig = &this->close_trigger_;
break; break;
default: default:
return; return;

View File

@@ -14,9 +14,9 @@ class TimeBasedCover : public cover::Cover, public Component {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
Trigger<> *get_open_trigger() const { return this->open_trigger_; } Trigger<> *get_open_trigger() { return &this->open_trigger_; }
Trigger<> *get_close_trigger() const { return this->close_trigger_; } Trigger<> *get_close_trigger() { return &this->close_trigger_; }
Trigger<> *get_stop_trigger() const { return this->stop_trigger_; } Trigger<> *get_stop_trigger() { return &this->stop_trigger_; }
void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; } void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; }
void set_close_duration(uint32_t close_duration) { this->close_duration_ = close_duration; } void set_close_duration(uint32_t close_duration) { this->close_duration_ = close_duration; }
cover::CoverTraits get_traits() override; cover::CoverTraits get_traits() override;
@@ -34,11 +34,11 @@ class TimeBasedCover : public cover::Cover, public Component {
void recompute_position_(); void recompute_position_();
Trigger<> *open_trigger_{new Trigger<>()}; Trigger<> open_trigger_;
uint32_t open_duration_; uint32_t open_duration_;
Trigger<> *close_trigger_{new Trigger<>()}; Trigger<> close_trigger_;
uint32_t close_duration_; uint32_t close_duration_;
Trigger<> *stop_trigger_{new Trigger<>()}; Trigger<> stop_trigger_;
Trigger<> *prev_command_trigger_{nullptr}; Trigger<> *prev_command_trigger_{nullptr};
uint32_t last_recompute_time_{0}; uint32_t last_recompute_time_{0};

View File

@@ -12,8 +12,8 @@ from esphome.components.packet_transport import (
) )
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_DATA, CONF_ID, CONF_PORT, CONF_TRIGGER_ID from esphome.const import CONF_DATA, CONF_ID, CONF_PORT, CONF_TRIGGER_ID
from esphome.core import ID, Lambda from esphome.core import ID
from esphome.cpp_generator import ExpressionStatement, MockObj from esphome.cpp_generator import literal
CODEOWNERS = ["@clydebarrow"] CODEOWNERS = ["@clydebarrow"]
DEPENDENCIES = ["network"] DEPENDENCIES = ["network"]
@@ -24,6 +24,8 @@ udp_ns = cg.esphome_ns.namespace("udp")
UDPComponent = udp_ns.class_("UDPComponent", cg.Component) UDPComponent = udp_ns.class_("UDPComponent", cg.Component)
UDPWriteAction = udp_ns.class_("UDPWriteAction", automation.Action) UDPWriteAction = udp_ns.class_("UDPWriteAction", automation.Action)
trigger_args = cg.std_vector.template(cg.uint8) trigger_args = cg.std_vector.template(cg.uint8)
trigger_argname = "data"
trigger_argtype = [(trigger_args, trigger_argname)]
CONF_ADDRESSES = "addresses" CONF_ADDRESSES = "addresses"
CONF_LISTEN_ADDRESS = "listen_address" CONF_LISTEN_ADDRESS = "listen_address"
@@ -111,13 +113,14 @@ async def to_code(config):
cg.add(var.set_addresses([str(addr) for addr in config[CONF_ADDRESSES]])) cg.add(var.set_addresses([str(addr) for addr in config[CONF_ADDRESSES]]))
if on_receive := config.get(CONF_ON_RECEIVE): if on_receive := config.get(CONF_ON_RECEIVE):
on_receive = on_receive[0] on_receive = on_receive[0]
trigger = cg.new_Pvariable(on_receive[CONF_TRIGGER_ID]) trigger_id = cg.new_Pvariable(on_receive[CONF_TRIGGER_ID])
trigger = await automation.build_automation( trigger = await automation.build_automation(
trigger, [(trigger_args, "data")], on_receive trigger_id, trigger_argtype, on_receive
) )
trigger = Lambda(str(ExpressionStatement(trigger.trigger(MockObj("data"))))) trigger_lambda = await cg.process_lambda(
trigger = await cg.process_lambda(trigger, [(trigger_args, "data")]) trigger.trigger(literal(trigger_argname)), trigger_argtype
cg.add(var.add_listener(trigger)) )
cg.add(var.add_listener(trigger_lambda))
cg.add(var.set_should_listen()) cg.add(var.set_should_listen())

View File

@@ -155,8 +155,9 @@ void USBCDCACMInstance::setup() {
return; return;
} }
// Use a larger stack size for (very) verbose logging // Use a larger stack size for very verbose logging
const size_t stack_size = esp_log_level_get(TAG) > ESP_LOG_DEBUG ? USB_TX_TASK_STACK_SIZE_VV : USB_TX_TASK_STACK_SIZE; constexpr size_t stack_size =
ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE ? USB_TX_TASK_STACK_SIZE_VV : USB_TX_TASK_STACK_SIZE;
// Create a simple, unique task name per interface // Create a simple, unique task name per interface
char task_name[] = "usb_tx_0"; char task_name[] = "usb_tx_0";

View File

@@ -197,7 +197,7 @@ void VoiceAssistant::loop() {
switch (this->state_) { switch (this->state_) {
case State::IDLE: { case State::IDLE: {
if (this->continuous_ && this->desired_state_ == State::IDLE) { if (this->continuous_ && this->desired_state_ == State::IDLE) {
this->idle_trigger_->trigger(); this->idle_trigger_.trigger();
this->set_state_(State::START_MICROPHONE, State::START_PIPELINE); this->set_state_(State::START_MICROPHONE, State::START_PIPELINE);
} else { } else {
this->deallocate_buffers_(); this->deallocate_buffers_();
@@ -254,7 +254,7 @@ void VoiceAssistant::loop() {
if (this->api_client_ == nullptr || if (this->api_client_ == nullptr ||
!this->api_client_->send_message(msg, api::VoiceAssistantRequest::MESSAGE_TYPE)) { !this->api_client_->send_message(msg, api::VoiceAssistantRequest::MESSAGE_TYPE)) {
ESP_LOGW(TAG, "Could not request start"); ESP_LOGW(TAG, "Could not request start");
this->error_trigger_->trigger("not-connected", "Could not request start"); this->error_trigger_.trigger("not-connected", "Could not request start");
this->continuous_ = false; this->continuous_ = false;
this->set_state_(State::IDLE, State::IDLE); this->set_state_(State::IDLE, State::IDLE);
break; break;
@@ -384,7 +384,7 @@ void VoiceAssistant::loop() {
this->wait_for_stream_end_ = false; this->wait_for_stream_end_ = false;
this->stream_ended_ = false; this->stream_ended_ = false;
this->tts_stream_end_trigger_->trigger(); this->tts_stream_end_trigger_.trigger();
} }
#endif #endif
if (this->continue_conversation_) { if (this->continue_conversation_) {
@@ -425,7 +425,7 @@ void VoiceAssistant::client_subscription(api::APIConnection *client, bool subscr
return; return;
} }
this->api_client_ = nullptr; this->api_client_ = nullptr;
this->client_disconnected_trigger_->trigger(); this->client_disconnected_trigger_.trigger();
return; return;
} }
@@ -440,7 +440,7 @@ void VoiceAssistant::client_subscription(api::APIConnection *client, bool subscr
} }
this->api_client_ = client; this->api_client_ = client;
this->client_connected_trigger_->trigger(); this->client_connected_trigger_.trigger();
} }
static const LogString *voice_assistant_state_to_string(State state) { static const LogString *voice_assistant_state_to_string(State state) {
@@ -491,7 +491,7 @@ void VoiceAssistant::set_state_(State state, State desired_state) {
void VoiceAssistant::failed_to_start() { void VoiceAssistant::failed_to_start() {
ESP_LOGE(TAG, "Failed to start server. See Home Assistant logs for more details."); ESP_LOGE(TAG, "Failed to start server. See Home Assistant logs for more details.");
this->error_trigger_->trigger("failed-to-start", "Failed to start server. See Home Assistant logs for more details."); this->error_trigger_.trigger("failed-to-start", "Failed to start server. See Home Assistant logs for more details.");
this->set_state_(State::STOP_MICROPHONE, State::IDLE); this->set_state_(State::STOP_MICROPHONE, State::IDLE);
} }
@@ -637,18 +637,18 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
} }
} }
#endif #endif
this->defer([this]() { this->start_trigger_->trigger(); }); this->defer([this]() { this->start_trigger_.trigger(); });
break; break;
case api::enums::VOICE_ASSISTANT_WAKE_WORD_START: case api::enums::VOICE_ASSISTANT_WAKE_WORD_START:
break; break;
case api::enums::VOICE_ASSISTANT_WAKE_WORD_END: { case api::enums::VOICE_ASSISTANT_WAKE_WORD_END: {
ESP_LOGD(TAG, "Wake word detected"); ESP_LOGD(TAG, "Wake word detected");
this->defer([this]() { this->wake_word_detected_trigger_->trigger(); }); this->defer([this]() { this->wake_word_detected_trigger_.trigger(); });
break; break;
} }
case api::enums::VOICE_ASSISTANT_STT_START: case api::enums::VOICE_ASSISTANT_STT_START:
ESP_LOGD(TAG, "STT started"); ESP_LOGD(TAG, "STT started");
this->defer([this]() { this->listening_trigger_->trigger(); }); this->defer([this]() { this->listening_trigger_.trigger(); });
break; break;
case api::enums::VOICE_ASSISTANT_STT_END: { case api::enums::VOICE_ASSISTANT_STT_END: {
std::string text; std::string text;
@@ -665,12 +665,12 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
text += "..."; text += "...";
} }
ESP_LOGD(TAG, "Speech recognised as: \"%s\"", text.c_str()); ESP_LOGD(TAG, "Speech recognised as: \"%s\"", text.c_str());
this->defer([this, text]() { this->stt_end_trigger_->trigger(text); }); this->defer([this, text]() { this->stt_end_trigger_.trigger(text); });
break; break;
} }
case api::enums::VOICE_ASSISTANT_INTENT_START: case api::enums::VOICE_ASSISTANT_INTENT_START:
ESP_LOGD(TAG, "Intent started"); ESP_LOGD(TAG, "Intent started");
this->defer([this]() { this->intent_start_trigger_->trigger(); }); this->defer([this]() { this->intent_start_trigger_.trigger(); });
break; break;
case api::enums::VOICE_ASSISTANT_INTENT_PROGRESS: { case api::enums::VOICE_ASSISTANT_INTENT_PROGRESS: {
ESP_LOGD(TAG, "Intent progress"); ESP_LOGD(TAG, "Intent progress");
@@ -693,7 +693,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
} }
} }
#endif #endif
this->defer([this, tts_url_for_trigger]() { this->intent_progress_trigger_->trigger(tts_url_for_trigger); }); this->defer([this, tts_url_for_trigger]() { this->intent_progress_trigger_.trigger(tts_url_for_trigger); });
break; break;
} }
case api::enums::VOICE_ASSISTANT_INTENT_END: { case api::enums::VOICE_ASSISTANT_INTENT_END: {
@@ -704,7 +704,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
this->continue_conversation_ = (arg.value == "1"); this->continue_conversation_ = (arg.value == "1");
} }
} }
this->defer([this]() { this->intent_end_trigger_->trigger(); }); this->defer([this]() { this->intent_end_trigger_.trigger(); });
break; break;
} }
case api::enums::VOICE_ASSISTANT_TTS_START: { case api::enums::VOICE_ASSISTANT_TTS_START: {
@@ -724,7 +724,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
} }
ESP_LOGD(TAG, "Response: \"%s\"", text.c_str()); ESP_LOGD(TAG, "Response: \"%s\"", text.c_str());
this->defer([this, text]() { this->defer([this, text]() {
this->tts_start_trigger_->trigger(text); this->tts_start_trigger_.trigger(text);
#ifdef USE_SPEAKER #ifdef USE_SPEAKER
if (this->speaker_ != nullptr) { if (this->speaker_ != nullptr) {
this->speaker_->start(); this->speaker_->start();
@@ -756,7 +756,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
} }
this->started_streaming_tts_ = false; // Helps indicate reaching the TTS_END stage this->started_streaming_tts_ = false; // Helps indicate reaching the TTS_END stage
#endif #endif
this->tts_end_trigger_->trigger(url); this->tts_end_trigger_.trigger(url);
}); });
State new_state = this->local_output_ ? State::STREAMING_RESPONSE : State::IDLE; State new_state = this->local_output_ ? State::STREAMING_RESPONSE : State::IDLE;
if (new_state != this->state_) { if (new_state != this->state_) {
@@ -776,7 +776,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
// No TTS start event ("nevermind") // No TTS start event ("nevermind")
this->set_state_(State::IDLE, State::IDLE); this->set_state_(State::IDLE, State::IDLE);
} }
this->defer([this]() { this->end_trigger_->trigger(); }); this->defer([this]() { this->end_trigger_.trigger(); });
break; break;
} }
case api::enums::VOICE_ASSISTANT_ERROR: { case api::enums::VOICE_ASSISTANT_ERROR: {
@@ -796,7 +796,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
// Wake word is not set up or not ready on Home Assistant so stop and do not retry until user starts again. // Wake word is not set up or not ready on Home Assistant so stop and do not retry until user starts again.
this->defer([this, code, message]() { this->defer([this, code, message]() {
this->request_stop(); this->request_stop();
this->error_trigger_->trigger(code, message); this->error_trigger_.trigger(code, message);
}); });
return; return;
} }
@@ -805,7 +805,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
this->signal_stop_(); this->signal_stop_();
this->set_state_(State::STOP_MICROPHONE, State::IDLE); this->set_state_(State::STOP_MICROPHONE, State::IDLE);
} }
this->defer([this, code, message]() { this->error_trigger_->trigger(code, message); }); this->defer([this, code, message]() { this->error_trigger_.trigger(code, message); });
break; break;
} }
case api::enums::VOICE_ASSISTANT_TTS_STREAM_START: { case api::enums::VOICE_ASSISTANT_TTS_STREAM_START: {
@@ -813,7 +813,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
if (this->speaker_ != nullptr) { if (this->speaker_ != nullptr) {
this->wait_for_stream_end_ = true; this->wait_for_stream_end_ = true;
ESP_LOGD(TAG, "TTS stream start"); ESP_LOGD(TAG, "TTS stream start");
this->defer([this] { this->tts_stream_start_trigger_->trigger(); }); this->defer([this] { this->tts_stream_start_trigger_.trigger(); });
} }
#endif #endif
break; break;
@@ -829,12 +829,12 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
} }
case api::enums::VOICE_ASSISTANT_STT_VAD_START: case api::enums::VOICE_ASSISTANT_STT_VAD_START:
ESP_LOGD(TAG, "Starting STT by VAD"); ESP_LOGD(TAG, "Starting STT by VAD");
this->defer([this]() { this->stt_vad_start_trigger_->trigger(); }); this->defer([this]() { this->stt_vad_start_trigger_.trigger(); });
break; break;
case api::enums::VOICE_ASSISTANT_STT_VAD_END: case api::enums::VOICE_ASSISTANT_STT_VAD_END:
ESP_LOGD(TAG, "STT by VAD end"); ESP_LOGD(TAG, "STT by VAD end");
this->set_state_(State::STOP_MICROPHONE, State::AWAITING_RESPONSE); this->set_state_(State::STOP_MICROPHONE, State::AWAITING_RESPONSE);
this->defer([this]() { this->stt_vad_end_trigger_->trigger(); }); this->defer([this]() { this->stt_vad_end_trigger_.trigger(); });
break; break;
default: default:
ESP_LOGD(TAG, "Unhandled event type: %" PRId32, msg.event_type); ESP_LOGD(TAG, "Unhandled event type: %" PRId32, msg.event_type);
@@ -876,17 +876,17 @@ void VoiceAssistant::on_timer_event(const api::VoiceAssistantTimerEventResponse
switch (msg.event_type) { switch (msg.event_type) {
case api::enums::VOICE_ASSISTANT_TIMER_STARTED: case api::enums::VOICE_ASSISTANT_TIMER_STARTED:
this->timer_started_trigger_->trigger(timer); this->timer_started_trigger_.trigger(timer);
break; break;
case api::enums::VOICE_ASSISTANT_TIMER_UPDATED: case api::enums::VOICE_ASSISTANT_TIMER_UPDATED:
this->timer_updated_trigger_->trigger(timer); this->timer_updated_trigger_.trigger(timer);
break; break;
case api::enums::VOICE_ASSISTANT_TIMER_CANCELLED: case api::enums::VOICE_ASSISTANT_TIMER_CANCELLED:
this->timer_cancelled_trigger_->trigger(timer); this->timer_cancelled_trigger_.trigger(timer);
this->timers_.erase(timer.id); this->timers_.erase(timer.id);
break; break;
case api::enums::VOICE_ASSISTANT_TIMER_FINISHED: case api::enums::VOICE_ASSISTANT_TIMER_FINISHED:
this->timer_finished_trigger_->trigger(timer); this->timer_finished_trigger_.trigger(timer);
this->timers_.erase(timer.id); this->timers_.erase(timer.id);
break; break;
} }
@@ -910,13 +910,13 @@ void VoiceAssistant::timer_tick_() {
} }
res.push_back(timer); res.push_back(timer);
} }
this->timer_tick_trigger_->trigger(res); this->timer_tick_trigger_.trigger(res);
} }
void VoiceAssistant::on_announce(const api::VoiceAssistantAnnounceRequest &msg) { void VoiceAssistant::on_announce(const api::VoiceAssistantAnnounceRequest &msg) {
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
if (this->media_player_ != nullptr) { if (this->media_player_ != nullptr) {
this->tts_start_trigger_->trigger(msg.text); this->tts_start_trigger_.trigger(msg.text);
this->media_player_response_state_ = MediaPlayerResponseState::URL_SENT; this->media_player_response_state_ = MediaPlayerResponseState::URL_SENT;
@@ -939,8 +939,8 @@ void VoiceAssistant::on_announce(const api::VoiceAssistantAnnounceRequest &msg)
this->set_state_(State::STREAMING_RESPONSE, State::STREAMING_RESPONSE); this->set_state_(State::STREAMING_RESPONSE, State::STREAMING_RESPONSE);
} }
this->tts_end_trigger_->trigger(msg.media_id); this->tts_end_trigger_.trigger(msg.media_id);
this->end_trigger_->trigger(); this->end_trigger_.trigger();
} }
#endif #endif
} }

Some files were not shown because too many files have changed in this diff Show More