labels and stuff
This commit is contained in:
312
printer_receiver.py
Normal file
312
printer_receiver.py
Normal file
@ -0,0 +1,312 @@
|
||||
#!/usr/bin/env python3
|
||||
from flask import Flask, request, jsonify
|
||||
from brother_ql.backends import backend_factory
|
||||
from brother_ql.backends.helpers import discover, status, send
|
||||
from brother_ql.raster import BrotherQLRaster
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG, # Changed to DEBUG for more detailed logging
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class LabelPrinterReceiver:
|
||||
def __init__(self, printer_identifier: str = "usb://0x04f9:0x20a7"):
|
||||
"""Initialize the label printer receiver.
|
||||
|
||||
Args:
|
||||
printer_identifier: USB identifier for the printer
|
||||
"""
|
||||
logger.info(f"Initializing LabelPrinterReceiver with printer identifier: {printer_identifier}")
|
||||
self.printer_identifier = printer_identifier
|
||||
self.backend = None
|
||||
self._print_lock = False
|
||||
self._last_print_time = None
|
||||
self._print_in_progress = False
|
||||
self._last_known_state = None
|
||||
self._last_state_time = None
|
||||
self._state_cache_timeout = 1.0 # seconds
|
||||
|
||||
def connect_printer(self) -> bool:
|
||||
"""Connect to the printer via USB.
|
||||
|
||||
Returns:
|
||||
bool: True if connection was successful, False otherwise
|
||||
"""
|
||||
logger.info("Attempting to connect to printer...")
|
||||
try:
|
||||
# Get the backend class from the factory
|
||||
backend_info = backend_factory('pyusb')
|
||||
logger.debug(f"Backend info: {backend_info}")
|
||||
self.backend = backend_info['backend_class'](device_specifier=self.printer_identifier)
|
||||
|
||||
# Connect to the printer
|
||||
self.backend.write = self.backend.write # This triggers the connection
|
||||
logger.info("Successfully connected to printer")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error connecting to printer: {e}")
|
||||
self.backend = None
|
||||
return False
|
||||
|
||||
def is_printer_busy(self) -> bool:
|
||||
"""Check if the printer is currently busy.
|
||||
|
||||
Returns:
|
||||
bool: True if printer is busy, False otherwise
|
||||
"""
|
||||
logger.debug("Checking printer status...")
|
||||
|
||||
# Check if we have a recent cached state
|
||||
if (self._last_known_state is not None and
|
||||
self._last_state_time is not None and
|
||||
time.time() - self._last_state_time < self._state_cache_timeout):
|
||||
logger.debug("Using cached printer state")
|
||||
return self._last_known_state.get('phase_type') != 'Waiting to receive'
|
||||
|
||||
try:
|
||||
if not self.backend:
|
||||
logger.warning("No backend connection, attempting to connect...")
|
||||
if not self.connect_printer():
|
||||
logger.error("Failed to connect to printer")
|
||||
return False
|
||||
|
||||
# Get actual printer status
|
||||
logger.debug("Requesting printer status...")
|
||||
status_info, raw_data = status(printer_identifier=self.printer_identifier)
|
||||
logger.debug(f"Raw status data: {raw_data}")
|
||||
logger.debug(f"Parsed status info: {status_info}")
|
||||
|
||||
# Cache the state
|
||||
self._last_known_state = status_info
|
||||
self._last_state_time = time.time()
|
||||
|
||||
# Check for any errors
|
||||
if status_info.get('errors'):
|
||||
logger.error(f"Printer errors detected: {status_info['errors']}")
|
||||
return False
|
||||
|
||||
# Check printer phase
|
||||
phase_type = status_info.get('phase_type')
|
||||
logger.debug(f"Printer phase type: {phase_type}")
|
||||
|
||||
if phase_type == 'Waiting to receive':
|
||||
logger.info("Printer is ready for next job")
|
||||
return False
|
||||
elif phase_type == 'Printing':
|
||||
logger.info("Printer is currently printing")
|
||||
return True
|
||||
else:
|
||||
logger.info(f"Printer is in unknown phase: {phase_type}")
|
||||
return True # Assume busy for unknown phases
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting printer status: {e}")
|
||||
# If we get an error, clear the cached state
|
||||
self._last_known_state = None
|
||||
self._last_state_time = None
|
||||
return False
|
||||
|
||||
def handle_print_request(self) -> tuple:
|
||||
"""Handle incoming print requests.
|
||||
|
||||
Returns:
|
||||
tuple: (response message, HTTP status code)
|
||||
"""
|
||||
logger.info("Received print request")
|
||||
if self._print_lock:
|
||||
logger.warning("Print lock is active, rejecting request")
|
||||
return {"message": "Another print job is being processed"}, 429
|
||||
|
||||
try:
|
||||
logger.info("Acquiring print lock")
|
||||
self._print_lock = True
|
||||
self._print_in_progress = True
|
||||
self._last_print_time = time.time()
|
||||
|
||||
# Get the print data from the request
|
||||
print_data = request.get_data()
|
||||
if not print_data:
|
||||
logger.error("No print data provided in request")
|
||||
return {"message": "No print data provided"}, 400
|
||||
logger.info(f"Received print data of size: {len(print_data)} bytes")
|
||||
|
||||
# Use the send helper which handles the complete print lifecycle
|
||||
logger.info("Sending print data to printer...")
|
||||
result = send(
|
||||
instructions=print_data,
|
||||
printer_identifier=self.printer_identifier,
|
||||
backend_identifier='pyusb',
|
||||
blocking=True
|
||||
)
|
||||
|
||||
logger.debug(f"Print result: {result}")
|
||||
|
||||
# Update cached state with the result
|
||||
if 'printer_state' in result:
|
||||
self._last_known_state = result['printer_state']
|
||||
self._last_state_time = time.time()
|
||||
|
||||
if result['outcome'] == 'error':
|
||||
logger.error(f"Print error: {result.get('printer_state', {}).get('errors', 'Unknown error')}")
|
||||
return {"message": f"Print error: {result.get('printer_state', {}).get('errors', 'Unknown error')}"}, 500
|
||||
|
||||
if not result['did_print']:
|
||||
logger.warning("Print may not have completed successfully")
|
||||
return {"message": "Print sent but completion status unclear"}, 200
|
||||
|
||||
logger.info("Print completed successfully")
|
||||
return {"message": "Print request processed successfully"}, 200
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing print request: {e}")
|
||||
# Clear cached state on error
|
||||
self._last_known_state = None
|
||||
self._last_state_time = None
|
||||
return {"message": f"Error: {str(e)}"}, 500
|
||||
finally:
|
||||
logger.info("Releasing print lock")
|
||||
self._print_lock = False
|
||||
self._print_in_progress = False
|
||||
|
||||
def start_server(self, host: str = "0.0.0.0", port: int = 8000):
|
||||
"""Start the web server to receive print requests.
|
||||
|
||||
Args:
|
||||
host: Host to bind the server to
|
||||
port: Port to listen on
|
||||
"""
|
||||
logger.info(f"Starting print server on {host}:{port}")
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/print', methods=['POST'])
|
||||
def print_endpoint():
|
||||
logger.info("Received print request at /print endpoint")
|
||||
response, status_code = self.handle_print_request()
|
||||
logger.info(f"Print request completed with status {status_code}: {response}")
|
||||
return jsonify(response), status_code
|
||||
|
||||
@app.route('/status', methods=['GET'])
|
||||
def status_endpoint():
|
||||
logger.info("Received status request at /status endpoint")
|
||||
try:
|
||||
if not self.backend:
|
||||
if not self.connect_printer():
|
||||
logger.error("Failed to connect to printer for status check")
|
||||
return jsonify({"status": "error", "message": "Could not connect to printer"}), 500
|
||||
|
||||
# Get actual printer status with retry logic
|
||||
max_retries = 3
|
||||
retry_delay = 1 # seconds
|
||||
last_error = None
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
logger.debug(f"Requesting printer status (attempt {attempt + 1}/{max_retries})...")
|
||||
status_info, raw_data = status(printer_identifier=self.printer_identifier)
|
||||
logger.debug(f"Raw status data: {raw_data}")
|
||||
logger.debug(f"Parsed status info: {status_info}")
|
||||
|
||||
# Check for any errors
|
||||
if status_info.get('errors'):
|
||||
logger.error(f"Printer errors detected: {status_info['errors']}")
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Printer errors: {status_info['errors']}"
|
||||
}), 500
|
||||
|
||||
# Check printer phase
|
||||
phase_type = status_info.get('phase_type')
|
||||
logger.debug(f"Printer phase type: {phase_type}")
|
||||
|
||||
# Convert status info to JSON-serializable format
|
||||
serializable_status = {
|
||||
'status_type': status_info.get('status_type'),
|
||||
'phase_type': phase_type,
|
||||
'model_name': status_info.get('model_name'),
|
||||
'media_type': status_info.get('media_type'),
|
||||
'media_width': status_info.get('media_width'),
|
||||
'media_length': status_info.get('media_length'),
|
||||
'errors': status_info.get('errors', [])
|
||||
}
|
||||
|
||||
# Add media info if available
|
||||
if 'identified_media' in status_info:
|
||||
media = status_info['identified_media']
|
||||
serializable_status['media'] = {
|
||||
'identifier': media.identifier,
|
||||
'tape_size': media.tape_size,
|
||||
'form_factor': str(media.form_factor),
|
||||
'color': str(media.color)
|
||||
}
|
||||
|
||||
if phase_type == 'Waiting to receive':
|
||||
logger.info("Printer status: ready")
|
||||
return jsonify({
|
||||
"status": "ready",
|
||||
"phase": phase_type,
|
||||
"details": serializable_status
|
||||
}), 200
|
||||
elif phase_type == 'Printing':
|
||||
logger.info("Printer status: busy")
|
||||
return jsonify({
|
||||
"status": "busy",
|
||||
"phase": phase_type,
|
||||
"details": serializable_status
|
||||
}), 200
|
||||
else:
|
||||
logger.info(f"Printer is in unknown phase: {phase_type}")
|
||||
return jsonify({
|
||||
"status": "busy",
|
||||
"phase": phase_type,
|
||||
"details": serializable_status
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
if "Resource busy" in str(e):
|
||||
logger.warning(f"Printer is busy, retrying in {retry_delay} seconds...")
|
||||
time.sleep(retry_delay)
|
||||
retry_delay *= 2 # Exponential backoff
|
||||
else:
|
||||
logger.error(f"Error checking printer status: {e}")
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": str(e)
|
||||
}), 500
|
||||
|
||||
# If we've exhausted all retries
|
||||
logger.error(f"Failed to get printer status after {max_retries} attempts. Last error: {last_error}")
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Printer is busy and not responding. Last error: {str(last_error)}"
|
||||
}), 503 # Service Unavailable
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking printer status: {e}")
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": str(e)
|
||||
}), 500
|
||||
|
||||
logger.info(f"Print server started successfully on {host}:{port}")
|
||||
app.run(host=host, port=port)
|
||||
|
||||
def main():
|
||||
# Create and start the printer receiver
|
||||
logger.info("Starting printer receiver...")
|
||||
receiver = LabelPrinterReceiver()
|
||||
receiver.start_server()
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Shutting down print server")
|
||||
sys.exit(0)
|
Reference in New Issue
Block a user