Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
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 day439

Things used in this project

Hardware components

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

Software apps and online services

Arduino IDE
Arduino IDE
Visual Studio 2017
Microsoft Visual Studio 2017
app.diagrams
macOS
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)

Story

Read more

Schematics

img_0071_q46cQfxGll.jpeg

flow diagram

flow diagram

Code

pipe.ino

C/C++
#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() {
  flowPulseCount++;
}

// 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");
    return;
  }

  // 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");
    return;
  }

  // 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) {
    detachInterrupt(digitalPinToInterrupt(flowSensorPin));

    // 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)
  delay(600000);
}

server.py

Python
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]...
    """.format(body)

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

    try:
        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
    try:
        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
    else:
        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='0.0.0.0', debug=True, port=5000)  # Run the Flask application

train.py

Python
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
np.random.seed(42)

# 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
    else:
        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)

AvrIOTCellularMini

Entire Working codes ML training, Flask Server, IOT

Credits

divya chandana

divya chandana

4 projects • 5 followers
https://www.linkedin.com/in/divya-chandana-75b27438
Avinash Chandana

Avinash Chandana

1 project • 0 followers

Comments