MatterFan

About

The MatterFan class provides a fan endpoint for Matter networks with speed and mode control. This endpoint implements the Matter fan control standard.

Features: * On/off control * Fan speed control (0-100%) * Fan mode control (OFF, LOW, MEDIUM, HIGH, AUTO). ON and SMART are remapped, not stored * Fan mode sequence configuration * Callback support for state, speed, and mode changes * Integration with Home Assistant, Apple Home, Amazon Alexa, and Google Home * Matter standard compliance

Use Cases: * Smart ceiling fans * Exhaust fans * Ventilation fans * Fan speed controllers * HVAC fan control

API Reference

Constructor

MatterFan

Creates a new Matter fan endpoint.

MatterFan();

Initialization

begin

Initializes the Matter fan endpoint with optional initial speed, mode, and mode sequence.

bool begin(uint8_t percent = 0, FanMode_t fanMode = FAN_MODE_OFF, FanModeSequence_t fanModeSeq = FAN_MODE_SEQ_OFF_HIGH);
  • percent - Initial speed percentage (0-100, default: 0). Forced to 0 when fanMode is FAN_MODE_OFF. In Auto, this is PercentCurrent only; PercentSetting is null.

  • fanMode - Initial fan mode (default: FAN_MODE_OFF)

  • fanModeSeq - Fan mode sequence configuration (default: FAN_MODE_SEQ_OFF_HIGH)

This function will return true if successful, false otherwise.

end

Stops processing Matter fan events.

void end();

Constants

MAX_SPEED

Maximum speed value (100%).

static const uint8_t MAX_SPEED = 100;

MIN_SPEED

Minimum speed value (1%).

static const uint8_t MIN_SPEED = 1;

OFF_SPEED

Speed value when fan is off (0%).

static const uint8_t OFF_SPEED = 0;

Fan Modes

FanMode_t

Fan mode enumeration:

  • FAN_MODE_OFF - Fan is off

  • FAN_MODE_LOW - Low speed

  • FAN_MODE_MEDIUM - Medium speed

  • FAN_MODE_HIGH - High speed

  • FAN_MODE_ON - Alias: stored as FAN_MODE_HIGH

  • FAN_MODE_AUTO - Auto mode (only valid in an Auto sequence)

  • FAN_MODE_SMART - Alias: stored as FAN_MODE_AUTO if the sequence includes Auto, otherwise FAN_MODE_HIGH

Matter FanModeSequence never includes On or Smart. CHIP remaps those writes the same way.

Fan Mode Sequences

FanModeSequence_t

Fan mode sequence enumeration:

  • FAN_MODE_SEQ_OFF_LOW_MED_HIGH - OFF, LOW, MEDIUM, HIGH

  • FAN_MODE_SEQ_OFF_LOW_HIGH - OFF, LOW, HIGH

  • FAN_MODE_SEQ_OFF_LOW_MED_HIGH_AUTO - OFF, LOW, MEDIUM, HIGH, AUTO

  • FAN_MODE_SEQ_OFF_LOW_HIGH_AUTO - OFF, LOW, HIGH, AUTO

  • FAN_MODE_SEQ_OFF_HIGH_AUTO - OFF, HIGH, AUTO

  • FAN_MODE_SEQ_OFF_HIGH - OFF, HIGH

On/Off Control

setOnOff

Sets the on/off state of the fan.

bool setOnOff(bool newState, bool performUpdate = true);
  • newState - New state (true = on, false = off)

  • performUpdate - Perform update after setting (default: true)

getOnOff

Gets the current on/off state.

bool getOnOff();

toggle

Toggles the on/off state.

bool toggle(bool performUpdate = true);

Speed Control

setSpeedPercent

Sets the fan speed percentage.

bool setSpeedPercent(uint8_t newPercent, bool performUpdate = true);
  • newPercent - Speed percentage (0-100)

  • performUpdate - Perform update after setting (default: true)

Writes nullable PercentSetting and non-nullable PercentCurrent separately. In Auto, Matter may null PercentSetting; PercentCurrent stays 0-100.

getSpeedPercent

Gets the current speed percentage.

uint8_t getSpeedPercent();

Mode Control

setMode

Sets the fan mode.

bool setMode(FanMode_t newMode, bool performUpdate = true);
  • newMode - Fan mode to set. FAN_MODE_ON and FAN_MODE_SMART are remapped as described under FanMode_t

  • performUpdate - Perform update after setting (default: true)

Matches CHIP after the mode write: FAN_MODE_OFF sets PercentSetting and PercentCurrent to 0; FAN_MODE_AUTO nulls PercentSetting and leaves PercentCurrent as the actual speed.

This function will return false if the remapped mode is not in the sequence passed to begin().

getMode

Gets the current fan mode.

FanMode_t getMode();

getFanModeString

Gets a friendly string for the fan mode.

static const char *getFanModeString(uint8_t mode);

Event Handling

onChange

Sets a callback for when any parameter changes.

void onChange(EndPointCB onChangeCB);

The callback signature is:

bool onChangeCallback(FanMode_t newMode, uint8_t newPercent);

onChangeMode

Sets a callback for mode changes.

void onChangeMode(EndPointModeCB onChangeCB);

onChangeSpeedPercent

Sets a callback for speed changes.

void onChangeSpeedPercent(EndPointSpeedCB onChangeCB);

updateAccessory

Updates the physical fan state using current Matter internal state.

void updateAccessory();

Operators

uint8_t operator

Returns the current speed percentage.

operator uint8_t();

Assignment operator

Sets the speed percentage.

void operator=(uint8_t speedPercent);

Example

Fan Control

// Copyright 2025 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Matter Manager
#include <Arduino.h>
#include <Matter.h>
// List of Matter Endpoints for this Node
// Fan Endpoint - On/Off control + Speed Percent Control + Fan Modes
MatterFan Fan;

// Wi-Fi credentials for this sketch. Fill these in when the board cannot
// commission over BLE (Arduino prebuild on ESP32 / ESP32-S2): the sketch
// joins the AP itself. When Matter commissions over BLE (CHIPoBLE), the
// hub sends SSID and password — leave the placeholders; they are unused.
#define WIFI_SSID     "your-ssid"
#define WIFI_PASSWORD "your-password"

// set your board USER BUTTON pin here - used for toggling On/Off and decommission the Matter Node
const uint8_t buttonPin = BOOT_PIN;  // Set your pin here. Using BOOT Button.
MatterButton button;

// set your board Analog Pin here - used for changing the Fan speed
const uint8_t analogPin = A0;  // Analog Pin depends on each board

// set your board PWM Pin here - used for controlling the Fan speed (DC motor example)
// for this example, it will use the builtin board RGB LED to simulate the Fan DC motor using its brightness
#ifdef RGB_BUILTIN
const uint8_t dcMotorPin = RGB_BUILTIN;
#else
const uint8_t dcMotorPin = 2;  // Set your pin here if the board has no RGB_BUILTIN
#warning "Do not forget to set the RGB LED pin"
#endif

void fanDCMotorDrive(bool fanState, uint8_t speedPercent) {
  // drive the Fan DC motor
  if (fanState == false) {
    // turn off the Fan
#ifndef RGB_BUILTIN
    // after analogWrite(), it is necessary to set the GPIO to digital mode first
    pinMode(dcMotorPin, OUTPUT);
#endif
    digitalWrite(dcMotorPin, LOW);
  } else {
    // set the Fan speed
    uint8_t fanDCMotorPWM = map(speedPercent, 0, 100, 0, 255);
#ifdef RGB_BUILTIN
    rgbLedWrite(dcMotorPin, fanDCMotorPWM, fanDCMotorPWM, fanDCMotorPWM);
#else
    analogWrite(dcMotorPin, fanDCMotorPWM);
#endif
  }
}

void setup() {
  // Initialize the USER BUTTON (Boot button) GPIO that will toggle the Fan (On/Off) and decommission the Matter Node
  button.begin(buttonPin);
  // Initialize the Analog Pin A0 used to read input voltage and to set the Fan speed accordingly
  pinMode(analogPin, INPUT);
  analogReadResolution(10);  // 10 bits resolution reading 0..1023
  // Initialize the PWM output pin for a Fan DC motor
  pinMode(dcMotorPin, OUTPUT);

  Serial.begin(115200);

// CONFIG_ENABLE_CHIPOBLE=n: sketch starts Wi-Fi here; with CHIPoBLE the hub delivers credentials.
#if !CONFIG_ENABLE_CHIPOBLE
  matterConnectWiFi(WIFI_SSID, WIFI_PASSWORD);
#endif

  // Boot: 0% speed, Off. Sequence is Off/High, so setOnOff(true) / FAN_MODE_ON store High.
  Fan.begin(0, MatterFan::FAN_MODE_OFF, MatterFan::FAN_MODE_SEQ_OFF_HIGH);

  // callback functions would control Fan motor
  // the Matter Controller will send new data whenever the User APP or Automation request

  // single feature callbacks take place before the generic (all features) callback
  // This callback will be executed whenever the speed percent matter attribute is updated
  Fan.onChangeSpeedPercent([](uint8_t speedPercent) {
    // setting speed to Zero, while the Fan is ON, shall turn the Fan OFF
    if (speedPercent == MatterFan::OFF_SPEED && Fan.getMode() != MatterFan::FAN_MODE_OFF) {
      // ATTR_UPDATE reports FanMode so the APP confirms Off. Cache stops re-entry.
      return Fan.setOnOff(false, Fan.ATTR_UPDATE);
    }
    // changing the speed to higher than Zero, while the Fan is OFF, shall turn the Fan ON
    if (speedPercent > MatterFan::OFF_SPEED && Fan.getMode() == MatterFan::FAN_MODE_OFF) {
      return Fan.setOnOff(true, Fan.ATTR_UPDATE);
    }
    // for other case, just return true
    return true;
  });

  // This callback will be executed whenever the fan mode matter attribute is updated
  // This will take action when user APP starts the Fan by changing the mode
  Fan.onChangeMode([](MatterFan::FanMode_t fanMode) {
    // when the Fan is turned ON using Mode Selection, while it is OFF, shall start it by setting the speed to 50%
    if (Fan.getSpeedPercent() == MatterFan::OFF_SPEED && fanMode != MatterFan::FAN_MODE_OFF) {
      Serial.printf("Fan set to %s mode -- speed percentage will go to 50%%\r\n", Fan.getFanModeString(fanMode));
      return Fan.setSpeedPercent(50, Fan.ATTR_UPDATE);
    }
    return true;
  });

  // Generic callback will be executed as soon as a single feature callback is done
  // In this example, it will just print status messages
  Fan.onChange([](MatterFan::FanMode_t fanMode, uint8_t speedPercent) {
    // just report state
    Serial.printf("Fan State: Mode %s | %u%% speed.\r\n", Fan.getFanModeString(fanMode), speedPercent);
    // drive the Fan DC motor
    fanDCMotorDrive(fanMode != MatterFan::FAN_MODE_OFF, speedPercent);
    // returns success
    return true;
  });

  // Matter beginning - Last step, after all EndPoints are initialized
  Matter.begin();
  matterWaitUntilReady();
}

void loop() {
  matterRestartIfNoFabric();

  matterButtonEvent_t ev;
  while ((ev = button.poll()) != MATTER_BUTTON_NONE) {
    if (ev == MATTER_BUTTON_CLICK) {
      Fan.toggle();
      Serial.printf("User button released. Setting the Fan %s.\r\n", Fan > 0 ? "ON" : "OFF");
    } else if (ev == MATTER_BUTTON_LONG_HOLD) {
      Serial.println("Decommissioning Fan Matter Accessory. It shall be commissioned again.");
      Matter.decommission();
    }
  }

  // checks Analog pin and adjust the speed only if it has changed
  static int lastRead = 0;
  // analog values (0..1023) / 103 => mapped into 10 steps (0..9)
  int anaVal = analogRead(analogPin) / 103;
  if (lastRead != anaVal) {
    // speed percent moves in steps of 10. Range is 10..100
    if (Fan.setSpeedPercent((anaVal + 1) * 10)) {
      lastRead = anaVal;
    }
  }
}