A complete dockerized simulation environment for the Panther Predator Tamp Print & Apply system. This simulator allows full development and testing of the label printing workflow without requiring the actual hardware.
- System Overview
- Sub-Systems & Architecture
- Docker Containers
- Dashboard
- System Diagram
- Real Hardware Integration Guide
- Quick Start
- Development
This system simulates a complete warehouse label printing and application workflow:
- Barcode Scan → Tracking code received
- Label Data Fetch → IT-01 API provides label information
- ZPL Generation → Label formatted for Zebra printer
- Print → Label printed to tamp pad
- Apply → Robotic applicator stamps label on box
- Monitor → Real-time status via MQTT and dashboard
The simulator recreates the actual production environment with mock services:
- Real System: UL-01 Vision Scanner → IT-01 API → Zebra ZE521-6 Print Engine → Panther Predator PLC → Electric Applicator
- Simulator: HTTP Trigger → Mock IT-01 → Mock Zebra → Mock Panther PLC → Simulated Timing
All communication protocols, timing, and I/O mapping match the real hardware specifications.
- Real Hardware: Barcode scanner that reads tracking codes from boxes on conveyor
- Simulator: HTTP POST endpoint at
http://localhost:8000/api/v1/trigger - Communication: Sends 13-character UPS tracking codes (starts with "1Z")
- Real Hardware: Backend service that provides shipping label information
- Simulator: Mock FastAPI service at port 8001
- Communication: HTTP POST with tracking code → Returns JSON with label data and ZPL
The main orchestration service that coordinates the entire workflow:
- HTTP Server: Receives barcode events
- IT-01 Client: Fetches label data via HTTP
- Zebra Client: Sends ZPL commands via TCP socket (port 9100)
- Panther Client: Controls applicator via Modbus TCP (port 502)
- MQTT Publisher: Broadcasts real-time events
- Modbus Server: Exposes status to NextPLC HMI (port 5021)
- Real Hardware: 6-inch industrial thermal transfer printer (203 DPI)
- Simulator: Mock TCP server that receives and logs ZPL commands
- Communication: Raw TCP on port 9100 (industry standard for Zebra)
- Real Hardware: 52 I/O point PLC controlling electric servo applicator
- Simulator: Mock Modbus TCP server simulating all I/O registers
- Communication: Modbus TCP on port 502 (or EtherNet/IP as alternative)
- Real Hardware: 48-inch stroke electric tamp with servo motor
- Simulator: Timing simulation (2-second cycle with realistic states)
- Purpose: Real-time visualization and manual control
- Technology: FastAPI + HTMX + Alpine.js + MQTT WebSocket
- Features: Status LEDs, event log, job history, manual triggers
- Purpose: Event-driven real-time updates
- Technology: Eclipse Mosquitto 2.0
- Topics: Job events, system status, metrics
Image: ups-label-system-controller
Ports:
8000- HTTP REST API5021- Modbus TCP Server
Purpose: Main orchestration service
Key Components:
services/controller/
├── main.py # FastAPI app and lifespan management
├── controller.py # Orchestration logic and workflow
├── config.py # Environment-based configuration
├── models.py # Pydantic data models
├── modbus_server.py # Modbus TCP server for HMI
├── mqtt_client.py # MQTT event publisher
└── clients/
├── it01_client.py # IT-01 HTTP client
├── zebra_client.py # ZPL printer client (port 9100)
└── panther_client.py # Modbus client for PLC
Environment Variables:
IT01_URL- IT-01 API endpointZEBRA_HOST- Zebra printer IPZEBRA_PORT- Zebra printer port (default 9100)PANTHER_HOST- Panther PLC IPPANTHER_MODBUS_PORT- Modbus port (default 502)MQTT_BROKER- MQTT broker hostMQTT_ENABLED- Enable MQTT publishing
Health Check: GET /health
Image: ups-label-system-mock-it01
Port: 8001
Purpose: Simulates the IT-01 label data service
Provides:
- Realistic label data (recipient, sender, service level)
- Pre-generated ZPL code for 6"x4" labels
- Barcode encoding (Code 128)
- Configurable response timing
API Endpoint:
POST http://localhost:8001/api/label
{
"code": "1Z999AA10123456"
}Response:
{
"tracking_number": "1Z999AA10123456",
"label_data": {
"recipient": { "name": "...", "address": "..." },
"sender": { "name": "...", "address": "..." },
"service_level": "Ground",
"weight": "5.2 lbs"
},
"rendering": {
"format": "ZPL",
"data": "^XA^FO50,50^A0N..."
}
}Image: ups-label-system-mock-zebra
Port: 9100
Purpose: Simulates Zebra ZE521-6 print engine
Simulates:
- TCP socket server on port 9100
- Receives ZPL commands
- Logs all print jobs
- Simulates print timing (500ms)
ZPL Commands Accepted:
^XA...^XZ- Label format~HS- Host status query- Any standard ZPL/ZPL II commands
Image: ups-label-system-mock-panther
Port: 5020 (maps to 502 internally)
Purpose: Simulates Panther Predator PLC via Modbus TCP
Register Map (matches real hardware):
Input Registers (Read by Controller):
Reg 0- Status bits (Print Engine Power, Error, Online, Fault)Reg 1- Applicator bits (Cycle Complete, Home, LOTAR)Reg 2-3- Applicator Cycle Time (ms)Reg 4-5- Print Cycle Time (ms)Reg 6-7- Error Code
Holding Registers (Written by Controller):
Reg 0- Command bits (Trigger 1, Trigger 2, Reset, Bypass)Reg 1-2- Product Height (optional)
Simulated Behavior:
- Receives TRIGGER 1 signal (holding register 0, bit 0)
- Clears "Applicator Home" status
- Waits 500ms (label printing simulation)
- Sets LOTAR bit (Label On Tamp And Ready)
- Waits 500ms (tamp extension)
- Clears LOTAR
- Waits 500ms (tamp retraction)
- Sets "Applicator Home" and "Cycle Complete"
- Reports cycle time (~2000ms)
Image: ups-label-system-dashboard
Port: 8080
Purpose: Real-time monitoring and control interface
See: Dashboard section below
Image: eclipse-mosquitto:2.0
Ports:
1883- MQTT protocol9001- WebSocket protocol
Purpose: Event-driven messaging for real-time updates
Topics Published (by controller):
ups/jobs/started- Job initiationups/jobs/status- Status transitionsups/jobs/completed- Job completionups/jobs/failed- Job failuresups/system/status- System healthups/system/metrics- Performance data
The web dashboard provides real-time monitoring and control of the label printing system.
Access: http://localhost:8080
- 6 LED Indicators with color coding:
- Green: System Ready, IT-01 Connected, Panther PLC, Print Engine
- Yellow: Job In Progress
- Red: Error State
- Manual Trigger:
- Tracking code input (13 characters, starts with "1Z")
- Validation feedback
- Trigger print/apply job button
- System Controls:
- Reset errors
- Emergency stop (placeholder)
- Jobs completed today
- Total jobs completed
- Last cycle time (milliseconds)
- System uptime (minutes)
- Chart.js placeholder for trend visualization
- Ready for jobs/hour metrics
- Last 10 jobs with:
- Job ID and tracking code
- Status (completed/failed)
- Cycle time
- Timestamp
- Error messages (if failed)
- Real-time scrolling log of all system events
- Color-coded messages:
- Blue (Info): Job status, subscriptions
- Green (Success): Completions, connections
- Yellow (Warning): Reconnections
- Red (Error): Failures
- Gray (System): Status updates
- Shows timestamps and MQTT topic names
- Auto-scrolls, keeps last 100 entries
- Backend: FastAPI (Python) - Proxies requests to controller
- Frontend: HTMX (auto-refresh) + Alpine.js (interactivity)
- Real-time: MQTT WebSocket client
- Styling: Industrial SCADA theme (dark with green LEDs)
- Charts: Chart.js
graph TD
UL01[UL-01 Barcode Scanner]
IT01[IT-01 Label Data API]
HTTP[HTTP Server FastAPI]
IT01Client[IT-01 Client]
ZPLGen[ZPL Generator]
ZebraClient[Zebra Client]
PantherClient[Panther Client]
Zebra[Zebra ZE521-6 Print Engine]
Panther[Panther Predator PLC]
Applicator[Electric Applicator]
UL01 -->|HTTP POST Tracking Code| HTTP
HTTP -->|Queue Job| IT01Client
IT01Client -->|Request Label Data| IT01
IT01 -->|JSON Response| IT01Client
IT01Client -->|Label Data| ZPLGen
ZPLGen -->|ZPL Commands| ZebraClient
ZPLGen -->|Ready Signal| PantherClient
ZebraClient -->|TCP Port 9100| Zebra
Zebra -->|Print to Tamp| Applicator
PantherClient -->|Modbus TCP 502| Panther
Panther -->|Control Servo| Applicator
Applicator -->|Status Feedback| Panther
Panther -->|I/O Status| PantherClient
classDef external fill:#e1f5ff,stroke:#0066cc,stroke-width:2px
classDef controller fill:#fff4e6,stroke:#ff9800,stroke-width:2px
classDef hardware fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
class UL01,IT01 external
class HTTP,IT01Client,ZPLGen,ZebraClient,PantherClient controller
class Zebra,Panther,Applicator hardware
| Connection | Protocol | Port/Method | Data Format |
|---|---|---|---|
| UL-01 → Python | HTTP POST | Port 8000 | JSON (tracking code) |
| Python → IT-01 | HTTP POST | Port 8001 | JSON (label request) |
| IT-01 → Python | HTTP Response | - | JSON (label data + ZPL) |
| Python → Zebra | Raw TCP | Port 9100 | ZPL (print commands) |
| Python → Panther | Modbus TCP | Port 502 | Binary I/O (triggers/status) |
| Controller → Dashboard | MQTT + HTTP | 1883/8000 | JSON events + REST API |
When transitioning from simulator to actual Panther Predator hardware, you'll need to configure these specific details.
The Panther Predator PLC exposes I/O via Modbus TCP on port 502 (unit ID typically 1).
Read these registers to monitor system status:
| Register | Bits/Range | Description |
|---|---|---|
| 0 | Status Flags | Print Engine Status |
| Bit 0 | Print Engine Power (1 = powered) | |
| Bit 1 | Print Engine Error (1 = error) | |
| Bit 2 | Ribbon Low | |
| Bit 3 | Label Out | |
| Bit 4 | Online/Data Ready | |
| Bit 7 | Panther Fault | |
| 1 | Status Flags | Applicator Status |
| Bit 0 | Cycle Complete (1 = done) | |
| Bit 1 | Applicator Home (1 = home position) | |
| Bit 3 | LOTAR - Label On Tamp And Ready | |
| 2-3 | 32-bit int | Applicator Cycle Time (milliseconds) |
| 4-5 | 32-bit int | Print Cycle Time (milliseconds) |
| 6-7 | 32-bit int | Current Error Code |
| 8 | 16-bit int | Scanner Status (if equipped) |
Write these registers to send commands:
| Register | Bits/Range | Description |
|---|---|---|
| 0 | Command Flags | Control Commands |
| Bit 0 | TRIGGER 1 - Initiate print/apply cycle | |
| Bit 1 | TRIGGER 2 - Optional second trigger | |
| Bit 2 | RESET - Clear errors | |
| Bit 3 | BYPASS - Bypass current cycle | |
| Bit 5 | Product Height Submit | |
| 1-2 | 32-bit int | Product Height (millimeters, optional) |
-
LOTAR Sensor: Optional hardware. If not installed, you must trigger the applicator immediately after sending ZPL to the print engine (no wait for LOTAR).
-
Trigger Timing: Set Trigger 1 bit HIGH to start cycle, then set LOW. The PLC detects the rising edge.
-
Error Recovery: Always read Register 0 Bit 7 (Panther Fault). If set, read error code from Registers 6-7, then write Reset bit (Register 0 Bit 2).
-
Cycle Completion: Poll Register 1 Bit 0 (Cycle Complete) until it goes HIGH. Typical cycle time: 1-3 seconds.
The Zebra print engine accepts ZPL (Zebra Programming Language) commands via multiple interfaces.
-
Ethernet (Recommended):
- Port: 9100 (raw TCP socket)
- Protocol: Send ZPL as plain text
- IP Configuration: Set via front panel or web interface
-
Serial (RS-232):
- Baud rate: 9600-115200
- Data bits: 8, Parity: None, Stop bits: 1
- Hardware flow control recommended
-
USB:
- Appears as printer device
- Can send ZPL directly via print spool
The IT-01 service provides ZPL, but if generating manually:
^XA # Start format
^FO50,50^A0N,40,40^FDShipping Label^FS # Text field
^FO50,100^GB700,3,3^FS # Horizontal line
^FO50,120^A0N,30,30^FDTracking: 1Z999AA10123456^FS
^FO50,160^A0N,25,25^FDTo: John Doe^FS
^FO50,190^A0N,25,25^FD123 Main Street^FS
^FO50,220^A0N,25,25^FDLouisville, KY 40292^FS
^FO50,270^GB700,3,3^FS # Horizontal line
^FO50,370^BY3^BCN,100,Y,N^FD1Z999AA10123456^FS # Barcode
^XZ # End format
Query printer status with ~HS command:
# Send status query
sock.send(b"~HS")
response = sock.recv(1024)
# Parse response for errors, ribbon status, etc.- Label Size: 6.00" x 4.00" (landscape)
- Resolution: 203 DPI (standard)
- Print Mode: Thermal transfer (ribbon required)
- Media Type: Die-cut labels
- Darkness: Adjust via
~SDcommand or front panel
The system follows this state machine for each print/apply job:
┌─────────────┐
│ IDLE │ ◄──────────────────────────┐
│ (Waiting) │ │
└──────┬──────┘ │
│ Tracking Code Received │
▼ │
┌─────────────┐ │
│ FETCHING │ │
│ LABEL DATA │ │
└──────┬──────┘ │
│ IT-01 Response │
▼ │
┌─────────────┐ │
│ PRINTING │ │
│ (Send ZPL) │ │
└──────┬──────┘ │
│ ZPL Sent │
▼ │
┌─────────────┐ │
│ WAIT_LOTAR │ ◄── (Only if sensor │
│ (Optional) │ installed) │
└──────┬──────┘ │
│ LOTAR = 1 or Timeout │
▼ │
┌─────────────┐ │
│ APPLYING │ │
│(Trigger PLC)│ │
└──────┬──────┘ │
│ Cycle Complete = 1 │
▼ │
┌─────────────┐ │
│ COMPLETED │ ───────────────────────────┘
│ (Log & Done)│
└─────────────┘
│ Error at any stage
▼
┌─────────────┐
│ ERROR │
│ (Log & Retry│ ───┐
│ or Reset) │ │ Reset Command
└─────────────┘ │
▲ │
└───────────┘
- IDLE: System ready, waiting for barcode scan
- FETCHING: Requesting label data from IT-01
- PRINTING: Sending ZPL to Zebra print engine
- WAIT_LOTAR: Waiting for label to be ready on tamp (optional)
- APPLYING: Applicator cycle in progress
- COMPLETED: Job successful, return to IDLE
- ERROR: Fault detected, log error, reset if possible
Configure these in .env:
LOTAR_TIMEOUT=10 # Max wait for LOTAR signal (seconds)
CYCLE_TIMEOUT=30 # Max wait for applicator cycle (seconds)
IT01_TIMEOUT=10 # Max wait for IT-01 response (seconds)When deploying to production network:
┌───────────────────────────────────────────────────┐
│ Production Network (Example) │
│ 192.168.1.0/24 │
├───────────────────────────────────────────────────┤
│ │
│ Controller Service: 192.168.1.50:8000 │
│ Zebra Print Engine: 192.168.1.100:9100 │
│ Panther PLC: 192.168.1.101:502 │
│ Dashboard: 192.168.1.50:8080 │
│ MQTT Broker: 192.168.1.50:1883 │
│ │
│ External IT-01: (configured URL) │
└───────────────────────────────────────────────────┘
Update .env file with real IP addresses:
# Zebra Print Engine
ZEBRA_HOST=192.168.1.100
ZEBRA_PORT=9100
# Panther PLC
PANTHER_HOST=192.168.1.101
PANTHER_MODBUS_PORT=502
# IT-01 Service
IT01_URL=http://it01.production.local- Docker and Docker Compose installed
- Ports 8000, 8080, 5021, 9001, 1883 available
-
Clone or navigate to project directory:
cd ~/projects/ups-label-system
-
Start all services:
docker-compose up -d
-
Verify services are running:
docker-compose ps
You should see 6 containers:
ups-controllerups-dashboardups-mqttups-mock-it01ups-mock-zebraups-mock-panther
-
Access the dashboard:
http://localhost:8080 -
Trigger a test job:
Via Dashboard:
- Enter tracking code:
1Z999AA901234 - Click "Trigger Print Job"
- Watch Event Log for real-time updates
Via API:
curl -X POST http://localhost:8000/api/v1/trigger \ -H "Content-Type: application/json" \ -d '{"code": "1Z999AA901234"}'
- Enter tracking code:
-
View logs:
# All services docker-compose logs -f # Specific service docker-compose logs -f controller docker-compose logs -f mock-panther
-
Stop the system:
docker-compose down
When you trigger a job, you'll see:
-
Dashboard Event Log:
[15:30:45] ups/jobs/started Job #1 started: 1Z999AA901234 [15:30:45] ups/jobs/status Job #1 status: fetching_label [15:30:45] ups/jobs/status Job #1 status: printing [15:30:45] ups/jobs/status Job #1 status: applying [15:30:47] ups/jobs/completed Job #1 completed in 2106ms -
Recent Jobs Panel: Shows completed job with cycle time
-
Metrics Panel: Increments job counters
-
Controller Logs: Full workflow details
ups-label-system/
├── docker-compose.yml # Service orchestration
├── .env # Configuration (create from .env.example)
├── label-sim-dashboard.png # Dashboard screenshot
│
├── services/
│ ├── controller/ # Main orchestration service
│ │ ├── Dockerfile
│ │ ├── requirements.txt
│ │ ├── main.py # FastAPI app
│ │ ├── controller.py # Workflow orchestration
│ │ ├── config.py # Configuration
│ │ ├── models.py # Data models
│ │ ├── modbus_server.py # Modbus TCP server
│ │ ├── mqtt_client.py # MQTT publisher
│ │ └── clients/
│ │ ├── it01_client.py
│ │ ├── zebra_client.py
│ │ └── panther_client.py
│ │
│ ├── dashboard/ # Web UI
│ │ ├── Dockerfile
│ │ ├── main.py # FastAPI proxy
│ │ ├── static/css/
│ │ └── templates/
│ │
│ ├── mock-it01/ # Mock label service
│ ├── mock-zebra/ # Mock print engine
│ └── mock-panther/ # Mock PLC
│
├── services/mqtt/ # MQTT broker config
│ └── config/
│ └── mosquitto.conf
│
└── README.md # This file
Rebuild after code changes:
docker-compose up -d --buildView specific service logs:
docker-compose logs -f controller
docker-compose logs -f dashboard
docker-compose logs -f mqttCheck service health:
curl http://localhost:8000/health # Controller
curl http://localhost:8080/health # DashboardMonitor MQTT messages:
# Install mosquitto clients
brew install mosquitto # macOS
apt-get install mosquitto-clients # Linux
# Subscribe to all topics
mosquitto_sub -h localhost -p 1883 -t "ups/#" -vAccess API documentation:
http://localhost:8000/docs # Controller API (Swagger)
Test IT-01 Mock:
curl -X POST http://localhost:8001/api/label \
-H "Content-Type: application/json" \
-d '{"code": "1Z999AA101234"}' | jqTest Zebra Mock (requires nc or telnet):
echo "^XA^FO50,50^A0N,40,40^FDTest^FS^XZ" | nc localhost 9100Test Modbus Server (requires pymodbus library):
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('localhost', 5021)
client.connect()
# Read controller status
result = client.read_input_registers(0, 10)
print(result.registers)
client.close()Full technical documentation is maintained in Obsidian:
📁 ~/Documents/ObsidianVault/PROJECTS/UPS-PRINTER/
Key Documents:
- implementation-01.md - Complete implementation guide
- ideas.md - Architecture and design decisions
- system_diagram.md - System architecture diagrams
- EQUIPT-DOCS/doc-summaries.md - Hardware specifications
Internal development project - not for public distribution.
© 2025 - UPS Label Printing System Simulator
