Skip to content

Commit e1d3852

Browse files
authored
v2.0.0a3 Bugfixes, better init handling (#19)
- #13: if we can't connect to projector on init, there will be a warning logged, but initialisation will continue as it may become available later - #18: mostly an upstream fix, if an unknown response which can't be decoded into a pretty string is sent by the projector, instead of a `KeyError`, it will now log a warning and return the raw response ascii code. - add picture modes, lamp power 'mid', anamorphic 'd', extra lens memory options for the NZ series
1 parent 3e8a822 commit e1d3852

File tree

3 files changed

+96
-15
lines changed

3 files changed

+96
-15
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,12 @@ And use type `Integration`. Once installed, proceed to follow README in the 'jvc
149149
#### Command Strings:
150150
These command strings will perform an operation on the projector. The corresponding entry in the `last_commands_response` attribute will be `success` if the operation succeeded, or `failed` otherwise. Values in '{}' indicate multiple choices.
151151
* **Power:** `power-{on,off}` (recommended to use the `remote.turn_on` and `remote.turn_off` services).
152-
* **Lens Memory:** `memory-{1-5}`
152+
* **Lens Memory:** `memory-{1-10}` (Not all projectors will have all 10)
153153
* **Source:** `input-{hdmi1, hdmi2}`
154-
* **Picture Mode:** `picture_mode-{cinema, natural, film, THX, hlg, hdr10}`, `picture_mode-{user1-user6}`
154+
* **Picture Mode:** `picture_mode-{cinema, natural, film, THX, hlg, hdr10}`, `picture_mode-{user1-user6}`, **NZ Series:** `frame_adapt_hdr`, `hdr10p`, `pana_pq`
155155
* **Low Latency Mode:** `low_latency-{on, off}`
156156
* **Mask** `mask-{off, custom1, custom2, custom3}`
157-
* **Lamp** `lamp-{high,low}`
157+
* **Lamp** `lamp-{high,low}`, **NZ Series:** `mid`
158158
* **Menu Controls** `menu-{menu, up, down, left, right, ok, back}`
159159
* **Lens Aperture** `aperture-{off, auto1, auto2}`
160160
* **Anamorphic** `anamorphic-{off, a, b, c}`
@@ -167,7 +167,7 @@ These command strings will store the response from the projector in the correspo
167167
* **Mask** `mask`, returns from `off, custom1, custom2, custom3`
168168
* **Lamp** `lamp`, returns from `high, low`
169169
* **Lens Aperture** `aperture`, returns from `off, auto1, auto2`
170-
* **Anamorphic** `anamorphic`, returns from `off, a, b, c`
170+
* **Anamorphic** `anamorphic`, returns from `off, a, b, c`, **NZ Series:** `d`
171171
* **MAC Address** `macaddr`, returns the projector's MAC address
172172
* **Model Info** `modelinfo`, returns the model string of the projector
173173

custom_components/jvcprojector/manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
"domain": "jvcprojector",
33
"name": "JVC Projector IP Control",
44
"documentation": "https://github.com/bezmi/hass_custom_components",
5-
"requirements": ["jvc-projector-remote == 0.2.0a2"],
5+
"requirements": ["jvc-projector-remote == 0.2.1"],
66
"dependencies": [],
77
"codeowners": [
88
"@bezmi"
99
],
10-
"version": "2.0.0a1"
10+
"version": "2.0.0a3"
1111
}

custom_components/jvcprojector/remote.py

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
import asyncio
66
from typing import Final
77
from jvc_projector_remote import JVCCommunicationError as comm_error
8+
from jvc_projector_remote import JVCConfigError as conf_error
9+
from jvc_projector_remote import JVCCannotConnectError as connect_error
10+
from jvc_projector_remote import JVCPoweredOffError as power_error
11+
from jvc_projector_remote import JVCProjector
812
import datetime
913

1014
JVC_RETRIES: Final = "max_retries"
@@ -52,15 +56,28 @@ def __init__(
5256
retries: int | None,
5357
) -> None:
5458
"""Initialize the Remote."""
55-
from jvc_projector_remote import JVCProjector
5659

57-
self._name = name or DEVICE_DEFAULT_NAME
58-
self._host = host
59-
self._password = password
60+
self._conf_name = name or DEVICE_DEFAULT_NAME
61+
self._conf_host = host
62+
self._conf_password = password
63+
self._conf_port = 20554 if port is None else port
64+
self._conf_delay = delay
65+
self._conf_timeout=timeout
66+
self._conf_retries = retries
6067

6168
self._last_commands_sent = None
62-
self._jvc = JVCProjector(host, password, port, delay, timeout, retries)
63-
self._power_state = self._jvc.power_state()
69+
70+
try:
71+
self._jvc = JVCProjector(self._conf_host, self._conf_password, self._conf_port, self._conf_delay, self._conf_timeout, self._conf_retries)
72+
except conf_error as e:
73+
_LOGGER.warning(f"Couldn't set up the componenent due to the following error")
74+
raise e
75+
76+
self._power_state = "not_connected" if not self._jvc.validate_connection() else self._jvc.power_state()
77+
78+
if self._power_state == "not_connected":
79+
_LOGGER.warning(f"Initial connection test to the projector at {self._conf_host}:{self._conf_port} failed. Please check your configuration.")
80+
6481
self._state = True if self._power_state == "lamp_on" else False
6582
self._signal_state = (
6683
"unknown" if not self._state else self._jvc.command("signal")
@@ -82,7 +99,7 @@ def should_poll(self) -> bool:
8299
@property
83100
def name(self) -> str:
84101
"""Return the name of the device if any."""
85-
return self._name
102+
return self._conf_name
86103

87104
async def async_update(self):
88105
await self.async_update_state()
@@ -129,6 +146,19 @@ async def async_send_command(self, command, delay_secs=0, **kwargs) -> None:
129146
"""Send a command to a device."""
130147

131148
async with self.state_lock:
149+
150+
if self._power_state is "not_connected":
151+
_LOGGER.warning(f"The projector is not connected, cannot send command")
152+
(
153+
self._input_state,
154+
self._signal_state,
155+
self._picture_mode_state,
156+
self._lamp_state,
157+
) = ("unknown", "unknown", "unknown", "unknown")
158+
self._last_commands_sent = []
159+
self._last_commands_response = []
160+
return
161+
132162
if type(command) != list:
133163
command = [command]
134164

@@ -148,13 +178,25 @@ async def async_send_command(self, command, delay_secs=0, **kwargs) -> None:
148178
except comm_error as e:
149179
# The projector is powered off
150180
_LOGGER.warning(
151-
f"Failed to send command, could not communicate with projector: {repr(e)}\nThis 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"
181+
f"Sent command, received communication error: {repr(e)}"
152182
)
153183
self._last_commands_sent.append(com)
154184
self._last_commands_response.append("failed")
185+
except power_error as e:
186+
_LOGGER.warning(f"Sent command, received power error: {repr(e)}")
187+
self._last_commands_sent.append(com)
188+
self._last_commands_response.append("failed")
189+
except connect_error as e:
190+
# the projector is likely off at the mains
191+
_LOGGER.warning(f"The projector at {self._conf_host}:{self._conf_port} did not respond to the connection request: {repr(e)}")
192+
self._power_state = "not_connected"
193+
self._last_commands_sent = ["unknown"]
194+
self._last_commands_response = ["failed"]
195+
return
196+
155197
except Exception as e:
156198
# when an error occured during sending, command execution probably failed
157-
_LOGGER.error(f"Unknown error, abort sending commands")
199+
_LOGGER.error(f"Unhandled error, abort sending commands")
158200
self._last_commands_sent = ["unknown"]
159201
self._last_commands_response = ["failed"]
160202
raise e
@@ -174,6 +216,19 @@ async def async_update_state(self) -> None:
174216
if self.state_lock.locked():
175217
return
176218

219+
# in case the init of the JVCProjector object failed due to a JVCConfigError
220+
is_connected = await self.hass.async_add_executor_job(self._jvc.validate_connection)
221+
if not is_connected:
222+
_LOGGER.warning(f"Couldn't connect to the projector at the specified address: {self._conf_host}:{self._conf_port}. Ensure the configuration is correct.")
223+
self._power_state = "not_connected"
224+
(
225+
self._input_state,
226+
self._signal_state,
227+
self._picture_mode_state,
228+
self._lamp_state,
229+
) = ("unknown", "unknown", "unknown", "unknown")
230+
return
231+
177232
try:
178233
self._power_state = await self.hass.async_add_executor_job(
179234
self._jvc.power_state
@@ -186,6 +241,8 @@ async def async_update_state(self) -> None:
186241
self._picture_mode_state,
187242
self._lamp_state,
188243
) = ("unknown", "unknown", "unknown", "unknown")
244+
self._last_commands_sent = ["power"]
245+
self._last_commands_response = [self._power_state]
189246
return
190247

191248
self._input_state = await self.hass.async_add_executor_job(
@@ -211,6 +268,30 @@ async def async_update_state(self) -> None:
211268
self._jvc.command, ("lamp")
212269
)
213270

271+
except connect_error as e:
272+
_LOGGER.warning(f"The projector at {self._conf_host}:{self._conf_port} did not respond to the connection request.")
273+
self._power_state = "not_connected"
274+
(
275+
self._input_state,
276+
self._signal_state,
277+
self._picture_mode_state,
278+
self._lamp_state,
279+
) = ("unknown", "unknown", "unknown", "unknown")
280+
return
281+
except comm_error as e:
282+
_LOGGER.warning(
283+
f"Failed to update state, received communication error : {repr(e)}."
284+
)
285+
self._power_state = "unknown"
286+
(
287+
self._input_state,
288+
self._signal_state,
289+
self._picture_mode_state,
290+
self._lamp_state,
291+
) = ("unknown", "unknown", "unknown", "unknown")
292+
return
293+
214294
except Exception as e:
295+
_LOGGER.error(f"Unhandled error occured")
215296
self._power_state = "unknown"
216297
raise e

0 commit comments

Comments
 (0)