在這個項目中,我們將使用物聯網構建一個智能農業系統。該項目的目的是幫助農民獲取實時數據(溫度、濕度、土壤水分、土壤溫度),以進行有效的環境監測,從而使他們能夠提高產品的整體產量和質量。這種使用由 NodeMCU 驅動的物聯網系統的智能農業由 DHT11 傳感器、濕度傳感器、DS18B20 傳感器探頭、LDR、水泵和 12V LED 燈條組成。當基于物聯網的農業監測系統啟動時,它會檢查土壤濕度、溫度、濕度和土壤溫度。然后它將這些數據發送到物聯網云進行實時監控。如果土壤濕度低于一定水平,它會自動啟動水泵。我們之前建自動植物灌溉系統 在移動設備上發送警報但不監控其他參數。除此之外, 雨水報警 和 土壤濕度檢測電路 也有助于構建智能農業監測系統。
智慧農業系統所需組件
NodeMCU ESP8266
土壤濕度傳感器
DHT11 傳感器
DS18B20 防水溫度傳感器探頭
LDR
潛水迷你水泵
12V LED燈條
7805穩壓器
電阻器(4.7K、10K)
電容器(0.1μF、10μF)
在線服務
Adafruit IO
智慧農業系統電路圖
智能農業系統的完整示意圖如下:
這個電路并不難。這里我們使用了4個傳感器,即DHT11,DS18B20傳感器探頭,LDR和土壤濕度傳感器,一個12V LED燈條,12V水泵,7805穩壓器,以及兩個TP122晶體管來控制LED燈條和水泵。7805 用于從 12V 適配器獲得穩壓 5V,DHT11 傳感器用于獲取溫度和濕度讀數。DS18B20傳感器探頭用于獲取土壤溫度,土壤濕度傳感器用于讀取土壤濕度,以便自動打開/關閉水泵。
Adafruit IO 設置
Adafruit IO 是一個開放數據平臺,可讓您聚合、可視化和分析云上的實時數據。使用 Adafruit IO,您可以通過 Internet 上傳、顯示和監控您的數據,并使您的項目支持物聯網。您可以使用 Adafruit IO 在互聯網上控制電機、讀取傳感器數據并制作酷炫的 IoT 應用程序。
要使用 Adafruit IO,首先,您必須在 Adafruit IO 上創建一個帳戶。為此,請訪問 Adafruit IO 網站并單擊屏幕右上角的“免費入門”。
完成帳戶創建過程后,登錄您的帳戶并單擊右上角的“查看 AIO Key”以獲取您的帳戶用戶名和 AIO 密鑰。
當您“AIO 密鑰”時,將彈出一個窗口,其中包含您的 Adafruit IO AIO 密鑰和用戶名。復制此密鑰和用戶名。您稍后將在代碼中使用它。
現在,在此之后,您需要創建一個提要。要創建提要,請單擊“提要”。然后單擊“操作”,您將看到一些選項,從中單擊“創建新提要”。
之后,將打開一個新窗口,您需要在其中輸入提要的名稱和描述。書寫描述是可選的。
點擊“創建”;在此之后,您將被重定向到新創建的提要。
對于這個項目,我們總共為水泵、LED 燈條、濕度數據、溫度、濕度、天氣數據和土壤溫度創建了 8 個源。按照與上述相同的過程創建其余的提要。
創建提要后,現在我們將創建一個 Adafruit IO 儀表板,以在單個頁面上顯示所有這些提要。為此,首先創建一個儀表板,然后在該儀表板中添加所有這些提要。
要創建儀表板,請單擊儀表板選項,然后單擊“操作”,然后單擊“創建新儀表板”。
在下一個窗口中,輸入儀表板的名稱,然后單擊“創建”。
創建儀表板后,現在我們將塊添加到儀表板。要添加塊,請單擊右上角的“齒輪”,然后單擊“創建新塊”。
首先,我們將添加兩個切換按鈕塊來手動打開/關閉 LED 燈條和水泵,然后添加四個滑塊來顯示溫度、濕度、土壤溫度和水分值,最后,兩個圖表塊來顯示最近 30 天的水分和土壤溫度數據。要在儀表板上添加按鈕,請單擊 Toggle 塊。
在下一個窗口中,它將要求您選擇提要,因此單擊 LED 提要。
在此之后,按照相同的過程添加其余的塊。添加所有塊后,我的儀表板如下所示:
您可以通過單擊設置按鈕來編輯儀表板。
獲取 OpenWeatherMap API
如前所述,我們還將在 Adafruit IO 儀表板上顯示天氣預報,為此,我們將使用OpenWeatherMap API 請求所選位置的當天天氣預報。OpenWeatherMap 提供了高度可識別的天氣產品,使處理天氣數據變得更加容易。可以通過遵循行業標準并與不同類型的企業系統兼容的快速、可靠的 API 訪問這些數據。OpenWeatherMap 提供付費和免費計劃,在這個項目中,我們將使用它的免費計劃來獲取天氣預報數據。
現在要獲取 API 密鑰,必須在他們的平臺上注冊,所以首先創建一個帳戶,一旦您的帳戶被創建,您將被重定向到儀表板,如下所示。從那里單擊您的姓名,然后單擊“我的 API 密鑰”,您將看到一個唯一的 API 密鑰以從站點中提取信息。
現在我們將使用 5 天/3 小時的預測數據 API。此 API 包含間隔 3 小時的天氣預報數據,并且預報數據以 JSON 或 XML 格式提供。要獲取您選擇的位置的天氣數據,請輸入以下 URL,將大括號中的部分替換為城市和您的唯一 API 密鑰:
api.openweathermap.org/data/2.5/forecast?q={城市名稱}&appid={API 密鑰}
例如,我們的 API URL 將是:
api.openweathermap.org/data/2.5/forecast?q=齋浦爾&appid=e8b22b36da932dce8f31ec9be9cb68a3
將此 URL 粘貼到瀏覽器的搜索欄中,它應該會為您提供一堆與您當地的天氣預報信息相對應的信息。
現在我們有了 JSON 數據,下一步將生成代碼,通過它我們可以讀取 JSON 數據并根據需要對其進行表述。為此,請轉到ArduinoJson 助手并在第一步中選擇處理器類型、模式和輸入類型。
然后在下一部分中,粘貼 JSON 數據。
然后在最后一步,您將獲得讀取天氣預報數據的代碼。我們不會使用助手生成的完整代碼。
智能農業系統編程NodeMCU
基于物聯網的農業監測系統的完整代碼在文檔末尾給出。在這里,我們將解釋代碼的一些重要部分。該代碼使用DallasTemperature、OneWire、?Adafruit_MQTT、?ArduinoJson和DHT.h庫。Adafruit_MQTT.h和DHT11.h可以從給定的鏈接下載,其余的庫可以直接從 Arduino IDE 庫管理器下載。
將庫安裝到 Arduino IDE 后,通過包含所需的庫文件來啟動代碼。
?
#include#include <達拉斯溫度.h> #include #include "DHT.h" #include "Adafruit_MQTT.h" #include "Adafruit_MQTT_Client.h" #include
?
然后輸入您從 Adafruit IO 服務器復制的 Wi-Fi 和 Adafruit IO 憑據。這些將包括 MQTT 服務器、端口號、用戶名和 AIO 密鑰。
?
const char *ssid = "Wi-Fi 名稱"; const char *pass = "Wi-Fi 密碼"; #define MQTT_SERV "io.adafruit.com" #define MQTT_PORT 1883 #define MQTT_NAME "Adafruit IO 用戶名" #define MQTT_PASS "AIO 密鑰"
?
然后設置用于存儲傳感器數據和控制 LED 和水泵的 Adafruit IO 饋送。在我的例子中,我定義了四個饋送來存儲不同的傳感器數據,即:土壤溫度、溫度、濕度和濕度,一個用于顯示天氣數據的饋送和兩個用于控制 LED 燈條和水泵的饋送。
?
Adafruit_MQTT_Client mqtt(&client, MQTT_SERV, MQTT_PORT, MQTT_NAME, MQTT_PASS); Adafruit_MQTT_Publish Moisture = Adafruit_MQTT_Publish(&mqtt,MQTT_NAME "/f/Moisture"); Adafruit_MQTT_Publish 溫度 = Adafruit_MQTT_Publish(&mqtt,MQTT_NAME "/f/Temperature"); Adafruit_MQTT_Publish Humidity = Adafruit_MQTT_Publish(&mqtt,MQTT_NAME "/f/Humidity"); Adafruit_MQTT_Publish SoilTemp = Adafruit_MQTT_Publish(&mqtt,MQTT_NAME "/f/SoilTemp"); Adafruit_MQTT_Subscribe LED = Adafruit_MQTT_Subscribe(&mqtt, MQTT_NAME "/f/LED"); Adafruit_MQTT_Subscribe Pump = Adafruit_MQTT_Subscribe(&mqtt, MQTT_NAME "/f/Pump");
?
現在在setup()函數中,以 9600 的波特率初始化串行監視器以進行調試。同時使用begin()函數初始化 DHT 傳感器和 DS18B20 傳感器。
?
無效設置() { 序列號.開始(9600); 延遲(10); dht.begin(); 傳感器.開始(); ……………….. }
?
現在是void loop()。這是執行所有任務的地方。因此,在這個循環中,首先我們將從 OpenWeatherMap API 獲取天氣預報數據,然后我們將讀取傳感器數據,最后一步,我們將在 Adafruit IO 儀表板上發布所有這些數據。
閱讀天氣預報:
要從 OpenWeatherMap API 讀取天氣預報數據,我們將使用我們使用 ArduinoJson 助手生成的代碼片段。在 void 循環中,我們只會在特定時間間隔后調用 API,以免超出每日限制。
?
if (millis() - lastConnectionTime > postInterval) { // 注意建立連接的時間: 最后連接時間 = 毫秒(); makehttpRequest(); }
?
讀取傳感器數據:?
現在獲取天氣數據后,接下來我們將讀取所有傳感器數據。這里我們使用 DHT11、DS18B20、LDR 和土壤濕度傳感器。LDR 和土壤濕度傳感器數據將用于自動化 LED 燈條和水泵。所以首先我們將讀取 LDR 狀態,如果 LDR 讀數小于 200,則 LED 將自動打開。同樣,如果土壤水分百分比小于 35,則水泵將打開。
?
int ldrStatus = 模擬讀取(ldrPin); 如果(ldrStatus <= 200){ 數字寫入(ledPin,高); } 別的 { 數字寫入(ledPin,低);} 濕度百分比 = ( 100.00 - ( (analogRead(moisturePin) / 1023.00) * 100.00 ) ); 如果(水分百分比 < 35){ 數字寫入(motorPin,高); } 溫度 = dht.readTemperature(); 濕度 = dht.readHumidity(); 傳感器.requestTemperatures(); 土壤溫度 = 傳感器.getTempCByIndex(0);
?
在 Adafruit IO 上發布數據:?
現在我們已經收集了所有數據,是時候在 Adafruit IO 儀表板上發布這些數據了,以便我們可以從任何地方對其進行監控。在這里,我們會將不同的傳感器數據發布到各自的提要中。
?
if (currentTime - previousTime >= 間隔) { if (! Moisture.publish(moisturePercentage)) 如果(!溫度。發布(溫度)) 如果(!濕度。發布(濕度)) if (!SoilTemp.publish(soiltemp)) 如果(!WeatherData.publish(圖標)) }
?
用于智能農業系統的 3D 打印外殼
由于這個項目將用于農業應用,我決定 3D 打印一個外殼。我用游標測量了裝置的尺寸來設計外殼。完成后,我的設計看起來像這樣:
對設計滿意后,我將其導出為 STL 文件,根據打印機設置對其進行切片,最后打印出來。STL 文件也可以從 Thingiverse 下載,您可以使用它打印自己的外殼。
打印完成后,我繼續將項目設置組裝在一個永久外殼中以備將來使用。完成連接后,我將電路組裝到我的外殼中,一切都非常合適,如下所示:
測試智能農業系統
為了測試這個項目,我在塑料托盤中發了一些種子,如下圖所示:
我將硬件盒安裝在托盤旁邊,將水泵連接到水瓶,并連接電源。完成后,它開始監測不同的參數,如土壤濕度、土壤溫度等。所有這些讀數都將發布在 Adafruit IO 儀表板上。
#include
#include
#include
#include "DHT.h"??
#include "Adafruit_MQTT.h"?
#include "Adafruit_MQTT_Client.h"?
#include
const char *ssid = "銀河-M20"; // 輸入您的 WiFi 名稱
const char *pass = "ac312124"; // 輸入您的 WiFi 密碼
WiFiClient 客戶端;
#define MQTT_SERV "io.adafruit.com"?
#define MQTT_PORT 1883?
#define MQTT_NAME "aschoudhary" // 你的 Adafruit IO 用戶
名 #define MQTT_PASS "1ac95cb8580b4271bbb6d9f75d0668f1" // Adafruit IO AIO key?
const char server[] = "api.openweathermap.org ";
字符串 apiKey = "e8b22b36da932dce8f31ec9be9cb68a3";?
字符串文本;
const char* icon="";?
int jsonend = 0;?
布爾 startJson = 假;
int 狀態 = WL_IDLE_STATUS;
#define JSON_BUFF_DIMENSION 2500?
unsigned long lastConnectionTime = 10 * 60 * 1000; // 上次連接服務器的時間,以毫秒為單位
const unsigned long postInterval = 10 * 60 * 1000; // 發布間隔 10 分鐘(10L * 1000L;測試延遲 10 秒)
const int ldrPin = D1;?
常量 int ledPin = D0;?
常量int濕氣Pin = A0;// 濕度傳感器引腳
const int motorPin = D8;?
漂浮水分百分比;//濕度讀數
int溫度,濕度,土壤溫度;
#define ONE_WIRE_BUS 4 //nodemcu 的 D2 引腳
#define DHTTYPE DHT11 //DHT 11?
#define dht_dpin D4?
DHT dht(dht_dpin, DHTTYPE);?
單線單線(ONE_WIRE_BUS);
達拉斯溫度傳感器(&oneWire);
const unsigned long 間隔 = 50000;?
unsigned long previousTime = 0;?
//設置您要發布到
Adafruit_MQTT_Client mqtt(&client, MQTT_SERV, MQTT_PORT, MQTT_NAME, MQTT_PASS);?
Adafruit_MQTT_Publish Moisture = Adafruit_MQTT_Publish(&mqtt,MQTT_NAME "/f/Moisture"); // Moisture 是將發布數據的源名稱
Adafruit_MQTT_Publish Temperature = Adafruit_MQTT_Publish(&mqtt,MQTT_NAME "/f/Temperature");
Adafruit_MQTT_Publish Humidity = Adafruit_MQTT_Publish(&mqtt,MQTT_NAME "/f/Humidity");?
Adafruit_MQTT_Publish SoilTemp = Adafruit_MQTT_Publish(&mqtt,MQTT_NAME "/f/SoilTemp");?
Adafruit_MQTT_Publish WeatherData = Adafruit_MQTT_Publish(&mqtt,MQTT_NAME "/f/WeatherData");?
//設置您訂閱的訂閱
?源 Adafruit_MQTT_Subscribe LED = Adafruit_MQTT_Subscribe(&mqtt, MQTT_NAME "/f/LED");?
?Adafruit_MQTT_Subscribe Pump = Adafruit_MQTT_Subscribe(&mqtt, MQTT_NAME "/f/Pump");??
無效設置()
{
? 序列.開始(9600);
? 延遲(10);
? dht.begin();?
? 傳感器.開始();
? mqtt.subscribe(&LED);?
? mqtt.subscribe(&Pump);
? pinMode(ledPin,輸出);
? pinMode(ldrPin,輸入);
? 數字寫入(motorPin,低);// 最初保持電機關閉 digitalWrite?
? (ledPin, HIGH);?
? text.reserve(JSON_BUFF_DIMENSION);?
? Serial.println("正在連接");?
? 序列號.println(ssid);?
? WiFi.開始(ssid,通過);
? 而(WiFi.status()!= WL_CONNECTED)
? {
? ? 延遲(500);
? ? Serial.print("."); // 打印...直到沒有連接
? }?
? Serial.println("");?
? Serial.println("WiFi 連接");?
}?
void loop()?
{?
?unsigned long currentTime = millis();?
?MQTT_connect();?
?if (millis() - lastConnectionTime >
? ? // 注意建立連接的時間:
? ? lastConnectionTime = millis();?
? ? makehttpRequest();?
? }?
//}?
?int ldrStatus = analogRead(ldrPin);?
? ? if (ldrStatus <= 200) {?
? ? ? ?digitalWrite(ledPin, HIGH);?
? ? ? ?Serial.print("天黑了,打開 LED :");?
? ? ? ?Serial.println(ldrStatus);? ??
? ? }??
? ? else {? ? ?
? ? ? digitalWrite(ledPin, LOW);?
? ? ? Serial.print("它很亮,關閉 LED :");?
? ? ? Serial.println(ldrStatus);?
? ? ?}
? 濕度百分比 = ( 100.00 - ( (analogRead(moisturePin) / 1023.00) * 100.00 ) );?
? Serial.print("土壤水分=");
? 序列號.print(moisturePercentage);?
? 序列號.println("%");? ?
if (moisturePercentage < 35) {?
? digitalWrite(motorPin, HIGH); // 調整電機
}?
if (moisturePercentage > 38) {?
? digitalWrite(motorPin, LOW); // 關閉電機
}?
?temperature = dht.readTemperature();??
?濕度 = dht.readHumidity();?
?//Serial.print("溫度:");?
?//Serial.print(溫度);?
?//Serial.println();??
?//Serial.print("濕度:");?
?//Serial.print(濕度);?
?//Serial.println();?
?傳感器.requestTemperatures();?
?土壤溫度 = 傳感器.getTempCByIndex(0);?
// Serial.println("土壤溫度:");
// Serial.println(soiltemp);?
if (currentTime - previousTime >= Interval) {??
? ? if (!Moisture.publish(moisturePercentage)) //此條件用于在 adafruit IO 上發布變量 (moisturePercentage)。根據你的改變變量。
? ? ? ? ?{? ? ? ? ? ? ? ? ? ? ??
? ? ? ? ? ? }?
? ? if (!Temperature.publish(temperature))??
? ? ? ? ?{? ? ? ? ? ? ? ? ? ? ??
? ? ? ? ? ?}?
? ? if (!Humidity.publish(humanity))??
? ? ? ? ?{? ? ? ? ? ? ? ? ? ? ??
? ? ? ? ?//delay(30000);? ?
? ? ? ? ? }?
? ? if (!SoilTemp.publish(soiltemp))??
? ? ? ?{? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? }?
? ? if (!WeatherData.publish(icon))?
? ? ? ?{? ? ? ? ? ?
? ? ? ? ?}
? ? ? ? ? 以前的時間 = 當前時間;
}?
Adafruit_MQTT_Subscribe * 訂閱;
while ((subscription = mqtt.readSubscription(5000))) //在控制某些東西或從 Adafruit IO 獲取數據之前不要使用這個。
? ? ?{?
? ? if (subscription == &LED)?
? ? ?{?
? ? ? //將新值打印到串口監視器
? ? ? Serial.println((char*) LED.lastread);?
? ? ? ? if (!strcmp((char*) LED.lastread, "OFF"))?
? ? ? ?{?
? ? ? ? ?digitalWrite(ledPin, LOW);?
? ? ? ? }?
? ? ? ? if (!strcmp((char*) LED.lastread, "ON"))?
? ? ? ? ? {?
? ? ? ? ?digitalWrite(ledPin, HIGH);?
? ? ? ? ?}
? ? ?}? ?
? ? if (subscription == &Pump)?
? ? ? {?
? ? ? //將新值打印到串口監視器
? ? ? Serial.println((char*) Pump.lastread);?
? ? ? if (!strcmp((char*) Pump.lastread, "OFF"))?
? ? ? ?{?
? ? ? ? digitalWrite(motorPin, HIGH);?
? ? ? ?}?
? ? ?if (!strcmp((char*) Pump.lastread, "ON"))?
? ? ? ?{?
? ? ? ? digitalWrite(motorPin, LOW);?
? ? ? ?}?
? ? ?}?
? ? }
? 延遲(9000);
?// client.publish(WeatherData, icon)?
}?
void MQTT_connect()??
{?
? int8_t ret;?
? // 如果已經連接則停止。
? if (mqtt.connected())??
? {
? ? 返回;
? }?
? uint8_t 重試次數 = 3;?
? while ((ret = mqtt.connect()) != 0) // connect 將返回 0 連接
? {??
? ? ? ?mqtt.disconnect();?
? ? ? ?延遲(5000);// 等待 5 秒
? ? ? ?重試--;?
? ? ? ?if (retries == 0)??
? ? ? ?{?
? ? ? ? ?// 基本上死了,等待 WDT 重置我
? ? ? ? ?while (1);?
? ? ? ?}?
? }?
}?
void makehttpRequest() {?
? // 在發送新請求之前關閉任何連接以允許客戶端與服務器建立連接
? client.stop();?
? // 如果連接成功:
? if (client.connect(server, 80)) {
? ? client.println("GET /data/2.5/forecast?q=" + nameOfCity + "&APPID=" + apiKey + "&mode=json&units=metric&cnt=2 HTTP/1.1");?
? ? client.println("主機:api.openweathermap.org");?
? ? client.println("用戶代理:ArduinoWiFi/1.1");?
? ? client.println("連接:關閉");?
? ? 客戶端.println();?
? ? 無符號長超時=毫秒();
? ? while (client.available() == 0) {?
? ? ? if (millis() - timeout > 5000) {?
? ? ? ? Serial.println(">>> Client Timeout !");?
? ? ? ? 客戶端.stop();?
? ? ? ? 返回;?
? ? ? }?
? ? }? ?
? ? 字符 c = 0;?
? ? while (client.available()) {?
? ? ? c = client.
? ? ? // 由于 json 包含相同數量的打開和關閉大括號,這意味著我們可以通過計算
? ? ? 打開和關閉出現次數來確定何時完全接收到 json,//?
? ? ? Serial.print(c);?
? ? ? if (c == '{') {?
? ? ? ? startJson = true; // 設置 startJson true 表示 json 消息已經開始
? ? ? ? jsonend++;?
? ? ? }?
? ? ? if (c == '}') {?
? ? ? ? jsonend--;?
? ? ? }?
? ? ? if (startJson == true) {?
? ? ? ? text += c;?
? ? ? }?
? ? ? // 如果 jsonend = 0 那么我們收到了相同數量的花括號?
? ? ? if (jsonend == 0 && startJson == true) {
? ? ? ? parseJson(text.c_str()); // 在 parseJson 函數中解析 c 字符串文本
? ? ? ? text = ""; // 下次清空字符串
? ? ? ? startJson = false; // 將 startJson 設置為 false,表示新消息尚未開始
? ? ? }?
? ? }?
? }?
? else {?
? ? // 如果沒有建立連接:
? ? Serial.println("connection failed");?
? ? 返回;?
? }?
}?
//解析從OWM接收的json數據
void parseJson(const char * jsonString) {?
? //StaticJsonBuffer<4000> jsonBuffer;
? const size_t bufferSize = 2*JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(2) + 4*JSON_OBJECT_SIZE(1) + 3*JSON_OBJECT_SIZE(2) + 3*JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(5) + 2*JSON_OBJECT_SIZE(7) + 2 *JSON_OBJECT_SIZE(8) + 720;
? DynamicJsonBuffer jsonBuffer(bufferSize);?
// DynamicJsonDocument(bufferSize);?
? // 在 JSON 樹中查找
? 字段 JsonObject& root = jsonBuffer.parseObject(jsonString);?
? if (!root.success()) {?
? ? Serial.println("parseObject() failed");?
? ? 返回;?
? }?
? JsonArray& list = root["list"];?
? JsonObject& nowT = list[0];?
? JsonObject& 稍后 = 列表 [1];?
? JsonObject& tommorow = list[2];?
// 字符串條件 = list.weather.main;
? // 包括溫度和濕度,供那些可能想破解它的人使用?
? String city = root["city"]["name"];?
? 字符串 weatherNow = nowT["天氣"][0]["描述"];?
? String weatherLater = later["weather"][0]["description"];?
? String list12 = later["weather"][0]["list"];?
? 序列號.println(list12);?
? Serial.println(weatherLater);?
? if(weatherLater == "幾朵云"){?
? ? icon = "幾朵云";?
? ? 序列號.print(icon);?
? }?
? else if(weatherLater == "rain"){?
? ? icon = "Rain";?
? ? 序列號.print(icon);?
? }?
? else if(weatherLater == "破云"?
? ? ){ icon = "破云";?
? ? 序列號.print(icon);?
? }
? 否則{
? ? 圖標=“晴天”;
? ? }?
}
評論