Compiles a host build with `store_yaml`, drives a raw plaintext API socket
(the released aioesphomeapi does not yet know about GetYamlRequest /
GetYamlResponse and would silently drop the streamed bytes as "unknown
message type"), sends GetYamlRequest, accumulates the streamed
GetYamlResponse chunks until done=true, zstd-decompresses, and verifies
both the envelope structure and that the fixture's distinctive markers
(`store-yaml-test`, `store_yaml:`) round-trip back through the recovery
blob.
- Advertise `has_store_yaml` in DeviceInfoResponse so recovery tooling can
detect support without timing out against firmware built without
USE_STORE_YAML.
- Suppress GetYamlResponse from the proto dump path. Every chunk would
otherwise log embedded configuration (including opted-in secrets) on
builds with HAS_PROTO_MESSAGE_DUMP, and bloat logs during recovery.
- Preserve the include graph for files outside the project root: use
`os.path.relpath` instead of just the basename so e.g. two
`../common.yaml` siblings don't collide on recovery.
- Keep `track_yaml_loads` open across `validate_config` so files loaded
by remote packages and substitution-resolved includes are captured.
- Add focused unit tests for `_gather_files` (redaction, secrets.yml,
opt-in, dedupe, external-path handling, missing sources) and
`_pack_envelope` (round-trip, UTF-8 paths, overlong-path guard).
- Make the test_bundle assertion case-insensitive.
`track_yaml_loads` already returns resolved Path objects, so re-wrapping
each entry in `Path(...).resolve()` was a no-op. The OR'd
`Path(path).name in SECRETS_FILES` branch always evaluated the same as
the first check on the resolved name; removing it. Document the symlink
edge case the resolver swallows so a future change to the loader can
revisit it.
- Promote `_force_load_include_files` from bundle.py to yaml_util.py as
`force_load_include_files`. bundle.py and the new caller now share it.
- Call it inside the `track_yaml_loads` block in config.py so deferred
`!include` values, packages, and similar are loaded while the listener
is active. Without this, `CORE.data["yaml_sources"]` only contained the
entry YAML, and store_yaml shipped an incomplete recovery blob.
- Treat both `secrets.yaml` and `secrets.yml` as secrets (matches
`esphome.const.SECRETS_FILES`); also check the original (non-resolved)
filename to catch a `secrets.yaml` symlinked to an unrelated target.
- Update bundle tests for the new import path and message wording.
Drop the pre-advance + rewind-on-failure dance in try_send_store_yaml_; match
the camera streaming pattern (advance after send_message succeeds, retry the
same chunk on WOULD_BLOCK). Also remove the unreachable `|| pos == 0` branch
in the while condition — on_get_yaml_request short-circuits the zero-size
path before this function is ever called.
- Coalesce dump_config into a single ESP_LOGCONFIG call.
- Replace the raw get_data() accessor with a read_chunk() helper that copies
bytes via progmem_read_byte, so callers cannot accidentally dereference the
PROGMEM pointer directly on ESP8266.
- Update the API streaming handler to use read_chunk().
Adds a new opt-in component that compresses the on-disk YAML files with zstd
at codegen time and stores them in PROGMEM. The native API exposes a chunked
GetYaml RPC so a lost configuration can be retrieved from a running device.
Decompression happens client-side; no decompressor is shipped on-device.