# pos_printer.py - ESC/POS 영수증 프린터 유틸리티 # 0bin-label-app/src/pos_settings_dialog.py 기반 import socket import logging from datetime import datetime # 프린터 설정 (config에서 불러올 수도 있음) POS_PRINTER_IP = "192.168.0.174" POS_PRINTER_PORT = 9100 POS_PRINTER_NAME = "올댓포스 오른쪽" # ESC/POS 명령어 ESC = b'\x1b' GS = b'\x1d' # 기본 명령 INIT = ESC + b'@' # 프린터 초기화 CUT = ESC + b'd\x03' # 피드 + 커트 (원본 방식) FEED = b'\n\n\n' # 줄바꿈 # 정렬 ALIGN_LEFT = ESC + b'a\x00' ALIGN_CENTER = ESC + b'a\x01' ALIGN_RIGHT = ESC + b'a\x02' # 폰트 스타일 BOLD_ON = ESC + b'E\x01' BOLD_OFF = ESC + b'E\x00' DOUBLE_HEIGHT = ESC + b'!\x10' DOUBLE_WIDTH = ESC + b'!\x20' DOUBLE_SIZE = ESC + b'!\x30' # 가로세로 2배 NORMAL_SIZE = ESC + b'!\x00' # 로깅 logging.basicConfig(level=logging.INFO) def print_raw(data: bytes, ip: str = None, port: int = None) -> bool: """ ESC/POS 바이트 데이터를 프린터로 전송 Args: data: ESC/POS 명령어 + 텍스트 바이트 ip: 프린터 IP (기본값: POS_PRINTER_IP) port: 프린터 포트 (기본값: POS_PRINTER_PORT) Returns: bool: 성공 여부 """ ip = ip or POS_PRINTER_IP port = port or POS_PRINTER_PORT try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(5) sock.connect((ip, port)) sock.sendall(data) sock.close() logging.info(f"[POS Printer] 전송 성공: {ip}:{port}") return True except socket.timeout: logging.error(f"[POS Printer] 연결 시간 초과: {ip}:{port}") return False except ConnectionRefusedError: logging.error(f"[POS Printer] 연결 거부됨: {ip}:{port}") return False except Exception as e: logging.error(f"[POS Printer] 전송 실패: {e}") return False def print_text(text: str, cut: bool = True) -> bool: """ 텍스트를 영수증 프린터로 출력 Args: text: 출력할 텍스트 (한글 지원) cut: 출력 후 용지 커트 여부 Returns: bool: 성공 여부 """ try: # EUC-KR 인코딩 (한글 지원) text_bytes = text.encode('euc-kr', errors='replace') # 명령어 조합 command = INIT + text_bytes + b'\n\n\n' if cut: command += CUT return print_raw(command) except Exception as e: logging.error(f"[POS Printer] 텍스트 인쇄 실패: {e}") return False def print_cusetc(customer_name: str, cusetc: str, phone: str = None) -> bool: """ 특이(참고)사항 영수증 출력 (단순 텍스트 방식) Args: customer_name: 고객 이름 cusetc: 특이사항 내용 phone: 전화번호 (선택) Returns: bool: 성공 여부 """ now = datetime.now().strftime('%Y-%m-%d %H:%M') # 전화번호 포맷팅 phone_display = "" if phone: phone_clean = phone.replace("-", "").replace(" ", "") if len(phone_clean) == 11: phone_display = f"{phone_clean[:3]}-{phone_clean[3:7]}-{phone_clean[7:]}" else: phone_display = phone # 80mm 프린터 = 48자 기준 LINE = "=" * 48 THIN = "-" * 48 message = f""" {LINE} [ 특이사항 ] {LINE} 고객: {customer_name} """ if phone_display: message += f"연락처: {phone_display}\n" message += f"""출력: {now} {THIN} {cusetc} {LINE} 청춘약국 """ return print_text(message, cut=True) def test_print() -> bool: """테스트 인쇄""" now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') test_message = f""" ================================ POS 프린터 테스트 ================================ IP: {POS_PRINTER_IP} Port: {POS_PRINTER_PORT} Time: {now} 청춘약국 마일리지 시스템 ESC/POS 정상 작동! ================================ """ return print_text(test_message, cut=True) if __name__ == "__main__": # 테스트 print("POS 프린터 테스트 인쇄...") result = test_print() print(f"결과: {'성공' if result else '실패'}")