Bump bluecurrent-api to 1.2.2 (#110483)

This commit is contained in:
Floris272 2024-03-20 11:28:27 +01:00 committed by GitHub
parent 6552e12161
commit 249f708071
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 86 additions and 91 deletions

View file

@ -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()

View file

@ -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

View file

@ -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"]
}

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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(

View file

@ -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