Bladeren bron

Recording the job count for printers, need to test it

master
DESKTOP-064TTA1\Fai LUK 18 uur geleden
bovenliggende
commit
259ca4e918
1 gewijzigde bestanden met toevoegingen van 94 en 26 verwijderingen
  1. +94
    -26
      python/Bag3.py

+ 94
- 26
python/Bag3.py Bestand weergeven

@@ -2,9 +2,15 @@
"""
Bag3 v3.2 – FPSMS job orders by plan date (this file is the maintained version).

Uses the public API GET /py/job-orders (no login required).
Uses the public API GET /py/job-orders and POST /py/job-order-print-submit (no login required).
UI tuned for aged users: larger font, Traditional Chinese labels, prev/next date.

Database print counts (py_job_order_print_submit):
Each finished print run calls submit_job_order_print_submit() with jobOrderId, qty,
and printChannel (DATAFLEX | LABEL | LASER). The server appends one row per call;
GET /py/job-orders returns cumulative bagPrintedQty / labelPrintedQty / laserPrintedQty
per job order. Re-printing the same job later adds another row (SUM increases).

Bag2 is kept as a separate legacy v2.x line; do not assume Bag2 matches Bag3.

Run: python Bag3.py
@@ -193,22 +199,28 @@ BG_STATUS_OK = "#90EE90" # light green when connected
FG_STATUS_OK = "#006400" # green text
RETRY_MS = 30 * 1000 # 30 seconds reconnect
REFRESH_MS = 60 * 1000 # 60 seconds refresh when connected
# POST /py/job-order-print-submit: retries when server is briefly unavailable
PRINT_SUBMIT_MAX_ATTEMPTS = 3
PRINT_SUBMIT_RETRY_DELAY_SEC = 1.0
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
# Gap between bag labels on DataFlex (one TCP session per batch; tune if bags are skipped)
DATAFLEX_INTER_LABEL_DELAY_SEC = 2.5
# After ~JA/~RO/~JR recovery, let firmware finish before sending label ZPL (avoids stuck E1005)
DATAFLEX_POST_RECOVERY_DELAY_SEC = 1.5
# Zebra ~RO only (used when FPSMS_DATAFLEX_NO_JR is set)
# Before each print job: light reset only (~JA + ~RO). Short delay so first bag starts quickly.
DATAFLEX_PREPRINT_BYTES = b"~JA\r\n~RO1\r\n~RO2\r\n"
DATAFLEX_POST_PREPRINT_DELAY_SEC = 0.35
# Full recovery (~JR soft reset) — used by「打袋重設」only; longer delay for firmware
DATAFLEX_POST_FULL_RECOVERY_DELAY_SEC = 1.2
# Zebra ~RO only (used when FPSMS_DATAFLEX_NO_JR is set for full recovery)
DATAFLEX_RESET_BYTES = b"~RO1\r\n~RO2\r\n"
# Full host recovery: ~JA clear buffers, ~RO counters, ~JR soft reset (clears latched errors without power cycle)
DATAFLEX_FULL_RECOVERY_BYTES = b"~JA\r\n~RO1\r\n~RO2\r\n~JR\r\n"


def _dataflex_recovery_payload() -> bytes:
"""~JA+~RO+~JR by default; set env FPSMS_DATAFLEX_NO_JR=1 to skip ~JR if firmware rejects it."""
def _dataflex_full_recovery_payload() -> bytes:
"""~JA+~RO+~JR for manual「打袋重設」; set env FPSMS_DATAFLEX_NO_JR=1 to skip ~JR."""
if os.environ.get("FPSMS_DATAFLEX_NO_JR", "").strip().lower() in ("1", "true", "yes"):
return b"~JA\r\n" + DATAFLEX_RESET_BYTES
return DATAFLEX_FULL_RECOVERY_BYTES
@@ -233,7 +245,7 @@ def generate_zpl_dataflex(
Row 1 (from zero): QR code, then item name (rotated 90°).
Row 2: Batch/lot (left), item code (right).
Label and QR use lotNo from API when present, else batch_no (Bxxxxx).
Host recovery (~JA/~RO/~JR) is sent separately via [send_dataflex_job_counter_reset] before labels.
Light preprint (~JA/~RO) is sent before labels; full ~JR recovery is only for「打袋重設」.
"""
desc = _zpl_escape((item_name or "—").strip())
code = _zpl_escape((item_code or "—").strip())
@@ -264,12 +276,33 @@ def generate_zpl_dataflex(
^XZ"""


def send_dataflex_job_counter_reset(ip: str, port: int) -> None:
def send_dataflex_preprint_reset(ip: str, port: int) -> None:
"""
Fast prep before printing: ~JA + ~RO (no ~JR). Clears buffer and zeros batch counters so the first
bag starts quickly. Use before fixed-qty batch and continuous mode.
"""
Prepare DataFlex for a new job without power cycling: ~JA (clear host buffer / cancel pending),
~RO1/~RO2 (counters), ~JR (soft reset — clears latched E1005 / “over qty” on many Zebra-class units).
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
except OSError:
pass
sock.settimeout(DATAFLEX_SEND_TIMEOUT)
try:
sock.connect((ip, port))
sock.sendall(DATAFLEX_PREPRINT_BYTES)
time.sleep(DATAFLEX_POST_PREPRINT_DELAY_SEC)
try:
sock.shutdown(socket.SHUT_WR)
except OSError:
pass
finally:
sock.close()


Brief pause after send so ~JR can finish before label data. Set FPSMS_DATAFLEX_NO_JR=1 to omit ~JR.
def send_dataflex_job_counter_reset(ip: str, port: int) -> None:
"""
Full host recovery for「打袋重設」: ~JA, ~RO, and ~JR (soft reset) to clear latched E1005.
Slower than [send_dataflex_preprint_reset]; do not use on every row click.
"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
@@ -279,8 +312,8 @@ def send_dataflex_job_counter_reset(ip: str, port: int) -> None:
sock.settimeout(DATAFLEX_SEND_TIMEOUT)
try:
sock.connect((ip, port))
sock.sendall(_dataflex_recovery_payload())
time.sleep(DATAFLEX_POST_RECOVERY_DELAY_SEC)
sock.sendall(_dataflex_full_recovery_payload())
time.sleep(DATAFLEX_POST_FULL_RECOVERY_DELAY_SEC)
try:
sock.shutdown(socket.SHUT_WR)
except OSError:
@@ -297,9 +330,8 @@ def send_dataflex_reset_and_labels(
delay_sec: float,
) -> None:
"""
One TCP connection: send ~RO reset, then `copies` identical ZPL labels with delay_sec between
copies (not after the last). Avoids rapid connect/disconnect per bag, which can cause skipped
prints on some DataFlex/Zebra TCP hosts.
One TCP connection: light preprint (~JA + ~RO), short pause, then `copies` identical ZPL labels
with delay_sec between copies (not after the last). Avoids rapid connect/disconnect per bag.
"""
if copies < 1:
return
@@ -312,8 +344,8 @@ def send_dataflex_reset_and_labels(
sock.settimeout(DATAFLEX_SEND_TIMEOUT)
try:
sock.connect((ip, port))
sock.sendall(_dataflex_recovery_payload())
time.sleep(DATAFLEX_POST_RECOVERY_DELAY_SEC)
sock.sendall(DATAFLEX_PREPRINT_BYTES)
time.sleep(DATAFLEX_POST_PREPRINT_DELAY_SEC)
for i in range(copies):
sock.sendall(raw_zpl)
if i < copies - 1:
@@ -958,6 +990,14 @@ def run_laser_row_send_thread(
f"已發送,但伺服器記錄失敗:{err}",
),
)
elif base_url:
root.after(
0,
lambda: messagebox.showwarning(
"激光機",
"已發送,但無工單 id,無法寫入伺服器記錄。",
),
)
root.after(
0,
lambda: set_status_message("已發送", is_error=False),
@@ -1032,6 +1072,14 @@ def run_dataflex_fixed_qty_thread(
f"已送出 {n} 張,但伺服器記錄失敗:{err}",
),
)
else:
root.after(
0,
lambda: messagebox.showwarning(
"打袋機",
f"已送出列印 {n} 張,但無工單 id,無法寫入伺服器記錄。",
),
)
except ConnectionRefusedError:
root.after(
0,
@@ -1198,14 +1246,34 @@ def submit_job_order_print_submit(
qty: int,
print_channel: str = "LABEL",
) -> None:
"""POST /py/job-order-print-submit — one row per submit for DB wastage/stock tracking."""
"""
Record printed quantity in the FPSMS database via PyController.

POST ``/api/py/job-order-print-submit`` (path under base_url) — **public endpoint, no login**
or API key required. Each successful call appends one row to ``py_job_order_print_submit``;
totals per job order and channel are aggregated server-side.

Raises ``requests.RequestException`` if all retry attempts fail.
"""
url = f"{base_url.rstrip('/')}/py/job-order-print-submit"
resp = requests.post(
url,
json={"jobOrderId": job_order_id, "qty": qty, "printChannel": print_channel},
timeout=30,
)
resp.raise_for_status()
payload = {
"jobOrderId": int(job_order_id),
"qty": int(qty),
"printChannel": print_channel,
}
last_err: Optional[Exception] = None
for attempt in range(PRINT_SUBMIT_MAX_ATTEMPTS):
try:
resp = requests.post(url, json=payload, timeout=30)
resp.raise_for_status()
return
except requests.RequestException as ex:
last_err = ex
if attempt < PRINT_SUBMIT_MAX_ATTEMPTS - 1:
time.sleep(PRINT_SUBMIT_RETRY_DELAY_SEC)
if last_err is not None:
raise last_err
raise RuntimeError("submit_job_order_print_submit: unexpected empty error")


def set_row_highlight(row_frame: tk.Frame, selected: bool) -> None:
@@ -2031,7 +2099,7 @@ def main() -> None:
try:
# One TCP job per bag (not one endless stream). Persistent socket
# caused E1005 over-qty on some DataFlex units after a few labels.
send_dataflex_job_counter_reset(ip, port)
send_dataflex_preprint_reset(ip, port)
while not stop_ev.is_set():
send_zpl_to_dataflex(ip, port, zpl)
printed += 1


Laden…
Annuleren
Opslaan