FiWiControl/docs/power-control-and-inventory.md

16 KiB
Raw Permalink Blame History

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 rows 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:

  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@<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.*]** rows expectations to live discovery (**usb=local** on the workstation, **usb=remote** via SSH to that rows **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).

  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@<RIG_IP> true**
  4. **ssh -o BatchMode=yes root@<RIG_IP> '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=<RIG_IP>** then **python3 -m pytest -q tests/test_remote_power_dependencies.py**
  7. **FIWI_RUN_REMOTE_TESTS=1 FIWI_REMOTE_IP=<RIG_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)

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.


  • **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.