MatterOccupancySensor
About
The MatterOccupancySensor class provides an occupancy sensor endpoint for Matter networks. This endpoint implements the Matter occupancy sensing standard for detecting occupied/unoccupied states (e.g., motion sensors, PIR sensors).
Features: * Occupancy state reporting (occupied/unoccupied) * Multiple sensor type support (PIR, Ultrasonic, Physical Contact) * HoldTime attribute for configuring how long the sensor holds the “occupied” state * HoldTimeLimits (min, max, default) for validation and controller guidance * HoldTime change callback for real-time updates from Matter controllers * Simple boolean state * Read-only sensor (no control functionality) * Automatic state updates * Integration with Apple HomeKit, Amazon Alexa, and Google Home * Matter standard compliance
Use Cases: * Motion sensors (PIR) * Occupancy detection * Security systems * Smart lighting automation * Energy management (turn off lights when unoccupied)
API Reference
Constructor
MatterOccupancySensor
Creates a new Matter occupancy sensor endpoint.
MatterOccupancySensor();
Initialization
begin
Initializes the Matter occupancy sensor endpoint with an initial occupancy state and sensor type.
bool begin(bool _occupancyState = false, OccupancySensorType_t _occupancySensorType = OCCUPANCY_SENSOR_TYPE_PIR);
_occupancyState- Initial occupancy state (true= occupied,false= unoccupied, default:false)_occupancySensorType- Sensor type (default:OCCUPANCY_SENSOR_TYPE_PIR)
This function will return true if successful, false otherwise.
end
Stops processing Matter occupancy sensor events.
void end();
Sensor Types
OccupancySensorType_t
Occupancy sensor type enumeration:
OCCUPANCY_SENSOR_TYPE_PIR- Passive Infrared (PIR) sensorOCCUPANCY_SENSOR_TYPE_ULTRASONIC- Ultrasonic sensorOCCUPANCY_SENSOR_TYPE_PIR_AND_ULTRASONIC- Combined PIR and UltrasonicOCCUPANCY_SENSOR_TYPE_PHYSICAL_CONTACT- Physical contact sensor
Occupancy State Control
setOccupancy
Sets the occupancy state.
bool setOccupancy(bool _occupancyState);
_occupancyState- Occupancy state (true= occupied,false= unoccupied)
This function will return true if successful, false otherwise.
getOccupancy
Gets the current occupancy state.
bool getOccupancy();
This function will return true if occupied, false if unoccupied.
HoldTime Control
setHoldTime
Sets the HoldTime value (in seconds). The HoldTime determines how long the sensor maintains the “occupied” state after the last detection.
bool setHoldTime(uint16_t _holdTime_seconds);
_holdTime_seconds- HoldTime value in seconds
Important: This function must be called after Matter.begin() has been called, as it requires the Matter event loop to be running.
This function will return true if successful, false otherwise.
getHoldTime
Gets the current HoldTime value (in seconds).
uint16_t getHoldTime();
This function will return the current HoldTime value in seconds.
setHoldTimeLimits
Sets the HoldTime limits (minimum, maximum, and default values). These limits define the valid range for HoldTime values and provide metadata for Matter controllers.
bool setHoldTimeLimits(uint16_t _holdTimeMin_seconds, uint16_t _holdTimeMax_seconds, uint16_t _holdTimeDefault_seconds);
_holdTimeMin_seconds- Minimum HoldTime value in seconds_holdTimeMax_seconds- Maximum HoldTime value in seconds_holdTimeDefault_seconds- Default/recommended HoldTime value in seconds (informational metadata for controllers)
Important:
* This function must be called after Matter.begin() has been called, as it requires the Matter event loop to be running.
* The holdTimeDefault_seconds parameter is informational metadata for Matter controllers (recommended default value). It does NOT automatically set the HoldTime attribute - use setHoldTime() to set the actual value.
* If the current HoldTime value is outside the new limits, it will be automatically adjusted to the nearest limit (minimum or maximum).
This function will return true if successful, false otherwise.
onHoldTimeChange
Sets a callback function that will be called when the HoldTime value is changed by a Matter Controller.
void onHoldTimeChange(HoldTimeChangeCB onHoldTimeChangeCB);
onHoldTimeChangeCB- Callback function of typeHoldTimeChangeCB
The callback function signature is:
using HoldTimeChangeCB = std::function<bool(uint16_t holdTime_seconds)>;
The callback receives the new HoldTime value and can return true to accept the change or false to reject it.
Example:
OccupancySensor.onHoldTimeChange([](uint16_t holdTime_seconds) -> bool {
Serial.printf("HoldTime changed to %u seconds\n", holdTime_seconds);
return true; // Accept the change
});
Operators
bool operator
Returns the current occupancy state.
operator bool();
Example:
if (mySensor) {
Serial.println("Room is occupied");
} else {
Serial.println("Room is unoccupied");
}
Assignment operator
Sets the occupancy state.
void operator=(bool _occupancyState);
Example:
mySensor = true; // Set to occupied
mySensor = false; // Set to unoccupied
Example
Basic Occupancy Sensor
// 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.
/*
* This example is an example code that will create a Matter Device which can be
* commissioned and controlled from a Matter Environment APP.
* Additionally the ESP32 will send debug messages indicating the Matter activity.
* Turning DEBUG Level ON may be useful to following Matter Accessory and Controller messages.
*
* The example will create a Matter Occupancy Sensor Device.
* The Occupancy Sensor will be simulated to change its state every 2 minutes.
*
* The onboard button can be kept pressed for 5 seconds to decommission the Matter Node.
* The example will also show the manual commissioning code and QR code to be used in the Matter environment.
*
*/
// Matter Manager
#include <Matter.h>
#if !CONFIG_ENABLE_CHIPOBLE
// if the device can be commissioned using BLE, WiFi is not used - save flash space
#include <WiFi.h>
#endif
// List of Matter Endpoints for this Node
// Matter Occupancy Sensor Endpoint
MatterOccupancySensor OccupancySensor;
// CONFIG_ENABLE_CHIPOBLE is enabled when BLE is used to commission the Matter Network
#if !CONFIG_ENABLE_CHIPOBLE
// WiFi is manually set and started
const char *ssid = "your-ssid"; // Change this to your WiFi SSID
const char *password = "your-password"; // Change this to your WiFi password
#endif
// set your board USER BUTTON pin here - decommissioning only
const uint8_t buttonPin = BOOT_PIN; // Set your pin here. Using BOOT Button.
// Button control
uint32_t button_time_stamp = 0; // debouncing control
bool button_state = false; // false = released | true = pressed
const uint32_t decommissioningTimeout = 5000; // keep the button pressed for 5s, or longer, to decommission
void setup() {
// Initialize the USER BUTTON (Boot button) that will be used to decommission the Matter Node
pinMode(buttonPin, INPUT_PULLUP);
Serial.begin(115200);
// CONFIG_ENABLE_CHIPOBLE is enabled when BLE is used to commission the Matter Network
#if !CONFIG_ENABLE_CHIPOBLE
// Manually connect to WiFi
WiFi.begin(ssid, password);
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
#endif
// set initial occupancy sensor state as false and connected to a PIR sensor type (default)
OccupancySensor.begin();
// Matter beginning - Last step, after all EndPoints are initialized
Matter.begin();
// Check Matter Accessory Commissioning state, which may change during execution of loop()
if (!Matter.isDeviceCommissioned()) {
Serial.println("");
Serial.println("Matter Node is not commissioned yet.");
Serial.println("Initiate the device discovery in your Matter environment.");
Serial.println("Commission it to your Matter hub with the manual pairing code or QR code");
Serial.printf("Manual pairing code: %s\r\n", Matter.getManualPairingCode().c_str());
Serial.printf("QR code URL: %s\r\n", Matter.getOnboardingQRCodeUrl().c_str());
// waits for Matter Occupancy Sensor Commissioning.
uint32_t timeCount = 0;
while (!Matter.isDeviceCommissioned()) {
delay(100);
if ((timeCount++ % 50) == 0) { // 50*100ms = 5 sec
Serial.println("Matter Node not commissioned yet. Waiting for commissioning.");
}
}
Serial.println("Matter Node is commissioned and connected to the network. Ready for use.");
}
}
bool simulatedHWOccupancySensor() {
// Simulated Occupancy Sensor
static bool occupancyState = false;
static uint32_t lastTime = millis();
const uint32_t occupancyTimeout = 120000; // 2 minutes to toggle the state
// Simulate a Occupancy Sensor state change every 2 minutes
if (millis() - lastTime > occupancyTimeout) {
occupancyState = !occupancyState;
lastTime = millis();
}
return occupancyState;
}
void loop() {
// Check if the button has been pressed
if (digitalRead(buttonPin) == LOW && !button_state) {
// deals with button debouncing
button_time_stamp = millis(); // record the time while the button is pressed.
button_state = true; // pressed.
}
if (button_state && digitalRead(buttonPin) == HIGH) {
button_state = false; // released
}
// Onboard User Button is kept pressed for longer than 5 seconds in order to decommission matter node
uint32_t time_diff = millis() - button_time_stamp;
if (button_state && time_diff > decommissioningTimeout) {
Serial.println("Decommissioning Occupancy Sensor Matter Accessory. It shall be commissioned again.");
Matter.decommission();
button_time_stamp = millis(); // avoid running decommissining again, reboot takes a second or so
}
// Check Simulated Occupancy Sensor and set Matter Attribute
OccupancySensor.setOccupancy(simulatedHWOccupancySensor());
delay(50);
}
Occupancy Sensor with HoldTime
For an example that demonstrates HoldTime functionality, see:
// 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.
/*
* This example is an example code that will create a Matter Device which can be
* commissioned and controlled from a Matter Environment APP.
* Additionally the ESP32 will send debug messages indicating the Matter activity.
* Turning DEBUG Level ON may be useful to following Matter Accessory and Controller messages.
*
* The example will create a Matter Occupancy Sensor Device.
* The Occupancy Sensor will be simulated to toggle occupancy every 2 minutes.
* When occupancy is detected, the sensor holds the "occupied" state for the HoldTime duration
* (default: 30 seconds, configurable via Matter Controller). After HoldTime expires,
* the sensor automatically switches to "unoccupied" state.
*
* The HoldTime attribute allows you to adjust how long the active status is retained
* after the person leaves. The HoldTime can be changed by a Matter Controller, and the
* onHoldTimeChange() callback is used to update the simulated sensor functionality.
*
* The HoldTime value is persisted to Preferences (NVS) and restored on reboot, so the
* last configured HoldTime value is maintained across device restarts.
*
* The onboard button can be kept pressed for 5 seconds to decommission the Matter Node.
* The example will also show the manual commissioning code and QR code to be used in the Matter environment.
*
*/
// Matter Manager
#include <Matter.h>
// CONFIG_ENABLE_CHIPOBLE is enabled when BLE is used to commission the Matter Network
#if !CONFIG_ENABLE_CHIPOBLE
// if the device can be commissioned using BLE, WiFi is not used - save flash space
#include <WiFi.h>
// WiFi is manually set and started
const char *ssid = "your-ssid"; // Change this to your WiFi SSID
const char *password = "your-password"; // Change this to your WiFi password
#endif
#include <Preferences.h>
// HoldTime configuration constants
const uint16_t HOLD_TIME_MIN = 0; // Minimum HoldTime in seconds
const uint16_t HOLD_TIME_MAX = 3600; // Maximum HoldTime in seconds (1 hour)
const uint16_t HOLD_TIME_DEFAULT = 30; // Default HoldTime in seconds
// List of Matter Endpoints for this Node
// Matter Occupancy Sensor Endpoint
MatterOccupancySensor OccupancySensor;
// Preferences to store HoldTime value across reboots
Preferences matterPref;
const char *holdTimePrefKey = "HoldTime";
// set your board USER BUTTON pin here - decommissioning only
const uint8_t buttonPin = BOOT_PIN; // Set your pin here. Using BOOT Button.
// Button control
uint32_t button_time_stamp = 0; // debouncing control
bool button_state = false; // false = released | true = pressed
const uint32_t decommissioningTimeout = 5000; // keep the button pressed for 5s, or longer, to decommission
// Simulated Occupancy Sensor with HoldTime support
// When occupancy is detected, it holds the "occupied" state for HoldTime seconds
// After HoldTime expires, it automatically switches to "unoccupied"
//
// Behavior for different HoldTime vs detectionInterval relationships:
// - holdTime_ms < detectionInterval: State switches to unoccupied after HoldTime, then waits for next detection
// - holdTime_ms == detectionInterval: If detections keep coming, timer resets (continuous occupancy)
// - holdTime_ms > detectionInterval: If detections keep coming, timer resets (continuous occupancy)
// If detections stop, HoldTime expires after the last detection
bool simulatedHWOccupancySensor() {
static bool occupancyState = false;
static uint32_t lastDetectionTime = 0;
static uint32_t lastDetectionEvent = millis();
const uint32_t detectionInterval = 120000; // Simulate detection every 2 minutes
// Get current HoldTime from the sensor (can be changed by Matter Controller)
uint32_t holdTime_ms = OccupancySensor.getHoldTime() * 1000; // Convert seconds to milliseconds
// Check HoldTime expiration FIRST (before processing new detections)
// This ensures HoldTime can expire even if a detection occurs in the same iteration
if (occupancyState && (millis() - lastDetectionTime > holdTime_ms)) {
occupancyState = false;
// Reset detection interval counter so next detection can happen immediately
// This makes the simulation more responsive after the room becomes unoccupied
lastDetectionEvent = millis();
Serial.println("HoldTime expired. Switching to unoccupied state.");
}
// Simulate periodic occupancy detection (e.g., motion detected)
// Check this AFTER HoldTime expiration so new detections can immediately re-trigger occupancy
if (millis() - lastDetectionEvent > detectionInterval) {
// New detection event occurred
lastDetectionEvent = millis();
if (!occupancyState) {
// Transition from unoccupied to occupied - start hold timer
occupancyState = true;
lastDetectionTime = millis();
Serial.printf("Occupancy detected! Holding state for %u seconds (HoldTime)\n", OccupancySensor.getHoldTime());
} else {
// Already occupied - new detection extends the hold period by resetting the timer
// This simulates continuous occupancy (person still present)
lastDetectionTime = millis();
Serial.printf("Occupancy still detected. Resetting hold timer to %u seconds (HoldTime)\n", OccupancySensor.getHoldTime());
}
}
return occupancyState;
}
void setup() {
// Initialize the USER BUTTON (Boot button) that will be used to decommission the Matter Node
pinMode(buttonPin, INPUT_PULLUP);
Serial.begin(115200);
// CONFIG_ENABLE_CHIPOBLE is enabled when BLE is used to commission the Matter Network
#if !CONFIG_ENABLE_CHIPOBLE
// Manually connect to WiFi
WiFi.begin(ssid, password);
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
#endif
// Initialize Preferences and read stored HoldTime value
matterPref.begin("MatterPrefs", false);
uint16_t storedHoldTime = matterPref.getUShort(holdTimePrefKey, HOLD_TIME_DEFAULT);
// Validate stored value is within limits
if (storedHoldTime < HOLD_TIME_MIN || storedHoldTime > HOLD_TIME_MAX) {
uint16_t invalidValue = storedHoldTime;
storedHoldTime = HOLD_TIME_DEFAULT;
Serial.printf("Invalid stored HoldTime (%u), using default: %u seconds\n", invalidValue, HOLD_TIME_DEFAULT);
} else if (storedHoldTime != HOLD_TIME_DEFAULT) {
Serial.printf("Restored HoldTime from Preferences: %u seconds\n", storedHoldTime);
}
// Register callback for HoldTime changes from Matter Controller
OccupancySensor.onHoldTimeChange([](uint16_t holdTime_seconds) -> bool {
Serial.printf("HoldTime changed to %u seconds by Matter Controller\n", holdTime_seconds);
// Store the new HoldTime value to Preferences for persistence across reboots
matterPref.putUShort(holdTimePrefKey, holdTime_seconds);
// The callback can return false to reject the change, or true to accept it
// In this case, we always accept the change and update the simulator
return true;
});
// set initial occupancy sensor state as false and connected to a PIR sensor type (default)
OccupancySensor.begin();
// Matter beginning - Last step, after all EndPoints are initialized
Matter.begin();
// Set HoldTimeLimits after Matter.begin() (optional, but recommended for validation)
if (!OccupancySensor.setHoldTimeLimits(HOLD_TIME_MIN, HOLD_TIME_MAX, HOLD_TIME_DEFAULT)) {
Serial.println("Warning: Failed to set HoldTimeLimits");
} else {
Serial.printf("HoldTimeLimits set: Min=%u, Max=%u, Default=%u seconds\n", HOLD_TIME_MIN, HOLD_TIME_MAX, HOLD_TIME_DEFAULT);
}
// Set initial HoldTime (use stored value if valid, otherwise use default)
// This must be done after Matter.begin() because setHoldTime() requires the Matter event loop
if (!OccupancySensor.setHoldTime(storedHoldTime)) {
Serial.printf("Warning: Failed to set HoldTime to %u seconds\n", storedHoldTime);
} else {
Serial.printf("HoldTime set to: %u seconds\n", storedHoldTime);
}
Serial.printf("Initial HoldTime: %u seconds\n", OccupancySensor.getHoldTime());
// Check Matter Accessory Commissioning state, which may change during execution of loop()
if (!Matter.isDeviceCommissioned()) {
Serial.println("");
Serial.println("Matter Node is not commissioned yet.");
Serial.println("Initiate the device discovery in your Matter environment.");
Serial.println("Commission it to your Matter hub with the manual pairing code or QR code");
Serial.printf("Manual pairing code: %s\r\n", Matter.getManualPairingCode().c_str());
Serial.printf("QR code URL: %s\r\n", Matter.getOnboardingQRCodeUrl().c_str());
// waits for Matter Occupancy Sensor Commissioning.
uint32_t timeCount = 0;
while (!Matter.isDeviceCommissioned()) {
delay(100);
if ((timeCount++ % 50) == 0) { // 50*100ms = 5 sec
Serial.println("Matter Node not commissioned yet. Waiting for commissioning.");
}
}
Serial.println("Matter Node is commissioned and connected to the network. Ready for use.");
}
}
void loop() {
// Check if the button has been pressed
if (digitalRead(buttonPin) == LOW && !button_state) {
// deals with button debouncing
button_time_stamp = millis(); // record the time while the button is pressed.
button_state = true; // pressed.
}
if (button_state && digitalRead(buttonPin) == HIGH) {
button_state = false; // released
}
// Onboard User Button is kept pressed for longer than 5 seconds in order to decommission matter node
uint32_t time_diff = millis() - button_time_stamp;
if (button_state && time_diff > decommissioningTimeout) {
Serial.println("Decommissioning Occupancy Sensor Matter Accessory. It shall be commissioned again.");
Matter.decommission();
button_time_stamp = millis(); // avoid running decommissining again, reboot takes a second or so
}
// Check Simulated Occupancy Sensor and set Matter Attribute
OccupancySensor.setOccupancy(simulatedHWOccupancySensor());
delay(50);
}
This example shows:
* How to configure HoldTimeLimits after Matter.begin()
* How to set and persist HoldTime values
* How to use the onHoldTimeChange() callback
* How to implement HoldTime expiration logic in sensor simulation