1 """SAJ solar inverter interface."""
3 from __future__
import annotations
5 from collections.abc
import Callable, Coroutine
6 from datetime
import date, datetime
11 import voluptuous
as vol
14 PLATFORM_SCHEMA
as SENSOR_PLATFORM_SCHEMA,
25 EVENT_HOMEASSISTANT_STOP,
40 _LOGGER = logging.getLogger(__name__)
45 INVERTER_TYPES = [
"ethernet",
"wifi"]
49 "h": UnitOfTime.HOURS,
50 "kg": UnitOfMass.KILOGRAMS,
51 "kWh": UnitOfEnergy.KILO_WATT_HOUR,
52 "W": UnitOfPower.WATT,
53 "°C": UnitOfTemperature.CELSIUS,
56 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
58 vol.Required(CONF_HOST): cv.string,
59 vol.Optional(CONF_NAME): cv.string,
60 vol.Optional(CONF_TYPE, default=INVERTER_TYPES[0]): vol.In(INVERTER_TYPES),
61 vol.Inclusive(CONF_USERNAME,
"credentials"): cv.string,
62 vol.Inclusive(CONF_PASSWORD,
"credentials"): cv.string,
70 async_add_entities: AddEntitiesCallback,
71 discovery_info: DiscoveryInfoType |
None =
None,
73 """Set up the SAJ sensors."""
75 remove_interval_update =
None
76 wifi = config[CONF_TYPE] == INVERTER_TYPES[1]
79 sensor_def = pysaj.Sensors(wifi)
82 hass_sensors: list[SAJsensor] = []
87 if config.get(CONF_USERNAME)
and config.get(CONF_PASSWORD):
88 kwargs[
"username"] = config[CONF_USERNAME]
89 kwargs[
"password"] = config[CONF_PASSWORD]
92 saj = pysaj.SAJ(config[CONF_HOST], **kwargs)
93 done = await saj.read(sensor_def)
94 except pysaj.UnauthorizedException:
95 _LOGGER.error(
"Username and/or password is wrong")
97 except pysaj.UnexpectedResponseException
as err:
99 "Error in SAJ, please check host/ip address. Original error: %s", err
104 raise PlatformNotReady
107 SAJsensor(saj.serialnumber, sensor, inverter_name=config.get(CONF_NAME))
108 for sensor
in sensor_def
114 async
def async_saj() -> bool:
115 """Update all the SAJ sensors."""
116 success = await saj.read(sensor_def)
118 for sensor
in hass_sensors:
119 state_unknown =
False
128 (sensor.per_day_basis
and date.today() > sensor.date_updated)
129 or (
not sensor.per_day_basis
and not sensor.per_total_basis)
132 sensor.async_update_values(unknown_state=state_unknown)
137 def start_update_interval(hass: HomeAssistant) ->
None:
138 """Start the update interval scheduling."""
139 nonlocal remove_interval_update
143 def stop_update_interval(event):
144 """Properly cancel the scheduled update."""
145 remove_interval_update()
147 hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, stop_update_interval)
153 hass: HomeAssistant, action: Callable[[], Coroutine[Any, Any, bool]]
155 """Add a listener that fires repetitively and increases the interval when failed."""
157 interval = MIN_INTERVAL
159 async
def interval_listener(now: datetime |
None =
None) ->
None:
160 """Handle elapsed interval with backoff."""
161 nonlocal interval, remove
164 interval = MIN_INTERVAL
166 interval =
min(interval * 2, MAX_INTERVAL)
170 hass.async_create_task(interval_listener())
172 def remove_listener() -> None:
173 """Remove interval listener."""
177 return remove_listener
181 """Representation of a SAJ sensor."""
183 _attr_should_poll =
False
187 serialnumber: str |
None,
188 pysaj_sensor: pysaj.Sensor,
189 inverter_name: str |
None =
None,
191 """Initialize the SAJ sensor."""
197 if pysaj_sensor.name
in (
"current_power",
"temperature"):
199 if pysaj_sensor.name ==
"total_yield":
203 native_uom = SAJ_UNIT_MAPPINGS[pysaj_sensor.unit]
206 self.
_attr_name_attr_name = f
"saj_{self._inverter_name}_{pysaj_sensor.name}"
208 self.
_attr_name_attr_name = f
"saj_{pysaj_sensor.name}"
209 if native_uom == UnitOfPower.WATT:
211 if native_uom == UnitOfEnergy.KILO_WATT_HOUR:
214 UnitOfTemperature.CELSIUS,
215 UnitOfTemperature.FAHRENHEIT,
221 """Return the state of the sensor."""
226 """Return if the sensors value is on daily basis or not."""
227 return self.
_sensor_sensor.per_day_basis
231 """Return if the sensors value is cumulative or not."""
232 return self.
_sensor_sensor.per_total_basis
236 """Return the date when the sensor was last updated."""
237 return self.
_sensor_sensor.date
241 """Update this sensor."""
248 if unknown_state
and self.
_state_state
is not None:
_attr_native_unit_of_measurement
bool per_total_basis(self)
None __init__(self, str|None serialnumber, pysaj.Sensor pysaj_sensor, str|None inverter_name=None)
def async_update_values(self, unknown_state=False)
None async_write_ha_state(self)
bool remove(self, _T matcher)
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
CALLBACK_TYPE async_track_time_interval_backoff(HomeAssistant hass, Callable[[], Coroutine[Any, Any, bool]] action)
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)
CALLBACK_TYPE async_at_start(HomeAssistant hass, Callable[[HomeAssistant], Coroutine[Any, Any, None]|None] at_start_cb)