diff --git a/listener/app.py b/listener/app.py index 7034ee82..53b35ef2 100644 --- a/listener/app.py +++ b/listener/app.py @@ -679,6 +679,7 @@ def parse_and_save_vitek_result(raw_data, port_name="VITEK"): session.rollback() finally: session.close() + def create_vitek_order_message(order): """ Membuat Frame Order Vitek sesuai Manual Ref 514937. @@ -741,9 +742,17 @@ def manage_vitek_port(config): with serial.Serial( port=port_name, baudrate=config['baud_rate'], - timeout=1 + timeout=2, + bytesize=serial.EIGHTBITS, + parity=serial.PARITY_NONE, + stopbits=serial.STOPBITS_ONE, + xonxoff=False, + rtscts=False, # Pastikan flow control mati agar tidak blocking hardware + dsrdtr=False ) as ser: - + ser.reset_input_buffer() + ser.reset_output_buffer() + print(f"[{port_name}] Port Connected. Waiting activity...") while True: has_activity = False # Penanda agar kita sleep kalau sepi @@ -753,47 +762,63 @@ def manage_vitek_port(config): try: if ser.in_waiting > 0: has_activity = True - data_chunk = ser.read(ser.in_waiting or 1024) + char = ser.read(1) + #data_chunk = ser.read(ser.in_waiting or 1024) + # 1. HANDLE HANDSHAKE (ENQ -> ACK) + if char == b'\x05': # ENQ + logging.info(f"[{port_name}] Terima ENQ. Kirim ACK.") + print(f"[{port_name}] Terima ENQ. Kirim ACK.") + ser.write(b'\x06') # ACK + rx_buffer = b"" # Siap terima frame baru + continue - if data_chunk: - # 1. Handle Handshake Awal (ENQ) - Alat minta izin kirim - if b'\x05' in data_chunk: - logging.info(f"[{port_name}] Terima ENQ. Kirim ACK.") - print(f"[{port_name}] Terima ENQ. Kirim ACK.") - ser.write(b'\x06') # ACK - rx_buffer = b"" # Reset buffer bytes - continue - - # 2. Tampung Data - rx_buffer += data_chunk + # 2. HANDLE END OF TRANSMISSION (EOT) + elif char == b'\x04': # EOT + logging.info(f"[{port_name}] Sesi Selesai (EOT).") + print(f"[{port_name}] Sesi Selesai (EOT).") + # Jika masih ada sisa buffer yang belum diproses (jarang terjadi jika logic benar), proses disini + rx_buffer = b"" + continue - # 3. Cek apakah Frame Selesai? - # Vitek biasanya mengirim per baris diakhiri LF (\n) atau CR (\r) - # Atau Frame diakhiri ETX (\x03) - # Kita kirim ACK setiap kali ada tanda akhir baris/frame agar alat lanjut kirim - if b'\x03' in data_chunk or b'\n' in data_chunk or b'\r' in data_chunk: - ser.write(b'\x06') # ACK data yang baru masuk + # 3. HANDLE DATA FRAME + else: + rx_buffer += char + if ser.in_waiting: + rx_buffer += ser.read(ser.in_waiting) + if b'\x03' in rx_buffer: + # Cek apakah sudah ada CRLF (akhir mutlak) + if rx_buffer.endswith(b'\n'): + logging.info(f"[{port_name}] Frame Lengkap diterima. KIRIM ACK!") + print(f"[{port_name}] Frame Lengkap diterima. KIRIM ACK!") + # --- KRITIKAL: KIRIM ACK SEKARANG JUGA --- + # Jangan tunggu EOT. Kirim ACK per frame agar Vitek lanjut ke pesan berikutnya (antibiotik) + ser.write(b'\x06') + # ----------------------------------------- + + # Proses Data + try: + full_str = rx_buffer.decode('latin-1', errors='ignore') + logging.info(f"[{port_name}] Data: {full_str[:50]}...") + print(f"[{port_name}] Data: {full_str[:50]}...") + # Panggil parser Vitek + if len(full_str) > 5: + parse_and_save_vitek_result(full_str, alat_name) + except Exception as e: + logging.error(f"Error Parse: {e}") + print(f"Error Parse: {e}") + # Kosongkan buffer untuk frame berikutnya + rx_buffer = b"" - # 4. Handle Akhir Transmisi (EOT) - Selesai - if b'\x04' in data_chunk: - logging.info(f"[{port_name}] Terima EOT. Memproses data...") - - # Decode saat data sudah lengkap (gunakan latin-1) - try: - full_str = rx_buffer.decode('latin-1', errors='ignore') - parse_and_save_vitek_result(full_str, alat_name) - except Exception as e: - logging.error(f"[{port_name}] Error Decoding/Parsing: {e}") - print(f"[{port_name}] Error Decoding/Parsing: {e}") - rx_buffer = b"" # Bersihkan buffer - continue - + # Safety: Jika buffer kepenuhan sampah (lebih dari 4KB) tanpa framing, buang. + if len(rx_buffer) > 5000: + logging.warning(f"[{port_name}] Buffer overflow/Garbage. Reset.") + print(f"[{port_name}] Buffer overflow/Garbage. Reset.") + rx_buffer = b"" except Exception as e: logging.error(f"[{port_name}] Error Reading: {e}") print(f"[{port_name}] Error Reading: {e}") rx_buffer = b"" # Reset jika error parah - # ========================================== # PHASE 2: SENDING ORDER (JIKA BUFFER KOSONG) # ========================================== @@ -810,80 +835,72 @@ def manage_vitek_port(config): has_activity = True # Jangan sleep lama-lama logging.info(f"[{port_name}] Menemukan Order: {pending_order.rnoreg}. Memulai Handshake...") print(f"[{port_name}] Menemukan Order: {pending_order.rnoreg}. Memulai Handshake...") - # --- STEP 1: HANDSHAKE (ENQ) --- - ser.reset_input_buffer() - ser.write(b'\x05') - time.sleep(0.5) - - # Baca balasan (tunggu ACK) - ack_response = ser.read(1) - - if ack_response == b'\x06': - logging.info(f"[{port_name}] Handshake dengan {alat_name}Sukses. Kirim Data...") - print(f"[{port_name}] Handshake dengan {alat_name} Sukses. Kirim Data...") - time.sleep(1.0) # Jeda aman - - frames = create_vitek_order_message(pending_order) - all_success = True - - # --- STEP 2: SEND FRAMES --- - for i, frame in enumerate(frames): - retry = 0 - frame_sent = False - while retry < 3: - logging.info(f"[{port_name}] Kirim Frame (Try {retry+1}): {frame}") - print(f"[{port_name}] Kirim Frame (Try {retry+1}): {frame}") - ser.write(frame) - - # Tunggu ACK - start_wait = time.time() - response = None - - while time.time() - start_wait < 3: # Timeout 3 detik - if ser.in_waiting: - response = ser.read(1) - break - - if response == b'\x06': # ACK - logging.info(f"[{port_name}] Frame ACK (OK).") - print(f"[{port_name}] Frame ACK (OK).") - frame_sent = True - break - elif response == b'\x15': # NAK - logging.warning(f"[{port_name}] Dibalas NAK (Checksum/Format Salah).") - print(f"[{port_name}] Dibalas NAK (Checksum/Format Salah).") - time.sleep(1) - retry += 1 - elif response is None: # TIMEOUT - logging.warning(f"[{port_name}] Timeout (Alat tidak membalas). Cek Framing/Kabel.") - print(f"[{port_name}] Timeout (Alat tidak membalas). Cek Framing/Kabel.") - retry += 1 - else: # Respon Aneh - logging.warning(f"[{port_name}] Respon aneh: {response}") - print(f"[{port_name}] Respon aneh: {response}") - retry += 1 + handshake_success = False + handshake_attempts = 0 + while handshake_attempts < 3: + # --- STEP 1: HANDSHAKE (ENQ) --- + ser.reset_input_buffer() + ser.write(b'\x05') + time.sleep(0.5) + response = ser.read(1) - if not frame_sent: - all_success = False + if response == b'\x06': # ACK received + handshake_success = True + break + elif response == b'\x15': # NAK (Alat nolak) + logging.warning(f"[{port_name}] Dibalas NAK. Tunggu 2 detik...") + time.sleep(2) + else: + # Timeout atau respon aneh + # logging.debug(f"[{port_name}] No ACK (Got: {response}). Retry {handshake_attempts+1}...") + pass + + handshake_attempts += 1 + time.sleep(1) # Jeda antar percobaan + + if handshake_success: + # === SUKSES HANDSHAKE, KIRIM DATA === + logging.info(f"[{port_name}] Handshake OK. Sending Frames...") + frames = create_vitek_order_message(pending_order) + all_frames_sent = True + + for frame in frames: + ser.write(frame) + # Tunggu ACK Frame + t_wait = time.time() + got_ack = False + while time.time() - t_wait < 3: # 3 detik timeout per frame + if ser.in_waiting: + if ser.read(1) == b'\x06': + got_ack = True + break + + if not got_ack: + logging.error(f"[{port_name}] Frame Timeout/NoACK. Abort.") + all_frames_sent = False break - # --- STEP 3: TERMINATE (EOT) --- - ser.write(b'\x04') + # Tutup Sesi + ser.write(b'\x04') # EOT - if all_success: - logging.info(f"[{port_name}] Order {pending_order.rnoreg} SELESAI.") - print(f"[{port_name}] Order {pending_order.rnoreg} SELESAI.") + if all_frames_sent: + logging.info(f"[{port_name}] Order Sukses Terkirim.") setattr(pending_order, flag_col, True) session.commit() else: - logging.error(f"[{port_name}] Gagal kirim order {pending_order.rnoreg}.") - print(f"[{port_name}] Gagal kirim order {pending_order.rnoreg}.") - else: - # Handshake gagal (Alat sibuk/Mati) - # logging.debug(f"[{port_name}] Alat sibuk/tidak balas ENQ.") - print(f"[{port_name}] Alat sibuk/tidak balas ENQ.") - pass + logging.warning(f"[{port_name}] Order Gagal Tengah Jalan.") + else: + # === GAGAL HANDSHAKE 3x -> FORCE RESET ALAT === + # Inilah solusi masalah "Stuck" Anda + logging.warning(f"[{port_name}] ALAT STUCK/SIBUK. Mengirim FORCE EOT (Reset State)...") + print(f"[{port_name}] ALAT STUCK -> FORCE RESET.") + + ser.write(b'\x04') # Kirim EOT paksa + time.sleep(2) # Beri waktu alat bernafas + ser.reset_input_buffer() # Buang sampah sisa + + # Kita tidak update DB, biar loop berikutnya coba lagi session.close() except Exception as e: @@ -897,7 +914,7 @@ def manage_vitek_port(config): # Jika tidak ada data masuk dan tidak ada order keluar, tidur sebentar # Ini penting agar CPU tidak 100% dan DB tidak jebol if not has_activity: - time.sleep(1.0) + time.sleep(0.5) except Exception as e: logging.critical(f"[{port_name}] Gagal connect Serial: {e}")