Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions cores/esp32/HardwareI2C.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include <inttypes.h>
#include "Stream.h"
#include <functional>

class HardwareI2C : public Stream {
public:
Expand All @@ -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<void(int)>&) = 0;
virtual void onRequest(const std::function<void()>&) = 0;
};
135 changes: 131 additions & 4 deletions docs/en/api/i2c.rst
Original file line number Diff line number Diff line change
Expand Up @@ -347,21 +347,148 @@ 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
^^^^^^^^^^

Expand Down
Original file line number Diff line number Diff line change
@@ -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() {}
5 changes: 5 additions & 0 deletions libraries/Wire/examples/WireSlaveFunctionalCallback/ci.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"requires": [
"CONFIG_SOC_I2C_SUPPORT_SLAVE=y"
]
}
6 changes: 3 additions & 3 deletions libraries/Wire/src/Wire.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
{
}
Expand Down Expand Up @@ -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
Expand Down
12 changes: 4 additions & 8 deletions libraries/Wire/src/Wire.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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<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 */
Expand Down Expand Up @@ -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<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);
Expand Down
Loading