RushiMane2003 commited on
Commit
d8eeadb
·
verified ·
1 Parent(s): e4d7927

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +51 -48
app.py CHANGED
@@ -1,4 +1,4 @@
1
- from flask import Flask, render_template, request, jsonify, session
2
  import joblib
3
  import pandas as pd
4
  import requests
@@ -15,11 +15,21 @@ import threading
15
  import os
16
  import time
17
  import secrets
 
 
18
 
19
  app = Flask(__name__)
20
 
21
  app.secret_key = os.getenv('FLASK_SECRET_KEY', secrets.token_hex(24))
22
 
 
 
 
 
 
 
 
 
23
  svm_poly_model = None
24
  try:
25
  svm_poly_model = joblib.load('svm_poly_model.pkl')
@@ -29,6 +39,9 @@ except FileNotFoundError:
29
  except Exception as e:
30
  print(f"Error loading SVM model: {e}")
31
 
 
 
 
32
  crop_type_mapping = {
33
  'BANANA': 0, 'BEAN': 1, 'CABBAGE': 2, 'CITRUS': 3, 'COTTON': 4, 'MAIZE': 5,
34
  'MELON': 6, 'MUSTARD': 7, 'ONION': 8, 'OTHER': 9, 'POTATO': 10, 'RICE': 11,
@@ -37,6 +50,9 @@ crop_type_mapping = {
37
  soil_type_mapping = {'DRY': 0, 'HUMID': 1, 'WET': 2}
38
  weather_condition_mapping = {'NORMAL': 0, 'RAINY': 1, 'SUNNY': 2, 'WINDY': 3}
39
 
 
 
 
40
  WEATHER_API_KEY = os.getenv('WEATHER_API', 'ee75ffd59875aa5ca6c207e594336b30')
41
  TWILIO_ACCOUNT_SID = os.getenv('TWILIO_ACCOUNT_SID', 'ACe45f7038c5338a153d1126ca6d547c84')
42
  TWILIO_AUTH_TOKEN = os.getenv('TWILIO_AUTH_TOKEN', '48b9eea898885ef395d48edc74924340')
@@ -45,7 +61,9 @@ USER_PHONE_NUMBER = os.getenv('FARMER_PHONE_NUMBER', 'whatsapp:+919763059811')
45
 
46
  twilio_client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
47
 
48
-
 
 
49
  def get_weather(city: str):
50
  url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={WEATHER_API_KEY}&units=metric"
51
  try:
@@ -56,11 +74,9 @@ def get_weather(city: str):
56
  return (data['main']['temp'], data['main']['humidity'],
57
  data['weather'][0]['description'], data['main']['pressure'])
58
  else:
59
- print(f"Weather API returned an error: {data.get('message', 'Unknown error')}")
60
- except requests.exceptions.RequestException as e:
61
  print(f"Error fetching weather data for {city}: {e}")
62
- except (KeyError, json.JSONDecodeError) as e:
63
- print(f"Error parsing weather data for {city}: {e}")
64
  return None, None, None, None
65
 
66
 
@@ -88,10 +104,7 @@ def trigger_irrigation_complete(crop_type, city, estimated_time_duration, time_u
88
  )
89
  send_whatsapp_message(user_phone, message_text)
90
  print(f"Irrigation complete message sent to {user_phone} after {estimated_time_duration:.2f} {time_unit}.")
91
-
92
-
93
- import math
94
- from matplotlib.patches import Wedge, Circle
95
 
96
 
97
  def fig_to_datauri(fig, fmt='png', dpi=150):
@@ -115,7 +128,6 @@ def draw_gauge(value, max_value, title="", unit=""):
115
 
116
  bg_wedge = Wedge((0, 0), 1.0, 180, 0, facecolor='#4CAF50', edgecolor='none', width=0.3)
117
  ax.add_patch(bg_wedge)
118
-
119
  fg_wedge = Wedge((0, 0), 1.0, 180, angle, facecolor='#FFFFFF', edgecolor='none', width=0.3)
120
  ax.add_patch(fg_wedge)
121
 
@@ -127,27 +139,26 @@ def draw_gauge(value, max_value, title="", unit=""):
127
  xi_inner, yi_inner = inner_r * math.cos(t_angle), inner_r * math.sin(t_angle)
128
  xi_outer, yi_outer = outer_r * math.cos(t_angle), outer_r * math.sin(t_angle)
129
  ax.plot([xi_inner, xi_outer], [yi_inner, yi_outer], color='#333', lw=1)
130
-
131
  lbl_r = 0.55
132
  xl, yl = lbl_r * math.cos(t_angle), lbl_r * math.sin(t_angle)
133
  lbl_val = int(round(max_value * t_ratio))
134
- ax.text(xl, yl, str(lbl_val), horizontalalignment='center', verticalalignment='center', fontsize=8, color='#333')
135
 
136
  needle_length = 0.85
137
  needle_angle = math.radians(angle)
138
  xn, yn = needle_length * math.cos(needle_angle), needle_length * math.sin(needle_angle)
139
  ax.plot([0, xn], [0, yn], color='red', lw=2)
140
- center = Circle((0, 0), 0.05, color='black')
141
- ax.add_patch(center)
142
-
143
- ax.text(0, -0.15, f"{title}", horizontalalignment='center', verticalalignment='center', fontsize=10, weight='bold')
144
- ax.text(0, -0.32, f"{val:.2f} {unit}", horizontalalignment='center', verticalalignment='center', fontsize=10, color='#216e39')
145
 
 
 
146
  ax.set_xlim(-1.05, 1.05)
147
  ax.set_ylim(-0.35, 1.05)
148
-
149
  return fig
150
 
 
 
 
151
  @app.route('/')
152
  def index():
153
  return render_template('index.html')
@@ -166,8 +177,8 @@ def fetch_weather_route():
166
  'pressure': pressure
167
  })
168
  else:
169
- return jsonify({'error': 'Could not fetch weather data for the specified city.'}), 404
170
- return jsonify({'error': 'City parameter is missing.'}), 400
171
 
172
 
173
  @app.route('/predict', methods=['POST'])
@@ -175,8 +186,6 @@ def predict():
175
  if svm_poly_model is None:
176
  return "Model not loaded, cannot perform prediction.", 500
177
 
178
- session.pop('irrigation_params', None)
179
-
180
  crop_type = request.form['crop_type']
181
  soil_type = request.form['soil_type']
182
  city = request.form['city']
@@ -186,39 +195,37 @@ def predict():
186
  if motor_capacity <= 0:
187
  return "Motor capacity must be a positive number.", 400
188
  except ValueError:
189
- return "Invalid motor capacity. Please enter a number.", 400
190
 
191
  temperature, humidity, description, pressure = get_weather(city)
192
  if temperature is None:
193
- print(f"Warning: Could not fetch live weather for {city}. Using default values.")
194
  temperature, humidity, description, pressure = 32.0, 60, "Not Available", 1012
195
  weather_condition = 'NORMAL'
196
  else:
197
  desc_lower = description.lower()
198
  weather_condition = ('SUNNY' if 'clear' in desc_lower or 'sun' in desc_lower else
199
  'RAINY' if 'rain' in desc_lower or 'drizzle' in desc_lower else
200
- 'WINDY' if 'wind' in desc_lower else
201
- 'NORMAL')
202
 
203
  if crop_type not in crop_type_mapping or soil_type not in soil_type_mapping:
204
  return "Invalid Crop Type or Soil Type.", 400
205
 
206
  user_data = pd.DataFrame({
207
- 'CROP TYPE': [crop_type_mapping.get(crop_type)],
208
- 'SOIL TYPE': [soil_type_mapping.get(soil_type)],
209
  'TEMPERATURE': [temperature],
210
- 'WEATHER CONDITION': [weather_condition_mapping.get(weather_condition)]
211
  })
212
 
213
  water_requirement = svm_poly_model.predict(user_data)[0]
214
- estimated_time_seconds = (water_requirement / motor_capacity) if motor_capacity > 0 else 0
215
 
216
  if estimated_time_seconds < 120:
217
  time_unit, display_time = "seconds", estimated_time_seconds
218
  else:
219
  time_unit, display_time = "minutes", estimated_time_seconds / 60
220
 
221
- session['irrigation_params'] = {
222
  "estimated_time_seconds": estimated_time_seconds,
223
  "estimated_time_duration": display_time,
224
  "time_unit": time_unit,
@@ -244,15 +251,14 @@ def predict():
244
  time_img = None
245
  try:
246
  max_water_axis = max(100, water_requirement * 1.5)
247
- fig_w = draw_gauge(water_requirement, max_water_axis, title="Water Req (m³/sq.m)", unit="")
248
  water_img = fig_to_datauri(fig_w)
249
  except Exception as e:
250
  print(f"Could not generate water gauge: {e}")
251
 
252
  try:
253
  max_time_axis = 60 if time_unit == 'seconds' else max(120, display_time * 1.5)
254
- time_val = round(display_time, 2)
255
- fig_t = draw_gauge(time_val, max_time_axis, title=f"Motor Time ({time_unit})", unit=time_unit)
256
  time_img = fig_to_datauri(fig_t)
257
  except Exception as e:
258
  print(f"Could not generate time gauge: {e}")
@@ -269,16 +275,14 @@ def predict():
269
  def twilio_reply():
270
  message_body = request.values.get('Body', '').strip()
271
  resp = MessagingResponse()
272
-
273
- irrigation_params = session.get('irrigation_params')
274
 
275
  if message_body == "1":
276
  if irrigation_params and 'estimated_time_seconds' in irrigation_params:
277
  duration_sec = irrigation_params['estimated_time_seconds']
278
-
279
  irrigation_params['monitoring_active'] = True
280
  irrigation_params['timer_start_time'] = time.time()
281
- session['irrigation_params'] = irrigation_params
282
 
283
  timer = threading.Timer(duration_sec, trigger_irrigation_complete, args=[
284
  irrigation_params['crop_type'],
@@ -291,12 +295,13 @@ def twilio_reply():
291
  timer.start()
292
 
293
  resp.message(
294
- f"✅ Motor started! Irrigation will run for {irrigation_params['estimated_time_duration']:.2f} {irrigation_params['time_unit']}. The monitoring page is now active.")
 
295
  else:
296
- resp.message("❌ Error: No pending irrigation task found. Please submit a new prediction first.")
297
  elif message_body == "0":
298
  resp.message("👍 Motor start has been canceled.")
299
- session.pop('irrigation_params', None)
300
  else:
301
  resp.message("Invalid reply. Please reply '1' to start or '0' to cancel.")
302
 
@@ -305,13 +310,11 @@ def twilio_reply():
305
 
306
  @app.route('/monitoring_status')
307
  def monitoring_status():
308
- irrigation_params = session.get('irrigation_params')
309
-
310
  if not irrigation_params:
311
  return jsonify({"monitoring": False})
312
 
313
  is_active = irrigation_params.get("monitoring_active", False)
314
-
315
  time_remaining = None
316
  if is_active:
317
  start_time = irrigation_params.get("timer_start_time", 0)
@@ -320,8 +323,8 @@ def monitoring_status():
320
  time_remaining = max(0, total_duration - elapsed_time)
321
  if time_remaining <= 0:
322
  is_active = False
323
- session.pop('irrigation_params', None)
324
- print("Monitoring status detected irrigation completion and cleared session.")
325
 
326
  return jsonify({
327
  "monitoring": is_active,
@@ -331,4 +334,4 @@ def monitoring_status():
331
 
332
 
333
  if __name__ == "__main__":
334
- app.run(host="0.0.0.0", port=7860, debug=True)
 
1
+ from flask import Flask, render_template, request, jsonify
2
  import joblib
3
  import pandas as pd
4
  import requests
 
15
  import os
16
  import time
17
  import secrets
18
+ import math
19
+ from matplotlib.patches import Wedge, Circle
20
 
21
  app = Flask(__name__)
22
 
23
  app.secret_key = os.getenv('FLASK_SECRET_KEY', secrets.token_hex(24))
24
 
25
+ # -------------------------
26
+ # 🔑 Global irrigation state (quick fix)
27
+ # -------------------------
28
+ irrigation_state = {}
29
+
30
+ # -------------------------
31
+ # Load ML Model
32
+ # -------------------------
33
  svm_poly_model = None
34
  try:
35
  svm_poly_model = joblib.load('svm_poly_model.pkl')
 
39
  except Exception as e:
40
  print(f"Error loading SVM model: {e}")
41
 
42
+ # -------------------------
43
+ # Mappings
44
+ # -------------------------
45
  crop_type_mapping = {
46
  'BANANA': 0, 'BEAN': 1, 'CABBAGE': 2, 'CITRUS': 3, 'COTTON': 4, 'MAIZE': 5,
47
  'MELON': 6, 'MUSTARD': 7, 'ONION': 8, 'OTHER': 9, 'POTATO': 10, 'RICE': 11,
 
50
  soil_type_mapping = {'DRY': 0, 'HUMID': 1, 'WET': 2}
51
  weather_condition_mapping = {'NORMAL': 0, 'RAINY': 1, 'SUNNY': 2, 'WINDY': 3}
52
 
53
+ # -------------------------
54
+ # API & Twilio Config
55
+ # -------------------------
56
  WEATHER_API_KEY = os.getenv('WEATHER_API', 'ee75ffd59875aa5ca6c207e594336b30')
57
  TWILIO_ACCOUNT_SID = os.getenv('TWILIO_ACCOUNT_SID', 'ACe45f7038c5338a153d1126ca6d547c84')
58
  TWILIO_AUTH_TOKEN = os.getenv('TWILIO_AUTH_TOKEN', '48b9eea898885ef395d48edc74924340')
 
61
 
62
  twilio_client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
63
 
64
+ # -------------------------
65
+ # Helper Functions
66
+ # -------------------------
67
  def get_weather(city: str):
68
  url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={WEATHER_API_KEY}&units=metric"
69
  try:
 
74
  return (data['main']['temp'], data['main']['humidity'],
75
  data['weather'][0]['description'], data['main']['pressure'])
76
  else:
77
+ print(f"Weather API error: {data.get('message', 'Unknown error')}")
78
+ except Exception as e:
79
  print(f"Error fetching weather data for {city}: {e}")
 
 
80
  return None, None, None, None
81
 
82
 
 
104
  )
105
  send_whatsapp_message(user_phone, message_text)
106
  print(f"Irrigation complete message sent to {user_phone} after {estimated_time_duration:.2f} {time_unit}.")
107
+ irrigation_state.pop('current', None) # clear state
 
 
 
108
 
109
 
110
  def fig_to_datauri(fig, fmt='png', dpi=150):
 
128
 
129
  bg_wedge = Wedge((0, 0), 1.0, 180, 0, facecolor='#4CAF50', edgecolor='none', width=0.3)
130
  ax.add_patch(bg_wedge)
 
131
  fg_wedge = Wedge((0, 0), 1.0, 180, angle, facecolor='#FFFFFF', edgecolor='none', width=0.3)
132
  ax.add_patch(fg_wedge)
133
 
 
139
  xi_inner, yi_inner = inner_r * math.cos(t_angle), inner_r * math.sin(t_angle)
140
  xi_outer, yi_outer = outer_r * math.cos(t_angle), outer_r * math.sin(t_angle)
141
  ax.plot([xi_inner, xi_outer], [yi_inner, yi_outer], color='#333', lw=1)
 
142
  lbl_r = 0.55
143
  xl, yl = lbl_r * math.cos(t_angle), lbl_r * math.sin(t_angle)
144
  lbl_val = int(round(max_value * t_ratio))
145
+ ax.text(xl, yl, str(lbl_val), ha='center', va='center', fontsize=8, color='#333')
146
 
147
  needle_length = 0.85
148
  needle_angle = math.radians(angle)
149
  xn, yn = needle_length * math.cos(needle_angle), needle_length * math.sin(needle_angle)
150
  ax.plot([0, xn], [0, yn], color='red', lw=2)
151
+ ax.add_patch(Circle((0, 0), 0.05, color='black'))
 
 
 
 
152
 
153
+ ax.text(0, -0.15, f"{title}", ha='center', va='center', fontsize=10, weight='bold')
154
+ ax.text(0, -0.32, f"{val:.2f} {unit}", ha='center', va='center', fontsize=10, color='#216e39')
155
  ax.set_xlim(-1.05, 1.05)
156
  ax.set_ylim(-0.35, 1.05)
 
157
  return fig
158
 
159
+ # -------------------------
160
+ # Routes
161
+ # -------------------------
162
  @app.route('/')
163
  def index():
164
  return render_template('index.html')
 
177
  'pressure': pressure
178
  })
179
  else:
180
+ return jsonify({'error': 'Could not fetch weather data.'}), 404
181
+ return jsonify({'error': 'City parameter missing.'}), 400
182
 
183
 
184
  @app.route('/predict', methods=['POST'])
 
186
  if svm_poly_model is None:
187
  return "Model not loaded, cannot perform prediction.", 500
188
 
 
 
189
  crop_type = request.form['crop_type']
190
  soil_type = request.form['soil_type']
191
  city = request.form['city']
 
195
  if motor_capacity <= 0:
196
  return "Motor capacity must be a positive number.", 400
197
  except ValueError:
198
+ return "Invalid motor capacity.", 400
199
 
200
  temperature, humidity, description, pressure = get_weather(city)
201
  if temperature is None:
 
202
  temperature, humidity, description, pressure = 32.0, 60, "Not Available", 1012
203
  weather_condition = 'NORMAL'
204
  else:
205
  desc_lower = description.lower()
206
  weather_condition = ('SUNNY' if 'clear' in desc_lower or 'sun' in desc_lower else
207
  'RAINY' if 'rain' in desc_lower or 'drizzle' in desc_lower else
208
+ 'WINDY' if 'wind' in desc_lower else 'NORMAL')
 
209
 
210
  if crop_type not in crop_type_mapping or soil_type not in soil_type_mapping:
211
  return "Invalid Crop Type or Soil Type.", 400
212
 
213
  user_data = pd.DataFrame({
214
+ 'CROP TYPE': [crop_type_mapping[crop_type]],
215
+ 'SOIL TYPE': [soil_type_mapping[soil_type]],
216
  'TEMPERATURE': [temperature],
217
+ 'WEATHER CONDITION': [weather_condition_mapping[weather_condition]]
218
  })
219
 
220
  water_requirement = svm_poly_model.predict(user_data)[0]
221
+ estimated_time_seconds = water_requirement / motor_capacity if motor_capacity > 0 else 0
222
 
223
  if estimated_time_seconds < 120:
224
  time_unit, display_time = "seconds", estimated_time_seconds
225
  else:
226
  time_unit, display_time = "minutes", estimated_time_seconds / 60
227
 
228
+ irrigation_state['current'] = {
229
  "estimated_time_seconds": estimated_time_seconds,
230
  "estimated_time_duration": display_time,
231
  "time_unit": time_unit,
 
251
  time_img = None
252
  try:
253
  max_water_axis = max(100, water_requirement * 1.5)
254
+ fig_w = draw_gauge(water_requirement, max_water_axis, title="Water Req (m³/sq.m)")
255
  water_img = fig_to_datauri(fig_w)
256
  except Exception as e:
257
  print(f"Could not generate water gauge: {e}")
258
 
259
  try:
260
  max_time_axis = 60 if time_unit == 'seconds' else max(120, display_time * 1.5)
261
+ fig_t = draw_gauge(display_time, max_time_axis, title=f"Motor Time ({time_unit})", unit=time_unit)
 
262
  time_img = fig_to_datauri(fig_t)
263
  except Exception as e:
264
  print(f"Could not generate time gauge: {e}")
 
275
  def twilio_reply():
276
  message_body = request.values.get('Body', '').strip()
277
  resp = MessagingResponse()
278
+ irrigation_params = irrigation_state.get('current')
 
279
 
280
  if message_body == "1":
281
  if irrigation_params and 'estimated_time_seconds' in irrigation_params:
282
  duration_sec = irrigation_params['estimated_time_seconds']
 
283
  irrigation_params['monitoring_active'] = True
284
  irrigation_params['timer_start_time'] = time.time()
285
+ irrigation_state['current'] = irrigation_params
286
 
287
  timer = threading.Timer(duration_sec, trigger_irrigation_complete, args=[
288
  irrigation_params['crop_type'],
 
295
  timer.start()
296
 
297
  resp.message(
298
+ f"✅ Motor started! Irrigation will run for {irrigation_params['estimated_time_duration']:.2f} {irrigation_params['time_unit']}."
299
+ )
300
  else:
301
+ resp.message("❌ Error: No pending irrigation task found.")
302
  elif message_body == "0":
303
  resp.message("👍 Motor start has been canceled.")
304
+ irrigation_state.pop('current', None)
305
  else:
306
  resp.message("Invalid reply. Please reply '1' to start or '0' to cancel.")
307
 
 
310
 
311
  @app.route('/monitoring_status')
312
  def monitoring_status():
313
+ irrigation_params = irrigation_state.get('current')
 
314
  if not irrigation_params:
315
  return jsonify({"monitoring": False})
316
 
317
  is_active = irrigation_params.get("monitoring_active", False)
 
318
  time_remaining = None
319
  if is_active:
320
  start_time = irrigation_params.get("timer_start_time", 0)
 
323
  time_remaining = max(0, total_duration - elapsed_time)
324
  if time_remaining <= 0:
325
  is_active = False
326
+ irrigation_state.pop('current', None)
327
+ print("Monitoring finished, state cleared.")
328
 
329
  return jsonify({
330
  "monitoring": is_active,
 
334
 
335
 
336
  if __name__ == "__main__":
337
+ app.run(host="0.0.0.0", port=7860, debug=True)