esp8266+mqtt入门笔记

本文是【太极创客】零基础入门学用物联网 – MQTT基础篇的学习笔记。

可能用到的头文件

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

mqtt服务端准备

服务端使用win10电脑自己起了个mqtt服务,客户端用的mqtt.fx是1.7.1版本。参考了MQTT | Windows + mosquitto搭建MQTT Broker(本地服务器)与MQTTX客户端联调

ESP8266连接服务器

const char* mqttServer = "127.0.0.1";
const char* mqttUserName = "admin";
const char* mqttPassword = "mqtt";
// 两个与mqtt相关的全局变量
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);

void setup() {
  // 设置MQTT服务器和端口号
  mqttClient.setServer(mqttServer, 1883);
  // 连接MQTT服务器
  connectMQTTServer();
}

void loop()
{
  if (mqttClient.connected()) {
    // 这里以确认连接成功,所以可以调用发送信息的函数。
    // 保持心跳
    mqttClient.loop();
  } else {                      // 如果开发板未能成功连接服务器
    connectMQTTServer(); // 则尝试连接服务器
  }
}

void connectMQTTServer() {
  // 连接MQTT服务器,带上客户端ID,用户名、密码。
  String clientId = "esp8266-" + WiFi.macAddress();
  if (mqttClient.connect(clientId.c_str(), mqttUserName, mqttPassword)) { 
    Serial.println("MQTT Server Connected.");
    Serial.println("Server Address: ");
    Serial.println(mqttServer);
    Serial.println("ClientId:");
    Serial.println(clientId);
  } else {
    Serial.print("MQTT Server Connect Failed. Client State:");
    Serial.println(mqttClient.state());
    delay(3000);
  }
}

如果连接失败,可检查错误码,对照下表排查问题。

返回码返回码描述
0成功连接
1连接被服务端拒绝,原因是不支持客户端的MQTT协议版本
2连接被服务端拒绝,原因是不支持客户端标识符的编码。可能造成此原因的是客户端标识符编码是UTF-8,但是服务端不允许使用此编码。
3连接被服务端拒绝,原因是服务端不可用。即,网络连接已经建立,但MQTT服务不可用。
4连接被服务端拒绝,原因是用户名或密码无效。
5连接被服务端拒绝,原因是客户端未被授权连接到此服务端。

发布消息

当连接成功后就可以发布消息了

// 发布信息
void pubMQTTmsg(){
  static int value; // 客户端发布信息用数字
 
  // 建立发布主题。主题名称以Taichi-Maker-为前缀,后面添加设备的MAC地址。
  // 这么做是为确保不同用户进行MQTT信息发布时,ESP8266客户端名称各不相同,
  String topicString = "Taichi-Maker-Pub-" + WiFi.macAddress();
 
  // 建立发布信息。信息内容以Hello World为起始,后面添加发布次数。
  String messageString = "Hello World " + String(value++); 
  
  // 实现ESP8266向主题发布信息
  if(mqttClient.publish(topicString.c_str(), messageString.c_str())){
    Serial.println("Publish Topic:");Serial.println(topicString);
    Serial.println("Publish message:");Serial.println(messageString );    
  } else {
    Serial.println("Message Publish Failed."); 
  }
}

接收消息

void setup()
{
  // 设置MQTT服务器和端口号
  mqttClient.setServer(mqttServer, 1883);
  // 设置MQTT订阅回调函数
  mqttClient.setCallback(receiveCallback);
  // 连接MQTT服务器
  connectMQTTServer();
}

void connectMQTTServer()
{
  // 连接MQTT服务器,带上客户端ID,用户名、密码。
  String clientId = "xxx";
  if (mqttClient.connect(clientId.c_str(), mqttUserName, mqttPassword))
  {
    // 连接成功后可以用串口输出一些信息。
    // 连接成功口就可订阅主题。
    subscribeTopic();
  }
  else
  {
    Serial.print("MQTT Server Connect Failed. Client State:");
    Serial.println(mqttClient.state());
    delay(3000);
  }
}

// 收到信息后的回调函数
// topic:主题
// paylod:字符列表
// length:字符列表长度
void receiveCallback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message Received [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println("");
  Serial.print("Message Length(Bytes) ");
  Serial.println(length);
 
  if ((char)payload[0] == '1') {     // 如果收到的信息以“1”为开始
    digitalWrite(BUILTIN_LED, LOW);  // 则点亮LED。
    Serial.println("LED ON");
  } else {                           
    digitalWrite(BUILTIN_LED, HIGH); // 否则熄灭LED。
    Serial.println("LED OFF");
  }
}

// 订阅指定主题
void subscribeTopic(){
 
  // 建立订阅主题。主题名称以Taichi-Maker-Sub为前缀,后面添加设备的MAC地址。
  // 这么做是为确保不同设备使用同一个MQTT服务器测试消息订阅时,所订阅的主题名称不同
  String topicString = "Taichi-Maker-Sub-" + WiFi.macAddress();
  char subTopic[topicString.length() + 1];  
  strcpy(subTopic, topicString.c_str());
  
  // 通过串口监视器输出是否成功订阅主题以及订阅的主题名称
  if(mqttClient.subscribe(subTopic)){
    Serial.println("Subscrib Topic:");
    Serial.println(subTopic);
  } else {
    Serial.print("Subscribe Fail...");
  }  
}

主题

主题可以分级;有“+”、“#”两种通配符;“$”开头的是特殊主题;不建议主题中使用空格;不建议使用“/”开头的主题;主题尽量使用ASCII字符;主题区分大小写。

点灯小项目

用一块开发板上的按键控制另一块开发板上的灯
控制端代码

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

#define button D3

const char *mqttServer = "192.168.1.6";
const char *mqttUserName = "admin";
const char *mqttPassword = "mqtt";

const char *ssid = "xxxx";
const char *password = "88888888";

// 两个与mqtt相关的全局变量
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);


void setup()
{
  pinMode(button, INPUT_PULLUP);
  Serial.begin(9600);
  WiFi.begin(ssid, password);
  int i = 0;
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(1000);
    Serial.print(i++);
    Serial.print(".");
  }
  Serial.println("name: ");
  Serial.println(WiFi.SSID());
  Serial.println("IP: ");
  Serial.println(WiFi.localIP());

  // 设置MQTT服务器和端口号
  mqttClient.setServer(mqttServer, 1883);
  // 连接MQTT服务器
  connectMQTTServer();
}

void loop()
{
  int key = refresh_btn_status();
  if (key >= 0) {
    String msg = key ? "HEIG" : "LOW";
    Serial.println(msg);    // 串口输出当前按键行为。
    if (key == HIGH && mqttClient.connected()) {    // 当松手时发布消息
      pubMQTTmsg();
    }
  }
  if (mqttClient.connected())
  {
    // 保持心跳
    mqttClient.loop();
  }
  else
  {                      // 如果开发板未能成功连接服务器
    connectMQTTServer(); // 则尝试连接服务器
  }
}

void connectMQTTServer()
{
  // 连接MQTT服务器,带上客户端ID,用户名、密码。
  String clientId = "esp8266-key";
  if (mqttClient.connect(clientId.c_str(), mqttUserName, mqttPassword))
  {
    Serial.println("MQTT Server Connected.");
    Serial.println("Server Address: ");
    Serial.println(mqttServer);
    Serial.println("ClientId:");
    Serial.println(clientId);
  }
  else
  {
    Serial.print("MQTT Server Connect Failed. Client State:");
    Serial.println(mqttClient.state());
    delay(3000);
  }
}

void pubMQTTmsg(){
  JsonDocument doc;
  String messageString;
  doc["change"] = 0;
  doc["pwm"] = 100;
  serializeJson(doc, messageString);

  String topicString = "led1/change";
  
  // 实现ESP8266向主题发布信息
  if(mqttClient.publish(topicString.c_str(), messageString.c_str())){
    Serial.println("Publish Topic:");Serial.println(topicString);
    Serial.println("Publish message:");Serial.println(messageString );    
  } else {
    Serial.println("Message Publish Failed."); 
  }
}

// 按键检测,当按下时return LOW;松手时return HIGH。
// 长按或不按时return -1。
int refresh_btn_status() {
  // 当前的按键状态,这是一个防抖后的稳定值。
  static int btn_cur_status = digitalRead(button);
  // 当前的按键状态,这是一个瞬时值,不稳定的值。
  static int btn_tmp_status = digitalRead(button);
  // 按键稳定下来后的最后时间
  static unsigned long last_time = millis();
  // 按键延时时间,单位毫秒。
  unsigned long delay_inter = 20;

  btn_tmp_status = digitalRead(button);
  if (btn_tmp_status != btn_cur_status) {
    if ((millis() - last_time) > delay_inter) {
      // 按键状态确实变化了。btn_cur_status
      btn_cur_status = btn_tmp_status;
      if (btn_cur_status == HIGH) {
        return HIGH;
      } else {
        return LOW;
      }
    } else {
      // last_time = millis();
    }
  } else {
    // 有一次状态没变化,说明在抖动
    last_time = millis();
  }
  return -1;
}

被控端代码

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


const char *mqttServer = "192.168.1.6";
const char *mqttUserName = "admin";
const char *mqttPassword = "mqtt";

const char *ssid = "xxxx";
const char *password = "88888888";

// 两个与mqtt相关的全局变量
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);


int led_pwm = 0;
int led_flag = LOW;

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(9600);
  WiFi.begin(ssid, password);
  int i = 0;
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(1000);
    Serial.print(i++);
    Serial.print(".");
  }
  Serial.println("name: ");
  Serial.println(WiFi.SSID());
  Serial.println("IP: ");
  Serial.println(WiFi.localIP());

  // 设置MQTT服务器和端口号
  mqttClient.setServer(mqttServer, 1883);
  // 设置MQTT订阅回调函数
  mqttClient.setCallback(receiveCallback);
  // 连接MQTT服务器
  connectMQTTServer();
}

void loop()
{
  if (mqttClient.connected())
  {
    // 保持心跳
    mqttClient.loop();
  }
  else
  {                      // 如果开发板未能成功连接服务器
    connectMQTTServer(); // 则尝试连接服务器
  }
}

void connectMQTTServer()
{
  // 连接MQTT服务器,带上客户端ID,用户名、密码。
  String clientId = "esp8266-led";
  if (mqttClient.connect(clientId.c_str(), mqttUserName, mqttPassword))
  {
    Serial.println("MQTT Server Connected.");
    Serial.println("Server Address: ");
    Serial.println(mqttServer);
    Serial.println("ClientId:");
    Serial.println(clientId);
    subscribeTopic();
  }
  else
  {
    Serial.print("MQTT Server Connect Failed. Client State:");
    Serial.println(mqttClient.state());
    delay(3000);
  }
}


void receiveCallback(char* topic, byte* payload, unsigned int length) {
  JsonDocument doc;

  Serial.print("Message Received [");
  Serial.print(topic);
  Serial.print("] ");
  // 下面for循环是逐字符取出数据
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println("");

  // 下面三行是将数据转字符串,并串口打印结果。
  String msg;
  byteToStr(payload, length, msg);
  Serial.println(msg);

  Serial.print("Message Length(Bytes) ");
  Serial.println(length);

  DeserializationError error = deserializeJson(doc, msg);
  if (error) {
    Serial.println("deserialize error");
  } else {
    Serial.println(String(doc["change"]));
    Serial.println(String(doc["pwm"]));
    if (!doc["change"].isNull()) {
      led_flag = !led_flag;
    }
    if (!doc["pwm"].isNull()) {
      led_pwm = doc["pwm"];
    }
    digitalWrite(LED_BUILTIN, led_flag);
    if (led_flag == LOW) {
      Serial.print(String(led_pwm));
      Serial.println(" on");
      analogWrite(LED_BUILTIN, led_pwm);
    }
    else {
      Serial.println("off");
    }
  }
}

// 订阅指定主题
void subscribeTopic(){
  // 订阅主题,并通过串口监视器输出是否成功、订阅主题、以及订阅的主题名称
  String subTopic = "led1/change";
  if(mqttClient.subscribe(subTopic.c_str())){
    Serial.println("Subscrib Topic:");
    Serial.println(subTopic);
  } else {
    Serial.print("Subscribe Fail...");
  }  
}


void byteToStr(byte* data, size_t size, String& str) {
    str = ""; // 清空字符串
    for (size_t i = 0; i < size; i++) {
        str += (char)data[i]; // 将byte转换为char添加到字符串中
    }
}

订阅QoS为1的消息

消息发布使用mqtt.fx,消息的接收使用esp8266。通过发布消息控制led灯的亮灭、亮度。
代码由上面“点灯小项目”修改而来。

const int subQos = 1;
const bool cleanSession = false;
const char* willTopic = "willTopic";
const char* willMsg = "willMsg";
const int willQos = 0;
const int willRetain = false;

void connectMQTTServer()
{
  // 连接MQTT服务器,带上客户端ID,用户名、密码,并设置与QoS相关内容。
  String clientId = "esp8266-led";
  // ** mqttClient.connect 函数是关键。 **
  if (mqttClient.connect(clientId.c_str(), mqttUserName, mqttPassword, willTopic, willQos, willRetain, willMsg, cleanSession))
  {
    Serial.println("MQTT Server Connected.");
    Serial.println("Server Address: ");
    Serial.println(mqttServer);
    Serial.println("ClientId:");
    Serial.println(clientId);
    subscribeTopic();
  }
  else
  {
    Serial.print("MQTT Server Connect Failed. Client State:");
    Serial.println(mqttClient.state());
    delay(3000);
  }
}

// 订阅指定主题
void subscribeTopic(){
  // 订阅主题,并通过串口监视器输出是否成功、订阅主题、以及订阅的主题名称
  String subTopic = "led1/change";
  if(mqttClient.subscribe(subTopic.c_str(), subQos)){     // ** 这里subQos是关键 **
    Serial.println("Subscrib Topic:");
    Serial.println(subTopic);
  } else {
    Serial.print("Subscribe Fail...");
  }  
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值