You can add support for your own custom traits that provide additional
capabilities beyond that provided by the standard traits. Custom traits like
this are referred to as *manufacturer-specific traits* or *MS traits* . MS traits
are defined in the standard `.matter` IDL format, then converted into an iOS
module that can be imported into your app.

The iOS Home APIs SDK includes a code generator that can perform this
conversion. Additionally, if needed, provisional traits can also be generated
using this code generator.

> [!NOTE]
> **Note:** The term *trait* in the Home APIs is equivalent to *cluster* in Matter. Although both terms refer to the same thing - a bundle of related device capabilities encapsulated in a reusable building block - the Home API documentation favors the use of *trait* over *cluster*.

## Prerequisites

In order to use the code generator, you need:

- Python 3.10 or newer.
- A `.matter` IDL file with the definition of your MS traits. This file should contain just the `client cluster` definitions. You can create one manually, or use ones generated as part of the Matter SDK build process for your device firmware.

The Matter codegen tool has some Python dependencies that
need to be installed. This is a one-time step that should be done *after* a new
version of the SDK is integrated to ensure the tool can be run in the Xcode
sandbox.

    swift package plugin --allow-network-connections "all" --allow-writing-to-package-directory matter-codegen-init

> [!NOTE]
> **Note:** Xcode ships with Python 3.9. To install other versions, consider using [pyenv](https://github.com/pyenv/pyenv).

For more information about the IDL format, see
[matter/idl](https://github.com/project-chip/connectedhomeip/tree/master/scripts/py_matter_idl/matter/idl)
on GitHub. The `/tests/inputs` directory there features a number of sample IDL
files. The complete IDL file for all Matter clusters, which
is the source for generated files on all platforms (including the [iOS
modules](https://developers.home.google.com/reference/swift/GoogleHomeTypes/GoogleHomeTypes) for
the Home APIs), can be found at
[controller-clusters.matter](https://github.com/project-chip/connectedhomeip/blob/master/src/controller/data_model/controller-clusters.matter).

## Generate the Swift files

The code generator is bundled with the SDK and integrated into [Swift Package
Manager](https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/)
(SwiftPM). Although the code generator is invoked by XCode
as a SwiftPM plugin, your project doesn't have to use SwiftPM for package
management.

1. Integrate the SDK into your project. See [Set up the iOS SDK](https://developers.home.google.com/apis/ios/apis/ios/sdk#setup) for instructions.
2. Set up the plugin. Because the plugin runs in a sandbox, you need to install some dependencies:

       swift package plugin matter-codegen-init \
        --allow-network-connections all \
        --allow-writing-to-package-directory

3. Decide on a namespace for the generated code, and add it as a `pragma swift`
   to the IDL file. For example, `MyCompany`:

   ```c
   // pragma kotlin(package=com.mycompany.matter.cluster, generate_namespace=true)
   // pragma swift(package=MyCompany, generate_namespace=true)

   client cluster SimpleCustom = 4294048768 {
       attribute int16u clusterAttr = 1;

       // Global Attributes
       readonly attribute command_id generatedCommandList[] = 65528;
       readonly attribute command_id acceptedCommandList[] = 65529;
       readonly attribute event_id eventList[] = 65530;
       readonly attribute attrib_id attributeList[] = 65531;
       readonly attribute bitmap32 featureMap = 65532;
       readonly attribute int16u clusterRevision = 65533;
   }
   ```
4. Run the generator:

       swift package plugin matter-codegen Clusters/MyCustomCluster.matter

   <br />

   The generated `.swift` files can be added to your app project
   or be added to a framework if you want them in a separate module.

### Alternative: Generate the traits automatically

The code generator includes an additional plugin that recognizes the `.matter`
file extension. The plugin will automatically invoke the code generator and add
the output Swift files to the current target. This avoids the need to commit
generated files to source control, and ensures that the traits are always
generated using the bundled version of the generator. If your app is already
using SwiftPM, **we highly recommend using this plugin**.

To use the plugin:

1. Add your `.matter` files to a target in your app.
2. Add the following plugin snippet to that target:

   ```swift
       .target(
         name: "MyAppTarget",
         plugins: [.plugin(name: "MatterCodegenPlugin")]
       ),
   ```

## Use the module

> [!NOTE]
> **Note:** Some generated files use access-level `import` statements. This feature was introduced in Swift 6.0. If your app uses an older version of Swift, you may need to either enable the experimental build flag `-enable-experimental-feature AccessLevelOnImport` or remove the access-level modifier on the `import` statement to get a successful build.

To use the generated output, copy the file(s) to your Xcode project. In this
case, the files are `MyCompany.swift` and `MyCustom.swift`.

If you are using a separate framework for your traits, then use an `import`
statement to import the applicable module.

Then MS traits should now be available through the Home APIs the same way
standard Matter traits are, as long as those MS
traits are defined in your Matter firmware. Simply
substitute a standard trait name with your MS trait name.

For example, if your MS trait is named `MyCustomTrait`, the following call
returns all attributes of `MyCustomTrait`:

```swift
let myCustomTrait = deviceType.traits[MyCompany.MyCustomTrait.self]
```

## Example

If you are not familiar with the IDL format, see the
[matter/idl/tests/inputs](https://github.com/project-chip/connectedhomeip/tree/master/scripts/py_matter_idl/matter/idl/tests/inputs)
directories for sample files.

### IDL input

A very simple MS trait can be defined in the IDL like this:

```idl
// mycustom.matter
// pragma kotlin(package=com.mycompany.matter.cluster, generate_namespace=true)
// pragma swift(package=MyCompany, generate_namespace=true)

client cluster MyCustom = 4294048768 {
    attribute int16u clusterAttr = 1;

    // Global Attributes
    readonly attribute command_id generatedCommandList[] = 65528;
    readonly attribute command_id acceptedCommandList[] = 65529;
    readonly attribute event_id eventList[] = 65530;
    readonly attribute attrib_id attributeList[] = 65531;
    readonly attribute bitmap32 featureMap = 65532;
    readonly attribute int16u clusterRevision = 65533;
}
```

In this example, the trait ID of `4294048768` corresponds to `0xFFF1FC00` in
hexadecimal, where the prefix of `0xFFF1` represents a test Vendor ID and the
suffix of `0xFC00` is a value reserved for Manufacturer-Specific traits. See
the *Manufacturer Extensible Identifier (MEI)* section of the
Matter Specification for more information. Make sure to use
an appropriate decimal trait ID for each MS trait in your IDL file.

If MS traits are being used in your device today, you likely have it
defined in this format already.

### Swift output

Two Swift files, `MyCustom.swift` (named after the trait) and
`MyCompany.swift` (named after the namespace), can be found in
the specified output directory. These files are formatted specifically for use
with the Home APIs.

> [!NOTE]
> **Note:** You can add the `generate_namespace=true` to the swift pragma when you run the code generator if you want to generate the `MyCompany.swift` file and the `enum` definition.

Once available (such as in your app's Xcode project),
the files can be used as described in [Use the module](https://developers.home.google.com/apis/ios/manufacturer-specific-traits.md.txt#use_the_module).

### MyCustom.swift

#### Click to expand to view \`MyCustom.swift\`

```swift
// This file contains machine-generated code.

public import Foundation
import GoogleHomeSDK
private import SwiftProtobuf

/*
 * This file was machine generated via the code generator
 * in `codegen.clusters.swift.CustomGenerator`
 *
 */

extension MyCompany {
/// :nodoc:
  public struct MyCustomTrait: MatterTrait {

    /// No supported events for `MyCustomTrait`.
    public static let supportedEventTypes: [Event.Type] = []

    /// No supported commands for `MyCustomTrait`.
    public static let supportedCommandTypes: [Command.Type] = []

    public static let identifier = MyCompany.MyCustomTrait.makeTraitID(for: 4294048768)

    public let metadata: TraitMetadata

    /// List of attributes for the `MyCustomTrait`.
    public let attributes: MyCompany.MyCustomTrait.Attributes

    private let interactionProxy: InteractionProxy

    public init(decoder: TraitDecoder, interactionProxy: InteractionProxy?, metadata: TraitMetadata) throws {
      guard let interactionProxy = interactionProxy else {
        throw HomeError.invalidArgument("InteractionProxy parameter required.")
      }
      let unwrappedDecoder = try decoder.unwrapPayload(namespace: Self.identifier.namespace)
      self.interactionProxy = interactionProxy
      self.attributes = try Attributes(decoder: unwrappedDecoder)
      self.metadata = metadata
    }

    // Internal for testing.
    internal init(attributes: MyCompany.MyCustomTrait.Attributes = .init(), interactionProxy: InteractionProxy?, metadata: TraitMetadata = .init()) throws {
      guard let interactionProxy = interactionProxy else {
        throw HomeError.invalidArgument("InteractionProxy parameter required.")
      }
      self.interactionProxy = interactionProxy
      self.attributes = attributes
      self.metadata = metadata
    }

    public func encode(with encoder: TraitEncoder) throws {
      encoder.wrapPayload(namespace: Self.identifier.namespace)
      try self.attributes.encode(with: encoder)
    }

    public func update(_ block: @Sendable (MutableAttributes) -> Void) async throws -> Self {
      let mutable = MutableAttributes(attributes: self.attributes)
      block(mutable)
      if self.interactionProxy.strictOperationValidation {
        guard self.attributes.$clusterAttr.isSupported || !mutable.clusterAttrIsSet else {
          throw HomeError.invalidArgument("clusterAttr is not supported.")
        }
      }
      let updatedTrait = try MyCompany.MyCustomTrait(attributes: self.attributes.apply(mutable), interactionProxy: self.interactionProxy, metadata: self.metadata)
      try await self.interactionProxy.update(trait: mutable, useTimedInteraction: false)
      return updatedTrait
    }
  }
}

// MARK: - ForceReadableTrait

extension MyCompany.MyCustomTrait: ForceReadableTrait {
  public func forceRead() async throws {
    try await self.interactionProxy.forceRead(traitID: Self.identifier)
  }
}

// MARK: - Attributes

extension MyCompany.MyCustomTrait {

  /// Attributes for the `MyCustomTrait`.
  public struct Attributes: Sendable {
    // Attributes required at runtime.
    /** A list of the attribute IDs of the attributes supported by the cluster instance. */
    /// Nullable: false.
    @TraitAttribute public var attributeList: [UInt32]?

    /// Nullable: false.
    @TraitAttribute public var clusterAttr: UInt16?
    /** A list of server-generated commands (server to client) which are supported by this
    cluster server instance. */
    /// Nullable: false.
    @TraitAttribute public var generatedCommandList: [UInt32]?
    /** A list of client-generated commands which are supported by this cluster server instance.
    */
    /// Nullable: false.
    @TraitAttribute public var acceptedCommandList: [UInt32]?
    /**  Whether the server supports zero or more optional cluster features. A cluster feature
    is a set of cluster elements that are mandatory or optional for a defined feature of the
    cluster. If a cluster feature is supported by the cluster instance, then the corresponding
    bit is set to 1, otherwise the bit is set to 0 (zero). */
    /// Nullable: false.
    @TraitAttribute public var featureMap: UInt32?
    /** The revision of the server cluster specification supported by the cluster instance. */
    /// Nullable: false.
    @TraitAttribute public var clusterRevision: UInt16?

    internal init(
      clusterAttr: UInt16? = nil,
      generatedCommandList: [UInt32]? = nil,
      acceptedCommandList: [UInt32]? = nil,
      attributeList: [UInt32]? = nil,
      featureMap: UInt32? = nil,
      clusterRevision: UInt16? = nil
    ) {
      self._clusterAttr = .init(
        wrappedValue: clusterAttr,
        isSupported: attributeList?.contains(0x01) ?? false,
        isNullable: false
      )
      self._generatedCommandList = .init(
        wrappedValue: generatedCommandList,
        isSupported: attributeList?.contains(0x0FFF8) ?? false,
        isNullable: false
      )
      self._acceptedCommandList = .init(
        wrappedValue: acceptedCommandList,
        isSupported: attributeList?.contains(0x0FFF9) ?? false,
        isNullable: false
      )
      self._attributeList = .init(
        wrappedValue: attributeList,
        isSupported: attributeList?.contains(0x0FFFB) ?? false,
        isNullable: false
      )
      self._featureMap = .init(
        wrappedValue: featureMap,
        isSupported: attributeList?.contains(0x0FFFC) ?? false,
        isNullable: false
      )
      self._clusterRevision = .init(
        wrappedValue: clusterRevision,
        isSupported: attributeList?.contains(0x0FFFD) ?? false,
        isNullable: false
      )
    }

    fileprivate init(decoder: TraitDecoder) throws {
      let decodedAttributeList: [UInt32] = try decoder.decodeOptionalArray(tag: 0x0FFFB) ?? []
      var generatedAttributeList = [UInt32]()
      generatedAttributeList.append(0x0FFFB)

      let clusterAttrValue: UInt16? = try decoder.decodeOptional(tag: 0x01)
      let clusterAttrIsSupported = clusterAttrValue != nil
      if clusterAttrIsSupported {
        generatedAttributeList.append(0x01)
      }
      self._clusterAttr = .init(
        wrappedValue: clusterAttrIsSupported ? clusterAttrValue : nil,
        isSupported: clusterAttrIsSupported,
        isNullable: false
      )

      let generatedCommandListValue: [UInt32]? = try decoder.decodeOptionalArray(tag: 0x0FFF8)
      let generatedCommandListIsSupported = generatedCommandListValue != nil
      if generatedCommandListIsSupported {
        generatedAttributeList.append(0x0FFF8)
      }
      self._generatedCommandList = .init(
        wrappedValue: generatedCommandListIsSupported ? generatedCommandListValue : nil,
        isSupported: generatedCommandListIsSupported,
        isNullable: false
      )

      let acceptedCommandListValue: [UInt32]? = try decoder.decodeOptionalArray(tag: 0x0FFF9)
      let acceptedCommandListIsSupported = acceptedCommandListValue != nil
      if acceptedCommandListIsSupported {
        generatedAttributeList.append(0x0FFF9)
      }
      self._acceptedCommandList = .init(
        wrappedValue: acceptedCommandListIsSupported ? acceptedCommandListValue : nil,
        isSupported: acceptedCommandListIsSupported,
        isNullable: false
      )

      let featureMapValue: UInt32? = try decoder.decodeOptional(tag: 0x0FFFC)
      let featureMapIsSupported = featureMapValue != nil
      if featureMapIsSupported {
        generatedAttributeList.append(0x0FFFC)
      }
      self._featureMap = .init(
        wrappedValue: featureMapIsSupported ? featureMapValue : nil,
        isSupported: featureMapIsSupported,
        isNullable: false
      )

      let clusterRevisionValue: UInt16? = try decoder.decodeOptional(tag: 0x0FFFD)
      let clusterRevisionIsSupported = clusterRevisionValue != nil
      if clusterRevisionIsSupported {
        generatedAttributeList.append(0x0FFFD)
      }
      self._clusterRevision = .init(
        wrappedValue: clusterRevisionIsSupported ? clusterRevisionValue : nil,
        isSupported: clusterRevisionIsSupported,
        isNullable: false
      )

      self._attributeList = .init(
        wrappedValue: generatedAttributeList,
        isSupported: true,
        isNullable: false
      )
    }

    fileprivate func apply(_ update: MyCompany.MyCustomTrait.MutableAttributes) -> Self {
      let clusterAttrValue = update.clusterAttrIsSet ? update.clusterAttr : self.clusterAttr
      let generatedCommandListValue = self.generatedCommandList
      let acceptedCommandListValue = self.acceptedCommandList
      let attributeListValue = self.attributeList
      let featureMapValue = self.featureMap
      let clusterRevisionValue = self.clusterRevision
      return MyCompany.MyCustomTrait.Attributes(
        clusterAttr: clusterAttrValue,
        generatedCommandList: generatedCommandListValue,
        acceptedCommandList: acceptedCommandListValue,
        attributeList: attributeListValue,
        featureMap: featureMapValue,
        clusterRevision: clusterRevisionValue
      )
    }

  }
}

extension MyCompany.MyCustomTrait.Attributes: TraitEncodable {
  public static var identifier: String { MyCompany.MyCustomTrait.identifier }

  public func encode(with encoder: TraitEncoder) throws {
    try encoder.encode(tag: 0x01, value: self.clusterAttr)
    try encoder.encode(tag: 0x0FFF8, value: self.generatedCommandList)
    try encoder.encode(tag: 0x0FFF9, value: self.acceptedCommandList)
    try encoder.encode(tag: 0x0FFFB, value: self.attributeList)
    try encoder.encode(tag: 0x0FFFC, value: self.featureMap)
    try encoder.encode(tag: 0x0FFFD, value: self.clusterRevision)
  }
}

// MARK: - Hashable & Equatable

extension MyCompany.MyCustomTrait: Hashable {
  public static func ==(lhs: MyCompany.MyCustomTrait, rhs: MyCompany.MyCustomTrait) -> Bool {
    return lhs.identifier == rhs.identifier
      && lhs.attributes == rhs.attributes
      && lhs.metadata == rhs.metadata
  }

  public func hash(into hasher: inout Hasher) {
    hasher.combine(identifier)
    hasher.combine(attributes)
    hasher.combine(metadata)
  }
}

extension MyCompany.MyCustomTrait.Attributes: Hashable {
  public static func ==(lhs: MyCompany.MyCustomTrait.Attributes, rhs: MyCompany.MyCustomTrait.Attributes) -> Bool {
    var result = true
    result = lhs.clusterAttr == rhs.clusterAttr && result
    result = lhs.generatedCommandList == rhs.generatedCommandList && result
    result = lhs.acceptedCommandList == rhs.acceptedCommandList && result
    result = lhs.attributeList == rhs.attributeList && result
    result = lhs.featureMap == rhs.featureMap && result
    result = lhs.clusterRevision == rhs.clusterRevision && result
    return result
  }

  public func hash(into hasher: inout Hasher) {
    hasher.combine(self.clusterAttr)
    hasher.combine(self.generatedCommandList)
    hasher.combine(self.acceptedCommandList)
    hasher.combine(self.attributeList)
    hasher.combine(self.featureMap)
    hasher.combine(self.clusterRevision)
  }
}

// MARK: - MutableAttributes

extension MyCompany.MyCustomTrait {

  public final class MutableAttributes: TraitEncodable {
    public static let identifier: String = MyCompany.MyCustomTrait.identifier
    private let baseAttributes: Attributes

    fileprivate var clusterAttr: UInt16?
    private(set) public var clusterAttrIsSet = false
    public func setClusterAttr(_ value: UInt16) {
      self.clusterAttr = value
      self.clusterAttrIsSet = true
    }
    public func clearClusterAttr() {
      self.clusterAttr = nil
      self.clusterAttrIsSet = false
    }

    internal init(attributes: MyCompany.MyCustomTrait.Attributes) {
      self.baseAttributes = attributes
    }

    public func encode(with encoder: TraitEncoder) throws {
      // MutableAttributes is encoded individually, e.g. through update(...),
      // therefore uddm wrapping needs to be applied.
      encoder.wrapPayload(namespace: Self.identifier.namespace)
      if self.clusterAttrIsSet {
        try encoder.encode(tag: 0x01, value: self.clusterAttr)
      }
    }
  }
}

// MARK: - Attributes definitions

extension MyCompany.MyCustomTrait {
  public enum Attribute: UInt32, Field, CaseIterable {
    case clusterAttr = 1
    case generatedCommandList = 65528
    case acceptedCommandList = 65529
    case attributeList = 65531
    case featureMap = 65532
    case clusterRevision = 65533

    public var id: UInt32 {
      self.rawValue
    }

    public var type: GoogleHomeSDK.FieldType {
      switch self {
        case .clusterAttr:
          return .uint16
        case .generatedCommandList:
          return .uint32
        case .acceptedCommandList:
          return .uint32
        case .attributeList:
          return .uint32
        case .featureMap:
          return .uint32
        case .clusterRevision:
          return .uint16
      }
    }
  }

  public static func attribute(id: UInt32) -> (any Field)? {
    return Attribute(rawValue: id)
  }
}

// MARK: - Attribute fieldSelect definitions

extension TypedReference where T == MyCompany.MyCustomTrait {
  public var clusterAttr: TypedExpression<UInt16> {
    fieldSelect(from: self, selectedField: T.Attribute.clusterAttr)
  }
  public var generatedCommandList: TypedExpression<[UInt32]> {
    fieldSelect(from: self, selectedField: T.Attribute.generatedCommandList)
  }
  public var acceptedCommandList: TypedExpression<[UInt32]> {
    fieldSelect(from: self, selectedField: T.Attribute.acceptedCommandList)
  }
  public var attributeList: TypedExpression<[UInt32]> {
    fieldSelect(from: self, selectedField: T.Attribute.attributeList)
  }
  public var featureMap: TypedExpression<UInt32> {
    fieldSelect(from: self, selectedField: T.Attribute.featureMap)
  }
  public var clusterRevision: TypedExpression<UInt16> {
    fieldSelect(from: self, selectedField: T.Attribute.clusterRevision)
  }
}

extension Updater where T == MyCompany.MyCustomTrait {
  public func setClusterAttr(_ value: UInt16) {
    self.set(Parameter(field: T.Attribute.clusterAttr, value: value))
  }
}


// MARK: - Struct Fields definitions

  
```

### MyCompany.swift

```swift
/// Namespace for all MyCompany Traits and DeviceTypes.
public enum MyCompany { }

```