Matterbridge

repository·main·Indexed 21 days ago

https://github.com/luligu/matterbridge

A Matter plugin manager that creates a Matter bridge device, allowing users to integrate various smart home devices into Matter-compatible ecosystems like Apple Home, Google Home, or Home Assistant without a dedicated hub. It supports both Bridge and Childbridge modes, plugin management via a web frontend or CLI, and provides tools for commissioning, SSL configuration, and multi-controller pairing.

Tokens
52.9K
Snippets
127
Records
197
Agent score
68%

What's inside matterbridge

  1. Create composed device types

    main

    Certain device types are considered 'composed devices'. Instead of being a single unit, they require you to create the main device and then explicitly add sub-components (like cabinets, surfaces, or parts) using specific methods.

    Oven Create an Oven and use .addCabinet(name, tags[]) to add cabinets.

    Cooktop Create a Cooktop and use .addSurface(name, tags[]) to add surfaces.

    Refrigerator Create a Refrigerator and use .addCabinet(name, tags[]) to add cabinets.

    Note: When adding components, you must provide an array of objects containing mfgCode, namespaceId, tag, and label.

    // Oven Example
    const oven = new Oven('Oven', 'OV1234567890');
    oven.addCabinet('Upper Cabinet', [{ mfgCode: null, namespaceId: PositionTag.Top.namespaceId, tag: PositionTag.Top.tag, label: PositionTag.Top.label }]);
    
    // Cooktop Example
    const cooktop = new Cooktop('Cooktop', 'CT1234567890');
    cooktop.addSurface('Surface Top Left', [
      { mfgCode: null, namespaceId: PositionTag.Top.namespaceId, tag: PositionTag.Top.tag, label: PositionTag.Top.label },
      { mfgCode: null, namespaceId: PositionTag.Left.namespaceId, tag: PositionTag.Left.tag, label: PositionTag.Left.label },
    ]);
    
    // Refrigerator Example
    const refrigerator = new Refrigerator('Refrigerator', 'RE1234567890');
    refrigerator.addCabinet('Refrigerator Top', [
      { mfgCode: null, namespaceId: PositionTag.Top.namespaceId, tag: PositionTag.Top.tag, label: 'Refrigerator Top' },
      { mfgCode: null, namespaceId: RefrigeratorTag.Refrigerator.namespaceId, tag: RefrigeratorTag.Refrigerator.tag, label: RefrigeratorTag.Refrigerator.label },
    ]);
  2. TypeScript and Naming Conventions

    main

    Follow these conventions when writing TypeScript code for Matterbridge:

    TypeScript Rules:

    • Use strict typing. Avoid any unless preceded by a comment: // intentional any: reason.
    • Use readonly or as const for constant structures and lookup tables.
    • Use type guards for narrowing instead of type assertions (as X).
    • Prefer enums or literal unions over magic numbers.

    Naming Rules:

    • Functions: Use a verb or verb phrase (e.g., createDevice, updateState).
    • Booleans: Prefix with is, has, can, or should (except for specific state flags like intervalOnOff).
    • Constants: Use UPPER_SNAKE_CASE only for process environment variables or true constants; otherwise, use camelCase.
    • Private Helpers: Only prefix file-local helpers with _ if they are intentionally unused (to silence the linter).
  3. Avoid installing matterbridge as a dependency in plugins

    main

    When developing Matterbridge plugins, do not include matterbridge or @matter as a dependency, devDependency, or peerDependency in your package.json.

    Why this is required

    1. Singleton Requirement: There must be exactly one instance of matterbridge and matter.js in the entire node_modules tree.
    2. Module Resolution Conflict: Matterbridge loads plugins as ESM modules using dynamic import(). Node.js module resolution prioritizes a plugin's local node_modules over Matterbridge's node_modules. If a plugin contains its own version of matterbridge, it will use that version instead of the host's version, causing runtime errors and breaking the plugin system.

    Best Practices

    • Production Publishing: Always publish plugins for production with devDependencies removed to prevent accidental installation of unnecessary packages.
    • Dependency Locking: Use npm shrinkwrap to lock direct dependencies, ensuring the user's environment matches your development environment exactly.
  4. Handle errors and validate inputs

    main

    Matterbridge follows a 'fail fast' approach with descriptive error messages.

    Validation Rules:

    • Never trust external inputs: Always validate parameters coming from device or network events using isValid... matterbridge functions.
    • Numeric Input: Reject invalid numeric input using Number.isFinite(n). Use Math.min and Math.max to clamp values.
    • Decoding: When decoding device values, always guard against null or undefined before performing mathematical operations.

    Error Strategy:

    • Throw errors only for programmer errors or configuration errors.
    • Do not throw for transient sensor states.
    • For non-critical sensor errors: Prefer returning 0 or an empty array and logging the issue at the debug level.
  5. How Matterbridge handles concurrency and threading

    main

    Matterbridge is evolving toward a multi-threaded architecture to provide real concurrency outside the Node.js main loop, memory optimization, and thread isolation.

    Key architectural features include:

    • Isolation: Individual plugins can run in childbridge mode, providing isolation between threads.
    • Hot Updates: In childbridge mode, plugins can be updated without restarting the main Matterbridge process.
    • Thread Management: The CLI acts as the threads manager.

    Currently, the following tasks run as workers (threads):

    • Update checks (system and Docker)
    • System checks
    • Spawning and archiving commands
    • Global node_modules directory checks

    Future releases will move the following into threads:

    • The main matterbridge process
    • The frontend
    • All plugins (in both bridge and childbridge modes)
  6. Understand Dev Container networking limitations

    main

    Networking behavior in the Dev Container depends on your Host OS and Docker setup:

    Docker Desktop (Windows/macOS)

    Because Docker runs inside a VM, Host networking mode is NOT available. To achieve local network functionality, use the following strategies:

    • Standard Dev Container: Use this for pairing with a Home Assistant instance running in Docker Compose on the same host. mDNS and local/remote network access work normally within the containers.
    • mDNS Reflector: If you need to pair with a controller on your actual local physical network (LAN), you must use the Matterbridge mDNS Reflector alongside the Dev Container system.

    Native Linux or WSL 2 (with Docker Engine CLI integration)

    • Host networking IS available using the --network=host flag.
    • Full local network access is supported, including pairing and mDNS.
  7. Understand the Matterbridge package architecture and dependencies

    main

    Matterbridge is organized into a layered, acyclic dependency graph. Understanding this hierarchy helps in identifying which packages are foundational and which are high-level entry points.

    • Foundation: @matterbridge/types and @matterbridge/utils have no internal dependencies.
    • Networking & Workers: @matterbridge/dgram and @matterbridge/thread depend on the foundation.
    • Test Helpers: @matterbridge/jest-utils and @matterbridge/vitest-utils depend on the foundation.
    • Core: @matterbridge/core is the central orchestrator, depending on dgram, thread, utils, and types.
    • Top Level: The matterbridge package is the primary entry point, declaring all scoped packages as direct dependencies.
  8. Run Matterbridge in bridge or childbridge mode

    main

    Matterbridge can be operated in two distinct modes via CLI flags:

    Bridge Mode (--bridge)

    In this mode, Matterbridge exposes itself as a single Matter device. You pair the bridge once by scanning the QR code shown in the frontend or console, and it will then load all registered plugins under that single bridge.

    Childbridge Mode (--childbridge)

    In this mode, Matterbridge exposes each registered plugin as an individual Matter device. You must pair each plugin separately by scanning their respective QR codes.

    Use matterbridge --help to view all available command line syntax.

    # Force bridge mode
    matterbridge --bridge
    
    # Force childbridge mode
    matterbridge --childbridge
  9. How to use client clusters and MatterbridgeBindingServer

    main

    Some devices act as controllers (clients) that consume clusters implemented on remote endpoints. Matterbridge handles this via MatterbridgeBindingServer.

    Option 1: Explicitly adding client clusters

    If you know the specific cluster IDs required, use createDefaultBindingClusterServer(clientList). This registers the IDs in the Binding cluster's clientList and the Descriptor cluster's clientList.

    import { ClosureControl } from '@matter/types/clusters/closure-control';
    import { closureController } from 'matterbridge';
    
    const device = new MatterbridgeEndpoint(closureController, { id: 'MyClosureController' })
      .createDefaultBindingClusterServer([ClosureControl.id])
      .addRequiredClusters();
    
    await this.registerDevice(device);

    Option 2: Automatic client clusters from Device Type

    If your DeviceTypeDefinition includes requiredClientClusters or optionalClientClusters, you can populate the binding automatically:

    import { closureController } from 'matterbridge';
    
    const device = new MatterbridgeEndpoint(closureController, { id: 'MyClosureController' })
      .addRequiredClusterServers()
      .addRequiredClusterClients() // Automatically adds clusters required by the definition
      .addOptionalClusterClients(); // Automatically adds optional clusters from the definition
    
    await this.registerDevice(device);
    import { ClosureControl } from '@matter/types/clusters/closure-control';
    import { closureController } from 'matterbridge';
    
    const device = new MatterbridgeEndpoint(closureController, { id: 'MyClosureController' })
      .createDefaultBindingClusterServer([ClosureControl.id]) // advertises ClosureControl as a client cluster
      .addRequiredClusters();
    
    await this.registerDevice(device);
  10. Understand Reportable (P quality) attributes in Matter

    main
    In the context of Matter clusters, Quality P stands for Reportable. An attribute with 'P quality' is intended to support interval or change reporting. This allows the device to automatically report attribute changes to the controller based on specific intervals or change thresholds, rather than requiring the controller to poll for updates.
  11. Understand Matter.js 0.17.x Attribute types

    main

    When working with clusters in Matter.js 0.17.x, use the following distinction for attribute types:

    • PowerSource.Attributes: The flattened full superset of all attributes (not feature-specific).
    • PowerSource.BaseAttributes: Attributes that are always present.
    • PowerSource.WiredAttributes: Attributes specific only to the Wired feature.
    • PowerSource.BaseAttributes & PowerSource.WiredAttributes: The correct type for a power source that exposes the Wired feature.
  12. How Bun runtime support works in Matterbridge

    main

    Matterbridge detects the Bun runtime using isBun() and switches package management and path resolution logic accordingly. Key features implemented for Bun include:

    • Global Package Resolution: bun link is used to register the local build as the global matterbridge package, allowing plugins to resolve import 'matterbridge' correctly.
    • Global Module Discovery: getGlobalNodeModules() resolves to the Bun global modules directory (derived from $BUN_INSTALL or ~/.bun) instead of npm root -g.
    • Plugin Management: PluginManager resolves plugins from the Bun global modules directory. Plugin installation, uninstallation, and Matterbridge updates use bun commands instead of npm.
    • Docker Recovery: Auto-reinstallation during Docker recreate uses bun if the runtime is detected as Bun.
    • Local Plugins: When running on Bun, the --add command for local plugins skips the npm link matterbridge step because bun link already provides the necessary resolution.