Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 10 additions & 33 deletions matter_server/server/device_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@
from chip.discovery import DiscoveryType
from chip.exceptions import ChipStackError
from chip.native import PyChipError
from zeroconf import BadTypeInNameException, IPVersion, ServiceStateChange, Zeroconf
from zeroconf import (
DNSQuestionType,
IPVersion,
ServiceStateChange,
Zeroconf,
)
from zeroconf.asyncio import AsyncServiceBrowser, AsyncServiceInfo, AsyncZeroconf

from matter_server.common.const import VERBOSE_LOG_LEVEL
Expand Down Expand Up @@ -215,11 +220,14 @@ async def start(self) -> None:
LOGGER.info("Loaded %s nodes from stored configuration", len(self._nodes))
# set-up mdns browser
self._aiozc = AsyncZeroconf(ip_version=IPVersion.All)
services = [MDNS_TYPE_OPERATIONAL_NODE, MDNS_TYPE_COMMISSIONABLE_NODE]
services = [
f"_I{self.compressed_fabric_id:0{16}X}._sub.{MDNS_TYPE_OPERATIONAL_NODE}"
]
self._aiobrowser = AsyncServiceBrowser(
self._aiozc.zeroconf,
services,
handlers=[self._on_mdns_service_state_change],
question_type=DNSQuestionType.QM,
)

async def stop(self) -> None:
Expand Down Expand Up @@ -1510,13 +1518,6 @@ def _on_mdns_service_state_change(
# We already have a timer to resolve this service, so ignore this callback.
return

if service_type == MDNS_TYPE_COMMISSIONABLE_NODE:
# process the event with a debounce timer
self._mdns_event_timer[name] = self._loop.call_later(
0.5, self._on_mdns_commissionable_node_state, name, state_change
)
return

if service_type == MDNS_TYPE_OPERATIONAL_NODE:
if self._fabric_id_hex is None or self._fabric_id_hex not in name.lower():
# filter out messages that are not for our fabric
Expand Down Expand Up @@ -1566,30 +1567,6 @@ def _on_mdns_operational_node_state(
)
)

def _on_mdns_commissionable_node_state(
self, name: str, state_change: ServiceStateChange
) -> None:
"""Handle a (commissionable) Matter node MDNS state change."""
self._mdns_event_timer.pop(name, None)
logger = LOGGER.getChild("mdns")

try:
info = AsyncServiceInfo(MDNS_TYPE_COMMISSIONABLE_NODE, name)
except BadTypeInNameException as ex:
logger.debug("Ignoring record with bad type in name: %s: %s", name, ex)
return

async def handle_commissionable_node_added() -> None:
if TYPE_CHECKING:
assert self._aiozc is not None
await info.async_request(self._aiozc.zeroconf, 3000)
logger.debug("Discovered commissionable Matter node: %s", info)

if state_change == ServiceStateChange.Added:
asyncio.create_task(handle_commissionable_node_added())
elif state_change == ServiceStateChange.Removed:
logger.debug("Commissionable Matter node disappeared: %s", info)

def _write_node_state(self, node_id: int, force: bool = False) -> None:
"""Schedule the write of the current node state to persistent storage."""
if node_id not in self._nodes:
Expand Down
23 changes: 22 additions & 1 deletion tests/server/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
from __future__ import annotations

from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
from unittest.mock import AsyncMock, MagicMock, create_autospec, patch

import pytest

from matter_server.common.helpers.api import parse_arguments
from matter_server.common.models import APICommand
from matter_server.server.sdk import ChipDeviceControllerWrapper
from matter_server.server.server import MatterServer

if TYPE_CHECKING:
Expand All @@ -21,6 +22,7 @@
"chip_native",
"chip_logging",
"chip_stack",
"chip_device_controller_wrapper",
"certificate_authority_manager",
"storage_controller",
)
Expand Down Expand Up @@ -74,6 +76,25 @@ def chip_stack_fixture() -> Generator[MagicMock, None, None]:
yield chip_stack


@pytest.fixture(name="chip_device_controller_wrapper")
def chip_device_controller_wrapper_fixture() -> Generator[MagicMock, None, None]:
"""Return a mocked chip native."""
with patch(
"matter_server.server.device_controller.ChipDeviceControllerWrapper",
autospec=True,
) as chip_device_controller_wrapper_class:
chip_device_controller_wrapper = create_autospec(
ChipDeviceControllerWrapper, instance=True
)
chip_device_controller_wrapper.get_compressed_fabric_id = AsyncMock(
return_value=1234
)
chip_device_controller_wrapper_class.return_value = (
chip_device_controller_wrapper
)
yield chip_device_controller_wrapper


@pytest.fixture(name="certificate_authority_manager")
def certificate_authority_manager_fixture() -> Generator[MagicMock, None, None]:
"""Return a mocked certificate authority manager."""
Expand Down
Loading