Work / picw-overview

picw-overview

Replacing a Mitsubishi PLC with a Raspberry Pi 4B as master of a dynamic checkweigher (Thames Side T16 / XT1-SO)

Open Source · 2026 · Active

Source on GitHub ↗ · Talk to me →

Readme

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 --kiosk

Every 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 clean

Real-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 --hardware

Interface

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

reject path and wears out.

failure for Pi appliances.

Stack

Python 3.11 · pytest · ruff · mypy (strict) · libgpiod · FastAPI · SQLite · systemd

More work

View all →

plc-checkweigher

One-command installer for the Mitsubishi PLC check-weigher system. Python · Raspberry Pi · Mitsubishi PLC · PDF Reports · npm CLI

checkweigher

High-speed industrial check-weigher on the edge. Python · Raspberry Pi 4 · XT1000 · T16 Load Cell

Tovex-CRM

Modular CRM platform with real-time dashboards. TypeScript · TypeScript · Modular Architecture · Real-time Dashboards

/ Work / {{ project.name }} {{ project.category }}

{{ project.name }}

{{ project.tagline }}

{{ m }}

Overview

{{ project.description }}

npm i {{ project.npm }}

Highlights

{{ f.n }}

{{ f.text }}

Stack

{{ t }}

Readme

{{ b.s }}

{{ b.s }}

{{ b.s }}

{{ b.s }}
{{ b.s }}
  • {{ it }}

Read the full README on GitHub →

Thoughts about this

More work

View all →

/ Work

Project not found.

That project doesn't exist. Head back to the full list.