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_entry_oauth2_flow,
|
||||||
config_validation as cv,
|
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 homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
from . import api, config_flow, local_auth
|
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
|
# This event triggered an update to a device that changed some
|
||||||
# properties which the DeviceManager should already have received.
|
# properties which the DeviceManager should already have received.
|
||||||
# Send a signal to refresh state of all listening devices.
|
# 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):
|
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]
|
auth, config[CONF_PROJECT_ID], config[CONF_SUBSCRIBER_ID]
|
||||||
)
|
)
|
||||||
subscriber.set_update_callback(SignalUpdateCallback(hass))
|
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
|
hass.data[DOMAIN][entry.entry_id] = subscriber
|
||||||
|
|
||||||
for component in PLATFORMS:
|
for component in PLATFORMS:
|
||||||
|
|
|
@ -61,6 +61,10 @@ class CodeInvalid(NestAuthError):
|
||||||
"""Raised when invalid authorization code."""
|
"""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)
|
@config_entries.HANDLERS.register(DOMAIN)
|
||||||
class NestFlowHandler(
|
class NestFlowHandler(
|
||||||
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
|
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
|
||||||
|
@ -111,7 +115,7 @@ class NestFlowHandler(
|
||||||
async def async_step_init(self, user_input=None):
|
async def async_step_init(self, user_input=None):
|
||||||
"""Handle a flow start."""
|
"""Handle a flow start."""
|
||||||
if self.is_sdm_api():
|
if self.is_sdm_api():
|
||||||
return None
|
raise UnexpectedStateError("Step only supported for legacy API")
|
||||||
|
|
||||||
flows = self.hass.data.get(DATA_FLOW_IMPL, {})
|
flows = self.hass.data.get(DATA_FLOW_IMPL, {})
|
||||||
|
|
||||||
|
@ -142,7 +146,7 @@ class NestFlowHandler(
|
||||||
deliver the authentication code.
|
deliver the authentication code.
|
||||||
"""
|
"""
|
||||||
if self.is_sdm_api():
|
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]
|
flow = self.hass.data[DATA_FLOW_IMPL][self.flow_impl]
|
||||||
|
|
||||||
|
@ -185,7 +189,7 @@ class NestFlowHandler(
|
||||||
async def async_step_import(self, info):
|
async def async_step_import(self, info):
|
||||||
"""Import existing auth from Nest."""
|
"""Import existing auth from Nest."""
|
||||||
if self.is_sdm_api():
|
if self.is_sdm_api():
|
||||||
return None
|
raise UnexpectedStateError("Step only supported for legacy API")
|
||||||
|
|
||||||
if self.hass.config_entries.async_entries(DOMAIN):
|
if self.hass.config_entries.async_entries(DOMAIN):
|
||||||
return self.async_abort(reason="single_instance_allowed")
|
return self.async_abort(reason="single_instance_allowed")
|
||||||
|
|
|
@ -13,5 +13,5 @@ async def async_setup_entry(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the sensors."""
|
"""Set up the sensors."""
|
||||||
if DATA_SDM not in entry.data:
|
if DATA_SDM not in entry.data:
|
||||||
return await async_setup_legacy_entry(hass, entry, async_add_entities)
|
await async_setup_legacy_entry(hass, entry, async_add_entities)
|
||||||
return await async_setup_sdm_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
|
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(
|
async def async_setup_sdm_entry(
|
||||||
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
|
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
|
||||||
|
@ -46,7 +53,7 @@ class SensorBase(Entity):
|
||||||
self._device = device
|
self._device = device
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_pool(self) -> bool:
|
def should_poll(self) -> bool:
|
||||||
"""Disable polling since entities have state pushed via pubsub."""
|
"""Disable polling since entities have state pushed via pubsub."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -89,28 +96,19 @@ class SensorBase(Entity):
|
||||||
# The API intentionally returns minimal information about specific
|
# The API intentionally returns minimal information about specific
|
||||||
# devices, instead relying on traits, but we can infer a generic model
|
# devices, instead relying on traits, but we can infer a generic model
|
||||||
# name based on the type
|
# name based on the type
|
||||||
if self._device.type == "sdm.devices.types.CAMERA":
|
return DEVICE_TYPE_MAP.get(self._device.type)
|
||||||
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
|
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Run when entity is added to register update signal handler."""
|
"""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
|
# Event messages trigger the SIGNAL_NEST_UPDATE, which is intercepted
|
||||||
# here to re-fresh the signals from _device. Unregister this callback
|
# here to re-fresh the signals from _device. Unregister this callback
|
||||||
# when the entity is removed.
|
# when the entity is removed.
|
||||||
self.async_on_remove(
|
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):
|
class FakeDeviceManager(DeviceManager):
|
||||||
"""Fake DeviceManager that can supply a list of devices and structures."""
|
"""Fake DeviceManager that can supply a list of devices and structures."""
|
||||||
|
@ -104,7 +106,7 @@ async def setup_sensor(hass, devices={}, structures={}):
|
||||||
"homeassistant.components.nest.GoogleNestSubscriber", return_value=subscriber
|
"homeassistant.components.nest.GoogleNestSubscriber", return_value=subscriber
|
||||||
):
|
):
|
||||||
assert await async_setup_component(hass, DOMAIN, CONFIG)
|
assert await async_setup_component(hass, DOMAIN, CONFIG)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
return subscriber
|
return subscriber
|
||||||
|
|
||||||
|
|
||||||
|
@ -114,7 +116,7 @@ async def test_thermostat_device(hass):
|
||||||
"some-device-id": Device.MakeDevice(
|
"some-device-id": Device.MakeDevice(
|
||||||
{
|
{
|
||||||
"name": "some-device-id",
|
"name": "some-device-id",
|
||||||
"type": "sdm.devices.types.Thermostat",
|
"type": THERMOSTAT_TYPE,
|
||||||
"traits": {
|
"traits": {
|
||||||
"sdm.devices.traits.Info": {
|
"sdm.devices.traits.Info": {
|
||||||
"customName": "My Sensor",
|
"customName": "My Sensor",
|
||||||
|
@ -140,6 +142,18 @@ async def test_thermostat_device(hass):
|
||||||
assert humidity is not None
|
assert humidity is not None
|
||||||
assert humidity.state == "35.0"
|
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):
|
async def test_no_devices(hass):
|
||||||
"""Test no devices returned by the api."""
|
"""Test no devices returned by the api."""
|
||||||
|
@ -158,7 +172,7 @@ async def test_device_no_sensor_traits(hass):
|
||||||
"some-device-id": Device.MakeDevice(
|
"some-device-id": Device.MakeDevice(
|
||||||
{
|
{
|
||||||
"name": "some-device-id",
|
"name": "some-device-id",
|
||||||
"type": "sdm.devices.types.Thermostat",
|
"type": THERMOSTAT_TYPE,
|
||||||
"traits": {},
|
"traits": {},
|
||||||
},
|
},
|
||||||
auth=None,
|
auth=None,
|
||||||
|
@ -179,7 +193,7 @@ async def test_device_name_from_structure(hass):
|
||||||
"some-device-id": Device.MakeDevice(
|
"some-device-id": Device.MakeDevice(
|
||||||
{
|
{
|
||||||
"name": "some-device-id",
|
"name": "some-device-id",
|
||||||
"type": "sdm.devices.types.Thermostat",
|
"type": THERMOSTAT_TYPE,
|
||||||
"traits": {
|
"traits": {
|
||||||
"sdm.devices.traits.Temperature": {
|
"sdm.devices.traits.Temperature": {
|
||||||
"ambientTemperatureCelsius": 25.2,
|
"ambientTemperatureCelsius": 25.2,
|
||||||
|
@ -205,7 +219,7 @@ async def test_event_updates_sensor(hass):
|
||||||
"some-device-id": Device.MakeDevice(
|
"some-device-id": Device.MakeDevice(
|
||||||
{
|
{
|
||||||
"name": "some-device-id",
|
"name": "some-device-id",
|
||||||
"type": "sdm.devices.types.Thermostat",
|
"type": THERMOSTAT_TYPE,
|
||||||
"traits": {
|
"traits": {
|
||||||
"sdm.devices.traits.Info": {
|
"sdm.devices.traits.Info": {
|
||||||
"customName": "My Sensor",
|
"customName": "My Sensor",
|
||||||
|
@ -246,3 +260,41 @@ async def test_event_updates_sensor(hass):
|
||||||
temperature = hass.states.get("sensor.my_sensor_temperature")
|
temperature = hass.states.get("sensor.my_sensor_temperature")
|
||||||
assert temperature is not None
|
assert temperature is not None
|
||||||
assert temperature.state == "26.2"
|
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