Calibrate: PCIe while port ON, Ctrl-C save; fix remote ON tracking.
- Keep hub port powered through fiber id + PCIe prompts; power off after. - Ctrl-C saves fiber_map.json, turns off current port, exits 130; handle during PCIe. - Remote calibrate: set step_powered only when SSH on succeeds; skip step if remote ON failed (was incorrectly treating failed ON as powered). - power() returns False when hubs cannot connect; CLI on/off exits 1 so SSH sees failures. - remote_hub_port_power: merge stdout+stderr for diagnostics; ssh_forward_capture uses stdin=DEVNULL. - Warn when calibrate_remotes yields zero remote steps; clarify remote_ssh.env.example. Made-with: Cursor
This commit is contained in:
parent
6d2fb4b957
commit
86e73bcff2
|
|
@ -310,17 +310,27 @@ class AcronameManager:
|
|||
return await asyncio.gather(*tasks)
|
||||
|
||||
def power(self, mode, target_str):
|
||||
if not self.hubs and not self.connect(): return
|
||||
if not self.hubs and not self.connect():
|
||||
print(
|
||||
"Error: No Acroname hubs connected (cannot change port power).",
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
)
|
||||
return False
|
||||
h_target, p_target = self._parse_target(target_str)
|
||||
for i, stem in enumerate(self.hubs):
|
||||
if h_target != 'all' and i != h_target: continue
|
||||
if h_target != 'all' and i != h_target:
|
||||
continue
|
||||
ports = self._ports_for_hub(stem, i + 1, p_target)
|
||||
if ports is None:
|
||||
return
|
||||
return False
|
||||
for port in ports:
|
||||
if mode.lower() == 'on': stem.usb.setPortEnable(port)
|
||||
else: stem.usb.setPortDisable(port)
|
||||
if mode.lower() == "on":
|
||||
stem.usb.setPortEnable(port)
|
||||
else:
|
||||
stem.usb.setPortDisable(port)
|
||||
self.status(target_str)
|
||||
return True
|
||||
|
||||
def setup_udev(self):
|
||||
if not self.hubs and not self.connect(): return
|
||||
|
|
@ -435,7 +445,8 @@ class AcronameManager:
|
|||
sys.exit(rs.ssh_forward(ssh, ["panel", mode, str(panel_1based)]))
|
||||
tgt = f"{tup[0]}.{tup[1]}"
|
||||
print(f"Panel {panel_1based} → hub target {tgt} ({mode})", flush=True)
|
||||
self.power(mode, tgt)
|
||||
if not self.power(mode, tgt):
|
||||
sys.exit(1)
|
||||
|
||||
def fiber_power(self, mode, fiber_port):
|
||||
"""Power via fiber_map.json fiber_ports key (any positive integer id)."""
|
||||
|
|
@ -455,7 +466,8 @@ class AcronameManager:
|
|||
sys.exit(rs.ssh_forward(ssh, ["power", "fiber-port", key, mode.lower()]))
|
||||
tgt = f"{tup[0]}.{tup[1]}"
|
||||
print(f"Fiber port {fiber_port} → hub target {tgt} ({mode})", flush=True)
|
||||
self.power(mode, tgt)
|
||||
if not self.power(mode, tgt):
|
||||
sys.exit(1)
|
||||
|
||||
def fiber_chip(self, fiber_port, save=False):
|
||||
"""
|
||||
|
|
@ -660,6 +672,21 @@ class AcronameManager:
|
|||
else:
|
||||
stem.usb.setPortDisable(port_0)
|
||||
|
||||
def _calibrate_step_power_off(self, ssh_host, hub_1, port_0):
|
||||
"""Turn off downstream port at end of one calibrate step (after PCIe prompts if mapped)."""
|
||||
if ssh_host is None:
|
||||
self._set_hub_port_power(hub_1, port_0, False)
|
||||
print(
|
||||
f">>> OFF local hub {hub_1} USB port {port_0} ({self._port_power_feedback(hub_1, port_0)})",
|
||||
flush=True,
|
||||
)
|
||||
else:
|
||||
rs.remote_hub_port_power(ssh_host, hub_1, port_0, False)
|
||||
print(
|
||||
f">>> OFF {ssh_host} hub {hub_1} USB port {port_0}",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
def _port_power_feedback(self, hub_1, port_0):
|
||||
"""Return short status string after a change (hub power state + optional current)."""
|
||||
h_idx = hub_1 - 1
|
||||
|
|
@ -767,6 +794,15 @@ class AcronameManager:
|
|||
steps.append((None, hub_1, port_0))
|
||||
for host in cli_hosts:
|
||||
remote_pairs = rs.fetch_calibrate_ports_json(host)
|
||||
if not remote_pairs:
|
||||
print(
|
||||
f"hub_manager: WARNING: 0 remote calibrate steps from {host!r} — SSH hub_manager returned "
|
||||
"no port list (often: Pi used system python3 without brainstem). On this PC set "
|
||||
"HUB_MANAGER_REMOTE_PYTHON and HUB_MANAGER_REMOTE_SCRIPT in remote_ssh.env to paths "
|
||||
"that exist on the Pi (venv python3 + hub_manager.py). See remote_ssh.env.example.",
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
)
|
||||
for hub_1, port_0 in remote_pairs:
|
||||
steps.append((host, hub_1, port_0))
|
||||
|
||||
|
|
@ -837,6 +873,7 @@ class AcronameManager:
|
|||
"All downstream ports were turned OFF first so only one port is ON per step.\n"
|
||||
"Order: all local hub ports (hub 1 port 0 first), then each --ssh host’s ports in order.\n"
|
||||
"Each step: lsusb snapshot (port OFF) → ON (~2s) → new lsusb lines (chip hint) → fiber id, s=skip, q=quit.\n"
|
||||
"Port stays ON through optional PCIe prompts, then powers OFF. Ctrl-C anytime saves fiber_map.json and exits.\n"
|
||||
"When you map a fiber, usb_id / chip_type are saved if new lsusb lines appeared.\n"
|
||||
"Remote steps store ssh in fiber_map.json automatically.\n"
|
||||
"After each fiber id you can pick PCIe by number: 1–6 = known Adnacom H3 card, then SFP 1–4 "
|
||||
|
|
@ -853,19 +890,35 @@ class AcronameManager:
|
|||
else rs.remote_lsusb_lines(ssh_host)
|
||||
)
|
||||
chip_hint_lines = []
|
||||
step_powered = False
|
||||
line = ""
|
||||
print(f"\n>>> ON {route} hub {hub_1} USB port {port_0}", flush=True)
|
||||
try:
|
||||
if ssh_host is None:
|
||||
self._set_hub_port_power(hub_1, port_0, True)
|
||||
print(f" {self._port_power_feedback(hub_1, port_0)}", flush=True)
|
||||
step_powered = True
|
||||
else:
|
||||
rc, rerr = rs.remote_hub_port_power(ssh_host, hub_1, port_0, True)
|
||||
rc, rmsg = rs.remote_hub_port_power(ssh_host, hub_1, port_0, True)
|
||||
if rc != 0:
|
||||
print(f" remote on failed ({rc}): {rerr.strip()[:200]}", flush=True)
|
||||
snippet = (rmsg or "").strip()[:800]
|
||||
print(
|
||||
f" remote on failed (exit {rc})"
|
||||
+ (f": {snippet}" if snippet else ""),
|
||||
flush=True,
|
||||
)
|
||||
step_powered = False
|
||||
else:
|
||||
time.sleep(0.25)
|
||||
print(f" {rs.remote_port_power_feedback(ssh_host, hub_1, port_0)}", flush=True)
|
||||
step_powered = True
|
||||
time.sleep(2.0)
|
||||
if ssh_host is not None and not step_powered:
|
||||
print(
|
||||
" Skipping this calibrate step (remote ON failed; fix SSH / Pi hub_manager exit code).",
|
||||
flush=True,
|
||||
)
|
||||
continue
|
||||
after_lsusb = (
|
||||
usb.lsusb_lines()
|
||||
if ssh_host is None
|
||||
|
|
@ -887,36 +940,45 @@ class AcronameManager:
|
|||
flush=True,
|
||||
)
|
||||
try:
|
||||
line = input("Which fiber port id? [s=skip q=quit]: ").strip()
|
||||
line = input(
|
||||
"Which fiber port id? [s=skip q=quit, Ctrl-C=save map & exit]: "
|
||||
).strip()
|
||||
except EOFError:
|
||||
print(
|
||||
"\n*** No input (stdin closed). Use a TTY, e.g. ssh -t … panel calibrate … ***\n",
|
||||
flush=True,
|
||||
)
|
||||
line = "q"
|
||||
finally:
|
||||
if ssh_host is None:
|
||||
self._set_hub_port_power(hub_1, port_0, False)
|
||||
print(f">>> OFF local hub {hub_1} USB port {port_0} ({self._port_power_feedback(hub_1, port_0)})", flush=True)
|
||||
else:
|
||||
rs.remote_hub_port_power(ssh_host, hub_1, port_0, False)
|
||||
print(
|
||||
f">>> OFF {ssh_host} hub {hub_1} USB port {port_0}",
|
||||
flush=True,
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
print(
|
||||
"\n*** Calibrate interrupted (Ctrl-C); writing fiber_map.json and powering off this port. ***\n",
|
||||
flush=True,
|
||||
)
|
||||
if step_powered:
|
||||
self._calibrate_step_power_off(ssh_host, hub_1, port_0)
|
||||
self._write_fiber_map_document(doc)
|
||||
raise SystemExit(130)
|
||||
|
||||
low = line.lower()
|
||||
if low == "q":
|
||||
if step_powered:
|
||||
self._calibrate_step_power_off(ssh_host, hub_1, port_0)
|
||||
break
|
||||
if low == "s" or not line:
|
||||
if step_powered:
|
||||
self._calibrate_step_power_off(ssh_host, hub_1, port_0)
|
||||
continue
|
||||
try:
|
||||
pn = int(line)
|
||||
except ValueError:
|
||||
print(" Ignored (not an integer).", flush=True)
|
||||
if step_powered:
|
||||
self._calibrate_step_power_off(ssh_host, hub_1, port_0)
|
||||
continue
|
||||
if pn < 1:
|
||||
print(" Ignored (fiber port id must be >= 1).", flush=True)
|
||||
if step_powered:
|
||||
self._calibrate_step_power_off(ssh_host, hub_1, port_0)
|
||||
continue
|
||||
key = str(pn)
|
||||
prev = fm.fiber_entry_hub_port(ports.get(key))
|
||||
|
|
@ -950,7 +1012,18 @@ class AcronameManager:
|
|||
):
|
||||
entry.pop(k, None)
|
||||
entry.update(fm.chip_fields_from_lsusb_lines(chip_hint_lines))
|
||||
action, pdata = fm.prompt_pcie_metadata_for_calibrate(entry.get("pcie"))
|
||||
try:
|
||||
action, pdata = fm.prompt_pcie_metadata_for_calibrate(entry.get("pcie"))
|
||||
except KeyboardInterrupt:
|
||||
print(
|
||||
"\n*** Interrupted during PCIe prompts; saving this port’s map row and powering off. ***\n",
|
||||
flush=True,
|
||||
)
|
||||
ports[key] = entry
|
||||
if step_powered:
|
||||
self._calibrate_step_power_off(ssh_host, hub_1, port_0)
|
||||
self._write_fiber_map_document(doc)
|
||||
raise SystemExit(130)
|
||||
if action == "clear":
|
||||
entry.pop("pcie", None)
|
||||
elif action == "set" and pdata is not None:
|
||||
|
|
@ -959,6 +1032,8 @@ class AcronameManager:
|
|||
else:
|
||||
entry.pop("pcie", None)
|
||||
ports[key] = entry
|
||||
if step_powered:
|
||||
self._calibrate_step_power_off(ssh_host, hub_1, port_0)
|
||||
|
||||
self._write_fiber_map_document(doc)
|
||||
|
||||
|
|
|
|||
|
|
@ -157,7 +157,8 @@ def main() -> int:
|
|||
print(f"Unknown panel subcommand: {sub!r}", file=sys.stderr, flush=True)
|
||||
return 2
|
||||
elif cmd in ("on", "off"):
|
||||
mgr.power(cmd, target)
|
||||
if not mgr.power(cmd, target):
|
||||
return 1
|
||||
elif cmd in ("reboot", "reboot-force"):
|
||||
mgr.reboot(target, skip_empty=(cmd == "reboot"))
|
||||
elif cmd == "setup":
|
||||
|
|
|
|||
|
|
@ -81,7 +81,13 @@ def ssh_forward_capture(remote_host, remote_args, timeout=90):
|
|||
extra = shlex.split(os.environ.get("HUB_MANAGER_SSH_OPTS", ""))
|
||||
cmd = [ssh_bin, *extra, remote_host, py, script, *remote_args]
|
||||
try:
|
||||
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
||||
proc = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
stdin=subprocess.DEVNULL,
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
return 124, "", "ssh/hub_manager timed out"
|
||||
return (
|
||||
|
|
@ -247,8 +253,9 @@ def fetch_calibrate_ports_json(ssh_host):
|
|||
|
||||
def remote_hub_port_power(ssh_host, hub_1, port_0, enable):
|
||||
sub = "on" if enable else "off"
|
||||
code, _, err = ssh_forward_capture(ssh_host, [sub, f"{hub_1}.{port_0}"])
|
||||
return code, err
|
||||
code, out, err = ssh_forward_capture(ssh_host, [sub, f"{hub_1}.{port_0}"])
|
||||
blob = "\n".join(x.strip() for x in (out or "", err or "") if x and x.strip()).strip()
|
||||
return code, blob
|
||||
|
||||
|
||||
def remote_port_power_feedback(ssh_host, hub_1, port_0):
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@
|
|||
HUB_MANAGER_REMOTE_PYTHON=/home/rjmcmahon/Code/acroname/env/bin/python3
|
||||
HUB_MANAGER_REMOTE_SCRIPT=/home/rjmcmahon/Code/acroname/hub_manager.py
|
||||
|
||||
# Optional: comma-separated Pi (or other) hosts. Then one command does local + remote calibrate:
|
||||
# Optional: comma-separated Pi (or other) hosts for hybrid calibrate (local + remote in one run):
|
||||
# python3 hub_manager.py panel calibrate
|
||||
# HUB_MANAGER_CALIBRATE_REMOTES=pi@192.168.1.50,pi@192.168.1.51
|
||||
#
|
||||
# Remotes still need the two lines above: without them, SSH uses `python3` on the Pi and you get
|
||||
# "No module named 'brainstem'" and 0 remote calibrate steps.
|
||||
|
|
|
|||
Loading…
Reference in New Issue