Add MyLA Vitek3 flag handling
This commit is contained in:
@@ -1078,6 +1078,7 @@ class FrontpageController extends Controller
|
||||
'flg_gxp1' => false,
|
||||
'flg_gxp2' => false,
|
||||
'flg_gxp3' => false,
|
||||
'flg_vitek3' => false,
|
||||
]
|
||||
);
|
||||
if ($pesan != ''){
|
||||
@@ -1295,6 +1296,7 @@ class FrontpageController extends Controller
|
||||
'flg_gxp1' => false,
|
||||
'flg_gxp2' => false,
|
||||
'flg_gxp3' => false,
|
||||
'flg_vitek3' => false,
|
||||
]
|
||||
);
|
||||
return response()->json(['status' => 'Sukses', 'message' => $pesan], 201);
|
||||
@@ -1544,6 +1546,7 @@ class FrontpageController extends Controller
|
||||
'flg_gxp1' => false,
|
||||
'flg_gxp2' => false,
|
||||
'flg_gxp3' => false,
|
||||
'flg_vitek3' => false,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -1668,6 +1671,7 @@ class FrontpageController extends Controller
|
||||
'flg_gxp1' => false,
|
||||
'flg_gxp2' => false,
|
||||
'flg_gxp3' => false,
|
||||
'flg_vitek3' => false,
|
||||
]
|
||||
);
|
||||
if ($pesan != ''){
|
||||
@@ -1736,6 +1740,7 @@ class FrontpageController extends Controller
|
||||
'flg_gxp1' => false,
|
||||
'flg_gxp2' => false,
|
||||
'flg_gxp3' => false,
|
||||
'flg_vitek3' => false,
|
||||
]
|
||||
);
|
||||
}catch (Exception $e) {
|
||||
|
||||
+170
-76
@@ -102,7 +102,7 @@ MYLA_PORT = 60090
|
||||
|
||||
# Karakter kontrol standar
|
||||
STX, ETX, ACK, NAK, EOT, ENQ = b'\x02', b'\x03', b'\x06', b'\x15', b'\x04', b'\x05'
|
||||
RS, GS = b'\x1e', b'\x1d' # Record Separator, Group Separator
|
||||
RS, GS = b'\x1e', b'\x1d'
|
||||
ports_lock = threading.Lock()
|
||||
active_serial_ports = {}
|
||||
|
||||
@@ -183,6 +183,7 @@ class PaslabOrder(Base):
|
||||
flg_gxp1 = Column(Boolean, default=False)
|
||||
flg_gxp2 = Column(Boolean, default=False)
|
||||
flg_gxp3 = Column(Boolean, default=False)
|
||||
flg_vitek3 = Column(Boolean, default=False)
|
||||
created_at = Column(SqDateTime, default=datetime.datetime.now)
|
||||
updated_at = Column(SqDateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)
|
||||
|
||||
@@ -271,7 +272,6 @@ def trigger_result_query_to_genexpert(accnumber, register_no, target_ip=None, wa
|
||||
if not target_ips:
|
||||
return {"ok": False, "message": f"Koneksi GeneXpert {target_ip} tidak aktif."}
|
||||
else:
|
||||
# Default: kirim query ke semua GeneXpert yang sedang terkoneksi.
|
||||
target_ips = active_ips
|
||||
|
||||
ts = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
|
||||
@@ -301,7 +301,6 @@ def trigger_result_query_to_genexpert(accnumber, register_no, target_ip=None, wa
|
||||
try:
|
||||
conn.sendall(mllp_payload)
|
||||
sent_ips.append(ip)
|
||||
logging.info(f"[GENEXPERT-QUERY] Kirim query hasil accnumber={accnumber} ke {ip}")
|
||||
print(f"[GENEXPERT-QUERY] Kirim query hasil accnumber={accnumber} ke {ip}")
|
||||
except Exception as e:
|
||||
failed_ips.append({"ip": ip, "error": str(e)})
|
||||
@@ -530,7 +529,10 @@ def create_hl7_dsr_response(order, msg_control_id, qrd_segment):
|
||||
# Mapping Data
|
||||
sample_id = str(order.rnoreg)
|
||||
pid_norm = order.norm if order.norm else ""
|
||||
pid_nama = order.nama if order.nama else "No Name"
|
||||
first_name, last_name = split_patient_name(order.nama)
|
||||
pid_nama = f"{last_name}^{first_name}"
|
||||
room = str(getattr(order, "ruangan", "") or "RSSA MALANG").strip().upper()
|
||||
room = room.replace("|", " ").replace("^", " ")[:30]
|
||||
|
||||
# Gender
|
||||
raw_gender = str(order.rjenis).upper()
|
||||
@@ -545,11 +547,12 @@ def create_hl7_dsr_response(order, msg_control_id, qrd_segment):
|
||||
# GeneXpert standar biasanya terima format ORM di dalam DSR atau sequence PID-ORC-OBR
|
||||
|
||||
pid = f"PID|1||{pid_norm}||{pid_nama}|||{pid_gender}"
|
||||
pv1 = f"PV1|1|I|{room}"
|
||||
orc = f"ORC|NW|{sample_id}"
|
||||
obr = f"OBR|1|{sample_id}||{test_code}^{nama_tes_db}^L|||{timestamp}"
|
||||
|
||||
# Gabungkan
|
||||
return f"{msh}\r{msa}\r{qrd}\r{qrf}\r{pid}\r{orc}\r{obr}\r"
|
||||
return f"{msh}\r{msa}\r{qrd}\r{qrf}\r{pid}\r{pv1}\r{orc}\r{obr}\r"
|
||||
|
||||
else:
|
||||
# Jika TIDAK ADA ORDER (Not Found)
|
||||
@@ -559,8 +562,8 @@ def create_hl7_dsr_response(order, msg_control_id, qrd_segment):
|
||||
|
||||
def parse_myla_result(hl7_message, device_name="MYLA"):
|
||||
"""
|
||||
Parser khusus untuk HL7 dari bioMérieux MYLA.
|
||||
Menangani hasil BACT/ALERT (Pos/Neg) dan VITEK (Organisme & AST/Sensitivitas).
|
||||
Parser khusus untuk HL7 dari bioMérieux MYLA (Kalibrasi V4.9).
|
||||
Menangani hasil BACT/ALERT (Kultur Darah) dan VITEK (Identifikasi & AST).
|
||||
"""
|
||||
session = SessionLocal()
|
||||
try:
|
||||
@@ -571,9 +574,8 @@ def parse_myla_result(hl7_message, device_name="MYLA"):
|
||||
patient_name = ""
|
||||
result_date = datetime.datetime.now()
|
||||
|
||||
# Penampung Hasil
|
||||
kultur_id = [] # Untuk hasil organisme / Positif/Negatif
|
||||
ast_results = [] # Untuk hasil sensitivitas antibiotik
|
||||
kultur_id = []
|
||||
ast_results = []
|
||||
|
||||
for segment in segments:
|
||||
fields = segment.split('|')
|
||||
@@ -591,35 +593,38 @@ def parse_myla_result(hl7_message, device_name="MYLA"):
|
||||
if len(fields) > 5: patient_name = fields[5].replace('^', ' ').strip()
|
||||
|
||||
elif seg_type == 'OBR':
|
||||
# OBR-3 (Filler Order Number) atau OBR-2 (Placer) biasanya berisi Sample ID
|
||||
if len(fields) > 3 and fields[3]:
|
||||
sample_id = fields[3].replace('^', '')
|
||||
elif len(fields) > 2 and fields[2]:
|
||||
sample_id = fields[2].replace('^', '')
|
||||
|
||||
elif seg_type == 'OBX':
|
||||
# Parsing detail OBX
|
||||
# OBX-3: Parameter (Misal: Organism / Nama Antibiotik)
|
||||
# OBX-5: Nilai Hasil / MIC
|
||||
# OBX-8: Interpretasi (S / I / R / + / -)
|
||||
|
||||
if len(fields) > 5:
|
||||
test_param = fields[3].split('^')[1] if '^' in fields[3] else fields[3]
|
||||
result_val = fields[5].replace('^', ' ').strip()
|
||||
|
||||
if "Time To Detection" in test_param:
|
||||
continue
|
||||
|
||||
raw_val = fields[5]
|
||||
val_parts = raw_val.split('^')
|
||||
|
||||
if len(val_parts) > 1:
|
||||
if val_parts[0] in ['<', '<=', '>', '>=', '=']:
|
||||
result_val = f"{val_parts[0]} {val_parts[1]}"
|
||||
else:
|
||||
result_val = val_parts[1]
|
||||
else:
|
||||
result_val = raw_val.strip()
|
||||
|
||||
interpretation = ""
|
||||
if len(fields) > 8 and fields[8]:
|
||||
interpretation = fields[8].strip()
|
||||
interpretation = fields[8].split('^')[0].strip().upper()
|
||||
|
||||
# Logika Pemisahan (Tergantung mapping kata kunci MYLA)
|
||||
# Biasanya VITEK mengirim interpretasi 'S', 'I', 'R' untuk antibiotik
|
||||
if interpretation in ['S', 'I', 'R']:
|
||||
if interpretation in ['S', 'I', 'R', 'NS']:
|
||||
ast_results.append(f"{test_param}: {result_val} ({interpretation})")
|
||||
else:
|
||||
# Ini kemungkinan hasil Kultur / Identifikasi Organisme
|
||||
kultur_id.append(f"{test_param}: {result_val}")
|
||||
|
||||
# --- GABUNGKAN HASIL ---
|
||||
final_results = []
|
||||
if kultur_id:
|
||||
final_results.append("ID: " + ", ".join(kultur_id))
|
||||
@@ -628,9 +633,7 @@ def parse_myla_result(hl7_message, device_name="MYLA"):
|
||||
|
||||
final_result_str = " | ".join(final_results) if final_results else "No Data"
|
||||
|
||||
# --- SIMPAN KE DB ---
|
||||
if not sample_id:
|
||||
# Fallback jika tidak ada ID
|
||||
sample_id = f"ERR_MYLA_{datetime.datetime.now().strftime('%H%M%S')}"
|
||||
final_result_str = f"[NO_ID] {final_result_str}"
|
||||
|
||||
@@ -642,7 +645,7 @@ def parse_myla_result(hl7_message, device_name="MYLA"):
|
||||
rnmpas=patient_name,
|
||||
tgl_data=result_date,
|
||||
rawdt=hl7_message,
|
||||
organisme=final_result_str[:255], # Potong jika field database terbatas
|
||||
organisme=final_result_str[:255],
|
||||
alat=device_name
|
||||
)
|
||||
session.add(new_entry)
|
||||
@@ -700,9 +703,73 @@ def handle_myla_client(conn, addr):
|
||||
|
||||
# --- PROSES QUERY (QRY/QBP) - JIKA MYLA BERTANYA ORDER ---
|
||||
elif "QRY^" in hl7_str or "QBP^" in hl7_str:
|
||||
logging.info(f"[MYLA] Menerima Query (Belum diimplementasikan detail)")
|
||||
# Anda bisa menambahkan logika lookup order disini mirip dengan GeneXpert
|
||||
pass
|
||||
client_ip = addr[0]
|
||||
logging.info(f"[MYLA] Menerima Query dari {client_ip} (Control ID: {incoming_control_id})")
|
||||
|
||||
# Khusus MyLA gunakan flag VITEK3.
|
||||
target_flag_col = "flg_vitek3"
|
||||
|
||||
lines = hl7_str.split('\r')
|
||||
search_sample_id = None
|
||||
qrd_line = ""
|
||||
|
||||
for line in lines:
|
||||
if line.startswith("QRD|"):
|
||||
qrd_line = line
|
||||
fields = line.split('|')
|
||||
if len(fields) > 8 and fields[8]:
|
||||
search_sample_id = fields[8].strip()
|
||||
break
|
||||
|
||||
if not search_sample_id:
|
||||
for line in lines:
|
||||
if line.startswith("QPD|"):
|
||||
qpd_fields = line.split('|')
|
||||
for candidate in qpd_fields[3:]:
|
||||
value = candidate.strip()
|
||||
if value:
|
||||
search_sample_id = value.split('^')[0].strip()
|
||||
break
|
||||
break
|
||||
|
||||
session = SessionLocal()
|
||||
try:
|
||||
flag_attr = getattr(PaslabOrder, target_flag_col, None)
|
||||
if flag_attr is None:
|
||||
raise Exception(f"Kolom flag tidak valid: {target_flag_col}")
|
||||
|
||||
order = None
|
||||
if search_sample_id:
|
||||
order = session.query(PaslabOrder).filter(
|
||||
PaslabOrder.rnoreg == search_sample_id,
|
||||
(flag_attr == False) | (flag_attr == None)
|
||||
).first()
|
||||
else:
|
||||
# Fallback: jika query tidak membawa sample ID, kirim order pending paling awal.
|
||||
order = session.query(PaslabOrder).filter(
|
||||
(flag_attr == False) | (flag_attr == None)
|
||||
).order_by(PaslabOrder.urut.asc()).first()
|
||||
|
||||
if order:
|
||||
reply_msg = create_hl7_dsr_response(order, incoming_control_id, qrd_line)
|
||||
conn.sendall(f"\x0b{reply_msg}\x1c\r".encode('utf-8'))
|
||||
|
||||
setattr(order, target_flag_col, True)
|
||||
session.commit()
|
||||
logging.info(f"[MYLA] Order terkirim rnoreg={order.rnoreg}, set {target_flag_col}=TRUE")
|
||||
else:
|
||||
reply_msg = create_hl7_dsr_response(None, incoming_control_id, qrd_line)
|
||||
conn.sendall(f"\x0b{reply_msg}\x1c\r".encode('utf-8'))
|
||||
logging.info(f"[MYLA] Tidak ada order pending untuk sample={search_sample_id or '-'}")
|
||||
|
||||
except Exception as db_err:
|
||||
session.rollback()
|
||||
logging.error(f"[MYLA] Gagal proses query: {db_err}")
|
||||
reply_msg = create_hl7_dsr_response(None, incoming_control_id, qrd_line)
|
||||
conn.sendall(f"\x0b{reply_msg}\x1c\r".encode('utf-8'))
|
||||
finally:
|
||||
session.close()
|
||||
continue
|
||||
|
||||
# --- KIRIM ACK ---
|
||||
if incoming_control_id:
|
||||
@@ -721,20 +788,29 @@ def handle_myla_client(conn, addr):
|
||||
conn.close()
|
||||
logging.info(f"[MYLA-TCP] Koneksi {addr} ditutup.")
|
||||
|
||||
# Tambahkan fungsi starter ini di atas blok if __name__ == '__main__':
|
||||
def start_myla_server(host, port):
|
||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server.bind((host, port))
|
||||
server.listen(5)
|
||||
logging.info(f"[START] MYLA TCP Server berjalan di {host}:{port}")
|
||||
|
||||
while True:
|
||||
client_socket, addr = server.accept()
|
||||
# Jalankan di thread terpisah agar bisa tangani banyak koneksi
|
||||
client_thread = threading.Thread(target=handle_myla_client, args=(client_socket, addr))
|
||||
client_thread.daemon = True
|
||||
client_thread.start()
|
||||
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
|
||||
try:
|
||||
server.bind((host, port))
|
||||
server.listen(5)
|
||||
logging.info(f"[START] MYLA TCP Server berjalan di {host}:{port}")
|
||||
print(f"[START] MYLA TCP Server berjalan di {host}:{port}")
|
||||
|
||||
while True:
|
||||
client_socket, addr = server.accept()
|
||||
# Jalankan di thread terpisah agar bisa tangani banyak koneksi
|
||||
client_thread = threading.Thread(target=handle_myla_client, args=(client_socket, addr), daemon=True)
|
||||
client_thread.start()
|
||||
except Exception as e:
|
||||
logging.critical(f"[START] Gagal start MYLA TCP Server di {host}:{port}: {e}")
|
||||
print(f"[START] Gagal start MYLA TCP Server di {host}:{port}: {e}")
|
||||
finally:
|
||||
try:
|
||||
server.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ==========================================
|
||||
# HL7 TCP LISTENER FOR GENEXPERT
|
||||
@@ -1201,6 +1277,41 @@ def parse_and_save_vitek_result(raw_data, port_name="VITEK"):
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def split_patient_name(full_name):
|
||||
"""
|
||||
Pecah nama pasien menjadi first_name dan last_name.
|
||||
- Jika ada '^', diasumsikan format ASTM/HL7: Last^First.
|
||||
- Jika nama biasa, token terakhir = last_name, sisanya = first_name.
|
||||
"""
|
||||
if not full_name:
|
||||
return "NO", "NAME"
|
||||
|
||||
raw = str(full_name).strip()
|
||||
if not raw:
|
||||
return "NO", "NAME"
|
||||
|
||||
first_name = ""
|
||||
last_name = ""
|
||||
|
||||
if "^" in raw:
|
||||
parts = [p.strip() for p in raw.split("^") if p.strip()]
|
||||
if len(parts) >= 2:
|
||||
last_name = parts[0]
|
||||
first_name = parts[1]
|
||||
elif len(parts) == 1:
|
||||
first_name = parts[0]
|
||||
else:
|
||||
tokens = raw.split()
|
||||
if len(tokens) >= 2:
|
||||
last_name = tokens[-1]
|
||||
first_name = " ".join(tokens[:-1])
|
||||
elif len(tokens) == 1:
|
||||
first_name = tokens[0]
|
||||
|
||||
first_name = (first_name or "NO").upper()[:20]
|
||||
last_name = (last_name or "NAME").upper()[:20]
|
||||
return first_name, last_name
|
||||
|
||||
def create_vitek_order_message(order):
|
||||
"""
|
||||
Membuat Frame Order Vitek sesuai Manual Ref 514937.
|
||||
@@ -1210,8 +1321,11 @@ def create_vitek_order_message(order):
|
||||
# Field Delimiter menggunakan Pipe '|'
|
||||
pid = str(order.norm).strip() if order.norm else ""
|
||||
sid = str(order.rnoreg).strip() if order.rnoreg else ""
|
||||
p_name = str(order.nama).strip().replace('^', ' ').upper()[:20] if order.nama else "NO NAME"
|
||||
first_name, last_name = split_patient_name(order.nama)
|
||||
p_name = f"{last_name}^{first_name}"
|
||||
specimen = str(order.kd_spesimen).upper() if order.kd_spesimen else "BLOOD"
|
||||
room = str(getattr(order, 'ruangan', "") or "RSSA MALANG").strip().upper()
|
||||
room = room.replace("|", " ").replace("^", " ")[:30]
|
||||
|
||||
now = datetime.datetime.now()
|
||||
date_str = now.strftime("%m/%d/%Y")
|
||||
@@ -1222,6 +1336,7 @@ def create_vitek_order_message(order):
|
||||
content_body = (
|
||||
f"mtmpr|pi{pid}|pn{p_name}"
|
||||
f"|si|ss{specimen}"
|
||||
f"|lo{room}"
|
||||
f"|s1{date_str}|s2{time_str}"
|
||||
f"|ci{sid}|t11|zz"
|
||||
)
|
||||
@@ -1255,9 +1370,7 @@ def manage_vitek_port(config):
|
||||
flag_col = config.get('flag_column')
|
||||
alat_name = config.get('alat_name', 'VITEK')
|
||||
|
||||
logging.info(f"[{port_name}] START VITEK SERVICE (Relaxed Mode)...")
|
||||
print(f"[{port_name}] START VITEK SERVICE (Relaxed Mode)...")
|
||||
# Deteksi stuck dalam window waktu tertentu.
|
||||
STUCK_WINDOW_SEC = 120
|
||||
stuck_count = 0
|
||||
stuck_window_start = None
|
||||
@@ -1276,7 +1389,7 @@ def manage_vitek_port(config):
|
||||
) as ser:
|
||||
|
||||
ser.reset_input_buffer()
|
||||
logging.info(f"[{port_name}] Ready & Listening.")
|
||||
print(f"[{port_name}] Ready & Listening.")
|
||||
|
||||
while True:
|
||||
# ==========================================
|
||||
@@ -1287,14 +1400,12 @@ def manage_vitek_port(config):
|
||||
|
||||
# --- 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()
|
||||
|
||||
# --- 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 = 8
|
||||
@@ -1310,12 +1421,9 @@ def manage_vitek_port(config):
|
||||
is_valid = True
|
||||
elif b'\x1d' in full_frame[-20:]:
|
||||
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')
|
||||
stuck_count = 0
|
||||
@@ -1362,7 +1470,6 @@ def manage_vitek_port(config):
|
||||
).first()
|
||||
|
||||
if pending_order:
|
||||
logging.info(f"[{port_name}] Ada Order: {pending_order.rnoreg}. Coba Handshake...")
|
||||
print(f"[{port_name}] Ada Order: {pending_order.rnoreg}...")
|
||||
|
||||
# --- LOGIC HANDSHAKE DENGAN RETRY ---
|
||||
@@ -1389,7 +1496,7 @@ def manage_vitek_port(config):
|
||||
stuck_count = 0
|
||||
stuck_window_start = None
|
||||
# === KIRIM DATA ORDER ===
|
||||
logging.info(f"[{port_name}] Handshake OK. Kirim Frames...")
|
||||
print(f"[{port_name}] Handshake OK. Kirim Frames...")
|
||||
frames = create_vitek_order_message(pending_order)
|
||||
all_sent = True
|
||||
|
||||
@@ -1412,7 +1519,6 @@ def manage_vitek_port(config):
|
||||
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()
|
||||
@@ -1420,12 +1526,12 @@ def manage_vitek_port(config):
|
||||
stuck_window_start = None
|
||||
else:
|
||||
logging.error(f"[{port_name}] Order Gagal (No ACK).")
|
||||
print(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(2.0)
|
||||
@@ -1584,8 +1690,9 @@ def create_astm_order_message(order):
|
||||
# --- 1. PERSIAPAN DATA ---
|
||||
pid = str(order.norm).strip() if order.norm else ""
|
||||
sid = str(order.rnoreg).strip() if order.rnoreg else ""
|
||||
# Nama Pasien: Ganti karakter topi '^' dengan spasi agar tidak merusak format
|
||||
p_name = str(order.nama).strip().replace('^', ' ')[:20] if order.nama else "No Name"
|
||||
# Nama pasien dipisah agar mengikuti format ASTM: Last^First
|
||||
first_name, last_name = split_patient_name(order.nama)
|
||||
p_name = f"{last_name}^{first_name}"
|
||||
sex = "M" if str(order.rjenis).upper().startswith("L") else "F"
|
||||
|
||||
# Lokasi / Ruangan (Field 26)
|
||||
@@ -1669,7 +1776,7 @@ def manage_bd_port(config):
|
||||
port_name = config['port']
|
||||
flag_col = config.get('flag_column')
|
||||
alat_name = config.get('alat_name', 'BD')
|
||||
logging.info(f"[{port_name}] Membuka port untuk alat {alat_name}...")
|
||||
print(f"[{port_name}] Membuka port untuk alat {alat_name}...")
|
||||
|
||||
# Buffer untuk menampung pecahan data
|
||||
rx_buffer = b""
|
||||
@@ -1695,7 +1802,6 @@ def manage_bd_port(config):
|
||||
if data_chunk:
|
||||
# 1. Handle Handshake Awal (ENQ)
|
||||
if b'\x05' in data_chunk:
|
||||
logging.info(f"[{port_name}] Terima ENQ (Alat mau kirim data). Kirim ACK.")
|
||||
print(f"[{port_name}] Terima ENQ (Alat mau kirim data). Kirim ACK.")
|
||||
ser.write(b'\x06') # ACK
|
||||
rx_buffer = "" # Reset buffer untuk data baru
|
||||
@@ -1703,7 +1809,6 @@ def manage_bd_port(config):
|
||||
|
||||
# 2. Handle Akhir Transmisi (EOT)
|
||||
if b'\x04' in data_chunk:
|
||||
logging.info(f"[{port_name}] Terima EOT (Selesai). Memproses data...")
|
||||
print(f"[{port_name}] Terima EOT (Selesai). Memproses data...")
|
||||
# Proses semua data yang terkumpul di buffer
|
||||
parse_and_save_bd_result(rx_buffer, alat_name)
|
||||
@@ -1748,7 +1853,6 @@ def manage_bd_port(config):
|
||||
|
||||
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...")
|
||||
# --- STEP 1: HANDSHAKE (ENQ) ---
|
||||
ser.reset_input_buffer()
|
||||
@@ -1758,7 +1862,6 @@ def manage_bd_port(config):
|
||||
ack_response = ser.read(1)
|
||||
|
||||
if ack_response == b'\x06':
|
||||
logging.info(f"[{port_name}] Handshake Sukses (Dapat ACK). Menunggu alat siap...")
|
||||
print(f"[{port_name}] Handshake Sukses (Dapat ACK). Menunggu alat siap...")
|
||||
# --- PERBAIKAN 1: BERI JEDA SETELAH HANDSHAKE ---
|
||||
# Mesin butuh napas sebelum terima data panjang
|
||||
@@ -1778,7 +1881,6 @@ def manage_bd_port(config):
|
||||
while retry_count < max_retries:
|
||||
ser.reset_input_buffer()
|
||||
|
||||
logging.info(f"[{port_name}] Kirim Frame {i+1} (Percobaan {retry_count+1})...")
|
||||
print(f"[{port_name}] Kirim Frame {i+1} (Percobaan {retry_count+1})...")
|
||||
ser.write(frame)
|
||||
|
||||
@@ -1790,21 +1892,18 @@ def manage_bd_port(config):
|
||||
|
||||
if frame_ack == b'\x06': # ACK (Sukses)
|
||||
frame_success = True
|
||||
logging.info(f"[{port_name}] Frame {i+1} ACK diterima.")
|
||||
print(f"[{port_name}] Frame {i+1} ACK diterima.")
|
||||
# Beri jeda dikit sebelum kirim frame berikutnya
|
||||
time.sleep(0.2)
|
||||
break
|
||||
|
||||
elif frame_ack == b'\x15': # NAK (Ditolak - Checksum Salah)
|
||||
logging.warning(f"[{port_name}] Frame ditolak (NAK). Checksum mungkin salah.")
|
||||
print(f"[{port_name}] Frame ditolak (NAK). Checksum mungkin salah.")
|
||||
time.sleep(2) # Tunggu 2 detik
|
||||
retry_count += 1
|
||||
|
||||
elif not frame_ack: # Timeout (Sepi)
|
||||
# --- PERBAIKAN 2: BERI JEDA SAAT TIMEOUT ---
|
||||
logging.warning(f"[{port_name}] Timeout (Alat diam). Menunggu sebelum retry...")
|
||||
print(f"[{port_name}] Timeout (Alat diam). Menunggu sebelum retry...")
|
||||
time.sleep(2) # Tunggu 2 detik agar alat recover
|
||||
retry_count += 1
|
||||
@@ -1825,7 +1924,6 @@ def manage_bd_port(config):
|
||||
# ---------------------------------------------------
|
||||
if all_frames_sent:
|
||||
ser.write(b'\x04') # EOT (End of Transmission)
|
||||
logging.info(f"[{port_name}] Order {pending_order.rnoreg} SUKSES Terkirim.")
|
||||
print(f"[{port_name}] Order {pending_order.rnoreg} SUKSES Terkirim.")
|
||||
# Update Database
|
||||
setattr(pending_order, flag_col, True)
|
||||
@@ -1908,7 +2006,12 @@ if __name__ == "__main__":
|
||||
all_threads.append(t_tcp)
|
||||
|
||||
# 3. Start Thread TCP Server (MyLA)
|
||||
myla_thread = threading.Thread(target=start_myla_server, args=(MYLA_HOST, MYLA_PORT))
|
||||
myla_thread = threading.Thread(
|
||||
target=start_myla_server,
|
||||
args=(MYLA_HOST, MYLA_PORT),
|
||||
name="Manager-TCP-MyLA",
|
||||
daemon=True
|
||||
)
|
||||
myla_thread.start()
|
||||
all_threads.append(myla_thread)
|
||||
|
||||
@@ -1918,31 +2021,22 @@ if __name__ == "__main__":
|
||||
all_threads.append(t_http)
|
||||
|
||||
# 5. LOOP UTAMA (Keep-Alive & Monitoring)
|
||||
# Ini sekarang bisa berjalan karena Flask sudah dipindah ke thread
|
||||
try:
|
||||
while True:
|
||||
logging.debug(f"--- Monitoring {len(all_threads)} Threads ---")
|
||||
print(f"--- Monitoring {len(all_threads)} Threads ---")
|
||||
# Cek status setiap thread
|
||||
alive_count = 0
|
||||
for t in all_threads:
|
||||
if t.is_alive():
|
||||
alive_count += 1
|
||||
else:
|
||||
logging.warning(f"!!! THREAD MATI: {t.name} !!!")
|
||||
print(f"!!! THREAD MATI: {t.name} !!!")
|
||||
# Disini Anda bisa menambahkan logika restart thread jika mati
|
||||
|
||||
# Jika semua mati, exit (atau restart service)
|
||||
if alive_count == 0:
|
||||
logging.critical("Semua thread mati. System Shutdown.")
|
||||
print("Semua thread mati. System Shutdown.")
|
||||
break
|
||||
|
||||
time.sleep(10) # Cek setiap 10 detik (Hemat CPU)
|
||||
time.sleep(10)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logging.info("Mematikan Service (Ctrl+C)...")
|
||||
print("Mematikan Service (Ctrl+C)...")
|
||||
stop_event.set()
|
||||
# Thread daemon akan mati otomatis saat main exit
|
||||
|
||||
Reference in New Issue
Block a user