From 2bcae967f67d93d94b3d74b02f4484566c02d178 Mon Sep 17 00:00:00 2001 From: "vluk@2fi-solutions.com.hk" Date: Wed, 25 Feb 2026 15:17:51 +0800 Subject: [PATCH] no message --- python/Bag1.py | 67 ++++++++++++++++++++++++++++++- python/README.md | 1 + python/label_zpl.py | 97 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 python/label_zpl.py diff --git a/python/Bag1.py b/python/Bag1.py index ae435e7..8e1a4ea 100644 --- a/python/Bag1.py +++ b/python/Bag1.py @@ -35,7 +35,7 @@ DEFAULT_SETTINGS = { "api_ip": "localhost", "api_port": "8090", "dabag_ip": "", - "dabag_port": "9100", + "dabag_port": "3008", "laser_ip": "", "laser_port": "9100", "label_com": "COM3", @@ -132,6 +132,47 @@ REFRESH_MS = 60 * 1000 # 60 seconds refresh when connected PRINTER_CHECK_MS = 60 * 1000 # 1 minute when printer OK PRINTER_RETRY_MS = 30 * 1000 # 30 seconds when printer failed PRINTER_SOCKET_TIMEOUT = 3 +DATAFLEX_SEND_TIMEOUT = 10 # seconds when sending ZPL to DataFlex + + +def _zpl_escape(s: str) -> str: + """Escape text for ZPL ^FD...^FS (backslash and caret).""" + return s.replace("\\", "\\\\").replace("^", "\\^") + + +def generate_zpl_dataflex(batch_no: str, item_code: str, item_name: str) -> str: + """ + Generate ZPL for DataFlex label (53 mm media, 90° rotated). + Uses UTF-8 (^CI28) and fonts msjh.ttc / msjhbd.ttc for Chinese/English. + """ + desc = _zpl_escape((item_name or "—").strip()) + code = _zpl_escape((item_code or "—").strip()) + batch = _zpl_escape(batch_no.strip()) + return f"""^XA +^PW420 +^LL780 +^PO N +^CI28 +^FO70,70 +^A@R,60,60,E:MSJH.TTC^FD{desc}^FS +^FO220,70 +^A@R,50,50,E:MSJHBD.TTC^FD{code}^FS +^FO310,70 +^A@R,45,45,E:MSJHBD.TTC^FD批次: {batch}^FS +^FO150,420 +^BQN,2,6^FDQA,{batch_no}^FS +^XZ""" + + +def send_zpl_to_dataflex(ip: str, port: int, zpl: str) -> None: + """Send ZPL to DataFlex printer via TCP. Raises on connection/send error.""" + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(DATAFLEX_SEND_TIMEOUT) + try: + sock.connect((ip, port)) + sock.sendall(zpl.encode("utf-8")) + finally: + sock.close() def format_qty(val) -> str: @@ -551,7 +592,29 @@ def main() -> None: set_row_highlight(r, True) selected_row_holder[0] = r selected_jo_id_ref[0] = j.get("id") - if printer_var.get() == "標簽機": + if printer_var.get() == "打袋機 DataFlex": + ip = (settings.get("dabag_ip") or "").strip() + port_str = (settings.get("dabag_port") or "3008").strip() + try: + port = int(port_str) + except ValueError: + port = 3008 + if not ip: + messagebox.showerror("打袋機", "請在設定中填寫打袋機 DataFlex 的 IP。") + else: + item_code = j.get("itemCode") or "—" + item_name = j.get("itemName") or "—" + zpl = generate_zpl_dataflex(b, item_code, item_name) + try: + send_zpl_to_dataflex(ip, port, zpl) + messagebox.showinfo("打袋機", f"已送出列印:批次 {b}") + except ConnectionRefusedError: + messagebox.showerror("打袋機", f"無法連線至 {ip}:{port},請確認印表機已開機且 IP 正確。") + except socket.timeout: + messagebox.showerror("打袋機", f"連線逾時 ({ip}:{port}),請檢查網路與連接埠。") + except OSError as err: + messagebox.showerror("打袋機", f"列印失敗:{err}") + elif printer_var.get() == "標簽機": count = ask_label_count(root) if count is not None: if count == "C": diff --git a/python/README.md b/python/README.md index 9b7d8da..22871e4 100644 --- a/python/README.md +++ b/python/README.md @@ -22,6 +22,7 @@ Set the backend base URL (optional, default below): |--------|-------------| | `Bag1.py` | **GUI**: date selector (default today) and job orders as buttons; click shows "Clicked on Job Order code XXXX item xxxx". Run: `python Bag1.py` | | `fetch_job_orders.py` | CLI: fetches job orders by plan date from `GET /py/job-orders` | +| `label_zpl.py` | ZPL label generator (90° rotated, UTF-8 Chinese, QR). `generate_zpl(batch_no, item_code, chinese_desc)`, `send_zpl(zpl, host, port)`. Run: `python label_zpl.py` to print one test label. | ## Building Bag1 as a standalone .exe diff --git a/python/label_zpl.py b/python/label_zpl.py new file mode 100644 index 0000000..0544660 --- /dev/null +++ b/python/label_zpl.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +""" +ZPL label generator and TCP send for Zebra label printer (標簽機). +- Rotated 90° clockwise for ~53 mm media width +- UTF-8 (^CI28) for Chinese +- Large fonts, QR code mag 6 + +Standalone: python label_zpl.py +Or import generate_zpl() / send_zpl() and call from Bag1. +""" + +import socket + +# Default printer (override via args or Bag1 settings) +DEFAULT_PRINTER_IP = "192.168.17.27" +DEFAULT_PRINTER_PORT = 3008 +SOCKET_TIMEOUT = 10 + + +def generate_zpl( + batch_no: str, + item_code: str = "PP2238-02", + chinese_desc: str = "(餐廳用)凍咖啡底P+10(0.91L包)", +) -> str: + """ + Generates ZPL label: + - Rotated 90° clockwise to fit ~53 mm media width + - UTF-8 mode (^CI28) for correct Chinese display + - Larger fonts + - Bigger QR code (mag 6) + """ + return f"""^XA +^PW420 ^# Fits ~53 mm width (~420 dots @ 203 dpi) +^LL780 ^# Taller label after rotation + bigger elements +^PO N ^# Normal — change to ^POI if upside-down + +^CI28 ^# Enable UTF-8 / Unicode for Chinese (critical fix for boxes) + +^FO70,70 +^A@R,60,60,E:SIMSUN.FNT^FD{chinese_desc}^FS + ^# Very large Chinese text, rotated + +^FO220,70 +^A0R,50,50^FD{item_code}^FS + ^# Larger item code + +^FO310,70 +^A0R,45,45^FDBatch: {batch_no}^FS + ^# Larger batch text + +^FO150,420 +^BQN,2,6^FDQA,{batch_no}^FS + ^# Bigger QR code (magnification 6), lower position for space + +^XZ""" + + +def send_zpl( + zpl: str, + host: str = DEFAULT_PRINTER_IP, + port: int = DEFAULT_PRINTER_PORT, + timeout: float = SOCKET_TIMEOUT, +) -> None: + """Send ZPL to printer over TCP. Raises on connection/send error.""" + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + sock.connect((host, port)) + sock.sendall(zpl.encode("utf-8")) + sock.close() + + +# ──────────────────────────────────────────────── +# Example usage — prints one label +# ──────────────────────────────────────────────── + +if __name__ == "__main__": + test_batch = "2025121209" + zpl = generate_zpl(test_batch) + + print("Sending ZPL (90° rotated, UTF-8 Chinese, bigger QR):") + print("-" * 90) + print(zpl) + print("-" * 90) + + try: + send_zpl(zpl) + print("Label sent successfully!") + print("→ Check Chinese — should show real characters (not 口口 or symbols)") + print("→ QR is now bigger (mag 6) — test scan with phone") + print("→ If upside-down: edit ^PO N → ^POI") + print("→ If still boxes: SimSun font may be missing — reinstall via Zebra Setup Utilities") + except ConnectionRefusedError: + print(f"Cannot connect to {DEFAULT_PRINTER_IP}:{DEFAULT_PRINTER_PORT} — printer off or wrong IP?") + except socket.timeout: + print("Connection timeout — check printer/network/port") + except Exception as e: + print(f"Error: {e}")