mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 01:08:12 +00:00
Add binary sensor for myq gateway connectivity (#33423)
This commit is contained in:
parent
12b408219e
commit
b783aab41b
@ -442,7 +442,6 @@ omit =
|
||||
homeassistant/components/mychevy/*
|
||||
homeassistant/components/mycroft/*
|
||||
homeassistant/components/mycroft/notify.py
|
||||
homeassistant/components/myq/cover.py
|
||||
homeassistant/components/mysensors/*
|
||||
homeassistant/components/mystrom/binary_sensor.py
|
||||
homeassistant/components/mystrom/light.py
|
||||
|
108
homeassistant/components/myq/binary_sensor.py
Normal file
108
homeassistant/components/myq/binary_sensor.py
Normal file
@ -0,0 +1,108 @@
|
||||
"""Support for MyQ gateways."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
DEVICE_CLASS_CONNECTIVITY,
|
||||
BinarySensorDevice,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
KNOWN_MODELS,
|
||||
MANUFACTURER,
|
||||
MYQ_COORDINATOR,
|
||||
MYQ_DEVICE_FAMILY,
|
||||
MYQ_DEVICE_FAMILY_GATEWAY,
|
||||
MYQ_DEVICE_STATE,
|
||||
MYQ_DEVICE_STATE_ONLINE,
|
||||
MYQ_GATEWAY,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up mysq covers."""
|
||||
data = hass.data[DOMAIN][config_entry.entry_id]
|
||||
myq = data[MYQ_GATEWAY]
|
||||
coordinator = data[MYQ_COORDINATOR]
|
||||
|
||||
entities = []
|
||||
|
||||
for device in myq.devices.values():
|
||||
if device.device_json[MYQ_DEVICE_FAMILY] == MYQ_DEVICE_FAMILY_GATEWAY:
|
||||
entities.append(MyQBinarySensorDevice(coordinator, device))
|
||||
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
class MyQBinarySensorDevice(BinarySensorDevice):
|
||||
"""Representation of a MyQ gateway."""
|
||||
|
||||
def __init__(self, coordinator, device):
|
||||
"""Initialize with API object, device id."""
|
||||
self._coordinator = coordinator
|
||||
self._device = device
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""We track connectivity for gateways."""
|
||||
return DEVICE_CLASS_CONNECTIVITY
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the garage door if any."""
|
||||
return f"{self._device.name} MyQ Gateway"
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return if the device is online."""
|
||||
if not self._coordinator.last_update_success:
|
||||
return False
|
||||
|
||||
# Not all devices report online so assume True if its missing
|
||||
return self._device.device_json[MYQ_DEVICE_STATE].get(
|
||||
MYQ_DEVICE_STATE_ONLINE, True
|
||||
)
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique, Home Assistant friendly identifier for this entity."""
|
||||
return self._device.device_id
|
||||
|
||||
async def async_update(self):
|
||||
"""Update status of cover."""
|
||||
await self._coordinator.async_request_refresh()
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device_info of the device."""
|
||||
device_info = {
|
||||
"identifiers": {(DOMAIN, self._device.device_id)},
|
||||
"name": self.name,
|
||||
"manufacturer": MANUFACTURER,
|
||||
"sw_version": self._device.firmware_version,
|
||||
}
|
||||
model = KNOWN_MODELS.get(self._device.device_id[2:4])
|
||||
if model:
|
||||
device_info["model"] = model
|
||||
|
||||
return device_info
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return False, updates are controlled via coordinator."""
|
||||
return False
|
||||
|
||||
@callback
|
||||
def _async_consume_update(self):
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe to updates."""
|
||||
self._coordinator.async_add_listener(self._async_consume_update)
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Undo subscription."""
|
||||
self._coordinator.async_remove_listener(self._async_consume_update)
|
@ -10,10 +10,14 @@ from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_O
|
||||
|
||||
DOMAIN = "myq"
|
||||
|
||||
PLATFORMS = ["cover"]
|
||||
PLATFORMS = ["cover", "binary_sensor"]
|
||||
|
||||
MYQ_DEVICE_TYPE = "device_type"
|
||||
MYQ_DEVICE_TYPE_GATE = "gate"
|
||||
|
||||
MYQ_DEVICE_FAMILY = "device_family"
|
||||
MYQ_DEVICE_FAMILY_GATEWAY = "gateway"
|
||||
|
||||
MYQ_DEVICE_STATE = "state"
|
||||
MYQ_DEVICE_STATE_ONLINE = "online"
|
||||
|
||||
@ -39,3 +43,36 @@ TRANSITION_START_DURATION = 7
|
||||
# Estimated time it takes myq to complete a transition
|
||||
# from one state to another
|
||||
TRANSITION_COMPLETE_DURATION = 37
|
||||
|
||||
MANUFACTURER = "The Chamberlain Group Inc."
|
||||
|
||||
KNOWN_MODELS = {
|
||||
"00": "Chamberlain Ethernet Gateway",
|
||||
"01": "LiftMaster Ethernet Gateway",
|
||||
"02": "Craftsman Ethernet Gateway",
|
||||
"03": "Chamberlain Wi-Fi hub",
|
||||
"04": "LiftMaster Wi-Fi hub",
|
||||
"05": "Craftsman Wi-Fi hub",
|
||||
"08": "LiftMaster Wi-Fi GDO DC w/Battery Backup",
|
||||
"09": "Chamberlain Wi-Fi GDO DC w/Battery Backup",
|
||||
"10": "Craftsman Wi-Fi GDO DC 3/4HP",
|
||||
"11": "MyQ Replacement Logic Board Wi-Fi GDO DC 3/4HP",
|
||||
"12": "Chamberlain Wi-Fi GDO DC 1.25HP",
|
||||
"13": "LiftMaster Wi-Fi GDO DC 1.25HP",
|
||||
"14": "Craftsman Wi-Fi GDO DC 1.25HP",
|
||||
"15": "MyQ Replacement Logic Board Wi-Fi GDO DC 1.25HP",
|
||||
"0A": "Chamberlain Wi-Fi GDO or Gate Operator AC",
|
||||
"0B": "LiftMaster Wi-Fi GDO or Gate Operator AC",
|
||||
"0C": "Craftsman Wi-Fi GDO or Gate Operator AC",
|
||||
"0D": "MyQ Replacement Logic Board Wi-Fi GDO or Gate Operator AC",
|
||||
"0E": "Chamberlain Wi-Fi GDO DC 3/4HP",
|
||||
"0F": "LiftMaster Wi-Fi GDO DC 3/4HP",
|
||||
"20": "Chamberlain MyQ Home Bridge",
|
||||
"21": "LiftMaster MyQ Home Bridge",
|
||||
"23": "Chamberlain Smart Garage Hub",
|
||||
"24": "LiftMaster Smart Garage Hub",
|
||||
"27": "LiftMaster Wi-Fi Wall Mount opener",
|
||||
"28": "LiftMaster Commercial Wi-Fi Wall Mount operator",
|
||||
"80": "EU LiftMaster Ethernet Gateway",
|
||||
"81": "EU Chamberlain Ethernet Gateway",
|
||||
}
|
||||
|
@ -27,6 +27,8 @@ from homeassistant.helpers.event import async_call_later
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
KNOWN_MODELS,
|
||||
MANUFACTURER,
|
||||
MYQ_COORDINATOR,
|
||||
MYQ_DEVICE_STATE,
|
||||
MYQ_DEVICE_STATE_ONLINE,
|
||||
@ -181,9 +183,12 @@ class MyQDevice(CoverDevice):
|
||||
device_info = {
|
||||
"identifiers": {(DOMAIN, self._device.device_id)},
|
||||
"name": self._device.name,
|
||||
"manufacturer": "The Chamberlain Group Inc.",
|
||||
"manufacturer": MANUFACTURER,
|
||||
"sw_version": self._device.firmware_version,
|
||||
}
|
||||
model = KNOWN_MODELS.get(self._device.device_id[2:4])
|
||||
if model:
|
||||
device_info["model"] = model
|
||||
if self._device.parent_device_id:
|
||||
device_info["via_device"] = (DOMAIN, self._device.parent_device_id)
|
||||
return device_info
|
||||
|
20
tests/components/myq/test_binary_sensor.py
Normal file
20
tests/components/myq/test_binary_sensor.py
Normal file
@ -0,0 +1,20 @@
|
||||
"""The scene tests for the myq platform."""
|
||||
|
||||
from homeassistant.const import STATE_ON
|
||||
|
||||
from .util import async_init_integration
|
||||
|
||||
|
||||
async def test_create_binary_sensors(hass):
|
||||
"""Test creation of binary_sensors."""
|
||||
|
||||
await async_init_integration(hass)
|
||||
|
||||
state = hass.states.get("binary_sensor.happy_place_myq_gateway")
|
||||
assert state.state == STATE_ON
|
||||
expected_attributes = {"device_class": "connectivity"}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
50
tests/components/myq/test_cover.py
Normal file
50
tests/components/myq/test_cover.py
Normal file
@ -0,0 +1,50 @@
|
||||
"""The scene tests for the myq platform."""
|
||||
|
||||
from homeassistant.const import STATE_CLOSED
|
||||
|
||||
from .util import async_init_integration
|
||||
|
||||
|
||||
async def test_create_covers(hass):
|
||||
"""Test creation of covers."""
|
||||
|
||||
await async_init_integration(hass)
|
||||
|
||||
state = hass.states.get("cover.large_garage_door")
|
||||
assert state.state == STATE_CLOSED
|
||||
expected_attributes = {
|
||||
"device_class": "garage",
|
||||
"friendly_name": "Large Garage Door",
|
||||
"supported_features": 3,
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
||||
|
||||
state = hass.states.get("cover.small_garage_door")
|
||||
assert state.state == STATE_CLOSED
|
||||
expected_attributes = {
|
||||
"device_class": "garage",
|
||||
"friendly_name": "Small Garage Door",
|
||||
"supported_features": 3,
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
||||
|
||||
state = hass.states.get("cover.gate")
|
||||
assert state.state == STATE_CLOSED
|
||||
expected_attributes = {
|
||||
"device_class": "gate",
|
||||
"friendly_name": "Gate",
|
||||
"supported_features": 3,
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
42
tests/components/myq/util.py
Normal file
42
tests/components/myq/util.py
Normal file
@ -0,0 +1,42 @@
|
||||
"""Tests for the myq integration."""
|
||||
|
||||
import json
|
||||
|
||||
from asynctest import patch
|
||||
|
||||
from homeassistant.components.myq.const import DOMAIN
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry, load_fixture
|
||||
|
||||
|
||||
async def async_init_integration(
|
||||
hass: HomeAssistant, skip_setup: bool = False,
|
||||
) -> MockConfigEntry:
|
||||
"""Set up the myq integration in Home Assistant."""
|
||||
|
||||
devices_fixture = "myq/devices.json"
|
||||
devices_json = load_fixture(devices_fixture)
|
||||
devices_dict = json.loads(devices_json)
|
||||
|
||||
def _handle_mock_api_request(method, endpoint, **kwargs):
|
||||
if endpoint == "Login":
|
||||
return {"SecurityToken": 1234}
|
||||
elif endpoint == "My":
|
||||
return {"Account": {"Id": 1}}
|
||||
elif endpoint == "Accounts/1/Devices":
|
||||
return devices_dict
|
||||
return {}
|
||||
|
||||
with patch("pymyq.api.API.request", side_effect=_handle_mock_api_request):
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN, data={CONF_USERNAME: "mock", CONF_PASSWORD: "mock"}
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
if not skip_setup:
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return entry
|
133
tests/fixtures/myq/devices.json
vendored
Normal file
133
tests/fixtures/myq/devices.json
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
{
|
||||
"count" : 4,
|
||||
"href" : "http://api.myqdevice.com/api/v5/accounts/account_id/devices",
|
||||
"items" : [
|
||||
{
|
||||
"device_type" : "ethernetgateway",
|
||||
"created_date" : "2020-02-10T22:54:58.423",
|
||||
"href" : "http://api.myqdevice.com/api/v5/accounts/account_id/devices/gateway_serial",
|
||||
"device_family" : "gateway",
|
||||
"name" : "Happy place",
|
||||
"device_platform" : "myq",
|
||||
"state" : {
|
||||
"homekit_enabled" : false,
|
||||
"pending_bootload_abandoned" : false,
|
||||
"online" : true,
|
||||
"last_status" : "2020-03-30T02:49:46.4121303Z",
|
||||
"physical_devices" : [],
|
||||
"firmware_version" : "1.6",
|
||||
"learn_mode" : false,
|
||||
"learn" : "http://api.myqdevice.com/api/v5/accounts/account_id/devices/gateway_serial/learn",
|
||||
"homekit_capable" : false,
|
||||
"updated_date" : "2020-03-30T02:49:46.4171299Z"
|
||||
},
|
||||
"serial_number" : "gateway_serial"
|
||||
},
|
||||
{
|
||||
"serial_number" : "gate_serial",
|
||||
"state" : {
|
||||
"report_ajar" : false,
|
||||
"aux_relay_delay" : "00:00:00",
|
||||
"is_unattended_close_allowed" : true,
|
||||
"door_ajar_interval" : "00:00:00",
|
||||
"aux_relay_behavior" : "None",
|
||||
"last_status" : "2020-03-30T02:47:40.2794038Z",
|
||||
"online" : true,
|
||||
"rex_fires_door" : false,
|
||||
"close" : "http://api.myqdevice.com/api/v5/accounts/account_id/devices/gate_serial/close",
|
||||
"invalid_shutout_period" : "00:00:00",
|
||||
"invalid_credential_window" : "00:00:00",
|
||||
"use_aux_relay" : false,
|
||||
"command_channel_report_status" : false,
|
||||
"last_update" : "2020-03-28T23:07:39.5611776Z",
|
||||
"door_state" : "closed",
|
||||
"max_invalid_attempts" : 0,
|
||||
"open" : "http://api.myqdevice.com/api/v5/accounts/account_id/devices/gate_serial/open",
|
||||
"passthrough_interval" : "00:00:00",
|
||||
"control_from_browser" : false,
|
||||
"report_forced" : false,
|
||||
"is_unattended_open_allowed" : true
|
||||
},
|
||||
"parent_device_id" : "gateway_serial",
|
||||
"name" : "Gate",
|
||||
"device_platform" : "myq",
|
||||
"device_family" : "garagedoor",
|
||||
"parent_device" : "http://api.myqdevice.com/api/v5/accounts/account_id/devices/gateway_serial",
|
||||
"href" : "http://api.myqdevice.com/api/v5/accounts/account_id/devices/gate_serial",
|
||||
"device_type" : "gate",
|
||||
"created_date" : "2020-02-10T22:54:58.423"
|
||||
},
|
||||
{
|
||||
"parent_device" : "http://api.myqdevice.com/api/v5/accounts/account_id/devices/gateway_serial",
|
||||
"href" : "http://api.myqdevice.com/api/v5/accounts/account_id/devices/large_garage_serial",
|
||||
"device_type" : "wifigaragedooropener",
|
||||
"created_date" : "2020-02-10T22:55:25.863",
|
||||
"device_platform" : "myq",
|
||||
"name" : "Large Garage Door",
|
||||
"device_family" : "garagedoor",
|
||||
"serial_number" : "large_garage_serial",
|
||||
"state" : {
|
||||
"report_forced" : false,
|
||||
"is_unattended_open_allowed" : true,
|
||||
"passthrough_interval" : "00:00:00",
|
||||
"control_from_browser" : false,
|
||||
"attached_work_light_error_present" : false,
|
||||
"max_invalid_attempts" : 0,
|
||||
"open" : "http://api.myqdevice.com/api/v5/accounts/account_id/devices/large_garage_serial/open",
|
||||
"command_channel_report_status" : false,
|
||||
"last_update" : "2020-03-28T23:58:55.5906643Z",
|
||||
"door_state" : "closed",
|
||||
"invalid_shutout_period" : "00:00:00",
|
||||
"use_aux_relay" : false,
|
||||
"invalid_credential_window" : "00:00:00",
|
||||
"rex_fires_door" : false,
|
||||
"close" : "http://api.myqdevice.com/api/v5/accounts/account_id/devices/large_garage_serial/close",
|
||||
"online" : true,
|
||||
"last_status" : "2020-03-30T02:49:46.4121303Z",
|
||||
"aux_relay_behavior" : "None",
|
||||
"door_ajar_interval" : "00:00:00",
|
||||
"gdo_lock_connected" : false,
|
||||
"report_ajar" : false,
|
||||
"aux_relay_delay" : "00:00:00",
|
||||
"is_unattended_close_allowed" : true
|
||||
},
|
||||
"parent_device_id" : "gateway_serial"
|
||||
},
|
||||
{
|
||||
"serial_number" : "small_garage_serial",
|
||||
"state" : {
|
||||
"last_status" : "2020-03-30T02:48:45.7501595Z",
|
||||
"online" : true,
|
||||
"report_ajar" : false,
|
||||
"aux_relay_delay" : "00:00:00",
|
||||
"is_unattended_close_allowed" : true,
|
||||
"gdo_lock_connected" : false,
|
||||
"door_ajar_interval" : "00:00:00",
|
||||
"aux_relay_behavior" : "None",
|
||||
"attached_work_light_error_present" : false,
|
||||
"control_from_browser" : false,
|
||||
"passthrough_interval" : "00:00:00",
|
||||
"is_unattended_open_allowed" : true,
|
||||
"report_forced" : false,
|
||||
"close" : "http://api.myqdevice.com/api/v5/accounts/account_id/devices/small_garage_serial/close",
|
||||
"rex_fires_door" : false,
|
||||
"invalid_credential_window" : "00:00:00",
|
||||
"use_aux_relay" : false,
|
||||
"invalid_shutout_period" : "00:00:00",
|
||||
"door_state" : "closed",
|
||||
"last_update" : "2020-03-26T15:45:31.4713796Z",
|
||||
"command_channel_report_status" : false,
|
||||
"open" : "http://api.myqdevice.com/api/v5/accounts/account_id/devices/small_garage_serial/open",
|
||||
"max_invalid_attempts" : 0
|
||||
},
|
||||
"parent_device_id" : "gateway_serial",
|
||||
"device_platform" : "myq",
|
||||
"name" : "Small Garage Door",
|
||||
"device_family" : "garagedoor",
|
||||
"parent_device" : "http://api.myqdevice.com/api/v5/accounts/account_id/devices/gateway_serial",
|
||||
"href" : "http://api.myqdevice.com/api/v5/accounts/account_id/devices/small_garage_serial",
|
||||
"device_type" : "wifigaragedooropener",
|
||||
"created_date" : "2020-02-10T23:11:47.487"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user