
    iY                     B   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mZ ddlZ ej"                  ej$                  j&                          ej(                  d      ZdadZdZdZd	Zd
ZdZdddddddddddddddZdZ	  ed      5 Z e D ]7  Z!e!jE                  d      se!jG                         jI                  dd      d   Z9 	 ddd       dZ&de'fd Z(defd!Z)dQd"e'de'fd#Z*dQd"e'de'fd$Z+dQd"e'de'fd%Z,dRd&e'd'e-d"e'de.dz  fd(Z/dRd&e'd)e.d'e-d"e'de0f
d*Z1dQd+e'd"e'de0fd,Z2dQd-e'd.e'd"e'de'dz  fd/Z3dQd"e'de4dz  fd0Z5dQd1e4d"e'de0fd2Z6dQd3e0d"e'de0fd4Z7dQd"e'de'fd5Z8dQd"e'de.fd6Z9dSd'e-de'dz  fd7Z:dQd"e'fd8Z;dTd9e-d"e'fd:Z<dQd"e'de0fd;Z=dUd<e'd"e'de'dz  fd=Z>dQd"e'de?fd>Z@dQd?e'd"e'de0fd@ZAdQdAe'd&e'd)e.de.dz  fdBZBdCeCdeCfdDZDdEe'de0fdFZEdEe'dGe-fdHZFdVdIe'dJe4de0fdKZGdWdLe'dMe-de0fdNZHg dOZIde0fdPZJy# 1 sw Y   1xY w# e%$ r Y <w xY w)Xu*  Shared TV control module — JointSpace, UPnP, ADB, Home Assistant, throttle.

Consolidates all TV/ADB/HA control functions used by sandman.py, sandman_bot.py,
and devices_server.py.  Import and call directly:

    import tv_control
    tv_control.js_key("Pause")
    vol = tv_control.get_volume()
    N)HTTPDigestAuth
tv_controlz192.168.1.50a1b2c3d4e5f6@8f075a1826341135dcd3a9dd2ed30a49c327d7e002399fbaefae28d8e1f9936fi  z/urn:schemas-upnp-org:service:RenderingControl:1z<?xml version="1.0"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>{body}</s:Body></s:Envelope>i  YouTubeNetflixzPrime VideozLive TVzMedia Browserz	Apple TV+Homez	Google TVSettingszDisney+mewatchSpotify)com.google.android.youtube.tvcom.netflix.ninja!com.amazon.amazonvideo.livingroomzorg.droidtv.channelsorg.droidtv.playtvzorg.droidtv.contentexplorer com.apple.atve.androidtv.appletvz$com.google.android.apps.tv.launcherxzcom.google.android.tvlauncherzcom.google.android.katnisszorg.droidtv.settingscom.disney.disneyplusz
tv.mewatchzcom.spotify.tv.android z/data/.ssh/environmentzSUPERVISOR_TOKEN==   z/share/sandman_bot_tv_ipreturnc                      	 t        t              j                         j                         } | r| at        S # t
        $ r Y t        S w xY w)zOReturn the current TV IP, checking cache file if module-level TV_IP is default.)openTV_IP_CACHEreadstripTV_IP	Exception)cacheds    /share/tv_control.py
_get_tv_ipr    Q   sK    k"'')//1E L  Ls   /7 	AAc                  *    t        t        t              S N)r   JS_DEVICE_IDJS_AUTH_KEY     r   _js_authr'   `   s    ,44r&   ipc                 *    | xs
 t               } d|  dS )Nhttps://z:1926/6)r    r(   s    r   _js_baser,   d   s    		z|BbT!!r&   c                 4    | xs
 t               } |  dt         S )N:)r    ADB_TARGET_PORTr+   s    r   _adb_targetr0   i   s     		z|BT?#$$r&   c                 8    | xs
 t               } d|  dt         dS )Nzhttp://r.   z/upnp/control/RenderingControl1)r    	UPNP_PORTr+   s    r   	_upnp_urlr3   n   s$    		z|BRD)$CDDr&   pathtimeoutc                 :   |xs
 t               }	 t        |       d| j                  d       }t        j                  |t               d|      }|j                          |j                         S # t        $ r!}t        j                  d| |       Y d}~yd}~ww xY w)z8GET a JointSpace endpoint.  Returns parsed JSON or None./F)authverifyr5   zjs_get /%s failed: %sN)r    r,   lstriprequestsgetr'   raise_for_statusjsonr   logdebug)r4   r5   r(   urlres         r   js_getrD   u   s    		z|B"aC 012LL8:eWM	vvx 		)43s   AA0 0	B9BBdatac                    |xs
 t               }	 t        |       d| j                  d       }t        j                  ||t               d|      }|j                  dk  S # t        $ r!}t        j                  d| |       Y d}~yd}~ww xY w)z8POST to a JointSpace endpoint.  Returns True on success.r7   F)r>   r8   r9   r5   i  zjs_post /%s failed: %sN)
r    r,   r:   r;   postr'   status_coder   r?   r@   )r4   rE   r5   r(   rA   rB   rC   s          r   js_postrI      s}    		z|B"aC 012MM#Dxz%QXY}}s"" 		*D!4s   AA   	B
)BB
keyc                 "    t        dd| i|      S )z Send a key press via JointSpace.z	input/keyrJ   r+   )rI   )rJ   r(   s     r   js_keyrL      s    ;44r&   action
body_innerc                    |xs
 t               }t        |      }t        j                  |      }ddt         d|  dd}	 t
        j                  j                  ||j                         |d      }t
        j                  j                  |d	      5 }|j                         j                         cd
d
d
       S # 1 sw Y   y
xY w# t        $ r!}t        j                  d| |       Y d
}~y
d
}~ww xY w)zMSend a UPnP SOAP request to RenderingControl.  Returns response body or None.)bodyztext/xml; charset="utf-8""#)Content-Type
SOAPActionPOSTrE   headersmethod   r5   NzUPnP %s failed: %s)r    r3   UPNP_ENVELOPEformatUPNP_SVCurllibrequestRequestencodeurlopenr   decoder   r?   r@   )	rM   rN   r(   rA   rP   rW   reqresprC   s	            r   _upnp_requestrf      s    		z|B
B-CZ0D3(1VHA.Gnn$$St{{}gV\$]^^##C#3t99;%%' 433 		&2s7   AC B;1	C ;C C C 	C1C,,C1c                     dt          d}t        d||       }|yt        j                  d|      }|rt	        |j                  d            S dS )z2Get current volume via UPnP.  Returns int or None.z<u:GetVolume xmlns:u="zC"><InstanceID>0</InstanceID><Channel>Master</Channel></u:GetVolume>	GetVolumer+   Nz$<CurrentVolume>(\d+)</CurrentVolume>r   )r]   rf   researchintgroup)r(   rP   re   ms       r   
get_volumern      sQ    #H:-pqDdr2D|
		94@A3qwwqz?)T)r&   volc                 h    t        dt        d|             } dt         d|  d}t        d||      duS )	zSet volume via UPnP (0-100).r   d   z<u:SetVolume xmlns:u="zD"><InstanceID>0</InstanceID><Channel>Master</Channel><DesiredVolume>z</DesiredVolume></u:SetVolume>	SetVolumer+   N)maxminr]   rf   )ro   r(   rP   s      r   
set_volumeru      sF    
aS#
C$XJ /!U"@BD dr2$>>r&   mutedc                 H    | rdnd}dt          d| d}t        d||      duS )	zSet mute state via UPnP.10z<u:SetMute xmlns:u="zB"><InstanceID>0</InstanceID><Channel>Master</Channel><DesiredMute>z</DesiredMute></u:SetMute>SetMuter+   N)r]   rf   )rv   r(   valrP   s       r   set_muter|      s<    #CC"8* -5 :<D DR0<<r&   c                 b    t        d|       }|r |j                  dd      j                         S y)z%Return 'On', 'Standby', or 'Unknown'.
powerstater+   Unknown)rD   r<   
capitalizer(   rE   s     r   get_power_stater      s.    ,2&Dxxi0;;==r&   c                 t    t        d|       }|r%|j                  dd      |j                  dd      dS dddS )z&Return {'volume': int, 'muted': bool}.zaudio/volumer+   currentr   rv   F)volumerv   )rD   r<   r   s     r   get_audio_stater      s=    .R(D((9a0488GU;STT%((r&   c                 0   	 dt        |        d}t        j                  t        j                  t        j                  t        j                        }|j                  t        j                  t        j                  d       |j                  t        | d             |j                  |j                         d       	 	 |j                  d      \  }}|j                  d      }d	|v sd
|v sd|j                         v r?|j                          t         j#                  d|d          |d   	 |j                          S # t$        $ r Y S w xY w# t        j&                  $ r Y nw xY w	 	 |j                          n4# t$        $ r Y n)w xY w# 	 |j                          w # t$        $ r Y w w xY wxY wn# t$        $ r Y nw xY wd }t)        dd      D cg c]  }d| 	 nc c}w }}t*        j,                  j/                  d      5 }	|D 
ci c]  }
|	j1                  ||
      |
 nc c}
w }}
t*        j,                  j3                  |      D ]7  }|j5                         }|st         j#                  d|       |c cddd       S  	 ddd       y# 1 sw Y   yxY w)zIFind the Philips TV via SSDP multicast, falling back to JointSpace probe.zKM-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
MAN: "ssdp:discover"
MX: z
ST: ssdp:all

      )z239.255.255.250il  i   replace)errorsPhilipsPhilipsIntelSDKandroidzSSDP discovered TV at %sr   c                     	 t        j                         }t        j                  j	                  t        j                  j                  d|  d      d|       | S # t        $ r Y y w xY w)Nr*   z:1926/6/systemr   )r5   context)ssl_create_unverified_contextr^   r_   rb   r`   r   )check_ipctxs     r   probezdiscover_tv.<locals>.probe   se    	002CNN""&&(>'JK3 #  O 		s   AA 	A'&A'   z
192.168.1.   )max_workerszProbe discovered TV at %sN)rk   socketAF_INET
SOCK_DGRAMIPPROTO_UDP
setsockopt
IPPROTO_IPIP_MULTICAST_TTL
settimeoutrt   sendtora   recvfromrc   lowercloser?   infor   r5   range
concurrentfuturesThreadPoolExecutorsubmitas_completedresult)r5   ssdp_msgsockrE   addrtextr   i
candidatespoolr(   futsfr   s                 r   discover_tvr      sQ    w<. !## 	 }}V^^V->->@R@RS))6+B+BAFGQ(HOO%'@A	!]]40
d{{){4$(9T(AYRVR\R\R^E^JJLHH7aA7

    ~~ 		

 

  	 -2!RL9LqJqc"L9J9				.	.2	.	>$5?@ZrE2&*Z@@##006AXXZF4f= 
?	>6 
?  
? s   B:F. =A.E ,D>=E >	E
F. 	E

F. E# F "E##F (E9 8F. 9	FF. FF. F*
FF*	F'$F*&F''F**F. .	F:9F:GJH"!<JJJJc                    | xs
 t               } 	 t        j                         }t               |_        d|_        t        |        d}|j                  |ddid       t        j                  d       t        d      D ]  }|j                  |dd	id        t        j                  d
       |j                  |ddid       |j                  |ddid       y# t        $ r }t        j                  d|       Y d}~yd}~ww xY w)zSToggle ambilight via menu navigation (AmbilightOnOff -> up 15x -> Confirm -> Back).Fz
/input/keyrJ   AmbilightOnOffr   )r>   r5   g333333?r   CursorUpg?ConfirmBackzAmbilight toggle failed: %sN)r    r;   Sessionr'   r8   r9   r,   rG   timesleepr   r   r?   warning)r(   srA   _rC   s        r   ambilight_toggler     s    		z|B6"j)	s%!12A>

4rAFF3eZ0!F< 

4	s%+Q7	s%!4 611556s   C
C 	D$C??Doff_durationc                 ^    t        |       t        j                  |        t        |       y)z+Toggle ambilight off, wait, toggle back on.r+   N)r   r   r   )r   r(   s     r   ambilight_glitchr   $  s    JJ|r&   c                    t        |       }	 t        j                  dd|gddd      }d|j                  j	                         v xs d|j                  j	                         v S # t
        $ r }t        j                  d|       Y d	}~y
d	}~ww xY w)z0Connect to TV via ADB.  Returns True on success.adbconnectT
   capture_outputr   r5   	connectedalreadyzadb_connect failed: %sNF)r0   
subprocessrunstdoutr   r   r?   r@   )r(   targetrB   rC   s       r   adb_connectr   -  s|    _FNNE9f5*.T2Gahhnn..O)qxx~~?O2OO 		*A.s   AA# #	B,BBoutput_pathc           	      
   t        |      }	 t        |       t        j                  dd|ddddgddd	       t        j                  dd|d
d| gddd	       | S # t        $ r }t
        j                  d|       Y d}~yd}~ww xY w)zKTake a screenshot via ADB.  Returns local path on success, None on failure.r   -sshell	screencapz-pz/sdcard/screen.pngTr   )r   r5   checkpullzadb_screenshot failed: %sN)r0   r   r   r   r   r?   r   )r   r(   r   rC   s       r   adb_screenshotr   9  s    _FBD&';>RS4	
 	D&&*>L4	
  /3s   AA 	B"A==Bc           	         t        |       }	 i }t        j                  dd|ddddgddd	      }d
}|j                  j	                         D ]B  }d|v st        j                  d|      }|r"|j                  d      j                         d   } n d
}t        j                  dd|dddgddd	      }d}	|j                  j	                         D ]  }d|v rd}	|	r!d|v rd|v rd}nd|v rd}|rt        j                  d|      }
t        j                  d|      }t        j                  d|      }|
r|rt        |
j                  d            }t        |j                  d            }|rt        |j                  d            nd}	 t        j                  dd|ddgddd	      }t        t        |j                  j                         d         dz        }||z
  |z  }||z   }|dkD  rt        |dz        |d<   |	s2d|v s8d|v s>t        j                  d |      }|r|j                  d      j                         j                  d!      j                  d"      }|j                  d"      D cg c]  }|j                          }}|r|d   |d#<   t        |      dkD  r|d   |d$<   d}	 |||fS # t        $ r |dkD  r|dz  |d<   Y w xY wc c}w # t        $ r }t        j!                  d%|       Y d
}~y&d
}~ww xY w)'zGet current foreground app, playback state, and extras via ADB.

    Returns (package_name, playback_state, extras_dict) where extras may
    contain 'title', 'artist', 'position_s'.
    Any element may be None on failure.
    r   r   r   dumpsysactivity
activitiesT   r   NtopResumedActivityz	(\S+)/\S+r   media_sessionFzactive=truezstate=PlaybackStatePLAYINGPAUSEDzposition=(\d+)zupdated=(\d+)zspeed=([\d.]+)g      ?zcat /proc/uptimer   r   i  
position_sz	metadata:zsize=zdescription=(.+?)(?:,\s*null)?$z, null,titleartistzadb_get_current_app failed: %s)NNN)r0   r   r   r   
splitlinesri   rj   rl   splitrk   floatr   r   rstriplenr?   r   )r(   r   extrasrB   pkglinerm   stater2found_activepos_mupd_mspd_mpos_msupdatedspeedup_r	uptime_ms
elapsed_msreal_pos_msdescppartsrC   s                           r   adb_get_current_appr  L  s    _F?  NNE4%z<A*.T1F HH'')D#t+IIlD1''!***,R0C * ^^UD&'&9+/dAG II((*D$# 5 =$%E%$EII&7>EII&6=EII&7>E!$U[[^!4"%ekk!n"59>ekk!n 5CF#->>!&fg?Q R/3$$KD ),E$++2C2C2Ea2H,ID,P(QI*3g*=)FJ*0:*=K*Q7:;$;N7O| 4 t 34II@$G771:++-44X>EEcJD04

3@1QWWYE@*/(w5zA~+08x($K +N E6!!  ) F%z7=~| 4F A   4a8 sc   AK D4K A3J6;K ?K K A(K 3K
+K 6KK KK 	L  K;;L packagec           	         t        |      }ddddddd}	 t        |       |j                  |       }|r"t        j                  dd	|d
d| dgdd       yt        j                  dd	|d
d|  dgdd      }|j
                  dk7  r!t        j                  dd	|d
d|  dgdd       y# t        $ r }t        j                  d|       Y d}~yd}~ww xY w)z:Launch an app on the TV via ADB.  Returns True on success.zcom.netflix.ninja/.MainActivityzWcom.google.android.youtube.tv/com.google.android.apps.youtube.tv.activity.ShellActivityz.com.apple.atve.androidtv.appletv/.MainActivityz/com.disney.disneyplus/.ui.splash.LaunchActivityzFcom.amazon.amazonvideo.livingroom/com.amazon.ignition.IgnitionActivityz"org.droidtv.playtv/.PlayTvActivity)r   r   r   r   r   r   r   r   r   z*am start -a android.intent.action.MAIN -n z --activity-clear-topTr   )r   r5   z
monkey -p z/ -c android.intent.category.LEANBACK_LAUNCHER 1r   z& -c android.intent.category.LAUNCHER 1zadb_launch_app failed: %sNF)	r0   r   r<   r   r   
returncoder   r?   r   )r  r(   r   launch_intents	componentrB   rC   s          r   adb_launch_appr    s
   _F> *C,\!R-uBNB"&&w/	NNE4G	{Rghj*.<  tVW",WI5d e g.2B@A ||q tVW",WI5[ \ ^.2B@  /3s   ?B( AB( (	C1CCrX   c                    	 d| }|r#t        j                  |      j                         nd}t        j                  j                  ||dt         dd|       }t        j                  j                  |d      }t        j                  |j                               S # t        $ r"}t        j                  d	| ||       Y d}~yd}~ww xY w)
z5Call HA Supervisor API.  Returns parsed JSON or None.zhttp://supervisor/core/apiNzBearer zapplication/json)AuthorizationrS   rV   r   rZ   zha_api %s %s failed: %s)r>   dumpsra   r^   r_   r`   SUPERVISOR_TOKENrb   loadsr   r   r?   r@   )rX   r4   rE   rA   rP   rd   re   rC   s           r   ha_apir    s    *4&1,0tzz$&&(dnn$$d#*+;*<!= 2  % 
 ~~%%c2%6zz$))+&& 		+VT1=s   BB 	C
(CC
device_listc                     t        dd      }|sg S g }|D ]E  }|d   }|| v s|j                  di       j                  d|      }|j                  |||d   f       G |S )zEReturn [(entity_id, friendly_name, state), ...] for given entity IDs.GETz/states	entity_id
attributesfriendly_namer   )r  r<   append)r  statesr   rC   eidnames         r   ha_get_device_statesr    sq    E9%F	Fn+55r*..DDMM3aj12	 
 Mr&   r  c                 
   	 t        dd|        }|sy|j                  dd      }| j                  d      d   }|dk(  rd	nd
}t        dd| d| d| i       y# t        $ r }t        j                  d|       Y d}~yd}~ww xY w)z3Toggle a HA switch/light.  Returns True on success.r  /states/Fr   off.r   onturn_offturn_onrU   
/services/r7   r  Tzha_switch_toggle failed: %sN)r  r<   r   r   r?   r   )r  
state_datar   domainservicerC   s         r   ha_switch_toggler$    s    EXi[#9:
..%0%a( '4*YvF81WI6i8PQ 115s   A AA 	B"A==Bdurationc                 p   	 t        dd|        }|sy|j                  dd      }| j                  d      d   }|dk(  rdnd	}t        d
d| d| d| i       t        j                  |       |dk(  rd	nd}t        d
d| d| d| i       y# t
        $ r }t        j                  d|       Y d}~yd}~ww xY w)zGToggle a HA switch for `duration` seconds, then restore original state.r  r  Nr   r  r  r   r  r  rU   r   r7   r  zha_switch_flicker failed: %s)r  r<   r   r   r   r   r?   r   )r  r%  r!  r   r"  
toggle_svcrestore_svcrC   s           r   ha_switch_flickerr)    s    7EXi[#9:
..%0%a(")U"2Y

vF81ZL9K;ST

8$+u$4j)vF81[M:[)<TU 72A667s   B A7B 	B5B00B5tv_ipbandwidth_kbpsc                    | xs
 t               } d}	 t        j                  dddd|dgd       |d	k  ryt        j                  ddd
d|ddddddgdd       t        j                  ddd
d|dddddddgdd       t        j                  ddd
d|dddddd| dd| dgdd       t        j                  ddd
d|dddddddd dd!|  d"d#dgdd       y# t        $ r }t        j                  d$|       Y d%}~y&d%}~ww xY w)'zDApply tc HTB throttle targeting the TV IP.  Returns True on success.end0tcqdiscdeldevrootTr   r   addhandlez1:htbdefault10)r   r   classparentclassidz1:10rate1000mbitz1:20kbitceilfilterprotocolr(   priorx   u32matchdstz/32flowidzthrottle_apply failed: %sNF)r    r   r   r   r?   r   )r*  r+  r1  rC   s       r   throttle_applyrG    sT   !Z\E
CgueS&A&*	,QgueS&(Dy$0&*$	8 	gueS(D)vz;&*$	8 	gueS(D)v.1A/F>"2$ 79 '+$	8 	huc8T:fc5'4 'x9 '+$	8  /3s   "C B C 	C?C::C?
sound_filer   c                     	 t        ddd|d       t        ddddd|  d       y	# t        $ r }t        j                  d
|       Y d}~yd}~ww xY w)zFPlay a sound file on the Xiaomi Sound Pro speaker via HA media_player.rU   z!/services/media_player/volume_setzmedia_player.sound_pro_1264)r  volume_levelz!/services/media_player/play_mediamusicz!http://192.168.1.187:8888/sounds/)r  media_content_typemedia_content_idTzspeaker_play_sound failed: %sNF)r  r   r?   r   )rH  r   rC   s      r   speaker_play_soundrN    sj    v:6"=
 	 	v:6")"CJ< P=
 	
  3Q7s   $' 	AAA)zknock_2.mp3zcough_single.mp3zthroat_final2.mp3ztongue_click.mp3zcustom_20to22.mp3zexhale_loud.mp3zexhale_soft.mp3c                      d} 	 t        j                  dddd| dgd       y# t        $ r }t        j	                  d	|       Y d
}~yd
}~ww xY w)z7Remove all tc throttle rules.  Returns True on success.r-  r.  r/  r0  r1  r2  Tr3  zthrottle_remove failed: %sNF)r   r   r   r?   r   )r1  rC   s     r   throttle_removerP  8  sO    
CgueS&A&*	, 0!4s   " 	AAAr"   )rY   N)g      @)g      @N)z/tmp/tv_screen.pngN)Ni  )g333333?)K__doc__concurrent.futuresr   r>   loggingosri   r   r   r   r   urllib.requestr^   r;   requests.authr   urllib3disable_warnings
exceptionsInsecureRequestWarning	getLoggerr?   r   r#   r$   r2   r]   r[   r/   	APP_NAMESr  r   _f_line
startswithr   r   r   r   strr    r'   r,   r0   r3   r   dictrD   boolrI   rL   rf   rk   rn   ru   r|   r   r   r   r   r   r   r   tupler  r  r  listr  r$  r)  rG  rN  SPEAKER_SOUNDSrP  r%   r&   r   <module>rf     s      	 	  
     (    ++BB Cg% 	P	<+   &/")6%##2(3,2%+"-&&'	&  		&	'2E 34#(;;=#6#6sA#>q#A   
( )
C 
5. 5" " "
%C %3 %
E# E E
 
u 
c 
TD[ 
	# 	T 	E 	3 	$ 	5 5 5 5# 3 C 3: $*3 *#* *?C ?S ?D ?=D =c =T = s ) )t )5 5t 5t6 6&5 C 	C 	4 	  sUYz &G C G 5 G TC S D D3 c   (d t    7 7 7&# c T <3   $	 	g 
(	'  		s0   H H3%HH HH HH