A name that says where it lives.
Most control projects end up with a long flat list of signals. FILL_V1_OPN, FILL_V1_FB, CAP_TRQ_OK. The structure is real — it lives in the naming convention, in a spreadsheet, and in the head of whoever set it up.
StateTick asks you to build the structure instead of encoding it in a prefix. A signal is not a tag in a list. It is a variable, on an interface, on a piece of equipment, placed in a machine — and its name says exactly that.
The vocabulary is built in layers
Each layer answers one question, and only that question.
An interface is a contract: a named set of typed variables. Valve might carry Open, Closed and Command. It says nothing about which valve, or where the valve is wired. It is the shape of a valve.
A piece of equipment is a named thing that implements one or more interfaces. A FillHead might implement Valve and FlowMeter together, because that is what a fill head is: one physical assembly with two aspects.
An instance is that equipment placed inside a particular machine, under a particular name. Filler1, Filler2, Filler3 — or an indexed set, if you have twelve of them and want to talk about Heads[0] through Heads[11].
Put those together and you get the address of a signal:
The variable at the end is properly typed. Bool, Int, Double, String and Time cover the ordinary cases; arrays, keyed maps and mixed-field records cover the ones that would otherwise turn into RECIPE_1_PARAM_07.
This is the same instinct as a UDT and an instance of it — the difference is that the interface, the equipment and the placement are three separate objects you edit in three separate places, and the wiring is a fourth. Change the valve interface and every valve in the plant gains the variable. Rename an instance and every reference to it moves with it.
The composite holds the instances
A machine is a container with a boundary, not a region of a shared program.
A composite is one machine. When you open one you are looking at everything that machine has: its state plan, the equipment instances placed in it, its timers, the logic blocks it instantiates, the queues it owns, and how often it runs.
That last one matters more than it sounds. A composite carries its own cycle time. A safety interlock can sit at 2 ms next to a recipe sequencer at 50 ms, on the same controller, because they are separate machines and always were.
Because the boundary is real, so is the addressing. Inside the composite, Filler1.Valve.Open is unambiguous. Across the project, a global is Global.BatchId and a field of a record global is Global.Recipe.Target. There is no third category of variable that lives nowhere in particular.
Write the step once, use it everywhere
A state that says “clamp and wait” should not have to be rewritten for each clamp.
States and conditions are authored in a place of their own, against placeholders rather than against a specific machine. A generic state can start a timer, execute a logic block, set an equipment variable, set a global, or push and pop a queue — but the thing it acts on is left open.
Then, in a composite, you fill the holes. The same template becomes a different concrete state in each machine it is used in, and the mapping is visible: you can see, on one screen, that Target means Filler1 here and Filler2 there.
Conditions work the same way. A guard can reference an edge on a placeholder signal, a timer, a queue’s state, or another machine’s state — and stays a template until a composite says what those refer to.
The practical effect: a line with eight near-identical stations is authored as one set of states and eight mapping tables, not as eight copies that drift apart over the next two years.
The path is already the API
Structure you built for yourself turns out to be the structure everyone else wanted.
Every equipment variable and every global carries a flag: expose this on the OPC UA server. Tick it, and the variable appears in the server’s address space — under a folder tree built by splitting the path you already have.
No export step. No alias table. No second naming scheme that has to be kept in step with the first. The tree a SCADA integrator browses is the machine you drew.
Complex variables are handled rather than flattened. An array, a keyed map or a record is expanded into individual nodes, so a client sees the elements it can actually subscribe to rather than one opaque blob.
Globals follow the same rule under their own root, including record fields and indexed elements — Global.Recipe.Target lands where you would look for it.
Worth saying out loud: exposure is per variable, not all-or-nothing. The address space contains what you decided to publish, which is usually a small, deliberate subset — not every internal bit in the controller.
Why structure earns its keep
The payoff is not tidiness. It is that questions have answers.
Questions that become easy
- What does this machine actually have? Open the composite.
- Where does this signal come from? Follow the path to a mapping row.
- Who else uses this state? The mapping table lists every machine.
- What is on OPC UA? The tree, and nothing you did not tick.
Mistakes that stop happening
- Two signals with the same meaning and different names.
- A rename that leaves half the references behind.
- An OPC tag list that no longer matches the controller.
- A copied station that quietly diverges from its siblings.
You are not naming signals. You are describing a machine.
The flat tag list is not wrong so much as lossy: it throws away the structure of the plant and asks the naming convention to carry it. Build the structure instead and the name becomes a consequence — readable to the person who arrives next year, and precise enough that the tools can follow it.
That is the whole argument for the composer. Interfaces say what a thing is. Equipment says what a thing has. A composite says what a machine is made of. And the address of any signal is just those three answers, joined by dots.
Sub-Machines and Machine Modes — what happens once you have more than one composite: parallel sections of a machine, and automatic and manual as two plans that hand over cleanly.
Queues: Batches That Flow Between Stages — how one machine hands work to the next when the thing being passed is data, not just timing.