diff --git a/htdocs/app/Http/Controllers/FrontpageController.php b/htdocs/app/Http/Controllers/FrontpageController.php index 599d40cb..18aaa3cc 100644 --- a/htdocs/app/Http/Controllers/FrontpageController.php +++ b/htdocs/app/Http/Controllers/FrontpageController.php @@ -24,7 +24,7 @@ use App\PeriksaSYNC; use App\Logbook; use App\Worklist; use App\Jawaban; -use App\PendaftaranOnListiner; +use App\Paslab; use App\Subjawaban; use App\Organisms; use App\Dokter; @@ -1095,39 +1095,33 @@ class FrontpageController extends Controller ]); $pesan = $nofoto; if ($kd_spesimen != '' AND $nm_spesimen != ''){ - PendaftaranOnListiner::updateOrCreate( + Paslab::updateOrCreate( [ 'rnoreg' => $nofoto, ], [ - 'rtglast' => $tglsekarang, - 'norm' => $noregister, 'nama' => $nama, + 'norm' => $noregister, + 'rtglast' => $mulai, 'alamat' => $alamat, - 'telp' => $telpon, - 'hp' => $telpon, - 'tgllahir' => $tgllahir, - 'umur' => $usia, 'rjenis' => $jk, - 'kodedok' => $notransaksi, - 'namadok' => $klinisi, - 'ruangan' => $kamar, + 'umur' => $usia, + 'namadok' => $dokter, + 'ruangan' => 'Mikrobiologi', 'tes' => $rekues, - 'alat' => 'All', + 'alat' => 'ALL', 'kd_spesimen' => $kd_spesimen, 'nm_spesimen' => $nm_spesimen, + 'tgllahir' => $tgllahir, + 'flg_vitek1' => false, + 'flg_vitek2' => false, + 'flg_bd1' => false, + 'flg_bd2' => false, + 'flg_gxp1' => false, + 'flg_gxp2' => false, + 'flg_gxp3' => false, ] ); - $dataForLis = [ - 'nama' => $nama, - 'noregister' => $noregister, - 'nofoto' => $nofoto, - 'kd_spesimen' => $kd_spesimen, - 'nm_spesimen' => $nm_spesimen, - 'tgllahir' => $tgllahir, - 'jk' => $jk, - ]; - //$this->sendRegistrationToLis($dataForLis); } if ($pesan != ''){ return response()->json(['status' => 'Sukses', 'message' => $pesan], 201); @@ -1320,65 +1314,33 @@ class FrontpageController extends Controller ]); $pesan = $nofoto; try { - DB::table('paslab')->insert([ - 'rnoreg' => $nofoto, - 'nama' => $nama, - 'norm' => $noregister, - 'rtglast' => $mulai, - 'alamat' => $alamat, - 'rjenis' => $jk, - 'umur' => $usia, - 'namadok' => $dokter, - 'ruangan' => 'Mikrobiologi', - 'tes' => $rekues, - 'alat' => 'ALL', - 'kd_spesimen' => $kd_spesimen, - 'nm_spesimen' => $nm_spesimen, - 'tgllahir' => $tgllahir, - 'flg_vitek1' => false, - 'flg_vitek2' => false, - 'flg_bd1' => false, - 'flg_bd2' => false, - 'flg_gxp1' => false, - 'flg_gxp2' => false, - ]); - if ($kd_spesimen != '' AND $nm_spesimen != ''){ - PendaftaranOnListiner::updateOrCreate( - [ - 'rnoreg' => $nofoto, - ], - [ - 'rtglast' => $tglsekarang, - 'norm' => $noregister, - 'nama' => $nama, - 'alamat' => $alamat, - 'telp' => $telpon, - 'hp' => $telpon, - 'tgllahir' => $tgllahir, - 'umur' => $usia, - 'rjenis' => $jk, - 'kodedok' => Session('id'), - 'namadok' => Session('nama'), - 'ruangan' => $kamar, - 'tes' => $rekues, - 'alat' => 'All', - 'kd_spesimen' => $kd_spesimen, - 'nm_spesimen' => $nm_spesimen, - ] - ); - /* - $dataForLis = [ - 'nama' => $nama, - 'noregister' => $noregister, - 'nofoto' => $nofoto, - 'kd_spesimen' => $kd_spesimen, - 'nm_spesimen' => $nm_spesimen, - 'tgllahir' => $tgllahir, - 'jk' => $jk, - ]; - $this->sendRegistrationToLis($dataForLis); - */ - } + Paslab::updateOrCreate( + [ + 'rnoreg' => $nofoto, + ], + [ + 'nama' => $nama, + 'norm' => $noregister, + 'rtglast' => $mulai, + 'alamat' => $alamat, + 'rjenis' => $jk, + 'umur' => $usia, + 'namadok' => $dokter, + 'ruangan' => 'Mikrobiologi', + 'tes' => $rekues, + 'alat' => 'ALL', + 'kd_spesimen' => $kd_spesimen, + 'nm_spesimen' => $nm_spesimen, + 'tgllahir' => $tgllahir, + 'flg_vitek1' => false, + 'flg_vitek2' => false, + 'flg_bd1' => false, + 'flg_bd2' => false, + 'flg_gxp1' => false, + 'flg_gxp2' => false, + 'flg_gxp3' => false, + ] + ); return response()->json(['status' => 'Sukses', 'message' => $pesan], 201); } catch (Exception $e) { return response()->json(['status' => 'Sukses', 'message' => $pesan], 201); @@ -1624,44 +1586,9 @@ class FrontpageController extends Controller 'flg_bd2' => false, 'flg_gxp1' => false, 'flg_gxp2' => false, + 'flg_gxp3' => false, ]); - if ($kd_spesimen != '' AND $nm_spesimen != ''){ - PendaftaranOnListiner::updateOrCreate( - [ - 'rnoreg' => $nofoto, - ], - [ - 'rtglast' => $mulai, - 'norm' => $noregister, - 'nama' => $nama, - 'alamat' => $alamat, - 'telp' => $telpon, - 'hp' => $telpon, - 'tgllahir' => $tgllahir, - 'umur' => $usia, - 'rjenis' => $jk, - 'kodedok' => Session('id'), - 'namadok' => Session('nama'), - 'ruangan' => $kamar, - 'tes' => $rekues, - 'alat' => 'All', - 'kd_spesimen' => $kd_spesimen, - 'nm_spesimen' => $nm_spesimen, - ] - ); - /* - $dataForLis = [ - 'nama' => $nama, - 'noregister' => $noregister, - 'nofoto' => $nofoto, - 'kd_spesimen' => $kd_spesimen, - 'nm_spesimen' => $nm_spesimen, - 'tgllahir' => $tgllahir, - 'jk' => $jk, - ]; - $this->sendRegistrationToLis($dataForLis); - */ - } + $pesan = $pesan.' Nomor Lokal '.$nofoto.' dengan No. Urut '.$noloket.' Berhasil di Simpan'; if ($pesan != ''){ if ($file != '' AND $jenisgambar != ''){ @@ -1758,32 +1685,33 @@ class FrontpageController extends Controller 'nm_spesimen' => $nm_spesimen, ]); $pesan = $pesan.' Data Berhasil di Update'; - if ($kd_spesimen != '' AND $nm_spesimen != ''){ - PendaftaranOnListiner::updateOrCreate( - [ - 'rnoreg' => $nofoto, - ], - [ - 'rtglast' => $mulai, - 'norm' => $noregister, - 'nama' => $nama, - 'alamat' => $alamat, - 'telp' => $telpon, - 'hp' => $telpon, - 'tgllahir' => $tgllahir, - 'umur' => $usia, - 'rjenis' => $jk, - 'kodedok' => Session('id'), - 'namadok' => Session('nama'), - 'ruangan' => 'Mikrobiologi', - 'tes' => $rekues, - 'alat' => 'All', - 'kd_spesimen' => $kd_spesimen, - 'nm_spesimen' => $nm_spesimen, - ] - ); - - } + Paslab::updateOrCreate( + [ + 'rnoreg' => $nofoto, + ], + [ + 'nama' => $nama, + 'norm' => $noregister, + 'rtglast' => $mulai, + 'alamat' => $alamat, + 'rjenis' => $jk, + 'umur' => $usia, + 'namadok' => $dokter, + 'ruangan' => 'Mikrobiologi', + 'tes' => $rekues, + 'alat' => 'ALL', + 'kd_spesimen' => $kd_spesimen, + 'nm_spesimen' => $nm_spesimen, + 'tgllahir' => $tgllahir, + 'flg_vitek1' => false, + 'flg_vitek2' => false, + 'flg_bd1' => false, + 'flg_bd2' => false, + 'flg_gxp1' => false, + 'flg_gxp2' => false, + 'flg_gxp3' => false, + ] + ); if ($pesan != ''){ if ($file != '' AND $jenisgambar != ''){ Xfiles::create([ diff --git a/htdocs/app/Paslab.php b/htdocs/app/Paslab.php new file mode 100644 index 00000000..252de0a5 --- /dev/null +++ b/htdocs/app/Paslab.php @@ -0,0 +1,11 @@ + 0: - has_activity = True - 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 + header = ser.read(1) - # 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 + # --- HANDSHAKE --- + if header == b'\x05': + logging.info(f"[{port_name}] Got ENQ -> Reply ACK") + print(f"[{port_name}] Got ENQ -> Reply ACK") + ser.write(b'\x06') + ser.reset_input_buffer() - # 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"" + # --- DATA FRAME --- + elif header == b'\x02': + logging.info(f"[{port_name}] Frame Start. Reading...") + print(f"[{port_name}] Frame Start. Reading...") + original_timeout = ser.timeout + ser.timeout = 1.5 + body = ser.read_until(b'\x03') + ser.timeout = original_timeout - # 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) - # ========================================== - # Kita hanya kirim order jika sedang tidak menerima data (buffer kosong) - if not rx_buffer and flag_col: - try: - session = SessionLocal() - # Cari order yang belum dikirim - pending_order = session.query(PaslabOrder).filter( - getattr(PaslabOrder, flag_col) == False - ).first() - - if pending_order: - 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...") - 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 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 + full_frame = header + body + + + is_valid = False + + if full_frame.endswith(b'\x03'): + is_valid = True + elif b'\x1d' in full_frame[-10:]: + # Cek 10 karakter terakhir, ada GS gak? + # Log Anda: ...|zz|\x1de7 (GS ada di posisi -3) + is_valid = True + logging.warning(f"[{port_name}] Frame tanpa ETX tapi ada Checksum. Menerima paksa...") + print(f"[{port_name}] Frame tanpa ETX tapi ada Checksum. Menerima paksa...") + if is_valid: + logging.info(f"[{port_name}] Frame Accepted. Sending ACK.") + print(f"[{port_name}] Frame OK -> ACK Sent.") + print(f"[{port_name}] FULL FRAME: {full_frame}") + # 1. KIRIM ACK (WAJIB) + ser.write(b'\x06') - 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 - - # Tutup Sesi - ser.write(b'\x04') # EOT - - if all_frames_sent: - logging.info(f"[{port_name}] Order Sukses Terkirim.") - setattr(pending_order, flag_col, True) - session.commit() - else: - logging.warning(f"[{port_name}] Order Gagal Tengah Jalan.") - + # 2. Proses Data + try: + full_str = full_frame.decode('latin-1', errors='ignore') + # Debug + # print(f"[{port_name}] CONTENT: {full_str}") + parse_and_save_vitek_result(full_str, alat_name) + except Exception as e: + logging.error(f"[{port_name}] Parse Err: {e}") + print(f"[{port_name}] Parse Err: {e}") 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.") + logging.warning(f"[{port_name}] Frame Corrupt/Timeout: {full_frame}") + print(f"[{port_name}] Frame Corrupt/Timeout: {full_frame}") + ser.write(b'\x15') # NAK + + # --- EOT --- + elif header == b'\x04': + logging.info(f"[{port_name}] Session End (EOT).") + print(f"[{port_name}] Session End (EOT).") + ser.reset_input_buffer() + + else: + pass + + # ========================================== + # PHASE 2: SENDING ORDER (JIKA IDLE) + # ========================================== + else: + # Kita masuk sini jika ser.in_waiting == 0 (Sepi) + # Pastikan kolom flag diset di config + if flag_col: + try: + session = SessionLocal() + # Cari order yang belum dikirim + pending_order = session.query(PaslabOrder).filter( + getattr(PaslabOrder, flag_col) == False + ).first() - 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() + if pending_order: + logging.info(f"[{port_name}] Ada Order: {pending_order.rnoreg}. Coba Handshake...") + print(f"[{port_name}] Ada Order: {pending_order.rnoreg}...") - except Exception as e: - logging.error(f"[{port_name}] Error Sending Logic: {e}") - print(f"[{port_name}] Error Sending Logic: {e}") - if 'session' in locals(): session.close() + # --- LOGIC HANDSHAKE DENGAN RETRY --- + handshake_success = False + + # Coba kirim ENQ max 3 kali + for attempt in range(3): + ser.reset_input_buffer() + ser.write(b'\x05') # Kirim ENQ + time.sleep(0.5) # Tunggu balasan + + if ser.in_waiting: + resp = ser.read(1) + if resp == b'\x06': # Dapat ACK + handshake_success = True + break + elif resp == b'\x15': # Dapat NAK + time.sleep(1) + else: + # Timeout, alat diam saja + pass + + if handshake_success: + # === KIRIM DATA ORDER === + logging.info(f"[{port_name}] Handshake OK. Kirim Frames...") + frames = create_vitek_order_message(pending_order) + all_sent = True + + for frame in frames: + ser.write(frame) + # Tunggu ACK per frame + got_ack = False + wait_start = time.time() + while time.time() - wait_start < 3: + if ser.in_waiting: + if ser.read(1) == b'\x06': + got_ack = True + break + + if not got_ack: + all_sent = False + break + + # Tutup Sesi + ser.write(b'\x04') # EOT + + if all_sent: + logging.info(f"[{port_name}] Order SELESAI Terkirim.") + print(f"[{port_name}] Order SELESAI Terkirim.") + setattr(pending_order, flag_col, True) + session.commit() + else: + logging.error(f"[{port_name}] Order Gagal (No ACK).") + + else: + # === FORCE RESET (ANTI-STUCK) === + # Jika sudah 3x ENQ tidak dibalas, anggap alat 'bengong' + # Kirim EOT untuk mereset status alat + logging.warning(f"[{port_name}] Alat Sibuk/Stuck. Kirim Force EOT.") + print(f"[{port_name}] Alat Sibuk/Stuck. Kirim Force EOT.") + ser.write(b'\x04') + time.sleep(1) - # ========================================== - # PHASE 3: IDLE MANAGEMENT - # ========================================== - # 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(0.5) + session.close() - except Exception as e: - logging.critical(f"[{port_name}] Gagal connect Serial: {e}") - print(f"[{port_name}] Gagal connect Serial: {e}") - time.sleep(5) + except Exception as e: + logging.error(f"[{port_name}] Sending Error: {e}") + print(f"[{port_name}] Sending Error: {e}") + if 'session' in locals(): session.close() + + # Sleep penting agar CPU tidak 100% saat idle + time.sleep(0.1) + + except Exception as e: + logging.critical(f"[{port_name}] Serial Crash: {e}") + time.sleep(5) + # ========================================== # BECTON DICKINSON (BD) BACTEC PARSER # ==========================================