Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """The Blue Current integration."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from contextlib import suppress
7 from typing import Any
8 
9 from bluecurrent_api import Client
10 from bluecurrent_api.exceptions import (
11  BlueCurrentException,
12  InvalidApiToken,
13  RequestLimitReached,
14  WebsocketError,
15 )
16 
17 from homeassistant.config_entries import ConfigEntry
18 from homeassistant.const import ATTR_NAME, CONF_API_TOKEN, Platform
19 from homeassistant.core import HomeAssistant
20 from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
21 from homeassistant.helpers.dispatcher import async_dispatcher_send
22 
23 from .const import DOMAIN, EVSE_ID, LOGGER, MODEL_TYPE
24 
25 type BlueCurrentConfigEntry = ConfigEntry[Connector]
26 
27 PLATFORMS = [Platform.SENSOR]
28 CHARGE_POINTS = "CHARGE_POINTS"
29 DATA = "data"
30 DELAY = 5
31 
32 GRID = "GRID"
33 OBJECT = "object"
34 VALUE_TYPES = ["CH_STATUS"]
35 
36 
38  hass: HomeAssistant, config_entry: BlueCurrentConfigEntry
39 ) -> bool:
40  """Set up Blue Current as a config entry."""
41  client = Client()
42  api_token = config_entry.data[CONF_API_TOKEN]
43  connector = Connector(hass, config_entry, client)
44 
45  try:
46  await client.validate_api_token(api_token)
47  except InvalidApiToken as err:
48  raise ConfigEntryAuthFailed("Invalid API token.") from err
49  except BlueCurrentException as err:
50  raise ConfigEntryNotReady from err
51  config_entry.async_create_background_task(
52  hass, connector.run_task(), "blue_current-websocket"
53  )
54 
55  await client.wait_for_charge_points()
56  config_entry.runtime_data = connector
57  await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
58 
59  return True
60 
61 
63  hass: HomeAssistant, config_entry: BlueCurrentConfigEntry
64 ) -> bool:
65  """Unload the Blue Current config entry."""
66 
67  return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
68 
69 
70 class Connector:
71  """Define a class that connects to the Blue Current websocket API."""
72 
73  def __init__(
74  self, hass: HomeAssistant, config: BlueCurrentConfigEntry, client: Client
75  ) -> None:
76  """Initialize."""
77  self.configconfig = config
78  self.hasshass = hass
79  self.clientclient = client
80  self.charge_points: dict[str, dict] = {}
81  self.gridgrid: dict[str, Any] = {}
82 
83  async def on_data(self, message: dict) -> None:
84  """Handle received data."""
85 
86  object_name: str = message[OBJECT]
87 
88  # gets charge point ids
89  if object_name == CHARGE_POINTS:
90  charge_points_data: list = message[DATA]
91  await self.handle_charge_point_datahandle_charge_point_data(charge_points_data)
92 
93  # gets charge point key / values
94  elif object_name in VALUE_TYPES:
95  value_data: dict = message[DATA]
96  evse_id = value_data.pop(EVSE_ID)
97  self.update_charge_pointupdate_charge_point(evse_id, value_data)
98 
99  # gets grid key / values
100  elif GRID in object_name:
101  data: dict = message[DATA]
102  self.gridgrid = data
103  self.dispatch_grid_update_signaldispatch_grid_update_signal()
104 
105  async def handle_charge_point_data(self, charge_points_data: list) -> None:
106  """Handle incoming chargepoint data."""
107  await asyncio.gather(
108  *(
109  self.handle_charge_pointhandle_charge_point(
110  entry[EVSE_ID], entry[MODEL_TYPE], entry[ATTR_NAME]
111  )
112  for entry in charge_points_data
113  ),
114  self.clientclient.get_grid_status(charge_points_data[0][EVSE_ID]),
115  )
116 
117  async def handle_charge_point(self, evse_id: str, model: str, name: str) -> None:
118  """Add the chargepoint and request their data."""
119  self.add_charge_pointadd_charge_point(evse_id, model, name)
120  await self.clientclient.get_status(evse_id)
121 
122  def add_charge_point(self, evse_id: str, model: str, name: str) -> None:
123  """Add a charge point to charge_points."""
124  self.charge_points[evse_id] = {MODEL_TYPE: model, ATTR_NAME: name}
125 
126  def update_charge_point(self, evse_id: str, data: dict) -> None:
127  """Update the charge point data."""
128  self.charge_points[evse_id].update(data)
129  self.dispatch_charge_point_update_signaldispatch_charge_point_update_signal(evse_id)
130 
131  def dispatch_charge_point_update_signal(self, evse_id: str) -> None:
132  """Dispatch a charge point update signal."""
133  async_dispatcher_send(self.hasshass, f"{DOMAIN}_charge_point_update_{evse_id}")
134 
135  def dispatch_grid_update_signal(self) -> None:
136  """Dispatch a grid update signal."""
137  async_dispatcher_send(self.hasshass, f"{DOMAIN}_grid_update")
138 
139  async def on_open(self) -> None:
140  """Fetch data when connection is established."""
141  await self.clientclient.get_charge_points()
142 
143  async def run_task(self) -> None:
144  """Start the receive loop."""
145  try:
146  while True:
147  try:
148  await self.clientclient.connect(self.on_dataon_data, self.on_openon_open)
149  except RequestLimitReached:
150  LOGGER.warning(
151  "Request limit reached. reconnecting at 00:00 (Europe/Amsterdam)"
152  )
153  delay = self.clientclient.get_next_reset_delta().seconds
154  except WebsocketError:
155  LOGGER.debug("Disconnected, retrying in background")
156  delay = DELAY
157 
158  self._on_disconnect_on_disconnect()
159  await asyncio.sleep(delay)
160  finally:
161  await self._disconnect_disconnect()
162 
163  def _on_disconnect(self) -> None:
164  """Dispatch signals to update entity states."""
165  for evse_id in self.charge_points:
166  self.dispatch_charge_point_update_signaldispatch_charge_point_update_signal(evse_id)
167  self.dispatch_grid_update_signaldispatch_grid_update_signal()
168 
169  async def _disconnect(self) -> None:
170  """Disconnect from the websocket."""
171  with suppress(WebsocketError):
172  await self.clientclient.disconnect()
173  self._on_disconnect_on_disconnect()
174 
175  @property
176  def connected(self) -> bool:
177  """Returns the connection status."""
178  return self.clientclient.is_connected()
None __init__(self, HomeAssistant hass, BlueCurrentConfigEntry config, Client client)
Definition: __init__.py:75
None dispatch_charge_point_update_signal(self, str evse_id)
Definition: __init__.py:131
None add_charge_point(self, str evse_id, str model, str name)
Definition: __init__.py:122
None handle_charge_point(self, str evse_id, str model, str name)
Definition: __init__.py:117
None update_charge_point(self, str evse_id, dict data)
Definition: __init__.py:126
None handle_charge_point_data(self, list charge_points_data)
Definition: __init__.py:105
bool async_setup_entry(HomeAssistant hass, BlueCurrentConfigEntry config_entry)
Definition: __init__.py:39
bool async_unload_entry(HomeAssistant hass, BlueCurrentConfigEntry config_entry)
Definition: __init__.py:64
IssData update(pyiss.ISS iss)
Definition: __init__.py:33
def get_status(hass, host, port)
Definition: panel.py:387
bool is_connected(HomeAssistant hass)
Definition: __init__.py:553
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193