Files
vet-print-client/client.py

193 lines
7.5 KiB
Python

import os
import json
import time
import threading
import paho.mqtt.client as mqtt
from PIL import Image, ImageDraw, ImageFont
from datetime import date
from brother_ql.raster import BrotherQLRaster
from brother_ql.conversion import convert
from brother_ql.backends.helpers import send
from templates import TEMPLATES
# Load configuration from environment
MQTT_HOST = os.getenv('MQTT_HOST', 'localhost')
MQTT_PORT = int(os.getenv('MQTT_PORT', 1883))
MQTT_TOPIC_SUB = os.getenv('MQTT_TOPIC_SUB', 'vet/labels/print')
MQTT_TOPIC_PUB_STATUS = os.getenv('MQTT_TOPIC_PUB_STATUS', 'vet/labels/status')
MQTT_TOPIC_HEARTBEAT = os.getenv('MQTT_TOPIC_HEARTBEAT', 'vet/labels/heartbeat')
PRINTER_DEVICE = os.getenv('PRINTER_DEVICE', '/dev/usb/lp0')
PRINTER_MODEL = os.getenv('PRINTER_MODEL', 'QL-800')
LABEL_SIZE_DEFAULT = os.getenv('LABEL_SIZE_DEFAULT', '29x90')
def print_label(image, printer=PRINTER_DEVICE, model=PRINTER_MODEL, label=LABEL_SIZE_DEFAULT):
"""Print the label directly using brother_ql module"""
import os
try:
# Check if printer device exists
if not os.path.exists(printer):
raise Exception(f"Printer device {printer} not found. Make sure the printer is powered on and connected via USB.")
qlr = BrotherQLRaster(model)
qlr.exception_on_warning = True
# Convert the PIL image to instructions
instructions = convert(qlr=qlr, images=[image], label=label, cut=True)
# Send to printer using linux_kernel backend
print(f"Sending to printer: {printer}")
status = send(instructions=instructions, printer_identifier=f"file://{printer}", backend_identifier='linux_kernel', blocking=True)
return status
except Exception as e:
raise e
def on_connect(client, userdata, flags, rc):
print(f"Connected to MQTT broker at {MQTT_HOST}:{MQTT_PORT} with result code {rc}")
if rc == 0:
print(f"Subscribing to topic: {MQTT_TOPIC_SUB}")
client.subscribe(MQTT_TOPIC_SUB)
print("Subscription successful")
else:
print(f"Failed to connect, result code: {rc}")
def on_message(client, userdata, msg):
try:
raw_payload = msg.payload.decode('utf-8')
except UnicodeDecodeError:
print(f"Received non-UTF-8 message on topic '{msg.topic}': {msg.payload}")
return
print(f"Raw message received on topic '{msg.topic}': {raw_payload}")
job_id = None
try:
payload = json.loads(raw_payload)
print(f"Parsed payload: {payload}")
job_id = payload.get('job_id')
template_id = payload.get('template_id', 'vet_label')
label_size = payload.get('label_size', LABEL_SIZE_DEFAULT)
variables = payload.get('variables', {})
test = payload.get('test', False)
print(f"Processing: template_id={template_id}, label_size={label_size}, test={test}")
# For now, only support one template
if template_id not in TEMPLATES:
raise ValueError(f"Unknown template_id: {template_id}")
template_func = TEMPLATES[template_id]
# TODO: Adjust dimensions based on label_size if needed
# For simplicity, using fixed dimensions
filename = f"/app/output/label_{template_id}.png" if test else None
image = template_func(variables, filename=filename)
if test:
print(f"Test mode: PNG saved as {filename}")
# Publish success status for test mode
success_details = {
"status": "success",
"job_id": job_id,
"template_id": template_id,
"label_size": label_size,
"test_mode": True,
"filename": filename,
"timestamp": time.time()
}
client.publish(MQTT_TOPIC_PUB_STATUS, json.dumps(success_details))
else:
print("Printing label...")
status = print_label(image, label=label_size)
print(f"Print status: {status}")
# Check for print errors
if not status.get('did_print', False):
printer_state = status.get('printer_state', {})
status_type = printer_state.get('status_type', 'Unknown')
media_type = printer_state.get('media_type', 'Unknown')
media_width = printer_state.get('media_width', 'Unknown')
errors = printer_state.get('errors', [])
error_details = {
"status": "error",
"job_id": job_id,
"error": f"Print failed: {status_type}",
"status_type": status_type,
"media_type": media_type,
"media_width": media_width,
"errors": errors,
"outcome": status.get('outcome', 'unknown'),
"original_payload": raw_payload
}
error_msg = f"Print failed: {status_type}. Media: {media_type} ({media_width}mm)"
print(error_msg)
client.publish(MQTT_TOPIC_PUB_STATUS, json.dumps(error_details))
raise Exception(error_msg)
else:
# Print successful - publish success status
success_details = {
"status": "success",
"job_id": job_id,
"template_id": template_id,
"label_size": label_size,
"timestamp": time.time()
}
print(f"Print successful: {template_id}")
client.publish(MQTT_TOPIC_PUB_STATUS, json.dumps(success_details))
except json.JSONDecodeError as e:
error_msg = f"Invalid JSON in message: {e}. Raw payload: {raw_payload}"
print(error_msg)
client.publish(MQTT_TOPIC_PUB_STATUS, json.dumps({"status": "error", "job_id": job_id, "error": error_msg, "topic": msg.topic}))
except Exception as e:
error_msg = f"Error processing message: {str(e)}"
print(error_msg)
client.publish(MQTT_TOPIC_PUB_STATUS, json.dumps({"status": "error", "job_id": job_id, "error": error_msg, "original_payload": raw_payload}))
def heartbeat(client):
while True:
try:
heartbeat_msg = {
"status": "alive",
"timestamp": time.time(),
"host": MQTT_HOST,
"port": MQTT_PORT
}
client.publish(MQTT_TOPIC_HEARTBEAT, json.dumps(heartbeat_msg))
print(f"Published heartbeat: {heartbeat_msg}")
time.sleep(30) # Publish every 30 seconds
except Exception as e:
print(f"Error publishing heartbeat: {e}")
time.sleep(5) # Retry sooner on error
def main():
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
print(f"Attempting to connect to MQTT broker at {MQTT_HOST}:{MQTT_PORT}")
client.connect(MQTT_HOST, MQTT_PORT, 60)
# Start the network loop in a background thread
client.loop_start()
# Start heartbeat thread
heartbeat_thread = threading.Thread(target=heartbeat, args=(client,))
heartbeat_thread.daemon = True
heartbeat_thread.start()
print("Client is running. Press Ctrl+C to stop.")
try:
while True:
time.sleep(1) # Keep main thread alive
except KeyboardInterrupt:
print("Shutting down...")
client.loop_stop()
client.disconnect()
if __name__ == "__main__":
main()