From 35a2f0fb389077424e5737eb99790fb8f6794e72 Mon Sep 17 00:00:00 2001 From: ewowi Date: Tue, 6 Oct 2026 11:51:50 +0200 Subject: [PATCH 1/2] Make the audio codec runtime configuration on one shared I2C bus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ES8311 codec is set per board in the device catalog instead of per firmware, so the S31, the P4-NANO, the Waveshare P4-ETH and the Guition M3 run their onboard microphone on the standard firmware. The I2C scan became the board's shared I2C bus and names what it finds, refreshing the UI keeps the audio running, and MoonLive gains a Backlight layout for a strip around a wall. KPI: 256lights | Desktop:2117KB | tick:1/7/7/2/12/7/266/7/7/11us | src:297(75514) | test:226(50675) | functions-over:335 | Flash split: esp32 +2.6 KB = +2.9 new, -1.5 removed, +2.6 grown, -1.6 shrunk, +0.3 other | esp32p4rev1-eth +21.9 KB (the esp_codec_dev component) | esp32s3-n16r8 +3.5 KB Commit: src +507/-291 lines | test +453/-154 lines | duplicated lines 3791 -> 3791 (+0) **Core** - AudioService: a `codec` select and a hex `codecAddr`; the codec re-attaches when the I2C bus moves, and is released only by the instance whose channel brought it up, since the /api/types probe instances released on every UI load and killed the live mic. - I2cScanModule becomes I2cBusModule: one I2C master bus, closed only by the instance that opened it; the scan names each address by the module driving it through a new MoonModule::i2cDevices(), and flags an expected device that stays silent. - ES8311 platform: the address pre-shifted for esp_codec_dev and a no-op data interface (the I2S data module boot-looped the S31); esp_codec_dev builds for P4 and S31 only. - Control gains a `hex` flag (setHexField). json::parseInt reads a quoted number and 0x hex and moves out of line (-1.3 KB on the classic); parseIntStr moves to util/parse.h as mm::parseIntStr, its callers including MQTT and Hue. - ~Scheduler clears the live instance pointer, so instance() never dangles after a test. **Light domain** - relayPins and doubleBuffer show from expert mode, the loopback self-test cluster in developer mode, on the parallel and RMT drivers. - moonlive/layouts/backlight.mll: a strip around a wall's edge pixels, each corner light shared by the two sides. **UI** - Hex number fields (0x18) in both render paths; migrate.js renames I2cScanModule to I2cBusModule. **Scripts/MoonDeck** - diag_launcher.py saves each object's compiler warnings, so check_nonblocking and check_code run on the normal desktop build in seconds; a build running another launcher is told to reconfigure instead of rebuilding clean. - A new Temporal vale rule flags words that date a sentence. The prose ratchet rises 0 -> 248 by the rule change: those are existing findings, not new writing. **Catalog** - Waveshare ESP32-P4-ETH (product page, schematic-checked pins) and Guition JC-ESP32P4-M3-DEV entries; S31 Audio and P4-NANO WiFi supported; codec addresses written "0x18". - Device images renamed to start with their firmware; three unreferenced ones are held back. **Tests** - I2cBusModule (ownership against a released probe, named scan), AudioService codec reporting, quoted and hex parsing, driver control modes, the hex field, and check_code's launcher handling. **Docs/CI** - System, services, drivers and supporting cards; MIGRATING entries for the codec and the I2C bus; S31 and MHC shield pages; installer README; a backlog entry for a second I2C bus; the plan. Docgen warnings 2477 -> 2466. **Reviews** - πŸ‘Ύ Four card bullets over 100 characters β†’ done, detail moved into the module docs. - πŸ‘Ύ backlight.mll placed every corner light twice β†’ done, 96 lights for 30 across and 20 down. - πŸ‘Ύ A hex string in the catalog costs a parser rule β†’ accepted as the PO's choice, documented in the installer README. - πŸ‘Ύ Guition M3 lists LEDs with no LED driver module β†’ declined: supported means the firmware drives it; the README rule is corrected. - πŸ‘Ύ A codec test that cannot fail β†’ done, deleted. - πŸ‘Ύ S31 Audio still planned while bench-verified β†’ done, page rewritten. - πŸ‘Ύ check_nonblocking rebuilds clean for minutes under ccache β†’ done, with a test. - πŸ‘Ύ The bus close is hard-wired to the codec β†’ accepted: ESP-IDF needs devices detached before the bus closes; revisit with a second device type. Co-Authored-By: Claude Opus 5.5 --- .vale.ini | 5 +- .vale/styles/MoonLight/Temporal.yml | 14 ++ CLAUDE.md | 2 +- CMakeLists.txt | 6 + .../{I2cScanModule.png => I2cBusModule.png} | Bin ... => esp32-16mb-quinled-dig-octa-32-8l.jpg} | Bin ...eld.jpg => esp32-16mb-serg-minishield.jpg} | Bin ...5.jpg => esp32-16mb-serg-unishield-v5.jpg} | Bin ...2-d0-16mb.jpg => esp32-16mb-testbench.jpg} | Bin ...ic-esp32-dev.jpg => esp32-generic-dev.jpg} | Bin .../{lolin-d32.jpg => esp32-lolin-d32.jpg} | Bin ...{mhc-v57-pro.jpg => esp32-mhc-v57-pro.jpg} | Bin ...2-gateway.png => esp32-olimex-gateway.png} | Bin .../deviceModels/esp32-p4-guition-m3.jpg | Bin 0 -> 105517 bytes ...hield.jpg => esp32-p4-mhc-wled-shield.jpg} | Bin ...-p4-eth.png => esp32-p4-waveshare-eth.png} | Bin ...4-nano.jpg => esp32-p4-waveshare-nano.jpg} | Bin ....jpg => esp32-pico-quinled-dig-next-2.jpg} | Bin ...ig-2-go.jpg => esp32-quinled-dig-2-go.jpg} | Bin ...d-v3.jpg => esp32-quinled-dig-quad-v3.jpg} | Bin ...no-v3.jpg => esp32-quinled-dig-uno-v3.jpg} | Bin ...2-s3-n16r8-hpwit-shift-register-board.jpg} | Bin ...bench.jpg => esp32-s3-n16r8-testbench.jpg} | Bin ....jpg => esp32-s3-n8r8-lightcrafter-16.jpg} | Bin ....jpg => esp32-s3-n8r8-stephanelec-16p.jpg} | Bin .../{shelly.jpg => esp32-shelly.jpg} | Bin docs/contributing/documentation-standards.md | 2 +- docs/explanation/architecture/mooncore.md | 2 +- docs/moonmodules/core/services.md | 4 +- docs/moonmodules/core/system.md | 18 +- docs/moonmodules/light/drivers.md | 3 +- docs/moonmodules/light/supporting.md | 1 + docs/reference/MIGRATING.md | 14 ++ .../reference/hardware/esp32-s31-coreboard.md | 10 +- .../hardware/mhc-wled-esp32-p4-shield.md | 7 +- docs/reference/metrics/code.md | 17 +- docs/reference/metrics/docgen.md | 28 +-- docs/reference/metrics/prose.md | 47 ++-- docs/reference/metrics/repo-health.json | 112 +++++----- docs/reference/metrics/repo-health.md | 100 +++++---- docs/work/future/backlog-core.md | 7 + docs/work/future/system-modules.md | 6 +- ...he audio codec as runtime configuration.md | 37 ++++ esp32/main/CMakeLists.txt | 1 + esp32/main/idf_component.yml | 15 +- moondeck/MoonDeck.md | 6 +- moondeck/build/diag_launcher.py | 36 ++++ moondeck/check/check_nonblocking.py | 102 ++++----- moondeck/docs/screenshot_modules.py | 6 +- moondeck/moondeck_config.json | 2 +- mooninstaller/README.md | 8 +- mooninstaller/deviceModels.json | 204 +++++++++++++++--- moonlive/layouts/backlight.mll | 34 +++ src/core/module/Control.h | 7 + src/core/module/MoonModule.h | 5 + src/core/module/Scheduler.h | 2 + src/core/services/AudioService.h | 85 ++++++-- src/core/system/HttpServerModule.cpp | 4 +- src/core/system/I2cBusModule.h | 154 +++++++++++++ src/core/system/I2cScanModule.h | 97 --------- src/core/system/MqttModule.cpp | 3 +- src/core/util/JsonUtil.cpp | 33 +-- src/core/util/JsonUtil.h | 34 +-- src/core/util/parse.cpp | 24 +++ src/core/util/parse.h | 34 +++ src/light/drivers/Drivers.h | 1 + src/light/drivers/HueDriver.h | 3 +- src/light/drivers/ParallelLedDriver.h | 13 +- src/light/drivers/RmtLedDriver.h | 5 +- src/light/moonlive/script_catalog.h | 7 +- src/main.cpp | 10 +- src/module_types.cpp | 4 +- src/platform/desktop/platform_config.h | 9 - src/platform/desktop/platform_desktop.cpp | 35 ++- .../desktop/platform_desktop_audio.cpp | 13 +- src/platform/esp32/platform_config.h | 21 -- src/platform/esp32/platform_esp32_es8311.cpp | 52 +++-- src/platform/esp32/platform_esp32_i2c.cpp | 71 ++++-- src/platform/platform.h | 30 ++- src/ui/app.js | 40 ++-- src/ui/migrate.js | 2 + test/CMakeLists.txt | 2 + test/js/ui-hex-field.test.mjs | 36 ++++ test/python/test_check_code.py | 67 +++++- ...lette_preset_changes_only_the_palette.json | 6 +- ...ment_adds_an_effect_with_its_controls.json | 6 +- ...cenario_Drivers_output_and_brightness.json | 6 +- ...o_Effects_pipeline_builds_and_renders.json | 10 +- .../scenario_Effects_swap_while_running.json | 14 +- ...cts_teardown_under_a_running_pipeline.json | 54 ++--- .../scenario_Layers_stack_and_blend_live.json | 60 +++--- ...nario_Layouts_resize_reallocates_live.json | 24 +-- ...cenario_Modifiers_reshape_the_mapping.json | 56 ++--- ..._Palettes_a_script_drives_the_effects.json | 14 +- test/unit/core/unit_AudioService_codec.cpp | 37 ++++ test/unit/core/unit_I2cBusModule.cpp | 130 +++++++++++ test/unit/core/unit_JsonUtil_parseint.cpp | 54 +++-- test/unit/core/unit_SystemModule.cpp | 8 +- test/unit/light/unit_Drivers_container.cpp | 23 ++ 99 files changed, 1540 insertions(+), 721 deletions(-) create mode 100644 .vale/styles/MoonLight/Temporal.yml rename docs/assets/core/{I2cScanModule.png => I2cBusModule.png} (100%) rename docs/assets/deviceModels/{quinled-dig-octa-32-8l.jpg => esp32-16mb-quinled-dig-octa-32-8l.jpg} (100%) rename docs/assets/deviceModels/{serg-minishield.jpg => esp32-16mb-serg-minishield.jpg} (100%) rename docs/assets/deviceModels/{serg-unishield-v5.jpg => esp32-16mb-serg-unishield-v5.jpg} (100%) rename docs/assets/deviceModels/{esp32-d0-16mb.jpg => esp32-16mb-testbench.jpg} (100%) rename docs/assets/deviceModels/{generic-esp32-dev.jpg => esp32-generic-dev.jpg} (100%) rename docs/assets/deviceModels/{lolin-d32.jpg => esp32-lolin-d32.jpg} (100%) rename docs/assets/deviceModels/{mhc-v57-pro.jpg => esp32-mhc-v57-pro.jpg} (100%) rename docs/assets/deviceModels/{olimex-esp32-gateway.png => esp32-olimex-gateway.png} (100%) create mode 100644 docs/assets/deviceModels/esp32-p4-guition-m3.jpg rename docs/assets/deviceModels/{mhc-wled-esp32-p4-shield.jpg => esp32-p4-mhc-wled-shield.jpg} (100%) rename docs/assets/deviceModels/{esp32-p4-eth.png => esp32-p4-waveshare-eth.png} (100%) rename docs/assets/deviceModels/{waveshare-esp32-p4-nano.jpg => esp32-p4-waveshare-nano.jpg} (100%) rename docs/assets/deviceModels/{quinled-dig-next-2.jpg => esp32-pico-quinled-dig-next-2.jpg} (100%) rename docs/assets/deviceModels/{quinled-dig-2-go.jpg => esp32-quinled-dig-2-go.jpg} (100%) rename docs/assets/deviceModels/{quinled-dig-quad-v3.jpg => esp32-quinled-dig-quad-v3.jpg} (100%) rename docs/assets/deviceModels/{quinled-dig-uno-v3.jpg => esp32-quinled-dig-uno-v3.jpg} (100%) rename docs/assets/deviceModels/{hpwit-shift-register-board.jpg => esp32-s3-n16r8-hpwit-shift-register-board.jpg} (100%) rename docs/assets/deviceModels/{esp32-s3-testbench.jpg => esp32-s3-n16r8-testbench.jpg} (100%) rename docs/assets/deviceModels/{lightcrafter-16.jpg => esp32-s3-n8r8-lightcrafter-16.jpg} (100%) rename docs/assets/deviceModels/{esp32-s3-stephanelec-16p.jpg => esp32-s3-n8r8-stephanelec-16p.jpg} (100%) rename docs/assets/deviceModels/{shelly.jpg => esp32-shelly.jpg} (100%) create mode 100644 docs/work/present/Plan-20261005 - The audio codec as runtime configuration.md create mode 100644 moondeck/build/diag_launcher.py create mode 100644 moonlive/layouts/backlight.mll create mode 100644 src/core/system/I2cBusModule.h delete mode 100644 src/core/system/I2cScanModule.h create mode 100644 src/core/util/parse.cpp create mode 100644 src/core/util/parse.h create mode 100644 test/js/ui-hex-field.test.mjs create mode 100644 test/unit/core/unit_AudioService_codec.cpp create mode 100644 test/unit/core/unit_I2cBusModule.cpp diff --git a/.vale.ini b/.vale.ini index 4c2272d05..6661a9ae1 100644 --- a/.vale.ini +++ b/.vale.ini @@ -77,13 +77,14 @@ MoonLight.Weasel = error MoonLight.NegatedHeading = error MoonLight.SelfReference = error -# The rules page must QUOTE the things it bans (an em-dash, "analyse", "this page", "really"), so -# the four rules whose examples it carries stay off here. The rest are strict. +# The rules page must QUOTE the things it bans (an em-dash, "analyse", "this page", "really", "today"), so +# the five rules whose examples it carries stay off here. The rest are strict. [docs/contributing/documentation-standards.md] BasedOnStyles = MoonLight MoonLight.EmDash = NO MoonLight.Spelling = NO MoonLight.SelfReference = NO MoonLight.Weasel = NO +MoonLight.Temporal = NO MoonLight.SentenceLength = error MoonLight.NegatedHeading = error diff --git a/.vale/styles/MoonLight/Temporal.yml b/.vale/styles/MoonLight/Temporal.yml new file mode 100644 index 000000000..3da3d57e5 --- /dev/null +++ b/.vale/styles/MoonLight/Temporal.yml @@ -0,0 +1,14 @@ +# documentation-standards.md: "Present tense only." These words date a sentence: they compare the system with a moment, which is history, so the sentence describes what exists without them. +extends: existence +message: "'%s' dates the sentence: describe what exists." +level: error +ignorecase: true +tokens: + - today + - currently + - at the moment + - at present + - for now + - no longer + - anymore + - not yet diff --git a/CLAUDE.md b/CLAUDE.md index 9d973b698..0f692d27f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -186,7 +186,7 @@ flowchart LR pc --> diff always["always
πŸ’€ check_specs Β· πŸ’€ check_prose Β· records, writes to the tree"] md[".md
build_docs --strict 🐒
πŸ’€ check_docgen Β· πŸ›Έ test_host --python (catalog pages)
check_taglines (front pages only)"] - code["src/ or test/
πŸ’€ check_code 🐒 Β· build_desktop 🐒
πŸ›Έ test_desktop 🐒 Β· run_scenario 🐒
πŸ’€ check_docgen (the headers it covers)
πŸ’€ check_esp32_built (not src/platform/desktop)
πŸ’€ build_desktop --no-jit 🐒 (MoonLive only)
πŸ’€ collect_kpi 🐒 Β· records, writes to the tree"] + code["src/ or test/
πŸ’€ check_code Β· build_desktop 🐒
πŸ›Έ test_desktop 🐒 Β· run_scenario 🐒
πŸ’€ check_docgen (the headers it covers)
πŸ’€ check_esp32_built (not src/platform/desktop)
πŸ’€ build_desktop --no-jit 🐒 (MoonLive only)
πŸ’€ collect_kpi 🐒 Β· records, writes to the tree"] web["src/ui or mooninstaller/
πŸ›Έ test_host --js Β· πŸ’€ check_devices"] py["moondeck/ or moonlive/
πŸ›Έ test_host --python Β· πŸ’€ check_firmwares"] board["the provisioning path,
with a board attached

πŸ›Έ improv_smoke_test
recommended, say so when skipped"] diff --git a/CMakeLists.txt b/CMakeLists.txt index fb02bb91f..d37da4db4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,6 +97,11 @@ else() # and that count may only fall, while each site stays a judgment (a driver may have to wait # on hardware), so failing the build on them would block every other gate. add_compile_options(-Wfunction-effects -Wno-error=function-effects) + # Each file's findings are kept beside its object, so the code report reads the whole tree after an incremental build instead of a clean one. + find_package(Python3 COMPONENTS Interpreter QUIET) + if(Python3_Interpreter_FOUND AND NOT CMAKE_CXX_COMPILER_LAUNCHER) + set(CMAKE_CXX_COMPILER_LAUNCHER "${Python3_EXECUTABLE};${CMAKE_SOURCE_DIR}/moondeck/build/diag_launcher.py") + endif() endif() endif() @@ -126,6 +131,7 @@ find_program(UV_EXECUTABLE NAMES uv REQUIRED add_library(mm_core STATIC src/core/module/Control.cpp src/core/util/JsonUtil.cpp + src/core/util/parse.cpp src/core/util/ScratchBuffer.cpp src/core/system/HttpServerModule.cpp src/core/system/FilesystemModule.cpp diff --git a/docs/assets/core/I2cScanModule.png b/docs/assets/core/I2cBusModule.png similarity index 100% rename from docs/assets/core/I2cScanModule.png rename to docs/assets/core/I2cBusModule.png diff --git a/docs/assets/deviceModels/quinled-dig-octa-32-8l.jpg b/docs/assets/deviceModels/esp32-16mb-quinled-dig-octa-32-8l.jpg similarity index 100% rename from docs/assets/deviceModels/quinled-dig-octa-32-8l.jpg rename to docs/assets/deviceModels/esp32-16mb-quinled-dig-octa-32-8l.jpg diff --git a/docs/assets/deviceModels/serg-minishield.jpg b/docs/assets/deviceModels/esp32-16mb-serg-minishield.jpg similarity index 100% rename from docs/assets/deviceModels/serg-minishield.jpg rename to docs/assets/deviceModels/esp32-16mb-serg-minishield.jpg diff --git a/docs/assets/deviceModels/serg-unishield-v5.jpg b/docs/assets/deviceModels/esp32-16mb-serg-unishield-v5.jpg similarity index 100% rename from docs/assets/deviceModels/serg-unishield-v5.jpg rename to docs/assets/deviceModels/esp32-16mb-serg-unishield-v5.jpg diff --git a/docs/assets/deviceModels/esp32-d0-16mb.jpg b/docs/assets/deviceModels/esp32-16mb-testbench.jpg similarity index 100% rename from docs/assets/deviceModels/esp32-d0-16mb.jpg rename to docs/assets/deviceModels/esp32-16mb-testbench.jpg diff --git a/docs/assets/deviceModels/generic-esp32-dev.jpg b/docs/assets/deviceModels/esp32-generic-dev.jpg similarity index 100% rename from docs/assets/deviceModels/generic-esp32-dev.jpg rename to docs/assets/deviceModels/esp32-generic-dev.jpg diff --git a/docs/assets/deviceModels/lolin-d32.jpg b/docs/assets/deviceModels/esp32-lolin-d32.jpg similarity index 100% rename from docs/assets/deviceModels/lolin-d32.jpg rename to docs/assets/deviceModels/esp32-lolin-d32.jpg diff --git a/docs/assets/deviceModels/mhc-v57-pro.jpg b/docs/assets/deviceModels/esp32-mhc-v57-pro.jpg similarity index 100% rename from docs/assets/deviceModels/mhc-v57-pro.jpg rename to docs/assets/deviceModels/esp32-mhc-v57-pro.jpg diff --git a/docs/assets/deviceModels/olimex-esp32-gateway.png b/docs/assets/deviceModels/esp32-olimex-gateway.png similarity index 100% rename from docs/assets/deviceModels/olimex-esp32-gateway.png rename to docs/assets/deviceModels/esp32-olimex-gateway.png diff --git a/docs/assets/deviceModels/esp32-p4-guition-m3.jpg b/docs/assets/deviceModels/esp32-p4-guition-m3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..429987e019e7e8ec872b8110a49c0f12061090e8 GIT binary patch literal 105517 zcmb@tcUV(T^fwrqAgFYZ8kDBeqzeKOktQO7NQX%8AWebDT07XmlElnNN-B- zJ)ucYKtdoPY`(vD_uc2&zxLT@PjZuaawqrBnS18UXFli5lT0szn< zzW|pgz%xyVs}lgAuMfBf008I!6w$5#YI2EO1;9-{006456aXsnH^qOyeWm=r&QdCU zrTV|h6k-2Wyxa#oc;Vvh@9pQ}{YF4m@;>0fV_kjf|E@-s|0xUqPmyVoFJTT2sHWb2 ze>HOt|2XvW7l4_LB8*arisCkal9_^vnc}h=03_d&hT?zZfBr`PprE9prny2(ca@%j zT%nE$KuJMGMM+IXL-XHlC_>2R0o2SiEVrc9udo{0(%$xAllhSHjZW}!RR_D#7)D6e z&Nu8TJqPDaE^c8F(K~nV$;m4yK2TEDc%rGLt)u(&rSU5hQ!{f5dk04+XBSsDKmUNY zfsmkg;SrHh(J`@csUJV3rDtS*&ibBTP*_x4Qu?F1rnauWp|PpCv#YzOx37O-aC~BN zYIOFJ`h{l+@Hz)U^NU zML`+(pW@8aG`FO$u&5i-+WN5Gmia)(_BiERRmW99StAU)o$nYuhmagf82g`U|D)Oe zJH^8OKWX;A75l&SS^#K}4eLLlB)_RBDantDiY(MusQ;5Iv{(K|X#Y>4`yaXbpD_Gy zxg=kN;=g}VQPGg!40KoM{`a~6*M-X^vRMjU&I7JdQIL&^iWvX`kcfA_#{vF-JR~VJ z*e1HGn0lFOhuT`y5xWtS6P&U%w3Vi@D0e16*eWEr*-dA^HBl%)BAB;{J zp>G@cI#3j`1o!`rOo~6m-(@7SyW@!1+WF|c6dFVqf?7Ng$0br9Je1wou}*xSdtc^H z6@@7ENjW@T*_6nB3HTKXY{bH1RQzPL=`+$dPc>IOq!VgW7iufG&p&eYz2j!cn+sqk zd4iZgt3@!(OYg{cTSP&U8uog{mui8md50(^&&vIU0|W1S#Ofz&$=ajueZ_FztML7^ zuS=o*?ezH1SYWSdGd%v}kFvsygy+c}56!k&C)xTrF5@2&6h^8COVRSIo0ejq;JXKL zB)?h^{+{VxzY0TfZ?0NHo?W??Uf9_4OTfOI2h|qe^HJ&4=dw0evBxBO##JQ0>pPnd zm{YXZ=kFI8h7C=VLT_EC(F4!tUO#4a3A}z(fA}fk-=83M*{3Nb-}W5bO148CV6Q%z z9))1_aPyd?@KuI3_YO6o$pf!jb;xAv_%s)}(hARIGnMInZJGN3fj7_n_Q9E8%(FcF zy_w#;ME<+j9K`+y^wsAn-&}E7Vau*N5~PvM41-+xo6nLi z0VRm_Xp}aQU1L=trA#!IsJ_M}mys$~A0lE7G`#5c;1|`)e0AH^fs28@)B|I{OITVV zviq(6$w_GNgETWlm`!`%V^lB=umeySv$h6^g@0IXC2A=b-u3;VpTmaPc}nd2*QW`) z-A;eepvD48_jKwr!@ey71pF*?c%^}Cf2O$aVL0!ob~(%S8B z2GV^sw1%xI<%7T@1XE(BW4KP9_P&h&tiL0N4bUJW8x@IO6LYFL<28YXMb}Aa zcNtkM>?km8E8cH5cqoJXbWE-LJu!pQC0=lAL;fQ{1lI8k<^`T-TqEUSGWznE{^f8F zqKDl}B`y9K$t~2`AM<^QS(EI7Mq0Yzgw~Z_rYPf2A*A)-Lm`uKiW`1dF#y&=_&@tGR0~L7`FsA*%xC*EQ zzh=}pE|;h!@!a#Z`O>4PCqbTBHv{{mDeto%aYboAa@1^apgrl)gnc>Pm%0SdXkXN~ zGpVBpD^W<+eW*@WZk~7p`cRLlE@!_s>TbofS>aJJT^CJ8@~GJ+1v&;@4Eomxc19jQ`}nBQqFh&l%_Q zaWNO4+xdt#Skagp-zp*L3{~ak^Uplzoa&p?Sv;xKIx`J2rFqwAKicGAcTwQ|G!!U` zyBkS+aDc`>`T6Hc_?5yS-p<1pjv(il%r8I68YGWCJ)siHP3r=)e3LY>viCbz|Bj3Z z9-)VI44*#}aUY>{nF$S>dG#{`bfzG-nnKoBo@^kEJy=6En)$z{Qfr`wOMt^bVLSXy zdBd6Dpt*+Jvj?CDA+5ZuOX833_T+6_Yr<%-ZkGUzA795^PfR^fwt67YktbCDEgiA! zqzm^34 z)$=B3*oi5mDHzKozy0kzAmUDaeU{Mp_mmfNSF8xwluJ0Drtwp`6wEm=xc zo;m&vTCIU$4AtLfkmqbxzpeByxm zsWq`pe#I?m%cGBK5o^>%RzHzkDT_hr@GziFVVNh8xurfwVeHlby}=iNol=MVSK#)D zCM>fW*5L$O{VqAzgjM+T=-UiYKbY@F63j+v&%F)7TcH0; z{w2usFbPz9`l0;>{u0o0U%dd5Z_+Scls?U2p6_04Y-)6{wlN@NpJxc3JUQIk$I_4g z{iPG*u_RQ>juO06udN+fse78T5-3<$^#JuNlN%0#;FoaPDtEE z#5|R*28&x!2xtsERu_CAfKGBx>N~vzXu>F)`}Tq4-aMs zODz$ieU<8vEOtZd-^=iR+S}QV1Pxe62WP=W9!jLC@3dAQIlPtC^0fxkJ}8El4PVP3 z@`BzJ?_C0DPr^7W_wT+uTb!;YT3iCc<1+Wb5vHq`fI4>%GO6nJW$*9L7=Y@@z2fvGD zfbJbVmrjb9pt_(hK^&VzlNf>sf#0LxCga(vjI%4ig3h*))l(w7D2e_9_Q#%7M4c!7 zib0fS+z#nz*=#j2(?OfN93j!&Cs2N0>xg+6@ckF;8@Wxcx@#c3_@`!W{0HwfcR$XY zEl|V>NOXvV3*-Wv{C*98N}R(_kbBIC+|$|6n~e~tt>1V3LOsDg*D^E9A>qQg$Lhu+ zuN=}KOKd+b=jy@!3zVJ9k)4Vvtx-0~I_N%UN31TVzE277^wyab?_#`opL%mu1k z6GnWb6Y^`UB&U82qf)Pj4&teBffuzHxa~}x{Sir2~Q+>z7gDHT}!D*YFkAVcF`+QNL2#^8<1PB3!4hAg0&vtu|dP_SrmvQzr6g_!mX5 zuTSp$u$y$-sCnLGB0F&%LQubGY8C!5w;d{tRjByta@yA)V(uCBJ=W(I1FsZa=4+ik zo*{trI5G#!MDAtBOMthk;2POKc9t#KYh_J$*?96*uGjgt8^5f*VnqFwD(Sg0)e3v$ zy`*4)8qio&@=reZv-eoq{SeH82Iio!^lu(>we4i&36>6M+--2$oI@>G8k$TwXvn&?d`KPFb9^!zP8b zgoj3Ua=rx>2>8nCfj{I*UNostp~196ZtcoH$5}R#C6@l|^nUaE5{7S%a+sIQwHS1E?z7eKzmo{5 zAbTbalA zT>{iD0YF$s=q148aBgi)e5p%?HwIf}mL!Il7XmbdmVIA_gc?07`iD1Me&LpO z!LzC`1|Y`JAU1vFDr~i#6;k~+kzdbRZE$k%QNq%qt`w^|7`#G z-K(gy3VGi%0IV{#buv zcuGnK;s=6gp6~jTC)P=OgubeN(`iro(7dF^)g0IpO&rS8fgbwZ3NYserUDE8f`tt5O;%IF*7=o{=u=|-e7gn z-b_z!=@~mxKkT8+oVe5f*qF)v>@VJ%U}J+9!#QGLdyD>MBaBuMDXYe*e+bsU#}|1< zk>1RykW;{oMWzQ{+E-d!wU(|mRlZZ~@&15hz63aX$7SVpddHi9=h_9vo;}`~frR8j zU1gXCpC;P}DABgPJN%V5r?+;xZ=g`>jNw!#E#+yN+3c&0vGH3ha+nP}I1WF}{oE^t zX5@gnAh9E{1v3HeNG3@9tCsoW#TB`%$n_6w*V2b-JPjuj?x33wqgLt|dq=`;gi?=D zW7zscU)#h=&sMcl1=&HiO~E&}=XWwg2CZIu^+Bg`&ticUpc=;gD$E*A<06$LDB266 zT7nF(^m5nrMJ@L(nHZb1^lHltS0p@cyp`$fZ9@#TB#%HPHPGJ`Dku*@llTBRx_5v? ziDH!hxQDKBE<4t?TyvRK(*IanadV@!D(Q3O7V0!k&Kuq?GTF}KV&PPJeL4sAc{Q70 zGHqVDmeNOjCVFI;T%LVp>$t!%`Rn|*NT2iKUlsN^aV$+kkCOZnYxntdhNmlJKIHpA z0R8Mv<@A8y?|c{GPn)vgeTHO*U~1a*eenbspNDyVI3J7)#tWmo;dxB?<4zc4yju6P zti|$K2E7zoR(tb_;F+bfVG978b+i%%yr%MM4bHV43c&mj?yah{*08sydSIXsGXa4xWqb6WrjZ8<~WbgjFQcHcVm{ICt z8dzG=`72MNTmM>IpR2BM%@wL^boA<#kq*WMp>q&MltGX8tO2fabn_y=cFa*E%~7&g zHK!H(=gsuqM>dxP#}A9Pzo^3-lOA*s%P*Q>u2M9iGr&4v9MVOK8sY?_wcMTPL+UhY zCYj|f3&sD=yjOC(!O#rG)2)XYyecHkWWkb%(j_ix(k)YU7ck?-+N$)!myPa-nr-Qf zD|g}@mtuXPh4**>4!q9uP#A%8n{)?Tgh2n%>%oOvP09;tVm6A?1C-WV9%x+ilzCtF zD_Mz=C5pv2qCr(_ec$fm^wvfS)xEFIK2+@6=ce7NZ#(~5{A~`1(|!1Zy}straQD7p zB$xfi_ex6gDOlDnh4!oXv{JnCAnNeegp@;_Wvt-Q?{;%Bq?ZA8c+zvN>(+QMmZ}$g zHB@XH#IDAFW^ax;8DE;`GFGOw3QrT{4SS@jzRvVW{vOq*%6A;GSnaNJcz2@O1TMO! zr5V%K_N#i@zFIT>MP1#CM`o|OXrybd%xVkRhpF>#!7#`kwR@I~^9?Ajs?pjqiQ8hq zc_Q-hFWL;>+Dq2onAL3hFqRYUVJ>sUuWvYyKlC0{{o*U>t~;2ABvLC?fluG?l6=No z?oKGN`=HHR#lSSwB)PTe``bJzBcAAvAc9)))@#TcDv!^%ew};e`0;hS%W-mKz-9DV z^8=Qp8>pFny$=5auGutks5#MFQ;dSS!XtqV#Ys|fe9z{C(%U(ZZTi-NtSc_28Qa_i zj(Xp98YDQ-fi&|W?%ywdLe&X|un<-P-EsOV!y$e?r&iTLk}(M>HdqBY3dr+A0EGSBVyRJnI8Pi9dx^P+6uohWF? z_ukjWQ6_|;5oXbSJ(R18raRgoI`<(~F$U_A5qPygq^WSW?h|Of_km=u?egDt9W`S& zO0hRz9j5(JLl2XS-U9j6Zn^?l0}z1i6#1Tt;@CRJ@qb6B*gxz^Ietos>qr9$6Q1nP zJ|c$NnLU{we*FN}iY7vxF%2e-lTe*PJ90 zH}525fU{M9+Fl?|fOGM$@F}bCt9Y%oXJcL&J+gUxN@Jj)f^4xa`iHS8t^Xh&-aZlL z`|t~pVv|R%N2JH^qrj|Cd2CeLCEzM;M5{bHXnoY1c&o5I^5W}^iJFz-L@28pjh5S1Ce>>&=Si@WZhkDND7Emnat3iENp49O&C&LuN+?9o0C7!C@WPK z$-4yDiUBY9(}>|@Cn$7cK#RA;ZH$4OFL=<5NpcMn@(<%ZWl;zDFqL^Z3!iary+Y|m zI_Ee{O)C52Erw)u;7r9#SNy{6zS^#J(z%$m%z@Y}%G5JaT=XvT%@81{*BxCGv1ao3 zZ_)VTnf=3s#EEm zs<6F5KAX8L_RqtMK~ER3+s7XdtxRze!`WE(?q$B_Bqish6NGxhV>Wfj!I+oOwR*PA zwy&;67G3o^=C68hZ2Y?2YHGgnveX=o7sVd-Exk_V+ zWY5u`>Pk>RG=r<0% zm&b4t1MN2}M!cwYFN||huB!W7$gtt&wXlQ54|wMD8SKOS(BEJl`9uyz=E~e;vp>|P z61N&7_TpB(Vj7PrjJBiZ-@FOAZ?H{E1pBmJ$B* za-&_MpR&ahav#O4J$#$C*aR@016F$#WADx2+$X(Nq)R{hYhyRV`HQ_HY3Tpf|KUwo zc$V3j)+J3<(lG$C)SXXtmdja!cY0I0^Q+>;ig%k0V*KwSZnU_{DUC~Qus;6DLZvHQ zsgu|cdc@g_{g9F{V~#eMr|n(gfO7tTayO4jN+6&!<3B)lW*VkENlrOR|6&V$8eHVTkT*Fk%YP9Tbx$ybcfcaiTUi6Dd6&)}OL z(_@}?KQS}0;`hrU(pZGj44QNqR!^u?zW9K<7572@2yCC(QPah8Ch~MDrx8&Tzo}a@ zP{yzOAb0E%K#ggts=ugdWhi%}|8pfVKoX(Q(G+0;)qj}$V4xH8FpIR+&Ja3EqPqB| z#$ye2!8_vS{^_hw3fczJrucr`?*9B;`oQ~h@axs}P~dOIPB>ohbRR^Jg}%ff`rzF7 zhK`ZKZCTkx%Q^7V)0e_attIw}-0a5`rt4u>v{ZL-45X`YbeSqG>1yN}kYf;)+GUO{ zM+`6h5YSlJH6L(MKNln1C;DJA_6-QC_?Ng8%=NmZ%2+hP)l0&*-B>U4PwSCof2z3m z?J%Llswja<#&_rR0Ksi=I+>Vy!a$~=z41bzj_dlNd+AxDfMgx9vgL3xJtd-c@@J@` zw?XI!p%8qc-!L7AFj~+mWy#L|`5-y3^sBW^psk_dtKWSN)|{p}voCul`z~Dnn0`-k zQg>Z!Se7SZVI93yS3ypP(SFVkq1L_yAsz#yv3C<6B%~sXs?hGS5gLcy%9lEnf4JaI z2N4p_Fe2cP`W|QJe{q+q5op9`i{mhLk;tPx?b7% z>x;Ks8|ecvf_WER&uJ3N*KRJzaUQZFUXI3*enM}x2~W@iGX^~1HfbauBm>A3G!I`@ z?1pndbuY4rucST%NC8(=GO9Jby#sF-%&7n}eJqa4SD>vogQOozLYZ|J?r{_G2^qkI?rasSAZ@B>P zyc$?P*=$EN;th%*!JW(Zqq|&vv*2-CNtXb_)Po&&gn|pR{makh^%V3p^o_hXWNqY8 z1PYifZ)$NSij7nCxvG12>0O<4P9}K>KcjW8d)f)2J6Dko9809Uj zXRGB0->}T9ZirOTD}rbnVsG8Ug^DPgrYEvlI_iXd5xc?*EVkGv$@8gAS%JR!IVBH_ znweI9S_{Xu+|^NUs_*gRQgLujm^oW;dpFay5=CY!Ov={0lend)iBg65aon!qM8A-= zr6y!w=!pYWOA(_@+69@3wIazwJufm~uSeL4E_h)y58?)-sLOcFQyf9e^iq&GL2KSW zs$?;<^w;c@UVn>NEjDay#j?$uBV!N1hLiGorIiO8$uu%5eZoHhfb@Z106(x^x>Uet zIr!YjLO3o0FG_ISY4+}udcVx?&1klhE7>}}UtS3B|6H*)Az>dE7{MZVJjD0ar;dX9 z$bCyHVWnDXpNb%9y}gYk1Cvw!o1SJvIH_7?9TBGeWpyK;#NCdQwQq=f7)~bX`z-FtcEofXc8NM!i+$?%kGPh2DFG*>sG1;@brMRIpY!(a-C7w zr%sWx)LQ?<`O7~!K>|G|TNen80w@ z#z>|e#~;Ekhu27@vqzQO!v||>-|$@MB^a&)s|pXZI5_Tn$6F<1?Z*9^*~+ClUkf@b z$^P4t`0LTH*V>{Y@atqtXkETF{tr@{_z<1frRw$Y%{JfC0o(@{&Z87yD!uZf{!fo_ zB>2;fVj*l^HSrQqt=I$qz@f%RQ&XYI3%p)}chefM zhK#bc`+zlv-R_9Ecff>pzL6ho@#gcouEi=MzgryF6I!ct_L__!4-m*u=FqMc7BwL8 zSq&cXgJ@Qhdo*`?({q1}y-n*h<3Wdt=OI`{k;oE{dzkUDh|K)TQt( zPcb78*yQt7*xpG**`R^ZMGE22eJ$S9L*K{!gz(Jye8#cJ{at7g&)!STyH;MzjZo(z zp9y*5wk~D-AU8Mv}Cdv8D6Qe75tcZK}oCC@1L z6c%cNu_tc=;8^hs_jw+glgMF@M#Rgpem7WaGxyx|c-GD{D&a5I0MD0NxHeJyItg2J zsKs{)cyf}57tGi*Kr<-9yO!BoCggb>6>rZUzxqQ`Y+8Dl%9a@ z6K%0#eMmkP_pd}H%#{SY#iPCgm-_LCKL$i5gv!SqTB~`voKvbHl?G^Jr}s@LKQe5l zyJ-Aq9+fNHK2b!_ES|shAbqiYN=#0hXY3Pmau7B51p+TfMr*~St6Qj%C=kYoPtB7N z4f-IG%L8KFF7;tucKbcf#ddLhoGa+9g_Q#h3h9QJhj-<%!(8B;bEJt} zsya+!G5p`IWj3FrWx_V@Hm|O|Ur7wx=gN)bAX!~j#(Rn`XG0F zIx~_7o|k}HJ7xcudPNeL35Q7ubn}PJh9uPI$2DJ7ytrPxONje-3Ao|<&Z&%`0kv+a z!kETGb<5pMe3u|jeVSMyL#u6Oz4UtbSQFojlqZ>n^aJl{f4|H+gz92ia3JtigOZgl zzP8?2=aOyijh4FU{<;D5vBxtOzBul?fj<^_YaT7zD`-t%P50)oU1oTSwKEm-@0RL9 zGjA)L92*U!?M(cApPpW?u@wkYbnx#Omu~u8M=;*ySC}A4>iLzf)SgH7W*5c(d93mw zuzFrTNTugNN}w$n#-AV%FsBjbKZT^O8D(Q-SZwa`OVVNp*?o1z2w5`VzuYb=pb(bW zF#4UHant0mALHqV8=9&l-Q0y;-IL?-E8fjg-Aq1JOwG>Tg?PzRBVz@!-Gbg&rrk-7 z>{v9*1t1hIvdB>-PcY809xIWQQ6N$bjQ2!Ocr{oJc#SP=obE~gdwV_7FQ+|PZaFG? z7W9+!c`X=5M^qgp@z3GEFBs`vJXdu+NmLfWxZi0=%fI+nVxc7TnnAPekrxr2L(sQ^ z0bl=^d1)U8e)la80`S6n<|M4m{zxAShdTI8 zWuC~N!uQ1p?gj)M2;+0iO-vt-uRXhbq(bY@TQ69MN~mQ@+BKWUDg)MY?S2t-6hk0q zPy~#Q%*E*S@;}613z?+cyid&=fEb#<%A#+S73q7(|1sk2-%f9|y}f4#qJ+ix zZ6E4t{R%L>OSvI2DE?m-I=ZKCvJTHW`EFcJe4;$ zq>lCOTZwND?7VR9QG6%ISv9=%;#E+D+B2tT>^G3XH@R$wX+qf7K(q1P%Jn%|+1_Sg zBEowWZ|WlHR6X-_arfTn0>ejdl_9CLm>YkKAVX|k@n2pE@sEzr$VqWR74dZ_cpRvX zqqKI!Z;|9hJzj_H5F_Yi}C8F=v8G`PGu~jCKFM8kexvNWXD~Y4t^QD9c|MCLLct)qZ;b zeF@-&z@N<&V6P)_9;UHD_MQ?|-OKSmUqyL;Imj>>CTkl?0oT#Q!ZB6K;us(`CU*=!~W@-s^w}!k65DN z^WsX2ZBE&t(iPXeaZNw*jL`x^n$Egv%K-uDgNHYh+&84?p>Cu9LZtMk)VN$=tf(1z zRindK;TAu~#ZjpTYcF*wgx)`rQF~i2Bln>ri0gd=rx}I?U>8Rk1tJo%mVr0Lbm1W6SgajwuXoHl&))1G z)~?eN*1ClQURAEAk)fl|Vp`~Xc3o!=sv6ZQa|4FwUPtoJ=kvfs88RlE3!OsNBKD&( z8QS>IKkzWrR>bRRXXSrcE(H0){iY9tAw{mAhr1zt>FQikXCZv$unujKFxenXVSDa} zakvqJ+S8SJw`bF^IfO0FC;4MBrQyBmTFvgbu3}yi3uy%lr5kH?cE1GB_lhyQ7lC*0 z2c^F+o)`btWVSDoMrYkKT0cWib%*cWO!uR0^wMgL;Ay77xy%+VW}vk6{rBnKAEg&c z+enNhk{l1MIoxN)nxYWhy8aUOUHX*+fnVLILJ{m~2`vs$cM2x5FK+R37&cN4+_@FS zDN3fO;VYOING7OROIrrc7^|J+Kf;SI>tWsTaWyeDO{^c+&UFr(mQNf0ld36T$aOdB zg-_gZyclq<_ys`Psn!=Fj2FqKKgyr$$*8L1Lm4a($k99HOb@cJQkbJbPz zZ-S%Qd8Y{8FUZyhEj16;$C}iwc|IN1i~VRhE+4;*O8p}goPKPdK+BoeJU_T>MWJhd zssZ_S(H?pquia^qcU^`3MhhxOf#KqzW%I=TpNikIJM#gxZ&L=xpOuOK%(Q!i(M@UO z&2>K)#%4nU@ZQ)KO>Y*WH+dSsNq9Y6^49le+7db`@Ux0O?uaRH&i#@wvkB{i$Gih|Bjc6(;BxU?9-aXY`}vnC7v^kZZ%3(i0~lob zni_ZXLw2zZo%+}^+>fLMv}g?J?v5q#p*_wL!MB~BrO@X0c1Fydy;Vtiq0fPDfS|#7 ztLu9C4fic_B(0_>8`Jk}67i2?YN0!q1^W;mX#BIyQm;_2QDS;emu4zl`Kh2-U96Ru z{dCKuk%?0Frt>KQi~NOCL{gmS?K+r5KmExnKtg1}sTAPB-W5FZzILDwz3n4{f{>My?zNT+|Qpnj>e@uTFEP!!McsSt&gi05? z)omU4s(uRkn45I3WSR;4u_~_r+!s{@rfcG*as&x5)^=qc5~FDo!e?;3NE$UNGJ{x?w@;?EXnZhz z6dh_RcxH5kMyGNYK23b1d1CI>dVTt`5w`y1wQ(9XcE0jqA}}crSVE8Kvc#nSkaxA^BADGdN&Mhm zi}7pLVWmDdY;-*!LVrZimzk{vXhk#dfJ=1NG(h-UM4raE%_JGjA(5=!a^Zyo6|VJU zPU7C;0z_v3(zN4yI^;mN_a=B|jd5R?Uu&D-z80*YIlIe7o-a%>?Slf1AKgc;m6(S&V6OlGN*5AjgN28V18bc-;j{J~ zmjHGWCuCEVvAcOCw$@D2Vd9`dNcYy%6Am!7?FDAYw4gcX`e&pz4Vos@2%_~zzv+|2?cXNacfa}1CY7$@uywbjB zw@J;44R}RweQs`k;k}J{0sVrp14X1zD&We`bnZ{hY?bg`9uj>ZL6p4`ZZan(FzM!yekw1e8P%}#4EtFkhJZ62Z#6}iS+jP6n4m!P z(6rB%%e&`=w|5koJ`zT1KEHl4SgAYkkE$Z%Y;9yN8*Tb)B>XK&_~J|b_PI8EqkhtV zuHvNk)1Z2?S4&LiH<5jJ#2h1b}HD`HDc-33vH^H-6cy3kw2Qu~UPkfbfm zNvWqAwiPP_KEV0?Q5~+Y&Vtfg6fr0`tw7wFvQEb;7W{+6ipp% z71jA9Nh|G*aaG`Bds?0`wYR)d@_XF9RRkG4RrkA$E*^6axqZdQjg#Ghi!cvtM#pR_X`7Ir|r4B(6t_a3vKo=4E$3FN21qsVb@{?7$?>sIxm zn!havu)5#X0^{pi`eTE(bGK4v0N939x5cMwu6S(u%B~G1X1eaj(AJ{GuJZ5~r-m$( zx+Y6kHu1sK*2uss1KTGL*8akxl)+t=hjTmEO?xtwxmCEW=e4izsfji=%4mz-zqR+m zd`o~SDADH_pgWRCnp4h*BWiwyiZxo3CEB0XDsSTTp(Kd$#R&5P}T!_`E` zY7ggHT^n*mCOjQ7o;APpR)ou6m()3x%ezyZG_UjVVa-N{gLiA6NK@5Sp+v?Ks5C}! zb-03{3Nb*pwZ*HWV_T&vmU!|M?5Xq|iSBOdVCur;Uy?+x#-lzfkDb)#PfLdIn_*;&!)>sQyR{glADISFu z)it8IH`jY2@CJfd%A?(Wr;zt```jzm@k*Y(qx}zXMi8i}W>M-3hE(#=J(j3p{e@*{3 zO*z6c`Rn&B74oOUx_+1|R4HG+4hzp0c>&soV`A!mVC*hY2V{quyNb{BOH5r0_v+_a`@ z8(2fTuf3{>&;AAp@3wft4Nv$-SfAw4Z*PJ?rOTdfDMw6W>g|6{9vYeoNk;`P{wNto zw<+T0@Jg6q6O32Rnn9SFe742K7f}6rJKV}rU3e=p;9;7pNKM0IqqpPvxjN%!1rICM zl^)M25u5Olm{f3-n$XA2`Gb7ha*uw$*Awg}k6)!FjP~>kz%$;QB4&Yer>mei;@wqv zG?@Zy*ssA%Z#~5*;+EQyD$t3FQr^XLzV#XJdb8p;_1={4BroL*am!?f3B6)J@C9Z+ z%oB&|Vx{&?(IY&&$Qp3>lIUX$Fsf2M@!loi<((ovez7+7YbMQ|a(hSK?J-o z_pU^Puh%i&{BOMxG;;{m!?YM?@Y5r);<}|a`e;a{P;hny@1mGamjCU?HrfDQq9*}8`E{Sz8s{RTNea+t=FX68I%f5v_%Ns5=?nDLd&jdnz|{M+||hO{N{}} zArFn)^siJO{J@qZteb?@yW{$B9ON0^4k~m}2y@1($q-*))ryPTi{%HkY2td=UPu%q zaxIQDHbJSrT=U_14Ebd1^8p1~CMX|eo z2jBV@^YZ$|of+W1Uy!4It2rw*l86F`z0D;c;;=_~W6$;O&(Y1gwEc7^=|0}y{R$(p z8?U2z3OYb{dM!+E4fpk30$7kP)3M*#)gFV+1#5l>!@ zQ-@wegvvpH`uKRvnl+YvGwsLY3c^sPU{ig7FSl3>w8*7U%P1+a|FyOo1~%_ax@t%O zK}GRTksKsCeDATr$LG`jvjr`a#SM+`7Z#u2N?C*atkUBOaC~b=LCt<3@vBfM)DcV? zBJW`+-m5Fw2C=PwR*@FB*vu|yyZE##yp)0aL{3}f_h7l*s)=nf(WDuT;^jWy?Onc! zU%-Te$JA)8-R2WQ`7ZM1=g3xvbCM7T{KUrzIow@L!@l*;JN?4s-!)B;fv zD;4VvLs{&uFcA&;MsRx#iriO9ad(SrCgonzOn(k6)nzN^UJQy{{~YI`QR@S?Ag_vF z68J6g&!Fnry=ttOzW8-4N9B1@tXzYihNtg@`1l{M${PdR?VOnsfc+X|95lXU%dAMJhQ4vEYKJDvOzNhhhcIZvR{HLtuiqdr8N>CZ@PUaPAT?V!Rssw00 z*7pGle~X|xJ8K2jFK``occ;#fUMyz-;y!4s*N(RuB<4dRZv$5Ei(Y9GH98mKP|9Ik z1NNzUc?w#^_niK;URvoKeR{MnZ6n{&XrkZvh8wLbH2>j|#Bx)=3T@$T9t%+s1&e8p zXx>k1@S2uhYzpR*SRT9*H@-5wAfBSZ!?#K!@I9s1SIk}d?>5_cB=Px?^$yHASDVKK zWxqExEZveeushuh3(p!x1`zD=PUPKtjcs+U@h@Y#M80IOEZr{9KkBWIl%%7Vj+x~J z+;_htmY%Kq<@+}(b6hV#YvNz2Lj-ttX``HlyT$)8OueQj)cBaa&!2D?Q2 zh|FlI*G^+g6`4<$aIcs3yzw$tyHyg;{q@I(V7o_^G7nC`

-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 AGynhq literal 0 HcmV?d00001 diff --git a/docs/assets/deviceModels/mhc-wled-esp32-p4-shield.jpg b/docs/assets/deviceModels/esp32-p4-mhc-wled-shield.jpg similarity index 100% rename from docs/assets/deviceModels/mhc-wled-esp32-p4-shield.jpg rename to docs/assets/deviceModels/esp32-p4-mhc-wled-shield.jpg diff --git a/docs/assets/deviceModels/esp32-p4-eth.png b/docs/assets/deviceModels/esp32-p4-waveshare-eth.png similarity index 100% rename from docs/assets/deviceModels/esp32-p4-eth.png rename to docs/assets/deviceModels/esp32-p4-waveshare-eth.png diff --git a/docs/assets/deviceModels/waveshare-esp32-p4-nano.jpg b/docs/assets/deviceModels/esp32-p4-waveshare-nano.jpg similarity index 100% rename from docs/assets/deviceModels/waveshare-esp32-p4-nano.jpg rename to docs/assets/deviceModels/esp32-p4-waveshare-nano.jpg diff --git a/docs/assets/deviceModels/quinled-dig-next-2.jpg b/docs/assets/deviceModels/esp32-pico-quinled-dig-next-2.jpg similarity index 100% rename from docs/assets/deviceModels/quinled-dig-next-2.jpg rename to docs/assets/deviceModels/esp32-pico-quinled-dig-next-2.jpg diff --git a/docs/assets/deviceModels/quinled-dig-2-go.jpg b/docs/assets/deviceModels/esp32-quinled-dig-2-go.jpg similarity index 100% rename from docs/assets/deviceModels/quinled-dig-2-go.jpg rename to docs/assets/deviceModels/esp32-quinled-dig-2-go.jpg diff --git a/docs/assets/deviceModels/quinled-dig-quad-v3.jpg b/docs/assets/deviceModels/esp32-quinled-dig-quad-v3.jpg similarity index 100% rename from docs/assets/deviceModels/quinled-dig-quad-v3.jpg rename to docs/assets/deviceModels/esp32-quinled-dig-quad-v3.jpg diff --git a/docs/assets/deviceModels/quinled-dig-uno-v3.jpg b/docs/assets/deviceModels/esp32-quinled-dig-uno-v3.jpg similarity index 100% rename from docs/assets/deviceModels/quinled-dig-uno-v3.jpg rename to docs/assets/deviceModels/esp32-quinled-dig-uno-v3.jpg diff --git a/docs/assets/deviceModels/hpwit-shift-register-board.jpg b/docs/assets/deviceModels/esp32-s3-n16r8-hpwit-shift-register-board.jpg similarity index 100% rename from docs/assets/deviceModels/hpwit-shift-register-board.jpg rename to docs/assets/deviceModels/esp32-s3-n16r8-hpwit-shift-register-board.jpg diff --git a/docs/assets/deviceModels/esp32-s3-testbench.jpg b/docs/assets/deviceModels/esp32-s3-n16r8-testbench.jpg similarity index 100% rename from docs/assets/deviceModels/esp32-s3-testbench.jpg rename to docs/assets/deviceModels/esp32-s3-n16r8-testbench.jpg diff --git a/docs/assets/deviceModels/lightcrafter-16.jpg b/docs/assets/deviceModels/esp32-s3-n8r8-lightcrafter-16.jpg similarity index 100% rename from docs/assets/deviceModels/lightcrafter-16.jpg rename to docs/assets/deviceModels/esp32-s3-n8r8-lightcrafter-16.jpg diff --git a/docs/assets/deviceModels/esp32-s3-stephanelec-16p.jpg b/docs/assets/deviceModels/esp32-s3-n8r8-stephanelec-16p.jpg similarity index 100% rename from docs/assets/deviceModels/esp32-s3-stephanelec-16p.jpg rename to docs/assets/deviceModels/esp32-s3-n8r8-stephanelec-16p.jpg diff --git a/docs/assets/deviceModels/shelly.jpg b/docs/assets/deviceModels/esp32-shelly.jpg similarity index 100% rename from docs/assets/deviceModels/shelly.jpg rename to docs/assets/deviceModels/esp32-shelly.jpg diff --git a/docs/contributing/documentation-standards.md b/docs/contributing/documentation-standards.md index 784ee390e..e9e1e136c 100644 --- a/docs/contributing/documentation-standards.md +++ b/docs/contributing/documentation-standards.md @@ -143,7 +143,7 @@ Two scales below a page. **A module** has exactly one reference page written and - **No sentence whose job is tone.** Every sentence carries a fact the reader needs. Three shapes to cut: a second person used for effect rather than instruction, a flourish before any information arrives, and a rhetorical question the page then answers. Vale checks spelling, sentence length and weasel words, so this one is the writer's judgement and the reviewer's check. - **Follow the [principles](../CLAUDE.md#principles).** Three bear on documentation directly: - **Minimalism**: every fact has one home; history lives in git. - - **Present tense only.** "No X anymore" narrates a removal, which is history. Describe the path that exists today. + - **Present tense only.** "No X anymore" narrates a removal, which is history. Describe the path that exists. Vale flags the words that date a sentence ("today", "currently", "no longer", "not yet"). - **Positive form only.** "Not", "never", "neither", "without", "un-" and "non-" are the alarm bells: a negation says everything a thing is not, which is no shape at all. A real constraint stays ("the DMA cannot read PSRAM at shift clock"); a bare absence goes. - **Industry standards**: the textbook name for a thing, so a reader recognizes it without being taught our vocabulary. A bespoke choice carries its one-line reason where it appears. - **Continuous improvement**: a doc describing what the code no longer does is a defect. Fix it in the change that opened the file, not in a sweep. diff --git a/docs/explanation/architecture/mooncore.md b/docs/explanation/architecture/mooncore.md index 1b041f22a..bf17789e8 100644 --- a/docs/explanation/architecture/mooncore.md +++ b/docs/explanation/architecture/mooncore.md @@ -50,7 +50,7 @@ A **service** is a MoonModule (role `ModuleRole::Service`) that bridges to the o The defining line is the **data relationship, not the connector**: *does the module consume the light output buffer?* If yes it's a **driver** (ArtNet, DMX, SPI-LED all consume the buffer, differing only in transport; a DMX sender uses a UART/RS-485 transport but is a driver because it sends the rendered buffer). If no, it's a **service**. -Services are **user-add/deletable children of the `Services` container**, the core-domain twin of the light pipeline's `Effects`/`Drivers`: a top-level container holding user-added children of one role. The firmware is identical whether or not the hardware is wired. The user adds the module when they solder a gyro on and removes it later, reusing the generic child add, replace, delete and persistence machinery; `Services` declares `acceptsChildRoles("service")`. Fixed device infrastructure lives under **System** instead, wired by code rather than user-added: identity, network, and the inspection tools Tasks and I2cScan. That is the System/Services split. Direction is per-module, not a role: a service may read (gyro), write (relay), or both, so one `Service` role spans the category. Each is a header-only or `.h`+`.cpp` core module under `src/core/`, reaches hardware only through a domain-neutral platform primitive (`platform::i2c*`, `platform::audioMic*`, …), and gets a spec in `docs/moonmodules/core/services.md` (enforced by `check_specs.py`). Most poll in `tick20ms` or `tick1s`. The exception is a service whose data an effect consumes *every frame*: [AudioService](../../moonmodules/core/moxygen/AudioService.md) reads + analyses its IΒ²S microphone in `tick()` because the audio effects react per render tick, and its per-tick cost (one FFT) is part of the render budget. Automatic bus-probe detection is out of scope; the manual path is the foundation. +Services are **user-add/deletable children of the `Services` container**, the core-domain twin of the light pipeline's `Effects`/`Drivers`: a top-level container holding user-added children of one role. The firmware is identical whether or not the hardware is wired. The user adds the module when they solder a gyro on and removes it later, reusing the generic child add, replace, delete and persistence machinery; `Services` declares `acceptsChildRoles("service")`. Fixed device infrastructure lives under **System** instead, wired by code rather than user-added: identity, network, the inspection tool Tasks, and the board's I2C bus. That is the System/Services split. Direction is per-module, not a role: a service may read (gyro), write (relay), or both, so one `Service` role spans the category. Each is a header-only or `.h`+`.cpp` core module under `src/core/`, reaches hardware only through a domain-neutral platform primitive (`platform::i2c*`, `platform::audioMic*`, …), and gets a spec in `docs/moonmodules/core/services.md` (enforced by `check_specs.py`). Most poll in `tick20ms` or `tick1s`. The exception is a service whose data an effect consumes *every frame*: [AudioService](../../moonmodules/core/moxygen/AudioService.md) reads and analyzes its IΒ²S microphone in `tick()`, because the audio effects react per render tick. Its per-tick cost, one FFT, is part of the render budget. Automatic bus-probe detection is out of scope; the manual path is the foundation. **An effect reads a service's data** through the shared-struct pull pattern from [Data exchange](moonmodule.md#data-exchange-between-modules), with no new mechanism. The service owns a small POD struct overwritten in place each poll or tick, and the consuming effect holds a `const` pointer to it. The first concrete case is audio: AudioService produces an `AudioFrame` (level + 16-band spectrum + peak) that [AudioSpectrumEffect](../../moonmodules/light/effects.md) and the other audio effects consume. It reaches the frame through a static `AudioService::latestFrame()` rather than a boot-time setter. That variation exists because an audio effect can be added through the UI *after* boot and must still find the one live mic (a setter only wired the boot instance). The active mic registers itself in `setup()` and clears the pointer in `release()`, so add/remove in any order returns either the live frame or a static silent one, never null. A service that only *displays* its readings (the gyro today) skips the consumer side entirely. diff --git a/docs/moonmodules/core/services.md b/docs/moonmodules/core/services.md index f0215d9d6..69f010c7f 100644 --- a/docs/moonmodules/core/services.md +++ b/docs/moonmodules/core/services.md @@ -23,7 +23,9 @@ A user-added Service: the audio source the audio-reactive effects consume. `mode - `mode`: Local audio, Receive network or Simulate, each showing only its own controls below. - `micMode`: (Local, IΒ²S targets) `I2S` for a three-wire part, `PDM` for a two-wire one. - `sckPin` / `wsPin` / `sdPin`: (Local, IΒ²S targets) the bus GPIOs, unset until entered. -- `mclkPin`: (Local, IΒ²S targets) the master clock a line-in ADC may need; unset for a plain mic. +- `mclkPin`: (Local, IΒ²S targets) the master clock an ADC or codec needs; unset for a plain mic. +- `codec`: (Local, IΒ²S targets) `ES8311` for a mic behind that codec, else `none`. +- `codecAddr`: (with a codec) its address on the [I2C bus](system.md#i2c-bus), in hex (`0x18`). - `device`: (Local, desktop) the OS capture input, `default` following the system setting. - `sampleRate`: (Local) mic/ADC sample rate. - `levels`: (Local) who sets the display window: `manual`'s two sliders, or `automatic`, a learner. diff --git a/docs/moonmodules/core/system.md b/docs/moonmodules/core/system.md index 18f6fc0a2..a03ed1d95 100644 --- a/docs/moonmodules/core/system.md +++ b/docs/moonmodules/core/system.md @@ -8,7 +8,7 @@ The device's fixed infrastructure: identity, network, provisioning, firmware, an ### System -The device's identity and vitals: name (behind mDNS `.local`, the SoftAP SSID, the DHCP hostname), uptime, heap and the tick it renders at. Its fixed inspection children (Tasks, I2C scan) hang beneath it. +The device's identity and vitals: name (behind mDNS `.local`, the SoftAP SSID, the DHCP hostname), uptime, heap and the tick it renders at. Its fixed children (Tasks, the I2C bus, Pins) hang beneath it. System module controls @@ -240,21 +240,19 @@ Known renames from [MIGRATING.md](../../reference/MIGRATING.md) apply before upl Detail: [technical](moxygen/FileManagerModule.md) - + -### I2C scan +### I2C bus -A fixed System module (wired-by-code, always present) that probes the IΒ²C bus on a button press and reports the addresses found, a hardware bring-up tool. The bus pins default to unused (βˆ’1), so a board without an IΒ²C device claims no GPIO for it; a board with a bus sets its pins via the catalog, or you type them for an ad-hoc scan. Passive until the scan button is pressed. +A fixed System module (wired-by-code, always present) that owns the board's IΒ²C bus: it opens the bus on its pins, every device on the bus attaches to it, and a scan probes it. A device names only its own address, such as AudioService's `codecAddr`; the wires are stated here, once per board. The pins default to unused (βˆ’1), so a board without an IΒ²C device claims no GPIO. -I2C scan module controls +I2C bus module controls -- `sda` / `scl`: the bus GPIOs, defaulting to βˆ’1 for unused. - -A board with a fixed bus injects its own through the catalog, or you type the pins for an ad-hoc scan. The classic Arduino-ESP32 pair is 21/22. +- `sda` / `scl`: the bus GPIOs, βˆ’1 for unused; a board's catalog entry sets them. - `scan`: a button; press to probe the bus now. -- read-only: `result` (addresses found). +- read-only: `result`, each address found, named by the module driving it: `0x18 Audio (ES8311)`. -Detail: [technical](moxygen/I2cScanModule.md) +Detail: [technical](moxygen/I2cBusModule.md) diff --git a/docs/moonmodules/light/drivers.md b/docs/moonmodules/light/drivers.md index 0d9be2a34..a8251bcc7 100644 --- a/docs/moonmodules/light/drivers.md +++ b/docs/moonmodules/light/drivers.md @@ -67,7 +67,8 @@ Plus the [shared controls](#shared-driver-controls) above: - `pins`: data GPIO list: `18,17,16`, or ranges like `20-23`, mixed freely. Empty idles until set. - `ledsPerPin`: lights per strand. Blank splits evenly, one number to all, a list per strand. - `peripheral`: the DMA peripheral, filtered to what the chip supports. It divides the card. -- `doubleBuffer`, `pinExpander`, `latchPin`, bus pins: shown per peripheral. +- 🎚️ `doubleBuffer`: encode the next frame while this one clocks out, where supported. +- `pinExpander`, `latchPin`, bus pins: shown per peripheral. - read-only `frameTime`: what one frame costs on the wire, the headroom the pipeline has left. - πŸ”§ `loopbackTest`: a TX to RX self-test, verdict in the status field. - πŸ”§ `loopbackStrand` / `loopbackIntrusive`: which strand it tests, and whether it may disturb output. diff --git a/docs/moonmodules/light/supporting.md b/docs/moonmodules/light/supporting.md index 1327eff71..1a9bff2b3 100644 --- a/docs/moonmodules/light/supporting.md +++ b/docs/moonmodules/light/supporting.md @@ -51,6 +51,7 @@ The container of driver modules, owning the shared buffer and the per-light outp - `on`: master power, on by default. Off scales the output to black while preserving `brightness`, so switching on restores the level. Every consumer drives it: the UI, infrared, the WLED app, Home Assistant and MQTT. +- 🎚️ `relayPins`: GPIOs switching the LED power supply, closed while `on` and brightness > 0. - `brightness`: global output brightness, multiplied with each driver's `localBrightness`. - `palette`: what effects sample from, built in or [scripted](moonlive.md); settable by name too. - `multicore`: run the output stage on the second core. diff --git a/docs/reference/MIGRATING.md b/docs/reference/MIGRATING.md index 6a82a7fe1..45dc57fdd 100644 --- a/docs/reference/MIGRATING.md +++ b/docs/reference/MIGRATING.md @@ -34,6 +34,20 @@ For a device still on old firmware (no Backup button yet), the [installer page]( ## Unreleased +### An audio codec is the device model's setting + +**Action: on an ESP32-S31 CoreBoard, apply its device model again, or set the I2C bus to SDA 51 / SCL 50 under System and AudioService's `codec` to `ES8311` with `mclkPin` 52.** + +The codec in front of a microphone is now AudioService's runtime configuration, where the S31 firmware used to assume it. One P4 firmware so serves every P4 board, with or without a codec. +A board configured before this change has no codec set, and its microphone stays silent until it does. + +### The I2C scan is the board's I2C bus + +**Action: *nothing* for a restored backup, which carries the pins over; on a running device, set `sda` and `scl` again under System, I2cBus.** + +The scan module became the owner of the board's I2C bus, which every device on it shares. A codec now names only its address, and the wires are stated once. +A device's saved scan pins sit under the old module type and are not read at boot. + ### The access point always broadcasts its name and follows the radio's channel **Action: *nothing*.** diff --git a/docs/reference/hardware/esp32-s31-coreboard.md b/docs/reference/hardware/esp32-s31-coreboard.md index 7189a94e2..b63f0712c 100644 --- a/docs/reference/hardware/esp32-s31-coreboard.md +++ b/docs/reference/hardware/esp32-s31-coreboard.md @@ -28,12 +28,13 @@ The onboard electret mic (J6) and speaker connect through an **ES8311 mono codec | PA_CTRL | 57 | NS4150B amplifier enable | > **SDA/SCL are GPIO51/GPIO50**, the *opposite* of what the schematic's `ESP_I2C_SDA` / -> `ESP_I2C_SCL` net labels suggest. Bench-confirmed: the [I2cScanModule](../../moonmodules/core/moxygen/I2cScanModule.md) +> `ESP_I2C_SCL` net labels suggest. Bench-confirmed: the [I2cBusModule](../../moonmodules/core/moxygen/I2cBusModule.md) > (sda=51, scl=50 in the S31 catalog entry) finds the ES8311 ACK at 0x18; with 50/51 nothing > ACKs. The other audio pins match the schematic + the chip's GPIO table (all of GPIO50–57 are > plain I/O GPIOs routed through the matrix, no special-function conflict). - **ES8311 I2C address: `0x18`** (the default; set by the `CE` pin tie). +- The S31 catalog entry opens the I2C bus on SDA 51 / SCL 50 and sets AudioService's `codec` to `ES8311` with `mclkPin` 52. Applying the device model is what turns the codec on. - Driven by Espressif's **`esp_codec_dev`** managed component (the ES8311 driver). The codec needs **MCLK running before it answers I2C**, and `es8311_codec_cfg.mclk_div` must be set (256, the standard ratio) or `open` fails "unable to configure sample rate". So AudioService brings up the I2S channel (which drives MCLK on GPIO52) **before** the codec I2C config. - **Mic-only path** (audio-reactive input) needs MCLK/SCLK/LRCK + ASDOUT (record) + the I2C bus. @@ -116,6 +117,7 @@ The two pins in **one column are physically stacked**, so a 2-pin jumper cap bri Wi-Fi 6 Β· Bluetooth (no separate BLE soc-flag) Β· IEEE 802.15.4 (Thread/Zigbee) Β· USB-OTG Β· GPSPI Β· TWAI (CAN) Β· RMT Β· Parlio Β· LCD_CAM i80 Β· on-chip EMAC Β· PSRAM. RISC-V dual-core. -The S31 catalog entry drives **LEDs** (RMT on GPIO60) and **Wi-Fi 6**, and wires an -**[I2cScanModule](../../moonmodules/core/moxygen/I2cScanModule.md)** on the codec bus (SDA 51 / SCL 50) for -I2C bring-up. The **[AudioService](../../moonmodules/core/moxygen/AudioService.md)** ES8311 path is implemented, the codec seam configures the ES8311 over I2C (codec reachable, ACK at 0x18) and AudioService reads the I2S mic. End-to-end mic validation depends on confirming MCLK at GPIO52; the S31 entry keeps **Audio** under `planned` until that bench check passes, so the installer advertises only what's confirmed working. The other board capabilities (Ethernet, Bluetooth, SD, USB host, …) likewise live in the entry's `planned` list, see the S31 entry in `mooninstaller/deviceModels.json`. +The S31 catalog entry drives **LEDs** (RMT on GPIO60), **Wi-Fi 6** and **Ethernet**, and opens the **[I2C bus](../../moonmodules/core/moxygen/I2cBusModule.md)** on the codec's pins (SDA 51 / SCL 50). +The **[AudioService](../../moonmodules/core/moxygen/AudioService.md)** ES8311 path is bench-verified end to end: the codec answers at 0x18 with MCLK on GPIO52, and the mic level follows sound. +The S31 entry therefore lists **Audio** as supported. +The other board capabilities (Bluetooth, SD, USB host, …) live in the entry's `planned` list in `mooninstaller/deviceModels.json`. diff --git a/docs/reference/hardware/mhc-wled-esp32-p4-shield.md b/docs/reference/hardware/mhc-wled-esp32-p4-shield.md index b8bf7bfda..ea081d344 100644 --- a/docs/reference/hardware/mhc-wled-esp32-p4-shield.md +++ b/docs/reference/hardware/mhc-wled-esp32-p4-shield.md @@ -5,7 +5,7 @@ Terminal pinout and onboard features for the **MHC-WLED ESP32-P4 shield** (myhom > **Board revision:** the terminal map and RS-485 wiring below are transcribed from a **V1** board (the builder's labeled V1 photos + schematics). The overview render is a **V2** render. Whether V2 keeps the identical GPIO↔terminal wiring is **not confirmed here**, so treat the map as V1-specific and verify against your own board's silkscreen if you have a different revision. **Sources** -- Overview render (board V2): [`docs/assets/deviceModels/mhc-wled-esp32-p4-shield.jpg`](../../assets/deviceModels/mhc-wled-esp32-p4-shield.jpg) +- Overview render (board V2): [`docs/assets/deviceModels/esp32-p4-mhc-wled-shield.jpg`](../../assets/deviceModels/esp32-p4-mhc-wled-shield.jpg) - Silkscreen (photographed) + the builder's V1 schematics and terminal maps (myhome-control / Wladi, 2026-07-16); the transcriptions below come from those. The schematics supersede the marketing render where they differ. ## Pinout @@ -29,7 +29,7 @@ The LED-data outputs. Each terminal is `O` on the silkscreen; a level shif | Terminal | GPIO | Note | |---|---|---| | O21 O20 O25 O5 O7 O23 O8 O27 | 21 20 25 5 7 23 8 27 | LED lanes (Parallel LED, peripheral `Parlio`, default), in terminal order | -| O7 / O8 | 7 / 8 | ALSO the IΒ²C bus (SDA 7 / SCL 8). Driving them as LED lanes means no IΒ²C on this shield, which is why the catalog entry carries no I2cScan module. To get IΒ²C back: move those two strands to `O22`/`O24` **and** set the driver's `pins` to `21,20,25,5,22,23,24,27` to match. Rewiring alone leaves the lanes still driving 7/8. | +| O7 / O8 | 7 / 8 | ALSO the IΒ²C bus (SDA 7 / SCL 8). Driving them as LED lanes means no IΒ²C on this shield, which is why the catalog entry carries no I2cBus module. To get IΒ²C back: move those two strands to `O22`/`O24` **and** set the driver's `pins` to `21,20,25,5,22,23,24,27` to match. Rewiring alone leaves the lanes still driving 7/8. | | O3 | 3 | also on RS-485 (`A-3-B`) β€” see note below | | O4 | 4 | also on RS-485 (`A-4-B`) β€” see note below | | GND | β€” | ground for the output block | @@ -72,6 +72,9 @@ Inputs are **diode-protected with a ~16 kHz low-pass filter** β€” designed for r An onboard **PCM1808** ADC captures a line-in signal and outputs IΒ²S to the P4. Catalog `AudioService`: **SCK 32 Β· WS 26 Β· SD 33 Β· MCLK 36** (the P4 is IΒ²S master, so it drives MCLK). +The P4-NANO's own microphone, behind its ES8311 codec, reads silent with the shield attached, though the codec answers on IΒ²C and the IΒ²S clocks run. +On a bare P4-NANO the same settings hear the room. The cause is not known yet, so on the shield the line-in is the audio input. + ### Ethernet (RMII) The P4-NANO's RMII PHY: **MDC 31 Β· MDIO 52 Β· RST 51 Β· CLK 50 (external-in) Β· PHY addr 1**, `ethType` IP101, external clock. (Catalog `EthernetModule`.) diff --git a/docs/reference/metrics/code.md b/docs/reference/metrics/code.md index 7885f79ba..9b2d89682 100644 --- a/docs/reference/metrics/code.md +++ b/docs/reference/metrics/code.md @@ -4,7 +4,7 @@ Generated by [`moondeck/check/check_code.py`](../../../moondeck/check/check_code Every function, block and file over a limit the [coding standards](../../contributing/coding-standards.md#static-checks) set. Current state only: the trend is this file's git history. The list only shrinks, per rule and in total, and a touched file clears its own rows. -**1497 finding(s)** across 309 file(s). 3.06% of all lines are duplicated, 3791 lines. +**1497 finding(s)** across 310 file(s). 3.05% of all lines are duplicated, 3791 lines. ## By rule @@ -68,9 +68,9 @@ The next function worth simplifying is at the top of each file's list. | `test/unit/light/unit_ParallelLedDriver_ring.cpp` | 20 | `lines 486-503, also in test/unit/light/unit_ParallelLedDriver_ring.cpp:432`, duplicated block 18 | | `src/core/system/NetworkModule.h` | 19 | `NetworkModule::syncAccessPoint`, complex function 18 | | `src/platform/esp32/platform_esp32.cpp` | 19 | `platform::httpRequest`, complex function 30 | -| `src/core/services/AudioService.h` | 18 | `AudioService::syncEnsureSocket`, complex function 17 | | `src/platform/esp32/platform_esp32_moon_i80.cpp` | 18 | `platform::createRingState`, complex function 32 | | `test/unit/core/unit_PinsModule.cpp` | 18 | `lines 46-57, also in test/unit/light/unit_Hub75Driver_pins.cpp:66`, duplicated block 12 | +| `src/core/services/AudioService.h` | 17 | `AudioService::syncEnsureSocket`, complex function 17 | | `src/light/effects/PacmanEffect.h` | 16 | `lines 243-257, also in src/light/effects/FishTankEffect.h:244`, duplicated block 15 | | `src/light/moonlive/MoonLiveBuiltins_light.h` | 15 | `moonlive::mm_light_escape`, complex function 13 | | `src/platform/esp32/platform_esp32_ota.cpp` | 15 | `platform::otaWriteMoonBase`, complex function 33 | @@ -266,8 +266,8 @@ The next function worth simplifying is at the top of each file's list. | `test/unit/light/unit_SingleColumnLayout.cpp` | 2 | `lines 12-25, also in test/unit/light/unit_GridLayout.cpp:8`, duplicated block 14 | | `moondeck/check/check_clang_query.py` | 1 | `check_clang_query.py`, large file 1057 | | `moondeck/check/check_docgen.py` | 1 | `check_docgen.py`, large file 1798 | -| `moondeck/check/check_footprint.py` | 1 | `lines 31-41, also in moondeck/check/check_nonblocking.py:29`, duplicated block 11 | -| `moondeck/check/check_nonblocking.py` | 1 | `lines 29-39, also in moondeck/check/check_footprint.py:31`, duplicated block 11 | +| `moondeck/check/check_footprint.py` | 1 | `lines 31-41, also in moondeck/check/check_nonblocking.py:30`, duplicated block 11 | +| `moondeck/check/check_nonblocking.py` | 1 | `lines 30-40, also in moondeck/check/check_footprint.py:31`, duplicated block 11 | | `moondeck/ci/verify_version.py` | 1 | `lines 39-45, also in moondeck/scenario/run_scenario.py:33`, duplicated block 7 | | `moondeck/docs/build_docs.py` | 1 | `lines 43-49, also in moondeck/scenario/run_scenario.py:33`, duplicated block 7 | | `moondeck/docs/screenshot_modules.py` | 1 | `screenshot_modules.py`, large file 1228 | @@ -286,6 +286,7 @@ The next function worth simplifying is at the top of each file's list. | `src/core/services/OscPacket.h` | 1 | `osc::parse`, complex function 32 | | `src/core/system/FileManagerModule.cpp` | 1 | `platform::filesystemUsed in tick1s`, blocking call on the render path 1 | | `src/core/system/HttpServerModule.h` | 1 | `HttpServerModule::cancelBufferedSend`, deeply nested 4 | +| `src/core/system/I2cBusModule.h` | 1 | `I2cBusModule::scan`, complex function 18 | | `src/core/system/Sha1.h` | 1 | `sha1`, deeply nested 5 | | `src/core/util/Addressing.h` | 1 | `sendAddressed`, complex function 11 | | `src/core/util/CaptivePortal.h` | 1 | `captive::dnsReply`, complex function 21 | @@ -324,13 +325,13 @@ The next function worth simplifying is at the top of each file's list. | `src/light/util/E131Packet.h` | 1 | `parseE131Packet`, complex function 17 | | `src/module_types.cpp` | 1 | `registerModuleTypes`, long function 148 | | `src/platform/desktop/moonlive_asm_host.h` | 1 | `lines 36-43, also in src/platform/esp32/moonlive_asm_riscv.h:36`, duplicated block 8 | -| `src/platform/desktop/platform_config.h` | 1 | `lines 80-94, also in src/platform/esp32/platform_config.h:246`, duplicated block 15 | +| `src/platform/desktop/platform_config.h` | 1 | `lines 71-85, also in src/platform/esp32/platform_config.h:225`, duplicated block 15 | | `src/platform/esp32/moonlive_asm_xtensa.h` | 1 | `lines 79-96, also in src/platform/esp32/moonlive_asm_riscv.h:74`, duplicated block 18 | -| `src/platform/esp32/platform_config.h` | 1 | `lines 246-260, also in src/platform/desktop/platform_config.h:80`, duplicated block 15 | +| `src/platform/esp32/platform_config.h` | 1 | `lines 225-239, also in src/platform/desktop/platform_config.h:71`, duplicated block 15 | | `src/platform/esp32/platform_esp32_i2s.cpp` | 1 | `platform::audioMicInit`, complex function 11 | | `src/platform/esp32/platform_esp32_tasks.cpp` | 1 | `platform::taskSnapshot`, complex function 12 | -| `src/platform/platform.h` | 1 | `platform.h`, large file 1174 | -| `src/ui/app.js` | 1 | `app.js`, large file 8953 | +| `src/platform/platform.h` | 1 | `platform.h`, large file 1192 | +| `src/ui/app.js` | 1 | `app.js`, large file 8969 | | `src/ui/install-picker.js` | 1 | `install-picker.js`, large file 1048 | | `src/ui/preview3d.js` | 1 | `preview3d.js`, large file 1431 | | `test/js/installer-eth-only.test.mjs` | 1 | `lines 13-23, also in test/js/ui-mooncloud.test.mjs:6`, duplicated block 11 | diff --git a/docs/reference/metrics/docgen.md b/docs/reference/metrics/docgen.md index b4dfaa727..a35b7fb10 100644 --- a/docs/reference/metrics/docgen.md +++ b/docs/reference/metrics/docgen.md @@ -4,9 +4,9 @@ Generated by [`moondeck/check/check_docgen.py`](../../../moondeck/check/check_do Every place the generated documentation breaks the shape [the standards](../../contributing/documentation-standards.md#the-card) define. Current state only: the trend is this file's git history. The list only shrinks. -**0 error(s)** and **2477 warning(s)** across 181 page(s). +**0 error(s)** and **2466 warning(s)** across 178 page(s). -**5634 appendix line(s)** after `@moreinfo`. This only falls as well: a cleanup cuts first, and moves only what neither the code, the test name nor the spec says. +**5633 appendix line(s)** after `@moreinfo`. This only falls as well: a cleanup cuts first, and moves only what neither the code, the test name nor the spec says. An error is in a file that generates a documentation page, a header or a catalog page, so the finding is a defect in what gets published and it fails the gate. A warning is in an implementation file, which publishes nothing: its comments are a note to the next reader, worth fixing without being worth stopping a commit for. Both are counted here, because a warning nobody sees is a warning nobody fixes. @@ -16,10 +16,10 @@ The split is temporary. It stages the sweep rather than ranking the two kinds of | Rule | Errors | Warnings | |---|---:|---:| -| over-wide comment lines | 0 | 1769 | -| multi-line comment blocks | 0 | 455 | -| over-long sentences | 0 | 91 | -| over-wide doc lines | 0 | 86 | +| over-wide comment lines | 0 | 1762 | +| multi-line comment blocks | 0 | 453 | +| over-long sentences | 0 | 90 | +| over-wide doc lines | 0 | 85 | | member deep dives | 0 | 76 | ## By documentation area @@ -28,8 +28,8 @@ The unit a sweep runs in: one summary page and the headers it owns, as the [hier | Summary page | Errors | Warnings | |---|---:|---:| -| `(tests, no card)` | 0 | 1878 | -| `platform/index.md` | 0 | 346 | +| `(tests, no card)` | 0 | 1872 | +| `platform/index.md` | 0 | 341 | | `core/system.md` | 0 | 253 | ## Where the work is @@ -38,7 +38,7 @@ Per area, since that is the unit a sweep runs in: the files ranked, then the rul ### (tests, no card) -**0 error(s)** and **1878 warning(s)** across 156 file(s). +**0 error(s)** and **1872 warning(s)** across 155 file(s). | Findings | File | | |---:|---|---| @@ -52,13 +52,13 @@ Per area, since that is the unit a sweep runs in: the files ranked, then the rul | 44 | `test/unit/core/unit_moonlive_codegen_xtensa.cpp` | warning | | 42 | `test/unit/light/unit_ParallelSlots.cpp` | warning | | 38 | `test/unit/light/unit_Drivers_rendersplit.cpp` | warning | -| 1-36 each | *146 more warning files, 1215 findings* | | +| 1-36 each | *145 more warning files, 1209 findings* | | -By rule, warnings: 1422 over-wide comment lines, 208 multi-line comment blocks, 90 over-long sentences, 85 over-wide doc lines, 73 member deep dives. +By rule, warnings: 1418 over-wide comment lines, 208 multi-line comment blocks, 89 over-long sentences, 84 over-wide doc lines, 73 member deep dives. ### platform/index.md -**0 error(s)** and **346 warning(s)** across 21 file(s). +**0 error(s)** and **341 warning(s)** across 19 file(s). | Findings | File | | |---:|---|---| @@ -72,9 +72,9 @@ By rule, warnings: 1422 over-wide comment lines, 208 multi-line comment blocks, | 19 | `src/platform/esp32/platform_esp32_parlio.cpp` | warning | | 10 | `src/platform/esp32/platform_esp32_i2s.cpp` | warning | | 10 | `src/platform/esp32/platform_esp32_improv.cpp` | warning | -| 1-9 each | *11 more warning files, 44 findings* | | +| 1-9 each | *9 more warning files, 39 findings* | | -By rule, warnings: 227 over-wide comment lines, 119 multi-line comment blocks. +By rule, warnings: 224 over-wide comment lines, 117 multi-line comment blocks. ### core/system.md diff --git a/docs/reference/metrics/prose.md b/docs/reference/metrics/prose.md index f24d756bd..01d38b3fe 100644 --- a/docs/reference/metrics/prose.md +++ b/docs/reference/metrics/prose.md @@ -9,14 +9,15 @@ This number is the debt behind that gate, and it RATCHETS: a rise fails the chec docgen.md does. A comment is prose wherever it lives, so a `//` in the web interface and a `#` in a script are counted beside a `///` in a header. -**2950 finding(s)** across 331 file(s). +**3186 finding(s)** across 378 file(s). | rule | findings | |---|---| -| `MoonLight.EmDash` | 1642 | -| `MoonLight.Weasel` | 742 | -| `MoonLight.SentenceLength` | 476 | -| `MoonLight.Spelling` | 44 | +| `MoonLight.EmDash` | 1636 | +| `MoonLight.Weasel` | 739 | +| `MoonLight.SentenceLength` | 474 | +| `MoonLight.Temporal` | 248 | +| `MoonLight.Spelling` | 43 | | `MoonLight.SelfReference` | 33 | | `MoonLight.NegatedHeading` | 13 | @@ -24,23 +25,23 @@ docgen.md does. A comment is prose wherever it lives, so a `//` in the web inter | file | findings | |---|---| -| `moondeck/MoonDeck.md` | 251 | -| `moondeck/moondeck.py` | 165 | -| `docs/how-to/home-automation.md` | 115 | -| `mooninstaller/install-orchestrator.js` | 94 | +| `moondeck/MoonDeck.md` | 256 | +| `moondeck/moondeck.py` | 169 | +| `docs/how-to/home-automation.md` | 117 | +| `mooninstaller/install-orchestrator.js` | 96 | | `docs/tutorials/build-your-own-moonmodules.md` | 92 | -| `moondeck/moondeck_ui/app.js` | 85 | -| `docs/how-to/building.md` | 82 | -| `mooninstaller/README.md` | 75 | -| `moondeck/check/check_clang_query.py` | 72 | -| `mooninstaller/install.js` | 67 | -| `src/ui/app.js` | 56 | -| `src/ui/install-picker.js` | 50 | -| `moondeck/check/check_nonblocking.py` | 49 | -| `moondeck/docs/gen_api.py` | 49 | -| `src/ui/preview3d.js` | 47 | -| `moondeck/build/build_esp32.py` | 46 | -| `docs/explanation/architecture/moonlight.md` | 42 | +| `moondeck/moondeck_ui/app.js` | 92 | +| `docs/how-to/building.md` | 88 | +| `mooninstaller/README.md` | 74 | +| `moondeck/check/check_clang_query.py` | 73 | +| `src/ui/app.js` | 72 | +| `mooninstaller/install.js` | 71 | +| `src/ui/install-picker.js` | 55 | +| `moondeck/docs/gen_api.py` | 53 | +| `src/ui/preview3d.js` | 49 | +| `moondeck/build/build_esp32.py` | 48 | +| `moondeck/check/check_nonblocking.py` | 47 | +| `docs/explanation/architecture/moonlight.md` | 45 | | `moondeck/docs/mkdocs_hooks.py` | 40 | -| `docs/reference/hardware/gpio-usage.md` | 36 | -| `src/platform/esp32/platform_esp32_moon_i80.cpp` | 36 | +| `docs/reference/testing.md` | 38 | +| `src/platform/esp32/platform_esp32_moon_i80.cpp` | 37 | diff --git a/docs/reference/metrics/repo-health.json b/docs/reference/metrics/repo-health.json index 43a3017ca..569909729 100644 --- a/docs/reference/metrics/repo-health.json +++ b/docs/reference/metrics/repo-health.json @@ -1,12 +1,12 @@ { - "commit": "aace8390", + "commit": "48657d07", "flash": { - "esp32s3-n16r8": 2239952, - "esp32": 2189136, - "esp32p4rev1-eth": 2150912, + "esp32s3-n16r8": 2243568, + "esp32": 2191840, + "esp32p4rev1-eth": 2173344, "esp32p4rev1-eth-wifi": 2358896, "esp32s3-n8r8": 2087168, - "esp32s31": 2542032, + "esp32s31": 2544880, "esp32-16mb": 2060368, "esp32-eth": 1681040, "esp32-wrover": 1843760, @@ -14,22 +14,22 @@ "esp32p4rev3-eth": 2073888, "esp32s3-zero": 2024192, "esp32-pico": 2107168, - "desktop-macos": 2167592, + "desktop-macos": 2168264, "desktop-windows": 1925120 }, "measured": { - "esp32p4rev1-eth": "2026-10-05", - "esp32s31": "2026-10-05", - "esp32": "2026-10-05", + "esp32p4rev1-eth": "2026-10-06", + "esp32s31": "2026-10-06", + "esp32": "2026-10-06", "esp32-pico": "2026-09-09", - "esp32s3-n16r8": "2026-10-05", + "esp32s3-n16r8": "2026-10-06", "esp32s3-n8r8": "2026-09-08", "esp32s3-zero": "2026-09-08", "esp32-16mb": "2026-09-09", "esp32p4rev1-eth-wifi": "2026-09-30", "esp32-eth": "2026-09-11", "esp32p4rev3-eth": "2026-09-29", - "desktop-macos": "2026-10-05", + "desktop-macos": "2026-10-06", "desktop-windows": "2026-09-30" }, "perf": { @@ -42,8 +42,8 @@ "desktop-macos": { "p50": 5, "p95": 462, - "n": 12, - "last": "2026-10-05" + "n": 14, + "last": "2026-10-06" }, "esp32s3-n16r8": { "p50": 2073, @@ -108,8 +108,8 @@ "desktop-macos": { "p50": 1, "p95": 555, - "n": 13, - "last": "2026-10-05" + "n": 15, + "last": "2026-10-06" }, "esp32s3-n16r8": { "p50": 2505, @@ -483,9 +483,9 @@ "Drivers_output_and_brightness": { "desktop-macos": { "p50": 7, - "p95": 10, + "p95": 9, "n": 32, - "last": "2026-10-05" + "last": "2026-10-06" }, "desktop-windows": { "p50": 4, @@ -509,9 +509,9 @@ "Effects_pipeline_builds_and_renders": { "desktop-macos": { "p50": 6, - "p95": 16, + "p95": 11, "n": 32, - "last": "2026-10-05" + "last": "2026-10-06" }, "desktop-windows": { "p50": 4, @@ -523,9 +523,9 @@ "Effects_swap_while_running": { "desktop-macos": { "p50": 1, - "p95": 2, + "p95": 1, "n": 32, - "last": "2026-10-05" + "last": "2026-10-06" }, "desktop-windows": { "p50": 4, @@ -549,9 +549,9 @@ "Effects_teardown_under_a_running_pipeline": { "desktop-macos": { "p50": 7, - "p95": 28, + "p95": 18, "n": 32, - "last": "2026-10-05" + "last": "2026-10-06" }, "esp32s3-n16r8": { "p50": 9080, @@ -575,17 +575,17 @@ "Layers_stack_and_blend_live": { "desktop-macos": { "p50": 267, - "p95": 697, + "p95": 454, "n": 32, - "last": "2026-10-05" + "last": "2026-10-06" } }, "Layouts_resize_reallocates_live": { "desktop-macos": { "p50": 10, - "p95": 22, + "p95": 19, "n": 32, - "last": "2026-10-05" + "last": "2026-10-06" }, "desktop-windows": { "p50": 93, @@ -609,9 +609,9 @@ "Modifiers_reshape_the_mapping": { "desktop-macos": { "p50": 7, - "p95": 20, + "p95": 19, "n": 32, - "last": "2026-10-05" + "last": "2026-10-06" }, "esp32s3-n16r8": { "p50": 10350, @@ -635,27 +635,27 @@ "Palettes_a_script_drives_the_effects": { "desktop-macos": { "p50": 7, - "p95": 204, - "n": 23, - "last": "2026-10-05" + "p95": 122, + "n": 25, + "last": "2026-10-06" } } }, "desktop-macos": { - "tick_us": 1, - "fps": 1000000, + "tick_us": 2, + "fps": 500000, "scenario_p50": { "Effects_pipeline_builds_and_renders": { "p50": 6, - "p95": 16, + "p95": 11, "n": 32, - "last": "2026-10-05" + "last": "2026-10-06" }, "Layouts_resize_reallocates_live": { "p50": 10, - "p95": 22, + "p95": 19, "n": 32, - "last": "2026-10-05" + "last": "2026-10-06" } } }, @@ -679,16 +679,16 @@ } }, "loc": { - "core": 25586, - "light": 31812, - "platform": 17158, - "ui": 12202, - "test": 62376, - "moondeck": 30382 + "core": 25734, + "light": 31821, + "platform": 17199, + "ui": 12220, + "test": 62638, + "moondeck": 30424 }, "comments": { "core": { - "lines": 6157, + "lines": 6191, "ratio": 0.266 }, "light": { @@ -696,36 +696,36 @@ "ratio": 0.269 }, "platform": { - "lines": 3624, - "ratio": 0.235 + "lines": 3619, + "ratio": 0.234 }, "ui": { - "lines": 3463, + "lines": 3468, "ratio": 0.3 }, "test": { - "lines": 6916, + "lines": 6933, "ratio": 0.128 }, "moondeck": { - "lines": 4967, + "lines": 4959, "ratio": 0.186 } }, "tests": { - "cases": 2303, + "cases": 2313, "scenarios": 30 }, "docs": { - "md_files": 141, - "md_lines": 30305, - "plans_files": 41, - "backlog_lines": 3265, + "md_files": 142, + "md_lines": 30376, + "plans_files": 42, + "backlog_lines": 3272, "lessons_lines": 532, "claude_md_lines": 283 }, "complexity": { - "functions": 4105, + "functions": 4139, "over_threshold": 335, "worst_ccn": 128 } diff --git a/docs/reference/metrics/repo-health.md b/docs/reference/metrics/repo-health.md index 5db93ab9c..d5f35199a 100644 --- a/docs/reference/metrics/repo-health.md +++ b/docs/reference/metrics/repo-health.md @@ -1,6 +1,6 @@ # Repo health -Measured at `aace8390`. Generated by [`moondeck/check/repo_health.py`](../../../moondeck/check/repo_health.py) on every KPI-gate run. **Do not edit by hand.** +Measured at `48657d07`. Generated by [`moondeck/check/repo_health.py`](../../../moondeck/check/repo_health.py) on every KPI-gate run. **Do not edit by hand.** Current state only; the trend is this file's git history (`git log -p docs/reference/metrics/repo-health.md`). Nothing here fails a build: the numbers make growth visible, the judgment stays human. @@ -8,20 +8,20 @@ Current state only; the trend is this file's git history (`git log -p docs/refer | Metric | v6.0.0 | Now | Change | |---|---:|---:|---:| -| flash desktop-macos | 1,963 KB | 2,117 KB | +153.8 KB (+7.8%) | -| flash esp32 | 2,061 KB | 2,138 KB | +76.6 KB (+3.7%) | -| flash esp32p4rev1-eth | 2,022 KB | 2,100 KB | +78.8 KB (+3.9%) | -| flash esp32s3-n16r8 | 2,111 KB | 2,187 KB | +76.0 KB (+3.6%) | -| flash esp32s31 | 2,395 KB | 2,482 KB | +87.0 KB (+3.6%) | +| flash desktop-macos | 1,963 KB | 2,117 KB | +154.4 KB (+7.9%) | +| flash esp32 | 2,061 KB | 2,140 KB | +79.3 KB (+3.8%) | +| flash esp32p4rev1-eth | 2,022 KB | 2,122 KB | +100.7 KB (+5.0%) | +| flash esp32s3-n16r8 | 2,111 KB | 2,191 KB | +79.5 KB (+3.8%) | +| flash esp32s31 | 2,395 KB | 2,485 KB | +89.8 KB (+3.8%) | | tick Effects_pipeline_builds_and_renders (desktop-macos) | 7 Β΅s | 6 Β΅s | -1 (-14.3%) | | tick Layouts_resize_reallocates_live (desktop-macos) | 12 Β΅s | 10 Β΅s | -2 (-16.7%) | -| lines core | 22,299 | 25,586 | +3,287 (+14.7%) | -| lines light | 31,054 | 31,812 | +758 (+2.4%) | -| lines platform | 16,798 | 17,158 | +360 (+2.1%) | -| lines ui | 11,394 | 12,202 | +808 (+7.1%) | -| lines test | 57,759 | 62,376 | +4,617 (+8.0%) | -| lines moondeck | 29,325 | 30,382 | +1,057 (+3.6%) | -| unit tests | 2,120 | 2,303 | +183 (+8.6%) | +| lines core | 22,299 | 25,734 | +3,435 (+15.4%) | +| lines light | 31,054 | 31,821 | +767 (+2.5%) | +| lines platform | 16,798 | 17,199 | +401 (+2.4%) | +| lines ui | 11,394 | 12,220 | +826 (+7.2%) | +| lines test | 57,759 | 62,638 | +4,879 (+8.4%) | +| lines moondeck | 29,325 | 30,424 | +1,099 (+3.7%) | +| unit tests | 2,120 | 2,313 | +193 (+9.1%) | | scenarios | 21 | 30 | +9 (+42.9%) | Against the release `v6.0.0`, the fixed point a user compares with; the tables below compare against the last commit. A firmware not rebuilt since `v6.0.0` is left out, and a measure the release did not record yet, such as the code report, joins from the next one. @@ -30,20 +30,20 @@ Against the release `v6.0.0`, the fixed point a user compares with; the tables b | Target | Flash | Capacity | Used | Built | |---|---:|---:|---:|:--:| -| desktop-macos | 2,117 KB | - | - | yes | -| desktop-windows | 1,880 KB | - | - | carried 5d | -| esp32 | 2,138 KB (+224 B) ⚠ | 2,496 KB | 86% | yes | -| esp32-16mb | 2,012 KB | 4,096 KB | 49% | **STALE 26d** | -| esp32-eth | 1,642 KB | 2,496 KB | 66% | **STALE 24d** | -| esp32-pico | 2,058 KB | 3,072 KB | 67% | **STALE 26d** | +| desktop-macos | 2,117 KB (+672 B) ⚠ | - | - | yes | +| desktop-windows | 1,880 KB | - | - | carried 6d | +| esp32 | 2,140 KB (+3 KB) ⚠ | 2,496 KB | 86% | yes | +| esp32-16mb | 2,012 KB | 4,096 KB | 49% | **STALE 27d** | +| esp32-eth | 1,642 KB | 2,496 KB | 66% | **STALE 25d** | +| esp32-pico | 2,058 KB | 3,072 KB | 67% | **STALE 27d** | | esp32-wrover | 1,801 KB | 2,496 KB | 72% | carried (age?) | -| esp32p4rev1-eth | 2,100 KB (+144 B) ⚠ | 4,096 KB | 51% | yes | -| esp32p4rev1-eth-wifi | 2,304 KB | 4,096 KB | 56% | carried 5d | -| esp32p4rev3-eth | 2,025 KB | 4,096 KB | 49% | carried 6d | -| esp32s3-n16r8 | 2,187 KB (+80 B) ⚠ | 4,096 KB | 53% | yes | -| esp32s3-n8r8 | 2,038 KB | 3,072 KB | 66% | **STALE 27d** | -| esp32s3-zero | 1,977 KB | 2,496 KB | 79% | **STALE 27d** | -| esp32s31 | 2,482 KB (+176 B) ⚠ | 4,096 KB | 61% | yes | +| esp32p4rev1-eth | 2,122 KB (+22 KB) ⚠ | 4,096 KB | 52% | yes | +| esp32p4rev1-eth-wifi | 2,304 KB | 4,096 KB | 56% | carried 6d | +| esp32p4rev3-eth | 2,025 KB | 4,096 KB | 49% | **STALE 7d** | +| esp32s3-n16r8 | 2,191 KB (+4 KB) ⚠ | 4,096 KB | 53% | yes | +| esp32s3-n8r8 | 2,038 KB | 3,072 KB | 66% | **STALE 28d** | +| esp32s3-zero | 1,977 KB | 2,496 KB | 79% | **STALE 28d** | +| esp32s31 | 2,485 KB (+3 KB) ⚠ | 4,096 KB | 61% | yes | | qemu | 1,351 KB | - | - | carried (age?) | `Built: yes` was measured this run. `carried (age?)` was not rebuilt either and predates this record, so its age is unknown: it dates itself on the next build. `carried Nd` was NOT rebuilt and its number is N days old, so an absent delta says nothing about the change. **STALE** marks a carry older than 7 days: the number has gone unchecked long enough that growth will surface later as one jump, blamed on whichever commit happens to rebuild that target. `Used` is against the app slot in the firmware's own partition table. @@ -52,17 +52,15 @@ Against the release `v6.0.0`, the fixed point a user compares with; the tables b | Target | Net | New | Removed | Grown | Shrunk | Other | Since | |---|---:|---:|---:|---:|---:|---:|---| -| esp32 | +0.2 KB | +0.0 KB | +0.0 KB | +0.3 KB | -0.1 KB | +0.0 KB | `aace8390` | -| esp32p4rev1-eth | +0.1 KB | +0.0 KB | +0.0 KB | +0.2 KB | -0.1 KB | -0.0 KB | `aace8390` | -| esp32s3-n16r8 | +0.1 KB | +0.0 KB | +0.0 KB | +0.2 KB | -0.1 KB | -0.0 KB | `aace8390` | -| esp32s31 | +0.2 KB | +0.0 KB | +0.0 KB | +0.2 KB | -0.1 KB | +0.0 KB | `aace8390` | +| esp32 | +2.6 KB | +2.9 KB | -1.5 KB | +2.6 KB | -1.6 KB | +0.3 KB | `48657d07` | +| esp32p4rev1-eth | +21.9 KB | +20.3 KB | -1.9 KB | +3.6 KB | -3.3 KB | +3.2 KB | `48657d07` | +| esp32s3-n16r8 | +3.5 KB | +2.9 KB | -1.6 KB | +3.8 KB | -1.8 KB | +0.3 KB | `48657d07` | Largest moves: -- **esp32**: `mm::ui::appJs` +182 B, `mm::registerModuleTypes()` +68 B, `_ZN2mm13ModuleFactory12registerTypeINS_13NetworkModuleEEEbPK` -38 B, `mm::NetworkModule::tick1s()` -31 B, `mm::Drivers::applyRelay()` +20 B -- **esp32p4rev1-eth**: `mm::ui::appJs` +182 B, `mm::registerModuleTypes()` -74 B, `mm::Drivers::applyRelay()` +36 B, `mm::NetworkModule::tick1s()` -16 B, `mm::ui::installPickerJs` +15 B -- **esp32s3-n16r8**: `mm::ui::appJs` +182 B, `_ZN2mm13ModuleFactory12registerTypeINS_16RubiksCubeEffectEEE` -108 B, `mm::NetworkModule::tick1s()` -27 B, `mm::Drivers::applyRelay()` +20 B, `mm::ui::installPickerJs` +15 B -- **esp32s31**: `mm::ui::appJs` +182 B, `mm::registerModuleTypes()` -68 B, `mm::Drivers::applyRelay()` +36 B, `mm::ui::installPickerJs` +15 B, `bool mm::ModuleFactory::registerType(char const` +4 B +- **esp32**: `mm::I2cBusModule::scan()` +806 B, `mm::applyControlValue(mm::ControlDescriptor const&, char con` -612 B, `mm::ui::appJs` +395 B, `mm::I2cBusModule* createOrDie(char const*)` +367 B, `mm::I2cScanModule* createOrDie(char const` -367 B +- **esp32p4rev1-eth**: `coeff_div` +1,520 B, `log` +1,224 B, `es8311_enable` +988 B, `exp` +984 B, `mm::I2cBusModule::scan()` +880 B +- **esp32s3-n16r8**: `mm::I2cBusModule::scan()` +806 B, `mm::applyControlValue(mm::ControlDescriptor const&, char con` -612 B, `unsigned char mm::warpImpl<3>(unsigned long, unsigned long, ` +488 B, `mm::ui::appJs` +395 B, `mm::I2cBusModule* createOrDie(char const*)` +367 B What this commit's flash went to, symbol by symbol against the measurement at `Since`. New is new functionality, removed and shrunk are what a cleanup saved, and other is what no symbol owns, such as merged strings and alignment. A rename reads as removed plus new, which the largest moves expose. @@ -70,7 +68,7 @@ What this commit's flash went to, symbol by symbol against the measurement at `S | Target | Tick | FPS | |---|---:|---:| -| desktop-macos | 1 Β΅s | 1,000,000 | +| desktop-macos | 2 Β΅s (+1 Β΅s) ⚠ | 500,000 (βˆ’500,000) ⚠ | | desktop-windows | 4 Β΅s | 250,000 | | esp32 | 8,354 Β΅s | 119 | @@ -87,7 +85,7 @@ What this commit's flash went to, symbol by symbol against the measurement at `S | Firmware_reports_what_is_running | 29 | 1,827 ? | - | 8,659 ? | - | - | - | - | 215 ? | - | | Fluid_solver | 218 | 6,950,405 ? | 45,974 ? | 102,058 ? | 71,150 ? | 66,874 ? | - | - | 3,371 ? | 4,661,597 ? | | GridLayout_resize | 120 | 219 ? | 1,352 ? | 18,414 ? | 7,218 ? | 8,919 ? | 95,771 ? | 82,231 ? | 2,005 ? | 6,735,719 ? | -| Layers_stack_and_blend_live | 267 (βˆ’1) βœ“ | - | - | - | - | - | - | - | - | - | +| Layers_stack_and_blend_live | 267 | - | - | - | - | - | - | - | - | - | | Layouts_resize_reallocates_live | 10 | 93 ? | - | 32,399 ? | - | - | - | - | 579 ? | - | | Modifiers_reshape_the_mapping | 7 | 29 ? | - | 10,350 ? | - | - | - | - | 273 ? | - | | Network_hardware_reconfigures_live | 21 ? | 2,834 ? | - | 8,692 ? | - | - | - | - | 237 ? | - | @@ -106,8 +104,8 @@ Microseconds. `?` marks a cell backed by fewer than 4 samples, which is a first | Scenario | p50 | p95 | n | |---|---:|---:|---:| -| Effects_pipeline_builds_and_renders | 6 Β΅s | 16 Β΅s | 32 | -| Layouts_resize_reallocates_live | 10 Β΅s | 22 Β΅s | 32 | +| Effects_pipeline_builds_and_renders | 6 Β΅s | 11 Β΅s | 32 | +| Layouts_resize_reallocates_live | 10 Β΅s | 19 Β΅s | 32 | These build a bare pipeline with no optional modules, so a change here is a change in the pipeline itself rather than in what was measured. A new module belongs in an advanced scenario, which keeps its own numbers. @@ -124,36 +122,36 @@ These build a bare pipeline with no optional modules, so a change here is a chan | Area | Lines | Comments | Comment share | |---|---:|---:|---:| -| core | 25,586 (βˆ’4) βœ“ | 6,157 | 26.6 % | -| light | 31,812 (+13) ⚠ | 7,640 | 26.9 % | -| platform | 17,158 (+9) ⚠ | 3,624 | 23.5 % | -| ui | 12,202 (+4) ⚠ | 3,463 | 30.0 % | -| test | 62,376 (+6) ⚠ | 6,916 | 12.8 % | -| moondeck | 30,382 (+2) ⚠ | 4,967 | 18.6 % | +| core | 25,734 (+148) ⚠ | 6,191 | 26.6 % | +| light | 31,821 (+9) ⚠ | 7,640 | 26.9 % | +| platform | 17,199 (+41) ⚠ | 3,619 | 23.4 % (βˆ’0.1 %) βœ“ | +| ui | 12,220 (+18) ⚠ | 3,468 | 30.0 % | +| test | 62,638 (+262) ⚠ | 6,933 | 12.8 % | +| moondeck | 30,424 (+42) ⚠ | 4,959 | 18.6 % | ## Tests | Kind | Count | |---|---:| -| unit cases | 2,303 | +| unit cases | 2,313 (+10) βœ“ | | scenarios | 30 | ## Complexity | Metric | Value | |---|---:| -| functions | 4,105 (+1) βœ“ | -| over threshold | 335 (βˆ’1) βœ“ | +| functions | 4,139 (+34) βœ“ | +| over threshold | 335 | | worst CCN | 128 | ## Documentation | Metric | Value | |---|---:| -| markdown files | 141 | -| markdown lines | 30,305 (+13) ⚠ | -| plan files | 41 | -| backlog lines | 3,265 | +| markdown files | 142 (+1) ⚠ | +| markdown lines | 30,376 (+71) ⚠ | +| plan files | 42 (+1) ⚠ | +| backlog lines | 3,272 (+7) ⚠ | | lessons lines | 532 | | CLAUDE.md lines | 283 | diff --git a/docs/work/future/backlog-core.md b/docs/work/future/backlog-core.md index 0495d71cc..7f798e417 100644 --- a/docs/work/future/backlog-core.md +++ b/docs/work/future/backlog-core.md @@ -590,6 +590,13 @@ Left out of the plan that makes Ethernet, WiFi and the access point Network subm ## Architecture +### A second I2C bus, once a board wires one (2026-10-06) + +`I2cBusModule` drives one bus on controller 0, which covers every catalog board: one bus carries about 112 addresses, so a codec, a sensor and a display share it. +A board needs a second bus for two chips on the same fixed address, devices at different voltages or pull-ups, or a long cable run kept off the onboard devices. +The shape then: the module becomes addable with a controller-number control, the platform keeps one bus per controller, and a device on a bus (`AudioService`'s codec first) gains a bus selector defaulting to 0, so the catalog's boards keep working unchanged. +The trigger is a real board; a touch controller or camera on its own pins next to the codec is the likely first, the Guition M3's touch wiring being unchecked. + ### An offline test run takes 16 minutes instead of one (2026-10-03) With the host's network down, `test_desktop` ran 16 minutes and the scenarios stalled past the gate's hour; online, no test takes over 4 seconds, so something waits out network timeouts. diff --git a/docs/work/future/system-modules.md b/docs/work/future/system-modules.md index c6309b8c6..9200d03dd 100644 --- a/docs/work/future/system-modules.md +++ b/docs/work/future/system-modules.md @@ -20,7 +20,7 @@ The load-bearing lesson from those tools: **they sample existing OS accounting c Every member is the **same recognizable shape** β€” the [TasksModule](../../moonmodules/core/system.md#tasks) template, reusing primitives that already exist: -- a **read-only** module presenting a `ControlType::List` via the `ListSource` adapter (DevicesModule/I2cScan shape); +- a **read-only** module presenting a `ControlType::List` via the `ListSource` adapter (DevicesModule/I2cBus shape); - refreshed on `loop1s()` (a periodic *sample*, never the hot path); - reading MoonLight's **existing self-report** (`loopTimeUs`/`classSize`/`dynamicBytes`) + **existing platform getters** (`freeHeap`/`freeInternalHeap`/`maxAllocBlock`/`totalHeap`, and per-module seams behind the boundary like `platform::taskSnapshot`); - **zero hot-path cost**, cross-platform (desktop shows what it can, stubs the rest). @@ -38,7 +38,7 @@ The System-vs-Services distinction forces a conceptual line that was already mud The clean model, matching your framing that *"System is really to view/manage the hardware a device has"*: -- **System** = the device's **fixed hardware + its inspection**: identity (deviceName, chip, mac), live vitals (uptime, fps, tick), reboot β€” **and the System Modules hang here: Tasks, Memory, Pins, I2cScan.** Always present, not user-added. These are the device's **inspection / bring-up toolkit** β€” Tasks (what runs), Memory (what's allocated), Pins (what's assigned), I2cScan (what's on the IΒ²C bus). All fixed, all "inspect *this* device." +- **System** = the device's **fixed hardware + its inspection**: identity (deviceName, chip, mac), live vitals (uptime, fps, tick), reboot β€” **and the System Modules hang here: Tasks, Memory, Pins, I2cBus.** Always present, not user-added. These are the device's **inspection / bring-up toolkit** β€” Tasks (what runs), Memory (what's allocated), Pins (what's assigned), I2cBus (what's on the IΒ²C bus). All fixed, all "inspect *this* device." - **Services** (new top-level container) = the **user-added capability modules**: Audio, IR β€” this-device bridges to the outside world. Optional, per-board. **DevicesModule is deliberately NOT in either bucket** β€” it is **fleet-scope** (discovers/lists *other* devices, drives Hue, the seed of future multi-device features: groups, sync, orchestration), so it's neither a this-device System Module nor a this-device Service Module. It stays a wired-by-code child of Network for now; its eventual home is a **later decision** β€” a standalone top-level module, or a "Fleet"/"Devices" top-level *container* once a second fleet module exists to justify one (don't build a container for one child). Flagged here so the distinction isn't lost. @@ -59,7 +59,7 @@ So: **Services** container, **`ModuleRole::Service`** role, and the split plan c The split maps onto one rule, matching how OS system managers behave (Task Manager / Activity Monitor are *always available*, never added or deleted): -- **Everything under `System` is FIXED** β€” always present, **no add/delete**, wired-by-code. That's System's own vitals **and** the System Modules (Tasks, I2cScan; Memory, Pins later) **and** the always-there infrastructure (Network, Firmware, Improv). You don't delete a System Module any more than you delete Task Manager. +- **Everything under `System` is FIXED** β€” always present, **no add/delete**, wired-by-code. That's System's own vitals **and** the System Modules (Tasks, I2cBus; Memory, Pins later) **and** the always-there infrastructure (Network, Firmware, Improv). You don't delete a System Module any more than you delete Task Manager. - **Everything under `Services` is USER-MANAGED** β€” add/delete/replace, `ModuleRole::Service`. Audio, IR. (MQTT stays code-wired under Network as always-there infra; Devices is fleet-scope, see its note.) **`Services` is the existing container shape, applied to the core domain** β€” the strongest justification (*Common patterns first*). "A top-level container holding the user-added children of one role" is a pattern the codebase already runs on; `Services` reuses it rather than inventing a second arrangement. So the split adds no new concept: it recognises that Audio and IR are user-added children of one role, and that is what a container of that shape is for. diff --git a/docs/work/present/Plan-20261005 - The audio codec as runtime configuration.md b/docs/work/present/Plan-20261005 - The audio codec as runtime configuration.md new file mode 100644 index 000000000..d9edad6ca --- /dev/null +++ b/docs/work/present/Plan-20261005 - The audio codec as runtime configuration.md @@ -0,0 +1,37 @@ +# The audio codec as runtime configuration + +A board with an ES8311 codec in front of its microphone is a `deviceModels.json` entry on an existing firmware, not a firmware of its own. + +## Why + +Two community PRs add a P4 board each with an onboard ES8311: #128 (Waveshare ESP32-P4-ETH) and #55 (Guition JC-ESP32P4-M3-DEV). +Both added a firmware variant, because the driver (`platform_esp32_es8311.cpp`) is built only for the S31 and the codec type and pins are compile-time constants in `platform_config.h`. +The two boards share their codec wiring, so a variant per board would multiply firmwares for what is one setting. + +## Steps + +1. **Take the two codec fixes from #128**, credited to TouchMyLight: pass the I2C address pre-shifted where `esp_codec_dev` expects the 8-bit form, and set the `data_if` that `esp_codec_dev_new` requires. Both affect the S31 as well. +2. **Build `esp_codec_dev` for the P4** as well as the S31, in `esp32/main/idf_component.yml`; measure the flash it adds on `esp32p4rev1-eth` with the flash split. +3. **Make the codec runtime configuration**: AudioService gains `codec` (none or ES8311) and `codecAddr`, the address shown only when a codec is chosen. The bus is the board's: the I2C scan module becomes I2cBusModule, which opens it once on its pins for every device on it. None is the default, so a P4 board without one never probes the bus. `audioCodecInit` takes them from AudioService; `audioCodecType` and `audioCodecPins` leave `platform_config.h`. +4. **The S31 CoreBoard entry** names its codec in `deviceModels.json` (SDA 51, SCL 50, MCLK 52, address 0x18), since the firmware leaves the codec to the device model. +5. **Two catalog entries** on `esp32p4rev1-eth`, as proposed on the PRs: Waveshare ESP32-P4-ETH and Guition JC-ESP32P4-M3-DEV, both SDA 7, SCL 8, 0x18, MCLK 13, BCLK 12, WS 10 and Ethernet `P4-NANO`, with mic data on 11 (Waveshare, its wiki and a bench test) and 48 (Guition, its schematic and #55's hardware test). + +## Tests + +- AudioService, on the host: no codec is asked for until one is named with both bus pins, the request carries the bus and the 7-bit address, and every codec setting re-initializes the microphone. +- The controls' visibility and the address shift live behind `hasI2sMic` and in the ESP32 driver, which a host test does not reach; the S31 bench run proves both. +- `check_devices` validates the three entries. + +## Verification + +- S31 on the bench: the mic reports level and onsets through the codec, which also proves the two fixes. +- The MM testbench P4 (direct I2S mic, no codec) keeps its audio unchanged with `codec` at none. +- The two P4-ETH boards are verified by their PR authors on their hardware. + +## What comes out + +The compile-time codec constants, and the need for #128's and #55's firmware variants; both PRs close or rebase onto this. + +## Docs + +The AudioService card describes the codec controls; MIGRATING notes that an S31 configured before this needs its device model applied again, or the codec set by hand. diff --git a/esp32/main/CMakeLists.txt b/esp32/main/CMakeLists.txt index 4ae4b8f74..16f975dfe 100644 --- a/esp32/main/CMakeLists.txt +++ b/esp32/main/CMakeLists.txt @@ -7,6 +7,7 @@ idf_component_register( "../../src/module_types.cpp" "../../src/core/module/Control.cpp" "../../src/core/util/JsonUtil.cpp" + "../../src/core/util/parse.cpp" "../../src/core/util/ScratchBuffer.cpp" "../../src/core/system/HttpServerModule.cpp" "../../src/core/system/FilesystemModule.cpp" diff --git a/esp32/main/idf_component.yml b/esp32/main/idf_component.yml index 334c744d9..37d51863b 100644 --- a/esp32/main/idf_component.yml +++ b/esp32/main/idf_component.yml @@ -44,18 +44,15 @@ dependencies: - if: "$CONFIG{MM_HLS} == True" espressif/esp-dsp: version: "^1.5.0" - # esp_codec_dev β€” Espressif's audio-codec driver library; we use its ES8311 part - # (es8311_codec_new) to configure the ESP32-S31 CoreBoard's onboard mic, which is - # an analog mic behind an ES8311 I2S codec (I2C addr 0x18) rather than a direct - # I2S MEMS mic. Referenced only from platform_esp32_es8311.cpp, behind the codec - # gate. The `rules` gate scopes it to the S31 β€” the only board with the codec β€” - # so other targets don't resolve a component they never compile (same pattern as - # ip101/w5500 above). A managed component outside mainline v6.0, like the P4 - # esp_hosted exception (docs/how-to/building.md Β§ ESP-IDF version). + # esp_codec_dev: Espressif's audio-codec library, whose ES8311 part configures an analog mic + # behind an ES8311 I2S codec. Which board has one, and on which pins, is AudioService's runtime + # configuration from the device model, so the component is built for every chip a codec board + # uses (the S31 CoreBoard, and P4 boards such as the Waveshare ESP32-P4-ETH) and nowhere else. + # Referenced only from platform_esp32_es8311.cpp, behind the codec gate. espressif/esp_codec_dev: version: "^1.3.0" rules: - - if: "target == esp32s31" + - if: "target in [esp32s31, esp32p4]" # esp_wifi_remote + esp_hosted β€” WiFi for the ESP32-P4 via the on-board ESP32-C6 # co-processor over SDIO. The P4 has no native radio; these present the C6's # radio through the standard esp_wifi_* API (API-compatible, so the WiFi seam in diff --git a/moondeck/MoonDeck.md b/moondeck/MoonDeck.md index 84e6df4aa..748398619 100644 --- a/moondeck/MoonDeck.md +++ b/moondeck/MoonDeck.md @@ -580,7 +580,9 @@ findings in one file stay together and the list diffs cleanly between runs. **Two engines, each doing what it is good at.** The compiler finds the blocking calls, because `-Wfunction-effects` is transitive and a matcher would have to rebuild the call graph to match it. clang-query then annotates each site with its guard β€” a purely local AST question β€” joined on -`file:line`. Measured ~1s per TU, against a report whose cost is the clean rebuild. +`file:line`. Measured ~1s per TU. + +**No clean rebuild.** The desktop build runs every compile through [`diag_launcher.py`](build/diag_launcher.py), CMake's `CXX_COMPILER_LAUNCHER` hook, which keeps each file's compiler output beside its object as `.diag`. An incremental build brings the tree up to date and the saved findings of every object in `compile_commands.json` give the whole picture, so a run takes seconds. Only a build from before the launcher is rebuilt clean, once; a build running another launcher, such as ccache, is told to reconfigure instead. **Guarded is not rare.** `if (enabled_)` is conditional and true every frame; only the `Β·rate` hint separates those, and it reads the condition's *spelling*, so it is a lead rather than a @@ -746,7 +748,7 @@ The report, [`docs/reference/metrics/code.md`](../docs/reference/metrics/code.md **Platform code outside src/platform** and **light include in core** are the architecture's two boundaries, counted per line. The first is a vendor header or a platform `#ifdef` outside `src/platform/`, the second a `src/core` file including a `light/` header. -**Blocking call on the render path** is the last rule, one row per site [check_nonblocking](#check_nonblocking) finds, a float conversion at a `formatTo` site included. It needs that script's clean rebuild of the desktop, so a full run takes minutes; `--module` and `--account` skip it and stay fast. A host without Clang 20 skips this rule with a SKIP line and leaves `code.md` as it is, while every other rule still ratchets. +**Blocking call on the render path** is the last rule, one row per site [check_nonblocking](#check_nonblocking) finds, a float conversion at a `formatTo` site included. It reads that script's saved findings after an incremental desktop build, so a full run takes seconds; `--module` and `--account` skip it. A host without Clang 20 skips this rule with a SKIP line and leaves `code.md` as it is, while every other rule still ratchets. The account, `--account`, reads `code.md` as the last full run wrote it, so it follows a full run in the same gate pass. A saving shows in the commit that made it, rather than only in a later total. diff --git a/moondeck/build/diag_launcher.py b/moondeck/build/diag_launcher.py new file mode 100644 index 000000000..584109256 --- /dev/null +++ b/moondeck/build/diag_launcher.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +"""Compile one file and keep what the compiler said about it beside the object, as `.diag`. + +CMake runs this as `CXX_COMPILER_LAUNCHER`, the hook ccache uses, so the build itself is unchanged. +An incremental build only recompiles what changed and prints nothing for the rest, so the whole tree's +findings live in these files instead: `check_nonblocking.py` reads them after an incremental build rather +than rebuilding the tree clean to make every file speak again. +""" + +import subprocess +import sys +from pathlib import Path + + +def object_path(args): + """The object file a compile writes, from its `-o`, or None for a command that writes none.""" + for i, a in enumerate(args): + if a == "-o" and i + 1 < len(args): + return args[i + 1] + if a.startswith("-o") and len(a) > 2: + return a[2:] + return None + + +def main(argv): + proc = subprocess.run(argv, stderr=subprocess.PIPE) + sys.stderr.buffer.write(proc.stderr) # the build log reads as without the launcher + obj = object_path(argv) + # Written even when empty and even on failure, so its presence says this file's findings were captured. + if obj: + Path(obj + ".diag").write_bytes(proc.stderr) + return proc.returncode + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/moondeck/check/check_nonblocking.py b/moondeck/check/check_nonblocking.py index 4dad6aaef..780a9cedc 100644 --- a/moondeck/check/check_nonblocking.py +++ b/moondeck/check/check_nonblocking.py @@ -26,6 +26,7 @@ """ import argparse +import json import re import subprocess import sys @@ -366,54 +367,63 @@ def function_effects_enabled(build_dir) -> bool: return re.search(r"^MM_HAS_WFUNCTION_EFFECTS:.*=(1|ON|TRUE|YES)\s*$", cache, re.IGNORECASE | re.MULTILINE) is not None -def build_output(build_dir, clean=True): - """A full rebuild's warnings, or None if the build failed. +def saved_findings(build_dir): + """Each compiled file's saved compiler output, joined, or None when a file was compiled before the launcher kept them. - `--clean-first` because an incremental build only recompiles what changed, and a cached TU - prints nothing β€” which would read as "no findings". Likewise a FAILED build produces partial - output: returning it would report a small number as if the tree were nearly clean, the same - silent-zero trap the other checks guard against. + The objects come from compile_commands.json, so a source removed from the build leaves no stale findings behind. """ + try: + entries = json.loads((build_dir / "compile_commands.json").read_text(encoding="utf-8")) + except (OSError, ValueError): + return None + parts = [] + for e in entries: + diag = build_dir / (e["output"] + ".diag") # CMake writes `output` relative to the top build dir, even for a subdirectory's targets + if not diag.exists(): + return None + parts.append(diag.read_text(encoding="utf-8", errors="replace")) + return "\n".join(parts) + + +def launcher_configured(build_dir) -> bool: + """Whether the generated build runs diag_launcher.py, read from its build files rather than learned by a clean rebuild.""" + files = [build_dir / "build.ninja", *build_dir.glob("CMakeFiles/*.dir/build.make")] + return any(f.exists() and "diag_launcher" in f.read_text(encoding="utf-8", errors="replace") for f in files) + + +def _build(build_dir, clean) -> bool: + """Build the tree, incrementally or from clean, saying why when it fails.""" cmd = ["cmake", "--build", str(build_dir)] + (["--clean-first"] if clean else []) proc = subprocess.run(cmd, cwd=ROOT, capture_output=True, text=True, check=False) - out = proc.stdout + proc.stderr - if proc.returncode != 0: - errors = [line for line in out.splitlines() if ": error:" in line] - print(f"Build FAILED (exit {proc.returncode}) β€” findings would be incomplete.", - file=sys.stderr) - for error_line in errors[:5]: - print(f" {error_line.replace(str(ROOT) + '/', '')}", file=sys.stderr) - return None + if proc.returncode == 0: + return True + errors = [line for line in (proc.stdout + proc.stderr).splitlines() if ": error:" in line] + print(f"Build FAILED (exit {proc.returncode}): findings would be incomplete.", file=sys.stderr) + for error_line in errors[:5]: + print(f" {error_line.replace(str(ROOT) + '/', '')}", file=sys.stderr) + return False - # FAIL CLOSED. -Wfunction-effects exists only on Clang 20+; CMake silently omits the flag - # on anything else, and this script would then report "0 findings" from a build that never - # ran the check: indistinguishable from a clean tree. A zero is only trustworthy if the - # warning is actually enabled, so say so instead of reporting a comfortable number. - # - # INCREMENTAL asks a narrower question and needs a narrower guard. A run where nothing - # recompiled legitimately produces no diagnostics, so an empty log there is not proof the - # flag is missing. What it IS proof of is that nothing was measured, and a gate that prints - # a tick for an empty read is the silent zero this whole function exists to prevent. So the - # caller is told which of the two happened rather than being handed a clean bill either way. - if not clean and "[-Wfunction-effects]" not in out: - compiled = any(line.startswith("[") and ".cpp" in line for line in out.splitlines()) - print("Nothing was measured: this incremental build produced no -Wfunction-effects\n" - + ("output, and no translation unit recompiled. Every file is cached, so this run\n" - "says nothing about the tree. Re-run without --incremental for the full picture.\n" - if not compiled else - "output even though something recompiled, which means the warning is not enabled\n" - "(it needs Clang 20+; CMake omits it silently otherwise).\n"), - file=sys.stderr) + +def build_output(build_dir): + """The whole tree's compiler warnings, or None if the build failed or the warning is off. + + An incremental build brings the tree up to date, and every file's warnings come from what moondeck/build/diag_launcher.py kept beside its object, so the files that did not recompile still speak. + Only a build from before the launcher, whose objects carry no saved findings, is rebuilt clean once; a build running another launcher (ccache) is told to reconfigure without one. + A FAILED build would hand back partial findings that read as a nearly clean tree, so it reports None instead. + """ + if not _build(build_dir, clean=False): + return None + out = saved_findings(build_dir) + if out is None and launcher_configured(build_dir): + if not _build(build_dir, clean=True): + return None + out = saved_findings(build_dir) + if out is None: + print("The build keeps no per-file findings: reconfigure it so CMake picks up diag_launcher.py.", file=sys.stderr) return None - if clean and "[-Wfunction-effects]" not in out: - # The cache says the warning is on and the whole tree recompiled, so silence is a measured clean tree, not a missing flag. - if function_effects_enabled(build_dir): - return out - print("No -Wfunction-effects diagnostics in the build output.\n" - "That means either the tree really is clean, or the compiler does not support the\n" - "warning (it needs Clang 20+; CMake omits it silently otherwise). Check with:\n" - f" grep -n 'Wfunction-effects' {(ROOT / 'CMakeLists.txt').relative_to(ROOT)}\n" - " cmake --build --clean-first 2>&1 | grep -c function-effects", + # FAIL CLOSED: -Wfunction-effects exists only on Clang 20+, and a build without it reports no findings that read as a clean tree. + if "[-Wfunction-effects]" not in out and not function_effects_enabled(build_dir): + print("No -Wfunction-effects diagnostics, and the build cache says the warning is off (it needs Clang 20+).", file=sys.stderr) return None return out @@ -490,10 +500,6 @@ def float_conversions_on_the_hot_path(): def main(): ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) ap.add_argument("--module", help="Only findings in this module's source files.") - ap.add_argument("--incremental", action="store_true", - help="Skip the clean rebuild β€” only files that changed are recompiled, so " - "this reports on THOSE. For a gate that asks 'did this change add a " - "blocking call', not for the full picture.") args = ap.parse_args() # BEFORE the build, because this is a grep rather than a compile: it answers in milliseconds, @@ -514,7 +520,7 @@ def main(): # NOT APPLICABLE is not the same as FAILED. -Wfunction-effects is Clang 20+ only, and # CMakeLists probes for it (`check_cxx_compiler_flag`) inside the non-MSVC branch, so an MSVC # cache carries no MM_HAS_WFUNCTION_EFFECTS at all. Without the warning this check can never - # produce a finding, and building to discover that wastes a full rebuild and then reports a + # produce a finding, and building to discover that wastes a build and then reports a # failure the toolchain guarantees. Reading the cache says the same thing for free. # # Skipping is safe here in a way it would not be for a normal gate: the desktop build that @@ -536,7 +542,7 @@ def main(): f"float-conversion findings above DID run.") return 1 if floats else 0 - out = build_output(build_dir, clean=not args.incremental) + out = build_output(build_dir) if out is None: return 2 rows = collect(out) diff --git a/moondeck/docs/screenshot_modules.py b/moondeck/docs/screenshot_modules.py index c452726fc..aeeea3f1e 100644 --- a/moondeck/docs/screenshot_modules.py +++ b/moondeck/docs/screenshot_modules.py @@ -182,7 +182,7 @@ def asset_dir_for(type_name: str) -> Path: "ImprovProvisioningModule", "AudioService", "AnalogService", - "I2cScanModule", + "I2cBusModule", "InfraredService", "ButtonService", "GamepadService", @@ -207,7 +207,7 @@ def asset_dir_for(type_name: str) -> Path: "MoonTalkModule": "MoonCloudModule", "DevicesModule": "NetworkModule", "ImprovProvisioningModule": "NetworkModule", - # Audio / infrared are user-added Services (children of the Services container); I2cScan is a + # Audio / infrared are user-added Services (children of the Services container); I2cBus is a # fixed System child (wired-by-code). They're added/present per-board and never exist in # the desktop tree β€” so they're captured against an ESP32, where these entries route the # shot to the right nav root. @@ -218,7 +218,7 @@ def asset_dir_for(type_name: str) -> Path: "GamepadService": "Services", "MidiService": "Services", "MoonLiveService": "Services", - "I2cScanModule": "SystemModule", + "I2cBusModule": "SystemModule", } # FileManagerModule, FirmwareUpdateModule, SystemModule, NetworkModule are top-level # (scheduler.addModule in main.cpp) β€” NOT listed here, so they capture as standalone cards. diff --git a/moondeck/moondeck_config.json b/moondeck/moondeck_config.json index c01ab9871..9e54b28af 100644 --- a/moondeck/moondeck_config.json +++ b/moondeck/moondeck_config.json @@ -136,7 +136,7 @@ "tab": "desktop", "group": "tools", "label": "Code report", - "speed": "slow", + "speed": "medium", "help": "check_code", "script": "check/check_code.py" }, diff --git a/mooninstaller/README.md b/mooninstaller/README.md index 41e3dcd77..30b47537e 100644 --- a/mooninstaller/README.md +++ b/mooninstaller/README.md @@ -50,7 +50,7 @@ POSTing to a plain-`http://` device (mixed-content), so the old HTTP fan-out + t The board picker is a visual card grid driven by each board's `image` and (optional) `url` catalog fields (see the schema below β€” both are optional; a card without an `image` just shows no photo, without a `url` shows no product link) plus -its `supported`/`planned` capability chips. It reuses the installer's flash machinery unchanged β€” the grid drives the +its `supported`/`planned` capability chips. It reuses the installer's flash machinery unchanged: the grid drives the shared [`install-picker.js`](../../src/ui/install-picker.js) through a hidden board `