Update nest integration with fixes from initial PR (#42250)
This commit is contained in:
parent
7a6d82d997
commit
0c852b5f81
5 changed files with 87 additions and 29 deletions
|
@ -30,7 +30,11 @@ from homeassistant.helpers import (
|
|||
config_entry_oauth2_flow,
|
||||
config_validation as cv,
|
||||
)
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect,
|
||||
async_dispatcher_send,
|
||||
dispatcher_send,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from . import api, config_flow, local_auth
|
||||
|
@ -176,7 +180,7 @@ class SignalUpdateCallback(EventCallback):
|
|||
# This event triggered an update to a device that changed some
|
||||
# properties which the DeviceManager should already have received.
|
||||
# Send a signal to refresh state of all listening devices.
|
||||
dispatcher_send(self._hass, SIGNAL_NEST_UPDATE)
|
||||
async_dispatcher_send(self._hass, SIGNAL_NEST_UPDATE)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
|
@ -203,7 +207,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||
auth, config[CONF_PROJECT_ID], config[CONF_SUBSCRIBER_ID]
|
||||
)
|
||||
subscriber.set_update_callback(SignalUpdateCallback(hass))
|
||||
hass.loop.create_task(subscriber.start_async())
|
||||
asyncio.create_task(subscriber.start_async())
|
||||
hass.data[DOMAIN][entry.entry_id] = subscriber
|
||||
|
||||
for component in PLATFORMS:
|
||||
|
|
|
@ -61,6 +61,10 @@ class CodeInvalid(NestAuthError):
|
|||
"""Raised when invalid authorization code."""
|
||||
|
||||
|
||||
class UnexpectedStateError(HomeAssistantError):
|
||||
"""Raised when the config flow is invoked in a 'should not happen' case."""
|
||||
|
||||
|
||||
@config_entries.HANDLERS.register(DOMAIN)
|
||||
class NestFlowHandler(
|
||||
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
|
||||
|
@ -111,7 +115,7 @@ class NestFlowHandler(
|
|||
async def async_step_init(self, user_input=None):
|
||||
"""Handle a flow start."""
|
||||
if self.is_sdm_api():
|
||||
return None
|
||||
raise UnexpectedStateError("Step only supported for legacy API")
|
||||
|
||||
flows = self.hass.data.get(DATA_FLOW_IMPL, {})
|
||||
|
||||
|
@ -142,7 +146,7 @@ class NestFlowHandler(
|
|||
deliver the authentication code.
|
||||
"""
|
||||
if self.is_sdm_api():
|
||||
return None
|
||||
raise UnexpectedStateError("Step only supported for legacy API")
|
||||
|
||||
flow = self.hass.data[DATA_FLOW_IMPL][self.flow_impl]
|
||||
|
||||
|
@ -185,7 +189,7 @@ class NestFlowHandler(
|
|||
async def async_step_import(self, info):
|
||||
"""Import existing auth from Nest."""
|
||||
if self.is_sdm_api():
|
||||
return None
|
||||
raise UnexpectedStateError("Step only supported for legacy API")
|
||||
|
||||
if self.hass.config_entries.async_entries(DOMAIN):
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
|
|
|
@ -13,5 +13,5 @@ async def async_setup_entry(
|
|||
) -> None:
|
||||
"""Set up the sensors."""
|
||||
if DATA_SDM not in entry.data:
|
||||
return await async_setup_legacy_entry(hass, entry, async_add_entities)
|
||||
return await async_setup_sdm_entry(hass, entry, async_add_entities)
|
||||
await async_setup_legacy_entry(hass, entry, async_add_entities)
|
||||
await async_setup_sdm_entry(hass, entry, async_add_entities)
|
||||
|
|
|
@ -18,6 +18,13 @@ from homeassistant.helpers.typing import HomeAssistantType
|
|||
|
||||
from .const import DOMAIN, SIGNAL_NEST_UPDATE
|
||||
|
||||
DEVICE_TYPE_MAP = {
|
||||
"sdm.devices.types.CAMERA": "Camera",
|
||||
"sdm.devices.types.DISPLAY": "Display",
|
||||
"sdm.devices.types.DOORBELL": "Doorbell",
|
||||
"sdm.devices.types.THERMOSTAT": "Thermostat",
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_sdm_entry(
|
||||
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
|
||||
|
@ -46,7 +53,7 @@ class SensorBase(Entity):
|
|||
self._device = device
|
||||
|
||||
@property
|
||||
def should_pool(self) -> bool:
|
||||
def should_poll(self) -> bool:
|
||||
"""Disable polling since entities have state pushed via pubsub."""
|
||||
return False
|
||||
|
||||
|
@ -89,28 +96,19 @@ class SensorBase(Entity):
|
|||
# The API intentionally returns minimal information about specific
|
||||
# devices, instead relying on traits, but we can infer a generic model
|
||||
# name based on the type
|
||||
if self._device.type == "sdm.devices.types.CAMERA":
|
||||
return "Camera"
|
||||
if self._device.type == "sdm.devices.types.DISPLAY":
|
||||
return "Display"
|
||||
if self._device.type == "sdm.devices.types.DOORBELL":
|
||||
return "Doorbell"
|
||||
if self._device.type == "sdm.devices.types.THERMOSTAT":
|
||||
return "Thermostat"
|
||||
return None
|
||||
return DEVICE_TYPE_MAP.get(self._device.type)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Run when entity is added to register update signal handler."""
|
||||
|
||||
async def async_update_state():
|
||||
"""Update sensor state."""
|
||||
await self.async_update_ha_state(True)
|
||||
|
||||
# Event messages trigger the SIGNAL_NEST_UPDATE, which is intercepted
|
||||
# here to re-fresh the signals from _device. Unregister this callback
|
||||
# when the entity is removed.
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(self.hass, SIGNAL_NEST_UPDATE, async_update_state)
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
SIGNAL_NEST_UPDATE,
|
||||
self.async_write_ha_state,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -41,6 +41,8 @@ CONFIG_ENTRY_DATA = {
|
|||
},
|
||||
}
|
||||
|
||||
THERMOSTAT_TYPE = "sdm.devices.types.THERMOSTAT"
|
||||
|
||||
|
||||
class FakeDeviceManager(DeviceManager):
|
||||
"""Fake DeviceManager that can supply a list of devices and structures."""
|
||||
|
@ -114,7 +116,7 @@ async def test_thermostat_device(hass):
|
|||
"some-device-id": Device.MakeDevice(
|
||||
{
|
||||
"name": "some-device-id",
|
||||
"type": "sdm.devices.types.Thermostat",
|
||||
"type": THERMOSTAT_TYPE,
|
||||
"traits": {
|
||||
"sdm.devices.traits.Info": {
|
||||
"customName": "My Sensor",
|
||||
|
@ -140,6 +142,18 @@ async def test_thermostat_device(hass):
|
|||
assert humidity is not None
|
||||
assert humidity.state == "35.0"
|
||||
|
||||
registry = await hass.helpers.entity_registry.async_get_registry()
|
||||
entry = registry.async_get("sensor.my_sensor_temperature")
|
||||
assert entry.unique_id == "some-device-id-temperature"
|
||||
assert entry.original_name == "My Sensor Temperature"
|
||||
assert entry.domain == "sensor"
|
||||
|
||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
||||
device = device_registry.async_get(entry.device_id)
|
||||
assert device.name == "My Sensor"
|
||||
assert device.model == "Thermostat"
|
||||
assert device.identifiers == {("nest", "some-device-id")}
|
||||
|
||||
|
||||
async def test_no_devices(hass):
|
||||
"""Test no devices returned by the api."""
|
||||
|
@ -158,7 +172,7 @@ async def test_device_no_sensor_traits(hass):
|
|||
"some-device-id": Device.MakeDevice(
|
||||
{
|
||||
"name": "some-device-id",
|
||||
"type": "sdm.devices.types.Thermostat",
|
||||
"type": THERMOSTAT_TYPE,
|
||||
"traits": {},
|
||||
},
|
||||
auth=None,
|
||||
|
@ -179,7 +193,7 @@ async def test_device_name_from_structure(hass):
|
|||
"some-device-id": Device.MakeDevice(
|
||||
{
|
||||
"name": "some-device-id",
|
||||
"type": "sdm.devices.types.Thermostat",
|
||||
"type": THERMOSTAT_TYPE,
|
||||
"traits": {
|
||||
"sdm.devices.traits.Temperature": {
|
||||
"ambientTemperatureCelsius": 25.2,
|
||||
|
@ -205,7 +219,7 @@ async def test_event_updates_sensor(hass):
|
|||
"some-device-id": Device.MakeDevice(
|
||||
{
|
||||
"name": "some-device-id",
|
||||
"type": "sdm.devices.types.Thermostat",
|
||||
"type": THERMOSTAT_TYPE,
|
||||
"traits": {
|
||||
"sdm.devices.traits.Info": {
|
||||
"customName": "My Sensor",
|
||||
|
@ -246,3 +260,41 @@ async def test_event_updates_sensor(hass):
|
|||
temperature = hass.states.get("sensor.my_sensor_temperature")
|
||||
assert temperature is not None
|
||||
assert temperature.state == "26.2"
|
||||
|
||||
|
||||
async def test_device_with_unknown_type(hass):
|
||||
"""Test a device without a custom name, inferring name from structure."""
|
||||
devices = {
|
||||
"some-device-id": Device.MakeDevice(
|
||||
{
|
||||
"name": "some-device-id",
|
||||
"type": "some-unknown-type",
|
||||
"traits": {
|
||||
"sdm.devices.traits.Info": {
|
||||
"customName": "My Sensor",
|
||||
},
|
||||
"sdm.devices.traits.Temperature": {
|
||||
"ambientTemperatureCelsius": 25.1,
|
||||
},
|
||||
},
|
||||
},
|
||||
auth=None,
|
||||
)
|
||||
}
|
||||
await setup_sensor(hass, devices)
|
||||
|
||||
temperature = hass.states.get("sensor.my_sensor_temperature")
|
||||
assert temperature is not None
|
||||
assert temperature.state == "25.1"
|
||||
|
||||
registry = await hass.helpers.entity_registry.async_get_registry()
|
||||
entry = registry.async_get("sensor.my_sensor_temperature")
|
||||
assert entry.unique_id == "some-device-id-temperature"
|
||||
assert entry.original_name == "My Sensor Temperature"
|
||||
assert entry.domain == "sensor"
|
||||
|
||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
||||
device = device_registry.async_get(entry.device_id)
|
||||
assert device.name == "My Sensor"
|
||||
assert device.model is None
|
||||
assert device.identifiers == {("nest", "some-device-id")}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue