api callbacks - Alteriom/painlessMesh GitHub Wiki
painlessMesh provides several callback functions to handle mesh events and messages. These callbacks allow your application to respond to network changes, receive messages, and handle various mesh events.
Called when a message is received from another node.
void mesh.onReceive(std::function<void(uint32_t from, String& msg)> callback)Parameters:
-
from- Node ID of the sender -
msg- The received message string
Example:
mesh.onReceive([](uint32_t from, String& msg) {
Serial.printf("Received from %u: %s\n", from, msg.c_str());
// Parse message and take action
if (msg == "turn_on_led") {
digitalWrite(LED_PIN, HIGH);
}
});Advanced Usage:
void receivedCallback(uint32_t from, String& msg) {
// Log the message
Serial.printf("Message from node %u: %s\n", from, msg.c_str());
// Parse JSON messages
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, msg);
if (!error) {
String command = doc["command"];
int value = doc["value"];
executeCommand(command, value);
}
// Note: since v2.0.0 you no longer need to hand-roll acknowledgments.
// Pass a delivery callback to sendSingle()/sendBroadcast() instead —
// the receiving node acknowledges automatically.
}
mesh.onReceive(&receivedCallback);sendSingle() and sendBroadcast() accept an optional
painlessmesh::ack::deliveryCallback_t that reports per-node delivery:
typedef std::function<void(uint32_t nodeId, bool delivered, uint32_t latencyMs)>
deliveryCallback_t;Parameters:
-
nodeId- The destination node this result refers to -
delivered-truewhen the node acknowledged the message,falsewhen the acknowledgment timed out -
latencyMs- Round-trip time when delivered, otherwise the timeout value
Example:
mesh.sendSingle(gatewayId, msg,
[](uint32_t nodeId, bool delivered, uint32_t latencyMs) {
if (delivered)
Serial.printf("Node %u confirmed in %u ms\n", nodeId, latencyMs);
else
Serial.printf("Delivery to %u timed out\n", nodeId);
}, 5000 /* ackTimeoutMs */);The callback fires exactly once per expected destination, from within
mesh.update(). See Core API for
details.
Type-safe callback for custom package types (requires painlessMesh plugin system).
void mesh.onPackage(uint8_t packageType, std::function<bool(protocol::Variant& variant)> callback)Parameters:
-
packageType- The type ID of the package -
callback- Function to handle the package, returns true if handled
Example:
// Define custom package
class SensorPackage : public painlessmesh::plugin::BroadcastPackage {
public:
float temperature;
float humidity;
// ... implementation
};
// Register handler
mesh.onPackage(200, [](protocol::Variant& variant) {
SensorPackage sensor = variant.to<SensorPackage>();
Serial.printf("Sensor data: T=%.1f°C, H=%.1f%%\n",
sensor.temperature, sensor.humidity);
return true; // Package handled
});Called when a new node connects to the mesh.
void mesh.onNewConnection(std::function<void(uint32_t nodeId)> callback)Parameters:
-
nodeId- The node ID of the newly connected node
Example:
mesh.onNewConnection([](uint32_t nodeId) {
Serial.printf("New node connected: %u\n", nodeId);
Serial.printf("Total nodes in mesh: %d\n", mesh.getNodeList().size() + 1);
// Send welcome message to new node
String welcome = "Welcome to the mesh!";
mesh.sendSingle(nodeId, welcome);
// Update network topology display
updateNetworkDisplay();
});Called when the mesh topology changes (nodes connect or disconnect).
void mesh.onChangedConnections(std::function<void()> callback)Example:
mesh.onChangedConnections([]() {
Serial.println("Mesh topology changed");
auto nodes = mesh.getNodeList();
Serial.printf("Currently connected to %d nodes: ", nodes.size());
for (auto nodeId : nodes) {
Serial.printf("%u ", nodeId);
}
Serial.println();
// Update routing tables or network status
updateNetworkMap();
});Called when a connection to a node is lost.
void mesh.onDroppedConnection(std::function<void(uint32_t nodeId)> callback)Parameters:
-
nodeId- The node ID of the disconnected node
Example:
mesh.onDroppedConnection([](uint32_t nodeId) {
Serial.printf("Lost connection to node: %u\n", nodeId);
// Clean up any node-specific data
removeNodeData(nodeId);
// Check if this was a critical node
if (isCriticalNode(nodeId)) {
Serial.println("WARNING: Critical node disconnected!");
triggerAlert();
}
});Called when the node's time is adjusted for mesh synchronization.
void mesh.onNodeTimeAdjusted(std::function<void(int32_t offset)> callback)Parameters:
-
offset- Time adjustment in microseconds
Example:
mesh.onNodeTimeAdjusted([](int32_t offset) {
Serial.printf("Time adjusted by %d microseconds\n", offset);
// Log significant time adjustments
if (abs(offset) > 1000000) { // More than 1 second
Serial.println("WARNING: Large time adjustment detected");
}
// Update any time-sensitive operations
recalibrateTimers(offset);
});Called when delay measurement to a node is completed.
void mesh.onNodeDelayReceived(std::function<void(uint32_t nodeId, int32_t delay)> callback)Parameters:
-
nodeId- The node ID -
delay- Round-trip delay in microseconds
Example:
mesh.onNodeDelayReceived([](uint32_t nodeId, int32_t delay) {
Serial.printf("Delay to node %u: %d µs\n", nodeId, delay);
// Track network performance
networkDelays[nodeId] = delay;
// Alert on high latency
if (delay > 100000) { // More than 100ms
Serial.printf("HIGH LATENCY to node %u: %d µs\n", nodeId, delay);
}
});Generic event handler for various mesh events.
void mesh.onEvent(std::function<void(painlessmesh::protocol::MeshEvent event,
uint32_t nodeId,
String data)> callback)Events:
-
NODE_JOINED- New node joined the mesh -
NODE_LEFT- Node left the mesh -
MESSAGE_SENT- Message was sent -
MESSAGE_RECEIVED- Message was received -
TIME_SYNC- Time synchronization occurred
Example:
mesh.onEvent([](painlessmesh::protocol::MeshEvent event, uint32_t nodeId, String data) {
switch(event) {
case painlessmesh::protocol::NODE_JOINED:
Serial.printf("Event: Node %u joined\n", nodeId);
break;
case painlessmesh::protocol::NODE_LEFT:
Serial.printf("Event: Node %u left\n", nodeId);
break;
case painlessmesh::protocol::MESSAGE_SENT:
Serial.printf("Event: Message sent to %u\n", nodeId);
break;
case painlessmesh::protocol::TIME_SYNC:
Serial.printf("Event: Time sync with %u\n", nodeId);
break;
}
});Filter messages by type or content:
mesh.onReceive([](uint32_t from, String& msg) {
// Only process sensor messages
if (msg.startsWith("SENSOR:")) {
processSensorData(from, msg.substring(7));
}
// Only process commands from authorized nodes
else if (msg.startsWith("CMD:") && isAuthorizedNode(from)) {
executeCommand(from, msg.substring(4));
}
});Update application state based on network events:
enum NodeRole { SENSOR, CONTROLLER, GATEWAY };
NodeRole myRole = SENSOR;
mesh.onNewConnection([](uint32_t nodeId) {
// Adjust role based on network size
int networkSize = mesh.getNodeList().size() + 1;
if (networkSize == 1) {
myRole = GATEWAY; // First node becomes gateway
} else if (networkSize <= 3) {
myRole = CONTROLLER; // Early nodes become controllers
} else {
myRole = SENSOR; // Later nodes become sensors
}
Serial.printf("Role changed to: %s\n", roleToString(myRole));
});Robust error handling in callbacks:
mesh.onReceive([](uint32_t from, String& msg) {
try {
// Parse and process message
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, msg);
if (error) {
Serial.printf("JSON parse error from node %u: %s\n",
from, error.c_str());
return;
}
// Validate message content
if (!doc.containsKey("type")) {
Serial.printf("Invalid message from node %u: missing type\n", from);
return;
}
String type = doc["type"];
if (type == "sensor") {
processSensorMessage(doc);
} else if (type == "command") {
processCommandMessage(doc);
} else {
Serial.printf("Unknown message type from node %u: %s\n",
from, type.c_str());
}
} catch (const std::exception& e) {
Serial.printf("Exception processing message from node %u: %s\n",
from, e.what());
}
});Monitor callback performance:
mesh.onReceive([](uint32_t from, String& msg) {
uint32_t startTime = micros();
// Process message
processMessage(from, msg);
uint32_t processingTime = micros() - startTime;
// Log slow processing
if (processingTime > 10000) { // More than 10ms
Serial.printf("Slow message processing: %u µs\n", processingTime);
}
});Callbacks should execute quickly to avoid blocking the mesh:
// ❌ Bad - blocking operation
mesh.onReceive([](uint32_t from, String& msg) {
delay(1000); // Blocks mesh operations!
processMessage(msg);
});
// ✅ Good - queue for later processing
std::queue<String> messageQueue;
mesh.onReceive([](uint32_t from, String& msg) {
messageQueue.push(msg); // Quick operation
});
void loop() {
mesh.update();
// Process queued messages
if (!messageQueue.empty()) {
processMessage(messageQueue.front());
messageQueue.pop();
}
}Always validate input and handle errors:
mesh.onReceive([](uint32_t from, String& msg) {
if (msg.length() == 0) {
Serial.println("Received empty message");
return;
}
if (msg.length() > MAX_MESSAGE_SIZE) {
Serial.printf("Message too large: %d bytes\n", msg.length());
return;
}
// Process valid message
processMessage(from, msg);
});Be careful with lambda captures to avoid memory issues:
// ❌ Dangerous - captures local variable by reference
void setupMesh() {
String localData = "important data";
mesh.onReceive([&localData](uint32_t from, String& msg) {
// localData may be invalid when callback executes!
processMessage(msg, localData);
});
} // localData goes out of scope
// ✅ Safe - capture by value or use global/member variables
String globalData = "important data";
mesh.onReceive([](uint32_t from, String& msg) {
processMessage(msg, globalData);
});Don't send messages from within message callbacks without guards:
// ❌ Dangerous - potential infinite loop
mesh.onReceive([](uint32_t from, String& msg) {
if (msg == "ping") {
mesh.sendSingle(from, "pong"); // Could cause ping-pong loop!
}
});
// ✅ Better - add guards and limits
int maxPingResponses = 3;
std::map<uint32_t, int> pingCounts;
mesh.onReceive([](uint32_t from, String& msg) {
if (msg == "ping" && pingCounts[from] < maxPingResponses) {
mesh.sendSingle(from, "pong");
pingCounts[from]++;
}
});See also:
- Core API - Complete method reference
- Configuration - Mesh setup options
- Alteriom Packages - Built-in package types