mirror of
https://github.com/esphome/esphome.git
synced 2026-01-20 18:09:10 -07:00
Compare commits
79 Commits
pipsolar_t
...
noise_stac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
935b4c3788 | ||
|
|
cfa9c3ed49 | ||
|
|
d8a38815fd | ||
|
|
bc776ffd59 | ||
|
|
fd0ea32100 | ||
|
|
6edecd3d45 | ||
|
|
055c00f1ac | ||
|
|
7dc40881e2 | ||
|
|
b04373687e | ||
|
|
b89c127f62 | ||
|
|
47dc5d0a1f | ||
|
|
21886dd3ac | ||
|
|
85a5a26519 | ||
|
|
79ccacd6d6 | ||
|
|
e2319ba651 | ||
|
|
ed4ebffa74 | ||
|
|
c213de4861 | ||
|
|
6cf320fd60 | ||
|
|
aeea340bc6 | ||
|
|
d0e50ed030 | ||
|
|
280d460025 | ||
|
|
ea70faf642 | ||
|
|
5d7b38b261 | ||
|
|
e88093ca60 | ||
|
|
b48d4ab785 | ||
|
|
8ade9dfc10 | ||
|
|
4e0e7796de | ||
|
|
62b6c9bf7c | ||
|
|
b5fe271d6b | ||
|
|
5d787e2512 | ||
|
|
8998ef0bc3 | ||
|
|
8ec31dd769 | ||
|
|
0193464f92 | ||
|
|
1996bc425f | ||
|
|
a0d3d54d69 | ||
|
|
ee264d0fd4 | ||
|
|
892e9b006f | ||
|
|
f8bd4ef57d | ||
|
|
bfcc0e26a3 | ||
|
|
86a1b4cf69 | ||
|
|
d8a28f6fba | ||
|
|
e80a940222 | ||
|
|
e99dbe05f7 | ||
|
|
f453a8d9a1 | ||
|
|
126190d26a | ||
|
|
e40201a98d | ||
|
|
8142f5db44 | ||
|
|
98ccab87a7 | ||
|
|
b9e72a8774 | ||
|
|
d9fc625c6a | ||
|
|
dfbf79d6d6 | ||
|
|
ea0fac96cb | ||
|
|
3182222d60 | ||
|
|
d8849b16f2 | ||
|
|
635983f163 | ||
|
|
6cbe672004 | ||
|
|
226867b05c | ||
|
|
67871a1683 | ||
|
|
f60c03e350 | ||
|
|
eb66429144 | ||
|
|
0f3bac5dd6 | ||
|
|
5b92d0b89e | ||
|
|
052b05df56 | ||
|
|
7b0db659d1 | ||
|
|
2f7270cf8f | ||
|
|
b44727aee6 | ||
|
|
1a55254258 | ||
|
|
baf2b0e3c9 | ||
|
|
680e92a226 | ||
|
|
db0b32bfc9 | ||
|
|
21794e28e5 | ||
|
|
728236270c | ||
|
|
01cdc4ed58 | ||
|
|
d6a0c8ffbb | ||
|
|
4cc0f874f7 | ||
|
|
ed58b9372f | ||
|
|
ee2a81923b | ||
|
|
0a1e7ee50b | ||
|
|
4d4283bcfa |
@@ -1,5 +1,6 @@
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
import argparse
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime
|
||||
import functools
|
||||
import getpass
|
||||
@@ -800,13 +801,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
|
||||
@@ -943,11 +937,21 @@ def command_dashboard(args: ArgsProtocol) -> int | None:
|
||||
return dashboard.start_dashboard(args)
|
||||
|
||||
|
||||
def command_update_all(args: ArgsProtocol) -> int | None:
|
||||
def run_multiple_configs(
|
||||
files: list, command_builder: Callable[[str], list[str]]
|
||||
) -> int:
|
||||
"""Run a command for each configuration file in a subprocess.
|
||||
|
||||
Args:
|
||||
files: List of configuration files to process.
|
||||
command_builder: Callable that takes a file path and returns a command list.
|
||||
|
||||
Returns:
|
||||
Number of failed files.
|
||||
"""
|
||||
import click
|
||||
|
||||
success = {}
|
||||
files = list_yaml_files(args.configuration)
|
||||
twidth = 60
|
||||
|
||||
def print_bar(middle_text):
|
||||
@@ -957,17 +961,19 @@ def command_update_all(args: ArgsProtocol) -> int | None:
|
||||
safe_print(f"{half_line}{middle_text}{half_line}")
|
||||
|
||||
for f in files:
|
||||
safe_print(f"Updating {color(AnsiFore.CYAN, str(f))}")
|
||||
f_path = Path(f) if not isinstance(f, Path) else f
|
||||
|
||||
if any(f_path.name == x for x in SECRETS_FILES):
|
||||
_LOGGER.warning("Skipping secrets file %s", f_path)
|
||||
continue
|
||||
|
||||
safe_print(f"Processing {color(AnsiFore.CYAN, str(f))}")
|
||||
safe_print("-" * twidth)
|
||||
safe_print()
|
||||
if CORE.dashboard:
|
||||
rc = run_external_process(
|
||||
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
|
||||
)
|
||||
else:
|
||||
rc = run_external_process(
|
||||
"esphome", "run", f, "--no-logs", "--device", "OTA"
|
||||
)
|
||||
|
||||
cmd = command_builder(f)
|
||||
rc = run_external_process(*cmd)
|
||||
|
||||
if rc == 0:
|
||||
print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {str(f)}")
|
||||
success[f] = True
|
||||
@@ -982,6 +988,8 @@ def command_update_all(args: ArgsProtocol) -> int | None:
|
||||
print_bar(f"[{color(AnsiFore.BOLD_WHITE, 'SUMMARY')}]")
|
||||
failed = 0
|
||||
for f in files:
|
||||
if f not in success:
|
||||
continue # Skipped file
|
||||
if success[f]:
|
||||
safe_print(f" - {str(f)}: {color(AnsiFore.GREEN, 'SUCCESS')}")
|
||||
else:
|
||||
@@ -990,6 +998,17 @@ def command_update_all(args: ArgsProtocol) -> int | None:
|
||||
return failed
|
||||
|
||||
|
||||
def command_update_all(args: ArgsProtocol) -> int | None:
|
||||
files = list_yaml_files(args.configuration)
|
||||
|
||||
def build_command(f):
|
||||
if CORE.dashboard:
|
||||
return ["esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"]
|
||||
return ["esphome", "run", f, "--no-logs", "--device", "OTA"]
|
||||
|
||||
return run_multiple_configs(files, build_command)
|
||||
|
||||
|
||||
def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
import json
|
||||
|
||||
@@ -1291,17 +1310,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",
|
||||
@@ -1551,38 +1559,48 @@ def run_esphome(argv):
|
||||
|
||||
_LOGGER.info("ESPHome %s", const.__version__)
|
||||
|
||||
for conf_path in args.configuration:
|
||||
conf_path = Path(conf_path)
|
||||
if any(conf_path.name == x for x in SECRETS_FILES):
|
||||
_LOGGER.warning("Skipping secrets file %s", conf_path)
|
||||
continue
|
||||
# Multiple configurations: use subprocesses to avoid state leakage
|
||||
# between compilations (e.g., LVGL touchscreen state in module globals)
|
||||
if len(args.configuration) > 1:
|
||||
# Build command by reusing argv, replacing all configs with single file
|
||||
# argv[0] is the program path, skip it since we prefix with "esphome"
|
||||
def build_command(f):
|
||||
return (
|
||||
["esphome"]
|
||||
+ [arg for arg in argv[1:] if arg not in args.configuration]
|
||||
+ [str(f)]
|
||||
)
|
||||
|
||||
CORE.config_path = conf_path
|
||||
CORE.dashboard = args.dashboard
|
||||
return run_multiple_configs(args.configuration, build_command)
|
||||
|
||||
# For logs command, skip updating external components
|
||||
skip_external = args.command == "logs"
|
||||
config = read_config(
|
||||
dict(args.substitution) if args.substitution else {},
|
||||
skip_external_update=skip_external,
|
||||
)
|
||||
if config is None:
|
||||
return 2
|
||||
CORE.config = config
|
||||
# Single configuration
|
||||
conf_path = Path(args.configuration[0])
|
||||
if any(conf_path.name == x for x in SECRETS_FILES):
|
||||
_LOGGER.warning("Skipping secrets file %s", conf_path)
|
||||
return 0
|
||||
|
||||
if args.command not in POST_CONFIG_ACTIONS:
|
||||
safe_print(f"Unknown command {args.command}")
|
||||
CORE.config_path = conf_path
|
||||
CORE.dashboard = args.dashboard
|
||||
|
||||
try:
|
||||
rc = POST_CONFIG_ACTIONS[args.command](args, config)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e, exc_info=args.verbose)
|
||||
return 1
|
||||
if rc != 0:
|
||||
return rc
|
||||
# For logs command, skip updating external components
|
||||
skip_external = args.command == "logs"
|
||||
config = read_config(
|
||||
dict(args.substitution) if args.substitution else {},
|
||||
skip_external_update=skip_external,
|
||||
)
|
||||
if config is None:
|
||||
return 2
|
||||
CORE.config = config
|
||||
|
||||
CORE.reset()
|
||||
return 0
|
||||
if args.command not in POST_CONFIG_ACTIONS:
|
||||
safe_print(f"Unknown command {args.command}")
|
||||
return 1
|
||||
|
||||
try:
|
||||
return POST_CONFIG_ACTIONS[args.command](args, config)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e, exc_info=args.verbose)
|
||||
return 1
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -4,7 +4,6 @@ from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
import json
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -439,28 +438,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
|
||||
|
||||
@@ -7,12 +7,6 @@ namespace am43 {
|
||||
|
||||
const uint8_t START_PACKET[5] = {0x00, 0xff, 0x00, 0x00, 0x9a};
|
||||
|
||||
std::string pkt_to_hex(const uint8_t *data, uint16_t len) {
|
||||
char buf[64]; // format_hex_size(31) = 63, fits 31 bytes of hex data
|
||||
format_hex_to(buf, sizeof(buf), data, len);
|
||||
return buf;
|
||||
}
|
||||
|
||||
Am43Packet *Am43Encoder::get_battery_level_request() {
|
||||
uint8_t data = 0x1;
|
||||
return this->encode_(0xA2, &data, 1);
|
||||
@@ -70,7 +64,9 @@ Am43Packet *Am43Encoder::encode_(uint8_t command, uint8_t *data, uint8_t length)
|
||||
memcpy(&this->packet_.data[7], data, length);
|
||||
this->packet_.length = length + 7;
|
||||
this->checksum_();
|
||||
ESP_LOGV("am43", "ENC(%d): 0x%s", packet_.length, pkt_to_hex(packet_.data, packet_.length).c_str());
|
||||
char hex_buf[format_hex_size(sizeof(this->packet_.data))];
|
||||
ESP_LOGV("am43", "ENC(%d): 0x%s", this->packet_.length,
|
||||
format_hex_to(hex_buf, this->packet_.data, this->packet_.length));
|
||||
return &this->packet_;
|
||||
}
|
||||
|
||||
@@ -85,7 +81,8 @@ void Am43Decoder::decode(const uint8_t *data, uint16_t length) {
|
||||
this->has_set_state_response_ = false;
|
||||
this->has_position_ = false;
|
||||
this->has_pin_response_ = false;
|
||||
ESP_LOGV("am43", "DEC(%d): 0x%s", length, pkt_to_hex(data, length).c_str());
|
||||
char hex_buf[format_hex_size(24)]; // Max expected packet size
|
||||
ESP_LOGV("am43", "DEC(%d): 0x%s", length, format_hex_to(hex_buf, data, length));
|
||||
|
||||
if (length < 2 || data[0] != 0x9a)
|
||||
return;
|
||||
|
||||
@@ -18,31 +18,31 @@ AnovaPacket *AnovaCodec::clean_packet_() {
|
||||
|
||||
AnovaPacket *AnovaCodec::get_read_device_status_request() {
|
||||
this->current_query_ = READ_DEVICE_STATUS;
|
||||
strncpy((char *) this->packet_.data, CMD_READ_DEVICE_STATUS, sizeof(this->packet_.data));
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_DEVICE_STATUS);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_read_target_temp_request() {
|
||||
this->current_query_ = READ_TARGET_TEMPERATURE;
|
||||
strncpy((char *) this->packet_.data, CMD_READ_TARGET_TEMP, sizeof(this->packet_.data));
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_TARGET_TEMP);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_read_current_temp_request() {
|
||||
this->current_query_ = READ_CURRENT_TEMPERATURE;
|
||||
strncpy((char *) this->packet_.data, CMD_READ_CURRENT_TEMP, sizeof(this->packet_.data));
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_CURRENT_TEMP);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_read_unit_request() {
|
||||
this->current_query_ = READ_UNIT;
|
||||
strncpy((char *) this->packet_.data, CMD_READ_UNIT, sizeof(this->packet_.data));
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_UNIT);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_read_data_request() {
|
||||
this->current_query_ = READ_DATA;
|
||||
strncpy((char *) this->packet_.data, CMD_READ_DATA, sizeof(this->packet_.data));
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_DATA);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
@@ -62,13 +62,13 @@ AnovaPacket *AnovaCodec::get_set_unit_request(char unit) {
|
||||
|
||||
AnovaPacket *AnovaCodec::get_start_request() {
|
||||
this->current_query_ = START;
|
||||
strncpy((char *) this->packet_.data, CMD_START, sizeof(this->packet_.data));
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_START);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_stop_request() {
|
||||
this->current_query_ = STOP;
|
||||
strncpy((char *) this->packet_.data, CMD_STOP, sizeof(this->packet_.data));
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_STOP);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
|
||||
@@ -1712,17 +1712,16 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
|
||||
}
|
||||
|
||||
// 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
|
||||
// HA state max length is 255 characters, but attributes can be much longer
|
||||
// Use stack buffer for common case (states), heap fallback for large attributes
|
||||
size_t state_len = msg.state.size();
|
||||
SmallBufferWithHeapFallback<MAX_STATE_LEN + 1> state_buf_alloc(state_len + 1);
|
||||
char *state_buf = reinterpret_cast<char *>(state_buf_alloc.get());
|
||||
if (state_len > 0) {
|
||||
memcpy(state_buf, msg.state.c_str(), state_len);
|
||||
}
|
||||
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));
|
||||
state_buf[state_len] = '\0';
|
||||
it.callback(StringRef(state_buf, state_len));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#ifdef USE_API_NOISE
|
||||
#include "api_connection.h" // For ClientInfo struct
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -256,28 +257,30 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
}
|
||||
if (state_ == State::SERVER_HELLO) {
|
||||
// send server hello
|
||||
constexpr size_t mac_len = 13; // 12 hex chars + null terminator
|
||||
const std::string &name = App.get_name();
|
||||
char mac[mac_len];
|
||||
char mac[MAC_ADDRESS_BUFFER_SIZE];
|
||||
get_mac_address_into_buffer(mac);
|
||||
|
||||
// Calculate positions and sizes
|
||||
size_t name_len = name.size() + 1; // including null terminator
|
||||
size_t name_offset = 1;
|
||||
size_t mac_offset = name_offset + name_len;
|
||||
size_t total_size = 1 + name_len + mac_len;
|
||||
size_t total_size = 1 + name_len + MAC_ADDRESS_BUFFER_SIZE;
|
||||
|
||||
auto msg = std::make_unique<uint8_t[]>(total_size);
|
||||
// 1 (proto) + name (max ESPHOME_DEVICE_NAME_MAX_LEN) + 1 (name null)
|
||||
// + mac (MAC_ADDRESS_BUFFER_SIZE - 1) + 1 (mac null)
|
||||
constexpr size_t max_msg_size = 1 + ESPHOME_DEVICE_NAME_MAX_LEN + 1 + MAC_ADDRESS_BUFFER_SIZE;
|
||||
uint8_t msg[max_msg_size];
|
||||
|
||||
// chosen proto
|
||||
msg[0] = 0x01;
|
||||
|
||||
// node name, terminated by null byte
|
||||
std::memcpy(msg.get() + name_offset, name.c_str(), name_len);
|
||||
std::memcpy(msg + name_offset, name.c_str(), name_len);
|
||||
// node mac, terminated by null byte
|
||||
std::memcpy(msg.get() + mac_offset, mac, mac_len);
|
||||
std::memcpy(msg + mac_offset, mac, MAC_ADDRESS_BUFFER_SIZE);
|
||||
|
||||
aerr = write_frame_(msg.get(), total_size);
|
||||
aerr = write_frame_(msg, total_size);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
@@ -353,35 +356,32 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
return APIError::OK;
|
||||
}
|
||||
void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reason) {
|
||||
// Max reject message: "Bad handshake packet len" (24) + 1 (failure byte) = 25 bytes
|
||||
uint8_t data[32];
|
||||
data[0] = 0x01; // failure
|
||||
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
// On ESP8266 with flash strings, we need to use PROGMEM-aware functions
|
||||
size_t reason_len = strlen_P(reinterpret_cast<PGM_P>(reason));
|
||||
size_t data_size = reason_len + 1;
|
||||
auto data = std::make_unique<uint8_t[]>(data_size);
|
||||
data[0] = 0x01; // failure
|
||||
|
||||
// Copy error message from PROGMEM
|
||||
if (reason_len > 0) {
|
||||
memcpy_P(data.get() + 1, reinterpret_cast<PGM_P>(reason), reason_len);
|
||||
memcpy_P(data + 1, reinterpret_cast<PGM_P>(reason), reason_len);
|
||||
}
|
||||
#else
|
||||
// Normal memory access
|
||||
const char *reason_str = LOG_STR_ARG(reason);
|
||||
size_t reason_len = strlen(reason_str);
|
||||
size_t data_size = reason_len + 1;
|
||||
auto data = std::make_unique<uint8_t[]>(data_size);
|
||||
data[0] = 0x01; // failure
|
||||
|
||||
// Copy error message in bulk
|
||||
if (reason_len > 0) {
|
||||
std::memcpy(data.get() + 1, reason_str, reason_len);
|
||||
// NOLINTNEXTLINE(bugprone-not-null-terminated-result) - binary protocol, not a C string
|
||||
std::memcpy(data + 1, reason_str, reason_len);
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t data_size = reason_len + 1;
|
||||
|
||||
// temporarily remove failed state
|
||||
auto orig_state = state_;
|
||||
state_ = State::EXPLICIT_REJECT;
|
||||
write_frame_(data.get(), data_size);
|
||||
write_frame_(data, data_size);
|
||||
state_ = orig_state;
|
||||
}
|
||||
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -76,7 +76,6 @@ class CS5460AComponent : public Component,
|
||||
void restart() { restart_(); }
|
||||
|
||||
void setup() override;
|
||||
void loop() override {}
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -25,29 +25,13 @@ dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr")
|
||||
Dsmr = dsmr_ns.class_("Dsmr", cg.Component, uart.UARTDevice)
|
||||
|
||||
|
||||
def _validate_key(value):
|
||||
value = cv.string_strict(value)
|
||||
parts = [value[i : i + 2] for i in range(0, len(value), 2)]
|
||||
if len(parts) != 16:
|
||||
raise cv.Invalid("Decryption key must consist of 16 hexadecimal numbers")
|
||||
parts_int = []
|
||||
if any(len(part) != 2 for part in parts):
|
||||
raise cv.Invalid("Decryption key must be format XX")
|
||||
for part in parts:
|
||||
try:
|
||||
parts_int.append(int(part, 16))
|
||||
except ValueError:
|
||||
# pylint: disable=raise-missing-from
|
||||
raise cv.Invalid("Decryption key must be hex values from 00 to FF")
|
||||
|
||||
return "".join(f"{part:02X}" for part in parts_int)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Dsmr),
|
||||
cv.Optional(CONF_DECRYPTION_KEY): _validate_key,
|
||||
cv.Optional(CONF_DECRYPTION_KEY): lambda value: cv.bind_key(
|
||||
value, name="Decryption key"
|
||||
),
|
||||
cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean,
|
||||
cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
|
||||
cv.Optional(CONF_WATER_MBUS_ID, default=2): cv.int_,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "dsmr.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <AES.h>
|
||||
@@ -294,8 +295,8 @@ void Dsmr::dump_config() {
|
||||
DSMR_TEXT_SENSOR_LIST(DSMR_LOG_TEXT_SENSOR, )
|
||||
}
|
||||
|
||||
void Dsmr::set_decryption_key(const std::string &decryption_key) {
|
||||
if (decryption_key.empty()) {
|
||||
void Dsmr::set_decryption_key(const char *decryption_key) {
|
||||
if (decryption_key == nullptr || decryption_key[0] == '\0') {
|
||||
ESP_LOGI(TAG, "Disabling decryption");
|
||||
this->decryption_key_.clear();
|
||||
if (this->crypt_telegram_ != nullptr) {
|
||||
@@ -305,21 +306,15 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (decryption_key.length() != 32) {
|
||||
ESP_LOGE(TAG, "Error, decryption key must be 32 character long");
|
||||
if (!parse_hex(decryption_key, this->decryption_key_, 16)) {
|
||||
ESP_LOGE(TAG, "Error, decryption key must be 32 hex characters");
|
||||
this->decryption_key_.clear();
|
||||
return;
|
||||
}
|
||||
this->decryption_key_.clear();
|
||||
|
||||
ESP_LOGI(TAG, "Decryption key is set");
|
||||
// Verbose level prints decryption key
|
||||
ESP_LOGV(TAG, "Using decryption key: %s", decryption_key.c_str());
|
||||
|
||||
char temp[3] = {0};
|
||||
for (int i = 0; i < 16; i++) {
|
||||
strncpy(temp, &(decryption_key.c_str()[i * 2]), 2);
|
||||
this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
|
||||
}
|
||||
ESP_LOGV(TAG, "Using decryption key: %s", decryption_key);
|
||||
|
||||
if (this->crypt_telegram_ == nullptr) {
|
||||
this->crypt_telegram_ = new uint8_t[this->max_telegram_len_]; // NOLINT
|
||||
|
||||
@@ -63,7 +63,7 @@ class Dsmr : public Component, public uart::UARTDevice {
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void set_decryption_key(const std::string &decryption_key);
|
||||
void set_decryption_key(const char *decryption_key);
|
||||
void set_max_telegram_length(size_t length) { this->max_telegram_len_ = length; }
|
||||
void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; }
|
||||
void set_request_interval(uint32_t interval) { this->request_interval_ = interval; }
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -660,9 +660,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.
|
||||
@@ -682,17 +679,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 *)
|
||||
@@ -1104,18 +1090,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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -19,7 +19,16 @@ static constexpr size_t KEY_BUFFER_SIZE = 12;
|
||||
|
||||
struct NVSData {
|
||||
uint32_t key;
|
||||
SmallInlineBuffer<8> data; // Most prefs fit in 8 bytes (covers fan, cover, select, etc.)
|
||||
std::unique_ptr<uint8_t[]> data;
|
||||
size_t len;
|
||||
|
||||
void set_data(const uint8_t *src, size_t size) {
|
||||
if (!this->data || this->len != size) {
|
||||
this->data = std::make_unique<uint8_t[]>(size);
|
||||
this->len = size;
|
||||
}
|
||||
memcpy(this->data.get(), src, size);
|
||||
}
|
||||
};
|
||||
|
||||
static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
@@ -32,14 +41,14 @@ class ESP32PreferenceBackend : public ESPPreferenceBackend {
|
||||
// try find in pending saves and update that
|
||||
for (auto &obj : s_pending_save) {
|
||||
if (obj.key == this->key) {
|
||||
obj.data.set(data, len);
|
||||
obj.set_data(data, len);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
NVSData save{};
|
||||
save.key = this->key;
|
||||
save.data.set(data, len);
|
||||
s_pending_save.push_back(std::move(save));
|
||||
save.set_data(data, len);
|
||||
s_pending_save.emplace_back(std::move(save));
|
||||
ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len);
|
||||
return true;
|
||||
}
|
||||
@@ -47,11 +56,11 @@ class ESP32PreferenceBackend : public ESPPreferenceBackend {
|
||||
// try find in pending saves and load from that
|
||||
for (auto &obj : s_pending_save) {
|
||||
if (obj.key == this->key) {
|
||||
if (obj.data.size() != len) {
|
||||
if (obj.len != len) {
|
||||
// size mismatch
|
||||
return false;
|
||||
}
|
||||
memcpy(data, obj.data.data(), len);
|
||||
memcpy(data, obj.data.get(), len);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -127,10 +136,10 @@ class ESP32Preferences : public ESPPreferences {
|
||||
snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key);
|
||||
ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str);
|
||||
if (this->is_changed_(this->nvs_handle, save, key_str)) {
|
||||
esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.data(), save.data.size());
|
||||
ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size());
|
||||
esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.get(), save.len);
|
||||
ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.len);
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", key_str, save.data.size(), esp_err_to_name(err));
|
||||
ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", key_str, save.len, esp_err_to_name(err));
|
||||
failed++;
|
||||
last_err = err;
|
||||
last_key = save.key;
|
||||
@@ -138,7 +147,7 @@ class ESP32Preferences : public ESPPreferences {
|
||||
}
|
||||
written++;
|
||||
} else {
|
||||
ESP_LOGV(TAG, "NVS data not changed skipping %" PRIu32 " len=%zu", save.key, save.data.size());
|
||||
ESP_LOGV(TAG, "NVS data not changed skipping %" PRIu32 " len=%zu", save.key, save.len);
|
||||
cached++;
|
||||
}
|
||||
s_pending_save.erase(s_pending_save.begin() + i);
|
||||
@@ -169,7 +178,7 @@ class ESP32Preferences : public ESPPreferences {
|
||||
return true;
|
||||
}
|
||||
// Check size first before allocating memory
|
||||
if (actual_len != to_save.data.size()) {
|
||||
if (actual_len != to_save.len) {
|
||||
return true;
|
||||
}
|
||||
auto stored_data = std::make_unique<uint8_t[]>(actual_len);
|
||||
@@ -178,7 +187,7 @@ class ESP32Preferences : public ESPPreferences {
|
||||
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
|
||||
return true;
|
||||
}
|
||||
return memcmp(to_save.data.data(), stored_data.get(), to_save.data.size()) != 0;
|
||||
return memcmp(to_save.data.get(), stored_data.get(), to_save.len) != 0;
|
||||
}
|
||||
|
||||
bool reset() override {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -46,6 +46,8 @@ class ESPBTUUID {
|
||||
|
||||
esp_bt_uuid_t get_uuid() const;
|
||||
|
||||
// Remove before 2026.8.0
|
||||
ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0")
|
||||
std::string to_string() const;
|
||||
const char *to_str(std::span<char, UUID_STR_LEN> output) const;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "esp32_ble_beacon.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "preferences.h"
|
||||
#include <Arduino.h>
|
||||
#include <Esp.h>
|
||||
#include <core_esp8266_features.h>
|
||||
|
||||
extern "C" {
|
||||
#include <user_interface.h>
|
||||
}
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -16,23 +20,19 @@ void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); }
|
||||
uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
||||
void arch_restart() {
|
||||
ESP.restart(); // NOLINT(readability-static-accessed-through-instance)
|
||||
system_restart();
|
||||
// restart() doesn't always end execution
|
||||
while (true) { // NOLINT(clang-diagnostic-unreachable-code)
|
||||
yield();
|
||||
}
|
||||
}
|
||||
void arch_init() {}
|
||||
void IRAM_ATTR HOT arch_feed_wdt() {
|
||||
ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
void IRAM_ATTR HOT arch_feed_wdt() { system_soft_wdt_feed(); }
|
||||
|
||||
uint8_t progmem_read_byte(const uint8_t *addr) {
|
||||
return pgm_read_byte(addr); // NOLINT
|
||||
}
|
||||
uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() {
|
||||
return ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { return esp_get_cycle_count(); }
|
||||
uint32_t arch_get_cpu_freq_hz() { return F_CPU; }
|
||||
|
||||
void force_link_symbols() {
|
||||
|
||||
@@ -802,8 +802,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`.
|
||||
@@ -820,10 +820,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
|
||||
|
||||
@@ -404,8 +404,7 @@ void EzoPMP::send_next_command_() {
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS: // Run an arbitrary command
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), this->arbitrary_command_,
|
||||
this->next_command_duration_);
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "%s", this->arbitrary_command_);
|
||||
ESP_LOGI(TAG, "Sending arbitrary command: %s", (char *) command_buffer);
|
||||
break;
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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_{};
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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_{};
|
||||
|
||||
@@ -42,8 +42,8 @@ ErrorCode I2CDevice::read_register16(uint16_t a_register, uint8_t *data, size_t
|
||||
}
|
||||
|
||||
ErrorCode I2CDevice::write_register(uint8_t a_register, const uint8_t *data, size_t len) const {
|
||||
SmallBufferWithHeapFallback<17> buffer_alloc; // Most I2C writes are <= 16 bytes
|
||||
uint8_t *buffer = buffer_alloc.get(len + 1);
|
||||
SmallBufferWithHeapFallback<17> buffer_alloc(len + 1); // Most I2C writes are <= 16 bytes
|
||||
uint8_t *buffer = buffer_alloc.get();
|
||||
|
||||
buffer[0] = a_register;
|
||||
std::copy(data, data + len, buffer + 1);
|
||||
@@ -51,8 +51,8 @@ ErrorCode I2CDevice::write_register(uint8_t a_register, const uint8_t *data, siz
|
||||
}
|
||||
|
||||
ErrorCode I2CDevice::write_register16(uint16_t a_register, const uint8_t *data, size_t len) const {
|
||||
SmallBufferWithHeapFallback<18> buffer_alloc; // Most I2C writes are <= 16 bytes + 2 for register
|
||||
uint8_t *buffer = buffer_alloc.get(len + 2);
|
||||
SmallBufferWithHeapFallback<18> buffer_alloc(len + 2); // Most I2C writes are <= 16 bytes + 2 for register
|
||||
uint8_t *buffer = buffer_alloc.get();
|
||||
|
||||
buffer[0] = a_register >> 8;
|
||||
buffer[1] = a_register;
|
||||
|
||||
@@ -11,22 +11,6 @@
|
||||
namespace esphome {
|
||||
namespace i2c {
|
||||
|
||||
/// @brief Helper class for efficient buffer allocation - uses stack for small sizes, heap for large
|
||||
template<size_t STACK_SIZE> class SmallBufferWithHeapFallback {
|
||||
public:
|
||||
uint8_t *get(size_t size) {
|
||||
if (size <= STACK_SIZE) {
|
||||
return this->stack_buffer_;
|
||||
}
|
||||
this->heap_buffer_ = std::unique_ptr<uint8_t[]>(new uint8_t[size]);
|
||||
return this->heap_buffer_.get();
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t stack_buffer_[STACK_SIZE];
|
||||
std::unique_ptr<uint8_t[]> heap_buffer_;
|
||||
};
|
||||
|
||||
/// @brief Error codes returned by I2CBus and I2CDevice methods
|
||||
enum ErrorCode {
|
||||
NO_ERROR = 0, ///< No error found during execution of method
|
||||
@@ -92,8 +76,8 @@ class I2CBus {
|
||||
total_len += read_buffers[i].len;
|
||||
}
|
||||
|
||||
SmallBufferWithHeapFallback<128> buffer_alloc; // Most I2C reads are small
|
||||
uint8_t *buffer = buffer_alloc.get(total_len);
|
||||
SmallBufferWithHeapFallback<128> buffer_alloc(total_len); // Most I2C reads are small
|
||||
uint8_t *buffer = buffer_alloc.get();
|
||||
|
||||
auto err = this->write_readv(address, nullptr, 0, buffer, total_len);
|
||||
if (err != ERROR_OK)
|
||||
@@ -116,8 +100,8 @@ class I2CBus {
|
||||
total_len += write_buffers[i].len;
|
||||
}
|
||||
|
||||
SmallBufferWithHeapFallback<128> buffer_alloc; // Most I2C writes are small
|
||||
uint8_t *buffer = buffer_alloc.get(total_len);
|
||||
SmallBufferWithHeapFallback<128> buffer_alloc(total_len); // Most I2C writes are small
|
||||
uint8_t *buffer = buffer_alloc.get();
|
||||
|
||||
size_t pos = 0;
|
||||
for (size_t i = 0; i != count; i++) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -11,7 +11,7 @@ static const char *const TAG = "kuntze";
|
||||
static const uint8_t CMD_READ_REG = 0x03;
|
||||
static const uint16_t REGISTER[] = {4136, 4160, 4680, 6000, 4688, 4728, 5832};
|
||||
|
||||
// Maximum bytes to log for Modbus responses (2 registers = 4 bytes, plus byte count = 5 bytes)
|
||||
// Maximum bytes to log for Modbus responses (2 registers = 4, plus count = 5)
|
||||
static constexpr size_t KUNTZE_MAX_LOG_BYTES = 8;
|
||||
|
||||
void Kuntze::on_modbus_data(const std::vector<uint8_t> &data) {
|
||||
|
||||
@@ -18,7 +18,16 @@ static constexpr size_t KEY_BUFFER_SIZE = 12;
|
||||
|
||||
struct NVSData {
|
||||
uint32_t key;
|
||||
SmallInlineBuffer<8> data; // Most prefs fit in 8 bytes (covers fan, cover, select, etc.)
|
||||
std::unique_ptr<uint8_t[]> data;
|
||||
size_t len;
|
||||
|
||||
void set_data(const uint8_t *src, size_t size) {
|
||||
if (!this->data || this->len != size) {
|
||||
this->data = std::make_unique<uint8_t[]>(size);
|
||||
this->len = size;
|
||||
}
|
||||
memcpy(this->data.get(), src, size);
|
||||
}
|
||||
};
|
||||
|
||||
static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
@@ -33,14 +42,14 @@ class LibreTinyPreferenceBackend : public ESPPreferenceBackend {
|
||||
// try find in pending saves and update that
|
||||
for (auto &obj : s_pending_save) {
|
||||
if (obj.key == this->key) {
|
||||
obj.data.set(data, len);
|
||||
obj.set_data(data, len);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
NVSData save{};
|
||||
save.key = this->key;
|
||||
save.data.set(data, len);
|
||||
s_pending_save.push_back(std::move(save));
|
||||
save.set_data(data, len);
|
||||
s_pending_save.emplace_back(std::move(save));
|
||||
ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len);
|
||||
return true;
|
||||
}
|
||||
@@ -49,11 +58,11 @@ class LibreTinyPreferenceBackend : public ESPPreferenceBackend {
|
||||
// try find in pending saves and load from that
|
||||
for (auto &obj : s_pending_save) {
|
||||
if (obj.key == this->key) {
|
||||
if (obj.data.size() != len) {
|
||||
if (obj.len != len) {
|
||||
// size mismatch
|
||||
return false;
|
||||
}
|
||||
memcpy(data, obj.data.data(), len);
|
||||
memcpy(data, obj.data.get(), len);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -117,11 +126,11 @@ class LibreTinyPreferences : public ESPPreferences {
|
||||
snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key);
|
||||
ESP_LOGVV(TAG, "Checking if FDB data %s has changed", key_str);
|
||||
if (this->is_changed_(&this->db, save, key_str)) {
|
||||
ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size());
|
||||
fdb_blob_make(&this->blob, save.data.data(), save.data.size());
|
||||
ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.len);
|
||||
fdb_blob_make(&this->blob, save.data.get(), save.len);
|
||||
fdb_err_t err = fdb_kv_set_blob(&this->db, key_str, &this->blob);
|
||||
if (err != FDB_NO_ERR) {
|
||||
ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%zu) failed: %d", key_str, save.data.size(), err);
|
||||
ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%zu) failed: %d", key_str, save.len, err);
|
||||
failed++;
|
||||
last_err = err;
|
||||
last_key = save.key;
|
||||
@@ -129,7 +138,7 @@ class LibreTinyPreferences : public ESPPreferences {
|
||||
}
|
||||
written++;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "FDB data not changed; skipping %" PRIu32 " len=%zu", save.key, save.data.size());
|
||||
ESP_LOGD(TAG, "FDB data not changed; skipping %" PRIu32 " len=%zu", save.key, save.len);
|
||||
cached++;
|
||||
}
|
||||
s_pending_save.erase(s_pending_save.begin() + i);
|
||||
@@ -153,7 +162,7 @@ class LibreTinyPreferences : public ESPPreferences {
|
||||
}
|
||||
|
||||
// Check size first - if different, data has changed
|
||||
if (kv.value_len != to_save.data.size()) {
|
||||
if (kv.value_len != to_save.len) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -167,7 +176,7 @@ class LibreTinyPreferences : public ESPPreferences {
|
||||
}
|
||||
|
||||
// Compare the actual data
|
||||
return memcmp(to_save.data.data(), stored_data.get(), kv.value_len) != 0;
|
||||
return memcmp(to_save.data.get(), stored_data.get(), kv.value_len) != 0;
|
||||
}
|
||||
|
||||
bool reset() override {
|
||||
|
||||
@@ -28,16 +28,14 @@ const LogString *lock_state_to_string(LockState state) {
|
||||
Lock::Lock() : state(LOCK_STATE_NONE) {}
|
||||
LockCall Lock::make_call() { return LockCall(this); }
|
||||
|
||||
void Lock::lock() {
|
||||
void Lock::set_state_(LockState state) {
|
||||
auto call = this->make_call();
|
||||
call.set_state(LOCK_STATE_LOCKED);
|
||||
this->control(call);
|
||||
}
|
||||
void Lock::unlock() {
|
||||
auto call = this->make_call();
|
||||
call.set_state(LOCK_STATE_UNLOCKED);
|
||||
call.set_state(state);
|
||||
this->control(call);
|
||||
}
|
||||
|
||||
void Lock::lock() { this->set_state_(LOCK_STATE_LOCKED); }
|
||||
void Lock::unlock() { this->set_state_(LOCK_STATE_UNLOCKED); }
|
||||
void Lock::open() {
|
||||
if (traits.get_supports_open()) {
|
||||
ESP_LOGD(TAG, "'%s' Opening.", this->get_name().c_str());
|
||||
|
||||
@@ -156,6 +156,9 @@ class Lock : public EntityBase {
|
||||
protected:
|
||||
friend LockCall;
|
||||
|
||||
/// Helper for lock/unlock convenience methods
|
||||
void set_state_(LockState state);
|
||||
|
||||
/** Perform the open latch action with hardware. This method is optional to implement
|
||||
* when creating a new lock.
|
||||
*
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
#ifdef USE_ESP8266
|
||||
#include "logger.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
static const char *const TAG = "logger";
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
case UART_SELECTION_UART0_SWAP:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
if (this->uart_ == UART_SELECTION_UART0_SWAP) {
|
||||
Serial.swap();
|
||||
}
|
||||
Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
uart_set_debug(UART_NO);
|
||||
}
|
||||
|
||||
global_logger = this;
|
||||
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
return LOG_STR("UART0");
|
||||
case UART_SELECTION_UART1:
|
||||
return LOG_STR("UART1");
|
||||
case UART_SELECTION_UART0_SWAP:
|
||||
default:
|
||||
return LOG_STR("UART0_SWAP");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::logger
|
||||
#endif
|
||||
@@ -1,22 +0,0 @@
|
||||
#if defined(USE_HOST)
|
||||
#include "logger.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) {
|
||||
time_t rawtime;
|
||||
struct tm *timeinfo;
|
||||
char buffer[80];
|
||||
|
||||
time(&rawtime);
|
||||
timeinfo = localtime(&rawtime);
|
||||
strftime(buffer, sizeof buffer, "[%H:%M:%S]", timeinfo);
|
||||
fputs(buffer, stdout);
|
||||
puts(msg);
|
||||
}
|
||||
|
||||
void Logger::pre_setup() { global_logger = this; }
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
#endif
|
||||
@@ -1,70 +0,0 @@
|
||||
#ifdef USE_LIBRETINY
|
||||
#include "logger.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
static const char *const TAG = "logger";
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
switch (this->uart_) {
|
||||
#if LT_HW_UART0
|
||||
case UART_SELECTION_UART0:
|
||||
this->hw_serial_ = &Serial0;
|
||||
Serial0.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#if LT_HW_UART1
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#if LT_HW_UART2
|
||||
case UART_SELECTION_UART2:
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
if (this->uart_ != UART_SELECTION_DEFAULT) {
|
||||
ESP_LOGW(TAG, " The chosen logger UART port is not available on this board."
|
||||
"The default port was used instead.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// change lt_log() port to match default Serial
|
||||
if (this->uart_ == UART_SELECTION_DEFAULT) {
|
||||
this->uart_ = (UARTSelection) (LT_UART_DEFAULT_SERIAL + 1);
|
||||
lt_log_set_port(LT_UART_DEFAULT_SERIAL);
|
||||
} else {
|
||||
lt_log_set_port(this->uart_ - 1);
|
||||
}
|
||||
}
|
||||
|
||||
global_logger = this;
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_DEFAULT:
|
||||
return LOG_STR("DEFAULT");
|
||||
case UART_SELECTION_UART0:
|
||||
return LOG_STR("UART0");
|
||||
case UART_SELECTION_UART1:
|
||||
return LOG_STR("UART1");
|
||||
case UART_SELECTION_UART2:
|
||||
default:
|
||||
return LOG_STR("UART2");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
#endif // USE_LIBRETINY
|
||||
@@ -1,48 +0,0 @@
|
||||
#ifdef USE_RP2040
|
||||
#include "logger.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
static const char *const TAG = "logger";
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
break;
|
||||
case UART_SELECTION_USB_CDC:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
global_logger = this;
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
return LOG_STR("UART0");
|
||||
case UART_SELECTION_UART1:
|
||||
return LOG_STR("UART1");
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
case UART_SELECTION_USB_CDC:
|
||||
return LOG_STR("USB_CDC");
|
||||
#endif
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::logger
|
||||
#endif // USE_RP2040
|
||||
@@ -1,96 +0,0 @@
|
||||
#ifdef USE_ZEPHYR
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/uart.h>
|
||||
#include <zephyr/usb/usb_device.h>
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
static const char *const TAG = "logger";
|
||||
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
void Logger::loop() {
|
||||
if (this->uart_ != UART_SELECTION_USB_CDC || nullptr == this->uart_dev_) {
|
||||
return;
|
||||
}
|
||||
static bool opened = false;
|
||||
uint32_t dtr = 0;
|
||||
uart_line_ctrl_get(this->uart_dev_, UART_LINE_CTRL_DTR, &dtr);
|
||||
|
||||
/* Poll if the DTR flag was set, optional */
|
||||
if (opened == dtr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!opened) {
|
||||
App.schedule_dump_config();
|
||||
}
|
||||
opened = !opened;
|
||||
}
|
||||
#endif
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
static const struct device *uart_dev = nullptr;
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(uart0));
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(uart1));
|
||||
break;
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
case UART_SELECTION_USB_CDC:
|
||||
uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(cdc_acm_uart0));
|
||||
if (device_is_ready(uart_dev)) {
|
||||
usb_enable(nullptr);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
if (!device_is_ready(uart_dev)) {
|
||||
ESP_LOGE(TAG, "%s is not ready.", LOG_STR_ARG(get_uart_selection_()));
|
||||
} else {
|
||||
this->uart_dev_ = uart_dev;
|
||||
}
|
||||
}
|
||||
global_logger = this;
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) {
|
||||
#ifdef CONFIG_PRINTK
|
||||
printk("%s\n", msg);
|
||||
#endif
|
||||
if (nullptr == this->uart_dev_) {
|
||||
return;
|
||||
}
|
||||
while (*msg) {
|
||||
uart_poll_out(this->uart_dev_, *msg);
|
||||
++msg;
|
||||
}
|
||||
uart_poll_out(this->uart_dev_, '\n');
|
||||
}
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
return LOG_STR("UART0");
|
||||
case UART_SELECTION_UART1:
|
||||
return LOG_STR("UART1");
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
case UART_SELECTION_USB_CDC:
|
||||
return LOG_STR("USB_CDC");
|
||||
#endif
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
#endif
|
||||
@@ -56,7 +56,7 @@ void MCP23016::pin_mode(uint8_t pin, gpio::Flags flags) {
|
||||
this->update_reg_(pin, false, iodir);
|
||||
}
|
||||
}
|
||||
float MCP23016::get_setup_priority() const { return setup_priority::IO; }
|
||||
float MCP23016::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
bool MCP23016::read_reg_(uint8_t reg, uint8_t *value) {
|
||||
if (this->is_failed())
|
||||
return false;
|
||||
|
||||
@@ -448,9 +448,6 @@ async def to_code(config):
|
||||
# The inference task queues detection events that need immediate processing
|
||||
socket.require_wake_loop_threadsafe()
|
||||
|
||||
# Keep ring buffer functions in IRAM for audio performance
|
||||
esp32.enable_ringbuf_in_iram()
|
||||
|
||||
mic_source = await microphone.microphone_source_to_code(config[CONF_MICROPHONE])
|
||||
cg.add(var.set_microphone_source(mic_source))
|
||||
|
||||
|
||||
@@ -224,12 +224,9 @@ class MipiSpi : public display::Display,
|
||||
this->madctl_ & MADCTL_BGR ? "BGR" : "RGB", DISPLAYPIXEL * 8, IS_BIG_ENDIAN ? "Big" : "Little");
|
||||
if (this->brightness_.has_value())
|
||||
esph_log_config(TAG, " Brightness: %u", this->brightness_.value());
|
||||
if (this->cs_ != nullptr)
|
||||
esph_log_config(TAG, " CS Pin: %s", this->cs_->dump_summary().c_str());
|
||||
if (this->reset_pin_ != nullptr)
|
||||
esph_log_config(TAG, " Reset Pin: %s", this->reset_pin_->dump_summary().c_str());
|
||||
if (this->dc_pin_ != nullptr)
|
||||
esph_log_config(TAG, " DC Pin: %s", this->dc_pin_->dump_summary().c_str());
|
||||
log_pin(TAG, " CS Pin: ", this->cs_);
|
||||
log_pin(TAG, " Reset Pin: ", this->reset_pin_);
|
||||
log_pin(TAG, " DC Pin: ", this->dc_pin_);
|
||||
esph_log_config(TAG,
|
||||
" SPI Mode: %d\n"
|
||||
" SPI Data rate: %dMHz\n"
|
||||
|
||||
@@ -26,5 +26,3 @@ ST7789V.extend(
|
||||
reset_pin=40,
|
||||
invert_colors=True,
|
||||
)
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -105,6 +105,3 @@ CO5300 = DriverChip(
|
||||
(WCE, 0x00),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -1,10 +1,45 @@
|
||||
from .ili import ILI9341
|
||||
from .ili import ILI9341, ILI9342, ST7789V
|
||||
|
||||
ILI9341.extend(
|
||||
# ESP32-2432S028 CYD board with Micro USB, has ILI9341 controller
|
||||
"ESP32-2432S028",
|
||||
data_rate="40MHz",
|
||||
cs_pin=15,
|
||||
dc_pin=2,
|
||||
cs_pin={"number": 15, "ignore_strapping_warning": True},
|
||||
dc_pin={"number": 2, "ignore_strapping_warning": True},
|
||||
)
|
||||
|
||||
models = {}
|
||||
ST7789V.extend(
|
||||
# ESP32-2432S028 CYD board with USB C + Micro USB, has ST7789V controller
|
||||
"ESP32-2432S028-7789",
|
||||
data_rate="40MHz",
|
||||
cs_pin={"number": 15, "ignore_strapping_warning": True},
|
||||
dc_pin={"number": 2, "ignore_strapping_warning": True},
|
||||
)
|
||||
|
||||
# fmt: off
|
||||
|
||||
ILI9342.extend(
|
||||
# ESP32-2432S028 CYD board with USB C + Micro USB, has ILI9342 controller
|
||||
"ESP32-2432S028-9342",
|
||||
data_rate="40MHz",
|
||||
cs_pin={"number": 15, "ignore_strapping_warning": True},
|
||||
dc_pin={"number": 2, "ignore_strapping_warning": True},
|
||||
initsequence=(
|
||||
(0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02), # Power Control A
|
||||
(0xCF, 0x00, 0xC1, 0x30), # Power Control B
|
||||
(0xE8, 0x85, 0x00, 0x78), # Driver timing control A
|
||||
(0xEA, 0x00, 0x00), # Driver timing control B
|
||||
(0xED, 0x64, 0x03, 0x12, 0x81), # Power on sequence control
|
||||
(0xF7, 0x20), # Pump ratio control
|
||||
(0xC0, 0x23), # Power Control 1
|
||||
(0xC1, 0x10), # Power Control 2
|
||||
(0xC5, 0x3E, 0x28), # VCOM Control 1
|
||||
(0xC7, 0x86), # VCOM Control 2
|
||||
(0xB1, 0x00, 0x1B), # Frame Rate Control
|
||||
(0xB6, 0x0A, 0xA2, 0x27, 0x00), # Display Function Control
|
||||
(0xF2, 0x00), # Enable 3G
|
||||
(0x26, 0x01), # Gamma Set
|
||||
(0xE0, 0x00, 0x0C, 0x11, 0x04, 0x11, 0x08, 0x37, 0x89, 0x4C, 0x06, 0x0C, 0x0A, 0x2E, 0x34, 0x0F), # Positive Gamma Correction
|
||||
(0xE1, 0x00, 0x0B, 0x11, 0x05, 0x13, 0x09, 0x33, 0x67, 0x48, 0x07, 0x0E, 0x0B, 0x23, 0x33, 0x0F), # Negative Gamma Correction
|
||||
)
|
||||
)
|
||||
|
||||
@@ -148,6 +148,34 @@ ILI9341 = DriverChip(
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# fmt: off
|
||||
|
||||
ILI9342 = DriverChip(
|
||||
"ILI9342",
|
||||
width=320,
|
||||
height=240,
|
||||
mirror_x=True,
|
||||
initsequence=(
|
||||
(0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02), # Power Control A
|
||||
(0xCF, 0x00, 0xC1, 0x30), # Power Control B
|
||||
(0xE8, 0x85, 0x00, 0x78), # Driver timing control A
|
||||
(0xEA, 0x00, 0x00), # Driver timing control B
|
||||
(0xED, 0x64, 0x03, 0x12, 0x81), # Power on sequence control
|
||||
(0xF7, 0x20), # Pump ratio control
|
||||
(0xC0, 0x23), # Power Control 1
|
||||
(0xC1, 0x10), # Power Control 2
|
||||
(0xC5, 0x3E, 0x28), # VCOM Control 1
|
||||
(0xC7, 0x86), # VCOM Control 2
|
||||
(0xB1, 0x00, 0x1B), # Frame Rate Control
|
||||
(0xB6, 0x0A, 0xA2, 0x27, 0x00), # Display Function Control
|
||||
(0xF2, 0x00), # Enable 3G
|
||||
(0x26, 0x01), # Gamma Set
|
||||
(0xE0, 0x0F, 0x1F, 0x1C, 0x0C, 0x0F, 0x08, 0x48, 0x98, 0x37, 0x0A, 0x13, 0x04, 0x11, 0x0D, 0x00), # Positive Gamma
|
||||
(0xE1, 0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75, 0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00), # Negative Gamma
|
||||
),
|
||||
)
|
||||
|
||||
# M5Stack Core2 uses ILI9341 chip - mirror_x disabled for correct orientation
|
||||
ILI9341.extend(
|
||||
"M5CORE2",
|
||||
@@ -758,5 +786,3 @@ ST7796.extend(
|
||||
dc_pin=0,
|
||||
invert_colors=True,
|
||||
)
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -588,5 +588,3 @@ DriverChip(
|
||||
(0x29, 0x00),
|
||||
),
|
||||
)
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -11,5 +11,3 @@ ST7789V.extend(
|
||||
dc_pin=21,
|
||||
reset_pin=18,
|
||||
)
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -56,5 +56,3 @@ ST7796.extend(
|
||||
backlight_pin=48,
|
||||
invert_colors=True,
|
||||
)
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -271,9 +271,9 @@ class ServerRegister {
|
||||
|
||||
// Formats a raw value into a string representation based on the value type for debugging
|
||||
std::string format_value(int64_t value) const {
|
||||
// max 48: float with %.1f can be up to 42 chars (3.4e38 → 38 integer digits + decimal + 1 digit + sign + null)
|
||||
// int64_t max is 20 chars + sign + null = 22, so 48 covers both
|
||||
char buf[48];
|
||||
// max 44: float with %.1f can be up to 42 chars (3.4e38 → 39 integer digits + sign + decimal + 1 digit)
|
||||
// plus null terminator = 43, rounded to 44 for 4-byte alignment
|
||||
char buf[44];
|
||||
switch (this->value_type) {
|
||||
case SensorValueType::U_WORD:
|
||||
case SensorValueType::U_DWORD:
|
||||
|
||||
@@ -18,7 +18,7 @@ bool CustomMQTTDevice::publish(const std::string &topic, float value, int8_t num
|
||||
}
|
||||
bool CustomMQTTDevice::publish(const std::string &topic, int value) {
|
||||
char buffer[24];
|
||||
size_t len = buf_append_printf(buffer, sizeof(buffer), 0, "%d", value);
|
||||
int len = snprintf(buffer, sizeof(buffer), "%d", value);
|
||||
return global_mqtt_client->publish(topic, buffer, len);
|
||||
}
|
||||
bool CustomMQTTDevice::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos, bool retain) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <utility>
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/version.h"
|
||||
@@ -66,10 +67,13 @@ void MQTTClientComponent::setup() {
|
||||
"esphome/discover", [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); },
|
||||
2);
|
||||
|
||||
std::string topic = "esphome/ping/";
|
||||
topic.append(App.get_name());
|
||||
// Format topic on stack - subscribe() copies it
|
||||
// "esphome/ping/" (13) + name (ESPHOME_DEVICE_NAME_MAX_LEN) + null (1)
|
||||
constexpr size_t ping_topic_buffer_size = 13 + ESPHOME_DEVICE_NAME_MAX_LEN + 1;
|
||||
char ping_topic[ping_topic_buffer_size];
|
||||
buf_append_printf(ping_topic, sizeof(ping_topic), 0, "esphome/ping/%s", App.get_name().c_str());
|
||||
this->subscribe(
|
||||
topic, [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2);
|
||||
ping_topic, [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2);
|
||||
}
|
||||
|
||||
if (this->enable_on_boot_) {
|
||||
@@ -81,8 +85,11 @@ void MQTTClientComponent::send_device_info_() {
|
||||
if (!this->is_connected() or !this->is_discovery_ip_enabled()) {
|
||||
return;
|
||||
}
|
||||
std::string topic = "esphome/discover/";
|
||||
topic.append(App.get_name());
|
||||
// Format topic on stack to avoid heap allocation
|
||||
// "esphome/discover/" (17) + name (ESPHOME_DEVICE_NAME_MAX_LEN) + null (1)
|
||||
constexpr size_t topic_buffer_size = 17 + ESPHOME_DEVICE_NAME_MAX_LEN + 1;
|
||||
char topic[topic_buffer_size];
|
||||
buf_append_printf(topic, sizeof(topic), 0, "esphome/discover/%s", App.get_name().c_str());
|
||||
|
||||
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
this->publish_json(
|
||||
@@ -91,17 +98,7 @@ void MQTTClientComponent::send_device_info_() {
|
||||
uint8_t index = 0;
|
||||
for (auto &ip : network::get_ip_addresses()) {
|
||||
if (ip.is_set()) {
|
||||
char key[8]; // "ip" + up to 3 digits + null
|
||||
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
if (index == 0) {
|
||||
key[0] = 'i';
|
||||
key[1] = 'p';
|
||||
key[2] = '\0';
|
||||
} else {
|
||||
buf_append_printf(key, sizeof(key), 0, "ip%u", index);
|
||||
}
|
||||
ip.str_to(ip_buf);
|
||||
root[key] = ip_buf;
|
||||
root["ip" + (index == 0 ? "" : esphome::to_string(index))] = ip.str();
|
||||
index++;
|
||||
}
|
||||
}
|
||||
@@ -406,6 +403,12 @@ void MQTTClientComponent::loop() {
|
||||
|
||||
this->last_connected_ = now;
|
||||
this->resubscribe_subscriptions_();
|
||||
|
||||
// Process pending resends for all MQTT components centrally
|
||||
// This is more efficient than each component polling in its own loop
|
||||
for (MQTTComponent *component : this->children_) {
|
||||
component->process_resend();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -510,39 +513,49 @@ bool MQTTClientComponent::publish(const std::string &topic, const std::string &p
|
||||
|
||||
bool MQTTClientComponent::publish(const std::string &topic, const char *payload, size_t payload_length, uint8_t qos,
|
||||
bool retain) {
|
||||
return publish({.topic = topic, .payload = std::string(payload, payload_length), .qos = qos, .retain = retain});
|
||||
return this->publish(topic.c_str(), payload, payload_length, qos, retain);
|
||||
}
|
||||
|
||||
bool MQTTClientComponent::publish(const MQTTMessage &message) {
|
||||
return this->publish(message.topic.c_str(), message.payload.c_str(), message.payload.length(), message.qos,
|
||||
message.retain);
|
||||
}
|
||||
bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos,
|
||||
bool retain) {
|
||||
return this->publish_json(topic.c_str(), f, qos, retain);
|
||||
}
|
||||
|
||||
bool MQTTClientComponent::publish(const char *topic, const char *payload, size_t payload_length, uint8_t qos,
|
||||
bool retain) {
|
||||
if (!this->is_connected()) {
|
||||
// critical components will re-transmit their messages
|
||||
return false;
|
||||
}
|
||||
bool logging_topic = this->log_message_.topic == message.topic;
|
||||
bool ret = this->mqtt_backend_.publish(message);
|
||||
size_t topic_len = strlen(topic);
|
||||
bool logging_topic = (topic_len == this->log_message_.topic.size()) &&
|
||||
(memcmp(this->log_message_.topic.c_str(), topic, topic_len) == 0);
|
||||
bool ret = this->mqtt_backend_.publish(topic, payload, payload_length, qos, retain);
|
||||
delay(0);
|
||||
if (!ret && !logging_topic && this->is_connected()) {
|
||||
delay(0);
|
||||
ret = this->mqtt_backend_.publish(message);
|
||||
ret = this->mqtt_backend_.publish(topic, payload, payload_length, qos, retain);
|
||||
delay(0);
|
||||
}
|
||||
|
||||
if (!logging_topic) {
|
||||
if (ret) {
|
||||
ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d qos=%d)", message.topic.c_str(), message.payload.c_str(),
|
||||
message.retain, message.qos);
|
||||
ESP_LOGV(TAG, "Publish(topic='%s' retain=%d qos=%d)", topic, retain, qos);
|
||||
ESP_LOGVV(TAG, "Publish payload (len=%u): '%.*s'", payload_length, static_cast<int>(payload_length), payload);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). Will retry", message.topic.c_str(),
|
||||
message.payload.length());
|
||||
ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). Will retry", topic, payload_length);
|
||||
this->status_momentary_warning("publish", 1000);
|
||||
}
|
||||
}
|
||||
return ret != 0;
|
||||
}
|
||||
bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos,
|
||||
bool retain) {
|
||||
|
||||
bool MQTTClientComponent::publish_json(const char *topic, const json::json_build_t &f, uint8_t qos, bool retain) {
|
||||
std::string message = json::build_json(f);
|
||||
return this->publish(topic, message, qos, retain);
|
||||
return this->publish(topic, message.c_str(), message.length(), qos, retain);
|
||||
}
|
||||
|
||||
void MQTTClientComponent::enable() {
|
||||
@@ -620,18 +633,10 @@ static bool topic_match(const char *message, const char *subscription) {
|
||||
}
|
||||
|
||||
void MQTTClientComponent::on_message(const std::string &topic, const std::string &payload) {
|
||||
#ifdef USE_ESP8266
|
||||
// on ESP8266, this is called in lwIP/AsyncTCP task; some components do not like running
|
||||
// from a different task.
|
||||
this->defer([this, topic, payload]() {
|
||||
#endif
|
||||
for (auto &subscription : this->subscriptions_) {
|
||||
if (topic_match(topic.c_str(), subscription.topic.c_str()))
|
||||
subscription.callback(topic, payload);
|
||||
}
|
||||
#ifdef USE_ESP8266
|
||||
});
|
||||
#endif
|
||||
for (auto &subscription : this->subscriptions_) {
|
||||
if (topic_match(topic.c_str(), subscription.topic.c_str()))
|
||||
subscription.callback(topic, payload);
|
||||
}
|
||||
}
|
||||
|
||||
// Setters
|
||||
|
||||
@@ -229,6 +229,9 @@ class MQTTClientComponent : public Component
|
||||
bool publish(const std::string &topic, const char *payload, size_t payload_length, uint8_t qos = 0,
|
||||
bool retain = false);
|
||||
|
||||
/// Publish directly without creating MQTTMessage (avoids heap allocation for topic)
|
||||
bool publish(const char *topic, const char *payload, size_t payload_length, uint8_t qos = 0, bool retain = false);
|
||||
|
||||
/** Construct and send a JSON MQTT message.
|
||||
*
|
||||
* @param topic The topic.
|
||||
@@ -237,6 +240,9 @@ class MQTTClientComponent : public Component
|
||||
*/
|
||||
bool publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos = 0, bool retain = false);
|
||||
|
||||
/// Publish JSON directly without heap allocation for topic
|
||||
bool publish_json(const char *topic, const json::json_build_t &f, uint8_t qos = 0, bool retain = false);
|
||||
|
||||
/// Setup the MQTT client, registering a bunch of callbacks and attempting to connect.
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
@@ -190,37 +190,27 @@ bool MQTTComponent::send_discovery_() {
|
||||
StringRef object_id = this->get_default_object_id_to_(object_id_buf);
|
||||
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
|
||||
char friendly_name_hash[9];
|
||||
buf_append_printf(friendly_name_hash, sizeof(friendly_name_hash), 0, "%08" PRIx32,
|
||||
fnv1_hash(this->friendly_name_()));
|
||||
snprintf(friendly_name_hash, sizeof(friendly_name_hash), "%08" PRIx32, fnv1_hash(this->friendly_name_()));
|
||||
// Format: mac-component_type-hash (e.g. "aabbccddeeff-sensor-12345678")
|
||||
// MAC (12) + "-" (1) + domain (max 20) + "-" (1) + hash (8) + null (1) = 43
|
||||
char unique_id[MAC_ADDRESS_BUFFER_SIZE + ESPHOME_DOMAIN_MAX_LEN + 11];
|
||||
char mac_buf[MAC_ADDRESS_BUFFER_SIZE];
|
||||
get_mac_address_into_buffer(mac_buf);
|
||||
buf_append_printf(unique_id, sizeof(unique_id), 0, "%s-%s-%s", mac_buf, this->component_type(),
|
||||
friendly_name_hash);
|
||||
snprintf(unique_id, sizeof(unique_id), "%s-%s-%s", mac_buf, this->component_type(), friendly_name_hash);
|
||||
root[MQTT_UNIQUE_ID] = unique_id;
|
||||
} else {
|
||||
// default to almost-unique ID. It's a hack but the only way to get that
|
||||
// gorgeous device registry view.
|
||||
// "ESP" (3) + component_type (max 20) + object_id (max 128) + null
|
||||
char unique_id_buf[3 + MQTT_COMPONENT_TYPE_MAX_LEN + OBJECT_ID_MAX_LEN + 1];
|
||||
buf_append_printf(unique_id_buf, sizeof(unique_id_buf), 0, "ESP%s%s", this->component_type(),
|
||||
object_id.c_str());
|
||||
root[MQTT_UNIQUE_ID] = unique_id_buf;
|
||||
root[MQTT_UNIQUE_ID] = "ESP" + std::string(this->component_type()) + object_id.c_str();
|
||||
}
|
||||
|
||||
const std::string &node_name = App.get_name();
|
||||
if (discovery_info.object_id_generator == MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR) {
|
||||
// node_name (max 31) + "_" (1) + object_id (max 128) + null
|
||||
char object_id_full[ESPHOME_DEVICE_NAME_MAX_LEN + 1 + OBJECT_ID_MAX_LEN + 1];
|
||||
buf_append_printf(object_id_full, sizeof(object_id_full), 0, "%s_%s", node_name.c_str(), object_id.c_str());
|
||||
root[MQTT_OBJECT_ID] = object_id_full;
|
||||
}
|
||||
if (discovery_info.object_id_generator == MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR)
|
||||
root[MQTT_OBJECT_ID] = node_name + "_" + object_id.c_str();
|
||||
|
||||
const std::string &friendly_name_ref = App.get_friendly_name();
|
||||
const std::string &node_friendly_name = friendly_name_ref.empty() ? node_name : friendly_name_ref;
|
||||
const char *node_area = App.get_area();
|
||||
std::string node_area = App.get_area();
|
||||
|
||||
JsonObject device_info = root[MQTT_DEVICE].to<JsonObject>();
|
||||
char mac[MAC_ADDRESS_BUFFER_SIZE];
|
||||
@@ -231,29 +221,18 @@ bool MQTTComponent::send_discovery_() {
|
||||
device_info[MQTT_DEVICE_SW_VERSION] = ESPHOME_PROJECT_VERSION " (ESPHome " ESPHOME_VERSION ")";
|
||||
const char *model = std::strchr(ESPHOME_PROJECT_NAME, '.');
|
||||
device_info[MQTT_DEVICE_MODEL] = model == nullptr ? ESPHOME_BOARD : model + 1;
|
||||
if (model == nullptr) {
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] = ESPHOME_PROJECT_NAME;
|
||||
} else {
|
||||
// Extract manufacturer (part before '.') using stack buffer to avoid heap allocation
|
||||
// memcpy is used instead of strncpy since we know the exact length and strncpy
|
||||
// would still require manual null-termination
|
||||
char manufacturer[sizeof(ESPHOME_PROJECT_NAME)];
|
||||
size_t len = model - ESPHOME_PROJECT_NAME;
|
||||
memcpy(manufacturer, ESPHOME_PROJECT_NAME, len);
|
||||
manufacturer[len] = '\0';
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] = manufacturer;
|
||||
}
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] =
|
||||
model == nullptr ? ESPHOME_PROJECT_NAME : std::string(ESPHOME_PROJECT_NAME, model - ESPHOME_PROJECT_NAME);
|
||||
#else
|
||||
static const char ver_fmt[] PROGMEM = ESPHOME_VERSION " (config hash 0x%08" PRIx32 ")";
|
||||
// Buffer sized for format string expansion: ~4 bytes net growth from format specifier to 8 hex digits, plus
|
||||
// safety margin
|
||||
char version_buf[sizeof(ver_fmt) + 8];
|
||||
#ifdef USE_ESP8266
|
||||
snprintf_P(version_buf, sizeof(version_buf), ver_fmt, App.get_config_hash());
|
||||
char fmt_buf[sizeof(ver_fmt)];
|
||||
strcpy_P(fmt_buf, ver_fmt);
|
||||
const char *fmt = fmt_buf;
|
||||
#else
|
||||
snprintf(version_buf, sizeof(version_buf), ver_fmt, App.get_config_hash());
|
||||
const char *fmt = ver_fmt;
|
||||
#endif
|
||||
device_info[MQTT_DEVICE_SW_VERSION] = version_buf;
|
||||
device_info[MQTT_DEVICE_SW_VERSION] = str_sprintf(fmt, App.get_config_hash());
|
||||
device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD;
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] = "Espressif";
|
||||
@@ -267,7 +246,7 @@ bool MQTTComponent::send_discovery_() {
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] = "Host";
|
||||
#endif
|
||||
#endif
|
||||
if (node_area[0] != '\0') {
|
||||
if (!node_area.empty()) {
|
||||
device_info[MQTT_DEVICE_SUGGESTED_AREA] = node_area;
|
||||
}
|
||||
|
||||
@@ -329,16 +308,12 @@ void MQTTComponent::call_setup() {
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTComponent::call_loop() {
|
||||
if (this->is_internal())
|
||||
void MQTTComponent::process_resend() {
|
||||
// Called by MQTTClientComponent when connected to process pending resends
|
||||
// Note: is_internal() check not needed - internal components are never registered
|
||||
if (!this->resend_state_)
|
||||
return;
|
||||
|
||||
this->loop();
|
||||
|
||||
if (!this->resend_state_ || !this->is_connected_()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->resend_state_ = false;
|
||||
if (this->is_discovery_enabled()) {
|
||||
if (!this->send_discovery_()) {
|
||||
|
||||
@@ -81,8 +81,6 @@ class MQTTComponent : public Component {
|
||||
/// Override setup_ so that we can call send_discovery() when needed.
|
||||
void call_setup() override;
|
||||
|
||||
void call_loop() override;
|
||||
|
||||
void call_dump_config() override;
|
||||
|
||||
/// Send discovery info the Home Assistant, override this.
|
||||
@@ -133,6 +131,9 @@ class MQTTComponent : public Component {
|
||||
/// Internal method for the MQTT client base to schedule a resend of the state on reconnect.
|
||||
void schedule_resend_state();
|
||||
|
||||
/// Process pending resend if needed (called by MQTTClientComponent)
|
||||
void process_resend();
|
||||
|
||||
/** Send a MQTT message.
|
||||
*
|
||||
* @param topic The topic.
|
||||
|
||||
@@ -175,7 +175,7 @@ bool MQTTFanComponent::publish_state() {
|
||||
auto traits = this->state_->get_traits();
|
||||
if (traits.supports_speed()) {
|
||||
char buf[12];
|
||||
size_t len = buf_append_printf(buf, sizeof(buf), 0, "%d", this->state_->speed);
|
||||
int len = snprintf(buf, sizeof(buf), "%d", this->state_->speed);
|
||||
bool success = this->publish(this->get_speed_level_state_topic(), buf, len);
|
||||
failed = failed || !success;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ bool MQTTNumberComponent::send_initial_state() {
|
||||
}
|
||||
bool MQTTNumberComponent::publish_state(float value) {
|
||||
char buffer[64];
|
||||
buf_append_printf(buffer, sizeof(buffer), 0, "%f", value);
|
||||
snprintf(buffer, sizeof(buffer), "%f", value);
|
||||
return this->publish(this->get_state_topic_(), buffer);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,8 @@ struct IPAddress {
|
||||
}
|
||||
IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); }
|
||||
IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; }
|
||||
// Remove before 2026.8.0
|
||||
ESPDEPRECATED("Use str_to() instead. Removed in 2026.8.0", "2026.2.0")
|
||||
std::string str() const {
|
||||
char buf[IP_ADDRESS_BUFFER_SIZE];
|
||||
this->str_to(buf);
|
||||
@@ -147,6 +149,8 @@ struct IPAddress {
|
||||
bool is_ip4() const { return IP_IS_V4(&ip_addr_); }
|
||||
bool is_ip6() const { return IP_IS_V6(&ip_addr_); }
|
||||
bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); }
|
||||
// Remove before 2026.8.0
|
||||
ESPDEPRECATED("Use str_to() instead. Removed in 2026.8.0", "2026.2.0")
|
||||
std::string str() const {
|
||||
char buf[IP_ADDRESS_BUFFER_SIZE];
|
||||
this->str_to(buf);
|
||||
|
||||
@@ -8,12 +8,12 @@ namespace pipsolar {
|
||||
static const char *const TAG = "pipsolar.output";
|
||||
|
||||
void PipsolarOutput::write_state(float state) {
|
||||
char tmp[10];
|
||||
char tmp[16];
|
||||
snprintf(tmp, sizeof(tmp), this->set_command_, state);
|
||||
|
||||
if (std::find(this->possible_values_.begin(), this->possible_values_.end(), state) != this->possible_values_.end()) {
|
||||
ESP_LOGD(TAG, "Will write: %s out of value %f / %02.0f", tmp, state, state);
|
||||
this->parent_->queue_command(tmp);
|
||||
this->parent_->queue_command(std::string(tmp));
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Will not write: %s as it is not in list of allowed values", tmp);
|
||||
}
|
||||
|
||||
@@ -45,20 +45,20 @@ void Pipsolar::loop() {
|
||||
} else {
|
||||
ESP_LOGD(TAG, "command not successful");
|
||||
}
|
||||
this->command_queue_[this->command_queue_position_].clear();
|
||||
this->command_queue_[this->command_queue_position_] = std::string("");
|
||||
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
|
||||
this->state_ = STATE_IDLE;
|
||||
} else {
|
||||
// crc failed
|
||||
// no log message necessary, check_incoming_crc_() logs
|
||||
this->command_queue_[this->command_queue_position_].clear();
|
||||
this->command_queue_[this->command_queue_position_] = std::string("");
|
||||
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
|
||||
this->state_ = STATE_IDLE;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "command %s response length not OK: with length %zu",
|
||||
this->command_queue_[this->command_queue_position_].c_str(), this->read_pos_);
|
||||
this->command_queue_[this->command_queue_position_].clear();
|
||||
this->command_queue_[this->command_queue_position_] = std::string("");
|
||||
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
|
||||
this->state_ = STATE_IDLE;
|
||||
}
|
||||
@@ -127,7 +127,7 @@ void Pipsolar::loop() {
|
||||
const char *command = this->command_queue_[this->command_queue_position_].c_str();
|
||||
this->command_start_millis_ = millis();
|
||||
ESP_LOGD(TAG, "command %s timeout", command);
|
||||
this->command_queue_[this->command_queue_position_].clear();
|
||||
this->command_queue_[this->command_queue_position_] = std::string("");
|
||||
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
|
||||
this->state_ = STATE_IDLE;
|
||||
return;
|
||||
@@ -722,7 +722,7 @@ void Pipsolar::publish_binary_sensor_(esphome::optional<bool> b, binary_sensor::
|
||||
}
|
||||
}
|
||||
|
||||
esphome::optional<bool> Pipsolar::get_bit_(const std::string &bits, uint8_t bit_pos) {
|
||||
esphome::optional<bool> Pipsolar::get_bit_(std::string bits, uint8_t bit_pos) {
|
||||
if (bit_pos >= bits.length()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ class Pipsolar : public uart::UARTDevice, public PollingComponent {
|
||||
|
||||
void publish_binary_sensor_(esphome::optional<bool> b, binary_sensor::BinarySensor *sensor);
|
||||
|
||||
esphome::optional<bool> get_bit_(const std::string &bits, uint8_t bit_pos);
|
||||
esphome::optional<bool> get_bit_(std::string bits, uint8_t bit_pos);
|
||||
|
||||
std::string command_queue_[COMMAND_QUEUE_LENGTH];
|
||||
uint8_t command_queue_position_ = 0;
|
||||
|
||||
@@ -80,21 +80,23 @@ class Select : public EntityBase {
|
||||
|
||||
void add_on_state_callback(std::function<void(size_t)> &&callback);
|
||||
|
||||
/** Set the value of the select by index, this is an optional virtual method.
|
||||
*
|
||||
* This method is called by the SelectCall when the index is already known.
|
||||
* Default implementation converts to string and calls control().
|
||||
* Override this to work directly with indices and avoid string conversions.
|
||||
*
|
||||
* @param index The index as validated by the SelectCall.
|
||||
*/
|
||||
virtual void control(size_t index) { this->control(this->option_at(index)); }
|
||||
|
||||
protected:
|
||||
friend class SelectCall;
|
||||
|
||||
size_t active_index_{0};
|
||||
|
||||
/** Set the value of the select by index, this is an optional virtual method.
|
||||
*
|
||||
* IMPORTANT: At least ONE of the two control() methods must be overridden by derived classes.
|
||||
* Overriding this index-based version is PREFERRED as it avoids string conversions.
|
||||
*
|
||||
* This method is called by the SelectCall when the index is already known.
|
||||
* Default implementation converts to string and calls control(const std::string&).
|
||||
*
|
||||
* @param index The index as validated by the SelectCall.
|
||||
*/
|
||||
virtual void control(size_t index) { this->control(this->option_at(index)); }
|
||||
|
||||
/** Set the value of the select, this is a virtual method that each select integration can implement.
|
||||
*
|
||||
* IMPORTANT: At least ONE of the two control() methods must be overridden by derived classes.
|
||||
|
||||
@@ -13,14 +13,14 @@ static const uint16_t SHTCX_COMMAND_READ_ID_REGISTER = 0xEFC8;
|
||||
static const uint16_t SHTCX_COMMAND_SOFT_RESET = 0x805D;
|
||||
static const uint16_t SHTCX_COMMAND_POLLING_H = 0x7866;
|
||||
|
||||
inline const char *to_string(SHTCXType type) {
|
||||
static const LogString *shtcx_type_to_string(SHTCXType type) {
|
||||
switch (type) {
|
||||
case SHTCX_TYPE_SHTC3:
|
||||
return "SHTC3";
|
||||
return LOG_STR("SHTC3");
|
||||
case SHTCX_TYPE_SHTC1:
|
||||
return "SHTC1";
|
||||
return LOG_STR("SHTC1");
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ void SHTCXComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"SHTCx:\n"
|
||||
" Model: %s (%04x)",
|
||||
to_string(this->type_), this->sensor_id_);
|
||||
LOG_STR_ARG(shtcx_type_to_string(this->type_)), this->sensor_id_);
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
|
||||
@@ -51,7 +51,7 @@ void Sim800LComponent::update() {
|
||||
} else if (state_ == STATE_RECEIVED_SMS) {
|
||||
// Serial Buffer should have flushed.
|
||||
// Send cmd to delete received sms
|
||||
char delete_cmd[20]; // "AT+CMGD=" (8) + int (max 11) + null = 20
|
||||
char delete_cmd[20]; // "AT+CMGD=" (8) + uint8_t (max 3) + null = 12 <= 20
|
||||
buf_append_printf(delete_cmd, sizeof(delete_cmd), 0, "AT+CMGD=%d", this->parse_index_);
|
||||
this->send_cmd_(delete_cmd);
|
||||
this->state_ = STATE_CHECK_SMS;
|
||||
|
||||
@@ -106,7 +106,7 @@ std::string bytes_repr(const BytesView &buffer) {
|
||||
for (auto const value : buffer) {
|
||||
// max 3: 2 hex digits + null
|
||||
char hex_buf[3];
|
||||
snprintf(hex_buf, sizeof(hex_buf), "%02x", value & 0xff);
|
||||
snprintf(hex_buf, sizeof(hex_buf), "%02x", static_cast<unsigned int>(value));
|
||||
repr += hex_buf;
|
||||
}
|
||||
return repr;
|
||||
|
||||
@@ -25,7 +25,7 @@ CONFIG_SCHEMA = (
|
||||
cv.Optional(CONF_SPEED): cv.invalid(
|
||||
"Configuring individual speeds is deprecated."
|
||||
),
|
||||
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_PRESET_MODES): validate_preset_modes,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace speed {
|
||||
|
||||
class SpeedFan : public Component, public fan::Fan {
|
||||
public:
|
||||
SpeedFan(uint8_t speed_count) : speed_count_(speed_count) {}
|
||||
SpeedFan(int speed_count) : speed_count_(speed_count) {}
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void set_output(output::FloatOutput *output) { this->output_ = output; }
|
||||
@@ -26,7 +26,7 @@ class SpeedFan : public Component, public fan::Fan {
|
||||
output::FloatOutput *output_;
|
||||
output::BinaryOutput *oscillating_{nullptr};
|
||||
output::BinaryOutput *direction_{nullptr};
|
||||
uint8_t speed_count_{};
|
||||
int speed_count_{};
|
||||
fan::FanTraits traits_;
|
||||
std::vector<const char *> preset_modes_{};
|
||||
};
|
||||
|
||||
@@ -43,13 +43,11 @@ SprinklerControllerSwitch::SprinklerControllerSwitch()
|
||||
: turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {}
|
||||
|
||||
void SprinklerControllerSwitch::loop() {
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
// Loop is only enabled when f_ has a value (see setup())
|
||||
auto s = (*this->f_)();
|
||||
if (!s.has_value())
|
||||
return;
|
||||
|
||||
this->publish_state(*s);
|
||||
if (s.has_value()) {
|
||||
this->publish_state(*s);
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerControllerSwitch::write_state(bool state) {
|
||||
@@ -74,7 +72,13 @@ float SprinklerControllerSwitch::get_setup_priority() const { return setup_prior
|
||||
Trigger<> *SprinklerControllerSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; }
|
||||
Trigger<> *SprinklerControllerSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; }
|
||||
|
||||
void SprinklerControllerSwitch::setup() { this->state = this->get_initial_state_with_restore_mode().value_or(false); }
|
||||
void SprinklerControllerSwitch::setup() {
|
||||
this->state = this->get_initial_state_with_restore_mode().value_or(false);
|
||||
// Disable loop if no state lambda is set - nothing to poll
|
||||
if (!this->f_.has_value()) {
|
||||
this->disable_loop();
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerControllerSwitch::dump_config() { LOG_SWITCH("", "Sprinkler Switch", this); }
|
||||
|
||||
@@ -327,25 +331,32 @@ SprinklerValveOperator *SprinklerValveRunRequest::valve_operator() { return this
|
||||
|
||||
SprinklerValveRunRequestOrigin SprinklerValveRunRequest::request_is_from() { return this->origin_; }
|
||||
|
||||
Sprinkler::Sprinkler() {}
|
||||
Sprinkler::Sprinkler(const std::string &name) {
|
||||
// The `name` is needed to set timers up, hence non-default constructor
|
||||
// replaces `set_name()` method previously existed
|
||||
this->name_ = name;
|
||||
Sprinkler::Sprinkler() : Sprinkler("") {}
|
||||
Sprinkler::Sprinkler(const char *name) : name_(name) {
|
||||
// The `name` is stored for dump_config logging
|
||||
this->timer_.init(2);
|
||||
this->timer_.push_back({this->name_ + "sm", false, 0, 0, std::bind(&Sprinkler::sm_timer_callback_, this)});
|
||||
this->timer_.push_back({this->name_ + "vs", false, 0, 0, std::bind(&Sprinkler::valve_selection_callback_, this)});
|
||||
// Timer names only need to be unique within this component instance
|
||||
this->timer_.push_back({"sm", false, 0, 0, std::bind(&Sprinkler::sm_timer_callback_, this)});
|
||||
this->timer_.push_back({"vs", false, 0, 0, std::bind(&Sprinkler::valve_selection_callback_, this)});
|
||||
}
|
||||
|
||||
void Sprinkler::setup() { this->all_valves_off_(true); }
|
||||
void Sprinkler::setup() {
|
||||
this->all_valves_off_(true);
|
||||
// Start with loop disabled - nothing to do when idle
|
||||
this->disable_loop();
|
||||
}
|
||||
|
||||
void Sprinkler::loop() {
|
||||
for (auto &vo : this->valve_op_) {
|
||||
vo.loop();
|
||||
}
|
||||
if (this->prev_req_.has_request() && this->prev_req_.has_valve_operator() &&
|
||||
this->prev_req_.valve_operator()->state() == IDLE) {
|
||||
this->prev_req_.reset();
|
||||
if (this->prev_req_.has_request()) {
|
||||
if (this->prev_req_.has_valve_operator() && this->prev_req_.valve_operator()->state() == IDLE) {
|
||||
this->prev_req_.reset();
|
||||
}
|
||||
} else if (this->state_ == IDLE) {
|
||||
// Nothing more to do - disable loop until next activation
|
||||
this->disable_loop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1333,6 +1344,8 @@ void Sprinkler::start_valve_(SprinklerValveRunRequest *req) {
|
||||
if (!this->is_a_valid_valve(req->valve())) {
|
||||
return; // we can't do anything if the valve number isn't valid
|
||||
}
|
||||
// Enable loop to monitor valve operator states
|
||||
this->enable_loop();
|
||||
for (auto &vo : this->valve_op_) { // find the first available SprinklerValveOperator, load it and start it up
|
||||
if (vo.state() == IDLE) {
|
||||
auto run_duration = req->run_duration() ? req->run_duration() : this->valve_run_duration_adjusted(req->valve());
|
||||
@@ -1575,8 +1588,7 @@ const LogString *Sprinkler::state_as_str_(SprinklerState state) {
|
||||
|
||||
void Sprinkler::start_timer_(const SprinklerTimerIndex timer_index) {
|
||||
if (this->timer_duration_(timer_index) > 0) {
|
||||
// FixedVector ensures timer_ can't be resized, so .c_str() pointers remain valid
|
||||
this->set_timeout(this->timer_[timer_index].name.c_str(), this->timer_duration_(timer_index),
|
||||
this->set_timeout(this->timer_[timer_index].name, this->timer_duration_(timer_index),
|
||||
this->timer_cbf_(timer_index));
|
||||
this->timer_[timer_index].start_time = millis();
|
||||
this->timer_[timer_index].active = true;
|
||||
@@ -1587,7 +1599,7 @@ void Sprinkler::start_timer_(const SprinklerTimerIndex timer_index) {
|
||||
|
||||
bool Sprinkler::cancel_timer_(const SprinklerTimerIndex timer_index) {
|
||||
this->timer_[timer_index].active = false;
|
||||
return this->cancel_timeout(this->timer_[timer_index].name.c_str());
|
||||
return this->cancel_timeout(this->timer_[timer_index].name);
|
||||
}
|
||||
|
||||
bool Sprinkler::timer_active_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].active; }
|
||||
@@ -1618,7 +1630,7 @@ void Sprinkler::sm_timer_callback_() {
|
||||
}
|
||||
|
||||
void Sprinkler::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Sprinkler Controller -- %s", this->name_.c_str());
|
||||
ESP_LOGCONFIG(TAG, "Sprinkler Controller -- %s", this->name_);
|
||||
if (this->manual_selection_delay_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Manual Selection Delay: %" PRIu32 " seconds", this->manual_selection_delay_.value_or(0));
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace esphome::sprinkler {
|
||||
|
||||
const std::string MIN_STR = "min";
|
||||
inline constexpr const char *MIN_STR = "min";
|
||||
|
||||
enum SprinklerState : uint8_t {
|
||||
// NOTE: these states are used by both SprinklerValveOperator and Sprinkler (the controller)!
|
||||
@@ -49,7 +49,7 @@ struct SprinklerQueueItem {
|
||||
};
|
||||
|
||||
struct SprinklerTimer {
|
||||
const std::string name;
|
||||
const char *name;
|
||||
bool active;
|
||||
uint32_t time;
|
||||
uint32_t start_time;
|
||||
@@ -176,7 +176,7 @@ class SprinklerValveRunRequest {
|
||||
class Sprinkler : public Component {
|
||||
public:
|
||||
Sprinkler();
|
||||
Sprinkler(const std::string &name);
|
||||
Sprinkler(const char *name);
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
@@ -504,7 +504,7 @@ class Sprinkler : public Component {
|
||||
uint32_t start_delay_{0};
|
||||
uint32_t stop_delay_{0};
|
||||
|
||||
std::string name_;
|
||||
const char *name_{""};
|
||||
|
||||
/// Sprinkler controller state
|
||||
SprinklerState state_{IDLE};
|
||||
|
||||
@@ -126,10 +126,9 @@ void StatsdComponent::update() {
|
||||
}
|
||||
out.append(s.name);
|
||||
// Buffer for ":" + value + "|g\n".
|
||||
// %g uses max 13 chars for value (sign + 6 significant digits + e+xxx)
|
||||
// Total: 1 + 13 + 4 = 18 chars + null, use 24 for safety
|
||||
char val_buf[24];
|
||||
buf_append_printf(val_buf, sizeof(val_buf), 0, ":%g|g\n", val);
|
||||
// %f with -DBL_MAX can produce up to 321 chars, plus ":" and "|g\n" (4) + null = 326
|
||||
char val_buf[330];
|
||||
buf_append_printf(val_buf, sizeof(val_buf), 0, ":%f|g\n", val);
|
||||
out.append(val_buf);
|
||||
|
||||
if (out.length() > SEND_THRESHOLD) {
|
||||
|
||||
@@ -7,14 +7,14 @@ DEPENDENCIES = ["network"]
|
||||
|
||||
status_ns = cg.esphome_ns.namespace("status")
|
||||
StatusBinarySensor = status_ns.class_(
|
||||
"StatusBinarySensor", binary_sensor.BinarySensor, cg.Component
|
||||
"StatusBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(
|
||||
StatusBinarySensor,
|
||||
device_class=DEVICE_CLASS_CONNECTIVITY,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
).extend(cv.polling_component_schema("1s"))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -10,12 +10,11 @@
|
||||
#include "esphome/components/api/api_server.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace status {
|
||||
namespace esphome::status {
|
||||
|
||||
static const char *const TAG = "status";
|
||||
|
||||
void StatusBinarySensor::loop() {
|
||||
void StatusBinarySensor::update() {
|
||||
bool status = network::is_connected();
|
||||
#ifdef USE_MQTT
|
||||
if (mqtt::global_mqtt_client != nullptr) {
|
||||
@@ -33,5 +32,4 @@ void StatusBinarySensor::loop() {
|
||||
void StatusBinarySensor::setup() { this->publish_initial_state(false); }
|
||||
void StatusBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Status Binary Sensor", this); }
|
||||
|
||||
} // namespace status
|
||||
} // namespace esphome
|
||||
} // namespace esphome::status
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace status {
|
||||
namespace esphome::status {
|
||||
|
||||
class StatusBinarySensor : public binary_sensor::BinarySensor, public Component {
|
||||
class StatusBinarySensor : public binary_sensor::BinarySensor, public PollingComponent {
|
||||
public:
|
||||
void loop() override;
|
||||
void update() override;
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
@@ -16,5 +15,4 @@ class StatusBinarySensor : public binary_sensor::BinarySensor, public Component
|
||||
bool is_status_binary_sensor() const override { return true; }
|
||||
};
|
||||
|
||||
} // namespace status
|
||||
} // namespace esphome
|
||||
} // namespace esphome::status
|
||||
|
||||
@@ -4,7 +4,8 @@ namespace esphome {
|
||||
namespace teleinfo {
|
||||
|
||||
static const char *const TAG = "teleinfo_sensor";
|
||||
void TeleInfoSensor::publish_val(const char *val) {
|
||||
TeleInfoSensor::TeleInfoSensor(const char *tag) { this->tag = std::string(tag); }
|
||||
void TeleInfoSensor::publish_val(const std::string &val) {
|
||||
auto newval = parse_number<float>(val).value_or(0.0f);
|
||||
publish_state(newval);
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace teleinfo {
|
||||
|
||||
class TeleInfoSensor : public TeleInfoListener, public sensor::Sensor, public Component {
|
||||
public:
|
||||
TeleInfoSensor(const char *tag) { this->tag = tag; }
|
||||
void publish_val(const char *val) override;
|
||||
TeleInfoSensor(const char *tag);
|
||||
void publish_val(const std::string &val) override;
|
||||
void dump_config() override;
|
||||
};
|
||||
|
||||
|
||||
@@ -172,15 +172,15 @@ void TeleInfo::loop() {
|
||||
/* Advance buf_finger to end of group */
|
||||
buf_finger += field_len + 1 + 1 + 1;
|
||||
|
||||
publish_value_(tag_, val_);
|
||||
publish_value_(std::string(tag_), std::string(val_));
|
||||
}
|
||||
state_ = OFF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
void TeleInfo::publish_value_(const char *tag, const char *val) {
|
||||
void TeleInfo::publish_value_(const std::string &tag, const std::string &val) {
|
||||
for (auto *element : teleinfo_listeners_) {
|
||||
if (strcmp(tag, element->tag) != 0)
|
||||
if (tag != element->tag)
|
||||
continue;
|
||||
element->publish_val(val);
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ static const uint16_t MAX_TIMESTAMP_SIZE = 14;
|
||||
|
||||
class TeleInfoListener {
|
||||
public:
|
||||
const char *tag{nullptr};
|
||||
virtual void publish_val(const char *val){};
|
||||
std::string tag;
|
||||
virtual void publish_val(const std::string &val){};
|
||||
};
|
||||
class TeleInfo : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
@@ -48,7 +48,7 @@ class TeleInfo : public PollingComponent, public uart::UARTDevice {
|
||||
} state_{OFF};
|
||||
bool read_chars_until_(bool drop, uint8_t c);
|
||||
bool check_crc_(const char *grp, const char *grp_end);
|
||||
void publish_value_(const char *tag, const char *val);
|
||||
void publish_value_(const std::string &tag, const std::string &val);
|
||||
};
|
||||
} // namespace teleinfo
|
||||
} // namespace esphome
|
||||
|
||||
@@ -4,7 +4,8 @@ namespace esphome {
|
||||
namespace teleinfo {
|
||||
|
||||
static const char *const TAG = "teleinfo_text_sensor";
|
||||
void TeleInfoTextSensor::publish_val(const char *val) { publish_state(val); }
|
||||
TeleInfoTextSensor::TeleInfoTextSensor(const char *tag) { this->tag = std::string(tag); }
|
||||
void TeleInfoTextSensor::publish_val(const std::string &val) { publish_state(val); }
|
||||
void TeleInfoTextSensor::dump_config() { LOG_TEXT_SENSOR(" ", "Teleinfo Text Sensor", this); }
|
||||
} // namespace teleinfo
|
||||
} // namespace esphome
|
||||
|
||||
@@ -5,8 +5,8 @@ namespace esphome {
|
||||
namespace teleinfo {
|
||||
class TeleInfoTextSensor : public TeleInfoListener, public text_sensor::TextSensor, public Component {
|
||||
public:
|
||||
TeleInfoTextSensor(const char *tag) { this->tag = tag; }
|
||||
void publish_val(const char *val) override;
|
||||
TeleInfoTextSensor(const char *tag);
|
||||
void publish_val(const std::string &val) override;
|
||||
void dump_config() override;
|
||||
};
|
||||
} // namespace teleinfo
|
||||
|
||||
@@ -19,7 +19,7 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.Optional(CONF_HAS_DIRECTION, default=False): cv.boolean,
|
||||
cv.Optional(CONF_HAS_OSCILLATING, default=False): cv.boolean,
|
||||
cv.Optional(CONF_SPEED_COUNT): cv.int_range(min=1, max=255),
|
||||
cv.Optional(CONF_SPEED_COUNT): cv.int_range(min=1),
|
||||
cv.Optional(CONF_PRESET_MODES): validate_preset_modes,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ class TemplateFan final : public Component, public fan::Fan {
|
||||
void dump_config() override;
|
||||
void set_has_direction(bool has_direction) { this->has_direction_ = has_direction; }
|
||||
void set_has_oscillating(bool has_oscillating) { this->has_oscillating_ = has_oscillating; }
|
||||
void set_speed_count(uint8_t count) { this->speed_count_ = count; }
|
||||
void set_speed_count(int count) { this->speed_count_ = count; }
|
||||
void set_preset_modes(std::initializer_list<const char *> presets) { this->preset_modes_ = presets; }
|
||||
fan::FanTraits get_traits() override { return this->traits_; }
|
||||
|
||||
@@ -21,7 +21,7 @@ class TemplateFan final : public Component, public fan::Fan {
|
||||
|
||||
bool has_oscillating_{false};
|
||||
bool has_direction_{false};
|
||||
uint8_t speed_count_{0};
|
||||
int speed_count_{0};
|
||||
fan::FanTraits traits_;
|
||||
std::vector<const char *> preset_modes_{};
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_SPEED_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_DIRECTION_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_SPEED_COUNT, default=3): cv.int_range(min=1, max=255),
|
||||
cv.Optional(CONF_SPEED_COUNT, default=3): cv.int_range(min=1, max=256),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace tuya {
|
||||
|
||||
class TuyaFan : public Component, public fan::Fan {
|
||||
public:
|
||||
TuyaFan(Tuya *parent, uint8_t speed_count) : parent_(parent), speed_count_(speed_count) {}
|
||||
TuyaFan(Tuya *parent, int speed_count) : parent_(parent), speed_count_(speed_count) {}
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void set_speed_id(uint8_t speed_id) { this->speed_id_ = speed_id; }
|
||||
@@ -27,7 +27,7 @@ class TuyaFan : public Component, public fan::Fan {
|
||||
optional<uint8_t> switch_id_{};
|
||||
optional<uint8_t> oscillation_id_{};
|
||||
optional<uint8_t> direction_id_{};
|
||||
uint8_t speed_count_{};
|
||||
int speed_count_{};
|
||||
TuyaDatapointType speed_type_{};
|
||||
TuyaDatapointType oscillation_type_{};
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ void TuyaTextSensor::setup() {
|
||||
}
|
||||
case TuyaDatapointType::ENUM: {
|
||||
char buf[4]; // uint8_t max is 3 digits + null
|
||||
snprintf(buf, sizeof(buf), "%u", datapoint.value_enum);
|
||||
buf_append_printf(buf, sizeof(buf), 0, "%u", datapoint.value_enum);
|
||||
ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, buf);
|
||||
this->publish_state(buf);
|
||||
break;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace tx20 {
|
||||
@@ -45,24 +45,25 @@ std::string Tx20Component::get_wind_cardinal_direction() const { return this->wi
|
||||
void Tx20Component::decode_and_publish_() {
|
||||
ESP_LOGVV(TAG, "Decode Tx20");
|
||||
|
||||
std::array<bool, MAX_BUFFER_SIZE> bit_buffer{};
|
||||
size_t bit_pos = 0;
|
||||
std::string string_buffer;
|
||||
std::string string_buffer_2;
|
||||
std::vector<bool> bit_buffer;
|
||||
bool current_bit = true;
|
||||
// Cap at MAX_BUFFER_SIZE to prevent out-of-bounds access (buffer_index can exceed MAX_BUFFER_SIZE in ISR)
|
||||
const int max_buffer_index = std::min(static_cast<int>(this->store_.buffer_index), static_cast<int>(MAX_BUFFER_SIZE));
|
||||
|
||||
for (int i = 1; i <= max_buffer_index; i++) {
|
||||
for (int i = 1; i <= this->store_.buffer_index; i++) {
|
||||
string_buffer_2 += to_string(this->store_.buffer[i]) + ", ";
|
||||
uint8_t repeat = this->store_.buffer[i] / TX20_BIT_TIME;
|
||||
// ignore segments at the end that were too short
|
||||
for (uint8_t j = 0; j < repeat && bit_pos < MAX_BUFFER_SIZE; j++) {
|
||||
bit_buffer[bit_pos++] = current_bit;
|
||||
}
|
||||
string_buffer.append(repeat, current_bit ? '1' : '0');
|
||||
bit_buffer.insert(bit_buffer.end(), repeat, current_bit);
|
||||
current_bit = !current_bit;
|
||||
}
|
||||
current_bit = !current_bit;
|
||||
size_t bits_before_padding = bit_pos;
|
||||
while (bit_pos < MAX_BUFFER_SIZE) {
|
||||
bit_buffer[bit_pos++] = current_bit;
|
||||
if (string_buffer.length() < MAX_BUFFER_SIZE) {
|
||||
uint8_t remain = MAX_BUFFER_SIZE - string_buffer.length();
|
||||
string_buffer_2 += to_string(remain) + ", ";
|
||||
string_buffer.append(remain, current_bit ? '1' : '0');
|
||||
bit_buffer.insert(bit_buffer.end(), remain, current_bit);
|
||||
}
|
||||
|
||||
uint8_t tx20_sa = 0;
|
||||
@@ -107,24 +108,8 @@ void Tx20Component::decode_and_publish_() {
|
||||
// 2. Check received checksum matches calculated checksum
|
||||
// 3. Check that Wind Direction matches Wind Direction (Inverted)
|
||||
// 4. Check that Wind Speed matches Wind Speed (Inverted)
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
// Build debug strings from completed data
|
||||
char debug_buf[320]; // buffer values: max 42 entries * 7 chars each
|
||||
size_t debug_pos = 0;
|
||||
for (int i = 1; i <= max_buffer_index; i++) {
|
||||
debug_pos = buf_append_printf(debug_buf, sizeof(debug_buf), debug_pos, "%u, ", this->store_.buffer[i]);
|
||||
}
|
||||
if (bits_before_padding < MAX_BUFFER_SIZE) {
|
||||
buf_append_printf(debug_buf, sizeof(debug_buf), debug_pos, "%zu, ", MAX_BUFFER_SIZE - bits_before_padding);
|
||||
}
|
||||
char bits_buf[MAX_BUFFER_SIZE + 1];
|
||||
for (size_t i = 0; i < MAX_BUFFER_SIZE; i++) {
|
||||
bits_buf[i] = bit_buffer[i] ? '1' : '0';
|
||||
}
|
||||
bits_buf[MAX_BUFFER_SIZE] = '\0';
|
||||
ESP_LOGVV(TAG, "BUFFER %s", debug_buf);
|
||||
ESP_LOGVV(TAG, "Decoded bits %s", bits_buf);
|
||||
#endif
|
||||
ESP_LOGVV(TAG, "BUFFER %s", string_buffer_2.c_str());
|
||||
ESP_LOGVV(TAG, "Decoded bits %s", string_buffer.c_str());
|
||||
|
||||
if (tx20_sa == 4) {
|
||||
if (chk == tx20_sd) {
|
||||
|
||||
@@ -81,6 +81,8 @@ struct Timer {
|
||||
this->id.c_str(), this->name.c_str(), this->total_seconds, this->seconds_left, YESNO(this->is_active));
|
||||
return buffer.data();
|
||||
}
|
||||
// Remove before 2026.8.0
|
||||
ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0")
|
||||
std::string to_string() const {
|
||||
char buffer[TO_STR_BUFFER_SIZE];
|
||||
return this->to_str(buffer);
|
||||
|
||||
@@ -406,9 +406,8 @@ void AsyncWebServerResponse::addHeader(const char *name, const char *value) {
|
||||
void AsyncResponseStream::print(float value) {
|
||||
// Use stack buffer to avoid temporary string allocation
|
||||
// Size: sign (1) + digits (10) + decimal (1) + precision (6) + exponent (5) + null (1) = 24, use 32 for safety
|
||||
constexpr size_t float_buf_size = 32;
|
||||
char buf[float_buf_size];
|
||||
int len = snprintf(buf, float_buf_size, "%f", value);
|
||||
char buf[32];
|
||||
int len = snprintf(buf, sizeof(buf), "%f", value);
|
||||
this->content_.append(buf, len);
|
||||
}
|
||||
|
||||
|
||||
@@ -1735,7 +1735,6 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() {
|
||||
(old_priority > std::numeric_limits<int8_t>::min()) ? (old_priority - 1) : std::numeric_limits<int8_t>::min();
|
||||
this->set_sta_priority(failed_bssid.value(), new_priority);
|
||||
}
|
||||
|
||||
char bssid_s[18];
|
||||
format_mac_addr_upper(failed_bssid.value().data(), bssid_s);
|
||||
ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %d → %d",
|
||||
|
||||
@@ -79,13 +79,17 @@ async def setup_conf(config, key):
|
||||
|
||||
async def to_code(config):
|
||||
# Request specific WiFi listeners based on which sensors are configured
|
||||
# Each sensor needs its own listener slot - call request for EACH sensor
|
||||
|
||||
# SSID and BSSID use WiFiConnectStateListener
|
||||
if CONF_SSID in config or CONF_BSSID in config:
|
||||
wifi.request_wifi_connect_state_listener()
|
||||
for key in (CONF_SSID, CONF_BSSID):
|
||||
if key in config:
|
||||
wifi.request_wifi_connect_state_listener()
|
||||
|
||||
# IP address and DNS use WiFiIPStateListener
|
||||
if CONF_IP_ADDRESS in config or CONF_DNS_ADDRESS in config:
|
||||
wifi.request_wifi_ip_state_listener()
|
||||
for key in (CONF_IP_ADDRESS, CONF_DNS_ADDRESS):
|
||||
if key in config:
|
||||
wifi.request_wifi_ip_state_listener()
|
||||
|
||||
# Scan results use WiFiScanResultsListener
|
||||
if CONF_SCAN_RESULTS in config:
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace x9c {
|
||||
|
||||
static const char *const TAG = "x9c.output";
|
||||
|
||||
void X9cOutput::trim_value(int change_amount) {
|
||||
void X9cOutput::trim_value(int32_t change_amount) {
|
||||
if (change_amount == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -47,17 +47,17 @@ void X9cOutput::setup() {
|
||||
|
||||
if (this->initial_value_ <= 0.50) {
|
||||
this->trim_value(-101); // Set min value (beyond 0)
|
||||
this->trim_value(static_cast<uint32_t>(roundf(this->initial_value_ * 100)));
|
||||
this->trim_value(lroundf(this->initial_value_ * 100));
|
||||
} else {
|
||||
this->trim_value(101); // Set max value (beyond 100)
|
||||
this->trim_value(static_cast<uint32_t>(roundf(this->initial_value_ * 100) - 100));
|
||||
this->trim_value(lroundf(this->initial_value_ * 100) - 100);
|
||||
}
|
||||
this->pot_value_ = this->initial_value_;
|
||||
this->write_state(this->initial_value_);
|
||||
}
|
||||
|
||||
void X9cOutput::write_state(float state) {
|
||||
this->trim_value(static_cast<uint32_t>(roundf((state - this->pot_value_) * 100)));
|
||||
this->trim_value(lroundf((state - this->pot_value_) * 100));
|
||||
this->pot_value_ = state;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class X9cOutput : public output::FloatOutput, public Component {
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void trim_value(int change_amount);
|
||||
void trim_value(int32_t change_amount);
|
||||
|
||||
protected:
|
||||
void write_state(float state) override;
|
||||
|
||||
@@ -1046,20 +1046,20 @@ def mac_address(value):
|
||||
return core.MACAddress(*parts_int)
|
||||
|
||||
|
||||
def bind_key(value):
|
||||
def bind_key(value, *, name="Bind key"):
|
||||
value = string_strict(value)
|
||||
parts = [value[i : i + 2] for i in range(0, len(value), 2)]
|
||||
if len(parts) != 16:
|
||||
raise Invalid("Bind key must consist of 16 hexadecimal numbers")
|
||||
raise Invalid(f"{name} must consist of 16 hexadecimal numbers")
|
||||
parts_int = []
|
||||
if any(len(part) != 2 for part in parts):
|
||||
raise Invalid("Bind key must be format XX")
|
||||
raise Invalid(f"{name} must be format XX")
|
||||
for part in parts:
|
||||
try:
|
||||
parts_int.append(int(part, 16))
|
||||
except ValueError:
|
||||
# pylint: disable=raise-missing-from
|
||||
raise Invalid("Bind key must be hex values from 00 to FF")
|
||||
raise Invalid(f"{name} must be hex values from 00 to FF")
|
||||
|
||||
return "".join(f"{part:02X}" for part in parts_int)
|
||||
|
||||
|
||||
@@ -761,15 +761,6 @@ class EsphomeCore:
|
||||
def relative_piolibdeps_path(self, *path: str | Path) -> Path:
|
||||
return self.relative_build_path(".piolibdeps", *path)
|
||||
|
||||
@property
|
||||
def platformio_cache_dir(self) -> str:
|
||||
"""Get the PlatformIO cache directory path."""
|
||||
# Check if running in Docker/HA addon with custom cache dir
|
||||
if (cache_dir := os.environ.get("PLATFORMIO_CACHE_DIR")) and cache_dir.strip():
|
||||
return cache_dir
|
||||
# Default PlatformIO cache location
|
||||
return os.path.expanduser("~/.platformio/.cache")
|
||||
|
||||
@property
|
||||
def firmware_bin(self) -> Path:
|
||||
if self.is_libretiny:
|
||||
|
||||
@@ -347,10 +347,7 @@ std::string format_hex(const uint8_t *data, size_t length) {
|
||||
format_hex_to(&ret[0], length * 2 + 1, data, length);
|
||||
return ret;
|
||||
}
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
std::string format_hex(const std::vector<uint8_t> &data) { return format_hex(data.data(), data.size()); }
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator) {
|
||||
return format_hex_internal(buffer, buffer_size, data, length, separator, 'A');
|
||||
@@ -519,8 +516,10 @@ int8_t step_to_accuracy_decimals(float step) {
|
||||
return str.length() - dot_pos - 1;
|
||||
}
|
||||
|
||||
// Store BASE64 characters as array - automatically placed in flash/ROM on embedded platforms
|
||||
static const char BASE64_CHARS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
// Use C-style string constant to store in ROM instead of RAM (saves 24 bytes)
|
||||
static constexpr const char *BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
// Helper function to find the index of a base64/base64url character in the lookup table.
|
||||
// Returns the character's position (0-63) if found, or 0 if not found.
|
||||
|
||||
@@ -132,78 +132,6 @@ template<typename T> class ConstVector {
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
/// Small buffer optimization - stores data inline when small, heap-allocates for large data
|
||||
/// This avoids heap fragmentation for common small allocations while supporting arbitrary sizes.
|
||||
/// Memory management is encapsulated - callers just use set() and data().
|
||||
template<size_t InlineSize = 8> class SmallInlineBuffer {
|
||||
public:
|
||||
SmallInlineBuffer() = default;
|
||||
~SmallInlineBuffer() {
|
||||
if (!this->is_inline_())
|
||||
delete[] this->heap_;
|
||||
}
|
||||
|
||||
// Move constructor
|
||||
SmallInlineBuffer(SmallInlineBuffer &&other) noexcept : len_(other.len_) {
|
||||
if (other.is_inline_()) {
|
||||
memcpy(this->inline_, other.inline_, this->len_);
|
||||
} else {
|
||||
this->heap_ = other.heap_;
|
||||
other.heap_ = nullptr;
|
||||
}
|
||||
other.len_ = 0;
|
||||
}
|
||||
|
||||
// Move assignment
|
||||
SmallInlineBuffer &operator=(SmallInlineBuffer &&other) noexcept {
|
||||
if (this != &other) {
|
||||
if (!this->is_inline_())
|
||||
delete[] this->heap_;
|
||||
this->len_ = other.len_;
|
||||
if (other.is_inline_()) {
|
||||
memcpy(this->inline_, other.inline_, this->len_);
|
||||
} else {
|
||||
this->heap_ = other.heap_;
|
||||
other.heap_ = nullptr;
|
||||
}
|
||||
other.len_ = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Disable copy (would need deep copy of heap data)
|
||||
SmallInlineBuffer(const SmallInlineBuffer &) = delete;
|
||||
SmallInlineBuffer &operator=(const SmallInlineBuffer &) = delete;
|
||||
|
||||
/// Set buffer contents, allocating heap if needed
|
||||
void set(const uint8_t *src, size_t size) {
|
||||
// Free existing heap allocation if switching from heap to inline or different heap size
|
||||
if (!this->is_inline_() && (size <= InlineSize || size != this->len_)) {
|
||||
delete[] this->heap_;
|
||||
this->heap_ = nullptr; // Defensive: prevent use-after-free if logic changes
|
||||
}
|
||||
// Allocate new heap buffer if needed
|
||||
if (size > InlineSize && (this->is_inline_() || size != this->len_)) {
|
||||
this->heap_ = new uint8_t[size]; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
this->len_ = size;
|
||||
memcpy(this->data(), src, size);
|
||||
}
|
||||
|
||||
uint8_t *data() { return this->is_inline_() ? this->inline_ : this->heap_; }
|
||||
const uint8_t *data() const { return this->is_inline_() ? this->inline_ : this->heap_; }
|
||||
size_t size() const { return this->len_; }
|
||||
|
||||
protected:
|
||||
bool is_inline_() const { return this->len_ <= InlineSize; }
|
||||
|
||||
size_t len_{0};
|
||||
union {
|
||||
uint8_t inline_[InlineSize]{}; // Zero-init ensures clean initial state
|
||||
uint8_t *heap_;
|
||||
};
|
||||
};
|
||||
|
||||
/// Minimal static vector - saves memory by avoiding std::vector overhead
|
||||
template<typename T, size_t N> class StaticVector {
|
||||
public:
|
||||
@@ -238,9 +166,6 @@ template<typename T, size_t N> class StaticVector {
|
||||
size_t size() const { return count_; }
|
||||
bool empty() const { return count_ == 0; }
|
||||
|
||||
// Direct access to size counter for efficient in-place construction
|
||||
size_t &count() { return count_; }
|
||||
|
||||
// Direct access to underlying data
|
||||
T *data() { return data_.data(); }
|
||||
const T *data() const { return data_.data(); }
|
||||
@@ -423,6 +348,8 @@ template<typename T> class FixedVector {
|
||||
|
||||
size_t size() const { return size_; }
|
||||
bool empty() const { return size_ == 0; }
|
||||
size_t capacity() const { return capacity_; }
|
||||
bool full() const { return size_ == capacity_; }
|
||||
|
||||
/// Access element without bounds checking (matches std::vector behavior)
|
||||
/// Caller must ensure index is valid (i < size())
|
||||
@@ -441,6 +368,35 @@ template<typename T> class FixedVector {
|
||||
const T *end() const { return data_ + size_; }
|
||||
};
|
||||
|
||||
/// @brief Helper class for efficient buffer allocation - uses stack for small sizes, heap for large
|
||||
/// This is useful when most operations need a small buffer but occasionally need larger ones.
|
||||
/// The stack buffer avoids heap allocation in the common case, while heap fallback handles edge cases.
|
||||
template<size_t STACK_SIZE> class SmallBufferWithHeapFallback {
|
||||
public:
|
||||
explicit SmallBufferWithHeapFallback(size_t size) {
|
||||
if (size <= STACK_SIZE) {
|
||||
this->buffer_ = this->stack_buffer_;
|
||||
} else {
|
||||
this->heap_buffer_ = new uint8_t[size];
|
||||
this->buffer_ = this->heap_buffer_;
|
||||
}
|
||||
}
|
||||
~SmallBufferWithHeapFallback() { delete[] this->heap_buffer_; }
|
||||
|
||||
// Delete copy and move operations to prevent double-delete
|
||||
SmallBufferWithHeapFallback(const SmallBufferWithHeapFallback &) = delete;
|
||||
SmallBufferWithHeapFallback &operator=(const SmallBufferWithHeapFallback &) = delete;
|
||||
SmallBufferWithHeapFallback(SmallBufferWithHeapFallback &&) = delete;
|
||||
SmallBufferWithHeapFallback &operator=(SmallBufferWithHeapFallback &&) = delete;
|
||||
|
||||
uint8_t *get() { return this->buffer_; }
|
||||
|
||||
private:
|
||||
uint8_t stack_buffer_[STACK_SIZE];
|
||||
uint8_t *heap_buffer_{nullptr};
|
||||
uint8_t *buffer_;
|
||||
};
|
||||
|
||||
///@}
|
||||
|
||||
/// @name Mathematics
|
||||
@@ -614,10 +570,6 @@ template<typename T> constexpr T convert_little_endian(T val) {
|
||||
bool str_equals_case_insensitive(const std::string &a, const std::string &b);
|
||||
/// Compare StringRefs for equality in case-insensitive manner.
|
||||
bool str_equals_case_insensitive(StringRef a, StringRef b);
|
||||
/// Compare C strings for equality in case-insensitive manner (no heap allocation).
|
||||
inline bool str_equals_case_insensitive(const char *a, const char *b) { return strcasecmp(a, b) == 0; }
|
||||
inline bool str_equals_case_insensitive(const std::string &a, const char *b) { return strcasecmp(a.c_str(), b) == 0; }
|
||||
inline bool str_equals_case_insensitive(const char *a, const std::string &b) { return strcasecmp(a, b.c_str()) == 0; }
|
||||
|
||||
/// Check whether a string starts with a value.
|
||||
bool str_startswith(const std::string &str, const std::string &start);
|
||||
@@ -695,11 +647,9 @@ inline uint32_t fnv1_hash_object_id(const char *str, size_t len) {
|
||||
}
|
||||
|
||||
/// snprintf-like function returning std::string of maximum length \p len (excluding null terminator).
|
||||
/// @warning Allocates heap memory. Use snprintf() with a stack buffer instead.
|
||||
std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t len, ...);
|
||||
|
||||
/// sprintf-like function returning std::string.
|
||||
/// @warning Allocates heap memory. Use snprintf() with a stack buffer instead.
|
||||
std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...);
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
@@ -1072,17 +1022,13 @@ std::string format_hex(const std::vector<uint8_t> &data);
|
||||
/// Causes heap fragmentation on long-running devices.
|
||||
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_hex(T val) {
|
||||
val = convert_big_endian(val);
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
return format_hex(reinterpret_cast<uint8_t *>(&val), sizeof(T));
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
/// Format the std::array \p data in lowercased hex.
|
||||
/// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead.
|
||||
/// Causes heap fragmentation on long-running devices.
|
||||
template<std::size_t N> std::string format_hex(const std::array<uint8_t, N> &data) {
|
||||
return format_hex(data.data(), data.size());
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
/** Format a byte array in pretty-printed, human-readable hex format.
|
||||
|
||||
@@ -118,9 +118,10 @@ class Scheduler {
|
||||
} name_;
|
||||
uint32_t interval;
|
||||
// Split time to handle millis() rollover. The scheduler combines the 32-bit millis()
|
||||
// with a 16-bit rollover counter to create a 48-bit time space (stored as 64-bit
|
||||
// for compatibility). With 49.7 days per 32-bit rollover, the 16-bit counter
|
||||
// supports 49.7 days × 65536 = ~8900 years. This ensures correct scheduling
|
||||
// with a 16-bit rollover counter to create a 48-bit time space (using 32+16 bits).
|
||||
// This is intentionally limited to 48 bits, not stored as a full 64-bit value.
|
||||
// With 49.7 days per 32-bit rollover, the 16-bit counter supports
|
||||
// 49.7 days × 65536 = ~8900 years. This ensures correct scheduling
|
||||
// even when devices run for months. Split into two fields for better memory
|
||||
// alignment on 32-bit systems.
|
||||
uint32_t next_execution_low_; // Lower 32 bits of execution time (millis value)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user