Multi-Unit Applications
The Quickstart tutorial shows an application with a single unit. Real machines are rarely that simple: a plant typically consists of several stations, each with its own state machine, sequences, and equipment, that still have to work together (a robot loading a station, several identical treatment baths, a shared safety circuit, ...). This page describes the patterns used to scale a Zeugwerk Application from one unit to many, based on a real multi-unit application (Ploetzeneder CPT4, 12 unit instances of 7 unit types).
Multiple instances of the same unit type
If a machine has several stations that behave identically but operate independently (four treatment baths, two loading/unloading stations, ...), it is usually not necessary to write a new unit type for each of them. Instead, one unit type is implemented once, and instantiated multiple times in ZModuleProgram, each time with its own displayName, its own slice of Config/Machine/Calibration data, and any extra parameter needed to distinguish the instances from each other:
TreatmentBath1CleaningMedium : TreatmentBathUnit(displayName := 'TreatmentBath1CleaningMedium',
parent := App,
configdata := ZGlobal.Data.Config.TreatmentBath1CleaningMedium,
machinedata := ZGlobal.Data.Machine.TreatmentBath1CleaningMedium,
calibrationdata := ZGlobal.Data.Calibration.TreatmentBath1CleaningMedium,
recipedata := ZGlobal.Data.Recipe, // shared data, see below
status := ZGlobal.Data.Status.TreatmentBath1CleaningMedium,
com := ZGlobal.Com.Unit.TreatmentBath1CleaningMedium,
bathIndex := TreatmentBathIndex.Bath1CleaningMedium); // extra parameter identifying this instance
TreatmentBath2Acid : TreatmentBathUnit(displayName := 'TreatmentBath2Acid',
parent := App,
configdata := ZGlobal.Data.Config.TreatmentBath2Acid,
machinedata := ZGlobal.Data.Machine.TreatmentBath2Acid,
calibrationdata := ZGlobal.Data.Calibration.TreatmentBath2Acid,
recipedata := ZGlobal.Data.Recipe,
status := ZGlobal.Data.Status.TreatmentBath2Acid,
com := ZGlobal.Com.Unit.TreatmentBath2Acid,
bathIndex := TreatmentBathIndex.Bath2Acid);
Both instances share the exact same TreatmentBathUnit implementation and the same _States/_Equipment/_SubObjects code; only their data, name, and bathIndex differ. Note that the instance names in ZModuleProgram (and therefore in ZGlobal.Com.Unit.<Name>/ZGlobal.Data.Config.<Name>) do not have to match the folder/type name: two loading stations of the same LoadingUnloadingUnit type are commonly instantiated as Loading and Unloading.
Orchestrator/hub units
Once an application has several units, some cross-cutting concerns (safety, recipe management, coordinating which station a robot should serve next, persisting data on shutdown, ...) need a place to live that isn't any single station. A common pattern is to introduce one unit that acts as an orchestrator or hub for these concerns (often simply called Module), which:
- owns shared safety I/O and exposes gating checks other units call before running, see Shared Equipment;
- owns or coordinates shared equipment, such as an EtherCAT slave read by several units;
- holds linked unit references to every other unit it needs to coordinate, so its sequences can call into them directly.
The hub does not replace the units it coordinates: each station still owns its own state machine and sequences and can be operated independently. The hub only adds sequences that need a cross-unit view.
A unit with many cross-references, such as a material-handling robot that has to visit every station, typically holds one linked unit reference per station it can serve, resolved once during initialization, rather than hard-coding station-specific logic into the robot's own state machine.
Shared vs. per-instance persisted data
As covered in Global Data & Communication, Config/Machine/Calibration are always per unit instance: every unit gets its own named member so its settings do not collide with another instance of the same type. Some data, however, is inherently shared across units, most commonly:
- Recipe data that describes a process executed across several stations (e.g. a cleaning recipe naming which baths to visit and for how long) - passed by reference to every unit that needs to read it.
- Workpiece/tool tracking data that follows a physical part as it moves from unit to unit (e.g. which recipe step a given workpiece is currently on) - passed by reference to every unit that reads or updates it, often as an array indexed by a workpiece/tool id.
Both are declared once at the top level of ZData (not nested under a specific unit's name) and injected into every unit constructor that needs them, exactly like the per-instance Config/Machine/Calibration structs, just without an intermediate per-unit member.
Where to go from here
- Linked Units - how units reference each other's interfaces at runtime, the mechanism this page builds on.
- Shared Equipment - sharing a single piece of hardware, a mutex, or safety I/O across units.
- Global Data & Communication - the full
ZCom/ZDatastructure.