media_player.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. """PJLink2 media_player platform."""
  2. from __future__ import annotations
  3. from collections.abc import Callable
  4. from datetime import timedelta
  5. import logging
  6. from typing import Any
  7. from aiopjlink import PJLink, PJLinkException, PJLinkProjectorError, Power, Sources, Lamp, Information
  8. from homeassistant import config_entries, core
  9. from homeassistant.components.media_player import (
  10. MediaPlayerEntity,
  11. MediaPlayerEntityFeature,
  12. MediaPlayerState,
  13. PLATFORM_SCHEMA
  14. )
  15. from homeassistant.const import CONF_HOST, CONF_PORT, CONF_NAME, CONF_PASSWORD, CONF_TIMEOUT
  16. from homeassistant.core import HomeAssistant as HomeAssistantType
  17. import homeassistant.helpers.config_validation as cv
  18. from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
  19. import voluptuous as vol
  20. from .const import DOMAIN, CONF_ENCODING, DEFAULT_ENCODING, DEFAULT_PORT, DEFAULT_TIMEOUT, ATTR_PRODUCT_NAME, ATTR_MANUFACTURER_NAME, ATTR_PROJECTOR_NAME, ATTR_RESOLUTION_X, ATTR_RESOLUTION_Y, ATTR_LAMP_HOURS, ProjectorState
  21. _LOGGER = logging.getLogger(__name__)
  22. # Time between updating data from projector
  23. SCAN_INTERVAL = timedelta(seconds=3)
  24. PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
  25. {
  26. vol.Required(CONF_HOST): cv.string,
  27. vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
  28. vol.Optional(CONF_NAME): cv.string,
  29. vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string,
  30. vol.Optional(CONF_PASSWORD) : cv.string,
  31. vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT) : cv.positive_float
  32. }
  33. )
  34. async def async_setup_platform(
  35. hass: HomeAssistantType,
  36. config: ConfigType,
  37. async_add_entities: Callable,
  38. discovery_info: DiscoveryInfoType | None = None,
  39. ) -> None:
  40. """Set up the media_player platform."""
  41. host = config.get(CONF_HOST)
  42. port = config.get(CONF_PORT)
  43. password = config.get(CONF_PASSWORD)
  44. timeout = config.get(CONF_TIMEOUT)
  45. name = config.get(CONF_NAME)
  46. pjl = PJLink(host, port, password, timeout)
  47. devices = [PJLink2MediaPlayer(pjl, name)]
  48. async_add_entities(devices, update_before_add=False)
  49. class PJLink2MediaPlayer(MediaPlayerEntity):
  50. """Representation of a PJLink2 media player."""
  51. _attr_supported_features = (
  52. MediaPlayerEntityFeature.TURN_ON
  53. | MediaPlayerEntityFeature.TURN_OFF
  54. | MediaPlayerEntityFeature.SELECT_SOURCE
  55. )
  56. def __init__(self, pjl, name):
  57. super().__init__()
  58. self._projector = pjl
  59. self.attrs: dict[str, Any] = {}
  60. self._name = name
  61. self._state = MediaPlayerState.OFF
  62. self._available = False
  63. self._connectionErrorLogged = False
  64. self._current_source = None
  65. # PJLink standard inputs. You can modify these to friendly names later.
  66. # Format is typically "31" for HDMI1, "32" for HDMI2, etc.
  67. self._source_list = ["11", "12", "21", "22", "31", "32", "33"]
  68. async def async_will_remove_from_hass(self) -> None:
  69. """Close connection."""
  70. await super().async_will_remove_from_hass()
  71. if self._available:
  72. try:
  73. await self._projector.__aexit__(0,0,0)
  74. except (PJLinkException, OSError) as err:
  75. _LOGGER.error("PJLink2 ERROR when closing connection: %s", repr(err))
  76. @property
  77. def name(self) -> str:
  78. return self._name
  79. @property
  80. def unique_id(self) -> str:
  81. return self._projector._address
  82. @property
  83. def available(self) -> bool:
  84. return self._available
  85. @property
  86. def state(self) -> MediaPlayerState:
  87. return self._state
  88. @property
  89. def source(self) -> str | None:
  90. """Name of the current input source."""
  91. return self._current_source
  92. @property
  93. def source_list(self) -> list[str]:
  94. """List of available input sources."""
  95. return self._source_list
  96. @property
  97. def extra_state_attributes(self) -> dict[str, Any]:
  98. """Return the custom PJLink2 attributes."""
  99. return self.attrs
  100. async def async_turn_on(self) -> None:
  101. """Turn the projector on."""
  102. await Power(self._projector).set(Power.ON)
  103. self._state = MediaPlayerState.ON
  104. async def async_turn_off(self) -> None:
  105. """Turn the projector off."""
  106. await Power(self._projector).set(Power.OFF)
  107. self._state = MediaPlayerState.OFF
  108. async def async_select_source(self, source: str) -> None:
  109. """Select input source."""
  110. await Sources(self._projector).set(source)
  111. self._current_source = source
  112. async def async_update(self) -> None:
  113. """Update data from projector."""
  114. try:
  115. if not self._available:
  116. await self._projector.__aenter__()
  117. self._available = True
  118. info = await Information(self._projector).table()
  119. self.attrs[ATTR_PRODUCT_NAME] = info["product_name"]
  120. self.attrs[ATTR_MANUFACTURER_NAME] = info["manufacturer_name"]
  121. self.attrs[ATTR_PROJECTOR_NAME] = info["projector_name"]
  122. if self._name == None: self._name = info["projector_name"]
  123. pwr = await Power(self._projector).get()
  124. if pwr == Power.State.OFF: self._state = MediaPlayerState.OFF
  125. elif pwr == Power.State.ON: self._state = MediaPlayerState.ON
  126. elif pwr in (Power.State.COOLING, Power.State.WARMING): self._state = MediaPlayerState.ON # Keeps UI active during transition
  127. if pwr == Power.ON:
  128. res = await Sources(self._projector).resolution()
  129. self.attrs[ATTR_RESOLUTION_X] = res[0]
  130. self.attrs[ATTR_RESOLUTION_Y] = res[1]
  131. self.attrs[ATTR_LAMP_HOURS] = await Lamp(self._projector).hours()
  132. # Fetch current source
  133. self._current_source = await Sources(self._projector).get()
  134. else:
  135. self.attrs.pop(ATTR_RESOLUTION_X, None)
  136. self.attrs.pop(ATTR_RESOLUTION_Y, None)
  137. self._current_source = None
  138. self._connectionErrorLogged = False
  139. except PJLinkProjectorError:
  140. self.attrs.pop(ATTR_RESOLUTION_X, None)
  141. self.attrs.pop(ATTR_RESOLUTION_Y, None)
  142. except (PJLinkException, OSError) as err:
  143. if not self._connectionErrorLogged:
  144. _LOGGER.error("PJLink2 ERROR for %s: %s", self._name, repr(err))
  145. self._connectionErrorLogged = True
  146. self._state = MediaPlayerState.OFF
  147. if self._available:
  148. self._available = False
  149. try:
  150. await self._projector.__aexit__(0,0,0)
  151. except Exception:
  152. pass