mirror of
https://github.com/esphome/esphome.git
synced 2026-01-19 17:46:23 -07:00
Compare commits
8 Commits
mqtt_less_
...
mqtt_forma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f8f052f43 | ||
|
|
86e70c7e76 | ||
|
|
40025bb277 | ||
|
|
438bb96687 | ||
|
|
c1e1325af2 | ||
|
|
944194e04e | ||
|
|
d27d6d64da | ||
|
|
2182d1e9f0 |
@@ -1,6 +1,5 @@
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
import argparse
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime
|
||||
import functools
|
||||
import getpass
|
||||
@@ -937,21 +936,11 @@ def command_dashboard(args: ArgsProtocol) -> int | None:
|
||||
return dashboard.start_dashboard(args)
|
||||
|
||||
|
||||
def run_multiple_configs(
|
||||
files: list, command_builder: Callable[[str], list[str]]
|
||||
) -> int:
|
||||
"""Run a command for each configuration file in a subprocess.
|
||||
|
||||
Args:
|
||||
files: List of configuration files to process.
|
||||
command_builder: Callable that takes a file path and returns a command list.
|
||||
|
||||
Returns:
|
||||
Number of failed files.
|
||||
"""
|
||||
def command_update_all(args: ArgsProtocol) -> int | None:
|
||||
import click
|
||||
|
||||
success = {}
|
||||
files = list_yaml_files(args.configuration)
|
||||
twidth = 60
|
||||
|
||||
def print_bar(middle_text):
|
||||
@@ -961,19 +950,17 @@ def run_multiple_configs(
|
||||
safe_print(f"{half_line}{middle_text}{half_line}")
|
||||
|
||||
for f in files:
|
||||
f_path = Path(f) if not isinstance(f, Path) else f
|
||||
|
||||
if any(f_path.name == x for x in SECRETS_FILES):
|
||||
_LOGGER.warning("Skipping secrets file %s", f_path)
|
||||
continue
|
||||
|
||||
safe_print(f"Processing {color(AnsiFore.CYAN, str(f))}")
|
||||
safe_print(f"Updating {color(AnsiFore.CYAN, str(f))}")
|
||||
safe_print("-" * twidth)
|
||||
safe_print()
|
||||
|
||||
cmd = command_builder(f)
|
||||
rc = run_external_process(*cmd)
|
||||
|
||||
if CORE.dashboard:
|
||||
rc = run_external_process(
|
||||
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
|
||||
)
|
||||
else:
|
||||
rc = run_external_process(
|
||||
"esphome", "run", f, "--no-logs", "--device", "OTA"
|
||||
)
|
||||
if rc == 0:
|
||||
print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {str(f)}")
|
||||
success[f] = True
|
||||
@@ -988,8 +975,6 @@ def run_multiple_configs(
|
||||
print_bar(f"[{color(AnsiFore.BOLD_WHITE, 'SUMMARY')}]")
|
||||
failed = 0
|
||||
for f in files:
|
||||
if f not in success:
|
||||
continue # Skipped file
|
||||
if success[f]:
|
||||
safe_print(f" - {str(f)}: {color(AnsiFore.GREEN, 'SUCCESS')}")
|
||||
else:
|
||||
@@ -998,17 +983,6 @@ def run_multiple_configs(
|
||||
return failed
|
||||
|
||||
|
||||
def command_update_all(args: ArgsProtocol) -> int | None:
|
||||
files = list_yaml_files(args.configuration)
|
||||
|
||||
def build_command(f):
|
||||
if CORE.dashboard:
|
||||
return ["esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"]
|
||||
return ["esphome", "run", f, "--no-logs", "--device", "OTA"]
|
||||
|
||||
return run_multiple_configs(files, build_command)
|
||||
|
||||
|
||||
def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
import json
|
||||
|
||||
@@ -1559,48 +1533,38 @@ def run_esphome(argv):
|
||||
|
||||
_LOGGER.info("ESPHome %s", const.__version__)
|
||||
|
||||
# Multiple configurations: use subprocesses to avoid state leakage
|
||||
# between compilations (e.g., LVGL touchscreen state in module globals)
|
||||
if len(args.configuration) > 1:
|
||||
# Build command by reusing argv, replacing all configs with single file
|
||||
# argv[0] is the program path, skip it since we prefix with "esphome"
|
||||
def build_command(f):
|
||||
return (
|
||||
["esphome"]
|
||||
+ [arg for arg in argv[1:] if arg not in args.configuration]
|
||||
+ [str(f)]
|
||||
)
|
||||
for conf_path in args.configuration:
|
||||
conf_path = Path(conf_path)
|
||||
if any(conf_path.name == x for x in SECRETS_FILES):
|
||||
_LOGGER.warning("Skipping secrets file %s", conf_path)
|
||||
continue
|
||||
|
||||
return run_multiple_configs(args.configuration, build_command)
|
||||
CORE.config_path = conf_path
|
||||
CORE.dashboard = args.dashboard
|
||||
|
||||
# Single configuration
|
||||
conf_path = Path(args.configuration[0])
|
||||
if any(conf_path.name == x for x in SECRETS_FILES):
|
||||
_LOGGER.warning("Skipping secrets file %s", conf_path)
|
||||
return 0
|
||||
# For logs command, skip updating external components
|
||||
skip_external = args.command == "logs"
|
||||
config = read_config(
|
||||
dict(args.substitution) if args.substitution else {},
|
||||
skip_external_update=skip_external,
|
||||
)
|
||||
if config is None:
|
||||
return 2
|
||||
CORE.config = config
|
||||
|
||||
CORE.config_path = conf_path
|
||||
CORE.dashboard = args.dashboard
|
||||
if args.command not in POST_CONFIG_ACTIONS:
|
||||
safe_print(f"Unknown command {args.command}")
|
||||
|
||||
# For logs command, skip updating external components
|
||||
skip_external = args.command == "logs"
|
||||
config = read_config(
|
||||
dict(args.substitution) if args.substitution else {},
|
||||
skip_external_update=skip_external,
|
||||
)
|
||||
if config is None:
|
||||
return 2
|
||||
CORE.config = config
|
||||
try:
|
||||
rc = POST_CONFIG_ACTIONS[args.command](args, config)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e, exc_info=args.verbose)
|
||||
return 1
|
||||
if rc != 0:
|
||||
return rc
|
||||
|
||||
if args.command not in POST_CONFIG_ACTIONS:
|
||||
safe_print(f"Unknown command {args.command}")
|
||||
return 1
|
||||
|
||||
try:
|
||||
return POST_CONFIG_ACTIONS[args.command](args, config)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e, exc_info=args.verbose)
|
||||
return 1
|
||||
CORE.reset()
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -69,7 +69,6 @@ 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;
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_DEVICE_STATUS);
|
||||
sprintf((char *) 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;
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_TARGET_TEMP);
|
||||
sprintf((char *) 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;
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_CURRENT_TEMP);
|
||||
sprintf((char *) this->packet_.data, "%s", CMD_READ_CURRENT_TEMP);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_read_unit_request() {
|
||||
this->current_query_ = READ_UNIT;
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_UNIT);
|
||||
sprintf((char *) this->packet_.data, "%s", CMD_READ_UNIT);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_read_data_request() {
|
||||
this->current_query_ = READ_DATA;
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_DATA);
|
||||
sprintf((char *) 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);
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), CMD_SET_TARGET_TEMP, temperature);
|
||||
sprintf((char *) 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;
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), CMD_SET_TEMP_UNIT, unit);
|
||||
sprintf((char *) this->packet_.data, CMD_SET_TEMP_UNIT, unit);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_start_request() {
|
||||
this->current_query_ = START;
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_START);
|
||||
sprintf((char *) this->packet_.data, CMD_START);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_stop_request() {
|
||||
this->current_query_ = STOP;
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_STOP);
|
||||
sprintf((char *) this->packet_.data, 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<MAX_STATE_LEN + 1> state_buf_alloc(state_len + 1);
|
||||
SmallBufferWithHeapFallback<256> 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);
|
||||
|
||||
@@ -207,24 +207,20 @@ void CSE7766Component::parse_data_() {
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
{
|
||||
// Buffer: 7 + 15 + 33 + 15 + 25 = 95 chars max + null, rounded to 128 for safety margin.
|
||||
// Float sizes with %.4f can be up to 11 chars for large values (e.g., 999999.9999).
|
||||
char buf[128];
|
||||
size_t pos = buf_append_printf(buf, sizeof(buf), 0, "Parsed:");
|
||||
std::string buf = "Parsed:";
|
||||
if (have_voltage) {
|
||||
pos = buf_append_printf(buf, sizeof(buf), pos, " V=%.4fV", voltage);
|
||||
buf += str_sprintf(" V=%fV", voltage);
|
||||
}
|
||||
if (have_current) {
|
||||
pos = buf_append_printf(buf, sizeof(buf), pos, " I=%.4fmA (~%.4fmA)", current * 1000.0f,
|
||||
calculated_current * 1000.0f);
|
||||
buf += str_sprintf(" I=%fmA (~%fmA)", current * 1000.0f, calculated_current * 1000.0f);
|
||||
}
|
||||
if (have_power) {
|
||||
pos = buf_append_printf(buf, sizeof(buf), pos, " P=%.4fW", power);
|
||||
buf += str_sprintf(" P=%fW", power);
|
||||
}
|
||||
if (energy != 0.0f) {
|
||||
buf_append_printf(buf, sizeof(buf), pos, " E=%.4fkWh (%u)", energy, cf_pulses);
|
||||
buf += str_sprintf(" E=%fkWh (%u)", energy, cf_pulses);
|
||||
}
|
||||
ESP_LOGVV(TAG, "%s", buf);
|
||||
ESP_LOGVV(TAG, "%s", buf.c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -127,9 +127,7 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
|
||||
this->min2_ = min2 = this->max2_ = max2 = this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 =
|
||||
this->max4_ = max4 = -1;
|
||||
|
||||
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f", min1 / 0.15, max1 / 0.15);
|
||||
this->cmd_ = buf;
|
||||
this->cmd_ = str_sprintf("detRangeCfg -1 %.0f %.0f", min1 / 0.15, max1 / 0.15);
|
||||
} else if (min3 < 0 || max3 < 0) {
|
||||
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
||||
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
||||
@@ -137,10 +135,7 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
|
||||
this->max2_ = max2 = round(max2 / 0.15) * 0.15;
|
||||
this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 = this->max4_ = max4 = -1;
|
||||
|
||||
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15,
|
||||
max2 / 0.15);
|
||||
this->cmd_ = buf;
|
||||
this->cmd_ = str_sprintf("detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15);
|
||||
} else if (min4 < 0 || max4 < 0) {
|
||||
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
||||
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
||||
@@ -150,10 +145,9 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
|
||||
this->max3_ = max3 = round(max3 / 0.15) * 0.15;
|
||||
this->min4_ = min4 = this->max4_ = max4 = -1;
|
||||
|
||||
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15,
|
||||
max2 / 0.15, min3 / 0.15, max3 / 0.15);
|
||||
this->cmd_ = buf;
|
||||
this->cmd_ = str_sprintf("detRangeCfg -1 "
|
||||
"%.0f %.0f %.0f %.0f %.0f %.0f",
|
||||
min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15);
|
||||
} else {
|
||||
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
||||
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
||||
@@ -164,10 +158,10 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
|
||||
this->min4_ = min4 = round(min4 / 0.15) * 0.15;
|
||||
this->max4_ = max4 = round(max4 / 0.15) * 0.15;
|
||||
|
||||
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15,
|
||||
min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15, min4 / 0.15, max4 / 0.15);
|
||||
this->cmd_ = buf;
|
||||
this->cmd_ = str_sprintf("detRangeCfg -1 "
|
||||
"%.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f",
|
||||
min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15, min4 / 0.15,
|
||||
max4 / 0.15);
|
||||
}
|
||||
|
||||
this->min1_ = min1;
|
||||
@@ -209,10 +203,7 @@ SetLatencyCommand::SetLatencyCommand(float delay_after_detection, float delay_af
|
||||
delay_after_disappear = std::round(delay_after_disappear / 0.025f) * 0.025f;
|
||||
this->delay_after_detection_ = clamp(delay_after_detection, 0.0f, 1638.375f);
|
||||
this->delay_after_disappear_ = clamp(delay_after_disappear, 0.0f, 1638.375f);
|
||||
// max 32: "setLatency "(11) + float(8) + " "(1) + float(8) + null, rounded to 32
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_);
|
||||
this->cmd_ = buf;
|
||||
this->cmd_ = str_sprintf("setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_);
|
||||
};
|
||||
|
||||
uint8_t SetLatencyCommand::on_message(std::string &message) {
|
||||
|
||||
@@ -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]; // "sensorCfgStart " (15) + "0/1" (1) + null = 17
|
||||
buf_append_printf(tmp_cmd, sizeof(tmp_cmd), 0, "sensorCfgStart %d", startup_mode);
|
||||
char tmp_cmd[20] = {0};
|
||||
sprintf(tmp_cmd, "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]; // "setSensitivity " (15) + "0-9" (1) + null = 17
|
||||
buf_append_printf(tmp_cmd, sizeof(tmp_cmd), 0, "setSensitivity %d", sensitivity);
|
||||
char tmp_cmd[20] = {0};
|
||||
sprintf(tmp_cmd, "setSensitivity %d", sensitivity);
|
||||
cmd_ = std::string(tmp_cmd);
|
||||
};
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
@@ -90,7 +90,9 @@ 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.StringRef, "event_type")], conf)
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.std_string, "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<StringRef> {
|
||||
class EventTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
EventTrigger(Event *event) {
|
||||
event->add_on_event_callback([this](StringRef event_type) { this->trigger(event_type); });
|
||||
event->add_on_event_callback([this](const std::string &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(StringRef(found));
|
||||
this->event_callback_.call(event_type);
|
||||
#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(StringRef event_type)> &&callback) {
|
||||
void Event::add_on_event_callback(std::function<void(const std::string &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(StringRef event_type)> &&callback);
|
||||
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
|
||||
|
||||
protected:
|
||||
LazyCallbackManager<void(StringRef event_type)> event_callback_;
|
||||
LazyCallbackManager<void(const std::string &event_type)> event_callback_;
|
||||
FixedVector<const char *> types_;
|
||||
|
||||
private:
|
||||
|
||||
@@ -160,7 +160,7 @@ void EZOSensor::loop() {
|
||||
this->commands_.pop_front();
|
||||
}
|
||||
|
||||
void EZOSensor::add_command_(const char *command, EzoCommandType command_type, uint16_t delay_ms) {
|
||||
void EZOSensor::add_command_(const std::string &command, EzoCommandType command_type, uint16_t delay_ms) {
|
||||
std::unique_ptr<EzoCommand> ezo_command(new EzoCommand);
|
||||
ezo_command->command = command;
|
||||
ezo_command->command_type = command_type;
|
||||
@@ -169,17 +169,13 @@ void EZOSensor::add_command_(const char *command, EzoCommandType command_type, u
|
||||
}
|
||||
|
||||
void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) {
|
||||
// max 21: "Cal,"(4) + type(4) + ","(1) + float(11) + null; use 24 for safety
|
||||
char payload[24];
|
||||
snprintf(payload, sizeof(payload), "Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
|
||||
std::string payload = str_sprintf("Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
|
||||
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
|
||||
}
|
||||
|
||||
void EZOSensor::set_address(uint8_t address) {
|
||||
if (address > 0 && address < 128) {
|
||||
// max 8: "I2C,"(4) + uint8(3) + null
|
||||
char payload[8];
|
||||
snprintf(payload, sizeof(payload), "I2C,%u", address);
|
||||
std::string payload = str_sprintf("I2C,%u", address);
|
||||
this->new_address_ = address;
|
||||
this->add_command_(payload, EzoCommandType::EZO_I2C);
|
||||
} else {
|
||||
@@ -198,9 +194,7 @@ void EZOSensor::get_slope() { this->add_command_("Slope,?", EzoCommandType::EZO_
|
||||
void EZOSensor::get_t() { this->add_command_("T,?", EzoCommandType::EZO_T); }
|
||||
|
||||
void EZOSensor::set_t(float value) {
|
||||
// max 14 bytes: "T,"(2) + float with "%0.2f" (up to 11 chars) + null(1); use 16 for alignment
|
||||
char payload[16];
|
||||
snprintf(payload, sizeof(payload), "T,%0.2f", value);
|
||||
std::string payload = str_sprintf("T,%0.2f", value);
|
||||
this->add_command_(payload, EzoCommandType::EZO_T);
|
||||
}
|
||||
|
||||
@@ -221,9 +215,7 @@ void EZOSensor::set_calibration_point_high(float value) {
|
||||
}
|
||||
|
||||
void EZOSensor::set_calibration_generic(float value) {
|
||||
// exact 16 bytes: "Cal," (4) + float with "%0.2f" (up to 11 chars, e.g. "-9999999.99") + null (1) = 16
|
||||
char payload[16];
|
||||
snprintf(payload, sizeof(payload), "Cal,%0.2f", value);
|
||||
std::string payload = str_sprintf("Cal,%0.2f", value);
|
||||
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
|
||||
}
|
||||
|
||||
@@ -231,11 +223,13 @@ void EZOSensor::clear_calibration() { this->add_command_("Cal,clear", EzoCommand
|
||||
|
||||
void EZOSensor::get_led_state() { this->add_command_("L,?", EzoCommandType::EZO_LED); }
|
||||
|
||||
void EZOSensor::set_led_state(bool on) { this->add_command_(on ? "L,1" : "L,0", EzoCommandType::EZO_LED); }
|
||||
|
||||
void EZOSensor::send_custom(const std::string &to_send) {
|
||||
this->add_command_(to_send.c_str(), EzoCommandType::EZO_CUSTOM);
|
||||
void EZOSensor::set_led_state(bool on) {
|
||||
std::string to_send = "L,";
|
||||
to_send += on ? "1" : "0";
|
||||
this->add_command_(to_send, EzoCommandType::EZO_LED);
|
||||
}
|
||||
|
||||
void EZOSensor::send_custom(const std::string &to_send) { this->add_command_(to_send, EzoCommandType::EZO_CUSTOM); }
|
||||
|
||||
} // namespace ezo
|
||||
} // namespace esphome
|
||||
|
||||
@@ -92,7 +92,7 @@ class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2
|
||||
std::deque<std::unique_ptr<EzoCommand>> commands_;
|
||||
int new_address_;
|
||||
|
||||
void add_command_(const char *command, EzoCommandType command_type, uint16_t delay_ms = 300);
|
||||
void add_command_(const std::string &command, EzoCommandType command_type, uint16_t delay_ms = 300);
|
||||
|
||||
void set_calibration_point_(EzoCalibrationType type, float value);
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ FanSpeedSetTrigger = fan_ns.class_(
|
||||
"FanSpeedSetTrigger", automation.Trigger.template(cg.int_)
|
||||
)
|
||||
FanPresetSetTrigger = fan_ns.class_(
|
||||
"FanPresetSetTrigger", automation.Trigger.template(cg.StringRef)
|
||||
"FanPresetSetTrigger", automation.Trigger.template(cg.std_string)
|
||||
)
|
||||
|
||||
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.StringRef, "x")], conf)
|
||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
||||
|
||||
|
||||
async def register_fan(var, config):
|
||||
|
||||
@@ -208,7 +208,7 @@ class FanSpeedSetTrigger : public Trigger<int> {
|
||||
int last_speed_;
|
||||
};
|
||||
|
||||
class FanPresetSetTrigger : public Trigger<StringRef> {
|
||||
class FanPresetSetTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
FanPresetSetTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
@@ -216,7 +216,7 @@ class FanPresetSetTrigger : public Trigger<StringRef> {
|
||||
auto should_trigger = preset_mode != this->last_preset_mode_;
|
||||
this->last_preset_mode_ = preset_mode;
|
||||
if (should_trigger) {
|
||||
this->trigger(preset_mode);
|
||||
this->trigger(std::string(preset_mode));
|
||||
}
|
||||
});
|
||||
this->last_preset_mode_ = state->get_preset_mode();
|
||||
|
||||
@@ -163,10 +163,9 @@ bool GDK101Component::read_fw_version_(uint8_t *data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// max 8: "255.255" (7 chars) + null
|
||||
char buf[8];
|
||||
snprintf(buf, sizeof(buf), "%d.%d", data[0], data[1]);
|
||||
this->fw_version_text_sensor_->publish_state(buf);
|
||||
const std::string fw_version_str = str_sprintf("%d.%d", data[0], data[1]);
|
||||
|
||||
this->fw_version_text_sensor_->publish_state(fw_version_str);
|
||||
}
|
||||
#endif // USE_TEXT_SENSOR
|
||||
return true;
|
||||
|
||||
@@ -224,9 +224,12 @@ class MipiSpi : public display::Display,
|
||||
this->madctl_ & MADCTL_BGR ? "BGR" : "RGB", DISPLAYPIXEL * 8, IS_BIG_ENDIAN ? "Big" : "Little");
|
||||
if (this->brightness_.has_value())
|
||||
esph_log_config(TAG, " Brightness: %u", this->brightness_.value());
|
||||
log_pin(TAG, " CS Pin: ", this->cs_);
|
||||
log_pin(TAG, " Reset Pin: ", this->reset_pin_);
|
||||
log_pin(TAG, " DC Pin: ", this->dc_pin_);
|
||||
if (this->cs_ != nullptr)
|
||||
esph_log_config(TAG, " CS Pin: %s", this->cs_->dump_summary().c_str());
|
||||
if (this->reset_pin_ != nullptr)
|
||||
esph_log_config(TAG, " Reset Pin: %s", this->reset_pin_->dump_summary().c_str());
|
||||
if (this->dc_pin_ != nullptr)
|
||||
esph_log_config(TAG, " DC Pin: %s", this->dc_pin_->dump_summary().c_str());
|
||||
esph_log_config(TAG,
|
||||
" SPI Mode: %d\n"
|
||||
" SPI Data rate: %dMHz\n"
|
||||
|
||||
@@ -26,3 +26,5 @@ ST7789V.extend(
|
||||
reset_pin=40,
|
||||
invert_colors=True,
|
||||
)
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -105,3 +105,6 @@ CO5300 = DriverChip(
|
||||
(WCE, 0x00),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -1,45 +1,10 @@
|
||||
from .ili import ILI9341, ILI9342, ST7789V
|
||||
from .ili import ILI9341
|
||||
|
||||
ILI9341.extend(
|
||||
# ESP32-2432S028 CYD board with Micro USB, has ILI9341 controller
|
||||
"ESP32-2432S028",
|
||||
data_rate="40MHz",
|
||||
cs_pin={"number": 15, "ignore_strapping_warning": True},
|
||||
dc_pin={"number": 2, "ignore_strapping_warning": True},
|
||||
cs_pin=15,
|
||||
dc_pin=2,
|
||||
)
|
||||
|
||||
ST7789V.extend(
|
||||
# ESP32-2432S028 CYD board with USB C + Micro USB, has ST7789V controller
|
||||
"ESP32-2432S028-7789",
|
||||
data_rate="40MHz",
|
||||
cs_pin={"number": 15, "ignore_strapping_warning": True},
|
||||
dc_pin={"number": 2, "ignore_strapping_warning": True},
|
||||
)
|
||||
|
||||
# fmt: off
|
||||
|
||||
ILI9342.extend(
|
||||
# ESP32-2432S028 CYD board with USB C + Micro USB, has ILI9342 controller
|
||||
"ESP32-2432S028-9342",
|
||||
data_rate="40MHz",
|
||||
cs_pin={"number": 15, "ignore_strapping_warning": True},
|
||||
dc_pin={"number": 2, "ignore_strapping_warning": True},
|
||||
initsequence=(
|
||||
(0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02), # Power Control A
|
||||
(0xCF, 0x00, 0xC1, 0x30), # Power Control B
|
||||
(0xE8, 0x85, 0x00, 0x78), # Driver timing control A
|
||||
(0xEA, 0x00, 0x00), # Driver timing control B
|
||||
(0xED, 0x64, 0x03, 0x12, 0x81), # Power on sequence control
|
||||
(0xF7, 0x20), # Pump ratio control
|
||||
(0xC0, 0x23), # Power Control 1
|
||||
(0xC1, 0x10), # Power Control 2
|
||||
(0xC5, 0x3E, 0x28), # VCOM Control 1
|
||||
(0xC7, 0x86), # VCOM Control 2
|
||||
(0xB1, 0x00, 0x1B), # Frame Rate Control
|
||||
(0xB6, 0x0A, 0xA2, 0x27, 0x00), # Display Function Control
|
||||
(0xF2, 0x00), # Enable 3G
|
||||
(0x26, 0x01), # Gamma Set
|
||||
(0xE0, 0x00, 0x0C, 0x11, 0x04, 0x11, 0x08, 0x37, 0x89, 0x4C, 0x06, 0x0C, 0x0A, 0x2E, 0x34, 0x0F), # Positive Gamma Correction
|
||||
(0xE1, 0x00, 0x0B, 0x11, 0x05, 0x13, 0x09, 0x33, 0x67, 0x48, 0x07, 0x0E, 0x0B, 0x23, 0x33, 0x0F), # Negative Gamma Correction
|
||||
)
|
||||
)
|
||||
models = {}
|
||||
|
||||
@@ -148,34 +148,6 @@ ILI9341 = DriverChip(
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# fmt: off
|
||||
|
||||
ILI9342 = DriverChip(
|
||||
"ILI9342",
|
||||
width=320,
|
||||
height=240,
|
||||
mirror_x=True,
|
||||
initsequence=(
|
||||
(0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02), # Power Control A
|
||||
(0xCF, 0x00, 0xC1, 0x30), # Power Control B
|
||||
(0xE8, 0x85, 0x00, 0x78), # Driver timing control A
|
||||
(0xEA, 0x00, 0x00), # Driver timing control B
|
||||
(0xED, 0x64, 0x03, 0x12, 0x81), # Power on sequence control
|
||||
(0xF7, 0x20), # Pump ratio control
|
||||
(0xC0, 0x23), # Power Control 1
|
||||
(0xC1, 0x10), # Power Control 2
|
||||
(0xC5, 0x3E, 0x28), # VCOM Control 1
|
||||
(0xC7, 0x86), # VCOM Control 2
|
||||
(0xB1, 0x00, 0x1B), # Frame Rate Control
|
||||
(0xB6, 0x0A, 0xA2, 0x27, 0x00), # Display Function Control
|
||||
(0xF2, 0x00), # Enable 3G
|
||||
(0x26, 0x01), # Gamma Set
|
||||
(0xE0, 0x0F, 0x1F, 0x1C, 0x0C, 0x0F, 0x08, 0x48, 0x98, 0x37, 0x0A, 0x13, 0x04, 0x11, 0x0D, 0x00), # Positive Gamma
|
||||
(0xE1, 0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75, 0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00), # Negative Gamma
|
||||
),
|
||||
)
|
||||
|
||||
# M5Stack Core2 uses ILI9341 chip - mirror_x disabled for correct orientation
|
||||
ILI9341.extend(
|
||||
"M5CORE2",
|
||||
@@ -786,3 +758,5 @@ ST7796.extend(
|
||||
dc_pin=0,
|
||||
invert_colors=True,
|
||||
)
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -588,3 +588,5 @@ DriverChip(
|
||||
(0x29, 0x00),
|
||||
),
|
||||
)
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -11,3 +11,5 @@ ST7789V.extend(
|
||||
dc_pin=21,
|
||||
reset_pin=18,
|
||||
)
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -56,3 +56,5 @@ ST7796.extend(
|
||||
backlight_pin=48,
|
||||
invert_colors=True,
|
||||
)
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -18,7 +18,7 @@ bool CustomMQTTDevice::publish(const std::string &topic, float value, int8_t num
|
||||
}
|
||||
bool CustomMQTTDevice::publish(const std::string &topic, int value) {
|
||||
char buffer[24];
|
||||
int len = snprintf(buffer, sizeof(buffer), "%d", value);
|
||||
size_t len = buf_append_printf(buffer, sizeof(buffer), 0, "%d", value);
|
||||
return global_mqtt_client->publish(topic, buffer, len);
|
||||
}
|
||||
bool CustomMQTTDevice::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos, bool retain) {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <utility>
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/version.h"
|
||||
@@ -67,13 +66,10 @@ void MQTTClientComponent::setup() {
|
||||
"esphome/discover", [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); },
|
||||
2);
|
||||
|
||||
// Format topic on stack - subscribe() copies it
|
||||
// "esphome/ping/" (13) + name (ESPHOME_DEVICE_NAME_MAX_LEN) + null (1)
|
||||
constexpr size_t ping_topic_buffer_size = 13 + ESPHOME_DEVICE_NAME_MAX_LEN + 1;
|
||||
char ping_topic[ping_topic_buffer_size];
|
||||
buf_append_printf(ping_topic, sizeof(ping_topic), 0, "esphome/ping/%s", App.get_name().c_str());
|
||||
std::string topic = "esphome/ping/";
|
||||
topic.append(App.get_name());
|
||||
this->subscribe(
|
||||
ping_topic, [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2);
|
||||
topic, [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2);
|
||||
}
|
||||
|
||||
if (this->enable_on_boot_) {
|
||||
@@ -85,11 +81,8 @@ void MQTTClientComponent::send_device_info_() {
|
||||
if (!this->is_connected() or !this->is_discovery_ip_enabled()) {
|
||||
return;
|
||||
}
|
||||
// Format topic on stack to avoid heap allocation
|
||||
// "esphome/discover/" (17) + name (ESPHOME_DEVICE_NAME_MAX_LEN) + null (1)
|
||||
constexpr size_t topic_buffer_size = 17 + ESPHOME_DEVICE_NAME_MAX_LEN + 1;
|
||||
char topic[topic_buffer_size];
|
||||
buf_append_printf(topic, sizeof(topic), 0, "esphome/discover/%s", App.get_name().c_str());
|
||||
std::string topic = "esphome/discover/";
|
||||
topic.append(App.get_name());
|
||||
|
||||
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
this->publish_json(
|
||||
@@ -98,7 +91,17 @@ void MQTTClientComponent::send_device_info_() {
|
||||
uint8_t index = 0;
|
||||
for (auto &ip : network::get_ip_addresses()) {
|
||||
if (ip.is_set()) {
|
||||
root["ip" + (index == 0 ? "" : esphome::to_string(index))] = ip.str();
|
||||
char key[8]; // "ip" + up to 3 digits + null
|
||||
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
if (index == 0) {
|
||||
key[0] = 'i';
|
||||
key[1] = 'p';
|
||||
key[2] = '\0';
|
||||
} else {
|
||||
buf_append_printf(key, sizeof(key), 0, "ip%u", index);
|
||||
}
|
||||
ip.str_to(ip_buf);
|
||||
root[key] = ip_buf;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
@@ -507,49 +510,39 @@ bool MQTTClientComponent::publish(const std::string &topic, const std::string &p
|
||||
|
||||
bool MQTTClientComponent::publish(const std::string &topic, const char *payload, size_t payload_length, uint8_t qos,
|
||||
bool retain) {
|
||||
return this->publish(topic.c_str(), payload, payload_length, qos, retain);
|
||||
return publish({.topic = topic, .payload = std::string(payload, payload_length), .qos = qos, .retain = retain});
|
||||
}
|
||||
|
||||
bool MQTTClientComponent::publish(const MQTTMessage &message) {
|
||||
return this->publish(message.topic.c_str(), message.payload.c_str(), message.payload.length(), message.qos,
|
||||
message.retain);
|
||||
}
|
||||
bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos,
|
||||
bool retain) {
|
||||
return this->publish_json(topic.c_str(), f, qos, retain);
|
||||
}
|
||||
|
||||
bool MQTTClientComponent::publish(const char *topic, const char *payload, size_t payload_length, uint8_t qos,
|
||||
bool retain) {
|
||||
if (!this->is_connected()) {
|
||||
// critical components will re-transmit their messages
|
||||
return false;
|
||||
}
|
||||
size_t topic_len = strlen(topic);
|
||||
bool logging_topic = (topic_len == this->log_message_.topic.size()) &&
|
||||
(memcmp(this->log_message_.topic.c_str(), topic, topic_len) == 0);
|
||||
bool ret = this->mqtt_backend_.publish(topic, payload, payload_length, qos, retain);
|
||||
bool logging_topic = this->log_message_.topic == message.topic;
|
||||
bool ret = this->mqtt_backend_.publish(message);
|
||||
delay(0);
|
||||
if (!ret && !logging_topic && this->is_connected()) {
|
||||
delay(0);
|
||||
ret = this->mqtt_backend_.publish(topic, payload, payload_length, qos, retain);
|
||||
ret = this->mqtt_backend_.publish(message);
|
||||
delay(0);
|
||||
}
|
||||
|
||||
if (!logging_topic) {
|
||||
if (ret) {
|
||||
ESP_LOGV(TAG, "Publish(topic='%s' retain=%d qos=%d)", topic, retain, qos);
|
||||
ESP_LOGVV(TAG, "Publish payload (len=%u): '%.*s'", payload_length, static_cast<int>(payload_length), payload);
|
||||
ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d qos=%d)", message.topic.c_str(), message.payload.c_str(),
|
||||
message.retain, message.qos);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). Will retry", topic, payload_length);
|
||||
ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). Will retry", message.topic.c_str(),
|
||||
message.payload.length());
|
||||
this->status_momentary_warning("publish", 1000);
|
||||
}
|
||||
}
|
||||
return ret != 0;
|
||||
}
|
||||
|
||||
bool MQTTClientComponent::publish_json(const char *topic, const json::json_build_t &f, uint8_t qos, bool retain) {
|
||||
bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos,
|
||||
bool retain) {
|
||||
std::string message = json::build_json(f);
|
||||
return this->publish(topic, message.c_str(), message.length(), qos, retain);
|
||||
return this->publish(topic, message, qos, retain);
|
||||
}
|
||||
|
||||
void MQTTClientComponent::enable() {
|
||||
|
||||
@@ -229,9 +229,6 @@ class MQTTClientComponent : public Component
|
||||
bool publish(const std::string &topic, const char *payload, size_t payload_length, uint8_t qos = 0,
|
||||
bool retain = false);
|
||||
|
||||
/// Publish directly without creating MQTTMessage (avoids heap allocation for topic)
|
||||
bool publish(const char *topic, const char *payload, size_t payload_length, uint8_t qos = 0, bool retain = false);
|
||||
|
||||
/** Construct and send a JSON MQTT message.
|
||||
*
|
||||
* @param topic The topic.
|
||||
@@ -240,9 +237,6 @@ class MQTTClientComponent : public Component
|
||||
*/
|
||||
bool publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos = 0, bool retain = false);
|
||||
|
||||
/// Publish JSON directly without heap allocation for topic
|
||||
bool publish_json(const char *topic, const json::json_build_t &f, uint8_t qos = 0, bool retain = false);
|
||||
|
||||
/// Setup the MQTT client, registering a bunch of callbacks and attempting to connect.
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
@@ -190,27 +190,37 @@ bool MQTTComponent::send_discovery_() {
|
||||
StringRef object_id = this->get_default_object_id_to_(object_id_buf);
|
||||
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
|
||||
char friendly_name_hash[9];
|
||||
snprintf(friendly_name_hash, sizeof(friendly_name_hash), "%08" PRIx32, fnv1_hash(this->friendly_name_()));
|
||||
buf_append_printf(friendly_name_hash, sizeof(friendly_name_hash), 0, "%08" PRIx32,
|
||||
fnv1_hash(this->friendly_name_()));
|
||||
// Format: mac-component_type-hash (e.g. "aabbccddeeff-sensor-12345678")
|
||||
// MAC (12) + "-" (1) + domain (max 20) + "-" (1) + hash (8) + null (1) = 43
|
||||
char unique_id[MAC_ADDRESS_BUFFER_SIZE + ESPHOME_DOMAIN_MAX_LEN + 11];
|
||||
char mac_buf[MAC_ADDRESS_BUFFER_SIZE];
|
||||
get_mac_address_into_buffer(mac_buf);
|
||||
snprintf(unique_id, sizeof(unique_id), "%s-%s-%s", mac_buf, this->component_type(), friendly_name_hash);
|
||||
buf_append_printf(unique_id, sizeof(unique_id), 0, "%s-%s-%s", mac_buf, this->component_type(),
|
||||
friendly_name_hash);
|
||||
root[MQTT_UNIQUE_ID] = unique_id;
|
||||
} else {
|
||||
// default to almost-unique ID. It's a hack but the only way to get that
|
||||
// gorgeous device registry view.
|
||||
root[MQTT_UNIQUE_ID] = "ESP" + std::string(this->component_type()) + object_id.c_str();
|
||||
// "ESP" (3) + component_type (max 20) + object_id (max 128) + null
|
||||
char unique_id_buf[3 + MQTT_COMPONENT_TYPE_MAX_LEN + OBJECT_ID_MAX_LEN + 1];
|
||||
buf_append_printf(unique_id_buf, sizeof(unique_id_buf), 0, "ESP%s%s", this->component_type(),
|
||||
object_id.c_str());
|
||||
root[MQTT_UNIQUE_ID] = unique_id_buf;
|
||||
}
|
||||
|
||||
const std::string &node_name = App.get_name();
|
||||
if (discovery_info.object_id_generator == MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR)
|
||||
root[MQTT_OBJECT_ID] = node_name + "_" + object_id.c_str();
|
||||
if (discovery_info.object_id_generator == MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR) {
|
||||
// node_name (max 31) + "_" (1) + object_id (max 128) + null
|
||||
char object_id_full[ESPHOME_DEVICE_NAME_MAX_LEN + 1 + OBJECT_ID_MAX_LEN + 1];
|
||||
buf_append_printf(object_id_full, sizeof(object_id_full), 0, "%s_%s", node_name.c_str(), object_id.c_str());
|
||||
root[MQTT_OBJECT_ID] = object_id_full;
|
||||
}
|
||||
|
||||
const std::string &friendly_name_ref = App.get_friendly_name();
|
||||
const std::string &node_friendly_name = friendly_name_ref.empty() ? node_name : friendly_name_ref;
|
||||
std::string node_area = App.get_area();
|
||||
const char *node_area = App.get_area();
|
||||
|
||||
JsonObject device_info = root[MQTT_DEVICE].to<JsonObject>();
|
||||
char mac[MAC_ADDRESS_BUFFER_SIZE];
|
||||
@@ -221,18 +231,29 @@ bool MQTTComponent::send_discovery_() {
|
||||
device_info[MQTT_DEVICE_SW_VERSION] = ESPHOME_PROJECT_VERSION " (ESPHome " ESPHOME_VERSION ")";
|
||||
const char *model = std::strchr(ESPHOME_PROJECT_NAME, '.');
|
||||
device_info[MQTT_DEVICE_MODEL] = model == nullptr ? ESPHOME_BOARD : model + 1;
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] =
|
||||
model == nullptr ? ESPHOME_PROJECT_NAME : std::string(ESPHOME_PROJECT_NAME, model - ESPHOME_PROJECT_NAME);
|
||||
if (model == nullptr) {
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] = ESPHOME_PROJECT_NAME;
|
||||
} else {
|
||||
// Extract manufacturer (part before '.') using stack buffer to avoid heap allocation
|
||||
// memcpy is used instead of strncpy since we know the exact length and strncpy
|
||||
// would still require manual null-termination
|
||||
char manufacturer[sizeof(ESPHOME_PROJECT_NAME)];
|
||||
size_t len = model - ESPHOME_PROJECT_NAME;
|
||||
memcpy(manufacturer, ESPHOME_PROJECT_NAME, len);
|
||||
manufacturer[len] = '\0';
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] = manufacturer;
|
||||
}
|
||||
#else
|
||||
static const char ver_fmt[] PROGMEM = ESPHOME_VERSION " (config hash 0x%08" PRIx32 ")";
|
||||
// Buffer sized for format string expansion: ~4 bytes net growth from format specifier to 8 hex digits, plus
|
||||
// safety margin
|
||||
char version_buf[sizeof(ver_fmt) + 8];
|
||||
#ifdef USE_ESP8266
|
||||
char fmt_buf[sizeof(ver_fmt)];
|
||||
strcpy_P(fmt_buf, ver_fmt);
|
||||
const char *fmt = fmt_buf;
|
||||
snprintf_P(version_buf, sizeof(version_buf), ver_fmt, App.get_config_hash());
|
||||
#else
|
||||
const char *fmt = ver_fmt;
|
||||
snprintf(version_buf, sizeof(version_buf), ver_fmt, App.get_config_hash());
|
||||
#endif
|
||||
device_info[MQTT_DEVICE_SW_VERSION] = str_sprintf(fmt, App.get_config_hash());
|
||||
device_info[MQTT_DEVICE_SW_VERSION] = version_buf;
|
||||
device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD;
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] = "Espressif";
|
||||
@@ -246,7 +267,7 @@ bool MQTTComponent::send_discovery_() {
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] = "Host";
|
||||
#endif
|
||||
#endif
|
||||
if (!node_area.empty()) {
|
||||
if (node_area[0] != '\0') {
|
||||
device_info[MQTT_DEVICE_SUGGESTED_AREA] = node_area;
|
||||
}
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ bool MQTTFanComponent::publish_state() {
|
||||
auto traits = this->state_->get_traits();
|
||||
if (traits.supports_speed()) {
|
||||
char buf[12];
|
||||
int len = snprintf(buf, sizeof(buf), "%d", this->state_->speed);
|
||||
size_t len = buf_append_printf(buf, sizeof(buf), 0, "%d", this->state_->speed);
|
||||
bool success = this->publish(this->get_speed_level_state_topic(), buf, len);
|
||||
failed = failed || !success;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ bool MQTTNumberComponent::send_initial_state() {
|
||||
}
|
||||
bool MQTTNumberComponent::publish_state(float value) {
|
||||
char buffer[64];
|
||||
snprintf(buffer, sizeof(buffer), "%f", value);
|
||||
buf_append_printf(buffer, sizeof(buffer), 0, "%f", value);
|
||||
return this->publish(this->get_state_topic_(), buffer);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace pipsolar {
|
||||
static const char *const TAG = "pipsolar.output";
|
||||
|
||||
void PipsolarOutput::write_state(float state) {
|
||||
char tmp[16];
|
||||
char tmp[10];
|
||||
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.StringRef, cg.size_t),
|
||||
automation.Trigger.template(cg.std_string, 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.StringRef, "x"), (cg.size_t, "i")], conf
|
||||
trigger, [(cg.std_string, "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<StringRef, size_t> {
|
||||
class SelectStateTrigger : public Trigger<std::string, size_t> {
|
||||
public:
|
||||
explicit SelectStateTrigger(Select *parent) : parent_(parent) {
|
||||
parent->add_on_state_callback(
|
||||
[this](size_t index) { this->trigger(StringRef(this->parent_->option_at(index)), index); });
|
||||
[this](size_t index) { this->trigger(std::string(this->parent_->option_at(index)), index); });
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "sim800l.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cstring>
|
||||
|
||||
@@ -51,8 +50,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]; // "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_);
|
||||
char delete_cmd[20];
|
||||
sprintf(delete_cmd, "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.StringRef, "x")], config[CONF_SET_ACTION]
|
||||
var.get_set_trigger(), [(cg.std_string, "x")], config[CONF_SET_ACTION]
|
||||
)
|
||||
|
||||
@@ -41,7 +41,7 @@ void TemplateSelect::update() {
|
||||
}
|
||||
|
||||
void TemplateSelect::control(size_t index) {
|
||||
this->set_trigger_->trigger(StringRef(this->option_at(index)));
|
||||
this->set_trigger_->trigger(std::string(this->option_at(index)));
|
||||
|
||||
if (this->optimistic_)
|
||||
this->publish_state(index);
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#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_ {
|
||||
@@ -18,7 +17,7 @@ class TemplateSelect final : public select::Select, public PollingComponent {
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
Trigger<StringRef> *get_set_trigger() const { return this->set_trigger_; }
|
||||
Trigger<std::string> *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; }
|
||||
@@ -28,7 +27,7 @@ class TemplateSelect final : public select::Select, public PollingComponent {
|
||||
bool optimistic_ = false;
|
||||
size_t initial_option_index_{0};
|
||||
bool restore_value_ = false;
|
||||
Trigger<StringRef> *set_trigger_ = new Trigger<StringRef>();
|
||||
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
|
||||
TemplateLambda<std::string> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "toshiba.h"
|
||||
#include "esphome/components/remote_base/toshiba_ac_protocol.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
@@ -428,17 +427,10 @@ void ToshibaClimate::setup() {
|
||||
// Never send nan to HA
|
||||
if (std::isnan(this->target_temperature))
|
||||
this->target_temperature = 24;
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
// Log final state for debugging HA errors
|
||||
const char *fan_mode_str = "NONE";
|
||||
char fan_mode_buf[4]; // max 3 digits for fan mode enum + null
|
||||
if (this->fan_mode.has_value()) {
|
||||
buf_append_printf(fan_mode_buf, sizeof(fan_mode_buf), 0, "%d", static_cast<int>(this->fan_mode.value()));
|
||||
fan_mode_str = fan_mode_buf;
|
||||
}
|
||||
ESP_LOGV(TAG, "Setup complete - Mode: %d, Fan: %s, Swing: %d, Temp: %.1f", static_cast<int>(this->mode), fan_mode_str,
|
||||
ESP_LOGV(TAG, "Setup complete - Mode: %d, Fan: %s, Swing: %d, Temp: %.1f", static_cast<int>(this->mode),
|
||||
this->fan_mode.has_value() ? std::to_string(static_cast<int>(this->fan_mode.value())).c_str() : "NONE",
|
||||
static_cast<int>(this->swing_mode), this->target_temperature);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ToshibaClimate::transmit_state() {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "wl_134.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
@@ -79,8 +78,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]; // "%03d" (3) + "%012" PRId64 (12) + null = 16 max
|
||||
buf_append_printf(buf, sizeof(buf), 0, "%03d%012" PRId64, reading.country, reading.id);
|
||||
char buf[20];
|
||||
sprintf(buf, "%03d%012lld", reading.country, reading.id);
|
||||
this->publish_state(buf);
|
||||
if (this->do_reset_) {
|
||||
this->set_timeout(1000, [this]() { this->publish_state(""); });
|
||||
|
||||
@@ -403,9 +403,7 @@ class Scheduler {
|
||||
for (size_t i = 0; i < remaining; i++) {
|
||||
this->defer_queue_[i] = std::move(this->defer_queue_[this->defer_queue_front_ + i]);
|
||||
}
|
||||
// Use erase() instead of resize() to avoid instantiating _M_default_append
|
||||
// (saves ~156 bytes flash). Erasing from the end is O(1) - no shifting needed.
|
||||
this->defer_queue_.erase(this->defer_queue_.begin() + remaining, this->defer_queue_.end());
|
||||
this->defer_queue_.resize(remaining);
|
||||
}
|
||||
this->defer_queue_front_ = 0;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,6 @@ 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); }
|
||||
|
||||
@@ -81,32 +80,6 @@ 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_;
|
||||
@@ -187,43 +160,6 @@ 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,4 +44,3 @@ 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")
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
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);
|
||||
@@ -1,143 +0,0 @@
|
||||
"""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}")
|
||||
@@ -34,7 +34,6 @@ from esphome.__main__ import (
|
||||
has_non_ip_address,
|
||||
has_resolvable_address,
|
||||
mqtt_get_ip,
|
||||
run_esphome,
|
||||
run_miniterm,
|
||||
show_logs,
|
||||
upload_program,
|
||||
@@ -1989,7 +1988,7 @@ esp32:
|
||||
clean_output = strip_ansi_codes(captured.out)
|
||||
|
||||
assert "test-device_123.yaml" in clean_output
|
||||
assert "Processing" in clean_output
|
||||
assert "Updating" in clean_output
|
||||
assert "SUCCESS" in clean_output
|
||||
assert "SUMMARY" in clean_output
|
||||
|
||||
@@ -3173,66 +3172,3 @@ def test_run_miniterm_buffer_limit_prevents_unbounded_growth() -> None:
|
||||
x_count = printed_line.count("X")
|
||||
assert x_count < 150, f"Expected truncation but got {x_count} X's"
|
||||
assert x_count == 95, f"Expected 95 X's after truncation but got {x_count}"
|
||||
|
||||
|
||||
def test_run_esphome_multiple_configs_with_secrets(
|
||||
tmp_path: Path,
|
||||
mock_run_external_process: Mock,
|
||||
capfd: CaptureFixture[str],
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test run_esphome with multiple configs and secrets file.
|
||||
|
||||
Verifies:
|
||||
- Multiple configs use subprocess isolation
|
||||
- Secrets files are skipped with warning
|
||||
- Secrets files don't appear in summary
|
||||
"""
|
||||
# Create two config files and a secrets file
|
||||
yaml_file1 = tmp_path / "device1.yaml"
|
||||
yaml_file1.write_text("""
|
||||
esphome:
|
||||
name: device1
|
||||
|
||||
esp32:
|
||||
board: nodemcu-32s
|
||||
""")
|
||||
yaml_file2 = tmp_path / "device2.yaml"
|
||||
yaml_file2.write_text("""
|
||||
esphome:
|
||||
name: device2
|
||||
|
||||
esp32:
|
||||
board: nodemcu-32s
|
||||
""")
|
||||
secrets_file = tmp_path / "secrets.yaml"
|
||||
secrets_file.write_text("wifi_password: secret123\n")
|
||||
|
||||
setup_core(tmp_path=tmp_path)
|
||||
mock_run_external_process.return_value = 0
|
||||
|
||||
# run_esphome expects argv[0] to be the program name (gets sliced off by parse_args)
|
||||
with caplog.at_level(logging.WARNING):
|
||||
result = run_esphome(
|
||||
["esphome", "compile", str(yaml_file1), str(secrets_file), str(yaml_file2)]
|
||||
)
|
||||
|
||||
assert result == 0
|
||||
|
||||
# Check secrets file was skipped with warning
|
||||
assert "Skipping secrets file" in caplog.text
|
||||
assert "secrets.yaml" in caplog.text
|
||||
|
||||
captured = capfd.readouterr()
|
||||
clean_output = strip_ansi_codes(captured.out)
|
||||
|
||||
# Both config files should be processed
|
||||
assert "device1.yaml" in clean_output
|
||||
assert "device2.yaml" in clean_output
|
||||
assert "SUMMARY" in clean_output
|
||||
|
||||
# Secrets should not appear in summary
|
||||
summary_section = (
|
||||
clean_output.split("SUMMARY")[1] if "SUMMARY" in clean_output else ""
|
||||
)
|
||||
assert "secrets.yaml" not in summary_section
|
||||
|
||||
Reference in New Issue
Block a user