import socket import logging _logger = logging.getLogger(__name__) def send_zpl_to_printer(ip, port, zpl_data): """ Send ZPL data to a printer via socket. :param ip: Printer IP address (str) :param port: Printer Port (int) :param zpl_data: ZPL content (str or bytes) :return: True if successful, raises Exception otherwise """ if not ip: raise ValueError("Printer IP is not configured.") if not zpl_data: return try: if isinstance(zpl_data, str): zpl_data = zpl_data.encode('utf-8') with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.settimeout(5) # 5 seconds timeout sock.connect((ip, int(port))) sock.sendall(zpl_data) _logger.info(f"Sent {len(zpl_data)} bytes to printer at {ip}:{port}") return True except Exception as e: _logger.error(f"Failed to print to {ip}:{port}. Error: {e}") raise e def convert_to_zpl_hex(image_data, width_dots=100, height_dots=100): """ Convert image binary data to ZPL hex string (^GFA command). :param image_data: Binary image data :param width_dots: Max width in dots :param height_dots: Max height in dots :return: ZPL command string (^GFA...) """ if not image_data: return "" try: from PIL import Image import io import binascii image = Image.open(io.BytesIO(image_data)) # Resize preserving aspect ratio image.thumbnail((width_dots, height_dots), Image.Resampling.LANCZOS) # Convert to 1-bit monochrome (dithered) # Convert to grayscale first, then 1-bit image = image.convert('L') image = image.convert('1') width, height = image.size row_bytes = (width + 7) // 8 total_bytes = row_bytes * height # Get bit data data = image.tobytes() # Convert to hex hex_data = binascii.hexlify(data).upper().decode('utf-8') # Create ZPL command # ^GFA,b,c,d,data # a = compression type (A=ASCII) # b = binary byte count # c = graphic field count (total bytes) # d = bytes per row zpl_cmd = f"^GFA,{total_bytes},{total_bytes},{row_bytes},{hex_data}" return zpl_cmd except Exception as e: _logger.error(f"Image conversion failed: {e}") return ""