check_concentrator: panel order, Power totals line; README + post-commit hook.
- Port table: sort by panel; Power: Total W / mA (per port follows) as sole header; dash alignment; fiber_map panel column unchanged. - Add README.md with git hook setup; githooks/post-commit (opt-in push + remote pull); scripts/install-git-hooks.sh sets core.hooksPath. Made-with: Cursor
This commit is contained in:
parent
31485b0019
commit
3ea65d9271
|
|
@ -0,0 +1,45 @@
|
||||||
|
# FiWiManager
|
||||||
|
|
||||||
|
Fi-Wi USB power-control hubs, `fiber_map.json`, SSH remotes, and related tooling. Deeper detail lives under [`docs/`](docs/) (for example [`docs/fiwi-cli.md`](docs/fiwi-cli.md) and [`docs/fiwi-design.md`](docs/fiwi-design.md)).
|
||||||
|
|
||||||
|
## Git hooks: post-commit push and remote pull
|
||||||
|
|
||||||
|
You can use a **post-commit** hook that, after each commit, runs **`git push`** for the current branch to **`origin`**, then **SSH** to another machine (e.g. a lab Pi) and runs **`git pull`** in a clone there.
|
||||||
|
|
||||||
|
### Install once per clone
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/install-git-hooks.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
That sets **`core.hooksPath`** to **`githooks/`** so Git uses the tracked hook in this repository.
|
||||||
|
|
||||||
|
### Enable (opt-in)
|
||||||
|
|
||||||
|
The hook **does nothing** unless you set:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export FIWI_POST_COMMIT_SYNC=1
|
||||||
|
export FIWI_POST_COMMIT_REMOTE='user@host'
|
||||||
|
export FIWI_POST_COMMIT_REMOTE_PATH='~/Code/FiWiManager' # optional; default shown
|
||||||
|
```
|
||||||
|
|
||||||
|
Put those in your shell profile if you want them every session.
|
||||||
|
|
||||||
|
| Variable | Meaning |
|
||||||
|
|----------|---------|
|
||||||
|
| `FIWI_POST_COMMIT_SYNC` | Must be `1` for the hook to run. |
|
||||||
|
| `FIWI_POST_COMMIT_REMOTE` | SSH target for `git pull` (e.g. `rjmcmahon@192.168.1.39`). **Required** when `SYNC=1`; if empty, the hook skips **both** push and pull and prints a message. |
|
||||||
|
| `FIWI_POST_COMMIT_REMOTE_PATH` | Directory of the FiWiManager clone on that host. Paths starting with `~/` are turned into `$HOME/…` on the remote. |
|
||||||
|
|
||||||
|
**Skip once:** `FIWI_POST_COMMIT_SYNC=0 git commit …`
|
||||||
|
|
||||||
|
**SSH:** the hook uses `ssh -o BatchMode=yes`, so it expects non-interactive auth (keys).
|
||||||
|
|
||||||
|
**Disable** hooks from this repo’s `githooks/` directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git config --unset core.hooksPath
|
||||||
|
```
|
||||||
|
|
||||||
|
See also comments at the top of [`githooks/post-commit`](githooks/post-commit).
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Post-commit hook: push this repo, then optionally `git pull` on another machine over SSH.
|
||||||
|
#
|
||||||
|
# Opt-in (all must be set for the hook to do anything):
|
||||||
|
# export FIWI_POST_COMMIT_SYNC=1
|
||||||
|
# export FIWI_POST_COMMIT_REMOTE='user@host' # SSH target for the Pi / lab box
|
||||||
|
# export FIWI_POST_COMMIT_REMOTE_PATH='~/Code/FiWiManager' # repo root on that host
|
||||||
|
#
|
||||||
|
# Skip once: FIWI_POST_COMMIT_SYNC=0 git commit ...
|
||||||
|
# Disable hook: git config --unset core.hooksPath (or point hooksPath elsewhere)
|
||||||
|
#
|
||||||
|
# One-time setup in this clone:
|
||||||
|
# ./scripts/install-git-hooks.sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ "${FIWI_POST_COMMIT_SYNC:-}" != "1" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${FIWI_POST_COMMIT_REMOTE:-}" ]]; then
|
||||||
|
echo "post-commit: FIWI_POST_COMMIT_SYNC=1 but FIWI_POST_COMMIT_REMOTE is empty; skipping." >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
root=$(git rev-parse --show-toplevel)
|
||||||
|
cd "$root"
|
||||||
|
|
||||||
|
branch=$(git rev-parse --abbrev-ref HEAD)
|
||||||
|
if [[ "$branch" == "HEAD" ]]; then
|
||||||
|
echo "post-commit: detached HEAD; skipping push." >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "post-commit: git push origin $branch" >&2
|
||||||
|
git push origin "$branch"
|
||||||
|
|
||||||
|
rpath=${FIWI_POST_COMMIT_REMOTE_PATH:-~/Code/FiWiManager}
|
||||||
|
echo "post-commit: ssh $FIWI_POST_COMMIT_REMOTE git pull in $rpath" >&2
|
||||||
|
if [[ "$rpath" == ~/* ]]; then
|
||||||
|
_tail=${rpath#~/}
|
||||||
|
ssh -o BatchMode=yes "$FIWI_POST_COMMIT_REMOTE" bash -lc "cd \"\$HOME/$_tail\" && git pull"
|
||||||
|
else
|
||||||
|
_q=$(printf '%q' "$rpath")
|
||||||
|
ssh -o BatchMode=yes "$FIWI_POST_COMMIT_REMOTE" bash -lc "cd ${_q} && git pull"
|
||||||
|
fi
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Point this repository at tracked hooks under githooks/ (post-commit push + optional remote pull).
|
||||||
|
set -euo pipefail
|
||||||
|
root=$(git rev-parse --show-toplevel 2>/dev/null) || {
|
||||||
|
echo "Run from inside a FiWiManager git clone." >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
cd "$root"
|
||||||
|
git config core.hooksPath githooks
|
||||||
|
echo "Set core.hooksPath=githooks in this repo."
|
||||||
|
echo ""
|
||||||
|
echo "To sync after each commit (when FIWI_POST_COMMIT_SYNC=1), add to your shell profile:"
|
||||||
|
echo " export FIWI_POST_COMMIT_SYNC=1"
|
||||||
|
echo " export FIWI_POST_COMMIT_REMOTE='user@host'"
|
||||||
|
echo " export FIWI_POST_COMMIT_REMOTE_PATH='~/Code/FiWiManager'"
|
||||||
|
|
@ -4,8 +4,10 @@ Smoke check: :class:`fiwi.concentrator.FiWiConcentrator` plus consolidated USB h
|
||||||
metrics tables covering **this machine** and each configured **remote**. The consolidated hub table
|
metrics tables covering **this machine** and each configured **remote**. The consolidated hub table
|
||||||
has **Location** (``local`` or remote IP/hostname, plus **tty** names when known) and **USB**
|
has **Location** (``local`` or remote IP/hostname, plus **tty** names when known) and **USB**
|
||||||
(Bus/Device, VID:PID, product from sysfs). The per-port table starts with **Panel** (``fiber_ports``
|
(Bus/Device, VID:PID, product from sysfs). The per-port table starts with **Panel** (``fiber_ports``
|
||||||
key / rack position when ``hub``, ``port``, and optional ``ssh`` match this run), then power metrics
|
key / rack position when ``hub``, ``port``, and optional ``ssh`` match this run), rows sorted by panel
|
||||||
and **Location** (no USB column).
|
number; a **Power: Total … W / … mA (per port follows)** line heads the section, then the column header
|
||||||
|
and rows.
|
||||||
|
(no USB column).
|
||||||
Adnacom PCIe catalog last.
|
Adnacom PCIe catalog last.
|
||||||
|
|
||||||
Requires BrainStem locally for hub enumeration on this host. Remotes use SSH ``show_hostcards``
|
Requires BrainStem locally for hub enumeration on this host. Remotes use SSH ``show_hostcards``
|
||||||
|
|
@ -20,7 +22,7 @@ and ``port-metrics-json``; the remote tree must include that command (same revis
|
||||||
|
|
||||||
``--powercycle`` runs a destructive self-test on **local** USB hubs and each host in
|
``--powercycle`` runs a destructive self-test on **local** USB hubs and each host in
|
||||||
``FIWI_CALIBRATE_REMOTES`` / merged hub hosts: all ports OFF (verify), then all ON (verify),
|
``FIWI_CALIBRATE_REMOTES`` / merged hub hosts: all ports OFF (verify), then all ON (verify),
|
||||||
then prints the **per-port power** table (snapshot after the test).
|
then prints the port power table (snapshot after the test).
|
||||||
|
|
||||||
With pytest::
|
With pytest::
|
||||||
|
|
||||||
|
|
@ -263,6 +265,23 @@ def _panel_cell(
|
||||||
return lookup.get((sk, h1, port_0), "—")
|
return lookup.get((sk, h1, port_0), "—")
|
||||||
|
|
||||||
|
|
||||||
|
def _panel_sort_tuple(pnl: str, hub_table_idx: int, port_0: int) -> tuple:
|
||||||
|
"""
|
||||||
|
Sort per-port rows by patch panel label: numeric ``fiber_ports`` keys first (min if comma-list),
|
||||||
|
then other labels, unmapped (—) last; tie-break hub # and port.
|
||||||
|
"""
|
||||||
|
s = (pnl or "").strip()
|
||||||
|
if not s or s == "—":
|
||||||
|
return (2, 0, hub_table_idx, port_0)
|
||||||
|
nums: list[int] = []
|
||||||
|
for part in re.split(r"[\s,]+", s):
|
||||||
|
if part.isdigit():
|
||||||
|
nums.append(int(part))
|
||||||
|
if nums:
|
||||||
|
return (0, min(nums), hub_table_idx, port_0)
|
||||||
|
return (1, s, hub_table_idx, port_0)
|
||||||
|
|
||||||
|
|
||||||
def _merged_hub_hosts() -> list[str]:
|
def _merged_hub_hosts() -> list[str]:
|
||||||
from fiwi.ssh import SshNodeConfig
|
from fiwi.ssh import SshNodeConfig
|
||||||
|
|
||||||
|
|
@ -672,17 +691,14 @@ def _print_per_port_power_table(
|
||||||
c: FiWiConcentrator,
|
c: FiWiConcentrator,
|
||||||
hub_rows: list[ConsolidatedHubRow],
|
hub_rows: list[ConsolidatedHubRow],
|
||||||
base_rc: int,
|
base_rc: int,
|
||||||
*,
|
|
||||||
title: str | None = None,
|
|
||||||
) -> int:
|
) -> int:
|
||||||
"""
|
"""
|
||||||
Per-port power, current (mA), voltage (V), and Location — same hub order as consolidated rows.
|
``Power: Total …`` line, then per-port current (mA), voltage (V), and Location (sorted by panel).
|
||||||
Returns ``base_rc`` OR’d with failure if any ``port-metrics-json`` remote call fails.
|
Returns ``base_rc`` OR’d with failure if any ``port-metrics-json`` remote call fails.
|
||||||
"""
|
"""
|
||||||
rc = base_rc
|
rc = base_rc
|
||||||
section_title = title or "Per-port power (consolidated hub order)"
|
|
||||||
if not hub_rows:
|
if not hub_rows:
|
||||||
print(" (no hubs — skipping per-port power table.)", flush=True)
|
print(" (no hubs — skipping port power table.)", flush=True)
|
||||||
print(flush=True)
|
print(flush=True)
|
||||||
return rc
|
return rc
|
||||||
|
|
||||||
|
|
@ -707,11 +723,12 @@ def _print_per_port_power_table(
|
||||||
remote_cache[ssh_target] = {}
|
remote_cache[ssh_target] = {}
|
||||||
return remote_cache[ssh_target]
|
return remote_cache[ssh_target]
|
||||||
|
|
||||||
print(section_title, flush=True)
|
# Collect rows; sum current (mA) and power (W) where metrics allow.
|
||||||
print("-" * len(_PER_PORT_PWR_HDR), flush=True)
|
lines_out: list[tuple[tuple, str]] = []
|
||||||
print(_PER_PORT_PWR_HDR, flush=True)
|
total_ma = 0.0
|
||||||
print("-" * len(_PER_PORT_PWR_HDR), flush=True)
|
n_ma = 0
|
||||||
|
total_power_w = 0.0
|
||||||
|
n_power = 0
|
||||||
for row in hub_rows:
|
for row in hub_rows:
|
||||||
sn_key = _norm_hub_serial(row.serial)
|
sn_key = _norm_hub_serial(row.serial)
|
||||||
if row.ssh_target is None:
|
if row.ssh_target is None:
|
||||||
|
|
@ -721,6 +738,8 @@ def _print_per_port_power_table(
|
||||||
|
|
||||||
for port in range(row.n_ports):
|
for port in range(row.n_ports):
|
||||||
m = by_port.get(port)
|
m = by_port.get(port)
|
||||||
|
cur_f: float | None = None
|
||||||
|
v_f: float | None = None
|
||||||
if m is None:
|
if m is None:
|
||||||
pwr, ma_s, v_s = "?", "—", "—"
|
pwr, ma_s, v_s = "?", "—", "—"
|
||||||
else:
|
else:
|
||||||
|
|
@ -730,7 +749,10 @@ def _print_per_port_power_table(
|
||||||
ma_s = "—"
|
ma_s = "—"
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
ma_s = f"{float(cur):.2f}"
|
cur_f = float(cur)
|
||||||
|
ma_s = f"{cur_f:.2f}"
|
||||||
|
total_ma += cur_f
|
||||||
|
n_ma += 1
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
ma_s = "—"
|
ma_s = "—"
|
||||||
vv = m.get("voltage_v")
|
vv = m.get("voltage_v")
|
||||||
|
|
@ -738,15 +760,39 @@ def _print_per_port_power_table(
|
||||||
v_s = "—"
|
v_s = "—"
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
v_s = f"{float(vv):.3f}"
|
v_f = float(vv)
|
||||||
|
v_s = f"{v_f:.3f}"
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
v_s = "—"
|
v_s = "—"
|
||||||
|
if cur_f is not None and v_f is not None:
|
||||||
|
total_power_w += v_f * (cur_f / 1000.0)
|
||||||
|
n_power += 1
|
||||||
pnl = _panel_cell(panel_lookup, hub_rows, row, port)
|
pnl = _panel_cell(panel_lookup, hub_rows, row, port)
|
||||||
print(
|
st = _panel_sort_tuple(pnl, row.idx, port)
|
||||||
|
line = (
|
||||||
f"{pnl:<8} | {row.idx:<4} | {row.serial:<12} | {port:<3} | {pwr:<5} | "
|
f"{pnl:<8} | {row.idx:<4} | {row.serial:<12} | {port:<3} | {pwr:<5} | "
|
||||||
f"{ma_s:>8} | {v_s:>8} | {_location_cell(row)}",
|
f"{ma_s:>8} | {v_s:>8} | {_location_cell(row)}"
|
||||||
flush=True,
|
|
||||||
)
|
)
|
||||||
|
lines_out.append((st, line))
|
||||||
|
|
||||||
|
if n_ma and n_power:
|
||||||
|
summary = (
|
||||||
|
f"Power: Total {total_power_w:.3f} W / {total_ma:.2f} mA (per port follows)"
|
||||||
|
)
|
||||||
|
elif n_ma:
|
||||||
|
summary = f"Power: Total — W / {total_ma:.2f} mA (per port follows)"
|
||||||
|
else:
|
||||||
|
summary = "Power: — (per port follows)"
|
||||||
|
|
||||||
|
print(summary, flush=True)
|
||||||
|
print("-" * len(_PER_PORT_PWR_HDR), flush=True)
|
||||||
|
|
||||||
|
print(_PER_PORT_PWR_HDR, flush=True)
|
||||||
|
print("-" * len(_PER_PORT_PWR_HDR), flush=True)
|
||||||
|
|
||||||
|
lines_out.sort(key=lambda x: x[0])
|
||||||
|
for _sk, line in lines_out:
|
||||||
|
print(line, flush=True)
|
||||||
|
|
||||||
print(flush=True)
|
print(flush=True)
|
||||||
|
|
||||||
|
|
@ -766,7 +812,7 @@ def _print_per_port_power_table(
|
||||||
|
|
||||||
|
|
||||||
def _print_usb_hub_tables(c: FiWiConcentrator, hub_rows: list[ConsolidatedHubRow], base_rc: int) -> int:
|
def _print_usb_hub_tables(c: FiWiConcentrator, hub_rows: list[ConsolidatedHubRow], base_rc: int) -> int:
|
||||||
"""Print consolidated hub table and per-port power / current / voltage table. Returns updated rc."""
|
"""Print consolidated hub table and port power / current / voltage table. Returns updated rc."""
|
||||||
rc = base_rc
|
rc = base_rc
|
||||||
print("USB power-control hubs (consolidated)", flush=True)
|
print("USB power-control hubs (consolidated)", flush=True)
|
||||||
print("-" * len(_CONSOLIDATED_HUB_HDR), flush=True)
|
print("-" * len(_CONSOLIDATED_HUB_HDR), flush=True)
|
||||||
|
|
@ -838,7 +884,7 @@ def _parse_args() -> argparse.Namespace:
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help=(
|
help=(
|
||||||
"Self-test: all downstream ports OFF then ON on local hubs and each merged "
|
"Self-test: all downstream ports OFF then ON on local hubs and each merged "
|
||||||
"remote hub host; verifies via port metrics; then print per-port power table "
|
"remote hub host; verifies via port metrics; then print port power table "
|
||||||
"(disrupts USB power)."
|
"(disrupts USB power)."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -868,12 +914,7 @@ def main() -> int:
|
||||||
hub_rows, brc = _build_consolidated_hub_rows(c)
|
hub_rows, brc = _build_consolidated_hub_rows(c)
|
||||||
remote_fail = remote_fail or brc
|
remote_fail = remote_fail or brc
|
||||||
hub_rows = _enrich_hub_rows_usb(hub_rows)
|
hub_rows = _enrich_hub_rows_usb(hub_rows)
|
||||||
remote_fail = _print_per_port_power_table(
|
remote_fail = _print_per_port_power_table(c, hub_rows, remote_fail)
|
||||||
c,
|
|
||||||
hub_rows,
|
|
||||||
remote_fail,
|
|
||||||
title="Per-port power (after power-cycle test)",
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
remote_fail = _print_consolidated_report(c)
|
remote_fail = _print_consolidated_report(c)
|
||||||
except AssertionError as exc:
|
except AssertionError as exc:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue