sensor.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. """GitHub sensor 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.sensor import PLATFORM_SCHEMA
  10. from homeassistant.const import CONF_HOST, CONF_PORT, CONF_NAME, CONF_PASSWORD, CONF_TIMEOUT
  11. from homeassistant.core import HomeAssistant as HomeAssistantType
  12. import homeassistant.helpers.config_validation as cv
  13. from homeassistant.helpers.entity import Entity
  14. from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
  15. import voluptuous as vol
  16. 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
  17. _LOGGER = logging.getLogger(__name__)
  18. # Time between updating data from projector
  19. SCAN_INTERVAL = timedelta(seconds=3)
  20. PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
  21. {
  22. vol.Required(CONF_HOST): cv.string,
  23. vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
  24. vol.Optional(CONF_NAME): cv.string,
  25. vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string,
  26. vol.Optional(CONF_PASSWORD) : cv.string,
  27. vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT) : cv.positive_float
  28. }
  29. )
  30. async def async_setup_platform(
  31. hass: HomeAssistantType,
  32. config: ConfigType,
  33. async_add_entities: Callable,
  34. discovery_info: DiscoveryInfoType | None = None,
  35. ) -> None:
  36. """Set up the sensor platform."""
  37. host = config.get(CONF_HOST)
  38. port = config.get(CONF_PORT)
  39. password = config.get(CONF_PASSWORD)
  40. timeout = config.get(CONF_TIMEOUT)
  41. name = config.get(CONF_NAME)
  42. pjl = PJLink(host, port, password, timeout)
  43. sensors = [PJLink2Sensor(pjl, name)]
  44. async_add_entities(sensors, update_before_add=False)
  45. class PJLink2Sensor(Entity):
  46. """Representation of a PJLink2 sensor."""
  47. def __init__(self, pjl, name):
  48. super().__init__()
  49. self._projector = pjl
  50. self.attrs: dict[str, Any] = {}
  51. self._name = name
  52. self._state = None
  53. self._available = False
  54. self._connectionErrorLogged = False
  55. async def async_will_remove_from_hass(self) -> None:
  56. """Close connection."""
  57. await super().async_will_remove_from_hass()
  58. if self._available:
  59. try:
  60. await self._projector.__aexit__(0,0,0)
  61. except (PJLinkException, OSError) as err:
  62. _LOGGER.error("PJLink2 ERROR when closing connection to %s: %s", self._name, repr(err))
  63. else:
  64. _LOGGER.info("PJLink2 INFO for %s: Connection closed.", self._name)
  65. @property
  66. def name(self) -> str:
  67. """Return the name of the entity."""
  68. return self._name
  69. @property
  70. def unique_id(self) -> str:
  71. """Return the unique ID of the sensor."""
  72. return self._projector._address
  73. @property
  74. def available(self) -> bool:
  75. """Return True if entity is available."""
  76. return self._available
  77. @property
  78. def state(self) -> str | None:
  79. return self._state
  80. @property
  81. def extra_state_attributes(self) -> dict[str, Any]:
  82. return self.attrs
  83. async def async_update(self) -> None:
  84. """Update all sensors."""
  85. try:
  86. if not self._available:
  87. # connect and init static information
  88. await self._projector.__aenter__()
  89. self._available = True
  90. info = await Information(self._projector).table()
  91. self.attrs[ATTR_PRODUCT_NAME] = info["product_name"]
  92. self.attrs[ATTR_MANUFACTURER_NAME] = info["manufacturer_name"]
  93. self.attrs[ATTR_PROJECTOR_NAME] = info["projector_name"]
  94. if self._name == None: self._name = info["projector_name"]
  95. _LOGGER.info("PJLink2 INFO for %s: Connection opened.", self._name)
  96. pwr = await Power(self._projector).get()
  97. if pwr == Power.State.OFF: self._state = ProjectorState.OFF
  98. elif pwr == Power.State.ON: self._state = ProjectorState.ON
  99. elif pwr == Power.State.COOLING: self._state = ProjectorState.COOLING
  100. elif pwr == Power.State.WARMING: self._state = ProjectorState.WARMING
  101. if pwr==Power.ON:
  102. res = await Sources(self._projector).resolution()
  103. self.attrs[ATTR_RESOLUTION_X] = res[0]
  104. self.attrs[ATTR_RESOLUTION_Y] = res[1]
  105. lmpHrs = await Lamp(self._projector).hours()
  106. self.attrs[ATTR_LAMP_HOURS] = lmpHrs
  107. else:
  108. if ATTR_RESOLUTION_X in self.attrs: del self.attrs[ATTR_RESOLUTION_X]
  109. if ATTR_RESOLUTION_Y in self.attrs: del self.attrs[ATTR_RESOLUTION_Y]
  110. self._connectionErrorLogged = False # after successful update, enable error logging for next connection issue
  111. except PJLinkProjectorError:
  112. # resolution cannot be queried due to no input
  113. if ATTR_RESOLUTION_X in self.attrs: del self.attrs[ATTR_RESOLUTION_X]
  114. if ATTR_RESOLUTION_Y in self.attrs: del self.attrs[ATTR_RESOLUTION_Y]
  115. _LOGGER.info("PJLink2 INFO for %s: Cannot get resolution", self._name)
  116. except (PJLinkException, OSError) as err:
  117. if not self._connectionErrorLogged:
  118. _LOGGER.error("PJLink2 ERROR for %s: %s", self._name, repr(err))
  119. self._connectionErrorLogged = True # do not spam logfile with same error message
  120. self._state = None
  121. if self._available:
  122. self._available = False # only call exit function once after disconnect
  123. try:
  124. await self._projector.__aexit__(0,0,0)
  125. except (PJLinkException, OSError) as err:
  126. _LOGGER.error("PJLink2 ERROR when closing connection to %s: %s", self._name, repr(err))
  127. else:
  128. _LOGGER.info("PJLink2 INFO for %s: Connection closed.", self._name)