UNPKG

homebridge

Version:
457 lines 23.1 kB
/** * Matter Types for Homebridge Plugin API * * This module provides types and interfaces for plugin developers * to create Matter-compatible accessories. */ /** * Optimized Matter.js Device and Cluster Imports * * Imports Matter.js devices and clusters from individual files instead of barrel exports, * which dramatically reduces startup time. * * Why this matters: * - Barrel import: `import * as devices from '@matter/main/devices'` loads ALL 186+ exports (~800ms) * - Individual imports: Only loads the 27 devices we actually use (~50-100ms) * - Result: 50-100x faster on powerful machines, even more improvement on Raspberry Pi * * This optimization is especially important for users on resource-constrained devices like * Raspberry Pi where the difference can be several minutes of startup time. */ // Direct imports from individual cluster files import { AirQuality } from '@matter/main/clusters/air-quality'; import { BooleanState } from '@matter/main/clusters/boolean-state'; import { CarbonMonoxideConcentrationMeasurement } from '@matter/main/clusters/carbon-monoxide-concentration-measurement'; import { ClosureControl } from '@matter/main/clusters/closure-control'; import { ColorControl } from '@matter/main/clusters/color-control'; import { DoorLock } from '@matter/main/clusters/door-lock'; import { ElectricalEnergyMeasurement } from '@matter/main/clusters/electrical-energy-measurement'; import { ElectricalPowerMeasurement } from '@matter/main/clusters/electrical-power-measurement'; import { FanControl } from '@matter/main/clusters/fan-control'; import { KeypadInput } from '@matter/main/clusters/keypad-input'; import { LevelControl } from '@matter/main/clusters/level-control'; import { MediaPlayback } from '@matter/main/clusters/media-playback'; import { NitrogenDioxideConcentrationMeasurement } from '@matter/main/clusters/nitrogen-dioxide-concentration-measurement'; import { OnOff } from '@matter/main/clusters/on-off'; import { OzoneConcentrationMeasurement } from '@matter/main/clusters/ozone-concentration-measurement'; import { Pm10ConcentrationMeasurement } from '@matter/main/clusters/pm10-concentration-measurement'; import { Pm25ConcentrationMeasurement } from '@matter/main/clusters/pm25-concentration-measurement'; import { PowerTopology } from '@matter/main/clusters/power-topology'; import { RvcOperationalState } from '@matter/main/clusters/rvc-operational-state'; import { Thermostat } from '@matter/main/clusters/thermostat'; import { ValveConfigurationAndControl } from '@matter/main/clusters/valve-configuration-and-control'; import { WindowCovering } from '@matter/main/clusters/window-covering'; // Direct imports from individual device files import { AirQualitySensorDevice } from '@matter/main/devices/air-quality-sensor'; import { BasicVideoPlayerDevice } from '@matter/main/devices/basic-video-player'; import { ClosureDevice, ClosureRequirements } from '@matter/main/devices/closure'; import { ColorTemperatureLightDevice } from '@matter/main/devices/color-temperature-light'; import { ContactSensorDevice } from '@matter/main/devices/contact-sensor'; import { DimmableLightDevice } from '@matter/main/devices/dimmable-light'; import { DimmablePlugInUnitDevice } from '@matter/main/devices/dimmable-plug-in-unit'; import { DoorLockDevice } from '@matter/main/devices/door-lock'; import { ExtendedColorLightDevice } from '@matter/main/devices/extended-color-light'; import { FanDevice } from '@matter/main/devices/fan'; import { GenericSwitchDevice, GenericSwitchRequirements } from '@matter/main/devices/generic-switch'; import { HumiditySensorDevice } from '@matter/main/devices/humidity-sensor'; import { LightSensorDevice } from '@matter/main/devices/light-sensor'; import { OccupancySensorDevice, OccupancySensorRequirements } from '@matter/main/devices/occupancy-sensor'; import { OnOffLightDevice } from '@matter/main/devices/on-off-light'; import { OnOffLightSwitchDevice } from '@matter/main/devices/on-off-light-switch'; import { OnOffPlugInUnitDevice } from '@matter/main/devices/on-off-plug-in-unit'; import { PumpDevice, PumpRequirements } from '@matter/main/devices/pump'; import { RoboticVacuumCleanerDevice, RoboticVacuumCleanerRequirements } from '@matter/main/devices/robotic-vacuum-cleaner'; import { RoomAirConditionerDevice, RoomAirConditionerRequirements } from '@matter/main/devices/room-air-conditioner'; import { SmokeCoAlarmDevice, SmokeCoAlarmRequirements } from '@matter/main/devices/smoke-co-alarm'; import { SpeakerDevice } from '@matter/main/devices/speaker'; import { TemperatureSensorDevice } from '@matter/main/devices/temperature-sensor'; import { ThermostatDevice, ThermostatRequirements } from '@matter/main/devices/thermostat'; import { WaterLeakDetectorDevice } from '@matter/main/devices/water-leak-detector'; import { WaterValveDevice, WaterValveRequirements } from '@matter/main/devices/water-valve'; import { WindowCoveringDevice, WindowCoveringRequirements } from '@matter/main/devices/window-covering'; import { BridgedNodeEndpoint } from '@matter/main/endpoints/bridged-node'; import { ElectricalSensorEndpoint, ElectricalSensorRequirements } from '@matter/main/endpoints/electrical-sensor'; import { DefaultClosureControlServer } from './behaviors/ClosureControlBehavior.js'; import { DefaultKeypadInputServer } from './behaviors/KeypadInputBehavior.js'; import { DefaultMediaPlaybackServer } from './behaviors/MediaPlaybackBehavior.js'; import { DefaultValveConfigurationAndControlServer } from './behaviors/ValveConfigurationAndControlBehavior.js'; // Note: the canonical MatterServerEvents declaration is at the bottom of this file. // A second declaration here is unnecessary; TypeScript would merge it silently and // the comment ("Currently empty - all events removed") was misleading because the // file's other declaration adds two events. /** * Matter Accessory Event Types * * Events that can be emitted by Matter accessories during their lifecycle. * * @example * ```typescript * Listen for when a Matter accessory is ready * const accessory: MatterAccessory = { ... }; * api.matter?.publishExternalAccessories('plugin-name', [accessory]); * * const internal = accessory as any; * internal._eventEmitter?.on(MatterAccessoryEventTypes.READY, (port: number) => { * console.log(`Accessory ready on port ${port}`); * }); * ``` * * @group Matter Accessory */ export var MatterAccessoryEventTypes; (function (MatterAccessoryEventTypes) { /** * Emitted when the Matter server is ready and the accessory is available on the network. * This is the main event to listen for to know when an external accessory is ready. * * **HAP Equivalent:** `AccessoryEventTypes.ADVERTISED` * * @param port - The port number the Matter server is listening on */ MatterAccessoryEventTypes["READY"] = "ready"; })(MatterAccessoryEventTypes || (MatterAccessoryEventTypes = {})); /** * Matter error type enum (for error handler categorization) */ // Internal Matter error class hierarchy lives in `./MatterError.ts` so the // lightweight `ChildBridgeMatterMessageHandler` can `instanceof`-check the // routing sentinel without transitively loading this file's heavy // `@matter/*` runtime imports. Re-exported here so all existing consumers // importing from `./types.js` keep working. export { MatterAccessoryNotOnBridgeError, MatterCommissioningError, MatterDeviceError, MatterError, MatterErrorType, MatterNetworkError, MatterStorageError, } from './MatterError.js'; /** * Matter device types * * All supported Matter device types, imported from individual files for optimal performance. */ const devices = { AirQualitySensorDevice, ColorTemperatureLightDevice, ContactSensorDevice, DimmableLightDevice, DimmablePlugInUnitDevice, DoorLockDevice, ElectricalSensorEndpoint, ElectricalSensorRequirements, ExtendedColorLightDevice, FanDevice, GenericSwitchDevice, GenericSwitchRequirements, HumiditySensorDevice, LightSensorDevice, OccupancySensorDevice, OccupancySensorRequirements, OnOffLightDevice, OnOffLightSwitchDevice, OnOffPlugInUnitDevice, PumpDevice, PumpRequirements, RoboticVacuumCleanerDevice, RoboticVacuumCleanerRequirements, RoomAirConditionerDevice, RoomAirConditionerRequirements, SmokeCoAlarmDevice, SmokeCoAlarmRequirements, TemperatureSensorDevice, ThermostatDevice, ThermostatRequirements, WaterLeakDetectorDevice, SpeakerDevice, WaterValveDevice, WaterValveRequirements, WindowCoveringDevice, WindowCoveringRequirements, BasicVideoPlayerDevice, ClosureDevice, ClosureRequirements, }; /** * Matter cluster types * * All supported Matter cluster types, imported from individual files for optimal performance. */ const clusters = { AirQuality, BooleanState, CarbonMonoxideConcentrationMeasurement, ClosureControl, ColorControl, DoorLock, ElectricalEnergyMeasurement, ElectricalPowerMeasurement, FanControl, KeypadInput, LevelControl, MediaPlayback, NitrogenDioxideConcentrationMeasurement, OnOff, OzoneConcentrationMeasurement, Pm10ConcentrationMeasurement, Pm25ConcentrationMeasurement, PowerTopology, RvcOperationalState, Thermostat, ValveConfigurationAndControl, WindowCovering, }; // Export Matter.js clusters and devices for direct access // Note: types.ts is only imported by MatterServer, MatterBridgeManager, etc. // which are themselves lazy-loaded, so these imports only happen when Matter is used export { clusters, devices }; /** * Friendly device type names for the Plugin API * Maps simplified names to actual Matter.js device types */ export const deviceTypes = { // Lighting OnOffLight: devices.OnOffLightDevice, DimmableLight: devices.DimmableLightDevice, ColorTemperatureLight: devices.ColorTemperatureLightDevice, ExtendedColorLight: devices.ExtendedColorLightDevice, // Switches & Outlets OnOffSwitch: devices.OnOffLightSwitchDevice, OnOffOutlet: devices.OnOffPlugInUnitDevice, DimmableOutlet: devices.DimmablePlugInUnitDevice, // Sensors AirQualitySensor: devices.AirQualitySensorDevice, TemperatureSensor: devices.TemperatureSensorDevice, HumiditySensor: devices.HumiditySensorDevice, LightSensor: devices.LightSensorDevice, // OccupancySensing is not part of the base device type — matter.js requires the // detector-type feature to be chosen. OccupancyEvent additionally makes matter.js // emit the OccupancyChanged event automatically when the occupancy state changes. MotionSensor: devices.OccupancySensorDevice.with(devices.OccupancySensorRequirements.OccupancySensingServer.with('PassiveInfrared', 'OccupancyEvent')), ContactSensor: devices.ContactSensorDevice, LeakSensor: devices.WaterLeakDetectorDevice, // SmokeCoAlarm is not part of the base device type — matter.js requires the // SmokeAlarm/CoAlarm features to be chosen. They are auto-detected from the // accessory's declared cluster attributes at registration // (see applySmokeCoAlarmFeatures in serverHelpers.ts). SmokeSensor: devices.SmokeCoAlarmDevice, // Standalone electrical sensor (power/energy metering, e.g. a solar or // whole-home meter). Its clusters are feature-gated in matter.js, so the // PowerTopology / ElectricalPowerMeasurement / ElectricalEnergyMeasurement // servers are auto-detected from the accessory's declared cluster attributes // at registration (see applyElectricalMeasurementClusters in serverHelpers.ts). // The same detection also runs for outlets, so an OnOffOutlet that declares // electricalPowerMeasurement / electricalEnergyMeasurement state gets these // clusters too — no separate device type needed for a metering smart plug. ElectricalSensor: devices.ElectricalSensorEndpoint, // HVAC // The Thermostat cluster is feature-gated in matter.js, so the base // ThermostatDevice carries no thermostat cluster. It is added at registration // with the features detected from the accessory's declared setpoints — a // heating-only accessory gets Heating alone rather than being forced to claim // cooling it cannot do (see applyThermostatFeatures in serverHelpers.ts). // Compose the cluster yourself with `.with(...)` to override the detection. Thermostat: devices.ThermostatDevice, Fan: devices.FanDevice, // Security DoorLock: devices.DoorLockDevice, // Window Coverings (features will be auto-detected based on accessory attributes) WindowCovering: devices.WindowCoveringDevice, // Closures — garage doors, gates and similar. Until the Closures work landed // in the spec there was no such device type, and the usual stand-in was // WindowCovering, which drives the hardware correctly but presents as a blind. // // ⚠️ Apple Home does not support this type yet, so an accessory using it will // not appear there. It is exposed for plugin authors building against Google // or Alexa, and to have the groundwork done for whenever Apple catches up. // // ClosureControl is not part of the base device type — matter.js requires a // feature to be chosen, and without one the endpoint is built carrying only // Identify and any closure state a plugin supplies is silently dropped. // Positioning is the one that makes open/closed/part-open meaningful, which // is what a garage door or gate needs; compose your own with `.with(...)` to // add Speed, Ventilation, Pedestrian or the rest. // // matter.js's own ClosureControlServer leaves every command unimplemented, so // use ours: it records the move, and is replaced by the handler-calling // version when a plugin supplies closureControl handlers. Closure: devices.ClosureDevice.with(DefaultClosureControlServer.with('Positioning')), // Media — the name says video, but nothing in the device type is video-only: // it carries MediaPlayback (play/pause/stop), MediaInput and AudioOutput // (source selection), Channel, TargetNavigator and KeypadInput. An audio-only // device is a legitimate use of it. // // Volume and mute are NOT here. In Matter those live on a separate Speaker // endpoint (LevelControl + OnOff), composed alongside this one under a // BridgedNode — which is why both are exposed together. // // ⚠️ Apple Home does not support either type yet; see the note on Closure. // // MediaPlayback and KeypadInput are the device type's two mandatory command // clusters and matter.js implements neither, so the same substitution as // Closure applies - otherwise every play/pause/key press would fail. MediaPlayer: devices.BasicVideoPlayerDevice.with(DefaultMediaPlaybackServer, DefaultKeypadInputServer), Speaker: devices.SpeakerDevice, // Appliances // RVC optional clusters (RvcCleanMode, ServiceArea) are added dynamically in matterServer // based on whether they're defined in the accessory configuration RoboticVacuumCleaner: devices.RoboticVacuumCleanerDevice, // Water Valve // The matter.js base server leaves open/close unimplemented, so use our // default implementation that reflects the commands in the cluster state. // Accessories with valve handlers get HomebridgeValveConfigurationAndControlServer // (which extends the default) applied at registration. WaterValve: devices.WaterValveDevice.with(DefaultValveConfigurationAndControlServer), // Other // Switch is not part of the base device type — matter.js requires latching vs // momentary to be chosen. The momentary feature set matches the press/release // helpers in SwitchAPI: MomentarySwitchRelease enables shortRelease/longRelease, // MomentarySwitchLongPress enables long-press detection, and // MomentarySwitchMultiPress enables multiPressComplete sequences. GenericSwitch: devices.GenericSwitchDevice.with(devices.GenericSwitchRequirements.SwitchServer.with('MomentarySwitch', 'MomentarySwitchRelease', 'MomentarySwitchLongPress', 'MomentarySwitchMultiPress')), // PumpConfigurationAndControl is not part of the base device type — matter.js // requires a control-mode feature to be chosen. ConstantSpeed is the simplest // mode for on/off and level-controlled pumps bridged from HomeKit. Pump: devices.PumpDevice.with(devices.PumpRequirements.PumpConfigurationAndControlServer.with('ConstantSpeed')), // Thermostat is not part of the base device type — matter.js requires the // Heating/Cooling features to be chosen. Both are enabled (like the Thermostat // device type above) so heat-capable air conditioners work and cooling-only // accessories simply leave the heating attributes at their defaults. RoomAirConditioner: devices.RoomAirConditionerDevice.with(devices.RoomAirConditionerRequirements.ThermostatServer.with('Heating', 'Cooling')), // Composed device container — use as parent for accessories with parts. // Children appear as a single accessory in Apple Home, expandable into separate tiles. BridgedNode: BridgedNodeEndpoint, }; /** * The matter.js "requirements" behind the feature-gated device types above, * keyed to match {@link deviceTypes}. * * Several Matter clusters are feature-gated: matter.js will not compose them * until the features are chosen, which is why the entries in `deviceTypes` * above call `.with(...)` on these. Homebridge picks sensible features from the * state an accessory declares, and that is right almost always — but the * choices are not always derivable. * * The thermostat is the clearest case: declaring a heating and a cooling * setpoint gets AutoMode too, because that is what nearly every thermostat * wants. A device that genuinely heats and cools but has no auto mode has no * way to say so, and `HEAT` + `COOL` without `AUTO` is perfectly legal in the * spec. Exposing these lets a plugin compose the cluster itself: * * ```typescript * deviceType: api.matter.deviceTypes.Thermostat.with( * api.matter.deviceRequirements.Thermostat.ThermostatServer.with('Heating', 'Cooling'), * ) * ``` * * Homebridge leaves a device type alone when the plugin has already composed * the cluster, so the features above are used as given rather than detected. */ export const deviceRequirements = { MotionSensor: devices.OccupancySensorRequirements, SmokeSensor: devices.SmokeCoAlarmRequirements, ElectricalSensor: devices.ElectricalSensorRequirements, Thermostat: devices.ThermostatRequirements, Closure: devices.ClosureRequirements, RoboticVacuumCleaner: devices.RoboticVacuumCleanerRequirements, WaterValve: devices.WaterValveRequirements, GenericSwitch: devices.GenericSwitchRequirements, Pump: devices.PumpRequirements, RoomAirConditioner: devices.RoomAirConditionerRequirements, WindowCovering: devices.WindowCoveringRequirements, }; /** * Matter Cluster Names * Commonly used cluster names for type safety and autocomplete * Use these with api.updateMatterAccessoryState() and api.getAccessoryState() * * @example * ```typescript * With autocomplete and type safety: * api.updateMatterAccessoryState(uuid, api.matterClusterNames.OnOff, { onOff: true }) * api.getAccessoryState(uuid, api.matterClusterNames.LevelControl) * ``` */ export const clusterNames = { // Control Clusters OnOff: 'onOff', LevelControl: 'levelControl', ColorControl: 'colorControl', DoorLock: 'doorLock', WindowCovering: 'windowCovering', Thermostat: 'thermostat', FanControl: 'fanControl', // Sensor Clusters AirQuality: 'airQuality', CarbonMonoxideConcentrationMeasurement: 'carbonMonoxideConcentrationMeasurement', NitrogenDioxideConcentrationMeasurement: 'nitrogenDioxideConcentrationMeasurement', OzoneConcentrationMeasurement: 'ozoneConcentrationMeasurement', Pm10ConcentrationMeasurement: 'pm10ConcentrationMeasurement', Pm25ConcentrationMeasurement: 'pm25ConcentrationMeasurement', TemperatureMeasurement: 'temperatureMeasurement', RelativeHumidityMeasurement: 'relativeHumidityMeasurement', IlluminanceMeasurement: 'illuminanceMeasurement', OccupancySensing: 'occupancySensing', BooleanState: 'booleanState', SmokeCoAlarm: 'smokeCoAlarm', // Robotic Vacuum Cleaner Clusters RvcRunMode: 'rvcRunMode', RvcOperationalState: 'rvcOperationalState', RvcCleanMode: 'rvcCleanMode', ServiceArea: 'serviceArea', // Power PowerSource: 'powerSource', // Pump & Other PumpConfigurationAndControl: 'pumpConfigurationAndControl', // Valve ValveConfigurationAndControl: 'valveConfigurationAndControl', // Closures — garage doors, gates and similar ClosureControl: 'closureControl', // Media — the player endpoint; volume and mute are levelControl/onOff on a // separate Speaker endpoint rather than anything here MediaPlayback: 'mediaPlayback', KeypadInput: 'keypadInput', // Identification Identify: 'identify', // Switch (GenericSwitch - stateless remotes and buttons) Switch: 'switch', // Electrical Measurement (power/energy readings on outlets, ElectricalSensor, ...) ElectricalPowerMeasurement: 'electricalPowerMeasurement', ElectricalEnergyMeasurement: 'electricalEnergyMeasurement', // Device Information (read-only, set during registration) BasicInformation: 'basicInformation', BridgedDeviceBasicInformation: 'bridgedDeviceBasicInformation', }; /** * Check if endpoint has state property (type guard) * * We use a runtime check to determine if an endpoint has a settable state. * This is necessary because Endpoint's state structure is complex and varies * based on device type. * * @param endpoint - The endpoint to check * @returns True if endpoint has state and set method */ export function hasEndpointState(endpoint) { return 'state' in endpoint && typeof endpoint.state === 'object' && endpoint.state !== null && 'set' in endpoint && typeof endpoint.set === 'function'; } /** * Safely update endpoint state * Uses the Endpoint's set method to update cluster attributes * * @param endpoint - The Matter endpoint * @param cluster - Cluster name * @param attributes - Attributes to update * @throws {Error} If endpoint does not support state updates */ export async function updateEndpointState(endpoint, cluster, attributes) { if (!hasEndpointState(endpoint)) { throw new Error('Endpoint does not support state updates'); } const updateObject = { [cluster]: attributes }; await endpoint.set(updateObject); } /** * Type-safe cluster access for WindowCovering */ export function getWindowCoveringCluster(accessory) { return accessory.clusters?.windowCovering; } //# sourceMappingURL=types.js.map