Improve coordinator logic in Tessie to allow sleep (#107988)
* Poll status before state * Tests
This commit is contained in:
parent
cdb798bec0
commit
32b0bf6b4e
6 changed files with 60 additions and 31 deletions
|
@ -14,7 +14,7 @@ from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .const import DOMAIN, TessieStatus
|
from .const import DOMAIN, TessieState
|
||||||
from .coordinator import TessieStateUpdateCoordinator
|
from .coordinator import TessieStateUpdateCoordinator
|
||||||
from .entity import TessieEntity
|
from .entity import TessieEntity
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ DESCRIPTIONS: tuple[TessieBinarySensorEntityDescription, ...] = (
|
||||||
TessieBinarySensorEntityDescription(
|
TessieBinarySensorEntityDescription(
|
||||||
key="state",
|
key="state",
|
||||||
device_class=BinarySensorDeviceClass.CONNECTIVITY,
|
device_class=BinarySensorDeviceClass.CONNECTIVITY,
|
||||||
is_on=lambda x: x == TessieStatus.ONLINE,
|
is_on=lambda x: x == TessieState.ONLINE,
|
||||||
),
|
),
|
||||||
TessieBinarySensorEntityDescription(
|
TessieBinarySensorEntityDescription(
|
||||||
key="charge_state_battery_heater_on",
|
key="charge_state_battery_heater_on",
|
||||||
|
|
|
@ -13,13 +13,21 @@ MODELS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TessieStatus(StrEnum):
|
class TessieState(StrEnum):
|
||||||
"""Tessie status."""
|
"""Tessie status."""
|
||||||
|
|
||||||
ASLEEP = "asleep"
|
ASLEEP = "asleep"
|
||||||
ONLINE = "online"
|
ONLINE = "online"
|
||||||
|
|
||||||
|
|
||||||
|
class TessieStatus(StrEnum):
|
||||||
|
"""Tessie status."""
|
||||||
|
|
||||||
|
ASLEEP = "asleep"
|
||||||
|
AWAKE = "awake"
|
||||||
|
WAITING = "waiting_for_sleep"
|
||||||
|
|
||||||
|
|
||||||
class TessieSeatHeaterOptions(StrEnum):
|
class TessieSeatHeaterOptions(StrEnum):
|
||||||
"""Tessie seat heater options."""
|
"""Tessie seat heater options."""
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aiohttp import ClientResponseError
|
from aiohttp import ClientResponseError
|
||||||
from tessie_api import get_state
|
from tessie_api import get_state, get_status
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
|
@ -45,11 +45,21 @@ class TessieStateUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
async def _async_update_data(self) -> dict[str, Any]:
|
async def _async_update_data(self) -> dict[str, Any]:
|
||||||
"""Update vehicle data using Tessie API."""
|
"""Update vehicle data using Tessie API."""
|
||||||
try:
|
try:
|
||||||
|
status = await get_status(
|
||||||
|
session=self.session,
|
||||||
|
api_key=self.api_key,
|
||||||
|
vin=self.vin,
|
||||||
|
)
|
||||||
|
if status["status"] == TessieStatus.ASLEEP:
|
||||||
|
# Vehicle is asleep, no need to poll for data
|
||||||
|
self.data["state"] = status["status"]
|
||||||
|
return self.data
|
||||||
|
|
||||||
vehicle = await get_state(
|
vehicle = await get_state(
|
||||||
session=self.session,
|
session=self.session,
|
||||||
api_key=self.api_key,
|
api_key=self.api_key,
|
||||||
vin=self.vin,
|
vin=self.vin,
|
||||||
use_cache=False,
|
use_cache=True,
|
||||||
)
|
)
|
||||||
except ClientResponseError as e:
|
except ClientResponseError as e:
|
||||||
if e.status == HTTPStatus.UNAUTHORIZED:
|
if e.status == HTTPStatus.UNAUTHORIZED:
|
||||||
|
@ -57,13 +67,7 @@ class TessieStateUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
raise ConfigEntryAuthFailed from e
|
raise ConfigEntryAuthFailed from e
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
if vehicle["state"] == TessieStatus.ONLINE:
|
return self._flatten(vehicle)
|
||||||
# Vehicle is online, all data is fresh
|
|
||||||
return self._flatten(vehicle)
|
|
||||||
|
|
||||||
# Vehicle is asleep, only update state
|
|
||||||
self.data["state"] = vehicle["state"]
|
|
||||||
return self.data
|
|
||||||
|
|
||||||
def _flatten(
|
def _flatten(
|
||||||
self, data: dict[str, Any], parent: str | None = None
|
self, data: dict[str, Any], parent: str | None = None
|
||||||
|
|
|
@ -6,7 +6,7 @@ from unittest.mock import patch
|
||||||
from aiohttp import ClientConnectionError, ClientResponseError
|
from aiohttp import ClientConnectionError, ClientResponseError
|
||||||
from aiohttp.client import RequestInfo
|
from aiohttp.client import RequestInfo
|
||||||
|
|
||||||
from homeassistant.components.tessie.const import DOMAIN
|
from homeassistant.components.tessie.const import DOMAIN, TessieStatus
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
@ -14,7 +14,9 @@ from tests.common import MockConfigEntry, load_json_object_fixture
|
||||||
|
|
||||||
TEST_STATE_OF_ALL_VEHICLES = load_json_object_fixture("vehicles.json", DOMAIN)
|
TEST_STATE_OF_ALL_VEHICLES = load_json_object_fixture("vehicles.json", DOMAIN)
|
||||||
TEST_VEHICLE_STATE_ONLINE = load_json_object_fixture("online.json", DOMAIN)
|
TEST_VEHICLE_STATE_ONLINE = load_json_object_fixture("online.json", DOMAIN)
|
||||||
TEST_VEHICLE_STATE_ASLEEP = load_json_object_fixture("asleep.json", DOMAIN)
|
TEST_VEHICLE_STATUS_AWAKE = {"status": TessieStatus.AWAKE}
|
||||||
|
TEST_VEHICLE_STATUS_ASLEEP = {"status": TessieStatus.ASLEEP}
|
||||||
|
|
||||||
TEST_RESPONSE = {"result": True}
|
TEST_RESPONSE = {"result": True}
|
||||||
TEST_RESPONSE_ERROR = {"result": False, "reason": "reason why"}
|
TEST_RESPONSE_ERROR = {"result": False, "reason": "reason why"}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,11 @@ from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from .common import TEST_STATE_OF_ALL_VEHICLES, TEST_VEHICLE_STATE_ONLINE
|
from .common import (
|
||||||
|
TEST_STATE_OF_ALL_VEHICLES,
|
||||||
|
TEST_VEHICLE_STATE_ONLINE,
|
||||||
|
TEST_VEHICLE_STATUS_AWAKE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -18,6 +22,16 @@ def mock_get_state():
|
||||||
yield mock_get_state
|
yield mock_get_state
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_get_status():
|
||||||
|
"""Mock get_status function."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.tessie.coordinator.get_status",
|
||||||
|
return_value=TEST_VEHICLE_STATUS_AWAKE,
|
||||||
|
) as mock_get_status:
|
||||||
|
yield mock_get_status
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_get_state_of_all_vehicles():
|
def mock_get_state_of_all_vehicles():
|
||||||
"""Mock get_state_of_all_vehicles function."""
|
"""Mock get_state_of_all_vehicles function."""
|
||||||
|
|
|
@ -10,8 +10,7 @@ from .common import (
|
||||||
ERROR_AUTH,
|
ERROR_AUTH,
|
||||||
ERROR_CONNECTION,
|
ERROR_CONNECTION,
|
||||||
ERROR_UNKNOWN,
|
ERROR_UNKNOWN,
|
||||||
TEST_VEHICLE_STATE_ASLEEP,
|
TEST_VEHICLE_STATUS_ASLEEP,
|
||||||
TEST_VEHICLE_STATE_ONLINE,
|
|
||||||
setup_platform,
|
setup_platform,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,59 +19,61 @@ from tests.common import async_fire_time_changed
|
||||||
WAIT = timedelta(seconds=TESSIE_SYNC_INTERVAL)
|
WAIT = timedelta(seconds=TESSIE_SYNC_INTERVAL)
|
||||||
|
|
||||||
|
|
||||||
async def test_coordinator_online(hass: HomeAssistant, mock_get_state) -> None:
|
async def test_coordinator_online(
|
||||||
|
hass: HomeAssistant, mock_get_state, mock_get_status
|
||||||
|
) -> None:
|
||||||
"""Tests that the coordinator handles online vehicles."""
|
"""Tests that the coordinator handles online vehicles."""
|
||||||
|
|
||||||
mock_get_state.return_value = TEST_VEHICLE_STATE_ONLINE
|
|
||||||
await setup_platform(hass)
|
await setup_platform(hass)
|
||||||
|
|
||||||
async_fire_time_changed(hass, utcnow() + WAIT)
|
async_fire_time_changed(hass, utcnow() + WAIT)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
mock_get_status.assert_called_once()
|
||||||
mock_get_state.assert_called_once()
|
mock_get_state.assert_called_once()
|
||||||
assert hass.states.get("binary_sensor.test_status").state == STATE_ON
|
assert hass.states.get("binary_sensor.test_status").state == STATE_ON
|
||||||
|
|
||||||
|
|
||||||
async def test_coordinator_asleep(hass: HomeAssistant, mock_get_state) -> None:
|
async def test_coordinator_asleep(hass: HomeAssistant, mock_get_status) -> None:
|
||||||
"""Tests that the coordinator handles asleep vehicles."""
|
"""Tests that the coordinator handles asleep vehicles."""
|
||||||
|
|
||||||
mock_get_state.return_value = TEST_VEHICLE_STATE_ASLEEP
|
|
||||||
await setup_platform(hass)
|
await setup_platform(hass)
|
||||||
|
mock_get_status.return_value = TEST_VEHICLE_STATUS_ASLEEP
|
||||||
|
|
||||||
async_fire_time_changed(hass, utcnow() + WAIT)
|
async_fire_time_changed(hass, utcnow() + WAIT)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
mock_get_state.assert_called_once()
|
mock_get_status.assert_called_once()
|
||||||
assert hass.states.get("binary_sensor.test_status").state == STATE_OFF
|
assert hass.states.get("binary_sensor.test_status").state == STATE_OFF
|
||||||
|
|
||||||
|
|
||||||
async def test_coordinator_clienterror(hass: HomeAssistant, mock_get_state) -> None:
|
async def test_coordinator_clienterror(hass: HomeAssistant, mock_get_status) -> None:
|
||||||
"""Tests that the coordinator handles client errors."""
|
"""Tests that the coordinator handles client errors."""
|
||||||
|
|
||||||
mock_get_state.side_effect = ERROR_UNKNOWN
|
mock_get_status.side_effect = ERROR_UNKNOWN
|
||||||
await setup_platform(hass)
|
await setup_platform(hass)
|
||||||
|
|
||||||
async_fire_time_changed(hass, utcnow() + WAIT)
|
async_fire_time_changed(hass, utcnow() + WAIT)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
mock_get_state.assert_called_once()
|
mock_get_status.assert_called_once()
|
||||||
assert hass.states.get("binary_sensor.test_status").state == STATE_UNAVAILABLE
|
assert hass.states.get("binary_sensor.test_status").state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
async def test_coordinator_auth(hass: HomeAssistant, mock_get_state) -> None:
|
async def test_coordinator_auth(hass: HomeAssistant, mock_get_status) -> None:
|
||||||
"""Tests that the coordinator handles timeout errors."""
|
"""Tests that the coordinator handles timeout errors."""
|
||||||
|
|
||||||
mock_get_state.side_effect = ERROR_AUTH
|
mock_get_status.side_effect = ERROR_AUTH
|
||||||
await setup_platform(hass)
|
await setup_platform(hass)
|
||||||
|
|
||||||
async_fire_time_changed(hass, utcnow() + WAIT)
|
async_fire_time_changed(hass, utcnow() + WAIT)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
mock_get_state.assert_called_once()
|
mock_get_status.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
async def test_coordinator_connection(hass: HomeAssistant, mock_get_state) -> None:
|
async def test_coordinator_connection(hass: HomeAssistant, mock_get_status) -> None:
|
||||||
"""Tests that the coordinator handles connection errors."""
|
"""Tests that the coordinator handles connection errors."""
|
||||||
|
|
||||||
mock_get_state.side_effect = ERROR_CONNECTION
|
mock_get_status.side_effect = ERROR_CONNECTION
|
||||||
await setup_platform(hass)
|
await setup_platform(hass)
|
||||||
async_fire_time_changed(hass, utcnow() + WAIT)
|
async_fire_time_changed(hass, utcnow() + WAIT)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
mock_get_state.assert_called_once()
|
mock_get_status.assert_called_once()
|
||||||
assert hass.states.get("binary_sensor.test_status").state == STATE_UNAVAILABLE
|
assert hass.states.get("binary_sensor.test_status").state == STATE_UNAVAILABLE
|
||||||
|
|
Loading…
Add table
Reference in a new issue