一、安装Ubuntu系统
安装的是服务器版本,占用资源较少。
下载地址https://pan.baidu.com/s/1ooi2Aj0Q_R3rVWyvgnz1Tw?pwd=5278我们需要使用FinalShell,一个SSH客户端,可以通过SSH连接我们的Linux系统。
不过记得,要在系统初始化勾选使用SSH功能。
二、网络设置
本文使用 NAT + 端口转发,这样设置的好处就是我们无需知道虚拟机的ip地址,只需要知道主机的ip地址就可以,ip地址是固定的,不需要频繁更改。
这里有一个比较大的坑,因为我在学校使用校园网,主机是一个校园网公有地址。因为我们使用NAT+端口转发的模式,所以消息会率先到达这个地址,但是校园网一般会有防火墙,也就是对陌生的连接请求,会拒绝掉。也就是说我们无法在主机上,使用校园网,
我使用的是手机热点,主机连接到手机热点。这时候主机会被分配一个私有ip,这时候又有一个问题了。如果服务器搭建在私有地址上,那么只有跟他位于一个网络下的设备才可以访问到,也就是说我们需要将esp32连接到手机热点上,才可以访问这个服务器。

三、搭建mosquito服务器
之前勾选系统初始化的mosquito下载,是snap下载的,与传统的管理工具apt有些差别,不太好用,所以下面的指令,都是基于apt下载mosquito的。
1、下载mosquito
bash
sudo apt update && sudo apt install mosquitto mosquitto-clients
mosquitto
:包含 MQTT 服务器(broker)本体。mosquitto-clients
:包含用于测试的客户端工具mosquitto_pub
和mosquitto_sub
。
2、配置mosquito
bash
sudo vim /etc/mosquitto/conf.d/default.conf
在配置文件中,确保有以下内容以实现您的需求(允许外部连接并设置认证):
bash
listener 1883 0.0.0.0
allow_anonymous true
password_file /etc/mosquitto/passwd
其中listener非常重要,因为默认情况下,mosquito只会监听本地环回地址下的1883端口。而我们使用NAT+端口转发,也就是消息首先发送到我们的主机ip,再转发到虚拟机ip,这个消息来自于我们主机的ip地址,所以mosquito不会接受。我们需要设置,mosquito监听所有地址的1883端口,这也是第一句代码的意思。
3、创建用户和密码文件
bash
sudo mosquitto_passwd -c /etc/mosquitto/passwd your_username
(将 your_username
替换为您想要的用户名,系统会提示您输入密码)
4、重启服务并验证
bash
sudo systemctl restart mosquitto
sudo systemctl status mosquitto
5、验证安装成功
bash
mosquitto -v
如果安装成功,这次将会显示 Mosquitto 的版本信息和启动日志,而不是 command not found
。
执行这句代码会有光标一直闪烁,代表这mosquito正在占用命令行,可以尝试用MQTT.fx连接它,会出来相应的连接信息。
输入ctrl + c 会强行中断mosquito程序,我们可以让mosquito作为守护进程在后台运行。
mosquitto -d
四、连接esp32和上位机
1、esp32
下面是我的下位机代码,功能是连接WiFi、服务器后,向test/esp32这个主题每三秒发送一个"1111"数据。
cpp
#include <WiFi.h>
#include <PubSubClient.h>
#include <Ticker.h>
#include <ArduinoJson.h>
void connectWifi();
void subscribeTopic();
void pubMQTTmsg();
void receiveCallback(char *topic, byte *payload, unsigned int length);
void connectMQTTserver();
void tickerCount();
String createJson();
// 设置wifi接入信息(请根据您的WiFi信息进行修改)
const char *ssid = "zzzz";
const char *password = "12345678";
const char *mqttServer = "192.168.136.55";
// 如以上MQTT服务器无法正常连接,请前往以下页面寻找解决方案
// http://www.taichi-maker.com/public-mqtt-broker/
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
Ticker ticker;
int count = 0; // Ticker计数用变量
// ****************************************************
// 注意!以下需要用户根据然也物联平台信息进行修改!否则无法工作!
// ****************************************************
const char *mqttUserName = "han"; // 服务端连接用户名(需要修改)
const char *mqttPassword = "123456"; // 服务端连接密码(需要修改)
const char *clientId = "ESP32"; // 客户端id (需要修改)
const char *subTopic = "test/mqtt_fx"; // 订阅主题(需要修改)
const char *pubTopic = "test/esp32"; // 订阅主题(需要修改)
const char *willTopic = "/broadcast/h9sj0dFIZzO/test1"; // 遗嘱主题名称(需要修改)
// ****************************************************
// 遗嘱相关信息
const char *willMsg = "esp8266 offline"; // 遗嘱主题信息
const int willQos = 0; // 遗嘱QoS
const int willRetain = false; // 遗嘱保留
const int subQoS = 1; // 客户端订阅主题时使用的QoS级别(截止2020-10-07,仅支持QoS = 1,不支持QoS = 2)
const bool cleanSession = false; // 清除会话(如QoS>0必须要设为false)
bool ledStatus = HIGH;
void setup()
{
Serial.begin(115200); // 启动串口通讯
ticker.attach(1, tickerCount); // Ticker定时对象
// 设置ESP8266工作模式为无线终端模式
WiFi.mode(WIFI_STA);
// 连接WiFi
connectWifi();
Serial.println(ESP.getChipModel());
// 设置MQTT服务器和端口号
mqttClient.setServer(mqttServer, 1883);
mqttClient.setCallback(receiveCallback);
// 连接MQTT服务器
connectMQTTserver();
}
void loop()
{
// 如果开发板未能成功连接服务器,则尝试连接服务器
if (!mqttClient.connected())
{
connectMQTTserver();
Serial.println("正在重连");
}
if (count >= 3)
{
pubMQTTmsg(); // 每隔3秒钟发布一次信息
count = 0;
}
// 定期发送心跳 检查服务器回复的心跳
//如果有订阅主题的消息到达 触发回调函数
mqttClient.loop();
delay(10);
}
// 计时器
void tickerCount()
{
count++;
}
// 连接MQTT服务器并订阅信息
void connectMQTTserver()
{
if (mqttClient.connect(clientId, mqttUserName,
mqttPassword))
{
Serial.print("MQTT Server Connected. ClientId: ");
Serial.println(clientId);
Serial.print("MQTT Server: ");
Serial.println(mqttServer);
subscribeTopic(); // 订阅指定主题
}
else
{
Serial.print("MQTT Server Connect Failed. Client State:");
Serial.println(mqttClient.state());
delay(5000);
}
}
// 收到信息后的回调函数
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"为开始
ledStatus = LOW;
}
else
{
ledStatus = HIGH;
}
pubMQTTmsg();
}
// 订阅指定主题
void subscribeTopic()
{
// 通过串口监视器输出是否成功订阅主题以及订阅的主题名称
// 请注意subscribe函数第二个参数数字为QoS级别。这里为QoS = 1
if (mqttClient.subscribe(subTopic, subQoS))
{
Serial.print("Subscribed Topic: ");
Serial.println(subTopic);
}
else
{
Serial.print("Subscribe Fail...");
}
}
// 发布信息
void pubMQTTmsg()
{
char *pubMessage1 = "{\"id\":\"123\",\"version\":\"1.0\",\"params\":{\"number\":{\"value\":2}}}";
String jsonStr = createJson(); // 保持String对象存活
const char *pubMessage2 = jsonStr.c_str();
const char * pubMessage = "1111";
// 实现ESP8266向主题发布信息
if (mqttClient.publish(pubTopic, pubMessage))
{
Serial.println("Publish Topic:");
Serial.println(pubTopic);
Serial.println(pubMessage);
}
else
{
Serial.println("Message Publish Failed.");
}
}
// 根据时间动态创建Json
// 1~100
String createJson()
{
DynamicJsonDocument doc(1024);
doc["id"] = "123";
doc["version"] = "1.0";
JsonObject params = doc.createNestedObject("params");
JsonObject number = params.createNestedObject("number");
static int num = 0;
if (count >= 3)
{
num++;
num %= 100;
}
number["value"] = num;
String result;
serializeJson(doc, result);
return result;
}
// ESP8266连接wifi
void connectWifi()
{
WiFi.begin(ssid, password);
// 等待WiFi连接,成功连接后输出成功信息
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi Connected!");
Serial.println("");
}
这是程序运行起来的样子,可以发现,连接成功,发送也成功了。

2、上位机
这时候没有编写上位机程序,用MQTT.fx,来充当一个上位机。
MQTT.fx出现了些问题,就先不展示连接参数了。
因为我们设置的可以匿名连接,所以只要服务器ip地址和端口没有输错就可以。
这是MQTT.fx收到esp32发来消息的截图。

这是MQTT.fx发送给esp32消息的截图。


可以发现esp32收到了来自上位机发来的消息。
至此,我们打通了下位机 MQTT 上位机 数据传输 的通路。