# 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 (Spring `@Scheduled` cron in `PlcScheduledTasks`, e.g. `0/30 * * * * ?` and `0/1 * * * * ?`) | | **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` | `@Component` with two `@Scheduled` cron methods (every 30 s and every 1 s) driving the 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](../reference/maintainer/deployment.md)). ## 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](../reference/maintainer/deployment.md) — `xlyPlc` is one of the runnable services. The deployment chapter needs a section on shop-floor deployment (one bridge per press model). ## Open verification items > **Item 1 — Deferred (outside the repository's reach).** The byte > protocols themselves come from each press model's vendor > documentation, not from the xly source tree. Each > `xlyPlc/src/main/resources/application-.yml` carries the > *parameters* (baud rate, framing, register addresses, polling > tunables); the *protocol semantics* are press-vendor knowledge. > Documenting either fully is a deployment-ops job, not a wiki audit > against src/db/web. 1. **The wire protocol.** Each press model has a different byte protocol; each `application-.yml` carries the parameters. Documenting the protocol per model is a separate, niche chapter that this wiki may or may not need. 2. ~~**Bridge → ERP-DB latency / polling interval.**~~ **CLOSED.** `PlcScheduledTasks.java` ships **two** Spring `@Scheduled` cron methods (no per-profile difference observed in the cron string): `0/30 * * * * ?` (every 30 s, line 74) and `0/1 * * * * ?` (every 1 s, line 105). A third commented-out cron at line 125 (`0 */2 * * * ?`) is dormant. Per-profile parameter tuning happens inside the polling code via the `application-.yml` YAML, not the cron expression itself. Shop-floor dashboard refresh is independent: its `viw_*` aggregations re-read `mftProduceReportMachineState` on each FROUNT request, so the dashboard sees a row at most ~30 s after the press emits it. 3. ~~**Why `xlyRxtx` is disabled in `settings.gradle`.**~~ **CLOSED.** git history on `xly-src/settings.gradle` shows `xlyRxtx` was originally added in commit `daf581311` ("1、添加串口功能 …" — added serial-port feature). The cleanup branch comments it out as part of the source-pruning pass that excludes hardware modules whose features are not yet exercised in the dev DB; a deployment that needs direct serial access to a press would re-enable the include line in `settings.gradle`. xlyPlc itself runs without RXTX — it relies on TCP/Ethernet for the press models documented here; serial-only press models would need RXTX re-enabled.