diff --git a/matter_server/common/models.py b/matter_server/common/models.py index 0c9dc144..efd4049d 100644 --- a/matter_server/common/models.py +++ b/matter_server/common/models.py @@ -166,6 +166,7 @@ class ServerInfoMessage: sdk_version: str wifi_credentials_set: bool thread_credentials_set: bool + bluetooth_enabled: bool MessageType = ( diff --git a/matter_server/server/__main__.py b/matter_server/server/__main__.py index 41a107f4..8f09a59e 100644 --- a/matter_server/server/__main__.py +++ b/matter_server/server/__main__.py @@ -103,6 +103,12 @@ action="store_true", help="Enable PAA root certificates and other device information from test-net DCL.", ) +parser.add_argument( + "--bluetooth-adapter", + type=int, + required=False, + help="Optional bluetooth adapter (id) to enable direct commisisoning support.", +) args = parser.parse_args() @@ -187,6 +193,7 @@ def main() -> None: args.primary_interface, args.paa_root_cert_dir, args.enable_test_net_dcl, + args.bluetooth_adapter, ) async def handle_stop(loop: asyncio.AbstractEventLoop) -> None: diff --git a/matter_server/server/device_controller.py b/matter_server/server/device_controller.py index db98fa32..ef043cb5 100644 --- a/matter_server/server/device_controller.py +++ b/matter_server/server/device_controller.py @@ -258,8 +258,10 @@ async def commission_with_code( :return: The NodeInfo of the commissioned device. """ - node_id = self._get_next_node_id() + if not network_only and not self.server.bluetooth_enabled: + raise NodeCommissionFailed("Bluetooth commissioning is not available.") + node_id = self._get_next_node_id() LOGGER.info( "Starting Matter commissioning with code using Node ID %s.", node_id, diff --git a/matter_server/server/server.py b/matter_server/server/server.py index ff588a52..63a71261 100644 --- a/matter_server/server/server.py +++ b/matter_server/server/server.py @@ -30,10 +30,7 @@ ServerInfoMessage, ) from ..server.client_handler import WebsocketClientHandler -from .const import ( - DEFAULT_PAA_ROOT_CERTS_DIR, - MIN_SCHEMA_VERSION, -) +from .const import DEFAULT_PAA_ROOT_CERTS_DIR, MIN_SCHEMA_VERSION from .device_controller import MatterDeviceController from .stack import MatterStack from .storage import StorageController @@ -109,6 +106,7 @@ def __init__( primary_interface: str | None = None, paa_root_cert_dir: Path | None = None, enable_test_net_dcl: bool = False, + bluetooth_adapter_id: int | None = None, ) -> None: """Initialize the Matter Server.""" self.storage_path = storage_path @@ -122,11 +120,12 @@ def __init__( else: self.paa_root_cert_dir = Path(paa_root_cert_dir).absolute() self.enable_test_net_dcl = enable_test_net_dcl + self.bluetooth_enabled = bluetooth_adapter_id is not None self.logger = logging.getLogger(__name__) self.app = web.Application() self.loop: asyncio.AbstractEventLoop | None = None # Instantiate the Matter Stack using the SDK using the given storage path - self.stack = MatterStack(self) + self.stack = MatterStack(self, bluetooth_adapter_id) self.storage = StorageController(self) self.vendor_info = VendorInfo(self) # we dynamically register command handlers @@ -243,6 +242,7 @@ def get_info(self) -> ServerInfoMessage: sdk_version=chip_clusters_version(), wifi_credentials_set=self._device_controller.wifi_credentials_set, thread_credentials_set=self._device_controller.thread_credentials_set, + bluetooth_enabled=self.bluetooth_enabled, ) @api_command(APICommand.SERVER_DIAGNOSTICS) diff --git a/matter_server/server/stack.py b/matter_server/server/stack.py index 212fb412..f653fe97 100644 --- a/matter_server/server/stack.py +++ b/matter_server/server/stack.py @@ -89,13 +89,22 @@ class MatterStack: def __init__( self, server: "MatterServer", + bluetooth_adapter_id: int | None = None, ) -> None: """Initialize Matter Stack.""" self.logger = logging.getLogger(__name__) self.logger.info("Initializing CHIP/Matter Controller Stack...") storage_file = os.path.join(server.storage_path, "chip.json") - self.logger.debug("Using storage file: %s", storage_file) - chip.native.Init() + self.logger.debug( + "Using storage file: %s - Bluetooth commissioning enabled: %s", + storage_file, + "NO" + if bluetooth_adapter_id is None + else f"YES (adapter {bluetooth_adapter_id})", + ) + # give the fake adapter id of 999 to disable bluetooth + # because None means use the default adapter + chip.native.Init(999 if bluetooth_adapter_id is None else bluetooth_adapter_id) # Initialize logging after stack init! # See: https://github.com/project-chip/connectedhomeip/issues/20233