Files
plast-track/README.md
2026-06-16 22:14:42 +01:00

10 KiB

Plast Track MVP

Lightweight MES/IoT MVP for supervising multi-brand injection molding machines using passive signal acquisition, MQTT event ingestion, operator input, and real-time web dashboards.

Objective

This MVP supervises production without Euromap dependency and without sending any command back to the press. It focuses on:

  • machine status visualization
  • automatic cycle counting
  • real cycle time capture
  • current production order tracking
  • produced quantity and scrap quantity
  • automatic downtime detection
  • operator downtime qualification
  • scrap declaration
  • daily OEE/TRS
  • workshop dashboard in real time

Architecture

Passive machine signal / external sensor
-> isolated relay / optocoupler / industrial DI module
-> IoT gateway or edge service
-> MQTT topic
-> FastAPI backend
-> PostgreSQL
-> WebSocket / REST API
-> React frontend dashboard

Services:

  • database: PostgreSQL with schema and seed data
  • mqtt: Mosquitto broker
  • backend: FastAPI API, MQTT consumer, downtime logic, OEE computation, WebSocket notifications
  • edge-simulator: publishes simulated machine events on MQTT
  • frontend: React + TypeScript operator/dashboard interface

Repository Layout

/backend
/frontend
/edge-simulator
/database/init.sql
/database/seed.sql
/mqtt/mosquitto.conf
/docker-compose.yml

Run Locally

Prerequisites:

  • Docker Desktop or Docker Engine with Compose

Start the full MVP:

docker compose up --build

Exposed services:

  • frontend: http://localhost:5173
  • backend API: http://localhost:8000
  • backend health: http://localhost:8000/health
  • PostgreSQL: localhost:5432
  • MQTT: localhost:1883

Deploy With Portainer

Use a Portainer Stack from the Git repository. Do not paste only the Compose file into the web editor, because the stack also needs these repository files:

  • database/init.sql
  • database/seed.sql
  • mqtt/mosquitto.conf
  • backend/
  • frontend/
  • edge-simulator/

1. Create The Stack

In Portainer:

  1. Go to Stacks.
  2. Click Add stack.
  3. Choose Repository.
  4. Set repository URL:
https://gitea.b2i.business/masterdev/plast-track.git
  1. Set branch:
main
  1. Set Compose path:
docker-compose.yml

2. Configure Stack Variables

For a local server accessed directly by IP, replace SERVER_IP with the host IP:

POSTGRES_DB=plasttrack
POSTGRES_USER=plasttrack
POSTGRES_PASSWORD=change-this-password
FRONTEND_ORIGIN=http://SERVER_IP:5173
VITE_API_BASE_URL=http://SERVER_IP:8000
VITE_WS_URL=ws://SERVER_IP:8000/ws/dashboard

For a domain with HTTPS and a reverse proxy, use:

POSTGRES_DB=plasttrack
POSTGRES_USER=plasttrack
POSTGRES_PASSWORD=change-this-password
FRONTEND_ORIGIN=https://plasttrack.example.com
VITE_API_BASE_URL=https://api.plasttrack.example.com
VITE_WS_URL=wss://api.plasttrack.example.com/ws/dashboard

Important: do not keep localhost in VITE_API_BASE_URL or VITE_WS_URL for a remote deployment. localhost would point to the operator's browser machine, not the Docker host.

3. Deploy

Click Deploy the stack.

Expected exposed ports:

  • frontend: 5173
  • backend API: 8000
  • MQTT: 1883
  • MQTT WebSocket listener: 9001

PostgreSQL is intentionally not exposed by default in the Compose file. It is only reachable by the backend on the internal Docker network.

4. Verify

Open:

http://SERVER_IP:5173

Check the backend:

http://SERVER_IP:8000/health

Expected response:

{"status":"ok"}

5. Updating From Gitea

After pushing new commits to Gitea:

  1. Open the stack in Portainer.
  2. Click Pull and redeploy or Update the stack.
  3. Enable rebuild if Portainer asks, because backend, frontend, and edge-simulator are built from the repository.

Demo Data

Seeded machines:

  • INJ-01 - Haitian Mars II
  • INJ-02 - Engel e-victory
  • INJ-03 - Arburg Allrounder

Seeded production orders:

  • OF-1001 running on INJ-01
  • OF-1002 planned on INJ-02

The edge simulator continuously publishes cycles and stop events for the demo machines.

MQTT Topics And Payloads

Topic pattern:

plasttrack/machines/{machine_code}/events

Raw signal event example:

{
  "machine_id": "INJ-01",
  "event_type": "digital_input_changed",
  "timestamp": "2026-06-14T10:32:15Z",
  "input_name": "cycle_signal",
  "input_value": true,
  "cycle_time_sec": 18.7,
  "source": "digital_input"
}

The backend derives valid cycles from the configured signal edge and timing rules. It still stores normalized cycle_completed events if upstream integrations publish them directly.

Automatic stop event example:

{
  "machine_id": "INJ-01",
  "event_type": "machine_stopped",
  "timestamp": "2026-06-14T10:35:20Z",
  "auto_detected": true
}

REST API

Machines:

  • GET /api/dashboard
  • GET /api/machines
  • GET /api/machines/{machine_id}
  • GET /api/machines/{machine_id}/config
  • PATCH /api/machines/{machine_id}/config
  • GET /api/machines/{machine_id}/status
  • GET /api/machines/{machine_id}/events
  • GET /api/machines/{machine_id}/cycles
  • GET /api/machines/{machine_id}/downtimes

Production orders:

  • GET /api/production-orders
  • POST /api/production-orders
  • POST /api/production-orders/{id}/start
  • POST /api/production-orders/{id}/pause
  • POST /api/production-orders/{id}/close

Downtimes:

  • GET /api/downtimes/open
  • POST /api/downtimes/{id}/qualify

Scrap:

  • GET /api/scraps
  • POST /api/scraps

OEE:

  • GET /api/oee/daily?date=2026-06-14
  • GET /api/oee/machines/{machine_id}?from=2026-06-14T00:00:00Z&to=2026-06-14T23:59:59Z

Reference data:

  • GET /api/reference-data

WebSocket:

  • ws://localhost:8000/ws/dashboard

Manual API Checks

List machines:

curl http://localhost:8000/api/machines

Create a production order:

curl -X POST http://localhost:8000/api/production-orders \
  -H "Content-Type: application/json" \
  -d '{
    "order_number": "OF-2001",
    "machine_id": 2,
    "article_ref": "ART-2001",
    "article_name": "Bac Technique",
    "mold_ref": "M-2001",
    "material_ref": "PP-BLACK",
    "planned_qty": 1200,
    "cavities": 2,
    "theoretical_cycle_time_sec": 21.5
  }'

Qualify a downtime:

curl -X POST http://localhost:8000/api/downtimes/1/qualify \
  -H "Content-Type: application/json" \
  -d '{
    "reason_code": "attente_matiere",
    "comment": "Material not available at the machine",
    "qualified_by": "operateur_1"
  }'

Declare scrap:

curl -X POST http://localhost:8000/api/scraps \
  -H "Content-Type: application/json" \
  -d '{
    "machine_id": "INJ-01",
    "production_order_id": 1,
    "quantity": 12,
    "reason_code": "bavure",
    "comment": "Flash on parting line",
    "operator_name": "operateur_1"
  }'

OEE Logic

Formula:

OEE = Availability x Performance x Quality

Implemented rules:

  • planned time: overlap of started production orders with the query window
  • downtime: overlap of machine downtimes with the query window
  • operating time: planned time - downtime
  • performance: theoretical cycle contribution / operating time, capped at 100%
  • quality: good quantity / total produced quantity

How The Simulator Works

The simulator publishes:

  • digital_input_changed for machine_power_on
  • digital_input_changed for cycle_signal
  • digital_input_changed for general_alarm

Each machine gets a nominal cycle time, a pulse width, and a random stop probability. The backend consumes these MQTT events, detects the configured cycle edge, and updates production data in PostgreSQL.

Connecting A Real Passive Gateway Later

To replace the simulator with a real gateway:

  1. Read passive, electrically isolated machine signals only.
  2. Publish normalized JSON events to the same MQTT topic pattern.
  3. Keep event timestamps in UTC ISO-8601 format.
  4. Map per-machine debounce, min/max cycle time, and stop detection delay in the database or future admin UI.

Expected minimum signals:

  • machine_power_on
  • cycle_signal

Optional signals:

  • auto_mode
  • mold_open
  • ejector_forward
  • general_alarm
  • pump_running

Industrial Safety

  • Never wire the IoT gateway directly to PLC outputs or machine circuits.
  • Use interface relays, optocouplers, or isolated industrial input modules.
  • Validate every wiring decision with a qualified automation or industrial electrical technician.
  • Keep acquisition passive and non-intrusive.
  • Do not modify the machine PLC logic for this MVP.
  • This MVP must never command or pilot the press.

Tests

Backend tests included:

  • OEE calculation unit test
  • MQTT event parsing test

Run locally:

cd backend
pytest

Frontend build check:

cd frontend
npm install
npm run build

Current Limits

  • No authentication or role management
  • No production scheduling calendar
  • No historian-grade buffering on the edge side
  • No historical reporting endpoints beyond daily OEE/TRS
  • No real passive gateway adapter package yet; the simulator publishes normalized MQTT payloads
  • WebSocket currently pushes refresh notifications, then the UI refetches data
  • The simulator models passive-signal-derived events, not actual electrical IO reads

Current UI Modules

The frontend is organized into module tabs rather than one long dashboard page:

  • Atelier: machine cards, status filtering, sorting, quick actions, fullscreen workshop mode
  • Machine: selected machine detail, cycle trend, recent events, downtime history
  • TRS: daily OEE/TRS summary and per-machine OEE
  • OF: production order creation and status management
  • Arrets: open downtime qualification
  • Rebuts: scrap declaration and scrap log
  • Config: persistent passive signal and timing configuration per machine

Operator forms include inline validation, inline API error feedback, and success toasts.

Industrial Readiness Backlog

Next hardening steps before a real shop-floor pilot:

  • define a real passive gateway adapter spec with exact input mapping and MQTT payload examples
  • add edge-side buffering for MQTT/backend outage periods
  • expand tests for downtime transitions, order status transitions, and signal debounce edge cases
  • add historical reporting endpoints for OEE, downtime, and scrap trends