2026-06-16 22:51:44 +01:00
2026-06-16 22:24:23 +01:00
2026-06-15 13:35:22 +01:00
2026-06-16 22:51:44 +01:00
2026-06-16 22:24:23 +01:00
2026-06-15 13:35:22 +01:00
2026-06-15 13:39:45 +01:00

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.

The stack builds small local images for database and mqtt so Portainer does not need to bind-mount individual files like database/init.sql or mqtt/mosquitto.conf. This avoids file-versus-directory mount errors in Portainer Git stacks.

The backend receives PostgreSQL connection settings as separate variables (POSTGRES_HOST, POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD) and builds the SQLAlchemy URL internally. This avoids broken database URLs when the password contains special characters. The backend also retries the database connection during startup to tolerate Portainer service/DNS startup delays.

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
Description
No description provided
Readme 377 KiB
Languages
TypeScript 43.2%
Python 41.3%
CSS 14.1%
HTML 0.9%
Dockerfile 0.5%