coordinator.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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. data = {
  81. endpoints[i]: results[i]
  82. for i in len(endpoints)
  83. }
  84. info = data[EP_DEVICESTATUS]['DeviceInfo']
  85. self.device_info = DeviceInfo(
  86. configuration_url=f'{API_SCHEMA}://{self.api.host}',
  87. identifiers={(DOMAIN, self.config_entry.entry_id)},
  88. model=info['ModelName'],
  89. manufacturer=info['Manufacturer'],
  90. name=info['Description'],
  91. sw_version=info['SoftwareVersion'],
  92. hw_version=info['HardwareVersion'],
  93. model_id=info['ProductClass'],
  94. serial_number=['SerialNumber'],
  95. )
  96. return RouterAPIData(data)
  97. except RouterAPIAuthError as err:
  98. _LOGGER.error(err)
  99. raise UpdateFailed(err) from err
  100. except Exception as err:
  101. # This will show entities as unavailable by raising UpdateFailed exception
  102. raise UpdateFailed(f"Error communicating with API: {err}") from err
  103. # # What is returned here is stored in self.data by the DataUpdateCoordinator
  104. # return RouterAPIData(self.api.controller_name, devices)
  105. def get_value(self, endpoint: str, path: list[int | str], default=None) -> StateType:
  106. """
  107. Get a value from the data by a given path.
  108. When the value is absent, the default (None) will be returned and an error will be logged.
  109. """
  110. value = self.data.get(endpoint, default)
  111. try:
  112. for key in path:
  113. value = value[key]
  114. value_type = type(value).__name__
  115. if value_type in ["int", "float", "str"]:
  116. _LOGGER.debug(
  117. "Path %s returns a %s (value = %s)", path, value_type, value
  118. )
  119. else:
  120. _LOGGER.debug("Path %s returns a %s", path, value_type)
  121. return value
  122. except (IndexError, KeyError):
  123. _LOGGER.warning("Can't find a value for %s in the API response", path)
  124. return default