Update nest integration with fixes from initial PR (#42250)

This commit is contained in:
Allen Porter 2020-10-24 11:48:28 -07:00 committed by GitHub
parent 7a6d82d997
commit 0c852b5f81
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 87 additions and 29 deletions

View file

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

View file

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

View file

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

View file

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

View file

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