Harun YenisanMehmet Emin Uğur
Published © MIT

River/Dam Water Level Monitoring with Cellular Flood Warning

River/Dam Water Level Monitoring with Cellular Flood Warning System

IntermediateWork in progress24 hours84
River/Dam Water Level Monitoring with Cellular Flood Warning

Things used in this project

Hardware components

RAKwireless ○ RAK3372
×1
RAKwireless ○ RAK19003
×1
RAKwireless RAK12005
×1
RAKwireless RAK12039
×1
RAKwireless Battery Connector
×1
RAKwireless Solar Panel
×1
Hologram Global IoT SIM Card
Hologram Global IoT SIM Card
×1

Software apps and online services

Arduino IDE
Arduino IDE
AWS SNS
Amazon Web Services AWS SNS
Grafana

Hand tools and fabrication machines

Multitool, Screwdriver
Multitool, Screwdriver

Story

Read more

Code

code

Arduino
River/Dam Water Level Monitoring with Cellular Flood Warning System
#include <WisBlock-api.h>      // For RAK WisBlock APIs
#include <TinyGPS++.h>         // For RAK12039 (GNSS) sensor
#include <PubSubClient.h>      // For MQTT communication (with AWS IoT Core)

// For RAK12005 Water Level Sensor, assuming analog reading
// If RAK12005 has I2C or another interface, the relevant library/code should be included.
#define WATER_LEVEL_SENSOR_PIN WB_A0 // Example analog pin connected to water level sensor on WisBlock A0

// SIM card and cellular network settings
#define TINY_GSM_MODEM_RAK3172 // Declares modem model for TinyGSM (RAK3372/BG77)
#include <TinyGsmClient.h>     // General header for TinyGSM library
#include <TinyGsmClientSIM7600.h> // Header for SIM7600/BG77 modem family used by RAK3372

// AWS IoT Core Settings
// These values must be obtained from your AWS IoT console and device registration.
const char* AWS_IOT_ENDPOINT = "YOUR_AWS_IOT_ENDPOINT.iot.your-region.amazonaws.com"; // <<< CHANGE THIS
const char* AWS_IOT_CLIENT_ID = "YourWaterLevelMonitorDevice"; // <<< CHANGE THIS (Unique device ID)
const char* AWS_IOT_TOPIC = "water_level/data"; // <<< CHANGE THIS (MQTT topic to send data to)

// GPRS connection information
const char apn[]  = "YOUR_APN";       // <<< CHANGE THIS (Your mobile operator's APN)
const char gprsUser[] = "";           // APN username (usually left blank)
const char gprsPass[] = "";           // APN password (usually left blank)

// MQTT TLS/SSL Certificates
// In a production environment, securely uploading and managing these certificates on the device is critical.
// Refer to RAK3372 documentation for methods like modem AT commands or using a file system like SPIFFS.
// In this example, certificates are placeholders. You need to upload them in a real project.
// const char aws_iot_root_ca[] = "-----BEGIN CERTIFICATE-----\n..."
// const char device_cert[] = "-----BEGIN CERTIFICATE-----\n..."
// const char device_private_key[] = "-----BEGIN RSA PRIVATE KEY-----\n..."

// Hardware Objects
TinyGsm modem(Serial1); // Serial port for RAK3372 modem (UART1)
TinyGsmClient client(modem); // TinyGSM client (TCP connection via modem)
PubSubClient mqttClient(client); // MQTT client

// Serial port for RAK12039 GPS
#define GPS_SERIAL Serial2 // Serial port connected to RAK12039 (UART2)
TinyGPSPlus gps; // TinyGPS++ object

// Timer Variables
long lastSendTime = 0;
const long sendInterval = 300000; // Data transmission interval: 5 minutes (5 * 60 * 1000 ms)

void setup() {
  // Start serial communication (for debug messages)
  Serial.begin(115200);
  while (!Serial); // Wait for serial port to be ready
  Serial.println("Water Level and Location Monitoring Starting...");

  // Set water level sensor pin as input
  pinMode(WATER_LEVEL_SENSOR_PIN, INPUT);

  // Initialize RAK3372 modem
  Serial1.begin(115200); // Set UART1 speed for RAK3372
  delay(100); // Small delay for modem to start
  
  Serial.println("Modem initializing...");
  modem.restart(); // Restart modem
  String modemInfo = modem.getModemInfo(); // Get modem information
  Serial.print("Modem Info: ");
  Serial.println(modemInfo);

  // Try to connect to GPRS network
  Serial.print("Connecting to GPRS network...");
  if (!modem.gprsConnect(apn, gprsUser, gprsPass)) {
    Serial.println("GPRS connection failed! Please check APN settings and SIM card.");
    while (true); // Stay in infinite loop if connection fails
  }
  Serial.println("GPRS connection successful!");

  // Initialize GPS module
  GPS_SERIAL.begin(9600); // Set serial port speed for RAK12039 GPS (typically 9600 baud)

  // Set MQTT Callback function (to handle client-to-cloud messages)
  mqttClient.setCallback(mqttCallback);

  // Set MQTT server connection details (AWS IoT Core)
  mqttClient.setServer(AWS_IOT_ENDPOINT, 8883); // TLS port for AWS IoT Core is typically 8883

  // Set MQTT SSL/TLS Certificates (if used)
  // This is crucial for production systems. These lines need to be enabled and certificates provided.
  // client.setCACert(aws_iot_root_ca);
  // client.setCertificate(device_cert);
  // client.setPrivateKey(device_private_key);

  connectMqtt(); // Try to connect to MQTT broker

  // Initialize timer
  lastSendTime = millis();
}

void loop() {
  modem.maintainConnections(); // Keep GPRS connection active
  
  // Run MQTT client loop (to send and receive messages)
  mqttClient.loop();

  // Read GPS data and encode into TinyGPS++ object
  while (GPS_SERIAL.available() > 0) {
    gps.encode(GPS_SERIAL.read());
  }

  // Check if it's time to send data
  if (millis() - lastSendTime > sendInterval) {
    sendData();       // Send sensor and location data
    lastSendTime = millis(); // Reset timer
  }
}

// Function to manage connecting to MQTT broker
void connectMqtt() {
  Serial.print("Connecting to MQTT...");
  // Stay in loop until connection is successful
  while (!mqttClient.connected()) {
    // Connect to MQTT with device ID, username, and password (SAS token)
    if (mqttClient.connect(AWS_IOT_CLIENT_ID)) {
      Serial.println("connected.");
      // If you expect to receive Cloud-to-Device (C2D) messages, you can subscribe to the relevant topic here.
      // mqttClient.subscribe("water_level/commands"); 
    } else {
      Serial.print("failed, rc=");
      Serial.print(mqttClient.state()); // Print error code
      Serial.println(" trying again in 5 seconds");
      delay(5000); // Wait before retrying
    }
  }
}

// Callback function to process incoming messages (downlink) via MQTT
void mqttCallback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message received - Topic: ");
  Serial.println(topic);
  Serial.print("Message: ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
  // You can add code here to make the device react to incoming commands.
}

// Function to collect water level and GPS data and send it to AWS IoT Core
void sendData() {
  // Check MQTT connection, reconnect if disconnected
  if (!mqttClient.connected()) {
    connectMqtt();
  }

  // Read raw analog value from water level sensor
  int rawWaterLevel = analogRead(WATER_LEVEL_SENSOR_PIN);
  // Convert raw value to water level in centimeters
  // This conversion should be adjusted based on your sensor and setup.
  // For example, 0-4095 (12-bit ADC) -> 0-100 cm example
  float waterLevelCm = map(rawWaterLevel, 0, 4095, 0, 100); 

  Serial.printf("Raw Water Level: %d, Water Level: %.2f cm\n", rawWaterLevel, waterLevelCm);

  // Get GPS coordinates
  double latitude = gps.location.isValid() ? gps.location.lat() : 0.0; // Latitude if valid location, else 0.0
  double longitude = gps.location.isValid() ? gps.location.lng() : 0.0; // Longitude if valid location, else 0.0

  Serial.printf("GPS Location: %.6f, %.6f\n", latitude, longitude);

  // Create a JSON formatted payload
  // This format can be easily processed by AWS IoT Core.
  String jsonPayload = "{\"water_level_cm\":";
  jsonPayload += String(waterLevelCm, 2); // 2 decimal places
  jsonPayload += ",\"latitude\":";
  jsonPayload += String(latitude, 6);   // 6 decimal places
  jsonPayload += ",\"longitude\":";
  jsonPayload += String(longitude, 6);  // 6 decimal places
  jsonPayload += "}";

  Serial.print("Sending Data: ");
  Serial.println(jsonPayload);

  // Publish the created JSON payload to the relevant topic in AWS IoT Core
  if (mqttClient.publish(AWS_IOT_TOPIC, jsonPayload.c_str())) {
    Serial.println("Data successfully sent to AWS IoT Core.");
  } else {
    Serial.println("Data sending failed!");
  }
}

Credits

Harun Yenisan
8 projects • 8 followers
Electronic engineer, working as an IT teacher at the Ministry of National Education.
Mehmet Emin Uğur
9 projects • 10 followers
Computer engineer, working as an IT teacher at the Ministry of National Education.

Comments