+
    F.j1                    f  a  RS tJ0 t R t^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t	^ RI
t
^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt]P(                  ! R4      t]P,                  '       ga   ]P.                  ! 4       t]P3                  ]P4                  ! RRR7      4       ]P7                  ]4       ]P9                  ]P:                  4       RTR R lltRtRUR	 R
 llt RVR R llt!R R lt"Rt#R t$R R lt%R R lt&RWR R llt'RXR R llt(R R lt)R R lt*R R lt+ ! R R 4      t,],! 4       t- ! R! R"4      t.].! 4       t/R#]P`                  ! 4       R$]P`                  ! 4       R%]P`                  ! 4       /t1] ^ k R&R#.R'R$.R(R%.R)R%.R*R%.R+R%.R,R%./t2] ^k R- R. lt3R/ R0 lt4R1 R2 lt5RTR3 R4 llt6 ! R5 R64      t7R7t8. t9] ^k RTR8 R9 llt:R: R; lt;R< R= lt<R> R? lt=RYR@ RA llt>RB RC lt?RZt@RD RE ltARF RG ltBRH RI ltCR[RJ RK lltDRL RM ltER\RN RO lltFRP tGRQ tH]IRR8X  d   ]H! 4        ]G! 4        R# R# )]u  Sandman — gradually make TV watching unpleasant after bedtime.

Attention-deficit-driven engine. The deficit grows while the TV is on and
recovers slowly while off; interference actions fire based on deficit level.

Usage:
    python3 sandman.py                # auto-discover TV, run engine
    python3 sandman.py --tv 192.168.1.50
    python3 sandman.py --dry-run      # log actions without sending
    python3 sandman.py --deficit 0.6  # inject starting deficit for testing
Nsandman'%(asctime)s [%(levelname)s] %(message)s%Y-%m-%d %H:%M:%Sdatefmtc                $    V ^8  d   QhR\         /# )   log_dirstr)formats   "/share/.ha_cache/ha_watchdog.py__annotate__r   2   s     : :3 :    c                j   \         P                  P                  4        \         P                  \        P
                  4       R\         n        \        P                  ! RRR7      p\        P                  ! \        P                  4      pVP                  \        P                  4       VP                  V4       \         P                  V4       V fA   \        P                  P!                  \        P                  P#                  \$        4      4      p \        P                  P'                  V R4      p\        P                  P)                  VR^RR7      pVP                  \        P
                  4       VP                  V4       \         P                  V4       \         P+                  R	V R
24       R# )z6Configure logging with console + rotating file output.Fr   r   r   Nzsandman.logzutf-8)maxBytesbackupCountencodingzLogging to z (5MB x 3 rotation)i  P )loghandlersclearsetLevelloggingDEBUG	propagate	FormatterStreamHandlersysstdoutINFOsetFormatter
addHandlerospathdirnameabspath__file__joinRotatingFileHandlerinfo)r	   fmtchlog_pathfhs   &    r   setup_loggingr.   2   s   LLLLCM


1#C 
		szz	*BKKOOCNN2 ''//"''//(";<ww||G]3H				-	-?G 
. 
B KKOOCNN2HH{8*$789r   z$/share/.ha_cache/.ha_wd_events.jsonlc                <    V ^8  d   QhR\         R\         R\        /# )r   action
event_typedeficitr   float)r   s   "r   r   r   R   s!      s  % r   c                   \         P                   P                  4       pRVP                  R4      RVP                  R4      RV RV/pVe   \        V^4      VR&    \	        \
        R	4      ;_uu_ 4       pVP                  \        P                  ! V4      R
,           4       RRR4       R#   + '       g   i     R# ; i  \         d     R# i ; i)z:Append a structured event to the shared events JSONL file.date%Y-%m-%dtime%H:%Mr0   typeNr2   a
)
datetimenowstrftimeroundopenEVENTS_FILEwritejsondumps	Exception)r0   r1   r2   r>   entryfs   &&&   r   
_log_eventrI   R   s    





!CS\\*-vs||G7Lvvz3E !,i+s##qGGDJJu%,- $### s0   B> 3-B* B> *B;	5B> ;B> >CCc                >    V ^8  d   QhR\         R\        R,          /# )r   timeoutreturnNr4   r   )r   s   "r   r   r   b   s     3 3 3t 3r   c                 0    \         P                  ! V R 7      # )rK   )
tv_controldiscover_tvrO   s   &r   rQ   rQ   b   s    !!'22r   c                >    V ^8  d   QhR\         R\        R,          /# )r   tv_iprL   Nr   int)r   s   "r   r   r   j   s     + +c +cDj +r   c                 0    \         P                  ! V R 7      # ip)rP   
get_volume)rS   s   &r   rZ   rZ   j   s      E**r   z!/share/.ha_cache/.ha_wd_action_tsc                       \        \        R 4      ;_uu_ 4       p V P                  \        \        P                  ! 4       4      4       RRR4       R#   + '       g   i     R# ; i  \
         d     R# i ; i)wN)rA   ACTION_MARKERrC   r   r8   rF   )rH   s    r   _touch_action_markerr^   p   sM    -%%GGC		$% &%%% s.   A& .AA& A#	A& #A& &A54A5c                <    V ^8  d   QhR\         R\        R\        /# )r   rS   volumerL   )r   rU   bool)r   s   "r   r   r   w   s!     3 3c 33 34 3r   c                 D    \        4        \        P                  ! WR 7      # rW   )r^   rP   
set_volume)rS   r`   s   &&r   rc   rc   w   s      22r   c                <    V ^8  d   QhR\         R\        R\        /# )r   rS   muterL   r   ra   )r   s   "r   r   r   |   s!     / /C /t / /r   c                 D    \        4        \        P                  ! WR 7      # rW   )r^   rP   set_mute)rS   re   s   &&r   rh   rh   |   s    t..r   c                <    V ^8  d   QhR\         R\        R\         /# )r   rS   duration_msstylerT   )r   s   "r   r   r      s!       # C r   c                H   VR8X  d   \        \        P                  ! ^^4      4       Fq  p\        V R4       \        P
                  ! \        P                  ! RR4      4       \        V R4       \        P
                  ! \        P                  ! RR4      4       Ks  	  R	# VR8X  dH   \        V 4      pVe7   \        V ^4       \        P
                  ! VR
,          4       \        W4       R	# R	# \        V R4       \        P
                  ! VR
,          4       \        V R4       R	# )u   Audio disruption with multiple styles.
Styles:
  single  — one mute/unmute (classic dropout)
  stutter — rapid on-off-on-off (sounds like buffering/packet loss)
  fade    — drop volume to 3 for duration then restore (gradual, eerie)
stutterT皙?333333?F皙?333333?fadeNi  )	rangerandomrandintrh   r8   sleepuniformrZ   rc   )rS   rj   rk   _origs   &&&  r   audio_glitchrz      s     	v~~a+,AUD!JJv~~c3/0UE"JJv~~dD12	 -
 
&% ua JJ{T)*u#  	

;%&r   c                0    V ^8  d   QhR\         R\        /# )r   rS   off_durationr3   )r   s   "r   r   r      s     8 8C 8u 8r   c                4    \         P                  ! WR7       R# )z1Toggle Ambilight off then on via menu navigation.rX   N)rP   ambilight_glitch)rS   r|   s   &&r   r~   r~      s    7r   c                <    V ^8  d   QhR\         R\         R\        /# )r   rS   key_namerL   rf   )r   s   "r   r   r      s!     1 1# 1 1 1r   c                0    \         P                  ! WR7      # )z Send a key press via JointSpace.rX   )rP   js_key)rS   r   s   &&r   r   r          X00r   c                0    V ^8  d   QhR\         R\         /# )r   rS   endpointr
   )r   s   "r   r   r      s     1 1# 1 1r   c                0    \         P                  ! WR7      # )zGET a JointSpace endpoint.rX   )rP   js_get)rS   r   s   &&r   r   r      r   r   c                0    V ^8  d   QhR\         R\         /# )r   rS   rL   r
   )r   s   "r   r   r      s      # # r   c                   \        V R4      pV'       d   VP                  R4      R8w  d   R# \        V R4      pV'       g   R# VP                  R/ 4      P                  RR	4      pR
VP                  4       9   d   R
# RVP                  4       9   d   R# RVP                  4       9   g   RVP                  4       9   d   R# RVP                  4       9   g   RVP                  4       9   d   R# RVP                  4       9   g   VR	8X  d   R# R# )z{Detect what program/app is active on the TV.
Returns: 'youtube', 'netflix', 'prime', 'livetv', 'home', 'off', or 'unknown'.z/powerstate
powerstateOnoffz/activities/currentunknown	componentpackageNameNAyoutubenetflixamazonprimeplaytvchannelslivetvlauncherhome)r   getlower)rS   poweractivitypkgs   &   r   detect_programr      s     5-(EEIIl+t3e23H
,,{B
'
+
+M4
@CCIIK	ciik	!	SYY[	 Gsyy{$:	SYY[	 J#))+$=	syy{	"cTkr   c                   \   a  ] tR t^t o RtR
V 3R lR lltRV 3R lR lltR tR tRt	V t
R	# )VolumeCapWatchdogu%  Enforces a volume ceiling for a duration. When volume rises above the cap,
waits a random drift delay then quietly nudges it back down — feels like the
TV settling, not a snap. arm() can be called repeatedly: takes the lower cap
and the later expiry so stacked actions extend the cap window.c                    < V ^8  d   QhRS[ /# )r   correction_delaytuple)r   __classdict__s   "r   r   VolumeCapWatchdog.__annotate__   s       r   c                    R V n         R V n        R V n        R V n        R V n        RV n        Wn        \        P                  ! 4       V n	        \        P                  ! 4       V n        R V n        ^ V n        R # )Ng     V@g     f@)rS   target_volume
expires_atarmed_atmax_expires_atextend_ranger   	threadingLock_lockEvent_stop_threadfight_backs)selfr   s   &&r   __init__VolumeCapWatchdog.__init__   s`    
!") 0^^%
__&
r   c          
      8   < V ^8  d   QhRS[ RS[RS[RS[RS[/# )r   rS   target
duration_sr   max_total_s)r   rU   r4   r   )r   r   s   "r   r   r      s2     ! ! !c !u !!>C!r   c                6   \         P                   ! 4       pV P                  ;_uu_ 4        Wn        Wc,           pWe,           pV P                  e   V P                  '       dn   V P                  V8  d]   \        V P                  V4      V n        \        V P                  V4      V n        \        V P                  ;'       g    TV4      V n        MW n        Wpn        W`n        Wn        ^ V n	        W@n
        R R R 4       V P                  e!   V P                  P                  4       '       g^   V P                  P                  4        \        P                   ! V P"                  RR7      V n        V P                  P%                  4        R # R #   + '       g   i     L; i)NTr   daemon)r8   r   rS   r   r   minmaxr   r   r   r   r   is_aliver   r   r   Thread_runstart)	r   rS   r   r   r   r   r>   
new_expirynew_maxs	   &&&&&&   r   armVolumeCapWatchdog.arm   s   iikZZZJ)J'G"".4???#-%(););V%D""%dooz"B&)$*=*=*H*H'&R#%+"", #&-##$  ,   <<t||'<'<'>'>JJ$++499TJDLLL  (?! Zs   6F!BFF	c                    V P                   P                  4        V P                  '       d   V P                  P                  ^R7       R# R#    rO   N)r   setr   r'   r   s   &r   stopVolumeCapWatchdog.stop   s3    

<<<LLa( r   c                   V P                   P                  4       '       Eg   V P                  ;_uu_ 4        V P                  pV P                  pV P
                  pR R R 4       Xe   Xe   \        P                  ! 4       V8  d   V P                  ;_uu_ 4        V P                  eR   \        P                  R\        P                  P                  4       P                  R4       RV P                   R24       R V n        R V n        R V n        R R R 4       R # \        X4      pVEe]   WA^,           8  EdO   V ;P                  ^,          un        \        P                  P                  4       P                  R4      pV P                  ;_uu_ 4        \        P                   ! V P"                  !  pV P                  V,           pV P                  e   \%        WpP                  4      pWpP                  ,
          pWpn        V\        P                  ! 4       ,
          p	R R R 4       \        P                  RV RV P                   RV RV RXR	 R
X	^<,          R R24       \        P                   ! V P&                  !  p
V P                   P)                  V
4       V P                   P                  4       '       d   R # V P                  ;_uu_ 4        V P                  pV P                  pR R R 4       Xe   Xe   \        P                  ! 4       V8  d   R # V\        P*                  ! ^ ^4      ,           p\-        W=4       \        P                  R\        P                  P                  4       P                  R4       RV RV
R	 R24       V P                   P)                  \        P                   ! ^^4      4       EK  R #   + '       g   i     ELg; i  + '       g   i     R # ; i  + '       g   i     EL; i  + '       g   i     EL; i)N  [%H:%M:%Su-   ] 🎚️  Volume cap released (fight-backs: )u   ] 👴 Fight-back #: z > cap z (+.0fu   s → .1fz	min left)u   ]    ↩️  Drifted to z (after s))r   is_setr   r   r   rS   r8   r   r)   r=   r>   r?   r   r   rZ   rt   rw   r   r   r   waitru   rc   )r   r   expiresrS   currentnow_sextendr   extended_by	remainingdelay
cur_target
cur_expiry	correcteds   &             r   r   VolumeCapWatchdog._run   s1   **##%%++//

  ~DIIK74JZZZ))53x'8'8'<'<'>'G'G
'S&T  UB  CG  CS  CS  BT  TU  "V  W)-D&&*DO*.D'    'G"w!';  A%  ))--/88D ZZZ#^^T->->?F!%6!9J**6%(5H5H%I
",">K&0O *TYY[ 8I   3ug%89I9I8J"WIU\]c\d e)#.fYr\#4FiQ R(=(=>

&::$$&&ZZZ!%!3!3J!%J   %);tyy{j?X&1)==	5,3x00446??
KLLdendoowx}  B  xC  CE  F  GJJOOFNN1a01S &
  Z   ZZ  ZZs1   %N9A5OB	O!O59O
	O	!O2	5P	)r   r   r   r   r   r   r   r   r   r   rS   N))      )r         @)__name__
__module____qualname____firstlineno____doc__r   r   r   r   __static_attributes____classdictcell__r   s   @r   r   r      s.     F
 ! !0)
*2 *2r   r   c                   X   a  ] tR tRt o RtR tRV 3R lR lltR tR tR t	R	 t
R
tV tR# )LadderDownWatchdogi)  u  A volume ceiling that marches down to mute by 'sagging', never snapping.

Two independent mechanisms:
  • The CEILING decrements by `step` every tick (~tick_min..tick_max s, jittered),
    anchored to the volume at arm() time, marching monotonically to 0.
  • ENFORCEMENT: whenever the live volume sits above the ceiling — whether from
    the natural march or from dad clutching the remote up — it waits a random
    reaction delay (`react_min..react_max`s, so it never looks instant/reactive)
    then eases the volume down by `sag_fraction` of the remaining gap every
    `sag_pace_min..sag_pace_max`s until it meets the ceiling. Big clutch-ups decay
    fast-then-slow (like the set dying); small pokes barely move. It NEVER raises
    the volume, so if dad turns it down further the ladder leaves it alone.

On reaching 0 it holds muted for `hold_s` (clutch-ups sag back, but the hold timer
runs from the first mute and is NOT extended by pokes), then releases.

While armed it holds the 'tv_volume' channel lock so the selector suppresses every
other volume action (and a second ladder) until it finishes. If the TV reads as
off/unreachable for two consecutive checks, it cancels and releases.c                2   R V n         R V n        ^V n        RV n        RV n        RV n        RV n        RV n        RV n        RV n	        RV n
        R V n        \        P                  ! 4       V n        \        P                  ! 4       V n        R V n        R V n        R # )	N      I@     R@r          @      2@      @      &@      ?)rS   ceilingsteptick_mintick_maxhold_s	react_min	react_maxsag_pace_minsag_pace_maxsag_fraction_next_decrementr   r   r   r   r   r   _channelr   s   &r   r   LadderDownWatchdog.__init__>  s    
	  #^^%
__&
r   c                \   < V ^8  d   QhRS[ RS[RS[RS[RS[RS[RS[RS[R	S[R
S[RS[/# )r   rS   start_volumer   r   r   r   r   r  r  r  r  )r   rU   r4   )r   r   s   "r   r   LadderDownWatchdog.__annotate__P  sh     ! ! !C !s !!.3!DI!!/4!  ! 6;!  	!r   c           	        V P                   ;_uu_ 4        Wn        \        ^ \        V4      4      V n        \        ^\        V4      4      V n        W@n        \        WE4      V n        W`n        Wpn	        \        Wx4      V n
        Wn        \        W4      V n        \        R\        RV4      4      V n        \        P                  ! 4       \         P"                  ! V P                  V P                  4      ,           V n        RRR4       \&        P)                  R4      pVe'   V P*                  f   VP-                  RR7       Wn        V P.                  e!   V P.                  P1                  4       '       g^   V P2                  P5                  4        \6        P8                  ! V P:                  RR7      V n        V P.                  P=                  4        R# R#   + '       g   i     L; i)	          ?rp   N	tv_volumeFblockingTr   )r   rS   r   rU   r   r   r   r   r   r   r  r  r  r   r  r8   rt   rw   r  _action_locksr   r  acquirer   r   r   r   r   r   r   r   )r   rS   r	  r   r   r   r   r   r  r  r  r  r+   s   &&&&&&&&&&&& r   r   LadderDownWatchdog.armP  sH   
 ZZZJq#l"34DLAs4y)DI$M3DM K&N 6DN , #L ?D #CT<)@ AD $(99;t}}1]#]D  " {+>dmm3JJJ&M<<t||'<'<'>'>JJ$++499TJDLLL  (?+ Zs   C(GG!	c                    V P                   e%    V P                   P                  4        R V n         R # R #   \         d     Li ; iN)r  releaseRuntimeErrorr   s   &r   _release_channel#LadderDownWatchdog._release_channelo  sC    ==$%%' !DM %   s   5 AAc                $   V P                   P                  4        V P                  '       d   V P                  P                  ^R7       V P                  ;_uu_ 4        RV n        RRR4       V P                  4        R#   + '       g   i     L"; ir   )r   r   r   r'   r   r   r  r   s   &r   r   LadderDownWatchdog.stopw  sW    

<<<LLa(ZZZDL  Zs   A??B	c                   V P                   ;_uu_ 4        V P                  f    RRR4       R# \        P                  ! 4       V P                  8  d   V P                  ^ 8  dw   \	        ^ V P                  V P
                  ,
          4      V n        \        P                  ! 4       \        P                  ! V P                  V P                  4      ,           V n        V P                  uuRRR4       #   + '       g   i     R# ; i)zGMarch the ceiling down if its tick is due. Returns the current ceiling.N)
r   r   r8   r  r   r   rt   rw   r   r   r   s   &r   _maybe_decrement#LadderDownWatchdog._maybe_decrement  s    ZZZ||# Z yy{d222t||a7G"1dllTYY&>?'+yy{V^^DMMSWS`S`5a'a$<< ZZZs   C0B6C00D	c                
   ^ pRp V P                   P                  4       '       Eg   V P                   P                  \        P                  ! ^^4      4      '       d    V P                  4        R# V P                  4       pVf    V P                  4        R# V P                  ;_uu_ 4        V P                  V P                  rTRRR4       \        X4      pVf   V^,          pV^8  d~   \        P                  P                  4       P                  R4      p\        P                  RV R24       V P                  ;_uu_ 4        RV n        RRR4        V P                  4        R# EKd  ^ pWc8  Ed   V P                   P                  \        P                  ! V P"                  V P$                  4      4      '       d    V P                  4        R# V P                   P                  4       '       Eg`   V P                  4       pVf    V P                  4        R# \        V4      pVf   EM'Wc8:  d   EMWc,
          p\'        W6\'        ^\)        WP*                  ,          4      4      ,
          4      p	\-        WI4       V^ 8X  d   V	^ 8X  d   \/        VR4       \        P                  P                  4       P                  R4      pV^ 8X  d   RMRp
\        P                  RV RV
 R	V R
V	 RV R24       V P                   P                  \        P                  ! V P0                  V P2                  4      4      '       g   EKm   V P                  4        R# V P                  4       pVf    V P                  4        R# V^ 8X  Ed&   Vfv   \4        P4                  ! 4       p\/        VR4       \        P                  P                  4       P                  R4      p\        P                  RV RX^<,          R R24       EK  \4        P4                  ! 4       V,
          X8  d   V P                  ;_uu_ 4        RV n        RRR4       \        P                  P                  4       P                  R4      p\        P                  RV RV^<,          R R24        V P                  4        R# EK  RpEK   V P                  4        R#   + '       g   i     EL; i  + '       g   i     EL; i  + '       g   i     L; i  T P                  4        i ; i)r  Nr   r   u0   ]   🪜 Ladder cancelled — TV off/unreachableTu   🪜🔇u   🪜]   z Sag    →z
 (ceiling r   u-   ]   🪜🔇 Ladder reached mute — holding r   r   u#   ]   🪜🔇 Ladder released after zmin mute hold)r   r   r   rt   rw   r  r  r   rS   r   rZ   r=   r>   r?   r   r)   r   r   r  r   r@   r  rc   rh   r  r  r8   )r   missesmuted_sincer   rS   r   curngapnewtags   &          r   r   LadderDownWatchdog._run  s   C	$jj''))::??6>>!Q#788~ !!#} //1?x !!#w ZZZ$(JJ6   ';aKF{$--113<<ZH3qc)Y!Z[!ZZZ+/DL (b !!#a  =zzv~~dnndnn'UVVP !!#O #jj//11"&"7"7"9"?"H !!#G )/;!>!!m!'QcDUDU>U8V1W+WX"5."a<C1H$UD1$--113<<ZH,3qLjf3qccU%uCuJwiWX!YZ::??6>>$:K:KTM^M^+_``"* !!#% //1?  !!# a<"*&*iik -$--113<<ZH3qc)VW]^`W`adVeeh!ij{2f<!ZZZ+/DL ($--113<<ZH3qc)LVTVYWZO[h!ij !!# = #'KA *D !!#w  ZZ (ZZV (Z !!#s    S7 5S7 0S7 S7 +R<BS7 SS7 'AS7  S7 1S7 DS7 S7 7B6S7 -S$5AS7 !S7 <S		S7 S!		S7 $S4	/S7 7T	)r  r   r  r   r   r   r   r  r   r  r  r  r   r   r   rS   N)	   r   r   r   r   r   r   r   r   )r   r   r   r   r   r   r   r  r   r  r   r   r   r   s   @r   r   r   )  s5     L($! !>!  F$ F$r   r   	yt_lounge
tv_networkr  youtube_ad_breakthrottleladder_down
volume_capvolume_drop_bigvolume_nudge	full_mutec                0    V ^8  d   QhR\         R\        /# r   action_namerL   rf   )r   s   "r   r   r     s        r   c                    \         P                  V . 4       F7  p\        P                  V4      pVf   K  VP                  4       '       g   K6   R# 	  R# )z>True if any channel this action would claim is currently held.TF)_ACTION_CHANNELSr   r  locked)r6  r+   locks   &  r   _channels_busyr;    sA    "";3  $ 4 r   c                F    V ^8  d   QhR\         R\        \         ,          /# r5  )r   list)r   s   "r   r   r     s      3 49 r   c                   . p\         P                  V . 4       Fm  p\        P                  V4      pVe   VP                  RR7      '       g)   V F  p\        V,          P	                  4        K   	  . u # VP                  V4       Ko  	  V# )zTry to acquire all channels for action_name. Returns the list of locks
successfully claimed (release these in finally). On partial failure releases
what was claimed and returns [].Fr  )r8  r   r  r  r  append)r6  claimedr+   r:  cs   &    r   _channels_acquirerB    sx     G"";3  $<t||U|;;a ((* Ir 4 Nr   c                >    V ^8  d   QhR\         \        ,          RR/# )r   r@  rL   N)r=  r   )r   s   "r   r   r     s      tCy T r   c                 t    V  F  p \         V,          P                  4        K!  	  R #   \         d     K3  i ; ir  )r  r  r  )r@  r+   s   & r   _channels_releaserE    s6    	"%%'   		s   (77c                0    V ^8  d   QhR\         R\        /# )r   r#   rL   )r   dict)r   s   "r   r   r     s      c T r   c                V   V f_   \         P                  P                  \         P                  P                  \         P                  P	                  \
        4      4      R4      p \        V 4      ;_uu_ 4       p\        P                  ! V4      uuRRR4       #   + '       g   i     R# ; i)z#Load sandman config from JSON file.Nwd_config.json)	r"   r#   r'   r$   r%   r&   rA   rD   load)r#   rH   s   & r   load_configrK    sX    |ww||BGGOOBGGOOH,EFHXY	dqyy| 
s   6BB(	c                      a  ] tR tRt o RtRV 3R lR lltV 3R lR ltV 3R lR ltV 3R	 lR
 ltV 3R lR lt	V 3R lR lt
R tRV 3R lR lltR tV 3R lR ltV 3R lR ltV 3R lR ltRtV tR# )AttentionModeli"  a<  Persistent attention deficit that grows with TV watching and recovers slowly.

deficit: 0.0 = fresh, 1.0 = maxed out.
Grows logarithmically while TV is on (first 30min low impact, then accelerates).
Recovers via exponential decay while TV is off (>24hr for full recovery).
Persisted to disk so it survives restarts.
c                2   < V ^8  d   QhRS[ RS[RS[RS[/# )r   config
state_filespeed	no_golden)rG  r   r4   ra   )r   r   s   "r   r   AttentionModel.__annotate__+  s+      t  04r   c                   VP                  R / 4      V n        W n        W0n        W0n        W@n        V P                  P                  RR4      V n        V P                  P                  RR4      V n        V P                  P                  RR4      V n        V P                  P                  R^ ^.4      V n	        V P                  P                  R. 4      V n
        V P                  P                  R	R
4      V n        RV n        \        P                  P                  4       P                  4       V n        RV n        RV n        RV n        \        P                  P                  4       P)                  R4      V n        V P-                  4        R# )attention_modelbase_impact_rateg~jth?recovery_rategMb@?sleep_recovery_multiplier       @sleep_hourstime_multipliersaction_thresholdrq           Nr7   )r   rO  rP  rQ  
_cli_speedrR  	base_raterW  
sleep_multrZ  r[  r\  r2   r=   r>   	isoformatlast_updatedsession_startnext_action_attotal_watch_today_minr?   _today_daterJ  )r   rO  rP  rQ  rR  s   &&&&&r   r   AttentionModel.__init__+  s'   jj!2B7$
" );UC![[___fE++//*EsK;;??=1a&A $0BB G $0BD I $--113==?!"%("#,,002;;JG		r   c                &   < V ^8  d   QhRS[ RS[ /# )r   
dt_minutessession_minutesr4   )r   r   s   "r   r   rS  E  s     1 1u 1u 1r   c                l   WP                   ,          pV P                  4       w  rEV P                  \        P                  ! ^VR,          ,           4      ,          V,          pV ;P
                  Wc,          ,          un        \        V P
                  R4      V n        V ;P                  V,          un        R# )zIncrease deficit while TV is on.

Logarithmic: first 30min has low impact, then accelerates.
dt_minutes: time elapsed since last update (real minutes, before speed).
session_minutes: total minutes in current session so far.
g      >@r  N)rQ  get_time_multiplierr_  mathr   r2   r   re  )r   ri  rj  dt	rate_multrx   impacts   &&&    r   growAttentionModel.growE  sy     **$//1	$((1/E+E"FFR#4<<-""j0"r   c                    < V ^8  d   QhRS[ /# )r   ri  rk  )r   r   s   "r   r   rS  S  s      % r   c                   V\        V RV P                  4      ,          p\        P                  P                  4       pV P                  w  rEWE8:  d!   WCP
                  u;8*  ;'       d    V8  Mu pM&VP
                  V8  ;'       g    VP
                  V8  pV'       d   V P                  MRpV ;P                  \        P                  ! V P                  ) V,          V,          4      ,          un        V P                  R8  d
   RV n        R# R# )zjDecrease deficit while TV is off via exponential decay.

Sleep hours (midnight-7am) get boosted recovery.
recovery_speedr  gMbP?r]  N)getattrrQ  r=   r>   rZ  hourr`  r2   rn  exprW  )r   ri  ro  r>   sleep_start	sleep_endis_sleep	rest_mults   &&      r   recoverAttentionModel.recoverS  s    
 '$(8$**EE##%!%!1!1#"hh:::Hxx;.FF#((Y2FH'/DOOS	$"4"4!4r!9I!EFF<<%DL  r   c                    < V ^8  d   QhRS[ /# r   rL   r   )r   r   s   "r   r   rS  f  s      U r   c                |   \         P                   P                  4       pVP                  ^<,          VP                  ,           pV P                   F  p\        \        VR,          P                  R4      4      w  rE\        \        VR,          P                  R4      4      w  rgV^<,          V,           pV^<,          V,           p	W8:  d   W(8  ;'       g    W)8  p
MYu;8*  ;'       d    V	8  Mu p
V
'       g   K  VP                  RR4      pV P                  '       d   V'       d   Ru # VR,          V'       * 3u # 	  R# )zoReturns (rate_multiplier, actions_allowed) based on current time.

During golden period: returns (0.3, False).
r   :end
no_actionsFrate)r  T)
r=   r>   rx  minuter[  maprU   splitr   rR  )r   r>   now_minsslotstart_hstart_mend_hend_m
start_minsend_minsin_ranger  s   &           r   rm  "AttentionModel.get_time_multiplierf  s    
 ##%88b=3::-))D"3W(;(;C(@AGsDK$5$5c$:;LE 2/JrzE)H %#1HHX5H%<<H<x!XXlE:
>>>j&&V*n55# *( r   c                    < V ^8  d   QhRS[ /# r  rk  )r   r   s   "r   r   rS    s      U r   c                    V P                   # )z"Returns current deficit float 0-1.)r2   r   s   &r   get_deficitAttentionModel.get_deficit  s    ||r   c                    < V ^8  d   QhRS[ /# )r   valuerk  )r   r   s   "r   r   rS    s     1 1 1r   c                <    \        R\        RV4      4      V n        R# )z/Set deficit directly (for test mode injection).r]  r  N)r   r   r2   )r   r  s   &&r   set_deficitAttentionModel.set_deficit  s    3C0r   c           	     ~    \        V P                  4      ;_uu_ 4       p\        P                  ! V4      pRRR4       XP	                  RV P
                  4      p\        W0P
                  ,
          4      R8  dU   V P
                  TrTW0n        V P                  pWF8  WV8  8w  d+   WV8  d   RMRp\        P                  RVR RV R	V R
24        \        R4      ;_uu_ 4       p\        P                  ! T4      pRRR4       XP	                  RR4      p	TP	                  RT	4      p
YP                  8w  d-   \        P                  RT P                   RT	 R24       Yn        T
\        T RT P                  4      8w  d:   \        P                  R\        T RT P                  4       RT
 R24       Yn        R# R#   + '       g   i     EL; i  \         d     ELi ; i  + '       g   i     L; i  \         d4    T P                  T P                  8w  d   T P                  T n         R#  R# \         d     R# i ; i)zNCheck if state or speed files were modified externally (e.g. by Telegram bot).Nr2   {Gz?abovebelowu     Deficit → .3f (z threshold r   z"/share/.ha_cache/.ha_wd_speed.jsonrQ  r  rv  z  Speed changed: u   x → xz  Recovery speed changed: )rA   rP  rD   rJ  r   r2   absr\  r   r)   rF   rQ  rw  rv  FileNotFoundErrorr^  )r   rH   statefile_deficitold_dnew_d	threshold	direction
speed_data	new_speednew_recoverys   &          r   check_external_update$AttentionModel.check_external_update  s   	doo&&!		! ' 99Y=L<,,./$6#||\u+ 11	&E,>?+0+=7IHH~eC[9+[QZP[[\]^	:;;q!YYq\
 <"w4I%>>*:IFLJJ&,TZZLykKL&
wt-=tzzJJ5gdDTVZV`V`6a5bbhiuhvvwxy&2# K- '&&  		 <; ! 	-zzT__,!__
 - 		s_   G F8BG G1 "G9B;G1 8G			G GGG.	)G1 17H<.H<7H<;H<Nc                    < V ^8  d   QhRS[ /# )r   rd  r
   )r   r   s   "r   r   rS    s     8 83 8r   c                p   Ve   Wn         R\        V P                  ^4      R\        P                  P	                  4       P                  4       RV P                  RV P                   R\        V P                  ^4      RV P                  /p V P                  R,           p\        VR	4      ;_uu_ 4       p\        P                  ! W$^R
7       RRR4       \        P                  ! W0P                  4       R#   + '       g   i     L2; i  \         d$   p\         P#                  RT 24        Rp?R# Rp?ii ; i)zPersist state to JSON file.Nr2   rb  rc  rd  re  
today_date.tmpr\   indentz  Failed to save state: )rd  r@   r2   r=   r>   ra  rc  re  rf  rP  rA   rD   dumpr"   replacerF   r   warning)r   rd  r  tmprH   es   &&    r   saveAttentionModel.save  s    %"0uT\\1-H--113==?T//d11#U4+E+Eq%I$**
	8//F*Cc31		%1-  JJsOO,    	8KK21#677	8s0   
'D 1C4
(D 4D	?D D5D00D5c                    \        V P                  4      ;_uu_ 4       p\        P                  ! V4      pRRR4       XP	                  RR4      V n        VP	                  R\        P                  P                  4       P                  4       4      V n	        VP	                  RR4      V n
        VP	                  RR4      p\        P                  P                  4       P                  R4      pW48X  d   VP	                  R	R4      V n        MRV n        W@n         \        P                  P                  V P                  4      p\        P                  P                  4       V,
          P                  4       R
,          pV^ 8  dG   V P                  f9   V P!                  V4       \"        P%                  RVR RV P
                  R 24       \"        P%                  RT P
                  R RT P                  R R24       R#   + '       g   i     EL; i  \&         d     LUi ; i  \(         d    \"        P%                  R4        R# \&         d,   p\"        P+                  RT R24       RT n         Rp?R# Rp?ii ; i)z=Load state from JSON file, with sensible defaults if missing.Nr2   r]  rb  rc  r   r7   re        N@z
  Applied r   z"min offline recovery, deficit now r  z  Loaded state: deficit=z, today_watch=r   u.     No state file — starting fresh (deficit=0)z  Failed to load state: u    — starting fresh)rA   rP  rD   rJ  r   r2   r=   r>   ra  rb  rc  r?   re  rf  fromisoformattotal_secondsr~  r   r)   rF   r  r  )r   rH   r  
saved_datetodaylastelapsed_minr  s   &       r   rJ  AttentionModel.load  s   !	doo&&!		! ' 99Y4DL %		.(:K:K:O:O:Q:[:[:] ^D!&?D!AD <4J%%))+44Z@E"-2YY7NPS-T*-0*#( ((66t7H7HI'00446=LLNQUU?t'9'9'ALL-HHz+c)::\]a]i]ijm\nop HH/S/A B$$($>$>s#C3H I5 '&&.  
 ! 	HHHEG 	KK21#5HIJDLL	s_   H! G<C!H! B0H 2H! <H		H! HH! HH! ! I9I9I9 I44I9c                &   < V ^8  d   QhRS[ RS[/# )r   
action_cfgrL   )rG  ra   )r   r   s   "r   r   rS    s     + +T +d +r   c                D    VP                  RR4      pV P                  V8  # )z;Check if current deficit >= action's min_deficit threshold.min_deficitr]  )r   r2   )r   r  r  s   && r   is_action_eligible!AttentionModel.is_action_eligible  s!     nn]C8||{**r   c                &   < V ^8  d   QhRS[ RS[/# r   rO  rL   )rG  r4   )r   r   s   "r   r   rS    s     	+ 	+4 	+E 	+r   c                   VP                  R/ 4      pVP                  R^4      pVP                  RR4      pW@P                  WC,
          ,          ,
          pV\        P                  ! RR4      ,          p\	        W5V,           4      # )z<Scale interval with deficit: high deficit = short intervals.intervalmin_secondsmax_secondsi  ro   g333333ӿ)r   r2   rt   rw   r   )r   rO  ivalmin_smax_sr   jitters   &&     r   get_intervalAttentionModel.get_interval  si    zz*b)+,77&..s3356/**r   c                    < V ^8  d   QhRS[ /# )r   watch_minutesrk  )r   r   s   "r   r   rS    s     - -u -r   c                    \         P                   P                  4       P                  R4      pW P                  8w  d   W n        RV n        R# R# )z7Track daily watch totals. Resets when the date changes.r7   r]  N)r=   r>   r?   rf  re  )r   r  r  s   && r   	log_dailyAttentionModel.log_daily  s@    !!%%'00<$$$$),D& %r   )r^  rf  r\  r_  rO  r2   rb  rd  rR  rW  rv  rc  rZ  r`  rQ  rP  r[  re  )"/share/.ha_cache/.ha_wd_state.jsonr  Fr  )r   r   r   r   r   r   rr  r~  rm  r  r  r  r  rJ  r  r  r  r   r   r   s   @r   rM  rM  "  sz      41 1 & < 1 1!F8 8(#J+ +
	+ 	+- -r   rM  z /share/.ha_cache/.ha_wd_rl.jsonlc                H    V ^8  d   QhR\         R\         R\        R\        /# )r   r2   session_minr6  apprM   )r   s   "r   r   r     s(      5 u 3 S r   c                   \         P                   P                  4       pRVP                  4       R\        V ^4      RVP	                  R4      RVR\        V^4      RVRR	R
R	/p \        \        R4      ;_uu_ 4       pVP                  4       pVP                  \        P                  ! V4      R,           4       R	R	R	4       \        P                  XV34       V\         P                  ! ^R7      ,
          p\         U	U
u. uF  w  rW8  g   K  W3NK  	  up
p	\        R&   V#   + '       g   i     Lq; iu up
p	i   \         d$   p\        P!                  RT 24        R	p?R	# R	p?ii ; i)zSLog an action to the RL log file. Returns the file offset for later reward fill-in.tsr2   r8   r9   r  r  r0   tv_off_within_5mNtv_off_within_15mr;   r<   )minutes:NNNz  RL log write failed: )r=   r>   ra  r@   r?   rA   RL_LOG_PATHtellrC   rD   rE   _rl_pendingr?  	timedeltarF   r   debug)r2   r  r6  r  r>   rG   rH   offsetcutoffotr  s   &&&&        r   rl_log_actionr    s%   





!Ccmmo5!$W%su[!,+DT	E+s##qVVXFGGDJJu%,- $ 	FC=)x))"55-8G[TQAJ&1&[GA $# H 		+A3/0sC   #D: ;=D!8AD: >
D4D4D: !D1	,D: :E(E##E(c                8    V ^8  d   QhR\         P                   /# )r   tv_off_time)r=   )r   s   "r   r   r   #  s     $3 $3!2!2 $3r   c                d   \         '       g   R#  \        \        R4      ;_uu_ 4       pVP                  4       pRRR4       Rp\          F  w  rEW,
          P	                  4       R,          pV^ 8  d   K+  VR8*  pVR8*  pVP                  4       p	\        X4       FN  w  rW9   g   K   \        P                  ! V4      pW|R&   WR&   \        P                  ! V4      R	,           W*&   R
p K  	  K  	  V'       d3   \        \        R4      ;_uu_ 4       pVP                  X4       RRR4       \         P                  4        R#   + '       g   i     EL; i  \        P                   d     Li ; i  + '       g   i     LU; i  \         d$   p\        P                  RT 24        Rp?R# Rp?ii ; i)zGWhen TV turns off, fill in tv_off_within_Xm for recent pending actions.NrFr  r   g      .@r  r  r<   Tr\   z  RL reward fill failed: )r  rA   r  	readlinesr  ra  	enumeraterD   loadsrE   JSONDecodeError
writelinesr   rF   r   r  )r  rH   linesupdatedr  action_timedeltawithin_5	within_15ts_strilinerG   r  s   &             r   rl_fill_rewardsr  #  se   ; 3+s##qKKME $ #.F .==?$FEqy|HI **,F$U+> $

4 04<015>12#'::e#4t#;"&  , $/( k3''1U# ( 	9 $##(  // 
 ('  3		-aS1223sp   F D?A(F '?E&F 7F E.!F ?E	
	F E+(F *E++F .E>	9F F/F**F/c                0    V ^8  d   QhR\         R\        /# r  )rG  r   )r   s   "r   r   r   L  s       # r   c                b   V P                  R/ 4      pVP                  RR4      '       g   \        4       # \        P                  P                  4       P	                  R4      p\
        P                  ! VR,           4      p\        V R,          P                  4       4      pVP                  \        V4      ^,          \        V4      ^,          ^,          4      p\        VP                  V\        V\        V4      ^,
          4      4      4      pVP                  R4       VP                  R4       V# )	z9Each day, randomly disable some action types for variety.opsecdaily_personalityFr7   personalityactionsr1  audio_stutter_long)r   r   r=   r>   r?   rt   Randomr=  keysru   lensampler   discard)rO  r  r  rngall_actionsnum_disableddisableds   &      r   _daily_action_filterr  L  s    JJw#E99(%00u!!#,,Z8E
---
.Cvi(--/0K;;s;/14c+6F6Ja6OPL3::k3|S=MPQ=Q+RSTH&')*Or   c                H    V ^8  d   QhR\         R\        R\        R\        /# )r   r2   rO  disabled_todayrL   )r4   rG  r   r   )r   s   "r   r   r   ]  s(     #= #= #= #=c #=e #=r   c                   \         P                   P                  4       pVP                  ^<,          VP                  ,           p. p. pVR,          P	                  4        EF9  w  rxWr9   d   K  VP                  RR4      p	W	8  d   K(  \        V4      '       d   K;  VP                  R4      p
V
'       d   V
P                  R4      '       d    \        \        V
R,          P                  R4      4      w  r\        \        V
R,          P                  R4      4      w  rV^<,          V,           pV^<,          V,           pVV8:  d   Yu;8:  d   V8  g   M K  MWO8  g   VV8  g   EK  VP                  Wx34       VP                  VP                  R	^
4      4       EK<  	  V'       g   R# \        P                  ! WV^R
7      ^ ,          #   \        \        3 d     Lti ; i)zPick a random action weighted by base weight, filtered by deficit threshold.
Returns (action_name, action_config) or (None, None).r
  r  r]  permissible_hoursenabledr   r  r  weight)weightskNN)r=   r>   rx  r  itemsr   r;  r  rU   r  
ValueErrorKeyErrorr?  rt   choices)r2   rO  r  r>   cur_minuteseligibler  nameacfgr  phshsmehemr  r  s   &&&              r   pick_action_deficitr,  ]  s    




!C((R-#**,KHGY'--/
!hh}c2 $XX)*"&&##S"W+"3"3C"89S"U)//#"67r'B,R"e##:U:  ; (2kE6I  	%txx"-.3 04 >>(q9!<< ) s   
BGGGGc                `    V ^8  d   QhR\         R\         R\        R\        R\        R\        /# )r   rS   r6  r  dry_runseverity_levelr2   )r   rG  ra   r4   )r   s   "r   r   r     sD     V- V-# V-C V-T V-D V-jo V-  AF V-r   c                   VP                  R/ 4      p\        P                  P                  4       P                  R4      pV'       d   \        P                  RV RV 24       R# \        4        VR8X  d_   \        V 4      pVeN   \        ^WP                  R^4      ,
          4      p	\        W	4       \        P                  RV RV R	V	 24       R# R# VR9   d   \        V 4      pVey   \        P                  ! VP                  R
^4      VP                  R^4      4      p
\        ^ W,
          4      p	\        W	4       \        P                  RV RV R	V	 RV
 R2	4       R# R# VR8X  Ed:   \        V 4      pVEe(   \        P                  ! VP                  R^4      VP                  R^4      4      p
\        ^ W,
          4      p\        P                  ! VP                  RR4      VP                  RR4      4      pVP                  R^Z4      VP                  R^4      3pVP                  RR4      p\        W4       \        P                  WVVVR7       \        P                  RV RV R	V RV^<,          R RV^ ,          R RV^,          R RV^<,          R R 24       R# R# VR!8X  EdE   \        V 4      pVEe3   \        P                  4        VP                  R"^4      pVP                  R#^24      pVP                  R$^K4      pVP                  R%R4      p\         P                  WVVVVVP                  R&^4      VP                  R'^4      VP                  R(^4      VP                  R)^4      VP                  R*R+4      R,7       V\        ^V4      ,          VV,           ^,          ,          R-,          p\        P                  RV R.V R/V R0VR RVR R1VR R2V^<,          R R 24       R# R# VR38X  d(   \#        V R44       \        P                  RV R524       R# VR9   Ed   VP                  R^
4      pVP                  R^<4      pVVV,
          \%        R6V4      ,          ,           p^ p^ pVV8  d   \#        V R44       \        P                  ! R7R84      p\&        P(                  ! V4       \#        V R94       \        P                  ! R:R;4      p\&        P(                  ! V4       VVV,           ,          pV^,          pK  \        P                  RV R<V R=VR R>VR? R2	4       R# VR@8X  d{   \        P*                  ! . RO4      p\-        V V4       \&        P(                  ! R84       \-        V \        P*                  ! RBRA.4      4       \        P                  RV RCV R24       R# VRD8X  dc   \        P                  ! VP                  RE^
4      VP                  RF^4      4      p\-        V RG4       \        P                  RV RHVR RI24       R# VRJ8X  d   \        P                  ! VP                  RK^4      VP                  RL^4      4      p\.        P0                  ! V RM7       \.        P2                  ! V 4      p\5        V4       F9  p\6        P8                  ! RNROVRPRQRRRS.R4^RT7       \&        P(                  ! R;4       K;  	  \        P                  RV RUV 24       R# VRV8X  dc   \        P                  ! VP                  RW^4      VP                  RXRY4      4      p\;        V VRZ4       \        P                  RV R[V R\24       R# VR]8X  dc   \        P                  ! VP                  RWR^4      VP                  RXR_4      4      p\;        V VR`4       \        P                  RV RaV R\24       R# VRb8X  d   \        P                  ! VP                  RE^4      VP                  RF^4      4      p\-        V RG4       \&        P(                  ! V4       \-        V Rc4       \        P                  RV RdVRe Rf24       R# VRg8X  d7   \=        WP                  Rh^4      4       \        P                  RV Ri24       R# VRj8X  d   \-        V Rk4       \        P                  ! VP                  Rl^4      VP                  Rm^
4      4      p \&        P(                  ! V 4       \-        V Rn4       \        P                  RV RoV R Rp24       R# VRq8X  dP   \5        ^4       F%  p\-        V Rr4       \&        P(                  ! Rs4       K'  	  \        P                  RV Rt24       R# VRu8X  dP   \5        ^4       F%  p\-        V Rv4       \&        P(                  ! Rs4       K'  	  \        P                  RV Rw24       R# VRx8X  di   \.        P0                  ! V RM7       \.        P2                  ! V 4      p\6        P8                  ! RNROVRPRQRRRS.R4^RT7       \        P                  RV Ry24       R# VRz8X  d(   \-        V R{4       \        P                  RV R|24       R# VR}8X  d   . ROp!\        P                  ! ^^4      p". p#\5        V"4       Fa  p\        P*                  ! V!4      p$\-        V V$4       \&        P(                  ! \        P                  ! R7R84      4       V#P?                  V$4       Kc  	  \        P                  RV RV" RRPA                  V#4       24       R# VR8X  Ed   VP                  RR^4      p%VP                  RR_4      p&VP                  RR4      pVP                  RR4      p\%        R6VR,          4      p'\        RY\C        V&V&V%,
          V',          ,
          4      4      p(VVV,
          V',          ,           p\D        PF                  PI                  R4      p)V)'       go    \6        PJ                  ! RR.R4\6        PL                  \6        PL                  R7       \&        P(                  ! ^4       \D        PF                  PI                  R4      p)V)'       g   \        PQ                  RV R24       R# \S        R4      p*V*'       g   \        P                  RV R24       R#  \.        PT                  ! V V(R7       \        P                  RV RV( RV^<,          R R24       R p+\V        PX                  ! V+VV*3R4R7      P[                  4        R# VR8X  d   \_        WWW4       R# VR8X  dJ   \-        V R{4       \&        P(                  ! ^4       \-        V R~4       \        P                  RV R24       R# VPa                  R4      '       d\   ^ RI1p-VP                  RR4      p.VP                  RR;4      p/V-Pe                  V.V/R7       \        P                  RV RV. RV/ 24       R# VR8X  d   ^ RI1p-0 Rmp0V-Pf                  Pi                  4        U1U2u. uF  w  p1p2V1V09  g   K  V1V23NK  	  p3p1p2 V-Pk                  4       w  p4 pV3 U5u. uF  p5V5^ ,          V48w  g   K  V5NK  	  p6p5V6'       dH   \        P*                  ! V64      w  p1p2V-Pm                  V14       \        P                  RV RV2 24       R# R# VPa                  R4      '       do   RRRRRRRR/p7V7P                  VR4      w  p8p9V8'       dF   \        P                  ! RR:4      p\o        V8V4       \        P                  RV RV9 RVRe Rp24       R# R# VR8X  d(   \-        V R4       \        P                  RV R24       R# R#   \N         d     EL(i ; i  \N         d2   p,\        PQ                  RT RT, 24       \]        T*4        Rp,?,R# Rp,?,ii ; iu up2p1i   \N         d    Rp4 ELi ; iu up5i )z Execute a single sandman action.paramsr   r   z] [DRY] Nr2  dropu   ]   📉 Vol r!  drop_mindrop_maxz (-r   r0  cap_drop_mincap_drop_maxduration_min_s,  duration_max_si  extend_per_fightback_min_sextend_per_fightback_max_sr     )r   r   u   ]   🎚️  Vol cap z for r   zmin (+-zs per fight-back, max min)r/  r   
tick_min_s
tick_max_smute_hold_sreact_min_sreact_max_ssag_pace_min_ssag_pace_max_sr  r   )	r   r   r   r   r   r  r  r  r  r  u   ]   🪜 Ladder armed from u    (−/zs, ~z+min to mute, sag back if raised, then hold r3  Tu    ]   🔇 FULL MUTE (stays muted)r  皙?      ?Frn   ro   u   ]   💥 Audio stutter (u   × over zs, deficit=.2foverlay_controlsCursorRight
CursorLeftu   ]   📺 Overlay controls (
pause_longpause_min_spause_max_sPauseu   ]   ⏸️  LONG PAUSE (u   s — dad must act)	back_spampresses_minpresses_maxrX   adbz-sshellinputkeyevent4)capture_outputrK   u   ]   ⬅️  Back spam ×
audio_blipduration_min_msduration_max_msi  singleu   ]   💥 Audio blip (zms)
audio_fade   i  rr   u   ]   💥 Audio fade (
pause_playPlayu   ]   ⏸️  Pause r   sambilight_flickeroff_duration_su   ]   💡 Ambilight flickerchannel_nudgeChannelStepUpdelay_min_sdelay_max_sChannelStepDownu   ]   📺 Channel flip (r   rewindRewindg?u   ]   ⏪ Rewind ×3fast_forwardFastForwardu   ]   ⏩ FastForward ×3backu   ]   ⬅️  Backr   Homeu   ]   🏠 Homenavigate_randomBacku   ]   🎲 Random nav (z): u    → r.  bandwidth_min_kbpsbandwidth_max_kbpsduration_s_minduration_s_maxi  ffffff?z#/share/.ha_cache/.netmon_state.jsonpython3z/share/.ha_cache/netmon.py)start_new_sessionr   stderru/   ]   🌐 MITM not running — skipping throttleu*   ]   🌐 Throttle skipped — channel busy)rS   bandwidth_kbpsu   ]   🌐 Throttle z	kbps for r   c                     \         P                  ! V 4       \        P                  ! 4        \        P                  R \        P                  P                  4       P                  R4       R24       \        V4       R#   \        T4       i ; i)r   r   u   ]   🌐 Throttle endedN)
r8   rv   rP   throttle_remover   r)   r=   r>   r?   rE  )secslockss   &&r   _remove_throttle(execute_action.<locals>._remove_throttleT  sb    5 JJt,&668HHs8+<+<+@+@+B+K+KJ+W*XXo%pq-e4-e4s   A/A> >B)r   argsr   u   ]   🌐 Throttle error: r-  screensaveru   ]   🌙 Screensaver triggeredsound_filezknock_2.mp3r`   )r`   u   ]   🔊 Speaker: z vol=
app_switchu   ]   🔀 App switch → flicker_flicker_toilet_lightflicker_tv_lightflicker_laundry_lightflicker_kitchen_lightr  r   z Light flicker (	power_offStandbyu   ]   ⚡ POWER OFF)volume_dropr1  )audio_stutterr  )
CursorDownr  rK  CursorUp)r  r  rL  rK  Confirmrq  rm  rk  >   org.droidtv.channelsorg.droidtv.settingscom.google.android.katnissorg.droidtv.contentexplorercom.google.android.tvlauncher$com.google.android.apps.tv.launcherx)z-switch.magic_switch_s1e_595a_kitchen_switch_2u   🚽)z%switch.magic_switch_s1e_bb55_switch_3u   💡)z-switch.magic_switch_s1e_595a_kitchen_switch_1u   🧺)zswitch.sonoff_10020b0c7c_2u   🍳)N?)8r   r=   r>   r?   r   r)   r^   rZ   r   rc   rt   ru   rw   _volume_cap_watchdogr   r   _ladder_down_watchdogrh   r   r8   rv   choicer   rP   adb_connect_adb_targetrs   
subprocessrunrz   r~   r?  r'   rU   r"   r#   existsPopenDEVNULLrF   r  rB  throttle_applyr   r   r   rE  _execute_youtube_ad_break
startswithtvmodspeaker_play_sound	APP_NAMESr  adb_get_current_appadb_launch_app_ha_switch_flicker):rS   r6  r  r.  r/  r2   r1  r%  volnew_volr2  capdurationr   	max_totalr   iminimaxholdetadur_mindur_maxelapsedcyclest1t2
key_choicepause_spressesr   rx   durr   nav_keyscount
desc_partsr   bw_minbw_maxdeficit_factorbwmitm_runningr@  r  r  _tc
sound_filer`   
_skip_pkgsr   r%  appspkg_nowr;   
other_appsflicker_mapentitylabels:   &&&&&&                                                    r   execute_actionr    s   ^^Hb)F((4A3qc+/0n$?!S::fa#889Gu&HHs1#]3%s7)<= 
 
:	:?>>&**Z";VZZ
TU=VWD!SZ(Gu&HHs1#]3%s7)3tfAFG	  
	$?>>&**^Q"?"(**^R"@BDa$C~~fjj1A3&G&,jj1A3&GIH"JJ'CRH"JJ'CSIKL

=$7Iu" $$U2>1: % < HHs1#23%s3%uXb[QTDU V&q/#.aQ/D E%bL-T3 4 " 
	%? %%'::fa(D::lB/D::lB/D::mT2D!%%tD **]A6 **]B7#ZZ(8!<#ZZ(8"=#ZZ= & ? Q%4$;!*;<tCCHHs1#8U4&$sSTUYZ]T^ _S	!LTRTWUXMY]_ ` $ 
	#3qc9:;	?	?**-r2**-r2g/3sG3DDD UD!S)BJJrNUE"S)BJJrNrBwGaKF3qc1&'#kZabeYffghi	*	*]]#Z[
uj!

3ufmm\=$ABC3qc4ZLBC		$..M2!>

=Z\@]^ug3qc1'#>QRS 
	#..M1!=vzz-YZ?[\%(''.wANNE4':sS*.;JJsO   	3qc1';<		$nnVZZ(93?L]_bAcdUC*3qc.se378		$nnVZZ(93?L]_cAdeUC(3qc.se378		$..M1!=vzz-YZ?[\ug

7uf3qc+GC=:;	+	+

+;Q ?@3qc345		'uo&vzz-;VZZWY=Z[

5u'(3qc0s2>?		 qA5(#JJsO  	3qc+,-		&qA5-(JJsO  	3qc012		%(''.tVWgz3O&*A	73qc)*+		uf3qc'(	)	)5q"%
uA}}X.H5(#JJv~~c3/0h'	 
 	3qc.ugSj9Q8RST	
	"0#60$7**-s3**-t4S'C-0c&FVO~#EEFGg/>AA ww~~&KL  )-I!J37(2(:(::CUCUW 

1!ww~~.ST KK#aS OPQ'
3G3qc!KLM/--E"MHHs1#%7t9Xb[QTDUUXYZ5 $$,<+3W*=,0227%'
 
*	*!%<		%uf

1uf3qc789				)	)ZZ6
Hc*z&93qc+J<uVHEF		$

 .1]]-@-@-B *-B	Tj( d-B *	335MGQ "&9A1aa
9j1ICs#HHs1#5dV<= 
 
		
	+	+"$] Q#%^#%K	
 $[A~~dC0Hvx0HHs1#T%(8#bIJ 
 
	#ui 3qc*+, 
$c  0 ! /KK#aS(A! EF%g../>*
  	G	9sV   !A-}(  A+}: ~9!~9-~? (}76}7:~6&~11~6?c                0    V ^8  d   QhR\         R\        /# )r   	entity_idr  r3   )r   s   "r   r   r     s     6 6# 6 6r   c                2    \         P                  ! W4       R# )zFToggle a HA switch for `duration` seconds then restore original state.N)rP   ha_switch_flicker)r  r  s   &&r   r  r    s      5r   c                $    V ^8  d   QhR\         /# r  rG  )r   s   "r   r   r     s     ' '$ 'r   c                      \          F8  p  \        V 4      ;_uu_ 4       p\        P                  ! V4      uuR R R 4       u # 	  R. R. /#   + '       g   i     KS  ; i  \        \        P
                  3 d     Kt  i ; i)Nadsdead_ids)_AD_CATALOG_PATHSrA   rD   rJ  r  r  )prH   s     r   _load_ad_catalogr    se    	aAyy|   2z2&&	 !4#7#78 		s-   AA	
A	AAAA<;A<c                (    V ^8  d   QhR\         RR/# )r   catrL   Nr  )r   s   "r   r   r     s      $ 4 r   c           	        \          F  p\        P                  P                  V4      '       g   K*   VR,           p\	        VR4      ;_uu_ 4       p\
        P                  ! W^R7       RRR4       \        P                  ! W!4        R# 	  R#   + '       g   i     L-; i  \         d%   p\        P                  RT 24        Rp? R# Rp?ii ; i)zFPersist catalog (used to mark dead IDs from runtime cycle-on-failure).r  r\   r  Nz  ad catalog save failed: )r  r"   r#   r  rA   rD   r  r  OSErrorr   r  )r  r  r  rH   r  s   &    r   _save_ad_catalogr    s    77>>!&j#s^^qIIcQ/ $

3"  $^  8<=s/   B B(B BB  C+C

Cc                H    V ^8  d   QhR\         R\        R\        R\         /# )r   r2   r  r  rL   )r4   rU   )r   s   "r   r   r     s.     C C% C CC CE Cr   c                    \        R\        RV 4      4      pVR,
          R,          pV^ 8:  g   V^ 8:  g   W!8X  d   \        V4      # \        V4      \        V4      \        V4      ,          V,          ,          # )un   Inter-ad interval as a function of deficit.
Exponential decay: max_s at deficit=0.25 → min_s at deficit=1.0.r   r  g      ?)r   r   r4   )r2   r  r  dratios   &&&  r   _ad_interval_secondsr    sa     	D#c7#$AXEzUaZ5>U|<E%L5<7EABBr   c                0    V ^8  d   QhR\         R\         /# )r   defaultrL   rk  )r   s   "r   r   r     s     
 
5 
5 
r   c           	     4   R FQ  p \        V4      ;_uu_ 4       p\        \        P                  ! V4      P	                  RV 4      4      uuRRR4       u # 	  V #   + '       g   i     Kh  ; i  \
        \        P                  \        \        3 d     K  i ; i)z{Read the live deficit from the runtime state file. Used by the
long-running ad campaign thread to react to deficit changes.r2   N)r  r  )	rA   r4   rD   rJ  r   r  r  	TypeErrorr   )r  r#   rH   s   &  r   _read_current_deficitr    ss    7	dqTYYq\--iAB 7 N	 !4#7#7JO 		s.   A./A	
A.A+%A.+A..%BBc          
      L    V ^8  d   QhR\         R\        R\        R\         RR/# )r   rS   r1  r2   r%  rL   N)r   rG  r4   )r   s   "r   r   r     s7     D7 D7S D7$ D7 D73 D7SW D7r   c                `  aaaaa	a
aaaaa \        R4      o	S	'       g   \        P                  RS R24       R# VP                  R^4      oVP                  R^d4      pVP                  R^4      pVP                  R^4      oVP                  R	^Z4      oVP                  R
^
4      oVP                  RR4      oVP                  RR4      oVP                  RR4      oVP                  R^ 4      ;'       g    ^ o
VVV	VV
VVVVVV3R lp\        P
                  ! VRR7      P                  4        R# )ub  Spawn a background thread that runs an ongoing ad-insertion campaign
via YouTube Lounge.

Each iteration: re-capture dad's currently-playing video, inject one boring
real ad, then resume the original video at the same timestamp. Sleep for an
interval that *shrinks* as deficit rises (5 min @ 0.25 → 10 s @ 1.00).

Loop exits when: deficit drops below `min_deficit_continue`, no video is
playing, max campaign duration elapsed, or `force_n_ads` cap reached.

The yt_lounge channel lock is held for the entire campaign — this prevents
the main loop from re-selecting youtube_ad_break while the campaign runs.r-  r   u>   ]   📺 Campaign skipped — yt_lounge busy (already running)Nmax_retry_per_adbuffering_pulse_kbpsbuffering_pulse_sad_length_floor_sad_length_ceil_sinterval_min_sinterval_max_sr8  max_campaign_duration_sr<  min_deficit_continuerG  force_n_adsc                  p  < R p  \        4       pVP                  R. 4       Uu. uFC  pVP                  R4      '       g   K  SVP                  R^4      u;8:  d
   S8:  g   K=  M KA  VNKE  	  ppV'       g(   \        P                  RS$ R24        \	        S4       R # \
        P                  ! R4      '       g(   \        P                  RS$ R24        \	        S4       R #  \
        P                  ! RR7      p V P                  4        \        P                  RS$ R
SR RS RS  RS!^<,          R R24       \        P                  ! 4       p\        4       p^ p^ p T^,          p\        P                  ! 4       T,
          S!8  d*   \        P                  RS$ RT RS!^<,          R R24       EMT^8X  d   SM\        SR7      pT^8  d-   TS#8  d&   \        P                  RS$ RTR RS# RT R2	4       EMnS'       d&   TS8  d   \        P                  RS$ RS R24       EMAT P                  4       p	T P                  '       d   \        P                  RS$ R24       \        P                  ! 4       ^<,           p
\        P                  ! 4       T
8  d'   T P                  '       d   T P!                  RR7       K@  T P                  '       d   \        P                  RS$ R24       M)\        P                  RS$ R24       T P                  4       p	T P"                  ;'       g    T	P                  R 4      p \%        T	P                  R!4      ;'       g    ^ 4      pT Uu. uF  q"R,          T9  g   K  TNK  	  ppT'       g   \        P                  RS$ R#24       EM\*        P,                  ! T4       R pTR S"  F  p T P/                  TR,          R"R$7      pT'       g   \        P                  RS$ R(24       T P!                  R)R7      p\5        TP                  R*R+4      4      R,8X  dQ   \        P                  RS$ R-TR,           R.TP                  R/R04       R124       TP3                  TR,          4       K  Tp M	  T'       g   \        P                  RS$ R2T R324       EM\7        S\9        S\;        TP                  R^4      4      4      4      pT'       d   R4T R5TR R62MR7p\        P                  RS$ R8T^,            R'TP                  R/R04       R.TR,           R9T R:T 24       \        P<                  ! T4       T'       d:    T P/                  TTR$7      pT'       g   \        P                  RS$ R;T R<24       M\        P                  RS$ R>24       T^,          pS'       d(   TS8  d!   \        P                  RS$ RS R?T R@24       M\        TR7      p\?        TS S4      p\        P                  RS$ RATR RBTR RC24       ^ p^pTT8  g   EK  \        P<                  ! \9        TTT,
          4      4       TT,          p\        TR7      S#8  g   KM  EK
  T'       dP   \        4       p\        TP                  RD. 4      4      pTPA                  T4       \C        T4      TRD&   \E        T4       \	        S4       R # u upi   \
        P                   d)    \        P                  RS$ R	24        \	        S4       R # i ; i  \&        \(        3 d    R"p ELi ; iu upi   \
        P                   d*    \        P                  RS$ R%24         \	        S4       R # \0         dJ   p\        P                  RS$ R&TR,           R'T 24       TP3                  TR,          4        R p?EK  R p?ii ; i  \0         d'   p\        P                  RS$ R=T 24        R p?ELMR p?ii ; i  \0         d)   p\        P                  RS$ RET 2RRF7        R p?ELfR p?ii ; i  \	        S4       i ; i)GNr  idlength_sr   u+   ]   📺 Campaign aborted — empty catalogphilipsu<   ]   📺 No lounge token — pair via /pair first. Aborting.)screen_nameu8   ]   📺 Token rejected — re-pair via /pair. Aborting.u!   ]   📺 Campaign START (deficit=rI  z, interval r=  zs, max r   r>  Tu0   ]   📺 Campaign END — max duration reached (z ads in r  u-   ]   📺 Campaign END — deficit dropped to z (< z); z ads playedu&   ]   📺 Campaign END — force_n_ads=z reachedu4   ]   📺 Native ad in progress — waiting up to 60srY  rO   u:   ]   📺 Native ad still going after 60s; injecting anywayu*   ]   📺 Native ad ended; refreshing statevideoIdcurrentTimer]  u+   ]   📺 Catalog exhausted; ending campaign)current_timeu0   ]   📺 Token rotated mid-campaign — abortingu   ]   📺 setPlaylist r   uF   ]   📺 setPlaylist sent (no state echo — may have rendered anyway)g      @r  r  1010u   ]   📺 Ad r  brandr  z) unavailable; cyclingu   ]   📺 Iter z!: no playable ad; ending campaignzorig z @ rb  z%orig=? (receiver not reporting state)u   ]   📺 ▶ ad #, u   s) — u   ]   📺 Resume u0    silent — link may have gone cold mid-campaignu   ]   📺 Resume failed: u9   ]   📺 Skipping resume — no original videoId capturedz
 reached (z ads played)u!   ]   📺 ◀ resumed; next ad in zs (deficit=r   r  u   ]   📺 Campaign crashed: )exc_info)#r  r   r   r  rE  rP   lounge_get_tokenLoungeSessionconnectLoungeAuthErrorr)   r8   r   r  request_now_playing	ad_active
poll_statecontent_video_idr4   r  r   rt   shuffleset_playlist_verifiedrF   addr   r   r   rU   rv   r  updatesortedr  )%sessr  r;   base_adscampaign_startdead_ids_now	iteration
ads_playedcur_deficitr  wait_deadlineoriginal_idoriginal_pospoolpickedcandlandedr  pollr  
orig_descrresumedr&  sleptr   pendingad_ceilad_floorr@  r2   r  r  r  max_campaign_s	max_retryr  r%  s%                             r   r   '_execute_youtube_ad_break.<locals>._run  s   c	'"$C#&775"#5 Y#5a55; +3quuZ7L+WPW+W +W #5H Yc!$OPQz g&w ..y99c!$`abr g&o!//IF
 HHs1# !!([8H.IY Z*2-c2$8 9 "YY[N5LIJQ	99;/.@HHs1# &!!+H^B5Fs4K4Q R *3ag=R[b=cq=[3G%GHHs1#%RS^_bRc d##7"8J<{T U:#<HHs1#%KK=X`ab 002 >>>HHs1#%YZ[$(IIK"$4M))+5$...4~~~3qc)c!de3qc)S!TU $ 8 8 : #33KKuyy7K'#(=)A)F)FQ#GL
 $,K8awl/J8KKK#aS(S TUt$ ),D!!%!;!;DJUX!;!Y " 3qc *= "> ???3?7D488GR01V;c!LdBtxxPWX[G\F] ^; %< =$((d4 !F/ -0 KK#aSykAb cdxWc&**ZQS:T6U)VWMXk]#l35GqI#J 3qc!2:a<.6::gVYCZB[ \#D\N"XJgj\K L

8$J"&"<"<[JV #= #X&KK#aS0@ NO )P Q
 HHs1#%^_`a
 :#<HHs1#%KK= Y!!+L: ; 4KH*;W3qc!B3s) L%%0$5Q8 9 ckJJs4u56TME,[ADXX &(cggj"56|,"(/J % g&CY -- c!$\]^d g&ix ":. '#&L' L &55 c!,\$]^R g&Q % !c!,A$t*RPQs$ST$((d4 !H % Jc!,DQC$HIIJB  	PKK#aS ;A3?$KOO	P g&so  _2 [["[&[,#_2 _2 9_2 !'[ C4_2 =A_2 A _2 %%_2 A_2 !_2 4\ \ _2 \&0\&6A_2 <\+_2 B(_2 C_2 ^> '^> *_2 .A)_2 >_2  
_2 +A_2 _2 .\	:_2 \		_2 \#_2 "\##_2 +.^;_2 (^;1^;2=^6/_2 6^;;_2 >_/	_*$_2 *_//_2 2`%=` `(  `%%`( (`5Tr   )rB  r   r)   r   r   r   r   )rS   r1  r2   r%  
pulse_kbpspulse_sr   r%  r&  r@  r  r  r  r'  r(  r  s   &&ff   @@@@@@@@@r   r  r    s      23G3qcWXY

-q1I2C8Jjj,a0Gzz-q1Hjj+R0GZZ 0"5NZZ 0#6NZZ 94@N!::&<dC**]A.33!Ke' e'N D.446r   c                `    V ^8  d   QhR\         R\         R\        R\        R\        R\        /# )r   	tv_ip_argconfig_pathr.  inject_deficitrQ  rR  )r   ra   r4   )r   s   "r   r   r     sA     q qc q qd q q05qHLqr   c                ,   a=a> \        V4      pT pVP                  R/ 4      p\        WdVR7      o>Ve+   S>P                  V4       \        P                  RVR 24       \        V4      p	VR8X  g   VfV   \        P                  R4       \        4       pV'       d   \        P                  RV 24       M\        P                  R	4       \        P                  R
4       \        P                  RT;'       g    R 24       \        P                  RS>P                  4       R 24       \        P                  RV R24       \        P                  RV'       d   RMR 24       \        P                  RVR,          R,           RVR,          R,           R24       \        P                  R\        VR,          4       R\        V	4       R24       V	'       d(   \        P                  RRP                  V	4       24       \        P                  R VP                  R!^ 4      R" 24       VP                  R#^ 4      p
\        P                  R$V
^ 8X  d   R%MT
 24       \        P                  R&V 24       RnV>3R' llp\        P                  ! \        P                  V4       \        P                  ! \        P                  V4       \        P                  ! \        P                  \        P                   4       R(pRp\"        P"                  P%                  4       p\"        P"                  P%                  4       p^ pR(p/ p. RoOp\'        V>3R) lV 4       ^ R*7      pTpS>P(                  ea   V'       d   \+        VR+4      MRpV'       d   VP                  R+4      R,8w  d-   \        P                  R-4       RS>n        S>P-                  4         \"        P"                  P%                  4       pVV,
          P/                  4       R/,          pTpS>P1                  4         \2        P4                  P7                  T;'       g    R04      p\9        \:        R14      '       d   V\:        P<                  8w  dT   \9        \:        R14      '       d3   \        V4      pVP                  R/ 4      p\        P                  R24       V\:        n        TP@                  ^<,          TPB                  ,           pTP                  R/ 4      PE                  4        EF  w  ppTP                  R34      pT'       d   TP                  R4      '       g   K9   \G        \H        TR4,          PK                  R54      4      w  pp\G        \H        TR6,          PK                  R54      4      w  p p!T^<,          T,           p"T ^<,          T!,           p#T"T#8:  d   T"Tu;8*  ;'       d    T#8  Mu p$MTT"8  ;'       g    TT#8  p$ TP                  T4      p%T%e   T$T%8w  d   T$'       dU   \        P                  R7X& R8T R9TR4,           R:TR6,           R;2	4       \Q        T R<TR4,           R:TR6,           R;2R=4       M?\        P                  R7X& R8T R>TR4,           R:TR6,           R;2	4       \Q        T R?2R=4       T$TT&   EK  	  Te   \S        T4      fJ   \        ^R@7      p'T''       d6   T'T8w  d/   T'pTPU                  RA4      p&\        P                  R7T& RBT 24       R(p(Rp)T'       d:   \+        TR+4      p*T*'       d&   T*P                  R+4      R,8X  d   \S        T4      p)T)RJp(TPU                  RA4      p&T('       Egb   T'       dg   \        P                  R7T& RCS>P                  4       R RDS>PV                  RE RF24       \Y        T4       RS>n        RS>n-        S>P-                  4        Rp^ pR(pT^ 8  d   S>P]                  T4       S>P                  4       o=T FR  p+S=T+u;8  d
   T8:  g   K  M K  \        P                  R7T& RGT+R RHS=R R;24       \'        T=3RI lT 4       ^ R*7      pTpKT  	  TT,
          P/                  4       RJ8  d=   S>P-                  4        \        P                  R7T& RKS>P                  4       R 24       Tp\^        P`                  ! ^<4       EK  T'       gR   TpTPc                  4       S>n        S>P-                  4        \        P                  R7T& RLS>P                  4       R 24       R.pT'       d   TT,
          P/                  4       R/,          M^ p,T^ 8  d$   S>Pe                  TT,4       S>Pg                  T,4       S>P                  4       o=T F  p+S=T+u;8  d   T8  d+   M M'\        P                  R7T& RMT+R RHS=R R;24       T+pT+pK<  S=T+u;8  d
   T8:  g   KM  M KQ  \        P                  R7T& RGT+R RHS=R R;24       \'        T=3RN lT 4       ^ R*7      pTpK  	  TT,
          P/                  4       ^<8  d   S>P-                  4        TpROp-\2        P4                  Pi                  T-4      '       Ed    \k        T-4      ;_uu_ 4       p.\l        Pn                  ! T.4      p/RRR4       X/P                  RP4      p0T0'       d   \"        P"                  Pq                  T04      p1TT18  d=   \2        Pr                  ! T-4       \        P                  R7T& RQ24       \Q        RRR=4       MT1T,
          p2\        Pu                  RST2Pv                   RTT2Px                  RU,           RV24       S>PZ                  '       d   RS>n-        S>P-                  4        \^        P`                  ! ^4       EK   \2        P4                  Pi                  T-4      '       d\   \        Pu                  RWS=R 24       S>PZ                  '       d   RS>n-        S>P-                  4        \^        P`                  ! ^4       EKH  S>P}                  4       w  p3p4T4'       * p5T5'       d/   T'       g'   \        P                  R7T& RX24       \Q        RYR=4       M5T5'       g.   T'       d&   \        P                  R7T& RZ24       \Q        R[R=4       T5pT5'       d]   \        Pu                  R\S=R R]24       S>PZ                  '       d   RS>n-        S>P-                  4        \^        P`                  ! ^4       E	K3  S=S>P~                  8  d@   \        Pu                  R^S=R R_S>P~                   24       \^        P`                  ! ^4       E	K  \        P                  ! 4       p6T6S=8  d   S>P                  T4      p7T\"        P                  ! T7R`7      ,           Pc                  4       p8\        P                  R7T& RaT6R RbS=R RcT7RE R2	4       S>P-                  T8Rd7       \^        P`                  ! T74       E
K,  TP                  R#^ 4      p9T9^ 8  dy   TT98  dr   \        P                  R7T& ReT9 Rf24       S>P-                  T\"        P                  ! RJR`7      ,           Pc                  4       Rd7       \^        P`                  ! RJ4       E
K  \        S=Yi4      w  p:p;T:'       dh   \        P                  R7T& RgT6R RhS=R RiT: 24       \Q        T:RjS=4       \        TT:T;TS=S=Rk7       T^,          pRp< \        T4      p<\        S=T,T:T<Rl7       S>P                  T4      p7\"        P"                  P%                  4       \"        P                  ! T7R`7      ,           Pc                  4       p8\        P                  R7T& RmT7RE R24       S>P-                  T8Rd7       \^        P`                  ! T74       EK    \>         d     E
Li ; i  \L        \N        3 d     E
K  i ; i  + '       g   i     ELN; i  \l        Pz                  \>        3 d     ELqi ; i  \>         d     ELi ; i)pzAttention-deficit-driven sandman engine. Runs continuously.

The deficit grows while the TV is on and recovers slowly while off.
Actions are selected based on deficit level, not grace periods or severity curves.
r  )rQ  rR  Nz  Injected starting deficit: rI  autoz!  Auto-discovering TV via SSDP...z  Found TV at z& TV not found -- will retry each cyclez&Sandman v5 -- attention deficit enginez   Target: zauto-discoverz   Deficit: r  z
   Speed: r  z   Golden period: r  r  z   Interval: r  r  zs - r  rb  z   Actions: r
  z configured, z disabled todayz   Disabled today: r  z   Skip probability: skip_probabilityz.0%max_actions_per_sessionz   Max actions/session: 	unlimitedz   Dry run: c                 ~   < \         P                  R 4       SP                  4        \        P                  ! ^ 4       R# )z&Sandman shutting down. Saving state...N)r   r)   r  r   exit)sigframemodels   &&r   cleanuprun_v4.<locals>.cleanup  s$    9:

r   Fc              3   T   <"   T F  qSP                  4       8:  g   K  Vx  K  	  R # 5ir  )r  ).0r  r9  s   & r   	<genexpr>run_v4.<locals>.<genexpr>  s#     U&8ARARAT<T11&8s   (
(r  r   r   z*  Clearing stale session_start (TV is off)Tr  rI  
_cfg_mtimez  Config hot-reloadedr  r   r  r  r   u   ] ⏰ z now active (r=  r   z window open (systemz now inactive (outside z window closedrO   r   z] TV at z] TV off -- deficit=z, today=r   r   u   ] ⬇ Deficit dropped below z (now c              3   8   <"   T F  qS8:  g   K  Vx  K  	  R # 5ir   r=  r  r2   s   & r   r>  r?  >  s     ,[8J1SZlQQ8J   
r8  z] Resting -- deficit=z] TV on -- deficit=u   ] ⬆ Deficit crossed c              3   8   <"   T F  qS8:  g   K  Vx  K  	  R # 5ir  rC  rD  s   & r   r>  r?  e  s     (W4Fqw,4FrE  z/share/.ha_cache/.ha_wd_paused	resume_atu(   ] ⏰ Holiday pause expired — resumingzHoliday pause expiredu   Holiday paused — zd i  zh leftzActions paused -- deficit=u/   ] 🌟 Golden period started — actions pausedzGolden period startedu.   ] 🌟 Golden period ended — actions resumedzGolden period endedzGolden period -- deficit=z, no actionszBelow threshold -- deficit=z < )secondsu   ] 🎲 Skip: roll=z > deficit=z
, next in )rd  z] Max actions (z) reached. Going quiet.u   ] 🎲 Hit: roll=z <= deficit=z | r   )r/  r2   )r  u   ] ⏱ Next check in r  )rq   ro   rH  rv  g?)GrK  r   rM  r  r   r)   r  rQ   r  r  r  r'   signalSIGINTSIGTERMSIGHUPSIG_IGNr=   r>   r   rc  r   r  r  r  r"   r#   getmtimehasattrrun_v4r@  rF   rx  r  r  r  rU   r  r   r!  rI   rZ   r?   re  r  rd  r~  r8   rv   ra  rr  r  r  rA   rD   rJ  r  remover  daysrH  r  rm  r\  rt   r  r  r,  r  r   r  )?r-  r.  r.  r/  rQ  rR  rO  rS   r  r  max_actr:  	tv_was_onsession_start_timelast_update	last_saveaction_countwas_in_goldenph_was_activeDEFICIT_THRESHOLDSlast_crossed_uplast_crossed_down
startup_pwr>   ri  	cfg_mtimecur_manamer&  r'  r(  r)  r*  r+  r  r  activewasnow_strnew_iptv_onr  pwr  rj  
pause_pathpf
pause_datarG  	resume_dtr   rx   actions_allowed	in_goldenrollr  next_atmax_actionsr6  r  r  r2   r9  s?   &&&&&&                                                       @@r   rP  rP    s    %FEJJw#E 6)DE!.)00DEF *&1N%-45HH~eW-.KK@AHH57HH{533O456HH|E--/456HHz%"#HH!	*y!IJKHH}VJ/>?tF:DVWdDeCffghiHH|Cy 123=^AT@UUdef&tyy'@&ABCHH$UYY/A1%Ec$JKLii115GHH'w!|'QRSHH|G9%&
 MM&--)
MM&..'*
MM&--0I##'')K!!%%'ILMM7U&8U_`aO' &49VE<0t
Z^^L9TAHHAB"&EJJL
##%K'6684?
 	##%		(()H)H8HII6<00IARAR4R6<00(5F"JJw3EHH45$-!
 2

*!::i4::<KE4-.BRVVI..
S"W+"3"3C"89BS"U)//#"67Br'B,R"e#$555F"g->>F  ##E*C6S=HHs7)6%bk]RSTVW\T]S^^_`a%r'{m1RYKqQS[\HHs7)6%8OPRSZP[}\]^`af^g]hhijk%7B#)M% / =4 =Ju-5 +F&E/,,z23wixw78 |,Bbff\*d2 '4,,z*u3wi';E<M<M<OPS;T U""'"="=c!B#G H  $&*#'+$

%)" IA~j) '')G'Q3"333HHs7)+G#wfU\]`Taabcd(+,[8J,[ef(g%&7O	 ( i..0C7

3wi'<U=N=N=PQT<UVW	JJrN !$"%--/EJJLHHs7)#6u7H7H7J36OPQ	 Pb3!33BBDtKgh >JJz?3OOO,##% $A!-o-3wi'=aWF7SV-WXYZ"#$%!11 1113wi'CAc7&QXY\P]]^_`$'(W4F(Wab$c!"3 $ )O**,2JJLI 6
77>>*%%*%%!%2J &&NN;7	 ( 1 1 ? ?	 JIi'		*-3wi/W!XY"#:HE$-O			$7	7Gr)J[J[]aJaIbbh"ij ///37E0!JJL

2    ww~~j))		6wsmDE'''+/E(JJL

2 #668?''	]HHs7)#RST.9}HHs7)#QRS,h7!II1'#lKL###'+$

JJrN U+++II3GC=EDZDZC[\]JJrN }}'>))&1HX//AALLNGHHs7)#5d3Z{7SV-WabjknaoopqrJJgJ.JJx  ii 91=?|{:HHs7)?;-?VWXJJsX-?-?-L'L&W&W&YJZJJsO #6gv"VZHHs7)#4T#Jl7SV-WZ[fZghi{Iw75+z7*17DAL C$U+ '?KSI %%f-$$((*X-?-?-QQ\\^3wi3HS>CD

'
*

8q  		$ ) R &%%" (()4 F  s   !~# 9B~# <B~5~5~5~5" 2	 " *A!" A" -" (A@ #~21~25
		" "A@@ A@@A@@A@c            	      F   \         P                  ! R R7      p V P                  RRRR7       V P                  RRRR	7       V P                  R
RRR7       V P                  RRRR7       V P                  R\        RRR7       V P                  R\        RRR7       V P                  RRRR	7       V P	                  4       p\        VP                  R7       \        VP                  VP                  VP                  VP                  VP                  VP                  R7       R# )z1Sandman v5 -- attention deficit TV sleep pressure)descriptionz--tvr1  z,TV IP address (or 'auto' for SSDP discovery))r  helpz	--dry-run
store_truezPrint actions only, don't send)r0   rs  z--configNz2Path to config JSON (default: sandman_config.json)z	--log-dirz1Directory for log files (default: same as script)z	--deficitz-Inject starting deficit (0.0-1.0) for testing)r:   r  rs  z--speedr  z:Time multiplier for testing (e.g. --speed 10 = 10x faster)z--no-goldenz9Disable golden period (actions allowed during 10-10:30pm))r	   )r-  r.  r.  r/  rQ  rR  )argparseArgumentParseradd_argumentr4   
parse_argsr.   r	   rP  tvrO  r.  r2   rQ  rR  )r  r  s     r   mainrz    s    ,_`ANN660^N_NN;|:ZN[NN:t2fNgNN;3fNgNN;UDG  INN95#T  VNN=S  U<<>D$,,'
TWW$++t||,,djjDNNTr   c                    Rp \         P                  ! 4       p \         P                  P                  V 4      '       dN   \	        \        V 4      P                  4       P                  4       4      pW!8w  d   \         P                  ! V^	4       \        T R4      ;_uu_ 4       pTP                  \        T4      4       RRR4       R#   \        \        3 d     LLi ; i  + '       g   i     R# ; i)zEKill any other sandman.py processes before starting. Uses a PID file.z/share/.ha_cache/.ha_wd.pidr\   N)r"   getpidr#   r  rU   rA   readstripkillProcessLookupErrorr   rC   r   )pidfilemy_pidold_pidrH   s       r   _ensure_single_instancer    s    +GYY[F77>>'""$w-,,.4467G # 
gs		q	F 
	 
+  
		s#   $C AC CCCC,	__main__c                    V ^8  d   Qh/ ^ \         9   d%   \        \        \        P                  3,          ;R&   ^\         9   d&   \        \        \
        \        ,          3,          ;R&   ^\         9   d
   \
        ;R&   # )r   r  r8  r  )__conditional_annotations__rG  r   r   r   r=  )r   s   "r   r   r      sk     
 
~ tC'( 
R $sDI~& S
F  T G
r   r  )r   N)r   )r_  r]  )g      @)FrH  rH  )zyoutube_ad_catalog.jsonzwd_ad_catalog.json)r]  )r1  NFNr  F)Kr  r   ru  r=   rD   r   logging.handlersrn  r"   rt   rerI  r  r   r   r8   urllib.requesturllibr  rP   	getLoggerr   r   r   _default_handlerr    r   r!   r   r   r.   rB   rI   rQ   rZ   r]   r^   rc   rh   rz   r~   r   r   r   r   r  r   r  r   r  r8  r;  rB  rE  rK  rM  r  r  r  r  r  r,  r  r  r  r  r  r  r  r  rP  rz  r  r   r   )r  s   @r   <module>r     s:  
       	  	   
     	" |||,,.!!'"3"31##  NN#$LL:: 5 3+ 43
/88114Z2 Z2| )* f$ f$T +,  )..")..")..",  *  [- [-@ 1  6$3R"#=LV-r6 F 'C
D7Nqh	T&" zF r   