from flask import Flask, render_template, request, 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 os app = Flask(__name__) # --- Global variable to store the latest irrigation parameters --- last_irrigation_params = {} # --- Load the pre-trained SVM model --- try: svm_poly_model = joblib.load('svm_poly_model.pkl') except FileNotFoundError: print("Error: svm_poly_model.pkl not found. Make sure the model file is in the correct directory.") svm_poly_model = None # --- Data Mappings for the 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} # --- API Keys and Credentials --- WEATHER_API_KEY = os.getenv('WEATHER_API', 'ee75ffd59875aa5ca6c207e594336b30') TWILIO_ACCOUNT_SID = os.getenv('TWILIO_ACCOUNT_SID', 'AC490e071f8d01bf0df2f03d086c788d87') TWILIO_AUTH_TOKEN = os.getenv('TWILIO_AUTH_TOKEN', '224b23b950ad5a4052aba15893fdf083') TWILIO_PHONE_NUMBER = 'whatsapp:+14155238886' USER_PHONE_NUMBER = 'whatsapp:+917559355282' # The farmer's WhatsApp number # Initialize Twilio Client twilio_client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN) def get_weather(city: str): """Fetches weather data from OpenWeatherMap API.""" url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={WEATHER_API_KEY}&units=metric" try: response = requests.get(url) response.raise_for_status() data = response.json() if data.get('cod') == 200: weather_description = data['weather'][0]['description'] temperature = data['main']['temp'] humidity = data['main']['humidity'] pressure = data['main']['pressure'] return temperature, humidity, weather_description, pressure except requests.exceptions.RequestException as e: print(f"Error fetching weather data: {e}") except (KeyError, json.JSONDecodeError): print("Error parsing weather data.") return None, None, None, None def send_whatsapp_message(to_number, body_text): """General function to send WhatsApp messages via Twilio.""" try: message = twilio_client.messages.create( from_=TWILIO_PHONE_NUMBER, body=body_text, to=to_number ) print(f"Twilio Message SID: {message.sid}") return message.sid except Exception as e: print(f"Error sending WhatsApp message: {e}") return None def trigger_irrigation_complete(): """Function called by the timer when irrigation is finished.""" global last_irrigation_params if not last_irrigation_params: print("No irrigation parameters found for completion message.") return crop_type = last_irrigation_params.get('crop_type', 'N/A') city = last_irrigation_params.get('city', 'N/A') estimated_time = last_irrigation_params.get('estimated_time_duration', 0) time_unit = last_irrigation_params.get('time_unit', 'seconds') message_text = ( f"✅ Irrigation Complete!\n\n" f"Crop: {crop_type}\n" f"Location: {city}\n" f"Duration: {estimated_time:.2f} {time_unit}\n\n" "The motor has been turned off automatically." ) send_whatsapp_message(USER_PHONE_NUMBER, message_text) print(f"Irrigation complete message sent after {estimated_time:.2f} {time_unit}.") # --- Flask Routes --- @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, description, pressure = get_weather(city) if temperature is not None: return jsonify({ 'description': description.capitalize(), 'temperature': temperature, 'humidity': humidity, 'pressure': pressure }) return jsonify(None), 404 @app.route('/predict', methods=['POST']) def predict(): if svm_poly_model is None: return "Model not loaded, cannot perform prediction.", 500 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']) temperature, humidity, description, pressure = get_weather(city) if temperature is None: temperature, humidity, description, pressure = 32.0, 60, "Not Available", 1012 weather_condition = 'NORMAL' else: desc_lower = description.lower() weather_condition = ('SUNNY' if 'clear' in desc_lower else 'RAINY' if 'rain' in desc_lower else 'WINDY' if 'wind' in desc_lower else 'NORMAL') user_data = pd.DataFrame({ 'CROP TYPE': [crop_type_mapping.get(crop_type)], 'SOIL TYPE': [soil_type_mapping.get(soil_type)], 'TEMPERATURE': [temperature], 'WEATHER CONDITION': [weather_condition_mapping.get(weather_condition)] }) water_requirement = svm_poly_model.predict(user_data)[0] estimated_time_seconds = (water_requirement / motor_capacity) if motor_capacity > 0 else 0 if estimated_time_seconds < 120: time_unit = "seconds" display_time = estimated_time_seconds else: time_unit = "minutes" display_time = estimated_time_seconds / 60 last_irrigation_params = { "estimated_time_seconds": estimated_time_seconds, "estimated_time_duration": display_time, "time_unit": time_unit, "crop_type": crop_type, "city": city, } message_to_farmer = ( f"💧 Irrigation Prediction Ready 💧\n\n" f"Crop: *{crop_type}*\n" f"Location: *{city}*\n" f"Weather: {description.capitalize()}, {temperature}°C\n" f"Water Needed: *{water_requirement:.2f} m³/sq.m*\n" f"Est. Motor Time: *{display_time:.2f} {time_unit}*\n\n" "Reply *1* to START the motor.\n" "Reply *0* to CANCEL." ) send_whatsapp_message(USER_PHONE_NUMBER, message_to_farmer) water_gauge = go.Figure(go.Indicator( mode="gauge+number", value=water_requirement, title={"text": "Water Req (m³/sq.m)"}, gauge={"axis": {"range": [None, 100]}, "bar": {"color": "royalblue"}} )) time_gauge = go.Figure(go.Indicator( mode="gauge+number", value=round(display_time, 2), title={"text": f"Time ({time_unit})"}, gauge={"axis": {"range": [None, 60 if time_unit == 'seconds' else 120]}, "bar": {"color": "green"}} )) return render_template('predict.html', water_requirement=round(water_requirement, 2), estimated_time_duration=round(display_time, 2), time_unit=time_unit, water_gauge=water_gauge.to_html(full_html=False), time_gauge=time_gauge.to_html(full_html=False), crop_type=crop_type, city=city) @app.route('/twilio_reply', methods=['POST']) def twilio_reply(): global last_irrigation_params message_body = request.values.get('Body', '').strip() resp = MessagingResponse() if message_body == "1": if last_irrigation_params and 'estimated_time_seconds' in last_irrigation_params: duration_sec = last_irrigation_params['estimated_time_seconds'] # Start a background timer thread timer = threading.Timer(duration_sec, trigger_irrigation_complete) timer.daemon = True timer.start() resp.message(f"✅ Motor started! Irrigation will run for {last_irrigation_params['estimated_time_duration']:.2f} {last_irrigation_params['time_unit']} and will stop automatically.") else: resp.message("❌ Error: No pending irrigation task found. Please submit a new prediction first.") elif message_body == "0": resp.message("👍 Motor start has been canceled.") last_irrigation_params = {} # Clear params else: resp.message("Invalid reply. Please reply '1' to start the motor or '0' to cancel.") return str(resp) # --- UPDATED: start_motor now performs the same server-side start as twilio '1' --- @app.route('/start_motor', methods=['POST']) def start_motor(): """Called from front-end. Starts server-side timer (same effect as twilio '1').""" global last_irrigation_params if last_irrigation_params and 'estimated_time_seconds' in last_irrigation_params: duration_sec = last_irrigation_params['estimated_time_seconds'] # Start server-side timer which will call trigger_irrigation_complete() timer = threading.Timer(duration_sec, trigger_irrigation_complete) timer.daemon = True timer.start() # Send confirmation to farmer via WhatsApp (optional but helpful) send_whatsapp_message(USER_PHONE_NUMBER, f"✅ Motor started (via UI). It will run for {last_irrigation_params['estimated_time_duration']:.2f} {last_irrigation_params['time_unit']} and stop automatically.") return jsonify({ "status": "motor_started", "estimated_time_duration": last_irrigation_params['estimated_time_duration'], "time_unit": last_irrigation_params['time_unit'] }) else: return jsonify({"status": "no_pending_task"}), 400 @app.route('/irrigation_complete', methods=['POST']) def irrigation_complete(): return jsonify({"status": "irrigation_complete_request_logged"}) if __name__ == "__main__": app.run(host="0.0.0.0", port=7860, debug=True)