Compare commits

..

15 Commits

Author SHA1 Message Date
J. Nick Koston
8151f29163 Merge branch 'dev' into combine_logs 2026-01-04 16:11:31 -10:00
J. Nick Koston
43d836a7d3 Merge branch 'dev' into combine_logs 2026-01-04 16:11:06 -10:00
J. Nick Koston
69f431b053 Merge branch 'dev' into combine_logs 2026-01-04 14:59:30 -10:00
J. Nick Koston
d4baaedd07 Merge branch 'dev' into combine_logs 2026-01-04 13:14:18 -10:00
J. Nick Koston
f22dec6cf1 Merge branch 'dev' into combine_logs 2026-01-04 13:07:22 -10:00
J. Nick Koston
f537632599 cleanups 2026-01-04 12:04:00 -10:00
J. Nick Koston
8c7dfa0c62 tweaks 2026-01-04 12:01:28 -10:00
J. Nick Koston
0dbf40dcdf Merge branch 'dev' into combine_logs 2026-01-04 11:57:00 -10:00
J. Nick Koston
d23c6a833b Merge branch 'dev' into combine_logs 2026-01-03 16:05:53 -10:00
J. Nick Koston
48ac8aa75b Merge branch 'dev' into combine_logs 2026-01-03 12:55:16 -10:00
J. Nick Koston
0058779481 Merge remote-tracking branch 'upstream/dev' into combine_logs
# Conflicts:
#	esphome/components/hlw8012/hlw8012.cpp
#	esphome/components/sonoff_d1/sonoff_d1.cpp
#	esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp
2026-01-02 16:48:10 -10:00
J. Nick Koston
5bb241e42d Combine logs 2025-12-29 23:07:36 -10:00
J. Nick Koston
28d4b6378b Combine logs 2025-12-29 23:03:40 -10:00
J. Nick Koston
eea8cdb543 Combine logs 2025-12-29 22:55:08 -10:00
J. Nick Koston
70496a4c20 Combine logs 2025-12-29 22:51:05 -10:00
312 changed files with 7796 additions and 15506 deletions

View File

@@ -1 +1 @@
191a0e6ab5842d153dd77a2023bc5742f9d4333c334de8d81b57f2b8d4d4b65e
94557f94be073390342833aff12ef8676a8b597db5fa770a5a1232e9425cb48f

View File

@@ -135,7 +135,7 @@ esphome/components/display_menu_base/* @numo68
esphome/components/dps310/* @kbx81
esphome/components/ds1307/* @badbadc0ffee
esphome/components/ds2484/* @mrk-its
esphome/components/dsmr/* @glmnet @PolarGoose @zuidwijk
esphome/components/dsmr/* @glmnet @zuidwijk
esphome/components/duty_time/* @dudanov
esphome/components/ee895/* @Stock-M
esphome/components/ektf2232/touchscreen/* @jesserockz
@@ -395,7 +395,6 @@ esphome/components/radon_eye_rd200/* @jeffeb3
esphome/components/rc522/* @glmnet
esphome/components/rc522_i2c/* @glmnet
esphome/components/rc522_spi/* @glmnet
esphome/components/rd03d/* @jasstrong
esphome/components/resampler/speaker/* @kahrendt
esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz

View File

@@ -431,33 +431,25 @@ def run_miniterm(config: ConfigType, port: str, args) -> int:
while tries < 5:
try:
with ser:
buffer = b""
ser.timeout = 0.1 # 100ms timeout for non-blocking reads
while True:
try:
# Read all available data and timestamp it
chunk = ser.read(ser.in_waiting or 1)
if not chunk:
continue
time_ = datetime.now()
nanoseconds = time_.microsecond // 1000
time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
# Add to buffer and process complete lines
buffer += chunk
while b"\n" in buffer:
raw_line, buffer = buffer.split(b"\n", 1)
line = raw_line.replace(b"\r", b"").decode(
"utf8", "backslashreplace"
)
safe_print(parser.parse_line(line, time_str))
backtrace_state = platformio_api.process_stacktrace(
config, line, backtrace_state=backtrace_state
)
raw = ser.readline()
except serial.SerialException:
_LOGGER.error("Serial port closed!")
return 0
line = (
raw.replace(b"\r", b"")
.replace(b"\n", b"")
.decode("utf8", "backslashreplace")
)
time_ = datetime.now()
nanoseconds = time_.microsecond // 1000
time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
safe_print(parser.parse_line(line, time_str))
backtrace_state = platformio_api.process_stacktrace(
config, line, backtrace_state=backtrace_state
)
except serial.SerialException:
tries += 1
time.sleep(1)
@@ -788,13 +780,6 @@ def command_vscode(args: ArgsProtocol) -> int | None:
def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
# Set memory analysis options in config
if args.analyze_memory:
config.setdefault(CONF_ESPHOME, {})["analyze_memory"] = True
if args.memory_report:
config.setdefault(CONF_ESPHOME, {})["memory_report_file"] = args.memory_report
exit_code = write_cpp(config)
if exit_code != 0:
return exit_code
@@ -1278,17 +1263,6 @@ def parse_args(argv):
help="Only generate source code, do not compile.",
action="store_true",
)
parser_compile.add_argument(
"--analyze-memory",
help="Analyze and display memory usage by component after compilation.",
action="store_true",
)
parser_compile.add_argument(
"--memory-report",
help="Save memory analysis report to a file (supports .json or .txt).",
type=str,
metavar="FILE",
)
parser_upload = subparsers.add_parser(
"upload",

View File

@@ -1,7 +1,6 @@
"""CLI interface for memory analysis with report generation."""
from collections import defaultdict
import json
import sys
from . import (
@@ -298,28 +297,6 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
return "\n".join(lines)
def to_json(self) -> str:
"""Export analysis results as JSON."""
data = {
"components": {
name: {
"text": mem.text_size,
"rodata": mem.rodata_size,
"data": mem.data_size,
"bss": mem.bss_size,
"flash_total": mem.flash_total,
"ram_total": mem.ram_total,
"symbol_count": mem.symbol_count,
}
for name, mem in self.components.items()
},
"totals": {
"flash": sum(c.flash_total for c in self.components.values()),
"ram": sum(c.ram_total for c in self.components.values()),
},
}
return json.dumps(data, indent=2)
def dump_uncategorized_symbols(self, output_file: str | None = None) -> None:
"""Dump uncategorized symbols for analysis."""
# Sort by size descending

View File

@@ -88,77 +88,6 @@ SYMBOL_PATTERNS = {
"sys_mbox_new",
"sys_arch_mbox_tryfetch",
],
# LibreTiny/Beken BK7231 radio calibration
"bk_radio_cal": [
"bk7011_",
"calibration_main",
"gcali_",
"rwnx_cal",
],
# LibreTiny/Beken WiFi MAC layer
"bk_wifi_mac": [
"rxu_", # RX upper layer
"txu_", # TX upper layer
"txl_", # TX lower layer
"rxl_", # RX lower layer
"scanu_", # Scan unit
"mm_hw_", # MAC management hardware
"mm_bcn", # MAC management beacon
"mm_tim", # MAC management TIM
"mm_check", # MAC management checks
"sm_connect", # Station management
"me_beacon", # Management entity beacon
"me_build", # Management entity build
"hapd_", # Host AP daemon
"chan_pre_", # Channel management
"handle_probe_", # Probe handling
],
# LibreTiny/Beken system control
"bk_system": [
"sctrl_", # System control
"icu_ctrl", # Interrupt control unit
"gdma_ctrl", # DMA control
"mpb_ctrl", # MPB control
"uf2_", # UF2 OTA
"bkreg_", # Beken registers
],
# LibreTiny/Beken BLE stack
"bk_ble": [
"gapc_", # GAP client
"gattc_", # GATT client
"attc_", # ATT client
"attmdb_", # ATT database
"atts_", # ATT server
"l2cc_", # L2CAP
"prf_env", # Profile environment
],
# LibreTiny/Beken scheduler
"bk_scheduler": [
"sch_plan_", # Scheduler plan
"sch_prog_", # Scheduler program
"sch_arb_", # Scheduler arbiter
],
# LibreTiny/Beken DMA descriptors
"bk_dma": [
"rx_payload_desc",
"rx_dma_hdrdesc",
"tx_hw_desc",
"host_event_data",
"host_cmd_data",
],
# ARM EABI compiler runtime (LibreTiny uses ARM Cortex-M)
"arm_runtime": [
"__aeabi_",
"__adddf3",
"__subdf3",
"__muldf3",
"__divdf3",
"__addsf3",
"__subsf3",
"__mulsf3",
"__divsf3",
"__gnu_unwind",
],
"xtensa": ["xt_", "_xt_", "xPortEnterCriticalTimeout"],
"heap": ["heap_", "multi_heap"],
"spi_flash": ["spi_flash"],
@@ -853,22 +782,7 @@ SYMBOL_PATTERNS = {
"math_internal": ["__mdiff", "__lshift", "__mprec_tens", "quorem"],
"character_class": ["__chclass"],
"camellia": ["camellia_", "camellia_feistel"],
"crypto_tables": [
"FSb",
"FSb2",
"FSb3",
"FSb4",
"Te0", # AES encryption table
"Td0", # AES decryption table
"crc32_table", # CRC32 lookup table
"crc_tab", # CRC lookup table
],
"crypto_hash": [
"SHA1Transform", # SHA1 hash function
"MD5Transform", # MD5 hash function
"SHA256",
"SHA512",
],
"crypto_tables": ["FSb", "FSb2", "FSb3", "FSb4"],
"event_buffer": ["g_eb_list_desc", "eb_space"],
"base_node": ["base_node_", "base_node_add_handler"],
"file_descriptor": ["s_fd_table"],

View File

@@ -20,8 +20,7 @@ bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &devic
sn |= ((uint32_t) it.data[2] << 16);
sn |= ((uint32_t) it.data[3] << 24);
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
ESP_LOGD(TAG, "Found AirThings device Serial:%" PRIu32 " (MAC: %s)", sn, device.address_str_to(addr_buf));
ESP_LOGD(TAG, "Found AirThings device Serial:%" PRIu32 " (MAC: %s)", sn, device.address_str().c_str());
return true;
}
}

View File

@@ -1,5 +1,4 @@
#include "airthings_wave_base.h"
#include "esphome/components/esp32_ble/ble_uuid.h"
// All information related to reading battery information came from the sensors.airthings_wave
// project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave)
@@ -94,10 +93,8 @@ void AirthingsWaveBase::update() {
bool AirthingsWaveBase::request_read_values_() {
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_);
if (chr == nullptr) {
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf),
this->sensors_data_characteristic_uuid_.to_str(char_buf));
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->sensors_data_characteristic_uuid_.to_string().c_str());
return false;
}
@@ -120,20 +117,17 @@ bool AirthingsWaveBase::request_battery_() {
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->access_control_point_characteristic_uuid_);
if (chr == nullptr) {
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "No access control point characteristic found at service %s char %s",
this->service_uuid_.to_str(service_buf), this->access_control_point_characteristic_uuid_.to_str(char_buf));
this->service_uuid_.to_string().c_str(),
this->access_control_point_characteristic_uuid_.to_string().c_str());
return false;
}
auto *descr = this->parent()->get_descriptor(this->service_uuid_, this->access_control_point_characteristic_uuid_,
CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID);
if (descr == nullptr) {
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_str(service_buf),
this->access_control_point_characteristic_uuid_.to_str(char_buf));
ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->access_control_point_characteristic_uuid_.to_string().c_str());
return false;
}

View File

@@ -15,8 +15,10 @@ namespace alpha3 {
namespace espbt = esphome::esp32_ble_tracker;
static const espbt::ESPBTUUID ALPHA3_GENI_SERVICE_UUID = espbt::ESPBTUUID::from_uint16(0xfe5d);
static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID = espbt::ESPBTUUID::from_raw(
{0xa9, 0x7b, 0xb8, 0x85, 0x00, 0x1a, 0x28, 0xaa, 0x2a, 0x43, 0x6e, 0x03, 0xd1, 0xff, 0x9c, 0x85});
static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID =
espbt::ESPBTUUID::from_raw({static_cast<char>(0xa9), 0x7b, static_cast<char>(0xb8), static_cast<char>(0x85), 0x0,
0x1a, 0x28, static_cast<char>(0xaa), 0x2a, 0x43, 0x6e, 0x3, static_cast<char>(0xd1),
static_cast<char>(0xff), static_cast<char>(0x9c), static_cast<char>(0x85)});
static const int16_t GENI_RESPONSE_HEADER_LENGTH = 13;
static const size_t GENI_RESPONSE_TYPE_LENGTH = 8;

View File

@@ -101,14 +101,16 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
auto &noise_ctx = parent->get_noise_ctx();
if (noise_ctx.has_psk()) {
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx)};
this->helper_ =
std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx, &this->client_info_)};
} else {
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)};
}
#elif defined(USE_API_PLAINTEXT)
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)};
#elif defined(USE_API_NOISE)
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
this->helper_ = std::unique_ptr<APIFrameHelper>{
new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx(), &this->client_info_)};
#else
#error "No frame helper defined"
#endif
@@ -129,9 +131,8 @@ void APIConnection::start() {
this->fatal_error_with_log_(LOG_STR("Helper init failed"), err);
return;
}
// Initialize client name with peername (IP address) until Hello message provides actual name
const char *peername = this->helper_->get_client_peername();
this->helper_->set_client_name(peername, strlen(peername));
this->client_info_.peername = helper_->getpeername();
this->client_info_.name = this->client_info_.peername;
}
APIConnection::~APIConnection() {
@@ -251,7 +252,8 @@ void APIConnection::loop() {
// Disconnect if not responded within 2.5*keepalive
if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
on_fatal_error();
this->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("is unresponsive; disconnecting"));
ESP_LOGW(TAG, "%s (%s) is unresponsive; disconnecting", this->client_info_.name.c_str(),
this->client_info_.peername.c_str());
}
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) {
// Only send ping if we're not disconnecting
@@ -285,7 +287,7 @@ bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) {
// remote initiated disconnect_client
// don't close yet, we still need to send the disconnect response
// close will happen on next loop
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("disconnected"));
ESP_LOGD(TAG, "%s (%s) disconnected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
this->flags_.next_close = true;
DisconnectResponse resp;
return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE);
@@ -376,7 +378,7 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne
bool is_single) {
auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity);
ListEntitiesBinarySensorResponse msg;
msg.device_class = binary_sensor->get_device_class_ref();
msg.set_device_class(binary_sensor->get_device_class_ref());
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
return fill_and_encode_entity_info(binary_sensor, msg, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn,
remaining_size, is_single);
@@ -408,7 +410,7 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c
msg.supports_position = traits.get_supports_position();
msg.supports_tilt = traits.get_supports_tilt();
msg.supports_stop = traits.get_supports_stop();
msg.device_class = cover->get_device_class_ref();
msg.set_device_class(cover->get_device_class_ref());
return fill_and_encode_entity_info(cover, msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -443,7 +445,7 @@ uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *co
if (traits.supports_direction())
msg.direction = static_cast<enums::FanDirection>(fan->direction);
if (traits.supports_preset_modes() && fan->has_preset_mode())
msg.preset_mode = StringRef(fan->get_preset_mode());
msg.set_preset_mode(StringRef(fan->get_preset_mode()));
return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -499,7 +501,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
resp.cold_white = values.get_cold_white();
resp.warm_white = values.get_warm_white();
if (light->supports_effects()) {
resp.effect = light->get_effect_name_ref();
resp.set_effect(light->get_effect_name_ref());
}
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -581,10 +583,10 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *
bool is_single) {
auto *sensor = static_cast<sensor::Sensor *>(entity);
ListEntitiesSensorResponse msg;
msg.unit_of_measurement = sensor->get_unit_of_measurement_ref();
msg.set_unit_of_measurement(sensor->get_unit_of_measurement_ref());
msg.accuracy_decimals = sensor->get_accuracy_decimals();
msg.force_update = sensor->get_force_update();
msg.device_class = sensor->get_device_class_ref();
msg.set_device_class(sensor->get_device_class_ref());
msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
return fill_and_encode_entity_info(sensor, msg, ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
@@ -611,7 +613,7 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection *
auto *a_switch = static_cast<switch_::Switch *>(entity);
ListEntitiesSwitchResponse msg;
msg.assumed_state = a_switch->assumed_state();
msg.device_class = a_switch->get_device_class_ref();
msg.set_device_class(a_switch->get_device_class_ref());
return fill_and_encode_entity_info(a_switch, msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -636,7 +638,7 @@ uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnec
bool is_single) {
auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
TextSensorStateResponse resp;
resp.state = StringRef(text_sensor->state);
resp.set_state(StringRef(text_sensor->state));
resp.missing_state = !text_sensor->has_state();
return fill_and_encode_entity_state(text_sensor, resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
@@ -645,7 +647,7 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect
bool is_single) {
auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
ListEntitiesTextSensorResponse msg;
msg.device_class = text_sensor->get_device_class_ref();
msg.set_device_class(text_sensor->get_device_class_ref());
return fill_and_encode_entity_info(text_sensor, msg, ListEntitiesTextSensorResponse::MESSAGE_TYPE, conn,
remaining_size, is_single);
}
@@ -675,13 +677,13 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
if (!traits.get_supported_custom_fan_modes().empty() && climate->has_custom_fan_mode()) {
resp.custom_fan_mode = StringRef(climate->get_custom_fan_mode());
resp.set_custom_fan_mode(StringRef(climate->get_custom_fan_mode()));
}
if (traits.get_supports_presets() && climate->preset.has_value()) {
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
}
if (!traits.get_supported_custom_presets().empty() && climate->has_custom_preset()) {
resp.custom_preset = StringRef(climate->get_custom_preset());
resp.set_custom_preset(StringRef(climate->get_custom_preset()));
}
if (traits.get_supports_swing_modes())
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
@@ -766,9 +768,9 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *
bool is_single) {
auto *number = static_cast<number::Number *>(entity);
ListEntitiesNumberResponse msg;
msg.unit_of_measurement = number->traits.get_unit_of_measurement_ref();
msg.set_unit_of_measurement(number->traits.get_unit_of_measurement_ref());
msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
msg.device_class = number->traits.get_device_class_ref();
msg.set_device_class(number->traits.get_device_class_ref());
msg.min_value = number->traits.get_min_value();
msg.max_value = number->traits.get_max_value();
msg.step = number->traits.get_step();
@@ -881,7 +883,7 @@ uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *c
bool is_single) {
auto *text = static_cast<text::Text *>(entity);
TextStateResponse resp;
resp.state = StringRef(text->state);
resp.set_state(StringRef(text->state));
resp.missing_state = !text->has_state();
return fill_and_encode_entity_state(text, resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -893,7 +895,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co
msg.mode = static_cast<enums::TextMode>(text->traits.get_mode());
msg.min_length = text->traits.get_min_length();
msg.max_length = text->traits.get_max_length();
msg.pattern = text->traits.get_pattern_ref();
msg.set_pattern(text->traits.get_pattern_ref());
return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -914,7 +916,7 @@ uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection
bool is_single) {
auto *select = static_cast<select::Select *>(entity);
SelectStateResponse resp;
resp.state = StringRef(select->current_option());
resp.set_state(StringRef(select->current_option()));
resp.missing_state = !select->has_state();
return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -939,7 +941,7 @@ uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *
bool is_single) {
auto *button = static_cast<button::Button *>(entity);
ListEntitiesButtonResponse msg;
msg.device_class = button->get_device_class_ref();
msg.set_device_class(button->get_device_class_ref());
return fill_and_encode_entity_info(button, msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -1008,7 +1010,7 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c
auto *valve = static_cast<valve::Valve *>(entity);
ListEntitiesValveResponse msg;
auto traits = valve->get_traits();
msg.device_class = valve->get_device_class_ref();
msg.set_device_class(valve->get_device_class_ref());
msg.assumed_state = traits.get_is_assumed_state();
msg.supports_position = traits.get_supports_position();
msg.supports_stop = traits.get_supports_stop();
@@ -1053,7 +1055,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec
for (auto &supported_format : traits.get_supported_formats()) {
msg.supported_formats.emplace_back();
auto &media_format = msg.supported_formats.back();
media_format.format = StringRef(supported_format.format);
media_format.set_format(StringRef(supported_format.format));
media_format.sample_rate = supported_format.sample_rate;
media_format.num_channels = supported_format.num_channels;
media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose);
@@ -1263,8 +1265,8 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA
for (auto &wake_word : config.available_wake_words) {
resp.available_wake_words.emplace_back();
auto &resp_wake_word = resp.available_wake_words.back();
resp_wake_word.id = StringRef(wake_word.id);
resp_wake_word.wake_word = StringRef(wake_word.wake_word);
resp_wake_word.set_id(StringRef(wake_word.id));
resp_wake_word.set_wake_word(StringRef(wake_word.wake_word));
for (const auto &lang : wake_word.trained_languages) {
resp_wake_word.trained_languages.push_back(lang);
}
@@ -1279,8 +1281,8 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA
resp.available_wake_words.emplace_back();
auto &resp_wake_word = resp.available_wake_words.back();
resp_wake_word.id = StringRef(wake_word.id);
resp_wake_word.wake_word = StringRef(wake_word.wake_word);
resp_wake_word.set_id(StringRef(wake_word.id));
resp_wake_word.set_wake_word(StringRef(wake_word.wake_word));
for (const auto &lang : wake_word.trained_languages) {
resp_wake_word.trained_languages.push_back(lang);
}
@@ -1421,7 +1423,7 @@ void APIConnection::send_event(event::Event *event, const char *event_type) {
uint16_t APIConnection::try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
uint32_t remaining_size, bool is_single) {
EventResponse resp;
resp.event_type = StringRef(event_type);
resp.set_event_type(StringRef(event_type));
return fill_and_encode_entity_state(event, resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -1429,7 +1431,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
bool is_single) {
auto *event = static_cast<event::Event *>(entity);
ListEntitiesEventResponse msg;
msg.device_class = event->get_device_class_ref();
msg.set_device_class(event->get_device_class_ref());
msg.event_types = &event->get_event_types();
return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
@@ -1452,11 +1454,11 @@ uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection
resp.has_progress = true;
resp.progress = update->update_info.progress;
}
resp.current_version = StringRef(update->update_info.current_version);
resp.latest_version = StringRef(update->update_info.latest_version);
resp.title = StringRef(update->update_info.title);
resp.release_summary = StringRef(update->update_info.summary);
resp.release_url = StringRef(update->update_info.release_url);
resp.set_current_version(StringRef(update->update_info.current_version));
resp.set_latest_version(StringRef(update->update_info.latest_version));
resp.set_title(StringRef(update->update_info.title));
resp.set_release_summary(StringRef(update->update_info.summary));
resp.set_release_url(StringRef(update->update_info.release_url));
}
return fill_and_encode_entity_state(update, resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -1464,7 +1466,7 @@ uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *
bool is_single) {
auto *update = static_cast<update::UpdateEntity *>(entity);
ListEntitiesUpdateResponse msg;
msg.device_class = update->get_device_class_ref();
msg.set_device_class(update->get_device_class_ref());
return fill_and_encode_entity_info(update, msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -1502,10 +1504,9 @@ void APIConnection::complete_authentication_() {
}
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("connected"));
ESP_LOGD(TAG, "%s (%s) connected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
this->parent_->get_client_connected_trigger()->trigger(std::string(this->helper_->get_client_name()),
std::string(this->helper_->get_client_peername()));
this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername);
#endif
#ifdef USE_HOMEASSISTANT_TIME
if (homeassistant::global_homeassistant_time != nullptr) {
@@ -1520,19 +1521,19 @@ void APIConnection::complete_authentication_() {
}
bool APIConnection::send_hello_response(const HelloRequest &msg) {
// Copy client name with truncation if needed (set_client_name handles truncation)
this->helper_->set_client_name(msg.client_info.c_str(), msg.client_info.size());
this->client_info_.name.assign(msg.client_info.c_str(), msg.client_info.size());
this->client_info_.peername = this->helper_->getpeername();
this->client_api_version_major_ = msg.api_version_major;
this->client_api_version_minor_ = msg.api_version_minor;
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->helper_->get_client_name(),
this->helper_->get_client_peername(), this->client_api_version_major_, this->client_api_version_minor_);
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.name.c_str(),
this->client_info_.peername.c_str(), this->client_api_version_major_, this->client_api_version_minor_);
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 14;
// Send only the version string - the client only logs this for debugging and doesn't use it otherwise
resp.server_info = ESPHOME_VERSION_REF;
resp.name = StringRef(App.get_name());
resp.set_server_info(ESPHOME_VERSION_REF);
resp.set_name(StringRef(App.get_name()));
// Auto-authenticate - password auth was removed in ESPHome 2026.1.0
this->complete_authentication_();
@@ -1547,24 +1548,24 @@ bool APIConnection::send_ping_response(const PingRequest &msg) {
bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
DeviceInfoResponse resp{};
resp.name = StringRef(App.get_name());
resp.friendly_name = StringRef(App.get_friendly_name());
resp.set_name(StringRef(App.get_name()));
resp.set_friendly_name(StringRef(App.get_friendly_name()));
#ifdef USE_AREAS
resp.suggested_area = StringRef(App.get_area());
resp.set_suggested_area(StringRef(App.get_area()));
#endif
// Stack buffer for MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
char mac_address[18];
uint8_t mac[6];
get_mac_address_raw(mac);
format_mac_addr_upper(mac, mac_address);
resp.mac_address = StringRef(mac_address);
resp.set_mac_address(StringRef(mac_address));
resp.esphome_version = ESPHOME_VERSION_REF;
resp.set_esphome_version(ESPHOME_VERSION_REF);
// Stack buffer for build time string
char build_time_str[Application::BUILD_TIME_STR_SIZE];
App.get_build_time_string(build_time_str);
resp.compilation_time = StringRef(build_time_str);
resp.set_compilation_time(StringRef(build_time_str));
// Manufacturer string - define once, handle ESP8266 PROGMEM separately
#if defined(USE_ESP8266) || defined(USE_ESP32)
@@ -1588,10 +1589,10 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
static const char MANUFACTURER_PROGMEM[] PROGMEM = ESPHOME_MANUFACTURER;
char manufacturer_buf[sizeof(MANUFACTURER_PROGMEM)];
memcpy_P(manufacturer_buf, MANUFACTURER_PROGMEM, sizeof(MANUFACTURER_PROGMEM));
resp.manufacturer = StringRef(manufacturer_buf, sizeof(MANUFACTURER_PROGMEM) - 1);
resp.set_manufacturer(StringRef(manufacturer_buf, sizeof(MANUFACTURER_PROGMEM) - 1));
#else
static constexpr auto MANUFACTURER = StringRef::from_lit(ESPHOME_MANUFACTURER);
resp.manufacturer = MANUFACTURER;
resp.set_manufacturer(MANUFACTURER);
#endif
#undef ESPHOME_MANUFACTURER
@@ -1599,10 +1600,10 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
static const char MODEL_PROGMEM[] PROGMEM = ESPHOME_BOARD;
char model_buf[sizeof(MODEL_PROGMEM)];
memcpy_P(model_buf, MODEL_PROGMEM, sizeof(MODEL_PROGMEM));
resp.model = StringRef(model_buf, sizeof(MODEL_PROGMEM) - 1);
resp.set_model(StringRef(model_buf, sizeof(MODEL_PROGMEM) - 1));
#else
static constexpr auto MODEL = StringRef::from_lit(ESPHOME_BOARD);
resp.model = MODEL;
resp.set_model(MODEL);
#endif
#ifdef USE_DEEP_SLEEP
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
@@ -1615,13 +1616,13 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
char project_version_buf[sizeof(PROJECT_VERSION_PROGMEM)];
memcpy_P(project_name_buf, PROJECT_NAME_PROGMEM, sizeof(PROJECT_NAME_PROGMEM));
memcpy_P(project_version_buf, PROJECT_VERSION_PROGMEM, sizeof(PROJECT_VERSION_PROGMEM));
resp.project_name = StringRef(project_name_buf, sizeof(PROJECT_NAME_PROGMEM) - 1);
resp.project_version = StringRef(project_version_buf, sizeof(PROJECT_VERSION_PROGMEM) - 1);
resp.set_project_name(StringRef(project_name_buf, sizeof(PROJECT_NAME_PROGMEM) - 1));
resp.set_project_version(StringRef(project_version_buf, sizeof(PROJECT_VERSION_PROGMEM) - 1));
#else
static constexpr auto PROJECT_NAME = StringRef::from_lit(ESPHOME_PROJECT_NAME);
static constexpr auto PROJECT_VERSION = StringRef::from_lit(ESPHOME_PROJECT_VERSION);
resp.project_name = PROJECT_NAME;
resp.project_version = PROJECT_VERSION;
resp.set_project_name(PROJECT_NAME);
resp.set_project_version(PROJECT_VERSION);
#endif
#endif
#ifdef USE_WEBSERVER
@@ -1632,7 +1633,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
// Stack buffer for Bluetooth MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
char bluetooth_mac[18];
bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty(bluetooth_mac);
resp.bluetooth_mac_address = StringRef(bluetooth_mac);
resp.set_bluetooth_mac_address(StringRef(bluetooth_mac));
#endif
#ifdef USE_VOICE_ASSISTANT
resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
@@ -1651,7 +1652,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
break;
auto &device_info = resp.devices[device_index++];
device_info.device_id = device->get_device_id();
device_info.name = StringRef(device->get_name());
device_info.set_name(StringRef(device->get_name()));
device_info.area_id = device->get_area_id();
}
#endif
@@ -1662,7 +1663,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
break;
auto &area_info = resp.areas[area_index++];
area_info.area_id = area->get_area_id();
area_info.name = StringRef(area->get_name());
area_info.set_name(StringRef(area->get_name()));
}
#endif
@@ -1691,18 +1692,10 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
continue;
}
// Create null-terminated state for callback (parse_number needs null-termination)
// HA state max length is 255, so 256 byte buffer covers all cases
char state_buf[256];
size_t copy_len = msg.state.size();
if (copy_len >= sizeof(state_buf)) {
copy_len = sizeof(state_buf) - 1; // Truncate to leave space for null terminator
}
if (copy_len > 0) {
memcpy(state_buf, msg.state.c_str(), copy_len);
}
state_buf[copy_len] = '\0';
it.callback(StringRef(state_buf, copy_len));
// Create temporary string for callback (callback takes const std::string &)
// Handle empty state
std::string state(!msg.state.empty() ? msg.state.c_str() : "", msg.state.size());
it.callback(state);
}
}
#endif
@@ -1741,7 +1734,7 @@ void APIConnection::send_execute_service_response(uint32_t call_id, bool success
ExecuteServiceResponse resp;
resp.call_id = call_id;
resp.success = success;
resp.error_message = error_message;
resp.set_error_message(error_message);
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
}
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
@@ -1750,7 +1743,7 @@ void APIConnection::send_execute_service_response(uint32_t call_id, bool success
ExecuteServiceResponse resp;
resp.call_id = call_id;
resp.success = success;
resp.error_message = error_message;
resp.set_error_message(error_message);
resp.response_data = response_data;
resp.response_data_len = response_data_len;
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
@@ -1819,26 +1812,10 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
return false;
}
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
const bool is_log_message = (message_type == SubscribeLogsResponse::MESSAGE_TYPE);
if (!this->try_to_clear_buffer(!is_log_message)) {
if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) { // SubscribeLogsResponse
return false;
}
// Toggle Nagle's algorithm based on message type to prevent log messages from
// filling the TCP send buffer and crowding out important state updates.
//
// - Log messages: Enable Nagle (NODELAY=false) so small log packets coalesce
// into fewer, larger packets. They flush naturally via TCP delayed ACK timer
// (~200ms), buffer filling, or when a state update triggers a flush.
//
// - All other messages (state updates, responses): Disable Nagle (NODELAY=true)
// for immediate delivery. These are time-sensitive and should not be delayed.
//
// This must be done proactively BEFORE the buffer fills up - checking buffer
// state here would be too late since we'd already be in a degraded state.
this->helper_->set_nodelay(!is_log_message);
APIError err = this->helper_->write_protobuf_packet(message_type, buffer);
if (err == APIError::WOULD_BLOCK)
return false;
@@ -1851,7 +1828,7 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
}
void APIConnection::on_no_setup_connection() {
this->on_fatal_error();
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("no connection setup"));
ESP_LOGD(TAG, "%s (%s) no connection setup", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
}
void APIConnection::on_fatal_error() {
this->helper_->close();
@@ -2087,10 +2064,10 @@ void APIConnection::process_state_subscriptions_() {
const auto &it = subs[this->state_subs_at_];
SubscribeHomeAssistantStateResponse resp;
resp.entity_id = StringRef(it.entity_id);
resp.set_entity_id(StringRef(it.entity_id));
// Avoid string copy by using the const char* pointer if it exists
resp.attribute = it.attribute != nullptr ? StringRef(it.attribute) : StringRef("");
resp.set_attribute(it.attribute != nullptr ? StringRef(it.attribute) : StringRef(""));
resp.once = it.once;
if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) {
@@ -2099,13 +2076,8 @@ void APIConnection::process_state_subscriptions_() {
}
#endif // USE_API_HOMEASSISTANT_STATES
void APIConnection::log_client_(int level, const LogString *message) {
esp_log_printf_(level, TAG, __LINE__, ESPHOME_LOG_FORMAT("%s (%s): %s"), this->helper_->get_client_name(),
this->helper_->get_client_peername(), LOG_STR_ARG(message));
}
void APIConnection::log_warning_(const LogString *message, APIError err) {
ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->helper_->get_client_name(), this->helper_->get_client_peername(),
ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->client_info_.name.c_str(), this->client_info_.peername.c_str(),
LOG_STR_ARG(message), LOG_STR_ARG(api_error_to_logstr(err)), errno);
}

View File

@@ -9,13 +9,18 @@
#include "esphome/core/application.h"
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/string_ref.h"
#include <functional>
#include <vector>
namespace esphome::api {
// Client information structure
struct ClientInfo {
std::string name; // Client name from Hello message
std::string peername; // IP:port from socket
};
// Keepalive timeout in milliseconds
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
// Maximum number of entities to process in a single batch during initial state/info sending
@@ -274,9 +279,8 @@ class APIConnection final : public APIServerConnection {
bool try_to_clear_buffer(bool log_out_of_space);
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
const char *get_name() const { return this->helper_->get_client_name(); }
/// Get peer name (IP address) - cached at connection init time
const char *get_peername() const { return this->helper_->get_client_peername(); }
const std::string &get_name() const { return this->client_info_.name; }
const std::string &get_peername() const { return this->client_info_.peername; }
protected:
// Helper function to handle authentication completion
@@ -317,16 +321,16 @@ class APIConnection final : public APIServerConnection {
// Buffer must remain in scope until encode_message_to_buffer is called
char object_id_buf[OBJECT_ID_MAX_LEN];
if (!conn->client_supports_api_version(1, 14)) {
msg.object_id = entity->get_object_id_to(object_id_buf);
msg.set_object_id(entity->get_object_id_to(object_id_buf));
}
if (entity->has_own_name()) {
msg.name = entity->get_name();
msg.set_name(entity->get_name());
}
// Set common EntityBase properties
#ifdef USE_ENTITY_ICON
msg.icon = entity->get_icon_ref();
msg.set_icon(entity->get_icon_ref());
#endif
msg.disabled_by_default = entity->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category());
@@ -522,7 +526,10 @@ class APIConnection final : public APIServerConnection {
std::unique_ptr<camera::CameraImageReader> image_reader_;
#endif
// Group 3: 4-byte types
// Group 3: Client info struct (24 bytes on 32-bit: 2 strings × 12 bytes each)
ClientInfo client_info_;
// Group 4: 4-byte types
uint32_t last_traffic_;
#ifdef USE_API_HOMEASSISTANT_STATES
int state_subs_at_ = -1;
@@ -749,8 +756,6 @@ class APIConnection final : public APIServerConnection {
return this->schedule_batch_();
}
// Helper function to log client messages with name and peername
void log_client_(int level, const LogString *message);
// Helper function to log API errors with errno
void log_warning_(const LogString *message, APIError err);
// Helper to handle fatal errors with logging

View File

@@ -1,5 +1,6 @@
#include "api_frame_helper.h"
#ifdef USE_API
#include "api_connection.h" // For ClientInfo struct
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
@@ -15,11 +16,8 @@ static const char *const TAG = "api.frame_helper";
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
static constexpr size_t API_MAX_LOG_BYTES = 168;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__)
#else
#define HELPER_LOG(msg, ...) ((void) 0)
#endif
#define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
#ifdef HELPER_LOG_PACKETS
#define LOG_PACKET_RECEIVED(buffer) \
@@ -245,8 +243,6 @@ APIError APIFrameHelper::init_common_() {
HELPER_LOG("Bad state for init %d", (int) state_);
return APIError::BAD_STATE;
}
// Cache peername now while socket is valid - needed for error logging after socket failure
this->socket_->getpeername_to(this->client_peername_);
int err = this->socket_->setblocking(false);
if (err != 0) {
state_ = State::FAILED;

View File

@@ -33,10 +33,10 @@ static constexpr uint16_t MAX_MESSAGE_SIZE = 32768; // 32 KiB for ESP32 and oth
// Must be >= MAX_INITIAL_PER_BATCH in api_connection.h (enforced by static_assert there)
static constexpr size_t MAX_MESSAGES_PER_BATCH = 34;
class ProtoWriteBuffer;
// Forward declaration
struct ClientInfo;
// Max client name length (e.g., "Home Assistant 2026.1.0.dev0" = 28 chars)
static constexpr size_t CLIENT_INFO_NAME_MAX_LEN = 32;
class ProtoWriteBuffer;
struct ReadPacketBuffer {
const uint8_t *data; // Points directly into frame helper's rx_buf_ (valid until next read_packet call)
@@ -86,23 +86,14 @@ const LogString *api_error_to_logstr(APIError err);
class APIFrameHelper {
public:
APIFrameHelper() = default;
explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {}
// Get client name (null-terminated)
const char *get_client_name() const { return this->client_name_; }
// Get client peername/IP (null-terminated, cached at init time for availability after socket failure)
const char *get_client_peername() const { return this->client_peername_; }
// Set client name from buffer with length (truncates if needed)
void set_client_name(const char *name, size_t len) {
size_t copy_len = std::min(len, sizeof(this->client_name_) - 1);
memcpy(this->client_name_, name, copy_len);
this->client_name_[copy_len] = '\0';
}
explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
: socket_(std::move(socket)), client_info_(client_info) {}
virtual ~APIFrameHelper() = default;
virtual APIError init() = 0;
virtual APIError loop();
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; }
std::string getpeername() { return socket_->getpeername(); }
int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
APIError close() {
state_ = State::CLOSED;
@@ -120,27 +111,6 @@ class APIFrameHelper {
}
return APIError::OK;
}
/// Toggle TCP_NODELAY socket option to control Nagle's algorithm.
///
/// This is used to allow log messages to coalesce (Nagle enabled) while keeping
/// state updates low-latency (NODELAY enabled). Without this, many small log
/// packets fill the TCP send buffer, crowding out important state updates.
///
/// State is tracked to minimize setsockopt() overhead - on lwip_raw (ESP8266/RP2040)
/// this is just a boolean assignment; on other platforms it's a lightweight syscall.
///
/// @param enable true to enable NODELAY (disable Nagle), false to enable Nagle
/// @return true if successful or already in desired state
bool set_nodelay(bool enable) {
if (this->nodelay_enabled_ == enable)
return true;
int val = enable ? 1 : 0;
int err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
if (err == 0) {
this->nodelay_enabled_ = enable;
}
return err == 0;
}
virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0;
// Write multiple protobuf messages in a single operation
// messages contains (message_type, offset, length) for each message in the buffer
@@ -216,10 +186,9 @@ class APIFrameHelper {
std::array<std::unique_ptr<SendBuffer>, API_MAX_SEND_QUEUE> tx_buf_;
std::vector<uint8_t> rx_buf_;
// Client name buffer - stores name from Hello message or initial peername
char client_name_[CLIENT_INFO_NAME_MAX_LEN]{};
// Cached peername/IP address - captured at init time for availability after socket failure
char client_peername_[socket::SOCKADDR_STR_LEN]{};
// Pointer to client info (4 bytes on 32-bit)
// Note: The pointed-to ClientInfo object must outlive this APIFrameHelper instance.
const ClientInfo *client_info_{nullptr};
// Group smaller types together
uint16_t rx_buf_len_ = 0;
@@ -229,10 +198,7 @@ class APIFrameHelper {
uint8_t tx_buf_head_{0};
uint8_t tx_buf_tail_{0};
uint8_t tx_buf_count_{0};
// Tracks TCP_NODELAY state to minimize setsockopt() calls. Initialized to true
// since init_common_() enables NODELAY. Used by set_nodelay() to allow log
// messages to coalesce while keeping state updates low-latency.
bool nodelay_enabled_{true};
// 8 bytes total, 0 bytes padding
// Common initialization for both plaintext and noise protocols
APIError init_common_();

View File

@@ -27,11 +27,8 @@ static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
static constexpr size_t API_MAX_LOG_BYTES = 168;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__)
#else
#define HELPER_LOG(msg, ...) ((void) 0)
#endif
#define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
#ifdef HELPER_LOG_PACKETS
#define LOG_PACKET_RECEIVED(buffer) \

View File

@@ -9,8 +9,8 @@ namespace esphome::api {
class APINoiseFrameHelper final : public APIFrameHelper {
public:
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, APINoiseContext &ctx)
: APIFrameHelper(std::move(socket)), ctx_(ctx) {
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, APINoiseContext &ctx, const ClientInfo *client_info)
: APIFrameHelper(std::move(socket), client_info), ctx_(ctx) {
// Noise header structure:
// Pos 0: indicator (0x01)
// Pos 1-2: encrypted payload size (16-bit big-endian)

View File

@@ -1,6 +1,7 @@
#include "api_frame_helper_plaintext.h"
#ifdef USE_API
#ifdef USE_API_PLAINTEXT
#include "api_connection.h" // For ClientInfo struct
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
@@ -20,11 +21,8 @@ static const char *const TAG = "api.plaintext";
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
static constexpr size_t API_MAX_LOG_BYTES = 168;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__)
#else
#define HELPER_LOG(msg, ...) ((void) 0)
#endif
#define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
#ifdef HELPER_LOG_PACKETS
#define LOG_PACKET_RECEIVED(buffer) \

View File

@@ -7,7 +7,8 @@ namespace esphome::api {
class APIPlaintextFrameHelper final : public APIFrameHelper {
public:
explicit APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : APIFrameHelper(std::move(socket)) {
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
: APIFrameHelper(std::move(socket), client_info) {
// Plaintext header structure (worst case):
// Pos 0: indicator (0x00)
// Pos 1-3: payload size varint (up to 3 bytes)

File diff suppressed because it is too large Load Diff

View File

@@ -315,12 +315,15 @@ enum ZWaveProxyRequestType : uint32_t {
class InfoResponseProtoMessage : public ProtoMessage {
public:
~InfoResponseProtoMessage() override = default;
StringRef object_id{};
StringRef object_id_ref_{};
void set_object_id(const StringRef &ref) { this->object_id_ref_ = ref; }
uint32_t key{0};
StringRef name{};
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
bool disabled_by_default{false};
#ifdef USE_ENTITY_ICON
StringRef icon{};
StringRef icon_ref_{};
void set_icon(const StringRef &ref) { this->icon_ref_ = ref; }
#endif
enums::EntityCategory entity_category{};
#ifdef USE_DEVICES
@@ -378,8 +381,10 @@ class HelloResponse final : public ProtoMessage {
#endif
uint32_t api_version_major{0};
uint32_t api_version_minor{0};
StringRef server_info{};
StringRef name{};
StringRef server_info_ref_{};
void set_server_info(const StringRef &ref) { this->server_info_ref_ = ref; }
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -457,7 +462,8 @@ class DeviceInfoRequest final : public ProtoMessage {
class AreaInfo final : public ProtoMessage {
public:
uint32_t area_id{0};
StringRef name{};
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -471,7 +477,8 @@ class AreaInfo final : public ProtoMessage {
class DeviceInfo final : public ProtoMessage {
public:
uint32_t device_id{0};
StringRef name{};
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
uint32_t area_id{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -489,19 +496,26 @@ class DeviceInfoResponse final : public ProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "device_info_response"; }
#endif
StringRef name{};
StringRef mac_address{};
StringRef esphome_version{};
StringRef compilation_time{};
StringRef model{};
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
StringRef mac_address_ref_{};
void set_mac_address(const StringRef &ref) { this->mac_address_ref_ = ref; }
StringRef esphome_version_ref_{};
void set_esphome_version(const StringRef &ref) { this->esphome_version_ref_ = ref; }
StringRef compilation_time_ref_{};
void set_compilation_time(const StringRef &ref) { this->compilation_time_ref_ = ref; }
StringRef model_ref_{};
void set_model(const StringRef &ref) { this->model_ref_ = ref; }
#ifdef USE_DEEP_SLEEP
bool has_deep_sleep{false};
#endif
#ifdef ESPHOME_PROJECT_NAME
StringRef project_name{};
StringRef project_name_ref_{};
void set_project_name(const StringRef &ref) { this->project_name_ref_ = ref; }
#endif
#ifdef ESPHOME_PROJECT_NAME
StringRef project_version{};
StringRef project_version_ref_{};
void set_project_version(const StringRef &ref) { this->project_version_ref_ = ref; }
#endif
#ifdef USE_WEBSERVER
uint32_t webserver_port{0};
@@ -509,16 +523,20 @@ class DeviceInfoResponse final : public ProtoMessage {
#ifdef USE_BLUETOOTH_PROXY
uint32_t bluetooth_proxy_feature_flags{0};
#endif
StringRef manufacturer{};
StringRef friendly_name{};
StringRef manufacturer_ref_{};
void set_manufacturer(const StringRef &ref) { this->manufacturer_ref_ = ref; }
StringRef friendly_name_ref_{};
void set_friendly_name(const StringRef &ref) { this->friendly_name_ref_ = ref; }
#ifdef USE_VOICE_ASSISTANT
uint32_t voice_assistant_feature_flags{0};
#endif
#ifdef USE_AREAS
StringRef suggested_area{};
StringRef suggested_area_ref_{};
void set_suggested_area(const StringRef &ref) { this->suggested_area_ref_ = ref; }
#endif
#ifdef USE_BLUETOOTH_PROXY
StringRef bluetooth_mac_address{};
StringRef bluetooth_mac_address_ref_{};
void set_bluetooth_mac_address(const StringRef &ref) { this->bluetooth_mac_address_ref_ = ref; }
#endif
#ifdef USE_API_NOISE
bool api_encryption_supported{false};
@@ -593,7 +611,8 @@ class ListEntitiesBinarySensorResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_binary_sensor_response"; }
#endif
StringRef device_class{};
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
bool is_status_binary_sensor{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -632,7 +651,8 @@ class ListEntitiesCoverResponse final : public InfoResponseProtoMessage {
bool assumed_state{false};
bool supports_position{false};
bool supports_tilt{false};
StringRef device_class{};
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
bool supports_stop{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -713,7 +733,8 @@ class FanStateResponse final : public StateResponseProtoMessage {
bool oscillating{false};
enums::FanDirection direction{};
int32_t speed_level{0};
StringRef preset_mode{};
StringRef preset_mode_ref_{};
void set_preset_mode(const StringRef &ref) { this->preset_mode_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -787,7 +808,8 @@ class LightStateResponse final : public StateResponseProtoMessage {
float color_temperature{0.0f};
float cold_white{0.0f};
float warm_white{0.0f};
StringRef effect{};
StringRef effect_ref_{};
void set_effect(const StringRef &ref) { this->effect_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -847,10 +869,12 @@ class ListEntitiesSensorResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_sensor_response"; }
#endif
StringRef unit_of_measurement{};
StringRef unit_of_measurement_ref_{};
void set_unit_of_measurement(const StringRef &ref) { this->unit_of_measurement_ref_ = ref; }
int32_t accuracy_decimals{0};
bool force_update{false};
StringRef device_class{};
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
enums::SensorStateClass state_class{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -887,7 +911,8 @@ class ListEntitiesSwitchResponse final : public InfoResponseProtoMessage {
const char *message_name() const override { return "list_entities_switch_response"; }
#endif
bool assumed_state{false};
StringRef device_class{};
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -937,7 +962,8 @@ class ListEntitiesTextSensorResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_text_sensor_response"; }
#endif
StringRef device_class{};
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -953,7 +979,8 @@ class TextSensorStateResponse final : public StateResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "text_sensor_state_response"; }
#endif
StringRef state{};
StringRef state_ref_{};
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -1052,7 +1079,8 @@ class SubscribeHomeassistantServicesRequest final : public ProtoMessage {
};
class HomeassistantServiceMap final : public ProtoMessage {
public:
StringRef key{};
StringRef key_ref_{};
void set_key(const StringRef &ref) { this->key_ref_ = ref; }
std::string value{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -1069,7 +1097,8 @@ class HomeassistantActionRequest final : public ProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "homeassistant_action_request"; }
#endif
StringRef service{};
StringRef service_ref_{};
void set_service(const StringRef &ref) { this->service_ref_ = ref; }
FixedVector<HomeassistantServiceMap> data{};
FixedVector<HomeassistantServiceMap> data_template{};
FixedVector<HomeassistantServiceMap> variables{};
@@ -1137,8 +1166,10 @@ class SubscribeHomeAssistantStateResponse final : public ProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "subscribe_home_assistant_state_response"; }
#endif
StringRef entity_id{};
StringRef attribute{};
StringRef entity_id_ref_{};
void set_entity_id(const StringRef &ref) { this->entity_id_ref_ = ref; }
StringRef attribute_ref_{};
void set_attribute(const StringRef &ref) { this->attribute_ref_ = ref; }
bool once{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -1199,7 +1230,8 @@ class GetTimeResponse final : public ProtoDecodableMessage {
#ifdef USE_API_USER_DEFINED_ACTIONS
class ListEntitiesServicesArgument final : public ProtoMessage {
public:
StringRef name{};
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
enums::ServiceArgType type{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -1216,7 +1248,8 @@ class ListEntitiesServicesResponse final : public ProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_services_response"; }
#endif
StringRef name{};
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
uint32_t key{0};
FixedVector<ListEntitiesServicesArgument> args{};
enums::SupportsResponseType supports_response{};
@@ -1285,7 +1318,8 @@ class ExecuteServiceResponse final : public ProtoMessage {
#endif
uint32_t call_id{0};
bool success{false};
StringRef error_message{};
StringRef error_message_ref_{};
void set_error_message(const StringRef &ref) { this->error_message_ref_ = ref; }
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
const uint8_t *response_data{nullptr};
uint16_t response_data_len{0};
@@ -1403,9 +1437,11 @@ class ClimateStateResponse final : public StateResponseProtoMessage {
enums::ClimateAction action{};
enums::ClimateFanMode fan_mode{};
enums::ClimateSwingMode swing_mode{};
StringRef custom_fan_mode{};
StringRef custom_fan_mode_ref_{};
void set_custom_fan_mode(const StringRef &ref) { this->custom_fan_mode_ref_ = ref; }
enums::ClimatePreset preset{};
StringRef custom_preset{};
StringRef custom_preset_ref_{};
void set_custom_preset(const StringRef &ref) { this->custom_preset_ref_ = ref; }
float current_humidity{0.0f};
float target_humidity{0.0f};
void encode(ProtoWriteBuffer buffer) const override;
@@ -1528,9 +1564,11 @@ class ListEntitiesNumberResponse final : public InfoResponseProtoMessage {
float min_value{0.0f};
float max_value{0.0f};
float step{0.0f};
StringRef unit_of_measurement{};
StringRef unit_of_measurement_ref_{};
void set_unit_of_measurement(const StringRef &ref) { this->unit_of_measurement_ref_ = ref; }
enums::NumberMode mode{};
StringRef device_class{};
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1597,7 +1635,8 @@ class SelectStateResponse final : public StateResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "select_state_response"; }
#endif
StringRef state{};
StringRef state_ref_{};
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -1696,7 +1735,8 @@ class ListEntitiesLockResponse final : public InfoResponseProtoMessage {
bool assumed_state{false};
bool supports_open{false};
bool requires_code{false};
StringRef code_format{};
StringRef code_format_ref_{};
void set_code_format(const StringRef &ref) { this->code_format_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1749,7 +1789,8 @@ class ListEntitiesButtonResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_button_response"; }
#endif
StringRef device_class{};
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1777,7 +1818,8 @@ class ButtonCommandRequest final : public CommandProtoMessage {
#ifdef USE_MEDIA_PLAYER
class MediaPlayerSupportedFormat final : public ProtoMessage {
public:
StringRef format{};
StringRef format_ref_{};
void set_format(const StringRef &ref) { this->format_ref_ = ref; }
uint32_t sample_rate{0};
uint32_t num_channels{0};
enums::MediaPlayerFormatPurpose purpose{};
@@ -2382,10 +2424,12 @@ class VoiceAssistantRequest final : public ProtoMessage {
const char *message_name() const override { return "voice_assistant_request"; }
#endif
bool start{false};
StringRef conversation_id{};
StringRef conversation_id_ref_{};
void set_conversation_id(const StringRef &ref) { this->conversation_id_ref_ = ref; }
uint32_t flags{0};
VoiceAssistantAudioSettings audio_settings{};
StringRef wake_word_phrase{};
StringRef wake_word_phrase_ref_{};
void set_wake_word_phrase(const StringRef &ref) { this->wake_word_phrase_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -2516,8 +2560,10 @@ class VoiceAssistantAnnounceFinished final : public ProtoMessage {
};
class VoiceAssistantWakeWord final : public ProtoMessage {
public:
StringRef id{};
StringRef wake_word{};
StringRef id_ref_{};
void set_id(const StringRef &ref) { this->id_ref_ = ref; }
StringRef wake_word_ref_{};
void set_wake_word(const StringRef &ref) { this->wake_word_ref_ = ref; }
std::vector<std::string> trained_languages{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -2657,7 +2703,8 @@ class ListEntitiesTextResponse final : public InfoResponseProtoMessage {
#endif
uint32_t min_length{0};
uint32_t max_length{0};
StringRef pattern{};
StringRef pattern_ref_{};
void set_pattern(const StringRef &ref) { this->pattern_ref_ = ref; }
enums::TextMode mode{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -2674,7 +2721,8 @@ class TextStateResponse final : public StateResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "text_state_response"; }
#endif
StringRef state{};
StringRef state_ref_{};
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -2818,7 +2866,8 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_event_response"; }
#endif
StringRef device_class{};
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
const FixedVector<const char *> *event_types{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -2835,7 +2884,8 @@ class EventResponse final : public StateResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "event_response"; }
#endif
StringRef event_type{};
StringRef event_type_ref_{};
void set_event_type(const StringRef &ref) { this->event_type_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -2853,7 +2903,8 @@ class ListEntitiesValveResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_valve_response"; }
#endif
StringRef device_class{};
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
bool assumed_state{false};
bool supports_position{false};
bool supports_stop{false};
@@ -2959,7 +3010,8 @@ class ListEntitiesUpdateResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_update_response"; }
#endif
StringRef device_class{};
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -2979,11 +3031,16 @@ class UpdateStateResponse final : public StateResponseProtoMessage {
bool in_progress{false};
bool has_progress{false};
float progress{0.0f};
StringRef current_version{};
StringRef latest_version{};
StringRef title{};
StringRef release_summary{};
StringRef release_url{};
StringRef current_version_ref_{};
void set_current_version(const StringRef &ref) { this->current_version_ref_ = ref; }
StringRef latest_version_ref_{};
void set_latest_version(const StringRef &ref) { this->latest_version_ref_ = ref; }
StringRef title_ref_{};
void set_title(const StringRef &ref) { this->title_ref_ = ref; }
StringRef release_summary_ref_{};
void set_release_summary(const StringRef &ref) { this->release_summary_ref_ = ref; }
StringRef release_url_ref_{};
void set_release_url(const StringRef &ref) { this->release_url_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP

View File

@@ -735,7 +735,9 @@ template<> const char *proto_enum_to_string<enums::ZWaveProxyRequestType>(enums:
void HelloRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HelloRequest");
dump_field(out, "client_info", this->client_info);
out.append(" client_info: ");
out.append("'").append(this->client_info.c_str(), this->client_info.size()).append("'");
out.append("\n");
dump_field(out, "api_version_major", this->api_version_major);
dump_field(out, "api_version_minor", this->api_version_minor);
}
@@ -743,8 +745,8 @@ void HelloResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HelloResponse");
dump_field(out, "api_version_major", this->api_version_major);
dump_field(out, "api_version_minor", this->api_version_minor);
dump_field(out, "server_info", this->server_info);
dump_field(out, "name", this->name);
dump_field(out, "server_info", this->server_info_ref_);
dump_field(out, "name", this->name_ref_);
}
void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); }
void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); }
@@ -755,32 +757,32 @@ void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfo
void AreaInfo::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "AreaInfo");
dump_field(out, "area_id", this->area_id);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
}
#endif
#ifdef USE_DEVICES
void DeviceInfo::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "DeviceInfo");
dump_field(out, "device_id", this->device_id);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
dump_field(out, "area_id", this->area_id);
}
#endif
void DeviceInfoResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "DeviceInfoResponse");
dump_field(out, "name", this->name);
dump_field(out, "mac_address", this->mac_address);
dump_field(out, "esphome_version", this->esphome_version);
dump_field(out, "compilation_time", this->compilation_time);
dump_field(out, "model", this->model);
dump_field(out, "name", this->name_ref_);
dump_field(out, "mac_address", this->mac_address_ref_);
dump_field(out, "esphome_version", this->esphome_version_ref_);
dump_field(out, "compilation_time", this->compilation_time_ref_);
dump_field(out, "model", this->model_ref_);
#ifdef USE_DEEP_SLEEP
dump_field(out, "has_deep_sleep", this->has_deep_sleep);
#endif
#ifdef ESPHOME_PROJECT_NAME
dump_field(out, "project_name", this->project_name);
dump_field(out, "project_name", this->project_name_ref_);
#endif
#ifdef ESPHOME_PROJECT_NAME
dump_field(out, "project_version", this->project_version);
dump_field(out, "project_version", this->project_version_ref_);
#endif
#ifdef USE_WEBSERVER
dump_field(out, "webserver_port", this->webserver_port);
@@ -788,16 +790,16 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
#ifdef USE_BLUETOOTH_PROXY
dump_field(out, "bluetooth_proxy_feature_flags", this->bluetooth_proxy_feature_flags);
#endif
dump_field(out, "manufacturer", this->manufacturer);
dump_field(out, "friendly_name", this->friendly_name);
dump_field(out, "manufacturer", this->manufacturer_ref_);
dump_field(out, "friendly_name", this->friendly_name_ref_);
#ifdef USE_VOICE_ASSISTANT
dump_field(out, "voice_assistant_feature_flags", this->voice_assistant_feature_flags);
#endif
#ifdef USE_AREAS
dump_field(out, "suggested_area", this->suggested_area);
dump_field(out, "suggested_area", this->suggested_area_ref_);
#endif
#ifdef USE_BLUETOOTH_PROXY
dump_field(out, "bluetooth_mac_address", this->bluetooth_mac_address);
dump_field(out, "bluetooth_mac_address", this->bluetooth_mac_address_ref_);
#endif
#ifdef USE_API_NOISE
dump_field(out, "api_encryption_supported", this->api_encryption_supported);
@@ -834,14 +836,14 @@ void SubscribeStatesRequest::dump_to(std::string &out) const { out.append("Subsc
#ifdef USE_BINARY_SENSOR
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesBinarySensorResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "device_class", this->device_class);
dump_field(out, "name", this->name_ref_);
dump_field(out, "device_class", this->device_class_ref_);
dump_field(out, "is_status_binary_sensor", this->is_status_binary_sensor);
dump_field(out, "disabled_by_default", this->disabled_by_default);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
#ifdef USE_DEVICES
@@ -861,16 +863,16 @@ void BinarySensorStateResponse::dump_to(std::string &out) const {
#ifdef USE_COVER
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesCoverResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
dump_field(out, "assumed_state", this->assumed_state);
dump_field(out, "supports_position", this->supports_position);
dump_field(out, "supports_tilt", this->supports_tilt);
dump_field(out, "device_class", this->device_class);
dump_field(out, "device_class", this->device_class_ref_);
dump_field(out, "disabled_by_default", this->disabled_by_default);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "supports_stop", this->supports_stop);
@@ -904,16 +906,16 @@ void CoverCommandRequest::dump_to(std::string &out) const {
#ifdef USE_FAN
void ListEntitiesFanResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesFanResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
dump_field(out, "supports_oscillation", this->supports_oscillation);
dump_field(out, "supports_speed", this->supports_speed);
dump_field(out, "supports_direction", this->supports_direction);
dump_field(out, "supported_speed_count", this->supported_speed_count);
dump_field(out, "disabled_by_default", this->disabled_by_default);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
for (const auto &it : *this->supported_preset_modes) {
@@ -930,7 +932,7 @@ void FanStateResponse::dump_to(std::string &out) const {
dump_field(out, "oscillating", this->oscillating);
dump_field(out, "direction", static_cast<enums::FanDirection>(this->direction));
dump_field(out, "speed_level", this->speed_level);
dump_field(out, "preset_mode", this->preset_mode);
dump_field(out, "preset_mode", this->preset_mode_ref_);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -947,7 +949,9 @@ void FanCommandRequest::dump_to(std::string &out) const {
dump_field(out, "has_speed_level", this->has_speed_level);
dump_field(out, "speed_level", this->speed_level);
dump_field(out, "has_preset_mode", this->has_preset_mode);
dump_field(out, "preset_mode", this->preset_mode);
out.append(" preset_mode: ");
out.append("'").append(this->preset_mode.c_str(), this->preset_mode.size()).append("'");
out.append("\n");
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -956,9 +960,9 @@ void FanCommandRequest::dump_to(std::string &out) const {
#ifdef USE_LIGHT
void ListEntitiesLightResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesLightResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
for (const auto &it : *this->supported_color_modes) {
dump_field(out, "supported_color_modes", static_cast<enums::ColorMode>(it), 4);
}
@@ -969,7 +973,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
}
dump_field(out, "disabled_by_default", this->disabled_by_default);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
#ifdef USE_DEVICES
@@ -990,7 +994,7 @@ void LightStateResponse::dump_to(std::string &out) const {
dump_field(out, "color_temperature", this->color_temperature);
dump_field(out, "cold_white", this->cold_white);
dump_field(out, "warm_white", this->warm_white);
dump_field(out, "effect", this->effect);
dump_field(out, "effect", this->effect_ref_);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1023,7 +1027,9 @@ void LightCommandRequest::dump_to(std::string &out) const {
dump_field(out, "has_flash_length", this->has_flash_length);
dump_field(out, "flash_length", this->flash_length);
dump_field(out, "has_effect", this->has_effect);
dump_field(out, "effect", this->effect);
out.append(" effect: ");
out.append("'").append(this->effect.c_str(), this->effect.size()).append("'");
out.append("\n");
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1032,16 +1038,16 @@ void LightCommandRequest::dump_to(std::string &out) const {
#ifdef USE_SENSOR
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesSensorResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "unit_of_measurement", this->unit_of_measurement);
dump_field(out, "unit_of_measurement", this->unit_of_measurement_ref_);
dump_field(out, "accuracy_decimals", this->accuracy_decimals);
dump_field(out, "force_update", this->force_update);
dump_field(out, "device_class", this->device_class);
dump_field(out, "device_class", this->device_class_ref_);
dump_field(out, "state_class", static_cast<enums::SensorStateClass>(this->state_class));
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
@@ -1062,16 +1068,16 @@ void SensorStateResponse::dump_to(std::string &out) const {
#ifdef USE_SWITCH
void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesSwitchResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "assumed_state", this->assumed_state);
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "device_class", this->device_class);
dump_field(out, "device_class", this->device_class_ref_);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1096,15 +1102,15 @@ void SwitchCommandRequest::dump_to(std::string &out) const {
#ifdef USE_TEXT_SENSOR
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesTextSensorResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "device_class", this->device_class);
dump_field(out, "device_class", this->device_class_ref_);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1112,7 +1118,7 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
void TextSensorStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "TextSensorStateResponse");
dump_field(out, "key", this->key);
dump_field(out, "state", this->state);
dump_field(out, "state", this->state_ref_);
dump_field(out, "missing_state", this->missing_state);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
@@ -1138,10 +1144,7 @@ void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const {
out.append(format_hex_pretty(this->key, this->key_len));
out.append("\n");
}
void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "NoiseEncryptionSetKeyResponse");
dump_field(out, "success", this->success);
}
void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const { dump_field(out, "success", this->success); }
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const {
@@ -1149,12 +1152,12 @@ void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const {
}
void HomeassistantServiceMap::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeassistantServiceMap");
dump_field(out, "key", this->key);
dump_field(out, "key", this->key_ref_);
dump_field(out, "value", this->value);
}
void HomeassistantActionRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeassistantActionRequest");
dump_field(out, "service", this->service);
dump_field(out, "service", this->service_ref_);
for (const auto &it : this->data) {
out.append(" data: ");
it.dump_to(out);
@@ -1187,7 +1190,9 @@ void HomeassistantActionResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeassistantActionResponse");
dump_field(out, "call_id", this->call_id);
dump_field(out, "success", this->success);
dump_field(out, "error_message", this->error_message);
out.append(" error_message: ");
out.append("'").append(this->error_message.c_str(), this->error_message.size()).append("'");
out.append("\n");
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
out.append(" response_data: ");
out.append(format_hex_pretty(this->response_data, this->response_data_len));
@@ -1201,32 +1206,40 @@ void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const {
}
void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "SubscribeHomeAssistantStateResponse");
dump_field(out, "entity_id", this->entity_id);
dump_field(out, "attribute", this->attribute);
dump_field(out, "entity_id", this->entity_id_ref_);
dump_field(out, "attribute", this->attribute_ref_);
dump_field(out, "once", this->once);
}
void HomeAssistantStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeAssistantStateResponse");
dump_field(out, "entity_id", this->entity_id);
dump_field(out, "state", this->state);
dump_field(out, "attribute", this->attribute);
out.append(" entity_id: ");
out.append("'").append(this->entity_id.c_str(), this->entity_id.size()).append("'");
out.append("\n");
out.append(" state: ");
out.append("'").append(this->state.c_str(), this->state.size()).append("'");
out.append("\n");
out.append(" attribute: ");
out.append("'").append(this->attribute.c_str(), this->attribute.size()).append("'");
out.append("\n");
}
#endif
void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
void GetTimeResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "GetTimeResponse");
dump_field(out, "epoch_seconds", this->epoch_seconds);
dump_field(out, "timezone", this->timezone);
out.append(" timezone: ");
out.append("'").append(this->timezone.c_str(), this->timezone.size()).append("'");
out.append("\n");
}
#ifdef USE_API_USER_DEFINED_ACTIONS
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesServicesArgument");
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
dump_field(out, "type", static_cast<enums::ServiceArgType>(this->type));
}
void ListEntitiesServicesResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesServicesResponse");
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
dump_field(out, "key", this->key);
for (const auto &it : this->args) {
out.append(" args: ");
@@ -1240,7 +1253,9 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
dump_field(out, "bool_", this->bool_);
dump_field(out, "legacy_int", this->legacy_int);
dump_field(out, "float_", this->float_);
dump_field(out, "string_", this->string_);
out.append(" string_: ");
out.append("'").append(this->string_.c_str(), this->string_.size()).append("'");
out.append("\n");
dump_field(out, "int_", this->int_);
for (const auto it : this->bool_array) {
dump_field(out, "bool_array", static_cast<bool>(it), 4);
@@ -1276,7 +1291,7 @@ void ExecuteServiceResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ExecuteServiceResponse");
dump_field(out, "call_id", this->call_id);
dump_field(out, "success", this->success);
dump_field(out, "error_message", this->error_message);
dump_field(out, "error_message", this->error_message_ref_);
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
out.append(" response_data: ");
out.append(format_hex_pretty(this->response_data, this->response_data_len));
@@ -1287,12 +1302,12 @@ void ExecuteServiceResponse::dump_to(std::string &out) const {
#ifdef USE_CAMERA
void ListEntitiesCameraResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesCameraResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
dump_field(out, "disabled_by_default", this->disabled_by_default);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
#ifdef USE_DEVICES
@@ -1319,9 +1334,9 @@ void CameraImageRequest::dump_to(std::string &out) const {
#ifdef USE_CLIMATE
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesClimateResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
dump_field(out, "supports_current_temperature", this->supports_current_temperature);
dump_field(out, "supports_two_point_target_temperature", this->supports_two_point_target_temperature);
for (const auto &it : *this->supported_modes) {
@@ -1348,7 +1363,7 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
}
dump_field(out, "disabled_by_default", this->disabled_by_default);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "visual_current_temperature_step", this->visual_current_temperature_step);
@@ -1372,9 +1387,9 @@ void ClimateStateResponse::dump_to(std::string &out) const {
dump_field(out, "action", static_cast<enums::ClimateAction>(this->action));
dump_field(out, "fan_mode", static_cast<enums::ClimateFanMode>(this->fan_mode));
dump_field(out, "swing_mode", static_cast<enums::ClimateSwingMode>(this->swing_mode));
dump_field(out, "custom_fan_mode", this->custom_fan_mode);
dump_field(out, "custom_fan_mode", this->custom_fan_mode_ref_);
dump_field(out, "preset", static_cast<enums::ClimatePreset>(this->preset));
dump_field(out, "custom_preset", this->custom_preset);
dump_field(out, "custom_preset", this->custom_preset_ref_);
dump_field(out, "current_humidity", this->current_humidity);
dump_field(out, "target_humidity", this->target_humidity);
#ifdef USE_DEVICES
@@ -1397,11 +1412,15 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
dump_field(out, "has_swing_mode", this->has_swing_mode);
dump_field(out, "swing_mode", static_cast<enums::ClimateSwingMode>(this->swing_mode));
dump_field(out, "has_custom_fan_mode", this->has_custom_fan_mode);
dump_field(out, "custom_fan_mode", this->custom_fan_mode);
out.append(" custom_fan_mode: ");
out.append("'").append(this->custom_fan_mode.c_str(), this->custom_fan_mode.size()).append("'");
out.append("\n");
dump_field(out, "has_preset", this->has_preset);
dump_field(out, "preset", static_cast<enums::ClimatePreset>(this->preset));
dump_field(out, "has_custom_preset", this->has_custom_preset);
dump_field(out, "custom_preset", this->custom_preset);
out.append(" custom_preset: ");
out.append("'").append(this->custom_preset.c_str(), this->custom_preset.size()).append("'");
out.append("\n");
dump_field(out, "has_target_humidity", this->has_target_humidity);
dump_field(out, "target_humidity", this->target_humidity);
#ifdef USE_DEVICES
@@ -1412,11 +1431,11 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
#ifdef USE_WATER_HEATER
void ListEntitiesWaterHeaterResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesWaterHeaterResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
@@ -1461,20 +1480,20 @@ void WaterHeaterCommandRequest::dump_to(std::string &out) const {
#ifdef USE_NUMBER
void ListEntitiesNumberResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesNumberResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "min_value", this->min_value);
dump_field(out, "max_value", this->max_value);
dump_field(out, "step", this->step);
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "unit_of_measurement", this->unit_of_measurement);
dump_field(out, "unit_of_measurement", this->unit_of_measurement_ref_);
dump_field(out, "mode", static_cast<enums::NumberMode>(this->mode));
dump_field(out, "device_class", this->device_class);
dump_field(out, "device_class", this->device_class_ref_);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1500,11 +1519,11 @@ void NumberCommandRequest::dump_to(std::string &out) const {
#ifdef USE_SELECT
void ListEntitiesSelectResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesSelectResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
for (const auto &it : *this->options) {
dump_field(out, "options", it, 4);
@@ -1518,7 +1537,7 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
void SelectStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "SelectStateResponse");
dump_field(out, "key", this->key);
dump_field(out, "state", this->state);
dump_field(out, "state", this->state_ref_);
dump_field(out, "missing_state", this->missing_state);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
@@ -1527,7 +1546,9 @@ void SelectStateResponse::dump_to(std::string &out) const {
void SelectCommandRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "SelectCommandRequest");
dump_field(out, "key", this->key);
dump_field(out, "state", this->state);
out.append(" state: ");
out.append("'").append(this->state.c_str(), this->state.size()).append("'");
out.append("\n");
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1536,11 +1557,11 @@ void SelectCommandRequest::dump_to(std::string &out) const {
#ifdef USE_SIREN
void ListEntitiesSirenResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesSirenResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
for (const auto &it : *this->tones) {
@@ -1567,7 +1588,9 @@ void SirenCommandRequest::dump_to(std::string &out) const {
dump_field(out, "has_state", this->has_state);
dump_field(out, "state", this->state);
dump_field(out, "has_tone", this->has_tone);
dump_field(out, "tone", this->tone);
out.append(" tone: ");
out.append("'").append(this->tone.c_str(), this->tone.size()).append("'");
out.append("\n");
dump_field(out, "has_duration", this->has_duration);
dump_field(out, "duration", this->duration);
dump_field(out, "has_volume", this->has_volume);
@@ -1580,18 +1603,18 @@ void SirenCommandRequest::dump_to(std::string &out) const {
#ifdef USE_LOCK
void ListEntitiesLockResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesLockResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "assumed_state", this->assumed_state);
dump_field(out, "supports_open", this->supports_open);
dump_field(out, "requires_code", this->requires_code);
dump_field(out, "code_format", this->code_format);
dump_field(out, "code_format", this->code_format_ref_);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1609,7 +1632,9 @@ void LockCommandRequest::dump_to(std::string &out) const {
dump_field(out, "key", this->key);
dump_field(out, "command", static_cast<enums::LockCommand>(this->command));
dump_field(out, "has_code", this->has_code);
dump_field(out, "code", this->code);
out.append(" code: ");
out.append("'").append(this->code.c_str(), this->code.size()).append("'");
out.append("\n");
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1618,15 +1643,15 @@ void LockCommandRequest::dump_to(std::string &out) const {
#ifdef USE_BUTTON
void ListEntitiesButtonResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesButtonResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "device_class", this->device_class);
dump_field(out, "device_class", this->device_class_ref_);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1642,7 +1667,7 @@ void ButtonCommandRequest::dump_to(std::string &out) const {
#ifdef USE_MEDIA_PLAYER
void MediaPlayerSupportedFormat::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "MediaPlayerSupportedFormat");
dump_field(out, "format", this->format);
dump_field(out, "format", this->format_ref_);
dump_field(out, "sample_rate", this->sample_rate);
dump_field(out, "num_channels", this->num_channels);
dump_field(out, "purpose", static_cast<enums::MediaPlayerFormatPurpose>(this->purpose));
@@ -1650,11 +1675,11 @@ void MediaPlayerSupportedFormat::dump_to(std::string &out) const {
}
void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesMediaPlayerResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
@@ -1687,7 +1712,9 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const {
dump_field(out, "has_volume", this->has_volume);
dump_field(out, "volume", this->volume);
dump_field(out, "has_media_url", this->has_media_url);
dump_field(out, "media_url", this->media_url);
out.append(" media_url: ");
out.append("'").append(this->media_url.c_str(), this->media_url.size()).append("'");
out.append("\n");
dump_field(out, "has_announcement", this->has_announcement);
dump_field(out, "announcement", this->announcement);
#ifdef USE_DEVICES
@@ -1731,10 +1758,7 @@ void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const {
dump_field(out, "mtu", this->mtu);
dump_field(out, "error", this->error);
}
void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothGATTGetServicesRequest");
dump_field(out, "address", this->address);
}
void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const { dump_field(out, "address", this->address); }
void BluetoothGATTDescriptor::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothGATTDescriptor");
for (const auto &it : this->uuid) {
@@ -1906,12 +1930,12 @@ void VoiceAssistantAudioSettings::dump_to(std::string &out) const {
void VoiceAssistantRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantRequest");
dump_field(out, "start", this->start);
dump_field(out, "conversation_id", this->conversation_id);
dump_field(out, "conversation_id", this->conversation_id_ref_);
dump_field(out, "flags", this->flags);
out.append(" audio_settings: ");
this->audio_settings.dump_to(out);
out.append("\n");
dump_field(out, "wake_word_phrase", this->wake_word_phrase);
dump_field(out, "wake_word_phrase", this->wake_word_phrase_ref_);
}
void VoiceAssistantResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantResponse");
@@ -1920,8 +1944,12 @@ void VoiceAssistantResponse::dump_to(std::string &out) const {
}
void VoiceAssistantEventData::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantEventData");
dump_field(out, "name", this->name);
dump_field(out, "value", this->value);
out.append(" name: ");
out.append("'").append(this->name.c_str(), this->name.size()).append("'");
out.append("\n");
out.append(" value: ");
out.append("'").append(this->value.c_str(), this->value.size()).append("'");
out.append("\n");
}
void VoiceAssistantEventResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantEventResponse");
@@ -1942,42 +1970,59 @@ void VoiceAssistantAudio::dump_to(std::string &out) const {
void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantTimerEventResponse");
dump_field(out, "event_type", static_cast<enums::VoiceAssistantTimerEvent>(this->event_type));
dump_field(out, "timer_id", this->timer_id);
dump_field(out, "name", this->name);
out.append(" timer_id: ");
out.append("'").append(this->timer_id.c_str(), this->timer_id.size()).append("'");
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name.c_str(), this->name.size()).append("'");
out.append("\n");
dump_field(out, "total_seconds", this->total_seconds);
dump_field(out, "seconds_left", this->seconds_left);
dump_field(out, "is_active", this->is_active);
}
void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantAnnounceRequest");
dump_field(out, "media_id", this->media_id);
dump_field(out, "text", this->text);
dump_field(out, "preannounce_media_id", this->preannounce_media_id);
out.append(" media_id: ");
out.append("'").append(this->media_id.c_str(), this->media_id.size()).append("'");
out.append("\n");
out.append(" text: ");
out.append("'").append(this->text.c_str(), this->text.size()).append("'");
out.append("\n");
out.append(" preannounce_media_id: ");
out.append("'").append(this->preannounce_media_id.c_str(), this->preannounce_media_id.size()).append("'");
out.append("\n");
dump_field(out, "start_conversation", this->start_conversation);
}
void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantAnnounceFinished");
dump_field(out, "success", this->success);
}
void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { dump_field(out, "success", this->success); }
void VoiceAssistantWakeWord::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantWakeWord");
dump_field(out, "id", this->id);
dump_field(out, "wake_word", this->wake_word);
dump_field(out, "id", this->id_ref_);
dump_field(out, "wake_word", this->wake_word_ref_);
for (const auto &it : this->trained_languages) {
dump_field(out, "trained_languages", it, 4);
}
}
void VoiceAssistantExternalWakeWord::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantExternalWakeWord");
dump_field(out, "id", this->id);
dump_field(out, "wake_word", this->wake_word);
out.append(" id: ");
out.append("'").append(this->id.c_str(), this->id.size()).append("'");
out.append("\n");
out.append(" wake_word: ");
out.append("'").append(this->wake_word.c_str(), this->wake_word.size()).append("'");
out.append("\n");
for (const auto &it : this->trained_languages) {
dump_field(out, "trained_languages", it, 4);
}
dump_field(out, "model_type", this->model_type);
out.append(" model_type: ");
out.append("'").append(this->model_type.c_str(), this->model_type.size()).append("'");
out.append("\n");
dump_field(out, "model_size", this->model_size);
dump_field(out, "model_hash", this->model_hash);
dump_field(out, "url", this->url);
out.append(" model_hash: ");
out.append("'").append(this->model_hash.c_str(), this->model_hash.size()).append("'");
out.append("\n");
out.append(" url: ");
out.append("'").append(this->url.c_str(), this->url.size()).append("'");
out.append("\n");
}
void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantConfigurationRequest");
@@ -2009,11 +2054,11 @@ void VoiceAssistantSetConfiguration::dump_to(std::string &out) const {
#ifdef USE_ALARM_CONTROL_PANEL
void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesAlarmControlPanelResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
@@ -2036,7 +2081,9 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "AlarmControlPanelCommandRequest");
dump_field(out, "key", this->key);
dump_field(out, "command", static_cast<enums::AlarmControlPanelStateCommand>(this->command));
dump_field(out, "code", this->code);
out.append(" code: ");
out.append("'").append(this->code.c_str(), this->code.size()).append("'");
out.append("\n");
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -2045,17 +2092,17 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
#ifdef USE_TEXT
void ListEntitiesTextResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesTextResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "min_length", this->min_length);
dump_field(out, "max_length", this->max_length);
dump_field(out, "pattern", this->pattern);
dump_field(out, "pattern", this->pattern_ref_);
dump_field(out, "mode", static_cast<enums::TextMode>(this->mode));
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
@@ -2064,7 +2111,7 @@ void ListEntitiesTextResponse::dump_to(std::string &out) const {
void TextStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "TextStateResponse");
dump_field(out, "key", this->key);
dump_field(out, "state", this->state);
dump_field(out, "state", this->state_ref_);
dump_field(out, "missing_state", this->missing_state);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
@@ -2073,7 +2120,9 @@ void TextStateResponse::dump_to(std::string &out) const {
void TextCommandRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "TextCommandRequest");
dump_field(out, "key", this->key);
dump_field(out, "state", this->state);
out.append(" state: ");
out.append("'").append(this->state.c_str(), this->state.size()).append("'");
out.append("\n");
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -2082,11 +2131,11 @@ void TextCommandRequest::dump_to(std::string &out) const {
#ifdef USE_DATETIME_DATE
void ListEntitiesDateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesDateResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
@@ -2119,11 +2168,11 @@ void DateCommandRequest::dump_to(std::string &out) const {
#ifdef USE_DATETIME_TIME
void ListEntitiesTimeResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesTimeResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
@@ -2156,15 +2205,15 @@ void TimeCommandRequest::dump_to(std::string &out) const {
#ifdef USE_EVENT
void ListEntitiesEventResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesEventResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "device_class", this->device_class);
dump_field(out, "device_class", this->device_class_ref_);
for (const auto &it : *this->event_types) {
dump_field(out, "event_types", it, 4);
}
@@ -2175,7 +2224,7 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const {
void EventResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "EventResponse");
dump_field(out, "key", this->key);
dump_field(out, "event_type", this->event_type);
dump_field(out, "event_type", this->event_type_ref_);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -2184,15 +2233,15 @@ void EventResponse::dump_to(std::string &out) const {
#ifdef USE_VALVE
void ListEntitiesValveResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesValveResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "device_class", this->device_class);
dump_field(out, "device_class", this->device_class_ref_);
dump_field(out, "assumed_state", this->assumed_state);
dump_field(out, "supports_position", this->supports_position);
dump_field(out, "supports_stop", this->supports_stop);
@@ -2223,11 +2272,11 @@ void ValveCommandRequest::dump_to(std::string &out) const {
#ifdef USE_DATETIME_DATETIME
void ListEntitiesDateTimeResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesDateTimeResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
@@ -2256,15 +2305,15 @@ void DateTimeCommandRequest::dump_to(std::string &out) const {
#ifdef USE_UPDATE
void ListEntitiesUpdateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesUpdateResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "device_class", this->device_class);
dump_field(out, "device_class", this->device_class_ref_);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -2276,11 +2325,11 @@ void UpdateStateResponse::dump_to(std::string &out) const {
dump_field(out, "in_progress", this->in_progress);
dump_field(out, "has_progress", this->has_progress);
dump_field(out, "progress", this->progress);
dump_field(out, "current_version", this->current_version);
dump_field(out, "latest_version", this->latest_version);
dump_field(out, "title", this->title);
dump_field(out, "release_summary", this->release_summary);
dump_field(out, "release_url", this->release_url);
dump_field(out, "current_version", this->current_version_ref_);
dump_field(out, "latest_version", this->latest_version_ref_);
dump_field(out, "title", this->title_ref_);
dump_field(out, "release_summary", this->release_summary_ref_);
dump_field(out, "release_url", this->release_url_ref_);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif

View File

@@ -125,18 +125,15 @@ void APIServer::loop() {
if (!sock)
break;
char peername[socket::SOCKADDR_STR_LEN];
sock->getpeername_to(peername);
// Check if we're at the connection limit
if (this->clients_.size() >= this->max_connections_) {
ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, peername);
ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, sock->getpeername().c_str());
// Immediately close - socket destructor will handle cleanup
sock.reset();
continue;
}
ESP_LOGD(TAG, "Accept %s", peername);
ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
auto *conn = new APIConnection(std::move(sock), this);
this->clients_.emplace_back(conn);
@@ -169,7 +166,8 @@ void APIServer::loop() {
// Network is down - disconnect all clients
for (auto &client : this->clients_) {
client->on_fatal_error();
client->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("Network down; disconnect"));
ESP_LOGW(TAG, "%s (%s): Network down; disconnect", client->client_info_.name.c_str(),
client->client_info_.peername.c_str());
}
// Continue to process and clean up the clients below
}
@@ -187,12 +185,12 @@ void APIServer::loop() {
// Rare case: handle disconnection
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
this->client_disconnected_trigger_->trigger(std::string(client->get_name()), std::string(client->get_peername()));
this->client_disconnected_trigger_->trigger(client->client_info_.name, client->client_info_.peername);
#endif
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
this->unregister_active_action_calls_for_connection(client.get());
#endif
ESP_LOGV(TAG, "Remove connection %s", client->get_name());
ESP_LOGV(TAG, "Remove connection %s", client->client_info_.name.c_str());
// Swap with the last element and pop (avoids expensive vector shifts)
if (client_index < this->clients_.size() - 1) {
@@ -390,8 +388,8 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef
#ifdef USE_API_HOMEASSISTANT_STATES
// Helper to add subscription (reduces duplication)
void APIServer::add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(StringRef)> f,
bool once) {
void APIServer::add_state_subscription_(const char *entity_id, const char *attribute,
std::function<void(std::string)> f, bool once) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
.entity_id = entity_id, .attribute = attribute, .callback = std::move(f), .once = once,
// entity_id_dynamic_storage and attribute_dynamic_storage remain nullptr (no heap allocation)
@@ -400,7 +398,7 @@ void APIServer::add_state_subscription_(const char *entity_id, const char *attri
// Helper to add subscription with heap-allocated strings (reduces duplication)
void APIServer::add_state_subscription_(std::string entity_id, optional<std::string> attribute,
std::function<void(StringRef)> f, bool once) {
std::function<void(std::string)> f, bool once) {
HomeAssistantStateSubscription sub;
// Allocate heap storage for the strings
sub.entity_id_dynamic_storage = std::make_unique<std::string>(std::move(entity_id));
@@ -420,43 +418,23 @@ void APIServer::add_state_subscription_(std::string entity_id, optional<std::str
// New const char* overload (for internal components - zero allocation)
void APIServer::subscribe_home_assistant_state(const char *entity_id, const char *attribute,
std::function<void(StringRef)> f) {
std::function<void(std::string)> f) {
this->add_state_subscription_(entity_id, attribute, std::move(f), false);
}
void APIServer::get_home_assistant_state(const char *entity_id, const char *attribute,
std::function<void(StringRef)> f) {
std::function<void(std::string)> f) {
this->add_state_subscription_(entity_id, attribute, std::move(f), true);
}
// std::string overload with StringRef callback (zero-allocation callback)
// Existing std::string overload (for custom_api_device.h - heap allocation)
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(StringRef)> f) {
std::function<void(std::string)> f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false);
}
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(StringRef)> f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true);
}
// Legacy helper: wraps std::string callback and delegates to StringRef version
void APIServer::add_state_subscription_(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f, bool once) {
// Wrap callback to convert StringRef -> std::string, then delegate
this->add_state_subscription_(std::move(entity_id), std::move(attribute),
std::function<void(StringRef)>([f = std::move(f)](StringRef state) { f(state.str()); }),
once);
}
// Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string)
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false);
}
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f) {
std::function<void(std::string)> f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true);
}

View File

@@ -10,7 +10,6 @@
#include "esphome/core/component.h"
#include "esphome/core/controller.h"
#include "esphome/core/log.h"
#include "esphome/core/string_ref.h"
#include "list_entities.h"
#include "subscribe_state.h"
#ifdef USE_LOGGER
@@ -192,7 +191,7 @@ class APIServer : public Component,
struct HomeAssistantStateSubscription {
const char *entity_id; // Pointer to flash (internal) or heap (external)
const char *attribute; // Pointer to flash or nullptr (nullptr means no attribute)
std::function<void(StringRef)> callback;
std::function<void(std::string)> callback;
bool once;
// Dynamic storage for external components using std::string API (custom_api_device.h)
@@ -202,20 +201,14 @@ class APIServer : public Component,
};
// New const char* overload (for internal components - zero allocation)
void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> f);
void get_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> f);
void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(std::string)> f);
void get_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(std::string)> f);
// std::string overload with StringRef callback (for custom_api_device.h with zero-allocation callback)
// Existing std::string overload (for custom_api_device.h - heap allocation)
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(StringRef)> f);
std::function<void(std::string)> f);
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(StringRef)> f);
// Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string for callback)
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f);
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f);
std::function<void(std::string)> f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
#endif
@@ -239,13 +232,10 @@ class APIServer : public Component,
#endif // USE_API_NOISE
#ifdef USE_API_HOMEASSISTANT_STATES
// Helper methods to reduce code duplication
void add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(StringRef)> f,
void add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(std::string)> f,
bool once);
void add_state_subscription_(std::string entity_id, optional<std::string> attribute, std::function<void(StringRef)> f,
bool once);
// Legacy helper: wraps std::string callback and delegates to StringRef version
void add_state_subscription_(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f, bool once);
std::function<void(std::string)> f, bool once);
#endif // USE_API_HOMEASSISTANT_STATES
// Pointers and pointer-like types first (4 bytes each)
std::unique_ptr<socket::Socket> socket_ = nullptr;

View File

@@ -122,36 +122,21 @@ class CustomAPIDevice {
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "climate.kitchen", "current_temperature");
* }
*
* void on_state_changed(StringRef state) {
* // State of climate.kitchen current_temperature is `state`
* // Use state.c_str() for C string, state.str() for std::string
* void on_state_changed(std::string state) {
* // State of sensor.weather_forecast is `state`
* }
* ```
*
* @tparam T The class type creating the service, automatically deduced from the function pointer.
* @param callback The member function to call when the entity state changes (zero-allocation).
* @param callback The member function to call when the entity state changes.
* @param entity_id The entity_id to track.
* @param attribute The entity state attribute to track.
*/
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(StringRef), const std::string &entity_id,
const std::string &attribute = "") {
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), std::move(f));
}
/** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version).
*
* @deprecated Use the StringRef overload for zero-allocation callbacks. Will be removed in 2027.1.0.
*/
template<typename T>
ESPDEPRECATED("Use void callback(StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0")
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
const std::string &attribute = "") {
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
// Explicit type to disambiguate overload resolution
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute),
std::function<void(const std::string &)>(f));
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
}
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
@@ -163,45 +148,23 @@ class CustomAPIDevice {
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
* }
*
* void on_state_changed(const std::string &entity_id, StringRef state) {
* void on_state_changed(std::string entity_id, std::string state) {
* // State of `entity_id` is `state`
* }
* ```
*
* @tparam T The class type creating the service, automatically deduced from the function pointer.
* @param callback The member function to call when the entity state changes (zero-allocation for state).
* @param callback The member function to call when the entity state changes.
* @param entity_id The entity_id to track.
* @param attribute The entity state attribute to track.
*/
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(const std::string &, StringRef), const std::string &entity_id,
const std::string &attribute = "") {
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), std::move(f));
}
/** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version).
*
* @deprecated Use the StringRef overload for zero-allocation callbacks. Will be removed in 2027.1.0.
*/
template<typename T>
ESPDEPRECATED("Use void callback(const std::string &, StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0")
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
const std::string &attribute = "") {
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
// Explicit type to disambiguate overload resolution
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute),
std::function<void(const std::string &)>(f));
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
}
#else
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(StringRef), const std::string &entity_id,
const std::string &attribute = "") {
static_assert(sizeof(T) == 0,
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
"of your YAML configuration");
}
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
const std::string &attribute = "") {
@@ -210,14 +173,6 @@ class CustomAPIDevice {
"of your YAML configuration");
}
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(const std::string &, StringRef), const std::string &entity_id,
const std::string &attribute = "") {
static_assert(sizeof(T) == 0,
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
"of your YAML configuration");
}
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
const std::string &attribute = "") {
@@ -240,7 +195,7 @@ class CustomAPIDevice {
*/
void call_homeassistant_service(const std::string &service_name) {
HomeassistantActionRequest resp;
resp.service = StringRef(service_name);
resp.set_service(StringRef(service_name));
global_api_server->send_homeassistant_action(resp);
}
@@ -260,12 +215,12 @@ class CustomAPIDevice {
*/
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
HomeassistantActionRequest resp;
resp.service = StringRef(service_name);
resp.set_service(StringRef(service_name));
resp.data.init(data.size());
for (auto &it : data) {
auto &kv = resp.data.emplace_back();
kv.key = StringRef(it.first);
kv.value = it.second; // value is std::string (no_zero_copy), assign directly
kv.set_key(StringRef(it.first));
kv.value = it.second;
}
global_api_server->send_homeassistant_action(resp);
}
@@ -282,7 +237,7 @@ class CustomAPIDevice {
*/
void fire_homeassistant_event(const std::string &event_name) {
HomeassistantActionRequest resp;
resp.service = StringRef(event_name);
resp.set_service(StringRef(event_name));
resp.is_event = true;
global_api_server->send_homeassistant_action(resp);
}
@@ -302,13 +257,13 @@ class CustomAPIDevice {
*/
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
HomeassistantActionRequest resp;
resp.service = StringRef(service_name);
resp.set_service(StringRef(service_name));
resp.is_event = true;
resp.data.init(data.size());
for (auto &it : data) {
auto &kv = resp.data.emplace_back();
kv.key = StringRef(it.first);
kv.value = it.second; // value is std::string (no_zero_copy), assign directly
kv.set_key(StringRef(it.first));
kv.value = it.second;
}
global_api_server->send_homeassistant_action(resp);
}

View File

@@ -147,7 +147,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
void play(const Ts &...x) override {
HomeassistantActionRequest resp;
std::string service_value = this->service_.value(x...);
resp.service = StringRef(service_value);
resp.set_service(StringRef(service_value));
resp.is_event = this->flags_.is_event;
this->populate_service_map(resp.data, this->data_, x...);
this->populate_service_map(resp.data_template, this->data_template_, x...);
@@ -209,7 +209,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
dest.init(source.size());
for (auto &it : source) {
auto &kv = dest.emplace_back();
kv.key = StringRef(it.key);
kv.set_key(StringRef(it.key));
kv.value = it.value.value(x...);
}
}

View File

@@ -54,16 +54,16 @@ inline constexpr int64_t decode_zigzag64(uint64_t value) {
* 3. Global/static strings: StringRef(GLOBAL_CONSTANT) - Always safe
* 4. Local variables: Safe ONLY if encoding happens before function returns:
* std::string temp = compute_value();
* msg.field = StringRef(temp);
* msg.set_field(StringRef(temp));
* return this->send_message(msg); // temp is valid during encoding
*
* Unsafe Patterns (WILL cause crashes/corruption):
* 1. Temporaries: msg.field = StringRef(obj.get_string()) // get_string() returns by value
* 2. Concatenation: msg.field = StringRef(str1 + str2) // Result is temporary
* 1. Temporaries: msg.set_field(StringRef(obj.get_string())) // get_string() returns by value
* 2. Concatenation: msg.set_field(StringRef(str1 + str2)) // Result is temporary
*
* For unsafe patterns, store in a local variable first:
* std::string temp = get_string(); // or str1 + str2
* msg.field = StringRef(temp);
* msg.set_field(StringRef(temp));
*
* The send_*_response pattern ensures proper lifetime management by encoding
* within the same function scope where temporaries are created.

View File

@@ -46,7 +46,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
ListEntitiesServicesResponse encode_list_service_response() override {
ListEntitiesServicesResponse msg;
msg.name = StringRef(this->name_);
msg.set_name(StringRef(this->name_));
msg.key = this->key_;
msg.supports_response = this->supports_response_;
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
@@ -54,7 +54,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
for (size_t i = 0; i < sizeof...(Ts); i++) {
auto &arg = msg.args.emplace_back();
arg.type = arg_types[i];
arg.name = StringRef(this->arg_names_[i]);
arg.set_name(StringRef(this->arg_names_[i]));
}
return msg;
}
@@ -108,7 +108,7 @@ template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor
ListEntitiesServicesResponse encode_list_service_response() override {
ListEntitiesServicesResponse msg;
msg.name = StringRef(this->name_);
msg.set_name(StringRef(this->name_));
msg.key = this->key_;
msg.supports_response = enums::SUPPORTS_RESPONSE_NONE; // Dynamic services don't support responses yet
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
@@ -116,7 +116,7 @@ template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor
for (size_t i = 0; i < sizeof...(Ts); i++) {
auto &arg = msg.args.emplace_back();
arg.type = arg_types[i];
arg.name = StringRef(this->arg_names_[i]);
arg.set_name(StringRef(this->arg_names_[i]));
}
return msg;
}

View File

@@ -10,37 +10,38 @@ namespace esphome::aqi {
class AQICalculator : public AbstractAQICalculator {
public:
uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_);
int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_);
return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index;
}
protected:
static constexpr int NUM_LEVELS = 6;
static const int AMOUNT_OF_LEVELS = 6;
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55}, {56, 125}, {126, 225}, {226, INT_MAX}};
int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55},
{56, 125}, {126, 225}, {226, INT_MAX}};
static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254},
{255, 354}, {355, 424}, {425, INT_MAX}};
int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254},
{255, 354}, {355, 424}, {425, INT_MAX}};
static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) {
int grid_index = get_grid_index(value, array);
int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
int grid_index = get_grid_index_(value, array);
if (grid_index == -1) {
return -1;
}
int aqi_lo = INDEX_GRID[grid_index][0];
int aqi_hi = INDEX_GRID[grid_index][1];
int aqi_lo = index_grid_[grid_index][0];
int aqi_hi = index_grid_[grid_index][1];
int conc_lo = array[grid_index][0];
int conc_hi = array[grid_index][1];
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
}
static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) {
for (int i = 0; i < NUM_LEVELS; i++) {
int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
for (int i = 0; i < AMOUNT_OF_LEVELS; i++) {
if (value >= array[i][0] && value <= array[i][1]) {
return i;
}

View File

@@ -1,52 +0,0 @@
#include "aqi_sensor.h"
#include "esphome/core/log.h"
namespace esphome::aqi {
static const char *const TAG = "aqi";
void AQISensor::setup() {
if (this->pm_2_5_sensor_ != nullptr) {
this->pm_2_5_sensor_->add_on_state_callback([this](float value) {
this->pm_2_5_value_ = value;
// Defer calculation to avoid double-publishing if both sensors update in the same loop
this->defer("update", [this]() { this->calculate_aqi_(); });
});
}
if (this->pm_10_0_sensor_ != nullptr) {
this->pm_10_0_sensor_->add_on_state_callback([this](float value) {
this->pm_10_0_value_ = value;
this->defer("update", [this]() { this->calculate_aqi_(); });
});
}
}
void AQISensor::dump_config() {
ESP_LOGCONFIG(TAG, "AQI Sensor:");
ESP_LOGCONFIG(TAG, " Calculation Type: %s", this->aqi_calc_type_ == AQI_TYPE ? "AQI" : "CAQI");
if (this->pm_2_5_sensor_ != nullptr) {
ESP_LOGCONFIG(TAG, " PM2.5 Sensor: '%s'", this->pm_2_5_sensor_->get_name().c_str());
}
if (this->pm_10_0_sensor_ != nullptr) {
ESP_LOGCONFIG(TAG, " PM10 Sensor: '%s'", this->pm_10_0_sensor_->get_name().c_str());
}
LOG_SENSOR(" ", "AQI", this);
}
void AQISensor::calculate_aqi_() {
if (std::isnan(this->pm_2_5_value_) || std::isnan(this->pm_10_0_value_)) {
return;
}
AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_);
if (calculator == nullptr) {
ESP_LOGW(TAG, "Unknown AQI calculator type");
return;
}
uint16_t aqi =
calculator->get_aqi(static_cast<uint16_t>(this->pm_2_5_value_), static_cast<uint16_t>(this->pm_10_0_value_));
this->publish_state(aqi);
}
} // namespace esphome::aqi

View File

@@ -1,31 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "aqi_calculator_factory.h"
namespace esphome::aqi {
class AQISensor : public sensor::Sensor, public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_pm_2_5_sensor(sensor::Sensor *sensor) { this->pm_2_5_sensor_ = sensor; }
void set_pm_10_0_sensor(sensor::Sensor *sensor) { this->pm_10_0_sensor_ = sensor; }
void set_aqi_calculation_type(AQICalculatorType type) { this->aqi_calc_type_ = type; }
protected:
void calculate_aqi_();
sensor::Sensor *pm_2_5_sensor_{nullptr};
sensor::Sensor *pm_10_0_sensor_{nullptr};
AQICalculatorType aqi_calc_type_{AQI_TYPE};
AQICalculatorFactory aqi_calculator_factory_;
float pm_2_5_value_{NAN};
float pm_10_0_value_{NAN};
};
} // namespace esphome::aqi

View File

@@ -1,5 +1,6 @@
#pragma once
#include "esphome/core/log.h"
#include "abstract_aqi_calculator.h"
namespace esphome::aqi {
@@ -7,37 +8,37 @@ namespace esphome::aqi {
class CAQICalculator : public AbstractAQICalculator {
public:
uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_);
int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_);
return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index;
}
protected:
static constexpr int NUM_LEVELS = 5;
static const int AMOUNT_OF_LEVELS = 5;
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}};
int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}};
static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 15}, {16, 30}, {31, 55}, {56, 110}, {111, 400}};
int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 15}, {16, 30}, {31, 55}, {56, 110}, {111, 400}};
static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 90}, {91, 180}, {181, 400}};
int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 25}, {26, 50}, {51, 90}, {91, 180}, {181, 400}};
static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) {
int grid_index = get_grid_index(value, array);
int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
int grid_index = get_grid_index_(value, array);
if (grid_index == -1) {
return -1;
}
int aqi_lo = INDEX_GRID[grid_index][0];
int aqi_hi = INDEX_GRID[grid_index][1];
int aqi_lo = index_grid_[grid_index][0];
int aqi_hi = index_grid_[grid_index][1];
int conc_lo = array[grid_index][0];
int conc_hi = array[grid_index][1];
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
}
static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) {
for (int i = 0; i < NUM_LEVELS; i++) {
int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
for (int i = 0; i < AMOUNT_OF_LEVELS; i++) {
if (value >= array[i][0] && value <= array[i][1]) {
return i;
}

View File

@@ -1,51 +0,0 @@
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_PM_2_5,
CONF_PM_10_0,
DEVICE_CLASS_AQI,
STATE_CLASS_MEASUREMENT,
)
from . import AQI_CALCULATION_TYPE, CONF_CALCULATION_TYPE, aqi_ns
CODEOWNERS = ["@jasstrong"]
DEPENDENCIES = ["sensor"]
UNIT_INDEX = "index"
AQISensor = aqi_ns.class_("AQISensor", sensor.Sensor, cg.Component)
CONFIG_SCHEMA = (
sensor.sensor_schema(
AQISensor,
unit_of_measurement=UNIT_INDEX,
accuracy_decimals=0,
device_class=DEVICE_CLASS_AQI,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(
{
cv.Required(CONF_PM_2_5): cv.use_id(sensor.Sensor),
cv.Required(CONF_PM_10_0): cv.use_id(sensor.Sensor),
cv.Required(CONF_CALCULATION_TYPE): cv.enum(
AQI_CALCULATION_TYPE, upper=True
),
}
)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
pm_2_5_sensor = await cg.get_variable(config[CONF_PM_2_5])
cg.add(var.set_pm_2_5_sensor(pm_2_5_sensor))
pm_10_0_sensor = await cg.get_variable(config[CONF_PM_10_0])
cg.add(var.set_pm_10_0_sensor(pm_10_0_sensor))
cg.add(var.set_aqi_calculation_type(config[CONF_CALCULATION_TYPE]))

View File

@@ -1,50 +1,37 @@
# Async TCP client support for all platforms
# Dummy integration to allow relying on AsyncTCP
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_LN882X,
PLATFORM_RTL87XX,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["network"]
def AUTO_LOAD() -> list[str]:
# Socket component needed for platforms using socket-based implementation
# ESP32, ESP8266, RP2040, and LibreTiny use AsyncTCP libraries, others use sockets
if (
not CORE.is_esp32
and not CORE.is_esp8266
and not CORE.is_rp2040
and not CORE.is_libretiny
):
return ["socket"]
return []
# Support all platforms - Arduino/ESP-IDF get libraries, other platforms use socket implementation
CONFIG_SCHEMA = cv.Schema({})
CONFIG_SCHEMA = cv.All(
cv.Schema({}),
cv.only_with_arduino,
cv.only_on(
[
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_BK72XX,
PLATFORM_LN882X,
PLATFORM_RTL87XX,
]
),
)
@coroutine_with_priority(CoroPriority.NETWORK_TRANSPORT)
async def to_code(config):
if CORE.is_esp32:
# https://github.com/ESP32Async/AsyncTCP
from esphome.components.esp32 import add_idf_component
add_idf_component(name="esp32async/asynctcp", ref="3.4.91")
elif CORE.is_libretiny:
if CORE.is_esp32 or CORE.is_libretiny:
# https://github.com/ESP32Async/AsyncTCP
cg.add_library("ESP32Async/AsyncTCP", "3.4.5")
elif CORE.is_esp8266:
# https://github.com/ESP32Async/ESPAsyncTCP
cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0")
elif CORE.is_rp2040:
# https://github.com/khoih-prog/AsyncTCP_RP2040W
cg.add_library("khoih-prog/AsyncTCP_RP2040W", "1.2.0")
# Other platforms (host, etc) use socket-based implementation
def FILTER_SOURCE_FILES() -> list[str]:
# Exclude socket implementation for platforms that use AsyncTCP libraries
if CORE.is_esp32 or CORE.is_esp8266 or CORE.is_rp2040 or CORE.is_libretiny:
return ["async_tcp_socket.cpp"]
return []

View File

@@ -1,16 +0,0 @@
#pragma once
#include "esphome/core/defines.h"
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
// Use AsyncTCP library for ESP32 (Arduino or ESP-IDF) and LibreTiny
#include <AsyncTCP.h>
#elif defined(USE_ESP8266)
// Use ESPAsyncTCP library for ESP8266 (always Arduino)
#include <ESPAsyncTCP.h>
#elif defined(USE_RP2040)
// Use AsyncTCP_RP2040W library for RP2040
#include <AsyncTCP_RP2040W.h>
#else
// Use socket-based implementation for other platforms
#include "async_tcp_socket.h"
#endif

View File

@@ -1,162 +0,0 @@
#include "async_tcp_socket.h"
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \
(defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS))
#include "esphome/components/network/util.h"
#include "esphome/core/log.h"
#include <cerrno>
#include <sys/select.h>
namespace esphome::async_tcp {
static const char *const TAG = "async_tcp";
// Read buffer size matches TCP MSS (1500 MTU - 40 bytes IP/TCP headers).
// This implementation only runs on ESP-IDF and host which have ample stack.
static constexpr size_t READ_BUFFER_SIZE = 1460;
bool AsyncClient::connect(const char *host, uint16_t port) {
if (connected_ || connecting_) {
ESP_LOGW(TAG, "Already connected/connecting");
return false;
}
// Resolve address
struct sockaddr_storage addr;
socklen_t addrlen = esphome::socket::set_sockaddr((struct sockaddr *) &addr, sizeof(addr), host, port);
if (addrlen == 0) {
ESP_LOGE(TAG, "Invalid address: %s", host);
if (error_cb_)
error_cb_(error_arg_, this, -1);
return false;
}
// Create socket with loop monitoring
int family = ((struct sockaddr *) &addr)->sa_family;
socket_ = esphome::socket::socket_loop_monitored(family, SOCK_STREAM, IPPROTO_TCP);
if (!socket_) {
ESP_LOGE(TAG, "Failed to create socket");
if (error_cb_)
error_cb_(error_arg_, this, -1);
return false;
}
socket_->setblocking(false);
int err = socket_->connect((struct sockaddr *) &addr, addrlen);
if (err == 0) {
// Connection succeeded immediately (rare, but possible for localhost)
connected_ = true;
if (connect_cb_)
connect_cb_(connect_arg_, this);
return true;
}
if (errno != EINPROGRESS) {
ESP_LOGE(TAG, "Connect failed: %d", errno);
close();
if (error_cb_)
error_cb_(error_arg_, this, errno);
return false;
}
connecting_ = true;
return true;
}
void AsyncClient::close() {
socket_.reset();
bool was_connected = connected_;
connected_ = false;
connecting_ = false;
if (was_connected && disconnect_cb_)
disconnect_cb_(disconnect_arg_, this);
}
size_t AsyncClient::write(const char *data, size_t len) {
if (!socket_ || !connected_)
return 0;
ssize_t sent = socket_->write(data, len);
if (sent < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
ESP_LOGE(TAG, "Write error: %d", errno);
close();
if (error_cb_)
error_cb_(error_arg_, this, errno);
}
return 0;
}
return sent;
}
void AsyncClient::loop() {
if (!socket_)
return;
if (connecting_) {
// For connecting, we need to check writability, not readability
// The Application's select() only monitors read FDs, so we do our own check here
// For ESP platforms lwip_select() might be faster, but this code isn't used
// on those platforms anyway. If it was, we'd fix the Application select()
// to report writability instead of doing it this way.
int fd = socket_->get_fd();
if (fd < 0) {
ESP_LOGW(TAG, "Invalid socket fd");
close();
return;
}
fd_set writefds;
FD_ZERO(&writefds);
FD_SET(fd, &writefds);
struct timeval tv = {0, 0};
int ret = select(fd + 1, nullptr, &writefds, nullptr, &tv);
if (ret > 0 && FD_ISSET(fd, &writefds)) {
int error = 0;
socklen_t len = sizeof(error);
if (socket_->getsockopt(SOL_SOCKET, SO_ERROR, &error, &len) == 0 && error == 0) {
connecting_ = false;
connected_ = true;
if (connect_cb_)
connect_cb_(connect_arg_, this);
} else {
ESP_LOGW(TAG, "Connection failed: %d", error);
close();
if (error_cb_)
error_cb_(error_arg_, this, error);
}
} else if (ret < 0) {
ESP_LOGE(TAG, "Select error: %d", errno);
close();
if (error_cb_)
error_cb_(error_arg_, this, errno);
}
} else if (connected_) {
// For connected sockets, use the Application's select() results
if (!socket_->ready())
return;
uint8_t buf[READ_BUFFER_SIZE];
ssize_t len = socket_->read(buf, READ_BUFFER_SIZE);
if (len == 0) {
ESP_LOGI(TAG, "Connection closed by peer");
close();
} else if (len > 0) {
if (data_cb_)
data_cb_(data_arg_, this, buf, len);
} else if (errno != EAGAIN && errno != EWOULDBLOCK) {
ESP_LOGW(TAG, "Read error: %d", errno);
close();
if (error_cb_)
error_cb_(error_arg_, this, errno);
}
}
}
} // namespace esphome::async_tcp
#endif

View File

@@ -1,73 +0,0 @@
#pragma once
#include "esphome/core/defines.h"
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \
(defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS))
#include "esphome/components/socket/socket.h"
#include <functional>
#include <memory>
#include <string>
#include <utility>
namespace esphome::async_tcp {
/// AsyncClient API for platforms using sockets (ESP-IDF, host, etc.)
/// NOTE: This class is NOT thread-safe. All methods must be called from the main loop.
class AsyncClient {
public:
using AcConnectHandler = std::function<void(void *, AsyncClient *)>;
using AcDataHandler = std::function<void(void *, AsyncClient *, void *data, size_t len)>;
using AcErrorHandler = std::function<void(void *, AsyncClient *, int8_t error)>;
AsyncClient() = default;
~AsyncClient() = default;
[[nodiscard]] bool connect(const char *host, uint16_t port);
void close();
[[nodiscard]] bool connected() const { return connected_; }
size_t write(const char *data, size_t len);
void onConnect(AcConnectHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming)
connect_cb_ = std::move(cb);
connect_arg_ = arg;
}
void onDisconnect(AcConnectHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming)
disconnect_cb_ = std::move(cb);
disconnect_arg_ = arg;
}
/// Set data callback. NOTE: data pointer is only valid during callback execution.
void onData(AcDataHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming)
data_cb_ = std::move(cb);
data_arg_ = arg;
}
void onError(AcErrorHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming)
error_cb_ = std::move(cb);
error_arg_ = arg;
}
// Must be called from loop()
void loop();
private:
std::unique_ptr<esphome::socket::Socket> socket_;
AcConnectHandler connect_cb_{nullptr};
void *connect_arg_{nullptr};
AcConnectHandler disconnect_cb_{nullptr};
void *disconnect_arg_{nullptr};
AcDataHandler data_cb_{nullptr};
void *data_arg_{nullptr};
AcErrorHandler error_cb_{nullptr};
void *error_arg_{nullptr};
bool connected_{false};
bool connecting_{false};
};
} // namespace esphome::async_tcp
// Expose AsyncClient in global namespace to match library behavior
using esphome::async_tcp::AsyncClient; // NOLINT(google-global-names-in-headers)
#endif

View File

@@ -21,9 +21,7 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
return false;
}
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
const char *addr_str = device.address_str_to(addr_buf);
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str);
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
bool success = false;
for (auto &service_data : device.get_service_datas()) {
@@ -34,7 +32,7 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device
if (!(parse_message_(service_data.data, *res))) {
continue;
}
if (!(report_results_(res, addr_str))) {
if (!(report_results_(res, device.address_str()))) {
continue;
}
if (res->temperature.has_value() && this->temperature_ != nullptr)
@@ -105,13 +103,13 @@ bool ATCMiThermometer::parse_message_(const std::vector<uint8_t> &message, Parse
return true;
}
bool ATCMiThermometer::report_results_(const optional<ParseResult> &result, const char *address) {
bool ATCMiThermometer::report_results_(const optional<ParseResult> &result, const std::string &address) {
if (!result.has_value()) {
ESP_LOGVV(TAG, "report_results(): no results available.");
return false;
}
ESP_LOGD(TAG, "Got ATC MiThermometer (%s):", address);
ESP_LOGD(TAG, "Got ATC MiThermometer (%s):", address.c_str());
if (result->temperature.has_value()) {
ESP_LOGD(TAG, " Temperature: %.1f °C", *result->temperature);

View File

@@ -41,7 +41,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice
optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data);
bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result);
bool report_results_(const optional<ParseResult> &result, const char *address);
bool report_results_(const optional<ParseResult> &result, const std::string &address);
};
} // namespace atc_mithermometer

View File

@@ -227,15 +227,14 @@ void ATM90E32Component::log_calibration_status_() {
}
if (offset_mismatch) {
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGW(TAG,
"[CALIBRATION][%s] ===================== Offset mismatch: using flash values =====================", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
cs);
"[CALIBRATION][%s] \n"
"[CALIBRATION][%s] ===================== Offset mismatch: using flash values =====================\n"
"[CALIBRATION][%s] ------------------------------------------------------------------------------\n"
"[CALIBRATION][%s] | Phase | offset_voltage | offset_current |\n"
"[CALIBRATION][%s] | | config | flash | config | flash |\n"
"[CALIBRATION][%s] ------------------------------------------------------------------------------",
cs, cs, cs, cs, cs, cs);
for (uint8_t phase = 0; phase < 3; ++phase) {
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6d | %6d | %6d | %6d |", cs, 'A' + phase,
this->config_offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].voltage_offset_,
@@ -245,15 +244,14 @@ void ATM90E32Component::log_calibration_status_() {
"[CALIBRATION][%s] ===============================================================================", cs);
}
if (power_mismatch) {
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGW(TAG,
"[CALIBRATION][%s] ================= Power offset mismatch: using flash values =================", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | offset_active_power|offset_reactive_power|", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
cs);
"[CALIBRATION][%s] \n"
"[CALIBRATION][%s] ================= Power offset mismatch: using flash values =================\n"
"[CALIBRATION][%s] ------------------------------------------------------------------------------\n"
"[CALIBRATION][%s] | Phase | offset_active_power|offset_reactive_power|\n"
"[CALIBRATION][%s] | | config | flash | config | flash |\n"
"[CALIBRATION][%s] ------------------------------------------------------------------------------",
cs, cs, cs, cs, cs, cs);
for (uint8_t phase = 0; phase < 3; ++phase) {
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6d | %6d | %6d | %6d |", cs, 'A' + phase,
this->config_power_offset_phase_[phase].active_power_offset,
@@ -265,15 +263,14 @@ void ATM90E32Component::log_calibration_status_() {
"[CALIBRATION][%s] ===============================================================================", cs);
}
if (gain_mismatch) {
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGW(TAG,
"[CALIBRATION][%s] ====================== Gain mismatch: using flash values =====================", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
cs);
"[CALIBRATION][%s] \n"
"[CALIBRATION][%s] ====================== Gain mismatch: using flash values =====================\n"
"[CALIBRATION][%s] ------------------------------------------------------------------------------\n"
"[CALIBRATION][%s] | Phase | voltage_gain | current_gain |\n"
"[CALIBRATION][%s] | | config | flash | config | flash |\n"
"[CALIBRATION][%s] ------------------------------------------------------------------------------",
cs, cs, cs, cs, cs, cs);
for (uint8_t phase = 0; phase < 3; ++phase) {
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6u | %6u | %6u | %6u |", cs, 'A' + phase,
this->config_gain_phase_[phase].voltage_gain, this->gain_phase_[phase].voltage_gain,
@@ -286,11 +283,13 @@ void ATM90E32Component::log_calibration_status_() {
ESP_LOGI(TAG, "[CALIBRATION][%s] Power & Voltage/Current offset calibration is disabled. Using config file values.",
cs);
} else if (this->restored_offset_calibration_ && !offset_mismatch) {
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ============== Restored offset calibration from memory ==============", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
ESP_LOGI(TAG,
"[CALIBRATION][%s]\n"
"[CALIBRATION][%s] ============== Restored offset calibration from memory ==============\n"
"[CALIBRATION][%s] --------------------------------------------------------------\n"
"[CALIBRATION][%s] | Phase | offset_voltage | offset_current |\n"
"[CALIBRATION][%s] --------------------------------------------------------------",
cs, cs, cs, cs, cs);
for (uint8_t phase = 0; phase < 3; phase++) {
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
this->offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].current_offset_);
@@ -299,11 +298,13 @@ void ATM90E32Component::log_calibration_status_() {
}
if (this->restored_power_offset_calibration_ && !power_mismatch) {
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ============ Restored power offset calibration from memory ============", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(TAG,
"[CALIBRATION][%s]\n"
"[CALIBRATION][%s] ============ Restored power offset calibration from memory ============\n"
"[CALIBRATION][%s] ---------------------------------------------------------------------\n"
"[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |\n"
"[CALIBRATION][%s] ---------------------------------------------------------------------",
cs, cs, cs, cs, cs);
for (uint8_t phase = 0; phase < 3; phase++) {
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
this->power_offset_phase_[phase].active_power_offset,
@@ -314,17 +315,21 @@ void ATM90E32Component::log_calibration_status_() {
if (!this->enable_gain_calibration_) {
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration is disabled. Using config file values.", cs);
} else if (this->restored_gain_calibration_ && !gain_mismatch) {
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ============ Restoring saved gain calibrations to registers ============", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(TAG,
"[CALIBRATION][%s]\n"
"[CALIBRATION][%s] ============ Restoring saved gain calibrations to registers ============\n"
"[CALIBRATION][%s] ---------------------------------------------------------------------\n"
"[CALIBRATION][%s] | Phase | voltage_gain | current_gain |\n"
"[CALIBRATION][%s] ---------------------------------------------------------------------",
cs, cs, cs, cs, cs);
for (uint8_t phase = 0; phase < 3; phase++) {
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase,
this->gain_phase_[phase].voltage_gain, this->gain_phase_[phase].current_gain);
}
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\\n", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration loaded and verified successfully.\n", cs);
ESP_LOGI(TAG,
"[CALIBRATION][%s] =====================================================================\n"
"[CALIBRATION][%s] Gain calibration loaded and verified successfully.\n",
cs, cs);
}
this->calibration_message_printed_ = true;
}
@@ -580,14 +585,14 @@ void ATM90E32Component::run_gain_calibrations() {
float ref_currents[3] = {this->get_reference_current(0), this->get_reference_current(1),
this->get_reference_current(2)};
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ========================= Gain Calibration =========================", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(
TAG,
"[CALIBRATION][%s] | Phase | V_meas (V) | I_meas (A) | V_ref | I_ref | V_gain (old→new) | I_gain (old→new) |",
cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
"[CALIBRATION][%s]\n"
"[CALIBRATION][%s] ========================= Gain Calibration =========================\n"
"[CALIBRATION][%s] ---------------------------------------------------------------------\n"
"[CALIBRATION][%s] | Phase | V_meas (V) | I_meas (A) | V_ref | I_ref | V_gain (old→new) | I_gain (old→new) |\n"
"[CALIBRATION][%s] ---------------------------------------------------------------------",
cs, cs, cs, cs, cs);
for (uint8_t phase = 0; phase < 3; phase++) {
float measured_voltage = this->get_phase_voltage_avg_(phase);
@@ -718,11 +723,13 @@ void ATM90E32Component::run_offset_calibrations() {
return;
}
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ======================== Offset Calibration ========================", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------", cs);
ESP_LOGI(TAG,
"[CALIBRATION][%s]\n"
"[CALIBRATION][%s] ======================== Offset Calibration ========================\n"
"[CALIBRATION][%s] ------------------------------------------------------------------\n"
"[CALIBRATION][%s] | Phase | offset_voltage | offset_current |\n"
"[CALIBRATION][%s] ------------------------------------------------------------------",
cs, cs, cs, cs, cs);
for (uint8_t phase = 0; phase < 3; phase++) {
int16_t voltage_offset = calibrate_offset(phase, true);
@@ -749,11 +756,13 @@ void ATM90E32Component::run_power_offset_calibrations() {
return;
}
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ===================== Power Offset Calibration =====================", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(TAG,
"[CALIBRATION][%s]\n"
"[CALIBRATION][%s] ===================== Power Offset Calibration =====================\n"
"[CALIBRATION][%s] ---------------------------------------------------------------------\n"
"[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |\n"
"[CALIBRATION][%s] ---------------------------------------------------------------------",
cs, cs, cs, cs, cs);
for (uint8_t phase = 0; phase < 3; ++phase) {
int16_t active_offset = calibrate_power_offset(phase, false);
@@ -953,10 +962,12 @@ void ATM90E32Component::restore_power_offset_calibrations_() {
void ATM90E32Component::clear_gain_calibrations() {
const char *cs = this->cs_summary_.c_str();
if (!this->using_saved_calibrations_) {
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored gain calibrations to clear. Current values:", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
ESP_LOGI(TAG,
"[CALIBRATION][%s] No stored gain calibrations to clear. Current values:\n"
"[CALIBRATION][%s] ----------------------------------------------------------\n"
"[CALIBRATION][%s] | Phase | voltage_gain | current_gain |\n"
"[CALIBRATION][%s] ----------------------------------------------------------",
cs, cs, cs, cs);
for (int phase = 0; phase < 3; phase++) {
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase,
this->gain_phase_[phase].voltage_gain, this->gain_phase_[phase].current_gain);
@@ -965,10 +976,12 @@ void ATM90E32Component::clear_gain_calibrations() {
return;
}
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored gain calibrations and restoring config-defined values", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
ESP_LOGI(TAG,
"[CALIBRATION][%s] Clearing stored gain calibrations and restoring config-defined values\n"
"[CALIBRATION][%s] ----------------------------------------------------------\n"
"[CALIBRATION][%s] | Phase | voltage_gain | current_gain |\n"
"[CALIBRATION][%s] ----------------------------------------------------------",
cs, cs, cs, cs);
for (int phase = 0; phase < 3; phase++) {
uint16_t voltage_gain = this->phase_[phase].voltage_gain_;
@@ -1002,10 +1015,12 @@ void ATM90E32Component::clear_gain_calibrations() {
void ATM90E32Component::clear_offset_calibrations() {
const char *cs = this->cs_summary_.c_str();
if (!this->restored_offset_calibration_) {
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored offset calibrations to clear. Current values:", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
ESP_LOGI(TAG,
"[CALIBRATION][%s] No stored offset calibrations to clear. Current values:\n"
"[CALIBRATION][%s] --------------------------------------------------------------\n"
"[CALIBRATION][%s] | Phase | offset_voltage | offset_current |\n"
"[CALIBRATION][%s] --------------------------------------------------------------",
cs, cs, cs, cs);
for (uint8_t phase = 0; phase < 3; phase++) {
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
this->offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].current_offset_);
@@ -1014,10 +1029,12 @@ void ATM90E32Component::clear_offset_calibrations() {
return;
}
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored offset calibrations and restoring config-defined values", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
ESP_LOGI(TAG,
"[CALIBRATION][%s] Clearing stored offset calibrations and restoring config-defined values\n"
"[CALIBRATION][%s] --------------------------------------------------------------\n"
"[CALIBRATION][%s] | Phase | offset_voltage | offset_current |\n"
"[CALIBRATION][%s] --------------------------------------------------------------",
cs, cs, cs, cs);
for (uint8_t phase = 0; phase < 3; phase++) {
int16_t voltage_offset =
@@ -1044,10 +1061,12 @@ void ATM90E32Component::clear_offset_calibrations() {
void ATM90E32Component::clear_power_offset_calibrations() {
const char *cs = this->cs_summary_.c_str();
if (!this->restored_power_offset_calibration_) {
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored power offsets to clear. Current values:", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(TAG,
"[CALIBRATION][%s] No stored power offsets to clear. Current values:\n"
"[CALIBRATION][%s] ---------------------------------------------------------------------\n"
"[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |\n"
"[CALIBRATION][%s] ---------------------------------------------------------------------",
cs, cs, cs, cs);
for (uint8_t phase = 0; phase < 3; phase++) {
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
this->power_offset_phase_[phase].active_power_offset,
@@ -1057,10 +1076,12 @@ void ATM90E32Component::clear_power_offset_calibrations() {
return;
}
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored power offsets and restoring config-defined values", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(TAG,
"[CALIBRATION][%s] Clearing stored power offsets and restoring config-defined values\n"
"[CALIBRATION][%s] ---------------------------------------------------------------------\n"
"[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |\n"
"[CALIBRATION][%s] ---------------------------------------------------------------------",
cs, cs, cs, cs);
for (uint8_t phase = 0; phase < 3; phase++) {
int16_t active_offset =

View File

@@ -13,9 +13,10 @@ void ATM90E32GainCalibrationButton::press_action() {
return;
}
ESP_LOGI(TAG, "%s", this->get_name().c_str());
ESP_LOGI(TAG,
"[CALIBRATION] Use gain_ct: & gain_voltage: under each phase_x: in your config file to save these values");
"%s\n"
"[CALIBRATION] Use gain_ct: & gain_voltage: under each phase_x: in your config file to save these values",
this->get_name().c_str());
this->parent_->run_gain_calibrations();
}
@@ -35,10 +36,12 @@ void ATM90E32OffsetCalibrationButton::press_action() {
return;
}
ESP_LOGI(TAG, "%s", this->get_name().c_str());
ESP_LOGI(TAG, "[CALIBRATION] **NOTE: CTs and ACVs must be 0 during this process. USB power only**");
ESP_LOGI(TAG, "[CALIBRATION] Use offset_voltage: & offset_current: under each phase_x: in your config file to save "
"these values");
ESP_LOGI(TAG,
"%s\n"
"[CALIBRATION] **NOTE: CTs and ACVs must be 0 during this process. USB power only**\n"
"[CALIBRATION] Use offset_voltage: & offset_current: under each phase_x: in your config file to save "
"these values",
this->get_name().c_str());
this->parent_->run_offset_calibrations();
}
@@ -58,10 +61,12 @@ void ATM90E32PowerOffsetCalibrationButton::press_action() {
return;
}
ESP_LOGI(TAG, "%s", this->get_name().c_str());
ESP_LOGI(TAG, "[CALIBRATION] **NOTE: CTs must be 0 during this process. Voltage reference should be present**");
ESP_LOGI(TAG, "[CALIBRATION] Use offset_active_power: & offset_reactive_power: under each phase_x: in your config "
"file to save these values");
ESP_LOGI(TAG,
"%s\n"
"[CALIBRATION] **NOTE: CTs must be 0 during this process. Voltage reference should be present**\n"
"[CALIBRATION] Use offset_active_power: & offset_reactive_power: under each phase_x: in your config "
"file to save these values",
this->get_name().c_str());
this->parent_->run_power_offset_calibrations();
}

View File

@@ -22,8 +22,7 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
return false;
}
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str_to(addr_buf));
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
const auto &service_datas = device.get_service_datas();
if (service_datas.size() != 1) {
ESP_LOGE(TAG, "Unexpected service_datas size (%d)", service_datas.size());

View File

@@ -3,7 +3,6 @@
#include "bedjet_hub.h"
#include "bedjet_child.h"
#include "bedjet_const.h"
#include "esphome/components/esp32_ble/ble_uuid.h"
#include "esphome/core/application.h"
#include <cinttypes>
@@ -194,9 +193,8 @@ bool BedJetHub::discover_characteristics_() {
result = false;
} else if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
char uuid_buf[espbt::UUID_STR_LEN];
ESP_LOGW(TAG, "Config descriptor 0x%x (uuid %s) is not a client config char uuid", this->char_handle_status_,
descr->uuid.to_str(uuid_buf));
descr->uuid.to_string().c_str());
result = false;
} else {
this->config_descr_status_ = descr->handle;

View File

@@ -1,8 +1,8 @@
#include "bh1750.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome::bh1750 {
namespace esphome {
namespace bh1750 {
static const char *const TAG = "bh1750.sensor";
@@ -13,31 +13,6 @@ static const uint8_t BH1750_COMMAND_ONE_TIME_L = 0b00100011;
static const uint8_t BH1750_COMMAND_ONE_TIME_H = 0b00100000;
static const uint8_t BH1750_COMMAND_ONE_TIME_H2 = 0b00100001;
static constexpr uint32_t MEASUREMENT_TIMEOUT_MS = 2000;
static constexpr float HIGH_LIGHT_THRESHOLD_LX = 7000.0f;
// Measurement time constants (datasheet values)
static constexpr uint16_t MTREG_DEFAULT = 69;
static constexpr uint16_t MTREG_MIN = 31;
static constexpr uint16_t MTREG_MAX = 254;
static constexpr uint16_t MEAS_TIME_L_MS = 24; // L-resolution max measurement time @ mtreg=69
static constexpr uint16_t MEAS_TIME_H_MS = 180; // H/H2-resolution max measurement time @ mtreg=69
// Conversion constants (datasheet formulas)
static constexpr float RESOLUTION_DIVISOR = 1.2f; // counts to lux conversion divisor
static constexpr float MODE_H2_DIVISOR = 2.0f; // H2 mode has 2x higher resolution
// MTreg calculation constants
static constexpr int COUNTS_TARGET = 50000; // Target counts for optimal range (avoid saturation)
static constexpr int COUNTS_NUMERATOR = 10;
static constexpr int COUNTS_DENOMINATOR = 12;
// MTreg register bit manipulation constants
static constexpr uint8_t MTREG_HI_SHIFT = 5; // High 3 bits start at bit 5
static constexpr uint8_t MTREG_HI_MASK = 0b111; // 3-bit mask for high bits
static constexpr uint8_t MTREG_LO_SHIFT = 0; // Low 5 bits start at bit 0
static constexpr uint8_t MTREG_LO_MASK = 0b11111; // 5-bit mask for low bits
/*
bh1750 properties:
@@ -68,7 +43,74 @@ void BH1750Sensor::setup() {
this->mark_failed();
return;
}
this->state_ = IDLE;
}
void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f) {
// turn on (after one-shot sensor automatically powers down)
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Power on failed");
f(NAN);
return;
}
if (active_mtreg_ != mtreg) {
// set mtreg
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> 5) & 0b111);
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> 0) & 0b11111);
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Set measurement time failed");
active_mtreg_ = 0;
f(NAN);
return;
}
active_mtreg_ = mtreg;
}
uint8_t cmd;
uint16_t meas_time;
switch (mode) {
case BH1750_MODE_L:
cmd = BH1750_COMMAND_ONE_TIME_L;
meas_time = 24 * mtreg / 69;
break;
case BH1750_MODE_H:
cmd = BH1750_COMMAND_ONE_TIME_H;
meas_time = 180 * mtreg / 69;
break;
case BH1750_MODE_H2:
cmd = BH1750_COMMAND_ONE_TIME_H2;
meas_time = 180 * mtreg / 69;
break;
default:
f(NAN);
return;
}
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Start measurement failed");
f(NAN);
return;
}
// probably not needed, but adjust for rounding
meas_time++;
this->set_timeout("read", meas_time, [this, mode, mtreg, f]() {
uint16_t raw_value;
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Read data failed");
f(NAN);
return;
}
raw_value = i2c::i2ctohs(raw_value);
float lx = float(raw_value) / 1.2f;
lx *= 69.0f / mtreg;
if (mode == BH1750_MODE_H2)
lx /= 2.0f;
f(lx);
});
}
void BH1750Sensor::dump_config() {
@@ -82,189 +124,45 @@ void BH1750Sensor::dump_config() {
}
void BH1750Sensor::update() {
const uint32_t now = millis();
// Start coarse measurement to determine optimal mode/mtreg
if (this->state_ != IDLE) {
// Safety timeout: reset if stuck
if (now - this->measurement_start_time_ > MEASUREMENT_TIMEOUT_MS) {
ESP_LOGW(TAG, "Measurement timeout, resetting state");
this->state_ = IDLE;
} else {
ESP_LOGW(TAG, "Previous measurement not complete, skipping update");
// first do a quick measurement in L-mode with full range
// to find right range
this->read_lx_(BH1750_MODE_L, 31, [this](float val) {
if (std::isnan(val)) {
this->status_set_warning();
this->publish_state(NAN);
return;
}
}
if (!this->start_measurement_(BH1750_MODE_L, MTREG_MIN, now)) {
this->status_set_warning();
this->publish_state(NAN);
return;
}
this->state_ = WAITING_COARSE_MEASUREMENT;
this->enable_loop(); // Enable loop while measurement in progress
}
void BH1750Sensor::loop() {
const uint32_t now = App.get_loop_component_start_time();
switch (this->state_) {
case IDLE:
// Disable loop when idle to save cycles
this->disable_loop();
break;
case WAITING_COARSE_MEASUREMENT:
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
this->state_ = READING_COARSE_RESULT;
}
break;
case READING_COARSE_RESULT: {
float lx;
if (!this->read_measurement_(lx)) {
this->fail_and_reset_();
break;
}
this->process_coarse_result_(lx);
// Start fine measurement with optimal settings
// fetch millis() again since the read can take a bit
if (!this->start_measurement_(this->fine_mode_, this->fine_mtreg_, millis())) {
this->fail_and_reset_();
break;
}
this->state_ = WAITING_FINE_MEASUREMENT;
break;
BH1750Mode use_mode;
uint8_t use_mtreg;
if (val <= 7000) {
use_mode = BH1750_MODE_H2;
use_mtreg = 254;
} else {
use_mode = BH1750_MODE_H;
// lx = counts / 1.2 * (69 / mtreg)
// -> mtreg = counts / 1.2 * (69 / lx)
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
// -> mtreg = 50000*(10/12)*(69/lx)
int ideal_mtreg = 50000 * 10 * 69 / (12 * (int) val);
use_mtreg = std::min(254, std::max(31, ideal_mtreg));
}
ESP_LOGV(TAG, "L result: %f -> Calculated mode=%d, mtreg=%d", val, (int) use_mode, use_mtreg);
case WAITING_FINE_MEASUREMENT:
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
this->state_ = READING_FINE_RESULT;
this->read_lx_(use_mode, use_mtreg, [this](float val) {
if (std::isnan(val)) {
this->status_set_warning();
this->publish_state(NAN);
return;
}
break;
case READING_FINE_RESULT: {
float lx;
if (!this->read_measurement_(lx)) {
this->fail_and_reset_();
break;
}
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), lx);
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), val);
this->status_clear_warning();
this->publish_state(lx);
this->state_ = IDLE;
break;
}
}
}
bool BH1750Sensor::start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now) {
// Power on
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Power on failed");
return false;
}
// Set MTreg if changed
if (this->active_mtreg_ != mtreg) {
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> MTREG_HI_SHIFT) & MTREG_HI_MASK);
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> MTREG_LO_SHIFT) & MTREG_LO_MASK);
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Set measurement time failed");
this->active_mtreg_ = 0;
return false;
}
this->active_mtreg_ = mtreg;
}
// Start measurement
uint8_t cmd;
uint16_t meas_time;
switch (mode) {
case BH1750_MODE_L:
cmd = BH1750_COMMAND_ONE_TIME_L;
meas_time = MEAS_TIME_L_MS * mtreg / MTREG_DEFAULT;
break;
case BH1750_MODE_H:
cmd = BH1750_COMMAND_ONE_TIME_H;
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
break;
case BH1750_MODE_H2:
cmd = BH1750_COMMAND_ONE_TIME_H2;
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
break;
default:
return false;
}
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Start measurement failed");
return false;
}
// Store current measurement parameters
this->current_mode_ = mode;
this->current_mtreg_ = mtreg;
this->measurement_start_time_ = now;
this->measurement_duration_ = meas_time + 1; // Add 1ms for safety
return true;
}
bool BH1750Sensor::read_measurement_(float &lx_out) {
uint16_t raw_value;
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Read data failed");
return false;
}
raw_value = i2c::i2ctohs(raw_value);
float lx = float(raw_value) / RESOLUTION_DIVISOR;
lx *= float(MTREG_DEFAULT) / this->current_mtreg_;
if (this->current_mode_ == BH1750_MODE_H2) {
lx /= MODE_H2_DIVISOR;
}
lx_out = lx;
return true;
}
void BH1750Sensor::process_coarse_result_(float lx) {
if (std::isnan(lx)) {
// Use defaults if coarse measurement failed
this->fine_mode_ = BH1750_MODE_H2;
this->fine_mtreg_ = MTREG_MAX;
return;
}
if (lx <= HIGH_LIGHT_THRESHOLD_LX) {
this->fine_mode_ = BH1750_MODE_H2;
this->fine_mtreg_ = MTREG_MAX;
} else {
this->fine_mode_ = BH1750_MODE_H;
// lx = counts / 1.2 * (69 / mtreg)
// -> mtreg = counts / 1.2 * (69 / lx)
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
// -> mtreg = 50000*(10/12)*(69/lx)
int ideal_mtreg = COUNTS_TARGET * COUNTS_NUMERATOR * MTREG_DEFAULT / (COUNTS_DENOMINATOR * (int) lx);
this->fine_mtreg_ = std::min((int) MTREG_MAX, std::max((int) MTREG_MIN, ideal_mtreg));
}
ESP_LOGV(TAG, "L result: %.1f -> Calculated mode=%d, mtreg=%d", lx, (int) this->fine_mode_, this->fine_mtreg_);
}
void BH1750Sensor::fail_and_reset_() {
this->status_set_warning();
this->publish_state(NAN);
this->state_ = IDLE;
this->publish_state(val);
});
});
}
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
} // namespace esphome::bh1750
} // namespace bh1750
} // namespace esphome

View File

@@ -4,9 +4,10 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome::bh1750 {
namespace esphome {
namespace bh1750 {
enum BH1750Mode : uint8_t {
enum BH1750Mode {
BH1750_MODE_L,
BH1750_MODE_H,
BH1750_MODE_H2,
@@ -20,36 +21,13 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
void setup() override;
void dump_config() override;
void update() override;
void loop() override;
float get_setup_priority() const override;
protected:
// State machine states
enum State : uint8_t {
IDLE,
WAITING_COARSE_MEASUREMENT,
READING_COARSE_RESULT,
WAITING_FINE_MEASUREMENT,
READING_FINE_RESULT,
};
void read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f);
// 4-byte aligned members
uint32_t measurement_start_time_{0};
uint32_t measurement_duration_{0};
// 1-byte members grouped together to minimize padding
State state_{IDLE};
BH1750Mode current_mode_{BH1750_MODE_L};
uint8_t current_mtreg_{31};
BH1750Mode fine_mode_{BH1750_MODE_H2};
uint8_t fine_mtreg_{254};
uint8_t active_mtreg_{0};
// Helper methods
bool start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now);
bool read_measurement_(float &lx_out);
void process_coarse_result_(float lx);
void fail_and_reset_();
};
} // namespace esphome::bh1750
} // namespace bh1750
} // namespace esphome

View File

@@ -186,10 +186,8 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
char char_buf[esp32_ble::UUID_STR_LEN];
char service_buf[esp32_ble::UUID_STR_LEN];
esph_log_w("ble_write_action", "Characteristic %s was not found in service %s",
this->char_uuid_.to_str(char_buf), this->service_uuid_.to_str(service_buf));
this->char_uuid_.to_string().c_str(), this->service_uuid_.to_string().c_str());
break;
}
this->char_handle_ = chr->handle;
@@ -201,13 +199,11 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP;
esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP");
} else {
char char_buf[esp32_ble::UUID_STR_LEN];
esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_str(char_buf));
esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_string().c_str());
break;
}
this->node_state = espbt::ClientState::ESTABLISHED;
char char_buf[esp32_ble::UUID_STR_LEN];
esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_str(char_buf),
esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(),
ble_client_->address_str());
break;
}

View File

@@ -9,15 +9,12 @@ static const char *const TAG = "ble_binary_output";
void BLEBinaryOutput::dump_config() {
ESP_LOGCONFIG(TAG, "BLE Binary Output:");
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
this->service_uuid_.to_str(service_buf);
this->char_uuid_.to_str(char_buf);
ESP_LOGCONFIG(TAG,
" MAC address : %s\n"
" Service UUID : %s\n"
" Characteristic UUID: %s",
this->parent_->address_str(), service_buf, char_buf);
this->parent_->address_str(), this->service_uuid_.to_string().c_str(),
this->char_uuid_.to_string().c_str());
LOG_BINARY_OUTPUT(this);
}
@@ -27,10 +24,8 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
char char_buf[esp32_ble::UUID_STR_LEN];
char service_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "Characteristic %s was not found in service %s", this->char_uuid_.to_str(char_buf),
this->service_uuid_.to_str(service_buf));
ESP_LOGW(TAG, "Characteristic %s was not found in service %s", this->char_uuid_.to_string().c_str(),
this->service_uuid_.to_string().c_str());
break;
}
this->char_handle_ = chr->handle;
@@ -42,24 +37,20 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP;
ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP");
} else {
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGE(TAG, "Characteristic %s does not allow writing with%s response", this->char_uuid_.to_str(char_buf),
ESP_LOGE(TAG, "Characteristic %s does not allow writing with%s response", this->char_uuid_.to_string().c_str(),
this->require_response_ ? "" : "out");
break;
}
this->node_state = espbt::ClientState::ESTABLISHED;
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_str(char_buf),
ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(),
this->parent()->address_str());
this->node_state = espbt::ClientState::ESTABLISHED;
break;
}
case ESP_GATTC_WRITE_CHAR_EVT: {
if (param->write.handle == this->char_handle_) {
if (param->write.status != 0) {
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_str(char_buf), param->write.status);
}
if (param->write.status != 0)
ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status);
}
break;
}
@@ -69,19 +60,18 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
}
void BLEBinaryOutput::write_state(bool state) {
char char_buf[esp32_ble::UUID_STR_LEN];
if (this->node_state != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.",
this->char_uuid_.to_str(char_buf));
this->char_uuid_.to_string().c_str());
return;
}
uint8_t state_as_uint = (uint8_t) state;
ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_str(char_buf), state_as_uint);
ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint);
esp_err_t err =
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_,
sizeof(state_as_uint), &state_as_uint, this->write_type_, ESP_GATT_AUTH_REQ_NONE);
if (err != ESP_GATT_OK)
ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_str(char_buf), err);
ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_string().c_str(), err);
}
} // namespace esphome::ble_client

View File

@@ -18,17 +18,14 @@ void BLESensor::loop() {
void BLESensor::dump_config() {
LOG_SENSOR("", "BLE Sensor", this);
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
char descr_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGCONFIG(TAG,
" MAC address : %s\n"
" Service UUID : %s\n"
" Characteristic UUID: %s\n"
" Descriptor UUID : %s\n"
" Notifications : %s",
this->parent()->address_str(), this->service_uuid_.to_str(service_buf),
this->char_uuid_.to_str(char_buf), this->descr_uuid_.to_str(descr_buf), YESNO(this->notify_));
this->parent()->address_str(), this->service_uuid_.to_string().c_str(),
this->char_uuid_.to_string().c_str(), this->descr_uuid_.to_string().c_str(), YESNO(this->notify_));
LOG_UPDATE_INTERVAL(this);
}
@@ -54,10 +51,8 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
if (chr == nullptr) {
this->status_set_warning();
this->publish_state(NAN);
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf),
this->char_uuid_.to_str(char_buf));
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->char_uuid_.to_string().c_str());
break;
}
this->handle = chr->handle;
@@ -66,12 +61,9 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
if (descr == nullptr) {
this->status_set_warning();
this->publish_state(NAN);
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
char descr_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s",
this->service_uuid_.to_str(service_buf), this->char_uuid_.to_str(char_buf),
this->descr_uuid_.to_str(descr_buf));
this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(),
this->descr_uuid_.to_string().c_str());
break;
}
this->handle = descr->handle;
@@ -117,8 +109,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
break;
}
this->node_state = espbt::ClientState::ESTABLISHED;
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGD(TAG, "Register for notify on %s complete", this->char_uuid_.to_str(char_buf));
ESP_LOGD(TAG, "Register for notify on %s complete", this->char_uuid_.to_string().c_str());
}
break;
}

View File

@@ -21,17 +21,14 @@ void BLETextSensor::loop() {
void BLETextSensor::dump_config() {
LOG_TEXT_SENSOR("", "BLE Text Sensor", this);
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
char descr_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGCONFIG(TAG,
" MAC address : %s\n"
" Service UUID : %s\n"
" Characteristic UUID: %s\n"
" Descriptor UUID : %s\n"
" Notifications : %s",
this->parent()->address_str(), this->service_uuid_.to_str(service_buf),
this->char_uuid_.to_str(char_buf), this->descr_uuid_.to_str(descr_buf), YESNO(this->notify_));
this->parent()->address_str(), this->service_uuid_.to_string().c_str(),
this->char_uuid_.to_string().c_str(), this->descr_uuid_.to_string().c_str(), YESNO(this->notify_));
LOG_UPDATE_INTERVAL(this);
}
@@ -56,10 +53,8 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
if (chr == nullptr) {
this->status_set_warning();
this->publish_state(EMPTY);
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf),
this->char_uuid_.to_str(char_buf));
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->char_uuid_.to_string().c_str());
break;
}
this->handle = chr->handle;
@@ -68,12 +63,9 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
if (descr == nullptr) {
this->status_set_warning();
this->publish_state(EMPTY);
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
char descr_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s",
this->service_uuid_.to_str(service_buf), this->char_uuid_.to_str(char_buf),
this->descr_uuid_.to_str(descr_buf));
this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(),
this->descr_uuid_.to_string().c_str());
break;
}
this->handle = descr->handle;

View File

@@ -1,8 +1,7 @@
#pragma once
#include <cinttypes>
#include <cstdio>
#include <ctime>
#include <string>
#include "esphome/core/component.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
@@ -16,13 +15,17 @@ namespace ble_scanner {
class BLEScanner : public text_sensor::TextSensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component {
public:
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
// Format JSON using stack buffer to avoid heap allocations from string concatenation
char buf[128];
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
snprintf(buf, sizeof(buf), "{\"timestamp\":%" PRId64 ",\"address\":\"%s\",\"rssi\":%d,\"name\":\"%s\"}",
static_cast<int64_t>(::time(nullptr)), device.address_str_to(addr_buf), device.get_rssi(),
device.get_name().c_str());
this->publish_state(buf);
this->publish_state("{\"timestamp\":" + to_string(::time(nullptr)) +
","
"\"address\":\"" +
device.address_str() +
"\","
"\"rssi\":" +
to_string(device.get_rssi()) +
","
"\"name\":\"" +
device.get_name() + "\"}");
return true;
}
void dump_config() override;

View File

@@ -4,7 +4,6 @@
#include "esphome/core/log.h"
#include <array>
#include <span>
#ifdef USE_ESP32
@@ -13,14 +12,15 @@ namespace bthome_mithermometer {
static const char *const TAG = "bthome_mithermometer";
static const char *format_mac_address(std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buffer, uint64_t address) {
static std::string format_mac_address(uint64_t address) {
std::array<uint8_t, MAC_ADDRESS_SIZE> mac{};
for (size_t i = 0; i < MAC_ADDRESS_SIZE; i++) {
mac[i] = (address >> ((MAC_ADDRESS_SIZE - 1 - i) * 8)) & 0xFF;
}
format_mac_addr_upper(mac.data(), buffer.data());
return buffer.data();
char buffer[MAC_ADDRESS_SIZE * 3];
format_mac_addr_upper(mac.data(), buffer);
return buffer;
}
static bool get_bthome_value_length(uint8_t obj_type, size_t &value_length) {
@@ -127,9 +127,8 @@ static bool get_bthome_value_length(uint8_t obj_type, size_t &value_length) {
}
void BTHomeMiThermometer::dump_config() {
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
ESP_LOGCONFIG(TAG, "BTHome MiThermometer");
ESP_LOGCONFIG(TAG, " MAC Address: %s", format_mac_address(addr_buf, this->address_));
ESP_LOGCONFIG(TAG, " MAC Address: %s", format_mac_address(this->address_).c_str());
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
@@ -173,9 +172,8 @@ bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceD
return false;
}
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
if (is_encrypted) {
ESP_LOGV(TAG, "Ignoring encrypted BTHome frame from %s", device.address_str_to(addr_buf));
ESP_LOGV(TAG, "Ignoring encrypted BTHome frame from %s", device.address_str().c_str());
return false;
}
@@ -195,7 +193,7 @@ bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceD
}
if (source_address != this->address_) {
ESP_LOGVV(TAG, "BTHome frame from unexpected device %s", format_mac_address(addr_buf, source_address));
ESP_LOGVV(TAG, "BTHome frame from unexpected device %s", format_mac_address(source_address).c_str());
return false;
}
@@ -288,7 +286,7 @@ bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceD
}
if (reported) {
ESP_LOGD(TAG, "BTHome data%sfrom %s", is_trigger_based ? " (triggered) " : " ", device.address_str_to(addr_buf));
ESP_LOGD(TAG, "BTHome data%sfrom %s", is_trigger_based ? " (triggered) " : " ", device.address_str().c_str());
}
return reported;

View File

@@ -7,7 +7,6 @@ from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_AP,
CONF_COMPRESSION,
CONF_ID,
PLATFORM_BK72XX,
PLATFORM_ESP32,
@@ -44,7 +43,6 @@ CONFIG_SCHEMA = cv.All(
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
web_server_base.WebServerBase
),
cv.Optional(CONF_COMPRESSION, default="br"): cv.one_of("br", "gzip"),
}
).extend(cv.COMPONENT_SCHEMA),
cv.only_on(
@@ -98,9 +96,6 @@ async def to_code(config):
await cg.register_component(var, config)
cg.add_define("USE_CAPTIVE_PORTAL")
if config[CONF_COMPRESSION] == "gzip":
cg.add_define("USE_CAPTIVE_PORTAL_GZIP")
if CORE.using_arduino:
if CORE.is_esp8266:
cg.add_library("DNSServer", None)

View File

@@ -3,158 +3,87 @@
#include "esphome/core/hal.h"
namespace esphome::captive_portal {
namespace esphome {
namespace captive_portal {
#ifdef USE_CAPTIVE_PORTAL_GZIP
const uint8_t INDEX_GZ[] PROGMEM = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x95, 0x56, 0xeb, 0x6f, 0xd4, 0x38, 0x10, 0xff, 0xce,
0x5f, 0xe1, 0x33, 0x8f, 0x26, 0xd0, 0x3c, 0xb7, 0xdb, 0x96, 0x6c, 0x12, 0x04, 0xdc, 0x21, 0x90, 0x28, 0x20, 0xb5,
0x70, 0x1f, 0x10, 0x52, 0xbd, 0xc9, 0x64, 0x63, 0x9a, 0x38, 0x39, 0xdb, 0xfb, 0x62, 0xb5, 0xf7, 0xb7, 0xdf, 0x38,
0xc9, 0x6e, 0xb7, 0x15, 0x9c, 0xee, 0x5a, 0x35, 0x1d, 0xdb, 0xf3, 0xf8, 0xcd, 0x78, 0x1e, 0x8e, 0x7f, 0xcb, 0x9b,
0x4c, 0xaf, 0x5b, 0x20, 0xa5, 0xae, 0xab, 0x34, 0x36, 0x5f, 0x52, 0x31, 0x31, 0x4b, 0x40, 0xe0, 0x0a, 0x58, 0x9e,
0xc6, 0x35, 0x68, 0x46, 0xb2, 0x92, 0x49, 0x05, 0x3a, 0xf9, 0x7c, 0xf5, 0xc6, 0x39, 0x4f, 0xe3, 0x8a, 0x8b, 0x1b,
0x22, 0xa1, 0x4a, 0x78, 0xd6, 0x08, 0x52, 0x4a, 0x28, 0x92, 0x9c, 0x69, 0x16, 0xf1, 0x9a, 0xcd, 0x60, 0x10, 0x11,
0xac, 0x86, 0x64, 0xc1, 0x61, 0xd9, 0x36, 0x52, 0x13, 0xe4, 0xd3, 0x20, 0x74, 0x42, 0x97, 0x3c, 0xd7, 0x65, 0x92,
0xc3, 0x82, 0x67, 0xe0, 0x74, 0x8b, 0x63, 0x2e, 0xb8, 0xe6, 0xac, 0x72, 0x54, 0xc6, 0x2a, 0x48, 0x82, 0xe3, 0xb9,
0x02, 0xd9, 0x2d, 0xd8, 0x14, 0xd7, 0xa2, 0xa1, 0x69, 0xac, 0x32, 0xc9, 0x5b, 0x4d, 0x0c, 0xd4, 0xa4, 0x6e, 0xf2,
0x79, 0x05, 0xa9, 0xe7, 0x31, 0x85, 0x90, 0x94, 0xc7, 0x45, 0x0e, 0x2b, 0x77, 0xea, 0x67, 0x99, 0x0f, 0xe7, 0xe7,
0xee, 0x77, 0xf5, 0x00, 0x9d, 0x9a, 0xd7, 0x68, 0xcd, 0xad, 0x9a, 0x8c, 0x69, 0xde, 0x08, 0x57, 0x01, 0x93, 0x59,
0x99, 0x24, 0x09, 0x7d, 0xa1, 0xd8, 0x02, 0xe8, 0x93, 0x27, 0xd6, 0x9e, 0x69, 0x06, 0xfa, 0x8f, 0x0a, 0x0c, 0xa9,
0x5e, 0xad, 0xaf, 0xd8, 0xec, 0x03, 0x02, 0xb7, 0x28, 0x53, 0x3c, 0x07, 0x6a, 0x7f, 0xf5, 0xbf, 0xb9, 0x4a, 0xaf,
0x2b, 0x70, 0x73, 0xae, 0xda, 0x8a, 0xad, 0x13, 0x3a, 0x45, 0xad, 0x37, 0xd4, 0x9e, 0x14, 0x73, 0x91, 0x19, 0xe5,
0x44, 0x59, 0x60, 0x6f, 0x2a, 0x40, 0x78, 0xc9, 0x05, 0xd3, 0xa5, 0x5b, 0xb3, 0x95, 0xd5, 0x13, 0x5c, 0x58, 0xe1,
0x53, 0x0b, 0x9e, 0x05, 0xbe, 0x6f, 0x1f, 0x77, 0x1f, 0xdf, 0xf6, 0xf0, 0xff, 0x44, 0x82, 0x9e, 0x4b, 0x41, 0x98,
0x75, 0x1d, 0xb7, 0xc8, 0x49, 0xf2, 0x84, 0x5e, 0x04, 0x21, 0x09, 0x9e, 0xbb, 0xe1, 0xf8, 0xbd, 0x7b, 0x46, 0x4e,
0xf0, 0x7f, 0x76, 0xe6, 0x8c, 0x49, 0x70, 0x82, 0x9f, 0x30, 0x74, 0xc7, 0xc4, 0xff, 0x41, 0x49, 0xc1, 0xab, 0x2a,
0xa1, 0xa2, 0x11, 0x40, 0x89, 0xd2, 0xb2, 0xb9, 0x81, 0x84, 0x66, 0x73, 0x29, 0x11, 0xfb, 0xeb, 0xa6, 0x6a, 0x24,
0xf5, 0xd2, 0x07, 0xff, 0x4b, 0xa1, 0x96, 0x4c, 0xa8, 0xa2, 0x91, 0x75, 0x42, 0xbb, 0xe8, 0x5b, 0x8f, 0x36, 0x7a,
0x4b, 0xcc, 0xc7, 0x3e, 0x38, 0x74, 0x1a, 0xc9, 0x67, 0x5c, 0x24, 0xd4, 0x68, 0x3c, 0x47, 0x23, 0xd7, 0xf6, 0x76,
0xef, 0x3d, 0x33, 0xde, 0x0f, 0xfe, 0x34, 0xd6, 0xd7, 0xeb, 0x58, 0x2d, 0x66, 0x64, 0x55, 0x57, 0x42, 0x25, 0xb4,
0xd4, 0xba, 0x8d, 0x3c, 0x6f, 0xb9, 0x5c, 0xba, 0xcb, 0x91, 0xdb, 0xc8, 0x99, 0x17, 0xfa, 0xbe, 0xef, 0x21, 0x07,
0x25, 0x7d, 0x22, 0xd0, 0xf0, 0x84, 0x92, 0x12, 0xf8, 0xac, 0xd4, 0x1d, 0x9d, 0x3e, 0xda, 0xc0, 0x36, 0x36, 0x1c,
0xe9, 0xf5, 0xb7, 0x03, 0x2b, 0xfc, 0xc0, 0x0a, 0xbc, 0x60, 0x16, 0xdd, 0xb9, 0x79, 0xd4, 0xb9, 0x79, 0xc6, 0x42,
0x12, 0x12, 0xbf, 0xfb, 0x0d, 0x1d, 0x43, 0x0f, 0x2b, 0xe7, 0xde, 0x8a, 0x1c, 0xac, 0x0c, 0x55, 0x9f, 0x3a, 0xcf,
0xf7, 0xb2, 0x81, 0xd9, 0x59, 0x04, 0xfe, 0xed, 0x86, 0x11, 0x78, 0x7b, 0x7a, 0xb8, 0x76, 0xc2, 0x2f, 0x87, 0x0c,
0xc6, 0x5a, 0x19, 0x7c, 0x39, 0x65, 0x63, 0x32, 0x1e, 0x76, 0xc6, 0x8e, 0xa1, 0xf7, 0x2b, 0x32, 0x5e, 0x20, 0x47,
0xed, 0x9c, 0x3a, 0x63, 0x36, 0x22, 0xa3, 0x01, 0x08, 0x52, 0xb8, 0x7d, 0x8a, 0x82, 0x07, 0x7b, 0xce, 0xe8, 0xc7,
0x91, 0x97, 0x52, 0x3b, 0xa2, 0xf4, 0xd6, 0xf3, 0xe6, 0xd0, 0x73, 0xf7, 0x7b, 0x83, 0x39, 0x45, 0x29, 0x46, 0x06,
0x74, 0x56, 0x5a, 0xd4, 0xc3, 0xc2, 0x2a, 0xf8, 0x0c, 0xb3, 0xbe, 0x11, 0xd4, 0x76, 0x75, 0x09, 0xc2, 0xda, 0x89,
0x1a, 0x41, 0xe8, 0x4e, 0xac, 0xfb, 0x27, 0xda, 0xde, 0xec, 0xf3, 0x5f, 0x73, 0x8d, 0x65, 0xa6, 0x5d, 0x53, 0xb0,
0xc7, 0xfb, 0xdd, 0x69, 0x93, 0xaf, 0x7f, 0x51, 0x1a, 0x65, 0xd0, 0xd7, 0x05, 0x17, 0x02, 0xe4, 0x15, 0xac, 0xf0,
0xe6, 0x2e, 0x5e, 0xbe, 0x26, 0x2f, 0xf3, 0x5c, 0x82, 0x52, 0x11, 0xa1, 0xcf, 0x34, 0xd6, 0x40, 0xf6, 0xdf, 0x75,
0x05, 0x77, 0x74, 0xfd, 0xc9, 0xdf, 0x70, 0xf2, 0x01, 0xf4, 0xb2, 0x91, 0x37, 0x83, 0x36, 0x03, 0x6d, 0x62, 0x2a,
0x4c, 0x22, 0x4e, 0xd6, 0x2a, 0x57, 0x55, 0xd8, 0x3e, 0xac, 0xc0, 0x46, 0x3b, 0xed, 0xad, 0x57, 0x62, 0x17, 0xa8,
0xeb, 0x38, 0xe7, 0x0b, 0x92, 0x55, 0xd8, 0x21, 0xb0, 0x5c, 0x7a, 0x55, 0x94, 0x3c, 0x20, 0xdd, 0x4f, 0x23, 0x32,
0x94, 0xbe, 0x49, 0xe8, 0x4f, 0x3a, 0xc0, 0xab, 0xf5, 0xbb, 0xdc, 0x3a, 0x52, 0x58, 0xfb, 0x47, 0xb6, 0xbb, 0x60,
0xd5, 0x1c, 0x48, 0x42, 0x74, 0xc9, 0xd5, 0x2d, 0xc0, 0xc9, 0x2f, 0xc5, 0x5a, 0x75, 0x83, 0x52, 0x05, 0x1e, 0x2b,
0xcb, 0xa6, 0xe9, 0x60, 0x2e, 0x66, 0x7d, 0x83, 0xa4, 0x0f, 0xe9, 0x3d, 0x44, 0x4e, 0x05, 0x85, 0xde, 0xf3, 0x11,
0x2c, 0x3b, 0x65, 0x09, 0x57, 0xa2, 0x75, 0x7b, 0xbb, 0xdf, 0x8c, 0x55, 0xcb, 0xc4, 0x7d, 0x41, 0x03, 0xd0, 0x94,
0x0a, 0x36, 0x36, 0xa4, 0x4c, 0xbd, 0x20, 0xd3, 0xde, 0xa0, 0xc7, 0x76, 0xe4, 0xa3, 0x0d, 0x47, 0x8d, 0xa6, 0x5f,
0xed, 0x35, 0xc6, 0x1e, 0x86, 0x26, 0xbd, 0xde, 0xda, 0xb7, 0x7e, 0xfc, 0x35, 0x07, 0xb9, 0xbe, 0x84, 0x0a, 0x32,
0xdd, 0x48, 0x8b, 0x3e, 0x44, 0x2b, 0x98, 0x4a, 0x9d, 0xc3, 0x6f, 0xaf, 0x2e, 0xde, 0x27, 0x8d, 0x25, 0xed, 0xe3,
0x5f, 0x71, 0x9b, 0x51, 0xf0, 0x15, 0x47, 0xc1, 0xdf, 0xc9, 0x91, 0x19, 0x06, 0x47, 0xdf, 0x50, 0xb4, 0xf3, 0xf7,
0xfa, 0x76, 0x22, 0x98, 0x72, 0x7e, 0x86, 0x2d, 0xe1, 0xd8, 0x78, 0xe8, 0x9c, 0x8e, 0xed, 0x2d, 0xda, 0x47, 0x04,
0x88, 0xbb, 0xeb, 0xeb, 0xd8, 0xdf, 0x4d, 0x8b, 0x4d, 0x9f, 0x6e, 0xa6, 0xcd, 0xca, 0x51, 0xfc, 0x07, 0x17, 0xb3,
0x88, 0x8b, 0x12, 0x24, 0xd7, 0x5b, 0x84, 0x8b, 0x13, 0xa2, 0x9d, 0xeb, 0x4d, 0xcb, 0xf2, 0xdc, 0x9c, 0x8c, 0xdb,
0xd5, 0xa4, 0xc0, 0x79, 0x62, 0x38, 0x21, 0x0a, 0xa0, 0xde, 0xf6, 0xe7, 0x5d, 0x47, 0x89, 0x9e, 0x8f, 0x1f, 0x6f,
0x4d, 0xc2, 0x6d, 0x34, 0x5e, 0x96, 0xc3, 0x2a, 0x3e, 0x13, 0x51, 0x86, 0xc0, 0x41, 0xf6, 0x42, 0x05, 0xab, 0x79,
0xb5, 0x8e, 0x14, 0xf6, 0x36, 0x07, 0x07, 0x0d, 0x2f, 0xb6, 0xd3, 0xb9, 0xd6, 0x8d, 0x40, 0xdb, 0x32, 0x07, 0x19,
0xf9, 0x93, 0x9e, 0x70, 0x24, 0xcb, 0xf9, 0x5c, 0x45, 0xee, 0x48, 0x42, 0x3d, 0x99, 0xb2, 0xec, 0x66, 0x26, 0x9b,
0xb9, 0xc8, 0x9d, 0xcc, 0x74, 0xda, 0xe8, 0x61, 0x50, 0xb0, 0x11, 0x64, 0x93, 0x61, 0x55, 0x14, 0xc5, 0x04, 0x43,
0x01, 0x4e, 0xdf, 0xcb, 0xa2, 0xd0, 0x3d, 0x31, 0x62, 0x07, 0x30, 0xdd, 0xd0, 0x6c, 0xf4, 0x18, 0x71, 0x04, 0x3c,
0x9e, 0xec, 0xdc, 0xf1, 0x27, 0xd8, 0xc2, 0x15, 0x2a, 0x69, 0xb1, 0xb6, 0x11, 0xe6, 0xb6, 0x66, 0x5c, 0x1c, 0xa2,
0x37, 0x69, 0x32, 0x19, 0xc6, 0x0f, 0x86, 0xa5, 0x33, 0xd3, 0x0d, 0xa1, 0x09, 0x0e, 0x98, 0x7e, 0x86, 0x46, 0xe1,
0xa9, 0xdf, 0xae, 0xb6, 0xee, 0x90, 0x20, 0x9b, 0x1d, 0x77, 0x51, 0xc1, 0x6a, 0xf2, 0x7d, 0xae, 0x34, 0x2f, 0xd6,
0xce, 0x30, 0x83, 0x23, 0x4c, 0x16, 0x9c, 0xbd, 0x53, 0x64, 0x05, 0x10, 0x93, 0xce, 0x86, 0xc3, 0x35, 0xd4, 0x6a,
0x88, 0xd3, 0x5e, 0x4d, 0x97, 0xa0, 0x77, 0x75, 0xfd, 0x1b, 0xb7, 0xc9, 0xc5, 0x4d, 0xcd, 0x24, 0x8e, 0x0a, 0x67,
0xda, 0x60, 0x4c, 0xeb, 0xc8, 0x39, 0xc3, 0xbb, 0x1a, 0xb6, 0x8c, 0x32, 0xf4, 0x1c, 0x61, 0x76, 0xb3, 0x75, 0x17,
0xef, 0xa0, 0x5d, 0x11, 0xd5, 0x54, 0x3c, 0x1f, 0xf8, 0x3a, 0x16, 0xe2, 0xef, 0xc3, 0x13, 0xe0, 0x75, 0x13, 0xb3,
0xb7, 0x0b, 0xf5, 0x49, 0x71, 0xce, 0x02, 0xff, 0x27, 0x37, 0x92, 0x17, 0x45, 0x38, 0x2d, 0xf6, 0x91, 0x32, 0x63,
0xd2, 0x94, 0x46, 0x97, 0x5a, 0xb1, 0xd7, 0xbf, 0x66, 0x4c, 0x66, 0xe0, 0x03, 0x05, 0x23, 0x8c, 0xef, 0x9b, 0x80,
0xf0, 0x3c, 0xc1, 0x4e, 0x95, 0x1e, 0xb4, 0x2f, 0x64, 0x0c, 0x76, 0x47, 0x48, 0xdd, 0x69, 0x46, 0xfd, 0x59, 0x87,
0x3e, 0x7d, 0xdd, 0x60, 0x7d, 0x60, 0xdb, 0x11, 0x33, 0xa2, 0x1b, 0x32, 0x84, 0xc0, 0x75, 0xdd, 0x78, 0x2a, 0xd3,
0x4f, 0x15, 0x30, 0x05, 0x64, 0xc9, 0xb8, 0x76, 0xb1, 0x1a, 0x3b, 0xfe, 0xbe, 0x8e, 0x51, 0x29, 0xb2, 0xa6, 0x43,
0xc1, 0xc6, 0xe5, 0xa8, 0x37, 0x70, 0x09, 0xda, 0x68, 0x32, 0x06, 0x46, 0x69, 0x6c, 0x46, 0x2e, 0x61, 0x5d, 0x4b,
0x4b, 0xbc, 0x25, 0x2f, 0xb8, 0x79, 0xb2, 0xa4, 0x71, 0x97, 0xe4, 0x46, 0x83, 0x89, 0x73, 0xff, 0xbc, 0xea, 0xa8,
0x0a, 0xc4, 0x0c, 0x27, 0xe9, 0x28, 0x24, 0xe8, 0x76, 0x06, 0x65, 0x53, 0x61, 0x58, 0x93, 0xcb, 0xcb, 0x77, 0xbf,
0xa7, 0x06, 0xcc, 0xad, 0x1c, 0xf6, 0xa7, 0x5e, 0xcc, 0x10, 0x83, 0xd4, 0xe9, 0x49, 0xff, 0xa8, 0x6a, 0xb1, 0xbf,
0xa0, 0x07, 0xf9, 0x1d, 0x1d, 0x9f, 0x86, 0xcd, 0x5e, 0x4f, 0xf7, 0xd7, 0x95, 0x4a, 0x7a, 0x89, 0x80, 0x62, 0x6f,
0x58, 0xc4, 0x9e, 0x01, 0xdc, 0x9f, 0x97, 0x03, 0x1f, 0xc6, 0xe9, 0xe3, 0xd5, 0x4b, 0xf2, 0xb9, 0xc5, 0x26, 0x00,
0x7d, 0xd8, 0x3a, 0xaf, 0xf0, 0x65, 0x58, 0x36, 0x79, 0xf2, 0xe9, 0xe3, 0xe5, 0xd5, 0xde, 0xc3, 0x79, 0xc7, 0x44,
0x40, 0x64, 0xfd, 0xf3, 0x6e, 0x5e, 0x69, 0xde, 0x32, 0xa9, 0x3b, 0xb5, 0x8e, 0xe9, 0x22, 0x3b, 0x1f, 0xba, 0x73,
0x7c, 0x03, 0x41, 0xef, 0x46, 0x2f, 0x98, 0x92, 0x1d, 0xaa, 0x9d, 0xb5, 0x7b, 0xb8, 0xbc, 0xfe, 0xb6, 0xbd, 0xfe,
0xea, 0xbd, 0xee, 0xa5, 0xfb, 0x0f, 0x53, 0x58, 0x46, 0xb2, 0xf9, 0x0a, 0x00, 0x00};
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x95, 0x16, 0x6b, 0x8f, 0xdb, 0x36, 0xf2, 0x7b, 0x7e,
0x05, 0x8f, 0x49, 0xbb, 0x52, 0xb3, 0x7a, 0x7a, 0xed, 0x6c, 0x24, 0x51, 0x45, 0x9a, 0xbb, 0xa2, 0x05, 0x9a, 0x36,
0xc0, 0x6e, 0x73, 0x1f, 0x82, 0x00, 0x4b, 0x53, 0x23, 0x8b, 0x31, 0x45, 0xea, 0x48, 0xca, 0x8f, 0x18, 0xbe, 0xdf,
0x7e, 0xa0, 0x24, 0x7b, 0x9d, 0x45, 0x73, 0xb8, 0xb3, 0x60, 0x61, 0x38, 0xef, 0x19, 0xcd, 0x83, 0xc5, 0xdf, 0x2a,
0xc5, 0xec, 0xbe, 0x03, 0xd4, 0xd8, 0x56, 0x94, 0x85, 0x7b, 0x23, 0x41, 0xe5, 0x8a, 0x80, 0x2c, 0x8b, 0x06, 0x68,
0x55, 0x16, 0x2d, 0x58, 0x8a, 0x58, 0x43, 0xb5, 0x01, 0x4b, 0xfe, 0xbc, 0xff, 0x39, 0xb8, 0x2d, 0x0b, 0xc1, 0xe5,
0x1a, 0x69, 0x10, 0x84, 0x33, 0x25, 0x51, 0xa3, 0xa1, 0x26, 0x15, 0xb5, 0x34, 0xe3, 0x2d, 0x5d, 0xc1, 0x24, 0x22,
0x69, 0x0b, 0x64, 0xc3, 0x61, 0xdb, 0x29, 0x6d, 0x11, 0x53, 0xd2, 0x82, 0xb4, 0x04, 0x6f, 0x79, 0x65, 0x1b, 0x52,
0xc1, 0x86, 0x33, 0x08, 0x86, 0xc3, 0x35, 0x97, 0xdc, 0x72, 0x2a, 0x02, 0xc3, 0xa8, 0x00, 0x92, 0x5c, 0xf7, 0x06,
0xf4, 0x70, 0xa0, 0x4b, 0x01, 0x44, 0x2a, 0x5c, 0x16, 0x86, 0x69, 0xde, 0x59, 0xe4, 0x5c, 0x25, 0xad, 0xaa, 0x7a,
0x01, 0x65, 0x14, 0x51, 0x63, 0xc0, 0x9a, 0x88, 0xcb, 0x0a, 0x76, 0xe1, 0x32, 0x66, 0x2c, 0x86, 0xdb, 0xdb, 0xf0,
0xb3, 0x79, 0x56, 0x29, 0xd6, 0xb7, 0x20, 0x6d, 0x28, 0x14, 0xa3, 0x96, 0x2b, 0x19, 0x1a, 0xa0, 0x9a, 0x35, 0x84,
0x10, 0xfc, 0xa3, 0xa1, 0x1b, 0xc0, 0xdf, 0x7f, 0xef, 0x9d, 0x99, 0x56, 0x60, 0xff, 0x21, 0xc0, 0x81, 0xe6, 0xa7,
0xfd, 0x3d, 0x5d, 0xfd, 0x4e, 0x5b, 0xf0, 0x30, 0x35, 0xbc, 0x02, 0xec, 0x7f, 0x8c, 0x3f, 0x85, 0xc6, 0xee, 0x05,
0x84, 0x15, 0x37, 0x9d, 0xa0, 0x7b, 0x82, 0x97, 0x42, 0xb1, 0x35, 0xf6, 0xf3, 0xba, 0x97, 0xcc, 0x29, 0x47, 0xc6,
0x03, 0xff, 0x20, 0xc0, 0x22, 0x4b, 0xde, 0x51, 0xdb, 0x84, 0x2d, 0xdd, 0x79, 0x23, 0xc0, 0xa5, 0x97, 0xfe, 0xe0,
0xc1, 0xcb, 0x24, 0x8e, 0xfd, 0xeb, 0xe1, 0x15, 0xfb, 0x51, 0x12, 0xc7, 0xb9, 0x06, 0xdb, 0x6b, 0x89, 0xa8, 0xf7,
0x50, 0x74, 0xd4, 0x36, 0xa8, 0x22, 0xf8, 0x5d, 0x92, 0xa2, 0xe4, 0x75, 0x98, 0xce, 0x7f, 0x0b, 0x5f, 0xa1, 0x9b,
0x30, 0x9d, 0xb3, 0x57, 0xc1, 0x1c, 0x25, 0x37, 0xc1, 0x1c, 0xa5, 0x69, 0x38, 0x47, 0xf1, 0x17, 0x8c, 0x6a, 0x2e,
0x04, 0xc1, 0x52, 0x49, 0xc0, 0xc8, 0x58, 0xad, 0xd6, 0x40, 0x30, 0xeb, 0xb5, 0x06, 0x69, 0xdf, 0x2a, 0xa1, 0x34,
0x8e, 0xca, 0x67, 0xff, 0x97, 0x42, 0xab, 0xa9, 0x34, 0xb5, 0xd2, 0x2d, 0xc1, 0x43, 0xf6, 0xbd, 0x17, 0x07, 0x7b,
0x44, 0xee, 0xe5, 0x5f, 0x10, 0x03, 0xa5, 0xf9, 0x8a, 0x4b, 0x82, 0x9d, 0xc6, 0x5b, 0x1c, 0x95, 0x0f, 0xfe, 0xf1,
0x1c, 0x3d, 0x75, 0xd1, 0x4f, 0xf1, 0x28, 0xef, 0xe3, 0x43, 0x61, 0x36, 0x2b, 0xb4, 0x6b, 0x85, 0x34, 0x04, 0x37,
0xd6, 0x76, 0x59, 0x14, 0x6d, 0xb7, 0xdb, 0x70, 0x3b, 0x0b, 0x95, 0x5e, 0x45, 0x69, 0x1c, 0xc7, 0x91, 0xd9, 0xac,
0x30, 0x1a, 0x0b, 0x01, 0xa7, 0x37, 0x18, 0x35, 0xc0, 0x57, 0x8d, 0x1d, 0xe0, 0xf2, 0xc5, 0x01, 0x8e, 0x85, 0xe3,
0x28, 0x1f, 0x3e, 0x5d, 0x58, 0xe1, 0x17, 0x56, 0xe0, 0x47, 0xea, 0xe1, 0x53, 0x98, 0x57, 0x43, 0x98, 0xaf, 0x68,
0x8a, 0x52, 0x14, 0x0f, 0x4f, 0x1a, 0x38, 0x78, 0x3a, 0x05, 0x4f, 0x4e, 0xe8, 0xe2, 0xe4, 0xa0, 0x76, 0x11, 0xbc,
0x3e, 0xcb, 0x26, 0x0e, 0xb3, 0x49, 0xe2, 0x47, 0x84, 0x13, 0xf8, 0x65, 0x71, 0x79, 0x0e, 0xd2, 0x0f, 0x97, 0x0c,
0xce, 0x5a, 0x93, 0x7c, 0x58, 0xd0, 0x39, 0x9a, 0x4f, 0x98, 0x79, 0xe0, 0xe0, 0xf3, 0x09, 0xcd, 0x37, 0x69, 0x93,
0xb4, 0xc1, 0x22, 0x98, 0xd3, 0x19, 0x9a, 0x4d, 0x8e, 0xcc, 0xd0, 0x6c, 0x93, 0x36, 0x8b, 0x0f, 0x8b, 0x4b, 0x5c,
0x30, 0xfb, 0x72, 0x15, 0x95, 0xd8, 0xcf, 0x30, 0x7e, 0x8c, 0x5c, 0x5d, 0x46, 0x1e, 0x7e, 0x56, 0x5c, 0x7a, 0x18,
0xfb, 0xc7, 0x1a, 0x2c, 0x6b, 0x3c, 0x1c, 0x31, 0x25, 0x6b, 0xbe, 0x0a, 0x3f, 0x1b, 0x25, 0xb1, 0x1f, 0xda, 0x06,
0xa4, 0x77, 0x12, 0x75, 0x82, 0x30, 0x50, 0xbc, 0xa7, 0x14, 0xeb, 0x1f, 0xce, 0xf5, 0x6f, 0xb9, 0x15, 0x40, 0x6c,
0xe8, 0x1a, 0xf6, 0xfa, 0x8c, 0x5d, 0xaa, 0x6a, 0xff, 0x8d, 0xd6, 0x68, 0x92, 0xb1, 0x2f, 0xb8, 0x94, 0xa0, 0xef,
0x61, 0x67, 0x09, 0x7e, 0xf7, 0xe6, 0x2d, 0x7a, 0x53, 0x55, 0x1a, 0x8c, 0xc9, 0x10, 0x7e, 0x69, 0xc3, 0x96, 0xb2,
0xff, 0x5d, 0x57, 0xf2, 0x95, 0xae, 0x7f, 0xf2, 0x9f, 0x39, 0xfa, 0x1d, 0xec, 0x56, 0xe9, 0xf5, 0xa4, 0xcd, 0xb9,
0x96, 0xbb, 0x0e, 0xd3, 0xc4, 0x86, 0xb4, 0x33, 0xa1, 0x11, 0x9c, 0x81, 0x97, 0xf8, 0x61, 0x4b, 0xbb, 0xc7, 0xa8,
0xe4, 0x29, 0x51, 0x0f, 0x45, 0xc5, 0x37, 0x88, 0x09, 0x6a, 0x0c, 0xc1, 0x72, 0x54, 0x85, 0xd1, 0x33, 0x34, 0xfc,
0x94, 0x64, 0x82, 0xb3, 0x35, 0xc1, 0x7f, 0x31, 0x01, 0x7e, 0xda, 0xff, 0x5a, 0x79, 0x57, 0xc6, 0xf0, 0xea, 0xca,
0x0f, 0x37, 0x54, 0xf4, 0x80, 0x08, 0xb2, 0x0d, 0x37, 0x8f, 0x0e, 0xe6, 0xdf, 0x14, 0xeb, 0xcc, 0xfa, 0xca, 0x0f,
0x6b, 0xc5, 0x7a, 0xe3, 0xf9, 0xb8, 0x9c, 0xcc, 0x15, 0x74, 0x1c, 0x90, 0xf8, 0x39, 0x7e, 0xe2, 0x51, 0x20, 0xa0,
0xb6, 0x67, 0x3e, 0x84, 0x5e, 0x1c, 0x8c, 0x27, 0x43, 0x6d, 0x0c, 0xf7, 0x8f, 0x67, 0x64, 0x61, 0x3a, 0x2a, 0x9f,
0x0a, 0x3a, 0x07, 0x5d, 0xab, 0xc8, 0xd0, 0x41, 0xae, 0x5f, 0x3a, 0x2a, 0xcf, 0x06, 0x23, 0x7a, 0x02, 0x5f, 0x1c,
0xb8, 0x27, 0xdd, 0x14, 0x5c, 0x9f, 0x35, 0x16, 0x51, 0xc5, 0x37, 0xe5, 0xc3, 0xd1, 0x7f, 0x8c, 0xe3, 0x5f, 0x3d,
0xe8, 0xfd, 0x1d, 0x08, 0x60, 0x56, 0x69, 0x0f, 0x3f, 0x97, 0x60, 0xb1, 0x3f, 0x06, 0xfc, 0xcb, 0xfd, 0xbb, 0xdf,
0x88, 0xf2, 0xb4, 0x7f, 0xfd, 0x2d, 0x6e, 0xb7, 0x0a, 0x3e, 0x6a, 0x10, 0xff, 0x26, 0x57, 0x6e, 0x19, 0x5c, 0x7d,
0xc2, 0x7e, 0x38, 0xc4, 0xfb, 0xf0, 0xb8, 0x11, 0x5c, 0x3b, 0xbf, 0xdc, 0xb5, 0xe2, 0xda, 0x45, 0x18, 0x2c, 0xe6,
0xfe, 0xf1, 0xe1, 0xe8, 0x1f, 0xfd, 0xbc, 0x88, 0xc6, 0xb9, 0x5e, 0x16, 0xc3, 0x88, 0x2d, 0x7f, 0x38, 0x2c, 0xd5,
0x2e, 0x30, 0xfc, 0x0b, 0x97, 0xab, 0x8c, 0xcb, 0x06, 0x34, 0xb7, 0xc7, 0x8a, 0x6f, 0xae, 0xb9, 0xec, 0x7a, 0x7b,
0xe8, 0x68, 0x55, 0x39, 0xca, 0xbc, 0xdb, 0xe5, 0xb5, 0x92, 0xd6, 0x71, 0x42, 0x96, 0x40, 0x7b, 0x1c, 0xe9, 0xc3,
0x44, 0xc9, 0x5e, 0xcf, 0xbf, 0x3b, 0xba, 0x82, 0x3b, 0x58, 0xd8, 0xd9, 0x80, 0x0a, 0xbe, 0x92, 0x19, 0x03, 0x69,
0x41, 0x8f, 0x42, 0x35, 0x6d, 0xb9, 0xd8, 0x67, 0x86, 0x4a, 0x13, 0x18, 0xd0, 0xbc, 0x3e, 0x2e, 0x7b, 0x6b, 0x95,
0x3c, 0x2c, 0x95, 0xae, 0x40, 0x67, 0x71, 0x3e, 0x02, 0x81, 0xa6, 0x15, 0xef, 0x4d, 0x16, 0xce, 0x34, 0xb4, 0xf9,
0x92, 0xb2, 0xf5, 0x4a, 0xab, 0x5e, 0x56, 0x01, 0x73, 0x93, 0x36, 0x7b, 0x9e, 0xd4, 0x74, 0x06, 0x2c, 0x9f, 0x4e,
0x75, 0x5d, 0xe7, 0x82, 0x4b, 0x08, 0xc6, 0x59, 0x96, 0xa5, 0xe1, 0x8d, 0x13, 0xbb, 0x70, 0x33, 0x4c, 0x1d, 0x62,
0xf4, 0x31, 0x89, 0xe3, 0xef, 0xf2, 0x53, 0x38, 0x71, 0xce, 0x7a, 0x6d, 0x94, 0xce, 0x3a, 0xc5, 0x9d, 0x9b, 0xc7,
0x96, 0x72, 0x79, 0xe9, 0xbd, 0x2b, 0x93, 0x7c, 0x5a, 0x3f, 0x19, 0x97, 0x83, 0x99, 0x61, 0x09, 0xe5, 0x2d, 0x97,
0xe3, 0x0e, 0xcd, 0xd2, 0x45, 0xdc, 0xed, 0x8e, 0xe1, 0x54, 0x20, 0x87, 0x13, 0x77, 0x2d, 0x60, 0x97, 0x7f, 0xee,
0x8d, 0xe5, 0xf5, 0x3e, 0x98, 0x76, 0x70, 0x66, 0x3a, 0xca, 0x20, 0x58, 0x82, 0xdd, 0x02, 0xc8, 0x7c, 0xb0, 0x11,
0x70, 0x0b, 0xad, 0x99, 0xf2, 0x74, 0x56, 0x33, 0x14, 0xe8, 0xd7, 0xba, 0xfe, 0x1b, 0xb7, 0xab, 0xc5, 0x43, 0x4b,
0xf5, 0x8a, 0xcb, 0x60, 0xa9, 0xac, 0x55, 0x6d, 0x16, 0xbc, 0xea, 0x76, 0xf9, 0x84, 0x72, 0xca, 0xb2, 0xc4, 0xb9,
0x39, 0xec, 0xd6, 0x53, 0xbe, 0x93, 0x6e, 0x87, 0x8c, 0x12, 0xbc, 0x9a, 0xf8, 0x06, 0x16, 0x14, 0x9f, 0xd3, 0x93,
0xcc, 0xbb, 0x1d, 0x72, 0xb8, 0x53, 0xaa, 0x6f, 0xea, 0x5b, 0x9a, 0xc4, 0x7f, 0xf1, 0x45, 0xaa, 0xba, 0x4e, 0x97,
0xf5, 0x39, 0x53, 0x6e, 0x4d, 0xba, 0xd6, 0x18, 0x4a, 0xab, 0x88, 0xc6, 0xdb, 0x8c, 0xab, 0x8c, 0xb2, 0x70, 0x19,
0x2e, 0x8b, 0x26, 0x41, 0xbc, 0x22, 0x2d, 0x65, 0xe5, 0xc5, 0xf8, 0x2a, 0xa2, 0x26, 0x39, 0x91, 0x9a, 0xa4, 0xfc,
0x6a, 0x18, 0x8d, 0xb4, 0xc1, 0xfb, 0xf2, 0xad, 0x92, 0x12, 0x98, 0xe5, 0x72, 0x85, 0xac, 0x42, 0x53, 0x0a, 0xc2,
0x30, 0x2c, 0x96, 0xba, 0x7c, 0x2f, 0x80, 0x1a, 0x40, 0x5b, 0xca, 0x6d, 0x58, 0x44, 0x23, 0xff, 0xd8, 0xc7, 0xbc,
0x22, 0x12, 0x6c, 0x39, 0x35, 0x6c, 0xd1, 0xcc, 0x46, 0x03, 0x77, 0x60, 0x9d, 0x26, 0x67, 0x60, 0x56, 0x16, 0x6e,
0xe5, 0x22, 0x3a, 0x8c, 0x34, 0x12, 0x6d, 0x79, 0xcd, 0xdd, 0x95, 0xa5, 0x2c, 0x86, 0x22, 0x77, 0x1a, 0x5c, 0x9e,
0xc7, 0xeb, 0xd5, 0x00, 0x09, 0x90, 0x2b, 0xdb, 0x90, 0x59, 0x8a, 0x3a, 0x41, 0x19, 0x34, 0x4a, 0x54, 0xa0, 0xc9,
0xdd, 0xdd, 0xaf, 0x7f, 0x2f, 0x9d, 0x33, 0x8f, 0x72, 0x9d, 0x59, 0x8f, 0x62, 0x0e, 0x98, 0xa4, 0x16, 0x37, 0xe3,
0xa5, 0xaa, 0xa3, 0xc6, 0x6c, 0x95, 0xae, 0xbe, 0xd2, 0xf1, 0x7e, 0x42, 0x8e, 0x7a, 0x86, 0xff, 0xd0, 0x2a, 0xe5,
0x1d, 0xdd, 0x40, 0x11, 0x4d, 0x87, 0x22, 0x72, 0x0e, 0x8f, 0xf4, 0x66, 0xe2, 0x6b, 0x92, 0xf2, 0x8f, 0xfb, 0x37,
0xe8, 0xcf, 0xae, 0xa2, 0x16, 0xc6, 0xb4, 0x0d, 0x51, 0xb5, 0x60, 0x1b, 0x55, 0x91, 0xf7, 0x7f, 0xdc, 0xdd, 0x9f,
0x23, 0xec, 0x07, 0x26, 0x04, 0x92, 0x8d, 0xd7, 0xbb, 0x5e, 0x58, 0xde, 0x51, 0x6d, 0x07, 0xb5, 0x81, 0x9b, 0x22,
0xa7, 0x18, 0x06, 0x7a, 0xcd, 0x05, 0x8c, 0x61, 0x8c, 0x82, 0x25, 0x3a, 0x79, 0x75, 0xb2, 0xf6, 0xc4, 0xaf, 0x68,
0xfc, 0xda, 0xd1, 0xf8, 0xe9, 0xa3, 0xe1, 0xa6, 0xfb, 0x1f, 0x53, 0x58, 0x46, 0xb2, 0xf9, 0x0a, 0x00, 0x00};
static constexpr size_t INDEX_SIZE = sizeof(INDEX_GZ);
static constexpr const char *INDEX_CONTENT_ENCODING = "gzip";
#else // Brotli (default, smaller)
const uint8_t INDEX_BR[] PROGMEM = {
0x1f, 0xf8, 0x0a, 0x00, 0x64, 0x5a, 0xd3, 0xfa, 0xe7, 0xf3, 0x62, 0xd8, 0x06, 0x1b, 0xe9, 0x6a, 0x8a, 0x81, 0x2b,
0xb5, 0x49, 0x14, 0x37, 0xdc, 0x9e, 0x1a, 0xcb, 0x56, 0x87, 0xfb, 0xff, 0xf7, 0x73, 0x75, 0x12, 0x0a, 0xd6, 0x48,
0x84, 0xc6, 0x21, 0xa4, 0x6d, 0xb5, 0x71, 0xef, 0x13, 0xbe, 0x4e, 0x54, 0xf1, 0x64, 0x8f, 0x3f, 0xcc, 0x9a, 0x78,
0xa5, 0x89, 0x25, 0xb3, 0xda, 0x2c, 0xa2, 0x32, 0x9c, 0x57, 0x07, 0x56, 0xbc, 0x34, 0x13, 0xff, 0x5c, 0x0a, 0xa1,
0x67, 0x82, 0xb8, 0x6b, 0x4c, 0x76, 0x31, 0x6c, 0xe3, 0x40, 0x46, 0xea, 0xb0, 0xd4, 0xf4, 0x3b, 0x02, 0x65, 0x18,
0xa4, 0xaf, 0xac, 0x6d, 0x55, 0xd6, 0xbe, 0x59, 0x66, 0x7a, 0x7c, 0x60, 0xb2, 0x83, 0x33, 0x23, 0xc9, 0x79, 0x82,
0x47, 0xb4, 0x28, 0xf4, 0x24, 0xb5, 0x23, 0x5a, 0x44, 0xe1, 0xc3, 0x27, 0x04, 0xe8, 0x0c, 0xdd, 0xb4, 0xd0, 0x8c,
0xfb, 0x10, 0x39, 0x93, 0x04, 0x2a, 0x66, 0x18, 0x4b, 0x74, 0xca, 0x31, 0x7f, 0xb2, 0xe5, 0x45, 0xc1, 0xdd, 0x72,
0x49, 0xff, 0x0e, 0xb3, 0xf0, 0x93, 0x18, 0xab, 0x68, 0xad, 0xe1, 0x9d, 0xe4, 0x29, 0xc0, 0xe3, 0x63, 0x54, 0x61,
0x1b, 0x45, 0xb9, 0x6c, 0x23, 0x0f, 0x99, 0x7f, 0x8e, 0x69, 0xaa, 0xc1, 0xb8, 0x4e, 0x42, 0x9c, 0xc5, 0x6e, 0x69,
0x40, 0x0e, 0x4f, 0x97, 0xd3, 0x23, 0x18, 0xf5, 0xc8, 0x75, 0x73, 0xb5, 0xbd, 0x46, 0x8a, 0x97, 0x7d, 0x83, 0xe4,
0x29, 0x72, 0x73, 0xc1, 0x39, 0x8e, 0x7e, 0x84, 0x39, 0x66, 0x57, 0xc6, 0x85, 0x19, 0x8b, 0xf2, 0x4d, 0xd9, 0xfe,
0x75, 0xa9, 0xe1, 0x2b, 0x21, 0x81, 0x58, 0x51, 0x99, 0xbc, 0xa4, 0x0b, 0x10, 0x6f, 0x86, 0x17, 0x0b, 0x92, 0x00,
0x11, 0x6f, 0x3b, 0xa4, 0xa4, 0x11, 0x7e, 0x0b, 0x97, 0x85, 0x23, 0x0c, 0x01, 0x6f, 0x2a, 0x18, 0xc6, 0xbe, 0x3d,
0x77, 0x1a, 0xe6, 0x00, 0x5c, 0x1a, 0x14, 0x47, 0xc6, 0xcc, 0xcc, 0x52, 0xbe, 0x04, 0x19, 0x31, 0x05, 0x46, 0xa0,
0xc3, 0x69, 0x0c, 0x60, 0xb7, 0x14, 0x57, 0xa0, 0x92, 0xbf, 0xb7, 0x0c, 0xd8, 0x3a, 0x79, 0x09, 0x99, 0xc9, 0x71,
0x88, 0x01, 0x8b, 0xa5, 0x61, 0x0a, 0xb5, 0xe8, 0xc7, 0x71, 0xe7, 0x70, 0x79, 0xb6, 0xe4, 0x01, 0xfc, 0x1a, 0x4a,
0x7b, 0x60, 0x6e, 0xef, 0x95, 0x62, 0x59, 0x28, 0xb5, 0x25, 0x56, 0x15, 0xe7, 0xca, 0xad, 0x32, 0xe6, 0xf7, 0x01,
0x31, 0x34, 0x87, 0x93, 0x0b, 0x9b, 0x9d, 0x26, 0xff, 0xe5, 0x92, 0xad, 0x6f, 0xb8, 0x3b, 0x16, 0xc1, 0xa0, 0x5a,
0x4f, 0x52, 0x0b, 0x2b, 0xc1, 0xa7, 0x95, 0x7b, 0x24, 0x51, 0xd3, 0xb3, 0x23, 0x62, 0x0b, 0xcc, 0xa0, 0x58, 0xa7,
0x64, 0x45, 0x2f, 0x0b, 0xdd, 0x1d, 0x97, 0x82, 0x1f, 0xcc, 0x64, 0xdb, 0xd3, 0xf4, 0xb0, 0x8b, 0xc8, 0xcf, 0x15,
0x81, 0x8b, 0xa1, 0x9d, 0xf8, 0xfc, 0xec, 0x49, 0x40, 0x12, 0x01, 0x09, 0x51, 0xf3, 0x73, 0x18, 0x24, 0x97, 0x55,
0x85, 0x6a, 0x92, 0x1a, 0xf5, 0x5a, 0x05, 0x54, 0x1f, 0x27, 0x0a, 0xa8, 0xa1, 0x94, 0x58, 0x78, 0x7d, 0x87, 0xa8,
0xdb, 0x13, 0x66, 0x20, 0x5e, 0x43, 0x18, 0x7a, 0xbb, 0x16, 0x16, 0x07, 0xc8, 0xab, 0x10, 0xe2, 0x50, 0xb9, 0xb1,
0xd8, 0x21, 0xc8, 0x4a, 0x2e, 0x99, 0x0e, 0x23, 0x52, 0xc6, 0xcb, 0x29, 0x84, 0x91, 0x03, 0xb1, 0xe2, 0x4c, 0x1d,
0x22, 0xd3, 0xc8, 0x79, 0x00, 0x8b, 0x8b, 0x88, 0x1e, 0x29, 0xd3, 0xae, 0x10, 0x15, 0x22, 0x6d, 0xb0, 0x87, 0x6f,
0x27, 0x2e, 0x7c, 0xc2, 0x7a, 0x61, 0xbd, 0x22, 0xe5, 0x5f, 0xdd, 0x7b, 0x00, 0x04, 0xf2, 0x7d, 0x5a, 0x03, 0x38,
0x1f, 0x69, 0x6d, 0x0b, 0xfb, 0xec, 0x45, 0xfe, 0x8b, 0x7f, 0xec, 0x7b, 0xad, 0xc2, 0x33, 0xf1, 0x9e, 0x9c, 0x71,
0xd9, 0xe8, 0x5e, 0x8f, 0xd4, 0xee, 0x87, 0x45, 0x6c, 0xe2, 0x12, 0xf8, 0xb8, 0xc5, 0xee, 0x43, 0xa6, 0x37, 0x91,
0xb5, 0x2c, 0x2f, 0xe9, 0xe8, 0x24, 0xd0, 0x45, 0xc1, 0x0c, 0x7c, 0xf0, 0xb2, 0xb5, 0x2d, 0x10, 0x36, 0x7e, 0x18,
0x7c, 0x79, 0x82, 0x69, 0x3d, 0x35, 0xca, 0x52, 0xee, 0xc9, 0xb5, 0x65, 0xa4, 0xa1, 0xfd, 0x70, 0x7e, 0xe0, 0x7d,
0x67, 0xf9, 0xa1, 0x71, 0xd2, 0x08, 0x74, 0x33, 0x5f, 0x69, 0xa4, 0x59, 0x03, 0xfd, 0xf8, 0xf0, 0x70, 0x1a, 0x50,
0x43, 0xfb, 0x61, 0xf0, 0x38, 0x18, 0x88, 0x85, 0x36, 0x23, 0x06, 0x4f, 0x02, 0xbb, 0x78, 0x1a, 0xaa, 0xd2, 0x02,
0x5e, 0xa0, 0x74, 0x30, 0xc8, 0x7a, 0x66, 0xab, 0xd9, 0x43, 0x99, 0x45, 0xb7, 0x0c, 0x5c, 0xec, 0xc8, 0x03, 0x0e,
0x0b, 0xca, 0x4a, 0x22, 0x48, 0xfb, 0xb7, 0x3d, 0x82, 0x07, 0x8d, 0x1b, 0x21, 0x87, 0x4d, 0x57, 0xa4, 0x5b, 0xd4,
0xe3, 0x88, 0x02, 0xc4, 0x81, 0xf9, 0x47, 0xe4, 0xbf, 0x3e, 0x39, 0xbb, 0x4f, 0x7e, 0x91, 0x63, 0x98, 0x97, 0xe4,
0x52, 0x01, 0x58, 0xba, 0x32, 0xbf, 0xae, 0xff, 0x45, 0xa1, 0xbc, 0x9b, 0xa4, 0x09, 0x0e, 0x79, 0xc0, 0x41, 0x86,
0x52, 0x88, 0x55, 0x39, 0x9d, 0xb6, 0xed, 0x35, 0x68, 0x29, 0xfa, 0xe6, 0x6c, 0x3d, 0x0a, 0xcd, 0x6a, 0x28, 0xfd,
0x65, 0x24, 0xce, 0x38, 0x98, 0x01, 0xd9, 0x3f, 0x1b, 0x4c, 0xc4, 0x5c, 0x1d, 0xaa, 0x21, 0x78, 0x67, 0xaf, 0x55,
0x72, 0x34, 0xf8, 0x1b, 0x03, 0x21, 0x27, 0x08, 0xbd, 0x59, 0x60, 0x48, 0x0d, 0xe2, 0x56, 0x9b, 0x30, 0x92, 0x8f,
0x67, 0x8a, 0x7f, 0x20, 0xbd, 0x2d, 0xfd, 0xc5, 0xb0, 0xa6, 0xaa, 0x77, 0x75, 0x26, 0x33, 0x2f, 0x20, 0x2a, 0xab,
0x5c, 0xd1, 0x3b, 0xda, 0xb2, 0x4c, 0xa4, 0x86, 0x25, 0x8d, 0x49, 0x05, 0xaf, 0x7a, 0xa8, 0xd4, 0x9c, 0x0d, 0xd3,
0x38, 0xa6, 0x5c, 0x29, 0x6b, 0x16, 0x27, 0x07, 0xf1, 0xbe, 0xe2, 0x24, 0xc1, 0x8d, 0x25, 0x76, 0xbc, 0xf6, 0x0d,
0xc2, 0x94, 0x25, 0xb8, 0xf3, 0x07, 0x9a, 0x49, 0xf4, 0x89, 0x82, 0x4d, 0x51, 0xb1, 0x96, 0x61, 0x62, 0x8d, 0xc8,
0x61, 0x65, 0x0d, 0x14, 0x34, 0x02, 0x65, 0x94, 0xcc, 0x1d, 0x85, 0x00, 0x0f, 0x1a, 0x57, 0x68, 0x15, 0xcf, 0xa4,
0xa2, 0x7d, 0x6d, 0x53, 0x60, 0xce, 0x5c, 0x61, 0x82, 0x17, 0x32, 0xc1, 0x87, 0x02, 0x0c, 0x91, 0x85, 0x57, 0x51,
0xbe, 0xb2, 0x38, 0x9f, 0x3d, 0x2a, 0x52, 0x5a, 0xad, 0xba, 0x46, 0x9e, 0x3c, 0x8a, 0xa0, 0x46, 0x15, 0xf4, 0x59,
0x74, 0x5f, 0x2a, 0xae, 0x96, 0x56, 0xf0, 0x54, 0x39, 0xaf, 0xac, 0x2a, 0xb9, 0xad, 0x32, 0x50, 0xc9, 0xc1, 0xee,
0xd2, 0x0d, 0x34, 0xaa, 0x98, 0x4d, 0x6d, 0x3d, 0xc6, 0xb9, 0x5b, 0x00, 0x5f, 0xea, 0xda, 0x16, 0xa6, 0x08, 0x43,
0x58, 0x4d, 0x8d, 0x07, 0x55, 0x62, 0x81, 0x44, 0xcc, 0x31, 0x04, 0x4b, 0x4c, 0x8b, 0x3e, 0xff, 0xd8, 0xf6, 0x65,
0x19, 0xa1, 0x94, 0x62, 0x65, 0x0a, 0xdd, 0x60, 0x38, 0xd3, 0xbe, 0x0d, 0xa3, 0x99, 0xd5, 0x37, 0x68, 0xa1, 0x71,
0xa3, 0x41, 0xe7, 0xbe, 0x9d, 0x72, 0x84, 0x75, 0xb6, 0x8d, 0x98, 0xd6, 0xb8, 0x2d, 0x43, 0x85, 0x5d, 0xf9, 0xca,
0xc3, 0x96, 0xa5, 0xa6, 0xe7, 0x50, 0x88, 0x6b, 0x84, 0x58, 0x44, 0x45, 0x20, 0xdf, 0x1e, 0x5a, 0xc9, 0xce, 0x42,
0x2a, 0x1f, 0x3e, 0x3c, 0x7b, 0x68, 0x3c, 0x34, 0x8b, 0x36, 0xba, 0x1f, 0xce, 0x0f, 0xa0, 0x60, 0x37, 0x5f, 0x1a,
0x03, 0x2b, 0x86, 0x29, 0x45, 0x7b, 0xb4, 0xb7, 0x06, 0x68, 0x17, 0x7e, 0x13, 0x76, 0x91, 0x4d, 0x27, 0xee, 0xbc,
0x7e, 0x80, 0xc2, 0x66, 0xac, 0xc6, 0xbf, 0xeb, 0x7f, 0xd7, 0x84, 0x79, 0xf3, 0xf1, 0xde, 0xec, 0xa6, 0x93, 0xa8,
0x13, 0x3b, 0x4a, 0x81, 0xfa, 0x11, 0x1e, 0x4a, 0xd2, 0x50, 0x2a, 0xea, 0x9a, 0xc2, 0x37, 0x08, 0xed, 0x01, 0xf5,
0xa2, 0xd5, 0x32, 0x29, 0x49, 0xc4, 0x1a, 0x11, 0xc0, 0xda, 0x24, 0x28, 0x84, 0x38, 0x60, 0x80, 0xcf, 0xd0, 0x45,
0x83, 0xa7, 0xca, 0x52, 0x5c, 0xac, 0x23, 0x01};
// Backwards compatibility alias
#define INDEX_GZ INDEX_BR
static constexpr size_t INDEX_SIZE = sizeof(INDEX_BR);
static constexpr const char *INDEX_CONTENT_ENCODING = "br";
#endif // USE_CAPTIVE_PORTAL_GZIP
} // namespace esphome::captive_portal
} // namespace captive_portal
} // namespace esphome

View File

@@ -54,13 +54,8 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
" SSID='%s'\n"
" Password=" LOG_SECRET("'%s'"),
ssid.c_str(), psk.c_str());
#ifdef USE_ESP8266
// ESP8266 is single-threaded, call directly
wifi::global_wifi_component->save_wifi_sta(ssid, psk);
#else
// Defer save to main loop thread to avoid NVS operations from HTTP thread
this->defer([ssid, psk]() { wifi::global_wifi_component->save_wifi_sta(ssid, psk); });
#endif
request->redirect(ESPHOME_F("/?save"));
}
@@ -112,11 +107,7 @@ void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
#else
auto *response = req->beginResponse_P(200, ESPHOME_F("text/html"), INDEX_GZ, sizeof(INDEX_GZ));
#endif
#ifdef USE_CAPTIVE_PORTAL_GZIP
response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip"));
#else
response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("br"));
#endif
req->send(response);
}

View File

@@ -264,7 +264,6 @@ async def to_code(config):
var.get_packet_trigger(),
[
(cg.std_vector.template(cg.uint8), "x"),
(cg.float_, "freq_offset"),
(cg.float_, "rssi"),
(cg.uint8, "lqi"),
],

View File

@@ -140,10 +140,7 @@ void CC1101Component::setup() {
this->write_(static_cast<Register>(i));
}
this->set_output_power(this->output_power_requested_);
if (!this->enter_rx_()) {
this->mark_failed();
return;
}
this->strobe_(Command::RX);
// Defer pin mode setup until after all components have completed setup()
// This handles the case where remote_transmitter runs after CC1101 and changes pin mode
@@ -166,7 +163,8 @@ void CC1101Component::loop() {
ESP_LOGW(TAG, "RX FIFO overflow, flushing");
this->enter_idle_();
this->strobe_(Command::FRX);
this->enter_rx_();
this->strobe_(Command::RX);
this->wait_for_state_(State::RX);
return;
}
@@ -183,28 +181,28 @@ void CC1101Component::loop() {
ESP_LOGW(TAG, "Invalid packet: rx_bytes %u, payload_length %u", rx_bytes, payload_length);
this->enter_idle_();
this->strobe_(Command::FRX);
this->enter_rx_();
this->strobe_(Command::RX);
this->wait_for_state_(State::RX);
return;
}
this->packet_.resize(payload_length);
this->read_(Register::FIFO, this->packet_.data(), payload_length);
// Read status from registers (more reliable than FIFO status bytes due to timing issues)
this->read_(Register::FREQEST);
this->read_(Register::RSSI);
this->read_(Register::LQI);
float freq_offset = static_cast<int8_t>(this->state_.FREQEST) * (XTAL_FREQUENCY / (1 << 14));
float rssi = (this->state_.RSSI * RSSI_STEP) - RSSI_OFFSET;
bool crc_ok = (this->state_.LQI & STATUS_CRC_OK_MASK) != 0;
uint8_t lqi = this->state_.LQI & STATUS_LQI_MASK;
if (this->state_.CRC_EN == 0 || crc_ok) {
this->packet_trigger_->trigger(this->packet_, freq_offset, rssi, lqi);
this->packet_trigger_->trigger(this->packet_, rssi, lqi);
}
// Return to rx
this->enter_idle_();
this->strobe_(Command::FRX);
this->enter_rx_();
this->strobe_(Command::RX);
this->wait_for_state_(State::RX);
}
void CC1101Component::dump_config() {
@@ -235,8 +233,9 @@ void CC1101Component::begin_tx() {
if (this->gdo0_pin_ != nullptr) {
this->gdo0_pin_->pin_mode(gpio::FLAG_OUTPUT);
}
if (!this->enter_tx_()) {
ESP_LOGW(TAG, "Failed to enter TX state!");
this->strobe_(Command::TX);
if (!this->wait_for_state_(State::TX, 50)) {
ESP_LOGW(TAG, "Timed out waiting for TX state!");
}
}
@@ -245,9 +244,7 @@ void CC1101Component::begin_rx() {
if (this->gdo0_pin_ != nullptr) {
this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT);
}
if (!this->enter_rx_()) {
ESP_LOGW(TAG, "Failed to enter RX state!");
}
this->strobe_(Command::RX);
}
void CC1101Component::reset() {
@@ -273,33 +270,11 @@ bool CC1101Component::wait_for_state_(State target_state, uint32_t timeout_ms) {
return false;
}
bool CC1101Component::enter_calibrated_(State target_state, Command cmd) {
// The PLL must be recalibrated until PLL lock is achieved
for (uint8_t retries = PLL_LOCK_RETRIES; retries > 0; retries--) {
this->strobe_(cmd);
if (!this->wait_for_state_(target_state)) {
return false;
}
this->read_(Register::FSCAL1);
if (this->state_.FSCAL1 != FSCAL1_PLL_NOT_LOCKED) {
return true;
}
ESP_LOGW(TAG, "PLL lock failed, retrying calibration");
this->enter_idle_();
}
ESP_LOGE(TAG, "PLL lock failed after retries");
return false;
}
void CC1101Component::enter_idle_() {
this->strobe_(Command::IDLE);
this->wait_for_state_(State::IDLE);
}
bool CC1101Component::enter_rx_() { return this->enter_calibrated_(State::RX, Command::RX); }
bool CC1101Component::enter_tx_() { return this->enter_calibrated_(State::TX, Command::TX); }
uint8_t CC1101Component::strobe_(Command cmd) {
uint8_t index = static_cast<uint8_t>(cmd);
if (cmd < Command::RES || cmd > Command::NOP) {
@@ -361,26 +336,18 @@ CC1101Error CC1101Component::transmit_packet(const std::vector<uint8_t> &packet)
this->write_(Register::FIFO, static_cast<uint8_t>(packet.size()));
}
this->write_(Register::FIFO, packet.data(), packet.size());
// Calibrate PLL
if (!this->enter_calibrated_(State::FSTXON, Command::FSTXON)) {
ESP_LOGW(TAG, "PLL lock failed during TX");
this->enter_idle_();
this->enter_rx_();
return CC1101Error::PLL_LOCK;
}
// Transmit packet
this->strobe_(Command::TX);
if (!this->wait_for_state_(State::IDLE, 1000)) {
ESP_LOGW(TAG, "TX timeout");
this->enter_idle_();
this->enter_rx_();
this->strobe_(Command::RX);
this->wait_for_state_(State::RX);
return CC1101Error::TIMEOUT;
}
// Return to rx
this->enter_rx_();
this->strobe_(Command::RX);
this->wait_for_state_(State::RX);
return CC1101Error::NONE;
}
@@ -437,7 +404,7 @@ void CC1101Component::set_frequency(float value) {
this->write_(Register::FREQ2);
this->write_(Register::FREQ1);
this->write_(Register::FREQ0);
this->enter_rx_();
this->strobe_(Command::RX);
}
}
@@ -464,7 +431,7 @@ void CC1101Component::set_channel(uint8_t value) {
if (this->initialized_) {
this->enter_idle_();
this->write_(Register::CHANNR);
this->enter_rx_();
this->strobe_(Command::RX);
}
}
@@ -533,7 +500,7 @@ void CC1101Component::set_modulation_type(Modulation value) {
this->set_output_power(this->output_power_requested_);
this->write_(Register::MDMCFG2);
this->write_(Register::FREND0);
this->enter_rx_();
this->strobe_(Command::RX);
}
}

View File

@@ -9,7 +9,7 @@
namespace esphome::cc1101 {
enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW, PLL_LOCK };
enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW };
class CC1101Component : public Component,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
@@ -73,7 +73,7 @@ class CC1101Component : public Component,
// Packet mode operations
CC1101Error transmit_packet(const std::vector<uint8_t> &packet);
Trigger<std::vector<uint8_t>, float, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; }
Trigger<std::vector<uint8_t>, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; }
protected:
uint16_t chip_id_{0};
@@ -89,8 +89,7 @@ class CC1101Component : public Component,
InternalGPIOPin *gdo0_pin_{nullptr};
// Packet handling
Trigger<std::vector<uint8_t>, float, float, uint8_t> *packet_trigger_{
new Trigger<std::vector<uint8_t>, float, float, uint8_t>()};
Trigger<std::vector<uint8_t>, float, uint8_t> *packet_trigger_{new Trigger<std::vector<uint8_t>, float, uint8_t>()};
std::vector<uint8_t> packet_;
// Low-level Helpers
@@ -103,10 +102,7 @@ class CC1101Component : public Component,
// State Management
bool wait_for_state_(State target_state, uint32_t timeout_ms = 100);
bool enter_calibrated_(State target_state, Command cmd);
void enter_idle_();
bool enter_rx_();
bool enter_tx_();
};
// Action Wrappers

View File

@@ -9,9 +9,6 @@ static constexpr float XTAL_FREQUENCY = 26000000;
static constexpr float RSSI_OFFSET = 74.0f;
static constexpr float RSSI_STEP = 0.5f;
static constexpr uint8_t FSCAL1_PLL_NOT_LOCKED = 0x3F;
static constexpr uint8_t PLL_LOCK_RETRIES = 3;
static constexpr uint8_t STATUS_CRC_OK_MASK = 0x80;
static constexpr uint8_t STATUS_LQI_MASK = 0x7F;

View File

@@ -31,8 +31,8 @@ void DallasTemperatureSensor::dump_config() {
ESP_LOGW(TAG, " Unable to select an address");
return;
}
LOG_ONE_WIRE_DEVICE(this);
ESP_LOGCONFIG(TAG, " Resolution: %u bits", this->resolution_);
LOG_ONE_WIRE_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
}

View File

@@ -63,13 +63,11 @@ def validate_auto_clear(value):
return cv.boolean(value)
def basic_display_schema(default_update_interval: str = "1s") -> cv.Schema:
"""Create a basic display schema with configurable default update interval."""
return cv.Schema(
{
cv.Exclusive(CONF_LAMBDA, CONF_LAMBDA): cv.lambda_,
}
).extend(cv.polling_component_schema(default_update_interval))
BASIC_DISPLAY_SCHEMA = cv.Schema(
{
cv.Exclusive(CONF_LAMBDA, CONF_LAMBDA): cv.lambda_,
}
).extend(cv.polling_component_schema("1s"))
def _validate_test_card(config):
@@ -83,41 +81,34 @@ def _validate_test_card(config):
return config
def full_display_schema(default_update_interval: str = "1s") -> cv.Schema:
"""Create a full display schema with configurable default update interval."""
schema = basic_display_schema(default_update_interval).extend(
{
cv.Optional(CONF_ROTATION): validate_rotation,
cv.Exclusive(CONF_PAGES, CONF_LAMBDA): cv.All(
cv.ensure_list(
{
cv.GenerateID(): cv.declare_id(DisplayPage),
cv.Required(CONF_LAMBDA): cv.lambda_,
}
),
cv.Length(min=1),
),
cv.Optional(CONF_ON_PAGE_CHANGE): automation.validate_automation(
FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
{
cv.Optional(CONF_ROTATION): validate_rotation,
cv.Exclusive(CONF_PAGES, CONF_LAMBDA): cv.All(
cv.ensure_list(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
DisplayOnPageChangeTrigger
),
cv.Optional(CONF_FROM): cv.use_id(DisplayPage),
cv.Optional(CONF_TO): cv.use_id(DisplayPage),
cv.GenerateID(): cv.declare_id(DisplayPage),
cv.Required(CONF_LAMBDA): cv.lambda_,
}
),
cv.Optional(
CONF_AUTO_CLEAR_ENABLED, default=CONF_UNSPECIFIED
): validate_auto_clear,
cv.Optional(CONF_SHOW_TEST_CARD): cv.boolean,
}
)
schema.add_extra(_validate_test_card)
return schema
BASIC_DISPLAY_SCHEMA = basic_display_schema("1s")
FULL_DISPLAY_SCHEMA = full_display_schema("1s")
cv.Length(min=1),
),
cv.Optional(CONF_ON_PAGE_CHANGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
DisplayOnPageChangeTrigger
),
cv.Optional(CONF_FROM): cv.use_id(DisplayPage),
cv.Optional(CONF_TO): cv.use_id(DisplayPage),
}
),
cv.Optional(
CONF_AUTO_CLEAR_ENABLED, default=CONF_UNSPECIFIED
): validate_auto_clear,
cv.Optional(CONF_SHOW_TEST_CARD): cv.boolean,
}
)
FULL_DISPLAY_SCHEMA.add_extra(_validate_test_card)
async def setup_display_core_(var, config):

View File

@@ -4,7 +4,7 @@ from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_RECEIVE_TIMEOUT, CONF_UART_ID
CODEOWNERS = ["@glmnet", "@zuidwijk", "@PolarGoose"]
CODEOWNERS = ["@glmnet", "@zuidwijk"]
MULTI_CONF = True
@@ -61,6 +61,7 @@ CONFIG_SCHEMA = cv.All(
): cv.positive_time_period_milliseconds,
}
).extend(uart.UART_DEVICE_SCHEMA),
cv.only_with_arduino,
)
@@ -82,7 +83,7 @@ async def to_code(config):
cg.add_build_flag("-DDSMR_WATER_MBUS_ID=" + str(config[CONF_WATER_MBUS_ID]))
# DSMR Parser
cg.add_library("esphome/dsmr_parser", "1.0.0")
cg.add_library("glmnet/Dsmr", "0.8")
# Crypto
cg.add_library("polargoose/Crypto-no-arduino", "0.4.0")
cg.add_library("rweather/Crypto", "0.4.0")

View File

@@ -1,3 +1,5 @@
#ifdef USE_ARDUINO
#include "dsmr.h"
#include "esphome/core/log.h"
@@ -5,7 +7,8 @@
#include <Crypto.h>
#include <GCM.h>
namespace esphome::dsmr {
namespace esphome {
namespace dsmr {
static const char *const TAG = "dsmr";
@@ -254,9 +257,9 @@ bool Dsmr::parse_telegram() {
ESP_LOGV(TAG, "Trying to parse telegram");
this->stop_requesting_data_();
const auto &res = dsmr_parser::P1Parser::parse(
data, this->telegram_, this->bytes_read_, false,
this->crc_check_); // Parse telegram according to data definition. Ignore unknown values.
::dsmr::ParseResult<void> res =
::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false,
this->crc_check_); // Parse telegram according to data definition. Ignore unknown values.
if (res.err) {
// Parsing error, show it
auto err_str = res.fullError(this->telegram_, this->telegram_ + this->bytes_read_);
@@ -326,4 +329,7 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
}
}
} // namespace esphome::dsmr
} // namespace dsmr
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -1,17 +1,24 @@
#pragma once
#ifdef USE_ARDUINO
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/log.h"
#include <dsmr_parser/fields.h>
#include <dsmr_parser/parser.h>
#include "esphome/core/defines.h"
// don't include <dsmr.h> because it puts everything in global namespace
#include <dsmr/parser.h>
#include <dsmr/fields.h>
#include <vector>
namespace esphome::dsmr {
namespace esphome {
namespace dsmr {
using namespace dsmr_parser::fields;
using namespace ::dsmr::fields;
// DSMR_**_LIST generated by ESPHome and written in esphome/core/defines
@@ -37,8 +44,8 @@ using namespace dsmr_parser::fields;
#define DSMR_DATA_SENSOR(s) s
#define DSMR_COMMA ,
using MyData = dsmr_parser::ParsedData<DSMR_TEXT_SENSOR_LIST(DSMR_DATA_SENSOR, DSMR_COMMA)
DSMR_BOTH DSMR_SENSOR_LIST(DSMR_DATA_SENSOR, DSMR_COMMA)>;
using MyData = ::dsmr::ParsedData<DSMR_TEXT_SENSOR_LIST(DSMR_DATA_SENSOR, DSMR_COMMA)
DSMR_BOTH DSMR_SENSOR_LIST(DSMR_DATA_SENSOR, DSMR_COMMA)>;
class Dsmr : public Component, public uart::UARTDevice {
public:
@@ -133,4 +140,7 @@ class Dsmr : public Component, public uart::UARTDevice {
std::vector<uint8_t> decryption_key_{};
bool crc_check_;
};
} // namespace esphome::dsmr
} // namespace dsmr
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -3,34 +3,27 @@ from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
DEVICE_CLASS_APPARENT_POWER,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_DURATION,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_FREQUENCY,
DEVICE_CLASS_GAS,
DEVICE_CLASS_POWER,
DEVICE_CLASS_REACTIVE_POWER,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_WATER,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE,
UNIT_CUBIC_METER,
UNIT_HERTZ,
UNIT_KILOVOLT_AMPS,
UNIT_KILOVOLT_AMPS_REACTIVE,
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
UNIT_KILOWATT,
UNIT_KILOWATT_HOURS,
UNIT_SECOND,
UNIT_VOLT,
)
from . import CONF_DSMR_ID, Dsmr
AUTO_LOAD = ["dsmr"]
UNIT_GIGA_JOULE = "GJ"
CONFIG_SCHEMA = cv.Schema(
{
@@ -53,18 +46,6 @@ CONFIG_SCHEMA = cv.Schema(
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("energy_delivered_tariff3"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT_HOURS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("energy_delivered_tariff4"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT_HOURS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("energy_returned_lux"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT_HOURS,
accuracy_decimals=3,
@@ -83,82 +64,14 @@ CONFIG_SCHEMA = cv.Schema(
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("energy_returned_tariff3"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT_HOURS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("energy_returned_tariff4"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT_HOURS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("energy_delivered_tariff1_ch"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT_HOURS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("energy_delivered_tariff2_ch"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT_HOURS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("energy_returned_tariff1_ch"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT_HOURS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("energy_returned_tariff2_ch"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT_HOURS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("total_imported_energy"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
accuracy_decimals=3,
),
cv.Optional("reactive_energy_delivered_tariff1"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
accuracy_decimals=3,
),
cv.Optional("reactive_energy_delivered_tariff2"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
accuracy_decimals=3,
),
cv.Optional("reactive_energy_delivered_tariff3"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
accuracy_decimals=3,
),
cv.Optional("reactive_energy_delivered_tariff4"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
accuracy_decimals=3,
),
cv.Optional("total_exported_energy"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
accuracy_decimals=3,
),
cv.Optional("reactive_energy_returned_tariff1"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
accuracy_decimals=3,
),
cv.Optional("reactive_energy_returned_tariff2"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
accuracy_decimals=3,
),
cv.Optional("reactive_energy_returned_tariff3"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
accuracy_decimals=3,
),
cv.Optional("reactive_energy_returned_tariff4"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
accuracy_decimals=3,
),
cv.Optional("power_delivered"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT,
accuracy_decimals=3,
@@ -171,195 +84,61 @@ CONFIG_SCHEMA = cv.Schema(
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("power_delivered_ch"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("power_returned_ch"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("reactive_power_delivered"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("reactive_power_returned"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("electricity_threshold"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("electricity_switch_position"): sensor.sensor_schema(
accuracy_decimals=3,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("electricity_failures"): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("electricity_long_failures"): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("electricity_sags_l1"): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("electricity_sags_l2"): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("electricity_sags_l3"): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("electricity_swells_l1"): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("electricity_swells_l2"): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("electricity_swells_l3"): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_sag_time_l1"): sensor.sensor_schema(
unit_of_measurement=UNIT_SECOND,
accuracy_decimals=0,
device_class=DEVICE_CLASS_DURATION,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_sag_time_l2"): sensor.sensor_schema(
unit_of_measurement=UNIT_SECOND,
accuracy_decimals=0,
device_class=DEVICE_CLASS_DURATION,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_sag_time_l3"): sensor.sensor_schema(
unit_of_measurement=UNIT_SECOND,
accuracy_decimals=0,
device_class=DEVICE_CLASS_DURATION,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_sag_l1"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_sag_l2"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_sag_l3"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_swell_time_l1"): sensor.sensor_schema(
unit_of_measurement=UNIT_SECOND,
accuracy_decimals=0,
device_class=DEVICE_CLASS_DURATION,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_swell_time_l2"): sensor.sensor_schema(
unit_of_measurement=UNIT_SECOND,
accuracy_decimals=0,
device_class=DEVICE_CLASS_DURATION,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_swell_time_l3"): sensor.sensor_schema(
unit_of_measurement=UNIT_SECOND,
accuracy_decimals=0,
device_class=DEVICE_CLASS_DURATION,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_swell_l1"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_swell_l2"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_swell_l3"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("current_l1"): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=3,
accuracy_decimals=1,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("current_l2"): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=3,
accuracy_decimals=1,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("current_l3"): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("current"): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("current_n"): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("current_sum"): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("current_fuse_l1"): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("current_fuse_l2"): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("current_fuse_l3"): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=3,
accuracy_decimals=1,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
@@ -402,93 +181,51 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional("reactive_power_delivered_l1"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("reactive_power_delivered_l2"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("reactive_power_delivered_l3"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("reactive_power_returned_l1"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("reactive_power_returned_l2"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("reactive_power_returned_l3"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_l1"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_l2"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_l3"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_avg_l1"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_avg_l2"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_avg_l3"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("frequency"): sensor.sensor_schema(
unit_of_measurement=UNIT_HERTZ,
accuracy_decimals=3,
device_class=DEVICE_CLASS_FREQUENCY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("abs_power"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("gas_delivered"): sensor.sensor_schema(
unit_of_measurement=UNIT_CUBIC_METER,
accuracy_decimals=3,
@@ -507,109 +244,6 @@ CONFIG_SCHEMA = cv.Schema(
device_class=DEVICE_CLASS_WATER,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("thermal_delivered"): sensor.sensor_schema(
unit_of_measurement=UNIT_GIGA_JOULE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("sub_delivered"): sensor.sensor_schema(
unit_of_measurement=UNIT_CUBIC_METER,
accuracy_decimals=3,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("gas_device_type"): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("gas_valve_position"): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("thermal_device_type"): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("thermal_valve_position"): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("water_device_type"): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("water_valve_position"): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("sub_device_type"): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("sub_valve_position"): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("apparent_delivery_power"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("apparent_delivery_power_l1"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("apparent_delivery_power_l2"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("apparent_delivery_power_l3"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("apparent_return_power"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("apparent_return_power_l1"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("apparent_return_power_l2"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("apparent_return_power_l3"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("active_demand_power"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("active_demand_abs"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(
"active_energy_import_current_average_demand"
): sensor.sensor_schema(
@@ -618,90 +252,6 @@ CONFIG_SCHEMA = cv.Schema(
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(
"active_energy_export_current_average_demand"
): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(
"reactive_energy_import_current_average_demand"
): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(
"reactive_energy_export_current_average_demand"
): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(
"apparent_energy_import_current_average_demand"
): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(
"apparent_energy_export_current_average_demand"
): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("active_energy_import_last_completed_demand"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("active_energy_export_last_completed_demand"): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(
"reactive_energy_import_last_completed_demand"
): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(
"reactive_energy_export_last_completed_demand"
): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(
"apparent_energy_import_last_completed_demand"
): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(
"apparent_energy_export_last_completed_demand"
): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOVOLT_AMPS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(
"active_energy_import_maximum_demand_running_month"
): sensor.sensor_schema(
@@ -718,14 +268,6 @@ CONFIG_SCHEMA = cv.Schema(
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("fw_core_version"): sensor.sensor_schema(
accuracy_decimals=3,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("fw_module_version"): sensor.sensor_schema(
accuracy_decimals=3,
state_class=STATE_CLASS_MEASUREMENT,
),
}
).extend(cv.COMPONENT_SCHEMA)

View File

@@ -18,15 +18,11 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional("electricity_failure_log"): text_sensor.text_sensor_schema(),
cv.Optional("message_short"): text_sensor.text_sensor_schema(),
cv.Optional("message_long"): text_sensor.text_sensor_schema(),
cv.Optional("equipment_id"): text_sensor.text_sensor_schema(),
cv.Optional("gas_equipment_id"): text_sensor.text_sensor_schema(),
cv.Optional("gas_equipment_id_be"): text_sensor.text_sensor_schema(),
cv.Optional("thermal_equipment_id"): text_sensor.text_sensor_schema(),
cv.Optional("water_equipment_id"): text_sensor.text_sensor_schema(),
cv.Optional("sub_equipment_id"): text_sensor.text_sensor_schema(),
cv.Optional("gas_delivered_text"): text_sensor.text_sensor_schema(),
cv.Optional("fw_core_checksum"): text_sensor.text_sensor_schema(),
cv.Optional("fw_module_checksum"): text_sensor.text_sensor_schema(),
cv.Optional("telegram"): text_sensor.text_sensor_schema().extend(
{cv.Optional(CONF_INTERNAL, default=True): cv.boolean}
),

View File

@@ -31,7 +31,6 @@ from esphome.const import (
CONF_TRANSFORM,
CONF_UPDATE_INTERVAL,
CONF_WIDTH,
SCHEDULER_DONT_RUN,
)
from esphome.cpp_generator import RawExpression
from esphome.final_validate import full_config
@@ -73,10 +72,12 @@ TRANSFORM_OPTIONS = {CONF_MIRROR_X, CONF_MIRROR_Y, CONF_SWAP_XY}
def model_schema(config):
model = MODELS[config[CONF_MODEL]]
class_name = epaper_spi_ns.class_(model.class_name, EPaperBase)
minimum_update_interval = update_interval(
model.get_default(CONF_MINIMUM_UPDATE_INTERVAL, "1s")
)
cv_dimensions = cv.Optional if model.get_default(CONF_WIDTH) else cv.Required
return (
display.full_display_schema("60s")
.extend(
display.FULL_DISPLAY_SCHEMA.extend(
spi.spi_device_schema(
cs_pin_required=False,
default_mode="MODE0",
@@ -93,6 +94,9 @@ def model_schema(config):
{
cv.Optional(CONF_ROTATION, default=0): validate_rotation,
cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True),
cv.Optional(CONF_UPDATE_INTERVAL, default=cv.UNDEFINED): cv.All(
update_interval, cv.Range(min=minimum_update_interval)
),
cv.Optional(CONF_TRANSFORM): cv.Schema(
{
cv.Required(CONF_MIRROR_X): cv.boolean,
@@ -146,22 +150,15 @@ def _final_validate(config):
global_config = full_config.get()
from esphome.components.lvgl import DOMAIN as LVGL_DOMAIN
# If no drawing methods are configured, and LVGL is not enabled, show a test card
if (
CONF_LAMBDA not in config
and CONF_PAGES not in config
and LVGL_DOMAIN not in global_config
):
config[CONF_SHOW_TEST_CARD] = True
interval = config[CONF_UPDATE_INTERVAL]
if interval != SCHEDULER_DONT_RUN:
model = MODELS[config[CONF_MODEL]]
minimum = update_interval(model.get_default(CONF_MINIMUM_UPDATE_INTERVAL, "1s"))
if interval < minimum:
raise cv.Invalid(
f"update_interval must be at least {minimum} for {model.name}, got {interval}"
)
if CONF_LAMBDA not in config and CONF_PAGES not in config:
if LVGL_DOMAIN in global_config:
if CONF_UPDATE_INTERVAL not in config:
config[CONF_UPDATE_INTERVAL] = update_interval("never")
else:
# If no drawing methods are configured, and LVGL is not enabled, show a test card
config[CONF_SHOW_TEST_CARD] = True
elif CONF_UPDATE_INTERVAL not in config:
config[CONF_UPDATE_INTERVAL] = update_interval("1min")
return config

View File

@@ -652,9 +652,6 @@ CONF_LOOP_TASK_STACK_SIZE = "loop_task_stack_size"
KEY_VFS_SELECT_REQUIRED = "vfs_select_required"
KEY_VFS_DIR_REQUIRED = "vfs_dir_required"
# Ring buffer IRAM requirement tracking
KEY_RINGBUF_IN_IRAM = "ringbuf_in_iram"
def require_vfs_select() -> None:
"""Mark that VFS select support is required by a component.
@@ -674,17 +671,6 @@ def require_vfs_dir() -> None:
CORE.data[KEY_VFS_DIR_REQUIRED] = True
def enable_ringbuf_in_iram() -> None:
"""Keep ring buffer functions in IRAM instead of moving them to flash.
Call this from components that use esphome/core/ring_buffer.cpp and need
the ring buffer functions to remain in IRAM for performance reasons
(e.g., voice assistants, audio components).
This prevents CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH from being enabled.
"""
CORE.data[KEY_RINGBUF_IN_IRAM] = True
def _parse_idf_component(value: str) -> ConfigType:
"""Parse IDF component shorthand syntax like 'owner/component^version'"""
# Match operator followed by version-like string (digit or *)
@@ -1096,18 +1082,14 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH", True)
# Place ring buffer functions into flash instead of IRAM by default
# This saves IRAM but may impact performance for audio/voice components.
# Components that need ring buffer in IRAM call enable_ringbuf_in_iram().
# Users can also set ringbuf_in_iram: true to force IRAM placement.
# In ESP-IDF 6.0 flash placement becomes the default.
if conf[CONF_ADVANCED][CONF_RINGBUF_IN_IRAM] or CORE.data.get(
KEY_RINGBUF_IN_IRAM, False
):
# User config or component requires ring buffer in IRAM for performance
# This saves IRAM. In ESP-IDF 6.0 flash placement becomes the default.
# Users can set ringbuf_in_iram: true as an escape hatch if they encounter issues.
if conf[CONF_ADVANCED][CONF_RINGBUF_IN_IRAM]:
# User requests ring buffer in IRAM
# IDF 6.0+: will need CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH=n
add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH", False)
else:
# No component needs it - place in flash to save IRAM
# Place in flash to save IRAM (default)
add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH", True)
# Place heap functions into flash to save IRAM (~4-6KB savings)

View File

@@ -85,6 +85,7 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
break;
}
gpio_set_intr_type(this->get_pin_num(), idf_type);
gpio_intr_enable(this->get_pin_num());
if (!isr_service_installed) {
auto res = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3);
if (res != ESP_OK) {
@@ -94,7 +95,6 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
isr_service_installed = true;
}
gpio_isr_handler_add(this->get_pin_num(), func, arg);
gpio_intr_enable(this->get_pin_num());
}
size_t ESP32InternalGPIOPin::dump_summary(char *buffer, size_t len) const {

View File

@@ -98,10 +98,6 @@ void ESP32BLE::advertising_set_service_data(const std::vector<uint8_t> &data) {
}
void ESP32BLE::advertising_set_manufacturer_data(const std::vector<uint8_t> &data) {
this->advertising_set_manufacturer_data(std::span<const uint8_t>(data));
}
void ESP32BLE::advertising_set_manufacturer_data(std::span<const uint8_t> data) {
this->advertising_init_();
this->advertising_->set_manufacturer_data(data);
this->advertising_start();

View File

@@ -118,7 +118,6 @@ class ESP32BLE : public Component {
void advertising_start();
void advertising_set_service_data(const std::vector<uint8_t> &data);
void advertising_set_manufacturer_data(const std::vector<uint8_t> &data);
void advertising_set_manufacturer_data(std::span<const uint8_t> data);
void advertising_set_appearance(uint16_t appearance) { this->appearance_ = appearance; }
void advertising_set_service_data_and_name(std::span<const uint8_t> data, bool include_name);
void advertising_add_service_uuid(ESPBTUUID uuid);

View File

@@ -59,10 +59,6 @@ void BLEAdvertising::set_service_data(const std::vector<uint8_t> &data) {
}
void BLEAdvertising::set_manufacturer_data(const std::vector<uint8_t> &data) {
this->set_manufacturer_data(std::span<const uint8_t>(data));
}
void BLEAdvertising::set_manufacturer_data(std::span<const uint8_t> data) {
delete[] this->advertising_data_.p_manufacturer_data;
this->advertising_data_.p_manufacturer_data = nullptr;
this->advertising_data_.manufacturer_len = data.size();

View File

@@ -37,7 +37,6 @@ class BLEAdvertising {
void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; }
void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }
void set_manufacturer_data(const std::vector<uint8_t> &data);
void set_manufacturer_data(std::span<const uint8_t> data);
void set_appearance(uint16_t appearance) { this->advertising_data_.appearance = appearance; }
void set_service_data(const std::vector<uint8_t> &data);
void set_service_data(std::span<const uint8_t> data);

View File

@@ -39,36 +39,36 @@ ESPBTUUID ESPBTUUID::from_raw_reversed(const uint8_t *data) {
ret.uuid_.uuid.uuid128[ESP_UUID_LEN_128 - 1 - i] = data[i];
return ret;
}
ESPBTUUID ESPBTUUID::from_raw(const char *data, size_t length) {
ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
ESPBTUUID ret;
if (length == 4) {
if (data.length() == 4) {
// 16-bit UUID as 4-character hex string
auto parsed = parse_hex<uint16_t>(data, length);
auto parsed = parse_hex<uint16_t>(data);
if (parsed.has_value()) {
ret.uuid_.len = ESP_UUID_LEN_16;
ret.uuid_.uuid.uuid16 = parsed.value();
}
} else if (length == 8) {
} else if (data.length() == 8) {
// 32-bit UUID as 8-character hex string
auto parsed = parse_hex<uint32_t>(data, length);
auto parsed = parse_hex<uint32_t>(data);
if (parsed.has_value()) {
ret.uuid_.len = ESP_UUID_LEN_32;
ret.uuid_.uuid.uuid32 = parsed.value();
}
} else if (length == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be
// investigated (lack of time)
} else if (data.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be
// investigated (lack of time)
ret.uuid_.len = ESP_UUID_LEN_128;
memcpy(ret.uuid_.uuid.uuid128, reinterpret_cast<const uint8_t *>(data), 16);
} else if (length == 36) {
memcpy(ret.uuid_.uuid.uuid128, (uint8_t *) data.data(), 16);
} else if (data.length() == 36) {
// If the length of the string is 36 bytes then we will assume it is a long hex string in
// UUID format.
ret.uuid_.len = ESP_UUID_LEN_128;
int n = 0;
for (size_t i = 0; i < length; i += 2) {
if (data[i] == '-')
for (uint i = 0; i < data.length(); i += 2) {
if (data.c_str()[i] == '-')
i++;
uint8_t msb = data[i];
uint8_t lsb = data[i + 1];
uint8_t msb = data.c_str()[i];
uint8_t lsb = data.c_str()[i + 1];
if (msb > '9')
msb -= 7;
@@ -77,7 +77,7 @@ ESPBTUUID ESPBTUUID::from_raw(const char *data, size_t length) {
ret.uuid_.uuid.uuid128[15 - n++] = ((msb & 0x0F) << 4) | (lsb & 0x0F);
}
} else {
ESP_LOGE(TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes - %s", data);
ESP_LOGE(TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes - %s", data.c_str());
}
return ret;
}
@@ -143,7 +143,7 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const {
return this->as_128bit() == uuid.as_128bit();
}
esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; }
const char *ESPBTUUID::to_str(std::span<char, UUID_STR_LEN> output) const {
void ESPBTUUID::to_str(std::span<char, UUID_STR_LEN> output) const {
char *pos = output.data();
switch (this->uuid_.len) {
@@ -155,7 +155,7 @@ const char *ESPBTUUID::to_str(std::span<char, UUID_STR_LEN> output) const {
*pos++ = format_hex_pretty_char((this->uuid_.uuid.uuid16 >> 4) & 0x0F);
*pos++ = format_hex_pretty_char(this->uuid_.uuid.uuid16 & 0x0F);
*pos = '\0';
return output.data();
return;
case ESP_UUID_LEN_32:
*pos++ = '0';
@@ -164,7 +164,7 @@ const char *ESPBTUUID::to_str(std::span<char, UUID_STR_LEN> output) const {
*pos++ = format_hex_pretty_char((this->uuid_.uuid.uuid32 >> shift) & 0x0F);
}
*pos = '\0';
return output.data();
return;
default:
case ESP_UUID_LEN_128:
@@ -178,7 +178,7 @@ const char *ESPBTUUID::to_str(std::span<char, UUID_STR_LEN> output) const {
}
}
*pos = '\0';
return output.data();
return;
}
}
std::string ESPBTUUID::to_string() const {

View File

@@ -7,7 +7,6 @@
#ifdef USE_ESP32
#ifdef USE_ESP32_BLE_UUID
#include <initializer_list>
#include <span>
#include <string>
#include <esp_bt_defs.h>
@@ -28,12 +27,7 @@ class ESPBTUUID {
static ESPBTUUID from_raw(const uint8_t *data);
static ESPBTUUID from_raw_reversed(const uint8_t *data);
static ESPBTUUID from_raw(const char *data, size_t length);
static ESPBTUUID from_raw(const char *data) { return from_raw(data, strlen(data)); }
static ESPBTUUID from_raw(const std::string &data) { return from_raw(data.c_str(), data.length()); }
static ESPBTUUID from_raw(std::initializer_list<uint8_t> data) {
return from_raw(reinterpret_cast<const char *>(data.begin()), data.size());
}
static ESPBTUUID from_raw(const std::string &data);
static ESPBTUUID from_uuid(esp_bt_uuid_t uuid);
@@ -47,7 +41,7 @@ class ESPBTUUID {
esp_bt_uuid_t get_uuid() const;
std::string to_string() const;
const char *to_str(std::span<char, UUID_STR_LEN> output) const;
void to_str(std::span<char, UUID_STR_LEN> output) const;
protected:
esp_bt_uuid_t uuid_;

View File

@@ -1,6 +1,5 @@
#include "esp32_ble_beacon.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#ifdef USE_ESP32

View File

@@ -529,7 +529,7 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_
case ESP_GAP_BLE_AUTH_CMPL_EVT:
if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr))
return;
char addr_str[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
char addr_str[MAC_ADDR_STR_LEN];
format_mac_addr_upper(param->ble_security.auth_cmpl.bd_addr, addr_str);
ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_, addr_str);
if (!param->ble_security.auth_cmpl.success) {

View File

@@ -22,6 +22,7 @@ namespace esphome::esp32_ble_client {
namespace espbt = esphome::esp32_ble_tracker;
static const int UNSET_CONN_ID = 0xFFFF;
static constexpr size_t MAC_ADDR_STR_LEN = 18; // "AA:BB:CC:DD:EE:FF\0"
class BLEClientBase : public espbt::ESPBTClient, public Component {
public:
@@ -110,8 +111,8 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
esp_gatt_status_t status_{ESP_GATT_OK};
// Group 4: Arrays
char address_str_[MAC_ADDRESS_PRETTY_BUFFER_SIZE]{};
esp_bd_addr_t remote_bda_; // 6 bytes
char address_str_[MAC_ADDR_STR_LEN]{}; // 18 bytes: "AA:BB:CC:DD:EE:FF\0"
esp_bd_addr_t remote_bda_; // 6 bytes
// Group 5: 2-byte types
uint16_t conn_id_{UNSET_CONN_ID};

View File

@@ -15,10 +15,7 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_characteristic_on_w
Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory)
new Trigger<std::vector<uint8_t>, uint16_t>();
characteristic->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) {
// Convert span to vector for trigger - copy is necessary because:
// 1. Trigger stores the data for use in automation actions that execute later
// 2. The span is only valid during this callback (points to temporary BLE stack data)
// 3. User lambdas in automations need persistent data they can access asynchronously
// Convert span to vector for trigger
on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id);
});
return on_write_trigger;
@@ -30,10 +27,7 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_descriptor_on_write
Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory)
new Trigger<std::vector<uint8_t>, uint16_t>();
descriptor->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) {
// Convert span to vector for trigger - copy is necessary because:
// 1. Trigger stores the data for use in automation actions that execute later
// 2. The span is only valid during this callback (points to temporary BLE stack data)
// 3. User lambdas in automations need persistent data they can access asynchronously
// Convert span to vector for trigger
on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id);
});
return on_write_trigger;

View File

@@ -639,8 +639,9 @@ void ESPBTDevice::parse_adv_(const uint8_t *payload, uint8_t len) {
}
std::string ESPBTDevice::address_str() const {
char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
return this->address_str_to(buf);
char mac[18];
format_mac_addr_upper(this->address_, mac);
return mac;
}
uint64_t ESPBTDevice::address_uint64() const { return esp32_ble::ble_addr_to_uint64(this->address_); }
@@ -675,8 +676,7 @@ void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) {
}
this->already_discovered_.push_back(address);
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
ESP_LOGD(TAG, "Found device %s RSSI=%d", device.address_str_to(addr_buf), device.get_rssi());
ESP_LOGD(TAG, "Found device %s RSSI=%d", device.address_str().c_str(), device.get_rssi());
const char *address_type_s;
switch (device.get_address_type()) {

View File

@@ -6,7 +6,6 @@
#include "esphome/core/helpers.h"
#include <array>
#include <span>
#include <string>
#include <vector>
@@ -74,12 +73,6 @@ class ESPBTDevice {
std::string address_str() const;
/// Format MAC address into provided buffer, returns pointer to buffer for convenience
const char *address_str_to(std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf) const {
format_mac_addr_upper(this->address_, buf.data());
return buf.data();
}
uint64_t address_uint64() const;
const uint8_t *address() const { return address_; }

View File

@@ -398,12 +398,9 @@ void ESP32ImprovComponent::check_wifi_connection_() {
#ifdef USE_ESP32_IMPROV_NEXT_URL
// Add next_url if configured (should be first per Improv BLE spec)
{
char url_buffer[384];
size_t len = this->get_formatted_next_url_(url_buffer, sizeof(url_buffer));
if (len > 0) {
url_strings[url_count++] = std::string(url_buffer, len);
}
std::string next_url = this->get_formatted_next_url_();
if (!next_url.empty()) {
url_strings[url_count++] = std::move(next_url);
}
#endif

View File

@@ -23,18 +23,12 @@ from esphome.helpers import copy_file_if_changed
from .boards import BOARDS, ESP8266_LD_SCRIPTS
from .const import (
CONF_EARLY_PIN_INIT,
CONF_ENABLE_SERIAL,
CONF_ENABLE_SERIAL1,
CONF_RESTORE_FROM_FLASH,
KEY_BOARD,
KEY_ESP8266,
KEY_FLASH_SIZE,
KEY_PIN_INITIAL_STATES,
KEY_SERIAL1_REQUIRED,
KEY_SERIAL_REQUIRED,
KEY_WAVEFORM_REQUIRED,
enable_serial,
enable_serial1,
esp8266_ns,
)
from .gpio import PinInitialState, add_pin_initial_states_array
@@ -177,8 +171,6 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_BOARD_FLASH_MODE, default="dout"): cv.one_of(
*BUILD_FLASH_MODES, lower=True
),
cv.Optional(CONF_ENABLE_SERIAL): cv.boolean,
cv.Optional(CONF_ENABLE_SERIAL1): cv.boolean,
}
),
set_core_data,
@@ -239,12 +231,6 @@ async def to_code(config):
if config[CONF_EARLY_PIN_INIT]:
cg.add_define("USE_ESP8266_EARLY_PIN_INIT")
# Allow users to force-enable Serial objects for use in lambdas or external libraries
if config.get(CONF_ENABLE_SERIAL):
enable_serial()
if config.get(CONF_ENABLE_SERIAL1):
enable_serial1()
# Arduino 2 has a non-standards conformant new that returns a nullptr instead of failing when
# out of memory and exceptions are disabled. Since Arduino 2.6.0, this flag can be used to make
# new abort instead. Use it so that OOM fails early (on allocation) instead of on dereference of
@@ -285,7 +271,6 @@ async def to_code(config):
CORE.add_job(add_pin_initial_states_array)
CORE.add_job(finalize_waveform_config)
CORE.add_job(finalize_serial_config)
@coroutine_with_priority(CoroPriority.WORKAROUNDS)
@@ -301,24 +286,6 @@ async def finalize_waveform_config() -> None:
cg.add_build_flag("-DUSE_ESP8266_WAVEFORM_STUBS")
@coroutine_with_priority(CoroPriority.WORKAROUNDS)
async def finalize_serial_config() -> None:
"""Exclude unused Arduino Serial objects from the build.
This runs at WORKAROUNDS priority (-999) to ensure all components
have had a chance to call enable_serial() or enable_serial1() first.
The Arduino ESP8266 core defines two global Serial objects (32 bytes each).
By adding NO_GLOBAL_SERIAL or NO_GLOBAL_SERIAL1 build flags, we prevent
unused Serial objects from being linked, saving 32 bytes each.
"""
esp8266_data = CORE.data.get(KEY_ESP8266, {})
if not esp8266_data.get(KEY_SERIAL_REQUIRED, False):
cg.add_build_flag("-DNO_GLOBAL_SERIAL")
if not esp8266_data.get(KEY_SERIAL1_REQUIRED, False):
cg.add_build_flag("-DNO_GLOBAL_SERIAL1")
# Called by writer.py
def copy_files() -> None:
dir = Path(__file__).parent

View File

@@ -6,12 +6,8 @@ KEY_BOARD = "board"
KEY_PIN_INITIAL_STATES = "pin_initial_states"
CONF_RESTORE_FROM_FLASH = "restore_from_flash"
CONF_EARLY_PIN_INIT = "early_pin_init"
CONF_ENABLE_SERIAL = "enable_serial"
CONF_ENABLE_SERIAL1 = "enable_serial1"
KEY_FLASH_SIZE = "flash_size"
KEY_WAVEFORM_REQUIRED = "waveform_required"
KEY_SERIAL_REQUIRED = "serial_required"
KEY_SERIAL1_REQUIRED = "serial1_required"
# esp8266 namespace is already defined by arduino, manually prefix esphome
esp8266_ns = cg.global_ns.namespace("esphome").namespace("esp8266")
@@ -33,35 +29,3 @@ def require_waveform() -> None:
require_waveform()
"""
CORE.data.setdefault(KEY_ESP8266, {})[KEY_WAVEFORM_REQUIRED] = True
def enable_serial() -> None:
"""Mark that Arduino Serial (UART0) is required.
Call this from components that use the global Serial object.
If no component calls this, Serial is excluded from the build
to save 32 bytes of RAM.
Example:
from esphome.components.esp8266.const import enable_serial
async def to_code(config):
enable_serial()
"""
CORE.data.setdefault(KEY_ESP8266, {})[KEY_SERIAL_REQUIRED] = True
def enable_serial1() -> None:
"""Mark that Arduino Serial1 (UART1) is required.
Call this from components that use the global Serial1 object.
If no component calls this, Serial1 is excluded from the build
to save 32 bytes of RAM.
Example:
from esphome.components.esp8266.const import enable_serial1
async def to_code(config):
enable_serial1()
"""
CORE.data.setdefault(KEY_ESP8266, {})[KEY_SERIAL1_REQUIRED] = True

View File

@@ -1,6 +1,7 @@
#ifdef USE_ESP8266
#include <c_types.h>
#include <cinttypes>
extern "C" {
#include "spi_flash.h"
}
@@ -26,16 +27,6 @@ static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200;
static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128;
static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4;
// RTC memory layout for preferences:
// - Eboot region: RTC words 0-31 (reserved, mapped from preference offset 96-127)
// - Normal region: RTC words 32-127 (mapped from preference offset 0-95)
static constexpr uint32_t RTC_EBOOT_REGION_WORDS = 32; // Words 0-31 reserved for eboot
static constexpr uint32_t RTC_NORMAL_REGION_WORDS = 96; // Words 32-127 for normal prefs
static constexpr uint32_t PREF_TOTAL_WORDS = RTC_EBOOT_REGION_WORDS + RTC_NORMAL_REGION_WORDS; // 128
// Maximum preference size in words (limited by uint8_t length_words field)
static constexpr uint32_t MAX_PREFERENCE_WORDS = 255;
#define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START)
#ifdef USE_ESP8266_PREFERENCES_FLASH
@@ -127,10 +118,6 @@ static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) {
return true;
}
// Stack buffer size - 16 words total: up to 15 words of preference data + 1 word CRC (60 bytes of preference data)
// This handles virtually all real-world preferences without heap allocation
static constexpr size_t PREF_BUFFER_WORDS = 16;
class ESP8266PreferenceBackend : public ESPPreferenceBackend {
public:
uint32_t type = 0;
@@ -139,54 +126,36 @@ class ESP8266PreferenceBackend : public ESPPreferenceBackend {
bool in_flash = false;
bool save(const uint8_t *data, size_t len) override {
if (bytes_to_words(len) != this->length_words)
if (bytes_to_words(len) != length_words) {
return false;
const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
uint32_t stack_buffer[PREF_BUFFER_WORDS];
std::unique_ptr<uint32_t[]> heap_buffer;
uint32_t *buffer;
if (buffer_size <= PREF_BUFFER_WORDS) {
buffer = stack_buffer;
} else {
heap_buffer = make_unique<uint32_t[]>(buffer_size);
buffer = heap_buffer.get();
}
memset(buffer, 0, buffer_size * sizeof(uint32_t));
size_t buffer_size = static_cast<size_t>(length_words) + 1;
std::unique_ptr<uint32_t[]> buffer(new uint32_t[buffer_size]()); // Note the () for zero-initialization
memcpy(buffer.get(), data, len);
buffer[length_words] = calculate_crc(buffer.get(), buffer.get() + length_words, type);
memcpy(buffer, data, len);
buffer[this->length_words] = calculate_crc(buffer, buffer + this->length_words, this->type);
return this->in_flash ? save_to_flash(this->offset, buffer, buffer_size)
: save_to_rtc(this->offset, buffer, buffer_size);
if (in_flash) {
return save_to_flash(offset, buffer.get(), buffer_size);
}
return save_to_rtc(offset, buffer.get(), buffer_size);
}
bool load(uint8_t *data, size_t len) override {
if (bytes_to_words(len) != this->length_words)
if (bytes_to_words(len) != length_words) {
return false;
const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
uint32_t stack_buffer[PREF_BUFFER_WORDS];
std::unique_ptr<uint32_t[]> heap_buffer;
uint32_t *buffer;
if (buffer_size <= PREF_BUFFER_WORDS) {
buffer = stack_buffer;
} else {
heap_buffer = make_unique<uint32_t[]>(buffer_size);
buffer = heap_buffer.get();
}
bool ret = this->in_flash ? load_from_flash(this->offset, buffer, buffer_size)
: load_from_rtc(this->offset, buffer, buffer_size);
size_t buffer_size = static_cast<size_t>(length_words) + 1;
std::unique_ptr<uint32_t[]> buffer(new uint32_t[buffer_size]());
bool ret = in_flash ? load_from_flash(offset, buffer.get(), buffer_size)
: load_from_rtc(offset, buffer.get(), buffer_size);
if (!ret)
return false;
if (buffer[this->length_words] != calculate_crc(buffer, buffer + this->length_words, this->type))
uint32_t crc = calculate_crc(buffer.get(), buffer.get() + length_words, type);
if (buffer[length_words] != crc) {
return false;
}
memcpy(data, buffer, len);
memcpy(data, buffer.get(), len);
return true;
}
};
@@ -207,42 +176,50 @@ class ESP8266Preferences : public ESPPreferences {
}
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
const uint32_t length_words = bytes_to_words(length);
if (length_words > MAX_PREFERENCE_WORDS) {
ESP_LOGE(TAG, "Preference too large: %u words", static_cast<unsigned int>(length_words));
uint32_t length_words = bytes_to_words(length);
if (length_words > 255) {
ESP_LOGE(TAG, "Preference too large: %" PRIu32 " words > 255", length_words);
return {};
}
if (in_flash) {
uint32_t start = current_flash_offset;
uint32_t end = start + length_words + 1;
if (end > ESP8266_FLASH_STORAGE_SIZE)
return {};
auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
pref->offset = static_cast<uint16_t>(start);
pref->type = type;
pref->length_words = static_cast<uint8_t>(length_words);
pref->in_flash = true;
current_flash_offset = end;
return {pref};
}
uint32_t start = current_offset;
uint32_t end = start + length_words + 1;
bool in_normal = start < 96;
// Normal: offset 0-95 maps to RTC offset 32 - 127,
// Eboot: offset 96-127 maps to RTC offset 0 - 31 words
if (in_normal && end > 96) {
// start is in normal but end is not -> switch to Eboot
current_offset = start = 96;
end = start + length_words + 1;
in_normal = false;
}
if (end > 128) {
// Doesn't fit in data, return uninitialized preference obj.
return {};
}
const uint32_t total_words = length_words + 1; // +1 for CRC
uint16_t offset;
if (in_flash) {
if (this->current_flash_offset + total_words > ESP8266_FLASH_STORAGE_SIZE)
return {};
offset = static_cast<uint16_t>(this->current_flash_offset);
this->current_flash_offset += total_words;
} else {
uint32_t start = this->current_offset;
bool in_normal = start < RTC_NORMAL_REGION_WORDS;
// Normal: offset 0-95 maps to RTC offset 32-127
// Eboot: offset 96-127 maps to RTC offset 0-31
if (in_normal && start + total_words > RTC_NORMAL_REGION_WORDS) {
// start is in normal but end is not -> switch to Eboot
this->current_offset = start = RTC_NORMAL_REGION_WORDS;
in_normal = false;
}
if (start + total_words > PREF_TOTAL_WORDS)
return {}; // Doesn't fit in RTC memory
// Convert preference offset to RTC memory offset
offset = static_cast<uint16_t>(in_normal ? start + RTC_EBOOT_REGION_WORDS : start - RTC_NORMAL_REGION_WORDS);
this->current_offset = start + total_words;
}
uint32_t rtc_offset = in_normal ? start + 32 : start - 96;
auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
pref->offset = offset;
pref->offset = static_cast<uint16_t>(rtc_offset);
pref->type = type;
pref->length_words = static_cast<uint8_t>(length_words);
pref->in_flash = in_flash;
pref->in_flash = false;
current_offset += length_words + 1;
return pref;
}

View File

@@ -4,7 +4,6 @@
#include "esphome/components/sha256/sha256.h"
#endif
#include "esphome/components/network/util.h"
#include "esphome/components/socket/socket.h"
#include "esphome/components/ota/ota_backend.h"
#include "esphome/components/ota/ota_backend_esp8266.h"
#include "esphome/components/ota/ota_backend_arduino_libretiny.h"
@@ -388,14 +387,14 @@ bool ESPHomeOTAComponent::readall_(uint8_t *buf, size_t len) {
while (len - at > 0) {
uint32_t now = millis();
if (now - start > OTA_SOCKET_TIMEOUT_DATA) {
ESP_LOGW(TAG, "Timeout reading %zu bytes", len);
ESP_LOGW(TAG, "Timeout reading %d bytes", len);
return false;
}
ssize_t read = this->client_->read(buf + at, len - at);
if (read == -1) {
if (!this->would_block_(errno)) {
ESP_LOGW(TAG, "Read err %zu bytes, errno %d", len, errno);
ESP_LOGW(TAG, "Read err %d bytes, errno %d", len, errno);
return false;
}
} else if (read == 0) {
@@ -415,14 +414,14 @@ bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) {
while (len - at > 0) {
uint32_t now = millis();
if (now - start > OTA_SOCKET_TIMEOUT_DATA) {
ESP_LOGW(TAG, "Timeout writing %zu bytes", len);
ESP_LOGW(TAG, "Timeout writing %d bytes", len);
return false;
}
ssize_t written = this->client_->write(buf + at, len - at);
if (written == -1) {
if (!this->would_block_(errno)) {
ESP_LOGW(TAG, "Write err %zu bytes, errno %d", len, errno);
ESP_LOGW(TAG, "Write err %d bytes, errno %d", len, errno);
return false;
}
} else {
@@ -444,9 +443,7 @@ void ESPHomeOTAComponent::log_socket_error_(const LogString *msg) {
void ESPHomeOTAComponent::log_read_error_(const LogString *what) { ESP_LOGW(TAG, "Read %s failed", LOG_STR_ARG(what)); }
void ESPHomeOTAComponent::log_start_(const LogString *phase) {
char peername[socket::SOCKADDR_STR_LEN];
this->client_->getpeername_to(peername);
ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), peername);
ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), this->client_->getpeername().c_str());
}
void ESPHomeOTAComponent::log_remote_closed_(const LogString *during) {
@@ -563,9 +560,7 @@ bool ESPHomeOTAComponent::handle_auth_send_() {
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
// (no passing to other functions). All hash operations must happen in this function.
// NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for
// hardware SHA acceleration DMA operations.
alignas(32) sha256::SHA256 hasher;
sha256::SHA256 hasher;
const size_t hex_size = hasher.get_size() * 2;
const size_t nonce_len = hasher.get_size() / 4;
@@ -639,9 +634,7 @@ bool ESPHomeOTAComponent::handle_auth_read_() {
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
// (no passing to other functions). All hash operations must happen in this function.
// NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for
// hardware SHA acceleration DMA operations.
alignas(32) sha256::SHA256 hasher;
sha256::SHA256 hasher;
hasher.init();
hasher.add(this->password_.c_str(), this->password_.length());

View File

@@ -779,8 +779,8 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char hex_buf[format_hex_pretty_size(PHY_REG_SIZE)];
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
#endif
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
/*
* Bit 7 is `RMII Reference Clock Select`. Default is `0`.
@@ -797,10 +797,8 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
ESPHL_ERROR_CHECK(err, "Write PHY Control 2 failed");
err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2));
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s",
format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
#endif
}
}
#endif // USE_ETHERNET_KSZ8081

View File

@@ -71,7 +71,7 @@ void FanCall::validate_() {
auto traits = this->parent_.get_traits();
if (this->speed_.has_value()) {
this->speed_ = clamp(*this->speed_, 1, static_cast<int>(traits.supported_speed_count()));
this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count());
// https://developers.home-assistant.io/docs/core/entity/fan/#preset-modes
// "Manually setting a speed must disable any set preset mode"

View File

@@ -11,7 +11,7 @@ namespace fan {
class FanTraits {
public:
FanTraits() = default;
FanTraits(bool oscillation, bool speed, bool direction, uint8_t speed_count)
FanTraits(bool oscillation, bool speed, bool direction, int speed_count)
: oscillation_(oscillation), speed_(speed), direction_(direction), speed_count_(speed_count) {}
/// Return if this fan supports oscillation.
@@ -23,9 +23,9 @@ class FanTraits {
/// Set whether this fan supports speed levels.
void set_speed(bool speed) { this->speed_ = speed; }
/// Return how many speed levels the fan has
uint8_t supported_speed_count() const { return this->speed_count_; }
int supported_speed_count() const { return this->speed_count_; }
/// Set how many speed levels this fan has.
void set_supported_speed_count(uint8_t speed_count) { this->speed_count_ = speed_count; }
void set_supported_speed_count(int speed_count) { this->speed_count_ = speed_count; }
/// Return if this fan supports changing direction
bool supports_direction() const { return this->direction_; }
/// Set whether this fan supports changing direction
@@ -64,7 +64,7 @@ class FanTraits {
bool oscillation_{false};
bool speed_{false};
bool direction_{false};
uint8_t speed_count_{};
int speed_count_{};
std::vector<const char *> preset_modes_{};
};

View File

@@ -39,7 +39,7 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_DECAY_MODE, default="SLOW"): cv.enum(
DECAY_MODE_OPTIONS, upper=True
),
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1, max=255),
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1),
cv.Optional(CONF_ENABLE_PIN): cv.use_id(output.FloatOutput),
cv.Optional(CONF_PRESET_MODES): validate_preset_modes,
}

View File

@@ -15,7 +15,7 @@ enum DecayMode {
class HBridgeFan : public Component, public fan::Fan {
public:
HBridgeFan(uint8_t speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {}
HBridgeFan(int speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {}
void set_pin_a(output::FloatOutput *pin_a) { pin_a_ = pin_a; }
void set_pin_b(output::FloatOutput *pin_b) { pin_b_ = pin_b; }
@@ -33,7 +33,7 @@ class HBridgeFan : public Component, public fan::Fan {
output::FloatOutput *pin_b_;
output::FloatOutput *enable_{nullptr};
output::BinaryOutput *oscillating_{nullptr};
uint8_t speed_count_{};
int speed_count_{};
DecayMode decay_mode_{DECAY_MODE_SLOW};
fan::FanTraits traits_;
std::vector<const char *> preset_modes_{};

View File

@@ -1,5 +1,3 @@
import logging
import esphome.codegen as cg
from esphome.components import i2c, sensor
from esphome.components.aqi import AQI_CALCULATION_TYPE, CONF_AQI, CONF_CALCULATION_TYPE
@@ -18,8 +16,6 @@ from esphome.const import (
UNIT_MICROGRAMS_PER_CUBIC_METER,
)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["aqi"]
CODEOWNERS = ["@freekode"]
@@ -103,12 +99,7 @@ async def to_code(config):
sens = await sensor.new_sensor(config[CONF_PM_10_0])
cg.add(var.set_pm_10_0_sensor(sens))
# Remove before 2026.12.0
if CONF_AQI in config:
_LOGGER.warning(
"The 'aqi' option in hm3301 is deprecated, "
"please use the standalone 'aqi' sensor platform instead."
)
sens = await sensor.new_sensor(config[CONF_AQI])
cg.add(var.set_aqi_sensor(sens))
cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE]))

View File

@@ -1,7 +1,6 @@
#include "homeassistant_binary_sensor.h"
#include "esphome/components/api/api_server.h"
#include "esphome/core/log.h"
#include "esphome/core/string_ref.h"
#include "esphome/components/api/api_server.h"
namespace esphome {
namespace homeassistant {
@@ -9,30 +8,31 @@ namespace homeassistant {
static const char *const TAG = "homeassistant.binary_sensor";
void HomeassistantBinarySensor::setup() {
api::global_api_server->subscribe_home_assistant_state(this->entity_id_, this->attribute_, [this](StringRef state) {
auto val = parse_on_off(state.c_str());
switch (val) {
case PARSE_NONE:
case PARSE_TOGGLE:
ESP_LOGW(TAG, "Can't convert '%s' to binary state!", state.c_str());
break;
case PARSE_ON:
case PARSE_OFF:
bool new_state = val == PARSE_ON;
if (this->attribute_ != nullptr) {
ESP_LOGD(TAG, "'%s::%s': Got attribute state %s", this->entity_id_, this->attribute_, ONOFF(new_state));
} else {
ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_, ONOFF(new_state));
api::global_api_server->subscribe_home_assistant_state(
this->entity_id_, this->attribute_, [this](const std::string &state) {
auto val = parse_on_off(state.c_str());
switch (val) {
case PARSE_NONE:
case PARSE_TOGGLE:
ESP_LOGW(TAG, "Can't convert '%s' to binary state!", state.c_str());
break;
case PARSE_ON:
case PARSE_OFF:
bool new_state = val == PARSE_ON;
if (this->attribute_ != nullptr) {
ESP_LOGD(TAG, "'%s::%s': Got attribute state %s", this->entity_id_, this->attribute_, ONOFF(new_state));
} else {
ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_, ONOFF(new_state));
}
if (this->initial_) {
this->publish_initial_state(new_state);
} else {
this->publish_state(new_state);
}
break;
}
if (this->initial_) {
this->publish_initial_state(new_state);
} else {
this->publish_state(new_state);
}
break;
}
this->initial_ = false;
});
this->initial_ = false;
});
}
void HomeassistantBinarySensor::dump_config() {
LOG_BINARY_SENSOR("", "Homeassistant Binary Sensor", this);

View File

@@ -3,15 +3,14 @@
#include "esphome/components/api/api_pb2.h"
#include "esphome/components/api/api_server.h"
#include "esphome/core/log.h"
#include "esphome/core/string_ref.h"
namespace esphome {
namespace homeassistant {
static const char *const TAG = "homeassistant.number";
void HomeassistantNumber::state_changed_(StringRef state) {
auto number_value = parse_number<float>(state.c_str());
void HomeassistantNumber::state_changed_(const std::string &state) {
auto number_value = parse_number<float>(state);
if (!number_value.has_value()) {
ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_, state.c_str());
this->publish_state(NAN);
@@ -24,8 +23,8 @@ void HomeassistantNumber::state_changed_(StringRef state) {
this->publish_state(number_value.value());
}
void HomeassistantNumber::min_retrieved_(StringRef min) {
auto min_value = parse_number<float>(min.c_str());
void HomeassistantNumber::min_retrieved_(const std::string &min) {
auto min_value = parse_number<float>(min);
if (!min_value.has_value()) {
ESP_LOGE(TAG, "'%s': Can't convert 'min' value '%s' to number!", this->entity_id_, min.c_str());
return;
@@ -34,8 +33,8 @@ void HomeassistantNumber::min_retrieved_(StringRef min) {
this->traits.set_min_value(min_value.value());
}
void HomeassistantNumber::max_retrieved_(StringRef max) {
auto max_value = parse_number<float>(max.c_str());
void HomeassistantNumber::max_retrieved_(const std::string &max) {
auto max_value = parse_number<float>(max);
if (!max_value.has_value()) {
ESP_LOGE(TAG, "'%s': Can't convert 'max' value '%s' to number!", this->entity_id_, max.c_str());
return;
@@ -44,8 +43,8 @@ void HomeassistantNumber::max_retrieved_(StringRef max) {
this->traits.set_max_value(max_value.value());
}
void HomeassistantNumber::step_retrieved_(StringRef step) {
auto step_value = parse_number<float>(step.c_str());
void HomeassistantNumber::step_retrieved_(const std::string &step) {
auto step_value = parse_number<float>(step);
if (!step_value.has_value()) {
ESP_LOGE(TAG, "'%s': Can't convert 'step' value '%s' to number!", this->entity_id_, step.c_str());
return;
@@ -86,15 +85,15 @@ void HomeassistantNumber::control(float value) {
static constexpr auto VALUE_KEY = StringRef::from_lit("value");
api::HomeassistantActionRequest resp;
resp.service = SERVICE_NAME;
resp.set_service(SERVICE_NAME);
resp.data.init(2);
auto &entity_id = resp.data.emplace_back();
entity_id.key = ENTITY_ID_KEY;
entity_id.set_key(ENTITY_ID_KEY);
entity_id.value = this->entity_id_;
auto &entity_value = resp.data.emplace_back();
entity_value.key = VALUE_KEY;
entity_value.set_key(VALUE_KEY);
entity_value.value = to_string(value);
api::global_api_server->send_homeassistant_action(resp);

View File

@@ -1,8 +1,10 @@
#pragma once
#include <map>
#include <string>
#include "esphome/components/number/number.h"
#include "esphome/core/component.h"
#include "esphome/core/string_ref.h"
namespace esphome {
namespace homeassistant {
@@ -16,10 +18,10 @@ class HomeassistantNumber : public number::Number, public Component {
float get_setup_priority() const override;
protected:
void state_changed_(StringRef state);
void min_retrieved_(StringRef min);
void max_retrieved_(StringRef max);
void step_retrieved_(StringRef step);
void state_changed_(const std::string &state);
void min_retrieved_(const std::string &min);
void max_retrieved_(const std::string &max);
void step_retrieved_(const std::string &step);
void control(float value) override;

View File

@@ -1,7 +1,6 @@
#include "homeassistant_sensor.h"
#include "esphome/components/api/api_server.h"
#include "esphome/core/log.h"
#include "esphome/core/string_ref.h"
#include "esphome/components/api/api_server.h"
namespace esphome {
namespace homeassistant {
@@ -9,21 +8,22 @@ namespace homeassistant {
static const char *const TAG = "homeassistant.sensor";
void HomeassistantSensor::setup() {
api::global_api_server->subscribe_home_assistant_state(this->entity_id_, this->attribute_, [this](StringRef state) {
auto val = parse_number<float>(state.c_str());
if (!val.has_value()) {
ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_, state.c_str());
this->publish_state(NAN);
return;
}
api::global_api_server->subscribe_home_assistant_state(
this->entity_id_, this->attribute_, [this](const std::string &state) {
auto val = parse_number<float>(state);
if (!val.has_value()) {
ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_, state.c_str());
this->publish_state(NAN);
return;
}
if (this->attribute_ != nullptr) {
ESP_LOGD(TAG, "'%s::%s': Got attribute state %.2f", this->entity_id_, this->attribute_, *val);
} else {
ESP_LOGD(TAG, "'%s': Got state %.2f", this->entity_id_, *val);
}
this->publish_state(*val);
});
if (this->attribute_ != nullptr) {
ESP_LOGD(TAG, "'%s::%s': Got attribute state %.2f", this->entity_id_, this->attribute_, *val);
} else {
ESP_LOGD(TAG, "'%s': Got state %.2f", this->entity_id_, *val);
}
this->publish_state(*val);
});
}
void HomeassistantSensor::dump_config() {
LOG_SENSOR("", "Homeassistant Sensor", this);

View File

@@ -1,7 +1,6 @@
#include "homeassistant_switch.h"
#include "esphome/components/api/api_server.h"
#include "esphome/core/log.h"
#include "esphome/core/string_ref.h"
namespace esphome {
namespace homeassistant {
@@ -11,7 +10,7 @@ static const char *const TAG = "homeassistant.switch";
using namespace esphome::switch_;
void HomeassistantSwitch::setup() {
api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullptr, [this](StringRef state) {
api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullptr, [this](const std::string &state) {
auto val = parse_on_off(state.c_str());
switch (val) {
case PARSE_NONE:
@@ -47,14 +46,14 @@ void HomeassistantSwitch::write_state(bool state) {
api::HomeassistantActionRequest resp;
if (state) {
resp.service = SERVICE_ON;
resp.set_service(SERVICE_ON);
} else {
resp.service = SERVICE_OFF;
resp.set_service(SERVICE_OFF);
}
resp.data.init(1);
auto &entity_id_kv = resp.data.emplace_back();
entity_id_kv.key = ENTITY_ID_KEY;
entity_id_kv.set_key(ENTITY_ID_KEY);
entity_id_kv.value = this->entity_id_;
api::global_api_server->send_homeassistant_action(resp);

View File

@@ -1,7 +1,6 @@
#include "homeassistant_text_sensor.h"
#include "esphome/components/api/api_server.h"
#include "esphome/core/log.h"
#include "esphome/core/string_ref.h"
#include "esphome/components/api/api_server.h"
namespace esphome {
namespace homeassistant {
@@ -9,14 +8,15 @@ namespace homeassistant {
static const char *const TAG = "homeassistant.text_sensor";
void HomeassistantTextSensor::setup() {
api::global_api_server->subscribe_home_assistant_state(this->entity_id_, this->attribute_, [this](StringRef state) {
if (this->attribute_ != nullptr) {
ESP_LOGD(TAG, "'%s::%s': Got attribute state '%s'", this->entity_id_, this->attribute_, state.c_str());
} else {
ESP_LOGD(TAG, "'%s': Got state '%s'", this->entity_id_, state.c_str());
}
this->publish_state(state.str());
});
api::global_api_server->subscribe_home_assistant_state(
this->entity_id_, this->attribute_, [this](const std::string &state) {
if (this->attribute_ != nullptr) {
ESP_LOGD(TAG, "'%s::%s': Got attribute state '%s'", this->entity_id_, this->attribute_, state.c_str());
} else {
ESP_LOGD(TAG, "'%s': Got state '%s'", this->entity_id_, state.c_str());
}
this->publish_state(state);
});
}
void HomeassistantTextSensor::dump_config() {
LOG_TEXT_SENSOR("", "Homeassistant Text Sensor", this);

View File

@@ -119,7 +119,7 @@ void IDFI2CBus::dump_config() {
if (s.second) {
ESP_LOGCONFIG(TAG, "Found device at address 0x%02X", s.first);
} else {
ESP_LOGCONFIG(TAG, "Unknown error at address 0x%02X", s.first);
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
}
}
}

View File

@@ -1,11 +1,6 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components.esp32 import (
add_idf_sdkconfig_option,
enable_ringbuf_in_iram,
get_esp32_variant,
)
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
@@ -15,6 +10,8 @@ from esphome.components.esp32.const import (
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
add_idf_sdkconfig_option,
get_esp32_variant,
)
import esphome.config_validation as cv
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE
@@ -281,9 +278,6 @@ async def to_code(config):
# Helps avoid callbacks being skipped due to processor load
add_idf_sdkconfig_option("CONFIG_I2S_ISR_IRAM_SAFE", True)
# Keep ring buffer functions in IRAM for audio performance
enable_ringbuf_in_iram()
cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN]))
if CONF_I2S_BCLK_PIN in config:
cg.add(var.set_bclk_pin(config[CONF_I2S_BCLK_PIN]))

View File

@@ -374,6 +374,23 @@ def is_svg_file(file):
return "<svg" in str(f.read(1024))
def validate_cairosvg_installed():
try:
import cairosvg
except ImportError as err:
raise cv.Invalid(
"Please install the cairosvg python package to use this feature. "
"(pip install cairosvg)"
) from err
major, minor, _ = cairosvg.__version__.split(".")
if major < "2" or major == "2" and minor < "2":
raise cv.Invalid(
"Please update your cairosvg installation to at least 2.2.0. "
"(pip install -U cairosvg)"
)
def validate_file_shorthand(value):
value = cv.string_strict(value)
parts = value.strip().split(":")
@@ -473,7 +490,9 @@ def validate_settings(value, path=()):
)
if file := value.get(CONF_FILE):
file = Path(file)
if not is_svg_file(file):
if is_svg_file(file):
validate_cairosvg_installed()
else:
try:
Image.open(file)
except UnidentifiedImageError as exc:
@@ -650,35 +669,44 @@ async def write_image(config, all_frames=False):
raise core.EsphomeError(f"Could not load image file {path}")
resize = config.get(CONF_RESIZE)
try:
if is_svg_file(path):
import resvg_py
if is_svg_file(path):
# Local import so use of non-SVG files needn't require cairosvg installed
from pyexpat import ExpatError
from xml.etree.ElementTree import ParseError
if resize:
width, height = resize
# resvg-py allows rendering by width/height directly
image_data = resvg_py.svg_to_bytes(
svg_path=str(path), width=int(width), height=int(height)
from cairosvg import svg2png
from cairosvg.helpers import PointError
if not resize:
resize = (None, None)
try:
with open(path, "rb") as file:
image = svg2png(
file_obj=file,
output_width=resize[0],
output_height=resize[1],
)
else:
# Default size
image_data = resvg_py.svg_to_bytes(svg_path=str(path))
# Convert bytes to Pillow Image
image = Image.open(io.BytesIO(image_data))
image = Image.open(io.BytesIO(image))
width, height = image.size
else:
image = Image.open(path)
width, height = image.size
if resize:
# Preserve aspect ratio
new_width_max = min(width, resize[0])
new_height_max = min(height, resize[1])
ratio = min(new_width_max / width, new_height_max / height)
width, height = int(width * ratio), int(height * ratio)
except (OSError, UnidentifiedImageError, ValueError) as exc:
raise core.EsphomeError(f"Could not read image file {path}: {exc}") from exc
except (
ValueError,
ParseError,
IndexError,
ExpatError,
AttributeError,
TypeError,
PointError,
) as e:
raise core.EsphomeError(f"Could not load SVG image {path}: {e}") from e
else:
image = Image.open(path)
width, height = image.size
if resize:
# Preserve aspect ratio
new_width_max = min(width, resize[0])
new_height_max = min(height, resize[1])
ratio = min(new_width_max / width, new_height_max / height)
width, height = int(width * ratio), int(height * ratio)
if not resize and (width > 500 or height > 500):
_LOGGER.warning(

View File

@@ -1,6 +1,5 @@
#include "improv_base.h"
#include <cstring>
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
@@ -14,54 +13,37 @@ static constexpr size_t DEVICE_NAME_PLACEHOLDER_LEN = sizeof(DEVICE_NAME_PLACEHO
static constexpr const char IP_ADDRESS_PLACEHOLDER[] = "{{ip_address}}";
static constexpr size_t IP_ADDRESS_PLACEHOLDER_LEN = sizeof(IP_ADDRESS_PLACEHOLDER) - 1;
/// Copy src to dest, returning pointer past last written char. Stops at end or if src is null.
static char *copy_to_buffer(char *dest, const char *end, const char *src) {
if (src == nullptr) {
return dest;
static void replace_all_in_place(std::string &str, const char *placeholder, size_t placeholder_len,
const std::string &replacement) {
size_t pos = 0;
const size_t replacement_len = replacement.length();
while ((pos = str.find(placeholder, pos)) != std::string::npos) {
str.replace(pos, placeholder_len, replacement);
pos += replacement_len;
}
while (*src != '\0' && dest < end) {
*dest++ = *src++;
}
return dest;
}
size_t ImprovBase::get_formatted_next_url_(char *buffer, size_t buffer_size) {
if (this->next_url_ == nullptr || buffer_size == 0) {
if (buffer_size > 0) {
buffer[0] = '\0';
}
return 0;
std::string ImprovBase::get_formatted_next_url_() {
if (this->next_url_.empty()) {
return "";
}
// Get IP address once for replacement
const char *ip_str = nullptr;
char ip_buffer[network::IP_ADDRESS_BUFFER_SIZE];
std::string formatted_url = this->next_url_;
// Replace all occurrences of {{device_name}}
replace_all_in_place(formatted_url, DEVICE_NAME_PLACEHOLDER, DEVICE_NAME_PLACEHOLDER_LEN, App.get_name());
// Replace all occurrences of {{ip_address}}
for (auto &ip : network::get_ip_addresses()) {
if (ip.is_ip4()) {
ip.str_to(ip_buffer);
ip_str = ip_buffer;
replace_all_in_place(formatted_url, IP_ADDRESS_PLACEHOLDER, IP_ADDRESS_PLACEHOLDER_LEN, ip.str());
break;
}
}
const char *device_name = App.get_name().c_str();
char *out = buffer;
const char *end = buffer + buffer_size - 1;
// Note: {{esphome_version}} is replaced at code generation time in Python
for (const char *p = this->next_url_; *p != '\0' && out < end;) {
if (strncmp(p, DEVICE_NAME_PLACEHOLDER, DEVICE_NAME_PLACEHOLDER_LEN) == 0) {
out = copy_to_buffer(out, end, device_name);
p += DEVICE_NAME_PLACEHOLDER_LEN;
} else if (ip_str != nullptr && strncmp(p, IP_ADDRESS_PLACEHOLDER, IP_ADDRESS_PLACEHOLDER_LEN) == 0) {
out = copy_to_buffer(out, end, ip_str);
p += IP_ADDRESS_PLACEHOLDER_LEN;
} else {
*out++ = *p++;
}
}
*out = '\0';
return out - buffer;
return formatted_url;
}
#endif

View File

@@ -1,6 +1,6 @@
#pragma once
#include <cstddef>
#include <string>
#include "esphome/core/defines.h"
namespace esphome {
@@ -9,14 +9,13 @@ namespace improv_base {
class ImprovBase {
public:
#if defined(USE_ESP32_IMPROV_NEXT_URL) || defined(USE_IMPROV_SERIAL_NEXT_URL)
void set_next_url(const char *next_url) { this->next_url_ = next_url; }
void set_next_url(const std::string &next_url) { this->next_url_ = next_url; }
#endif
protected:
#if defined(USE_ESP32_IMPROV_NEXT_URL) || defined(USE_IMPROV_SERIAL_NEXT_URL)
/// Format next_url_ into buffer, replacing placeholders. Returns length written.
size_t get_formatted_next_url_(char *buffer, size_t buffer_size);
const char *next_url_{nullptr};
std::string get_formatted_next_url_();
std::string next_url_;
#endif
};

View File

@@ -182,12 +182,8 @@ void ImprovSerialComponent::write_data_(const uint8_t *data, const size_t size)
std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
std::vector<std::string> urls;
#ifdef USE_IMPROV_SERIAL_NEXT_URL
{
char url_buffer[384];
size_t len = this->get_formatted_next_url_(url_buffer, sizeof(url_buffer));
if (len > 0) {
urls.emplace_back(url_buffer, len);
}
if (!this->next_url_.empty()) {
urls.push_back(this->get_formatted_next_url_());
}
#endif
#ifdef USE_WEBSERVER

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