Add support for Reolink Floodlight PoE/WiFi (#146778)

* Add support for Floodlight PoE/WiFi

* Adjust test

* Add test
This commit is contained in:
starkillerOG 2025-06-23 20:43:01 +02:00 committed by GitHub
parent 3806e5b65c
commit 2862f76fca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 86 additions and 4 deletions

View File

@ -32,6 +32,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import ( from .const import (
BATTERY_PASSIVE_WAKE_UPDATE_INTERVAL, BATTERY_PASSIVE_WAKE_UPDATE_INTERVAL,
CONF_BC_ONLY,
CONF_BC_PORT, CONF_BC_PORT,
CONF_SUPPORTS_PRIVACY_MODE, CONF_SUPPORTS_PRIVACY_MODE,
CONF_USE_HTTPS, CONF_USE_HTTPS,
@ -107,6 +108,7 @@ async def async_setup_entry(
or host.api.supported(None, "privacy_mode") or host.api.supported(None, "privacy_mode")
!= config_entry.data.get(CONF_SUPPORTS_PRIVACY_MODE) != config_entry.data.get(CONF_SUPPORTS_PRIVACY_MODE)
or host.api.baichuan.port != config_entry.data.get(CONF_BC_PORT) or host.api.baichuan.port != config_entry.data.get(CONF_BC_PORT)
or host.api.baichuan_only != config_entry.data.get(CONF_BC_ONLY)
): ):
if host.api.port != config_entry.data[CONF_PORT]: if host.api.port != config_entry.data[CONF_PORT]:
_LOGGER.warning( _LOGGER.warning(
@ -130,6 +132,7 @@ async def async_setup_entry(
CONF_PORT: host.api.port, CONF_PORT: host.api.port,
CONF_USE_HTTPS: host.api.use_https, CONF_USE_HTTPS: host.api.use_https,
CONF_BC_PORT: host.api.baichuan.port, CONF_BC_PORT: host.api.baichuan.port,
CONF_BC_ONLY: host.api.baichuan_only,
CONF_SUPPORTS_PRIVACY_MODE: host.api.supported(None, "privacy_mode"), CONF_SUPPORTS_PRIVACY_MODE: host.api.supported(None, "privacy_mode"),
} }
hass.config_entries.async_update_entry(config_entry, data=data) hass.config_entries.async_update_entry(config_entry, data=data)

View File

@ -38,7 +38,13 @@ from homeassistant.helpers import config_validation as cv, selector
from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from .const import CONF_BC_PORT, CONF_SUPPORTS_PRIVACY_MODE, CONF_USE_HTTPS, DOMAIN from .const import (
CONF_BC_ONLY,
CONF_BC_PORT,
CONF_SUPPORTS_PRIVACY_MODE,
CONF_USE_HTTPS,
DOMAIN,
)
from .exceptions import ( from .exceptions import (
PasswordIncompatible, PasswordIncompatible,
ReolinkException, ReolinkException,
@ -296,6 +302,7 @@ class ReolinkFlowHandler(ConfigFlow, domain=DOMAIN):
user_input[CONF_PORT] = host.api.port user_input[CONF_PORT] = host.api.port
user_input[CONF_USE_HTTPS] = host.api.use_https user_input[CONF_USE_HTTPS] = host.api.use_https
user_input[CONF_BC_PORT] = host.api.baichuan.port user_input[CONF_BC_PORT] = host.api.baichuan.port
user_input[CONF_BC_ONLY] = host.api.baichuan_only
user_input[CONF_SUPPORTS_PRIVACY_MODE] = host.api.supported( user_input[CONF_SUPPORTS_PRIVACY_MODE] = host.api.supported(
None, "privacy_mode" None, "privacy_mode"
) )

View File

@ -4,6 +4,7 @@ DOMAIN = "reolink"
CONF_USE_HTTPS = "use_https" CONF_USE_HTTPS = "use_https"
CONF_BC_PORT = "baichuan_port" CONF_BC_PORT = "baichuan_port"
CONF_BC_ONLY = "baichuan_only"
CONF_SUPPORTS_PRIVACY_MODE = "privacy_mode_supported" CONF_SUPPORTS_PRIVACY_MODE = "privacy_mode_supported"
# Conserve battery by not waking the battery cameras each minute during normal update # Conserve battery by not waking the battery cameras each minute during normal update

View File

@ -38,6 +38,7 @@ from .const import (
BATTERY_ALL_WAKE_UPDATE_INTERVAL, BATTERY_ALL_WAKE_UPDATE_INTERVAL,
BATTERY_PASSIVE_WAKE_UPDATE_INTERVAL, BATTERY_PASSIVE_WAKE_UPDATE_INTERVAL,
BATTERY_WAKE_UPDATE_INTERVAL, BATTERY_WAKE_UPDATE_INTERVAL,
CONF_BC_ONLY,
CONF_BC_PORT, CONF_BC_PORT,
CONF_SUPPORTS_PRIVACY_MODE, CONF_SUPPORTS_PRIVACY_MODE,
CONF_USE_HTTPS, CONF_USE_HTTPS,
@ -97,6 +98,7 @@ class ReolinkHost:
timeout=DEFAULT_TIMEOUT, timeout=DEFAULT_TIMEOUT,
aiohttp_get_session_callback=get_aiohttp_session, aiohttp_get_session_callback=get_aiohttp_session,
bc_port=config.get(CONF_BC_PORT, DEFAULT_BC_PORT), bc_port=config.get(CONF_BC_PORT, DEFAULT_BC_PORT),
bc_only=config.get(CONF_BC_ONLY, False),
) )
self.last_wake: defaultdict[int, float] = defaultdict(float) self.last_wake: defaultdict[int, float] = defaultdict(float)
@ -220,19 +222,27 @@ class ReolinkHost:
enable_onvif = None enable_onvif = None
enable_rtmp = None enable_rtmp = None
if not self._api.rtsp_enabled: if not self._api.rtsp_enabled and not self._api.baichuan_only:
_LOGGER.debug( _LOGGER.debug(
"RTSP is disabled on %s, trying to enable it", self._api.nvr_name "RTSP is disabled on %s, trying to enable it", self._api.nvr_name
) )
enable_rtsp = True enable_rtsp = True
if not self._api.onvif_enabled and onvif_supported: if (
not self._api.onvif_enabled
and onvif_supported
and not self._api.baichuan_only
):
_LOGGER.debug( _LOGGER.debug(
"ONVIF is disabled on %s, trying to enable it", self._api.nvr_name "ONVIF is disabled on %s, trying to enable it", self._api.nvr_name
) )
enable_onvif = True enable_onvif = True
if not self._api.rtmp_enabled and self._api.protocol == "rtmp": if (
not self._api.rtmp_enabled
and self._api.protocol == "rtmp"
and not self._api.baichuan_only
):
_LOGGER.debug( _LOGGER.debug(
"RTMP is disabled on %s, trying to enable it", self._api.nvr_name "RTMP is disabled on %s, trying to enable it", self._api.nvr_name
) )

View File

@ -10,6 +10,7 @@ from reolink_aio.exceptions import ReolinkError
from homeassistant.components.reolink.config_flow import DEFAULT_PROTOCOL from homeassistant.components.reolink.config_flow import DEFAULT_PROTOCOL
from homeassistant.components.reolink.const import ( from homeassistant.components.reolink.const import (
CONF_BC_ONLY,
CONF_BC_PORT, CONF_BC_PORT,
CONF_SUPPORTS_PRIVACY_MODE, CONF_SUPPORTS_PRIVACY_MODE,
CONF_USE_HTTPS, CONF_USE_HTTPS,
@ -219,6 +220,7 @@ def config_entry(hass: HomeAssistant) -> MockConfigEntry:
CONF_USE_HTTPS: TEST_USE_HTTPS, CONF_USE_HTTPS: TEST_USE_HTTPS,
CONF_SUPPORTS_PRIVACY_MODE: TEST_PRIVACY, CONF_SUPPORTS_PRIVACY_MODE: TEST_PRIVACY,
CONF_BC_PORT: TEST_BC_PORT, CONF_BC_PORT: TEST_BC_PORT,
CONF_BC_ONLY: False,
}, },
options={ options={
CONF_PROTOCOL: DEFAULT_PROTOCOL, CONF_PROTOCOL: DEFAULT_PROTOCOL,

View File

@ -19,6 +19,7 @@ from homeassistant import config_entries
from homeassistant.components.reolink import DEVICE_UPDATE_INTERVAL from homeassistant.components.reolink import DEVICE_UPDATE_INTERVAL
from homeassistant.components.reolink.config_flow import DEFAULT_PROTOCOL from homeassistant.components.reolink.config_flow import DEFAULT_PROTOCOL
from homeassistant.components.reolink.const import ( from homeassistant.components.reolink.const import (
CONF_BC_ONLY,
CONF_BC_PORT, CONF_BC_PORT,
CONF_SUPPORTS_PRIVACY_MODE, CONF_SUPPORTS_PRIVACY_MODE,
CONF_USE_HTTPS, CONF_USE_HTTPS,
@ -91,6 +92,7 @@ async def test_config_flow_manual_success(
CONF_USE_HTTPS: TEST_USE_HTTPS, CONF_USE_HTTPS: TEST_USE_HTTPS,
CONF_SUPPORTS_PRIVACY_MODE: TEST_PRIVACY, CONF_SUPPORTS_PRIVACY_MODE: TEST_PRIVACY,
CONF_BC_PORT: TEST_BC_PORT, CONF_BC_PORT: TEST_BC_PORT,
CONF_BC_ONLY: False,
} }
assert result["options"] == { assert result["options"] == {
CONF_PROTOCOL: DEFAULT_PROTOCOL, CONF_PROTOCOL: DEFAULT_PROTOCOL,
@ -144,6 +146,7 @@ async def test_config_flow_privacy_success(
CONF_USE_HTTPS: TEST_USE_HTTPS, CONF_USE_HTTPS: TEST_USE_HTTPS,
CONF_SUPPORTS_PRIVACY_MODE: TEST_PRIVACY, CONF_SUPPORTS_PRIVACY_MODE: TEST_PRIVACY,
CONF_BC_PORT: TEST_BC_PORT, CONF_BC_PORT: TEST_BC_PORT,
CONF_BC_ONLY: False,
} }
assert result["options"] == { assert result["options"] == {
CONF_PROTOCOL: DEFAULT_PROTOCOL, CONF_PROTOCOL: DEFAULT_PROTOCOL,
@ -153,6 +156,49 @@ async def test_config_flow_privacy_success(
reolink_connect.baichuan.privacy_mode.return_value = False reolink_connect.baichuan.privacy_mode.return_value = False
async def test_config_flow_baichuan_only(
hass: HomeAssistant, reolink_connect: MagicMock, mock_setup_entry: MagicMock
) -> None:
"""Successful flow manually initialized by the user for baichuan only device."""
reolink_connect.baichuan_only = True
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: TEST_USERNAME,
CONF_PASSWORD: TEST_PASSWORD,
CONF_HOST: TEST_HOST,
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == TEST_NVR_NAME
assert result["data"] == {
CONF_HOST: TEST_HOST,
CONF_USERNAME: TEST_USERNAME,
CONF_PASSWORD: TEST_PASSWORD,
CONF_PORT: TEST_PORT,
CONF_USE_HTTPS: TEST_USE_HTTPS,
CONF_SUPPORTS_PRIVACY_MODE: TEST_PRIVACY,
CONF_BC_PORT: TEST_BC_PORT,
CONF_BC_ONLY: True,
}
assert result["options"] == {
CONF_PROTOCOL: DEFAULT_PROTOCOL,
}
assert result["result"].unique_id == TEST_MAC
reolink_connect.baichuan_only = False
async def test_config_flow_errors( async def test_config_flow_errors(
hass: HomeAssistant, reolink_connect: MagicMock, mock_setup_entry: MagicMock hass: HomeAssistant, reolink_connect: MagicMock, mock_setup_entry: MagicMock
) -> None: ) -> None:
@ -308,6 +354,7 @@ async def test_config_flow_errors(
CONF_USE_HTTPS: TEST_USE_HTTPS, CONF_USE_HTTPS: TEST_USE_HTTPS,
CONF_SUPPORTS_PRIVACY_MODE: TEST_PRIVACY, CONF_SUPPORTS_PRIVACY_MODE: TEST_PRIVACY,
CONF_BC_PORT: TEST_BC_PORT, CONF_BC_PORT: TEST_BC_PORT,
CONF_BC_ONLY: False,
} }
assert result["options"] == { assert result["options"] == {
CONF_PROTOCOL: DEFAULT_PROTOCOL, CONF_PROTOCOL: DEFAULT_PROTOCOL,
@ -329,6 +376,7 @@ async def test_options_flow(hass: HomeAssistant, mock_setup_entry: MagicMock) ->
CONF_PORT: TEST_PORT, CONF_PORT: TEST_PORT,
CONF_USE_HTTPS: TEST_USE_HTTPS, CONF_USE_HTTPS: TEST_USE_HTTPS,
CONF_BC_PORT: TEST_BC_PORT, CONF_BC_PORT: TEST_BC_PORT,
CONF_BC_ONLY: False,
}, },
options={ options={
CONF_PROTOCOL: "rtsp", CONF_PROTOCOL: "rtsp",
@ -368,6 +416,7 @@ async def test_reauth(hass: HomeAssistant, mock_setup_entry: MagicMock) -> None:
CONF_PORT: TEST_PORT, CONF_PORT: TEST_PORT,
CONF_USE_HTTPS: TEST_USE_HTTPS, CONF_USE_HTTPS: TEST_USE_HTTPS,
CONF_BC_PORT: TEST_BC_PORT, CONF_BC_PORT: TEST_BC_PORT,
CONF_BC_ONLY: False,
}, },
options={ options={
CONF_PROTOCOL: DEFAULT_PROTOCOL, CONF_PROTOCOL: DEFAULT_PROTOCOL,
@ -414,6 +463,7 @@ async def test_reauth_abort_unique_id_mismatch(
CONF_PORT: TEST_PORT, CONF_PORT: TEST_PORT,
CONF_USE_HTTPS: TEST_USE_HTTPS, CONF_USE_HTTPS: TEST_USE_HTTPS,
CONF_BC_PORT: TEST_BC_PORT, CONF_BC_PORT: TEST_BC_PORT,
CONF_BC_ONLY: False,
}, },
options={ options={
CONF_PROTOCOL: DEFAULT_PROTOCOL, CONF_PROTOCOL: DEFAULT_PROTOCOL,
@ -484,6 +534,7 @@ async def test_dhcp_flow(hass: HomeAssistant, mock_setup_entry: MagicMock) -> No
CONF_USE_HTTPS: TEST_USE_HTTPS, CONF_USE_HTTPS: TEST_USE_HTTPS,
CONF_SUPPORTS_PRIVACY_MODE: TEST_PRIVACY, CONF_SUPPORTS_PRIVACY_MODE: TEST_PRIVACY,
CONF_BC_PORT: TEST_BC_PORT, CONF_BC_PORT: TEST_BC_PORT,
CONF_BC_ONLY: False,
} }
assert result["options"] == { assert result["options"] == {
CONF_PROTOCOL: DEFAULT_PROTOCOL, CONF_PROTOCOL: DEFAULT_PROTOCOL,
@ -507,6 +558,7 @@ async def test_dhcp_ip_update_aborted_if_wrong_mac(
CONF_PORT: TEST_PORT, CONF_PORT: TEST_PORT,
CONF_USE_HTTPS: TEST_USE_HTTPS, CONF_USE_HTTPS: TEST_USE_HTTPS,
CONF_BC_PORT: TEST_BC_PORT, CONF_BC_PORT: TEST_BC_PORT,
CONF_BC_ONLY: False,
}, },
options={ options={
CONF_PROTOCOL: DEFAULT_PROTOCOL, CONF_PROTOCOL: DEFAULT_PROTOCOL,
@ -548,6 +600,7 @@ async def test_dhcp_ip_update_aborted_if_wrong_mac(
timeout=DEFAULT_TIMEOUT, timeout=DEFAULT_TIMEOUT,
aiohttp_get_session_callback=ANY, aiohttp_get_session_callback=ANY,
bc_port=TEST_BC_PORT, bc_port=TEST_BC_PORT,
bc_only=False,
) )
assert expected_call in reolink_connect_class.call_args_list assert expected_call in reolink_connect_class.call_args_list
@ -606,6 +659,7 @@ async def test_dhcp_ip_update(
CONF_PORT: TEST_PORT, CONF_PORT: TEST_PORT,
CONF_USE_HTTPS: TEST_USE_HTTPS, CONF_USE_HTTPS: TEST_USE_HTTPS,
CONF_BC_PORT: TEST_BC_PORT, CONF_BC_PORT: TEST_BC_PORT,
CONF_BC_ONLY: False,
}, },
options={ options={
CONF_PROTOCOL: DEFAULT_PROTOCOL, CONF_PROTOCOL: DEFAULT_PROTOCOL,
@ -649,6 +703,7 @@ async def test_dhcp_ip_update(
timeout=DEFAULT_TIMEOUT, timeout=DEFAULT_TIMEOUT,
aiohttp_get_session_callback=ANY, aiohttp_get_session_callback=ANY,
bc_port=TEST_BC_PORT, bc_port=TEST_BC_PORT,
bc_only=False,
) )
assert expected_call in reolink_connect_class.call_args_list assert expected_call in reolink_connect_class.call_args_list
@ -686,6 +741,7 @@ async def test_dhcp_ip_update_ingnored_if_still_connected(
CONF_PORT: TEST_PORT, CONF_PORT: TEST_PORT,
CONF_USE_HTTPS: TEST_USE_HTTPS, CONF_USE_HTTPS: TEST_USE_HTTPS,
CONF_BC_PORT: TEST_BC_PORT, CONF_BC_PORT: TEST_BC_PORT,
CONF_BC_ONLY: False,
}, },
options={ options={
CONF_PROTOCOL: DEFAULT_PROTOCOL, CONF_PROTOCOL: DEFAULT_PROTOCOL,
@ -718,6 +774,7 @@ async def test_dhcp_ip_update_ingnored_if_still_connected(
timeout=DEFAULT_TIMEOUT, timeout=DEFAULT_TIMEOUT,
aiohttp_get_session_callback=ANY, aiohttp_get_session_callback=ANY,
bc_port=TEST_BC_PORT, bc_port=TEST_BC_PORT,
bc_only=False,
) )
assert expected_call in reolink_connect_class.call_args_list assert expected_call in reolink_connect_class.call_args_list
@ -748,6 +805,7 @@ async def test_reconfig(hass: HomeAssistant, mock_setup_entry: MagicMock) -> Non
CONF_PORT: TEST_PORT, CONF_PORT: TEST_PORT,
CONF_USE_HTTPS: TEST_USE_HTTPS, CONF_USE_HTTPS: TEST_USE_HTTPS,
CONF_BC_PORT: TEST_BC_PORT, CONF_BC_PORT: TEST_BC_PORT,
CONF_BC_ONLY: False,
}, },
options={ options={
CONF_PROTOCOL: DEFAULT_PROTOCOL, CONF_PROTOCOL: DEFAULT_PROTOCOL,
@ -795,6 +853,7 @@ async def test_reconfig_abort_unique_id_mismatch(
CONF_PORT: TEST_PORT, CONF_PORT: TEST_PORT,
CONF_USE_HTTPS: TEST_USE_HTTPS, CONF_USE_HTTPS: TEST_USE_HTTPS,
CONF_BC_PORT: TEST_BC_PORT, CONF_BC_PORT: TEST_BC_PORT,
CONF_BC_ONLY: False,
}, },
options={ options={
CONF_PROTOCOL: DEFAULT_PROTOCOL, CONF_PROTOCOL: DEFAULT_PROTOCOL,