diff --git a/custom_components/jvc_projectors/manifest.json b/custom_components/jvc_projectors/manifest.json index 8dfdb61..b4c9363 100644 --- a/custom_components/jvc_projectors/manifest.json +++ b/custom_components/jvc_projectors/manifest.json @@ -4,7 +4,7 @@ "config_flow": false, "documentation": "https://www.home-assistant.io/integrations/jvc_projector", "requirements": [ - "jvc-projector-remote-improved>=1.2.4" + "jvc-projector-remote-improved>=1.2.5" ], "ssdp": [], "zeroconf": [], @@ -14,5 +14,5 @@ "@iloveicedgreentea" ], "iot_class": "local_polling", - "version": "1.2.4" + "version": "1.2.5" } \ No newline at end of file diff --git a/custom_components/jvc_projectors/remote.py b/custom_components/jvc_projectors/remote.py index a44b9ab..5cd8794 100644 --- a/custom_components/jvc_projectors/remote.py +++ b/custom_components/jvc_projectors/remote.py @@ -52,9 +52,7 @@ async def async_setup_platform( # Maybe make this user supplied in the future # timeout = config.get(CONF_TIMEOUT) # IF this is not high enough connections will start tripping over each other - # TODO: implement some kind of global locking SCAN_INTERVAL = config.get(CONF_SCAN_INTERVAL) - # Have HA fetch data first with True async_add_entities( [ JVCRemote(name, host, password), @@ -62,6 +60,7 @@ async def async_setup_platform( ) platform = entity_platform.async_get_current_platform() + # Register the services platform.async_register_entity_service( INFO_COMMAND, {}, f"service_async_{INFO_COMMAND}" ) @@ -89,6 +88,7 @@ def __init__( ) -> None: self._name = name self._host = host + # Timeout for connections. Everything takes less than 3 seconds to run if timeout is None: self.timeout = 5 else: @@ -100,6 +100,8 @@ def __init__( self._state = None self._ll_state = None # Because we can only have one connection at a time, we need to lock every command + # otherwise JVC's server implementation will cancel the running command + # and just confuse everything, then cause HA to freak out self._lock = asyncio.Lock() @property @@ -139,31 +141,39 @@ def is_on(self): async def async_turn_on(self, **kwargs): """Send the power on command.""" - if self._lock.locked(): - _LOGGER.debug("State is locked. Waiting to run command") - asyncio.sleep(3) + # if self._lock.locked(): + # _LOGGER.debug("State is locked. Waiting to run command") + # await asyncio.sleep(3) async with self._lock: - return await self.jvc_client.async_power_on() + _, success = await self.jvc_client.async_power_on() + if success: + self._state = True + else: + self._state = None async def async_turn_off(self, **kwargs): """Send the power off command.""" - if self._lock.locked(): - _LOGGER.debug("State is locked. Waiting to run command") - asyncio.sleep(3) + # if self._lock.locked(): + # _LOGGER.debug("State is locked. Waiting to run command") + # await asyncio.sleep(3) async with self._lock: - return await self.jvc_client.async_power_off() + _, success = await self.jvc_client.async_power_off() + if success: + self._state = False + else: + self._state = None async def _async_collect_updates(self): """ Run each update sequentially. Attributes to update should be added here """ # Okay clearly not enough locking - if self._lock.locked(): - _LOGGER.debug("State is locked. Waiting to get power state") - asyncio.sleep(3) + # if self._lock.locked(): + # _LOGGER.debug("State is locked. Waiting to get power state") + # await asyncio.sleep(3) async with self._lock: # Try to make it so if something is hanging, just end the connection @@ -173,10 +183,10 @@ async def _async_collect_updates(self): self._state = await self.jvc_client.async_is_on() except asyncio.TimeoutError: _LOGGER.error("Timed out getting power state") - - if self._lock.locked(): - _LOGGER.debug("State is locked. Waiting to get low latency state") - asyncio.sleep(3) + return + # if self._lock.locked(): + # _LOGGER.debug("State is locked. Waiting to get low latency state") + # await asyncio.sleep(3) async with self._lock: try: @@ -184,33 +194,40 @@ async def _async_collect_updates(self): self._ll_state = await self.jvc_client.async_get_low_latency_state() except asyncio.TimeoutError: _LOGGER.error("Timed out getting low latency state") + return async def async_update(self): """Retrieve latest state.""" - # lock to prevent concurrent connections - if self._lock.locked(): - _LOGGER.debug("State is locked. Waiting to run update") - asyncio.sleep(3) + # if self._lock.locked(): + # _LOGGER.debug("State is locked. Waiting to run update") + # await asyncio.sleep(3) - await self._async_collect_updates() + # await self._async_collect_updates() + self._state = await self.jvc_client.async_is_on() + # self._ll_state = await self.jvc_client.async_get_low_latency_state() async def async_send_command(self, command: list[str], **kwargs): """Send commands to a device.""" # Wait until unlocked so commmands dont cause a failure loop - if self._lock.locked(): - _LOGGER.debug("State is locked. Waiting to run command") - asyncio.sleep(3) + # if self._lock.locked(): + # _LOGGER.debug("State is locked. Waiting to run command") + # await asyncio.sleep(3) async with self._lock: - return await self.jvc_client.async_exec_command(command) + _, success = await self.jvc_client.async_exec_command(command) + if success: + if "power" in command and "on" in command: + self._state = True + if "power" in command and "off" in command: + self._state = False async def service_async_info(self) -> None: """ Brings up the info screen """ - if self._lock.locked(): - _LOGGER.debug("State is locked. Waiting to run command") - asyncio.sleep(3) + # if self._lock.locked(): + # _LOGGER.debug("State is locked. Waiting to run command") + # await asyncio.sleep(3) async with self._lock: return await self.jvc_client.async_info() @@ -219,9 +236,9 @@ async def service_async_gaming_mode_hdr(self) -> None: """ Sets optimal gaming modes """ - if self._lock.locked(): - _LOGGER.debug("State is locked. Waiting to run command") - asyncio.sleep(3) + # if self._lock.locked(): + # _LOGGER.debug("State is locked. Waiting to run command") + # await asyncio.sleep(3) async with self._lock: return await self.jvc_client.async_gaming_mode_hdr() @@ -230,9 +247,9 @@ async def service_async_gaming_mode_sdr(self) -> None: """ Sets optimal gaming modes """ - if self._lock.locked(): - _LOGGER.debug("State is locked. Waiting to run command") - asyncio.sleep(3) + # if self._lock.locked(): + # _LOGGER.debug("State is locked. Waiting to run command") + # await asyncio.sleep(3) async with self._lock: return await self.jvc_client.async_gaming_mode_sdr() @@ -241,9 +258,9 @@ async def service_async_hdr_picture_mode(self) -> None: """ Sets optimal HDR modes """ - if self._lock.locked(): - _LOGGER.debug("State is locked. Waiting to run command") - asyncio.sleep(3) + # if self._lock.locked(): + # _LOGGER.debug("State is locked. Waiting to run command") + # await asyncio.sleep(3) async with self._lock: return await self.jvc_client.async_hdr_picture_mode() @@ -252,9 +269,9 @@ async def service_async_sdr_picture_mode(self) -> None: """ Sets optimal SDR modes """ - if self._lock.locked(): - _LOGGER.debug("State is locked. Waiting to run command") - asyncio.sleep(3) + # if self._lock.locked(): + # _LOGGER.debug("State is locked. Waiting to run command") + # await asyncio.sleep(3) async with self._lock: return await self.jvc_client.async_sdr_picture_mode() diff --git a/readme.md b/readme.md index 8d69488..c5b6c91 100644 --- a/readme.md +++ b/readme.md @@ -3,15 +3,20 @@ This is the Home Assistant JVC Component implementing my [JVC library](https://github.com/iloveicedgreentea/jvc_projector_improved) ## Features + All the features in my [JVC library](https://github.com/iloveicedgreentea/jvc_projector_improved) including: -* Power -* Picture Modes -* Laser power and dimming -* Low Latency meta-functions -* Optimal gaming and movie setting meta-functions -* and so on + +- Power +- Picture Modes +- Laser power and dimming +- Low Latency meta-functions +- Optimal gaming and movie setting meta-functions +- and so on + +Because everything is async, it will run each button/command in the order it received. so commands won't disappear from the queue due to JVCs PJ server requiring the handshake. Currently WIP to use one long running connection to have lightning fast commands. ## Installation + This is currently only a custom component. Working on getting this into HA Core Install HACS, then install the component by adding this as a custom repo https://hacs.xyz/docs/faq/custom_repositories @@ -24,13 +29,14 @@ You can also just copy all the files into your custom_components folder but then # configuration.yaml remote: - platform: jvc_projectors - name: {friendly name} - password: {password} - host: {IP addr} + name: { friendly name } + password: { password } + host: { IP addr } scan_interval: 30 # recommend 30-60. Power state will poll in this interval ``` ## Useful Stuff + I used this to re-create the JVC remote in HA. Add the YAML to your dashboard to get a grid which resembles most of the remote. Other functions can be used via remote.send_command. See the library readme for details. Add this sensor to your configuration.yml. Replace the nz7 with the name of your entity. Restart HA. @@ -40,19 +46,20 @@ sensor: platform: template sensors: jvc_low_latency: - value_template: > - {% if is_state('remote.nz7', 'on') %} - {% if states.remote.nz7.attributes.low_latency == false %} - Off - {% elif states.remote.nz7.attributes.low_latency == true %} - On - {% endif %} - {% else %} - Off - {% endif %} + value_template: > + {% if is_state('remote.nz7', 'on') %} + {% if states.remote.nz7.attributes.low_latency == false %} + Off + {% elif states.remote.nz7.attributes.low_latency == true %} + On + {% endif %} + {% else %} + Off + {% endif %} ``` Add this to lovelace + ```yaml type: grid cards: @@ -223,5 +230,4 @@ cards: action: toggle show_icon: false columns: 5 - ```