Skip to content

Customising Features

Ben edited this page Nov 1, 2026 · 4 revisions

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.


How it works

Three steps:

  1. 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'),
    )
  2. 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.

  3. 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.


Worked example: a thermostat without Auto mode

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:

  • systemMode must not be Auto (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 the min pair) 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.


What can be customised

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.


Related pages

  • 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

Clone this wiki locally