Work / picw-overview
picw-overview: Raspberry Pi 4B checkweigher controller, no Mitsubishi PLC
Replacing a Mitsubishi PLC with a Raspberry Pi 4B as master of a dynamic checkweigher (Thames Side T16 / XT1-SO)
Source on GitHub ↗ · Need one like it? →
How does it work?
Replacing a Mitsubishi PLC with a Raspberry Pi 4B as the master of a dynamic checkweigher.
Load cell: Thames Side T16 (viscous damped, 5–35 kg). Transmitter: Thames Side XT1-SO (24-bit, 2400 readings/s, RS485 Modbus RTU). Throughput: 40 items/min.
Source is in a private repository. This page is an overview.
Why replace the PLC
The PLC is scan-cycle bound, can't hold product recipe tables or item history, and has no capacity for signal processing on the weight trace. None of that is fixed by a faster PLC.
Safety
The E-stop and guard interlocks are hardwired through a safety relay that removes power from the conveyor motor and pneumatic actuators directly. The Pi reads E-stop state for display, logging, and interlocking its own outputs. It is never the only element between an operator and a moving part. No part of this software is a safety function.
Regulatory position
Internal quality control only. The machine is unstamped and holds no OIML R51 approval as an automatic weighing instrument — the XT1000's OIML R76 certificate covers non-automatic weighing and approves the transmitter as a module, not the machine.
So there is no legal metrology constraint. If the machine is ever brought into a legally controlled application, the design must be revisited first.
Architecture
One service plus a kiosk browser. The control loop runs on a dedicated thread and is never driven from an HTTP handler — a slow request must not be able to stall a reject.
Machine one tick drives everything
├── RTLoop edges, item queue, desync detection, reject, jitter
├── ScaleService XT1000 over Modbus RTU, reconnect with backoff
├── Scanner+Binder per-item binding, product changeover
└── Store products, items, events, audit (SQLite WAL)
↑ ticks on its own thread
FastAPI REST + WebSocket → chromium --kioskEvery hardware boundary is a protocol with two implementations:
Dual verdict
The XT1000 classifies in hardware from 2400 readings/s and drives its setpoint outputs. The Pi independently classifies from the streamed weight with its own filtering. Both are logged per item; a config key picks which drives the rejector, defaulting to the XT1000.
The point is evidence: prove the Pi's classification is actually better on this machine with this product before trusting it with reject decisions.
Roles, not pins
Runtime code binds to role names — item_entry, reject_actuator, weight_ready — never to GPIO numbers. The setup wizard maps physical channels onto roles, and the machine cannot leave setup mode until every required role is bound exactly once.
Queue desynchronisation
The failure mode the design is built around. If the entry photo-eye counts an item the exit eye misses, the FIFO shifts by one and every subsequent item inherits the previous item's verdict — silently rejecting good product and passing bad. Each queued item carries an expected arrival window derived from belt speed and distance; an exit edge matching no window is treated as a desync, not an item. Default response is to stop the belt.
Status
Complete and running.
327 tests · 90% coverage · ruff clean · mypy strict cleanReal-time core, Modbus driver for the transmitter, SQLite storage with product recipes and an append-only audit log, barcode binding and product changeover, REST + WebSocket API, touchscreen HMI and setup wizard, one-line installer, systemd units, kiosk browser, Samba export, and WiFi priority management.
Every hardware boundary has a simulated implementation behind the same protocol, so the whole machine — packages, weights, barcodes, rejects, faults — runs on a Raspberry Pi with nothing attached.
sudo bash deploy/install.sh # simulated mode
sudo bash deploy/install.sh --hardwareInterface
Five tabs sized for a 10" panel: Run (live weight, throughput, verdict conflicts, jitter, simulator with fault injection), Products (recipe editor with barcode binding), I/O (live input LEDs, guarded output forcing), Setup (role bindings, belt geometry, serial scan with generated udev rules), Diagnostics (jitter histogram, events, audit).
Vanilla JS and CSS — no build step, no npm, no CDN, because it runs in a kiosk browser on a read-only root.
Setup wizard
The commissioning tool, reachable from the HMI and re-runnable step by step as a diagnostic. Serial auto-detect sweep across baud/parity/address; live sensor state indicators you can trip by hand; guarded output forcing; reject-delay learn mode; product recipes bound to barcodes; and a supervised trial run reporting verdict disagreement, reject confirm rate, throughput, and the jitter histogram that decides whether the Python real-time approach holds.
Design notes
- All control timing uses CLOCK_MONOTONIC. An NTP step must never move a reject window.
- Safe state is "stop the conveyor," not "pass everything." Nothing ships unweighed.
- Solid-state outputs, not relays. A relay adds 5–15 ms of switching latency to the
reject path and wears out.
- Boot from USB SSD. SD card corruption on unclean power-down is the dominant field
failure for Pi appliances.
- Immutable data throughout. Frozen dataclasses; state transitions return new instances.
Stack
Python 3.11 · pytest · ruff · mypy (strict) · libgpiod · FastAPI · SQLite · systemd
What do people ask about it?
Why replace a Mitsubishi PLC with a Raspberry Pi on a checkweigher?
The PLC is scan-cycle bound, cannot hold product recipe tables or item history, and has no capacity for signal processing on the weight trace — and a faster PLC fixes none of that. A Raspberry Pi 4B runs the control loop on its own dedicated thread, never from an HTTP handler, keeps products, items, events and an audit trail in SQLite, and still serves the operator's kiosk screen.
Which load cell and transmitter does it read?
A Thames Side T16 viscous-damped load cell (5–35 kg) and a Thames Side XT1-SO transmitter: 24-bit, 2,400 readings a second, over RS485 Modbus RTU with reconnect and backoff. Throughput is 40 items a minute.
Is the Raspberry Pi a safety device?
No. The E-stop and guard interlocks are hardwired through a safety relay that removes power from the conveyor motor and the pneumatic actuators directly. The Pi reads the E-stop state for display, logging and interlocking its own outputs; it is never the only element between an operator and a moving part, and no part of the software is a safety function.
Is it legal for trade — OIML approved?
It is internal quality control only. The machine is unstamped and holds no OIML R51 approval as an automatic weighing instrument; the XT1000's OIML R76 certificate approves the transmitter as a module for non-automatic weighing, not the machine. A legally controlled application would mean revisiting the design first.
Can it run without the hardware?
Yes. Every hardware boundary is a protocol with two implementations — GPIO through libgpiod or a simulated I/O, the Modbus scale or a simulated one, the serial scanner or a simulated one — so the whole machine runs on a laptop for development and tests.
What else did I build?
GXBridge-MCP
Control Mitsubishi GX Works3 and your PLC with AI — an MCP server for any agent GX Works3 · Industrial Automation
eplanbridge
Drive EPLAN Pro Panel 2022.0.3 from outside the application — and build a 6-axis robot-arm control schematic with it. C# · EPLAN
tovex-cli-showcase
tovex-cli: command-line control of Siemens TIA Portal and live S7 PLCs, built for AI agents. Public overview; source is private.