Tech 技术 · 2022年12月31日 0

Tech 技术:基于stm32物联网智能生态箱的搭建

这是在大三冬季学期综合工程设计的一个项目,因为这个项目接触到了物联网,从而获得了之后的实习offer和物联网方向的秋招offer。物联网是通信行业的大的风口,我觉得可以学习一下。

项目介绍:使用STM32F407开发板作为主控面板,使用搭载MQTT协议的ESP8266 01S芯片作为物联网模块,使用微信小程序作为远程控制端,可以实时查看温度、水浑浊情况等信息,具有定时换水、供氧、喂食、灯照等功能,同时可在微信小程序上实时查看生态箱状态并远程控制。

项目原理:

硬件实现总体思路
远程控制端整体思路

项目分工:

  1. 使用AD设计搭载stm32的PCB板
  2. 使用Keli 5编写stm32固件
  3. 使用Arduino搭建ESP8266环境
  4. 使用微信开发者工具编写配套微信小程序
  5. 使用Solidworks设计主控板外壳并3D打印

项目展示:

项目实现(因为我负责项目分工的3、4,所以只可以共享该部分代码):

  • 微信小程序index页前端源码:
<template>
<div class="wrapper">
  <div class="header-wrapper">
    <div class="header-title">
      <span>当前气温</span>
      <span>{{location}}</span>
    </div>
    <div class="header-text">
      <span>{{wea_temp}}℃</span>
      <span>{{weather}}</span>
    </div>
    <div class="weather-highlow">当日最高气温为{{high_temp}}℃,最低温为{{low_temp}}℃,{{wind_dire}}风{{wind_scale}}级</div>
  </div>
  <div class="body-wrapper">
    <div class="body">
      <div class="data-wrapper">
        <div class="advice">
          <img class="advice-logo" src="/static/images/advice.png">
          <transition name="slide">
            <div class="advice-text" :key="advdata.id">{{advdata.val}}</div>
          </transition> 
        </div>
      </div>
      <div class="data-wrapper">
        <div class="data">
          <div v-if="bad[0]">
            <img class="data-logo" src="/static/images/badtemperature.png">
          </div>
          <div v-else>
            <img class="data-logo" src="/static/images/temperature.png">
          </div>
          <div class="data-text">
            <div class="data-title">水温</div>
            <div class="data-value">{{temp}}℃</div>
          </div>
        </div>
         <div class="data">
          <div v-if="bad[1]">
            <img class="data-logo" src="/static/images/badwater-depth.png">
          </div>
          <div v-else>
            <img class="data-logo" src="/static/images/water-depth.png">
          </div>
          <div class="data-text">
            <div class="data-title">水量</div>
            <div class="data-value">{{depth}}</div>
          </div>
        </div>
      </div>
      <div class="data-wrapper">
        <div class="data">
          <div v-if="bad[2]">
            <img class="data-logo" src="/static/images/badturbidity.png">
          </div>
          <div v-else>
            <img class="data-logo" src="/static/images/turbidity.png">
          </div>
          <div class="data-text">
            <div class="data-title">水质</div>
            <div class="data-value">{{turb}}</div>
          </div>
        </div>
         <div class="data">
          <img class="data-logo" src="/static/images/heating.png">
          <div class="data-text">
            <div class="data-title">恒温</div>
            <div class="data-value">
               <switch @change="onHeatingChange" :checked="heating" color="#3d7ef9" />
            </div>
          </div>
        </div>
      </div>
      <div class="data-wrapper">
        <div class="data">
          <img class="data-logo" src="/static/images/light.png">
          <div class="data-text">
            <div class="data-title">灯光</div>
            <div class="data-value">
              <switch @change="onLightChange" :checked="light" color="#3d7ef9" />
            </div>
          </div>
        </div>
         <div class="data">
          <img class="data-logo" src="/static/images/food.png">
          <div class="data-text">
            <div class="data-title">喂食</div>
            <div class="data-value">
               <switch @change="onFoodChange" :checked="food" :disabled="runningfood" color="#3d7ef9" />
            </div>
          </div>
        </div>
      </div>
      <div class="data-wrapper">
        <div class="data">
          <img class="data-logo" src="/static/images/oxygen.png">
          <div class="data-text">
            <div class="data-title">供氧</div>
            <div class="data-value">
              <switch @change="onOxygenChange" :checked="oxygen" :disabled="runningoxygen" color="#3d7ef9" />
            </div>
          </div>  
        </div>
        <div v-if="badturb">
         <div class="data">
          <img class="data-logo" src="/static/images/rewater.png">
          <div class="data-text">
            <div class="data-title">换水</div>
            <div class="data-value">
              <switch @change="onRewaterChange" :checked="rewater" :disabled="runningrewater" color="#3d7ef9" />
            </div>
          </div>
        </div>
        </div>
      </div>
    </div>
  </div>
</div>
</template>

<script>
import { connect } from "mqtt/dist/mqtt.js";

const mqtturl = 'wxs://yourmqtturl'

export default {
  data () {
    return {
      wea_temp: '-',
      location: "获取中",
      weather: "--",
      high_temp: '-',
      low_temp: '-',
      wind_dire: "--",
      wind_scale: '-',
      //——————————————————————————————————————//
      client:{
      },
      temp: '--',
      depth: '--',
      turb: '--',
      advice: [
        '未获取到水温数据,请检查网络连接',
        '未获取到水量数据,请检查网络连接',
        '未获取到水质数据,请检查网络连接'],
      advnum: 0,
      bad:[false,false,false],//水温、水量、水质
      heating: false,
      light: false,
      food: false,
      oxygen: false,
      rewater: false,
      runningfood: false,
      runningoxygen: false,
      runningrewater: false,
    }
  },

  computed: {
    advdata () {
      return {
        id: this.advnum,
        val: this.advice[this.advnum]
      }
    }
  },

  mounted () {
    this.startMove()
  },

  methods: {
    startMove () {
      // eslint-disable-next-line
      let timer = setTimeout(() => {
        if (this.advnum === 2) {
          this.advnum = 0;
         } else {
          this.advnum += 1;
         }
        this.startMove();
      }, 2000); // 滚动不需要停顿则将2000改成动画持续时间
    },
    Refresh(event){
      var that = this;
      that.client.publish("/aee/sub",'{"Refresh":1}', function(err){
        if(!err){
          console.log("成功下发命令——更新")
        }
      })
    },
    onHeatingChange(event){
      var that = this;
      let sw = event.mp.detail.value;
      if(sw){
        that.client.publish("/aee/sub",'{"Heating_SW":1}', function(err){
          if(!err){
            console.log("成功下发命令——恒温")
          }
        })
      }
      else{
        that.client.publish("/aee/sub",'{"Heating_SW":0}', function(err){
          if(!err){
            console.log("成功下发命令——关恒温")
          }
        })
      }
    },
    onLightChange(event){
      var that = this;
      let sw = event.mp.detail.value
      if(sw){
        that.client.publish("/aee/sub",'{"Light_SW":1}', function(err){
          if(!err){
            console.log("成功下发命令——开灯")
          }
        })
      }
      else{
        that.client.publish("/aee/sub",'{"Light_SW":0}', function(err){
          if(!err){
            console.log("成功下发命令——关灯")
          }
        })
      }
    },
    onFoodChange(event){
      var that = this;
      let sw = event.mp.detail.value;
      if(sw){
        that.client.publish("/aee/sub",'{"Food_SW":1}', function(err){
          if(!err){
            console.log("成功下发命令——喂食")
          }
        })
      }
    },
    onOxygenChange(event){
      var that = this;
      let sw = event.mp.detail.value;
      if(sw){
        that.client.publish("/aee/sub",'{"Oxygen_SW":1}', function(err){
          if(!err){
            console.log("成功下发命令——供氧")
          }
        })
      }
      else{
        that.client.publish("/aee/sub",'{"Oxygen_SW":0}', function(err){
          if(!err){
            console.log("成功下发命令——关氧")
          }
        })
      }
    },
    onRewaterChange(event){
      var that = this;
      let sw = event.mp.detail.value;
      if(sw){
        that.client.publish("/aee/sub",'{"Rewater_SW":1}', function(err){
          if(!err){
            console.log("成功下发命令——换水")
          }
        })
      }
    },
  },

  onPullDownRefresh() {
    var that = this;
    that.Refresh();
    wx.getLocation({
      type: "wgs84",
      success(res){
        const latitude = res.latitude;
        const longitude = res.longitude;
        const key = "yourkey";
        wx.request({
          url: `https://api.seniverse.com/v3/weather/now.json?key=${key}&location=${latitude}:${longitude}`,
          success(res) {
            //console.log(res.data);
            const {location,now} = res.data.results[0];
            that.location = location.name;
            that.wea_temp = now.temperature;
            that.weather = now.text;
          }
        });
        wx.request({
          url: `https://api.seniverse.com/v3/weather/daily.json?&key=${key}&location=${latitude}:${longitude}&days=1`,
          success(res) {
            //console.log(res.data);
            const {daily,last_update,location} = res.data.results[0];
            that.high_temp = daily[0].high;
            that.low_temp = daily[0].low;
            that.wind_dire = daily[0].wind_direction;
            that.wind_scale = daily[0].wind_scale;
          }
        });   
      }
    });
     wx.stopPullDownRefresh();
  },

  onShow(){
    var that = this;
    that.client = connect(mqtturl);
    that.client.on("connect",function() {
      that.client.subscribe("/aee/pub",function (err) {
        if(!err){
          console.log("成功订阅设备上行数据Topic!")
        }
      })
    })
    that.Refresh();
    that.client.on("message",function (topic,message) {
      console.log(message);
      let dataFromDev = {};
      dataFromDev = JSON.parse(message);
      that.temp = dataFromDev.temp;
      if(dataFromDev.depth==1) {that.depth = "正常"; that.bad[1] = false; that.advice[1] = "水量良好,暂无操作建议";}
      if(dataFromDev.depth==2) {that.depth = "适中"; that.bad[1] = false; that.advice[1] = "水量良好,暂无操作建议";}
      if(dataFromDev.depth==3) {that.depth = "缺水"; that.bad[1] = true; that.advice[1] = "水量低,请及时补水";}
      if(dataFromDev.turb==1) {that.turb = "优"; that.bad[2] = false; that.advice[2] = "水质良好,暂无操作建议";}
      if(dataFromDev.turb==2) {that.turb = "良"; that.bad[2] = false; that.advice[2] = "水质良好,暂无操作建议";}
      if(dataFromDev.turb==3) {that.turb = "中"; that.bad[2] = false; that.advice[2] = "水质良好,暂无操作建议";}
      if(dataFromDev.turb==4) {that.turb = "差"; that.bad[2] = true; that.advice[2] = "水质差,建议开启换水";}
      if(dataFromDev.temp<=18) {that.bad[0] = true; that.advice[0] = "水温低,建议开启恒温";}
      if(dataFromDev.temp>18) {that.bad[0] = false; that.advice[0] = "水温良好,暂无操作建议";}
      that.heating = dataFromDev.heating;
      that.light = dataFromDev.light;
      that.food = dataFromDev.food;
      that.runningfood = dataFromDev.runningfood;
      that.oxygen = dataFromDev.oxygen;
      that.runningoxygen = dataFromDev.runningoxygen;
      that.rewater = dataFromDev.rewater;
      that.runningrewater = dataFromDev.runningrewater;
    })
    wx.getLocation({
      type: "wgs84",
      success(res){
        const latitude = res.latitude;
        const longitude = res.longitude;
        const key = "yourkey";
        wx.request({
          url: `https://api.seniverse.com/v3/weather/now.json?key=${key}&location=${latitude}:${longitude}`,
          success(res) {
            //console.log(res.data);
            const {location,now} = res.data.results[0];
            that.location = location.name;
            that.wea_temp = now.temperature;
            that.weather = now.text;
          }
        });
        wx.request({
          url: `https://api.seniverse.com/v3/weather/daily.json?&key=${key}&location=${latitude}:${longitude}&days=1`,
          success(res) {
            //console.log(res.data);
            const {daily,last_update,location} = res.data.results[0];
            that.high_temp = daily[0].high;
            that.low_temp = daily[0].low;
            that.wind_dire = daily[0].wind_direction;
            that.wind_scale = daily[0].wind_scale;
          }
        });   
      }
    });
  }
};
</script>

<style lang="scss" scoped>
.wrapper{
  padding:15px;
  .header-wrapper{
    background-color: #3d7ef9;
    border-radius: 20px;
    color: #fff;
    box-shadow: #d6d6d6 0px 0px 5px;
    padding: 15px 30px;
    .header-title{
      display: flex;
      justify-content: space-between;
    }
    .header-text{
      margin-top: 10px;
      font-size: 32px;
      font-weight: 400;
      display: flex;
      justify-content: space-between;
    }
    .weather-highlow{
      margin-top: 10px;
      font-size: 12px;
    }
  }
  .data-wrapper{
    margin-top: 20px;
    display: flex;
    justify-content: space-between;
    .data{
      background-color: #fff;
      width: 150px;
      height: 80px;
      border-radius: 20px;
      display: flex;
      justify-content: space-between;
      padding: 0 8px;
      box-shadow: #d6d6d6 0px 0px 5px;
      .data-logo{
        height: 36px;
        width: 36px;
        padding: 20px 15px;
      }
      .data-text{
        margin-top: 10px;
        color: #7f7f7f;
        .data-title{
          font-size: 18px;
          padding: 0px 10px;
        }
        .data-value{
          font-size: 24px;
        }
      }
    }
    .advice{
      background-color: #fff;
      width: 330px;
      height: 40px;
      border-radius: 20px;
      display: flex;
      justify-content: space-between;
      padding: 0 8px;
      box-shadow: #d6d6d6 0px 0px 5px;
      overflow: hidden;
      .advice-logo{
        height: 36px;
        width: 36px;
        padding: 3px 15px;
      }
      .advice-text{
        padding: 10px 13px;
        color: #7f7f7f;
        font-size: 14px;
      }
      .slide-enter-active, .slide-leave-active {
        transition: all 0.5s linear;
      }
      .slide-enter{
        transform: translateY(20px) scale(1);
        opacity: 1;
      }
      .slide-leave-to {
        transform: translateY(-20px) scale(0.8);
        opacity: 0;
      }
    }
  }
}
</style>