sensor.py 6.1 KB

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