or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration.mdcore-api.mdhap-integration.mdindex.mdlogging.mdplatform-accessories.mdplugin-system.md
tile.json

platform-accessories.mddocs/

Platform Accessories

Platform Accessories provide a persistent wrapper system around HAP-NodeJS accessories, enabling caching, context storage, and lifecycle management for HomeKit accessories. They are primarily used with Dynamic Platform plugins to maintain accessory state and configuration across Homebridge restarts.

Capabilities

PlatformAccessory Class

The main wrapper class for managing HomeKit accessories with persistence and caching.

/**
 * Wrapper class for HAP-NodeJS accessories with persistence and caching
 * Provides context storage and automatic restoration across restarts
 */
class PlatformAccessory extends EventEmitter {
  /** 
   * Create a new platform accessory
   * @param displayName - Human-readable name for the accessory
   * @param uuid - Unique identifier for the accessory
   * @param category - HomeKit category (optional, defaults to OTHER)
   */
  constructor(displayName: string, uuid: string, category?: Categories);
  
  /** Current display name of the accessory */
  readonly displayName: string;
  /** Unique identifier for the accessory */
  readonly UUID: string;
  /** HomeKit category for the accessory */
  readonly category: Categories;
  /** Array of services attached to this accessory */
  readonly services: Service[];
  /** Plugin-specific context storage (persisted to disk) */
  context: UnknownContext;
  /** @deprecated Use configureController instead */
  readonly reachable: boolean;
  
  /** Update the display name of the accessory */
  updateDisplayName(displayName: string): void;
  
  /** Add a service to the accessory */
  addService(service: Service): Service;
  /** Add a service by type to the accessory */
  addService(serviceType: typeof Service, displayName?: string, subtype?: string): Service;
  
  /** Remove a service from the accessory */
  removeService(service: Service): void;
  
  /** Get a service by name or type */
  getService(name: string | typeof Service): Service | undefined;
  /** Get a service by name/type and subtype */
  getServiceById(name: string | typeof Service, subtype: string): Service | undefined;
  /** @deprecated Use getServiceById instead */
  getServiceByUUIDAndSubType(uuid: string, subtype: string): Service | undefined;
  
  /** @deprecated Use removeService instead */
  updateReachability(reachable: boolean): void;
  /** @deprecated Use configureController instead */
  configureCameraSource(cameraSource: CameraStreamingDelegate): void;
  
  /** Configure a controller for the accessory */
  configureController(controller: Controller): void;
  /** Remove a controller from the accessory */
  removeController(controller: Controller): void;
  
  /** Listen for identification requests */
  on(event: "identify", listener: () => void): this;
}

Usage Examples:

import { DynamicPlatformPlugin, PlatformAccessory, Logger, PlatformConfig, API } from "homebridge";

class MyPlatform implements DynamicPlatformPlugin {
  private accessories: PlatformAccessory[] = [];

  constructor(private log: Logger, private config: PlatformConfig, private api: API) {
    this.api.on("didFinishLaunching", () => {
      this.createAccessories();
    });
  }

  configureAccessory(accessory: PlatformAccessory) {
    this.log.info("Restoring accessory from cache:", accessory.displayName);
    
    // Restore context data
    const deviceId = accessory.context.deviceId;
    const lastState = accessory.context.lastKnownState;
    
    // Set up services and event handlers
    this.setupAccessoryServices(accessory);
    this.accessories.push(accessory);
  }

  private createAccessories() {
    // Create new accessory
    const uuid = this.api.hap.uuid.generate("unique-device-id");
    const accessory = new this.api.platformAccessory("My Device", uuid, this.api.hap.Categories.LIGHTBULB);
    
    // Store context data (persisted automatically)
    accessory.context.deviceId = "device-123";
    accessory.context.lastKnownState = { on: false, brightness: 100 };
    accessory.context.configurationOptions = { pollingInterval: 5000 };
    
    // Set up services
    this.setupAccessoryServices(accessory);
    
    // Register with Homebridge for caching
    this.api.registerPlatformAccessories("my-plugin", "MyPlatform", [accessory]);
    this.accessories.push(accessory);
  }

  private setupAccessoryServices(accessory: PlatformAccessory) {
    // Add or get existing service
    let service = accessory.getService(this.api.hap.Service.Lightbulb);
    if (!service) {
      service = accessory.addService(this.api.hap.Service.Lightbulb, accessory.displayName);
    }
    
    // Set up characteristics
    service.getCharacteristic(this.api.hap.Characteristic.On)
      .onGet(() => this.getState(accessory))
      .onSet((value) => this.setState(accessory, value));
      
    service.getCharacteristic(this.api.hap.Characteristic.Brightness)
      .onGet(() => this.getBrightness(accessory))
      .onSet((value) => this.setBrightness(accessory, value));
    
    // Handle identify requests
    accessory.on("identify", () => {
      this.log.info("Identify requested for:", accessory.displayName);
      this.identifyDevice(accessory.context.deviceId);
    });
  }

  private async getState(accessory: PlatformAccessory): Promise<boolean> {
    const state = accessory.context.lastKnownState?.on || false;
    this.log.debug("Getting state for", accessory.displayName, ":", state);
    return state;
  }

  private async setState(accessory: PlatformAccessory, value: boolean) {
    this.log.info("Setting", accessory.displayName, "to", value);
    
    // Update context (automatically persisted)
    accessory.context.lastKnownState = {
      ...accessory.context.lastKnownState,
      on: value
    };
    
    // Control actual device
    await this.controlDevice(accessory.context.deviceId, { on: value });
  }
}

Platform Accessory Events

Events emitted by platform accessories.

/**
 * Events emitted by platform accessories
 */
enum PlatformAccessoryEvent {
  /** Emitted when HomeKit requests accessory identification */
  IDENTIFY = "identify"
}

Usage Examples:

// Listen for identify requests
accessory.on(PlatformAccessoryEvent.IDENTIFY, () => {
  console.log("User requested identification of:", accessory.displayName);
  // Flash lights, beep, or other identification behavior
  this.identifyDevice(accessory.context.deviceId);
});

// Alternative using string literal
accessory.on("identify", () => {
  this.log.info("Identify requested");
});

Service Management

Methods for managing services on platform accessories.

/**
 * Service management methods
 * Services define the capabilities exposed to HomeKit
 */
interface ServiceManagement {
  /** Add a pre-configured service instance */
  addService(service: Service): Service;
  /** Add a service by type with optional display name and subtype */
  addService(serviceType: typeof Service, displayName?: string, subtype?: string): Service;
  /** Remove a service from the accessory */
  removeService(service: Service): void;
  /** Find a service by name or constructor */
  getService(name: string | typeof Service): Service | undefined;
  /** Find a service by name/constructor and subtype */
  getServiceById(name: string | typeof Service, subtype: string): Service | undefined;
}

Usage Examples:

// Add services by type
const lightService = accessory.addService(this.api.hap.Service.Lightbulb, "Main Light");
const infoService = accessory.addService(this.api.hap.Service.AccessoryInformation);

// Add multiple services of the same type with subtypes
const bedroom = accessory.addService(this.api.hap.Service.Lightbulb, "Bedroom Light", "bedroom");
const kitchen = accessory.addService(this.api.hap.Service.Lightbulb, "Kitchen Light", "kitchen");

// Get existing services
let service = accessory.getService(this.api.hap.Service.Lightbulb);
if (!service) {
  service = accessory.addService(this.api.hap.Service.Lightbulb);
}

// Get service by subtype
const bedroomLight = accessory.getServiceById(this.api.hap.Service.Lightbulb, "bedroom");

// Remove services
const oldService = accessory.getService(this.api.hap.Service.Fan);
if (oldService) {
  accessory.removeService(oldService);
}

Controller Management

Methods for managing controllers on platform accessories.

/**
 * Controller management methods
 * Controllers provide advanced functionality like camera streaming
 */
interface ControllerManagement {
  /** Add a controller to the accessory */
  configureController(controller: Controller): void;
  /** Remove a controller from the accessory */
  removeController(controller: Controller): void;
}

Usage Examples:

import { CameraController, DoorbellController } from "hap-nodejs";

// Configure camera controller
const cameraController = new CameraController({
  cameraStreamCount: 2,
  delegate: this.cameraDelegate,
  streamingOptions: {
    supportedCryptoSuites: [this.api.hap.SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80],
    video: {
      resolutions: [
        [320, 180, 30],
        [320, 240, 15],
        [1280, 720, 30],
        [1280, 960, 30],
        [1920, 1080, 30]
      ],
      codec: {
        profiles: [this.api.hap.H264Profile.BASELINE, this.api.hap.H264Profile.MAIN],
        levels: [this.api.hap.H264Level.LEVEL3_1, this.api.hap.H264Level.LEVEL3_2]
      }
    },
    audio: {
      twoWayAudio: false,
      codecs: [
        {
          type: this.api.hap.AudioStreamingCodecType.AAC_ELD,
          samplerate: this.api.hap.AudioStreamingSamplerate.KHZ_16
        }
      ]
    }
  }
});

accessory.configureController(cameraController);

// Configure doorbell controller
const doorbellController = new DoorbellController({
  name: "Front Door",
  manufacturer: "My Company",
  model: "Doorbell v1",
  firmwareRevision: "1.0.0",
  serialNumber: "DB123456"
});

accessory.configureController(doorbellController);

Context Storage

Persistent storage for plugin-specific data.

/**
 * Plugin-specific context storage
 * Automatically persisted to disk and restored on restart
 */
type UnknownContext = Record<string, any>;

Usage Examples:

// Store device configuration
accessory.context.deviceConfig = {
  ipAddress: "192.168.1.100",
  port: 8080,
  protocol: "http"
};

// Store state information
accessory.context.lastKnownState = {
  on: true,
  brightness: 75,
  hue: 120,
  saturation: 100
};

// Store plugin settings
accessory.context.pluginSettings = {
  pollingInterval: 5000,
  enableLogging: true,
  temperatureUnit: "celsius"
};

// Store device metadata
accessory.context.deviceInfo = {
  manufacturer: "ACME Corp",
  model: "Smart Light v2",
  firmwareVersion: "2.1.4",
  macAddress: "aa:bb:cc:dd:ee:ff"
};

// Access stored context
const deviceId = accessory.context.deviceConfig?.ipAddress;
const lastState = accessory.context.lastKnownState?.on || false;

// Update context (changes are automatically persisted)
accessory.context.lastKnownState.brightness = 50;

Types

type UnknownContext = Record<string, any>;

interface BridgeConfiguration {
  name: string;
  username: string;
  pin: string;
  advertiser?: MDNSAdvertiser;
  port?: number;
  bind?: string | string[];
  setupID?: string;
  manufacturer?: string;
  model?: string;
  disableIpc?: boolean;
  firmwareRevision?: string;
  serialNumber?: string;
}