coordinator.py 5.6 KB

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