/**
- ESP32-C3 MQTTS EC200U Example (SPIFFS Version - Merged)
- This version uses the user’s robust loop and AT command functions,
- combined with all the necessary SSL fixes.
- *** FINAL FIX APPLIED ***
- This version now FORCE-DELETES all old/conflicting certificates from
- the modem’s UFS on boot to prevent SSL stack confusion.
- It includes fixes for:
-
- Correct command order: Config SSL/MQTT before activating network.
-
- Force-deletes ALL old certs from modem on boot.
-
- Uses the full “UFS:” path for the certificate.
-
- Uses “seclevel 1” (Server auth only).
-
- Forces “sslversion 3” (TLS 1.2 only).
-
- Forces a specific modern cipher suite (0XC02F).
-
- Enables SNI (“sni”, 1).
-
- Ignores modem’s local time (“ignorelocaltime”, 1).
-
- Links MQTT to the correct PDP context (“pdpcid”, 1).
-
- Waits for the correct URCs (e.g., “+QMTOPEN: 0,0”).
-
- Removes the failing “clientid” command.
- PLATFORMIO SPIFFS PREPARATION:
-
- Create a ‘data’ folder in your project’s root.
-
- Place ‘isrgroot.pem’ inside the ‘data’ folder.
-
- Run ‘pio run -t uploadfs’ to upload the file to the ESP32’s SPIFFS.
- HARDWARE CONNECTIONS (ESP32-C3 ↔ EC200U):
-
- ESP32 3.3V → EC200U VCC
-
- ESP32 GND → EC200U GND
-
- ESP32 TXD1 (19) → EC200U Main RX
-
- ESP32 RXD1 (18) → EC200U Main TX
*/
- ESP32 RXD1 (18) → EC200U Main TX
#include <Arduino.h> // Required for PlatformIO
#include <HardwareSerial.h> // Include for HardwareSerial communication
#include <string.h> // Include for strlen
#include “SPIFFS.h” // Include SPIFFS library
// — User Configuration —
// Modem communication pins and baud rate
#define MODEM_RX 18
#define MODEM_TX 19
#define MODEM_BAUD 115200
#define MStat 2 // Status LED
// APN Configuration
#define APN “”
// MQTT Broker Details
#define MQTT_SERVER “”
#define MQTT_PORT 8883
#define MQTT_USERNAME “”
#define MQTT_PASSWORD “”
#define MQTT_CLIENT_ID “esp32-c3-device-01” // MUST be unique
#define MQTT_TOPIC “test”
// Certificate file paths
#define CERT_FILENAME_ON_SPIFFS “/isrgroot.pem” // Path on ESP32
#define CERT_FILENAME_ON_MODEM “isrgroot.pem” // Filename on Modem
#define CERT_FULLPATH_ON_MODEM “UFS:isrgroot.pem” // Full path on Modem
#define SSL_CONTEXT_ID 0 // We’ll use SSL context 0
// Create a HardwareSerial instance for the modem
HardwareSerial modem(1);
// Global flag to track MQTT connection status
bool mqttConnected = false;
// — Utility Functions —
/**
- @brief Sends an AT command and waits for a specific expected response.
*/
String sendATCommand(String command, int timeout, String expectedResponse) {
// Clear any existing serial data before sending
while (modem.available()) {
modem.read();
}
if (command != “”) {
modem.println(command);
}
String response = “”;
long startTime = millis();
Serial.print(">>> “);
Serial.println(command);
Serial.print(”<<< ");
while (millis() - startTime < timeout) {
if (modem.available()) {
char c = (char)modem.read();
response += c;
// Print the character that was just read and added to ‘response’
Serial.print(c);
// Check for expected response only after reading
if (expectedResponse != "" && response.indexOf(expectedResponse) != -1) {
return response;
}
if (response.indexOf("ERROR") != -1) {
return response; // Return on error
}
}
}
Serial.println(); // Newline for clean output if timeout occurs
// If we just wanted to send, and didn’t timeout or error, return OK
if (expectedResponse == “” && response.indexOf(“ERROR”) == -1) return “OK”;
return response; // Return response (which may be a timeout)
}
/**
- @brief Waits for the PDP context to be fully active.
*/
bool waitForActivation(uint32_t timeout = 30000) {
Serial.println(“\n[INFO] Waiting for PDP Context Activation (30s timeout)…”);
long startTime = millis();
while (millis() - startTime < timeout) {
String response = sendATCommand(“AT+QIACT?”, 2000, “+QIACT: 1,1,1”);
if (response.indexOf(“+QIACT: 1,1,1”) != -1) {
Serial.println(“[INFO] PDP Context is active.”);
return true;
}
Serial.println(“[INFO] Waiting… (PDP context not active yet)”);
delay(2000);
}
Serial.println(“[ERROR] PDP Context activation timed out!”);
return false;
}
/**
- @brief Checks if a file exists on the modem’s UFS.
/
bool checkFileOnModem(const char filename) {
Serial.println(“\n[INFO] Checking for file on modem…”);
String response = sendATCommand(“AT+QFLST”, 5000, “OK”);
if (response.indexOf(filename) != -1) {
Serial.println(“[INFO] File found in modem file list.”);
return true;
}
Serial.println(“[INFO] File not found in modem file list.”);
return false;
}
/**
- Reads a file from ESP32 SPIFFS and uploads it to the modem’s UFS.
/
bool uploadFileToModem(const char spiffsFilename, const char* modemFilename) {
Serial.println(“\n[INFO] Uploading file from SPIFFS to modem…”);
File file = SPIFFS.open(spiffsFilename, “r”);
if (!file) {
Serial.print("[ERROR] Failed to open file from SPIFFS: “);
Serial.println(spiffsFilename);
return false;
}
size_t fileSize = file.size();
Serial.print(”[INFO] File found on SPIFFS. Size: ");
Serial.println(fileSize);
String cmd = “AT+QFUPL="” + String(modemFilename) + “",” + String(fileSize) + “,100”;
if (sendATCommand(cmd, 5000, “CONNECT”).indexOf(“CONNECT”) == -1) {
Serial.println(“!! Cert upload failed: No CONNECT received.”);
file.close();
return false;
}
Serial.println(“[INFO] Modem is ready. Sending file data…”);
uint8_t buffer[256];
size_t bytesSent = 0;
while (bytesSent < fileSize) {
size_t bytesToRead = file.read(buffer, sizeof(buffer));
if (bytesToRead == 0) break; // End of file
modem.write(buffer, bytesToRead);
bytesSent += bytesToRead;
Serial.print(“.”);
}
Serial.println(“\n[INFO] File data sent.”);
file.close();
if (sendATCommand(“”, 10000, “OK”).indexOf(“OK”) != -1) {
Serial.println(“>> Cert Uploaded Successfully: " + String(modemFilename));
delay(1000); // Give filesystem time
return true;
} else {
Serial.println(”!! Cert upload failed: Modem did not confirm OK.");
return false;
}
}
// -----------------------------------------------------------------
// — MQTT and Connection Functions (MERGED & RE-ORDERED) —
// -----------------------------------------------------------------
/**
- Initializes the modem and checks SIM/Network.
*/
bool initAndCheckNetwork() {
if (sendATCommand(“AT”, 2000, “OK”).indexOf(“OK”) == -1) {
Serial.println(“\n[ERROR] Modem not ready.”);
return false;
}
Serial.println(“\n[INFO] Modem: OK.”);
sendATCommand(“ATE0”, 500, “OK”); // Disable echo
sendATCommand(“AT+CFUN=1”, 10000, “OK”); // Set full functionality
Serial.print(“[INFO] Checking SIM and Network…”);
if (sendATCommand(“AT+CPIN?”, 5000, “+CPIN: READY”).indexOf(“READY”) == -1) {
Serial.println(“\n[ERROR] SIM not ready (AT+CPIN?).”); return false;
}
if (sendATCommand(“AT+CSQ”, 1000, “+CSQ:”).indexOf(“+CSQ:”) == -1) {
Serial.println(“\n[ERROR] Failed to check signal quality (AT+CSQ).”); return false;
}
String regResponse = sendATCommand(“AT+CREG?”, 2000, “OK”);
if (regResponse.indexOf(“+CREG: 0,1”) == -1 && regResponse.indexOf(“+CREG: 0,5”) == -1) {
Serial.println(“\n[ERROR] Not registered on network (AT+CREG?).”); return false;
}
if (sendATCommand(“AT+CGATT=1”, 1000, “OK”).indexOf(“OK”) == -1) {
Serial.println(“\n[ERROR] Failed to attach to GPRS (AT+CGATT=1).”); return false;
}
Serial.println(“\n[INFO] SIM/Network Checks OK.”);
return true;
}
/**
- @brief Configures the modem’s SSL Context with all required settings.
*/
bool configureSSL() {
// *** NEW: FORCE-DELETE ALL OLD/CONFLICTING CERT FILES ***
Serial.println(“\n[INFO] Forcing deletion of old certificate file(s)…”);
sendATCommand(“AT+QFDEL="” + String(CERT_FULLPATH_ON_MODEM) + “"”, 5000, “”);
sendATCommand(“AT+QFDEL="UFS:ca.crt"”, 5000, “”);
sendATCommand(“AT+QFDEL="UFS:client.crt"”, 5000, “”);
sendATCommand(“AT+QFDEL="UFS:client.key"”, 5000, “”);
sendATCommand(“AT+QFDEL="UFS:isrg.pem"”, 5000, “”);
sendATCommand(“AT+QFDEL="UFS:isrgrootx1.pem"”, 5000, “”);
Serial.println(“[INFO] Old file cleanup complete.”);
Serial.println(“\n[INFO] Checking/Uploading Certificate…”);
// Check if file exists. If not, upload it.
// We use the FILENAME here, not the full path, for the check
if (!checkFileOnModem(CERT_FILENAME_ON_MODEM)) {
Serial.println(“[INFO] Certificate not found on modem, uploading…”);
// We use the FILENAME for the upload command
if (!uploadFileToModem(CERT_FILENAME_ON_SPIFFS, CERT_FILENAME_ON_MODEM)) {
Serial.println(“[ERROR] CRITICAL: Failed to upload certificate to modem!”);
return false;
}
} else {
Serial.println(“[INFO] Certificate file already exists on modem.”);
}
Serial.println(“\n[INFO] Configuring SSL Context…”);
// Force TLS 1.2
if (sendATCommand(“AT+QSSLCFG="sslversion",” + String(SSL_CONTEXT_ID) + “,3”, 1000, “OK”).indexOf(“OK”) == -1) return false;
// Force modern Cipher
if (sendATCommand(“AT+QSSLCFG="ciphersuite",” + String(SSL_CONTEXT_ID) + “,0XC02F”, 1000, “OK”).indexOf(“OK”) == -1) return false;
// Server Auth Only
if (sendATCommand(“AT+QSSLCFG="seclevel",” + String(SSL_CONTEXT_ID) + “,1”, 1000, “OK”).indexOf(“OK”) == -1) return false;
// Set CA cert path (using the FULL UFS path)
if (sendATCommand(“AT+QSSLCFG="cacert",” + String(SSL_CONTEXT_ID) + “,"” + String(CERT_FULLPATH_ON_MODEM) + “"”, 1000, “OK”).indexOf(“OK”) == -1) return false;
// Ignore modem clock
if (sendATCommand(“AT+QSSLCFG="ignorelocaltime",” + String(SSL_CONTEXT_ID) + “,1”, 1000, “OK”).indexOf(“OK”) == -1) return false;
// Enable SNI
if (sendATCommand(“AT+QSSLCFG="sni",” + String(SSL_CONTEXT_ID) + “,1”, 1000, “OK”).indexOf(“OK”) == -1) return false;
Serial.println(“[INFO] SSL Context Configured.”);
return true;
}
/**
- @brief Configures the MQTT instance 0 to use the SSL context.
*/
bool configureMQTT() {
Serial.println(“\n[INFO] Configuring MQTT for SSL…”);
// Link to PDP context 1
if (sendATCommand(“AT+QMTCFG="pdpcid",0,1”, 1000, “OK”).indexOf(“OK”) == -1) return false;
// Enable SSL on MQTT instance 0, link to SSL Context 0
if (sendATCommand(“AT+QMTCFG="ssl",0,1,” + String(SSL_CONTEXT_ID), 1000, “OK”).indexOf(“OK”) == -1) return false;
// Use MQTT 3.1.1
if (sendATCommand(“AT+QMTCFG="version",0,4”, 1000, “OK”).indexOf(“OK”) == -1) return false;
Serial.println(“[INFO] MQTT Configured for SSL.”);
return true;
}
/**
- @brief Activates the network data connection (PDP Context).
*/
bool activateNetwork() {
Serial.print(“[INFO] Deactivating old PDP context (AT+QIDEACT=1)…”);
sendATCommand(“AT+QIDEACT=1”, 5000, “”); // We don’t care about the response
Serial.println(“Done.”);
String apnCommand = “AT+QICSGP=1,1,"” + String(APN) + “","","",0”;
Serial.print(“[INFO] Setting APN…”);
if (sendATCommand(apnCommand, 5000, “OK”).indexOf(“OK”) == -1) {
Serial.println(“\n[ERROR] Failed to set APN (AT+QICSGP).”);
return false;
}
Serial.println(“\n[INFO] APN Set.”);
Serial.print(“[INFO] Connecting to GPRS/LTE…”);
if (sendATCommand(“AT+QIACT=1”, 30000, “OK”).indexOf(“OK”) == -1) {
Serial.println(“\n[ERROR] Failed to connect to GPRS/LTE. Check SIM/APN.”);
return false;
}
Serial.println(“\n[INFO] Connected to GPRS/LTE.”);
if (!waitForActivation()) {
return false; // Timeout waiting for IP
}
return true;
}
/**
- @brief Main connection function, calls helpers in the correct order.
*/
bool connectModemAndMQTT() {
// This is the new, “forum-approved” order of operations
if (!initAndCheckNetwork()) return false;
if (!configureSSL()) return false; // Configure SSL before network
if (!configureMQTT()) return false; // Configure MQTT before network
if (!activateNetwork()) return false; // Then activate the network
// — All configs are done, now try to connect —
// Open MQTT Network Connection (QMTOPEN)
Serial.print(“[INFO] Connecting to MQTT Broker…”);
String openCommand = “AT+QMTOPEN=0,"” + String(MQTT_SERVER) + “",” + String(MQTT_PORT);
// Wait for +QMTOPEN: 0,0 (Success)
if (sendATCommand(openCommand, 15000, “+QMTOPEN: 0,0”).indexOf(“+QMTOPEN: 0,0”) == -1) {
Serial.println(“\n[ERROR] Failed to open network connection. Check SSL config.”);
return false;
}
Serial.println(“\n[INFO] MQTT Network Open.”);
// Connect to MQTT Broker (QMTCONN)
Serial.print(“[INFO] Logging into MQTT Broker…”);
String connCommand = “AT+QMTCONN=0,"” + String(MQTT_CLIENT_ID) + “","” + String(MQTT_USERNAME) + “","” + String(MQTT_PASSWORD) + “"”;
// Wait for +QMTCONN: 0,0,0 (Success)
if (sendATCommand(connCommand, 15000, “+QMTCONN: 0,0,0”).indexOf(“+QMTCONN: 0,0,0”) == -1) {
Serial.println(“\n[ERROR] Failed to connect to MQTT broker. Check credentials.”);
sendATCommand(“AT+QMTCLOSE=0”, 2000, “OK”);
return false;
}
Serial.println(“\n[INFO] MQTT Connected Successfully!”);
return true;
}
/**
- @brief Publishes a message to the MQTT topic.
/
bool mqttPublish(const char payload) {
static int messageId = 1;
Serial.print(“[INFO] Publishing message: '”);
Serial.print(payload);
Serial.print("’ to topic: ");
Serial.println(MQTT_TOPIC);
String pubCommand = “AT+QMTPUB=0,” + String(messageId++) + “,0,0,"” + String(MQTT_TOPIC) + “"”;
// 1. Send the AT+QMTPUB command and wait for the “>” prompt
if (sendATCommand(pubCommand, 3000, “>”).indexOf(“>”) == -1) {
Serial.println(“\n[ERROR] Failed to publish (did not get > prompt).”);
return false;
}
// 2. Send the payload and the Ctrl+Z character
modem.print(payload);
modem.write(0x1A); // Send Ctrl+Z (ASCII 26) to signal end of data
Serial.print("<<< “); Serial.print(payload); Serial.print(” + [Ctrl+Z] … ");
// 4. Wait for the final response from the modem
// We expect +QMTPUB: 0, (messageId-1), 0 (for QoS 0)
String expectedResponse = “+QMTPUB: 0,” + String(messageId - 1) + “,0”;
if (sendATCommand(“”, 5000, expectedResponse).indexOf(expectedResponse) != -1) {
Serial.println(“\n[INFO] Publish successful!”);
digitalWrite(MStat, HIGH);
delay(100);
digitalWrite(MStat, LOW);
return true;
} else {
Serial.println(“\n[ERROR] Message publish failed.”);
return false;
}
}
// — Setup and Loop —
void setup() {
Serial.begin(115200);
pinMode(MStat, OUTPUT);
// Initialize the HardwareSerial for the modem
modem.begin(MODEM_BAUD, SERIAL_8N1, MODEM_RX, MODEM_TX);
Serial.println(“— EC200U MQTTS Publisher Start (Merged Code) —”);
// *** NEW: Initialize SPIFFS ***
if (!SPIFFS.begin(true)) {
Serial.println(“[ERROR] SPIFFS Mount Failed! Halting.”);
while(1) delay(1000); // Halt if SPIFFS fails
} else {
Serial.println(“[INFO] SPIFFS Mounted.”);
}
// Establish connection in setup
mqttConnected = connectModemAndMQTT();
if (!mqttConnected) {
Serial.println(“\n[FATAL] Initial connection failed. Loop will attempt reconnect.”);
}
}
void loop() {
static unsigned long lastPublishTime = 0;
const long publishInterval = 10000; // Publish every 10 seconds
// Read and print any unsolicited messages (URCs)
while (modem.available()) {
Serial.print((char)modem.read());
}
// If not connected, try to reconnect
if (!mqttConnected) {
Serial.println(“\n[WARN] Attempting full modem and MQTT reconnect…”);
// Give the modem a moment if it failed recently
delay(2000);
mqttConnected = connectModemAndMQTT();
}
// If connected, publish the message periodically
if (mqttConnected && millis() - lastPublishTime >= publishInterval) {
lastPublishTime = millis();
if (!mqttPublish(“hello”)) {
// If publish fails, assume connection is lost and trigger a full reconnect
mqttConnected = false;
}
}
}
output _
ESP-ROM:esp32c3-api1-20210207
Build:Feb 7 2021
rst:0x1 (POWERON),boot:0xc (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fcd5810,len:0x438
load:0x403cc710,len:0x90c
load:0x403ce710,len:0x2624
entry 0x403cc710
— EC200U MQTTS Publisher Start (Merged Code) —
[INFO] SPIFFS Mounted.
AT
<<<
OK
[INFO] Modem: OK.
ATE0
<<<
OK>>> AT+CFUN=1
<<<
OK[INFO] Checking SIM and Network…>>> AT+CPIN?
<<<
+CPIN: READY>>> AT+CSQ
<<<
+CSQ:>>> AT+CREG?
<<<
+CREG: 0,1
OK>>> AT+CGATT=1
<<<
OK
[INFO] SIM/Network Checks OK.
[INFO] Forcing deletion of old certificate file(s)…
AT+QFDEL=“UFS:isrgroot.pem”
<<<
OK
AT+QFDEL=“UFS:ca.crt”
<<<
+CME ERROR>>> AT+QFDEL=“UFS:client.crt”
<<<
+CME ERROR>>> AT+QFDEL=“UFS:client.key”
<<<
+CME ERROR>>> AT+QFDEL=“UFS:isrg.pem”
<<<
+CME ERROR>>> AT+QFDEL=“UFS:isrgrootx1.pem”
<<<
+CME ERROR[INFO] Old file cleanup complete.
[INFO] Checking/Uploading Certificate…
[INFO] Checking for file on modem…
AT+QFLST
<<<
+QFLST: “UFS:boot”,15004
+QFLST: “UFS:firm”,243616
+QFLST: “UFS:gnss_data”,4636
+QFLST: “UFS:gnss_loca”,47
+QFLST: “UFS:gnss_time”,21
OK[INFO] File not found in modem file list.
[INFO] Certificate not found on modem, uploading…
[INFO] Uploading file from SPIFFS to modem…
[INFO] File found on SPIFFS. Size: 1939
AT+QFUPL=“isrgroot.pem”,1939,100
<<<
CONNECT[INFO] Modem is ready. Sending file data…
…
[INFO] File data sent.
<<< +QFUPL: 1939,4f64
OK>> Cert Uploaded Successfully: isrgroot.pem
[INFO] Configuring SSL Context…
AT+QSSLCFG=“sslversion”,0,3
<<<
OK>>> AT+QSSLCFG=“ciphersuite”,0,0XC02F
<<<
OK>>> AT+QSSLCFG=“seclevel”,0,1
<<<
OK>>> AT+QSSLCFG=“cacert”,0,“UFS:isrgroot.pem”
<<<
OK>>> AT+QSSLCFG=“ignorelocaltime”,0,1
<<<
OK>>> AT+QSSLCFG=“sni”,0,1
<<<
OK[INFO] SSL Context Configured.
[INFO] Configuring MQTT for SSL…
AT+QMTCFG=“pdpcid”,0,1
<<<
OK>>> AT+QMTCFG=“ssl”,0,1,0
<<<
OK>>> AT+QMTCFG=“version”,0,4
<<<
OK[INFO] MQTT Configured for SSL.
[INFO] Deactivating old PDP context (AT+QIDEACT=1)…>>> AT+QIDEACT=1
<<<
OK
Done.
[INFO] Setting APN…>>> AT+QICSGP=1,1,“www”,“”,“”,0
<<<
OK
[INFO] APN Set.
[INFO] Connecting to GPRS/LTE…>>> AT+QIACT=1
<<<
OK
[INFO] Connected to GPRS/LTE.
[INFO] Waiting for PDP Context Activation (30s timeout)…
AT+QIACT?
<<<
+QIACT: 1,1,1[INFO] PDP Context is active.
[INFO] Connecting to MQTT Broker…>>> AT+QMTOPEN=0,“”,8883
<<<
OK
+QMTOPEN: 0,5
[ERROR] Failed to open network connection. Check SSL config.
[FATAL] Initial connection failed. Loop will attempt reconnect.
[WARN] Attempting full modem and MQTT reconnect…
AT
<<<
OK
[INFO] Modem: OK.
ATE0
<<<
OK>>> AT+CFUN=1
<<<
OK[INFO] Checking SIM and Network…>>> AT+CPIN?
<<<
+CPIN: READY>>> AT+CSQ
<<<
+CSQ:>>> AT+CREG?
<<<
+CREG: 0,1
OK>>> AT+CGATT=1
<<<
OK
[INFO] SIM/Network Checks OK.
[INFO] Forcing deletion of old certificate file(s)…
AT+QFDEL=“UFS:isrgroot.pem”
<<<
OK
again and again +QMTOPEN: 0,5