Spaces:
Sleeping
Sleeping
| 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 | |
| import time | |
| app = Flask(__name__) | |
| # --- Global variable to store the latest irrigation parameters and state --- | |
| 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', 'ACe45f7038c5338a153d1126ca6d547c84') | |
| TWILIO_AUTH_TOKEN = os.getenv('TWILIO_AUTH_TOKEN', '48b9eea898885ef395d48edc74924340') | |
| TWILIO_PHONE_NUMBER = 'whatsapp:+14155238886' | |
| USER_PHONE_NUMBER = 'whatsapp:+919763059811' # 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: | |
| return (data['main']['temp'], data['main']['humidity'], | |
| data['weather'][0]['description'], data['main']['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}.") | |
| # Reset state after completion | |
| last_irrigation_params = {} | |
| # --- Flask Routes --- | |
| def index(): | |
| return render_template('index.html') | |
| 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 | |
| def predict(): | |
| if svm_poly_model is None: | |
| return "Model not loaded, cannot perform prediction.", 500 | |
| global last_irrigation_params | |
| # Clear previous session data | |
| 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, display_time = "seconds", estimated_time_seconds | |
| else: | |
| time_unit, display_time = "minutes", estimated_time_seconds / 60 | |
| # Store all necessary data for the session | |
| last_irrigation_params = { | |
| "estimated_time_seconds": estimated_time_seconds, | |
| "estimated_time_duration": display_time, | |
| "time_unit": time_unit, | |
| "crop_type": crop_type, | |
| "city": city, | |
| "monitoring_active": False, # NEW: State flag for the UI | |
| "timer_start_time": 0 # To calculate remaining time | |
| } | |
| 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 and monitoring.\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)) | |
| 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'] | |
| # Set the monitoring flag to True | |
| last_irrigation_params['monitoring_active'] = True | |
| last_irrigation_params['timer_start_time'] = time.time() | |
| # Start the background timer to run the completion function | |
| 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']}. The monitoring page is now active.") | |
| 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 or '0' to cancel.") | |
| return str(resp) | |
| # --- NEW ENDPOINT FOR UI POLLING --- | |
| def monitoring_status(): | |
| """Provides the current monitoring status to the front-end.""" | |
| global last_irrigation_params | |
| if not last_irrigation_params: | |
| return jsonify({"monitoring": False}) | |
| is_active = last_irrigation_params.get("monitoring_active", False) | |
| # Calculate time remaining if active | |
| time_remaining = None | |
| if is_active: | |
| start_time = last_irrigation_params.get("timer_start_time", 0) | |
| total_duration = last_irrigation_params.get("estimated_time_seconds", 0) | |
| elapsed_time = time.time() - start_time | |
| time_remaining = max(0, total_duration - elapsed_time) | |
| return jsonify({ | |
| "monitoring": is_active, | |
| "time_remaining": time_remaining | |
| }) | |
| if __name__ == "__main__": | |
| app.run(host="0.0.0.0", port=7860, debug=True) |