__init__.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. """The Solar of Things integration."""
  2. from __future__ import annotations
  3. import logging
  4. from datetime import timedelta
  5. from homeassistant.config_entries import ConfigEntry
  6. from homeassistant.const import Platform
  7. from homeassistant.core import HomeAssistant
  8. from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
  9. from .api import SolarOfThingsAPI
  10. from .const import DOMAIN, CONF_DEVICE_ID
  11. _LOGGER = logging.getLogger(__name__)
  12. PLATFORMS: list[Platform] = [Platform.SENSOR, Platform.NUMBER, Platform.SELECT, Platform.SWITCH]
  13. DEVICE_UPDATE_INTERVAL = timedelta(minutes=5)
  14. STATION_UPDATE_INTERVAL = timedelta(minutes=30)
  15. async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
  16. """Set up Solar of Things from a config entry."""
  17. hass.data.setdefault(DOMAIN, {})
  18. api = SolarOfThingsAPI(
  19. iot_token=entry.data["iot_token"],
  20. time_zone=entry.data.get("time_zone") or entry.options.get("time_zone"),
  21. )
  22. station_id = entry.data["station_id"]
  23. configured_device_id = (entry.data.get(CONF_DEVICE_ID) or "").strip()
  24. # Station coordinator (device list + monthly)
  25. station_coordinator = SolarOfThingsStationCoordinator(
  26. hass=hass,
  27. api=api,
  28. station_id=station_id,
  29. )
  30. await station_coordinator.async_config_entry_first_refresh()
  31. # Create one coordinator per device
  32. devices = station_coordinator.data.get("devices", []) if station_coordinator.data else []
  33. # If the user configured a specific deviceId, only create entities for that device.
  34. # (We still keep station-level data like monthly summary.)
  35. if configured_device_id:
  36. filtered = [d for d in devices if str(d.get("id")) == configured_device_id]
  37. if filtered:
  38. devices = filtered
  39. else:
  40. # Fallback: allow the integration to work even if the device isn't returned
  41. # by the station device list (permissions / pagination / API quirks).
  42. devices = [{"id": configured_device_id, "name": configured_device_id}]
  43. device_coordinators: dict[str, SolarOfThingsDeviceCoordinator] = {}
  44. for dev in devices:
  45. device_id = str(dev.get("id") or "")
  46. if not device_id:
  47. continue
  48. c = SolarOfThingsDeviceCoordinator(
  49. hass=hass,
  50. api=api,
  51. station_id=station_id,
  52. device=device_id,
  53. device_meta=dev,
  54. )
  55. await c.async_config_entry_first_refresh()
  56. device_coordinators[device_id] = c
  57. hass.data[DOMAIN][entry.entry_id] = {
  58. "api": api,
  59. "station_id": station_id,
  60. "station_coordinator": station_coordinator,
  61. "device_coordinators": device_coordinators,
  62. "devices": devices,
  63. }
  64. await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
  65. return True
  66. async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
  67. """Unload a config entry."""
  68. if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
  69. hass.data[DOMAIN].pop(entry.entry_id, None)
  70. return unload_ok
  71. class SolarOfThingsStationCoordinator(DataUpdateCoordinator):
  72. """Fetch station-level data (device list + monthly stats)."""
  73. def __init__(self, hass: HomeAssistant, api: SolarOfThingsAPI, station_id: str) -> None:
  74. self.api = api
  75. self.station_id = station_id
  76. super().__init__(
  77. hass,
  78. _LOGGER,
  79. name=f"{DOMAIN}_station_{station_id}",
  80. update_interval=STATION_UPDATE_INTERVAL,
  81. )
  82. async def _async_update_data(self):
  83. try:
  84. devices = await self.hass.async_add_executor_job(self.api.list_devices, self.station_id)
  85. monthly = await self.hass.async_add_executor_job(self.api.fetch_monthly_summary, self.station_id)
  86. return {"devices": devices, "monthly": monthly}
  87. except Exception as err:
  88. raise UpdateFailed(f"Station update failed: {err}")
  89. class SolarOfThingsDeviceCoordinator(DataUpdateCoordinator):
  90. """Fetch device-level telemetry + settings."""
  91. def __init__(
  92. self,
  93. hass: HomeAssistant,
  94. api: SolarOfThingsAPI,
  95. station_id: str,
  96. device: str,
  97. device_meta: dict,
  98. ) -> None:
  99. self.api = api
  100. self.station_id = station_id
  101. self.device_id = device
  102. self.device_meta = device_meta
  103. super().__init__(
  104. hass,
  105. _LOGGER,
  106. name=f"{DOMAIN}_device_{device}",
  107. update_interval=DEVICE_UPDATE_INTERVAL,
  108. )
  109. async def _async_update_data(self):
  110. try:
  111. time_series = await self.hass.async_add_executor_job(self.api.fetch_latest_data, self.device_id)
  112. settings = await self.hass.async_add_executor_job(self.api.fetch_settings, self.device_id)
  113. return {
  114. "time_series": time_series,
  115. "settings": settings,
  116. "device": self.device_id,
  117. "station_id": self.station_id,
  118. "device_meta": self.device_meta,
  119. }
  120. except Exception as err:
  121. raise UpdateFailed(f"Device update failed for {self.device_id}: {err}")