-
Notifications
You must be signed in to change notification settings - Fork 7.8k
feat(matter): adds HoldTime attribute to the Matter Occupancy endpoint #12146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+1,058
−11
Merged
Changes from 1 commit
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
743de9f
feat(matter): adds hold time attribute to the Matter Occupancy endpoint
SuGlider 78d3f49
fix(matter): changing type to hold bigger value
SuGlider 94e0bb8
fix(matter): code reorganization
SuGlider 2efca1d
Merge branch 'master' into feature/matter_occupancy_hold_time
SuGlider 96adbe0
feat(matter): update documentation to follow the same standard
SuGlider 791d3a5
ci(pre-commit): Apply automatic fixes
pre-commit-ci-lite[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
232 changes: 232 additions & 0 deletions
232
libraries/Matter/examples/MatterOccupancyWithHoldTime/MatterOccupancyWithHoldTime.ino
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,232 @@ | ||
| // 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 | ||
|
|
||
| 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."); | ||
| } | ||
| } | ||
|
|
||
| // 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) | ||
| uint16_t holdTime_ms = OccupancySensor.getHoldTime() * 1000; // Convert seconds to milliseconds | ||
SuGlider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // 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; | ||
| } | ||
SuGlider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.