How to send sensor data to a gateway using PainlessMesh in 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 with a NodeMCUv2 (ESP8266) board and send data from one ESP8266 board to another ESP8266 board.

What Problem we are solving?

Previously we explored how to use MQTT with ESP8266.

Now let's integrate painlessmesh with MQTT.

Let's assume we have 2 ESP8266 boards.
One is gateway and another is node.

We want to send data from node to gateway using painlessmesh. Next we want to send data from gateway to cloud using MQTT.

For example we will use DHT11 sensor and mq series gas sensor with node to send data to gateway.

This operation should be done with very minimal data transfer.

If one node has 2 or more sensors it should be compressed and sent to gateway.

What We will be achieving?

Painlessmesh is very heavy library. It does lot of operations to keep network alive. With this if we send data without compressing it. It will consume lot of power and data. Casuing network to be unstable.

Main Goal is

  1. Receive data from multiple sensors
  2. Compress the data
  3. Send the data to gateway
  4. Publish the data from received at gateway to cloud using MQTT
  5. Scallable MQTT topics publishing

I will be combining all the above mentioned in single project.

In this article we will be covering first 2 points.

  1. Receiving Data from Multiple Sensors
  2. Compressing the data
  3. Send the data to gateway

Receiving Data from Multiple Sensors and Compressing it

For this example we will use DHT11 sensor and MQ2 gas sensor.

  1. Setup connection with painlessmesh We will use these credentials for painlessmesh.
#define MESH_PREFIX "secreat_name"
#define MESH_PASSWORD "SecreateKey"
#define MESH_PORT 5555

With this funtion we create scheduler and painlessmesh object.

#include <painlessMesh.h>

  mesh.init(MESH_PREFIX, MESH_PASSWORD, &scheduler, MESH_PORT);
  mesh.onReceive(&onReceive);
  mesh.onNewConnection(&onNewConnection);
  1. Setup connection with DHT11 sensor

We use DHT library sensor to measure temperature and humidity.

We use D2 pin to connect DHT11 sensor.

#include <DHT.h>
#define DHT_PIN D2
#define DHT_TYPE DHT11

DHT dht(DHT_PIN, DHT_TYPE);

Read the temperature and humidity from the sensor.

float t = dht.readTemperature();
float h = dht.readHumidity();

As Dht sensor should have 1 second delay between readings. We will use scheduler to read the temperature and humidity from the sensor.

Task readDHTTask(10000, TASK_FOREVER, []() {
    float t = dht.readTemperature();
    float h = dht.readHumidity();
    if (isnan(t) || isnan(h)) {
        Serial.println("Failed to read from DHT sensor!");
        return;
    }
    Serial.printf("Temperature: %.2f C, Humidity: %.2f %%", t, h);
});
  1. Setup connection with MQ2 gas sensor

We use MQ2 gas sensor to measure the gas concentration.

We use A0 pin to connect MQ2 gas sensor.

#define MQ2_PIN A0

Read the gas concentration from the sensor.

float gas = analogRead(MQ2_PIN);

As MQ2 sensor should have 1 second delay between readings. We will use scheduler to read the gas concentration from the sensor.

Task readMQ2Task(10000, TASK_FOREVER, []() {
    float gas = analogRead(MQ2_PIN);
    if (isnan(gas)) {
        Serial.println("Failed to read from MQ2 sensor!");
        return;
    }
    Serial.printf("Gas: %.2f ppm", gas);
});
  1. Compress the data with bit packing

We use bit packing to compress the data.

As sensor will be in specified range we can use bit packing to compress the data.

String packToHex(float temp, float hum, float gas) {
  uint16_t packedTemp = (uint16_t)(temp * 10.0f) & 0x1FF;         // 9 bits
  uint16_t packedHum = (uint16_t)((hum - 20.0f) * 10.0f) & 0x3FF; // 10 bits
  uint16_t packedGas = (uint16_t)(gas / 10.0f) & 0x3FF;           // 10 bits

  uint32_t packed = 0;
  packed |= (uint32_t)packedTemp << 20; // bits 28-20
  packed |= (uint32_t)packedHum << 10;  // bits 19-10
  packed |= (uint32_t)packedGas;        // bits  9-0

  char buf[9];
  snprintf(buf, sizeof(buf), "%08X", packed);
  return String(buf);
}

Finaly commbining all these into one code.

/**
 * sensor_node_1.cpp — PainlessMesh Sensor Node (DHT11 + MQ6)
 *
 * Boot flow:
 *   1. Joins the mesh network
 *   2. On first connection to gateway → broadcasts /config (packing schema)
 *      The gateway forwards /config to MQTT → backend caches the schema
 *   3. After 3 s delay → starts sending /data every 5 s
 *      (gives backend time to process /config before first hex payload arrives)
 *
 * /alert is sent immediately any time gas > GAS_THRESHOLD.
 *
 * Bit packing layout (29 bits used, 3 MSBs unused):
 *   bits 28-20 (9 bits) : temp  = temp_celsius * 10        (0–511 → 0–51.1°C)
 *   bits 19-10 (10 bits): hum   = (humidity - 20) * 10     (0–1023 → 20–122.3%)
 *   bits  9-0  (10 bits): gas   = gas_ppm / 10             (0–1023 → 0–10230
 * ppm)
 */

#include <Arduino.h>
#include <ArduinoJson.h>
#include <DHT.h>
#include <painlessMesh.h>

// ─── Mesh credentials (must match gateway) ───────────────────────────────────
#define MESH_PREFIX "secreat_name"
#define MESH_PASSWORD "SecreateKey"
#define MESH_PORT 5555

// ─── Sensor pins ─────────────────────────────────────────────────────────────
#define DHT_PIN D2
#define DHT_TYPE DHT11
#define MQ6_PIN A0 // analog pin

// ─── Alert threshold ─────────────────────────────────────────────────────────
#define GAS_THRESHOLD 5000 // ppm — triggers /alert

// ─── Timing ──────────────────────────────────────────────────────────────────
#define DATA_INTERVAL_MS 5000

// ─── Globals ─────────────────────────────────────────────────────────────────
Scheduler scheduler;
painlessMesh mesh;
DHT dht(DHT_PIN, DHT_TYPE);
bool configSent = false; // send /config once per gateway connection

// ─── Bit packing ─────────────────────────────────────────────────────────────

/**
 * Pack temperature, humidity and gas into an 8-char hex string.
 * The encoding matches what the backend's unpackHex() expects.
 *
 *   temp  → 9 bits, mult 10, min 0
 *   hum   → 10 bits, mult 10, min 20
 *   gas   → 10 bits, div 10, min 0
 */
String packToHex(float temp, float hum, float gas) {
  uint16_t packedTemp = (uint16_t)(temp * 10.0f) & 0x1FF;         // 9 bits
  uint16_t packedHum = (uint16_t)((hum - 20.0f) * 10.0f) & 0x3FF; // 10 bits
  uint16_t packedGas = (uint16_t)(gas / 10.0f) & 0x3FF;           // 10 bits

  uint32_t packed = 0;
  packed |= (uint32_t)packedTemp << 20; // bits 28-20
  packed |= (uint32_t)packedHum << 10;  // bits 19-10
  packed |= (uint32_t)packedGas;        // bits  9-0

  char buf[9];
  snprintf(buf, sizeof(buf), "%08X", packed);
  return String(buf</spa

1 Comment

0 votes

More Posts

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

Karol Modelskiverified - Mar 19

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

Ganesh Kumar - Apr 13

How I Built a React Portfolio in 7 Days That Landed ₹1.2L in Freelance Work

Dharanidharan - Feb 9

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

Tom Smithverified - Mar 16

How to use the DHT22 sensor with ESP8266?

Ganesh Kumar - Apr 3
chevron_left

Related Jobs

View all jobs →

Commenters (This Week)

3 comments
2 comments
2 comments

Contribute meaningful comments to climb the leaderboard and earn badges!