The Nautilus ST Editor
Structured Text in VS Code, with Live PLC Values
All posts · Nautilus ·
Companion post for the week-2 video. The 6-minute editor tour covers the same ground on screen: watch it here.
Here is what it’s like to use Nautilus’ Structured Text editor in VS Code, on a running controller.
Our example is a heated surge tank. The project is made up of three text files in a git repository:
program.st the pump control and temperature PID loop
sim.st the plant physics, also in Structured Text
nautilus.yaml config file including every tag with its type, units & description
program.st imports the tags defined in nautilus.yaml with VAR_EXTERNAL — they're values updated each scan — and it also keeps its own state in VAR. The pump starts and stops based on level and the heater is controlled by a PID loop. All in plain text and tracked by Git.
Live values, without going online
Start the controller (nautilus run) and within a couple of seconds live values appear as pills next to every identifier: field tags and the program's own locals like integral and err. The extension fetches them over the controller's REST API; there is no "go online" ceremony. Point the editor at a remote controller with nautilus: Connect to Controller… and the URL lives in the workspace, so the whole team's editor follows the same one. The status bar says which controller it's following.
If the stream drops, the pills go grey and keep their last value. When the controller comes back, the extension reconnects on its own and they go green again.
The manifest knows your tags
Hover a tag and the editor tells you it's a REAL, that it's the tank temperature, measured in °C, and where it comes from. None of that is in the declaration, it's read from nautilus.yaml.
The manifest is also a to-do list. It already declares three tags for a high-temperature alarm — a trip point, a clear point, and the alarm bit — and nothing binds them yet, so nautilus check says so:
nautilus.yaml declares setpoint "HiTempSP", which no program binds — dead, or a stale generated entry
Inside VAR_EXTERNAL, completion offers exactly the manifest tags this program hasn't claimed, with their descriptions, and inserts the whole declaration with the right type. Nothing to reconstruct from memory.
Typo → instant diagnostic
Now the logic. I misspell the trip point on purpose — HiTemSP — and the red squiggle is there before the line is finished. It's the same compiler that runs in CI, so what the editor flags is exactly what would fail the build. Hover shows the error; the Problems panel lists it. Fix the spelling and it clears. The running controller isn't touched by any of this.
The finished alarm is the same shape as the pump latch above it:
(* AH-101: high temperature alarm — a hysteresis latch, same idiom as P-101 *)
IF TempC >= HiTempSP THEN
HiTempAlm := TRUE;
ELSIF TempC <= HiTempClrSP THEN
HiTempAlm := FALSE;
END_IF;
Trips on high, doesn't clear until the temperature drops below the clear threshold — a deadband.
Navigate, then rename
The VS Code features you'd expect work here: F12 jumps to a declaration, hover gives you the type. Rename is the interesting one. Rename TempC to TankTempC and every occurrence in the program changes, its pill drops out (the running controller still knows it as TempC — the status bar flips to program differs), and the manifest entry now reads name: TankTempC. One edit rewrites every program that binds the tag and the manifest that declares it. Download, and the live value is back.
Hysteresis, live
Download the program and the alarm is running: HiTempAlm reads FALSE, because the tank sits at setpoint, below the trip. Proving the latch means moving the setpoints, and there are four ways to do that, each hitting the same tag store:
- Right-click a tag in the code → Set Live Value… Drop the trip point to 60, below the current temperature: the alarm goes
TRUE. - Command palette → Set Live Value. Put the trip point back to 68: the alarm stays
TRUE, because the temperature hasn't crossed the clear point. - The Live Values panel: a watch window of every tag and local, click a row to edit. Set the clear point to 66, above the temperature: the alarm drops
FALSEin the panel and in the code. - The REST API: The same trip over plain HTTP, from any client:
curl -X POST localhost:8080/api/tags -H 'Content-Type: application/json' \
-d '{"name":"HiTempSP","value":60}'
And nautilus check is clean now — every tag the manifest declares is bound by a program.
What's next
The other languages get the same treatment: ladder, function block and sequential function chart, each with live values in the diagram. Next week we’ll look at our extensions ladder editor. It’s a real graphical ladder editor in VSCode that supports viewing code differences visually within the ladder diagram.
Docs: nautilus.joyautomation.com · Repo: github.com/joyautomation/nautilus
Subscribe to our weekly newsletter to keep up to date: buttondown.com/joyautomation