This commit is contained in:
Dwi Swandhana
2026-01-31 15:51:45 +07:00
parent 11bc7e172f
commit 3af340d09d
3 changed files with 251 additions and 320 deletions
@@ -24,7 +24,7 @@ use App\PeriksaSYNC;
use App\Logbook; use App\Logbook;
use App\Worklist; use App\Worklist;
use App\Jawaban; use App\Jawaban;
use App\PendaftaranOnListiner; use App\Paslab;
use App\Subjawaban; use App\Subjawaban;
use App\Organisms; use App\Organisms;
use App\Dokter; use App\Dokter;
@@ -1095,39 +1095,33 @@ class FrontpageController extends Controller
]); ]);
$pesan = $nofoto; $pesan = $nofoto;
if ($kd_spesimen != '' AND $nm_spesimen != ''){ if ($kd_spesimen != '' AND $nm_spesimen != ''){
PendaftaranOnListiner::updateOrCreate( Paslab::updateOrCreate(
[ [
'rnoreg' => $nofoto, 'rnoreg' => $nofoto,
], ],
[ [
'rtglast' => $tglsekarang,
'norm' => $noregister,
'nama' => $nama, 'nama' => $nama,
'norm' => $noregister,
'rtglast' => $mulai,
'alamat' => $alamat, 'alamat' => $alamat,
'telp' => $telpon,
'hp' => $telpon,
'tgllahir' => $tgllahir,
'umur' => $usia,
'rjenis' => $jk, 'rjenis' => $jk,
'kodedok' => $notransaksi, 'umur' => $usia,
'namadok' => $klinisi, 'namadok' => $dokter,
'ruangan' => $kamar, 'ruangan' => 'Mikrobiologi',
'tes' => $rekues, 'tes' => $rekues,
'alat' => 'All', 'alat' => 'ALL',
'kd_spesimen' => $kd_spesimen, 'kd_spesimen' => $kd_spesimen,
'nm_spesimen' => $nm_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 != ''){ if ($pesan != ''){
return response()->json(['status' => 'Sukses', 'message' => $pesan], 201); return response()->json(['status' => 'Sukses', 'message' => $pesan], 201);
@@ -1320,65 +1314,33 @@ class FrontpageController extends Controller
]); ]);
$pesan = $nofoto; $pesan = $nofoto;
try { try {
DB::table('paslab')->insert([ Paslab::updateOrCreate(
'rnoreg' => $nofoto, [
'nama' => $nama, 'rnoreg' => $nofoto,
'norm' => $noregister, ],
'rtglast' => $mulai, [
'alamat' => $alamat, 'nama' => $nama,
'rjenis' => $jk, 'norm' => $noregister,
'umur' => $usia, 'rtglast' => $mulai,
'namadok' => $dokter, 'alamat' => $alamat,
'ruangan' => 'Mikrobiologi', 'rjenis' => $jk,
'tes' => $rekues, 'umur' => $usia,
'alat' => 'ALL', 'namadok' => $dokter,
'kd_spesimen' => $kd_spesimen, 'ruangan' => 'Mikrobiologi',
'nm_spesimen' => $nm_spesimen, 'tes' => $rekues,
'tgllahir' => $tgllahir, 'alat' => 'ALL',
'flg_vitek1' => false, 'kd_spesimen' => $kd_spesimen,
'flg_vitek2' => false, 'nm_spesimen' => $nm_spesimen,
'flg_bd1' => false, 'tgllahir' => $tgllahir,
'flg_bd2' => false, 'flg_vitek1' => false,
'flg_gxp1' => false, 'flg_vitek2' => false,
'flg_gxp2' => false, 'flg_bd1' => false,
]); 'flg_bd2' => false,
if ($kd_spesimen != '' AND $nm_spesimen != ''){ 'flg_gxp1' => false,
PendaftaranOnListiner::updateOrCreate( 'flg_gxp2' => false,
[ 'flg_gxp3' => false,
'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);
*/
}
return response()->json(['status' => 'Sukses', 'message' => $pesan], 201); return response()->json(['status' => 'Sukses', 'message' => $pesan], 201);
} catch (Exception $e) { } catch (Exception $e) {
return response()->json(['status' => 'Sukses', 'message' => $pesan], 201); return response()->json(['status' => 'Sukses', 'message' => $pesan], 201);
@@ -1624,44 +1586,9 @@ class FrontpageController extends Controller
'flg_bd2' => false, 'flg_bd2' => false,
'flg_gxp1' => false, 'flg_gxp1' => false,
'flg_gxp2' => 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'; $pesan = $pesan.' Nomor Lokal '.$nofoto.' dengan No. Urut '.$noloket.' Berhasil di Simpan';
if ($pesan != ''){ if ($pesan != ''){
if ($file != '' AND $jenisgambar != ''){ if ($file != '' AND $jenisgambar != ''){
@@ -1758,32 +1685,33 @@ class FrontpageController extends Controller
'nm_spesimen' => $nm_spesimen, 'nm_spesimen' => $nm_spesimen,
]); ]);
$pesan = $pesan.' Data Berhasil di Update'; $pesan = $pesan.' Data Berhasil di Update';
if ($kd_spesimen != '' AND $nm_spesimen != ''){ Paslab::updateOrCreate(
PendaftaranOnListiner::updateOrCreate( [
[ 'rnoreg' => $nofoto,
'rnoreg' => $nofoto, ],
], [
[ 'nama' => $nama,
'rtglast' => $mulai, 'norm' => $noregister,
'norm' => $noregister, 'rtglast' => $mulai,
'nama' => $nama, 'alamat' => $alamat,
'alamat' => $alamat, 'rjenis' => $jk,
'telp' => $telpon, 'umur' => $usia,
'hp' => $telpon, 'namadok' => $dokter,
'tgllahir' => $tgllahir, 'ruangan' => 'Mikrobiologi',
'umur' => $usia, 'tes' => $rekues,
'rjenis' => $jk, 'alat' => 'ALL',
'kodedok' => Session('id'), 'kd_spesimen' => $kd_spesimen,
'namadok' => Session('nama'), 'nm_spesimen' => $nm_spesimen,
'ruangan' => 'Mikrobiologi', 'tgllahir' => $tgllahir,
'tes' => $rekues, 'flg_vitek1' => false,
'alat' => 'All', 'flg_vitek2' => false,
'kd_spesimen' => $kd_spesimen, 'flg_bd1' => false,
'nm_spesimen' => $nm_spesimen, 'flg_bd2' => false,
] 'flg_gxp1' => false,
); 'flg_gxp2' => false,
'flg_gxp3' => false,
} ]
);
if ($pesan != ''){ if ($pesan != ''){
if ($file != '' AND $jenisgambar != ''){ if ($file != '' AND $jenisgambar != ''){
Xfiles::create([ Xfiles::create([
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Paslab extends Model
{
protected $table = "paslab";
protected $guarded = [];
}
+168 -176
View File
@@ -733,194 +733,186 @@ def manage_vitek_port(config):
port_name = config['port'] port_name = config['port']
flag_col = config.get('flag_column') flag_col = config.get('flag_column')
alat_name = config.get('alat_name', 'VITEK') alat_name = config.get('alat_name', 'VITEK')
logging.info(f"[{port_name}] Membuka port untuk alat {alat_name}...")
print(f"[{port_name}] Membuka port untuk alat {alat_name}...") logging.info(f"[{port_name}] START VITEK SERVICE (Relaxed Mode)...")
# Buffer untuk menampung pecahan data print(f"[{port_name}] START VITEK SERVICE (Relaxed Mode)...")
rx_buffer = b"" while True:
try:
with serial.Serial(
port=port_name,
baudrate=config['baud_rate'],
timeout=2,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
xonxoff=False,
rtscts=False,
dsrdtr=False
) as ser:
ser.reset_input_buffer()
logging.info(f"[{port_name}] Ready & Listening.")
try: while True:
with serial.Serial( # ==========================================
port=port_name, # PHASE 1: LISTENING
baudrate=config['baud_rate'], # ==========================================
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
# ==========================================
# PHASE 1: LISTENING (PRIORITAS UTAMA)
# ==========================================
try:
if ser.in_waiting > 0: if ser.in_waiting > 0:
has_activity = True header = ser.read(1)
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
# 2. HANDLE END OF TRANSMISSION (EOT) # --- HANDSHAKE ---
elif char == b'\x04': # EOT if header == b'\x05':
logging.info(f"[{port_name}] Sesi Selesai (EOT).") logging.info(f"[{port_name}] Got ENQ -> Reply ACK")
print(f"[{port_name}] Sesi Selesai (EOT).") print(f"[{port_name}] Got ENQ -> Reply ACK")
# Jika masih ada sisa buffer yang belum diproses (jarang terjadi jika logic benar), proses disini ser.write(b'\x06')
rx_buffer = b"" ser.reset_input_buffer()
continue
# 3. HANDLE DATA FRAME # --- DATA FRAME ---
else: elif header == b'\x02':
rx_buffer += char logging.info(f"[{port_name}] Frame Start. Reading...")
if ser.in_waiting: print(f"[{port_name}] Frame Start. Reading...")
rx_buffer += ser.read(ser.in_waiting) original_timeout = ser.timeout
if b'\x03' in rx_buffer: ser.timeout = 1.5
# Cek apakah sudah ada CRLF (akhir mutlak) body = ser.read_until(b'\x03')
if rx_buffer.endswith(b'\n'): ser.timeout = original_timeout
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""
# Safety: Jika buffer kepenuhan sampah (lebih dari 4KB) tanpa framing, buang. full_frame = header + body
if len(rx_buffer) > 5000:
logging.warning(f"[{port_name}] Buffer overflow/Garbage. Reset.")
print(f"[{port_name}] Buffer overflow/Garbage. Reset.") is_valid = False
rx_buffer = b""
except Exception as e: if full_frame.endswith(b'\x03'):
logging.error(f"[{port_name}] Error Reading: {e}") is_valid = True
print(f"[{port_name}] Error Reading: {e}") elif b'\x1d' in full_frame[-10:]:
rx_buffer = b"" # Reset jika error parah # Cek 10 karakter terakhir, ada GS gak?
# Log Anda: ...|zz|\x1de7 (GS ada di posisi -3)
# ========================================== is_valid = True
# PHASE 2: SENDING ORDER (JIKA BUFFER KOSONG) 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...")
# Kita hanya kirim order jika sedang tidak menerima data (buffer kosong) if is_valid:
if not rx_buffer and flag_col: logging.info(f"[{port_name}] Frame Accepted. Sending ACK.")
try: print(f"[{port_name}] Frame OK -> ACK Sent.")
session = SessionLocal() print(f"[{port_name}] FULL FRAME: {full_frame}")
# Cari order yang belum dikirim # 1. KIRIM ACK (WAJIB)
pending_order = session.query(PaslabOrder).filter( ser.write(b'\x06')
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
handshake_attempts += 1 # 2. Proses Data
time.sleep(1) # Jeda antar percobaan try:
full_str = full_frame.decode('latin-1', errors='ignore')
if handshake_success: # Debug
# === SUKSES HANDSHAKE, KIRIM DATA === # print(f"[{port_name}] CONTENT: {full_str}")
logging.info(f"[{port_name}] Handshake OK. Sending Frames...") parse_and_save_vitek_result(full_str, alat_name)
frames = create_vitek_order_message(pending_order) except Exception as e:
all_frames_sent = True logging.error(f"[{port_name}] Parse Err: {e}")
print(f"[{port_name}] Parse Err: {e}")
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.")
else: else:
# === GAGAL HANDSHAKE 3x -> FORCE RESET ALAT === logging.warning(f"[{port_name}] Frame Corrupt/Timeout: {full_frame}")
# Inilah solusi masalah "Stuck" Anda print(f"[{port_name}] Frame Corrupt/Timeout: {full_frame}")
logging.warning(f"[{port_name}] ALAT STUCK/SIBUK. Mengirim FORCE EOT (Reset State)...") ser.write(b'\x15') # NAK
print(f"[{port_name}] ALAT STUCK -> FORCE RESET.")
# --- 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 if pending_order:
time.sleep(2) # Beri waktu alat bernafas logging.info(f"[{port_name}] Ada Order: {pending_order.rnoreg}. Coba Handshake...")
ser.reset_input_buffer() # Buang sampah sisa print(f"[{port_name}] Ada Order: {pending_order.rnoreg}...")
# Kita tidak update DB, biar loop berikutnya coba lagi
session.close()
except Exception as e: # --- LOGIC HANDSHAKE DENGAN RETRY ---
logging.error(f"[{port_name}] Error Sending Logic: {e}") handshake_success = False
print(f"[{port_name}] Error Sending Logic: {e}")
if 'session' in locals(): session.close() # 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)
# ========================================== session.close()
# 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)
except Exception as e: except Exception as e:
logging.critical(f"[{port_name}] Gagal connect Serial: {e}") logging.error(f"[{port_name}] Sending Error: {e}")
print(f"[{port_name}] Gagal connect Serial: {e}") print(f"[{port_name}] Sending Error: {e}")
time.sleep(5) 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 # BECTON DICKINSON (BD) BACTEC PARSER
# ========================================== # ==========================================