注册
web

前端程序员是怎么做物联网开发的(上)

前端程序员是怎么做物联网开发的

image-20230104162825029

上图是我历时一周做的在线的温湿度可视化项目,可以查看截至目前往前一天的温度、湿度变化趋势,并且实时更新当前温湿度

本文可能含有知识诅咒

概述和基础讲解

该项目用到的技术有:

  • 前端:jq、less、echarts、mqtt.js

  • 后端:eggjs、egg-emqtt

  • 数据库:mysql

  • 服务器:emqx(mqtt broker)

  • 硬件:

    • 板子:wemos D1 R2(设计基于 Arduino Uno R3 , 搭载esp8266 wifi模块)

  • 调试工具:mqttx、Arduino IDE v2.0.3 使用Arduino C开发

必备知识:

  • nodejs(eggjs框架)能面向业务即可

  • mysql 能写基本插入查询语句即可

  • C语言的基本语法了解即可

  • 知道mqtt协议的运作方式即可

  • arduino 开发板或任何其他电路板的初步了解即可

简单介绍一下上面几个的知识点:

  1. 从来没有后端学习经验的同学,推荐一个全栈项目供你参考:vue-xmw-admin-pro ,该项目用到了 前端VUE、后端eggjs、mysql、redis,对全栈学习很有帮助。

  2. mysql 只需要知道最简单的插入和查询语句即可,在本项目中,其实使用mongodb是更合适的,但是我为了方便,直接用了现成的mysql

  3. 即使你不知道C语言的基本语法,也可以在一小时内快速了解一下,知道简单的定义变量、函数、返回值即可

  4. MQTT(消息队列遥测传输)是一种网络协议(长连接,意思就是除了客户端可以主动向服务器通信外,服务器也可以主动向客户端发起),也是基于TCP/IP的,适用于算力低下的硬件设备使用,基于发布\订阅范式的消息协议,具体示例如下:

    image-20230104170333941

    当某客户端想发布消息时,图大概长这样:

    image-20230104171235368

    由上图可知,当客户端通过验证上线后,还需要订阅主题,当某客户端向某主题发布消息时,只有订阅了该主题的客户端会收到broker的转发。

    举一个简单的例子:你和我,还有他,我们把自己的名字、学号报告给门卫大爷(broker),门卫大爷就同意我们在警卫室玩一会,警卫室有无数块黑板(topic),我们每个人都可以向门卫请求:如果某黑板上被人写了字,请转告给我。门卫会记住每个人的要求,比如当你向一块黑板写了字(你向某topic发送了消息),所有要求门卫告诉的人都会被门卫告知你写了什么(如果你也要求被告知,那么也包括你自己)。

  5. 开发板可以被写入程序,程序可以使用简单的代码控制某个针脚的高低电平,或者读取某针脚的数据。

开始

  1. 购买 wemos d1开发板、DHT11温湿度传感器,共计19.3元。

  2. 使用arduino ide(以下简称ide) 对wemos d1编程需要下载esp8266依赖 参见:Arduino IDE安装esp8266 SDK

  3. 在ide的菜单栏选择:文件>首选项>其他开发板管理器地址填入:arduino.esp8266.com/stable/pack…,可以顺便改个中文

  4. 安装ch340驱动参见: win10 安装 CH340驱动 实测win11同样可用

  5. 使用 micro-usb 线,连接电脑和开发板,在ide菜单中选择:工具>开发板>esp8266>LOLIN(WEMOS) D1 R2 & mini

  6. 选择端口,按win+x,打开设备管理器,查看你的ch340在哪个端口,在ide中选择对应的端口

  7. 当ide右下角显示LOLIN(WEMOS) D1 R2 & mini 在comXX上时,连接就成功了

  8. 打开ide菜单栏 :文件>示例>esp8266>blink,此时ide会打开新窗口,在新窗口点击左上角的上传按钮,等待上传完成,当板子上的灯一闪一闪,就表明:环境、设置、板子都没问题,可以开始编程了,如果报错,那么一定是哪一步出问题了,我相信你能够根据错误提示找出到底是什么问题,如果实在找不出问题,那么可能买到了坏的板子(故障率还是蛮高的)

wemos d1 针脚中有一个 3.3v电源输出,三个或更多的GND接地口,当安装DHT11传感器元件时,需要将正极插入3.3v口,负极插入GND口,中间的数据线插入随便的数字输入口,比如D5口(D5口的PIN值是14,后面会用到)。

使用DHT11传感器,需要安装库:DHT sensor library by Adafruit , 在ide的左侧栏中的库管理中直接搜索安装即可

下面是一个获取DHT11数据的简单示例,如果正常的话,在串口监视器中,会每秒输出温湿度数据

#include "DHT.h" //这是依赖或者叫库,或者叫驱动也行
#include "string.h"
#define DHTPIN 14     // DHT11数据引脚连接到D5引脚 D5引脚的PIN值是14
#define DHTTYPE DHT11 // 定义DHT11传感器
DHT dht(DHTPIN, DHTTYPE); //初始化传感器

void setup() {
Serial.begin(115200);
//wemos d1 的波特率是 115200
pinMode(BUILTIN_LED, OUTPUT); //设置一个输出的LED
dht.begin(); //启动传感器
}

char* getDHT11Data() {
float h = dht.readHumidity(); //获取湿度值
float t = dht.readTemperature(); //获取温度值
static char data[100];
if (isnan(h) || isnan(t)) {
  Serial.println("Failed to read from DHT sensor!");
  sprintf(data, "Temperature: %.1f, Humidity: %.1f", 0.0, 0.0); //如果任何一个值没有值,直接返回两个0.0,这样我们就知道传感器可能出问题了
  return data;
}
sprintf(data, "Temperature: %.1f, Humidity: %.1f", t, h); //正常就取到值,我这里拼成了一句话
return data;
}

void loop() {
char* data = getDHT11Data(); //此处去取传感器值
Serial.println("got: " + String(data)); // 打印主题内容
delay(1000); //每次循环延迟一秒
}

继续

到了这一步,如果你用的是普通的arduino uno r3板子,就可以结束了。

取到数据之后,你就可以根据数据做一些其他的事情了,比如打开接在d6引脚上的继电器,而这个继电器控制着一个加湿器。

如果你跟我一样,使用了带wifi网络的板子,就可以继续跟我做。

我们继续分步操作:

设备端:

  1. 引入esp8266库(上面已经提到安装过程)

    1. #include "ESP8266WiFi.h"
      复制代码
  2. 安装mqtt客户端库 ,直接在库商店搜索 PubSubClient ,下载 PubSubClient by Nick O'Leary 那一项,下载完成后:

    1. #include "PubSubClient.h"
      复制代码
  3. 至此,库文件已全部安装引入完毕

  4. 设置 wifi ssid(即名字) 和 密码,如:

    1. char* ssid = "2104";
      char* passwd = "13912428897";
      复制代码
  5. 尝试连接 wifi

    1. WiFiClient espClient;
      int isConnect = 0;
      void connectWIFI() {
       isConnect = 0;
       WiFi.mode(WIFI_STA);  //不知道什么意思,照着写就完了
       WiFi.begin(ssidpasswd); //尝试连接
       int timeCount = 0;  //尝试次数
       while (WiFi.status() !WL_CONNECTED) { //如果没有连上,继续循环
         for (int i = 200i <= 255i++) {
           analogWrite(BUILTIN_LEDi);
           delay(2);
        }
         for (int i = 255i >= 200i--) {
           analogWrite(BUILTIN_LEDi);
           delay(2);
        }
         // 上两个循环共计200ms左右,在控制LED闪烁而已,你也可以不写
         Serial.println("wifi connecting......" + String(timeCount));
         timeCount++;
         isConnect = 1//每次都需要把连接状态码设置一下,只有连不上时设置为0
         // digitalWrite(BUILTIN_LED, LOW);
         if (timeCount >= 200) {
           // 当40000毫秒时还没连上,就不连了
           isConnect = 0//设置状态码为 0
           break;
        }
      }
       if (isConnect == 1) {
         Serial.println("Connect to wifi successfully!" + String("SSID is ") + WiFi.SSID());
         Serial.println(String("mac address is ") + WiFi.macAddress());
         // digitalWrite(BUILTIN_LED, LOW);
         analogWrite(BUILTIN_LED250); //设置LED常亮,250的亮度对我来说已经很合适了
         settMqttConfig();  //尝试连接mqtt服务器,在下一步有详细代码
      else {
         analogWrite(BUILTIN_LED255); //设置LED常灭,不要问我为什么255是常灭,因为我的灯是高电平熄灭的
         //连接wifi失败,等待一分钟重连
         delay(60000);
      }
      }
      复制代码
  6. 尝试连接 mqtt

    1. const char* mqtt_server = "larryblog.top"; //这里是我的服务器,当你看到这篇文章的时候,很可能已经没了,因为我的服务器还剩11天到期
      const char* TOPIC = "testtopic";           // 设置信息主题
      const char* client_id = "mqttx_3b2687d2";   //client_id不可重复,可以随便取,相当于你的网名
      PubSubClient client(espClient);
      void settMqttConfig() {
      client.setServer(mqtt_server, 1883); //设定MQTT服务器与使用的端口,1883是默认的MQTT端口
      client.setCallback(onMessage); //设置收信函数,当订阅的主题有消息进来时,会进这个函数
      Serial.println("try connect mqtt broker");
      client.connect(client_id, "wemos", "aa995231030"); //后两个参数是用户名密码
      client.subscribe(TOPIC); //订阅主题
      Serial.println("mqtt connected"); //一切正常的话,就连上了
      }
      //收信函数
      void onMessage(char* topic, byte* payload, unsigned int length) {
      Serial.print("Message arrived [");
      Serial.print(topic); // 打印主题信息
      Serial.print("]:");
      char* payloadStr = (char*)malloc(length + 1);
      memcpy(payloadStr, payload, length);
      payloadStr[length] = '\0';
      Serial.println(payloadStr); // 打印主题内容
      if (strcmp(payloadStr, (char*)"getDHTData") == 0) {
        char* data = getDHT11Data();
        Serial.println("got: " + String(data)); // 打印主题内容
        client.publish("wemos/dht11", data);
      }
      free(payloadStr); // 释放内存
      }
      复制代码
  7. 发送消息

    1. client.publish("home/status/", "{device:client_id,'status':'on'}");
      //注意,这里向另外一个主题发送的消息,消息内容就是设备在线,当有其他的客户端(比如web端)订阅了此主题,便能收到此消息
      复制代码

至此,板子上的代码基本上就写完了,完整代码如下:

#include "ESP8266WiFi.h"
#include "PubSubClient.h"
#include "DHT.h"
#include "string.h"
#define DHTPIN 14      // DHT11数据引脚连接到D5引脚
#define DHTTYPE DHT11  // DHT11传感器
DHT dht(DHTPINDHTTYPE);

charssid = "2104";
charpasswd = "13912428897";
const charmqtt_server = "larryblog.top";
const charTOPIC = "testtopic";            // 订阅信息主题
const charclient_id = "mqttx_3b2687d2";
int isConnect = 0;
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
void setup() {
 Serial.begin(115200);
 // Set WiFi to station mode
 connectWIFI();
 pinMode(BUILTIN_LEDOUTPUT);
 dht.begin();
}
chargetDHT11Data() {
 float h = dht.readHumidity();
 float t = dht.readTemperature();
 static char data[100];
 if (isnan(h) || isnan(t)) {
   Serial.println("Failed to read from DHT sensor!");
   sprintf(data"Temperature: %.1f, Humidity: %.1f"0.00.0);
   return data;
}
 sprintf(data"Temperature: %.1f, Humidity: %.1f"th);
 return data;
}
void connectWIFI() {
 isConnect = 0;
 WiFi.mode(WIFI_STA);
 WiFi.begin(ssidpasswd);
 int timeCount = 0;
 while (WiFi.status() !WL_CONNECTED) {
   for (int i = 200i <= 255i++) {
     analogWrite(BUILTIN_LEDi);
     delay(2);
  }
   for (int i = 255i >= 200i--) {
     analogWrite(BUILTIN_LEDi);
     delay(2);
  }
   // 上两个循环共计200ms左右
   Serial.println("wifi connecting......" + String(timeCount));
   timeCount++;
   isConnect = 1;
   // digitalWrite(BUILTIN_LED, LOW);
   if (timeCount >= 200) {
     // 当40000毫秒时还没连上,就不连了
     isConnect = 0;
     break;
  }
}
 if (isConnect == 1) {
   Serial.println("Connect to wifi successfully!" + String("SSID is ") + WiFi.SSID());
   Serial.println(String("mac address is ") + WiFi.macAddress());
   // digitalWrite(BUILTIN_LED, LOW);
   analogWrite(BUILTIN_LED250);
   settMqttConfig();
else {
   analogWrite(BUILTIN_LED255);
   //连接wifi失败,等待一分钟重连
   delay(60000);
}
}
void settMqttConfig() {
 client.setServer(mqtt_server1883);  //设定MQTT服务器与使用的端口,1883是默认的MQTT端口
 client.setCallback(onMessage);
 Serial.println("try connect mqtt broker");
 client.connect(client_id"wemos""aa995231030");
 client.subscribe(TOPIC);
 Serial.println("mqtt connected");
}
void onMessage(chartopicbytepayloadunsigned int length) {
 Serial.print("Message arrived [");
 Serial.print(topic);  // 打印主题信息
 Serial.print("]:");
 charpayloadStr = (char*)malloc(length + 1);
 memcpy(payloadStrpayloadlength);
 payloadStr[length] = '\0';
 Serial.println(payloadStr);  // 打印主题内容
 if (strcmp(payloadStr, (char*)"getDHTData") == 0) {
   chardata = getDHT11Data();
   Serial.println("got: " + String(data));  // 打印主题内容
   client.publish("wemos/dht11"data);
}
 free(payloadStr);  // 释放内存
}
void publishDhtData() {
 chardata = getDHT11Data();
 Serial.println("got: " + String(data));  // 打印主题内容
 client.publish("wemos/dht11"data);
 delay(2000);
}
void reconnect() {
 Serial.print("Attempting MQTT connection...");
 // Attempt to connect
 if (client.connect(client_id"wemos""aa995231030")) {
   Serial.println("reconnected successfully");
   // 连接成功时订阅主题
   client.subscribe(TOPIC);
else {
   Serial.print("failed, rc=");
   Serial.print(client.state());
   Serial.println(" try again in 5 seconds");
   // Wait 5 seconds before retrying
   delay(5000);
}
}
void loop() {
 if (!client.connected() && isConnect == 1) {
   reconnect();
}
 if (WiFi.status() !WL_CONNECTED) {
   connectWIFI();
}
 client.loop();
 publishDhtData();
 long now = millis();
 if (now - lastMsg > 2000) {
   lastMsg = now;
   client.publish("home/status/""{device:client_id,'status':'on'}");
}
 // Wait a bit before scanning again
 delay(1000);
}

服务器

刚才的一同操作很可能让人一头雾水,相信大家对上面mqtt的操作还是一知半解的,不过没有关系,通过对服务端的设置,你会对mqtt的机制了解的更加透彻

我们需要在服务端部署 mqtt broker,也就是mqtt的消息中心服务器

在网络上搜索 emqx , 点击 EMQX: 大规模分布式物联网 MQTT 消息服务器 ,这是一个带有可视化界面的软件,而且画面特别精美,操作特别丝滑,功能相当强大,使用起来基本上没有心智负担。点击立即下载,并选择适合你的服务器系统的版本:

image-20230223102450653

这里拿 ubuntu和windows说明举例,相信其他系统也都大差不差

在ubuntu上,推荐使用apt下载,按上图步骤操作即可,如中途遇到其他问题,请自行解决

  1. sudo ufw status 查看开放端口,一般情况下,你只会看到几个你手动开放过的端口,或者只有80、443端口

  2. udo ufw allow 18083 此端口是 emqx dashboard 使用的端口,开启此端口后,可以在外网访问 emqx看板控制台

image-20230223103352676 当你看到如图所示的画面,说明已经开启成功了

windows下直接下载安装包,上传到服务器,双击安装即可

  1. 打开 “高级安全Windows Defender 防火墙”,点击入站规则>新建规则

  2. 点击端口 > 下一步

  3. 点击TCP、特定本地端口 、输入18083,点击下一步

  4. 一直下一步到最后一步,输入名称,推荐输入 emqx 即可

image-20230223103810837

当你看到如图所示画面,说明你已经配置成功了。

完成服务端程序安装和防火墙端口配置后,我们需要配置服务器后台的安全策略,这里拿阿里云举例:

如果你是 ESC 云主机,点击实例>点击你的服务器名>安全组>配置规则>手动添加

添加这么一条即可:

image-20230223104139442

如果你是轻量服务器,点击安全>防火墙>添加规则 即可,跟esc设置大差不差。

完成后,可以在本地浏览器尝试访问你的emqx控制台

image-20230223104408482

直接输入域名:18083即可,初始用户名为admin,初始密码为public,登录完成后,你便会看到如下画面

image-20230223104559151

接下来需要配置 客户端登录名和密码,比如刚刚在设备中写的用户名密码,就是在这个系统中设置的

点击 访问控制>认证 > 创建,然后无脑下一步即可,完成后你会看到如下画面

image-20230223104906488

点击用户管理,添加用户即可,用户名和密码都是自定义的,这些用户名密码可以分配给设备端、客户端、服务端、测试端使用,可以参考我的配置

image-20230223105013597

userClient是准备给前端页面用的 ,server是给后端用的,995231030是我个人自留的超级用户,wemos是设备用的,即上面设备连接时输入的用户名密码。

至此,emqx 控制台配置完成。

下载 mqttx,作为测试端尝试连接一下

image-20230223105505838

点击连接,你会发现,根本连接不上......

因为,1883(mqtt默认端口)也是没有开启的,当然,和开启18083的方法一样。

同时,还建议你开启:

  • 1803 websocket 默认端口

  • 1804 websockets 默认端口

  • 3306 mysql默认端口

后面这四个端口都会用到。

当你开启完成后,再次尝试使用mqttx连接broker,会发现可以连接了

image-20230223105957929

这个页面的功能也是很易懂的,我们在左侧添加订阅,右侧的聊天框里会出现该topic的消息

image-20230223110105586

你是否还记得,在上面的设备代码中,我们在loop中每一秒向 home/status/ 发送一条设备在线的提示,我们现在在这里就收到了。

当你看到这些消息的时候,就说明,你的设备、服务器、emqx控制台已经跑通了。

前后端以及数据库

前端

前端不必多说,我们使用echarts承载展示数据,由于体量较小,我们不使用任何框架,直接使用jq和echarts实现,这里主要讲前端怎么连接mqtt

首先引入mqtt库

然后设置连接参数

  const options = {
clean: true, // true: 清除会话, false: 保留会话
connectTimeout: 4000, // 超时时间
clientId: 'userClient_' + generateRandomString(),
//前端客户端很可能比较多,所以这里我们生成一个随机的6位字母加数字作为clientId,以保证不会重复
username: 'userClient',
password: 'aa995231030',
}
function generateRandomString() {
let result = '';
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let charactersLength = characters.length;
for (let i = 0; i < 6; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}

连接

  // const connectUrl = 'mqtt://larryblog.top/mqtt' 当然你可以使用mqtt协议,但是有可能会遇到 ssl 跨域的问题,如果你不使用 https 可以忽略这一项,直接使用mqtt即可
const connectUrl = 'wss://larryblog.top/mqtt' //注意,这里使用了nginx进行转发,后面会讲
const client = mqtt.connect(connectUrl, options)

因为前端代码不多,我这里直接贴了

html:

index.html











<span class="http"><span class="properties"><span class="hljs-attr">wemos</span> <span class="hljs-string">d1 test</span></span></span>






Loading device status





Current temperature:
loading...



Current humidity:
loading...












续:前端程序员是怎么做物联网开发的(下)

作者:加伊juejin
来源:juejin.cn/post/7203180003471081531

0 个评论

要回复文章请先登录注册