1 """Code to handle the Plenticore API."""
3 from __future__
import annotations
5 from collections
import defaultdict
6 from collections.abc
import Mapping
7 from datetime
import datetime, timedelta
9 from typing
import cast
11 from aiohttp.client_exceptions
import ClientError
12 from pykoplenti
import (
15 AuthenticationException,
27 from .const
import DOMAIN
28 from .helper
import get_hostname_id
30 _LOGGER = logging.getLogger(__name__)
34 """Manages the Plenticore API."""
37 """Create a new plenticore manager instance."""
48 """Return the host of the Plenticore inverter."""
53 """Return the Plenticore API client."""
57 """Set up Plenticore API client."""
58 self.
_client_client = ExtendedApiClient(
63 except AuthenticationException
as err:
65 "Authentication exception connecting to %s: %s", self.
hosthost, err
68 except (ClientError, TimeoutError)
as err:
69 _LOGGER.error(
"Error connecting to %s", self.
hosthost)
70 raise ConfigEntryNotReady
from err
72 _LOGGER.debug(
"Log-in successfully to %s", self.
hosthost)
80 settings = await self.
_client_client.get_setting_values(
83 "Properties:SerialNo",
84 "Branding:ProductName1",
85 "Branding:ProductName2",
86 "Properties:VersionIOC",
87 "Properties:VersionMC",
89 "scb:network": [hostname_id],
93 device_local = settings[
"devices:local"]
94 prod1 = device_local[
"Branding:ProductName1"]
95 prod2 = device_local[
"Branding:ProductName2"]
98 configuration_url=f
"http://{self.host}",
99 identifiers={(DOMAIN, device_local[
"Properties:SerialNo"])},
100 manufacturer=
"Kostal",
101 model=f
"{prod1} {prod2}",
102 name=settings[
"scb:network"][hostname_id],
104 f
'IOC: {device_local["Properties:VersionIOC"]}'
105 f
' MC: {device_local["Properties:VersionMC"]}'
112 """Call from Homeassistant shutdown event."""
118 """Unload the Plenticore API client."""
122 await self.
_client_client.logout()
124 _LOGGER.debug(
"Logged out from %s", self.
hosthost)
128 """Base implementation for read and write data."""
130 _plenticore: Plenticore
134 self, module_id: str, data_id: str
135 ) -> Mapping[str, Mapping[str, str]] |
None:
136 """Read data from Plenticore."""
137 if (client := self._plenticore.client)
is None:
141 return await client.get_setting_values(module_id, data_id)
146 """Write settings back to Plenticore."""
147 if (client := self._plenticore.client)
is None:
151 "Setting value for %s in module %s to %s", self.name, module_id, value
155 await client.set_setting_values(module_id, value)
163 """Base implementation of DataUpdateCoordinator for Plenticore data."""
168 logger: logging.Logger,
170 update_inverval: timedelta,
171 plenticore: Plenticore,
173 """Create a new update coordinator for plenticore data."""
178 update_interval=update_inverval,
181 self._fetch: dict[str, list[str]] = defaultdict(list)
185 """Start fetching the given data (module-id and data-id)."""
186 self._fetch[module_id].append(data_id)
190 async
def force_refresh(event_time: datetime) ->
None:
196 """Stop fetching the given data (module-id and data-id)."""
197 self._fetch[module_id].
remove(data_id)
201 PlenticoreUpdateCoordinator[Mapping[str, Mapping[str, str]]]
203 """Implementation of PlenticoreUpdateCoordinator for process data."""
208 if not self._fetch
or client
is None:
211 _LOGGER.debug(
"Fetching %s for %s", self.
namename, self._fetch)
213 fetched_data = await client.get_process_data_values(self._fetch)
216 process_data.id: process_data.value
217 for process_data
in fetched_data[module_id].values()
219 for module_id
in fetched_data
224 PlenticoreUpdateCoordinator[Mapping[str, Mapping[str, str]]],
225 DataUpdateCoordinatorMixin,
227 """Implementation of PlenticoreUpdateCoordinator for settings data."""
232 if not self._fetch
or client
is None:
235 _LOGGER.debug(
"Fetching %s for %s", self.
namename, self._fetch)
237 return await client.get_setting_values(self._fetch)
241 """Base implementation of DataUpdateCoordinator for Plenticore data."""
246 logger: logging.Logger,
248 update_inverval: timedelta,
249 plenticore: Plenticore,
251 """Create a new update coordinator for plenticore data."""
256 update_interval=update_inverval,
259 self._fetch: dict[str, list[str | list[str]]] = defaultdict(list)
263 self, module_id: str, data_id: str, all_options: list[str]
265 """Start fetching the given data (module-id and entry-id)."""
266 self._fetch[module_id].append(data_id)
267 self._fetch[module_id].append(all_options)
271 async
def force_refresh(event_time: datetime) ->
None:
277 self, module_id: str, data_id: str, all_options: list[str]
279 """Stop fetching the given data (module-id and entry-id)."""
280 self._fetch[module_id].
remove(all_options)
281 self._fetch[module_id].
remove(data_id)
285 PlenticoreSelectUpdateCoordinator[dict[str, dict[str, str]]],
286 DataUpdateCoordinatorMixin,
288 """Implementation of PlenticoreUpdateCoordinator for select data."""
294 _LOGGER.debug(
"Fetching select %s for %s", self.
namename, self._fetch)
300 module_id: dict[str, list[str | list[str]]],
301 ) -> dict[str, dict[str, str]]:
302 """Get current option."""
303 for mid, pids
in module_id.items():
304 all_options = cast(list[str], pids[1])
305 for all_option
in all_options:
306 if all_option ==
"None" or not (
310 for option
in val.values():
311 if option[all_option] ==
"1":
312 return {mid: {cast(str, pids[0]): all_option}}
314 return {mid: {cast(str, pids[0]):
"None"}}
Mapping[str, Mapping[str, str]]|None async_read_data(self, str module_id, str data_id)
bool async_write_data(self, str module_id, dict[str, str] value)
CALLBACK_TYPE start_fetch_data(self, str module_id, str data_id, list[str] all_options)
None __init__(self, HomeAssistant hass, logging.Logger logger, str name, timedelta update_inverval, Plenticore plenticore)
None stop_fetch_data(self, str module_id, str data_id, list[str] all_options)
None stop_fetch_data(self, str module_id, str data_id)
CALLBACK_TYPE start_fetch_data(self, str module_id, str data_id)
None __init__(self, HomeAssistant hass, logging.Logger logger, str name, timedelta update_inverval, Plenticore plenticore)
_shutdown_remove_listener
def __init__(self, hass, config_entry)
def _async_shutdown(self, event)
dict[str, dict[str, str]] _async_update_data(self)
dict[str, dict[str, str]] _async_get_current_option(self, dict[str, list[str|list[str]]] module_id)
dict[str, dict[str, str]] _async_update_data(self)
Mapping[str, Mapping[str, str]] _async_update_data(self)
None async_request_refresh(self)
bool remove(self, _T matcher)
str get_hostname_id(ApiClient client)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)
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)