api core api - Alteriom/painlessMesh GitHub Wiki
This document provides a comprehensive reference for the painlessMesh core API. All functions and classes are part of the painlessmesh namespace unless otherwise specified.
The main class for mesh network functionality.
painlessMesh mesh;No parameters required. Creates a mesh instance ready for initialization.
Initialize the mesh network.
void init(TSTRING meshPrefix, TSTRING meshPassword, uint16_t port);
void init(TSTRING meshPrefix, TSTRING meshPassword, Scheduler* userScheduler, uint16_t port);Parameters:
-
meshPrefix- Network name (SSID prefix) -
meshPassword- Network password -
userScheduler- Optional external TaskScheduler instance -
port- TCP port for mesh communication (default: 5555)
Example:
Scheduler userScheduler;
mesh.init("MyMesh", "password123", &userScheduler, 5555);Main mesh update loop. Call this in your loop() function.
void update();Example:
void loop() {
mesh.update(); // Always call this
}Send a message to all nodes in the mesh.
bool sendBroadcast(TSTRING msg, bool includeSelf = false);Parameters:
-
msg- Message to broadcast -
includeSelf- Whether to send to self (default: false)
Returns: true if message was queued successfully
Example:
String msg = "Hello everyone!";
bool sent = mesh.sendBroadcast(msg);
if (!sent) {
Serial.println("Failed to send broadcast");
}Send a message to a specific node.
bool sendSingle(uint32_t destId, TSTRING msg);Parameters:
-
destId- Target node ID -
msg- Message to send
Returns: true if message was queued successfully
Example:
uint32_t targetNode = 123456789;
String msg = "Hello specific node!";
bool sent = mesh.sendSingle(targetNode, msg);Both send functions accept an optional delivery callback. When provided, the message is tagged with a unique id, the receiving node automatically replies with an acknowledgment (protocol type 630, routed across multiple hops), and the callback reports the result per destination node:
// Callback type (painlessmesh::ack::deliveryCallback_t)
void(uint32_t nodeId, bool delivered, uint32_t latencyMs);
bool sendSingle(uint32_t destId, TSTRING msg,
painlessmesh::ack::deliveryCallback_t ackCallback,
uint32_t ackTimeoutMs = 5000);
// includeSelf and ackCallback must be passed explicitly on this overload
bool sendBroadcast(TSTRING msg, bool includeSelf,
painlessmesh::ack::deliveryCallback_t ackCallback,
uint32_t ackTimeoutMs = 5000);Behavior:
- The callback fires exactly once per expected node:
delivered = truewith the measured round-trip latency, ordelivered = falseafterackTimeoutMselapsed. - For broadcasts, the set of expected nodes is snapshotted from the mesh layout at send time; the local node never acknowledges itself. Receiving nodes stagger their broadcast ACK replies by up to 50 ms so large meshes do not converge simultaneous ACK bursts on the sender.
- Timeouts are processed inside
mesh.update()— no blocking waits. - Passing
nullptras the callback behaves exactly like the plain overloads (zero wire and CPU overhead). - At most
PAINLESSMESH_MAX_PENDING_ACKS(default 32, build-time overridable) messages may await acknowledgment at once; beyond that the send is rejected (falsereturned, nothing sent). CheckpendingAcks()before bursts. - Message ids are seeded randomly at
init()so a delayed ACK from before a reboot cannot be mistaken for a fresh message's acknowledgment. - To combine priority with a delivery callback, use the
SendOptionsoverloads (below) — the separate priority and ack overloads remain for backwards compatibility but cannot express both in one call. - Power note: while any ack is outstanding, the scheduler polls timeouts
every
PAINLESSMESH_ACK_CHECK_INTERVAL_MS(default 100 ms) for up toackTimeoutMs. Battery/light-sleep nodes can override the interval at build time; when no ack is pending the poll task is fully disabled.
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);
});painlessmesh::SendOptions bundles priority and delivery confirmation into
one composable struct; every other sendSingle() / sendBroadcast()
overload is a thin wrapper over these two:
struct painlessmesh::SendOptions {
uint8_t priority = PRIORITY_NORMAL; // PRIORITY_CRITICAL(0) … PRIORITY_LOW(3)
painlessmesh::ack::deliveryCallback_t ackCallback = nullptr;
uint32_t ackTimeoutMs = 5000;
};
bool sendSingle(uint32_t destId, TSTRING msg,
const painlessmesh::SendOptions& options);
bool sendBroadcast(TSTRING msg, const painlessmesh::SendOptions& options,
bool includeSelf = false);painlessmesh::SendOptions options;
options.priority = painlessmesh::protocol::PRIORITY_HIGH;
options.ackCallback = [](uint32_t nodeId, bool delivered, uint32_t latencyMs) {
if (!delivered) Serial.printf("Node %u missed the alarm\n", nodeId);
};
mesh.sendBroadcast(alarmMsg, options);Priority is carried across hops in version 2.0. The priority level is
serialized on the wire (as "prio", only when it deviates from
PRIORITY_NORMAL, so default sends carry zero overhead) and every
forwarding node re-enqueues the package at the sender's priority.
Process pending acknowledgment timeouts (non-blocking poll).
size_t checkAcks();Returns: Number of messages still awaiting acknowledgment. Timeout
processing also happens automatically inside mesh.update(), so calling
this manually is only useful in tight loops.
size_t pendingAcks() const;Returns: Number of messages still awaiting delivery acknowledgment.
Send a custom package through the plugin system.
bool sendPackage(const protocol::PackageInterface* pkg);Parameters:
-
pkg- Pointer to package object
Returns: true if package was sent successfully
Example:
SensorPackage sensor;
sensor.temperature = 25.0;
sensor.humidity = 60.0;
bool sent = mesh.sendPackage(&sensor);Register a handler for specific package types.
void onPackage(int type, std::function<bool(protocol::Variant&)> function);Parameters:
-
type- Package type ID to handle -
function- Handler function (returntrueto stop propagation)
Example:
mesh.onPackage(SENSOR_DATA, [](protocol::Variant& variant) {
SensorPackage sensor = variant.to<SensorPackage>();
Serial.printf("Temp: %.1f°C\n", sensor.temperature);
return false; // Don't stop propagation
});Get this node's unique identifier.
uint32_t getNodeId();Returns: 32-bit node ID
Example:
uint32_t myId = mesh.getNodeId();
Serial.printf("My node ID: %u\n", myId);Get list of all connected nodes.
std::list<uint32_t> getNodeList();Returns: List of node IDs
Example:
auto nodes = mesh.getNodeList();
Serial.printf("Connected to %d nodes\n", nodes.size());
for (auto nodeId : nodes) {
Serial.printf(" Node: %u\n", nodeId);
}Get mesh topology as JSON string.
TSTRING subConnectionJson(bool pretty = false);Parameters:
-
pretty- Whether to format JSON nicely (default: false)
Returns: JSON representation of mesh topology
Example:
String topology = mesh.subConnectionJson(true);
Serial.println("Mesh topology:");
Serial.println(topology);Get synchronized mesh time.
uint32_t getNodeTime();Returns: Time in microseconds since mesh start
Example:
uint32_t timestamp = mesh.getNodeTime();
uint32_t seconds = timestamp / 1000000;
Serial.printf("Mesh time: %u seconds\n", seconds);Add a task to the mesh scheduler.
// Recurring task
std::shared_ptr<Task> addTask(Scheduler& scheduler, unsigned long interval,
long iterations, std::function<void()> callback);
// One-time task
std::shared_ptr<Task> addTask(Scheduler& scheduler, std::function<void()> callback);Parameters:
-
scheduler- Scheduler instance -
interval- Task interval in milliseconds -
iterations- Number of iterations (TASK_FOREVERfor infinite) -
callback- Function to execute
Returns: Shared pointer to task
Example:
// Recurring task every 30 seconds
auto task = mesh.addTask(userScheduler, 30000, TASK_FOREVER, [](){
Serial.println("Periodic task executed");
});
// One-time task
mesh.addTask(userScheduler, [](){
Serial.println("One-time task executed");
});Set callback for receiving messages.
void onReceive(std::function<void(uint32_t from, String& msg)> callback);Parameters:
-
callback- Function to call when message received
Example:
mesh.onReceive([](uint32_t from, String& msg) {
Serial.printf("Received from %u: %s\n", from, msg.c_str());
});Set callback for new node connections.
void onNewConnection(std::function<void(uint32_t nodeId)> callback);Example:
mesh.onNewConnection([](uint32_t nodeId) {
Serial.printf("New node connected: %u\n", nodeId);
});Set callback for lost connections.
void onDroppedConnection(std::function<void(uint32_t nodeId)> callback);Example:
mesh.onDroppedConnection([](uint32_t nodeId) {
Serial.printf("Lost connection to: %u\n", nodeId);
});Set callback for topology changes.
void onChangedConnections(std::function<void()> callback);Example:
mesh.onChangedConnections([]() {
auto nodes = mesh.getNodeList();
Serial.printf("Topology changed. Now connected to %d nodes\n", nodes.size());
});Set callback for time synchronization events.
void 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);
});Measure network delay to a specific node.
bool startDelayMeas(uint32_t nodeId);Parameters:
-
nodeId- Target node for delay measurement
Returns: true if measurement started successfully
Example:
uint32_t targetNode = 123456789;
if (mesh.startDelayMeas(targetNode)) {
Serial.println("Delay measurement started");
}Set callback for delay measurement results.
void onNodeDelayReceived(std::function<void(uint32_t nodeId, int32_t delay)> callback);Parameters:
-
nodeId- Node that was measured -
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);
});Control debug message output.
void setDebugMsgTypes(uint16_t types);Parameters:
-
types- Bitwise combination of debug types
Debug Types:
#define ERROR 0x0001
#define STARTUP 0x0002
#define CONNECTION 0x0004
#define SYNC 0x0008
#define COMMUNICATION 0x0010
#define GENERAL 0x0020
#define MSG_TYPES 0x0040
#define REMOTE 0x0080Example:
// Enable error, startup, and connection messages
mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION);
// Enable all debug messages
mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION | SYNC |
COMMUNICATION | GENERAL | MSG_TYPES | REMOTE);Stop the mesh and clean up resources.
void stop();Example:
void shutdown() {
mesh.stop();
Serial.println("Mesh stopped");
}Cross-platform string type that works on both ESP32/ESP8266 and desktop platforms.
#if defined(ESP32) || defined(ESP8266)
typedef String TSTRING;
#else
typedef std::string TSTRING;
#endif// Task scheduler constants
#define TASK_FOREVER -1
#define TASK_ONCE 1
// Common intervals
#define TASK_SECOND 1000
#define TASK_MINUTE (60 * TASK_SECOND)
#define TASK_HOUR (60 * TASK_MINUTE)
// Default values
#define MESH_DEFAULT_PORT 5555
#define MAX_CONNECTIONS 4 // Platform dependent#include "painlessMesh.h"
#define MESH_PREFIX "MyMeshNetwork"
#define MESH_PASSWORD "secretPassword"
#define MESH_PORT 5555
Scheduler userScheduler;
painlessMesh mesh;
void setup() {
Serial.begin(115200);
// Set up callbacks
mesh.onReceive(&receivedCallback);
mesh.onNewConnection(&newConnectionCallback);
mesh.onChangedConnections(&changedConnectionCallback);
mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
// Initialize mesh
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
// Set debug level
mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION);
}
void loop() {
mesh.update();
}void receivedCallback(uint32_t from, String& msg) {
Serial.printf("Received from %u: %s\n", from, msg.c_str());
// Parse JSON if needed
DynamicJsonDocument doc(1024);
deserializeJson(doc, msg);
if (doc["type"] == "sensor") {
handleSensorData(doc);
} else if (doc["type"] == "command") {
handleCommand(doc);
}
}// Define custom package
class MyPackage : public painlessmesh::plugin::BroadcastPackage {
public:
float value = 0.0;
MyPackage() : BroadcastPackage(100) {} // Unique type ID
MyPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
value = jsonObj["value"];
}
JsonObject addTo(JsonObject&& jsonObj) const override {
jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
jsonObj["value"] = value;
return jsonObj;
}
};
// Register handler
mesh.onPackage(100, [](protocol::Variant& variant) {
MyPackage pkg = variant.to<MyPackage>();
Serial.printf("Received value: %.2f\n", pkg.value);
return false;
});
// Send package
MyPackage pkg;
pkg.value = 42.5;
mesh.sendPackage(&pkg);// Always check return values
if (!mesh.sendBroadcast(message)) {
Serial.println("Failed to send broadcast - check connections");
}
if (!mesh.sendSingle(nodeId, message)) {
Serial.println("Failed to send to specific node - node may be disconnected");
}void monitorConnections() {
auto nodes = mesh.getNodeList();
if (nodes.empty()) {
Serial.println("Warning: No mesh connections!");
} else {
Serial.printf("Connected to %d nodes\n", nodes.size());
}
}- Use
TSTRING::reserve()for strings that will grow - Implement
jsonObjectSize()accurately in custom packages - Monitor heap usage on ESP8266 (limited to ~80KB)
- Keep messages small to reduce network overhead
- Use binary data in JSON strings for large data
- Batch multiple values in single messages when possible
- ESP8266: Typically 2-4 connections maximum
- ESP32: Can handle 4-10 connections depending on memory
- Monitor connection count and implement backoff if needed
- Explore the Plugin System for advanced package handling
- Learn about Configuration options
- See Callbacks for detailed event handling
- Check the Mesh Architecture guide