How to Send Data from PainlessMesh to the Cloud Using MQTT in an ESP8266?

posted Originally published at dev.to 12 min read

Hello, I'm Ganesh. I'm building git-lrc, an AI code reviewer that runs on every commit. It is free, unlimited, and source-available on Github. Star Us to help devs discover the project. Do give it a try and share your feedback for improving the product.

In this article, I will be demonstrating how to use PainlessMesh and MQTT with a NodeMCUv2 (ESP8266) board.

What Problem we are solving?

We will be reciving data from multiple nodes and send it to cloud using MQTT.

  1. Publish the data from received at gateway to cloud using MQTT
  2. Scallable MQTT topics publishing

How it works?

  1. Receive data from multiple nodes

These data will be in compressed in these format.

void onMeshReceive(const uint32_t &from, const String &msg) {
  Serial.printf("[mesh] From %u: %s\n", from, msg.c_str());

  JsonDocument doc;
  if (deserializeJson(doc, msg) != DeserializationError::Ok) {
    Serial.println("[mesh] JSON parse failed");
    return;
  }

  const char *msgType = doc["type"] | "data";
  String topic = buildTopic(from, msgType);

  if (mqttClient.connected()) {
    mqttClient.publish(topic.c_str(), msg.c_str());
    Serial.printf("[mqtt] Published → %s\n", topic.c_str());
  } else {
    Serial.println("[mqtt] Not connected — message dropped");
  }
}
  1. Publish the data from received at gateway to cloud using MQTT

Setup wifi credentials.

#define STATION_SSID "your_ssid"
#define STATION_PASSWORD "your_password"

Connect to wifi using painlessmesh.

As painlessmesh is heavy library it will consume lot of power and data.

So we will use mesh.stationManual() to connect to wifi.

 mesh.stationManual(STATION_SSID,
                     STATION_PASSWORD); // ← key: mesh owns the STA
  mesh.setHostname("ais-gateway")
  1. Scallable MQTT topics publishing We will use MQTT to send data from gateway to cloud.

We will use these credentials for MQTT.

#define MQTT_HOST "your_mqtt_host"
#define MQTT_PORT_TLS 8883
#define MQTT_USER "your_mqtt_user"
#define MQTT_PASS "your_mqtt_password"

We will use these topics for MQTT.

Each topic will have different meaning.

For example we will use these topics for MQTT.

For sending an industrial sensor data we will use these topics.

#define T_VERSION "v1"
#define T_TENANT "electrotech"
#define T_INDUSTRY "pcba"
#define T_SITE "plant1"
#define T_GATEWAY "gw_01"

Finally followed with config message.

{
  "type": "config",
  "schema": {
    "temp": {"type":"float","scale":10,"offset":0,"min":0,"max":51.1},
    "hum": {"type":"float","scale":10,"offset":-20,"min":20,"max":122.3},
    "gas": {"type":"float","scale":0.1,"offset":0,"min":0,"max":1023}
  }
}
/**
 * gateway.cpp — PainlessMesh Root + MQTT Bridge (HiveMQ TLS)
 *
 * Follows the official painlessMesh MQTT bridge pattern:
 *   - mesh.stationManual() hands WiFi STA management to the mesh library
 *     so it never disconnects from the router to scan for mesh peers.
 *   - MQTT connect is triggered by IP change detection in loop().
 *   - mesh.setRoot(true) called BEFORE init().
 *
 * Data flow: sensor node → mesh → gateway → HiveMQ MQTT → backend
 * Topic format: v1/{tenant}/{industry}/{site}/{gateway}/{node}/{type}
 */

#include <Arduino.h>
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <WiFiClientSecure.h>
#include <painlessMesh.h>

// ─── WiFi credentials ────────────────────────────────────────────────────────
#define STATION_SSID "your_ssid"
#define STATION_PASSWORD "your_password"

// ─── Mesh credentials ────────────────────────────────────────────────────────
#define MESH_PREFIX "your_mesh_prefix"
#define MESH_PASSWORD "your_mesh_password"
#define MESH_PORT 5555

// ─── MQTT credentials (from backend .env) ────────────────────────────────────
#define MQTT_HOST "your_mqtt_host"
#define MQTT_PORT_TLS 8883
#define MQTT_USER "your_mqtt_user"
#define MQTT_PASS "your_mqtt_password"

// ─── Topic routing constants ─────────────────────────────────────────────────
#define T_VERSION "v1"
#define T_TENANT "electrotech"
#define T_INDUSTRY "pcba"
#define T_SITE "plant1"
#define T_GATEWAY "gw_01"

// ─── Globals ─────────────────────────────────────────────────────────────────
painlessMesh mesh;
BearSSL::WiFiClientSecure wifiClient;
PubSubClient mqttClient(wifiClient);
IPAddress myIP(0, 0, 0, 0);
unsigned long lastMqttAttempt = 0;

// ─── Helpers ─────────────────────────────────────────────────────────────────

String buildTopic(uint32_t nodeId, const char *msgType) {
  return String(T_VERSION) + "/" + T_TENANT + "/" + T_INDUSTRY + "/" + T_SITE +
         "/" + T_GATEWAY + "/" + String(nodeId) + "/" + msgType;
}

IPAddress getStationIP() { return IPAddress(mesh.getStationIP()); }

// ─── MQTT callback (placeholder for future /cmd downlink) ────────────────────
void mqttCallback(char *topic, byte *payload, unsigned int len) {
  Serial.printf("[mqtt] Received on %s: %.*s\n", topic, (int)len,
                (char *)payload);
  // Phase 2: parse /cmd and forward to specific mesh node via mesh.sendSingle()
}

// ─── Mesh callback: forward every message to the correct MQTT topic
// ───────────
void onMeshReceive(const uint32_t &from, const String &msg) {
  Serial.printf("[mesh] From %u: %s\n", from, msg.c_str());

  JsonDocument doc;
  if (deserializeJson(doc, msg) != DeserializationError::Ok) {
    Serial.println("[mesh] JSON parse failed");
    return;
  }

  const char *msgType = doc["type"] | "data";
  String topic = buildTopic(from, msgType);

  if (mqttClient.connected()) {
    mqttClient.publish(topic.c_str(), msg.c_str());
    Serial.printf("[mqtt] Published → %s\n", topic.c_str());
  } else {
    Serial.println("[mqtt] Not connected — message dropped");
  }
}

// ─── Setup ───────────────────────────────────────────────────────────────────
void setup() {

More Posts

Breaking the AI Data Bottleneck: How Hammerspace's AI Data Platform Eliminates Migration Nightmares

Tom Smithverified - Mar 16

Optimizing the Clinical Interface: Data Management for Efficient Medical Outcomes

Huifer - Jan 26

The End of Data Export: Why the Cloud is a Compliance Trap

Pocket Portfolioverified - Apr 6

Bridging the Silence: Why Objective Data Outperforms Subjective Health Reports in Elderly Care

Huifer - Jan 27

I’m a Senior Dev and I’ve Forgotten How to Think Without a Prompt

Karol Modelskiverified - Mar 19
chevron_left

Related Jobs

View all jobs →

Commenters (This Week)

2 comments
2 comments
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!