
    i#                        d Z ddlmZ ddlZddlZddlZddlZddlZddlm	Z	m
Z
mZmZmZ ddlZddlmZ  ej"                  e      ZdZ G d d	      Zy)
zContinuous face recognition loop.

Captures JPEG frames from the robot camera via ``FrameCapture`` and sends them
to the FR server over a persistent WebSocket connection.  Detection results are
forwarded to registered callbacks.
    )annotationsN)AnyCallableDictListOptional   )FrameCaptureg       @c                  r    e Zd ZdZ	 d	 	 	 	 	 	 	 ddZddZddZddZddZddZ	ddZ
dd	Zdd
ZddZy)FRLoopa1  Continuously capture frames and run face recognition.

    Args:
        frame_capture: A ``FrameCapture`` instance for grabbing JPEG frames.
        fr_endpoint: WebSocket URL of the FR server
            (e.g. ``ws://kluster.klass.dev:42067/``).
        fps: Target frames per second to send to FR.
    c                    || _         || _        dt        |d      z  | _        g | _        d| _        d | _        i | _        i | _        d| _	        t        t        t        j                  dd            d      | _        y )Ng      ?g?Fr   FR_METRICS_LOG_EVERY10r	   )_frame_capture_fr_endpointmax	_interval
_callbacks_running_task_last_cycle_metrics_last_fr_payload_metric_countintosgetenv_metrics_log_every)selfframe_capturefr_endpointfpss        edge-proxy/edge_proxy/fr_loop.py__init__zFRLoop.__init__%   st     ,'s3},GI-1
35 02"%c"))4JD*Q&RTU"V    c                :    | j                   j                  |       y)z6Register a callback invoked with a list of detections.N)r   append)r   callbacks     r"   on_detectionszFRLoop.on_detections:   s    x(r$   c                ,    t        | j                        S )z3Return a copy of the latest FR loop timing metrics.)dictr   r   s    r"   get_last_cycle_metricszFRLoop.get_last_cycle_metrics>   s    D,,--r$   c                   K   | j                   ryd| _         t        j                  | j                               | _        t
        j                  d| j                  | j                         yw)z'Start the FR loop as a background task.NTz-FRLoop: started (endpoint=%s, interval=%.2fs))	r   asynciocreate_task_runr   loggerinfor   r   r+   s    r"   startzFRLoop.startB   sM     ==((5
CTEVEVX\XfXfgs   A)A+c                    d| _         | j                  r4| j                  j                         s| j                  j                          t        j                  d       y)zStop the FR loop.FzFRLoop: stoppedN)r   r   donecancelr1   r2   r+   s    r"   stopzFRLoop.stopJ   s:    ::djjoo/JJ%&r$   c                J  K   | j                   r	 t        j                  | j                        4 d{   }t        j                  d| j                         | j                  |       d{    ddd      d{    | j                   ryy7 \7 %7 # 1 d{  7  sw Y   'xY w# t        j                  $ r Y yt        $ rX}| j                   sY d}~yt        j                  d|t               t        j                  t               d{  7   Y d}~d}~ww xY ww)z=Outer loop: maintain a persistent WS connection to FR server.Nz$FRLoop: connected to FR server at %su9   FRLoop: FR connection error: %s — reconnecting in %.0fs)r   
websocketsconnectr   r1   r2   _capture_loopr.   CancelledError	Exceptionwarning_RECONNECT_DELAY_SECsleep)r   wsexcs      r"   r0   zFRLoop._runU   s     mm
:%--d.?.?@ 1 1BKK FHYHYZ,,R0001 1 mm101 1 1 1 ))  :}}Z\_auvmm$8999	:s   D##B+ BB+ 5B,B-B1B+ <B=B+ D#D#B+ BB+ B(BB($B+ +D >D# D DD#7DDDD#D  D#c                  K   t        j                         }| j                  r t        j                         }dt        j                         i}d}t        j                         }	 |j                  d| j                  j                         d{   }t        j                         |z
  dz  |d<   	 | j                  j                         }	t        |	t              r:|	|d<   |	j                  d      }
t        |
t        t         f      rt!        |
      |d<   |t        j                         }| j#                  ||       d{   }t        j                         |z
  dz  |d	<   | j$                  j                  d
      }t        |t        t         f      rC|dkD  r>t!        |      |d<   t'        dt        j                         t!        |      z
  dz        |d<   | j$                  j                  d      }t        |t              r:||d<   |j                  d      }t        |t        t         f      rt!        |      |d<   t        |      | _        |Ut+        |      }t        j                         }| j-                  |       d{    t        j                         |z
  dz  |d<   t        j                         |z
  }|dz  |d<   t        |      | _        | j/                  ||       | j0                  |z
  }|dkD  rt        j2                  |       d{    | j                  ryy7 # t        $ r#}t        j                  d|       d}Y d}~d}~ww xY w# t        $ r Y Nw xY w7 &7 7 [w)z<Inner loop: capture frame -> send to FR -> notify callbacks.loop_started_atr   Nu,   FRLoop: frame capture error: %s — skippingg     @@
capture_mscapture_statsframe_age_msfr_roundtrip_ms	timestampfr_server_receive_timestampg        edge_since_fr_receive_msmetricsfr_server_metricstotal_msfr_server_total_ms	notify_msloop_ms)r.   get_event_loopr   time	monotonicrun_in_executorr   capturer=   r1   debug	get_stats
isinstancer*   getr   float_send_to_frr   r   r   len_notify_maybe_log_metricsr   r@   )r   rA   loopt0rL   detections_count
capture_t0framerB   rF   rG   fr_t0
detections
fr_recv_tsrM   rN   	notify_t0elapsed
sleep_times                      r"   r;   zFRLoop._capture_loopd   s    %%'mm!B'8$))+&FG  )J/3/C/C$--550 * &*^^%5
%Bf$LGL!	 $ 3 3 = = ?mT2/<GO,#0#4#4^#DL!,e=272E/
  (#'#3#3B#>>
.2nn.>.F&-P)*!2266{C
j3,7JN=B:=NG9::=diikE*,==G;G67 %)$9$9$=$=i$H!/63DG/0044Z@H!(S%L98=h 45+/=()'*:$ $ 0I,,z222,0NN,<y,HF+RGK( nn&+G!(6!1GI'+G}D$##G-=>'1JA~mmJ///{ mm*  KSQ   ?* 3 0s   A"M.%)L( L%L( M.1A$M ,M.M'D?M.M*BM.M,M.#M.%L( (	M1M	M.MM.	M$ M.#M$$M.*M.,M.c                L  K   	 |j                  |       d{    t        j                  |j                         d       d{   }t	        |t
        t        f      r|j                  dd      }t        j                  |      }t	        |t              st        d      || _        |j                  dg       }t	        |t              r|S g S 7 7 # t        j                  $ r t         j#                  d	        t$        $ r}t         j#                  d
|        d}~ww xY ww)zSend binary JPEG to FR server WS, receive JSON detections.

        Returns the detections list, or ``None`` if the exchange failed
        (which will cause the outer loop to reconnect).
        N      @)timeoutzutf-8replace)errorsz!FR response must be a JSON objectrf   z$FRLoop: FR server response timed outzFRLoop: FR exchange error: %s)sendr.   wait_forrecvrY   bytes	bytearraydecodejsonloadsr*   
ValueErrorr   rZ   listTimeoutErrorr1   r>   r=   )r   rA   
jpeg_bytesresponsedatarf   rB   s          r"   r\   zFRLoop._send_to_fr   s     	''*%%%$--bggiEEH(UI$67#??79?E::h'DdD) !DEE$(D!,3J!+J!=:E2E &E ## 	NNAB 	NN:C@	sW   D$C C,C CBC D$C D$C C 1D!DD!!D$c                   K   | j                   D ]*  }	  ||      }t        j                  |      r
| d{    , y7 # t        $ r }t        j                  d|       Y d}~Td}~ww xY ww)z9Invoke all registered callbacks with the detections list.NzFRLoop: callback error: %s)r   r.   iscoroutiner=   r1   r>   )r   rf   cbresultrB   s        r"   r^   zFRLoop._notify   sh     // 	BBBJ&&v. LL		B ! B;SAABs7   A+"?=?A+?	A(A#A+#A((A+c                   | xj                   dz  c_         | j                   | j                  z  dk7  ry dd}t        j                  d| j                    ||j	                  d             ||j	                  d             ||j	                  d             ||j	                  d             ||j	                  d	             ||j	                  d
            |	       y )Nr	   r   c                N    t        | t        t        f      rt        |       ddS y)Nz.1fmszn/a)rY   r   r[   )values    r"   _fmtz'FRLoop._maybe_log_metrics.<locals>._fmt   s'    %#u.,s+2..r$   zbFRLoop metrics #%d: capture=%s frame_age=%s fr_rtt=%s fr_server=%s notify=%s loop=%s detections=%drE   rG   rH   rO   rP   rQ   )r   r   returnstr)r   r   r1   r2   rZ   )r   rL   rb   r   s       r"   r_   zFRLoop._maybe_log_metrics   s    a 7 771<	
 	p\*+^,-./0123[)*Y'(
	
r$   N)rl   )r   r
   r    r   r!   r[   r   None)r'   z%Callable[[List[Dict[str, Any]]], Any]r   r   )r   Dict[str, Any])r   r   )rA   r   r   r   )rA   r   r{   rs   r   zOptional[List[Dict[str, Any]]])rf   zList[Dict[str, Any]]r   r   )rL   r   rb   r   r   r   )__name__
__module____qualname____doc__r#   r(   r,   r3   r7   r0   r;   r\   r^   r_    r$   r"   r   r      sm     	W#W W 	W
 
W*).h':@0D0B
r$   r   )r   
__future__r   r.   rv   loggingr   rS   typingr   r   r   r   r   r9   r   r
   	getLoggerr   r1   r?   r   r   r$   r"   <module>r      sP    #    	  6 6  '			8	$  A
 A
r$   