From 51d1bd81f0cdb375fd7c92a4b440e3a2f36441e5 Mon Sep 17 00:00:00 2001 From: SuGlider Date: Thu, 4 Dec 2025 13:50:29 -0300 Subject: [PATCH 01/22] feat(matter): adds temperature controlled cabinet matter endpoint --- CMakeLists.txt | 1 + .../ep_temperature_controlled_cabinet.rst | 293 +++++++++ docs/en/matter/matter.rst | 2 + .../MatterTemperatureControlledCabinet.ino | 187 ++++++ .../README.md | 202 ++++++ .../MatterTemperatureControlledCabinet/ci.yml | 5 + ...tterTemperatureControlledCabinetLevels.ino | 209 ++++++ .../README.md | 204 ++++++ .../ci.yml | 5 + libraries/Matter/keywords.txt | 13 + libraries/Matter/src/Matter.h | 2 + .../MatterTemperatureControlledCabinet.cpp | 621 ++++++++++++++++++ .../MatterTemperatureControlledCabinet.h | 96 +++ 13 files changed, 1840 insertions(+) create mode 100644 docs/en/matter/ep_temperature_controlled_cabinet.rst create mode 100644 libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino create mode 100644 libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md create mode 100644 libraries/Matter/examples/MatterTemperatureControlledCabinet/ci.yml create mode 100644 libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino create mode 100644 libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/README.md create mode 100644 libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/ci.yml create mode 100644 libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp create mode 100644 libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4732354aa73..7f2f5bc5186 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,6 +191,7 @@ set(ARDUINO_LIBRARY_Matter_SRCS libraries/Matter/src/MatterEndpoints/MatterEnhancedColorLight.cpp libraries/Matter/src/MatterEndpoints/MatterFan.cpp libraries/Matter/src/MatterEndpoints/MatterTemperatureSensor.cpp + libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp libraries/Matter/src/MatterEndpoints/MatterHumiditySensor.cpp libraries/Matter/src/MatterEndpoints/MatterContactSensor.cpp libraries/Matter/src/MatterEndpoints/MatterPressureSensor.cpp diff --git a/docs/en/matter/ep_temperature_controlled_cabinet.rst b/docs/en/matter/ep_temperature_controlled_cabinet.rst new file mode 100644 index 00000000000..395be1fe6a6 --- /dev/null +++ b/docs/en/matter/ep_temperature_controlled_cabinet.rst @@ -0,0 +1,293 @@ +####################################### +MatterTemperatureControlledCabinet +####################################### + +About +----- + +The ``MatterTemperatureControlledCabinet`` class provides a temperature controlled cabinet endpoint for Matter networks. This endpoint implements the Matter temperature control standard for managing temperature setpoints with min/max limits, optional step control, or temperature level support. + +**Important:** The ``temperature_number`` and ``temperature_level`` features are **mutually exclusive**. Only one can be enabled at a time. Use ``begin(tempSetpoint, minTemp, maxTemp, step)`` for temperature_number mode or ``begin(supportedLevels, levelCount, selectedLevel)`` for temperature_level mode. + +**Features:** +* Two initialization modes: + * **Temperature Number Mode** (``begin(tempSetpoint, minTemp, maxTemp, step)``): Temperature setpoint control with min/max limits and optional step + * **Temperature Level Mode** (``begin(supportedLevels, levelCount, selectedLevel)``): Temperature level control with array of supported levels +* 1/100th degree Celsius precision (for temperature_number mode) +* Min/max temperature limits with validation (temperature_number mode) +* Optional temperature step control (temperature_number mode) +* Temperature level array support (temperature_level mode) +* Automatic setpoint validation against limits +* Feature validation - methods return errors if called with wrong feature mode +* Integration with Apple HomeKit, Amazon Alexa, and Google Home +* Matter standard compliance + +**Use Cases:** +* Refrigerators and freezers +* Wine coolers +* Medical storage cabinets +* Laboratory equipment +* Food storage systems +* Temperature-controlled storage units + +API Reference +------------- + +Constructor +*********** + +MatterTemperatureControlledCabinet +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Creates a new Matter temperature controlled cabinet endpoint. + +.. code-block:: arduino + + MatterTemperatureControlledCabinet(); + +Initialization +************** + +begin +^^^^^ + +Initializes the Matter temperature controlled cabinet endpoint with **temperature_number** feature mode. This enables temperature setpoint control with min/max limits and optional step. + +**Note:** This mode is mutually exclusive with temperature_level mode. Use ``begin(supportedLevels, levelCount, selectedLevel)`` for temperature level control. + +.. code-block:: arduino + + bool begin(double tempSetpoint = 0.00, double minTemperature = -10.0, double maxTemperature = 32.0, double step = 0.50); + +* ``tempSetpoint`` - Initial temperature setpoint in Celsius (default: 0.00) +* ``minTemperature`` - Minimum allowed temperature in Celsius (default: -10.0) +* ``maxTemperature`` - Maximum allowed temperature in Celsius (default: 32.0) +* ``step`` - Temperature step value in Celsius for step control feature (default: 0.50, disabled if 0) + +This function will return ``true`` if successful, ``false`` otherwise. + +**Note:** The implementation stores temperature with 1/100th degree Celsius precision internally. If step is greater than 0, the temperature_step feature will be enabled. + +begin (overloaded) +^^^^^^^^^^^^^^^^^^ + +Initializes the Matter temperature controlled cabinet endpoint with **temperature_level** feature mode. This enables temperature level control with an array of supported levels. + +**Note:** This mode is mutually exclusive with temperature_number mode. Use ``begin(tempSetpoint, minTemp, maxTemp, step)`` for temperature setpoint control. + +.. code-block:: arduino + + bool begin(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel = 0); + +* ``supportedLevels`` - Pointer to array of temperature level values (uint8_t, 0-255) +* ``levelCount`` - Number of levels in the array (maximum: 16) +* ``selectedLevel`` - Initial selected temperature level (default: 0) + +This function will return ``true`` if successful, ``false`` otherwise. + +**Note:** The maximum number of supported levels is 16 (defined by ``temperature_control::k_max_temp_level_count``). The array is copied internally, so it does not need to remain valid after the function returns. This method uses a custom endpoint implementation that properly supports the temperature_level feature. + +end +^^^ + +Stops processing Matter temperature controlled cabinet events. + +.. code-block:: arduino + + void end(); + +Temperature Setpoint Control +**************************** + +setTemperatureSetpoint +^^^^^^^^^^^^^^^^^^^^^^^ + +Sets the temperature setpoint value. The setpoint will be validated against min/max limits. + +**Requires:** temperature_number feature mode (use ``begin()``) + +.. code-block:: arduino + + bool setTemperatureSetpoint(double temperature); + +* ``temperature`` - Temperature setpoint in Celsius + +This function will return ``true`` if successful, ``false`` otherwise (e.g., if temperature is out of range or wrong feature mode). + +**Note:** Temperature is stored with 1/100th degree Celsius precision. The setpoint must be within the min/max temperature range. This method will return ``false`` and log an error if called when using temperature_level mode. + +getTemperatureSetpoint +^^^^^^^^^^^^^^^^^^^^^^ + +Gets the current temperature setpoint value. + +.. code-block:: arduino + + double getTemperatureSetpoint(); + +This function will return the temperature setpoint in Celsius with 1/100th degree precision. + +Min/Max Temperature Control +**************************** + +setMinTemperature +^^^^^^^^^^^^^^^^^ + +Sets the minimum allowed temperature. + +**Requires:** temperature_number feature mode (use ``begin()``) + +.. code-block:: arduino + + bool setMinTemperature(double temperature); + +* ``temperature`` - Minimum temperature in Celsius + +This function will return ``true`` if successful, ``false`` otherwise. Will return ``false`` and log an error if called when using temperature_level mode. + +getMinTemperature +^^^^^^^^^^^^^^^^^ + +Gets the minimum allowed temperature. + +.. code-block:: arduino + + double getMinTemperature(); + +This function will return the minimum temperature in Celsius with 1/100th degree precision. + +setMaxTemperature +^^^^^^^^^^^^^^^^^ + +Sets the maximum allowed temperature. + +**Requires:** temperature_number feature mode (use ``begin()``) + +.. code-block:: arduino + + bool setMaxTemperature(double temperature); + +* ``temperature`` - Maximum temperature in Celsius + +This function will return ``true`` if successful, ``false`` otherwise. Will return ``false`` and log an error if called when using temperature_level mode. + +getMaxTemperature +^^^^^^^^^^^^^^^^^ + +Gets the maximum allowed temperature. + +.. code-block:: arduino + + double getMaxTemperature(); + +This function will return the maximum temperature in Celsius with 1/100th degree precision. + +Temperature Step Control (Optional) +*********************************** + +setStep +^^^^^^^ + +Sets the temperature step value. This enables the temperature_step feature. + +**Requires:** temperature_number feature mode (use ``begin()``) + +.. code-block:: arduino + + bool setStep(double step); + +* ``step`` - Temperature step value in Celsius + +This function will return ``true`` if successful, ``false`` otherwise. + +**Note:** The temperature_step feature requires the temperature_number feature to be enabled. This method will return ``false`` and log an error if called when using temperature_level mode. + +getStep +^^^^^^^ + +Gets the current temperature step value. + +.. code-block:: arduino + + double getStep(); + +This function will return the temperature step in Celsius with 1/100th degree precision. + +Temperature Level Control (Optional) +************************************* + +setSelectedTemperatureLevel +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Sets the selected temperature level. + +**Requires:** temperature_level feature mode (use ``begin(supportedLevels, levelCount, selectedLevel)``) + +.. code-block:: arduino + + bool setSelectedTemperatureLevel(uint8_t level); + +* ``level`` - Temperature level (0-255) + +This function will return ``true`` if successful, ``false`` otherwise. + +**Note:** Temperature level and temperature number features are mutually exclusive. This method will return ``false`` and log an error if called when using temperature_number mode. + +getSelectedTemperatureLevel +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Gets the current selected temperature level. + +.. code-block:: arduino + + uint8_t getSelectedTemperatureLevel(); + +This function will return the selected temperature level (0-255). + +setSupportedTemperatureLevels +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Sets the supported temperature levels array. + +**Requires:** temperature_level feature mode (use ``begin(supportedLevels, levelCount, selectedLevel)``) + +.. code-block:: arduino + + bool setSupportedTemperatureLevels(uint8_t *levels, uint16_t count); + +* ``levels`` - Pointer to array of temperature level values (array is copied internally) +* ``count`` - Number of levels in the array (maximum: 16) + +This function will return ``true`` if successful, ``false`` otherwise. + +**Note:** The maximum number of supported levels is 16. The array is copied internally, so it does not need to remain valid after the function returns. This method will return ``false`` and log an error if called when using temperature_number mode or if count exceeds the maximum. + +getSupportedTemperatureLevelsCount +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Gets the count of supported temperature levels. + +.. code-block:: arduino + + uint16_t getSupportedTemperatureLevelsCount(); + +This function will return the number of supported temperature levels. + +Example +------- + +Temperature Controlled Cabinet +****************************** + +The example demonstrates the temperature_number mode with dynamic temperature updates. The temperature setpoint automatically cycles between the minimum and maximum limits every 1 second using the configured step value, allowing Matter controllers to observe real-time changes. + +.. literalinclude:: ../../../libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino + :language: arduino + +Temperature Controlled Cabinet (Level Mode) +******************************************** + +A separate example demonstrates the temperature_level mode with dynamic level updates. The temperature level automatically cycles through all supported levels every 1 second, allowing Matter controllers to observe real-time changes. + +See ``MatterTemperatureControlledCabinetLevels`` example for the temperature level mode implementation. + diff --git a/docs/en/matter/matter.rst b/docs/en/matter/matter.rst index 7fd3cc9ee2e..ccadad4ef76 100644 --- a/docs/en/matter/matter.rst +++ b/docs/en/matter/matter.rst @@ -130,6 +130,8 @@ The library provides specialized endpoint classes for different device types. Ea **Control Endpoints:** +* ``MatterTemperatureControlledCabinet``: Temperature controlled cabinet (setpoint control with min/max limits) + * ``MatterFan``: Fan with speed and mode control * ``MatterThermostat``: Thermostat with temperature control and setpoints * ``MatterOnOffPlugin``: On/off plugin unit (power outlet/relay) diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino b/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino new file mode 100644 index 00000000000..877f7492265 --- /dev/null +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino @@ -0,0 +1,187 @@ +// 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 demonstrates the Temperature Number mode of the Matter Temperature Controlled Cabinet Device. + * + * This example 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 Temperature Controlled Cabinet Device using temperature_number feature. + * The Temperature Controlled Cabinet can be controlled via Matter controllers to set + * temperature setpoint with min/max limits and optional step control. + * + * This mode is mutually exclusive with temperature_level mode. + * See MatterTemperatureControlledCabinetLevels example for temperature level control. + */ + +// Matter Manager +#include +#if !CONFIG_ENABLE_CHIPOBLE +// if the device can be commissioned using BLE, WiFi is not used - save flash space +#include +#endif + +// List of Matter Endpoints for this Node +// Matter Temperature Controlled Cabinet Endpoint +MatterTemperatureControlledCabinet TemperatureCabinet; + +// 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 button +const uint8_t buttonPin = BOOT_PIN; // Set your pin here. Using BOOT Button. + +// Button control - decommision the Matter Node +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 Temperature Controlled Cabinet with: + // - Initial setpoint: 4.0°C (typical refrigerator temperature) + // - Min temperature: -10.0°C + // - Max temperature: 10.0°C + // - Step: 0.5°C (optional, for temperature_step feature) + TemperatureCabinet.begin(4.0, -10.0, 10.0, 0.5); + + // 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 Temperature Controlled Cabinet 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."); + } + + // Print initial configuration + Serial.println("\nTemperature Controlled Cabinet Configuration:"); + Serial.printf(" Setpoint: %.02f°C\n", TemperatureCabinet.getTemperatureSetpoint()); + Serial.printf(" Min Temperature: %.02f°C\n", TemperatureCabinet.getMinTemperature()); + Serial.printf(" Max Temperature: %.02f°C\n", TemperatureCabinet.getMaxTemperature()); + Serial.printf(" Step: %.02f°C\n", TemperatureCabinet.getStep()); +} + +void loop() { + static uint32_t timeCounter = 0; + static uint32_t lastUpdateTime = 0; + static bool initialized = false; + static bool increasing = true; // Direction of temperature change + static double currentSetpoint = 0.0; // Will be initialized from actual setpoint + + // Initialize currentSetpoint from actual setpoint on first run + if (!initialized) { + currentSetpoint = TemperatureCabinet.getTemperatureSetpoint(); + initialized = true; + } + + // Update temperature setpoint dynamically every 1 second + uint32_t currentTime = millis(); + if (currentTime - lastUpdateTime >= 1000) { // 1 second interval + lastUpdateTime = currentTime; + + double minTemp = TemperatureCabinet.getMinTemperature(); + double maxTemp = TemperatureCabinet.getMaxTemperature(); + double step = TemperatureCabinet.getStep(); + + // Calculate next setpoint based on direction and step + if (increasing) { + currentSetpoint += step; + if (currentSetpoint >= maxTemp) { + currentSetpoint = maxTemp; + increasing = false; // Reverse direction + } + } else { + currentSetpoint -= step; + if (currentSetpoint <= minTemp) { + currentSetpoint = minTemp; + increasing = true; // Reverse direction + } + } + + // Update the temperature setpoint + if (TemperatureCabinet.setTemperatureSetpoint(currentSetpoint)) { + Serial.printf("Temperature setpoint updated to: %.02f°C (Range: %.02f°C to %.02f°C)\r\n", + currentSetpoint, minTemp, maxTemp); + } else { + Serial.printf("Failed to update temperature setpoint to: %.02f°C\r\n", currentSetpoint); + } + } + + // Print the current temperature setpoint every 5s + if (!(timeCounter++ % 10)) { // delaying for 500ms x 10 = 5s + // Print the current temperature setpoint value + Serial.printf("Current Temperature Setpoint: %.02f°C (Range: %.02f°C to %.02f°C)\r\n", + TemperatureCabinet.getTemperatureSetpoint(), + TemperatureCabinet.getMinTemperature(), + TemperatureCabinet.getMaxTemperature()); + } + + // 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 (digitalRead(buttonPin) == HIGH && button_state) { + 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 Temperature Controlled Cabinet Matter Accessory. It shall be commissioned again."); + Matter.decommission(); + button_time_stamp = millis(); // avoid running decommissining again, reboot takes a second or so + } + + delay(500); +} + diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md b/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md new file mode 100644 index 00000000000..684c0c6a877 --- /dev/null +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md @@ -0,0 +1,202 @@ +# Matter Temperature Controlled Cabinet Example + +This example demonstrates how to create a Matter-compatible temperature controlled cabinet device using an ESP32 SoC microcontroller with the **temperature_number** feature mode. This mode provides precise temperature setpoint control with min/max limits and optional step control. + +**Important:** The `temperature_number` and `temperature_level` features are **mutually exclusive**. Only one can be enabled at a time. See `MatterTemperatureControlledCabinetLevels` example for temperature level control mode. + +## Supported Targets + +| SoC | Wi-Fi | Thread | BLE Commissioning | Status | +| --- | ---- | ------ | ----------------- | ------ | +| ESP32 | ✅ | ❌ | ❌ | Fully supported | +| ESP32-S2 | ✅ | ❌ | ❌ | Fully supported | +| ESP32-S3 | ✅ | ❌ | ✅ | Fully supported | +| ESP32-C3 | ✅ | ❌ | ✅ | Fully supported | +| ESP32-C5 | ✅ | ❌ | ✅ | Fully supported | +| ESP32-C6 | ✅ | ❌ | ✅ | Fully supported | +| ESP32-H2 | ❌ | ✅ | ✅ | Supported (Thread only) | + +### Note on Commissioning: + +- **ESP32 & ESP32-S2** do not support commissioning over Bluetooth LE. For these chips, you must provide Wi-Fi credentials directly in the sketch code so they can connect to your network manually. +- **ESP32-C6** Although it has Thread support, the ESP32 Arduino Matter Library has been pre compiled using Wi-Fi only. In order to configure it for Thread-only operation it is necessary to build the project as an ESP-IDF component and to disable the Matter Wi-Fi station feature. +- **ESP32-C5** Although it has Thread support, the ESP32 Arduino Matter Library has been pre compiled using Wi-Fi only. In order to configure it for Thread-only operation it is necessary to build the project as an ESP-IDF component and to disable the Matter Wi-Fi station feature. + +## Features + +- Matter protocol implementation for a temperature controlled cabinet device +- Support for both Wi-Fi and Thread(*) connectivity +- Temperature setpoint control with min/max limits +- Temperature step control (optional) +- Temperature setpoint validation against min/max limits +- Button control for factory reset (decommission) +- Matter commissioning via QR code or manual pairing code +- Integration with Apple HomeKit, Amazon Alexa, and Google Home +(*) It is necessary to compile the project using Arduino as IDF Component. + +## Use Case + +Use this mode when you need precise temperature control with specific setpoint values (e.g., 4.0°C for a refrigerator, -18.0°C for a freezer). For preset-based temperature control using levels, see the `MatterTemperatureControlledCabinetLevels` example. + +## Hardware Requirements + +- ESP32 compatible development board (see supported targets table) +- User button for factory reset (uses BOOT button by default) +- Optional: Connect temperature control hardware (relays, heaters, coolers, etc.) to implement actual temperature control + +## Pin Configuration + +- **Button**: Uses `BOOT_PIN` by default + +## Software Setup + +### Prerequisites + +1. Install the Arduino IDE (2.0 or newer recommended) +2. Install ESP32 Arduino Core with Matter support +3. ESP32 Arduino libraries: + - `Matter` + - `Wi-Fi` (only for ESP32 and ESP32-S2) + +### Configuration + +Before uploading the sketch, configure the following: + +1. **Wi-Fi credentials** (if not using BLE commissioning - mandatory for ESP32 | ESP32-S2): + ```cpp + const char *ssid = "your-ssid"; // Change to your Wi-Fi SSID + const char *password = "your-password"; // Change to your Wi-Fi password + ``` + +2. **Button pin configuration** (optional): + By default, the `BOOT` button (GPIO 0) is used for factory reset. You can change this to a different pin if needed. + ```cpp + const uint8_t buttonPin = BOOT_PIN; // Set your button pin here + ``` + +3. **Temperature range configuration** (optional): + Adjust the initial temperature setpoint, min, max, and step values in the `begin()` call: + ```cpp + TemperatureCabinet.begin(4.0, -10.0, 10.0, 0.5); + // Parameters: setpoint, min_temp, max_temp, step (all in Celsius) + ``` + +## Building and Flashing + +1. Open the `MatterTemperatureControlledCabinet.ino` sketch in the Arduino IDE. +2. Select your ESP32 board from the **Tools > Board** menu. +3. Connect your ESP32 board to your computer via USB. +4. Click the **Upload** button to compile and flash the sketch. + +## Expected Output + +Once the sketch is running, open the Serial Monitor at a baud rate of **115200**. The Wi-Fi connection messages will be displayed only for ESP32 and ESP32-S2. Other targets will use Matter CHIPoBLE to automatically setup the IP Network. You should see output similar to the following, which provides the necessary information for commissioning: + +``` +Connecting to your-wifi-ssid +....... +Wi-Fi connected +IP address: 192.168.1.100 + +Matter Node is not commissioned yet. +Initiate the device discovery in your Matter environment. +Commission it to your Matter hub with the manual pairing code or QR code +Manual pairing code: 34970112332 +QR code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=MT%3A6FCJ142C00KA0648G00 +Matter Node not commissioned yet. Waiting for commissioning. +Matter Node not commissioned yet. Waiting for commissioning. +... +Matter Node is commissioned and connected to the network. Ready for use. + +Temperature Controlled Cabinet Configuration: + Setpoint: 4.00°C + Min Temperature: -10.00°C + Max Temperature: 10.00°C + Step: 0.50°C +Temperature setpoint updated to: 4.50°C (Range: -10.00°C to 10.00°C) +Temperature setpoint updated to: 5.00°C (Range: -10.00°C to 10.00°C) +Temperature setpoint updated to: 5.50°C (Range: -10.00°C to 10.00°C) +... +Current Temperature Setpoint: 6.00°C (Range: -10.00°C to 10.00°C) +``` + +## Using the Device + +### Manual Control + +The user button (BOOT button by default) provides manual control: + +- **Long press (>5 seconds)**: Factory reset the device (decommission) + +### Smart Home Integration + +Use a Matter-compatible hub (like an Apple HomePod, Google Nest Hub, or Amazon Echo) to commission the device. + +#### Apple Home + +1. Open the Home app on your iOS device +2. Tap the "+" button > Add Accessory +3. Scan the QR code displayed in the Serial Monitor, or +4. Tap "I Don't Have a Code or Cannot Scan" and enter the manual pairing code +5. Follow the prompts to complete setup +6. The device will appear as a temperature controlled cabinet in your Home app +7. You can adjust the temperature setpoint within the min/max range + +#### Amazon Alexa + +1. Open the Alexa app +2. Tap More > Add Device > Matter +3. Select "Scan QR code" or "Enter code manually" +4. Complete the setup process +5. The temperature controlled cabinet will appear in your Alexa app +6. You can control the temperature setpoint and set up routines + +#### Google Home + +1. Open the Google Home app +2. Tap "+" > Set up device > New device +3. Choose "Matter device" +4. Scan the QR code or enter the manual pairing code +5. Follow the prompts to complete setup +6. The temperature controlled cabinet will appear in your Google Home app +7. You can control the temperature setpoint + +## Code Structure + +The MatterTemperatureControlledCabinet example consists of the following main components: + +1. **`setup()`**: Initializes hardware (button), configures Wi-Fi (if needed), sets up the Matter Temperature Controlled Cabinet endpoint with initial temperature configuration, and waits for Matter commissioning. +2. **`loop()`**: + - **Dynamic Temperature Updates**: Automatically changes the temperature setpoint every 1 second, cycling between the minimum and maximum temperature limits using the configured step value. This demonstrates the temperature control functionality and allows Matter controllers to observe real-time changes. + - Periodically prints the current temperature setpoint (every 5 seconds) + - Handles button input for factory reset + +## API Usage + +The example demonstrates the following API methods: + +- `begin(tempSetpoint, minTemperature, maxTemperature, step)` - Initialize the cabinet with temperature settings +- `getTemperatureSetpoint()` - Get current temperature setpoint +- `setTemperatureSetpoint(temperature)` - Set temperature setpoint (validated against min/max) +- `getMinTemperature()` / `getMaxTemperature()` - Get temperature limits +- `setMinTemperature(temperature)` / `setMaxTemperature(temperature)` - Set temperature limits +- `getStep()` / `setStep(step)` - Get/set temperature step value + +## Troubleshooting + +- **Device not visible during commissioning**: Ensure Wi-Fi or Thread connectivity is properly configured +- **Temperature setpoint not updating**: Check Serial Monitor output to verify setpoint changes are being processed +- **Setpoint out of range error**: Ensure the setpoint is within the min/max temperature range +- **Failed to commission**: Try factory resetting the device by long-pressing the button. Other option would be to erase the SoC Flash Memory by using `Arduino IDE Menu` -> `Tools` -> `Erase All Flash Before Sketch Upload: "Enabled"` or directly with `esptool.py --port erase_flash` +- **No serial output**: Check baudrate (115200) and USB connection + +## Related Documentation + +- [Matter Overview](https://docs.espressif.com/projects/arduino-esp32/en/latest/matter/matter.html) +- [Matter Endpoint Base Class](https://docs.espressif.com/projects/arduino-esp32/en/latest/matter/matter_ep.html) +- [Matter Temperature Controlled Cabinet Endpoint](https://docs.espressif.com/projects/arduino-esp32/en/latest/matter/ep_temperature_controlled_cabinet.html) + +## License + +This example is licensed under the Apache License, Version 2.0. + diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinet/ci.yml b/libraries/Matter/examples/MatterTemperatureControlledCabinet/ci.yml new file mode 100644 index 00000000000..1cbd404d6ed --- /dev/null +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinet/ci.yml @@ -0,0 +1,5 @@ +fqbn_append: PartitionScheme=huge_app + +requires: + - CONFIG_ESP_MATTER_ENABLE_DATA_MODEL=y + diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino new file mode 100644 index 00000000000..c934660555f --- /dev/null +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino @@ -0,0 +1,209 @@ +// 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 demonstrates the Temperature Level mode of the Matter Temperature Controlled Cabinet Device. + * + * This example 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 Temperature Controlled Cabinet Device using temperature_level feature. + * The Temperature Controlled Cabinet can be controlled via Matter controllers to set + * temperature levels from a predefined array of supported levels. + * + * This mode is mutually exclusive with temperature_number mode. + * See MatterTemperatureControlledCabinet example for temperature setpoint control. + */ + +// Matter Manager +#include +#if !CONFIG_ENABLE_CHIPOBLE +// if the device can be commissioned using BLE, WiFi is not used - save flash space +#include +#endif + +// List of Matter Endpoints for this Node +// Matter Temperature Controlled Cabinet Endpoint +MatterTemperatureControlledCabinet TemperatureCabinet; + +// 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 button +const uint8_t buttonPin = BOOT_PIN; // Set your pin here. Using BOOT Button. + +// Button control - decommision the Matter Node +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 + +// Temperature levels array - these represent different temperature presets +// Example: 0 = Off, 1 = Low, 2 = Medium, 3 = High, 4 = Maximum +// The actual temperature values are application-specific +uint8_t supportedLevels[] = {0, 1, 2, 3, 4}; +const uint16_t levelCount = sizeof(supportedLevels) / sizeof(supportedLevels[0]); +const uint8_t initialLevel = 2; // Start with level 2 (Medium) + +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 Temperature Controlled Cabinet with temperature_level feature: + // - supportedLevels: Array of temperature level values (0-255) + // - levelCount: Number of levels in the array + // - initialLevel: Initial selected temperature level + // + // Note: This mode is mutually exclusive with temperature_number mode. + // See MatterTemperatureControlledCabinet example for temperature setpoint control. + if (!TemperatureCabinet.begin(supportedLevels, levelCount, initialLevel)) { + Serial.println("Failed to initialize Temperature Controlled Cabinet!"); + while (1) { + delay(1000); + } + } + + // 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 Temperature Controlled Cabinet 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."); + } + + // Print initial configuration + Serial.println("\nTemperature Controlled Cabinet Configuration (Temperature Level Mode):"); + Serial.printf(" Selected Level: %u\n", TemperatureCabinet.getSelectedTemperatureLevel()); + Serial.printf(" Supported Levels Count: %u\n", TemperatureCabinet.getSupportedTemperatureLevelsCount()); + Serial.print(" Supported Levels: "); + for (uint16_t i = 0; i < levelCount; i++) { + Serial.printf("%u", supportedLevels[i]); + if (i < levelCount - 1) { + Serial.print(", "); + } + } + Serial.println(); +} + +void loop() { + static uint32_t timeCounter = 0; + static uint32_t lastUpdateTime = 0; + static bool initialized = false; + static uint16_t currentLevelIndex = 0; // Will be initialized from actual level + + // Initialize currentLevelIndex from actual selected level on first run + if (!initialized) { + uint8_t currentLevel = TemperatureCabinet.getSelectedTemperatureLevel(); + // Find the index of current level in supportedLevels array + for (uint16_t i = 0; i < levelCount; i++) { + if (supportedLevels[i] == currentLevel) { + currentLevelIndex = i; + break; + } + } + initialized = true; + } + + // Update temperature level dynamically every 1 second + uint32_t currentTime = millis(); + if (currentTime - lastUpdateTime >= 1000) { // 1 second interval + lastUpdateTime = currentTime; + + // Cycle through supported levels + currentLevelIndex = (currentLevelIndex + 1) % levelCount; + uint8_t newLevel = supportedLevels[currentLevelIndex]; + + // Update the temperature level + if (TemperatureCabinet.setSelectedTemperatureLevel(newLevel)) { + Serial.printf("Temperature level updated to: %u (Supported Levels: ", newLevel); + for (uint16_t i = 0; i < levelCount; i++) { + Serial.printf("%u", supportedLevels[i]); + if (i < levelCount - 1) { + Serial.print(", "); + } + } + Serial.println(")"); + } else { + Serial.printf("Failed to update temperature level to: %u\r\n", newLevel); + } + } + + // Print the current temperature level every 5s + if (!(timeCounter++ % 10)) { // delaying for 500ms x 10 = 5s + // Print the current temperature level value + uint8_t currentLevel = TemperatureCabinet.getSelectedTemperatureLevel(); + Serial.printf("Current Temperature Level: %u (Supported Levels: ", currentLevel); + for (uint16_t i = 0; i < levelCount; i++) { + Serial.printf("%u", supportedLevels[i]); + if (i < levelCount - 1) { + Serial.print(", "); + } + } + Serial.println(")"); + } + + // 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 (digitalRead(buttonPin) == HIGH && button_state) { + 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 Temperature Controlled Cabinet Matter Accessory. It shall be commissioned again."); + Matter.decommission(); + button_time_stamp = millis(); // avoid running decommissining again, reboot takes a second or so + } + + delay(500); +} + diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/README.md b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/README.md new file mode 100644 index 00000000000..1881d465a3e --- /dev/null +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/README.md @@ -0,0 +1,204 @@ +# Matter Temperature Controlled Cabinet Example (Temperature Level Mode) + +This example demonstrates how to create a Matter-compatible temperature controlled cabinet device using the **temperature_level** feature mode. This mode provides temperature control using predefined levels (e.g., Off, Low, Medium, High, Maximum) rather than precise temperature setpoint values. + +**Important:** The `temperature_number` and `temperature_level` features are **mutually exclusive**. Only one can be enabled at a time. See `MatterTemperatureControlledCabinet` example for temperature setpoint control mode. + +## Supported Targets + +| SoC | Wi-Fi | Thread | BLE Commissioning | Status | +| --- | ---- | ------ | ----------------- | ------ | +| ESP32 | ✅ | ❌ | ❌ | Fully supported | +| ESP32-S2 | ✅ | ❌ | ❌ | Fully supported | +| ESP32-S3 | ✅ | ❌ | ✅ | Fully supported | +| ESP32-C3 | ✅ | ❌ | ✅ | Fully supported | +| ESP32-C5 | ✅ | ❌ | ✅ | Fully supported | +| ESP32-C6 | ✅ | ❌ | ✅ | Fully supported | +| ESP32-H2 | ❌ | ✅ | ✅ | Supported (Thread only) | + +### Note on Commissioning: + +- **ESP32 & ESP32-S2** do not support commissioning over Bluetooth LE. For these chips, you must provide Wi-Fi credentials directly in the sketch code so they can connect to your network manually. +- **ESP32-C6** Although it has Thread support, the ESP32 Arduino Matter Library has been pre compiled using Wi-Fi only. In order to configure it for Thread-only operation it is necessary to build the project as an ESP-IDF component and to disable the Matter Wi-Fi station feature. +- **ESP32-C5** Although it has Thread support, the ESP32 Arduino Matter Library has been pre compiled using Wi-Fi only. In order to configure it for Thread-only operation it is necessary to build the project as an ESP-IDF component and to disable the Matter Wi-Fi station feature. + +## Features + +- Matter protocol implementation for a temperature controlled cabinet device +- Support for both Wi-Fi and Thread(*) connectivity +- Temperature level control with array of supported levels +- Up to 16 predefined temperature levels +- Button control for factory reset (decommission) +- Matter commissioning via QR code or manual pairing code +- Integration with Apple HomeKit, Amazon Alexa, and Google Home +(*) It is necessary to compile the project using Arduino as IDF Component. + +## Use Case + +Use this mode when you need simple preset-based temperature control (e.g., 0=Off, 1=Low, 2=Medium, 3=High, 4=Maximum) rather than precise temperature values. This is ideal for devices where users select from predefined temperature presets rather than setting exact temperatures. + +## Hardware Requirements + +- ESP32 compatible development board (see supported targets table) +- User button for factory reset (uses BOOT button by default) +- Optional: Connect temperature control hardware (relays, heaters, coolers, etc.) to implement actual temperature control + +## Pin Configuration + +- **Button**: Uses `BOOT_PIN` by default + +## Software Setup + +### Prerequisites + +1. Install the Arduino IDE (2.0 or newer recommended) +2. Install ESP32 Arduino Core with Matter support +3. ESP32 Arduino libraries: + - `Matter` + - `Wi-Fi` (only for ESP32 and ESP32-S2) + +### Configuration + +Before uploading the sketch, configure the following: + +1. **Wi-Fi credentials** (if not using BLE commissioning - mandatory for ESP32 | ESP32-S2): + ```cpp + const char *ssid = "your-ssid"; // Change to your Wi-Fi SSID + const char *password = "your-password"; // Change to your Wi-Fi password + ``` + +2. **Button pin configuration** (optional): + By default, the `BOOT` button (GPIO 0) is used for factory reset. You can change this to a different pin if needed. + ```cpp + const uint8_t buttonPin = BOOT_PIN; // Set your button pin here + ``` + +3. **Temperature levels configuration** (optional): + Adjust the supported levels array and initial level in the sketch: + ```cpp + uint8_t supportedLevels[] = {0, 1, 2, 3, 4}; // Define your levels + const uint16_t levelCount = sizeof(supportedLevels) / sizeof(supportedLevels[0]); + const uint8_t initialLevel = 2; // Initial selected level + TemperatureCabinet.begin(supportedLevels, levelCount, initialLevel); + // Note: The array is copied internally, so it doesn't need to remain valid after begin() returns + ``` + +## Building and Flashing + +1. Open the `MatterTemperatureControlledCabinetLevels.ino` sketch in the Arduino IDE. +2. Select your ESP32 board from the **Tools > Board** menu. +3. Connect your ESP32 board to your computer via USB. +4. Click the **Upload** button to compile and flash the sketch. + +## Expected Output + +Once the sketch is running, open the Serial Monitor at a baud rate of **115200**. The Wi-Fi connection messages will be displayed only for ESP32 and ESP32-S2. Other targets will use Matter CHIPoBLE to automatically setup the IP Network. You should see output similar to the following: + +``` +Connecting to your-wifi-ssid +....... +Wi-Fi connected +IP address: 192.168.1.100 + +Matter Node is not commissioned yet. +Initiate the device discovery in your Matter environment. +Commission it to your Matter hub with the manual pairing code or QR code +Manual pairing code: 34970112332 +QR code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=MT%3A6FCJ142C00KA0648G00 +Matter Node not commissioned yet. Waiting for commissioning. +Matter Node not commissioned yet. Waiting for commissioning. +... +Matter Node is commissioned and connected to the network. Ready for use. + +Temperature Controlled Cabinet Configuration (Temperature Level Mode): + Selected Level: 2 + Supported Levels Count: 5 + Supported Levels: 0, 1, 2, 3, 4 +Temperature level updated to: 3 (Supported Levels: 0, 1, 2, 3, 4) +Temperature level updated to: 4 (Supported Levels: 0, 1, 2, 3, 4) +Temperature level updated to: 0 (Supported Levels: 0, 1, 2, 3, 4) +Temperature level updated to: 1 (Supported Levels: 0, 1, 2, 3, 4) +... +Current Temperature Level: 2 (Supported Levels: 0, 1, 2, 3, 4) +``` + +## Using the Device + +### Manual Control + +The user button (BOOT button by default) provides manual control: + +- **Long press (>5 seconds)**: Factory reset the device (decommission) + +### Smart Home Integration + +Use a Matter-compatible hub (like an Apple HomePod, Google Nest Hub, or Amazon Echo) to commission the device. + +#### Apple Home + +1. Open the Home app on your iOS device +2. Tap the "+" button > Add Accessory +3. Scan the QR code displayed in the Serial Monitor, or +4. Tap "I Don't Have a Code or Cannot Scan" and enter the manual pairing code +5. Follow the prompts to complete setup +6. The device will appear as a temperature controlled cabinet in your Home app +7. You can select from the available temperature levels + +#### Amazon Alexa + +1. Open the Alexa app +2. Tap More > Add Device > Matter +3. Select "Scan QR code" or "Enter code manually" +4. Complete the setup process +5. The temperature controlled cabinet will appear in your Alexa app +6. You can select temperature levels and set up routines + +#### Google Home + +1. Open the Google Home app +2. Tap "+" > Set up device > New device +3. Choose "Matter device" +4. Scan the QR code or enter the manual pairing code +5. Follow the prompts to complete setup +6. The temperature controlled cabinet will appear in your Google Home app +7. You can select from available temperature levels + +## Code Structure + +The MatterTemperatureControlledCabinetLevels example consists of the following main components: + +1. **`setup()`**: Initializes hardware (button), configures Wi-Fi (if needed), sets up the Matter Temperature Controlled Cabinet endpoint with temperature level configuration, and waits for Matter commissioning. +2. **`loop()`**: + - **Dynamic Level Updates**: Automatically cycles through all supported temperature levels every 1 second. This demonstrates the temperature level control functionality and allows Matter controllers to observe real-time changes. + - Periodically prints the current temperature level (every 5 seconds) + - Handles button input for factory reset + +## API Usage + +The example demonstrates the following API methods: + +- `begin(supportedLevels, levelCount, selectedLevel)` - Initialize the cabinet with temperature levels +- `getSelectedTemperatureLevel()` - Get current selected temperature level +- `setSelectedTemperatureLevel(level)` - Set selected temperature level +- `getSupportedTemperatureLevelsCount()` - Get count of supported levels +- `setSupportedTemperatureLevels(levels, count)` - Set supported temperature levels array + +## Troubleshooting + +- **Device not visible during commissioning**: Ensure Wi-Fi or Thread connectivity is properly configured +- **Temperature level not updating**: Check Serial Monitor output to verify level changes are being processed +- **Invalid level error**: Ensure the selected level is in the supported levels array +- **Failed to commission**: Try factory resetting the device by long-pressing the button. Other option would be to erase the SoC Flash Memory by using `Arduino IDE Menu` -> `Tools` -> `Erase All Flash Before Sketch Upload: "Enabled"` or directly with `esptool.py --port erase_flash` +- **No serial output**: Check baudrate (115200) and USB connection +- **Wrong mode error**: Remember that temperature_number and temperature_level modes are mutually exclusive. Make sure you're using the correct example and API methods for temperature level mode + +## Related Documentation + +- [Matter Overview](https://docs.espressif.com/projects/arduino-esp32/en/latest/matter/matter.html) +- [Matter Endpoint Base Class](https://docs.espressif.com/projects/arduino-esp32/en/latest/matter/matter_ep.html) +- [Matter Temperature Controlled Cabinet Endpoint](https://docs.espressif.com/projects/arduino-esp32/en/latest/matter/ep_temperature_controlled_cabinet.html) + +## License + +This example is licensed under the Apache License, Version 2.0. + diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/ci.yml b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/ci.yml new file mode 100644 index 00000000000..1cbd404d6ed --- /dev/null +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/ci.yml @@ -0,0 +1,5 @@ +fqbn_append: PartitionScheme=huge_app + +requires: + - CONFIG_ESP_MATTER_ENABLE_DATA_MODEL=y + diff --git a/libraries/Matter/keywords.txt b/libraries/Matter/keywords.txt index 6c2e092e417..73653c3e06c 100644 --- a/libraries/Matter/keywords.txt +++ b/libraries/Matter/keywords.txt @@ -19,6 +19,7 @@ MatterFan KEYWORD1 FanMode_t KEYWORD1 FanModeSequence_t KEYWORD1 MatterTemperatureSensor KEYWORD1 +MatterTemperatureControlledCabinet KEYWORD1 MatterHumiditySensor KEYWORD1 MatterContactSensor KEYWORD1 MatterPressureSensor KEYWORD1 @@ -86,6 +87,18 @@ onChangeMode KEYWORD2 onChangeSpeedPercent KEYWORD2 setTemperature KEYWORD2 getTemperature KEYWORD2 +setTemperatureSetpoint KEYWORD2 +getTemperatureSetpoint KEYWORD2 +setMinTemperature KEYWORD2 +getMinTemperature KEYWORD2 +setMaxTemperature KEYWORD2 +getMaxTemperature KEYWORD2 +setStep KEYWORD2 +getStep KEYWORD2 +setSelectedTemperatureLevel KEYWORD2 +getSelectedTemperatureLevel KEYWORD2 +setSupportedTemperatureLevels KEYWORD2 +getSupportedTemperatureLevelsCount KEYWORD2 setHumidity KEYWORD2 getHumidity KEYWORD2 setContact KEYWORD2 diff --git a/libraries/Matter/src/Matter.h b/libraries/Matter/src/Matter.h index 89360e81d4b..cf828a32e98 100644 --- a/libraries/Matter/src/Matter.h +++ b/libraries/Matter/src/Matter.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -192,6 +193,7 @@ class ArduinoMatter { friend class MatterEnhancedColorLight; friend class MatterFan; friend class MatterTemperatureSensor; + friend class MatterTemperatureControlledCabinet; friend class MatterHumiditySensor; friend class MatterContactSensor; friend class MatterPressureSensor; diff --git a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp new file mode 100644 index 00000000000..1e813333065 --- /dev/null +++ b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp @@ -0,0 +1,621 @@ +// 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. + +#include +#ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL + +#include +#include +#include +#include +#include + +using namespace esp_matter; +using namespace esp_matter::endpoint; +using namespace esp_matter::cluster; +using namespace chip::app::Clusters; + +// Custom endpoint for temperature_level_controlled_cabinet device +// This endpoint uses temperature_level feature instead of temperature_number +namespace esp_matter { +using namespace cluster; +namespace endpoint { +namespace temperature_level_controlled_cabinet { +typedef struct config { + cluster::descriptor::config_t descriptor; + cluster::temperature_control::config_t temperature_control; +} config_t; + +uint32_t get_device_type_id() { + return ESP_MATTER_TEMPERATURE_CONTROLLED_CABINET_DEVICE_TYPE_ID; +} + +uint8_t get_device_type_version() { + return ESP_MATTER_TEMPERATURE_CONTROLLED_CABINET_DEVICE_TYPE_VERSION; +} + +esp_err_t add(endpoint_t *endpoint, config_t *config) { + if (!endpoint) { + log_e("Endpoint cannot be NULL"); + return ESP_ERR_INVALID_ARG; + } + esp_err_t err = add_device_type(endpoint, get_device_type_id(), get_device_type_version()); + if (err != ESP_OK) { + log_e("Failed to add device type id:%" PRIu32 ",err: %d", get_device_type_id(), err); + return err; + } + + // Create temperature_control cluster with temperature_level feature + // Note: temperature_number and temperature_level are mutually exclusive + temperature_control::create(endpoint, &(config->temperature_control), CLUSTER_FLAG_SERVER, temperature_control::feature::temperature_level::get_id()); + + return ESP_OK; +} + +endpoint_t *create(node_t *node, config_t *config, uint8_t flags, void *priv_data) { + endpoint_t *endpoint = endpoint::create(node, flags, priv_data); + add(endpoint, config); + return endpoint; +} +} // namespace temperature_level_controlled_cabinet +} // namespace endpoint +} // namespace esp_matter + +bool MatterTemperatureControlledCabinet::attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val) { + bool ret = true; + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + log_d("Temperature Controlled Cabinet Attr update callback: endpoint: %u, cluster: %u, attribute: %u", endpoint_id, cluster_id, attribute_id); + + // Handle TemperatureControl cluster attribute changes from Matter controller + if (cluster_id == TemperatureControl::Id) { + switch (attribute_id) { + case TemperatureControl::Attributes::TemperatureSetpoint::Id: + if (useTemperatureNumber) { + rawTempSetpoint = val->val.i16; + log_i("Temperature setpoint changed to %.02fC", (float)rawTempSetpoint / 100.0); + } else { + log_w("Temperature setpoint change ignored - temperature_level feature is active"); + } + break; + + case TemperatureControl::Attributes::MinTemperature::Id: + if (useTemperatureNumber) { + rawMinTemperature = val->val.i16; + log_i("Min temperature changed to %.02fC", (float)rawMinTemperature / 100.0); + } else { + log_w("Min temperature change ignored - temperature_level feature is active"); + } + break; + + case TemperatureControl::Attributes::MaxTemperature::Id: + if (useTemperatureNumber) { + rawMaxTemperature = val->val.i16; + log_i("Max temperature changed to %.02fC", (float)rawMaxTemperature / 100.0); + } else { + log_w("Max temperature change ignored - temperature_level feature is active"); + } + break; + + case TemperatureControl::Attributes::Step::Id: + if (useTemperatureNumber) { + rawStep = val->val.i16; + log_i("Temperature step changed to %.02fC", (float)rawStep / 100.0); + } else { + log_w("Temperature step change ignored - temperature_level feature is active"); + } + break; + + case TemperatureControl::Attributes::SelectedTemperatureLevel::Id: + if (!useTemperatureNumber) { + selectedTempLevel = val->val.u8; + log_i("Selected temperature level changed to %u", selectedTempLevel); + } else { + log_w("Selected temperature level change ignored - temperature_number feature is active"); + } + break; + + case TemperatureControl::Attributes::SupportedTemperatureLevels::Id: + // SupportedTemperatureLevels is read-only (managed via iterator delegate) + // But if a controller tries to write it, we should log it + log_w("SupportedTemperatureLevels change attempted - this attribute is read-only"); + break; + + default: + log_d("Unhandled TemperatureControl Attribute ID: %u", attribute_id); + break; + } + } + + return ret; +} + +MatterTemperatureControlledCabinet::MatterTemperatureControlledCabinet() {} + +MatterTemperatureControlledCabinet::~MatterTemperatureControlledCabinet() { + end(); +} + +bool MatterTemperatureControlledCabinet::begin(double tempSetpoint, double minTemperature, double maxTemperature, double step) { + int16_t rawSetpoint = static_cast(tempSetpoint * 100.0); + int16_t rawMin = static_cast(minTemperature * 100.0); + int16_t rawMax = static_cast(maxTemperature * 100.0); + int16_t rawStepValue = static_cast(step * 100.0); + return begin(rawSetpoint, rawMin, rawMax, rawStepValue); +} + +bool MatterTemperatureControlledCabinet::begin(int16_t _rawTempSetpoint, int16_t _rawMinTemperature, int16_t _rawMaxTemperature, int16_t _rawStep) { + ArduinoMatter::_init(); + + if (getEndPointId() != 0) { + log_e("Temperature Controlled Cabinet with Endpoint Id %d device has already been created.", getEndPointId()); + return false; + } + + temperature_controlled_cabinet::config_t cabinet_config; + cabinet_config.temperature_control.temperature_number.temp_setpoint = _rawTempSetpoint; + cabinet_config.temperature_control.temperature_number.min_temperature = _rawMinTemperature; + cabinet_config.temperature_control.temperature_number.max_temperature = _rawMaxTemperature; + cabinet_config.temperature_control.temperature_step.step = _rawStep; + cabinet_config.temperature_control.temperature_level.selected_temp_level = 0; + + // Enable temperature_number feature (required) + // Note: temperature_number and temperature_level are mutually exclusive. + // Only one of them can be enabled at a time. + cabinet_config.temperature_control.features = temperature_control::feature::temperature_number::get_id(); + + // Enable temperature_step feature if step is provided + // Note: temperature_step requires temperature_number feature (which is always enabled for this mode) + if (_rawStep > 0) { + cabinet_config.temperature_control.features |= temperature_control::feature::temperature_step::get_id(); + } + + // endpoint handles can be used to add/modify clusters + endpoint_t *endpoint = temperature_controlled_cabinet::create(node::get(), &cabinet_config, ENDPOINT_FLAG_NONE, (void *)this); + if (endpoint == nullptr) { + log_e("Failed to create Temperature Controlled Cabinet endpoint"); + return false; + } + + rawTempSetpoint = _rawTempSetpoint; + rawMinTemperature = _rawMinTemperature; + rawMaxTemperature = _rawMaxTemperature; + rawStep = _rawStep; + selectedTempLevel = 0; + useTemperatureNumber = true; // Set feature mode to temperature_number + + setEndPointId(endpoint::get_id(endpoint)); + log_i("Temperature Controlled Cabinet created with temperature_number feature, endpoint_id %d", getEndPointId()); + + started = true; + return true; +} + +bool MatterTemperatureControlledCabinet::begin(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel) { + if (supportedLevels == nullptr || levelCount == 0) { + log_e("Invalid supportedLevels array or levelCount. Must provide at least one level."); + return false; + } + + // Validate against maximum from ESP-Matter + if (levelCount > temperature_control::k_max_temp_level_count) { + log_e("Level count %u exceeds maximum %u", levelCount, temperature_control::k_max_temp_level_count); + return false; + } + + return beginInternal(supportedLevels, levelCount, selectedLevel); +} + +bool MatterTemperatureControlledCabinet::beginInternal(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel) { + ArduinoMatter::_init(); + + if (getEndPointId() != 0) { + log_e("Temperature Controlled Cabinet with Endpoint Id %d device has already been created.", getEndPointId()); + return false; + } + + // Use custom temperature_level_controlled_cabinet endpoint that supports temperature_level feature + temperature_level_controlled_cabinet::config_t cabinet_config; + // Initialize temperature_number config (not used but required for struct) + cabinet_config.temperature_control.temperature_number.temp_setpoint = 0; + cabinet_config.temperature_control.temperature_number.min_temperature = 0; + cabinet_config.temperature_control.temperature_number.max_temperature = 0; + cabinet_config.temperature_control.temperature_step.step = 0; + cabinet_config.temperature_control.temperature_level.selected_temp_level = selectedLevel; + + // Enable temperature_level feature + // Note: temperature_number and temperature_level are mutually exclusive. + // Only one of them can be enabled at a time. + cabinet_config.temperature_control.features = temperature_control::feature::temperature_level::get_id(); + + // endpoint handles can be used to add/modify clusters + endpoint_t *endpoint = temperature_level_controlled_cabinet::create(node::get(), &cabinet_config, ENDPOINT_FLAG_NONE, (void *)this); + if (endpoint == nullptr) { + log_e("Failed to create Temperature Level Controlled Cabinet endpoint"); + return false; + } + + // Copy supported levels array into internal buffer + if (levelCount > temperature_control::k_max_temp_level_count) { + log_e("Level count %u exceeds maximum %u", levelCount, temperature_control::k_max_temp_level_count); + return false; + } + memcpy(supportedLevelsArray, supportedLevels, levelCount * sizeof(uint8_t)); + supportedLevelsCount = levelCount; + selectedTempLevel = selectedLevel; + useTemperatureNumber = false; // Set feature mode to temperature_level + + // Initialize temperature_number values to 0 (not used in this mode) + rawTempSetpoint = 0; + rawMinTemperature = 0; + rawMaxTemperature = 0; + rawStep = 0; + + setEndPointId(endpoint::get_id(endpoint)); + log_i("Temperature Level Controlled Cabinet created with temperature_level feature, endpoint_id %d", getEndPointId()); + + // Set supported temperature levels using internal copy + if (!setSupportedTemperatureLevels(supportedLevelsArray, levelCount)) { + log_e("Failed to set supported temperature levels"); + return false; + } + + // Set selected temperature level + if (!setSelectedTemperatureLevel(selectedLevel)) { + log_e("Failed to set selected temperature level"); + return false; + } + + started = true; + return true; +} + +void MatterTemperatureControlledCabinet::end() { + started = false; + useTemperatureNumber = true; // Reset to default + supportedLevelsCount = 0; + // No need to clear array - it's a fixed-size buffer +} + +bool MatterTemperatureControlledCabinet::setRawTemperatureSetpoint(int16_t _rawTemperature) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (!useTemperatureNumber) { + log_e("Temperature setpoint methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return false; + } + + // Validate against min/max + if (_rawTemperature < rawMinTemperature || _rawTemperature > rawMaxTemperature) { + log_e("Temperature setpoint %.02fC is out of range [%.02fC, %.02fC]", + (float)_rawTemperature / 100.0, (float)rawMinTemperature / 100.0, (float)rawMaxTemperature / 100.0); + return false; + } + + // avoid processing if there was no change + if (rawTempSetpoint == _rawTemperature) { + return true; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + + if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::TemperatureSetpoint::Id, &tempVal)) { + log_e("Failed to get Temperature Controlled Cabinet Temperature Setpoint Attribute."); + return false; + } + if (tempVal.val.i16 != _rawTemperature) { + tempVal.val.i16 = _rawTemperature; + bool ret; + ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::TemperatureSetpoint::Id, &tempVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Temperature Setpoint Attribute."); + return false; + } + rawTempSetpoint = _rawTemperature; + } + log_v("Temperature Controlled Cabinet setpoint set to %.02fC", (float)_rawTemperature / 100.00); + + return true; +} + +bool MatterTemperatureControlledCabinet::setTemperatureSetpoint(double temperature) { + int16_t rawValue = static_cast(temperature * 100.0); + return setRawTemperatureSetpoint(rawValue); +} + +double MatterTemperatureControlledCabinet::getTemperatureSetpoint() { + if (!useTemperatureNumber) { + log_e("Temperature setpoint methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return 0.0; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::TemperatureSetpoint::Id, &tempVal)) { + rawTempSetpoint = tempVal.val.i16; + } + return (double)rawTempSetpoint / 100.0; +} + +bool MatterTemperatureControlledCabinet::setRawMinTemperature(int16_t _rawTemperature) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (!useTemperatureNumber) { + log_e("Min temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return false; + } + + if (rawMinTemperature == _rawTemperature) { + return true; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + + if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MinTemperature::Id, &tempVal)) { + log_e("Failed to get Temperature Controlled Cabinet Min Temperature Attribute."); + return false; + } + if (tempVal.val.i16 != _rawTemperature) { + tempVal.val.i16 = _rawTemperature; + bool ret; + ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MinTemperature::Id, &tempVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Min Temperature Attribute."); + return false; + } + rawMinTemperature = _rawTemperature; + } + log_v("Temperature Controlled Cabinet min temperature set to %.02fC", (float)_rawTemperature / 100.00); + + return true; +} + +bool MatterTemperatureControlledCabinet::setMinTemperature(double temperature) { + int16_t rawValue = static_cast(temperature * 100.0); + return setRawMinTemperature(rawValue); +} + +double MatterTemperatureControlledCabinet::getMinTemperature() { + if (!useTemperatureNumber) { + log_e("Min temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return 0.0; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MinTemperature::Id, &tempVal)) { + rawMinTemperature = tempVal.val.i16; + } + return (double)rawMinTemperature / 100.0; +} + +bool MatterTemperatureControlledCabinet::setRawMaxTemperature(int16_t _rawTemperature) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (!useTemperatureNumber) { + log_e("Max temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return false; + } + + if (rawMaxTemperature == _rawTemperature) { + return true; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + + if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MaxTemperature::Id, &tempVal)) { + log_e("Failed to get Temperature Controlled Cabinet Max Temperature Attribute."); + return false; + } + if (tempVal.val.i16 != _rawTemperature) { + tempVal.val.i16 = _rawTemperature; + bool ret; + ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MaxTemperature::Id, &tempVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Max Temperature Attribute."); + return false; + } + rawMaxTemperature = _rawTemperature; + } + log_v("Temperature Controlled Cabinet max temperature set to %.02fC", (float)_rawTemperature / 100.00); + + return true; +} + +bool MatterTemperatureControlledCabinet::setMaxTemperature(double temperature) { + int16_t rawValue = static_cast(temperature * 100.0); + return setRawMaxTemperature(rawValue); +} + +double MatterTemperatureControlledCabinet::getMaxTemperature() { + if (!useTemperatureNumber) { + log_e("Max temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return 0.0; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MaxTemperature::Id, &tempVal)) { + rawMaxTemperature = tempVal.val.i16; + } + return (double)rawMaxTemperature / 100.0; +} + +bool MatterTemperatureControlledCabinet::setRawStep(int16_t _rawStep) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (!useTemperatureNumber) { + log_e("Temperature step methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return false; + } + + if (rawStep == _rawStep) { + return true; + } + + esp_matter_attr_val_t stepVal = esp_matter_invalid(NULL); + + if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::Step::Id, &stepVal)) { + log_e("Failed to get Temperature Controlled Cabinet Step Attribute."); + return false; + } + if (stepVal.val.i16 != _rawStep) { + stepVal.val.i16 = _rawStep; + bool ret; + ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::Step::Id, &stepVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Step Attribute."); + return false; + } + rawStep = _rawStep; + } + log_v("Temperature Controlled Cabinet step set to %.02fC", (float)_rawStep / 100.00); + + return true; +} + +bool MatterTemperatureControlledCabinet::setStep(double step) { + int16_t rawValue = static_cast(step * 100.0); + return setRawStep(rawValue); +} + +double MatterTemperatureControlledCabinet::getStep() { + if (!useTemperatureNumber) { + log_e("Temperature step methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return 0.0; + } + + esp_matter_attr_val_t stepVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::Step::Id, &stepVal)) { + rawStep = stepVal.val.i16; + } + return (double)rawStep / 100.0; +} + +bool MatterTemperatureControlledCabinet::setSelectedTemperatureLevel(uint8_t level) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (useTemperatureNumber) { + log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); + return false; + } + + if (selectedTempLevel == level) { + return true; + } + + esp_matter_attr_val_t levelVal = esp_matter_invalid(NULL); + + if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SelectedTemperatureLevel::Id, &levelVal)) { + log_e("Failed to get Temperature Controlled Cabinet Selected Temperature Level Attribute."); + return false; + } + if (levelVal.val.u8 != level) { + levelVal.val.u8 = level; + bool ret; + ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SelectedTemperatureLevel::Id, &levelVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Selected Temperature Level Attribute."); + return false; + } + selectedTempLevel = level; + } + log_v("Temperature Controlled Cabinet selected temperature level set to %u", level); + + return true; +} + +uint8_t MatterTemperatureControlledCabinet::getSelectedTemperatureLevel() { + if (useTemperatureNumber) { + log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); + return 0; + } + + esp_matter_attr_val_t levelVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SelectedTemperatureLevel::Id, &levelVal)) { + selectedTempLevel = levelVal.val.u8; + } + return selectedTempLevel; +} + +bool MatterTemperatureControlledCabinet::setSupportedTemperatureLevels(uint8_t *levels, uint16_t count) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (useTemperatureNumber) { + log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); + return false; + } + + if (levels == nullptr || count == 0) { + log_e("Invalid levels array or count."); + return false; + } + + // Validate against maximum from ESP-Matter + if (count > temperature_control::k_max_temp_level_count) { + log_e("Level count %u exceeds maximum %u", count, temperature_control::k_max_temp_level_count); + return false; + } + + // Copy the array into internal buffer + memcpy(supportedLevelsArray, levels, count * sizeof(uint8_t)); + supportedLevelsCount = count; + + // Use internal copy for Matter attribute update + // Use esp_matter_array helper function which properly initializes the structure + esp_matter_attr_val_t levelsVal = esp_matter_array(supportedLevelsArray, sizeof(uint8_t), count); + + bool ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SupportedTemperatureLevels::Id, &levelsVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Supported Temperature Levels Attribute."); + return false; + } + log_v("Temperature Controlled Cabinet supported temperature levels updated, count: %u", count); + + return true; +} + +uint16_t MatterTemperatureControlledCabinet::getSupportedTemperatureLevelsCount() { + if (useTemperatureNumber) { + log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); + return 0; + } + + esp_matter_attr_val_t levelsVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SupportedTemperatureLevels::Id, &levelsVal)) { + return levelsVal.val.a.n; // a.n is the count (number of elements) + } + return 0; +} + +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ + diff --git a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.h b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.h new file mode 100644 index 00000000000..d9c4d7be1c5 --- /dev/null +++ b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.h @@ -0,0 +1,96 @@ +// 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. + +#pragma once +#include +#ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL + +#include +#include + +class MatterTemperatureControlledCabinet : public MatterEndPoint { +public: + MatterTemperatureControlledCabinet(); + ~MatterTemperatureControlledCabinet(); + + // begin with temperature_number feature (mutually exclusive with temperature_level) + // This enables temperature setpoint control with min/max limits and optional step + bool begin(double tempSetpoint = 0.00, double minTemperature = -10.0, double maxTemperature = 32.0, double step = 0.50); + + // begin with temperature_level feature (mutually exclusive with temperature_number) + // This enables temperature level control with an array of supported levels + bool begin(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel = 0); + + // this will stop processing Temperature Controlled Cabinet Matter events + void end(); + + // set the temperature setpoint + bool setTemperatureSetpoint(double temperature); + // returns the temperature setpoint in Celsius + double getTemperatureSetpoint(); + + // set the minimum temperature + bool setMinTemperature(double temperature); + // returns the minimum temperature in Celsius + double getMinTemperature(); + + // set the maximum temperature + bool setMaxTemperature(double temperature); + // returns the maximum temperature in Celsius + double getMaxTemperature(); + + // set the temperature step (optional, requires temperature_step feature) + bool setStep(double step); + // returns the temperature step in Celsius + double getStep(); + + // set the selected temperature level (optional, requires temperature_level feature) + bool setSelectedTemperatureLevel(uint8_t level); + // returns the selected temperature level + uint8_t getSelectedTemperatureLevel(); + + // set supported temperature levels (optional, requires temperature_level feature) + bool setSupportedTemperatureLevels(uint8_t *levels, uint16_t count); + // get supported temperature levels count + uint16_t getSupportedTemperatureLevelsCount(); + + // this function is called by Matter internal event processor. It could be overwritten by the application, if necessary. + bool attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val); + +protected: + bool started = false; + // Feature mode: true = temperature_number, false = temperature_level + // Note: temperature_number and temperature_level are mutually exclusive + bool useTemperatureNumber = true; + + // implementation keeps temperature in 1/100th Celsius x 100 (int16_t) normalized value + int16_t rawTempSetpoint = 0; + int16_t rawMinTemperature = 0; + int16_t rawMaxTemperature = 0; + int16_t rawStep = 0; + uint8_t selectedTempLevel = 0; + // Fixed-size buffer for supported temperature levels (max 16 as per Matter spec: temperature_control::k_max_temp_level_count) + uint8_t supportedLevelsArray[16]; // Size matches esp_matter::cluster::temperature_control::k_max_temp_level_count + uint16_t supportedLevelsCount = 0; + + // internal functions to set the raw temperature values (Matter Cluster) + bool setRawTemperatureSetpoint(int16_t _rawTemperature); + bool setRawMinTemperature(int16_t _rawTemperature); + bool setRawMaxTemperature(int16_t _rawTemperature); + bool setRawStep(int16_t _rawStep); + bool begin(int16_t _rawTempSetpoint, int16_t _rawMinTemperature, int16_t _rawMaxTemperature, int16_t _rawStep); + bool beginInternal(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel); +}; +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ + From 657d118a2eb3acd1232cd74f1ec4c5bdd72ad3db Mon Sep 17 00:00:00 2001 From: SuGlider Date: Thu, 4 Dec 2025 15:12:30 -0300 Subject: [PATCH 02/22] feat(matter): fixes step attibute issue from esp-matter --- .../ep_temperature_controlled_cabinet.rst | 18 +++---- .../README.md | 3 +- .../MatterTemperatureControlledCabinet.cpp | 48 +++++++++++++++---- 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/docs/en/matter/ep_temperature_controlled_cabinet.rst b/docs/en/matter/ep_temperature_controlled_cabinet.rst index 395be1fe6a6..72b3433563e 100644 --- a/docs/en/matter/ep_temperature_controlled_cabinet.rst +++ b/docs/en/matter/ep_temperature_controlled_cabinet.rst @@ -5,17 +5,17 @@ MatterTemperatureControlledCabinet About ----- -The ``MatterTemperatureControlledCabinet`` class provides a temperature controlled cabinet endpoint for Matter networks. This endpoint implements the Matter temperature control standard for managing temperature setpoints with min/max limits, optional step control, or temperature level support. +The ``MatterTemperatureControlledCabinet`` class provides a temperature controlled cabinet endpoint for Matter networks. This endpoint implements the Matter temperature control standard for managing temperature setpoints with min/max limits, step control (always enabled), or temperature level support. **Important:** The ``temperature_number`` and ``temperature_level`` features are **mutually exclusive**. Only one can be enabled at a time. Use ``begin(tempSetpoint, minTemp, maxTemp, step)`` for temperature_number mode or ``begin(supportedLevels, levelCount, selectedLevel)`` for temperature_level mode. **Features:** * Two initialization modes: - * **Temperature Number Mode** (``begin(tempSetpoint, minTemp, maxTemp, step)``): Temperature setpoint control with min/max limits and optional step + * **Temperature Number Mode** (``begin(tempSetpoint, minTemp, maxTemp, step)``): Temperature setpoint control with min/max limits and step control (step value can be set in begin() or later) * **Temperature Level Mode** (``begin(supportedLevels, levelCount, selectedLevel)``): Temperature level control with array of supported levels * 1/100th degree Celsius precision (for temperature_number mode) * Min/max temperature limits with validation (temperature_number mode) -* Optional temperature step control (temperature_number mode) +* Temperature step control (temperature_number mode, always enabled) * Temperature level array support (temperature_level mode) * Automatic setpoint validation against limits * Feature validation - methods return errors if called with wrong feature mode @@ -62,11 +62,11 @@ Initializes the Matter temperature controlled cabinet endpoint with **temperatur * ``tempSetpoint`` - Initial temperature setpoint in Celsius (default: 0.00) * ``minTemperature`` - Minimum allowed temperature in Celsius (default: -10.0) * ``maxTemperature`` - Maximum allowed temperature in Celsius (default: 32.0) -* ``step`` - Temperature step value in Celsius for step control feature (default: 0.50, disabled if 0) +* ``step`` - Initial temperature step value in Celsius (default: 0.50) This function will return ``true`` if successful, ``false`` otherwise. -**Note:** The implementation stores temperature with 1/100th degree Celsius precision internally. If step is greater than 0, the temperature_step feature will be enabled. +**Note:** The implementation stores temperature with 1/100th degree Celsius precision internally. The temperature_step feature is always enabled for temperature_number mode, allowing ``setStep()`` to be called later even if step is not provided in ``begin()``. begin (overloaded) ^^^^^^^^^^^^^^^^^^ @@ -182,13 +182,13 @@ Gets the maximum allowed temperature. This function will return the maximum temperature in Celsius with 1/100th degree precision. -Temperature Step Control (Optional) -*********************************** +Temperature Step Control +************************* setStep ^^^^^^^ -Sets the temperature step value. This enables the temperature_step feature. +Sets the temperature step value. **Requires:** temperature_number feature mode (use ``begin()``) @@ -200,7 +200,7 @@ Sets the temperature step value. This enables the temperature_step feature. This function will return ``true`` if successful, ``false`` otherwise. -**Note:** The temperature_step feature requires the temperature_number feature to be enabled. This method will return ``false`` and log an error if called when using temperature_level mode. +**Note:** The temperature_step feature is always enabled when using temperature_number mode, so this method can be called at any time to set or change the step value, even if step was not provided in ``begin()``. This method will return ``false`` and log an error if called when using temperature_level mode. getStep ^^^^^^^ diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md b/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md index 684c0c6a877..f200d251c4d 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md @@ -27,7 +27,7 @@ This example demonstrates how to create a Matter-compatible temperature controll - Matter protocol implementation for a temperature controlled cabinet device - Support for both Wi-Fi and Thread(*) connectivity - Temperature setpoint control with min/max limits -- Temperature step control (optional) +- Temperature step control (always enabled, can be set via begin() or setStep()) - Temperature setpoint validation against min/max limits - Button control for factory reset (decommission) - Matter commissioning via QR code or manual pairing code @@ -79,6 +79,7 @@ Before uploading the sketch, configure the following: ```cpp TemperatureCabinet.begin(4.0, -10.0, 10.0, 0.5); // Parameters: setpoint, min_temp, max_temp, step (all in Celsius) + // Note: Step can also be set later using setStep() even if not provided here ``` ## Building and Flashing diff --git a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp index 1e813333065..b133ebd0a4b 100644 --- a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp +++ b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp @@ -166,6 +166,11 @@ bool MatterTemperatureControlledCabinet::begin(int16_t _rawTempSetpoint, int16_t return false; } + // Note: esp-matter automatically creates all attributes from the config struct when features are enabled + // - temperature_number feature creates: TemperatureSetpoint, MinTemperature, MaxTemperature + // - temperature_step feature creates: Step (always enabled for temperature_number mode to allow setStep() later) + // No need to manually set attributes here as they are already created with the config values + temperature_controlled_cabinet::config_t cabinet_config; cabinet_config.temperature_control.temperature_number.temp_setpoint = _rawTempSetpoint; cabinet_config.temperature_control.temperature_number.min_temperature = _rawMinTemperature; @@ -178,11 +183,10 @@ bool MatterTemperatureControlledCabinet::begin(int16_t _rawTempSetpoint, int16_t // Only one of them can be enabled at a time. cabinet_config.temperature_control.features = temperature_control::feature::temperature_number::get_id(); - // Enable temperature_step feature if step is provided + // Always enable temperature_step feature to allow setStep() to be called later // Note: temperature_step requires temperature_number feature (which is always enabled for this mode) - if (_rawStep > 0) { - cabinet_config.temperature_control.features |= temperature_control::feature::temperature_step::get_id(); - } + // The step value can be set initially via begin() or later via setStep() + cabinet_config.temperature_control.features |= temperature_control::feature::temperature_step::get_id(); // endpoint handles can be used to add/modify clusters endpoint_t *endpoint = temperature_controlled_cabinet::create(node::get(), &cabinet_config, ENDPOINT_FLAG_NONE, (void *)this); @@ -201,6 +205,33 @@ bool MatterTemperatureControlledCabinet::begin(int16_t _rawTempSetpoint, int16_t setEndPointId(endpoint::get_id(endpoint)); log_i("Temperature Controlled Cabinet created with temperature_number feature, endpoint_id %d", getEndPointId()); + // Workaround: Manually create Step attribute if it wasn't created automatically + // This handles the case where temperature_step::add() fails due to feature map timing issue + // The feature map check in temperature_step::add() may not see the temperature_number feature + // immediately after it's added, causing the Step attribute to not be created + cluster_t *cluster = cluster::get(endpoint, TemperatureControl::Id); + if (cluster != nullptr) { + attribute_t *step_attr = attribute::get(cluster, TemperatureControl::Attributes::Step::Id); + if (step_attr == nullptr) { + // Step attribute wasn't created, manually create it + log_w("Step attribute not found after endpoint creation, manually creating it"); + step_attr = temperature_control::attribute::create_step(cluster, _rawStep); + if (step_attr != nullptr) { + // Update the feature map to include temperature_step feature + // This ensures the feature is properly registered even though the attribute was created manually + esp_matter_attr_val_t feature_map_val = esp_matter_invalid(NULL); + attribute_t *feature_map_attr = attribute::get(cluster, Globals::Attributes::FeatureMap::Id); + if (feature_map_attr != nullptr && attribute::get_val(feature_map_attr, &feature_map_val) == ESP_OK) { + feature_map_val.val.u32 |= temperature_control::feature::temperature_step::get_id(); + attribute::set_val(feature_map_attr, &feature_map_val); + } + log_i("Step attribute manually created with value %.02fC", (float)_rawStep / 100.0); + } else { + log_e("Failed to manually create Step attribute"); + } + } + } + started = true; return true; } @@ -315,7 +346,6 @@ bool MatterTemperatureControlledCabinet::setRawTemperatureSetpoint(int16_t _rawT } esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); - if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::TemperatureSetpoint::Id, &tempVal)) { log_e("Failed to get Temperature Controlled Cabinet Temperature Setpoint Attribute."); return false; @@ -369,7 +399,6 @@ bool MatterTemperatureControlledCabinet::setRawMinTemperature(int16_t _rawTemper } esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); - if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MinTemperature::Id, &tempVal)) { log_e("Failed to get Temperature Controlled Cabinet Min Temperature Attribute."); return false; @@ -423,7 +452,6 @@ bool MatterTemperatureControlledCabinet::setRawMaxTemperature(int16_t _rawTemper } esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); - if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MaxTemperature::Id, &tempVal)) { log_e("Failed to get Temperature Controlled Cabinet Max Temperature Attribute."); return false; @@ -477,9 +505,8 @@ bool MatterTemperatureControlledCabinet::setRawStep(int16_t _rawStep) { } esp_matter_attr_val_t stepVal = esp_matter_invalid(NULL); - if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::Step::Id, &stepVal)) { - log_e("Failed to get Temperature Controlled Cabinet Step Attribute."); + log_e("Failed to get Temperature Controlled Cabinet Step Attribute. Temperature_step feature may not be enabled."); return false; } if (stepVal.val.i16 != _rawStep) { @@ -508,6 +535,8 @@ double MatterTemperatureControlledCabinet::getStep() { return 0.0; } + // Read from attribute (should always exist after begin() due to workaround) + // If read fails, use stored rawStep value from begin() esp_matter_attr_val_t stepVal = esp_matter_invalid(NULL); if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::Step::Id, &stepVal)) { rawStep = stepVal.val.i16; @@ -531,7 +560,6 @@ bool MatterTemperatureControlledCabinet::setSelectedTemperatureLevel(uint8_t lev } esp_matter_attr_val_t levelVal = esp_matter_invalid(NULL); - if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SelectedTemperatureLevel::Id, &levelVal)) { log_e("Failed to get Temperature Controlled Cabinet Selected Temperature Level Attribute."); return false; From 132e752ba2d1b5fbce6ebe949c4009c49f5d92dd Mon Sep 17 00:00:00 2001 From: SuGlider Date: Thu, 4 Dec 2025 15:52:19 -0300 Subject: [PATCH 03/22] feat(matter): organize examples, keywords and fix init error --- .../ep_temperature_controlled_cabinet.rst | 4 +- .../MatterTemperatureControlledCabinet.ino | 182 ++++++++++------ .../README.md | 13 ++ ...tterTemperatureControlledCabinetLevels.ino | 199 ++++++++++++------ .../README.md | 16 +- libraries/Matter/keywords.txt | 1 - .../MatterTemperatureControlledCabinet.cpp | 6 +- 7 files changed, 292 insertions(+), 129 deletions(-) diff --git a/docs/en/matter/ep_temperature_controlled_cabinet.rst b/docs/en/matter/ep_temperature_controlled_cabinet.rst index 72b3433563e..db2bca9a0c6 100644 --- a/docs/en/matter/ep_temperature_controlled_cabinet.rst +++ b/docs/en/matter/ep_temperature_controlled_cabinet.rst @@ -279,7 +279,7 @@ Example Temperature Controlled Cabinet ****************************** -The example demonstrates the temperature_number mode with dynamic temperature updates. The temperature setpoint automatically cycles between the minimum and maximum limits every 1 second using the configured step value, allowing Matter controllers to observe real-time changes. +The example demonstrates the temperature_number mode with dynamic temperature updates. The temperature setpoint automatically cycles between the minimum and maximum limits every 1 second using the configured step value, allowing Matter controllers to observe real-time changes. The example also monitors and logs when the initial setpoint is reached or overpassed in each direction. .. literalinclude:: ../../../libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino :language: arduino @@ -287,7 +287,7 @@ The example demonstrates the temperature_number mode with dynamic temperature up Temperature Controlled Cabinet (Level Mode) ******************************************** -A separate example demonstrates the temperature_level mode with dynamic level updates. The temperature level automatically cycles through all supported levels every 1 second, allowing Matter controllers to observe real-time changes. +A separate example demonstrates the temperature_level mode with dynamic level updates. The temperature level automatically cycles through all supported levels every 1 second in both directions (increasing and decreasing), allowing Matter controllers to observe real-time changes. The example also monitors and logs when the initial level is reached or overpassed in each direction. See ``MatterTemperatureControlledCabinetLevels`` example for the temperature level mode implementation. diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino b/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino index 877f7492265..536a4255fed 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino @@ -3,7 +3,7 @@ // 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 @@ -53,6 +53,119 @@ 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 +// Temperature control state +struct TemperatureControlState { + bool initialized; + bool increasing; + double currentSetpoint; + double initialSetpoint; + bool setpointReachedIncreasing; + bool setpointReachedDecreasing; +}; + +static TemperatureControlState tempState = { + .initialized = false, + .increasing = true, + .currentSetpoint = 0.0, + .initialSetpoint = 0.0, + .setpointReachedIncreasing = false, + .setpointReachedDecreasing = false +}; + +// Initialize temperature control state +void initTemperatureControl() { + if (!tempState.initialized) { + tempState.currentSetpoint = TemperatureCabinet.getTemperatureSetpoint(); + tempState.initialSetpoint = tempState.currentSetpoint; + tempState.initialized = true; + } +} + +// Check and log when initial setpoint is reached/overpassed +void checkSetpointReached(double newSetpoint, bool isIncreasing, bool directionChanged) { + if (directionChanged) { + // Reset flags when direction changes + tempState.setpointReachedIncreasing = false; + tempState.setpointReachedDecreasing = false; + return; + } + + if (isIncreasing && !tempState.setpointReachedIncreasing && newSetpoint >= tempState.initialSetpoint) { + Serial.printf("*** Temperature setpoint %.02f°C reached/overpassed while increasing ***\r\n", tempState.initialSetpoint); + tempState.setpointReachedIncreasing = true; + } else if (!isIncreasing && !tempState.setpointReachedDecreasing && newSetpoint <= tempState.initialSetpoint) { + Serial.printf("*** Temperature setpoint %.02f°C reached/overpassed while decreasing ***\r\n", tempState.initialSetpoint); + tempState.setpointReachedDecreasing = true; + } +} + +// Update temperature setpoint with cycling logic +void updateTemperatureSetpoint() { + double minTemp = TemperatureCabinet.getMinTemperature(); + double maxTemp = TemperatureCabinet.getMaxTemperature(); + double step = TemperatureCabinet.getStep(); + + // Calculate next setpoint based on direction and step + bool directionChanged = false; + + if (tempState.increasing) { + tempState.currentSetpoint += step; + if (tempState.currentSetpoint >= maxTemp) { + tempState.currentSetpoint = maxTemp; + tempState.increasing = false; // Reverse direction + directionChanged = true; + } + } else { + tempState.currentSetpoint -= step; + if (tempState.currentSetpoint <= minTemp) { + tempState.currentSetpoint = minTemp; + tempState.increasing = true; // Reverse direction + directionChanged = true; + } + } + + // Check if setpoint has been reached or overpassed + checkSetpointReached(tempState.currentSetpoint, tempState.increasing, directionChanged); + + // Update the temperature setpoint + if (TemperatureCabinet.setTemperatureSetpoint(tempState.currentSetpoint)) { + Serial.printf("Temperature setpoint updated to: %.02f°C (Range: %.02f°C to %.02f°C)\r\n", + tempState.currentSetpoint, minTemp, maxTemp); + } else { + Serial.printf("Failed to update temperature setpoint to: %.02f°C\r\n", tempState.currentSetpoint); + } +} + +// Print current temperature status +void printTemperatureStatus() { + Serial.printf("Current Temperature Setpoint: %.02f°C (Range: %.02f°C to %.02f°C)\r\n", + TemperatureCabinet.getTemperatureSetpoint(), + TemperatureCabinet.getMinTemperature(), + TemperatureCabinet.getMaxTemperature()); +} + +// Handle button press for decommissioning +void handleButtonPress() { + // 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 (digitalRead(buttonPin) == HIGH && button_state) { + 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 Temperature Controlled Cabinet Matter Accessory. It shall be commissioned again."); + Matter.decommission(); + button_time_stamp = millis(); // avoid running decommissining again, reboot takes a second or so + } +} + void setup() { // Initialize the USER BUTTON (Boot button) that will be used to decommission the Matter Node pinMode(buttonPin, INPUT_PULLUP); @@ -111,77 +224,24 @@ void setup() { void loop() { static uint32_t timeCounter = 0; static uint32_t lastUpdateTime = 0; - static bool initialized = false; - static bool increasing = true; // Direction of temperature change - static double currentSetpoint = 0.0; // Will be initialized from actual setpoint - - // Initialize currentSetpoint from actual setpoint on first run - if (!initialized) { - currentSetpoint = TemperatureCabinet.getTemperatureSetpoint(); - initialized = true; - } + + // Initialize temperature control state on first run + initTemperatureControl(); // Update temperature setpoint dynamically every 1 second uint32_t currentTime = millis(); if (currentTime - lastUpdateTime >= 1000) { // 1 second interval lastUpdateTime = currentTime; - - double minTemp = TemperatureCabinet.getMinTemperature(); - double maxTemp = TemperatureCabinet.getMaxTemperature(); - double step = TemperatureCabinet.getStep(); - - // Calculate next setpoint based on direction and step - if (increasing) { - currentSetpoint += step; - if (currentSetpoint >= maxTemp) { - currentSetpoint = maxTemp; - increasing = false; // Reverse direction - } - } else { - currentSetpoint -= step; - if (currentSetpoint <= minTemp) { - currentSetpoint = minTemp; - increasing = true; // Reverse direction - } - } - - // Update the temperature setpoint - if (TemperatureCabinet.setTemperatureSetpoint(currentSetpoint)) { - Serial.printf("Temperature setpoint updated to: %.02f°C (Range: %.02f°C to %.02f°C)\r\n", - currentSetpoint, minTemp, maxTemp); - } else { - Serial.printf("Failed to update temperature setpoint to: %.02f°C\r\n", currentSetpoint); - } + updateTemperatureSetpoint(); } // Print the current temperature setpoint every 5s if (!(timeCounter++ % 10)) { // delaying for 500ms x 10 = 5s - // Print the current temperature setpoint value - Serial.printf("Current Temperature Setpoint: %.02f°C (Range: %.02f°C to %.02f°C)\r\n", - TemperatureCabinet.getTemperatureSetpoint(), - TemperatureCabinet.getMinTemperature(), - TemperatureCabinet.getMaxTemperature()); + printTemperatureStatus(); } - // 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 (digitalRead(buttonPin) == HIGH && button_state) { - 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 Temperature Controlled Cabinet Matter Accessory. It shall be commissioned again."); - Matter.decommission(); - button_time_stamp = millis(); // avoid running decommissining again, reboot takes a second or so - } + // Handle button press for decommissioning + handleButtonPress(); delay(500); } - diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md b/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md index f200d251c4d..6607f1f0d7f 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md @@ -115,10 +115,14 @@ Temperature Controlled Cabinet Configuration: Max Temperature: 10.00°C Step: 0.50°C Temperature setpoint updated to: 4.50°C (Range: -10.00°C to 10.00°C) +*** Temperature setpoint 4.00°C reached/overpassed while increasing *** Temperature setpoint updated to: 5.00°C (Range: -10.00°C to 10.00°C) Temperature setpoint updated to: 5.50°C (Range: -10.00°C to 10.00°C) ... Current Temperature Setpoint: 6.00°C (Range: -10.00°C to 10.00°C) +... +*** Temperature setpoint 4.00°C reached/overpassed while decreasing *** +Temperature setpoint updated to: 3.50°C (Range: -10.00°C to 10.00°C) ``` ## Using the Device @@ -167,11 +171,20 @@ Use a Matter-compatible hub (like an Apple HomePod, Google Nest Hub, or Amazon E The MatterTemperatureControlledCabinet example consists of the following main components: 1. **`setup()`**: Initializes hardware (button), configures Wi-Fi (if needed), sets up the Matter Temperature Controlled Cabinet endpoint with initial temperature configuration, and waits for Matter commissioning. + 2. **`loop()`**: - **Dynamic Temperature Updates**: Automatically changes the temperature setpoint every 1 second, cycling between the minimum and maximum temperature limits using the configured step value. This demonstrates the temperature control functionality and allows Matter controllers to observe real-time changes. + - **Setpoint Reached Detection**: Monitors when the initial setpoint is reached or overpassed in each direction and prints a notification message once per direction. - Periodically prints the current temperature setpoint (every 5 seconds) - Handles button input for factory reset +3. **Helper Functions**: + - `initTemperatureControl()`: Initializes the temperature control state from the current setpoint + - `checkSetpointReached()`: Checks and logs when the initial setpoint is reached/overpassed + - `updateTemperatureSetpoint()`: Updates the temperature setpoint with cycling logic and boundary detection + - `printTemperatureStatus()`: Prints the current temperature status + - `handleButtonPress()`: Handles button press detection and factory reset functionality + ## API Usage The example demonstrates the following API methods: diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino index c934660555f..ead9a7f95bf 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino @@ -3,7 +3,7 @@ // 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 @@ -60,6 +60,136 @@ uint8_t supportedLevels[] = {0, 1, 2, 3, 4}; const uint16_t levelCount = sizeof(supportedLevels) / sizeof(supportedLevels[0]); const uint8_t initialLevel = 2; // Start with level 2 (Medium) +// Temperature level control state +struct LevelControlState { + bool initialized; + bool increasing; + uint16_t currentLevelIndex; + uint8_t initialLevel; + bool levelReachedIncreasing; + bool levelReachedDecreasing; +}; + +static LevelControlState levelState = { + .initialized = false, + .increasing = true, + .currentLevelIndex = 0, + .initialLevel = 0, + .levelReachedIncreasing = false, + .levelReachedDecreasing = false +}; + +// Initialize level control state +void initLevelControl() { + if (!levelState.initialized) { + uint8_t currentLevel = TemperatureCabinet.getSelectedTemperatureLevel(); + levelState.initialLevel = currentLevel; + // Find the index of current level in supportedLevels array + for (uint16_t i = 0; i < levelCount; i++) { + if (supportedLevels[i] == currentLevel) { + levelState.currentLevelIndex = i; + break; + } + } + levelState.initialized = true; + } +} + +// Check and log when initial level is reached/overpassed +void checkLevelReached(uint8_t newLevel, bool isIncreasing, bool directionChanged) { + if (directionChanged) { + // Reset flags when direction changes + levelState.levelReachedIncreasing = false; + levelState.levelReachedDecreasing = false; + return; + } + + if (isIncreasing && !levelState.levelReachedIncreasing && newLevel >= levelState.initialLevel) { + Serial.printf("*** Temperature level %u reached/overpassed while increasing ***\r\n", levelState.initialLevel); + levelState.levelReachedIncreasing = true; + } else if (!isIncreasing && !levelState.levelReachedDecreasing && newLevel <= levelState.initialLevel) { + Serial.printf("*** Temperature level %u reached/overpassed while decreasing ***\r\n", levelState.initialLevel); + levelState.levelReachedDecreasing = true; + } +} + +// Update temperature level with cycling logic +void updateTemperatureLevel() { + // Cycle through supported levels in both directions + bool directionChanged = false; + + if (levelState.increasing) { + levelState.currentLevelIndex++; + if (levelState.currentLevelIndex >= levelCount) { + levelState.currentLevelIndex = levelCount - 1; + levelState.increasing = false; // Reverse direction + directionChanged = true; + } + } else { + if (levelState.currentLevelIndex == 0) { + levelState.currentLevelIndex = 0; + levelState.increasing = true; // Reverse direction + directionChanged = true; + } else { + levelState.currentLevelIndex--; + } + } + + uint8_t newLevel = supportedLevels[levelState.currentLevelIndex]; + + // Check if initial level has been reached or overpassed + checkLevelReached(newLevel, levelState.increasing, directionChanged); + + // Update the temperature level + if (TemperatureCabinet.setSelectedTemperatureLevel(newLevel)) { + Serial.printf("Temperature level updated to: %u (Supported Levels: ", newLevel); + for (uint16_t i = 0; i < levelCount; i++) { + Serial.printf("%u", supportedLevels[i]); + if (i < levelCount - 1) { + Serial.print(", "); + } + } + Serial.println(")"); + } else { + Serial.printf("Failed to update temperature level to: %u\r\n", newLevel); + } +} + +// Print current level status +void printLevelStatus() { + uint8_t currentLevel = TemperatureCabinet.getSelectedTemperatureLevel(); + Serial.printf("Current Temperature Level: %u (Supported Levels: ", currentLevel); + for (uint16_t i = 0; i < levelCount; i++) { + Serial.printf("%u", supportedLevels[i]); + if (i < levelCount - 1) { + Serial.print(", "); + } + } + Serial.println(")"); +} + +// Handle button press for decommissioning +void handleButtonPress() { + // 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 (digitalRead(buttonPin) == HIGH && button_state) { + 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 Temperature Controlled Cabinet Matter Accessory. It shall be commissioned again."); + Matter.decommission(); + button_time_stamp = millis(); // avoid running decommissining again, reboot takes a second or so + } +} + void setup() { // Initialize the USER BUTTON (Boot button) that will be used to decommission the Matter Node pinMode(buttonPin, INPUT_PULLUP); @@ -131,79 +261,24 @@ void setup() { void loop() { static uint32_t timeCounter = 0; static uint32_t lastUpdateTime = 0; - static bool initialized = false; - static uint16_t currentLevelIndex = 0; // Will be initialized from actual level - // Initialize currentLevelIndex from actual selected level on first run - if (!initialized) { - uint8_t currentLevel = TemperatureCabinet.getSelectedTemperatureLevel(); - // Find the index of current level in supportedLevels array - for (uint16_t i = 0; i < levelCount; i++) { - if (supportedLevels[i] == currentLevel) { - currentLevelIndex = i; - break; - } - } - initialized = true; - } + // Initialize level control state on first run + initLevelControl(); // Update temperature level dynamically every 1 second uint32_t currentTime = millis(); if (currentTime - lastUpdateTime >= 1000) { // 1 second interval lastUpdateTime = currentTime; - - // Cycle through supported levels - currentLevelIndex = (currentLevelIndex + 1) % levelCount; - uint8_t newLevel = supportedLevels[currentLevelIndex]; - - // Update the temperature level - if (TemperatureCabinet.setSelectedTemperatureLevel(newLevel)) { - Serial.printf("Temperature level updated to: %u (Supported Levels: ", newLevel); - for (uint16_t i = 0; i < levelCount; i++) { - Serial.printf("%u", supportedLevels[i]); - if (i < levelCount - 1) { - Serial.print(", "); - } - } - Serial.println(")"); - } else { - Serial.printf("Failed to update temperature level to: %u\r\n", newLevel); - } + updateTemperatureLevel(); } // Print the current temperature level every 5s if (!(timeCounter++ % 10)) { // delaying for 500ms x 10 = 5s - // Print the current temperature level value - uint8_t currentLevel = TemperatureCabinet.getSelectedTemperatureLevel(); - Serial.printf("Current Temperature Level: %u (Supported Levels: ", currentLevel); - for (uint16_t i = 0; i < levelCount; i++) { - Serial.printf("%u", supportedLevels[i]); - if (i < levelCount - 1) { - Serial.print(", "); - } - } - Serial.println(")"); - } - - // 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. + printLevelStatus(); } - if (digitalRead(buttonPin) == HIGH && button_state) { - 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 Temperature Controlled Cabinet Matter Accessory. It shall be commissioned again."); - Matter.decommission(); - button_time_stamp = millis(); // avoid running decommissining again, reboot takes a second or so - } + // Handle button press for decommissioning + handleButtonPress(); delay(500); } - diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/README.md b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/README.md index 1881d465a3e..9ffc431243c 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/README.md +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/README.md @@ -115,8 +115,11 @@ Temperature Controlled Cabinet Configuration (Temperature Level Mode): Supported Levels Count: 5 Supported Levels: 0, 1, 2, 3, 4 Temperature level updated to: 3 (Supported Levels: 0, 1, 2, 3, 4) +*** Temperature level 2 reached/overpassed while increasing *** Temperature level updated to: 4 (Supported Levels: 0, 1, 2, 3, 4) -Temperature level updated to: 0 (Supported Levels: 0, 1, 2, 3, 4) +Temperature level updated to: 3 (Supported Levels: 0, 1, 2, 3, 4) +Temperature level updated to: 2 (Supported Levels: 0, 1, 2, 3, 4) +*** Temperature level 2 reached/overpassed while decreasing *** Temperature level updated to: 1 (Supported Levels: 0, 1, 2, 3, 4) ... Current Temperature Level: 2 (Supported Levels: 0, 1, 2, 3, 4) @@ -168,11 +171,20 @@ Use a Matter-compatible hub (like an Apple HomePod, Google Nest Hub, or Amazon E The MatterTemperatureControlledCabinetLevels example consists of the following main components: 1. **`setup()`**: Initializes hardware (button), configures Wi-Fi (if needed), sets up the Matter Temperature Controlled Cabinet endpoint with temperature level configuration, and waits for Matter commissioning. + 2. **`loop()`**: - - **Dynamic Level Updates**: Automatically cycles through all supported temperature levels every 1 second. This demonstrates the temperature level control functionality and allows Matter controllers to observe real-time changes. + - **Dynamic Level Updates**: Automatically cycles through all supported temperature levels every 1 second in both directions (increasing and decreasing). This demonstrates the temperature level control functionality and allows Matter controllers to observe real-time changes. + - **Level Reached Detection**: Monitors when the initial level is reached or overpassed in each direction and prints a notification message once per direction. - Periodically prints the current temperature level (every 5 seconds) - Handles button input for factory reset +3. **Helper Functions**: + - `initLevelControl()`: Initializes the level control state from the current selected level + - `checkLevelReached()`: Checks and logs when the initial level is reached/overpassed + - `updateTemperatureLevel()`: Updates the temperature level with cycling logic and boundary detection + - `printLevelStatus()`: Prints the current level status + - `handleButtonPress()`: Handles button press detection and factory reset functionality + ## API Usage The example demonstrates the following API methods: diff --git a/libraries/Matter/keywords.txt b/libraries/Matter/keywords.txt index 73653c3e06c..e3a4ea55a20 100644 --- a/libraries/Matter/keywords.txt +++ b/libraries/Matter/keywords.txt @@ -121,7 +121,6 @@ setCoolingHeatingSetpoints KEYWORD2 setLocalTemperature KEYWORD2 getLocalTemperature KEYWORD2 getThermostatModeString KEYWORD2 -onChangeMode KEYWORD2 onChangeLocalTemperature KEYWORD2 onChangeCoolingSetpoint KEYWORD2 onChangeHeatingSetpoint KEYWORD2 diff --git a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp index b133ebd0a4b..d350e7c06d5 100644 --- a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp +++ b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp @@ -299,19 +299,23 @@ bool MatterTemperatureControlledCabinet::beginInternal(uint8_t *supportedLevels, setEndPointId(endpoint::get_id(endpoint)); log_i("Temperature Level Controlled Cabinet created with temperature_level feature, endpoint_id %d", getEndPointId()); + // Set started flag before calling setter methods (they check for started) + started = true; + // Set supported temperature levels using internal copy if (!setSupportedTemperatureLevels(supportedLevelsArray, levelCount)) { log_e("Failed to set supported temperature levels"); + started = false; // Reset on failure return false; } // Set selected temperature level if (!setSelectedTemperatureLevel(selectedLevel)) { log_e("Failed to set selected temperature level"); + started = false; // Reset on failure return false; } - started = true; return true; } From 390140c0271e7f0edf4862db03edfa00e806800b Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Thu, 4 Dec 2025 16:05:18 -0300 Subject: [PATCH 04/22] fix(matter): typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../MatterTemperatureControlledCabinetLevels.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino index ead9a7f95bf..2c8e72f1012 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino @@ -186,7 +186,7 @@ void handleButtonPress() { if (button_state && time_diff > decommissioningTimeout) { Serial.println("Decommissioning Temperature Controlled Cabinet Matter Accessory. It shall be commissioned again."); Matter.decommission(); - button_time_stamp = millis(); // avoid running decommissining again, reboot takes a second or so + button_time_stamp = millis(); // avoid running decommissioning again, reboot takes a second or so } } From 59bc50c4ecb7c7f27867231948bfa4d0246bdbb3 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Thu, 4 Dec 2025 16:06:23 -0300 Subject: [PATCH 05/22] fix(matter): typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../MatterTemperatureControlledCabinet.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino b/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino index 536a4255fed..6fe45c9c816 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino @@ -162,7 +162,7 @@ void handleButtonPress() { if (button_state && time_diff > decommissioningTimeout) { Serial.println("Decommissioning Temperature Controlled Cabinet Matter Accessory. It shall be commissioned again."); Matter.decommission(); - button_time_stamp = millis(); // avoid running decommissining again, reboot takes a second or so + button_time_stamp = millis(); // avoid running decommissioning again, reboot takes a second or so } } From 73c9f72fc2dd01ec6ddbff78544f4fbb655caf7c Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Thu, 4 Dec 2025 16:15:15 -0300 Subject: [PATCH 06/22] fix(matter): improves commentaries Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/MatterEndpoints/MatterTemperatureControlledCabinet.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.h b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.h index d9c4d7be1c5..e56dd4da96a 100644 --- a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.h +++ b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.h @@ -74,7 +74,7 @@ class MatterTemperatureControlledCabinet : public MatterEndPoint { // Note: temperature_number and temperature_level are mutually exclusive bool useTemperatureNumber = true; - // implementation keeps temperature in 1/100th Celsius x 100 (int16_t) normalized value + // temperature in 1/100th Celsius (stored as int16_t by multiplying by 100) int16_t rawTempSetpoint = 0; int16_t rawMinTemperature = 0; int16_t rawMaxTemperature = 0; From 9d9255e2425622a999b8b93ddf5665d42bb9b078 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Thu, 4 Dec 2025 16:15:43 -0300 Subject: [PATCH 07/22] fix(matter): typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../MatterTemperatureControlledCabinetLevels.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino index 2c8e72f1012..4a107ed9f56 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino @@ -48,7 +48,7 @@ const char *password = "your-password"; // Change this to your WiFi password // set your board USER BUTTON pin here - decommissioning button const uint8_t buttonPin = BOOT_PIN; // Set your pin here. Using BOOT Button. -// Button control - decommision the Matter Node +// Button control - decommission the Matter Node 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 From 38f04b83a02db50f8ea807a9ac5c7dae26d06362 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Thu, 4 Dec 2025 16:16:01 -0300 Subject: [PATCH 08/22] fix(matter): typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../MatterTemperatureControlledCabinet.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino b/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino index 6fe45c9c816..1466fe9e5f5 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino @@ -48,7 +48,7 @@ const char *password = "your-password"; // Change this to your WiFi password // set your board USER BUTTON pin here - decommissioning button const uint8_t buttonPin = BOOT_PIN; // Set your pin here. Using BOOT Button. -// Button control - decommision the Matter Node +// Button control - decommission the Matter Node 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 From d2b20609deb70c43028a7fb75535078ea6d11414 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Thu, 4 Dec 2025 16:18:33 -0300 Subject: [PATCH 09/22] feat(matter): checks level to verify that it is valid Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../MatterTemperatureControlledCabinet.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp index d350e7c06d5..ecb22cd7ff4 100644 --- a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp +++ b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp @@ -247,7 +247,19 @@ bool MatterTemperatureControlledCabinet::begin(uint8_t *supportedLevels, uint16_ log_e("Level count %u exceeds maximum %u", levelCount, temperature_control::k_max_temp_level_count); return false; } - + + // Validate that selectedLevel exists in supportedLevels array + bool levelFound = false; + for (uint16_t i = 0; i < levelCount; i++) { + if (supportedLevels[i] == selectedLevel) { + levelFound = true; + break; + } + } + if (!levelFound) { + log_e("Selected level %u is not in the supported levels array", selectedLevel); + return false; + } return beginInternal(supportedLevels, levelCount, selectedLevel); } From 988ea97f45b3c7421892bc27f01bd00904c3e6e4 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Thu, 4 Dec 2025 16:19:30 -0300 Subject: [PATCH 10/22] feat(matter): checks level to verify that it is valid Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../MatterTemperatureControlledCabinet.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp index ecb22cd7ff4..e244c6d2d72 100644 --- a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp +++ b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp @@ -571,6 +571,18 @@ bool MatterTemperatureControlledCabinet::setSelectedTemperatureLevel(uint8_t lev return false; } + // Validate that level is in supported levels array + bool levelFound = false; + for (uint16_t i = 0; i < supportedLevelsCount; i++) { + if (supportedLevelsArray[i] == level) { + levelFound = true; + break; + } + } + if (!levelFound) { + log_e("Temperature level %u is not in the supported levels array", level); + return false; + } if (selectedTempLevel == level) { return true; } From 786d1b7260c7b3b8c5f298bbfc7d89d1e02c9019 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Thu, 4 Dec 2025 16:50:46 -0300 Subject: [PATCH 11/22] fix(matter_docs): rst formatting --- docs/en/matter/ep_temperature_controlled_cabinet.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/matter/ep_temperature_controlled_cabinet.rst b/docs/en/matter/ep_temperature_controlled_cabinet.rst index db2bca9a0c6..8ed7ba90df5 100644 --- a/docs/en/matter/ep_temperature_controlled_cabinet.rst +++ b/docs/en/matter/ep_temperature_controlled_cabinet.rst @@ -234,7 +234,7 @@ This function will return ``true`` if successful, ``false`` otherwise. **Note:** Temperature level and temperature number features are mutually exclusive. This method will return ``false`` and log an error if called when using temperature_number mode. getSelectedTemperatureLevel -^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^ Gets the current selected temperature level. From 3c9d3b4f691fdacc8a4fe6be4107e1d364ef1f70 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Thu, 4 Dec 2025 17:28:19 -0300 Subject: [PATCH 12/22] fix(matter): typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../MatterTemperatureControlledCabinetLevels/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/README.md b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/README.md index 9ffc431243c..9a36593c214 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/README.md +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/README.md @@ -19,8 +19,8 @@ This example demonstrates how to create a Matter-compatible temperature controll ### Note on Commissioning: - **ESP32 & ESP32-S2** do not support commissioning over Bluetooth LE. For these chips, you must provide Wi-Fi credentials directly in the sketch code so they can connect to your network manually. -- **ESP32-C6** Although it has Thread support, the ESP32 Arduino Matter Library has been pre compiled using Wi-Fi only. In order to configure it for Thread-only operation it is necessary to build the project as an ESP-IDF component and to disable the Matter Wi-Fi station feature. -- **ESP32-C5** Although it has Thread support, the ESP32 Arduino Matter Library has been pre compiled using Wi-Fi only. In order to configure it for Thread-only operation it is necessary to build the project as an ESP-IDF component and to disable the Matter Wi-Fi station feature. +- **ESP32-C6** Although it has Thread support, the ESP32 Arduino Matter Library has been precompiled using Wi-Fi only. In order to configure it for Thread-only operation it is necessary to build the project as an ESP-IDF component and to disable the Matter Wi-Fi station feature. +- **ESP32-C5** Although it has Thread support, the ESP32 Arduino Matter Library has been precompiled using Wi-Fi only. In order to configure it for Thread-only operation it is necessary to build the project as an ESP-IDF component and to disable the Matter Wi-Fi station feature. ## Features From daad7dc6c8ecfa00d4446ad5500727ab531b59f5 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Thu, 4 Dec 2025 17:29:02 -0300 Subject: [PATCH 13/22] fix(matter): typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../examples/MatterTemperatureControlledCabinet/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md b/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md index 6607f1f0d7f..ef28d650961 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md @@ -19,8 +19,8 @@ This example demonstrates how to create a Matter-compatible temperature controll ### Note on Commissioning: - **ESP32 & ESP32-S2** do not support commissioning over Bluetooth LE. For these chips, you must provide Wi-Fi credentials directly in the sketch code so they can connect to your network manually. -- **ESP32-C6** Although it has Thread support, the ESP32 Arduino Matter Library has been pre compiled using Wi-Fi only. In order to configure it for Thread-only operation it is necessary to build the project as an ESP-IDF component and to disable the Matter Wi-Fi station feature. -- **ESP32-C5** Although it has Thread support, the ESP32 Arduino Matter Library has been pre compiled using Wi-Fi only. In order to configure it for Thread-only operation it is necessary to build the project as an ESP-IDF component and to disable the Matter Wi-Fi station feature. +- **ESP32-C6** Although it has Thread support, the ESP32 Arduino Matter Library has been precompiled using Wi-Fi only. In order to configure it for Thread-only operation it is necessary to build the project as an ESP-IDF component and to disable the Matter Wi-Fi station feature. +- **ESP32-C5** Although it has Thread support, the ESP32 Arduino Matter Library has been precompiled using Wi-Fi only. In order to configure it for Thread-only operation it is necessary to build the project as an ESP-IDF component and to disable the Matter Wi-Fi station feature. ## Features From f4a0ac1a131bb72377a5574bc254158f09e011d5 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Thu, 4 Dec 2025 17:30:41 -0300 Subject: [PATCH 14/22] fix(matter): removes redundant code Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../MatterEndpoints/MatterTemperatureControlledCabinet.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp index e244c6d2d72..43210dfd836 100644 --- a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp +++ b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp @@ -293,10 +293,6 @@ bool MatterTemperatureControlledCabinet::beginInternal(uint8_t *supportedLevels, } // Copy supported levels array into internal buffer - if (levelCount > temperature_control::k_max_temp_level_count) { - log_e("Level count %u exceeds maximum %u", levelCount, temperature_control::k_max_temp_level_count); - return false; - } memcpy(supportedLevelsArray, supportedLevels, levelCount * sizeof(uint8_t)); supportedLevelsCount = levelCount; selectedTempLevel = selectedLevel; From e5203f1eb447b2b222b50e93b499a4614b0e5270 Mon Sep 17 00:00:00 2001 From: SuGlider Date: Thu, 4 Dec 2025 18:14:40 -0300 Subject: [PATCH 15/22] fix(docs): identation and allignment --- docs/en/matter/ep_temperature_controlled_cabinet.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/en/matter/ep_temperature_controlled_cabinet.rst b/docs/en/matter/ep_temperature_controlled_cabinet.rst index 8ed7ba90df5..0b1414fff5e 100644 --- a/docs/en/matter/ep_temperature_controlled_cabinet.rst +++ b/docs/en/matter/ep_temperature_controlled_cabinet.rst @@ -1,6 +1,6 @@ -####################################### +################################## MatterTemperatureControlledCabinet -####################################### +################################## About ----- @@ -11,8 +11,8 @@ The ``MatterTemperatureControlledCabinet`` class provides a temperature controll **Features:** * Two initialization modes: - * **Temperature Number Mode** (``begin(tempSetpoint, minTemp, maxTemp, step)``): Temperature setpoint control with min/max limits and step control (step value can be set in begin() or later) - * **Temperature Level Mode** (``begin(supportedLevels, levelCount, selectedLevel)``): Temperature level control with array of supported levels + * **Temperature Number Mode** (``begin(tempSetpoint, minTemp, maxTemp, step)``): Temperature setpoint control with min/max limits and step control (step value can be set in begin() or later) + * **Temperature Level Mode** (``begin(supportedLevels, levelCount, selectedLevel)``): Temperature level control with array of supported levels * 1/100th degree Celsius precision (for temperature_number mode) * Min/max temperature limits with validation (temperature_number mode) * Temperature step control (temperature_number mode, always enabled) From 9e57f746cd928c20a02aa105e82950fd9ec3d567 Mon Sep 17 00:00:00 2001 From: SuGlider Date: Thu, 4 Dec 2025 18:26:24 -0300 Subject: [PATCH 16/22] fix(docs): identation and allignment --- docs/en/matter/ep_temperature_controlled_cabinet.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/matter/ep_temperature_controlled_cabinet.rst b/docs/en/matter/ep_temperature_controlled_cabinet.rst index 0b1414fff5e..d9b9df02e8a 100644 --- a/docs/en/matter/ep_temperature_controlled_cabinet.rst +++ b/docs/en/matter/ep_temperature_controlled_cabinet.rst @@ -11,7 +11,7 @@ The ``MatterTemperatureControlledCabinet`` class provides a temperature controll **Features:** * Two initialization modes: - * **Temperature Number Mode** (``begin(tempSetpoint, minTemp, maxTemp, step)``): Temperature setpoint control with min/max limits and step control (step value can be set in begin() or later) + * **Temperature Number Mode** (``begin(tempSetpoint, minTemp, maxTemp, step)``): Temperature setpoint control with min/max limits and step control * **Temperature Level Mode** (``begin(supportedLevels, levelCount, selectedLevel)``): Temperature level control with array of supported levels * 1/100th degree Celsius precision (for temperature_number mode) * Min/max temperature limits with validation (temperature_number mode) From cc5dac1931bbf54c81d03146cb7c7b7924d53d08 Mon Sep 17 00:00:00 2001 From: SuGlider Date: Thu, 4 Dec 2025 18:31:33 -0300 Subject: [PATCH 17/22] fix(docs): identation and allignment --- docs/en/matter/ep_temperature_controlled_cabinet.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/en/matter/ep_temperature_controlled_cabinet.rst b/docs/en/matter/ep_temperature_controlled_cabinet.rst index d9b9df02e8a..2ce3fbdf443 100644 --- a/docs/en/matter/ep_temperature_controlled_cabinet.rst +++ b/docs/en/matter/ep_temperature_controlled_cabinet.rst @@ -11,8 +11,10 @@ The ``MatterTemperatureControlledCabinet`` class provides a temperature controll **Features:** * Two initialization modes: - * **Temperature Number Mode** (``begin(tempSetpoint, minTemp, maxTemp, step)``): Temperature setpoint control with min/max limits and step control - * **Temperature Level Mode** (``begin(supportedLevels, levelCount, selectedLevel)``): Temperature level control with array of supported levels + + - **Temperature Number Mode** (``begin(tempSetpoint, minTemp, maxTemp, step)``): Temperature setpoint control with min/max limits and step control + - **Temperature Level Mode** (``begin(supportedLevels, levelCount, selectedLevel)``): Temperature level control with array of supported levels + * 1/100th degree Celsius precision (for temperature_number mode) * Min/max temperature limits with validation (temperature_number mode) * Temperature step control (temperature_number mode, always enabled) From e118e02680682cb4667492d567a10427775532ee Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Fri, 5 Dec 2025 11:34:50 -0300 Subject: [PATCH 18/22] fix(docs): reorganization of the text Reinstate important note about mutually exclusive features for temperature control modes. --- docs/en/matter/ep_temperature_controlled_cabinet.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/en/matter/ep_temperature_controlled_cabinet.rst b/docs/en/matter/ep_temperature_controlled_cabinet.rst index 2ce3fbdf443..059d5e9518e 100644 --- a/docs/en/matter/ep_temperature_controlled_cabinet.rst +++ b/docs/en/matter/ep_temperature_controlled_cabinet.rst @@ -7,9 +7,8 @@ About The ``MatterTemperatureControlledCabinet`` class provides a temperature controlled cabinet endpoint for Matter networks. This endpoint implements the Matter temperature control standard for managing temperature setpoints with min/max limits, step control (always enabled), or temperature level support. -**Important:** The ``temperature_number`` and ``temperature_level`` features are **mutually exclusive**. Only one can be enabled at a time. Use ``begin(tempSetpoint, minTemp, maxTemp, step)`` for temperature_number mode or ``begin(supportedLevels, levelCount, selectedLevel)`` for temperature_level mode. - **Features:** + * Two initialization modes: - **Temperature Number Mode** (``begin(tempSetpoint, minTemp, maxTemp, step)``): Temperature setpoint control with min/max limits and step control @@ -24,7 +23,10 @@ The ``MatterTemperatureControlledCabinet`` class provides a temperature controll * Integration with Apple HomeKit, Amazon Alexa, and Google Home * Matter standard compliance +**Important:** The ``temperature_number`` and ``temperature_level`` features are **mutually exclusive**. Only one can be enabled at a time. Use ``begin(tempSetpoint, minTemp, maxTemp, step)`` for temperature_number mode or ``begin(supportedLevels, levelCount, selectedLevel)`` for temperature_level mode. + **Use Cases:** + * Refrigerators and freezers * Wine coolers * Medical storage cabinets From 27ab18ead7afa7bd39dc9ab1bcdb2f31e0cc5f26 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 6 Dec 2025 18:32:12 +0000 Subject: [PATCH 19/22] ci(pre-commit): Apply automatic fixes --- .../ep_temperature_controlled_cabinet.rst | 1 - .../MatterTemperatureControlledCabinet.ino | 23 +- .../README.md | 3 +- .../MatterTemperatureControlledCabinet/ci.yml | 1 - ...tterTemperatureControlledCabinetLevels.ino | 21 +- .../README.md | 3 +- .../ci.yml | 673 +++++++++++++++- .../MatterTemperatureControlledCabinet.cpp | 728 ++---------------- 8 files changed, 766 insertions(+), 687 deletions(-) diff --git a/docs/en/matter/ep_temperature_controlled_cabinet.rst b/docs/en/matter/ep_temperature_controlled_cabinet.rst index 059d5e9518e..f374d878992 100644 --- a/docs/en/matter/ep_temperature_controlled_cabinet.rst +++ b/docs/en/matter/ep_temperature_controlled_cabinet.rst @@ -294,4 +294,3 @@ Temperature Controlled Cabinet (Level Mode) A separate example demonstrates the temperature_level mode with dynamic level updates. The temperature level automatically cycles through all supported levels every 1 second in both directions (increasing and decreasing), allowing Matter controllers to observe real-time changes. The example also monitors and logs when the initial level is reached or overpassed in each direction. See ``MatterTemperatureControlledCabinetLevels`` example for the temperature level mode implementation. - diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino b/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino index 1466fe9e5f5..ba02e44e68d 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinet/MatterTemperatureControlledCabinet.ino @@ -14,7 +14,7 @@ /* * This example demonstrates the Temperature Number mode of the Matter Temperature Controlled Cabinet Device. - * + * * This example 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. @@ -22,7 +22,7 @@ * The example will create a Matter Temperature Controlled Cabinet Device using temperature_number feature. * The Temperature Controlled Cabinet can be controlled via Matter controllers to set * temperature setpoint with min/max limits and optional step control. - * + * * This mode is mutually exclusive with temperature_level mode. * See MatterTemperatureControlledCabinetLevels example for temperature level control. */ @@ -104,10 +104,10 @@ void updateTemperatureSetpoint() { double minTemp = TemperatureCabinet.getMinTemperature(); double maxTemp = TemperatureCabinet.getMaxTemperature(); double step = TemperatureCabinet.getStep(); - + // Calculate next setpoint based on direction and step bool directionChanged = false; - + if (tempState.increasing) { tempState.currentSetpoint += step; if (tempState.currentSetpoint >= maxTemp) { @@ -123,14 +123,13 @@ void updateTemperatureSetpoint() { directionChanged = true; } } - + // Check if setpoint has been reached or overpassed checkSetpointReached(tempState.currentSetpoint, tempState.increasing, directionChanged); - + // Update the temperature setpoint if (TemperatureCabinet.setTemperatureSetpoint(tempState.currentSetpoint)) { - Serial.printf("Temperature setpoint updated to: %.02f°C (Range: %.02f°C to %.02f°C)\r\n", - tempState.currentSetpoint, minTemp, maxTemp); + Serial.printf("Temperature setpoint updated to: %.02f°C (Range: %.02f°C to %.02f°C)\r\n", tempState.currentSetpoint, minTemp, maxTemp); } else { Serial.printf("Failed to update temperature setpoint to: %.02f°C\r\n", tempState.currentSetpoint); } @@ -138,10 +137,10 @@ void updateTemperatureSetpoint() { // Print current temperature status void printTemperatureStatus() { - Serial.printf("Current Temperature Setpoint: %.02f°C (Range: %.02f°C to %.02f°C)\r\n", - TemperatureCabinet.getTemperatureSetpoint(), - TemperatureCabinet.getMinTemperature(), - TemperatureCabinet.getMaxTemperature()); + Serial.printf( + "Current Temperature Setpoint: %.02f°C (Range: %.02f°C to %.02f°C)\r\n", TemperatureCabinet.getTemperatureSetpoint(), + TemperatureCabinet.getMinTemperature(), TemperatureCabinet.getMaxTemperature() + ); } // Handle button press for decommissioning diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md b/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md index ef28d650961..8ed6c69454a 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinet/README.md @@ -172,7 +172,7 @@ The MatterTemperatureControlledCabinet example consists of the following main co 1. **`setup()`**: Initializes hardware (button), configures Wi-Fi (if needed), sets up the Matter Temperature Controlled Cabinet endpoint with initial temperature configuration, and waits for Matter commissioning. -2. **`loop()`**: +2. **`loop()`**: - **Dynamic Temperature Updates**: Automatically changes the temperature setpoint every 1 second, cycling between the minimum and maximum temperature limits using the configured step value. This demonstrates the temperature control functionality and allows Matter controllers to observe real-time changes. - **Setpoint Reached Detection**: Monitors when the initial setpoint is reached or overpassed in each direction and prints a notification message once per direction. - Periodically prints the current temperature setpoint (every 5 seconds) @@ -213,4 +213,3 @@ The example demonstrates the following API methods: ## License This example is licensed under the Apache License, Version 2.0. - diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinet/ci.yml b/libraries/Matter/examples/MatterTemperatureControlledCabinet/ci.yml index 1cbd404d6ed..050a80ff543 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinet/ci.yml +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinet/ci.yml @@ -2,4 +2,3 @@ fqbn_append: PartitionScheme=huge_app requires: - CONFIG_ESP_MATTER_ENABLE_DATA_MODEL=y - diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino index 4a107ed9f56..e246f7d8a56 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/MatterTemperatureControlledCabinetLevels.ino @@ -14,7 +14,7 @@ /* * This example demonstrates the Temperature Level mode of the Matter Temperature Controlled Cabinet Device. - * + * * This example 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. @@ -22,7 +22,7 @@ * The example will create a Matter Temperature Controlled Cabinet Device using temperature_level feature. * The Temperature Controlled Cabinet can be controlled via Matter controllers to set * temperature levels from a predefined array of supported levels. - * + * * This mode is mutually exclusive with temperature_number mode. * See MatterTemperatureControlledCabinet example for temperature setpoint control. */ @@ -71,12 +71,7 @@ struct LevelControlState { }; static LevelControlState levelState = { - .initialized = false, - .increasing = true, - .currentLevelIndex = 0, - .initialLevel = 0, - .levelReachedIncreasing = false, - .levelReachedDecreasing = false + .initialized = false, .increasing = true, .currentLevelIndex = 0, .initialLevel = 0, .levelReachedIncreasing = false, .levelReachedDecreasing = false }; // Initialize level control state @@ -117,7 +112,7 @@ void checkLevelReached(uint8_t newLevel, bool isIncreasing, bool directionChange void updateTemperatureLevel() { // Cycle through supported levels in both directions bool directionChanged = false; - + if (levelState.increasing) { levelState.currentLevelIndex++; if (levelState.currentLevelIndex >= levelCount) { @@ -134,12 +129,12 @@ void updateTemperatureLevel() { levelState.currentLevelIndex--; } } - + uint8_t newLevel = supportedLevels[levelState.currentLevelIndex]; - + // Check if initial level has been reached or overpassed checkLevelReached(newLevel, levelState.increasing, directionChanged); - + // Update the temperature level if (TemperatureCabinet.setSelectedTemperatureLevel(newLevel)) { Serial.printf("Temperature level updated to: %u (Supported Levels: ", newLevel); @@ -212,7 +207,7 @@ void setup() { // - supportedLevels: Array of temperature level values (0-255) // - levelCount: Number of levels in the array // - initialLevel: Initial selected temperature level - // + // // Note: This mode is mutually exclusive with temperature_number mode. // See MatterTemperatureControlledCabinet example for temperature setpoint control. if (!TemperatureCabinet.begin(supportedLevels, levelCount, initialLevel)) { diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/README.md b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/README.md index 9a36593c214..88f74a634bd 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/README.md +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/README.md @@ -172,7 +172,7 @@ The MatterTemperatureControlledCabinetLevels example consists of the following m 1. **`setup()`**: Initializes hardware (button), configures Wi-Fi (if needed), sets up the Matter Temperature Controlled Cabinet endpoint with temperature level configuration, and waits for Matter commissioning. -2. **`loop()`**: +2. **`loop()`**: - **Dynamic Level Updates**: Automatically cycles through all supported temperature levels every 1 second in both directions (increasing and decreasing). This demonstrates the temperature level control functionality and allows Matter controllers to observe real-time changes. - **Level Reached Detection**: Monitors when the initial level is reached or overpassed in each direction and prints a notification message once per direction. - Periodically prints the current temperature level (every 5 seconds) @@ -213,4 +213,3 @@ The example demonstrates the following API methods: ## License This example is licensed under the Apache License, Version 2.0. - diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/ci.yml b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/ci.yml index 1cbd404d6ed..3e0545af141 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/ci.yml +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/ci.yml @@ -1,5 +1,672 @@ -fqbn_append: PartitionScheme=huge_app +// 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 -requires: - - CONFIG_ESP_MATTER_ENABLE_DATA_MODEL=y +// 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. +#include +#ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL + +#include +#include +#include +#include +#include + +using namespace esp_matter; +using namespace esp_matter::endpoint; +using namespace esp_matter::cluster; +using namespace chip::app::Clusters; + +// Custom endpoint for temperature_level_controlled_cabinet device +// This endpoint uses temperature_level feature instead of temperature_number +namespace esp_matter { +using namespace cluster; +namespace endpoint { +namespace temperature_level_controlled_cabinet { +typedef struct config { + cluster::descriptor::config_t descriptor; + cluster::temperature_control::config_t temperature_control; +} config_t; + +uint32_t get_device_type_id() { + return ESP_MATTER_TEMPERATURE_CONTROLLED_CABINET_DEVICE_TYPE_ID; +} + +uint8_t get_device_type_version() { + return ESP_MATTER_TEMPERATURE_CONTROLLED_CABINET_DEVICE_TYPE_VERSION; +} + +esp_err_t add(endpoint_t *endpoint, config_t *config) { + if (!endpoint) { + log_e("Endpoint cannot be NULL"); + return ESP_ERR_INVALID_ARG; + } + esp_err_t err = add_device_type(endpoint, get_device_type_id(), get_device_type_version()); + if (err != ESP_OK) { + log_e("Failed to add device type id:%" PRIu32 ",err: %d", get_device_type_id(), err); + return err; + } + + // Create temperature_control cluster with temperature_level feature + // Note: temperature_number and temperature_level are mutually exclusive + temperature_control::create(endpoint, &(config->temperature_control), CLUSTER_FLAG_SERVER, temperature_control::feature::temperature_level::get_id()); + + return ESP_OK; +} + +endpoint_t *create(node_t *node, config_t *config, uint8_t flags, void *priv_data) { + endpoint_t *endpoint = endpoint::create(node, flags, priv_data); + add(endpoint, config); + return endpoint; +} +} // namespace temperature_level_controlled_cabinet +} // namespace endpoint +} // namespace esp_matter + +bool MatterTemperatureControlledCabinet::attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val) { + bool ret = true; + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + log_d("Temperature Controlled Cabinet Attr update callback: endpoint: %u, cluster: %u, attribute: %u", endpoint_id, cluster_id, attribute_id); + + // Handle TemperatureControl cluster attribute changes from Matter controller + if (cluster_id == TemperatureControl::Id) { + switch (attribute_id) { + case TemperatureControl::Attributes::TemperatureSetpoint::Id: + if (useTemperatureNumber) { + rawTempSetpoint = val->val.i16; + log_i("Temperature setpoint changed to %.02fC", (float)rawTempSetpoint / 100.0); + } else { + log_w("Temperature setpoint change ignored - temperature_level feature is active"); + } + break; + + case TemperatureControl::Attributes::MinTemperature::Id: + if (useTemperatureNumber) { + rawMinTemperature = val->val.i16; + log_i("Min temperature changed to %.02fC", (float)rawMinTemperature / 100.0); + } else { + log_w("Min temperature change ignored - temperature_level feature is active"); + } + break; + + case TemperatureControl::Attributes::MaxTemperature::Id: + if (useTemperatureNumber) { + rawMaxTemperature = val->val.i16; + log_i("Max temperature changed to %.02fC", (float)rawMaxTemperature / 100.0); + } else { + log_w("Max temperature change ignored - temperature_level feature is active"); + } + break; + + case TemperatureControl::Attributes::Step::Id: + if (useTemperatureNumber) { + rawStep = val->val.i16; + log_i("Temperature step changed to %.02fC", (float)rawStep / 100.0); + } else { + log_w("Temperature step change ignored - temperature_level feature is active"); + } + break; + + case TemperatureControl::Attributes::SelectedTemperatureLevel::Id: + if (!useTemperatureNumber) { + selectedTempLevel = val->val.u8; + log_i("Selected temperature level changed to %u", selectedTempLevel); + } else { + log_w("Selected temperature level change ignored - temperature_number feature is active"); + } + break; + + case TemperatureControl::Attributes::SupportedTemperatureLevels::Id: + // SupportedTemperatureLevels is read-only (managed via iterator delegate) + // But if a controller tries to write it, we should log it + log_w("SupportedTemperatureLevels change attempted - this attribute is read-only"); + break; + + default: log_d("Unhandled TemperatureControl Attribute ID: %u", attribute_id); break; + } + } + + return ret; +} + +MatterTemperatureControlledCabinet::MatterTemperatureControlledCabinet() {} + +MatterTemperatureControlledCabinet::~MatterTemperatureControlledCabinet() { + end(); +} + +bool MatterTemperatureControlledCabinet::begin(double tempSetpoint, double minTemperature, double maxTemperature, double step) { + int16_t rawSetpoint = static_cast(tempSetpoint * 100.0); + int16_t rawMin = static_cast(minTemperature * 100.0); + int16_t rawMax = static_cast(maxTemperature * 100.0); + int16_t rawStepValue = static_cast(step * 100.0); + return begin(rawSetpoint, rawMin, rawMax, rawStepValue); +} + +bool MatterTemperatureControlledCabinet::begin(int16_t _rawTempSetpoint, int16_t _rawMinTemperature, int16_t _rawMaxTemperature, int16_t _rawStep) { + ArduinoMatter::_init(); + + if (getEndPointId() != 0) { + log_e("Temperature Controlled Cabinet with Endpoint Id %d device has already been created.", getEndPointId()); + return false; + } + + // Note: esp-matter automatically creates all attributes from the config struct when features are enabled + // - temperature_number feature creates: TemperatureSetpoint, MinTemperature, MaxTemperature + // - temperature_step feature creates: Step (always enabled for temperature_number mode to allow setStep() later) + // No need to manually set attributes here as they are already created with the config values + + temperature_controlled_cabinet::config_t cabinet_config; + cabinet_config.temperature_control.temperature_number.temp_setpoint = _rawTempSetpoint; + cabinet_config.temperature_control.temperature_number.min_temperature = _rawMinTemperature; + cabinet_config.temperature_control.temperature_number.max_temperature = _rawMaxTemperature; + cabinet_config.temperature_control.temperature_step.step = _rawStep; + cabinet_config.temperature_control.temperature_level.selected_temp_level = 0; + + // Enable temperature_number feature (required) + // Note: temperature_number and temperature_level are mutually exclusive. + // Only one of them can be enabled at a time. + cabinet_config.temperature_control.features = temperature_control::feature::temperature_number::get_id(); + + // Always enable temperature_step feature to allow setStep() to be called later + // Note: temperature_step requires temperature_number feature (which is always enabled for this mode) + // The step value can be set initially via begin() or later via setStep() + cabinet_config.temperature_control.features |= temperature_control::feature::temperature_step::get_id(); + + // endpoint handles can be used to add/modify clusters + endpoint_t *endpoint = temperature_controlled_cabinet::create(node::get(), &cabinet_config, ENDPOINT_FLAG_NONE, (void *)this); + if (endpoint == nullptr) { + log_e("Failed to create Temperature Controlled Cabinet endpoint"); + return false; + } + + rawTempSetpoint = _rawTempSetpoint; + rawMinTemperature = _rawMinTemperature; + rawMaxTemperature = _rawMaxTemperature; + rawStep = _rawStep; + selectedTempLevel = 0; + useTemperatureNumber = true; // Set feature mode to temperature_number + + setEndPointId(endpoint::get_id(endpoint)); + log_i("Temperature Controlled Cabinet created with temperature_number feature, endpoint_id %d", getEndPointId()); + + // Workaround: Manually create Step attribute if it wasn't created automatically + // This handles the case where temperature_step::add() fails due to feature map timing issue + // The feature map check in temperature_step::add() may not see the temperature_number feature + // immediately after it's added, causing the Step attribute to not be created + cluster_t *cluster = cluster::get(endpoint, TemperatureControl::Id); + if (cluster != nullptr) { + attribute_t *step_attr = attribute::get(cluster, TemperatureControl::Attributes::Step::Id); + if (step_attr == nullptr) { + // Step attribute wasn't created, manually create it + log_w("Step attribute not found after endpoint creation, manually creating it"); + step_attr = temperature_control::attribute::create_step(cluster, _rawStep); + if (step_attr != nullptr) { + // Update the feature map to include temperature_step feature + // This ensures the feature is properly registered even though the attribute was created manually + esp_matter_attr_val_t feature_map_val = esp_matter_invalid(NULL); + attribute_t *feature_map_attr = attribute::get(cluster, Globals::Attributes::FeatureMap::Id); + if (feature_map_attr != nullptr && attribute::get_val(feature_map_attr, &feature_map_val) == ESP_OK) { + feature_map_val.val.u32 |= temperature_control::feature::temperature_step::get_id(); + attribute::set_val(feature_map_attr, &feature_map_val); + } + log_i("Step attribute manually created with value %.02fC", (float)_rawStep / 100.0); + } else { + log_e("Failed to manually create Step attribute"); + } + } + } + + started = true; + return true; +} + +bool MatterTemperatureControlledCabinet::begin(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel) { + if (supportedLevels == nullptr || levelCount == 0) { + log_e("Invalid supportedLevels array or levelCount. Must provide at least one level."); + return false; + } + + // Validate against maximum from ESP-Matter + if (levelCount > temperature_control::k_max_temp_level_count) { + log_e("Level count %u exceeds maximum %u", levelCount, temperature_control::k_max_temp_level_count); + return false; + } + + // Validate that selectedLevel exists in supportedLevels array + bool levelFound = false; + for (uint16_t i = 0; i < levelCount; i++) { + if (supportedLevels[i] == selectedLevel) { + levelFound = true; + break; + } + } + if (!levelFound) { + log_e("Selected level %u is not in the supported levels array", selectedLevel); + return false; + } + return beginInternal(supportedLevels, levelCount, selectedLevel); +} + +bool MatterTemperatureControlledCabinet::beginInternal(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel) { + ArduinoMatter::_init(); + + if (getEndPointId() != 0) { + log_e("Temperature Controlled Cabinet with Endpoint Id %d device has already been created.", getEndPointId()); + return false; + } + + // Use custom temperature_level_controlled_cabinet endpoint that supports temperature_level feature + temperature_level_controlled_cabinet::config_t cabinet_config; + // Initialize temperature_number config (not used but required for struct) + cabinet_config.temperature_control.temperature_number.temp_setpoint = 0; + cabinet_config.temperature_control.temperature_number.min_temperature = 0; + cabinet_config.temperature_control.temperature_number.max_temperature = 0; + cabinet_config.temperature_control.temperature_step.step = 0; + cabinet_config.temperature_control.temperature_level.selected_temp_level = selectedLevel; + + // Enable temperature_level feature + // Note: temperature_number and temperature_level are mutually exclusive. + // Only one of them can be enabled at a time. + cabinet_config.temperature_control.features = temperature_control::feature::temperature_level::get_id(); + + // endpoint handles can be used to add/modify clusters + endpoint_t *endpoint = temperature_level_controlled_cabinet::create(node::get(), &cabinet_config, ENDPOINT_FLAG_NONE, (void *)this); + if (endpoint == nullptr) { + log_e("Failed to create Temperature Level Controlled Cabinet endpoint"); + return false; + } + + // Copy supported levels array into internal buffer + memcpy(supportedLevelsArray, supportedLevels, levelCount * sizeof(uint8_t)); + supportedLevelsCount = levelCount; + selectedTempLevel = selectedLevel; + useTemperatureNumber = false; // Set feature mode to temperature_level + + // Initialize temperature_number values to 0 (not used in this mode) + rawTempSetpoint = 0; + rawMinTemperature = 0; + rawMaxTemperature = 0; + rawStep = 0; + + setEndPointId(endpoint::get_id(endpoint)); + log_i("Temperature Level Controlled Cabinet created with temperature_level feature, endpoint_id %d", getEndPointId()); + + // Set started flag before calling setter methods (they check for started) + started = true; + + // Set supported temperature levels using internal copy + if (!setSupportedTemperatureLevels(supportedLevelsArray, levelCount)) { + log_e("Failed to set supported temperature levels"); + started = false; // Reset on failure + return false; + } + + // Set selected temperature level + if (!setSelectedTemperatureLevel(selectedLevel)) { + log_e("Failed to set selected temperature level"); + started = false; // Reset on failure + return false; + } + + return true; +} + +void MatterTemperatureControlledCabinet::end() { + started = false; + useTemperatureNumber = true; // Reset to default + supportedLevelsCount = 0; + // No need to clear array - it's a fixed-size buffer +} + +bool MatterTemperatureControlledCabinet::setRawTemperatureSetpoint(int16_t _rawTemperature) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (!useTemperatureNumber) { + log_e("Temperature setpoint methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return false; + } + + // Validate against min/max + if (_rawTemperature < rawMinTemperature || _rawTemperature > rawMaxTemperature) { + log_e( + "Temperature setpoint %.02fC is out of range [%.02fC, %.02fC]", (float)_rawTemperature / 100.0, (float)rawMinTemperature / 100.0, + (float)rawMaxTemperature / 100.0 + ); + return false; + } + + // avoid processing if there was no change + if (rawTempSetpoint == _rawTemperature) { + return true; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::TemperatureSetpoint::Id, &tempVal)) { + log_e("Failed to get Temperature Controlled Cabinet Temperature Setpoint Attribute."); + return false; + } + if (tempVal.val.i16 != _rawTemperature) { + tempVal.val.i16 = _rawTemperature; + bool ret; + ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::TemperatureSetpoint::Id, &tempVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Temperature Setpoint Attribute."); + return false; + } + rawTempSetpoint = _rawTemperature; + } + log_v("Temperature Controlled Cabinet setpoint set to %.02fC", (float)_rawTemperature / 100.00); + + return true; +} + +bool MatterTemperatureControlledCabinet::setTemperatureSetpoint(double temperature) { + int16_t rawValue = static_cast(temperature * 100.0); + return setRawTemperatureSetpoint(rawValue); +} + +double MatterTemperatureControlledCabinet::getTemperatureSetpoint() { + if (!useTemperatureNumber) { + log_e("Temperature setpoint methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return 0.0; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::TemperatureSetpoint::Id, &tempVal)) { + rawTempSetpoint = tempVal.val.i16; + } + return (double)rawTempSetpoint / 100.0; +} + +bool MatterTemperatureControlledCabinet::setRawMinTemperature(int16_t _rawTemperature) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (!useTemperatureNumber) { + log_e("Min temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return false; + } + + if (rawMinTemperature == _rawTemperature) { + return true; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MinTemperature::Id, &tempVal)) { + log_e("Failed to get Temperature Controlled Cabinet Min Temperature Attribute."); + return false; + } + if (tempVal.val.i16 != _rawTemperature) { + tempVal.val.i16 = _rawTemperature; + bool ret; + ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MinTemperature::Id, &tempVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Min Temperature Attribute."); + return false; + } + rawMinTemperature = _rawTemperature; + } + log_v("Temperature Controlled Cabinet min temperature set to %.02fC", (float)_rawTemperature / 100.00); + + return true; +} + +bool MatterTemperatureControlledCabinet::setMinTemperature(double temperature) { + int16_t rawValue = static_cast(temperature * 100.0); + return setRawMinTemperature(rawValue); +} + +double MatterTemperatureControlledCabinet::getMinTemperature() { + if (!useTemperatureNumber) { + log_e("Min temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return 0.0; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MinTemperature::Id, &tempVal)) { + rawMinTemperature = tempVal.val.i16; + } + return (double)rawMinTemperature / 100.0; +} + +bool MatterTemperatureControlledCabinet::setRawMaxTemperature(int16_t _rawTemperature) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (!useTemperatureNumber) { + log_e("Max temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return false; + } + + if (rawMaxTemperature == _rawTemperature) { + return true; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MaxTemperature::Id, &tempVal)) { + log_e("Failed to get Temperature Controlled Cabinet Max Temperature Attribute."); + return false; + } + if (tempVal.val.i16 != _rawTemperature) { + tempVal.val.i16 = _rawTemperature; + bool ret; + ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MaxTemperature::Id, &tempVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Max Temperature Attribute."); + return false; + } + rawMaxTemperature = _rawTemperature; + } + log_v("Temperature Controlled Cabinet max temperature set to %.02fC", (float)_rawTemperature / 100.00); + + return true; +} + +bool MatterTemperatureControlledCabinet::setMaxTemperature(double temperature) { + int16_t rawValue = static_cast(temperature * 100.0); + return setRawMaxTemperature(rawValue); +} + +double MatterTemperatureControlledCabinet::getMaxTemperature() { + if (!useTemperatureNumber) { + log_e("Max temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return 0.0; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MaxTemperature::Id, &tempVal)) { + rawMaxTemperature = tempVal.val.i16; + } + return (double)rawMaxTemperature / 100.0; +} + +bool MatterTemperatureControlledCabinet::setRawStep(int16_t _rawStep) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (!useTemperatureNumber) { + log_e("Temperature step methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return false; + } + + if (rawStep == _rawStep) { + return true; + } + + esp_matter_attr_val_t stepVal = esp_matter_invalid(NULL); + if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::Step::Id, &stepVal)) { + log_e("Failed to get Temperature Controlled Cabinet Step Attribute. Temperature_step feature may not be enabled."); + return false; + } + if (stepVal.val.i16 != _rawStep) { + stepVal.val.i16 = _rawStep; + bool ret; + ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::Step::Id, &stepVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Step Attribute."); + return false; + } + rawStep = _rawStep; + } + log_v("Temperature Controlled Cabinet step set to %.02fC", (float)_rawStep / 100.00); + + return true; +} + +bool MatterTemperatureControlledCabinet::setStep(double step) { + int16_t rawValue = static_cast(step * 100.0); + return setRawStep(rawValue); +} + +double MatterTemperatureControlledCabinet::getStep() { + if (!useTemperatureNumber) { + log_e("Temperature step methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return 0.0; + } + + // Read from attribute (should always exist after begin() due to workaround) + // If read fails, use stored rawStep value from begin() + esp_matter_attr_val_t stepVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::Step::Id, &stepVal)) { + rawStep = stepVal.val.i16; + } + return (double)rawStep / 100.0; +} + +bool MatterTemperatureControlledCabinet::setSelectedTemperatureLevel(uint8_t level) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (useTemperatureNumber) { + log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); + return false; + } + + // Validate that level is in supported levels array + bool levelFound = false; + for (uint16_t i = 0; i < supportedLevelsCount; i++) { + if (supportedLevelsArray[i] == level) { + levelFound = true; + break; + } + } + if (!levelFound) { + log_e("Temperature level %u is not in the supported levels array", level); + return false; + } + if (selectedTempLevel == level) { + return true; + } + + esp_matter_attr_val_t levelVal = esp_matter_invalid(NULL); + if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SelectedTemperatureLevel::Id, &levelVal)) { + log_e("Failed to get Temperature Controlled Cabinet Selected Temperature Level Attribute."); + return false; + } + if (levelVal.val.u8 != level) { + levelVal.val.u8 = level; + bool ret; + ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SelectedTemperatureLevel::Id, &levelVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Selected Temperature Level Attribute."); + return false; + } + selectedTempLevel = level; + } + log_v("Temperature Controlled Cabinet selected temperature level set to %u", level); + + return true; +} + +uint8_t MatterTemperatureControlledCabinet::getSelectedTemperatureLevel() { + if (useTemperatureNumber) { + log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); + return 0; + } + + esp_matter_attr_val_t levelVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SelectedTemperatureLevel::Id, &levelVal)) { + selectedTempLevel = levelVal.val.u8; + } + return selectedTempLevel; +} + +bool MatterTemperatureControlledCabinet::setSupportedTemperatureLevels(uint8_t *levels, uint16_t count) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (useTemperatureNumber) { + log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); + return false; + } + + if (levels == nullptr || count == 0) { + log_e("Invalid levels array or count."); + return false; + } + + // Validate against maximum from ESP-Matter + if (count > temperature_control::k_max_temp_level_count) { + log_e("Level count %u exceeds maximum %u", count, temperature_control::k_max_temp_level_count); + return false; + } + + // Copy the array into internal buffer + memcpy(supportedLevelsArray, levels, count * sizeof(uint8_t)); + supportedLevelsCount = count; + + // Use internal copy for Matter attribute update + // Use esp_matter_array helper function which properly initializes the structure + esp_matter_attr_val_t levelsVal = esp_matter_array(supportedLevelsArray, sizeof(uint8_t), count); + + bool ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SupportedTemperatureLevels::Id, &levelsVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Supported Temperature Levels Attribute."); + return false; + } + log_v("Temperature Controlled Cabinet supported temperature levels updated, count: %u", count); + + return true; +} + +uint16_t MatterTemperatureControlledCabinet::getSupportedTemperatureLevelsCount() { + if (useTemperatureNumber) { + log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); + return 0; + } + + esp_matter_attr_val_t levelsVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SupportedTemperatureLevels::Id, &levelsVal)) { + return levelsVal.val.a.n; // a.n is the count (number of elements) + } + return 0; +} + +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ diff --git a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp index 43210dfd836..09377cf85bb 100644 --- a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp +++ b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp @@ -12,662 +12,84 @@ // See the License for the specific language governing permissions and // limitations under the License. +#pragma once #include #ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL -#include #include -#include -#include -#include - -using namespace esp_matter; -using namespace esp_matter::endpoint; -using namespace esp_matter::cluster; -using namespace chip::app::Clusters; - -// Custom endpoint for temperature_level_controlled_cabinet device -// This endpoint uses temperature_level feature instead of temperature_number -namespace esp_matter { -using namespace cluster; -namespace endpoint { -namespace temperature_level_controlled_cabinet { -typedef struct config { - cluster::descriptor::config_t descriptor; - cluster::temperature_control::config_t temperature_control; -} config_t; - -uint32_t get_device_type_id() { - return ESP_MATTER_TEMPERATURE_CONTROLLED_CABINET_DEVICE_TYPE_ID; -} - -uint8_t get_device_type_version() { - return ESP_MATTER_TEMPERATURE_CONTROLLED_CABINET_DEVICE_TYPE_VERSION; -} - -esp_err_t add(endpoint_t *endpoint, config_t *config) { - if (!endpoint) { - log_e("Endpoint cannot be NULL"); - return ESP_ERR_INVALID_ARG; - } - esp_err_t err = add_device_type(endpoint, get_device_type_id(), get_device_type_version()); - if (err != ESP_OK) { - log_e("Failed to add device type id:%" PRIu32 ",err: %d", get_device_type_id(), err); - return err; - } - - // Create temperature_control cluster with temperature_level feature +#include + +class MatterTemperatureControlledCabinet : public MatterEndPoint { +public: + MatterTemperatureControlledCabinet(); + ~MatterTemperatureControlledCabinet(); + + // begin with temperature_number feature (mutually exclusive with temperature_level) + // This enables temperature setpoint control with min/max limits and optional step + bool begin(double tempSetpoint = 0.00, double minTemperature = -10.0, double maxTemperature = 32.0, double step = 0.50); + + // begin with temperature_level feature (mutually exclusive with temperature_number) + // This enables temperature level control with an array of supported levels + bool begin(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel = 0); + + // this will stop processing Temperature Controlled Cabinet Matter events + void end(); + + // set the temperature setpoint + bool setTemperatureSetpoint(double temperature); + // returns the temperature setpoint in Celsius + double getTemperatureSetpoint(); + + // set the minimum temperature + bool setMinTemperature(double temperature); + // returns the minimum temperature in Celsius + double getMinTemperature(); + + // set the maximum temperature + bool setMaxTemperature(double temperature); + // returns the maximum temperature in Celsius + double getMaxTemperature(); + + // set the temperature step (optional, requires temperature_step feature) + bool setStep(double step); + // returns the temperature step in Celsius + double getStep(); + + // set the selected temperature level (optional, requires temperature_level feature) + bool setSelectedTemperatureLevel(uint8_t level); + // returns the selected temperature level + uint8_t getSelectedTemperatureLevel(); + + // set supported temperature levels (optional, requires temperature_level feature) + bool setSupportedTemperatureLevels(uint8_t *levels, uint16_t count); + // get supported temperature levels count + uint16_t getSupportedTemperatureLevelsCount(); + + // this function is called by Matter internal event processor. It could be overwritten by the application, if necessary. + bool attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val); + +protected: + bool started = false; + // Feature mode: true = temperature_number, false = temperature_level // Note: temperature_number and temperature_level are mutually exclusive - temperature_control::create(endpoint, &(config->temperature_control), CLUSTER_FLAG_SERVER, temperature_control::feature::temperature_level::get_id()); - - return ESP_OK; -} - -endpoint_t *create(node_t *node, config_t *config, uint8_t flags, void *priv_data) { - endpoint_t *endpoint = endpoint::create(node, flags, priv_data); - add(endpoint, config); - return endpoint; -} -} // namespace temperature_level_controlled_cabinet -} // namespace endpoint -} // namespace esp_matter - -bool MatterTemperatureControlledCabinet::attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val) { - bool ret = true; - if (!started) { - log_e("Matter Temperature Controlled Cabinet device has not begun."); - return false; - } - - log_d("Temperature Controlled Cabinet Attr update callback: endpoint: %u, cluster: %u, attribute: %u", endpoint_id, cluster_id, attribute_id); - - // Handle TemperatureControl cluster attribute changes from Matter controller - if (cluster_id == TemperatureControl::Id) { - switch (attribute_id) { - case TemperatureControl::Attributes::TemperatureSetpoint::Id: - if (useTemperatureNumber) { - rawTempSetpoint = val->val.i16; - log_i("Temperature setpoint changed to %.02fC", (float)rawTempSetpoint / 100.0); - } else { - log_w("Temperature setpoint change ignored - temperature_level feature is active"); - } - break; - - case TemperatureControl::Attributes::MinTemperature::Id: - if (useTemperatureNumber) { - rawMinTemperature = val->val.i16; - log_i("Min temperature changed to %.02fC", (float)rawMinTemperature / 100.0); - } else { - log_w("Min temperature change ignored - temperature_level feature is active"); - } - break; - - case TemperatureControl::Attributes::MaxTemperature::Id: - if (useTemperatureNumber) { - rawMaxTemperature = val->val.i16; - log_i("Max temperature changed to %.02fC", (float)rawMaxTemperature / 100.0); - } else { - log_w("Max temperature change ignored - temperature_level feature is active"); - } - break; - - case TemperatureControl::Attributes::Step::Id: - if (useTemperatureNumber) { - rawStep = val->val.i16; - log_i("Temperature step changed to %.02fC", (float)rawStep / 100.0); - } else { - log_w("Temperature step change ignored - temperature_level feature is active"); - } - break; - - case TemperatureControl::Attributes::SelectedTemperatureLevel::Id: - if (!useTemperatureNumber) { - selectedTempLevel = val->val.u8; - log_i("Selected temperature level changed to %u", selectedTempLevel); - } else { - log_w("Selected temperature level change ignored - temperature_number feature is active"); - } - break; - - case TemperatureControl::Attributes::SupportedTemperatureLevels::Id: - // SupportedTemperatureLevels is read-only (managed via iterator delegate) - // But if a controller tries to write it, we should log it - log_w("SupportedTemperatureLevels change attempted - this attribute is read-only"); - break; - - default: - log_d("Unhandled TemperatureControl Attribute ID: %u", attribute_id); - break; - } - } - - return ret; -} - -MatterTemperatureControlledCabinet::MatterTemperatureControlledCabinet() {} - -MatterTemperatureControlledCabinet::~MatterTemperatureControlledCabinet() { - end(); -} - -bool MatterTemperatureControlledCabinet::begin(double tempSetpoint, double minTemperature, double maxTemperature, double step) { - int16_t rawSetpoint = static_cast(tempSetpoint * 100.0); - int16_t rawMin = static_cast(minTemperature * 100.0); - int16_t rawMax = static_cast(maxTemperature * 100.0); - int16_t rawStepValue = static_cast(step * 100.0); - return begin(rawSetpoint, rawMin, rawMax, rawStepValue); -} - -bool MatterTemperatureControlledCabinet::begin(int16_t _rawTempSetpoint, int16_t _rawMinTemperature, int16_t _rawMaxTemperature, int16_t _rawStep) { - ArduinoMatter::_init(); - - if (getEndPointId() != 0) { - log_e("Temperature Controlled Cabinet with Endpoint Id %d device has already been created.", getEndPointId()); - return false; - } - - // Note: esp-matter automatically creates all attributes from the config struct when features are enabled - // - temperature_number feature creates: TemperatureSetpoint, MinTemperature, MaxTemperature - // - temperature_step feature creates: Step (always enabled for temperature_number mode to allow setStep() later) - // No need to manually set attributes here as they are already created with the config values - - temperature_controlled_cabinet::config_t cabinet_config; - cabinet_config.temperature_control.temperature_number.temp_setpoint = _rawTempSetpoint; - cabinet_config.temperature_control.temperature_number.min_temperature = _rawMinTemperature; - cabinet_config.temperature_control.temperature_number.max_temperature = _rawMaxTemperature; - cabinet_config.temperature_control.temperature_step.step = _rawStep; - cabinet_config.temperature_control.temperature_level.selected_temp_level = 0; - - // Enable temperature_number feature (required) - // Note: temperature_number and temperature_level are mutually exclusive. - // Only one of them can be enabled at a time. - cabinet_config.temperature_control.features = temperature_control::feature::temperature_number::get_id(); - - // Always enable temperature_step feature to allow setStep() to be called later - // Note: temperature_step requires temperature_number feature (which is always enabled for this mode) - // The step value can be set initially via begin() or later via setStep() - cabinet_config.temperature_control.features |= temperature_control::feature::temperature_step::get_id(); - - // endpoint handles can be used to add/modify clusters - endpoint_t *endpoint = temperature_controlled_cabinet::create(node::get(), &cabinet_config, ENDPOINT_FLAG_NONE, (void *)this); - if (endpoint == nullptr) { - log_e("Failed to create Temperature Controlled Cabinet endpoint"); - return false; - } - - rawTempSetpoint = _rawTempSetpoint; - rawMinTemperature = _rawMinTemperature; - rawMaxTemperature = _rawMaxTemperature; - rawStep = _rawStep; - selectedTempLevel = 0; - useTemperatureNumber = true; // Set feature mode to temperature_number - - setEndPointId(endpoint::get_id(endpoint)); - log_i("Temperature Controlled Cabinet created with temperature_number feature, endpoint_id %d", getEndPointId()); - - // Workaround: Manually create Step attribute if it wasn't created automatically - // This handles the case where temperature_step::add() fails due to feature map timing issue - // The feature map check in temperature_step::add() may not see the temperature_number feature - // immediately after it's added, causing the Step attribute to not be created - cluster_t *cluster = cluster::get(endpoint, TemperatureControl::Id); - if (cluster != nullptr) { - attribute_t *step_attr = attribute::get(cluster, TemperatureControl::Attributes::Step::Id); - if (step_attr == nullptr) { - // Step attribute wasn't created, manually create it - log_w("Step attribute not found after endpoint creation, manually creating it"); - step_attr = temperature_control::attribute::create_step(cluster, _rawStep); - if (step_attr != nullptr) { - // Update the feature map to include temperature_step feature - // This ensures the feature is properly registered even though the attribute was created manually - esp_matter_attr_val_t feature_map_val = esp_matter_invalid(NULL); - attribute_t *feature_map_attr = attribute::get(cluster, Globals::Attributes::FeatureMap::Id); - if (feature_map_attr != nullptr && attribute::get_val(feature_map_attr, &feature_map_val) == ESP_OK) { - feature_map_val.val.u32 |= temperature_control::feature::temperature_step::get_id(); - attribute::set_val(feature_map_attr, &feature_map_val); - } - log_i("Step attribute manually created with value %.02fC", (float)_rawStep / 100.0); - } else { - log_e("Failed to manually create Step attribute"); - } - } - } - - started = true; - return true; -} - -bool MatterTemperatureControlledCabinet::begin(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel) { - if (supportedLevels == nullptr || levelCount == 0) { - log_e("Invalid supportedLevels array or levelCount. Must provide at least one level."); - return false; - } - - // Validate against maximum from ESP-Matter - if (levelCount > temperature_control::k_max_temp_level_count) { - log_e("Level count %u exceeds maximum %u", levelCount, temperature_control::k_max_temp_level_count); - return false; - } - - // Validate that selectedLevel exists in supportedLevels array - bool levelFound = false; - for (uint16_t i = 0; i < levelCount; i++) { - if (supportedLevels[i] == selectedLevel) { - levelFound = true; - break; - } - } - if (!levelFound) { - log_e("Selected level %u is not in the supported levels array", selectedLevel); - return false; - } - return beginInternal(supportedLevels, levelCount, selectedLevel); -} - -bool MatterTemperatureControlledCabinet::beginInternal(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel) { - ArduinoMatter::_init(); - - if (getEndPointId() != 0) { - log_e("Temperature Controlled Cabinet with Endpoint Id %d device has already been created.", getEndPointId()); - return false; - } - - // Use custom temperature_level_controlled_cabinet endpoint that supports temperature_level feature - temperature_level_controlled_cabinet::config_t cabinet_config; - // Initialize temperature_number config (not used but required for struct) - cabinet_config.temperature_control.temperature_number.temp_setpoint = 0; - cabinet_config.temperature_control.temperature_number.min_temperature = 0; - cabinet_config.temperature_control.temperature_number.max_temperature = 0; - cabinet_config.temperature_control.temperature_step.step = 0; - cabinet_config.temperature_control.temperature_level.selected_temp_level = selectedLevel; - - // Enable temperature_level feature - // Note: temperature_number and temperature_level are mutually exclusive. - // Only one of them can be enabled at a time. - cabinet_config.temperature_control.features = temperature_control::feature::temperature_level::get_id(); - - // endpoint handles can be used to add/modify clusters - endpoint_t *endpoint = temperature_level_controlled_cabinet::create(node::get(), &cabinet_config, ENDPOINT_FLAG_NONE, (void *)this); - if (endpoint == nullptr) { - log_e("Failed to create Temperature Level Controlled Cabinet endpoint"); - return false; - } - - // Copy supported levels array into internal buffer - memcpy(supportedLevelsArray, supportedLevels, levelCount * sizeof(uint8_t)); - supportedLevelsCount = levelCount; - selectedTempLevel = selectedLevel; - useTemperatureNumber = false; // Set feature mode to temperature_level - - // Initialize temperature_number values to 0 (not used in this mode) - rawTempSetpoint = 0; - rawMinTemperature = 0; - rawMaxTemperature = 0; - rawStep = 0; - - setEndPointId(endpoint::get_id(endpoint)); - log_i("Temperature Level Controlled Cabinet created with temperature_level feature, endpoint_id %d", getEndPointId()); - - // Set started flag before calling setter methods (they check for started) - started = true; - - // Set supported temperature levels using internal copy - if (!setSupportedTemperatureLevels(supportedLevelsArray, levelCount)) { - log_e("Failed to set supported temperature levels"); - started = false; // Reset on failure - return false; - } - - // Set selected temperature level - if (!setSelectedTemperatureLevel(selectedLevel)) { - log_e("Failed to set selected temperature level"); - started = false; // Reset on failure - return false; - } - - return true; -} - -void MatterTemperatureControlledCabinet::end() { - started = false; - useTemperatureNumber = true; // Reset to default - supportedLevelsCount = 0; - // No need to clear array - it's a fixed-size buffer -} - -bool MatterTemperatureControlledCabinet::setRawTemperatureSetpoint(int16_t _rawTemperature) { - if (!started) { - log_e("Matter Temperature Controlled Cabinet device has not begun."); - return false; - } - - if (!useTemperatureNumber) { - log_e("Temperature setpoint methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); - return false; - } - - // Validate against min/max - if (_rawTemperature < rawMinTemperature || _rawTemperature > rawMaxTemperature) { - log_e("Temperature setpoint %.02fC is out of range [%.02fC, %.02fC]", - (float)_rawTemperature / 100.0, (float)rawMinTemperature / 100.0, (float)rawMaxTemperature / 100.0); - return false; - } - - // avoid processing if there was no change - if (rawTempSetpoint == _rawTemperature) { - return true; - } - - esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); - if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::TemperatureSetpoint::Id, &tempVal)) { - log_e("Failed to get Temperature Controlled Cabinet Temperature Setpoint Attribute."); - return false; - } - if (tempVal.val.i16 != _rawTemperature) { - tempVal.val.i16 = _rawTemperature; - bool ret; - ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::TemperatureSetpoint::Id, &tempVal); - if (!ret) { - log_e("Failed to update Temperature Controlled Cabinet Temperature Setpoint Attribute."); - return false; - } - rawTempSetpoint = _rawTemperature; - } - log_v("Temperature Controlled Cabinet setpoint set to %.02fC", (float)_rawTemperature / 100.00); - - return true; -} - -bool MatterTemperatureControlledCabinet::setTemperatureSetpoint(double temperature) { - int16_t rawValue = static_cast(temperature * 100.0); - return setRawTemperatureSetpoint(rawValue); -} - -double MatterTemperatureControlledCabinet::getTemperatureSetpoint() { - if (!useTemperatureNumber) { - log_e("Temperature setpoint methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); - return 0.0; - } - - esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); - if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::TemperatureSetpoint::Id, &tempVal)) { - rawTempSetpoint = tempVal.val.i16; - } - return (double)rawTempSetpoint / 100.0; -} - -bool MatterTemperatureControlledCabinet::setRawMinTemperature(int16_t _rawTemperature) { - if (!started) { - log_e("Matter Temperature Controlled Cabinet device has not begun."); - return false; - } - - if (!useTemperatureNumber) { - log_e("Min temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); - return false; - } - - if (rawMinTemperature == _rawTemperature) { - return true; - } - - esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); - if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MinTemperature::Id, &tempVal)) { - log_e("Failed to get Temperature Controlled Cabinet Min Temperature Attribute."); - return false; - } - if (tempVal.val.i16 != _rawTemperature) { - tempVal.val.i16 = _rawTemperature; - bool ret; - ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MinTemperature::Id, &tempVal); - if (!ret) { - log_e("Failed to update Temperature Controlled Cabinet Min Temperature Attribute."); - return false; - } - rawMinTemperature = _rawTemperature; - } - log_v("Temperature Controlled Cabinet min temperature set to %.02fC", (float)_rawTemperature / 100.00); - - return true; -} - -bool MatterTemperatureControlledCabinet::setMinTemperature(double temperature) { - int16_t rawValue = static_cast(temperature * 100.0); - return setRawMinTemperature(rawValue); -} - -double MatterTemperatureControlledCabinet::getMinTemperature() { - if (!useTemperatureNumber) { - log_e("Min temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); - return 0.0; - } - - esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); - if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MinTemperature::Id, &tempVal)) { - rawMinTemperature = tempVal.val.i16; - } - return (double)rawMinTemperature / 100.0; -} - -bool MatterTemperatureControlledCabinet::setRawMaxTemperature(int16_t _rawTemperature) { - if (!started) { - log_e("Matter Temperature Controlled Cabinet device has not begun."); - return false; - } - - if (!useTemperatureNumber) { - log_e("Max temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); - return false; - } - - if (rawMaxTemperature == _rawTemperature) { - return true; - } - - esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); - if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MaxTemperature::Id, &tempVal)) { - log_e("Failed to get Temperature Controlled Cabinet Max Temperature Attribute."); - return false; - } - if (tempVal.val.i16 != _rawTemperature) { - tempVal.val.i16 = _rawTemperature; - bool ret; - ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MaxTemperature::Id, &tempVal); - if (!ret) { - log_e("Failed to update Temperature Controlled Cabinet Max Temperature Attribute."); - return false; - } - rawMaxTemperature = _rawTemperature; - } - log_v("Temperature Controlled Cabinet max temperature set to %.02fC", (float)_rawTemperature / 100.00); - - return true; -} - -bool MatterTemperatureControlledCabinet::setMaxTemperature(double temperature) { - int16_t rawValue = static_cast(temperature * 100.0); - return setRawMaxTemperature(rawValue); -} - -double MatterTemperatureControlledCabinet::getMaxTemperature() { - if (!useTemperatureNumber) { - log_e("Max temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); - return 0.0; - } - - esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); - if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MaxTemperature::Id, &tempVal)) { - rawMaxTemperature = tempVal.val.i16; - } - return (double)rawMaxTemperature / 100.0; -} - -bool MatterTemperatureControlledCabinet::setRawStep(int16_t _rawStep) { - if (!started) { - log_e("Matter Temperature Controlled Cabinet device has not begun."); - return false; - } - - if (!useTemperatureNumber) { - log_e("Temperature step methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); - return false; - } - - if (rawStep == _rawStep) { - return true; - } - - esp_matter_attr_val_t stepVal = esp_matter_invalid(NULL); - if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::Step::Id, &stepVal)) { - log_e("Failed to get Temperature Controlled Cabinet Step Attribute. Temperature_step feature may not be enabled."); - return false; - } - if (stepVal.val.i16 != _rawStep) { - stepVal.val.i16 = _rawStep; - bool ret; - ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::Step::Id, &stepVal); - if (!ret) { - log_e("Failed to update Temperature Controlled Cabinet Step Attribute."); - return false; - } - rawStep = _rawStep; - } - log_v("Temperature Controlled Cabinet step set to %.02fC", (float)_rawStep / 100.00); - - return true; -} - -bool MatterTemperatureControlledCabinet::setStep(double step) { - int16_t rawValue = static_cast(step * 100.0); - return setRawStep(rawValue); -} - -double MatterTemperatureControlledCabinet::getStep() { - if (!useTemperatureNumber) { - log_e("Temperature step methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); - return 0.0; - } - - // Read from attribute (should always exist after begin() due to workaround) - // If read fails, use stored rawStep value from begin() - esp_matter_attr_val_t stepVal = esp_matter_invalid(NULL); - if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::Step::Id, &stepVal)) { - rawStep = stepVal.val.i16; - } - return (double)rawStep / 100.0; -} - -bool MatterTemperatureControlledCabinet::setSelectedTemperatureLevel(uint8_t level) { - if (!started) { - log_e("Matter Temperature Controlled Cabinet device has not begun."); - return false; - } - - if (useTemperatureNumber) { - log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); - return false; - } - - // Validate that level is in supported levels array - bool levelFound = false; - for (uint16_t i = 0; i < supportedLevelsCount; i++) { - if (supportedLevelsArray[i] == level) { - levelFound = true; - break; - } - } - if (!levelFound) { - log_e("Temperature level %u is not in the supported levels array", level); - return false; - } - if (selectedTempLevel == level) { - return true; - } - - esp_matter_attr_val_t levelVal = esp_matter_invalid(NULL); - if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SelectedTemperatureLevel::Id, &levelVal)) { - log_e("Failed to get Temperature Controlled Cabinet Selected Temperature Level Attribute."); - return false; - } - if (levelVal.val.u8 != level) { - levelVal.val.u8 = level; - bool ret; - ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SelectedTemperatureLevel::Id, &levelVal); - if (!ret) { - log_e("Failed to update Temperature Controlled Cabinet Selected Temperature Level Attribute."); - return false; - } - selectedTempLevel = level; - } - log_v("Temperature Controlled Cabinet selected temperature level set to %u", level); - - return true; -} - -uint8_t MatterTemperatureControlledCabinet::getSelectedTemperatureLevel() { - if (useTemperatureNumber) { - log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); - return 0; - } - - esp_matter_attr_val_t levelVal = esp_matter_invalid(NULL); - if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SelectedTemperatureLevel::Id, &levelVal)) { - selectedTempLevel = levelVal.val.u8; - } - return selectedTempLevel; -} - -bool MatterTemperatureControlledCabinet::setSupportedTemperatureLevels(uint8_t *levels, uint16_t count) { - if (!started) { - log_e("Matter Temperature Controlled Cabinet device has not begun."); - return false; - } - - if (useTemperatureNumber) { - log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); - return false; - } - - if (levels == nullptr || count == 0) { - log_e("Invalid levels array or count."); - return false; - } - - // Validate against maximum from ESP-Matter - if (count > temperature_control::k_max_temp_level_count) { - log_e("Level count %u exceeds maximum %u", count, temperature_control::k_max_temp_level_count); - return false; - } - - // Copy the array into internal buffer - memcpy(supportedLevelsArray, levels, count * sizeof(uint8_t)); - supportedLevelsCount = count; - - // Use internal copy for Matter attribute update - // Use esp_matter_array helper function which properly initializes the structure - esp_matter_attr_val_t levelsVal = esp_matter_array(supportedLevelsArray, sizeof(uint8_t), count); - - bool ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SupportedTemperatureLevels::Id, &levelsVal); - if (!ret) { - log_e("Failed to update Temperature Controlled Cabinet Supported Temperature Levels Attribute."); - return false; - } - log_v("Temperature Controlled Cabinet supported temperature levels updated, count: %u", count); - - return true; -} - -uint16_t MatterTemperatureControlledCabinet::getSupportedTemperatureLevelsCount() { - if (useTemperatureNumber) { - log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); - return 0; - } - - esp_matter_attr_val_t levelsVal = esp_matter_invalid(NULL); - if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SupportedTemperatureLevels::Id, &levelsVal)) { - return levelsVal.val.a.n; // a.n is the count (number of elements) - } - return 0; -} - + bool useTemperatureNumber = true; + + // temperature in 1/100th Celsius (stored as int16_t by multiplying by 100) + int16_t rawTempSetpoint = 0; + int16_t rawMinTemperature = 0; + int16_t rawMaxTemperature = 0; + int16_t rawStep = 0; + uint8_t selectedTempLevel = 0; + // Fixed-size buffer for supported temperature levels (max 16 as per Matter spec: temperature_control::k_max_temp_level_count) + uint8_t supportedLevelsArray[16]; // Size matches esp_matter::cluster::temperature_control::k_max_temp_level_count + uint16_t supportedLevelsCount = 0; + + // internal functions to set the raw temperature values (Matter Cluster) + bool setRawTemperatureSetpoint(int16_t _rawTemperature); + bool setRawMinTemperature(int16_t _rawTemperature); + bool setRawMaxTemperature(int16_t _rawTemperature); + bool setRawStep(int16_t _rawStep); + bool begin(int16_t _rawTempSetpoint, int16_t _rawMinTemperature, int16_t _rawMaxTemperature, int16_t _rawStep); + bool beginInternal(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel); +}; #endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ - From b8d3fb28b54bb5d3f990d938be1e7282c248a26c Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Sat, 6 Dec 2025 15:37:24 -0300 Subject: [PATCH 20/22] fix(matter_example): bad ci.yml content --- .../ci.yml | 674 +----------------- 1 file changed, 3 insertions(+), 671 deletions(-) diff --git a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/ci.yml b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/ci.yml index 3e0545af141..050a80ff543 100644 --- a/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/ci.yml +++ b/libraries/Matter/examples/MatterTemperatureControlledCabinetLevels/ci.yml @@ -1,672 +1,4 @@ -// 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 +fqbn_append: PartitionScheme=huge_app -// 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. - -#include -#ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL - -#include -#include -#include -#include -#include - -using namespace esp_matter; -using namespace esp_matter::endpoint; -using namespace esp_matter::cluster; -using namespace chip::app::Clusters; - -// Custom endpoint for temperature_level_controlled_cabinet device -// This endpoint uses temperature_level feature instead of temperature_number -namespace esp_matter { -using namespace cluster; -namespace endpoint { -namespace temperature_level_controlled_cabinet { -typedef struct config { - cluster::descriptor::config_t descriptor; - cluster::temperature_control::config_t temperature_control; -} config_t; - -uint32_t get_device_type_id() { - return ESP_MATTER_TEMPERATURE_CONTROLLED_CABINET_DEVICE_TYPE_ID; -} - -uint8_t get_device_type_version() { - return ESP_MATTER_TEMPERATURE_CONTROLLED_CABINET_DEVICE_TYPE_VERSION; -} - -esp_err_t add(endpoint_t *endpoint, config_t *config) { - if (!endpoint) { - log_e("Endpoint cannot be NULL"); - return ESP_ERR_INVALID_ARG; - } - esp_err_t err = add_device_type(endpoint, get_device_type_id(), get_device_type_version()); - if (err != ESP_OK) { - log_e("Failed to add device type id:%" PRIu32 ",err: %d", get_device_type_id(), err); - return err; - } - - // Create temperature_control cluster with temperature_level feature - // Note: temperature_number and temperature_level are mutually exclusive - temperature_control::create(endpoint, &(config->temperature_control), CLUSTER_FLAG_SERVER, temperature_control::feature::temperature_level::get_id()); - - return ESP_OK; -} - -endpoint_t *create(node_t *node, config_t *config, uint8_t flags, void *priv_data) { - endpoint_t *endpoint = endpoint::create(node, flags, priv_data); - add(endpoint, config); - return endpoint; -} -} // namespace temperature_level_controlled_cabinet -} // namespace endpoint -} // namespace esp_matter - -bool MatterTemperatureControlledCabinet::attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val) { - bool ret = true; - if (!started) { - log_e("Matter Temperature Controlled Cabinet device has not begun."); - return false; - } - - log_d("Temperature Controlled Cabinet Attr update callback: endpoint: %u, cluster: %u, attribute: %u", endpoint_id, cluster_id, attribute_id); - - // Handle TemperatureControl cluster attribute changes from Matter controller - if (cluster_id == TemperatureControl::Id) { - switch (attribute_id) { - case TemperatureControl::Attributes::TemperatureSetpoint::Id: - if (useTemperatureNumber) { - rawTempSetpoint = val->val.i16; - log_i("Temperature setpoint changed to %.02fC", (float)rawTempSetpoint / 100.0); - } else { - log_w("Temperature setpoint change ignored - temperature_level feature is active"); - } - break; - - case TemperatureControl::Attributes::MinTemperature::Id: - if (useTemperatureNumber) { - rawMinTemperature = val->val.i16; - log_i("Min temperature changed to %.02fC", (float)rawMinTemperature / 100.0); - } else { - log_w("Min temperature change ignored - temperature_level feature is active"); - } - break; - - case TemperatureControl::Attributes::MaxTemperature::Id: - if (useTemperatureNumber) { - rawMaxTemperature = val->val.i16; - log_i("Max temperature changed to %.02fC", (float)rawMaxTemperature / 100.0); - } else { - log_w("Max temperature change ignored - temperature_level feature is active"); - } - break; - - case TemperatureControl::Attributes::Step::Id: - if (useTemperatureNumber) { - rawStep = val->val.i16; - log_i("Temperature step changed to %.02fC", (float)rawStep / 100.0); - } else { - log_w("Temperature step change ignored - temperature_level feature is active"); - } - break; - - case TemperatureControl::Attributes::SelectedTemperatureLevel::Id: - if (!useTemperatureNumber) { - selectedTempLevel = val->val.u8; - log_i("Selected temperature level changed to %u", selectedTempLevel); - } else { - log_w("Selected temperature level change ignored - temperature_number feature is active"); - } - break; - - case TemperatureControl::Attributes::SupportedTemperatureLevels::Id: - // SupportedTemperatureLevels is read-only (managed via iterator delegate) - // But if a controller tries to write it, we should log it - log_w("SupportedTemperatureLevels change attempted - this attribute is read-only"); - break; - - default: log_d("Unhandled TemperatureControl Attribute ID: %u", attribute_id); break; - } - } - - return ret; -} - -MatterTemperatureControlledCabinet::MatterTemperatureControlledCabinet() {} - -MatterTemperatureControlledCabinet::~MatterTemperatureControlledCabinet() { - end(); -} - -bool MatterTemperatureControlledCabinet::begin(double tempSetpoint, double minTemperature, double maxTemperature, double step) { - int16_t rawSetpoint = static_cast(tempSetpoint * 100.0); - int16_t rawMin = static_cast(minTemperature * 100.0); - int16_t rawMax = static_cast(maxTemperature * 100.0); - int16_t rawStepValue = static_cast(step * 100.0); - return begin(rawSetpoint, rawMin, rawMax, rawStepValue); -} - -bool MatterTemperatureControlledCabinet::begin(int16_t _rawTempSetpoint, int16_t _rawMinTemperature, int16_t _rawMaxTemperature, int16_t _rawStep) { - ArduinoMatter::_init(); - - if (getEndPointId() != 0) { - log_e("Temperature Controlled Cabinet with Endpoint Id %d device has already been created.", getEndPointId()); - return false; - } - - // Note: esp-matter automatically creates all attributes from the config struct when features are enabled - // - temperature_number feature creates: TemperatureSetpoint, MinTemperature, MaxTemperature - // - temperature_step feature creates: Step (always enabled for temperature_number mode to allow setStep() later) - // No need to manually set attributes here as they are already created with the config values - - temperature_controlled_cabinet::config_t cabinet_config; - cabinet_config.temperature_control.temperature_number.temp_setpoint = _rawTempSetpoint; - cabinet_config.temperature_control.temperature_number.min_temperature = _rawMinTemperature; - cabinet_config.temperature_control.temperature_number.max_temperature = _rawMaxTemperature; - cabinet_config.temperature_control.temperature_step.step = _rawStep; - cabinet_config.temperature_control.temperature_level.selected_temp_level = 0; - - // Enable temperature_number feature (required) - // Note: temperature_number and temperature_level are mutually exclusive. - // Only one of them can be enabled at a time. - cabinet_config.temperature_control.features = temperature_control::feature::temperature_number::get_id(); - - // Always enable temperature_step feature to allow setStep() to be called later - // Note: temperature_step requires temperature_number feature (which is always enabled for this mode) - // The step value can be set initially via begin() or later via setStep() - cabinet_config.temperature_control.features |= temperature_control::feature::temperature_step::get_id(); - - // endpoint handles can be used to add/modify clusters - endpoint_t *endpoint = temperature_controlled_cabinet::create(node::get(), &cabinet_config, ENDPOINT_FLAG_NONE, (void *)this); - if (endpoint == nullptr) { - log_e("Failed to create Temperature Controlled Cabinet endpoint"); - return false; - } - - rawTempSetpoint = _rawTempSetpoint; - rawMinTemperature = _rawMinTemperature; - rawMaxTemperature = _rawMaxTemperature; - rawStep = _rawStep; - selectedTempLevel = 0; - useTemperatureNumber = true; // Set feature mode to temperature_number - - setEndPointId(endpoint::get_id(endpoint)); - log_i("Temperature Controlled Cabinet created with temperature_number feature, endpoint_id %d", getEndPointId()); - - // Workaround: Manually create Step attribute if it wasn't created automatically - // This handles the case where temperature_step::add() fails due to feature map timing issue - // The feature map check in temperature_step::add() may not see the temperature_number feature - // immediately after it's added, causing the Step attribute to not be created - cluster_t *cluster = cluster::get(endpoint, TemperatureControl::Id); - if (cluster != nullptr) { - attribute_t *step_attr = attribute::get(cluster, TemperatureControl::Attributes::Step::Id); - if (step_attr == nullptr) { - // Step attribute wasn't created, manually create it - log_w("Step attribute not found after endpoint creation, manually creating it"); - step_attr = temperature_control::attribute::create_step(cluster, _rawStep); - if (step_attr != nullptr) { - // Update the feature map to include temperature_step feature - // This ensures the feature is properly registered even though the attribute was created manually - esp_matter_attr_val_t feature_map_val = esp_matter_invalid(NULL); - attribute_t *feature_map_attr = attribute::get(cluster, Globals::Attributes::FeatureMap::Id); - if (feature_map_attr != nullptr && attribute::get_val(feature_map_attr, &feature_map_val) == ESP_OK) { - feature_map_val.val.u32 |= temperature_control::feature::temperature_step::get_id(); - attribute::set_val(feature_map_attr, &feature_map_val); - } - log_i("Step attribute manually created with value %.02fC", (float)_rawStep / 100.0); - } else { - log_e("Failed to manually create Step attribute"); - } - } - } - - started = true; - return true; -} - -bool MatterTemperatureControlledCabinet::begin(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel) { - if (supportedLevels == nullptr || levelCount == 0) { - log_e("Invalid supportedLevels array or levelCount. Must provide at least one level."); - return false; - } - - // Validate against maximum from ESP-Matter - if (levelCount > temperature_control::k_max_temp_level_count) { - log_e("Level count %u exceeds maximum %u", levelCount, temperature_control::k_max_temp_level_count); - return false; - } - - // Validate that selectedLevel exists in supportedLevels array - bool levelFound = false; - for (uint16_t i = 0; i < levelCount; i++) { - if (supportedLevels[i] == selectedLevel) { - levelFound = true; - break; - } - } - if (!levelFound) { - log_e("Selected level %u is not in the supported levels array", selectedLevel); - return false; - } - return beginInternal(supportedLevels, levelCount, selectedLevel); -} - -bool MatterTemperatureControlledCabinet::beginInternal(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel) { - ArduinoMatter::_init(); - - if (getEndPointId() != 0) { - log_e("Temperature Controlled Cabinet with Endpoint Id %d device has already been created.", getEndPointId()); - return false; - } - - // Use custom temperature_level_controlled_cabinet endpoint that supports temperature_level feature - temperature_level_controlled_cabinet::config_t cabinet_config; - // Initialize temperature_number config (not used but required for struct) - cabinet_config.temperature_control.temperature_number.temp_setpoint = 0; - cabinet_config.temperature_control.temperature_number.min_temperature = 0; - cabinet_config.temperature_control.temperature_number.max_temperature = 0; - cabinet_config.temperature_control.temperature_step.step = 0; - cabinet_config.temperature_control.temperature_level.selected_temp_level = selectedLevel; - - // Enable temperature_level feature - // Note: temperature_number and temperature_level are mutually exclusive. - // Only one of them can be enabled at a time. - cabinet_config.temperature_control.features = temperature_control::feature::temperature_level::get_id(); - - // endpoint handles can be used to add/modify clusters - endpoint_t *endpoint = temperature_level_controlled_cabinet::create(node::get(), &cabinet_config, ENDPOINT_FLAG_NONE, (void *)this); - if (endpoint == nullptr) { - log_e("Failed to create Temperature Level Controlled Cabinet endpoint"); - return false; - } - - // Copy supported levels array into internal buffer - memcpy(supportedLevelsArray, supportedLevels, levelCount * sizeof(uint8_t)); - supportedLevelsCount = levelCount; - selectedTempLevel = selectedLevel; - useTemperatureNumber = false; // Set feature mode to temperature_level - - // Initialize temperature_number values to 0 (not used in this mode) - rawTempSetpoint = 0; - rawMinTemperature = 0; - rawMaxTemperature = 0; - rawStep = 0; - - setEndPointId(endpoint::get_id(endpoint)); - log_i("Temperature Level Controlled Cabinet created with temperature_level feature, endpoint_id %d", getEndPointId()); - - // Set started flag before calling setter methods (they check for started) - started = true; - - // Set supported temperature levels using internal copy - if (!setSupportedTemperatureLevels(supportedLevelsArray, levelCount)) { - log_e("Failed to set supported temperature levels"); - started = false; // Reset on failure - return false; - } - - // Set selected temperature level - if (!setSelectedTemperatureLevel(selectedLevel)) { - log_e("Failed to set selected temperature level"); - started = false; // Reset on failure - return false; - } - - return true; -} - -void MatterTemperatureControlledCabinet::end() { - started = false; - useTemperatureNumber = true; // Reset to default - supportedLevelsCount = 0; - // No need to clear array - it's a fixed-size buffer -} - -bool MatterTemperatureControlledCabinet::setRawTemperatureSetpoint(int16_t _rawTemperature) { - if (!started) { - log_e("Matter Temperature Controlled Cabinet device has not begun."); - return false; - } - - if (!useTemperatureNumber) { - log_e("Temperature setpoint methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); - return false; - } - - // Validate against min/max - if (_rawTemperature < rawMinTemperature || _rawTemperature > rawMaxTemperature) { - log_e( - "Temperature setpoint %.02fC is out of range [%.02fC, %.02fC]", (float)_rawTemperature / 100.0, (float)rawMinTemperature / 100.0, - (float)rawMaxTemperature / 100.0 - ); - return false; - } - - // avoid processing if there was no change - if (rawTempSetpoint == _rawTemperature) { - return true; - } - - esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); - if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::TemperatureSetpoint::Id, &tempVal)) { - log_e("Failed to get Temperature Controlled Cabinet Temperature Setpoint Attribute."); - return false; - } - if (tempVal.val.i16 != _rawTemperature) { - tempVal.val.i16 = _rawTemperature; - bool ret; - ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::TemperatureSetpoint::Id, &tempVal); - if (!ret) { - log_e("Failed to update Temperature Controlled Cabinet Temperature Setpoint Attribute."); - return false; - } - rawTempSetpoint = _rawTemperature; - } - log_v("Temperature Controlled Cabinet setpoint set to %.02fC", (float)_rawTemperature / 100.00); - - return true; -} - -bool MatterTemperatureControlledCabinet::setTemperatureSetpoint(double temperature) { - int16_t rawValue = static_cast(temperature * 100.0); - return setRawTemperatureSetpoint(rawValue); -} - -double MatterTemperatureControlledCabinet::getTemperatureSetpoint() { - if (!useTemperatureNumber) { - log_e("Temperature setpoint methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); - return 0.0; - } - - esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); - if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::TemperatureSetpoint::Id, &tempVal)) { - rawTempSetpoint = tempVal.val.i16; - } - return (double)rawTempSetpoint / 100.0; -} - -bool MatterTemperatureControlledCabinet::setRawMinTemperature(int16_t _rawTemperature) { - if (!started) { - log_e("Matter Temperature Controlled Cabinet device has not begun."); - return false; - } - - if (!useTemperatureNumber) { - log_e("Min temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); - return false; - } - - if (rawMinTemperature == _rawTemperature) { - return true; - } - - esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); - if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MinTemperature::Id, &tempVal)) { - log_e("Failed to get Temperature Controlled Cabinet Min Temperature Attribute."); - return false; - } - if (tempVal.val.i16 != _rawTemperature) { - tempVal.val.i16 = _rawTemperature; - bool ret; - ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MinTemperature::Id, &tempVal); - if (!ret) { - log_e("Failed to update Temperature Controlled Cabinet Min Temperature Attribute."); - return false; - } - rawMinTemperature = _rawTemperature; - } - log_v("Temperature Controlled Cabinet min temperature set to %.02fC", (float)_rawTemperature / 100.00); - - return true; -} - -bool MatterTemperatureControlledCabinet::setMinTemperature(double temperature) { - int16_t rawValue = static_cast(temperature * 100.0); - return setRawMinTemperature(rawValue); -} - -double MatterTemperatureControlledCabinet::getMinTemperature() { - if (!useTemperatureNumber) { - log_e("Min temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); - return 0.0; - } - - esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); - if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MinTemperature::Id, &tempVal)) { - rawMinTemperature = tempVal.val.i16; - } - return (double)rawMinTemperature / 100.0; -} - -bool MatterTemperatureControlledCabinet::setRawMaxTemperature(int16_t _rawTemperature) { - if (!started) { - log_e("Matter Temperature Controlled Cabinet device has not begun."); - return false; - } - - if (!useTemperatureNumber) { - log_e("Max temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); - return false; - } - - if (rawMaxTemperature == _rawTemperature) { - return true; - } - - esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); - if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MaxTemperature::Id, &tempVal)) { - log_e("Failed to get Temperature Controlled Cabinet Max Temperature Attribute."); - return false; - } - if (tempVal.val.i16 != _rawTemperature) { - tempVal.val.i16 = _rawTemperature; - bool ret; - ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MaxTemperature::Id, &tempVal); - if (!ret) { - log_e("Failed to update Temperature Controlled Cabinet Max Temperature Attribute."); - return false; - } - rawMaxTemperature = _rawTemperature; - } - log_v("Temperature Controlled Cabinet max temperature set to %.02fC", (float)_rawTemperature / 100.00); - - return true; -} - -bool MatterTemperatureControlledCabinet::setMaxTemperature(double temperature) { - int16_t rawValue = static_cast(temperature * 100.0); - return setRawMaxTemperature(rawValue); -} - -double MatterTemperatureControlledCabinet::getMaxTemperature() { - if (!useTemperatureNumber) { - log_e("Max temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); - return 0.0; - } - - esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); - if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MaxTemperature::Id, &tempVal)) { - rawMaxTemperature = tempVal.val.i16; - } - return (double)rawMaxTemperature / 100.0; -} - -bool MatterTemperatureControlledCabinet::setRawStep(int16_t _rawStep) { - if (!started) { - log_e("Matter Temperature Controlled Cabinet device has not begun."); - return false; - } - - if (!useTemperatureNumber) { - log_e("Temperature step methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); - return false; - } - - if (rawStep == _rawStep) { - return true; - } - - esp_matter_attr_val_t stepVal = esp_matter_invalid(NULL); - if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::Step::Id, &stepVal)) { - log_e("Failed to get Temperature Controlled Cabinet Step Attribute. Temperature_step feature may not be enabled."); - return false; - } - if (stepVal.val.i16 != _rawStep) { - stepVal.val.i16 = _rawStep; - bool ret; - ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::Step::Id, &stepVal); - if (!ret) { - log_e("Failed to update Temperature Controlled Cabinet Step Attribute."); - return false; - } - rawStep = _rawStep; - } - log_v("Temperature Controlled Cabinet step set to %.02fC", (float)_rawStep / 100.00); - - return true; -} - -bool MatterTemperatureControlledCabinet::setStep(double step) { - int16_t rawValue = static_cast(step * 100.0); - return setRawStep(rawValue); -} - -double MatterTemperatureControlledCabinet::getStep() { - if (!useTemperatureNumber) { - log_e("Temperature step methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); - return 0.0; - } - - // Read from attribute (should always exist after begin() due to workaround) - // If read fails, use stored rawStep value from begin() - esp_matter_attr_val_t stepVal = esp_matter_invalid(NULL); - if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::Step::Id, &stepVal)) { - rawStep = stepVal.val.i16; - } - return (double)rawStep / 100.0; -} - -bool MatterTemperatureControlledCabinet::setSelectedTemperatureLevel(uint8_t level) { - if (!started) { - log_e("Matter Temperature Controlled Cabinet device has not begun."); - return false; - } - - if (useTemperatureNumber) { - log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); - return false; - } - - // Validate that level is in supported levels array - bool levelFound = false; - for (uint16_t i = 0; i < supportedLevelsCount; i++) { - if (supportedLevelsArray[i] == level) { - levelFound = true; - break; - } - } - if (!levelFound) { - log_e("Temperature level %u is not in the supported levels array", level); - return false; - } - if (selectedTempLevel == level) { - return true; - } - - esp_matter_attr_val_t levelVal = esp_matter_invalid(NULL); - if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SelectedTemperatureLevel::Id, &levelVal)) { - log_e("Failed to get Temperature Controlled Cabinet Selected Temperature Level Attribute."); - return false; - } - if (levelVal.val.u8 != level) { - levelVal.val.u8 = level; - bool ret; - ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SelectedTemperatureLevel::Id, &levelVal); - if (!ret) { - log_e("Failed to update Temperature Controlled Cabinet Selected Temperature Level Attribute."); - return false; - } - selectedTempLevel = level; - } - log_v("Temperature Controlled Cabinet selected temperature level set to %u", level); - - return true; -} - -uint8_t MatterTemperatureControlledCabinet::getSelectedTemperatureLevel() { - if (useTemperatureNumber) { - log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); - return 0; - } - - esp_matter_attr_val_t levelVal = esp_matter_invalid(NULL); - if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SelectedTemperatureLevel::Id, &levelVal)) { - selectedTempLevel = levelVal.val.u8; - } - return selectedTempLevel; -} - -bool MatterTemperatureControlledCabinet::setSupportedTemperatureLevels(uint8_t *levels, uint16_t count) { - if (!started) { - log_e("Matter Temperature Controlled Cabinet device has not begun."); - return false; - } - - if (useTemperatureNumber) { - log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); - return false; - } - - if (levels == nullptr || count == 0) { - log_e("Invalid levels array or count."); - return false; - } - - // Validate against maximum from ESP-Matter - if (count > temperature_control::k_max_temp_level_count) { - log_e("Level count %u exceeds maximum %u", count, temperature_control::k_max_temp_level_count); - return false; - } - - // Copy the array into internal buffer - memcpy(supportedLevelsArray, levels, count * sizeof(uint8_t)); - supportedLevelsCount = count; - - // Use internal copy for Matter attribute update - // Use esp_matter_array helper function which properly initializes the structure - esp_matter_attr_val_t levelsVal = esp_matter_array(supportedLevelsArray, sizeof(uint8_t), count); - - bool ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SupportedTemperatureLevels::Id, &levelsVal); - if (!ret) { - log_e("Failed to update Temperature Controlled Cabinet Supported Temperature Levels Attribute."); - return false; - } - log_v("Temperature Controlled Cabinet supported temperature levels updated, count: %u", count); - - return true; -} - -uint16_t MatterTemperatureControlledCabinet::getSupportedTemperatureLevelsCount() { - if (useTemperatureNumber) { - log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); - return 0; - } - - esp_matter_attr_val_t levelsVal = esp_matter_invalid(NULL); - if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SupportedTemperatureLevels::Id, &levelsVal)) { - return levelsVal.val.a.n; // a.n is the count (number of elements) - } - return 0; -} - -#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ +requires: + - CONFIG_ESP_MATTER_ENABLE_DATA_MODEL=y From e72dd510abef414737c72d73b937461d63209f2d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 6 Dec 2025 18:38:00 +0000 Subject: [PATCH 21/22] ci(pre-commit): Apply automatic fixes --- .../MatterTemperatureControlledCabinet.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.h b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.h index e56dd4da96a..09377cf85bb 100644 --- a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.h +++ b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.h @@ -23,15 +23,15 @@ class MatterTemperatureControlledCabinet : public MatterEndPoint { public: MatterTemperatureControlledCabinet(); ~MatterTemperatureControlledCabinet(); - + // begin with temperature_number feature (mutually exclusive with temperature_level) // This enables temperature setpoint control with min/max limits and optional step bool begin(double tempSetpoint = 0.00, double minTemperature = -10.0, double maxTemperature = 32.0, double step = 0.50); - + // begin with temperature_level feature (mutually exclusive with temperature_number) // This enables temperature level control with an array of supported levels bool begin(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel = 0); - + // this will stop processing Temperature Controlled Cabinet Matter events void end(); @@ -73,7 +73,7 @@ class MatterTemperatureControlledCabinet : public MatterEndPoint { // Feature mode: true = temperature_number, false = temperature_level // Note: temperature_number and temperature_level are mutually exclusive bool useTemperatureNumber = true; - + // temperature in 1/100th Celsius (stored as int16_t by multiplying by 100) int16_t rawTempSetpoint = 0; int16_t rawMinTemperature = 0; @@ -83,7 +83,7 @@ class MatterTemperatureControlledCabinet : public MatterEndPoint { // Fixed-size buffer for supported temperature levels (max 16 as per Matter spec: temperature_control::k_max_temp_level_count) uint8_t supportedLevelsArray[16]; // Size matches esp_matter::cluster::temperature_control::k_max_temp_level_count uint16_t supportedLevelsCount = 0; - + // internal functions to set the raw temperature values (Matter Cluster) bool setRawTemperatureSetpoint(int16_t _rawTemperature); bool setRawMinTemperature(int16_t _rawTemperature); @@ -93,4 +93,3 @@ class MatterTemperatureControlledCabinet : public MatterEndPoint { bool beginInternal(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel); }; #endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ - From 9fae46d6a9f7351df32d1b35a3f8edb8c72a0c29 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Sat, 6 Dec 2025 15:40:33 -0300 Subject: [PATCH 22/22] fix(matter): bad content in source file Refactor MatterTemperatureControlledCabinet to support both temperature_number and temperature_level features. Introduce new methods for handling temperature levels and update existing methods to accommodate the new structure. --- .../MatterTemperatureControlledCabinet.cpp | 727 ++++++++++++++++-- 1 file changed, 652 insertions(+), 75 deletions(-) diff --git a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp index 09377cf85bb..3e0545af141 100644 --- a/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp +++ b/libraries/Matter/src/MatterEndpoints/MatterTemperatureControlledCabinet.cpp @@ -12,84 +12,661 @@ // See the License for the specific language governing permissions and // limitations under the License. -#pragma once #include #ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL +#include #include -#include - -class MatterTemperatureControlledCabinet : public MatterEndPoint { -public: - MatterTemperatureControlledCabinet(); - ~MatterTemperatureControlledCabinet(); - - // begin with temperature_number feature (mutually exclusive with temperature_level) - // This enables temperature setpoint control with min/max limits and optional step - bool begin(double tempSetpoint = 0.00, double minTemperature = -10.0, double maxTemperature = 32.0, double step = 0.50); - - // begin with temperature_level feature (mutually exclusive with temperature_number) - // This enables temperature level control with an array of supported levels - bool begin(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel = 0); - - // this will stop processing Temperature Controlled Cabinet Matter events - void end(); - - // set the temperature setpoint - bool setTemperatureSetpoint(double temperature); - // returns the temperature setpoint in Celsius - double getTemperatureSetpoint(); - - // set the minimum temperature - bool setMinTemperature(double temperature); - // returns the minimum temperature in Celsius - double getMinTemperature(); - - // set the maximum temperature - bool setMaxTemperature(double temperature); - // returns the maximum temperature in Celsius - double getMaxTemperature(); - - // set the temperature step (optional, requires temperature_step feature) - bool setStep(double step); - // returns the temperature step in Celsius - double getStep(); - - // set the selected temperature level (optional, requires temperature_level feature) - bool setSelectedTemperatureLevel(uint8_t level); - // returns the selected temperature level - uint8_t getSelectedTemperatureLevel(); - - // set supported temperature levels (optional, requires temperature_level feature) - bool setSupportedTemperatureLevels(uint8_t *levels, uint16_t count); - // get supported temperature levels count - uint16_t getSupportedTemperatureLevelsCount(); - - // this function is called by Matter internal event processor. It could be overwritten by the application, if necessary. - bool attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val); - -protected: - bool started = false; - // Feature mode: true = temperature_number, false = temperature_level +#include +#include +#include + +using namespace esp_matter; +using namespace esp_matter::endpoint; +using namespace esp_matter::cluster; +using namespace chip::app::Clusters; + +// Custom endpoint for temperature_level_controlled_cabinet device +// This endpoint uses temperature_level feature instead of temperature_number +namespace esp_matter { +using namespace cluster; +namespace endpoint { +namespace temperature_level_controlled_cabinet { +typedef struct config { + cluster::descriptor::config_t descriptor; + cluster::temperature_control::config_t temperature_control; +} config_t; + +uint32_t get_device_type_id() { + return ESP_MATTER_TEMPERATURE_CONTROLLED_CABINET_DEVICE_TYPE_ID; +} + +uint8_t get_device_type_version() { + return ESP_MATTER_TEMPERATURE_CONTROLLED_CABINET_DEVICE_TYPE_VERSION; +} + +esp_err_t add(endpoint_t *endpoint, config_t *config) { + if (!endpoint) { + log_e("Endpoint cannot be NULL"); + return ESP_ERR_INVALID_ARG; + } + esp_err_t err = add_device_type(endpoint, get_device_type_id(), get_device_type_version()); + if (err != ESP_OK) { + log_e("Failed to add device type id:%" PRIu32 ",err: %d", get_device_type_id(), err); + return err; + } + + // Create temperature_control cluster with temperature_level feature // Note: temperature_number and temperature_level are mutually exclusive - bool useTemperatureNumber = true; - - // temperature in 1/100th Celsius (stored as int16_t by multiplying by 100) - int16_t rawTempSetpoint = 0; - int16_t rawMinTemperature = 0; - int16_t rawMaxTemperature = 0; - int16_t rawStep = 0; - uint8_t selectedTempLevel = 0; - // Fixed-size buffer for supported temperature levels (max 16 as per Matter spec: temperature_control::k_max_temp_level_count) - uint8_t supportedLevelsArray[16]; // Size matches esp_matter::cluster::temperature_control::k_max_temp_level_count - uint16_t supportedLevelsCount = 0; - - // internal functions to set the raw temperature values (Matter Cluster) - bool setRawTemperatureSetpoint(int16_t _rawTemperature); - bool setRawMinTemperature(int16_t _rawTemperature); - bool setRawMaxTemperature(int16_t _rawTemperature); - bool setRawStep(int16_t _rawStep); - bool begin(int16_t _rawTempSetpoint, int16_t _rawMinTemperature, int16_t _rawMaxTemperature, int16_t _rawStep); - bool beginInternal(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel); -}; + temperature_control::create(endpoint, &(config->temperature_control), CLUSTER_FLAG_SERVER, temperature_control::feature::temperature_level::get_id()); + + return ESP_OK; +} + +endpoint_t *create(node_t *node, config_t *config, uint8_t flags, void *priv_data) { + endpoint_t *endpoint = endpoint::create(node, flags, priv_data); + add(endpoint, config); + return endpoint; +} +} // namespace temperature_level_controlled_cabinet +} // namespace endpoint +} // namespace esp_matter + +bool MatterTemperatureControlledCabinet::attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val) { + bool ret = true; + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + log_d("Temperature Controlled Cabinet Attr update callback: endpoint: %u, cluster: %u, attribute: %u", endpoint_id, cluster_id, attribute_id); + + // Handle TemperatureControl cluster attribute changes from Matter controller + if (cluster_id == TemperatureControl::Id) { + switch (attribute_id) { + case TemperatureControl::Attributes::TemperatureSetpoint::Id: + if (useTemperatureNumber) { + rawTempSetpoint = val->val.i16; + log_i("Temperature setpoint changed to %.02fC", (float)rawTempSetpoint / 100.0); + } else { + log_w("Temperature setpoint change ignored - temperature_level feature is active"); + } + break; + + case TemperatureControl::Attributes::MinTemperature::Id: + if (useTemperatureNumber) { + rawMinTemperature = val->val.i16; + log_i("Min temperature changed to %.02fC", (float)rawMinTemperature / 100.0); + } else { + log_w("Min temperature change ignored - temperature_level feature is active"); + } + break; + + case TemperatureControl::Attributes::MaxTemperature::Id: + if (useTemperatureNumber) { + rawMaxTemperature = val->val.i16; + log_i("Max temperature changed to %.02fC", (float)rawMaxTemperature / 100.0); + } else { + log_w("Max temperature change ignored - temperature_level feature is active"); + } + break; + + case TemperatureControl::Attributes::Step::Id: + if (useTemperatureNumber) { + rawStep = val->val.i16; + log_i("Temperature step changed to %.02fC", (float)rawStep / 100.0); + } else { + log_w("Temperature step change ignored - temperature_level feature is active"); + } + break; + + case TemperatureControl::Attributes::SelectedTemperatureLevel::Id: + if (!useTemperatureNumber) { + selectedTempLevel = val->val.u8; + log_i("Selected temperature level changed to %u", selectedTempLevel); + } else { + log_w("Selected temperature level change ignored - temperature_number feature is active"); + } + break; + + case TemperatureControl::Attributes::SupportedTemperatureLevels::Id: + // SupportedTemperatureLevels is read-only (managed via iterator delegate) + // But if a controller tries to write it, we should log it + log_w("SupportedTemperatureLevels change attempted - this attribute is read-only"); + break; + + default: log_d("Unhandled TemperatureControl Attribute ID: %u", attribute_id); break; + } + } + + return ret; +} + +MatterTemperatureControlledCabinet::MatterTemperatureControlledCabinet() {} + +MatterTemperatureControlledCabinet::~MatterTemperatureControlledCabinet() { + end(); +} + +bool MatterTemperatureControlledCabinet::begin(double tempSetpoint, double minTemperature, double maxTemperature, double step) { + int16_t rawSetpoint = static_cast(tempSetpoint * 100.0); + int16_t rawMin = static_cast(minTemperature * 100.0); + int16_t rawMax = static_cast(maxTemperature * 100.0); + int16_t rawStepValue = static_cast(step * 100.0); + return begin(rawSetpoint, rawMin, rawMax, rawStepValue); +} + +bool MatterTemperatureControlledCabinet::begin(int16_t _rawTempSetpoint, int16_t _rawMinTemperature, int16_t _rawMaxTemperature, int16_t _rawStep) { + ArduinoMatter::_init(); + + if (getEndPointId() != 0) { + log_e("Temperature Controlled Cabinet with Endpoint Id %d device has already been created.", getEndPointId()); + return false; + } + + // Note: esp-matter automatically creates all attributes from the config struct when features are enabled + // - temperature_number feature creates: TemperatureSetpoint, MinTemperature, MaxTemperature + // - temperature_step feature creates: Step (always enabled for temperature_number mode to allow setStep() later) + // No need to manually set attributes here as they are already created with the config values + + temperature_controlled_cabinet::config_t cabinet_config; + cabinet_config.temperature_control.temperature_number.temp_setpoint = _rawTempSetpoint; + cabinet_config.temperature_control.temperature_number.min_temperature = _rawMinTemperature; + cabinet_config.temperature_control.temperature_number.max_temperature = _rawMaxTemperature; + cabinet_config.temperature_control.temperature_step.step = _rawStep; + cabinet_config.temperature_control.temperature_level.selected_temp_level = 0; + + // Enable temperature_number feature (required) + // Note: temperature_number and temperature_level are mutually exclusive. + // Only one of them can be enabled at a time. + cabinet_config.temperature_control.features = temperature_control::feature::temperature_number::get_id(); + + // Always enable temperature_step feature to allow setStep() to be called later + // Note: temperature_step requires temperature_number feature (which is always enabled for this mode) + // The step value can be set initially via begin() or later via setStep() + cabinet_config.temperature_control.features |= temperature_control::feature::temperature_step::get_id(); + + // endpoint handles can be used to add/modify clusters + endpoint_t *endpoint = temperature_controlled_cabinet::create(node::get(), &cabinet_config, ENDPOINT_FLAG_NONE, (void *)this); + if (endpoint == nullptr) { + log_e("Failed to create Temperature Controlled Cabinet endpoint"); + return false; + } + + rawTempSetpoint = _rawTempSetpoint; + rawMinTemperature = _rawMinTemperature; + rawMaxTemperature = _rawMaxTemperature; + rawStep = _rawStep; + selectedTempLevel = 0; + useTemperatureNumber = true; // Set feature mode to temperature_number + + setEndPointId(endpoint::get_id(endpoint)); + log_i("Temperature Controlled Cabinet created with temperature_number feature, endpoint_id %d", getEndPointId()); + + // Workaround: Manually create Step attribute if it wasn't created automatically + // This handles the case where temperature_step::add() fails due to feature map timing issue + // The feature map check in temperature_step::add() may not see the temperature_number feature + // immediately after it's added, causing the Step attribute to not be created + cluster_t *cluster = cluster::get(endpoint, TemperatureControl::Id); + if (cluster != nullptr) { + attribute_t *step_attr = attribute::get(cluster, TemperatureControl::Attributes::Step::Id); + if (step_attr == nullptr) { + // Step attribute wasn't created, manually create it + log_w("Step attribute not found after endpoint creation, manually creating it"); + step_attr = temperature_control::attribute::create_step(cluster, _rawStep); + if (step_attr != nullptr) { + // Update the feature map to include temperature_step feature + // This ensures the feature is properly registered even though the attribute was created manually + esp_matter_attr_val_t feature_map_val = esp_matter_invalid(NULL); + attribute_t *feature_map_attr = attribute::get(cluster, Globals::Attributes::FeatureMap::Id); + if (feature_map_attr != nullptr && attribute::get_val(feature_map_attr, &feature_map_val) == ESP_OK) { + feature_map_val.val.u32 |= temperature_control::feature::temperature_step::get_id(); + attribute::set_val(feature_map_attr, &feature_map_val); + } + log_i("Step attribute manually created with value %.02fC", (float)_rawStep / 100.0); + } else { + log_e("Failed to manually create Step attribute"); + } + } + } + + started = true; + return true; +} + +bool MatterTemperatureControlledCabinet::begin(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel) { + if (supportedLevels == nullptr || levelCount == 0) { + log_e("Invalid supportedLevels array or levelCount. Must provide at least one level."); + return false; + } + + // Validate against maximum from ESP-Matter + if (levelCount > temperature_control::k_max_temp_level_count) { + log_e("Level count %u exceeds maximum %u", levelCount, temperature_control::k_max_temp_level_count); + return false; + } + + // Validate that selectedLevel exists in supportedLevels array + bool levelFound = false; + for (uint16_t i = 0; i < levelCount; i++) { + if (supportedLevels[i] == selectedLevel) { + levelFound = true; + break; + } + } + if (!levelFound) { + log_e("Selected level %u is not in the supported levels array", selectedLevel); + return false; + } + return beginInternal(supportedLevels, levelCount, selectedLevel); +} + +bool MatterTemperatureControlledCabinet::beginInternal(uint8_t *supportedLevels, uint16_t levelCount, uint8_t selectedLevel) { + ArduinoMatter::_init(); + + if (getEndPointId() != 0) { + log_e("Temperature Controlled Cabinet with Endpoint Id %d device has already been created.", getEndPointId()); + return false; + } + + // Use custom temperature_level_controlled_cabinet endpoint that supports temperature_level feature + temperature_level_controlled_cabinet::config_t cabinet_config; + // Initialize temperature_number config (not used but required for struct) + cabinet_config.temperature_control.temperature_number.temp_setpoint = 0; + cabinet_config.temperature_control.temperature_number.min_temperature = 0; + cabinet_config.temperature_control.temperature_number.max_temperature = 0; + cabinet_config.temperature_control.temperature_step.step = 0; + cabinet_config.temperature_control.temperature_level.selected_temp_level = selectedLevel; + + // Enable temperature_level feature + // Note: temperature_number and temperature_level are mutually exclusive. + // Only one of them can be enabled at a time. + cabinet_config.temperature_control.features = temperature_control::feature::temperature_level::get_id(); + + // endpoint handles can be used to add/modify clusters + endpoint_t *endpoint = temperature_level_controlled_cabinet::create(node::get(), &cabinet_config, ENDPOINT_FLAG_NONE, (void *)this); + if (endpoint == nullptr) { + log_e("Failed to create Temperature Level Controlled Cabinet endpoint"); + return false; + } + + // Copy supported levels array into internal buffer + memcpy(supportedLevelsArray, supportedLevels, levelCount * sizeof(uint8_t)); + supportedLevelsCount = levelCount; + selectedTempLevel = selectedLevel; + useTemperatureNumber = false; // Set feature mode to temperature_level + + // Initialize temperature_number values to 0 (not used in this mode) + rawTempSetpoint = 0; + rawMinTemperature = 0; + rawMaxTemperature = 0; + rawStep = 0; + + setEndPointId(endpoint::get_id(endpoint)); + log_i("Temperature Level Controlled Cabinet created with temperature_level feature, endpoint_id %d", getEndPointId()); + + // Set started flag before calling setter methods (they check for started) + started = true; + + // Set supported temperature levels using internal copy + if (!setSupportedTemperatureLevels(supportedLevelsArray, levelCount)) { + log_e("Failed to set supported temperature levels"); + started = false; // Reset on failure + return false; + } + + // Set selected temperature level + if (!setSelectedTemperatureLevel(selectedLevel)) { + log_e("Failed to set selected temperature level"); + started = false; // Reset on failure + return false; + } + + return true; +} + +void MatterTemperatureControlledCabinet::end() { + started = false; + useTemperatureNumber = true; // Reset to default + supportedLevelsCount = 0; + // No need to clear array - it's a fixed-size buffer +} + +bool MatterTemperatureControlledCabinet::setRawTemperatureSetpoint(int16_t _rawTemperature) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (!useTemperatureNumber) { + log_e("Temperature setpoint methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return false; + } + + // Validate against min/max + if (_rawTemperature < rawMinTemperature || _rawTemperature > rawMaxTemperature) { + log_e( + "Temperature setpoint %.02fC is out of range [%.02fC, %.02fC]", (float)_rawTemperature / 100.0, (float)rawMinTemperature / 100.0, + (float)rawMaxTemperature / 100.0 + ); + return false; + } + + // avoid processing if there was no change + if (rawTempSetpoint == _rawTemperature) { + return true; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::TemperatureSetpoint::Id, &tempVal)) { + log_e("Failed to get Temperature Controlled Cabinet Temperature Setpoint Attribute."); + return false; + } + if (tempVal.val.i16 != _rawTemperature) { + tempVal.val.i16 = _rawTemperature; + bool ret; + ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::TemperatureSetpoint::Id, &tempVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Temperature Setpoint Attribute."); + return false; + } + rawTempSetpoint = _rawTemperature; + } + log_v("Temperature Controlled Cabinet setpoint set to %.02fC", (float)_rawTemperature / 100.00); + + return true; +} + +bool MatterTemperatureControlledCabinet::setTemperatureSetpoint(double temperature) { + int16_t rawValue = static_cast(temperature * 100.0); + return setRawTemperatureSetpoint(rawValue); +} + +double MatterTemperatureControlledCabinet::getTemperatureSetpoint() { + if (!useTemperatureNumber) { + log_e("Temperature setpoint methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return 0.0; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::TemperatureSetpoint::Id, &tempVal)) { + rawTempSetpoint = tempVal.val.i16; + } + return (double)rawTempSetpoint / 100.0; +} + +bool MatterTemperatureControlledCabinet::setRawMinTemperature(int16_t _rawTemperature) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (!useTemperatureNumber) { + log_e("Min temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return false; + } + + if (rawMinTemperature == _rawTemperature) { + return true; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MinTemperature::Id, &tempVal)) { + log_e("Failed to get Temperature Controlled Cabinet Min Temperature Attribute."); + return false; + } + if (tempVal.val.i16 != _rawTemperature) { + tempVal.val.i16 = _rawTemperature; + bool ret; + ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MinTemperature::Id, &tempVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Min Temperature Attribute."); + return false; + } + rawMinTemperature = _rawTemperature; + } + log_v("Temperature Controlled Cabinet min temperature set to %.02fC", (float)_rawTemperature / 100.00); + + return true; +} + +bool MatterTemperatureControlledCabinet::setMinTemperature(double temperature) { + int16_t rawValue = static_cast(temperature * 100.0); + return setRawMinTemperature(rawValue); +} + +double MatterTemperatureControlledCabinet::getMinTemperature() { + if (!useTemperatureNumber) { + log_e("Min temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return 0.0; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MinTemperature::Id, &tempVal)) { + rawMinTemperature = tempVal.val.i16; + } + return (double)rawMinTemperature / 100.0; +} + +bool MatterTemperatureControlledCabinet::setRawMaxTemperature(int16_t _rawTemperature) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (!useTemperatureNumber) { + log_e("Max temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return false; + } + + if (rawMaxTemperature == _rawTemperature) { + return true; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MaxTemperature::Id, &tempVal)) { + log_e("Failed to get Temperature Controlled Cabinet Max Temperature Attribute."); + return false; + } + if (tempVal.val.i16 != _rawTemperature) { + tempVal.val.i16 = _rawTemperature; + bool ret; + ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MaxTemperature::Id, &tempVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Max Temperature Attribute."); + return false; + } + rawMaxTemperature = _rawTemperature; + } + log_v("Temperature Controlled Cabinet max temperature set to %.02fC", (float)_rawTemperature / 100.00); + + return true; +} + +bool MatterTemperatureControlledCabinet::setMaxTemperature(double temperature) { + int16_t rawValue = static_cast(temperature * 100.0); + return setRawMaxTemperature(rawValue); +} + +double MatterTemperatureControlledCabinet::getMaxTemperature() { + if (!useTemperatureNumber) { + log_e("Max temperature methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return 0.0; + } + + esp_matter_attr_val_t tempVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::MaxTemperature::Id, &tempVal)) { + rawMaxTemperature = tempVal.val.i16; + } + return (double)rawMaxTemperature / 100.0; +} + +bool MatterTemperatureControlledCabinet::setRawStep(int16_t _rawStep) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (!useTemperatureNumber) { + log_e("Temperature step methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return false; + } + + if (rawStep == _rawStep) { + return true; + } + + esp_matter_attr_val_t stepVal = esp_matter_invalid(NULL); + if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::Step::Id, &stepVal)) { + log_e("Failed to get Temperature Controlled Cabinet Step Attribute. Temperature_step feature may not be enabled."); + return false; + } + if (stepVal.val.i16 != _rawStep) { + stepVal.val.i16 = _rawStep; + bool ret; + ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::Step::Id, &stepVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Step Attribute."); + return false; + } + rawStep = _rawStep; + } + log_v("Temperature Controlled Cabinet step set to %.02fC", (float)_rawStep / 100.00); + + return true; +} + +bool MatterTemperatureControlledCabinet::setStep(double step) { + int16_t rawValue = static_cast(step * 100.0); + return setRawStep(rawValue); +} + +double MatterTemperatureControlledCabinet::getStep() { + if (!useTemperatureNumber) { + log_e("Temperature step methods require temperature_number feature. Use begin(tempSetpoint, minTemp, maxTemp, step) instead."); + return 0.0; + } + + // Read from attribute (should always exist after begin() due to workaround) + // If read fails, use stored rawStep value from begin() + esp_matter_attr_val_t stepVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::Step::Id, &stepVal)) { + rawStep = stepVal.val.i16; + } + return (double)rawStep / 100.0; +} + +bool MatterTemperatureControlledCabinet::setSelectedTemperatureLevel(uint8_t level) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (useTemperatureNumber) { + log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); + return false; + } + + // Validate that level is in supported levels array + bool levelFound = false; + for (uint16_t i = 0; i < supportedLevelsCount; i++) { + if (supportedLevelsArray[i] == level) { + levelFound = true; + break; + } + } + if (!levelFound) { + log_e("Temperature level %u is not in the supported levels array", level); + return false; + } + if (selectedTempLevel == level) { + return true; + } + + esp_matter_attr_val_t levelVal = esp_matter_invalid(NULL); + if (!getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SelectedTemperatureLevel::Id, &levelVal)) { + log_e("Failed to get Temperature Controlled Cabinet Selected Temperature Level Attribute."); + return false; + } + if (levelVal.val.u8 != level) { + levelVal.val.u8 = level; + bool ret; + ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SelectedTemperatureLevel::Id, &levelVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Selected Temperature Level Attribute."); + return false; + } + selectedTempLevel = level; + } + log_v("Temperature Controlled Cabinet selected temperature level set to %u", level); + + return true; +} + +uint8_t MatterTemperatureControlledCabinet::getSelectedTemperatureLevel() { + if (useTemperatureNumber) { + log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); + return 0; + } + + esp_matter_attr_val_t levelVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SelectedTemperatureLevel::Id, &levelVal)) { + selectedTempLevel = levelVal.val.u8; + } + return selectedTempLevel; +} + +bool MatterTemperatureControlledCabinet::setSupportedTemperatureLevels(uint8_t *levels, uint16_t count) { + if (!started) { + log_e("Matter Temperature Controlled Cabinet device has not begun."); + return false; + } + + if (useTemperatureNumber) { + log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); + return false; + } + + if (levels == nullptr || count == 0) { + log_e("Invalid levels array or count."); + return false; + } + + // Validate against maximum from ESP-Matter + if (count > temperature_control::k_max_temp_level_count) { + log_e("Level count %u exceeds maximum %u", count, temperature_control::k_max_temp_level_count); + return false; + } + + // Copy the array into internal buffer + memcpy(supportedLevelsArray, levels, count * sizeof(uint8_t)); + supportedLevelsCount = count; + + // Use internal copy for Matter attribute update + // Use esp_matter_array helper function which properly initializes the structure + esp_matter_attr_val_t levelsVal = esp_matter_array(supportedLevelsArray, sizeof(uint8_t), count); + + bool ret = updateAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SupportedTemperatureLevels::Id, &levelsVal); + if (!ret) { + log_e("Failed to update Temperature Controlled Cabinet Supported Temperature Levels Attribute."); + return false; + } + log_v("Temperature Controlled Cabinet supported temperature levels updated, count: %u", count); + + return true; +} + +uint16_t MatterTemperatureControlledCabinet::getSupportedTemperatureLevelsCount() { + if (useTemperatureNumber) { + log_e("Temperature level methods require temperature_level feature. Use begin(supportedLevels, levelCount, selectedLevel) instead."); + return 0; + } + + esp_matter_attr_val_t levelsVal = esp_matter_invalid(NULL); + if (getAttributeVal(TemperatureControl::Id, TemperatureControl::Attributes::SupportedTemperatureLevels::Id, &levelsVal)) { + return levelsVal.val.a.n; // a.n is the count (number of elements) + } + return 0; +} + #endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */