[wifi_info] Reduce heap allocations in text sensor formatting (#12660)

This commit is contained in:
J. Nick Koston
2025-12-26 12:52:41 -10:00
committed by GitHub
parent 5a2e0612a8
commit bdc087148a
3 changed files with 65 additions and 13 deletions

View File

@@ -40,6 +40,9 @@ using ip4_addr_t = in_addr;
namespace esphome {
namespace network {
/// Buffer size for IP address string (IPv6 max: 39 chars + null)
static constexpr size_t IP_ADDRESS_BUFFER_SIZE = 40;
struct IPAddress {
public:
#ifdef USE_HOST
@@ -50,6 +53,10 @@ 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; }
std::string str() const { return str_lower_case(inet_ntoa(ip_addr_)); }
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
char *str_to(char *buf) const {
return const_cast<char *>(inet_ntop(AF_INET, &ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE));
}
#else
IPAddress() { ip_addr_set_zero(&ip_addr_); }
IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) {
@@ -128,6 +135,8 @@ struct IPAddress {
bool is_ip6() const { return IP_IS_V6(&ip_addr_); }
bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); }
std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); }
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
char *str_to(char *buf) const { return ipaddr_ntoa_r(&ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE); }
bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
IPAddress &operator+=(uint8_t increase) {

View File

@@ -46,8 +46,13 @@ void DNSAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "DNS Address", this
void DNSAddressWifiInfo::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1,
const network::IPAddress &dns2) {
std::string dns_results = dns1.str() + " " + dns2.str();
this->publish_state(dns_results);
// IP_ADDRESS_BUFFER_SIZE (40) = max IP (39) + null; space reuses first null's slot
char buf[network::IP_ADDRESS_BUFFER_SIZE * 2];
dns1.str_to(buf);
size_t len1 = strlen(buf);
buf[len1] = ' ';
dns2.str_to(buf + len1 + 1);
this->publish_state(buf);
}
/**********************
@@ -58,22 +63,36 @@ void ScanResultsWiFiInfo::setup() { wifi::global_wifi_component->add_scan_result
void ScanResultsWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "Scan Results", this); }
// Format: "SSID: -XXdB\n" - caller must ensure ssid_len + 9 bytes available in buffer
static char *format_scan_entry(char *buf, const char *ssid, size_t ssid_len, int8_t rssi) {
memcpy(buf, ssid, ssid_len);
buf += ssid_len;
*buf++ = ':';
*buf++ = ' ';
buf = int8_to_str(buf, rssi);
*buf++ = 'd';
*buf++ = 'B';
*buf++ = '\n';
return buf;
}
void ScanResultsWiFiInfo::on_wifi_scan_results(const wifi::wifi_scan_vector_t<wifi::WiFiScanResult> &results) {
std::string scan_results;
char buf[MAX_STATE_LENGTH + 1];
char *ptr = buf;
const char *end = buf + MAX_STATE_LENGTH;
for (const auto &scan : results) {
if (scan.get_is_hidden())
continue;
const std::string &ssid = scan.get_ssid();
// Max space: ssid + ": " (2) + "-128" (4) + "dB\n" (3) = ssid + 9
if (ptr + ssid.size() + 9 > end)
break;
ptr = format_scan_entry(ptr, ssid.c_str(), ssid.size(), scan.get_rssi());
}
scan_results += scan.get_ssid();
scan_results += ": ";
scan_results += esphome::to_string(scan.get_rssi());
scan_results += "dB\n";
}
// There's a limit of 255 characters per state; longer states just don't get sent so we truncate it
if (scan_results.length() > MAX_STATE_LENGTH) {
scan_results.resize(MAX_STATE_LENGTH);
}
this->publish_state(scan_results);
*ptr = '\0';
this->publish_state(buf);
}
/***************

View File

@@ -684,6 +684,30 @@ inline char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' +
/// This always uses uppercase (A-F) for pretty/human-readable output
inline char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; }
/// Write int8 value to buffer without modulo operations.
/// Buffer must have at least 4 bytes free. Returns pointer past last char written.
inline char *int8_to_str(char *buf, int8_t val) {
int32_t v = val;
if (v < 0) {
*buf++ = '-';
v = -v;
}
if (v >= 100) {
*buf++ = '1'; // int8 max is 128, so hundreds digit is always 1
v -= 100;
// Must write tens digit (even if 0) after hundreds
int32_t tens = v / 10;
*buf++ = '0' + tens;
v -= tens * 10;
} else if (v >= 10) {
int32_t tens = v / 10;
*buf++ = '0' + tens;
v -= tens * 10;
}
*buf++ = '0' + v;
return buf;
}
/// Format MAC address as XX:XX:XX:XX:XX:XX (uppercase)
inline void format_mac_addr_upper(const uint8_t *mac, char *output) {
for (size_t i = 0; i < 6; i++) {