Repository navigation
Section 14 Energy
Matter Specification § 14 - Devices that generate, store, use or measure energy.
Note
Every device type on this page requires Homebridge v2.5.0 or later.
Warning
It is not yet known how Apple Home presents these device types. They register and pair, and other Matter controllers may show them before Apple Home does. Test them in the Home app before replacing something that already works there, such as a heating-only Thermostat or an outlet with power monitoring.
All electrical values use the raw Matter units: voltage in millivolts, current in milliamps, power in milliwatts and energy in milliwatt-hours. null means "no reading right now". The electricalPowerMeasurement and electricalEnergyMeasurement clusters work exactly as they do on an outlet - see Smart Plugs (§5) for the full description.
Solar Power, Battery Storage and the EV Charger are composed devices: the parent carries the device type, and its parts carry what the spec requires of it. The parts use these device types:
| Part device type | Becomes |
|---|---|
api.matter?.deviceTypes.PowerSource |
A power source. Declare bat* attributes for a battery (it gets the Battery and Rechargeable features), or only wired attributes for mains, an inverter or the grid (it gets the Wired feature). |
api.matter?.deviceTypes.ElectricalSensor |
A meter. Its clusters come from the declared electricalPowerMeasurement / electricalEnergyMeasurement state. powerMode: 1 makes it measure DC; the default is AC. |
api.matter?.deviceTypes.DeviceEnergyManagement |
Lets a controller ask the device to change its power use. Its features come from the declared state (see below). |
Important
A composed energy device must have exactly the parts the spec lists, with the attributes it lists for each. matter.js checks this when the accessory registers and logs any gap as violates device type requirements, naming the missing attribute or feature. Run Homebridge with -D and check the log after adding one.
When testing a change, start from fresh storage or remove the cached accessory first. A restored accessory keeps its cached state, so new clusters values never reach it.
Declared in deviceEnergyManagement
|
Feature turned on |
|---|---|
powerAdjustmentCapability |
PowerAdjustment |
forecast |
PowerForecastReporting |
Other features (Pausable, StartTimeAdjustment and so on) can be composed by hand with api.matter?.deviceRequirements.DeviceEnergyManagement - see Customising Features. matter.js implements none of this cluster's commands, so Homebridge records each request in the state; supply deviceEnergyManagement handlers to act on them.
| Property | Value |
|---|---|
| Device Type | api.matter?.deviceTypes.EnergyEvse |
| Description | Electric vehicle supply equipment - a car charger. |
| Matter Specification | § 14.1 |
The charger's own endpoint carries two clusters:
-
energyEvse- whether a car is plugged in, whether charging is allowed, and the current limits -
energyEvseMode- its modes, which needsupportedModesandcurrentMode. One mode must be tagged Manual (0x4000); others can be tagged Time of Use (0x4001), Solar Charging (0x4002) or V2X (0x4003)
and three parts: a wired PowerSource, an ElectricalSensor for the power going to the car, and a DeviceEnergyManagement part declaring a forecast.
Optional energyEvse attributes turn on extra features: stateOfCharge or batteryCapacity add SoCReporting, vehicleId adds PlugAndCharge, and any discharging attribute (such as maximumDischargeCurrent) adds V2X.
| Attribute | Type | Description |
|---|---|---|
state |
number | null | 0=not plugged in, 1=plugged in, 2=plugged in and wants charge, 3=charging, 4=discharging, 5=session ending, 6=fault |
supplyState |
number | 0=disabled, 1=charging enabled, 2=discharging enabled, 3=disabled by an error, 4=disabled for diagnostics, 5=enabled |
faultState |
number | 0=no error (see the spec for the fault codes) |
chargingEnabledUntil |
number | null | When charging stops being allowed; null = until told otherwise |
circuitCapacity |
number | The circuit's limit, in mA |
minimumChargeCurrent |
number | The lowest current to charge at, in mA (6 A is usual) |
maximumChargeCurrent |
number | The highest current allowed right now, in mA |
deviceType: api.matter?.deviceTypes.EnergyEvse,
clusters: {
energyEvse: {
state: 1, // plugged in
supplyState: 0, // disabled until a controller enables charging
faultState: 0,
chargingEnabledUntil: null,
circuitCapacity: 32_000, // 32 A
minimumChargeCurrent: 6_000, // 6 A
maximumChargeCurrent: 0,
},
energyEvseMode: {
supportedModes: [
{ label: 'Manual', mode: 0, modeTags: [{ value: 0x4000 }] },
{ label: 'Cheapest Times', mode: 1, modeTags: [{ value: 0x4001 }] },
{ label: 'Solar Only', mode: 2, modeTags: [{ value: 0x4002 }] },
],
currentMode: 0,
},
},
parts: [
{
id: 'supply',
displayName: 'Supply',
deviceType: api.matter?.deviceTypes.PowerSource,
clusters: {
powerSource: { status: 1, order: 0, description: 'Mains', wiredCurrentType: 0 },
},
},
{
id: 'meter',
displayName: 'Charging Power',
deviceType: api.matter?.deviceTypes.ElectricalSensor,
clusters: {
electricalPowerMeasurement: { voltage: 230_000, activeCurrent: 0, activePower: 0 },
electricalEnergyMeasurement: { cumulativeEnergyImported: { energy: 0 } },
},
},
{
id: 'energy-management',
displayName: 'Energy Management',
deviceType: api.matter?.deviceTypes.DeviceEnergyManagement,
clusters: {
deviceEnergyManagement: {
esaType: 0, // EVSE
esaCanGenerate: false,
esaState: 1, // online
absMinPower: 0,
absMaxPower: 7_400_000, // 7.4 kW
forecast: null, // turns on PowerForecastReporting
},
},
},
],Handlers
matter.js implements none of the charger's commands. Without a handler, Homebridge records each command in the state; with one, your handler runs first. There is no getTargets handler - Homebridge answers it with the targets the last setTargets gave.
handlers: {
energyEvse: {
enableCharging: async ({ chargingEnabledUntil, minimumChargeCurrent, maximumChargeCurrent }) => {
await myChargerAPI.enableCharging(Number(maximumChargeCurrent) / 1000)
},
disable: async () => {
await myChargerAPI.disable()
},
// also: enableDischarging, startDiagnostics, setTargets, clearTargets
},
energyEvseMode: {
changeToMode: async ({ newMode }) => {
await myChargerAPI.setMode(newMode)
},
},
}The example accessory in this plugin is EvChargerAccessory (option enableEvCharger).
| Property | Value |
|---|---|
| Device Type | api.matter?.deviceTypes.WaterHeater |
| Description | A hot water tank or boiler, with operating modes and a boost. |
| Matter Specification | § 14.2 |
A water heater carries three clusters:
-
thermostat- heating only. Declare the water temperature asexternalMeasuredIndoorTemperatureand the target asoccupiedHeatingSetpoint -
waterHeaterMode- needssupportedModesandcurrentMode. One mode must be tagged Off (0x4000) and one Manual (0x4001); Timed (0x4002) is optional. A mode tagged Off, Manual or Timed carries no other tag -
waterHeaterManagement- needsheaterTypes,heatDemandandboostState
matter.js will not build the endpoint without every one of those.
Important
Declare absMinHeatSetpointLimit and absMaxHeatSetpointLimit too. They default to a room thermostat's 7°C - 30°C, and matter.js pulls any higher limit or target down to fit - a 55°C target becomes 30°C.
deviceType: api.matter?.deviceTypes.WaterHeater,
clusters: {
thermostat: {
externalMeasuredIndoorTemperature: 4800, // water at 48.00°C
occupiedHeatingSetpoint: 5500, // heat to 55.00°C
absMinHeatSetpointLimit: 3000, // 30.00°C
absMaxHeatSetpointLimit: 6500, // 65.00°C
minHeatSetpointLimit: 3000,
maxHeatSetpointLimit: 6500,
controlSequenceOfOperation: 2, // heating only
systemMode: 4, // heat
},
waterHeaterMode: {
supportedModes: [
{ label: 'Off', mode: 0, modeTags: [{ value: 0x4000 }] },
{ label: 'Manual', mode: 1, modeTags: [{ value: 0x4001 }] },
{ label: 'Timed', mode: 2, modeTags: [{ value: 0x4002 }] },
],
currentMode: 1,
},
waterHeaterManagement: {
heaterTypes: { immersionElement1: true, heatPump: true }, // the heat sources it has
heatDemand: {}, // which are heating now
boostState: 0, // 0=inactive, 1=active
// tankVolume, estimatedHeatRequired and tankPercentage need features that
// Homebridge does not turn on from state - compose the cluster first, see
// Customising Features
},
},Handlers
matter.js leaves boost unimplemented, so Homebridge records it: once your boost handler returns, boostState becomes active. Ending the boost is up to the plugin - set boostState back to 0 when the duration runs out.
handlers: {
thermostat: {
occupiedHeatingSetpointChange: async ({ occupiedHeatingSetpoint }) => {
await myWaterHeaterAPI.setTargetTemperature(occupiedHeatingSetpoint / 100)
},
},
waterHeaterMode: {
changeToMode: async ({ newMode }) => {
await myWaterHeaterAPI.setMode(newMode)
},
},
waterHeaterManagement: {
boost: async ({ boostInfo }) => {
// boostInfo.duration is in seconds; also oneShot, emergencyBoost,
// temporarySetpoint, targetPercentage and targetReheat
await myWaterHeaterAPI.boost(boostInfo.duration)
setTimeout(() => {
void api.matter?.updateAccessoryState(uuid, 'waterHeaterManagement', { boostState: 0 })
}, boostInfo.duration * 1000)
},
cancelBoost: async () => {
await myWaterHeaterAPI.cancelBoost()
},
},
}The example accessory in this plugin is WaterHeaterAccessory (option enableWaterHeater).
| Property | Value |
|---|---|
| Device Type | api.matter?.deviceTypes.SolarPower |
| Description | A solar inverter, reporting what it generates. |
| Matter Specification | § 14.3 |
A composed device with two parts: a wired PowerSource (the inverter's output) and an ElectricalSensor reporting voltage, current, power and cumulativeEnergyExported. Generation is reported as negative active power: power flowing out of the device.
deviceType: api.matter?.deviceTypes.SolarPower,
parts: [
{
id: 'inverter',
displayName: 'Inverter',
deviceType: api.matter?.deviceTypes.PowerSource,
clusters: {
powerSource: { status: 1, order: 0, description: 'Inverter', wiredCurrentType: 0 },
},
},
{
id: 'meter',
displayName: 'Generation',
deviceType: api.matter?.deviceTypes.ElectricalSensor,
clusters: {
electricalPowerMeasurement: {
voltage: 230_000,
activeCurrent: -10_870,
activePower: -2_500_000, // generating 2.5 kW
},
electricalEnergyMeasurement: {
cumulativeEnergyExported: { energy: 0 },
},
},
},
],Update a part's state by passing its id as the last argument:
await api.matter?.updateAccessoryState(uuid, 'electricalPowerMeasurement', { activePower: -3_100_000 }, 'meter')The example accessory in this plugin is SolarPowerAccessory (option enableSolarPower).
| Property | Value |
|---|---|
| Device Type | api.matter?.deviceTypes.BatteryStorage |
| Description | A home battery that stores energy and gives it back. |
| Matter Specification | § 14.4 |
A composed device with five parts:
- a battery PowerSource. On top of the usual battery attributes, the spec requires
batVoltage,batCapacity(mAh),batTimeRemaining,batTimeToFullCharge,batChargingCurrent,activeBatFaultsandactiveBatChargeFaults- declarenullor[]for any you cannot report yet - a wired PowerSource - the grid side
- an ElectricalSensor for the AC side and one for the DC side. Both must report power and energy, and the DC one declares
powerMode: 1 - a DeviceEnergyManagement part declaring
powerAdjustmentCapability, so a controller can ask the battery to charge or discharge at a set power
Positive power is charging (into the battery), negative is discharging.
deviceType: api.matter?.deviceTypes.BatteryStorage,
parts: [
{
id: 'battery',
displayName: 'Battery',
deviceType: api.matter?.deviceTypes.PowerSource,
clusters: {
powerSource: {
status: 1, order: 0, description: 'Battery',
batPercentRemaining: 160, // half-percent steps: 160 = 80%
batChargeLevel: 0, // ok
batReplacementNeeded: false,
batReplaceability: 3, // factory replaceable
batChargeState: 3, // not charging
batFunctionalWhileCharging: true,
batVoltage: 400_000, // mV
batCapacity: 33_750, // mAh
batTimeRemaining: null,
batTimeToFullCharge: null,
batChargingCurrent: null,
activeBatFaults: [],
activeBatChargeFaults: [],
},
},
},
{
id: 'grid',
displayName: 'Grid Connection',
deviceType: api.matter?.deviceTypes.PowerSource,
clusters: {
powerSource: { status: 1, order: 1, description: 'Grid', wiredCurrentType: 0 },
},
},
{
id: 'ac-meter',
displayName: 'AC Side',
deviceType: api.matter?.deviceTypes.ElectricalSensor,
clusters: {
electricalPowerMeasurement: { voltage: 230_000, activeCurrent: 0, activePower: 0 },
electricalEnergyMeasurement: {
cumulativeEnergyImported: { energy: 0 },
cumulativeEnergyExported: { energy: 0 },
},
},
},
{
id: 'dc-meter',
displayName: 'DC Side',
deviceType: api.matter?.deviceTypes.ElectricalSensor,
clusters: {
electricalPowerMeasurement: { powerMode: 1, voltage: 400_000, activeCurrent: 0, activePower: 0 },
electricalEnergyMeasurement: {
cumulativeEnergyImported: { energy: 0 },
cumulativeEnergyExported: { energy: 0 },
},
},
},
{
id: 'energy-management',
displayName: 'Energy Management',
deviceType: api.matter?.deviceTypes.DeviceEnergyManagement,
clusters: {
deviceEnergyManagement: {
esaType: 5, // battery storage
esaCanGenerate: true,
esaState: 1, // online
absMinPower: -5_000_000,
absMaxPower: 5_000_000,
powerAdjustmentCapability: {
powerAdjustCapability: [
{ minPower: -5_000_000, maxPower: 5_000_000, minDuration: 60, maxDuration: 3600 },
],
cause: 0, // normal operation
},
optOutState: 0,
},
},
handlers: {
deviceEnergyManagement: {
powerAdjustRequest: async ({ power, duration }) => {
await myBatteryAPI.setPower(Number(power) / 1000, duration)
},
cancelPowerAdjustRequest: async () => {
await myBatteryAPI.resumeNormalOperation()
},
},
},
},
],The example accessory in this plugin is BatteryStorageAccessory (option enableBatteryStorage).
| Property | Value |
|---|---|
| Device Type | api.matter?.deviceTypes.ElectricalMeter |
| Description | A meter on a circuit or the whole home. |
| Matter Specification | § 14.8 |
Not composed: declare the measurement clusters on the accessory itself. Like an outlet's Electrical Sensor, the features come from the attributes you declare - declaring both cumulativeEnergyImported and cumulativeEnergyExported gives a meter that counts both ways.
deviceType: api.matter?.deviceTypes.ElectricalMeter,
clusters: {
electricalPowerMeasurement: {
voltage: 230_000,
activeCurrent: 4_350,
activePower: 1_000_000, // 1 kW drawn from the grid
},
electricalEnergyMeasurement: {
cumulativeEnergyImported: { energy: 1_250_000_000 }, // 1250 kWh
cumulativeEnergyExported: { energy: 310_000_000 }, // 310 kWh
},
},The example accessory in this plugin is ElectricalMeterAccessory (option enableElectricalMeter).
api.matter?.deviceTypes.ElectricalUtilityMeter (§ 14.9) and ElectricalEnergyTariff (§ 14.7) are also available. Both are Matter 1.5 types, so controllers may not know them yet, and this plugin has no examples of them.
- A utility meter is the energy company's own meter: declare
meterIdentificationstate (meterType,pointOfDelivery,meterSerialNumber,protocolVersion). - A tariff carries no cluster of its own: compose
CommodityPriceorCommodityTarifffromapi.matter?.deviceRequirements.ElectricalEnergyTariff, and give it the 1 to 6 descriptor tags it requires asclusters.descriptor.tagList.
- Introduction
- Core Concepts
- Getting Started
- State Management
- Monitoring External Changes
- Error Handling
- Best Practices
- Advanced Patterns
- Customising Features