divya chandanaAvinash Chandana
Published © GPL3+

Pipe 360 : Urban Pipe Predictive Freeze Alert System

Leverages IoT and ML to predict and alert urban communities of pipe freeze risks, ensuring water safety and infrastructure resilience

AdvancedFull instructions providedOver 1 day378

Things used in this project

Hardware components

AVR IoT Mini Cellular Board
Microchip AVR IoT Mini Cellular Board
Water Flow Sensor
Jumper wires (generic)
Jumper wires (generic)
Breadboard (generic)
Breadboard (generic)
Light Pipe, 15.9 mm
Light Pipe, 15.9 mm
SparkFun Breadboard Power Supply 5V/3.3V
SparkFun Breadboard Power Supply 5V/3.3V

Software apps and online services

Arduino IDE
Arduino IDE
Visual Studio 2017
Microsoft Visual Studio 2017
Jupyter Notebook
Jupyter Notebook

Hand tools and fabrication machines

Premium Female/Male Extension Jumper Wires, 40 x 6" (150mm)
Premium Female/Male Extension Jumper Wires, 40 x 6" (150mm)


Read more



flow diagram

flow diagram



#include <mcp9808.h>        // Include library for MCP9808 temperature sensor
#include <Arduino.h>        // Include Arduino core library
#include <http_client.h>    // Include HTTP client library
#include <ArduinoJson.h>    // Include Arduino JSON library
#include <led_ctrl.h>       // Include LED control library
#include <log.h>            // Include logging library
#include <lte.h>            // Include LTE library

const char* serverPath = "/log";       // Server path for data logging
const char* serverDomain = "<Your Server address>";   // Server domain
const int serverPort = 5000;           // Server port

const byte flowSensorPin = 13;          // Pin connected to flow sensor
const float flowCalibrationFactor = 0.2; // Calibration factor for flow sensor
volatile byte flowPulseCount = 0;       // Counter for flow pulses
float flowRate = 0.0;                    // Calculated flow rate
unsigned long oldTime = 0;               // Variable to store previous time for flow rate calculation

// Interrupt service routine for flow sensor pulse counting
void flowPulseCounter() {

// Function to send data to server
void sendDataToServer(float temperature, float flowRate) {
  // Configure HTTP client with server details
  if (!HttpClient.configure(serverDomain, serverPort, false)) {
    Log.error("Failed to configure HTTP client");

  // Create JSON document to store temperature and flow rate
  JsonDocument doc;
  doc["Temperature"] = temperature;
  doc["Water Flow"] = flowRate;

  // Serialize JSON document to string
  String json;
  serializeJson(doc, json);

  // Send POST request to server with JSON data
  HttpResponse response = HttpClient.post(serverPath, json.c_str());

  // Check response from server
  if (response.status_code > 0) {
    Log.infof("POST - status code: %u, data size: %u\r\n", response.status_code, response.data_size);
  } else {
    Log.error("Error on sending POST");

// Setup function - executed once at the beginning
void setup() {
  Serial.begin(115200);       // Start serial communication
  Log.begin(115200);          // Initialize logging
  LedCtrl.begin();            // Initialize LED control
  LedCtrl.startupCycle();     // Start LED startup cycle

  const int8_t error = Mcp9808.begin(); // Initialize MCP9808 temperature sensor
  if (error) {
    Serial.println("Error: could not start MCP9808 library");

  // Initialize LTE module
  if (!Lte.begin()) {
    Log.error("Failed to connect to the operator");

  // Set flow sensor pin as input with pull-up resistor
  pinMode(flowSensorPin, INPUT_PULLUP);
  // Attach interrupt to flow sensor pin for pulse counting
  attachInterrupt(digitalPinToInterrupt(flowSensorPin), flowPulseCounter, FALLING);

  Log.info("Flow sensor measurement started");

  Log.infof("Connected to operator: %s\r\n", Lte.getOperator().c_str());

// Function to calculate flow rate
float readFlowRate() {
  float flowRateLPM = 0.0;
  // Calculate flow rate if time interval is greater than 900 milliseconds
  if ((millis() - oldTime) > 900) {

    // Calculate flow rate in liters per minute (LPM)
    flowRateLPM = (flowPulseCount / flowCalibrationFactor) / ((millis() - oldTime) / 60000.0);

    // Update old time and reset pulse count
    oldTime = millis();
    flowPulseCount = 0;

    attachInterrupt(digitalPinToInterrupt(flowSensorPin), flowPulseCounter, FALLING);
  return flowRateLPM;

// Loop function - main program loop
void loop() {
  // Read temperature from MCP9808 sensor
  const float temperature = Mcp9808.readTempC();
  // Read flow rate from flow sensor
  flowRate = readFlowRate();
  // Print temperature to serial monitor
  Serial.printf("Temperature (*C): %f\r\n", (double)temperature);
  // Send temperature and flow rate data to server
  sendDataToServer(temperature, flowRate);
  // Delay for 10 minutes (600,000 milliseconds)


from flask import Flask, request, jsonify  # Import Flask and related modules
from datetime import datetime, timedelta   # Import datetime functions
import smtplib                             # Import SMTP library for email sending
from email.mime.text import MIMEText       # Import MIMEText for email message
from email.mime.multipart import MIMEMultipart  # Import MIMEMultipart for email message
import pickle                              # Import pickle for loading ML model
import numpy as np                         # Import numpy for array operations
import json                                # Import json for handling JSON data

app = Flask(__name__)  # Initialize Flask application

model_path = './piper.pkl'  # Path to the ML model file

with open(model_path, 'rb') as file:  # Load the ML model
    model = pickle.load(file)

# Function to send email alerts
def send_email(subject, body):
    sender_email = "<Sender Email>"     # Sender's email address
    receiver_email = "<Receiver Email>" # Receiver's email address
    password = "<App Password>"         # App password for email authentication

    message = MIMEMultipart()           # Create a MIME multipart message
    message["From"] = sender_email      # Set sender's email
    message["To"] = receiver_email      # Set receiver's email
    message["Subject"] = subject        # Set email subject

    # Email body with freeze prevention measures
    detailed_body = """
    [Freeze prevention measures]...

    message.attach(MIMEText(detailed_body, "plain"))  # Attach body to email

        with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:  # Connect to SMTP server
            server.login(sender_email, password)   # Login to sender's email account
            server.sendmail(sender_email, receiver_email, message.as_string())  # Send email
        print("Email sent successfully!")  # Print success message
    except Exception as e:
        print(f"Failed to send email: {e}")  # Print failure message if email sending fails

# Route to receive sensor data and trigger alerts
@app.route('/log', methods=['POST'])
def receive_log():
    global last_alert_time  # Access global variable for last alert time
    data = request.data     # Get request data
    print("Received data:", data)  # Print received data

    decoded_data = data.decode('utf-8')  # Decode received data
    data = json.loads(decoded_data)      # Parse JSON data

    # Extract temperature and water flow data from JSON
        temperature = data['Temperature']
        water_flow = data['Water Flow']
        default_values = {
            # Default environmental values
    except KeyError:
        return jsonify({"status": "error", "message": "Missing data in request"}), 400  # Return error if data is missing

    features = np.array([temperature, water_flow] + list(default_values.values())).reshape(1, -1)  # Prepare features for prediction

    prediction = model.predict(features)  # Make prediction using ML model

    current_time = datetime.now()  # Get current time
    # Check if freeze condition is detected and an alert can be sent
    if prediction[0] == 1 and (last_alert_time is None or current_time - last_alert_time >= timedelta(hours=1)):
        last_alert_time = current_time  # Update last alert time
        send_email("Freeze Alert", "A freeze condition is likely to occur.")  # Send email alert
        response_message = "Freeze alert sent."  # Set response message
        response_message = "No alert sent."  # Set response message

    # Respond to the AVR-IoT Cellular Mini with success status
    return jsonify({"status": "success", "message": response_message}), 200

# Route for testing server availability
@app.route('/', methods=['GET'])
def hello():
    return jsonify({"status": "success", "message": "Server is running."}), 200

if __name__ == '__main__':
    app.run(host='', debug=True, port=5000)  # Run the Flask application


import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import LabelEncoder
import pickle

# Seed for reproducibility

# Number of samples
n_samples = 100000

# Generate simulated data
data = {
    'Temperature (°C)': np.random.uniform(-15, 15, n_samples),  # Between -15°C and 15°C
    'Water Flow (L/min)': np.random.uniform(0, 5, n_samples),  # Between 0 L/min (stagnant) and 5 L/min
    'Pipe Material': np.random.choice(['Copper', 'PVC', 'Steel'], n_samples),  # Types of pipe materials
    'Pipe Insulation': np.random.choice(['None', 'Low', 'High'], n_samples),  # Insulation levels
    'Pipe Diameter (cm)': np.random.uniform(1, 10, n_samples),  # Diameter between 1cm and 10cm
    'Pipe Length (m)': np.random.uniform(1, 100, n_samples),  # Length between 1m and 100m
    'Exposure to Wind': np.random.choice(['Low', 'Moderate', 'High'], n_samples),  # Wind exposure levels
    'Humidity (%)': np.random.uniform(20, 100, n_samples),  # Humidity between 20% and 100%
    'Pipe Location': np.random.choice(['Basement', 'Attic', 'Outside Wall', 'Heated Space'], n_samples),  # Possible locations
    'Air Circulation': np.random.choice(['Poor', 'Moderate', 'Good'], n_samples),  # Air circulation levels

df = pd.DataFrame(data)

def determine_freezing_condition_adjusted(row):
    if row['Temperature (°C)'] <= 0 and row['Water Flow (L/min)'] < 0.5 and \
       row['Pipe Insulation'] in ['None', 'Low'] and \
       row['Pipe Location'] in ['Basement', 'Attic', 'Outside Wall'] and \
       row['Air Circulation'] == 'Poor':
        return 1
        return 0

df['Freeze Condition'] = df.apply(determine_freezing_condition_adjusted, axis=1)

noise_level = 0.05
df['Temperature (°C)'] += np.random.normal(0, noise_level * np.abs(df['Temperature (°C)'].mean()), n_samples)
df['Water Flow (L/min)'] += np.random.normal(0, noise_level * np.abs(df['Water Flow (L/min)'].mean()), n_samples)

flip_percentage = 0.05
indices_to_flip = np.random.choice(df.index, size=int(flip_percentage * len(df)), replace=False)
df.loc[indices_to_flip, 'Freeze Condition'] = 1 - df.loc[indices_to_flip, 'Freeze Condition']

encoder = LabelEncoder()
for col in ['Pipe Material', 'Pipe Insulation', 'Exposure to Wind', 'Pipe Location', 'Air Circulation']:
    df[col] = encoder.fit_transform(df[col])

X = df.drop('Freeze Condition', axis=1)
y = df['Freeze Condition']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

predictions = model.predict(X_test)
accuracy = accuracy_score(y_test, predictions)
print(f'Accuracy: {accuracy}')

with open('piper.pkl', 'wb') as file:
    pickle.dump(model, file)


Entire Working codes ML training, Flask Server, IOT


divya chandana

divya chandana

4 projects • 4 followers
Avinash Chandana

Avinash Chandana

0 projects • 0 followers