
    L0&jR                     h   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mZ ddlm	Z	m
Z
mZmZ 	 ddlZdZn# e$ r dZdZY nw xY wddlmZmZ ddlmZmZmZmZ  ej        e          Zd	efd
Z G d de          Zdddddededee         dee         ded	e
ee	f         fdZ d	efdZ!d Z"ddZ#dS )a  
Home Assistant platform adapter.

Connects to the HA WebSocket API for real-time event monitoring.
State-change events are converted to MessageEvent objects and forwarded
to the agent for processing.  Outbound messages are delivered as HA
persistent notifications.

Requires:
- aiohttp (already in messaging extras)
- HASS_TOKEN env var (Long-Lived Access Token)
- HASS_URL env var (default: http://homeassistant.local:8123)
    N)datetime)AnyDictOptionalSetTF)PlatformPlatformConfig)BasePlatformAdapterMessageEventMessageType
SendResultreturnc                  D    t           sdS t          j        d          sdS dS )zBCheck if Home Assistant dependencies are available and configured.F
HASS_TOKENT)AIOHTTP_AVAILABLEosgetenv     L/home/ubuntu/.hermes/hermes-agent/plugins/platforms/homeassistant/adapter.pycheck_ha_requirementsr   *   s+     u9\"" u4r   c                   h    e Zd ZdZdZg dZdef fdZdefdZ	de
fdZde
fd	ZddZddZddZddZdeeef         dd
fdZededeeef         deeef         dee         fd            Z	 	 ddededee         deeeef                  def
dZddedd
fdZdedeeef         fdZ xZS )HomeAssistantAdapterz
    Home Assistant WebSocket adapter.

    Subscribes to ``state_changed`` events and forwards them as
    MessageEvent objects.  Supports domain/entity filtering and
    per-entity cooldowns to avoid event floods.
    i   )   
      <   configc                 $   t                                          |t          j                   d | _        d | _        d | _        d | _        d| _        |j	        pi }|j
        pt          j        dd          }|                    d          pt          j        dd          }|                    d          | _        || _        t#          |                    dg                     | _        t#          |                    d	g                     | _        t#          |                    d
g                     | _        t+          |                    dd                    | _        t/          |                    dd                    | _        i | _        d S )Nr   r    urlHASS_URLzhttp://homeassistant.local:8123/watch_domainswatch_entitiesignore_entities	watch_allFcooldown_secondsr   )super__init__r   HOMEASSISTANT_session_ws_rest_session_listen_task_msg_idextratokenr   r   getrstrip	_hass_url_hass_tokenset_watch_domains_watch_entities_ignore_entitiesbool
_watch_allint_cooldown_seconds_last_event_time)selfr   r1   r2   r!   	__class__s        r   r*   zHomeAssistantAdapter.__init__A   sT   !7888 <@@D@D48 ";	, ; ;iiZ")J8Y"Z"Z!jjoo % ),EIIor,J,J(K(K),UYY7G-L-L)M)M*-eii8I2.N.N*O*O $UYY{E%B%B C C&)%))4F*K*K&L&L 35r   r   c                 0    | xj         dz  c_         | j         S )z%Return the next WebSocket message ID.   )r0   r@   s    r   _next_idzHomeAssistantAdapter._next_id\   s    |r   c                   K   t           s"t                              d| j                   dS | j        s"t                              d| j                   dS 	 |                                  d{V }|sdS t          j        t          j        d                    | _	        | j
        s.| j        s'| j        s t                              d| j                   t          j        |                                           | _        d	| _        t                              d
| j        | j                   d	S # t(          $ r,}t                              d| j        |           Y d}~dS d}~ww xY w)z4Connect to HA WebSocket API and subscribe to events.z4[%s] aiohttp not installed. Run: pip install aiohttpFz[%s] No HASS_TOKEN configuredNr   totaltimeoutz[%s] No watch_domains, watch_entities, or watch_all configured. All state_changed events will be dropped. Configure filters in your HA platform config to receive events.Tz[%s] Connected to %sz[%s] Failed to connect: %s)r   loggerwarningnamer6   _ws_connectaiohttpClientSessionClientTimeoutr.   r8   r9   r<   asynciocreate_task_listen_loopr/   _runninginfor5   	Exceptionerror)r@   successes      r   connectzHomeAssistantAdapter.connecte   s       	NNQSWS\]]]5 	NN:DIFFF5	 ,,........G u ")!6-B777" " "D
 & t/C DO A I	   !( 3D4E4E4G4G H HD DMKK.	4>JJJ4 	 	 	LL5ty!DDD55555	s   D0 4B:D0 0
E&:!E!!E&c                   K   | j                             dd                              dd          }| d}t          j        t          j        d                    | _        | j                            |dd	           d
{V | _        | j                                         d
{V }|	                    d          dk    rJt                              d|	                    d                     |                                  d
{V  dS | j                            d| j        d           d
{V  | j                                         d
{V }|	                    d          dk    r7t                              d|           |                                  d
{V  dS |                                 }| j                            |ddd           d
{V  | j                                         d
{V }|	                    d          s7t                              d|           |                                  d
{V  dS dS )z0Establish WebSocket connection and authenticate.zhttps://zwss://zhttp://zws://z/api/websocketr   rG   rI   )	heartbeatrJ   Ntypeauth_requiredzExpected auth_required, got: %sFauth)r^   access_tokenauth_okzAuth failed: %ssubscribe_eventsstate_changed)idr^   
event_typerY   z!Failed to subscribe to events: %sT)r5   replacerO   rP   rQ   r,   
ws_connectr-   receive_jsonr3   rK   rX   _cleanup_ws	send_jsonr6   rE   )r@   ws_urlmsgsub_ids       r   rN   z HomeAssistantAdapter._ws_connect   s     ''
H==EEiQXYY***-)333
 
 
 11&BPR1SSSSSSSS H))++++++++776??o--LL:CGGFOOLLL""$$$$$$$$$5 h   ,"
 "
   	 	 	 	 	 	 	 H))++++++++776??i''LL*C000""$$$$$$$$$5 h  &)"
 "
   	 	 	 	 	 	 	 H))++++++++wwy!! 	LL<cBBB""$$$$$$$$$5tr   Nc                    K   | j         r+| j         j        s| j                                          d{V  d| _         | j        r+| j        j        s| j                                         d{V  d| _        dS )zClose WebSocket and session.N)r-   closedcloser,   rD   s    r   rj   z HomeAssistantAdapter._cleanup_ws   s      8 	#DHO 	#(.."""""""""= 	(!5 	(-%%'''''''''r   c                   K   d| _         | j        rD| j                                         	 | j         d{V  n# t          j        $ r Y nw xY wd| _        |                                  d{V  | j        r+| j        j        s| j                                         d{V  d| _        t          
                    d| j                   dS )zDisconnect from Home Assistant.FNz[%s] Disconnected)rU   r/   cancelrR   CancelledErrorrj   r.   rp   rq   rK   rV   rM   rD   s    r   
disconnectzHomeAssistantAdapter.disconnect   s      	%$$&&&''''''''')    $D          	-d&8&? 	-$**,,,,,,,,,!'33333s   9 A
Ac                   K   d}| j         rz	 |                                  d{V  nI# t          j        $ r Y dS t          $ r+}t
                              d| j        |           Y d}~nd}~ww xY w| j         sdS | j        t          |t          | j                  dz
                     }t
                              d| j        |           t          j        |           d{V  |dz  }	 |                                  d{V  |                                  d{V }|r"d}t
                              d| j                   n8# t          $ r+}t
                              d| j        |           Y d}~nd}~ww xY w| j         xdS dS )z,Main event loop with automatic reconnection.r   Nz[%s] WebSocket error: %srC   z[%s] Reconnecting in %ds...z[%s] Reconnectedz[%s] Reconnection failed: %s)rU   _read_eventsrR   rt   rW   rK   rL   rM   _BACKOFF_STEPSminlenrV   sleeprj   rN   )r@   backoff_idxrZ   delayrY   s        r   rT   z!HomeAssistantAdapter._listen_loop   s     m 	MI'')))))))))))    I I I949aHHHHHHHHI =  'KT=P9Q9QTU9U(V(VWEKK5ty%HHH-&&&&&&&&&1KM&&((((((((( $ 0 0 2 2222222 ?"#KKK 2DI>>> M M M=ty!LLLLLLLLM/ m 	M 	M 	M 	M 	Ms3   ) A/	A/!A**A/,AE 
E:!E55E:c                    K   | j         | j         j        rdS | j         2 3 d{V }|j        t          j        j        k    r	 t          j        |j                  }|	                    d          dk    r/| 
                    |	                    di                      d{V  # t          j        $ r+ t                              d|j        dd                    Y w xY w|j        t          j        j        t          j        j        hv r dS 6 dS )z.Read events from WebSocket until disconnected.Nr^   eventzInvalid JSON from HA WS: %s   )r-   rp   r^   rO   	WSMsgTypeTEXTjsonloadsdatar3   _handle_ha_eventJSONDecodeErrorrK   debugCLOSEDERROR)r@   ws_msgr   s      r   rw   z!HomeAssistantAdapter._read_events   s8     8txF H 		 		 		 		 		 		 		&{g/444S:fk22Dxx''722"33DHHWb4I4IJJJJJJJJJ+ S S SLL!>DSD@QRRRRRS!2!97;L;R SSS T %HHs   DA!B##7CCr   c                 R  K   |                     di           }|                     dd          }|sdS || j        v rdS d|v r|                    d          d         nd}| j        s| j        r+| j        r	|| j        v nd}| j        r	|| j        v nd}|s|sdS n	| j        sdS t          j                    }| j                             |d          }||z
  | j        k     rdS || j        |<   |                     di           }	|                     d	i           }
| 	                    ||	|
          }|sdS | 
                    d
dddd          }t          |t          j        |d| dt          |           t          j                              }|                     |           d{V  dS )z2Process a state_changed event from Home Assistant.r   	entity_idr    N.r   F	old_state	new_state	ha_eventsHome Assistant EventschannelhomeassistantHome Assistant)chat_id	chat_name	chat_typeuser_id	user_nameha__)textmessage_typesource
message_id	timestamp)r3   r:   splitr8   r9   r<   timer?   r>   _format_state_changebuild_sourcer   r   r   r=   r   nowhandle_message)r@   r   
event_datar   domaindomain_matchentity_matchr   lastr   r   messager   	msg_events                 r   r   z%HomeAssistantAdapter._handle_ha_event  s     YYvr**
#R88	 	F ---F -09,<,<%%a((" 	$"6 	<@<OZ6T%888UZL@D@T_9(<<<Z_L   	F ikk$((A66$J$000F+.i( NN;33	NN;33	++Iy)LL 	F ""-#& # 
 
 !$)3Y33S33lnn
 
 
	 !!),,,,,,,,,,,r   r   r   r   c                    |sdS |r|                     dd          nd}|                     dd          }||k    rdS |                     di                                d|           }d| v r|                     d          d         nd}|d	k    rT|                     di           }|                     d
d          }|                     dd          }	d| d| d| d| d|	 dS |dk    r9|                     di                                dd          }
d| d| |
 d| |
 S |dk    rd| d|dk    rdnd d|dk    rdnd dS |dv rd| d|dk    rdnd S |d k    rd| d!| d| d"S d| d#|  d$| d| d"	S )%z@Convert a state_changed event into a human-readable description.Nstateunknown
attributesfriendly_namer   r   r    climatecurrent_temperature?temperaturez[Home Assistant] z: HVAC mode changed from 'z' to 'z' (current: z
, target: )sensorunit_of_measurementz: changed from z to binary_sensor: on	triggeredclearedz (was >   fanlightswitchz	: turned offalarm_control_panelz: alarm state changed from ''z (z): changed from ')r3   r   )r   r   r   old_valnew_valr   r   attrstemptargetunits              r   r   z)HomeAssistantAdapter._format_state_change@  s     	47@O)--333i--33 g4!lB77;;OYWW,/9,<,<%%a((" YMM,33E992C88DYY}c22FRM R RR R#*R R8<R RHNR R R
 X==r22667LbQQD6M 6 66 6 6&-6/36 6
 _$$IM I I")T//;;yI I'.$II I I ///8M 8 8"d??448 8
 ***.M . .. .#*. . .7 7 7 7 7$7 7,37 7 7	
r   r   contentreply_tometadatac           	      P  K   | j          d}d| j         dd}d|d| j                 d}	 | j        r| j                            |||t          j        d	          
          4 d{V 	 }|j        dk     rAt          dt          j
                    j        dd                   cddd          d{V  S |                                 d{V }	t          dd|j         d|	           cddd          d{V  S # 1 d{V swxY w Y   dS t          j                    4 d{V }
|
                    |||t          j        d	          
          4 d{V 	 }|j        dk     rSt          dt          j
                    j        dd                   cddd          d{V  cddd          d{V  S |                                 d{V }	t          dd|j         d|	           cddd          d{V  cddd          d{V  S # 1 d{V swxY w Y   	 ddd          d{V  dS # 1 d{V swxY w Y   dS # t          j        $ r t          dd          cY S t           $ r(}t          dt#          |                    cY d}~S d}~ww xY w)zSend a notification via HA REST API (persistent_notification.create).

        Uses the REST API instead of WebSocket to avoid a race condition
        with the event listener loop that reads from the same WS connection.
        z,/api/services/persistent_notification/createBearer application/jsonAuthorizationzContent-TypezHermes AgentN)titler   r   rG   )headersr   rJ   i,  T   )rY   r   FzHTTP r   )rY   rX   z"Timeout sending notification to HA)r5   r6   MAX_MESSAGE_LENGTHr.   postrO   rQ   statusr   uuiduuid4hexr   rP   rR   TimeoutErrorrW   str)r@   r   r   r   r   r!   r   payloadrespbodysessionrZ   s               r   sendzHomeAssistantAdapter.send  s=      MMM9t'799.
 

 $7 778
 

	;! b-22# #1;;;	 3   
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^
 {S(()$4:<<CSTWUWTWCXYYY
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ &*YY[[000000)%?\t{?\?\VZ?\?\]]]
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ 
^ #022 b b b b b b bg&|| '$ ' 5B ? ? ?	  ,     
b 
b 
b 
b 
b 
b 
b 
b
 ;,,#-dtz||GWX[Y[X[G\#]#]#]
b 
b 
b 
b 
b 
b 
b 
b 
b 
b 
b 
b 
bb b b b b b b b b b b b b b *.#4#4#4#4#4#4D#-eC`4;C`C`Z^C`C`#a#a#a
b 
b 
b 
b 
b 
b 
b 
b 
b 
b 
b 
b 
bb b b b b b b b b b b b b b
b 
b 
b 
b 
b 
b 
b 
b 
b 
b 
b 
b 
b 
b 
bb b b b b b b b b b b b b b b b b b b b b b b b b b b b b b # 	Y 	Y 	Ye3WXXXXXX 	; 	; 	;e3q66:::::::::	;s   >I ,:D&I 95D.I 
DI DI I -3H?!:HH?-I  5H5H?I 
H$	$H?'H$	(H?,I ?
I		I I	I  J%4	J%=J J% J%c                 
   K   dS )z'No typing indicator for Home Assistant.Nr   )r@   r   r   s      r   send_typingz HomeAssistantAdapter.send_typing  s
        r   c                    K   dd| j         dS )z-Return basic info about the HA event channel.r   r   )rM   r^   r!   )r5   )r@   r   s     r   get_chat_infoz"HomeAssistantAdapter.get_chat_info  s"       ,>
 
 	
r   r   N)NN)N)__name__
__module____qualname____doc__r   rx   r	   r*   r=   rE   r;   r[   rN   rj   ru   rT   rw   r   r   r   r   staticmethodr   r   r   r   r   r   __classcell__)rA   s   @r   r   r   3   s@          %__N5~ 5 5 5 5 5 56#    %t % % % %N-4 - - - -^   4 4 4 4*M M M M<   8-DcN 8-t 8- 8- 8- 8-t ;
;
S>;
 S>;
 
#	;
 ;
 ;
 \;
J #'-14; 4;4; 4; 3-	4;
 4S>*4; 
4; 4; 4; 4;l6 6 6 6 6 6 6
3 
4S> 
 
 
 
 
 
 
 
r   r   )	thread_idmedia_filesforce_documentr   r   r   r   r   c                  K   t           sddiS t          | di           pi }|                    d          pt          j        dd                              d          }t          | dd	          pt          j        d
d                                          }|r|sddiS | d}	d| dd}
||d}	 t          j        t          j	        d                    4 d	{V 	 }|
                    |	|
|          4 d	{V }|j        dvrM|                                 d	{V }dd|j         d| icd	d	d	          d	{V  cd	d	d	          d	{V  S 	 d	d	d	          d	{V  n# 1 d	{V swxY w Y   d	d	d	          d	{V  n# 1 d	{V swxY w Y   dd|dS # t          j        $ r ddicY S t          $ r}dd| icY d	}~S d	}~ww xY w)u  Send a notification via the HA ``notify.notify`` service without a
    live gateway adapter.

    Used by ``tools/send_message_tool._send_via_adapter`` when the gateway
    runner is not in this process (typical for cron jobs running
    out-of-process).  The HTTP path is the same one the legacy
    ``_send_homeassistant`` helper used in ``tools/send_message_tool.py``
    before this migration.

    Reads ``HASS_TOKEN`` from ``pconfig.token`` (set by the gateway config
    loader from env) and falls back to the ``HASS_TOKEN`` env var.  Server
    URL comes from ``pconfig.extra["url"]`` (seeded by the env loader in
    ``gateway/config.py``) or the ``HASS_URL`` env var.

    ``thread_id``, ``media_files`` and ``force_document`` are accepted for
    signature parity with other standalone senders.  HA notifications have
    no native threading or attachment model — these arguments are ignored.
    rX   z/aiohttp not installed. Run: pip install aiohttpr1   r!   r"   r    r#   r2   Nr   zHHome Assistant standalone send: HASS_URL and HASS_TOKEN must both be setz/api/services/notify/notifyr   r   r   )r   r   r   rG   rI   )r   r   >   r      zHome Assistant API error (z): Tr   )rY   platformr   z.Timeout sending notification to Home AssistantzHome Assistant send failed: )r   getattrr3   r   r   r4   striprO   rP   rQ   r   r   r   rR   r   rW   )pconfigr   r   r   r   r   r1   hass_urlr2   r!   r   r   r   r   r   rZ   s                   r   _standalone_sendr     s     6  LJKKGWb))/RE		%  =BIj"$=$=EEcJJHWgt,,K	,0K0KRRTTE 
5 
#
 	
 
2
2
2C*5*** G "W55G=()333
 
 
 
	 
	 
	 
	 
	 
	 
	 
	||Cw|GG       4;j00!%,,,,,,DOOOOO            
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 1                          
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 '
 
 	

  K K KIJJJJ = = =;;;<<<<<<<=sx   1.F#  F?2E)1FF# F)
E3	3F6E3	7F:F# 
FF# FF# #G8	GGGGc                 z    ddl m} t          |                    d          pd                                          S )aq  Home Assistant is considered connected when ``HASS_TOKEN`` is set.

    Looks up via ``hermes_cli.gateway.get_env_value`` at call time (not via
    the plugin's own bound import) so tests that patch
    ``gateway_mod.get_env_value`` can suppress ambient ``HASS_TOKEN`` env
    vars.  Matches what the legacy connected-platforms check did before
    this migration.
    r   Nr   r    )hermes_cli.gatewaygatewayr;   get_env_valuer   )r   gateway_mods     r   _is_connectedr     sC     -,,,,,**<88>BEEGGHHHr   c                      t          |           S )zKFactory wrapper that constructs HomeAssistantAdapter from a PlatformConfig.)r   )r   s    r   _build_adapterr  '  s    '''r   c                     |                      ddt          t          t          dgdt          t
          j        dd           dS )	u:   Plugin entry point — called by the Hermes plugin system.r   r   r   zpip install aiohttpu   🏠T)rM   labeladapter_factorycheck_fnis_connectedrequired_envinstall_hintstandalone_sender_fnmax_message_lengthemojiallow_update_commandN)register_platformr  r   r   r   r   r   )ctxs    r   registerr  ,  sS    &&""^*
 . 0B!%      r   r   )$r   rR   r   loggingr   r   r   r   typingr   r   r   r   rO   r   ImportErrorgateway.configr   r	   gateway.platforms.baser
   r   r   r   	getLoggerr   rK   r;   r   r   r   listr   r   r  r  r   r   r   <module>r     sD       				         + + + + + + + + + + + +NNN   GGG 4 3 3 3 3 3 3 3            
	8	$	$t    N
 N
 N
 N
 N
. N
 N
 N
v  $"& D= D= D=D= D=
 }D= $D= D= 
#s(^D= D= D= D=X
IT 
I 
I 
I 
I$( ( (
     s   5 	A A