@sohumsuthar/anvil
v0.1.0
Published
Anvil Connector — bridges lab instruments to the hosted Anvil web app via WebSocket. Discovers VISA instruments, executes SCPI commands, and includes a 5-instrument simulator for development.
Maintainers
Readme
Anvil Connector
A lightweight connector agent that runs on your desktop and bridges lab instruments to the hosted Anvil web app at anvil.sohumsuthar.com.
How It Works
The connector discovers VISA-compatible instruments on your machine using PyVISA, then maintains a persistent WebSocket connection to the Anvil cloud. When the web app sends a SCPI command, the connector executes it locally against the target instrument and returns the result.
┌─────────────┐ WebSocket ┌──────────────────┐ SCPI/VISA ┌─────────────┐
│ Anvil Web │ ◄──────────────► │ Anvil Connector │ ◄──────────────► │ Instruments │
│ (Cloud) │ │ (Local Agent) │ │ (Lab) │
└─────────────┘ └──────────────────┘ └─────────────┘Prerequisites
- Node.js 20+ — nodejs.org
- Python 3 — for instrument discovery (optional)
- PyVISA —
pip3 install pyvisa pyvisa-py(optional)
Simulation Mode
If Python or PyVISA is not installed, the connector automatically runs in simulation mode with 5 dummy instruments (VNA, PSU, DMM, Scope, SigGen). Each dummy returns physically plausible measurements and responds to the full SCPI command set. No PyVISA installation is needed to develop against Anvil or test workflows.
Quick Start
# Install
npm install
npm link # makes "anvil" available globally
# First-time setup (saves credentials + starts):
anvil setup --id <CONNECTOR_ID> --secret <CONNECTOR_SECRET>
# Check status:
anvil status
# Or run manually in foreground (debug):
anvil runAuthentication Flow
First-time setup
- Register a connector from the Anvil web UI (Settings > Connectors).
- Run
anvil setup --id <CONNECTOR_ID> --secret <SECRET>. - Credentials are saved to
~/.anvil/connector.json.
Ongoing authentication
- The connector loads
connectorId+connectorSecretfrom the config file. - Heartbeat and poll requests use
Authorization: Bearer <connectorId>:<connectorSecret>. - HTTP polling is the primary transport (works on Vercel). WebSocket is a legacy fallback.
- No session token is involved.
Alternative: direct credentials
If you already have a connector ID and secret (e.g., from the web dashboard):
anvil setup --id <ID> --secret <SECRET>This saves credentials to ~/.anvil/connector.json and starts the connector.
CLI
After npm link, the anvil command is available globally:
anvil <command>
start Start the connector in the background
stop Stop the running connector
status Check connector status
setup --id <id> --secret <secret>
First-time setup with credentials
run Run connector in foreground (debug)
uninstall Remove background service registration
help Show helpConfig is loaded from ~/.anvil/connector.json automatically.
Advanced: anvild options
anvild [OPTIONS]
--port <PORT> Local HTTP server port (default: 7400)
--token <TOKEN> Session token for initial registration only
--connector-id <ID> Connector ID (from prior registration)
--connector-secret <SECRET> Connector secret (from prior registration)
--cloud-url <URL> Cloud API base URL (default: https://anvil.sohumsuthar.com)
--config <PATH> Path to config JSON file (default: ~/.anvil/connector.json)
--simulation, --sim Force simulation mode (5 dummy instruments, skip PyVISA)
-h, --help Show help
-v, --version Show versionEnvironment Variables
| Variable | Description |
|----------|-------------|
| ANVIL_TOKEN | Session token for initial registration (same as --token) |
| ANVIL_CONNECTOR_ID | Connector ID (same as --connector-id) |
| ANVIL_CONNECTOR_SECRET | Connector secret (same as --connector-secret) |
| ANVIL_PORT | Local server port (same as --port) |
| ANVIL_CLOUD_URL | Cloud URL (same as --cloud-url) |
| ANVIL_SIMULATION | Force simulation mode (1 or true, same as --simulation) |
Config File
After registration, ~/.anvil/connector.json looks like:
{
"connectorId": "clxyz...",
"connectorSecret": "a1b2c3d4...",
"port": 7400,
"cloudUrl": "https://anvil.sohumsuthar.com",
"heartbeatInterval": 30000,
"scanInterval": 30000
}Note: The connectorSecret is sensitive. Keep this file secure.
Local Endpoints
The connector runs a local HTTP server (default port 7400):
| Endpoint | Description |
|----------|-------------|
| GET /health | Health check: { status, instruments, uptime, cloudConnected } |
| GET /instruments | List discovered instruments |
| GET /status | Detailed status including memory usage |
Architecture
src/
cli.js Unified CLI entry point (anvil start/stop/status/setup/run)
index.js Connector daemon, orchestrates all components
setup.js Background process management (start/stop/status/uninstall)
config.js Configuration from CLI flags + env + config file
auth.js Registration (session token) + credential management
instrument-scanner.js Discovers VISA instruments via PyVISA, enriches with name/type from *IDN?
scpi-bridge.js Executes SCPI commands (real or simulated)
cloud-client.js WebSocket client with reconnect logic
heartbeat.js Periodic heartbeat with device inventory
server.js Local HTTP health/status server
stateful-simulator.js Stateful instrument simulator (used when PyVISA unavailable)WebSocket Protocol
Messages between the connector and cloud are JSON:
Connector -> Cloud (authentication):
{ "type": "authenticate", "connectorId": "...", "secret": "..." }Cloud -> Connector (auth response):
{ "type": "authenticated", "connectorId": "..." }Cloud -> Connector:
{ "type": "command_request", "id": "uuid", "address": "TCPIP::...", "command": "*IDN?" }
{ "type": "list_instruments", "id": "uuid" }
{ "type": "ping", "id": "uuid" }Connector -> Cloud:
{ "type": "command_response", "id": "uuid", "ok": true, "result": "Keysight..." }
{ "type": "instrument_list", "id": "uuid", "instruments": [...] }
{ "type": "pong", "id": "uuid" }Start / Stop
anvil start
anvil stop
anvil statusLogs (macOS/Linux): ~/.anvil/logs/connector.log
Dummy Instruments
When no real instruments are detected (or PyVISA is not installed), the connector provides 5 dummy instruments that emulate real popular models:
| Type | Model | Key Specs | |------|-------|-----------| | VNA | Keysight E5071C | 9kHz–8.5GHz, 2-port | | PSU | Rigol DP832 | 3ch: 30V/3A, 30V/3A, 5V/3A | | DMM | Keysight 34465A | 6.5 digit, 30ppm DCV | | Scope | Rigol DS1054Z | 50MHz, 4ch, 1GSa/s | | SigGen | Rigol DG1022Z | 25MHz, 2ch, 200MSa/s |
Each returns its real *IDN? string with accurate firmware version format and responds to the full SCPI command set with physically plausible measurements.
