from flask import Flask, render_template, request, redirect, url_for, jsonify import joblib import pandas as pd import requests import json import plotly.graph_objects as go from twilio.rest import Client from twilio.twiml.messaging_response import MessagingResponse import threading import time import os app = Flask(__name__) # Global variable to store the latest irrigation parameters last_irrigation_params = {} # Load the SVM model svm_poly_model = joblib.load('svm_poly_model.pkl') # Label encoding mappings for SVM model crop_type_mapping = { 'BANANA': 0, 'BEAN': 1, 'CABBAGE': 2, 'CITRUS': 3, 'COTTON': 4, 'MAIZE': 5, 'MELON': 6, 'MUSTARD': 7, 'ONION': 8, 'OTHER': 9, 'POTATO': 10, 'RICE': 11, 'SOYABEAN': 12, 'SUGARCANE': 13, 'TOMATO': 14, 'WHEAT': 15 } soil_type_mapping = {'DRY': 0, 'HUMID': 1, 'WET': 2} weather_condition_mapping = {'NORMAL': 0, 'RAINY': 1, 'SUNNY': 2, 'WINDY': 3} # Provided weather API key (hardcoded) WEATHER_API_KEY = os.getenv('WEATHER_API') def get_weather(city): url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={WEATHER_API_KEY}" try: response = requests.get(url) response.raise_for_status() except requests.exceptions.HTTPError: return None, None, None, None try: data = json.loads(response.text) if data['cod'] != 200: return None, None, None, None except json.JSONDecodeError: return None, None, None, None weather_description = data['weather'][0]['description'] temperature = data['main']['temp'] humidity = data['main']['humidity'] pressure = data['main']['pressure'] # Convert temperature from Kelvin to Celsius temperature = round(temperature - 273.15, 2) return temperature, humidity, weather_description, pressure def send_whatsapp_message(crop_type, weather_description, temperature, humidity, pressure, motor_capacity, water_requirement, estimated_time, time_unit, city): """ Send a WhatsApp message with irrigation details. """ message_text = ( f"Crop Name: {crop_type}\n" f"Weather: {weather_description.capitalize()}, Temp: {temperature}°C, Humidity: {humidity}%, Pressure: {pressure} hPa\n" f"Motor Discharge Rate: {motor_capacity} liters/sec\n" f"Water Requirement for Irrigation: {water_requirement} m³/sq.m\n" f"Total Motor On-Time: {estimated_time} {time_unit}\n" f"Location: {city}\n\n" "Do you want to start the motor?\nReply 1 to start, reply 0 to not start." ) account_sid = 'AC490e071f8d01bf0df2f03d086c788d87' auth_token = '224b23b950ad5a4052aba15893fdf083' client = Client(account_sid, auth_token) msg = client.messages.create( from_='whatsapp:+14155238886', body=message_text, to='whatsapp:+917559355282' ) print("Twilio Message SID:", msg.sid) def send_irrigation_complete_message(crop_type, city, irrigation_time, time_unit): """ Send a WhatsApp message indicating that irrigation is over. """ message_text = ( f"Crop Name: {crop_type}\n" f"Location: {city}\n" f"Irrigation completed successfully.\n" f"Total irrigation time: {irrigation_time} {time_unit}\n\n" "Irrigation time is over and pump is auto offed." ) account_sid = 'AC490e071f8d01bf0df2f03d086c788d87' auth_token = '224b23b950ad5a4052aba15893fdf083' client = Client(account_sid, auth_token) msg = client.messages.create( from_='whatsapp:+14155238886', body=message_text, to='whatsapp:+917559355282' ) print("Irrigation Complete Message SID:", msg.sid) def trigger_irrigation_complete(): """ Function called by the timer to send the irrigation complete message. """ global last_irrigation_params crop_type = last_irrigation_params.get('crop_type', 'Unknown Crop') city = last_irrigation_params.get('city', 'Unknown Location') estimated_time = last_irrigation_params.get('estimated_time_duration', 0) time_unit = last_irrigation_params.get('time_unit', 'seconds') # Format time for display formatted_time = round(estimated_time, 2) send_irrigation_complete_message(crop_type, city, formatted_time, time_unit) print(f"Irrigation complete message sent automatically after {formatted_time} {time_unit}.") @app.route('/') def index(): return render_template('index.html') @app.route('/fetch_weather', methods=['GET']) def fetch_weather(): city = request.args.get('city') if city: temperature, humidity, weather_description, pressure = get_weather(city) if temperature is not None and weather_description is not None: return json.dumps({ 'description': weather_description.capitalize(), 'temperature': temperature, 'humidity': humidity, 'pressure': pressure }) return json.dumps(None) @app.route('/predict', methods=['POST']) def predict(): global last_irrigation_params crop_type = request.form['crop_type'] soil_type = request.form['soil_type'] city = request.form['city'] motor_capacity = float(request.form['motor_capacity']) # Get weather data temperature, humidity, weather_description, pressure = get_weather(city) if temperature is None or weather_description is None: auto_weather_condition = 'NORMAL' temperature = 32.0 weather_description = "Not Available" else: auto_weather_condition = ('SUNNY' if 'clear' in weather_description.lower() else 'RAINY' if 'rain' in weather_description.lower() else 'WINDY' if 'wind' in weather_description.lower() else 'NORMAL') # Encode inputs for the model crop_type_encoded = crop_type_mapping[crop_type] soil_type_encoded = soil_type_mapping[soil_type] weather_condition_encoded = weather_condition_mapping[auto_weather_condition] # Create DataFrame for model input user_data = pd.DataFrame({ 'CROP TYPE': [crop_type_encoded], 'SOIL TYPE': [soil_type_encoded], 'TEMPERATURE': [temperature], 'WEATHER CONDITION': [weather_condition_encoded] }) # Predict water requirement water_requirement = svm_poly_model.predict(user_data)[0] # Calculate estimated time duration (in seconds or minutes) estimated_time_duration = water_requirement / motor_capacity if motor_capacity > 0 else 0 time_unit = "seconds" if estimated_time_duration < 60 else "minutes" if time_unit == "minutes": estimated_time_duration = estimated_time_duration / 60 # Round to 2 decimal places for display formatted_estimated_time = round(estimated_time_duration, 2) # Save irrigation parameters globally for later use when the farmer replies last_irrigation_params = { "estimated_time_duration": estimated_time_duration, "time_unit": time_unit, "crop_type": crop_type, "city": city, "water_requirement": water_requirement } # Send WhatsApp message with irrigation details send_whatsapp_message(crop_type, weather_description, temperature, humidity, pressure, motor_capacity, round(water_requirement, 2), formatted_estimated_time, time_unit, city) # Create gauge charts for display water_gauge = go.Figure(go.Indicator( mode="gauge+number", value=water_requirement, title={"text": "Water Requirement (m³/sq.m)"}, gauge={ "axis": {"range": [None, 100]}, "bar": {"color": "blue"}, "steps": [ {"range": [0, 20], "color": "lightgray"}, {"range": [20, 50], "color": "yellow"}, {"range": [50, 100], "color": "green"} ] } )) max_range = 60 if time_unit == "seconds" else 120 time_gauge = go.Figure(go.Indicator( mode="gauge+number", value=formatted_estimated_time, title={"text": f"Estimated Time ({time_unit.capitalize()})"}, gauge={ "axis": {"range": [None, max_range]}, "bar": {"color": "blue"}, "steps": [ {"range": [0, max_range * 0.33], "color": "lightgray"}, {"range": [max_range * 0.33, max_range * 0.66], "color": "yellow"}, {"range": [max_range * 0.66, max_range], "color": "green"} ] } )) water_gauge_path = water_gauge.to_html(full_html=False) time_gauge_path = time_gauge.to_html(full_html=False) weather_info = f"Weather in {city}: {weather_description.capitalize()}
" \ f"Temperature: {temperature}°C
" \ f"Humidity: {humidity}%
" \ f"Pressure: {pressure} hPa
" return render_template('predict.html', water_requirement=round(water_requirement, 2), estimated_time_duration=formatted_estimated_time, time_unit=time_unit, weather_info=weather_info, water_gauge=water_gauge_path, time_gauge=time_gauge_path, crop_type=crop_type, city=city) @app.route('/start_motor', methods=['POST']) def start_motor(): """ This route is called from the web interface but should not start the timer. The timer is started only from WhatsApp when the user replies with '1'. """ estimated_time_duration = float(request.json.get('estimated_time_duration')) time_unit = request.json.get('time_unit', 'seconds') print(f"Web interface: Start motor button clicked - NOT starting timer") # NO TIMER IS STARTED HERE - just return a status return jsonify({"status": "motor_request_received"}) @app.route('/twilio_reply', methods=['POST']) def twilio_reply(): """ Handle incoming WhatsApp replies from Twilio. THIS is where the timer should be started when the user replies with '1'. """ global last_irrigation_params message_body = request.values.get('Body', '').strip() resp = MessagingResponse() print(f"Received WhatsApp reply: '{message_body}'") if message_body == "1": if last_irrigation_params: estimated_time = last_irrigation_params.get('estimated_time_duration') time_unit = last_irrigation_params.get('time_unit', 'seconds') # Convert to seconds for the timer if time_unit.lower() == "minutes": estimated_time_seconds = estimated_time * 60 else: estimated_time_seconds = estimated_time print(f"Starting timer for {estimated_time_seconds} seconds due to WhatsApp reply '1'") # THIS IS WHERE THE TIMER IS STARTED t = threading.Timer(estimated_time_seconds, trigger_irrigation_complete) t.daemon = True # Make thread exit when main thread exits t.start() resp.message( f"Motor started as per your confirmation. Irrigation will run for {round(estimated_time, 2)} {time_unit} and will complete automatically.") else: resp.message("No irrigation parameters found. Cannot start motor.") elif message_body == "0": resp.message("Motor start canceled as per your reply.") else: resp.message("Invalid reply. Please reply 1 to start the motor or 0 to cancel.") return str(resp) @app.route('/irrigation_complete', methods=['POST']) def irrigation_complete(): """ Manual endpoint for irrigation completion - not used for automatic completion. Automatic completion happens via the timer in twilio_reply. """ crop_type = request.json.get('crop_type', 'Unknown Crop') city = request.json.get('city', 'Unknown Location') estimated_time = last_irrigation_params.get('estimated_time_duration', 0) time_unit = last_irrigation_params.get('time_unit', 'seconds') send_irrigation_complete_message(crop_type, city, round(estimated_time, 2), time_unit) return jsonify({"status": "irrigation_complete_message_sent"}) if __name__ == "__main__": app.run(host="0.0.0.0", port=7860)