feat(wire): std::functional Wire slave callback functions (#11582)

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
This commit is contained in:
Sugar Glider 2025-07-12 02:12:36 -03:00 committed by GitHub
parent 4ee17dea04
commit 0a45a06142
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 184 additions and 17 deletions

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,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);