Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Project Management with acctl

acctl is the command-line tool for managing AutoCore projects. It handles project creation, deployment, monitoring, and sending commands to the server and its modules.

Configuration

acctl reads server connection settings from acctl.toml in the project directory (created by acctl clone or acctl set-target), falling back to ~/.acctl.toml for global defaults.

[server]
host = "192.168.1.100"
port = 11969

[build]
release = true

Global flags --host and --port override all config files for a single invocation:

acctl --host 192.168.1.200 status

acctl Command Reference

Project Creation

CommandDescription
acctl new <name>Create a new project from the standard template (Rust control program + React web UI)
acctl new-tis-project <name>Create a new project pre-wired with TIS (<TisProvider> + tick_with_autostart).
acctl clone <host> [project] [-P port] [-d dir]Clone a project from a remote server
acctl clone <host> --listList available projects on a server
acctl new my_machine
acctl clone 192.168.1.100 my_machine
acctl clone 192.168.1.100 --list

Subsystem Retrofit

Two idempotent commands flip subsystems on for an existing project:

CommandDescription
acctl add-tisAdds an empty test_methods: {} block to project.json so Project::normalize() injects the nine tis_* GM scalars next time codegen runs. See Chapter 15.
acctl add-amsAdds an empty asset_types: {} block to project.json so the three baseline ams_* GM scalars are injected and <AmsProvider> has something to mount against. See Chapter 16.
acctl add-axis --name <N> [--link <slave>] [--type pp] [--backend ethercat|virtual]Adds a CiA-402 axis to project.json. EtherCAT axes (--link <slave>) go in modules.ethercat.config.axes; --backend virtual creates a fieldbus-less simulated axis in modules.motion.config.axes. Idempotent on --name. Run acctl codegen afterward. See Chapter 8b.

Run them in any order on a project that started without those subsystems; re-running on a project that already has them is a no-op that prints “already enabled” and exits 0.

AMS Backup / Restore

CommandDescription
acctl ams export --output <file.json>Pull the full Asset Management System dataset (registry + per-asset history + usage) from the server into a single JSON document.
acctl ams import --input <file.json> [--dry-run]Apply an exported document to the current server. Default behaviour merges (preserves IDs, appends calibrations, takes max-of usage counters). --dry-run previews changes.

See Chapter 16 for the export shape and merge semantics.

Project Inspection

CommandDescription
acctl infoShow a human-readable project summary (modules, variables, control program, www status)
acctl validateCheck project.json for errors. Runs both the local cheap checks (syntax, variable types, duplicate names, broken links) and — if a server is reachable — the full server-side validator (AMS placeholder resolution, AMS registry/asset integrity, module schema).
acctl statusShow server status, control program state, and project list (requires server connection)
acctl info         # Local — reads project.json, no server needed

Offline Code Generation

The autocore_server executable can generate the gm.rs and results.ts files directly without needing to start the full system (useful for CI/CD or development machines lacking physical hardware like EtherCAT).

# Generate gm.rs and results.ts offline
cargo run --bin autocore_server -- --generate /path/to/project.json

This bypasses the background servelets and exits immediately with code 0 on success. acctl validate # Local — checks for errors before deploying acctl status # Remote — queries the running server


`acctl validate` runs two passes:

**Local pass** (always runs):
- JSON syntax
- Required fields on variables (`type`)
- Valid type values (`f64`, `bool`, `u64`, etc.)
- Duplicate variable names
- Variable `link` targets reference configured module domains

**Server pass** (when a server is reachable — `system.validate_project` IPC command):
- `project_schema` — the file deserialises as a `Project`
- `module_schema` — each module entry has valid `enabled` / `args` / `executable` shape
- `ams.placeholder` — every `${ams.*}` placeholder anywhere in the project resolves against the server's AMS registry. **Warning-severity** (see "Severities" below).
- `ams.registry`, `ams.asset`, `ams.calibration` — the on-disk AMS state itself is well-formed
- `ams.asset_type`, `ams.asset_ref` — project asset_type entries don't shadow built-ins and asset_refs target known types
- `variable_duplicate` — two or more variables share the same hardware `link` (mirrors `acctl dedup-vars`)
- `variable_link_shape` — non-empty `link` values look like a dotted FQDN

If no server is reachable, the server pass is skipped with a `Note:` line and only the local pass counts. Connect to a server (`acctl set-target …`) before relying on this for AMS-aware checks.

The same server-side validator runs automatically as a pre-flight on:
- `acctl sync` when you push your local file to the server — error-severity findings block the push; warnings are shown and the push proceeds.
- `acctl codegen` before any code is generated — broken AMS placeholders don't get baked into `gm.rs`.

#### Severities

Each finding carries one of two severities:

- **`error`** (default for nearly every category) — blocks `acctl sync push` and `acctl codegen`. Indicates a structural problem (malformed JSON, broken schema, missing AMS registry, duplicate IDs, cross-module link typos).
- **`warning`** — surfaces in acctl output but does not block. Currently only `ams.placeholder` emits at this severity. The reasoning is that an unresolved AMS placeholder reflects asset-record state that the operator fixes through the AIS UI; blocking sync would leave them with no way to land the project the UI needs.

The `acctl sync` push output partitions findings:

Project validation warnings (not blocking):

ams.placeholder (18) /modules/ni/config/tasks/0/channels/0/create_args/max_val asset LC-... at location tsdr1 has no field capacity (looked under custom.capacity) …

Project validation: OK (18 warning(s); fix in the AIS UI when convenient)


A mix of warnings + errors prints warnings first in yellow, errors below in red, and exits non-zero on the errors.

> **Why warnings exist.** Before this split, a stale asset (missing
> field, wrong type) blocked the project from landing at all. The
> operator would need to edit asset.json by hand on the server to
> recover. Now the project lands, the AIS UI on the (now-up) server
> exposes the bad asset, the operator fixes it there. The module
> supervisor still refuses to spawn modules whose configs contain
> unresolved placeholders at runtime — `UnresolvedAmsPlaceholders` —
> so the eventual hardware path is still gated on the asset being
> correct. The warning surfaces the same information, just earlier
> and without locking the operator out of the UI.

#### Server Configuration

| Command | Description |
|---|---|
| `acctl set-target <host> [--port PORT]` | Save the server address to `acctl.toml` |
| `acctl switch <project> [--restart]` | Switch the active project on the server |

#### Deployment

| Command | Flags | Description |
|---|---|---|
| `acctl push project` | `--restart` | Upload project.json to the server |
| `acctl push www` | `--no-build`, `--source` | Build (`npm run build`) and upload web HMI. `--no-build` skips the build. `--source` pushes full `www/` instead of `www/dist/`. |
| `acctl push control` | `--start`, `--no-build`, `--source`, `--force` | Build (`cargo build`) and upload the control binary. `--start` starts it after upload. `--source` pushes full source for remote build. `--force` skips project.json sync check. |
| `acctl push doc` | `--no-build` | Build (`acctl doc build`) and upload the generated documentation. `--no-build` uploads an existing `doc/book/` without rebuilding (fails if missing). |
| `acctl push assets` | `--no-reinit` | Publish local AMS data (`datastore/assets/` — registry, per-asset records, calibration history, usage) to the server. AMS records are machine-local, so `acctl sync` never auto-pushes them; this is the deliberate publish path. Calls `ams.reinitialize` after upload so the running server reloads from disk; `--no-reinit` skips that (restart the server yourself). |
| `acctl pull` | `--extract` | Download the active project as a zip |
| `acctl upload <file>` | `--dest PATH` | Upload an arbitrary file to the project directory (default: `lib/<filename>`) |

```bash
# Typical deploy workflow
acctl push project
acctl push www
acctl push control --start

# Or individually with options
acctl push www --no-build        # Skip npm build, push existing dist/
acctl push control --no-build    # Skip cargo build, push existing binary

Control Program Lifecycle

CommandDescription
acctl control startStart the control program
acctl control stopStop the control program
acctl control restartRestart the control program
acctl control statusShow control program state and PID

Monitoring

CommandDescription
acctl statusServer status, control program state, project list
acctl logsShow recent control program log output
acctl logs --followStream logs in real time (colorized by level)

Log levels are colorized: ERROR (red), WARN (yellow), INFO (green), DEBUG (blue), TRACE (dimmed).

Code Generation and Sync

CommandDescription
acctl codegenRegenerate control/src/gm.rs from the server’s project.json (shared memory bindings). Requires a running server. Also scaffolds axis output/option variables (see Chapter 8b) and, before generating, checks that control/Cargo.lock’s autocore-std is new enough for the codegen output — aborting with the exact cargo update --precise to run if it’s too old, rather than letting you hit a cryptic Rust error.
acctl codegen-tags [--force]Regenerate www/src/AutoCoreTags.ts from the local project.json. Pure local operation — no server connection needed.
acctl syncCompare local vs server project.json interactively (pull, push, or skip per-section; auto-runs codegen after), then pull the critical datastore files: autocore_gnv.ini and assets/.
acctl sync allSame project.json reconcile, then a full mtime-wins sync of the entire datastore/ tree (excluding results/). datastore is accepted as an alias for all.
acctl diff(Planned) Show what would change on push

After adding or removing variables in project.json, always run acctl codegen to update the Rust shared memory bindings before rebuilding the control program.

The plain acctl sync deliberately skips the bulk of the datastore (captures/, scripts/, …): on long-running projects those grow to hundreds of files and make every sync slow — especially over remote tailscale links — when usually you only want the project file and the machine-critical state backed up. Run acctl sync all when you do want the whole tree; transfers are batched into ~8 MiB requests so large datastores no longer overflow a single websocket message.

acctl sync all reconciles the datastore/ directory mtime-wins, but two kinds of file are pull-only — sync brings a fresher server copy down but never pushes the local copy up:

  • datastore/autocore_gnv.ini — non-volatile values written by the running control program. Restore deliberately with acctl push gnv.
  • datastore/assets/ — AMS asset and calibration records. These are machine-local (the transducer actually installed in this machine, its cert history, usage counters); the shared project.json is the same on every machine but this data is not. Auto-pushing would let one machine overwrite another’s assets on the shared server. Publish deliberately with acctl push assets.

results/ is excluded from sync entirely (pull deliberately with acctl pull-results). Everything else under datastore/ (e.g. scripts/, captures/) syncs both ways under acctl sync all.

acctl codegen-tags — web UI tag generation

Each variable in project.json supports an optional boolean field ux. When "ux": true, acctl codegen-tags emits a record for that variable into the generated block of www/src/AutoCoreTags.ts, giving the React web UI a typed handle (tagName, fqdn, valueType) for subscriptions and controls. Variables without ux: true are ignored.

"variables": {
  "lift_axis_position": { "type": "f64", "ux": true,  "description": "Lift axis position (mm)" },
  "internal_watchdog":  { "type": "u32", "ux": false, "description": "Never shown in HMI" },
  "req_start_auto":     { "type": "bool", "ux": true }
}

Type mapping from project.json to TypeScript’s valueType:

project.json typeTS valueType
bool"boolean"
u8u64, i8i64, f32, f64"number"
string"string"
anything else(warns and skips)

Tag names are derived from the variable name by snake_case → camelCase conversion (lift_axis_positionliftAxisPosition). The FQDN is always gm.<variable_name>.

Output file layout

The generated file contains two arrays combined into the exported acTagSpec:

// autocore-codegen:generated-start
// DO NOT EDIT: this block is regenerated by `acctl codegen-tags`.
export const acTagSpecGenerated = [
    { "tagName": "liftAxisPosition", "fqdn": "gm.lift_axis_position", "valueType": "number" },
    { "tagName": "reqStartAuto",     "fqdn": "gm.req_start_auto",     "valueType": "boolean" },
    // ... one record per variable with ux: true ...
] as const satisfies readonly TagConfig[];
// autocore-codegen:generated-end

// Hand-written tags and per-tag overrides — safe to edit.
export const acTagSpecCustom = [
    { tagName: "liftPosition", fqdn: "gm.lift_axis_position", valueType: "number",
      subscriptionOptions: { sampling_interval_ms: 300 }, scale: "position" },
    // ...
] as const satisfies readonly TagConfig[];

export const acTagSpec = [
    ...acTagSpecGenerated,
    ...acTagSpecCustom,
] as const satisfies readonly TagConfig[];

Put anything that needs subscriptionOptions, scale, or any other TagConfig property into acTagSpecCustom — the generated block only carries the three basic fields. The sentinel comments // autocore-codegen:generated-start and // autocore-codegen:generated-end delimit the replaceable region.

Regeneration behavior

On each run, acctl codegen-tags decides whether to replace only the generated block or rewrite the whole file:

SituationAction
www/src/AutoCoreTags.ts doesn’t existWrite full file from template.
File exists, has both sentinel comments and acTagSpecCustomReplace only the generated block; acTagSpecCustom is preserved.
File exists but missing a sentinel or acTagSpecCustomFull rewrite from template. Old file is saved to AutoCoreTags.ts.bak.
--force is passedFull rewrite regardless. Old file saved to .bak.

A .bak sibling is only ever produced when the tool actually overwrites a hand-edited file — routine in-place updates leave nothing on disk besides the new AutoCoreTags.ts.

Workflow
# Mark variables for the UI in project.json (editor or acctl import-vars)
# Then:
acctl codegen-tags            # → www/src/AutoCoreTags.ts
cd www && npm run dev         # React picks up the updated tag list immediately

Run codegen-tags any time you flip ux on/off or add/rename variables. The React side has no caching — refreshing the dev server or the built app picks up the new list on next load.

Variable Management

CommandDescription
acctl export-vars [--output FILE]Export variables to CSV (default: variables.csv)
acctl import-vars [--input FILE]Import variables from CSV (default: variables.csv)
acctl dedup-varsFind and interactively resolve variables with duplicate hardware links

CSV columns: name, type, link, description, initial.

acctl export-vars --output variables.csv
# Edit in spreadsheet...
acctl import-vars --input variables.csv
acctl dedup-vars   # Check for conflicts

Project Documentation

Every project created by acctl new includes a doc/ directory with an mdBook-based user manual. The acctl doc subcommands build, serve, and keep that manual in sync with project.json and the control program source.

CommandFlagsDescription
acctl doc init--forceScaffold doc/ (book.toml + the five starter Markdown files) in an existing project. Skips files that already exist; --force overwrites. Use this to add the doc directory to projects created before acctl doc support.
acctl doc buildBuild static HTML output at doc/book/. Runs generate-vars and cargo doc automatically.
acctl doc serve--port PORT (default 4444)Serve the book locally with live reload.
acctl doc generate-varsRegenerate doc/src/variables.md from project.json (hardware-linked / bit-mapped / plain tables).
acctl doc cleanRemove doc/book/ and doc/src/rustdoc/.
acctl doc init                     # Scaffold doc/ if the project doesn't have one yet
acctl doc serve                    # http://localhost:4444 with live reload
acctl doc serve --port 8080        # Custom port
acctl doc build                    # One-shot build → doc/book/index.html

New projects created by acctl new already contain a scaffolded doc/, so you only need acctl doc init when retrofitting an older project or when you’ve deleted doc/ and want to start over. The command reads the project name from project.json to populate the book title and introduction page, and never overwrites files by default — safe to run repeatedly.

On first use, if mdbook is not on your PATH, acctl installs it automatically via cargo install mdbook --locked (one-time, ~60s). cargo doc ships with every Rust toolchain, so no additional installation is needed for the Rustdoc section.

Distribution

The output at doc/book/ is a self-contained static site. You have three distribution options:

  1. Push to the serveracctl push doc builds the book and uploads it to the active project on the server. autocore-server automatically serves the active project’s documentation on its documentation port (default 4444, configurable in config.ini):

    http://<server-ip>:4444/
    

    Operators get up-to-date docs as a side effect of deployment — no separate hosting required. When no documentation has been pushed, the port serves a placeholder page explaining how to run acctl push doc. Switching the active project on the server automatically swaps the served docs to the new project’s book.

  2. Zip and sharedoc/book/ is fully self-contained. Zip it, email it, or drop it on a shared drive. Recipients unzip and open index.html directly from the filesystem.

  3. Host elsewhere — Push doc/book/ to any static web host (GitHub Pages, S3, internal nginx, etc.). All links are relative.

Configuring the documentation port

The server reads the doc port from config.ini:

[general]
port = 80          # Main HMI port
doc_port = 4444    # Documentation port (this)

Omit doc_port to use the default of 4444. Set it to a different value if 4444 is already in use on the target.

Note: As of this writing, autocore-server’s HTTP endpoints — including port 4444 — are unauthenticated. If your project documentation contains sensitive information, keep the server on a trusted network until the forthcoming authentication gate ships.

Sending Commands to Modules

CommandDescription
acctl cmd <topic> [args...]Send a command to the server (same as the AutoCore console)

The topic format is domain.command. Arguments are parsed as --key value pairs. Values are auto-detected as numbers, booleans, JSON objects/arrays, or strings.

# System commands
acctl cmd system.get_domains
acctl cmd system.list_modules
acctl cmd system.load_module --name ni
acctl cmd system.new_project --project_name my_machine

# Global Memory (read/write variables)
acctl cmd gm.read --name motor_speed
acctl cmd gm.write --name motor_speed_setpoint --value 1500

# Module commands (NI example)
acctl cmd ni.status
acctl cmd ni.describe
acctl cmd ni.add_channel --task AnalogInput --name ai0 --physical_channel Dev1/ai0 --type voltage
acctl cmd ni.save_config --generate_variables true

# Any module that implements CommandRegistry
acctl cmd modbus.status
acctl cmd labelit.camera_start --ip 192.168.1.50

Managing Tools and Editors

Tools and editors (such as labelit-studio) are registered with the server through the tool registry. acctl can list them and trigger a live rescan — see Tools and Editors for the full picture.

CommandDescription
acctl tools listList registered tools with running state, URL, and the module domains each edits
acctl tools rescanRe-read the registry and start/stop service tools without a server restart
acctl tools list
acctl tools rescan   # package install/uninstall scripts call this automatically

Working with Multiple Projects

Each AutoCore server can host multiple projects, but only one is active at a time.

acctl status                                    # See all projects and which is active
acctl switch other_project --restart            # Switch to a different project
acctl cmd system.new_project --project_name new_machine  # Create a new project on the server

Deploying to a Remote Server

# 1. Set the target server (saved to acctl.toml)
acctl set-target 192.168.1.100

# 2. Verify the connection
acctl status

# 3. Validate before deploying
acctl validate

# 4. Deploy
acctl push project
acctl push www
acctl push control --start

# 5. Monitor remotely
acctl logs --follow

Importing and Exporting Variables

For large projects, manage variables in a spreadsheet and import them:

acctl export-vars --output variables.csv
# Edit the CSV in your spreadsheet application...
acctl import-vars --input variables.csv
acctl dedup-vars   # Resolve any duplicate links

The CSV format has these columns: name, type, link, description, initial.

Writing Project Documentation

The project’s doc/ directory is an mdBook — the same tool used for this manual. Source files live in doc/src/ as Markdown; the table of contents is doc/src/SUMMARY.md.

Default layout (created by acctl new, or by acctl doc init on an existing project):

doc/
├── book.toml              # mdBook configuration
└── src/
    ├── SUMMARY.md         # Table of contents
    ├── introduction.md    # Edit this — your project overview
    ├── variables.md       # Auto-generated — do not edit
    └── control_api.md     # Links to the Rustdoc section

Retrofitting older projects. If your project was created before acctl doc support and doc/book.toml doesn’t exist, run acctl doc init from the project root. It scaffolds the same five files that acctl new would have produced, pulling the project name from project.json for the book title. Existing files are left untouched; pass --force to overwrite.

What Gets Auto-Generated

Two parts of the book are regenerated every time you run acctl doc build or acctl doc serve — do not edit them by hand:

  • doc/src/variables.md — a grouped FQDN table of every entry in project.json’s variables map. Three sections are emitted when non-empty: Hardware-Linked (entries with a link field), Bit-Mapped (entries with source + bit), and Other. Columns include FQDN, type, description, and the relevant linkage fields.
  • doc/src/rustdoc/ — a copy of cargo doc --no-deps output from control/. The default control_api.md chapter links into rustdoc/index.html. Doc comments (///) in your control program source become a browsable API reference.

Typical Authoring Workflow

# Start the live-reload server while you write
acctl doc serve

# In another shell, edit doc/src/introduction.md and any other chapters
# Add new chapters by creating new .md files and listing them in SUMMARY.md

# Once happy, produce a static build to hand off
acctl doc build
# → doc/book/index.html

Because generate-vars and cargo doc run on every build, changes to project.json variables or doc comments in control/ appear in the book without any extra step.

Distribution

doc/book/ is a standalone static site — no runtime dependencies, all links relative. Typical distribution options:

  • Zip doc/book/ and email or share the archive; recipients unzip and open index.html directly.
  • Push doc/book/ to any static web host (GitHub Pages, S3, an internal nginx, etc.).
  • Commit doc/book/ to a docs branch for versioned online access.

Add doc/book/ and doc/src/rustdoc/ to your .gitignore if you prefer to keep only sources in version control — both are fully reproducible from the sources plus project.json and control/.