mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Add dhcp ip update support to onvif (#91474)
* Add dhcp ip update support to onvif If we know the mac address of the camera we can update the config entry when the ip changes * fix lookup * coverage * remove unreachable * remove unreachable * remove unreachable
This commit is contained in:
parent
d16e1b4ed0
commit
7f7909e0d1
@ -13,6 +13,7 @@ from wsdiscovery.service import Service
|
|||||||
from zeep.exceptions import Fault
|
from zeep.exceptions import Fault
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components import dhcp
|
||||||
from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS
|
from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS
|
||||||
from homeassistant.components.stream import (
|
from homeassistant.components.stream import (
|
||||||
CONF_RTSP_TRANSPORT,
|
CONF_RTSP_TRANSPORT,
|
||||||
@ -27,6 +28,8 @@ from homeassistant.const import (
|
|||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
|
||||||
from .const import CONF_DEVICE_ID, DEFAULT_ARGUMENTS, DEFAULT_PORT, DOMAIN, LOGGER
|
from .const import CONF_DEVICE_ID, DEFAULT_ARGUMENTS, DEFAULT_PORT, DOMAIN, LOGGER
|
||||||
from .device import get_device
|
from .device import get_device
|
||||||
@ -101,6 +104,30 @@ class OnvifFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
data_schema=vol.Schema({vol.Required("auto", default=True): bool}),
|
data_schema=vol.Schema({vol.Required("auto", default=True): bool}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult:
|
||||||
|
"""Handle dhcp discovery."""
|
||||||
|
hass = self.hass
|
||||||
|
mac = discovery_info.macaddress
|
||||||
|
registry = dr.async_get(self.hass)
|
||||||
|
if not (
|
||||||
|
device := registry.async_get_device(
|
||||||
|
identifiers=set(), connections={(dr.CONNECTION_NETWORK_MAC, mac)}
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return self.async_abort(reason="no_devices_found")
|
||||||
|
for entry_id in device.config_entries:
|
||||||
|
if (
|
||||||
|
not (entry := hass.config_entries.async_get_entry(entry_id))
|
||||||
|
or entry.domain != DOMAIN
|
||||||
|
or entry.state is config_entries.ConfigEntryState.LOADED
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
if hass.config_entries.async_update_entry(
|
||||||
|
entry, data=entry.data | {CONF_HOST: discovery_info.ip}
|
||||||
|
):
|
||||||
|
hass.async_create_task(self.hass.config_entries.async_reload(entry_id))
|
||||||
|
return self.async_abort(reason="already_configured")
|
||||||
|
|
||||||
async def async_step_device(self, user_input=None):
|
async def async_step_device(self, user_input=None):
|
||||||
"""Handle WS-Discovery.
|
"""Handle WS-Discovery.
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
"codeowners": ["@hunterjm"],
|
"codeowners": ["@hunterjm"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"dependencies": ["ffmpeg"],
|
"dependencies": ["ffmpeg"],
|
||||||
|
"dhcp": [{ "registered_devices": true }],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/onvif",
|
"documentation": "https://www.home-assistant.io/integrations/onvif",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["onvif", "wsdiscovery", "zeep"],
|
"loggers": ["onvif", "wsdiscovery", "zeep"],
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
|
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
|
||||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||||
"onvif_error": "Error setting up ONVIF device. Check logs for more information.",
|
"onvif_error": "Error setting up ONVIF device. Check logs for more information.",
|
||||||
"no_h264": "There were no H264 streams available. Check the profile configuration on your device.",
|
"no_h264": "There were no H264 streams available. Check the profile configuration on your device.",
|
||||||
|
@ -339,6 +339,10 @@ DHCP: list[dict[str, str | bool]] = [
|
|||||||
"hostname": "kohlergen*",
|
"hostname": "kohlergen*",
|
||||||
"macaddress": "00146F*",
|
"macaddress": "00146F*",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"domain": "onvif",
|
||||||
|
"registered_devices": True,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"domain": "overkiz",
|
"domain": "overkiz",
|
||||||
"hostname": "gateway*",
|
"hostname": "gateway*",
|
||||||
|
@ -6,7 +6,13 @@ from zeep.exceptions import Fault
|
|||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.onvif import config_flow
|
from homeassistant.components.onvif import config_flow
|
||||||
from homeassistant.components.onvif.const import CONF_SNAPSHOT_AUTH
|
from homeassistant.components.onvif.const import CONF_SNAPSHOT_AUTH
|
||||||
from homeassistant.components.onvif.models import Capabilities, DeviceInfo, Profile
|
from homeassistant.components.onvif.models import (
|
||||||
|
Capabilities,
|
||||||
|
DeviceInfo,
|
||||||
|
Profile,
|
||||||
|
Resolution,
|
||||||
|
Video,
|
||||||
|
)
|
||||||
from homeassistant.const import HTTP_DIGEST_AUTHENTICATION
|
from homeassistant.const import HTTP_DIGEST_AUTHENTICATION
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
@ -100,7 +106,7 @@ def setup_mock_device(mock_device):
|
|||||||
index=0,
|
index=0,
|
||||||
token="dummy",
|
token="dummy",
|
||||||
name="profile1",
|
name="profile1",
|
||||||
video=None,
|
video=Video("any", Resolution(640, 480)),
|
||||||
ptz=None,
|
ptz=None,
|
||||||
video_source_token=None,
|
video_source_token=None,
|
||||||
)
|
)
|
||||||
@ -120,7 +126,7 @@ async def setup_onvif_integration(
|
|||||||
unique_id=MAC,
|
unique_id=MAC,
|
||||||
entry_id="1",
|
entry_id="1",
|
||||||
source=config_entries.SOURCE_USER,
|
source=config_entries.SOURCE_USER,
|
||||||
):
|
) -> tuple[MockConfigEntry, MagicMock, MagicMock]:
|
||||||
"""Create an ONVIF config entry."""
|
"""Create an ONVIF config entry."""
|
||||||
if not config:
|
if not config:
|
||||||
config = {
|
config = {
|
||||||
|
@ -2,8 +2,13 @@
|
|||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from homeassistant import config_entries, data_entry_flow
|
from homeassistant import config_entries, data_entry_flow
|
||||||
from homeassistant.components.onvif import config_flow
|
from homeassistant.components import dhcp
|
||||||
|
from homeassistant.components.onvif import DOMAIN, config_flow
|
||||||
|
from homeassistant.config_entries import SOURCE_DHCP
|
||||||
|
from homeassistant.const import CONF_HOST
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
HOST,
|
HOST,
|
||||||
@ -34,6 +39,16 @@ DISCOVERY = [
|
|||||||
"MAC": "ee:dd:cc:bb:aa",
|
"MAC": "ee:dd:cc:bb:aa",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
DHCP_DISCOVERY = dhcp.DhcpServiceInfo(
|
||||||
|
hostname="any",
|
||||||
|
ip="5.6.7.8",
|
||||||
|
macaddress=MAC,
|
||||||
|
)
|
||||||
|
DHCP_DISCOVERY_SAME_IP = dhcp.DhcpServiceInfo(
|
||||||
|
hostname="any",
|
||||||
|
ip="1.2.3.4",
|
||||||
|
macaddress=MAC,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup_mock_discovery(
|
def setup_mock_discovery(
|
||||||
@ -339,3 +354,88 @@ async def test_option_flow(hass: HomeAssistant) -> None:
|
|||||||
config_flow.CONF_RTSP_TRANSPORT: list(config_flow.RTSP_TRANSPORTS)[1],
|
config_flow.CONF_RTSP_TRANSPORT: list(config_flow.RTSP_TRANSPORTS)[1],
|
||||||
config_flow.CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True,
|
config_flow.CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_discovered_by_dhcp_updates_host(hass: HomeAssistant) -> None:
|
||||||
|
"""Test dhcp updates existing host."""
|
||||||
|
config_entry, _camera, device = await setup_onvif_integration(hass)
|
||||||
|
device.profiles = device.async_get_profiles()
|
||||||
|
registry = dr.async_get(hass)
|
||||||
|
devices = dr.async_entries_for_config_entry(registry, config_entry.entry_id)
|
||||||
|
assert len(devices) == 1
|
||||||
|
device = devices[0]
|
||||||
|
assert device.model == "TestModel"
|
||||||
|
assert device.connections == {(dr.CONNECTION_NETWORK_MAC, MAC)}
|
||||||
|
assert config_entry.data[CONF_HOST] == "1.2.3.4"
|
||||||
|
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_DHCP}, data=DHCP_DISCOVERY
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
assert config_entry.data[CONF_HOST] == DHCP_DISCOVERY.ip
|
||||||
|
|
||||||
|
|
||||||
|
async def test_discovered_by_dhcp_does_nothing_if_host_is_the_same(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
) -> None:
|
||||||
|
"""Test dhcp update does nothing if host is the same."""
|
||||||
|
config_entry, _camera, device = await setup_onvif_integration(hass)
|
||||||
|
device.profiles = device.async_get_profiles()
|
||||||
|
registry = dr.async_get(hass)
|
||||||
|
devices = dr.async_entries_for_config_entry(registry, config_entry.entry_id)
|
||||||
|
assert len(devices) == 1
|
||||||
|
device = devices[0]
|
||||||
|
assert device.model == "TestModel"
|
||||||
|
assert device.connections == {(dr.CONNECTION_NETWORK_MAC, MAC)}
|
||||||
|
assert config_entry.data[CONF_HOST] == DHCP_DISCOVERY_SAME_IP.ip
|
||||||
|
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_DHCP}, data=DHCP_DISCOVERY_SAME_IP
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
assert config_entry.data[CONF_HOST] == DHCP_DISCOVERY_SAME_IP.ip
|
||||||
|
|
||||||
|
|
||||||
|
async def test_discovered_by_dhcp_does_not_update_if_already_loaded(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
) -> None:
|
||||||
|
"""Test dhcp does not update existing host if its already loaded."""
|
||||||
|
config_entry, _camera, device = await setup_onvif_integration(hass)
|
||||||
|
device.profiles = device.async_get_profiles()
|
||||||
|
registry = dr.async_get(hass)
|
||||||
|
devices = dr.async_entries_for_config_entry(registry, config_entry.entry_id)
|
||||||
|
assert len(devices) == 1
|
||||||
|
device = devices[0]
|
||||||
|
assert device.model == "TestModel"
|
||||||
|
assert device.connections == {(dr.CONNECTION_NETWORK_MAC, MAC)}
|
||||||
|
assert config_entry.data[CONF_HOST] == "1.2.3.4"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_DHCP}, data=DHCP_DISCOVERY
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
assert config_entry.data[CONF_HOST] != DHCP_DISCOVERY.ip
|
||||||
|
|
||||||
|
|
||||||
|
async def test_discovered_by_dhcp_does_not_update_if_no_matching_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
) -> None:
|
||||||
|
"""Test dhcp does not update existing host if there are no matching entries."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_DHCP}, data=DHCP_DISCOVERY
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "no_devices_found"
|
||||||
|
@ -63,7 +63,10 @@ async def test_diagnostics(
|
|||||||
"index": 0,
|
"index": 0,
|
||||||
"token": "dummy",
|
"token": "dummy",
|
||||||
"name": "profile1",
|
"name": "profile1",
|
||||||
"video": None,
|
"video": {
|
||||||
|
"encoding": "any",
|
||||||
|
"resolution": {"width": 640, "height": 480},
|
||||||
|
},
|
||||||
"ptz": None,
|
"ptz": None,
|
||||||
"video_source_token": None,
|
"video_source_token": None,
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user