Skip to main content

nspmctl

A thin, fast CLI over nanonis-spm for agent-driven Nanonis SPM controller automation (real controller or STM Simulator).

nspmctl runs as either a short-lived one-shot command or, by default, a thin client that forwards to a persistent background daemon. The daemon holds one warm NanonisController and the open TCP connection to the instrument, so subsequent agent tool calls converge toward loopback IPC + Python startup latency instead of paying the import + connect cost every time.

What this provides

  • nspmctl: atomic CLI commands (get / set / ramp / act / capabilities / policy / doctor / daemon ...) with stable JSON output schemas.
  • nspmctl daemon: persistent warm-controller daemon, auto-spawned on first call.
  • Strict write semantics:
    • set is always a guarded single-step write.
    • ramp is always an explicit multi-step trajectory.
  • Default runtime policy: allow_writes=true, dry_run=false.

Performance

End-to-end get bias_v against the STM Simulator on a developer laptop:

Mode p50
nspmctl --no-daemon get bias_v 525 ms
nspmctl get bias_v (warm daemon) 105 ms
raw nanonis_spm one-shot floor 110 ms

The daemon path approaches the raw nanonis_spm floor; agents that make many tool calls amortize the import + TCP-connect cost almost entirely.

v0.2 support contract

  • Stable CLI surface: documented nspmctl subcommands and JSON outputs.
  • Stable Python symbols for embedding: nspmctl.client.create_client, nspmctl.config.load_settings.
  • Other Python symbols are provisional and may change across minor releases.

Install

pip install nspmctl
nspmctl capabilities

That's it. nanonis-spm and numpy come along as transitive dependencies, and a parameter manifest is bundled inside the package.

Configure

Defaults work out of the box against the Nanonis STM Simulator on 127.0.0.1 (ports 3364, 6501-6504, allow_writes=true, dry_run=false). To override, use either environment variables or a runtime YAML.

Common environment variables:

Variable Default Purpose
NANONIS_HOST 127.0.0.1 Controller host
NANONIS_PORTS 3364,6501,6502,6503,6504 Candidate TCP ports
NANONIS_TIMEOUT_S 2.0 Per-command timeout
NANONIS_ALLOW_WRITES true Master gate for set / ramp / act
NANONIS_DRY_RUN false Plan writes but do not apply
NSPMCTL_NO_DAEMON unset Set to 1 to disable the warm daemon

For richer config (per-channel slew limits, custom backend selection), point --config-file path/to/runtime.yaml at any subcommand or set NANONIS_CONFIG_FILE. See nspmctl doctor for the live resolved view.

The parameter manifest (which get / set / act / ramp target) is shipped inside the package at nspmctl/resources/config/parameters.yaml and is loaded automatically. To extend or restrict it, pass --parameters-file your-manifest.yaml.

Daemon

# Auto-managed: the first cold call spawns a daemon in the background,
# subsequent calls are warm.
nspmctl get bias_v

# Explicit lifecycle (optional):
nspmctl daemon start
nspmctl daemon status
nspmctl daemon stop
nspmctl daemon restart
nspmctl daemon logs --tail 80

# Diagnostics / CI: bypass the daemon entirely.
nspmctl --no-daemon get bias_v
# or:
$env:NSPMCTL_NO_DAEMON = "1"; nspmctl get bias_v

The daemon exits automatically after 30 minutes of idle. PID + port + log files live under %LOCALAPPDATA%\nspmctl\ on Windows (~/.local/state/nspmctl/ on Linux/macOS).

CLI command guide (nspmctl)

Inspect and introspect

Get the machine-readable execution contract (lean payload):

nspmctl capabilities

Capabilities item schemas (nspmctl capabilities):

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://bb-84c.github.io/nspmctl/schemas/capabilities-parameter-item.schema.json",
  "title": "nspmctl capabilities parameters.items[*]",
  "type": "object",
  "required": [
    "name",
    "label",
    "readable",
    "writable",
    "has_ramp",
    "get_cmd",
    "set_cmd",
    "safety"
  ],
  "properties": {
    "name": { "type": "string", "minLength": 1 },
    "label": { "type": "string" },
    "readable": { "type": "boolean" },
    "writable": { "type": "boolean" },
    "has_ramp": { "type": "boolean" },
    "get_cmd": {
      "oneOf": [
        { "type": "null" },
        {
          "type": "object",
          "required": ["command", "payload_index", "arg_fields", "response_fields"],
          "properties": {
            "command": { "type": "string", "minLength": 1 },
            "payload_index": { "type": "integer", "minimum": 0 },
            "description": { "type": "string" },
            "arg_fields": {
              "type": "array",
              "items": {
                "type": "object",
                "required": [
                  "name",
                  "type",
                  "unit",
                  "wire_type",
                  "required",
                  "description",
                  "default"
                ],
                "properties": {
                  "name": { "type": "string", "minLength": 1 },
                  "type": { "type": "string" },
                  "unit": { "type": "string" },
                  "wire_type": { "type": "string" },
                  "required": { "type": "boolean" },
                  "description": { "type": "string" },
                  "default": {}
                },
                "additionalProperties": false
              }
            },
            "response_fields": {
              "type": "array",
              "items": {
                "type": "object",
                "required": ["index", "name", "type", "unit", "wire_type", "description"],
                "properties": {
                  "index": { "type": "integer", "minimum": 0 },
                  "name": { "type": "string", "minLength": 1 },
                  "type": { "type": "string" },
                  "unit": { "type": "string" },
                  "wire_type": { "type": "string" },
                  "description": { "type": "string" }
                },
                "additionalProperties": false
              }
            }
          },
          "additionalProperties": true
        }
      ]
    },
    "set_cmd": {
      "oneOf": [
        { "type": "null" },
        {
          "type": "object",
          "required": ["command", "arg_fields"],
          "properties": {
            "command": { "type": "string", "minLength": 1 },
            "description": { "type": "string" },
            "arg_fields": {
              "type": "array",
              "items": {
                "type": "object",
                "required": [
                  "name",
                  "type",
                  "unit",
                  "wire_type",
                  "required",
                  "description",
                  "default"
                ],
                "properties": {
                  "name": { "type": "string", "minLength": 1 },
                  "type": { "type": "string" },
                  "unit": { "type": "string" },
                  "wire_type": { "type": "string" },
                  "required": { "type": "boolean" },
                  "description": { "type": "string" },
                  "default": {}
                },
                "additionalProperties": false
              }
            }
          },
          "additionalProperties": true
        }
      ]
    },
    "safety": {
      "oneOf": [
        { "type": "null" },
        {
          "type": "object",
          "required": [
            "min_value",
            "max_value",
            "max_step",
            "max_slew_per_s",
            "cooldown_s",
            "ramp_enabled",
            "ramp_interval_s"
          ],
          "properties": {
            "min_value": { "type": ["number", "null"] },
            "max_value": { "type": ["number", "null"] },
            "max_step": { "type": ["number", "null"] },
            "max_slew_per_s": { "type": ["number", "null"] },
            "cooldown_s": { "type": ["number", "null"] },
            "ramp_enabled": { "type": "boolean" },
            "ramp_interval_s": { "type": ["number", "null"] }
          },
          "additionalProperties": false
        }
      ]
    }
  },
  "additionalProperties": false
}
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://bb-84c.github.io/nspmctl/schemas/capabilities-action-command-item.schema.json",
  "title": "nspmctl capabilities action_commands.items[*]",
  "type": "object",
  "required": ["name", "action_cmd", "safety_mode"],
  "properties": {
    "name": { "type": "string", "minLength": 1 },
    "action_cmd": {
      "type": "object",
      "required": ["command", "arg_fields"],
      "properties": {
        "command": { "type": "string", "minLength": 1 },
        "description": { "type": "string" },
        "arg_fields": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "name",
              "type",
              "unit",
              "wire_type",
              "required",
              "description",
              "default"
            ],
            "properties": {
              "name": { "type": "string", "minLength": 1 },
              "type": { "type": "string" },
              "unit": { "type": "string" },
              "wire_type": { "type": "string" },
              "required": { "type": "boolean" },
              "description": { "type": "string" },
              "default": {}
            },
            "additionalProperties": false
          }
        }
      },
      "additionalProperties": true
    },
    "safety_mode": {
      "type": "string",
      "enum": ["alwaysAllowed", "guarded", "blocked"]
    }
  },
  "additionalProperties": false
}

Show the legacy full payload (old capabilities surface):

nspmctl showall

Inspect backend command inventory and connectivity preflight:

nspmctl backend commands --match Scan
nspmctl doctor --command-probe

List observable metadata and high-level CLI action descriptors:

nspmctl observables list
nspmctl actions list

Inspect and update runtime policy:

nspmctl policy show
nspmctl policy set --allow-writes true --dry-run false

Execute operations

Read a parameter:

nspmctl get bias_v

For multi-field responses, get returns structured fields (not only one scalar):

nspmctl get scan_buffer

Apply writes with structured args (canonical form):

nspmctl set bias_v --arg Bias_value_V=0.12 (single arg input)
nspmctl set scan_buffer --arg Pixels=512 --arg Lines=512 (multiple args input)

Defaulting/autofill mechanism for partial set:

  • Explicit --arg values always win.
  • Missing required set fields trigger one read (get_cmd) and are filled by normalized field name.
  • Matching is by field name, not response index position.
  • Get-only fields with no set counterpart are ignored.
  • Remaining unresolved optional fields can fall back to manifest defaults.

Apply explicit guarded ramp (scalar parameters):

nspmctl ramp bias_v 0.10 0.25 0.01 --interval-s 0.10

Invoke one manifest action command with structured args:

nspmctl act Scan_Action --arg Scan_action=0 --arg Scan_direction=1
nspmctl act Scan_WaitEndOfScan --arg Timeout_ms=5000

For act, required/default behavior is driven by action_cmd.arg_fields in the manifest.

act vs metadata surfaces

  • nspmctl act <action_name> --arg key=value executes one backend action command from the manifest actions section.
  • nspmctl actions list lists CLI-level action descriptors (what workflows the CLI supports, with safety hints and templates).
  • nspmctl capabilities exposes executable manifest action inventory under action_commands.items[*] (command schema, arg_fields, safety mode).

Output and help

JSON is the default output format. Use --text for human-readable key/value output.

nspmctl -help
nspmctl -help showall
nspmctl -help set
nspmctl -help act

Embedded Python usage

The CLI is the primary surface, but NanonisController is importable for notebooks and embedding scenarios:

from nspmctl.controller import NanonisController

nanonis = NanonisController("nanonis_demo", auto_connect=True)
try:
    print(nanonis.get_parameter_value("bias_v"))
    nanonis.set_parameter_fields("bias_v", args={"Bias_value_V": 0.15})
    report = nanonis.ramp_parameter(
        "bias_v",
        start_value=0.15,
        end_value=0.25,
        step_value=0.01,
        interval_s=0.05,
    )
    print(report)
finally:
    nanonis.close()

This bypasses the daemon and pays the full import + connect cost on construction, so prefer the CLI for hot loops and reach for the Python API when you need to compose with custom Python tooling.

Developing

git clone https://github.com/BB-84C/NanonisSPMController-CLI.git
cd NanonisSPMController-CLI
pip install -e ".[dev]"
pytest
ruff check .
black --check .
mypy nspmctl

Documentation index

  • CLI contract: docs/cli_contract.md
  • Extension workflow: docs/extension_workflow.md
  • Safety model: docs/safety_model.md
  • Architecture overview: docs/architecture.md
  • Simulator quickstart: docs/quickstart_simulator.md
  • Porting to real controller: docs/porting_to_real_controller.md

Metadata

Release files for nspmctl 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nspmctl 0.3.0
File Size Uploaded Provenance
nspmctl-0.3.0.tar.gz 150.2 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for nspmctl 0.3.0
File Interpreter ABI Platform
nspmctl-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 284.5 kB

Release files / nspmctl-0.3.0.tar.gz

Download URL nspmctl-0.3.0.tar.gz
Size 150.2 kB
Tags Source
SHA-256 checksum
How to use checksums
b5d503c966c8f588f35f5d186b8dbdb7360a60d91b02c662ead59df3e3f75093
BLAKE2b-256 checksum
How to use checksums
eec76ad8085fb21342da5f3fb31f976641791d694b9ebead0045383c12adac4c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.5

Release files / nspmctl-0.3.0-py3-none-any.whl

Download URL nspmctl-0.3.0-py3-none-any.whl
Size 134.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2b979b5ab657a0e5925ed185ad4844191c7f09d23913b205c5ffda6d2a91ac9b
BLAKE2b-256 checksum
How to use checksums
a16c00eac43601502691845fad92118ebc732d6390a6a6acb77a1921d7fb4908
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.5

Security for nspmctl 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page