Contents

Adding Matter support to your ecosystem

Allow people to add Matter accessories to your platform.

Overview

With the MatterSupport framework, you can administer, add, and configure Matter devices in your ecosystem using an iOS device. To onboard a device, you need to configure discovery, set up a home, create an extension request handler, and override its methods.

Configure discovery

Add the following to your app’s Info.plist so it can discover Matter-related services. See NSBonjourServices for more information.

<key>NSBonjourServices</key>
    <array>
        <string>_matter._tcp</string>
        <string>_matterc._udp</string>
        <string>_matterd._udp</string>
</array>

Set up the home

Define the home’s name, then create the topology and pass your ecosystem name and an array of homes.

import MatterSupport
import os.log

let homes = [MatterAddDeviceRequest.Home(name: "My Home")]
let topology = MatterAddDeviceRequest.Topology(ecosystemName: "MyEcosystemName", homes: homes)

Use the newly created topology object to create a request to add a device.

let request = MatterAddDeviceRequest(topology: topology)

You can optionally provide the Matter setup code programmatically while setting up a Matter device in an ecosystem. To do this, pass a string containing the payload information from the device’s packaging, such as a QR code. For more information on when this is appropriate, see Matter Allow Setup Payload.

// Provide the Matter setup code programmatically.
let setupCode = "12345678910"
request.setupPayload = MTRSetupPayload(payload: setupCode)

// Provide the Matter setup code programmatically using a QR code and add the MT: prefix.
let qrCodePayload = "MT:12345678910ABCDEFG12"
request.setupPayload = MTRSetupPayload(payload: qrCodePayload)

Next, start the user interface flow for adding the device. Handle any errors that may occur during the setup.

do {
    try await request.perform()
    print("Successfully set up device!")

    // Handle the success full setup request and update your app's UI, register the device in your database, or set up any default automations.
} catch let error as MyMatterAddDeviceExtensionRequestHandler {
    // Handle specific Matter errors.
    switch error {
    case .cancelled:
        print("Someone cancelled the setup process.")
    case .accessDenied:
        print("Access denied - check entitlements and permissions.")
    case .unsupported:
        print("Matter setup is not supported on this device.")
    default:
        print("Failed with Matter error: \(error.localizedDescription).")
    }
} catch {
    // Handle other errors.
    print("Failed to set up device with error: \(error.localizedDescription).")
}

Create the extension request handler

Subclass MatterAddDeviceExtensionRequestHandler and override its methods. This class facilitates the user interface flow during the setup of a new Matter device.

class MyMatterAddDeviceExtensionRequestHandler: MatterAddDeviceExtensionRequestHandler {
    // Define an error type for pairing failures.
    enum PairingError: Error {
        case invalidCredentials
        case pairingFailed
    }

    // Use OSLog to log debugging information.
    private let logger = Logger(subsystem: "com.yourcompany.matterapp", category: "DeviceSetup")

    override init() {
        super.init()
        logger.debug("MatterAddDeviceExtensionRequestHandler initialized")
    }

    // Override this method to return the rooms in the home.
    override func rooms(in home: MatterAddDeviceRequest.Home?) async -> [MatterAddDeviceRequest.Room] {
        logger.debug("Received request to fetch rooms in home: \(String(describing: home?.name)).")

        // In your app, fetch rooms from your database or ecosystem.
        let rooms: [String] = ["Living Room", "Bedroom", "Office", "Kitchen", "Dining Room"]
        return rooms.map { MatterAddDeviceRequest.Room(displayName: $0) }
    }

    // Override this method to commission the device to your application.
    override func commissionDevice(in home: MatterAddDeviceRequest.Home?, onboardingPayload: String, commissioningID: UUID) async throws {
        logger.debug("Commissioning device in home '\(String(describing: home?.name))' with payload: \(onboardingPayload).")

        do {
            // Parse the onboarding payload and commission the device to your app using the Matter framework APIs.
            logger.info("Successfully commissioned device with ID: \(commissioningID)")

            // Make sure that your application returns from this method when it finished pairing the accessory with your application.
            // Returning from this method indicates to the MatterSupport framework that the pairing process is completed,

            // and the system displays a view to indicate that the pairing process is completed.
        } catch {
            logger.error("Failed to commission device: \(error.localizedDescription)")
            throw PairingError.pairingFailed
        }
    }

    // Override this method to configure the device to your application.
    override func configureDevice(named name: String, in room: MatterAddDeviceRequest.Room?) async {
        logger.debug("Configuring device '\(name)' in room: \(String(describing: room?.displayName))")

        // Retrieve and configure the newly paired device in your ecosystem;
        // for example, find the device, set its name or room, apply default configurations, and save information in your database.
        logger.info("Device '\(name)' successfully configured")
    }

    // Override this method to validate the device's credentials.
    override func validateDeviceCredential(_ deviceCredential: MatterAddDeviceExtensionRequestHandler.DeviceCredential) async throws {
        logger.debug("Validating device credential")

        // This code snippet skips validation for better readability.
        // Make sure to replace the following line with actual validation code.
        let isValid = true

        if !isValid {
            logger.warning("Device credential validation failed")
            throw PairingError.invalidCredentials
        } else {
            logger.info("Device credential successfully validated")
        }
    }

    // Override this method to select a specific Wi-Fi network or to ask the Matter framework to select the default WiFi network.
    override func selectWiFiNetwork(from wifiScanResults: [MatterAddDeviceExtensionRequestHandler.WiFiScanResult]) async throws -> MatterAddDeviceExtensionRequestHandler.WiFiNetworkAssociation {
        logger.debug("Selecting WiFi network from \(wifiScanResults.count) scan results")

        // Check if a specific network is available.
        let preferredSSID = "YourPreferredNetwork"
        let preferredNetwork = wifiScanResults.first { result in
            result.ssid == preferredSSID && result.security != .open
        }

        if let network = preferredNetwork {
            // Use stored credentials or prompt the person for their password.
            let credentials = "YourSecurePassword"
            logger.info("Selected specific WiFi network: \(network.ssid)")
            return .network(ssid: network.ssid, credentials: credentials)
        } else {
            // Use the system's default network.
            logger.info("Using default system WiFi network.")
            return .defaultSystemNetwork
        }
    }

    // Override this method to select a specific Thread network or to ask the Matter framework to select the default Thread network.
    override func selectThreadNetwork(from threadScanResults: [MatterAddDeviceExtensionRequestHandler.ThreadScanResult]) async throws -> MatterAddDeviceExtensionRequestHandler.ThreadNetworkAssociation {
        logger.debug("Selecting Thread network from \(threadScanResults.count) scan results")

        // Check if a specific network is available by name.
        let preferredNetworkName = "HomeThread"
        let preferredNetwork = threadScanResults.first { result in
            result.networkName == preferredNetworkName
        }

        if let network = preferredNetwork, let extendedPANID = network.extendedPANID {
            logger.info("Selected specific Thread network: \(network.networkName ?? "Unnamed")")
            return .network(extendedPANID: extendedPANID)
        } else {
            // Use the system's default Thread network.
            logger.info("Using default system Thread network")
            return .defaultSystemNetwork
        }
    }
}

Set the principal class

Register the subclass you created above in the app’s Info.plist as the NSPrincipalClass for the com.apple.matter.support.extension.device-setup extension point identifier.

<key>NSExtension</key>
<dict>
    <key>NSExtensionPointIdentifier</key>
    <string>com.apple.matter.support.extension.device-setup</string>
    <key>NSExtensionPrincipalClass</key>
    <string>$(PRODUCT_MODULE_NAME).MyMatterAddDeviceExtensionRequestHandler</string>
</dict>

See Also

Adding a device