
    J~j8                       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mZ ddl	m
Z
mZmZ ej                            d e ee                                          j        j                             ddlmZmZmZmZmZ dZdZdd	d
dZdZdZef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'e(d&k    r ej)         e'                       dS dS )/a  Error escalation watchdog with a capped, graceful auto-reboot.

Polls the mission-critical services' state files (camera + sensors --
deliberately NOT lumen/epaper, matching led_status.py's reasoning: a
display or light glitch isn't mission failure and doesn't warrant a
disruptive reboot). A per-service systemd restart (Restart=on-failure) is
given first crack at self-healing; only once a service has remained
continuously in error for `watchdog.escalation_after_seconds` does this
watchdog escalate to a full Pi reboot.

Reboots are capped at `watchdog.max_reboot_attempts` (persisted on /data,
survives the reboot it's counting) so a permanently broken device can't
send the Pi into a reboot-crash-loop for the rest of the mission, burning
battery and SD wear for no benefit. Once the cap is hit, this watchdog
gives up rebooting and reports its own state as "error" -- which is what
makes led_status.py leave the LED in its blinking-BLUE fault indication
for the remainder of the mission, since watchdog is itself one of
led_status.py's monitored mission-critical services.

If this process itself dies, its state file goes stale, which
state_lib.has_error() also treats as an error -- so a watchdog crash
surfaces the same way a watchdog-detected fault would, rather than
silently going unsupervised.
    )annotationsN)Path)DictListOptional)
bounded_ioconfigeventsreboot_guard	state_lib<   zbubblecam-led.servicezbubblecam-epaper.servicezbubblecam-lumen.service)ledepaperlumenx   i,  stale_afterfloatc                    g }t                                           D ]G\  }}t          j        |          }|,t          j        ||           s|                    ||f           H|S )z(name, unit) pairs for non-critical services whose heartbeat exists
    but has gone stale. Services with no state file at all are skipped --
    never-started is a choice (e.g. lumen deliberately disabled), not a
    fault this remediation should fight.)NON_CRITICAL_UNITSitemsr   
read_stateis_freshappend)r   stalenameunitstates        IC:\Users\jvoigtlander\Desktop\BubbleCamCode\opt\bubblecam\bin\watchdog.pystale_noncritical_unitsr   A   sl    
 E(..00 ' '
d$T**Y%7{%K%KLL$&&&L    returnboolc                 H    t           j                            d          dk    S )NBUBBLECAM_SIM1)osenvironget r    r   	_simulater*   N   s     :>>/**c11r    cfgdict	List[str]c                V    | d         d         }|                     d          rddgS dgS )Ncameramode	dual_nodecamera_leftcamera_rightcamera_primary
startswith)r+   r0   s     r   camera_service_namesr7   U   s8    x= D{## /~..r    c                n    | d         d         }|                     d          rddgndg}d |D             S )Nr/   r0   r1   leftrightprimaryc                    g | ]}d | d	S )zbubblecam-camera@z.servicer)   ).0instances     r   
<listcomp>z(camera_systemd_units.<locals>.<listcomp>_   s$    MMMx2222MMMr    r5   )r+   r0   	instancess      r   camera_systemd_unitsrA   \   sF    x= D%)__[%A%AR!!	{IMM9MMMMr    cmdlisttimeoutNonec                :     t          j         fd|           dS )a,  Run a command, abandoning it (not just killing it) on timeout.

    On a machine whose storage has died, merely exec'ing systemctl can
    block forever in uninterruptible sleep -- the binary has to be paged
    in from the dead device. Python's subprocess timeout can't recover
    from that (it SIGKILLs then wait()s, and a D-state child never
    reaps), so the whole call runs on a throwaway thread instead.
    Observed for real: the watchdog froze inside a systemctl call
    mid-escalation and the Pi hung 12.5 hours with the fix one line away.
    c                 0    t          j         d          S )NFcheck)
subprocessrun)rB   s   r   <lambda>z"_run_abandonable.<locals>.<lambda>m   s    
s%(H(H(H r    rD   N)r   run_with_timeout)rB   rD   s   ` r   _run_abandonablerO   b   s,      H H H HRYZZZZZZr    c                    dgt          |           z   D ]7}t                      rt          d|            #t          dd|gd           8dS )a  Ask ffmpeg (via each capture_supervisor) to stop gracefully so the
    current video segment gets finalized before we reboot out from under it.

    The record-switch daemon is stopped first: otherwise its reconcile loop
    would see stopped-but-switch-says-record units and restart them inside
    the pre-reboot grace window, un-finalizing the very segment this stop
    exists to protect. It comes back with everything else after the reboot.zbubblecam-record-switch.servicez[SIM WATCHDOG] systemctl stop 	systemctlstopr   rM   N)rA   r*   printrO   )r+   r   s     r   stop_camera_servicesrT   p   su     336J36O6OO C C;; 	9499:::+vt4cBBBBB	C Cr    	video_dirr   c                v     t                      rdS d fd}t          t          j        |d                    S )a  Time-bounded write probe of the data partition.

    A dead/wedged SD card doesn't announce itself: writes just never return,
    and every service that touches the card silently stops making progress
    while the kernel stays healthy enough that the hardware watchdog never
    fires. Observed in a real soak -- the Pi hung for 8.5 hours with no log
    line explaining it, because writing that log line needed the same dead
    card. This probe is what turns that silent hang into a reboot.
    Tr!   r"   c                 (   dz  } t          | d          5 }|                    d           |                                 t          j        |                                           d d d            n# 1 swxY w Y   |                                  dS )Nz.bubblecam-storage-probewokT)openwriteflushr&   fsyncfilenounlink)
probe_pathfrU   s     r   _probezstorage_healthy.<locals>._probe   s    !;;
*c"" 	!aGGDMMMGGIIIHQXXZZ   	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	ts   AA33A7:A7F)defaultr!   r"   )r*   r"   r   rN   )rU   rb   s   ` r   storage_healthyre      sT     {{ t      
+FEBBBCCCr    c                 j    t                      rt          d           d S t          ddgd           d S )Nz[SIM WATCHDOG] systemctl rebootrQ   reboot   rM   )r*   rS   rO   r)   r    r   trigger_rebootri      s@    {{ /000k8,b999999r    c                    t                      rt          d           dS 	 t          dd          5 } |                     d           ddd           dS # 1 swxY w Y   dS # t          $ r t          j        ddgd	           Y dS w xY w)
a  Reset via the kernel directly, bypassing systemd's shutdown sequence.

    Only reached when a graceful reboot didn't happen within
    FORCED_REBOOT_AFTER_SECONDS -- which on this hardware means systemd is
    blocked unmounting a filesystem whose device stopped responding. Data
    loss is not a concern at that point: the card already isn't accepting
    writes, and every completed segment is crash-safe by design.
    z&[SIM WATCHDOG] forced reboot via sysrqNz/proc/sysrq-triggerrX   brg   z-fFrH   )r*   rS   rZ   r[   OSErrorrJ   rK   )ra   s    r   force_rebootrm      s     {{ 67776'-- 	GGCLLL	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 6 6 6$'u55555556s4   A! AA! AA! AA! !"BBintc                 	   t          j                    } | d         }|d         }|d         |d         }|d         }|d         }|d         }t          |           }t          | d         d	                   }i i }	d }
d
}d}t	          j        dd           t          j                    }t          j	        dd|rd| dndz              	 t          j                    |z
  t          k    r|}t          |          }}|r|st          j	        dd           n|r|st          j	        dd           t                      D ]\  }}|	                    |          }||z
  t           k     r+|	|<   t          j	        d| d|            t#                      rt%          d|            kt'          dd|gd           t	          j        |          }|r|ng dgz   }g }|dgz   D ]]}||v rAt	          j        ||          r,                    |           |                    |           G                    |d            ^|s,                    d           |                    d           n                    dd            fd|D             }|rd                    fd|D                       }t          j        |          r:t	          j        ddd| d | !           t          j	        dd| d"|            n4d|v }t	          j        ddd#| !           |s#t7          |            t          j        |           t          j                    }t	          j        ddd$| d%| d&| !           t          j	        dd'|rd(nd) d*| d%| d&|            |r@t#                      s#t=                       t          j        d+           nft=                       nWt?                       t#                      s;t          j        t@                     t=                       t          j        d+           d }
n|r0t	          j        ddd,d-                    |           !           d }
nMt	          j        dd           |
}
n3|
z
  |k    r*t          j                    d.k    rt          j!                     t          j        |           )/Nwatchdogstale_after_secondsescalation_after_secondsmax_reboot_attemptshealthy_reset_after_secondspoll_secondsgraceful_stop_grace_secondspathsrU   Tg        runningz	system upz -- after recovery reboot (z attempt(s) on record) z2STORAGE PROBE FAILED -- /data not accepting writeszstorage recoveredz heartbeat stale -- restarting z![SIM WATCHDOG] systemctl restart rQ   restartr   rM   sensorsstoragec                2    g | ]}|         z
  k    |S r)   r)   )r=   r   error_sinceescalation_afternows     r   r?   zmain.<locals>.<listcomp>   s5     
 
 
3T9J3JN^3^3^D3^3^3^r    z; c              3  <   K   | ]}| d |         z
  ddV  dS )z	 errored z.0fsNr)   )r=   r   r~   r   s     r   	<genexpr>zmain.<locals>.<genexpr>  sB      ppTX$OO{47H1HOOOOppppppr    errorzreboot attempts exhausted (z); giving up: )detailz) -- giving up: zescalating reboot: zrebooting now (attempt /z): zREBOOTING (forcedgracefulz
, attempt rh   z monitoring, not yet escalating: z, r   )"r	   loadr7   r   r   write_stater   
read_countr
   logtime	monotonicSTORAGE_PROBE_INTERVAL_SECONDSre   r   r(   %NON_CRITICAL_RESTART_COOLDOWN_SECONDSr*   rS   rO   recording_requested	has_error
setdefaultr   popjoin	exhaustedrT   sleep	incrementrm   ri   FORCED_REBOOT_AFTER_SECONDSreset)r+   watchdog_cfgr   max_attemptshealthy_reset_afterru   graceful_stop_gracecamera_namesrU   noncritical_restarted_athealthy_since
storage_okstorage_checked_atboot_attemptswas_okr   r   lastr   critical_servicescurrently_erroringescalate_candidatesr   storage_failedattemptr~   r   r   s                            @@@r   mainr      s3   
+--Cz?L45K#$>? 56L&'DE/L&'DE',,LS\+.//I$&K13%)MJ*i000 +--M
Jz;O\dKmKKKKbd   {!n##'EEEF(33J!$ <j < 
:'[\\\\ <F <
:':;;; 677 	Q 	Q
d/33D99#d
5Z(Z(Z14(.
:$'U'Ut'U'UVVV;; QDdDDEEEE$k9d%CRPPPPP (;KHH-@H\\bYKW I;. 	, 	,D(((Y-@{-S-S(&&tS111"))$////d++++
  	-""9c222%%i0000OOIt,,,
 
 
 
 
 
/
 
 
  C	%YYppppp\opppppF%l33 0'%]]]U[]]    
:'k\'k'kci'k'kllll!*.A!A%j'B`X^B`B`aaaa% 4 )---J2333&022%XWXX|XXPVXX    
Cn"L((* C C&C C)5C C:@C C   " ' %;; '$
2$"$$$$;; ' 
#>???$
2 MM 	% !IY$))DV:W:WYY    !MM!*i888$ #}$(;;;@W@Y@Y\]@]@]"$$$
<   w{!r    __main__)r   r   rd   )r+   r,   r!   r-   )rB   rC   rD   r   r!   rE   )r+   r,   r!   rE   )rU   r   r!   r"   )r!   rE   )r!   rn   )*__doc__
__future__r   r&   rJ   sysr   pathlibr   typingr   r   r   pathinsertstr__file__resolveparent	bubblecamr   r	   r
   r   r   r   r   r    NON_CRITICAL_STALE_AFTER_SECONDSr   r   r*   r7   rA   rO   rT   re   ri   rm   r   __name__exitr)   r    r   <module>r      s1   2 # " " " " " 				     



        ' ' ' ' ' ' ' ' ' ' 33ttH~~--//6=>> ? ? ? I I I I I I I I I I I I I I
 "$ 
 !  #(&  
 $'   ), % 2R 
 
 
 
 
2 2 2 2   N N N N[ [ [ [C C C CD D D D2: : : :6 6 6 6&T! T! T! T!n zCHTTVV r    