Skip to content
This repository has been archived by the owner on Aug 24, 2024. It is now read-only.

Stop gap to fix concurrent command issues #2

Merged
merged 2 commits into from
Feb 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions custom_components/jvc_projectors/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [],
Expand All @@ -14,5 +14,5 @@
"@iloveicedgreentea"
],
"iot_class": "local_polling",
"version": "1.2.4"
"version": "1.2.5"
}
99 changes: 58 additions & 41 deletions custom_components/jvc_projectors/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,15 @@ 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),
]
)

platform = entity_platform.async_get_current_platform()
# Register the services
platform.async_register_entity_service(
INFO_COMMAND, {}, f"service_async_{INFO_COMMAND}"
)
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -173,44 +183,51 @@ 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:
async with timeout(5):
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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
46 changes: 26 additions & 20 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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:
Expand Down Expand Up @@ -223,5 +230,4 @@ cards:
action: toggle
show_icon: false
columns: 5

```