# trigger_loop.py import asyncio import time import config from Logger import Logs sanad_logger = Logs() sanad_logger.LogEngine("G1_Logs", "trigger_loop") def build_photographer_prompt(photo_total_sec: float, photo_thanks_sec: float) -> str: talk_sec = max(1.0, photo_total_sec - photo_thanks_sec) thanks_sec = max(0.5, min(photo_thanks_sec, photo_total_sec - 0.5)) return ( "In english only speak You are Bousanad the photographer. Speak in Emirati (UAE) dialect using short, energetic sentences. " "Start with: 'Yalla.. get ready.. smile!' " f"For about {talk_sec:.0f} seconds, give quick photo directions like: " "'Come a bit closer' 'Hold still' 'Look at the camera' 'Nice, perfect' 'One.. two.. three' " f"And in the last about {thanks_sec:.0f} seconds say: 'thank youooo' in a long, friendly way." ) async def trigger_loop( hub, replay, voice, ws, take_photo_sync_callable, ): """ R2+X: - start replay (thread) - send Gemini prompt (immediate) - take photo AFTER PHOTO_DELAY_SEC (default=5.0) using teleimager python R2+L1: - cancels pending delayed photo (replay cancel handled in replay engine) Debounce: - must release R2+X to re-trigger - blocks retrigger for PHOTO_TOTAL_SEC window """ r2x_prev = False waiting_release = False photo_busy_until = 0.0 pending_photo_task: asyncio.Task | None = None async def delayed_photo(delay_sec: float): await asyncio.sleep(delay_sec) photo_result = await asyncio.to_thread(take_photo_sync_callable) sanad_logger.print_and_log(f"📸 Saved photo: {photo_result}", message_type="info") while True: await asyncio.sleep(0.02) if not hub.first_state: continue # Cancel combo: cancel pending photo if hub.combo_r2l1(): if pending_photo_task and not pending_photo_task.done(): pending_photo_task.cancel() sanad_logger.print_and_log("🛑 Pending photo cancelled (R2+L1).", message_type="warning") r2x_now = hub.combo_r2x() # Require release before next trigger if waiting_release: if not r2x_now: waiting_release = False r2x_prev = r2x_now continue # Do not retrigger while replay is playing if replay.is_playing: r2x_prev = r2x_now continue now = time.time() # Rising edge trigger + not inside busy window if r2x_now and (not r2x_prev) and (now >= photo_busy_until): waiting_release = True photo_busy_until = now + config.PHOTO_TOTAL_SEC # 1) start replay (blocking -> thread) asyncio.create_task(asyncio.to_thread(replay.run, config.REPLAY_FILE, config.HOME_FILE, 1.0)) # 2) send photographer prompt prompt = build_photographer_prompt(config.PHOTO_TOTAL_SEC, config.PHOTO_THANKS_SEC) await voice.send_text_prompt(ws, prompt) # 3) schedule delayed photo if pending_photo_task and not pending_photo_task.done(): pending_photo_task.cancel() # ✅ You want photo exactly at 5.0 seconds when total is 5s: # - if PHOTO_TOTAL_SEC is 5, delay becomes 5 (clamped) delay = max(0.0, min(config.PHOTO_DELAY_SEC, config.PHOTO_TOTAL_SEC)) pending_photo_task = asyncio.create_task(delayed_photo(delay)) r2x_prev = r2x_now