diff --git a/client.py b/client.py index cd1aece..b7c5e3b 100644 --- a/client.py +++ b/client.py @@ -2,6 +2,7 @@ import os import json import time import threading +import ssl import paho.mqtt.client as mqtt from PIL import Image, ImageDraw, ImageFont from datetime import date @@ -13,6 +14,11 @@ from templates import TEMPLATES # Load configuration from environment MQTT_HOST = os.getenv('MQTT_HOST', 'localhost') MQTT_PORT = int(os.getenv('MQTT_PORT', 1883)) +MQTT_USE_WEBSOCKET = os.getenv('MQTT_USE_WEBSOCKET', 'False').lower() == 'true' +MQTT_WEBSOCKET_PATH = os.getenv('MQTT_WEBSOCKET_PATH', '/mqtt') +MQTT_TLS_INSECURE = os.getenv('MQTT_TLS_INSECURE', 'False').lower() == 'true' +MQTT_USERNAME = os.getenv('MQTT_USERNAME', '') +MQTT_PASSWORD = os.getenv('MQTT_PASSWORD', '') 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') @@ -142,12 +148,14 @@ def on_message(client, userdata, msg): 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_details = {"status": "error", "job_id": job_id, "error": error_msg, "topic": msg.topic} + client.publish(MQTT_TOPIC_PUB_STATUS, json.dumps(error_details)) + send_webhook("parse_error", error_details) 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})) - + error_details = {"status": "error", "job_id": job_id, "error": error_msg, "original_payload": raw_payload} + client.publish(MQTT_TOPIC_PUB_STATUS, json.dumps(error_details)) + send_webhook("processing_error", error_details) def heartbeat(client): while True: try: @@ -155,7 +163,8 @@ def heartbeat(client): "status": "alive", "timestamp": time.time(), "host": MQTT_HOST, - "port": MQTT_PORT + "port": MQTT_PORT, + "websocket": MQTT_USE_WEBSOCKET } client.publish(MQTT_TOPIC_HEARTBEAT, json.dumps(heartbeat_msg)) time.sleep(30) # Publish every 30 seconds @@ -164,11 +173,45 @@ def heartbeat(client): time.sleep(5) # Retry sooner on error def main(): - client = mqtt.Client() + client = mqtt.Client(client_id="printer_client", transport="websockets" if MQTT_USE_WEBSOCKET else "tcp") client.on_connect = on_connect client.on_message = on_message + # Configure TLS if using WebSocket + if MQTT_USE_WEBSOCKET: + if MQTT_TLS_INSECURE: + # Disable TLS certificate verification (use with caution) + print("WARNING: TLS certificate verification is disabled!") + client.tls_set( + ca_certs=None, + certfile=None, + keyfile=None, + cert_reqs=ssl.CERT_NONE, + tls_version=ssl.PROTOCOL_TLS, + ciphers=None + ) + client.tls_insecure_set(True) + else: + # Use default CA certificates + client.tls_set( + ca_certs=None, + certfile=None, + keyfile=None, + cert_reqs=ssl.CERT_REQUIRED, + tls_version=ssl.PROTOCOL_TLS, + ciphers=None + ) + + # Set username and password if provided + if MQTT_USERNAME or MQTT_PASSWORD: + client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD) + + # Construct connection string with WebSocket path if needed print(f"Attempting to connect to MQTT broker at {MQTT_HOST}:{MQTT_PORT}") + if MQTT_USE_WEBSOCKET: + print(f"Using WebSocket transport with path: {MQTT_WEBSOCKET_PATH}") + print(f"TLS Certificate Validation: {'Disabled' if MQTT_TLS_INSECURE else 'Enabled'}") + client.connect(MQTT_HOST, MQTT_PORT, 60) # Start the network loop in a background thread diff --git a/templates.py b/templates.py index 507038c..9cf41da 100644 --- a/templates.py +++ b/templates.py @@ -148,9 +148,89 @@ def new_label_template(variables, width_pixels=991, height_pixels=306, filename= return image +def new_label_large_template(variables, width_pixels=991, height_pixels=413, filename=None): + """Simplified label template with centered drug and dose - larger format""" + image = Image.new('RGB', (width_pixels, height_pixels), 'white') + draw = ImageDraw.Draw(image) + + try: + font_header = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 36) + font_bold = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 48) + font_normal = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 36) + font_small = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 28) + font_footer = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Oblique.ttf', 22) + except: + font_header = ImageFont.load_default() + font_bold = ImageFont.load_default() + font_normal = ImageFont.load_default() + font_small = ImageFont.load_default() + font_footer = ImageFont.load_default() + + margin = 10 + top = margin + left_col = margin + x1 = 200 + x2 = 500 + x3 = 580 + line_height = 60 + + # Practice name (top left) + draw.text((left_col, top), variables.get('practice_name', ''), fill='black', font=font_header) + + # Date (top right) + today = date.today().strftime("%d %b %Y") + bbox = draw.textbbox((0, 0), today, font=font_small) + date_width = bbox[2] - bbox[0] + draw.text((width_pixels - margin - date_width, top), today, fill='black', font=font_small) + + # Centered drug name (bold) + y = top + line_height + 20 + drug_name = variables.get('drug_name', '') + bbox = draw.textbbox((0, 0), drug_name, font=font_bold) + drug_width = bbox[2] - bbox[0] + drug_x = (width_pixels - drug_width) / 2 + drug_y = y + draw.text((drug_x, drug_y), drug_name, fill='black', font=font_bold) + + # Centered dose underneath + dosage = variables.get('dosage', '') + bbox = draw.textbbox((0, 0), dosage, font=font_normal) + dose_width = bbox[2] - bbox[0] + dose_x = (width_pixels - dose_width) / 2 + dose_y = drug_y + 70 + draw.text((dose_x, dose_y), dosage, fill='black', font=font_normal) + + # Animal and Qty on same line below drug/dose + animal_qty_y = dose_y + 70 + draw.text((left_col, animal_qty_y), "Animal:", fill='black', font=font_normal) + draw.text((x1, animal_qty_y), variables.get('animal_name', ''), fill='black', font=font_normal) + draw.text((x2, animal_qty_y), "Qty:", fill='black', font=font_normal) + draw.text((x3, animal_qty_y), variables.get('quantity', ''), fill='black', font=font_normal) + + # Expiry and Vet on next line + expiry_vet_y = animal_qty_y + line_height + draw.text((left_col, expiry_vet_y), "Expiry:", fill='black', font=font_normal) + draw.text((x1, expiry_vet_y), variables.get('expiry_date', ''), fill='black', font=font_normal) + draw.text((x2, expiry_vet_y), "Vet:", fill='black', font=font_normal) + draw.text((x3, expiry_vet_y), "________________________", fill='black', font=font_normal) + + # Footer at bottom + footer_text = "For animal treatment only" + bbox = draw.textbbox((0, 0), footer_text, font=font_footer) + text_width = bbox[2] - bbox[0] + footer_x = (width_pixels - text_width) / 2 + footer_y = height_pixels - margin - 20 + draw.text((footer_x, footer_y), footer_text, fill='black', font=font_footer) + + if filename: + image.save(filename) + + return image + # Template registry TEMPLATES = { 'vet_label': vet_label_template, 'new_label': new_label_template, + 'new_label_large': new_label_large_template, # Add more templates here, e.g., 'vet_label_small': vet_label_small_template } \ No newline at end of file