物联网技术(AIR780E cat1-4G模块+小程序+腾讯云MQTT)驱动的智能井盖系统设计(连载05)

目录

一、项目意义和项目简介

二、硬件设计

1.原理图设计

2.PCB设计

3、主要器件

3.1三轴加速度传感器

3.2可燃气体传感器

3.3GPS模块

4、MQTT通信协议设计

三、微信小程序设计

3.1、连接EMQX服务器步骤

3.2、MQTT发布订阅测试

3.3程序界面设计

1)APP主页面:

2)地图界面设计

3)设备信息界面设计

4)设备日志页面设计

3.3程序界面设计

总体流程图如下图:

1)APP主页面:

APP主页面流程图

界面布局设计:

index.wxss

.background{
    background-color: #dddddd;
    width: 100%;
    height: 100vh;
  }
  .swiper{
    height: 300rpx;
  }
  .img{
    width: 100%;
    height: 300rpx;
  }
  .BoxsBar{
    display: flex;
    flex-wrap: wrap;
  }
  .Boxs{
    width: 50%;
    height: 260rpx;
    display: flex;
    margin-top: 20px;
    justify-content: center;
    text-align: center;
  }
  .Box{
    display: flex;
    justify-content: space-around;
    width: 350rpx;
    height: 260rpx;
    border-radius: 20rpx;
    background-color: #FFFFFF;
  }
  .image{
    margin-top: 10rpx;
    height: 220rpx; 
    width: 180rpx;
  }

  .List{
    margin-top: 50rpx;
  }
  .list{
    margin-top: 30rpx;
  }
  .text{
    width: 54%;
  }
  .switch{
    zoom:0.8;
    width: 46%;
    text-align:right;
  }

index.wxml

<view>
    <view class="background">
        <van-cell-group>
            <van-cell bindtap="scanQRCode" style="font-weight:bold" title="井盖编号" value="{{scanResult}}" size="large">
                <van-icon name="scan" bindtap="onScan" size="20px" />
            </van-cell>
        </van-cell-group>
        <button bindtap="subscribeButtonTap">授权订阅</button>
        <view class="BoxsBar" style="width: 677rpx; height: 102rpx; display: flex; box-sizing: content-box">
            <!-- cstx节点1 -->
            <view class="Boxs" style="width: 706rpx; height: 100rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx">
                <view class="Box" style="width: 800rpx; height: 102rpx; display: flex; box-sizing: border-box; position: relative; left: 24rpx; top: -6rpx">
                    <image class="image" src="/images/dianchi.png" style="width: 54rpx; height: 62rpx; display: block; box-sizing: border-box; position: relative; left: -89rpx; top: 6rpx"></image>
                    <view class="List" style="width: 220rpx; height: 62rpx; display: block; box-sizing: border-box">
                        <view style="font-weight: bold; position: relative; left: -318rpx; top: -18rpx">电池电压</view>
                        <view class="list">
                            <view style="position: relative; left: 3rpx; top: -92rpx">{{volt}}V</view>
                        </view>
                    </view>
                </view>
            </view>
            <view class="Boxs" style="width: 706rpx; height: 100rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx">
                <view class="Box" style="width: 800rpx; height: 102rpx; display: flex; box-sizing: border-box; position: relative; left: 24rpx; top: -33rpx">
                    <image class="image" src="/images/gongdian.png" style="width: 54rpx; height: 62rpx; display: block; box-sizing: border-box; position: relative; left: -83rpx; top: 6rpx">
                    </image>
                    <view class="List">
                        <view style="font-weight: bold; position: relative; left: -318rpx; top: -18rpx">板载电压</view>
                        <view class="list">
                            <view style="position: relative; left: 3rpx; top: -99rpx">{{vbat}}</view>
                        </view>
                    </view>
                </view>
            </view>
            <!-- cstx节点1 -->
            <view class="Boxs" style="width: 706rpx; height: 100rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx">
                <view class="Box" style="width: 800rpx; height: 102rpx; display: flex; box-sizing: border-box; position: relative; left: 25rpx; top: -31rpx">
                    <image class="image" src="/images/qingxie.png" style="width: 54rpx; height: 62rpx; display: block; box-sizing: border-box; position: relative; left: -83rpx; top: 6rpx"></image>
                    <view class="List1">
                        <view style="font-weight: bold; position: relative; left: -276rpx; top: 0rpx">X与Y轴的倾斜度</view>
                        <view class="list">
                            <view style="display: flex; flex-direction: column;">
                                <view style="position: relative; left: 15rpx; top: -78rpx">{{accelDisplay}}</view>
                            </view>
                        </view>
                    </view>
                </view>
            </view>
            <view class="Boxs" style="width: 706rpx; height: 100rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx">
                <view class="Box" style="width: 800rpx; height: 102rpx; display: flex; box-sizing: border-box; position: relative; left: 24rpx; top: -54rpx">
                    <image class="image" src="/images/qingxie.png" style="width: 54rpx; height: 62rpx; display: block; box-sizing: border-box; position: relative; left: -83rpx; top: 6rpx"></image>
                    <view class="List1">
                        <view style="font-weight: bold; position: relative; left: -276rpx; top: 0rpx">X与Z轴的倾斜度</view>
                        <view class="list">
                            <view style="display: flex; flex-direction: column;">
                                <view style="position: relative; left: 15rpx; top: -73rpx">{{accelDisplay1}}</view>
                            </view>
                        </view>
                    </view>
                </view>
            </view>
            <view class="Boxs" style="width: 706rpx; height: 100rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx">
                <view class="Box" style="width: 800rpx; height: 102rpx; display: flex; box-sizing: border-box; position: relative; left: 24rpx; top: -38rpx">
                    <image class="image" src="/images/dingwei.png" style="width: 54rpx; height: 62rpx; display: block; box-sizing: border-box; position: relative; left: -83rpx; top: 6rpx"></image>
                    <view class="List">
                        <view style="font-weight: bold; position: relative; left: -318rpx; top: -18rpx">纬度 N°</view>
                        <view class="list">
                            <view style="position: relative; left: -12rpx; top: -101rpx">{{latitude}}°</view>
                        </view>
                    </view>
                </view>
            </view>
            <view class="Boxs" style="width: 706rpx; height: 100rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx">
                <view class="Box" style="width: 800rpx; height: 102rpx; display: flex; box-sizing: border-box; position: relative; left: 26rpx; top: -52rpx">
                    <image class="image" src="/images/dingwei.png" style="width: 54rpx; height: 62rpx; display: block; box-sizing: border-box; position: relative; left: -83rpx; top: 6rpx"></image>
                    <view class="List">
                        <view style="font-weight: bold; position: relative; left: -318rpx; top: -18rpx">经度 E°</view>
                        <view class="list">
                            <view style="position: relative; left: -22rpx; top: -87rpx">{{longitude}}°</view>
                        </view>
                    </view>
                </view>
            </view>
            <view class="Boxs" style="width: 706rpx; height: 100rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx">
                <view class="Box" style="width: 800rpx; height: 102rpx; display: flex; box-sizing: border-box; position: relative; left: 26rpx; top: -61rpx">
                    <image class="image" src="/images/qiti.png" style="width: 54rpx; height: 62rpx; display: block; box-sizing: border-box; position: relative; left: -83rpx; top: 6rpx"></image>
                    <view class="List">
                        <view style="font-weight: bold; position: relative; left: -318rpx; top: -18rpx">可燃气体</view>
                        <view class="list">
                            <view style="position: relative; left: -22rpx; top: -87rpx">{{gas}}</view>
                        </view>
                    </view>
                </view>
            </view>
            <view class="Boxs" style="width: 706rpx; height: 100rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx">
                <view class="Box" style="width: 706rpx; height: 104rpx; display: flex; box-sizing: border-box; position: relative; left: 24rpx; top: -75rpx">
                    <image class="image" src="/images/temp.png" style="width: 54rpx; height: 62rpx; display: block; box-sizing: border-box; position: relative; left: -83rpx; top: 6rpx"></image>
                    <view class="List">
                        <view style="font-weight: bold; position: relative; left: -318rpx; top: -18rpx">温度</view>
                        <view class="list">
                            <view style="position: relative; left: -22rpx; top: -87rpx">{{temp}}°C</view>
                        </view>
                    </view>
                </view>
            </view>
        </view>
    </view>
</view>
<page-meta>
    <navigation-bar title="{{nbTitle}}" loading="{{nbLoading}}" front-color="{{nbFrontColor}}" background-color="{{nbBackgroundColor}}" color-animation-duration="0" color-animation-timing-func="easeIn" />
</page-meta>

数据显示设计:

这里由于我的板子发送上来的数据有乱码,所以用了另外的方法解决,我先提取{}里面的数据,再把里面的数据进行JSON格式化,然后用setData进行将数据展示在页面上

//接收消息监听
        client.on('message', (topic, message) => {
            // 数据加载完成,隐藏Toast  
  wx.hideToast();
            let msg = message.toString();
            const data = JSON.parse(msg);
            const timestamp = new Date().toISOString().substr(0, 19).replace('T', ' ');
            // 假设lon和lat是浮点数,我们将它们乘以100并保留两位小数  
            const lonFormatted = (data.lon / 100).toFixed(6);
            const latFormatted = (data.lat / 100).toFixed(6);
            console.log('纬度:', latFormatted, '经度', lonFormatted, '角度:', accelDisplay, '气体:', data.gas, "电池电压:", data.volt, "4G模块电池电压", data.vbat, "板子温度:", data.temp, '角度:', accelDisplay1);
            let data1 = data.volt;
            let data2 = data.temp;
            let roundedData2 = Math.round(data2 * 1e3) / 1e3;
            let roundedData1 = Math.round(data1 * 1e3) / 1e3;
            // 假设 data.accel 是您的定位数据  
            let accelDisplay = "";
            let accelDisplay1 = '';
            // 遍历并处理加速度数据  
            let accel = data.accel;
            let pitch = 0,
                roll = 0; // 假设roll是绕y轴的旋转(横滚角),pitch是绕x轴的旋转(俯仰角)  
            // 注意:在实际应用中,你可能需要使用Math.atan2而不是Math.atan,以获取正确的象限信息  
            // 这里为了简化示例,我们仅使用Math.atan并假设设备静止且没有旋转到负值区域  
            if (accel.x !== 0 && accel.y !== 0 && accel.z !== 0) {
                // 俯仰角pitch(绕x轴旋转,与y轴和z轴构成的平面之间的角度)  
                pitch = Math.atan(accel.y / Math.sqrt(Math.pow(accel.x, 2) + Math.pow(accel.z, 2))) * (180 / Math.PI);
                roll = Math.atan(accel.x / Math.sqrt(Math.pow(accel.y, 2) + Math.pow(accel.z, 2))) * (180 / Math.PI);
                // 根据你的需求调整角度的正负号和范围(0-360或-180到180)  
                // 这里我们假设pitch和roll的范围是-90到90度  
                pitch = pitch > 180 ? 180 : pitch < -180 ? -180 : pitch;
                roll = roll > 180 ? 180 : roll < -180 ? -180 : roll;
            }
            // 将加速度数据添加到accelDisplay中  
            accelDisplay += `${pitch.toFixed(2)}°`;
            accelDisplay1 += `${roll.toFixed(2)}°`
            // 设置数据
            this.setData({
                isLoading:false,
                longitude: lonFormatted,
                latitude: latFormatted,
                gas: data.gas,
                accelDisplay: accelDisplay,
                accelDisplay1: accelDisplay1,
                volt: roundedData1,
                vbat: data.vbat,
                temp: roundedData2
            });
            appInstance.globalData.sharedLocation = {
                latitude: latFormatted,
                longitude: lonFormatted
            };
            const updatedData = {
                ...data,
                timestamp: timestamp
            };
            app.emitGlobalEvent('mqttDataUpdatedWithTime', updatedData);
            if (data.gas > 2000 && !app.globalData.hasShownGasAlarm) {
                wx.showModal({
                    title: '报警',
                    content: '气体浓度超过阈值!',
                    showCancel: true, // 不显示取消按钮  
                    success(res) {
                        if (res.confirm) {
                            // 用户点击确定后的回调,这里可以什么都不做或者执行其他逻辑  
                        }
                        // 更新全局标志位  
                        app.globalData.hasShownGasAlarm = true;
                    }
                });
            }
            if (pitch.toFixed(2) > 10 && !app.globalData.hasShownGasAlarm1) {
                wx.showModal({
                    title: '报警',
                    content: '有抖动现象!',
                    showCancel: true, // 不显示取消按钮  
                    success(res) {
                        if (res.confirm) {
                            // 用户点击确定后的回调,这里可以什么都不做或者执行其他逻辑  
                        }
                        // 更新全局标志位  
                        app.globalData.hasShownGasAlarm1 = true;
                    }
                });
            }
            if (pitch.toFixed(2) < -10 && !app.globalData.hasShownGasAlarm1) {
                wx.showModal({
                    title: '报警',
                    content: '有抖动现象!',
                    showCancel: true, // 不显示取消按钮  
                    success(res) {
                        if (res.confirm) {
                            // 用户点击确定后的回调,这里可以什么都不做或者执行其他逻辑  
                        }
                        // 更新全局标志位  
                        app.globalData.hasShownGasAlarm1 = true;
                    }
                });
            }
            if (roll.toFixed(2) > 10 && !app.globalData.hasShownGasAlarm2) {
                wx.showModal({
                    title: '报警',
                    content: '有抖动现象!',
                    showCancel: true, // 不显示取消按钮  
                    success(res) {
                        if (res.confirm) {
                            // 用户点击确定后的回调,这里可以什么都不做或者执行其他逻辑  
                        }
                        // 更新全局标志位  
                        app.globalData.hasShownGasAlarm2 = true;
                    }
                });
            }
            if (roll.toFixed(2) < -10 && !app.globalData.hasShownGasAlarm1) {
                wx.showModal({
                    title: '报警',
                    content: '有抖动现象!',
                    showCancel: true, // 不显示取消按钮  
                    success(res) {
                        if (res.confirm) {
                            // 用户点击确定后的回调,这里可以什么都不做或者执行其他逻辑  
                        }
                        // 更新全局标志位  
                        app.globalData.hasShownGasAlarm1 = true;
                    }
                });
            }
            if (roundedData1 === 0) {
                // 调用云函数发送订阅消息(这里需要确保你有调用云函数的权限)  
                wx.cloud.callFunction({
                    name: 'sendSubscriptionMessage', // 云函数名称  
                    data: {
                        volt: roundedData1 // 传递需要的数据给云函数  
                    },
                    success: res => {
                        console.log('云函数调用成功', res)
                    },
                    fail: err => {
                        console.error('云函数调用失败', err)
                    }
                })
            }
        })
    },
    // 按钮点击事件处理函数  
    subscribeButtonTap: function () {
        wx.requestSubscribeMessage({
            tmplIds: ['BChyKXOjPqeDNQnq8POD3DMFxAxmpam38Hk3FjawP98'], // 替换为你的模板ID  
            success(res) {
                // 用户同意订阅  
                console.log('用户同意订阅', res);
                // 这里可以保存用户的订阅状态到数据库等  
                // 或者在符合发送条件时直接调用云函数发送订阅消息  
            },
            fail(err) {
                // 用户拒绝订阅  
                console.log('用户拒绝订阅', err);
            }
        });
    },

假设你们收到的数据中存在[][]{传感器的数据},以下代码是我的解决方法,首先我在数据中看到的是[][]{传感器的数据},然后进行找到并提取一个括号里面的内容作为对象。

client.on('message', (topic, message) => {    
let msg = message.toString();
     var str = msg;
     var regex = /\{([^}]*)\}/;  
     var match = str.match(regex);
     if (match) {  
      // match[0] 是整个匹配到的字符串,包括{}  
      // match[1] 是第一个括号内的内容,即{}里面的内容  
      var contentInsideBrackets = match[1];       
      // 将提取的内容转换为对象,方便访问  
      try {  
         var timestamp = new Date().toISOString();  
          var data = JSON.parse('{' + contentInsideBrackets + '}');
          // 假设提取的内容是键值对形式的字符串,并且可以直接转换为对象  
          // 这里我们定义变量来存储温度和湿度数据  
          var temperture= data.temperture;  
          var humidity = data.humidity;
          console.log("Temperature:",temperture);  
          console.log("Humidity:", humidity); 
          console.log("timestamp",timestamp);
      } catch (e) {  
            // 如果转换失败,可能是因为内容格式不正确  
            console.error("Failed to parse the content inside brackets:", e);  
        } 
          this.setData({
            temperture:temperture,
            humidity:humidity,
            timestamp:timestamp
          }); 
  };

通信功能程序:

我们用的是MQTT通信方式进行连接EMQX服务器

const options = {
      keepalive: 60, //60s
      clean: true, //cleanSession不保持持久会话
      protocolVersion: 4 ,//MQTT v3.1.1
      clientId:Math.random().toString(36).substr(2)
    };
    console.log(options)
    let url = "wx://www.visionexpand.com.cn:8083/mqtt";//这个地址是emq官方的公开免费地址,请换成自己服务器的地址
    console.log(url);
    const client = mqtt.connect(url,options)
    client.on('connect', function () {
      console.log('连接emqx服务器成功')
      client.subscribe('$thing/up/property/IQMPOB8BI9/temp/humi',{qos:2},function(err){
        if(!err)
            {console.log('订阅成功')}
      })
    })

二维码扫描识别功能:

用户使用手机点击扫码,进行实现扫描二维码功能

data:{
scanResult: ''
},
scanQRCode: function() {  
    const that = this;  
    wx.scanCode({  
      success: function(res) {  
        console.log('扫描结果', res.result);  
        that.setData({  
          scanResult: res.result  
        });  
      },  
      fail: function(err) {  
        console.error('扫描失败', err);  
        wx.showToast({  
          title: '扫描失败',  
          icon: 'none'  
        });  
      }  
    });  
  }

2)地图界面设计

流程图:

数据显示设计:

先在app.js定义一个全局变量,然后再index.js用这个函数取出经纬度变量的值,然后再map.js初始化这个函数,调用函数,setData实时更新显示数据,定位就出来了!

map.js

getFuzzyLocation: function (message) {
   const sharedLocation = appInstance.globalData.sharedLocation;    
      const marker = {  
        id: 1, // 标记的id,可以根据需要设置  
        latitude: sharedLocation.latitude, // 纬度  
        longitude: sharedLocation.longitude, // 经度
        name: '井盖保镖',
        desc: '我现在的位置'
      };  
      this.setData({  
        markers: [marker], // 设置markers数组,只包含一个标记  
        latitude: sharedLocation.latitude, // 可选:更新页面数据中的纬度值  
        longitude: sharedLocation.longitude, // 可选:更新页面数据中的经度值  
      });
  },

地图插件:

浏览器搜搜微信小程序公众平台,点击微信公众平台

接下来的内容还有很多,就不一一列出了。

对本项目感兴趣的同学、朋友,或者需要定制、学术论文使用的都可以扫码联系:

  • 28
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值