|
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__) |
|
|
|
|
|
last_irrigation_params = {} |
|
|
|
|
|
svm_poly_model = joblib.load('svm_poly_model.pkl') |
|
|
|
|
|
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} |
|
|
|
|
|
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'] |
|
|
|
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') |
|
|
|
|
|
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']) |
|
|
|
|
|
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') |
|
|
|
|
|
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] |
|
|
|
|
|
user_data = pd.DataFrame({ |
|
'CROP TYPE': [crop_type_encoded], |
|
'SOIL TYPE': [soil_type_encoded], |
|
'TEMPERATURE': [temperature], |
|
'WEATHER CONDITION': [weather_condition_encoded] |
|
}) |
|
|
|
|
|
water_requirement = svm_poly_model.predict(user_data)[0] |
|
|
|
|
|
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 |
|
|
|
|
|
formatted_estimated_time = round(estimated_time_duration, 2) |
|
|
|
|
|
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(crop_type, weather_description, temperature, humidity, pressure, motor_capacity, |
|
round(water_requirement, 2), formatted_estimated_time, time_unit, city) |
|
|
|
|
|
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()}<br>" \ |
|
f"Temperature: {temperature}°C<br>" \ |
|
f"Humidity: {humidity}%<br>" \ |
|
f"Pressure: {pressure} hPa<br>" |
|
|
|
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") |
|
|
|
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') |
|
|
|
|
|
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'") |
|
|
|
|
|
t = threading.Timer(estimated_time_seconds, trigger_irrigation_complete) |
|
t.daemon = True |
|
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) |