perf: offload network printer socket communication to background thread to prevent POS blocking
This commit is contained in:
parent
035dfe3dab
commit
9248f9b7c8
@ -1,5 +1,6 @@
|
|||||||
import socket
|
import socket
|
||||||
import logging
|
import logging
|
||||||
|
import threading
|
||||||
from odoo import models, fields, api, _
|
from odoo import models, fields, api, _
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
@ -28,72 +29,75 @@ class PosPrinter(models.Model):
|
|||||||
def print_network_kitchen_receipt(self, printer_id, receipt_data):
|
def print_network_kitchen_receipt(self, printer_id, receipt_data):
|
||||||
"""
|
"""
|
||||||
Receives structural receipt_data from POS frontend and sends ESC/POS string to network printer.
|
Receives structural receipt_data from POS frontend and sends ESC/POS string to network printer.
|
||||||
receipt_data expected format:
|
Optimized to use background threading to prevent blocking the POS transaction/RPC.
|
||||||
{
|
|
||||||
'order_name': 'Order 0001',
|
|
||||||
'order_time': '2023-10-01 12:00:00',
|
|
||||||
'lines': [ { 'name': 'Burger', 'qty': 1, 'note': 'No onions' } ],
|
|
||||||
'waiter': 'Mitchell Admin',
|
|
||||||
'table': 'T1'
|
|
||||||
}
|
|
||||||
"""
|
"""
|
||||||
printer = self.browse(printer_id)
|
printer = self.browse(printer_id)
|
||||||
if not printer or not printer.network_printer_ip:
|
if not printer or not printer.network_printer_ip:
|
||||||
return {'successful': False, 'message': 'Printer IP not configured'}
|
return {'successful': False, 'message': 'Printer IP not configured'}
|
||||||
|
|
||||||
# Build basic ESC/POS commands
|
# Start printing in a background thread to avoid POS timeout
|
||||||
ESC = b'\x1b'
|
# We don't need a new cursor here as we've already browsed the printer
|
||||||
GS = b'\x1d'
|
# and we don't write to the database in the thread.
|
||||||
INIT = ESC + b'@'
|
thread = threading.Thread(target=self._threaded_print, args=(printer.network_printer_ip, receipt_data))
|
||||||
CUT = GS + b'V\x41\x00'
|
thread.daemon = True # Ensure thread doesn't block server shutdown
|
||||||
BOLD_ON = ESC + b'E\x01'
|
thread.start()
|
||||||
BOLD_OFF = ESC + b'E\x00'
|
|
||||||
SIZE_DOUBLE = GS + b'!\x11'
|
|
||||||
SIZE_NORMAL = GS + b'!\x00'
|
|
||||||
ALIGN_CENTER = ESC + b'a\x01'
|
|
||||||
ALIGN_LEFT = ESC + b'a\x00'
|
|
||||||
NEWLINE = b'\n'
|
|
||||||
|
|
||||||
data = INIT
|
return {'successful': True, 'message': 'Print job sent to background queue'}
|
||||||
data += ALIGN_CENTER + SIZE_DOUBLE + BOLD_ON
|
|
||||||
data += b"KITCHEN RECEIPT" + NEWLINE
|
|
||||||
data += SIZE_NORMAL + BOLD_OFF + NEWLINE
|
|
||||||
|
|
||||||
if receipt_data.get('table'):
|
def _threaded_print(self, ip, receipt_data):
|
||||||
data += ALIGN_LEFT + SIZE_DOUBLE
|
"""Perform the actual socket communication in the background."""
|
||||||
data += f"Table: {receipt_data['table']}".encode('utf-8') + NEWLINE
|
|
||||||
data += SIZE_NORMAL
|
|
||||||
|
|
||||||
data += ALIGN_LEFT
|
|
||||||
data += f"Order: {receipt_data.get('order_name', '')}".encode('utf-8') + NEWLINE
|
|
||||||
data += f"Date: {receipt_data.get('order_time', '')}".encode('utf-8') + NEWLINE
|
|
||||||
if receipt_data.get('waiter'):
|
|
||||||
data += f"Waiter: {receipt_data['waiter']}".encode('utf-8') + NEWLINE
|
|
||||||
data += b"-" * 32 + NEWLINE
|
|
||||||
|
|
||||||
for line in receipt_data.get('lines', []):
|
|
||||||
qty = line.get('qty', 1)
|
|
||||||
name = line.get('name', '')
|
|
||||||
note = line.get('note', '')
|
|
||||||
|
|
||||||
data += SIZE_DOUBLE
|
|
||||||
data += f"{qty} x {name}".encode('utf-8') + NEWLINE
|
|
||||||
data += SIZE_NORMAL
|
|
||||||
if note:
|
|
||||||
data += f" NOTE: {note}".encode('utf-8') + NEWLINE
|
|
||||||
|
|
||||||
data += NEWLINE
|
|
||||||
|
|
||||||
data += b"-" * 32 + NEWLINE + NEWLINE + NEWLINE + CUT
|
|
||||||
|
|
||||||
# Send to printer
|
|
||||||
try:
|
try:
|
||||||
|
# Build basic ESC/POS commands
|
||||||
|
ESC = b'\x1b'
|
||||||
|
GS = b'\x1d'
|
||||||
|
INIT = ESC + b'@'
|
||||||
|
CUT = GS + b'V\x41\x00'
|
||||||
|
BOLD_ON = ESC + b'E\x01'
|
||||||
|
BOLD_OFF = ESC + b'E\x00'
|
||||||
|
SIZE_DOUBLE = GS + b'!\x11'
|
||||||
|
SIZE_NORMAL = GS + b'!\x00'
|
||||||
|
ALIGN_CENTER = ESC + b'a\x01'
|
||||||
|
ALIGN_LEFT = ESC + b'a\x00'
|
||||||
|
NEWLINE = b'\n'
|
||||||
|
|
||||||
|
data = INIT
|
||||||
|
data += ALIGN_CENTER + SIZE_DOUBLE + BOLD_ON
|
||||||
|
data += b"KITCHEN RECEIPT" + NEWLINE
|
||||||
|
data += SIZE_NORMAL + BOLD_OFF + NEWLINE
|
||||||
|
|
||||||
|
if receipt_data.get('table'):
|
||||||
|
data += ALIGN_LEFT + SIZE_DOUBLE
|
||||||
|
data += f"Table: {receipt_data['table']}".encode('utf-8') + NEWLINE
|
||||||
|
data += SIZE_NORMAL
|
||||||
|
|
||||||
|
data += ALIGN_LEFT
|
||||||
|
data += f"Order: {receipt_data.get('order_name', '')}".encode('utf-8') + NEWLINE
|
||||||
|
data += f"Date: {receipt_data.get('order_time', '')}".encode('utf-8') + NEWLINE
|
||||||
|
if receipt_data.get('waiter'):
|
||||||
|
data += f"Waiter: {receipt_data['waiter']}".encode('utf-8') + NEWLINE
|
||||||
|
data += b"-" * 32 + NEWLINE
|
||||||
|
|
||||||
|
for line in receipt_data.get('lines', []):
|
||||||
|
qty = line.get('qty', 1)
|
||||||
|
name = line.get('name', '')
|
||||||
|
note = line.get('note', '')
|
||||||
|
|
||||||
|
data += SIZE_DOUBLE
|
||||||
|
data += f"{qty} x {name}".encode('utf-8') + NEWLINE
|
||||||
|
data += SIZE_NORMAL
|
||||||
|
if note:
|
||||||
|
data += f" NOTE: {note}".encode('utf-8') + NEWLINE
|
||||||
|
|
||||||
|
data += NEWLINE
|
||||||
|
|
||||||
|
data += b"-" * 32 + NEWLINE + NEWLINE + NEWLINE + CUT
|
||||||
|
|
||||||
|
# Send to printer
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
s.settimeout(3.0)
|
s.settimeout(5.0) # Slightly longer timeout for background print
|
||||||
s.connect((printer.network_printer_ip, 9100))
|
s.connect((ip, 9100))
|
||||||
s.sendall(data)
|
s.sendall(data)
|
||||||
s.close()
|
s.close()
|
||||||
return {'successful': True}
|
_logger.info("Successfully printed to kitchen printer at %s", ip)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.error("Failed to print to network ESC/POS printer %s: %s", printer.network_printer_ip, e)
|
_logger.error("Background kitchen print failed for IP %s: %s", ip, e)
|
||||||
return {'successful': False, 'message': str(e)}
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user