# Power control, discovery, and lab INI inventory (FiWiControl) **Packages:** `**fiwicontrol.lab`** (USB discovery, INI load, inventory verification), `**fiwicontrol.power**` (`**Power**`, `**AcronamePower**`, `**MonsoonPower**`, CLI entry points). **Code:** `src/fiwicontrol/lab/`, `src/fiwicontrol/power/`. **Related:** `**fiwicontrol.fronthaul`**, `**fiwicontrol.telemetry**`, `**fiwicontrol.radio**`, `**fiwicontrol.concentrator**` (workstation snapshot — `**scripts/system/dump_concentrator.py**`), `**fiwicontrol.fabric**` (bench `**FabricDefinition**` JSON and `**[fabric]**` / `**[fabric.rrh.*]**` merge). See `**README.md**` for the full package map. This document explains the **lab INI** format (one file shared with `**fiwicontrol.fabric`** and `**scripts/system/**` harnesses), **what runs on each machine** for USB discovery, and how to **verify** installs. --- ## Lab INI layout Files are standard Python `**configparser`** INIs. FiWiControl reads `**[site]**`, one `**[machine.]**` section per bench machine (SSH identity plus expected USB hardware), and optional `**[fabric]**` / `**[fabric.rrh.]**` for fabric JSON defaults and per-RRH overrides. Other keys are ignored unless additional tooling reads them. The repo template is `**configs/default.ini**`. Pass `**-c /path/to.ini**` to `**fiwicontrol.power**` and other tools, or set `**FIWI_LAB_INI**` so the default path resolves without repeating `**-c**`. ### `[site]` (optional) | Key | Meaning | | ---------- | ---------------------------------------------------------------- | | `**name**` | Human-readable site label (printed by `**--verify-inventory**`). | ### `[machine.]` — one row per machine `` is the section suffix (e.g. `**fedora42**` in `**[machine.fedora42]**`). The same id is used in `**[fabric] concentrator = …**` and in logs. | Key | Required | Meaning | | --------------------------------------------------------------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `**ipaddr**` | yes | SSH target (`**root@**`, passwordless keys for automation). | | `**sshtype**` | no (default `**ssh**`) | Transport for `**ssh_node**` (OpenSSH). | | `**usb**` | no (default `**local**`) | `**local**` — BrainStem / Monsoon discovery runs **on the host where you invoke the tool** (this machine for a concentrator). `**remote`** — discovery runs **over SSH on this row’s `ipaddr`** (USB cabled to that machine, e.g. a lab Pi). | | `**acroname**` | no | Comma-separated `**StemClass:downstream_usb_ports**` entries. Each token is one expected Acroname / BrainStem module (multiset match against discovery). Example: `**USBHub2x4:4, USBHub3p:8**`. | | `**monsoon**` or `**hvpm**` | no | Comma-separated `**HVPM:**` Monsoon HVPM expectations (counts sum). Discovery reports devices under JSON key `**monsoon**`. If neither key is set, expected HVPM count is **0**. | | `**machine.name`**, `**machine.type**`, `**machine.location**`, `**label**` | no | Metadata for operators and `**[fabric] concentrator**` resolution (see `**resolve_fabric_concentrator**` in `**fiwicontrol.lab.inventory_config**`). | **Stem names and port counts:** run `**python3 -m fiwicontrol.power --discovery-json`** on the host that owns the USB tree (or use `**scripts/system/test_acroname_usb_discovery.py**`, which follows `**usb**` in the INI). Match `**stem_class**` and `**downstream_usb_ports**` in the JSON to your `**acroname**` tokens. **Empty bench:** use `**acroname =`** empty and `**monsoon = HVPM:0**` (or omit both) when a machine has no Acroname / Monsoon inventory to track. ### `[fabric]` and `[fabric.rrh.]` (optional) Used when tools merge the lab INI over `**FabricDefinition**` JSON (harnesses, `**Fabric.load_merged_lab_ini**`, etc.). See `**docs/fabric-builder.md**` and `**docs/system-test-scripts.md**`. | Section / key | Meaning | | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `**[fabric]` `fabric_id**` | Overrides JSON `**fabric_id**` when set. | | `**[fabric]` `concentrator**` | Resolves to a `**[machine.*]**` row (section id, `**machine.name**`, `**label**`, `**machine.type**`, or optional flat `**machine =**` — see `**resolve_fabric_concentrator**`). Supplies concentrator SSH context for harnesses. | | `**[fabric]` `concentrator_node**` | Alias for `**concentrator**` (same resolution rules). | | `**[fabric]` `concentrator_adnacom_adapter_count**` (alias `**adnacom_adapter_count**`) | Expected Adnacom fronthaul adapters (plant metadata). | | `**[fabric]` `patch_panel_ports**` | Patch panel port count metadata. | | `**[fabric.rrh.X]**` | Per-RRH overrides: `**acroname_port**`, `**acroname_module_serial**`, `**patch_panel_port**` for `**radio_id**` **X** (JSON list key `**rrhs`**; Python `**FabricDefinition.rrhs**`). | ### Minimal example ```ini [site] name = MyLab [machine.workstation] machine.name = workstation ipaddr = 192.168.1.10 sshtype = ssh usb = local acroname = USBHub2x4:4 monsoon = HVPM:0 [machine.pi] machine.name = pi ipaddr = 192.168.1.50 sshtype = ssh usb = remote acroname = USBHub3p:8 monsoon = HVPM:1 ``` At least one inventory row is required: `**[machine.*]**` sections and/or `**[node.*]**` + `**[host.*]**` as implemented in `**fiwicontrol.lab.inventory_config.load_lab_ini**`. --- ## Discovery JSON (`python3 -m fiwicontrol.power --discovery-json`) Runs on **whichever host executes the command** (local shell or remote via SSH). Typical top-level keys: | Key | Meaning | | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `**acroname`** | List of BrainStem modules (`**stem_class**`, `**serial_number**`, `**downstream_usb_ports**`, `**hub_port_entities**`, …). | | `**monsoon**` | Monsoon-class USB devices (HVPM paths may appear as `**/dev/bus/usb/...**` when no TTY exists). | | `**brainstem_version**` | Installed `**brainstem**` pip version on that host. | | `**acroname_error**`, `**monsoon_error**` | Present when enumeration failed (missing dependency, permissions, etc.). | **Monsoon / sysfs:** HVPM often appears in `**lsusb`** as `**2ab9:0001**` without a `**/dev/ttyACM***` device. FiWiControl counts Monsoon via **pyserial** when a TTY exists and, on **Linux**, also matches VID/PID under `**/sys/bus/usb/devices`**, so `**monsoon**` can still reflect `**lsusb**` when `**list_ports**` is empty. ### Example JSON shape (Acroname + Monsoon) ```json { "acroname": [ { "transport": "USB", "serial_number": 882238458, "module_address": 0, "model_id": 19, "model_name": "USBHub3p", "model_description": "Programmable 8+1 port USB 3.0 Hub: Default module address is 6.", "stem_class": "USBHub3p", "downstream_usb_ports": 8, "hub_port_entities": 12 } ], "monsoon": [ { "device": "/dev/bus/usb/5/2", "serial_number": "35004", "vid": 10937, "pid": 1, "manufacturer": null, "product": null, "hwid": "sysfs:5-2" } ], "brainstem_version": "2.12.0" } ``` | Field / pattern | Meaning | | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `**acroname**` | One object per BrainStem module. Use `**stem_class**` + `**downstream_usb_ports**` for `**acroname =**` multiset entries. | | `**monsoon**` | One object per Monsoon-class device. | | `**vid` `10937**` | Decimal `**0x2AB9**` (Monsoon). `**pid` `1**` is `**0x0001**`. | | `**hwid` `sysfs:…**` | Sysfs device directory name (correlate with `**lsusb**`). | | `**acroname_error` / `monsoon_error**` | Read the string; fix dependencies or USB access. | Use this output to tune `**[machine.*]**` `**acroname**` and `**monsoon**`, then run `**--verify-inventory**`. --- ## What must run on each machine Remote verification runs: `python3 -m fiwicontrol.power --discovery-json` on **the remote host** over SSH. That requires: 1. **Python 3.11+** (same baseline as this repo). 2. `**fiwicontrol`** importable on that host (`**pip install -e ".[power]"**` or equivalent). 3. `**brainstem**` and `**pyserial**` for hardware enumeration. 4. **Passwordless `root@` SSH** (see `**docs/node-control-asyncio-design.md`**). If `**python3**` on the remote is not the interpreter used for `**pip**`, set `**FIWI_REMOTE_PYTHON**` on the workstation when invoking tools that SSH (see `**docs/install.md**`). **Provisioning:** `**docs/install.md`** → **Bring up systems** / **Remote host setup**. **List devices for every machine row in the INI:** ```bash python3 -m fiwicontrol.power --list-power-devices # or: python3 -m fiwicontrol.power --list-power-devices -c configs/default.ini ``` **Sanity on a rig after install:** ```bash python3 -c "import fiwicontrol.power, brainstem, serial; print('imports ok')" python3 -m fiwicontrol.power --discovery-json ``` --- ## `--verify-inventory` Compares each `**[machine.*]**` row’s expectations to live discovery (`**usb=local**` on the workstation, `**usb=remote**` via SSH to that row’s `**ipaddr**`). ```bash cd ~/Code/FiWiControl python3 -m pip install -e ".[power]" python3 -m fiwicontrol.power --verify-inventory # or: python3 -m fiwicontrol.power --verify-inventory -c configs/default.ini ``` **Successful run** prints the loaded INI path, site name, one summary line per machine, then `**OK: discovery matches INI for all machine rows.`** and exits **0**. **Failures** print `**MISMATCH:`** on stderr with lines such as `**Acroname multiset mismatch**` or `**HVPM count mismatch**`, then exit **1**. Align `**acroname`** / `**monsoon**` with `**--list-power-devices**` / `**--discovery-json**`, or fix cabling and USB permissions. `**--ssh-controlmaster`:** pass through for SSH sessions (matches other tools). **SSH noise on stdout:** host `**ssh_config`** drop-ins can prepend warnings and break JSON parsing. Prefer `**FIWI_SSH_CONFIG**` pointing at `**tests/fixtures/ssh_config_minimal**` (see `**tests/conftest.py**`) or fix system config permissions. --- ## Verification checklist (after install) Run on the **workstation** (repo root, `**pip install -e ".[power]"`** where USB tools run). 1. `**python3 -c "import fiwicontrol.commands, fiwicontrol.power; print('ok')"**` 2. `**python3 -m fiwicontrol.power --discovery-json**` — JSON includes `**acroname**`, `**monsoon**`, `**brainstem_version**` (or `***_error**` keys if something is wrong). 3. `**ssh -o BatchMode=yes root@ true**` 4. `**ssh -o BatchMode=yes root@ 'python3 -c "import fiwicontrol.power; print(\"ok\")"'**` 5. Remote `**python3 -m fiwicontrol.power --discovery-json**` via the same SSH user. 6. `**export FIWI_RUN_REMOTE_TESTS=1**` and `**export FIWI_REMOTE_IP=**` then `**python3 -m pytest -q tests/test_remote_power_dependencies.py**` 7. `**FIWI_RUN_REMOTE_TESTS=1 FIWI_REMOTE_IP= python3 -m pytest -q tests/test_node_control.py**` 8. Edit `**configs/*.ini**`, then `**python3 -m fiwicontrol.power --list-power-devices**` and `**python3 -m fiwicontrol.power --verify-inventory**`. 9. Optional: `**FIWI_VERIFY_POWER_INI=1 python3 -m pytest -q tests/test_inventory_verify_live.py**` **Environment:** `**FIWI_RUN_REMOTE_TESTS`**, `**FIWI_REMOTE_IP**`, `**FIWI_REMOTE_PYTHON**`, `**FIWI_REMOTE_PIP_FLAGS**`, and the full table in `**docs/install.md**` → **Environment variables (quick reference)**. --- ## Commands (recap) ```bash cd ~/Code/FiWiControl python3 -m pip install -e ".[power]" python3 -m fiwicontrol.power --verify-inventory FIWI_RUN_REMOTE_TESTS=1 python3 -m pytest -q tests/test_remote_power_dependencies.py ``` --- ## Acroname and concurrency BrainStem calls are **blocking**; a single USB tree should be owned by one lock or coroutine if multiple async tasks might touch it. --- ## Related docs - `**docs/install.md`** — install and remote setup. - `**docs/fabric-builder.md**` — `**python3 -m fiwicontrol.fabric build**` / `**bind**`. - `**docs/system-test-scripts.md**` — harness `**--fabric-json**`, `**scripts/system/test_acroname_usb_discovery.py**`, patterns. - `**docs/node-control-asyncio-design.md**` — `**ssh_node**`, timeouts, tests.