mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Implement Review Feedback for Tessie (#105937)
This commit is contained in:
parent
aac02d7b84
commit
a88335272a
@ -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
|
from .const import DOMAIN, TessieStatus
|
||||||
from .coordinator import TessieDataUpdateCoordinator
|
from .coordinator import TessieDataUpdateCoordinator
|
||||||
from .entity import TessieEntity
|
from .entity import TessieEntity
|
||||||
|
|
||||||
@ -27,6 +27,11 @@ class TessieBinarySensorEntityDescription(BinarySensorEntityDescription):
|
|||||||
|
|
||||||
|
|
||||||
DESCRIPTIONS: tuple[TessieBinarySensorEntityDescription, ...] = (
|
DESCRIPTIONS: tuple[TessieBinarySensorEntityDescription, ...] = (
|
||||||
|
TessieBinarySensorEntityDescription(
|
||||||
|
key="state",
|
||||||
|
device_class=BinarySensorDeviceClass.CONNECTIVITY,
|
||||||
|
is_on=lambda x: x == TessieStatus.ONLINE,
|
||||||
|
),
|
||||||
TessieBinarySensorEntityDescription(
|
TessieBinarySensorEntityDescription(
|
||||||
key="charge_state_battery_heater_on",
|
key="charge_state_battery_heater_on",
|
||||||
device_class=BinarySensorDeviceClass.HEAT,
|
device_class=BinarySensorDeviceClass.HEAT,
|
||||||
|
@ -18,6 +18,9 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
|||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
TESSIE_SCHEMA = vol.Schema({vol.Required(CONF_ACCESS_TOKEN): str})
|
TESSIE_SCHEMA = vol.Schema({vol.Required(CONF_ACCESS_TOKEN): str})
|
||||||
|
DESCRIPTION_PLACEHOLDERS = {
|
||||||
|
"url": "[my.tessie.com/settings/api](https://my.tessie.com/settings/api)"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TessieConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
class TessieConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
@ -57,6 +60,7 @@ class TessieConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user",
|
step_id="user",
|
||||||
data_schema=TESSIE_SCHEMA,
|
data_schema=TESSIE_SCHEMA,
|
||||||
|
description_placeholders=DESCRIPTION_PLACEHOLDERS,
|
||||||
errors=errors,
|
errors=errors,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -98,5 +102,6 @@ class TessieConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="reauth_confirm",
|
step_id="reauth_confirm",
|
||||||
data_schema=TESSIE_SCHEMA,
|
data_schema=TESSIE_SCHEMA,
|
||||||
|
description_placeholders=DESCRIPTION_PLACEHOLDERS,
|
||||||
errors=errors,
|
errors=errors,
|
||||||
)
|
)
|
||||||
|
@ -18,4 +18,3 @@ class TessieStatus(StrEnum):
|
|||||||
|
|
||||||
ASLEEP = "asleep"
|
ASLEEP = "asleep"
|
||||||
ONLINE = "online"
|
ONLINE = "online"
|
||||||
OFFLINE = "offline"
|
|
||||||
|
@ -20,7 +20,7 @@ TESSIE_SYNC_INTERVAL = 10
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TessieDataUpdateCoordinator(DataUpdateCoordinator):
|
class TessieDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
"""Class to manage fetching data from the Tessie API."""
|
"""Class to manage fetching data from the Tessie API."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -35,16 +35,15 @@ class TessieDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
name="Tessie",
|
name="Tessie",
|
||||||
update_method=self.async_update_data,
|
|
||||||
update_interval=timedelta(seconds=TESSIE_SYNC_INTERVAL),
|
update_interval=timedelta(seconds=TESSIE_SYNC_INTERVAL),
|
||||||
)
|
)
|
||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
self.vin = vin
|
self.vin = vin
|
||||||
self.session = async_get_clientsession(hass)
|
self.session = async_get_clientsession(hass)
|
||||||
self.data = self._flattern(data)
|
self.data = self._flatten(data)
|
||||||
self.did_first_update = False
|
self.did_first_update = False
|
||||||
|
|
||||||
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:
|
||||||
vehicle = await get_state(
|
vehicle = await get_state(
|
||||||
@ -54,10 +53,6 @@ class TessieDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
use_cache=self.did_first_update,
|
use_cache=self.did_first_update,
|
||||||
)
|
)
|
||||||
except ClientResponseError as e:
|
except ClientResponseError as e:
|
||||||
if e.status == HTTPStatus.REQUEST_TIMEOUT:
|
|
||||||
# Vehicle is offline, only update state and dont throw error
|
|
||||||
self.data["state"] = TessieStatus.OFFLINE
|
|
||||||
return self.data
|
|
||||||
if e.status == HTTPStatus.UNAUTHORIZED:
|
if e.status == HTTPStatus.UNAUTHORIZED:
|
||||||
# Auth Token is no longer valid
|
# Auth Token is no longer valid
|
||||||
raise ConfigEntryAuthFailed from e
|
raise ConfigEntryAuthFailed from e
|
||||||
@ -66,22 +61,22 @@ class TessieDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
self.did_first_update = True
|
self.did_first_update = True
|
||||||
if vehicle["state"] == TessieStatus.ONLINE:
|
if vehicle["state"] == TessieStatus.ONLINE:
|
||||||
# Vehicle is online, all data is fresh
|
# Vehicle is online, all data is fresh
|
||||||
return self._flattern(vehicle)
|
return self._flatten(vehicle)
|
||||||
|
|
||||||
# Vehicle is asleep, only update state
|
# Vehicle is asleep, only update state
|
||||||
self.data["state"] = vehicle["state"]
|
self.data["state"] = vehicle["state"]
|
||||||
return self.data
|
return self.data
|
||||||
|
|
||||||
def _flattern(
|
def _flatten(
|
||||||
self, data: dict[str, Any], parent: str | None = None
|
self, data: dict[str, Any], parent: str | None = None
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Flattern the data structure."""
|
"""Flatten the data structure."""
|
||||||
result = {}
|
result = {}
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
if parent:
|
if parent:
|
||||||
key = f"{parent}_{key}"
|
key = f"{parent}_{key}"
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
result.update(self._flattern(value, key))
|
result.update(self._flatten(value, key))
|
||||||
else:
|
else:
|
||||||
result[key] = value
|
result[key] = value
|
||||||
return result
|
return result
|
||||||
|
@ -27,7 +27,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
|
|
||||||
from .const import DOMAIN, TessieStatus
|
from .const import DOMAIN
|
||||||
from .coordinator import TessieDataUpdateCoordinator
|
from .coordinator import TessieDataUpdateCoordinator
|
||||||
from .entity import TessieEntity
|
from .entity import TessieEntity
|
||||||
|
|
||||||
@ -40,11 +40,6 @@ class TessieSensorEntityDescription(SensorEntityDescription):
|
|||||||
|
|
||||||
|
|
||||||
DESCRIPTIONS: tuple[TessieSensorEntityDescription, ...] = (
|
DESCRIPTIONS: tuple[TessieSensorEntityDescription, ...] = (
|
||||||
TessieSensorEntityDescription(
|
|
||||||
key="state",
|
|
||||||
options=[status.value for status in TessieStatus],
|
|
||||||
device_class=SensorDeviceClass.ENUM,
|
|
||||||
),
|
|
||||||
TessieSensorEntityDescription(
|
TessieSensorEntityDescription(
|
||||||
key="charge_state_usable_battery_level",
|
key="charge_state_usable_battery_level",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"access_token": "[%key:common::config_flow::data::access_token%]"
|
"access_token": "[%key:common::config_flow::data::access_token%]"
|
||||||
},
|
},
|
||||||
"description": "Enter your access token from [my.tessie.com/settings/api](https://my.tessie.com/settings/api)."
|
"description": "Enter your access token from {url}."
|
||||||
},
|
},
|
||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
"data": {
|
"data": {
|
||||||
@ -23,14 +23,6 @@
|
|||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"state": {
|
|
||||||
"name": "Status",
|
|
||||||
"state": {
|
|
||||||
"online": "Online",
|
|
||||||
"asleep": "Asleep",
|
|
||||||
"offline": "Offline"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"charge_state_usable_battery_level": {
|
"charge_state_usable_battery_level": {
|
||||||
"name": "Battery level"
|
"name": "Battery level"
|
||||||
},
|
},
|
||||||
@ -96,6 +88,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"binary_sensor": {
|
"binary_sensor": {
|
||||||
|
"state": {
|
||||||
|
"name": "Status"
|
||||||
|
},
|
||||||
"charge_state_battery_heater_on": {
|
"charge_state_battery_heater_on": {
|
||||||
"name": "Battery heater"
|
"name": "Battery heater"
|
||||||
},
|
},
|
||||||
|
@ -2,15 +2,13 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from homeassistant.components.tessie.coordinator import TESSIE_SYNC_INTERVAL
|
from homeassistant.components.tessie.coordinator import TESSIE_SYNC_INTERVAL
|
||||||
from homeassistant.components.tessie.sensor import TessieStatus
|
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
|
||||||
from homeassistant.const import STATE_UNAVAILABLE
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
from .common import (
|
from .common import (
|
||||||
ERROR_AUTH,
|
ERROR_AUTH,
|
||||||
ERROR_CONNECTION,
|
ERROR_CONNECTION,
|
||||||
ERROR_TIMEOUT,
|
|
||||||
ERROR_UNKNOWN,
|
ERROR_UNKNOWN,
|
||||||
TEST_VEHICLE_STATE_ASLEEP,
|
TEST_VEHICLE_STATE_ASLEEP,
|
||||||
TEST_VEHICLE_STATE_ONLINE,
|
TEST_VEHICLE_STATE_ONLINE,
|
||||||
@ -31,7 +29,7 @@ async def test_coordinator_online(hass: HomeAssistant, mock_get_state) -> None:
|
|||||||
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_state.assert_called_once()
|
||||||
assert hass.states.get("sensor.test_status").state == TessieStatus.ONLINE
|
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_state) -> None:
|
||||||
@ -43,7 +41,7 @@ async def test_coordinator_asleep(hass: HomeAssistant, mock_get_state) -> None:
|
|||||||
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_state.assert_called_once()
|
||||||
assert hass.states.get("sensor.test_status").state == TessieStatus.ASLEEP
|
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_state) -> None:
|
||||||
@ -55,19 +53,7 @@ async def test_coordinator_clienterror(hass: HomeAssistant, mock_get_state) -> N
|
|||||||
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_state.assert_called_once()
|
||||||
assert hass.states.get("sensor.test_status").state == STATE_UNAVAILABLE
|
assert hass.states.get("binary_sensor.test_status").state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
async def test_coordinator_timeout(hass: HomeAssistant, mock_get_state) -> None:
|
|
||||||
"""Tests that the coordinator handles timeout errors."""
|
|
||||||
|
|
||||||
mock_get_state.side_effect = ERROR_TIMEOUT
|
|
||||||
await setup_platform(hass)
|
|
||||||
|
|
||||||
async_fire_time_changed(hass, utcnow() + WAIT)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
mock_get_state.assert_called_once()
|
|
||||||
assert hass.states.get("sensor.test_status").state == TessieStatus.OFFLINE
|
|
||||||
|
|
||||||
|
|
||||||
async def test_coordinator_auth(hass: HomeAssistant, mock_get_state) -> None:
|
async def test_coordinator_auth(hass: HomeAssistant, mock_get_state) -> None:
|
||||||
@ -89,4 +75,4 @@ async def test_coordinator_connection(hass: HomeAssistant, mock_get_state) -> No
|
|||||||
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_state.assert_called_once()
|
||||||
assert hass.states.get("sensor.test_status").state == STATE_UNAVAILABLE
|
assert hass.states.get("binary_sensor.test_status").state == STATE_UNAVAILABLE
|
||||||
|
Loading…
x
Reference in New Issue
Block a user