mirror of
https://github.com/esphome/esphome.git
synced 2026-02-03 11:29:39 -07:00
Compare commits
66 Commits
json_web_s
...
scanf_bloa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f42f222e82 | ||
|
|
ccf5c1f7e9 | ||
|
|
efecea9450 | ||
|
|
26e4cda610 | ||
|
|
a6543d32bd | ||
|
|
da947d060f | ||
|
|
1119003eb5 | ||
|
|
c089d9aeac | ||
|
|
4f0894e970 | ||
|
|
848c237159 | ||
|
|
6892805094 | ||
|
|
aa8ccfc32b | ||
|
|
18991686ab | ||
|
|
62f34bea83 | ||
|
|
6114005952 | ||
|
|
c0e5ae4298 | ||
|
|
420de987bc | ||
|
|
61e33217cd | ||
|
|
b5b9a89561 | ||
|
|
bc9fc66225 | ||
|
|
6727fe9040 | ||
|
|
56110d4495 | ||
|
|
1362ff6cba | ||
|
|
dbd7401721 | ||
|
|
f0801ecac0 | ||
|
|
379652f631 | ||
|
|
18c152723c | ||
|
|
09b76d5e4a | ||
|
|
8791c24072 | ||
|
|
652c02b9ab | ||
|
|
4ab552d750 | ||
|
|
e420964b93 | ||
|
|
7d717a78dc | ||
|
|
2f0abd5c3f | ||
|
|
d49d8095df | ||
|
|
8a8c1290db | ||
|
|
01ffeba2c2 | ||
|
|
78ed898f0b | ||
|
|
75ee9a718a | ||
|
|
bfeb447178 | ||
|
|
29f8d70b35 | ||
|
|
1ff2f3b6a3 | ||
|
|
891382a32e | ||
|
|
0fd50b2381 | ||
|
|
e68b302bba | ||
|
|
3e11a9d8a5 | ||
|
|
9dcb469460 | ||
|
|
5e3561d60b | ||
|
|
ca9ed369f9 | ||
|
|
4e96b20b46 | ||
|
|
a1a60c44da | ||
|
|
898c8a5836 | ||
|
|
20edd11ca7 | ||
|
|
9a8c71a58b | ||
|
|
1a7435250e | ||
|
|
3c91d72403 | ||
|
|
0a63fc6f05 | ||
|
|
50e739ee8e | ||
|
|
6c84f20491 | ||
|
|
a68506f924 | ||
|
|
a20d42ca0b | ||
|
|
4ec8846198 | ||
|
|
40ea65b1c0 | ||
|
|
f7937ef952 | ||
|
|
d6bf137026 | ||
|
|
ed9a672f44 |
@@ -1 +1 @@
|
|||||||
cf3d341206b4184ec8b7fe85141aef4fe4696aa720c3f8a06d4e57930574bdab
|
069fa9526c52f7c580a9ec17c7678d12f142221387e9b561c18f95394d4629a3
|
||||||
|
|||||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -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}}"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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) {}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|
||||||
|
|||||||
57
esphome/components/dlms_meter/__init__.py
Normal file
57
esphome/components/dlms_meter/__init__.py
Normal 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]]))
|
||||||
71
esphome/components/dlms_meter/dlms.h
Normal file
71
esphome/components/dlms_meter/dlms.h
Normal 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
|
||||||
468
esphome/components/dlms_meter/dlms_meter.cpp
Normal file
468
esphome/components/dlms_meter/dlms_meter.cpp
Normal 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
|
||||||
96
esphome/components/dlms_meter/dlms_meter.h
Normal file
96
esphome/components/dlms_meter/dlms_meter.h
Normal 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
|
||||||
69
esphome/components/dlms_meter/mbus.h
Normal file
69
esphome/components/dlms_meter/mbus.h
Normal 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
|
||||||
94
esphome/components/dlms_meter/obis.h
Normal file
94
esphome/components/dlms_meter/obis.h
Normal 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
|
||||||
124
esphome/components/dlms_meter/sensor/__init__.py
Normal file
124
esphome/components/dlms_meter/sensor/__init__.py
Normal 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))
|
||||||
|
)
|
||||||
37
esphome/components/dlms_meter/text_sensor/__init__.py
Normal file
37
esphome/components/dlms_meter/text_sensor/__init__.py
Normal 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)),
|
||||||
|
)
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
@@ -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};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]))
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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_));
|
||||||
|
|||||||
@@ -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};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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_
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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_
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -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...> {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user