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:
parent
72cb1f5480
commit
3115bf9aab
4 changed files with 152 additions and 35 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue