Printer realtime status

This commit is contained in:
2026-02-07 09:38:36 -05:00
parent 20d8cefe22
commit 7fd2e0050b
4 changed files with 200 additions and 11 deletions

View File

@@ -6,7 +6,7 @@ from datetime import datetime, timedelta
from .database import engine, get_db, Base from .database import engine, get_db, Base
from .models import Drug, DrugVariant, Dispensing, User from .models import Drug, DrugVariant, Dispensing, User
from .auth import hash_password, verify_password, create_access_token, get_current_user, get_current_admin_user, ACCESS_TOKEN_EXPIRE_MINUTES from .auth import hash_password, verify_password, create_access_token, get_current_user, get_current_admin_user, ACCESS_TOKEN_EXPIRE_MINUTES
from .mqtt_service import publish_label_print from .mqtt_service import publish_label_print_with_response
from pydantic import BaseModel from pydantic import BaseModel
# Create tables # Create tables
@@ -543,19 +543,31 @@ def print_label(label_request: LabelPrintRequest, current_user: User = Depends(g
"test": test_mode "test": test_mode
} }
# Publish to MQTT # Publish to MQTT and wait for response
success = publish_label_print(mqtt_message) success, response = publish_label_print_with_response(mqtt_message, timeout=10.0)
print(f"Label print result: success={success}, response={response}")
if success: if success:
return LabelPrintResponse( result = LabelPrintResponse(
success=True, success=True,
message="Label print request sent successfully" message=response.get("message", "Label printed successfully")
) )
print(f"Returning success response: {result}")
return result
else: else:
raise HTTPException( # Return error details from printer
status_code=500, # Check both 'message' and 'error' fields for error details
detail="Failed to send label print request to MQTT broker" if response:
error_msg = response.get("message") or response.get("error", "Unknown error")
else:
error_msg = "No response from printer"
result = LabelPrintResponse(
success=False,
message=f"Print failed: {error_msg}"
) )
print(f"Returning error response: {result}")
return result
except Exception as e: except Exception as e:
raise HTTPException( raise HTTPException(

View File

@@ -1,8 +1,12 @@
import os import os
import json import json
import logging import logging
from typing import Optional import uuid
import time
from typing import Optional, Dict, Any
import paho.mqtt.client as mqtt import paho.mqtt.client as mqtt
import threading
import atexit
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -12,10 +16,169 @@ MQTT_BROKER_PORT = int(os.getenv("MQTT_BROKER_PORT", "1883"))
MQTT_USERNAME = os.getenv("MQTT_USERNAME", "") MQTT_USERNAME = os.getenv("MQTT_USERNAME", "")
MQTT_PASSWORD = os.getenv("MQTT_PASSWORD", "") MQTT_PASSWORD = os.getenv("MQTT_PASSWORD", "")
MQTT_LABEL_TOPIC = os.getenv("MQTT_LABEL_TOPIC", "vet/labels/print") MQTT_LABEL_TOPIC = os.getenv("MQTT_LABEL_TOPIC", "vet/labels/print")
MQTT_STATUS_TOPIC = os.getenv("MQTT_STATUS_TOPIC", "vet/labels/status")
# Store responses by job_id
_response_store: Dict[str, Any] = {}
_response_lock = threading.Lock()
# Persistent MQTT client
_mqtt_client: Optional[mqtt.Client] = None
_client_connected = threading.Event()
_client_lock = threading.Lock()
_initialization_attempted = False
def on_connect(client, userdata, flags, rc):
"""Callback when connected to broker"""
if rc == 0:
print(f"MQTT: Connected to broker at {MQTT_BROKER_HOST}:{MQTT_BROKER_PORT}")
# Subscribe to status topic
result = client.subscribe(MQTT_STATUS_TOPIC, qos=1)
print(f"MQTT: Subscribed to {MQTT_STATUS_TOPIC}, result={result}")
_client_connected.set()
else:
print(f"MQTT: Failed to connect, return code: {rc}")
_client_connected.clear()
def on_disconnect(client, userdata, rc):
"""Callback when disconnected from broker"""
print(f"MQTT: Disconnected, return code: {rc}")
_client_connected.clear()
def on_status_message(client, userdata, message):
"""Callback for status messages"""
print(f"MQTT: Received message on topic '{message.topic}': {message.payload.decode()[:200]}")
try:
payload = json.loads(message.payload.decode())
job_id = payload.get("job_id")
if job_id:
with _response_lock:
_response_store[job_id] = payload
print(f"MQTT: Stored response for job {job_id}: {payload.get('status')}")
else:
print(f"MQTT: Message has no job_id: {payload}")
except Exception as e:
print(f"MQTT: Error processing status message: {str(e)}")
def get_mqtt_client():
"""Get or initialize the persistent MQTT client"""
global _mqtt_client, _initialization_attempted
with _client_lock:
# If client exists and is connected, return it
if _mqtt_client is not None and _client_connected.is_set():
return _mqtt_client
# If we already tried and failed recently, don't retry immediately
if _initialization_attempted and _mqtt_client is None:
return None
_initialization_attempted = True
try:
print(f"MQTT: Initializing client connection to {MQTT_BROKER_HOST}:{MQTT_BROKER_PORT}")
# Clean up old client if exists
if _mqtt_client is not None:
try:
_mqtt_client.loop_stop()
_mqtt_client.disconnect()
except:
pass
# Create new MQTT client
_mqtt_client = mqtt.Client(client_id=f"drug-inventory-main")
# Set username and password if provided
if MQTT_USERNAME and MQTT_PASSWORD:
_mqtt_client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
# Set up callbacks
_mqtt_client.on_connect = on_connect
_mqtt_client.on_disconnect = on_disconnect
_mqtt_client.on_message = on_status_message
# Connect to broker
_mqtt_client.connect(MQTT_BROKER_HOST, MQTT_BROKER_PORT, 60)
# Start network loop in background
_mqtt_client.loop_start()
# Wait for connection (max 3 seconds)
if _client_connected.wait(timeout=3.0):
print("MQTT: Client initialized successfully")
return _mqtt_client
else:
print("MQTT: Connection timeout")
_mqtt_client.loop_stop()
_mqtt_client = None
return None
except Exception as e:
print(f"MQTT: Error initializing client: {str(e)}")
_mqtt_client = None
return None
def publish_label_print_with_response(label_data: dict, timeout: float = 5.0) -> tuple[bool, Optional[dict]]:
"""
Publish a label print request to MQTT broker and wait for response
Args:
label_data: Dictionary containing label print information
timeout: Maximum time to wait for response in seconds
Returns:
Tuple of (success, response_data)
"""
# Get or initialize MQTT client
client = get_mqtt_client()
if client is None:
print("MQTT: Client not available")
return False, {"status": "error", "message": "MQTT client not connected"}
job_id = str(uuid.uuid4())
label_data["job_id"] = job_id
try:
# Publish message
message = json.dumps(label_data)
result = client.publish(MQTT_LABEL_TOPIC, message, qos=1)
if result.rc != mqtt.MQTT_ERR_SUCCESS:
print(f"MQTT: Failed to publish, rc={result.rc}")
return False, {"status": "error", "message": "Failed to publish message"}
print(f"MQTT: Published job {job_id}")
# Wait for response
start_time = time.time()
while time.time() - start_time < timeout:
with _response_lock:
if job_id in _response_store:
response = _response_store.pop(job_id)
# Check if print was successful
status = response.get("status", "").lower()
success = status in ["success", "completed", "ok"]
print(f"MQTT: Job {job_id} completed with status: {status}")
return success, response
time.sleep(0.05) # Check every 50ms
# Timeout - no response received
print(f"MQTT: Timeout waiting for job {job_id}")
# Clean up in case response arrives late
with _response_lock:
_response_store.pop(job_id, None)
return False, {"status": "timeout", "message": "No response from printer"}
except Exception as e:
print(f"MQTT: Error in publish_label_print_with_response: {str(e)}")
return False, {"status": "error", "message": str(e)}
def publish_label_print(label_data: dict) -> bool: def publish_label_print(label_data: dict) -> bool:
""" """
Publish a label print request to MQTT broker Publish a label print request to MQTT broker (fire and forget)
Args: Args:
label_data: Dictionary containing label print information label_data: Dictionary containing label print information

View File

@@ -19,6 +19,7 @@ services:
- MQTT_USERNAME=${MQTT_USERNAME:-} - MQTT_USERNAME=${MQTT_USERNAME:-}
- MQTT_PASSWORD=${MQTT_PASSWORD:-} - MQTT_PASSWORD=${MQTT_PASSWORD:-}
- MQTT_LABEL_TOPIC=${MQTT_LABEL_TOPIC:-vet/labels/print} - MQTT_LABEL_TOPIC=${MQTT_LABEL_TOPIC:-vet/labels/print}
- MQTT_STATUS_TOPIC=${MQTT_STATUS_TOPIC:-vet/labels/status}
- LABEL_TEMPLATE_ID=${LABEL_TEMPLATE_ID:-vet_label} - LABEL_TEMPLATE_ID=${LABEL_TEMPLATE_ID:-vet_label}
- LABEL_SIZE=${LABEL_SIZE:-29x90} - LABEL_SIZE=${LABEL_SIZE:-29x90}
- LABEL_TEST=${LABEL_TEST:-false} - LABEL_TEST=${LABEL_TEST:-false}

View File

@@ -742,7 +742,20 @@ async function handlePrescribeDrug(e) {
console.error('Label printing failed, but drug was dispensed'); console.error('Label printing failed, but drug was dispensed');
showToast('Drug prescribed successfully, but label printing failed', 'warning', 5000); showToast('Drug prescribed successfully, but label printing failed', 'warning', 5000);
} else { } else {
showToast('Drug prescribed and label sent to printer!', 'success'); const labelResult = await labelResponse.json();
console.log('Label print result:', labelResult);
if (labelResult.success) {
showToast('Drug prescribed and label printed successfully!', 'success');
} else {
// Show as error toast if it contains specific error keywords
const isError = labelResult.message && (
labelResult.message.includes('not found') ||
labelResult.message.includes('error') ||
labelResult.message.includes('failed')
);
const toastType = isError ? 'error' : 'warning';
showToast('Drug prescribed but ' + labelResult.message, toastType, 5000);
}
} }
document.getElementById('prescribeForm').reset(); document.getElementById('prescribeForm').reset();