sensor.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. """Sensor platform for Solar of Things integration."""
  2. from __future__ import annotations
  3. import logging
  4. from homeassistant.components.sensor import SensorEntity, SensorDeviceClass, SensorStateClass
  5. from homeassistant.config_entries import ConfigEntry
  6. from homeassistant.const import (
  7. PERCENTAGE,
  8. UnitOfElectricCurrent,
  9. UnitOfElectricPotential,
  10. UnitOfEnergy,
  11. UnitOfPower,
  12. )
  13. from homeassistant.core import HomeAssistant
  14. from homeassistant.helpers.entity_platform import AddEntitiesCallback
  15. from homeassistant.helpers.update_coordinator import CoordinatorEntity
  16. from .const import DOMAIN, SENSOR_DEFINITIONS
  17. _LOGGER = logging.getLogger(__name__)
  18. async def async_setup_entry(
  19. hass: HomeAssistant,
  20. entry: ConfigEntry,
  21. async_add_entities: AddEntitiesCallback,
  22. ) -> None:
  23. """Set up Solar of Things sensors."""
  24. data = hass.data[DOMAIN][entry.entry_id]
  25. station_id: str = data["station_id"]
  26. device_coordinators = data["device_coordinators"]
  27. station_coordinator = data["station_coordinator"]
  28. entities: list[SensorEntity] = []
  29. # Per-device sensors
  30. for device_id, coordinator in device_coordinators.items():
  31. device_name = (coordinator.device_meta or {}).get("name") or device_id
  32. for key, definition in SENSOR_DEFINITIONS.items():
  33. if key.startswith("monthly_"):
  34. continue
  35. entities.append(
  36. SolarOfThingsDeviceSensor(
  37. coordinator=coordinator,
  38. station_id=station_id,
  39. device_id=device_id,
  40. device_name=device_name,
  41. sensor_key=key,
  42. sensor_definition=definition,
  43. )
  44. )
  45. # Station-level monthly sensors
  46. if station_coordinator:
  47. for key, definition in SENSOR_DEFINITIONS.items():
  48. if not key.startswith("monthly_"):
  49. continue
  50. entities.append(
  51. SolarOfThingsStationMonthlySensor(
  52. coordinator=station_coordinator,
  53. station_id=station_id,
  54. sensor_key=key,
  55. sensor_definition=definition,
  56. )
  57. )
  58. async_add_entities(entities)
  59. class SolarOfThingsDeviceSensor(CoordinatorEntity, SensorEntity):
  60. """Per-device telemetry sensor."""
  61. def __init__(
  62. self,
  63. coordinator,
  64. station_id: str,
  65. device_id: str,
  66. device_name: str,
  67. sensor_key: str,
  68. sensor_definition: dict,
  69. ) -> None:
  70. super().__init__(coordinator)
  71. self._station_id = station_id
  72. self._device_id = device_id
  73. self._device_name = device_name
  74. self._sensor_key = sensor_key
  75. self._sensor_definition = sensor_definition
  76. self._attr_name = f"{device_name} {sensor_definition['name']}"
  77. self._attr_unique_id = f"{DOMAIN}_{station_id}_{device_id}_{sensor_key}"
  78. self._attr_icon = sensor_definition.get("icon")
  79. unit = sensor_definition.get("unit")
  80. if unit == "W":
  81. self._attr_device_class = SensorDeviceClass.POWER
  82. self._attr_native_unit_of_measurement = UnitOfPower.WATT
  83. self._attr_state_class = SensorStateClass.MEASUREMENT
  84. elif unit == "kWh":
  85. self._attr_device_class = SensorDeviceClass.ENERGY
  86. self._attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR
  87. self._attr_state_class = SensorStateClass.TOTAL_INCREASING
  88. elif unit == "A":
  89. self._attr_device_class = SensorDeviceClass.CURRENT
  90. self._attr_native_unit_of_measurement = UnitOfElectricCurrent.AMPERE
  91. self._attr_state_class = SensorStateClass.MEASUREMENT
  92. elif unit == "V":
  93. self._attr_device_class = SensorDeviceClass.VOLTAGE
  94. self._attr_native_unit_of_measurement = UnitOfElectricPotential.VOLT
  95. self._attr_state_class = SensorStateClass.MEASUREMENT
  96. elif unit == "%":
  97. if "battery" in sensor_key.lower():
  98. self._attr_device_class = SensorDeviceClass.BATTERY
  99. self._attr_native_unit_of_measurement = PERCENTAGE
  100. self._attr_state_class = SensorStateClass.MEASUREMENT
  101. @property
  102. def device_info(self):
  103. return {
  104. "identifiers": {(DOMAIN, self._station_id, self._device_id)},
  105. "name": self._device_name,
  106. "manufacturer": "Siseli",
  107. "model": (self.coordinator.data.get("device_meta") or {}).get("model") if self.coordinator.data else None,
  108. "via_device": (DOMAIN, self._station_id),
  109. }
  110. @property
  111. def native_value(self):
  112. ts = (self.coordinator.data or {}).get("time_series") or {}
  113. val = ts.get(self._sensor_key)
  114. if val is None:
  115. return None
  116. try:
  117. return round(float(val), 2)
  118. except Exception:
  119. return None
  120. class SolarOfThingsStationMonthlySensor(CoordinatorEntity, SensorEntity):
  121. """Station-level monthly summary sensor."""
  122. def __init__(
  123. self,
  124. coordinator,
  125. station_id: str,
  126. sensor_key: str,
  127. sensor_definition: dict,
  128. ) -> None:
  129. super().__init__(coordinator)
  130. self._station_id = station_id
  131. self._sensor_key = sensor_key
  132. self._sensor_definition = sensor_definition
  133. self._attr_name = f"Station {station_id} {sensor_definition['name']}"
  134. self._attr_unique_id = f"{DOMAIN}_{station_id}_{sensor_key}"
  135. self._attr_icon = sensor_definition.get("icon")
  136. unit = sensor_definition.get("unit")
  137. if unit == "kWh":
  138. self._attr_device_class = SensorDeviceClass.ENERGY
  139. self._attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR
  140. self._attr_state_class = SensorStateClass.TOTAL
  141. elif unit == "%":
  142. self._attr_native_unit_of_measurement = PERCENTAGE
  143. self._attr_state_class = SensorStateClass.MEASUREMENT
  144. @property
  145. def device_info(self):
  146. return {
  147. "identifiers": {(DOMAIN, self._station_id)},
  148. "name": f"Solar Station {self._station_id}",
  149. "manufacturer": "Siseli",
  150. "model": "Station",
  151. }
  152. @property
  153. def native_value(self):
  154. monthly = (self.coordinator.data or {}).get("monthly") or {}
  155. val = monthly.get(self._sensor_key)
  156. if val is None:
  157. return None
  158. try:
  159. return round(float(val), 2)
  160. except Exception:
  161. return None