244 lines
16 KiB
Markdown
244 lines
16 KiB
Markdown
# 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
|
||
|
||
```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.*]**` 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@<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.
|
||
|