Merge branch 'master' into release/v3.3.x

This commit is contained in:
Sugar Glider 2025-07-16 09:10:59 -03:00 committed by GitHub
commit 3f32903125
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 552 additions and 18 deletions

1
.github/CODEOWNERS vendored
View file

@ -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

View file

@ -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" | \

25
.gitlab-ci.yml Normal file
View file

@ -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"

View file

@ -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

View file

@ -0,0 +1,6 @@
hello-world:
stage: test
script:
- echo "Hello, World from GitLab CI!"
rules:
- if: $CI_PIPELINE_SOURCE == "push"

View file

@ -20,6 +20,7 @@
#include <inttypes.h>
#include "Stream.h"
#include <functional>
class HardwareI2C : public Stream {
public:
@ -36,6 +37,7 @@ public:
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<void(int)> &) = 0;
virtual void onRequest(const std::function<void()> &) = 0;
};

View file

@ -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<void(int)>& 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<void(int)> 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( void (*)(void) );
void onRequest(const std::function<void()>& 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
// 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<void()> 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
^^^^^^^^^^

View file

@ -0,0 +1,157 @@
/*
SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
SPDX-License-Identifier: Apache-2.0
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.
ESP32 Lambda FunctionalInterrupt Example
========================================
This example demonstrates how to use lambda functions with FunctionalInterrupt
for GPIO pin interrupt callbacks on ESP32. It shows CHANGE mode detection
with LED toggle functionality and proper debouncing.
Hardware Setup:
- Use BOOT Button or connect a button between BUTTON_PIN and GND (with internal pullup)
- Use Builtin Board LED or connect an LED with resistor to GPIO 2 (LED_PIN)
Features Demonstrated:
1. CHANGE mode lambda to detect both RISING and FALLING edges
2. LED toggle on button press (FALLING edge)
3. Edge type detection using digitalRead() within ISR
4. Hardware debouncing with configurable timeout
IMPORTANT NOTE ABOUT ESP32 INTERRUPT BEHAVIOR:
- Only ONE interrupt handler can be attached per GPIO pin at a time
- Calling attachInterrupt() on a pin that already has an interrupt will override the previous one
- This applies regardless of edge type (RISING, FALLING, CHANGE)
- If you need both RISING and FALLING detection on the same pin, use CHANGE mode
and determine the edge type within your handler by reading the pin state
*/
#include <Arduino.h>
#include <FunctionalInterrupt.h>
// Pin definitions
#define BUTTON_PIN BOOT_PIN // BOOT BUTTON - change as needed
#ifdef LED_BUILTIN
#define LED_PIN LED_BUILTIN
#else
#warning Using LED_PIN = GPIO 2 as default - change as needed
#define LED_PIN 2 // change as needed
#endif
// Global variables for interrupt handling (volatile for ISR safety)
volatile uint32_t buttonPressCount = 0;
volatile uint32_t buttonReleaseCount = 0;
volatile bool buttonPressed = false;
volatile bool buttonReleased = false;
volatile bool ledState = false;
volatile bool ledStateChanged = false; // Flag to indicate LED needs updating
// Debouncing variables (volatile for ISR safety)
volatile unsigned long lastButtonInterruptTime = 0;
const unsigned long DEBOUNCE_DELAY_MS = 50; // 50ms debounce delay
// State-based debouncing to prevent hysteresis issues
volatile bool lastButtonState = HIGH; // Track last stable state (HIGH = released)
// Global lambda function (declared at file scope) - ISR in IRAM
IRAM_ATTR std::function<void()> changeModeLambda = []() {
// Simple debouncing: check if enough time has passed since last interrupt
unsigned long currentTime = millis();
if (currentTime - lastButtonInterruptTime < DEBOUNCE_DELAY_MS) {
return; // Ignore this interrupt due to bouncing
}
// Read current pin state to determine edge type
bool currentState = digitalRead(BUTTON_PIN);
// State-based debouncing: only process if state actually changed
if (currentState == lastButtonState) {
return; // No real state change, ignore (hysteresis/noise)
}
// Update timing and state
lastButtonInterruptTime = currentTime;
lastButtonState = currentState;
if (currentState == LOW) {
// FALLING edge detected (button pressed) - set flag for main loop
// volatile variables require use of temporary value transfer
uint32_t temp = buttonPressCount + 1;
buttonPressCount = temp;
buttonPressed = true;
ledStateChanged = true; // Signal main loop to toggle LED
} else {
// RISING edge detected (button released) - set flag for main loop
// volatile variables require use of temporary value transfer
uint32_t temp = buttonReleaseCount + 1;
buttonReleaseCount = temp;
buttonReleased = true;
}
};
void setup() {
Serial.begin(115200);
delay(1000); // Allow serial monitor to connect
Serial.println("ESP32 Lambda FunctionalInterrupt Example");
Serial.println("========================================");
// Configure pins
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// CHANGE mode lambda to handle both RISING and FALLING edges
// This toggles the LED on button press (FALLING edge)
Serial.println("Setting up CHANGE mode lambda for LED toggle");
// Use the global lambda function
attachInterrupt(BUTTON_PIN, changeModeLambda, CHANGE);
Serial.println();
Serial.printf("Lambda interrupt configured on Pin %d (CHANGE mode)\r\n", BUTTON_PIN);
Serial.printf("Debounce delay: %lu ms\r\n", DEBOUNCE_DELAY_MS);
Serial.println();
Serial.println("Press the button to toggle the LED!");
Serial.println("Button press (FALLING edge) will toggle the LED.");
Serial.println("Button release (RISING edge) will be detected and reported.");
Serial.println("Button includes debouncing to prevent mechanical bounce issues.");
Serial.println();
}
void loop() {
// Handle LED state changes (ISR-safe approach)
if (ledStateChanged) {
ledStateChanged = false;
ledState = !ledState; // Toggle LED state in main loop
digitalWrite(LED_PIN, ledState);
}
// Check for button presses
if (buttonPressed) {
buttonPressed = false;
Serial.printf("==> Button PRESSED! Count: %lu, LED: %s (FALLING edge)\r\n", buttonPressCount, ledState ? "ON" : "OFF");
}
// Check for button releases
if (buttonReleased) {
buttonReleased = false;
Serial.printf("==> Button RELEASED! Count: %lu (RISING edge)\r\n", buttonReleaseCount);
}
delay(10);
}

View file

@ -0,0 +1,147 @@
# ESP32 Lambda FunctionalInterrupt Example
This example demonstrates how to use lambda functions with FunctionalInterrupt for GPIO pin interrupt callbacks on ESP32. It shows CHANGE mode detection with LED toggle functionality and proper debouncing.
## Features Demonstrated
1. **CHANGE mode lambda** to detect both RISING and FALLING edges
2. **LED toggle on button press** (FALLING edge)
3. **Edge type detection** using digitalRead() within ISR
4. **Hardware debouncing** with configurable timeout
5. **IRAM_ATTR lambda declaration** for optimal ISR performance in RAM
## Hardware Setup
- Use BOOT Button or connect a button between BUTTON_PIN and GND (with internal pullup)
- Use Builtin Board LED (no special hardware setup) or connect an LED with resistor to GPIO assigned as LED_PIN.\
Some boards have an RGB LED that needs no special hardware setup to work as a simple white on/off LED.
```
ESP32 Board Button/LED
----------- ---------
BOOT_PIN ------------ [BUTTON] ---- GND
LED_PIN --------------- [LED] ----- GND
¦
[330O] (*) Only needed when using an external LED attached to the GPIO.
¦
3V3
```
## Important ESP32 Interrupt Behavior
**CRITICAL:** Only ONE interrupt handler can be attached per GPIO pin at a time on ESP32.
- Calling `attachInterrupt()` on a pin that already has an interrupt will **override** the previous one
- This applies regardless of edge type (RISING, FALLING, CHANGE)
- If you need both RISING and FALLING detection on the same pin, use **CHANGE mode** and determine the edge type within your handler by reading the pin state
## Code Overview
This example demonstrates a simple CHANGE mode lambda interrupt that:
- **Detects both button press and release** using a single interrupt handler
- **Toggles LED only on button press** (FALLING edge)
- **Reports both press and release events** to Serial output
- **Uses proper debouncing** to prevent switch bounce issues
- **Implements minimal lambda captures** for simplicity
## Lambda Function Pattern
### CHANGE Mode Lambda with IRAM Declaration
```cpp
// Global lambda declared with IRAM_ATTR for optimal ISR performance
IRAM_ATTR std::function<void()> changeModeLambda = []() {
// Debouncing check
unsigned long currentTime = millis();
if (currentTime - lastButtonInterruptTime < DEBOUNCE_DELAY_MS) {
return; // Ignore bouncing
}
// Determine edge type
bool currentState = digitalRead(BUTTON_PIN);
if (currentState == lastButtonState) {
return; // No real state change
}
// Update state and handle edges
lastButtonInterruptTime = currentTime;
lastButtonState = currentState;
if (currentState == LOW) {
// Button pressed (FALLING edge)
buttonPressCount++;
buttonPressed = true;
ledStateChanged = true; // Signal LED toggle
} else {
// Button released (RISING edge)
buttonReleaseCount++;
buttonReleased = true;
}
};
attachInterrupt(BUTTON_PIN, changeModeLambda, CHANGE);
```
## Key Concepts
### Edge Detection in CHANGE Mode
```cpp
if (digitalRead(pin) == LOW) {
// FALLING edge detected (button pressed)
} else {
// RISING edge detected (button released)
}
```
### Debouncing Strategy
This example implements dual-layer debouncing:
1. **Time-based**: Ignores interrupts within 50 ms of previous one
2. **State-based**: Only processes actual state changes
### Main Loop Processing
```cpp
void loop() {
// Handle LED changes safely outside ISR
if (ledStateChanged) {
ledStateChanged = false;
ledState = !ledState;
digitalWrite(LED_PIN, ledState);
}
// Report button events
if (buttonPressed) {
// Handle press event
}
if (buttonReleased) {
// Handle release event
}
}
```
## Expected Output
```
ESP32 Lambda FunctionalInterrupt Example
========================================
Setting up CHANGE mode lambda for LED toggle
Lambda interrupt configured on Pin 0 (CHANGE mode)
Debounce delay: 50 ms
Press the button to toggle the LED!
Button press (FALLING edge) will toggle the LED.
Button release (RISING edge) will be detected and reported.
Button includes debouncing to prevent mechanical bounce issues.
==> Button PRESSED! Count: 1, LED: ON (FALLING edge)
==> Button RELEASED! Count: 1 (RISING edge)
==> Button PRESSED! Count: 2, LED: OFF (FALLING edge)
==> Button RELEASED! Count: 2 (RISING edge)
```
## Pin Configuration
The example uses these default pins:
- `BUTTON_PIN`: BOOT_PIN (automatically assigned by the Arduino Core)
- `LED_PIN`: LED_BUILTIN (may not be available for your board - please verify it)

View file

@ -13,7 +13,7 @@
// limitations under the License.
/*
This example create 6 on-off light endpoint that share the same onChangeOnOff() callback code.
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:

View file

@ -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() {}

View file

@ -0,0 +1,5 @@
{
"requires": [
"CONFIG_SOC_I2C_SUPPORT_SLAVE=y"
]
}

View file

@ -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<void(int)> &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<void()> &function) {
#if SOC_I2C_SUPPORT_SLAVE
user_onRequest = function;
#endif

View file

@ -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 @@ protected:
private:
#if SOC_I2C_SUPPORT_SLAVE
bool is_slave;
void (*user_onRequest)(void);
void (*user_onReceive)(int);
std::function<void()> user_onRequest;
std::function<void(int)> 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 @@ public:
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<void(int)> &) override;
void onRequest(const std::function<void()> &) override;
//call setPins() first, so that begin() can be called without arguments from libraries
bool setPins(int sda, int scl);