From 75b4b1faeca13cd3ee8f90af324683e2a8ba6616 Mon Sep 17 00:00:00 2001 From: "t.galimkhanov" Date: Mon, 14 Sep 2026 09:10:10 +0500 Subject: [PATCH 1/6] feat(firmware): embed the ESP-IDF app descriptor; re-release images 20260914085743 --- README.md | 61 +++++++ README.ru.md | 61 +++++++ firmware/Cargo.lock | 164 ++++++++++++++++++ firmware/NOTES.md | 36 ++++ firmware/README.md | 36 +++- firmware/README.ru.md | 36 +++- firmware/a/Cargo.toml | 3 + firmware/a/src/main.rs | 5 + firmware/bin/20260910101457-build-info.md | 34 ---- firmware/bin/20260914085743-build-info.md | 38 ++++ ...7-firmware-a => 20260914085743-firmware-a} | Bin 242344 -> 242492 bytes ...7-firmware-p => 20260914085743-firmware-p} | Bin 227448 -> 227596 bytes ...7-firmware-q => 20260914085743-firmware-q} | Bin 254640 -> 254788 bytes firmware/p/Cargo.toml | 3 + firmware/p/src/main.rs | 5 + firmware/q/Cargo.toml | 3 + firmware/q/src/main.rs | 5 + 17 files changed, 444 insertions(+), 46 deletions(-) create mode 100644 firmware/NOTES.md delete mode 100644 firmware/bin/20260910101457-build-info.md create mode 100644 firmware/bin/20260914085743-build-info.md rename firmware/bin/{20260910101457-firmware-a => 20260914085743-firmware-a} (59%) rename firmware/bin/{20260910101457-firmware-p => 20260914085743-firmware-p} (55%) rename firmware/bin/{20260910101457-firmware-q => 20260914085743-firmware-q} (59%) diff --git a/README.md b/README.md index f42d8d6..fcf966f 100644 --- a/README.md +++ b/README.md @@ -223,6 +223,67 @@ and Q; 1× ESP32-S3-WROOM-1 N16R8 CAM with OV2640 — node P + the stretch camera (the purchase list — [`docs/eng/equipment.md`](./docs/eng/equipment.md)). +The bench's first flash — 2026-09-14, node A (`firmware-a`, a DevKitC-1 +clone with a CH343 bridge): S0 passed → espflash wrote the 2nd-stage +bootloader, the partition table and the app in one image; the node booted +and calibrated its zero. Log: + +```text +espflash flash --monitor --port /dev/ttyACM0 target/xtensa-esp32s3-none-elf/release/firmware-a +[2026-09-14T03:17:32Z INFO ] Serial port: '/dev/ttyACM0' +[2026-09-14T03:17:32Z INFO ] Connecting... +[2026-09-14T03:17:32Z INFO ] Using flash stub +Chip type: esp32s3 (revision v0.2) +Crystal frequency: 40 MHz +Flash size: 16MB +Features: WiFi, BLE, Embedded Flash +MAC address: 44:1b:f6:fd:ea:cc +App/part. size: 109,856/16,384,000 bytes, 0.67% +[00:00:01] [========================================] 1/1 0x0 Verifying... OK! +[00:00:00] [========================================] 1/1 0x8000 Verifying... OK! +[00:00:03] [========================================] 3/3 0x10000 Verifying... OK! +[2026-09-14T03:17:39Z INFO ] Flashing has completed! +Commands: + CTRL+R Reset chip + CTRL+C Exit + +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT) +SPIWP:0xee +mode:DIO, clock div:2 +load:0x3fce2820,len:0x14d0 +load:0x403c8700,len:0xdcc +load:0x403cb700,len:0x2f54 +entry 0x403c8900 +I (29) boot: ESP-IDF v6.1-beta1-497-g14f663f003e 2nd stage bootloader +I (30) boot: Multicore bootloader +I (30) boot: chip revision: v0.2 +I (30) boot: efuse block revision: v1.4 +I (34) boot.esp32s3: Boot SPI Speed : 40MHz +I (38) boot.esp32s3: SPI Mode : DIO +I (42) boot.esp32s3: SPI Flash Size : 16MB +I (45) boot: Enabling RNG early entropy source... +I (50) boot: Partition Table: +I (52) boot: ## Label Usage Type ST Offset Length +I (59) boot: 0 nvs WiFi data 01 02 00009000 00006000 +I (65) boot: 1 phy_init RF data 01 01 0000f000 00001000 +I (72) boot: 2 factory factory app 00 00 00010000 00fa0000 +I (78) boot: End of partition table +I (82) esp_image: segment 0: paddr=00010020 vaddr=3c000020 size=02990h ( 10640) map +I (92) esp_image: segment 1: paddr=000129b8 vaddr=3fc89998 size=009c4h ( 2500) load +I (97) esp_image: segment 2: paddr=00013384 vaddr=40378000 size=01998h ( 6552) load +I (106) esp_image: segment 3: paddr=00014d24 vaddr=00000000 size=0b2f4h (45812) +I (123) esp_image: segment 4: paddr=00020020 vaddr=42010020 size=0acd4h (44244) map +I (135) boot: Loaded app from partition at offset 0x10000 +I (135) boot: Disabling RNG early entropy source... +a: boot, run_id=bench-a +a: zero=2229 +a,bench-a,160,idle +``` + +The shakedown facts — [`firmware/NOTES.md`](./firmware/NOTES.md). + ## Status Done: weeks 1–6 — the Conv1D kernel (optimized in week 6: zero-point diff --git a/README.ru.md b/README.ru.md index e1a1750..a9e50fc 100644 --- a/README.ru.md +++ b/README.ru.md @@ -212,6 +212,67 @@ Conv1D — контракт недель 2–3). [`docs/rus/equipment.md`](./docs/rus/equipment.md)). Декомпозиция обкатки — [`docs/rus/decompose/firmware.md`](./docs/rus/decompose/firmware.md). +Первая прошивка на стенде — 2026-09-14, узел A (`firmware-a`, клон DevKitC-1 +с мостом CH343): этап S0 пройден → espflash залил вторичный загрузчик, +таблицу разделов и приложение одним образом, узел поднялся и откалибровал +нуль. Журнал: + +```text +espflash flash --monitor --port /dev/ttyACM0 target/xtensa-esp32s3-none-elf/release/firmware-a +[2026-09-14T03:17:32Z INFO ] Serial port: '/dev/ttyACM0' +[2026-09-14T03:17:32Z INFO ] Connecting... +[2026-09-14T03:17:32Z INFO ] Using flash stub +Chip type: esp32s3 (revision v0.2) +Crystal frequency: 40 MHz +Flash size: 16MB +Features: WiFi, BLE, Embedded Flash +MAC address: 44:1b:f6:fd:ea:cc +App/part. size: 109,856/16,384,000 bytes, 0.67% +[00:00:01] [========================================] 1/1 0x0 Verifying... OK! +[00:00:00] [========================================] 1/1 0x8000 Verifying... OK! +[00:00:03] [========================================] 3/3 0x10000 Verifying... OK! +[2026-09-14T03:17:39Z INFO ] Flashing has completed! +Commands: + CTRL+R Reset chip + CTRL+C Exit + +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT) +SPIWP:0xee +mode:DIO, clock div:2 +load:0x3fce2820,len:0x14d0 +load:0x403c8700,len:0xdcc +load:0x403cb700,len:0x2f54 +entry 0x403c8900 +I (29) boot: ESP-IDF v6.1-beta1-497-g14f663f003e 2nd stage bootloader +I (30) boot: Multicore bootloader +I (30) boot: chip revision: v0.2 +I (30) boot: efuse block revision: v1.4 +I (34) boot.esp32s3: Boot SPI Speed : 40MHz +I (38) boot.esp32s3: SPI Mode : DIO +I (42) boot.esp32s3: SPI Flash Size : 16MB +I (45) boot: Enabling RNG early entropy source... +I (50) boot: Partition Table: +I (52) boot: ## Label Usage Type ST Offset Length +I (59) boot: 0 nvs WiFi data 01 02 00009000 00006000 +I (65) boot: 1 phy_init RF data 01 01 0000f000 00001000 +I (72) boot: 2 factory factory app 00 00 00010000 00fa0000 +I (78) boot: End of partition table +I (82) esp_image: segment 0: paddr=00010020 vaddr=3c000020 size=02990h ( 10640) map +I (92) esp_image: segment 1: paddr=000129b8 vaddr=3fc89998 size=009c4h ( 2500) load +I (97) esp_image: segment 2: paddr=00013384 vaddr=40378000 size=01998h ( 6552) load +I (106) esp_image: segment 3: paddr=00014d24 vaddr=00000000 size=0b2f4h (45812) +I (123) esp_image: segment 4: paddr=00020020 vaddr=42010020 size=0acd4h (44244) map +I (135) boot: Loaded app from partition at offset 0x10000 +I (135) boot: Disabling RNG early entropy source... +a: boot, run_id=bench-a +a: zero=2229 +a,bench-a,160,idle +``` + +Факты шейкдауна — [`firmware/NOTES.md`](./firmware/NOTES.md). + ## Статус Готово: недели 1–6 — кернел Conv1D (в неделю 6 оптимизирован: вынос diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock index c2b6bc4..c4fbffa 100644 --- a/firmware/Cargo.lock +++ b/firmware/Cargo.lock @@ -119,6 +119,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + [[package]] name = "critical-section" version = "1.2.0" @@ -212,6 +221,37 @@ dependencies = [ "syn 3.0.4", ] +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + [[package]] name = "delegate" version = "0.13.5" @@ -480,6 +520,23 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "esp-bootloader-esp-idf" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba0e487f6843419bb085d871f6e860da1bb8c267ce1c5db1051b83882e943054" +dependencies = [ + "document-features", + "esp-config", + "esp-hal-procmacros", + "esp-metadata-generated", + "esp-rom-sys", + "esp-storage", + "jiff", + "sha2", + "strum", +] + [[package]] name = "esp-config" version = "0.8.0" @@ -597,6 +654,21 @@ dependencies = [ "esp32s3", ] +[[package]] +name = "esp-storage" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef0e785f54e316f64a1e14a7a2f061a2ea0b038b284902ca71e1e1f5574cf47" +dependencies = [ + "document-features", + "esp-hal", + "esp-hal-procmacros", + "esp-metadata-generated", + "esp-rom-sys", + "esp-sync", + "esp32s3", +] + [[package]] name = "esp-sync" version = "0.3.0" @@ -725,6 +797,7 @@ name = "firmware-a" version = "0.1.0" dependencies = [ "board", + "esp-bootloader-esp-idf", "esp-hal", "features-cli", "fmt-util", @@ -737,6 +810,7 @@ name = "firmware-p" version = "0.1.0" dependencies = [ "board", + "esp-bootloader-esp-idf", "esp-hal", "fmt-util", ] @@ -746,6 +820,7 @@ name = "firmware-q" version = "0.1.0" dependencies = [ "board", + "esp-bootloader-esp-idf", "esp-hal", "features-cli", "fmt-util", @@ -929,6 +1004,49 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jiff" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" +dependencies = [ + "defmt", + "log", +] + +[[package]] +name = "jiff-static" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + [[package]] name = "libm" version = "0.2.16" @@ -941,6 +1059,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + [[package]] name = "matrixmultiply" version = "0.3.11" @@ -1090,6 +1214,15 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -1315,6 +1448,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.11.3", +] + [[package]] name = "simba" version = "0.8.1" @@ -1461,6 +1605,26 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" diff --git a/firmware/NOTES.md b/firmware/NOTES.md new file mode 100644 index 0000000..90e8c7b --- /dev/null +++ b/firmware/NOTES.md @@ -0,0 +1,36 @@ +# NOTES — факты шейкдауна (колея железа) + +Рабочий журнал обкатки на стенде. План сессий S0–S7 — +[docs/rus/decompose/firmware.md](../docs/rus/decompose/firmware.md); итоговый +гейт (`firmware-gate.md`, рус/англ) оформляется на S7. Пополняется по ходу +сессий. + +## 2026-09-14 — S0/S1: первая прошивка, узел A + +- **Плата — клон DevKitC-1**: мост USB-UART — **CH343** (QinHeng + `1a86:55d3`), не CP2102 → порт `/dev/ttyACM0`, а не `/dev/ttyUSB*`. + Маркировка портов «COM»/«USB» (USB-C), а не «UART»/«USB» как у v1.0 с + micro-USB. Нативный «USB»-порт платы (Espressif `303a:4001`) тоже даёт + `ttyACM*` — для монитора бесполезен (вывод идёт в аппаратный UART). + Различение плат при нескольких подключённых — `/dev/serial/by-id/` + (серийник CH343 уникален у каждой платы). +- **espflash ≥ 4.6** отказывается прошивать образ без ESP-IDF App + Descriptor → в `firmware-{a,q,p}` добавлены target-gated зависимость + `esp-bootloader-esp-idf = "0.6"` (feature `esp32s3`, MSRV rustc 1.95) и + вызов `esp_bootloader_esp_idf::esp_app_desc!()` в `mod app`. +- espflash заливает **три региона**: вторичный загрузчик @0x0, таблица + разделов @0x8000, приложение @0x10000 — образ самодостаточен, фабричный + загрузчик в flash не нужен (загрузчик ESP-IDF v6.1-beta1 идёт в бандле + espflash). +- Чистая машина: `cargo install espup` + `cargo install espflash`; с + asdf-рустом бинарники попадают в `~/.asdf/installs/rust//bin` → + после установки `asdf reshim rust`. Права на порт — группа `dialout` + (`sudo usermod -aG dialout $USER` + перелогин; в текущем шелле — + `newgrp dialout`). +- **Железо узла A**: esp32s3 rev v0.2, 16 MB flash, кварц 40 МГц. + Стартовая калибровка нуля на висящем входе ACS712: **zero=2229** отсчётов + (середина шкалы 12-битного АЦП ≈ 2048; допуск калибровки — вопрос S1). +- Первые строки живого образа — в точности как в README: `a: boot, + run_id=bench-a` → `a: zero=2229` → `a,bench-a,160,idle`. +- Отклонение от плана: blinky (S0) пропущен — сразу прошит рабочий + `firmware-a`; полный журнал первой загрузки — в корневом README. diff --git a/firmware/README.md b/firmware/README.md index c4ebe2b..767e6be 100644 --- a/firmware/README.md +++ b/firmware/README.md @@ -9,10 +9,11 @@ node P plus the stretch camera (its camera wiring takes some pins). A separate workspace, like `fork/microflow`: the target toolchain (Xtensa, `espup`) must not affect the host CI of the root workspace. -## Status: implemented, awaiting bench bring-up +## Status: implemented; bench bring-up started (S0 — 2026-09-14) The track is implemented in code; the physical bring-up (S0 blinky → S6 -counting) is the remaining human-on-hardware part: +counting) is the human-on-hardware part, started 2026-09-14. The shakedown +facts — [NOTES.md](NOTES.md). - **`firmware-{a,q,p}`** build for `xtensa-esp32s3-none-elf` (esp-hal 1.2, the `unstable` driver modules) **and** on the host (an empty stub binary; @@ -47,6 +48,10 @@ cargo install espup && espup install # the patched Xtensa toolchain . $HOME/export-esp.sh # the linker (xtensa-esp-elf-gcc) PATH ``` +The flasher (host, once): `cargo install espflash`. With asdf-managed Rust +the binary lands in `~/.asdf/installs/rust//bin` — run +`asdf reshim rust` after installing. + The esp toolchain lives in `~/.rustup/toolchains/esp` (espup's default), which the asdf-managed shell rustup does not see. Prepend its `bin` to `PATH` — called by full path, esp's cargo still resolves `rustc` from @@ -74,7 +79,8 @@ the board's wiring (`board` is the single source of truth): | DevKitC-1 #2 (servo GPIO11 + INMP441) | Q | `firmware-q` | | CAM board (TCRT5000 on GPIO5) | P | `firmware-p` | -The boards differ by their USB serial port — check `/dev/ttyUSB*`: +The boards differ by their USB serial port — check `/dev/ttyUSB*` (a +CP2102 bridge) or `/dev/ttyACM*` (a CH343 — see the note below): ```bash espflash flash --port /dev/ttyUSB0 target/xtensa-esp32s3-none-elf/release/firmware-a @@ -86,6 +92,22 @@ espflash flash --port /dev/ttyUSB2 target/xtensa-esp32s3-none-elf/release/firmwa board is not seen, hold **BOOT** while plugging it in (the USB download mode). +The device name depends on the board's USB-UART bridge: the original +DevKitC-1 carries a CP2102 → `/dev/ttyUSB*`; clones often replace it with +a CH343 → `/dev/ttyACM*` (on USB-C revisions the bridge connector is +labeled "COM", on the v1.0 micro-USB one — "UART"). espflash does not +care whether it opens a `ttyUSB` or a `ttyACM`. The board's native "USB" +port also shows up as `/dev/ttyACM*`, but it is useless for monitoring: +the output goes to the hardware UART. With several boards all on +`ttyACM*`, tell them apart by the stable by-id names (a CH343 serial is +unique per board): + +```bash +ls -l /dev/serial/by-id/ +espflash flash --port /dev/serial/by-id/usb-QinHeng_Electronics_USB_Single_Serial_-if00 \ + target/xtensa-esp32s3-none-elf/release/firmware-a +``` + What a live image prints over UART (115200) — the first bring-up check: - A: `a: boot, run_id=bench-a` → `a: zero=NNN` (the startup zero @@ -114,9 +136,11 @@ before the real I2S driver lands (DMA buffers will add on top). ## Checking the UART output -Plug the board's **UART** micro-USB port (the one by the buttons, -labeled "UART", through the CP2102 bridge → `/dev/ttyUSB*`) — not the -OTG port: the firmware does not use the native USB. +Plug the board's **bridge port** — the connector by the buttons: on v1.0 +a micro-USB labeled "UART", on USB-C revisions and clones a USB-C +labeled "COM". Not the OTG "USB" port: the firmware does not use the +native USB. A CP2102 bridge gives `/dev/ttyUSB*`, a CH343 on clones +`/dev/ttyACM0` (the examples below use `/dev/ttyUSB0`). ```bash # option 1: espflash (already installed for flashing) — sets 115200 and diff --git a/firmware/README.ru.md b/firmware/README.ru.md index 8bcddce..e3c89b8 100644 --- a/firmware/README.ru.md +++ b/firmware/README.ru.md @@ -9,10 +9,12 @@ как `fork/microflow`: целевой тулчейн (Xtensa, `espup`) не должен влиять на хостовый CI корневого workspace. -## Статус: реализовано, ожидает шейкдауна на стенде +## Статус: реализовано; шейкдаун начат (S0 — 2026-09-14) Колея реализована в коде; физический подъём (S0 blinky → S6 счёт) — -оставшаяся человеческая часть на железе: +человеческая часть на железе, начата 2026-09-14. + +Факты шейкдауна — [NOTES.md](NOTES.md). - **`firmware-{a,q,p}`** собираются под `xtensa-esp32s3-none-elf` (esp-hal 1.2, драйверные модули `unstable`) **и** на хосте (бинарь — @@ -48,6 +50,10 @@ cargo install espup && espup install # пропатченный Xtensa-тул . $HOME/export-esp.sh # PATH линкера (xtensa-esp-elf-gcc) ``` +Прошивальщик (хост, один раз): `cargo install espflash`. С asdf-рустом +бинарник встаёт в `~/.asdf/installs/rust//bin` — после установки +сделайте `asdf reshim rust`. + Esp-тулчейн стоит в `~/.rustup/toolchains/esp` (дефолт espup), который asdf-шелловый rustup не видит. Добавьте его `bin` в начало `PATH` — при вызове по полному пути cargo из esp всё равно берёт `rustc` из `PATH` @@ -75,7 +81,8 @@ cargo build --release \ | DevKitC-1 №2 (серво GPIO11 + INMP441) | Q | `firmware-q` | | CAM-плата (TCRT5000 на GPIO5) | P | `firmware-p` | -Платы различаются по USB-последовательному порту — смотрите `/dev/ttyUSB*`: +Платы различаются по USB-последовательному порту — смотрите `/dev/ttyUSB*` +(мост CP2102) или `/dev/ttyACM*` (мост CH343 — см. примечание ниже): ```bash espflash flash --port /dev/ttyUSB0 target/xtensa-esp32s3-none-elf/release/firmware-a @@ -86,6 +93,21 @@ espflash flash --port /dev/ttyUSB2 target/xtensa-esp32s3-none-elf/release/firmwa `--monitor` сразу после прошивки покажет консоль. Если плата не видится — зажмите **BOOT** при подключении (режим прошивки по USB). +Имя устройства зависит от моста платы: оригинальный DevKitC-1 несёт CP2102 +→ `/dev/ttyUSB*`; на клонах его часто заменяет CH343 → `/dev/ttyACM*` +(на ревизиях с USB-C разъём моста подписан «COM», на v1.0 с micro-USB — +«UART»). espflash'у безразлично, `ttyUSB` это или `ttyACM`. Нативный +«USB»-порт платы тоже даёт `/dev/ttyACM*`, но для монитора бесполезен: +вывод идёт в аппаратный UART. Если подключено несколько плат и все они +`ttyACM*`, различайте их по стабильным by-id-именам (серийник CH343 +уникален у каждой платы): + +```bash +ls -l /dev/serial/by-id/ +espflash flash --port /dev/serial/by-id/usb-QinHeng_Electronics_USB_Single_Serial_-if00 \ + target/xtensa-esp32s3-none-elf/release/firmware-a +``` + Что печатает живой образ в UART (115200) — первая проверка шейкдауна: - A: `a: boot, run_id=bench-a` → `a: zero=NNN` (стартовая калибровка @@ -114,9 +136,11 @@ Bring-up: всё равно проверить высокую воду до пе ## Проверка вывода UART -Подключайте **UART**-порт платы (micro-USB со стороны кнопок, подписан -«UART», через мост CP2102 → `/dev/ttyUSB*`) — не OTG-порт: прошивка -нативный USB не использует. +Подключайте порт моста платы — разъём со стороны кнопок: на v1.0 это +micro-USB с маркировкой «UART», на ревизиях с USB-C и клонах — USB-C с +маркировкой «COM». Не OTG-порт «USB»: прошивка нативный USB не +использует. CP2102 даёт `/dev/ttyUSB*`, CH343 на клонах — `/dev/ttyACM0` +(примеры ниже — для `/dev/ttyUSB0`). ```bash # вариант 1: espflash (уже стоит для прошивки) — сам ставит 115200 diff --git a/firmware/a/Cargo.toml b/firmware/a/Cargo.toml index 9bb4609..6e683f4 100644 --- a/firmware/a/Cargo.toml +++ b/firmware/a/Cargo.toml @@ -19,3 +19,6 @@ nalgebra = { workspace = true } # the driver modules (uart/adc/gpio/delay) are public only under it. [target.'cfg(target_arch = "xtensa")'.dependencies] esp-hal = { version = "1.2", features = ["esp32s3", "unstable"] } +# The ESP-IDF app descriptor (esp_app_desc! in main.rs): required by the +# 2nd-stage bootloader; espflash >= 4.6 refuses a descriptor-less image. +esp-bootloader-esp-idf = { version = "0.6", features = ["esp32s3"] } diff --git a/firmware/a/src/main.rs b/firmware/a/src/main.rs index 0d98082..8e73140 100644 --- a/firmware/a/src/main.rs +++ b/firmware/a/src/main.rs @@ -36,6 +36,11 @@ mod app { CONFIRM_AFTER, SAMPLE_US, }; + // The ESP-IDF app descriptor at the image head: the 2nd-stage + // bootloader requires it, and espflash >= 4.6 refuses to flash an + // image without it. + esp_bootloader_esp_idf::esp_app_desc!(); + /// Run id of this firmware image (the offline-CSV family uses it verbatim). const RUN_ID: &str = "bench-a"; diff --git a/firmware/bin/20260910101457-build-info.md b/firmware/bin/20260910101457-build-info.md deleted file mode 100644 index 4053973..0000000 --- a/firmware/bin/20260910101457-build-info.md +++ /dev/null @@ -1,34 +0,0 @@ -# Firmware images — build 20260910101457 - -- **Target**: `xtensa-esp32s3-none-elf`, release (optimized, LTO) -- **Toolchain**: rustc 1.97.0-nightly (8ea53bcd7 2026-07-08) — espup, `~/.rustup/toolchains/esp` -- **Built**: 2026-09-10 10:14 (+0500), from the `firmware/` workspace -- **git HEAD**: `e497501ad07f43b4743bacfdffa39b71d1aa4dad` + uncommitted firmware review fixes (this build carries them) -- **Fixes vs the 20260910072946 set** (plan `docs/rus/backlog/plan/20260910095045-bl-plan.md`): - - `firmware-p`: EdgeCounter mirrors the host semantics — rising-edge detection (no phantom count on a boot-blocked barrier), anti-double window anchored to the last counted part (a glitch storm no longer freezes the counter); pinned by a shared-trace test with `nodes::p` (card 05) - - `firmware-a`: `t_ms` advances (`u64` microseconds, was `SAMPLE_US/1000 == 0`) (card 06) - - `firmware-q`: the 4 KiB window is static, not on the stack; measured headroom ~294 KiB vs ~25 KiB peak (card 32, see firmware/README "Stack headroom") - - `firmware-tools`: uart-bridge survives serial garbage (lossy UTF-8) and publish errors; state/verdict whitelisted against JSON injection; end markers on every exit path (card 33) - - host-only: `fmt-util` crate (shared Cursor, NaN-safe argmax), contract tests pin WINDOW to `features_cli::window_spec`, real label=good fixture for the Q parity test (cards 07-fw, 41) - -## Files - -One ELF per board (espflash converts it into a bootable image itself). Board mapping per `firmware/README.md`: - -| File | Node | Board | sha256 | -| --------------------------- | ---- | -------------------------------------- | ---------------------------------------------------------------- | -| `20260910101457-firmware-a` | A | DevKitC-1 #1 (ACS712 on GPIO4) | `a9246eea700f98245feea29dc9d69de6430d013338f47eb2586dc6b3d56c937e` | -| `20260910101457-firmware-q` | Q | DevKitC-1 #2 (servo GPIO11 + INMP441) | `2d8326a23f159117e64eceb7aaf80e6940b784323e0ff5141dca7f31fff834a7` | -| `20260910101457-firmware-p` | P | CAM board (TCRT5000 on GPIO5) | `53382eca2396c9b4082caf44df756c9596d0352868c984c450de18d7933ac768` | - -## Flashing - -From the `firmware/` directory (ports per `firmware/README.md` — boards differ by `/dev/ttyUSB*`): - -```bash -espflash flash --port /dev/ttyUSB0 bin/20260910101457-firmware-a -espflash flash --port /dev/ttyUSB1 bin/20260910101457-firmware-q -espflash flash --port /dev/ttyUSB2 bin/20260910101457-firmware-p -``` - -Do NOT flash `qemu/target/.../oee-qemu` — a different target (LM3S6965/Cortex-M3); it will not boot on an ESP32-S3. diff --git a/firmware/bin/20260914085743-build-info.md b/firmware/bin/20260914085743-build-info.md new file mode 100644 index 0000000..22370fb --- /dev/null +++ b/firmware/bin/20260914085743-build-info.md @@ -0,0 +1,38 @@ +# Firmware images — build 20260914085743 + +- **Target**: `xtensa-esp32s3-none-elf`, release (optimized, LTO) +- **Toolchain**: rustc 1.97.0-nightly (8ea53bcd7 2026-07-08) (1.97.0.0) — espup, `~/.rustup/toolchains/esp` +- **Built**: 2026-09-14 (local, +0500), from the `firmware/` workspace +- **git HEAD**: `de022a45363665a45f10cc56d802e7379ea01153` + uncommitted working-tree changes on `hardware-firmware` (this build carries them) +- **Changes vs the 20260910101457 set**: + - all nodes: the ESP-IDF app descriptor is embedded (`esp-bootloader-esp-idf` 0.6, + feature `esp32s3`, `esp_app_desc!()` in each `mod app`) — required by espflash >= 4.6 + (refuses a descriptor-less image) and accepted by the 2nd-stage bootloader; first + bench flash verified 2026-09-14 (node A on a CH343-bridge clone, S0 — see + `firmware/NOTES.md` and the root README) + - espflash writes bootloader @0x0 + partition table @0x8000 + app @0x10000 — the + image is self-contained + - docs only (no code): CH343/`ttyACM*` port naming, `/dev/serial/by-id`, espflash + install notes + +## Files + +One ELF per board (espflash converts it into a bootable image itself). Board mapping per `firmware/README.md`: + +| File | Node | Board | sha256 | +| --------------------------- | ---- | -------------------------------------- | ------------------------------------------------------------------ | +| `20260914085743-firmware-a` | A | DevKitC-1 #1 (ACS712 on GPIO4) | `b4b79ed303d00ac8559e81ea10d50333a3935ee2c8c5a1a01f8dfc368ffd5ad4` | +| `20260914085743-firmware-q` | Q | DevKitC-1 #2 (servo GPIO11 + INMP441) | `4cebffe6fcd72acb6de2d12c07a6ea6898ac538c6665150ae252c25cfded7f5d` | +| `20260914085743-firmware-p` | P | CAM board (TCRT5000 on GPIO5) | `bc301f1be9f9b89167b3d5f306ea0ad424f1a9cc4bff80db350b3e6f79990dd6` | + +## Flashing + +From the `firmware/` directory (ports per `firmware/README.md`; on CH343-bridge clones the port is `/dev/ttyACM*`, see NOTES): + +```bash +espflash flash --port /dev/ttyACM0 bin/20260914085743-firmware-a +espflash flash --port /dev/ttyACM1 bin/20260914085743-firmware-q +espflash flash --port /dev/ttyACM2 bin/20260914085743-firmware-p +``` + +Do NOT flash `qemu/target/.../oee-qemu` — a different target (LM3S6965/Cortex-M3); it will not boot on an ESP32-S3. diff --git a/firmware/bin/20260910101457-firmware-a b/firmware/bin/20260914085743-firmware-a similarity index 59% rename from firmware/bin/20260910101457-firmware-a rename to firmware/bin/20260914085743-firmware-a index 2a029ef295fcdbe917ab2af14e1aaf15a73c344b..45c9a20087df1dafc0d95de8028bf22951d6b5c2 100755 GIT binary patch delta 41983 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 LjR#x4TE6{1&`>Lq diff --git a/firmware/bin/20260910101457-firmware-p b/firmware/bin/20260914085743-firmware-p similarity index 55% rename from firmware/bin/20260910101457-firmware-p rename to firmware/bin/20260914085743-firmware-p index adc8e95905b93589afd7a7c88c4a8729e85bacea..2dbd253b6b160c38a301e0a6641583423cbf28ab 100755 GIT binary patch delta 41656 zcmd75e|*>T{{R1cy;`-jYSq%JRjXD_Ev=dwmR_x;VP%+1EiDbDVrm%TwPjctR*UCK z7{aKIrBMjWiIXgf(3`Mo=bVnyVI3hE`&=KdAA8<5o&UaneCP7)x$FJ$cs_pKe?6Xi zZB90HD*11x(&%CJOWIiGE5VAk+F7yI0PZe0I-JY0lFSv~&$8nCZNB*PE~ zfVNiBA}U2KOp^CZ>DWfZzj!W^^C7 zFMB^`k~h>!x*qCDi?Q6tb+VEcaPQC6L3jw?x8;iMU?t7s-kWh7u7Kxqzl6(h$Ql0j zy72uOTnhrsUikPJd^O*@0?)gTol&-8a35D#+4F;^Fv;%+kKx~eL#{${=a9Ml`^}Kv zqcaO#$vk9*1g_EoS91E|npvSCAqyE>IkA3BA9pBy^U z)w%4Wp}pNP`DNb?9XYDAe-e{<#BA_c0ZwVfrcZx)*~PI#2UBYXk{yAe^Q{%eWd;>? zk1IL!Zdda5ySF?Rd)U?O&jT)7G3*g*gM?Kp6?3W_i;DF z{gk`e+XI37*8%yrf%~Dr{Yc=R{p79*X21Uq8+%BC%_1Fx&G3E*-2WS>?>!Ot=I6lu zWZ-@(aBmFU{|MZzDF49RfqO{c-j2KJphMsu7Pxl_+`9$tJp%VhH{Xisc%^ zbph8!Tp3)m`;>i?w&DNhL)_YW*|6*S z7&jb?G$qBAyo&E5`Q9q9tf=0Wr#jN|m^cy3_ceTfz)e|fShf`%mfh5Zj+$Z%&&LCG zx4DudaN)6$1PXjo64%Flt)vF71bdvv=a>4?K36Q4himKrYrwrbuiLuwI@jvn?h{Mx z83nG9Z5hC^Gwp{9T!VUaXS7^qY-!gUN9?km?ry(d;IjKhpoX`&l6!K$wuhBez;%>r zA>2h6B?-#&Bl}JT-VJ?ieS|7TougV zKWsvxsk_WfxbCTV%Zg5JYU*!AyRa%&eM~r$;0l@GN*=|1ELR-YNUrm_#&a>S$i}87 zZy{#=4RRa=*Ig)_vha zEvu0(?~1UjOq%sRR&KriP&?~<%JZ4aNwYDG^O}xUQZe(W>Sq7rKG&3U_%?%mdylCU zutERf`!BffMQ+Ww)}8+Bt_gWu`CMsSnb*3K--)oVz27ym1JyirR`#V4_S*Yh=&t?w z{cMiQ{`r2_o4p9Ok?2>%^5MtwnD>Asfht%ehMF^98O8%`D(1bo;6l%X*m^-b;rxQT26b6770(=zJYlB3A}isoBD0S8_e-*8#PtMRttqAXg+R63dmywGn=m ztN2dpdR)oaTGGaRS8_aC632BeOF!=pS8^ivB>(QZ(~;NK)`p#x^~k%iS&z6=ds%~$ z!aHM%m$|Hhzx>gZNyE8ZMNjOSP{#(fQviPkc=)u(t>MN?^U_{v%J zrR5ddI;_m9FDd&FU( z%O-YsW@6~MU9a5{vN^IoZ2O^}^@Wl3o7#PCR%7#-^`C_9unwJBU)a6g{`pr|{2B2_ z!s6Pwtj!~i*zu=a{oGc)oqo#IXJq4%P3`u$>q4yQyRE{LM>e&8s|^jDd8%#ShCrtc zN5aw&nT^`~gT3~YYv83JN0wi75epblJ%Z}I`e{&5T*#1aOap}9Eu6HN4T^4FB zn-~%nIsL8Bus&g(3O_u&r(@u8MpHa!8Dp>++3Uyy z=T0xIw#ZB#eq`WJoNgq@WC|$d(a@0#}RxB@{_~n|XI~=K4vuwrkm7z!0#)h@& z+YohN`7P5+etZAFO<}utG#xIy>+oA=FcD*5){(e3cUVUXzqeQZ;Tq|VJ7mB6hij1Q zd;1@MxKdpE?BPwW5pCZ)RA?QtXEnK^TyNWVB5}Q9S2nrEByKwFx9N?CPiNCR+OW^= zmCqkuHD}?fr>oblT=jvqYR-a{&mP|T=?T|wt{Fcc+VRVwPj-Y9{(Q*Ujq*W-T@Dw# z7+&n2{+;{j2M$%N37@{EU1jLv9qz)54!vbG?WKQ)RaTtzZ{@j%HnnNq%w3_rZ5e&Y z{{9!&@LsWp*p^r-1b59}{DZ&vs-C8}r&DY+6J7ZFkkQM3xmR9!$ml|OyGpm|JMzzc zhqmb3uM)06-@nw+_|J7t-~Qsa{l(q?VT%9m6yxvgXt%pOeDW3PBQ8q0@VX_rV@59- z6K`#;al0qFyo6<{SZ#c#R)Tdin^UM|rIYBGdmF2xx#6HmzxZn7JHk<)C+1RlE z#M#Eene$a;f(T!2EOWButliA!q{G+>LfoU;)OE?Wt3%vL1ZpeYh}&3^=4P!YzS<#e z-HHCEeKJ$p*FL|kyT7-ybpfK-K%W+UUJ1tu-v}oP7s5%xkHNt)o9ADG6Qq0>94}lC zrv$kh54c?>kMvKH2JC;gbx&}0-a5RU``eJq!mPy{0MqXEubDaa-V3iL_?6I(%L=jH zf=?`UC7ah5ZkqcM&YtH=9z?T-Q#fE2lc)6Xm)kQtx+iq)Y~9EC(BzaG{nhLZ9o-{Y zjo%4x8RQR71X{a+V`=6>SMsR&~5`-^tB|jG!-HUK28QAOQc`p?PnS*_ASF)L&dBBFN;7bD!cxc$9Y*7Ke8Lo?T zCHD((6KvAmvjWdgK#NT3+daVfut|Ym4%A-UPUpfVd+in|uYgTbeN&+PN7&?d-Q51Ud9bLs z^B9fUOS4zEBFOW&l8p(OrP~cx!@&Xd?qyjfpEIwlO!;is3_ z^UBNA@7CM0Oo}}sP(BS#Vma=jyt}FJAOe$Ij`lxbFX-ain+LnNXQRs%eX*2Ts6ycJ zAK{v79G=pTHzTuM$(;h_tKr6MSMrPie*>E|`@_I+F5=Z((RHrm_XAuF$CC>iN8{oh z+rVfnpJQi~VS9eKJMj#s)ZP&89wNN;t8jP!Hf-i^-F=(caV(wXxsqc8i(LwvM1EkP zvwy%QaSxcWHDMfj&zW<0p!@~coVA0Sc?dS=^PpSkm53X-)0Mm_P=7jXa_lz@o6UR^ zK@5WX0~I=)Ygs1gjt;EGRj_%{wk1%$6W$I_4e-eG{4e9;1AHHB4#L3+{Q{dqad5yn z=UbL}oA@{Cd(CE6BQU4r_W}(Zhl5`s*72s^zA)mK7#=L_viex9%g3}TzYwk;i>u`; zoag7liNAg{0rdI=c?e?4#u*P|bz?`1;gU<82Oft*LLGh{&X)#v!WFZf^8bVDPx?CW zwSO2+8{(9ogrhGq?DdbR>v-&eJ*IyK(AkQCqyKOk7zJmY=QMC(tNLkh?JTE!Hk{br z;XB}piyeLx-aZ_rf8PjSMNog0Q(-r}LI!XEj_l@?e+yTc1_IAF!BH1D+z-!?E&~__ zR}Zi}Mv1$*k2fX2>*iV>)4y*3QxU{Za2i+u&l}}%K78V8hfClJ{~r3Dx8bB|PI(o) zKh5Fy;It_Ye+HkNWO+To0USqAHPESW3O;(`xKR>c2jP4dm*bQVgo|Xrli>B4PWcsZ z_6&!Yz)MpcUIV9{<#hy=2$GTEd0xx+ugMY4tMlG&0$mWP<{Taj*X-U??$ z;*z~&1EO^2P$6HmcnW+#9La2$uXOW|@nm`M#GMF3zi_p z6bbY4KR7IJQ!Ijp7|U}VGdB)xG@O*&T%joqu9A6O2`5TOSp=t@@6<1Zv!wi4_=L>+ zHF#IEKlC1NI)ETV9OCzI-5jUECV2fMhr3S3|HzC-!`sDQrNF8h>F`mB0GaTy*g$(; zbngZP^CSd30PhrISqHB>*BQVjI9nX%8}LCG@(fL3*AE>(mpLVPdj(izu%WBUuI~{osU#$Kg8h zTN~k}Q)Q{%L=Yop{VBXn9MefS_F8A;XOPIJh@a^XdnA0v!|VDv^^@UH8SvF`+56e?a~W zYY>7e@s#JoX%fDtz=<-zbhu8;;d*$XDAV2WUa{9z@KLe%SKw{FEx^N?71)EIj-SRb zU|)*&1)L)4T?eN(+t=}?V{mkTXN9_4L(MB95yYFUx@;e%I5hV>De znE3Wq^*?~?MgR80JH>+k2}g?y_zj*H^p@6fZ&Q~v8X#WuFmuLYdcnoA+C$)4nS3IA zP;!~e;bT~wd6{LN&w@iGvfl~Ui2*$TM>h|Ec=0HLsA9(eHo~hVb=V2(3VjAQihlhA zJ|O%f946b}k>g;!ln;d~lLEVG;>8pMJ108ZI|ojk?C`yC?HGrjg%iahUxnAnitU3> zNCZ6$@0WBlWEy%Oa4|k7*b6}&F9lgapFs?VYb1B!_+UkhcLp#Mj*%su4;KgBIup7N z-Y)}u8jcp9^&VX7Z{P0(zd%qi$kBms;c%J34{(lHP{gJ9C{fCxaE6%prSL9s5p&@< z@mcr5jXswWaDrKQ@#x@6_>FaPRB}`o#6i= zsE`@f!&x$bpW&hrPJ`VpW6!f3j)gsv5%QQ3JQZ(7G^DgVjaICDr3vjyV(3^0^0H?o$aK4wwI}EKeFIRp+kRyrfnOERglAH%d!3i?M zi{XV*J|C`-E%6%e>pcH395=_|=V9+Iag}c&m?z22=ddNA`yX(=WLLk!>KQ{a2z*k% z2OKUd77u$Qx49q#{jU&TK8p%*Vgxye6Gxy+~$u4|5F$D1OilRnA-c+X+g%a&!pC(d%( z_s&5OCK>o$@Ih4?c&Ye>r{VR&)o{Ax8vEc_QR07@`jUIJy9%>AyLkZKrU3}zWed)R z!$X@ZG);%s%N8tvYeff&;3Dz;FPd`M!X0p_g!eDu)y*qF{BJ;T0J|JymY4+MH+cJa zX8@6ztjPC{5}pMgkc=nMl*{??N_e4oz6EfSOQQqroA|#LL87xHP21oYQIb8d zYWe5zLdieBhqGnI5myu4Bx4#5ub1$9J{&#CKLB6+zZ$_V@pN zQOcR{dYMoToG4p*hbi|tzCiphLy#rG<~g`d4ndpYd~vlO!J%TN-SX5$0JA--+U3gU9|pIUc;D=k9B7Wjgl^*JYTHf9yn5XHJmQb zSHZf)AHo|smo~@$Ul8n~R8IEi<|VUMj(8K73Fzp1a`HeVylRICi$f zFPieJCH}vPV7+*@U2voXi_Z*;Qhf;@l^pRXoG{37wI|>j8F0s0tblCM+3=8`ypCO8 zfZ(8vuhwWx#7UIbU!1`Kv5?m{;_bND6 z!f`IVQKH{+cs&_bC&pnitS3zQZ;lymnC(T2FK|ZqJ{78E0QGReg--b|@HWY4x@1#d z_OuUNDXw`OykBBRDjc2Tv^U#}pg~sP4)}m9>1sG!%y2V2KpfNSu4?;i*%WrSgKEX$HSy&t@^tux{=aEP2_rohEw=hvI(S!(lTbm&%C2Y44;;3%cH zX+^6FHXJKC;Y)C%#P_%0=Vb+U!!>=J0e%geul8JBt=`su;1rp0k82qip3l??w}}6v z5bSqG)HE5+5xbrPXAO27%L2Gg9MuZATB4&3S4rN#87>yjw-b&EuAqtk-y@z70rZ;`DTI7K$(3s~CsS_ctSir@PME|p_LyX$epj!w3S@3`c zw7-E33dTZXgl8f+DK;<|t{06M2YY14Q{c#813Z5zT$|x^Fau5#oxcOFe8&H~M_sKD z>ruEq+h5;jgp~-k%R8&>aI}f%F5|~StqGOlPWc9SqY3`>@ACq0 zAc%`~D(o^WT3-*>$N<~ih?ZaB)b9*izdPwgZ+M-UX*|4Bf_VzO4L@r7_YL3*1REu% z&4CYyU$_;n6|;N<-Y#bN0z6OL)H`sx#~I-N!AnIcPr^~>OA_8;0g=G36utoshSiKl z!UtsqGGR-U>}EJjW?TfTV}B6dDUSUq`1)TY3Ezw$UlOQYaHg2u-{DG8vfto2zc?vu zCnCjq(SaUt!P(9V#KSL0_HY5b-z$P?2sTQ>bOW3(GrR{*6RUUt))iW5>Wk-p23{?D zUIUwy)NHv)No!$mwV2hv5QK}Co`Mg^p7&UUQ;-3j4M&RWo(AiZXTnk985hF!l3jQg zq5rY6ClyqPmKkn_*Guqu2j14!S<+A8OsRhu-f7H=FB4mTfvw37cfW}(5l=hhCiGvI za3mGXXOx2tOfY=8)4)u)TDELHTr|M>c+Fx{j$i0X1IDl12}g`_o-cuu#Ht?iB8U~& zS_$*Jww~YsK7@0`)qVwss~N$OvZNhuW&p#S=ZC?3@dn+$BIDr`vO?Z01iAuq;dDtc zZ-N_Si3{Ola*kOE=SqVw!RsX-*bX0%V73n~xu$u5-ln4n!X=~o39gc0)Fzibm6KgJ zxI+9u44fw^?07g|I?OQT`1+o#fZ393V5_;D_&cqN=Jj$;XYOtki1I6?;WxOpBWWL`e4 ze-YL#t%g%1w*1YMC;999;{TTjY9xU;3TJn78vNdr2NDKjR&5uvrxTp=7u+$Ltl(a_Gpj-V zLIT4<4Bjb@sNE8Rk*_}XZ)OyQV4b8?!{Gusw|L;3L6)bC28|L;gO5rMcmrG{uNUrs zGbC0#05=Mk!)A;4tfBG6{GI{=ez@-eeFLh66PmNs<4s?~@e-Vl!KwY6`oF;&CCfb{ zk6v z>SBf8#!O{t`oh_wJj3A-v4+WT@b`2~$J5~wvF8QwLh0yE_@sEy<#0wY%=_N@BM9|4 zBddn@N>uwRTqHqdFPtLw{SUZNR?2ldVOh4~OgLAP(>S;x5Y&7QXFP&=;odMf$x)Vg2Hoz&PobnntOw#iYU{7yfx!>V@iy%qNsR>>yx!f7~Y=J1x z5V%On&w;ne7Nx@}lJ75ok4jj-7jE!n%RT+tS%x4q#~JZU_T4Fbgz{~~Ib!yo!*P;R zHo%tT57r&@Bf&8eJ|R0X2F?y{Jw|mYoFqGU4P5J#dz+RZ$Prz<53Z8nRt{%Mj`|`T zFDv~DZ1L+~{Ohyg_u!MVFbCl6vh&9c4-BpY(fAYsk0d;u?rdp=QEYC286L$Io>h_{22ulauy0z)|7` zPQmr!7$fgOb!5wi!nLB?6XERvYxWt(EChQeIvw5&m;0loZ%H43Pl)b41M8>WUV{C9 zE5W}7@4&?};{z~1lko(%=sP&mAItp45weu%nCMi9g0p1L#=!g8v#zvYg5h|$QdaCD z_^2ph4jd*&#oOTRGNJq6jlR$lh{iSo^}Nr)sj{Rs<^fruZ{Y^wM6ZQ;B4`f8{iyCfc^&aVK9%ym(jVS1?YcGvLonFNU)HkxF@_#2Gk!; zINzy%4!m@V!!zL=nei=fgjmVlaFs;$HH9pZ?nwm|a^+<97F-lOIWgkT;GL3Z)WI3z zCw?>KqUBwekw>+61{eeHy4>NB@uq?#sT1H>+4E_p93=|cai%>!ji@~zampmCc_r_F zcS(Y_9L|u5KVs^OH9rGyyVz=e{&hG)+|PUPIx&{RaHO*i-ljJ9vWK$u-Qk6@5M$s2 z5~U}?yDoG(%z$;_GvPcruq`p=;*Rcz%h!wx4A|?oRv}m?{^(6OT0+J?xS+q&;c-|y z{0Xj=3A8I>r$rU}!bx&C7zf9O2HG#r$oGM%JH5?b%QeV_=X!U?~9|t-K5xwDEvZo_p{k^9Yn4cjx>%a^+M2zV+_^1qc z6>JUg)%Qp3O$gSD!`urm_2;}k^F9op5M%0iKQnTasHqp6DgG)3UN1T_8BP}^z7me| z4H&C6{$oCZlf#`EEQcHAP0=dD_xY8^ynZZ)_sf=Sg`@g9&;Je9E!Yd65Px+X&d8=d z{rd)BJwT==GwKHKz1C@PD4Z`!dI20GGrAbg5PvrVwj|~)gsa7&u7J$|P5-_#^hyL- zvH~06bz*j}!l6>W7v4V88Q@{KShlDM))fdZCTdGE9Ro)Um&-- z!0W`*eFblq&#C+yPLlVtomUXG)3Tfu7=<8KI+zM4N-UTG9}`Ej2u=}R3P(xiwh~?> zxorhpBm=Bof&Pa}jQW5I*|O(f!N)w#0RIigh?RssNPTg=XTnR*ck0K$;WD6e;EY*L z`Na>S{}FQ>K^7JE_jfoCZWOb61Wu9xJOP)Bj%sw4EgEdcW~4Yr~O~N2rAk- zg0K>_T$Vf1CrV0tBU~UJ{C2oltoK1URuaGs z@M?L($(VSEnHbFv|C({Bh8#65&ROpGHT?9RB1`{@TS6jeGKJ}Ig<9$qTeKND`0 z`t#vl&945N{E}SF{9E0_9^gqGo&;Ly{111EudxRN^cjy5} z%0>==i-Z2uq#5TRsF49ohfA(>wqy~^r+J$l@*Qx9__K%L1aY_(@I1)`UxlmWQ|BMR zxq&ShVm$ai5#)(+HNkZf!@8B)-%oe<^G27lwEpzK=lO@j`NHSG@nXf7z)9VliCh6& z$qp}u8^!O<*^g0*t`$+9;*7)Fvk7PKy-EB`|9qIm9~;9a6@?N_lS;&!6oJkf=Cc;O|^4AbC)vSK-~N33ZHTtC8J z-Ac&J_Uj>JW^1cmMizD6#M@uaI9Zr?>rCk}3Lv*Awye^=eMmMA3gZd)r90Z}V zXIH^RVxBj{72?T@;5ZpT6f7=_N@}g9f_T6);Ubw) zf4E$t|0sC7oaxixrJ`iB;W|0!<-?w!e_#b3fPFe+QbO}%ore&_No=b$4_x8QU>lq! zd-y(F98@A2{6Bc7Eb%G$ZHa}Uk8&yy7v2wEIM?#nAv4{P-Z7A>@C3M2PEj*VZ3!s1 zzz3!G`{4vRRIP)nWO?3zw~2S#18;QR!FZbvAPAB5`wotf>9l(c79$@E^GlW})Tbev#8)9h@l|)yc-DN^G2f z@r$=2s7ZAiSOb?%aQJ0-wP^AC@CljW7jT2b?!$1&h0gQ;g(HO9tYyOR)%5QhzyJg> z{hS9z!lg+LPlhuGIezvEI8qYiJK%$kT{jimi)NFLK99J956gtmO;-}rx8bF7R{GR5 zBfmCr0L~CqIShx%QRXEH>j(;B#5eqU<~94+4aBe<@aaW5wkRLKj*&g*a|SYuCGG{({su9l2C9xhFG zI-G1+R%99+B{{|%SXXc%Tola0jC)yuAYVLs1-w)I*ETp*^GW!a?Bsr!-)wANfq%n^ z5`nuvfjhc?oUhy@%+YWHf0T&%g&IB)<&L69>Bo-Z{isu_JJT=uiW!O4#iwvMkx+ z!EhWpw%@oHqhx0z2p9J;0WOx~ZaSPRGrR^)yVe=N61c?-;Zj+_m2iVN>{sBhx$?uR zod^=7gKyx4ayB?-9tb3+X2sf65RxR-KNAj}C;z(-|sQ{cq@PWenYL)QK#xL{e(V}x1v!ByfA zo`vH@yZ!<>psb<+dim2LkHt%a~>(2+wjJxDAe!ANa2@4~R>vfGflSyaq26gWV5T4shE01}>Gb z_Y)jg0k1D%ajnOxO9!!Vk-QK~fvYDt4bFrMdvKm?!QbI|vc$i`{DOV+mi2y)1V(h`Jb1ONP#Qe1IZZ#_G!srU z1EhbSz2qUNl_>BKoHyL*U^`qQ1Nay|A;+l0uq6ijGaM>9(&c${WU#XWQE<*gacLtE zWaHy##y6meaB4e;FNY&#ORk0UuZZJV3F1fpsqm>-pI>X^&$8oSn8o0pjdn12u zcB(y|n|D5?F-cBqcf-*#oQL7PDNgxQuzskw5)PAhBRk+4(a8O9n!GLg54_EJ6X0!X z_aa_FI*5k%idQ)YZV(Nc2A9ivUjsXzVd1B1aHa&oyWwqOW9#6WfE)AuZs9i5fXA8P zSMX{HdJS-q#1z*{IBKaM1*gddkAzi&#=_OvPWu^fd@y%4x#?U4sgl?%hI8d0SO`am zia!k3^>-TF0&5iC35ST=+6TwUmi`;wA211@BX@7Wsvii3BA3+-u84Lz7zP)RN%ceJ zOwb$y*GLo}568;{ron0A7O#W(!@$iNdoNtz_e%cUv!yDlYdHxaDGu+D$53ah#>7W1(m$UY2INEY0`=9S*ZGfXB z+t_W&#m#*VhlyMJFC5cH9J%{1r2B214kO@5F}q=~CCTw5I9L4aR8wEJBojU;e&A*} zRnF>b;Jy5AxyL_)<4qe7oR9&$1#grYeF`6t6V|uzdRd`gU~B813BmS4UZw-_BmLlF z8BhXT>J_a|MX*%W*$rglEkqn4gc87OL`A|4ltYBOf`ky6s zJ(&u5?VS!Th3DaTf*oeUb>hk9!8M{2_rvA#&TbW)E3xDSI9*QdZ&!6;PeiNt*|To( z{ngDnQ&XbM&v2-WwEbrGMx1&ioGoiT3eJjhMt&~5OOz!YP7$A;4Oa%ks0j);BZ!j< z_rRIGodzF;^$*6q4ChFc-v#et>4FEHPv8>CgBsvyS=wLWhJXX|1%>ceENh~q6;W_w zaD)sn9vLV&>A4~Wd<-lq@iR^76tiL`u z0X{L?>G)DOFV^Af;Qbdmd^?;go~{Tkm#u!plsl&cZ_`T%^zSo$2uDja`VQ8WI0+Ys zV+nti`h%ST^oA28Nk0eX&&W30c?z80Y}dR$%tDYQ%5@{$DCT!3oFJa*5x9Wx#zOj{ z+q3ZMDGu+3<0agF4e#=2B)G7t>tt`82<2abV`{187lfD!#63&5(MS)ktIZiI$^c=ii-dDY2 zo}U$H&p5SD5TwW|{Q#HCTi4KSOiyC;Fu1t+XVu4>#=_gBgG*r5yL7l({C_6AH=yV~ zr#2Tsg=D6;!3}+!8Q%xji(XW~Mg5%eDp)&w2QC)9I|Qc&b9*NIZ#c(qnSQ6{dYw2T zGw1_ni_;ngH;Tff!ug_SGvM?&&VUxd+eOg};G>QLG*!aozAzGSYOf-w^EeIE!nv{{ zhhd%JKjAF-S;Z;%m;|zJZ?MD?m!jdEpyjXvBj6;nBJ}Swt@99Q2cA}ZA)MN5`^TFW zz*mWiFNO1iN<{~&;56BS4RDF9(7W(n+`8%CH-N7Z#7LQi^Jh!+0*mjItf%)!PT+?`S5nhRqlf;<@B=# zj`7MKZbDEYd-e_-En2)APL&yb0>?`I!*GmPNuwzjv+MX4?p~BS2KE+6`ZgMYF7ZTo zy_C;_`D10xo^XL-`AF_PaQb+sd@Yj<;qf|<<^H08D`zYPwP6?y>XZw`AbzRU_$!V&WPX4q=B z3bLrT5tPUteFxXbdF>>e(EI@ZkAfX&vG|2+AM|3?-t zAlNTv_zs-lKlS)VydMr5U^O@RZ}_OVV%J|OFCXVAahXGp%jyirO6uMVPLVUpdGI{n zmV5n6l7^r`a=L5bByk*fzydgI{rhJ4 zJA#8^X5Dwk8^tWg!?hCqE`hhpS>XnFy(sOy@G)`a&%qX+(Dd&cz1B_+Nw|L|%iefj3Gl*aTM( zbsGFDe3|U=J~&4Fz)?6!Tdsd$7*INpy`j5s91_p_zyF z^6^gncXy%x#j-@-QK4GwwCx9+bR?cf!TDD?4V(qXXFGf@93m??6|N9Zcs!aqZ^H}43O<5kWzWBbqr_@{hvQ`e9d~n-6RYs{LvU0a zMLe9F;tb$oI7(J%7JN`nK8xT6S>pQ*%VBdRTp=mj<8X+h^xmd75tND@?|~1>lKmUb zluuLq4#!D~*6Bm`RPu-6aE-X?@rEUboCQZUw@>_EieRsV*GJ$?S<>g>owBFf;nm^^ zYvB^&c@Or~ym0;ku9E@%6FwlG@;A6P$maatxt6yi?VTk*3qFCJ_Y5>R8jitNgFdCX z9L^Tkd=s1`o^37MC|k6_JTL!?vOREWKqq|X|1S}!-5!Mx%8InvgH?#q^n|s+QE<38 zrVHSFaZKrOSU+b1H^4dm@a{kV7a&NH=v5A9O7wdX&XW9KE4)nxxDzfFEBOre$n*b% zcZpy415TV|1{8?@JwBpCv4WxS3HhPHSXkdJj)$|wmtSsJ%yN!-UaaO8c)xhcBDmC8 zO>AIKD-djyJiZE!!FA4}+$h!ScHiZ^IeQneN7DN*;NrH<%Kic$l*Fj>$4uPgln;Yz zB?OFzx5+|Hh4aP3-wa1N3+-*X8$pEV>SJ&zA29CAI0w2rTTj6AqAqZ-o!b z^UL7sRA&G$!p*i}K4A2k3g9>4FxmPq;A|Pdf8cGB__W!F`*0j!Q#UwA9OMAFxkK7N z8x9Ga+I`k~5rQ;v@w4HR@@Ay~?k68OdKgZX8CSs7^3t{%4wcsoHSl^dmapK-Y~PCb zt@RfKd7@OEKOqm1Eg1?&UFKDPGqJ+;F z9_Zwz8%%vM!)jRD+X<(7MavH%Xv9MYqx5&J9{3q9mlf&sIZG(6btLR@Tx-(=xKWn; zGB{JV_(s^fP-4=p2x=vFdB{T>>wa4yMDpq;udZ35rYLOoi|(%wxBy)Bf)79yfE8&evB8vQQ4CexJ2yy za(JG{c_7Es7qeRk=SX&61ZN7{@IgsEpMiD7ye}ar6|>m}M~Nrf18}|2qU}E5~_u1(fU$I7Na<_b;*2xlRW|;ia;~N$^5B5M2t_ zOT5p9vn5*I4yOhkT@1eeQ3UJcJ;T#*L%h@BR(sDXGPMs7^FMoO_NWKpd7_)YzdoUOf0w;;$E>I~o>c-v@)SHPvR)oWnw;0d@= zI;euvWoKW5Ka#}hJ-DWMC)uew1SOK%xero77OFFxATeqX%>PHedBw)UJH>cXO?~;( z_&37)uW{-xg&Tu8yg65`KoBBP`2~2d=w1yR-^Xd-0KD{ChrfeY3;z!1NL~~EHMSrF zjD*7i#@W`S3B&FGtakSaPZ)d7xj8v=#*RMsoH;pj6Gv~2eAGSB)$zvbGIM5JHs-3F zNq5>gkGaRFVCyrFx&PiqCHAVd?gcFbU%%?^Z9n#;ySshPXf2JgYu35X`?K`p&F%sAp6A@1|6KII6YjAsq`u)De!@Mdg*5z0_na1j z;4Cg`A^q-2cS#FD;ZyEQ;>V88yK{a<>g|bRm&_g&fBpQ$i*BE@_`2D-*6hot-8OZ} z*xA$aCN0rrrMDh-*9y0%MO#@F?)(;l()I30`fW@#Ms+jINfL>DqAU9a;1CPpC5jwbv56#yS>inti!S>m&oUT2# z>@@rG#Vdc-s2F?L7Wd%RBN}Xbx48TMSz5cr-LJLO=ZPZh5!I(tHk?$1UEaC~k8+`X z{x)}y?sIVnt!L{iP2cJc3qM^^q`l?UR;tJYx3n15)vvh|TL`Ez%HH!@s|^WCd)V~W z^Ry52u(xeJ-BXPW-pruo8u=RZ713aq?$@1ua-(djT-c(!Ctr6Dv6sBwO2?75G?4H& z-2?6Pzq#Am{oZg7Z_xrpvG&r_O20gfv}HW;J^a|4r)%=WX{DEMKV99b(@HzPb-KC> zTS*^#oAK}dv#`ZLeU8N*4SNDA?3+SGVA_(r->HO?v-yEv#)V-TQv4p$1hyzV)gG4C0l)wQ4u`KwG=ZuK!uI z@P8M5@V|?OeDFUTy!U?>)&1|H@w?qaTFi?m)iPBhx(#blqkgx$EKU-s+2gKTk~@3e zoQ1w1lDTBj=`*cuAGT`v*ALxOw!UBM?&Jy?6(65qU$fC2v-Q*-_h?tggh@AEF>cP?>YOLT6Z^l^oQ==9Rj1cYPNmZ%kKX6##(n* zQ*ZRvBYWNd8QkUCn{Uorc=Mc`>lU9qZQ|6)cFm|ZgSVde*}b7{M>CB%xw*4*u3NJ8 ziAMJqw|2UI;hZJ&&PIl4?$*VN+l+A86-(OWyWX~Y<+Zugwa1>H*XAm(+luBtD=9kM z3jROh_=fX^?_Z9L+&Pi@u4q1=v(4=el=yGKdN&2?m{L?_!R@@fr?(Kb1=DjUezyPF0>G|;R`)xPZz1Ci@_uJYW zu=s~QZLNJuqQ);@7UY<(cqhu~>BR8waK{Blg>gAfqPgORI!@fsEoXn)_ob+QXC4=J zoRfGARqhRP68msJz4YecQ6YX9{n8`D_qts@OK%%FZA7q>cpVka=Q3@64lm^XCRaZn zmwr6*G*^$(<3`PL^?zy3s0l9Dv85}bm-Xx6Brc)CRc^;Qh5Lpcr9DSa2<_=4UIA5e zxk^78J*aff=m{YR=ONfu`rMd7rB94LzMo(49zrv^ojpt69X-=?jFWgJG>{f!xK9gp z60hYxoU51c7{2ep6%*nl&gVXuaT~6HFXw&^m*L=Eo$dAI`(wGT^)svH;}-Z*zIXYb zcenH^T{UKit9R*BV`eeQ@5W5#-;rZ4L2}#J%lY@Kv4ba7-s(!`At%^>HQwS%PQU4f znK?Jl$jRZ~^}U_M1(fv;35>dQe9Qz_Xz84oS^WEG%t@|3r60x&c31zY^qZKIPVUn= zi8Fa*D%UtaryRki&(8AFv&W4cO|7{|w)uz7w@xUR8C1E)m0WhGD>=>c(nI6+yZZlr zz@@7uJm4AZB(l!V{r-E2|Gvh5U+cd=Seo0t6ezYH$j6g$=ot0#}W$=&REga7^!cQf3-aW{Lr$AACQFaL-CzR!Pe z^53%_ZJ2KM`)kH%Vd7Z+9em>0P&V3_=Yw25 zUb^7KSwmiQ1-nYiW)J9xg}=h}0oNB?KOI;4#_S(^pWq~>a%FLq{&w~Wr9YlMf@@^2 zF8NEt&Kc$ML^z3$aXrOV#q|c)POeY5_Hq5lb(pK?ASW@JYZ6xy*M(eJTsbV`P2BI~ zx|eG;R|VH5u4=C3l)u4!JJ)Ai`?$X6`i(1ul^fQde};0!a82eqgKIWd2G_#DrC+5z z`~Ue6UmA1XJ;9#x<*wu-uc|+g9a-o95fA-c=k?P0mV>W^zU0c97wkHrbV2q&qXOv_ z4b!tuD7`cLG*`dUSF?w^!b?BTp4n%BlNeeyEy*czqKaLm5A!`|&^y!wJWY#|(55+we;} zxOPT5iLG4m-f2lb-!zo=xnj7IxTcP9M%-0@#n$>OTx$or3-9#K&3B#Dg8{S*@ZO*A z8a1FlqvbMVOS{rIW0&)IfA72bF7MFeQNtTu$pg7B4tEmsxtggK%>B^u-rEXX!(Bn% z^#!g`Bf^}-nzLNVt!KEBvk7@gr@NAGMz(of!}Kz)@V?%!Ozm**Zw0PpuH(Hot#Cc; z3g3F%U9Pb%*9qRKg|1sYSlvy8)aHIpQgEC*D}^hGi<#G@jCPVXGUmi2Cu=_S_G8ES z7}O!^E`_URx{|BbH%xD*P81tcj4Y39EmwA`E4i_slNipZ5fqsQQ+%UZ++m&3xl2vmByS&@)b&c{+)Sv56jw|^dGK5rC(Wq*sxudpA%&PL} z4)o?@GJ@rB8CMB?KFxKOnFZW|ZeNn(I4>~6yXbN+_j*_C_%HSC3Y>_FDSwU;XbRf*wB;MxqoY-D-V zO>!mI@qOZLuH-lxkL5arEog%ixF>S$*s&vY+`I?gikbI-J2k=?l}Ik=nBK_1^6ig*RIx5>&bpqJ zAvbMv7rfl``Z0|m&Zd8bR#vptg$@brQ}ATdb3q^UMmX`4x3 zVbI#9(1`_WsnET6`T<|@C3l(PyKJ#Db&*pvYW2heC1qiA{_S49t*N9aY))CvO1J5| z@b`V^cI(^i>-#!?-|G+5W_~h=P`;*WHbt5T|I-l@UXV3RWI0x^d;c$N$q5``TOE56h$)2;Iz{>R^M+V_4_L6iS+-@ppO_OJYC&9;Pf+g5H_vn_1h=!IQ)n6@qmWzv z>^=5p*U0lL_OHBP#WrWxxR=3UR$PqsNL6XT^-*(1cN*DM`@2l6(?G`ys*z(_p9qRSE={3 zcGr}Mn*C+2{FN)B_;ytyr~k^~U|{V?u+5wnpw*dE=boU*G+qBXs+=hxZo@-2eKqET6Hu z^8K-|Z7bhju*&N?>^jNa(CD3X*fq+v$~)(6+{P z28ruDZ^Un|$qBRfcN%pn%l3Pt-WJ40yH_XgU$bcGn#ZfxuU_+>vu4q?tEcbZ`nN-_ zUtRsz?Aunl@8fO31#66|o89zAx!q+US1ik!JZahFxUCIt_Y9YZc#J=Af_z6-$4Mun z>11;RfL!H!Yfqy3$mQ~4Qi zjPS*9tnf8(f^Y$xDEtr{n2c#}6C7{Ljt3x~tFDnLXYA z2)>}Va}&qFw7WVNlhd|y7rd5mSWGi6X7oB-$T8Tw#BkI9k8su!SMn&DHJrjRvxrP( zKxeskS#S6Betn#~IVGB`a&u=j@7ulICwclfhjH>5WDt+|Tf2(GY35Q_@-Ke=95$Kf zct4L|w@v10HpTRI9h};W;LWZY{x_U)9b3y*8J>+cge`L=Kj|OcGjIqw*em9F4;4lk z^%(3*Hq$c?c;PDeeE$PUG;ET#NI%~IH%7RUhxxe!HVN-I|MSyPCzJdh@8{*PNrGSS z*Dr^QY2T>DOw-{nM*kwa7RI>){o6d$Ht-uCIbANTY{eVii3wQ(N#d8gR<98S5+mAueDyb0u< zyU3F_vx}ygtKmJx(~{Qv%3aO|crE4T&6FwM563WlzczSB9p~HD;1f^+&P0Jh$H68w zU*mu58rY=c6aBm%HtF~*KYs$7lswkY(E~9OQsox^FfWHqD%{^+UICj7{d!Yww)IB@ zCR^<9?i^t2YxRPZ`-?R?bD-|UZY-2%(AhVc$&p)0wMzkDs+p5;oO>*udvlgz*G z?{@ZZtoaI8^1FVnhS{t{^Oa|xT)#@j33RXN1HjC4HDx=7CCCB)8rvx_Vh>`yCf5PV6;P)ZU^l2C!?*sqp zFJB3pBkntXeg`&3;t7V$_D@XUeI0^;eq05c_Xh!|^%!jO{^$G+d;y!>|5ZOHoq{_2 ziGz)w?}yD{HQ4&fw?59y7yg1m=ZdoWH`cr_lf$m~kF#4(+}X zQ@F+{2O&c1}ldU@DHDuP_>z3n%>ip)Y6|z7~!y zotD&x^8P&Gfs4<#_1D0`A(kJ3mrHxE!4(T_`7ZcCn`z(E+2P+2q>Zr^zK5e`TMjv$ zEntt$0Q<87L*S_2>;Plnd8gX?ryo&&CcI<5EuRA?90Sw8Z^k(YD$ceQis9M`mN&x( zF0s4~UL^zA2}ks|<)6Y;^88P55t*`@bn`yZjt{Q3i zAl!WDAbZ$3fn)eCHrtjDf(vE9r@|XEZTZ=7)?AMjT!SDl#c~OpHqP>6aAKn6S72U{ zcTK>TW5rqCOL^u{%Pp{Zbru-#aeNo=nPUaR5EL`Rz!r>wGuy3_O@#~3vGvb|lhQ3; z56?T@auJ*=I`fDrr+uQ0Z;PLTJ@G52aqBdzI=+jN4tK#}Vio^}6BgJG4#T0M)uA&` zQdxnKFt5C+)wxB};I#>sFPwq?SIC~{QlU7~R=5M6H{9}ixJ738JX|r!mT!X_FSGo2 zc*%v9zkv&73lGomuqUIOB$EIct?!jgq+MlKWE7ktN*4<+7cD*m-s5v5oim&R^ObJ? z@i{Jda}Osezy)yCG|P{85ah;N!4_B@&8u*TIHC_s{flk=eMi*)8jh6#{SV%Axvk%K zCRVf1@&!W>6rE%Rli(V$f|+oeY{^A%x$NmeI72$T(tGg`_i)cL#95*mg>aFm(t0>p zbhirLE8W(?k+F7Ijc~;j%fG?#VoZHgD7WnC7>=Md+DW>Cr8he}4o*zA4a|hAWL{}- zf`pW8IPElBe>pr)%Gbk(WZuug4PE}wbFgD4f?#ooU&D=yY=b|+8)sS$ITQCIGad)m zioZGyR?V0NH%kOKA8v{9x91^fE*^Q?BEL&a(=ZNv* zOPyRZ0Q&b0pb9~Z4B$Oe0e@yvOJfxu!)wLt+Tm&$(BM>7P#owaI8CDZ8E}FOa2DK% zm6-m01DKCssVG@4yi4qOCEP52;2F3^wxAwvtJ(Kx;2&ahFe+H*_nccyTCOB%i zq!m49(}CzE5{|9(Uz=1jkJsmyM zXn+thz|0q834@DdrH8^hWa1~mdnLV@4Yy!y<{h1R{vtR;V*QP9qZrSfa8%a-2qDD? zB8#l@Z-CcIHt`y)E3^Y{7uEU_-Xr`C94b5S<^*^^%7?*~iT<55A><4M^)u|&X2Pjw zTD}e5G1>BZI6P1_qh{)2w9CFg|1)tP_7 z2{OQ+;bzgHe&ni~7u)tGz{O^T0wIJGhLa&dGXqYL8D0hFNmRZA<`Y<5OL{+?B3tq( zTs_y8zW|qrx7Y@!V;2}6%9AY(mKxX_7oG0hHPv9_#c?aQW$%A@ah?PhP834Ck zCLv@zg6%)qyk$CECVM&u4weC44=+ixD^LK($O=3Pr;84~2xmwl_BVLBhp0OqvoKNj zI|SL1%l5el$C79}h=Jo}hBM%$Ql14@$d=q z89WH<04m`Wd430+D_it693^J{TL${SN78_v7qg_I<-_0g`b7fCCzAnV?>D?O?^o_4#RCH zb`8MO5pfAhBU>;U4h!k3&~YxjQMO<)yhC*0cDPV{e}yTREv$h{B)oqFukBg^;{U%8 z?7=QanI$HH_yMjx-438{CM)utRl+EEk7PV2nQ}Q&rol_a^DTxGB_FsM&WrN5Z{q)I z1POLYI$nUIMM>&m)$$MErILDn4QI)WdtXX)lZ$R5f8ZL zG5}G^FnF_cI2O(jUp~{6i&9<)Zhdt|FH-X#5X6wwW9S)c@twk&~`ja zXq5CC%9o23+zdwu-v_75^H0ON#c#ozIg)n8|L+j&me&X&^Eg7DZTENrTrD#)Ke)-0 zV3Y;#m5k>mcHBq^X&|A6C1 zSy%fXxK0M_p3e%%7LA6-{MTdcdJ2NQa!5{w10N|cdwL!mELwgwyjHjX4wYC?0qW73CD@6Z!ph$M63USpfJnM=to#*+;#-_UVvv4$1(`!a{*l|G6imv*pLQ?N_f5u z_LO(n705wQXS9+pGh+{&FII6sTq#Ss362xq1}BN{-vje20$uGj!?|LnKP*813#G%u zR45lu7O{{Ax*J_#OC=m}xWICXUPVUj&sh!k|Tt<4B%90M6@SM?4-5mXpjGaFN*g zeDgd@ZN7{SEroS}H^KQh|aSj?7m4S@I`iy&LY*F6dMMc51+R(CG%s_SU6nDQ{gJfs50O}(Wz_Td45;# zi~n~b&^@{rPLOw5rEs?F@x~+SSDX6cCpg;bI*fhdfGN6N|Tw+O&D|ksY!4Bv|H?Mfi8yD}yDehxDCsAA}8QV-) zHzXBq8);SYVz^FD_gBLWm)Yj;gmY$Du7FcygWiCpeUI}tf=cmwU%@4EZ1@(=J>53g z0S8NP?scW(BuR%O;CL~*Q{f6(kF(*NE}Osz=OSp3_ZK(8yCquR3u{KV5f1Lk;tzJb z3G?2pYsWu;WrohDaD!~XcW_o978)ZwjG#^OpU|r~IEmXC1}BLIOn^DMbaglt=D69# zGvG9_fh@T4iOvu6^mBrpJorFXXMLX$K7gQB-dSycYsT0HYvE>D(!aqGskXcsE)jN? zkiLjx?*}KGW$TZD@eF3bok@5Cf_B-0v*A{W-j~3RoC;RJagwAz1gA*S{Uls0Tlg-V zj!_Px8I!{P4KDK4_Z#6M1mUtry{^V_hS(9u!*$V?li=OQSe^@q$T{a)I5WzY-wkh; zV80fQjiH?WeO}-RBM>cr1Fn-9eF?{3WE*UTox?VrI0SQYNJ8ps@7Qbb0usE3!8PJX zC&HUglO+5M1bf5}oDc61GrStE6|*aVmx!BM2d5|50lWm~i4uMcN77+nfKBkcvm^=c zK%i#R>soSHS%FEgBT6`k~* zllxc%$gR_i+A+w!LGCBu`7W5CQKNj)6COBte>iV7bgN6HXJWSOn_|U2E!# z=g))J%AT)>O$ut3+@zpS!PR0_Z^NE2(bC-r_Q;-p4=2iiLat)~;;Lg{UGhnAqc-ZD0yqEqgW-E*xP$VUuRc@eBRT6607h;P8{}^H;)&VpTbC zjCj@+9t8ZxZxa9d?Dk2RPbPHn%W#;Q5zOx%b(J^4)#EJ(-(Xs@pHCSGACeWC3abuz zPDhX~DP}6%DocDB+#=_eYvCMeun^vOnH|7G@E!?X&%(tEZ24Pom~}*+j-3dqB=~#@ z*UL$+1+EZ3@Ee>f3Dv+HtUx+E5zfWe4`dn4mL$NAl+TCPcC9G!e;I;MsjwO@li=|p z+$aNRfZJrn_QLgcMjhY4)v|&?HxeD?F z1gqn3qC;8op>UhThEw5UNpaKQq|UQ)XZ*hc!7f?CTj6+7vIpUCd585loEum|2Dk}6 zhS|Ef`Z^9yjil^ z18}7%u`8D?6IVS5Rvj5*m=!hs`;;giL9Hy&*>JOH@g;Djq+mD0`EoAGgR>+FErmBr zdoRQ0%WRX`qU|uhROv}FLZf8=Mo>S`&aeYc6(v0GX38a4je(tD^K|F)li^*m=QCkG zQkoPf&w!W8mRt!(&z1OpGfSvT^d~Af{$D-m&##`8z{R43kHDKH+`bL(NVXk*3P+Yq zOERCiH{sX(6^Fp@`Sj=ae7>PP!y`-fKLlB#R6TBCg7QjfI2`!JA~T>kxLEA`On9jb zAOmg_&w4qWA@M&K_Jkza5#Ntsmqfcq;6e#Do8T0&`#QK?Qm`-J8rh;&I7gD!p0^TP zB-jnS75!f#zI-AT5+$0=gdN$li{OwG?SOBD(*&Q-j`GO zXiOR6Y;gxwaIEB-+hIrYiqGL@37S8^hh!)E{0Y^Ptse&`%FZRgJ8Ze9<6H#UqKgaR zDhYZw!I_e~-UY|WN*BX{Us^NP{TSRP3-cmeD?9(b;gNxLAS&-hkR*xFzhJe_pWrxg zc;UC9i=sLw!%6<%!ZXG)6Hbu{WWtAH105dhxDl=n{6yUR(!$*c+U=5bJPgNOZXeie z8u+h0Pi%)H#XanX4~WD30WOp+bKTC8i*65rYyH;ji^|gw?3!UaoDG*r6kP-#65Y## z^@DGPaOYof=yZ_l;3Apvi*S`>ZuM|xXN>EN%3mSKORxj@37#i=)+dh+*|UCZwF!~~ z;YwMtQE;;;;TdqKD1SO!D-&7>Z}x>8e^kB^f$q5nPL(BHZyt~pdIfG3|Mf0xv-@M5 zk6~@E30|IMCwK_nVPaMwD)+eqvlFEm4u?u2bUM6OtRNMRk{M>gb@OcpH^MQb6@eAI z18$bQXEnTw74M458xTZ`e|ip%6^HjGyhF79D|ntn?St@U*+Tc7Bu?TW`@tTo^dVevsYK?00TqRL`SpiF+dvY5Ua=MOe2RpoQp`55*fa@jCcoohNKk;ZR(3P=fO3SQI^8t;(i{3%f(n~;Rw49o{ldO@TaP}2JkJsR2HJoU7QLe zN)Le>&a(ASgmvL3!MW3H`MIWC+))->wr-lgKabm4ilAKl(SvZ5gp6n5{Nc92_hIdD zC%i)@&9aIAkzeNlV+5f6M0r%8x71UJZ@9&*=p=Pa}XjE958n9|{98Sqlr z8R4tn8MW_4uu&Z5CYZnf(xnr%@F6j#ui;dyL>)iEnc}a0gExwf48Mn*Ta@@DIMO#@ ztk!7#OayHcY=@V_?eYd`so}djmByTlZi08qmaK*&huY^Khjj}!!H2|Oy$@%YC{F*r z0ep@iPG-~s?^pgHw!vDsNVe$ju&%&2aEm0)aPy!TRDeg&KqW%*7xQ&#XnxK&nUGh9BxV;}enf<1D+sD~3|#{Y)- zOJH3CYJ+u#u9ZY}v4R0`u&l^LxJr~f6>jl}3!jgm4zmaZ&8y&8S+ZN5)0zt7I8GQ;1uCGaAbmQ?^^ge z$!%|g3uS=!uR{OBBt~tZLYD0L%WzAQZSWm9TC8NhsV}a#73TNCySC^zI7|lA_dcv- zzAcZw5B(2cWCc^HuzR@W^Wb(dtE=He8NjV@ndrzG*byCi2F{S5D%Qi1l3IS`K~T}d zcKA?hf^d?xXiFj@J6^rT=}9x1Qqgj;9*!l8}Krm zExvpwTrZhkGkidfgg?XaQopbF>kQJ2iGOBcdfIuO3MWWPn*!&H2fqL=66?JJj*$fL zZg{P{d%DZByE`wTkpVMbhr+|4vE4m;ZV`_ayU^Mco){s)9-}MA10h(222QQgqMhS_#Tds zjrvA@&LPh2zAEN5hHz?L;QR zPO{}RxLy3-X}d8>(X|DXr`U0LI+h~XC7ydV%*U9MXx2BN$KWv0@fvuy_`&z#7MbY3 z;Rf;iKf`=}v#Y)StJ%7)H~G9sibRko$~74-7OOr3&XgBTbKrRSApZ@pRlYus2M)0_ z?066kmkB)&*95G~=*rs&o)OQz6K)V?+XpWfxAPO6E4py(8m#FYJHs*XURkj-;3To8 zbKwIMJL~&W(kl_fO0-`Ehl=u+!PVl3pM|3&mVO4OO8U|WM~jX$!{vVEG`jfDI|b$!*1MGMLO3blA6S7!uuo@9N@#xGa}|PEiES&) z0~gr_*1&19hZS&9K#6GZCAeOecsKlp#KQe>v$*g!cYp4n8E$ zZ-N`eM5+z@x5(!Nb|Pqy$zGaw*WNRbf9Rv=e^Crf}!eO$4r^3s{ zq-MgYonEW+TsjxQW(j%M!Wpup`Ead#qUm0^O#E~S94k6d0dE!)dKQioC4LX?^xGJr zvB6Ifw8$QR1M?S#yMFoRAiR96-NT@DXt@laKU{R0Egu1EU>FUD$d*lm`2}BlEGC2gVLm3_#TD=&nc)_=RbqE7TzrV^ftRIPK8Js6)rRPjD^6L{X z!Wp6}wQ#5$W!{JDMYXsm~Shb0528yG6@b5)j1z7lLi;V z?ehFG_(0J#Uxy|={4*RS-n$ZR>|)%@#|WzA1!FUujx`Qs;f=8zgsUZ^KDL~NPqqUd zZdg_%29A^*<1|=T@GQ76kcAodl8s=wc=X%gdhuUt;1JCx;TGA+=ix%hLEeEABm#d6 z&%4L2$S-g_f3J%lFE}2vM8`eCj4!h@8U{CtE>3}CWlJuG^JPn}gEz`q%>!ozX2=99 z;AnA2)i8fT%WP4nd)bX3OhUpTxK*sV&!a3MOB~pe6X0fvX(z%(vSMe!k#d~A*gP*v zJQq&&olgBZ#&rmGiF?R{_ew}!2`>=``!rlX#;(}waJ=Zyc373L1umB@?to*_vE9BL z10@T2j1>^~F$gY_QMyo(m@Tp zRL%zPng{%esmT++gc~H)Z#CtT7DiMsAvqq$!#T2Lsc@eD9@hc{?c(3^;dK9BW;GqI zHVuelc@z$l6?y?K7A4*Z&y%416&xW^{yVs4j>j%xJAyru>i2#ek1Qb}64oUi4>ya_ z&V=)&{v0?EAq}R&`6~jBq_?vWt`a|S z2OK9#R|M;d6&vnMD|{2Ed;|yNP5tT7u79ID4?Ie=XdMj`TxEOz>I;uN5=A z58fq8>v;@8u57`Z@Df?#&)_%-E{EVlqBG%7(t)hd7m~*bzJ41&4@^{2PuPZCBtYI9s&-n5TFg&qpgLA@P3* zg4CV?0m(5OAzP9N=gN`)9Jo--ZV_BATEEP&X!)PvR7p@Cfj0)W)WrYi5G0MW1E_;@ zCD?op9}u1R3C!3- zmTxsIdwd_fM^>a9&KYCt{{_yIE!Ybub{^=)_(s%>AWZDIXC(uY1|qx{U*jI`8tc88 zn`b7a(TTSCOW-IO&JuW6iY@;W+$cu30uGf|CJ)1PRwX;0htuRG(qG{k`+~sJ(TJcz zI`{?NHP3d~_Zfn%XiyAXCK{9g+mE*JLpL~6V&f%njo8>Na9x)hLuJ>P29j)tFT-mk z7H)?NCDeQYS4sV!;56CbUYojWP#9dDW!payjtiu;MrBV&kSh638k{5N!^`0CfR0mt z3EU`=<;ZxbY2(s@Dgd&&I0#`)Y5eGku-z32rhLsyX z*axnY;64zJlL^GYY2p{B!~7{>zn3`JF(2lSi5c!pS+7ID?=N;$SOHf_4!j0#JK2_R zhGV3|SK;N7#D55baxpM%a&fRdNWE&ez zxwyG1I8@x)4mf&1v`a9}4G)pB)S9mP~^8iXWH_r(R&6 zUk2~uSI?6=XTa~HA~+-i@WPvAM$f@}^$f;365& zaW7yM9?|-d2=e65{GJB4$|+zr+;W+1a3Q=D9T-55#w@RbH;IxiGv%`6_rTH8-#XaS zB73w6L53*FD{$cdU+C}jb>4*eNm18~K7?yz3%-K0#4Nvs>!sXP#g>T`^s7Su=ZRep zr$X*Awu5o-5*!Z`^lAAdxKTV=GF&G*kp-8@tGuOfj>M7zI9(3&WmVW;k!ba^rh>6c ze~15aQO?Uo!{*J|tEOCn&n`GbCa@1~6<_}YoF!Y(dkbbK(ero@f(B8d@o|$^Qnoievf)o+0m# ze}da(fXBW_?2u?U0In3zItGqS>Kd@8V+MlwkS;;TVtDN+>#MJU!=%Am;Lxu4&HG@u zR&?f3xSV%z?3`oX!MzGcNc~-KOP3B2{|_KoI>HX%JGf3|0^}XlYmdo$#T6KZ&P-e;^3w?Oz`nG%x>tgj>ZL4x0uf5ef75 zxP_#91A@$9F=^0|VJS@5C7w%>X1jx#Oiz**u(9)!zf-cQ0yBPw4a~OvQ?02jgsL|tx!3y0bARj}=_kFyRg3aF#W{9i+m8aN8T z@4(q&v0uO;GK2rZS>nrrUttSGk4D1FMg8L8^hI_+v*234{`)fjxd@uAVsxy4`Ez+) z3jYAym}L1WI7e2b7SYCZ2^(rxPZG66w4Bo!@I=WZ-t{Jg71RkhuRhS z2OKBmt?+Wukq)>u+13wxjS2OH>EAbi5eRa{$4r9v%9dOK?-r-I91fQA?0vAVP&u3> zCxfSsD1QO2!)KcQeFNBrV6QlvPvPq&jXVJBE0;DnPkubvqZX}}6eJvOl<;*jTrDeb zAzXX0Wc~{gRLWs#85}KpcrRQLFdMdP9ULWId?TDHQD8G1BlT&=AI#dGGuX&%|bKO<-ne_jqJiX(X* zj*%3i0d5t~w->IF!1*m)bg5OkcDT|goqx+i-yrjk7PAj_MA$O>|&ATq%yL<_+|}YPcOyBNg_@2)~CDM%s^!wZRpVsE54Co@dzS zPk^IXw7?9b;b1Y-sc@bA3hTLVddT`@iLKK(zEQoGFfHNF7QeOL{V_13VSZzs#<{*+-P8!~E^nB!@5K=oY~_ zfwU9$tUwSh4crGi;(E*AV%eg4xK2)YAH(rde;?c;exU_Uk{S2fMzFH3+S4%>L8#b$ zDqKc7*w5)a&t3$FiDSA3PLHuOx)V;60Tsdg=Nd^o?wj$`aI;M~I${tvkcpu!hz&7|lxK4g#bKF~OnQY;3I7C)-3S2A+%lYuGlbp^0n)ts2L9KL< z2kQU|;N4<|>)?1f3q22qj&M2~q`i0GW^u(|m~#H3>tdHV3%Q(TIL4pR`{MtP2vX$i z68<)cqwM(@xK(nxL^x6W#zk;`{xshJ%6+1J@Ln;qZ{fAVLG>t=nB_otheW^8aIG96X22VLO6%!VlKBW)#Fu+uXSyB0 zV{l35hRtd+rcC71#)V87LFD_@D`kir#1ci2C&mq5G87YL&VH}fy1+H2Ls+E z!x5c`h0U>ZJnfk?(3$WaGedv-m%>YALf6A6xhL6FFfUd9gdii)KClW-6HmAab|kBP z4X%}>^aD6&l#LZ%z~z!~`~U}^X4`lDl@*-jv4XJ(md~|39X=#>Jr`apj^s+XN_1ic zTqm)j1kRVYM32F0)=$F3U+d|fN02UN_yOD?X1E{TCGq^faIOTGo(-&+yg7`5gXL(K z47W&dnhSd}tS|2HASgTCu0T0lG|O^5TrFn$54cd``A_ijOKkn1_sEK~EFTXC%L?Z7Ot^MU>bL*Zy~ z)v<7vxaL$iQ5;(?+}^cCygs_yG$4Q3_Gvg(bfOwoyL}7ZD=YFPTrE0t5Z3m3e}IP- z#}olC4>%?h|HmT;9cE`Z1J0KCJ{L}r=yemEDbepPc%I}3tKk|M;G=MfSjh`;lK**M z{NIM4K^(&#IANw8(D!hrSb^(9wm{B?VX(e(90+HLD~~lSW_g-#XtTEcXden zAs^u{{fBm+E{{TxCNBOIxNWu_@mzSAd^~9hoGLTE4X&1Vx%a~%@|s~iyisiBWwIHCNq<#K+c!y63{kiN4 z1iD1`!ex@lmBDEe!(M<}L@BqyIpWcG!TP6bzJ}+C68;3o8DkCPvd8htI9AgCNH|hD zoB(f>yyX;AE*~$x$dt>LErDw!@3|XJlH)`W(booiiSmF4j*{cWYB*Z_+Y9i#3+;g4f-@wf z?1mGi{vo(bl(6q7oCsq*wtesahE*q0;mzmT`U~I?QNldKBW-GWx2Z2?ct5P| zJqo9KM9W`8(2j==MCtk?9@qt!$%_07J|M2O*Qe~Ub*&wP;C5N^32>%taSH5NDlzGN z1Un?JxgJiE8LxoLWX2odj63)*p*bX*9EXGsv<4wp##&7Y$GyG3h*KBIyp z%l+Wo`==#U`Z{nqqv1U1AP&|gJq@msn0PLnDY0S+93$-c%;>+I%8N|}iFQxJQIbZ~ zz%7fc4!mdTOSJnO-Yr}3EnFwT=`g%B%Rb-7vxj6^_GB1bEOs6XFG;cwoMGyV*_{Pv zOLo5i&J?~8-YcnR9;_?oDMU~rX0ry46i@avTq|4jCLAti_XWIMw(M7UmrN-1@1&|S z<0$xmO*K6o@d!4{E96;l367+nKTk=A+hj=>8y2O_h4)6;6?y>9k^wyd*UOf^2p{TV z;(tAYwAIs+8vIJO2TqY-@-58Yq)%czzQoJ*IZ7x?d;+{wa^G?A0g3mg!dVh6FMv}6 zjxL&n;RXcd@}A*#xHZmpxZ2yJluT^{;<&k1{1*N zT@}I&%RDE*#rA0J={N^L##lRmOt?mV-jWTM$W||dRo8BXE2V=~aJuYl3H+fXPLIKL zT|3E6y^5e%QoFsTf-F=s94|5IFq|#QAGQ}&5aSsM>wj&X0`Fd6CpZUg59ILX)RB!K zSfX+Pyi0U%Jsda0Ht-^xx7cz$yjJ)#I9u|XZ{W=`z#rgHzj4NySJ6GbM0X_0I#*tO z({;Bly6K9AIVVneOkICof(+En(84t?3WCXSjM#iofW>8xoS^E`+d=ykGQ9Hllq1?_)+($Zqn38-HW;j z0<)OiO*-;1cX2ntE04L)={-3+1p?|-VIOvzM0Y9AO1iZ?d5@Szi+1O52dY7|It9w zDvwa5+bZ4X{l3Zn9a#+}ALBjenIndJ^E2*A-5!4N8TZNE1m8X5UeQf(`zH75Zi0c& zcAwMMbDnkA1$V3BGpJLm+$a5BTK0$1pQ?@+mv6%^#qNIJ0@nBk?fPsq=EWmybjFMB zS-)?=w~gCgJYsEpgYNs%?=>gdyZ9yd=p&mM?cMs4d+6__!PV|zM@oI(WuSLS_3xYU z6(x3i0N-$yckovC0PljA-62O7U2>!#!uy|>kC=+Dh1RX^F=6K(X-cdA;!ZfSg_FHs znBM5J`+R7|UsSZ!J@5~o>b|};*RPz3Z&g_;oy7jvEDhax+iq2hIFJi>2-HcZ}riO`oDJ6r*e*7^x@Hq zVr!4u;Hsk+H6N|0yP4W4ZG8QYx{0e}w(d8MT9kG4qBo9S6#3>+8@%o4MSG4`^aoS& zjeBj~QM>u!=tb$<{^+Tww^@kik6sk|)={6j=IBKYM=Sb+DfvcF`1T)l6ZS5H+kf<; zr23;i_2|)y+K*l|_no6QSaYG z!p1+8y59Svx(ohL`r02zyJs*y?{f3@BT~etwjYrK{%gB?d^i6S`M$d}R`QvJ)2>*S zvv3Lj_lZe(GM8P4!|R^FeDVGf`PRuh+_StX@4JV2U)$yGANhZ2>=JL)bM8oQ&Ij&b zZ{LsH{elvvp5h(9%e}<=`40E9L8nZc>OJFqcl6fhK5$QR^`Px+sFYmcpm+ufhsymjAAK@(iw-nl`4 za=qZaJ2&Wj*BjnXa)U1E&C%6~;(b?*+wJXpbI^d(79uc0x4$4NjFX9ZrSAU<_TR7K zZt4Ybncsv8G~mkhzH@WXSl1oiZ_Ta3JKz>>_jyk@x7FS&%+2flGdFMXEkWt7w|(Ec z_IM}WiYWHhpmcYz%X{LjL1SElydNMN5JfkpC&!8M{Yy_yh<96V(D4J}yNY}zP8~(T z-hMYzbOuGobV_~y)>D+=d#e9A{-@&n#qDnIoy&v9^jR9@c1l*8zqZI9we)UT9yFv! b`&zd%#{1FopivPcZwhiUcrx%6`1bz*@|^(G diff --git a/firmware/bin/20260910101457-firmware-q b/firmware/bin/20260914085743-firmware-q similarity index 59% rename from firmware/bin/20260910101457-firmware-q rename to firmware/bin/20260914085743-firmware-q index 0d08d21603add48a8e940529db36ab54d8ee6dfb..4b935bae46ca717d4271b6336693c38dad67ae9f 100755 GIT binary patch delta 42278 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 delta 41764 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 CGAjE3 diff --git a/firmware/p/Cargo.toml b/firmware/p/Cargo.toml index 0b541be..6c98b83 100644 --- a/firmware/p/Cargo.toml +++ b/firmware/p/Cargo.toml @@ -13,3 +13,6 @@ fmt-util = { workspace = true } # The on-target half (see a/Cargo.toml). No model on this node: pure GPIO. [target.'cfg(target_arch = "xtensa")'.dependencies] esp-hal = { version = "1.2", features = ["esp32s3", "unstable"] } +# The ESP-IDF app descriptor (esp_app_desc! in main.rs): required by the +# 2nd-stage bootloader; espflash >= 4.6 refuses a descriptor-less image. +esp-bootloader-esp-idf = { version = "0.6", features = ["esp32s3"] } diff --git a/firmware/p/src/main.rs b/firmware/p/src/main.rs index e6a5605..211b4ac 100644 --- a/firmware/p/src/main.rs +++ b/firmware/p/src/main.rs @@ -24,6 +24,11 @@ mod app { use firmware_p::{format_count, EdgeCounter, DEBOUNCE_MS}; + // The ESP-IDF app descriptor at the image head: the 2nd-stage + // bootloader requires it, and espflash >= 4.6 refuses to flash an + // image without it. + esp_bootloader_esp_idf::esp_app_desc!(); + /// Run id of this firmware image. const RUN_ID: &str = "bench-p"; diff --git a/firmware/q/Cargo.toml b/firmware/q/Cargo.toml index 33f221e..89acd8f 100644 --- a/firmware/q/Cargo.toml +++ b/firmware/q/Cargo.toml @@ -17,3 +17,6 @@ libm = "0.2" # The on-target half (see a/Cargo.toml). [target.'cfg(target_arch = "xtensa")'.dependencies] esp-hal = { version = "1.2", features = ["esp32s3", "unstable"] } +# The ESP-IDF app descriptor (esp_app_desc! in main.rs): required by the +# 2nd-stage bootloader; espflash >= 4.6 refuses a descriptor-less image. +esp-bootloader-esp-idf = { version = "0.6", features = ["esp32s3"] } diff --git a/firmware/q/src/main.rs b/firmware/q/src/main.rs index 3b974a2..2d8247c 100644 --- a/firmware/q/src/main.rs +++ b/firmware/q/src/main.rs @@ -41,6 +41,11 @@ mod app { use firmware_q::{classify, format_verdict, WINDOW}; + // The ESP-IDF app descriptor at the image head: the 2nd-stage + // bootloader requires it, and espflash >= 4.6 refuses to flash an + // image without it. + esp_bootloader_esp_idf::esp_app_desc!(); + /// Run id of this firmware image. const RUN_ID: &str = "bench-q"; From 1549188526190b07bbb2b03375f3d80f138a9036 Mon Sep 17 00:00:00 2001 From: "t.galimkhanov" Date: Mon, 14 Sep 2026 10:21:02 +0500 Subject: [PATCH 2/6] refactor(firmware-q): extract synthetic_tap_window into the lib, pin its verdict --- firmware/NOTES.md | 16 ++++++++++++++++ firmware/q/src/lib.rs | 26 ++++++++++++++++++++++++++ firmware/q/src/main.rs | 13 ++++++------- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/firmware/NOTES.md b/firmware/NOTES.md index 90e8c7b..6f4e102 100644 --- a/firmware/NOTES.md +++ b/firmware/NOTES.md @@ -34,3 +34,19 @@ run_id=bench-a` → `a: zero=2229` → `a,bench-a,160,idle`. - Отклонение от плана: blinky (S0) пропущен — сразу прошит рабочий `firmware-a`; полный журнал первой загрузки — в корневом README. + +## 2026-09-14 — узел Q: прошит; вердикт синтетики — `cracked` + +- Прошит вторым (`bin/20260914085743-firmware-q`, DevKitC-1 №2): boot, строки + `q,bench-q,,` каждые ровно 400 мс — конвейер + «серво → окно → модель → UART» подтверждён. +- **Расхождение**: синтетическое окно (quiet decay ~940 Гц, амплитуда 0.05) + модель классифицирует как `cracked`, хотя комментарий в `main.rs` обещал + «the good-part family». Причина: формула жила в xtensa-гейтед `mod app` — + хостовые тесты её не видели, ожидание не было закреплено. +- Закрытие: формула вынесена в lib (`synthetic_tap_window`), фактический + вердикт `cracked` закреплён хостовым тестом `synthetic_tap_verdict_is_pinned`, + комментарий исправлен. Смысл вердикты обретут на S4 (реальный I2S/INMP441); + до тех пор константность вердикта — норма (проверка контура, не метки). +- Замечание: сет `bin/20260914085743-*` собран до этого рефакторинга; + поведение идентично (та же формула, тот же вердикт). diff --git a/firmware/q/src/lib.rs b/firmware/q/src/lib.rs index 5c3caf4..a77f182 100644 --- a/firmware/q/src/lib.rs +++ b/firmware/q/src/lib.rs @@ -64,6 +64,20 @@ pub fn i2s_slots_to_f32(slots: &[u32], out: &mut [f32]) -> usize { written } +/// Fills the window with the deterministic synthetic tap (the S4 stand-in +/// until the I2S/INMP441 driver lands): a quiet decay +/// `sin(120·π·t)·(1−t)·0.05`, `t = i/WINDOW`. +/// +/// Out of the model's training distribution by construction: the pinned +/// verdict on it is `cracked` (the bench fact, 2026-09-14) — a constant +/// verdict proves the tap→window→model→UART loop, not a part label. +pub fn synthetic_tap_window(out: &mut [f32]) { + for (i, slot) in out.iter_mut().enumerate() { + let t = i as f32 / WINDOW as f32; + *slot = libm::sinf(t * 120.0 * core::f32::consts::PI) * (1.0 - t) * 0.05; + } +} + /// Formats a verdict line for the UART bridge / capture log: /// `q,,,`. pub fn format_verdict(out: &mut [u8], run_id: &str, t_ms: u32, verdict: usize) -> Option { @@ -111,6 +125,18 @@ mod tests { assert_eq!(verdict, 0, "the good class: probs={probs:?}"); } + /// The synthetic tap is out of the training distribution, so its verdict + /// is a pinned convention, not a truth: `cracked`, the bench-observed + /// fact (2026-09-14, board #2). Meaningful verdicts arrive with the real + /// I2S input (S4); until then the constancy is the loop check. + #[test] + fn synthetic_tap_verdict_is_pinned() { + let mut window = [0.0f32; WINDOW]; + synthetic_tap_window(&mut window); + let (probs, verdict) = classify(&window); + assert_eq!(verdict, 1, "the synthetic quiet decay: probs={probs:?}"); + } + /// Review card 20260909120007 (firmware half): the WINDOW constant is /// a hand copy — pin it to the single source of truth. The rate /// (16 kHz) is fixed by the I2S driver on the target; the window size diff --git a/firmware/q/src/main.rs b/firmware/q/src/main.rs index 2d8247c..98aa501 100644 --- a/firmware/q/src/main.rs +++ b/firmware/q/src/main.rs @@ -39,7 +39,7 @@ mod app { uart::{Config as UartConfig, UartTx}, }; - use firmware_q::{classify, format_verdict, WINDOW}; + use firmware_q::{classify, format_verdict, synthetic_tap_window, WINDOW}; // The ESP-IDF app descriptor at the image head: the 2nd-stage // bootloader requires it, and espflash >= 4.6 refuses to flash an @@ -107,14 +107,13 @@ mod app { set_duty(&mut servo, REST_DUTY_PCT); t_ms = t_ms.wrapping_add(SETTLE_MS); - // The window (S4 TODO: I2S INMP441; a deterministic synthetic - // tap until then — quiet decay, the good-part family). + // The window (S4 TODO: I2S INMP441; the deterministic synthetic + // tap until then — `synthetic_tap_window` in the lib: a quiet + // decay out of the training distribution, the pinned verdict + // is `cracked` — the loop check, not a label). // SAFETY: see the WINDOW_BUF declaration above. let window = unsafe { &mut *core::ptr::addr_of_mut!(WINDOW_BUF) }; - for (i, slot) in window.iter_mut().enumerate() { - let t = i as f32 / WINDOW as f32; - *slot = libm::sinf(t * 120.0 * core::f32::consts::PI) * (1.0 - t) * 0.05; - } + synthetic_tap_window(window); let verdict = classify(&window).1; if let Some(n) = format_verdict(&mut line, RUN_ID, t_ms, verdict) { From 5d9d37099e2de0f83e1b3d060b82bed81970e2ee Mon Sep 17 00:00:00 2001 From: "t.galimkhanov" Date: Mon, 14 Sep 2026 10:53:40 +0500 Subject: [PATCH 3/6] docs(firmware): fix the by-id example to the observed usb-1a86_* form, record board serials --- firmware/NOTES.md | 5 ++++- firmware/README.md | 8 +++++--- firmware/README.ru.md | 8 +++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/firmware/NOTES.md b/firmware/NOTES.md index 6f4e102..1a2fe97 100644 --- a/firmware/NOTES.md +++ b/firmware/NOTES.md @@ -13,7 +13,10 @@ micro-USB. Нативный «USB»-порт платы (Espressif `303a:4001`) тоже даёт `ttyACM*` — для монитора бесполезен (вывод идёт в аппаратный UART). Различение плат при нескольких подключённых — `/dev/serial/by-id/` - (серийник CH343 уникален у каждой платы). + (CH343 не отдаёт строку производителя, udev строит имя от VID — + `usb-1a86_USB_Single_Serial_-if00`; серийник уникален). Серийники + стенда: DevKitC-1 №1 (узел A) — `5C94148486`, №2 (узел Q) — + `5C94152266`. - **espflash ≥ 4.6** отказывается прошивать образ без ESP-IDF App Descriptor → в `firmware-{a,q,p}` добавлены target-gated зависимость `esp-bootloader-esp-idf = "0.6"` (feature `esp32s3`, MSRV rustc 1.95) и diff --git a/firmware/README.md b/firmware/README.md index 767e6be..8acd011 100644 --- a/firmware/README.md +++ b/firmware/README.md @@ -99,12 +99,14 @@ labeled "COM", on the v1.0 micro-USB one — "UART"). espflash does not care whether it opens a `ttyUSB` or a `ttyACM`. The board's native "USB" port also shows up as `/dev/ttyACM*`, but it is useless for monitoring: the output goes to the hardware UART. With several boards all on -`ttyACM*`, tell them apart by the stable by-id names (a CH343 serial is -unique per board): +`ttyACM*`, tell them apart by the stable by-id names (the CH343 exposes no +manufacturer string, so udev builds the name from the VID — +`usb-1a86_USB_Single_Serial_-if00`; the serial is unique per +board): ```bash ls -l /dev/serial/by-id/ -espflash flash --port /dev/serial/by-id/usb-QinHeng_Electronics_USB_Single_Serial_-if00 \ +espflash flash --port /dev/serial/by-id/usb-1a86_USB_Single_Serial_-if00 \ target/xtensa-esp32s3-none-elf/release/firmware-a ``` diff --git a/firmware/README.ru.md b/firmware/README.ru.md index e3c89b8..4b63873 100644 --- a/firmware/README.ru.md +++ b/firmware/README.ru.md @@ -99,12 +99,14 @@ espflash flash --port /dev/ttyUSB2 target/xtensa-esp32s3-none-elf/release/firmwa «UART»). espflash'у безразлично, `ttyUSB` это или `ttyACM`. Нативный «USB»-порт платы тоже даёт `/dev/ttyACM*`, но для монитора бесполезен: вывод идёт в аппаратный UART. Если подключено несколько плат и все они -`ttyACM*`, различайте их по стабильным by-id-именам (серийник CH343 -уникален у каждой платы): +`ttyACM*`, различайте их по стабильным by-id-именам (CH343 не отдаёт +строку производителя, udev строит имя от VID — +`usb-1a86_USB_Single_Serial_-if00`; серийник уникален у каждой +платы): ```bash ls -l /dev/serial/by-id/ -espflash flash --port /dev/serial/by-id/usb-QinHeng_Electronics_USB_Single_Serial_-if00 \ +espflash flash --port /dev/serial/by-id/usb-1a86_USB_Single_Serial_-if00 \ target/xtensa-esp32s3-none-elf/release/firmware-a ``` From 1bb3e5c955e0d6c9a28f63ba3b97e0339ed4087e Mon Sep 17 00:00:00 2001 From: "t.galimkhanov" Date: Mon, 14 Sep 2026 11:24:52 +0500 Subject: [PATCH 4/6] docs: node Q and P bring-up logs in the root READMEs, NOTES updates --- README.md | 124 ++++++++++++++++++++++++++++++++++++++++++++++ README.ru.md | 124 ++++++++++++++++++++++++++++++++++++++++++++++ firmware/NOTES.md | 16 +++++- 3 files changed, 263 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fcf966f..9c20873 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,130 @@ a: zero=2229 a,bench-a,160,idle ``` +Node Q (DevKitC-1 #2, `firmware-q`) — reflashed with the final HEAD build +(after the synthetic-window extraction into the lib): `cracked` verdicts +every 400 ms — the test-pinned synthetic behavior until S4 (a loop check, +not a quality label). The bootloader and the partition table were already +in flash — only the app region (0x10000) changed. Log: + +```text +espflash flash --monitor --port /dev/ttyACM0 target/xtensa-esp32s3-none-elf/release/firmware-q +[2026-09-14T05:36:49Z INFO ] Serial port: '/dev/ttyACM0' +[2026-09-14T05:36:49Z INFO ] Connecting... +[2026-09-14T05:36:49Z INFO ] Using flash stub +Chip type: esp32s3 (revision v0.2) +Crystal frequency: 40 MHz +Flash size: 16MB +Features: WiFi, BLE, Embedded Flash +MAC address: 44:1b:f6:fd:fa:60 +App/part. size: 113,456/16,384,000 bytes, 0.69% +[00:00:04] [========================================] 3/3 0x10000 Verifying... OK! +[2026-09-14T05:36:55Z INFO ] Flashing has completed! +Commands: + CTRL+R Reset chip + CTRL+C Exit + +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT) +SPIWP:0xee +mode:DIO, clock div:2 +load:0x3fce2820,len:0x14d0 +load:0x403c8700,len:0xdcc +load:0x403cb700,len:0x2f54 +entry 0x403c8900 +I (29) boot: ESP-IDF v6.1-beta1-497-g14f663f003e 2nd stage bootloader +I (30) boot: Multicore bootloader +I (30) boot: chip revision: v0.2 +I (30) boot: efuse block revision: v1.4 +I (34) boot.esp32s3: Boot SPI Speed : 40MHz +I (38) boot.esp32s3: SPI Mode : DIO +I (42) boot.esp32s3: SPI Flash Size : 16MB +I (45) boot: Enabling RNG early entropy source... +I (50) boot: Partition Table: +I (52) boot: ## Label Usage Type ST Offset Length +I (59) boot: 0 nvs WiFi data 01 02 00009000 00006000 +I (65) boot: 1 phy_init RF data 01 01 0000f000 00001000 +I (72) boot: 2 factory factory app 00 00 00010000 00fa0000 +I (78) boot: End of partition table +I (82) esp_image: segment 0: paddr=00010020 vaddr=3c000020 size=04300h ( 17152) map +I (93) esp_image: segment 1: paddr=00014328 vaddr=3fc89998 size=009bch ( 2492) load +I (97) esp_image: segment 2: paddr=00014cec vaddr=40378000 size=01998h ( 6552) load +I (106) esp_image: segment 3: paddr=0001668c vaddr=00000000 size=0998ch ( 39308) +I (123) esp_image: segment 4: paddr=00020020 vaddr=42010020 size=0bae8h ( 47848) map +I (134) boot: Loaded app from partition at offset 0x10000 +I (135) boot: Disabling RNG early entropy source... +q: boot, run_id=bench-q +q,bench-q,30,cracked +q,bench-q,430,cracked +q,bench-q,830,cracked +q,bench-q,1230,cracked +q,bench-q,1630,cracked +q,bench-q,2030,cracked +q,bench-q,2430,cracked +q,bench-q,2830,cracked +q,bench-q,3230,cracked +q,bench-q,3630,cracked +q,bench-q,4030,cracked +``` + +Node P (the CAM board, `firmware-p`) — a 99,040-byte app, the smallest of +the three (no model); after the boot line it stays silent — the norm +without a sensor. A CAM-board quirk: flash and monitor through the bridge +port only (the native USB port does not auto-enter download mode). Log: + +```text +espflash flash --monitor --port /dev/ttyACM0 bin/20260914085743-firmware-p +[2026-09-14T06:09:02Z INFO ] Serial port: '/dev/ttyACM0' +[2026-09-14T06:09:02Z INFO ] Connecting... +[2026-09-14T06:09:02Z INFO ] Using flash stub +Chip type: esp32s3 (revision v0.2) +Crystal frequency: 40 MHz +Flash size: 16MB +Features: WiFi, BLE, Embedded Flash +MAC address: 90:70:69:f8:fc:70 +App/part. size: 99,040/16,384,000 bytes, 0.60% +[00:00:01] [========================================] 1/1 0x0 Verifying... OK! +[00:00:00] [========================================] 1/1 0x8000 Verifying... OK! +[00:00:03] [========================================] 2/2 0x10000 Verifying... OK! +[2026-09-14T06:09:08Z INFO ] Flashing has completed! +Commands: + CTRL+R Reset chip + CTRL+C Exit + +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT) +SPIWP:0xee +mode:DIO, clock div:2 +load:0x3fce2820,len:0x14d0 +load:0x403c8700,len:0xdcc +load:0x403cb700,len:0x2f54 +entry 0x403c8900 +I (29) boot: ESP-IDF v6.1-beta1-497-g14f663f003e 2nd stage bootloader +I (30) boot: Multicore bootloader +I (30) boot: chip revision: v0.2 +I (30) boot: efuse block revision: v1.4 +I (34) boot.esp32s3: Boot SPI Speed : 40MHz +I (38) boot.esp32s3: SPI Mode : DIO +I (42) boot.esp32s3: SPI Flash Size : 16MB +I (45) boot: Enabling RNG early entropy source... +I (50) boot: Partition Table: +I (52) boot: ## Label Usage Type ST Offset Length +I (59) boot: 0 nvs WiFi data 01 02 00009000 00006000 +I (65) boot: 1 phy_init RF data 01 01 0000f000 00001000 +I (72) boot: 2 factory factory app 00 00 00010000 00fa0000 +I (78) boot: End of partition table +I (82) esp_image: segment 0: paddr=00010020 vaddr=3c000020 size=01e30h ( 7728) map +I (91) esp_image: segment 1: paddr=00011e58 vaddr=3fc89998 size=009b0h ( 2480) load +I (97) esp_image: segment 2: paddr=00012810 vaddr=40378000 size=01998h ( 6552) load +I (106) esp_image: segment 3: paddr=000141b0 vaddr=00000000 size=0be68h ( 48744) +I (123) esp_image: segment 4: paddr=00020020 vaddr=42010020 size=08298h ( 33432) map +I (133) boot: Loaded app from partition at offset 0x10000 +I (133) boot: Disabling RNG early entropy source... +p: boot, run_id=bench-p +``` + The shakedown facts — [`firmware/NOTES.md`](./firmware/NOTES.md). ## Status diff --git a/README.ru.md b/README.ru.md index a9e50fc..c41c6a9 100644 --- a/README.ru.md +++ b/README.ru.md @@ -271,6 +271,130 @@ a: zero=2229 a,bench-a,160,idle ``` +Узел Q (DevKitC-1 №2, `firmware-q`) — перепрошит финальной HEAD-сборкой +(после выноса синтетического окна в lib): вердикты `cracked` каждые 400 мс — +закреплённое тестом поведение синтетики до S4 (проверка контура, не метка +качества). Загрузчик и таблица разделов уже во flash — менялось только +приложение (одна область 0x10000). Журнал: + +```text +espflash flash --monitor --port /dev/ttyACM0 target/xtensa-esp32s3-none-elf/release/firmware-q +[2026-09-14T05:36:49Z INFO ] Serial port: '/dev/ttyACM0' +[2026-09-14T05:36:49Z INFO ] Connecting... +[2026-09-14T05:36:49Z INFO ] Using flash stub +Chip type: esp32s3 (revision v0.2) +Crystal frequency: 40 MHz +Flash size: 16MB +Features: WiFi, BLE, Embedded Flash +MAC address: 44:1b:f6:fd:fa:60 +App/part. size: 113,456/16,384,000 bytes, 0.69% +[00:00:04] [========================================] 3/3 0x10000 Verifying... OK! +[2026-09-14T05:36:55Z INFO ] Flashing has completed! +Commands: + CTRL+R Reset chip + CTRL+C Exit + +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT) +SPIWP:0xee +mode:DIO, clock div:2 +load:0x3fce2820,len:0x14d0 +load:0x403c8700,len:0xdcc +load:0x403cb700,len:0x2f54 +entry 0x403c8900 +I (29) boot: ESP-IDF v6.1-beta1-497-g14f663f003e 2nd stage bootloader +I (30) boot: Multicore bootloader +I (30) boot: chip revision: v0.2 +I (30) boot: efuse block revision: v1.4 +I (34) boot.esp32s3: Boot SPI Speed : 40MHz +I (38) boot.esp32s3: SPI Mode : DIO +I (42) boot.esp32s3: SPI Flash Size : 16MB +I (45) boot: Enabling RNG early entropy source... +I (50) boot: Partition Table: +I (52) boot: ## Label Usage Type ST Offset Length +I (59) boot: 0 nvs WiFi data 01 02 00009000 00006000 +I (65) boot: 1 phy_init RF data 01 01 0000f000 00001000 +I (72) boot: 2 factory factory app 00 00 00010000 00fa0000 +I (78) boot: End of partition table +I (82) esp_image: segment 0: paddr=00010020 vaddr=3c000020 size=04300h ( 17152) map +I (93) esp_image: segment 1: paddr=00014328 vaddr=3fc89998 size=009bch ( 2492) load +I (97) esp_image: segment 2: paddr=00014cec vaddr=40378000 size=01998h ( 6552) load +I (106) esp_image: segment 3: paddr=0001668c vaddr=00000000 size=0998ch ( 39308) +I (123) esp_image: segment 4: paddr=00020020 vaddr=42010020 size=0bae8h ( 47848) map +I (134) boot: Loaded app from partition at offset 0x10000 +I (135) boot: Disabling RNG early entropy source... +q: boot, run_id=bench-q +q,bench-q,30,cracked +q,bench-q,430,cracked +q,bench-q,830,cracked +q,bench-q,1230,cracked +q,bench-q,1630,cracked +q,bench-q,2030,cracked +q,bench-q,2430,cracked +q,bench-q,2830,cracked +q,bench-q,3230,cracked +q,bench-q,3630,cracked +q,bench-q,4030,cracked +``` + +Узел P (CAM-плата, `firmware-p`) — app 99 040 байт, минимальный из трёх +(нет модели); после boot-строки тишина — норма без датчика. Нюанс CAM-платы: +прошивать и мониторить только через мостовой порт (нативный USB-порт не +даёт авто-download). Журнал: + +```text +espflash flash --monitor --port /dev/ttyACM0 bin/20260914085743-firmware-p +[2026-09-14T06:09:02Z INFO ] Serial port: '/dev/ttyACM0' +[2026-09-14T06:09:02Z INFO ] Connecting... +[2026-09-14T06:09:02Z INFO ] Using flash stub +Chip type: esp32s3 (revision v0.2) +Crystal frequency: 40 MHz +Flash size: 16MB +Features: WiFi, BLE, Embedded Flash +MAC address: 90:70:69:f8:fc:70 +App/part. size: 99,040/16,384,000 bytes, 0.60% +[00:00:01] [========================================] 1/1 0x0 Verifying... OK! +[00:00:00] [========================================] 1/1 0x8000 Verifying... OK! +[00:00:03] [========================================] 2/2 0x10000 Verifying... OK! +[2026-09-14T06:09:08Z INFO ] Flashing has completed! +Commands: + CTRL+R Reset chip + CTRL+C Exit + +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT) +SPIWP:0xee +mode:DIO, clock div:2 +load:0x3fce2820,len:0x14d0 +load:0x403c8700,len:0xdcc +load:0x403cb700,len:0x2f54 +entry 0x403c8900 +I (29) boot: ESP-IDF v6.1-beta1-497-g14f663f003e 2nd stage bootloader +I (30) boot: Multicore bootloader +I (30) boot: chip revision: v0.2 +I (30) boot: efuse block revision: v1.4 +I (34) boot.esp32s3: Boot SPI Speed : 40MHz +I (38) boot.esp32s3: SPI Mode : DIO +I (42) boot.esp32s3: SPI Flash Size : 16MB +I (45) boot: Enabling RNG early entropy source... +I (50) boot: Partition Table: +I (52) boot: ## Label Usage Type ST Offset Length +I (59) boot: 0 nvs WiFi data 01 02 00009000 00006000 +I (65) boot: 1 phy_init RF data 01 01 0000f000 00001000 +I (72) boot: 2 factory factory app 00 00 00010000 00fa0000 +I (78) boot: End of partition table +I (82) esp_image: segment 0: paddr=00010020 vaddr=3c000020 size=01e30h ( 7728) map +I (91) esp_image: segment 1: paddr=00011e58 vaddr=3fc89998 size=009b0h ( 2480) load +I (97) esp_image: segment 2: paddr=00012810 vaddr=40378000 size=01998h ( 6552) load +I (106) esp_image: segment 3: paddr=000141b0 vaddr=00000000 size=0be68h ( 48744) +I (123) esp_image: segment 4: paddr=00020020 vaddr=42010020 size=08298h ( 33432) map +I (133) boot: Loaded app from partition at offset 0x10000 +I (133) boot: Disabling RNG early entropy source... +p: boot, run_id=bench-p +``` + Факты шейкдауна — [`firmware/NOTES.md`](./firmware/NOTES.md). ## Статус diff --git a/firmware/NOTES.md b/firmware/NOTES.md index 1a2fe97..068fdbe 100644 --- a/firmware/NOTES.md +++ b/firmware/NOTES.md @@ -16,7 +16,7 @@ (CH343 не отдаёт строку производителя, udev строит имя от VID — `usb-1a86_USB_Single_Serial_-if00`; серийник уникален). Серийники стенда: DevKitC-1 №1 (узел A) — `5C94148486`, №2 (узел Q) — - `5C94152266`. + `5C94152266`, CAM (узел P) — `5CCC048683`. - **espflash ≥ 4.6** отказывается прошивать образ без ESP-IDF App Descriptor → в `firmware-{a,q,p}` добавлены target-gated зависимость `esp-bootloader-esp-idf = "0.6"` (feature `esp32s3`, MSRV rustc 1.95) и @@ -53,3 +53,17 @@ до тех пор константность вердикта — норма (проверка контура, не метки). - Замечание: сет `bin/20260914085743-*` собран до этого рефакторинга; поведение идентично (та же формула, тот же вердикт). + +## 2026-09-14 — узел P: прошит; S0 закрыт по всему стенду + +- CAM-плата прошита третьей (`bin/20260914085743-firmware-p`): esp32s3 v0.2, + 16 MB, app 99 040 байт — минимальный из трёх узлов (нет модели, только + счётчик с антидребезгом); boot-строка `p: boot, run_id=bench-p` получена, + далее тишина — норма без датчика (у P нет периодики, только события). +- **Грабля нативного порта**: первая попытка прошивки шла через нативный + USB-порт CAM-платы (Espressif `303a:4001`) — «Failed to connect», + авто-вход в download-режим не сработал. Прошивать и мониторить — только + через мостовой порт: на CAM-плате тоже CH343, серийник `5CCC048683`. +- До S6: сверить по схеме конкретной CAM-платы, что GPIO5 свободен от + обвязки камеры (TCRT5000) — CAM-платы разных производителей различаются + раскладкой. From 6a647de00126026635cf37ceb3dc2114007c19b6 Mon Sep 17 00:00:00 2001 From: "t.galimkhanov" Date: Mon, 14 Sep 2026 12:21:20 +0500 Subject: [PATCH 5/6] docs(firmware): the detailed shakedown runbook (rus+eng) + cross-links, node P/Q bring-up notes --- .../decompose/firmware-shakedown-runbook.md | 431 ++++++++++++++++++ docs/eng/decompose/firmware.md | 1 + .../decompose/firmware-shakedown-runbook.md | 421 +++++++++++++++++ docs/rus/decompose/firmware.md | 1 + firmware/README.md | 1 + firmware/README.ru.md | 1 + 6 files changed, 856 insertions(+) create mode 100644 docs/eng/decompose/firmware-shakedown-runbook.md create mode 100644 docs/rus/decompose/firmware-shakedown-runbook.md diff --git a/docs/eng/decompose/firmware-shakedown-runbook.md b/docs/eng/decompose/firmware-shakedown-runbook.md new file mode 100644 index 0000000..3384ffa --- /dev/null +++ b/docs/eng/decompose/firmware-shakedown-runbook.md @@ -0,0 +1,431 @@ +# Hardware track: the detailed shakedown runbook (updated 2026-09-14) + +Russian original: [firmware-shakedown-runbook.md](../../rus/decompose/firmware-shakedown-runbook.md). + +Context: the host track (weeks 1–6 + the report) is complete; 2026-09-14 +closed S0 — all three boards are flashed and up (the release +`firmware/bin/20260914085743-*`, the logs are in the root READMEs, the +facts are in `firmware/NOTES.md`). The session plan — +`docs/eng/decompose/firmware.md`; the assembly guide — +`docs/eng/HARDWARE-assembly-guide.md`. Bridge serials: node A — +`5C94148486`, node Q — `5C94152266`, node P (CAM) — `5CCC048683`; there +are no `/dev/ttyUSB*` ports on this bench — the actual names are +`/dev/serial/by-id/usb-1a86_USB_Single_Serial_-if00`. + +| Step | Topic | Time | Code needed? | +| ---- | ------------------------------------------- | ------ | ------------------------ | +| 0 | the day's tails: commit the docs, CI | 5 min | — | +| 0.5 | the wire check of P | 5 min | — | +| 1 | S1: node A, ADC and calibration | ~3 h | + the capture mode | +| 2 | S2: node A, statuses across regimes | ~2 h | everything exists | +| 3 | S3: the board vs the host twin cross-check | ~2 h | everything exists | +| 4 | S4: node Q, the INMP441 I2S driver | ~3 h | + the I2S driver | +| 5 | S5: node Q, the servo and the verdicts | ~3 h | everything exists | +| 6 | S6: node P, part counting | ~2 h | everything exists | +| 7 | S7: the track gate and the phase-2 decision | ~2–3 h | + the gate docs | +| 8 | phase 2: the bench in the full OEE loop | ~1–2 h | exists (the uart-bridge) | + +## Step 0 — the day's tails (5 min) + +Commit the day's documentation (the root READMEs ×2, `firmware/README ×2`, +`firmware/NOTES.md`, `firmware/bin/20260914085743-build-info.md`) and +check CI on the `hardware-firmware` branch: the `firmware` job builds the +working tree with the `esp-bootloader-esp-idf` 0.6 dependency for the +first time (it requires rustc 1.95 or newer; CI installs a fresh espup, +expected green). If the job is red exactly on the rustc version — record +the fact in NOTES and roll the dependency back to 0.5 (minimum rustc +1.88). + +## Step 0.5 — the wire check of node P (5 min) + +Short GPIO5 to 3V3 with a jumper wire: hold for at least 60 ms, release, +repeat. The 50 ms debounce ignores closures shorter than 50 ms, so every +correct touch gives exactly one counter increment — the monitor shows a +`p,bench-p,,N` line with the next N. If the counter grows from +short touches (below 50 ms), the `DEBOUNCE_MS` constant in the firmware +does not match the actual parameter — that is a finding for NOTES, not +for a silent fix. + +## Step 1 — S1. Node A: the ADC and calibration (~3 h) + +The goal: prove with numbers that the current the board measures matches +the physical current. At rest the firmware must read zero (within the +tolerance); under a known load it must match the multimeter. + +Wiring: + +- ACS712-20A: VCC — 5 V, GND — common with the board, OUT — through a + 2:1 resistor divider (for example, 10 kΩ + 10 kΩ) to GPIO4 + (`board::node_a::ADC_CURRENT`). The divider is mandatory: the ACS712-20A + output at zero current is 2.5 V, the sensitivity is 100 mV per ampere, + the working range is 0.5–4.5 V; the ESP32-S3 ADC with 11 dB attenuation + measures up to about 3.1 V, and the divider maps the sensor range into + 0.25–2.25 V. +- GPIO4 is chosen because it is an ADC1 channel: the ADC2 channels on the + ESP32-S3 conflict with Wi-Fi, and phase 2 may theoretically use it. + +What is already implemented: at startup the node averages 800 readings +taken every 625 μs (half a second, 1.6 kHz) and prints `a: zero=NNN` — +the startup zero point (`CurrentCalibration::acs712_20a_div2()` plus +`with_zero_counts`). Then every 128 readings (an 80 ms window) the whole +window goes into `model_a`; a status change is printed only after +confirmation by two consecutive windows (the ×2 hysteresis) as an +`a,bench-a,,` line. + +What is missing (this session's code): the capture mode. Today the board +prints only statuses, but the multimeter comparison and step 3 need +capture lines in the `features_cli::capture` schema — +`t_ms,node,run_id,value,state,note`. Add to `firmware-a` behind a +compile-time constant: once per window (80 ms) print a line with the +window-average current in amperes. Traffic estimate: 12.5 lines per +second at ~35 bytes — about 440 bytes per second against the UART's +115200 baud ≈ 11.5 kilobytes per second, a twentyfold margin. Printing +every individual sample is not possible: 1600 lines per second at the +same ~35 bytes give 56 kilobytes per second — the UART will not keep up +and lines will be lost. + +Actions: + +1. Wire the sensor as above; do not power the load yet. +2. Rebuild the image with the capture mode and flash board A (the port by + the `5C94148486` serial), open the monitor recording to a file. +3. Rest: 60 seconds of recording. The criterion: the average `value` is + at most 0.05 A in absolute value, the span (max minus min) is at most + 0.1 A. Tolerance derivation: one ADC count corresponds to about 15 mA + (3.1 V / 4095 counts — that is 0.76 mV per count at the board pin, the + divider doubles the voltage, and the sensor sensitivity is 100 mV per + ampere); 0.05 A is three counts. +4. Linearity: the multimeter in the 10 A current mode in series with the + load. Three levels: 0.5 A, 1.0 A, 2.0 A — resistors on 5 V: 10 Ω rated + at least 5 W (0.5 A), 5.1 Ω at least 10 W (about 1.0 A), 2.7 Ω at + least 25 W (about 1.9 A). Record 30 seconds at each level. +5. For each level compute the average `value` from the capture file and + compare with the multimeter reading. + +Pass criteria: rest — per item 3; linearity — at each level the +board-versus-multimeter deviation is at most the larger of 0.1 A and 5 +percent of the reading; the `a: zero=` value changes by at most 5 counts +between two resets (Ctrl+R in the monitor). + +Failure scenarios and their meaning: the readings are about half the +multimeter — the divider is not 2:1, or the sensor module has a different +sensitivity (the ACS712-5A is 185 mV per ampere; check the module marking +against the `acs712_20a_div2` constant); zero drifted while `zero=` is +stable — the calibration ran before the sensor was connected, reset the +board with the sensor attached; the values jump by ±0.5 A — no common +GND between the sensor supply and the board, or the input is floating. + +Record in NOTES: the `zero` value with the sensor attached, the table +"level, board (A), multimeter (A), deviation", the final tolerance. + +## Step 2 — S2. Node A: statuses across regimes (~2 h) + +The goal: show that the status follows the physical regime within known +delay bounds and does not flip on its own inside a regime. + +Scenario: the node distinguishes regimes by current; physically imitate +them with the same load from step 1 — alternate 0 A (30 seconds, the +idle regime) and 1–2 A (30 seconds, the run regime), at least five +switches each way and no more often than one switch per two seconds. + +Where the delay bounds come from: a window of 128 readings at 1.6 kHz is +80 ms; the hysteresis confirms a change only after two consecutive +windows. So a status change cannot appear earlier than 80 ms and no later +than three windows, i.e. 240 ms. + +Actions: record the monitor to a file for the whole run; for each switch +compute the delay — the status line time minus the actual switch time by +the stopwatch; count spontaneous flips inside the 30-second phases. + +Pass criteria: each of the ten or more switches fits the 80–500 ms +interval (240 ms of theory plus a stopwatch-accuracy margin); spontaneous +flips inside the phases — zero over at least five minutes of recording. + +Failure scenarios: the delay is consistently over a second — the actual +run current is below what the training set maps to the run regime +(compare the `value` during the phase with the week-4 experiment data); +the status flip-flops at the boundary — the phase current sits exactly at +the class-separation threshold, raise the run-current level (the +hysteresis must not be changed — the ×2 contract is pinned by tests). + +Record in NOTES: the number of switches, the minimum, median and maximum +delays, the number of spontaneous flips. + +## Step 3 — S3. The cross-check: the board vs the host twin (~2 h) + +The goal: on one and the same physical run prove that the hardware node A +and the host node A produce the same status sequences, with discrepancies +allowed only at window boundaries. + +Why boundary discrepancies are normal: both sides use the same model (the +rust-born `model_a` with the bit-exact Conv1D kernel), the same ×2 +hysteresis, and the same input — the current in amperes computed on the +board. The only difference is the window phase: the board's windows start +from its boot moment, the host node's windows start from the first line +of the input file, so the samples at a regime-switch boundary fall into +different windows. + +Actions: + +1. Take the board capture file from step 1 or 2 (the monitor recording) — + `tmp/s3.capture.csv`. +2. Convert it with the repository utility: it reads standard input, + renames the `value` column to `current_a`, keeps the node A lines and + writes the host node's ready input: + `cargo run --release -p firmware-tools --bin capture-to-run < tmp/s3.capture.csv > tmp/s3.run.csv` + (the utility prints a counter to stderr: how many lines were taken and + how many skipped — the header, other nodes, bad lines). +3. Run the host node on the same run: + `cargo run --release -p nodes --bin node -- --kind a --input tmp/s3.run.csv --offline tmp/s3.statuses.csv --run-id bench-a` + (without `--mqtt` — an offline comparison, no broker needed). +4. Compare the two status sequences: the `a,bench-a,…` lines from the + board's monitor recording against the lines from `tmp/s3.statuses.csv`. + +Pass criteria: one hundred percent status agreement outside the zones +adjacent to regime switches (a zone is two windows, 160 ms, on each side +of a switch); inside the zones discrepancies are allowed. Any discrepancy +outside the zones is an escalation-level finding from the track plan +("why did the parity test not catch it"): fix the cause and close it with +a test, do not accept the discrepancy. + +Record in NOTES: the table "total switches, agreed, discrepancies, +whether all discrepancies are inside boundary zones". + +## Step 4 — S4. Node Q: the INMP441 I2S driver (~3 h, the only big piece of code) + +The goal: replace the synthetic window with real sound. Today node Q's +verdict is always `cracked` — pinned by a test as the loop check; the +verdicts gain meaning only with the real microphone. + +INMP441 wiring: VDD — strictly 3.3 V (at 5 V the module dies), GND — +common, the L/R channel-select input — to GND (the left channel), SCK — +GPIO12 (`board::node_q::I2S_SCK`), WS — GPIO13 (`I2S_WS`), SD — GPIO14 +(`I2S_SD`). + +Code: + +1. Add an I2S receiver to `firmware-q`: esp-hal, the `i2s::master` module + (the driver modules are already exposed by the `unstable` feature), + the Philips standard mode, the 16 kHz sample rate (the `window_spec(Q)` + contract), a 32-bit slot, one channel (left), reception over DMA into + a 1024-slot buffer (4 kilobytes). The window is read with one blocking + read right after the servo strike. +2. The slot conversion is already written and pinned by host tests: + `i2s_slots_to_f32` takes the top 24 bits of the 32-bit slot, + sign-extends and divides by 2 to the power 23 — the full scale comes + out at about ±1.0. The byte and channel order is exactly the question + the conversion was lifted into a testable library for. +3. In the main loop replace the `synthetic_tap_window(window)` call with + the DMA window read and conversion. Do not delete `synthetic_tap_window` + from the library: its verdict is pinned by a test and stays the + reference point. +4. A temporary dump mode (behind the same compile constant as the capture + in step 1): print the first window after startup — the spectrum is + computed from it. + +The tone check: + +1. Play a 1 kHz tone near the microphone (a generator or a phone tone + app, normal speech loudness). +2. Take a window dump and compute the spectrum on the host by any means + (for example numpy: the discrete Fourier transform of the 1024 + values). +3. Expectation: the peak in bin 64. The math: the bin width is 16000 Hz + / 1024 = 15.625 Hz; 1000 Hz / 15.625 = bin 64; the ±5 percent + tolerance — the 950–1050 Hz band, bins 61–67. +4. The sample rate is confirmed by the same peak: were it half, the 1 kHz + tone would land in bin 32. The window duration is 1024 / 16000 = + exactly 64 ms. +5. Amplitude: in silence the values are around 0.001–0.01, on the tone — + 0.1–0.5. + +Failure scenarios: exact zeros in every window — the L/R channel-select +level is wrong, or SCK and WS are swapped; a raw-slot dump (before +conversion) will show zeros immediately. The peak in bin 32 — the actual +sample rate is 8 kHz, an I2S configuration error. Noise across all bins +instead of a peak — pickup on the data line: shorten the wires, check the +common GND. + +Record in NOTES: the discovered byte and channel order (whether it +matched the conversion contract), the peak bin, the silence and tone +amplitudes. + +After this step — re-release the images into `firmware/bin/` by the same +procedure as 2026-09-14 (a new timestamp, hashes, the old set to +`tmp/trash/`), because the Q firmware behavior changes. + +## Step 5 — S5. Node Q: the servo tapper and the verdicts on reference parts (~3 h) + +The goal: the verdicts depend on the part. A good part yields mostly +`good`, a cracked one — mostly `cracked`. + +Servo wiring: the SG90 signal wire to GPIO11 +(`board::node_q::SERVO_PWM`); the servo power — only from a separate 5 V +supply rated at least 1 A, the supply's minus tied to the board's GND; a +470 μF capacitor across the servo supply terminals. The failure scenario: +if the servo is powered from the board's USB port, at the strike moment +the servo draws an inrush of about 0.5–1 A, the 5 V rail sags below the +processor's brownout threshold, and the board reboots mid-tap — in the +monitor it looks like a sudden repeat of the boot banner. + +What is already implemented: a 50 Hz PWM with 14-bit duty; the strike — +6 percent duty (a ~1.2 ms pulse), rest — 8 percent (~1.6 ms); after the +strike a 30 ms pause (the arm leaves, the part's ring begins), then the +64 ms window; the full cycle is 400 ms. Where the arm strikes and its +length are mechanical-assembly parameters, not code. + +Actions: + +1. Mount the servo over the part holder so that the arm touches the part + on actuation — the strike must be audible. +2. Power the servo from the separate supply and make sure the board does + not reboot across ten consecutive strikes (no repeated boot banners in + the monitor). +3. Five good parts: at least three strikes each, write down all the + verdicts. +4. Five cracked parts: the same. +5. If the dump mode from step 4 is still in the firmware — capture one + window for a good and a cracked part: the spectra must differ + noticeably. + +Pass criteria: for each part at least two thirds of the strikes give its +class verdict; in total at least eight of ten parts are classified +correctly. Every discrepancy — into NOTES with a description of the part. + +Failure scenarios: the verdicts do not depend on the part (for example +all `cracked`, as with the synthetic) — the window missed the ring; a +dump will show the sound starting after the window start, and then the +post-strike pause is increased in the firmware (the `SETTLE_MS` constant, +currently 30 ms, the next sensible step is 50 ms), or the servo +physically misses the part. The verdicts are right but the board reboots +on strikes — the supply is still insufficient: at the strike moment the +servo terminals must stay above roughly 4.5 V by multimeter. + +Record in NOTES: the servo strike current (a multimeter in the supply +break), the per-part verdict table, any `SETTLE_MS` changes. + +## Step 6 — S6. Node P: part counting (~2 h) + +The goal: the counter equals the fact — N passed parts give exactly N +increments, and the bounce at the sensor edge creates no false counts. + +The mandatory pre-mount check: node P lives on the CAM board, the sensor +is assigned to GPIO5 (`board::node_p::IR_OUT`), but the OV2640 camera +wiring on boards from different vendors occupies different pins, and on +some boards GPIO5 belongs to the camera. Check your board's pinout +against its schematic: if GPIO5 is taken, the sensor moves to a free pin +and the fix lands in the `board` crate (the bench-configuration level; +the node contract does not change). The `used_pins_are_free_and_distinct` +test will verify that the new pin is not in the reserved list: 0, 3, 45, +46 — the boot-strapping pins; 19, 20 — the USB lines; 43, 44 — the UART0 +console; 35, 36, 37 — the octal-PSRAM lines of the N16R8 module. + +TCRT5000 wiring: the module with a comparator — VCC per the module's +marking (3.3 or 5 V), GND common, OUT to GPIO5. The sensor is reflective +infrared: it triggers on reflection from an object at roughly 1–25 mm, +the threshold is tuned by the on-module trimmer. + +Actions: + +1. Tune the distance: a part at 5–10 mm from the sensor — the module's + LED lights up. +2. The quick wire check from step 0.5 (every correct touch longer than + 60 ms — exactly one increment). +3. Run 1: 20 slow passes of a part (or a finger) — the final count + exactly 20. +4. Run 2: 20 passes at the pace of the future belt. +5. The bounce test: hold a target at the triggering edge and wiggle it + for five seconds. + +Pass criteria: in both runs the count-versus-fact deviation is zero; the +bounce test gives at most two extra increments in five seconds (the 50 ms +debounce suppresses series shorter than 50 ms; sustained re-crossings +longer than 50 ms are real events already). + +Failure scenarios: the count is double the fact — the signal drops below +the threshold and returns mid-pass (visible on an OUT-level recording): +increase the sensor gap or reduce the sensitivity with the trimmer. The +count is zero although the module's LED triggers — the module has an +inverted output (active low): check the OUT level at rest and at +triggering with a tester; on inversion the fix goes into the pin-reading +driver in the firmware (not the contract) and is recorded in NOTES. + +Record in NOTES: the trigger polarity, the distance, the results of both +runs, the bounce-test result. + +## Step 7 — S7. The track gate and the phase-2 decision (~2–3 h) + +The goal: shape the S0–S6 results into verifiable artifacts and make the +phase-2 decision. + +Actions: + +1. Finalize `firmware/NOTES.md`: each session must have its numbers — S1: + `zero`, the linearity table, the tolerance; S2: the status delays; S3: + the cross-check table; S4: the I2S byte and channel order, the tone + bin; S5: the servo current, the per-part verdicts; S6: the sensor + polarity and distance, the counting results. +2. Write `docs/rus/firmware-gate.md` and `docs/eng/firmware-gate.md` + following the weekly-gate pattern (week1–week6-gate). The track gate + checklist is listed in the decomposition: the A statuses match the + host ones on the same data; the Q verdicts on reference parts match + the model on the same windows; the P count equals the run's fact; the + board's capture CSV is readable by the host tooling and reproducible; + the CI `firmware` job is green; NOTES is filled. For every item — + passed, or a deviation with the reason. The "blinky skipped, the + working firmware-a flashed at once" deviation is already in NOTES — + carry it into the gate too. +3. The phase-2 decision with justification: the UART bridge (already + written and tested, not a single line of network code on the boards) + versus esp-wifi with a ported mqtt-min (a week of extra work plus the + risk of the bench's Wi-Fi environment). Recommendation: the bridge — + it closes the goal today; Wi-Fi — into the report's future work. +4. Check that both CI jobs (`firmware` — the xtensa build with the new + dependency, `firmware-host` — formatting, clippy, the library tests) + are green on the latest commit. + +Pass criterion: both gate docs are written, every checklist item has a +status, the phase-2 decision is recorded with its justification. + +## Step 8 — phase 2: the bench in the full OEE loop (~1–2 h, after the gate) + +The goal: the physical bench replaces the host nodes in the week-5 loop — +the three boards' lines reach MQTT through the UART bridge, the +aggregator folds them into OEE, the dashboard shows live metrics. + +Actions: + +1. Terminal 1 — the loop's tail end (the "The bench loop" section of + `firmware/README.md`): + `./target/debug/broker 1883 &`; + `./target/debug/aggregator --mqtt 127.0.0.1:1883 --ideal-cycle-ms 400 --out windows.csv`; + `./target/debug/oee-dashboard --mqtt 127.0.0.1:1883`. +2. Terminals 2–4 — one bridge per board, the ports by the serials: + `cat /dev/serial/by-id/usb-1a86_USB_Single_Serial_5C94148486-if00 | cargo run -p firmware-tools --bin uart-bridge -- 127.0.0.1:1883` + and two analogous commands for the serials `5C94152266` (Q) and + `5CCC048683` (P). The bridge drops the boot lines (pinned by its + tests), publishes the statuses, verdicts and counts to `oee/line1/*`, + and on input-stream break prints the end marker — the aggregator + closes the window correctly. +3. The no-hardware check (known): feed the bridge four lines via printf — + expect `bridge: 4 messages (a+p+q)` in the output. +4. The live run: turn on the physical stimuli — the current for node A, + the taps for Q, the part passes for P — and watch the dashboard. + +Pass criterion: over a five-minute run the dashboard shows A status +changes, Q verdicts with every tap, the P count equal to the passed +parts, and `windows.csv` fills with window rows without gaps between the +end markers. + +Failure scenarios: the dashboard is empty, the bridge is silent — the +board line does not pass the bridge's whitelist; compare the actual line +from the monitor with the printf-test line format (`a,bench-a,1000,run`). +The OEE does not converge with the expectation — the 400 ms ideal-cycle +parameter is given to the aggregator while the bench's actual tap pace +differs; reconcile `--ideal-cycle-ms` with the fact (it is an aggregator +parameter, the firmware does not change). + +A documentation note: the assembly guide +(`docs/eng/HARDWARE-assembly-guide.md` and its Russian original) names +the ports with the old `/dev/ttyUSB0` — on this bench the actual names +are `/dev/ttyACM*` or by-id; update the guide when convenient. diff --git a/docs/eng/decompose/firmware.md b/docs/eng/decompose/firmware.md index 84c683d..e524107 100644 --- a/docs/eng/decompose/firmware.md +++ b/docs/eng/decompose/firmware.md @@ -12,6 +12,7 @@ > and the stretch camera; the sensors ACS712-20A, INMP441, TCRT5000, a servo > tapper, a separate 5 V supply. Bench details and the bring-up procedure — > [`firmware/README.md`](../../../firmware/README.md). +> The detailed execution runbook (wiring, commands, acceptance criteria) — [firmware-shakedown-runbook.md](firmware-shakedown-runbook.md). > Input: the contracts are already in the repo — `features-cli` > (`window_spec`, the ADC→amps calibration, the `capture` schema), diff --git a/docs/rus/decompose/firmware-shakedown-runbook.md b/docs/rus/decompose/firmware-shakedown-runbook.md new file mode 100644 index 0000000..4a5cdd1 --- /dev/null +++ b/docs/rus/decompose/firmware-shakedown-runbook.md @@ -0,0 +1,421 @@ +# Колея железа: детальный runbook шейкдауна (обновлено 2026-09-14) + +Английская версия: [firmware-shakedown-runbook.md](../../eng/decompose/firmware-shakedown-runbook.md). + +Контекст: хост-трек (недели 1–6 + отчёт) завершён; 2026-09-14 закрыт S0 — +все три платы прошиты и подняты (релиз `firmware/bin/20260914085743-*`, +журналы — в корневых README, факты — в `firmware/NOTES.md`). План сессий — +`docs/rus/decompose/firmware.md`, руководство по сборке — +`docs/rus/HARDWARE-assembly-guide.md`. Серийники мостов: узел A — +`5C94148486`, узел Q — `5C94152266`, узел P (CAM) — `5CCC048683`; порта +`/dev/ttyUSB*` на этом стенде нет, фактические имена — +`/dev/serial/by-id/usb-1a86_USB_Serial_-if00`. + +| Шаг | Тема | Время | Есть код? | +| --- | ---------------------------------------- | ------ | ---------------------- | +| 0 | хвосты дня: коммит доков, CI | 5 мин | — | +| 0.5 | проверка P проводком | 5 мин | — | +| 1 | S1: узел A, АЦП и калибровка | ~3 ч | + capture-режим | +| 2 | S2: узел A, статусы на режимах | ~2 ч | всё есть | +| 3 | S3: кросс-чек платы и хостового двойника | ~2 ч | всё есть | +| 4 | S4: узел Q, драйвер I2S INMP441 | ~3 ч | + драйвер I2S | +| 5 | S5: узел Q, серво и вердикты | ~3 ч | всё есть | +| 6 | S6: узел P, счёт деталей | ~2 ч | всё есть | +| 7 | S7: гейт колеи и решение фазы 2 | ~2–3 ч | + гейт-доки | +| 8 | фаза 2: стенд в полном цикле OEE | ~1–2 ч | всё есть (uart-bridge) | + +## Шаг 0 — хвосты дня (5 мин) + +Закоммитить документацию дня (корневые README×2, `firmware/README×2`, +`firmware/NOTES.md`, `firmware/bin/20260914085743-build-info.md`) и +посмотреть CI на ветке `hardware-firmware`: джоба `firmware` впервые +собирает рабочее дерево с зависимостью `esp-bootloader-esp-idf` 0.6 +(требует rustc не ниже 1.95; CI ставит espup свежей версии, ожидаемо +зелёная). Если джоба красная именно на версии rustc — зафиксировать факт в +NOTES и откатить зависимость на версию 0.5 (минимальный rustc 1.88). + +## Шаг 0.5 — проверка узла P проводком (5 мин) + +Проводником замкнуть GPIO5 на 3V3: держать не меньше 60 мс, отпустить, +повторить. Антидребезг 50 мс игнорирует замыкания короче 50 мс, поэтому +каждое корректное касание даёт ровно один инкремент счётчика — в мониторе +появляется строка `p,bench-p,,N` со следующим значением N. Если +счётчик растёт от коротких касаний (меньше 50 мс), значит константа +`DEBOUNCE_MS` в прошивке не соответствует фактическому параметру — это +находка для NOTES, а не для молчаливой правки. + +## Шаг 1 — S1. Узел A: АЦП и калибровка (~3 ч) + +Цель: доказать числами, что ток, который измеряет плата, совпадает с +физическим током. В покое прошивка обязана показывать ноль (в пределах +допуска), при известной нагрузке — совпадать с мультиметром. + +Подключение: + +- ACS712-20A: VCC — 5 В, GND — общий с платой, OUT — через резисторный + делитель 2:1 (например, 10 кОм + 10 кОм) на GPIO4 + (`board::node_a::ADC_CURRENT`). Делитель обязателен: у ACS712-20A выход + при нуле тока равен 2.5 В, чувствительность 100 мВ на ампер, рабочий + диапазон 0.5–4.5 В; АЦП ESP32-S3 с ослаблением 11 дБ измеряет примерно до + 3.1 В, делитель переводит диапазон датчика в 0.25–2.25 В. +- GPIO4 выбран потому, что это канал ADC1: каналы ADC2 на ESP32-S3 + конфликтуют с Wi-Fi, а фаза 2 теоретически может его задействовать. + +Что уже реализовано: при старте узел усредняет 800 замеров с периодом +625 мкс (полсекунды, частота 1.6 кГц) и печатает `a: zero=NNN` — +стартовую точку нуля (`CurrentCalibration::acs712_20a_div2()` плюс +`with_zero_counts`). Дальше каждые 128 замеров (окно 80 мс) окно целиком +подается в `model_a`; смена статуса печатается только после подтверждения +двумя окнами подряд (гистерезис ×2) строкой `a,bench-a,,`. + +Чего не хватает (код этой сессии): capture-режима. Сейчас плата печатает +только статусы, а для сверки с мультиметром и для шага 3 нужны строки +захвата по схеме `features_cli::capture` — `t_ms,node,run_id,value,state,note`. +Добавить в `firmware-a` за константой времени компиляции: раз в окно (80 мс) +печатать строку со средним током окна в амперах. Оценка трафика: 12.5 +строки в секунду по ~35 байт — это около 440 байт в секунду при +пропускной способности UART 115200 бод примерно 11.5 килобайт в секунду, +запас двадцатикратный. Печатать каждый отдельный замер нельзя: 1600 строк +в секунду теми же ~35 байтами дают 56 килобайт в секунду, UART не успеет и +часть строк потеряется. + +Действия: + +1. Подключить датчик по схеме выше; нагрузку не подавать. +2. Пересобрать образ с capture-режимом и прошить плату A (порт по + серийнику `5C94148486`), открыть монитор с записью в файл. +3. Покой: 60 секунд записи. Критерий: среднее `value` не больше 0.05 А по + модулю, размах (максимум минус минимум) не больше 0.1 А. Обоснование + допуска: один отсчёт АЦП соответствует примерно 15 мА (3.1 В / 4095 + отсчётов — это 0.76 мВ на отсчёт на пине платы, делитель удваивает + напряжение, а чувствительность датчика 100 мВ на ампер); 0.05 А — это + три отсчёта. +4. Линейность: мультиметр в режиме измерения тока 10 А последовательно с + нагрузкой. Три уровня: 0.5 А, 1.0 А, 2.0 А — резисторы на 5 В: 10 Ом + мощностью не меньше 5 Вт (ток 0.5 А), 5.1 Ом не меньше 10 Вт (около + 1.0 А), 2.7 Ом не меньше 25 Вт (около 1.9 А). На каждом уровне 30 + секунд записи. +5. Для каждого уровня вычислить среднее `value` из файла захвата и + сравнить с показанием мультиметра. + +Критерии пройдено: покой — по пункту 3; линейность — на каждом уровне +расхождение платы с мультиметром не больше, чем большая из величин 0.1 А +и 5 процентов показания; значение `a: zero=` между двумя перезагрузками +(Ctrl+R в мониторе) меняется не больше чем на 5 отсчётов. + +Сценарии неудач и их смысл: показания примерно вдвое меньше мультиметра — +делитель не 2:1 или модуль датчика другой чувствительности (у ACS712-5A +это 185 мВ на ампер; сверить маркировку модуля с константой +`acs712_20a_div2`); ноль уехал при стабильном `zero=` — калибровка +выполнялась до подключения датчика, перезагрузить плату уже с датчиком; +значения скачут на ±0.5 А — нет общего провода GND между питанием датчика +и платой либо вход висит. + +Записать в NOTES: значение `zero` с подключённым датчиком, таблицу +«уровень, плата (А), мультиметр (А), отклонение», итоговый допуск. + +## Шаг 2 — S2. Узел A: статусы на режимах (~2 ч) + +Цель: показать, что статус следует за физическим режимом с задержкой в +известных границах и не меняется сам по себе внутри режима. + +Сценарий: узел различает режимы по току; физически имитируем их той же +нагрузкой из шага 1 — чередуем 0 А (30 секунд, режим покоя) и 1–2 А (30 +секунд, рабочий режим), не меньше пяти переключений в каждую сторону и не +чаще одного переключения в две секунды. + +Откуда берутся границы задержки: окно 128 замеров на частоте 1.6 кГц — это +80 мс; гистерезис подтверждает смену только после двух окон подряд. Значит +смена статуса физически не может появиться раньше чем через 80 мс и не +позже чем через три окна, то есть 240 мс. + +Действия: записать монитор в файл на весь прогон; для каждой смены +посчитать задержку — время строки статуса минус фактическое время +переключения по секундомеру; внутри 30-секундных фаз посчитать +самопроизвольные смены. + +Критерии пройдено: каждая из десяти и более смен укладывается в интервал +80–500 мс (240 мс теории плюс запас на погрешность секундомера); +самопроизвольных смен внутри фаз — ноль на не менее чем пяти минутах +записи. + +Сценарии неудач: задержка стабильно больше секунды — фактический рабочий +ток ниже того, что в обучающей выборке соответствует рабочему режиму +(сравнить `value` во время фазы с данными эксперимента недели 4); статус +меняется туда-обратно на границе — ток фазы сидит ровно на пороге +разделения классов, поднять уровень рабочего тока (менять гистерезис +нельзя — контракт ×2 закреплен тестами). + +Записать в NOTES: количество смен, минимальную, медианную и максимальную +задержки, количество самопроизвольных смен. + +## Шаг 3 — S3. Кросс-чек: плата против хостового двойника (~2 ч) + +Цель: на одном и том же физическом прогоне доказать, что железный узел A +и хостовый узел A выдают одинаковые последовательности статусов, а +расхождения возможны только у границ окон. + +Почему расхождения у границ — это нормально: обе стороны используют одну +и ту же модель (rust-born `model_a` с бит-точным кернелем Conv1D), один и +тот же гистерезис ×2 и один и тот же вход — ток в амперах, посчитанный на +плате. Единственное отличие — фаза окон: окна платы начинаются от момента +её старта, окна хостового узла — от первой строки входного файла, поэтому +замеры на границе переключения режима попадают в разные окна. + +Действия: + +1. Взять файл захвата с платы из шага 1 или 2 (запись монитора) — + `tmp/s3.capture.csv`. +2. Конвертировать его утилитой из репозитория: она читает стандартный + ввод, переименовывает колонку `value` в `current_a`, отбирает строки + узла A и пишет готовый вход хостового узла: + `cargo run --release -p firmware-tools --bin capture-to-run < tmp/s3.capture.csv > tmp/s3.run.csv` + (в stderr утилита печатает счётчик: сколько строк взято и сколько + пропущено — заголовок, чужие узлы, битые строки). +3. Прогнать хостовый узел на том же прогоне: + `cargo run --release -p nodes --bin node -- --kind a --input tmp/s3.run.csv --offline tmp/s3.statuses.csv --run-id bench-a` + (без `--mqtt` — сверка офлайн, брокер не нужен). +4. Сравнить две последовательности статусов: строки `a,bench-a,…` из + записи монитора платы против строк из `tmp/s3.statuses.csv`. + +Критерии пройдено: сто процентов совпадения статусов вне зон, +прилегающих к переключениям режима (зона — два окна, 160 мс, с каждой +стороны от переключения); внутри зон расхождения допустимы. Любое +расхождение вне зон — находка уровня эскалации из плана колеи («почему +паритет-тест это не поймал»): причину чинить и закрывать тестом, а не +соглашаться с расхождением. + +Записать в NOTES: таблицу «всего смен, совпало, расхождения, все ли +расхождения в пограничных зонах». + +## Шаг 4 — S4. Узел Q: драйвер I2S INMP441 (~3 ч, единственный большой кусок кода) + +Цель: заменить синтетическое окно реальным звуком. Сейчас вердикт узла Q +всегда `cracked` — это закреплено тестом как проверка контура; смысл +вердикты обретут только на реальном микрофоне. + +Подключение INMP441: VDD — строго 3.3 В (от 5 В модуль выходит из строя), +GND — общий, вход выбора канала L/R — на GND (левый канал), SCK — GPIO12 +(`board::node_q::I2S_SCK`), WS — GPIO13 (`I2S_WS`), SD — GPIO14 +(`I2S_SD`). + +Код: + +1. В `firmware-q` добавить приёмник I2S: esp-hal, модуль `i2s::master` + (драйверные модули уже открыты фичей `unstable`), стандартный режим + Philips, частота дискретизации 16 кГц (это контракт + `window_spec(Q)`), слот 32 бита, один канал (левый), приём через DMA в + буфер на 1024 слота (4 килобайта). Окно читается одним блокирующим + чтением сразу после удара серво. +2. Конвертация слотов уже написана и закреплена хостовыми тестами: + `i2s_slots_to_f32` берёт старшие 24 бита 32-битного слота, растягивает + по знаку и делит на 2 в степени 23 — полный масштаб получается около + ±1.0. Порядок байтов и каналов — именно тот вопрос, ради которого + конвертация вынесена в тестируемую библиотеку. +3. В главном цикле заменить вызов `synthetic_tap_window(window)` на чтение + DMA-окна и конвертацию. Саму функцию `synthetic_tap_window` из + библиотеки не удалять: её вердикт закреплён тестом и остаётся + контрольным ориентиром. +4. Временный дамп-режим (за той же константой компиляции, что и capture в + шаге 1): напечатать значения первого окна после старта — по нему + считается спектр. + +Проверка тоном: + +1. Рядом с микрофоном включить тон 1 кГц (генератор или приложение тона + на телефоне, громкость обычной речи). +2. Снять дамп окна и посчитать спектр на хосте любым способом (например, + numpy: модуль дискретного преобразования Фурье от 1024 значений). +3. Ожидание: пик в бине 64. Расчёт: ширина бина 16000 Гц / 1024 = 15.625 + Гц; 1000 Гц / 15.625 = бин 64; допуск ±5 процентов — полоса 950–1050 Гц, + бины 61–67. +4. Частота дискретизации подтверждается тем же пиком: если бы она была + вдвое ниже, тон 1 кГц оказался бы в бине 32. Длительность окна 1024 / + 16000 = ровно 64 мс. +5. Амплитуда: в тишине значения порядка 0.001–0.01, на тоне — 0.1–0.5. + +Сценарии неудач: на всех окнах ровно ноль — не тот уровень на входе +выбора канала L/R либо SCK и WS переставлены местами; дамп сырых слотов +(до конвертации) сразу покажет нули. Пик в бине 32 — фактическая частота +дискретизации 8 кГц, ошибка в конфигурации I2S. Вместо пика шум по всем +бинам — наводка на линию данных: укоротить провода, проверить общий GND. + +Записать в NOTES: найденный порядок байтов и каналов (совпал ли с +контрактом конвертации), бин пика, амплитуды тишины и тона. + +После этого шага — перевыпуск образов в `firmware/bin/` по той же +процедуре, что 2026-09-14 (новая метка времени, хеши, старый сет в +`tmp/trash/`), потому что меняется поведение прошивки Q. + +## Шаг 5 — S5. Узел Q: серво-тапер и вердикты на эталонных деталях (~3 ч) + +Цель: вердикты зависят от детали. Годная деталь даёт преимущественно +`good`, треснувшая — преимущественно `cracked`. + +Подключение серво: сигнальный провод SG90 на GPIO11 +(`board::node_q::SERVO_PWM`); питание серво — только от отдельного блока +5 В на ток не меньше 1 А, минус блока соединить с GND платы; конденсатор +470 мкФ на клеммах питания серво. Сценарий ошибки: если запитать серво от +USB-порта платы, то в момент удара серво потребляет бросок порядка +0.5–1 А, шина 5 В проседает ниже порога срабатывания защиты процессора, +и плата перезагружается посреди такта — в мониторе это выглядит как +внезапный повтор загрузочного баннера. + +Что уже реализовано: ШИМ 50 Гц с 14-битным заполнением; удар — заполнение +6 процентов (импульс примерно 1.2 мс), покой — 8 процентов (примерно +1.6 мс); после удара пауза 30 мс (рычаг отходит, звон детали начинается), +затем окно 64 мс; полный такт 400 мс. Где бьёт рычаг и какой он длины — +параметр механической сборки, кода это не касается. + +Действия: + +1. Смонтировать серво над держателем детали так, чтобы рычаг касался + детали при срабатывании — удар должен быть слышен. +2. Включить питание серво от отдельного блока и убедиться, что на десяти + подряд ударах плата не перезагружается (в мониторе нет повторных + загрузочных баннеров). +3. Пять годных деталей: для каждой не меньше трёх ударов, записать все + вердикты. +4. Пять треснувших деталей: аналогично. +5. Если дамп-режим из шага 4 ещё в прошивке — снять по одному окну для + годной и треснувшей детали: спектры должны заметно отличаться. + +Критерии пройдено: для каждой детали не меньше двух третей ударов дают +вердикт её класса; в сумме не меньше восьми из десяти деталей +классифицированы верно. Каждое расхождение — в NOTES с описанием детали. + +Сценарии неудач: вердикты не зависят от детали (например, все `cracked`, +как на синтетике) — окно не попало на звон детали; дамп покажет, что звук +начинается позже начала окна, и тогда пауза после удара увеличивается в +прошивке (константа `SETTLE_MS`, сейчас 30 мс, следующий разумный шаг — +50 мс), либо серво физически не касается детали. Вердикты верные, но +плата перезагружается на ударах — питания всё ещё не хватает: +мультиметром в момент удара на клеммах серво должно оставаться не ниже +примерно 4.5 В. + +Записать в NOTES: ток серво при ударе (мультиметр в разрыв питания), +таблицу вердиктов по деталям, правки `SETTLE_MS`, если были. + +## Шаг 6 — S6. Узел P: счёт деталей (~2 ч) + +Цель: счётчик равен факту — N прошедших деталей дают ровно N инкрементов, +дребезг на краю датчика не создаёт ложных. + +Обязательная проверка до монтажа: узел P живет на CAM-плате, датчик +назначен на GPIO5 (`board::node_p::IR_OUT`), но обвязка камеры OV2640 на +платах разных производителей занимает разные пины, и на части плат GPIO5 +принадлежит камере. Сверить распиновку вашей платы со схемой: если GPIO5 +занят, датчик переносится на свободный пин, а правка делается в крейте +`board` (уровень конфигурации стенда; контракт узла не меняется). Тест +`used_pins_are_free_and_distinct` проверит, что новый пин не входит в +зарезервированный список: 0, 3, 45, 46 — пины режимов загрузки; 19, 20 — +линии USB; 43, 44 — консоль UART0; 35, 36, 37 — линии восьмиразрядной +PSRAM модуля N16R8. + +Подключение TCRT5000: модуль с компаратором — VCC по маркировке модуля +(3.3 или 5 В), GND общий, OUT на GPIO5. Датчик рефлективный +инфракрасный: срабатывает на отражение от объекта на расстоянии +примерно 1–25 мм, порог подстраивается резистором на модуле. + +Действия: + +1. Подстроить расстояние: деталь на 5–10 мм от датчика — светодиод + модуля загорается. +2. Быстрая проверка из шага 0.5 проводником (каждое корректное касание + длиннее 60 мс — ровно один инкремент). +3. Прогон 1: 20 медленных проходов детали (или пальца) — итоговый счёт + ровно 20. +4. Прогон 2: 20 проходов в темпе будущей ленты. +5. Тест дребезга: держать цель на краю срабатывания и шевелить её пять + секунд. + +Критерии пройдено: в обоих прогонах расхождение счёта с фактом — ноль; +тест дребезга даёт не больше двух лишних инкрементов за пять секунд +(антидребезг 50 мс подавляет серии короче 50 мс; устойчивые повторные +пересечения порога длиннее 50 мс — это уже реальные события). + +Сценарии неудач: счёт вдвое больше факта — сигнал во время прохода +уходит за порог и возвращается (видно на записи уровня OUT): увеличить +зазор датчика или уменьшить чувствительность резистором. Счёт ноль, хотя +светодиод модуля срабатывает, — у модуля инвертированный выход +(активный низкий уровень): проверить тестером уровень OUT в покое и при +срабатывании; при инверсии правка делается в драйвере чтения пина в +прошивке (не в контракте) и фиксируется в NOTES. + +Записать в NOTES: полярность срабатывания, дистанцию, итоги обоих +прогонов, результат теста дребезга. + +## Шаг 7 — S7. Гейт колеи и решение фазы 2 (~2–3 ч) + +Цель: оформить результаты S0–S6 в проверяемые артефакты и принять решение +по фазе 2. + +Действия: + +1. Финализировать `firmware/NOTES.md`: по каждой сессии должны стоять + числа — S1: `zero`, таблица линейности, допуск; S2: задержки статусов; + S3: таблица кросс-чека; S4: порядок байтов и каналов I2S, бин тона; + S5: ток серво, вердикты по деталям; S6: полярность и дистанция + датчика, итоги счёта. +2. Написать `docs/rus/firmware-gate.md` и `docs/eng/firmware-gate.md` по + образцу недельных гейтов (week1–week6-gate). Чеклист гейта трека + перечислен в декомпозиции: статусы A совпадают с хостовыми на тех же + данных; вердикты Q на эталонных деталях совпадают с моделью на тех же + окнах; счёт P равен факту прогона; capture-CSV с платы читается + хостовыми инструментами и воспроизводим; джоба CI `firmware` зелёная; + NOTES заполнен. По каждому пункту — пройдено или отступление с + причиной. Отклонение «пропущен blinky, сразу прошит рабочий firmware-a» + уже зафиксировано в NOTES — перенести его и в гейт. +3. Решение фазы 2 с обоснованием: UART-мост (уже написан и протестирован, + на платах нет ни строчки сетевого кода) против esp-wifi с + портированием mqtt-min (неделя дополнительной работы плюс риск + Wi-Fi-окружения на стенде). Рекомендация: мост — он закрывает цель + уже сегодня; Wi-Fi — в будущую работу отчёта. +4. Проверить, что обе джобы CI (`firmware` — сборка под xtensa с новой + зависимостью, `firmware-host` — форматирование, clippy, тесты + библиотек) зелёные на последнем коммите. + +Критерий пройдено: оба гейт-дока написаны, у каждого пункта чеклиста +есть статус, решение фазы 2 зафиксировано с обоснованием. + +## Шаг 8 — фаза 2: стенд в полном цикле OEE (~1–2 ч, после гейта) + +Цель: физический стенд заменяет хостовые узлы в цикле недели 5 — строки +трёх плат попадают через UART-мост в MQTT, агрегатор сворачивает их в +OEE, дашборд показывает живые метрики. + +Действия: + +1. Терминал 1 — хвостовая часть цикла (раздел «Цикл стенда» в + `firmware/README.ru.md`): + `./target/debug/broker 1883 &`; + `./target/debug/aggregator --mqtt 127.0.0.1:1883 --ideal-cycle-ms 400 --out windows.csv`; + `./target/debug/oee-dashboard --mqtt 127.0.0.1:1883`. +2. Терминалы 2–4 — по одному мосту на плату, порта по серийникам: + `cat /dev/serial/by-id/usb-1a86_USB_Serial_5C94148486-if00 | cargo run -p firmware-tools --bin uart-bridge -- 127.0.0.1:1883` + и две аналогичные команды для серийников `5C94152266` (Q) и + `5CCC048683` (P). Мост отбрасывает загрузочные строки (закреплено его + тестами), публикует статусы, вердикты и счёт в `oee/line1/*`, а при + обрыве входного потока печатает конечный маркер — агрегатор корректно + закрывает окно. +3. Проверка без железа (известная): подать на вход моста четыре строки + через printf — ожидать в выводе `bridge: 4 messages (a+p+q)`. +4. Живой прогон: включить физические воздействия — ток для узла A, тапы + для Q, проходы деталей для P — и наблюдать дашборд. + +Критерий пройдено: за пять минут прогона дашборд показывает смену статусов +A, вердикты Q с каждым тапом, счёт P равен числу прошедших деталей, а +файл `windows.csv` наполняется строками окон без пропусков между +конечными маркерами. + +Сценарии неудач: дашборд пуст, мост молчит — строка платы не проходит +белый список моста; сверить фактическую строку из монитора с форматом +строк из printf-теста (`a,bench-a,1000,run`). OEE не сходится с +ожиданием — параметр идеального цикла 400 мс задан агрегатору, а +фактический темп тапов стенда другой; согласовать `--ideal-cycle-ms` с +фактом (это параметр агрегатора, прошивка не меняется). + +Примечание к документации: в руководстве по сборке +(`docs/rus/HARDWARE-assembly-guide.md` и английский аналог) порты указаны +старым именем `/dev/ttyUSB0` — на этом стенде фактические имена +`/dev/ttyACM*` или by-id; при случае обновить руководство. diff --git a/docs/rus/decompose/firmware.md b/docs/rus/decompose/firmware.md index 93f99c1..419cffe 100644 --- a/docs/rus/decompose/firmware.md +++ b/docs/rus/decompose/firmware.md @@ -11,6 +11,7 @@ > stretch-камера; сенсоры ACS712-20A, INMP441, TCRT5000, серво-таппер, > отдельный БП 5 В. Подробности стенда и процедура оживления — > [`firmware/README.md`](../../../firmware/README.md). +> Детальный runbook выполнения (подключение, команды, критерии приёмки) — [firmware-shakedown-runbook.md](firmware-shakedown-runbook.md). > Вход: контракты уже в репо — `features-cli` (`window_spec`, калибровка > ADC→амперы, схема `capture`), `nodes::source::SensorSource` diff --git a/firmware/README.md b/firmware/README.md index 8acd011..402f25d 100644 --- a/firmware/README.md +++ b/firmware/README.md @@ -2,6 +2,7 @@ Russian version: [README.ru.md](README.ru.md). The shakedown plan (sessions S0–S7, the gate): [docs/eng/decompose/firmware.md](../docs/eng/decompose/firmware.md). +The detailed runbook (criteria, commands, tolerances) — [docs/eng/decompose/firmware-shakedown-runbook.md](../docs/eng/decompose/firmware-shakedown-runbook.md). ESP32-S3 firmwares for nodes A/P/Q. The bench: 2× ESP32-S3-DevKitC-1 (N16R8) — nodes A and Q; 1× ESP32-S3-WROOM-1 N16R8 **CAM** with an on-board OV2640 — diff --git a/firmware/README.ru.md b/firmware/README.ru.md index 4b63873..2130174 100644 --- a/firmware/README.ru.md +++ b/firmware/README.ru.md @@ -2,6 +2,7 @@ Английская версия: [README.md](README.md). План шейкдауна (сессии S0–S7, гейт): [docs/rus/../eng/decompose/firmware.md](../docs/eng/decompose/firmware.md). +Детальный runbook (критерии, команды, допуски) — [docs/rus/decompose/firmware-shakedown-runbook.md](../docs/rus/decompose/firmware-shakedown-runbook.md). Прошивки ESP32-S3 для узлов A/P/Q. Стенд: 2× ESP32-S3-DevKitC-1 (N16R8) — узлы A и Q; 1× ESP32-S3-WROOM-1 N16R8 **CAM** с OV2640 на борту — узел P и From 32cb982c2db9231ab061e4d1eb03a893ad6829d2 Mon Sep 17 00:00:00 2001 From: "t.galimkhanov" Date: Mon, 14 Sep 2026 12:31:31 +0500 Subject: [PATCH 6/6] docs: backlog dir refactoring --- ...260909120005-critical-counts-levels-not-edges-firmware-p-bl.md | 0 .../20260909120006-critical-tms-always-zero-firmware-a-bl.md | 0 ...09120007-important-features-cli-contract-drift-workspace-bl.md | 0 .../20260909120031-important-qemu-not-in-ci-ci-bl.md | 0 .../20260909120032-important-q-stack-overflow-firmware-q-bl.md | 0 ...9120033-important-bridge-dies-on-bad-byte-firmware-tools-bl.md | 0 .../{resolved => done}/20260909120041-minor-firmware-bl.md | 0 .../backlog/{resolved => done}/20260909120047-rec-firmware-bl.md | 0 .../20260909120001-critical-invalid-qmeta-json-nodes-bl.md | 0 ...09120002-critical-hang-on-lost-end-marker-oee-aggregator-bl.md | 0 .../20260909120003-critical-midpacket-retry-desync-mqtt-min-bl.md | 0 ...0909120004-critical-clamp-panic-missing-class-ml-trainer-bl.md | 0 ...0909120008-important-f32-time-degradation-line-simulator-bl.md | 0 ...09120009-important-toml-validation-panics-line-simulator-bl.md | 0 ...10-important-unconditional-current-stream-line-simulator-bl.md | 0 ...09120011-important-csv-buffered-in-memory-line-simulator-bl.md | 0 ...260909120012-important-taps-seed-unsalted-line-simulator-bl.md | 0 ...909120013-important-clock-loop-duplicated-line-simulator-bl.md | 0 ...909120014-important-determinism-test-gaps-line-simulator-bl.md | 0 .../20260909120015-important-end-marker-no-retry-nodes-bl.md | 0 .../20260909120016-important-run-id-unvalidated-nodes-bl.md | 0 .../20260909120017-important-taps-bad-row-double-loss-nodes-bl.md | 0 ...20260909120018-important-status-loss-on-disconnect-nodes-bl.md | 0 ...0909120019-important-terminal-not-restored-oee-dashboard-bl.md | 0 ...09120020-important-filter-hardcoded-prefix-oee-dashboard-bl.md | 0 ...9120021-important-zero-err-claim-unpinned-oee-aggregator-bl.md | 0 ...09120022-important-csv-truncate-vs-append-oee-aggregator-bl.md | 0 .../20260909120023-important-no-max-packet-size-mqtt-min-bl.md | 0 .../20260909120024-important-capture-channel-leak-mqtt-min-bl.md | 0 .../20260909120025-important-keepalive-violated-mqtt-min-bl.md | 0 ...60909120026-important-no-connect-write-timeouts-mqtt-min-bl.md | 0 .../20260909120027-important-duplicate-subscribe-mqtt-min-bl.md | 0 ...20028-important-interp-panics-foreign-tflite-ml-exporter-bl.md | 0 ...260909120029-important-bit-identical-untested-ml-trainer-bl.md | 0 ...120030-important-parity-gen-duplicates-quant-ml-exporter-bl.md | 0 .../backlog/{ => open}/20260909120034-minor-line-simulator-bl.md | 0 docs/eng/backlog/{ => open}/20260909120035-minor-nodes-bl.md | 0 .../backlog/{ => open}/20260909120036-minor-oee-aggregator-bl.md | 0 .../backlog/{ => open}/20260909120037-minor-oee-dashboard-bl.md | 0 docs/eng/backlog/{ => open}/20260909120038-minor-mqtt-min-bl.md | 0 docs/eng/backlog/{ => open}/20260909120039-minor-ml-bl.md | 0 docs/eng/backlog/{ => open}/20260909120040-minor-qemu-bl.md | 0 .../backlog/{ => open}/20260909120042-rec-line-simulator-bl.md | 0 docs/eng/backlog/{ => open}/20260909120043-rec-nodes-bl.md | 0 .../backlog/{ => open}/20260909120044-rec-oee-aggregator-bl.md | 0 docs/eng/backlog/{ => open}/20260909120045-rec-mqtt-min-bl.md | 0 docs/eng/backlog/{ => open}/20260909120046-rec-ml-bl.md | 0 .../20260909120048-info-rust-analyzer-macro-artifact-nodes-bl.md | 0 ...04756-important-flaky-aggregator-pipeline-oee-aggregator-bl.md | 0 ...260909120005-critical-counts-levels-not-edges-firmware-p-bl.md | 0 .../20260909120006-critical-tms-always-zero-firmware-a-bl.md | 0 ...09120007-important-features-cli-contract-drift-workspace-bl.md | 0 .../20260909120031-important-qemu-not-in-ci-ci-bl.md | 0 .../20260909120032-important-q-stack-overflow-firmware-q-bl.md | 0 ...9120033-important-bridge-dies-on-bad-byte-firmware-tools-bl.md | 0 .../{resolved => done}/20260909120041-minor-firmware-bl.md | 0 .../backlog/{resolved => done}/20260909120047-rec-firmware-bl.md | 0 .../20260909120001-critical-invalid-qmeta-json-nodes-bl.md | 0 ...09120002-critical-hang-on-lost-end-marker-oee-aggregator-bl.md | 0 .../20260909120003-critical-midpacket-retry-desync-mqtt-min-bl.md | 0 ...0909120004-critical-clamp-panic-missing-class-ml-trainer-bl.md | 0 ...0909120008-important-f32-time-degradation-line-simulator-bl.md | 0 ...09120009-important-toml-validation-panics-line-simulator-bl.md | 0 ...10-important-unconditional-current-stream-line-simulator-bl.md | 0 ...09120011-important-csv-buffered-in-memory-line-simulator-bl.md | 0 ...260909120012-important-taps-seed-unsalted-line-simulator-bl.md | 0 ...909120013-important-clock-loop-duplicated-line-simulator-bl.md | 0 ...909120014-important-determinism-test-gaps-line-simulator-bl.md | 0 .../20260909120015-important-end-marker-no-retry-nodes-bl.md | 0 .../20260909120016-important-run-id-unvalidated-nodes-bl.md | 0 .../20260909120017-important-taps-bad-row-double-loss-nodes-bl.md | 0 ...20260909120018-important-status-loss-on-disconnect-nodes-bl.md | 0 ...0909120019-important-terminal-not-restored-oee-dashboard-bl.md | 0 ...09120020-important-filter-hardcoded-prefix-oee-dashboard-bl.md | 0 ...9120021-important-zero-err-claim-unpinned-oee-aggregator-bl.md | 0 ...09120022-important-csv-truncate-vs-append-oee-aggregator-bl.md | 0 .../20260909120023-important-no-max-packet-size-mqtt-min-bl.md | 0 .../20260909120024-important-capture-channel-leak-mqtt-min-bl.md | 0 .../20260909120025-important-keepalive-violated-mqtt-min-bl.md | 0 ...60909120026-important-no-connect-write-timeouts-mqtt-min-bl.md | 0 .../20260909120027-important-duplicate-subscribe-mqtt-min-bl.md | 0 ...20028-important-interp-panics-foreign-tflite-ml-exporter-bl.md | 0 ...260909120029-important-bit-identical-untested-ml-trainer-bl.md | 0 ...120030-important-parity-gen-duplicates-quant-ml-exporter-bl.md | 0 .../backlog/{ => open}/20260909120034-minor-line-simulator-bl.md | 0 docs/rus/backlog/{ => open}/20260909120035-minor-nodes-bl.md | 0 .../backlog/{ => open}/20260909120036-minor-oee-aggregator-bl.md | 0 .../backlog/{ => open}/20260909120037-minor-oee-dashboard-bl.md | 0 docs/rus/backlog/{ => open}/20260909120038-minor-mqtt-min-bl.md | 0 docs/rus/backlog/{ => open}/20260909120039-minor-ml-bl.md | 0 docs/rus/backlog/{ => open}/20260909120040-minor-qemu-bl.md | 0 .../backlog/{ => open}/20260909120042-rec-line-simulator-bl.md | 0 docs/rus/backlog/{ => open}/20260909120043-rec-nodes-bl.md | 0 .../backlog/{ => open}/20260909120044-rec-oee-aggregator-bl.md | 0 docs/rus/backlog/{ => open}/20260909120045-rec-mqtt-min-bl.md | 0 docs/rus/backlog/{ => open}/20260909120046-rec-ml-bl.md | 0 .../20260909120048-info-rust-analyzer-macro-artifact-nodes-bl.md | 0 ...04756-important-flaky-aggregator-pipeline-oee-aggregator-bl.md | 0 98 files changed, 0 insertions(+), 0 deletions(-) rename docs/eng/backlog/{resolved => done}/20260909120005-critical-counts-levels-not-edges-firmware-p-bl.md (100%) rename docs/eng/backlog/{resolved => done}/20260909120006-critical-tms-always-zero-firmware-a-bl.md (100%) rename docs/eng/backlog/{resolved => done}/20260909120007-important-features-cli-contract-drift-workspace-bl.md (100%) rename docs/eng/backlog/{resolved => done}/20260909120031-important-qemu-not-in-ci-ci-bl.md (100%) rename docs/eng/backlog/{resolved => done}/20260909120032-important-q-stack-overflow-firmware-q-bl.md (100%) rename docs/eng/backlog/{resolved => done}/20260909120033-important-bridge-dies-on-bad-byte-firmware-tools-bl.md (100%) rename docs/eng/backlog/{resolved => done}/20260909120041-minor-firmware-bl.md (100%) rename docs/eng/backlog/{resolved => done}/20260909120047-rec-firmware-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120001-critical-invalid-qmeta-json-nodes-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120002-critical-hang-on-lost-end-marker-oee-aggregator-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120003-critical-midpacket-retry-desync-mqtt-min-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120004-critical-clamp-panic-missing-class-ml-trainer-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120008-important-f32-time-degradation-line-simulator-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120009-important-toml-validation-panics-line-simulator-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120010-important-unconditional-current-stream-line-simulator-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120011-important-csv-buffered-in-memory-line-simulator-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120012-important-taps-seed-unsalted-line-simulator-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120013-important-clock-loop-duplicated-line-simulator-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120014-important-determinism-test-gaps-line-simulator-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120015-important-end-marker-no-retry-nodes-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120016-important-run-id-unvalidated-nodes-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120017-important-taps-bad-row-double-loss-nodes-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120018-important-status-loss-on-disconnect-nodes-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120019-important-terminal-not-restored-oee-dashboard-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120020-important-filter-hardcoded-prefix-oee-dashboard-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120021-important-zero-err-claim-unpinned-oee-aggregator-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120022-important-csv-truncate-vs-append-oee-aggregator-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120023-important-no-max-packet-size-mqtt-min-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120024-important-capture-channel-leak-mqtt-min-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120025-important-keepalive-violated-mqtt-min-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120026-important-no-connect-write-timeouts-mqtt-min-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120027-important-duplicate-subscribe-mqtt-min-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120028-important-interp-panics-foreign-tflite-ml-exporter-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120029-important-bit-identical-untested-ml-trainer-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120030-important-parity-gen-duplicates-quant-ml-exporter-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120034-minor-line-simulator-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120035-minor-nodes-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120036-minor-oee-aggregator-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120037-minor-oee-dashboard-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120038-minor-mqtt-min-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120039-minor-ml-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120040-minor-qemu-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120042-rec-line-simulator-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120043-rec-nodes-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120044-rec-oee-aggregator-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120045-rec-mqtt-min-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120046-rec-ml-bl.md (100%) rename docs/eng/backlog/{ => open}/20260909120048-info-rust-analyzer-macro-artifact-nodes-bl.md (100%) rename docs/eng/backlog/{ => open}/20260910104756-important-flaky-aggregator-pipeline-oee-aggregator-bl.md (100%) rename docs/rus/backlog/{resolved => done}/20260909120005-critical-counts-levels-not-edges-firmware-p-bl.md (100%) rename docs/rus/backlog/{resolved => done}/20260909120006-critical-tms-always-zero-firmware-a-bl.md (100%) rename docs/rus/backlog/{resolved => done}/20260909120007-important-features-cli-contract-drift-workspace-bl.md (100%) rename docs/rus/backlog/{resolved => done}/20260909120031-important-qemu-not-in-ci-ci-bl.md (100%) rename docs/rus/backlog/{resolved => done}/20260909120032-important-q-stack-overflow-firmware-q-bl.md (100%) rename docs/rus/backlog/{resolved => done}/20260909120033-important-bridge-dies-on-bad-byte-firmware-tools-bl.md (100%) rename docs/rus/backlog/{resolved => done}/20260909120041-minor-firmware-bl.md (100%) rename docs/rus/backlog/{resolved => done}/20260909120047-rec-firmware-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120001-critical-invalid-qmeta-json-nodes-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120002-critical-hang-on-lost-end-marker-oee-aggregator-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120003-critical-midpacket-retry-desync-mqtt-min-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120004-critical-clamp-panic-missing-class-ml-trainer-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120008-important-f32-time-degradation-line-simulator-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120009-important-toml-validation-panics-line-simulator-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120010-important-unconditional-current-stream-line-simulator-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120011-important-csv-buffered-in-memory-line-simulator-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120012-important-taps-seed-unsalted-line-simulator-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120013-important-clock-loop-duplicated-line-simulator-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120014-important-determinism-test-gaps-line-simulator-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120015-important-end-marker-no-retry-nodes-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120016-important-run-id-unvalidated-nodes-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120017-important-taps-bad-row-double-loss-nodes-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120018-important-status-loss-on-disconnect-nodes-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120019-important-terminal-not-restored-oee-dashboard-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120020-important-filter-hardcoded-prefix-oee-dashboard-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120021-important-zero-err-claim-unpinned-oee-aggregator-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120022-important-csv-truncate-vs-append-oee-aggregator-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120023-important-no-max-packet-size-mqtt-min-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120024-important-capture-channel-leak-mqtt-min-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120025-important-keepalive-violated-mqtt-min-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120026-important-no-connect-write-timeouts-mqtt-min-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120027-important-duplicate-subscribe-mqtt-min-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120028-important-interp-panics-foreign-tflite-ml-exporter-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120029-important-bit-identical-untested-ml-trainer-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120030-important-parity-gen-duplicates-quant-ml-exporter-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120034-minor-line-simulator-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120035-minor-nodes-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120036-minor-oee-aggregator-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120037-minor-oee-dashboard-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120038-minor-mqtt-min-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120039-minor-ml-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120040-minor-qemu-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120042-rec-line-simulator-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120043-rec-nodes-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120044-rec-oee-aggregator-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120045-rec-mqtt-min-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120046-rec-ml-bl.md (100%) rename docs/rus/backlog/{ => open}/20260909120048-info-rust-analyzer-macro-artifact-nodes-bl.md (100%) rename docs/rus/backlog/{ => open}/20260910104756-important-flaky-aggregator-pipeline-oee-aggregator-bl.md (100%) diff --git a/docs/eng/backlog/resolved/20260909120005-critical-counts-levels-not-edges-firmware-p-bl.md b/docs/eng/backlog/done/20260909120005-critical-counts-levels-not-edges-firmware-p-bl.md similarity index 100% rename from docs/eng/backlog/resolved/20260909120005-critical-counts-levels-not-edges-firmware-p-bl.md rename to docs/eng/backlog/done/20260909120005-critical-counts-levels-not-edges-firmware-p-bl.md diff --git a/docs/eng/backlog/resolved/20260909120006-critical-tms-always-zero-firmware-a-bl.md b/docs/eng/backlog/done/20260909120006-critical-tms-always-zero-firmware-a-bl.md similarity index 100% rename from docs/eng/backlog/resolved/20260909120006-critical-tms-always-zero-firmware-a-bl.md rename to docs/eng/backlog/done/20260909120006-critical-tms-always-zero-firmware-a-bl.md diff --git a/docs/eng/backlog/resolved/20260909120007-important-features-cli-contract-drift-workspace-bl.md b/docs/eng/backlog/done/20260909120007-important-features-cli-contract-drift-workspace-bl.md similarity index 100% rename from docs/eng/backlog/resolved/20260909120007-important-features-cli-contract-drift-workspace-bl.md rename to docs/eng/backlog/done/20260909120007-important-features-cli-contract-drift-workspace-bl.md diff --git a/docs/eng/backlog/resolved/20260909120031-important-qemu-not-in-ci-ci-bl.md b/docs/eng/backlog/done/20260909120031-important-qemu-not-in-ci-ci-bl.md similarity index 100% rename from docs/eng/backlog/resolved/20260909120031-important-qemu-not-in-ci-ci-bl.md rename to docs/eng/backlog/done/20260909120031-important-qemu-not-in-ci-ci-bl.md diff --git a/docs/eng/backlog/resolved/20260909120032-important-q-stack-overflow-firmware-q-bl.md b/docs/eng/backlog/done/20260909120032-important-q-stack-overflow-firmware-q-bl.md similarity index 100% rename from docs/eng/backlog/resolved/20260909120032-important-q-stack-overflow-firmware-q-bl.md rename to docs/eng/backlog/done/20260909120032-important-q-stack-overflow-firmware-q-bl.md diff --git a/docs/eng/backlog/resolved/20260909120033-important-bridge-dies-on-bad-byte-firmware-tools-bl.md b/docs/eng/backlog/done/20260909120033-important-bridge-dies-on-bad-byte-firmware-tools-bl.md similarity index 100% rename from docs/eng/backlog/resolved/20260909120033-important-bridge-dies-on-bad-byte-firmware-tools-bl.md rename to docs/eng/backlog/done/20260909120033-important-bridge-dies-on-bad-byte-firmware-tools-bl.md diff --git a/docs/eng/backlog/resolved/20260909120041-minor-firmware-bl.md b/docs/eng/backlog/done/20260909120041-minor-firmware-bl.md similarity index 100% rename from docs/eng/backlog/resolved/20260909120041-minor-firmware-bl.md rename to docs/eng/backlog/done/20260909120041-minor-firmware-bl.md diff --git a/docs/eng/backlog/resolved/20260909120047-rec-firmware-bl.md b/docs/eng/backlog/done/20260909120047-rec-firmware-bl.md similarity index 100% rename from docs/eng/backlog/resolved/20260909120047-rec-firmware-bl.md rename to docs/eng/backlog/done/20260909120047-rec-firmware-bl.md diff --git a/docs/eng/backlog/20260909120001-critical-invalid-qmeta-json-nodes-bl.md b/docs/eng/backlog/open/20260909120001-critical-invalid-qmeta-json-nodes-bl.md similarity index 100% rename from docs/eng/backlog/20260909120001-critical-invalid-qmeta-json-nodes-bl.md rename to docs/eng/backlog/open/20260909120001-critical-invalid-qmeta-json-nodes-bl.md diff --git a/docs/eng/backlog/20260909120002-critical-hang-on-lost-end-marker-oee-aggregator-bl.md b/docs/eng/backlog/open/20260909120002-critical-hang-on-lost-end-marker-oee-aggregator-bl.md similarity index 100% rename from docs/eng/backlog/20260909120002-critical-hang-on-lost-end-marker-oee-aggregator-bl.md rename to docs/eng/backlog/open/20260909120002-critical-hang-on-lost-end-marker-oee-aggregator-bl.md diff --git a/docs/eng/backlog/20260909120003-critical-midpacket-retry-desync-mqtt-min-bl.md b/docs/eng/backlog/open/20260909120003-critical-midpacket-retry-desync-mqtt-min-bl.md similarity index 100% rename from docs/eng/backlog/20260909120003-critical-midpacket-retry-desync-mqtt-min-bl.md rename to docs/eng/backlog/open/20260909120003-critical-midpacket-retry-desync-mqtt-min-bl.md diff --git a/docs/eng/backlog/20260909120004-critical-clamp-panic-missing-class-ml-trainer-bl.md b/docs/eng/backlog/open/20260909120004-critical-clamp-panic-missing-class-ml-trainer-bl.md similarity index 100% rename from docs/eng/backlog/20260909120004-critical-clamp-panic-missing-class-ml-trainer-bl.md rename to docs/eng/backlog/open/20260909120004-critical-clamp-panic-missing-class-ml-trainer-bl.md diff --git a/docs/eng/backlog/20260909120008-important-f32-time-degradation-line-simulator-bl.md b/docs/eng/backlog/open/20260909120008-important-f32-time-degradation-line-simulator-bl.md similarity index 100% rename from docs/eng/backlog/20260909120008-important-f32-time-degradation-line-simulator-bl.md rename to docs/eng/backlog/open/20260909120008-important-f32-time-degradation-line-simulator-bl.md diff --git a/docs/eng/backlog/20260909120009-important-toml-validation-panics-line-simulator-bl.md b/docs/eng/backlog/open/20260909120009-important-toml-validation-panics-line-simulator-bl.md similarity index 100% rename from docs/eng/backlog/20260909120009-important-toml-validation-panics-line-simulator-bl.md rename to docs/eng/backlog/open/20260909120009-important-toml-validation-panics-line-simulator-bl.md diff --git a/docs/eng/backlog/20260909120010-important-unconditional-current-stream-line-simulator-bl.md b/docs/eng/backlog/open/20260909120010-important-unconditional-current-stream-line-simulator-bl.md similarity index 100% rename from docs/eng/backlog/20260909120010-important-unconditional-current-stream-line-simulator-bl.md rename to docs/eng/backlog/open/20260909120010-important-unconditional-current-stream-line-simulator-bl.md diff --git a/docs/eng/backlog/20260909120011-important-csv-buffered-in-memory-line-simulator-bl.md b/docs/eng/backlog/open/20260909120011-important-csv-buffered-in-memory-line-simulator-bl.md similarity index 100% rename from docs/eng/backlog/20260909120011-important-csv-buffered-in-memory-line-simulator-bl.md rename to docs/eng/backlog/open/20260909120011-important-csv-buffered-in-memory-line-simulator-bl.md diff --git a/docs/eng/backlog/20260909120012-important-taps-seed-unsalted-line-simulator-bl.md b/docs/eng/backlog/open/20260909120012-important-taps-seed-unsalted-line-simulator-bl.md similarity index 100% rename from docs/eng/backlog/20260909120012-important-taps-seed-unsalted-line-simulator-bl.md rename to docs/eng/backlog/open/20260909120012-important-taps-seed-unsalted-line-simulator-bl.md diff --git a/docs/eng/backlog/20260909120013-important-clock-loop-duplicated-line-simulator-bl.md b/docs/eng/backlog/open/20260909120013-important-clock-loop-duplicated-line-simulator-bl.md similarity index 100% rename from docs/eng/backlog/20260909120013-important-clock-loop-duplicated-line-simulator-bl.md rename to docs/eng/backlog/open/20260909120013-important-clock-loop-duplicated-line-simulator-bl.md diff --git a/docs/eng/backlog/20260909120014-important-determinism-test-gaps-line-simulator-bl.md b/docs/eng/backlog/open/20260909120014-important-determinism-test-gaps-line-simulator-bl.md similarity index 100% rename from docs/eng/backlog/20260909120014-important-determinism-test-gaps-line-simulator-bl.md rename to docs/eng/backlog/open/20260909120014-important-determinism-test-gaps-line-simulator-bl.md diff --git a/docs/eng/backlog/20260909120015-important-end-marker-no-retry-nodes-bl.md b/docs/eng/backlog/open/20260909120015-important-end-marker-no-retry-nodes-bl.md similarity index 100% rename from docs/eng/backlog/20260909120015-important-end-marker-no-retry-nodes-bl.md rename to docs/eng/backlog/open/20260909120015-important-end-marker-no-retry-nodes-bl.md diff --git a/docs/eng/backlog/20260909120016-important-run-id-unvalidated-nodes-bl.md b/docs/eng/backlog/open/20260909120016-important-run-id-unvalidated-nodes-bl.md similarity index 100% rename from docs/eng/backlog/20260909120016-important-run-id-unvalidated-nodes-bl.md rename to docs/eng/backlog/open/20260909120016-important-run-id-unvalidated-nodes-bl.md diff --git a/docs/eng/backlog/20260909120017-important-taps-bad-row-double-loss-nodes-bl.md b/docs/eng/backlog/open/20260909120017-important-taps-bad-row-double-loss-nodes-bl.md similarity index 100% rename from docs/eng/backlog/20260909120017-important-taps-bad-row-double-loss-nodes-bl.md rename to docs/eng/backlog/open/20260909120017-important-taps-bad-row-double-loss-nodes-bl.md diff --git a/docs/eng/backlog/20260909120018-important-status-loss-on-disconnect-nodes-bl.md b/docs/eng/backlog/open/20260909120018-important-status-loss-on-disconnect-nodes-bl.md similarity index 100% rename from docs/eng/backlog/20260909120018-important-status-loss-on-disconnect-nodes-bl.md rename to docs/eng/backlog/open/20260909120018-important-status-loss-on-disconnect-nodes-bl.md diff --git a/docs/eng/backlog/20260909120019-important-terminal-not-restored-oee-dashboard-bl.md b/docs/eng/backlog/open/20260909120019-important-terminal-not-restored-oee-dashboard-bl.md similarity index 100% rename from docs/eng/backlog/20260909120019-important-terminal-not-restored-oee-dashboard-bl.md rename to docs/eng/backlog/open/20260909120019-important-terminal-not-restored-oee-dashboard-bl.md diff --git a/docs/eng/backlog/20260909120020-important-filter-hardcoded-prefix-oee-dashboard-bl.md b/docs/eng/backlog/open/20260909120020-important-filter-hardcoded-prefix-oee-dashboard-bl.md similarity index 100% rename from docs/eng/backlog/20260909120020-important-filter-hardcoded-prefix-oee-dashboard-bl.md rename to docs/eng/backlog/open/20260909120020-important-filter-hardcoded-prefix-oee-dashboard-bl.md diff --git a/docs/eng/backlog/20260909120021-important-zero-err-claim-unpinned-oee-aggregator-bl.md b/docs/eng/backlog/open/20260909120021-important-zero-err-claim-unpinned-oee-aggregator-bl.md similarity index 100% rename from docs/eng/backlog/20260909120021-important-zero-err-claim-unpinned-oee-aggregator-bl.md rename to docs/eng/backlog/open/20260909120021-important-zero-err-claim-unpinned-oee-aggregator-bl.md diff --git a/docs/eng/backlog/20260909120022-important-csv-truncate-vs-append-oee-aggregator-bl.md b/docs/eng/backlog/open/20260909120022-important-csv-truncate-vs-append-oee-aggregator-bl.md similarity index 100% rename from docs/eng/backlog/20260909120022-important-csv-truncate-vs-append-oee-aggregator-bl.md rename to docs/eng/backlog/open/20260909120022-important-csv-truncate-vs-append-oee-aggregator-bl.md diff --git a/docs/eng/backlog/20260909120023-important-no-max-packet-size-mqtt-min-bl.md b/docs/eng/backlog/open/20260909120023-important-no-max-packet-size-mqtt-min-bl.md similarity index 100% rename from docs/eng/backlog/20260909120023-important-no-max-packet-size-mqtt-min-bl.md rename to docs/eng/backlog/open/20260909120023-important-no-max-packet-size-mqtt-min-bl.md diff --git a/docs/eng/backlog/20260909120024-important-capture-channel-leak-mqtt-min-bl.md b/docs/eng/backlog/open/20260909120024-important-capture-channel-leak-mqtt-min-bl.md similarity index 100% rename from docs/eng/backlog/20260909120024-important-capture-channel-leak-mqtt-min-bl.md rename to docs/eng/backlog/open/20260909120024-important-capture-channel-leak-mqtt-min-bl.md diff --git a/docs/eng/backlog/20260909120025-important-keepalive-violated-mqtt-min-bl.md b/docs/eng/backlog/open/20260909120025-important-keepalive-violated-mqtt-min-bl.md similarity index 100% rename from docs/eng/backlog/20260909120025-important-keepalive-violated-mqtt-min-bl.md rename to docs/eng/backlog/open/20260909120025-important-keepalive-violated-mqtt-min-bl.md diff --git a/docs/eng/backlog/20260909120026-important-no-connect-write-timeouts-mqtt-min-bl.md b/docs/eng/backlog/open/20260909120026-important-no-connect-write-timeouts-mqtt-min-bl.md similarity index 100% rename from docs/eng/backlog/20260909120026-important-no-connect-write-timeouts-mqtt-min-bl.md rename to docs/eng/backlog/open/20260909120026-important-no-connect-write-timeouts-mqtt-min-bl.md diff --git a/docs/eng/backlog/20260909120027-important-duplicate-subscribe-mqtt-min-bl.md b/docs/eng/backlog/open/20260909120027-important-duplicate-subscribe-mqtt-min-bl.md similarity index 100% rename from docs/eng/backlog/20260909120027-important-duplicate-subscribe-mqtt-min-bl.md rename to docs/eng/backlog/open/20260909120027-important-duplicate-subscribe-mqtt-min-bl.md diff --git a/docs/eng/backlog/20260909120028-important-interp-panics-foreign-tflite-ml-exporter-bl.md b/docs/eng/backlog/open/20260909120028-important-interp-panics-foreign-tflite-ml-exporter-bl.md similarity index 100% rename from docs/eng/backlog/20260909120028-important-interp-panics-foreign-tflite-ml-exporter-bl.md rename to docs/eng/backlog/open/20260909120028-important-interp-panics-foreign-tflite-ml-exporter-bl.md diff --git a/docs/eng/backlog/20260909120029-important-bit-identical-untested-ml-trainer-bl.md b/docs/eng/backlog/open/20260909120029-important-bit-identical-untested-ml-trainer-bl.md similarity index 100% rename from docs/eng/backlog/20260909120029-important-bit-identical-untested-ml-trainer-bl.md rename to docs/eng/backlog/open/20260909120029-important-bit-identical-untested-ml-trainer-bl.md diff --git a/docs/eng/backlog/20260909120030-important-parity-gen-duplicates-quant-ml-exporter-bl.md b/docs/eng/backlog/open/20260909120030-important-parity-gen-duplicates-quant-ml-exporter-bl.md similarity index 100% rename from docs/eng/backlog/20260909120030-important-parity-gen-duplicates-quant-ml-exporter-bl.md rename to docs/eng/backlog/open/20260909120030-important-parity-gen-duplicates-quant-ml-exporter-bl.md diff --git a/docs/eng/backlog/20260909120034-minor-line-simulator-bl.md b/docs/eng/backlog/open/20260909120034-minor-line-simulator-bl.md similarity index 100% rename from docs/eng/backlog/20260909120034-minor-line-simulator-bl.md rename to docs/eng/backlog/open/20260909120034-minor-line-simulator-bl.md diff --git a/docs/eng/backlog/20260909120035-minor-nodes-bl.md b/docs/eng/backlog/open/20260909120035-minor-nodes-bl.md similarity index 100% rename from docs/eng/backlog/20260909120035-minor-nodes-bl.md rename to docs/eng/backlog/open/20260909120035-minor-nodes-bl.md diff --git a/docs/eng/backlog/20260909120036-minor-oee-aggregator-bl.md b/docs/eng/backlog/open/20260909120036-minor-oee-aggregator-bl.md similarity index 100% rename from docs/eng/backlog/20260909120036-minor-oee-aggregator-bl.md rename to docs/eng/backlog/open/20260909120036-minor-oee-aggregator-bl.md diff --git a/docs/eng/backlog/20260909120037-minor-oee-dashboard-bl.md b/docs/eng/backlog/open/20260909120037-minor-oee-dashboard-bl.md similarity index 100% rename from docs/eng/backlog/20260909120037-minor-oee-dashboard-bl.md rename to docs/eng/backlog/open/20260909120037-minor-oee-dashboard-bl.md diff --git a/docs/eng/backlog/20260909120038-minor-mqtt-min-bl.md b/docs/eng/backlog/open/20260909120038-minor-mqtt-min-bl.md similarity index 100% rename from docs/eng/backlog/20260909120038-minor-mqtt-min-bl.md rename to docs/eng/backlog/open/20260909120038-minor-mqtt-min-bl.md diff --git a/docs/eng/backlog/20260909120039-minor-ml-bl.md b/docs/eng/backlog/open/20260909120039-minor-ml-bl.md similarity index 100% rename from docs/eng/backlog/20260909120039-minor-ml-bl.md rename to docs/eng/backlog/open/20260909120039-minor-ml-bl.md diff --git a/docs/eng/backlog/20260909120040-minor-qemu-bl.md b/docs/eng/backlog/open/20260909120040-minor-qemu-bl.md similarity index 100% rename from docs/eng/backlog/20260909120040-minor-qemu-bl.md rename to docs/eng/backlog/open/20260909120040-minor-qemu-bl.md diff --git a/docs/eng/backlog/20260909120042-rec-line-simulator-bl.md b/docs/eng/backlog/open/20260909120042-rec-line-simulator-bl.md similarity index 100% rename from docs/eng/backlog/20260909120042-rec-line-simulator-bl.md rename to docs/eng/backlog/open/20260909120042-rec-line-simulator-bl.md diff --git a/docs/eng/backlog/20260909120043-rec-nodes-bl.md b/docs/eng/backlog/open/20260909120043-rec-nodes-bl.md similarity index 100% rename from docs/eng/backlog/20260909120043-rec-nodes-bl.md rename to docs/eng/backlog/open/20260909120043-rec-nodes-bl.md diff --git a/docs/eng/backlog/20260909120044-rec-oee-aggregator-bl.md b/docs/eng/backlog/open/20260909120044-rec-oee-aggregator-bl.md similarity index 100% rename from docs/eng/backlog/20260909120044-rec-oee-aggregator-bl.md rename to docs/eng/backlog/open/20260909120044-rec-oee-aggregator-bl.md diff --git a/docs/eng/backlog/20260909120045-rec-mqtt-min-bl.md b/docs/eng/backlog/open/20260909120045-rec-mqtt-min-bl.md similarity index 100% rename from docs/eng/backlog/20260909120045-rec-mqtt-min-bl.md rename to docs/eng/backlog/open/20260909120045-rec-mqtt-min-bl.md diff --git a/docs/eng/backlog/20260909120046-rec-ml-bl.md b/docs/eng/backlog/open/20260909120046-rec-ml-bl.md similarity index 100% rename from docs/eng/backlog/20260909120046-rec-ml-bl.md rename to docs/eng/backlog/open/20260909120046-rec-ml-bl.md diff --git a/docs/eng/backlog/20260909120048-info-rust-analyzer-macro-artifact-nodes-bl.md b/docs/eng/backlog/open/20260909120048-info-rust-analyzer-macro-artifact-nodes-bl.md similarity index 100% rename from docs/eng/backlog/20260909120048-info-rust-analyzer-macro-artifact-nodes-bl.md rename to docs/eng/backlog/open/20260909120048-info-rust-analyzer-macro-artifact-nodes-bl.md diff --git a/docs/eng/backlog/20260910104756-important-flaky-aggregator-pipeline-oee-aggregator-bl.md b/docs/eng/backlog/open/20260910104756-important-flaky-aggregator-pipeline-oee-aggregator-bl.md similarity index 100% rename from docs/eng/backlog/20260910104756-important-flaky-aggregator-pipeline-oee-aggregator-bl.md rename to docs/eng/backlog/open/20260910104756-important-flaky-aggregator-pipeline-oee-aggregator-bl.md diff --git a/docs/rus/backlog/resolved/20260909120005-critical-counts-levels-not-edges-firmware-p-bl.md b/docs/rus/backlog/done/20260909120005-critical-counts-levels-not-edges-firmware-p-bl.md similarity index 100% rename from docs/rus/backlog/resolved/20260909120005-critical-counts-levels-not-edges-firmware-p-bl.md rename to docs/rus/backlog/done/20260909120005-critical-counts-levels-not-edges-firmware-p-bl.md diff --git a/docs/rus/backlog/resolved/20260909120006-critical-tms-always-zero-firmware-a-bl.md b/docs/rus/backlog/done/20260909120006-critical-tms-always-zero-firmware-a-bl.md similarity index 100% rename from docs/rus/backlog/resolved/20260909120006-critical-tms-always-zero-firmware-a-bl.md rename to docs/rus/backlog/done/20260909120006-critical-tms-always-zero-firmware-a-bl.md diff --git a/docs/rus/backlog/resolved/20260909120007-important-features-cli-contract-drift-workspace-bl.md b/docs/rus/backlog/done/20260909120007-important-features-cli-contract-drift-workspace-bl.md similarity index 100% rename from docs/rus/backlog/resolved/20260909120007-important-features-cli-contract-drift-workspace-bl.md rename to docs/rus/backlog/done/20260909120007-important-features-cli-contract-drift-workspace-bl.md diff --git a/docs/rus/backlog/resolved/20260909120031-important-qemu-not-in-ci-ci-bl.md b/docs/rus/backlog/done/20260909120031-important-qemu-not-in-ci-ci-bl.md similarity index 100% rename from docs/rus/backlog/resolved/20260909120031-important-qemu-not-in-ci-ci-bl.md rename to docs/rus/backlog/done/20260909120031-important-qemu-not-in-ci-ci-bl.md diff --git a/docs/rus/backlog/resolved/20260909120032-important-q-stack-overflow-firmware-q-bl.md b/docs/rus/backlog/done/20260909120032-important-q-stack-overflow-firmware-q-bl.md similarity index 100% rename from docs/rus/backlog/resolved/20260909120032-important-q-stack-overflow-firmware-q-bl.md rename to docs/rus/backlog/done/20260909120032-important-q-stack-overflow-firmware-q-bl.md diff --git a/docs/rus/backlog/resolved/20260909120033-important-bridge-dies-on-bad-byte-firmware-tools-bl.md b/docs/rus/backlog/done/20260909120033-important-bridge-dies-on-bad-byte-firmware-tools-bl.md similarity index 100% rename from docs/rus/backlog/resolved/20260909120033-important-bridge-dies-on-bad-byte-firmware-tools-bl.md rename to docs/rus/backlog/done/20260909120033-important-bridge-dies-on-bad-byte-firmware-tools-bl.md diff --git a/docs/rus/backlog/resolved/20260909120041-minor-firmware-bl.md b/docs/rus/backlog/done/20260909120041-minor-firmware-bl.md similarity index 100% rename from docs/rus/backlog/resolved/20260909120041-minor-firmware-bl.md rename to docs/rus/backlog/done/20260909120041-minor-firmware-bl.md diff --git a/docs/rus/backlog/resolved/20260909120047-rec-firmware-bl.md b/docs/rus/backlog/done/20260909120047-rec-firmware-bl.md similarity index 100% rename from docs/rus/backlog/resolved/20260909120047-rec-firmware-bl.md rename to docs/rus/backlog/done/20260909120047-rec-firmware-bl.md diff --git a/docs/rus/backlog/20260909120001-critical-invalid-qmeta-json-nodes-bl.md b/docs/rus/backlog/open/20260909120001-critical-invalid-qmeta-json-nodes-bl.md similarity index 100% rename from docs/rus/backlog/20260909120001-critical-invalid-qmeta-json-nodes-bl.md rename to docs/rus/backlog/open/20260909120001-critical-invalid-qmeta-json-nodes-bl.md diff --git a/docs/rus/backlog/20260909120002-critical-hang-on-lost-end-marker-oee-aggregator-bl.md b/docs/rus/backlog/open/20260909120002-critical-hang-on-lost-end-marker-oee-aggregator-bl.md similarity index 100% rename from docs/rus/backlog/20260909120002-critical-hang-on-lost-end-marker-oee-aggregator-bl.md rename to docs/rus/backlog/open/20260909120002-critical-hang-on-lost-end-marker-oee-aggregator-bl.md diff --git a/docs/rus/backlog/20260909120003-critical-midpacket-retry-desync-mqtt-min-bl.md b/docs/rus/backlog/open/20260909120003-critical-midpacket-retry-desync-mqtt-min-bl.md similarity index 100% rename from docs/rus/backlog/20260909120003-critical-midpacket-retry-desync-mqtt-min-bl.md rename to docs/rus/backlog/open/20260909120003-critical-midpacket-retry-desync-mqtt-min-bl.md diff --git a/docs/rus/backlog/20260909120004-critical-clamp-panic-missing-class-ml-trainer-bl.md b/docs/rus/backlog/open/20260909120004-critical-clamp-panic-missing-class-ml-trainer-bl.md similarity index 100% rename from docs/rus/backlog/20260909120004-critical-clamp-panic-missing-class-ml-trainer-bl.md rename to docs/rus/backlog/open/20260909120004-critical-clamp-panic-missing-class-ml-trainer-bl.md diff --git a/docs/rus/backlog/20260909120008-important-f32-time-degradation-line-simulator-bl.md b/docs/rus/backlog/open/20260909120008-important-f32-time-degradation-line-simulator-bl.md similarity index 100% rename from docs/rus/backlog/20260909120008-important-f32-time-degradation-line-simulator-bl.md rename to docs/rus/backlog/open/20260909120008-important-f32-time-degradation-line-simulator-bl.md diff --git a/docs/rus/backlog/20260909120009-important-toml-validation-panics-line-simulator-bl.md b/docs/rus/backlog/open/20260909120009-important-toml-validation-panics-line-simulator-bl.md similarity index 100% rename from docs/rus/backlog/20260909120009-important-toml-validation-panics-line-simulator-bl.md rename to docs/rus/backlog/open/20260909120009-important-toml-validation-panics-line-simulator-bl.md diff --git a/docs/rus/backlog/20260909120010-important-unconditional-current-stream-line-simulator-bl.md b/docs/rus/backlog/open/20260909120010-important-unconditional-current-stream-line-simulator-bl.md similarity index 100% rename from docs/rus/backlog/20260909120010-important-unconditional-current-stream-line-simulator-bl.md rename to docs/rus/backlog/open/20260909120010-important-unconditional-current-stream-line-simulator-bl.md diff --git a/docs/rus/backlog/20260909120011-important-csv-buffered-in-memory-line-simulator-bl.md b/docs/rus/backlog/open/20260909120011-important-csv-buffered-in-memory-line-simulator-bl.md similarity index 100% rename from docs/rus/backlog/20260909120011-important-csv-buffered-in-memory-line-simulator-bl.md rename to docs/rus/backlog/open/20260909120011-important-csv-buffered-in-memory-line-simulator-bl.md diff --git a/docs/rus/backlog/20260909120012-important-taps-seed-unsalted-line-simulator-bl.md b/docs/rus/backlog/open/20260909120012-important-taps-seed-unsalted-line-simulator-bl.md similarity index 100% rename from docs/rus/backlog/20260909120012-important-taps-seed-unsalted-line-simulator-bl.md rename to docs/rus/backlog/open/20260909120012-important-taps-seed-unsalted-line-simulator-bl.md diff --git a/docs/rus/backlog/20260909120013-important-clock-loop-duplicated-line-simulator-bl.md b/docs/rus/backlog/open/20260909120013-important-clock-loop-duplicated-line-simulator-bl.md similarity index 100% rename from docs/rus/backlog/20260909120013-important-clock-loop-duplicated-line-simulator-bl.md rename to docs/rus/backlog/open/20260909120013-important-clock-loop-duplicated-line-simulator-bl.md diff --git a/docs/rus/backlog/20260909120014-important-determinism-test-gaps-line-simulator-bl.md b/docs/rus/backlog/open/20260909120014-important-determinism-test-gaps-line-simulator-bl.md similarity index 100% rename from docs/rus/backlog/20260909120014-important-determinism-test-gaps-line-simulator-bl.md rename to docs/rus/backlog/open/20260909120014-important-determinism-test-gaps-line-simulator-bl.md diff --git a/docs/rus/backlog/20260909120015-important-end-marker-no-retry-nodes-bl.md b/docs/rus/backlog/open/20260909120015-important-end-marker-no-retry-nodes-bl.md similarity index 100% rename from docs/rus/backlog/20260909120015-important-end-marker-no-retry-nodes-bl.md rename to docs/rus/backlog/open/20260909120015-important-end-marker-no-retry-nodes-bl.md diff --git a/docs/rus/backlog/20260909120016-important-run-id-unvalidated-nodes-bl.md b/docs/rus/backlog/open/20260909120016-important-run-id-unvalidated-nodes-bl.md similarity index 100% rename from docs/rus/backlog/20260909120016-important-run-id-unvalidated-nodes-bl.md rename to docs/rus/backlog/open/20260909120016-important-run-id-unvalidated-nodes-bl.md diff --git a/docs/rus/backlog/20260909120017-important-taps-bad-row-double-loss-nodes-bl.md b/docs/rus/backlog/open/20260909120017-important-taps-bad-row-double-loss-nodes-bl.md similarity index 100% rename from docs/rus/backlog/20260909120017-important-taps-bad-row-double-loss-nodes-bl.md rename to docs/rus/backlog/open/20260909120017-important-taps-bad-row-double-loss-nodes-bl.md diff --git a/docs/rus/backlog/20260909120018-important-status-loss-on-disconnect-nodes-bl.md b/docs/rus/backlog/open/20260909120018-important-status-loss-on-disconnect-nodes-bl.md similarity index 100% rename from docs/rus/backlog/20260909120018-important-status-loss-on-disconnect-nodes-bl.md rename to docs/rus/backlog/open/20260909120018-important-status-loss-on-disconnect-nodes-bl.md diff --git a/docs/rus/backlog/20260909120019-important-terminal-not-restored-oee-dashboard-bl.md b/docs/rus/backlog/open/20260909120019-important-terminal-not-restored-oee-dashboard-bl.md similarity index 100% rename from docs/rus/backlog/20260909120019-important-terminal-not-restored-oee-dashboard-bl.md rename to docs/rus/backlog/open/20260909120019-important-terminal-not-restored-oee-dashboard-bl.md diff --git a/docs/rus/backlog/20260909120020-important-filter-hardcoded-prefix-oee-dashboard-bl.md b/docs/rus/backlog/open/20260909120020-important-filter-hardcoded-prefix-oee-dashboard-bl.md similarity index 100% rename from docs/rus/backlog/20260909120020-important-filter-hardcoded-prefix-oee-dashboard-bl.md rename to docs/rus/backlog/open/20260909120020-important-filter-hardcoded-prefix-oee-dashboard-bl.md diff --git a/docs/rus/backlog/20260909120021-important-zero-err-claim-unpinned-oee-aggregator-bl.md b/docs/rus/backlog/open/20260909120021-important-zero-err-claim-unpinned-oee-aggregator-bl.md similarity index 100% rename from docs/rus/backlog/20260909120021-important-zero-err-claim-unpinned-oee-aggregator-bl.md rename to docs/rus/backlog/open/20260909120021-important-zero-err-claim-unpinned-oee-aggregator-bl.md diff --git a/docs/rus/backlog/20260909120022-important-csv-truncate-vs-append-oee-aggregator-bl.md b/docs/rus/backlog/open/20260909120022-important-csv-truncate-vs-append-oee-aggregator-bl.md similarity index 100% rename from docs/rus/backlog/20260909120022-important-csv-truncate-vs-append-oee-aggregator-bl.md rename to docs/rus/backlog/open/20260909120022-important-csv-truncate-vs-append-oee-aggregator-bl.md diff --git a/docs/rus/backlog/20260909120023-important-no-max-packet-size-mqtt-min-bl.md b/docs/rus/backlog/open/20260909120023-important-no-max-packet-size-mqtt-min-bl.md similarity index 100% rename from docs/rus/backlog/20260909120023-important-no-max-packet-size-mqtt-min-bl.md rename to docs/rus/backlog/open/20260909120023-important-no-max-packet-size-mqtt-min-bl.md diff --git a/docs/rus/backlog/20260909120024-important-capture-channel-leak-mqtt-min-bl.md b/docs/rus/backlog/open/20260909120024-important-capture-channel-leak-mqtt-min-bl.md similarity index 100% rename from docs/rus/backlog/20260909120024-important-capture-channel-leak-mqtt-min-bl.md rename to docs/rus/backlog/open/20260909120024-important-capture-channel-leak-mqtt-min-bl.md diff --git a/docs/rus/backlog/20260909120025-important-keepalive-violated-mqtt-min-bl.md b/docs/rus/backlog/open/20260909120025-important-keepalive-violated-mqtt-min-bl.md similarity index 100% rename from docs/rus/backlog/20260909120025-important-keepalive-violated-mqtt-min-bl.md rename to docs/rus/backlog/open/20260909120025-important-keepalive-violated-mqtt-min-bl.md diff --git a/docs/rus/backlog/20260909120026-important-no-connect-write-timeouts-mqtt-min-bl.md b/docs/rus/backlog/open/20260909120026-important-no-connect-write-timeouts-mqtt-min-bl.md similarity index 100% rename from docs/rus/backlog/20260909120026-important-no-connect-write-timeouts-mqtt-min-bl.md rename to docs/rus/backlog/open/20260909120026-important-no-connect-write-timeouts-mqtt-min-bl.md diff --git a/docs/rus/backlog/20260909120027-important-duplicate-subscribe-mqtt-min-bl.md b/docs/rus/backlog/open/20260909120027-important-duplicate-subscribe-mqtt-min-bl.md similarity index 100% rename from docs/rus/backlog/20260909120027-important-duplicate-subscribe-mqtt-min-bl.md rename to docs/rus/backlog/open/20260909120027-important-duplicate-subscribe-mqtt-min-bl.md diff --git a/docs/rus/backlog/20260909120028-important-interp-panics-foreign-tflite-ml-exporter-bl.md b/docs/rus/backlog/open/20260909120028-important-interp-panics-foreign-tflite-ml-exporter-bl.md similarity index 100% rename from docs/rus/backlog/20260909120028-important-interp-panics-foreign-tflite-ml-exporter-bl.md rename to docs/rus/backlog/open/20260909120028-important-interp-panics-foreign-tflite-ml-exporter-bl.md diff --git a/docs/rus/backlog/20260909120029-important-bit-identical-untested-ml-trainer-bl.md b/docs/rus/backlog/open/20260909120029-important-bit-identical-untested-ml-trainer-bl.md similarity index 100% rename from docs/rus/backlog/20260909120029-important-bit-identical-untested-ml-trainer-bl.md rename to docs/rus/backlog/open/20260909120029-important-bit-identical-untested-ml-trainer-bl.md diff --git a/docs/rus/backlog/20260909120030-important-parity-gen-duplicates-quant-ml-exporter-bl.md b/docs/rus/backlog/open/20260909120030-important-parity-gen-duplicates-quant-ml-exporter-bl.md similarity index 100% rename from docs/rus/backlog/20260909120030-important-parity-gen-duplicates-quant-ml-exporter-bl.md rename to docs/rus/backlog/open/20260909120030-important-parity-gen-duplicates-quant-ml-exporter-bl.md diff --git a/docs/rus/backlog/20260909120034-minor-line-simulator-bl.md b/docs/rus/backlog/open/20260909120034-minor-line-simulator-bl.md similarity index 100% rename from docs/rus/backlog/20260909120034-minor-line-simulator-bl.md rename to docs/rus/backlog/open/20260909120034-minor-line-simulator-bl.md diff --git a/docs/rus/backlog/20260909120035-minor-nodes-bl.md b/docs/rus/backlog/open/20260909120035-minor-nodes-bl.md similarity index 100% rename from docs/rus/backlog/20260909120035-minor-nodes-bl.md rename to docs/rus/backlog/open/20260909120035-minor-nodes-bl.md diff --git a/docs/rus/backlog/20260909120036-minor-oee-aggregator-bl.md b/docs/rus/backlog/open/20260909120036-minor-oee-aggregator-bl.md similarity index 100% rename from docs/rus/backlog/20260909120036-minor-oee-aggregator-bl.md rename to docs/rus/backlog/open/20260909120036-minor-oee-aggregator-bl.md diff --git a/docs/rus/backlog/20260909120037-minor-oee-dashboard-bl.md b/docs/rus/backlog/open/20260909120037-minor-oee-dashboard-bl.md similarity index 100% rename from docs/rus/backlog/20260909120037-minor-oee-dashboard-bl.md rename to docs/rus/backlog/open/20260909120037-minor-oee-dashboard-bl.md diff --git a/docs/rus/backlog/20260909120038-minor-mqtt-min-bl.md b/docs/rus/backlog/open/20260909120038-minor-mqtt-min-bl.md similarity index 100% rename from docs/rus/backlog/20260909120038-minor-mqtt-min-bl.md rename to docs/rus/backlog/open/20260909120038-minor-mqtt-min-bl.md diff --git a/docs/rus/backlog/20260909120039-minor-ml-bl.md b/docs/rus/backlog/open/20260909120039-minor-ml-bl.md similarity index 100% rename from docs/rus/backlog/20260909120039-minor-ml-bl.md rename to docs/rus/backlog/open/20260909120039-minor-ml-bl.md diff --git a/docs/rus/backlog/20260909120040-minor-qemu-bl.md b/docs/rus/backlog/open/20260909120040-minor-qemu-bl.md similarity index 100% rename from docs/rus/backlog/20260909120040-minor-qemu-bl.md rename to docs/rus/backlog/open/20260909120040-minor-qemu-bl.md diff --git a/docs/rus/backlog/20260909120042-rec-line-simulator-bl.md b/docs/rus/backlog/open/20260909120042-rec-line-simulator-bl.md similarity index 100% rename from docs/rus/backlog/20260909120042-rec-line-simulator-bl.md rename to docs/rus/backlog/open/20260909120042-rec-line-simulator-bl.md diff --git a/docs/rus/backlog/20260909120043-rec-nodes-bl.md b/docs/rus/backlog/open/20260909120043-rec-nodes-bl.md similarity index 100% rename from docs/rus/backlog/20260909120043-rec-nodes-bl.md rename to docs/rus/backlog/open/20260909120043-rec-nodes-bl.md diff --git a/docs/rus/backlog/20260909120044-rec-oee-aggregator-bl.md b/docs/rus/backlog/open/20260909120044-rec-oee-aggregator-bl.md similarity index 100% rename from docs/rus/backlog/20260909120044-rec-oee-aggregator-bl.md rename to docs/rus/backlog/open/20260909120044-rec-oee-aggregator-bl.md diff --git a/docs/rus/backlog/20260909120045-rec-mqtt-min-bl.md b/docs/rus/backlog/open/20260909120045-rec-mqtt-min-bl.md similarity index 100% rename from docs/rus/backlog/20260909120045-rec-mqtt-min-bl.md rename to docs/rus/backlog/open/20260909120045-rec-mqtt-min-bl.md diff --git a/docs/rus/backlog/20260909120046-rec-ml-bl.md b/docs/rus/backlog/open/20260909120046-rec-ml-bl.md similarity index 100% rename from docs/rus/backlog/20260909120046-rec-ml-bl.md rename to docs/rus/backlog/open/20260909120046-rec-ml-bl.md diff --git a/docs/rus/backlog/20260909120048-info-rust-analyzer-macro-artifact-nodes-bl.md b/docs/rus/backlog/open/20260909120048-info-rust-analyzer-macro-artifact-nodes-bl.md similarity index 100% rename from docs/rus/backlog/20260909120048-info-rust-analyzer-macro-artifact-nodes-bl.md rename to docs/rus/backlog/open/20260909120048-info-rust-analyzer-macro-artifact-nodes-bl.md diff --git a/docs/rus/backlog/20260910104756-important-flaky-aggregator-pipeline-oee-aggregator-bl.md b/docs/rus/backlog/open/20260910104756-important-flaky-aggregator-pipeline-oee-aggregator-bl.md similarity index 100% rename from docs/rus/backlog/20260910104756-important-flaky-aggregator-pipeline-oee-aggregator-bl.md rename to docs/rus/backlog/open/20260910104756-important-flaky-aggregator-pipeline-oee-aggregator-bl.md