mirror of
https://github.com/esphome/esphome.git
synced 2026-02-08 00:31:58 +00:00
Merge remote-tracking branch 'upstream/dev' into integration
This commit is contained in:
@@ -5,7 +5,7 @@ Constants already defined in esphome.const are not duplicated here and must be i
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
|
||||
from esphome import codegen as cg, config_validation as cv
|
||||
from esphome.const import CONF_ITEMS
|
||||
@@ -96,13 +96,9 @@ class LValidator:
|
||||
return None
|
||||
if isinstance(value, Lambda):
|
||||
# Local import to avoid circular import
|
||||
from .lvcode import CodeContext, LambdaContext
|
||||
from .lvcode import get_lambda_context_args
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# CodeContext does not have get_automation_parameters
|
||||
# so we need to assert the type here
|
||||
assert isinstance(CodeContext.code_context, LambdaContext)
|
||||
args = args or CodeContext.code_context.get_automation_parameters()
|
||||
args = args or get_lambda_context_args()
|
||||
return cg.RawExpression(
|
||||
call_lambda(
|
||||
await cg.process_lambda(value, args, return_type=self.rtype)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import image
|
||||
@@ -404,14 +404,9 @@ class TextValidator(LValidator):
|
||||
self, value: Any, args: list[tuple[SafeExpType, str]] | None = None
|
||||
) -> Expression:
|
||||
# Local import to avoid circular import at module level
|
||||
from .lvcode import get_lambda_context_args
|
||||
|
||||
from .lvcode import CodeContext, LambdaContext
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# CodeContext does not have get_automation_parameters
|
||||
# so we need to assert the type here
|
||||
assert isinstance(CodeContext.code_context, LambdaContext)
|
||||
args = args or CodeContext.code_context.get_automation_parameters()
|
||||
args = args or get_lambda_context_args()
|
||||
|
||||
if isinstance(value, dict):
|
||||
if format_str := value.get(CONF_FORMAT):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import abc
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from esphome import codegen as cg
|
||||
from esphome.config import Config
|
||||
@@ -200,6 +201,21 @@ class LvContext(LambdaContext):
|
||||
return self.add(*args)
|
||||
|
||||
|
||||
def get_lambda_context_args() -> list[tuple[SafeExpType, str]]:
|
||||
"""Get automation parameters from the current lambda context if available.
|
||||
|
||||
When called from outside LVGL's context (e.g., from interval),
|
||||
CodeContext.code_context will be None, so return empty args.
|
||||
"""
|
||||
if CodeContext.code_context is None:
|
||||
return []
|
||||
if TYPE_CHECKING:
|
||||
# CodeContext base class doesn't define get_automation_parameters(),
|
||||
# but LambdaContext and LvContext (the concrete implementations) do.
|
||||
assert isinstance(CodeContext.code_context, LambdaContext)
|
||||
return CodeContext.code_context.get_automation_parameters()
|
||||
|
||||
|
||||
class LocalVariable(MockObj):
|
||||
"""
|
||||
Create a local variable and enclose the code using it within a block.
|
||||
|
||||
@@ -49,7 +49,8 @@ void OneWireBus::search() {
|
||||
break;
|
||||
auto *address8 = reinterpret_cast<uint8_t *>(&address);
|
||||
if (crc8(address8, 7) != address8[7]) {
|
||||
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str());
|
||||
char hex_buf[17];
|
||||
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex_to(hex_buf, address));
|
||||
} else {
|
||||
this->devices_.push_back(address);
|
||||
}
|
||||
@@ -82,8 +83,9 @@ void OneWireBus::dump_devices_(const char *tag) {
|
||||
ESP_LOGW(tag, " Found no devices!");
|
||||
} else {
|
||||
ESP_LOGCONFIG(tag, " Found devices:");
|
||||
char hex_buf[17]; // uint64_t = 16 hex chars + null
|
||||
for (auto &address : this->devices_) {
|
||||
ESP_LOGCONFIG(tag, " 0x%s (%s)", format_hex(address).c_str(), LOG_STR_ARG(get_model_str(address & 0xff)));
|
||||
ESP_LOGCONFIG(tag, " 0x%s (%s)", format_hex_to(hex_buf, address), LOG_STR_ARG(get_model_str(address & 0xff)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,7 +527,9 @@ void SX126x::dump_config() {
|
||||
this->spreading_factor_, cr, this->preamble_size_);
|
||||
}
|
||||
if (!this->sync_value_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", format_hex(this->sync_value_).c_str());
|
||||
char hex_buf[17]; // 8 bytes max = 16 hex chars + null
|
||||
ESP_LOGCONFIG(TAG, " Sync Value: 0x%s",
|
||||
format_hex_to(hex_buf, this->sync_value_.data(), this->sync_value_.size()));
|
||||
}
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Configuring SX126x failed");
|
||||
|
||||
@@ -476,7 +476,9 @@ void SX127x::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " Payload Length: %" PRIu32, this->payload_length_);
|
||||
}
|
||||
if (!this->sync_value_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", format_hex(this->sync_value_).c_str());
|
||||
char hex_buf[17]; // 8 bytes max = 16 hex chars + null
|
||||
ESP_LOGCONFIG(TAG, " Sync Value: 0x%s",
|
||||
format_hex_to(hex_buf, this->sync_value_.data(), this->sync_value_.size()));
|
||||
}
|
||||
if (this->preamble_size_ > 0 || this->preamble_detect_ > 0) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
|
||||
@@ -899,12 +899,20 @@ void WiFiComponent::print_connect_params_() {
|
||||
ESP_LOGCONFIG(TAG, " Disabled");
|
||||
return;
|
||||
}
|
||||
// Use stack buffers for IP address formatting to avoid heap allocations
|
||||
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
for (auto &ip : wifi_sta_ip_addresses()) {
|
||||
if (ip.is_set()) {
|
||||
ESP_LOGCONFIG(TAG, " IP Address: %s", ip.str().c_str());
|
||||
ESP_LOGCONFIG(TAG, " IP Address: %s", ip.str_to(ip_buf));
|
||||
}
|
||||
}
|
||||
int8_t rssi = wifi_rssi();
|
||||
// Use stack buffers for SSID and all IP addresses to avoid heap allocations
|
||||
char ssid_buf[SSID_BUFFER_SIZE];
|
||||
char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" SSID: " LOG_SECRET("'%s'") "\n"
|
||||
" BSSID: " LOG_SECRET("%s") "\n"
|
||||
@@ -915,9 +923,9 @@ void WiFiComponent::print_connect_params_() {
|
||||
" Gateway: %s\n"
|
||||
" DNS1: %s\n"
|
||||
" DNS2: %s",
|
||||
wifi_ssid().c_str(), bssid_s, App.get_name().c_str(), rssi, LOG_STR_ARG(get_signal_bars(rssi)),
|
||||
get_wifi_channel(), wifi_subnet_mask_().str().c_str(), wifi_gateway_ip_().str().c_str(),
|
||||
wifi_dns_ip_(0).str().c_str(), wifi_dns_ip_(1).str().c_str());
|
||||
wifi_ssid_to(ssid_buf), bssid_s, App.get_name().c_str(), rssi, LOG_STR_ARG(get_signal_bars(rssi)),
|
||||
get_wifi_channel(), wifi_subnet_mask_().str_to(subnet_buf), wifi_gateway_ip_().str_to(gateway_buf),
|
||||
wifi_dns_ip_(0).str_to(dns1_buf), wifi_dns_ip_(1).str_to(dns2_buf));
|
||||
#ifdef ESPHOME_LOG_HAS_VERBOSE
|
||||
if (const WiFiAP *config = this->get_selected_sta_(); config && config->has_bssid()) {
|
||||
ESP_LOGV(TAG, " Priority: %d", this->get_sta_priority(config->get_bssid()));
|
||||
|
||||
@@ -61,6 +61,9 @@ namespace esphome::wifi {
|
||||
/// Sentinel value for RSSI when WiFi is not connected
|
||||
static constexpr int8_t WIFI_RSSI_DISCONNECTED = -127;
|
||||
|
||||
/// Buffer size for SSID (IEEE 802.11 max 32 bytes + null terminator)
|
||||
static constexpr size_t SSID_BUFFER_SIZE = 33;
|
||||
|
||||
struct SavedWifiSettings {
|
||||
char ssid[33];
|
||||
char password[65];
|
||||
@@ -408,6 +411,9 @@ class WiFiComponent : public Component {
|
||||
|
||||
network::IPAddresses wifi_sta_ip_addresses();
|
||||
std::string wifi_ssid();
|
||||
/// Write SSID to buffer without heap allocation.
|
||||
/// Returns pointer to buffer, or empty string if not connected.
|
||||
const char *wifi_ssid_to(std::span<char, SSID_BUFFER_SIZE> buffer);
|
||||
bssid_t wifi_bssid();
|
||||
|
||||
int8_t wifi_rssi();
|
||||
|
||||
@@ -913,6 +913,18 @@ bssid_t WiFiComponent::wifi_bssid() {
|
||||
return bssid;
|
||||
}
|
||||
std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
|
||||
const char *WiFiComponent::wifi_ssid_to(std::span<char, SSID_BUFFER_SIZE> buffer) {
|
||||
struct station_config conf {};
|
||||
if (!wifi_station_get_config(&conf)) {
|
||||
buffer[0] = '\0';
|
||||
return buffer.data();
|
||||
}
|
||||
// conf.ssid is uint8[32], not null-terminated if full
|
||||
size_t len = strnlen(reinterpret_cast<const char *>(conf.ssid), sizeof(conf.ssid));
|
||||
memcpy(buffer.data(), conf.ssid, len);
|
||||
buffer[len] = '\0';
|
||||
return buffer.data();
|
||||
}
|
||||
int8_t WiFiComponent::wifi_rssi() {
|
||||
if (WiFi.status() != WL_CONNECTED)
|
||||
return WIFI_RSSI_DISCONNECTED;
|
||||
|
||||
@@ -1086,6 +1086,19 @@ std::string WiFiComponent::wifi_ssid() {
|
||||
size_t len = strnlen(ssid_s, sizeof(info.ssid));
|
||||
return {ssid_s, len};
|
||||
}
|
||||
const char *WiFiComponent::wifi_ssid_to(std::span<char, SSID_BUFFER_SIZE> buffer) {
|
||||
wifi_ap_record_t info{};
|
||||
esp_err_t err = esp_wifi_sta_get_ap_info(&info);
|
||||
if (err != ESP_OK) {
|
||||
buffer[0] = '\0';
|
||||
return buffer.data();
|
||||
}
|
||||
// info.ssid is uint8[33], but only 32 bytes are SSID data
|
||||
size_t len = strnlen(reinterpret_cast<const char *>(info.ssid), 32);
|
||||
memcpy(buffer.data(), info.ssid, len);
|
||||
buffer[len] = '\0';
|
||||
return buffer.data();
|
||||
}
|
||||
int8_t WiFiComponent::wifi_rssi() {
|
||||
wifi_ap_record_t info;
|
||||
esp_err_t err = esp_wifi_sta_get_ap_info(&info);
|
||||
|
||||
@@ -554,6 +554,14 @@ bssid_t WiFiComponent::wifi_bssid() {
|
||||
return bssid;
|
||||
}
|
||||
std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
|
||||
const char *WiFiComponent::wifi_ssid_to(std::span<char, SSID_BUFFER_SIZE> buffer) {
|
||||
// TODO: Find direct LibreTiny API to avoid Arduino String allocation
|
||||
String ssid = WiFi.SSID();
|
||||
size_t len = std::min(static_cast<size_t>(ssid.length()), SSID_BUFFER_SIZE - 1);
|
||||
memcpy(buffer.data(), ssid.c_str(), len);
|
||||
buffer[len] = '\0';
|
||||
return buffer.data();
|
||||
}
|
||||
int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; }
|
||||
int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); }
|
||||
network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; }
|
||||
|
||||
@@ -214,6 +214,14 @@ bssid_t WiFiComponent::wifi_bssid() {
|
||||
return bssid;
|
||||
}
|
||||
std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
|
||||
const char *WiFiComponent::wifi_ssid_to(std::span<char, SSID_BUFFER_SIZE> buffer) {
|
||||
// TODO: Find direct CYW43 API to avoid Arduino String allocation
|
||||
String ssid = WiFi.SSID();
|
||||
size_t len = std::min(static_cast<size_t>(ssid.length()), SSID_BUFFER_SIZE - 1);
|
||||
memcpy(buffer.data(), ssid.c_str(), len);
|
||||
buffer[len] = '\0';
|
||||
return buffer.data();
|
||||
}
|
||||
int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; }
|
||||
int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); }
|
||||
|
||||
|
||||
@@ -297,6 +297,19 @@ std::string format_hex(const uint8_t *data, size_t length) {
|
||||
}
|
||||
std::string format_hex(const std::vector<uint8_t> &data) { return format_hex(data.data(), data.size()); }
|
||||
|
||||
char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) {
|
||||
size_t max_bytes = (buffer_size - 1) / 2;
|
||||
if (length > max_bytes) {
|
||||
length = max_bytes;
|
||||
}
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
buffer[2 * i] = format_hex_char(data[i] >> 4);
|
||||
buffer[2 * i + 1] = format_hex_char(data[i] & 0x0F);
|
||||
}
|
||||
buffer[length * 2] = '\0';
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Shared implementation for uint8_t and string hex formatting
|
||||
static std::string format_hex_pretty_uint8(const uint8_t *data, size_t length, char separator, bool show_length) {
|
||||
if (data == nullptr || length == 0)
|
||||
|
||||
@@ -747,6 +747,24 @@ inline void format_mac_addr_lower_no_sep(const uint8_t *mac, char *output) {
|
||||
output[12] = '\0';
|
||||
}
|
||||
|
||||
/// Format byte array as lowercase hex to buffer (base implementation).
|
||||
char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length);
|
||||
|
||||
/// Format byte array as lowercase hex to buffer. Automatically deduces buffer size.
|
||||
/// Truncates output if data exceeds buffer capacity. Returns pointer to buffer.
|
||||
template<size_t N> inline char *format_hex_to(char (&buffer)[N], const uint8_t *data, size_t length) {
|
||||
static_assert(N >= 3, "Buffer must hold at least one hex byte (3 chars)");
|
||||
return format_hex_to(buffer, N, data, length);
|
||||
}
|
||||
|
||||
/// Format an unsigned integer in lowercased hex to buffer, starting with the most significant byte.
|
||||
template<size_t N, typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0>
|
||||
inline char *format_hex_to(char (&buffer)[N], T val) {
|
||||
static_assert(N >= sizeof(T) * 2 + 1, "Buffer too small for type");
|
||||
val = convert_big_endian(val);
|
||||
return format_hex_to(buffer, reinterpret_cast<const uint8_t *>(&val), sizeof(T));
|
||||
}
|
||||
|
||||
/// Format the six-byte array \p mac into a MAC address.
|
||||
std::string format_mac_address_pretty(const uint8_t mac[6]);
|
||||
/// Format the byte array \p data of length \p len in lowercased hex.
|
||||
|
||||
Reference in New Issue
Block a user