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