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 | |
| 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 --- | |
| 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 | |
| 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) | |
| 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' --- | |
| 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 | |
| def irrigation_complete(): | |
| return jsonify({"status": "irrigation_complete_request_logged"}) | |
| if __name__ == "__main__": | |
| app.run(host="0.0.0.0", port=7860, debug=True) | |