Compare commits

..

11 Commits

Author SHA1 Message Date
J. Nick Koston
25147920a5 Merge branch 'memory_api' into memory_api_action_chaining_const_ref 2025-11-04 08:34:51 -06:00
J. Nick Koston
67c57aa5ed Merge branch 'memory_api' into memory_api_action_chaining_const_ref 2025-11-03 22:43:37 -06:00
J. Nick Koston
ba2e8759e5 Merge branch 'action_chaining_const_ref' into memory_api_action_chaining_const_ref 2025-11-03 21:04:56 -06:00
J. Nick Koston
9a9fecfedf missed some 2025-11-03 19:29:35 -06:00
J. Nick Koston
0fc7912171 missed some 2025-11-03 19:28:07 -06:00
J. Nick Koston
3a191e571b fix new code 2025-11-03 18:37:03 -06:00
J. Nick Koston
025e9d4ae2 remove some now useless moves 2025-11-03 18:08:36 -06:00
J. Nick Koston
4ecab07562 [core] Reduce action framework argument copies by 83% 2025-11-03 17:49:11 -06:00
J. Nick Koston
38fe47b554 Merge branch 'dev' into action_chaining 2025-11-03 17:45:59 -06:00
J. Nick Koston
738cf4cace Merge branch 'dev' into action_chaining 2025-11-02 19:09:44 -06:00
Jesse Hills
0a01fde503 Merge branch 'dev' into action_chaining 2025-11-03 14:07:15 +13:00
144 changed files with 413 additions and 1544 deletions

View File

@@ -51,79 +51,7 @@ 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 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
* **C++:** Follows the Google C++ Style Guide.
* **Component Structure:**
* **Standard Files:**

View File

@@ -181,7 +181,7 @@ esphome/components/gdk101/* @Szewcson
esphome/components/gl_r01_i2c/* @pkejval
esphome/components/globals/* @esphome/core
esphome/components/gp2y1010au0f/* @zry98
esphome/components/gp8403/* @jesserockz @sebydocky
esphome/components/gp8403/* @jesserockz
esphome/components/gpio/* @esphome/core
esphome/components/gpio/one_wire/* @ssieb
esphome/components/gps/* @coogle @ximex

View File

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

View File

@@ -23,57 +23,11 @@ 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(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 {
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]))...);
}
// 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)
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_.c_str());
this->key_ = fnv1_hash(this->name_);
}
ListEntitiesServicesResponse encode_list_service_response() override {
@@ -93,7 +47,7 @@ template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor
bool execute_service(const ExecuteServiceRequest &req) override {
if (req.key != this->key_)
return false;
if (req.args.size() != sizeof...(Ts))
if (req.args.size() != this->arg_names_.size())
return false;
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
return true;
@@ -105,16 +59,14 @@ template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor
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};
std::array<std::string, sizeof...(Ts)> arg_names_;
};
template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...>, public Trigger<Ts...> {
public:
// Constructor for static names (YAML-defined services - used by code generator)
UserServiceTrigger(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
UserServiceTrigger(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
: UserServiceBase<Ts...>(name, arg_names) {}
protected:

View File

@@ -8,7 +8,6 @@ BYTE_ORDER_BIG = "big_endian"
CONF_COLOR_DEPTH = "color_depth"
CONF_DRAW_ROUNDING = "draw_rounding"
CONF_ENABLED = "enabled"
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(size_t index) override { this->publish_state(index); }
void control(const std::string &value) override { this->publish_state(value); }
};
} // namespace demo

View File

@@ -176,117 +176,7 @@ class Display;
class DisplayPage;
class DisplayOnPageChangeTrigger;
/** Optimized display writer that uses function pointers for stateless lambdas.
*
* Similar to TemplatableValue but specialized for display writer callbacks.
* Saves ~8 bytes per stateless lambda on 32-bit platforms (16 bytes std::function → ~8 bytes discriminator+pointer).
*
* Supports both:
* - Stateless lambdas (from YAML) → function pointer (4 bytes)
* - Stateful lambdas/std::function (from C++ code) → std::function* (heap allocated)
*
* @tparam T The display type (e.g., Display, Nextion, GPIOLCDDisplay)
*/
template<typename T> class DisplayWriter {
public:
DisplayWriter() : type_(NONE) {}
// For stateless lambdas (convertible to function pointer): use function pointer (4 bytes)
template<typename F>
DisplayWriter(F f) requires std::invocable<F, T &> && std::convertible_to<F, void (*)(T &)>
: type_(STATELESS_LAMBDA) {
this->stateless_f_ = f; // Implicit conversion to function pointer
}
// For stateful lambdas and std::function (not convertible to function pointer): use std::function* (heap allocated)
// This handles backwards compatibility with external components
template<typename F>
DisplayWriter(F f) requires std::invocable<F, T &> &&(!std::convertible_to<F, void (*)(T &)>) : type_(LAMBDA) {
this->f_ = new std::function<void(T &)>(std::move(f));
}
// Copy constructor
DisplayWriter(const DisplayWriter &other) : type_(other.type_) {
if (type_ == LAMBDA) {
this->f_ = new std::function<void(T &)>(*other.f_);
} else if (type_ == STATELESS_LAMBDA) {
this->stateless_f_ = other.stateless_f_;
}
}
// Move constructor
DisplayWriter(DisplayWriter &&other) noexcept : type_(other.type_) {
if (type_ == LAMBDA) {
this->f_ = other.f_;
other.f_ = nullptr;
} else if (type_ == STATELESS_LAMBDA) {
this->stateless_f_ = other.stateless_f_;
}
other.type_ = NONE;
}
// Assignment operators
DisplayWriter &operator=(const DisplayWriter &other) {
if (this != &other) {
this->~DisplayWriter();
new (this) DisplayWriter(other);
}
return *this;
}
DisplayWriter &operator=(DisplayWriter &&other) noexcept {
if (this != &other) {
this->~DisplayWriter();
new (this) DisplayWriter(std::move(other));
}
return *this;
}
~DisplayWriter() {
if (type_ == LAMBDA) {
delete this->f_;
}
// STATELESS_LAMBDA/NONE: no cleanup needed (function pointer or empty)
}
bool has_value() const { return this->type_ != NONE; }
void call(T &display) const {
switch (this->type_) {
case STATELESS_LAMBDA:
this->stateless_f_(display); // Direct function pointer call
break;
case LAMBDA:
(*this->f_)(display); // std::function call
break;
case NONE:
default:
break;
}
}
// Operator() for convenience
void operator()(T &display) const { this->call(display); }
// Operator* for backwards compatibility with (*writer_)(*this) pattern
DisplayWriter &operator*() { return *this; }
const DisplayWriter &operator*() const { return *this; }
protected:
enum : uint8_t {
NONE,
LAMBDA,
STATELESS_LAMBDA,
} type_;
union {
std::function<void(T &)> *f_;
void (*stateless_f_)(T &);
};
};
// Type alias for Display writer - uses optimized DisplayWriter instead of std::function
using display_writer_t = DisplayWriter<Display>;
using display_writer_t = std::function<void(Display &)>;
#define LOG_DISPLAY(prefix, type, obj) \
if ((obj) != nullptr) { \
@@ -788,7 +678,7 @@ class Display : public PollingComponent {
void sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3);
DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES};
display_writer_t writer_{};
optional<display_writer_t> writer_{};
DisplayPage *page_{nullptr};
DisplayPage *previous_page_{nullptr};
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;

View File

@@ -3,9 +3,9 @@
namespace esphome {
namespace es8388 {
void ADCInputMicSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_adc_input_mic(static_cast<AdcInputMicLine>(index));
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()));
}
} // namespace es8388

View File

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

View File

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

View File

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

View File

@@ -231,7 +231,7 @@ void Fan::save_state_() {
state.direction = this->direction;
const char *preset = this->get_preset_mode();
if (preset != nullptr) {
if (traits.supports_preset_modes() && preset != nullptr) {
const auto &preset_modes = traits.supported_preset_modes();
// Find index of current preset mode (pointer comparison is safe since preset is from traits)
for (size_t i = 0; i < preset_modes.size(); i++) {

View File

@@ -62,6 +62,7 @@ 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_);
@@ -71,10 +72,6 @@ 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; }
@@ -156,18 +153,18 @@ bool GDK101Component::read_status_(uint8_t *data) {
}
bool GDK101Component::read_fw_version_(uint8_t *data) {
#ifdef USE_TEXT_SENSOR
if (this->fw_version_text_sensor_ != nullptr) {
#ifdef USE_SENSOR
if (this->fw_version_sensor_ != nullptr) {
if (!this->read_bytes(GDK101_REG_READ_FIRMWARE, data, 2)) {
ESP_LOGE(TAG, "Updating GDK101 failed!");
return false;
}
const std::string fw_version_str = str_sprintf("%d.%d", data[0], data[1]);
const float fw_version = data[0] + (data[1] / 10.0f);
this->fw_version_text_sensor_->publish_state(fw_version_str);
this->fw_version_sensor_->publish_state(fw_version);
}
#endif // USE_TEXT_SENSOR
#endif // USE_SENSOR
return true;
}

View File

@@ -8,9 +8,6 @@
#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 {
@@ -28,14 +25,12 @@ 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,8 +40,9 @@ CONFIG_SCHEMA = cv.Schema(
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_VERSION): cv.invalid(
"The 'version' option has been moved to the `text_sensor` component."
cv.Optional(CONF_VERSION): sensor.sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
accuracy_decimals=1,
),
cv.Optional(CONF_STATUS): sensor.sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
@@ -70,6 +71,10 @@ 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

@@ -1,23 +0,0 @@
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

@@ -1,25 +1,19 @@
import esphome.codegen as cg
from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MODEL, CONF_VOLTAGE
from esphome.const import CONF_ID, CONF_VOLTAGE
CODEOWNERS = ["@jesserockz", "@sebydocky"]
CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["i2c"]
MULTI_CONF = True
gp8403_ns = cg.esphome_ns.namespace("gp8403")
GP8403Component = gp8403_ns.class_("GP8403Component", cg.Component, i2c.I2CDevice)
GP8403 = gp8403_ns.class_("GP8403", cg.Component, i2c.I2CDevice)
GP8403Voltage = gp8403_ns.enum("GP8403Voltage")
GP8403Model = gp8403_ns.enum("GP8403Model")
CONF_GP8403_ID = "gp8403_id"
MODELS = {
"GP8403": GP8403Model.GP8403,
"GP8413": GP8403Model.GP8413,
}
VOLTAGES = {
"5V": GP8403Voltage.GP8403_VOLTAGE_5V,
"10V": GP8403Voltage.GP8403_VOLTAGE_10V,
@@ -28,8 +22,7 @@ VOLTAGES = {
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(GP8403Component),
cv.Optional(CONF_MODEL, default="GP8403"): cv.enum(MODELS, upper=True),
cv.GenerateID(): cv.declare_id(GP8403),
cv.Required(CONF_VOLTAGE): cv.enum(VOLTAGES, upper=True),
}
)
@@ -42,5 +35,5 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_model(config[CONF_MODEL]))
cg.add(var.set_voltage(config[CONF_VOLTAGE]))

View File

@@ -8,48 +8,16 @@ namespace gp8403 {
static const char *const TAG = "gp8403";
static const uint8_t RANGE_REGISTER = 0x01;
static const uint8_t OUTPUT_REGISTER = 0x02;
const LogString *model_to_string(GP8403Model model) {
switch (model) {
case GP8403Model::GP8403:
return LOG_STR("GP8403");
case GP8403Model::GP8413:
return LOG_STR("GP8413");
}
return LOG_STR("Unknown");
};
void GP8403::setup() { this->write_register(RANGE_REGISTER, (uint8_t *) (&this->voltage_), 1); }
void GP8403Component::setup() { this->write_register(RANGE_REGISTER, (uint8_t *) (&this->voltage_), 1); }
void GP8403Component::dump_config() {
void GP8403::dump_config() {
ESP_LOGCONFIG(TAG,
"GP8403:\n"
" Voltage: %dV\n"
" Model: %s",
this->voltage_ == GP8403_VOLTAGE_5V ? 5 : 10, LOG_STR_ARG(model_to_string(this->model_)));
" Voltage: %dV",
this->voltage_ == GP8403_VOLTAGE_5V ? 5 : 10);
LOG_I2C_DEVICE(this);
}
void GP8403Component::write_state(float state, uint8_t channel) {
uint16_t val = 0;
switch (this->model_) {
case GP8403Model::GP8403:
val = ((uint16_t) (4095 * state)) << 4;
break;
case GP8403Model::GP8413:
val = ((uint16_t) (32767 * state)) << 1;
break;
default:
ESP_LOGE(TAG, "Unknown model %s", LOG_STR_ARG(model_to_string(this->model_)));
return;
}
ESP_LOGV(TAG, "Calculated DAC value: %" PRIu16, val);
i2c::ErrorCode err = this->write_register(OUTPUT_REGISTER + (2 * channel), (uint8_t *) &val, 2);
if (err != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Error writing to %s, code %d", LOG_STR_ARG(model_to_string(this->model_)), err);
}
}
} // namespace gp8403
} // namespace esphome

View File

@@ -11,24 +11,15 @@ enum GP8403Voltage {
GP8403_VOLTAGE_10V = 0x11,
};
enum GP8403Model {
GP8403,
GP8413,
};
class GP8403Component : public Component, public i2c::I2CDevice {
class GP8403 : public Component, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_model(GP8403Model model) { this->model_ = model; }
void set_voltage(gp8403::GP8403Voltage voltage) { this->voltage_ = voltage; }
void write_state(float state, uint8_t channel);
void set_voltage(gp8403::GP8403Voltage voltage) { this->voltage_ = voltage; }
protected:
GP8403Voltage voltage_;
GP8403Model model_{GP8403Model::GP8403};
};
} // namespace gp8403

View File

@@ -3,7 +3,7 @@ from esphome.components import i2c, output
import esphome.config_validation as cv
from esphome.const import CONF_CHANNEL, CONF_ID
from .. import CONF_GP8403_ID, GP8403Component, gp8403_ns
from .. import CONF_GP8403_ID, GP8403, gp8403_ns
DEPENDENCIES = ["gp8403"]
@@ -14,7 +14,7 @@ GP8403Output = gp8403_ns.class_(
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(GP8403Output),
cv.GenerateID(CONF_GP8403_ID): cv.use_id(GP8403Component),
cv.GenerateID(CONF_GP8403_ID): cv.use_id(GP8403),
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=1),
}
).extend(cv.COMPONENT_SCHEMA)

View File

@@ -7,6 +7,8 @@ namespace gp8403 {
static const char *const TAG = "gp8403.output";
static const uint8_t OUTPUT_REGISTER = 0x02;
void GP8403Output::dump_config() {
ESP_LOGCONFIG(TAG,
"GP8403 Output:\n"
@@ -14,7 +16,13 @@ void GP8403Output::dump_config() {
this->channel_);
}
void GP8403Output::write_state(float state) { this->parent_->write_state(state, this->channel_); }
void GP8403Output::write_state(float state) {
uint16_t value = ((uint16_t) (state * 4095)) << 4;
i2c::ErrorCode err = this->parent_->write_register(OUTPUT_REGISTER + (2 * this->channel_), (uint8_t *) &value, 2);
if (err != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Error writing to GP8403, code %d", err);
}
}
} // namespace gp8403
} // namespace esphome

View File

@@ -8,11 +8,13 @@
namespace esphome {
namespace gp8403 {
class GP8403Output : public Component, public output::FloatOutput, public Parented<GP8403Component> {
class GP8403Output : public Component, public output::FloatOutput, public Parented<GP8403> {
public:
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA - 1; }
void set_channel(uint8_t channel) { this->channel_ = channel; }
void write_state(float state) override;
protected:

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_ref();
valstr += trace->sensor_->get_unit_of_measurement();
}
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_ref();
valstr += trace->sensor_->get_unit_of_measurement();
}
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

@@ -34,8 +34,8 @@ void GT911Touchscreen::setup() {
this->interrupt_pin_->digital_write(false);
}
delay(2);
this->reset_pin_->digital_write(true); // wait at least T3+T4 ms as per the datasheet
this->set_timeout(5 + 50 + 1, [this] { this->setup_internal_(); });
this->reset_pin_->digital_write(true); // wait 50ms after reset
this->set_timeout(50, [this] { this->setup_internal_(); });
return;
}
this->setup_internal_();

View File

@@ -2,18 +2,13 @@
#include "esphome/core/hal.h"
#include "esphome/components/lcd_base/lcd_display.h"
#include "esphome/components/display/display.h"
namespace esphome {
namespace lcd_gpio {
class GPIOLCDDisplay;
using gpio_lcd_writer_t = display::DisplayWriter<GPIOLCDDisplay>;
class GPIOLCDDisplay : public lcd_base::LCDDisplay {
public:
void set_writer(gpio_lcd_writer_t &&writer) { this->writer_ = std::move(writer); }
void set_writer(std::function<void(GPIOLCDDisplay &)> &&writer) { this->writer_ = std::move(writer); }
void setup() override;
void set_data_pins(GPIOPin *d0, GPIOPin *d1, GPIOPin *d2, GPIOPin *d3) {
this->data_pins_[0] = d0;
@@ -48,7 +43,7 @@ class GPIOLCDDisplay : public lcd_base::LCDDisplay {
GPIOPin *rw_pin_{nullptr};
GPIOPin *enable_pin_{nullptr};
GPIOPin *data_pins_[8]{nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
gpio_lcd_writer_t writer_;
std::function<void(GPIOLCDDisplay &)> writer_;
};
} // namespace lcd_gpio

View File

@@ -3,18 +3,13 @@
#include "esphome/core/component.h"
#include "esphome/components/lcd_base/lcd_display.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/display/display.h"
namespace esphome {
namespace lcd_pcf8574 {
class PCF8574LCDDisplay;
using pcf8574_lcd_writer_t = display::DisplayWriter<PCF8574LCDDisplay>;
class PCF8574LCDDisplay : public lcd_base::LCDDisplay, public i2c::I2CDevice {
public:
void set_writer(pcf8574_lcd_writer_t &&writer) { this->writer_ = std::move(writer); }
void set_writer(std::function<void(PCF8574LCDDisplay &)> &&writer) { this->writer_ = std::move(writer); }
void setup() override;
void dump_config() override;
void backlight();
@@ -29,7 +24,7 @@ class PCF8574LCDDisplay : public lcd_base::LCDDisplay, public i2c::I2CDevice {
// Stores the current state of the backlight.
uint8_t backlight_value_;
pcf8574_lcd_writer_t writer_;
std::function<void(PCF8574LCDDisplay &)> writer_;
};
} // namespace lcd_pcf8574

View File

@@ -174,7 +174,7 @@ static uint8_t calc_checksum(void *data, size_t size) {
static int get_firmware_int(const char *version_string) {
std::string version_str = version_string;
if (version_str[0] == 'v') {
version_str.erase(0, 1);
version_str = version_str.substr(1);
}
version_str.erase(remove(version_str.begin(), version_str.end(), '.'), version_str.end());
int version_integer = stoi(version_str);

View File

@@ -3,8 +3,6 @@ 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",
@@ -23,48 +21,23 @@ lv_fonts_used = set()
esphome_fonts_used = set()
lvgl_components_required = set()
# noqa
f_regex = re.compile(
r"""
def validate_printf(value):
cfmt = r"""
( # start of capture group 1
% # literal "%"
[-+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
(?:[-+0 #]{0,5}) # optional flags
(?:\d+|\*)? # width
(?:\.(?:\d+|\*))? # precision
(?:h|l|ll|w|I|I32|I64)? # size
[cCdiouxXeEfgGaAnpsSZ] # type
)
""",
flags=re.VERBOSE,
)
def validate_printf(value):
format_string = value[CONF_FORMAT]
matches = c_regex.findall(format_string)
""" # noqa
matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.VERBOSE)
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

@@ -1,5 +1,4 @@
import re
import textwrap
import esphome.config_validation as cv
from esphome.const import CONF_HEIGHT, CONF_TYPE, CONF_WIDTH
@@ -123,7 +122,7 @@ class FlexLayout(Layout):
def get_layout_schemas(self, config: dict) -> tuple:
layout = config.get(CONF_LAYOUT)
if not isinstance(layout, dict) or layout.get(CONF_TYPE).lower() != TYPE_FLEX:
if not isinstance(layout, dict) or layout.get(CONF_TYPE) != TYPE_FLEX:
return None, {}
child_schema = FLEX_OBJ_SCHEMA
if grow := layout.get(CONF_FLEX_GROW):
@@ -162,8 +161,6 @@ class DirectionalLayout(FlexLayout):
return self.direction
def get_layout_schemas(self, config: dict) -> tuple:
if not isinstance(config.get(CONF_LAYOUT), str):
return None, {}
if config.get(CONF_LAYOUT, "").lower() != self.direction:
return None, {}
return cv.one_of(self.direction, lower=True), flex_hv_schema(self.direction)
@@ -209,7 +206,7 @@ class GridLayout(Layout):
# Not a valid grid layout string
return None, {}
if not isinstance(layout, dict) or layout.get(CONF_TYPE).lower() != TYPE_GRID:
if not isinstance(layout, dict) or layout.get(CONF_TYPE) != TYPE_GRID:
return None, {}
return (
{
@@ -262,7 +259,7 @@ class GridLayout(Layout):
)
# should be guaranteed to be a dict at this point
assert isinstance(layout, dict)
assert layout.get(CONF_TYPE).lower() == TYPE_GRID
assert layout.get(CONF_TYPE) == TYPE_GRID
rows = len(layout[CONF_GRID_ROWS])
columns = len(layout[CONF_GRID_COLUMNS])
used_cells = [[None] * columns for _ in range(rows)]
@@ -338,17 +335,6 @@ def append_layout_schema(schema, config: dict):
if CONF_LAYOUT not in config:
# If no layout is specified, return the schema as is
return schema.extend({cv.Optional(CONF_WIDGETS): any_widget_schema()})
layout = config[CONF_LAYOUT]
# Sanity check the layout to avoid redundant checks in each type
if not isinstance(layout, str) and not isinstance(layout, dict):
raise cv.Invalid(
"The 'layout' option must be a string or a dictionary", [CONF_LAYOUT]
)
if isinstance(layout, dict) and not isinstance(layout.get(CONF_TYPE), str):
raise cv.Invalid(
"Invalid layout type; must be a string ('flex' or 'grid')",
[CONF_LAYOUT, CONF_TYPE],
)
for layout_class in LAYOUT_CLASSES:
layout_schema, child_schema = layout_class.get_layout_schemas(config)
@@ -362,17 +348,10 @@ def append_layout_schema(schema, config: dict):
layout_schema.add_extra(layout_class.validate)
return layout_schema.extend(schema)
if isinstance(layout, dict):
raise cv.Invalid(
"Invalid layout type; must be 'flex' or 'grid'", [CONF_LAYOUT, CONF_TYPE]
)
raise cv.Invalid(
textwrap.dedent(
"""
Invalid 'layout' value
layout choices are 'horizontal', 'vertical', '<rows>x<cols>',
or a dictionary with a 'type' key
"""
),
[CONF_LAYOUT],
# If no layout class matched, return a default schema
return cv.Schema(
{
cv.Optional(CONF_LAYOUT): cv.one_of(*LAYOUT_CHOICES, lower=True),
cv.Optional(CONF_WIDGETS): any_widget_schema(),
}
)

View File

@@ -33,13 +33,7 @@ from .defines import (
call_lambda,
literal,
)
from .helpers import (
CONF_IF_NAN,
add_lv_use,
esphome_fonts_used,
lv_fonts_used,
requires_component,
)
from .helpers import 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")
@@ -418,13 +412,7 @@ 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)
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)
return literal(f"str_sprintf({format_str}, {arg_expr}).c_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 CONF_IF_NAN, requires_component, validate_printf
from .helpers import requires_component, validate_printf
from .layout import (
FLEX_OBJ_SCHEMA,
GRID_CELL_SCHEMA,
@@ -54,7 +54,6 @@ 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() {
auto index = this->widget_->get_selected_index();
this->publish_state(index);
this->publish_state(this->widget_->get_selected_text());
if (this->restore_) {
auto index = this->widget_->get_selected_index();
this->pref_.save(&index);
}
}
protected:
void control(size_t index) override {
this->widget_->set_selected_index(index, this->anim_);
void control(const std::string &value) override {
this->widget_->set_selected_text(value, this->anim_);
this->publish();
}
void set_options_() {
@@ -59,8 +59,8 @@ class LVGLSelect : public select::Select, public Component {
const auto &opts = this->widget_->get_options();
FixedVector<const char *> opt_ptrs;
opt_ptrs.init(opts.size());
for (const auto &opt : opts) {
opt_ptrs.push_back(opt.c_str());
for (size_t i = 0; i < opts.size(); i++) {
opt_ptrs[i] = opts[i].c_str();
}
this->traits.set_options(opt_ptrs);
}

View File

@@ -4,14 +4,13 @@
#include "esphome/core/time.h"
#include "esphome/components/spi/spi.h"
#include "esphome/components/display/display.h"
namespace esphome {
namespace max7219 {
class MAX7219Component;
using max7219_writer_t = display::DisplayWriter<MAX7219Component>;
using max7219_writer_t = std::function<void(MAX7219Component &)>;
class MAX7219Component : public PollingComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
@@ -58,7 +57,7 @@ class MAX7219Component : public PollingComponent,
uint8_t num_chips_{1};
uint8_t *buffer_;
bool reverse_{false};
max7219_writer_t writer_{};
optional<max7219_writer_t> writer_{};
};
} // namespace max7219

View File

@@ -271,11 +271,7 @@ void MAX7219Component::send64pixels(uint8_t chip, const uint8_t pixels[8]) {
}
}
} else if (this->orientation_ == 1) {
if (this->flip_x_) {
b = pixels[7 - col];
} else {
b = pixels[col];
}
b = pixels[col];
} else if (this->orientation_ == 2) {
for (uint8_t i = 0; i < 8; i++) {
if (this->flip_x_) {
@@ -286,11 +282,7 @@ void MAX7219Component::send64pixels(uint8_t chip, const uint8_t pixels[8]) {
}
} else {
for (uint8_t i = 0; i < 8; i++) {
if (this->flip_x_) {
b |= ((pixels[col] >> i) & 1) << (7 - i);
} else {
b |= ((pixels[7 - col] >> i) & 1) << (7 - i);
}
b |= ((pixels[7 - col] >> i) & 1) << (7 - i);
}
}
// send this byte to display at selected chip

View File

@@ -23,7 +23,7 @@ enum ScrollMode {
class MAX7219Component;
using max7219_writer_t = display::DisplayWriter<MAX7219Component>;
using max7219_writer_t = std::function<void(MAX7219Component &)>;
class MAX7219Component : public display::DisplayBuffer,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
@@ -117,7 +117,7 @@ class MAX7219Component : public display::DisplayBuffer,
uint32_t last_scroll_ = 0;
uint16_t stepsleft_;
size_t get_buffer_length_();
max7219_writer_t writer_local_{};
optional<max7219_writer_t> writer_local_{};
};
} // namespace max7219digit

View File

@@ -37,6 +37,8 @@ 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.
@@ -177,7 +179,7 @@ void MDNSComponent::dump_config() {
ESP_LOGCONFIG(TAG,
"mDNS:\n"
" Hostname: %s",
App.get_name().c_str());
this->hostname_.c_str());
#ifdef USE_MDNS_STORE_SERVICES
ESP_LOGV(TAG, " Services:");
for (const auto &service : this->services_) {

View File

@@ -76,6 +76,7 @@ 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,7 +2,6 @@
#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"
@@ -28,9 +27,8 @@ void MDNSComponent::setup() {
return;
}
const char *hostname = App.get_name().c_str();
mdns_hostname_set(hostname);
mdns_instance_name_set(hostname);
mdns_hostname_set(this->hostname_.c_str());
mdns_instance_name_set(this->hostname_.c_str());
for (const auto &service : services) {
auto txt_records = std::make_unique<mdns_txt_item_t[]>(service.txt_records.size());

View File

@@ -4,7 +4,6 @@
#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"
@@ -21,7 +20,7 @@ void MDNSComponent::setup() {
this->compile_records_(services);
#endif
MDNS.begin(App.get_name().c_str());
MDNS.begin(this->hostname_.c_str());
for (const auto &service : services) {
// Strip the leading underscore from the proto and service_type. While it is

View File

@@ -3,7 +3,6 @@
#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"
@@ -21,7 +20,7 @@ void MDNSComponent::setup() {
this->compile_records_(services);
#endif
MDNS.begin(App.get_name().c_str());
MDNS.begin(this->hostname_.c_str());
for (const auto &service : services) {
// Strip the leading underscore from the proto and service_type. While it is

View File

@@ -3,7 +3,6 @@
#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"
@@ -21,7 +20,7 @@ void MDNSComponent::setup() {
this->compile_records_(services);
#endif
MDNS.begin(App.get_name().c_str());
MDNS.begin(this->hostname_.c_str());
for (const auto &service : services) {
// Strip the leading underscore from the proto and service_type. While it is

View File

@@ -3,7 +3,6 @@ import binascii
from esphome import automation
import esphome.codegen as cg
from esphome.components import modbus
from esphome.components.const import CONF_ENABLED
import esphome.config_validation as cv
from esphome.const import (
CONF_ADDRESS,
@@ -21,6 +20,7 @@ from .const import (
CONF_BYTE_OFFSET,
CONF_COMMAND_THROTTLE,
CONF_CUSTOM_COMMAND,
CONF_ENABLED,
CONF_FORCE_NEW_RANGE,
CONF_MAX_CMD_RETRIES,
CONF_MODBUS_CONTROLLER_ID,

View File

@@ -2,6 +2,7 @@ CONF_ALLOW_DUPLICATE_COMMANDS = "allow_duplicate_commands"
CONF_BITMASK = "bitmask"
CONF_BYTE_OFFSET = "byte_offset"
CONF_COMMAND_THROTTLE = "command_throttle"
CONF_ENABLED = "enabled"
CONF_OFFLINE_SKIP_UPDATES = "offline_skip_updates"
CONF_CUSTOM_COMMAND = "custom_command"
CONF_FORCE_NEW_RANGE = "force_new_range"

View File

@@ -28,9 +28,8 @@ 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);
ESP_LOGV(TAG, "Found option %s for value %lld", this->option_at(idx), value);
this->publish_state(idx);
return;
new_state = std::string(this->option_at(idx));
ESP_LOGV(TAG, "Found option %s for value %lld", new_state->c_str(), value);
} else {
ESP_LOGE(TAG, "No option found for mapping %lld", value);
}
@@ -41,16 +40,19 @@ void ModbusSelect::parse_and_publish(const std::vector<uint8_t> &data) {
}
}
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);
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());
std::vector<uint16_t> data;
if (this->write_transform_func_.has_value()) {
// Transform func requires string parameter for backward compatibility
auto val = (*this->write_transform_func_)(this, std::string(option), *mapval, data);
auto val = (*this->write_transform_func_)(this, value, *mapval, data);
if (val.has_value()) {
mapval = *val;
ESP_LOGV(TAG, "write_lambda returned mapping value %lld", *mapval);
@@ -83,7 +85,7 @@ void ModbusSelect::control(size_t index) {
this->parent_->queue_command(write_cmd);
if (this->optimistic_)
this->publish_state(index);
this->publish_state(value);
}
} // 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(size_t index) override;
void control(const std::string &value) 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,12 +30,9 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor
}
void MQTTBinarySensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// 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)
// 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();
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,9 +33,8 @@ 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;
const auto device_class = this->button_->get_device_class_ref();
if (!device_class.empty()) {
root[MQTT_DEVICE_CLASS] = device_class;
if (!this->button_->get_device_class().empty()) {
root[MQTT_DEVICE_CLASS] = this->button_->get_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,16 +85,12 @@ 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;
// 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)
if (!this->get_icon().empty())
root[MQTT_ICON] = this->get_icon();
const auto entity_category = this->get_entity()->get_entity_category();
switch (entity_category) {
@@ -126,7 +122,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 {
@@ -188,7 +184,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) {
@@ -272,9 +268,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(); }
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(); }
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(); }
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.
std::string friendly_name_() const;
virtual std::string friendly_name() const;
/// Get the icon field of this component as StringRef
StringRef get_icon_ref_() const;
/// Get the icon field of this component
virtual std::string get_icon() const;
/// Get whether the underlying Entity is disabled by default
bool is_disabled_by_default_() const;
virtual 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,12 +67,9 @@ void MQTTCoverComponent::dump_config() {
}
}
void MQTTCoverComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// 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)
// 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();
auto traits = this->cover_->get_traits();
if (traits.get_is_assumed_state()) {

View File

@@ -21,12 +21,8 @@ void MQTTEventComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConf
for (const auto &event_type : this->event_->get_event_types())
event_types.add(event_type);
// 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)
if (!this->event_->get_device_class().empty())
root[MQTT_DEVICE_CLASS] = this->event_->get_device_class();
config.command_topic = false;
}

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,11 +44,8 @@ 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();
// 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;
}
if (!this->number_->traits.get_unit_of_measurement().empty())
root[MQTT_UNIT_OF_MEASUREMENT] = this->number_->traits.get_unit_of_measurement();
switch (this->number_->traits.get_mode()) {
case NUMBER_MODE_AUTO:
break;
@@ -59,11 +56,8 @@ void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon
root[MQTT_MODE] = "slider";
break;
}
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)
if (!this->number_->traits.get_device_class().empty())
root[MQTT_DEVICE_CLASS] = this->number_->traits.get_device_class();
config.command_topic = true;
}

View File

@@ -44,17 +44,13 @@ 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) {
// 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;
// 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();
}
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->sensor_->get_unit_of_measurement().empty())
root[MQTT_UNIT_OF_MEASUREMENT] = this->sensor_->get_unit_of_measurement();
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,12 +15,10 @@ using namespace esphome::text_sensor;
MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {}
void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// 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;
// 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();
}
// 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,12 +49,10 @@ void MQTTValveComponent::dump_config() {
}
}
void MQTTValveComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// 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;
// 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();
}
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
auto traits = this->valve_->get_traits();
if (traits.get_is_assumed_state()) {

View File

@@ -9,7 +9,6 @@
#include "esphome/components/uart/uart.h"
#include "nextion_base.h"
#include "nextion_component.h"
#include "esphome/components/display/display.h"
#include "esphome/components/display/display_color_utils.h"
#ifdef USE_NEXTION_TFT_UPLOAD
@@ -32,7 +31,7 @@ namespace nextion {
class Nextion;
class NextionComponentBase;
using nextion_writer_t = display::DisplayWriter<Nextion>;
using nextion_writer_t = std::function<void(Nextion &)>;
static const std::string COMMAND_DELIMITER{static_cast<char>(255), static_cast<char>(255), static_cast<char>(255)};
@@ -1472,7 +1471,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
CallbackManager<void(uint8_t, uint8_t, bool)> touch_callback_{};
CallbackManager<void()> buffer_overflow_callback_{};
nextion_writer_t writer_;
optional<nextion_writer_t> writer_;
optional<float> brightness_;
#ifdef USE_NEXTION_CONFIG_DUMP_DEVICE_INFO

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, TimePeriodMilliseconds
from esphome.core import CORE
import esphome.final_validate as fv
from esphome.types import ConfigType
@@ -22,7 +22,6 @@ from .const import (
CONF_NETWORK_KEY,
CONF_NETWORK_NAME,
CONF_PAN_ID,
CONF_POLL_PERIOD,
CONF_PSKC,
CONF_SRP_ID,
CONF_TLV,
@@ -90,7 +89,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 synchronized sleepy end devices (SSED)
# TODO: Add suport for sleepy end devices
add_idf_sdkconfig_option(f"CONFIG_OPENTHREAD_{config.get(CONF_DEVICE_TYPE)}", True)
@@ -114,17 +113,6 @@ _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
@@ -147,7 +135,6 @@ 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),
@@ -180,8 +167,6 @@ 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,7 +6,6 @@ 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,23 +29,6 @@ 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,7 +22,6 @@ 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; }
@@ -36,9 +35,6 @@ 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);
@@ -50,9 +46,6 @@ 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,32 +105,6 @@ 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_ref().c_str());
stream->print(obj->get_unit_of_measurement().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

@@ -3,7 +3,6 @@
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/display/display.h"
#include <cinttypes>
@@ -30,7 +29,7 @@ enum UNIT {
UNIT_DEG_E, ///< show "°E"
};
using pvvx_writer_t = display::DisplayWriter<PVVXDisplay>;
using pvvx_writer_t = std::function<void(PVVXDisplay &)>;
class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent {
public:
@@ -127,7 +126,7 @@ class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent {
esp32_ble_tracker::ESPBTUUID char_uuid_ =
esp32_ble_tracker::ESPBTUUID::from_raw("00001f1f-0000-1000-8000-00805f9b34fb");
pvvx_writer_t writer_{};
optional<pvvx_writer_t> writer_{};
};
} // namespace pvvx_mithermometer

View File

@@ -71,7 +71,6 @@ static const uint16_t FALLBACK_FREQUENCY = 64767U; // To use with frequency = 0
static const uint32_t MICROSECONDS_IN_SECONDS = 1000000UL;
static const uint16_t PRONTO_DEFAULT_GAP = 45000;
static const uint16_t MARK_EXCESS_MICROS = 20;
static constexpr size_t PRONTO_LOG_CHUNK_SIZE = 230;
static uint16_t to_frequency_k_hz(uint16_t code) {
if (code == 0)
@@ -226,17 +225,18 @@ optional<ProntoData> ProntoProtocol::decode(RemoteReceiveData src) {
}
void ProntoProtocol::dump(const ProntoData &data) {
std::string rest = data.data;
std::string rest;
rest = data.data;
ESP_LOGI(TAG, "Received Pronto: data=");
do {
size_t chunk_size = rest.size() > PRONTO_LOG_CHUNK_SIZE ? PRONTO_LOG_CHUNK_SIZE : rest.size();
ESP_LOGI(TAG, "%.*s", (int) chunk_size, rest.c_str());
if (rest.size() > PRONTO_LOG_CHUNK_SIZE) {
rest.erase(0, PRONTO_LOG_CHUNK_SIZE);
while (true) {
ESP_LOGI(TAG, "%s", rest.substr(0, 230).c_str());
if (rest.size() > 230) {
rest = rest.substr(230);
} else {
break;
}
} while (true);
}
}
} // namespace remote_base

View File

@@ -35,9 +35,9 @@ void Rtttl::dump_config() {
void Rtttl::play(std::string rtttl) {
if (this->state_ != State::STATE_STOPPED && this->state_ != State::STATE_STOPPING) {
size_t pos = this->rtttl_.find(':');
size_t len = (pos != std::string::npos) ? pos : this->rtttl_.length();
ESP_LOGW(TAG, "Already playing: %.*s", (int) len, this->rtttl_.c_str());
int pos = this->rtttl_.find(':');
auto name = this->rtttl_.substr(0, pos);
ESP_LOGW(TAG, "Already playing: %s", name.c_str());
return;
}
@@ -59,7 +59,8 @@ void Rtttl::play(std::string rtttl) {
return;
}
ESP_LOGD(TAG, "Playing song %.*s", (int) this->position_, this->rtttl_.c_str());
auto name = this->rtttl_.substr(0, this->position_);
ESP_LOGD(TAG, "Playing song %s", name.c_str());
// get default duration
this->position_ = this->rtttl_.find("d=", this->position_);

View File

@@ -3,9 +3,12 @@
namespace esphome {
namespace seeed_mr24hpc1 {
void ExistenceBoundarySelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_existence_boundary(index);
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());
}
}
} // namespace seeed_mr24hpc1

View File

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

View File

@@ -3,9 +3,12 @@
namespace esphome {
namespace seeed_mr24hpc1 {
void MotionBoundarySelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_motion_boundary(index);
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());
}
}
} // namespace seeed_mr24hpc1

View File

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

View File

@@ -3,9 +3,12 @@
namespace esphome {
namespace seeed_mr24hpc1 {
void SceneModeSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_scene_mode(index);
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());
}
}
} // namespace seeed_mr24hpc1

View File

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

View File

@@ -3,9 +3,12 @@
namespace esphome {
namespace seeed_mr24hpc1 {
void UnmanTimeSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_unman_time(index);
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());
}
}
} // namespace seeed_mr24hpc1

View File

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

View File

@@ -3,9 +3,12 @@
namespace esphome {
namespace seeed_mr60fda2 {
void HeightThresholdSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_height_threshold(index);
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());
}
}
} // namespace seeed_mr60fda2

View File

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

View File

@@ -3,9 +3,12 @@
namespace esphome {
namespace seeed_mr60fda2 {
void InstallHeightSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_install_height(index);
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());
}
}
} // namespace seeed_mr60fda2

View File

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

View File

@@ -3,9 +3,12 @@
namespace esphome {
namespace seeed_mr60fda2 {
void SensitivitySelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_sensitivity(index);
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());
}
}
} // namespace seeed_mr60fda2

View File

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

View File

@@ -35,7 +35,7 @@ void Select::publish_state(size_t index) {
this->state_callback_.call(std::string(option), index);
}
const char *Select::current_option() const { return this->has_state() ? this->option_at(this->active_index_) : ""; }
const char *Select::current_option() const { return this->option_at(this->active_index_); }
void Select::add_on_state_callback(std::function<void(std::string, size_t)> &&callback) {
this->state_callback_.add(std::move(callback));

View File

@@ -93,14 +93,19 @@ class Select : public EntityBase {
size_t active_index_{0};
/** Set the value of the select, this is a virtual method that each select integration can implement.
*
* IMPORTANT: At least ONE of the two control() methods must be overridden by derived classes.
* Overriding control(size_t) is PREFERRED as it avoids string conversions.
*
* This method is called by control(size_t) when not overridden, or directly by external code.
* Integrations can either:
* 1. Override this method to handle string-based control (traditional approach)
* 2. Override control(size_t) instead to work with indices directly (recommended)
*
* Default implementation converts to index and calls control(size_t).
*
* @param value The value as validated by the caller.
* Delegation chain:
* - SelectCall::perform() → control(size_t) → [if not overridden] → control(string)
* - External code → control(string) → publish_state(string) → publish_state(size_t)
*
* @param value The value as validated by the SelectCall.
*/
virtual void control(const std::string &value) {
auto index = this->index_of(value);

View File

@@ -7,8 +7,8 @@ void SelectTraits::set_options(const std::initializer_list<const char *> &option
void SelectTraits::set_options(const FixedVector<const char *> &options) {
this->options_.init(options.size());
for (const auto &opt : options) {
this->options_.push_back(opt);
for (size_t i = 0; i < options.size(); i++) {
this->options_[i] = options[i];
}
}

View File

@@ -78,7 +78,7 @@ void SGP30Component::setup() {
uint32_t hash = fnv1_hash(App.get_compilation_time() + std::to_string(this->serial_number_));
this->pref_ = global_preferences->make_preference<SGP30Baselines>(hash, true);
if (this->store_baseline_ && this->pref_.load(&this->baselines_storage_)) {
if (this->pref_.load(&this->baselines_storage_)) {
ESP_LOGI(TAG, "Loaded eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", this->baselines_storage_.eco2,
baselines_storage_.tvoc);
this->eco2_baseline_ = this->baselines_storage_.eco2;

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_ref() == MIN_STR) {
if (this->valve_[valve_number.value()].run_duration_number->traits.get_unit_of_measurement() == 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_ref() == MIN_STR) {
if (this->valve_[valve_number].run_duration_number->traits.get_unit_of_measurement() == 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

@@ -9,7 +9,7 @@ namespace st7920 {
class ST7920;
using st7920_writer_t = display::DisplayWriter<ST7920>;
using st7920_writer_t = std::function<void(ST7920 &)>;
class ST7920 : public display::DisplayBuffer,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
@@ -44,7 +44,7 @@ class ST7920 : public display::DisplayBuffer,
void end_transaction_();
int16_t width_ = 128, height_ = 64;
st7920_writer_t writer_local_{};
optional<st7920_writer_t> writer_local_{};
};
} // namespace st7920

View File

@@ -49,7 +49,7 @@ struct SensorInfo {
uint8_t store_index;
};
class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControlPanel, public Component {
class TemplateAlarmControlPanel : 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 final : public Component, public binary_sensor::BinarySensor {
class TemplateBinarySensor : 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 final : public button::Button {
class TemplateButton : 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 final : public cover::Cover, public Component {
class TemplateCover : public cover::Cover, public Component {
public:
TemplateCover();

View File

@@ -14,7 +14,7 @@
namespace esphome {
namespace template_ {
class TemplateDate final : public datetime::DateEntity, public PollingComponent {
class TemplateDate : 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 final : public datetime::DateTimeEntity, public PollingComponent {
class TemplateDateTime : 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 final : public datetime::TimeEntity, public PollingComponent {
class TemplateTime : 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 final : public Component, public event::Event {};
class TemplateEvent : public Component, public event::Event {};
} // namespace template_
} // namespace esphome

View File

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

View File

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

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