Repository navigation
Customising Features
Note
Requires Homebridge v2.4.0 or later.
Many Matter clusters are feature-gated: matter.js will not add the cluster to a device type until its features are chosen. Homebridge normally makes those choices for you, based on the state your accessory declares — declare a heating setpoint and you get the Heating feature, declare a currentPositionTiltPercent100ths and your blind gets Tilt, and so on. For almost every device, that is exactly right and there is nothing to configure.
Sometimes, though, the right features cannot be derived from state alone. The classic case is a thermostat that heats and cools but has no auto mode: declaring both setpoints makes Homebridge enable Auto too, because that is what nearly every heat+cool thermostat wants. The Matter spec is fine with Heating + Cooling and no AutoMode — there was just no way to say it.
From Homebridge v2.4.0, api.matter.deviceRequirements exposes the matter.js "requirements" behind each feature-gated device type, so a plugin can compose the cluster itself with exactly the features the device has. When your device type already carries the cluster, Homebridge uses your feature choices as given instead of detecting its own.
Three steps:
-
Compose the device type yourself, choosing the cluster's features with
.with():const matter = api.matter! const deviceType = matter.deviceTypes.Thermostat.with( matter.deviceRequirements.Thermostat.ThermostatServer.with('Heating', 'Cooling'), )
-
Declare cluster state that matches the features you chose. A feature you did not enable must not have its attributes declared, and a feature you did enable needs its mandatory attributes.
-
That is it — register the accessory as normal. Homebridge sees the cluster is already composed and leaves your choices alone.
Important
Once you compose a cluster yourself, you own all of its feature choices — Homebridge no longer adds any for you. If your accessory declares unoccupied setpoints, for example, you must include 'Occupancy' in your .with() call yourself, or those attributes will fail conformance.
A device that heats and cools, but where the user must pick one — no auto-changeover.
import type { API, Logger } from 'homebridge'
import { getMatter } from '../utils.js'
import { BaseMatterAccessory } from './BaseMatterAccessory.js'
export class NoAutoThermostatAccessory extends BaseMatterAccessory {
constructor(api: API, log: Logger) {
const matter = getMatter(api)
super(api, log, {
UUID: matter.uuid.generate('my-no-auto-thermostat'),
displayName: 'Thermostat',
// Compose the thermostat cluster ourselves: Heating and Cooling, no AutoMode.
deviceType: matter.deviceTypes.Thermostat.with(
matter.deviceRequirements.Thermostat.ThermostatServer.with('Heating', 'Cooling'),
),
serialNumber: 'THERMO-001',
manufacturer: 'My Company',
model: 'Heat/Cool Thermostat',
clusters: {
thermostat: {
externalMeasuredIndoorTemperature: 2100, // 21.00°C
occupiedHeatingSetpoint: 2000, // 20.00°C
occupiedCoolingSetpoint: 2400, // 24.00°C
minHeatSetpointLimit: 700, // 7.00°C
maxHeatSetpointLimit: 3000, // 30.00°C
minCoolSetpointLimit: 1600, // 16.00°C
maxCoolSetpointLimit: 3200, // 32.00°C
controlSequenceOfOperation: 4, // cooling and heating
systemMode: 4, // heat — Auto (1) is not valid without the AutoMode feature
},
},
handlers: {
thermostat: {
systemModeChange: async request => this.handleSystemModeChange(request),
occupiedHeatingSetpointChange: async request => this.handleSetHeating(request),
occupiedCoolingSetpointChange: async request => this.handleSetCooling(request),
},
},
})
}
// ... handlers as normal
}Three things worth noticing, all of them consequences of Auto being gone:
-
systemModemust not beAuto(1). Use Heat (4) or Cool (3) as the initial mode. In Apple Home the user now switches between Off / Heat / Cool, with no Auto option shown. -
No
minSetpointDeadBand. The deadband is an Auto-mode concept — the minimum gap auto-changeover keeps between the two setpoints. Without AutoMode it must not be declared. -
The setpoint limits no longer need a gap between them. With AutoMode, matter.js requires
maxCoolSetpointLimit - maxHeatSetpointLimit(and theminpair) to be at least the deadband — a rule that catches many people out (see HVAC (§9)). Without AutoMode there is no such constraint, so the full spec range on both sides is fine.
Tip
Heat-only and cool-only thermostats do not need any of this. Homebridge has always derived those from the setpoints you declare: declare only occupiedHeatingSetpoint and you get a heat-only thermostat. Composing the cluster yourself is only needed when the right features cannot be inferred — like heat + cool without auto.
api.matter.deviceRequirements is keyed to match api.matter.deviceTypes. The table below lists each entry, the feature-gated server it exposes, and the features Homebridge would otherwise pick.
| Key | Server | Available features | Default without customisation |
|---|---|---|---|
Thermostat |
ThermostatServer |
Heating, Cooling, AutoMode, Occupancy, Setback, LocalTemperatureNotExposed, … |
Detected from declared setpoints: heating setpoint → Heating, cooling → Cooling, both → + AutoMode, unoccupied setpoints → + Occupancy |
SmokeSensor |
SmokeCoAlarmServer |
SmokeAlarm, CoAlarm
|
Detected from declared smokeState / coState
|
MotionSensor |
OccupancySensingServer |
PassiveInfrared, Ultrasonic, PhysicalContact, ActiveInfrared, Radar, RfSensing, Vision, OccupancyEvent, … |
PassiveInfrared + OccupancyEvent
|
GenericSwitch |
SwitchServer |
LatchingSwitch, MomentarySwitch, MomentarySwitchRelease, MomentarySwitchLongPress, MomentarySwitchMultiPress, ActionSwitch
|
The full momentary set (matches the api.matter.switch press/release helpers) |
Pump |
PumpConfigurationAndControlServer |
ConstantSpeed, ConstantPressure, CompensatedPressure, ConstantFlow, ConstantTemperature, Automatic, LocalOperation
|
ConstantSpeed |
RoomAirConditioner |
ThermostatServer |
as Thermostat above |
Heating + Cooling; from Homebridge v2.5.0, + AutoMode when it starts in Auto (systemMode: 1) or declares minSetpointDeadBand
|
Closure |
ClosureControlServer |
Positioning, MotionLatching, Instantaneous, Speed, Ventilation, Pedestrian, Calibration, Protection, ManuallyOperable
|
Positioning |
ElectricalSensor |
ElectricalPowerMeasurementServer, ElectricalEnergyMeasurementServer, PowerTopologyServer
|
per server | Detected from declared electrical cluster state |
WaterValve |
ValveConfigurationAndControlServer |
TimeSync, Level
|
none (base server) |
RoboticVacuumCleaner |
RvcRunModeServer, RvcCleanModeServer, ServiceAreaServer
|
per server | ServiceArea features detected from declared areas/maps; RvcCleanMode DirectModeChange through the accessory's features.rvcCleanMode.directModeChange option, not .with() (Homebridge v2.5.0+, needs a changeToMode handler) |
WindowCovering |
WindowCoveringServer |
Lift, Tilt, PositionAwareLift, PositionAwareTilt, … |
Detected from the declared position attributes |
WaterHeater |
ThermostatServer, WaterHeaterManagementServer
|
Thermostat: as above. Management: EnergyManagement, TankPercent
|
Thermostat Heating only; management none - compose it to declare tankVolume, estimatedHeatRequired or tankPercentage (Homebridge v2.5.0+) |
EnergyEvse |
EnergyEvseServer |
ChargingPreferences, SoCReporting, PlugAndCharge, Rfid, V2X
|
ChargingPreferences, plus SoCReporting from stateOfCharge/batteryCapacity, PlugAndCharge from vehicleId, V2X from any discharging attribute (Homebridge v2.5.0+) |
DeviceEnergyManagement |
DeviceEnergyManagementServer |
PowerAdjustment, PowerForecastReporting, StateForecastReporting, Pausable, StartTimeAdjustment, … |
PowerAdjustment from powerAdjustmentCapability, a forecast feature from forecast (Homebridge v2.5.0+) |
PowerSource |
PowerSourceServer |
Wired, Battery, Rechargeable, Replaceable
|
Battery + Rechargeable when any bat* attribute is declared, otherwise Wired
|
ElectricalMeter, ElectricalUtilityMeter
|
the electrical measurement servers (and MeterIdentificationServer for the utility meter) |
per server | Detected from declared electrical cluster state (Homebridge v2.5.0+) |
ElectricalEnergyTariff |
CommodityPriceServer, CommodityTariffServer, … |
per server | none - a tariff has no cluster of its own until you compose one (Homebridge v2.5.0+) |
Note
Some clusters are customised by what you declare, not by composing: WindowCovering (Lift/Tilt from the position attributes you declare), ColorControl (from your colour handlers and attributes), FanControl (Auto from a fanModeSequence that offers it, Homebridge v2.5.0 or later), ServiceArea and the electrical measurement clusters. For those, declaring different state is the customisation — deviceRequirements is not needed.
Warning
Feature choices must still be legal per the Matter spec. A thermostat must have Heating or Cooling (or both); AutoMode requires both; a GenericSwitch must be latching or momentary, not neither. matter.js validates conformance at registration and will reject an impossible combination. Also note that the api.matter.switch helpers (singlePress, longPress, …) assume the momentary feature set — a latching switch composed yourself should drive the switch cluster state directly instead.
- HVAC (§9) — the full thermostat reference, including the Auto-mode deadband rules
- Core Concepts — clusters, features and device types from the ground up
- Matter Types — every device type and its clusters
- Introduction
- Core Concepts
- Getting Started
- State Management
- Monitoring External Changes
- Error Handling
- Best Practices
- Advanced Patterns
- Customising Features