简介

本文主要介绍如何使用 ESP8266 连接MQTT服务器,利用 PubSubClient 库,实现客户端与 MQTT 服务器的连接、订阅、收发消息等功能。服务器使用EMQX为例。部分代码使用 PubSubClient 库自带的示例。

连接到MQTT服务器分为TCP方式连接和TLS/SSL方式连接,使用EMQX的公共服务器的话是TCP方式连接,使用EMQX的私有服务器为TLS/SSL方式连接。TCP和TLS/SSL连接的区别在于定义espClient的方式不同,连接服务器的端口不同,以及TLS/SSL需要填写服务器指纹,并使用espClient.setFingerprint(fingerprint);设置指纹

EMQX公共服务器链接使用说明

EMQX私有服务器链接使用说明,私有服务器有一定的免费额度,每月1百万连接分钟数(大约23个设备持续在线连接1个月)和1G流量。

首先先在Arduino IDE的 管理库 中添加 PubSubClient


常用函数

WiFiClient espClient;  //TCP连接
WiFiClientSecure espClient;  //TLS/SSL连接
PubSubClient client(espClient);  //定义client
espClient.setFingerprint(fingerprint);  //TLS/SSL连接,设置指纹
client.setServer(mqtt_server, mqtt_port);   //设置服务器地址和端口
client.setCallback(callback);    //设置接收消息的函数
client.connect(clientId.c_str());  //连接服务器,不使用用户名和密码
client.connect(clientId.c_str() , mqtt_name , mqtt_password);  //连接服务器,使用用户名和密码
client.connected();  //返回是否连接成功,0为失败,1为成功
client.state();  //返回连接状态码
client.publish(topic, "connected");  //向主题发布消息,可以不用订阅主题
client.subscribe(topic);  //订阅主题,用于接收消息,qos默认为0
client.subscribe(topic,qos);  //订阅主题,用于接收消息,并填写qos
client.unsubscribe(topic);  //取消订阅
client.loop();  

头文件

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

定义基本信息

const char* ssid = "";  //WiFi名
const char* password = "";  //WiFi密码
const char* mqtt_server = "";  //MQTT服务器地址
const int mqtt_port = 1883;   //服务器连接端口
const char* mqtt_name = "";  //MQTT连接用户名(可选)
const char* mqtt_password = "";  //MQTT连接密码(可选)
const char* topic="";   //订阅、发布的主题

MQTT连接用户名和密码都是可选的,具体要根据MQTT服务器。一般来说,使用TCP连接服务器,端口为1883;使用TLS/SSL连接服务器,端口为8883。

如果使用 EMQX的公共服务器,使用TCP连接,服务器地址为broker.emqx.io,端口为1883,连接用户名为emqx,密码为public,也可以不填。

如果使用私有服务器,使用TLS/SSL连接,服务器地址根据控制台具体信息,端口为8883,用户名和密码自己在控制台里定义。

定义 espClient client 。如果使用TCP连接,则按以下定义:

WiFiClient espClient;
PubSubClient client(espClient);

TLS/SSL连接

如果使用TLS/SSL连接,则按以下连接:

设置服务器和证书:

// WiFi和MQTT客户端初始化
BearSSL::WiFiClientSecure espClient;
PubSubClient mqtt_client(espClient);

// NTP服务器设置
const char *ntp_server = "pool.ntp.org";     // 默认NTP服务器
// const char* ntp_server = "cn.pool.ntp.org"; // 为国内推荐的NTP服务器
const long gmt_offset_sec = 0;            // 以秒为单位的GMT时差(根据时区进行调整)
const int daylight_offset_sec = 0;        // 夏令时偏移量(秒)

// MQTT代理的SSL证书
// 如果使用公共服务器: broker.emqx.io
的DigiCert Global Root G2
static const char ca_cert[]
PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----
)EOF";

// 如果使用EMQX的Serverless服务
/*
static const char ca_cert[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
)EOF";
*/

setup函数

如果TLS/SSL连接,则需要使用 Client.setFingerprint(fingerprint);设置指纹

void setup() {
  Serial.begin(115200, SERIAL_8N1, SERIAL_TX_ONLY);
  pinMode(BUILTIN_LED, OUTPUT);   
  Serial.begin(115200);
  setup_wifi();  //连接WiFi
//syncTime();  // 如果使用TLS/SSL连接,需要添加此行,X.509验证需要同步时间
  
//espClient.setFingerprint(fingerprint);  
  client.setServer(mqtt_server, mqtt_port);   //设置服务器地址和端口
  client.setCallback(callback);    //设置接收消息的函数
}

/*
void syncTime() {
    configTime(gmt_offset_sec, daylight_offset_sec, ntp_server);
    Serial.print("Waiting for NTP time sync: ");
    while (time(nullptr) < 8 * 3600 * 2) {
        delay(1000);
        Serial.print(".");
    }
    Serial.println("Time synchronized");
}
*/

建立WiFi连接

void setup_wifi() {
  digitalWrite(BUILTIN_LED, LOW);
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  digitalWrite(BUILTIN_LED, HIGH);
}

建立MQTT连接

建立MQTT连接以及断开后的重连,这里需要定义设备名, 设备名可以自己定义,也可以使用XX-随机数,XX-IP地址等,例如ESP8266-1898,ESP8266-123.456.7.891

如果存在相同的设备ID,那么所有拥有相同设备ID的设备将会不断的断开重连。所以建议在设备ID后添加上IP地址。

前面提过,连接用户名和密码可填可不填,如果使用用户名和密码,则使用 client.connect(clientId.c_str() , mqtt_name , mqtt_password) 来连接MQTT服务器,如果不使用用户名和密码,则使用 client.connect(clientId.c_str()) 来连接。

void reconnect() {
//循环直到回连成功
//BearSSL::X509List serverTrustedCA(ca_cert);
   //如果使用TLS/SSL连接,需要添加这两句
//espClient.setTrustAnchors(&serverTrustedCA);
  while (!client.connected()) 
  {
    Serial.print("Attempting MQTT connection...");
    String clientId = "ESP8266-";  //设备名
//  clientId += String(random(0xffff), HEX);     使用XX-随机数
    clientId += WiFi.localIP().toString().c_str();  //XX-IP地址
//  尝试连接
    if (client.connect(clientId.c_str() , mqtt_name , mqtt_password))     //client.connect(clientId.c_str())    
    {  
      Serial.println("connected");  //连接成功
      client.publish(topic, "connected");  //连接成功后,向主题发布消息,也可以不发布
      client.subscribe(topic);  //订阅主题
    } 
    else 
    {
      char err_buf[128];
      espClient.getLastSSLError(err_buf, sizeof(err_buf));
      Serial.print("failed, rc=");  //连接失败,并打印状态码
      Serial.print(client.state());
      Serial.print("  SSL error: ");
      Serial.println(err_buf);
      Serial.println(" try again in 5 seconds");  
      delay(5000);  //5秒后再次尝试连接
    }
  }
}

 PubSubClient 库中  PubSubClient.h 对于状态码的定义

// Possible values for client.state()
#define MQTT_CONNECTION_TIMEOUT     -4
#define MQTT_CONNECTION_LOST        -3
#define MQTT_CONNECT_FAILED         -2
#define MQTT_DISCONNECTED           -1
#define MQTT_CONNECTED               0
#define MQTT_CONNECT_BAD_PROTOCOL    1
#define MQTT_CONNECT_BAD_CLIENT_ID   2
#define MQTT_CONNECT_UNAVAILABLE     3
#define MQTT_CONNECT_BAD_CREDENTIALS 4
#define MQTT_CONNECT_UNAUTHORIZED    5

接收订阅的消息

void callback(char* topic, byte* payload, unsigned int length)  //主题,消息,消息长度
{
  Serial.print("Message arrived [");
  Serial.print(topic);  //打印主题
  Serial.print("] ");
  
  char msg[length];
  for (int i = 0; i < length; i++) 
    msg[i]=(char)payload[i];  //将消息转存到msg中
  snprintf (msg, length+1, msg);
  Serial.println(msg);  //打印消息
 
  if (!strcmp(msg, "on")) digitalWrite(BUILTIN_LED, LOW);   //如果接收到on,则将esp8266板载的led点亮,注意,将BUILTIN_LED置为低时,LED会被点亮
  if (!strcmp(msg, "off")) digitalWrite(BUILTIN_LED, HIGH);  //如果接收到on,则将led熄灭,注意,将BUILTIN_LED置为高时,LED会被熄灭
}

loop函数

void loop() 
{
  if (!client.connected()) 
 //如果断连,则进行重连
  {
    reconnect();
  }
  client.loop();
 

  unsigned long now = millis();
  if (now - lastMsg > 2000) {
    lastMsg = now;
    ++value;
    snprintf (msg, MSG_BUFFER_SIZE, "hello world #%ld", value);
    Serial.print("Publish message: ");
    Serial.println(msg);
    client.publish(topic, msg);
       //每2秒发送一次消息
  }
}

完整代码示例

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

const char* ssid = "";  //WiFi名
const char* password = "";  //WiFi密码
const char* mqtt_server = "broker.emqx.io";  //MQTT服务器地址
const int mqtt_port = 1883;   //服务器连接端口
const char* mqtt_name = "emqx";  //MQTT连接用户名(可选)
const char* mqtt_password = "public";  //MQTT连接密码(可选)
const char* topic="testtopic";   //订阅、发布的主题

/*
// NTP服务器设置
const char *ntp_server = "pool.ntp.org";     // 默认NTP服务器
// const char* ntp_server = "cn.pool.ntp.org"; // 为国内推荐的NTP服务器
const long gmt_offset_sec = 0;            // 以秒为单位的GMT时差(根据时区进行调整)
const int daylight_offset_sec = 0;        // 夏令时偏移量(秒)

// WiFi和MQTT客户端初始化
WiFiClientSecure espClient;
PubSubClient client(espClient);


// MQTT代理的SSL证书
// 如果使用公共服务器: broker.emqx.io的DigiCert Global Root G2
static const char ca_cert[]
PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----
)EOF";*/

// 如果使用EMQX的Serverless服务
/*
static const char ca_cert[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
)EOF";
*/

WiFiClient espClient;   //如果使用TLS/SSL连接,需要注释掉这两行
PubSubClient client(espClient);

unsigned long lastMsg = 0;
#define MSG_BUFFER_SIZE  (50)
char msg[MSG_BUFFER_SIZE];
int value = 0;

void setup() {
  Serial.begin(115200, SERIAL_8N1, SERIAL_TX_ONLY);
  pinMode(BUILTIN_LED, OUTPUT);   
  Serial.begin(115200);
  setup_wifi();  //连接WiFi
//syncTime();  // 如果使用TLS/SSL连接,需要添加此行,X.509验证需要同步时间
  
  client.setServer(mqtt_server, mqtt_port);   //设置服务器地址和端口
  client.setCallback(callback);    //设置接收消息的函数
}

/*
void syncTime() {
    configTime(gmt_offset_sec, daylight_offset_sec, ntp_server);
    Serial.print("Waiting for NTP time sync: ");
    while (time(nullptr) < 8 * 3600 * 2) {
        delay(1000);
        Serial.print(".");
    }
    Serial.println("Time synchronized");
}
*/

void setup_wifi() {
  digitalWrite(BUILTIN_LED, LOW);
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  digitalWrite(BUILTIN_LED, HIGH);
}

void reconnect() {
//循环直到回连成功
//BearSSL::X509List serverTrustedCA(ca_cert);   //如果使用TLS/SSL连接,需要添加这两句
//espClient.setTrustAnchors(&serverTrustedCA);
  while (!client.connected()) 
  {
    Serial.print("Attempting MQTT connection...");
    String clientId = "ESP8266-";  //设备名
//  clientId += String(random(0xffff), HEX);     使用XX-随机数
    clientId += WiFi.localIP().toString().c_str();  //XX-IP地址
//  尝试连接
    if (client.connect(clientId.c_str() , mqtt_name , mqtt_password))     //client.connect(clientId.c_str())    
    {  
      Serial.println("connected");  //连接成功
      client.publish(topic, "connected");  //连接成功后,向主题发布消息,也可以不发布
      client.subscribe(topic);  //订阅主题
    } 
    else 
    {
      char err_buf[128];
      espClient.getLastSSLError(err_buf, sizeof(err_buf));
      Serial.print("failed, rc=");  //连接失败,并打印状态码
      Serial.print(client.state());
      Serial.print("  SSL error: ");
      Serial.println(err_buf);
      Serial.println(" try again in 5 seconds");  
      delay(5000);  //5秒后再次尝试连接
    }
  }
}

void callback(char* topic, byte* payload, unsigned int length)  //主题,消息,消息长度
{
  Serial.print("Message arrived [");
  Serial.print(topic);  //打印主题
  Serial.print("] ");
  
  char msg[length];
  for (int i = 0; i < length; i++) 
    msg[i]=(char)payload[i];  //将消息转存到msg中
  snprintf (msg, length+1, msg);
  Serial.println(msg);  //打印消息
 
  if (!strcmp(msg, "on")) digitalWrite(BUILTIN_LED, LOW);   //如果接收到on,则将esp8266板载的led点亮,注意,将BUILTIN_LED置为低时,LED会被点亮
  if (!strcmp(msg, "off")) digitalWrite(BUILTIN_LED, HIGH);  //如果接收到on,则将led熄灭,注意,将BUILTIN_LED置为高时,LED会被熄灭
}

void loop() 
{
  if (!client.connected())  //如果断连,则进行重连
  {
    reconnect();
  }
  client.loop();
 
  unsigned long now = millis();
  if (now - lastMsg > 2000) {
    lastMsg = now;
    ++value;
    snprintf (msg, MSG_BUFFER_SIZE, "hello world #%ld", value);
    Serial.print("Publish message: ");
    Serial.println(msg);
    client.publish(topic, msg);  //每2秒发送一次消息
  }
}


循之际,如星夜般的幻想。