Author: Bibin VR
License: MIT — see LICENSE
Hardware: Raspberry Pi 4 · Thames Side XT1000 · T16 Load Cell · IR + Motion GPIO sensors
Dynamic, in-motion checkweighing for a 3-section conveyor using a Thames Side XT1000 indicator (RS232), a T16 fluid-damped load cell, and an IR photoelectric trigger — no encoder required, products never stop.
Targets 300 products/min (5/s) by capturing the full weight waveform as each product crosses the weigh conveyor and estimating the static weight from its shape (plateau / peak / area / rise-fall), refined by a lightweight ML model.
XT1000 ─RS232─▶ SerialReader ─┐
├─▶ RingBuffer ─▶ TrackingEngine ─▶ SQLite + HMI
IR sensor ─GPIO─▶ IRTrigger ──┘ (5 s) (segment→weigh→classify)
pip install -r requirements.txt # numpy/pyyaml/pyserial required; rest optional
# Run with NO hardware (synthetic line) — proves the whole pipeline:
python -m checkweigher.main --simulate
# Real hardware:
python -m checkweigher.main --config config/config.yaml
# Dashboard (default): http://<pi-ip>:8090 (8080 is used by another app here)Console prints live stats; the HMI shows the live waveform, counts, throughput, accept/reject/lost, belt speed and a recent-products table.
| Requirement | Module |
|---|---|
| 1. RS232 acquisition thread | acquisition/serial_reader.py |
| 2. IR GPIO interrupts, µs stamps | acquisition/ir_trigger.py |
| 3. Product Tracking Engine | tracking/tracking_engine.py, tracking/product.py |
| 4. Weight Acquisition Buffer (ring) | core/ring_buffer.py |
| 5. Dynamic weighing (waveform) | dsp/waveform.py |
| 6. Filters: MA / median / Kalman | dsp/filters.py |
| 7. Vibration rejection | dsp/vibration.py |
| 8. Waveform feature extraction | dsp/waveform.py |
| 9. AI-assisted estimation | estimation/ml_model.py, scripts/train_model.py |
| 10. Dashboard | dashboard/app.py |
| 11. Data logger (SQLite) | storage/logger.py |
| 12. Encoder-ready position layer | position/encoder_interface.py |
| 13. Architecture / diagrams / perf | docs/ |
checkweigher/
├── config/config.yaml # tune to your physical line
├── checkweigher/
│ ├── main.py # thread orchestration + lifecycle
│ ├── config.py # typed config
│ ├── metrics.py # live HMI metrics (thread-safe)
│ ├── core/ # clock, ring_buffer, events, simulator
│ ├── acquisition/ # serial_reader, ir_trigger
│ ├── tracking/ # product, tracking_engine (state machine)
│ ├── dsp/ # filters, vibration, waveform
│ ├── estimation/ # classical, ml_model
│ ├── position/ # encoder_interface (future-proof)
│ ├── storage/ # logger (SQLite WAL, batched)
│ └── dashboard/ # Flask HMI + template
├── scripts/ # train_model.py, replay.py
├── tests/ # pytest, runs without hardware
└── docs/ # ARCHITECTURE / diagrams / performance
- docs/ARCHITECTURE.md — full software architecture, thread model, pipeline.
- docs/diagrams.md — data flow, state machine, class & sequence diagrams.
- docs/performance.md — single-occupancy constraint, sample-rate sizing, latency budget, throughput math, tuning checklist.
- XT1000 RS232: set the indicator to continuous output at its max stable
rate (≥ 200 Hz). Adjust
serial.frame_formatparsing inacquisition/serial_reader.py:parse_frameto your exact print string. - IR sensor: wire to a 3.3 V-safe GPIO (default BCM 17). Uses
libgpiodhardware timestamps when available, elseRPi.GPIO. - Level shifting: the Pi UART is 3.3 V — use a MAX3232 (or USB-RS232) for the XT1000’s RS232 levels.
- Low jitter: run with
sudo(orCAP_SYS_NICE) so the serial thread can take SCHED_FIFO; considerisolcpus/tasksetto pin it on a busy line.
python -m pytest tests/ -q # filters, waveform, ring buffer, config
python scripts/replay.py --n 5 # compare filters on logged waveforms
python scripts/train_model.py # train the LightGBM estimatorImplement QuadratureEncoderSource in
position/encoder_interface.py
(edge counting → counts/position/speed) and return it from
make_speed_source. The tracking engine, geometry math and windowing are
unchanged — windows become exact even if the belt accelerates.