QingSpace 发表于 2024-10-31 20:59

【2024 DigiKey创意大赛】智慧农业 作品提交

本帖最后由 QingSpace 于 2024-11-1 16:15 编辑

# 一、作品简介

​                本作品以**ESP32C6**为主控,实现了对于几种农业数据的采集与通过控制相应外设调节这些因素对于农作物影响的功能【基础功能】;在此基础上,又使用APP Inventer制作了蓝牙数据接收APP,使用**树莓派5**搭建Home Assistant服务器,实现了数据的多端同步【进阶功能】;目前,可以通过操作串口屏实现外设的模式切换(自动/开/关),未来将加入通过蓝牙APP和Home Assistant网页实现外设的切换,并进一步实现临界数值的调整【未来功能】。

​                在本作品中,**nRF52840 Dongle**起到了调试BLE的作用,在电脑上不方便查看BLE的广播数据,使用这款迷你板卡搭配NORDIC官方的软件,就可以很方便地在电脑上查看BLE的数据便于我进行调试。

# 二、系统框图



# 三、各部分功能说明

本程序使用了FreeRTOS,下面将介绍各个任务的代码以及功能

## 3.1 传感器读取任务

```C++
void SensorDataAcquisition(void *arg)
{
// 光线传感器
Wire.begin(LP_I2C_SDA, LP_I2C_SCL);
lightMeter.begin();

// 土壤湿度传感器
pinMode(SOIL_SENSOR, INPUT);

// 温湿度传感器
dht11.setup(DHT11_PIN, DHTesp::DHT11);

while(1)
{
    lux = lightMeter.readLightLevel();
    soil = 100 * (soil_max - analogRead(SOIL_SENSOR)) / (soil_max - soil_min);
    TH = dht11.getTempAndHumidity();

#if UART
    Serial.print("Light: ");
    Serial.print(lux);
    Serial.println(" lx");

    Serial.print("Soil: ");
    Serial.print(soil);
    Serial.println(" %");

    Serial.print("Temperature: ");
    Serial.print(TH.temperature);
    Serial.print(" °C\tHumidity: ");
    Serial.print(int(TH.humidity));
    Serial.println(" %");
#endif

    if (Serial.available() >= 9)
    {
    // 读取九位十六进制数据
    byte data;
    for (int i = 0; i < 9; i++)
    {
      data = Serial.read();
    }

    // 提取第七位(B7)和第八位(B8)
    byte B7 = data;
    byte B8 = data;

    // 计算二氧化碳浓度
    co2 = B7 * 256 + B8;
#if UART
    // 输出结果
    Serial.print("CO2 Concentration: ");
    Serial.print(co2);
    Serial.println(" PPM");
#endif
    }

    sprintf(BT, "%4.1f %2.0f %4.0f %2.0f %4.0f\n", TH.temperature, TH.humidity, lux, soil, co2);

#if UART
    Serial.println();
    Serial.println(BT);
#endif

    delay(100);
}
}
```

​                任务1主要负责传感器数据的获取传感器的数据,其中光线传感器和温湿度传感器通过函数直接获取,土壤湿度传感器通过电容上的电压值计算出相对湿度,二氧化碳传感器则会通过串口把数据发送给ESP32,而ESP32C6恰好有两个串口,其中一个是LP串口(低功耗串口),它可以当成普通串口用。我将普通串口用作接收二氧化碳传感器的数据,LP串口专门与串口屏通信。

​                此外,我将所有的串口输出都加了“UART”宏,如果该宏的定义为1,则会在串口上显示各种数据,反之则不会,方便在调试时查看串口数据,调试好后就不需要串口输出了。

## 3.2 蓝牙任务

```C++
      /* 以下为BLE部分变量 */
BLEServer *pServer = NULL;
BLECharacteristic *pTxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;

void BLESend(const String& message);
void HMISend();

class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
    deviceConnected = true;
};

void onDisconnect(BLEServer *pServer) {
    deviceConnected = false;
}
};

class MyCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
    String rxValue = pCharacteristic->getValue();
#if UART
    if (rxValue.length() > 0) {
      Serial.println("*********");
      Serial.print("Received Value: ");
      for (int i = 0; i < rxValue.length(); i++) {
      Serial.print(rxValue);
      }

      Serial.println();
      Serial.println("*********");
    }
#endif
}
};

void BLETask(void *arg)
{
   // Create the BLE Device
BLEDevice::init("Smart Agriculture");

// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());

// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);

// Create a BLE Characteristic
pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);

pTxCharacteristic->addDescriptor(new BLE2902());

BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);

pRxCharacteristic->setCallbacks(new MyCallbacks());

// Start the service
pService->start();

// Start advertising
pServer->getAdvertising()->start();
#if UART
Serial.println("Waiting a client connection to notify...");
#endif

while(1)
{
    BLESend(BT);

    // disconnecting
    if (!deviceConnected && oldDeviceConnected) {
      delay(500);                   // give the bluetooth stack the chance to get things ready
      pServer->startAdvertising();// restart advertising
#if UART
      Serial.println("start advertising");
#endif
      oldDeviceConnected = deviceConnected;
    }
    // connecting
    if (deviceConnected && !oldDeviceConnected) {
      // do stuff here on connecting
      oldDeviceConnected = deviceConnected;
    }
}
}
```

​                此处的代码来自Arduino IDE中的ESP32的示例程序,不做过多讲解。总体上就是负责创建一个BLE设备、服务器、服务和特征,并处理设备连接和断开连接的逻辑等。在连接成功后会发送数据到客户端(即蓝牙APP)。

## 3.3 串口屏任务

```C++
HardwareSerial LP_Serial(1); //这一行在最开始

LP_Serial.begin(115200, SERIAL_8N1, LP_UART_RXD, LP_UART_TXD); //这一行在setup函数中

void HMITask(void *arg)
{
while(1)
{
    LP_Serial.printf("data.temp.txt=\"%.1f\"", TH.temperature);
    HMISend();

    LP_Serial.printf("data.huma.txt=\"%.0f\"", TH.humidity);
    HMISend();

    LP_Serial.printf("data.lit.txt=\"%.0f\"", lux);
    HMISend();

    LP_Serial.printf("data.hums.txt=\"%.0f %\"", soil);
    HMISend();

    LP_Serial.printf("data.co.txt=\"%.0f\"", co2);
    HMISend();

    delay(1000);

    if (LP_Serial.available())
    {
      String LP_data = LP_Serial.readStringUntil(';');

      LP_data.trim();

      if (LP_data == "1:0") {
          led_state = 0;
      } else if (LP_data == "1:1") {
          led_state = 1;
      } else if (LP_data == "1:2") {
          led_state = 2;
      } else if (LP_data == "2:0") {
          fan_state = 0;
      } else if (LP_data == "2:1") {
          fan_state = 1;
      } else if (LP_data == "2:2") {
          fan_state = 2;
      } else if (LP_data == "3:0") {
          pump_state = 0;
      } else if (LP_data == "3:1") {
          pump_state = 1;
      } else if (LP_data == "3:2") {
          pump_state = 2;
      }
    }
}
}
```

​                此任务首先需要定义一下LP串口,然后初始化LP串口,并向串口屏不断发送传感器的数据,同时如果接收到串口屏发来的控制指令做出相应处理。外设的状态有:0-关,1-开,2-自动,而“1:2”中前一个数字是外设编号,第二个数字是外设更改后的状态,外设1是LED,外设2是风扇,外设3是水泵。

## 3.4 WiFi/MQTT任务

```C++
      /* 以下为WiFi和MQTT部分变量 */
const char *ssid = "Mate 40 Pro"; // Wifi 账号
const char *password = "15239570078gzc";// wifi 密码

//客户端变量
WiFiClient espClient;
PubSubClient client(espClient);

// 配置消息
char config_temperature[] = "{\"unique_id\":\"Smart-Agriculture-Temperature\",\"name\":\"温度传感器\",\"icon\":\"mdi:thermometer\",\"state_topic\":\"Smart-Agriculture/temperature/state\",\"json_attributes_topic\":\"Smart-Agriculture/temperature/attributes\",\"unit_of_measurement\":\"℃\",\"device\":{\"identifiers\":\"ESP32\",\"manufacturer\":\"QingSpace\",\"model\":\"HA\",\"name\":\"ESP32\",\"sw_version\":\"1.0\"}}";
char config_humidity[] = "{\"unique_id\":\"Smart-Agriculture-Humidity\",\"name\":\"空气湿度传感器\",\"icon\":\"mdi:water-percent\",\"state_topic\":\"Smart-Agriculture/humidity/state\",\"json_attributes_topic\":\"Smart-Agriculture/humidity/attributes\",\"unit_of_measurement\":\"%\",\"device\":{\"identifiers\":\"ESP32\",\"manufacturer\":\"QingSpace\",\"model\":\"HA\",\"name\":\"ESP32\",\"sw_version\":\"1.0\"}}";
char config_light[] = "{\"unique_id\":\"Smart-Agriculture-Light\",\"name\":\"光照传感器\",\"icon\":\"mdi:brightness-5\",\"state_topic\":\"Smart-Agriculture/light/state\",\"json_attributes_topic\":\"Smart-Agriculture/light/attributes\",\"unit_of_measurement\":\"lux\",\"device\":{\"identifiers\":\"ESP32\",\"manufacturer\":\"QingSpace\",\"model\":\"HA\",\"name\":\"ESP32\",\"sw_version\":\"1.0\"}}";
char config_soil_moisture[] = "{\"unique_id\":\"Smart-Agriculture-Soil-Moisture\",\"name\":\"土壤湿度传感器\",\"icon\":\"mdi:water-outline\",\"state_topic\":\"Smart-Agriculture/soil-moisture/state\",\"json_attributes_topic\":\"Smart-Agriculture/soil-moisture/attributes\",\"unit_of_measurement\":\"%\",\"device\":{\"identifiers\":\"ESP32\",\"manufacturer\":\"QingSpace\",\"model\":\"HA\",\"name\":\"ESP32\",\"sw_version\":\"1.0\"}}";
char config_co2[] = "{\"unique_id\":\"Smart-Agriculture-CO2\",\"name\":\"二氧化碳传感器\",\"icon\":\"mdi:molecule-co2\",\"state_topic\":\"Smart-Agriculture/co2/state\",\"json_attributes_topic\":\"Smart-Agriculture/co2/attributes\",\"unit_of_measurement\":\"ppm\",\"device\":{\"identifiers\":\"ESP32\",\"manufacturer\":\"QingSpace\",\"model\":\"HA\",\"name\":\"ESP32\",\"sw_version\":\"1.0\"}}";


// MQTT Broker 服务端连接
const char *mqtt_broker = "192.168.43.179";//mqtt服务器地址
const char *mqtt_username = "QSG";
const char *mqtt_password = "gzc20050414";
const int mqtt_port = 1883;//端口


void MQTTTask(void *arg)
{
// connecting to a WiFi network
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
      delay(2000);
#if UART
      Serial.println("Connecting to WiFi...");
#endif
}
#if UART
Serial.println("Connected to WiFi");
#endif

//connecting to a mqtt broker 连接服务端
client.setBufferSize(512); // 增加消息缓冲区大小
client.setServer(mqtt_broker, mqtt_port);
while (!client.connected()) {
      String client_id = "esp32-client-";
      client_id += String(WiFi.macAddress());
      Serial.printf("The client %s connects to the public mqtt broker\n", client_id.c_str());
      if (client.connect(client_id.c_str(), mqtt_username, mqtt_password)) {
#if UART
          Serial.println("Public emqx mqtt broker connected");
#endif
      } else {
#if UART
          Serial.print("failed with state ");
          Serial.print(client.state());//返回连接状态
#endif
          delay(2000);
      }
}

// 发送初始化配置消息
client.publish("homeassistant/sensor/HA/Smart-Agriculture-Temperature/config", config_temperature);
client.publish("homeassistant/sensor/HA/Smart-Agriculture-Humidity/config", config_humidity);
client.publish("homeassistant/sensor/HA/Smart-Agriculture-Light/config", config_light);
client.publish("homeassistant/sensor/HA/Smart-Agriculture-Soil-Moisture/config", config_soil_moisture);
client.publish("homeassistant/sensor/HA/Smart-Agriculture-CO2/config", config_co2);
while(1)
{
    client.publish("Smart-Agriculture/temperature/state", String(TH.temperature).c_str());
    client.publish("Smart-Agriculture/humidity/state", String(TH.humidity).c_str());
    client.publish("Smart-Agriculture/light/state", String(lux).c_str());
    client.publish("Smart-Agriculture/soil-moisture/state", String(soil).c_str());
    client.publish("Smart-Agriculture/co2/state", String(co2).c_str());

    delay(1000);
}
}
```

​                此任务的配置部分也是节选自Arduino IDE中ESP32的示例程序,主要说一下发送的信息内容。在每次连接成功后,我们需要先向搭建在树莓派上的MQTT服务器发送传感器的初始化配置信息,如果Home Assistant上没有相应的设备则会添加上去,如果已经有了就不会有变化。然后不断向MQTT服务器发送传感器数据,不断更新网页上的传感器数据。这样可以在相对较远的距离下无线查看传感器数据。不过目前只能在局域网内查看,即ESP32,树莓派和查看数据的设备要再同一个局域网内。

## 3.5 外设控制任务

```C++
void ControlTask(void *arg)
{
pinMode(LED_PIN, OUTPUT);
pinMode(FAN_PIN1, OUTPUT);
pinMode(FAN_PIN2, OUTPUT);

while(1)
{
    if(led_state == 2)
    {
      digitalWrite(LED_PIN, lux < lux_limit ? HIGH : LOW);
    }
    else
    {
      digitalWrite(LED_PIN, led_state ? HIGH : LOW);
    }

    if(fan_state == 2)
    {
      analogWrite(FAN_PIN1, (TH.temperature > temp_limit ? 1 : 0) * 75);
    }
    else
    {
      analogWrite(FAN_PIN1, (fan_state ? 1 : 0) * 75);
    }

    if(pump_state == 2)
    {
      analogWrite(PUMP_PIN1, (soil < soil_limit ? 1 : 0) * 255);
    }
    else
    {
      analogWrite(PUMP_PIN1, (pump_state ? 1 : 0) * 255);
    }
}
}
```

​                这里就比较简单了,只是根据外设的状态去自动或手动控制外设的运行与否。

# 四、作品源码
https://download.eeworld.com.cn/detail/QingSpace/634862
# 五、作品功能演示视频
<iframe src="//player.bilibili.com/player.html?isOutside=true&aid=113402068409921&bvid=BV1dKSJY5Ees&cid=26550995552&p=1" width=500 height=300 scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>
# 六、项目总结

​                在该项目中,我第一次使用到了树莓派,遇到了很多问题,但是都被我一一克服。同时,我也发现了ESP32在无线通信方面的强大优势,也是第一次将ESP32投入实际应用之中,我相信在之后的创意之路上ESP32一定能成为我的好帮手。

# 七、其他

树莓派搭建Home Assistant参考了这篇文章:[[活动资料\] 【2024 DigiKey 创意大赛】环境搭建【Docker + HA + MQTT】 - DigiKey得捷技术专区 - 电子工程世界-论坛 (eeworld.com.cn)](https://bbs.eeworld.com.cn/thread-1295356-1-1.html)
页: [1]
查看完整版本: 【2024 DigiKey创意大赛】智慧农业 作品提交