ESP32 and Arduino

SIM7600 MQTT: Publish from an ESP32 over 4G LTE with TinyGSM

Connect an ESP32 to an MQTT broker over 4G LTE with a SIM7600 modem, TinyGSM and PubSubClient, recover from network drops and keep cellular data usage and costs under control.

ESP32 board with a SIM7600 4G modem and antenna sending sim7600 mqtt messages
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

SIM7600 MQTT lets an ESP32 publish data where there is no Wi-Fi at all: pumping stations, construction sites, vehicles, agricultural fields and remote cabinets. In this tutorial you will power up a SIM7600 4G LTE modem, attach it to the mobile network with TinyGSM, connect PubSubClient to broker.connectmq.com through the modem, and recover cleanly from network drops. You will also learn where cellular data actually goes and how to keep your monthly usage small.

How the Pieces Fit Together

The SIM7600 is a complete cellular modem with its own TCP/IP stack. The ESP32 talks to it over a UART using AT commands. TinyGSM wraps those commands in an Arduino Client class, TinyGsmClient, and because PubSubClient accepts any Client, the MQTT code is almost identical to a Wi-Fi sketch.

Layer Component Responsibility
Application Your sketch Read sensors, decide what to publish
MQTT PubSubClient CONNECT, PUBLISH, SUBSCRIBE, keep-alive
Transport TinyGsmClient Opens TCP sockets inside the modem via AT commands
Modem SIM7600 Network registration, PDP context, TCP/IP
Radio LTE network and SIM Coverage, APN, data plan

Most problems with cellular MQTT are not MQTT problems. They are power, antenna, SIM or APN problems that show up as "MQTT does not connect." The sketch therefore checks each layer separately and logs where it fails.

Hardware

  • An ESP32 board with a SIM7600 module. Integrated boards such as the LilyGO T-SIM7600 series are the easiest start; a separate SIM7600 breakout plus an ESP32 DevKit also works.
  • A micro or nano SIM with an active data plan. IoT data SIMs are ideal; a phone SIM works for testing.
  • An LTE antenna connected to the main antenna port. Never power the modem without an antenna.
  • A power supply able to deliver the modem's transmit current peaks. A good 18650 cell or a 5 V supply rated for at least 2 A is a sensible baseline.

Power is the number one cause of failures. LTE modems draw short, high current bursts while transmitting. A weak USB port or thin wires cause voltage dips that reset the modem mid-connection, and the symptoms look random: sometimes it registers, sometimes it does not, sometimes it disconnects after a few messages.

Signal Typical pin on LilyGO T-SIM7600 Purpose
Modem TX to ESP32 RX GPIO 26 UART data from modem
Modem RX from ESP32 TX GPIO 27 UART data to modem
PWRKEY GPIO 4 Pulse to switch modem on
Power enable GPIO 12 Enables modem supply on some board revisions

Pin assignments differ between boards and revisions. Check your board's documentation and adjust the defines in the sketch.

Libraries

Install through the Arduino Library Manager:

  • TinyGSM by Volodymyr Shymanskyy.
  • PubSubClient by Nick O'Leary.

TinyGSM needs to know the modem model at compile time, which is why #define TINY_GSM_MODEM_SIM7600 must appear before the include.

The Complete SIM7600 MQTT Sketch

#define TINY_GSM_MODEM_SIM7600
#define TINY_GSM_RX_BUFFER 1024          // larger buffer for incoming MQTT data

#include <TinyGsmClient.h>
#include <PubSubClient.h>

// ---- Board pins (LilyGO T-SIM7600 defaults, adjust for your board) ----
#define MODEM_TX      27
#define MODEM_RX      26
#define MODEM_PWRKEY  4
#define MODEM_POWER   12

// ---- Cellular settings ----
const char APN[]       = "internet";       // from your SIM provider
const char APN_USER[]  = "";
const char APN_PASS[]  = "";
const char SIM_PIN[]   = "";               // leave empty if the SIM has no PIN

// ---- MQTT settings ----
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* CLIENT_ID = "lte-81c3e5";      // unique
const char* BASE = "connectmq/demo/lte-81c3e5";

HardwareSerial SerialAT(1);
TinyGsm modem(SerialAT);
TinyGsmClient gsmClient(modem);
PubSubClient mqtt(gsmClient);

unsigned long lastAttempt = 0, lastPublish = 0;
const unsigned long PUBLISH_INTERVAL = 60000;  // one reading per minute

void powerOnModem() {
  pinMode(MODEM_POWER, OUTPUT);
  digitalWrite(MODEM_POWER, HIGH);
  pinMode(MODEM_PWRKEY, OUTPUT);
  digitalWrite(MODEM_PWRKEY, LOW);
  delay(100);
  digitalWrite(MODEM_PWRKEY, HIGH);              // pulse to switch on
  delay(1000);
  digitalWrite(MODEM_PWRKEY, LOW);
}

bool ensureNetwork() {
  if (!modem.isNetworkConnected()) {
    Serial.println("Waiting for network registration...");
    if (!modem.waitForNetwork(60000L)) {
      Serial.println("No network: check antenna, SIM and coverage");
      return false;
    }
    Serial.printf("Registered, signal quality %d\n", modem.getSignalQuality());
  }
  if (!modem.isGprsConnected()) {
    Serial.printf("Opening data connection with APN %s\n", APN);
    if (!modem.gprsConnect(APN, APN_USER, APN_PASS)) {
      Serial.println("Data connection failed: check APN and data plan");
      return false;
    }
    Serial.print("Local IP: ");
    Serial.println(modem.localIP());
  }
  return true;
}

void onMessage(char* topic, byte* payload, unsigned int length) {
  Serial.printf("Command %s: %.*s\n", topic, (int)length, (const char*)payload);
}

bool connectMqtt() {
  String status = String(BASE) + "/status";
  if (!mqtt.connect(CLIENT_ID, nullptr, nullptr, status.c_str(), 1, true, "offline")) {
    Serial.printf("MQTT failed, state=%d\n", mqtt.state());
    return false;
  }
  mqtt.publish(status.c_str(), "online", true);
  mqtt.subscribe((String(BASE) + "/cmd").c_str(), 1);
  Serial.println("MQTT connected over LTE");
  return true;
}

void setup() {
  Serial.begin(115200);
  SerialAT.begin(115200, SERIAL_8N1, MODEM_RX, MODEM_TX);
  powerOnModem();

  Serial.println("Initializing modem...");
  if (!modem.init()) {
    Serial.println("init failed, trying restart");
    modem.restart();
  }
  Serial.println("Modem: " + modem.getModemInfo());
  if (strlen(SIM_PIN) && modem.getSimStatus() == SIM_LOCKED) modem.simUnlock(SIM_PIN);

  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(onMessage);
  mqtt.setKeepAlive(120);                        // fewer pings, less data
  mqtt.setSocketTimeout(15);                     // cellular round trips are slower
}

void loop() {
  if (!mqtt.connected()) {
    if (millis() - lastAttempt > 15000) {
      lastAttempt = millis();
      if (ensureNetwork()) connectMqtt();
    }
    return;
  }
  mqtt.loop();

  if (millis() - lastPublish > PUBLISH_INTERVAL) {
    lastPublish = millis();
    char json[64];
    snprintf(json, sizeof(json), "{\"t\":%.1f,\"csq\":%d}",
             20.0 + (esp_random() % 100) / 10.0, modem.getSignalQuality());
    mqtt.publish((String(BASE) + "/telemetry").c_str(), json);
  }
}

What to look for on the serial monitor

A healthy start prints the modem identification, Registered, signal quality N, the local IP and MQTT connected over LTE. Signal quality is the CSQ value from 0 to 31, with 99 meaning unknown. Values below about 10 are marginal; reposition the antenna before blaming software. If the sketch stops at registration, the problem is SIM, coverage or antenna. If it registers but the data connection fails, the APN is wrong or the plan has no data. If both succeed but MQTT fails with state -2, the TCP connection is being blocked; some restrictive SIMs only allow whitelisted destinations.

Test from Your Computer

mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-81c3e5 -v -t "connectmq/demo/lte-81c3e5/#"

You should see status online followed by a telemetry message every minute. Send a command to verify the downlink path, which also proves that the carrier's NAT keeps the connection open:

mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-81c3e5 -t "connectmq/demo/lte-81c3e5/cmd" -m "ping"

The free public MQTT broker is fine for bring-up, but anything you publish there is readable by everyone, so switch to your own credentials before the device leaves the bench.

Keeping SIM7600 MQTT Data Usage Low

Cellular plans bill by the byte, and MQTT is efficient only if you use it efficiently. These are the levers that matter most, roughly in order of impact:

  1. Avoid reconnects. Every new connection costs a TCP handshake, the MQTT CONNECT and CONNACK, subscriptions and, with TLS, a full handshake including the certificate chain. A device that reconnects every few minutes can spend more data on connecting than on telemetry. Fix the root cause of drops rather than reconnecting on a timer.
  2. Tune keep-alive. Every keep-alive period the client sends a small PINGREQ and the broker answers. A longer keep-alive saves data, but carrier NAT gateways drop idle connections after a while, and if the keep-alive is longer than that, the connection dies silently. Start around two minutes and adjust based on what you observe.
  3. Shorten payloads. Use short JSON keys ("t" instead of "temperature"), round values, and skip fields that rarely change.
  4. Batch readings. Sending ten readings in one message once every ten minutes costs far less overhead than ten separate messages.
  5. Publish on change. For slowly changing values, publish only when the value moves by more than a threshold, plus a periodic heartbeat.
  6. Use short topics. The topic is sent with every message. f/42/t is cheaper than a long descriptive path, though readability matters too; find a balance.
  7. Watch subscriptions. Wildcard subscriptions to busy topics can pull in much more data than you expect.

Remember that operators may round each session or billing period up to a minimum unit, so measure real usage on your SIM provider's portal for a few days rather than relying on calculations.

For production, use TLS on port 8883 and the username, password and client ID generated in the ConnectMQ portal. On the SIM7600 you have two options. Some TinyGSM versions and forks expose a secure client that uses the modem's built-in SSL engine, which saves ESP32 memory; check whether your installed version supports it for the SIM7600. Alternatively, run TLS on the ESP32 itself with a wrapper library such as SSLClient layered on top of TinyGsmClient. Either way, verify the broker certificate against the ISRG Root X1 root rather than disabling verification. The ESP32 MQTT SSL guide explains the certificate choice and why NTP or network time matters; the SIM7600 can also provide network time.

Troubleshooting

  • Modem never responds to AT. Wrong UART pins, wrong baud rate, or the modem is not powered. Check the power enable pin and the PWRKEY pulse for your board.
  • Registers on 2G or 3G only, or not at all. The band may not be supported by your SIM7600 variant (E, A, SA, G and others cover different regions).
  • Connects, then drops after a few minutes of silence. Keep-alive longer than the carrier's NAT timeout. Shorten it.
  • Brownout resets during transmission. Power supply cannot deliver peak current. Add a large capacitor near the modem and use thicker wires.
  • Messages delayed by seconds. Normal on cellular links; latency is higher and more variable than Wi-Fi. Design for it.

For a more robust reconnect strategy with backoff, adapt the state machine from ESP32 MQTT reconnect logic. If the device has a wired option on site, compare with ESP32 Ethernet MQTT with W5500, and for moving assets, see the ESP32 GPS tracker over MQTT.

Running a Cellular Fleet

Cellular devices are expensive to visit, so the broker side should be the last thing you worry about. A fully managed MQTT service keeps the endpoint available while the devices roam between cells and reconnect. ConnectMQ gives each device its own credentials, shows connection state through $SYS/broker/connection/{clientId}/state, and lets you watch live traffic in the portal, which is useful when you are trying to understand where a SIM's data goes. A cloud MQTT broker with a public hostname also avoids the private-APN and VPN setups that self-hosted brokers often require for cellular devices. If this is your first ESP32 project, start with the ESP32 IoT tutorial.

Get Started with ConnectMQ

Create a free Serverless broker at app.connectmq.com, no credit card required. Your SIM7600 devices can publish to it from anywhere with coverage, and you can watch every message arrive in the live topic explorer.

Frequently Asked Questions

Does PubSubClient work with the SIM7600?

Yes. TinyGSM provides TinyGsmClient, which implements the Arduino Client interface, so PubSubClient uses the modem's TCP sockets exactly as it would use Wi-Fi.

How much data does SIM7600 MQTT use?

It depends mostly on reconnect frequency, keep-alive interval and payload size. A device that stays connected and sends compact messages uses very little; frequent reconnects, especially with TLS, dominate usage. Measure on your SIM provider's portal.

Which APN should I use?

The APN comes from your SIM provider and is usually listed in their documentation or IoT portal. A wrong APN lets the modem register on the network but prevents the data connection.

Can I use MQTT over TLS with the SIM7600?

Yes, either through the modem's SSL engine if your TinyGSM version supports it for the SIM7600, or by running TLS on the ESP32 with a wrapper library over TinyGsmClient. Always verify the broker certificate.

Related articles

ESP-IDF MQTT Example: Build an esp-mqtt Client from Scratch

Build an ESP-IDF project from an empty folder with the esp-mqtt component: Wi-Fi station setup, the MQTT event handler, publish and subscribe, MQTT 5 properties and TLS with the certificate bundle.

8 min read

Using esp-mqtt MQTT 5 Features on ESP32 with ESP-IDF

A hands-on ESP-IDF project that enables MQTT 5.0 in esp-mqtt and uses user properties, message expiry, topic aliases, session expiry and response topics against a real MQTT 5 broker.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.