Websockets MQTT support

This commit is contained in:
2026-02-09 20:23:11 +00:00
parent af7c4ade1d
commit ae622aa5bd
2 changed files with 129 additions and 6 deletions

View File

@@ -2,6 +2,7 @@ import os
import json import json
import time import time
import threading import threading
import ssl
import paho.mqtt.client as mqtt import paho.mqtt.client as mqtt
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
from datetime import date from datetime import date
@@ -13,6 +14,11 @@ from templates import TEMPLATES
# Load configuration from environment # Load configuration from environment
MQTT_HOST = os.getenv('MQTT_HOST', 'localhost') MQTT_HOST = os.getenv('MQTT_HOST', 'localhost')
MQTT_PORT = int(os.getenv('MQTT_PORT', 1883)) 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_SUB = os.getenv('MQTT_TOPIC_SUB', 'vet/labels/print')
MQTT_TOPIC_PUB_STATUS = os.getenv('MQTT_TOPIC_PUB_STATUS', 'vet/labels/status') MQTT_TOPIC_PUB_STATUS = os.getenv('MQTT_TOPIC_PUB_STATUS', 'vet/labels/status')
MQTT_TOPIC_HEARTBEAT = os.getenv('MQTT_TOPIC_HEARTBEAT', 'vet/labels/heartbeat') 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: except json.JSONDecodeError as e:
error_msg = f"Invalid JSON in message: {e}. Raw payload: {raw_payload}" error_msg = f"Invalid JSON in message: {e}. Raw payload: {raw_payload}"
print(error_msg) print(error_msg)
client.publish(MQTT_TOPIC_PUB_STATUS, json.dumps({"status": "error", "job_id": job_id, "error": error_msg, "topic": msg.topic})) error_details = {"status": "error", "job_id": job_id, "error": error_msg, "topic": msg.topic}
except Exception as e: client.publish(MQTT_TOPIC_PUB_STATUS, json.dumps(error_details))
send_webhook("parse_error", error_details)
error_msg = f"Error processing message: {str(e)}" error_msg = f"Error processing message: {str(e)}"
print(error_msg) 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): def heartbeat(client):
while True: while True:
try: try:
@@ -155,7 +163,8 @@ def heartbeat(client):
"status": "alive", "status": "alive",
"timestamp": time.time(), "timestamp": time.time(),
"host": MQTT_HOST, "host": MQTT_HOST,
"port": MQTT_PORT "port": MQTT_PORT,
"websocket": MQTT_USE_WEBSOCKET
} }
client.publish(MQTT_TOPIC_HEARTBEAT, json.dumps(heartbeat_msg)) client.publish(MQTT_TOPIC_HEARTBEAT, json.dumps(heartbeat_msg))
time.sleep(30) # Publish every 30 seconds time.sleep(30) # Publish every 30 seconds
@@ -164,11 +173,45 @@ def heartbeat(client):
time.sleep(5) # Retry sooner on error time.sleep(5) # Retry sooner on error
def main(): 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_connect = on_connect
client.on_message = on_message 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}") 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) client.connect(MQTT_HOST, MQTT_PORT, 60)
# Start the network loop in a background thread # Start the network loop in a background thread

View File

@@ -148,9 +148,89 @@ def new_label_template(variables, width_pixels=991, height_pixels=306, filename=
return image 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 # Template registry
TEMPLATES = { TEMPLATES = {
'vet_label': vet_label_template, 'vet_label': vet_label_template,
'new_label': new_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 # Add more templates here, e.g., 'vet_label_small': vet_label_small_template
} }