Add dhcp support to iSmartGate (#50309)
parent
3b272ec54c
commit
7314247ce3
|
@ -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(
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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%]",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,6 +76,10 @@ DHCP = [
|
|||
"hostname": "guardian*",
|
||||
"macaddress": "30AEA4*"
|
||||
},
|
||||
{
|
||||
"domain": "gogogate2",
|
||||
"hostname": "ismartgate*"
|
||||
},
|
||||
{
|
||||
"domain": "hunterdouglas_powerview",
|
||||
"hostname": "hunter*",
|
||||
|
|
|
@ -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=""),
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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."""
|
||||
|
|
Loading…
Reference in New Issue