Skip to content

Add lablink scan to discover and identify attached devices - #5

Merged
techmanual-ai merged 1 commit into
mainfrom
feat/scan-discovery-20260918
Sep 18, 2026
Merged

techmanual-ai merged 1 commit into
mainfrom
feat/scan-discovery-20260918

Conversation

@techmanual-ai

Copy link
Copy Markdown
Owner

Committed on feat/scan-discovery-20260918 as 5fb2d98. Not pushed.


What changed

lablink scan is a new shared CLI command that enumerates what is physically attached, asks each candidate who it is, and prints a table.

  • lablink/discovery.py (new) — the sweep. scan() enumerates VISA resources via pyvisa.ResourceManager(...).list_resources() and serial ports via serial.tools.list_ports.comports(), probes each candidate (open → *IDN? → read → close) with a 2 s per-probe timeout, and returns a ScanResult. Pure helpers parse_idn() and suggest_alias() are unit-testable on their own.
  • lablink/base.py — DiscoveredDevice and ScanResult dataclasses, following the topology subsystem's split (§15.1): models in base.py, logic in its own module.
  • lablink/cli.py — the scan command, next to diagnose and list. It only formats: table to stdout, counts/install steps/per-device detail to stderr, per the repo's CLI output rule.
  • tests/test_discovery.py (new, 21 tests) — resource found and identified; opens but never answers *IDN?; raises on open; list_resources() itself failing; pyvisa missing; pyserial missing; both missing; nothing found at all; serial port identified with VID/PID metadata; silent serial port; *IDN? parsing and alias slugging; plus CLI rendering of the table, the empty-scan guidance, and action items.
  • Docs — README Quick Start now opens with lablink scan (with sample output) and the python -c "import pyvisa; ..." one-liner is gone; scan added to the CLI reference; the VISA troubleshooting entry reworded in terms of scan; examples/configs/visa_scope.toml comment updated. CHANGELOG entry under [Unreleased]. docs/ARCHITECTURE.md gains §17 (module placement, probe contract, degradation) plus directory-structure and core-component entries. docs/agent_docs/agent_development.md records two generalizable rules: CLI-only commands still keep logic in a module, and monkeypatch.setitem(sys.modules, "pyvisa", None) is how to test a lazy-import fallback.

Behavior on missing drivers follows diagnose() rather than inventing a second pattern: each sweep lazy-imports its library and, on ImportError, contributes one action_items entry naming the extra (pip install lablink-mcp[visa]). The other sweeps still run.

Verification

  • .venv/bin/python -m pytest -q → 371 passed. The suite was green before this change; no pre-existing failures.
  • Ran lablink scan for real on the dev machine: found 8 candidates (4 serial ports, each also visible as an ASRL VISA resource), 0 identified, each with its "no *IDN? reply" reason — no traceback, no stall.
  • Ran lablink scan with both pyvisa and serial imports blocked: exit 0, two install action items, and the empty-scan explanation.
  • End to end against lablink-sim: the VISA probe identified LabLink,FG-100,SIMFG001,0.2.0 over the real TCP socket (suggested alias lablink_fg_100) and DAQ-8 on 5026; the serial probe identified DAQ-8 over the simulator's pty.

Assumptions and notes

  • The simulator can be probed but not discovered. A raw TCP SOCKET resource and a pty are not returned by list_resources() or comports(), so lablink scan cannot find the sim on its own — I verified the identify path against it by calling the probe with the sim's resource string directly. That is a property of those buses, not of scan; it's stated in §17.3. Real USB/GPIB/ASRL instruments (and LAN instruments via pyvisa-py's VXI-11 discovery) do enumerate.
  • --write-configs deliberately absent, per the task scope. The footer line tells the user where to write the config; wiring that up is the follow-up.
  • No --timeout flag. The task specified "about 2 seconds"; scan(timeout_s=...) takes the parameter for tests and future use, but exposing a flag would be scope beyond the ask. Worth adding if slow GPIB instruments need it.
  • A device on a serial port shows up twice — once as ASRL/dev/...::INSTR (visa driver) and once as /dev/... (serial driver). I left both rows: they are different resource strings served by different drivers, so each is separately actionable. Deduping would mean guessing which driver the user wants.
  • I updated the *IDN?-discovery paragraph in the MCP server instructions (lablink/mcp_server.py) to name lablink scan first, keeping the python -c one-liner as the fallback. The instructions already make the agent responsible for writing device configs, so leaving it pointing only at the one-liner would have been stale the moment this merged. scan itself is not an MCP tool — every MCP tool is alias-addressed, and scanning happens before any alias exists.

🤖 Generated with Claude Code

After `pip install`, the first thing a new user had to do was hand-author a
TOML file with a VISA resource string found via a `python -c "import pyvisa;
..."` one-liner copied out of the README. That is the narrowest point in the
funnel, and it is work LabLink can do itself.

`lablink scan` enumerates VISA resources and serial ports, asks each candidate
`*IDN?` with a short per-probe timeout, and prints resource string, interface
type, the parsed identity fields, and a suggested `<vendor>_<model>` alias.

Design notes:
- Discovery crosses the visa and serial drivers, so it is a shared subsystem
  (`lablink/discovery.py`, models in `base.py`) and a shared CLI command
  alongside `diagnose`/`list` — not a driver method. The CLI command only
  renders; the sweep is reachable from tests without click.
- A candidate that cannot be opened, or never answers, is still reported as
  found-but-unidentified. Not everything on a serial bus speaks SCPI, and a
  hidden device is worse than an honest "did not identify".
- Each sweep lazy-imports its library and, on ImportError, contributes an
  install action item instead of failing — the same degradation `diagnose()`
  already provides. `scan` with no extras installed prints two install steps
  and an explanation of the empty result, not a traceback.

Discovery and reporting only; writing configs is a separate change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@techmanual-ai
techmanual-ai merged commit 9dd561c into main Sep 18, 2026
4 checks passed
@techmanual-ai
techmanual-ai deleted the feat/scan-discovery-20260918 branch September 18, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant