Slice 6 — a hardware-integrated module (xlyPlc) (optional)
This slice is for readers who'll touch the printing-press shop floor. If you only ever read or write metadata, skip it.
xly is a printing-industry ERP. On the shop floor, printing presses run
under PLC control (programmable logic controllers — industrial automation
boxes). Each press exposes a stream of state — running / stopped /
fault, current speed, current sheet count, current job number — that
the ERP wants to record on the right work order in real time. xly's
xlyPlc Spring Boot module is the bridge that does this.
This slice covers the bridge's architecture, the per-press profile mechanism, and the boundary where hardware-specific code stops and the generic framework picks up.
What we're documenting
| Module |
xlyPlc (a sibling Spring Boot service in the codebase) |
| Direction | One-way: PLC → ERP DB (no commands sent back to the press) |
| Cadence | Polled on a schedule (Quartz PlcScheduledTasks) |
| Per-press differentiation | Spring profile (-S10, -T0, -T1, -15S, -CT, -yt, -pro) |
| Database tables it writes to |
mftProduceReportMachineState, and related machine-state slaves |
How small the module is
xlyPlc/src/main/java/com/xly/ has just five files:
| File | Role |
|---|---|
PlcApplicationBoot.java |
Spring Boot entry point |
web/scheduler/PlcScheduledTasks.java |
Quartz-driven poll loop |
web/scheduler/PlcRunStatus.java |
In-memory state for the current poll cycle |
web/scheduler/service/PlcToErpService.java |
Interface |
web/scheduler/service/impl/PlcToErpServiceImpl.java |
The implementation: read PLC, write DB |
That is the entire bridge. The protocol-handling for each press model is
done by libraries pulled in via the per-profile application-*.yml and
optional native serial-port code (xlyRxtx, currently disabled in the
build — see Maintainer Reference: deployment).
Per-press profile
Nine YAML files ship in xlyPlc/src/main/resources/ — the default plus
eight named profiles:
application.yml (default — chooses a profile)
application-dev.yml (developer config)
application-S10.yml (S10-model presses)
application-T0.yml (T0-model)
application-T1.yml (T1-model)
application-15S.yml (15S-model)
application-CT.yml (CT-model)
application-yt.yml (one customer's named profile)
application-pro.yml (production)
A given xlyPlc instance is started with one profile active (e.g.
-Dspring.profiles.active=S10). Each profile carries the press-model's
serial-port settings, polling cadence, byte-protocol parameters, and the
target ERP DB connection. One xlyPlc deployment per press, or one per
press-model per machine. This is operational, not customer-facing.
What it writes
The poll loop reads PLC registers, transforms them into ERP-domain rows, and writes:
-
mftProduceReportMachineState— the time-series of machine status (running/stopped/fault). High-volume: this is one of the largest tables in the schema once a press has been polling for any length of time. - Related
mftProductionReport*slaves where applicable.
Once the rows land, the rest of the ERP picks them up exactly like any other data:
-
viw_*views aggregate them into shop-floor dashboards. - Standard Slice-1-style modules render those views.
- The metadata layer (Slice 4 customization) lets a customer add fields to the dashboards without touching the bridge.
The framework / hardware boundary
This is the cleanest story xly tells about an awkward problem:
- Above the line (xlyEntry, xlyApi, all the metadata machinery): generic framework. No knowledge of presses, PLCs, byte protocols.
- Below the line (xlyPlc): hardware-specific. Knows how to talk to a press.
The two communicate only through the database. The bridge writes rows; the framework reads rows. There's no RPC, no shared in-process state, no callback. This makes xlyPlc:
- Independently deployable (and several customers run it on a machine next to the press, separate from the central ERP server).
- Independently failable: if the bridge crashes, the framework keeps running on stale machine-state data. If the framework is down, the bridge keeps writing — when the framework comes back, it sees the buffered rows.
- Hard to test end-to-end without an actual press. Most CI tests stub the PLC reads.
Concepts this slice introduces
This slice is intentionally a boundary case and doesn't introduce new framework concepts. It exists to show readers that the data-driven runtime is only the framework's problem; once a row has been written to the database, the framework treats it the same regardless of whether a PM typed it into BACK, a customer submitted it from FROUNT, or a press's PLC pushed it through xlyPlc.
Reference entries this slice exercises
-
Maintainer: multi-service deployment
—
xlyPlcis one of the runnable services. The deployment chapter needs a section on shop-floor deployment (one bridge per press model).
Open verification items
-
The wire protocol. Each press model has a different byte
protocol; each
application-<model>.ymlcarries the parameters. Documenting the protocol per model is a separate, niche chapter that this wiki may or may not need. - Bridge → ERP-DB latency. What's the polling interval per profile, and how does that interact with shop-floor dashboard refresh? Operational concern, worth a paragraph.
-
Why
xlyRxtxis disabled insettings.gradle. RXTX is the native serial-port library; the build excludes it. Understanding whether xlyPlc currently runs without serial support, or whether it expects serial only on certain deployments, is worth confirming.