1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-17 18:52:19 +01:00

Merge pull request #10725 from esphome/bump-2025.9.0b2

2025.9.0b2
This commit is contained in:
Jesse Hills
2025-09-15 18:28:51 +12:00
committed by GitHub
40 changed files with 2753 additions and 454 deletions

View File

@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 2025.9.0b1
PROJECT_NUMBER = 2025.9.0b2
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a

View File

@@ -15,9 +15,11 @@ import argcomplete
from esphome import const, writer, yaml_util
import esphome.codegen as cg
from esphome.components.mqtt import CONF_DISCOVER_IP
from esphome.config import iter_component_configs, read_config, strip_default_ids
from esphome.const import (
ALLOWED_NAME_CHARS,
CONF_API,
CONF_BAUD_RATE,
CONF_BROKER,
CONF_DEASSERT_RTS_DTR,
@@ -43,6 +45,7 @@ from esphome.const import (
SECRETS_FILES,
)
from esphome.core import CORE, EsphomeError, coroutine
from esphome.enum import StrEnum
from esphome.helpers import get_bool_env, indent, is_ip_address
from esphome.log import AnsiFore, color, setup_log
from esphome.types import ConfigType
@@ -106,13 +109,15 @@ def choose_prompt(options, purpose: str = None):
return options[opt - 1][1]
class Purpose(StrEnum):
UPLOADING = "uploading"
LOGGING = "logging"
def choose_upload_log_host(
default: list[str] | str | None,
check_default: str | None,
show_ota: bool,
show_mqtt: bool,
show_api: bool,
purpose: str | None = None,
purpose: Purpose,
) -> list[str]:
# Convert to list for uniform handling
defaults = [default] if isinstance(default, str) else default or []
@@ -132,13 +137,30 @@ def choose_upload_log_host(
]
resolved.append(choose_prompt(options, purpose=purpose))
elif device == "OTA":
if CORE.address and (
(show_ota and "ota" in CORE.config)
or (show_api and "api" in CORE.config)
# ensure IP adresses are used first
if is_ip_address(CORE.address) and (
(purpose == Purpose.LOGGING and has_api())
or (purpose == Purpose.UPLOADING and has_ota())
):
resolved.append(CORE.address)
elif show_mqtt and has_mqtt_logging():
resolved.append("MQTT")
if purpose == Purpose.LOGGING:
if has_api() and has_mqtt_ip_lookup():
resolved.append("MQTTIP")
if has_mqtt_logging():
resolved.append("MQTT")
if has_api() and has_non_ip_address():
resolved.append(CORE.address)
elif purpose == Purpose.UPLOADING:
if has_ota() and has_mqtt_ip_lookup():
resolved.append("MQTTIP")
if has_ota() and has_non_ip_address():
resolved.append(CORE.address)
else:
resolved.append(device)
if not resolved:
@@ -149,39 +171,111 @@ def choose_upload_log_host(
options = [
(f"{port.path} ({port.description})", port.path) for port in get_serial_ports()
]
if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
options.append((f"Over The Air ({CORE.address})", CORE.address))
if show_mqtt and has_mqtt_logging():
mqtt_config = CORE.config[CONF_MQTT]
options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
if purpose == Purpose.LOGGING:
if has_mqtt_logging():
mqtt_config = CORE.config[CONF_MQTT]
options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
if has_api():
if has_resolvable_address():
options.append((f"Over The Air ({CORE.address})", CORE.address))
if has_mqtt_ip_lookup():
options.append(("Over The Air (MQTT IP lookup)", "MQTTIP"))
elif purpose == Purpose.UPLOADING and has_ota():
if has_resolvable_address():
options.append((f"Over The Air ({CORE.address})", CORE.address))
if has_mqtt_ip_lookup():
options.append(("Over The Air (MQTT IP lookup)", "MQTTIP"))
if check_default is not None and check_default in [opt[1] for opt in options]:
return [check_default]
return [choose_prompt(options, purpose=purpose)]
def mqtt_logging_enabled(mqtt_config):
def has_mqtt_logging() -> bool:
"""Check if MQTT logging is available."""
if CONF_MQTT not in CORE.config:
return False
mqtt_config = CORE.config[CONF_MQTT]
# enabled by default
if CONF_LOG_TOPIC not in mqtt_config:
return True
log_topic = mqtt_config[CONF_LOG_TOPIC]
if log_topic is None:
return False
if CONF_TOPIC not in log_topic:
return False
return log_topic.get(CONF_LEVEL, None) != "NONE"
return log_topic[CONF_LEVEL] != "NONE"
def has_mqtt_logging() -> bool:
"""Check if MQTT logging is available."""
return (mqtt_config := CORE.config.get(CONF_MQTT)) and mqtt_logging_enabled(
mqtt_config
)
def has_mqtt() -> bool:
"""Check if MQTT is available."""
return CONF_MQTT in CORE.config
def has_api() -> bool:
"""Check if API is available."""
return CONF_API in CORE.config
def has_ota() -> bool:
"""Check if OTA is available."""
return CONF_OTA in CORE.config
def has_mqtt_ip_lookup() -> bool:
"""Check if MQTT is available and IP lookup is supported."""
if CONF_MQTT not in CORE.config:
return False
# Default Enabled
if CONF_DISCOVER_IP not in CORE.config[CONF_MQTT]:
return True
return CORE.config[CONF_MQTT][CONF_DISCOVER_IP]
def has_mdns() -> bool:
"""Check if MDNS is available."""
return CONF_MDNS not in CORE.config or not CORE.config[CONF_MDNS][CONF_DISABLED]
def has_non_ip_address() -> bool:
"""Check if CORE.address is set and is not an IP address."""
return CORE.address is not None and not is_ip_address(CORE.address)
def has_ip_address() -> bool:
"""Check if CORE.address is a valid IP address."""
return CORE.address is not None and is_ip_address(CORE.address)
def has_resolvable_address() -> bool:
"""Check if CORE.address is resolvable (via mDNS or is an IP address)."""
return has_mdns() or has_ip_address()
def mqtt_get_ip(config: ConfigType, username: str, password: str, client_id: str):
from esphome import mqtt
return mqtt.get_esphome_device_ip(config, username, password, client_id)
_PORT_TO_PORT_TYPE = {
"MQTT": "MQTT",
"MQTTIP": "MQTTIP",
}
def get_port_type(port: str) -> str:
if port.startswith("/") or port.startswith("COM"):
return "SERIAL"
if port == "MQTT":
return "MQTT"
return "NETWORK"
return _PORT_TO_PORT_TYPE.get(port, "NETWORK")
def run_miniterm(config: ConfigType, port: str, args) -> int:
@@ -226,7 +320,9 @@ def run_miniterm(config: ConfigType, port: str, args) -> int:
.replace(b"\n", b"")
.decode("utf8", "backslashreplace")
)
time_str = datetime.now().time().strftime("[%H:%M:%S]")
time_ = datetime.now()
nanoseconds = time_.microsecond // 1000
time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
safe_print(parser.parse_line(line, time_str))
backtrace_state = platformio_api.process_stacktrace(
@@ -437,23 +533,9 @@ def upload_program(
password = ota_conf.get(CONF_PASSWORD, "")
binary = args.file if getattr(args, "file", None) is not None else CORE.firmware_bin
# Check if we should use MQTT for address resolution
# This happens when no device was specified, or the current host is "MQTT"/"OTA"
if (
CONF_MQTT in config # pylint: disable=too-many-boolean-expressions
and (not devices or host in ("MQTT", "OTA"))
and (
((config[CONF_MDNS][CONF_DISABLED]) and not is_ip_address(CORE.address))
or get_port_type(host) == "MQTT"
)
):
from esphome import mqtt
devices = [
mqtt.get_esphome_device_ip(
config, args.username, args.password, args.client_id
)
]
# MQTT address resolution
if get_port_type(host) in ("MQTT", "MQTTIP"):
devices = mqtt_get_ip(config, args.username, args.password, args.client_id)
return espota2.run_ota(devices, remote_port, password, binary)
@@ -474,20 +556,28 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
if get_port_type(port) == "SERIAL":
check_permissions(port)
return run_miniterm(config, port, args)
if get_port_type(port) == "NETWORK" and "api" in config:
addresses_to_use = devices
if config[CONF_MDNS][CONF_DISABLED] and CONF_MQTT in config:
from esphome import mqtt
mqtt_address = mqtt.get_esphome_device_ip(
port_type = get_port_type(port)
# Check if we should use API for logging
if has_api():
addresses_to_use: list[str] | None = None
if port_type == "NETWORK" and (has_mdns() or is_ip_address(port)):
addresses_to_use = devices
elif port_type in ("NETWORK", "MQTT", "MQTTIP") and has_mqtt_ip_lookup():
# Only use MQTT IP lookup if the first condition didn't match
# (for MQTT/MQTTIP types, or for NETWORK when mdns/ip check fails)
addresses_to_use = mqtt_get_ip(
config, args.username, args.password, args.client_id
)[0]
addresses_to_use = [mqtt_address]
)
from esphome.components.api.client import run_logs
if addresses_to_use is not None:
from esphome.components.api.client import run_logs
return run_logs(config, addresses_to_use)
if get_port_type(port) in ("NETWORK", "MQTT") and "mqtt" in config:
return run_logs(config, addresses_to_use)
if port_type in ("NETWORK", "MQTT") and has_mqtt_logging():
from esphome import mqtt
return mqtt.show_logs(
@@ -553,10 +643,7 @@ def command_upload(args: ArgsProtocol, config: ConfigType) -> int | None:
devices = choose_upload_log_host(
default=args.device,
check_default=None,
show_ota=True,
show_mqtt=False,
show_api=False,
purpose="uploading",
purpose=Purpose.UPLOADING,
)
exit_code, _ = upload_program(config, args, devices)
@@ -581,10 +668,7 @@ def command_logs(args: ArgsProtocol, config: ConfigType) -> int | None:
devices = choose_upload_log_host(
default=args.device,
check_default=None,
show_ota=False,
show_mqtt=True,
show_api=True,
purpose="logging",
purpose=Purpose.LOGGING,
)
return show_logs(config, args, devices)
@@ -610,10 +694,7 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
devices = choose_upload_log_host(
default=args.device,
check_default=None,
show_ota=True,
show_mqtt=False,
show_api=True,
purpose="uploading",
purpose=Purpose.UPLOADING,
)
exit_code, successful_device = upload_program(config, args, devices)
@@ -630,10 +711,7 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
devices = choose_upload_log_host(
default=successful_device,
check_default=successful_device,
show_ota=False,
show_mqtt=True,
show_api=True,
purpose="logging",
purpose=Purpose.LOGGING,
)
return show_logs(config, args, devices)

View File

@@ -11,15 +11,8 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_ANALOG,
CONF_INPUT,
CONF_NUMBER,
PLATFORM_ESP8266,
PlatformFramework,
)
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
from esphome.core import CORE
CODEOWNERS = ["@esphome/core"]
@@ -273,21 +266,3 @@ def validate_adc_pin(value):
)(value)
raise NotImplementedError
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"adc_sensor_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"adc_sensor_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"adc_sensor_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
}
)

View File

@@ -9,6 +9,7 @@ from esphome.components.zephyr import (
zephyr_add_prj_conf,
zephyr_add_user,
)
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_ATTENUATION,
@@ -20,6 +21,7 @@ from esphome.const import (
PLATFORM_NRF52,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
PlatformFramework,
)
from esphome.core import CORE
@@ -174,3 +176,21 @@ async def to_code(config):
}};
"""
)
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"adc_sensor_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"adc_sensor_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"adc_sensor_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
}
)

View File

@@ -27,9 +27,6 @@ service APIConnection {
rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
rpc get_time (GetTimeRequest) returns (GetTimeResponse) {
option (needs_authentication) = false;
}
rpc execute_service (ExecuteServiceRequest) returns (void) {}
rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {}
@@ -809,12 +806,12 @@ message HomeAssistantStateResponse {
// ==================== IMPORT TIME ====================
message GetTimeRequest {
option (id) = 36;
option (source) = SOURCE_BOTH;
option (source) = SOURCE_SERVER;
}
message GetTimeResponse {
option (id) = 37;
option (source) = SOURCE_BOTH;
option (source) = SOURCE_CLIENT;
option (no_delay) = true;
fixed32 epoch_seconds = 1;

View File

@@ -42,6 +42,8 @@ static constexpr uint8_t MAX_PING_RETRIES = 60;
static constexpr uint16_t PING_RETRY_INTERVAL = 1000;
static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2;
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
static const char *const TAG = "api.connection";
#ifdef USE_CAMERA
static const int CAMERA_STOP_STREAM = 5000;
@@ -1081,12 +1083,6 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) {
}
#endif
bool APIConnection::send_get_time_response(const GetTimeRequest &msg) {
GetTimeResponse resp;
resp.epoch_seconds = ::time(nullptr);
return this->send_message(resp, GetTimeResponse::MESSAGE_TYPE);
}
#ifdef USE_BLUETOOTH_PROXY
void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags);
@@ -1376,9 +1372,8 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) {
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 12;
// Temporary string for concatenation - will be valid during send_message call
std::string server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
resp.set_server_info(StringRef(server_info));
// Send only the version string - the client only logs this for debugging and doesn't use it otherwise
resp.set_server_info(ESPHOME_VERSION_REF);
resp.set_name(StringRef(App.get_name()));
#ifdef USE_API_PASSWORD
@@ -1425,8 +1420,6 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
std::string mac_address = get_mac_address_pretty();
resp.set_mac_address(StringRef(mac_address));
// Compile-time StringRef constants
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
resp.set_esphome_version(ESPHOME_VERSION_REF);
resp.set_compilation_time(App.get_compilation_time_ref());

View File

@@ -219,7 +219,6 @@ class APIConnection final : public APIServerConnection {
#ifdef USE_API_HOMEASSISTANT_STATES
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
#endif
bool send_get_time_response(const GetTimeRequest &msg) override;
#ifdef USE_API_SERVICES
void execute_service(const ExecuteServiceRequest &msg) override;
#endif

View File

@@ -921,14 +921,6 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
}
return true;
}
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->epoch_seconds);
buffer.encode_string(2, this->timezone_ref_);
}
void GetTimeResponse::calculate_size(ProtoSize &size) const {
size.add_fixed32(1, this->epoch_seconds);
size.add_length(1, this->timezone_ref_.size());
}
#ifdef USE_API_SERVICES
void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->name_ref_);

View File

@@ -1180,10 +1180,6 @@ class GetTimeResponse final : public ProtoDecodableMessage {
#endif
uint32_t epoch_seconds{0};
std::string timezone{};
StringRef timezone_ref_{};
void set_timezone(const StringRef &ref) { this->timezone_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif

View File

@@ -1113,13 +1113,7 @@ void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeReques
void GetTimeResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "GetTimeResponse");
dump_field(out, "epoch_seconds", this->epoch_seconds);
out.append(" timezone: ");
if (!this->timezone_ref_.empty()) {
out.append("'").append(this->timezone_ref_.c_str()).append("'");
} else {
out.append("'").append(this->timezone).append("'");
}
out.append("\n");
dump_field(out, "timezone", this->timezone);
}
#ifdef USE_API_SERVICES
void ListEntitiesServicesArgument::dump_to(std::string &out) const {

View File

@@ -160,15 +160,6 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
break;
}
#endif
case GetTimeRequest::MESSAGE_TYPE: {
GetTimeRequest msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str());
#endif
this->on_get_time_request(msg);
break;
}
case GetTimeResponse::MESSAGE_TYPE: {
GetTimeResponse msg;
msg.decode(msg_data, msg_size);
@@ -656,11 +647,6 @@ void APIServerConnection::on_subscribe_home_assistant_states_request(const Subsc
}
}
#endif
void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
if (this->check_connection_setup_() && !this->send_get_time_response(msg)) {
this->on_fatal_error();
}
}
#ifdef USE_API_SERVICES
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) {
if (this->check_authenticated_()) {

View File

@@ -71,7 +71,7 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_API_HOMEASSISTANT_STATES
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
#endif
virtual void on_get_time_request(const GetTimeRequest &value){};
virtual void on_get_time_response(const GetTimeResponse &value){};
#ifdef USE_API_SERVICES
@@ -226,7 +226,6 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_API_HOMEASSISTANT_STATES
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
#endif
virtual bool send_get_time_response(const GetTimeRequest &msg) = 0;
#ifdef USE_API_SERVICES
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
#endif
@@ -348,7 +347,6 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_API_HOMEASSISTANT_STATES
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
#endif
void on_get_time_request(const GetTimeRequest &msg) override;
#ifdef USE_API_SERVICES
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
#endif

View File

@@ -62,9 +62,11 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
time_ = datetime.now()
message: bytes = msg.message
text = message.decode("utf8", "backslashreplace")
for parsed_msg in parse_log_message(
text, f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}]"
):
nanoseconds = time_.microsecond // 1000
timestamp = (
f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
)
for parsed_msg in parse_log_message(text, timestamp):
print(parsed_msg.replace("\033", "\\033") if dashboard else parsed_msg)
stop = await async_run(cli, on_log, name=name)

View File

@@ -130,7 +130,9 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ
std::string get_bluetooth_mac_address_pretty() {
const uint8_t *mac = esp_bt_dev_get_address();
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
char buf[18];
format_mac_addr_upper(mac, buf);
return std::string(buf);
}
protected:

View File

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

View File

@@ -7,6 +7,7 @@
#include <cstdio>
#include <cinttypes>
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome::esp32_ble {
@@ -169,22 +170,42 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const {
}
esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; }
std::string ESPBTUUID::to_string() const {
char buf[40]; // Enough for 128-bit UUID with dashes
char *pos = buf;
switch (this->uuid_.len) {
case ESP_UUID_LEN_16:
return str_snprintf("0x%02X%02X", 6, this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff);
*pos++ = '0';
*pos++ = 'x';
*pos++ = format_hex_pretty_char(this->uuid_.uuid.uuid16 >> 12);
*pos++ = format_hex_pretty_char((this->uuid_.uuid.uuid16 >> 8) & 0x0F);
*pos++ = format_hex_pretty_char((this->uuid_.uuid.uuid16 >> 4) & 0x0F);
*pos++ = format_hex_pretty_char(this->uuid_.uuid.uuid16 & 0x0F);
*pos = '\0';
return std::string(buf);
case ESP_UUID_LEN_32:
return str_snprintf("0x%02" PRIX32 "%02" PRIX32 "%02" PRIX32 "%02" PRIX32, 10, (this->uuid_.uuid.uuid32 >> 24),
(this->uuid_.uuid.uuid32 >> 16 & 0xff), (this->uuid_.uuid.uuid32 >> 8 & 0xff),
this->uuid_.uuid.uuid32 & 0xff);
*pos++ = '0';
*pos++ = 'x';
for (int shift = 28; shift >= 0; shift -= 4) {
*pos++ = format_hex_pretty_char((this->uuid_.uuid.uuid32 >> shift) & 0x0F);
}
*pos = '\0';
return std::string(buf);
default:
case ESP_UUID_LEN_128:
std::string buf;
// Format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
for (int8_t i = 15; i >= 0; i--) {
buf += str_snprintf("%02X", 2, this->uuid_.uuid.uuid128[i]);
if (i == 6 || i == 8 || i == 10 || i == 12)
buf += "-";
uint8_t byte = this->uuid_.uuid.uuid128[i];
*pos++ = format_hex_pretty_char(byte >> 4);
*pos++ = format_hex_pretty_char(byte & 0x0F);
if (i == 12 || i == 10 || i == 8 || i == 6) {
*pos++ = '-';
}
}
return buf;
*pos = '\0';
return std::string(buf);
}
return "";
}

View File

@@ -31,12 +31,13 @@ void ESP32BLEBeacon::dump_config() {
char uuid[37];
char *bpos = uuid;
for (int8_t ii = 0; ii < 16; ++ii) {
bpos += sprintf(bpos, "%02X", this->uuid_[ii]);
*bpos++ = format_hex_pretty_char(this->uuid_[ii] >> 4);
*bpos++ = format_hex_pretty_char(this->uuid_[ii] & 0x0F);
if (ii == 3 || ii == 5 || ii == 7 || ii == 9) {
bpos += sprintf(bpos, "-");
*bpos++ = '-';
}
}
uuid[36] = '\0';
*bpos = '\0';
ESP_LOGCONFIG(TAG,
" UUID: %s, Major: %u, Minor: %u, Min Interval: %ums, Max Interval: %ums, Measured Power: %d"
", TX Power: %ddBm",

View File

@@ -60,11 +60,14 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
if (address == 0) {
this->address_str_ = "";
} else {
this->address_str_ =
str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, (uint8_t) (this->address_ >> 40) & 0xff,
(uint8_t) (this->address_ >> 32) & 0xff, (uint8_t) (this->address_ >> 24) & 0xff,
(uint8_t) (this->address_ >> 16) & 0xff, (uint8_t) (this->address_ >> 8) & 0xff,
(uint8_t) (this->address_ >> 0) & 0xff);
char buf[18];
uint8_t mac[6] = {
(uint8_t) ((this->address_ >> 40) & 0xff), (uint8_t) ((this->address_ >> 32) & 0xff),
(uint8_t) ((this->address_ >> 24) & 0xff), (uint8_t) ((this->address_ >> 16) & 0xff),
(uint8_t) ((this->address_ >> 8) & 0xff), (uint8_t) ((this->address_ >> 0) & 0xff),
};
format_mac_addr_upper(mac, buf);
this->address_str_ = buf;
}
}
const std::string &address_str() const { return this->address_str_; }

View File

@@ -607,9 +607,8 @@ void ESPBTDevice::parse_adv_(const uint8_t *payload, uint8_t len) {
}
std::string ESPBTDevice::address_str() const {
char mac[24];
snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X", this->address_[0], this->address_[1], this->address_[2],
this->address_[3], this->address_[4], this->address_[5]);
char mac[18];
format_mac_addr_upper(this->address_, mac);
return mac;
}

View File

@@ -300,6 +300,7 @@ void EthernetComponent::loop() {
this->state_ = EthernetComponentState::CONNECTING;
this->start_connect_();
} else {
this->finish_connect_();
// When connected and stable, disable the loop to save CPU cycles
this->disable_loop();
}
@@ -486,10 +487,35 @@ void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_
}
#endif /* USE_NETWORK_IPV6 */
void EthernetComponent::finish_connect_() {
#if USE_NETWORK_IPV6
// Retry IPv6 link-local setup if it failed during initial connect
// This handles the case where min_ipv6_addr_count is NOT set (or is 0),
// allowing us to reach CONNECTED state with just IPv4.
// If IPv6 setup failed in start_connect_() because the interface wasn't ready:
// - Bootup timing issues (#10281)
// - Cable unplugged/network interruption (#10705)
// We can now retry since we're in CONNECTED state and the interface is definitely up.
if (!this->ipv6_setup_done_) {
esp_err_t err = esp_netif_create_ip6_linklocal(this->eth_netif_);
if (err == ESP_OK) {
ESP_LOGD(TAG, "IPv6 link-local address created (retry succeeded)");
}
// Always set the flag to prevent continuous retries
// If IPv6 setup fails here with the interface up and stable, it's
// likely a persistent issue (IPv6 disabled at router, hardware
// limitation, etc.) that won't be resolved by further retries.
// The device continues to work with IPv4.
this->ipv6_setup_done_ = true;
}
#endif /* USE_NETWORK_IPV6 */
}
void EthernetComponent::start_connect_() {
global_eth_component->got_ipv4_address_ = false;
#if USE_NETWORK_IPV6
global_eth_component->ipv6_count_ = 0;
this->ipv6_setup_done_ = false;
#endif /* USE_NETWORK_IPV6 */
this->connect_begin_ = millis();
this->status_set_warning(LOG_STR("waiting for IP configuration"));
@@ -545,9 +571,27 @@ void EthernetComponent::start_connect_() {
}
}
#if USE_NETWORK_IPV6
// Attempt to create IPv6 link-local address
// We MUST attempt this here, not just in finish_connect_(), because with
// min_ipv6_addr_count set, the component won't reach CONNECTED state without IPv6.
// However, this may fail with ESP_FAIL if the interface is not up yet:
// - At bootup when link isn't ready (#10281)
// - After disconnection/cable unplugged (#10705)
// We'll retry in finish_connect_() if it fails here.
err = esp_netif_create_ip6_linklocal(this->eth_netif_);
if (err != ESP_OK) {
ESPHL_ERROR_CHECK(err, "Enable IPv6 link local failed");
if (err == ESP_ERR_ESP_NETIF_INVALID_PARAMS) {
// This is a programming error, not a transient failure
ESPHL_ERROR_CHECK(err, "esp_netif_create_ip6_linklocal invalid parameters");
} else {
// ESP_FAIL means the interface isn't up yet
// This is expected and non-fatal, happens in multiple scenarios:
// - During reconnection after network interruptions (#10705)
// - At bootup when the link isn't ready yet (#10281)
// We'll retry once we reach CONNECTED state and the interface is up
ESP_LOGW(TAG, "esp_netif_create_ip6_linklocal failed: %s", esp_err_to_name(err));
// Don't mark component as failed - this is a transient error
}
}
#endif /* USE_NETWORK_IPV6 */
@@ -638,7 +682,9 @@ void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) {
std::string EthernetComponent::get_eth_mac_address_pretty() {
uint8_t mac[6];
get_eth_mac_address_raw(mac);
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
char buf[18];
format_mac_addr_upper(mac, buf);
return std::string(buf);
}
eth_duplex_t EthernetComponent::get_duplex_mode() {

View File

@@ -102,6 +102,7 @@ class EthernetComponent : public Component {
#endif /* LWIP_IPV6 */
void start_connect_();
void finish_connect_();
void dump_connect_params_();
/// @brief Set `RMII Reference Clock Select` bit for KSZ8081.
void ksz8081_set_clock_reference_(esp_eth_mac_t *mac);
@@ -144,6 +145,7 @@ class EthernetComponent : public Component {
bool got_ipv4_address_{false};
#if LWIP_IPV6
uint8_t ipv6_count_{0};
bool ipv6_setup_done_{false};
#endif /* LWIP_IPV6 */
// Pointers at the end (naturally aligned)

View File

@@ -1,7 +1,13 @@
#include "factory_reset_button.h"
#include "esphome/core/defines.h"
#ifdef USE_OPENTHREAD
#include "esphome/components/openthread/openthread.h"
#endif
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace factory_reset {
@@ -13,9 +19,20 @@ void FactoryResetButton::press_action() {
ESP_LOGI(TAG, "Resetting");
// Let MQTT settle a bit
delay(100); // NOLINT
#ifdef USE_OPENTHREAD
openthread::global_openthread_component->on_factory_reset(FactoryResetButton::factory_reset_callback);
#else
global_preferences->reset();
App.safe_reboot();
#endif
}
#ifdef USE_OPENTHREAD
void FactoryResetButton::factory_reset_callback() {
global_preferences->reset();
App.safe_reboot();
}
#endif
} // namespace factory_reset
} // namespace esphome

View File

@@ -1,7 +1,9 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/components/button/button.h"
#include "esphome/core/component.h"
namespace esphome {
namespace factory_reset {
@@ -9,6 +11,9 @@ namespace factory_reset {
class FactoryResetButton : public button::Button, public Component {
public:
void dump_config() override;
#ifdef USE_OPENTHREAD
static void factory_reset_callback();
#endif
protected:
void press_action() override;

View File

@@ -1,7 +1,13 @@
#include "factory_reset_switch.h"
#include "esphome/core/defines.h"
#ifdef USE_OPENTHREAD
#include "esphome/components/openthread/openthread.h"
#endif
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace factory_reset {
@@ -17,10 +23,21 @@ void FactoryResetSwitch::write_state(bool state) {
ESP_LOGI(TAG, "Resetting");
// Let MQTT settle a bit
delay(100); // NOLINT
#ifdef USE_OPENTHREAD
openthread::global_openthread_component->on_factory_reset(FactoryResetSwitch::factory_reset_callback);
#else
global_preferences->reset();
App.safe_reboot();
#endif
}
}
#ifdef USE_OPENTHREAD
void FactoryResetSwitch::factory_reset_callback() {
global_preferences->reset();
App.safe_reboot();
}
#endif
} // namespace factory_reset
} // namespace esphome

View File

@@ -1,7 +1,8 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/switch/switch.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
namespace esphome {
namespace factory_reset {
@@ -9,6 +10,9 @@ namespace factory_reset {
class FactoryResetSwitch : public switch_::Switch, public Component {
public:
void dump_config() override;
#ifdef USE_OPENTHREAD
static void factory_reset_callback();
#endif
protected:
void write_state(bool state) override;

View File

@@ -18,6 +18,7 @@ from esphome.const import (
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE,
UNIT_CELSIUS,
UNIT_VOLT,
@@ -162,7 +163,7 @@ INA2XX_SCHEMA = cv.Schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=8,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_MEASUREMENT,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
key=CONF_NAME,
),
@@ -170,7 +171,8 @@ INA2XX_SCHEMA = cv.Schema(
sensor.sensor_schema(
unit_of_measurement=UNIT_JOULE,
accuracy_decimals=8,
state_class=STATE_CLASS_MEASUREMENT,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
key=CONF_NAME,
),

View File

@@ -1,4 +1,3 @@
#include <cstdio>
#include <cstring>
#include "md5.h"
#ifdef USE_MD5
@@ -44,7 +43,9 @@ void MD5Digest::get_bytes(uint8_t *output) { memcpy(output, this->digest_, 16);
void MD5Digest::get_hex(char *output) {
for (size_t i = 0; i < 16; i++) {
sprintf(output + i * 2, "%02x", this->digest_[i]);
uint8_t byte = this->digest_[i];
output[i * 2] = format_hex_char(byte >> 4);
output[i * 2 + 1] = format_hex_char(byte & 0x0F);
}
}

View File

@@ -11,8 +11,6 @@
#include <openthread/instance.h>
#include <openthread/logging.h>
#include <openthread/netdata.h>
#include <openthread/srp_client.h>
#include <openthread/srp_client_buffers.h>
#include <openthread/tasklet.h>
#include <cstring>
@@ -77,8 +75,14 @@ std::optional<otIp6Address> OpenThreadComponent::get_omr_address_(InstanceLock &
return {};
}
void srp_callback(otError err, const otSrpClientHostInfo *host_info, const otSrpClientService *services,
const otSrpClientService *removed_services, void *context) {
void OpenThreadComponent::defer_factory_reset_external_callback() {
ESP_LOGD(TAG, "Defer factory_reset_external_callback_");
this->defer([this]() { this->factory_reset_external_callback_(); });
}
void OpenThreadSrpComponent::srp_callback(otError err, const otSrpClientHostInfo *host_info,
const otSrpClientService *services,
const otSrpClientService *removed_services, void *context) {
if (err != 0) {
ESP_LOGW(TAG, "SRP client reported an error: %s", otThreadErrorToString(err));
for (const otSrpClientHostInfo *host = host_info; host; host = nullptr) {
@@ -90,16 +94,30 @@ void srp_callback(otError err, const otSrpClientHostInfo *host_info, const otSrp
}
}
void srp_start_callback(const otSockAddr *server_socket_address, void *context) {
void OpenThreadSrpComponent::srp_start_callback(const otSockAddr *server_socket_address, void *context) {
ESP_LOGI(TAG, "SRP client has started");
}
void OpenThreadSrpComponent::srp_factory_reset_callback(otError err, const otSrpClientHostInfo *host_info,
const otSrpClientService *services,
const otSrpClientService *removed_services, void *context) {
OpenThreadComponent *obj = (OpenThreadComponent *) context;
if (err == OT_ERROR_NONE && removed_services != NULL && host_info != NULL &&
host_info->mState == OT_SRP_CLIENT_ITEM_STATE_REMOVED) {
ESP_LOGD(TAG, "Successful Removal SRP Host and Services");
} else if (err != OT_ERROR_NONE) {
// Handle other SRP client events or errors
ESP_LOGW(TAG, "SRP client event/error: %s", otThreadErrorToString(err));
}
obj->defer_factory_reset_external_callback();
}
void OpenThreadSrpComponent::setup() {
otError error;
InstanceLock lock = InstanceLock::acquire();
otInstance *instance = lock.get_instance();
otSrpClientSetCallback(instance, srp_callback, nullptr);
otSrpClientSetCallback(instance, OpenThreadSrpComponent::srp_callback, nullptr);
// set the host name
uint16_t size;
@@ -179,7 +197,8 @@ void OpenThreadSrpComponent::setup() {
ESP_LOGD(TAG, "Added service: %s", full_service.c_str());
}
otSrpClientEnableAutoStartMode(instance, srp_start_callback, nullptr);
otSrpClientEnableAutoStartMode(instance, OpenThreadSrpComponent::srp_start_callback, nullptr);
ESP_LOGD(TAG, "Finished SRP setup");
}
void *OpenThreadSrpComponent::pool_alloc_(size_t size) {
@@ -217,6 +236,21 @@ bool OpenThreadComponent::teardown() {
return this->teardown_complete_;
}
void OpenThreadComponent::on_factory_reset(std::function<void()> callback) {
factory_reset_external_callback_ = callback;
ESP_LOGD(TAG, "Start Removal SRP Host and Services");
otError error;
InstanceLock lock = InstanceLock::acquire();
otInstance *instance = lock.get_instance();
otSrpClientSetCallback(instance, OpenThreadSrpComponent::srp_factory_reset_callback, this);
error = otSrpClientRemoveHostAndServices(instance, true, true);
if (error != OT_ERROR_NONE) {
ESP_LOGW(TAG, "Failed to Remove SRP Host and Services");
return;
}
ESP_LOGD(TAG, "Waiting on Confirmation Removal SRP Host and Services");
}
} // namespace openthread
} // namespace esphome

View File

@@ -6,6 +6,8 @@
#include "esphome/components/network/ip_address.h"
#include "esphome/core/component.h"
#include <openthread/srp_client.h>
#include <openthread/srp_client_buffers.h>
#include <openthread/thread.h>
#include <optional>
@@ -28,11 +30,14 @@ class OpenThreadComponent : public Component {
network::IPAddresses get_ip_addresses();
std::optional<otIp6Address> get_omr_address();
void ot_main();
void on_factory_reset(std::function<void()> callback);
void defer_factory_reset_external_callback();
protected:
std::optional<otIp6Address> get_omr_address_(InstanceLock &lock);
bool teardown_started_{false};
bool teardown_complete_{false};
std::function<void()> factory_reset_external_callback_;
};
extern OpenThreadComponent *global_openthread_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
@@ -43,6 +48,12 @@ class OpenThreadSrpComponent : public Component {
// This has to run after the mdns component or else no services are available to advertise
float get_setup_priority() const override { return this->mdns_->get_setup_priority() - 1.0; }
void setup() override;
static void srp_callback(otError err, const otSrpClientHostInfo *host_info, const otSrpClientService *services,
const otSrpClientService *removed_services, void *context);
static void srp_start_callback(const otSockAddr *server_socket_address, void *context);
static void srp_factory_reset_callback(otError err, const otSrpClientHostInfo *host_info,
const otSrpClientService *services, const otSrpClientService *removed_services,
void *context);
protected:
esphome::mdns::MDNSComponent *mdns_{nullptr};

View File

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

View File

@@ -593,7 +593,7 @@ void WiFiComponent::check_scanning_finished() {
for (auto &res : this->scan_result_) {
char bssid_s[18];
auto bssid = res.get_bssid();
sprintf(bssid_s, "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
format_mac_addr_upper(bssid.data(), bssid_s);
if (res.get_matches()) {
ESP_LOGI(TAG, "- '%s' %s" LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(),

View File

@@ -1,6 +1,7 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/wifi/wifi_component.h"
#ifdef USE_WIFI
@@ -106,8 +107,8 @@ class BSSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
wifi::bssid_t bssid = wifi::global_wifi_component->wifi_bssid();
if (memcmp(bssid.data(), last_bssid_.data(), 6) != 0) {
std::copy(bssid.begin(), bssid.end(), last_bssid_.begin());
char buf[30];
sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
char buf[18];
format_mac_addr_upper(bssid.data(), buf);
this->publish_state(buf);
}
}

View File

@@ -4,7 +4,7 @@ from enum import Enum
from esphome.enum import StrEnum
__version__ = "2025.9.0b1"
__version__ = "2025.9.0b2"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = (
@@ -114,6 +114,7 @@ CONF_AND = "and"
CONF_ANGLE = "angle"
CONF_ANY = "any"
CONF_AP = "ap"
CONF_API = "api"
CONF_APPARENT_POWER = "apparent_power"
CONF_ARDUINO_VERSION = "arduino_version"
CONF_AREA = "area"

View File

@@ -255,23 +255,22 @@ size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) {
}
std::string format_mac_address_pretty(const uint8_t *mac) {
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
char buf[18];
format_mac_addr_upper(mac, buf);
return std::string(buf);
}
static char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; }
std::string format_hex(const uint8_t *data, size_t length) {
std::string ret;
ret.resize(length * 2);
for (size_t i = 0; i < length; i++) {
ret[2 * i] = format_hex_char((data[i] & 0xF0) >> 4);
ret[2 * i] = format_hex_char(data[i] >> 4);
ret[2 * i + 1] = format_hex_char(data[i] & 0x0F);
}
return ret;
}
std::string format_hex(const std::vector<uint8_t> &data) { return format_hex(data.data(), data.size()); }
static char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; }
// 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)
@@ -280,7 +279,7 @@ static std::string format_hex_pretty_uint8(const uint8_t *data, size_t length, c
uint8_t multiple = separator ? 3 : 2; // 3 if separator is not \0, 2 otherwise
ret.resize(multiple * length - (separator ? 1 : 0));
for (size_t i = 0; i < length; i++) {
ret[multiple * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4);
ret[multiple * i] = format_hex_pretty_char(data[i] >> 4);
ret[multiple * i + 1] = format_hex_pretty_char(data[i] & 0x0F);
if (separator && i != length - 1)
ret[multiple * i + 2] = separator;
@@ -591,7 +590,9 @@ bool HighFrequencyLoopRequester::is_high_frequency() { return num_requests > 0;
std::string get_mac_address() {
uint8_t mac[6];
get_mac_address_raw(mac);
return str_snprintf("%02x%02x%02x%02x%02x%02x", 12, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
char buf[13];
format_mac_addr_lower_no_sep(mac, buf);
return std::string(buf);
}
std::string get_mac_address_pretty() {

View File

@@ -380,6 +380,35 @@ template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> optional<
return parse_hex<T>(str.c_str(), str.length());
}
/// Convert a nibble (0-15) to lowercase hex char
inline char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; }
/// Convert a nibble (0-15) to uppercase hex char (used for pretty printing)
/// 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; }
/// 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++) {
uint8_t byte = mac[i];
output[i * 3] = format_hex_pretty_char(byte >> 4);
output[i * 3 + 1] = format_hex_pretty_char(byte & 0x0F);
if (i < 5)
output[i * 3 + 2] = ':';
}
output[17] = '\0';
}
/// Format MAC address as xxxxxxxxxxxxxx (lowercase, no separators)
inline void format_mac_addr_lower_no_sep(const uint8_t *mac, char *output) {
for (size_t i = 0; i < 6; i++) {
uint8_t byte = mac[i];
output[i * 2] = format_hex_char(byte >> 4);
output[i * 2 + 1] = format_hex_char(byte & 0x0F);
}
output[12] = '\0';
}
/// 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.

View File

@@ -345,7 +345,7 @@ void HOT Scheduler::call(uint32_t now) {
// Execute callback without holding lock to prevent deadlocks
// if the callback tries to call defer() again
if (!this->should_skip_item_(item.get())) {
this->execute_item_(item.get(), now);
now = this->execute_item_(item.get(), now);
}
// Recycle the defer item after execution
this->recycle_item_(std::move(item));
@@ -483,7 +483,7 @@ void HOT Scheduler::call(uint32_t now) {
// Warning: During callback(), a lot of stuff can happen, including:
// - timeouts/intervals get added, potentially invalidating vector pointers
// - timeouts/intervals get cancelled
this->execute_item_(item.get(), now);
now = this->execute_item_(item.get(), now);
LockGuard guard{this->lock_};
@@ -568,11 +568,11 @@ void HOT Scheduler::pop_raw_() {
}
// Helper to execute a scheduler item
void HOT Scheduler::execute_item_(SchedulerItem *item, uint32_t now) {
uint32_t HOT Scheduler::execute_item_(SchedulerItem *item, uint32_t now) {
App.set_current_component(item->component);
WarnIfComponentBlockingGuard guard{item->component, now};
item->callback();
guard.finish();
return guard.finish();
}
// Common implementation for cancel operations

View File

@@ -254,7 +254,7 @@ class Scheduler {
}
// Helper to execute a scheduler item
void execute_item_(SchedulerItem *item, uint32_t now);
uint32_t execute_item_(SchedulerItem *item, uint32_t now);
// Helper to check if item should be skipped
bool should_skip_item_(SchedulerItem *item) const {

View File

@@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
esptool==5.0.2
click==8.1.7
esphome-dashboard==20250904.0
aioesphomeapi==40.1.0
aioesphomeapi==40.2.1
zeroconf==0.147.2
puremagic==1.30
ruamel.yaml==0.18.15 # dashboard_import

View File

@@ -1,13 +1,16 @@
from __future__ import annotations
import asyncio
from collections.abc import Generator
import gzip
import json
import os
from unittest.mock import Mock
from pathlib import Path
from unittest.mock import MagicMock, Mock, patch
import pytest
import pytest_asyncio
from tornado.httpclient import AsyncHTTPClient, HTTPResponse
from tornado.httpclient import AsyncHTTPClient, HTTPClientError, HTTPResponse
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.testing import bind_unused_port
@@ -34,6 +37,66 @@ class DashboardTestHelper:
return await future
@pytest.fixture
def mock_async_run_system_command() -> Generator[MagicMock]:
"""Fixture to mock async_run_system_command."""
with patch("esphome.dashboard.web_server.async_run_system_command") as mock:
yield mock
@pytest.fixture
def mock_trash_storage_path(tmp_path: Path) -> Generator[MagicMock]:
"""Fixture to mock trash_storage_path."""
trash_dir = tmp_path / "trash"
with patch(
"esphome.dashboard.web_server.trash_storage_path", return_value=str(trash_dir)
) as mock:
yield mock
@pytest.fixture
def mock_archive_storage_path(tmp_path: Path) -> Generator[MagicMock]:
"""Fixture to mock archive_storage_path."""
archive_dir = tmp_path / "archive"
with patch(
"esphome.dashboard.web_server.archive_storage_path",
return_value=str(archive_dir),
) as mock:
yield mock
@pytest.fixture
def mock_dashboard_settings() -> Generator[MagicMock]:
"""Fixture to mock dashboard settings."""
with patch("esphome.dashboard.web_server.settings") as mock_settings:
# Set default auth settings to avoid authentication issues
mock_settings.using_auth = False
mock_settings.on_ha_addon = False
yield mock_settings
@pytest.fixture
def mock_ext_storage_path(tmp_path: Path) -> Generator[MagicMock]:
"""Fixture to mock ext_storage_path."""
with patch("esphome.dashboard.web_server.ext_storage_path") as mock:
mock.return_value = str(tmp_path / "storage.json")
yield mock
@pytest.fixture
def mock_storage_json() -> Generator[MagicMock]:
"""Fixture to mock StorageJSON."""
with patch("esphome.dashboard.web_server.StorageJSON") as mock:
yield mock
@pytest.fixture
def mock_idedata() -> Generator[MagicMock]:
"""Fixture to mock platformio_api.IDEData."""
with patch("esphome.dashboard.web_server.platformio_api.IDEData") as mock:
yield mock
@pytest_asyncio.fixture()
async def dashboard() -> DashboardTestHelper:
sock, port = bind_unused_port()
@@ -80,3 +143,499 @@ async def test_devices_page(dashboard: DashboardTestHelper) -> None:
first_device = configured_devices[0]
assert first_device["name"] == "pico"
assert first_device["configuration"] == "pico.yaml"
@pytest.mark.asyncio
async def test_wizard_handler_invalid_input(dashboard: DashboardTestHelper) -> None:
"""Test the WizardRequestHandler.post method with invalid inputs."""
# Test with missing name (should fail with 422)
body_no_name = json.dumps(
{
"name": "", # Empty name
"platform": "ESP32",
"board": "esp32dev",
}
)
with pytest.raises(HTTPClientError) as exc_info:
await dashboard.fetch(
"/wizard",
method="POST",
body=body_no_name,
headers={"Content-Type": "application/json"},
)
assert exc_info.value.code == 422
# Test with invalid wizard type (should fail with 422)
body_invalid_type = json.dumps(
{
"name": "test_device",
"type": "invalid_type",
"platform": "ESP32",
"board": "esp32dev",
}
)
with pytest.raises(HTTPClientError) as exc_info:
await dashboard.fetch(
"/wizard",
method="POST",
body=body_invalid_type,
headers={"Content-Type": "application/json"},
)
assert exc_info.value.code == 422
@pytest.mark.asyncio
async def test_wizard_handler_conflict(dashboard: DashboardTestHelper) -> None:
"""Test the WizardRequestHandler.post when config already exists."""
# Try to create a wizard for existing pico.yaml (should conflict)
body = json.dumps(
{
"name": "pico", # This already exists in fixtures
"platform": "ESP32",
"board": "esp32dev",
}
)
with pytest.raises(HTTPClientError) as exc_info:
await dashboard.fetch(
"/wizard",
method="POST",
body=body,
headers={"Content-Type": "application/json"},
)
assert exc_info.value.code == 409
@pytest.mark.asyncio
async def test_download_binary_handler_not_found(
dashboard: DashboardTestHelper,
) -> None:
"""Test the DownloadBinaryRequestHandler.get with non-existent config."""
with pytest.raises(HTTPClientError) as exc_info:
await dashboard.fetch(
"/download.bin?configuration=nonexistent.yaml",
method="GET",
)
assert exc_info.value.code == 404
@pytest.mark.asyncio
@pytest.mark.usefixtures("mock_ext_storage_path")
async def test_download_binary_handler_no_file_param(
dashboard: DashboardTestHelper,
tmp_path: Path,
mock_storage_json: MagicMock,
) -> None:
"""Test the DownloadBinaryRequestHandler.get without file parameter."""
# Mock storage to exist, but still should fail without file param
mock_storage = Mock()
mock_storage.name = "test_device"
mock_storage.firmware_bin_path = str(tmp_path / "firmware.bin")
mock_storage_json.load.return_value = mock_storage
with pytest.raises(HTTPClientError) as exc_info:
await dashboard.fetch(
"/download.bin?configuration=pico.yaml",
method="GET",
)
assert exc_info.value.code == 400
@pytest.mark.asyncio
@pytest.mark.usefixtures("mock_ext_storage_path")
async def test_download_binary_handler_with_file(
dashboard: DashboardTestHelper,
tmp_path: Path,
mock_storage_json: MagicMock,
) -> None:
"""Test the DownloadBinaryRequestHandler.get with existing binary file."""
# Create a fake binary file
build_dir = tmp_path / ".esphome" / "build" / "test"
build_dir.mkdir(parents=True)
firmware_file = build_dir / "firmware.bin"
firmware_file.write_bytes(b"fake firmware content")
# Mock storage JSON
mock_storage = Mock()
mock_storage.name = "test_device"
mock_storage.firmware_bin_path = str(firmware_file)
mock_storage_json.load.return_value = mock_storage
response = await dashboard.fetch(
"/download.bin?configuration=test.yaml&file=firmware.bin",
method="GET",
)
assert response.code == 200
assert response.body == b"fake firmware content"
assert response.headers["Content-Type"] == "application/octet-stream"
assert "attachment" in response.headers["Content-Disposition"]
assert "test_device-firmware.bin" in response.headers["Content-Disposition"]
@pytest.mark.asyncio
@pytest.mark.usefixtures("mock_ext_storage_path")
async def test_download_binary_handler_compressed(
dashboard: DashboardTestHelper,
tmp_path: Path,
mock_storage_json: MagicMock,
) -> None:
"""Test the DownloadBinaryRequestHandler.get with compression."""
# Create a fake binary file
build_dir = tmp_path / ".esphome" / "build" / "test"
build_dir.mkdir(parents=True)
firmware_file = build_dir / "firmware.bin"
original_content = b"fake firmware content for compression test"
firmware_file.write_bytes(original_content)
# Mock storage JSON
mock_storage = Mock()
mock_storage.name = "test_device"
mock_storage.firmware_bin_path = str(firmware_file)
mock_storage_json.load.return_value = mock_storage
response = await dashboard.fetch(
"/download.bin?configuration=test.yaml&file=firmware.bin&compressed=1",
method="GET",
)
assert response.code == 200
# Decompress and verify content
decompressed = gzip.decompress(response.body)
assert decompressed == original_content
assert response.headers["Content-Type"] == "application/octet-stream"
assert "firmware.bin.gz" in response.headers["Content-Disposition"]
@pytest.mark.asyncio
@pytest.mark.usefixtures("mock_ext_storage_path")
async def test_download_binary_handler_custom_download_name(
dashboard: DashboardTestHelper,
tmp_path: Path,
mock_storage_json: MagicMock,
) -> None:
"""Test the DownloadBinaryRequestHandler.get with custom download name."""
# Create a fake binary file
build_dir = tmp_path / ".esphome" / "build" / "test"
build_dir.mkdir(parents=True)
firmware_file = build_dir / "firmware.bin"
firmware_file.write_bytes(b"content")
# Mock storage JSON
mock_storage = Mock()
mock_storage.name = "test_device"
mock_storage.firmware_bin_path = str(firmware_file)
mock_storage_json.load.return_value = mock_storage
response = await dashboard.fetch(
"/download.bin?configuration=test.yaml&file=firmware.bin&download=custom_name.bin",
method="GET",
)
assert response.code == 200
assert "custom_name.bin" in response.headers["Content-Disposition"]
@pytest.mark.asyncio
@pytest.mark.usefixtures("mock_ext_storage_path")
async def test_download_binary_handler_idedata_fallback(
dashboard: DashboardTestHelper,
tmp_path: Path,
mock_async_run_system_command: MagicMock,
mock_storage_json: MagicMock,
mock_idedata: MagicMock,
) -> None:
"""Test the DownloadBinaryRequestHandler.get falling back to idedata for extra images."""
# Create build directory but no bootloader file initially
build_dir = tmp_path / ".esphome" / "build" / "test"
build_dir.mkdir(parents=True)
firmware_file = build_dir / "firmware.bin"
firmware_file.write_bytes(b"firmware")
# Create bootloader file that idedata will find
bootloader_file = tmp_path / "bootloader.bin"
bootloader_file.write_bytes(b"bootloader content")
# Mock storage JSON
mock_storage = Mock()
mock_storage.name = "test_device"
mock_storage.firmware_bin_path = str(firmware_file)
mock_storage_json.load.return_value = mock_storage
# Mock idedata response
mock_image = Mock()
mock_image.path = str(bootloader_file)
mock_idedata_instance = Mock()
mock_idedata_instance.extra_flash_images = [mock_image]
mock_idedata.return_value = mock_idedata_instance
# Mock async_run_system_command to return idedata JSON
mock_async_run_system_command.return_value = (0, '{"extra_flash_images": []}', "")
response = await dashboard.fetch(
"/download.bin?configuration=test.yaml&file=bootloader.bin",
method="GET",
)
assert response.code == 200
assert response.body == b"bootloader content"
@pytest.mark.asyncio
async def test_edit_request_handler_post_invalid_file(
dashboard: DashboardTestHelper,
) -> None:
"""Test the EditRequestHandler.post with non-yaml file."""
with pytest.raises(HTTPClientError) as exc_info:
await dashboard.fetch(
"/edit?configuration=test.txt",
method="POST",
body=b"content",
)
assert exc_info.value.code == 404
@pytest.mark.asyncio
async def test_edit_request_handler_post_existing(
dashboard: DashboardTestHelper,
tmp_path: Path,
mock_dashboard_settings: MagicMock,
) -> None:
"""Test the EditRequestHandler.post with existing yaml file."""
# Create a temporary yaml file to edit (don't modify fixtures)
test_file = tmp_path / "test_edit.yaml"
test_file.write_text("esphome:\n name: original\n")
# Configure the mock settings
mock_dashboard_settings.rel_path.return_value = str(test_file)
mock_dashboard_settings.absolute_config_dir = test_file.parent
new_content = "esphome:\n name: modified\n"
response = await dashboard.fetch(
"/edit?configuration=test_edit.yaml",
method="POST",
body=new_content.encode(),
)
assert response.code == 200
# Verify the file was actually modified
assert test_file.read_text() == new_content
@pytest.mark.asyncio
async def test_unarchive_request_handler(
dashboard: DashboardTestHelper,
mock_archive_storage_path: MagicMock,
mock_dashboard_settings: MagicMock,
tmp_path: Path,
) -> None:
"""Test the UnArchiveRequestHandler.post method."""
# Set up an archived file
archive_dir = Path(mock_archive_storage_path.return_value)
archive_dir.mkdir(parents=True, exist_ok=True)
archived_file = archive_dir / "archived.yaml"
archived_file.write_text("test content")
# Set up the destination path where the file should be moved
config_dir = tmp_path / "config"
config_dir.mkdir(parents=True, exist_ok=True)
destination_file = config_dir / "archived.yaml"
mock_dashboard_settings.rel_path.return_value = str(destination_file)
response = await dashboard.fetch(
"/unarchive?configuration=archived.yaml",
method="POST",
body=b"",
)
assert response.code == 200
# Verify the file was actually moved from archive to config
assert not archived_file.exists() # File should be gone from archive
assert destination_file.exists() # File should now be in config
assert destination_file.read_text() == "test content" # Content preserved
@pytest.mark.asyncio
async def test_secret_keys_handler_no_file(dashboard: DashboardTestHelper) -> None:
"""Test the SecretKeysRequestHandler.get when no secrets file exists."""
# By default, there's no secrets file in the test fixtures
with pytest.raises(HTTPClientError) as exc_info:
await dashboard.fetch("/secret_keys", method="GET")
assert exc_info.value.code == 404
@pytest.mark.asyncio
async def test_secret_keys_handler_with_file(
dashboard: DashboardTestHelper,
tmp_path: Path,
mock_dashboard_settings: MagicMock,
) -> None:
"""Test the SecretKeysRequestHandler.get when secrets file exists."""
# Create a secrets file in temp directory
secrets_file = tmp_path / "secrets.yaml"
secrets_file.write_text(
"wifi_ssid: TestNetwork\nwifi_password: TestPass123\napi_key: test_key\n"
)
# Configure mock to return our temp secrets file
# Since the file actually exists, os.path.isfile will return True naturally
mock_dashboard_settings.rel_path.return_value = str(secrets_file)
response = await dashboard.fetch("/secret_keys", method="GET")
assert response.code == 200
data = json.loads(response.body.decode())
assert "wifi_ssid" in data
assert "wifi_password" in data
assert "api_key" in data
@pytest.mark.asyncio
async def test_json_config_handler(
dashboard: DashboardTestHelper,
mock_async_run_system_command: MagicMock,
) -> None:
"""Test the JsonConfigRequestHandler.get method."""
# This will actually run the esphome config command on pico.yaml
mock_output = json.dumps(
{
"esphome": {"name": "pico"},
"esp32": {"board": "esp32dev"},
}
)
mock_async_run_system_command.return_value = (0, mock_output, "")
response = await dashboard.fetch(
"/json-config?configuration=pico.yaml", method="GET"
)
assert response.code == 200
data = json.loads(response.body.decode())
assert data["esphome"]["name"] == "pico"
@pytest.mark.asyncio
async def test_json_config_handler_invalid_config(
dashboard: DashboardTestHelper,
mock_async_run_system_command: MagicMock,
) -> None:
"""Test the JsonConfigRequestHandler.get with invalid config."""
# Simulate esphome config command failure
mock_async_run_system_command.return_value = (1, "", "Error: Invalid configuration")
with pytest.raises(HTTPClientError) as exc_info:
await dashboard.fetch("/json-config?configuration=pico.yaml", method="GET")
assert exc_info.value.code == 422
@pytest.mark.asyncio
async def test_json_config_handler_not_found(dashboard: DashboardTestHelper) -> None:
"""Test the JsonConfigRequestHandler.get with non-existent file."""
with pytest.raises(HTTPClientError) as exc_info:
await dashboard.fetch(
"/json-config?configuration=nonexistent.yaml", method="GET"
)
assert exc_info.value.code == 404
def test_start_web_server_with_address_port(
tmp_path: Path,
mock_trash_storage_path: MagicMock,
mock_archive_storage_path: MagicMock,
) -> None:
"""Test the start_web_server function with address and port."""
app = Mock()
trash_dir = Path(mock_trash_storage_path.return_value)
archive_dir = Path(mock_archive_storage_path.return_value)
# Create trash dir to test migration
trash_dir.mkdir()
(trash_dir / "old.yaml").write_text("old")
web_server.start_web_server(app, None, "127.0.0.1", 6052, str(tmp_path / "config"))
# The function calls app.listen directly for non-socket mode
app.listen.assert_called_once_with(6052, "127.0.0.1")
# Verify trash was moved to archive
assert not trash_dir.exists()
assert archive_dir.exists()
assert (archive_dir / "old.yaml").exists()
@pytest.mark.asyncio
async def test_edit_request_handler_get(dashboard: DashboardTestHelper) -> None:
"""Test EditRequestHandler.get method."""
# Test getting a valid yaml file
response = await dashboard.fetch("/edit?configuration=pico.yaml")
assert response.code == 200
assert response.headers["content-type"] == "application/yaml"
content = response.body.decode()
assert "esphome:" in content # Verify it's a valid ESPHome config
# Test getting a non-existent file
with pytest.raises(HTTPClientError) as exc_info:
await dashboard.fetch("/edit?configuration=nonexistent.yaml")
assert exc_info.value.code == 404
# Test getting a non-yaml file
with pytest.raises(HTTPClientError) as exc_info:
await dashboard.fetch("/edit?configuration=test.txt")
assert exc_info.value.code == 404
# Test path traversal attempt
with pytest.raises(HTTPClientError) as exc_info:
await dashboard.fetch("/edit?configuration=../../../etc/passwd")
assert exc_info.value.code == 404
@pytest.mark.asyncio
async def test_archive_request_handler_post(
dashboard: DashboardTestHelper,
mock_archive_storage_path: MagicMock,
mock_ext_storage_path: MagicMock,
tmp_path: Path,
) -> None:
"""Test ArchiveRequestHandler.post method."""
# Set up temp directories
config_dir = Path(get_fixture_path("conf"))
archive_dir = tmp_path / "archive"
# Create a test configuration file
test_config = config_dir / "test_archive.yaml"
test_config.write_text("esphome:\n name: test_archive\n")
# Archive the configuration
response = await dashboard.fetch(
"/archive",
method="POST",
body="configuration=test_archive.yaml",
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
assert response.code == 200
# Verify file was moved to archive
assert not test_config.exists()
assert (archive_dir / "test_archive.yaml").exists()
assert (
archive_dir / "test_archive.yaml"
).read_text() == "esphome:\n name: test_archive\n"
@pytest.mark.skipif(os.name == "nt", reason="Unix sockets are not supported on Windows")
@pytest.mark.usefixtures("mock_trash_storage_path", "mock_archive_storage_path")
def test_start_web_server_with_unix_socket(tmp_path: Path) -> None:
"""Test the start_web_server function with unix socket."""
app = Mock()
socket_path = tmp_path / "test.sock"
# Don't create trash_dir - it doesn't exist, so no migration needed
with (
patch("tornado.httpserver.HTTPServer") as mock_server_class,
patch("tornado.netutil.bind_unix_socket") as mock_bind,
):
server = Mock()
mock_server_class.return_value = server
mock_bind.return_value = Mock()
web_server.start_web_server(
app, str(socket_path), None, None, str(tmp_path / "config")
)
mock_server_class.assert_called_once_with(app)
mock_bind.assert_called_once_with(str(socket_path), mode=0o666)
server.add_socket.assert_called_once()

File diff suppressed because it is too large Load Diff