16 KiB
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.<id>]** section per bench machine (SSH identity plus expected USB hardware), and optional **[fabric]** / **[fabric.rrh.<radio_id>]** 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.<id>] — one row per machine
<id> 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@<ipaddr>**, 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:<n>** 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.<radio_id>] (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
[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)
{
"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:
- Python 3.11+ (same baseline as this repo).
**fiwicontrol** importable on that host (**pip install -e ".[power]"**or equivalent).**brainstem**and**pyserial**for hardware enumeration.- Passwordless
root@<ip>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:
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:
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**).
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).
**python3 -c "import fiwicontrol.commands, fiwicontrol.power; print('ok')"****python3 -m fiwicontrol.power --discovery-json**— JSON includes**acroname**,**monsoon**,**brainstem_version**(or***_error**keys if something is wrong).**ssh -o BatchMode=yes root@<RIG_IP> true****ssh -o BatchMode=yes root@<RIG_IP> 'python3 -c "import fiwicontrol.power; print(\"ok\")"'**- Remote
**python3 -m fiwicontrol.power --discovery-json**via the same SSH user. **export FIWI_RUN_REMOTE_TESTS=1**and**export FIWI_REMOTE_IP=<RIG_IP>**then**python3 -m pytest -q tests/test_remote_power_dependencies.py****FIWI_RUN_REMOTE_TESTS=1 FIWI_REMOTE_IP=<RIG_IP> python3 -m pytest -q tests/test_node_control.py**- Edit
**configs/*.ini**, then**python3 -m fiwicontrol.power --list-power-devices**and**python3 -m fiwicontrol.power --verify-inventory**. - 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)
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.