Compare commits
2 Commits
ee6eafd6b3
...
b412900b4b
| Author | SHA1 | Date | |
|---|---|---|---|
| b412900b4b | |||
| 0fab91c077 |
@@ -6,7 +6,6 @@ ENV PYTHONUNBUFFERED=1
|
|||||||
# Install system dependencies
|
# Install system dependencies
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
fonts-dejavu \
|
fonts-dejavu \
|
||||||
usbutils \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Set working directory
|
# Set working directory
|
||||||
@@ -16,9 +15,6 @@ WORKDIR /app
|
|||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
# Copy the application
|
# Application files will be mounted via volume
|
||||||
COPY client.py .
|
|
||||||
COPY templates.py .
|
|
||||||
|
|
||||||
# Run the application
|
# Run the application
|
||||||
CMD ["python", "client.py"]
|
CMD ["python", "client.py"]
|
||||||
34
client.py
34
client.py
@@ -22,15 +22,22 @@ LABEL_SIZE_DEFAULT = os.getenv('LABEL_SIZE_DEFAULT', '29x90')
|
|||||||
|
|
||||||
def print_label(image, printer=PRINTER_DEVICE, model=PRINTER_MODEL, label=LABEL_SIZE_DEFAULT):
|
def print_label(image, printer=PRINTER_DEVICE, model=PRINTER_MODEL, label=LABEL_SIZE_DEFAULT):
|
||||||
"""Print the label directly using brother_ql module"""
|
"""Print the label directly using brother_ql module"""
|
||||||
|
import os
|
||||||
|
|
||||||
try:
|
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 = BrotherQLRaster(model)
|
||||||
qlr.exception_on_warning = True
|
qlr.exception_on_warning = True
|
||||||
|
|
||||||
# Convert the PIL image to instructions
|
# Convert the PIL image to instructions
|
||||||
instructions = convert(qlr=qlr, images=[image], label=label, cut=True)
|
instructions = convert(qlr=qlr, images=[image], label=label, cut=True)
|
||||||
|
|
||||||
# Send to printer
|
# Send to printer using linux_kernel backend
|
||||||
status = send(instructions=instructions, printer_identifier=printer, backend_identifier='linux_kernel', blocking=True)
|
print(f"Sending to printer: {printer}")
|
||||||
|
status = send(instructions=instructions, printer_identifier=f"file://{printer}", backend_identifier='linux_kernel', blocking=True)
|
||||||
|
|
||||||
return status
|
return status
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -83,6 +90,29 @@ def on_message(client, userdata, msg):
|
|||||||
status = print_label(image, label=label_size)
|
status = print_label(image, label=label_size)
|
||||||
print(f"Print status: {status}")
|
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 = {
|
||||||
|
"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_ERRORS, json.dumps(error_details))
|
||||||
|
raise Exception(error_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)
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ services:
|
|||||||
build: .
|
build: .
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
devices:
|
privileged: true # Required for /dev access when printer powers on/off
|
||||||
- /dev/usb/lp0:/dev/usb/lp0
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./output:/app/output # For test PNGs
|
- ./output:/app/output # For test PNGs
|
||||||
|
- ./client.py:/app/client.py # Mount source for live updates
|
||||||
|
- ./templates.py:/app/templates.py # Mount templates for live updates
|
||||||
|
- /dev:/dev # Mount /dev for dynamic device access
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
80
templates.py
80
templates.py
@@ -69,8 +69,88 @@ def vet_label_template(variables, width_pixels=991, height_pixels=306, filename=
|
|||||||
|
|
||||||
return image
|
return image
|
||||||
|
|
||||||
|
def new_label_template(variables, width_pixels=991, height_pixels=306, filename=None):
|
||||||
|
"""Simplified label template with centered drug and dose"""
|
||||||
|
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 = 50
|
||||||
|
|
||||||
|
# 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 + 10
|
||||||
|
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 + 60
|
||||||
|
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 + 50
|
||||||
|
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,
|
||||||
# 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