CtrlK
BlogDocsLog inGet started
Tessl Logo

dpearson2699/swift-ios-skills

Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.

71

Quality

89%

Does it follow best practices?

Impact

No eval scenarios have been run

SecuritybySnyk

Advisory

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

matter-commissioning.mdskills/homekit/references/

HomeKit + Matter Extended Patterns

Overflow reference for the homekit skill. Contains advanced patterns that exceed the main skill file's scope.

Contents

  • SwiftUI HomeKit Integration
  • Full Delegate Wiring
  • Service Type Discovery
  • Advanced Matter Extension Handler
  • Testing with HomeKit Accessory Simulator

SwiftUI HomeKit Integration

HomeKit Store with @Observable

import HomeKit
import SwiftUI

@Observable
@MainActor
final class HomeStore: NSObject {
    static let shared = HomeStore()

    let homeManager = HMHomeManager()

    var homes: [HMHome] = []
    var primaryHome: HMHome?
    var isAuthorized = false

    override init() {
        super.init()
        homeManager.delegate = self
    }

    var accessories: [HMAccessory] {
        primaryHome?.accessories ?? []
    }

    var rooms: [HMRoom] {
        primaryHome?.rooms ?? []
    }

    func addHome(name: String) async throws {
        try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
            homeManager.addHome(withName: name) { home, error in
                if let error {
                    continuation.resume(throwing: error)
                } else {
                    continuation.resume()
                }
            }
        }
    }
}

extension HomeStore: HMHomeManagerDelegate {
    nonisolated func homeManagerDidUpdateHomes(_ manager: HMHomeManager) {
        Task { @MainActor in
            homes = manager.homes
            primaryHome = manager.primaryHome
        }
    }

    nonisolated func homeManager(
        _ manager: HMHomeManager,
        didUpdate status: HMHomeManagerAuthorizationStatus
    ) {
        Task { @MainActor in
            isAuthorized = status.contains(.authorized)
        }
    }

    nonisolated func homeManager(
        _ manager: HMHomeManager,
        didAdd home: HMHome
    ) {
        Task { @MainActor in
            homes = manager.homes
        }
    }

    nonisolated func homeManager(
        _ manager: HMHomeManager,
        didRemove home: HMHome
    ) {
        Task { @MainActor in
            homes = manager.homes
        }
    }
}

Accessory List View

struct AccessoryListView: View {
    @State private var homeStore = HomeStore.shared

    var body: some View {
        NavigationStack {
            Group {
                if !homeStore.isAuthorized {
                    ContentUnavailableView(
                        "HomeKit Access Required",
                        systemImage: "house.fill",
                        description: Text("Grant access in Settings to manage your home.")
                    )
                } else if homeStore.accessories.isEmpty {
                    ContentUnavailableView(
                        "No Accessories",
                        systemImage: "lightbulb",
                        description: Text("Add accessories using the Home app.")
                    )
                } else {
                    accessoryList
                }
            }
            .navigationTitle(homeStore.primaryHome?.name ?? "Home")
        }
    }

    private var accessoryList: some View {
        List {
            ForEach(homeStore.rooms, id: \.uniqueIdentifier) { room in
                Section(room.name) {
                    let roomAccessories = homeStore.accessories.filter {
                        $0.room?.uniqueIdentifier == room.uniqueIdentifier
                    }
                    ForEach(roomAccessories, id: \.uniqueIdentifier) { accessory in
                        AccessoryRow(accessory: accessory)
                    }
                }
            }
        }
    }
}

struct AccessoryRow: View {
    let accessory: HMAccessory

    var body: some View {
        HStack {
            Image(systemName: iconName)
            VStack(alignment: .leading) {
                Text(accessory.name)
                    .font(.headline)
                Text(accessory.isReachable ? "Reachable" : "Not Reachable")
                    .font(.caption)
                    .foregroundStyle(accessory.isReachable ? .green : .secondary)
            }
        }
    }

    private var iconName: String {
        switch accessory.category.categoryType {
        case HMAccessoryCategoryTypeLightbulb: return "lightbulb.fill"
        case HMAccessoryCategoryTypeThermostat: return "thermometer"
        case HMAccessoryCategoryTypeLock: return "lock.fill"
        case HMAccessoryCategoryTypeSwitch: return "light.switch.2"
        default: return "house.fill"
        }
    }
}

Light Control View

struct LightControlView: View {
    let accessory: HMAccessory

    @State private var isOn = false
    @State private var brightness: Double = 100

    private var lightbulbService: HMService? {
        accessory.services.first {
            $0.serviceType == HMServiceTypeLightbulb
        }
    }

    private var powerCharacteristic: HMCharacteristic? {
        lightbulbService?.characteristics.first {
            $0.characteristicType == HMCharacteristicTypePowerState
        }
    }

    private var brightnessCharacteristic: HMCharacteristic? {
        lightbulbService?.characteristics.first {
            $0.characteristicType == HMCharacteristicTypeBrightness
        }
    }

    var body: some View {
        VStack {
            Toggle("Power", isOn: $isOn)
                .onChange(of: isOn) { _, newValue in
                    powerCharacteristic?.writeValue(newValue) { _ in }
                }

            if isOn {
                Slider(value: $brightness, in: 0...100, step: 1)
                    .onChange(of: brightness) { _, newValue in
                        brightnessCharacteristic?.writeValue(
                            Int(newValue)
                        ) { _ in }
                    }
                Text("Brightness: \(Int(brightness))%")
            }
        }
        .padding()
        .task { await readCurrentState() }
    }

    private func readCurrentState() async {
        powerCharacteristic?.readValue { _ in
            if let value = powerCharacteristic?.value as? Bool {
                isOn = value
            }
        }
        brightnessCharacteristic?.readValue { _ in
            if let value = brightnessCharacteristic?.value as? Int {
                brightness = Double(value)
            }
        }
    }
}

Full Delegate Wiring

HMHomeDelegate

extension HomeStore: HMHomeDelegate {
    nonisolated func home(
        _ home: HMHome,
        didAdd accessory: HMAccessory
    ) {
        accessory.delegate = self
        Task { @MainActor in
            // Refresh accessory list
        }
    }

    nonisolated func home(
        _ home: HMHome,
        didRemove accessory: HMAccessory
    ) {
        Task { @MainActor in
            // Refresh accessory list
        }
    }

    nonisolated func home(
        _ home: HMHome,
        didAdd room: HMRoom
    ) {
        Task { @MainActor in
            // Refresh room list
        }
    }

    nonisolated func home(
        _ home: HMHome,
        didUpdateNameFor room: HMRoom
    ) {
        Task { @MainActor in
            // Update room name display
        }
    }
}

HMAccessoryDelegate

extension HomeStore: HMAccessoryDelegate {
    nonisolated func accessory(
        _ accessory: HMAccessory,
        service: HMService,
        didUpdateValueFor characteristic: HMCharacteristic
    ) {
        Task { @MainActor in
            // Update UI for the changed characteristic
            let newValue = characteristic.value
            print("\(accessory.name).\(characteristic.characteristicType) = \(newValue ?? "nil")")
        }
    }

    nonisolated func accessoryDidUpdateReachability(
        _ accessory: HMAccessory
    ) {
        Task { @MainActor in
            print("\(accessory.name) reachable: \(accessory.isReachable)")
        }
    }

    nonisolated func accessoryDidUpdateName(
        _ accessory: HMAccessory
    ) {
        Task { @MainActor in
            // Refresh name display
        }
    }
}

Service Type Discovery

Finding Specific Service Types

// Find all thermostats in the home
let thermostats = home.servicesWithTypes([HMServiceTypeThermostat]) ?? []

for service in thermostats {
    let currentTemp = service.characteristics.first {
        $0.characteristicType == HMCharacteristicTypeCurrentTemperature
    }
    let targetTemp = service.characteristics.first {
        $0.characteristicType == HMCharacteristicTypeTargetTemperature
    }

    currentTemp?.readValue { _ in
        print("Current: \(currentTemp?.value ?? "?")")
    }
}

Common Service Types

ConstantDescription
HMServiceTypeLightbulbLight control (on/off, brightness, color)
HMServiceTypeThermostatTemperature control
HMServiceTypeLockMechanismDoor lock
HMServiceTypeGarageDoorOpenerGarage door
HMServiceTypeSwitchGeneric on/off switch
HMServiceTypeMotionSensorMotion detection
HMServiceTypeTemperatureSensorTemperature reading
HMServiceTypeContactSensorDoor/window open/close

Common Characteristic Types

ConstantValue TypeDescription
HMCharacteristicTypePowerStateBoolOn/off
HMCharacteristicTypeBrightnessInt (0-100)Light brightness
HMCharacteristicTypeHueFloat (0-360)Light hue
HMCharacteristicTypeSaturationFloat (0-100)Light saturation
HMCharacteristicTypeCurrentTemperatureFloatCelsius reading
HMCharacteristicTypeTargetTemperatureFloatCelsius target
HMCharacteristicTypeLockCurrentStateIntLock state (0=unsecured)
HMCharacteristicTypeLockTargetStateIntLock target state

Advanced Matter Extension Handler

Full Handler with Network Selection

import MatterSupport

final class MyMatterHandler: MatterAddDeviceExtensionRequestHandler {

    override func validateDeviceCredential(
        _ deviceCredential: DeviceCredential
    ) async throws {
        // Validate the Device Attestation Certificate (DAC) against
        // your Product Attestation Authority (PAA) root certificates.
        let dac = deviceCredential.deviceAttestationCertificate
        let pai = deviceCredential.productAttestationIntermediateCertificate
        let cd = deviceCredential.certificationDeclaration

        // If validation fails, throw an error to reject the device
        guard isValidCertificateChain(dac: dac, pai: pai, cd: cd) else {
            throw MatterCommissioningError.invalidCredential
        }
    }

    override func rooms(
        in home: MatterAddDeviceRequest.Home?
    ) async -> [MatterAddDeviceRequest.Room] {
        // Fetch rooms from your backend for the given home
        guard let home else { return [] }

        let roomNames = await fetchRoomsFromBackend(homeName: home.displayName)
        return roomNames.map { MatterAddDeviceRequest.Room(displayName: $0) }
    }

    override func configureDevice(
        named name: String,
        in room: MatterAddDeviceRequest.Room?
    ) async {
        // Save device configuration to your ecosystem backend
        await saveDeviceToBackend(
            deviceName: name,
            roomName: room?.displayName
        )
    }

    override func commissionDevice(
        in home: MatterAddDeviceRequest.Home?,
        onboardingPayload: String,
        commissioningID: UUID
    ) async throws {
        // Commission the device into your Matter fabric
        // using the Matter framework (MTRDeviceController)
        try await commissionToFabric(
            payload: onboardingPayload,
            commissioningID: commissioningID
        )
    }

    override func selectWiFiNetwork(
        from networks: [WiFiScanResult]
    ) async throws -> WiFiNetworkAssociation {
        // Use the system default network or specify one
        return .defaultSystemNetwork
    }

    override func selectThreadNetwork(
        from networks: [ThreadScanResult]
    ) async throws -> ThreadNetworkAssociation {
        return .defaultSystemNetwork
    }
}

Testing with HomeKit Accessory Simulator

  1. Download Additional Tools for Xcode from Apple's developer downloads page
  2. Launch HomeKit Accessory Simulator
  3. Create simulated accessories (lights, locks, sensors)
  4. Pair them with your app running in Simulator or on device
// Enable verbose logging during development
#if DEBUG
import os
let homeKitLog = Logger(subsystem: "com.example.app", category: "HomeKit")

func logAccessories() {
    guard let home = homeManager.primaryHome else { return }
    for accessory in home.accessories {
        homeKitLog.debug("Accessory: \(accessory.name), reachable: \(accessory.isReachable)")
        for service in accessory.services {
            homeKitLog.debug("  Service: \(service.localizedDescription ?? service.serviceType)")
        }
    }
}
#endif

skills

CHANGELOG.md

README.md

tile.json