From 4ee17dea045b2b4f8bd5965bdde6e9110b612026 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Fri, 11 Jul 2025 11:25:27 -0300 Subject: [PATCH 1/3] feat(matter): new matter lambda function example (#11561) Adds a new Matter Library example using lambda function to creat 6 endpoints using a single callback --- .../MatterLambdaSingleCallbackManyEPs.ino | 112 ++++++++++++++++++ .../MatterLambdaSingleCallbackManyEPs/ci.json | 7 ++ libraries/Matter/keywords.txt | 6 + 3 files changed, 125 insertions(+) create mode 100644 libraries/Matter/examples/MatterLambdaSingleCallbackManyEPs/MatterLambdaSingleCallbackManyEPs.ino create mode 100644 libraries/Matter/examples/MatterLambdaSingleCallbackManyEPs/ci.json diff --git a/libraries/Matter/examples/MatterLambdaSingleCallbackManyEPs/MatterLambdaSingleCallbackManyEPs.ino b/libraries/Matter/examples/MatterLambdaSingleCallbackManyEPs/MatterLambdaSingleCallbackManyEPs.ino new file mode 100644 index 00000000000..4992771d925 --- /dev/null +++ b/libraries/Matter/examples/MatterLambdaSingleCallbackManyEPs/MatterLambdaSingleCallbackManyEPs.ino @@ -0,0 +1,112 @@ +// 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 creates 6 on-off light endpoints that share the same onChangeOnOff() callback code. + It uses Lambda Function with an extra Lambda Capture information that links the Endpoint to its individual information. + After the Matter example is commissioned, the expected Serial output shall be similar to this: + +Matter App Control: 'Room 1' (OnOffLight[0], Endpoint 1, GPIO 2) changed to: OFF +Matter App Control: 'Room 1' (OnOffLight[0], Endpoint 1, GPIO 2) changed to: ON +Matter App Control: 'Room 5' (OnOffLight[4], Endpoint 5, GPIO 10) changed to: ON +Matter App Control: 'Room 2' (OnOffLight[1], Endpoint 2, GPIO 4) changed to: ON +Matter App Control: 'Room 4' (OnOffLight[3], Endpoint 4, GPIO 8) changed to: ON +Matter App Control: 'Room 6' (OnOffLight[5], Endpoint 6, GPIO 12) changed to: ON +Matter App Control: 'Room 3' (OnOffLight[2], Endpoint 3, GPIO 6) changed to: ON +Matter App Control: 'Room 5' (OnOffLight[4], Endpoint 5, GPIO 10) changed to: OFF +*/ + +// Matter Manager +#include +#include + +// 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 + +//number of On-Off Lights: +const uint8_t MAX_LIGHT_NUMBER = 6; + +// array of OnOffLight endpoints +MatterOnOffLight OnOffLight[MAX_LIGHT_NUMBER]; + +// all pins, one for each on-off light +uint8_t lightPins[MAX_LIGHT_NUMBER] = {2, 4, 6, 8, 10, 12}; // must replace it by the real pin for the target SoC and application + +// friendly OnOffLights names used for printing a message in the callback +const char *lightName[MAX_LIGHT_NUMBER] = { + "Room 1", "Room 2", "Room 3", "Room 4", "Room 5", "Room 6", +}; + +// simple setup() function +void setup() { + Serial.begin(115200); // callback will just print a message in the console + + // 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); + + // setup all the OnOff Light endpoint and their lambda callback functions + for (uint8_t i = 0; i < MAX_LIGHT_NUMBER; i++) { + pinMode(lightPins[i], OUTPUT); // set the GPIO function + OnOffLight[i].begin(false); // off + + // inline lambda function using capture array index -> it will just print a message in the console + OnOffLight[i].onChangeOnOff([i](bool state) -> bool { + // Display message with the specific light name and details + Serial.printf( + "Matter App Control: '%s' (OnOffLight[%d], Endpoint %d, GPIO %d) changed to: %s\r\n", lightName[i], i, OnOffLight[i].getEndPointId(), lightPins[i], + state ? "ON" : "OFF" + ); + + return true; + }); + } + // last step, starting Matter Stack + Matter.begin(); +} + +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.println("Matter Node is commissioned and connected to the WiFi network. Ready for use."); + } + + delay(500); +} diff --git a/libraries/Matter/examples/MatterLambdaSingleCallbackManyEPs/ci.json b/libraries/Matter/examples/MatterLambdaSingleCallbackManyEPs/ci.json new file mode 100644 index 00000000000..556a8a9ee6b --- /dev/null +++ b/libraries/Matter/examples/MatterLambdaSingleCallbackManyEPs/ci.json @@ -0,0 +1,7 @@ +{ + "fqbn_append": "PartitionScheme=huge_app", + "requires": [ + "CONFIG_SOC_WIFI_SUPPORTED=y", + "CONFIG_ESP_MATTER_ENABLE_DATA_MODEL=y" + ] +} diff --git a/libraries/Matter/keywords.txt b/libraries/Matter/keywords.txt index 68aaebb1d4d..edba06083bd 100644 --- a/libraries/Matter/keywords.txt +++ b/libraries/Matter/keywords.txt @@ -36,8 +36,10 @@ EndPointSpeedCB KEYWORD1 EndPointOnOffCB KEYWORD1 EndPointBrightnessCB KEYWORD1 EndPointRGBColorCB KEYWORD1 +EndPointIdentifyCB KEYWORD1 matterEvent_t KEYWORD1 matterEventCB KEYWORD1 +attrOperation_t KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -111,6 +113,10 @@ onChangeLocalTemperature KEYWORD2 onChangeCoolingSetpoint KEYWORD2 onChangeHeatingSetpoint KEYWORD2 onEvent KEYWORD2 +setEndPointId KEYWORD2 +getEndPointId KEYWORD2 +onIdentify KEYWORD2 +endpointIdentifyCB KEYWORD2 ####################################### # Constants (LITERAL1) From 0a45a0614244002f82fe7f006effeda7c8075469 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Sat, 12 Jul 2025 02:12:36 -0300 Subject: [PATCH 2/3] feat(wire): std::functional Wire slave callback functions (#11582) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR enhances the Wire library to support std::function–based callbacks for I2C slave mode, enabling the use of lambdas and captured contexts. - Replaces raw function pointers in TwoWire and HardwareI2C with std::function for onRequest and onReceive - Updates constructors, method signatures, and default initializations to use std::function - Adds new example sketch, CI config, and documentation updates demonstrating the functional callback API --- cores/esp32/HardwareI2C.h | 6 +- docs/en/api/i2c.rst | 135 +++++++++++++++++- .../WireSlaveFunctionalCallback.ino | 37 +++++ .../WireSlaveFunctionalCallback/ci.json | 5 + libraries/Wire/src/Wire.cpp | 6 +- libraries/Wire/src/Wire.h | 12 +- 6 files changed, 184 insertions(+), 17 deletions(-) create mode 100644 libraries/Wire/examples/WireSlaveFunctionalCallback/WireSlaveFunctionalCallback.ino create mode 100644 libraries/Wire/examples/WireSlaveFunctionalCallback/ci.json diff --git a/cores/esp32/HardwareI2C.h b/cores/esp32/HardwareI2C.h index 65b7e2036b2..c44f34e1ee7 100644 --- a/cores/esp32/HardwareI2C.h +++ b/cores/esp32/HardwareI2C.h @@ -20,6 +20,7 @@ #include #include "Stream.h" +#include class HardwareI2C : public Stream { public: @@ -36,6 +37,7 @@ class HardwareI2C : public Stream { virtual size_t requestFrom(uint8_t address, size_t len, bool stopBit) = 0; virtual size_t requestFrom(uint8_t address, size_t len) = 0; - virtual void onReceive(void (*)(int)) = 0; - virtual void onRequest(void (*)(void)) = 0; + // Update base class to use std::function + virtual void onReceive(const std::function &) = 0; + virtual void onRequest(const std::function &) = 0; }; diff --git a/docs/en/api/i2c.rst b/docs/en/api/i2c.rst index eac04b76a23..06d4d1953a6 100644 --- a/docs/en/api/i2c.rst +++ b/docs/en/api/i2c.rst @@ -347,20 +347,147 @@ This function will return ``true`` if the peripheral was initialized correctly. onReceive ^^^^^^^^^ -The ``onReceive`` function is used to define the callback for the data received from the master. +The ``onReceive`` function is used to define the callback for data received from the master device. .. code-block:: arduino - void onReceive( void (*)(int) ); + void onReceive(const std::function& callback); + +**Function Signature:** + +The callback function must have the signature ``void(int numBytes)`` where ``numBytes`` indicates how many bytes were received from the master. + +**Usage Examples:** + +.. code-block:: arduino + + // Method 1: Regular function + void handleReceive(int numBytes) { + Serial.printf("Received %d bytes: ", numBytes); + while (Wire.available()) { + char c = Wire.read(); + Serial.print(c); + } + Serial.println(); + } + Wire.onReceive(handleReceive); + + // Method 2: Lambda function + Wire.onReceive([](int numBytes) { + Serial.printf("Master sent %d bytes\n", numBytes); + while (Wire.available()) { + uint8_t data = Wire.read(); + // Process received data + Serial.printf("Data: 0x%02X\n", data); + } + }); + + // Method 3: Lambda with capture (for accessing variables) + int deviceId = 42; + Wire.onReceive([deviceId](int numBytes) { + Serial.printf("Device %d received %d bytes\n", deviceId, numBytes); + // Process data... + }); + + // Method 4: Using std::function variable + std::function receiveHandler = [](int bytes) { + Serial.printf("Handling %d received bytes\n", bytes); + }; + Wire.onReceive(receiveHandler); + + // Method 5: Class member function (using lambda wrapper) + class I2CDevice { + private: + int deviceAddress; + public: + I2CDevice(int addr) : deviceAddress(addr) {} + + void handleReceive(int numBytes) { + Serial.printf("Device 0x%02X received %d bytes\n", deviceAddress, numBytes); + } + + void setup() { + Wire.onReceive([this](int bytes) { + this->handleReceive(bytes); + }); + } + }; + +.. note:: + The ``onReceive`` callback is triggered when the I2C master sends data to this slave device. + Use ``Wire.available()`` and ``Wire.read()`` inside the callback to retrieve the received data. onRequest ^^^^^^^^^ -The ``onRequest`` function is used to define the callback for the data to be send to the master. +The ``onRequest`` function is used to define the callback for responding to master read requests. + +.. code-block:: arduino + + void onRequest(const std::function& callback); + +**Function Signature:** + +The callback function must have the signature ``void()`` with no parameters. This callback is triggered when the master requests data from this slave device. + +**Usage Examples:** .. code-block:: arduino - void onRequest( void (*)(void) ); + // Method 1: Regular function + void handleRequest() { + static int counter = 0; + Wire.printf("Response #%d", counter++); + } + Wire.onRequest(handleRequest); + + // Method 2: Lambda function + Wire.onRequest([]() { + // Send sensor data to master + int sensorValue = analogRead(A0); + Wire.write(sensorValue >> 8); // High byte + Wire.write(sensorValue & 0xFF); // Low byte + }); + + // Method 3: Lambda with capture (for accessing variables) + int deviceStatus = 1; + String deviceName = "Sensor1"; + Wire.onRequest([&deviceStatus, &deviceName]() { + Wire.write(deviceStatus); + Wire.write(deviceName.c_str(), deviceName.length()); + }); + + // Method 4: Using std::function variable + std::function requestHandler = []() { + Wire.write("Hello Master!"); + }; + Wire.onRequest(requestHandler); + + // Method 5: Class member function (using lambda wrapper) + class TemperatureSensor { + private: + float temperature; + public: + void updateTemperature() { + temperature = 25.5; // Read from actual sensor + } + + void sendTemperature() { + // Convert float to bytes and send + uint8_t* tempBytes = (uint8_t*)&temperature; + Wire.write(tempBytes, sizeof(float)); + } + + void setup() { + Wire.onRequest([this]() { + this->sendTemperature(); + }); + } + }; + +.. note:: + The ``onRequest`` callback is triggered when the I2C master requests data from this slave device. + Use ``Wire.write()`` inside the callback to send response data back to the master. slaveWrite ^^^^^^^^^^ diff --git a/libraries/Wire/examples/WireSlaveFunctionalCallback/WireSlaveFunctionalCallback.ino b/libraries/Wire/examples/WireSlaveFunctionalCallback/WireSlaveFunctionalCallback.ino new file mode 100644 index 00000000000..a18fd2f023e --- /dev/null +++ b/libraries/Wire/examples/WireSlaveFunctionalCallback/WireSlaveFunctionalCallback.ino @@ -0,0 +1,37 @@ +// This example demonstrates the use of functional callbacks with the Wire library +// for I2C slave communication. It shows how to handle requests and data reception + +#include "Wire.h" + +#define I2C_DEV_ADDR 0x55 + +uint32_t i = 0; + +void setup() { + Serial.begin(115200); + Serial.setDebugOutput(true); + + Wire.onRequest([]() { + Wire.print(i++); + Wire.print(" Packets."); + Serial.println("onRequest"); + }); + + Wire.onReceive([](int len) { + Serial.printf("onReceive[%d]: ", len); + while (Wire.available()) { + Serial.write(Wire.read()); + } + Serial.println(); + }); + + Wire.begin((uint8_t)I2C_DEV_ADDR); + +#if CONFIG_IDF_TARGET_ESP32 + char message[64]; + snprintf(message, 64, "%lu Packets.", i++); + Wire.slaveWrite((uint8_t *)message, strlen(message)); +#endif +} + +void loop() {} diff --git a/libraries/Wire/examples/WireSlaveFunctionalCallback/ci.json b/libraries/Wire/examples/WireSlaveFunctionalCallback/ci.json new file mode 100644 index 00000000000..3c877975d62 --- /dev/null +++ b/libraries/Wire/examples/WireSlaveFunctionalCallback/ci.json @@ -0,0 +1,5 @@ +{ + "requires": [ + "CONFIG_SOC_I2C_SUPPORT_SLAVE=y" + ] +} diff --git a/libraries/Wire/src/Wire.cpp b/libraries/Wire/src/Wire.cpp index 34c814b5117..cda098d2d5b 100644 --- a/libraries/Wire/src/Wire.cpp +++ b/libraries/Wire/src/Wire.cpp @@ -48,7 +48,7 @@ TwoWire::TwoWire(uint8_t bus_num) #endif #if SOC_I2C_SUPPORT_SLAVE , - is_slave(false), user_onRequest(NULL), user_onReceive(NULL) + is_slave(false), user_onRequest(nullptr), user_onReceive(nullptr) #endif /* SOC_I2C_SUPPORT_SLAVE */ { } @@ -596,14 +596,14 @@ void TwoWire::flush() { //i2cFlush(num); // cleanup } -void TwoWire::onReceive(void (*function)(int)) { +void TwoWire::onReceive(const std::function &function) { #if SOC_I2C_SUPPORT_SLAVE user_onReceive = function; #endif } // sets function called on slave read -void TwoWire::onRequest(void (*function)(void)) { +void TwoWire::onRequest(const std::function &function) { #if SOC_I2C_SUPPORT_SLAVE user_onRequest = function; #endif diff --git a/libraries/Wire/src/Wire.h b/libraries/Wire/src/Wire.h index b84aa5b2131..9cebdfaa304 100644 --- a/libraries/Wire/src/Wire.h +++ b/libraries/Wire/src/Wire.h @@ -48,10 +48,6 @@ #ifndef I2C_BUFFER_LENGTH #define I2C_BUFFER_LENGTH 128 // Default size, if none is set using Wire::setBuffersize(size_t) #endif -#if SOC_I2C_SUPPORT_SLAVE -typedef void (*user_onRequest)(void); -typedef void (*user_onReceive)(uint8_t *, int); -#endif /* SOC_I2C_SUPPORT_SLAVE */ class TwoWire : public HardwareI2C { protected: @@ -77,8 +73,8 @@ class TwoWire : public HardwareI2C { private: #if SOC_I2C_SUPPORT_SLAVE bool is_slave; - void (*user_onRequest)(void); - void (*user_onReceive)(int); + std::function user_onRequest; + std::function user_onReceive; static void onRequestService(uint8_t, void *); static void onReceiveService(uint8_t, uint8_t *, size_t, bool, void *); #endif /* SOC_I2C_SUPPORT_SLAVE */ @@ -116,8 +112,8 @@ class TwoWire : public HardwareI2C { size_t requestFrom(uint8_t address, size_t len, bool stopBit) override; size_t requestFrom(uint8_t address, size_t len) override; - void onReceive(void (*)(int)) override; - void onRequest(void (*)(void)) override; + void onReceive(const std::function &) override; + void onRequest(const std::function &) override; //call setPins() first, so that begin() can be called without arguments from libraries bool setPins(int sda, int scl); From 1f0d4b5dc0d6008583ac9527907325b9f2506caa Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:17:56 -0300 Subject: [PATCH 3/3] ci(gitlab): Initial GitLab setup (#11577) * ci(gitlab): Initial GitLab setup * fix(version): Add to version update script * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- .github/CODEOWNERS | 1 + .github/scripts/update-version.sh | 5 +++++ .gitlab-ci.yml | 25 +++++++++++++++++++++++++ .gitlab/workflows/common.yml | 26 ++++++++++++++++++++++++++ .gitlab/workflows/sample.yml | 6 ++++++ 5 files changed, 63 insertions(+) create mode 100644 .gitlab-ci.yml create mode 100644 .gitlab/workflows/common.yml create mode 100644 .gitlab/workflows/sample.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 75a2b46d619..64d241ba20a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -11,6 +11,7 @@ # CI /.github/ @lucasssvaz @me-no-dev @P-R-O-C-H-Y +/.gitlab/ @lucasssvaz /tests/ @lucasssvaz @P-R-O-C-H-Y # Tools diff --git a/.github/scripts/update-version.sh b/.github/scripts/update-version.sh index 622f2fe8ff8..59a95d01105 100755 --- a/.github/scripts/update-version.sh +++ b/.github/scripts/update-version.sh @@ -45,6 +45,11 @@ cat docs/conf_common.py | \ sed "s/.. |version| replace:: .*/.. |version| replace:: $ESP_ARDUINO_VERSION/g" | \ sed "s/.. |idf_version| replace:: .*/.. |idf_version| replace:: $ESP_IDF_VERSION/g" > docs/__conf_common.py && mv docs/__conf_common.py docs/conf_common.py +echo "Updating .gitlab/workflows/common.yml..." +cat .gitlab/workflows/common.yml | \ +sed "s/ESP_IDF_VERSION:.*/ESP_IDF_VERSION: \"$ESP_IDF_VERSION\"/g" | \ +sed "s/ESP_ARDUINO_VERSION:.*/ESP_ARDUINO_VERSION: \"$ESP_ARDUINO_VERSION\"/g" > .gitlab/workflows/__common.yml && mv .gitlab/workflows/__common.yml .gitlab/workflows/common.yml + echo "Updating cores/esp32/esp_arduino_version.h..." cat cores/esp32/esp_arduino_version.h | \ sed "s/#define ESP_ARDUINO_VERSION_MAJOR.*/#define ESP_ARDUINO_VERSION_MAJOR $ESP_ARDUINO_VERSION_MAJOR/g" | \ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000000..89a45022bc2 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,25 @@ +workflow: + rules: + # Disable those non-protected push triggered pipelines + - if: '$CI_COMMIT_REF_NAME != "master" && $CI_COMMIT_BRANCH !~ /^release\/v/ && $CI_COMMIT_TAG !~ /^\d+\.\d+(\.\d+)?($|-)/ && $CI_PIPELINE_SOURCE == "push"' + when: never + # when running merged result pipelines, CI_COMMIT_SHA represents the temp commit it created. + # Please use PIPELINE_COMMIT_SHA at all places that require a commit sha of the original commit. + - if: $CI_OPEN_MERGE_REQUESTS != null + variables: + PIPELINE_COMMIT_SHA: $CI_MERGE_REQUEST_SOURCE_BRANCH_SHA + IS_MR_PIPELINE: 1 + - if: $CI_OPEN_MERGE_REQUESTS == null + variables: + PIPELINE_COMMIT_SHA: $CI_COMMIT_SHA + IS_MR_PIPELINE: 0 + - if: '$CI_PIPELINE_SOURCE == "schedule"' + variables: + IS_SCHEDULED_RUN: "true" + - when: always + +# Place the default settings in `.gitlab/workflows/common.yml` instead + +include: + - ".gitlab/workflows/common.yml" + - ".gitlab/workflows/sample.yml" diff --git a/.gitlab/workflows/common.yml b/.gitlab/workflows/common.yml new file mode 100644 index 00000000000..9086da018ab --- /dev/null +++ b/.gitlab/workflows/common.yml @@ -0,0 +1,26 @@ +##################### +# Default Variables # +##################### + +stages: + - pre_check + - build + - test + - result + +variables: + ESP_IDF_VERSION: "5.4" + ESP_ARDUINO_VERSION: "3.2.1" + +############# +# `default` # +############# + +default: + retry: + max: 2 + when: + # In case of a runner failure we could hop to another one, or a network error could go away. + - runner_system_failure + # Job execution timeout may be caused by a network issue. + - job_execution_timeout diff --git a/.gitlab/workflows/sample.yml b/.gitlab/workflows/sample.yml new file mode 100644 index 00000000000..32b6fce042d --- /dev/null +++ b/.gitlab/workflows/sample.yml @@ -0,0 +1,6 @@ +hello-world: + stage: test + script: + - echo "Hello, World from GitLab CI!" + rules: + - if: $CI_PIPELINE_SOURCE == "push"