Star Micronics POS Printer Integration
A comprehensive cloud-based receipt printing system for Dash applications, enabling remote printer management, real-time status monitoring, and automated order printing through the Star Micronics CloudPRNT platform.
Overview
The Star Micronics POS Printer integration connects your Dash application to physical receipt printers via the StarIO.Online cloud service. This enables:
- Remote Cloud Printing - Send print jobs from anywhere via REST API
- Real-Time Status Monitoring - Track printer health, paper levels, and queue status
- Automatic Order Printing - Print receipts on successful checkout/payment
- Web-Based Management - Full-featured admin dashboard for printer control
- Multiple Print Formats - Text markup, images, QR codes, and barcodes
- Queue Management - Monitor and clear pending print jobs
Key Features
- ✅ Zero Infrastructure - No local print server required, fully cloud-based
- ✅ Production Ready - Used in live POS systems with Stripe integration
- ✅ Multi-Printer Support - Manage multiple printers from single dashboard
- ✅ Mobile Responsive - Admin dashboard works on all devices
- ✅ Error Handling - Comprehensive diagnostics and retry logic
- ✅ Secure - Admin-only access with Clerk authentication
- ✅ Theme Aware - Automatic dark/light mode support
Supported Hardware
Compatible Printers
mC-Print Series:
- mC-Print2 (firmware 1.2+)
- mC-Print3 (firmware 1.2+, MQTT support 5.1+)
TSP Series:
- TSP100IV (firmware 1.0+, MQTT support 2.2+)
All printers must support the CloudPRNT protocol for cloud connectivity.
Connection Modes
- HTTP Polling - Compatible with all firmware versions (3-5 second intervals)
- MQTT - Real-time push notifications (requires newer firmware)
Installation
Prerequisites
pip install requests python-dotenv Pillow qrcode[pil]
Required Dependencies
dash>=4.2.0
dash-mantine-components>=2.8.0
requests>=2.32.5
python-dotenv>=1.2.2
Pillow>=10.0.0
qrcode[pil]>=7.4.2
GitHub Repository
The complete integration includes two core files:
printer_service.py- Star Micronics API wrapperpages/printer.py- Admin management dashboard
Quick Start
1. StarIO.Online Account Setup
Register for Account:
# Visit registration page
https://www.starmicronicscloud.com
# Select your region:
US: https://api.stario.online
EU: https://eu-api.stario.online
Create Device Group:
- Navigate to "Device Groups" in dashboard
- Create new group (e.g., "your-restaurant")
- Note the Group Path (e.g.,
yourcompany) - Enable settings:
- ✅ AutoCreateDeviceQueue
- ✅ WelcomePrint
- ❌ RequireDeviceKey
Generate API Key:
- Navigate to "API Keys"
- Create new key with permissions:
- ✅ PrintToDevice
- ✅ ViewDeviceGroups
- ✅ ViewDevice
- ✅ FlushQueue
- ✅ ModifyDevice
- Save API key securely (shown only once)
2. Printer Configuration
Get CloudPRNT URL:
Format: https://api.stario.online/v1/a/{GROUP_PATH}/cloudprnt
Example: https://api.stario.online/v1/a/yourcompany/cloudprnt
Configure Printer:
- Access printer web interface (http://[PRINTER_IP])
- Navigate to Settings → CloudPRNT
- Enable CloudPRNT
- Enter CloudPRNT URL
- Set polling interval: 3-5 seconds
- Save and restart printer
3. Environment Variables
Create .env file in project root:
# Star Micronics Configuration
STAR_MICRONICS=your_api_key_here
STAR_GROUP_PATH=yourcompany
STAR_DEVICE_ID=ABC123DEF456 # Optional: default device
Configuration Walkthrough
Step-by-step setup guide with visual feedback:
# File: docs/dash_pos_printer/setup_guide_example.py
"""
Setup Guide Example - Star Micronics POS Printer
Interactive step-by-step configuration guide with visual feedback
"""
import dash_mantine_components as dmc
from dash import html, callback, Input, Output, State, ALL
from dash_iconify import DashIconify
# Configuration steps
STEPS = [
{
"title": "StarIO.Online Account",
"description": "Register for a Star Micronics cloud account",
"icon": "tabler:cloud",
"details": [
"Visit www.starmicronicscloud.com",
"Select your region (US or EU)",
"Complete registration form",
"Verify your email address"
]
},
{
"title": "Create Device Group",
"description": "Set up a device group for your printers",
"icon": "tabler:folder",
"details": [
"Log into StarIO.Online dashboard",
"Navigate to 'Device Groups'",
"Create new group with your business name",
"Save the Group Path (e.g., 'yourcompany')",
"Enable AutoCreateDeviceQueue"
]
},
{
"title": "Generate API Key",
"description": "Create API credentials for your application",
"icon": "tabler:key",
"details": [
"Go to 'API Keys' section",
"Click 'Create New API Key'",
"Enable all permissions:",
" • PrintToDevice",
" • ViewDeviceGroups",
" • ViewDevice",
" • FlushQueue",
"Save the API key securely"
]
},
{
"title": "Configure Printer",
"description": "Connect your printer to CloudPRNT",
"icon": "tabler:printer",
"details": [
"Access printer web interface (http://[PRINTER_IP])",
"Navigate to Settings → CloudPRNT",
"Enable CloudPRNT",
"Enter CloudPRNT URL:",
" https://api.stario.online/v1/a/[GROUP_PATH]/cloudprnt",
"Set polling interval to 3-5 seconds",
"Save and restart printer"
]
},
{
"title": "Environment Setup",
"description": "Configure your application environment",
"icon": "tabler:settings",
"details": [
"Create .env file in project root",
"Add STAR_MICRONICS=your_api_key",
"Add STAR_GROUP_PATH=yourcompany",
"Add STAR_DEVICE_ID=ABC123 (optional)",
"Install required packages"
]
},
{
"title": "Test Connection",
"description": "Verify everything is working",
"icon": "tabler:check",
"details": [
"Run test script: python printer_service.py",
"Verify devices appear in list",
"Send a test print",
"Check printer receives the job",
"Monitor print queue status"
]
}
]
# Component layout
component = dmc.Container([
# Header
dmc.Stack([
dmc.Group([
dmc.ThemeIcon(
DashIconify(icon="tabler:checklist", width=30),
size=40,
radius="md",
variant="light",
color="blue"
),
dmc.Title("Setup Guide", order=2)
]),
dmc.Text(
"Step-by-step configuration guide for Star Micronics printer integration",
c="dimmed",
size="sm"
)
], gap="xs", mb="lg"),
# Progress indicator
dmc.Card([
dmc.Stack([
dmc.Group([
dmc.Text("Setup Progress", fw=600),
dmc.Badge(id="progress-badge", color="blue", children="0%")
], justify="space-between"),
dmc.Progress(id="progress-bar", value=0, color="blue", size="lg")
])
], shadow="sm", padding="md", radius="md", withBorder=True, mb="lg"),
# Stepper
dmc.Stepper(
id="setup-stepper",
active=0,
orientation="vertical",
children=[
dmc.StepperStep(
label=step["title"],
description=step["description"],
icon=DashIconify(icon=step["icon"], width=20),
children=[
dmc.Card([
dmc.Stack([
dmc.Text("Instructions:", size="sm", fw=600, c="dimmed"),
dmc.List([
dmc.ListItem(detail) for detail in step["details"]
], size="sm", spacing="xs"),
dmc.Divider(),
dmc.Group([
dmc.Button(
"Previous",
variant="light",
color="gray",
id={"type": "prev-btn", "index": i},
disabled=i == 0
) if i > 0 else html.Div(),
dmc.Button(
"Next" if i < len(STEPS) - 1 else "Complete Setup",
id={"type": "next-btn", "index": i},
color="blue" if i < len(STEPS) - 1 else "green",
leftSection=DashIconify(
icon="tabler:arrow-right" if i < len(STEPS) - 1 else "tabler:check",
width=16
)
)
], justify="flex-end")
], gap="sm")
], shadow="xs", padding="md", radius="md", bg="var(--mantine-color-violet-4)", mb="md")
]
) for i, step in enumerate(STEPS)
] + [
dmc.StepperCompleted([
dmc.Card([
dmc.Stack([
dmc.Center([
dmc.ThemeIcon(
DashIconify(icon="tabler:circle-check", width=40),
size=80,
radius="xl",
variant="light",
color="green"
)
]),
dmc.Title("Setup Complete!", order=3, ta="center", c="green"),
dmc.Text(
"Your Star Micronics printer integration is ready to use.",
ta="center",
c="dimmed"
),
dmc.Divider(),
dmc.SimpleGrid([
dmc.Card([
dmc.Stack([
DashIconify(icon="tabler:book", width=24),
dmc.Text("View Docs", size="sm", fw=500),
dmc.Text("Read full documentation", size="xs", c="dimmed")
], align="center")
], padding="md", withBorder=True),
dmc.Card([
dmc.Stack([
DashIconify(icon="tabler:printer", width=24),
dmc.Text("Test Print", size="sm", fw=500),
dmc.Text("Send a test receipt", size="xs", c="dimmed")
], align="center")
], padding="md", withBorder=True),
dmc.Card([
dmc.Stack([
DashIconify(icon="tabler:dashboard", width=24),
dmc.Text("Dashboard", size="sm", fw=500),
dmc.Text("Open printer management", size="xs", c="dimmed")
], align="center")
], padding="md", withBorder=True)
], cols=3, spacing="sm"),
dmc.Button(
"Start Over",
variant="light",
color="gray",
id="restart-btn",
fullWidth=True
)
])
], shadow="sm", padding="lg", radius="md",)
])
]
)
], size="md", px="md", py="xl")
@callback(
[
Output('setup-stepper', 'active'),
Output('progress-bar', 'value'),
Output('progress-badge', 'children')
],
[
Input({"type": "next-btn", "index": ALL}, 'n_clicks'),
Input({"type": "prev-btn", "index": ALL}, 'n_clicks'),
Input('restart-btn', 'n_clicks')
],
State('setup-stepper', 'active'),
prevent_initial_call=True
)
def handle_navigation(next_clicks, prev_clicks, restart_click, current_step):
"""Handle stepper navigation"""
from dash import ctx
if ctx.triggered_id == 'restart-btn':
return 0, 0, "0%"
if isinstance(ctx.triggered_id, dict):
if ctx.triggered_id["type"] == "next-btn":
new_step = min(current_step + 1, len(STEPS))
else: # prev-btn
new_step = max(current_step - 1, 0)
else:
return current_step, current_step / len(STEPS) * 100, f"{int(current_step / len(STEPS) * 100)}%"
progress = new_step / len(STEPS) * 100
return new_step, progress, f"{int(progress)}%"
Device Status Example
Monitor printer health with real-time status cards:
Note: the examples on this page send toasts to
notification-container,this documentation site's global
dmc.NotificationContainer. If you copy anexample into your own app, add
dmc.NotificationContainer(id="notification-container")inside your
MantineProvider(or retarget theOutputto your own container id).
# File: docs/dash_pos_printer/device_status_example.py
"""
Device Status Example - Star Micronics POS Printer
Displays real-time printer status with health diagnostics
"""
import dash_mantine_components as dmc
from dash import html, callback, Input, Output, no_update, ALL
from dash_iconify import DashIconify
# Mock device data for demonstration
MOCK_DEVICES = [
{
'Id': 'Kitchen-Printer-01',
'AccessIdentifier': 'ABC123DEF456',
'Mac': '00:11:62:XX:XX:XX',
'ClientType': 'mC-Print3',
'Status': {
'Online': True,
'PaperEmpty': False,
'PaperLow': False,
'CoverOpen': False,
'MechanicalError': False,
'CutterError': False,
'HoldPrint': False
},
'QueuedJobs': 0,
'LastConnection': 0, # MQTT mode
'PaperWidthMM': 72,
'PollingInterval': 0
},
{
'Id': 'Front-Desk-Printer',
'AccessIdentifier': 'XYZ789GHI012',
'Mac': '00:11:62:YY:YY:YY',
'ClientType': 'TSP100IV',
'Status': {
'Online': True,
'PaperEmpty': False,
'PaperLow': True, # Warning
'CoverOpen': False,
'MechanicalError': False,
'CutterError': False,
'HoldPrint': False
},
'QueuedJobs': 3, # Some jobs pending
'LastConnection': 4, # HTTP polling
'PaperWidthMM': 80,
'PollingInterval': 3
},
{
'Id': 'Bar-Printer',
'AccessIdentifier': 'JKL345MNO678',
'Mac': '00:11:62:ZZ:ZZ:ZZ',
'ClientType': 'mC-Print2',
'Status': {
'Online': False, # Offline
'PaperEmpty': False,
'PaperLow': False,
'CoverOpen': False,
'MechanicalError': False,
'CutterError': False,
'HoldPrint': False
},
'QueuedJobs': 0,
'LastConnection': 325, # Not seen in 5+ minutes
'PaperWidthMM': 72,
'PollingInterval': 5
}
]
def analyze_device_status(device):
"""Analyze device status and return diagnostic info"""
status = device.get('Status', {})
issues = []
warnings = []
# Check critical issues
if not status.get('Online', False):
issues.append("Printer is offline")
if status.get('PaperEmpty', False):
issues.append("Paper is empty")
if status.get('CoverOpen', False):
issues.append("Cover is open")
if status.get('MechanicalError', False):
issues.append("Mechanical error detected")
if status.get('CutterError', False):
issues.append("Cutter error")
# Check warnings
if status.get('PaperLow', False):
warnings.append("Paper is low")
if device.get('QueuedJobs', 0) > 5:
warnings.append(f"{device.get('QueuedJobs')} jobs in queue")
if status.get('HoldPrint', False):
warnings.append("Print is on hold")
# Check connection
last_conn = device.get('LastConnection', -1)
if last_conn == 0:
connection_mode = "MQTT"
elif last_conn > 0:
if last_conn > 120:
warnings.append(f"Last seen {last_conn}s ago")
connection_mode = "HTTP Polling"
else:
connection_mode = "Unknown"
return {
'issues': issues,
'warnings': warnings,
'connection_mode': connection_mode,
'healthy': len(issues) == 0
}
def create_device_card(device):
"""Create a status card for a single device"""
analysis = analyze_device_status(device)
# Determine overall status
if not analysis['healthy']:
status_color = "red"
status_icon = "tabler:alert-circle"
status_text = "Issues Detected"
elif analysis['warnings']:
status_color = "yellow"
status_icon = "tabler:alert-triangle"
status_text = "Warnings"
else:
status_color = "green"
status_icon = "tabler:circle-check"
status_text = "Healthy"
# Build diagnostic messages
diagnostic_items = []
# Issues (red)
for issue in analysis['issues']:
diagnostic_items.append(
dmc.Group([
DashIconify(icon="tabler:x", width=16, color="#dc2626"),
dmc.Text(issue, size="sm", c="red")
], gap="xs")
)
# Warnings (yellow)
for warning in analysis['warnings']:
diagnostic_items.append(
dmc.Group([
DashIconify(icon="tabler:alert-triangle", width=16, color="#eab308"),
dmc.Text(warning, size="sm", c="yellow.8")
], gap="xs")
)
# If healthy
if not diagnostic_items:
diagnostic_items.append(
dmc.Group([
DashIconify(icon="tabler:check", width=16, color="#16a34a"),
dmc.Text("All systems operational", size="sm", c="green")
], gap="xs")
)
return dmc.Card([
dmc.Stack([
# Device header with status
dmc.Group([
dmc.Group([
dmc.ThemeIcon(
DashIconify(icon=status_icon, width=24),
size=40,
radius="xl",
variant="light",
color=status_color
),
dmc.Stack([
dmc.Text(device.get('Id', 'Unknown Device'), fw=600),
dmc.Group([
dmc.Badge(
status_text,
color=status_color,
variant="light",
size="sm"
),
dmc.Badge(
analysis['connection_mode'],
color="blue",
variant="outline",
size="sm"
)
], gap="xs")
], gap=0)
])
], justify="space-between"),
dmc.Divider(variant="dashed"),
# Device info grid
dmc.SimpleGrid([
dmc.Stack([
dmc.Text("Access ID", size="xs", c="dimmed"),
dmc.Code(device.get('AccessIdentifier', 'N/A'), style={"fontSize": "11px"})
], gap=2),
dmc.Stack([
dmc.Text("Model", size="xs", c="dimmed"),
dmc.Text(device.get('ClientType', 'Unknown'), size="sm", fw=500)
], gap=2),
dmc.Stack([
dmc.Text("Queue", size="xs", c="dimmed"),
dmc.Badge(
f"{device.get('QueuedJobs', 0)} jobs",
color="blue" if device.get('QueuedJobs', 0) == 0 else "yellow",
variant="filled"
)
], gap=2),
dmc.Stack([
dmc.Text("Paper", size="xs", c="dimmed"),
dmc.Text(
f"{device.get('PaperWidthMM', 'N/A')}mm",
size="sm",
fw=500
)
], gap=2),
], cols=2, spacing="sm", verticalSpacing="sm"),
# Diagnostics section
dmc.Paper(
dmc.Stack(diagnostic_items, gap="xs"),
p="sm",
radius="sm",
withBorder=True
),
# Action buttons
dmc.Group([
dmc.Button(
"Test Print",
size="sm",
variant="light",
color="blue",
leftSection=DashIconify(icon="tabler:printer", width=16),
id={'type': 'test-print', 'index': device.get('AccessIdentifier', '')}
),
dmc.Button(
"Clear Queue",
size="sm",
variant="subtle",
color="red",
leftSection=DashIconify(icon="tabler:trash", width=16),
id={'type': 'clear-queue', 'index': device.get('AccessIdentifier', '')},
disabled=device.get('QueuedJobs', 0) == 0
)
], grow=True)
])
], shadow="sm", padding="lg", radius="md", withBorder=True,
style={"borderColor": f"var(--mantine-color-{status_color}-2)"})
# Component layout
component = dmc.Container([
# Header
dmc.Stack([
dmc.Group([
dmc.ThemeIcon(
DashIconify(icon="tabler:printer", width=30),
size=40,
radius="md",
variant="light",
color="blue"
),
dmc.Title("Device Status Monitor", order=2)
]),
dmc.Text(
"Real-time printer health monitoring with diagnostics",
c="dimmed",
size="sm"
)
], gap="xs", mb="lg"),
# Summary badges
dmc.Group([
dmc.Badge(
f"{len(MOCK_DEVICES)} Devices",
size="lg",
variant="filled",
color="blue"
),
dmc.Badge(
f"{sum(1 for d in MOCK_DEVICES if d['Status']['Online'])} Online",
size="lg",
variant="filled",
color="green"
),
dmc.Badge(
f"{sum(d.get('QueuedJobs', 0) for d in MOCK_DEVICES)} Queued Jobs",
size="lg",
variant="filled",
color="yellow"
),
dmc.Button(
"Refresh",
variant="light",
color="blue",
leftSection=DashIconify(icon="tabler:refresh", width=20),
id="refresh-btn"
)
], mb="lg"),
# Device grid
dmc.Grid([
dmc.GridCol(
create_device_card(device),
span={"base": 12, "md": 6, "lg": 4}
) for device in MOCK_DEVICES
], gutter="md")
], size="xl", px="md", py="xl")
# Callbacks for button actions
@callback(
Output('notification-container', 'sendNotifications'),
Input('refresh-btn', 'n_clicks'),
prevent_initial_call=True
)
def handle_refresh_device_status(n_clicks):
"""Handle refresh button click"""
return [{
"id": "refresh",
"title": "Status Updated",
"message": "All device statuses have been refreshed",
"action": "show",
"color": "blue",
"icon": DashIconify(icon="tabler:refresh", width=20),
"autoClose": 2000
}]
@callback(
Output('notification-container', 'sendNotifications', allow_duplicate=True),
Input({'type': 'test-print', 'index': ALL}, 'n_clicks'),
prevent_initial_call=True
)
def handle_test_print_device_status(n_clicks):
"""Handle test print button clicks"""
if not any(n_clicks):
return no_update
return [{
"id": "test-print",
"title": "Test Print Sent",
"message": "Receipt has been sent to the print queue",
"action": "show",
"color": "green",
"icon": DashIconify(icon="tabler:printer-check", width=20),
"autoClose": 3000
}]
@callback(
Output('notification-container', 'sendNotifications', allow_duplicate=True),
Input({'type': 'clear-queue', 'index': ALL}, 'n_clicks'),
prevent_initial_call=True
)
def handle_clear_queue_device_status(n_clicks):
"""Handle clear queue button clicks"""
if not any(n_clicks):
return no_update
return [{
"id": "clear-queue",
"title": "Queue Cleared",
"message": "All pending print jobs have been removed",
"action": "show",
"color": "orange",
"icon": DashIconify(icon="tabler:trash-check", width=20),
"autoClose": 2000
}]
This example demonstrates:
- Real-time device status monitoring
- Health diagnostics with color-coded indicators
- Connection mode detection (HTTP vs MQTT)
- Paper level warnings
- Queue status tracking
Print Receipt Example
Send formatted receipts to your printer:
# File: docs/dash_pos_printer/print_receipt_example.py
"""
Print Receipt Example - Star Micronics POS Printer
Interactive receipt printing with Star Document Markup
"""
import dash_mantine_components as dmc
from dash import html, callback, Input, Output, State, no_update
from dash_iconify import DashIconify
from datetime import datetime
# Sample receipt templates
RECEIPT_TEMPLATES = {
"simple": """[align: center]
[bold][mag: w 2; h 2]YOUR STORE NAME[normal][mag: w 1; h 1]
================================
[align: left]
Order #: ORD-12345
Date: {date}
Time: {time}
================================
[bold]ITEMS:[normal]
2x Lobster Roll $35.98
1x Clam Chowder $12.99
--------------------------------
Subtotal: $48.97
Tax (8.25%): $4.04
================================
[bold]TOTAL: $52.01[normal]
Payment: Credit Card
[align: center]
Thank you for your order!
Visit us again soon!
[cut]""",
"detailed": """[align: center]
[bold][mag: w 2; h 2]RESTAURANT NAME[normal][mag: w 1; h 1]
123 Main Street
City, State 12345
Phone: (555) 123-4567
================================
[align: left]
Order #: ORD-12345
Server: John Doe
Table: 5
Date: {date}
Time: {time}
================================
[bold]ITEMS:[normal]
Qty Item Price Total
--------------------------------
2 Lobster Roll 17.99 35.98
- Extra Mayo
1 Clam Chowder 12.99 12.99
- Bread Bowl
1 Iced Tea 2.99 2.99
--------------------------------
Subtotal: $51.96
Tax (8.25%): $4.29
Tip (18%): $9.35
================================
[bold]TOTAL: $65.60[normal]
Payment: Credit Card ****1234
Auth Code: 123456
[align: center]
★★★ Thank You! ★★★
Please visit us again soon!
Rate your experience:
www.yourrestaurant.com/review
[cut]""",
"minimal": """[align: center]
QUICK RECEIPT
[align: left]
Order: #{order_num}
Items: 3
Total: $52.01
{date} {time}
[align: center]
Thank you!
[cut]"""
}
# Component layout
component = dmc.Container([
# Header
dmc.Stack([
dmc.Group([
dmc.ThemeIcon(
DashIconify(icon="tabler:receipt", width=30),
size=40,
radius="md",
variant="light",
color="blue"
),
dmc.Title("Print Receipt", order=2)
]),
dmc.Text(
"Create and send formatted receipts using Star Document Markup",
c="dimmed",
size="sm"
)
], gap="xs", mb="lg"),
dmc.Grid([
# Left column - Template selection and preview
dmc.GridCol([
dmc.Card([
dmc.Stack([
dmc.Title("Receipt Template", order=3, c="dimmed", size="h4"),
# Template selector
dmc.SegmentedControl(
id="template-selector",
data=[
{"value": "simple", "label": "Simple"},
{"value": "detailed", "label": "Detailed"},
{"value": "minimal", "label": "Minimal"},
],
value="simple",
fullWidth=True,
color="blue"
),
dmc.Divider(),
# Preview
dmc.Stack([
dmc.Group([
DashIconify(icon="tabler:eye", width=16),
dmc.Text("Preview", size="sm", fw=500)
]),
dmc.ScrollArea(
dmc.Code(
id="receipt-preview",
block=True,
style={
"whiteSpace": "pre",
"fontFamily": "monospace",
"fontSize": "12px",
"lineHeight": "1.4"
}
),
h=400,
style={
"border": "1px solid var(--mantine-color-gray-3)",
"borderRadius": "var(--mantine-radius-sm)"
}
)
])
])
], shadow="sm", padding="lg", radius="md", withBorder=True)
], span={"base": 12, "md": 6}),
# Right column - Print options and controls
dmc.GridCol([
dmc.Card([
dmc.Stack([
dmc.Title("Print Options", order=3, c="dimmed", size="h4"),
# Device selector
dmc.Select(
id="device-select",
label="Target Printer",
placeholder="Select a printer",
data=[
{"value": "ABC123", "label": "🟢 Kitchen Printer (ABC123)"},
{"value": "XYZ789", "label": "🟢 Front Desk (XYZ789)"},
{"value": "JKL345", "label": "🔴 Bar Printer (JKL345) - Offline"},
],
value="ABC123",
leftSection=DashIconify(icon="tabler:printer", width=16)
),
# Print settings
dmc.NumberInput(
id="copies-input",
label="Number of Copies",
description="Print 1-10 copies",
value=1,
min=1,
max=10,
step=1,
leftSection=DashIconify(icon="tabler:copy", width=16)
),
dmc.Group([
dmc.NumberInput(
id="buzzer-before",
label="Buzzer Before",
description="Beeps before printing",
value=0,
min=0,
max=3,
step=1,
style={"flex": 1}
),
dmc.NumberInput(
id="buzzer-after",
label="Buzzer After",
description="Beeps after printing",
value=1,
min=0,
max=3,
step=1,
style={"flex": 1}
)
]),
dmc.Divider(),
# Action buttons
dmc.Stack([
dmc.Button(
"Send to Printer",
id="print-btn",
color="blue",
leftSection=DashIconify(icon="tabler:send", width=20),
fullWidth=True,
size="lg"
),
dmc.Button(
"Test Print",
id="test-print-btn",
variant="light",
color="blue",
leftSection=DashIconify(icon="tabler:printer", width=20),
fullWidth=True
)
]),
# Info alert
dmc.Alert(
[
dmc.Stack([
dmc.Text("Star Document Markup", size="sm", fw=600),
dmc.Text(
"Use markup commands like [bold], [align: center], and [mag: w 2; h 2] to format your receipts.",
size="xs"
)
])
],
title="Formatting Guide",
icon=DashIconify(icon="tabler:info-circle", width=20),
color="blue",
variant="light"
)
])
], shadow="sm", padding="lg", radius="md", withBorder=True)
], span={"base": 12, "md": 6})
], gutter="md")
], size="xl", px="md", py="xl")
@callback(
Output('receipt-preview', 'children'),
Input('template-selector', 'value')
)
def update_preview(template_key):
"""Update receipt preview based on selected template"""
now = datetime.now()
receipt = RECEIPT_TEMPLATES.get(template_key, RECEIPT_TEMPLATES['simple'])
# Fill in dynamic values
receipt = receipt.format(
date=now.strftime('%m/%d/%Y'),
time=now.strftime('%I:%M %p'),
order_num="12345"
)
return receipt
@callback(
Output('notification-container', 'sendNotifications', allow_duplicate=True),
Input('print-btn', 'n_clicks'),
Input('test-print-btn', 'n_clicks'),
State('template-selector', 'value'),
State('device-select', 'value'),
State('copies-input', 'value'),
State('buzzer-before', 'value'),
State('buzzer-after', 'value'),
prevent_initial_call=True
)
def handle_print_receipt(print_clicks, test_clicks, template, device, copies, buzzer_before, buzzer_after):
"""Handle print and test print button clicks"""
from dash import ctx
if ctx.triggered_id == 'print-btn':
return [{
"id": "print-success",
"title": "Receipt Sent",
"message": f"Print job sent to {device} ({copies} copies)",
"action": "show",
"color": "green",
"icon": DashIconify(icon="tabler:printer-check", width=20),
"autoClose": 3000
}]
elif ctx.triggered_id == 'test-print-btn':
return [{
"id": "test-print",
"title": "Test Print Sent",
"message": f"Test receipt sent to {device}",
"action": "show",
"color": "blue",
"icon": DashIconify(icon="tabler:printer", width=20),
"autoClose": 2000
}]
return no_update
Features:
- Star Document Markup formatting
- Text alignment (left, center, right)
- Bold and sized text
- Automatic paper cutting
- Buzzer alerts
Star Document Markup
The system uses Star Document Markup for formatting receipts:
Text Formatting
# Alignment
"[align: left]Left aligned text"
"[align: center]Centered text"
"[align: right]Right aligned text"
# Bold text
"[bold]Bold text[normal]"
# Text sizing
"[mag: w 2; h 1]Double width"
"[mag: w 1; h 2]Double height"
"[mag: w 2; h 2]Double both"
# Paper cut (required at end)
"[cut]"
Example Receipt
receipt = """[align: center]
[bold][mag: w 2; h 2]YOUR STORE[normal][mag: w 1; h 1]
================================
[align: left]
Order #: ORD-12345
Customer: John Doe
Date: 11/17/2025
================================
[bold]ITEMS:[normal]
2x Lobster Roll $35.98
1x Clam Chowder $12.99
--------------------------------
Subtotal: $48.97
Tax (8.25%): $4.04
================================
[bold]TOTAL: $52.01[normal]
Payment: Credit Card
[align: center]
Thank you for your order!
Visit us again soon!
[cut]"""
Printer Management Dashboard
Full-featured admin dashboard for printer control:
# File: docs/dash_pos_printer/printer_dashboard_example.py
"""
Printer Dashboard Example - Star Micronics POS Printer
Mini dashboard with tabbed interface for printer management
"""
import dash_mantine_components as dmc
from dash import html, callback, Input, Output, no_update
from dash_iconify import DashIconify
# Component layout
component = dmc.Container([
# Header
dmc.Stack([
dmc.Group([
dmc.ThemeIcon(
DashIconify(icon="tabler:layout-dashboard", width=30),
size=40,
radius="md",
variant="light",
color="blue"
),
dmc.Title("Printer Management Dashboard", order=2)
]),
dmc.Text(
"Comprehensive printer control and monitoring interface",
c="dimmed",
size="sm"
)
], gap="xs", mb="lg"),
# Status summary
dmc.Group([
dmc.Badge("3 Devices", size="lg", variant="filled", color="blue"),
dmc.Badge("2 Online", size="lg", variant="filled", color="green"),
dmc.Badge("3 Queued Jobs", size="lg", variant="filled", color="yellow"),
dmc.Button(
"Refresh All",
variant="light",
color="blue",
leftSection=DashIconify(icon="tabler:refresh", width=20),
id="refresh-all"
)
], mb="lg"),
# Tabbed interface
dmc.Tabs([
dmc.TabsList([
dmc.TabsTab(
"Overview",
value="overview",
leftSection=DashIconify(icon="tabler:layout", width=16)
),
dmc.TabsTab(
"Print Queue",
value="queue",
leftSection=DashIconify(icon="tabler:list", width=16)
),
dmc.TabsTab(
"Quick Print",
value="print",
leftSection=DashIconify(icon="tabler:printer", width=16)
),
dmc.TabsTab(
"History",
value="history",
leftSection=DashIconify(icon="tabler:history", width=16)
)
]),
# Overview Tab
dmc.TabsPanel(value="overview", children=[
dmc.SimpleGrid([
# Printer 1 - Healthy
dmc.Card([
dmc.Stack([
dmc.Group([
dmc.ThemeIcon(
DashIconify(icon="tabler:circle-check", width=20),
color="green",
variant="light",
size="lg"
),
dmc.Stack([
dmc.Text("Kitchen Printer", fw=600),
dmc.Badge("Healthy", color="green", size="sm")
], gap=0)
]),
dmc.Text("mC-Print3 • MQTT • 0 jobs", size="sm", c="dimmed"),
dmc.Button(
"Test Print",
size="sm",
variant="light",
fullWidth=True,
leftSection=DashIconify(icon="tabler:printer", width=16)
)
])
], shadow="sm", padding="md", radius="md", withBorder=True),
# Printer 2 - Warning
dmc.Card([
dmc.Stack([
dmc.Group([
dmc.ThemeIcon(
DashIconify(icon="tabler:alert-triangle", width=20),
color="yellow",
variant="light",
size="lg"
),
dmc.Stack([
dmc.Text("Front Desk", fw=600),
dmc.Badge("Paper Low", color="yellow", size="sm")
], gap=0)
]),
dmc.Text("TSP100IV • HTTP • 3 jobs", size="sm", c="dimmed"),
dmc.Button(
"Clear Queue",
size="sm",
variant="light",
color="orange",
fullWidth=True,
leftSection=DashIconify(icon="tabler:trash", width=16)
)
])
], shadow="sm", padding="md", radius="md", withBorder=True),
# Printer 3 - Offline
dmc.Card([
dmc.Stack([
dmc.Group([
dmc.ThemeIcon(
DashIconify(icon="tabler:alert-circle", width=20),
color="red",
variant="light",
size="lg"
),
dmc.Stack([
dmc.Text("Bar Printer", fw=600),
dmc.Badge("Offline", color="red", size="sm")
], gap=0)
]),
dmc.Text("mC-Print2 • Last seen 5m ago", size="sm", c="dimmed"),
dmc.Button(
"Diagnose",
size="sm",
variant="light",
color="red",
fullWidth=True,
leftSection=DashIconify(icon="tabler:stethoscope", width=16)
)
])
], shadow="sm", padding="md", radius="md", withBorder=True)
], cols={"base": 1, "sm": 2, "lg": 3}, spacing="md", mt="md")
]),
# Print Queue Tab
dmc.TabsPanel(value="queue", children=[
dmc.Card([
dmc.Stack([
dmc.Title("Active Print Queue", order=4),
dmc.Text("3 jobs pending across all devices", size="sm", c="dimmed"),
dmc.Divider(),
# Queue items
dmc.Paper([
dmc.Group([
dmc.Stack([
dmc.Text("Order #12345", fw=500),
dmc.Text("Front Desk Printer", size="xs", c="dimmed")
], gap=0),
dmc.Badge("Pending", color="yellow")
], justify="space-between")
], p="sm", withBorder=True, mb="xs"),
dmc.Paper([
dmc.Group([
dmc.Stack([
dmc.Text("Order #12346", fw=500),
dmc.Text("Front Desk Printer", size="xs", c="dimmed")
], gap=0),
dmc.Badge("Pending", color="yellow")
], justify="space-between")
], p="sm", withBorder=True, mb="xs"),
dmc.Paper([
dmc.Group([
dmc.Stack([
dmc.Text("Test Print", fw=500),
dmc.Text("Front Desk Printer", size="xs", c="dimmed")
], gap=0),
dmc.Badge("Pending", color="yellow")
], justify="space-between")
], p="sm", withBorder=True, mb="md"),
dmc.Button(
"Clear All Queues",
color="red",
variant="outline",
fullWidth=True,
leftSection=DashIconify(icon="tabler:trash", width=16)
)
])
], shadow="sm", padding="lg", radius="md", withBorder=True, mt="md")
]),
# Quick Print Tab
dmc.TabsPanel(value="print", children=[
dmc.Card([
dmc.Stack([
dmc.Title("Print Templates", order=4),
dmc.SimpleGrid([
dmc.Button(
"Test Receipt",
color="blue",
variant="light",
h=60,
leftSection=DashIconify(icon="tabler:receipt", width=20)
),
dmc.Button(
"Sample Order",
color="blue",
variant="light",
h=60,
leftSection=DashIconify(icon="tabler:shopping-cart", width=20)
),
dmc.Button(
"Test Pattern",
color="blue",
variant="light",
h=60,
leftSection=DashIconify(icon="tabler:grid-pattern", width=20)
),
dmc.Button(
"Buzzer Test",
color="blue",
variant="light",
h=60,
leftSection=DashIconify(icon="tabler:bell", width=20)
)
], cols=2, spacing="sm")
])
], shadow="sm", padding="lg", radius="md", withBorder=True, mt="md")
]),
# History Tab
dmc.TabsPanel(value="history", children=[
dmc.Card([
dmc.Stack([
dmc.Group([
dmc.Title("Print History", order=4),
dmc.Button(
"Clear",
size="sm",
variant="subtle",
color="red",
leftSection=DashIconify(icon="tabler:trash", width=16)
)
], justify="space-between"),
# History items
dmc.Timeline([
dmc.TimelineItem(
[
dmc.Text("Order #12347 printed", fw=500, size="sm"),
dmc.Text("Kitchen Printer • 2 minutes ago", size="xs", c="dimmed")
],
bullet=DashIconify(icon="tabler:check", width=12, color="green"),
# bulletSize=24
),
dmc.TimelineItem(
[
dmc.Text("Test Print sent", fw=500, size="sm"),
dmc.Text("Front Desk • 5 minutes ago", size="xs", c="dimmed")
],
bullet=DashIconify(icon="tabler:check", width=12, color="green"),
# bulletSize=24
),
dmc.TimelineItem(
[
dmc.Text("Order #12346 failed", fw=500, size="sm"),
dmc.Text("Bar Printer • 10 minutes ago", size="xs", c="dimmed")
],
bullet=DashIconify(icon="tabler:x", width=12, color="red"),
# bulletSize=24
)
])
])
], shadow="sm", padding="lg", radius="md", withBorder=True, mt="md")
])
], value="overview")
], size="xl", px="md", py="xl")
@callback(
Output('notification-container', 'sendNotifications', allow_duplicate=True),
Input('refresh-all', 'n_clicks'),
prevent_initial_call=True
)
def handle_refresh_dashboard(n_clicks):
return [{
"id": "refresh",
"title": "Dashboard Refreshed",
"message": "All printer statuses updated",
"action": "show",
"color": "blue",
"icon": DashIconify(icon="tabler:refresh", width=20),
"autoClose": 2000
}]
Dashboard Features:
- Device Overview - Real-time status for all printers
- Print Queue - View and manage pending jobs
- Quick Print - Template prints and custom jobs
- Diagnostics - System health analysis
- Print History - Last 20 print jobs with status
StarPrinterService API
Initialization
from printer_service import StarPrinterService
# Initialize (reads from .env)
printer = StarPrinterService()
Device Management
# List all devices
devices = printer.get_device_list()
for device in devices:
print(f"Device: {device['Id']}")
print(f"Online: {device['Status']['Online']}")
print(f"Queue: {device['QueuedJobs']} jobs")
# Get specific device status
status = printer.get_device_status('ABC123DEF456')
Print Operations
# Basic print
result = printer.print_receipt(
content="[align: center]Hello World\n[cut]",
job_name="Test_Print",
device_id="ABC123DEF456"
)
# Print with buzzer
result = printer.print_receipt(
content=receipt_content,
job_name="Order_12345",
device_id="ABC123DEF456",
startbuzzer=2, # Buzz twice before
endbuzzer=1 # Buzz once after
)
# Print multiple copies
result = printer.print_receipt(
content=receipt_content,
copies=3,
device_id="ABC123DEF456"
)
# Check result
if 'JobId' in result:
print(f"✅ Success! Job ID: {result['JobId']}")
else:
print(f"❌ Error: {result.get('error')}")
Queue Management
# Clear all pending jobs
success = printer.clear_queue('ABC123DEF456')
if success:
print("✅ Queue cleared successfully")
Generate Receipt from Order
order_data = {
'order_number': 'ORD-12345',
'customer_name': 'John Doe',
'customer_phone': '(555) 123-4567',
'items': [
{'name': 'Lobster Roll', 'quantity': 2, 'amount': 35.98},
{'name': 'Clam Chowder', 'quantity': 1, 'amount': 12.99}
],
'amount': 48.97,
'payment_method': 'Credit Card'
}
# Generate formatted receipt
receipt_markup = printer.generate_receipt_markup(order_data)
# Print it
printer.print_receipt(receipt_markup, job_name=f"Order_{order_data['order_number']}")
Automatic Order Printing
Integrate with your checkout process to automatically print receipts:
from printer_service import StarPrinterService
import stripe
def on_checkout_success(session_id):
"""Print receipt when customer completes checkout"""
# Retrieve order from Stripe
session = stripe.checkout.Session.retrieve(
session_id,
expand=['line_items', 'line_items.data.price.product']
)
# Format order data
order_data = {
'order_number': session.id[:12].upper(),
'customer_name': session.customer_details.name,
'customer_phone': session.customer_details.phone,
'items': format_line_items(session.line_items),
'amount': session.amount_total / 100,
'payment_method': 'Credit Card'
}
# Print receipt
printer = StarPrinterService()
receipt = printer.generate_receipt_markup(order_data)
result = printer.print_receipt(
receipt,
job_name=f"Order_{order_data['order_number']}",
startbuzzer=2 # Alert staff with 2 beeps
)
if 'JobId' in result:
print(f"✅ Receipt printed! Job ID: {result['JobId']}")
else:
print(f"⚠️ Print failed: {result.get('error')}")
# Queue for retry or alert admin
Troubleshooting
Printer Shows Offline
Symptoms:
- Device appears offline in dashboard
- Status shows "Printer is offline"
Solutions:
# Check network connection
ping [PRINTER_IP]
# Verify CloudPRNT URL
# Should be: https://api.stario.online/v1/a/[GROUP_PATH]/cloudprnt
# Check StarIO.Online dashboard
# Device should appear under Device Groups
# Restart printer
# Power cycle and wait 30 seconds
Jobs Stuck in Queue
Symptoms:
- QueuedJobs count > 0
- Jobs not printing
- LastConnection timestamp increasing
Solutions:
# Check connection mode
status = printer.get_device_status(device_id)
last_conn = status['LastConnection']
if last_conn == 0:
print("MQTT mode - check firmware version")
print("Required: mC-Print3 5.1+ or TSP100IV 2.2+")
else:
print(f"HTTP polling - last seen {last_conn}s ago")
if last_conn > 60:
print("Printer not polling - verify CloudPRNT URL")
# Clear stuck queue
printer.clear_queue(device_id)
Authentication Errors
Symptoms:
- "API key not found" error
- 401 Unauthorized responses
Solutions:
# Verify .env file exists and is correct
cat .env | grep STAR_MICRONICS
# Ensure no spaces in values
STAR_MICRONICS=your_key_here # Correct
STAR_MICRONICS= your_key_here # Wrong (has space)
# Check API key permissions in StarIO.Online dashboard
# Required: PrintToDevice, ViewDeviceGroups, ViewDevice, FlushQueue
# Test connection
python printer_service.py
Component Properties
StarPrinterService Class
| Property | Type | Default | Description |
|---|---|---|---|
api_key | string | env: STAR_MICRONICS | Star Micronics API key for authentication |
group_path | string | env: STAR_GROUP_PATH | Device group path from StarIO.Online |
device_id | string | env: STAR_DEVICE_ID | Optional default device identifier |
base_url | string | https://api.stario.online/v1 | API base URL (US region) |
Method: print_receipt()
| Parameter | Type | Default | Description |
|---|---|---|---|
content | string | Required | Star Markup formatted receipt content |
job_name | string | None | Name for job tracking in dashboard |
device_id | string | None | Target device (uses default if not set) |
copies | integer | 1 | Number of copies to print (1-10) |
startbuzzer | integer | 0 | Number of beeps before printing (0-3) |
endbuzzer | integer | 0 | Number of beeps after printing (0-3) |
Device Status Object
| Property | Type | Description |
|---|---|---|
Id | string | Device name/identifier |
AccessIdentifier | string | Unique device ID for API calls |
Mac | string | MAC address of printer |
ClientType | string | Printer model (e.g., mC-Print3) |
Status.Online | boolean | Whether printer is connected |
Status.PaperEmpty | boolean | Paper roll is empty |
Status.PaperLow | boolean | Paper roll is running low |
Status.CoverOpen | boolean | Printer cover is open |
QueuedJobs | integer | Number of pending print jobs |
LastConnection | integer | Seconds since last poll (0 for MQTT) |
PaperWidthMM | integer | Paper width in millimeters (72 or 80) |
PollingInterval | integer | Polling interval in seconds |
Best Practices
1. Error Handling
Always wrap printer calls in try-except blocks:
def safe_print(content, device_id):
try:
printer = StarPrinterService()
result = printer.print_receipt(content, device_id=device_id)
if 'error' in result:
# Log error but don't fail checkout
logger.error(f"Print error: {result['error']}")
# Maybe queue for retry
return False
return True
except Exception as e:
logger.exception(f"Printer exception: {e}")
# Alert admin but continue
return False
2. Queue Monitoring
Monitor print queues and alert on issues:
def check_queue_health():
"""Run periodically to monitor queue health"""
printer = StarPrinterService()
devices = printer.get_device_list()
for device in devices:
queue_count = device.get('QueuedJobs', 0)
if queue_count > 10:
# Alert admin
send_alert(f"Printer {device['Id']} has {queue_count} pending jobs")
if queue_count > 50:
# Auto-clear very old jobs
printer.clear_queue(device['AccessIdentifier'])
3. Receipt Design
Design for thermal printer constraints:
# GOOD: Clear, readable, proper width
receipt = """[align: center]
[bold][mag: w 2; h 2]RESTAURANT NAME[normal][mag: w 1; h 1]
[align: left]
Order: 12345
2x Item Name $35.98
1x Another Item $12.99
--------------------------
TOTAL: $48.97
[align: center]
Thank you!
[cut]"""
# BAD: Too wide, will wrap or cut off
# Lines should not exceed 32 chars for 72mm paper
# or 48 chars for 80mm paper
4. Performance Optimization
Cache device status to reduce API calls:
import time
class CachedPrinterService:
def __init__(self):
self.printer = StarPrinterService()
self._devices_cache = None
self._cache_time = 0
self._cache_ttl = 30 # 30 seconds
def get_device_list(self):
now = time.time()
if self._devices_cache is None or (now - self._cache_time) > self._cache_ttl:
self._devices_cache = self.printer.get_device_list()
self._cache_time = now
return self._devices_cache
Live Printer Test
Send custom messages directly to your actual Star Micronics printer:
# File: docs/dash_pos_printer/live_print_example.py
"""
Live Print Example - Star Micronics POS Printer
Send a message directly to the creator of this documentation via their printer
"""
import os
import dash_mantine_components as dmc
import dash_ag_grid as dag
from dash import html, callback, Input, Output, State, no_update, dcc
from dash_iconify import DashIconify
from datetime import datetime
# Import printer service module but don't instantiate yet
printer_service = None
PRINTER_SERVICE_AVAILABLE = False
PRINTER_IMPORT_ERROR = None
try:
# Import from support_files
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../..'))
from support_files.printer_service import StarPrinterService
PRINTER_SERVICE_AVAILABLE = True
except Exception as e:
PRINTER_IMPORT_ERROR = str(e)
print(f"Warning: Printer service module not available: {e}")
StarPrinterService = None
# Get device ID from environment
STAR_API_KEY = os.environ.get('STAR_MICRONICS')
DEVICE_ID = os.environ.get('STAR_DEVICE_ID')
GROUP_PATH = os.environ.get('STAR_GROUP_PATH', 'pipinstallpython')
REGION = os.environ.get('STAR_REGION', 'US')
# Diagnostic logging on startup
print("\n" + "="*60)
print("PRINTER SERVICE ENVIRONMENT CHECK")
print("="*60)
print(f"STAR_MICRONICS API Key: {'✅ SET' if STAR_API_KEY else '❌ NOT SET'}")
print(f"STAR_DEVICE_ID: {'✅ ' + DEVICE_ID if DEVICE_ID else '❌ NOT SET'}")
print(f"STAR_GROUP_PATH: {GROUP_PATH}")
print(f"STAR_REGION: {REGION}")
print(f"Service Import: {'✅ SUCCESS' if PRINTER_SERVICE_AVAILABLE else '❌ FAILED: ' + str(PRINTER_IMPORT_ERROR)}")
print("="*60 + "\n")
PRINTER_AVAILABLE = PRINTER_SERVICE_AVAILABLE and DEVICE_ID is not None and STAR_API_KEY is not None
MISSING_ENV_VARS = []
if not STAR_API_KEY:
MISSING_ENV_VARS.append('STAR_MICRONICS')
if not DEVICE_ID:
MISSING_ENV_VARS.append('STAR_DEVICE_ID')
def get_printer_service():
"""Lazy initialization of printer service - only create when needed"""
global printer_service
if not PRINTER_SERVICE_AVAILABLE:
return None
if printer_service is None:
try:
printer_service = StarPrinterService()
except Exception as e:
print(f"Error initializing printer service: {e}")
return None
return printer_service
# Component layout
component = dmc.Container([
# Interval for queue updates (disabled by default to prevent auto-refresh)
dcc.Interval(id='queue-refresh-interval', interval=5000, disabled=True, n_intervals=0),
# Header
dmc.Stack([
dmc.Group([
dmc.ThemeIcon(
DashIconify(icon="tabler:send", width=30),
size=40,
radius="md",
variant="light",
color="blue" if PRINTER_AVAILABLE else "red"
),
dmc.Title("Message the Creator", order=2)
]),
dmc.Text(
"Send a message directly to the creator of this documentation" if PRINTER_AVAILABLE else "Printer not configured - check environment variables",
c="dimmed" if PRINTER_AVAILABLE else "red",
size="sm"
)
], gap="xs", mb="lg"),
# Status indicator with detailed environment variable diagnostics
dmc.Card([
dmc.Stack([
# Overall status
dmc.Group([
DashIconify(
icon="tabler:circle-check" if PRINTER_AVAILABLE else "tabler:alert-circle",
width=20,
color="#16a34a" if PRINTER_AVAILABLE else "#dc2626"
),
dmc.Text(
"Printer Connected" if PRINTER_AVAILABLE else "Printer Not Available",
fw=600,
c="green" if PRINTER_AVAILABLE else "red"
)
]),
dmc.Divider(variant="dashed"),
# Environment variable status
dmc.Stack([
dmc.Text("Environment Variables", size="xs", fw=600, c="dimmed", tt="uppercase"),
dmc.Group([
DashIconify(
icon="tabler:check" if STAR_API_KEY else "tabler:x",
width=14,
color="#16a34a" if STAR_API_KEY else "#dc2626"
),
dmc.Text(
f"STAR_MICRONICS: {'Set (***{STAR_API_KEY[-4:]})' if STAR_API_KEY else '❌ NOT SET'}",
size="sm",
c="dimmed" if STAR_API_KEY else "red"
)
], gap="xs"),
dmc.Group([
DashIconify(
icon="tabler:check" if DEVICE_ID else "tabler:x",
width=14,
color="#16a34a" if DEVICE_ID else "#dc2626"
),
dmc.Text(
f"STAR_DEVICE_ID: {DEVICE_ID if DEVICE_ID else '❌ NOT SET'}",
size="sm",
c="dimmed" if DEVICE_ID else "red"
)
], gap="xs"),
dmc.Group([
DashIconify(icon="tabler:check", width=14, color="#16a34a"),
dmc.Text(f"STAR_GROUP_PATH: {GROUP_PATH}", size="sm", c="dimmed")
], gap="xs"),
dmc.Group([
DashIconify(icon="tabler:check", width=14, color="#16a34a"),
dmc.Text(f"STAR_REGION: {REGION}", size="sm", c="dimmed")
], gap="xs"),
], gap="xs"),
# Missing variables alert
dmc.Alert(
[
dmc.Stack([
dmc.Text("Missing Environment Variables", size="sm", fw=600),
dmc.Text(
f"Please configure: {', '.join(MISSING_ENV_VARS)}",
size="xs"
),
dmc.Text(
"On Render: Dashboard → Environment → Add Variable",
size="xs",
c="dimmed"
)
], gap="xs")
],
title="Configuration Required",
icon=DashIconify(icon="tabler:alert-triangle", width=20),
color="red",
variant="light"
) if MISSING_ENV_VARS else None,
], gap="sm")
], shadow="sm", padding="md", radius="md", withBorder=True, mb="lg"),
# Message composer
dmc.Grid([
# Left column - Message input
dmc.GridCol([
dmc.Card([
dmc.Stack([
dmc.Title("Compose Message", order=3, c="dimmed", size="h4"),
# Message template selector
dmc.Select(
id="message-template",
label="Message Type",
placeholder="Custom message",
data=[
{"value": "custom", "label": "Custom Message"},
],
value="custom",
leftSection=DashIconify(icon="tabler:message", width=16),
disabled=True
),
dmc.Divider(),
# Message editor
dmc.Textarea(
id="message-content",
label="Your Message",
description="Write your message to the creator - supports formatting like [bold], [align: center]",
placeholder="Write your message to the creator...",
minRows=10,
maxRows=15,
autosize=True,
value="[align: center]\n[bold][mag: w 2; h 2]Hello![normal][mag: w 1; h 1]\n\n[align: left]\nGreat documentation!\n\nI'm exploring dash-pos-printer and wanted to say hi.\n\n[align: center]\n{date} • {time}\n\n[cut]",
),
# Print settings
dmc.Group([
dmc.NumberInput(
id="live-copies",
label="Copies",
value=1,
min=1,
max=10,
step=1,
style={"flex": 1}
),
dmc.NumberInput(
id="live-buzzer",
label="Buzzer",
value=1,
min=0,
max=3,
step=1,
style={"flex": 1}
),
]),
])
], shadow="sm", padding="lg", radius="md", withBorder=True)
], span={"base": 12, "md": 6}),
# Right column - Tabs with Preview and Queue
dmc.GridCol([
dmc.Card([
dmc.Tabs([
dmc.TabsList([
dmc.TabsTab(
"Preview & Actions",
value="preview",
leftSection=DashIconify(icon="tabler:eye", width=16)
),
dmc.TabsTab(
"Queue Monitor",
value="queue",
leftSection=DashIconify(icon="tabler:list", width=16)
),
]),
# Preview tab
dmc.TabsPanel(value="preview", children=[
dmc.Stack([
# Preview area
dmc.Stack([
dmc.Text("Message Preview", size="sm", fw=600, c="dimmed"),
dmc.ScrollArea(
dmc.Code(
id="message-preview",
block=True,
style={
"whiteSpace": "pre",
"fontFamily": "monospace",
"fontSize": "12px",
"lineHeight": "1.4",
"minHeight": "200px"
}
),
h=200,
style={
"border": "1px solid var(--mantine-color-gray-3)",
"borderRadius": "var(--mantine-radius-sm)"
}
)
]),
dmc.Divider(),
# Action buttons
dmc.Stack([
dmc.Button(
"Send to Printer",
id="live-print-btn",
color="blue",
leftSection=DashIconify(icon="tabler:printer-check", width=20),
fullWidth=True,
size="lg",
disabled=not PRINTER_AVAILABLE
),
dmc.Button(
"Clear Message",
id="clear-message-btn",
variant="light",
color="gray",
leftSection=DashIconify(icon="tabler:eraser", width=20),
fullWidth=True
),
]),
# Info alert
dmc.Alert(
[
dmc.Stack([
dmc.Text("Message the Creator", size="sm", fw=600),
dmc.Text(
"Your message will be sent to the creator's printer via the Star Micronics CloudPRNT API. Make it fun!",
size="xs"
)
])
],
title="Direct to Creator",
icon=DashIconify(icon="tabler:info-circle", width=20),
color="blue" if PRINTER_AVAILABLE else "red",
variant="light"
)
], gap="md")
]),
# Queue tab
dmc.TabsPanel(value="queue", children=[
dmc.Stack([
dmc.Group([
dmc.Text("Current Queued Jobs", size="sm", fw=600, c="dimmed"),
dmc.Group([
dmc.Button(
"Refresh",
id="queue-refresh-btn",
size="xs",
variant="light",
leftSection=DashIconify(icon="tabler:refresh", width=14),
disabled=not PRINTER_AVAILABLE
),
dmc.Button(
"Clear Queue",
id="queue-clear-btn",
size="xs",
variant="light",
color="red",
leftSection=DashIconify(icon="tabler:trash", width=14),
disabled=not PRINTER_AVAILABLE
),
], gap="xs")
], justify="space-between"),
# Queue grid
dag.AgGrid(
id="queue-grid",
columnDefs=[
{"headerName": "Job ID", "field": "JobId", "flex": 1},
{"headerName": "Name", "field": "JobName", "flex": 1},
{"headerName": "Status", "field": "Status", "flex": 1},
{"headerName": "Created", "field": "Created", "flex": 1},
],
rowData=[],
defaultColDef={
"sortable": True,
"filter": True,
"resizable": True,
},
dashGridOptions={
"pagination": False,
"domLayout": "autoHeight",
},
style={"height": "250px"},
),
dmc.Text(
id="queue-status",
size="xs",
c="dimmed",
ta="center"
)
], gap="sm")
]),
], id="live-print-tabs", value="preview")
], shadow="sm", padding="lg", radius="md", withBorder=True)
], span={"base": 12, "md": 6})
], gutter="md"),
# Star Document Markup reference
dmc.Card([
dmc.Stack([
dmc.Title("Star Document Markup Quick Reference", order=4),
dmc.SimpleGrid([
dmc.Stack([
dmc.Text("Formatting", size="sm", fw=600, c="blue"),
dmc.Code("[bold]Bold text[normal]", block=True),
dmc.Code("[align: center]Centered", block=True),
dmc.Code("[align: left]Left aligned", block=True),
dmc.Code("[align: right]Right aligned", block=True),
], gap="xs"),
dmc.Stack([
dmc.Text("Size", size="sm", fw=600, c="blue"),
dmc.Code("[mag: w 2; h 2]Big text", block=True),
dmc.Code("[mag: w 1; h 1]Normal", block=True),
dmc.Code("[invert]Inverted colors", block=True),
dmc.Code("[ul]Underlined text", block=True),
], gap="xs"),
dmc.Stack([
dmc.Text("Special", size="sm", fw=600, c="blue"),
dmc.Code("[cut]Cut paper", block=True),
dmc.Code("[buzzer: pattern=A]Beep", block=True),
dmc.Code("---Dashed line---", block=True),
dmc.Code("===Double line===", block=True),
], gap="xs"),
], cols=3, spacing="md")
])
], shadow="sm", padding="lg", radius="md", withBorder=True, mt="lg")
], size="xl", px="md", py="xl")
@callback(
Output('message-preview', 'children'),
Input('message-content', 'value'),
)
def update_preview(content):
"""Update preview with formatted date/time"""
if not content:
return "No content to preview..."
now = datetime.now()
try:
preview = content.format(
date=now.strftime('%m/%d/%Y'),
time=now.strftime('%I:%M %p')
)
except Exception:
preview = content
return preview
@callback(
Output('message-content', 'value'),
Input('message-template', 'value'),
prevent_initial_call=True
)
def load_template(template):
"""Load default message template"""
# Only one template now - custom message for creator
return "[align: center]\n[bold][mag: w 2; h 2]Hello![normal][mag: w 1; h 1]\n\n[align: left]\nGreat documentation!\n\nI'm exploring dash-pos-printer and wanted to say hi.\n\n[align: center]\n{date} • {time}\n\n[cut]"
@callback(
Output('notification-container', 'sendNotifications', allow_duplicate=True),
Input('live-print-btn', 'n_clicks'),
State('message-content', 'value'),
State('live-copies', 'value'),
State('live-buzzer', 'value'),
prevent_initial_call=True
)
def handle_live_print(n_clicks, content, copies, buzzer):
"""Send message to actual printer"""
# Explicitly check if button was clicked (prevent auto-fire on page load)
if not n_clicks or n_clicks == 0:
return no_update
if not PRINTER_AVAILABLE:
return [{
"id": "printer-error",
"title": "Printer Not Available",
"message": "Please configure environment variables: STAR_MICRONICS, STAR_GROUP_PATH, STAR_DEVICE_ID",
"action": "show",
"color": "red",
"icon": DashIconify(icon="tabler:alert-circle", width=20),
"autoClose": 5000
}]
if not content:
return [{
"id": "no-content",
"title": "No Content",
"message": "Please enter a message to print",
"action": "show",
"color": "yellow",
"icon": DashIconify(icon="tabler:alert-triangle", width=20),
"autoClose": 3000
}]
try:
# Get printer service (lazy initialization)
service = get_printer_service()
if not service:
return [{
"id": "service-init-error",
"title": "Service Initialization Failed",
"message": "Could not initialize printer service",
"action": "show",
"color": "red",
"icon": DashIconify(icon="tabler:alert-circle", width=20),
"autoClose": 5000
}]
# Format message with current date/time
now = datetime.now()
try:
formatted_content = content.format(
date=now.strftime('%m/%d/%Y'),
time=now.strftime('%I:%M %p')
)
except Exception:
formatted_content = content
# Send to printer
result = service.print_receipt(
content=formatted_content,
job_name=f"Visitor Message {now.strftime('%H:%M:%S')}",
device_id=DEVICE_ID,
copies=copies or 1,
endbuzzer=buzzer or 0
)
# Check result
if "error" in result:
return [{
"id": "print-error",
"title": "Print Failed",
"message": f"Error: {result['error']}",
"action": "show",
"color": "red",
"icon": DashIconify(icon="tabler:alert-circle", width=20),
"autoClose": 5000
}]
return [{
"id": "print-success",
"title": "Message Sent!",
"message": f"Your message has been sent to the creator's printer!",
"action": "show",
"color": "green",
"icon": DashIconify(icon="tabler:circle-check", width=20),
"autoClose": 3000
}]
except Exception as e:
return [{
"id": "print-exception",
"title": "Print Error",
"message": f"Exception: {str(e)}",
"action": "show",
"color": "red",
"icon": DashIconify(icon="tabler:bug", width=20),
"autoClose": 5000
}]
@callback(
Output('message-content', 'value', allow_duplicate=True),
Input('clear-message-btn', 'n_clicks'),
prevent_initial_call=True
)
def clear_message(n_clicks):
"""Clear the message content"""
return ""
@callback(
[Output('queue-grid', 'rowData'),
Output('queue-status', 'children')],
[Input('queue-refresh-btn', 'n_clicks'),
Input('queue-refresh-interval', 'n_intervals')],
prevent_initial_call=True
)
def update_queue(n_clicks, n_intervals):
"""Fetch and update queue data"""
if not PRINTER_AVAILABLE:
return [], "Printer not available"
try:
# Get printer service (lazy initialization)
service = get_printer_service()
if not service:
return [], "Service initialization failed"
queue_data = service.get_queue(DEVICE_ID)
if not queue_data or len(queue_data) == 0:
return [], f"No jobs in queue - Last checked: {datetime.now().strftime('%I:%M:%S %p')}"
# Format queue data for AG Grid
rows = []
for job in queue_data:
rows.append({
"JobId": job.get("JobId", "N/A"),
"JobName": job.get("JobName", "Unnamed"),
"Status": job.get("Status", "Pending"),
"Created": job.get("Created", "Unknown"),
})
return rows, f"{len(rows)} job(s) in queue - Last updated: {datetime.now().strftime('%I:%M:%S %p')}"
except Exception as e:
return [], f"Error fetching queue: {str(e)}"
@callback(
Output('notification-container', 'sendNotifications', allow_duplicate=True),
Input('queue-clear-btn', 'n_clicks'),
prevent_initial_call=True
)
def handle_clear_queue(n_clicks):
"""Clear all pending print jobs from queue"""
# Explicitly check if button was clicked (prevent auto-fire on page load)
if not n_clicks or n_clicks == 0:
return no_update
if not PRINTER_AVAILABLE:
return [{
"id": "queue-clear-error",
"title": "Printer Not Available",
"message": "Cannot clear queue - printer not configured",
"action": "show",
"color": "red",
"icon": DashIconify(icon="tabler:alert-circle", width=20),
"autoClose": 3000
}]
try:
# Get printer service (lazy initialization)
service = get_printer_service()
if not service:
return [{
"id": "service-init-error",
"title": "Service Initialization Failed",
"message": "Could not initialize printer service",
"action": "show",
"color": "red",
"icon": DashIconify(icon="tabler:alert-circle", width=20),
"autoClose": 3000
}]
success = service.clear_queue(DEVICE_ID)
if success:
return [{
"id": "queue-cleared",
"title": "Queue Cleared",
"message": "All pending print jobs have been cleared",
"action": "show",
"color": "green",
"icon": DashIconify(icon="tabler:check", width=20),
"autoClose": 2000
}]
else:
return [{
"id": "queue-clear-failed",
"title": "Clear Failed",
"message": "Could not clear the print queue",
"action": "show",
"color": "red",
"icon": DashIconify(icon="tabler:alert-circle", width=20),
"autoClose": 3000
}]
except Exception as e:
return [{
"id": "queue-clear-exception",
"title": "Clear Error",
"message": f"Error clearing queue: {str(e)}",
"action": "show",
"color": "red",
"icon": DashIconify(icon="tabler:bug", width=20),
"autoClose": 3000
}]
@callback(
[Output('queue-grid', 'rowData', allow_duplicate=True),
Output('queue-status', 'children', allow_duplicate=True)],
Input('live-print-tabs', 'value'),
prevent_initial_call=True
)
def load_queue_on_tab_change(tab_value):
"""Load queue data when Queue Monitor tab is selected"""
if tab_value != "queue":
return no_update, no_update
if not PRINTER_AVAILABLE:
return [], "Printer not available"
try:
# Get printer service (lazy initialization)
service = get_printer_service()
if not service:
return [], "Service initialization failed"
queue_data = service.get_queue(DEVICE_ID)
if not queue_data or len(queue_data) == 0:
return [], f"No jobs in queue - Last checked: {datetime.now().strftime('%I:%M:%S %p')}"
# Format queue data for AG Grid
rows = []
for job in queue_data:
rows.append({
"JobId": job.get("JobId", "N/A"),
"JobName": job.get("JobName", "Unnamed"),
"Status": job.get("Status", "Pending"),
"Created": job.get("Created", "Unknown"),
})
return rows, f"{len(rows)} job(s) in queue - Last checked: {datetime.now().strftime('%I:%M:%S %p')}"
except Exception as e:
return [], f"Error fetching queue: {str(e)}"
This example connects to your real printer using the Star Micronics CloudPRNT API. Features:
- Real API Connection - Uses environment variables to connect to actual printer
- Custom Message Composer - Write any message with live preview
- Template Library - Pre-built templates for quick printing
- Live Formatting - Automatic date/time injection
- Error Handling - Comprehensive error messages and diagnostics
- Connection Status - Shows printer availability and configuration
Environment Variables Required:
STAR_MICRONICS- Your Star Micronics API keySTAR_GROUP_PATH- Your device group pathSTAR_DEVICE_ID- Target printer device IDSTAR_REGION- API region (US or EU)
Resources
Official Documentation
- Star Micronics Docs
- StarIO.Online Dashboard (US) / EU
- CloudPRNT Specification - Available in dashboard Help section
Printer Manuals
Support
Last Updated: November 2025