|
|
@@ -730,6 +730,130 @@ def laser_push_loop( |
|
|
pass |
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run_laser_continuous_print( |
|
|
|
|
|
root: tk.Tk, |
|
|
|
|
|
laser_conn_ref: list, |
|
|
|
|
|
laser_thread_ref: list, |
|
|
|
|
|
laser_stop_ref: list, |
|
|
|
|
|
ip: str, |
|
|
|
|
|
port: int, |
|
|
|
|
|
item_id: Optional[int], |
|
|
|
|
|
stock_in_line_id: Optional[int], |
|
|
|
|
|
item_code: str, |
|
|
|
|
|
item_name: str, |
|
|
|
|
|
set_status_message: Callable[[str, bool], None], |
|
|
|
|
|
) -> None: |
|
|
|
|
|
""" |
|
|
|
|
|
Laser continuous send (連續 C) in a background thread. |
|
|
|
|
|
User can stop via Stop button or keyboard: `Esc` / `Q`. |
|
|
|
|
|
""" |
|
|
|
|
|
# Stop any previous continuous run. |
|
|
|
|
|
if laser_stop_ref[0] is not None: |
|
|
|
|
|
try: |
|
|
|
|
|
laser_stop_ref[0].set() |
|
|
|
|
|
except Exception: |
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
stop_event = threading.Event() |
|
|
|
|
|
laser_stop_ref[0] = stop_event |
|
|
|
|
|
|
|
|
|
|
|
win = tk.Toplevel(root) |
|
|
|
|
|
win.title("激光機 連續列印") |
|
|
|
|
|
win.geometry("360x170") |
|
|
|
|
|
win.transient(root) |
|
|
|
|
|
win.configure(bg=BG_TOP) |
|
|
|
|
|
|
|
|
|
|
|
ttk.Label( |
|
|
|
|
|
win, |
|
|
|
|
|
text="連續送出中,按「停止」或 Esc / Q 結束", |
|
|
|
|
|
font=get_font(FONT_SIZE), |
|
|
|
|
|
background=BG_TOP, |
|
|
|
|
|
).pack(pady=(14, 8)) |
|
|
|
|
|
|
|
|
|
|
|
count_lbl = tk.Label(win, text="已送出:0 次", font=get_font(FONT_SIZE), bg=BG_TOP) |
|
|
|
|
|
count_lbl.pack(pady=6) |
|
|
|
|
|
|
|
|
|
|
|
# Ensure stop keys work even if focus is on the main window. |
|
|
|
|
|
def _unbind_stop_keys() -> None: |
|
|
|
|
|
try: |
|
|
|
|
|
root.unbind_all("<Escape>") |
|
|
|
|
|
root.unbind_all("<KeyPress-q>") |
|
|
|
|
|
root.unbind_all("<KeyPress-Q>") |
|
|
|
|
|
except Exception: |
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
def on_stop() -> None: |
|
|
|
|
|
if stop_event.is_set(): |
|
|
|
|
|
return |
|
|
|
|
|
stop_event.set() |
|
|
|
|
|
_unbind_stop_keys() |
|
|
|
|
|
try: |
|
|
|
|
|
win.destroy() |
|
|
|
|
|
except Exception: |
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
def _key_stop(_e: tk.Event) -> str: |
|
|
|
|
|
on_stop() |
|
|
|
|
|
return "break" |
|
|
|
|
|
|
|
|
|
|
|
root.bind_all("<Escape>", _key_stop) |
|
|
|
|
|
root.bind_all("<KeyPress-q>", _key_stop) |
|
|
|
|
|
root.bind_all("<KeyPress-Q>", _key_stop) |
|
|
|
|
|
|
|
|
|
|
|
ttk.Button(win, text="停止", command=on_stop, width=12).pack(pady=10) |
|
|
|
|
|
win.protocol("WM_DELETE_WINDOW", on_stop) |
|
|
|
|
|
|
|
|
|
|
|
def worker() -> None: |
|
|
|
|
|
sent = 0 |
|
|
|
|
|
try: |
|
|
|
|
|
while not stop_event.is_set(): |
|
|
|
|
|
ok, msg = send_job_to_laser_with_retry( |
|
|
|
|
|
laser_conn_ref, |
|
|
|
|
|
ip, |
|
|
|
|
|
port, |
|
|
|
|
|
item_id, |
|
|
|
|
|
stock_in_line_id, |
|
|
|
|
|
item_code, |
|
|
|
|
|
item_name, |
|
|
|
|
|
) |
|
|
|
|
|
if not ok: |
|
|
|
|
|
root.after(0, lambda m=msg: set_status_message(f"連續送出失敗:{m}", is_error=True)) |
|
|
|
|
|
break |
|
|
|
|
|
|
|
|
|
|
|
sent += 1 |
|
|
|
|
|
root.after(0, lambda s=sent: count_lbl.configure(text=f"已送出:{s} 次")) |
|
|
|
|
|
|
|
|
|
|
|
# Small delay between sends; check stop frequently. |
|
|
|
|
|
for _ in range(4): # 4 * 0.05 = 0.20 sec |
|
|
|
|
|
if stop_event.is_set(): |
|
|
|
|
|
break |
|
|
|
|
|
time.sleep(0.05) |
|
|
|
|
|
except Exception as e: |
|
|
|
|
|
root.after(0, lambda err=str(e): set_status_message(f"連續送出意外錯誤:{err}", is_error=True)) |
|
|
|
|
|
finally: |
|
|
|
|
|
_unbind_stop_keys() |
|
|
|
|
|
if not stop_event.is_set(): |
|
|
|
|
|
# Failure path ended the worker; keep window close. |
|
|
|
|
|
try: |
|
|
|
|
|
win.destroy() |
|
|
|
|
|
except Exception: |
|
|
|
|
|
pass |
|
|
|
|
|
else: |
|
|
|
|
|
# Stopped intentionally. |
|
|
|
|
|
root.after(0, lambda s=sent: set_status_message(f"已停止激光機連續送出:{s} 次", is_error=False)) |
|
|
|
|
|
|
|
|
|
|
|
laser_stop_ref[0] = None |
|
|
|
|
|
laser_thread_ref[0] = None |
|
|
|
|
|
try: |
|
|
|
|
|
win.destroy() |
|
|
|
|
|
except Exception: |
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
t = threading.Thread(target=worker, daemon=True) |
|
|
|
|
|
laser_thread_ref[0] = t |
|
|
|
|
|
t.start() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def send_job_to_laser( |
|
|
def send_job_to_laser( |
|
|
conn_ref: list, |
|
|
conn_ref: list, |
|
|
ip: str, |
|
|
ip: str, |
|
|
@@ -740,16 +864,23 @@ def send_job_to_laser( |
|
|
item_name: str, |
|
|
item_name: str, |
|
|
) -> tuple[bool, str]: |
|
|
) -> tuple[bool, str]: |
|
|
""" |
|
|
""" |
|
|
Send to laser. Standard format: {"itemId": xxx, "stockInLineId": xxx}. |
|
|
|
|
|
|
|
|
Send to laser using `;` separated 3 params: |
|
|
|
|
|
{"itemID": itemId, "stockInLineId": stockInLineId} ; itemCode ; itemName ;; |
|
|
conn_ref: [socket or None] - reused across calls; closed only when switching printer. |
|
|
conn_ref: [socket or None] - reused across calls; closed only when switching printer. |
|
|
When both item_id and stock_in_line_id present, sends JSON; else fallback: 0;item_code;item_name;; |
|
|
|
|
|
|
|
|
When both item_id and stock_in_line_id present, sends JSON first param; else fallback: 0;item_code;item_name;; |
|
|
Returns (success, message). |
|
|
Returns (success, message). |
|
|
""" |
|
|
""" |
|
|
|
|
|
code_str = (item_code or "").strip().replace(";", ",") |
|
|
|
|
|
name_str = (item_name or "").strip().replace(";", ",") |
|
|
|
|
|
|
|
|
if item_id is not None and stock_in_line_id is not None: |
|
|
if item_id is not None and stock_in_line_id is not None: |
|
|
reply = json.dumps({"itemId": item_id, "stockInLineId": stock_in_line_id}) |
|
|
|
|
|
|
|
|
# Use compact JSON so device-side parser doesn't get spaces. |
|
|
|
|
|
json_part = json.dumps( |
|
|
|
|
|
{"itemID": item_id, "stockInLineId": stock_in_line_id}, |
|
|
|
|
|
separators=(",", ":"), |
|
|
|
|
|
) |
|
|
|
|
|
reply = f"{json_part};{code_str};{name_str};;" |
|
|
else: |
|
|
else: |
|
|
code_str = (item_code or "").strip().replace(";", ",") |
|
|
|
|
|
name_str = (item_name or "").strip().replace(";", ",") |
|
|
|
|
|
reply = f"0;{code_str};{name_str};;" |
|
|
reply = f"0;{code_str};{name_str};;" |
|
|
conn = conn_ref[0] |
|
|
conn = conn_ref[0] |
|
|
try: |
|
|
try: |
|
|
@@ -1487,23 +1618,38 @@ def main() -> None: |
|
|
stock_in_line_id = j.get("stockInLineId") |
|
|
stock_in_line_id = j.get("stockInLineId") |
|
|
item_code_val = j.get("itemCode") or "" |
|
|
item_code_val = j.get("itemCode") or "" |
|
|
item_name_val = j.get("itemName") or "" |
|
|
item_name_val = j.get("itemName") or "" |
|
|
n = 100 if count == -1 else count |
|
|
|
|
|
sent = 0 |
|
|
|
|
|
for i in range(n): |
|
|
|
|
|
ok, msg = send_job_to_laser_with_retry( |
|
|
|
|
|
laser_conn_ref, ip, port, |
|
|
|
|
|
item_id, stock_in_line_id, |
|
|
|
|
|
item_code_val, item_name_val, |
|
|
|
|
|
|
|
|
if count == -1: |
|
|
|
|
|
run_laser_continuous_print( |
|
|
|
|
|
root=root, |
|
|
|
|
|
laser_conn_ref=laser_conn_ref, |
|
|
|
|
|
laser_thread_ref=laser_thread_ref, |
|
|
|
|
|
laser_stop_ref=laser_stop_ref, |
|
|
|
|
|
ip=ip, |
|
|
|
|
|
port=port, |
|
|
|
|
|
item_id=item_id, |
|
|
|
|
|
stock_in_line_id=stock_in_line_id, |
|
|
|
|
|
item_code=item_code_val, |
|
|
|
|
|
item_name=item_name_val, |
|
|
|
|
|
set_status_message=set_status_message, |
|
|
) |
|
|
) |
|
|
if ok: |
|
|
|
|
|
sent += 1 |
|
|
|
|
|
else: |
|
|
|
|
|
set_status_message(f"已送出 {sent} 次,第 {sent + 1} 次失敗:{msg}", is_error=True) |
|
|
|
|
|
break |
|
|
|
|
|
if i < n - 1: |
|
|
|
|
|
time.sleep(0.2) |
|
|
|
|
|
if sent == n: |
|
|
|
|
|
set_status_message(f"已送出激光機:{sent} 次", is_error=False) |
|
|
|
|
|
|
|
|
else: |
|
|
|
|
|
n = count |
|
|
|
|
|
sent = 0 |
|
|
|
|
|
for i in range(n): |
|
|
|
|
|
ok, msg = send_job_to_laser_with_retry( |
|
|
|
|
|
laser_conn_ref, ip, port, |
|
|
|
|
|
item_id, stock_in_line_id, |
|
|
|
|
|
item_code_val, item_name_val, |
|
|
|
|
|
) |
|
|
|
|
|
if ok: |
|
|
|
|
|
sent += 1 |
|
|
|
|
|
else: |
|
|
|
|
|
set_status_message(f"已送出 {sent} 次,第 {sent + 1} 次失敗:{msg}", is_error=True) |
|
|
|
|
|
break |
|
|
|
|
|
if i < n - 1: |
|
|
|
|
|
time.sleep(0.2) |
|
|
|
|
|
if sent == n: |
|
|
|
|
|
set_status_message(f"已送出激光機:{sent} 次", is_error=False) |
|
|
|
|
|
|
|
|
for w in (row, left, batch_lbl, code_lbl, name_lbl): |
|
|
for w in (row, left, batch_lbl, code_lbl, name_lbl): |
|
|
w.bind("<Button-1>", _on_click) |
|
|
w.bind("<Button-1>", _on_click) |
|
|
|