一、开发环境
1.1.概述
由于之前在ESP32最小开发板的文章中已经详细说明了如何实现WiFi联网并获取天气数据,因此这里不在详细介绍ESP32模组的WiFi功能,可参考之前的文章介绍,这个文章重点实现通过运营商基站网络进行通讯,并读取位置传感器信息发布至MQTT服务器。
我们的ESP32+BC260Y+L76K物联网开发板目的是实现GPS卫星定位功能,并通过运营商基站网络进行通讯,将位置信息发送到物联网平台进行下一步的处理,GPS定位是需要户外进行,否则会搜索不到卫星数据,在户外我们是无法使用ESP32的WiFi功能(即便是有),因此我们只能借助运营商的基站无线网络,这里选择的是NB-IoT网络,选用的模组是移远的BC260Y,选用NB-IoT通讯协议主要是为了低成本,低功耗,覆盖面广,它无法与4G网络速度相比,如果想要实时传输大量数据还是需要4G网络,后期我们会陆续迭代升级。
定位模组选用的是移远的L76K,关于通讯模组和定位模组这里不在详细说明,可参考官方的数据手册。
在户外需要电池来供电,我们在开发板中设计了电池供电、充电功能,以方便实验测试。
1.2.准备工作
1.2.1.硬件
- ESP32+BC260Y+L76K物联网开发板
- 物联网卡
- 网络通讯天线
- GPS天线
- USB 数据线 (Type-C 数据线)
- 电脑(Windows 10)
1.2.2.软件
准备软件安装包:
我们在开发资料包中提供的Arduino IDE版本是 2.3.2
如需要安装其它版本,可到Arduino官网去另行下载并安装。
Arduino官网地址: https://www.arduino.cc/en/software
安装过程不在赘述,可参考之前的文档。
二、运营商基站NB-IoT网络通讯
2.1.初始化BC260Y通讯模组
我们打开Arduino IDE开发工具,新建项目ESP32_BC260Y_INIT,输入代码:
#include <Arduino.h>
#include <Quectel_BC260Y.h>
/*定义移远BC260Y串口,使用ESP32的UART2:RX=GPIO16, TX=GPIO17*/
#define SERIAL_PORT Serial2
// 初始化移远BC260Y库
Quectel_BC260Y quectel;
void setup() {
// 初始化串口波特率
Serial.begin(9600);
Serial.println("\r\nQuectel BC260Y Module init example");
Serial.println("=================================================================");
// 初始化BC260Y
quectel.begin(&SERIAL_PORT);
// 查询固件版本号
Serial.print("FirmwareVersion: ");
Serial.println(quectel.getFirmwareVersion());
// 查询网络日期时间
Serial.print("Time: ");
Serial.println(quectel.getDateAndTime());
// 查询IMSI
Serial.print("IMSI: ");
Serial.println(quectel.getIMSI());
// 查询ICCID
Serial.print("ICCID: ");
Serial.println(quectel.getICCID());
// 查询网络信号质量
Serial.print("RSSI: ");
Serial.println(quectel.getRSSI());
// 查询网络状态
Serial.print("Status code: ");
Serial.println(quectel.getStatusCode());
Serial.print("Status: ");
Serial.println(quectel.getStatus());
}
void loop() {
}
编译并上传代码到开发板,可以看到运行结果,串口监视器中会打印出网络基本信息。
返回Status code状态码为1表明已经正常注册到网络。
三、GPS/GNSS定位
3.1.初始化L76K定位模组
开发之前,我们需要在Arduino中引入两个第三方库,EspSoftwareSerial和TinyGPSPlus。
我们打开Arduino IDE开发工具,新建项目L76K_INIT,输入代码:
#include <Arduino.h>
#include <SoftwareSerial.h>
#include <TinyGPSPlus.h>
// GPIO16、GPIO17引脚用于实现跟BC260Y的UART通讯,使用ESP32的UART2
#define RXD2 16
#define TXD2 17
// GPIO4、GPIO14引脚用于实现跟L76K的UART通讯 //
#define GPSRXD 14
#define GPSTXD 4
HardwareSerial SerialPort(2);
SoftwareSerial GPSSerial(GPSTXD, GPSRXD);
TinyGPSPlus gps;
// 定义GNSS数据结构
struct gnssDataStruct {
String StatelliteNum; // 卫星数量
bool StatelliteIsValid;
bool GpsLocationIsValid;
double Latitude; // 经度
double Longitude; // 纬度
String Altitude; // 海拔高度
bool AltitudeIsValid;
String Speed; // 速度
String UTC_Date; // 网络授时
String UTC_Time; // 网络授时
TinyGPSDate date;
TinyGPSTime time;
};
gnssDataStruct gnssData;
// 定义延迟函数
static void smartDelay(unsigned long ms) {
unsigned long start = millis();
do {
while (GPSSerial.available())
gps.encode(GPSSerial.read());
} while (millis() - start < ms);
}
// 获取GPS/GNSS数据
void get_GNSSData(unsigned long ms) {
while (GPSSerial.available() > 0) {
int c = GPSSerial.read();
if (gps.encode(c)) {
gnssData.StatelliteNum = gps.satellites.value();
gnssData.StatelliteIsValid = gps.satellites.isValid();
gnssData.GpsLocationIsValid = gps.location.isValid();
gnssData.Latitude = gps.location.lat();
gnssData.Longitude = gps.location.lng();
gnssData.Altitude = gps.altitude.meters();
gnssData.AltitudeIsValid = gps.altitude.isValid();
gnssData.Speed = gps.speed.mps();
String day = String(gps.date.day());
String month = String(gps.date.month());
String year = String(gps.date.year());
gnssData.UTC_Date = day + "/" + month + "/" + year;
String hour = String(gps.time.hour());
String minute = String(gps.time.minute());
String second = String(gps.time.second());
gnssData.UTC_Time = hour + "/" + minute + "/" + second;
gnssData.date = gps.date;
gnssData.time = gps.time;
}
}
smartDelay(1000);
}
void setup() {
// 初始化串口波特率
Serial.begin(9600);
SerialPort.begin(9600, SERIAL_8N1, RXD2, TXD2);
GPSSerial.begin(9600);
}
void loop() {
// 获取GPS数据
get_GNSSData(3000);
// 串口打印数据
// 纬度
Serial.print("Latitude:");
Serial.println(gnssData.Latitude);
// 经度
Serial.print("Longitude:");
Serial.println(gnssData.Longitude);
// 海拔
Serial.print("Altitude:");
Serial.println(gnssData.Altitude);
// 速度
Serial.print("Speed:");
Serial.println(gnssData.Speed);
// 卫星数量
Serial.print("StatelliteNum:");
Serial.println(gnssData.StatelliteNum);
// 日期
Serial.print("Date:");
Serial.println(gnssData.UTC_Date);
// 时间
Serial.print("Time:");
Serial.println(gnssData.UTC_Time);
delay(200);
}
编译并上传代码到开发板,可以看到运行结果,串口监视器中会打印出解析到的GPS基本信息。
注意事项:程序初次运行,由于有搜索卫星的动作,需要等待一段时间才可以看到解析到的GPS位置数据
四、物联网平台IoT
4.1.向MQTT服务器发布GPS数据
我们在最小开发板的文章中详细讲解了MQTT服务器的环境安装和配置,这里就不在赘述关于开发环境的搭建,利用之前学习过的知识我们可以轻松的实现将GPS数据构建成json格式的字符串,通过NB-IoT网络发送到MQTT服务器。
我们打开Arduino IDE开发工具,新建项目Esp32_GpsData_Send_MQTTServer,输入代码:
#include <Arduino.h>
#include <Quectel_BC260Y.h>
#include <ArduinoJson.h>
// 定义移远BC260Y串口,使用ESP32的UART2:RX=GPIO16, TX=GPIO17//
#define SERIAL_PORT Serial2
// 初始化移远BC260Y库
Quectel_BC260Y quectel;
// MQTT服务器
const char *mqtt_broker = "MQTT服务器地址"; // 服务器地址
const char *clientID = "HTKI-0001"; // 自定义clientid
const char *topic = "hantu_iot/testtopic"; // 定义 Topic
const char *mqtt_username = "用户名"; // 用户名
const char *mqtt_password = "密码"; // 密码
const int mqtt_port = 1883; // 端口
// 定义变量
String payload = "";
JsonDocument jsonbuf;
const long interval = 20000;//定义发布数据时间间隔,单位ms,这里表示20秒发一次
unsigned long previousMillis = 0;
int cnt = 0;
// 生成payload上传信息json格式
void genPayloadInfo() {
payload = "";
// 序列化网络基础信息
jsonbuf["FirmwareVersion"] = quectel.getFirmwareVersion();
jsonbuf["DateTime"] = removeQuotes(quectel.getDateAndTime());
jsonbuf["IMSI"] = quectel.getIMSI();
jsonbuf["ICCID"] = quectel.getICCID();
jsonbuf["RSSI"] = quectel.getRSSI();
// 序列化GPS信息
char lat[sizeof(double)];
char lng[sizeof(double)];
jsonbuf["Latitude"] = quectel.gnssData.Latitude;
jsonbuf["Longitude"] = quectel.gnssData.Longitude;
jsonbuf["Altitude"] = quectel.gnssData.Altitude;
jsonbuf["Speed"] = quectel.gnssData.Speed;
jsonbuf["Nsat"] = quectel.gnssData.StatelliteNum;
jsonbuf["Date"] = quectel.gnssData.UTC_Date;
jsonbuf["Time"] = quectel.gnssData.UTC_Time;
// 序列化电池数据信息
quectel.readBattery();
jsonbuf["Battery_Voltage"] = quectel.batData.Voltage;
jsonbuf["Battery_Percent"] = quectel.batData.Percent;
// 生成json字符串payload
serializeJson(jsonbuf, payload);
}
// 读取GPS/GNSS位置传感器值
void readSensors() {
quectel.read_GNSS_Data(3000);
}
// 发布数据到MQTT服务器
void publishData() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// 计数器,用于显示发送数据的条数,这里是为了观察发送数据的稳定性
cnt++;
// 读取传感器数据
readSensors();
// 生成传感器数据发布payload
genPayloadInfo();
String lastTemp = String(payload) + String(cnt);
int lastTempLength = lastTemp.length();
quectel.publishMQTT(lastTemp.c_str(), lastTempLength, "hantu_iot/testtopic");
previousMillis = currentMillis;
}
}
// 连接到NB-IoT运营商网络
void connectToNBIoT() {
Serial.println("Connect to NB-IoT network");
Serial.println("=================================");
while (!quectel.getRegistrationStatus(5)) {
Serial.println("Waiting for network registration...");
delay(5000);
}
Serial.println("Module is successfully registered to network");
}
// 创建连接到MQTT服务器
void connectToMQTT() {
quectel.configMQTT();
Serial.println("\nTurn off deep sleep mode");
quectel.setDeepSleep();
delay(1000);
Serial.println("\nOpen MQTT connection");
quectel.openMQTT(mqtt_broker);
delay(1000);
Serial.println("\nConnect to MQTT broker");
quectel.connectMQTT(clientID, mqtt_username, mqtt_password);
delay(1000);
}
void setup() {
// 初始化串口波特率
Serial.begin(9600);
// 初始化BC260Y
quectel.begin(&SERIAL_PORT);
// BC260Y模组连接NB-IoT网络并注册
connectToNBIoT();
// 连接到MQTT服务器
connectToMQTT();
}
void loop() {
// 发布数据
publishData();
}
//去除字符串中的引号
String removeQuotes(String str) {
String result = "";
for (int i = 0; i < str.length(); i++) {
if (str[i] != '"' && str[i] != '\'') {
result += str[i];
}
}
return result;
}
编译并上传代码到开发板,程序运行后会发送数据到MQTT服务器,我们需要借助MQTTX工具来查看服务器接收到的数据
通过数据我们可以看到开发板会每20秒向服务器发送一条数据信息,包含了我们的BC260Y硬件信息,GPS位置信息,以及我们的电池电量。
目前我们只是得到了这些数据,下一步我们要做的处理是把数据展示并存储到数据库中,以便后期进一步的应用开发,这里我们需要用Node-RED工具进一步的处理。
五、Node-RED数据面板
5.1.Node-RED数据面板
之前我们介绍过使用Node-RED来开发数据面板,使用的组件是dashboard,dashboard有了新的升级版本V2.0,我们目前也升级到了该版本
安装节点@flowfuse/node-red-dashboard
在线文档:Widgets | Node-RED Dashboard 2.0
在线示例:km/dashboard_2_0.json at main · nygma2004/km · GitHub
由于需要使用地图,这里我们也引入了高德地图,需要开发者到高德地图开发平台上申请开发者key和密钥,这个过程不在赘述。
Node-RED工作流如下所示
运行界面截图:
因为是Web界面服务,手机端也可以直接访问