homebridge
Version:
HomeKit support for the impatient
457 lines • 23.1 kB
JavaScript
/**
* 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