import re with open('esp32_serial_backend/esp32_serial_backend.ino', 'r', encoding='utf8') as f: content = f.read() # 1. Globals content = content.replace( '''bool gPendingRecog = false; unsigned long gRecogCaptureAt = 0; // millis() when to actually capture''', '''// Auto-retry RECOG / ENROLL state String gKeepTryingCmd = ""; unsigned long gKeepTryingUntil = 0; bool gLastActionSuccess = false;''') # 2. RFID Trigger old_rfid = ''' // Set pending RECOG - kamera preview akan tampil dulu 3 detik di loop() gPendingRecog = true; gRecogCaptureAt = millis() + 3000; // capture setelah 3 detik gCameraActive = true; // tampilkan kamera preview mulai sekarang''' new_rfid = ''' // Mulai proses pengenalan wajah terus-menerus selama 15 detik gKeepTryingCmd = "RECOG"; gKeepTryingUntil = millis() + 15000; gLastActionSuccess = false; gCameraActive = true; // tampilkan kamera preview mulai sekarang''' content = content.replace(old_rfid, new_rfid) # 3. Web Job Trigger (ENROLL) old_enroll_job = ''' if (type == "ENROLL") { showStatus("TAKE ENROLL", TFT_CYAN); Serial.println(String("[JOB] ENROLL start name=") + name); bool enrollOk = handleCommand(String("ENROLL ") + name); String result = "{\\"from\\":\\"esp32\\",\\"type\\":\\"ENROLL\\",\\"name\\":\\"" + name + "\\",\\"ok\\":" + (enrollOk ? "true" : "false"); if (uid.length()) result += ",\\"uid\\":\\"" + uid + "\\""; if (personId.length()) result += ",\\"person_id\\":\\"" + personId + "\\""; result += "}"; String jr = postJson(String(SERVER_BASE) + "/job/result", result, 6000); Serial.println(String("[JOB] ENROLL result post=") + jr); if (jr.startsWith("HTTP") || jr.startsWith("WIFI")) showStatus("JOB RESULT FAIL\\n" + jr, TFT_YELLOW); return; }''' new_enroll_job = ''' if (type == "ENROLL") { showStatus("START ENROLL", TFT_CYAN); Serial.println(String("[JOB] ENROLL start name=") + name); // Aktifkan continuous capture gKeepTryingCmd = String("ENROLL ") + name; gKeepTryingUntil = millis() + 15000; gLastActionSuccess = false; gCameraActive = true; // We notify the server immediately that we took the job. String result = "{\\"from\\":\\"esp32\\",\\"type\\":\\"ENROLL\\",\\"name\\":\\"" + name + "\\",\\"ok\\":true"; if (uid.length()) result += ",\\"uid\\":\\"" + uid + "\\""; if (personId.length()) result += ",\\"person_id\\":\\"" + personId + "\\""; result += "}"; postJson(String(SERVER_BASE) + "/job/result", result, 6000); return; }''' content = content.replace(old_enroll_job, new_enroll_job) # 4. Web Job Trigger (RECOG) old_recog_job = ''' if (type == "RECOG") { // Save job params so /recognize uses same threshold+uid as the web request. gJobRecogThreshold = thr; gJobRecogUid = uid; gJobPersonId = personId; showStatus("TAKE RECOG", TFT_CYAN); Serial.println(String("[JOB] RECOG start thr=") + String(thr, 2) + String(" uid=") + uid); Serial.println(String("[JOB] WiFi=") + (WiFi.status() == WL_CONNECTED ? "OK" : "DISCONNECTED")); showStatus("RECOG...", TFT_CYAN); bool recogOk = handleCommand("RECOG"); String result = String("{\\"from\\":\\"esp32\\",\\"type\\":\\"RECOG\\",\\"threshold\\":") + String(thr, 2) + String(",\\"uid\\":\\"") + uid + String("\\",\\"ok\\":") + (recogOk ? "true" : "false") + String("}"); String jr = postJson(String(SERVER_BASE) + "/job/result", result, 6000); Serial.println(String("[JOB] RECOG result post=") + jr); if (jr.startsWith("HTTP") || jr.startsWith("WIFI")) showStatus("JOB RES:\\n" + jr, TFT_YELLOW); return; }''' new_recog_job = ''' if (type == "RECOG") { gJobRecogThreshold = thr; gJobRecogUid = uid; gJobPersonId = personId; showStatus("START RECOG", TFT_CYAN); Serial.println(String("[JOB] RECOG start thr=") + String(thr, 2) + String(" uid=") + uid); // Aktifkan continuous capture gKeepTryingCmd = "RECOG"; gKeepTryingUntil = millis() + 15000; gLastActionSuccess = false; gCameraActive = true; String result = String("{\\"from\\":\\"esp32\\",\\"type\\":\\"RECOG\\",\\"threshold\\":") + String(thr, 2) + String(",\\"uid\\":\\"") + uid + String("\\",\\"ok\\":true}"); postJson(String(SERVER_BASE) + "/job/result", result, 6000); return; }''' content = content.replace(old_recog_job, new_recog_job) # 5. Success detection in handleCommand(ENROLL) old_enroll_resp = ''' String resp = postFrameWithRetry(url, fb, 60000); showStatus(resp, TFT_CYAN);''' new_enroll_resp = ''' String resp = postFrameWithRetry(url, fb, 60000); if (!resp.startsWith("HTTP ERR") && !resp.startsWith("HTTP 4") && !resp.startsWith("HTTP 5") && !resp.startsWith("WIFI")) { gLastActionSuccess = true; } showStatus(resp, TFT_CYAN);''' content = content.replace(old_enroll_resp, new_enroll_resp) # 6. Success detection in handleCommand(RECOG) old_recog_resp = ''' if (respLower.indexOf(expLower) >= 0) { showStatus("OK: " + gExpectedName, TFT_GREEN); } else { showStatus("FAIL: " + gExpectedName, TFT_RED); }''' new_recog_resp = ''' if (respLower.indexOf(expLower) >= 0 || respLower.indexOf("\\"match\\":true") >= 0 || respLower.indexOf("\\"match\\": true") >= 0) { showStatus("OK: " + gExpectedName, TFT_GREEN); gLastActionSuccess = true; } else { showStatus("FAIL: " + gExpectedName, TFT_RED); }''' content = content.replace(old_recog_resp, new_recog_resp) # 7. Add fallback success for RECOG when no ExpectedName is set (e.g. from Web Job) old_recog_resp_fallback = ''' else { showStatus(resp, TFT_CYAN); }''' new_recog_resp_fallback = ''' else { String respLower = resp; respLower.toLowerCase(); if (respLower.indexOf("\\"match\\":true") >= 0 || respLower.indexOf("\\"match\\": true") >= 0) { gLastActionSuccess = true; showStatus("Dikenali", TFT_GREEN); } else { showStatus(resp, TFT_CYAN); } }''' content = content.replace(old_recog_resp_fallback, new_recog_resp_fallback) # 8. Loop logic old_loop_broken = '''// ===== PENDING RECOG (dipicu oleh RFID) ===== // Kamera sudah aktif (gCameraActive=true), tampilkan preview + overlay countdown // Saat timer habis, jalankan capture if (gPendingRecog && !gBusy) { if (now >= gRecogCaptureAt) { // Waktu habis - jalankan RECOG sekarang gPendingRecog = false; gCameraActive = false; showStatus("MEMPROSES...", TFT_CYAN, "", 8000); Serial.println("[RFID] Countdown habis - jalankan RECOG"); bool attendOk = handleCommand("RECOG"); gCameraActive = false; Serial.println(String("[RFID] auto attendance result=") + (attendOk ? "OK" : "FAIL")); } else { // Tampilkan countdown di overlay (tapi biarkan kamera preview tetap jalan) int secsLeft = (int)((gRecogCaptureAt - now) / 1000) + 1; Serial.println("[RECOG] Executing delayed capture..."); handleCommand("RECOG"); lastJobPoll = millis(); // Reset penanda waktu poll setelah proses RECOG gBusy = false; } else { // Tampilkan hitung mundur int secsLeft = (gRecogCaptureAt - now) / 1000 + 1; showOverlayMessage(String("Siap-siap absen..."), String("Capture dalam ") + secsLeft + "s...", 4000); } }''' new_loop_continuous = '''// ===== CONTINUOUS CAPTURE (Auto-Retry) ===== if (gKeepTryingCmd.length() > 0 && !gBusy) { if (now >= gKeepTryingUntil) { // Timeout gKeepTryingCmd = ""; gCameraActive = false; showStatus("TIMEOUT", TFT_RED, "Wajah tidak dikenali", 3000); } else { Serial.println(String("[AUTO] Executing ") + gKeepTryingCmd); showOverlayMessage("MEMPROSES...", "Jangan bergerak", 2000); handleCommand(gKeepTryingCmd); lastJobPoll = millis(); // Reset poll timer if (gLastActionSuccess) { gKeepTryingCmd = ""; // Stop trying gCameraActive = false; // Turn off camera on success showStatus("BERHASIL", TFT_GREEN, "", 3000); } } }''' content = content.replace(old_loop_broken, new_loop_continuous) with open('esp32_serial_backend/esp32_serial_backend.ino', 'w', encoding='utf8') as f: f.write(content) print("Update complete")