mirror of
https://github.com/esphome/esphome.git
synced 2026-10-27 00:12:11 +00:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cd8781a94 | ||
|
|
cfe5809654 | ||
|
|
9c54f96520 | ||
|
|
0cc5b372fb | ||
|
|
f7c8cba8b7 | ||
|
|
79a22e9a9a | ||
|
|
103023737d | ||
|
|
6b6b70a8b6 | ||
|
|
ff26d32406 | ||
|
|
2df80fbf71 | ||
|
|
470f7eb51d | ||
|
|
e7b2dfed57 | ||
|
|
83080c4560 | ||
|
|
104cb09475 | ||
|
|
a7824ed4e7 | ||
|
|
6420db90b1 | ||
|
|
d3c2a7c734 |
No files matched your search
+17
-48
@@ -1305,10 +1305,7 @@ def upload_program(
|
||||
f"{option_string} is only supported with the esphome OTA platform; "
|
||||
"the web_server OTA path can only update the firmware image."
|
||||
)
|
||||
binary = CORE.firmware_bin
|
||||
if getattr(args, "file", None) is not None:
|
||||
binary = Path(args.file)
|
||||
return _upload_via_web_server(config, network_devices, binary)
|
||||
return _upload_via_web_server(config, network_devices, _app_ota_image(args))
|
||||
|
||||
return _upload_via_native_api(config, network_devices, args)
|
||||
|
||||
@@ -1364,6 +1361,16 @@ def _esphome_ota_conf(config: ConfigType) -> ConfigType | None:
|
||||
return next(iter(esphome_ota_items(config)), None)
|
||||
|
||||
|
||||
def _app_ota_image(args: ArgsProtocol) -> Path:
|
||||
"""The file an app update sends: ``--file`` or the build output, as the
|
||||
target platform's ota_image hook picks it."""
|
||||
binary = CORE.firmware_bin
|
||||
if (file := getattr(args, "file", None)) is not None:
|
||||
binary = Path(file)
|
||||
ota_image = platform_hooks.get_platform_hook(CORE.target_platform, "ota_image")
|
||||
return ota_image(binary) if ota_image is not None else binary
|
||||
|
||||
|
||||
def _upload_via_native_api(
|
||||
config: ConfigType, network_devices: list[str], args: ArgsProtocol
|
||||
) -> tuple[int, str | None]:
|
||||
@@ -1413,27 +1420,27 @@ def _upload_via_native_api(
|
||||
f"flash a build with the option enabled, and then retry {option_string}."
|
||||
)
|
||||
|
||||
binary = CORE.firmware_bin
|
||||
ota_type = espota2.OTA_TYPE_UPDATE_APP
|
||||
file = getattr(args, "file", None)
|
||||
if getattr(args, "partition_table", False):
|
||||
# Fail fast if the resolved ESPHome OTA config does not enable allow_partition_access.
|
||||
# The device-side handshake also rejects this with "Device only supports app updates",
|
||||
# but checking here surfaces the misconfiguration before opening a network connection.
|
||||
check_partition_access("--partition-table")
|
||||
binary = CORE.partition_table_bin
|
||||
binary = Path(file) if file is not None else CORE.partition_table_bin
|
||||
ota_type = espota2.OTA_TYPE_UPDATE_PARTITION_TABLE
|
||||
elif getattr(args, "bootloader", False):
|
||||
check_partition_access("--bootloader")
|
||||
if (
|
||||
getattr(args, "file", None) is None
|
||||
file is None
|
||||
and (native := native_backend())
|
||||
and (hint := getattr(native, "missing_image_hint", lambda: None)())
|
||||
):
|
||||
raise EsphomeError(hint)
|
||||
binary = CORE.bootloader_bin
|
||||
binary = Path(file) if file is not None else CORE.bootloader_bin
|
||||
ota_type = espota2.OTA_TYPE_UPDATE_BOOTLOADER
|
||||
if getattr(args, "file", None) is not None:
|
||||
binary = Path(args.file)
|
||||
else:
|
||||
binary = _app_ota_image(args)
|
||||
|
||||
if ota_type == espota2.OTA_TYPE_UPDATE_PARTITION_TABLE:
|
||||
_validate_partition_table_binary(binary)
|
||||
@@ -2233,12 +2240,6 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def command_rotate_key(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
from esphome.cli.rotate_key import command_rotate_key as run
|
||||
|
||||
return run(args, config)
|
||||
|
||||
|
||||
def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
from esphome.cli.rename import command_rename as run
|
||||
|
||||
@@ -2265,7 +2266,6 @@ POST_CONFIG_ACTIONS = {
|
||||
"clean-mqtt": command_clean_mqtt,
|
||||
"idedata": command_idedata,
|
||||
"rename": command_rename,
|
||||
"rotate-key": command_rotate_key,
|
||||
"discover": command_discover,
|
||||
"analyze-memory": command_analyze_memory,
|
||||
"bundle": command_bundle,
|
||||
@@ -2663,37 +2663,6 @@ def parse_args(argv):
|
||||
)
|
||||
parser_rename.add_argument("name", help="The new name for the device.", type=str)
|
||||
|
||||
parser_rotate_key = subparsers.add_parser(
|
||||
"rotate-key",
|
||||
help=(
|
||||
"Change the OTA encryption key over the air: write a new key to the "
|
||||
"yaml, build with it, upload with the current key, confirm."
|
||||
),
|
||||
parents=[mqtt_options],
|
||||
)
|
||||
parser_rotate_key.add_argument(
|
||||
"configuration", help="Your YAML configuration file.", nargs=1
|
||||
)
|
||||
parser_rotate_key.add_argument(
|
||||
"--device",
|
||||
action="append",
|
||||
help=ARGUMENT_HELP_DEVICE,
|
||||
)
|
||||
parser_rotate_key.add_argument(
|
||||
"--prompt-new-key",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Ask for the new key instead of generating one; read without echo, "
|
||||
"or as one line from stdin when that is not a terminal."
|
||||
),
|
||||
)
|
||||
parser_rotate_key.add_argument(
|
||||
"-y",
|
||||
"--yes",
|
||||
action="store_true",
|
||||
help="Do not ask for confirmation when the api encryption key changes too.",
|
||||
)
|
||||
|
||||
parser_analyze_memory = subparsers.add_parser(
|
||||
"analyze-memory",
|
||||
help="Analyze memory usage by component.",
|
||||
|
||||
@@ -1,289 +0,0 @@
|
||||
"""``esphome rotate-key``."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
from esphome import espota2
|
||||
from esphome.components.noise import (
|
||||
generate_encryption_key,
|
||||
static_encryption_key,
|
||||
validate_encryption_key,
|
||||
)
|
||||
from esphome.config_validation import Invalid
|
||||
from esphome.const import CONF_API, CONF_PASSWORD, CONF_PORT
|
||||
from esphome.core import CORE, EsphomeError
|
||||
from esphome.espota2 import ProbeResult
|
||||
from esphome.log import AnsiFore, color
|
||||
from esphome.ota_key_edit import locate_key_edits, old_key_edit, with_sharers
|
||||
from esphome.types import ConfigType
|
||||
from esphome.upload_targets import PortType, get_port_type
|
||||
from esphome.util import (
|
||||
ESPHOME_COMMAND,
|
||||
read_secret_line,
|
||||
run_external_process,
|
||||
safe_input,
|
||||
safe_print,
|
||||
)
|
||||
from esphome.yaml_edit import RestoreError, Snapshot, apply_line_edits, restore_files
|
||||
|
||||
PRECHECK_TIMEOUT = 15.0
|
||||
# What to do when the device does not accept the key in the yaml before anything is written
|
||||
PRECHECK_ADVICE = {
|
||||
ProbeResult.UNREACHABLE: (
|
||||
"The device could not be reached; check that it is online, then try again."
|
||||
),
|
||||
ProbeResult.REJECTED: (
|
||||
"The device runs a different key than the yaml. Install the current "
|
||||
"configuration first, presenting its key with --prompt-ota-key, then rotate."
|
||||
),
|
||||
ProbeResult.NOT_OFFERED: (
|
||||
"The device does not offer encryption: it runs a firmware older than "
|
||||
"ESPHome 2026.9.0 or without a key. Install the current configuration "
|
||||
"first, then rotate."
|
||||
),
|
||||
ProbeResult.DEVICE_ERROR: (
|
||||
"The device answered with an error (see the warning above); fix that first."
|
||||
),
|
||||
}
|
||||
# Why the device did not confirm the new key after the upload
|
||||
CONFIRM_FAILURE = {
|
||||
ProbeResult.REJECTED: "The device still answers with the previous key.",
|
||||
ProbeResult.NOT_OFFERED: "The device came back without encryption.",
|
||||
ProbeResult.DEVICE_ERROR: "The device answered with an error (see the warning above).",
|
||||
ProbeResult.UNREACHABLE: "The device did not come back to confirm the new key.",
|
||||
}
|
||||
|
||||
|
||||
def command_rotate_key(args: argparse.Namespace, config: ConfigType) -> int | None:
|
||||
"""Write a new key, build with it, upload with the current one, confirm."""
|
||||
from esphome.__main__ import (
|
||||
Purpose,
|
||||
_esphome_ota_conf,
|
||||
_resolve_network_devices,
|
||||
choose_upload_log_host,
|
||||
)
|
||||
|
||||
def fail(message: str) -> int:
|
||||
safe_print(color(AnsiFore.BOLD_RED, message))
|
||||
return 1
|
||||
|
||||
ota_conf = _esphome_ota_conf(config)
|
||||
if ota_conf is None:
|
||||
return fail("rotate-key needs the esphome OTA platform in the configuration")
|
||||
old_key = static_encryption_key(ota_conf)
|
||||
if old_key is None:
|
||||
return fail(
|
||||
"rotate-key needs 'encryption:' under the esphome OTA platform with a "
|
||||
"key in the yaml; a key provisioned at runtime is rotated from Home "
|
||||
"Assistant"
|
||||
)
|
||||
if CORE.is_host:
|
||||
return fail("rotate-key is for devices updated over the air")
|
||||
old_key = str(old_key)
|
||||
remote_port = int(ota_conf[CONF_PORT])
|
||||
|
||||
devices = choose_upload_log_host(
|
||||
default=args.device, check_default=None, purpose=Purpose.UPLOADING
|
||||
)
|
||||
if get_port_type(devices[0]) in (PortType.SERIAL, PortType.BOOTSEL):
|
||||
return fail(
|
||||
"rotate-key works over the air; on a serial connection change the "
|
||||
"key in the yaml and run 'esphome run' instead"
|
||||
)
|
||||
network_devices = _resolve_network_devices(devices, config, args)
|
||||
|
||||
if args.prompt_new_key:
|
||||
new_key = read_secret_line("New OTA encryption key: ")
|
||||
else:
|
||||
new_key = generate_encryption_key()
|
||||
try:
|
||||
validate_encryption_key(new_key)
|
||||
except Invalid as err:
|
||||
return fail(f"Invalid new key: {err}")
|
||||
if new_key == old_key:
|
||||
return fail("The new key is the same as the current one")
|
||||
|
||||
try:
|
||||
edits = locate_key_edits(old_key, new_key)
|
||||
# The previous key stays in the config as old_key so an install still
|
||||
# reaches the device if it ends up running either key
|
||||
edits += old_key_edit(old_key)
|
||||
unchecked = with_sharers(edits)
|
||||
except EsphomeError as err:
|
||||
return fail(str(err))
|
||||
|
||||
warnings = []
|
||||
# With the recommended shape the ota inherits the api key, so rotating
|
||||
# it changes what Home Assistant connects with
|
||||
if str(static_encryption_key(config.get(CONF_API) or {})) == old_key:
|
||||
warnings.append(
|
||||
"The api encryption key changes too; Home Assistant will ask for "
|
||||
"the new key after the install."
|
||||
)
|
||||
shared: dict[str, set[str]] = {}
|
||||
for edit in edits:
|
||||
what = f"secret '{edit.secret}'" if edit.secret else edit.path.name
|
||||
for path in edit.shared_with:
|
||||
shared.setdefault(
|
||||
str(path.relative_to(CORE.config_dir.resolve())), set()
|
||||
).add(what)
|
||||
if unchecked:
|
||||
warnings.append(
|
||||
"Could not check every configuration for shared use: "
|
||||
+ "; ".join(unchecked)
|
||||
)
|
||||
if shared:
|
||||
warnings.append(
|
||||
"Other configurations share what is rewritten: "
|
||||
+ "; ".join(
|
||||
f"{p} ({', '.join(sorted(w))})" for p, w in sorted(shared.items())
|
||||
)
|
||||
+ ". Their devices keep the previous key until their next install; "
|
||||
"make sure those configurations carry it as old_key."
|
||||
)
|
||||
if warnings:
|
||||
for warning in warnings:
|
||||
safe_print(color(AnsiFore.BOLD_YELLOW, warning))
|
||||
if not args.yes:
|
||||
if not sys.stdin.isatty():
|
||||
return fail("Confirm with --yes when there is no terminal")
|
||||
if safe_input("Continue? [y/N] ").strip().lower() not in ("y", "yes"):
|
||||
return 1
|
||||
|
||||
safe_print("Checking that the device accepts the current key...")
|
||||
found = espota2.probe_ota_key(
|
||||
network_devices,
|
||||
remote_port,
|
||||
old_key,
|
||||
timeout=PRECHECK_TIMEOUT,
|
||||
retry_rejected=False,
|
||||
)
|
||||
if found is not ProbeResult.ACCEPTED:
|
||||
return fail(PRECHECK_ADVICE[found])
|
||||
|
||||
# Both keys are shown only when a file could not be put back
|
||||
keys = f"Previous key: {old_key}\nNew key: {new_key}"
|
||||
try:
|
||||
originals = apply_line_edits(edits)
|
||||
except RestoreError as err:
|
||||
safe_print(keys)
|
||||
return fail(str(err))
|
||||
except EsphomeError as err:
|
||||
return fail(str(err))
|
||||
except KeyboardInterrupt as err:
|
||||
if notes := getattr(err, "__notes__", None): # a rollback that failed too
|
||||
safe_print(keys)
|
||||
return fail("Interrupted; " + "; ".join(notes))
|
||||
return fail("Interrupted before the upload")
|
||||
# From here every exit restores or reports, so nothing sits outside the try
|
||||
uploaded = False
|
||||
try:
|
||||
safe_print(
|
||||
"Wrote the new key to "
|
||||
+ ", ".join(color(AnsiFore.CYAN, str(p)) for p in originals)
|
||||
)
|
||||
# Global options go before the subcommand; the compile parser is strict
|
||||
cli_args = ["--dashboard"] if CORE.dashboard else []
|
||||
if args.toolchain:
|
||||
cli_args += ["--toolchain", str(args.toolchain)]
|
||||
for key, value in args.substitution or []:
|
||||
cli_args += ["-s", key, value]
|
||||
cli_args += ["compile", str(CORE.config_path)]
|
||||
if run_external_process(*ESPHOME_COMMAND, *cli_args) != 0:
|
||||
return fail("Compiling with the new key failed")
|
||||
|
||||
def image_sent() -> None:
|
||||
# The device accepted the session and may commit from here on;
|
||||
# the edited config reaches it on either key
|
||||
nonlocal uploaded
|
||||
uploaded = True
|
||||
|
||||
rc, _ = espota2.run_ota(
|
||||
network_devices,
|
||||
remote_port,
|
||||
ota_conf.get(CONF_PASSWORD),
|
||||
CORE.firmware_bin,
|
||||
noise_psk=old_key,
|
||||
plaintext_fallback=False,
|
||||
on_upload_start=image_sent,
|
||||
)
|
||||
if rc != 0:
|
||||
return fail(
|
||||
"Uploading with the current key failed. The configuration keeps "
|
||||
"the new key with the previous one as old_key, so the next "
|
||||
"install reaches the device whichever key it runs."
|
||||
if uploaded
|
||||
else "The upload failed before the image was sent (see the error "
|
||||
"above); nothing changed on the device."
|
||||
)
|
||||
safe_print("Waiting for the device to come back with the new key...")
|
||||
found = espota2.probe_ota_key(network_devices, remote_port, new_key)
|
||||
if found is not ProbeResult.ACCEPTED:
|
||||
return fail(
|
||||
CONFIRM_FAILURE[found]
|
||||
+ " The configuration keeps the new key, with the previous one as "
|
||||
"old_key, so the next install reaches the device either way."
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
return fail(
|
||||
"Interrupted; the device's key was not confirmed"
|
||||
if uploaded
|
||||
else "Interrupted before the upload"
|
||||
)
|
||||
finally:
|
||||
# Before the upload nothing changed on the device; after it the device
|
||||
# most likely runs the new key, and old_key covers the other case
|
||||
if uploaded:
|
||||
safe_print(f"New OTA encryption key: {color(AnsiFore.CYAN, new_key)}")
|
||||
else:
|
||||
_restore(originals, keys)
|
||||
|
||||
safe_print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
|
||||
return 0
|
||||
|
||||
|
||||
def _restore(originals: dict[Path, Snapshot], keys: str) -> None:
|
||||
"""Put the previous key back, and drop the images a build made with the
|
||||
new key; both keys are shown when a step fails."""
|
||||
files = ", ".join(str(p) for p in originals)
|
||||
try:
|
||||
restore_files(originals)
|
||||
except (EsphomeError, KeyboardInterrupt) as err:
|
||||
# An interrupted restore carries its report as a note
|
||||
why = str(err) or "; ".join(getattr(err, "__notes__", ()))
|
||||
safe_print(color(AnsiFore.BOLD_RED, why or f"Interrupted restoring {files}"))
|
||||
safe_print(keys)
|
||||
else:
|
||||
safe_print(color(AnsiFore.BOLD_YELLOW, f"Restored the previous key in {files}"))
|
||||
build_dir = CORE.firmware_bin.parent
|
||||
failed: list[str] = []
|
||||
images: list[Path] = []
|
||||
try:
|
||||
images = [p for s in (".bin", ".uf2") for p in build_dir.glob(f"*{s}")]
|
||||
for image in images: # every one is tried, whatever the others did
|
||||
try:
|
||||
image.unlink()
|
||||
except OSError as err:
|
||||
failed.append(f"{image}: {err}")
|
||||
except (OSError, KeyboardInterrupt) as err:
|
||||
failed.append(f"{build_dir}: {str(err) or 'interrupted'}")
|
||||
if failed:
|
||||
safe_print(
|
||||
color(
|
||||
AnsiFore.BOLD_RED,
|
||||
"Could not remove images that may hold the new key: "
|
||||
+ "; ".join(failed),
|
||||
)
|
||||
)
|
||||
safe_print(keys)
|
||||
elif images:
|
||||
safe_print(
|
||||
color(
|
||||
AnsiFore.BOLD_YELLOW,
|
||||
f"Removed the images in {build_dir}, which may hold the new key; "
|
||||
"compile again before the next upload.",
|
||||
)
|
||||
)
|
||||
@@ -297,9 +297,7 @@ void ADE7880::init_device_() {
|
||||
void ADE7880::reset_device_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
ESP_LOGD(TAG, "Reset device using RESET pin");
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(1);
|
||||
this->reset_pin_->digital_write(true);
|
||||
pulse_reset(this->reset_pin_, {.low_ms = 1});
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Reset device using SWRST command");
|
||||
this->write_u16_register16_(CONFIG, CONFIG_SWRST);
|
||||
|
||||
@@ -16,13 +16,7 @@ constexpr static const uint8_t AXS_READ_TOUCHPAD[11] = {0xb5, 0xab, 0xa5, 0x5a,
|
||||
}
|
||||
|
||||
void AXS15231Touchscreen::setup() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(10);
|
||||
}
|
||||
pulse_reset(this->reset_pin_, {.low_ms = 5, .high_after_ms = 10});
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->interrupt_pin_->setup();
|
||||
|
||||
@@ -78,3 +78,7 @@ async def pin_to_code(config: ConfigType) -> MockObj:
|
||||
# Called by writer.py; delegates to the shared libretiny implementation.
|
||||
def copy_files() -> None:
|
||||
libretiny.copy_files()
|
||||
|
||||
|
||||
# Called by the OTA upload; the shared libretiny implementation.
|
||||
ota_image = libretiny.device_ota_image
|
||||
@@ -6,12 +6,7 @@ ESPHOME_LOG_TAG(TAG, "cst226.touchscreen");
|
||||
|
||||
void CST226Touchscreen::setup() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(true);
|
||||
pulse_reset(this->reset_pin_, {.high_before_ms = 5, .low_ms = 5});
|
||||
this->set_timeout(30, [this] { this->continue_setup_(); });
|
||||
} else {
|
||||
this->continue_setup_();
|
||||
|
||||
@@ -43,9 +43,7 @@ void CST328Touchscreen::setup() {
|
||||
}
|
||||
|
||||
void CST328Touchscreen::reset_device_() {
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(true);
|
||||
pulse_reset(this->reset_pin_, {.low_ms = 5});
|
||||
this->set_timeout(CST328_TRANSITION_TIMEOUT, [this] { this->continue_setup_(); });
|
||||
}
|
||||
|
||||
|
||||
@@ -52,12 +52,7 @@ void CST816Touchscreen::continue_setup_() {
|
||||
|
||||
void CST816Touchscreen::setup() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(true);
|
||||
pulse_reset(this->reset_pin_, {.high_before_ms = 5, .low_ms = 5});
|
||||
this->set_timeout(30, [this] { this->continue_setup_(); });
|
||||
} else {
|
||||
this->continue_setup_();
|
||||
|
||||
@@ -10,14 +10,7 @@ static constexpr uint32_t REREAD_TIMEOUT_ID = 1;
|
||||
static constexpr uint32_t REREAD_DELAY_MS = 20;
|
||||
|
||||
void CST9220Touchscreen::setup() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
pulse_reset(this->reset_pin_, {.high_before_ms = 5, .low_ms = 10});
|
||||
// Wait for the controller to leave its bootloader before talking to it.
|
||||
this->set_timeout(30, [this] { this->continue_setup_(); });
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
cg.add_define("USE_LINE_READER")
|
||||
await uart.new_uart_component(config)
|
||||
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
namespace esphome::dfrobot_sen0395 {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "dfrobot_sen0395");
|
||||
static constexpr char ASCII_CR = 0x0D;
|
||||
static constexpr char ASCII_LF = 0x0A;
|
||||
static constexpr float DET_RANGE_STEP_M = 0.15f;
|
||||
static constexpr float LATENCY_STEP_S = 0.025f;
|
||||
// Sent after a reset with no line end
|
||||
static constexpr char PROMPT[] PROGMEM = "leapMMW:/>";
|
||||
|
||||
// What each CommandType cannot do while the sensor runs; empty for commands that do not need it stopped
|
||||
PROGMEM_STRING_TABLE(BusyActions, "", "configure range config", "configure output latency",
|
||||
@@ -121,9 +121,9 @@ bool DfrobotSen0395Component::run_command_(const Command &cmd, uint32_t now) {
|
||||
return false; // Command not done yet
|
||||
}
|
||||
|
||||
if (this->read_message_()) {
|
||||
if (ESPHOME_strstr_P(this->read_buffer_, ESPHOME_PSTR("is not recognized as a CLI command")) == nullptr) {
|
||||
if (!this->handle_reply_(cmd))
|
||||
if (const char *message = this->read_message_()) {
|
||||
if (ESPHOME_strstr_P(message, ESPHOME_PSTR("is not recognized as a CLI command")) == nullptr) {
|
||||
if (!this->handle_reply_(cmd, message))
|
||||
return false; // Command not done yet
|
||||
this->read_message_(); // Skip the prompt
|
||||
return true;
|
||||
@@ -146,12 +146,13 @@ bool DfrobotSen0395Component::run_command_(const Command &cmd, uint32_t now) {
|
||||
}
|
||||
|
||||
void DfrobotSen0395Component::read_state_() {
|
||||
if (!this->read_message_())
|
||||
const char *message = this->read_message_();
|
||||
if (message == nullptr)
|
||||
return;
|
||||
if (ESPHOME_strstr_P(this->read_buffer_, ESPHOME_PSTR("$JYBSS,0, , , *")) != nullptr) {
|
||||
if (ESPHOME_strstr_P(message, ESPHOME_PSTR("$JYBSS,0, , , *")) != nullptr) {
|
||||
this->set_detected_(false);
|
||||
this->set_active(true);
|
||||
} else if (ESPHOME_strstr_P(this->read_buffer_, ESPHOME_PSTR("$JYBSS,1, , , *")) != nullptr) {
|
||||
} else if (ESPHOME_strstr_P(message, ESPHOME_PSTR("$JYBSS,1, , , *")) != nullptr) {
|
||||
this->set_detected_(true);
|
||||
this->set_active(true);
|
||||
}
|
||||
@@ -206,8 +207,7 @@ void DfrobotSen0395Component::format_command(const Command &cmd, char *buf, size
|
||||
}
|
||||
}
|
||||
|
||||
bool DfrobotSen0395Component::handle_reply_(const Command &cmd) {
|
||||
const char *message = this->read_buffer_;
|
||||
bool DfrobotSen0395Component::handle_reply_(const Command &cmd, const char *message) {
|
||||
auto is = [message](ESPHOME_PGM_P text) { return ESPHOME_strcmp_P(message, text) == 0; };
|
||||
|
||||
// Commands with their own replies
|
||||
@@ -230,7 +230,7 @@ bool DfrobotSen0395Component::handle_reply_(const Command &cmd) {
|
||||
}
|
||||
return true;
|
||||
case CommandType::COMMAND_TYPE_RESET_SYSTEM:
|
||||
if (!is(ESPHOME_PSTR("leapMMW:/>")))
|
||||
if (!is(PROMPT))
|
||||
return false;
|
||||
ESP_LOGI(TAG, "Restarted sensor.");
|
||||
return true;
|
||||
@@ -300,34 +300,24 @@ bool DfrobotSen0395Component::handle_reply_(const Command &cmd) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DfrobotSen0395Component::read_message_() {
|
||||
const char *DfrobotSen0395Component::read_message_() {
|
||||
while (this->available()) {
|
||||
uint8_t byte;
|
||||
this->read_byte(&byte);
|
||||
|
||||
if (this->read_pos_ == MMWAVE_READ_BUFFER_LENGTH)
|
||||
this->read_pos_ = 0;
|
||||
|
||||
ESP_LOGVV(TAG, "Buffer pos: %u %d", this->read_pos_, byte);
|
||||
|
||||
if (byte == ASCII_CR)
|
||||
continue;
|
||||
if (byte >= 0x7F)
|
||||
byte = '?'; // needs to be valid utf8 string for log functions.
|
||||
this->read_buffer_[this->read_pos_] = byte;
|
||||
|
||||
if (this->read_pos_ == 9 && byte == '>')
|
||||
this->read_buffer_[++this->read_pos_] = ASCII_LF;
|
||||
|
||||
if (this->read_buffer_[this->read_pos_] == ASCII_LF) {
|
||||
this->read_buffer_[this->read_pos_] = 0;
|
||||
this->read_pos_ = 0;
|
||||
ESP_LOGV(TAG, "Message: %s", this->read_buffer_);
|
||||
return true; // Full message in buffer
|
||||
const char *line = this->line_reader_.feed(byte);
|
||||
if (line == nullptr && byte == '>' && this->line_reader_.length() == sizeof(PROMPT) - 1) {
|
||||
line = this->line_reader_.partial();
|
||||
this->line_reader_.reset();
|
||||
}
|
||||
this->read_pos_++;
|
||||
if (line != nullptr) {
|
||||
ESP_LOGV(TAG, "Message: %s", line);
|
||||
return line;
|
||||
}
|
||||
this->line_reader_.log_if_overflowed(TAG);
|
||||
}
|
||||
return false; // No full message yet
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void DfrobotSen0395Component::set_detected_(bool detected) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/line_reader.h"
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
@@ -14,7 +15,7 @@
|
||||
|
||||
namespace esphome::dfrobot_sen0395 {
|
||||
|
||||
static constexpr uint8_t MMWAVE_READ_BUFFER_LENGTH = 255;
|
||||
static constexpr uint8_t MMWAVE_READ_BUFFER_LENGTH = 253; // largest LineReader size with a one byte position
|
||||
static constexpr uint8_t COMMAND_QUEUE_SIZE = 20;
|
||||
|
||||
// Keep in sync with the BusyActions table in dfrobot_sen0395.cpp
|
||||
@@ -121,8 +122,7 @@ class DfrobotSen0395Component final : public uart::UARTDevice, public Component
|
||||
bool led_active_{false};
|
||||
bool uart_presence_active_{false};
|
||||
bool start_after_boot_{false};
|
||||
char read_buffer_[MMWAVE_READ_BUFFER_LENGTH];
|
||||
size_t read_pos_{0};
|
||||
LineReader<MMWAVE_READ_BUFFER_LENGTH> line_reader_;
|
||||
bool cmd_sent_{false};
|
||||
uint8_t retries_left_{2};
|
||||
StaticRingBuffer<Command, COMMAND_QUEUE_SIZE> cmd_queue_;
|
||||
@@ -131,12 +131,13 @@ class DfrobotSen0395Component final : public uart::UARTDevice, public Component
|
||||
int16_t det_range_steps_[8]{};
|
||||
uint16_t latency_steps_[2]{};
|
||||
|
||||
bool read_message_();
|
||||
/// The next complete line, valid until the next call, or nullptr
|
||||
const char *read_message_();
|
||||
void read_state_();
|
||||
/// Run the command at the front of the queue; true when it is done
|
||||
bool run_command_(const Command &cmd, uint32_t now);
|
||||
/// Handle a reply to a sent command; true when the command is done
|
||||
bool handle_reply_(const Command &cmd);
|
||||
bool handle_reply_(const Command &cmd, const char *message);
|
||||
|
||||
void set_detected_(bool detected);
|
||||
};
|
||||
|
||||
@@ -20,9 +20,7 @@ void EKTF2232Touchscreen::setup() {
|
||||
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
|
||||
this->reset_pin_->setup();
|
||||
|
||||
this->hard_reset_();
|
||||
pulse_reset(this->reset_pin_, {.low_ms = 15, .high_after_ms = 15});
|
||||
if (!this->soft_reset_()) {
|
||||
ESP_LOGE(TAG, "Failed to soft reset EKT2232!");
|
||||
this->interrupt_pin_->detach_interrupt();
|
||||
@@ -96,13 +94,6 @@ bool EKTF2232Touchscreen::get_power_state() {
|
||||
return (received[1] >> 3) & 1;
|
||||
}
|
||||
|
||||
void EKTF2232Touchscreen::hard_reset_() {
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(15);
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(15);
|
||||
}
|
||||
|
||||
bool EKTF2232Touchscreen::soft_reset_() {
|
||||
auto err = this->write(SOFT_RESET_CMD, 4);
|
||||
if (err != i2c::ERROR_OK)
|
||||
|
||||
@@ -22,7 +22,6 @@ class EKTF2232Touchscreen final : public Touchscreen, public i2c::I2CDevice {
|
||||
bool get_power_state();
|
||||
|
||||
protected:
|
||||
void hard_reset_();
|
||||
bool soft_reset_();
|
||||
void update_touches() override;
|
||||
|
||||
|
||||
@@ -72,9 +72,7 @@ bool EPaperJD79660::reset() {
|
||||
|
||||
// As commented on SLEEP_MS_RESET1: Reset pulse must happen within time window.
|
||||
// So do not use FSM loop, and avoid other calls/logs during pulse below.
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(SLEEP_MS_RESET1);
|
||||
this->reset_pin_->digital_write(true);
|
||||
pulse_reset(this->reset_pin_, {.low_ms = SLEEP_MS_RESET1});
|
||||
|
||||
this->reset_duration_ = SLEEP_MS_RESET2;
|
||||
this->step_ = FSMState::RESET_STEP2_IDLECHECK;
|
||||
|
||||
@@ -373,7 +373,7 @@ void ESPHomeOTAComponent::handle_handshake_() {
|
||||
// Value initialized: a corrupt stream that back references the
|
||||
// window before it is filled then copies zeros, never stale memory.
|
||||
// Default placement, PSRAM first where present: the session lives for one
|
||||
// upload and keeps 4.9 KB of internal heap free while it runs
|
||||
// upload and keeps 6.0 KB of internal heap free while it runs
|
||||
this->inflate_ = RAMAllocator<InflateSession>().make_unique();
|
||||
if (this->inflate_ != nullptr) {
|
||||
this->handshake_buf_[1] |= SERVER_FEATURE_SUPPORTS_DEFLATE;
|
||||
|
||||
@@ -36,7 +36,10 @@
|
||||
* Altered for ESPHome: this is the raw deflate decoder from uzlib's
|
||||
* tinflate.c (v2.9.5) with the gzip/zlib header parsers, checksums,
|
||||
* runtime table builder and in-memory (non ring window) output path
|
||||
* removed, and the public names prefixed with ota_inflate.
|
||||
* removed, and the public names prefixed with ota_inflate. Symbols are
|
||||
* decoded from a 32 bit bit buffer through 9 bit lookup tables, with a
|
||||
* fast loop for when a whole symbol fits in both the input and the output,
|
||||
* and the output is written straight into the ring window.
|
||||
*/
|
||||
|
||||
#include "ota_esphome_inflate.h"
|
||||
@@ -49,16 +52,6 @@
|
||||
#define TINF_DICT_ERROR OTA_INFLATE_DICT_ERROR
|
||||
#define TINF_DATA struct OtaInflateState
|
||||
#define TINF_TREE struct OtaInflateTree
|
||||
#define TINF_ARRAY_SIZE(arr) (sizeof(arr) / sizeof(*(arr)))
|
||||
|
||||
/* every output byte also goes into the ring window */
|
||||
#define TINF_PUT(d, c) \
|
||||
{ \
|
||||
*d->dest++ = c; \
|
||||
d->dict_ring[d->dict_idx++] = c; \
|
||||
if (d->dict_idx == d->dict_size) \
|
||||
d->dict_idx = 0; \
|
||||
}
|
||||
|
||||
/* --------------------------------------------------- *
|
||||
* -- constant tables (upstream builds them at runtime) -- *
|
||||
@@ -82,34 +75,52 @@ static const unsigned char CLCIDX[] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4,
|
||||
* -- utility functions -- *
|
||||
* ----------------------- */
|
||||
|
||||
/* given an array of code lengths, build a tree */
|
||||
static void tinf_build_tree(TINF_TREE *t, const unsigned char *lengths, unsigned int num) {
|
||||
unsigned short offs[16];
|
||||
unsigned int i, sum;
|
||||
enum {
|
||||
LIT_FAST_BITS = OTA_INFLATE_LIT_FAST_BITS,
|
||||
DIST_FAST_BITS = OTA_INFLATE_DIST_FAST_BITS,
|
||||
/* a lookup table entry is (code length << SYM_BITS) | symbol; 0 marks a longer code */
|
||||
SYM_BITS = 9,
|
||||
SYM_MASK = (1 << SYM_BITS) - 1,
|
||||
/* input bytes one pass of the fast loop may take: three refills of up to 4 bytes */
|
||||
FAST_IN_MIN = 12,
|
||||
};
|
||||
|
||||
/* given an array of code lengths, build a tree and its lookup table */
|
||||
static void tinf_build_tree(TINF_TREE *t, const unsigned char *lengths, unsigned int num, unsigned int fast_bits) {
|
||||
const unsigned int fast_size = 1u << fast_bits;
|
||||
unsigned short offs[16], next[16];
|
||||
unsigned int i, sum, code;
|
||||
|
||||
/* clear code length count table */
|
||||
for (i = 0; i < 16; ++i)
|
||||
t->table[i] = 0;
|
||||
|
||||
/* scan symbol lengths, and sum code length counts */
|
||||
for (i = 0; i < num; ++i)
|
||||
t->table[lengths[i]]++;
|
||||
|
||||
/* In the lengths array, 0 means unused code. So, t->table[0] now contains
|
||||
number of unused codes. But table's purpose is to contain # of codes of
|
||||
particular length, and there're 0 codes of length 0. */
|
||||
t->table[0] = 0;
|
||||
|
||||
/* compute offset table for distribution sort */
|
||||
for (sum = 0, i = 0; i < 16; ++i) {
|
||||
for (sum = 0, code = 0, i = 0; i < 16; ++i) {
|
||||
offs[i] = sum;
|
||||
sum += t->table[i];
|
||||
if (i > 0)
|
||||
code = (code + t->table[i - 1]) << 1;
|
||||
next[i] = code;
|
||||
}
|
||||
|
||||
/* create code->symbol translation table (symbols sorted by code) */
|
||||
for (i = 0; i < fast_size; ++i)
|
||||
t->fast[i] = 0;
|
||||
|
||||
for (i = 0; i < num; ++i) {
|
||||
if (lengths[i])
|
||||
t->trans[offs[lengths[i]]++] = i;
|
||||
unsigned int len = lengths[i];
|
||||
if (!len)
|
||||
continue;
|
||||
t->trans[offs[len]++] = i;
|
||||
if (len <= fast_bits) {
|
||||
/* codes arrive MSB first, the bit buffer is LSB first */
|
||||
unsigned int c = next[len]++, rev = 0, j;
|
||||
for (j = 0; j < len; ++j, c >>= 1)
|
||||
rev = (rev << 1) | (c & 1);
|
||||
for (j = rev; j < fast_size; j += 1u << len)
|
||||
t->fast[j] = (unsigned short) ((len << SYM_BITS) | i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,89 +128,78 @@ static void tinf_build_tree(TINF_TREE *t, const unsigned char *lengths, unsigned
|
||||
* -- decode functions -- *
|
||||
* ---------------------- */
|
||||
|
||||
static unsigned char uzlib_get_byte(TINF_DATA *d) {
|
||||
/* If end of source buffer is not reached, return next byte from source
|
||||
buffer. */
|
||||
if (d->source < d->source_limit) {
|
||||
return *d->source++;
|
||||
/* top the bit buffer up from the input buffer, without blocking for more */
|
||||
static inline void tinf_refill(TINF_DATA *d) {
|
||||
while (d->bitcount <= 24 && d->source < d->source_limit) {
|
||||
d->tag |= (unsigned int) *d->source++ << d->bitcount;
|
||||
d->bitcount += 8;
|
||||
}
|
||||
|
||||
/* Otherwise if there's callback and we haven't seen EOF yet, try to
|
||||
read next byte using it. (Note: the callback can also update ->source
|
||||
and ->source_limit). */
|
||||
if (!d->eof) {
|
||||
int val = d->source_read_cb(d);
|
||||
if (val >= 0) {
|
||||
return (unsigned char) val;
|
||||
}
|
||||
}
|
||||
|
||||
/* Otherwise, we hit EOF (either from ->source_read_cb() or from exhaustion
|
||||
of the buffer), and it will be "sticky", i.e. further calls to this
|
||||
function will end up here too. */
|
||||
d->eof = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* get one bit from source stream */
|
||||
static int tinf_getbit(TINF_DATA *d) {
|
||||
unsigned int bit;
|
||||
|
||||
/* check if tag is empty */
|
||||
if (!d->bitcount--) {
|
||||
/* load next tag */
|
||||
d->tag = uzlib_get_byte(d);
|
||||
d->bitcount = 7;
|
||||
/* make at least num (<= 16) bits available; past the end of input reads zeros and sets eof */
|
||||
static inline void tinf_need(TINF_DATA *d, unsigned int num) {
|
||||
while (d->bitcount < num) {
|
||||
int val = -1;
|
||||
if (d->source < d->source_limit) {
|
||||
val = *d->source++;
|
||||
} else if (!d->eof) {
|
||||
val = d->source_read_cb(d);
|
||||
}
|
||||
if (val < 0) {
|
||||
d->eof = true;
|
||||
val = 0;
|
||||
}
|
||||
d->tag |= (unsigned int) val << d->bitcount;
|
||||
d->bitcount += 8;
|
||||
}
|
||||
}
|
||||
|
||||
/* shift bit out of tag */
|
||||
bit = d->tag & 0x01;
|
||||
d->tag >>= 1;
|
||||
|
||||
return bit;
|
||||
static inline unsigned int tinf_take(TINF_DATA *d, unsigned int num) {
|
||||
unsigned int val = d->tag & ((1u << num) - 1);
|
||||
d->tag >>= num;
|
||||
d->bitcount -= num;
|
||||
return val;
|
||||
}
|
||||
|
||||
/* read a num bit value from a stream and add base */
|
||||
static unsigned int tinf_read_bits(TINF_DATA *d, int num, int base) {
|
||||
unsigned int val = 0;
|
||||
|
||||
/* read num bits */
|
||||
if (num) {
|
||||
unsigned int limit = 1 << (num);
|
||||
unsigned int mask;
|
||||
|
||||
for (mask = 1; mask < limit; mask *= 2)
|
||||
if (tinf_getbit(d))
|
||||
val += mask;
|
||||
}
|
||||
|
||||
return val + base;
|
||||
static inline unsigned int tinf_read_bits(TINF_DATA *d, unsigned int num, unsigned int base) {
|
||||
tinf_need(d, num);
|
||||
return tinf_take(d, num) + base;
|
||||
}
|
||||
|
||||
/* given a data stream and a tree, decode a symbol */
|
||||
static int tinf_decode_symbol(TINF_DATA *d, TINF_TREE *t) {
|
||||
int sum = 0, cur = 0, len = 0;
|
||||
|
||||
/* get more bits while code value is above sum */
|
||||
do {
|
||||
cur = 2 * cur + tinf_getbit(d);
|
||||
|
||||
if (++len == TINF_ARRAY_SIZE(t->table)) {
|
||||
return TINF_DATA_ERROR;
|
||||
/* canonical decode of the code at the bottom of tag; len gets the code length.
|
||||
The trans index stays below the tree's symbol count, which trans always holds */
|
||||
static int tinf_decode_long(const TINF_TREE *t, unsigned int tag, unsigned int *len) {
|
||||
unsigned int code = 0, first = 0, index = 0, l;
|
||||
for (l = 1; l < 16; ++l, tag >>= 1) {
|
||||
unsigned int count = t->table[l];
|
||||
code |= tag & 1;
|
||||
if (code - first < count) {
|
||||
*len = l;
|
||||
return t->trans[index + code - first];
|
||||
}
|
||||
|
||||
sum += t->table[len];
|
||||
cur -= t->table[len];
|
||||
|
||||
} while (cur >= 0);
|
||||
|
||||
sum += cur;
|
||||
if (sum < 0 || sum >= t->size) {
|
||||
return TINF_DATA_ERROR;
|
||||
index += count;
|
||||
first = (first + count) << 1;
|
||||
code <<= 1;
|
||||
}
|
||||
return TINF_DATA_ERROR;
|
||||
}
|
||||
|
||||
return t->trans[sum];
|
||||
/* decode a symbol, reading more input only while the code is not all in the bit
|
||||
buffer; bits above bitcount are zero, so a code no longer than bitcount is real */
|
||||
static int tinf_decode_symbol(TINF_DATA *d, const TINF_TREE *t) {
|
||||
tinf_refill(d);
|
||||
for (;;) {
|
||||
unsigned int len;
|
||||
int sym = tinf_decode_long(t, d->tag, &len);
|
||||
if (sym >= 0 && len <= d->bitcount) {
|
||||
tinf_take(d, len);
|
||||
return sym;
|
||||
}
|
||||
if (d->bitcount >= 15)
|
||||
return TINF_DATA_ERROR;
|
||||
tinf_need(d, d->bitcount + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* given a data stream, decode dynamic trees from it */
|
||||
@@ -230,7 +230,7 @@ static int tinf_decode_trees(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt) {
|
||||
}
|
||||
|
||||
/* build code length tree, temporarily use length tree */
|
||||
tinf_build_tree(lt, lengths, 19);
|
||||
tinf_build_tree(lt, lengths, 19, LIT_FAST_BITS);
|
||||
|
||||
/* decode code lengths for the dynamic trees */
|
||||
hlimit = hlit + hdist;
|
||||
@@ -282,8 +282,8 @@ static int tinf_decode_trees(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt) {
|
||||
}
|
||||
|
||||
/* build dynamic trees */
|
||||
tinf_build_tree(lt, lengths, hlit);
|
||||
tinf_build_tree(dt, lengths + hlit, hdist);
|
||||
tinf_build_tree(lt, lengths, hlit, LIT_FAST_BITS);
|
||||
tinf_build_tree(dt, lengths + hlit, hdist, DIST_FAST_BITS);
|
||||
|
||||
return TINF_OK;
|
||||
}
|
||||
@@ -302,84 +302,189 @@ static void tinf_build_fixed_trees(TINF_TREE *lt, TINF_TREE *dt) {
|
||||
lengths[i] = 7;
|
||||
for (; i < 288; ++i)
|
||||
lengths[i] = 8;
|
||||
tinf_build_tree(lt, lengths, 288);
|
||||
tinf_build_tree(lt, lengths, 288, LIT_FAST_BITS);
|
||||
|
||||
for (i = 0; i < 32; ++i)
|
||||
lengths[i] = 5;
|
||||
tinf_build_tree(dt, lengths, 32);
|
||||
tinf_build_tree(dt, lengths, 32, DIST_FAST_BITS);
|
||||
}
|
||||
|
||||
/* inflate with the bit buffer and both cursors in locals while the input holds a
|
||||
whole symbol; a match that runs past the end of the ring is left in curlen */
|
||||
static int tinf_inflate_fast(TINF_DATA *d, const TINF_TREE *lt, const TINF_TREE *dt) {
|
||||
const unsigned char *src = d->source;
|
||||
const unsigned char *const src_end = d->source_limit;
|
||||
unsigned char *const ring = d->dict_ring;
|
||||
unsigned char *const ring_end = ring + d->dict_size;
|
||||
unsigned char *out = d->dest;
|
||||
const uint16_t *const lfast = lt->fast;
|
||||
const uint16_t *const dfast = dt->fast;
|
||||
unsigned int tag = d->tag, bits = d->bitcount;
|
||||
int res = TINF_OK;
|
||||
|
||||
#define REFILL() \
|
||||
while (bits <= 24) { \
|
||||
tag |= (unsigned int) *src++ << bits; \
|
||||
bits += 8; \
|
||||
}
|
||||
#define DROP(n) (tag >>= (n), bits -= (n))
|
||||
|
||||
while (src_end - src >= FAST_IN_MIN && out < ring_end) {
|
||||
unsigned int e, len;
|
||||
int sym;
|
||||
|
||||
REFILL();
|
||||
e = lfast[tag & ((1u << LIT_FAST_BITS) - 1)];
|
||||
if (e) {
|
||||
len = e >> SYM_BITS;
|
||||
sym = e & SYM_MASK;
|
||||
} else {
|
||||
sym = tinf_decode_long(lt, tag, &len);
|
||||
if (sym < 0) {
|
||||
res = sym;
|
||||
break;
|
||||
}
|
||||
}
|
||||
DROP(len);
|
||||
|
||||
if (sym < 256) {
|
||||
*out++ = sym;
|
||||
continue;
|
||||
}
|
||||
if (sym == 256) {
|
||||
res = TINF_DONE;
|
||||
break;
|
||||
}
|
||||
sym -= 257;
|
||||
if (sym >= 29) {
|
||||
res = TINF_DATA_ERROR;
|
||||
break;
|
||||
}
|
||||
/* the lit/len code and its extra bits fit the refilled buffer together */
|
||||
unsigned int length = LENGTH_BASE[sym] + (tag & ((1u << LENGTH_BITS[sym]) - 1));
|
||||
DROP(LENGTH_BITS[sym]);
|
||||
|
||||
REFILL();
|
||||
e = dfast[tag & ((1u << DIST_FAST_BITS) - 1)];
|
||||
if (e) {
|
||||
len = e >> SYM_BITS;
|
||||
sym = e & SYM_MASK;
|
||||
} else {
|
||||
sym = tinf_decode_long(dt, tag, &len);
|
||||
}
|
||||
if (sym < 0 || sym >= 30) {
|
||||
res = TINF_DATA_ERROR;
|
||||
break;
|
||||
}
|
||||
DROP(len);
|
||||
REFILL();
|
||||
unsigned int offs = DIST_BASE[sym] + (tag & ((1u << DIST_BITS[sym]) - 1));
|
||||
DROP(DIST_BITS[sym]);
|
||||
if (offs > d->dict_size) {
|
||||
res = TINF_DICT_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
/* wrap as an index: a pointer before the ring would be undefined */
|
||||
unsigned int at = out - ring;
|
||||
const unsigned char *from = ring + (at >= offs ? at - offs : at + d->dict_size - offs);
|
||||
unsigned int n = ring_end - out;
|
||||
if (n > length)
|
||||
n = length;
|
||||
length -= n;
|
||||
/* at most two runs: up to the end of the ring, then on from its start */
|
||||
while (n) {
|
||||
unsigned int run = ring_end - from;
|
||||
if (run > n)
|
||||
run = n;
|
||||
n -= run;
|
||||
do {
|
||||
*out++ = *from++;
|
||||
} while (--run);
|
||||
if (from == ring_end)
|
||||
from = ring;
|
||||
}
|
||||
if (length) {
|
||||
d->curlen = length;
|
||||
d->lz_off = from - ring;
|
||||
}
|
||||
}
|
||||
#undef REFILL
|
||||
#undef DROP
|
||||
|
||||
d->source = src;
|
||||
d->dest = out;
|
||||
d->tag = tag;
|
||||
d->bitcount = bits;
|
||||
return res;
|
||||
}
|
||||
|
||||
/* ----------------------------- *
|
||||
* -- block inflate functions -- *
|
||||
* ----------------------------- */
|
||||
|
||||
/* given a stream and two trees, inflate next chunk of output (a byte or more) */
|
||||
/* given a stream and two trees, inflate until the ring is full or the block ends */
|
||||
static int tinf_inflate_block_data(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt) {
|
||||
if (d->curlen == 0) {
|
||||
unsigned char *const ring_end = d->dict_ring + d->dict_size;
|
||||
while (d->dest < ring_end) {
|
||||
if (d->curlen != 0) {
|
||||
unsigned int n = ring_end - d->dest;
|
||||
if (n > d->curlen)
|
||||
n = d->curlen;
|
||||
d->curlen -= n;
|
||||
do {
|
||||
*d->dest++ = d->dict_ring[d->lz_off];
|
||||
if ((unsigned) ++d->lz_off == d->dict_size)
|
||||
d->lz_off = 0;
|
||||
} while (--n);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (d->source_limit - d->source >= FAST_IN_MIN) {
|
||||
int res = tinf_inflate_fast(d, lt, dt);
|
||||
if (res != TINF_OK)
|
||||
return res;
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned int offs;
|
||||
int dist;
|
||||
int sym = tinf_decode_symbol(d, lt);
|
||||
|
||||
if (d->eof) {
|
||||
if (d->eof)
|
||||
return TINF_DATA_ERROR;
|
||||
}
|
||||
|
||||
if (sym < 0) {
|
||||
if (sym < 0)
|
||||
return sym;
|
||||
}
|
||||
|
||||
/* literal byte */
|
||||
if (sym < 256) {
|
||||
TINF_PUT(d, sym);
|
||||
return TINF_OK;
|
||||
*d->dest++ = sym;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* end of block */
|
||||
if (sym == 256) {
|
||||
if (sym == 256)
|
||||
return TINF_DONE;
|
||||
}
|
||||
|
||||
/* substring from sliding dictionary */
|
||||
sym -= 257;
|
||||
if (sym >= 29) {
|
||||
if (sym >= 29)
|
||||
return TINF_DATA_ERROR;
|
||||
}
|
||||
|
||||
/* possibly get more bits from length code */
|
||||
d->curlen = tinf_read_bits(d, LENGTH_BITS[sym], LENGTH_BASE[sym]);
|
||||
|
||||
dist = tinf_decode_symbol(d, dt);
|
||||
if (dist < 0 || dist >= 30) {
|
||||
if (dist < 0 || dist >= 30)
|
||||
return TINF_DATA_ERROR;
|
||||
}
|
||||
|
||||
/* possibly get more bits from distance code */
|
||||
offs = tinf_read_bits(d, DIST_BITS[dist], DIST_BASE[dist]);
|
||||
|
||||
/* calculate and validate actual LZ offset to use */
|
||||
if (offs > d->dict_size) {
|
||||
if (offs > d->dict_size)
|
||||
return TINF_DICT_ERROR;
|
||||
}
|
||||
/* Note: we don't try to catch offset which points to not yet filled
|
||||
part of the dictionary here. Doing so would require keeping another
|
||||
variable to track "filled in" size of the dictionary. Appearance of
|
||||
such an offset cannot lead to accessing memory outside of the
|
||||
dictionary buffer, and clients which don't want to leak unrelated
|
||||
information, should explicitly initialize dictionary buffer passed
|
||||
to uzlib. */
|
||||
|
||||
d->lz_off = d->dict_idx - offs;
|
||||
if (d->lz_off < 0) {
|
||||
d->lz_off = (int) (d->dest - d->dict_ring) - (int) offs;
|
||||
if (d->lz_off < 0)
|
||||
d->lz_off += d->dict_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* copy next byte from dict substring */
|
||||
TINF_PUT(d, d->dict_ring[d->lz_off]);
|
||||
if ((unsigned) ++d->lz_off == d->dict_size) {
|
||||
d->lz_off = 0;
|
||||
}
|
||||
d->curlen--;
|
||||
return TINF_OK;
|
||||
}
|
||||
|
||||
@@ -388,30 +493,22 @@ static int tinf_inflate_uncompressed_block(TINF_DATA *d) {
|
||||
if (d->curlen == 0) {
|
||||
unsigned int length, invlength;
|
||||
|
||||
/* get length */
|
||||
length = uzlib_get_byte(d);
|
||||
length += 256 * uzlib_get_byte(d);
|
||||
/* get one's complement of length */
|
||||
invlength = uzlib_get_byte(d);
|
||||
invlength += 256 * uzlib_get_byte(d);
|
||||
/* check length */
|
||||
/* the block starts on a byte boundary */
|
||||
tinf_take(d, d->bitcount & 7);
|
||||
length = tinf_read_bits(d, 16, 0);
|
||||
invlength = tinf_read_bits(d, 16, 0);
|
||||
if (length != (~invlength & 0x0000ffff))
|
||||
return TINF_DATA_ERROR;
|
||||
|
||||
/* increment length to properly return TINF_DONE below, without
|
||||
producing data at the same time */
|
||||
d->curlen = length + 1;
|
||||
|
||||
/* make sure we start next block on a byte boundary */
|
||||
d->bitcount = 0;
|
||||
}
|
||||
|
||||
if (--d->curlen == 0) {
|
||||
if (--d->curlen == 0)
|
||||
return TINF_DONE;
|
||||
}
|
||||
|
||||
unsigned char c = uzlib_get_byte(d);
|
||||
TINF_PUT(d, c);
|
||||
/* read first: a read that needs input may flush the output up to dest */
|
||||
unsigned char c = tinf_read_bits(d, 8, 0);
|
||||
*d->dest++ = c;
|
||||
return TINF_OK;
|
||||
}
|
||||
|
||||
@@ -419,10 +516,10 @@ static int tinf_inflate_uncompressed_block(TINF_DATA *d) {
|
||||
* -- public functions -- *
|
||||
* ---------------------- */
|
||||
|
||||
/* initialize decompression structure */
|
||||
void ota_inflate_init(TINF_DATA *d, unsigned char *dict, unsigned int dict_len) {
|
||||
d->source = NULL;
|
||||
d->source_limit = NULL;
|
||||
d->dest = dict;
|
||||
d->tag = 0;
|
||||
d->eof = 0;
|
||||
d->bitcount = 0;
|
||||
@@ -431,68 +528,56 @@ void ota_inflate_init(TINF_DATA *d, unsigned char *dict, unsigned int dict_len)
|
||||
d->btype = -1;
|
||||
d->dict_size = dict_len;
|
||||
d->dict_ring = dict;
|
||||
d->dict_idx = 0;
|
||||
d->curlen = 0;
|
||||
d->ltree.trans = d->ltrans;
|
||||
d->ltree.size = TINF_ARRAY_SIZE(d->ltrans);
|
||||
d->ltree.fast = d->lfast;
|
||||
d->dtree.trans = d->dtrans;
|
||||
d->dtree.size = TINF_ARRAY_SIZE(d->dtrans);
|
||||
d->dtree.fast = d->dfast;
|
||||
}
|
||||
|
||||
/* inflate next output bytes from compressed stream */
|
||||
int ota_inflate(TINF_DATA *d) {
|
||||
/* the caller has taken the previous ringful */
|
||||
if (d->dest == d->dict_ring + d->dict_size)
|
||||
d->dest = d->dict_ring;
|
||||
|
||||
do {
|
||||
int res;
|
||||
|
||||
/* start a new block */
|
||||
if (d->btype == -1) {
|
||||
int old_btype;
|
||||
next_blk:
|
||||
old_btype = d->btype;
|
||||
/* read final block flag */
|
||||
d->bfinal = tinf_getbit(d);
|
||||
/* read block type (2 bits) */
|
||||
d->bfinal = tinf_read_bits(d, 1, 0);
|
||||
d->btype = tinf_read_bits(d, 2, 0);
|
||||
|
||||
if (d->btype == 1 && old_btype != 1) {
|
||||
/* build fixed huffman trees */
|
||||
tinf_build_fixed_trees(&d->ltree, &d->dtree);
|
||||
} else if (d->btype == 2) {
|
||||
/* decode trees from stream */
|
||||
res = tinf_decode_trees(d, &d->ltree, &d->dtree);
|
||||
if (res != TINF_OK) {
|
||||
if (res != TINF_OK)
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* process current block */
|
||||
switch (d->btype) {
|
||||
case 0:
|
||||
/* decompress uncompressed block */
|
||||
res = tinf_inflate_uncompressed_block(d);
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
/* decompress block with fixed/dynamic huffman trees */
|
||||
/* trees were decoded previously, so it's the same routine for both */
|
||||
res = tinf_inflate_block_data(d, &d->ltree, &d->dtree);
|
||||
break;
|
||||
default:
|
||||
return TINF_DATA_ERROR;
|
||||
}
|
||||
|
||||
if (res == TINF_DONE && !d->bfinal) {
|
||||
/* the block has ended (without producing more data), but we
|
||||
can't return without data, so start procesing next block */
|
||||
if (res == TINF_DONE && !d->bfinal)
|
||||
goto next_blk;
|
||||
}
|
||||
|
||||
if (res != TINF_OK) {
|
||||
if (res != TINF_OK)
|
||||
return res;
|
||||
}
|
||||
|
||||
} while (d->dest < d->dest_limit);
|
||||
} while (d->dest < d->dict_ring + d->dict_size);
|
||||
|
||||
return TINF_OK;
|
||||
}
|
||||
@@ -16,10 +16,14 @@ enum OtaInflateResult {
|
||||
OTA_INFLATE_DICT_ERROR = -5,
|
||||
};
|
||||
|
||||
/* Code bits each lookup table resolves; longer codes take the canonical decode.
|
||||
The distance alphabet has 30 symbols, so its table is kept small */
|
||||
enum { OTA_INFLATE_LIT_FAST_BITS = 9, OTA_INFLATE_DIST_FAST_BITS = 6 };
|
||||
|
||||
struct OtaInflateTree {
|
||||
uint16_t table[16]; /* table of code length counts */
|
||||
uint16_t *trans; /* code -> symbol translation table, size entries */
|
||||
uint16_t size;
|
||||
uint16_t *fast; /* (len << 9) | symbol by the next input bits, 0 = longer code */
|
||||
uint16_t *trans; /* code -> symbol translation table */
|
||||
};
|
||||
|
||||
struct OtaInflateState {
|
||||
@@ -29,9 +33,8 @@ struct OtaInflateState {
|
||||
/* Called when source is exhausted; returns the next byte or -1 at EOF.
|
||||
It may refill source/source_limit for buffered operation. */
|
||||
int (*source_read_cb)(struct OtaInflateState *d);
|
||||
/* Output cursor and one past the end of the output buffer */
|
||||
/* Output cursor in dict_ring: each call fills the ring from here */
|
||||
unsigned char *dest;
|
||||
unsigned char *dest_limit;
|
||||
/* Ring window holding the last dict_size output bytes for back references */
|
||||
unsigned char *dict_ring;
|
||||
|
||||
@@ -39,9 +42,8 @@ struct OtaInflateState {
|
||||
unsigned int curlen;
|
||||
int lz_off;
|
||||
unsigned int dict_size;
|
||||
unsigned int dict_idx;
|
||||
|
||||
/* One word: btype is -1 between blocks, bitcount never exceeds 7 */
|
||||
/* btype is -1 between blocks; bitcount is the number of valid bits in tag (<= 32) */
|
||||
int8_t btype;
|
||||
uint8_t bfinal;
|
||||
uint8_t bitcount;
|
||||
@@ -51,13 +53,15 @@ struct OtaInflateState {
|
||||
struct OtaInflateTree dtree; /* dynamic distance tree */
|
||||
uint16_t ltrans[288];
|
||||
uint16_t dtrans[32]; /* the distance alphabet has 30 symbols, so the tree is kept small */
|
||||
uint16_t lfast[1 << OTA_INFLATE_LIT_FAST_BITS];
|
||||
uint16_t dfast[1 << OTA_INFLATE_DIST_FAST_BITS];
|
||||
};
|
||||
|
||||
/* dict must cover the encoder's window (its largest back reference) */
|
||||
void ota_inflate_init(struct OtaInflateState *d, unsigned char *dict, unsigned int dict_len);
|
||||
/* Fills dest up to dest_limit (OK) or to the end of the stream (DONE). dest may
|
||||
alias dict only if dest_limit - dest == dict_len and dest is reset to dict
|
||||
exactly when a call returns OK, so the ring index and dest stay in lockstep */
|
||||
/* Inflates into dict until it is full (OK) or the stream ends (DONE); the output
|
||||
is dict up to dest. The next call after OK starts dict over, so take its
|
||||
contents first */
|
||||
int ota_inflate(struct OtaInflateState *d);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -65,16 +65,15 @@ ota::OTAResponseTypes ESPHomeOTAComponent::inflate_data_(uint8_t *in, size_t ima
|
||||
};
|
||||
|
||||
int res;
|
||||
session.flushed = 0;
|
||||
do {
|
||||
// The ring index wrapped to 0 exactly when the window filled
|
||||
session.dest = session.window;
|
||||
session.dest_limit = session.window + OTA_INFLATE_WINDOW_SIZE;
|
||||
session.flushed = 0;
|
||||
res = ota_inflate(&session);
|
||||
// A stored block keeps emitting zeros after a failed read, hence eof
|
||||
if (res < 0 || session.eof)
|
||||
break;
|
||||
session.error = this->inflate_flush_(session);
|
||||
// A full window is all in flash; the next call starts it over
|
||||
session.flushed = 0;
|
||||
} while (res != OTA_INFLATE_DONE && session.error == ota::OTA_RESPONSE_OK);
|
||||
|
||||
// Transport and flash failures are logged where they happen
|
||||
|
||||
@@ -34,10 +34,7 @@ void FT63X6Touchscreen::setup() {
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_ANY_EDGE);
|
||||
}
|
||||
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->hard_reset_();
|
||||
}
|
||||
pulse_reset(this->reset_pin_, {.low_ms = 10});
|
||||
|
||||
// Get touch resolution
|
||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||
@@ -57,14 +54,6 @@ void FT63X6Touchscreen::setup() {
|
||||
this->write_byte(FT6X36_ADDR_TOUCHRATE_ACTIVE, 0x0E);
|
||||
}
|
||||
|
||||
void FT63X6Touchscreen::hard_reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
|
||||
void FT63X6Touchscreen::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "FT63X6 Touchscreen:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
|
||||
@@ -27,7 +27,6 @@ class FT63X6Touchscreen final : public Touchscreen, public i2c::I2CDevice {
|
||||
void set_threshold(uint8_t threshold) { this->threshold_ = threshold; }
|
||||
|
||||
protected:
|
||||
void hard_reset_();
|
||||
void update_touches() override;
|
||||
|
||||
InternalGPIOPin *interrupt_pin_{nullptr};
|
||||
|
||||
@@ -83,13 +83,7 @@ void GSL3670Touchscreen::update_touches() {
|
||||
void GSL3670Touchscreen::clear_reg_() {
|
||||
ESP_LOGD(TAG, "clear_reg");
|
||||
|
||||
// GPIO reset pulse
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(1);
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(5);
|
||||
}
|
||||
pulse_reset(this->reset_pin_, {.low_ms = 1, .high_after_ms = 5});
|
||||
|
||||
this->write_reg8_(0x88, 0x01);
|
||||
// delay(5);
|
||||
@@ -106,12 +100,7 @@ void GSL3670Touchscreen::clear_reg_() {
|
||||
void GSL3670Touchscreen::reset_() {
|
||||
ESP_LOGD(TAG, "reset");
|
||||
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(1);
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(5);
|
||||
}
|
||||
pulse_reset(this->reset_pin_, {.low_ms = 1, .high_after_ms = 5});
|
||||
|
||||
this->write_reg8_(0xe4, 0x04);
|
||||
|
||||
|
||||
@@ -19,12 +19,7 @@ void ICNT86Touchscreen::setup() {
|
||||
}
|
||||
|
||||
// Perform reset if necessary
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
pulse_reset(this->reset_pin_, {.low_ms = 10});
|
||||
|
||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||
this->x_raw_max_ = this->display_->get_native_width();
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
"""Shared base for the infrared and radio_frequency entity components."""
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import remote_base
|
||||
from esphome.components.api import DOMAIN as API_DOMAIN
|
||||
from esphome.const import CONF_API
|
||||
from esphome.core import CORE
|
||||
from esphome.core.entity_helpers import queue_entity_register
|
||||
from esphome.types import ConfigType
|
||||
@@ -24,8 +23,10 @@ async def attach_transmitter(var: cg.MockObj, config: ConfigType, key: str) -> N
|
||||
does. Without this call anywhere in the build a request is answered as
|
||||
soon as the frame is handed over.
|
||||
"""
|
||||
from esphome.components import remote_base
|
||||
|
||||
await remote_base.register_transmittable(var, config, key)
|
||||
if API_DOMAIN in CORE.config:
|
||||
if CONF_API in CORE.config:
|
||||
cg.add_define("USE_IR_RF_TRANSMIT_COMPLETE")
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.core.config import BOARD_MAX_LENGTH
|
||||
from esphome.helpers import copy_file_if_changed
|
||||
from esphome.helpers import copy_file_if_changed, write_file
|
||||
from esphome.platformio.toolchain import copy_ccache_script
|
||||
from esphome.storage_json import StorageJSON
|
||||
|
||||
@@ -656,6 +656,26 @@ async def component_to_code(config):
|
||||
await cg.register_component(var, config)
|
||||
|
||||
|
||||
# Called by the OTA upload through the platforms' ota_image hook
|
||||
def device_ota_image(firmware: Path) -> Path:
|
||||
"""The file to send for an app update: the UF2 without the images the
|
||||
device skips, written next to the build output, or ``firmware`` itself
|
||||
when there is nothing to drop."""
|
||||
from .ota_uf2 import device_ota_uf2
|
||||
|
||||
data = firmware.read_bytes()
|
||||
if (trimmed := device_ota_uf2(data)) is None:
|
||||
return firmware
|
||||
image = CORE.relative_pioenvs_path(CORE.name, "firmware.ota.uf2")
|
||||
write_file(image, trimmed)
|
||||
_LOGGER.info(
|
||||
"Sending the images this device writes: %s of %s bytes",
|
||||
len(trimmed),
|
||||
len(data),
|
||||
)
|
||||
return image
|
||||
|
||||
|
||||
# Called by writer.py
|
||||
def copy_files() -> None:
|
||||
script_dir = Path(__file__).parent
|
||||
|
||||
@@ -91,6 +91,10 @@ async def pin_to_code(config: ConfigType) -> MockObj:
|
||||
# Called by writer.py; delegates to the shared libretiny implementation.
|
||||
def copy_files() -> None:
|
||||
libretiny.copy_files()
|
||||
|
||||
|
||||
# Called by the OTA upload; the shared libretiny implementation.
|
||||
ota_image = libretiny.device_ota_image
|
||||
'''
|
||||
|
||||
BASE_CODE_BOARDS = '''
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
"""Trim a LibreTiny UF2 package to the images a device writes over OTA.
|
||||
|
||||
A LibreTiny UF2 holds one image per OTA scheme: the device schemes (single,
|
||||
dual slot 1, dual slot 2) and the flasher schemes used over UART. The device
|
||||
skips every block of an image whose scheme it does not run (uf2ota), so an
|
||||
image no device scheme writes is only sent to be thrown away. On single OTA
|
||||
chips (BK72xx, LN882H) that is the whole flasher image, more than half the
|
||||
package. The legacy OTA format names the same device partitions, so devices
|
||||
that read it write the same images.
|
||||
"""
|
||||
|
||||
import struct
|
||||
|
||||
UF2_BLOCK_SIZE = 512
|
||||
UF2_MAGIC_HEAD = struct.pack("<2I", 0x0A324655, 0x9E5D5157)
|
||||
UF2_MAGIC_TAIL = struct.pack("<I", 0x0AB16F30)
|
||||
UF2_FLAG_HAS_TAGS = 0x8000
|
||||
# Header words after the magic: flags, target address, payload length, block
|
||||
# number, block count
|
||||
_HEADER_WORDS = struct.Struct("<5I")
|
||||
_SEQ_OFFSET = 20
|
||||
_DATA_START = 32
|
||||
_DATA_END = UF2_BLOCK_SIZE - len(UF2_MAGIC_TAIL)
|
||||
|
||||
# One partition index nibble per scheme, high nibble first: device single,
|
||||
# dual 1 and dual 2 are the first three, the flasher schemes the next three
|
||||
UF2_TAG_OTA_PART_INFO = 0xC0EE0C
|
||||
# Tags uf2ota acts on beyond the image they sit in; one in an image that
|
||||
# would be dropped leaves the package untrimmed
|
||||
_KEEP_TAGS = frozenset(
|
||||
{
|
||||
0x5D57D0, # OTA_FORMAT_1
|
||||
0x6C8492, # OTA_FORMAT_2
|
||||
0x6EC68A, # OTA_PART_LIST
|
||||
0xB948DE, # BINPATCH
|
||||
0x8288ED, # FAL_PTABLE
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _tags(block: bytes, length: int) -> dict[int, bytes] | None:
|
||||
"""The extension tags after a block's payload, or None if malformed."""
|
||||
tags: dict[int, bytes] = {}
|
||||
pos = _DATA_START + length
|
||||
while pos + 4 <= _DATA_END and (size := block[pos]):
|
||||
if size < 4 or pos + size > _DATA_END:
|
||||
return None
|
||||
tags[int.from_bytes(block[pos + 1 : pos + 4], "little")] = block[
|
||||
pos + 4 : pos + size
|
||||
]
|
||||
pos += (size + 3) & ~3
|
||||
return tags
|
||||
|
||||
|
||||
def device_ota_uf2(data: bytes) -> bytes | None:
|
||||
"""``data`` without the images no device scheme writes.
|
||||
|
||||
None when there is nothing to drop, or when ``data`` is not a well
|
||||
formed UF2, so the caller sends the file as it is.
|
||||
"""
|
||||
if len(data) % UF2_BLOCK_SIZE:
|
||||
return None
|
||||
count = len(data) // UF2_BLOCK_SIZE
|
||||
kept: list[bytes] = []
|
||||
keep = True
|
||||
for seq in range(count):
|
||||
block = data[seq * UF2_BLOCK_SIZE : (seq + 1) * UF2_BLOCK_SIZE]
|
||||
flags, _, length, block_seq, block_count = _HEADER_WORDS.unpack_from(block, 8)
|
||||
# A missing or repeated block must stay visible to the device, which
|
||||
# takes the update size from the block count
|
||||
if (
|
||||
block[:8] != UF2_MAGIC_HEAD
|
||||
or block[_DATA_END:] != UF2_MAGIC_TAIL
|
||||
or _DATA_START + length > _DATA_END
|
||||
or (block_seq, block_count) != (seq, count)
|
||||
):
|
||||
return None
|
||||
tags = _tags(block, length) if flags & UF2_FLAG_HAS_TAGS else {}
|
||||
if tags is None:
|
||||
return None
|
||||
if (part_info := tags.get(UF2_TAG_OTA_PART_INFO)) is not None:
|
||||
# uf2ota rejects a selector without the whole scheme map
|
||||
if len(part_info) < 3:
|
||||
return None
|
||||
# Any of the three device scheme nibbles names a partition
|
||||
keep = int.from_bytes(part_info[:2], "big") >> 4 != 0
|
||||
if keep:
|
||||
kept.append(block)
|
||||
elif not _KEEP_TAGS.isdisjoint(tags):
|
||||
# Without its image's OTA_PART_INFO the device would write this
|
||||
# block to the partition of the image before it
|
||||
return None
|
||||
if len(kept) == count:
|
||||
return None
|
||||
out = bytearray(b"".join(kept))
|
||||
for seq in range(len(kept)):
|
||||
struct.pack_into("<2I", out, seq * UF2_BLOCK_SIZE + _SEQ_OFFSET, seq, len(kept))
|
||||
return bytes(out)
|
||||
@@ -77,3 +77,7 @@ async def pin_to_code(config: ConfigType) -> MockObj:
|
||||
# Called by writer.py; delegates to the shared libretiny implementation.
|
||||
def copy_files() -> None:
|
||||
libretiny.copy_files()
|
||||
|
||||
|
||||
# Called by the OTA upload; the shared libretiny implementation.
|
||||
ota_image = libretiny.device_ota_image
|
||||
@@ -2,7 +2,6 @@ from collections.abc import Callable
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import audio
|
||||
from esphome.components.const import CONF_BITRATE
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -116,6 +115,8 @@ def validate_preferred_format(
|
||||
"""
|
||||
|
||||
def validator(config: ConfigType) -> ConfigType:
|
||||
from esphome.components import audio
|
||||
|
||||
# Inherit settings from audio device if not manually set
|
||||
inherit_property_from(CONF_NUM_CHANNELS, audio_device_key)(config)
|
||||
inherit_property_from(CONF_SAMPLE_RATE, audio_device_key)(config)
|
||||
@@ -155,6 +156,8 @@ def request_codecs_for_format_configs(
|
||||
|
||||
Public API for external components. Do not remove.
|
||||
"""
|
||||
from esphome.components import audio
|
||||
|
||||
needed_formats: set[str] = set()
|
||||
need_all = False
|
||||
|
||||
|
||||
@@ -107,12 +107,7 @@ void MipiDsi::setup() {
|
||||
}
|
||||
#endif
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(true);
|
||||
pulse_reset(this->reset_pin_, {.high_before_ms = 5, .low_ms = 5});
|
||||
} else {
|
||||
esp_lcd_panel_io_tx_param(this->io_handle_, SW_RESET_CMD, nullptr, 0);
|
||||
}
|
||||
|
||||
@@ -33,14 +33,7 @@ void MipiRgb::setup_enables_() {
|
||||
}
|
||||
delay(10);
|
||||
}
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
pulse_reset(this->reset_pin_, {.high_before_ms = 5, .low_ms = 5});
|
||||
}
|
||||
|
||||
#ifdef USE_SPI
|
||||
|
||||
@@ -151,15 +151,8 @@ class MipiSpi : public display::Display,
|
||||
pin->setup();
|
||||
pin->digital_write(true);
|
||||
}
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(true);
|
||||
// required delay after reset is already in the init sequence, don't duplicate
|
||||
}
|
||||
// required delay after reset is already in the init sequence, don't duplicate
|
||||
pulse_reset(this->reset_pin_, {.high_before_ms = 5, .low_ms = 5});
|
||||
|
||||
// need to know when the display is ready for SLPOUT command - will be 120ms after reset
|
||||
auto when = millis() + 120;
|
||||
|
||||
@@ -226,6 +226,12 @@ def set_sdkconfig_options(config: ConfigType) -> None:
|
||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_DNS64_CLIENT", not border_router)
|
||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT", not border_router)
|
||||
if not border_router:
|
||||
# The DNS64 hook breaks IPv6 literal lookups; see openthread_esp.cpp.
|
||||
# Fixed upstream on ESP-IDF master, remove this once a release includes
|
||||
# "fix(openthread): bypass DNS64 synthesis for IPv6 literals",
|
||||
# https://github.com/espressif/esp-idf/commit/87b133ee1a7e91c1ee95176b4733b7f72236e5da
|
||||
# (merged in https://github.com/espressif/esp-idf/commit/83239509832bf5d427935cd365f4b62a320e8765)
|
||||
cg.add_build_flag("-Wl,--wrap=lwip_hook_dns_external_resolve")
|
||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT_MAX_SERVICES", 5)
|
||||
|
||||
if border_router:
|
||||
|
||||
@@ -35,6 +35,10 @@
|
||||
#include "esp_vfs_eventfd.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#ifndef USE_OPENTHREAD_BORDER_ROUTER
|
||||
#include "lwip/dns.h"
|
||||
#include "lwip/ip_addr.h"
|
||||
#endif
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "openthread");
|
||||
|
||||
@@ -489,4 +493,26 @@ InstanceLock::~InstanceLock() {
|
||||
}
|
||||
|
||||
} // namespace esphome::openthread
|
||||
|
||||
#ifndef USE_OPENTHREAD_BORDER_ROUTER
|
||||
// ESP-IDF's DNS64 hook runs before lwIP's literal address check and retries every lookup as IPv4,
|
||||
// so an IPv6 literal never resolves. Let lwIP handle those; linked with --wrap from __init__.py.
|
||||
// Fixed upstream on ESP-IDF master, remove this once a release includes
|
||||
// "fix(openthread): bypass DNS64 synthesis for IPv6 literals",
|
||||
// https://github.com/espressif/esp-idf/commit/87b133ee1a7e91c1ee95176b4733b7f72236e5da
|
||||
// (merged in https://github.com/espressif/esp-idf/commit/83239509832bf5d427935cd365f4b62a320e8765)
|
||||
// NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
|
||||
extern "C" {
|
||||
int __real_lwip_hook_dns_external_resolve(const char *name, ip_addr_t *addr, dns_found_callback found,
|
||||
void *callback_arg, u8_t addrtype, err_t *err);
|
||||
int __wrap_lwip_hook_dns_external_resolve(const char *name, ip_addr_t *addr, dns_found_callback found,
|
||||
void *callback_arg, u8_t addrtype, err_t *err) {
|
||||
ip_addr_t literal;
|
||||
if (ipaddr_aton(name, &literal) && IP_IS_V6_VAL(literal))
|
||||
return 0;
|
||||
return __real_lwip_hook_dns_external_resolve(name, addr, found, callback_arg, addrtype, err);
|
||||
}
|
||||
} // extern "C"
|
||||
// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
|
||||
#endif // USE_OPENTHREAD_BORDER_ROUTER
|
||||
#endif
|
||||
@@ -9,23 +9,10 @@ ESPHOME_LOG_TAG(TAG, "pcd_8544");
|
||||
|
||||
void PCD8544::setup_pins_() {
|
||||
this->spi_setup();
|
||||
this->init_reset_();
|
||||
pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10});
|
||||
this->dc_pin_->setup();
|
||||
}
|
||||
|
||||
void PCD8544::init_reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(1);
|
||||
// Trigger Reset
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
// Wake up
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
|
||||
void PCD8544::initialize() {
|
||||
this->init_internal_(this->get_buffer_length_());
|
||||
|
||||
|
||||
@@ -57,8 +57,6 @@ class PCD8544 final : public display::DisplayBuffer,
|
||||
|
||||
void setup_pins_();
|
||||
|
||||
void init_reset_();
|
||||
|
||||
size_t get_buffer_length_();
|
||||
|
||||
void start_command_();
|
||||
|
||||
@@ -77,3 +77,7 @@ async def pin_to_code(config: ConfigType) -> MockObj:
|
||||
# Called by writer.py; delegates to the shared libretiny implementation.
|
||||
def copy_files() -> None:
|
||||
libretiny.copy_files()
|
||||
|
||||
|
||||
# Called by the OTA upload; the shared libretiny implementation.
|
||||
ota_image = libretiny.device_ota_image
|
||||
@@ -122,18 +122,7 @@ void SH1122::fill(Color color) {
|
||||
const uint8_t fill = (color4 & SH1122_COLORMASK) | ((color4 & SH1122_COLORMASK) << SH1122_COLORSHIFT);
|
||||
memset(this->buffer_, fill, this->get_buffer_length_());
|
||||
}
|
||||
void SH1122::init_reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(1);
|
||||
// Trigger Reset
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
// Wake up
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
void SH1122::init_reset_() { pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}); }
|
||||
const LogString *SH1122::model_str_() {
|
||||
switch (this->model_) {
|
||||
case SH1122_MODEL_256_64:
|
||||
|
||||
@@ -355,18 +355,7 @@ void SSD1306::fill(Color color) {
|
||||
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
|
||||
this->buffer_[i] = fill;
|
||||
}
|
||||
void SSD1306::init_reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(1);
|
||||
// Trigger Reset
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
// Wake up
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
void SSD1306::init_reset_() { pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}); }
|
||||
const LogString *SSD1306::model_str_() {
|
||||
return ModelStrings::get_log_str(static_cast<uint8_t>(this->model_), ModelStrings::LAST_INDEX);
|
||||
}
|
||||
|
||||
@@ -184,18 +184,7 @@ void SSD1322::fill(Color color) {
|
||||
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
|
||||
this->buffer_[i] = fill;
|
||||
}
|
||||
void SSD1322::init_reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(1);
|
||||
// Trigger Reset
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
// Wake up
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
void SSD1322::init_reset_() { pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}); }
|
||||
const char *SSD1322::model_str_() {
|
||||
switch (this->model_) {
|
||||
case SSD1322_MODEL_256_64:
|
||||
|
||||
@@ -211,18 +211,7 @@ void SSD1325::fill(Color color) {
|
||||
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
|
||||
this->buffer_[i] = fill;
|
||||
}
|
||||
void SSD1325::init_reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(1);
|
||||
// Trigger Reset
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
// Wake up
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
void SSD1325::init_reset_() { pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}); }
|
||||
const char *SSD1325::model_str_() {
|
||||
switch (this->model_) {
|
||||
case SSD1325_MODEL_128_32:
|
||||
|
||||
@@ -160,18 +160,7 @@ void SSD1327::fill(Color color) {
|
||||
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
|
||||
this->buffer_[i] = fill;
|
||||
}
|
||||
void SSD1327::init_reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(1);
|
||||
// Trigger Reset
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
// Wake up
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
void SSD1327::init_reset_() { pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}); }
|
||||
const char *SSD1327::model_str_() {
|
||||
switch (this->model_) {
|
||||
case SSD1327_MODEL_128_128:
|
||||
|
||||
@@ -142,17 +142,6 @@ void SSD1331::fill(Color color) {
|
||||
}
|
||||
}
|
||||
}
|
||||
void SSD1331::init_reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(1);
|
||||
// Trigger Reset
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
// Wake up
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
void SSD1331::init_reset_() { pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}); }
|
||||
|
||||
} // namespace esphome::ssd1331_base
|
||||
@@ -174,18 +174,7 @@ void SSD1351::fill(Color color) {
|
||||
}
|
||||
}
|
||||
}
|
||||
void SSD1351::init_reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(1);
|
||||
// Trigger Reset
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
// Wake up
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
void SSD1351::init_reset_() { pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}); }
|
||||
const char *SSD1351::model_str_() {
|
||||
switch (this->model_) {
|
||||
case SSD1351_MODEL_128_96:
|
||||
|
||||
@@ -9,12 +9,8 @@ ESPHOME_LOG_TAG(TAG, "st7123.touchscreen");
|
||||
|
||||
void ST7123Touchscreen::setup() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(false); // TP_RESX is active low, assert for at least tRSTW (2ms)
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(true);
|
||||
// TP_RESX is active low, assert for at least tRSTW (2ms)
|
||||
pulse_reset(this->reset_pin_, {.high_before_ms = 5, .low_ms = 5});
|
||||
// The controller needs up to 20ms to initialize after reset before it can be accessed.
|
||||
this->setup_time_ = millis() + 30;
|
||||
}
|
||||
|
||||
@@ -137,18 +137,7 @@ void ST7567::fill(Color color) {
|
||||
memset(buffer_, fill, this->get_buffer_length_());
|
||||
}
|
||||
|
||||
void ST7567::init_reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(1);
|
||||
// Trigger Reset
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
// Wake up
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
void ST7567::init_reset_() { pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}); }
|
||||
|
||||
const char *ST7567::model_str_() { return "ST7567 128x64"; }
|
||||
|
||||
|
||||
@@ -54,10 +54,7 @@ void TT21100Touchscreen::setup() {
|
||||
}
|
||||
|
||||
// Perform reset if necessary
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_();
|
||||
}
|
||||
pulse_reset(this->reset_pin_, {.low_ms = 10, .high_after_ms = 10});
|
||||
|
||||
// Update display dimensions if they were updated during display setup
|
||||
if (this->display_ != nullptr) {
|
||||
@@ -122,15 +119,6 @@ void TT21100Touchscreen::update_touches() {
|
||||
}
|
||||
}
|
||||
|
||||
void TT21100Touchscreen::reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
|
||||
void TT21100Touchscreen::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "TT21100 Touchscreen:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
|
||||
@@ -28,8 +28,6 @@ class TT21100Touchscreen final : public Touchscreen, public i2c::I2CDevice {
|
||||
void register_button_listener(TT21100ButtonListener *listener) { this->button_listeners_.push_back(listener); }
|
||||
|
||||
protected:
|
||||
void reset_();
|
||||
|
||||
void update_touches() override;
|
||||
|
||||
InternalGPIOPin *interrupt_pin_;
|
||||
|
||||
@@ -245,6 +245,22 @@ WiFiConfigureAction = wifi_ns.class_(
|
||||
)
|
||||
|
||||
|
||||
# The supplicant parses the bootstrapping key as a DER encoded P-256 private key in hex; these wrap the
|
||||
# 32 byte key the way the ESP-IDF enrollee example does
|
||||
DPP_KEY_DER_PREFIX = "30310201010420"
|
||||
DPP_KEY_DER_SUFFIX = "a00a06082a8648ce3d030107"
|
||||
|
||||
|
||||
def _validate_dpp_private_key(value: object) -> str:
|
||||
"""The key is 32 bytes written as 64 hexadecimal characters."""
|
||||
value = cv.string_strict(value)
|
||||
if len(value) != 64 or any(c not in "0123456789abcdefABCDEF" for c in value):
|
||||
raise cv.Invalid(
|
||||
"private_key must be 32 bytes written as 64 hexadecimal characters"
|
||||
)
|
||||
return value.lower()
|
||||
|
||||
|
||||
def _validate_dpp_device_info(value: object) -> str:
|
||||
"""The supplicant puts the text on a space separated command line and reads it up to the next space."""
|
||||
value = cv.string_strict(value)
|
||||
@@ -322,7 +338,7 @@ EAP_AUTH_SCHEMA = cv.All(
|
||||
# Wi-Fi Easy Connect (DPP) enrollee; the ESP-IDF supplicant supports the QR code method
|
||||
DPP_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_PRIVATE_KEY): cv.sensitive(cv.string_strict),
|
||||
cv.Optional(CONF_PRIVATE_KEY): cv.sensitive(_validate_dpp_private_key),
|
||||
cv.Optional(CONF_DEVICE_INFO): _validate_dpp_device_info,
|
||||
cv.Optional(CONF_CHANNELS, default=[6]): cv.ensure_list(
|
||||
cv.int_range(min=1, max=14)
|
||||
@@ -904,7 +920,7 @@ async def to_code(config):
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DPP_SUPPORT", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_HOST_FEAT_WIFI_EXT_DPP", True)
|
||||
if (key := dpp.get(CONF_PRIVATE_KEY)) is not None:
|
||||
cg.add(var.set_dpp_key(key))
|
||||
cg.add(var.set_dpp_key(f"{DPP_KEY_DER_PREFIX}{key}{DPP_KEY_DER_SUFFIX}"))
|
||||
if (info := dpp.get(CONF_DEVICE_INFO)) is not None:
|
||||
cg.add(var.set_dpp_device_info(info))
|
||||
cg.add(var.set_dpp_channels(",".join(str(c) for c in dpp[CONF_CHANNELS])))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -18,4 +19,18 @@ void log_pin(const char *tag, const char *prefix, GPIOPin *pin) {
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace internal {
|
||||
void pulse_reset_ms(GPIOPin *pin, uint32_t high_before_ms, uint32_t low_ms, uint32_t high_after_ms) {
|
||||
if (pin == nullptr)
|
||||
return;
|
||||
pin->setup();
|
||||
const uint32_t waits[] = {high_before_ms, low_ms, high_after_ms};
|
||||
// Level for each step is high, low, high; a zero high_before_ms skips the first step
|
||||
for (size_t i = high_before_ms == 0; i < 3; i++) {
|
||||
pin->digital_write(i != 1);
|
||||
delay(waits[i]);
|
||||
}
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
} // namespace esphome
|
||||
@@ -144,4 +144,24 @@ void log_pin(const char *tag, const __FlashStringHelper *prefix, GPIOPin *pin);
|
||||
void log_pin(const char *tag, const char *prefix, GPIOPin *pin);
|
||||
#endif
|
||||
|
||||
/// Waveform for pulse_reset(): high, low, then high again. Name the fields at the call site, e.g.
|
||||
/// pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}).
|
||||
struct ResetPulse {
|
||||
uint32_t high_before_ms{0}; ///< Drive high this long first; skipped when 0
|
||||
uint32_t low_ms{0}; ///< Hold the pin low (in reset) this long
|
||||
uint32_t high_after_ms{0}; ///< Wait this long after releasing the pin
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
/// Out-of-line body of pulse_reset(); call that instead.
|
||||
void pulse_reset_ms(GPIOPin *pin, uint32_t high_before_ms, uint32_t low_ms, uint32_t high_after_ms);
|
||||
} // namespace internal
|
||||
|
||||
/// Set up an active-low reset pin and drive it through \p pulse. Blocks for the whole sequence;
|
||||
/// does nothing when pin is null. Inline so the fields reach pulse_reset_ms() in registers
|
||||
/// (RISC-V would otherwise pass the struct by reference).
|
||||
ESPHOME_ALWAYS_INLINE inline void pulse_reset(GPIOPin *pin, ResetPulse pulse) {
|
||||
internal::pulse_reset_ms(pin, pulse.high_before_ms, pulse.low_ms, pulse.high_after_ms);
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
@@ -1,385 +0,0 @@
|
||||
"""Rewrite the OTA encryption key in place: the `key:` line the loader read
|
||||
it from, or the secrets.yaml line a `!secret` on it points to."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
from esphome.components.noise import static_encryption_key
|
||||
from esphome.const import CONF_API, CONF_ENCRYPTION, CONF_KEY
|
||||
from esphome.core import CORE, EsphomeError
|
||||
from esphome.espota2 import CONF_OLD_KEY, esphome_ota_items
|
||||
from esphome.types import ConfigType
|
||||
from esphome.yaml_edit import (
|
||||
LineEdit,
|
||||
editable_file,
|
||||
field_line_re,
|
||||
own_documents,
|
||||
read_text,
|
||||
rewrite,
|
||||
source_line,
|
||||
source_of,
|
||||
)
|
||||
from esphome.yaml_util import secrets_path_for
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_DOMAIN = "ota_key_edit"
|
||||
|
||||
|
||||
@dataclass
|
||||
class KeyEdit(LineEdit):
|
||||
"""A line edit with the secret it holds and the other configs using it."""
|
||||
|
||||
secret: str | None = None
|
||||
shared_with: list[Path] = field(default_factory=list)
|
||||
|
||||
|
||||
def _api_encryption() -> ConfigType:
|
||||
return ((CORE.raw_config or {}).get(CONF_API) or {}).get(CONF_ENCRYPTION) or {}
|
||||
|
||||
|
||||
def _key_blocks(raw: ConfigType, key: str) -> list[ConfigType]:
|
||||
"""The api and esphome ota ``encryption:`` mappings whose key is ``key``."""
|
||||
blocks = [raw.get(CONF_API) or {}, *esphome_ota_items(raw)]
|
||||
found = []
|
||||
for block in blocks:
|
||||
if (node := static_encryption_key(block)) is None or str(node) != key:
|
||||
continue
|
||||
encryption = block[CONF_ENCRYPTION]
|
||||
if source_of(encryption, CONF_KEY) is not None:
|
||||
found.append(encryption)
|
||||
elif hasattr(next(k for k in encryption if k == CONF_KEY), "esp_range"):
|
||||
# Read from a file but not on a line of its own, as with a merge
|
||||
# key; only a key validation added, which has no range at all,
|
||||
# is passed over (a bare ota block takes the api key)
|
||||
raise EsphomeError(
|
||||
f"The '{CONF_KEY}' of an '{CONF_ENCRYPTION}:' block is not on a "
|
||||
"line of its own; edit the key by hand"
|
||||
)
|
||||
return found
|
||||
|
||||
|
||||
_INDENT_RE = re.compile(r"\s*")
|
||||
_KEY_FIELD_RE = re.compile(rf"^\s*{CONF_KEY}\s*:")
|
||||
_BARE_BLOCK_RE = re.compile(rf"^\s*{CONF_ENCRYPTION}\s*:\s*(#.*)?$")
|
||||
_YAML_SUFFIXES = (".yaml", ".yml")
|
||||
_INCLUDE_DIR_RE = re.compile(r"!include_dir_\w+\s+(?P<dir>[^\s#]+)")
|
||||
# An include or package file whose path holds a substitution
|
||||
_SUBSTITUTED_INCLUDE_RE = re.compile(
|
||||
r"(?:!include\w*\s+|\bfile:\s*)[\"']?(?P<target>[^\s#\"']*\$[^\s#\"']*)"
|
||||
)
|
||||
|
||||
|
||||
def _secret_ref(name: str = r"[^\s\"']+") -> str:
|
||||
"""`!secret name` as the whole scalar: `!secret ota key` is not `ota`, and
|
||||
a comment needs whitespace before its `#`, so `a#b` is one name."""
|
||||
return rf"!secret\s+(?P<q>[\"']?)(?P<name>{name})(?P=q)(?=\s+#|\s*$)"
|
||||
|
||||
|
||||
def _secret_field_re(name: str) -> re.Pattern[str]:
|
||||
"""``name: !secret <any>``."""
|
||||
return re.compile(rf"^\s*{name}\s*:\s*{_secret_ref()}")
|
||||
|
||||
|
||||
def _secret_use_re(name: str) -> re.Pattern[str]:
|
||||
"""A use of the secret ``name`` anywhere on a line."""
|
||||
return re.compile(_secret_ref(re.escape(name)))
|
||||
|
||||
|
||||
_KEY_SECRET_RE = _secret_field_re(CONF_KEY)
|
||||
_OLD_KEY_SECRET_RE = _secret_field_re(CONF_OLD_KEY)
|
||||
|
||||
|
||||
def _indent(text: str) -> str:
|
||||
return _INDENT_RE.match(text).group()
|
||||
|
||||
|
||||
def _without_comment(text: str) -> str:
|
||||
"""The line up to its comment: a `#` after whitespace that is not inside
|
||||
a quoted scalar, so `!include "shared # ota.yaml"` keeps its name."""
|
||||
quote = ""
|
||||
for i, char in enumerate(text):
|
||||
if quote:
|
||||
if char == quote:
|
||||
quote = ""
|
||||
elif char in "\"'":
|
||||
quote = char
|
||||
elif char == "#" and (i == 0 or text[i - 1].isspace()):
|
||||
return text[:i]
|
||||
return text
|
||||
|
||||
|
||||
def _file_use_re(file_name: str) -> re.Pattern[str]:
|
||||
"""The file name at a path boundary; `wifi_api.yaml` is not `api.yaml`."""
|
||||
return re.compile(rf"(?<![\w.-]){re.escape(file_name)}(?![\w.])")
|
||||
|
||||
|
||||
def _root_indent(lines: list[str]) -> str:
|
||||
"""The indent of the first content line; a root mapping may be indented."""
|
||||
content = (
|
||||
t for t in lines if t.strip() and not t.strip().startswith(("#", "---", "%"))
|
||||
)
|
||||
return next((_indent(t) for t in content), "")
|
||||
|
||||
|
||||
def _config_texts() -> tuple[list[tuple[Path, str]], list[str]]:
|
||||
"""Every yaml file under the config dir with its text, and what could
|
||||
not be read (a symlinked directory is not walked); walked once per run."""
|
||||
if (found := CORE.data.get(_DOMAIN)) is None:
|
||||
found = CORE.data[_DOMAIN] = _walk_config_texts()
|
||||
return found
|
||||
|
||||
|
||||
def _walk_config_texts() -> tuple[list[tuple[Path, str]], list[str]]:
|
||||
data_dir = CORE.data_dir.resolve()
|
||||
found = []
|
||||
skipped: list[str] = []
|
||||
|
||||
def skip(what: object) -> None:
|
||||
skipped.append(str(what))
|
||||
_LOGGER.debug("Skipping %s", what)
|
||||
|
||||
for dirpath, dirnames, filenames in os.walk(
|
||||
CORE.config_dir.resolve(), onerror=skip
|
||||
):
|
||||
for name in list(dirnames):
|
||||
path = Path(dirpath, name)
|
||||
if path == data_dir:
|
||||
dirnames.remove(name)
|
||||
elif path.is_symlink():
|
||||
dirnames.remove(name)
|
||||
skip(f"{path} is a link")
|
||||
for filename in filenames:
|
||||
path = Path(dirpath, filename)
|
||||
if path.suffix not in _YAML_SUFFIXES:
|
||||
continue
|
||||
try:
|
||||
text = read_text(path)
|
||||
except EsphomeError as err:
|
||||
skip(err)
|
||||
continue
|
||||
# Searched, never edited: comments do not count
|
||||
found.append((path, "\n".join(map(_without_comment, text.splitlines()))))
|
||||
return sorted(found), skipped
|
||||
|
||||
|
||||
def with_sharers(edits: list[KeyEdit]) -> list[str]:
|
||||
"""Fill in ``shared_with``: the other configs that use each rewritten
|
||||
line's secret or include, directly or through further includes. Returns
|
||||
what the scan could not follow (unreadable files, directory and
|
||||
substituted includes)."""
|
||||
main = CORE.config_path.resolve()
|
||||
own = own_documents()
|
||||
# This config's own includes stay in: another device may reach a line
|
||||
# through one of them
|
||||
texts, skipped = _config_texts()
|
||||
texts = [(path, text) for path, text in texts if path.resolve() != main]
|
||||
unchecked = [*skipped]
|
||||
for path, text in texts:
|
||||
if match := _INCLUDE_DIR_RE.search(text):
|
||||
unchecked.append(f"{path} includes the directory {match['dir']}")
|
||||
if match := _SUBSTITUTED_INCLUDE_RE.search(text):
|
||||
unchecked.append(
|
||||
f"{path} includes {match['target']} through a substitution"
|
||||
)
|
||||
for edit in edits:
|
||||
if edit.insert_after and edit.secret:
|
||||
continue # a new secrets line has no users yet
|
||||
ref = (
|
||||
_secret_use_re(edit.secret) if edit.secret else _file_use_re(edit.path.name)
|
||||
)
|
||||
users = {path for path, text in texts if ref.search(text)}
|
||||
frontier = users
|
||||
while frontier:
|
||||
frontier = {
|
||||
path
|
||||
for name in {p.name for p in frontier}
|
||||
for path, text in texts
|
||||
if path not in users and _file_use_re(name).search(text)
|
||||
}
|
||||
users |= frontier
|
||||
edit.shared_with = sorted(p for p in users if p.resolve() not in own)
|
||||
return unchecked
|
||||
|
||||
|
||||
def _secret_file(doc: Path) -> Path:
|
||||
return editable_file(secrets_path_for(doc))
|
||||
|
||||
|
||||
def _secret_line(
|
||||
secrets_path: Path, name: str, value: str | None = None
|
||||
) -> tuple[int, re.Match[str]] | None:
|
||||
"""The root level ``name:`` line, None when absent."""
|
||||
lines = read_text(secrets_path).splitlines()
|
||||
line_re = field_line_re(name, value, re.escape(_root_indent(lines)))
|
||||
return next(
|
||||
((i, m) for i, text in enumerate(lines) if (m := line_re.match(text))), None
|
||||
)
|
||||
|
||||
|
||||
def _secret_rewrite(
|
||||
secrets_path: Path, name: str, value: str, expect: str | None = None
|
||||
) -> KeyEdit | None:
|
||||
"""Set the root level ``name:`` line to ``value``; None when it is not a
|
||||
plain scalar holding ``expect``."""
|
||||
if (hit := _secret_line(secrets_path, name, expect)) is None:
|
||||
return None
|
||||
i, m = hit
|
||||
return KeyEdit(secrets_path, i, m.string, rewrite(m, value), secret=name)
|
||||
|
||||
|
||||
def _secret_insert(secrets_path: Path, after: str, name: str, value: str) -> KeyEdit:
|
||||
"""Add ``name: value`` under the root level ``after`` line."""
|
||||
if (hit := _secret_line(secrets_path, after)) is None:
|
||||
raise EsphomeError(
|
||||
f"No plain '{after}:' line in {secrets_path}; edit it by hand"
|
||||
)
|
||||
i, m = hit
|
||||
return KeyEdit(
|
||||
secrets_path,
|
||||
i,
|
||||
m.string,
|
||||
f'{_indent(m.string)}{name}: "{value}"',
|
||||
insert_after=True,
|
||||
secret=name,
|
||||
)
|
||||
|
||||
|
||||
def locate_key_edits(old_key: str, new_key: str) -> list[KeyEdit]:
|
||||
"""The edits that replace the current key; refuses a substitution, flow
|
||||
mapping or remote package."""
|
||||
literal_re = field_line_re(CONF_KEY, old_key)
|
||||
blocks = _key_blocks(CORE.raw_config or {}, old_key)
|
||||
if not blocks:
|
||||
raise EsphomeError("The current key was not found on a line of the yaml")
|
||||
edits: list[KeyEdit] = []
|
||||
key_lines: set[tuple[Path, int]] = set()
|
||||
for block in blocks:
|
||||
doc, line_no, text = source_line(block, CONF_KEY)
|
||||
key_lines.add((doc.resolve(), line_no))
|
||||
if match := _KEY_SECRET_RE.match(text):
|
||||
name, secrets_path = match["name"], _secret_file(doc)
|
||||
edit = _secret_rewrite(secrets_path, name, new_key, expect=old_key)
|
||||
if edit is None:
|
||||
raise EsphomeError(
|
||||
f"No '{name}:' line with the current key in {secrets_path}"
|
||||
)
|
||||
elif match := literal_re.match(text):
|
||||
edit = KeyEdit(doc, line_no, text, rewrite(match, new_key))
|
||||
else:
|
||||
raise EsphomeError(
|
||||
f"{doc}:{line_no + 1} does not hold the key as a plain value or "
|
||||
"a !secret; edit the key by hand"
|
||||
)
|
||||
# The api and ota blocks may share one !secret line
|
||||
if edit not in edits:
|
||||
edits.append(edit)
|
||||
for edit in edits:
|
||||
if edit.secret:
|
||||
_refuse_other_own_uses(edit.secret, key_lines)
|
||||
return edits
|
||||
|
||||
|
||||
def _refuse_other_own_uses(name: str, allowed: set[tuple[Path, int]]) -> None:
|
||||
"""The secret may only feed the ``allowed`` lines; a wifi password on it
|
||||
would change too."""
|
||||
ref = _secret_use_re(name)
|
||||
for doc in own_documents():
|
||||
for i, text in enumerate(read_text(doc).splitlines()):
|
||||
if ref.search(_without_comment(text)) and (doc, i) not in allowed:
|
||||
raise EsphomeError(
|
||||
f"'{name}' is also used at {doc}:{i + 1}; a rotation would "
|
||||
"change that value too, edit the key by hand"
|
||||
)
|
||||
|
||||
|
||||
def _secret_name(block: ConfigType) -> str | None:
|
||||
"""The secrets file name a ``key: !secret <name>`` line of ``block`` holds."""
|
||||
if source_of(block, CONF_KEY) is None:
|
||||
return None
|
||||
match = _KEY_SECRET_RE.match(source_line(block, CONF_KEY)[2])
|
||||
return match["name"] if match else None
|
||||
|
||||
|
||||
def old_key_edit(old_key: str) -> list[KeyEdit]:
|
||||
"""Set ``old_key:`` on the esphome ota block, rewriting or adding it. A
|
||||
key from secrets.yaml stays there as ``<name>_old``. Of several entries
|
||||
the one with its own old_key or key line gets it."""
|
||||
items = [
|
||||
(item, item[CONF_ENCRYPTION] or {})
|
||||
for item in esphome_ota_items(CORE.raw_config or {})
|
||||
if CONF_ENCRYPTION in item
|
||||
]
|
||||
if not items:
|
||||
raise EsphomeError("The esphome OTA platform has no 'encryption:' block")
|
||||
item, encryption = next(
|
||||
(
|
||||
pair
|
||||
for name in (CONF_OLD_KEY, CONF_KEY)
|
||||
for pair in items
|
||||
if source_of(pair[1], name) is not None
|
||||
),
|
||||
items[0],
|
||||
)
|
||||
if (existing := encryption.get(CONF_OLD_KEY)) is not None:
|
||||
doc, line_no, text = source_line(encryption, CONF_OLD_KEY)
|
||||
if match := _OLD_KEY_SECRET_RE.match(text):
|
||||
name, secrets_path = match["name"], _secret_file(doc)
|
||||
if (edit := _secret_rewrite(secrets_path, name, old_key)) is None:
|
||||
raise EsphomeError(
|
||||
f"No plain '{name}:' line in {secrets_path}; edit it by hand"
|
||||
)
|
||||
_refuse_other_own_uses(name, {(doc.resolve(), line_no)})
|
||||
return [edit]
|
||||
match = field_line_re(CONF_OLD_KEY, str(existing)).match(text)
|
||||
if match is None:
|
||||
raise EsphomeError(
|
||||
f"{doc}:{line_no + 1} does not hold '{CONF_OLD_KEY}' as a plain "
|
||||
"value; edit it by hand"
|
||||
)
|
||||
return [KeyEdit(doc, line_no, text, rewrite(match, old_key))]
|
||||
# Indent from the block's own key line, else one level under `encryption:`
|
||||
if source_of(encryption, CONF_KEY) is not None:
|
||||
mapping, anchor, anchor_re, step = encryption, CONF_KEY, _KEY_FIELD_RE, ""
|
||||
else:
|
||||
mapping, anchor, anchor_re, step = item, CONF_ENCRYPTION, _BARE_BLOCK_RE, " "
|
||||
doc, line_no, text = source_line(mapping, anchor)
|
||||
if not anchor_re.match(text):
|
||||
raise EsphomeError(
|
||||
f"{doc}:{line_no + 1} does not hold '{anchor}:' as a block line; "
|
||||
"edit it by hand"
|
||||
)
|
||||
# The block's own key line decides; a bare block takes after the api key
|
||||
keyed = encryption if mapping is encryption else _api_encryption()
|
||||
if (secret := _secret_name(keyed)) is None:
|
||||
rendered = f'{CONF_OLD_KEY}: "{old_key}"'
|
||||
extra = []
|
||||
else:
|
||||
rendered = f"{CONF_OLD_KEY}: !secret {secret}_old"
|
||||
secrets_path = _secret_file(doc) # the file that gets old_key
|
||||
# An existing `<secret>_old` line is reused only when nothing uses it
|
||||
if (
|
||||
kept := _secret_rewrite(secrets_path, f"{secret}_old", old_key)
|
||||
) is not None:
|
||||
ref = _secret_use_re(f"{secret}_old")
|
||||
texts, skipped = _config_texts()
|
||||
if skipped: # an unread file might use the line
|
||||
raise EsphomeError(
|
||||
"Could not read every configuration to check the secret: "
|
||||
+ skipped[0]
|
||||
)
|
||||
if users := [p for p, t in texts if ref.search(t)]:
|
||||
raise EsphomeError(
|
||||
f"'{secret}_old:' in {secrets_path} is used by "
|
||||
+ ", ".join(str(p) for p in users)
|
||||
+ f"; rename it or add '{CONF_OLD_KEY}' by hand"
|
||||
)
|
||||
extra = [kept or _secret_insert(secrets_path, secret, f"{secret}_old", old_key)]
|
||||
edit = KeyEdit(
|
||||
doc, line_no, text, f"{_indent(text)}{step}{rendered}", insert_after=True
|
||||
)
|
||||
return [edit, *extra]
|
||||
@@ -26,17 +26,20 @@ from esphome.const import (
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_LN882X,
|
||||
PLATFORM_NRF52,
|
||||
PLATFORM_RP2,
|
||||
PLATFORM_RTL87XX,
|
||||
Platform,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Hooks whose loss only degrades diagnostics; skipping one of these is
|
||||
# logged at debug, while skipping a hook that changes what the CLI does
|
||||
# (upload method, log transport) warns. A new hook is loud by default.
|
||||
COSMETIC_HOOKS: Final = frozenset({"process_stacktrace"})
|
||||
# Hooks whose loss only degrades diagnostics or upload size; skipping one
|
||||
# of these is logged at debug, while skipping a hook that changes what the
|
||||
# CLI does (upload method, log transport) warns. A new hook is loud by
|
||||
# default.
|
||||
COSMETIC_HOOKS: Final = frozenset({"process_stacktrace", "ota_image"})
|
||||
|
||||
# Platforms whose firmware prints the crash handler report share one gate.
|
||||
_CRASH_HANDLER_GATE = r"0x[0-9a-fA-F]{3,}\b|CRASH DETECTED ON PREVIOUS BOOT"
|
||||
@@ -71,6 +74,8 @@ PLATFORM_HOOKS: Final[dict[str, frozenset[str]]] = {
|
||||
"show_logs": frozenset({PLATFORM_NRF52}),
|
||||
"upload_program": frozenset({PLATFORM_NRF52}),
|
||||
"process_stacktrace": frozenset(STACKTRACE_GATES),
|
||||
# The file an app update sends, when it is not the build output itself
|
||||
"ota_image": frozenset({PLATFORM_BK72XX, PLATFORM_LN882X, PLATFORM_RTL87XX}),
|
||||
}
|
||||
|
||||
|
||||
|
||||
+3
-15
@@ -966,7 +966,9 @@ class ESPHomeLoaderMixin:
|
||||
if self.name == CORE.config_path:
|
||||
raise e
|
||||
try:
|
||||
secrets = self.yaml_loader(CORE.config_path.parent / SECRET_YAML)
|
||||
main_config_dir = CORE.config_path.parent
|
||||
main_secret_yml = main_config_dir / SECRET_YAML
|
||||
secrets = self.yaml_loader(main_secret_yml)
|
||||
except EsphomeError as er:
|
||||
raise EsphomeError(f"{e}\n{er}") from er
|
||||
|
||||
@@ -1355,20 +1357,6 @@ def find_files(directory: Path, pattern: str) -> Iterator[Path]:
|
||||
yield filename
|
||||
|
||||
|
||||
def secrets_path_for(document: Path) -> Path:
|
||||
"""The secrets.yaml a ``!secret`` in ``document`` resolves against: the
|
||||
one beside the document when it loads, else the main config's, the
|
||||
way construct_secret falls back."""
|
||||
beside = document.parent / SECRET_YAML
|
||||
if document == CORE.config_path:
|
||||
return beside
|
||||
try:
|
||||
load_yaml(beside, clear_secrets=False, track_document_range=False)
|
||||
except EsphomeError:
|
||||
return CORE.config_path.parent / SECRET_YAML
|
||||
return beside
|
||||
|
||||
|
||||
def is_secret(value):
|
||||
try:
|
||||
return _SECRET_VALUES[str(value)]
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
wifi:
|
||||
dpp:
|
||||
private_key: 00112233445566778899AABBCCDDEEFF00112233445566778899aabbccddeeff
|
||||
@@ -0,0 +1,42 @@
|
||||
"""The DPP bootstrapping key is given as 32 raw bytes in hex and handed to the supplicant DER encoded."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components import wifi
|
||||
import esphome.config_validation as cv
|
||||
|
||||
RAW_KEY = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value",
|
||||
[
|
||||
RAW_KEY[:-2],
|
||||
RAW_KEY + "00",
|
||||
"zz" + RAW_KEY[2:],
|
||||
wifi.DPP_KEY_DER_PREFIX + RAW_KEY + wifi.DPP_KEY_DER_SUFFIX,
|
||||
],
|
||||
)
|
||||
def test_key_must_be_64_hex_digits(value: str) -> None:
|
||||
with pytest.raises(cv.Invalid, match="64 hexadecimal characters"):
|
||||
wifi._validate_dpp_private_key(value)
|
||||
|
||||
|
||||
def test_key_is_lower_cased() -> None:
|
||||
assert wifi._validate_dpp_private_key(RAW_KEY.upper()) == RAW_KEY
|
||||
|
||||
|
||||
def test_generated_key_is_der_encoded(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""The ESP-IDF supplicant expects the P-256 DER header around the raw key."""
|
||||
main_cpp = generate_main(component_config_path("dpp_key.yaml"))
|
||||
|
||||
assert (
|
||||
f'set_dpp_key("{wifi.DPP_KEY_DER_PREFIX}{RAW_KEY}{wifi.DPP_KEY_DER_SUFFIX}")'
|
||||
in main_cpp
|
||||
)
|
||||
@@ -0,0 +1,51 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome::core::testing {
|
||||
|
||||
class RecordingPin : public GPIOPin {
|
||||
public:
|
||||
void setup() override { this->events += 'S'; }
|
||||
void pin_mode(gpio::Flags /*flags*/) override {}
|
||||
gpio::Flags get_flags() const override { return gpio::FLAG_OUTPUT; }
|
||||
bool digital_read() override { return false; }
|
||||
void digital_write(bool value) override {
|
||||
this->events += value ? 'H' : 'L';
|
||||
this->write_ms.push_back(millis());
|
||||
}
|
||||
|
||||
std::string events;
|
||||
std::vector<uint32_t> write_ms;
|
||||
};
|
||||
|
||||
TEST(PulseReset, NullPinIsIgnored) { pulse_reset(nullptr, {.low_ms = 1}); }
|
||||
|
||||
TEST(PulseReset, LowThenHigh) {
|
||||
RecordingPin pin;
|
||||
pulse_reset(&pin, {.low_ms = 1});
|
||||
EXPECT_EQ(pin.events, "SLH");
|
||||
}
|
||||
|
||||
TEST(PulseReset, PreHighDrivesHighFirst) {
|
||||
RecordingPin pin;
|
||||
pulse_reset(&pin, {.high_before_ms = 1, .low_ms = 1, .high_after_ms = 1});
|
||||
EXPECT_EQ(pin.events, "SHLH");
|
||||
}
|
||||
|
||||
// Distinct waits, so dropping or swapping one shows up; only lower bounds, a busy host can run long
|
||||
TEST(PulseReset, HoldsEachLevelForItsTime) {
|
||||
RecordingPin pin;
|
||||
pulse_reset(&pin, {.high_before_ms = 10, .low_ms = 20, .high_after_ms = 30});
|
||||
const uint32_t end = millis();
|
||||
ASSERT_EQ(pin.events, "SHLH");
|
||||
EXPECT_GE(pin.write_ms[1] - pin.write_ms[0], 10u);
|
||||
EXPECT_GE(pin.write_ms[2] - pin.write_ms[1], 20u);
|
||||
EXPECT_GE(end - pin.write_ms[2], 30u);
|
||||
}
|
||||
|
||||
} // namespace esphome::core::testing
|
||||
@@ -0,0 +1,14 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.types import ConfigType
|
||||
from tests.testing_helpers import ComponentManifestOverride
|
||||
|
||||
|
||||
def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
# One empty config entry so to_code runs and sets the define that keeps
|
||||
# core/line_reader.cpp in the test build.
|
||||
manifest.multi_conf = False
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
cg.add_define("USE_LINE_READER")
|
||||
|
||||
manifest.to_code = to_code
|
||||
@@ -11,6 +11,14 @@ namespace esphome::testing {
|
||||
// build_plain() compressed with the CLI's window (espota2.DEFLATE_WINDOW_BITS):
|
||||
// DEFLATED = zlib.compress(plain, 9, wbits=-12)
|
||||
// STORED = zlib.compress(plain[:300], 0, wbits=-12)
|
||||
// c = zlib.compressobj(9, zlib.DEFLATED, -12, 9, zlib.Z_FIXED)
|
||||
// FIXED = c.compress(plain) + c.flush()
|
||||
// and build_skewed(), whose rare bytes get codes longer than the lookup tables:
|
||||
// SKEWED = zlib.compress(skewed, 9, wbits=-12)
|
||||
// and build_mixed() in three parts, so stored blocks follow Huffman blocks:
|
||||
// c = zlib.compressobj(9, zlib.DEFLATED, -12)
|
||||
// MIXED = (c.compress(text) + c.flush(zlib.Z_SYNC_FLUSH) + c.compress(noise)
|
||||
// + c.flush(zlib.Z_SYNC_FLUSH) + c.compress(tail) + c.flush())
|
||||
static const uint8_t DEFLATED[] = {
|
||||
0xed, 0xc8, 0xf7, 0x3f, 0xd4, 0x0f, 0x03, 0x00, 0x70, 0x67, 0xaf, 0x4b, 0x67, 0x66, 0x9f, 0x90, 0x91, 0x11, 0xc2,
|
||||
0x11, 0x91, 0xb8, 0xb3, 0xf7, 0x3a, 0xd9, 0x5f, 0x4e, 0x99, 0x67, 0x1e, 0xce, 0x8a, 0xac, 0xec, 0x59, 0xb8, 0xc2,
|
||||
@@ -201,6 +209,532 @@ static const uint8_t STORED[] = {
|
||||
0x68, 0x6f, 0x6d, 0x65, 0x20, 0x6f, 0x74, 0x61, 0x20, 0x64, 0x65, 0x66, 0x6c, 0x61, 0x74, 0x65, 0x20,
|
||||
};
|
||||
|
||||
static const uint8_t FIXED[] = {
|
||||
0x4b, 0x2d, 0x2e, 0xc8, 0xc8, 0xcf, 0x4d, 0x55, 0xc8, 0x2f, 0x49, 0x54, 0x48, 0x49, 0x4d, 0xcb, 0x49, 0x2c, 0x49,
|
||||
0x55, 0x48, 0x1d, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b,
|
||||
0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15,
|
||||
0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x23, 0x49, 0xec, 0x58, 0x5d, 0x63, 0xb6, 0xf7, 0xef,
|
||||
0x47, 0xbf, 0x43, 0xbe, 0xed, 0xbd, 0x5f, 0x23, 0xf3, 0xb0, 0x9d, 0x71, 0xbf, 0xe1, 0xbd, 0xb0, 0x22, 0x7e, 0xf7,
|
||||
0xf4, 0xb4, 0xf6, 0xc8, 0x55, 0x1d, 0x36, 0x91, 0xaf, 0xc2, 0x84, 0xab, 0x2f, 0xb5, 0x2e, 0xbc, 0x61, 0x13, 0x12,
|
||||
0xaa, 0x6f, 0xbe, 0x2e, 0x35, 0xfa, 0x16, 0x53, 0xe5, 0x8c, 0x33, 0x8f, 0xa5, 0xca, 0xfa, 0xe2, 0x6f, 0xce, 0xec,
|
||||
0x97, 0xb7, 0x37, 0x7b, 0xe7, 0x5c, 0xe1, 0xcb, 0xfb, 0x6b, 0xdf, 0xb2, 0x5b, 0x4f, 0xda, 0xfa, 0xee, 0x68, 0xe6,
|
||||
0xfa, 0xfd, 0x0f, 0x7b, 0x58, 0xa0, 0xf0, 0xbb, 0x7f, 0x63, 0x04, 0xeb, 0x84, 0xa3, 0x9c, 0x77, 0x82, 0xcf, 0xae,
|
||||
0xb2, 0xf6, 0x98, 0x19, 0x74, 0x39, 0x68, 0x2e, 0xdb, 0xfc, 0x57, 0x5b, 0x0f, 0xb1, 0x09, 0xcf, 0xf0, 0xdc, 0xc4,
|
||||
0x28, 0xb7, 0xc6, 0xa8, 0xc3, 0x70, 0x4e, 0x90, 0xdb, 0xd4, 0x42, 0xb3, 0xfe, 0xf0, 0x6f, 0x96, 0xb2, 0x62, 0xbf,
|
||||
0x3a, 0x4a, 0xbe, 0xce, 0xa8, 0x11, 0x8f, 0x71, 0xdc, 0x9d, 0x5b, 0xd8, 0xc7, 0x5f, 0x10, 0x79, 0x9c, 0x51, 0x5a,
|
||||
0xdf, 0xd8, 0x76, 0xe2, 0x01, 0xd9, 0xa5, 0xbc, 0xbc, 0xab, 0x8d, 0x7b, 0xeb, 0xe2, 0xfa, 0xed, 0x9e, 0x65, 0x94,
|
||||
0x2c, 0xb3, 0xda, 0x78, 0x78, 0xb2, 0xe0, 0x8a, 0x94, 0xe3, 0xb7, 0x4f, 0x3d, 0x48, 0x78, 0xf8, 0x79, 0x3f, 0x27,
|
||||
0x43, 0xfa, 0xa2, 0xc7, 0xaa, 0x0b, 0x14, 0x0d, 0xdb, 0xaf, 0x26, 0x1d, 0x5d, 0xe1, 0x5f, 0xa7, 0xc7, 0x99, 0x3d,
|
||||
0x65, 0xfe, 0x86, 0xdc, 0x95, 0xf3, 0xa2, 0xb8, 0xdd, 0x0a, 0x1a, 0xb6, 0x9d, 0x77, 0xe7, 0x59, 0xb6, 0x54, 0xeb,
|
||||
0xc6, 0x9a, 0xdf, 0x0b, 0x5e, 0x6f, 0xaf, 0x54, 0x29, 0x52, 0x9e, 0xe4, 0xd1, 0x70, 0x74, 0xd9, 0xf2, 0xd6, 0xed,
|
||||
0xd7, 0x7b, 0x26, 0x3c, 0x59, 0x9d, 0xec, 0x12, 0x94, 0xf6, 0x78, 0x8e, 0xb1, 0xea, 0xcf, 0xb8, 0x55, 0xbb, 0x8a,
|
||||
0x13, 0x62, 0xbd, 0x0b, 0xeb, 0xf6, 0xad, 0xec, 0x09, 0x97, 0x2c, 0x3c, 0x7c, 0x2a, 0xee, 0xa9, 0x96, 0xf1, 0x9a,
|
||||
0x8e, 0xc0, 0xb4, 0x85, 0xd5, 0xa5, 0xe9, 0x29, 0xb3, 0x32, 0xdf, 0xe7, 0x87, 0x39, 0x2d, 0x90, 0x0d, 0x3c, 0xca,
|
||||
0xf4, 0x7d, 0xf7, 0x24, 0xd7, 0x7d, 0xf9, 0xbc, 0xdb, 0x2c, 0xce, 0x08, 0xfc, 0xdd, 0x1d, 0x12, 0x28, 0x53, 0xcd,
|
||||
0x3e, 0x45, 0x7d, 0x72, 0xed, 0xa4, 0xc3, 0x57, 0x8e, 0x2d, 0x4d, 0x0c, 0x64, 0xb4, 0xb0, 0x58, 0xbe, 0xff, 0x23,
|
||||
0x0b, 0xaf, 0xe8, 0xec, 0x06, 0xf9, 0xe6, 0xab, 0x4b, 0x32, 0x3b, 0x6a, 0xe6, 0x6f, 0x63, 0xbc, 0x35, 0x59, 0xdc,
|
||||
0xb5, 0x5b, 0x68, 0x13, 0x93, 0x71, 0x4c, 0xc0, 0xb5, 0x87, 0x61, 0x4b, 0xd6, 0x3a, 0x79, 0xc5, 0xdc, 0x6d, 0x4b,
|
||||
0x7c, 0xc9, 0x2c, 0xf4, 0x90, 0x7f, 0xf6, 0x2b, 0x35, 0x9d, 0xc4, 0x3b, 0x49, 0x1e, 0xd9, 0xb9, 0x22, 0x0f, 0x98,
|
||||
0x5b, 0xbd, 0x8a, 0xdc, 0x6e, 0x4d, 0x3b, 0x51, 0x2b, 0x73, 0x91, 0xd5, 0xee, 0xe9, 0xa4, 0x02, 0xe7, 0xf8, 0x1c,
|
||||
0x66, 0xd6, 0xcd, 0xaf, 0x37, 0x2b, 0x98, 0xfa, 0xd6, 0xa5, 0x05, 0x30, 0x9a, 0x1d, 0x30, 0x7e, 0xc8, 0x7f, 0xd2,
|
||||
0x42, 0xef, 0xa5, 0xa6, 0xa4, 0x7f, 0xdc, 0xc6, 0x8b, 0x9e, 0xdd, 0xd6, 0xc1, 0x7f, 0xe7, 0xdb, 0xbf, 0x53, 0x55,
|
||||
0x35, 0xad, 0xe6, 0x15, 0x5c, 0xef, 0x23, 0xd8, 0x63, 0x74, 0xe5, 0x56, 0xfd, 0x0d, 0xb1, 0xf0, 0x87, 0xcb, 0xce,
|
||||
0xd5, 0x1e, 0x5c, 0x97, 0xb4, 0x5f, 0xf8, 0x49, 0xbb, 0x8f, 0xd5, 0xc1, 0xcd, 0x3c, 0x91, 0x33, 0xdd, 0x23, 0xa2,
|
||||
0xf6, 0x56, 0xd4, 0xec, 0x0a, 0x60, 0x7c, 0x2b, 0xfd, 0xaa, 0xcb, 0xb3, 0xe3, 0xdd, 0x35, 0x91, 0xd6, 0xd5, 0x1b,
|
||||
0x74, 0xee, 0x99, 0x4e, 0x16, 0xd4, 0x65, 0x94, 0xb9, 0xae, 0xe1, 0x6c, 0xf0, 0x7c, 0x03, 0xc7, 0xdb, 0xca, 0x99,
|
||||
0xc2, 0x81, 0x97, 0xac, 0xca, 0xd7, 0xda, 0x6e, 0xf9, 0x71, 0xfc, 0x14, 0xb3, 0xd2, 0xa5, 0x93, 0xc7, 0xd4, 0xf9,
|
||||
0x59, 0xce, 0x55, 0xd9, 0x1f, 0xc8, 0xd0, 0x39, 0x5f, 0x94, 0xc5, 0x79, 0xc8, 0x89, 0xa1, 0x28, 0xce, 0xd1, 0xe4,
|
||||
0xc7, 0xb4, 0x4c, 0xfb, 0xbd, 0x56, 0x11, 0x13, 0xbb, 0x1f, 0x9e, 0x59, 0xb4, 0x71, 0xd2, 0xdd, 0xf2, 0x85, 0xa6,
|
||||
0xff, 0x3e, 0x7b, 0xef, 0xd9, 0xf8, 0xd8, 0x5c, 0x90, 0xf7, 0x78, 0xea, 0xbe, 0x8f, 0x89, 0x4f, 0xe3, 0xd8, 0xfe,
|
||||
0x9b, 0x1e, 0x2f, 0xeb, 0x8c, 0xfd, 0x92, 0xe7, 0x75, 0x66, 0x6b, 0x48, 0xdd, 0x47, 0xd1, 0x13, 0x0b, 0x66, 0xf6,
|
||||
0xc7, 0x14, 0x70, 0xbf, 0x17, 0x39, 0xf6, 0x94, 0x6b, 0x8e, 0xe4, 0x16, 0x59, 0x9f, 0x73, 0x61, 0x6c, 0x77, 0x9c,
|
||||
0x04, 0x55, 0x9f, 0x4f, 0xcb, 0xe7, 0x57, 0xb4, 0xbd, 0xff, 0x33, 0xdc, 0x9d, 0xf7, 0xbe, 0x76, 0xd6, 0x9f, 0xf2,
|
||||
0xde, 0xab, 0x2f, 0x6f, 0xfe, 0xdc, 0xfa, 0xe0, 0x75, 0x51, 0x8b, 0x54, 0x9f, 0x93, 0x88, 0x6c, 0x57, 0x5e, 0xfc,
|
||||
0x24, 0xab, 0xdf, 0xdc, 0x4f, 0xbf, 0x3d, 0x39, 0x30, 0xdf, 0xf5, 0x9a, 0x56, 0xf3, 0xfe, 0x8d, 0x67, 0xb3, 0x8e,
|
||||
0xec, 0xef, 0xb9, 0x77, 0x7f, 0xd3, 0xf7, 0xca, 0xef, 0x09, 0xe1, 0x7f, 0xac, 0x6d, 0xab, 0xf5, 0x4e, 0xcf, 0x71,
|
||||
0xac, 0x56, 0x5f, 0xfa, 0xd8, 0x23, 0x42, 0x94, 0x5d, 0xfc, 0xc1, 0xce, 0xd6, 0xf8, 0xe4, 0x15, 0xdf, 0x34, 0x85,
|
||||
0x9c, 0x19, 0xb2, 0x6e, 0xbf, 0x4b, 0x51, 0x09, 0xea, 0x3e, 0x62, 0x1d, 0xbb, 0xdb, 0x54, 0x62, 0xd1, 0xe5, 0xce,
|
||||
0xff, 0x9b, 0x16, 0x44, 0x1a, 0x7c, 0xba, 0x7d, 0xf5, 0xa0, 0x6f, 0x96, 0xb7, 0xd9, 0x9c, 0xd8, 0x8a, 0x67, 0x17,
|
||||
0x16, 0x4f, 0xe2, 0x7d, 0x3a, 0x41, 0x70, 0x43, 0x1b, 0xbf, 0xa3, 0x49, 0xc3, 0xb2, 0xce, 0xbd, 0x2f, 0xf5, 0x2b,
|
||||
0xdc, 0x79, 0x03, 0xa6, 0xb6, 0x4b, 0xef, 0x7f, 0x5c, 0x3f, 0xc5, 0xdc, 0xec, 0x49, 0xbe, 0xa5, 0x85, 0x3e, 0x4f,
|
||||
0xb3, 0x55, 0xeb, 0xfd, 0xc0, 0x3d, 0x1e, 0x37, 0xc3, 0x76, 0x57, 0x4e, 0xad, 0xdc, 0x7b, 0xc5, 0x23, 0x60, 0xee,
|
||||
0xca, 0xd4, 0x58, 0xf1, 0x1a, 0x61, 0x6e, 0xa1, 0x18, 0xff, 0xf4, 0x0d, 0x2c, 0x0f, 0xe7, 0x49, 0x6c, 0x66, 0xb0,
|
||||
0xfa, 0x77, 0xfa, 0x88, 0xcc, 0x77, 0xed, 0x00, 0x8b, 0x3a, 0xbf, 0xdd, 0xc2, 0x47, 0x15, 0x0e, 0xff, 0xb3, 0x5d,
|
||||
0x62, 0xc0, 0xff, 0xc4, 0x9d, 0xeb, 0x49, 0x10, 0x63, 0x95, 0x78, 0xa3, 0x61, 0x43, 0x43, 0xbc, 0x69, 0x94, 0xae,
|
||||
0xe8, 0x99, 0x0d, 0x4a, 0xa2, 0xba, 0x0d, 0x17, 0x9f, 0x3d, 0x39, 0x13, 0xb1, 0x3e, 0x9f, 0xb5, 0xb6, 0x75, 0x8e,
|
||||
0x69, 0x56, 0xc9, 0x82, 0x0f, 0x1a, 0xfe, 0xdf, 0x7f, 0xde, 0xb1, 0x60, 0xd8, 0x7c, 0xe4, 0x5d, 0x88, 0xdf, 0xc7,
|
||||
0x9b, 0xaf, 0xd6, 0x1e, 0xba, 0xfe, 0x5a, 0x52, 0xe5, 0x48, 0xd8, 0x8a, 0xee, 0xd3, 0x21, 0xd9, 0xeb, 0x0b, 0x22,
|
||||
0xa2, 0xd8, 0x23, 0xff, 0x31, 0xb0, 0xdd, 0x5f, 0xf8, 0x4c, 0x22, 0x72, 0xd7, 0xc1, 0x68, 0xe5, 0x3f, 0xd1, 0x72,
|
||||
0x05, 0x06, 0x4e, 0x52, 0x57, 0x2e, 0x18, 0x15, 0x4d, 0x48, 0x73, 0xca, 0x99, 0xbb, 0xe8, 0xe2, 0xdb, 0x72, 0x3b,
|
||||
0x83, 0x6d, 0xeb, 0x26, 0xf1, 0x26, 0xea, 0x7d, 0x5b, 0x24, 0xe5, 0x79, 0x7b, 0xa1, 0x6c, 0xe7, 0x8a, 0x7b, 0x9f,
|
||||
0x2c, 0xc2, 0x76, 0x65, 0xaf, 0x3e, 0x15, 0x1c, 0x15, 0xfc, 0x2d, 0x57, 0xb8, 0x71, 0x9d, 0xfc, 0xd2, 0x3f, 0x5e,
|
||||
0xb6, 0xd7, 0x5d, 0x19, 0x3b, 0x9f, 0x2c, 0x61, 0x98, 0xf1, 0xed, 0xb7, 0x6f, 0x5b, 0x8a, 0x5b, 0x7c, 0xe4, 0x9a,
|
||||
0xaf, 0x95, 0x66, 0xfa, 0xaf, 0x4e, 0xb9, 0xad, 0x0f, 0x70, 0x4b, 0xeb, 0x54, 0x74, 0x9a, 0xb8, 0xb1, 0xec, 0x12,
|
||||
0x6f, 0x51, 0xef, 0xe3, 0x88, 0xc7, 0x73, 0xc4, 0x2f, 0x6a, 0x44, 0x24, 0xab, 0xe7, 0xb9, 0x64, 0x37, 0x2d, 0xd9,
|
||||
0x35, 0xa3, 0xf8, 0xd7, 0xee, 0xff, 0x73, 0xa4, 0xca, 0x3e, 0xc9, 0x6b, 0xce, 0x4c, 0x3a, 0x51, 0x13, 0xfd, 0xfb,
|
||||
0xa7, 0x94, 0xdb, 0xdf, 0xc8, 0x6f, 0x47, 0x6f, 0xdb, 0xbc, 0x2c, 0x9c, 0x76, 0xa1, 0x50, 0xe6, 0x06, 0xaf, 0xce,
|
||||
0xcc, 0x0b, 0x51, 0x42, 0x81, 0x17, 0x18, 0x4a, 0xdb, 0x57, 0xf8, 0xef, 0x4a, 0x3b, 0x30, 0xe9, 0xea, 0x85, 0xef,
|
||||
0x5b, 0xda, 0x9e, 0xda, 0xaf, 0x0f, 0x0d, 0xfd, 0xba, 0xc3, 0x2f, 0x8d, 0x51, 0xa7, 0xf6, 0xc8, 0x26, 0x0b, 0x0d,
|
||||
0x9e, 0x30, 0xef, 0xf3, 0xe2, 0x73, 0x6c, 0x9f, 0xb0, 0xaf, 0xb6, 0xf1, 0x12, 0xfa, 0x57, 0x3d, 0x41, 0x90, 0x6d,
|
||||
0xe6, 0xab, 0xe3, 0xb5, 0x17, 0x3f, 0x7f, 0xea, 0x79, 0xae, 0x2a, 0x32, 0xe7, 0x9c, 0xc8, 0xbf, 0x3f, 0x92, 0xb9,
|
||||
0x8a, 0xe6, 0x1a, 0x9b, 0xa6, 0x18, 0xf3, 0x6f, 0x7e, 0xc2, 0xe5, 0x7a, 0x7a, 0xfe, 0x0a, 0xc1, 0x07, 0xf3, 0x35,
|
||||
0xb7, 0x48, 0x88, 0xbf, 0x0f, 0x8f, 0x89, 0x6f, 0xdb, 0xdc, 0x5b, 0x6f, 0xd9, 0xd4, 0x59, 0x5b, 0xb8, 0xf2, 0x4e,
|
||||
0xfa, 0x05, 0x25, 0x37, 0x79, 0xc1, 0xd5, 0x1f, 0x5f, 0xce, 0x33, 0xc8, 0xdf, 0xf6, 0xee, 0x67, 0xa9, 0xde, 0xd2,
|
||||
0x29, 0x91, 0xf5, 0x99, 0x0d, 0xbe, 0x2f, 0x5a, 0xe7, 0x45, 0xb2, 0x38, 0x44, 0x48, 0x5d, 0xff, 0xdd, 0x67, 0x33,
|
||||
0x8b, 0xd7, 0x75, 0xa7, 0x5b, 0x3c, 0xdf, 0xb9, 0x47, 0xc7, 0x2c, 0x0e, 0xf5, 0xaa, 0x6c, 0x0d, 0xf3, 0xb6, 0x3d,
|
||||
0xcb, 0xdd, 0x1f, 0xd9, 0x22, 0xd6, 0x33, 0xff, 0x8c, 0x8a, 0x8d, 0x4e, 0xf6, 0x39, 0xdd, 0x6f, 0xab, 0x6e, 0xf1,
|
||||
0xa5, 0x1c, 0x36, 0xff, 0xbb, 0x92, 0x63, 0x7b, 0xdf, 0x93, 0xcb, 0x5d, 0xb3, 0x7f, 0x1a, 0xd6, 0x9d, 0xd3, 0xf5,
|
||||
0xfd, 0xf1, 0xbe, 0x79, 0xde, 0xc6, 0x77, 0xb7, 0x2e, 0x18, 0x6d, 0x38, 0x5c, 0xcc, 0x3b, 0x4b, 0x25, 0xed, 0xe1,
|
||||
0xbd, 0x3e, 0x26, 0xee, 0x8e, 0x58, 0x36, 0x1d, 0xf7, 0xa9, 0xae, 0xf1, 0x7f, 0xca, 0x05, 0xcd, 0x59, 0x9e, 0xa5,
|
||||
0x55, 0xbb, 0xd5, 0x5e, 0x5b, 0xf8, 0x3b, 0xd7, 0x82, 0xdb, 0x41, 0x5c, 0x80, 0x39, 0x36, 0x77, 0x6f, 0xc5, 0x65,
|
||||
0xce, 0xd4, 0x32, 0x75, 0xae, 0x85, 0xe9, 0x85, 0x9b, 0x9e, 0x73, 0x2f, 0x3e, 0xb0, 0xdb, 0x72, 0x56, 0xdf, 0xd4,
|
||||
0xe0, 0x67, 0xaf, 0x27, 0x76, 0x45, 0x6d, 0xbb, 0x79, 0x3d, 0xc8, 0xfe, 0xd2, 0x96, 0xe3, 0xb1, 0x9c, 0xf3, 0x44,
|
||||
0xfc, 0xef, 0xf8, 0xb4, 0x06, 0xbf, 0x58, 0xb3, 0x94, 0xc3, 0x6c, 0x91, 0x4b, 0x8b, 0x4a, 0x83, 0x97, 0xa9, 0xa8,
|
||||
0xb3, 0x7d, 0xc5, 0x8d, 0xc9, 0xd3, 0x7e, 0xdf, 0xac, 0xdc, 0x73, 0x99, 0xeb, 0xde, 0xd3, 0x98, 0xfe, 0xe3, 0x13,
|
||||
0xaf, 0xe8, 0x04, 0x3d, 0xd8, 0x9e, 0x5f, 0x30, 0xfb, 0xc6, 0xdc, 0x84, 0x7f, 0x2e, 0xb1, 0xef, 0xdd, 0xaf, 0xa9,
|
||||
0x15, 0xfe, 0x9f, 0x95, 0x55, 0xcb, 0xfd, 0xa8, 0x3e, 0xa7, 0x50, 0x2b, 0x68, 0xc2, 0xeb, 0xb5, 0xa7, 0x4c, 0xf5,
|
||||
0x0e, 0xff, 0x8d, 0xfc, 0xce, 0x28, 0xaa, 0x75, 0x8b, 0x9f, 0xd1, 0xe5, 0x94, 0xfb, 0xed, 0xe5, 0xe9, 0xc2, 0x32,
|
||||
0x55, 0xdc, 0xcc, 0x4d, 0x8d, 0x93, 0x37, 0xee, 0x49, 0x78, 0x1b, 0x7a, 0xbb, 0x37, 0x4d, 0xbd, 0x52, 0x6c, 0x63,
|
||||
0xc5, 0x72, 0x89, 0x6d, 0xfd, 0x33, 0x7e, 0x2b, 0xb8, 0xf0, 0xe5, 0x2d, 0x8d, 0xeb, 0x50, 0x13, 0x59, 0xa7, 0x11,
|
||||
0xa6, 0xf0, 0x22, 0xed, 0xed, 0x3b, 0x97, 0xf2, 0x49, 0x09, 0x37, 0xaa, 0x13, 0xe4, 0xb7, 0x64, 0x26, 0x66, 0xef,
|
||||
0xde, 0x7d, 0x66, 0x91, 0xcb, 0xcd, 0x7f, 0x13, 0x4b, 0xdc, 0xac, 0xea, 0x22, 0x7b, 0x14, 0x3f, 0x1e, 0x7f, 0xf1,
|
||||
0xc1, 0xed, 0xf3, 0xb6, 0xea, 0x2f, 0x17, 0x67, 0xbf, 0x9d, 0xad, 0x5b, 0x62, 0x7b, 0xbe, 0x9b, 0x31, 0x7f, 0xf9,
|
||||
0x81, 0xc0, 0x7e, 0x16, 0xdf, 0xb7, 0x79, 0x0b, 0xeb, 0x8e, 0x48, 0xdf, 0x77, 0xbf, 0xa5, 0xe0, 0x77, 0x73, 0x7d,
|
||||
0xd3, 0x6f, 0xf6, 0x82, 0xb2, 0x9a, 0x98, 0x07, 0x8f, 0xf7, 0xfc, 0x60, 0x99, 0xd3, 0xae, 0x3f, 0x31, 0xea, 0xca,
|
||||
0x03, 0xb1, 0xaa, 0x6b, 0x53, 0x5f, 0xd6, 0x1c, 0x8c, 0xbf, 0x6c, 0x1e, 0x93, 0x5f, 0x7d, 0xd7, 0xbb, 0x64, 0xf2,
|
||||
0x66, 0xa9, 0x1d, 0x47, 0x7a, 0x39, 0x7f, 0x45, 0x71, 0xcd, 0xe2, 0x5c, 0x3f, 0xf5, 0xb6, 0x6a, 0xa4, 0x78, 0x89,
|
||||
0xa8, 0x70, 0x4d, 0x3e, 0x47, 0xce, 0x9b, 0x53, 0x3a, 0x86, 0xc7, 0xf6, 0x09, 0xcc, 0x8e, 0x39, 0xbb, 0xcb, 0xb2,
|
||||
0xb0, 0xaf, 0x63, 0x4e, 0xb1, 0xc5, 0xf1, 0x23, 0x15, 0x1f, 0x44, 0x7d, 0x7f, 0x5f, 0x2a, 0x8f, 0x0c, 0x3e, 0x68,
|
||||
0x69, 0xf3, 0xfd, 0x7d, 0xe7, 0xab, 0x62, 0xed, 0x4b, 0x8a, 0x9a, 0xef, 0x6e, 0x86, 0x9d, 0x50, 0x99, 0x95, 0xd8,
|
||||
0xb7, 0xeb, 0xc1, 0x0b, 0xdb, 0xd7, 0xcb, 0xbb, 0xef, 0x79, 0x1b, 0x5e, 0xb1, 0x9c, 0xf0, 0xea, 0x2e, 0xbf, 0xf2,
|
||||
0xdf, 0x7d, 0x72, 0xd9, 0x9b, 0xf6, 0x5e, 0xba, 0xd2, 0x67, 0x7a, 0x7a, 0xa1, 0xa6, 0x93, 0x70, 0xf9, 0x59, 0x23,
|
||||
0xe9, 0xa5, 0x97, 0x57, 0x57, 0x99, 0xec, 0x9b, 0x93, 0xb6, 0x49, 0xe4, 0xc9, 0xbb, 0xfd, 0x0c, 0x47, 0xff, 0x86,
|
||||
0xac, 0x64, 0xe7, 0xbf, 0x16, 0xf0, 0x66, 0xc3, 0x7d, 0x9d, 0xeb, 0x3b, 0x8f, 0xb3, 0xee, 0xf6, 0xfa, 0x32, 0xc9,
|
||||
0xa5, 0xed, 0xd9, 0x94, 0x13, 0x82, 0x8c, 0xeb, 0xdf, 0x78, 0x4b, 0x72, 0x8b, 0x79, 0x1e, 0x58, 0x37, 0x6d, 0xba,
|
||||
0xff, 0xf4, 0xc9, 0x1b, 0xb6, 0xce, 0xde, 0x6e, 0xc5, 0xc4, 0x38, 0x8b, 0x69, 0xd3, 0x9d, 0x0f, 0xbb, 0x76, 0x69,
|
||||
0x17, 0x96, 0x84, 0x6c, 0xec, 0xbe, 0xdb, 0x3d, 0xf5, 0x94, 0x55, 0xeb, 0x2e, 0x66, 0x95, 0x29, 0x77, 0x5c, 0x98,
|
||||
0x7f, 0xe7, 0x57, 0xfb, 0x34, 0x58, 0xbc, 0xac, 0xda, 0xb6, 0xc2, 0xe5, 0x1c, 0xfb, 0xef, 0xf5, 0xc7, 0x9a, 0x45,
|
||||
0xa3, 0x62, 0x73, 0xc4, 0x7f, 0x72, 0xd4, 0x1e, 0x79, 0x96, 0xfb, 0x6f, 0xba, 0xe8, 0xc3, 0xce, 0x05, 0xbb, 0x57,
|
||||
0xcc, 0x54, 0xda, 0xf7, 0xe6, 0xc6, 0xbb, 0xdb, 0x95, 0xaa, 0x75, 0x33, 0xed, 0xd2, 0x2f, 0x7c, 0x6c, 0x11, 0xe1,
|
||||
0xd8, 0xf5, 0xba, 0xe1, 0xe8, 0x35, 0xcd, 0x67, 0xf6, 0xf2, 0x4d, 0x16, 0xaa, 0xfc, 0xec, 0xcb, 0x2c, 0x0c, 0xfb,
|
||||
0x3e, 0x2c, 0xf7, 0x2e, 0xcd, 0xd1, 0xf4, 0x10, 0xbb, 0x76, 0xf7, 0x05, 0xc7, 0xed, 0x87, 0x6a, 0xd2, 0x29, 0x5b,
|
||||
0x72, 0x84, 0x16, 0xfb, 0x54, 0xca, 0xdd, 0xfb, 0xc6, 0x27, 0xff, 0xef, 0x53, 0xcc, 0xac, 0x6b, 0xa7, 0x74, 0x2b,
|
||||
0x4c, 0xcb, 0xae, 0xb4, 0xac, 0x32, 0xbc, 0xb6, 0x48, 0x4a, 0x32, 0xf4, 0x02, 0x73, 0xa7, 0xc3, 0xbd, 0x5e, 0x73,
|
||||
0x9b, 0xb7, 0xa1, 0x3c, 0x81, 0x2b, 0x7f, 0x1e, 0x0b, 0x75, 0x4b, 0x0e, 0x90, 0xb4, 0xb9, 0x76, 0x27, 0xf4, 0xe0,
|
||||
0xae, 0x63, 0xc1, 0xdc, 0xea, 0x9a, 0x71, 0x4e, 0xc6, 0xb6, 0xaf, 0xdc, 0xff, 0x94, 0x37, 0xa8, 0x97, 0xc4, 0x15,
|
||||
0xc4, 0xbe, 0xd7, 0x37, 0x39, 0x9d, 0x67, 0xb0, 0xac, 0x7c, 0xe5, 0x95, 0x47, 0x6c, 0xf7, 0xa6, 0xfc, 0xfc, 0x19,
|
||||
0xd3, 0x11, 0xb5, 0xf2, 0xdc, 0x71, 0x46, 0x66, 0x8f, 0x96, 0x58, 0x16, 0xe1, 0xa7, 0x4c, 0x9f, 0x2d, 0x17, 0x09,
|
||||
0x27, 0x9d, 0x4f, 0xca, 0x7d, 0xc4, 0x7a, 0x6d, 0x4a, 0xe7, 0xfb, 0x89, 0x71, 0xaa, 0x02, 0xeb, 0x12, 0x6d, 0x57,
|
||||
0x2b, 0xc8, 0x9e, 0x3e, 0x75, 0xfd, 0xd5, 0x1e, 0xee, 0x19, 0x0b, 0x94, 0x75, 0x67, 0x72, 0x38, 0xc6, 0xdd, 0x67,
|
||||
0x35, 0x73, 0x8a, 0x68, 0x6a, 0x3e, 0xbc, 0x43, 0xda, 0x7d, 0xb6, 0x48, 0xb7, 0xe9, 0x62, 0xfb, 0x1b, 0x11, 0x37,
|
||||
0x5f, 0x38, 0xb4, 0x19, 0x15, 0x3c, 0x5c, 0xaa, 0xda, 0xa3, 0xb3, 0xf0, 0xcb, 0x7c, 0x0e, 0xcd, 0x1d, 0x57, 0x8e,
|
||||
0xea, 0x98, 0xfc, 0x3f, 0x2e, 0x1e, 0x66, 0xf8, 0xfe, 0x74, 0x8f, 0xec, 0x89, 0x84, 0xb3, 0x7a, 0x65, 0x73, 0x92,
|
||||
0x52, 0xe2, 0x0d, 0x2b, 0x3e, 0x4d, 0xdb, 0x95, 0x3e, 0x93, 0xa7, 0xe4, 0xc0, 0xa1, 0xd2, 0x3d, 0x92, 0x71, 0xbf,
|
||||
0x7d, 0xa6, 0xd7, 0x2d, 0x33, 0x75, 0xd8, 0xd8, 0x36, 0xe7, 0x9f, 0xe2, 0x52, 0x93, 0xa2, 0x9c, 0x0d, 0xf5, 0x75,
|
||||
0xce, 0xf1, 0x47, 0x26, 0x1e, 0x5d, 0x75, 0x7e, 0xe3, 0xcc, 0x55, 0xd9, 0x5e, 0x67, 0xfd, 0x17, 0xec, 0x28, 0x3a,
|
||||
0xbe, 0x76, 0xda, 0x97, 0x95, 0x47, 0x8e, 0x58, 0x3d, 0xed, 0xb0, 0x6a, 0xcc, 0x75, 0x9f, 0xf0, 0x63, 0xfe, 0x77,
|
||||
0x4f, 0x99, 0xca, 0x4f, 0x8f, 0x2f, 0x55, 0x17, 0xce, 0x77, 0x8b, 0x3e, 0x25, 0xd0, 0x9a, 0x9d, 0x99, 0x76, 0xe7,
|
||||
0xf4, 0x84, 0x8a, 0x0f, 0x7e, 0xe7, 0x26, 0xcf, 0xd2, 0x75, 0x60, 0xe9, 0xc8, 0xeb, 0x4e, 0x9f, 0x2a, 0x34, 0x75,
|
||||
0xdd, 0x63, 0x46, 0xb6, 0x0f, 0xfb, 0xb6, 0x35, 0x7d, 0x37, 0x58, 0xb5, 0xb8, 0x23, 0xa5, 0x69, 0x47, 0xc1, 0xee,
|
||||
0xef, 0xf6, 0xc1, 0x1b, 0x3a, 0x55, 0x4c, 0x72, 0x1e, 0xef, 0x38, 0xac, 0xc1, 0x5b, 0x90, 0xe5, 0x1e, 0x92, 0x24,
|
||||
0xa6, 0xff, 0xb3, 0xfe, 0xd8, 0xeb, 0x39, 0x13, 0xaf, 0x34, 0xa5, 0x7d, 0x61, 0x13, 0xf9, 0x29, 0x12, 0xb2, 0x4b,
|
||||
0x72, 0x55, 0xb9, 0xb4, 0xf8, 0x56, 0xb3, 0x73, 0x8c, 0x56, 0x05, 0x25, 0xdd, 0x7b, 0x5e, 0x4c, 0xd8, 0x53, 0x7d,
|
||||
0x59, 0x37, 0x22, 0x47, 0x59, 0x4f, 0xf0, 0xf7, 0xc4, 0xe2, 0x9c, 0xe6, 0xdc, 0x8d, 0x25, 0x9d, 0xaa, 0x7c, 0x4a,
|
||||
0x7b, 0xa6, 0xd7, 0xb7, 0xaf, 0x15, 0x7b, 0xb4, 0xdf, 0xcf, 0xee, 0xd6, 0x34, 0x9d, 0x8a, 0xbc, 0xef, 0x39, 0x3e,
|
||||
0x89, 0x92, 0xed, 0x59, 0xfe, 0xe9, 0x47, 0xcb, 0x7a, 0x8c, 0xab, 0xa7, 0xed, 0x91, 0x66, 0xd9, 0x6b, 0x64, 0xde,
|
||||
0xb0, 0x8d, 0x93, 0xc3, 0xff, 0x03, 0xab, 0x82, 0x0f, 0x8f, 0xfa, 0xc4, 0x93, 0xea, 0x42, 0x62, 0xfe, 0xcf, 0x15,
|
||||
0x58, 0x84, 0xdc, 0x9d, 0xdf, 0x99, 0x29, 0xcf, 0x93, 0x3e, 0xd2, 0x7c, 0x7f, 0x65, 0xd6, 0xf2, 0xa4, 0xda, 0x6f,
|
||||
0xd7, 0xd8, 0x3f, 0x4d, 0x7c, 0x7e, 0xde, 0x76, 0x77, 0x8f, 0xe5, 0x22, 0xae, 0xa4, 0xf8, 0x88, 0x45, 0x3b, 0xcf,
|
||||
0xdb, 0x16, 0x2b, 0x6b, 0x59, 0x99, 0xed, 0xab, 0x2f, 0x51, 0x75, 0x4c, 0x35, 0xaf, 0x3d, 0x79, 0x6b, 0xf1, 0x96,
|
||||
0xc4, 0xfb, 0x22, 0x55, 0xc1, 0xe7, 0x83, 0x8b, 0x0c, 0x9b, 0xeb, 0xd6, 0x2e, 0xd2, 0xd8, 0xfe, 0xfa, 0xdd, 0xed,
|
||||
0xfe, 0x09, 0xee, 0x9c, 0x86, 0xe9, 0x77, 0x3b, 0xfd, 0xa4, 0x16, 0x94, 0x9d, 0xc9, 0xbd, 0x7c, 0x4e, 0xcf, 0x2e,
|
||||
0xf4, 0xcc, 0x41, 0xdb, 0x75, 0xac, 0x3d, 0xd9, 0x77, 0x5c, 0xbe, 0x86, 0xdc, 0x51, 0x37, 0xe6, 0xfe, 0x5e, 0x56,
|
||||
0xfe, 0xea, 0xa2, 0xdb, 0x2d, 0x76, 0xe3, 0x4b, 0xac, 0xa5, 0x93, 0x45, 0x16, 0xd9, 0x67, 0xaa, 0xb7, 0xa9, 0x1a,
|
||||
0xc6, 0xcc, 0x16, 0xb1, 0x93, 0xeb, 0x4f, 0x77, 0x6d, 0xb3, 0x7b, 0xb0, 0x74, 0xef, 0x83, 0x94, 0x72, 0xd1, 0x4f,
|
||||
0x55, 0x51, 0xcf, 0x9e, 0x36, 0xfd, 0xd6, 0xea, 0xdc, 0x9a, 0x7e, 0xc3, 0xe2, 0x81, 0x00, 0xe3, 0xa1, 0x07, 0x0b,
|
||||
0x56, 0xcb, 0xdc, 0x69, 0x2c, 0x3c, 0xee, 0xaa, 0x79, 0x9d, 0xa9, 0x5f, 0x2d, 0x6f, 0xfd, 0x9a, 0xb7, 0xab, 0x59,
|
||||
0x7d, 0x93, 0x3a, 0x7e, 0x5f, 0x57, 0xbf, 0x51, 0xa0, 0xbe, 0x34, 0x5b, 0x8a, 0xed, 0xf6, 0x53, 0xd3, 0xec, 0x97,
|
||||
0xf1, 0xcf, 0x8b, 0xa3, 0x77, 0x97, 0x2a, 0x9a, 0x1d, 0xd7, 0xb8, 0x7a, 0x54, 0xf2, 0x6f, 0x9f, 0xc2, 0x12, 0xfe,
|
||||
0xfb, 0x8d, 0x72, 0x47, 0x37, 0xb9, 0x09, 0xed, 0x3e, 0x70, 0xf4, 0x48, 0xd1, 0x99, 0x2b, 0x27, 0xb6, 0x2d, 0x12,
|
||||
0x4e, 0xbc, 0x7c, 0x99, 0x61, 0x56, 0xb9, 0xee, 0x0e, 0xb5, 0xd4, 0xbf, 0x7d, 0x3f, 0xef, 0xb3, 0xf8, 0x2e, 0xbe,
|
||||
0x60, 0xe5, 0x9b, 0xbf, 0xd7, 0xc5, 0xc2, 0x3e, 0x6b, 0xfb, 0x2c, 0x55, 0x1f, 0x2e, 0xad, 0x43, 0x9b, 0x62, 0x6f,
|
||||
0xc5, 0x6f, 0x7c, 0xb3, 0xe0, 0xec, 0x95, 0x40, 0xa6, 0x3f, 0x0d, 0xb5, 0x29, 0x71, 0x9a, 0xa5, 0x82, 0x3f, 0xcd,
|
||||
0xcf, 0x70, 0xfb, 0x6d, 0x7b, 0x32, 0xf9, 0xdd, 0x81, 0xc7, 0xe5, 0x57, 0x57, 0x95, 0xcb, 0xf5, 0x7e, 0xf4, 0x0a,
|
||||
0x61, 0x7e, 0xec, 0x72, 0x4d, 0x83, 0xb7, 0x65, 0xae, 0x7b, 0x86, 0xcc, 0x6b, 0x4d, 0xa7, 0x3e, 0xb1, 0x57, 0xef,
|
||||
0xb8, 0xc4, 0x67, 0x6a, 0xa7, 0x5e, 0x9a, 0x1c, 0x7a, 0x53, 0x79, 0x8e, 0x6f, 0xfd, 0x9a, 0x43, 0x9a, 0xbd, 0x89,
|
||||
0x9f, 0xe6, 0xf8, 0x5f, 0x08, 0x57, 0x79, 0x6e, 0x58, 0xf6, 0xed, 0x88, 0x11, 0xf7, 0xca, 0xc7, 0xf5, 0x0b, 0x97,
|
||||
0x9c, 0x10, 0xfe, 0xec, 0x69, 0xed, 0xc8, 0x7a, 0xef, 0x70, 0x23, 0x93, 0xc3, 0x93, 0xdf, 0x57, 0xdc, 0xc4, 0xaf,
|
||||
0xb6, 0x7e, 0xeb, 0xb5, 0x8d, 0xcc, 0x5a, 0x62, 0xba, 0xbc, 0x41, 0x4d, 0xa9, 0xda, 0xd5, 0x45, 0xe8, 0x9d, 0x7c,
|
||||
0xe8, 0x05, 0x87, 0x3d, 0x7e, 0xab, 0x76, 0xed, 0x63, 0xf8, 0xeb, 0xa6, 0x2c, 0xda, 0x7a, 0xaa, 0xec, 0x44, 0x9a,
|
||||
0xe7, 0xf6, 0xf4, 0x9f, 0x3b, 0xb9, 0xbb, 0xc2, 0x17, 0x29, 0x0b, 0xae, 0xa8, 0xce, 0xbb, 0xaf, 0x79, 0xe4, 0x5f,
|
||||
0xc4, 0xbf, 0x47, 0xd6, 0xb3, 0xf7, 0xee, 0xf7, 0xaa, 0xdf, 0x26, 0x67, 0xed, 0xcf, 0xb2, 0xef, 0xab, 0x5b, 0xa8,
|
||||
0xd9, 0xc3, 0x93, 0x95, 0xdd, 0x91, 0x69, 0xab, 0x27, 0x9d, 0x9f, 0xb0, 0x88, 0x73, 0xaf, 0x44, 0x78, 0x71, 0x40,
|
||||
0xf6, 0xd4, 0xed, 0x57, 0xfa, 0x9c, 0xca, 0xef, 0xdd, 0xca, 0x37, 0xb4, 0x2e, 0x57, 0x94, 0x98, 0x33, 0xf3, 0xee,
|
||||
0x86, 0xbf, 0xb5, 0x5f, 0x75, 0x6e, 0x29, 0x4d, 0x6d, 0x17, 0x3c, 0xd1, 0x9b, 0x32, 0x55, 0xeb, 0xce, 0xf2, 0x9b,
|
||||
0x55, 0x0c, 0xe7, 0xbf, 0x29, 0x30, 0x48, 0x2d, 0x32, 0x36, 0x0d, 0xbf, 0xb8, 0xe5, 0x4e, 0xa7, 0x46, 0x6f, 0xac,
|
||||
0x83, 0x9a, 0x4f, 0xe4, 0x76, 0xa1, 0x79, 0x65, 0xeb, 0x16, 0xf5, 0xbd, 0x62, 0x2b, 0xa8, 0x3e, 0x21, 0x27, 0x7b,
|
||||
0x9b, 0xf7, 0x83, 0xd3, 0xa6, 0xac, 0x57, 0xd2, 0x17, 0xce, 0xf7, 0x7e, 0x3a, 0xbb, 0xe7, 0xb8, 0xef, 0x51, 0x77,
|
||||
0xc1, 0xeb, 0x57, 0xde, 0x78, 0xf0, 0x07, 0x88, 0xfb, 0x6c, 0x3a, 0x59, 0x1c, 0xfd, 0xf2, 0xd0, 0x07, 0x6d, 0x23,
|
||||
0x9d, 0x69, 0x27, 0x05, 0x96, 0xf1, 0x9a, 0x14, 0x95, 0xbf, 0xd8, 0xf1, 0xd4, 0x7d, 0x2b, 0x4f, 0xc5, 0x82, 0xa3,
|
||||
0xda, 0xc5, 0x1a, 0x7b, 0x3e, 0x6f, 0x9f, 0x73, 0xbd, 0xee, 0x73, 0x0f, 0x83, 0x4b, 0xcf, 0xcd, 0xc9, 0x6f, 0x3d,
|
||||
0xdf, 0xbd, 0xfb, 0x7e, 0xe8, 0xe0, 0x6e, 0x9e, 0xb6, 0x8e, 0xbf, 0x0a, 0xfc, 0x36, 0xb7, 0xfd, 0xd2, 0xce, 0xeb,
|
||||
0x17, 0x4c, 0xd3, 0x39, 0xd7, 0x70, 0xe9, 0xd3, 0xd4, 0x4d, 0xa9, 0x1f, 0x2c, 0x7c, 0xe3, 0x12, 0x4e, 0xf9, 0x7e,
|
||||
0x7f, 0xc2, 0xb3, 0xeb, 0xa4, 0x53, 0xe9, 0xb1, 0x4d, 0xf7, 0x58, 0x57, 0x36, 0xfd, 0x3e, 0x65, 0x9b, 0xb1, 0x91,
|
||||
0x61, 0x14, 0x8c, 0x82, 0x51, 0x30, 0x0a, 0x46, 0xc1, 0x28, 0x18, 0x05, 0xa3, 0x60, 0x14, 0x8c, 0x82, 0x51, 0x30,
|
||||
0x0a, 0x46, 0xc1, 0x28, 0x18, 0x05, 0xa3, 0x60, 0x14, 0x8c, 0x82, 0x51, 0x40, 0x73, 0x90, 0x5a, 0x5c, 0x90, 0x91,
|
||||
0x9f, 0x9b, 0xaa, 0x90, 0x5f, 0x92, 0xa8, 0x90, 0x92, 0x9a, 0x96, 0x93, 0x58, 0x92, 0xaa, 0x30, 0x2a, 0x36, 0x2a,
|
||||
0x36, 0x2a, 0x36, 0x2a, 0x36, 0x2a, 0x36, 0x2a, 0x36, 0x2a, 0x36, 0xb4, 0xc4, 0x00,
|
||||
};
|
||||
|
||||
static const uint8_t SKEWED[] = {
|
||||
0x2d, 0x97, 0x79, 0xf0, 0x97, 0x55, 0x15, 0xc6, 0x2d, 0x0a, 0xc5, 0x25, 0x25, 0x1b, 0x9d, 0x6c, 0xcc, 0xe3, 0xd8,
|
||||
0xf0, 0x07, 0x4c, 0x8e, 0x96, 0x63, 0xe2, 0x16, 0x90, 0x26, 0x6a, 0x35, 0x14, 0x2a, 0xc5, 0xe8, 0xc8, 0x35, 0xaf,
|
||||
0x7a, 0x5d, 0xce, 0x51, 0xba, 0x1a, 0x82, 0x4c, 0x63, 0x2a, 0x69, 0xa6, 0xe0, 0xa8, 0x8c, 0xb8, 0x11, 0x1a, 0x03,
|
||||
0x29, 0x2e, 0x29, 0xa2, 0x69, 0x2e, 0x8c, 0x1a, 0x05, 0x3f, 0xad, 0x46, 0x74, 0xc4, 0x22, 0xb7, 0x18, 0xd3, 0x16,
|
||||
0x74, 0x10, 0xd1, 0x98, 0xac, 0xe7, 0x73, 0xbe, 0xf2, 0x63, 0xbe, 0xdf, 0xef, 0xfb, 0xbe, 0xf7, 0xbd, 0xf7, 0x2c,
|
||||
0xcf, 0x79, 0xce, 0x73, 0xa2, 0x37, 0xf3, 0x62, 0xad, 0xd6, 0xe2, 0xbd, 0x4d, 0xa8, 0x2d, 0xfa, 0x3e, 0xde, 0x7b,
|
||||
0x6b, 0xd6, 0xad, 0xb8, 0x7b, 0x09, 0xd7, 0xf3, 0x28, 0xad, 0x78, 0x44, 0xf3, 0x68, 0x25, 0xf4, 0x82, 0x45, 0xd7,
|
||||
0xb3, 0xda, 0x43, 0x1f, 0xc5, 0x9e, 0xd0, 0x97, 0x5d, 0x5a, 0xb4, 0x4b, 0x58, 0x78, 0xdb, 0xb7, 0x9b, 0xf5, 0x28,
|
||||
0xd6, 0x3b, 0xaf, 0x15, 0xf3, 0x1e, 0xb5, 0xd9, 0x5c, 0x8f, 0xd7, 0xa2, 0x0f, 0xd5, 0xf2, 0x78, 0xf7, 0x56, 0xce,
|
||||
0x2a, 0x5e, 0x6a, 0xad, 0xad, 0x1e, 0xe3, 0x75, 0xad, 0x47, 0xd1, 0xda, 0xd6, 0xf4, 0x15, 0xd3, 0x2d, 0x22, 0x7a,
|
||||
0x75, 0x5d, 0x55, 0x1d, 0x12, 0xd5, 0x8e, 0x73, 0x0f, 0xed, 0x50, 0xb4, 0x58, 0xdb, 0x37, 0x9d, 0x58, 0xe3, 0x56,
|
||||
0xad, 0xd7, 0x06, 0xa1, 0x45, 0xd3, 0x4b, 0x2d, 0xda, 0x5f, 0x26, 0xb4, 0xae, 0x9d, 0xab, 0x75, 0xad, 0xd3, 0x6b,
|
||||
0xf2, 0xa6, 0xd7, 0xa8, 0xdd, 0xc7, 0x58, 0x95, 0xd9, 0xba, 0x3b, 0x24, 0xdf, 0x46, 0xba, 0x95, 0x16, 0xb2, 0x4b,
|
||||
0xe7, 0x45, 0x9b, 0x6e, 0xde, 0xfc, 0xf6, 0x43, 0xd7, 0x85, 0xc9, 0xab, 0xf6, 0x57, 0x59, 0xed, 0xc3, 0x65, 0x83,
|
||||
0xce, 0x97, 0x2d, 0xda, 0x42, 0xeb, 0x88, 0x40, 0x8f, 0xc7, 0xe4, 0xbb, 0x36, 0x6d, 0xae, 0xa8, 0x5c, 0x25, 0xc3,
|
||||
0xaa, 0x36, 0xd0, 0x6f, 0xfd, 0x23, 0x78, 0x66, 0x55, 0x6e, 0x1e, 0xd1, 0x8b, 0x2c, 0xb2, 0x1f, 0xd6, 0xbd, 0xcc,
|
||||
0x1e, 0x96, 0xe9, 0x17, 0xd6, 0x3e, 0x59, 0x2b, 0x4d, 0xd1, 0xd2, 0x4a, 0xc5, 0xad, 0x17, 0x1f, 0x5a, 0xad, 0x6d,
|
||||
0x0c, 0x7f, 0x74, 0xb7, 0xee, 0xf8, 0xf6, 0x07, 0xeb, 0xe3, 0x06, 0xed, 0x2b, 0x03, 0xf1, 0xae, 0x6a, 0xaf, 0xbf,
|
||||
0xeb, 0xd0, 0x62, 0xda, 0x68, 0x92, 0x5e, 0xf3, 0xd2, 0x74, 0x4f, 0x6e, 0x34, 0x57, 0x30, 0xc2, 0x8f, 0xd4, 0x16,
|
||||
0x79, 0x75, 0x4f, 0x9d, 0x17, 0x25, 0xec, 0xe4, 0x6a, 0x6b, 0x6b, 0xd7, 0xa2, 0x8a, 0xf7, 0x85, 0xe8, 0xcb, 0x74,
|
||||
0x9d, 0x75, 0x80, 0x0e, 0xab, 0x3b, 0xe8, 0x51, 0x89, 0x47, 0x9b, 0x55, 0xe5, 0x58, 0x36, 0x12, 0x54, 0x65, 0xf0,
|
||||
0x4c, 0x25, 0xd7, 0xdb, 0x24, 0x8f, 0xcc, 0xb0, 0x16, 0x7b, 0xb5, 0x6a, 0xed, 0x70, 0xc5, 0xa5, 0x96, 0x6e, 0x3a,
|
||||
0x82, 0x5c, 0x2b, 0xca, 0xfd, 0xc5, 0xee, 0x33, 0x5d, 0x89, 0x5e, 0x14, 0x32, 0x6e, 0x8a, 0xde, 0x7f, 0x49, 0x56,
|
||||
0x0a, 0x14, 0xe5, 0x90, 0x57, 0x3d, 0xd6, 0x44, 0xf8, 0x6d, 0x5d, 0x89, 0x7b, 0x5b, 0x10, 0x50, 0x34, 0x2f, 0x2d,
|
||||
0x44, 0x5e, 0xdb, 0x3d, 0x7b, 0x7f, 0x08, 0x4e, 0x0a, 0x6c, 0x74, 0x23, 0x64, 0xe5, 0x12, 0xf9, 0x6f, 0x02, 0xcb,
|
||||
0x2b, 0x3e, 0x96, 0xa0, 0xd6, 0xae, 0x84, 0x68, 0x1d, 0x59, 0xd1, 0x8b, 0x24, 0x64, 0xa1, 0x1c, 0x2f, 0x56, 0xff,
|
||||
0xe6, 0xb1, 0x8d, 0x70, 0xb1, 0x61, 0x85, 0x96, 0x09, 0x72, 0x7e, 0x94, 0x37, 0x6b, 0x06, 0x0e, 0x14, 0x7d, 0xfd,
|
||||
0x92, 0x8f, 0x02, 0x80, 0x11, 0x97, 0x32, 0xbb, 0x5d, 0xa8, 0x3c, 0x29, 0x09, 0x72, 0xf2, 0xa1, 0xe2, 0xb7, 0x2a,
|
||||
0xd0, 0x4a, 0xaf, 0x7d, 0x17, 0x58, 0xcc, 0x27, 0x9d, 0x2c, 0x55, 0x4c, 0x46, 0xc9, 0x26, 0x85, 0x4c, 0xc0, 0xe8,
|
||||
0x3b, 0xd7, 0x2a, 0x70, 0xe9, 0x52, 0x5b, 0x96, 0x8f, 0x57, 0x21, 0xb4, 0xbc, 0x25, 0x2c, 0xb5, 0xf2, 0x9c, 0xff,
|
||||
0x51, 0xe9, 0x39, 0x5e, 0x89, 0x57, 0xfe, 0xea, 0xeb, 0x42, 0xb6, 0x82, 0x25, 0xe0, 0xcb, 0x4a, 0x61, 0x8f, 0x57,
|
||||
0x0a, 0xa0, 0x53, 0xd2, 0xff, 0x22, 0x8b, 0xfa, 0xdd, 0x72, 0x40, 0xc7, 0x78, 0x4c, 0xee, 0xd6, 0xfa, 0xaf, 0x65,
|
||||
0x41, 0xd4, 0xe3, 0x48, 0x30, 0xc1, 0xaf, 0x8d, 0x78, 0xbb, 0x71, 0xbe, 0x16, 0x0d, 0x70, 0x54, 0xfa, 0x52, 0x59,
|
||||
0x17, 0x53, 0x74, 0x5f, 0xb1, 0x68, 0xf2, 0xb5, 0x39, 0x15, 0xa2, 0xed, 0xbb, 0xa0, 0xa3, 0x20, 0xe6, 0xb7, 0x1e,
|
||||
0x29, 0xdc, 0x05, 0xa4, 0x7b, 0xfb, 0x9f, 0xf2, 0xd8, 0x95, 0x5b, 0x25, 0x24, 0xfa, 0xfe, 0xbe, 0x1f, 0x96, 0xd4,
|
||||
0xb4, 0xa4, 0xb7, 0x45, 0xed, 0x04, 0x53, 0xaa, 0x04, 0x13, 0xcb, 0xc8, 0x28, 0x91, 0x9d, 0xca, 0xd8, 0x55, 0x38,
|
||||
0x71, 0x30, 0xe7, 0x27, 0x5a, 0x98, 0x91, 0xda, 0xe7, 0x14, 0x34, 0x6a, 0xa9, 0xe0, 0x80, 0x5d, 0x5f, 0x41, 0xae,
|
||||
0xd0, 0xe8, 0x32, 0xec, 0x88, 0xba, 0x84, 0x0a, 0x97, 0xa7, 0xbc, 0x4b, 0xa2, 0x7d, 0x27, 0x2a, 0xa6, 0x6f, 0x92,
|
||||
0x05, 0x06, 0x20, 0x95, 0x6a, 0xb9, 0xda, 0xff, 0xa5, 0x7a, 0xac, 0xfd, 0x61, 0x2d, 0x15, 0xc2, 0xde, 0x95, 0xad,
|
||||
0xde, 0xee, 0x28, 0xb6, 0xc8, 0x6f, 0x96, 0xcd, 0xb7, 0x41, 0x0b, 0x20, 0xba, 0x53, 0x57, 0x0d, 0xcb, 0xbb, 0xbd,
|
||||
0xaf, 0x13, 0x40, 0x78, 0x91, 0xed, 0x72, 0x6f, 0x34, 0x29, 0x93, 0x8f, 0xa5, 0x43, 0x33, 0x8b, 0x65, 0xd9, 0x5a,
|
||||
0xd5, 0xbe, 0x3f, 0x2f, 0x83, 0x74, 0x2b, 0xca, 0x51, 0x14, 0x97, 0x78, 0x42, 0x68, 0xa1, 0x98, 0x14, 0x7e, 0xaf,
|
||||
0xfb, 0xc3, 0x49, 0xf2, 0x5e, 0x99, 0x14, 0x88, 0x47, 0x0a, 0x26, 0x7d, 0xc2, 0x09, 0x8a, 0x4d, 0x5c, 0x11, 0xa7,
|
||||
0xc8, 0xb1, 0x7b, 0xea, 0x5b, 0x02, 0x77, 0x07, 0xc4, 0x03, 0x94, 0x41, 0x20, 0xfe, 0x61, 0xc7, 0x9a, 0x58, 0x63,
|
||||
0x13, 0xb4, 0x9e, 0xa4, 0xab, 0x30, 0x15, 0xa2, 0x42, 0xc9, 0xad, 0x57, 0x88, 0x27, 0xea, 0x96, 0x58, 0xc1, 0x6d,
|
||||
0x7b, 0x45, 0xc4, 0xa1, 0x88, 0x0a, 0xe0, 0x15, 0xe0, 0xe3, 0xbb, 0x3c, 0x6d, 0xf6, 0x16, 0x27, 0x1c, 0xff, 0x4d,
|
||||
0xed, 0xa5, 0xd7, 0xbe, 0xe6, 0xd4, 0xa4, 0x00, 0xac, 0x10, 0x29, 0x2a, 0xba, 0xb5, 0x97, 0x98, 0x61, 0x8d, 0xa2,
|
||||
0x2e, 0x5c, 0x89, 0x01, 0xcb, 0x37, 0x14, 0x59, 0x5d, 0xc8, 0x42, 0x31, 0x4e, 0xf2, 0x64, 0x83, 0x7b, 0xa6, 0xba,
|
||||
0x70, 0x11, 0x5b, 0x29, 0x58, 0x41, 0x41, 0xa1, 0xd3, 0xe9, 0x04, 0xe6, 0xd9, 0xcf, 0x6a, 0xb9, 0x4a, 0x4d, 0x0f,
|
||||
0xeb, 0x07, 0x3a, 0xff, 0x0c, 0x3f, 0x6c, 0xc4, 0x91, 0xef, 0x4d, 0x2d, 0x49, 0x06, 0xd0, 0xa4, 0x7c, 0x50, 0x14,
|
||||
0xb5, 0xb9, 0xf2, 0x13, 0x00, 0xfb, 0x5e, 0x08, 0xb0, 0x2b, 0xec, 0xbe, 0xa0, 0x1c, 0xf4, 0x7b, 0xef, 0x38, 0x13,
|
||||
0x31, 0x8a, 0x78, 0x2b, 0x7e, 0x49, 0x54, 0x54, 0xc7, 0x54, 0x59, 0xd7, 0x37, 0x34, 0x10, 0x58, 0x4f, 0x25, 0xce,
|
||||
0x61, 0x73, 0xec, 0x41, 0x85, 0x06, 0x8e, 0xcd, 0x5c, 0xaa, 0xd0, 0x55, 0x6e, 0xc2, 0xa8, 0x8a, 0x4d, 0x41, 0x16,
|
||||
0x5b, 0xe8, 0xae, 0x51, 0x84, 0x14, 0xc7, 0x17, 0xa1, 0x0e, 0x51, 0x9d, 0x5c, 0x15, 0xaa, 0xb4, 0xae, 0xe8, 0x42,
|
||||
0x68, 0xb6, 0xef, 0x28, 0x09, 0xa7, 0xcd, 0x50, 0x00, 0x94, 0x93, 0x00, 0xd9, 0x09, 0x4f, 0x19, 0xa8, 0xe0, 0x89,
|
||||
0x08, 0xc0, 0xa7, 0x56, 0xee, 0xec, 0x6d, 0x27, 0xfb, 0x4d, 0x87, 0x44, 0x28, 0x93, 0x78, 0x5a, 0x34, 0x54, 0x49,
|
||||
0x8d, 0x90, 0x29, 0xb7, 0x2d, 0x46, 0x97, 0xb2, 0x8e, 0x3e, 0x21, 0x9f, 0xaf, 0x55, 0xc8, 0xa8, 0x0c, 0x19, 0x14,
|
||||
0xb7, 0xc3, 0x9b, 0xca, 0xc0, 0x79, 0x25, 0x39, 0x8c, 0x7e, 0x22, 0x20, 0x2b, 0x5e, 0x43, 0x24, 0x75, 0x48, 0xc7,
|
||||
0xb6, 0x8b, 0x2b, 0x99, 0x80, 0xe1, 0xe5, 0x6c, 0xeb, 0xe7, 0x89, 0x69, 0x74, 0xa8, 0xf2, 0x6f, 0x97, 0x90, 0x59,
|
||||
0x48, 0xb8, 0x7d, 0x0b, 0xc2, 0x83, 0xc6, 0x8c, 0x8e, 0x62, 0x47, 0x44, 0x8c, 0x59, 0xdb, 0xab, 0x7d, 0x99, 0x1e,
|
||||
0x46, 0xfe, 0xa8, 0x61, 0x71, 0xa8, 0x01, 0x08, 0xd5, 0x5f, 0xc1, 0x85, 0x68, 0xf7, 0x0a, 0x59, 0x22, 0xe9, 0x42,
|
||||
0xd4, 0x5a, 0x9b, 0x53, 0xff, 0xdb, 0x62, 0x83, 0x2e, 0x5a, 0x6c, 0xce, 0x9a, 0xac, 0x90, 0x81, 0x12, 0xd3, 0x6f,
|
||||
0x86, 0x94, 0xa9, 0x90, 0xce, 0x16, 0x01, 0xc3, 0x41, 0x66, 0xad, 0xff, 0xc9, 0xe1, 0x69, 0xba, 0x0c, 0xdc, 0x65,
|
||||
0x49, 0x39, 0xb0, 0xa3, 0x9c, 0x9f, 0x50, 0x20, 0xf9, 0x3d, 0x45, 0x73, 0x76, 0x1f, 0xcc, 0x01, 0x78, 0x94, 0xaa,
|
||||
0x37, 0xf4, 0x65, 0xdb, 0x38, 0xf1, 0x83, 0x26, 0xba, 0xbd, 0x53, 0xa8, 0xaf, 0xfa, 0x3b, 0xac, 0x52, 0x68, 0x2b,
|
||||
0x9b, 0xe0, 0x1b, 0xb4, 0x12, 0x2d, 0xe9, 0x60, 0xb9, 0xa2, 0xaa, 0x2a, 0x73, 0x18, 0xbe, 0x72, 0x50, 0xc0, 0x87,
|
||||
0xb2, 0x99, 0x66, 0x4b, 0x16, 0x1d, 0x24, 0xf6, 0x3a, 0xc2, 0xc1, 0xd6, 0xe7, 0x68, 0xd5, 0x72, 0x79, 0x17, 0xed,
|
||||
0x73, 0xb7, 0xd8, 0x87, 0x64, 0x8a, 0x80, 0xee, 0xa1, 0xcd, 0x71, 0x42, 0x9b, 0x5a, 0xe9, 0x2a, 0x7a, 0x00, 0x0b,
|
||||
0x0b, 0x4a, 0x9f, 0xc2, 0xba, 0xb0, 0x9b, 0x08, 0x30, 0x68, 0x10, 0x54, 0xa7, 0x7d, 0x66, 0xdd, 0x83, 0x59, 0x39,
|
||||
0x49, 0xb7, 0xf2, 0x50, 0x49, 0x2c, 0xf8, 0x79, 0x83, 0xac, 0xd7, 0x2a, 0x6d, 0x9f, 0x0c, 0x40, 0x3f, 0xa9, 0xfe,
|
||||
0x3a, 0x8c, 0x5f, 0x29, 0x9d, 0xa3, 0xe9, 0x1c, 0xda, 0x53, 0x15, 0xb0, 0x27, 0x85, 0x29, 0x9b, 0x15, 0x7a, 0xca,
|
||||
0x83, 0xcc, 0x68, 0x47, 0xc5, 0xe4, 0x2c, 0xa1, 0x8a, 0xfa, 0x1b, 0x05, 0x97, 0xc9, 0x21, 0x03, 0x4c, 0x0e, 0x9a,
|
||||
0x74, 0xdb, 0xe3, 0x05, 0xc2, 0x57, 0xa7, 0x72, 0x9e, 0xf6, 0x39, 0x5c, 0xd4, 0x01, 0x3e, 0xb2, 0xa9, 0x26, 0x11,
|
||||
0x5b, 0xbf, 0xd1, 0x07, 0xbc, 0x1a, 0x20, 0x85, 0xf8, 0x08, 0xa7, 0xe9, 0x18, 0x0d, 0x43, 0x49, 0xd0, 0xa3, 0x8d,
|
||||
0x49, 0xe9, 0xf4, 0xe6, 0xcc, 0x8a, 0xbe, 0xe9, 0x6a, 0x38, 0x22, 0x82, 0xb0, 0x91, 0x9d, 0xf2, 0x4b, 0x97, 0x94,
|
||||
0x7d, 0x2a, 0x4f, 0xb8, 0x85, 0x04, 0x10, 0x11, 0x75, 0x58, 0xb4, 0x5b, 0xe0, 0x28, 0x80, 0xd2, 0xe9, 0xd2, 0x73,
|
||||
0x4b, 0x79, 0xb9, 0xb3, 0x99, 0x32, 0x55, 0x00, 0xfc, 0x53, 0xc9, 0x71, 0x85, 0x64, 0xf4, 0x8d, 0x2a, 0xc8, 0x96,
|
||||
0x04, 0xa6, 0xe3, 0x75, 0xe4, 0xae, 0x75, 0x97, 0x93, 0x14, 0xb6, 0x5c, 0x2b, 0x32, 0x85, 0xf1, 0xaf, 0x6d, 0x74,
|
||||
0x25, 0x79, 0x48, 0xcf, 0x53, 0x52, 0xc7, 0xc9, 0x7a, 0x10, 0x93, 0x92, 0x45, 0x31, 0xfc, 0x27, 0x0c, 0xa4, 0xe0,
|
||||
0x42, 0x42, 0x60, 0x44, 0xb1, 0x2a, 0xd9, 0xec, 0x00, 0x66, 0x87, 0xcd, 0xb2, 0x22, 0x9d, 0x3d, 0x69, 0xe9, 0xa5,
|
||||
0x3e, 0x04, 0xa9, 0xb6, 0x74, 0xce, 0x11, 0x1c, 0x5b, 0xb5, 0xab, 0x4a, 0x18, 0xaa, 0x69, 0x3b, 0x8b, 0x82, 0x5f,
|
||||
0xe6, 0x80, 0x0a, 0x92, 0x9d, 0xaa, 0x26, 0x4d, 0x22, 0xd2, 0xb9, 0x7a, 0x33, 0x61, 0xcc, 0x8b, 0x9e, 0xdf, 0xfd,
|
||||
0xa3, 0xb6, 0x28, 0x03, 0xf6, 0x03, 0x85, 0x08, 0xb2, 0xd9, 0xaa, 0xf2, 0x02, 0xa3, 0xd8, 0x35, 0x4e, 0x14, 0x3b,
|
||||
0xf2, 0xad, 0x95, 0xbd, 0x91, 0x2e, 0x78, 0x0f, 0x83, 0xaf, 0x90, 0x3c, 0xd0, 0x0f, 0x27, 0x6d, 0x47, 0x09, 0x9a,
|
||||
0x72, 0x58, 0x2d, 0x93, 0x76, 0xad, 0xc3, 0xfe, 0x1c, 0x33, 0x54, 0xf9, 0x2d, 0x93, 0x83, 0xa4, 0xa9, 0x10, 0x38,
|
||||
0x50, 0xa5, 0x70, 0x2f, 0x30, 0xff, 0xe4, 0x1d, 0xc5, 0x97, 0x2d, 0x2e, 0xbf, 0x2c, 0x33, 0xe5, 0xce, 0x23, 0xdd,
|
||||
0x16, 0x54, 0xb4, 0x03, 0x10, 0x8a, 0x64, 0x2a, 0x42, 0x1f, 0x64, 0x9f, 0x28, 0x25, 0x21, 0xb5, 0xd8, 0xcd, 0x7c,
|
||||
0x42, 0xd0, 0x34, 0x91, 0x83, 0x3f, 0xc2, 0x1e, 0x05, 0x53, 0xb6, 0xa8, 0x5c, 0xfe, 0xd1, 0x32, 0xc1, 0x14, 0x21,
|
||||
0x25, 0xa1, 0x07, 0xb3, 0xa1, 0xd7, 0x8c, 0x61, 0xf3, 0xff, 0xe8, 0x65, 0x98, 0xab, 0xc5, 0x39, 0xbe, 0x55, 0x61,
|
||||
0xae, 0x3d, 0x63, 0x5a, 0xb6, 0xa8, 0x21, 0x5c, 0xde, 0x92, 0xc1, 0x2b, 0x42, 0x47, 0xd8, 0x38, 0x87, 0x2e, 0x58,
|
||||
0x3f, 0x86, 0x0e, 0xdb, 0x6c, 0x75, 0x5e, 0x6d, 0xe7, 0x88, 0x5e, 0xeb, 0x89, 0x9c, 0x22, 0xc3, 0x6f, 0x27, 0xe5,
|
||||
0x38, 0x08, 0xe3, 0x02, 0x47, 0xfe, 0xb7, 0xc5, 0x6d, 0x2c, 0x85, 0xb4, 0xad, 0x21, 0x95, 0xfd, 0x5c, 0x7b, 0xc5,
|
||||
0x4f, 0x87, 0xf6, 0xba, 0xbf, 0xc1, 0x2a, 0xa4, 0x09, 0x1d, 0x11, 0x2a, 0x4f, 0xc7, 0x94, 0x8f, 0xd1, 0x54, 0xaf,
|
||||
0x12, 0x80, 0xfe, 0x2a, 0x94, 0x90, 0xdb, 0x95, 0x15, 0x7a, 0x48, 0x23, 0x56, 0xf9, 0xd5, 0x0d, 0xa8, 0xa2, 0xc6,
|
||||
0x9e, 0x04, 0x7f, 0x14, 0x3e, 0x02, 0xa8, 0x53, 0xe0, 0x70, 0x06, 0x0d, 0xa5, 0xd7, 0xe5, 0xb6, 0xfc, 0x8a, 0xf2,
|
||||
0x25, 0x2e, 0xe1, 0xcf, 0x98, 0x1c, 0x7d, 0x8b, 0xb6, 0x98, 0xee, 0x71, 0x12, 0xe6, 0x86, 0x1d, 0x27, 0x98, 0xdf,
|
||||
0x58, 0x10, 0x43, 0x1d, 0x1e, 0x1d, 0x26, 0x42, 0xda, 0xaf, 0x67, 0xd3, 0x97, 0x04, 0x5e, 0x2d, 0x8c, 0x4d, 0x84,
|
||||
0x7f, 0x55, 0x7a, 0x00, 0x8f, 0x0c, 0x93, 0x38, 0xa9, 0x21, 0x52, 0x10, 0x68, 0x36, 0xbf, 0x18, 0xe3, 0x75, 0xe0,
|
||||
0x2a, 0x3e, 0x07, 0x55, 0xe5, 0xd9, 0x58, 0x69, 0x4c, 0x28, 0x51, 0x79, 0x0f, 0x05, 0x06, 0x10, 0xf3, 0x3e, 0x1f,
|
||||
0xf5, 0x90, 0x35, 0x78, 0x97, 0x70, 0xb4, 0x5e, 0x71, 0xfe, 0x05, 0xd5, 0xa8, 0x3b, 0x47, 0xd5, 0x14, 0x1e, 0x34,
|
||||
0x13, 0x8a, 0x96, 0xb0, 0xf8, 0x19, 0xb1, 0x14, 0xe9, 0x95, 0x22, 0x5a, 0x31, 0xa5, 0x71, 0xd3, 0xcd, 0xe4, 0xa1,
|
||||
0xb5, 0x6b, 0x8c, 0x93, 0xea, 0x99, 0xd1, 0x95, 0x9f, 0x71, 0x14, 0x8e, 0x63, 0x8f, 0xc2, 0x5a, 0x76, 0x03, 0xcb,
|
||||
0xf4, 0xf3, 0x2b, 0xe9, 0x8f, 0x11, 0x29, 0x0c, 0x5b, 0x1b, 0x66, 0x30, 0xb2, 0x4f, 0xcf, 0xf6, 0xc1, 0x5f, 0xc9,
|
||||
0x41, 0xc6, 0x53, 0x49, 0x44, 0x96, 0xb5, 0xce, 0x38, 0x15, 0x6e, 0xe8, 0xe3, 0x22, 0x99, 0x38, 0x45, 0xaa, 0x0a,
|
||||
0x17, 0x25, 0x4a, 0x57, 0xf9, 0x1e, 0x42, 0x0f, 0x78, 0x7b, 0x5d, 0xc2, 0xe9, 0xc8, 0x64, 0x18, 0x09, 0x25, 0x84,
|
||||
0x16, 0x8e, 0x24, 0xf6, 0x65, 0x17, 0x55, 0x94, 0x37, 0x08, 0xc2, 0xf9, 0x82, 0x2a, 0xef, 0x67, 0xab, 0xb5, 0x55,
|
||||
0x14, 0x7a, 0x63, 0x9a, 0x50, 0xf2, 0x52, 0x71, 0x8b, 0x55, 0xd0, 0x99, 0x46, 0xa7, 0x3d, 0x2b, 0x05, 0x59, 0x50,
|
||||
0xb3, 0xe0, 0x77, 0xa3, 0x4c, 0x2f, 0x91, 0x93, 0x05, 0x3c, 0x4a, 0x8f, 0x41, 0x6d, 0xc6, 0xd2, 0x24, 0xa8, 0x8e,
|
||||
0x92, 0x54, 0x43, 0x15, 0xd8, 0x53, 0x79, 0x56, 0x4b, 0x06, 0xa8, 0x36, 0x5b, 0x76, 0xb4, 0xd7, 0x10, 0x82, 0x8c,
|
||||
0x63, 0x5f, 0xad, 0xcf, 0x2b, 0x67, 0x4b, 0xe4, 0xff, 0xb7, 0xa3, 0xbc, 0xaf, 0x9b, 0x2a, 0xaf, 0x1c, 0x80, 0xbe,
|
||||
0x1f, 0x7e, 0x05, 0xf4, 0x8a, 0xb8, 0x13, 0x10, 0xa8, 0x02, 0x05, 0xf7, 0xee, 0xc1, 0x89, 0x54, 0x7d, 0xc1, 0x4e,
|
||||
0x5a, 0x80, 0x38, 0x80, 0x09, 0x49, 0x86, 0xd6, 0xe1, 0x0b, 0x2b, 0x73, 0x13, 0x9a, 0xb0, 0xd0, 0xe6, 0x15, 0xa8,
|
||||
0xb9, 0x6e, 0x89, 0xbc, 0x8e, 0x2c, 0x73, 0x95, 0x9c, 0xe4, 0x91, 0x3c, 0xde, 0x74, 0x26, 0x49, 0xac, 0x7b, 0x24,
|
||||
0xa3, 0x50, 0x61, 0xa7, 0x88, 0xb6, 0xb5, 0xe5, 0x3c, 0xcb, 0xde, 0x94, 0x7d, 0x0a, 0x91, 0x91, 0x50, 0x3a, 0xd8,
|
||||
0x50, 0xed, 0x2b, 0xc8, 0x84, 0xcc, 0x11, 0x3d, 0xaf, 0x67, 0xd0, 0x84, 0x9a, 0x0a, 0x95, 0xe1, 0xc8, 0x87, 0xed,
|
||||
0xe5, 0xff, 0xaf, 0x12, 0xc6, 0x57, 0x5d, 0x27, 0x2a, 0x2a, 0x28, 0x93, 0xb6, 0x03, 0x1c, 0xb0, 0x97, 0x36, 0x3b,
|
||||
0xa6, 0xd3, 0x57, 0x82, 0x49, 0x42, 0x5f, 0x9b, 0xd4, 0x7b, 0x37, 0xdb, 0x16, 0x71, 0x1d, 0xec, 0xd7, 0xe2, 0x52,
|
||||
0x20, 0x93, 0xfa, 0x27, 0xd1, 0xd4, 0xfd, 0x19, 0x21, 0x46, 0xc7, 0x5c, 0x8c, 0x1a, 0x56, 0x3e, 0x0f, 0xf4, 0x36,
|
||||
0xda, 0x99, 0x55, 0xc1, 0x74, 0xaa, 0xf5, 0xdd, 0x0d, 0x4e, 0xfb, 0x50, 0x3e, 0xa5, 0x16, 0x52, 0xe4, 0x67, 0x20,
|
||||
0x38, 0xd4, 0x0a, 0x0e, 0xa9, 0x7e, 0xf6, 0xf9, 0xe8, 0x1f, 0xa8, 0xf7, 0xeb, 0xaa, 0xfc, 0x41, 0x93, 0xe8, 0xe5,
|
||||
0xae, 0xda, 0x6f, 0x53, 0x90, 0xb6, 0x3c, 0xb5, 0x0c, 0x6f, 0x7e, 0x2c, 0xbf, 0x17, 0x40, 0xd4, 0x3d, 0xfb, 0x85,
|
||||
0xaa, 0x78, 0xb8, 0xf8, 0x6d, 0x5f, 0xc6, 0x4c, 0x79, 0x5e, 0x9e, 0xce, 0x96, 0xc7, 0xe8, 0x0a, 0x39, 0xfa, 0x81,
|
||||
0x25, 0xcf, 0x6b, 0xe8, 0xd8, 0x3d, 0x60, 0x64, 0xc4, 0x73, 0x6a, 0x94, 0x96, 0x1d, 0x0c, 0xfe, 0xfc, 0x4a, 0x49,
|
||||
0xdd, 0xf3, 0x80, 0x32, 0x4f, 0x47, 0xb4, 0xf1, 0x72, 0x59, 0x79, 0xfc, 0x81, 0xcc, 0xe8, 0x39, 0xbe, 0x4a, 0xc2,
|
||||
0x3c, 0x3e, 0x4d, 0xe9, 0xbb, 0x2c, 0x36, 0x32, 0xed, 0xc2, 0xc2, 0x29, 0x4d, 0xfa, 0xad, 0x88, 0xb2, 0x1c, 0x25,
|
||||
0x9c, 0x91, 0x64, 0xd0, 0x61, 0x69, 0x97, 0x66, 0x57, 0xfb, 0xb3, 0x43, 0xfa, 0x75, 0x2c, 0x22, 0x06, 0x0c, 0xd2,
|
||||
0xac, 0xcb, 0x41, 0x07, 0x0e, 0x14, 0x45, 0x1a, 0x81, 0xf6, 0x43, 0x19, 0xe5, 0xdf, 0x24, 0x00, 0xac, 0x20, 0x5e,
|
||||
0x17, 0xc9, 0x01, 0x28, 0xe6, 0x9c, 0x9e, 0x88, 0x29, 0x9d, 0xc7, 0x52, 0xda, 0x72, 0xa5, 0x7d, 0x90, 0x31, 0x22,
|
||||
0xe0, 0x29, 0xc2, 0xb3, 0x50, 0x40, 0xff, 0x70, 0xde, 0xb1, 0xc8, 0xe1, 0x65, 0xd2, 0x95, 0x07, 0x97, 0x0d, 0x6d,
|
||||
0x26, 0xda, 0x1e, 0x61, 0xf2, 0x9a, 0x80, 0x07, 0x71, 0x32, 0xee, 0xd5, 0xd8, 0xda, 0x08, 0x35, 0xda, 0x8a, 0x72,
|
||||
0x40, 0x91, 0xff, 0x5c, 0x8e, 0x31, 0xce, 0x40, 0x6d, 0x32, 0xff, 0x32, 0xec, 0xaf, 0x4c, 0x79, 0x42, 0xd0, 0x6f,
|
||||
0xb3, 0xcd, 0x8b, 0xca, 0x73, 0x42, 0xc0, 0x03, 0x85, 0x78, 0xf5, 0x89, 0x93, 0x99, 0x19, 0x7d, 0x39, 0x13, 0xe1,
|
||||
0x4b, 0x63, 0xc0, 0x90, 0x10, 0xb5, 0x8f, 0xd3, 0xf2, 0x8e, 0x1d, 0x05, 0x55, 0xb5, 0xfa, 0xcc, 0x9c, 0xec, 0xee,
|
||||
0x6a, 0x6c, 0xe6, 0x39, 0x61, 0xda, 0x9d, 0x6d, 0xd9, 0x4d, 0x0c, 0x48, 0xd3, 0x30, 0xbe, 0xc2, 0x61, 0xac, 0x97,
|
||||
0x07, 0xa4, 0x12, 0x7f, 0xca, 0x23, 0xc0, 0xb7, 0x33, 0xe8, 0x65, 0xf7, 0x98, 0xd8, 0x16, 0x67, 0x8a, 0x21, 0x5b,
|
||||
0xea, 0x96, 0x1e, 0xa7, 0x64, 0xa2, 0xdc, 0xcb, 0x4a, 0xfa, 0xb0, 0xd8, 0xbc, 0xa6, 0x6c, 0x44, 0x77, 0x0f, 0xc4,
|
||||
0x98, 0xbc, 0x9f, 0xf5, 0x62, 0x20, 0x81, 0xa9, 0xab, 0xeb, 0xeb, 0xcf, 0x50, 0x67, 0x8c, 0x35, 0x3d, 0x27, 0x2a,
|
||||
0x79, 0xda, 0x11, 0xbb, 0x6d, 0xf4, 0x2a, 0x55, 0xcc, 0xd0, 0x11, 0x8b, 0xf5, 0x73, 0x72, 0xf8, 0x58, 0x26, 0x9e,
|
||||
0x32, 0xa4, 0x8a, 0x38, 0x0d, 0x14, 0x6b, 0xc3, 0x4a, 0x67, 0xf5, 0x72, 0x7f, 0xed, 0x2b, 0x4b, 0x8a, 0x26, 0x54,
|
||||
0xcb, 0x44, 0xb2, 0x45, 0x50, 0xc9, 0xb2, 0x22, 0x76, 0xaa, 0x82, 0xae, 0xa7, 0x57, 0x09, 0x11, 0x36, 0x8b, 0x99,
|
||||
0x32, 0x49, 0xa5, 0x51, 0xe6, 0x9c, 0x14, 0xc9, 0x4e, 0x9e, 0x33, 0x5e, 0x30, 0xdb, 0xc9, 0xa4, 0x43, 0x54, 0xdc,
|
||||
0x0f, 0x28, 0xed, 0xa7, 0xe9, 0x41, 0x4f, 0xd0, 0xe4, 0x54, 0x58, 0xdf, 0xe5, 0x95, 0x31, 0x35, 0x91, 0x10, 0x82,
|
||||
0xf0, 0xb0, 0xd8, 0x1b, 0x19, 0xdf, 0xb7, 0xb6, 0xb5, 0xad, 0xaf, 0x93, 0xff, 0x4c, 0xb3, 0xf2, 0x7e, 0x3b, 0xd1,
|
||||
0xea, 0x13, 0x36, 0x07, 0x09, 0x5c, 0x6d, 0xdb, 0x6c, 0xef, 0xb2, 0x05, 0x31, 0x4a, 0xff, 0x32, 0x3e, 0x2e, 0x8a,
|
||||
0x96, 0xbd, 0x1b, 0x01, 0x24, 0xac, 0x9c, 0x2b, 0x60, 0x8c, 0x64, 0x36, 0x64, 0x0a, 0x55, 0xa0, 0xb7, 0x11, 0x64,
|
||||
0xda, 0x82, 0x5a, 0x2e, 0xa8, 0x34, 0x13, 0x5f, 0xfd, 0x66, 0x1c, 0x8d, 0x2c, 0xe0, 0x55, 0xc5, 0xf6, 0x0f, 0x1d,
|
||||
0x9d, 0x8d, 0xed, 0x71, 0xfa, 0x8e, 0x1e, 0x2f, 0x7b, 0x4d, 0x72, 0xbe, 0xb9, 0x41, 0xc1, 0x0a, 0xde, 0x7c, 0x5d,
|
||||
0x31, 0x5d, 0xd1, 0x47, 0x4a, 0xb6, 0x6e, 0x06, 0x83, 0xb5, 0xde, 0xde, 0x24, 0x65, 0xed, 0x30, 0x79, 0xfb, 0xf8,
|
||||
0xa0, 0x23, 0xd4, 0x72, 0x2f, 0x9b, 0x36, 0xba, 0x05, 0x50, 0x0b, 0xbf, 0x3f, 0x72, 0xb0, 0xd1, 0x3b, 0x8f, 0xf9,
|
||||
0x1a, 0xf8, 0x38, 0x67, 0xb2, 0x32, 0xe0, 0xc1, 0x27, 0x43, 0x42, 0x1d, 0x0d, 0x29, 0x79, 0x54, 0x88, 0xbe, 0x98,
|
||||
0x02, 0xc5, 0xe5, 0xf1, 0x53, 0x44, 0x0d, 0xcc, 0xad, 0x3b, 0x10, 0x86, 0xbd, 0xdf, 0x6c, 0xdb, 0x7e, 0x17, 0x4d,
|
||||
0xec, 0x1a, 0x2d, 0xa5, 0x09, 0xa8, 0x60, 0xc6, 0x78, 0x2a, 0x0f, 0xed, 0xc9, 0xa5, 0x12, 0x0e, 0xfc, 0x8e, 0xd5,
|
||||
0x79, 0x75, 0xba, 0xb8, 0x2f, 0xa9, 0x5c, 0x0b, 0x27, 0x19, 0x5c, 0x2b, 0x07, 0x56, 0x45, 0x49, 0x31, 0x55, 0xb9,
|
||||
0x8f, 0x0a, 0xa5, 0x22, 0x05, 0xb4, 0x4f, 0x35, 0x5f, 0xa7, 0x84, 0x30, 0xc7, 0x6a, 0xab, 0xf1, 0x34, 0x9a, 0x32,
|
||||
0x3e, 0xc6, 0x56, 0xdb, 0xb1, 0x93, 0xaf, 0x93, 0x99, 0x35, 0x10, 0x85, 0xf7, 0x31, 0xee, 0x22, 0xd4, 0xf5, 0x81,
|
||||
0xe8, 0xf1, 0x3d, 0x39, 0xcf, 0x62, 0x8a, 0x00, 0xaf, 0xb4, 0x97, 0x32, 0x2f, 0xd5, 0x44, 0x5d, 0x9d, 0xa5, 0x64,
|
||||
0xa9, 0x6d, 0x65, 0x52, 0x6a, 0x70, 0x79, 0x6b, 0x7e, 0x19, 0x40, 0xb1, 0x9d, 0x98, 0x6a, 0x8b, 0x2d, 0x41, 0x82,
|
||||
0xd0, 0x97, 0x67, 0x31, 0x09, 0xf9, 0x41, 0xb1, 0xc9, 0x63, 0x4b, 0xd4, 0x5d, 0x27, 0xaa, 0x27, 0xd5, 0xcf, 0x3f,
|
||||
0x11, 0xf3, 0x0d, 0xc9, 0x81, 0x9b, 0xb2, 0xe7, 0x55, 0x27, 0xbe, 0x72, 0xe3, 0xd3, 0xd9, 0x9e, 0x64, 0x06, 0xcf,
|
||||
0x56, 0xde, 0xd2, 0x61, 0x3f, 0xa4, 0x68, 0x4b, 0xda, 0x22, 0x43, 0x8c, 0xb9, 0xba, 0xfb, 0x62, 0x4f, 0x7e, 0x13,
|
||||
0x0c, 0xed, 0x27, 0x08, 0x88, 0x54, 0x11, 0x71, 0x3e, 0x30, 0xd5, 0xff, 0x19, 0x49, 0x20, 0xb3, 0x0c, 0x09, 0xdc,
|
||||
0x46, 0xc4, 0x10, 0xb2, 0xd6, 0x60, 0xb7, 0xde, 0xdf, 0x73, 0x92, 0xc3, 0x4f, 0x6d, 0xe4, 0xe3, 0x69, 0x56, 0xda,
|
||||
0xa8, 0x6e, 0x96, 0x1b, 0xb4, 0x35, 0x32, 0xa8, 0xfb, 0x1b, 0x6a, 0x56, 0x7e, 0x21, 0x6e, 0xdc, 0xeb, 0xb0, 0x51,
|
||||
0x36, 0x49, 0x4d, 0x99, 0x35, 0x72, 0xe6, 0xea, 0x54, 0xf8, 0x64, 0x56, 0x33, 0x73, 0x22, 0x4b, 0x20, 0x33, 0x01,
|
||||
0x56, 0xb6, 0xee, 0xa3, 0xc8, 0x13, 0x16, 0xf9, 0xa2, 0x58, 0x28, 0x2b, 0x1f, 0xfa, 0x17, 0x7c, 0x4b, 0x1f, 0xa7,
|
||||
0xe2, 0xf0, 0xfb, 0xe4, 0x21, 0xbd, 0x59, 0x5e, 0x26, 0x6d, 0x54, 0x3a, 0x97, 0xdd, 0x04, 0x86, 0x6b, 0x8c, 0x49,
|
||||
0x9a, 0x1c, 0xa8, 0x68, 0xac, 0xa9, 0x31, 0x98, 0x52, 0xe5, 0xea, 0x47, 0x03, 0xaa, 0x68, 0x1c, 0x4a, 0x69, 0x14,
|
||||
0x8a, 0xd3, 0x84, 0x5a, 0xbb, 0x9c, 0x79, 0x92, 0x81, 0xcc, 0x92, 0x31, 0x73, 0xb0, 0x75, 0x5a, 0xb7, 0xaf, 0x04,
|
||||
0x26, 0x00, 0x16, 0x92, 0x69, 0x03, 0x7a, 0x61, 0x5a, 0x41, 0xd5, 0x97, 0x63, 0x00, 0x5f, 0x63, 0x30, 0x85, 0xcc,
|
||||
0x6b, 0x8e, 0x8a, 0xbb, 0xa3, 0xff, 0x25, 0x15, 0x54, 0x49, 0x76, 0x0d, 0x9a, 0xb2, 0xd9, 0x70, 0x04, 0x9f, 0xbe,
|
||||
0xfd, 0x25, 0x81, 0x50, 0x87, 0xcc, 0x54, 0x8d, 0x34, 0x32, 0x2b, 0x10, 0xc8, 0x82, 0x43, 0x11, 0x3a, 0xe0, 0x91,
|
||||
0xb4, 0xc7, 0xa3, 0x8c, 0x98, 0xfa, 0x59, 0x72, 0x9a, 0x92, 0x15, 0x0b, 0x03, 0x93, 0x0c, 0xee, 0xd0, 0x2e, 0x15,
|
||||
0xa7, 0x19, 0xcc, 0x62, 0x84, 0xce, 0xab, 0x19, 0x62, 0xa5, 0xf3, 0x01, 0x6d, 0xd4, 0x21, 0x6e, 0xe5, 0x9d, 0xe6,
|
||||
0x87, 0x56, 0xa5, 0x43, 0x40, 0x93, 0xa8, 0x09, 0xbd, 0x95, 0xfa, 0x87, 0xd6, 0x30, 0xb9, 0xf4, 0x65, 0x35, 0x07,
|
||||
0x93, 0xd4, 0x85, 0x9e, 0xa3, 0x98, 0x62, 0xb7, 0x0d, 0x81, 0xc4, 0xf2, 0xca, 0x1c, 0x02, 0x70, 0x73, 0x5c, 0x11,
|
||||
0xae, 0xac, 0x1e, 0x20, 0x4c, 0xac, 0x18, 0xe8, 0xa4, 0x14, 0xbf, 0x0a, 0x09, 0x3e, 0xf7, 0xd8, 0x2e, 0x07, 0xc1,
|
||||
0x9e, 0x13, 0x90, 0xce, 0xa6, 0x69, 0xdb, 0x5a, 0xf8, 0x47, 0x60, 0x1c, 0x4c, 0x71, 0xef, 0xd8, 0x8d, 0x7d, 0x11,
|
||||
0xbd, 0xce, 0x10, 0x1f, 0x8e, 0xf8, 0xa4, 0x58, 0x30, 0xad, 0xa1, 0xe3, 0xca, 0xb3, 0x2d, 0xc7, 0xd4, 0x52, 0xfe,
|
||||
0x8d, 0x18, 0xab, 0xd3, 0x22, 0x49, 0x6b, 0xa5, 0x8a, 0xcd, 0xec, 0x5d, 0x66, 0x2e, 0x1d, 0x88, 0xa6, 0xef, 0x03,
|
||||
0x15, 0x4c, 0x47, 0x47, 0x27, 0xd1, 0x9c, 0x91, 0x27, 0x8d, 0x2f, 0x14, 0x15, 0x0a, 0x8f, 0x03, 0x69, 0xba, 0xed,
|
||||
0x4e, 0x6b, 0x4b, 0x0b, 0x83, 0xec, 0xf9, 0x54, 0x08, 0x9a, 0xaa, 0xbc, 0x50, 0x18, 0x65, 0xd1, 0x37, 0x23, 0xda,
|
||||
0x28, 0x44, 0x7e, 0xb7, 0x4f, 0xe4, 0x80, 0xa8, 0xf8, 0x2f, 0xdc, 0xad, 0xaf, 0xb4, 0xfa, 0x7f,
|
||||
};
|
||||
|
||||
static const uint8_t MIXED[] = {
|
||||
0x4a, 0x2d, 0x2e, 0xc8, 0xc8, 0xcf, 0x4d, 0x55, 0xc8, 0x2f, 0x49, 0x54, 0x48, 0x49, 0x4d, 0xcb, 0x49, 0x2c, 0x49,
|
||||
0x55, 0x48, 0x1d, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x84, 0x62, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0x00, 0xb8, 0x0b, 0x47, 0xf4, 0x53, 0xc3, 0x7d, 0x78, 0x8e, 0xb4, 0x4d, 0xb7, 0x48, 0x2f, 0x6d, 0x46, 0x3d, 0x19,
|
||||
0xe5, 0x70, 0x24, 0x4c, 0xbb, 0xa0, 0xe3, 0x58, 0xfc, 0x78, 0x74, 0xfa, 0x8c, 0xb1, 0x95, 0x5c, 0xaf, 0xb5, 0x32,
|
||||
0x12, 0x53, 0xfe, 0x93, 0xd1, 0x23, 0x2c, 0x45, 0xed, 0x4c, 0xe9, 0xc9, 0x99, 0x0d, 0x7d, 0xff, 0xdc, 0x01, 0x30,
|
||||
0x51, 0x55, 0x2c, 0x63, 0xa0, 0xb0, 0xc7, 0x6d, 0xee, 0xe4, 0xcc, 0x36, 0xd0, 0x32, 0x40, 0x96, 0x91, 0xdd, 0x43,
|
||||
0x6b, 0x26, 0xaa, 0xd8, 0x7c, 0xd6, 0x16, 0x75, 0x11, 0xa6, 0x5a, 0x4a, 0x4e, 0x86, 0x1f, 0x51, 0x53, 0x3c, 0x01,
|
||||
0x1a, 0x16, 0x14, 0xc6, 0x54, 0xfb, 0x44, 0x5b, 0x1a, 0x38, 0x21, 0x92, 0x03, 0xeb, 0x04, 0x9d, 0xe8, 0xf8, 0xfa,
|
||||
0x4a, 0x73, 0xa4, 0x2f, 0xfc, 0x6d, 0xf3, 0x18, 0x6d, 0xc4, 0xc1, 0x62, 0x25, 0x5d, 0xa3, 0x9d, 0xb9, 0x9f, 0x7b,
|
||||
0xa8, 0xc4, 0xbb, 0xdd, 0xdb, 0xa7, 0xbd, 0x25, 0xf7, 0x00, 0x54, 0x54, 0xce, 0xeb, 0x61, 0xaf, 0xb2, 0xfb, 0x42,
|
||||
0x16, 0x9f, 0xf7, 0xdb, 0x25, 0x28, 0x54, 0x68, 0x0c, 0x22, 0x76, 0x06, 0x2f, 0x12, 0xa7, 0xfa, 0x7c, 0x57, 0xd3,
|
||||
0xc8, 0x90, 0x17, 0x09, 0xf5, 0x89, 0xea, 0xb2, 0x96, 0xa9, 0x49, 0x8f, 0xa1, 0xb0, 0xb5, 0x74, 0xf0, 0xf6, 0xa7,
|
||||
0xc6, 0x14, 0x4a, 0x3a, 0xb6, 0xdf, 0x8d, 0x9a, 0x3a, 0xb0, 0x0e, 0x2c, 0xd0, 0x7c, 0xa5, 0x7b, 0xf2, 0xa1, 0x8e,
|
||||
0xe5, 0x58, 0x6b, 0x0b, 0x0a, 0xf0, 0x62, 0xb8, 0xf0, 0x9e, 0x59, 0xac, 0xf6, 0xb3, 0x38, 0x54, 0x7f, 0x2f, 0x84,
|
||||
0x10, 0x5a, 0xb7, 0xb3, 0x8a, 0xf3, 0x54, 0x32, 0xdb, 0x3b, 0xf1, 0x32, 0x5c, 0x59, 0x93, 0x36, 0x4c, 0x0d, 0x56,
|
||||
0x5e, 0x26, 0xe8, 0x2b, 0x71, 0xc0, 0x2e, 0x53, 0xab, 0x23, 0x86, 0x9a, 0x4c, 0x2e, 0x68, 0x54, 0xdd, 0xe9, 0x43,
|
||||
0x19, 0x40, 0xaa, 0x71, 0x40, 0x7f, 0xea, 0xdb, 0x1c, 0x51, 0xe4, 0x6c, 0xf9, 0x6b, 0xf3, 0x37, 0xd4, 0x8d, 0xa9,
|
||||
0x67, 0xde, 0x48, 0xae, 0xea, 0xaf, 0x8f, 0x5f, 0xdd, 0x4a, 0x05, 0x22, 0xb6, 0xd5, 0x00, 0x8b, 0x33, 0x15, 0x60,
|
||||
0x30, 0x06, 0xab, 0x13, 0x4c, 0x3d, 0x10, 0x63, 0x16, 0x73, 0x52, 0x06, 0xdf, 0xb8, 0x8c, 0xe4, 0xee, 0x41, 0x26,
|
||||
0xa6, 0x1c, 0xd0, 0xd2, 0x03, 0x2e, 0xe2, 0x41, 0xc9, 0xf5, 0x9a, 0xa9, 0xad, 0x27, 0x70, 0xf8, 0xd4, 0xfc, 0x99,
|
||||
0xd5, 0x1d, 0x88, 0x35, 0x5b, 0x35, 0xdb, 0x3b, 0x94, 0x58, 0xda, 0x24, 0xb7, 0xe7, 0xdc, 0x5a, 0x22, 0xaf, 0x04,
|
||||
0x5f, 0x16, 0xf3, 0x61, 0xbd, 0x34, 0x3f, 0xc6, 0xb2, 0xbb, 0xec, 0x6a, 0x97, 0x7f, 0x72, 0x40, 0x14, 0xa8, 0x70,
|
||||
0x15, 0x06, 0x30, 0x7d, 0x6b, 0x2b, 0x1a, 0xac, 0x17, 0x93, 0x73, 0xe1, 0x9c, 0xc7, 0x0a, 0xd7, 0x46, 0xb6, 0xee,
|
||||
0xed, 0x06, 0x63, 0xac, 0xb3, 0x11, 0x50, 0x45, 0x38, 0x37, 0xa9, 0xf1, 0x11, 0x04, 0x31, 0x97, 0x2a, 0x98, 0xed,
|
||||
0x07, 0x4c, 0x46, 0x92, 0xfd, 0x72, 0xef, 0xb0, 0xd6, 0xcb, 0x1d, 0x9b, 0x10, 0x8f, 0xde, 0x1c, 0x83, 0x82, 0x66,
|
||||
0xdc, 0x61, 0x49, 0x64, 0x8c, 0xef, 0x70, 0x20, 0xd5, 0x00, 0x37, 0x59, 0x03, 0x36, 0x1f, 0xe1, 0x71, 0xf8, 0x39,
|
||||
0xf4, 0xb2, 0x32, 0x29, 0x5d, 0x84, 0xcf, 0xfd, 0x47, 0x75, 0xf8, 0xb0, 0xe4, 0x53, 0x09, 0x7c, 0xbf, 0x55, 0x58,
|
||||
0xe3, 0xe2, 0x0a, 0xa5, 0x7c, 0xa7, 0x05, 0x5f, 0x7e, 0x5e, 0x45, 0x9c, 0x0e, 0xa4, 0x67, 0xd6, 0x48, 0x7e, 0x8b,
|
||||
0x66, 0x0f, 0x38, 0x06, 0xc2, 0x95, 0x00, 0xcc, 0x77, 0xa9, 0x41, 0x9b, 0xe4, 0xc3, 0xc1, 0xe8, 0xbc, 0xd1, 0x79,
|
||||
0x8a, 0x39, 0xbf, 0x38, 0x29, 0x21, 0xc9, 0xf6, 0x65, 0x7a, 0x80, 0xf9, 0xc6, 0x0c, 0x9d, 0xaa, 0x66, 0x9b, 0x89,
|
||||
0x34, 0x60, 0xe1, 0xa8, 0xe2, 0xf7, 0x4d, 0x69, 0x35, 0x86, 0x7e, 0x0f, 0xc8, 0x2c, 0x7e, 0x3c, 0xe4, 0x33, 0xbf,
|
||||
0x42, 0xe3, 0x44, 0xd6, 0x29, 0x45, 0x4d, 0xf9, 0x80, 0x94, 0x2e, 0x3a, 0xe4, 0xf8, 0x26, 0x80, 0x52, 0x99, 0x00,
|
||||
0x4a, 0x2c, 0xb7, 0xfd, 0x24, 0x0c, 0x8b, 0x80, 0xe5, 0x4c, 0xdd, 0x7c, 0xad, 0x52, 0x62, 0x9e, 0xa2, 0x9c, 0xdb,
|
||||
0x3a, 0x65, 0x8f, 0xee, 0xf6, 0x56, 0xff, 0xc3, 0x38, 0x8d, 0x7d, 0x5e, 0x50, 0x93, 0x64, 0xbf, 0x65, 0xe4, 0xa2,
|
||||
0xbb, 0x21, 0x25, 0x47, 0x98, 0x1c, 0x25, 0xcf, 0x6b, 0x09, 0x95, 0x30, 0x32, 0xa3, 0x87, 0xa2, 0xb1, 0x55, 0xa9,
|
||||
0x30, 0x0d, 0xac, 0x3e, 0x03, 0x29, 0x7d, 0xe0, 0x69, 0xc4, 0xd5, 0xf7, 0xa8, 0x4f, 0xa5, 0xf8, 0x85, 0x8f, 0x2a,
|
||||
0x5e, 0x93, 0xf7, 0x1e, 0x69, 0x3c, 0xc3, 0xa0, 0x99, 0x91, 0xd4, 0xe4, 0xe9, 0xd1, 0x4f, 0x9a, 0xcc, 0xbb, 0xf4,
|
||||
0x1f, 0xe8, 0x94, 0x3e, 0x66, 0x95, 0xf7, 0x3f, 0xa5, 0x14, 0x5f, 0x3a, 0xbf, 0x8f, 0x75, 0xfe, 0x74, 0xd5, 0x1a,
|
||||
0x05, 0x4b, 0xd2, 0x34, 0x50, 0x3b, 0xcf, 0x39, 0x00, 0x1c, 0x6f, 0x7d, 0xb4, 0xd0, 0x65, 0x3b, 0xa6, 0x30, 0xf6,
|
||||
0x65, 0x84, 0xb8, 0x32, 0x2c, 0x10, 0xf3, 0xf2, 0x4e, 0x76, 0xa5, 0x90, 0x22, 0x71, 0xba, 0x69, 0x64, 0x1c, 0xf1,
|
||||
0x14, 0xc1, 0x98, 0x48, 0x5f, 0x21, 0x63, 0x26, 0x0d, 0xb8, 0x73, 0x4b, 0x52, 0xcc, 0x16, 0x78, 0x0a, 0x44, 0x89,
|
||||
0xdd, 0xbd, 0xf4, 0x5a, 0x47, 0x54, 0xaa, 0x7e, 0x06, 0x97, 0xf8, 0x31, 0x9f, 0x6e, 0xbf, 0x91, 0x36, 0xd4, 0x80,
|
||||
0xf9, 0xba, 0x9a, 0x62, 0x1e, 0xce, 0xe1, 0x03, 0xec, 0x7a, 0x55, 0xff, 0x1e, 0x96, 0x2a, 0x41, 0x9f, 0xf2, 0xd7,
|
||||
0x0c, 0xa4, 0x46, 0x95, 0xc8, 0x86, 0xdd, 0x96, 0x8d, 0x60, 0x00, 0x06, 0x71, 0x6d, 0x23, 0xc2, 0x91, 0x1e, 0xd2,
|
||||
0xdb, 0xe2, 0xfe, 0x59, 0xc9, 0xb5, 0x46, 0x36, 0x6f, 0x0b, 0x3f, 0x3f, 0xd2, 0x9e, 0x5c, 0x93, 0x99, 0xac, 0x13,
|
||||
0x42, 0x44, 0x82, 0x7f, 0xbc, 0x2c, 0xcc, 0xb7, 0xfa, 0x3f, 0x9e, 0xec, 0x6e, 0x77, 0x41, 0x44, 0xab, 0x1f, 0xbe,
|
||||
0x7a, 0xd4, 0xc5, 0x2c, 0x32, 0xc6, 0xfe, 0xba, 0x1d, 0x05, 0xdc, 0x7c, 0xd1, 0x67, 0x30, 0xf6, 0x65, 0x83, 0x80,
|
||||
0x69, 0xd0, 0xd7, 0xc7, 0xdf, 0xff, 0xbb, 0xef, 0xf8, 0xb8, 0x09, 0x0f, 0x73, 0x31, 0x89, 0x64, 0x7c, 0x6f, 0x1f,
|
||||
0x11, 0xb8, 0x60, 0xb4, 0x97, 0x11, 0xb7, 0xe4, 0x12, 0x41, 0x8b, 0x6c, 0x39, 0x20, 0xaf, 0x51, 0x13, 0xae, 0xcf,
|
||||
0xd1, 0x7a, 0xe0, 0x4f, 0x0b, 0x51, 0x2b, 0xe3, 0x6d, 0x84, 0xd1, 0xee, 0xe1, 0xc4, 0xee, 0x9c, 0xb7, 0x00, 0x41,
|
||||
0xbe, 0x51, 0xa2, 0xbb, 0x68, 0x90, 0x8e, 0xca, 0x4c, 0x02, 0xdb, 0x62, 0xd5, 0xc8, 0x4f, 0xd0, 0x03, 0x48, 0x9d,
|
||||
0x3e, 0x09, 0x98, 0x5d, 0x05, 0xa8, 0xa5, 0xcf, 0xb6, 0x4a, 0x27, 0x50, 0xe6, 0xdd, 0x45, 0x98, 0xbd, 0x75, 0x07,
|
||||
0xb9, 0x3d, 0xa0, 0x7f, 0xd7, 0x53, 0x88, 0xb7, 0xa8, 0xa0, 0xcc, 0x59, 0xab, 0x04, 0x1b, 0x21, 0x27, 0xee, 0x94,
|
||||
0x03, 0xea, 0x66, 0xe1, 0x1b, 0xbf, 0xd7, 0x0d, 0x59, 0xac, 0x9e, 0x29, 0xe9, 0xf0, 0x53, 0xa3, 0x63, 0xbf, 0xda,
|
||||
0x61, 0xb7, 0xbb, 0x28, 0xa0, 0xd6, 0x32, 0xd5, 0x90, 0x21, 0x1a, 0x66, 0xab, 0x92, 0xcd, 0x58, 0xd9, 0xad, 0x85,
|
||||
0xec, 0x45, 0x24, 0x95, 0xb6, 0xfd, 0x4d, 0x6d, 0xe3, 0x4c, 0x44, 0x4b, 0x4d, 0xd7, 0xe0, 0xc2, 0x40, 0x7a, 0x56,
|
||||
0xec, 0x0a, 0xe0, 0xaf, 0x6e, 0x45, 0xe4, 0xeb, 0x30, 0xe3, 0xab, 0xf3, 0xd9, 0xfe, 0x7e, 0x40, 0x0d, 0x5d, 0x68,
|
||||
0x4c, 0x66, 0xc5, 0x93, 0xbb, 0x33, 0x06, 0x63, 0x49, 0xf3, 0xc1, 0x4f, 0x73, 0x90, 0xfa, 0x84, 0xe5, 0x5b, 0x61,
|
||||
0x38, 0xa6, 0xcb, 0x60, 0xce, 0x05, 0xf5, 0xe3, 0xaf, 0x43, 0xcb, 0xeb, 0xe2, 0xfd, 0x14, 0xd2, 0x71, 0x60, 0x2f,
|
||||
0x5c, 0x7c, 0x0b, 0x87, 0x81, 0x1a, 0xc2, 0xd5, 0xfd, 0xf3, 0xd9, 0x19, 0x93, 0x9e, 0x5b, 0x56, 0x25, 0xbb, 0xfd,
|
||||
0x10, 0x75, 0xd0, 0xc5, 0x86, 0xb9, 0xe4, 0x7a, 0xb0, 0xe0, 0xdd, 0xca, 0xf7, 0xa7, 0x9a, 0x3e, 0xb6, 0x5a, 0xd0,
|
||||
0xde, 0x75, 0x6b, 0xa5, 0xa3, 0xdf, 0xb4, 0x0e, 0xa1, 0x89, 0x8c, 0xe9, 0xf1, 0x62, 0x8b, 0xda, 0x5f, 0xf8, 0x1d,
|
||||
0xe6, 0xd9, 0x70, 0xc9, 0xd0, 0x92, 0x42, 0x3f, 0x36, 0xf9, 0xb9, 0x23, 0x6b, 0x5d, 0x26, 0x9c, 0x14, 0x5d, 0xe7,
|
||||
0x9b, 0x81, 0x11, 0x1b, 0xec, 0xd1, 0xf9, 0x22, 0xfe, 0xc4, 0xa9, 0xd7, 0x71, 0x5e, 0x32, 0x8d, 0x64, 0x41, 0x50,
|
||||
0xca, 0x7c, 0xc8, 0x55, 0xc6, 0xb5, 0xe3, 0xd8, 0xa1, 0xe8, 0xb5, 0x1c, 0x68, 0x20, 0xef, 0x42, 0xc6, 0x9d, 0xea,
|
||||
0x2f, 0x8a, 0xa4, 0x87, 0x3b, 0xdc, 0x0c, 0xc7, 0x9f, 0x3f, 0x89, 0x29, 0x9b, 0x46, 0xb7, 0xc7, 0x6a, 0x16, 0xe6,
|
||||
0x2f, 0xe6, 0x9f, 0xa8, 0x02, 0xfb, 0x99, 0x1b, 0x4f, 0xcd, 0x40, 0xf7, 0xad, 0xa9, 0xd0, 0x56, 0x1a, 0xad, 0xc0,
|
||||
0x47, 0x96, 0x36, 0xbe, 0x11, 0x7f, 0x0c, 0x6d, 0x47, 0xab, 0x4f, 0xe1, 0x90, 0x47, 0x21, 0xd6, 0x33, 0x72, 0x11,
|
||||
0xb4, 0x65, 0x99, 0x4a, 0x43, 0x52, 0x90, 0x80, 0x2e, 0xed, 0x75, 0x96, 0x39, 0x78, 0x45, 0x0f, 0x42, 0xd2, 0x3b,
|
||||
0x3b, 0xf9, 0x84, 0xed, 0x1d, 0x5e, 0x8a, 0x24, 0x1f, 0xff, 0xe1, 0x7e, 0x75, 0xef, 0xd4, 0xc5, 0x51, 0x85, 0x07,
|
||||
0x0e, 0xcb, 0xda, 0x40, 0x92, 0x8e, 0x00, 0xf9, 0x4c, 0x44, 0x03, 0xa6, 0x56, 0xbf, 0xa0, 0xc6, 0x04, 0xec, 0xc7,
|
||||
0xac, 0xbd, 0x78, 0xd2, 0x0a, 0x9d, 0x3f, 0x83, 0x42, 0xcc, 0x79, 0xc0, 0x6d, 0x9d, 0xa3, 0x0d, 0x27, 0x66, 0x2f,
|
||||
0xcd, 0x21, 0x22, 0xeb, 0x38, 0x64, 0xc7, 0x31, 0x1b, 0x66, 0x68, 0xd4, 0x53, 0xce, 0x0b, 0xc5, 0x07, 0x07, 0x48,
|
||||
0x89, 0xa9, 0x86, 0xa6, 0x5b, 0xaa, 0xdc, 0xb5, 0x20, 0x00, 0x7b, 0xeb, 0x0f, 0x58, 0x48, 0x3f, 0x1d, 0x1b, 0xe4,
|
||||
0x87, 0x29, 0x23, 0xba, 0x92, 0xec, 0x3a, 0xc8, 0x05, 0x9e, 0x56, 0x2e, 0xf6, 0x69, 0x97, 0x79, 0x4a, 0x8f, 0xfc,
|
||||
0xac, 0xcf, 0x5a, 0xe9, 0x12, 0x18, 0xc7, 0x59, 0xc6, 0x1c, 0xf1, 0xe3, 0xfe, 0x8d, 0x3f, 0x6e, 0x1c, 0xfa, 0x4d,
|
||||
0xb3, 0x73, 0xa4, 0x9b, 0x77, 0xd9, 0x20, 0xf8, 0x86, 0xf1, 0xb0, 0xac, 0x6e, 0x35, 0x62, 0x68, 0x01, 0xc6, 0xe6,
|
||||
0xee, 0x88, 0xf3, 0x30, 0x80, 0xc7, 0x8b, 0xd2, 0x0a, 0xb5, 0xe3, 0x17, 0x0e, 0xf8, 0xa6, 0xdf, 0x53, 0x22, 0x5f,
|
||||
0x3d, 0x4d, 0xae, 0xc7, 0xd1, 0x4b, 0xb9, 0xd0, 0xe6, 0x63, 0x80, 0x6e, 0x4c, 0x1d, 0x9d, 0x28, 0xf4, 0xe2, 0x01,
|
||||
0x62, 0x4d, 0x23, 0xb0, 0x67, 0x61, 0x49, 0x3f, 0x39, 0xae, 0x5f, 0x0f, 0x18, 0xc5, 0x81, 0x47, 0xd4, 0xa5, 0x04,
|
||||
0x93, 0xd3, 0xd5, 0x63, 0x9e, 0xe2, 0x43, 0xec, 0x50, 0xbc, 0xdf, 0x2e, 0xc7, 0x5a, 0xf7, 0x21, 0x17, 0x00, 0x51,
|
||||
0x14, 0xc2, 0x04, 0x0a, 0x57, 0xf6, 0x5d, 0x31, 0xac, 0x89, 0xef, 0x22, 0x6e, 0xb5, 0x98, 0xa4, 0x7f, 0x93, 0xfa,
|
||||
0xc2, 0xf3, 0x58, 0x07, 0x6a, 0x7e, 0x53, 0x4e, 0xc7, 0x9e, 0xa2, 0x07, 0x5e, 0x87, 0xb7, 0xdf, 0xea, 0xd3, 0x8f,
|
||||
0x84, 0xf8, 0xe6, 0xac, 0xf0, 0x42, 0x22, 0xd7, 0x76, 0xcd, 0xd0, 0x97, 0x8e, 0xc1, 0xc9, 0x6f, 0x61, 0x0c, 0xe8,
|
||||
0xdb, 0x13, 0xb5, 0x2f, 0x09, 0xd2, 0x00, 0xbc, 0x58, 0xa3, 0x47, 0x6a, 0x91, 0xe7, 0x93, 0x46, 0xe7, 0xb0, 0xfd,
|
||||
0xb8, 0xb0, 0xc6, 0xc7, 0x6d, 0xe1, 0x77, 0x39, 0x07, 0x4c, 0x22, 0x3d, 0x84, 0x98, 0x82, 0xba, 0x6f, 0x04, 0xbb,
|
||||
0xb0, 0xc9, 0xdb, 0x26, 0x18, 0xb4, 0xb6, 0xdd, 0x79, 0xe5, 0x74, 0x05, 0x4a, 0xc7, 0xfa, 0xde, 0x0b, 0x70, 0xab,
|
||||
0x8c, 0x22, 0x45, 0xa6, 0xa3, 0x76, 0x6b, 0xc2, 0x74, 0xcd, 0xf4, 0x4b, 0x19, 0xe6, 0xc6, 0x79, 0x42, 0x56, 0x4b,
|
||||
0xb4, 0xbe, 0x23, 0xdb, 0x8a, 0xc9, 0x22, 0xe9, 0x6c, 0x18, 0x10, 0xef, 0x2c, 0xa5, 0x73, 0x1a, 0x37, 0x3a, 0x2e,
|
||||
0xb5, 0x92, 0xc0, 0xe7, 0x57, 0x95, 0x45, 0x19, 0xcd, 0x6a, 0xce, 0xf8, 0xc5, 0x45, 0xe2, 0x08, 0x3f, 0x61, 0xd3,
|
||||
0x7b, 0xb3, 0xbe, 0xcb, 0x4f, 0x03, 0x9b, 0x9a, 0x24, 0x1f, 0xb5, 0x41, 0xde, 0x7a, 0x3e, 0x3e, 0xff, 0x30, 0x9c,
|
||||
0x95, 0xc1, 0xf4, 0xe6, 0xa3, 0xf6, 0xbb, 0x1f, 0xa2, 0xa0, 0x2a, 0x30, 0xfb, 0x50, 0xc0, 0xa7, 0x4f, 0x3f, 0xc3,
|
||||
0x32, 0x43, 0x2e, 0xec, 0xd9, 0x0f, 0xfd, 0xd1, 0xff, 0xc6, 0x0d, 0x16, 0x17, 0x63, 0x57, 0x53, 0x25, 0x97, 0x49,
|
||||
0xc4, 0xfb, 0x55, 0x64, 0xb4, 0x31, 0x19, 0x97, 0xa6, 0xe1, 0xfc, 0x59, 0x48, 0x27, 0x79, 0x88, 0x18, 0x5c, 0xfd,
|
||||
0x04, 0xd3, 0x0d, 0x30, 0x0a, 0xa2, 0xbd, 0x06, 0x52, 0x04, 0x5f, 0x84, 0xe8, 0x7a, 0x92, 0x52, 0xcc, 0xf4, 0x9a,
|
||||
0x06, 0x48, 0xfe, 0x22, 0x2a, 0x13, 0xaa, 0xb4, 0x13, 0x2a, 0x3c, 0xf4, 0x61, 0x6a, 0x98, 0xa0, 0x55, 0xee, 0x6c,
|
||||
0x48, 0xd6, 0x2b, 0x1d, 0xcc, 0x41, 0xcc, 0x73, 0x9a, 0xf4, 0x4e, 0x03, 0xa4, 0x98, 0x5c, 0x60, 0x24, 0x32, 0xe8,
|
||||
0x00, 0x0f, 0xe9, 0x0d, 0xf0, 0x5a, 0x95, 0xa8, 0x38, 0xef, 0x0b, 0xb0, 0x0c, 0x6e, 0x2b, 0x59, 0xb7, 0xa4, 0xa6,
|
||||
0xed, 0x47, 0xcc, 0x90, 0x65, 0xf9, 0x88, 0xa9, 0xca, 0x63, 0x9d, 0x6b, 0x4e, 0x64, 0x71, 0xd3, 0x29, 0xcb, 0x46,
|
||||
0xf0, 0x34, 0xca, 0x34, 0x2c, 0x48, 0x19, 0xe8, 0x5d, 0x55, 0xe9, 0x1f, 0x8b, 0x41, 0x8f, 0xe0, 0x7f, 0x86, 0xed,
|
||||
0x7c, 0x10, 0x8a, 0xa0, 0x46, 0x2d, 0xbd, 0xeb, 0x13, 0xa9, 0x75, 0x66, 0x70, 0xca, 0x89, 0x66, 0xa6, 0x8f, 0xaf,
|
||||
0x2b, 0x6e, 0xc5, 0x96, 0xcd, 0x74, 0xc8, 0xc2, 0xe3, 0x8e, 0x1a, 0x2b, 0xf8, 0xb9, 0xa5, 0x95, 0xaf, 0xd8, 0xd0,
|
||||
0xae, 0xb0, 0x2b, 0x44, 0xe9, 0x59, 0x90, 0x7b, 0x1d, 0x26, 0x7d, 0x0d, 0xdc, 0xdc, 0xb9, 0xba, 0x94, 0x78, 0xdd,
|
||||
0x36, 0x68, 0xdb, 0x8e, 0xb8, 0xcc, 0x32, 0x77, 0x41, 0xe3, 0x27, 0x09, 0xac, 0x98, 0xc9, 0xf2, 0x7a, 0x7e, 0x3c,
|
||||
0x5e, 0x5b, 0x68, 0x20, 0xf0, 0x7c, 0xc8, 0xc2, 0x7f, 0x94, 0xc8, 0x05, 0x91, 0xac, 0x2a, 0x0e, 0x98, 0xb1, 0xe0,
|
||||
0xb3, 0xee, 0xba, 0x6a, 0xa1, 0x92, 0x1a, 0xf3, 0x4f, 0xa0, 0x25, 0x2b, 0xa6, 0x92, 0xc3, 0xe3, 0x6d, 0x39, 0xb6,
|
||||
0x6a, 0x76, 0x79, 0xab, 0xa9, 0x8a, 0xc7, 0x02, 0x00, 0x15, 0x64, 0x5d, 0xdf, 0x92, 0x56, 0xea, 0x25, 0xb5, 0x2b,
|
||||
0x71, 0x39, 0x5a, 0x6d, 0x1c, 0xe9, 0x30, 0xe4, 0x0a, 0x05, 0x24, 0x8e, 0x92, 0xba, 0x90, 0x5f, 0x57, 0xb0, 0x20,
|
||||
0xb9, 0x10, 0xe3, 0x88, 0xa9, 0x14, 0x42, 0xe8, 0xe7, 0xa8, 0x09, 0xfa, 0x8c, 0x09, 0xdf, 0x01, 0x91, 0x38, 0xaf,
|
||||
0x71, 0x0b, 0x88, 0x46, 0x5f, 0x29, 0xe8, 0xb1, 0xa7, 0xe9, 0xf1, 0x54, 0xdf, 0x9d, 0xae, 0xca, 0xff, 0x21, 0x2f,
|
||||
0x80, 0xcc, 0xd9, 0xcc, 0x11, 0x09, 0x6d, 0x3a, 0x5f, 0x5d, 0x44, 0x4f, 0x75, 0x03, 0xc1, 0x95, 0x1f, 0x33, 0x40,
|
||||
0x90, 0x47, 0x54, 0xcf, 0xcf, 0x0e, 0xde, 0xac, 0xb5, 0xae, 0x11, 0x83, 0x05, 0x12, 0x57, 0x64, 0x2f, 0x32, 0x7a,
|
||||
0x86, 0x5c, 0x30, 0x86, 0xbe, 0x3b, 0x34, 0x7c, 0xbd, 0x89, 0x08, 0xbb, 0x0c, 0x63, 0x72, 0x2e, 0xc9, 0x4b, 0x55,
|
||||
0x36, 0x1f, 0xfc, 0x86, 0x56, 0x86, 0xed, 0x6f, 0xa1, 0xc0, 0xa7, 0x66, 0xe4, 0x8f, 0xc7, 0xcb, 0x93, 0x36, 0xd2,
|
||||
0xe4, 0x74, 0xb6, 0xbd, 0x78, 0x0d, 0xc5, 0x37, 0x2d, 0xcf, 0x8e, 0xbc, 0xa1, 0xff, 0x2c, 0x5a, 0x4b, 0x68, 0xe4,
|
||||
0x42, 0x10, 0xc2, 0x24, 0x0e, 0xa5, 0xe0, 0x3f, 0xd4, 0xa7, 0x9d, 0xe6, 0xee, 0x7b, 0x85, 0x66, 0x87, 0xe7, 0x42,
|
||||
0xa4, 0xe6, 0x6d, 0xcd, 0xd8, 0x7a, 0x6b, 0x50, 0x0c, 0xaa, 0xf4, 0xdd, 0x53, 0x59, 0x6c, 0xd1, 0xca, 0x3b, 0xe6,
|
||||
0x06, 0x50, 0xda, 0x3d, 0xbc, 0x05, 0x67, 0xf4, 0x45, 0x83, 0x41, 0xce, 0x72, 0xdf, 0x47, 0x2c, 0xc0, 0x5e, 0xde,
|
||||
0x2b, 0x42, 0xf7, 0xc1, 0x7a, 0x4e, 0x8d, 0x8b, 0xfc, 0xe7, 0xe9, 0x06, 0x21, 0xee, 0x79, 0x31, 0x04, 0x07, 0xca,
|
||||
0x13, 0x44, 0x4c, 0xc7, 0x42, 0xa4, 0xb4, 0xaf, 0x32, 0x62, 0x42, 0xd7, 0x3f, 0x58, 0xed, 0x25, 0x78, 0xd3, 0x52,
|
||||
0x48, 0x31, 0x36, 0x1c, 0xb8, 0x47, 0x4f, 0x2f, 0x71, 0x31, 0x75, 0x97, 0x6f, 0xcc, 0x66, 0x34, 0xe8, 0xe3, 0xe2,
|
||||
0x21, 0x4c, 0x7f, 0x06, 0xe9, 0xfd, 0xf4, 0x6a, 0x69, 0xce, 0xa8, 0xfa, 0x83, 0x3d, 0xa2, 0x95, 0x87, 0x70, 0xd6,
|
||||
0x67, 0x62, 0xf2, 0x33, 0x06, 0x84, 0x2a, 0x68, 0x4f, 0x93, 0x9f, 0x7a, 0xff, 0xd1, 0xee, 0x07, 0x10, 0x4d, 0x86,
|
||||
0x57, 0xda, 0xcd, 0x2a, 0x28, 0xe5, 0x77, 0x24, 0x36, 0x8f, 0x41, 0x4a, 0x8c, 0x62, 0x1e, 0xb2, 0x8d, 0x32, 0xe6,
|
||||
0x34, 0xc1, 0xf9, 0x01, 0xa4, 0x61, 0x73, 0xdb, 0xcc, 0xa1, 0x79, 0x08, 0x2c, 0xc0, 0x1f, 0x32, 0x71, 0xd0, 0x8c,
|
||||
0xc6, 0xb5, 0x45, 0x9c, 0x62, 0x7c, 0x40, 0x37, 0xfd, 0x6a, 0x99, 0x6f, 0xd4, 0xc6, 0xae, 0x5c, 0x18, 0xaf, 0xef,
|
||||
0xb7, 0x59, 0x20, 0x25, 0x39, 0xb0, 0xa5, 0x85, 0xb1, 0xaf, 0xda, 0x78, 0xe8, 0x09, 0xa9, 0x28, 0x34, 0x02, 0x3f,
|
||||
0xca, 0xde, 0x95, 0xd3, 0xae, 0x35, 0x67, 0x16, 0x08, 0x6f, 0x6e, 0x78, 0x7c, 0x43, 0x60, 0x21, 0x6b, 0x46, 0xde,
|
||||
0xaa, 0x00, 0x09, 0x5d, 0x9f, 0xf8, 0xf0, 0xd8, 0xb6, 0x35, 0xd1, 0x38, 0xcc, 0x02, 0x50, 0x7e, 0xa3, 0x24, 0xfd,
|
||||
0xb6, 0x5d, 0xa5, 0x2a, 0x9c, 0xb7, 0x60, 0x8d, 0x0b, 0x07, 0x4b, 0x9b, 0x29, 0xf4, 0x87, 0x9f, 0x54, 0xf7, 0x29,
|
||||
0x9c, 0xcb, 0x94, 0xc6, 0xe8, 0x1d, 0x59, 0xc1, 0x85, 0x52, 0x93, 0x54, 0x3e, 0xda, 0xd3, 0x5f, 0x86, 0x39, 0x24,
|
||||
0xf3, 0x10, 0x70, 0x09, 0x9e, 0x2d, 0x2a, 0x39, 0x74, 0xea, 0xac, 0x19, 0xe2, 0xe5, 0x79, 0xfd, 0x32, 0xf3, 0xc8,
|
||||
0x1f, 0xdd, 0x74, 0xa7, 0x5b, 0x94, 0x70, 0xeb, 0xb0, 0x0d, 0x7a, 0xde, 0x77, 0x8e, 0x41, 0xa8, 0x61, 0x3a, 0xf6,
|
||||
0x79, 0x32, 0x1e, 0x62, 0x7c, 0x3f, 0xfb, 0xc3, 0x7b, 0xa8, 0xd1, 0x6a, 0x52, 0x56, 0x30, 0x15, 0x15, 0xb3, 0x3e,
|
||||
0x0d, 0x01, 0x2c, 0x87, 0xff, 0x23, 0x68, 0x88, 0x9b, 0xca, 0x45, 0xf9, 0xee, 0xae, 0x97, 0xde, 0x80, 0xdd, 0x9f,
|
||||
0x4c, 0x2d, 0x69, 0x4d, 0xfc, 0xc3, 0xd3, 0x36, 0x91, 0x61, 0x36, 0x53, 0xa9, 0x8a, 0x33, 0x7f, 0xa5, 0x0c, 0xa0,
|
||||
0xaf, 0x39, 0x93, 0xef, 0xee, 0xb2, 0x0c, 0x60, 0x20, 0x0a, 0xbb, 0xe7, 0x73, 0xad, 0x1b, 0xdb, 0xa4, 0x09, 0x79,
|
||||
0x63, 0xf7, 0xd8, 0x4f, 0xa0, 0xf3, 0x2d, 0x61, 0x9a, 0xe1, 0x40, 0x99, 0xec, 0x03, 0xf9, 0xa3, 0x2f, 0x95, 0x3d,
|
||||
0x48, 0x28, 0x85, 0xfc, 0x8b, 0xb1, 0xf1, 0xf7, 0x86, 0x68, 0x64, 0x52, 0x01, 0x19, 0xd0, 0xdd, 0xd7, 0xec, 0x4b,
|
||||
0x22, 0x09, 0x4e, 0x8a, 0x2e, 0x9f, 0xa0, 0x1e, 0x20, 0x43, 0xa2, 0x70, 0x73, 0x9b, 0x9e, 0x7d, 0x0b, 0x69, 0x54,
|
||||
0x52, 0x01, 0x65, 0xa8, 0x47, 0x2e, 0xd6, 0x0e, 0xfb, 0x79, 0xf2, 0xb1, 0x13, 0xe1, 0xfb, 0x66, 0xb1, 0x49, 0x16,
|
||||
0x54, 0xb6, 0x07, 0xe9, 0x5f, 0xb8, 0x2a, 0xfc, 0x59, 0xc2, 0x8f, 0xcd, 0xe8, 0xce, 0xa3, 0xb0, 0x35, 0x03, 0xf5,
|
||||
0x6d, 0x59, 0xaf, 0x83, 0x98, 0x88, 0x03, 0xc0, 0xae, 0xfb, 0x90, 0x68, 0xf4, 0x9f, 0x85, 0x04, 0x10, 0x7f, 0xa5,
|
||||
0x3c, 0x64, 0xf2, 0x0b, 0xe0, 0x2e, 0x84, 0x9e, 0xb5, 0x60, 0xa5, 0x52, 0xff, 0x40, 0x14, 0x23, 0xd3, 0xc0, 0x09,
|
||||
0xd2, 0x19, 0x9b, 0x25, 0x5e, 0x63, 0x34, 0x1a, 0x41, 0x71, 0x2e, 0x1a, 0x70, 0x80, 0xca, 0x01, 0x10, 0x57, 0x04,
|
||||
0x41, 0xf8, 0x0f, 0x6d, 0x91, 0xea, 0xa5, 0xc7, 0x55, 0x91, 0x41, 0x62, 0xcc, 0x3a, 0x43, 0x39, 0xfb, 0x52, 0x13,
|
||||
0xcb, 0x5d, 0xa4, 0xa5, 0xbb, 0x47, 0x4f, 0xce, 0x27, 0x1d, 0x88, 0x4a, 0xc8, 0x37, 0x15, 0x89, 0xce, 0x90, 0x84,
|
||||
0x3e, 0x79, 0x34, 0x2f, 0xa5, 0x77, 0x68, 0xf1, 0x96, 0x01, 0x96, 0xa6, 0x4e, 0xb3, 0xff, 0x64, 0xf9, 0x2f, 0x1f,
|
||||
0x7b, 0xff, 0x72, 0xdf, 0x2d, 0x13, 0xf1, 0x7d, 0x2d, 0xfc, 0x7b, 0x3c, 0xd9, 0x21, 0xce, 0xcb, 0x37, 0xd7, 0xf6,
|
||||
0x76, 0xb1, 0x6b, 0x69, 0x0e, 0x90, 0xee, 0xe4, 0x99, 0x3a, 0xc3, 0x04, 0xba, 0x2b, 0xe9, 0xa3, 0xdf, 0xb5, 0x09,
|
||||
0xf8, 0x2c, 0x78, 0x3f, 0x6d, 0x2b, 0x9f, 0xa8, 0x3f, 0x30, 0xdf, 0xb0, 0xd8, 0x8d, 0x30, 0x19, 0xec, 0x7b, 0x4a,
|
||||
0xca, 0x55, 0xc3, 0xde, 0x5d, 0xaf, 0x2f, 0x99, 0x66, 0xb3, 0xb4, 0xda, 0x84, 0x54, 0x5c, 0x2d, 0x2a, 0x9b, 0xf5,
|
||||
0x8f, 0x2b, 0x43, 0x7c, 0x61, 0x07, 0x14, 0x46, 0x28, 0xf7, 0x00, 0xf4, 0x0a, 0xba, 0xff, 0x14, 0x08, 0x1e, 0xaa,
|
||||
0x97, 0xfc, 0x4d, 0x99, 0xf7, 0x52, 0xdd, 0x7e, 0x25, 0x86, 0x95, 0xfe, 0x33, 0x61, 0x00, 0x53, 0xc6, 0xf3, 0xb4,
|
||||
0xa2, 0x39, 0x4f, 0x5f, 0x1d, 0x92, 0x09, 0x96, 0xd8, 0x25, 0x70, 0x5e, 0x6c, 0x0a, 0x8c, 0x76, 0x4e, 0x40, 0xd6,
|
||||
0x6a, 0xed, 0x9e, 0xba, 0x59, 0x8d, 0x7d, 0xcd, 0x81, 0xe6, 0x27, 0xce, 0x8f, 0x7b, 0xfe, 0x5e, 0xa8, 0xbc, 0x6c,
|
||||
0x7f, 0x37, 0xdb, 0x8e, 0xce, 0x75, 0x6e, 0xa0, 0x80, 0xb9, 0xc9, 0x27, 0x1e, 0x85, 0x19, 0xe0, 0x00, 0x4c, 0x41,
|
||||
0x6b, 0x8b, 0x05, 0x74, 0xb7, 0x2a, 0x70, 0x97, 0x2c, 0x0d, 0x2b, 0x54, 0x9b, 0xa4, 0x74, 0xfe, 0xe7, 0xda, 0xbb,
|
||||
0x27, 0x70, 0x12, 0xef, 0x03, 0x43, 0xe2, 0x85, 0x7b, 0x02, 0x92, 0x48, 0x11, 0x94, 0x53, 0xe4, 0x76, 0x8d, 0x3f,
|
||||
0x30, 0xed, 0x5c, 0x15, 0x3e, 0x5d, 0x37, 0xb0, 0x24, 0x39, 0xc7, 0x4c, 0x89, 0x10, 0x93, 0x79, 0xef, 0xf3, 0x2d,
|
||||
0xdb, 0xc2, 0x8a, 0x00, 0x00, 0x00, 0xff, 0xff, 0x1b, 0xed, 0xbb, 0x8c, 0x8a, 0x8d, 0x8a, 0xe1, 0x16, 0x03, 0x00,
|
||||
};
|
||||
|
||||
static constexpr size_t WINDOW = 4096;
|
||||
static constexpr size_t PLAIN_SIZE = 16000;
|
||||
|
||||
@@ -223,18 +757,55 @@ static std::vector<uint8_t> build_plain() {
|
||||
return plain;
|
||||
}
|
||||
|
||||
// Text, incompressible noise that zlib stores, then text again
|
||||
static std::vector<uint8_t> build_mixed() {
|
||||
std::vector<uint8_t> plain;
|
||||
const char *text = "esphome ota deflate ";
|
||||
for (int i = 0; i < 60; i++)
|
||||
plain.insert(plain.end(), text, text + strlen(text));
|
||||
uint32_t x = 3;
|
||||
for (int i = 0; i < 3000; i++)
|
||||
plain.push_back(lcg_next(x));
|
||||
for (int i = 0; i < 40; i++)
|
||||
plain.insert(plain.end(), text, text + strlen(text));
|
||||
return plain;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> build_skewed() {
|
||||
std::vector<uint8_t> plain;
|
||||
const char *common = "etaoin ";
|
||||
uint32_t x = 7;
|
||||
for (int i = 0; i < 6000; i++) {
|
||||
uint8_t r = lcg_next(x);
|
||||
plain.push_back(r < 230 ? common[r % 7] : lcg_next(x));
|
||||
}
|
||||
return plain;
|
||||
}
|
||||
|
||||
// Mirrors the OTA session: chunked input through the read callback, window as output
|
||||
struct Session : OtaInflateState {
|
||||
const uint8_t *in;
|
||||
size_t in_len;
|
||||
size_t in_pos;
|
||||
size_t chunk;
|
||||
// Like the session where the ack follows the write: output is taken
|
||||
// before each read, up to dest
|
||||
bool flush_in_read;
|
||||
size_t flushed;
|
||||
std::vector<uint8_t> out;
|
||||
uint8_t window[WINDOW];
|
||||
};
|
||||
|
||||
static void take_output(Session &s) {
|
||||
const size_t produced = s.dest - s.window;
|
||||
s.out.insert(s.out.end(), s.window + s.flushed, s.window + produced);
|
||||
s.flushed = produced;
|
||||
}
|
||||
|
||||
static int read_cb(OtaInflateState *d) {
|
||||
auto *s = static_cast<Session *>(d);
|
||||
if (s->flush_in_read)
|
||||
take_output(*s);
|
||||
if (s->in_pos >= s->in_len)
|
||||
return -1;
|
||||
size_t n = std::min(s->chunk, s->in_len - s->in_pos);
|
||||
@@ -245,23 +816,26 @@ static int read_cb(OtaInflateState *d) {
|
||||
}
|
||||
|
||||
// Inflates the whole input; returns the decoder result and fills s.out
|
||||
static int inflate_all(Session &s, const uint8_t *in, size_t in_len, size_t chunk) {
|
||||
static int inflate_all(Session &s, const uint8_t *in, size_t in_len, size_t chunk, bool flush_in_read = false) {
|
||||
s.in = in;
|
||||
s.in_len = in_len;
|
||||
s.in_pos = 0;
|
||||
s.chunk = chunk;
|
||||
s.flush_in_read = flush_in_read;
|
||||
s.flushed = 0;
|
||||
s.out.clear();
|
||||
memset(s.window, 0, sizeof(s.window));
|
||||
ota_inflate_init(&s, s.window, WINDOW);
|
||||
s.source_read_cb = read_cb;
|
||||
int res;
|
||||
do {
|
||||
s.dest = s.window;
|
||||
s.dest_limit = s.window + WINDOW;
|
||||
// Starts the window over once the previous call filled it
|
||||
res = ota_inflate(&s);
|
||||
if (res < 0 || s.eof)
|
||||
return res < 0 ? res : OTA_INFLATE_DATA_ERROR;
|
||||
s.out.insert(s.out.end(), s.window, s.dest);
|
||||
take_output(s);
|
||||
// A full window is taken; the next call starts it over
|
||||
s.flushed = 0;
|
||||
if (s.out.size() > PLAIN_SIZE)
|
||||
return OTA_INFLATE_DATA_ERROR;
|
||||
} while (res != OTA_INFLATE_DONE);
|
||||
@@ -289,6 +863,36 @@ TEST(OtaInflate, StoredBlock) {
|
||||
EXPECT_EQ(s->out, plain);
|
||||
}
|
||||
|
||||
// Dynamic blocks, fixed Huffman blocks, codes longer than the lookup tables, and
|
||||
// stored blocks that start with read ahead bits in the bit buffer
|
||||
struct Stream {
|
||||
const uint8_t *data;
|
||||
size_t len;
|
||||
std::vector<uint8_t> (*plain)();
|
||||
};
|
||||
static const Stream STREAMS[] = {{DEFLATED, sizeof(DEFLATED), build_plain},
|
||||
{FIXED, sizeof(FIXED), build_plain},
|
||||
{SKEWED, sizeof(SKEWED), build_skewed},
|
||||
{MIXED, sizeof(MIXED), build_mixed}};
|
||||
|
||||
TEST(OtaInflate, EveryReadSizeGivesTheSameOutput) {
|
||||
// 1 byte reads never leave enough input for the fast loop, 11 to 13 straddle
|
||||
// its threshold, and the larger ones run it across read and window boundaries
|
||||
auto s = std::make_unique<Session>();
|
||||
for (const auto &stream : STREAMS) {
|
||||
const auto plain = stream.plain();
|
||||
for (bool flush_in_read : {false, true}) {
|
||||
for (size_t chunk : {size_t{1}, size_t{11}, size_t{12}, size_t{13}, size_t{300}, size_t{1040}, stream.len}) {
|
||||
ASSERT_EQ(inflate_all(*s, stream.data, stream.len, chunk, flush_in_read), OTA_INFLATE_DONE)
|
||||
<< "stream of " << stream.len << " B, read size " << chunk << ", flush in read " << flush_in_read;
|
||||
EXPECT_EQ(s->out, plain) << "stream of " << stream.len << " B, read size " << chunk << ", flush in read "
|
||||
<< flush_in_read;
|
||||
EXPECT_EQ(s->in_pos, stream.len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OtaInflate, TruncatedStreamFails) {
|
||||
auto s = std::make_unique<Session>();
|
||||
for (size_t cut : {size_t{1}, size_t{100}, size_t{1000}, sizeof(DEFLATED) - 1}) {
|
||||
@@ -305,12 +909,14 @@ TEST(OtaInflate, TruncatedStoredBlockFails) {
|
||||
TEST(OtaInflate, CorruptStreamsNeverEscapeTheWindow) {
|
||||
// Flipped bytes and garbage; the sanitizers check the decoder stays in bounds
|
||||
auto s = std::make_unique<Session>();
|
||||
std::vector<uint8_t> bad(DEFLATED, DEFLATED + sizeof(DEFLATED));
|
||||
// A coarse, non-aligned stride: neighbouring offsets hit the same paths
|
||||
for (size_t i = 0; i < bad.size(); i += 29) {
|
||||
bad[i] ^= 0x5a;
|
||||
inflate_all(*s, bad.data(), bad.size(), 1040);
|
||||
bad[i] ^= 0x5a;
|
||||
for (const auto &stream : STREAMS) {
|
||||
std::vector<uint8_t> bad(stream.data, stream.data + stream.len);
|
||||
// A coarse, non-aligned stride: neighbouring offsets hit the same paths
|
||||
for (size_t i = 0; i < bad.size(); i += 29) {
|
||||
bad[i] ^= 0x5a;
|
||||
inflate_all(*s, bad.data(), bad.size(), 1040);
|
||||
bad[i] ^= 0x5a;
|
||||
}
|
||||
}
|
||||
uint32_t x = 99;
|
||||
std::vector<uint8_t> garbage(2000);
|
||||
|
||||
@@ -2,6 +2,7 @@ wifi:
|
||||
dpp:
|
||||
channels: [1, 6, 11]
|
||||
device_info: ESPHome
|
||||
private_key: 00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff
|
||||
timeout: 30s
|
||||
on_dpp_uri:
|
||||
- logger.log:
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
esphome:
|
||||
name: uart-mock-dfrobot-sen0395
|
||||
|
||||
host:
|
||||
api:
|
||||
batch_delay: 0ms # Disable batching to receive all state updates
|
||||
logger:
|
||||
level: VERBOSE
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: EXTERNAL_COMPONENT_PATH
|
||||
|
||||
# Dummy uart entry to satisfy DEPENDENCIES = ["uart"]
|
||||
uart:
|
||||
baud_rate: 115200
|
||||
port: /dev/null
|
||||
|
||||
uart_mock:
|
||||
id: mock_uart
|
||||
baud_rate: 115200
|
||||
# A small model of the module's command line: echo, answer and the prompt (no line end)
|
||||
on_tx:
|
||||
- then:
|
||||
- lambda: |-
|
||||
std::string cmd(data.begin(), data.end());
|
||||
std::string reply = cmd + "\r\n";
|
||||
if (cmd == "resetSystem") {
|
||||
// The restart only answers with the prompt
|
||||
} else {
|
||||
reply += "Done\r\n";
|
||||
}
|
||||
reply += "leapMMW:/>";
|
||||
id(mock_uart).inject_to_rx_buffer(std::vector<uint8_t>(reply.begin(), reply.end()));
|
||||
|
||||
dfrobot_sen0395:
|
||||
id: sen0395
|
||||
uart_id: mock_uart
|
||||
|
||||
binary_sensor:
|
||||
- platform: dfrobot_sen0395
|
||||
name: Detected
|
||||
|
||||
switch:
|
||||
- platform: dfrobot_sen0395
|
||||
type: sensor_active
|
||||
name: Sensor Active
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: Inject Present
|
||||
on_press:
|
||||
- lambda: |-
|
||||
// '$' is kept apart so it is not read as a substitution
|
||||
std::string frame = std::string(1, '$') + "JYBSS,1, , , *\r\n";
|
||||
id(mock_uart).inject_to_rx_buffer(std::vector<uint8_t>(frame.begin(), frame.end()));
|
||||
- platform: template
|
||||
name: Inject Overlong Then Absent
|
||||
on_press:
|
||||
- lambda: |-
|
||||
// An overlong line is dropped up to its line end; the next frame still parses
|
||||
std::string data(300, 'x');
|
||||
data += "\r\n" + std::string(1, '$') + "JYBSS,0, , , *\r\n";
|
||||
id(mock_uart).inject_to_rx_buffer(std::vector<uint8_t>(data.begin(), data.end()));
|
||||
- platform: template
|
||||
name: Reset Sensor
|
||||
on_press:
|
||||
- dfrobot_sen0395.reset:
|
||||
id: sen0395
|
||||
@@ -0,0 +1,93 @@
|
||||
"""Integration test for the DFRobot SEN0395 component against a model of the module's command line.
|
||||
|
||||
Checks presence frames, recovery after an overlong line, a command answered by "Done" and the
|
||||
reset command, which the module answers only with its "leapMMW:/>" prompt (no line end).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
from aioesphomeapi import (
|
||||
BinarySensorInfo,
|
||||
ButtonInfo,
|
||||
EntityInfo,
|
||||
EntityState,
|
||||
SwitchInfo,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from .state_utils import require_entity
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_uart_mock_dfrobot_sen0395(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Frames parse, an overlong line is dropped and the prompt completes the reset."""
|
||||
external_components_path = str(
|
||||
Path(__file__).parent / "fixtures" / "external_components"
|
||||
)
|
||||
yaml_config = yaml_config.replace(
|
||||
"EXTERNAL_COMPONENT_PATH", external_components_path
|
||||
)
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
dropped = loop.create_future()
|
||||
restarted = loop.create_future()
|
||||
|
||||
def line_callback(line: str) -> None:
|
||||
if "Line over 252 chars dropped" in line and not dropped.done():
|
||||
dropped.set_result(True)
|
||||
if "Restarted sensor." in line and not restarted.done():
|
||||
restarted.set_result(True)
|
||||
|
||||
states: dict[int, EntityState] = {}
|
||||
|
||||
async def wait_for(condition: Callable[[], bool], what: str) -> None:
|
||||
try:
|
||||
async with asyncio.timeout(10.0):
|
||||
while not condition():
|
||||
await asyncio.sleep(0.05)
|
||||
except TimeoutError:
|
||||
pytest.fail(f"Timeout waiting for {what}; states: {states}")
|
||||
|
||||
async with (
|
||||
run_compiled(yaml_config, line_callback=line_callback),
|
||||
api_client_connected() as client,
|
||||
):
|
||||
entities, _ = await client.list_entities_services()
|
||||
detected = require_entity(entities, "detected", BinarySensorInfo)
|
||||
active = require_entity(entities, "sensor_active", SwitchInfo)
|
||||
present = require_entity(entities, "inject_present", ButtonInfo)
|
||||
overlong = require_entity(entities, "inject_overlong_then_absent", ButtonInfo)
|
||||
reset = require_entity(entities, "reset_sensor", ButtonInfo)
|
||||
|
||||
client.subscribe_states(lambda state: states.__setitem__(state.key, state))
|
||||
|
||||
def state_of(entity: EntityInfo) -> object | None:
|
||||
state = states.get(entity.key)
|
||||
return None if state is None or state.missing_state else state.state
|
||||
|
||||
client.button_command(present.key)
|
||||
await wait_for(lambda: state_of(detected) is True, "presence")
|
||||
|
||||
client.button_command(overlong.key)
|
||||
await wait_for(
|
||||
lambda: state_of(detected) is False, "absence after overlong line"
|
||||
)
|
||||
await asyncio.wait_for(dropped, timeout=5.0)
|
||||
|
||||
client.switch_command(active.key, False)
|
||||
await wait_for(lambda: state_of(active) is False, "sensor stopped")
|
||||
|
||||
client.button_command(reset.key)
|
||||
try:
|
||||
await asyncio.wait_for(restarted, timeout=10.0)
|
||||
except TimeoutError:
|
||||
pytest.fail("The reset was not completed by the prompt")
|
||||
@@ -1,494 +0,0 @@
|
||||
"""Tests for ``esphome rotate-key``."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import ANY, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import yaml_util
|
||||
from esphome.cli.rotate_key import PRECHECK_ADVICE, command_rotate_key
|
||||
from esphome.const import (
|
||||
CONF_API,
|
||||
CONF_ENCRYPTION,
|
||||
CONF_ESPHOME,
|
||||
CONF_KEY,
|
||||
CONF_OTA,
|
||||
CONF_PLATFORM,
|
||||
CONF_PORT,
|
||||
CONF_WEB_SERVER,
|
||||
KEY_CORE,
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_ESP32,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError, Toolchain
|
||||
from esphome.espota2 import ProbeResult
|
||||
from esphome.ota_key_edit import KeyEdit
|
||||
from esphome.upload_targets import PortType
|
||||
from esphome.util import ESPHOME_COMMAND
|
||||
from esphome.yaml_edit import RestoreError
|
||||
|
||||
|
||||
@dataclass
|
||||
class MockArgs:
|
||||
device: list[str] | None = None
|
||||
prompt_new_key: bool = False
|
||||
yes: bool = False
|
||||
substitution: list[list[str]] | None = None
|
||||
toolchain: Toolchain | None = None
|
||||
|
||||
|
||||
def _interrupt_after(writes: int) -> Any:
|
||||
"""A write_keeping_mode that lets ``writes`` calls through, then Ctrl-C."""
|
||||
from esphome.yaml_edit import write_keeping_mode
|
||||
|
||||
calls = 0
|
||||
|
||||
def write(*args: Any, **kwargs: Any) -> None:
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
if calls > writes:
|
||||
raise KeyboardInterrupt
|
||||
write_keeping_mode(*args, **kwargs)
|
||||
|
||||
return write
|
||||
|
||||
|
||||
def setup_core(tmp_path: Path) -> None:
|
||||
CORE.reset()
|
||||
CORE.config_path = tmp_path / "test.yaml"
|
||||
CORE.name = "test"
|
||||
CORE.build_path = tmp_path / ".esphome" / "build" / "test"
|
||||
CORE.data[KEY_CORE] = {KEY_TARGET_PLATFORM: PLATFORM_ESP32}
|
||||
|
||||
|
||||
OLD_KEY = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="
|
||||
NEW_KEY = "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA="
|
||||
API_YAML = f"""esphome:
|
||||
name: test
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: "{OLD_KEY}"
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
encryption:
|
||||
"""
|
||||
OTA_CONF = {
|
||||
CONF_PLATFORM: CONF_ESPHOME,
|
||||
CONF_PORT: 3232,
|
||||
CONF_ENCRYPTION: {CONF_KEY: OLD_KEY},
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def env(tmp_path: Path) -> Generator[dict[str, Mock]]:
|
||||
"""A config with an inline api key, loaded the way read_config does so
|
||||
the key node carries its source range, and every network step mocked."""
|
||||
setup_core(tmp_path)
|
||||
CORE.config_path.write_text(API_YAML, encoding="utf-8")
|
||||
CORE.raw_config = yaml_util.load_yaml(CORE.config_path)
|
||||
CORE.config = {
|
||||
CONF_API: {CONF_ENCRYPTION: {CONF_KEY: OLD_KEY}},
|
||||
CONF_OTA: [OTA_CONF],
|
||||
}
|
||||
with (
|
||||
patch("esphome.__main__.choose_upload_log_host", return_value=["dev.local"]),
|
||||
patch("esphome.__main__._resolve_network_devices", return_value=["dev.local"]),
|
||||
patch("esphome.cli.rotate_key.get_port_type", return_value=PortType.NETWORK),
|
||||
patch(
|
||||
"esphome.espota2.probe_ota_key", return_value=ProbeResult.ACCEPTED
|
||||
) as probe,
|
||||
patch(
|
||||
"esphome.cli.rotate_key.run_external_process", return_value=0
|
||||
) as compile_,
|
||||
patch("esphome.espota2.run_ota") as upload,
|
||||
patch("esphome.cli.rotate_key.safe_input", return_value="y") as confirm,
|
||||
patch("esphome.cli.rotate_key.sys.stdin") as stdin,
|
||||
patch(
|
||||
"esphome.components.noise.secrets.token_bytes",
|
||||
return_value=bytes(range(1, 33)),
|
||||
),
|
||||
):
|
||||
stdin.isatty.return_value = True
|
||||
# The real upload reports the connection before its result
|
||||
upload.return_value = (0, "dev.local")
|
||||
|
||||
def upload_after_connecting(
|
||||
*_args: Any, **kwargs: Any
|
||||
) -> tuple[int, str | None]:
|
||||
kwargs["on_upload_start"]()
|
||||
return upload.return_value
|
||||
|
||||
upload.side_effect = upload_after_connecting
|
||||
yield {
|
||||
"probe": probe,
|
||||
"compile": compile_,
|
||||
"upload": upload,
|
||||
"confirm": confirm,
|
||||
"stdin": stdin,
|
||||
}
|
||||
|
||||
|
||||
def test_success(env: dict[str, Mock], capfd: pytest.CaptureFixture[str]) -> None:
|
||||
"""Generate, write, compile in a child, upload with the old key, confirm
|
||||
with the new one, print it."""
|
||||
args = MockArgs()
|
||||
assert command_rotate_key(args, CORE.config) == 0
|
||||
|
||||
assert (
|
||||
CORE.config_path.read_text()
|
||||
== API_YAML.replace(OLD_KEY, NEW_KEY) + f' old_key: "{OLD_KEY}"\n'
|
||||
)
|
||||
env["confirm"].assert_called_once()
|
||||
precheck, confirm = env["probe"].call_args_list
|
||||
assert precheck.args[2] == OLD_KEY
|
||||
assert precheck.kwargs["retry_rejected"] is False
|
||||
assert confirm.args[2] == NEW_KEY
|
||||
compile_args = env["compile"].call_args.args
|
||||
assert compile_args[-2:] == ("compile", str(CORE.config_path))
|
||||
env["upload"].assert_called_once_with(
|
||||
["dev.local"],
|
||||
3232,
|
||||
None,
|
||||
CORE.firmware_bin,
|
||||
noise_psk=OLD_KEY,
|
||||
plaintext_fallback=False,
|
||||
on_upload_start=ANY,
|
||||
)
|
||||
out = capfd.readouterr().out
|
||||
assert NEW_KEY in out
|
||||
assert "SUCCESS" in out
|
||||
|
||||
|
||||
def test_yes_skips_confirmation(
|
||||
env: dict[str, Mock],
|
||||
) -> None:
|
||||
assert command_rotate_key(MockArgs(yes=True), CORE.config) == 0
|
||||
env["confirm"].assert_not_called()
|
||||
|
||||
|
||||
def test_declined(env: dict[str, Mock]) -> None:
|
||||
env["confirm"].return_value = "n"
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
assert CORE.config_path.read_text() == API_YAML
|
||||
env["probe"].assert_not_called()
|
||||
|
||||
|
||||
def test_warns_about_a_shared_secret(
|
||||
env: dict[str, Mock], capfd: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
"""Other configurations using the rewritten secret are named before the
|
||||
confirmation."""
|
||||
|
||||
def scan(edits: list[KeyEdit]) -> list[str]:
|
||||
edits[0].secret = "device_key"
|
||||
edits[0].shared_with = [CORE.config_dir / "b.yaml"]
|
||||
return ["c.yaml is a link"]
|
||||
|
||||
with patch("esphome.cli.rotate_key.with_sharers", side_effect=scan):
|
||||
env["confirm"].return_value = "n"
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
out = capfd.readouterr().out
|
||||
assert "b.yaml (secret 'device_key')" in out
|
||||
assert "Could not check every configuration for shared use: c.yaml is a link" in out
|
||||
|
||||
|
||||
def test_no_terminal_needs_yes(
|
||||
env: dict[str, Mock],
|
||||
) -> None:
|
||||
env["stdin"].isatty.return_value = False
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
assert CORE.config_path.read_text() == API_YAML
|
||||
|
||||
|
||||
def test_own_ota_key_asks_nothing(
|
||||
env: dict[str, Mock],
|
||||
) -> None:
|
||||
"""Without an api key nothing changes for Home Assistant."""
|
||||
config = {CONF_OTA: [OTA_CONF]}
|
||||
assert command_rotate_key(MockArgs(), config) == 0
|
||||
env["confirm"].assert_not_called()
|
||||
assert NEW_KEY in CORE.config_path.read_text()
|
||||
|
||||
|
||||
def test_prompted_new_key(env: dict[str, Mock]) -> None:
|
||||
key = "AgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICE="
|
||||
with patch("esphome.cli.rotate_key.read_secret_line", return_value=key):
|
||||
assert command_rotate_key(MockArgs(prompt_new_key=True), CORE.config) == 0
|
||||
assert key in CORE.config_path.read_text()
|
||||
assert env["probe"].call_args_list[1].args[2] == key
|
||||
|
||||
|
||||
@pytest.mark.parametrize("key", ["not-base64", OLD_KEY], ids=["invalid", "same"])
|
||||
def test_rejects_bad_new_key(env: dict[str, Mock], key: str) -> None:
|
||||
with patch("esphome.cli.rotate_key.read_secret_line", return_value=key):
|
||||
assert command_rotate_key(MockArgs(prompt_new_key=True), CORE.config) == 1
|
||||
assert CORE.config_path.read_text() == API_YAML
|
||||
env["probe"].assert_not_called()
|
||||
|
||||
|
||||
def test_precheck_fails_writes_nothing(
|
||||
env: dict[str, Mock], capfd: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
env["probe"].return_value = ProbeResult.REJECTED
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
assert CORE.config_path.read_text() == API_YAML
|
||||
env["compile"].assert_not_called()
|
||||
assert "runs a different key than the yaml" in capfd.readouterr().out
|
||||
for found in (
|
||||
ProbeResult.UNREACHABLE,
|
||||
ProbeResult.NOT_OFFERED,
|
||||
ProbeResult.DEVICE_ERROR,
|
||||
):
|
||||
env["probe"].return_value = found
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
assert PRECHECK_ADVICE[found] in capfd.readouterr().out
|
||||
|
||||
|
||||
@pytest.mark.parametrize("step", ["compile", "interrupt"])
|
||||
def test_restores_before_the_upload(
|
||||
env: dict[str, Mock], capfd: pytest.CaptureFixture[str], step: str
|
||||
) -> None:
|
||||
if step == "compile":
|
||||
env["compile"].return_value = 1
|
||||
else:
|
||||
env["compile"].side_effect = KeyboardInterrupt
|
||||
image = CORE.firmware_bin
|
||||
image.parent.mkdir(parents=True)
|
||||
factory = image.with_name("firmware.factory.bin")
|
||||
for built in (image, factory):
|
||||
built.write_bytes(b"built with the new key")
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
assert CORE.config_path.read_text() == API_YAML
|
||||
assert env["probe"].call_count == 1
|
||||
out = capfd.readouterr().out
|
||||
assert ("Interrupted before the upload" in out) is (step == "interrupt")
|
||||
# The images may hold the new key, so no upload can install them
|
||||
assert not image.exists() and not factory.exists()
|
||||
assert "compile again before the next upload" in out
|
||||
assert NEW_KEY not in out
|
||||
|
||||
|
||||
def test_a_build_that_cannot_be_removed_shows_the_new_key(
|
||||
env: dict[str, Mock], capfd: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
"""Every image is tried; the ones that stay are all named."""
|
||||
env["compile"].return_value = 1
|
||||
image = CORE.firmware_bin
|
||||
image.mkdir(parents=True) # unlink fails on a directory
|
||||
stuck = image.with_name("firmware.factory.bin")
|
||||
stuck.mkdir()
|
||||
removable = image.with_name("firmware.ota.bin")
|
||||
removable.write_bytes(b"")
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
out = capfd.readouterr().out
|
||||
assert str(image) in out and str(stuck) in out
|
||||
assert not removable.exists()
|
||||
assert OLD_KEY in out and NEW_KEY in out
|
||||
|
||||
|
||||
def test_an_interrupt_during_the_image_cleanup_shows_the_keys(
|
||||
env: dict[str, Mock], capfd: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
env["compile"].return_value = 1
|
||||
CORE.firmware_bin.parent.mkdir(parents=True)
|
||||
CORE.firmware_bin.write_bytes(b"")
|
||||
with patch("pathlib.Path.glob", side_effect=KeyboardInterrupt):
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
out = capfd.readouterr().out
|
||||
assert f"{CORE.firmware_bin.parent}: interrupted" in out
|
||||
assert OLD_KEY in out and NEW_KEY in out
|
||||
|
||||
|
||||
def test_a_build_from_before_the_rotation_is_kept(
|
||||
env: dict[str, Mock], capfd: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
env["probe"].return_value = ProbeResult.UNREACHABLE
|
||||
image = CORE.firmware_bin
|
||||
image.parent.mkdir(parents=True)
|
||||
image.write_bytes(b"built with the current key")
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
assert image.exists()
|
||||
assert "compile again" not in capfd.readouterr().out
|
||||
|
||||
|
||||
@pytest.mark.parametrize("rollback", ["succeeded", "failed"])
|
||||
def test_an_interrupt_while_writing_reports_a_failed_rollback(
|
||||
env: dict[str, Mock], capfd: pytest.CaptureFixture[str], rollback: str
|
||||
) -> None:
|
||||
"""apply_line_edits keeps an interrupt an interrupt and notes a rollback
|
||||
that failed too; then a file still holds the new key, so both are shown."""
|
||||
interrupt = KeyboardInterrupt()
|
||||
if rollback == "failed":
|
||||
interrupt.add_note("Could not restore test.yaml: disk")
|
||||
with patch("esphome.cli.rotate_key.apply_line_edits", side_effect=interrupt):
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
out = capfd.readouterr().out
|
||||
assert ("Could not restore test.yaml: disk" in out) is (rollback == "failed")
|
||||
assert (NEW_KEY in out) is (rollback == "failed")
|
||||
assert ("Interrupted before the upload" in out) is (rollback == "succeeded")
|
||||
|
||||
|
||||
def test_a_second_interrupt_during_the_restore_shows_the_keys(
|
||||
env: dict[str, Mock], capfd: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
env["compile"].return_value = 1
|
||||
with patch("esphome.yaml_edit.write_keeping_mode", side_effect=_interrupt_after(1)):
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
out = capfd.readouterr().out
|
||||
assert f"Could not restore {CORE.config_path}: interrupted" in out
|
||||
assert OLD_KEY in out and NEW_KEY in out
|
||||
|
||||
|
||||
def test_restores_when_the_device_was_never_reached(
|
||||
env: dict[str, Mock], capfd: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
"""An upload that fails before the image is sent cannot have committed."""
|
||||
env["upload"].side_effect = None
|
||||
env["upload"].return_value = (1, None)
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
assert CORE.config_path.read_text() == API_YAML
|
||||
out = capfd.readouterr().out
|
||||
assert "failed before the image was sent" in out
|
||||
assert "put back" not in out
|
||||
assert "Restored the previous key" in out
|
||||
|
||||
|
||||
def test_keeps_the_edit_after_a_failed_upload(
|
||||
env: dict[str, Mock], capfd: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
"""An attempted upload may have committed, so the config keeps both keys
|
||||
and the new one is printed."""
|
||||
env["upload"].return_value = (1, None)
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
text = CORE.config_path.read_text()
|
||||
assert NEW_KEY in text and f'old_key: "{OLD_KEY}"' in text
|
||||
assert NEW_KEY in capfd.readouterr().out
|
||||
assert env["probe"].call_count == 1
|
||||
|
||||
|
||||
def test_shows_both_keys_only_when_a_file_stayed_rewritten(
|
||||
env: dict[str, Mock], capfd: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
with patch(
|
||||
"esphome.cli.rotate_key.apply_line_edits", side_effect=RestoreError("stuck")
|
||||
):
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
out = capfd.readouterr().out
|
||||
assert "stuck" in out and OLD_KEY in out and NEW_KEY in out
|
||||
|
||||
|
||||
def test_reports_a_failed_restore(
|
||||
env: dict[str, Mock], capfd: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
env["compile"].return_value = 1
|
||||
with patch(
|
||||
"esphome.cli.rotate_key.restore_files",
|
||||
side_effect=RestoreError("Could not restore x"),
|
||||
):
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
out = capfd.readouterr().out
|
||||
assert "Could not restore x" in out
|
||||
assert OLD_KEY in out and NEW_KEY in out
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"outcome", ["unreachable", "rejected", "not_offered", "device_error", "interrupted"]
|
||||
)
|
||||
def test_keeps_the_new_key_after_the_upload(
|
||||
env: dict[str, Mock], capfd: pytest.CaptureFixture[str], outcome: str
|
||||
) -> None:
|
||||
"""Once the upload went through the device most likely runs the new key
|
||||
and old_key covers the other case, so the files stay and the key is
|
||||
printed."""
|
||||
env["probe"].side_effect = [
|
||||
ProbeResult.ACCEPTED,
|
||||
{
|
||||
"interrupted": KeyboardInterrupt,
|
||||
"unreachable": ProbeResult.UNREACHABLE,
|
||||
"rejected": ProbeResult.REJECTED,
|
||||
"not_offered": ProbeResult.NOT_OFFERED,
|
||||
"device_error": ProbeResult.DEVICE_ERROR,
|
||||
}[outcome],
|
||||
]
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
assert NEW_KEY in CORE.config_path.read_text()
|
||||
assert f'old_key: "{OLD_KEY}"' in CORE.config_path.read_text()
|
||||
out = capfd.readouterr().out
|
||||
assert NEW_KEY in out
|
||||
assert ("Interrupted" in out) is (outcome == "interrupted")
|
||||
assert ("still answers with the previous key" in out) is (outcome == "rejected")
|
||||
assert ("did not come back" in out) is (outcome == "unreachable")
|
||||
assert ("came back without encryption" in out) is (outcome == "not_offered")
|
||||
assert ("answered with an error" in out) is (outcome == "device_error")
|
||||
|
||||
|
||||
def test_child_compile_gets_global_options_first(
|
||||
env: dict[str, Mock],
|
||||
) -> None:
|
||||
"""The compile parser is strict, so -s and --toolchain precede it."""
|
||||
args = MockArgs(substitution=[["name", "kitchen"]])
|
||||
CORE.dashboard = True
|
||||
args.toolchain = Toolchain.PLATFORMIO
|
||||
assert command_rotate_key(args, CORE.config) == 0
|
||||
assert env["compile"].call_args.args[len(ESPHOME_COMMAND) :] == (
|
||||
"--dashboard",
|
||||
"--toolchain",
|
||||
"platformio",
|
||||
"-s",
|
||||
"name",
|
||||
"kitchen",
|
||||
"compile",
|
||||
str(CORE.config_path),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("config", "port_type", "match"),
|
||||
[
|
||||
(
|
||||
{CONF_OTA: [{CONF_PLATFORM: CONF_ESPHOME, CONF_PORT: 3232}]},
|
||||
"NETWORK",
|
||||
"needs 'encryption:'",
|
||||
),
|
||||
({CONF_OTA: [{CONF_PLATFORM: CONF_WEB_SERVER}]}, "NETWORK", "esphome OTA"),
|
||||
({CONF_OTA: [OTA_CONF]}, PortType.SERIAL, "over the air"),
|
||||
],
|
||||
ids=["no_encryption", "web_server_only", "serial"],
|
||||
)
|
||||
def test_refuses(
|
||||
env: dict[str, Mock],
|
||||
capfd: pytest.CaptureFixture[str],
|
||||
config: dict[str, Any],
|
||||
port_type: str,
|
||||
match: str,
|
||||
) -> None:
|
||||
with patch("esphome.cli.rotate_key.get_port_type", return_value=port_type):
|
||||
assert command_rotate_key(MockArgs(), config) == 1
|
||||
assert match in capfd.readouterr().out
|
||||
env["probe"].assert_not_called()
|
||||
|
||||
|
||||
def test_refuses_host(env: dict[str, Mock]) -> None:
|
||||
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "host"
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
env["probe"].assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("helper", ["locate_key_edits", "apply_line_edits"])
|
||||
def test_reports_edit_errors(
|
||||
env: dict[str, Mock], capfd: pytest.CaptureFixture[str], helper: str
|
||||
) -> None:
|
||||
with patch(f"esphome.cli.rotate_key.{helper}", side_effect=EsphomeError("nope")):
|
||||
assert command_rotate_key(MockArgs(), CORE.config) == 1
|
||||
out = capfd.readouterr().out
|
||||
assert "nope" in out
|
||||
# A refusal, or an edit rolled back cleanly, shows no key
|
||||
assert NEW_KEY not in out
|
||||
assert CORE.config_path.read_text() == API_YAML
|
||||
@@ -0,0 +1,125 @@
|
||||
"""Tests for trimming a LibreTiny UF2 to the images a device writes over OTA."""
|
||||
|
||||
from pathlib import Path
|
||||
import struct
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components.libretiny import device_ota_image
|
||||
from esphome.components.libretiny.ota_uf2 import UF2_TAG_OTA_PART_INFO, device_ota_uf2
|
||||
from esphome.core import CORE
|
||||
|
||||
# Written from the UF2 and LibreTiny formats, not from the module under test
|
||||
MAGIC_1, MAGIC_2, MAGIC_3 = 0x0A324655, 0x9E5D5157, 0x0AB16F30
|
||||
FLAG_FAMILY, FLAG_TAGS = 0x2000, 0x8000
|
||||
FAMILY = 0x7B3EF230
|
||||
TAG_OTA_FORMAT_2 = 0x6C8492
|
||||
TAG_BINPATCH = 0xB948DE
|
||||
# OTA_PART_INFO index nibbles: device single, dual 1, dual 2, flasher single, dual 1, dual 2
|
||||
DEVICE_SINGLE = b"\x10\x00\x00"
|
||||
FLASHER_SINGLE = b"\x00\x01\x00"
|
||||
DEVICE_DUAL_1 = b"\x01\x00\x00"
|
||||
DEVICE_DUAL_2 = b"\x00\x10\x00"
|
||||
|
||||
|
||||
def _tag(tag: int, value: bytes) -> bytes:
|
||||
size = 4 + len(value)
|
||||
return bytes([size]) + tag.to_bytes(3, "little") + value + bytes(-size % 4)
|
||||
|
||||
|
||||
def _block(payload: bytes = b"", tags: dict[int, bytes] | None = None) -> bytes:
|
||||
"""A block numbered 0 of 0; _package numbers it."""
|
||||
flags = FLAG_FAMILY | (FLAG_TAGS if tags else 0)
|
||||
header = struct.pack("<8I", MAGIC_1, MAGIC_2, flags, 0, len(payload), 0, 0, FAMILY)
|
||||
body = payload + b"".join(_tag(t, v) for t, v in (tags or {}).items())
|
||||
return header + body + bytes(512 - 32 - 4 - len(body)) + struct.pack("<I", MAGIC_3)
|
||||
|
||||
|
||||
def _image(part_info: bytes, blocks: int, fill: int) -> list[bytes]:
|
||||
"""An image: its first block names the partitions, the rest carry data."""
|
||||
data = bytes([fill]) * 256
|
||||
first = _block(data, {UF2_TAG_OTA_PART_INFO: part_info + b"part\x00"})
|
||||
return [first] + [_block(data) for _ in range(blocks - 1)]
|
||||
|
||||
|
||||
def _package(*blocks: bytes) -> bytes:
|
||||
"""The blocks numbered in order, as a UF2 file."""
|
||||
return b"".join(
|
||||
block[:20] + struct.pack("<2I", seq, len(blocks)) + block[28:]
|
||||
for seq, block in enumerate(blocks)
|
||||
)
|
||||
|
||||
|
||||
HEADER = _block(tags={TAG_OTA_FORMAT_2: b"\x02"})
|
||||
FLASHER = _image(FLASHER_SINGLE, 5, 0xAA)
|
||||
DEVICE = _image(DEVICE_SINGLE, 3, 0xBB)
|
||||
SLOT_1 = _image(DEVICE_DUAL_1, 2, 0x11)
|
||||
SLOT_2 = _image(DEVICE_DUAL_2, 2, 0x22)
|
||||
PATCHED = _block(bytes([0xAA]) * 256, {TAG_BINPATCH: b"\x01\x02\x03\x04"})
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("blocks", "expected"),
|
||||
[
|
||||
pytest.param([HEADER, *FLASHER, *DEVICE], [HEADER, *DEVICE], id="single_ota"),
|
||||
pytest.param(
|
||||
[HEADER, *SLOT_1, *FLASHER, *SLOT_2],
|
||||
[HEADER, *SLOT_1, *SLOT_2],
|
||||
id="dual_ota_keeps_both_slots",
|
||||
),
|
||||
pytest.param(
|
||||
[HEADER, *FLASHER, PATCHED, *DEVICE],
|
||||
None,
|
||||
id="dropped_image_carries_a_tag_the_device_acts_on",
|
||||
),
|
||||
pytest.param([HEADER, *DEVICE], None, id="nothing_to_drop"),
|
||||
],
|
||||
)
|
||||
def test_device_ota_uf2(blocks: list[bytes], expected: list[bytes] | None) -> None:
|
||||
trimmed = device_ota_uf2(_package(*blocks))
|
||||
assert trimmed == (_package(*expected) if expected is not None else None)
|
||||
|
||||
|
||||
def test_data_that_is_not_a_well_formed_uf2_is_left_alone() -> None:
|
||||
package = _package(HEADER, *FLASHER, *DEVICE)
|
||||
bad_magic = package[:512] + b"\x00" + package[513:]
|
||||
# A tag that runs past the data area of the first flasher block
|
||||
at = 512 + 32 + 256
|
||||
bad_tag = package[:at] + bytes([250]) + package[at + 1 :]
|
||||
|
||||
# A whole block missing, or repeated, keeps the original numbering
|
||||
missing = package[:1024] + package[1536:]
|
||||
repeated = package[:1536] + package[1024:]
|
||||
# OTA_PART_INFO too short to hold the scheme map, which uf2ota rejects
|
||||
short_selectors = [
|
||||
_package(HEADER, _block(bytes(256), {UF2_TAG_OTA_PART_INFO: bytes(n)}), *DEVICE)
|
||||
for n in range(3)
|
||||
]
|
||||
|
||||
for data in (
|
||||
b"",
|
||||
bytes(range(256)) * 4,
|
||||
package[:-1],
|
||||
bad_magic,
|
||||
bad_tag,
|
||||
missing,
|
||||
repeated,
|
||||
*short_selectors,
|
||||
):
|
||||
assert device_ota_uf2(data) is None
|
||||
|
||||
|
||||
def test_device_ota_image(setup_core: Path) -> None:
|
||||
tmp_path = setup_core
|
||||
CORE.build_path = tmp_path / "build"
|
||||
CORE.name = "test"
|
||||
firmware = tmp_path / "firmware.uf2"
|
||||
|
||||
firmware.write_bytes(_package(HEADER, *FLASHER, *DEVICE))
|
||||
image = device_ota_image(firmware)
|
||||
assert image == CORE.relative_pioenvs_path("test", "firmware.ota.uf2")
|
||||
assert image.read_bytes() == _package(HEADER, *DEVICE)
|
||||
|
||||
# Nothing to drop: the firmware itself is sent
|
||||
firmware.write_bytes(_package(HEADER, *DEVICE))
|
||||
assert device_ota_image(firmware) == firmware
|
||||
@@ -222,15 +222,9 @@ def test_api_client_does_not_import_heavy_modules() -> None:
|
||||
|
||||
# Packages a domain needs for its own schema; cheap and never an integration
|
||||
_ENTITY_COMPONENT_DEPENDENCIES = {
|
||||
"infrared": ("esphome.components.ir_rf_base",),
|
||||
"light": ("esphome.components.power_supply", "esphome.components.const.css_colors"),
|
||||
}
|
||||
|
||||
|
||||
# Entity domains that still import integrations at top level
|
||||
_ENTITY_COMPONENT_KNOWN_LEAKS = {
|
||||
"infrared": "imports remote_base and api at top level",
|
||||
"media_player": "audio pulls in the esp32 chain",
|
||||
"radio_frequency": "imports remote_base and api at top level",
|
||||
"radio_frequency": ("esphome.components.ir_rf_base",),
|
||||
}
|
||||
|
||||
|
||||
@@ -248,26 +242,11 @@ def _entity_components() -> list[str]:
|
||||
def test_entity_component_tables_match_discovery() -> None:
|
||||
"""Catch stale table entries and a discovery that silently finds nothing."""
|
||||
found = set(_entity_components())
|
||||
stale = (
|
||||
_ENTITY_COMPONENT_KNOWN_LEAKS.keys() | _ENTITY_COMPONENT_DEPENDENCIES.keys()
|
||||
) - found
|
||||
stale = _ENTITY_COMPONENT_DEPENDENCIES.keys() - found
|
||||
assert not stale, f"not discovered as entity components: {sorted(stale)}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"component",
|
||||
[
|
||||
pytest.param(
|
||||
c,
|
||||
marks=pytest.mark.xfail(
|
||||
reason=_ENTITY_COMPONENT_KNOWN_LEAKS[c], strict=True
|
||||
),
|
||||
)
|
||||
if c in _ENTITY_COMPONENT_KNOWN_LEAKS
|
||||
else c
|
||||
for c in _entity_components()
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("component", _entity_components())
|
||||
def test_entity_component_does_not_import_integrations(component: str) -> None:
|
||||
"""An entity component must not drag in its optional integrations.
|
||||
|
||||
|
||||
+112
-29
@@ -2135,6 +2135,118 @@ def test_upload_program_ota_success(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("config", "uploader", "image_arg"),
|
||||
[
|
||||
pytest.param(
|
||||
{CONF_OTA: [{CONF_PLATFORM: CONF_ESPHOME, CONF_PORT: 8892}]},
|
||||
"mock_run_ota",
|
||||
3,
|
||||
id="native",
|
||||
),
|
||||
pytest.param(
|
||||
{
|
||||
CONF_OTA: [{CONF_PLATFORM: CONF_WEB_SERVER}],
|
||||
CONF_WEB_SERVER: {CONF_PORT: 80},
|
||||
},
|
||||
"mock_run_web_server_ota",
|
||||
4,
|
||||
id="web_server",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_upload_program_ota_sends_the_platform_ota_image(
|
||||
config: ConfigType,
|
||||
uploader: str,
|
||||
image_arg: int,
|
||||
mock_get_port_type: Mock,
|
||||
tmp_path: Path,
|
||||
request: pytest.FixtureRequest,
|
||||
) -> None:
|
||||
"""An app update sends the file the platform's ota_image hook picks."""
|
||||
setup_core(platform=PLATFORM_BK72XX, tmp_path=tmp_path)
|
||||
mock_get_port_type.return_value = "NETWORK"
|
||||
mock_upload = request.getfixturevalue(uploader)
|
||||
mock_upload.return_value = (0, "192.168.1.100")
|
||||
image = tmp_path / "firmware.ota.uf2"
|
||||
|
||||
with patch(
|
||||
"esphome.components.bk72xx.ota_image", return_value=image
|
||||
) as mock_ota_image:
|
||||
exit_code, _ = upload_program(config, MockArgs(), ["192.168.1.100"])
|
||||
|
||||
assert exit_code == 0
|
||||
mock_ota_image.assert_called_once_with(CORE.firmware_bin)
|
||||
assert mock_upload.call_args.args[image_arg] == image
|
||||
|
||||
|
||||
def test_upload_program_ota_file_goes_through_the_ota_image_hook(
|
||||
mock_run_ota: Mock,
|
||||
mock_get_port_type: Mock,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""--file is the firmware the platform's ota_image hook starts from."""
|
||||
setup_core(platform=PLATFORM_BK72XX, tmp_path=tmp_path)
|
||||
mock_get_port_type.return_value = "NETWORK"
|
||||
mock_run_ota.return_value = (0, "192.168.1.100")
|
||||
other = tmp_path / "other.uf2"
|
||||
image = tmp_path / "firmware.ota.uf2"
|
||||
|
||||
config = {CONF_OTA: [{CONF_PLATFORM: CONF_ESPHOME, CONF_PORT: 8892}]}
|
||||
with patch(
|
||||
"esphome.components.bk72xx.ota_image", return_value=image
|
||||
) as mock_ota_image:
|
||||
upload_program(config, MockArgs(file=str(other)), ["192.168.1.100"])
|
||||
|
||||
mock_ota_image.assert_called_once_with(other)
|
||||
assert mock_run_ota.call_args.args[3] == image
|
||||
|
||||
|
||||
def test_upload_program_ota_partition_table_skips_the_ota_image_hook(
|
||||
mock_run_ota: Mock,
|
||||
mock_get_port_type: Mock,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Only app updates go through the ota_image hook."""
|
||||
setup_core(platform=PLATFORM_BK72XX, tmp_path=tmp_path)
|
||||
mock_get_port_type.return_value = "NETWORK"
|
||||
mock_run_ota.return_value = (0, "192.168.1.100")
|
||||
partition_file = tmp_path / "partitions.bin"
|
||||
partition_file.write_bytes(_make_partition_table_bytes())
|
||||
|
||||
config = {
|
||||
CONF_OTA: [
|
||||
{
|
||||
CONF_PLATFORM: CONF_ESPHOME,
|
||||
CONF_PORT: 8892,
|
||||
"allow_partition_access": True,
|
||||
}
|
||||
]
|
||||
}
|
||||
args = MockArgs(file=str(partition_file), partition_table=True)
|
||||
with patch("esphome.components.bk72xx.ota_image") as mock_ota_image:
|
||||
upload_program(config, args, ["192.168.1.100"])
|
||||
|
||||
mock_ota_image.assert_not_called()
|
||||
assert mock_run_ota.call_args.args[3] == partition_file
|
||||
|
||||
|
||||
def test_upload_program_ota_without_a_hook_sends_the_firmware(
|
||||
mock_run_ota: Mock,
|
||||
mock_get_port_type: Mock,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""A platform without an ota_image hook sends its build output."""
|
||||
setup_core(platform=PLATFORM_ESP32, tmp_path=tmp_path)
|
||||
mock_get_port_type.return_value = "NETWORK"
|
||||
mock_run_ota.return_value = (0, "192.168.1.100")
|
||||
|
||||
config = {CONF_OTA: [{CONF_PLATFORM: CONF_ESPHOME, CONF_PORT: 3232}]}
|
||||
upload_program(config, MockArgs(), ["192.168.1.100"])
|
||||
|
||||
assert mock_run_ota.call_args.args[3].name == "firmware.bin"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"old_key",
|
||||
[None, "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA="],
|
||||
@@ -8136,32 +8248,3 @@ def test_command_rename_is_dispatched_to_the_cli_module() -> None:
|
||||
with patch("esphome.cli.rename.command_rename", return_value=7) as run:
|
||||
assert main.command_rename(args, {}) == 7
|
||||
run.assert_called_once_with(args, {})
|
||||
|
||||
|
||||
def test_command_rotate_key_is_dispatched_to_the_cli_module() -> None:
|
||||
args = MockArgs()
|
||||
with patch("esphome.cli.rotate_key.command_rotate_key", return_value=7) as run:
|
||||
assert main.command_rotate_key(args, {}) == 7
|
||||
run.assert_called_once_with(args, {})
|
||||
|
||||
|
||||
def test_parse_args_rotate_key() -> None:
|
||||
args = parse_args(
|
||||
[
|
||||
"esphome",
|
||||
"rotate-key",
|
||||
"--device",
|
||||
"dev.local",
|
||||
"--prompt-new-key",
|
||||
"-y",
|
||||
"--username",
|
||||
"mqtt-user",
|
||||
"device.yaml",
|
||||
]
|
||||
)
|
||||
assert args.command == "rotate-key"
|
||||
assert args.device == ["dev.local"]
|
||||
assert args.prompt_new_key is True
|
||||
assert args.yes is True
|
||||
# MQTT device resolution reads these like upload does
|
||||
assert args.username == "mqtt-user"
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user