Bump bluecurrent-api to 1.2.2 (#110483)
This commit is contained in:
parent
6552e12161
commit
249f708071
8 changed files with 86 additions and 91 deletions
|
@ -4,7 +4,6 @@ from __future__ import annotations
|
|||
|
||||
import asyncio
|
||||
from contextlib import suppress
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from bluecurrent_api import Client
|
||||
|
@ -16,24 +15,17 @@ from bluecurrent_api.exceptions import (
|
|||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_NAME,
|
||||
CONF_API_TOKEN,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.const import ATTR_NAME, CONF_API_TOKEN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
|
||||
from .const import DOMAIN, EVSE_ID, LOGGER, MODEL_TYPE
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
CHARGE_POINTS = "CHARGE_POINTS"
|
||||
DATA = "data"
|
||||
SMALL_DELAY = 1
|
||||
LARGE_DELAY = 20
|
||||
DELAY = 5
|
||||
|
||||
GRID = "GRID"
|
||||
OBJECT = "object"
|
||||
|
@ -48,26 +40,19 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
|||
connector = Connector(hass, config_entry, client)
|
||||
|
||||
try:
|
||||
await connector.connect(api_token)
|
||||
await client.validate_api_token(api_token)
|
||||
except InvalidApiToken as err:
|
||||
raise ConfigEntryAuthFailed("Invalid API token.") from err
|
||||
except BlueCurrentException as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
config_entry.async_create_background_task(
|
||||
hass, connector.run_task(), "blue_current-websocket"
|
||||
)
|
||||
|
||||
hass.async_create_background_task(connector.start_loop(), "blue_current-websocket")
|
||||
await client.get_charge_points()
|
||||
|
||||
await client.wait_for_response()
|
||||
await client.wait_for_charge_points()
|
||||
hass.data[DOMAIN][config_entry.entry_id] = connector
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||
|
||||
config_entry.async_on_unload(connector.disconnect)
|
||||
|
||||
async def _async_disconnect_websocket(_: Event) -> None:
|
||||
await connector.disconnect()
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_disconnect_websocket)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
@ -95,12 +80,6 @@ class Connector:
|
|||
self.client = client
|
||||
self.charge_points: dict[str, dict] = {}
|
||||
self.grid: dict[str, Any] = {}
|
||||
self.available = False
|
||||
|
||||
async def connect(self, token: str) -> None:
|
||||
"""Register on_data and connect to the websocket."""
|
||||
await self.client.connect(token)
|
||||
self.available = True
|
||||
|
||||
async def on_data(self, message: dict) -> None:
|
||||
"""Handle received data."""
|
||||
|
@ -158,34 +137,39 @@ class Connector:
|
|||
"""Dispatch a grid signal."""
|
||||
async_dispatcher_send(self.hass, f"{DOMAIN}_grid_update")
|
||||
|
||||
async def start_loop(self) -> None:
|
||||
async def run_task(self) -> None:
|
||||
"""Start the receive loop."""
|
||||
try:
|
||||
await self.client.start_loop(self.on_data)
|
||||
except BlueCurrentException as err:
|
||||
LOGGER.warning(
|
||||
"Disconnected from the Blue Current websocket. Retrying to connect in background. %s",
|
||||
err,
|
||||
)
|
||||
while True:
|
||||
try:
|
||||
await self.client.connect(self.on_data)
|
||||
except RequestLimitReached:
|
||||
LOGGER.warning(
|
||||
"Request limit reached. reconnecting at 00:00 (Europe/Amsterdam)"
|
||||
)
|
||||
delay = self.client.get_next_reset_delta().seconds
|
||||
except WebsocketError:
|
||||
LOGGER.debug("Disconnected, retrying in background")
|
||||
delay = DELAY
|
||||
|
||||
async_call_later(self.hass, SMALL_DELAY, self.reconnect)
|
||||
self._on_disconnect()
|
||||
await asyncio.sleep(delay)
|
||||
finally:
|
||||
await self._disconnect()
|
||||
|
||||
async def reconnect(self, _event_time: datetime | None = None) -> None:
|
||||
"""Keep trying to reconnect to the websocket."""
|
||||
try:
|
||||
await self.connect(self.config.data[CONF_API_TOKEN])
|
||||
LOGGER.debug("Reconnected to the Blue Current websocket")
|
||||
self.hass.async_create_task(self.start_loop())
|
||||
except RequestLimitReached:
|
||||
self.available = False
|
||||
async_call_later(
|
||||
self.hass, self.client.get_next_reset_delta(), self.reconnect
|
||||
)
|
||||
except WebsocketError:
|
||||
self.available = False
|
||||
async_call_later(self.hass, LARGE_DELAY, self.reconnect)
|
||||
def _on_disconnect(self) -> None:
|
||||
"""Dispatch signals to update entity states."""
|
||||
for evse_id in self.charge_points:
|
||||
self.dispatch_value_update_signal(evse_id)
|
||||
self.dispatch_grid_update_signal()
|
||||
|
||||
async def disconnect(self) -> None:
|
||||
async def _disconnect(self) -> None:
|
||||
"""Disconnect from the websocket."""
|
||||
with suppress(WebsocketError):
|
||||
await self.client.disconnect()
|
||||
self._on_disconnect()
|
||||
|
||||
@property
|
||||
def connected(self) -> bool:
|
||||
"""Returns the connection status."""
|
||||
return self.client.is_connected()
|
||||
|
|
|
@ -40,7 +40,7 @@ class BlueCurrentEntity(Entity):
|
|||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return entity availability."""
|
||||
return self.connector.available and self.has_value
|
||||
return self.connector.connected and self.has_value
|
||||
|
||||
@callback
|
||||
@abstractmethod
|
||||
|
|
|
@ -5,5 +5,6 @@
|
|||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/blue_current",
|
||||
"iot_class": "cloud_push",
|
||||
"requirements": ["bluecurrent-api==1.0.6"]
|
||||
"loggers": ["bluecurrent_api"],
|
||||
"requirements": ["bluecurrent-api==1.2.2"]
|
||||
}
|
||||
|
|
|
@ -568,7 +568,7 @@ blinkpy==0.22.6
|
|||
blockchain==1.4.4
|
||||
|
||||
# homeassistant.components.blue_current
|
||||
bluecurrent-api==1.0.6
|
||||
bluecurrent-api==1.2.2
|
||||
|
||||
# homeassistant.components.bluemaestro
|
||||
bluemaestro-ble==0.2.3
|
||||
|
|
|
@ -487,7 +487,7 @@ blebox-uniapi==2.2.2
|
|||
blinkpy==0.22.6
|
||||
|
||||
# homeassistant.components.blue_current
|
||||
bluecurrent-api==1.0.6
|
||||
bluecurrent-api==1.2.2
|
||||
|
||||
# homeassistant.components.bluemaestro
|
||||
bluemaestro-ble==0.2.3
|
||||
|
|
|
@ -31,15 +31,21 @@ def create_client_mock(
|
|||
future_container: FutureContainer,
|
||||
started_loop: Event,
|
||||
charge_point: dict,
|
||||
status: dict | None,
|
||||
grid: dict | None,
|
||||
status: dict,
|
||||
grid: dict,
|
||||
) -> MagicMock:
|
||||
"""Create a mock of the bluecurrent-api Client."""
|
||||
client_mock = MagicMock(spec=Client)
|
||||
received_charge_points = Event()
|
||||
|
||||
async def start_loop(receiver):
|
||||
async def wait_for_charge_points():
|
||||
"""Wait until chargepoints are received."""
|
||||
await received_charge_points.wait()
|
||||
|
||||
async def connect(receiver):
|
||||
"""Set the receiver and await future."""
|
||||
client_mock.receiver = receiver
|
||||
await client_mock.get_charge_points()
|
||||
|
||||
started_loop.set()
|
||||
started_loop.clear()
|
||||
|
@ -50,13 +56,13 @@ def create_client_mock(
|
|||
|
||||
async def get_charge_points() -> None:
|
||||
"""Send a list of charge points to the callback."""
|
||||
await started_loop.wait()
|
||||
await client_mock.receiver(
|
||||
{
|
||||
"object": "CHARGE_POINTS",
|
||||
"data": [charge_point],
|
||||
}
|
||||
)
|
||||
received_charge_points.set()
|
||||
|
||||
async def get_status(evse_id: str) -> None:
|
||||
"""Send the status of a charge point to the callback."""
|
||||
|
@ -71,7 +77,8 @@ def create_client_mock(
|
|||
"""Send the grid status to the callback."""
|
||||
await client_mock.receiver({"object": "GRID_STATUS", "data": grid})
|
||||
|
||||
client_mock.start_loop.side_effect = start_loop
|
||||
client_mock.connect.side_effect = connect
|
||||
client_mock.wait_for_charge_points.side_effect = wait_for_charge_points
|
||||
client_mock.get_charge_points.side_effect = get_charge_points
|
||||
client_mock.get_status.side_effect = get_status
|
||||
client_mock.get_grid_status.side_effect = get_grid_status
|
||||
|
@ -92,6 +99,12 @@ async def init_integration(
|
|||
if charge_point is None:
|
||||
charge_point = DEFAULT_CHARGE_POINT
|
||||
|
||||
if status is None:
|
||||
status = {}
|
||||
|
||||
if grid is None:
|
||||
grid = {}
|
||||
|
||||
future_container = FutureContainer(hass.loop.create_future())
|
||||
started_loop = Event()
|
||||
|
||||
|
|
|
@ -127,6 +127,11 @@ async def test_reauth(
|
|||
), patch(
|
||||
"homeassistant.components.blue_current.config_flow.Client.get_email",
|
||||
return_value="test@email.com",
|
||||
), patch(
|
||||
"homeassistant.components.blue_current.config_flow.Client.wait_for_charge_points",
|
||||
), patch(
|
||||
"homeassistant.components.blue_current.Client.connect",
|
||||
lambda self, on_data: hass.loop.create_future(),
|
||||
):
|
||||
config_entry.add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
|
|
|
@ -29,15 +29,22 @@ async def test_load_unload_entry(
|
|||
hass: HomeAssistant, config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test load and unload entry."""
|
||||
with patch("homeassistant.components.blue_current.Client", autospec=True):
|
||||
with patch(
|
||||
"homeassistant.components.blue_current.Client.validate_api_token"
|
||||
), patch(
|
||||
"homeassistant.components.blue_current.Client.wait_for_charge_points"
|
||||
), patch("homeassistant.components.blue_current.Client.disconnect"), patch(
|
||||
"homeassistant.components.blue_current.Client.connect",
|
||||
lambda self, on_data: hass.loop.create_future(),
|
||||
):
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -55,43 +62,29 @@ async def test_config_exceptions(
|
|||
) -> None:
|
||||
"""Test if the correct config error is raised when connecting to the api fails."""
|
||||
with patch(
|
||||
"homeassistant.components.blue_current.Client.connect",
|
||||
"homeassistant.components.blue_current.Client.validate_api_token",
|
||||
side_effect=api_error,
|
||||
), pytest.raises(config_error):
|
||||
config_entry.add_to_hass(hass)
|
||||
await async_setup_entry(hass, config_entry)
|
||||
|
||||
|
||||
async def test_start_loop(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
|
||||
"""Test start_loop."""
|
||||
async def test_connect_websocket_error(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test reconnect when connect throws a WebsocketError."""
|
||||
|
||||
with patch("homeassistant.components.blue_current.SMALL_DELAY", 0):
|
||||
with patch("homeassistant.components.blue_current.DELAY", 0):
|
||||
mock_client, started_loop, future_container = await init_integration(
|
||||
hass, config_entry
|
||||
)
|
||||
future_container.future.set_exception(BlueCurrentException)
|
||||
future_container.future.set_exception(WebsocketError)
|
||||
|
||||
await started_loop.wait()
|
||||
assert mock_client.connect.call_count == 2
|
||||
|
||||
|
||||
async def test_reconnect_websocket_error(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test reconnect when connect throws a WebsocketError."""
|
||||
|
||||
with patch("homeassistant.components.blue_current.LARGE_DELAY", 0):
|
||||
mock_client, started_loop, future_container = await init_integration(
|
||||
hass, config_entry
|
||||
)
|
||||
future_container.future.set_exception(BlueCurrentException)
|
||||
mock_client.connect.side_effect = [WebsocketError, None]
|
||||
|
||||
await started_loop.wait()
|
||||
assert mock_client.connect.call_count == 3
|
||||
|
||||
|
||||
async def test_reconnect_request_limit_reached_error(
|
||||
async def test_connect_request_limit_reached_error(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test reconnect when connect throws a RequestLimitReached."""
|
||||
|
@ -99,10 +92,9 @@ async def test_reconnect_request_limit_reached_error(
|
|||
mock_client, started_loop, future_container = await init_integration(
|
||||
hass, config_entry
|
||||
)
|
||||
future_container.future.set_exception(BlueCurrentException)
|
||||
mock_client.connect.side_effect = [RequestLimitReached, None]
|
||||
future_container.future.set_exception(RequestLimitReached)
|
||||
mock_client.get_next_reset_delta.return_value = timedelta(seconds=0)
|
||||
|
||||
await started_loop.wait()
|
||||
assert mock_client.get_next_reset_delta.call_count == 1
|
||||
assert mock_client.connect.call_count == 3
|
||||
assert mock_client.connect.call_count == 2
|
||||
|
|
Loading…
Add table
Reference in a new issue