GITHUB REPOSITORY : EC25_MQTT_SSL_with DATACAKE #
NORVI devices are robust industrial controllers designed for IoT applications. When combined with the Quectel EC25 module and SSL configurations, these devices can leverage cellular connectivity to communicate with cloud platforms securely using the MQTT protocol.
This guide provides step-by-step instructions on how to use NORVI GSM series devices with the Quectel EC25 module to establish a secure SSL connection with an MQTT broker using AT commands. The Quectel EC25 is a popular LTE module widely used in IoT applications for cellular communication. This guide will cover the necessary setup, code explanation, and essential AT commands.
MQTT PROTOCOL #
SSL MQTT Communication #
SSL (Secure Sockets Layer) is a standard security technology for establishing an encrypted link between a server and a client. In the context of MQTT, SSL ensures that the data sent between the device and the MQTT broker is encrypted, providing confidentiality and integrity.
SSL with MQTT #
SSL protects data in transit from being intercepted or altered by unauthorized parties. It is particularly important for sensitive data or when communicating over public networks.
Configuring SSL on NORVI GSM series EC25 Devices #
To establish an SSL-encrypted MQTT connection with the NORVI GSM series EC25 device, follow these steps,
- Ensure the SIM card is correctly inserted into the NORVI GSM series EC25 device.
- Connect the device to a power source and power it on.
- Open the serial monitor on your development environment to interact with the device.
- Configure the Quectel EC25 module to use SSL for MQTT communication. This involves sending specific AT commands to the module.
- Ensure your device and broker certificates are up-to-date to maintain a secure connection.
Understanding NORVI GSM series Devices with the Quectel EC25 Module #
NORVI GSM series devices are known for their reliability in industrial environments, featuring various input/output options, robust enclosures, and compatibility with multiple communication protocols. The integration of the Quectel EC25 module allows these devices to operate in areas where traditional Wi-Fi or Ethernet connections are not feasible. The Quectel EC25 module provides LTE connectivity, enabling NORVI GSM series devices to transmit data securely over cellular networks.
Setting Up NORVI EC25 to MQTT SSL #
Here we’ll be considering the NORVI GSM series EC25 device as a Publisher and “MQTT.FX” software as the Subscriber. With the NORVI device, we’ll be reading the data values of the digital inputs, analog inputs, and relay/transistor outputs from the MODBUS Software and storing the values in the MQTT Broker and the data visualization platform.
Key features of NORVI GSM series devices with the Quectel EC25 module, #
- Industrial-grade design: Suitable for harsh environments.
- Versatile I/O options: Digital and analog inputs/outputs for various sensors and actuators.
- Cellular connectivity: LTE support via the Quectel EC25 module for remote monitoring and control.
- SSL/TLS security: Secure data transmission using SSL/TLS over MQTT.
Prerequisites #
Before you begin, ensure you have the following,
- NORVI GSM series Device with an integrated Quectel EC25 module
- MQTT broker details (hostname, port, username, password)
- MQTT.fx software for testing and monitoring
- Arduino IDE and necessary libraries for programming the NORVI GSM series device.
- Data visualization platform (e.g., DATACAKE)
- SSL Certificates: ( CA certificate, client certificate, and client key).
Required Libraries #
- Arduino.h
- Wire.h
- WiFi.h
- ArduinoJson.h
Sensitive Information Handling #
- Secret.h: This file should contain the MQTT username and password, which should be kept secure.
SSL Certificate Setup #
To establish an SSL-encrypted connection, include the SSL certificates in the code.
- Root CA Certificate: The certificate authority that issued the broker’s certificate.
- Client Certificate: Your device’s certificate.
- Client Key: The private key corresponding to the client certificate.
Hardware Setup #
Pin Connections #
- MODEM_TX (TX Pin)
- MODEM_RX (RX Pin)
- GSM_RESET (Reset Pin)
- Digital Input Pins (D0, D1, D2, D3)
- Relay Output Pin (R0)
Setting Up the MQTT Broker #
MQTT Broker Configuration: #
- MQTT Broker URL (IP or domain)
- Port number (often 8883 for SSL connections)
- Username and Password (if authentication is required)
- Client ID (optional but recommended)
- Topic(s) to publish/subscribe to
Certificates: Appropriate SSL certificates are required to establish an SSL connection. #
- CA Certificate
- Client Certificate (if two-way SSL is required)
- Client Private Key (if two-way SSL is required)
Understanding the Test program #
This code is for an ESP32-based NORVI GSM series device connected to a Quectel EC25 module via UART. It sets up a secure MQTT connection using SSL, subscribes to a topic to control a relay output, and periodically publishes the status of several digital inputs to an MQTT broker. The code also handles reconnection to both the network and the MQTT broker if connections are lost.
Download the example program.
Libraries and Includes #
#include <Arduino.h>
#include <Wire.h>
#include <WiFi.h> // Include the WiFi library for MAC address
#include <ArduinoJson.h>
#include "Secret.h" // The file to get the username and password of MQTT server
#include"datacake.h" //includes functions for interacting with Datacake.
Define Global Variables and Constants #
String gsm_send_serial(String command, int delay); //Function for sending AT commands to the GSM module.
//#define TINY_GSM_MODEM_SIM7600
#define SerialMon Serial
#define SerialAT Serial1
#define GSM_PIN ""
GPRS Credentials #
// Your GPRS credentials
const char apn[] = "dialogbb";
const char gprsUser[] = "";
const char gprsPass[] = "";
MQTT Details #
// MQTT details
String broker = "mqtt.datacake.co";
String MQTTport = "8883";
Sleep Settings #
#define uS_TO_S_FACTOR 1000000ULL
#define TIME_TO_SLEEP 60
Pin Definitions #
#define UART_BAUD 115200
#define MODEM_TX 32
#define MODEM_RX 33
#define GSM_RESET 21
#define D0 13
#define D1 34
#define D2 35
#define D3 14
#define R0 12
#define MAC_ADDRESS_SIZE 18
//Assuming MAC address is in format "XX:XX:XX:XX:XX:XX"
MAC Address #
byte mac[6];
String str_macAddress;
Timing and Device Details #
unsigned long prevMillis = 0;
const unsigned long interval = 60000; // Interval for sending messages
// Device-specific details
const char* deviceSerial = "*************"; // Replace with your device serial
MQTT Callback Function #
// Process incoming MQTT messages.
void mqttCallback(char* topic, String payload, unsigned int len) {
SerialMon.print("Message arrived [");
SerialMon.print(topic);
SerialMon.print("]: ");
// SerialMon.write(payload, len);
// SerialMon.println();
// Extract serial number from the topic
String topicStr = String(topic);
int firstSlash = topicStr.indexOf('/');
int lastSlash = topicStr.lastIndexOf('/');
String MAC_ID = topicStr.substring(firstSlash + 1, lastSlash);
SerialMon.print("MAC ID: ");
SerialMon.println(MAC_ID);
// Extract the payload
int state = payload.toInt();
SerialMon.print("STATE: ");
SerialMon.println(state);
// Handle state changes here
if (state == 0) {
digitalWrite(R0, LOW);
} else if (state == 1) {
digitalWrite(R0, HIGH);
}
}
Setup Function #
void setup() {
// Set console baud rate
Serial.begin(115200);
delay(10);
SerialAT.begin(UART_BAUD, SERIAL_8N1, MODEM_RX, MODEM_TX);
delay(2000);
pinMode(GSM_RESET, OUTPUT);
digitalWrite(GSM_RESET, HIGH); // RS-485
delay(2000);
pinMode(D0, INPUT);
pinMode(D1, INPUT);
pinMode(D2, INPUT);
pinMode(D3, INPUT);
pinMode(R0, OUTPUT);
Init();
connectToGPRS();
connectToMQTT();
}
Main Loop #
void loop() {
//Check if 60 Seconds Have Passed
if (millis() - prevMillis >= interval) {
prevMillis = millis();
// Read input values (assuming these are commented out for now)
bool IN1 = digitalRead(D0);
bool IN2 = digitalRead(D1);
bool IN3 = digitalRead(D2);
bool IN4 = digitalRead(D3);
String DI0 = String(IN1 ? 1 : 0);
String DI1 = String(IN2 ? 1 : 0);
String DI2 = String(IN3 ? 1 : 0);
String DI3 = String(IN4 ? 1 : 0);
//Prepare MQTT Publish Commands
StringDigital_input_0 = "dtck-pub/dc_mqtt_broker/9ccc450d-96ec-4676-8e57-a3661bf528a6/D0";
String Digital_input_1 = "dtck-pub/dc_mqtt_broker/9ccc450d-96ec-4676-8e57-a3661bf528a6/D1";
String Digital_input_2 = "dtck-pub/dc_mqtt_broker/9ccc450d-96ec-4676-8e57-a3661bf528a6/D2";
String Digital_input_3 = "dtck-pub/dc_mqtt_broker/9ccc450d-96ec-4676-8e57-a3661bf528a6/D3";
String command_0 = "AT+QMTPUBEX=0,1,1,0,\"" + Digital_input_0 + "\"," +String(DI0.length());
String publishResponse_0 = gsm_send_serial(command_0, 1000);
publishResponse_0 += gsm_send_serial(DI0 + "\x1A", 1000);
String command_1 = "AT+QMTPUBEX=0,1,1,0,\"" + Digital_input_1 + "\"," +String(DI1.length());
String publishResponse_1 = gsm_send_serial(command_1, 1000);
publishResponse_1 += gsm_send_serial(DI1 + "\x1A", 1000);
String command_2 = "AT+QMTPUBEX=0,1,1,0,\"" + Digital_input_2 + "\"," +String(DI2.length());
String publishResponse_2 = gsm_send_serial(command_2, 1000);
publishResponse_2 += gsm_send_serial(DI2 + "\x1A", 1000);
String command_3 = "AT+QMTPUBEX=0,1,1,0,\"" + Digital_input_3 + "\"," +String(DI3.length());
//sends the AT command to the GSM module to publish the input states.
String publishResponse_3 = gsm_send_serial(command_3, 1000);
publishResponse_3 += gsm_send_serial(DI3 + "\x1A", 1000);
// Check if the publish command was successful
if (publishResponse_0.indexOf("ERROR") != -1) {
SerialMon.println("MQTT publish failed. Reconnecting...");
connectToMQTT();
if (!isGPRSConnected()) {
SerialMon.println("GPRS connection lost. Reconnecting...");
connectToGPRS();
connectToMQTT();
}
if (!isNetworkConnected()) {
SerialMon.println("Network connection lost. Reconnecting...");
Init();
connectToGPRS();
connectToMQTT();
}
}
}
// Handle incoming MQTT messages
handleIncomingMessages();
}
Handling Incoming Messages #
void handleIncomingMessages() {
// Request messages from the EC25 module
String response = gsm_send_serial("AT+QMTRECV=0,1", 1000);
// Print the raw response for debugging
SerialMon.print("Raw MQTT Response: ");
SerialMon.println(response);
// Check if the response contains "+QMTRECV:"
int startPos = response.indexOf("+QMTRECV:");
if (startPos != -1) {
// Extract the part of the response containing the message
String messagePart = response.substring(startPos);
// Print the extracted message part for debugging
SerialMon.print("Extracted Message Part: ");
SerialMon.println(messagePart);
// Remove any extraneous text before "+QMTRECV:"
messagePart.trim();
// Check if the response is in the expected format
if (messagePart.startsWith("+QMTRECV:")) {
// Extract the part after "+QMTRECV:" (skip the "+QMTRECV:" prefix)
messagePart = messagePart.substring(messagePart.indexOf(':') + 1);
// Extract client_idx and msg_id
int firstComma = messagePart.indexOf(',');
int secondComma = messagePart.indexOf(',', firstComma + 1);
String client_idx = messagePart.substring(0, firstComma);
String msg_id = messagePart.substring(firstComma + 1, secondComma);
// Extract topic
int firstQuote = messagePart.indexOf('"', secondComma + 1);
int secondQuote = messagePart.indexOf('"', firstQuote + 1);
String topic = messagePart.substring(firstQuote + 1, secondQuote);
// Extract payload length
int thirdComma = messagePart.indexOf(',', secondQuote + 1);
int fourthComma = messagePart.indexOf(',', thirdComma + 1);
String payloadLengthStr = messagePart.substring(thirdComma + 1, fourthComma);
int payloadLength = payloadLengthStr.toInt();
// Extract payload
int thirdQuote = messagePart.indexOf('"', fourthComma + 1);
int fourthQuote = messagePart.indexOf('"', thirdQuote + 1);
String payload = messagePart.substring(thirdQuote + 1, fourthQuote );
// Debug print
SerialMon.print("Received Topic: ");
SerialMon.println(topic);
SerialMon.print("Received Payload: ");
SerialMon.println(payload);
SerialMon.print("Payload Length: ");
SerialMon.println(payloadLength);
// Convert topic and payload to mutable char arrays
char topicArr[topic.length() + 1];
// byte payloadArr[payload.length() + 1];
topic.toCharArray(topicArr, topic.length() + 1);
// Call the MQTT callback function with the extracted values
mqttCallback(topicArr, payload, payload.length());
} else {
SerialMon.println("Unexpected response format.");
}
} else {
SerialMon.println("No new MQTT messages or unexpected response format.");
}
}
Initialization Function #
void Init(void) { // Connecting with the network and GPRS
delay(5000);
gsm_send_serial("AT+CFUN=1", 10000);
gsm_send_serial("AT+CPIN?", 10000);
gsm_send_serial("AT+CSQ", 1000);
gsm_send_serial("AT+CREG?", 1000);
gsm_send_serial("AT+COPS?", 1000);
gsm_send_serial("AT+CGATT?", 1000);
gsm_send_serial("AT+CPSI?", 500);
gsm_send_serial("AT+CGDCONT=1,\"IP\",\"dialogbb\"", 1000);
gsm_send_serial("AT+CGACT=1,1", 1000);
gsm_send_serial("AT+CGATT?", 1000);
gsm_send_serial("AT+CGPADDR=1", 500);
gsm_send_serial("ATI", 500);
}
void connectToGPRS(void) {
gsm_send_serial("AT+CGATT=1", 1000);
gsm_send_serial("AT+CGDCONT=1,\"IP\",\"dialogbb\"", 1000);
gsm_send_serial("AT+CGACT=1,1", 1000);
gsm_send_serial("AT+CGPADDR=1", 500);
}
void connectToMQTT(void) {
// Initialize MQTT configurations
gsm_send_serial("AT+QMTCFG=\"recv/mode\",0,0,1", 1000);
gsm_send_serial("AT+QMTCFG=\"SSL\",0,1,2", 1000);
int cert_length = mqtt_ca_cert.length(); // Get the length of the CA certificate
String ca_cert = "AT+QFUPL=\"RAM:datacake_ca.pem\"," + String(cert_length) + ",100";
gsm_send_serial(ca_cert, 1000); // Send the command
delay(1000);
gsm_send_serial(mqtt_ca_cert, 1000); // Send the command to upload CA singned certificate
delay(1000);
gsm_send_serial("AT+QSSLCFG=\"cacert\",2,\"RAM:datacake_ca.pem\"", 1000);
gsm_send_serial("AT+QMTOPEN=0,\"mqtt.datacake.co\",8883", 1000);
delay(2000); // Wait for the connection to establish
String mqtt_conn = "AT+QMTCONN=0,\"EC25 client\",\"" + username + "\",\"" + password + "\"";
gsm_send_serial(mqtt_conn, 1000);
delay(2000); // Wait for the connection to establish
// Subscribe to the downlink topic
String downlinkTopic = "dtck/dc_mqtt_broker/9ccc450d-96ec-4676-8e57-a3661bf528a6/R1";
String subscribeCommand = "AT+QMTSUB=0,1,\"" + downlinkTopic + "\",0"; // QoS 1
gsm_send_serial(subscribeCommand, 1000);
delay(2000); // Allow time for subscription confirmation
// Check for subscription confirmation
String response = gsm_send_serial("AT+QMTSUB?", 1000); // Check subscription status
SerialMon.print("Subscription Response: ");
SerialMon.println(response);
// Debug: Print MQTT connection status
String connStatus = gsm_send_serial("AT+QMTCONN?", 1000);
SerialMon.print("MQTT Connection Status: ");
SerialMon.println(connStatus);
}
Utility Functions #
bool isNetworkConnected() {
String response = gsm_send_serial("AT+CREG?", 3000);
return (response.indexOf("+CREG: 0,1") != -1 || response.indexOf("+CREG: 0,5") != -1);
}
bool isGPRSConnected() {
String response = gsm_send_serial("AT+CGATT?", 3000);
return (response.indexOf("+CGATT: 1") != -1);
}
String gsm_send_serial(String command, int timeout) {
String buff_resp = "";
Serial.println("Send ->: " + command);
SerialAT.println(command);
unsigned long startMillis = millis();
while (millis() - startMillis < timeout) {
while (SerialAT.available()) {
char c = SerialAT.read();
buff_resp += c;
}
delay(10); // Small delay to allow for incoming data to accumulate
}
Serial.println("Response ->: " + buff_resp);
return buff_resp;
}
Software Configurations #
Essential AT Commands #
After uploading the code to the NORVI GSM series device, it’s important to verify that the SIM card is correctly registered and the device is able to communicate with the network.
This can be done by checking the serial monitor for specific AT command responses.
Functionality level set (AT+CFUN=1) #
- sets the modem to full functionality mode, enabling it to connect to the network and operate normally.
SIM check (AT+CPIN?) #
- +CPIN: READY indicates that the SIM card is inserted and ready for use.
Signal Quality Check (AT+CSQ), #
Good Signal: An RSSI value of 18 indicates strong signal strength.
- Weak Signal: A lower value, like 5, suggests weak signal strength, which could cause connection issues.
Network Registration Check (AT+CREG?), #
Registered: The 1 indicates the device is registered on its home network.
- Not Registered: A response of 0,0 means the device isn’t registered and isn’t searching for a network, possibly due to SIM or network issues.
Operator Selection Check (AT+COPS?), #
- This response shows that the device is connected to the operator “Dialog.”
- The value “7” indicates the type of network (e.g., LTE).
- If the operator details are missing or incorrect, it may indicate a problem with network registration or SIM card compatibility.
Query MQTT Connection Status(AT+QMTCONN?) #
The 3 suggests that the MQTT connection is established and connected.
Query Attach Status(AT+CGATT?) #
- This command output shows that the device is currently attached to the GPRS network.
- If the response were +CGATT: 0, it would indicate that the device isn’t connected, which would prevent any data transfer over the cellular network.
Query Packet-Switched Service Information(AT+CPSI?) #
This command is typically used to get information about the serving cell, including the network status, cell ID, and signal quality.
- The ‘ERROR’ response suggests that the command might not be supported or a configuration issue. Some modules may require different configurations or initialization before using this command.
Define PDP Context(AT+CGDCONT=1,”IP”,”dialogbb”) #
- This command defines a PDP (Packet Data Protocol) context.
- It specifies the context identifier (1 in this case), PDP type (IP), and the APN (Access Point Name), which is dialog bb.
Activate/Deactivate PDP Context(AT+CGACT=1,1) #
- This command activates the specified PDP context.
- The first 1 indicates the context identifier, and the second 1 specifies that it should be activated.
Query PDP Address(AT+CGPADDR=1) #
- This command queries the IP address assigned to the specified PDP context.
- The device has been assigned the IP address 10.101.103.185 for the PDP context 1.
Configure MQTT Receive Mode(AT+QMTCFG=”recv/mode”,0,0,1) #
- The parameters typically include the mode of receiving messages (QoS 0 or 1).
- Indicates that there was an issue. This could be due to an incorrect parameter or an unsupported feature
Configures SSL settings for MQTT(AT+QMTCFG=”SSL”,0,1,2) #
- “SSL” specifies that SSL/TLS encryption is being configured.
- 0 refers to the MQTT client index.
- 1 enables SSL, and 2 could refer to a specific SSL version or mode.
Uploads a file ( CA certificate) (AT+QFUPL=”RAM:datacake_ca.pem”,1806,100) #
- “RAM:datacake_ca.pem” specifies the filename and location in the RAM
- 1806 is the size of the file in bytes.
- 100 is the timeout value in seconds for the upload process.
Configures the SSL settings for the client(AT+QSSLCFG=”cacert”,2,”RAM:datacake_ca.pem”) #
- “cacert”: Refers to the CA certificate.
- 2: Refers to the SSL context index.
- “RAM:datacake_ca.pem”: The location and name of the CA certificate stored in the modem’s RAM.
Open MQTT Connection(AT+QMTOPEN=0,”mqtt.datacake.co”,8883) #
- 0: MQTT client identifier.
- “mqtt.datacake.co”: The hostname of the MQTT broker.
- 8883: The port number for SSL/TLS connections.
Connect to MQTT Broker( AT+QMTCONN=0,”EC25 client”,”********”,”*******”,”*********” ) #
- Connects to the MQTT broker with client ID, username , and password .
Subscribe to MQTT Topic( AT+QMTSUB=0,1,”dtck/dc……8a6/**”,0) #
- Subscribes to the MQTT topic with QoS level 0(relay output) or 1(digital inputs).
Setting Up the MQTT Broker #
MQTT Broker Configuration #
Use the MQTT.fx software as a Subscriber to set up the MQTT broker for the NORVI GSM series device.
Below is the essential information required to configure the MQTT broker.
- MQTT Broker URL (IP or domain)
- Port number
- Client ID (optional but recommended)
- Username and Password (if authentication is required)
- Topic(s) to publish/subscribe to
String Digital_input_0 = "dtck-pub/dc_mqtt_broker/9ccc450d-96ec-4676-8e57-a3661bf528a6/D0";
String Digital_input_1 = "dtck-pub/dc_mqtt_broker/9ccc450d-96ec-4676-8e57-a3661bf528a6/D1";
String Digital_input_2 = "dtck-pub/dc_mqtt_broker/9ccc450d-96ec-4676-8e57-a3661bf528a6/D2";
String Digital_input_3 = "dtck-pub/dc_mqtt_broker/9ccc450d-96ec-4676-8e57-a3661bf528a6/D3";
//MQTT Topic
Steps to Configure in MQTT.fx #
Check this link for detailed instructions on how to configure the MQTT broker and the Subscriber.
- Download MQTT.fx client and install it.
- Open the MQTT.fx and click the Settings icon.
- Click + to create a profile.
- Enter the Connection Profile and General information.
- Enter the User Credentials information
- After completing the above steps, click Apply > OK to save.
- Then, select the name of the file just created in the profile box and click Connect.
- If the round icon in the top-right corner is green, the connection to IoT Hub is successful, and publishing and subscribing operations can be performed.
- Select the Subscribe tab in the client, scan and select the topic name, and click Subscribe to subscribe to the topic.
- The subscribing result can be seen in the bottom right corner.
Integration with Data Visualization Platform #
The NORVI Devices can be connected to an IoT platform, allowing users to visualize data collected by the NORVI devices. The platform used in this case is DATACAKE. Here’s how to integrate the NORVI devices with DATACAKE.
- Access the DATACAKE from this link and navigate the Datacake dashboard. Select the “Add Devices”.
- For the MQTT integration select the API. This will add the device to the Datacake account.
- Click on Configuration Scroll down a bit and go to the new panel “MQTT Configuration”. Press on “Add New MQTT Broker”. Fill in the server details and add.
- To create a first database field, click on the “Add Field”. This will open another modal asking for some required input for the fields.
When typing the name for that field, the identifier auto-fills. This identifier is unique and cannot be changed. it’s required for accessing the field from within the payload decoder or API.
- Added the downlink details.
- After the settings in the configuration section, the dashboard can be created. Use the Tab Bar on the Device to navigate into the ”Dashboard” view.