Skip to content

Allen-Bradley Logix

Your Logix code lives in git as text. Every change is diffed and reviewed. CI verifies it. The controller is checked for drift against the repo. You watch live values and write setpoints from your editor.

Nothing about the runtime changes. Logix stays the controller and the program of record; nautilus supplies the software engineering around it. (The other direction, writing the logic in nautilus and generating the Logix project from it, is Authoring for Logix, which is experimental.)

From an .ACD to live rungs, in three steps

Section titled “From an .ACD to live rungs, in three steps”

The whole path, start to finish. Each step names the release it needs; install the latest naut and every step works.

1. Convert the project to text: naut logix convert

Section titled “1. Convert the project to text: naut logix convert”

Needs: naut v0.11.0 or later, and a logixd agent on a Windows machine with a licensed Studio 5000 and the Logix Designer SDK.

An .ACD is a binary database only Logix Designer can open. logixd asks the SDK to save it as L5X: the file goes up, the L5X comes back, and naut runs on any OS.

Terminal window
export NAUTILUS_LOGIXD_URL=http://plc-box:8188 # where logixd listens
export NAUTILUS_LOGIXD_TOKEN=... # %ProgramData%\logixd\logixd.token on that machine
naut logix convert DemoLine.ACD DemoLine.L5X
# A whole folder of projects:
for f in projects/*.ACD; do naut logix convert "$f" "${f%.*}.L5X"; done

--agent and --token set the same two things per command. With neither, naut tries http://127.0.0.1:8188, so if logixd stays on loopback (the installer’s default) reach it through an SSH tunnel instead of opening a port:

Terminal window
ssh -N -L 8188:127.0.0.1:8188 you@plc-box & # Windows OpenSSH Server
naut logix convert DemoLine.ACD DemoLine.L5X # token still required

logixd installs with one command on that machine; see Install it.

2. Import it as a nautilus project: naut logix import --project

Section titled “2. Import it as a nautilus project: naut logix import --project”

Needs: naut v0.16.0 or later. No Rockwell software.

Terminal window
naut logix import --project demoline DemoLine.L5X
wrote demoline/MainProgram.ld
wrote demoline/nautilus.yaml
wrote demoline/tags/logix.yaml
1 ladder routine, 1 complete; 2 rungs, 2 carried

Every ladder routine becomes a nautilus PROGRAM in ladder source, with its rung comments, its tags’ descriptions and their initial values (HiLevelSP : REAL := 85.0). The manifest gets target: logix, so naut check --target logix demoline passes on what came across. A rung with no nautilus form is kept as a comment and counted under not carried, never guessed at. What the import covers, and what you do with the project afterwards, is in Authoring for Logix → Bringing an existing program in.

3. Watch it run: naut logix serve and the editor

Section titled “3. Watch it run: naut logix serve and the editor”

Needs: naut v0.16.1 or later, and the VS Code extension 0.14.0 or later (stable channel; on the pre-release channel, 0.13.1 or later). An older extension draws the rungs but shows no values on program-scope tags. An older naut (from v0.14.0) serves values, but the first write after half a minute without one is refused with EOF.

Terminal window
naut logix serve --host 10.0.0.20 --l5x DemoLine.L5X

Set nautilus.runtimeUrl to http://localhost:8080 and open DemoLine.L5X. The rungs light up with the controller’s values. Program tags are served as <Program>_<Tag> (MainProgram_StartPB), and the ladder view finds them from the rung’s own StartPB. To write a setpoint, click the pencil on a tag in the Live Values panel (or right-click it, Set Live Value…); the write goes to the controller. Live values from a running controller has the details.

No controller to hand? naut logix emulate --l5x DemoLine.L5X serves the export’s tags as a ControlLogix on 127.0.0.1:44818; point serve at it with --host 127.0.0.1.


The rest of this page is the reference behind those steps. There are two halves, and the first needs nothing from Rockwell at all.

Half one: reading L5X — no Rockwell software

Section titled “Half one: reading L5X — no Rockwell software”

An L5X is the XML Logix Designer writes on File → Export. naut reads it directly, on any OS:

Terminal window
naut logix info DemoLine.L5X # what is in this export?
naut logix import DemoLine.L5X # UDTs -> IEC types, tags -> a tag file
naut logix graph DemoLine.L5X # RLL routines -> the ladder model
naut logix normalize DemoLine.L5X # pin the attributes that move on every export
Terminal window
naut logix import DemoLine.L5X --scope '*'
# wrote logix_types.st (3 types) and tags/logix.yaml (8 tags)
- { name: DwellTmr, role: input, type: TIMER, init: { PRE: 30000, ACC: 0 }, desc: "Dwell before the pump is allowed to restart" }
- { name: StartPB, role: input, init: false, desc: "HS-101 start pushbutton" }

That desc: is the point. Logix keeps tag documentation in the offline project file, so a live CIP browse cannot see it — naut eip import has to leave descriptions empty. Reading the L5X recovers them.

UDTs come across as real IEC types. A Logix UDT has no BOOL members — an authored BOOL is exported as a hidden byte host plus BIT overlays — so nautilus drops the hosts and declares the BOOLs you actually wrote:

Analog_Input : STRUCT
Raw : INT;
EU : REAL;
Alarms : Limits;
History : ARRAY [0..9] OF REAL;
Fault : BOOL;
END_STRUCT;

.L5X files open in the ladder view exactly like nautilus .ld source — contacts, branches, in-rung blocks, coils — and diff as diagrams between git revisions. Rendering only: nautilus draws the rung Logix exported and claims nothing about how it executes, which is why an instruction it has never seen still draws, as a box with its operands.

L5X is read-only in that view. It is a vendor export; edit it in Logix Designer.

Every export stamps a new ExportDate, so a raw diff is never empty. Normalizing pins that and the other volatile attributes, and then two exports of unchanged code compare equal:

Terminal window
naut logix normalize --check controller.L5X repo.L5X

naut logix serve puts a running controller behind the same HTTP API a nautilus controller serves, over EtherNet/IP, so the editor treats the PLC as if it were one:

Terminal window
naut logix serve --host 10.0.0.20 --l5x repo.L5X

Then set nautilus.runtimeUrl to http://localhost:8080. Live values, hover, the Live Values panel and Set Live Value… now work against the PLC, and so does the built-in dashboard at that address.

  • Tags are discovered, not configured. The controller is browsed at startup and every user tag it can decode is served, except module I/O (--tags narrows it with globs such as "Program:MainProgram.*"). Controller-scope tags keep their names. A program-scope tag is served as <Program>_<Tag>, because two programs may each have a Sts.
  • --l5x supplies descriptions. A CIP browse can’t read tag documentation, so hover and the tag table show the descriptions from an export of the running project.
  • A write goes to the controller. What you see afterwards is the value the next poll read back, not the value you sent. A write names one value, so P101.Run works but a whole UDT doesn’t. A tag the controller refuses to write returns its refusal.
  • Program commands say what to use instead. A Logix controller holds no nautilus source and has nothing to warm-swap. Diff with Controller and Download explain that, and point at naut logix drift and naut logix push.

Run it where the controller is reachable. If only one machine can reach the controller network, which is often the licensed Windows box, run naut logix serve there and point the editor at that machine. naut is one binary for Linux, macOS and Windows, and the path to the controller is still direct EtherNet/IP.

Why not route values through logixd? The Logix Designer SDK reads and writes one tag per call, at roughly half a second each by Rockwell’s own figure. It only does that with the project open and online, while the SDK holds its licence session. EtherNet/IP reads many tags per request, every 250 ms, and keeps working when the licence doesn’t. logixd stays the path for project work: build, download, online edits.

This needs no Studio 5000, licence or Windows machine. Writes are open to anything that can reach the port, just as with naut run. Before listening anywhere but loopback, set NAUTILUS_TOKEN: a write here changes a running PLC.

Half two: driving a project — the logixd agent

Section titled “Half two: driving a project — the logixd agent”

Creating projects, compiling, downloading and online edits need the Studio 5000 Logix Designer SDK, which is Windows-only, licensed, and reachable only from a .NET client on the same machine — it is a gRPC service bound to 127.0.0.1 with no remote protocol.

So nautilus does not talk to the SDK. It talks to logixd, a small agent you run on the licensed Windows machine.

your laptop / CI ──HTTPS+token──▶ logixd (Windows) ──gRPC──▶ Studio 5000 SDK
nautilus CLI tools/logixd 127.0.0.1:53204
Studio 5000 Logix Designerv31+; v37+ for build. Projects older than v31 are not supported.
Logix Designer SDK2.02.00 or later (installs LdSdkService).
.NET SDK10.x, to build logixd. The one-line install adds a private one if there is none.
FactoryTalk LinxThe SDK supports only FactoryTalk Linx — RSLinx is not supported. Your controller must be visible in the FT Linx Network Browser.
OS64-bit only.

In an elevated PowerShell on the licensed machine:

Terminal window
irm https://github.com/joyautomation/nautilus/releases/latest/download/install-logixd.ps1 | iex

To pass options (any of install.ps1’s; -Version 0.16.2 pins a release):

Terminal window
& ([scriptblock]::Create((irm https://github.com/joyautomation/nautilus/releases/latest/download/install-logixd.ps1))) -Listen 0.0.0.0 -Port 8188

logixd is published as source, not as a binary. It links the SDK’s .NET client, which is Rockwell’s and comes only with the SDK install, so it is built on the machine that has it and nothing of Rockwell’s is ever redistributed. The script downloads logixd.zip from the release (v0.16.2 and later carry it), installs a private .NET 10 SDK under C:\Program Files\logixd-dotnet if the machine has none (not on PATH, used only by logixd), and runs install.ps1 from the zip. From a clone, run that directly:

Terminal window
cd tools\logixd
.\install.ps1 -Listen 0.0.0.0 -Port 8188 # elevated PowerShell

That is the whole setup. It checks the prerequisites, builds logixd, mints a bearer token, registers it to start at logon, adds a firewall rule, then starts it and prints the licensing probe gate by gate — so you find out whether the SDK is actually usable before you trust it, not later.

Re-running it is an upgrade; -Uninstall reverses it. The token lands in %ProgramData%\logixd\logixd.token, readable by administrators only.

Start with naut logix probe. Every gate that fails now prints what to do about it, underneath the failure. If the probe itself cannot connect, or the symptom is in a verb rather than a gate, find it here.

What you seeWhat it isWhat to do
logixd: unreachable ... connection refusedThe agent is not running. After a reboot this is almost always it.Log in at the machine’s console, then Start-ScheduledTask -TaskName logixd. The agent is an interactive task, not a service — see above.
logixd: unauthorized: http://plc-box:8188 refused the token; check --token or NAUTILUS_LOGIXD_TOKEN against %ProgramData%\logixd\logixd.token on the agent's machineThe agent answered HTTP 401: wrong bearer token. Re-installing mints a new one. With no token set at all the message reads ... wants a token and none was sent; set --token or NAUTILUS_LOGIXD_TOKEN from ....Re-read %ProgramData%\logixd\logixd.token and pass it as --token or NAUTILUS_LOGIXD_TOKEN.
A bare System.TimeoutException from any SDK call, nothing in any logDOTNET_ROOT is set to an x64 .NET.Unset it, set DOTNET_ROOT_X64 instead, restart the agent. The installer refuses to proceed while it is set.
RxCMP_E_AUDIT_INVALIDOPTYPE - Invalid type. on build or downloadAn instruction the project uses does not exist under that name. The L5X importer does not validate instruction names, so a bad one imports cleanly and only fails at verify.Check the mnemonic. Neutral text uses the short spellings: GE GT LE LT EQ NE MOVE LIMIT — not the GEQ/GRT/MOV/LIM captions the ladder editor shows. Opening the project in Logix Designer and verifying names the rung and the fix; the SDK does not pass that detail through.
RxCL_E_CANNOT_UPLOAD_PHYS_ADDR going onlineYou passed a project file to an online edit.Don’t — an online edit takes no project file. See Online edits.
drift reports a difference of megabytes on a project you just downloadedA detailed export compared against a basic one.Fixed in current builds: drift matches the repo file’s export kind. If you see it, your CLI is older than your agent.
build fails on a project Logix Designer opens fineUsually a real verify error the SDK reports only as a code.Open the project in Logix Designer and run Verify Controller. Its error list names the rung and the instruction; that is currently the fastest way to a diagnosis.

If a gate fails and the remedy does not resolve it, naut logix probe --json gives the whole result, including which Logix revisions the agent found installed.

Terminal window
naut logix probe --agent http://plc-box:8188 --token $TOKEN
ok sdk-service LdSdkServer running
ok logix-designer installed: v38
ok interactive-session a user is logged on at the console
ok live-sdk-call v38 offers 106 processor types, e.g. 1756-L71
ok create-project created and closed a v38 1756-L71 project

The SDK needs several independent things — a FactoryTalk token, a FlexNet feature, a CodeMeter entitlement — and each fails differently and uninformatively, with whichever fails first masking the rest. probe reports them separately so you know which one to fix.

Terminal window
# Format conversion, either direction — two SDK calls, no GUI.
naut logix convert Plant.ACD Plant.L5X
naut logix convert Plant.L5X Plant.ACD
# CI for control logic: compile it. No controller, no downtime, no risk.
naut logix build Plant.ACD
# Does the controller still match the repo?
naut logix drift repo.L5X --comm-path 'AB_ETH-1\10.0.0.5\Backplane\0'
# A warm rung edit on a RUNNING controller.
naut logix push Plant.ACD rungs.L5X \
--program MainProgram --routine F07_Alarms \
--at 12 --replace 1 \
--comm-path 'AB_ETH-1\10.0.0.5\Backplane\0' --finalize

Don’t transcribe the comm path out of a GUI. Ask:

Terminal window
naut logix browse
# AB_ETH-1\10.0.0.5\Backplane\0 PlantCtl (1756-L85E)

browse reads what FactoryTalk Linx has already discovered — it does not scan the network — and prints the controller sitting on the end of each path, so you pick the one you meant rather than assembling a string and hoping. If it finds nothing, browse to the controller once in the FT Linx Network Browser and try again.

--accept sends the change to the controller; --finalize also assembles it if the controller is in Run. Both require --comm-path: offline the SDK ignores the option, so without it the edit would land as pending edits nobody accepted while the command reported success. nautilus refuses instead.

An online edit takes no project file:

Terminal window
naut logix push --comm-path 'AB_ETH-1\10.0.0.5\Backplane\0' \
--program MainProgram --routine MainRoutine --at 1 --replace 1 \
--accept rungs.L5X

It uploads the program the controller is running, imports into that, and sends it back. This is not a convenience — it is the only thing that works. A project file on disk cannot go online even when its logic matches the controller byte for byte, because downloading stamps match information into the project and that copy stays on the machine that did the download. Push your repo’s own .ACD and the SDK fails with:

RxCL_E_CANNOT_UPLOAD_PHYS_ADDR - Failed to upload physical address information.

which is about as far from “wrong project file” as an error message gets. Pass -o if you want to keep the resulting project locally.

To check the controller still matches your repo, use naut logix drift — it exits 1 on a mismatch, so it works as a CI gate.

logixd can stop a controller. Treat it accordingly.

  • Loopback by default. Binding a routable address requires a token, and the agent refuses to start without one.
  • LOGIXD_COMM_ALLOW is agent-side configuration, never a request parameter: a pipeline cannot name a controller the operator has not blessed.
  • Every path the agent touches is confined to LOGIXD_WORKDIR, compared after full resolution so traversal is rejected rather than string-matched.
  • download never changes the controller mode unless explicitly asked.

None of that makes it safe to expose to a plant network. It is an agent for a trusted host on a trusted link.

A download stops the controller and resets tags to project values, and the SDK — unlike the GUI — neither changes the mode for you nor checks that it is right. Gate it: a reviewed pipeline, an agent-side allowlist, and, best of all, a permissive tag operators set from the HMI.

Byte-exact round-tripping of a hand-edited L5X is not offered, and nautilus does not claim a generated rung and a hand-written one compute the same thing. Where something cannot be done, the tooling says so rather than imitating it.