基于ESP32+微信小程序的物联网应用(小程序部分)

这篇具有很好参考价值的文章主要介绍了基于ESP32+微信小程序的物联网应用(小程序部分)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本项目是基于 ESP32 实现的全栈物联网应用,将会手把手教用户实现 MQTT 环境的搭建硬件传感器的驱动以及小程序的编写

手把手教学配套视频地址

您将会学习到 HTML、CSS、JS、微信小程序官方开发语言、Echarts、NodeJs、Express、MySQL、ARDUINO 等技能,此文章篇幅较长,建议分为多次观看,一起往下看吧~

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

一、开发环境搭建和微信开发者工具安装

nodejs 环境安装

我们写 js 的环境一般都需要 nodejs 的支持,另外安装 npm(js 包管理工具)也需要 nodejs 环境,现在就教大家一起来安装

官网下载地址Node.js (nodejs.org) 选择长期维护版本并下载根据提示安装即可

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

腾讯软件管家下载地址 https://pc.qq.com/detail/5/detail_24845.html

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

验证是否安装成功

打开 cmd 输入 node-v 如图所示有版本号即为安装成功(版本号不一致不碍事)

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

接下来输入 npm -v 验证 npm 是否安装成功 npm 是 nodejs 附带的

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

至此 nodejs 已安装成功

微信开发者工具安装和配置

工具下载地址https://developers.weixin.qq.com/miniprogram/dev/devtools/devtools.html

选择适合自己操作系统的版本下载

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

文档地址(用于学习和参考)

官方文档写的很详细 不懂的地方一定要多看文档

https://developers.weixin.qq.com/miniprogram/dev/framework/

安装

这里直接下一步就行

界面

打开后允许通过防火墙并扫码登陆 整体界面如下

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

二、新建项目和项目整体配置

开发者工具界面

打开微信开发者工具先扫码登陆

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

由于我们开发的是小程序 这里选择小程序 点击 + 号创建一个小程序项目

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

接着选择项目保存路径,尽量放在自己能找到的地方 采用英文名

Appleid 这里需要上线的话选择注册,会跳转到注册连链接(需要认证费用) 如果不是打算上线的话选择测试号即可,这里我们选择测试

后端服务选择 不使用云服务

模板选择 javascript-基础模板

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

然后点击确定就创建项目完成了

微信开发者工具结构配置介绍

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

文件结构

一个小程序包含一个描述整体程序的 app 和多个描述各个页面的 pages。 一个小程序主体由三个文件组成,必须放在项目的根目录下:
app.js:小程序逻辑文件

  • app.json: 小程序公共设置(主体配置文件)
  • app.wxss:小程序公共样式表(全局样式) (可以删除)
  • 一个小小程序 page 页面由四个文件组成,分别是:
  • js:页面逻辑
  • wxml:页面结构
  • wxss:页面样式(可以删除)
  • json:页面配置(可以删除)
  • utils:工具目录(可以删除),实现了模块化工具类,工具函数。
  • sitemap.json:小程序搜索所用。
  • project.config.json:编辑器配置。

小程序配置 app.json

app.json 文件用来对小程序进行全局配置,决定小程序的页面数量、窗口表现、设置网络超时时间、设置底部或顶部菜单等。

app.json 中不能添加任何注释,key 和 value 字符串必须用双引号引起来,数组或对象最后一位不能有逗号。

app.json 配置项列表

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

pages
"pages": [
    "pages/index/index",  // 数组第一项,小程序的初始页面。
    "pages/logs/logs"
],

pages 指定由哪些页面组成,每一项代表对应页面【路径+目录名+文件名】。

pages 数组的第一项代表小程序的初始页面(首页/展示页)。

小程序中新增/减少页面,都需要对 pages 数组进行修改 。

window

用于设置小程序的状态栏、导航条、标题、窗口背景色。

"window":{
    "enablePullDownRefresh": true, //是否开启开启全局的下拉刷新 true 开启,false 关闭。
    "backgroundColor":" #ccc", //窗口的背景色
    "backgroundTextStyle":"light", //下拉 loading 的样式,仅支持 dark 和 light
    "navigationBarBackgroundColor": "#fff", //导航栏背景色
    "navigationBarTitleText": "Weixin", //导航栏标题文字内容
    "navigationBarTextStyle":"black", //导航栏标题颜色,仅支持白色(white)和黑色(black)
    "navigationStyle":"custom" //去除导航栏
},
tabBar

设置小程序的底部或顶部菜单栏。

注释:当设置 position 为 top 时,将不会显示 icon 图标。

当 tabBar 中的 list 是一个数组,只能配置最少 2 个、最多 5 个菜单。

  "tabBar": {
    "color": "#999", //tab上文字默认颜色,仅支持十六进制颜色
    "selectedColor": "", //tab 上的文字选中时的颜色,仅支持十六进制颜色
    "backgroundColor": "", //tab 的背景色,仅支持十六进制颜色
    "position": "bottom", //tabBar 的位置,仅支持 bottom / top
    "borderStyle": "black", //tabbar 上边框的颜色, 仅支持 black / white
    "list": [
      {
        "pagePath": "pagePath", //页面路径
        "text": "text", //页面文字
        "iconPath": "iconPath", //默认图标路径
        "selectedIconPath": "selectedIconPath" //选中图标路径
      },
      {
        "pagePath": "pagePath", //页面路径
        "text": "text", //页面文字
        "iconPath": "iconPath", //默认图标路径
        "selectedIconPath": "selectedIconPath" //选中图标路径
      }
    ]
  },
networkTimeout

可以设置各种网络请求的超时时间,单位毫秒。

"networkTimeout": {
    "request": 10000 //发起HTTP请求的超时时间 10s   (一般设置5s:5000毫秒)
 },

三、头部 ui 制作

页面标题

我们需要在 app.json 里去除掉标题块 设置 “navigationStyle”: "custom”

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

头部直接使用imagetext标签即可

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

天气板快

使用 css3 的 flex 布局

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

对于 flex
布局不理解的同学可以去看这篇文章,里面有很详细的讲解 Flex 布局教程:语法篇 - 阮一峰的网络日志 (ruanyifeng.com)

四、MQTT 连接 ui 制作

mqtt 板块

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

这里主要运用的是 flex 布局的两端对其属性justify-content:space-between

引入 Vant 组件库

什么是组件?为什么要使用组件库?

组件是页面中最基本的元素。按钮、输入框、下拉选择都是组件,组件和组件组合成为更复杂的组件。至于为什么要用组件库,我觉得应该是体验。用户体验和开发者的开发体验,用户在页面上的交互都是通过组件进行的。一个价值高的组件,第一眼就能吸引用户点击,这就是用户体验。更不用说开发经验了,组件是同一类型不同交互的封装。使用组件库可以让我们更专注于业务开发和产品交互。

怎么引入 Vant 组件库?

1、确保安装有 nodejs

2、在项目根目录打开 cmd 输入初始化项目的命令 npm init 一直按回车即可

3、然后通过 npm 指令安装 Vant Weapp npm i @vant/weapp -S --production

4、修改 app.json

将 app.json 中的 “style”: “v2” 去除,小程序的新版基础组件强行加上了许多样式,难以覆盖,不关闭将造成部分组件样式混乱。

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

5、构建 npm 包

打开微信开发者工具,点击 工具 -> 构建 npm,并勾选 使用 npm 模块 选项,可见官方文档 快速上手 的
步骤四。新版的微信开发者工具中,详情 -> 本地设置中没有【使用 npm 模块】选项,则不用理会, 如果有则需要勾选。

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

6、使用组件

你只需要在 app.json 或 你需要使用 vant 的页面中的 json 文件进行组件的注册即可使用了

这里涉及到注册组件的两种方式,后面会讲到。下面,以在 app.json 全局注册 button 组件为例:

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

注册引入组件后,在 wxml 中直接使用组件

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

mqtt 连接弹出框

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

弹窗使用的是 Vant Weapp 组件库的 Popup 弹出层 组件

输入框使用的是 Field 输入框

按钮使用的是 button 按钮

<!-- 弹出框 -->
<van-popup
  show="{{ mqttShow }}"
  round
  position="bottom"
  custom-style="height: 65%"
  bind:close="closeMqttDialog"
>
</van-popup>

<!-- js变量 mqttShow:false -->
<!-- 输入框 -->
<van-cell-group>
  <van-field
    disabled="{{isConnect}}"
    model:value="{{address}}"
    clearable
    label="address"
    placeholder="请输入地址"
    bind:click-icon="onClickIcon"
  />
  <van-field
    disabled="{{isConnect}}"
    model:value="{{ port }}"
    type="number"
    clearable
    label="port"
    placeholder="请输入端口号"
    bind:click-icon="onClickIcon"
  />
  <van-field
    disabled="{{isConnect}}"
    model:value="{{ username }}"
    clearable
    label="username"
    placeholder="请输入用户名"
    bind:click-icon="onClickIcon"
  />
  <van-field
    disabled="{{isConnect}}"
    model:value="{{ password }}"
    type="password"
    label="password"
    placeholder="请输入密码"
  />
</van-cell-group>

<!-- js变量 address: "", post: "", username: "", password: "", -->

mqtt 连接框逻辑编写

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

定义变量是否连接,默认值为 false isConnect=false

给这一块的代码加上点击事件 bindtap=”事件名”

/* 展示mqtt连接弹窗 */
 事件名() {
    this.setData({ mqttShow: true });
 },

根据是否连接切换颜色

<view style="color: {{isConnect?'RGB(0,176,80)':'RGB(192,0,0)'}};">
  {{isConnect?'已连接':'未连接'}}
</view>

根据连接弹窗是否打开切换箭头上下 需引入icon

<van-icon wx:if="{{!mqttShow}}" name="arrow-down" />
<van-icon name="arrow-up" wx:if="{{mqttShow}}" />

连接/断开按钮编写和变色逻辑

<van-button
  size="small"
  disabled="{{isConnect}}"
  color="{{isConnect?'#d9d9d9':'#97baff'}}"
  bindtap="mqttConnect"
  >连接</van-button
>
<van-button
  size="small"
  disabled="{{!isConnect}}"
  color="{{!isConnect?'#d9d9d9':'#97baff'}}"
  bindtap="mqttBreak"
  >断开</van-button
>

五、传感器设备和其他设备 ui 制作

具体使用 flex 布局,采用一行两个板块

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

/* 父元素设置 */
display: flex;
flex-wrap: wrap;
width: 100%;

/* 子元素为父元素/2的值 即可一行两个 超过换行 */
width: 315rpx;

通过数据驱动视图来循环出多个数据板块 默认值给 0

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

sensorList: [
      //传感器列表
      //图 名字 参数 值 单位 序号
      {
        img: "/images/P1.png",
        name: "DHT22",
        parameter: "温度",
        value: 0,
        unit: "°C",
        idx: 0,
      },
      {
        img: "/images/P2.png",
        name: "DHT22",
        parameter: "湿度",
        value: 0,
        unit: "%rh",
        // isPass: true,
        idx: 1,
      },
      {
        img: "/images/P3.png",
        name: "TEMT6000",
        parameter: "光强",
        value: 0,
        unit: "lx",
        idx: 2,
      },
      {
        img: "/images/P4.png",
        name: "MQ2",
        parameter: "烟雾",
        value: 0,
        unit: "ppm",
        idx: 3,
      },
],

其他设备 ui 制作

同理,使用 flex 布局

开关组件使用 指定数据并循环展示

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

otherSensorList: [
      { img: "/images/deng.png", name: "灯", isOpen: false },
      { img: "/images/fengshan.png", name: "风扇", isOpen: false },
      {
        img: "/images/chuanglian.png",
        name: "窗帘",
        schedule: 0,//进度条
        isOpen: false,
      },
    ],

进度条使用的是 vant
的 progress 组件

六、天气 Api 申请和定位显示天气

步骤:

1、申请高德地图 key 我的应用 | 高德控制台 (amap.com)

2、将高德地图 api(https://restapi.amap.com)增加到小程序 request 合法域名

3、在 app.json 里增加

"permission": {
    "scope.userLocation": {
      "desc": "你的位置信息将用于小程序天气接口的效果展示"
    }
 }

4、获取设置函数wx.getSetting
看是否授权定位

5、获取定位函数wx.getLocation
获取经纬度 : latitude 纬度 longitude 经度

6、通过高德地图逆地理编码接口传入经纬度获取地区编码 接口文档

7、高德地图天气查询接口传入地区编码查询天气并显示天气和位置 接口文档

8、回显位置和天气情况并显示天气图图标

申请高德地图 key

点击创建新应用

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

选择 web 服务 ip 白名单设置为空

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

添加到 request 合法域名 https://restapi.amap.com

处于安全性考虑,小程序官方对于数据接口的请求作出了如下两个限制:

  • 只能请求 https 类型的接口;
  • 必须将接口的域名添加到信任列表中

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

配置 request 合法域名

假设需要在自己的微信小程序中,希望请求https://www.escook.cn/域名下的接口

配置步骤:登录微信小程序管理后台 ⇒ 开发 ⇒ 开发设置 ⇒ 服务器域名 ⇒ 修改 request 合法域名

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

跳过 request 合法域名校验(上线无法使用)

如果仅仅提供了 http 协议的接口,暂时没有提供 https 协议的接口。

此时为了不耽误开发进度,我们可以在微信开发者工具中,临时开启「开发环境不校验请求域名、TLS 版本以及 HTTPS 证书」选项,跳过
request 合法域名校验。

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

查看是否授权过定位权限

getUserLocation: function () {
    let that = this;
    wx.getSetting({
      success: (res) => {
        console.log(res, JSON.stringify(res));
        // res.authSetting['scope.userLocation'] == undefined    表示 初始化进入该页面
        // res.authSetting['scope.userLocation'] == false    表示 非初始化进入该页面,且未授权
        // res.authSetting['scope.userLocation'] == true    表示 地理位置授权

        if (
          res.authSetting["scope.userLocation"] != undefined &&
          res.authSetting["scope.userLocation"] != true
        ) {
          wx.showModal({
            title: "请求授权当前位置",
            content: "需要获取您的地理位置,请确认授权",
            success: function (res) {
              if (res.cancel) {
                wx.showToast({
                  title: "拒绝授权",
                  icon: "none",
                  duration: 1000,
                });
              } else if (res.confirm) {
                wx.openSetting({
                  success: function (dataAu) {
                    if (dataAu.authSetting["scope.userLocation"] == true) {
                      wx.showToast({
                        title: "授权成功",
                        icon: "success",
                        duration: 1000,
                      });
                      //再次授权,调用wx.getLocation的API
                      that.getLocation();
                    } else {
                      wx.showToast({
                        title: "授权失败",
                        icon: "none",
                        duration: 1000,
                      });
                    }
                  },
                });
              }
            },
          });
        } else if (res.authSetting["scope.userLocation"] == undefined) {
          //调用wx.getLocation的API
          that.getLocation();
        } else {
          //res.authSetting['scope.userLocation'] == true
          //调用wx.getLocation的API
          that.getLocation();
        }
      },
    });
  },

获取定位经纬度

getLocation() {
    let that = this;
    wx.getLocation({
      type: "wgs84",
      success(res) {
        console.log("经纬度", res);
        if (res?.errMsg === "getLocation:ok") {

					/* ----------------通过经纬度获取地区编码---------------- */
          wx.request({
            url: "https://restapi.amap.com/v3/geocode/regeo?parameters",
            data: {
              key: KEY, //填入自己申请到的Key
              location: res.longitude + "," + res.latitude,   //传入经纬度
            },
            header: {
              "content-type": "application/json",
            },
            success: function (res) {
              console.log("坐标转换和查询天气", res.data);
              wx.setStorageSync(
                "city",
                res.data.regeocode.addressComponent.adcode  //地区编码
              );
              that.setData({
                location:
                  res.data.regeocode.addressComponent.city +
                  " " +
                  res.data.regeocode.addressComponent.district,
              });

              wx.request({
                url: "https://restapi.amap.com/v3/weather/weatherInfo",
                data: {
                  key: KEY, //填入自己申请到的Key
                  city: res.data.regeocode.addressComponent.adcode, //传入地区编码
                },
                header: {
                  "content-type": "application/json",
                },
                success: function (weather) {
                  console.log("天气", weather.data);
                  that.setData({
                    weatherNum: weather.data.lives[0].temperature,//温度
                    weatherText: weather.data.lives[0].weather, //天气描述 晴天 下雨天...
										welcome: "欢迎欢迎!今天的天气是 " + weather.data.lives[0].weather, //欢迎语
                  });
                },
              });
            },
          });
        }
      },
			fail(err){
        console.log('获取经纬度错误信息',err);
      }
    });
  },

回显位置和天气情况并显示天气图图标

上述变量中绑定的变量温度为 weatherNum 天气描述为 weatherText 显示 :

<view class="welcome">{{welcome}}</view>

<view class="weather-num" wx:if="{{weatherNum}}">{{weatherNum}}℃</view>

天气图标显示 :

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

<wxs src="../../wxs/tools.wxs" module="tools"></wxs>
<view>
  <image
    wx:if="{{weatherText === '晴'}}"
    class="weather"
    src="/images/weather/qing.png"
    mode="widthFix"
  />
  <image
    wx:if="{{tools.strIndexOf(weatherText,'雨')}}"
    class="weather"
    src="/images/weather/yu.png"
    mode="widthFix"
  />
  <image
    wx:if="{{tools.strIndexOf(weatherText,'雪')}}"
    class="weather"
    src="/images/weather/xue.png"
    mode="widthFix"
  />
  <image
    wx:if="{{weatherText === '多云'}}"
    class="weather"
    src="/images/weather/duoyun.png"
    mode="widthFix"
  />
  <image
    wx:if="{{weatherText === '阴'}}"
    class="weather"
    src="/images/weather/duoyun.png"
    mode="widthFix"
  />
  <image
    wx:if="{{weatherText === '雷'}}"
    class="weather"
    src="/images/weather/lei.png"
    mode="widthFix"
  />
  <image
    wx:if="{{weatherText === '冰雹'}}"
    class="weather"
    src="/images/weather/bingbao.png"
    mode="widthFix"
  />
</view>

考虑到天气可能是大雪/小雪、大雨/小雨等 和我们定义的雨、雪不一样 所以要引入一个函数 根据包含了 雨、雪的状态来显示相应的图

function strIndexOf(weatherText, text) {
  //str.indexOf("")的值为-1时表示不包含
  if (weatherText) {
    // console.log(weatherText, text);
    if (weatherText.indexOf(text) !== -1) {
      return true;
    } else {
      return false;
    }
  }
}

module.exports = {
  strIndexOf: strIndexOf
};

//使用方法
strIndexOf(weatherText, "雨"); //weatherText中包含了雨则显示  雨/大雨/小雨/雷阵雨
strIndexOf(weatherText, "雪"); //weatherText中包含了雪则显示  雪/大雪/小雪

参考资料

有时候传入参数不当会导致接口报错 这里可以根据报错状态码查询问题

返回的天气文字描述对照表 列举了天气功能中所能返回的天气现象

七、MQTT 下载引入和配置连接

mqtt 官网和官方教程

MQTT 协议入门与进阶 | EMQ (emqx.com)

MQTT.js 入门教程 | EMQ (emqx.com)

mqtt 下载

进入https://unpkg.com/mqtt@4.1.0/dist/mqtt.min.js 右击另存为到桌面

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

导入和使用

将下载到本地的 mqtt.min.js 拷贝到项目的 utils 目录下,如下图所示

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

const app = getApp();
import mqtt from "../../utils/mqtt.min";
const MQTTADDRESS = "你的mqtt服务器地址"; //mqtt服务器地址
let client = null; //mqtt服务

创建连接

connectMqtt(){
    let that = this;
    const options = {
      connectTimeout: 4000,
      address: this.data.address,//输入的地址
      port: this.data.port, //输入的端口号
      username: this.data.username,//输入的用户名
      password: this.data.password,//输入的密码
    };

    console.log("address是:", options.address);
    client = mqtt.connect(MQTTADDRESS, options); //连接
		client.on("connect", (e) => {
      console.log('连接成功');
    })

		client.on("reconnect", (error) => {
      console.log("正在重连:", error);
      wx.showToast({
        icon: "none",
        title: "正在重连",
      });
    });
    client.on("error", (error) => {
      console.log("连接失败:", error);
      wx.showToast({
        icon: "none",
        title: "mqtt连接失败",
      });
    });
}

八、MQTT 订阅/发布和数据显示

添加订阅/添加发布(连接成功状态)

//订阅一个主题
client.subscribe("主题名字", { qos: 0 }, function (err) {
  if (!err) {
    console.log("成功");
    wx.showToast({
      icon: "none",
      title: "添加成功"
    });
  }
});

取消订阅/发布(连接成功状态)

client.unsubscribe(this.data.subscribeTopic, function (err) {
  if (!err) {
    wx.showToast({
      icon: "none",
      title: "取消成功"
    });
  }
});

发布消息(连接成功状态)

根据判断是哪个设备的开关 然后发送消息

发送的所有可能为:{LIGHT: “ON”} { LIGHT: “OFF” } { FAN: “ON” } { FAN: “OFF” } { CURTAIN: “ON” } { CURTAIN: “OFF” }

let msg = "发出的消息";
client.subscribe("发布地址", { qos: 0 }, function (err) {
  if (!err) {
    // 发布消息
    console.log("发出的", msg);
    client.publish("地址", JSON.stringify(msg)); //转换json格式
  }
});

收到消息和数据回显

client.on("message", (topic, message) => {
  /* 温度、湿度、光强、烟雾
      TEMPERATURE
      HUMIDITY
      LIGHT_INTENSITY
      SMOKE
      */
  console.log("收到消息:", message.toString());
  // wx.showToast({
  //   icon: "none",
  //   title: message.toString(),
  // });
  let getMessageObj = {}; //收到的消息
  getMessageObj = JSON.parse(message); //收到的消息转换成json对象

  if (getMessageObj.hasOwnProperty("TEMPERATURE"))
    this.setData({
      /*这里的sensorList为上面定义的传感器设备数据的数组 如果包含某项数据将更新那条数据 会自动刷新视图和百分比*/
      "sensorList[0].value": getMessageObj.TEMPERATURE ? Number(getMessageObj.TEMPERATURE) : 0
    });
  if (getMessageObj.hasOwnProperty("HUMIDITY"))
    this.setData({
      "sensorList[1].value": Number(getMessageObj.HUMIDITY)
    });
  if (getMessageObj.hasOwnProperty("LIGHT_INTENSITY"))
    this.setData({
      "sensorList[2].value": Number(getMessageObj.LIGHT_INTENSITY)
    });
  if (getMessageObj.hasOwnProperty("SMOKE"))
    this.setData({
      "sensorList[3].value": Number(getMessageObj.SMOKE)
    });
  if (getMessageObj.hasOwnProperty("CURATIN_PROGRESS"))
    this.setData({
      "otherSensorList[2].schedule": Number(getMessageObj.CURATIN_PROGRESS)
    });
});

九、设置传感器阈值和数据保存本地缓存

点击传感器出来弹窗

在循环的(wx:if)传感器块上面增加点击事件 bindtap="thresholdSetting”data-item="{{item}}”

//缓存数据
data:{
    thresholdDialog: false, //阈值
    thresholdObj: {
      top: "",
      bottom: "",
    },
    tempParameter: "", //某个参数的阈值
    tempIdx: "",
    tempTop: "",
    tempBottom: "",
},

//点击传感器事件
thresholdSetting(e) {
    console.log(e.currentTarget.dataset.item);
    this.setData({
      tempTop: e.currentTarget.dataset.item.top
        ? e.currentTarget.dataset.item.top
        : e.currentTarget.dataset.item.top == 0
        ? e.currentTarget.dataset.item.top
        : "",
      tempBottom: e.currentTarget.dataset.item.bottom
        ? e.currentTarget.dataset.item.bottom
        : e.currentTarget.dataset.item.bottom == 0
        ? e.currentTarget.dataset.item.bottom
        : "",
      thresholdDialog: true,//打开弹窗
      tempParameter: e.currentTarget.dataset.item.parameter, //某个参数的阈值
      tempIdx: e.currentTarget.dataset.item.idx,
      "thresholdObj.top": e.currentTarget.dataset.item.top
        ? e.currentTarget.dataset.item.top
        : e.currentTarget.dataset.item.top == 0
        ? e.currentTarget.dataset.item.top
        : "",
      "thresholdObj.bottom": e.currentTarget.dataset.item.bottom
        ? e.currentTarget.dataset.item.bottom
        : e.currentTarget.dataset.item.bottom == 0
        ? e.currentTarget.dataset.item.bottom
        : "",
    });
  },

wxml 部分 输入事件绑定函数 bindinput

<van-dialog
  use-slot
  title="设置安全阈值"
  show="{{ thresholdDialog }}"
  show-cancel-button
  bind:confirm="thresholdClose"
>
  <van-field
    value="{{thresholdObj.top}}"
    bindinput="getTop"
    type="number"
    clearable
    label="上限"
    placeholder="请输入上限"
    bind:click-icon="onClickIcon"
  />
  <van-field
    value="{{ thresholdObj.bottom }}"
    bindinput="getBottom"
    type="number"
    clearable
    label="下限"
    placeholder="请输入下限"
    bind:click-icon="onClickIcon"
  />
</van-dialog>

输入上限和下限事件

  getTop(e) {
    this.setData({ tempTop: e.detail });
  },
  getBottom(e) {
    this.setData({ tempBottom: e.detail });
  },

弹窗点击确定事件

thresholdClose() {
    console.log(
      this.data.tempParameter,
      this.data.tempIdx,

      this.data.tempTop,
      this.data.tempBottom
    );
    let index = this.data.tempIdx;
    this.setData({
      ["sensorList[" + index + "].top"]: Number(this.data.tempTop),
      ["sensorList[" + index + "].bottom"]: Number(this.data.tempBottom),
    });
    console.log(this.data.sensorList);
  },

连接数据保存本地缓存

这里要用到的三个 api:

wx.getStorageSync("key名"); 读取本地缓存中的某个字段

wx.setStorageSync("key名", key值); 将某个字段存入本地缓存

wx.removeStorageSync(”key名"); 将本地缓存中某个字段删除

我们需要的场景是连接成功的状态下次进来自动连接

//在mtqq连接成功后 我们把连接状态存到本地缓存 以及地址端口号账号密码存到本地缓存 用于在下次进来时候自动带入输入框 并且调用连接函数
let { address, post, username, password } = this.data;
let mqttInfo = { address, post, username, password };
wx.setStorageSync("mqttInfo", mqttInfo);
this.setData({ isConnect: true });
wx.setStorageSync("isConnect", true);

//点击断开连接后  删除本地缓存中是否有地址和登陆信息 有的话删除
this.setData({ isConnect: false, isPush: false, isSubscribe: false });
wx.setStorageSync("isConnect", false);
wx.getStorageSync("subscribeTopic") && wx.removeStorageSync("subscribeTopic");
wx.getStorageSync("pushTopic") && wx.removeStorageSync("pushTopic");
wx.getStorageSync("mqttInfo") && wx.removeStorageSync("mqttInfo");

进入小程序在 onLoad()生命周期中判断本地缓存中是否有数据 有的话带入并连接 mqtt

/*判断本地缓存中的登陆数据和是否连接数据是否存在*/
if (wx.getStorageSync("mqttInfo") && wx.getStorageSync("isConnect")) {
  let mqttInfo = wx.getStorageSync("mqttInfo");
  console.log(mqttInfo);
  this.setData({
    address: mqttInfo.address,
    post: mqttInfo.post,
    username: mqttInfo.username,
    password: mqttInfo.password
  });
  this.connectMqtt(); //连接mqtt
}
if (
  wx.getStorageSync("mqttInfo") &&
  wx.getStorageSync("isConnect") &&
  wx.getStorageSync("subscribeTopic")
) {
  this.setData({ subscribeTopic: wx.getStorageSync("subscribeTopic") });
  this.toSubscribe(); //添加订阅地址
}
if (
  wx.getStorageSync("mqttInfo") &&
  wx.getStorageSync("isConnect") &&
  wx.getStorageSync("pushTopic")
) {
  this.setData({ pushTopic: wx.getStorageSync("pushTopic") });
  this.toPush(); //添加发布地址
}

十、数据记录功能(本地缓存实现)

展示图表官网

需要使用 echarts
图表的折线图功能,官网 ,折线图配置链接

对体积有要求的话使用定制图表 (可能导致图表出不来)

在微信小程序使用

echarts-for-weixin项目提供了一个小程序组件,用这种方式可以方便地使用
ECharts。

使用方式
  1. 下载该项目
  2. 如有必要,将ec-canvas目录下的echarts.js替换为最新版的
    ECharts。如果希望减小包体积大小,可以使用自定义构建生成并替换echarts.js
  3. pages目录下是使用的示例文件,可以作为参考,或者删除不需要的页面。

更详细的说明请参见echarts-for-weixin项目。

注意事项

最新版的 ECharts 微信小程序支持微信 Canvas 2d,当用户的基础库版本 >= 2.9.0 且没有设置force-use-old-canvas="true"
的情况下,使用新的 Canvas 2d(默认)。

使用新的 Canvas 2d 可以提升渲染性能,解决非同层渲染问题,强烈建议开启。

更详细的说明请参见Canvas 2d 版本要求。

数据读取和放本地缓存

将数据存在本地缓存和 app.js 里全局变量里,来达到每个页面都能增删改查

// 在app.js下面四个变量,分别为温度,湿度,光强,烟雾
globalData: {
    chart_wd: wx.getStorageSync("chart_wd")
      ? JSON.parse(wx.getStorageSync("chart_wd"))
      : [],
    chart_sd: wx.getStorageSync("chart_sd")
      ? JSON.parse(wx.getStorageSync("chart_sd"))
      : [],
    chart_gq: wx.getStorageSync("chart_gq")
      ? JSON.parse(wx.getStorageSync("chart_gq"))
      : [],
    chart_yw: wx.getStorageSync("chart_yw")
      ? JSON.parse(wx.getStorageSync("chart_yw"))
      : [],
  },

// 全局隐藏时候存入到本地缓存
onHide() {
    wx.setStorageSync("chart_wd", JSON.stringify(this.globalData.chart_wd));
    wx.setStorageSync("chart_sd", JSON.stringify(this.globalData.chart_sd));
    wx.setStorageSync("chart_gq", JSON.stringify(this.globalData.chart_gq));
    wx.setStorageSync("chart_yw", JSON.stringify(this.globalData.chart_yw));
 },

数据存入到全局

// index.js
import { formatTime } from "../../utils/util"; // 引入时间处理方法

// 在接收到的数据里转换成一条为[时间:数据值]的数据并存入到app.globalData
app.globalData[变量名].push([
  formatTime(new Date()).slice(5), // 时间切割 /月/日 时分秒]);

在图表页获取数据和显示出来

// 定义的初始折线图数据
const option = {
    xAxis: {
      type: "category",
      data: ["none"],
    },
    yAxis: {
      type: "value",
    },
    series: [
      {
        data: [0],
        type: "line",
      },
    ],
  };

// 在onload里动态改变标题和读取缓存里的数据
onLoad(options) {
    let data = JSON.parse(options.param);
    wx.setNavigationBarTitle({ title: data.parameter + "记录", });

    let canshu =
      data.parameter === "温度" ? "wd" : data.parameter === "湿度" ? "sd" :
			data.parameter === "光强" ? "gq" : "yw";
    console.log("参数名", "chart_" + canshu);
    console.log(app.globalData["chart_" + canshu]);
    this.setData({ chartData: app.globalData["chart_" + canshu] });
 },

// data里增加变量
chartData : []

// onReady里拿到数据并渲染出来
onReady: function () {
    // 获取组件
    this.ecComponent = this.selectComponent("#mychart-dom-bar");
    this.init();
    setTimeout(() => {
      console.log(this.data.chartData);
      this.data.chartData.length &&
        this.chart.setOption({
          xAxis: {
            type: "category",
            data: this.data.chartData.map((item) => item[0]),
          },
          yAxis: {
            type: "value",
          },
          series: [
            {
              data: this.data.chartData.map((item) => item[1]),
              type: "line",
            },
          ],
        });
    }, 500);
},

十一、小程序配置发布

点击上传(注意 测试号无法上传)

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

完成上传后,需要登录到微信公众平台,在左侧“管理”–“版本管理”中,找到使用微信开发者工具上传的小程序版本,提交审核。提交代码至微信团队审核,审核通过后即可发布成为线上版本(公测期间不能发布)。

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

十二、MySQL 的安装和常用语句的使用

MySQL安装包

需要安装包的小伙伴可以进群自取

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

MySQL 安装步骤

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

!!!这里输入管理员密码 一定要谨记

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

MySQL 常用语句

MySQL 数据库中常用的一些 SQL 语句包括但不限于以下内容:

注意:在 MySQL 操作中所有的语句后面都必须带分号 → ;

  1. 连接 MySQL 服务器

    mysql -u username -p
    

    这将提示输入密码,然后连接到本地 MySQL 服务器。username是你的 MySQL 用户名,一般为 root

    或直接打开 MySQL 命令行工具 输入密码即可连接

    基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

  2. 创建数据库

    CREATE DATABASE 数据库名;
    
  3. 选择(使用)数据库

    USE 数据库名;
    
  4. 显示所有数据库

    SHOW DATABASES;
    
  5. 创建表

    CREATE TABLE 表名 (
        列名1 数据类型 PRIMARY KEY,
        列名2 数据类型,
        ...
    );
    
  6. 插入数据

    INSERT INTO 表名 (1,2, ...) VALUES (1,2, ...);
    
  7. 查询数据

    SELECT * FROM 表名;
    
  8. 更新数据

    UPDATE 表名 SET 列名 = 新值 WHERE 条件;
    
  9. 删除数据

    DELETE FROM 表名 WHERE 条件;
    
  10. 删除表

    DROP TABLE 表名;
    
  11. 查看表结构

    DESC 表名;
    

十三、MySQL 可视化工具 Navicat 的安装和使用

Navicat 介绍

Navicat 是一款由 PremiumSoft CyberTech Ltd 开发的流行且功能强大的数据库管理和开发工具,它提供了一个直观、易于使用的图形用户界面(GUI)来简化数据库管理任务,并支持多种主流关系型数据库系统。通过
Navicat,用户可以在 Windows、macOS 及 Linux 平台上高效地设计数据库结构、执行 SQL 查询和脚本、管理数据表内容、创建图表及报告、进行数据迁移、同步以及备份与恢复操作。

Navicat 支持的主要数据库类型包括:

  • MySQL
  • MariaDB
  • PostgreSQL
  • Oracle
  • SQL Server
  • SQLite
  • Amazon RDS
  • Amazon Aurora
  • Microsoft Azure
  • Google Cloud 等云端数据库服务

其主要功能特点如下:

  1. 多连接管理:允许在同一程序中同时连接到多个数据库服务器。
  2. 数据库设计:使用可视化设计器创建、修改和管理数据库对象,如表、视图、存储过程、触发器等。
  3. 数据传输:在不同数据库之间进行数据迁移或同步,支持自定义字段映射和过滤条件。
  4. 数据备份与恢复:可安排自动备份计划,并能够将备份文件恢复到数据库中。
  5. 查询构建与运行:具有高级 SQL 编辑器,支持语法高亮、代码完成、实时错误检查和调试等功能。
  6. 模型比较与同步:可以比较数据库之间的结构差异,并生成同步脚本来更新目标数据库。
  7. 安全管理:管理用户权限和访问控制,实现对数据库安全性的有效监控。
  8. 批处理作业调度:设定定时任务执行一系列数据库操作,例如运行 SQL 脚本、导出数据等。
  9. 云数据库支持:无缝连接到各大云服务商托管的数据库实例。

总之,Navicat 旨在为数据库管理员、开发人员以及需要高效管理和开发数据库的用户提供一个全面、稳定且高效的解决方案。

Navicat 下载地址

https://navicat.com.cn/download/navicat-premium

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

连接本地数据库

注意主机为 localhost 端口为 3306

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

新建数据库

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

注意字符集一定要根据图中的来 不然不支持中文

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

新建表

在需要建表的数据库双击并找到表,右击新建表

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

输入表字段信息后点击保存,接着输入表名即可

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

十四、NodeJs 增删改查数据库示例

Express 介绍

Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架。通俗的理解:Express 的作用和 Node.js 内置的 http 模块类似,是专门用来创建
Web 服务器的。

创建所需要的库和表

建立库 esp32_records
CREATE DATABASE esp32_records
  DEFAULT CHARACTER SET utf8mb4
  DEFAULT COLLATE utf8mb4_general_ci;
建立表 esp32_data
create table `esp32_data`(
    id int(8) primary key auto_increment comment '主键',
    sensor_name varchar(30) not null comment '传感器名',
    sensor_value int(8) not null comment '值',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP comment '创建时间',
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment '修改时间'
);

安装 Express 脚手架和创建 Express 项目

全局安装Express脚手架

npm install -g express-generator

创建项目

express 项目名

设置淘宝镜像

npm config set registry https://registry.npmmirror.com

安装依赖

npm install

启动项目

npm start

全局安装 nodemon - 作用:监控 node.js 源代码的任何变化和自动重启你的服务器,不用每次改完代码手动重启

npm install -g nodemon

在 package.json 文件中更改"start": "node ./bin/www""start": "nodemon ./bin/www"

安装 mysql 依赖

npm install mysql

代码编辑器

下载编辑器

vscode 官网

腾讯管家 vscode 下载界面 速度快,选择立即下载-直接下载

安装一些必备插件(在 vscode 插件 tab 页下载)

Chinese (Simplified) (简体中文)

ES7+ React/Redux/React-Native snippets

Prettier - Code formatter

Material Icon Theme

操作数据库

MySQL 配置文件
// options/mysqlOption.js

const mysql = require("mysql");
let options = {
  host: "localhost",
  //port:"3306",// 可选,默认3306
  user: "root", // 这里改成你自己的数据库账号
  password: "admin", // 这里改成你自己的数据库密码
  database: "esp32_records" // 这里改成你自己的数据库的名字 不是表名
};

//创建与数据库进行连接的连接对象
const connection = mysql.createConnection(options);
module.exports = connection;
连接 MySQL
connection.connect(err => {
  if (!err) {
    // 数据库连接成功
    console.log("数据库连接成功");
  }
});

测试用的 sql 语句

SELECT * FROM esp32_data

INSERT INTO esp32_data (sensor_name,sensor_value) VALUES ('SMOKE', 20);

UPDATE esp32_data SET sensor_value = 60 WHERE id = 1;

DELETE FROM esp32_data WHERE id = 1

执行 sql 语句方法

注:方便教学,这里采用简单的直接拼接方式,但是可能会有 sql
注入的风险,有兴趣的小伙伴可以去了解一下用**[Knex.js](https://www.knexjs.cn/)**

connection.query("sql语句", (err, result, fields) => {
  if (!err) {
    // 执行成功
    return;
  }
  // 执行失败
});

格式化时间工具函数

/*  ISO 8601 时间格式转换为其他自定义格式 */
function handleData(isoString) {
  const date = new Date(isoString);

  const formattedDate = `${date.getFullYear()}-${(date.getMonth() + 1)
    .toString()
    .padStart(2, "0")}-${date.getDate().toString().padStart(2, "0")}`;
  const formattedTime = `${date.getHours().toString().padStart(2, "0")}:${date
    .getMinutes()
    .toString()
    .padStart(2, "0")}:${date.getSeconds().toString().padStart(2, "0")}`;

  return `${formattedDate} ${formattedTime}`;
}

module.exports = handleData;

十五、NodeJs 实现小程序历史数据接口

两种 HTTP 请求方法:GET 和 POST 介绍

在客户机和服务器之间进行请求-响应时,两种最常被用到的方法是:GET 和 POST。

  • GET- 从指定的资源请求数据。
  • POST- 向指定的资源提交要被处理的数据。

GET 提交参数一般显示在 URL 上,POST 通过表单提交不会显示在 URL 上,POST 更具隐蔽性:

思路和获取接口参数

从前端传参获取需要的数据 -> 再拼接 SQL 语句来执行 → 再返回结果给前端

获取 get 的参数 let { 参数名 , 参数名 } = req.query

获取 post 的参数 let { 参数名 , 参数名 } = req.body;

这里的参数名一定要传过来 不然获取的值将是 undefined

数据记录增删改查路由实现

增加数据记录接口
/* 新增 */
router.post("/api/add_record", function (req, res, next) {
  // 从请求体中获取 sensor_name 和 sensor_value 的值
  let { sensor_name, sensor_value } = req.body;
  // 拼接 sql 语句
  let sqlStr = `INSERT INTO esp32_data (sensor_name,sensor_value) VALUES ('${sensor_name}', ${sensor_value});`;
  // 在控制台打印需要执行的 sql 语句
  console.log(sqlStr);
  // 执行sql语句 通过connection.query方法
  connection.query(sqlStr, (err, result, fields) => {
    if (!err) {
      // 如果没有错误,返回添加成功的信息
      res.json({ code: 200, msg: "添加成功" });
      return;
    }
    // 如果有错误,返回添加失败的信息
    res.json({ code: 404, msg: "添加失败" });
  });
});
删除数据记录接口
/* 删除 */
router.post("/api/del_record", function (req, res, next) {
  let { id } = req.body;
  let sqlStr = `DELETE FROM esp32_data WHERE id = ${id}`;
  console.log(sqlStr);
  connection.query(sqlStr, (err, result, fields) => {
    if (!err) {
      res.json({ code: 200, msg: "删除成功" });
      return;
    }
    res.json({ code: 404, msg: "删除失败" });
  });
});
修改数据记录接口
/* 修改 */
router.post("/api/update_record", function (req, res, next) {
  let { id, sensor_value } = req.body;
  let sqlStr = `UPDATE esp32_data SET sensor_value = ${sensor_value} WHERE id = ${id};`;
  console.log(sqlStr);
  connection.query(sqlStr, (err, result, fields) => {
    if (!err) {
      res.json({ code: 200, msg: "修改成功" });
      return;
    }
    res.json({ code: 404, msg: "修改失败" });
  });
});
查询数据记录接口
/* 查询 */
router.get("/api/get_records", function (req, res, next) {
  let { sensor_name, create_time } = req.query;
  let sqlStr = `SELECT * FROM esp32_data WHERE sensor_name = '${sensor_name}' AND DATE(create_time) = '${create_time}'`;
  console.log(sqlStr);
  // http://localhost:3000/api/get_records?sensor_name=SMOKE&create_time=2024-02-28
  connection.query(sqlStr, (err, result, fields) => {
    if (!err) {
      console.log("查询结果:" + result.length + "条");
      // 对查询结果进行处理
      let resultData = result.map(item => {
        return {
          ...item,
          create_time: handleData(item.create_time), // 调用工具函数处理时间
          update_time: handleData(item.update_time)
        };
      });

      res.json({ code: 200, msg: "查询成功", data: resultData });
      return;
    }
    res.json({ code: 404, msg: "查询失败" });
  });
});

十六、接口调试工具 Apifox 的安装和使用

官方给出的介绍:

Apifox 是接口管理、开发、测试全流程集成工具,定位 Postman + Swagger + Mock +
JMeter。通过一套系统、一份数据,解决多个系统之间的数据同步问题。只要定义好接口文档,接口调试、数据
Mock、接口测试就可以直接使用,无需再次定义;接口文档和接口开发调试使用同一个工具,接口调试完成后即可保证和接口文档定义完全一致。高效、及时、准确!

下载 Apifox

Apifox 官网链接

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

新建项目

我们给项目起名为 esp32

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

点击新增环境 新增名为“本地环境 3000 端口”的环境

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

新建接口

选择get或者post类型,输入接口命名,编辑请求参数,然后点击保存,这样我们就新增了一个接口

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

运行接口测试

点击运行按钮

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

填写参数后点击发送可调用接口进行测试

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

最后

把我们需要用到的四个接口(增删改查)都添加并测试有没有问题,没有问题说明接口已经创建并测试完毕了

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

十七、小程序结合后端实现增删改查历史数据

下载 tabbar 图标并设置 tabbar

下载图标阿里巴巴矢量图标库

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express
基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express
基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express
基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

命名方式和路径如下图所示

基于ESP32+微信小程序的物联网应用(小程序部分),小程序,微信小程序,物联网,stm32,mysql,node.js,express

app.json 的 pages 里加入 "pages/record_crud/record_crud" 页面

在 app.json 里加入

 "tabBar": {
    "borderStyle": "white",
    "selectedColor": "#15A1EA",
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页",
        "iconPath": "images/tabbar/tab1.png",
        "selectedIconPath": "images/tabbar/tab1-c.png"
      },
      {
        "pagePath": "pages/record_crud/record_crud",
        "text": "记录",
        "iconPath": "images/tabbar/tab2.png",
        "selectedIconPath": "images/tabbar/tab2-c.png"
      }
    ]
 },

集中管理接口调用函数

新建api目录并新建index.js文件

// api/index.js
const BASE_URL = "http://localhost:3000/api";
const app = getApp();

// 查询
export function get_records(sensor_name, create_time) {
  return new Promise((resolve, reject) => {
    wx.request({
      url: `${BASE_URL}/get_records`,
      data: {
        sensor_name,
        create_time: `${create_time}`
      },
      success(response) {
        resolve(response);
      },
      fail(e) {
        reject(e);
      }
    });
  });
}

// 删除
export function del_record(id) {
  return new Promise((resolve, reject) => {
    wx.request({
      url: `${BASE_URL}/del_record`,
      method: "POST",
      data: {
        id
      },
      success(response) {
        wx.showToast({ title: "删除成功", icon: "none" });
        resolve();
      },
      fail(e) {
        wx.showToast({ title: "删除失败", icon: "none" });
        reject(e);
      }
    });
  });
}

// 新增
export function add_record(sensor_name, sensor_value) {
  wx.request({
    url: `${BASE_URL}/add_record`,
    method: "POST",
    data: {
      sensor_name,
      sensor_value
    },
    success(response) {},
    fail(e) {}
  });
}

// 修改
export function update_record(id, sensor_value) {
  return new Promise((resolve, reject) => {
    wx.request({
      url: `${BASE_URL}/update_record`,
      method: "POST",
      data: {
        id,
        sensor_value
      },
      success(response) {
        resolve();
      },
      fail(e) {}
    });
  });
}

更改数据记录逻辑

将之前编写的数据记录存入本地缓存的逻辑删除

将首页数据存入本地缓存方式改用存入数据库接口

import { add_record } from "../../api/index";

add_record("TEMPERATURE", Number(getMessageObj.TEMPERATURE));
add_record("HUMIDITY", Number(getMessageObj.HUMIDITY));
add_record("LIGHT_INTENSITY", Number(getMessageObj.LIGHT_INTENSITY));
add_record("SMOKE", Number(getMessageObj.SMOKE));

抽离图表组件供多个页面复用

新建components目录并新建record_chart组件

record_chart.json里的usingComponents字段增加 "ec-canvas": "../../ec-canvas/ec-canvas”

导入 echarts 和封装的调用接口函数

import * as echarts from "../../ec-canvas/echarts.min";
import { get_records } from "../../api/index";

获取全局实例和定于中文字段和传感器的对应关系

const app = getApp();

const sensorMapObj = {
  温度: "TEMPERATURE",
  湿度: "HUMIDITY",
  光强: "LIGHT_INTENSITY",
  烟雾: "SMOKE"
};

定义图表初始值和设置图表值函数

/* 设置chart方法 */
function setOption(chart) {
  let option = {
    grid: { containLabel: true },
    tooltip: { show: true, trigger: "axis" },
    xAxis: { type: "category", data: ["none"] },
    yAxis: { type: "value" },
    series: [{ data: [0], type: "line" }],
    dataZoom: [
      { type: "slider", start: 0, end: 100 },
      { type: "inside", start: 0, end: 100 }
    ]
  };
  chart.setOption(option);
}

定义组件属性,用于在外面传入

  properties: {
    sensorZhName: { type: String, value: "" }, // 传感器中文名字
    searchData: { type: String, value: "" }, //查询日期
    isNeedGetData: { type: Boolean, value: false }, // 是否需要接口获取数据
    chartData: { type: Array, value: () => [] } // 图表数据
    // myProperty2: String // 简化的定义方式
  },

定义组件 data 数据

ec: { lazyLoad: true }, // 将 lazyLoad 设为 true 后,需要手动初始化图表
isLoaded: false,
isDisposed: false,
chartData: []

定义组件生命周期函数,用于处理数据

lifetimes: {
    attached() {
      if (this.properties.isNeedGetData) {
        // console.log("从接口获取数据");
        get_records(
          sensorMapObj[this.properties.sensorZhName],
          this.properties.searchData
        ).then((response) => {
          this.setData({ chartData: response.data.data });
        });
      } else {
        this.setData({ chartData: this.properties.chartData });
      }
    },
    ready() {
      // 获取组件节点
      this.ecComponent = this.selectComponent("#mychart-dom-bar");

      this.init();
      setTimeout(() => {
        console.log("图表数据", this.data.chartData);
        this.data.chartData.length &&
          this.chart?.setOption({
            xAxis: {
              type: "category",
              data: this.data.chartData.map((item) =>
                item.create_time.slice(-5)
              ),
            },
            series: [
              {
                data: this.data.chartData.map((item) => item.sensor_value),
                type: "line",
              },
            ],
          });
      }, 1000);
    },
 },

定义组件方法列表

    // 点击按钮后初始化图表
    init() {
      this.ecComponent.init((canvas, width, height, dpr) => {
        // 获取组件的 canvas、width、height 后的回调函数
        // 在这里初始化图表
        const chart = echarts.init(canvas, null, {
          width: width,
          height: height,
          devicePixelRatio: dpr, // new
        });
        setOption(chart);

        // 将图表实例绑定到 this 上,可以在其他成员函数(如 dispose)中访问
        this.chart = chart;

        this.setData({ isLoaded: true, isDisposed: false });

        // 注意这里一定要返回 chart 实例,否则会影响事件处理等
        return chart;
      });
    },

    dispose() {
      this.chart && this.chart.dispose();

      this.setData({ isDisposed: true });
    },

编写组件的页面结构

<ec-canvas
  wx:if="{{!isDisposed}}"
  id="mychart-dom-bar"
  canvas-id="mychart-bar"
  ec="{{ ec }}"
></ec-canvas>

编写组件的样式

ec-canvas {
  width: 100%;
  height: 90%;
}

至此我们的图表组件就定义完成了!可以在多个页面使用了

制作数据记录页面

拷贝首页的顶部图表和标题过来

抽离主页顶部样式为全局样式

编写头部下拉选择日期和传感器名盒子

编写数据列表展示的样式

引入图表组件并编写切换图表和列表展示按钮来切换

选择日期、传感器名、展示模式样式编写

<view class="select-box">
  <view class="title"
    >历史数据查询
    <van-icon style="margin-left: 40rpx" bind:tap="selectData" name="replay" />
  </view>

  <picker
    mode="date"
    value="{{timeValue}}"
    start="2024-01-01"
    end="2034-01-01"
    bindchange="bindDateChange"
  >
    <view class="picker-box">
      <view> 查询日期: </view>
      <view class="picker-font-color">{{timeValue}}</view>
    </view>
  </picker>
  <picker bindchange="bindPickerChange" value="{{index}}" range="{{sensorNameList}}">
    <view class="picker-box">
      <view> 传感器名:</view>
      <view class="picker-font-color">{{sensorNameList[sensorNameIndex]}} </view>
    </view>
  </picker>
  <picker bindchange="bindrevealTypeChange" value="{{revealTypeIndex}}" range="{{revealList}}">
    <view class="picker-box">
      <view> 展示模式:</view>
      <view class="picker-font-color">{{revealList[revealTypeIndex]}} </view>
    </view>
  </picker>
</view>

在 util.js 写入工具函数来获取当前年月日 YYYY-MM-DD

function getCurrYearMonthDate() {
  const currentDate = new Date();
  const year = currentDate.getFullYear();
  const month = (currentDate.getMonth() + 1).toString().padStart(2, "0");
  const day = currentDate.getDate().toString().padStart(2, "0");
  const formattedDate = `${year}-${month}-${day}`;
  //   console.log(formattedDate); // 输出当前日期,例如 "2024-01-14"
  return formattedDate;
}

// 并导出

在记录页面导入获取时间函数和接口

import {getCurrYearMonthDate} from '../../utils/util’

import { get_records, del_record, update_record } from "../../api/index";

定义 data 的一些参数并赋予默认值

 revealList: ["列表", "图表"], // 数据展示模式
 revealTypeIndex: 0, // 连接方式的类型值 0列表 1图表
 showChart: false, // 控制图表显示隐藏

 timeValue: getCurrYearMonthDate(), //时间选择框的值

 sensorNameList: ["温度", "湿度", "光强", "烟雾"], //传感器下拉数据源
 sensorEnList: ["TEMPERATURE", "HUMIDITY", "LIGHT_INTENSITY", "SMOKE"], //传感器下拉数据源英文
 sensorNameIndex: "0", //传感器下拉值的index
 sensorNameEnValue: "TEMPERATURE",

 sensorMap: {
 TEMPERATURE: "°C",
 HUMIDITY: "%rh",
 LIGHT_INTENSITY: "lx",
 SMOKE: "ppm",
 }, //传感器单位

 sersorInfoList: [], // 数据列表

在页面首次进入函数里判断如果有值就查询数据

  onLoad() {
    if (this.data.timeValue && this.data.sensorNameEnValue) this.selectData();
  },

定义选择器改变事件

// 日期改变事件
  bindDateChange(e) {
    this.setData({ timeValue: e.detail.value });
    this.selectData();
  },
  // 传感器改变事件
  bindPickerChange(e) {
    this.setData({
      sensorNameIndex: e.detail.value,
      sensorNameEnValue: this.data.sensorEnList[Number(e.detail.value)],
    });
    this.selectData();
  },
  // 切换展示模式
  bindrevealTypeChange(e) {
    this.setData({ revealTypeIndex: Number(e.detail.value) });

    if (e.detail.value === '1') {
      this.setData({ showChart: true });
    }
  },

在页面的 json 文件里引入组件

"record_chart":"../../components/record_chart/record_chart”

在 app.json 引入

"van-cell": "@vant/weapp/cell/index", "van-cell-group": "@vant/weapp/cell-group/index"

编写页面增删改查方法

  // 获取数据记录
  selectData() {
    if (this.data.revealTypeIndex === 1) this.setData({ showChart: false });

    get_records(this.data.sensorNameEnValue, `${this.data.timeValue}`).then(
      (response) => {
        this.setData({ sersorInfoList: response.data.data });
        if (this.data.revealTypeIndex === 1) this.setData({ showChart: true });
      }
    );
  },
  // 删除记录
  delRecord(e) {
    let that = this;
    wx.showModal({
      title: "系统提示",
      content: "是否确认删除本条消息",
      complete: (res) => {
        if (res.confirm) {
          del_record(e.currentTarget.dataset.id).then(() => {
            that.selectData();
          });
        }
      },
    });
  },
  // 修改记录
  updateRecord(e) {
    wx.showModal({
      title: "修改记录",
      editable: true,
      placeholderText: "请输入需要修改的值",
      confirmText: "确认修改",
      complete: (res) => {
        if (res.confirm) {
          if (res.content && !isNaN(Number(res.content))) {
            // console.log(Number(res.content));

            update_record(e.currentTarget.dataset.id, Number(res.content)).then(
              () => {
                wx.showToast({ title: "修改成功", icon: "none" });
                this.selectData();
              }
            );
          } else {
            wx.showToast({ title: "输入有误", icon: "none" });
          }
        }
      },
    });
  },

编写页面展示列表和图表

<!-- 列表模式 -->
<view wx:if="{{revealTypeIndex===0}}" class="list-mode">
  <scroll-view scroll-y="true" style="height: calc(100vh - 620rpx)">
    <van-cell-group inset border="{{false}}">
      <van-cell
        wx:for="{{sersorInfoList}}"
        title="{{item.sensor_value + sensorMap[sensorNameEnValue]}}"
        wx:key="item"
        data-id="{{item.id}}"
        data-value="{{item.sersor_value}}"
        bind:longpress="delRecord"
        bind:tap="updateRecord"
        value="{{item.create_time}}"
      />
    </van-cell-group>
  </scroll-view>
</view>
<!-- 图表模式 -->
<view
  wx:if="{{revealTypeIndex === 1 && showChart}}"
  style="height: calc(100vh - 530rpx); margin: 0 2rpx 4rpx"
>
  <record_chart chart-data="{{sersorInfoList}}" />
</view>

最后附上页面样式文件

.select-box {
  margin: 38rpx 32rpx 0;
  box-shadow: 0 8rpx 20rpx 0 rgba(0, 0, 0, 0.3);
  border-radius: 32rpx;
  padding: 20rpx;
  background-color: #ffffff;
}

.title {
  text-align: center;
  font-size: 44rpx;
  margin-bottom: 20rpx;
}

.picker-box {
  display: flex;
  margin-bottom: 10rpx;
}

.list-mode {
  background-color: #ffffff;
  margin: 38rpx 32rpx 0;
  box-shadow: 0 8rpx 20rpx 0 rgba(0, 0, 0, 0.3);
  border-radius: 32rpx;
}

.picker-font-color {
  color: #969799;
}

改写之前的图表页面逻辑

将 chart 页面删除,新建 record 页面,改成调用我们写的图表组件

// record.js
import { getCurrYearMonthDate } from "../../utils/util";

const app = getApp();

Page({
  data: {
    showChart: false, // 控制图表显示隐藏
    sensorZhName: "", // 传感器英文名
    timeValue: getCurrYearMonthDate() //时间选择框的值
  },
  onLoad({ data }) {
    // console.log(JSON.parse(data));
    let { name, parameter, unit } = JSON.parse(data) || {};
    wx.setNavigationBarTitle({ title: `${parameter}记录(单位${unit}` });
    this.setData({ sensorZhName: parameter, showChart: true });
  },
  bindDateChange(e) {
    this.setData({ showChart: false, timeValue: e.detail.value });
    setTimeout(() => {
      this.setData({ showChart: true });
    }, 500);
  }
});
<!-- record.wxml -->
<view class="page-container">
  <picker
    mode="date"
    value="{{timeValue}}"
    start="2024-01-01"
    end="2034-01-01"
    bindchange="bindDateChange"
  >
    <view class="picker-box">
      <view> 查询日期: </view>
      <view class="picker-font-color">{{timeValue}}</view>
    </view>
  </picker>
  <record_chart
    is-need-get-data
    sensor-zh-name="{{sensorZhName}}"
    search-data="{{timeValue}}"
    wx:if="{{showChart}}"
  />
</view>
/* record.wxss */
.page-container {
  width: 100vw;
  height: 92vh;
}
.picker-box {
  display: flex;
  justify-content: space-between;
  margin: 10rpx 72rpx;
}
{
  "usingComponents": {
    "record_chart": "../../components/record_chart/record_chart"
  },
  "navigationBarTitleText": "记录",
  "navigationStyle": "default"
}

更改跳转 record 页面逻辑

  // wxml增加
  data-param="{{item}}"


  let data = e.currentTarget.dataset.item;
  wx.navigateTo({
      url: "/pages/record/record?data=" + JSON.stringify(data)
  });

至此本篇教程已经到此完毕!如果大家感兴趣的话后续将会出更多教程文章来源地址https://www.toymoban.com/news/detail-854452.html

到了这里,关于基于ESP32+微信小程序的物联网应用(小程序部分)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于ESP32+Platformio的物联网RTOS_SDK-CC_Device

    本项目基于ESP32以及Platformio平台开发,请自行查阅如何配置这个环境 开源gitee地址:cc_smart_device 如果愿意贡献项目or提出疑问和修改的,请在gitee上提issue 项目里的mqtt服务器是公共的 请大家最好换成私有的 否则容易收到其他用户的错误数据 该项目主要用于更加便捷快速的开

    2024年02月22日
    浏览(43)
  • 基于STM32F103C8T6与ESP8266的物联网智能温度采集与蓝牙OLED数字钟的设计与实现

    作者: 颜孙炜 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wC12xZrc-1673843611066)(C:UsersadminAppDataRoamingTyporatypora-user-imagesimage-20230110223524043.png)] 用STM32F103C8T6自有的RTC功能实现一款数字钟的设计,包括温度输入检测和显示模块、数字钟显示模块

    2024年02月02日
    浏览(60)
  • 微信小程序可自定义单片机温湿度阈值(基于esp32c3+onenet+微信小程序)

    ​ 前段时间有个粉丝问我能不能出一个微信小程序调节阈值的教程,我就下班之余在原来的基础上改进一下,因为是修改阈值,这里我就用继电器控制风扇、温度达到一定阈值控制风扇启动来做例子。这个成功了,其他阈值修改都是依样画葫芦啦。 ​ 之前博客 :基于物联网

    2024年02月07日
    浏览(45)
  • 详细!基于ESP32的智能门禁系统(华为云iot+微信小程序)

    git地址:智能门禁(云IOT+微信小程序) 开关门效果 创建产品 创建产品 , 协议类型选择MQTT,数据格式选择JSON ,其他参数自定 设备注册 找到所属产品,认证类型选择密钥,单击确定后注册成功 注册成功后出现如下页面,点击保存并关闭,会自动下载好\\\"device_id\\\"和\\\"secret\\\",保

    2023年04月26日
    浏览(45)
  • 基于STM32设计的智慧农业管理系统(ESP8266+腾讯云微信小程序)

    基于STM32设计的智慧农业控制系统(ESP8266+腾讯云微信小程序) 随着人们对食品安全和生态环境的日益重视,智慧农业逐渐成为一个备受关注的领域。智能化管理可以提高农业生产效率,减少资源浪费,改善生态环境。因此,基于物联网技术的智慧农业管理系统越来越受到农民和

    2024年02月08日
    浏览(47)
  • 基于STM32的物联网环境监测系统

    基于机智云物联网的环境监测系统 视频演示 摘 要:随着人民对美好生活的向往,人们对于环境的重视程度越来越强烈,环境对生活的影响已经成为一个热点问题。本设计以STM32单片机作为控制和数据处理的单元,使用AHT10、BH1750和BMP280传感器去监测周围的环境参数,在LCD屏完

    2024年02月07日
    浏览(63)
  • 基于STM32+OneNet设计的物联网智慧路灯

    近年来,构筑智慧城市、推动城镇发展被国家列入重要工作范畴。发布的《超级智慧城市报告》显示,全球已启动或在建的智慧城市有1000多个,中国在建500个,远超排名第二的欧洲(90个)。从在建智慧城市的分布来看,我国已初步形成环渤海、长三角、珠三角、中西部四大

    2024年02月10日
    浏览(51)
  • 基于STM32的物联网智能温湿度检测系统

    Windows10+ 系统电脑一台 可发射WiFi信号设备一台(手机即可) STM32F103核心板 STM32仿真器 ESP8266-01模块(需要刷OneNet固件) Wifi模块刷入OneNet固件 - 每日书库 ESP8266-01 WIFI模块刷入OneNet固件,使用MQTT连接方式接入OneNet,以及AT固件的介绍 https://www.tao-space.top/2023/04/24/Wifi%E6%A8%A1%E5%9

    2024年02月19日
    浏览(46)
  • 基于云计算的物联网应用案例:智能农业解决方案

    作者:禅与计算机程序设计艺术 引言 随着物联网和云计算技术的快速发展,各种智能传感器和设备的应用范围越来越广。智能农业作为物联网应用领域的重要分支,通过各种传感器和设备的协同工作,实现对农业生产全过程的实时监控和控制,从而提高农业生产效率。本文

    2024年02月16日
    浏览(49)
  • 基于STM32+OneNet设计的物联网智能鱼缸(2023升级版)

    基于STM32+OneNet设计的智能鱼缸(升级版) 随着物联网技术的快速发展,智能家居和智能养殖领域的应用越来越广泛。智能鱼缸作为智能家居和智能养殖的结合体,受到了越来越多消费者的关注。本项目设计一款基于STM32的物联网智能鱼缸,通过集成多种传感器和智能化控制模块

    2024年02月07日
    浏览(69)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包