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

244 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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