| @@ -213,15 +213,12 @@ def generate_zpl_dataflex( | |||||
| lot_no: Optional[str] = None, | lot_no: Optional[str] = None, | ||||
| font_regular: str = "E:STXihei.ttf", | font_regular: str = "E:STXihei.ttf", | ||||
| font_bold: str = "E:STXihei.ttf", | font_bold: str = "E:STXihei.ttf", | ||||
| reset_counter_first: bool = False, | |||||
| ) -> str: | ) -> str: | ||||
| """ | """ | ||||
| Row 1 (from zero): QR code, then item name (rotated 90°). | Row 1 (from zero): QR code, then item name (rotated 90°). | ||||
| Row 2: Batch/lot (left), item code (right). | Row 2: Batch/lot (left), item code (right). | ||||
| Label and QR use lotNo from API when present, else batch_no (Bxxxxx). | Label and QR use lotNo from API when present, else batch_no (Bxxxxx). | ||||
| When reset_counter_first is True, Zebra counter reset commands (~RO1, ~RO2) are inserted after ^XA | |||||
| in the *same* label job. Many printers ignore a separate reset-only TCP job. | |||||
| Job counter reset (~RO) is sent separately via [send_dataflex_job_counter_reset] before labels. | |||||
| """ | """ | ||||
| desc = _zpl_escape((item_name or "—").strip()) | desc = _zpl_escape((item_name or "—").strip()) | ||||
| code = _zpl_escape((item_code or "—").strip()) | code = _zpl_escape((item_code or "—").strip()) | ||||
| @@ -233,9 +230,8 @@ def generate_zpl_dataflex( | |||||
| else: | else: | ||||
| qr_payload = label_line if label_line else batch_no.strip() | qr_payload = label_line if label_line else batch_no.strip() | ||||
| qr_value = _zpl_escape(qr_payload) | qr_value = _zpl_escape(qr_payload) | ||||
| counter_lines = _zpl_dataflex_counter_reset_lines() if reset_counter_first else "" | |||||
| return f"""^XA | return f"""^XA | ||||
| {counter_lines}^CI28 | |||||
| ^CI28 | |||||
| ^PW700 | ^PW700 | ||||
| ^LL500 | ^LL500 | ||||
| ^PO N | ^PO N | ||||
| @@ -250,13 +246,23 @@ def generate_zpl_dataflex( | |||||
| ^XZ""" | ^XZ""" | ||||
| def _zpl_dataflex_counter_reset_lines() -> str: | |||||
| def send_dataflex_job_counter_reset(ip: str, port: int) -> None: | |||||
| """ | """ | ||||
| Zebra ZPL counter reset (tilde commands), embedded after ^XA on the first label of each job: | |||||
| ~RO1 = reset counter 1, ~RO2 = reset counter 2. | |||||
| See Zebra ZPL / printer manual; add ~RO3 etc. if your model exposes more counters. | |||||
| Reset printer job / label counters when switching to a new job (new row). | |||||
| Zebra: ~RO1 / ~RO2 reset counters 1 and 2. Sent as a *standalone* TCP write *before* any ^XA…^XZ | |||||
| label, because many firmwares ignore ~ commands inside a format block. | |||||
| Uses CRLF line endings (common Zebra expectation). Raises on TCP error like [send_zpl_to_dataflex]. | |||||
| """ | """ | ||||
| return "~RO1\n~RO2\n" | |||||
| raw = "~RO1\r\n~RO2\r\n".encode("ascii") | |||||
| sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |||||
| sock.settimeout(DATAFLEX_SEND_TIMEOUT) | |||||
| try: | |||||
| sock.connect((ip, port)) | |||||
| sock.sendall(raw) | |||||
| finally: | |||||
| sock.close() | |||||
| def generate_zpl_label_small( | def generate_zpl_label_small( | ||||
| @@ -563,7 +569,7 @@ def send_image_to_label_printer(printer_name: str, pil_image: "Image.Image") -> | |||||
| def send_zpl_to_dataflex(ip: str, port: int, zpl: str) -> None: | def send_zpl_to_dataflex(ip: str, port: int, zpl: str) -> None: | ||||
| """Send ZPL to DataFlex printer via TCP. Raises on connection/send error.""" | |||||
| """Send ZPL label (^XA…^XZ) to DataFlex printer via TCP. Raises on connection/send error.""" | |||||
| sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||||
| sock.settimeout(DATAFLEX_SEND_TIMEOUT) | sock.settimeout(DATAFLEX_SEND_TIMEOUT) | ||||
| try: | try: | ||||
| @@ -1628,16 +1634,6 @@ def main() -> None: | |||||
| item_id=item_id, | item_id=item_id, | ||||
| stock_in_line_id=stock_in_line_id, | stock_in_line_id=stock_in_line_id, | ||||
| lot_no=lot_no, | lot_no=lot_no, | ||||
| reset_counter_first=False, | |||||
| ) | |||||
| zpl_first_job = generate_zpl_dataflex( | |||||
| b, | |||||
| item_code, | |||||
| item_name, | |||||
| item_id=item_id, | |||||
| stock_in_line_id=stock_in_line_id, | |||||
| lot_no=lot_no, | |||||
| reset_counter_first=True, | |||||
| ) | ) | ||||
| label_text = (lot_no or b).strip() | label_text = (lot_no or b).strip() | ||||
| if continuous: | if continuous: | ||||
| @@ -1648,9 +1644,9 @@ def main() -> None: | |||||
| printed = 0 | printed = 0 | ||||
| error_shown = False | error_shown = False | ||||
| try: | try: | ||||
| send_dataflex_job_counter_reset(ip, port) | |||||
| while not stop_ev.is_set(): | while not stop_ev.is_set(): | ||||
| payload = zpl_first_job if printed == 0 else zpl | |||||
| send_zpl_to_dataflex(ip, port, payload) | |||||
| send_zpl_to_dataflex(ip, port, zpl) | |||||
| printed += 1 | printed += 1 | ||||
| _sleep_interruptible(stop_ev, 2.0) | _sleep_interruptible(stop_ev, 2.0) | ||||
| except ConnectionRefusedError: | except ConnectionRefusedError: | ||||
| @@ -1718,12 +1714,9 @@ def main() -> None: | |||||
| threading.Thread(target=dflex_worker, daemon=True).start() | threading.Thread(target=dflex_worker, daemon=True).start() | ||||
| else: | else: | ||||
| try: | try: | ||||
| send_dataflex_job_counter_reset(ip, port) | |||||
| for i in range(n): | for i in range(n): | ||||
| send_zpl_to_dataflex( | |||||
| ip, | |||||
| port, | |||||
| zpl_first_job if i == 0 else zpl, | |||||
| ) | |||||
| send_zpl_to_dataflex(ip, port, zpl) | |||||
| if i < n - 1: | if i < n - 1: | ||||
| time.sleep(2) | time.sleep(2) | ||||
| set_status_message(f"已送出列印:批次 {label_text} x {n} 張", is_error=False) | set_status_message(f"已送出列印:批次 {label_text} x {n} 張", is_error=False) | ||||