基于 Arduino ESP8266 的智慧农业大棚项目

基于 Arduino ESP8266 的智慧农业大棚项目

引言

  • 为什么做智慧大棚项目

    我参加了一个大学大创相关的团队,团队主题是智慧农业大棚,我是负责技术相关的。

    虽然这个团队更偏向于做ppt,写调查报告,甚至有点吹牛逼的成分在里面,但是我还是决心坚持做出点成果,能弄出一个技术相关的方案。

    这个方案可能在很多计算机大佬面前可能看起来很 ”简单“ 甚至 “弱智”,但是这里面都是我不断自学,不断挑战自我做出来的成果,也同样是我自己成长的一个记录。

    希望有一天我成为一个大佬后,看到我的项目也能会心一笑,能轻松说出当年误区和弯路。

  • ESP8266 的优势

    我选择esp8266作为微控制器有如下原因:

  • 本文目标

    分享硬件组成与基本功能。包括硬件部分的设计,和MQTT网络的连接。

项目硬件介绍

核心控制器:ESP8266

  • 我选用的是和ESP8266-NodeMCU硬件参考 – 太极创客教程一致的开发板。串口芯片是cp2102版本。
  • NodeMCU-Pin-Layout我对这块开发板其实很满意,作为新手来说已经很足够了。不过美中不足的是它只有一个模拟输入口(A0)。原本我打算用无源光敏电阻来检测光照,但土壤湿度传感器必须占用这个模拟口,所以我最终选择了走 I²C 接口的 GY-302 光照传感器。

传感器部分

​ 下图是我很早之前用 Fritzing画的图,接线可能与后文不太对的上。

image-20250926194145931

实物图:

image-20250926194159307

  • DHT22

    我原先选择的是DHT11传感器,想着刚好有太极创客的DHT11教程可以参考。

    但是按照教程一走,发现DHT11的精度实在是不太让人满意,温度只能输出整数 ,故选择了DHT22。

    我是用的是github.com/dvarrel/DHT22 DHT22库,在arduino库管理能直接下载。

    示例代码:DHT22/examples/dht22test dvarrel/DHT22 · GitHub

    核心代码如下:

    1
    2
    3
    4
    5
    6
    #include <DHT22.h>
    #define pinDATA SDA // SDA, or almost any other I/O pin
    DHT22 dht22(pinDATA);
    //下面两行代码放到loop()
    float t = dht22.getTemperature();
    float h = dht22.getHumidity();
  • SGP30:空气质量传感器(CO₂、VOC检测)

    使用的是GitHub - RobTillaart/SGP30: Arduino library for SGP30 environment sensor库,同样在arduino能直接下。

    示例代码:SGP30/examples/SGP30_demo/SGP30_demo.ino at master · RobTillaart/SGP30 · GitHub

    核心代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include "SGP30.h"
    SGP30 SGP;
    Wire.begin();
    SGP.GenericReset();

    //setRelHumidity(float T, float RH) sets the compensation for temperature (5-55°C) and relative humidity (10-95%). These values can be obtained e.g. from an SHT30, DHT22 or similar sensor.
    // T in °C
    // RH == 0..100
    SGP.setRelHumidity(float T, float RH);

    Serial.println("\nTVOC \teCO2 \tH2 \tETH");
    Serial.print(SGP.getTVOC());
    Serial.print("\t");
    Serial.print(SGP.getCO2());
    Serial.print("\t");
    Serial.print(SGP.getH2());
    Serial.print("\t");
    Serial.print(SGP.getEthanol());
    Serial.println();
  • 需要注意的是:

    1. 开机后需要预热一段时间,数值才能趋于稳定。
    2. H2 和乙醇的获取是带有实验性质的,没有经过校准,只能作为相对参考。
    3. CO2 单位为 ppm,TVOC 单位为 ppb。H2 和乙醇的单位为 ppm。
    4. setRelHumidity(float T, float RH)函数填入的是摄氏度和1到100的相对湿度,绝对湿度有其矫正函数可以适用。
  • GY-302(BH1750光照强度传感器):测量光照,I²C接口

    使用GitHub - claws/BH1750 库,也同样能在arduino库管理下载。

    我使用了示例里面的BH1750autoadjust 能高精度,自动选择量程(测量时间寄存器),在高亮度下不超量程,防止溢出,在低亮度下能提高灵敏度,测量微弱光线。

    除了能选择高精度,选择测量时间寄存器(Measurement Time Register,MTreg) ,还能选择工作模式是单次还是连续。

    具体的可以去GitHub上的README.md看看,就不一一列举了。

    核心代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    #include <BH1750.h>
    #include <Wire.h>
    BH1750 lightMeter;
    Wire.begin();
    lightMeter.begin(BH1750::ONE_TIME_HIGH_RES_MODE);

    if (lightMeter.measurementReady(true))
    {
    float lux = lightMeter.readLightLevel();
    if (lux < 0)
    {
    Serial.println(F("[system] 光照读取错误!"));
    }
    // 可选:根据光照强度调整 MTreg
    if (lux > 40000.0)
    {
    lightMeter.setMTreg(32);
    }
    else if (lux > 10.0)
    {
    lightMeter.setMTreg(69);
    }
    else
    {
    lightMeter.setMTreg(138);
    }
    // 调试打印
    Serial.print("[system] 光照: ");
    Serial.print(data.light);
    Serial.println(" lx");
    }

    注意:

    • 返回值为-1:没有从传感器传输有效数据。可能是通信错误。
    • 返回值为-2:配置错误。
  • 土壤湿度传感器:测量土壤含水量,模拟信号。

​ 本项目用的是最简单的模拟土壤湿度传感器,按道理来说应该没什么可讲的。但是看资料的时候看到了这一条。

image-20250927091637778

里面 Input voltage range is 0 - 1.0 v引起了我的注意,esp8266 VCC 是3.3v。所以我简单制作了一个分压电路,使用1kΩ和2.2kΩ的电阻进行分压。

使土壤湿度传感器在短路时,分到A0的电压在1.0伏特附近。

arduino对获取模拟口的范围是0-1024 所以经过分压后,当探针断路时,模拟口分压为1v左右,所以获得数值在img 左右。

代码:

1
2
#define analogInPin A0 //模拟引脚,用于土壤湿度检测
analogRead(analogInPin)

使用这个函数就能调用analogInPin的模拟数值。

image-20250926194611938

项目设计与功能

  • 各传感器与 ESP8266 的连接方式

    • 土壤湿度检测放到了A0模拟口。
    • DHT22放到了 GPIO 2口也就是D4
    • GY302和SGP30 共用 iic总线
      • D1 SCL
      • D2 SDA
  • WiFi 网络与数据传输

    • 我的项目采用mqtt协议传输,使用的代码基于太极创客的mqtt教程。

    • 使用的是test.mosquitto.org 服务器。

      以下是太极创客关于mqtt传输的示范案例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      /**********************************************************************
      项目名称/Project : 零基础入门学用物联网
      程序名称/Program name : publish_ranye_url
      团队/Team : 太极创客团队 / Taichi-Maker (www.taichi-maker.com)
      作者/Author : CYNO朔
      日期/Date(YYYYMMDD) : 20200813
      程序目的/Purpose :
      本程序旨在演示如何使用PubSubClient库使用ESP8266向MQTT服务器发布信息。
      -----------------------------------------------------------------------
      本示例程序为太极创客团队制作的《零基础入门学用物联网》中示例程序。
      该教程为对物联网开发感兴趣的朋友所设计和制作。如需了解更多该教程的信息,请参考以下网页:
      http://www.taichi-maker.com/homepage/esp8266-nodemcu-iot/iot-c/esp8266-nodemcu-web-client/http-request/
      ***********************************************************************/
      #include <ESP8266WiFi.h>
      #include <PubSubClient.h>
      #include <Ticker.h>

      // 设置wifi接入信息(请根据您的WiFi信息进行修改)
      const char* ssid = "taichi-maker";
      const char* password = "12345678";
      const char* mqttServer = "test.ranye-iot.net";

      // 如以上MQTT服务器无法正常连接,请前往以下页面寻找解决方案
      // http://www.taichi-maker.com/public-mqtt-broker/

      Ticker ticker;
      WiFiClient wifiClient;
      PubSubClient mqttClient(wifiClient);

      int count; // Ticker计数用变量

      void setup() {
      Serial.begin(9600);

      //设置ESP8266工作模式为无线终端模式
      WiFi.mode(WIFI_STA);

      // 连接WiFi
      connectWifi();

      // 设置MQTT服务器和端口号
      mqttClient.setServer(mqttServer, 1883);

      // 连接MQTT服务器
      connectMQTTServer();

      // Ticker定时对象
      ticker.attach(1, tickerCount);
      }

      void loop() {
      if (mqttClient.connected()) { // 如果开发板成功连接服务器
      // 每隔3秒钟发布一次信息
      if (count >= 3){
      pubMQTTmsg();
      count = 0;
      }
      // 保持心跳
      mqttClient.loop();
      } else { // 如果开发板未能成功连接服务器
      connectMQTTServer(); // 则尝试连接服务器
      }
      }

      void tickerCount(){
      count++;
      }

      void connectMQTTServer(){
      // 根据ESP8266的MAC地址生成客户端ID(避免与其它ESP8266的客户端ID重名)
      String clientId = "esp8266-" + WiFi.macAddress();

      // 连接MQTT服务器
      if (mqttClient.connect(clientId.c_str())) {
      Serial.println("MQTT Server Connected.");
      Serial.println("Server Address: ");
      Serial.println(mqttServer);
      Serial.println("ClientId:");
      Serial.println(clientId);
      } else {
      Serial.print("MQTT Server Connect Failed. Client State:");
      Serial.println(mqttClient.state());
      delay(3000);
      }
      }

      // 发布信息
      void pubMQTTmsg(){
      static int value; // 客户端发布信息用数字

      // 建立发布主题。主题名称以Taichi-Maker-为前缀,后面添加设备的MAC地址。
      // 这么做是为确保不同用户进行MQTT信息发布时,ESP8266客户端名称各不相同,
      String topicString = "Taichi-Maker-Pub-" + WiFi.macAddress();
      char publishTopic[topicString.length() + 1];
      strcpy(publishTopic, topicString.c_str());

      // 建立发布信息。信息内容以Hello World为起始,后面添加发布次数。
      String messageString = "Hello World " + String(value++);
      char publishMsg[messageString.length() + 1];
      strcpy(publishMsg, messageString.c_str());

      // 实现ESP8266向主题发布信息
      if(mqttClient.publish(publishTopic, publishMsg)){
      Serial.println("Publish Topic:");Serial.println(publishTopic);
      Serial.println("Publish message:");Serial.println(publishMsg);
      } else {
      Serial.println("Message Publish Failed.");
      }
      }

      // ESP8266连接wifi
      void connectWifi(){

      WiFi.begin(ssid, password);

      //等待WiFi连接,成功连接后输出成功信息
      while (WiFi.status() != WL_CONNECTED) {
      delay(1000);
      Serial.print(".");
      }
      Serial.println("");
      Serial.println("WiFi Connected!");
      Serial.println("");
      }

      当然,esp8266也能从服务器订阅消息,但是我的项目暂时对这个功能没要求。

      如果需要,可以上太极创客官网学习。

      我想增强mqtt的传输安全性,想使用双向TLS验证。

      注意:由于我无法访问test.ranye-iot.net 后面的教程都是基于test.mosquitto.org的。

      • 修改成双向TLS

        1. 我们需要同步时间,时间不同步会导致TLS失败

          我们可以在setup() 里面校时

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        #include <time.h>

        void setupTime() {
        configTime(0, 0, "pool.ntp.org", "time.nist.gov");
        time_t now = time(nullptr);
        while (now < 8 * 3600 * 2) { // 等待直到时间同步
        delay(500);
        now = time(nullptr);
        }
        Serial.printf("Current time: %s\n", ctime(&now));
        }

        在连接上Wi-Fi后,调用setupTime();

        1. WiFiClient换成 WiFiClientSecureBearSSL
        1
        2
        3
        4
        5
        6
        #include <WiFiClientSecureBearSSL.h>

        // 原来:WiFiClient wifiClient;
        // 改成:
        BearSSL::WiFiClientSecure wifiClient;
        PubSubClient mqttClient(wifiClient);
        1. 准备好三个证书

          • mosquitto.org.crt(CA 根证书)-

          • client.crt(你上传 CSR 后生成的客户端证书)

          • client.key(本地生成的私钥)

            第一个CA证书在这个页面下载test.mosquitto.org

            第三个证书需要自己生成 跟随Generate client certificates for test.mosquitto.org 指引

            然后在第二个网址上传自己生成好的CSR,就能生成并下载CRT证书

        2. 把它们粘贴进代码里:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          // 根证书
          static const char ca_cert[] PROGMEM = R"EOF(
          -----BEGIN CERTIFICATE-----
          ... test.mosquitto.org 的 CA 证书 ...
          -----END CERTIFICATE-----
          )EOF";

          // 客户端证书
          static const char client_cert[] PROGMEM = R"EOF(
          -----BEGIN CERTIFICATE-----
          ... 你上传 CSR 后下载到的 client.crt ...
          -----END CERTIFICATE-----
          )EOF";

          // 客户端私钥
          static const char client_key[] PROGMEM = R"EOF(
          -----BEGIN RSA PRIVATE KEY-----
          ... 你本地生成的 client.key ...
          -----END RSA PRIVATE KEY-----
          )EOF";
        3. 初始化BearSSL

          1
          2
          3
          4
          5
          6
          BearSSL::X509List cert(ca_cert);
          wifiClient.setTrustAnchors(&cert);

          BearSSL::X509List client_crt(client_cert);
          BearSSL::PrivateKey key(client_key);
          wifiClient.setClientRSACert(&client_crt, &key);
        4. 改服务器端口成8884

  • 数据转化成JSON字符串发送

    项目代码节选:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    #include <ArduinoJson.h>

    struct Data {
    float air_tmp = 0;
    float air_RH = 0;
    uint16_t eath_H = 0;
    uint16_t light = 0;
    uint16_t co2 = 0;
    uint16_t tvoc = 0;
    float h2 = 0;
    float eth = 0;
    } data;

    void setup() {
    Serial.begin(115200);

    // 创建 JSON 文档
    StaticJsonDocument<256> doc;

    }

    void loop() {
    doc["air_tmp"] = air_tmp;
    doc["air_RH"] = air_RH;
    doc["eath_H"] = eath_H;
    doc["light"] = light;
    doc["co2"] = co2;
    doc["tvoc"] = voc;
    doc["h2"] = h2;
    doc["eth"] = eth;

    char cstr[256];
    serializeJson(doc, cstr, sizeof(cstr));
    //到这里数据就转换成JSON格式的字符串 cstr了
    Serial.println(cstr);
    }

总结与展望

  • 项目成果

    • 项目实现了数据采集并实时上传
    • 使用SSL加密
    • 学习了MQTT协议
  • 可以拓展的方向

    1. 后端尚未开发,可以开发后端
    2. 硬件与预想时剔除了水泵,但是扩展板存在电机驱动电路,可以添加上
    3. 若要添加水泵,可以采用订阅MQTT

参考资料: