Community project
Giám Sát Mực Nước AWD
The AWD Water Level Monitoring Station is a solar-powered remote sensor system built around an ESP32 that tracks water depth, soil moisture, and temperature using LoRa wireless transmission. The station combines an AJ-SR04M waterproof ultrasonic sensor for water level measurement, a capacitive soil moisture sensor, and a DS18B20 temperature sensor to gather environmental data from hard-to-reach locations.
This guide provides a complete parts list, wiring diagram, and step-by-step assembly instructions for building a weatherproof monitoring station. The included firmware handles sensor calibration, multi-sample averaging, LoRa transmission on the 868 MHz band, and deep sleep power management to extend battery life. Builders will learn how to integrate solar charging, configure the ultrasonic distance sensor for water depth calculation, and deploy a low-power IoT device for long-term environmental monitoring.
Wiring diagram

Gather all the parts
Assemble it in 6 steps
1. Lắp ống đo mực nước
Dùng pvc_awdtube là ống PVC Ø90 mm dài khoảng 1 m. Khoan các lỗ nhỏ ở nửa dưới để nước ruộng đi vào ống, nhưng không khoan gần đầu dò. Cắm ống thẳng đứng xuống nền ruộng, để miệng ống cao hơn mực nước cao nhất dự kiến. Gắn đầu dò kín nước của water_level_sensor ở nắp ống, mặt đầu dò hướng thẳng xuống nước.
- Đặt ống tại vị trí đại diện cho ruộng, tránh sát bờ hoặc chỗ nước chảy mạnh.
- Đo khoảng cách từ mặt đầu dò đến mặt đất trong ống. Firmware đang dùng 30 cm; sửa hằng SENSOR_TO_SOIL_CM nếu số đo thực tế khác.
- Không để bùn, rơm hoặc lá bịt các lỗ của ống; nước vào ống chậm sẽ làm mực nước đo được không đúng.
2. Nối đầu dò mực nước
Đưa cáp của water_level_sensor vào abs_enclosure qua đầu siết cáp. Nối VCC → 3V3 (nguồn), GND → GND (ground), TRIG → GPIO25 (ra lệnh đo), ECHO → GPIO26 (tín hiệu phản hồi). AJ-SR04M được cấp 3,3 V nên ECHO cũng an toàn cho ESP32, không dùng mạch điện trở hạ áp.
- Để AJ-SR04M ở chế độ xung mặc định để firmware hiện tại đo bằng chân TRIG và ECHO.
- Bọc mối nối bằng ống co nhiệt trước khi cho vào hộp.
- Không cấp 5 V cho AJ-SR04M khi ECHO được nối trực tiếp với GPIO26; ECHO 5 V có thể làm hỏng ESP32.
3. Lắp cảm biến nhiệt độ và ẩm đất
Đặt temperature_sensor ở vị trí cần theo dõi nhiệt độ, tránh để dây bị dập. Nối VCC → 3V3 (nguồn), GND → GND (ground), DATA → GPIO27 (tín hiệu). Nối temperature_pullup: END_A → 3V3 (nguồn), END_B → chân DATA của temperature_sensor (giữ tín hiệu ổn định). Cắm đầu dò soil_moisture_sensor vào đất tại vùng rễ: VCC → 3V3 (nguồn), GND → GND (ground), AOUT → GPIO34 (tín hiệu đo độ ẩm).
- Cảm biến điện dung chịu ruộng tốt hơn loại hai thanh kim loại, nhưng vẫn cần hiệu chuẩn một mẫu đất khô và một mẫu đất bão hòa.
- Giữ phần mạch điện tử của cảm biến ẩm đất ở trên mặt đất và che mưa; chỉ cắm phần đầu dò vào đất.
- Không nhúng mối nối hoặc phần mạch của cảm biến vào nước ruộng; nước có thể gây chập và làm số liệu sai.
4. Nối mô-đun LoRa Ra-01H
Đặt lora_radio trong abs_enclosure. Nối 3.3V → 3V3 (nguồn), GND → GND (ground), SCK → GPIO18 (xung), MISO → GPIO19 (dữ liệu về), MOSI → GPIO23 (dữ liệu đi), NSS → GPIO4 (chọn mô-đun), RESET → GPIO32 (khởi động lại), DIO0 → GPIO33 (báo trạng thái). Hàn hoặc gắn đúng anten cho dải 868 MHz vào chân ANT trước khi cấp nguồn.
- Ra-01H hoạt động trong dải 803–930 MHz; firmware đặt 868 MHz. Bộ thu phải dùng cùng tần số, thông số LoRa và anten 868 MHz.
- Đặt anten thẳng đứng, cách dây nguồn và tấm pin mặt trời ít nhất 10 cm.
- Chỉ cấp 3,3 V cho Ra-01H; cấp 5 V có thể làm hỏng mô-đun.
- Không phát khi chưa gắn anten; điều đó có thể làm hỏng phần phát vô tuyến.
- Hãy xác nhận 868 MHz được phép sử dụng tại địa phương trước khi triển khai hàng loạt.
5. Lắp nguồn năng lượng mặt trời
Trong abs_enclosure, nối solar_panel: PV+ → VIN của solar_charger (nguồn), PV− → VSS của solar_charger (ground). Nối solar_charger: VBAT → +V của battery_18650 (sạc pin), GND → GND của battery_18650 (ground). Nối battery_18650: +V → IN+ của boost_converter (nguồn), GND → IN− của boost_converter (ground). Nối boost_converter: OUT+ → VIN/5V của ESP32 (nguồn), OUT− → GND của ESP32 (ground).
- Cố định pin để không lăn, không bị vật kim loại chạm cả hai đầu.
- Đặt tấm pin ở nơi ít bị lúa hoặc cây che bóng.
- Trước khi nối ESP32, dùng đồng hồ đo chỉnh OUT+ của boost_converter đúng 5,0 V; điện áp cao hơn có thể làm hỏng bo mạch.
- Không hàn trực tiếp vào viên pin 18650 rời; dùng khay pin hoặc pack pin có mạch bảo vệ.
6. Đóng hộp và đưa trạm vào hoạt động
Đặt ESP32, solar_charger, boost_converter và lora_radio trong abs_enclosure. Siết chặt đầu siết cáp, bọc mọi mối nối hở và đóng nắp hộp. Gắn hộp cao hơn mặt nước, nghiêng nhẹ để nước mưa không đọng trên nắp. Dán mã trạm ở ngoài hộp để ghép đúng bản ghi LoRa với đúng thửa ruộng.
- Trạm đo, gửi gói dữ liệu rồi ngủ 15 phút để giảm hao pin.
- Hiệu chuẩn SOIL_DRY_RAW và SOIL_WET_RAW trong firmware từ mẫu đất thực tế trước khi nhân rộng.
- Không đóng hộp khi còn dây trần hoặc chỗ nối hở; hơi ẩm có thể gây chập mạch và làm trạm dừng hoạt động.
Review all connections
1. Connections between "solar_panel" and "ESP32"
2. Connections between "solar_charger" and "ESP32"
3. Connections between "boost_converter" and "ESP32"
4. Connections between "water_level_sensor" and "ESP32"
5. Connections between "lora_radio" and "ESP32"
6. Connections between "temperature_sensor" and "ESP32"
7. Connections between "soil_moisture_sensor" and "ESP32"
8. Connections between "temperature_pullup" and "ESP32"
Deploy the firmware
#include <Arduino.h>
#include <SPI.h>
#include <LoRa.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <esp_sleep.h>
// Forward declarations
float readDistanceCm();
int readSoilRaw();
int soilPercentFromRaw(int raw);
float readTemperatureC();
bool sendMeasurement(float waterDepthCm, float distanceCm, float temperatureC,
int soilRaw, int soilPercent);
constexpr int TRIG_PIN = 14;
constexpr int ECHO_PIN = 21;
constexpr int LORA_SCK_PIN = 12;
constexpr int LORA_MISO_PIN = 13;
constexpr int LORA_MOSI_PIN = 11;
constexpr int LORA_NSS_PIN = 10;
constexpr int LORA_RESET_PIN = 38;
constexpr int LORA_DIO0_PIN = 39;
constexpr int DS18B20_PIN = 4;
constexpr int SOIL_MOISTURE_PIN = 1;
constexpr long LORA_FREQUENCY_HZ = 868000000L;
constexpr float SENSOR_TO_SOIL_CM = 30.0f;
constexpr uint64_t SLEEP_MINUTES = 15;
constexpr uint8_t SENSOR_SAMPLES = 5;
constexpr uint8_t SOIL_SAMPLES = 10;
// Calibrate these values with one installed sensor in dry and saturated soil.
constexpr int SOIL_DRY_RAW = 3000;
constexpr int SOIL_WET_RAW = 1400;
OneWire oneWire(DS18B20_PIN);
DallasTemperature ds18b20(&oneWire);
float readDistanceCm() {
float total = 0.0f;
uint8_t validSamples = 0;
for (uint8_t i = 0; i < SENSOR_SAMPLES; ++i) {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(3);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
const unsigned long duration = pulseIn(ECHO_PIN, HIGH, 30000);
if (duration > 0) {
const float distanceCm = duration * 0.0343f / 2.0f;
if (distanceCm >= 2.0f && distanceCm <= 400.0f) {
total += distanceCm;
++validSamples;
}
}
delay(60);
}
return validSamples ? total / validSamples : -1.0f;
}
int readSoilRaw() {
uint32_t total = 0;
for (uint8_t i = 0; i < SOIL_SAMPLES; ++i) {
total += analogRead(SOIL_MOISTURE_PIN);
delay(10);
}
return total / SOIL_SAMPLES;
}
int soilPercentFromRaw(int raw) {
return constrain(map(raw, SOIL_DRY_RAW, SOIL_WET_RAW, 0, 100), 0, 100);
}
float readTemperatureC() {
ds18b20.requestTemperatures();
const float temperatureC = ds18b20.getTempCByIndex(0);
return temperatureC == DEVICE_DISCONNECTED_C ? -127.0f : temperatureC;
}
bool sendMeasurement(float waterDepthCm, float distanceCm, float temperatureC,
int soilRaw, int soilPercent) {
SPI.begin(LORA_SCK_PIN, LORA_MISO_PIN, LORA_MOSI_PIN, LORA_NSS_PIN);
LoRa.setPins(LORA_NSS_PIN, LORA_RESET_PIN, LORA_DIO0_PIN);
if (!LoRa.begin(LORA_FREQUENCY_HZ)) {
Serial.println("LoRa radio not detected");
return false;
}
LoRa.setTxPower(17);
LoRa.beginPacket();
LoRa.print("AWD,depth_cm=");
LoRa.print(waterDepthCm, 1);
LoRa.print(",distance_cm=");
LoRa.print(distanceCm, 1);
LoRa.print(",temp_c=");
LoRa.print(temperatureC, 1);
LoRa.print(",soil_raw=");
LoRa.print(soilRaw);
LoRa.print(",soil_pct=");
LoRa.print(soilPercent);
const bool sent = LoRa.endPacket() == 1;
LoRa.sleep();
return sent;
}
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(SOIL_MOISTURE_PIN, INPUT);
analogSetPinAttenuation(SOIL_MOISTURE_PIN, ADC_11db);
digitalWrite(TRIG_PIN, LOW);
delay(250);
ds18b20.begin();
const float distanceCm = readDistanceCm();
float waterDepthCm = -1.0f;
if (distanceCm >= 0.0f) {
waterDepthCm = SENSOR_TO_SOIL_CM - distanceCm;
if (waterDepthCm < 0.0f) waterDepthCm = 0.0f;
}
const float temperatureC = readTemperatureC();
const int soilRaw = readSoilRaw();
const int soilPercent = soilPercentFromRaw(soilRaw);
const bool sent = sendMeasurement(waterDepthCm, distanceCm, temperatureC,
soilRaw, soilPercent);
Serial.printf("Depth %.1f cm, temperature %.1f C, soil raw %d, soil %d%%, sent %s\n",
waterDepthCm, temperatureC, soilRaw, soilPercent,
sent ? "yes" : "no");
esp_sleep_enable_timer_wakeup(SLEEP_MINUTES * 60ULL * 1000000ULL);
delay(100);
esp_deep_sleep_start();
}
void loop() {
// The ESP32 restarts from setup() after each timed deep-sleep wake-up.
}Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.




