
    i]                        U d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddlZddlZddlZddlZ ej&                  d      ZdBdefdZdZdCdeded	efd
ZdDdededz  fdZdededz  fdZdZd ZdededefdZ dededefdZ!dEdededefdZ"dFdedefdZ#dededefdZ$dedefdZ%dedefdZ& G d  d!      Z' e'       Z(dBd"ede)fd#Z* G d$ d%      Z+d&Z,g Z-e.e/d'<   dBd	ed(ed)ed*efd+Z0d,ej                  fd-Z1d.e)de2fd/Z3d	ed.e)d0e2de4fd1Z5dGded)ed2e)d3ed4ed	efd5Z6d6ed7efd8Z7	 	 dHd9ed:ed3ed;ed<ed=efd>Z8d? Z9d@ Z:e;dAk(  r e:         e9        yy)Iu  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log_dirc                 `   t         j                  j                          t         j                  t        j
                         dt         _        t	        j                  dd      }t	        j                  t        j                        }|j                  t        j                         |j                  |       t         j                  |       | @t        j                  j!                  t        j                  j#                  t$                    } t        j                  j'                  | d      }t        j                  j)                  |ddd	
      }|j                  t        j
                         |j                  |       t         j                  |       t         j+                  d| d       y)z6Configure logging with console + rotating file output.Fz'%(asctime)s [%(levelname)s] %(message)sz%Y-%m-%d %H:%M:%S)datefmtNzsandman.logi  P    zutf-8)maxBytesbackupCountencodingzLogging to z (5MB x 3 rotation))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        /share/.ha_cache/ha_watchdog.pysetup_loggingr&   %   s   LLLLCM


1#C 
		szz	*BKKOOCNN2 ''//"''//(";<ww||G]3H				-	-?G 
. 
B KKOOCNN2HH{8*$789    z$/share/.ha_cache/.ha_wd_events.jsonlaction
event_typedeficitc                 d   t         j                   j                         }|j                  d      |j                  d      | |d}|t        |d      |d<   	 t	        t
        d      5 }|j                  t        j                  |      dz          ddd       y# 1 sw Y   yxY w# t        $ r Y yw xY w)	z:Append a structured event to the shared events JSONL file.%Y-%m-%d%H:%M)datetimer(   typeNr   r*   a
)
datetimenowstrftimeroundopenEVENTS_FILEwritejsondumps	Exception)r(   r)   r*   r4   entryfs         r%   
_log_eventr?   E   s    





!C\\*-s||G7Lz3E !,i+s#qGGDJJu%,- $## s0   B# &(BB# B B#  B# #	B/.B/timeoutreturnc                 .    t        j                  |       S )Nr@   )
tv_controldiscover_tvrC   s    r%   rE   rE   U   s    !!'22r'   tv_ipc                 .    t        j                  |       S Nip)rD   
get_volume)rF   s    r%   rK   rK   ]   s      E**r'   z!/share/.ha_cache/.ha_wd_action_tsc                      	 t        t        d      5 } | j                  t        t	        j                                      d d d        y # 1 sw Y   y xY w# t
        $ r Y y w xY w)Nw)r7   ACTION_MARKERr9   strr/   r<   )r>   s    r%   _touch_action_markerrP   c   sG    -%GGC		$% &%% s-   A -AA AA A 	A A volumec                 D    t                t        j                  ||       S rH   )rP   rD   
set_volume)rF   rQ   s     r%   rS   rS   j   s      E22r'   mutec                 D    t                t        j                  ||       S rH   )rP   rD   set_mute)rF   rT   s     r%   rV   rV   o   s    t..r'   duration_msstylec                    |dk(  rt        t        j                  dd            D ]l  }t        | d       t	        j
                  t        j                  dd             t        | d       t	        j
                  t        j                  dd	             n y|d
k(  r?t        |       }|1t        | d       t	        j
                  |dz         t        | |       yyt        | d       t	        j
                  |dz         t        | d       y)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)
    stutterr      T皙?333333?Fg?333333?fadeNi  )	rangerandomrandintrV   r/   sleepuniformrK   rS   )rF   rW   rX   _origs        r%   audio_glitchrg   v   s     	v~~a+,AUD!JJv~~c3/0UE"JJv~~dD12	 -
 
&% ua JJ{T)*ud#  	

;%&r'   off_durationc                 2    t        j                  ||        y)z1Toggle Ambilight off then on via menu navigation.rI   N)rD   ambilight_glitch)rF   rh   s     r%   rj   rj      s    7r'   key_namec                 0    t        j                  ||       S )z Send a key press via JointSpace.rI   )rD   js_key)rF   rk   s     r%   rm   rm          X%00r'   endpointc                 0    t        j                  ||       S )zGET a JointSpace endpoint.rI   )rD   js_get)rF   ro   s     r%   rq   rq      rn   r'   c                    t        | d      }|r|j                  d      dk7  ryt        | d      }|sy|j                  di       j                  dd	      }d
|j                         v ry
d|j                         v ryd|j                         v sd|j                         v ryd|j                         v sd|j                         v ryd|j                         v s|d	k(  ryy)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)rq   getlower)rF   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                   J    e Zd ZdZddefdZ	 ddededededef
d	Z	d
 Z
d Zy)VolumeCapWatchdogu1  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.correction_delayc                     d | _         d | _        d | _        d | _        d | _        d| _        || _        t        j                         | _	        t        j                         | _        d | _        d| _        y )Ng     V@g     f@r   )rF   target_volume
expires_atarmed_atmax_expires_atextend_ranger   	threadingLock_lockEvent_stop_threadfight_backs)selfr   s     r%   __init__zVolumeCapWatchdog.__init__   sb    
!") 0^^%
__&
r'   rF   target
duration_sr   max_total_sc                    t        j                          }| j                  5  || _        ||z   }||z   }| j                  q| j                  re| j                  |kD  rVt        | j                  |      | _        t        | j                  |      | _        t        | j                  xs ||      | _        n#|| _        || _        || _        || _        d| _	        || _
        d d d        | j                  | j                  j                         s[| j                  j                          t        j                   | j"                  d      | _        | j                  j%                          y y # 1 sw Y   xY w)Nr   T)r   daemon)r/   r   rF   r   r   minmaxr   r   r   r   r   is_aliver   r   r   Thread_runstart)	r   rF   r   r   r   r   r4   
new_expirynew_maxs	            r%   armzVolumeCapWatchdog.arm   s!   iikZZDJz)JK'G"".4??#-%(););V%D""%dooz"B&)$*=*=*H'&R#%+"", #&-##$  ,D   <<t||'<'<'>JJ$++499TJDLLL  (?! Zs   B9E$$E-c                     | j                   j                          | j                  r| j                  j                  d       y y )Nr   rC   )r   setr   r   r   s    r%   stopzVolumeCapWatchdog.stop   s1    

<<LLa( r'   c                 D   | j                   j                         sR| j                  5  | j                  }| j                  }| j
                  }d d d        t        j                         |k\  r| j                  5  | j                  Qt        j                  dt        j                  j                         j                  d       d| j                   d       d | _        d | _        d | _        d d d        y t              }|||dz   kD  r| xj                  dz  c_        t        j                  j                         j                  d      }| j                  5  t        j                   | j"                   }| j                  |z   }| j                  t%        || j                        }|| j                  z
  }|| _        |t        j                         z
  }	d d d        t        j                  d| d| j                   d| d| d	d
d	dz  dd       t        j                   | j&                   }
| j                   j)                  |
       | j                   j                         ry | j                  5  | j                  }| j                  }d d d        t        j                         |k\  ry |t        j*                  dd      z   }t-        ||       t        j                  dt        j                  j                         j                  d       d| d|
d
d       | j                   j)                  t        j                   dd             | j                   j                         sQy y # 1 sw Y   $xY w# 1 sw Y   y xY w# 1 sw Y   xY w# 1 sw Y   	xY w)N  [%H:%M:%Su-   ] 🎚️  Volume cap released (fight-backs: )   u   ] 👴 Fight-back #z: z > cap z (+.0fu   s → <   .1fz	min left)r      u   ]    ↩️  Drifted to z (after s)r      )r   is_setr   r   r   rF   r/   r   r    r3   r4   r5   r   r   rK   ra   rd   r   r   r   waitrb   rS   )r   r   expiresrF   currentnow_sextendr   extended_by	remainingdelay
cur_target
cur_expiry	correcteds                 r%   r   zVolumeCapWatchdog._run   s5   **##%++//

  ~DIIK74JZZ))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 ZZ#^^T->->?F!%6!9J**6%(T5H5H%I
",t">K&0DO *TYY[ 8I   3ug%89I9I8J"WIU\]c\d e)#.fYr\#4FiQ R(=(=>

&::$$&ZZ!%!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  Zs1   %M/<A3M<A;N5N/M9<NNNN))      )r   g      @)__name__
__module____qualname____doc__tupler   rO   intfloatr   r   r    r'   r%   r   r      sO    F
  GM! !c !u !!>C!0)
*2r'   r   r   c                 .   | ^t         j                  j                  t         j                  j                  t         j                  j	                  t
                    d      } t        |       5 }t        j                  |      cddd       S # 1 sw Y   yxY w)z#Load sandman config from JSON file.Nwd_config.json)	r   r   r   r   r   r   r7   r:   load)r   r>   s     r%   load_configr     sT    |ww||BGGOOBGGOOH,EFHXY	dqyy| 
s   ,BBc            	           e Zd ZdZ	 	 ddedededefdZdedefd	Z	defd
Z
defdZdefdZdefdZd ZddefdZd ZdedefdZdedefdZdefdZy)AttentionModelaP  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.
    config
state_filespeed	no_goldenc                    |j                  di       | _        || _        || _        || _        || _        | j                  j                  dd      | _        | j                  j                  dd      | _        | j                  j                  dd      | _        | j                  j                  dd	d
g      | _	        | j                  j                  dg       | _
        | j                  j                  dd      | _        d| _        t        j                  j                         j                         | _        d | _        d | _        d| _        t        j                  j                         j)                  d      | _        | j-                          y )Nattention_modelbase_impact_rateg~jth?recovery_rategMb@?sleep_recovery_multiplierg       @sleep_hoursr      time_multipliersaction_thresholdr^           r,   )r   r   r   r   
_cli_speedr   	base_rater   
sleep_multr   r   r   r*   r3   r4   	isoformatlast_updatedsession_startnext_action_attotal_watch_today_minr5   _today_dater   )r   r   r   r   r   s        r%   r   zAttentionModel.__init__1  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'   
dt_minutessession_minutesc                 .   || j                   z  }| j                         \  }}| j                  t        j                  d|dz  z         z  |z  }| xj
                  ||z  z  c_        t        | j
                  d      | _        | xj                  |z  c_        y)a   Increase 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.
        r   g      >@      ?N)r   get_time_multiplierr   mathr   r*   r   r   )r   r   r   dt	rate_multre   impacts          r%   growzAttentionModel.growK  s     $**$//1	1$((1/E+E"FFR#4<<-""j0"r'   c                    |t        | d| j                        z  }t        j                  j                         }| j                  \  }}||k  r||j
                  cxk  xr |k  nc }n |j
                  |k\  xs |j
                  |k  }|r| j                  nd}| xj                  t        j                  | j                   |z  |z        z  c_        | j                  dk  rd| _        yy)zzDecrease deficit while TV is off via exponential decay.

        Sleep hours (midnight-7am) get boosted recovery.
        recovery_speedr   gMbP?r   N)getattrr   r3   r4   r   hourr   r*   r   expr   )r   r   r   r4   sleep_start	sleep_endis_sleep	rest_mults           r%   recoverzAttentionModel.recoverY  s    
 '$(8$**EE##%!%!1!1Y)#"chh::Hxx;.F#((Y2FH'/DOOS	$"4"4!4r!9I!EFF<<%DL  r'   rA   c                    t         j                   j                         }|j                  dz  |j                  z   }| j                  D ]  }t        t        |d   j                  d            \  }}t        t        |d   j                  d            \  }}|dz  |z   }|dz  |z   }	|	|k  r||k\  xs ||	k  }
n||cxk  xr |	k  nc }
|
s|j                  dd      }| j                  r|r y|d   | fc S  y)	zReturns (rate_multiplier, actions_allowed) based on current time.

        During golden period: returns (0.3, False).
        r   r   :end
no_actionsF)r   Trate)
r3   r4   r   minuter   mapr   splitr   r   )r   r4   now_minsslotstart_hstart_mend_hend_m
start_minsend_minsin_ranger  s               r%   r   z"AttentionModel.get_time_multiplierl  s    
 ##%88b=3::-))D"3W(;(;C(@AGWsDK$5$5c$:;LE5 2/JrzE)H :%#z1HX5H%<H<!XXlE:
>>j&V*n55# *( r'   c                     | j                   S )z"Returns current deficit float 0-1.)r*   r   s    r%   get_deficitzAttentionModel.get_deficit  s    ||r'   valuec                 :    t        dt        d|            | _        y)z/Set deficit directly (for test mode injection).r   r   N)r   r   r*   )r   r  s     r%   set_deficitzAttentionModel.set_deficit  s    3C0r'   c           	         	 t        | j                        5 }t        j                  |      }ddd       j	                  d| j
                        }t        || j
                  z
        dkD  rU| j
                  |}}|| _        | j                  }||k\  ||k\  k7  r)||k\  rdnd}t        j                  d|dd| d	| d
       	 t        d      5 }t        j                  |      }ddd       j	                  dd      }	|j	                  d|	      }
|	| j                  k7  r-t        j                  d| j                   d|	 d       |	| _        |
t        | d| j                        k7  r9t        j                  dt        | d| j                         d|
 d       |
| _        yy# 1 sw Y   txY w# t        $ r Y w xY w# 1 sw Y   xY w# t        $ r/ | j                  | j                  k7  r| j                  | _        Y yY yt        $ r Y yw xY w)zNCheck if state or speed files were modified externally (e.g. by Telegram bot).Nr*   {Gz?abovebelowu     Deficit → .3fz (z threshold r   z"/share/.ha_cache/.ha_wd_speed.jsonr   r   r   z  Speed changed: u   x → xz  Recovery speed changed: )r7   r   r:   r   r   r*   absr   r   r    r<   r   r   r   FileNotFoundErrorr   )r   r>   statefile_deficitold_dnew_d	threshold	direction
speed_data	new_speednew_recoverys              r%   check_external_updatez$AttentionModel.check_external_update  s   	doo&!		! ' 99Y=L<$,,./$6#||\u+ 11	Y&EY,>?+0I+=7IHH~eC[9+[QZP[[\]^	:;q!YYq\
 <"w4I%>>*:IFLDJJ&,TZZLykKL&
wt-=tzzJJ5gdDTVZV`V`6a5bbhiuhvvwxy&2# K- '&  		 <; ! 	-zzT__,!__
 - 		sY   F, FBF, G F;#B:G F)$F, ,	F87F8;G G 3H	>H	H	Nr   c                 6   ||| _         t        | j                  d      t        j                  j	                         j                         | j                  | j                   t        | j                  d      | j                  d}	 | j                  dz   }t        |d      5 }t        j                  ||d       ddd       t        j                  || j                         y# 1 sw Y   *xY w# t        $ r"}t         j#                  d	|        Y d}~yd}~ww xY w)
zPersist state to JSON file.Nr   r   )r*   r   r   r   r   
today_datez.tmprM   r   )indentz  Failed to save state: )r   r6   r*   r3   r4   r   r   r   r   r   r7   r:   dumpr   replacer<   r   warning)r   r   r  tmpr>   es         r%   savezAttentionModel.save  s    %"0DT\\1-$--113==?!//"11%*4+E+Eq%I**
	8//F*Cc31		%1-  JJsDOO,    	8KK21#677	8s0   C- C!8(C- !C*&C- -	D6DDc                    	 t        | j                        5 }t        j                  |      }ddd       j	                  dd      | _        |j	                  dt        j                  j                         j                               | _	        |j	                  dd      | _
        |j	                  dd      }t        j                  j                         j                  d      }||k(  r|j	                  d	d      | _        nd| _        || _        	 t        j                  j                  | j                        }t        j                  j                         |z
  j                         d
z  }|dkD  rD| j                  8| j!                  |       t"        j%                  d|dd| j
                  d       t"        j%                  d| j
                  dd| j                  dd       y# 1 sw Y   xY w# t&        $ r Y Kw xY w# t(        $ r t"        j%                  d       Y yt&        $ r*}t"        j+                  d| d       d| _        Y d}~yd}~ww xY w)z=Load state from JSON file, with sensible defaults if missing.Nr*   r   r   r   r)   r,   r         N@r   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)r7   r   r:   r   r   r*   r3   r4   r   r   r   r5   r   r   fromisoformattotal_secondsr   r   r    r<   r  r-  )r   r>   r  
saved_datetodaylastelapsed_minr/  s           r%   r   zAttentionModel.load  s    !	doo&!		! ' 99Y4DL %		.(:K:K:O:O:Q:[:[:] ^D!&?D!AD <4J%%))+44Z@EU"-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	sY   H G&C!H B$G3 32H &G0+H 3	G?<H >G??H I"I* II
action_cfgc                 D    |j                  dd      }| j                  |k\  S )z;Check if current deficit >= action's min_deficit threshold.min_deficitr   )r   r*   )r   r:  r<  s      r%   is_action_eligiblez!AttentionModel.is_action_eligible  s!     nn]C8||{**r'   c                     |j                  di       }|j                  dd      }|j                  dd      }|| j                  ||z
  z  z
  }|t        j                  dd      z  }t	        |||z         S )z<Scale interval with deficit: high deficit = short intervals.intervalmin_secondsr   max_secondsi  g333333ӿr]   )r   r*   ra   rd   r   )r   r   ivalmin_smax_sr   jitters          r%   get_intervalzAttentionModel.get_interval  so    zz*b)+,77&..s335&6/**r'   watch_minutesc                     t         j                   j                         j                  d      }|| j                  k7  r|| _        d| _        yy)z7Track daily watch totals. Resets when the date changes.r,   r   N)r3   r4   r5   r   r   )r   rG  r7  s      r%   	log_dailyzAttentionModel.log_daily  sD    !!%%'00<D$$$$D),D& %r'   )z"/share/.ha_cache/.ha_wd_state.jsonr   FN)r   r   r   r   dictrO   r   boolr   r   r   r   r   r  r  r'  r0  r   r=  rF  rI  r   r'   r%   r   r   (  s     8\7<t  0441u 1u 1% &U <U 1 1!F83 8(#J+T +d +
	+4 	+E 	+-u -r'   r   z /share/.ha_cache/.ha_wd_rl.jsonl_rl_pendingsession_minaction_nameappc           	         t         j                   j                         }|j                         t        | d      |j	                  d      |t        |d      |ddd}	 t        t        d      5 }|j                         }|j                  t        j                  |      dz          ddd       t        j                  |f       |t        j                  d	      z
  }t        D 	
cg c]  \  }	}
|
|kD  s|	|
f c}
}	t        dd |S # 1 sw Y   cxY wc c}
}	w # t        $ r"}t        j!                  d
|        Y d}~yd}~ww xY w)zSLog an action to the RL log file. Returns the file offset for later reward fill-in.   r-   r   N)tsr*   r/   rP  rN  r(   tv_off_within_5mtv_off_within_15mr1   r2   r   )minutesz  RL log write failed: )r3   r4   r   r6   r5   r7   RL_LOG_PATHtellr9   r:   r;   rM  append	timedeltar<   r   debug)r*   rN  rO  rP  r4   r=   r>   offsetcutoffotr/  s               r%   rl_log_actionr`    s   





!Cmmo!$W%[!, !	E+s#qVVXFGGDJJu%,- $ 	FC=)x))"55-8G[TQAJ1a&[GA $# H 		+A3/0sC   D ,8D$AD %D3D9D DD 	E!D>>Etv_off_timec                    t         sy	 t        t        d      5 }|j                         }ddd       d}t         D ]  \  }}| |z
  j	                         dz  }|dk  r"|dk  }|dk  }|j                         }	t              D ]H  \  }
}|	|v s	 t        j                  |      }||d<   ||d	<   t        j                  |      d
z   ||
<   d}   |r*t        t        d      5 }|j                         ddd       t         j                          y# 1 sw Y   xY w# t        j                  $ r Y gw xY w# 1 sw Y   CxY w# t        $ r"}t        j                  d|        Y d}~yd}~ww xY w)zGWhen TV turns off, fill in tv_off_within_Xm for recent pending actions.NrFr3  r         @g      .@rT  rU  r2   TrM   z  RL reward fill failed: )rM  r7   rW  	readlinesr5  r   	enumerater:   loadsr;   JSONDecodeError
writelinesr   r<   r   r[  )ra  r>   linesupdatedr\  action_timedeltawithin_5	within_15ts_striliner=   r/  s                 r%   rl_fill_rewardsrs  )  sh    3+s#qKKME $ #.FK ;.==?$FEqy|HI **,F$U+4T> $

4 04<015>12#'::e#4t#;a"&  , $/( k3'1U# ( 	9 $#(  // 
 ('  3		-aS1223sj   E  DA!E  <D	E   D42E  DE  D1.E  0D11E  4D=9E   	E+	E&&E+r   c                 $   | j                  di       }|j                  dd      s
t               S t        j                  j                         j	                  d      }t        j                  |dz         }t        | d   j                               }|j                  t        |      dz  t        |      dz  dz        }t        |j                  |t        |t        |      d	z
                    }|j                  d
       |j                  d       |S )z9Each day, randomly disable some action types for variety.opsecdaily_personalityFr,   personalityactionsr[   r   r   volume_drop_bigaudio_stutter_long)r   r   r3   r4   r5   ra   Randomlistkeysrb   lensampler   discard)r   ru  r7  rngall_actionsnum_disableddisableds          r%   _daily_action_filterr  R  s    JJw#E99(%0u!!#,,Z8E
---
.Cvi(--/0K;;s;/14c+6F6Ja6OPL3::k3|S=MPQ=Q+RSTH&')*Or'   disabled_todayc                    t         j                   j                         }|j                  dz  |j                  z   }g }g }|d   j	                         D ]  \  }}||v r|j                  dd      }	| |	k  r#|j                  d      }
|
r|
j                  d      ry	 t        t        |
d   j                  d            \  }}t        t        |
d	   j                  d            \  }}|dz  |z   }|dz  |z   }||k  r||cxk  r|k  sn ||k\  s||k  s|j                  ||f       |j                  |j                  d
d              |syt        j                  ||d      d   S # t        t        f$ r Y ew xY w)zPick a random action weighted by base weight, filtered by deficit threshold.
    Returns (action_name, action_config) or (None, None).r   rx  r<  r   permissible_hoursenabledr   r  r  weight
   NNr   )weightskr   )r3   r4   r   r  itemsr   r  r   r  
ValueErrorKeyErrorrY  ra   choices)r*   r   r  r4   cur_minuteseligibler  nameacfgr<  phshsmehemr  r  s                    r%   pick_action_deficitr  c  s~    




!C((R-#**,KHGY'--/
d>!hh}c2[ XX)*"&&#S"W+"3"3C"89BS"U)//#"67Br'B,R"e##{:U: '72kE6I  	t%txx"-./ 00 >>(Gq9!<< ) s   A,E&
E&&E87E8r:  dry_runseverity_levelc                 X   |j                  di       }t        j                  j                         j                  d      }|rt        j                  d| d|        yt                |dk(  rXt        |       }|Jt        d||j                  dd	      z
        }	t        | |	       t        j                  d| d
| d|	        yy|dv rt        |       }|tt        j                  |j                  dd      |j                  dd            }
t        d||
z
        }	t        | |	       t        j                  d| d
| d|	 d|
 d	       yy|dk(  r"t        |       }|t        j                  |j                  dd      |j                  dd            }
t        d||
z
        }t        j                  |j                  dd      |j                  dd            }|j                  dd      |j                  dd       f}|j                  d!d"      }t        | |       t        j                  | ||||#       t        j                  d| d$| d| d%|d&z  d'd(|d   d'd)|d	   d'd*|d&z  d'd+       yy|d,k(  r&t        | d-       t        j                  d| d.       y|d/v r|j                  dd0      }|j                  dd&      }|||z
  t!        d1|      z  z   }d}d}||k  rt        | d-       t        j                  d2d3      }t#        j$                  |       t        | d4       t        j                  d5d6      }t#        j$                  |       |||z   z  }|d	z  }||k  rt        j                  d| d7| d8|d'd9|d:d	       y|d;k(  rvt        j&                  g d<      }t)        | |       t#        j$                  d3       t)        | t        j&                  d=d>g             t        j                  d| d?| d       y|d@k(  r`t        j                  |j                  dAd0      |j                  dBdC            }t)        | dD       t        j                  d| dE|d'dF       y|dGk(  rt        j                  |j                  dHd      |j                  dId            }t+        j,                  | J       t+        j.                  |       }t1        |      D ]6  }t3        j4                  dKdL|dMdNdOdPgd-dQ       t#        j$                  d6       8 t        j                  d| dR|        y|dSk(  r`t        j                  |j                  dTdU      |j                  dVdW            }t7        | |dX       t        j                  d| dY| dZ       y|d[k(  r`t        j                  |j                  dTd\      |j                  dVd]            }t7        | |d^       t        j                  d| d_| dZ       y|d`k(  rt        j                  |j                  dAd      |j                  dBda            }t)        | dD       t#        j$                  |       t)        | db       t        j                  d| dc|ddde       y|dfk(  r6t9        | |j                  dgd             t        j                  d| dh       y|dik(  rt)        | dj       t        j                  |j                  dkd      |j                  dld0            }t#        j$                  |       t)        | dm       t        j                  d| dn|d'do       y|dpk(  rKt1        d      D ]#  }t)        | dq       t#        j$                  dr       % t        j                  d| ds       y|dtk(  rKt1        d      D ]#  }t)        | du       t#        j$                  dr       % t        j                  d| dv       y|dwk(  rdt+        j,                  | J       t+        j.                  |       }t3        j4                  dKdL|dMdNdOdPgd-dQ       t        j                  d| dx       y|dyk(  r&t)        | dz       t        j                  d| d{       y|d|k(  rg d}}t        j                  d~d      }g }t1        |      D ]]  }t        j&                  |      }t)        | |       t#        j$                  t        j                  d2d3             |j;                  |       _ t        j                  d| d| ddj=                  |              y|dk(  r|j                  dd\      } |j                  dd]      }!|j                  dd      }|j                  dd      }t!        d1|dz        }"t        dWt?        |!|!| z
  |"z  z
              }#|||z
  |"z  z   }t@        jB                  jE                  d      }$|$sl	 t3        jF                  ddgd-t2        jH                  t2        jH                         t#        j$                  d       t@        jB                  jE                  d      }$|$st        jM                  d| d       y	 t+        jN                  | |#       t        j                  d| d|# d|d&z  d'd       d }%tQ        jR                  |%|fd-      jU                          y|dk(  rGt)        | dz       t#        j$                  d       t)        | d       t        j                  d| d       y|jW                  d      rZddl,}'|j                  dd      }(|j                  dd6      })|'j[                  |(|)       t        j                  d| d|( d|)        y|dk(  rddl,}'h d}*|'j\                  j_                         D +,cg c]  \  }+},|+|*vr|+|,f }-}+},	 |'ja                         \  }.}}|-D /cg c]  }/|/d   |.k7  s|/ }0}/|0rEt        j&                  |0      \  }+},|'jc                  |+       t        j                  d| d|,        yy|jW                  d      rbddddd}1|1j                  |d      \  }2}3|2rCt        j                  dd5      }te        |2|       t        j                  d| d|3 d|dddo       yy|dk(  r&t)        | d       t        j                  d| d       yy# tJ        $ r Y w xY w# tJ        $ r%}&t        jM                  d| d|&        Y d}&~&yd}&~&ww xY wc c},}+w # tJ        $ r d}.Y Tw xY wc c}/w )z Execute a single sandman action.paramsr   r   z] [DRY] Nvolume_nudger   dropr   u   ]   📉 Vol u   →)volume_dropry  drop_minr   drop_maxrR  r   z (-r   
volume_capcap_drop_minr[   cap_drop_max   duration_min_s,  duration_max_si  extend_per_fightback_min_sZ   extend_per_fightback_max_s   r   i  )r   r   u   ]   🎚️  Vol cap z for r   r   zmin (+-zs per fight-back, max zmin)	full_muteTu    ]   🔇 FULL MUTE (stays muted))audio_stutterrz  r  r   g?      ?Fr\   r]   u   ]   💥 Audio stutter (u   × over zs, deficit=.2foverlay_controls)
CursorDownr  CursorRightCursorUp
CursorLeftr  u   ]   📺 Overlay controls (
pause_longpause_min_spause_max_s   Pauseu   ]   ⏸️  LONG PAUSE (u   s — dad must act)	back_spampresses_minpresses_maxrI   adbz-sshellinputkeyevent4)capture_outputr@   u   ]   ⬅️  Back spam ×
audio_blipduration_min_ms   duration_max_msi  singleu   ]   💥 Audio blip (zms)
audio_fade   i  r_   u   ]   💥 Audio fade (
pause_playr   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)r  r  r  r  ConfirmBackr  r  r   u   ]   🎲 Random nav (z): u    → throttle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 throttle)rF   bandwidth_kbpsu   ]   🌐 Throttle z	kbps for r   c                     t        j                  |        t        j                          t        j                  dt        j                  j                         j                  d       d       y )Nr   r   u   ]   🌐 Throttle ended)	r/   rc   rD   throttle_remover   r    r3   r4   r5   )secss    r%   _remove_throttlez(execute_action.<locals>._remove_throttle@  sK    JJt$..0HHs8#4#4#8#8#:#C#CJ#O"PPghir'   )r   argsr   u   ]   🌐 Throttle error: screensaverr  u   ]   🌙 Screensaver triggeredsound_filezknock_2.mp3rQ   )rQ   u   ]   🔊 Speaker: z vol=
app_switch>   org.droidtv.channelsorg.droidtv.settingscom.google.android.katnissorg.droidtv.contentexplorercom.google.android.tvlauncher$com.google.android.apps.tv.launcherxu   ]   🔀 App switch → flicker_)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   🍳)flicker_toilet_lightflicker_tv_lightflicker_laundry_lightflicker_kitchen_light)N?r  z]   z Light flicker (	power_offStandbyu   ]   ⚡ POWER OFF)3r   r3   r4   r5   r   r    rP   rK   r   rS   ra   rb   rd   _volume_cap_watchdogr   rV   r   r/   rc   choicerm   rD   adb_connect_adb_targetr`   
subprocessrunrg   rj   rY  r   r   r   r   existsPopenDEVNULLr<   r-  throttle_applyr   r   r   
startswithtvmodspeaker_play_sound	APP_NAMESr  adb_get_current_appadb_launch_app_ha_switch_flicker)4rF   rO  r:  r  r  r*   r  nvolnew_volr  capdurationr   	max_totaldur_mindur_maxelapsedcyclest1t2
key_choicepause_spressesr   re   durr   nav_keyscount
desc_partsrk   bw_minbw_maxdeficit_factorbwmitm_runningr  r/  _tc
sound_filerQ   
_skip_pkgsr   r  appspkg_nowr1   
other_appsflicker_mapentitylabels4                                                       r%   execute_actionr6    sc   ^^Hb)F((4A3qc+/0n$?!S6::fa#889Gug&HHs1#]3%s7)<= 
 
:	:?>>&**Z";VZZ
TU=VWD!S4Z(Gug&HHs1#]3%s7)3tfAFG	  
	$?>>&**^Q"?"(**^R"@BDat$C~~fjj1A3&G&,jj1A3&GIH"JJ'CRH"JJ'CSIKL

=$7Iuc" $$UC2>1: % < HHs1#23%s3%uXb[QTDU V&q/#.aQ/D E%bL-T3 4 " 
	#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
C))bI3qc!3B4y"S@QQTUVj   (8{SWX^^` 
	%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a
9j1ICs#HHs1#5dV<= 
 
		
	+$] Q%^%K	
 $[A~~dC0Hvx0HHs1#T%(8#bIJ 
 
	#ui 3qc*+, 
$I    Cc!$=aSABBC6*
  	G	9sP   A+s A$s %t;t t'!t'	ss	t'ttt$#t$	entity_idr  c                 0    t        j                  | |       y)zFToggle a HA switch for `duration` seconds then restore original state.N)rD   ha_switch_flicker)r7  r  s     r%   r  r    s      H5r'   	tv_ip_argconfig_pathinject_deficitr   r   c                   => t        |      }| }|j                  di       }t        |||      >|*>j                  |       t        j                  d|d       t        |      }	|dk(  s|Ot        j                  d       t               }|rt        j                  d|        nt        j                  d	       t        j                  d
       t        j                  d|xs d        t        j                  d>j                         d       t        j                  d| d       t        j                  d|rdnd        t        j                  d|d   d    d|d   d    d       t        j                  dt        |d          dt        |	       d       |	r't        j                  ddj                  |	              t        j                  d |j                  d!d"      d#       |j                  d$d"      }
t        j                  d%|
d"k(  rd&n|
        t        j                  d'|        dt>fd(	}t        j                  t        j                  |       t        j                  t        j                  |       t        j                  t        j                  t        j                          d)}d}t"        j"                  j%                         }t"        j"                  j%                         }d"}d)}i }g d*}t'        >fd+|D        d",      }|}>j(                  R|rt+        |d-      nd}|r|j                  d-      d.k7  r,t        j                  d/       d>_        >j-                          	 t"        j"                  j%                         }||z
  j/                         d1z  }|}>j1                          	 t2        j4                  j7                  |xs d2      }t9        t:        d3      r|t:        j<                  k7  rMt9        t:        d3      r2t        |      }|j                  di       }t        j                  d4       |t:        _        |j@                  d5z  |jB                  z   }|j                  di       jE                         D ]B  \  }}|j                  d6      }|r|j                  d      s,	 tG        tH        |d7   jK                  d8            \  }}tG        tH        |d9   jK                  d8            \  } }!|d5z  |z   }"| d5z  |!z   }#|"|#k  r|"|cxk  xr |#k  nc }$n||"k\  xs ||#k  }$|j                  |      }%|%|$|%k7  r}|$rDt        j                  d:& d;| d<|d7    d=|d9    d>	       tQ        | d?|d7    d=|d9    d>d@       n7t        j                  d:& d;| dA|d7    d=|d9    d>	       tQ        | dBd@       |$||<   E |tS        |      At        dCD      }'|'r3|'|k7  r.|'}|jU                  dE      }&t        j                  d:|& dF|        d)}(d})|r1t+        |d-      }*|*r#|*j                  d-      d.k(  rtS        |      })|)du}(|jU                  dE      }&|(sH|rft        j                  d:|& dG>j                         ddH>jV                  dIdJ       tY        |       d>_        d>_-        >j-                          d}d"}d)}|d"kD  r>j]                  |       >j                         =|D ]J  }+=|+cxk  r|k  sn t        j                  d:|& dK|+ddL=dd>       t'        =fdM|D        d",      }|}L ||z
  j/                         dNk\  r<>j-                          t        j                  d:|& dO>j                         d       |}t_        j`                  d5       >|sQ|}|jc                         >_        >j-                          t        j                  d:|& dP>j                         d       d0}|r||z
  j/                         d1z  nd"},|d"kD  r#>je                  ||,       >jg                  |,       >j                         =|D ]~  }+=|+cxk\  r|kD  r)n n&t        j                  d:|& dQ|+ddL=dd>       |+}|+}7=|+cxk  r|k  sCn Ft        j                  d:|& dK|+ddL=dd>       t'        =fdR|D        d",      }|} ||z
  j/                         d5k\  r>j-                          |}dS}-t2        j4                  ji                  |-      r	 tk        |-      5 }.tm        jn                  |.      }/ddd       /j                  dT      }0|0rt"        j"                  jq                  |0      }1||1k\  r;t3        jr                  |-       t        j                  d:|& dU       tQ        dVd@       nr|1|z
  }2t        ju                  dW|2jv                   dX|2jx                  dYz   dZ       >jZ                  rd>_-        >j-                          t_        j`                  d[       t2        j4                  ji                  |-      rSt        ju                  d\=d       >jZ                  rd>_-        >j-                          t_        j`                  d[       2>j}                         \  }3}4|4 }5|5r(|s&t        j                  d:|& d]       tQ        d^d@       n)|5s'|r%t        j                  d:|& d_       tQ        d`d@       |5}|5rTt        ju                  da=ddb       >jZ                  rd>_-        >j-                          t_        j`                  d[       =>j~                  k  r=t        ju                  dc=ddd>j~                          t_        j`                  d[       ?t        j                         }6|6=kD  r>j                  |      }7|t#        j                  |7e      z   jc                         }8t        j                  d:|& df|6ddg=ddh|7dId	       >j-                  |8i       t_        j`                  |7       |j                  d$d"      }9|9d"kD  ro||9k\  rjt        j                  d:|& dj|9 dk       >j-                  |t#        j                  dNe      z   jc                         i       t_        j`                  dN       	dt        =||	      \  }:};|:rct        j                  d:|& dl|6ddm=ddn|:        tQ        |:do=       t        ||:|;|==p       |dqz  }d}<	 t        |      }<t        =|,|:|<r       >j                  |      }7t"        j"                  j%                         t#        j                  |7e      z   jc                         }8t        j                  d:|& ds|7dId       >j-                  |8i       t_        j`                  |7       
r# t>        $ r Y 	w xY w# tL        tN        f$ r Y 	~w xY w# 1 sw Y   xY w# tl        jz                  t>        f$ r Y w xY w# t>        $ r Y w xY w)uzAttention-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.
    ru  )r   r   Nz  Injected starting deficit: r  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 - rA  r  z   Actions: rx  z configured, z disabled todayz   Disabled today: z, z   Skip probability: skip_probabilityr   z.0%max_actions_per_sessionz   Max actions/session: 	unlimitedz   Dry run: c                 z    t         j                  d       j                          t        j                  d       y )Nz&Sandman shutting down. Saving state...r   )r   r    r0  r   exit)sigframemodels     r%   cleanupzrun_v4.<locals>.cleanup  s$    9:

r'   F)r^   r]   r  r  g?c              3   J   K   | ]  }|j                         k  s|  y wrJ  )r  ).0r_  rF  s     r%   	<genexpr>zrun_v4.<locals>.<genexpr>  s$     U&8AARARAT<T1&8s   ##)defaultrs   rt   z*  Clearing stale session_start (TV is off)Tr3  r   
_cfg_mtimez  Config hot-reloadedr   r  r   r  r  r   u   ] ⏰ z now active (r  r   z window open (systemz now inactive (outside z window closedr[   rC   r   z] TV at z] TV off -- deficit=z, today=r   r   u   ] ⬇ Deficit dropped below z (now c              3   .   K   | ]  }|k  s	|  y wrJ  r   rI  r  r*   s     r%   rJ  zrun_v4.<locals>.<genexpr>$  s     ,[8J1aSZlQ8J   
r  z] Resting -- deficit=z] TV on -- deficit=u   ] ⬆ Deficit crossed c              3   .   K   | ]  }|k  s	|  y wrJ  r   rO  s     r%   rJ  zrun_v4.<locals>.<genexpr>K  s     (W4Fq!w,4FrP  z/share/.ha_cache/.ha_wd_paused	resume_atu(   ] ⏰ Holiday pause expired — resumingzHoliday pause expiredu   Holiday paused — zd i  zh leftr  z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 )r   z] Max actions (z) reached. Going quiet.u   ] 🎲 Hit: roll=z <= deficit=z | r   )r  r*   r   )rP  u   ] ⏱ Next check in r  )Gr   r   r   r  r   r    r  rE   r-  r  r~  r   signalSIGINTSIGTERMSIGHUPSIG_IGNr3   r4   r   r   rq   r0  r5  r'  r   r   getmtimehasattrrun_v4rL  r<   r   r  r  r  r   r  r  r  r?   rK   r5   r   rs  r   r   r/   rc   r   r   rI  r
  r7   r:   r   r4  remover[  daysrS  rh  r   r   ra   rF  rZ  r  r6  r   r`  )?r:  r;  r  r<  r   r   r   rF   ru  r  max_actrG  	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_pwr4   r   	cfg_mtimecur_manamer  r  r  r  r  r  r  r  activewasnow_strnew_iptv_onr  pwr_  r   
pause_pathpf
pause_datarR  	resume_dtr   re   actions_allowed	in_goldenrollr?  next_atmax_actionsrO  r:  rP  r*   rF  s?                                                                @@r%   r[  r[    se    %FEJJw#E 6)DE!.)00DEF *&1N%-45HH~eW-.KK@AHH57HH{5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?
 	##%		(()H8HII6<0IARAR4R6<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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*3wi';E<M<M<OPS;T U""'"="=c!B#G H  $&*#'+$

%)" IA~j) '')G'Q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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S "  		$ ) R &%" (()4 F  sc   Bw1 A<x1x$ <xC*x$ =y 1	w>=w>xxx!x$ $y y	yyc                  B   t        j                  d      } | j                  ddd       | j                  ddd	
       | j                  dd d       | j                  dd d       | j                  dt        d d       | j                  dt        dd       | j                  ddd
       | j	                         }t        |j                         t        |j                  |j                  |j                  |j                  |j                  |j                         y )Nz1Sandman v5 -- attention deficit TV sleep pressure)descriptionz--tvr>  z,TV IP address (or 'auto' for SSDP discovery))rK  helpz	--dry-run
store_truezPrint actions only, don't send)r(   r~  z--configz2Path 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)r0   rK  r~  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<  r   r   )argparseArgumentParseradd_argumentr   
parse_argsr&   r   r[  tvr   r  r*   r   r   )pr  s     r%   mainr    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                     d} t        j                         }	 t         j                  j                  |       rKt	        t        |       j                         j                               }||k7  rt        j                  |d       t        | d      5 }|j                  t        |             ddd       y# t        t        f$ r Y Aw xY w# 1 sw Y   yxY w)zEKill any other sandman.py processes before starting. Uses a PID file.z/share/.ha_cache/.ha_wd.pid	   rM   N)r   getpidr   r
  r   r7   readstripkillProcessLookupErrorr  r9   rO   )pidfilemy_pidold_pidr>   s       r%   _ensure_single_instancer    s    +GYY[F77>>'"$w-,,.4467G& # 
gs	q	F 
	 
+  
	s   A*B2 C2CCC__main__rJ  )r   N)rd  )r  r  )g      @)Fr  r  )r>  NFNr   F)<r   r  r3   r:   r   logging.handlersr   r   ra   rerT  r  r   r   r/   urllib.requesturllibr  rD   	getLoggerr   rO   r&   r8   r   r?   rE   r   rK   rN   rP   rL  rS   rV   rg   rj   rm   rq   r   r   r  rK  r   r   rW  rM  r|  __annotations__r`  rs  r   r  r   r  r6  r  r[  r  r  r   r   r'   r%   <module>r     s  
       	  	   
     g	":3 :: 5s  %  3 3t 3+c +cDj + 43c 33 34 3
/C /t / / # C 88C 8u 81# 1 1 11# 1 1# # 4Z2 Z2| )* 
c T [- [-@ 1 T 5 u 3 S 6$3!2!2 $3R # "!= != !=c !=e !=Hu-# u-C u-T u-D u-jo u-  AF u-p6# 6 6
 NSOTqc q qd q q05qHLqh	T&" zF r'   