krushimitravit's picture
Upload 9 files
f574f17 verified
from flask import Flask, render_template, request, jsonify
import joblib
import pandas as pd
import requests
import json
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import io
import base64
from twilio.rest import Client
from twilio.twiml.messaging_response import MessagingResponse
import threading
import os
import time
import secrets
import math
from matplotlib.patches import Wedge, Circle
app = Flask(__name__)
app.secret_key = os.getenv('FLASK_SECRET_KEY', secrets.token_hex(24))
# -------------------------
# 🔑 Persistent irrigation state (file-based)
# -------------------------
STATE_FILE = 'irrigation_state.json'
def load_irrigation_state():
"""Load irrigation state from JSON file"""
if os.path.exists(STATE_FILE):
try:
with open(STATE_FILE, 'r') as f:
return json.load(f)
except Exception as e:
print(f"Error loading state: {e}")
return {}
return {}
def save_irrigation_state(state):
"""Save irrigation state to JSON file"""
try:
with open(STATE_FILE, 'w') as f:
json.dump(state, f, indent=2)
except Exception as e:
print(f"Error saving state: {e}")
irrigation_state = load_irrigation_state()
# -------------------------
# Load ML Model
# -------------------------
svm_poly_model = None
try:
svm_poly_model = joblib.load('svm_poly_model.pkl')
print("SVM model loaded successfully.")
except FileNotFoundError:
print("Error: svm_poly_model.pkl not found. Make sure the model file is in the correct directory.")
except Exception as e:
print(f"Error loading SVM model: {e}")
# -------------------------
# Mappings
# -------------------------
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 & Twilio Config
# -------------------------
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 = os.getenv('FARMER_PHONE_NUMBER', 'whatsapp:+919763059811')
twilio_client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
# -------------------------
# Helper Functions
# -------------------------
def get_weather(city: str):
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'])
else:
print(f"Weather API error: {data.get('message', 'Unknown error')}")
except Exception as e:
print(f"Error fetching weather data for {city}: {e}")
return None, None, None, None
def send_whatsapp_message(to_number, body_text):
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 to {to_number}: {e}")
return None
def trigger_irrigation_complete(crop_type, city, estimated_time_duration, time_unit, user_phone):
message_text = (
f"✅ Irrigation Complete!\n\n"
f"Crop: {crop_type}\n"
f"Location: {city}\n"
f"Duration: {estimated_time_duration:.2f} {time_unit}\n\n"
"The motor has been turned off automatically."
)
send_whatsapp_message(user_phone, message_text)
print(f"Irrigation complete message sent to {user_phone} after {estimated_time_duration:.2f} {time_unit}.")
irrigation_state.pop('current', None) # clear state
save_irrigation_state(irrigation_state)
def fig_to_datauri(fig, fmt='png', dpi=150):
buf = io.BytesIO()
fig.savefig(buf, format=fmt, bbox_inches='tight', dpi=dpi)
buf.seek(0)
data = base64.b64encode(buf.read()).decode('ascii')
plt.close(fig)
return f"data:image/{fmt};base64,{data}"
def draw_gauge(value, max_value, title="", unit=""):
val = max(0.0, min(value, max_value))
ratio = val / float(max_value) if max_value > 0 else 0.0
start_angle, end_angle = 180, 0
angle = start_angle + (end_angle - start_angle) * ratio
fig, ax = plt.subplots(figsize=(5, 3))
ax.set_aspect('equal')
ax.axis('off')
bg_wedge = Wedge((0, 0), 1.0, 180, 0, facecolor='#4CAF50', edgecolor='none', width=0.3)
ax.add_patch(bg_wedge)
fg_wedge = Wedge((0, 0), 1.0, 180, angle, facecolor='#FFFFFF', edgecolor='none', width=0.3)
ax.add_patch(fg_wedge)
num_ticks = 6
for i in range(num_ticks + 1):
t_ratio = i / float(num_ticks)
t_angle = math.radians(180 - (180 * t_ratio))
inner_r, outer_r = 0.7, 0.9
xi_inner, yi_inner = inner_r * math.cos(t_angle), inner_r * math.sin(t_angle)
xi_outer, yi_outer = outer_r * math.cos(t_angle), outer_r * math.sin(t_angle)
ax.plot([xi_inner, xi_outer], [yi_inner, yi_outer], color='#333', lw=1)
lbl_r = 0.55
xl, yl = lbl_r * math.cos(t_angle), lbl_r * math.sin(t_angle)
lbl_val = int(round(max_value * t_ratio))
ax.text(xl, yl, str(lbl_val), ha='center', va='center', fontsize=8, color='#333')
needle_length = 0.85
needle_angle = math.radians(angle)
xn, yn = needle_length * math.cos(needle_angle), needle_length * math.sin(needle_angle)
ax.plot([0, xn], [0, yn], color='red', lw=2)
ax.add_patch(Circle((0, 0), 0.05, color='black'))
ax.text(0, -0.15, f"{title}", ha='center', va='center', fontsize=10, weight='bold')
ax.text(0, -0.32, f"{val:.2f} {unit}", ha='center', va='center', fontsize=10, color='#216e39')
ax.set_xlim(-1.05, 1.05)
ax.set_ylim(-0.35, 1.05)
return fig
# -------------------------
# Routes
# -------------------------
@app.route('/')
def index():
return render_template('index.html')
@app.route('/fetch_weather', methods=['GET'])
def fetch_weather_route():
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
})
else:
return jsonify({'error': 'Could not fetch weather data.'}), 404
return jsonify({'error': 'City parameter missing.'}), 400
@app.route('/predict', methods=['POST'])
def predict():
if svm_poly_model is None:
return "Model not loaded, cannot perform prediction.", 500
crop_type = request.form['crop_type']
soil_type = request.form['soil_type']
city = request.form['city']
try:
motor_capacity = float(request.form['motor_capacity'])
if motor_capacity <= 0:
return "Motor capacity must be a positive number.", 400
except ValueError:
return "Invalid motor capacity.", 400
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 or 'sun' in desc_lower else
'RAINY' if 'rain' in desc_lower or 'drizzle' in desc_lower else
'WINDY' if 'wind' in desc_lower else 'NORMAL')
if crop_type not in crop_type_mapping or soil_type not in soil_type_mapping:
return "Invalid Crop Type or Soil Type.", 400
user_data = pd.DataFrame({
'CROP TYPE': [crop_type_mapping[crop_type]],
'SOIL TYPE': [soil_type_mapping[soil_type]],
'TEMPERATURE': [temperature],
'WEATHER CONDITION': [weather_condition_mapping[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
irrigation_state['current'] = {
"estimated_time_seconds": estimated_time_seconds,
"estimated_time_duration": display_time,
"time_unit": time_unit,
"crop_type": crop_type,
"city": city,
"monitoring_active": False,
"timer_start_time": 0
}
save_irrigation_state(irrigation_state)
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_img = None
time_img = None
try:
max_water_axis = max(100, water_requirement * 1.5)
fig_w = draw_gauge(water_requirement, max_water_axis, title="Water Req (m³/sq.m)")
water_img = fig_to_datauri(fig_w)
except Exception as e:
print(f"Could not generate water gauge: {e}")
try:
max_time_axis = 60 if time_unit == 'seconds' else max(120, display_time * 1.5)
fig_t = draw_gauge(display_time, max_time_axis, title=f"Motor Time ({time_unit})", unit=time_unit)
time_img = fig_to_datauri(fig_t)
except Exception as e:
print(f"Could not generate time gauge: {e}")
return render_template('predict.html',
water_requirement=round(water_requirement, 2),
estimated_time_duration=round(display_time, 2),
time_unit=time_unit,
water_img=water_img,
time_img=time_img)
@app.route('/twilio_reply', methods=['POST'])
def twilio_reply():
# Reload state from file to get latest data
global irrigation_state
irrigation_state = load_irrigation_state()
message_body = request.values.get('Body', '').strip()
print(f"[DEBUG] Received WhatsApp message: '{message_body}'")
print(f"[DEBUG] Current irrigation_state: {irrigation_state}")
resp = MessagingResponse()
irrigation_params = irrigation_state.get('current')
print(f"[DEBUG] irrigation_params: {irrigation_params}")
if message_body == "1":
print(f"[DEBUG] User replied '1' to start motor")
if irrigation_params and 'estimated_time_seconds' in irrigation_params:
print(f"[DEBUG] Valid irrigation params found!")
duration_sec = irrigation_params['estimated_time_seconds']
irrigation_params['monitoring_active'] = True
irrigation_params['timer_start_time'] = time.time()
irrigation_state['current'] = irrigation_params
save_irrigation_state(irrigation_state)
print(f"[DEBUG] State saved, starting timer for {duration_sec} seconds")
timer = threading.Timer(duration_sec, trigger_irrigation_complete, args=[
irrigation_params['crop_type'],
irrigation_params['city'],
irrigation_params['estimated_time_duration'],
irrigation_params['time_unit'],
USER_PHONE_NUMBER
])
timer.daemon = True
timer.start()
resp.message(
f"✅ Motor started! Irrigation will run for {irrigation_params['estimated_time_duration']:.2f} {irrigation_params['time_unit']}."
)
print(f"[DEBUG] Success message sent")
else:
print(f"[DEBUG] ERROR: No valid irrigation params found!")
print(f"[DEBUG] irrigation_params is None: {irrigation_params is None}")
if irrigation_params:
print(f"[DEBUG] 'estimated_time_seconds' in params: {'estimated_time_seconds' in irrigation_params}")
resp.message("❌ Error: No pending irrigation task found.")
elif message_body == "0":
print(f"[DEBUG] User replied '0' to cancel")
resp.message("👍 Motor start has been canceled.")
irrigation_state.pop('current', None)
save_irrigation_state(irrigation_state)
else:
print(f"[DEBUG] Invalid reply received: '{message_body}'")
resp.message("Invalid reply. Please reply '1' to start or '0' to cancel.")
return str(resp)
@app.route('/monitoring_status')
def monitoring_status():
# Reload state from file to get latest data
global irrigation_state
irrigation_state = load_irrigation_state()
irrigation_params = irrigation_state.get('current')
if not irrigation_params:
return jsonify({"monitoring": False})
is_active = irrigation_params.get("monitoring_active", False)
time_remaining = None
if is_active:
start_time = irrigation_params.get("timer_start_time", 0)
total_duration = irrigation_params.get("estimated_time_seconds", 0)
elapsed_time = time.time() - start_time
time_remaining = max(0, total_duration - elapsed_time)
if time_remaining <= 0:
is_active = False
irrigation_state.pop('current', None)
save_irrigation_state(irrigation_state)
print("Monitoring finished, state cleared.")
return jsonify({
"monitoring": is_active,
"time_remaining": time_remaining,
"total_duration": irrigation_params.get("estimated_time_seconds", 0)
})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=7860, debug=True)