44from homeassistant import util
55import asyncio
66from typing import Final
7+ from jvc_projector import JVCCommunicationError as comm_error
8+ import datetime
79
810JVC_RETRIES : Final = "max_retries"
911
1921_LOGGER = logging .getLogger (__name__ )
2022
2123
22- def setup_platform (hass , config , add_entities , discovery_info = None ):
24+ def setup_platform (hass , config , add_entities , discovery_info = None ) -> None :
2325 """Set up the remote."""
2426 if config .get (CONF_HOST ) is not None :
2527 host = config .get (CONF_HOST )
@@ -39,41 +41,59 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
3941class JVCRemote (remote .RemoteEntity ):
4042 """Home assistant JVC remote representation"""
4143
42- def __init__ (self , name , host , password , port , delay , timeout , retries ):
44+ def __init__ (
45+ self ,
46+ name : str | None ,
47+ host : str ,
48+ password : str | None ,
49+ port : int | None ,
50+ delay : int | None ,
51+ timeout : float | None ,
52+ retries : int | None ,
53+ ) -> None :
4354 """Initialize the Remote."""
4455 from jvc_projector import JVCProjector
45- from jvc_projector import JVCPoweredOffError
4656
4757 self ._name = name or DEVICE_DEFAULT_NAME
4858 self ._host = host
4959 self ._password = password
50- self ._last_command_sent = None
60+
61+ self ._last_commands_sent = None
5162 self ._jvc = JVCProjector (host , password , port , delay , timeout , retries )
52- self ._state = None
53- self ._power_state = "N/A"
63+ self ._power_state = self ._jvc .power_state ()
64+ self ._state = True if self ._power_state == "lamp_on" else False
65+ self ._signal_state = (
66+ "unknown" if not self ._state else self ._jvc .command ("signal" )
67+ )
68+ self ._input_state = "unknown" if not self ._state else self ._jvc .command ("input" )
69+ self ._lamp_state = "unknown" if not self ._state else self ._jvc .command ("lamp" )
70+ self ._picture_mode_state = (
71+ "unknown" if not self ._state else self ._jvc .command ("picture_mode" )
72+ )
73+ self ._last_commands_response = None
5474 self .state_lock = asyncio .Lock ()
5575
5676 @property
57- def should_poll (self ):
77+ def should_poll (self ) -> bool :
5878 # poll the device so we know if it was state changed
5979 # via an external method, like the physical remote
6080 return True
6181
6282 @property
63- def name (self ):
83+ def name (self ) -> str :
6484 """Return the name of the device if any."""
6585 return self ._name
6686
6787 async def async_update (self ):
6888 await self .async_update_state ()
6989
7090 @property
71- def is_on (self ):
91+ def is_on (self ) -> bool :
7292 """Return true if remote is on."""
7393 return self ._state
7494
7595 @property
76- def extra_state_attributes (self ):
96+ def extra_state_attributes (self ) -> dict :
7797 """Return device state attributes."""
7898
7999 if self ._power_state in ["lamp_on" , "reserved" ]:
@@ -82,51 +102,72 @@ def extra_state_attributes(self):
82102 self ._state = False
83103
84104 return {
85- "last_command_sent" : self ._last_command_sent
86- if self ._last_command_sent is not None
87- else "N/A" ,
105+ "last_commands_sent" : self ._last_commands_sent ,
106+ "last_commands_response" : self ._last_commands_response ,
88107 "power_state" : self ._power_state ,
108+ "signal_state" : self ._signal_state ,
109+ "input_state" : self ._input_state ,
110+ "lamp_state" : self ._lamp_state ,
111+ "picture_mode" : self ._picture_mode_state ,
89112 }
90113
91- async def async_turn_on (self , ** kwargs ):
114+ async def async_turn_on (self , ** kwargs ) -> None :
92115 """Turn the remote on."""
116+ if self ._state :
117+ return
93118 await self .async_send_command ("power-on" )
94119 self ._state = True
95120
96- async def async_turn_off (self , ** kwargs ):
121+ async def async_turn_off (self , ** kwargs ) -> None :
97122 """Turn the remote off."""
123+ if not self ._state :
124+ return
98125 await self .async_send_command ("power-off" )
99126 self ._state = False
100127
101- async def async_send_command (self , command , ** kwargs ):
128+ async def async_send_command (self , command , delay_secs = 0 , ** kwargs ) -> None :
102129 """Send a command to a device."""
103130
104131 async with self .state_lock :
105132 if type (command ) != list :
106133 command = [command ]
107134
135+ self ._last_commands_sent = []
136+ self ._last_commands_response = []
137+
108138 for com in command :
109139 _LOGGER .info (f"sending command: { com } " )
110140 try :
111- command_sent = await self .hass .async_add_executor_job (
141+ resp = await self .hass .async_add_executor_job (
112142 self ._jvc .command , (com )
113143 )
114- except JVCPoweredOffError as e :
144+ if resp is None :
145+ resp = "success"
146+ self ._last_commands_sent .append (com )
147+ self ._last_commands_response .append (resp )
148+ except comm_error as e :
115149 # The projector is powered off
116- command_sent = False
117- _LOGGER .error (f"Failed to send command, projector is powered off" )
150+ _LOGGER .warning (
151+ f"Failed to send command, could not communicate with projector: { repr (e )} \n This could happen if the command only works when the projector is on but the current state is off, or the timeout setting is too low"
152+ )
153+ self ._last_commands_sent .append (com )
154+ self ._last_commands_response .append ("failed" )
118155 except Exception as e :
119156 # when an error occured during sending, command execution probably failed
120- command_sent = False
157+ _LOGGER .error (f"Unknown error, abort sending commands" )
158+ self ._last_commands_sent = ["unknown" ]
159+ self ._last_commands_response = ["failed" ]
121160 raise e
122161
123- if not command_sent :
124- self ._last_command_sent = "N/A"
125- continue
126- else :
127- self ._last_command_sent = com
162+ self .async_write_ha_state ()
163+ delta = (
164+ datetime .datetime .now () - self ._jvc .last_command_time
165+ ).total_seconds ()
166+ if delay_secs >= delta :
167+ _LOGGER .debug (f"waiting { delay_secs } seconds before next command" )
168+ await asyncio .sleep (delay_secs - delta )
128169
129- async def async_update_state (self ):
170+ async def async_update_state (self ) -> None :
130171 """ "Update the state with the Power Status (if available)"""
131172
132173 # do nothing until lock is released
@@ -137,6 +178,29 @@ async def async_update_state(self):
137178 self ._power_state = await self .hass .async_add_executor_job (
138179 self ._jvc .power_state
139180 )
181+
182+ if self ._power_state != "lamp_on" :
183+ (
184+ self ._input_state ,
185+ self ._signal_state ,
186+ self ._picture_mode_state ,
187+ self ._lamp_state ,
188+ ) = ("unknown" , "unknown" , "unknown" , "unknown" )
189+ return
190+
191+ self ._input_state = await self .hass .async_add_executor_job (
192+ self ._jvc .command , ("input" )
193+ )
194+ self ._signal_state = await self .hass .async_add_executor_job (
195+ self ._jvc .command , ("signal" )
196+ )
197+ self ._picture_mode_state = await self .hass .async_add_executor_job (
198+ self ._jvc .command , ("picture_mode" )
199+ )
200+ self ._lamp_state = await self .hass .async_add_executor_job (
201+ self ._jvc .command , ("lamp" )
202+ )
203+
140204 except Exception as e :
141205 self ._power_state = "unknown"
142206 raise e
0 commit comments