Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ Do not `!remove` blocks from Apollo's package without checking what depends on t
- `espefuse summary` showed `VDD_SPI_FORCE` unburned, so the strap alone decides the flash voltage.
- **A power cycle recovers, and the overlay prevents the orderly cases.** Hold the power button for 6 seconds, since a reset leaves the camera streaming. The overlay's `on_shutdown` holds the camera in reset, and an OTA reboot with the camera streaming came back clean. The reset after an esptool flash bypasses that hook. Turn `Camera Enable` off over the API first, and a USB flash then boots without a power cycle. The switch drives the sensor's reset line rather than its rail, so turning it back on resets the sensor's registers and needs a restart. A crash or a brownout bypasses the shutdown hook as well.
- **Burning `VDD_SPI` to 3.3V removes the hazard, and nothing here does it.** `espefuse set-flash-voltage 3.3V` is permanent, and the flash voltage is inferred from behavior rather than read from a schematic. The template leaves it to the owner.
- **The camera snapshot endpoint has no authentication.** `esp32_camera_web_server` serves it on port 8081 with no option to protect it, so flash the example device only onto a bench network.
- **The camera web server asserts on reboot after the camera failed setup.** `esp32_camera_web_server` marks itself failed before it creates its semaphore, and its `on_shutdown` deletes the null semaphore in `vQueueDelete`. It shows as a crash on the reboot after an OTA, and a working camera never reaches it.
- **The microphone and the speaker share one I2S bus, so only one runs at a time.** The example device stops its `sound_level` sensor, waits until the microphone stops capturing, plays the tune, and restarts the sensor when playback finishes. A version without the wait froze the screen on a press, and the version with it has not.
- **The touch mapping matches M5GFX, 0 to 319 and 0 to 239 with no offset.** A raw capture over the API ran from y 0 at the top edge to 239 at the bottom. Fingertip taps read up to about 20 pixels low near the bottom and within 4 pixels near the top. That is finger contact rather than a panel offset, so the template does not correct for it.
Expand Down Expand Up @@ -676,7 +677,7 @@ Sharp edges in the tooling around this repository, each one learned by tripping

`--user "$(id -u):$(id -g)"` runs the container as the invoking host user rather than the image's own default, so it reads `$lint_root` through the same owner permission bits `mktemp -d`'s default `0700` already grants, with no `chmod` opening it to any other local account. This assumes a standard rootful Docker daemon, where the container's user namespace is the host's own. Under **rootless** Docker the daemon runs in its own user namespace, so the host UID passed to `--user` maps to an unrelated subordinate UID inside the container rather than to the owner of the bind mount, and the snapshot becomes unreadable instead. On a rootless daemon, `chmod -R o+rX "$lint_root"` after the tar step is the fallback, accepting the wider local-account readability for the run's duration. The trap removes the snapshot on exit either way.

**This includes the hub's `scripts/docker_lint.py` wrapper, and it cannot currently take this snapshot as its `--root`.** Its own target discovery runs `git -C "$root" ls-files`, and the snapshot above deliberately holds only the `git ls-files` output, not `.git` itself, so `--root "$lint_root"` fails with "not a git repository" before any linter runs. Passing the live checkout instead would defeat the whole point of the snapshot. Until the wrapper accepts a plain snapshot or file manifest, lint this repository with the direct `docker run` invocations above rather than `docker_lint.py`.
**The hub's `scripts/docker_lint.py` wrapper builds this same snapshot itself.** It copies the `git ls-files --cached --others --exclude-standard` output into a temporary tree, and each container mounts only that tree, read-only with networking off. So `secrets.yaml` never reaches a container, and running the wrapper with `--root` on a checkout is the route for this repository.

- **The installed `gh` is old enough that `gh pr edit` always fails**, on every repository, and the message names a GraphQL field rather than a permission. See [The `gh` CLI Is Too Old for `gh pr edit`][gh-cli-too-old] for the cause and the REST workaround.
- **A compile job that fails in about 18 seconds is a registry timeout, not a template defect.** The compile jobs pull the [`ptr727/esphome-nonroot`][esphome-nonroot-link] image, and a slow Docker Hub kills the job with `registry-1.docker.io ... context deadline exceeded` before any ESPHome work starts. A real esp-idf compile runs for minutes, so the duration tells the two apart at a glance. Re-run the job rather than reading the template for a fault that is not there.
Expand Down
12 changes: 12 additions & 0 deletions tests/compile_lint/test_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,3 +525,15 @@ def test_an_inlined_expression_owns_only_the_occurrence_at_the_column(tmp_path:
set_main(build, directive(root, 9), " other(q * 2.5); call.set_level(y * 2.5);")
result = lint(root, build).run()
assert result.findings == []


def test_a_build_tree_under_a_dot_directory_is_not_repository_code(tmp_path: Path) -> None:
root, _ = write_build(tmp_path, "")
generated = root / ".esphome" / "build" / "device-test" / "src" / "esphome" / "core.h"
generated.parent.mkdir(parents=True)
generated.write_text("float f(float v) { return v * 2.5; }\n")
lint_build = BuildLint(
root / ".esphome" / "build" / "device-test", root, [], {}, YamlIndex(root)
)
assert lint_build.owned_path(generated) is None
assert lint_build.owned_path(root / "test" / "device.yaml") == "test/device.yaml"
10 changes: 8 additions & 2 deletions tools/compile-lint/src/compile_lint/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,11 +260,17 @@ def owned_sources(root: Path) -> dict[str, str]:


def relative_to_root(path: Path, root: Path) -> str | None:
"""Return a path relative to the config root, or None when it lies outside it."""
"""Return a path relative to the config root, or None outside it or in a dot directory.

A dot directory such as .esphome holds ESPHome's build tree and packages, not owned code.
"""
try:
return path.relative_to(root).as_posix()
relative = path.relative_to(root)
except ValueError:
return None
if any(part.startswith(".") for part in relative.parts[:-1]):
return None
return relative.as_posix()
Comment thread
ptr727 marked this conversation as resolved.


def emitted_lines(text: str, first: int) -> tuple[tuple[int, str], ...]:
Expand Down
Loading