基于ARM开发板搭建物联网服务器
一、项目需求
1.1设备需求
1.嵌入式开发板:
(1)可以接入网络:目的是为了MQTT的订阅端
(2)能够运行最小Linux系统:为了移植MQTT、SQLite3、Boa应用
2.PC:
(1)虚拟机安装Linux系统:本人使用的是Ubuntu 20.04版本 对版本没有要求
(2)MQTT测试工具
(3)HyperTerminal串口通讯工具
3.服务器
本人使用的是阿里云服务器作为MQTT的broker
1.2知识需求
1.对嵌入式开发板有一定了解,能够移植相关应用
2.了解数据库的基本使用
3.了解MQTT传输原理
4.了解HTML+CSS+JavaScript+AJAX基本原理
5.了解Boa+CGI作用
1.3项目介绍
项目过程:
1.通过获取MQTT订阅服务器某一主题获取到消息如下:
{
"Devaddr":1,
"temp":23.56
}
2.嵌入式开发板通过mosquitto+JSON获取到JSON数据并将其解析出来
3.解析成功后写入SQLITE3数据库中
4.由BOA搭建的网页通过JS+AJAX+CGI调用C函数
5.数据库部分数据的打印到网页上
6.最终实现局域网下的物联网数据展示系统
二、开发环境搭建
2.1阿里云服务器配置
这里我使用的是FinalShell接入我的阿里云服务器
连接成功如图所示
apt-get install mosquitto
安装mqtt服务器
安装完成之后
打开mosuitto服务并查看服务状态
service mosquitto start
service mosquitto status
我们测试mqtt服务器是否有效
打开MQTT.fx
找到设置-添加阿里云服务器IP 端口号1883 点击OK退出
点击connect 发现灯变绿了
(按顺序)
1.在subscribe填写主题test 点击subscribe
2.在publish前的空白处 填写主题test
3.在publish窗口输入发送内容 点击publish
订阅端成功收到数据 hello mqtt!
服务器配置完成!
2.2虚拟机交叉编译环境搭建
1.下载交叉编译环境文件
这里我用的最新的2014.05-29版本
2.为什么要使用交叉编译工具链
因为我们使用的电脑一般为x86的芯片架构,x86上编译程序不能在arm架构上运行,
需要交叉编译工具链才能够生成arm能够运行的程序。
x86:
arm:
3.交叉编译环境的搭建
在有交叉编译工具链的文件夹中
sudo tar -xvf arm-2014.05-29-arm-none-linux-gnueabi-i686-pc-linux-gnu.tar.bz2
方便使用mv 修改文件夹名字
移动文件到 /usr/local/文件夹下
mv arm-2014.05-29-arm-none-linux-gnueabi-i686-pc-linux-gnu arm-2014.05.29
cp -r arm-2014.05.29 /usr/local/arm-linux
修改路径
vi /etc/profile
在最后一行添加
export PATH=$PATH:/usr/local/arm-2014.05.29/bin
退出后
source /etc/profile
然后再输入arm 双击 tab 就会出现如下图所示命令 表示为安装成功
2.3下载相关应用压缩文件
本文下载的主要有六个文件
1.mosquitto(mqtt订阅/发布工具)
2.libuuid(mosquitto依赖库)
3.openssl(mosquitto依赖库)
4.boa(能够在开发板运行网站)
5.sqlite3(数据库)
6.ntpclient(时间校正应用)
三、应用移植
准备好移植文件
本人移植准备的应用如下
3.1MQTT移植
创建一个新文件夹备用
这里我创建的是mqtt-arm
在/opt下创建一个mosquitto-arm文件夹备用(无视mosquitto-x86)
(1)libuuid移植
1.输入指令解压
tar -xvf libuuid-1.0.3.tar.gz
cd libuuid-1.0.3/
2.输入指令
./configure --prefix=/opt/mosquitto-arm/ CC=arm-none-linux-gnueabi-gcc --host=arm-linux
make -j8
sudo make install
完成第一步移植
(2)openssl移植
OpenSSL 是一个安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议,并提供丰富的应用程序供测试或其它目的使用。
1.输入指令解压
tar -xvf openssl-1.1.0i.tar.gz
cd openssl-1.1.0i/
2.输入指令
setarch i386 ./config no-asm shared --prefix=/opt/mosquitto-arm/openssl/
解释:
setarch i386:声明生成的是 32 位 CPU,如果是 64 位 CPU 则去除该部分
no-asm: 是在交叉编译过程中不使用汇编代码代码加速编译过程,原因是它的汇编代码是对arm格式不支持的。
shared :生成动态连接库。
–prefix :指定make install后生成目录的路径,不修改此项则默认为OPENSSLDIR目录(/usr/local/ssl)。
3.打开Makefile文件修改
vi Makefile
CROSS_COMPILE=arm-linux-none-gnueabi-
4.保存编译
make
如果出现错误option “-m32”
则在Makefiile中的CFLAGS与LIB_LDFLAGS行去除 -m32选项即可
5.再次
sudo make
sudo makeinstall
这里可能会出现
arm-none-linux-gnueabi-ranlib: command not found
指令未找到的错误
原因是我们在root下没有安装交叉编译工具链又使用的root环境导致的
所以我们需要在root环境下安装工具链或者将mosquitto-arm文件移植用户文件下
这样就不会导致权限引起的问题
6.最后在 /opt/mosquitto-arm/下生成openssl文件夹
第二步完成
(3)Mosquitto移植
输入指令解压
tar -xvf mosquitto-1.5.tar.gz
cd mosquitto-1.5/
执行编译命令
make WITH_SRV=no CC=arm-none-linux-gnueabi-gcc CXX=arm-none-linux-gnueabi-g++ CFLAGS="-I /opt/mosquitto-arm/openssl/include -I /opt/mosquitto-arm/libuuid-1.0.3/include -I /opt/mosquitto-arm/openssl/lib -I /opt/mosquit/to-arm/libuuid-1.0.3/lib" LDFLAGS="-L /opt/mosquitto-arm/openssl/lib -L /opt/mosquitto-arm/libuuid-1.0.3/lib -lssl -lcrypto -luuid"
注意,如果这里安装 libuuid 和 openssl 的库的时候路径和我的不一致,要把-I 和-L 指定的库和头文件的路径修改成自己对应安装 uuid 和 openssl 库的路径,否则编译不过去。
make DESTDIR=/opt/mosquitto-arm/mosquitto-1.5 install
最终生成mosquitto-1.5/
(4)文件移植
至此三个文件夹都完成了
接着就是文件的移植
现在进入我们一开始创建的mqtt-arm文件夹
cd ~/mqtt-arm
主要库要复制进来
1.二进制文件以及配置文件
cp /etc/mosquitto/mosquitto.conf.example .
cp /opt/mosquitto-arm/mosquitto-1.5/usr/local/bin/* .
cp /opt/mosquitto-arm/mosquitto-1.5/usr/local/sbin/mosquitto .
这三部分别是移植配置文件,移植订阅、发布文件,移植作为服务器的文件
2./opt/mosquitto-arm 目录下复制 libuuid与openssl与mosquitto 到 mqtt-arm
cp /opt/mosquitto-arm/* . -r
最终文件夹如下图所示
打包文件
cd ../
tar -czf mqtt-arm.tar.gz mqtt-arm/
最终得到一个mqtt-arm.tar.gz压缩文件
我们把mqtt-arm.tar.gz移动到开发板中并解压出来
进入mqtt-arm把libuuid-1.0.3/ mosquitto-1.5/ openssl/三个文件下的 lib 下
的库全部放到开发板的/lib 下面
cd mqtt-arm
cp libuuid-1.0.3/lib/* /lib/ -r
cp openssl/lib/* /lib/ -r
cp mosquitto-1.5/usr/local/lib/* /lib -r
再移动到二进制可运行文件到/bin目录下
mv mosquitto mosquitto_* /bin
验证:
输入mos 按键Tab自动补全发现有命令则移植成功
备注:如果想要再X86平台下运行直接安装mosquitto即可使用
sudo apt-get install mosquitto
(5)验证
在服务器开启mqtt服务器的前提下
我们使用虚拟机用于订阅mqtt消息,开发板用于发布消息作为自测试。
虚拟机:(149.xxx.xx.xx为本人服务器)
mosquitto_sub -h 149.xxx.xx.xx -t “mqtt” -v
开发板:
mosquitto_pub -h 149.xxx.xx.xx -t “mqtt” -m "Hello Mqtt"
测试成功!
如果没有服务器也可以以虚拟机作为服务器:相关网址点我
3.2BOA移植
BOA简介
其可执行代码只有大约60KB左右,Boa是一个单任务的HTTP服务器,Boa只能依次完成用户的请求,而不会fork出新的进程来处理并发连接请求。Boa支持CGI。
Boa的设计目标是速度和安全。(CGI只是一个进程,用来提供接口),自动目录生成和自动文件枪支进行拼接。
Boa的主要设计目标是速度和安全性。安全性在“不能被恶意用户破坏”的意义上,不是“细粒度访问控制和加密通信”。
特点:可靠性和可移植性,Boa不是作为功能强大的服务器。
开发平台:GNU / Linux是目前的开发平台。
解压BOA文件 并运行配置文件
tar -xvf tar -xvf boa-0.94.13.tar.gz
cd boa-0.94.13/src/
./config
1.我们修改一下 src下的 compat.h文件 120行 去掉foo后的##
2.还有boa.c 225行注释掉 if(setuid(0) != -1)…
3.修改一下编译器
vi Makefile
修改CC 与 CPP如下图所示
保存退出执行
make
生成了一个二进制boa文件
此时我们还要给他瘦身
arm-none-linux-gnueabi-strip boa
对比发现由原来的 244195byte->60588byte 为原来的四分之一左右
紧接着还需要配置一下 boa-0.94.13/boa.conf 配置文件
vi ../boa.conf
附件:配置详解
(1)Group的修改
修改 Group nogroup
为 Group 0
(2)user的修改
修改 User nobody
为 User 0
(3)ScriptAlias的修改
修改ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
为 ScriptAlias /cgi-bin/ /var/www/cgi-bin/
(5)DoucmentRoot的修改
DoucmentRoot /var/www
(6)ServerName的设置
修改#ServerName www.your.org.here
为 ServerName www.your.org.here
否则会出现错误“gethostbyname::No such file or directory”
(7)AccessLog修改
修改AccessLog /var/log/boa/access_log
为#AccessLog /var/log/boa/access_log
否则会出现错误提示:“unable to dup2 the error log: Bad file descriptor”
(8)以下配置和boa.conf的配置有关,都是在ARM根文件系统中创建
以下步骤在开发板上进行:
创建目录/etc/boa并且把boa 和 boa.conf拷贝到这个目录下
mkdir /etc/boa
创建HTML文档的主目录/www
mkdir /www/cgi-bin
CGI脚本所在录 /www/cgi-bin
以下步骤在ubuntu下进行:
将boa.conf拷贝到开发板根文件系统的/etc/boa下
将boa拷贝到开发板根文件系统的/bin下
将ubuntu下/etc/mime.types拷贝到开发板根文件系统的/etc下
cp /etc/mime.types /etc
将你的主页index.html拷贝到 /var/www 目录下
最后我们测试一下
首先在开发板上运行 ./boa
然后创建一个 index.html
文件内容如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My First HTML</title>
</head>
<body>
<h1>Hello BOA!</h1>
</body>
</html>
复制index.html 到开发板的 /www/文件下
然后打开浏览器 连接开发板的IP
最后成功如下所示
至此BOA移植成功!
3.3sqlite3数据库移植
本文讲述sqlite3数据库的嵌入式设备移植过程并结合小例子说明如何使用sqlite3的库进行编程。数据库在程序开发过程中起到举足轻重的作用,肩负着用户和系统设置数据的保存、查找、增删等操作,是程序运行的“粮食“。数据库的提供商有很多,诸如oracle、candence、mysql、sqlite等。但是sqlite3作为一款高可靠性且小巧玲珑的数据库工具,以及它的跨平台特性使得它十分适合在嵌入式领域使用。本文以ITop-4412系列嵌入式平台为目标,实现sqlite3的嵌入式移植,并使平台具有数据库开发的能力,方便保存用户信息及音视频配置信息。
解压sqlite3
tar -xvf sqlite-autoconf-3340100.tar.gz
cd sqlite-autoconf-3340100/
./configure CC=arm-none-linux-gnueabi-gcc --host=arm-linux --prefix=/home/sqlite3-arm/
make
make install
生成文件如下
把bin下的sqlite3拷贝到开发板/bin下
把lib下所有库拷贝到开办板/lib下
至此开发板能够运行sqlite3命令即移植完成
移植成功!
3.4ntpclient移植
Ntpclient是时间服务提供程序。用于校准开发板系统时间,这样在使用sqlite3数据库导入时间时不会出错。
1.解压文件
tar -xvf ntpclient_2015_365.tar.gz
cd ntpclient_2015_365/
vi Makefile +5
2.修改第五行如图所示
3.保存退出编译
make
4.生成ntpclient文件拷贝到开发板/bin目录下
5.使用指令校准时间
ntpclient -s -d -c 1 -i 5 -h 223.113.120.195
date
6.显示时间同步成功 移植成功!
至此6个应用库都移植完成!
四、MQTT+C的使用
4.1MQTT介绍
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。
MQTT是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(IoT)。其在,通过卫星链路通信传感器、偶尔拨号的医疗设备、智能家居、及一些小型化设备中已广泛使用。
简单了解之后知道我们需要的是一个订阅端,一个发布端和一个服务器,由于服务器环境已经在第二章搭建完成,我们现在只需要知道服务器IP和端口号即可,这里我的服务器IP为149.xxx.xxx.xxx 端口号默认为1883。
4.2订阅与发布
由于我们服务器以及搭建完毕,Mosquittto(MQTT应用程序)也移植到了开发板上,我们在虚拟机上进行mosquitto指令测试,熟悉mqtt通信方式。
首先我们有了一个IP+端口号,除此之外我们还需要一个Topic主题才能进行消息的订阅以及发送。因此我们在虚拟机下的Linux系统中打开两个Terminal测试服务器以及mosquitto是否正常。
1.第一个窗口输入
mosquitto_sub -h 149.xxx.xxx.xxx -t "mqtt" -v
2.第二个窗口输入
mosquitto_pub -h 149.xxx.xxx.xxx -t “mqtt” -m "Hello Mqtt"
如图中左边为订阅端,右边为发布端
在订阅端中
-h IP //后面跟随的是IP地址
-t "topic" //topic部分填写的是发送的主题
-v 在收到的信息前打印主题"mqtt"
在发布端中
-h IP //后面跟随的是IP地址
-t "topic" //topic部分填写的是发送的主题
-m "message" //message部分填写的是需要发送的信息
明白简单的MQTT通信之后我们就可以引用msqtuitto.h库文件来写程序啦
4.3基于Linux+C的MQTT通信程序
4.3.1Publish端代码分析
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <mosquitto.h>
void mqtt_log_callback(struct mosquitto *mqtt, void *ubuf, int level, const char *str)
{
printf("[log] level=%d str=%s ubuf=%s\n", level, str, (char *)ubuf);
}
void mqtt_connect_callback(struct mosquitto *mqtt, void *ubuf, int result)
{
int i;
printf("[connect] level=%d\n", result);
if(!result){
/* Subscribe to broker information topics on successful connect. */
mosquitto_subscribe(mqtt, NULL, "mqtt", 2);
}else{
fprintf(stderr, "Connect failed\n");
}
}
void mqtt_message_callback(struct mosquitto *mqtt, void *ubuf, const struct mosquitto_message *message)
{
if(message->payloadlen){
printf("%s %s\n", message->topic, (char *)message->payload);
}else{
printf("%s (null)\n", message->topic);
}
fflush(stdout);
}
void mqtt_disconnect_callback(struct mosquitto *mosq, void *obj, int result)
{
printf("mqtt disconnect\n");
}
void mqtt_publish_callback(struct mosquitto *mosq, void *obj, int mid)
{
printf("mqtt_publish_callback\n");
}
int main(int argc, char *argv[])
{
int ret;
int session = true;
char buf[1024];
char name[] = "mqtt_pub";
struct mosquitto *mosquitto_pub = NULL;
mosquitto_lib_init();
mosquitto_pub = mosquitto_new(name, session, NULL);
if(!mosquitto_pub){
printf("mqtt new client error\n");
return -1;
}
mosquitto_connect_callback_set(mosquitto_pub, mqtt_connect_callback);
mosquitto_message_callback_set(mosquitto_pub, mqtt_message_callback);
mosquitto_disconnect_callback_set(mosquitto_pub, mqtt_disconnect_callback);
mosquitto_publish_callback_set(mosquitto_pub, mqtt_publish_callback);
ret = mosquitto_connect(mosquitto_pub, argv[1], 1883, 60);
if(ret){
printf("connect error\n");
return ret;
}
printf("connect succeed\n");
while(1)
{
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
if(strcmp(buf, "\n") == 0){
printf("bye....\n");
break;
}
mosquitto_publish(mosquitto_pub, NULL, "mqtt", strlen(buf)+1, buf, 0, 0);
}
return 0;
}
代码分析
首先我们直接从**int main()**开始分析
1.先申请一个mosquitto结构体指针为空,然后我们需要往结构体里填入信息
在填入信息之前需要使用mosquitto_lib_init();函数清空,在mosquitto.h文件介绍中提到开始时必须使用这个函数。
2.使用mosquitto_new(name, session, NULL);返回一个mosquitto结构体指针,返回值非空即结构体填充成功。函数介绍如下图所示
PS:
mosquitto_connect_callback_set(mosquitto_pub, mqtt_connect_callback);
mosquitto_message_callback_set(mosquitto_pub, mqtt_message_callback);
mosquitto_disconnect_callback_set(mosquitto_pub, mqtt_disconnect_callback);
mosquitto_publish_callback_set(mosquitto_pub, mqtt_publish_callback);
以上四个为信号设置函数,意思是触发某种信号就会触发相应的回调函数,正是因此我们可以在回调函数中进行后续操作
3.填充成功后我们就可以使用mosquitto_connect(mosquitto_pub, argv[1], 1883, 60);函数介绍如下,分别填入mosquitto结构体指针,ip地址,端口号,超时检测。返回值非0即连接成功。
4.进入发送模式,功能是不断从stdin键盘输入数据然后通过mosquitto_pub发送输入的数据。
while(1)
{
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
if(strcmp(buf, "\n") == 0){
printf("bye....\n");
break;
}
mosquitto_publish(mosquitto_pub, NULL, "topeet", strlen(buf)+1, buf, 0, 0);
}
其中memset函数功能为清空数组内容,fegs为获取键盘输入字符串,strcmp对比字符串是否为空,如果为空即退出循环。
非空即进入 mosquitto_publish(mosquitto_pub, NULL, “mqtt”, strlen(buf)+1, buf, 0, 0);
介绍如下
其中参数分别为moquitto结构体,第二个为空,主题这里用的"mqtt"作为主题,发送数据长度因为有’\0’结尾所以我们要多+1,然后是发送的字符串,qos与retain填0即可。
成功后回返回MOSQ_ERR_SUCESS即代表发送成功。
5.接着还有相应的回调函数需要分析,分别是
mqtt_connect_callback(struct mosquitto *mqtt, void *ubuf, int result);
mosquitto_subscribe(mqtt, NULL, "mqtt", 2);
在mosqtuitto_connect成功后回触发一个信号,这个信号在mqtt_connect_callback_set();之后会触发上面这个函数,我们就在这个函数连接成功后返回相应的主题信息,再去订阅相应的主题,作用是这样就可以实现我们发布之后同时收到自己发布的内容,验证消息是否发送成功。
mqtt_message_callback(struct mosquitto *mqtt, void *ubuf, const struct mosquitto_message *message);
在主题收到信息之后会触发一个信号,这个信号在mqtt_message_callback_set();之后回触发上面这个函数,我们就可以打印出来接收到的内容。
4.3.2Subscribe端代码分析
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <mosquitto.h>
void mqtt_log_callback(struct mosquitto *mqtt, void *ubuf, int level, const char *str)
{
printf("[log] level=%d str=%s ubuf=%s\n", level, str, (char *)ubuf);
}
void mqtt_connect_callback(struct mosquitto *mqtt, void *ubuf, int result)
{
int i;
printf("[connect] level=%d\n", result);
if(!result){
/* Subscribe to broker information topics on successful connect. */
mosquitto_subscribe(mqtt, NULL, "mqtt", 2);
}else{
fprintf(stderr, "Connect failed\n");
}
}
void mqtt_message_callback(struct mosquitto *mqtt, void *ubuf, const struct mosquitto_message *message)
{
printf("mqtt_message_callback..\n");
if(message->payloadlen){
printf("%s %s\n", message->topic, (char *)message->payload);
}else{
printf("%s (null)\n", message->topic);
}
fflush(stdout);
}
void mqtt_disconnect_callback(struct mosquitto *mosq, void *obj, int result)
{
printf("mqtt_disconnect\n");
}
void mqtt_publish_callback(struct mosquitto *mosq, void *obj, int mid)
{
printf("publish_callback\n");
}
int main(int argc, char *argv[])
{
char buf[1024];
bool session = true;
char name[] = "mqtt_sub";
struct mosquitto *mosquitto_sub = NULL;
mosquitto_lib_init();
mosquitto_sub = mosquitto_new(name, session, NULL);
if(!mosquitto_sub){
printf("create sub failed..\n");
mosquitto_lib_cleanup();
return 1;
}
mosquitto_log_callback_set(mosquitto_sub, mqtt_log_callback);
mosquitto_connect_callback_set(mosquitto_sub, mqtt_connect_callback);
mosquitto_message_callback_set(mosquitto_sub, mqtt_message_callback);
mosquitto_disconnect_callback_set(mosquitto_sub, mqtt_disconnect_callback);
mosquitto_publish_callback_set(mosquitto_sub, mqtt_publish_callback);
if(mosquitto_connect(mosquitto_sub, argv[1], 1883, 60)){
fprintf(stderr, "connect failed\n");
return 1;
}
printf("start recv...\n");
mosquitto_loop_forever(mosquitto_sub, -1, 1);
return 0;
}
代码分析
订阅端大部分与发布端代码类似,主要是在连接成功后触发连接回调函数,订阅相应主题
mqtt_connect_callback(struct mosquitto *mqtt, void *ubuf, int result);
mosquitto_subscribe(mqtt, NULL, "mqtt", 2);
在mosqtuitto_connect成功后回触发一个信号,这个信号在mqtt_connect_callback_set();之后会触发上面这个函数,我们就在这个函数连接成功后返回相应的主题信息,再去订阅相应的主题,作用是这样就可以实现我们发布之后同时收到自己发布的内容,验证消息是否发送成功。
mqtt_message_callback(struct mosquitto *mqtt, void *ubuf, const struct mosquitto_message *message);
在主题收到信息之后会触发一个信号,这个信号在mqtt_message_callback_set();之后回触发上面这个函数,我们就可以打印出来接收到的内容。
其中涉及到的回调函数不懂的可以查看mosquitto.h文档
其中涉及到的mosquitto_subscribe();函数解释
4.4编译验证
接下来我们就要将文件编译验证,此时还是在X86虚拟机Ubuntu上编译运行调试
这里编译指令特别折磨人 但是理解了就好了
重点
1.这里提供了在ARM开发板以及X86_Linux上编译指令
2.X86_Linux我重新gcc编译了openssl、libuuid、mosquitto三个库,所以我的/opt/文件夹下有两个版本,一个是mosquitto-x86,一个是mosquitto-arm,分别用于两种不同的设备环境运行调试用。
订阅端
ARM-SUB:
arm-linux-gcc mqtt_sub.c -o mqtt_sub -I /opt/mosquitto-arm/mosquitto-1.5/usr/local/include -L /opt/mosquitto-arm/mosquitto-1.5/usr/local/lib -L /opt/mosquitto-arm/libuuid-1.0.3/lib -L /opt/mosquitto-arm/openssl/lib -lmosquitto -lssl -lcrypto -luuid
X86-SUB:
gcc mqtt_sub.c -o mqtt_sub -I /opt/mosquitto-x86/mosquitto-1.5/usr/local/include -L /opt/mosquitto-x86/mosquitto-1.5/usr/local/lib -L /opt/mosquitto-x86/libuuid-1.0.3/lib -L /opt/mosquitto-x86/openssl/lib -lmosquitto -lssl -lcrypto -luuid
发布端
ARM-PUB:
arm-linux-gcc mqtt_pub.c -o mqtt_pub -I /opt/mosquitto-arm/mosquitto-1.5/usr/local/include -L /opt/mosquitto-arm/mosquitto-1.5/usr/local/lib -L /opt/mosquitto-arm/libuuid-1.0.3/lib -L /opt/mosquitto-arm/openssl/lib -lmosquitto -lssl -lcrypto -luuid
X86-PUB:
gcc mqtt_pub.c -o mqtt_pub -I /opt/mosquitto-x86/mosquitto-1.5/usr/local/include -L /opt/mosquitto-x86/mosquitto-1.5/usr/local/lib -L /opt/mosquitto-x86/libuuid-1.0.3/lib -L /opt/mosquitto-x86/openssl/lib -lmosquitto -lssl -lcrypto -luuid
指令解释:
1.arm-linux-gcc/gcc:编译器
2.mqtt_pub.c/mqtt_sub.c 待编译的C文件
3.-I +include文件夹(C语言的mosquitto.h库文件地址)
4.-L +lib文件夹 (mosquitto/openssl/libuuid库文件地址)
5.-lmosquitto -lssl -lcrypto -luuid代表使用什么库
编译成功后获得两个程序mqtt_sub和mqtt_pub
此时打开两个窗口左边为订阅端,右边为发布端
我们右边发布端输入 hello world!
订阅端成功收到数据,发送成功!
五、SQLITE3数据库的使用
sqlite3是一个轻量级,不需要用户名,密码,直接就进行操作的数据库,由于可以方便移植,还有相应的库问题提供,所以这里就使用sqlite3移植到开发板上来完成数据存储与读取的功能。
5.1数据库常用语句
在完成数据库移植之后要熟悉下数据库的使用
以下是简单使用的代码
在Terminal下输入sqlite3就可以进入数据库程序,使用以下语句就可以进行数据库操作了。
1 .help 帮助
2 .exit 退出
3 .quit 退出
4
5 create table stu(id Integer,name char,score Integer); 创建表格 id为整形 name为字符串类型
6 insert into values(1001,'zhangsan',80); 插入信息
7 insert into stu (id,name)values(1003,'wangwu') 插入部分信息
8 select * from stu; 查看表格所有信息
9 select * from stu where name='wangwu'; 查找wangwu的信息
10 delete from stu where name='wangwu'; 删除wangwu的信息
11 update stu set name='wangwu' where id= 1001; 改变id1005名字为wangwu
12 alter table stu add colum address char; 添加address列
13
14 删除列操作
15 create table stu1 as select id,name,score from stu; 从stu复制前三列到stu1
16 drop table stu; 删除stu
17 alter table stu1 rename to stu; 重命名
18
19 select * from record order by date asc; 降序显示
20 select * from record order by date desc; 升序显示
21 select * from record order by date desc limit 10; 显示最后十个数据
5.2数据库C代码
熟悉了代码之后我们就用C来实现数据的插入与读取。
以下是sqlite3.c代码
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>
#include <sqlite3.h>
#include <time.h>
#define DATABASE "tempdata.db"
int get_date(char *date)
{
time_t t;
struct tm *tp;
time(&t);
//时间格式转换
tp = localtime(&t);
sprintf(date,"%d-%02d-%02d %02d:%02d:%02d",tp->tm_year + 1900,tp->tm_mon + 1,tp->tm_mday,tp->tm_hour,tp->tm_min,tp->tm_sec);
return 0;
}
int main(int argc, char *argv[])
{
sqlite3 *db;
char date[128],sql[512];
char table_cmd[128];
char *errmsg;
int Devaddr;
float temp;
char inputc;
while(1)
{
get_date(date);
printf("Please input temp\n");
scanf("%f",&temp);
printf("Please input Devaddr\n");
scanf("%d",&Devaddr);
sprintf(sql,"insert into record values('%s','%d','%.2f');",date,Devaddr,temp);
if(sqlite3_open(DATABASE,&db) != SQLITE_OK)
{
printf("%s\n",sqlite3_errmsg(db));
return 0;
}else
{
sprintf(table_cmd,"create table if not exists record(date char,Devaddr integer,Temp integer);");
if(sqlite3_exec(db,table_cmd,NULL,NULL,&errmsg) != SQLITE_OK)
{
printf("%s\n",errmsg);
}
}
if(sizeof(sql) != 0)
{
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg) != SQLITE_OK)
{
printf("%s\n",errmsg);
return 0;
}else
{
printf("storge ok!\n");
printf("show table?y/n\n");
scanf("%s",&inputc);
if(inputc == 'y'||inputc == 'Y')
{
char **resultp,sql_out[128];
int nrow,ncloumn,i,j,index;
sprintf(sql,"select * from record order by date desc limit 10;");
if(sqlite3_get_table(db,sql,&resultp,&nrow,&ncloumn,&errmsg) != SQLITE_OK)
{
printf("%s\n",errmsg);
}
index = ncloumn;
for(i=0;i<nrow;i++)
{
for(j=0;j<ncloumn;j++)
{
printf(" %11s ",resultp[index++]);
}
putchar(10);
}
}
}
}
}
return 0;
}
代码分析:
1.依旧从main()主函数开始看
首先sqlite3 *db;是申请sqlite3类型的句柄,用于后续操作。
两个scanf分别是输入两个参数一个是Devaddr一个是temp。
2.这里getdate()函数是用来获取系统时间,物联网数据库要求是必须要有时间表达。
3.sprintf语句作用是将sqlite语句加上获取到的时间+输入的数据加入到sql[]数组中保存
4.用sqlite_open()语句打开数据库生成句柄保存在db中,其中参数database为数据库名称,这里我们命名为tempdata.db
5.申请成功后使用sprintf把sql语句加入到cmd_table数组中,意思为如果不存在名为record图表就创建,其格式为date 字符型,Devaddr数字,Temp数字。
create table if not exists record(date char,Devaddr integer,Temp integer);
6.接下来就是数据库操作的重点函数
int sqlite3_exec(
sqlite3*, /* open 打开的数据库 */
const char *sql, /* 执行的sql功能语句 */
int (*callback)(void*,int,char**,char**), /* sql语句对应的回调函数 */
void *, /* 传递给回调函数的 指针参数 */
char **errmsg /* 错误信息 */
);
该函数回返回一个 SQLITE_OK即为操作成功。
7.另一个重点函数是
int sqlite3_get_table(
sqlite3 *db, /* An open database */
const char *zSql, /* SQL to be evaluated */
char ***pazResult, /* Results of the query */
int *pnRow, /* Number of result rows written here */
int *pnColumn, /* Number of result columns written here */
char **pzErrmsg /* Error msg written here */
);
sqlite3_get_table(db,sql,&resultp,&nrow,&ncloumn,&errmsg)
该函数回返回行指针和列指针,还有一个结果指针。
两个for循环为循环打印出来最近十行数据结果。
5.3代码验证
我们先进行编译 这里同样提供两种不同平台编译方式
x86://apt-get install sqlite3 使用以下命令就可以找多需要的库与.h文件
gcc sqlite3.c -o sqlite -g -Wall -I /usr/local/lib/ -lsqlite3
arm://这个文件夹是上面移植sqlite3时保存的 如果需要开发板上运行就需要交叉编译
arm-linux-gcc -o sqlite -g -Wall -I /home/sqlite3-arm/include/ -L /home/sqlite3-arm/lib/ -lsqlite3 -lm
编译成功后会有一个sqlite可执行文件 我们执行并 随便输入两组数据 然后ctrl+c退出程序
退出后可以看到有一个tempdata.db文件
我们用sqlite3 tempdata.db进入sqlite3 然后
select * from record;
最终展示出来的数据如图所示
在程序中输出 结果如图所示
sqlite3使用介绍结束!
六、Boa+HTML的使用
6.1HTML的CGI调用
制作动态网页我们需要对html、css、javascript有个比较基础的了解。
这里引用B站UP主DasAutoooo的视频快速入门:
HTML CSS JAVASCRIPT
在有基础的知识储备之后我们先制作一个简单的HTML可交互的网页
思路是:
在嵌入式开发板上移植入HTML网页及其文件夹
通过局域网IP地址进入网页
按下按钮然后不刷新网页的前提下打印特定的数据
使用的技术是HTML+CSS+JAVASCRIPT+AJAX+CGI(boa接口)
一个完整的网页文件夹如图所示
网页代码:
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./css/index.css">
<link rel="icon" href="./images/ico.ico" type="image/x-icon" />
<link rel="shortcut icon" href="./images/ico.ico" type="image/x-icon" />
<script></script>
<title>嵌入式物联网平台</title>
</head>
<body>
<div class="imgsitech">
<img src="images/mainpage.png" width="100vw">
</div>
<div>
<p class="first_title">数据显示框</p>
</div>
<div class="data_show">
<p id="return_value">按下刷新开始</p>
</div>
<div class="botton_box">
<input type="button" value="刷新" onclick="sender()" />
</div>
</body>
<script src="javascript/index.js"></script>
</html>
CSS:
body {
background-color: #f4f4f4;
color: #555555;
margin: auto;
width: 80%;
font-size: 16px;
line-height: 1.6em;
margin-top: 20px;
margin-bottom: 40px;
margin-left: 80px;
margin-right: 60px;
padding-top: 8px;
padding-bottom: 40px;
padding-left: 20px;
padding-right: 10px;
}
.imgsitech {
text-align: center;
}
#localtime {
text-align: center;
}
.first_title {
text-align: center;
font-size: 50px;
color: #555555;
}
.data_show {
margin-left: 650px;
width: 15%;
text-align: center;
border: solid #5c5c5c 2px;
word-wrap: break-word;
}
.botton_box {
text-align: center;
}
JAVASCRIPT:AJAX
/*
*创建异步访问对象
*/
function createXHR() {
var xhr;
try {
xhr = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
} catch (E) {
xhr = false;
}
}
if (!xhr && typeof XMLHttpRequest != 'undefined') {
xhr = new XMLHttpRequest();
}
return xhr;
}
/*
*异步访问提交处理
*/
function sender() {
xhr = createXHR();
if (xhr) {
xhr.onreadystatechange = callbackFunction;
//test.cgi后面跟个cur_time参数是为了防止Ajax页面缓存
xhr.open("GET", "cgi-bin/test.cgi?cur_time=" + new Date().getTime());
xhr.send(null);
} else {
//XMLHttpRequest对象创建失败
alert("浏览器不支持,请更换浏览器!");
}
}
/*
*异步回调函数处理
*/
function callbackFunction() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
var returnValue = xhr.responseText;
if (returnValue != null && returnValue.length > 0) {
document.getElementById("return_value").innerHTML = returnValue;
} else {
alert("结果为空!");
}
} else {
alert("页面出现异常!");
}
}
}
CGI程序:
#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h>
#include <strings.h>
#include <string.h>
int main(void)
{
//这一句一定要加,否则异步访问会出现页面异常
printf("Content type: text/html\n");
printf("\nHello AJAX\n"); //这里的\n一定不能去掉否则会出错
}
交叉编译生成 cgi文件
arm-linux-gcc test.c -o test.cgi -g -Wall
把css javascript images index.html放到开发板/www文件下
把test.cgi 放到开发板/www/cgi-bin/文件夹下
此时/www及 /www/cgi-bin/下的文件
就可以通过开发板ip访问网页了,这里我的开发板ip地址为192.168.1.230
访问之后得到一个网页 点击刷新即可刷新数据 不刷新网页获取到了HelloAJAX字符串
七、项目整合
7.1 cJSON库的使用
cjson.c+cjson.h文件
提取码:10e3
7.1.1示例程序
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "cJSON.h"
int main(int argc, char **argv)
{
cJSON *json;
char *buf;
char *led1;
int led2;
json = cJSON_CreateObject();
//封装
cJSON_AddStringToObject(json, "led1", "on");
cJSON_AddNumberToObject(json, "led2", 1);
buf = cJSON_Print(json);
printf("%s\n", buf);
//解析
cJSON* cjson = cJSON_Parse(buf);
if(cjson == NULL){
printf("json pack into cjson error...\n");
}else{
int output;
led1 = cJSON_GetObjectItem(cjson, "led1")->valuestring;
led2 = cJSON_GetObjectItem(cjson, "led2")->valueint;
printf("led1=%s\n", led1);
printf("led2=%d\n", led2);
cJSON_Delete(cjson);
}
free(buf);
cJSON_Delete(json);
return 0;
}
1、2为打包流程,3、4为解析流程
1.打包json部分需要先创建一个Object
json = cJSON_CreateObject();
2.在json创建完之后可以添加项目如下 得到的数据内容 {“led1”:“on”,“led2”:1}
cJSON_AddStringToObject(json, "led1", "on");
cJSON_AddNumberToObject(json, "led2", 1);
3.解析流程需要先创建一个buf来收纳json内容,然后用创建一个cjson指针保存cJSON_Parse(buf);返回来的数据。
4.设置变量来保存cJSON_GetObjectItem()返回的值,注意要求的数值,这样就可以得到数据了。
led1 = cJSON_GetObjectItem(cjson, "led1")->valuestring;
led2 = cJSON_GetObjectItem(cjson, "led2")->valueint;
7.2MQTT+SQLITE3实现网络到本地数据储存
7.2.1MQTT+SQLITE3代码分析
代码:mqtt_sub.c
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>
#include <sqlite3.h>
#include <time.h>
#include "cJSON.h"
#include <mosquitto.h>
void mqtt_log_callback(struct mosquitto *mqtt, void *ubuf, int level, const char *str)
{
printf("[log] level=%d str=%s ubuf=%s\n", level, str, (char *)ubuf);
}
void mqtt_connect_callback(struct mosquitto *mqtt, void *ubuf, int result)
{
int i;
printf("[connect] level=%d\n", result);
if(!result){
/* Subscribe to broker information topics on successful connect. */
mosquitto_subscribe(mqtt, NULL, "mqtt", 2);
}else{
fprintf(stderr, "Connect failed\n");
}
}
int get_date(char *date)
{
time_t t;
struct tm *tp;
time(&t);
//时间格式转换
tp = localtime(&t);
sprintf(date,"%d-%02d-%02d %02d:%02d:%02d",tp->tm_year + 1900,tp->tm_mon + 1,tp->tm_mday,tp->tm_hour,tp->tm_min,tp->tm_sec);
return 0;
}
void mqtt_message_callback(struct mosquitto *mqtt, void *ubuf, const struct mosquitto_message *message)
{
#define DATABASE "tempdata.db"
pid_t pid;
sqlite3 *db;
char date[128],sql[512];
char table_cmd[128];
char *errmsg;
if((pid = fork()) < 0)
{
perror("fork");
return ;
}else if(pid == 0)
{
if(message->payloadlen)
{
cJSON* cjson = cJSON_Parse((char *)message->payload);
printf("%s %s\n", message->topic, (char *)message->payload);
if(cjson == NULL)
{
printf("json pack into json error....\n");
}else
{
if(cJSON_GetObjectItem(cjson,"Devaddr") != NULL)
{
int Devaddr = (int)(cJSON_GetObjectItem(cjson,"Devaddr")->valueint);
//printf("Devaddr:%d\n",Devaddr);
double temp = (double)cJSON_GetObjectItem(cjson,"temp")->valuedouble;
get_date(date);
sprintf(sql,"insert into record values('%s','%d','%.2f');",date,Devaddr,temp);
}
}
if(sqlite3_open(DATABASE,&db) != SQLITE_OK)
{
printf("%s\n",sqlite3_errmsg(db));//printf("%s\n",&errmsg);
return ;
}else
{
sprintf(table_cmd,"create table if not exists record(date char,Devaddr integer,Temp integer);");
if(sqlite3_exec(db,table_cmd,NULL,NULL,&errmsg) != SQLITE_OK)
{
printf("%s\n",errmsg);
}
}
if(sizeof(sql) != 0)
{
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg) != SQLITE_OK)
{
printf("%s\n",errmsg);
return ;
}else
{
printf("storge ok!\n");
}
}
}
}else
{
//父进程
}
}
void mqtt_disconnect_callback(struct mosquitto *mosq, void *obj, int result)
{
printf("mqtt_disconnect\n");
}
void mqtt_publish_callback(struct mosquitto *mosq, void *obj, int mid)
{
printf("publish_callback\n");
}
int main(int argc, char *argv[])
{
char buf[1024];
bool session = true;
char name[] = "mqtt";
struct mosquitto *mosquitto_sub = NULL;
mosquitto_lib_init();
mosquitto_sub = mosquitto_new(name, session, NULL);
if(!mosquitto_sub){
printf("create sub failed..\n");
mosquitto_lib_cleanup();
return 1;
}
mosquitto_log_callback_set(mosquitto_sub, mqtt_log_callback);
mosquitto_connect_callback_set(mosquitto_sub, mqtt_connect_callback);
mosquitto_message_callback_set(mosquitto_sub, mqtt_message_callback);
mosquitto_disconnect_callback_set(mosquitto_sub, mqtt_disconnect_callback);
mosquitto_publish_callback_set(mosquitto_sub, mqtt_publish_callback);
if(mosquitto_connect(mosquitto_sub, argv[1], 1883, 60)){
fprintf(stderr, "connect failed\n");
return 1;
}
printf("start recv...\n");
mosquitto_loop_forever(mosquitto_sub, -1, 1);
return 0;
}
直接看代码我们会觉得一头雾水,先看业务流程图吧。
通过仔细比较可以发现从初始化到进程生成完成都是在main函数中进行的,而生成新进程到关闭进程都是在mqtt_message_callback函数中进行的,这样分开看我们就很清晰的可以知道各函数的作用了。而main函数部分在第四章mqtt+c的使用中已经介绍的差不多了,所以我们直接在mqtt_message_callback函数部分分析。
1.当我们收到回调函数第一时间是新建一个子进程防止消息发送过快造成消息拥堵。
2.在进入回调函数之后会默认有一个message指针指向着发送过来的结构体的内存地址,所以我们用mseeage->payloadlen检测收到的值是否为空。
3.如果不为空那就使用cjson进行解析,打印出订阅的主题和收到的内容,再经过cJSON_GetObjectItem获取到相应的参数类型的值。
4.获取完成后打开数据库按照第三章sqlite3数据库的使用中介绍的那样保存数据。
5.保存完成后关闭子进程等待下一次创建。
7.2.2程序验证(开发板上)
这里同样提供两种平台编译指令
json+sqlite3+mosquitto x86_sub:
gcc cJSON.c mqtt_sub.c -o mqtt_sub -I /opt/mosquitto-x86/mosquitto-1.5/usr/local/include -L /opt/mosquitto-x86/mosquitto-1.5/usr/local/lib -L /opt/mosquitto-x86/libuuid-1.0.3/lib -L /opt/mosquitto-x86/openssl/lib -lmosquitto -lssl -lcrypto -luuid -I /usr/local/lib/ -lsqlite3
json+sqlite3+mosquitto arm_sub:
arm-linux-gcc cJSON.c mqtt_sub.c -o mqtt_sub -I /opt/mosquitto-arm/mosquitto-1.5/usr/local/include -L /opt/mosquitto-arm/mosquitto-1.5/usr/local/lib -L /opt/mosquitto-arm/libuuid-1.0.3/lib -L /opt/mosquitto-arm/openssl/lib -lmosquitto -lssl -lcrypto -luuid -I /home/sqlite3-arm/include/ -L /home/sqlite3-arm/lib/ -lsqlite3 -lm
ps:注意arm编译要多一个 -lm链接数字库
编译通过后再开发板上/目录上创建一个文件夹database
./mqtt_sub 149.xxx.xxx.xxx
运行程序 同时打开MQTT测试软件连接并填写相应参数
点击publish 发现开发板获取到数据
退出开发板 ls 发现多了一个 tempdata.db(图中的我的名字为Seatempdata.db)文件夹
输入slelect * from record;查看数据库内容
与我们mqtt测试程序输入的数据一致!
MQTT+SQLITE3实现网络到本地数据储存成功!
7.3SQLITE3+HTML实现本地数据展示
7.3.1SQLITE3+HTML+CGI文件代码分析
大部分代码与第六章Boa+HTML的使用类似 就不详细介绍了
代码:data_show.c
#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h>
#include <strings.h>
#include <string.h>
#define BACKLOG 5
#define DATABASE "/database/tempdata.db" //数据库一定要对不然显示会错误!
int main(void)
{
sqlite3 *db;
char sql[128];
char *errmsg;
sqlite3_open(DATABASE,&db);
sprintf(sql,"select * from record order by date desc limit 10;");
//这一句一定要加,否则异步访问会出现页面异常
printf("Content type: text/html\n");
char **resultp;
int nrow,ncloumn,i,j,index;
if(sqlite3_get_table(db,sql,&resultp,&nrow,&ncloumn,&errmsg) != SQLITE_OK)
{
printf("%s\n",errmsg);
}
index = ncloumn;
for(i=0;i<nrow;i++)
{
for(j=0;j<ncloumn;j++)
{
printf(" %11s ",resultp[index++]);
}
putchar(10);
}
}
7.3.2程序验证
编译程序得到 data_show.cgi
arm-linux-gcc data_show.c -o data_show.cgi -g -Wall -I /home/sqlite3-arm/include/ -L /home/sqlite3-arm/lib/ -lsqlite3 -lm
*PS:如果要更换名字需要在 /www/javascript/文件夹中的index.jx中修改data_show.cgi的名字
xhr.open("GET", "cgi-bin/data_show.cgi?cur_time=" + new Date().getTime());*
把data_show.cgi文件复制到开发板/www/cgi-bin/文件下
此时我们输入开发板ip地址 192.168.1.230 进入页面点击刷新
刷新后
成功获取数据并展示!
八、javascript-Mqtt脚本
为了准确验证 这里我提供了一个javascript脚本不断发送mqttjson数据来检验物联网平台搭建是否成功。
需要安装node.js
npm install mqtt mockjs --save --registry=https://registry.npm.taobao.org
node mock.js
注意EMQX_SERVER修改为自己的ip
// mock.js
const mqtt = require('mqtt')
const Mock = require('mockjs')
const EMQX_SERVER = 'mqtt://127.0.0.1:1883'
const CLIENT_NUM = 10
const STEP = 5000 // 模拟采集时间间隔 ms
const AWAIT = 5000 // 每次发送完后休眠时间,防止消息速率过快 ms
const CLIENT_POOL = []
startMock()
function sleep(timer = 100) {
return new Promise(resolve => {
setTimeout(resolve, timer)
})
}
async function startMock() {
const now = Date.now()
for (let i = 0; i < CLIENT_NUM; i++) {
const client = await createClient(`mock_client_${
i}`)
CLIENT_POOL.push(client)
}
// last 24h every 5s
const last = 24 * 3600 * 1000
for (let ts = now - last; ts <= now; ts += STEP) {
for (const client of CLIENT_POOL) {
const mockData = generateMockData()
const data = {
...mockData,
}
client.publish('mqtt', JSON.stringify(data))
}
const dateStr = new Date(ts).toLocaleTimeString()
console.log(`${
dateStr} send success.`)
await sleep(AWAIT)
}
console.log(`Done, use ${
(Date.now() - now) / 1000}s`)
}
/**
* Init a virtual mqtt client
* @param {string} clientId ClientID
*/
function createClient(clientId) {
return new Promise((resolve, reject) => {
const client = mqtt.connect(EMQX_SERVER, {
clientId,
})
client.on('connect', () => {
console.log(`client ${
clientId} connected`)
resolve(client)
})
client.on('reconnect', () => {
console.log('reconnect')
})
client.on('error', (e) => {
console.error(e)
reject(e)
})
})
}
/**
* Generate mock data
*/
function generateMockData() {
let i = Mock.Random.integer(0, 9);
return {
"temp": parseFloat(Mock.Random.float(10, 40).toFixed(2)),
"Devaddr": parseFloat(Mock.Random.int(1, 30).toFixed(2))
}
}
运行前记得现在开发板运行 mqtt_pub程序
在vscode Terminal中 运行
node ./mock.js
然后打开局域网网页我们就可以见到数据在不断刷新啦
最后补充一下我使用的开发板为 讯为的 Cortex-A9 4412
转载:https://blog.csdn.net/weixin_45955597/article/details/114113224