Occasionally, this integration misses events (or maybe they never get sent) from the projector and gets "stuck" in the wrong power state. Currently, this prevents this integration from changing the power state as it thinks its already in the correct state. Only way to resolve this is to reboot home assistant. This PR makes it a little more resilient by attempting to send the correct command even when it thinks it's already in the correct state.
177 lines
5.3 KiB
Python
177 lines
5.3 KiB
Python
"""Support for controlling projector via the PJLink protocol."""
|
|
from pypjlink import MUTE_AUDIO, Projector
|
|
from pypjlink.projector import ProjectorError
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity
|
|
from homeassistant.components.media_player.const import (
|
|
SUPPORT_SELECT_SOURCE,
|
|
SUPPORT_TURN_OFF,
|
|
SUPPORT_TURN_ON,
|
|
SUPPORT_VOLUME_MUTE,
|
|
)
|
|
from homeassistant.const import (
|
|
CONF_HOST,
|
|
CONF_NAME,
|
|
CONF_PASSWORD,
|
|
CONF_PORT,
|
|
STATE_OFF,
|
|
STATE_ON,
|
|
)
|
|
import homeassistant.helpers.config_validation as cv
|
|
|
|
CONF_ENCODING = "encoding"
|
|
|
|
DEFAULT_PORT = 4352
|
|
DEFAULT_ENCODING = "utf-8"
|
|
DEFAULT_TIMEOUT = 10
|
|
|
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|
{
|
|
vol.Required(CONF_HOST): cv.string,
|
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
|
vol.Optional(CONF_NAME): cv.string,
|
|
vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string,
|
|
vol.Optional(CONF_PASSWORD): cv.string,
|
|
}
|
|
)
|
|
|
|
SUPPORT_PJLINK = (
|
|
SUPPORT_VOLUME_MUTE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE
|
|
)
|
|
|
|
|
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
|
"""Set up the PJLink platform."""
|
|
host = config.get(CONF_HOST)
|
|
port = config.get(CONF_PORT)
|
|
name = config.get(CONF_NAME)
|
|
encoding = config.get(CONF_ENCODING)
|
|
password = config.get(CONF_PASSWORD)
|
|
|
|
if "pjlink" not in hass.data:
|
|
hass.data["pjlink"] = {}
|
|
hass_data = hass.data["pjlink"]
|
|
|
|
device_label = f"{host}:{port}"
|
|
if device_label in hass_data:
|
|
return
|
|
|
|
device = PjLinkDevice(host, port, name, encoding, password)
|
|
hass_data[device_label] = device
|
|
add_entities([device], True)
|
|
|
|
|
|
def format_input_source(input_source_name, input_source_number):
|
|
"""Format input source for display in UI."""
|
|
return f"{input_source_name} {input_source_number}"
|
|
|
|
|
|
class PjLinkDevice(MediaPlayerEntity):
|
|
"""Representation of a PJLink device."""
|
|
|
|
def __init__(self, host, port, name, encoding, password):
|
|
"""Iinitialize the PJLink device."""
|
|
self._host = host
|
|
self._port = port
|
|
self._name = name
|
|
self._password = password
|
|
self._encoding = encoding
|
|
self._muted = False
|
|
self._pwstate = STATE_OFF
|
|
self._current_source = None
|
|
with self.projector() as projector:
|
|
if not self._name:
|
|
self._name = projector.get_name()
|
|
inputs = projector.get_inputs()
|
|
self._source_name_mapping = {format_input_source(*x): x for x in inputs}
|
|
self._source_list = sorted(self._source_name_mapping.keys())
|
|
|
|
def projector(self):
|
|
"""Create PJLink Projector instance."""
|
|
|
|
projector = Projector.from_address(
|
|
self._host, self._port, self._encoding, DEFAULT_TIMEOUT
|
|
)
|
|
projector.authenticate(self._password)
|
|
return projector
|
|
|
|
def update(self):
|
|
"""Get the latest state from the device."""
|
|
|
|
with self.projector() as projector:
|
|
try:
|
|
pwstate = projector.get_power()
|
|
if pwstate in ("on", "warm-up"):
|
|
self._pwstate = STATE_ON
|
|
self._muted = projector.get_mute()[1]
|
|
self._current_source = format_input_source(*projector.get_input())
|
|
else:
|
|
self._pwstate = STATE_OFF
|
|
self._muted = False
|
|
self._current_source = None
|
|
except KeyError as err:
|
|
if str(err) == "'OK'":
|
|
self._pwstate = STATE_OFF
|
|
self._muted = False
|
|
self._current_source = None
|
|
else:
|
|
raise
|
|
except ProjectorError as err:
|
|
if str(err) == "unavailable time":
|
|
self._pwstate = STATE_OFF
|
|
self._muted = False
|
|
self._current_source = None
|
|
else:
|
|
raise
|
|
|
|
@property
|
|
def name(self):
|
|
"""Return the name of the device."""
|
|
return self._name
|
|
|
|
@property
|
|
def state(self):
|
|
"""Return the state of the device."""
|
|
return self._pwstate
|
|
|
|
@property
|
|
def is_volume_muted(self):
|
|
"""Return boolean indicating mute status."""
|
|
return self._muted
|
|
|
|
@property
|
|
def source(self):
|
|
"""Return current input source."""
|
|
return self._current_source
|
|
|
|
@property
|
|
def source_list(self):
|
|
"""Return all available input sources."""
|
|
return self._source_list
|
|
|
|
@property
|
|
def supported_features(self):
|
|
"""Return projector supported features."""
|
|
return SUPPORT_PJLINK
|
|
|
|
def turn_off(self):
|
|
"""Turn projector off."""
|
|
with self.projector() as projector:
|
|
projector.set_power("off")
|
|
|
|
def turn_on(self):
|
|
"""Turn projector on."""
|
|
with self.projector() as projector:
|
|
projector.set_power("on")
|
|
|
|
def mute_volume(self, mute):
|
|
"""Mute (true) of unmute (false) media player."""
|
|
with self.projector() as projector:
|
|
projector.set_mute(MUTE_AUDIO, mute)
|
|
|
|
def select_source(self, source):
|
|
"""Set the input source."""
|
|
source = self._source_name_mapping[source]
|
|
with self.projector() as projector:
|
|
projector.set_input(*source)
|