coordinator.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. """Integration 101 Template integration using DataUpdateCoordinator."""
  2. from dataclasses import dataclass
  3. from datetime import timedelta
  4. import logging
  5. import asyncio
  6. import traceback as tb
  7. from homeassistant.config_entries import ConfigEntry
  8. from homeassistant.const import (
  9. CONF_HOST,
  10. CONF_PASSWORD,
  11. CONF_SCAN_INTERVAL,
  12. CONF_USERNAME,
  13. )
  14. from homeassistant.core import DOMAIN, HomeAssistant
  15. from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
  16. from homeassistant.helpers.aiohttp_client import async_get_clientsession
  17. from homeassistant.helpers.typing import StateType
  18. from homeassistant.helpers.entity import DeviceInfo
  19. from .api import RouterAPI, RouterAPIAuthError
  20. from .const import (DEFAULT_SCAN_INTERVAL,
  21. EP_CELLINFO,
  22. EP_DEVICESTATUS,
  23. EP_LANINFO,
  24. API_SCHEMA)
  25. _LOGGER = logging.getLogger(__name__)
  26. @dataclass
  27. class RouterAPIData:
  28. """Class to hold api data."""
  29. data: dict
  30. class RouterCoordinator(DataUpdateCoordinator):
  31. """My example coordinator."""
  32. data: RouterAPIData
  33. def __init__(self, hass: HomeAssistant,
  34. config_entry: ConfigEntry) -> None:
  35. """Initialize coordinator."""
  36. # Set variables from values entered in config flow setup
  37. self.host = config_entry.data[CONF_HOST]
  38. self.user = config_entry.data[CONF_USERNAME]
  39. self.pwd = config_entry.data[CONF_PASSWORD]
  40. self.device_info = None
  41. self.config_entry = config_entry
  42. # set variables from options. You need a default here incase options have not been set
  43. self.poll_interval = config_entry.options.get(
  44. CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
  45. )
  46. # Initialise DataUpdateCoordinator
  47. super().__init__(
  48. hass,
  49. _LOGGER,
  50. name=f"{DOMAIN} ({config_entry.unique_id})",
  51. # Method to call on every update interval.
  52. update_method=self.async_update_data,
  53. # Polling interval. Will only be polled if there are subscribers.
  54. # Using config option here but you can just use a value.
  55. update_interval=timedelta(seconds=self.poll_interval),
  56. )
  57. session = async_get_clientsession(
  58. hass=hass,
  59. verify_ssl=False
  60. )
  61. session.cookie_jar._unsafe = True
  62. # Initialise your api here
  63. self.api = RouterAPI(host=self.host,
  64. user=self.user,
  65. pwd=self.pwd,
  66. session=session)
  67. async def async_update_data(self):
  68. """Fetch data from API endpoint.
  69. This is the place to pre-process the data to lookup tables
  70. so entities can quickly look up their data.
  71. """
  72. try:
  73. # First login to refresh session
  74. if await self.api.async_login():
  75. # Get API endpoints
  76. endpoints = [EP_CELLINFO,
  77. EP_DEVICESTATUS,
  78. EP_LANINFO]
  79. results = await asyncio.gather(
  80. *[self.api.async_query_api(oid=endpoint) for endpoint in endpoints],
  81. return_exceptions=True)
  82. data = {
  83. endpoints[i]: results[i]
  84. for i in range(len(endpoints))
  85. }
  86. info = data[EP_DEVICESTATUS]['DeviceInfo']
  87. self.device_info = DeviceInfo(
  88. configuration_url=f'{API_SCHEMA}://{self.api.host}',
  89. identifiers={(DOMAIN, self.config_entry.entry_id)},
  90. model=info['ModelName'],
  91. manufacturer=info['Manufacturer'],
  92. name=info['Description'],
  93. sw_version=info['SoftwareVersion'],
  94. hw_version=info['HardwareVersion'],
  95. model_id=info['ProductClass'],
  96. serial_number=['SerialNumber'],
  97. )
  98. return RouterAPIData(data)
  99. except RouterAPIAuthError as err:
  100. _LOGGER.error(err)
  101. raise UpdateFailed(err) from err
  102. except Exception as err:
  103. # This will show entities as unavailable by raising UpdateFailed exception
  104. _LOGGER.error(''.join(tb.format_exception(None, err, err.__traceback__)))
  105. _LOGGER.error(err)
  106. raise UpdateFailed(f"Error communicating with API: {err}") from err
  107. # # What is returned here is stored in self.data by the DataUpdateCoordinator
  108. # return RouterAPIData(self.api.controller_name, devices)
  109. def get_value(self, endpoint: str, path: list[int | str], default=None) -> StateType:
  110. """
  111. Get a value from the data by a given path.
  112. When the value is absent, the default (None) will be returned and an error will be logged.
  113. """
  114. value = self.data.get(endpoint, default)
  115. try:
  116. for key in path:
  117. value = value[key]
  118. value_type = type(value).__name__
  119. if value_type in ["int", "float", "str"]:
  120. _LOGGER.debug(
  121. "Path %s returns a %s (value = %s)", path, value_type, value
  122. )
  123. else:
  124. _LOGGER.debug("Path %s returns a %s", path, value_type)
  125. return value
  126. except (IndexError, KeyError):
  127. _LOGGER.warning("Can't find a value for %s in the API response", path)
  128. return default