1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-09 03:21:52 +00:00

Compare commits

...

29 Commits

Author SHA1 Message Date
J. Nick Koston
9a2fc8aa51 part 2025-11-07 23:44:43 -06:00
J. Nick Koston
c77bb3b269 [event] Store event types in flash memory (#11767) 2025-11-07 15:46:16 -06:00
dependabot[bot]
79d1a558af Bump ruff from 0.14.3 to 0.14.4 (#11768)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-11-07 20:12:15 +00:00
J. Nick Koston
a5bf55b6ac [ci] Fix component batching for beta/release branches (3-4 → 40 per batch) (#11759) 2025-11-07 20:19:45 +13:00
J. Nick Koston
85d2565f25 [tests] Fix determine_jobs tests failing when target branch is beta (#11758) 2025-11-07 20:18:43 +13:00
J. Nick Koston
4f08f0750a [ai_instructions] Add public API and breaking changes guidelines (#11756) 2025-11-06 22:34:53 -06:00
J. Nick Koston
3c41e080c5 [core] Use ESPDEPRECATED macro for deprecation warnings (#11755) 2025-11-07 03:37:02 +00:00
J. Nick Koston
7c30d57391 [wifi] Refactor AP selection to use index instead of copy (saves 88 bytes) (#11749) 2025-11-06 21:26:53 -06:00
J. Nick Koston
182e106bfa [wifi] Guard AP-related members with USE_WIFI_AP to save RAM (#11753) 2025-11-07 15:44:40 +13:00
J. Nick Koston
d0b399d771 [ci] Reduce release time by removing 468 redundant ESP32-C3 IDF tests (#11737) 2025-11-07 15:44:01 +13:00
philippderdiedas
5d20e3a3b4 Add MCP3221 i2c A-D-Converter (#7764) 2025-11-07 14:25:14 +13:00
Kevin Ahrendt
ba5fa7c10a [psram] Add option to disable ignore not found sdkconfig setting (#11411)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-11-07 14:22:50 +13:00
J. Nick Koston
5cdb891b58 [socket] Deduplicate IP formatting in LWIP raw TCP implementation (#11747) 2025-11-07 14:21:58 +13:00
rwrozelle
26607713bb [openthread] add poll period for mtd devices (#11374)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-11-06 16:57:31 +13:00
Szewcson
895d76ca03 [gdk101] Fix fw version reporting (#11029)
Signed-off-by: szewcu <szewcson@gmail.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-11-05 22:19:29 -05:00
J. Nick Koston
74187845b7 [select] Convert remaining components to use index-based control() (#11693)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-11-06 15:55:26 +13:00
J. Nick Koston
822eacfd77 [core] Fix wait_until and for_condition timing regression in automation chains (#11716) 2025-11-06 15:49:24 +13:00
Clyde Stubbs
ab5d8f67ae [core] Add helper functions for clamp_at_... (#10387) 2025-11-06 15:48:02 +13:00
J. Nick Koston
83f30a64ed [api] Store YAML service names in flash instead of heap (#11744) 2025-11-06 15:31:59 +13:00
J. Nick Koston
5eea7bdb44 Update AI instructions with C++ style guidelines from developers docs (#11743) 2025-11-06 14:45:48 +13:00
J. Nick Koston
bdfd88441a [ci] Skip memory impact analysis when more than 40 components changed (#11741) 2025-11-05 19:31:23 -06:00
Clyde Stubbs
20b6e0d5c2 [lvgl] Allow text substitution for NaN (#11712) 2025-11-06 10:37:38 +11:00
J. Nick Koston
ce5e608863 [ci] Skip memory impact analysis for release and beta branches (#11740) 2025-11-05 14:32:45 -06:00
J. Nick Koston
aa5795c019 [tests] Fix ID collision between bl0940 and nau7802 component tests (#11739) 2025-11-05 13:17:34 -06:00
J. Nick Koston
00c0854323 [core] Deprecate get_icon(), get_device_class(), get_unit_of_measurement() and fix remaining non-MQTT usages (#11732) 2025-11-05 12:50:35 -06:00
J. Nick Koston
be006ecadd [mdns] Eliminate redundant hostname copy to save heap memory (#11734) 2025-11-05 18:31:19 +00:00
J. Nick Koston
b08419fa47 [mqtt] Use StringRef to avoid string copies in discovery (#11731) 2025-11-06 07:30:45 +13:00
J. Nick Koston
d36ef050a9 [template] Mark all component classes as final (#11733) 2025-11-06 07:15:50 +13:00
J. Nick Koston
df53ff7afe [scheduler] Extract helper functions to improve code readability (#11730) 2025-11-06 07:13:12 +13:00
604 changed files with 1896 additions and 2681 deletions

View File

@@ -51,7 +51,79 @@ This document provides essential context for AI models interacting with this pro
* **Naming Conventions:**
* **Python:** Follows PEP 8. Use clear, descriptive names following snake_case.
* **C++:** Follows the Google C++ Style Guide.
* **C++:** Follows the Google C++ Style Guide with these specifics (following clang-tidy conventions):
- Function, method, and variable names: `lower_snake_case`
- Class/struct/enum names: `UpperCamelCase`
- Top-level constants (global/namespace scope): `UPPER_SNAKE_CASE`
- Function-local constants: `lower_snake_case`
- Protected/private fields: `lower_snake_case_with_trailing_underscore_`
- Favor descriptive names over abbreviations
* **C++ Field Visibility:**
* **Prefer `protected`:** Use `protected` for most class fields to enable extensibility and testing. Fields should be `lower_snake_case_with_trailing_underscore_`.
* **Use `private` for safety-critical cases:** Use `private` visibility when direct field access could introduce bugs or violate invariants:
1. **Pointer lifetime issues:** When setters validate and store pointers from known lists to prevent dangling references.
```cpp
// Helper to find matching string in vector and return its pointer
inline const char *vector_find(const std::vector<const char *> &vec, const char *value) {
for (const char *item : vec) {
if (strcmp(item, value) == 0)
return item;
}
return nullptr;
}
class ClimateDevice {
public:
void set_custom_fan_modes(std::initializer_list<const char *> modes) {
this->custom_fan_modes_ = modes;
this->active_custom_fan_mode_ = nullptr; // Reset when modes change
}
bool set_custom_fan_mode(const char *mode) {
// Find mode in supported list and store that pointer (not the input pointer)
const char *validated_mode = vector_find(this->custom_fan_modes_, mode);
if (validated_mode != nullptr) {
this->active_custom_fan_mode_ = validated_mode;
return true;
}
return false;
}
private:
std::vector<const char *> custom_fan_modes_; // Pointers to string literals in flash
const char *active_custom_fan_mode_{nullptr}; // Must point to entry in custom_fan_modes_
};
```
2. **Invariant coupling:** When multiple fields must remain synchronized to prevent buffer overflows or data corruption.
```cpp
class Buffer {
public:
void resize(size_t new_size) {
auto new_data = std::make_unique<uint8_t[]>(new_size);
if (this->data_) {
std::memcpy(new_data.get(), this->data_.get(), std::min(this->size_, new_size));
}
this->data_ = std::move(new_data);
this->size_ = new_size; // Must stay in sync with data_
}
private:
std::unique_ptr<uint8_t[]> data_;
size_t size_{0}; // Must match allocated size of data_
};
```
3. **Resource management:** When setters perform cleanup or registration operations that derived classes might skip.
* **Provide `protected` accessor methods:** When derived classes need controlled access to `private` members.
* **C++ Preprocessor Directives:**
* **Avoid `#define` for constants:** Using `#define` for constants is discouraged and should be replaced with `const` variables or enums.
* **Use `#define` only for:**
- Conditional compilation (`#ifdef`, `#ifndef`)
- Compile-time sizes calculated during Python code generation (e.g., configuring `std::array` or `StaticVector` dimensions via `cg.add_define()`)
* **C++ Additional Conventions:**
* **Member access:** Prefix all class member access with `this->` (e.g., `this->value_` not `value_`)
* **Indentation:** Use spaces (two per indentation level), not tabs
* **Type aliases:** Prefer `using type_t = int;` over `typedef int type_t;`
* **Line length:** Wrap lines at no more than 120 characters
* **Component Structure:**
* **Standard Files:**
@@ -368,3 +440,45 @@ This document provides essential context for AI models interacting with this pro
* **Python:** When adding a new Python dependency, add it to the appropriate `requirements*.txt` file and `pyproject.toml`.
* **C++ / PlatformIO:** When adding a new C++ dependency, add it to `platformio.ini` and use `cg.add_library`.
* **Build Flags:** Use `cg.add_build_flag(...)` to add compiler flags.
## 8. Public API and Breaking Changes
* **Public C++ API:**
* **Components**: Only documented features at [esphome.io](https://esphome.io) are public API. Undocumented `public` members are internal.
* **Core/Base Classes** (`esphome/core/`, `Component`, `Sensor`, etc.): All `public` members are public API.
* **Components with Global Accessors** (`global_api_server`, etc.): All `public` members are public API (except config setters).
* **Public Python API:**
* All documented configuration options at [esphome.io](https://esphome.io) are public API.
* Python code in `esphome/core/` actively used by existing core components is considered stable API.
* Other Python code is internal unless explicitly documented for external component use.
* **Breaking Changes Policy:**
* Aim for **6-month deprecation window** when possible
* Clean breaks allowed for: signature changes, deep refactorings, resource constraints
* Must document migration path in PR description (generates release notes)
* Blog post required for core/base class changes or significant architectural changes
* Full details: https://developers.esphome.io/contributing/code/#public-api-and-breaking-changes
* **Breaking Change Checklist:**
- [ ] Clear justification (RAM/flash savings, architectural improvement)
- [ ] Explored non-breaking alternatives
- [ ] Added deprecation warnings if possible (use `ESPDEPRECATED` macro for C++)
- [ ] Documented migration path in PR description with before/after examples
- [ ] Updated all internal usage and esphome-docs
- [ ] Tested backward compatibility during deprecation period
* **Deprecation Pattern (C++):**
```cpp
// Remove before 2026.6.0
ESPDEPRECATED("Use new_method() instead. Removed in 2026.6.0", "2025.12.0")
void old_method() { this->new_method(); }
```
* **Deprecation Pattern (Python):**
```python
# Remove before 2026.6.0
if CONF_OLD_KEY in config:
_LOGGER.warning(f"'{CONF_OLD_KEY}' deprecated, use '{CONF_NEW_KEY}'. Removed in 2026.6.0")
config[CONF_NEW_KEY] = config.pop(CONF_OLD_KEY) # Auto-migrate
```

View File

@@ -11,7 +11,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.14.3
rev: v0.14.4
hooks:
# Run the linter.
- id: ruff

View File

@@ -290,6 +290,7 @@ esphome/components/mcp23x17_base/* @jesserockz
esphome/components/mcp23xxx_base/* @jesserockz
esphome/components/mcp2515/* @danielschramm @mvturnho
esphome/components/mcp3204/* @rsumner
esphome/components/mcp3221/* @philippderdiedas
esphome/components/mcp4461/* @p1ngb4ck
esphome/components/mcp4728/* @berfenger
esphome/components/mcp47a1/* @jesserockz

View File

@@ -2147,7 +2147,7 @@ message ListEntitiesEventResponse {
EntityCategory entity_category = 7;
string device_class = 8;
repeated string event_types = 9;
repeated string event_types = 9 [(container_pointer_no_template) = "FixedVector<const char *>"];
uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"];
}
message EventResponse {

View File

@@ -1310,8 +1310,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
auto *event = static_cast<event::Event *>(entity);
ListEntitiesEventResponse msg;
msg.set_device_class(event->get_device_class_ref());
for (const auto &event_type : event->get_event_types())
msg.event_types.push_back(event_type);
msg.event_types = &event->get_event_types();
return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}

View File

@@ -2877,8 +2877,8 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_uint32(7, static_cast<uint32_t>(this->entity_category));
buffer.encode_string(8, this->device_class_ref_);
for (auto &it : this->event_types) {
buffer.encode_string(9, it, true);
for (const char *it : *this->event_types) {
buffer.encode_string(9, it, strlen(it), true);
}
#ifdef USE_DEVICES
buffer.encode_uint32(10, this->device_id);
@@ -2894,9 +2894,9 @@ void ListEntitiesEventResponse::calculate_size(ProtoSize &size) const {
size.add_bool(1, this->disabled_by_default);
size.add_uint32(1, static_cast<uint32_t>(this->entity_category));
size.add_length(1, this->device_class_ref_.size());
if (!this->event_types.empty()) {
for (const auto &it : this->event_types) {
size.add_length_force(1, it.size());
if (!this->event_types->empty()) {
for (const char *it : *this->event_types) {
size.add_length_force(1, strlen(it));
}
}
#ifdef USE_DEVICES

View File

@@ -2788,7 +2788,7 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage {
#endif
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
std::vector<std::string> event_types{};
const FixedVector<const char *> *event_types{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP

View File

@@ -2053,7 +2053,7 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const {
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "device_class", this->device_class_ref_);
for (const auto &it : this->event_types) {
for (const auto &it : *this->event_types) {
dump_field(out, "event_types", it, 4);
}
#ifdef USE_DEVICES

View File

@@ -9,11 +9,11 @@
namespace esphome::api {
#ifdef USE_API_SERVICES
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceDynamic<Ts...> {
public:
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
void (T::*callback)(Ts...))
: UserServiceBase<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
: UserServiceDynamic<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
protected:
void execute(Ts... x) override { (this->obj_->*this->callback_)(x...); } // NOLINT

View File

@@ -23,11 +23,13 @@ template<typename T> T get_execute_arg_value(const ExecuteServiceArgument &arg);
template<typename T> enums::ServiceArgType to_service_arg_type();
// Base class for YAML-defined services (most common case)
// Stores only pointers to string literals in flash - no heap allocation
template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
public:
UserServiceBase(std::string name, const std::array<std::string, sizeof...(Ts)> &arg_names)
: name_(std::move(name)), arg_names_(arg_names) {
this->key_ = fnv1_hash(this->name_);
UserServiceBase(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
: name_(name), arg_names_(arg_names) {
this->key_ = fnv1_hash(name);
}
ListEntitiesServicesResponse encode_list_service_response() override {
@@ -47,7 +49,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
bool execute_service(const ExecuteServiceRequest &req) override {
if (req.key != this->key_)
return false;
if (req.args.size() != this->arg_names_.size())
if (req.args.size() != sizeof...(Ts))
return false;
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
return true;
@@ -59,14 +61,60 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
this->execute((get_execute_arg_value<Ts>(args[S]))...);
}
std::string name_;
// Pointers to string literals in flash - no heap allocation
const char *name_;
std::array<const char *, sizeof...(Ts)> arg_names_;
uint32_t key_{0};
};
// Separate class for custom_api_device services (rare case)
// Stores copies of runtime-generated names
template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor {
public:
UserServiceDynamic(std::string name, const std::array<std::string, sizeof...(Ts)> &arg_names)
: name_(std::move(name)), arg_names_(arg_names) {
this->key_ = fnv1_hash(this->name_.c_str());
}
ListEntitiesServicesResponse encode_list_service_response() override {
ListEntitiesServicesResponse msg;
msg.set_name(StringRef(this->name_));
msg.key = this->key_;
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
msg.args.init(sizeof...(Ts));
for (size_t i = 0; i < sizeof...(Ts); i++) {
auto &arg = msg.args.emplace_back();
arg.type = arg_types[i];
arg.set_name(StringRef(this->arg_names_[i]));
}
return msg;
}
bool execute_service(const ExecuteServiceRequest &req) override {
if (req.key != this->key_)
return false;
if (req.args.size() != sizeof...(Ts))
return false;
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
return true;
}
protected:
virtual void execute(Ts... x) = 0;
template<typename ArgsContainer, int... S> void execute_(const ArgsContainer &args, seq<S...> type) {
this->execute((get_execute_arg_value<Ts>(args[S]))...);
}
// Heap-allocated strings for runtime-generated names
std::string name_;
std::array<std::string, sizeof...(Ts)> arg_names_;
uint32_t key_{0};
};
template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...>, public Trigger<Ts...> {
public:
UserServiceTrigger(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
// Constructor for static names (YAML-defined services - used by code generator)
UserServiceTrigger(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
: UserServiceBase<Ts...>(name, arg_names) {}
protected:

View File

@@ -9,6 +9,7 @@ BYTE_ORDER_BIG = "big_endian"
CONF_COLOR_DEPTH = "color_depth"
CONF_DRAW_ROUNDING = "draw_rounding"
CONF_ENABLED = "enabled"
CONF_IGNORE_NOT_FOUND = "ignore_not_found"
CONF_ON_RECEIVE = "on_receive"
CONF_ON_STATE_CHANGE = "on_state_change"
CONF_REQUEST_HEADERS = "request_headers"

View File

@@ -8,7 +8,7 @@ namespace demo {
class DemoSelect : public select::Select, public Component {
protected:
void control(const std::string &value) override { this->publish_state(value); }
void control(size_t index) override { this->publish_state(index); }
};
} // namespace demo

View File

@@ -3,9 +3,9 @@
namespace esphome {
namespace es8388 {
void ADCInputMicSelect::control(const std::string &value) {
this->publish_state(value);
this->parent_->set_adc_input_mic(static_cast<AdcInputMicLine>(this->index_of(value).value()));
void ADCInputMicSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_adc_input_mic(static_cast<AdcInputMicLine>(index));
}
} // namespace es8388

View File

@@ -8,7 +8,7 @@ namespace es8388 {
class ADCInputMicSelect : public select::Select, public Parented<ES8388> {
protected:
void control(const std::string &value) override;
void control(size_t index) override;
};
} // namespace es8388

View File

@@ -3,9 +3,9 @@
namespace esphome {
namespace es8388 {
void DacOutputSelect::control(const std::string &value) {
this->publish_state(value);
this->parent_->set_dac_output(static_cast<DacOutputLine>(this->index_of(value).value()));
void DacOutputSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_dac_output(static_cast<DacOutputLine>(index));
}
} // namespace es8388

View File

@@ -8,7 +8,7 @@ namespace es8388 {
class DacOutputSelect : public select::Select, public Parented<ES8388> {
protected:
void control(const std::string &value) override;
void control(size_t index) override;
};
} // namespace es8388

View File

@@ -8,11 +8,11 @@ namespace event {
static const char *const TAG = "event";
void Event::trigger(const std::string &event_type) {
// Linear search - faster than std::set for small datasets (1-5 items typical)
const std::string *found = nullptr;
for (const auto &type : this->types_) {
if (type == event_type) {
found = &type;
// Linear search with strcmp - faster than std::set for small datasets (1-5 items typical)
const char *found = nullptr;
for (const char *type : this->types_) {
if (strcmp(type, event_type.c_str()) == 0) {
found = type;
break;
}
}
@@ -20,11 +20,27 @@ void Event::trigger(const std::string &event_type) {
ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str());
return;
}
last_event_type = found;
ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), last_event_type->c_str());
this->last_event_type_ = found;
ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), this->last_event_type_);
this->event_callback_.call(event_type);
}
void Event::set_event_types(const FixedVector<const char *> &event_types) {
this->types_.init(event_types.size());
for (const char *type : event_types) {
this->types_.push_back(type);
}
this->last_event_type_ = nullptr; // Reset when types change
}
void Event::set_event_types(const std::vector<const char *> &event_types) {
this->types_.init(event_types.size());
for (const char *type : event_types) {
this->types_.push_back(type);
}
this->last_event_type_ = nullptr; // Reset when types change
}
void Event::add_on_event_callback(std::function<void(const std::string &event_type)> &&callback) {
this->event_callback_.add(std::move(callback));
}

View File

@@ -1,6 +1,8 @@
#pragma once
#include <cstring>
#include <string>
#include <vector>
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
@@ -22,16 +24,39 @@ namespace event {
class Event : public EntityBase, public EntityBase_DeviceClass {
public:
const std::string *last_event_type;
void trigger(const std::string &event_type);
void set_event_types(const std::initializer_list<std::string> &event_types) { this->types_ = event_types; }
const FixedVector<std::string> &get_event_types() const { return this->types_; }
/// Set the event types supported by this event (from initializer list).
void set_event_types(std::initializer_list<const char *> event_types) {
this->types_ = event_types;
this->last_event_type_ = nullptr; // Reset when types change
}
/// Set the event types supported by this event (from FixedVector).
void set_event_types(const FixedVector<const char *> &event_types);
/// Set the event types supported by this event (from vector).
void set_event_types(const std::vector<const char *> &event_types);
// Deleted overloads to catch incorrect std::string usage at compile time with clear error messages
void set_event_types(std::initializer_list<std::string> event_types) = delete;
void set_event_types(const FixedVector<std::string> &event_types) = delete;
void set_event_types(const std::vector<std::string> &event_types) = delete;
/// Return the event types supported by this event.
const FixedVector<const char *> &get_event_types() const { return this->types_; }
/// Return the last triggered event type (pointer to string in types_), or nullptr if no event triggered yet.
const char *get_last_event_type() const { return this->last_event_type_; }
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
protected:
CallbackManager<void(const std::string &event_type)> event_callback_;
FixedVector<std::string> types_;
FixedVector<const char *> types_;
private:
/// Last triggered event type - must point to entry in types_ to ensure valid lifetime.
/// Set by trigger() after validation, reset to nullptr when types_ changes.
const char *last_event_type_{nullptr};
};
} // namespace event

View File

@@ -62,7 +62,6 @@ void GDK101Component::dump_config() {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
}
#ifdef USE_SENSOR
LOG_SENSOR(" ", "Firmware Version", this->fw_version_sensor_);
LOG_SENSOR(" ", "Average Radaition Dose per 1 minute", this->rad_1m_sensor_);
LOG_SENSOR(" ", "Average Radaition Dose per 10 minutes", this->rad_10m_sensor_);
LOG_SENSOR(" ", "Status", this->status_sensor_);
@@ -72,6 +71,10 @@ void GDK101Component::dump_config() {
#ifdef USE_BINARY_SENSOR
LOG_BINARY_SENSOR(" ", "Vibration Status", this->vibration_binary_sensor_);
#endif // USE_BINARY_SENSOR
#ifdef USE_TEXT_SENSOR
LOG_TEXT_SENSOR(" ", "Firmware Version", this->fw_version_text_sensor_);
#endif // USE_TEXT_SENSOR
}
float GDK101Component::get_setup_priority() const { return setup_priority::DATA; }
@@ -153,18 +156,18 @@ bool GDK101Component::read_status_(uint8_t *data) {
}
bool GDK101Component::read_fw_version_(uint8_t *data) {
#ifdef USE_SENSOR
if (this->fw_version_sensor_ != nullptr) {
#ifdef USE_TEXT_SENSOR
if (this->fw_version_text_sensor_ != nullptr) {
if (!this->read_bytes(GDK101_REG_READ_FIRMWARE, data, 2)) {
ESP_LOGE(TAG, "Updating GDK101 failed!");
return false;
}
const float fw_version = data[0] + (data[1] / 10.0f);
const std::string fw_version_str = str_sprintf("%d.%d", data[0], data[1]);
this->fw_version_sensor_->publish_state(fw_version);
this->fw_version_text_sensor_->publish_state(fw_version_str);
}
#endif // USE_SENSOR
#endif // USE_TEXT_SENSOR
return true;
}

View File

@@ -8,6 +8,9 @@
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif // USE_BINARY_SENSOR
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif // USE_TEXT_SENSOR
#include "esphome/components/i2c/i2c.h"
namespace esphome {
@@ -25,12 +28,14 @@ class GDK101Component : public PollingComponent, public i2c::I2CDevice {
SUB_SENSOR(rad_1m)
SUB_SENSOR(rad_10m)
SUB_SENSOR(status)
SUB_SENSOR(fw_version)
SUB_SENSOR(measurement_duration)
#endif // USE_SENSOR
#ifdef USE_BINARY_SENSOR
SUB_BINARY_SENSOR(vibration)
#endif // USE_BINARY_SENSOR
#ifdef USE_TEXT_SENSOR
SUB_TEXT_SENSOR(fw_version)
#endif // USE_TEXT_SENSOR
public:
void setup() override;

View File

@@ -40,9 +40,8 @@ CONFIG_SCHEMA = cv.Schema(
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_VERSION): sensor.sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
accuracy_decimals=1,
cv.Optional(CONF_VERSION): cv.invalid(
"The 'version' option has been moved to the `text_sensor` component."
),
cv.Optional(CONF_STATUS): sensor.sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
@@ -71,10 +70,6 @@ async def to_code(config):
sens = await sensor.new_sensor(radiation_dose_per_10m)
cg.add(hub.set_rad_10m_sensor(sens))
if version_config := config.get(CONF_VERSION):
sens = await sensor.new_sensor(version_config)
cg.add(hub.set_fw_version_sensor(sens))
if status_config := config.get(CONF_STATUS):
sens = await sensor.new_sensor(status_config)
cg.add(hub.set_status_sensor(sens))

View File

@@ -0,0 +1,23 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import CONF_VERSION, ENTITY_CATEGORY_DIAGNOSTIC, ICON_CHIP
from . import CONF_GDK101_ID, GDK101Component
DEPENDENCIES = ["gdk101"]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_GDK101_ID): cv.use_id(GDK101Component),
cv.Required(CONF_VERSION): text_sensor.text_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP
),
}
)
async def to_code(config):
hub = await cg.get_variable(config[CONF_GDK101_ID])
var = await text_sensor.new_text_sensor(config[CONF_VERSION])
cg.add(hub.set_fw_version_text_sensor(var))

View File

@@ -235,7 +235,7 @@ void GraphLegend::init(Graph *g) {
std::string valstr =
value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
if (this->units_) {
valstr += trace->sensor_->get_unit_of_measurement();
valstr += trace->sensor_->get_unit_of_measurement_ref();
}
this->font_value_->measure(valstr.c_str(), &fw, &fos, &fbl, &fh);
if (fw > valw)
@@ -371,7 +371,7 @@ void Graph::draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_of
std::string valstr =
value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
if (legend_->units_) {
valstr += trace->sensor_->get_unit_of_measurement();
valstr += trace->sensor_->get_unit_of_measurement_ref();
}
buff->printf(xv, yv, legend_->font_value_, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", valstr.c_str());
ESP_LOGV(TAG, " value: %s", valstr.c_str());

View File

@@ -3,6 +3,8 @@ import re
from esphome import config_validation as cv
from esphome.const import CONF_ARGS, CONF_FORMAT
CONF_IF_NAN = "if_nan"
lv_uses = {
"USER_DATA",
"LOG",
@@ -21,23 +23,48 @@ lv_fonts_used = set()
esphome_fonts_used = set()
lvgl_components_required = set()
def validate_printf(value):
cfmt = r"""
# noqa
f_regex = re.compile(
r"""
( # start of capture group 1
% # literal "%"
(?:[-+0 #]{0,5}) # optional flags
[-+0 #]{0,5} # optional flags
(?:\d+|\*)? # width
(?:\.(?:\d+|\*))? # precision
(?:h|l|ll|w|I|I32|I64)? # size
f # type
)
""",
flags=re.VERBOSE,
)
# noqa
c_regex = re.compile(
r"""
( # start of capture group 1
% # literal "%"
[-+0 #]{0,5} # optional flags
(?:\d+|\*)? # width
(?:\.(?:\d+|\*))? # precision
(?:h|l|ll|w|I|I32|I64)? # size
[cCdiouxXeEfgGaAnpsSZ] # type
)
""" # noqa
matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.VERBOSE)
""",
flags=re.VERBOSE,
)
def validate_printf(value):
format_string = value[CONF_FORMAT]
matches = c_regex.findall(format_string)
if len(matches) != len(value[CONF_ARGS]):
raise cv.Invalid(
f"Found {len(matches)} printf-patterns ({', '.join(matches)}), but {len(value[CONF_ARGS])} args were given!"
)
if value.get(CONF_IF_NAN) and len(f_regex.findall(format_string)) != 1:
raise cv.Invalid(
"Use of 'if_nan' requires a single valid printf-pattern of type %f"
)
return value

View File

@@ -33,7 +33,13 @@ from .defines import (
call_lambda,
literal,
)
from .helpers import add_lv_use, esphome_fonts_used, lv_fonts_used, requires_component
from .helpers import (
CONF_IF_NAN,
add_lv_use,
esphome_fonts_used,
lv_fonts_used,
requires_component,
)
from .types import lv_font_t, lv_gradient_t
opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER")
@@ -412,7 +418,13 @@ class TextValidator(LValidator):
str_args = [str(x) for x in value[CONF_ARGS]]
arg_expr = cg.RawExpression(",".join(str_args))
format_str = cpp_string_escape(format_str)
return literal(f"str_sprintf({format_str}, {arg_expr}).c_str()")
sprintf_str = f"str_sprintf({format_str}, {arg_expr}).c_str()"
if nanval := value.get(CONF_IF_NAN):
nanval = cpp_string_escape(nanval)
return literal(
f"(std::isfinite({arg_expr}) ? {sprintf_str} : {nanval})"
)
return literal(sprintf_str)
if time_format := value.get(CONF_TIME_FORMAT):
source = value[CONF_TIME]
if isinstance(source, Lambda):

View File

@@ -20,7 +20,7 @@ from esphome.core.config import StartupTrigger
from . import defines as df, lv_validation as lvalid
from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR
from .helpers import requires_component, validate_printf
from .helpers import CONF_IF_NAN, requires_component, validate_printf
from .layout import (
FLEX_OBJ_SCHEMA,
GRID_CELL_SCHEMA,
@@ -54,6 +54,7 @@ PRINTF_TEXT_SCHEMA = cv.All(
{
cv.Required(CONF_FORMAT): cv.string,
cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_),
cv.Optional(CONF_IF_NAN): cv.string,
},
),
validate_printf,

View File

@@ -41,16 +41,16 @@ class LVGLSelect : public select::Select, public Component {
}
void publish() {
this->publish_state(this->widget_->get_selected_text());
auto index = this->widget_->get_selected_index();
this->publish_state(index);
if (this->restore_) {
auto index = this->widget_->get_selected_index();
this->pref_.save(&index);
}
}
protected:
void control(const std::string &value) override {
this->widget_->set_selected_text(value, this->anim_);
void control(size_t index) override {
this->widget_->set_selected_index(index, this->anim_);
this->publish();
}
void set_options_() {

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@philippderdiedas"]

View File

@@ -0,0 +1,31 @@
#include "mcp3221_sensor.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome {
namespace mcp3221 {
static const char *const TAG = "mcp3221";
float MCP3221Sensor::sample() {
uint8_t data[2];
if (this->read(data, 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Read failed");
this->status_set_warning();
return NAN;
}
this->status_clear_warning();
uint16_t value = encode_uint16(data[0], data[1]);
float voltage = value * this->reference_voltage_ / 4096.0f;
return voltage;
}
void MCP3221Sensor::update() {
float v = this->sample();
this->publish_state(v);
}
} // namespace mcp3221
} // namespace esphome

View File

@@ -0,0 +1,28 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h"
#include <cinttypes>
namespace esphome {
namespace mcp3221 {
class MCP3221Sensor : public sensor::Sensor,
public PollingComponent,
public voltage_sampler::VoltageSampler,
public i2c::I2CDevice {
public:
void set_reference_voltage(float reference_voltage) { this->reference_voltage_ = reference_voltage; }
void update() override;
float sample() override;
protected:
float reference_voltage_;
};
} // namespace mcp3221
} // namespace esphome

View File

@@ -0,0 +1,49 @@
import esphome.codegen as cg
from esphome.components import i2c, sensor, voltage_sampler
import esphome.config_validation as cv
from esphome.const import (
CONF_REFERENCE_VOLTAGE,
DEVICE_CLASS_VOLTAGE,
ICON_SCALE,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
)
AUTO_LOAD = ["voltage_sampler"]
DEPENDENCIES = ["i2c"]
mcp3221_ns = cg.esphome_ns.namespace("mcp3221")
MCP3221Sensor = mcp3221_ns.class_(
"MCP3221Sensor",
sensor.Sensor,
voltage_sampler.VoltageSampler,
cg.PollingComponent,
i2c.I2CDevice,
)
CONFIG_SCHEMA = (
sensor.sensor_schema(
MCP3221Sensor,
icon=ICON_SCALE,
accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT,
device_class=DEVICE_CLASS_VOLTAGE,
unit_of_measurement=UNIT_VOLT,
)
.extend(
{
cv.Optional(CONF_REFERENCE_VOLTAGE, default="3.3V"): cv.voltage,
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x48))
)
async def to_code(config):
var = await sensor.new_sensor(config)
cg.add(var.set_reference_voltage(config[CONF_REFERENCE_VOLTAGE]))
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)

View File

@@ -37,8 +37,6 @@ MDNS_STATIC_CONST_CHAR(SERVICE_TCP, "_tcp");
MDNS_STATIC_CONST_CHAR(VALUE_VERSION, ESPHOME_VERSION);
void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services) {
this->hostname_ = App.get_name();
// IMPORTANT: The #ifdef blocks below must match COMPONENTS_WITH_MDNS_SERVICES
// in mdns/__init__.py. If you add a new service here, update both locations.
@@ -179,7 +177,7 @@ void MDNSComponent::dump_config() {
ESP_LOGCONFIG(TAG,
"mDNS:\n"
" Hostname: %s",
this->hostname_.c_str());
App.get_name().c_str());
#ifdef USE_MDNS_STORE_SERVICES
ESP_LOGV(TAG, " Services:");
for (const auto &service : this->services_) {

View File

@@ -76,7 +76,6 @@ class MDNSComponent : public Component {
#ifdef USE_MDNS_STORE_SERVICES
StaticVector<MDNSService, MDNS_SERVICE_COUNT> services_{};
#endif
std::string hostname_;
void compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services);
};

View File

@@ -2,6 +2,7 @@
#if defined(USE_ESP32) && defined(USE_MDNS)
#include <mdns.h>
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "mdns_component.h"
@@ -27,8 +28,9 @@ void MDNSComponent::setup() {
return;
}
mdns_hostname_set(this->hostname_.c_str());
mdns_instance_name_set(this->hostname_.c_str());
const char *hostname = App.get_name().c_str();
mdns_hostname_set(hostname);
mdns_instance_name_set(hostname);
for (const auto &service : services) {
auto txt_records = std::make_unique<mdns_txt_item_t[]>(service.txt_records.size());

View File

@@ -4,6 +4,7 @@
#include <ESP8266mDNS.h>
#include "esphome/components/network/ip_address.h"
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "mdns_component.h"
@@ -20,7 +21,7 @@ void MDNSComponent::setup() {
this->compile_records_(services);
#endif
MDNS.begin(this->hostname_.c_str());
MDNS.begin(App.get_name().c_str());
for (const auto &service : services) {
// Strip the leading underscore from the proto and service_type. While it is

View File

@@ -3,6 +3,7 @@
#include "esphome/components/network/ip_address.h"
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "mdns_component.h"
@@ -20,7 +21,7 @@ void MDNSComponent::setup() {
this->compile_records_(services);
#endif
MDNS.begin(this->hostname_.c_str());
MDNS.begin(App.get_name().c_str());
for (const auto &service : services) {
// Strip the leading underscore from the proto and service_type. While it is

View File

@@ -3,6 +3,7 @@
#include "esphome/components/network/ip_address.h"
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "mdns_component.h"
@@ -20,7 +21,7 @@ void MDNSComponent::setup() {
this->compile_records_(services);
#endif
MDNS.begin(this->hostname_.c_str());
MDNS.begin(App.get_name().c_str());
for (const auto &service : services) {
// Strip the leading underscore from the proto and service_type. While it is

View File

@@ -28,8 +28,9 @@ void ModbusSelect::parse_and_publish(const std::vector<uint8_t> &data) {
if (map_it != this->mapping_.cend()) {
size_t idx = std::distance(this->mapping_.cbegin(), map_it);
new_state = std::string(this->option_at(idx));
ESP_LOGV(TAG, "Found option %s for value %lld", new_state->c_str(), value);
ESP_LOGV(TAG, "Found option %s for value %lld", this->option_at(idx), value);
this->publish_state(idx);
return;
} else {
ESP_LOGE(TAG, "No option found for mapping %lld", value);
}
@@ -40,19 +41,16 @@ void ModbusSelect::parse_and_publish(const std::vector<uint8_t> &data) {
}
}
void ModbusSelect::control(const std::string &value) {
auto idx = this->index_of(value);
if (!idx.has_value()) {
ESP_LOGW(TAG, "Invalid option '%s'", value.c_str());
return;
}
optional<int64_t> mapval = this->mapping_[idx.value()];
ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, value.c_str());
void ModbusSelect::control(size_t index) {
optional<int64_t> mapval = this->mapping_[index];
const char *option = this->option_at(index);
ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, option);
std::vector<uint16_t> data;
if (this->write_transform_func_.has_value()) {
auto val = (*this->write_transform_func_)(this, value, *mapval, data);
// Transform func requires string parameter for backward compatibility
auto val = (*this->write_transform_func_)(this, std::string(option), *mapval, data);
if (val.has_value()) {
mapval = *val;
ESP_LOGV(TAG, "write_lambda returned mapping value %lld", *mapval);
@@ -85,7 +83,7 @@ void ModbusSelect::control(const std::string &value) {
this->parent_->queue_command(write_cmd);
if (this->optimistic_)
this->publish_state(value);
this->publish_state(index);
}
} // namespace modbus_controller

View File

@@ -38,7 +38,7 @@ class ModbusSelect : public Component, public select::Select, public SensorItem
void dump_config() override;
void parse_and_publish(const std::vector<uint8_t> &data) override;
void control(const std::string &value) override;
void control(size_t index) override;
protected:
std::vector<int64_t> mapping_{};

View File

@@ -36,7 +36,7 @@ void MQTTAlarmControlPanelComponent::setup() {
} else if (strcasecmp(payload.c_str(), "TRIGGERED") == 0) {
call.triggered();
} else {
ESP_LOGW(TAG, "'%s': Received unknown command payload %s", this->friendly_name().c_str(), payload.c_str());
ESP_LOGW(TAG, "'%s': Received unknown command payload %s", this->friendly_name_().c_str(), payload.c_str());
}
call.perform();
});

View File

@@ -30,9 +30,12 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor
}
void MQTTBinarySensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (!this->binary_sensor_->get_device_class().empty())
root[MQTT_DEVICE_CLASS] = this->binary_sensor_->get_device_class();
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
const auto device_class = this->binary_sensor_->get_device_class_ref();
if (!device_class.empty()) {
root[MQTT_DEVICE_CLASS] = device_class;
}
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
if (this->binary_sensor_->is_status_binary_sensor())
root[MQTT_PAYLOAD_ON] = mqtt::global_mqtt_client->get_availability().payload_available;
if (this->binary_sensor_->is_status_binary_sensor())

View File

@@ -20,7 +20,7 @@ void MQTTButtonComponent::setup() {
if (payload == "PRESS") {
this->button_->press();
} else {
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str());
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str());
this->status_momentary_warning("state", 5000);
}
});
@@ -33,8 +33,9 @@ void MQTTButtonComponent::dump_config() {
void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
config.state_topic = false;
if (!this->button_->get_device_class().empty()) {
root[MQTT_DEVICE_CLASS] = this->button_->get_device_class();
const auto device_class = this->button_->get_device_class_ref();
if (!device_class.empty()) {
root[MQTT_DEVICE_CLASS] = device_class;
}
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
}

View File

@@ -64,11 +64,11 @@ bool MQTTComponent::send_discovery_() {
const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
if (discovery_info.clean) {
ESP_LOGV(TAG, "'%s': Cleaning discovery", this->friendly_name().c_str());
ESP_LOGV(TAG, "'%s': Cleaning discovery", this->friendly_name_().c_str());
return global_mqtt_client->publish(this->get_discovery_topic_(discovery_info), "", 0, this->qos_, true);
}
ESP_LOGV(TAG, "'%s': Sending discovery", this->friendly_name().c_str());
ESP_LOGV(TAG, "'%s': Sending discovery", this->friendly_name_().c_str());
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
return global_mqtt_client->publish_json(
@@ -85,12 +85,16 @@ bool MQTTComponent::send_discovery_() {
}
// Fields from EntityBase
root[MQTT_NAME] = this->get_entity()->has_own_name() ? this->friendly_name() : "";
root[MQTT_NAME] = this->get_entity()->has_own_name() ? this->friendly_name_() : "";
if (this->is_disabled_by_default())
if (this->is_disabled_by_default_())
root[MQTT_ENABLED_BY_DEFAULT] = false;
if (!this->get_icon().empty())
root[MQTT_ICON] = this->get_icon();
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
const auto icon_ref = this->get_icon_ref_();
if (!icon_ref.empty()) {
root[MQTT_ICON] = icon_ref;
}
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
const auto entity_category = this->get_entity()->get_entity_category();
switch (entity_category) {
@@ -122,7 +126,7 @@ bool MQTTComponent::send_discovery_() {
const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
char friendly_name_hash[9];
sprintf(friendly_name_hash, "%08" PRIx32, fnv1_hash(this->friendly_name()));
sprintf(friendly_name_hash, "%08" PRIx32, fnv1_hash(this->friendly_name_()));
friendly_name_hash[8] = 0; // ensure the hash-string ends with null
root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash;
} else {
@@ -184,7 +188,7 @@ bool MQTTComponent::is_discovery_enabled() const {
}
std::string MQTTComponent::get_default_object_id_() const {
return str_sanitize(str_snake_case(this->friendly_name()));
return str_sanitize(str_snake_case(this->friendly_name_()));
}
void MQTTComponent::subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos) {
@@ -268,9 +272,9 @@ void MQTTComponent::schedule_resend_state() { this->resend_state_ = true; }
bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connected(); }
// Pull these properties from EntityBase if not overridden
std::string MQTTComponent::friendly_name() const { return this->get_entity()->get_name(); }
std::string MQTTComponent::get_icon() const { return this->get_entity()->get_icon(); }
bool MQTTComponent::is_disabled_by_default() const { return this->get_entity()->is_disabled_by_default(); }
std::string MQTTComponent::friendly_name_() const { return this->get_entity()->get_name(); }
StringRef MQTTComponent::get_icon_ref_() const { return this->get_entity()->get_icon_ref(); }
bool MQTTComponent::is_disabled_by_default_() const { return this->get_entity()->is_disabled_by_default(); }
bool MQTTComponent::is_internal() {
if (this->has_custom_state_topic_) {
// If the custom state_topic is null, return true as it is internal and should not publish

View File

@@ -165,13 +165,13 @@ class MQTTComponent : public Component {
virtual const EntityBase *get_entity() const = 0;
/// Get the friendly name of this MQTT component.
virtual std::string friendly_name() const;
std::string friendly_name_() const;
/// Get the icon field of this component
virtual std::string get_icon() const;
/// Get the icon field of this component as StringRef
StringRef get_icon_ref_() const;
/// Get whether the underlying Entity is disabled by default
virtual bool is_disabled_by_default() const;
bool is_disabled_by_default_() const;
/// Get the MQTT topic that new states will be shared to.
std::string get_state_topic_() const;

View File

@@ -67,9 +67,12 @@ void MQTTCoverComponent::dump_config() {
}
}
void MQTTCoverComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (!this->cover_->get_device_class().empty())
root[MQTT_DEVICE_CLASS] = this->cover_->get_device_class();
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
const auto device_class = this->cover_->get_device_class_ref();
if (!device_class.empty()) {
root[MQTT_DEVICE_CLASS] = device_class;
}
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
auto traits = this->cover_->get_traits();
if (traits.get_is_assumed_state()) {

View File

@@ -21,8 +21,12 @@ void MQTTEventComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConf
for (const auto &event_type : this->event_->get_event_types())
event_types.add(event_type);
if (!this->event_->get_device_class().empty())
root[MQTT_DEVICE_CLASS] = this->event_->get_device_class();
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
const auto device_class = this->event_->get_device_class_ref();
if (!device_class.empty()) {
root[MQTT_DEVICE_CLASS] = device_class;
}
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
config.command_topic = false;
}
@@ -34,8 +38,8 @@ void MQTTEventComponent::setup() {
void MQTTEventComponent::dump_config() {
ESP_LOGCONFIG(TAG, "MQTT Event '%s': ", this->event_->get_name().c_str());
ESP_LOGCONFIG(TAG, "Event Types: ");
for (const auto &event_type : this->event_->get_event_types()) {
ESP_LOGCONFIG(TAG, "- %s", event_type.c_str());
for (const char *event_type : this->event_->get_event_types()) {
ESP_LOGCONFIG(TAG, "- %s", event_type);
}
LOG_MQTT_COMPONENT(true, true);
}

View File

@@ -24,15 +24,15 @@ void MQTTFanComponent::setup() {
auto val = parse_on_off(payload.c_str());
switch (val) {
case PARSE_ON:
ESP_LOGD(TAG, "'%s' Turning Fan ON.", this->friendly_name().c_str());
ESP_LOGD(TAG, "'%s' Turning Fan ON.", this->friendly_name_().c_str());
this->state_->turn_on().perform();
break;
case PARSE_OFF:
ESP_LOGD(TAG, "'%s' Turning Fan OFF.", this->friendly_name().c_str());
ESP_LOGD(TAG, "'%s' Turning Fan OFF.", this->friendly_name_().c_str());
this->state_->turn_off().perform();
break;
case PARSE_TOGGLE:
ESP_LOGD(TAG, "'%s' Toggling Fan.", this->friendly_name().c_str());
ESP_LOGD(TAG, "'%s' Toggling Fan.", this->friendly_name_().c_str());
this->state_->toggle().perform();
break;
case PARSE_NONE:
@@ -48,11 +48,11 @@ void MQTTFanComponent::setup() {
auto val = parse_on_off(payload.c_str(), "forward", "reverse");
switch (val) {
case PARSE_ON:
ESP_LOGD(TAG, "'%s': Setting direction FORWARD", this->friendly_name().c_str());
ESP_LOGD(TAG, "'%s': Setting direction FORWARD", this->friendly_name_().c_str());
this->state_->make_call().set_direction(fan::FanDirection::FORWARD).perform();
break;
case PARSE_OFF:
ESP_LOGD(TAG, "'%s': Setting direction REVERSE", this->friendly_name().c_str());
ESP_LOGD(TAG, "'%s': Setting direction REVERSE", this->friendly_name_().c_str());
this->state_->make_call().set_direction(fan::FanDirection::REVERSE).perform();
break;
case PARSE_TOGGLE:
@@ -75,11 +75,11 @@ void MQTTFanComponent::setup() {
auto val = parse_on_off(payload.c_str(), "oscillate_on", "oscillate_off");
switch (val) {
case PARSE_ON:
ESP_LOGD(TAG, "'%s': Setting oscillating ON", this->friendly_name().c_str());
ESP_LOGD(TAG, "'%s': Setting oscillating ON", this->friendly_name_().c_str());
this->state_->make_call().set_oscillating(true).perform();
break;
case PARSE_OFF:
ESP_LOGD(TAG, "'%s': Setting oscillating OFF", this->friendly_name().c_str());
ESP_LOGD(TAG, "'%s': Setting oscillating OFF", this->friendly_name_().c_str());
this->state_->make_call().set_oscillating(false).perform();
break;
case PARSE_TOGGLE:

View File

@@ -24,7 +24,7 @@ void MQTTLockComponent::setup() {
} else if (strcasecmp(payload.c_str(), "OPEN") == 0) {
this->lock_->open();
} else {
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str());
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str());
this->status_momentary_warning("state", 5000);
}
});

View File

@@ -44,8 +44,11 @@ void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon
root[MQTT_MIN] = traits.get_min_value();
root[MQTT_MAX] = traits.get_max_value();
root[MQTT_STEP] = traits.get_step();
if (!this->number_->traits.get_unit_of_measurement().empty())
root[MQTT_UNIT_OF_MEASUREMENT] = this->number_->traits.get_unit_of_measurement();
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
const auto unit_of_measurement = this->number_->traits.get_unit_of_measurement_ref();
if (!unit_of_measurement.empty()) {
root[MQTT_UNIT_OF_MEASUREMENT] = unit_of_measurement;
}
switch (this->number_->traits.get_mode()) {
case NUMBER_MODE_AUTO:
break;
@@ -56,8 +59,11 @@ void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon
root[MQTT_MODE] = "slider";
break;
}
if (!this->number_->traits.get_device_class().empty())
root[MQTT_DEVICE_CLASS] = this->number_->traits.get_device_class();
const auto device_class = this->number_->traits.get_device_class_ref();
if (!device_class.empty()) {
root[MQTT_DEVICE_CLASS] = device_class;
}
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
config.command_topic = true;
}

View File

@@ -44,13 +44,17 @@ void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire
void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; }
void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (!this->sensor_->get_device_class().empty()) {
root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class();
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
const auto device_class = this->sensor_->get_device_class_ref();
if (!device_class.empty()) {
root[MQTT_DEVICE_CLASS] = device_class;
}
if (!this->sensor_->get_unit_of_measurement().empty())
root[MQTT_UNIT_OF_MEASUREMENT] = this->sensor_->get_unit_of_measurement();
const auto unit_of_measurement = this->sensor_->get_unit_of_measurement_ref();
if (!unit_of_measurement.empty()) {
root[MQTT_UNIT_OF_MEASUREMENT] = unit_of_measurement;
}
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
if (this->get_expire_after() > 0)
root[MQTT_EXPIRE_AFTER] = this->get_expire_after() / 1000;

View File

@@ -29,7 +29,7 @@ void MQTTSwitchComponent::setup() {
break;
case PARSE_NONE:
default:
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str());
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str());
this->status_momentary_warning("state", 5000);
break;
}

View File

@@ -15,10 +15,12 @@ using namespace esphome::text_sensor;
MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {}
void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (!this->sensor_->get_device_class().empty()) {
root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class();
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
const auto device_class = this->sensor_->get_device_class_ref();
if (!device_class.empty()) {
root[MQTT_DEVICE_CLASS] = device_class;
}
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
config.command_topic = false;
}
void MQTTTextSensor::setup() {

View File

@@ -20,7 +20,7 @@ void MQTTUpdateComponent::setup() {
if (payload == "INSTALL") {
this->update_->perform();
} else {
ESP_LOGW(TAG, "'%s': Received unknown update payload: %s", this->friendly_name().c_str(), payload.c_str());
ESP_LOGW(TAG, "'%s': Received unknown update payload: %s", this->friendly_name_().c_str(), payload.c_str());
this->status_momentary_warning("state", 5000);
}
});

View File

@@ -49,10 +49,12 @@ void MQTTValveComponent::dump_config() {
}
}
void MQTTValveComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (!this->valve_->get_device_class().empty()) {
root[MQTT_DEVICE_CLASS] = this->valve_->get_device_class();
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
const auto device_class = this->valve_->get_device_class_ref();
if (!device_class.empty()) {
root[MQTT_DEVICE_CLASS] = device_class;
}
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
auto traits = this->valve_->get_traits();
if (traits.get_is_assumed_state()) {

View File

@@ -9,7 +9,7 @@ from esphome.components.esp32 import (
from esphome.components.mdns import MDNSComponent, enable_mdns_storage
import esphome.config_validation as cv
from esphome.const import CONF_CHANNEL, CONF_ENABLE_IPV6, CONF_ID, CONF_USE_ADDRESS
from esphome.core import CORE
from esphome.core import CORE, TimePeriodMilliseconds
import esphome.final_validate as fv
from esphome.types import ConfigType
@@ -22,6 +22,7 @@ from .const import (
CONF_NETWORK_KEY,
CONF_NETWORK_NAME,
CONF_PAN_ID,
CONF_POLL_PERIOD,
CONF_PSKC,
CONF_SRP_ID,
CONF_TLV,
@@ -89,7 +90,7 @@ def set_sdkconfig_options(config):
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT", True)
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT_MAX_SERVICES", 5)
# TODO: Add suport for sleepy end devices
# TODO: Add suport for synchronized sleepy end devices (SSED)
add_idf_sdkconfig_option(f"CONFIG_OPENTHREAD_{config.get(CONF_DEVICE_TYPE)}", True)
@@ -113,6 +114,17 @@ _CONNECTION_SCHEMA = cv.Schema(
def _validate(config: ConfigType) -> ConfigType:
if CONF_USE_ADDRESS not in config:
config[CONF_USE_ADDRESS] = f"{CORE.name}.local"
device_type = config.get(CONF_DEVICE_TYPE)
poll_period = config.get(CONF_POLL_PERIOD)
if (
device_type == "FTD"
and poll_period
and poll_period > TimePeriodMilliseconds(milliseconds=0)
):
raise cv.Invalid(
f"{CONF_POLL_PERIOD} can only be used with {CONF_DEVICE_TYPE}: MTD"
)
return config
@@ -135,6 +147,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_FORCE_DATASET): cv.boolean,
cv.Optional(CONF_TLV): cv.string_strict,
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
cv.Optional(CONF_POLL_PERIOD): cv.positive_time_period_milliseconds,
}
).extend(_CONNECTION_SCHEMA),
cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV),
@@ -167,6 +180,8 @@ async def to_code(config):
ot = cg.new_Pvariable(config[CONF_ID])
cg.add(ot.set_use_address(config[CONF_USE_ADDRESS]))
await cg.register_component(ot, config)
if (poll_period := config.get(CONF_POLL_PERIOD)) is not None:
cg.add(ot.set_poll_period(poll_period))
srp = cg.new_Pvariable(config[CONF_SRP_ID])
mdns_component = await cg.get_variable(config[CONF_MDNS_ID])

View File

@@ -6,6 +6,7 @@ CONF_MESH_LOCAL_PREFIX = "mesh_local_prefix"
CONF_NETWORK_NAME = "network_name"
CONF_NETWORK_KEY = "network_key"
CONF_PAN_ID = "pan_id"
CONF_POLL_PERIOD = "poll_period"
CONF_PSKC = "pskc"
CONF_SRP_ID = "srp_id"
CONF_TLV = "tlv"

View File

@@ -29,6 +29,23 @@ OpenThreadComponent *global_openthread_component = // NOLINT(cppcoreguidelines-
OpenThreadComponent::OpenThreadComponent() { global_openthread_component = this; }
void OpenThreadComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Open Thread:");
#if CONFIG_OPENTHREAD_FTD
ESP_LOGCONFIG(TAG, " Device Type: FTD");
#elif CONFIG_OPENTHREAD_MTD
ESP_LOGCONFIG(TAG, " Device Type: MTD");
// TBD: Synchronized Sleepy End Device
if (this->poll_period > 0) {
ESP_LOGCONFIG(TAG, " Device is configured as Sleepy End Device (SED)");
uint32_t duration = this->poll_period / 1000;
ESP_LOGCONFIG(TAG, " Poll Period: %" PRIu32 "s", duration);
} else {
ESP_LOGCONFIG(TAG, " Device is configured as Minimal End Device (MED)");
}
#endif
}
bool OpenThreadComponent::is_connected() {
auto lock = InstanceLock::try_acquire(100);
if (!lock) {

View File

@@ -22,6 +22,7 @@ class OpenThreadComponent : public Component {
public:
OpenThreadComponent();
~OpenThreadComponent();
void dump_config() override;
void setup() override;
bool teardown() override;
float get_setup_priority() const override { return setup_priority::WIFI; }
@@ -35,6 +36,9 @@ class OpenThreadComponent : public Component {
const char *get_use_address() const;
void set_use_address(const char *use_address);
#if CONFIG_OPENTHREAD_MTD
void set_poll_period(uint32_t poll_period) { this->poll_period = poll_period; }
#endif
protected:
std::optional<otIp6Address> get_omr_address_(InstanceLock &lock);
@@ -46,6 +50,9 @@ class OpenThreadComponent : public Component {
// Stores a pointer to a string literal (static storage duration).
// ONLY set from Python-generated code with string literals - never dynamic strings.
const char *use_address_{""};
#if CONFIG_OPENTHREAD_MTD
uint32_t poll_period{0};
#endif
};
extern OpenThreadComponent *global_openthread_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -105,6 +105,32 @@ void OpenThreadComponent::ot_main() {
esp_cli_custom_command_init();
#endif // CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
otLinkModeConfig link_mode_config = {0};
#if CONFIG_OPENTHREAD_FTD
link_mode_config.mRxOnWhenIdle = true;
link_mode_config.mDeviceType = true;
link_mode_config.mNetworkData = true;
#elif CONFIG_OPENTHREAD_MTD
if (this->poll_period > 0) {
if (otLinkSetPollPeriod(esp_openthread_get_instance(), this->poll_period) != OT_ERROR_NONE) {
ESP_LOGE(TAG, "Failed to set OpenThread pollperiod.");
}
uint32_t link_polling_period = otLinkGetPollPeriod(esp_openthread_get_instance());
ESP_LOGD(TAG, "Link Polling Period: %d", link_polling_period);
}
link_mode_config.mRxOnWhenIdle = this->poll_period == 0;
link_mode_config.mDeviceType = false;
link_mode_config.mNetworkData = false;
#endif
if (otThreadSetLinkMode(esp_openthread_get_instance(), link_mode_config) != OT_ERROR_NONE) {
ESP_LOGE(TAG, "Failed to set OpenThread linkmode.");
}
link_mode_config = otThreadGetLinkMode(esp_openthread_get_instance());
ESP_LOGD(TAG, "Link Mode Device Type: %s", link_mode_config.mDeviceType ? "true" : "false");
ESP_LOGD(TAG, "Link Mode Network Data: %s", link_mode_config.mNetworkData ? "true" : "false");
ESP_LOGD(TAG, "Link Mode RX On When Idle: %s", link_mode_config.mRxOnWhenIdle ? "true" : "false");
// Run the main loop
#if CONFIG_OPENTHREAD_CLI
esp_openthread_cli_create_task();

View File

@@ -158,7 +158,7 @@ void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor
stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str());
stream->print(ESPHOME_F("\",unit=\""));
stream->print(obj->get_unit_of_measurement().c_str());
stream->print(obj->get_unit_of_measurement_ref().c_str());
stream->print(ESPHOME_F("\"} "));
stream->print(value_accuracy_to_string(obj->state, obj->get_accuracy_decimals()).c_str());
stream->print(ESPHOME_F("\n"));

View File

@@ -2,6 +2,7 @@ import logging
import textwrap
import esphome.codegen as cg
from esphome.components.const import CONF_IGNORE_NOT_FOUND
from esphome.components.esp32 import (
CONF_CPU_FREQUENCY,
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES,
@@ -123,6 +124,7 @@ def get_config_schema(config):
cv.Optional(CONF_ENABLE_ECC, default=False): cv.boolean,
cv.Optional(CONF_SPEED, default=speeds[0]): cv.one_of(*speeds, upper=True),
cv.Optional(CONF_DISABLED, default=False): cv.boolean,
cv.Optional(CONF_IGNORE_NOT_FOUND, default=True): cv.boolean,
}
)(config)
@@ -147,7 +149,9 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_SPIRAM", True)
add_idf_sdkconfig_option("CONFIG_SPIRAM_USE", True)
add_idf_sdkconfig_option("CONFIG_SPIRAM_USE_CAPS_ALLOC", True)
add_idf_sdkconfig_option("CONFIG_SPIRAM_IGNORE_NOTFOUND", True)
add_idf_sdkconfig_option(
"CONFIG_SPIRAM_IGNORE_NOTFOUND", config[CONF_IGNORE_NOT_FOUND]
)
add_idf_sdkconfig_option(f"CONFIG_SPIRAM_MODE_{SDK_MODES[config[CONF_MODE]]}", True)

View File

@@ -3,12 +3,9 @@
namespace esphome {
namespace seeed_mr24hpc1 {
void ExistenceBoundarySelect::control(const std::string &value) {
this->publish_state(value);
auto index = this->index_of(value);
if (index.has_value()) {
this->parent_->set_existence_boundary(index.value());
}
void ExistenceBoundarySelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_existence_boundary(index);
}
} // namespace seeed_mr24hpc1

View File

@@ -11,7 +11,7 @@ class ExistenceBoundarySelect : public select::Select, public Parented<MR24HPC1C
ExistenceBoundarySelect() = default;
protected:
void control(const std::string &value) override;
void control(size_t index) override;
};
} // namespace seeed_mr24hpc1

View File

@@ -3,12 +3,9 @@
namespace esphome {
namespace seeed_mr24hpc1 {
void MotionBoundarySelect::control(const std::string &value) {
this->publish_state(value);
auto index = this->index_of(value);
if (index.has_value()) {
this->parent_->set_motion_boundary(index.value());
}
void MotionBoundarySelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_motion_boundary(index);
}
} // namespace seeed_mr24hpc1

View File

@@ -11,7 +11,7 @@ class MotionBoundarySelect : public select::Select, public Parented<MR24HPC1Comp
MotionBoundarySelect() = default;
protected:
void control(const std::string &value) override;
void control(size_t index) override;
};
} // namespace seeed_mr24hpc1

View File

@@ -3,12 +3,9 @@
namespace esphome {
namespace seeed_mr24hpc1 {
void SceneModeSelect::control(const std::string &value) {
this->publish_state(value);
auto index = this->index_of(value);
if (index.has_value()) {
this->parent_->set_scene_mode(index.value());
}
void SceneModeSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_scene_mode(index);
}
} // namespace seeed_mr24hpc1

View File

@@ -11,7 +11,7 @@ class SceneModeSelect : public select::Select, public Parented<MR24HPC1Component
SceneModeSelect() = default;
protected:
void control(const std::string &value) override;
void control(size_t index) override;
};
} // namespace seeed_mr24hpc1

View File

@@ -3,12 +3,9 @@
namespace esphome {
namespace seeed_mr24hpc1 {
void UnmanTimeSelect::control(const std::string &value) {
this->publish_state(value);
auto index = this->index_of(value);
if (index.has_value()) {
this->parent_->set_unman_time(index.value());
}
void UnmanTimeSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_unman_time(index);
}
} // namespace seeed_mr24hpc1

View File

@@ -11,7 +11,7 @@ class UnmanTimeSelect : public select::Select, public Parented<MR24HPC1Component
UnmanTimeSelect() = default;
protected:
void control(const std::string &value) override;
void control(size_t index) override;
};
} // namespace seeed_mr24hpc1

View File

@@ -3,12 +3,9 @@
namespace esphome {
namespace seeed_mr60fda2 {
void HeightThresholdSelect::control(const std::string &value) {
this->publish_state(value);
auto index = this->index_of(value);
if (index.has_value()) {
this->parent_->set_height_threshold(index.value());
}
void HeightThresholdSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_height_threshold(index);
}
} // namespace seeed_mr60fda2

View File

@@ -11,7 +11,7 @@ class HeightThresholdSelect : public select::Select, public Parented<MR60FDA2Com
HeightThresholdSelect() = default;
protected:
void control(const std::string &value) override;
void control(size_t index) override;
};
} // namespace seeed_mr60fda2

View File

@@ -3,12 +3,9 @@
namespace esphome {
namespace seeed_mr60fda2 {
void InstallHeightSelect::control(const std::string &value) {
this->publish_state(value);
auto index = this->index_of(value);
if (index.has_value()) {
this->parent_->set_install_height(index.value());
}
void InstallHeightSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_install_height(index);
}
} // namespace seeed_mr60fda2

View File

@@ -11,7 +11,7 @@ class InstallHeightSelect : public select::Select, public Parented<MR60FDA2Compo
InstallHeightSelect() = default;
protected:
void control(const std::string &value) override;
void control(size_t index) override;
};
} // namespace seeed_mr60fda2

View File

@@ -3,12 +3,9 @@
namespace esphome {
namespace seeed_mr60fda2 {
void SensitivitySelect::control(const std::string &value) {
this->publish_state(value);
auto index = this->index_of(value);
if (index.has_value()) {
this->parent_->set_sensitivity(index.value());
}
void SensitivitySelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_sensitivity(index);
}
} // namespace seeed_mr60fda2

View File

@@ -11,7 +11,7 @@ class SensitivitySelect : public select::Select, public Parented<MR60FDA2Compone
SensitivitySelect() = default;
protected:
void control(const std::string &value) override;
void control(size_t index) override;
};
} // namespace seeed_mr60fda2

View File

@@ -35,7 +35,7 @@ class Select : public EntityBase {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
/// @deprecated Use current_option() instead. This member will be removed in ESPHome 2026.5.0.
__attribute__((deprecated("Use current_option() instead of .state. Will be removed in 2026.5.0")))
ESPDEPRECATED("Use current_option() instead of .state. Will be removed in 2026.5.0", "2025.11.0")
std::string state{};
Select() = default;

View File

@@ -72,9 +72,9 @@ StateClass Sensor::get_state_class() {
void Sensor::publish_state(float state) {
this->raw_state = state;
if (this->raw_callback_) {
this->raw_callback_->call(state);
}
// Call raw callbacks (before filters)
this->callbacks_.call_first(this->raw_count_, state);
ESP_LOGV(TAG, "'%s': Received new state %f", this->name_.c_str(), state);
@@ -85,12 +85,12 @@ void Sensor::publish_state(float state) {
}
}
void Sensor::add_on_state_callback(std::function<void(float)> &&callback) { this->callback_.add(std::move(callback)); }
void Sensor::add_on_state_callback(std::function<void(float)> &&callback) {
this->callbacks_.add_second(std::move(callback));
}
void Sensor::add_on_raw_state_callback(std::function<void(float)> &&callback) {
if (!this->raw_callback_) {
this->raw_callback_ = make_unique<CallbackManager<void(float)>>();
}
this->raw_callback_->add(std::move(callback));
this->callbacks_.add_first(std::move(callback), &this->raw_count_);
}
void Sensor::add_filter(Filter *filter) {
@@ -130,7 +130,13 @@ void Sensor::internal_send_state_to_frontend(float state) {
this->state = state;
ESP_LOGD(TAG, "'%s': Sending state %.5f %s with %d decimals of accuracy", this->get_name().c_str(), state,
this->get_unit_of_measurement_ref().c_str(), this->get_accuracy_decimals());
this->callback_.call(state);
// Call filtered callbacks (after filters)
this->callbacks_.call_second(this->raw_count_, state);
#if defined(USE_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_sensor_update(this);
#endif
}
} // namespace sensor

View File

@@ -124,8 +124,7 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
void internal_send_state_to_frontend(float state);
protected:
std::unique_ptr<CallbackManager<void(float)>> raw_callback_; ///< Storage for raw state callbacks (lazy allocated).
CallbackManager<void(float)> callback_; ///< Storage for filtered state callbacks.
PartitionedCallbackManager<void(float)> callbacks_;
Filter *filter_list_{nullptr}; ///< Store all active filters.
@@ -140,6 +139,8 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
uint8_t force_update : 1;
uint8_t reserved : 5; // Reserved for future use
} sensor_flags_{};
uint8_t raw_count_{0}; ///< Number of raw callbacks (partition point in callbacks_ vector)
};
} // namespace sensor

View File

@@ -172,16 +172,7 @@ class LWIPRawImpl : public Socket {
errno = ECONNRESET;
return "";
}
char buffer[50] = {};
if (IP_IS_V4_VAL(pcb_->remote_ip)) {
inet_ntoa_r(pcb_->remote_ip, buffer, sizeof(buffer));
}
#if LWIP_IPV6
else if (IP_IS_V6_VAL(pcb_->remote_ip)) {
inet6_ntoa_r(pcb_->remote_ip, buffer, sizeof(buffer));
}
#endif
return std::string(buffer);
return this->format_ip_address_(pcb_->remote_ip);
}
int getsockname(struct sockaddr *name, socklen_t *addrlen) override {
if (pcb_ == nullptr) {
@@ -199,16 +190,7 @@ class LWIPRawImpl : public Socket {
errno = ECONNRESET;
return "";
}
char buffer[50] = {};
if (IP_IS_V4_VAL(pcb_->local_ip)) {
inet_ntoa_r(pcb_->local_ip, buffer, sizeof(buffer));
}
#if LWIP_IPV6
else if (IP_IS_V6_VAL(pcb_->local_ip)) {
inet6_ntoa_r(pcb_->local_ip, buffer, sizeof(buffer));
}
#endif
return std::string(buffer);
return this->format_ip_address_(pcb_->local_ip);
}
int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override {
if (pcb_ == nullptr) {
@@ -499,6 +481,19 @@ class LWIPRawImpl : public Socket {
}
protected:
std::string format_ip_address_(const ip_addr_t &ip) {
char buffer[50] = {};
if (IP_IS_V4_VAL(ip)) {
inet_ntoa_r(ip, buffer, sizeof(buffer));
}
#if LWIP_IPV6
else if (IP_IS_V6_VAL(ip)) {
inet6_ntoa_r(ip, buffer, sizeof(buffer));
}
#endif
return std::string(buffer);
}
int ip2sockaddr_(ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen) {
if (family_ == AF_INET) {
if (*addrlen < sizeof(struct sockaddr_in)) {

View File

@@ -650,7 +650,7 @@ void Sprinkler::set_valve_run_duration(const optional<size_t> valve_number, cons
return;
}
auto call = this->valve_[valve_number.value()].run_duration_number->make_call();
if (this->valve_[valve_number.value()].run_duration_number->traits.get_unit_of_measurement() == MIN_STR) {
if (this->valve_[valve_number.value()].run_duration_number->traits.get_unit_of_measurement_ref() == MIN_STR) {
call.set_value(run_duration.value() / 60.0);
} else {
call.set_value(run_duration.value());
@@ -732,7 +732,7 @@ uint32_t Sprinkler::valve_run_duration(const size_t valve_number) {
return 0;
}
if (this->valve_[valve_number].run_duration_number != nullptr) {
if (this->valve_[valve_number].run_duration_number->traits.get_unit_of_measurement() == MIN_STR) {
if (this->valve_[valve_number].run_duration_number->traits.get_unit_of_measurement_ref() == MIN_STR) {
return static_cast<uint32_t>(roundf(this->valve_[valve_number].run_duration_number->state * 60));
} else {
return static_cast<uint32_t>(roundf(this->valve_[valve_number].run_duration_number->state));

View File

@@ -49,7 +49,7 @@ struct SensorInfo {
uint8_t store_index;
};
class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, public Component {
class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControlPanel, public Component {
public:
TemplateAlarmControlPanel();
void dump_config() override;

View File

@@ -7,7 +7,7 @@
namespace esphome {
namespace template_ {
class TemplateBinarySensor : public Component, public binary_sensor::BinarySensor {
class TemplateBinarySensor final : public Component, public binary_sensor::BinarySensor {
public:
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }

View File

@@ -5,7 +5,7 @@
namespace esphome {
namespace template_ {
class TemplateButton : public button::Button {
class TemplateButton final : public button::Button {
public:
// Implements the abstract `press_action` but the `on_press` trigger already handles the press.
void press_action() override{};

View File

@@ -14,7 +14,7 @@ enum TemplateCoverRestoreMode {
COVER_RESTORE_AND_CALL,
};
class TemplateCover : public cover::Cover, public Component {
class TemplateCover final : public cover::Cover, public Component {
public:
TemplateCover();

View File

@@ -14,7 +14,7 @@
namespace esphome {
namespace template_ {
class TemplateDate : public datetime::DateEntity, public PollingComponent {
class TemplateDate final : public datetime::DateEntity, public PollingComponent {
public:
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }

View File

@@ -14,7 +14,7 @@
namespace esphome {
namespace template_ {
class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponent {
class TemplateDateTime final : public datetime::DateTimeEntity, public PollingComponent {
public:
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }

View File

@@ -14,7 +14,7 @@
namespace esphome {
namespace template_ {
class TemplateTime : public datetime::TimeEntity, public PollingComponent {
class TemplateTime final : public datetime::TimeEntity, public PollingComponent {
public:
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }

View File

@@ -6,7 +6,7 @@
namespace esphome {
namespace template_ {
class TemplateEvent : public Component, public event::Event {};
class TemplateEvent final : public Component, public event::Event {};
} // namespace template_
} // namespace esphome

View File

@@ -6,7 +6,7 @@
namespace esphome {
namespace template_ {
class TemplateFan : public Component, public fan::Fan {
class TemplateFan final : public Component, public fan::Fan {
public:
TemplateFan() {}
void setup() override;

View File

@@ -8,7 +8,7 @@
namespace esphome {
namespace template_ {
class TemplateLock : public lock::Lock, public Component {
class TemplateLock final : public lock::Lock, public Component {
public:
TemplateLock();

View File

@@ -9,7 +9,7 @@
namespace esphome {
namespace template_ {
class TemplateNumber : public number::Number, public PollingComponent {
class TemplateNumber final : public number::Number, public PollingComponent {
public:
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }

View File

@@ -7,7 +7,7 @@
namespace esphome {
namespace template_ {
class TemplateBinaryOutput : public output::BinaryOutput {
class TemplateBinaryOutput final : public output::BinaryOutput {
public:
Trigger<bool> *get_trigger() const { return trigger_; }
@@ -17,7 +17,7 @@ class TemplateBinaryOutput : public output::BinaryOutput {
Trigger<bool> *trigger_ = new Trigger<bool>();
};
class TemplateFloatOutput : public output::FloatOutput {
class TemplateFloatOutput final : public output::FloatOutput {
public:
Trigger<float> *get_trigger() const { return trigger_; }

View File

@@ -9,7 +9,7 @@
namespace esphome {
namespace template_ {
class TemplateSelect : public select::Select, public PollingComponent {
class TemplateSelect final : public select::Select, public PollingComponent {
public:
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }

View File

@@ -7,7 +7,7 @@
namespace esphome {
namespace template_ {
class TemplateSensor : public sensor::Sensor, public PollingComponent {
class TemplateSensor final : public sensor::Sensor, public PollingComponent {
public:
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }

View File

@@ -8,7 +8,7 @@
namespace esphome {
namespace template_ {
class TemplateSwitch : public switch_::Switch, public Component {
class TemplateSwitch final : public switch_::Switch, public Component {
public:
TemplateSwitch();

View File

@@ -60,7 +60,7 @@ template<uint8_t SZ> class TextSaver : public TemplateTextSaverBase {
}
};
class TemplateText : public text::Text, public PollingComponent {
class TemplateText final : public text::Text, public PollingComponent {
public:
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }

View File

@@ -8,7 +8,7 @@
namespace esphome {
namespace template_ {
class TemplateTextSensor : public text_sensor::TextSensor, public PollingComponent {
class TemplateTextSensor final : public text_sensor::TextSensor, public PollingComponent {
public:
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }

View File

@@ -14,7 +14,7 @@ enum TemplateValveRestoreMode {
VALVE_RESTORE_AND_CALL,
};
class TemplateValve : public valve::Valve, public Component {
class TemplateValve final : public valve::Valve, public Component {
public:
TemplateValve();

View File

@@ -24,9 +24,9 @@ void log_text_sensor(const char *tag, const char *prefix, const char *type, Text
void TextSensor::publish_state(const std::string &state) {
this->raw_state = state;
if (this->raw_callback_) {
this->raw_callback_->call(state);
}
// Call raw callbacks (before filters)
this->callbacks_.call_first(this->raw_count_, state);
ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), state.c_str());
@@ -68,13 +68,11 @@ void TextSensor::clear_filters() {
}
void TextSensor::add_on_state_callback(std::function<void(std::string)> callback) {
this->callback_.add(std::move(callback));
this->callbacks_.add_second(std::move(callback));
}
void TextSensor::add_on_raw_state_callback(std::function<void(std::string)> callback) {
if (!this->raw_callback_) {
this->raw_callback_ = make_unique<CallbackManager<void(std::string)>>();
}
this->raw_callback_->add(std::move(callback));
this->callbacks_.add_first(std::move(callback), &this->raw_count_);
}
std::string TextSensor::get_state() const { return this->state; }
@@ -83,7 +81,13 @@ void TextSensor::internal_send_state_to_frontend(const std::string &state) {
this->state = state;
this->set_has_state(true);
ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str());
this->callback_.call(state);
// Call filtered callbacks (after filters)
this->callbacks_.call_second(this->raw_count_, state);
#if defined(USE_TEXT_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_text_sensor_update(this);
#endif
}
} // namespace text_sensor

View File

@@ -58,11 +58,11 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass {
void internal_send_state_to_frontend(const std::string &state);
protected:
std::unique_ptr<CallbackManager<void(std::string)>>
raw_callback_; ///< Storage for raw state callbacks (lazy allocated).
CallbackManager<void(std::string)> callback_; ///< Storage for filtered state callbacks.
PartitionedCallbackManager<void(std::string)> callbacks_;
Filter *filter_list_{nullptr}; ///< Store all active filters.
uint8_t raw_count_{0}; ///< Number of raw callbacks (partition point in callbacks_ vector)
};
} // namespace text_sensor

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