Websockets MQTT support
This commit is contained in:
55
client.py
55
client.py
@@ -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
|
||||||
|
|||||||
80
templates.py
80
templates.py
@@ -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
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user