Add dhcp support to iSmartGate (#50309)

pull/50498/head
J. Nick Koston 2021-05-11 17:20:03 -05:00 committed by GitHub
parent 3b272ec54c
commit 7314247ce3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 292 additions and 135 deletions

View File

@ -6,6 +6,8 @@ from ismartgate.common import AbstractInfoResponse, ApiError
from ismartgate.const import GogoGate2ApiErrorCode, ISmartGateApiErrorCode
import voluptuous as vol
from homeassistant import data_entry_flow
from homeassistant.components.dhcp import IP_ADDRESS, MAC_ADDRESS
from homeassistant.config_entries import ConfigFlow
from homeassistant.const import (
CONF_DEVICE,
@ -17,6 +19,11 @@ from homeassistant.const import (
from .common import get_api
from .const import DEVICE_TYPE_GOGOGATE2, DEVICE_TYPE_ISMARTGATE, DOMAIN
DEVICE_NAMES = {
DEVICE_TYPE_GOGOGATE2: "Gogogate2",
DEVICE_TYPE_ISMARTGATE: "ismartgate",
}
class Gogogate2FlowHandler(ConfigFlow, domain=DOMAIN):
"""Gogogate2 config flow."""
@ -31,13 +38,25 @@ class Gogogate2FlowHandler(ConfigFlow, domain=DOMAIN):
async def async_step_homekit(self, discovery_info):
"""Handle homekit discovery."""
await self.async_set_unique_id(discovery_info["properties"]["id"])
self._abort_if_unique_id_configured({CONF_IP_ADDRESS: discovery_info["host"]})
return await self._async_discovery_handler(discovery_info["host"])
ip_address = discovery_info["host"]
async def async_step_dhcp(self, discovery_info):
"""Handle dhcp discovery."""
await self.async_set_unique_id(discovery_info[MAC_ADDRESS])
return await self._async_discovery_handler(discovery_info[IP_ADDRESS])
async def _async_discovery_handler(self, ip_address):
"""Start the user flow from any discovery."""
self.context[CONF_IP_ADDRESS] = ip_address
self._abort_if_unique_id_configured({CONF_IP_ADDRESS: ip_address})
self._async_abort_entries_match({CONF_IP_ADDRESS: ip_address})
self._ip_address = ip_address
for progress in self._async_in_progress():
if progress.get("context", {}).get(CONF_IP_ADDRESS) == self._ip_address:
raise data_entry_flow.AbortFlow("already_in_progress")
self._device_type = DEVICE_TYPE_ISMARTGATE
return await self.async_step_user()
@ -83,6 +102,11 @@ class Gogogate2FlowHandler(ConfigFlow, domain=DOMAIN):
except Exception: # pylint: disable=broad-except
errors["base"] = "cannot_connect"
if self._ip_address and self._device_type:
self.context["title_placeholders"] = {
CONF_DEVICE: DEVICE_NAMES[self._device_type],
CONF_IP_ADDRESS: self._ip_address,
}
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(

View File

@ -1,6 +1,6 @@
{
"domain": "gogogate2",
"name": "Gogogate2 and iSmartGate",
"name": "Gogogate2 and ismartgate",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/gogogate2",
"requirements": ["ismartgate==4.0.0"],
@ -8,5 +8,10 @@
"homekit": {
"models": ["iSmartGate"]
},
"dhcp": [
{
"hostname": "ismartgate*"
}
],
"iot_class": "local_polling"
}

View File

@ -1,5 +1,6 @@
{
"config": {
"flow_title": "{device} ({ip_address})",
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
@ -9,7 +10,7 @@
},
"step": {
"user": {
"title": "Setup GogoGate2 or iSmartGate",
"title": "Setup Gogogate2 or ismartgate",
"description": "Provide requisite information below.",
"data": {
"ip_address": "[%key:common::config_flow::data::ip%]",

View File

@ -7,6 +7,7 @@
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication"
},
"flow_title": "{device} ({ip_address})",
"step": {
"user": {
"data": {
@ -15,7 +16,7 @@
"username": "Username"
},
"description": "Provide requisite information below.",
"title": "Setup GogoGate2 or iSmartGate"
"title": "Setup Gogogate2 or ismartgate"
}
}
}

View File

@ -76,6 +76,10 @@ DHCP = [
"hostname": "guardian*",
"macaddress": "30AEA4*"
},
{
"domain": "gogogate2",
"hostname": "ismartgate*"
},
{
"domain": "hunterdouglas_powerview",
"hostname": "hunter*",

View File

@ -1 +1,139 @@
"""Tests for the GogoGate2 component."""
from ismartgate.common import (
DoorMode,
DoorStatus,
GogoGate2Door,
GogoGate2InfoResponse,
ISmartGateDoor,
ISmartGateInfoResponse,
Network,
Outputs,
Wifi,
)
def _mocked_gogogate_open_door_response():
return GogoGate2InfoResponse(
user="user1",
gogogatename="gogogatename0",
model="gogogate2",
apiversion="",
remoteaccessenabled=False,
remoteaccess="abc123.blah.blah",
firmwareversion="222",
apicode="",
door1=GogoGate2Door(
door_id=1,
permission=True,
name="Door1",
gate=False,
mode=DoorMode.GARAGE,
status=DoorStatus.OPENED,
sensor=True,
sensorid=None,
camera=False,
events=2,
temperature=None,
voltage=40,
),
door2=GogoGate2Door(
door_id=2,
permission=True,
name=None,
gate=True,
mode=DoorMode.GARAGE,
status=DoorStatus.UNDEFINED,
sensor=True,
sensorid=None,
camera=False,
events=0,
temperature=None,
voltage=40,
),
door3=GogoGate2Door(
door_id=3,
permission=True,
name=None,
gate=False,
mode=DoorMode.GARAGE,
status=DoorStatus.UNDEFINED,
sensor=True,
sensorid=None,
camera=False,
events=0,
temperature=None,
voltage=40,
),
outputs=Outputs(output1=True, output2=False, output3=True),
network=Network(ip=""),
wifi=Wifi(SSID="", linkquality="", signal=""),
)
def _mocked_ismartgate_closed_door_response():
return ISmartGateInfoResponse(
user="user1",
ismartgatename="ismartgatename0",
model="ismartgatePRO",
apiversion="",
remoteaccessenabled=False,
remoteaccess="abc321.blah.blah",
firmwareversion="555",
pin=123,
lang="en",
newfirmware=False,
door1=ISmartGateDoor(
door_id=1,
permission=True,
name="Door1",
gate=False,
mode=DoorMode.GARAGE,
status=DoorStatus.CLOSED,
sensor=True,
sensorid=None,
camera=False,
events=2,
temperature=None,
enabled=True,
apicode="apicode0",
customimage=False,
voltage=40,
),
door2=ISmartGateDoor(
door_id=2,
permission=True,
name="Door2",
gate=True,
mode=DoorMode.GARAGE,
status=DoorStatus.CLOSED,
sensor=True,
sensorid=None,
camera=False,
events=2,
temperature=None,
enabled=True,
apicode="apicode0",
customimage=False,
voltage=40,
),
door3=ISmartGateDoor(
door_id=3,
permission=True,
name=None,
gate=False,
mode=DoorMode.GARAGE,
status=DoorStatus.UNDEFINED,
sensor=True,
sensorid=None,
camera=False,
events=0,
temperature=None,
enabled=True,
apicode="apicode0",
customimage=False,
voltage=40,
),
network=Network(ip=""),
wifi=Wifi(SSID="", linkquality="", signal=""),
)

View File

@ -1,7 +1,7 @@
"""Tests for the GogoGate2 component."""
from unittest.mock import MagicMock, patch
from ismartgate import GogoGate2Api
from ismartgate import GogoGate2Api, ISmartGateApi
from ismartgate.common import ApiError
from ismartgate.const import GogoGate2ApiErrorCode
@ -19,7 +19,13 @@ from homeassistant.const import (
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_FORM
from homeassistant.data_entry_flow import (
RESULT_TYPE_ABORT,
RESULT_TYPE_CREATE_ENTRY,
RESULT_TYPE_FORM,
)
from . import _mocked_ismartgate_closed_door_response
from tests.common import MockConfigEntry
@ -75,6 +81,24 @@ async def test_auth_fail(
assert result["type"] == RESULT_TYPE_FORM
assert result["errors"] == {"base": "cannot_connect"}
api.reset_mock()
api.async_info.side_effect = ApiError(0, "blah")
result = await hass.config_entries.flow.async_init(
"gogogate2", context={"source": SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_DEVICE: DEVICE_TYPE_GOGOGATE2,
CONF_IP_ADDRESS: "127.0.0.2",
CONF_USERNAME: "user0",
CONF_PASSWORD: "password0",
},
)
assert result
assert result["type"] == RESULT_TYPE_FORM
assert result["errors"] == {"base": "cannot_connect"}
async def test_form_homekit_unique_id_already_setup(hass):
"""Test that we abort from homekit if gogogate2 is already setup."""
@ -145,3 +169,86 @@ async def test_form_homekit_ip_address(hass):
CONF_PASSWORD: "password",
CONF_USERNAME: "username",
}
@patch("homeassistant.components.gogogate2.async_setup_entry", return_value=True)
@patch("homeassistant.components.gogogate2.common.ISmartGateApi")
async def test_discovered_dhcp(
ismartgateapi_mock, async_setup_entry_mock, hass
) -> None:
"""Test we get the form with homekit and abort for dhcp source when we get both."""
api: ISmartGateApi = MagicMock(spec=ISmartGateApi)
ismartgateapi_mock.return_value = api
api.reset_mock()
await setup.async_setup_component(hass, "persistent_notification", {})
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data={"ip": "1.2.3.4", "macaddress": MOCK_MAC_ADDR},
)
assert result["type"] == RESULT_TYPE_FORM
assert result["errors"] == {}
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_DEVICE: DEVICE_TYPE_ISMARTGATE,
CONF_IP_ADDRESS: "1.2.3.4",
CONF_USERNAME: "user0",
CONF_PASSWORD: "password0",
},
)
assert result2
assert result2["type"] == RESULT_TYPE_FORM
assert result2["errors"] == {"base": "cannot_connect"}
api.reset_mock()
closed_door_response = _mocked_ismartgate_closed_door_response()
api.async_info.return_value = closed_door_response
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
user_input={
CONF_DEVICE: DEVICE_TYPE_ISMARTGATE,
CONF_IP_ADDRESS: "1.2.3.4",
CONF_USERNAME: "user0",
CONF_PASSWORD: "password0",
},
)
assert result3
assert result3["type"] == RESULT_TYPE_CREATE_ENTRY
assert result3["data"] == {
"device": "ismartgate",
"ip_address": "1.2.3.4",
"password": "password0",
"username": "user0",
}
async def test_discovered_by_homekit_and_dhcp(hass):
"""Test we get the form with homekit and abort for dhcp source when we get both."""
await setup.async_setup_component(hass, "persistent_notification", {})
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_HOMEKIT},
data={"host": "1.2.3.4", "properties": {"id": MOCK_MAC_ADDR}},
)
assert result["type"] == RESULT_TYPE_FORM
assert result["errors"] == {}
result2 = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data={"ip": "1.2.3.4", "macaddress": MOCK_MAC_ADDR},
)
assert result2["type"] == RESULT_TYPE_ABORT
assert result2["reason"] == "already_in_progress"
result3 = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data={"ip": "1.2.3.4", "macaddress": "00:00:00:00:00:00"},
)
assert result3["type"] == RESULT_TYPE_ABORT
assert result3["reason"] == "already_in_progress"

View File

@ -9,8 +9,6 @@ from ismartgate.common import (
GogoGate2ActivateResponse,
GogoGate2Door,
GogoGate2InfoResponse,
ISmartGateDoor,
ISmartGateInfoResponse,
Network,
Outputs,
TransitionDoorStatus,
@ -47,135 +45,14 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.util.dt import utcnow
from . import (
_mocked_gogogate_open_door_response,
_mocked_ismartgate_closed_door_response,
)
from tests.common import MockConfigEntry, async_fire_time_changed, mock_device_registry
def _mocked_gogogate_open_door_response():
return GogoGate2InfoResponse(
user="user1",
gogogatename="gogogatename0",
model="gogogate2",
apiversion="",
remoteaccessenabled=False,
remoteaccess="abc123.blah.blah",
firmwareversion="222",
apicode="",
door1=GogoGate2Door(
door_id=1,
permission=True,
name="Door1",
gate=False,
mode=DoorMode.GARAGE,
status=DoorStatus.OPENED,
sensor=True,
sensorid=None,
camera=False,
events=2,
temperature=None,
voltage=40,
),
door2=GogoGate2Door(
door_id=2,
permission=True,
name=None,
gate=True,
mode=DoorMode.GARAGE,
status=DoorStatus.UNDEFINED,
sensor=True,
sensorid=None,
camera=False,
events=0,
temperature=None,
voltage=40,
),
door3=GogoGate2Door(
door_id=3,
permission=True,
name=None,
gate=False,
mode=DoorMode.GARAGE,
status=DoorStatus.UNDEFINED,
sensor=True,
sensorid=None,
camera=False,
events=0,
temperature=None,
voltage=40,
),
outputs=Outputs(output1=True, output2=False, output3=True),
network=Network(ip=""),
wifi=Wifi(SSID="", linkquality="", signal=""),
)
def _mocked_ismartgate_closed_door_response():
return ISmartGateInfoResponse(
user="user1",
ismartgatename="ismartgatename0",
model="ismartgatePRO",
apiversion="",
remoteaccessenabled=False,
remoteaccess="abc321.blah.blah",
firmwareversion="555",
pin=123,
lang="en",
newfirmware=False,
door1=ISmartGateDoor(
door_id=1,
permission=True,
name="Door1",
gate=False,
mode=DoorMode.GARAGE,
status=DoorStatus.CLOSED,
sensor=True,
sensorid=None,
camera=False,
events=2,
temperature=None,
enabled=True,
apicode="apicode0",
customimage=False,
voltage=40,
),
door2=ISmartGateDoor(
door_id=2,
permission=True,
name="Door2",
gate=True,
mode=DoorMode.GARAGE,
status=DoorStatus.CLOSED,
sensor=True,
sensorid=None,
camera=False,
events=2,
temperature=None,
enabled=True,
apicode="apicode0",
customimage=False,
voltage=40,
),
door3=ISmartGateDoor(
door_id=3,
permission=True,
name=None,
gate=False,
mode=DoorMode.GARAGE,
status=DoorStatus.UNDEFINED,
sensor=True,
sensorid=None,
camera=False,
events=0,
temperature=None,
enabled=True,
apicode="apicode0",
customimage=False,
voltage=40,
),
network=Network(ip=""),
wifi=Wifi(SSID="", linkquality="", signal=""),
)
@patch("homeassistant.components.gogogate2.common.GogoGate2Api")
async def test_open_close_update(gogogate2api_mock, hass: HomeAssistant) -> None:
"""Test open and close and data update."""