mirror of
https://github.com/esphome/esphome.git
synced 2026-01-19 17:46:23 -07:00
Compare commits
6 Commits
globals_po
...
shtcx_fix_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1721aca99e | ||
|
|
ee264d0fd4 | ||
|
|
892e9b006f | ||
|
|
f8bd4ef57d | ||
|
|
bfcc0e26a3 | ||
|
|
86a1b4cf69 |
@@ -69,6 +69,7 @@ from esphome.cpp_types import ( # noqa: F401
|
||||
JsonObjectConst,
|
||||
Parented,
|
||||
PollingComponent,
|
||||
StringRef,
|
||||
arduino_json_ns,
|
||||
bool_,
|
||||
const_char_ptr,
|
||||
|
||||
@@ -18,31 +18,31 @@ AnovaPacket *AnovaCodec::clean_packet_() {
|
||||
|
||||
AnovaPacket *AnovaCodec::get_read_device_status_request() {
|
||||
this->current_query_ = READ_DEVICE_STATUS;
|
||||
sprintf((char *) this->packet_.data, "%s", CMD_READ_DEVICE_STATUS);
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_DEVICE_STATUS);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_read_target_temp_request() {
|
||||
this->current_query_ = READ_TARGET_TEMPERATURE;
|
||||
sprintf((char *) this->packet_.data, "%s", CMD_READ_TARGET_TEMP);
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_TARGET_TEMP);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_read_current_temp_request() {
|
||||
this->current_query_ = READ_CURRENT_TEMPERATURE;
|
||||
sprintf((char *) this->packet_.data, "%s", CMD_READ_CURRENT_TEMP);
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_CURRENT_TEMP);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_read_unit_request() {
|
||||
this->current_query_ = READ_UNIT;
|
||||
sprintf((char *) this->packet_.data, "%s", CMD_READ_UNIT);
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_UNIT);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_read_data_request() {
|
||||
this->current_query_ = READ_DATA;
|
||||
sprintf((char *) this->packet_.data, "%s", CMD_READ_DATA);
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_DATA);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
@@ -50,25 +50,25 @@ AnovaPacket *AnovaCodec::get_set_target_temp_request(float temperature) {
|
||||
this->current_query_ = SET_TARGET_TEMPERATURE;
|
||||
if (this->fahrenheit_)
|
||||
temperature = ctof(temperature);
|
||||
sprintf((char *) this->packet_.data, CMD_SET_TARGET_TEMP, temperature);
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), CMD_SET_TARGET_TEMP, temperature);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_set_unit_request(char unit) {
|
||||
this->current_query_ = SET_UNIT;
|
||||
sprintf((char *) this->packet_.data, CMD_SET_TEMP_UNIT, unit);
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), CMD_SET_TEMP_UNIT, unit);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_start_request() {
|
||||
this->current_query_ = START;
|
||||
sprintf((char *) this->packet_.data, CMD_START);
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_START);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_stop_request() {
|
||||
this->current_query_ = STOP;
|
||||
sprintf((char *) this->packet_.data, CMD_STOP);
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_STOP);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
|
||||
@@ -1715,7 +1715,7 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
|
||||
// HA state max length is 255 characters, but attributes can be much longer
|
||||
// Use stack buffer for common case (states), heap fallback for large attributes
|
||||
size_t state_len = msg.state.size();
|
||||
SmallBufferWithHeapFallback<256> state_buf_alloc(state_len + 1);
|
||||
SmallBufferWithHeapFallback<MAX_STATE_LEN + 1> state_buf_alloc(state_len + 1);
|
||||
char *state_buf = reinterpret_cast<char *>(state_buf_alloc.get());
|
||||
if (state_len > 0) {
|
||||
memcpy(state_buf, msg.state.c_str(), state_len);
|
||||
|
||||
@@ -75,8 +75,8 @@ class SetLatencyCommand : public Command {
|
||||
class SensorCfgStartCommand : public Command {
|
||||
public:
|
||||
SensorCfgStartCommand(bool startup_mode) : startup_mode_(startup_mode) {
|
||||
char tmp_cmd[20] = {0};
|
||||
sprintf(tmp_cmd, "sensorCfgStart %d", startup_mode);
|
||||
char tmp_cmd[20]; // "sensorCfgStart " (15) + "0/1" (1) + null = 17
|
||||
buf_append_printf(tmp_cmd, sizeof(tmp_cmd), 0, "sensorCfgStart %d", startup_mode);
|
||||
cmd_ = std::string(tmp_cmd);
|
||||
}
|
||||
uint8_t on_message(std::string &message) override;
|
||||
@@ -142,8 +142,8 @@ class SensitivityCommand : public Command {
|
||||
SensitivityCommand(uint8_t sensitivity) : sensitivity_(sensitivity) {
|
||||
if (sensitivity > 9)
|
||||
sensitivity_ = sensitivity = 9;
|
||||
char tmp_cmd[20] = {0};
|
||||
sprintf(tmp_cmd, "setSensitivity %d", sensitivity);
|
||||
char tmp_cmd[20]; // "setSensitivity " (15) + "0-9" (1) + null = 17
|
||||
buf_append_printf(tmp_cmd, sizeof(tmp_cmd), 0, "setSensitivity %d", sensitivity);
|
||||
cmd_ = std::string(tmp_cmd);
|
||||
};
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
@@ -90,9 +90,7 @@ async def setup_event_core_(var, config, *, event_types: list[str]):
|
||||
|
||||
for conf in config.get(CONF_ON_EVENT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.std_string, "event_type")], conf
|
||||
)
|
||||
await automation.build_automation(trigger, [(cg.StringRef, "event_type")], conf)
|
||||
|
||||
cg.add(var.set_event_types(event_types))
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ template<typename... Ts> class TriggerEventAction : public Action<Ts...>, public
|
||||
void play(const Ts &...x) override { this->parent_->trigger(this->event_type_.value(x...)); }
|
||||
};
|
||||
|
||||
class EventTrigger : public Trigger<std::string> {
|
||||
class EventTrigger : public Trigger<StringRef> {
|
||||
public:
|
||||
EventTrigger(Event *event) {
|
||||
event->add_on_event_callback([this](const std::string &event_type) { this->trigger(event_type); });
|
||||
event->add_on_event_callback([this](StringRef event_type) { this->trigger(event_type); });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ void Event::trigger(const std::string &event_type) {
|
||||
}
|
||||
this->last_event_type_ = found;
|
||||
ESP_LOGD(TAG, "'%s' >> '%s'", this->get_name().c_str(), this->last_event_type_);
|
||||
this->event_callback_.call(event_type);
|
||||
this->event_callback_.call(StringRef(found));
|
||||
#if defined(USE_EVENT) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_event(this);
|
||||
#endif
|
||||
@@ -45,7 +45,7 @@ void Event::set_event_types(const std::vector<const char *> &event_types) {
|
||||
this->last_event_type_ = nullptr; // Reset when types change
|
||||
}
|
||||
|
||||
void Event::add_on_event_callback(std::function<void(const std::string &event_type)> &&callback) {
|
||||
void Event::add_on_event_callback(std::function<void(StringRef event_type)> &&callback) {
|
||||
this->event_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
|
||||
@@ -70,10 +70,10 @@ class Event : public EntityBase, public EntityBase_DeviceClass {
|
||||
/// Check if an event has been triggered.
|
||||
bool has_event() const { return this->last_event_type_ != nullptr; }
|
||||
|
||||
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
|
||||
void add_on_event_callback(std::function<void(StringRef event_type)> &&callback);
|
||||
|
||||
protected:
|
||||
LazyCallbackManager<void(const std::string &event_type)> event_callback_;
|
||||
LazyCallbackManager<void(StringRef event_type)> event_callback_;
|
||||
FixedVector<const char *> types_;
|
||||
|
||||
private:
|
||||
|
||||
@@ -77,7 +77,7 @@ FanSpeedSetTrigger = fan_ns.class_(
|
||||
"FanSpeedSetTrigger", automation.Trigger.template(cg.int_)
|
||||
)
|
||||
FanPresetSetTrigger = fan_ns.class_(
|
||||
"FanPresetSetTrigger", automation.Trigger.template(cg.std_string)
|
||||
"FanPresetSetTrigger", automation.Trigger.template(cg.StringRef)
|
||||
)
|
||||
|
||||
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template())
|
||||
@@ -287,7 +287,7 @@ async def setup_fan_core_(var, config):
|
||||
await automation.build_automation(trigger, [(cg.int_, "x")], conf)
|
||||
for conf in config.get(CONF_ON_PRESET_SET, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
||||
await automation.build_automation(trigger, [(cg.StringRef, "x")], conf)
|
||||
|
||||
|
||||
async def register_fan(var, config):
|
||||
|
||||
@@ -208,7 +208,7 @@ class FanSpeedSetTrigger : public Trigger<int> {
|
||||
int last_speed_;
|
||||
};
|
||||
|
||||
class FanPresetSetTrigger : public Trigger<std::string> {
|
||||
class FanPresetSetTrigger : public Trigger<StringRef> {
|
||||
public:
|
||||
FanPresetSetTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
@@ -216,7 +216,7 @@ class FanPresetSetTrigger : public Trigger<std::string> {
|
||||
auto should_trigger = preset_mode != this->last_preset_mode_;
|
||||
this->last_preset_mode_ = preset_mode;
|
||||
if (should_trigger) {
|
||||
this->trigger(std::string(preset_mode));
|
||||
this->trigger(preset_mode);
|
||||
}
|
||||
});
|
||||
this->last_preset_mode_ = state->get_preset_mode();
|
||||
|
||||
@@ -6,7 +6,6 @@ from esphome.const import (
|
||||
CONF_INITIAL_VALUE,
|
||||
CONF_RESTORE_VALUE,
|
||||
CONF_TYPE,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
CONF_VALUE,
|
||||
)
|
||||
from esphome.core import CoroPriority, coroutine_with_priority
|
||||
@@ -14,37 +13,25 @@ from esphome.core import CoroPriority, coroutine_with_priority
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
globals_ns = cg.esphome_ns.namespace("globals")
|
||||
GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component)
|
||||
RestoringGlobalsComponent = globals_ns.class_(
|
||||
"RestoringGlobalsComponent", cg.PollingComponent
|
||||
)
|
||||
RestoringGlobalsComponent = globals_ns.class_("RestoringGlobalsComponent", cg.Component)
|
||||
RestoringGlobalStringComponent = globals_ns.class_(
|
||||
"RestoringGlobalStringComponent", cg.PollingComponent
|
||||
"RestoringGlobalStringComponent", cg.Component
|
||||
)
|
||||
GlobalVarSetAction = globals_ns.class_("GlobalVarSetAction", automation.Action)
|
||||
|
||||
CONF_MAX_RESTORE_DATA_LENGTH = "max_restore_data_length"
|
||||
|
||||
|
||||
def validate_update_interval(config):
|
||||
if CONF_UPDATE_INTERVAL in config and not config.get(CONF_RESTORE_VALUE, False):
|
||||
raise cv.Invalid("update_interval requires restore_value to be true")
|
||||
return config
|
||||
|
||||
|
||||
MULTI_CONF = True
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(GlobalsComponent),
|
||||
cv.Required(CONF_TYPE): cv.string_strict,
|
||||
cv.Optional(CONF_INITIAL_VALUE): cv.string_strict,
|
||||
cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MAX_RESTORE_DATA_LENGTH): cv.int_range(0, 254),
|
||||
cv.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
validate_update_interval,
|
||||
)
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(GlobalsComponent),
|
||||
cv.Required(CONF_TYPE): cv.string_strict,
|
||||
cv.Optional(CONF_INITIAL_VALUE): cv.string_strict,
|
||||
cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MAX_RESTORE_DATA_LENGTH): cv.int_range(0, 254),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
# Run with low priority so that namespaces are registered first
|
||||
@@ -78,8 +65,6 @@ async def to_code(config):
|
||||
value = value.encode()
|
||||
hash_ = int(hashlib.md5(value).hexdigest()[:8], 16)
|
||||
cg.add(glob.set_name_hash(hash_))
|
||||
if CONF_UPDATE_INTERVAL in config:
|
||||
cg.add(glob.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome::globals {
|
||||
namespace esphome {
|
||||
namespace globals {
|
||||
|
||||
template<typename T> class GlobalsComponent : public Component {
|
||||
public:
|
||||
@@ -23,14 +24,13 @@ template<typename T> class GlobalsComponent : public Component {
|
||||
T value_{};
|
||||
};
|
||||
|
||||
template<typename T> class RestoringGlobalsComponent : public PollingComponent {
|
||||
template<typename T> class RestoringGlobalsComponent : public Component {
|
||||
public:
|
||||
using value_type = T;
|
||||
explicit RestoringGlobalsComponent() : PollingComponent(1000) {}
|
||||
explicit RestoringGlobalsComponent(T initial_value) : PollingComponent(1000), value_(initial_value) {}
|
||||
explicit RestoringGlobalsComponent() = default;
|
||||
explicit RestoringGlobalsComponent(T initial_value) : value_(initial_value) {}
|
||||
explicit RestoringGlobalsComponent(
|
||||
std::array<typename std::remove_extent<T>::type, std::extent<T>::value> initial_value)
|
||||
: PollingComponent(1000) {
|
||||
std::array<typename std::remove_extent<T>::type, std::extent<T>::value> initial_value) {
|
||||
memcpy(this->value_, initial_value.data(), sizeof(T));
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ template<typename T> class RestoringGlobalsComponent : public PollingComponent {
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
void update() override { store_value_(); }
|
||||
void loop() override { store_value_(); }
|
||||
|
||||
void on_shutdown() override { store_value_(); }
|
||||
|
||||
@@ -66,14 +66,13 @@ template<typename T> class RestoringGlobalsComponent : public PollingComponent {
|
||||
};
|
||||
|
||||
// Use with string or subclasses of strings
|
||||
template<typename T, uint8_t SZ> class RestoringGlobalStringComponent : public PollingComponent {
|
||||
template<typename T, uint8_t SZ> class RestoringGlobalStringComponent : public Component {
|
||||
public:
|
||||
using value_type = T;
|
||||
explicit RestoringGlobalStringComponent() : PollingComponent(1000) {}
|
||||
explicit RestoringGlobalStringComponent(T initial_value) : PollingComponent(1000) { this->value_ = initial_value; }
|
||||
explicit RestoringGlobalStringComponent() = default;
|
||||
explicit RestoringGlobalStringComponent(T initial_value) { this->value_ = initial_value; }
|
||||
explicit RestoringGlobalStringComponent(
|
||||
std::array<typename std::remove_extent<T>::type, std::extent<T>::value> initial_value)
|
||||
: PollingComponent(1000) {
|
||||
std::array<typename std::remove_extent<T>::type, std::extent<T>::value> initial_value) {
|
||||
memcpy(this->value_, initial_value.data(), sizeof(T));
|
||||
}
|
||||
|
||||
@@ -91,7 +90,7 @@ template<typename T, uint8_t SZ> class RestoringGlobalStringComponent : public P
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
void update() override { store_value_(); }
|
||||
void loop() override { store_value_(); }
|
||||
|
||||
void on_shutdown() override { store_value_(); }
|
||||
|
||||
@@ -145,4 +144,5 @@ template<typename T> T &id(GlobalsComponent<T> *value) { return value->value();
|
||||
template<typename T> T &id(RestoringGlobalsComponent<T> *value) { return value->value(); }
|
||||
template<typename T, uint8_t SZ> T &id(RestoringGlobalStringComponent<T, SZ> *value) { return value->value(); }
|
||||
|
||||
} // namespace esphome::globals
|
||||
} // namespace globals
|
||||
} // namespace esphome
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace pipsolar {
|
||||
static const char *const TAG = "pipsolar.output";
|
||||
|
||||
void PipsolarOutput::write_state(float state) {
|
||||
char tmp[10];
|
||||
char tmp[16];
|
||||
snprintf(tmp, sizeof(tmp), this->set_command_, state);
|
||||
|
||||
if (std::find(this->possible_values_.begin(), this->possible_values_.end(), state) != this->possible_values_.end()) {
|
||||
|
||||
@@ -33,7 +33,7 @@ SelectPtr = Select.operator("ptr")
|
||||
# Triggers
|
||||
SelectStateTrigger = select_ns.class_(
|
||||
"SelectStateTrigger",
|
||||
automation.Trigger.template(cg.std_string, cg.size_t),
|
||||
automation.Trigger.template(cg.StringRef, cg.size_t),
|
||||
)
|
||||
|
||||
# Actions
|
||||
@@ -100,7 +100,7 @@ async def setup_select_core_(var, config, *, options: list[str]):
|
||||
for conf in config.get(CONF_ON_VALUE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.std_string, "x"), (cg.size_t, "i")], conf
|
||||
trigger, [(cg.StringRef, "x"), (cg.size_t, "i")], conf
|
||||
)
|
||||
|
||||
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
namespace esphome::select {
|
||||
|
||||
class SelectStateTrigger : public Trigger<std::string, size_t> {
|
||||
class SelectStateTrigger : public Trigger<StringRef, size_t> {
|
||||
public:
|
||||
explicit SelectStateTrigger(Select *parent) : parent_(parent) {
|
||||
parent->add_on_state_callback(
|
||||
[this](size_t index) { this->trigger(std::string(this->parent_->option_at(index)), index); });
|
||||
[this](size_t index) { this->trigger(StringRef(this->parent_->option_at(index)), index); });
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -13,14 +13,14 @@ static const uint16_t SHTCX_COMMAND_READ_ID_REGISTER = 0xEFC8;
|
||||
static const uint16_t SHTCX_COMMAND_SOFT_RESET = 0x805D;
|
||||
static const uint16_t SHTCX_COMMAND_POLLING_H = 0x7866;
|
||||
|
||||
inline const char *to_string(SHTCXType type) {
|
||||
static const LogString *shtcx_type_to_string(SHTCXType type) {
|
||||
switch (type) {
|
||||
case SHTCX_TYPE_SHTC3:
|
||||
return "SHTC3";
|
||||
return LOG_STR("SHTC3");
|
||||
case SHTCX_TYPE_SHTC1:
|
||||
return "SHTC1";
|
||||
return LOG_STR("SHTC1");
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ void SHTCXComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"SHTCx:\n"
|
||||
" Model: %s (%04x)",
|
||||
to_string(this->type_), this->sensor_id_);
|
||||
LOG_STR_ARG(shtcx_type_to_string(this->type_)), this->sensor_id_);
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "sim800l.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cstring>
|
||||
|
||||
@@ -50,8 +51,8 @@ void Sim800LComponent::update() {
|
||||
} else if (state_ == STATE_RECEIVED_SMS) {
|
||||
// Serial Buffer should have flushed.
|
||||
// Send cmd to delete received sms
|
||||
char delete_cmd[20];
|
||||
sprintf(delete_cmd, "AT+CMGD=%d", this->parse_index_);
|
||||
char delete_cmd[20]; // "AT+CMGD=" (8) + uint8_t (max 3) + null = 12 <= 20
|
||||
buf_append_printf(delete_cmd, sizeof(delete_cmd), 0, "AT+CMGD=%d", this->parse_index_);
|
||||
this->send_cmd_(delete_cmd);
|
||||
this->state_ = STATE_CHECK_SMS;
|
||||
this->expect_ack_ = true;
|
||||
|
||||
@@ -88,5 +88,5 @@ async def to_code(config):
|
||||
|
||||
if CONF_SET_ACTION in config:
|
||||
await automation.build_automation(
|
||||
var.get_set_trigger(), [(cg.std_string, "x")], config[CONF_SET_ACTION]
|
||||
var.get_set_trigger(), [(cg.StringRef, "x")], config[CONF_SET_ACTION]
|
||||
)
|
||||
|
||||
@@ -41,7 +41,7 @@ void TemplateSelect::update() {
|
||||
}
|
||||
|
||||
void TemplateSelect::control(size_t index) {
|
||||
this->set_trigger_->trigger(std::string(this->option_at(index)));
|
||||
this->set_trigger_->trigger(StringRef(this->option_at(index)));
|
||||
|
||||
if (this->optimistic_)
|
||||
this->publish_state(index);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
#include "esphome/core/template_lambda.h"
|
||||
|
||||
namespace esphome::template_ {
|
||||
@@ -17,7 +18,7 @@ class TemplateSelect final : public select::Select, public PollingComponent {
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; }
|
||||
Trigger<StringRef> *get_set_trigger() const { return this->set_trigger_; }
|
||||
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
void set_initial_option_index(size_t initial_option_index) { this->initial_option_index_ = initial_option_index; }
|
||||
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
|
||||
@@ -27,7 +28,7 @@ class TemplateSelect final : public select::Select, public PollingComponent {
|
||||
bool optimistic_ = false;
|
||||
size_t initial_option_index_{0};
|
||||
bool restore_value_ = false;
|
||||
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
|
||||
Trigger<StringRef> *set_trigger_ = new Trigger<StringRef>();
|
||||
TemplateLambda<std::string> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "wl_134.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
@@ -78,8 +79,8 @@ Wl134Component::Rfid134Error Wl134Component::read_packet_() {
|
||||
reading.id, reading.country, reading.isData ? "true" : "false", reading.isAnimal ? "true" : "false",
|
||||
reading.reserved0, reading.reserved1);
|
||||
|
||||
char buf[20];
|
||||
sprintf(buf, "%03d%012lld", reading.country, reading.id);
|
||||
char buf[20]; // "%03d" (3) + "%012" PRId64 (12) + null = 16 max
|
||||
buf_append_printf(buf, sizeof(buf), 0, "%03d%012" PRId64, reading.country, reading.id);
|
||||
this->publish_state(buf);
|
||||
if (this->do_reset_) {
|
||||
this->set_timeout(1000, [this]() { this->publish_state(""); });
|
||||
|
||||
@@ -72,6 +72,7 @@ class StringRef {
|
||||
|
||||
constexpr const char *c_str() const { return base_; }
|
||||
constexpr size_type size() const { return len_; }
|
||||
constexpr size_type length() const { return len_; }
|
||||
constexpr bool empty() const { return len_ == 0; }
|
||||
constexpr const_reference operator[](size_type pos) const { return *(base_ + pos); }
|
||||
|
||||
@@ -80,6 +81,32 @@ class StringRef {
|
||||
|
||||
operator std::string() const { return str(); }
|
||||
|
||||
/// Find first occurrence of substring, returns std::string::npos if not found.
|
||||
/// Note: Requires the underlying string to be null-terminated.
|
||||
size_type find(const char *s, size_type pos = 0) const {
|
||||
if (pos >= len_)
|
||||
return std::string::npos;
|
||||
const char *result = std::strstr(base_ + pos, s);
|
||||
// Verify entire match is within bounds (strstr searches to null terminator)
|
||||
if (result && result + std::strlen(s) <= base_ + len_)
|
||||
return static_cast<size_type>(result - base_);
|
||||
return std::string::npos;
|
||||
}
|
||||
size_type find(char c, size_type pos = 0) const {
|
||||
if (pos >= len_)
|
||||
return std::string::npos;
|
||||
const void *result = std::memchr(base_ + pos, static_cast<unsigned char>(c), len_ - pos);
|
||||
return result ? static_cast<size_type>(static_cast<const char *>(result) - base_) : std::string::npos;
|
||||
}
|
||||
|
||||
/// Return substring as std::string
|
||||
std::string substr(size_type pos = 0, size_type count = std::string::npos) const {
|
||||
if (pos >= len_)
|
||||
return std::string();
|
||||
size_type actual_count = (count == std::string::npos || pos + count > len_) ? len_ - pos : count;
|
||||
return std::string(base_ + pos, actual_count);
|
||||
}
|
||||
|
||||
private:
|
||||
const char *base_;
|
||||
size_type len_;
|
||||
@@ -160,6 +187,43 @@ inline std::string operator+(const std::string &lhs, const StringRef &rhs) {
|
||||
str.append(rhs.c_str(), rhs.size());
|
||||
return str;
|
||||
}
|
||||
// String conversion functions for ADL compatibility (allows stoi(x) where x is StringRef)
|
||||
// Must be in esphome namespace for ADL to find them. Uses strtol/strtod directly to avoid heap allocation.
|
||||
namespace internal {
|
||||
// NOLINTBEGIN(google-runtime-int)
|
||||
template<typename R, typename F> inline R parse_number(const StringRef &str, size_t *pos, F conv) {
|
||||
char *end;
|
||||
R result = conv(str.c_str(), &end);
|
||||
// Set pos to 0 on conversion failure (when no characters consumed), otherwise index after number
|
||||
if (pos)
|
||||
*pos = (end == str.c_str()) ? 0 : static_cast<size_t>(end - str.c_str());
|
||||
return result;
|
||||
}
|
||||
template<typename R, typename F> inline R parse_number(const StringRef &str, size_t *pos, int base, F conv) {
|
||||
char *end;
|
||||
R result = conv(str.c_str(), &end, base);
|
||||
// Set pos to 0 on conversion failure (when no characters consumed), otherwise index after number
|
||||
if (pos)
|
||||
*pos = (end == str.c_str()) ? 0 : static_cast<size_t>(end - str.c_str());
|
||||
return result;
|
||||
}
|
||||
// NOLINTEND(google-runtime-int)
|
||||
} // namespace internal
|
||||
// NOLINTBEGIN(readability-identifier-naming,google-runtime-int)
|
||||
inline int stoi(const StringRef &str, size_t *pos = nullptr, int base = 10) {
|
||||
return static_cast<int>(internal::parse_number<long>(str, pos, base, std::strtol));
|
||||
}
|
||||
inline long stol(const StringRef &str, size_t *pos = nullptr, int base = 10) {
|
||||
return internal::parse_number<long>(str, pos, base, std::strtol);
|
||||
}
|
||||
inline float stof(const StringRef &str, size_t *pos = nullptr) {
|
||||
return internal::parse_number<float>(str, pos, std::strtof);
|
||||
}
|
||||
inline double stod(const StringRef &str, size_t *pos = nullptr) {
|
||||
return internal::parse_number<double>(str, pos, std::strtod);
|
||||
}
|
||||
// NOLINTEND(readability-identifier-naming,google-runtime-int)
|
||||
|
||||
#ifdef USE_JSON
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
inline void convertToJson(const StringRef &src, JsonVariant dst) { dst.set(src.c_str()); }
|
||||
|
||||
@@ -44,3 +44,4 @@ gpio_Flags = gpio_ns.enum("Flags", is_class=True)
|
||||
EntityCategory = esphome_ns.enum("EntityCategory")
|
||||
Parented = esphome_ns.class_("Parented")
|
||||
ESPTime = esphome_ns.struct("ESPTime")
|
||||
StringRef = esphome_ns.class_("StringRef")
|
||||
|
||||
85
tests/integration/fixtures/select_stringref_trigger.yaml
Normal file
85
tests/integration/fixtures/select_stringref_trigger.yaml
Normal file
@@ -0,0 +1,85 @@
|
||||
esphome:
|
||||
name: select-stringref-test
|
||||
friendly_name: Select StringRef Test
|
||||
|
||||
host:
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
||||
api:
|
||||
|
||||
select:
|
||||
- platform: template
|
||||
name: "Test Select"
|
||||
id: test_select
|
||||
optimistic: true
|
||||
options:
|
||||
- "Option A"
|
||||
- "Option B"
|
||||
- "Option C"
|
||||
initial_option: "Option A"
|
||||
on_value:
|
||||
then:
|
||||
# Test 1: Log the value directly (StringRef -> const char* via c_str())
|
||||
- logger.log:
|
||||
format: "Select value: %s"
|
||||
args: ['x.c_str()']
|
||||
# Test 2: String concatenation (StringRef + const char* -> std::string)
|
||||
- lambda: |-
|
||||
std::string with_suffix = x + " selected";
|
||||
ESP_LOGI("test", "Concatenated: %s", with_suffix.c_str());
|
||||
# Test 3: Comparison (StringRef == const char*)
|
||||
- lambda: |-
|
||||
if (x == "Option B") {
|
||||
ESP_LOGI("test", "Option B was selected");
|
||||
}
|
||||
# Test 4: Use index parameter (variable name is 'i')
|
||||
- lambda: |-
|
||||
ESP_LOGI("test", "Select index: %d", (int)i);
|
||||
# Test 5: StringRef.length() method
|
||||
- lambda: |-
|
||||
ESP_LOGI("test", "Length: %d", (int)x.length());
|
||||
# Test 6: StringRef.find() method with substring
|
||||
- lambda: |-
|
||||
if (x.find("Option") != std::string::npos) {
|
||||
ESP_LOGI("test", "Found 'Option' in value");
|
||||
}
|
||||
# Test 7: StringRef.find() method with character
|
||||
- lambda: |-
|
||||
size_t space_pos = x.find(' ');
|
||||
if (space_pos != std::string::npos) {
|
||||
ESP_LOGI("test", "Space at position: %d", (int)space_pos);
|
||||
}
|
||||
# Test 8: StringRef.substr() method
|
||||
- lambda: |-
|
||||
std::string prefix = x.substr(0, 6);
|
||||
ESP_LOGI("test", "Substr prefix: %s", prefix.c_str());
|
||||
|
||||
# Second select with numeric options to test ADL functions
|
||||
- platform: template
|
||||
name: "Baud Rate"
|
||||
id: baud_select
|
||||
optimistic: true
|
||||
options:
|
||||
- "9600"
|
||||
- "115200"
|
||||
initial_option: "9600"
|
||||
on_value:
|
||||
then:
|
||||
# Test 9: stoi via ADL
|
||||
- lambda: |-
|
||||
int baud = stoi(x);
|
||||
ESP_LOGI("test", "stoi result: %d", baud);
|
||||
# Test 10: stol via ADL
|
||||
- lambda: |-
|
||||
long baud_long = stol(x);
|
||||
ESP_LOGI("test", "stol result: %ld", baud_long);
|
||||
# Test 11: stof via ADL
|
||||
- lambda: |-
|
||||
float baud_float = stof(x);
|
||||
ESP_LOGI("test", "stof result: %.0f", baud_float);
|
||||
# Test 12: stod via ADL
|
||||
- lambda: |-
|
||||
double baud_double = stod(x);
|
||||
ESP_LOGI("test", "stod result: %.0f", baud_double);
|
||||
143
tests/integration/test_select_stringref_trigger.py
Normal file
143
tests/integration/test_select_stringref_trigger.py
Normal file
@@ -0,0 +1,143 @@
|
||||
"""Integration test for select on_value trigger with StringRef parameter."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_select_stringref_trigger(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Test select on_value trigger passes StringRef that works with string operations."""
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
# Track log messages to verify StringRef operations work
|
||||
value_logged_future = loop.create_future()
|
||||
concatenated_future = loop.create_future()
|
||||
comparison_future = loop.create_future()
|
||||
index_logged_future = loop.create_future()
|
||||
length_future = loop.create_future()
|
||||
find_substr_future = loop.create_future()
|
||||
find_char_future = loop.create_future()
|
||||
substr_future = loop.create_future()
|
||||
# ADL functions
|
||||
stoi_future = loop.create_future()
|
||||
stol_future = loop.create_future()
|
||||
stof_future = loop.create_future()
|
||||
stod_future = loop.create_future()
|
||||
|
||||
# Patterns to match in logs
|
||||
value_pattern = re.compile(r"Select value: Option B")
|
||||
concatenated_pattern = re.compile(r"Concatenated: Option B selected")
|
||||
comparison_pattern = re.compile(r"Option B was selected")
|
||||
index_pattern = re.compile(r"Select index: 1")
|
||||
length_pattern = re.compile(r"Length: 8") # "Option B" is 8 chars
|
||||
find_substr_pattern = re.compile(r"Found 'Option' in value")
|
||||
find_char_pattern = re.compile(r"Space at position: 6") # space at index 6
|
||||
substr_pattern = re.compile(r"Substr prefix: Option")
|
||||
# ADL function patterns (115200 from baud rate select)
|
||||
stoi_pattern = re.compile(r"stoi result: 115200")
|
||||
stol_pattern = re.compile(r"stol result: 115200")
|
||||
stof_pattern = re.compile(r"stof result: 115200")
|
||||
stod_pattern = re.compile(r"stod result: 115200")
|
||||
|
||||
def check_output(line: str) -> None:
|
||||
"""Check log output for expected messages."""
|
||||
if not value_logged_future.done() and value_pattern.search(line):
|
||||
value_logged_future.set_result(True)
|
||||
if not concatenated_future.done() and concatenated_pattern.search(line):
|
||||
concatenated_future.set_result(True)
|
||||
if not comparison_future.done() and comparison_pattern.search(line):
|
||||
comparison_future.set_result(True)
|
||||
if not index_logged_future.done() and index_pattern.search(line):
|
||||
index_logged_future.set_result(True)
|
||||
if not length_future.done() and length_pattern.search(line):
|
||||
length_future.set_result(True)
|
||||
if not find_substr_future.done() and find_substr_pattern.search(line):
|
||||
find_substr_future.set_result(True)
|
||||
if not find_char_future.done() and find_char_pattern.search(line):
|
||||
find_char_future.set_result(True)
|
||||
if not substr_future.done() and substr_pattern.search(line):
|
||||
substr_future.set_result(True)
|
||||
# ADL functions
|
||||
if not stoi_future.done() and stoi_pattern.search(line):
|
||||
stoi_future.set_result(True)
|
||||
if not stol_future.done() and stol_pattern.search(line):
|
||||
stol_future.set_result(True)
|
||||
if not stof_future.done() and stof_pattern.search(line):
|
||||
stof_future.set_result(True)
|
||||
if not stod_future.done() and stod_pattern.search(line):
|
||||
stod_future.set_result(True)
|
||||
|
||||
async with (
|
||||
run_compiled(yaml_config, line_callback=check_output),
|
||||
api_client_connected() as client,
|
||||
):
|
||||
# Verify device info
|
||||
device_info = await client.device_info()
|
||||
assert device_info is not None
|
||||
assert device_info.name == "select-stringref-test"
|
||||
|
||||
# List entities to find our select
|
||||
entities, _ = await client.list_entities_services()
|
||||
|
||||
select_entity = next(
|
||||
(e for e in entities if hasattr(e, "options") and e.name == "Test Select"),
|
||||
None,
|
||||
)
|
||||
assert select_entity is not None, "Test Select entity not found"
|
||||
|
||||
baud_entity = next(
|
||||
(e for e in entities if hasattr(e, "options") and e.name == "Baud Rate"),
|
||||
None,
|
||||
)
|
||||
assert baud_entity is not None, "Baud Rate entity not found"
|
||||
|
||||
# Change select to Option B - this should trigger on_value with StringRef
|
||||
client.select_command(select_entity.key, "Option B")
|
||||
# Change baud to 115200 - this tests ADL functions (stoi, stol, stof, stod)
|
||||
client.select_command(baud_entity.key, "115200")
|
||||
|
||||
# Wait for all log messages confirming StringRef operations work
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
asyncio.gather(
|
||||
value_logged_future,
|
||||
concatenated_future,
|
||||
comparison_future,
|
||||
index_logged_future,
|
||||
length_future,
|
||||
find_substr_future,
|
||||
find_char_future,
|
||||
substr_future,
|
||||
stoi_future,
|
||||
stol_future,
|
||||
stof_future,
|
||||
stod_future,
|
||||
),
|
||||
timeout=5.0,
|
||||
)
|
||||
except TimeoutError:
|
||||
results = {
|
||||
"value_logged": value_logged_future.done(),
|
||||
"concatenated": concatenated_future.done(),
|
||||
"comparison": comparison_future.done(),
|
||||
"index_logged": index_logged_future.done(),
|
||||
"length": length_future.done(),
|
||||
"find_substr": find_substr_future.done(),
|
||||
"find_char": find_char_future.done(),
|
||||
"substr": substr_future.done(),
|
||||
"stoi": stoi_future.done(),
|
||||
"stol": stol_future.done(),
|
||||
"stof": stof_future.done(),
|
||||
"stod": stod_future.done(),
|
||||
}
|
||||
pytest.fail(f"StringRef operations failed - received: {results}")
|
||||
Reference in New Issue
Block a user