Add temperature sensor for gogogate2 wireless door sensor (#47754)

* Add temperature sensor for gogogate2 wireless door sensor

* Chain sensor generators
This commit is contained in:
Erik Montnemery 2021-03-12 19:04:56 +01:00 committed by GitHub
parent 72cb1f5480
commit 3115bf9aab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 152 additions and 35 deletions

View file

@ -69,12 +69,13 @@ class GoGoGate2Entity(CoordinatorEntity):
config_entry: ConfigEntry,
data_update_coordinator: DeviceDataUpdateCoordinator,
door: AbstractDoor,
unique_id: str,
) -> None:
"""Initialize gogogate2 base entity."""
super().__init__(data_update_coordinator)
self._config_entry = config_entry
self._door = door
self._unique_id = cover_unique_id(config_entry, door)
self._unique_id = unique_id
@property
def unique_id(self) -> Optional[str]:
@ -137,6 +138,13 @@ def cover_unique_id(config_entry: ConfigEntry, door: AbstractDoor) -> str:
return f"{config_entry.unique_id}_{door.door_id}"
def sensor_unique_id(
config_entry: ConfigEntry, door: AbstractDoor, sensor_type: str
) -> str:
"""Generate a cover entity unique id."""
return f"{config_entry.unique_id}_{door.door_id}_{sensor_type}"
def get_api(config_data: dict) -> AbstractGateApi:
"""Get an api object for config data."""
gate_class = GogoGate2Api

View file

@ -18,6 +18,7 @@ from homeassistant.helpers.entity import Entity
from .common import (
DeviceDataUpdateCoordinator,
GoGoGate2Entity,
cover_unique_id,
get_data_update_coordinator,
)
from .const import DOMAIN
@ -66,7 +67,8 @@ class DeviceCover(GoGoGate2Entity, CoverEntity):
door: AbstractDoor,
) -> None:
"""Initialize the object."""
super().__init__(config_entry, data_update_coordinator, door)
unique_id = cover_unique_id(config_entry, door)
super().__init__(config_entry, data_update_coordinator, door, unique_id)
self._api = data_update_coordinator.api
self._is_available = True

View file

@ -1,14 +1,24 @@
"""Support for Gogogate2 garage Doors."""
from itertools import chain
from typing import Callable, List, Optional
from gogogate2_api.common import get_configured_doors
from gogogate2_api.common import AbstractDoor, get_configured_doors
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import DEVICE_CLASS_BATTERY
from homeassistant.const import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_TEMPERATURE,
TEMP_CELSIUS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
from .common import GoGoGate2Entity, get_data_update_coordinator
from .common import (
DeviceDataUpdateCoordinator,
GoGoGate2Entity,
get_data_update_coordinator,
sensor_unique_id,
)
SENSOR_ID_WIRED = "WIRE"
@ -21,17 +31,33 @@ async def async_setup_entry(
"""Set up the config entry."""
data_update_coordinator = get_data_update_coordinator(hass, config_entry)
async_add_entities(
sensors = chain(
[
DoorSensor(config_entry, data_update_coordinator, door)
DoorSensorBattery(config_entry, data_update_coordinator, door)
for door in get_configured_doors(data_update_coordinator.data)
if door.sensorid and door.sensorid != SENSOR_ID_WIRED
]
],
[
DoorSensorTemperature(config_entry, data_update_coordinator, door)
for door in get_configured_doors(data_update_coordinator.data)
if door.sensorid and door.sensorid != SENSOR_ID_WIRED
],
)
async_add_entities(sensors)
class DoorSensor(GoGoGate2Entity):
"""Sensor entity for goggate2."""
class DoorSensorBattery(GoGoGate2Entity):
"""Battery sensor entity for gogogate2 door sensor."""
def __init__(
self,
config_entry: ConfigEntry,
data_update_coordinator: DeviceDataUpdateCoordinator,
door: AbstractDoor,
) -> None:
"""Initialize the object."""
unique_id = sensor_unique_id(config_entry, door, "battery")
super().__init__(config_entry, data_update_coordinator, door, unique_id)
@property
def name(self):
@ -56,3 +82,46 @@ class DoorSensor(GoGoGate2Entity):
if door.sensorid is not None:
return {"door_id": door.door_id, "sensor_id": door.sensorid}
return None
class DoorSensorTemperature(GoGoGate2Entity):
"""Temperature sensor entity for gogogate2 door sensor."""
def __init__(
self,
config_entry: ConfigEntry,
data_update_coordinator: DeviceDataUpdateCoordinator,
door: AbstractDoor,
) -> None:
"""Initialize the object."""
unique_id = sensor_unique_id(config_entry, door, "temperature")
super().__init__(config_entry, data_update_coordinator, door, unique_id)
@property
def name(self):
"""Return the name of the door."""
return f"{self._get_door().name} temperature"
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return DEVICE_CLASS_TEMPERATURE
@property
def state(self):
"""Return the state of the entity."""
door = self._get_door()
return door.temperature
@property
def unit_of_measurement(self):
"""Return the unit_of_measurement."""
return TEMP_CELSIUS
@property
def device_state_attributes(self):
"""Return the state attributes."""
door = self._get_door()
if door.sensorid is not None:
return {"door_id": door.door_id, "sensor_id": door.sensorid}
return None

View file

@ -20,11 +20,13 @@ from homeassistant.components.gogogate2.const import DEVICE_TYPE_ISMARTGATE, DOM
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_UNIT_OF_MEASUREMENT,
CONF_DEVICE,
CONF_IP_ADDRESS,
CONF_PASSWORD,
CONF_USERNAME,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_TEMPERATURE,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
@ -34,7 +36,7 @@ from homeassistant.util.dt import utcnow
from tests.common import MockConfigEntry, async_fire_time_changed
def _mocked_gogogate_sensor_response(battery_level: int):
def _mocked_gogogate_sensor_response(battery_level: int, temperature: float):
return GogoGate2InfoResponse(
user="user1",
gogogatename="gogogatename0",
@ -55,7 +57,7 @@ def _mocked_gogogate_sensor_response(battery_level: int):
sensorid="ABCD",
camera=False,
events=2,
temperature=None,
temperature=temperature,
voltage=battery_level,
),
door2=GogoGate2Door(
@ -69,7 +71,7 @@ def _mocked_gogogate_sensor_response(battery_level: int):
sensorid="WIRE",
camera=False,
events=0,
temperature=None,
temperature=temperature,
voltage=battery_level,
),
door3=GogoGate2Door(
@ -83,7 +85,7 @@ def _mocked_gogogate_sensor_response(battery_level: int):
sensorid=None,
camera=False,
events=0,
temperature=None,
temperature=temperature,
voltage=battery_level,
),
outputs=Outputs(output1=True, output2=False, output3=True),
@ -92,7 +94,7 @@ def _mocked_gogogate_sensor_response(battery_level: int):
)
def _mocked_ismartgate_sensor_response(battery_level: int):
def _mocked_ismartgate_sensor_response(battery_level: int, temperature: float):
return ISmartGateInfoResponse(
user="user1",
ismartgatename="ismartgatename0",
@ -115,7 +117,7 @@ def _mocked_ismartgate_sensor_response(battery_level: int):
sensorid="ABCD",
camera=False,
events=2,
temperature=None,
temperature=temperature,
enabled=True,
apicode="apicode0",
customimage=False,
@ -132,7 +134,7 @@ def _mocked_ismartgate_sensor_response(battery_level: int):
sensorid="WIRE",
camera=False,
events=2,
temperature=None,
temperature=temperature,
enabled=True,
apicode="apicode0",
customimage=False,
@ -149,7 +151,7 @@ def _mocked_ismartgate_sensor_response(battery_level: int):
sensorid=None,
camera=False,
events=0,
temperature=None,
temperature=temperature,
enabled=True,
apicode="apicode0",
customimage=False,
@ -164,16 +166,23 @@ def _mocked_ismartgate_sensor_response(battery_level: int):
async def test_sensor_update(gogogate2api_mock, hass: HomeAssistant) -> None:
"""Test data update."""
expected_attributes = {
bat_attributes = {
"device_class": "battery",
"door_id": 1,
"friendly_name": "Door1 battery",
"sensor_id": "ABCD",
}
temp_attributes = {
"device_class": "temperature",
"door_id": 1,
"friendly_name": "Door1 temperature",
"sensor_id": "ABCD",
"unit_of_measurement": "°C",
}
api = MagicMock(GogoGate2Api)
api.async_activate.return_value = GogoGate2ActivateResponse(result=True)
api.async_info.return_value = _mocked_gogogate_sensor_response(25)
api.async_info.return_value = _mocked_gogogate_sensor_response(25, 7.0)
gogogate2api_mock.return_value = api
config_entry = MockConfigEntry(
@ -189,31 +198,40 @@ async def test_sensor_update(gogogate2api_mock, hass: HomeAssistant) -> None:
assert hass.states.get("cover.door1") is None
assert hass.states.get("cover.door2") is None
assert hass.states.get("cover.door2") is None
assert hass.states.get("cover.door3") is None
assert hass.states.get("sensor.door1_battery") is None
assert hass.states.get("sensor.door2_battery") is None
assert hass.states.get("sensor.door2_battery") is None
assert hass.states.get("sensor.door3_battery") is None
assert hass.states.get("sensor.door1_temperature") is None
assert hass.states.get("sensor.door2_temperature") is None
assert hass.states.get("sensor.door3_temperature") is None
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("cover.door1")
assert hass.states.get("cover.door2")
assert hass.states.get("cover.door2")
assert hass.states.get("cover.door3")
assert hass.states.get("sensor.door1_battery").state == "25"
assert dict(hass.states.get("sensor.door1_battery").attributes) == bat_attributes
assert hass.states.get("sensor.door2_battery") is None
assert hass.states.get("sensor.door2_battery") is None
assert hass.states.get("sensor.door1_temperature").state == "7.0"
assert (
dict(hass.states.get("sensor.door1_battery").attributes) == expected_attributes
dict(hass.states.get("sensor.door1_temperature").attributes) == temp_attributes
)
assert hass.states.get("sensor.door2_battery") is None
assert hass.states.get("sensor.door2_battery") is None
assert hass.states.get("sensor.door2_temperature") is None
assert hass.states.get("sensor.door3_temperature") is None
api.async_info.return_value = _mocked_gogogate_sensor_response(40)
api.async_info.return_value = _mocked_gogogate_sensor_response(40, 10.0)
async_fire_time_changed(hass, utcnow() + timedelta(hours=2))
await hass.async_block_till_done()
assert hass.states.get("sensor.door1_battery").state == "40"
assert hass.states.get("sensor.door1_temperature").state == "10.0"
api.async_info.return_value = _mocked_gogogate_sensor_response(None)
api.async_info.return_value = _mocked_gogogate_sensor_response(None, None)
async_fire_time_changed(hass, utcnow() + timedelta(hours=2))
await hass.async_block_till_done()
assert hass.states.get("sensor.door1_battery").state == STATE_UNKNOWN
assert hass.states.get("sensor.door1_temperature").state == STATE_UNKNOWN
assert await hass.config_entries.async_unload(config_entry.entry_id)
assert not hass.states.async_entity_ids(DOMAIN)
@ -222,14 +240,21 @@ async def test_sensor_update(gogogate2api_mock, hass: HomeAssistant) -> None:
@patch("homeassistant.components.gogogate2.common.ISmartGateApi")
async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None:
"""Test availability."""
expected_attributes = {
bat_attributes = {
"device_class": "battery",
"door_id": 1,
"friendly_name": "Door1 battery",
"sensor_id": "ABCD",
}
temp_attributes = {
"device_class": "temperature",
"door_id": 1,
"friendly_name": "Door1 temperature",
"sensor_id": "ABCD",
"unit_of_measurement": "°C",
}
sensor_response = _mocked_ismartgate_sensor_response(35)
sensor_response = _mocked_ismartgate_sensor_response(35, -4.0)
api = MagicMock(ISmartGateApi)
api.async_info.return_value = sensor_response
ismartgateapi_mock.return_value = api
@ -248,34 +273,47 @@ async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None:
assert hass.states.get("cover.door1") is None
assert hass.states.get("cover.door2") is None
assert hass.states.get("cover.door2") is None
assert hass.states.get("cover.door3") is None
assert hass.states.get("sensor.door1_battery") is None
assert hass.states.get("sensor.door2_battery") is None
assert hass.states.get("sensor.door2_battery") is None
assert hass.states.get("sensor.door3_battery") is None
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("cover.door1")
assert hass.states.get("cover.door2")
assert hass.states.get("cover.door2")
assert hass.states.get("cover.door3")
assert hass.states.get("sensor.door1_battery").state == "35"
assert hass.states.get("sensor.door2_battery") is None
assert hass.states.get("sensor.door2_battery") is None
assert hass.states.get("sensor.door3_battery") is None
assert hass.states.get("sensor.door1_temperature").state == "-4.0"
assert hass.states.get("sensor.door2_temperature") is None
assert hass.states.get("sensor.door3_temperature") is None
assert (
hass.states.get("sensor.door1_battery").attributes[ATTR_DEVICE_CLASS]
== DEVICE_CLASS_BATTERY
)
assert (
hass.states.get("sensor.door1_temperature").attributes[ATTR_DEVICE_CLASS]
== DEVICE_CLASS_TEMPERATURE
)
assert (
hass.states.get("sensor.door1_temperature").attributes[ATTR_UNIT_OF_MEASUREMENT]
== "°C"
)
api.async_info.side_effect = Exception("Error")
async_fire_time_changed(hass, utcnow() + timedelta(hours=2))
await hass.async_block_till_done()
assert hass.states.get("sensor.door1_battery").state == STATE_UNAVAILABLE
assert hass.states.get("sensor.door1_temperature").state == STATE_UNAVAILABLE
api.async_info.side_effect = None
api.async_info.return_value = sensor_response
async_fire_time_changed(hass, utcnow() + timedelta(hours=2))
await hass.async_block_till_done()
assert hass.states.get("sensor.door1_battery").state == "35"
assert dict(hass.states.get("sensor.door1_battery").attributes) == bat_attributes
assert (
dict(hass.states.get("sensor.door1_battery").attributes) == expected_attributes
dict(hass.states.get("sensor.door1_temperature").attributes) == temp_attributes
)