diff --git a/CMakeLists.txt b/CMakeLists.txt index 22f67c370e1..de7403ebe77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,6 +198,7 @@ set(ARDUINO_LIBRARY_Matter_SRCS libraries/Matter/src/MatterEndpoints/MatterPressureSensor.cpp libraries/Matter/src/MatterEndpoints/MatterOccupancySensor.cpp libraries/Matter/src/MatterEndpoints/MatterOnOffPlugin.cpp + libraries/Matter/src/MatterEndpoints/MatterDimmablePlugin.cpp libraries/Matter/src/MatterEndpoints/MatterThermostat.cpp libraries/Matter/src/Matter.cpp libraries/Matter/src/MatterEndPoint.cpp) diff --git a/docs/en/matter/ep_dimmable_plugin.rst b/docs/en/matter/ep_dimmable_plugin.rst new file mode 100644 index 00000000000..f96e6adca64 --- /dev/null +++ b/docs/en/matter/ep_dimmable_plugin.rst @@ -0,0 +1,257 @@ +#################### +MatterDimmablePlugin +#################### + +About +----- + +The ``MatterDimmablePlugin`` class provides a dimmable plugin unit endpoint for Matter networks. This endpoint implements the Matter dimmable plugin standard for controlling power outlets, relays, and other dimmable devices with power level control. + +**Features:** +* On/off control +* Power level control (0-255) +* State persistence support +* Callback support for state and level changes +* Integration with Apple HomeKit, Amazon Alexa, and Google Home +* Matter standard compliance + +**Use Cases:** +* Dimmable smart power outlets +* Variable power control +* Smart dimmer plugs +* Level-controlled device control + +API Reference +------------- + +Constructor +*********** + +MatterDimmablePlugin +^^^^^^^^^^^^^^^^^^^^ + +Creates a new Matter dimmable plugin endpoint. + +.. code-block:: arduino + + MatterDimmablePlugin(); + +Initialization +************** + +begin +^^^^^ + +Initializes the Matter dimmable plugin endpoint with optional initial state and level. + +.. code-block:: arduino + + bool begin(bool initialState = false, uint8_t level = 64); + +* ``initialState`` - Initial on/off state (``true`` = on, ``false`` = off, default: ``false``) +* ``level`` - Initial power level (0-255, default: 64 = 25%) + +This function will return ``true`` if successful, ``false`` otherwise. + +end +^^^ + +Stops processing Matter plugin events. + +.. code-block:: arduino + + void end(); + +On/Off Control +************** + +setOnOff +^^^^^^^^ + +Sets the on/off state of the plugin. + +.. code-block:: arduino + + bool setOnOff(bool newState); + +* ``newState`` - New state (``true`` = on, ``false`` = off) + +This function will return ``true`` if successful, ``false`` otherwise. + +getOnOff +^^^^^^^^ + +Gets the current on/off state of the plugin. + +.. code-block:: arduino + + bool getOnOff(); + +This function will return ``true`` if the plugin is on, ``false`` if off. + +toggle +^^^^^^ + +Toggles the on/off state of the plugin. + +.. code-block:: arduino + + bool toggle(); + +This function will return ``true`` if successful, ``false`` otherwise. + +Level Control +************** + +setLevel +^^^^^^^^ + +Sets the power level of the plugin. + +.. code-block:: arduino + + bool setLevel(uint8_t newLevel); + +* ``newLevel`` - New power level (0-255, where 0 = off, 255 = maximum level) + +This function will return ``true`` if successful, ``false`` otherwise. + +getLevel +^^^^^^^^ + +Gets the current power level of the plugin. + +.. code-block:: arduino + + uint8_t getLevel(); + +This function will return the power level (0-255). + +Constants +********* + +MAX_LEVEL +^^^^^^^^^^ + +Maximum power level value constant. + +.. code-block:: arduino + + static const uint8_t MAX_LEVEL = 255; + +Operators +********* + +bool operator +^^^^^^^^^^^^^ + +Returns the current on/off state of the plugin. + +.. code-block:: arduino + + operator bool(); + +Example: + +.. code-block:: arduino + + if (myPlugin) { + Serial.println("Plugin is on"); + } + +Assignment operator +^^^^^^^^^^^^^^^^^^^ + +Turns the plugin on or off. + +.. code-block:: arduino + + void operator=(bool state); + +Example: + +.. code-block:: arduino + + myPlugin = true; // Turn plugin on + myPlugin = false; // Turn plugin off + +Event Handling +************** + +onChange +^^^^^^^^ + +Sets a callback function to be called when any parameter changes. + +.. code-block:: arduino + + void onChange(EndPointCB onChangeCB); + +* ``onChangeCB`` - Function to call when state changes + +The callback signature is: + +.. code-block:: arduino + + bool onChangeCallback(bool newState, uint8_t newLevel); + +* ``newState`` - New on/off state (``true`` = on, ``false`` = off) +* ``newLevel`` - New power level (0-255) + +onChangeOnOff +^^^^^^^^^^^^^ + +Sets a callback function to be called when the on/off state changes. + +.. code-block:: arduino + + void onChangeOnOff(EndPointOnOffCB onChangeCB); + +* ``onChangeCB`` - Function to call when on/off state changes + +The callback signature is: + +.. code-block:: arduino + + bool onChangeCallback(bool newState); + +* ``newState`` - New on/off state (``true`` = on, ``false`` = off) + +onChangeLevel +^^^^^^^^^^^^^ + +Sets a callback function to be called when the power level changes. + +.. code-block:: arduino + + void onChangeLevel(EndPointLevelCB onChangeCB); + +* ``onChangeCB`` - Function to call when level changes + +The callback signature is: + +.. code-block:: arduino + + bool onChangeCallback(uint8_t newLevel); + +* ``newLevel`` - New power level (0-255) + +updateAccessory +^^^^^^^^^^^^^^^ + +Updates the state of the plugin using the current Matter Plugin internal state. + +.. code-block:: arduino + + void updateAccessory(); + +This function will call the registered callback with the current state. + +Example +------- + +Dimmable Plugin +*************** + +.. literalinclude:: ../../../libraries/Matter/examples/MatterDimmablePlugin/MatterDimmablePlugin.ino + :language: arduino diff --git a/docs/en/matter/matter.rst b/docs/en/matter/matter.rst index 278606d4e81..89d51c08946 100644 --- a/docs/en/matter/matter.rst +++ b/docs/en/matter/matter.rst @@ -135,6 +135,7 @@ The library provides specialized endpoint classes for different device types. Ea * ``MatterFan``: Fan with speed and mode control * ``MatterThermostat``: Thermostat with temperature control and setpoints * ``MatterOnOffPlugin``: On/off plugin unit (power outlet/relay) +* ``MatterDimmablePlugin``: Dimmable plugin unit (power outlet/relay with brightness control) * ``MatterGenericSwitch``: Generic switch endpoint (smart button) .. toctree:: diff --git a/libraries/Matter/examples/MatterDimmablePlugin/MatterDimmablePlugin.ino b/libraries/Matter/examples/MatterDimmablePlugin/MatterDimmablePlugin.ino new file mode 100644 index 00000000000..2cbebc5b742 --- /dev/null +++ b/libraries/Matter/examples/MatterDimmablePlugin/MatterDimmablePlugin.ino @@ -0,0 +1,186 @@ +// Copyright 2025 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Matter Manager +#include +#if !CONFIG_ENABLE_CHIPOBLE +// if the device can be commissioned using BLE, WiFi is not used - save flash space +#include +#endif +#include + +// List of Matter Endpoints for this Node +// Dimmable Plugin Endpoint +MatterDimmablePlugin DimmablePlugin; + +// 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 + +// it will keep last OnOff & Level state stored, using Preferences +Preferences matterPref; +const char *onOffPrefKey = "OnOff"; +const char *levelPrefKey = "Level"; + +// set your board RGB LED pin here +#ifdef RGB_BUILTIN +const uint8_t pluginPin = RGB_BUILTIN; // Using built-in RGB LED for visualization +#else +const uint8_t pluginPin = 2; // Set your pin here if your board has not defined RGB_BUILTIN +#warning "Do not forget to set the RGB LED pin" +#endif + +// set your board USER BUTTON pin here +const uint8_t buttonPin = BOOT_PIN; // Set your pin here. Using BOOT Button. + +// Button control +uint32_t button_time_stamp = 0; // debouncing control +bool button_state = false; // false = released | true = pressed +const uint32_t debounceTime = 250; // button debouncing time (ms) +const uint32_t decommissioningTimeout = 5000; // keep the button pressed for 5s, or longer, to decommission + +// Set the RGB LED Plugin output based on the current state and level +bool setPluginState(bool state, uint8_t level) { + Serial.printf("User Callback :: New Plugin State = %s, Level = %d\r\n", state ? "ON" : "OFF", level); + if (state) { + // Plugin is ON - set RGB LED level (0-255 maps to 0-100% power) +#ifdef RGB_BUILTIN + rgbLedWrite(pluginPin, level, level, level); +#else + analogWrite(pluginPin, level); +#endif + } else { + // Plugin is OFF - turn off output +#ifndef RGB_BUILTIN + // After analogWrite(), it is necessary to set the GPIO to digital mode first + pinMode(pluginPin, OUTPUT); +#endif + digitalWrite(pluginPin, LOW); + } + // store last Level and OnOff state for when the Plugin is restarted / power goes off + matterPref.putUChar(levelPrefKey, level); + matterPref.putBool(onOffPrefKey, state); + // This callback must return the success state to Matter core + return true; +} + +void setup() { + // Initialize the USER BUTTON (Boot button) GPIO that will act as a toggle switch + pinMode(buttonPin, INPUT_PULLUP); + // Initialize the RGB LED (plugin) GPIO + pinMode(pluginPin, OUTPUT); + digitalWrite(pluginPin, LOW); // Start with plugin off + + Serial.begin(115200); + +// CONFIG_ENABLE_CHIPOBLE is enabled when BLE is used to commission the Matter Network +#if !CONFIG_ENABLE_CHIPOBLE + // We start by connecting to a WiFi network + Serial.print("Connecting to "); + Serial.println(ssid); + // Manually connect to WiFi + WiFi.begin(ssid, password); + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("\r\nWiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + delay(500); +#endif + + // Initialize Matter EndPoint + matterPref.begin("MatterPrefs", false); + // default OnOff state is OFF if not stored before + bool lastOnOffState = matterPref.getBool(onOffPrefKey, false); + // default level ~= 25% (64/255) + uint8_t lastLevel = matterPref.getUChar(levelPrefKey, 64); + DimmablePlugin.begin(lastOnOffState, lastLevel); + // set the callback function to handle the Plugin state change + DimmablePlugin.onChange(setPluginState); + + // lambda functions are used to set the attribute change callbacks + DimmablePlugin.onChangeOnOff([](bool state) { + Serial.printf("Plugin OnOff changed to %s\r\n", state ? "ON" : "OFF"); + return true; + }); + DimmablePlugin.onChangeLevel([](uint8_t level) { + Serial.printf("Plugin Level changed to %d\r\n", level); + return true; + }); + + // Matter beginning - Last step, after all EndPoints are initialized + Matter.begin(); + // This may be a restart of a already commissioned Matter accessory + if (Matter.isDeviceCommissioned()) { + Serial.println("Matter Node is commissioned and connected to the network. Ready for use."); + Serial.printf("Initial state: %s | level: %d\r\n", DimmablePlugin ? "ON" : "OFF", DimmablePlugin.getLevel()); + // configure the Plugin based on initial on-off state and level + DimmablePlugin.updateAccessory(); + } +} + +void loop() { + // Check Matter Plugin 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 Plugin 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.printf("Initial state: %s | level: %d\r\n", DimmablePlugin ? "ON" : "OFF", DimmablePlugin.getLevel()); + // configure the Plugin based on initial on-off state and level + DimmablePlugin.updateAccessory(); + Serial.println("Matter Node is commissioned and connected to the network. Ready for use."); + } + + // A button is also used to control the plugin + // 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. + } + + // Onboard User Button is used as a Plugin toggle switch or to decommission it + uint32_t time_diff = millis() - button_time_stamp; + if (digitalRead(buttonPin) == HIGH && button_state && time_diff > debounceTime) { + // Toggle button is released - toggle the plugin + Serial.println("User button released. Toggling Plugin!"); + DimmablePlugin.toggle(); // Matter Controller also can see the change + button_state = false; // released + } + + // Onboard User Button is kept pressed for longer than 5 seconds in order to decommission matter node + if (button_state && time_diff > decommissioningTimeout) { + Serial.println("Decommissioning the Plugin Matter Accessory. It shall be commissioned again."); + DimmablePlugin = false; // turn the plugin off + Matter.decommission(); + button_time_stamp = millis(); // avoid running decommissioning again, reboot takes a second or so + } +} diff --git a/libraries/Matter/examples/MatterDimmablePlugin/README.md b/libraries/Matter/examples/MatterDimmablePlugin/README.md new file mode 100644 index 00000000000..d823c5c1ca7 --- /dev/null +++ b/libraries/Matter/examples/MatterDimmablePlugin/README.md @@ -0,0 +1,222 @@ +# Matter Dimmable Plugin Example + +This example demonstrates how to create a Matter-compatible dimmable plugin unit (power outlet with level control) device using an ESP32 SoC microcontroller.\ +The application showcases Matter commissioning, device control via smart home ecosystems, and state persistence for dimmable power control applications. + +## Supported Targets + +| SoC | Wi-Fi | Thread | BLE Commissioning | Relay/Dimmer | Status | +| --- | ---- | ------ | ----------------- | ------------ | ------ | +| ESP32 | ✅ | ❌ | ❌ | Required | Fully supported | +| ESP32-S2 | ✅ | ❌ | ❌ | Required | Fully supported | +| ESP32-S3 | ✅ | ❌ | ✅ | Required | Fully supported | +| ESP32-C3 | ✅ | ❌ | ✅ | Required | Fully supported | +| ESP32-C5 | ✅ | ❌ | ✅ | Required | Fully supported | +| ESP32-C6 | ✅ | ❌ | ✅ | Required | Fully supported | +| ESP32-H2 | ❌ | ✅ | ✅ | Required | 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 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 + +- Matter protocol implementation for a dimmable plugin unit (power outlet with level control) device +- Support for both Wi-Fi and Thread(*) connectivity +- On/off control and power level control (0-255 levels) +- State persistence using `Preferences` library +- Button control for toggling plugin and factory reset +- 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. + +## Hardware Requirements + +- ESP32 compatible development board (see supported targets table) +- Power relay/dimmer module or RGB LED for visualization (for testing, uses built-in RGB LED if available) +- User button for manual control (uses BOOT button by default) + +## Pin Configuration + +- **RGB LED/Relay/Dimmer Pin**: Uses `RGB_BUILTIN` if defined (for testing with RGB LED visualization), otherwise pin 2. For production use, connect this to a PWM-capable pin for dimmer control or relay control pin. +- **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` + - `Preferences` + - `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. **Power relay/dimmer pin configuration** (if not using built-in LED): + For production use, change this to a PWM-capable GPIO pin connected to your dimmer control module: + ```cpp + const uint8_t pluginPin = 2; // Set your PWM-capable pin here for dimmer control + ``` + + **Note**: The example uses `RGB_BUILTIN` if available on your board (e.g., ESP32-S3, ESP32-C3) to visually demonstrate the level control. The RGB LED brightness will change based on the power level (0-255). For boards without RGB LED, it falls back to a regular pin with PWM support. + +3. **Button pin configuration** (optional): + By default, the `BOOT` button (GPIO 0) is used for the Plugin On/Off manual control. You can change this to a different pin if needed. + ```cpp + const uint8_t buttonPin = BOOT_PIN; // Set your button pin here + ``` + +## Building and Flashing + +1. Open the `MatterDimmablePlugin.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. +... +Initial state: OFF | level: 64 +Matter Node is commissioned and connected to the network. Ready for use. +Plugin OnOff changed to ON +Plugin Level changed to 128 +User Callback :: New Plugin State = ON, Level = 128 +``` + +## Using the Device + +### Manual Control + +The user button (BOOT button by default) provides manual control: + +- **Short press of the button**: Toggle plugin on/off +- **Long press (>5 seconds)**: Factory reset the device (decommission) + +### State Persistence + +The device saves the last known on/off state and power level using the `Preferences` library. After a power cycle or restart: + +- The device will restore to the last saved state (ON or OFF) and power level +- Default state is OFF with level 64 (25%) if no previous state was saved +- The Matter controller will be notified of the restored state +- The relay/dimmer will reflect the restored state and level + +### Power Relay/Dimmer Integration + +For production use with a power relay or dimmer module: + +1. **For Dimmer Control (PWM-based)**: + - Connect the dimmer module to your ESP32: + - Dimmer VCC → ESP32 3.3 V or 5 V (check dimmer module specifications) + - Dimmer GND → ESP32 GND + - Dimmer PWM/Control → ESP32 GPIO pin with PWM support (configured as `pluginPin`) + - Update the pin configuration in the sketch: + ```cpp + const uint8_t pluginPin = 2; // Your PWM-capable pin for dimmer control + ``` + - The level (0-255) will control the dimmer output power (0% to 100%) + +2. **For Relay Control (On/Off only)**: + - Connect the relay module to your ESP32: + - Relay VCC → ESP32 3.3 V or 5 V (check relay module specifications) + - Relay GND → ESP32 GND + - Relay IN → ESP32 GPIO pin (configured as `pluginPin`) + - Update the pin configuration in the sketch: + ```cpp + const uint8_t pluginPin = 2; // Your relay control pin + ``` + - Note: When using a relay, the level control will still work but the relay will only switch on/off based on the state + +3. **Test the relay/dimmer** by controlling it via Matter app - the device should respond to both on/off and level changes + +### 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 dimmable outlet/switch in your Home app +7. You can control both the on/off state and power level (0-100%) + +#### 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 dimmable plugin will appear in your Alexa app +6. You can control power level using voice commands like "Alexa, set outlet to 50 percent" + +#### 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. You can control power level using voice commands or the slider in the app + +## Code Structure + +The MatterDimmablePlugin example consists of the following main components: + +1. **`setup()`**: Initializes hardware (button, relay/dimmer pin), configures Wi-Fi (if needed), initializes `Preferences` library, sets up the Matter plugin endpoint with the last saved state (defaults to OFF with level 64 if not previously saved), registers callback functions, and starts the Matter stack. + +2. **`loop()`**: Checks the Matter commissioning state, handles button input for toggling the plugin and factory reset, and allows the Matter stack to process events. + +3. **Callbacks**: + - `setPluginState()`: Controls the physical relay/dimmer based on the on/off state and power level, saves the state to `Preferences` for persistence, and prints the state change to Serial Monitor. + - `onChangeOnOff()`: Handles on/off state changes. + - `onChangeLevel()`: Handles power level changes (0-255). + +## Troubleshooting + +- **Device not visible during commissioning**: Ensure Wi-Fi or Thread connectivity is properly configured +- **Relay/Dimmer not responding**: Verify pin configurations and connections. For dimmer modules, ensure the pin supports PWM (analogWrite). For relay modules, ensure proper power supply and wiring +- **Level control not working**: For dimmer control, verify the pin supports PWM. Check that `analogWrite()` or `rgbLedWrite()` (for RGB LED) is working correctly on your board. On boards with RGB LED, the brightness will change based on the level value (0-255) +- **State not persisting**: Check that the `Preferences` library is properly initialized and that flash memory is not corrupted +- **Relay not switching**: For relay modules, verify the control signal voltage levels match your relay module requirements (some relays need 5 V, others work with 3.3 V) +- **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 Dimmable Plugin Endpoint](https://docs.espressif.com/projects/arduino-esp32/en/latest/matter/ep_dimmable_plugin.html) + +## License + +This example is licensed under the Apache License, Version 2.0. diff --git a/libraries/Matter/examples/MatterDimmablePlugin/ci.yml b/libraries/Matter/examples/MatterDimmablePlugin/ci.yml new file mode 100644 index 00000000000..050a80ff543 --- /dev/null +++ b/libraries/Matter/examples/MatterDimmablePlugin/ci.yml @@ -0,0 +1,4 @@ +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 fc8ccf082f0..e78b84843bc 100644 --- a/libraries/Matter/keywords.txt +++ b/libraries/Matter/keywords.txt @@ -26,6 +26,7 @@ MatterRainSensor KEYWORD1 MatterPressureSensor KEYWORD1 MatterOccupancySensor KEYWORD1 MatterOnOffPlugin KEYWORD1 +MatterDimmablePlugin KEYWORD1 MatterThermostat KEYWORD1 ControlSequenceOfOperation_t KEYWORD1 ThermostatMode_t KEYWORD1 @@ -62,6 +63,8 @@ getOnOff KEYWORD2 toggle KEYWORD2 setBrightness KEYWORD2 getBrightness KEYWORD2 +setLevel KEYWORD2 +getLevel KEYWORD2 setColorTemperature KEYWORD2 getColorTemperature KEYWORD2 setColorRGB KEYWORD2 diff --git a/libraries/Matter/src/Matter.h b/libraries/Matter/src/Matter.h index a5afab14de1..0482402c737 100644 --- a/libraries/Matter/src/Matter.h +++ b/libraries/Matter/src/Matter.h @@ -34,6 +34,7 @@ #include #include #include +#include #include // Matter Event types used when there is a user callback for Matter Events @@ -189,6 +190,7 @@ class ArduinoMatter { friend class MatterGenericSwitch; friend class MatterOnOffLight; friend class MatterDimmableLight; + friend class MatterDimmablePlugin; friend class MatterColorTemperatureLight; friend class MatterColorLight; friend class MatterEnhancedColorLight; diff --git a/libraries/Matter/src/MatterEndpoints/MatterDimmablePlugin.cpp b/libraries/Matter/src/MatterEndpoints/MatterDimmablePlugin.cpp new file mode 100644 index 00000000000..8e7318cf577 --- /dev/null +++ b/libraries/Matter/src/MatterEndpoints/MatterDimmablePlugin.cpp @@ -0,0 +1,194 @@ +// 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 + +using namespace esp_matter; +using namespace esp_matter::endpoint; +using namespace chip::app::Clusters; + +bool MatterDimmablePlugin::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 DimmablePlugin device has not begun."); + return false; + } + + log_d("DimmablePlugin Attr update callback: endpoint: %u, cluster: %u, attribute: %u, val: %u", endpoint_id, cluster_id, attribute_id, val->val.u32); + + if (endpoint_id == getEndPointId()) { + switch (cluster_id) { + case OnOff::Id: + if (attribute_id == OnOff::Attributes::OnOff::Id) { + log_d("DimmablePlugin On/Off State changed to %d", val->val.b); + if (_onChangeOnOffCB != NULL) { + ret &= _onChangeOnOffCB(val->val.b); + } + if (_onChangeCB != NULL) { + ret &= _onChangeCB(val->val.b, level); + } + if (ret == true) { + onOffState = val->val.b; + } + } + break; + case LevelControl::Id: + if (attribute_id == LevelControl::Attributes::CurrentLevel::Id) { + log_d("DimmablePlugin Level changed to %d", val->val.u8); + if (_onChangeLevelCB != NULL) { + ret &= _onChangeLevelCB(val->val.u8); + } + if (_onChangeCB != NULL) { + ret &= _onChangeCB(onOffState, val->val.u8); + } + if (ret == true) { + level = val->val.u8; + } + } + break; + } + } + return ret; +} + +MatterDimmablePlugin::MatterDimmablePlugin() {} + +MatterDimmablePlugin::~MatterDimmablePlugin() { + end(); +} + +bool MatterDimmablePlugin::begin(bool initialState, uint8_t level) { + ArduinoMatter::_init(); + if (getEndPointId() != 0) { + log_e("Matter Dimmable Plugin with Endpoint Id %d device has already been created.", getEndPointId()); + return false; + } + + dimmable_plugin_unit::config_t plugin_config; + plugin_config.on_off.on_off = initialState; + plugin_config.on_off.lighting.start_up_on_off = nullptr; + onOffState = initialState; + + plugin_config.level_control.current_level = level; + plugin_config.level_control.lighting.start_up_current_level = nullptr; + this->level = level; + + // endpoint handles can be used to add/modify clusters. + endpoint_t *endpoint = dimmable_plugin_unit::create(node::get(), &plugin_config, ENDPOINT_FLAG_NONE, (void *)this); + if (endpoint == nullptr) { + log_e("Failed to create dimmable plugin endpoint"); + return false; + } + + setEndPointId(endpoint::get_id(endpoint)); + log_i("Dimmable Plugin created with endpoint_id %d", getEndPointId()); + + /* Mark deferred persistence for some attributes that might be changed rapidly */ + cluster_t *level_control_cluster = cluster::get(endpoint, LevelControl::Id); + esp_matter::attribute_t *current_level_attribute = attribute::get(level_control_cluster, LevelControl::Attributes::CurrentLevel::Id); + attribute::set_deferred_persistence(current_level_attribute); + + started = true; + return true; +} + +void MatterDimmablePlugin::end() { + started = false; +} + +bool MatterDimmablePlugin::setOnOff(bool newState) { + if (!started) { + log_e("Matter Dimmable Plugin device has not begun."); + return false; + } + + // avoid processing if there was no change + if (onOffState == newState) { + return true; + } + + onOffState = newState; + + endpoint_t *endpoint = endpoint::get(node::get(), getEndPointId()); + cluster_t *cluster = cluster::get(endpoint, OnOff::Id); + esp_matter::attribute_t *attribute = attribute::get(cluster, OnOff::Attributes::OnOff::Id); + + esp_matter_attr_val_t val = esp_matter_invalid(NULL); + attribute::get_val(attribute, &val); + + if (val.val.b != onOffState) { + val.val.b = onOffState; + attribute::update(getEndPointId(), OnOff::Id, OnOff::Attributes::OnOff::Id, &val); + } + return true; +} + +void MatterDimmablePlugin::updateAccessory() { + if (_onChangeCB != NULL) { + _onChangeCB(onOffState, level); + } +} + +bool MatterDimmablePlugin::getOnOff() { + return onOffState; +} + +bool MatterDimmablePlugin::toggle() { + return setOnOff(!onOffState); +} + +bool MatterDimmablePlugin::setLevel(uint8_t newLevel) { + if (!started) { + log_w("Matter Dimmable Plugin device has not begun."); + return false; + } + + // avoid processing if there was no change + if (level == newLevel) { + return true; + } + + level = newLevel; + + endpoint_t *endpoint = endpoint::get(node::get(), getEndPointId()); + cluster_t *cluster = cluster::get(endpoint, LevelControl::Id); + esp_matter::attribute_t *attribute = attribute::get(cluster, LevelControl::Attributes::CurrentLevel::Id); + + esp_matter_attr_val_t val = esp_matter_invalid(NULL); + attribute::get_val(attribute, &val); + + if (val.val.u8 != level) { + val.val.u8 = level; + attribute::update(getEndPointId(), LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id, &val); + } + return true; +} + +uint8_t MatterDimmablePlugin::getLevel() { + return level; +} + +MatterDimmablePlugin::operator bool() { + return getOnOff(); +} + +void MatterDimmablePlugin::operator=(bool newState) { + setOnOff(newState); +} +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ diff --git a/libraries/Matter/src/MatterEndpoints/MatterDimmablePlugin.h b/libraries/Matter/src/MatterEndpoints/MatterDimmablePlugin.h new file mode 100644 index 00000000000..1fb4e32c3a9 --- /dev/null +++ b/libraries/Matter/src/MatterEndpoints/MatterDimmablePlugin.h @@ -0,0 +1,75 @@ +// 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 MatterDimmablePlugin : public MatterEndPoint { +public: + static const uint8_t MAX_LEVEL = 255; + + MatterDimmablePlugin(); + ~MatterDimmablePlugin(); + // default initial state is off and level is 64 (25%) + virtual bool begin(bool initialState = false, uint8_t level = 64); + // this will just stop processing Plugin Matter events + void end(); + + bool setOnOff(bool newState); // returns true if successful + bool getOnOff(); // returns current plugin state + bool toggle(); // returns true if successful + + bool setLevel(uint8_t newLevel); // returns true if successful + uint8_t getLevel(); // returns current level + + // User Callback for whenever the Plugin On/Off state is changed by the Matter Controller + using EndPointOnOffCB = std::function; + void onChangeOnOff(EndPointOnOffCB onChangeCB) { + _onChangeOnOffCB = onChangeCB; + } + + // User Callback for whenever the Plugin level value [0..255] is changed by the Matter Controller + using EndPointLevelCB = std::function; + void onChangeLevel(EndPointLevelCB onChangeCB) { + _onChangeLevelCB = onChangeCB; + } + + // User Callback for whenever any parameter is changed by the Matter Controller + using EndPointCB = std::function; + void onChange(EndPointCB onChangeCB) { + _onChangeCB = onChangeCB; + } + + // used to update the state of the plugin using the current Matter Plugin internal state + // It is necessary to set a user callback function using onChange() to handle the physical plugin state + void updateAccessory(); + + operator bool(); // returns current on/off plugin state + void operator=(bool state); // turns plugin on or off + // 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; + bool onOffState = false; // default initial state is off, but it can be changed by begin(bool) + uint8_t level = 0; // default initial level is 0, but it can be changed by begin(bool, uint8_t) + EndPointOnOffCB _onChangeOnOffCB = NULL; + EndPointLevelCB _onChangeLevelCB = NULL; + EndPointCB _onChangeCB = NULL; +}; +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */