krushimitravit commited on
Commit
63a00a1
·
verified ·
1 Parent(s): 4e1d7b2

Upload 11 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ static/alarn_tune.mp3 filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.9-slim
3
+
4
+ # Set the working directory
5
+ WORKDIR /app
6
+
7
+ # Copy the application files to the container
8
+ COPY . /app
9
+
10
+ # Install Python dependencies
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Expose the Flask port (Hugging Face Spaces uses port 7860 by default)
14
+ EXPOSE 7860
15
+
16
+ # Command to run the Flask app
17
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, redirect, url_for, jsonify
2
+ from tensorflow.keras.models import load_model
3
+ import numpy as np
4
+ import joblib
5
+ import pandas as pd
6
+ import io
7
+ import requests
8
+ import os
9
+ import threading
10
+ import time
11
+ from PIL import Image # Import for image processing
12
+
13
+ app = Flask(__name__)
14
+
15
+ # Load models
16
+ pump_model = joblib.load('pump_status_dt_model.pkl')
17
+
18
+ # Try to load soil model, but continue if it fails
19
+ try:
20
+ soil_model = load_model('soil_classification_model.h5')
21
+ print("✓ Soil classification model loaded successfully")
22
+ except Exception as e:
23
+ soil_model = None
24
+ print(f"⚠ Warning: Could not load soil classification model: {e}")
25
+ print(" The app will run without soil image classification feature.")
26
+
27
+ # Dictionaries for crop types, regions, etc.
28
+ crop_types = {'BANANA': 0, 'BEAN': 1, 'CABBAGE': 2, 'CITRUS': 3, 'COTTON': 4,
29
+ 'MAIZE': 5, 'MELON': 6, 'MUSTARD': 7, 'ONION': 8, 'OTHER': 9,
30
+ 'POTATO': 10, 'RICE': 11, 'SOYABEAN': 12, 'SUGARCANE': 13,
31
+ 'TOMATO': 14, 'WHEAT': 15}
32
+
33
+ soil_types = {'DRY': 0, 'HUMID': 1, 'WET': 2}
34
+ regions = {'DESERT': 0, 'HUMID': 1, 'SEMI ARID': 2, 'SEMI HUMID': 3}
35
+ weather_conditions = {'SUNNY': 0, 'RAINY': 1, 'WINDY': 2, 'NORMAL': 3}
36
+ irrigation_types = {'Drip Irrigation': 0, 'Manual Irrigation': 1,
37
+ 'Sprinkler Irrigation': 2, 'Subsurface Irrigation': 3,
38
+ 'Surface Irrigation': 4}
39
+
40
+ soil_labels = {1: 'Black Soil', 2: 'Clay Soil', 0: 'Alluvial Soil', 3: 'Red Soil'}
41
+
42
+ # Global variables
43
+ soil_moisture_data = []
44
+ pump_status = "Off"
45
+ previous_pump_status = "Off"
46
+ graph_data = []
47
+
48
+
49
+ # Function to fetch weather data
50
+ def get_weather(city):
51
+ api_key = os.getenv('WEATHER_API')
52
+ url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}&units=metric"
53
+ try:
54
+ response = requests.get(url)
55
+ response.raise_for_status()
56
+ data = response.json()
57
+ temp = data['main']['temp']
58
+ pressure = data['main']['pressure']
59
+ humidity = data['main']['humidity']
60
+ weather_desc = data['weather'][0]['main']
61
+ return temp, pressure, humidity, weather_desc
62
+ except requests.exceptions.HTTPError:
63
+ return None, None, None, None
64
+
65
+
66
+ # Function to map soil type to pump model's expected format
67
+ def map_soil_to_pump_model(soil_label):
68
+ if soil_label in ['Black Soil', 'Red Soil']:
69
+ return 'DRY'
70
+ elif soil_label == 'Clay Soil':
71
+ return 'WET'
72
+ elif soil_label == 'Alluvial Soil':
73
+ return 'HUMID'
74
+ return None
75
+
76
+
77
+ # Function to run predictions for all soil moisture values
78
+ # Function to run predictions for all soil moisture values
79
+ def run_predictions(crop_type, soil_type_for_pump, region, temperature, pressure, humidity, crop_age, irrigation_type, auto_weather_condition):
80
+ global pump_status, graph_data, previous_pump_status
81
+ pump_status = "Off"
82
+ previous_pump_status = "Off"
83
+ graph_data = []
84
+
85
+ for soil_moisture in soil_moisture_data:
86
+ try:
87
+ soil_moisture_value = float(soil_moisture) # Ensure this is a float
88
+ except ValueError:
89
+ print(f"Skipping invalid soil moisture value: {soil_moisture}")
90
+ continue
91
+
92
+ # Prepare features for pump prediction
93
+ features = np.array([crop_types[crop_type], soil_types[soil_type_for_pump],
94
+ regions[region], temperature if temperature else 0,
95
+ weather_conditions.get(auto_weather_condition, 0),
96
+ pressure if pressure else 0, humidity if humidity else 0,
97
+ int(crop_age), irrigation_types[irrigation_type],
98
+ soil_moisture_value]).reshape(1, -1)
99
+
100
+ # Make the pump prediction
101
+ pump_prediction = pump_model.predict(features)
102
+ pump_status = 'On' if pump_prediction[0] == 1 else 'Off'
103
+ graph_data.append((soil_moisture_value, 1 if pump_status == 'On' else -1)) # Update status to -1 for Off
104
+
105
+ print(f"Predicted Pump Status: {pump_status} for Soil Moisture: {soil_moisture_value}") # Debugging output
106
+
107
+ # Play sound if pump is Off and it wasn't Off previously
108
+ if pump_status == "Off" and previous_pump_status != "Off":
109
+ play_sound()
110
+
111
+ previous_pump_status = pump_status
112
+
113
+ # Wait for 1 second before next prediction
114
+ time.sleep(2)
115
+
116
+
117
+ def play_sound():
118
+ # You can use any sound file here
119
+ print("Beep! Pump is Off.") # Placeholder for actual sound functionality
120
+
121
+
122
+ # Main route
123
+ @app.route('/', methods=['GET', 'POST'])
124
+ def index():
125
+ global soil_moisture_data
126
+
127
+ city = crop_type = region = crop_age = irrigation_type = None
128
+ temperature = pressure = humidity = weather_desc = auto_weather_condition = None
129
+ soil_image_url = None
130
+
131
+ if request.method == 'POST':
132
+ city = request.form.get('city', '')
133
+ crop_type = request.form.get('crop_type', '')
134
+ region = request.form.get('region', '')
135
+ crop_age = request.form.get('crop_age', '')
136
+ irrigation_type = request.form.get('irrigation_type', '')
137
+
138
+ # Handle CSV file upload
139
+ if 'soil_moisture' in request.files:
140
+ soil_moisture_file = request.files['soil_moisture']
141
+ if soil_moisture_file:
142
+ # Read CSV file
143
+ df = pd.read_csv(soil_moisture_file)
144
+ soil_moisture_data = df['Soil Moisture'].tolist()
145
+
146
+ # Handle soil image upload
147
+ soil_image_file = request.files.get('soil_image')
148
+ if soil_image_file and soil_model is not None:
149
+ # Load and preprocess the image for prediction
150
+ image = Image.open(io.BytesIO(soil_image_file.read()))
151
+ image = image.resize((150, 150))
152
+ image = np.array(image) / 255.0
153
+ if image.shape[-1] == 4:
154
+ image = image[..., :3]
155
+ image = np.expand_dims(image, axis=0)
156
+
157
+ # Predict the soil type
158
+ soil_pred = soil_model.predict(image)
159
+ soil_label = soil_labels[np.argmax(soil_pred)]
160
+ soil_type_for_pump = map_soil_to_pump_model(soil_label)
161
+ elif soil_image_file and soil_model is None:
162
+ # Model not available, use default or form input
163
+ print("⚠ Soil image uploaded but model not available")
164
+ soil_type_for_pump = request.form.get('soil_type', 'HUMID')
165
+ else:
166
+ soil_type_for_pump = request.form.get('soil_type')
167
+
168
+ if city:
169
+ temperature, pressure, humidity, weather_desc = get_weather(city)
170
+ auto_weather_condition = "NORMAL" # Default weather condition
171
+ if weather_desc:
172
+ if 'sunny' in weather_desc.lower():
173
+ auto_weather_condition = 'SUNNY'
174
+ elif 'rain' in weather_desc.lower():
175
+ auto_weather_condition = 'RAINY'
176
+ elif 'wind' in weather_desc.lower():
177
+ auto_weather_condition = 'WINDY'
178
+
179
+ if 'predict' in request.form:
180
+ # Start a thread for predictions
181
+ threading.Thread(target=run_predictions, args=(
182
+ crop_type, soil_type_for_pump, region, temperature, pressure, humidity, crop_age, irrigation_type, auto_weather_condition)).start()
183
+ return redirect(url_for('predict'))
184
+
185
+ return render_template('index.html', temperature=temperature, pressure=pressure,
186
+ humidity=humidity, weather_desc=weather_desc, crop_types=crop_types,
187
+ regions=regions, irrigation_types=irrigation_types, soil_types=soil_types,
188
+ crop_type=crop_type, region=region, crop_age=crop_age,
189
+ irrigation_type=irrigation_type, city=city, soil_image_url=soil_image_url)
190
+
191
+
192
+ # Prediction route
193
+ @app.route('/predict', methods=['GET'])
194
+ def predict():
195
+ global pump_status, graph_data
196
+ return render_template('predict.html', pump_status=pump_status, graph_data=graph_data)
197
+
198
+
199
+ # Update graph data every second
200
+ @app.route('/update_graph', methods=['GET'])
201
+ def update_graph():
202
+ global graph_data
203
+ return jsonify(graph_data)
204
+
205
+
206
+ # Update pump status every second
207
+ @app.route('/update_pump_status', methods=['GET'])
208
+ def update_pump_status():
209
+ global pump_status
210
+ return jsonify({'pump_status': pump_status})
211
+
212
+
213
+ if __name__ == '__main__':
214
+ app.run(port=7860,host='0.0.0.0')
final_irrigation_data.csv ADDED
The diff for this file is too large to render. See raw diff
 
pump_status_dt_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:63bd7159f70e651ec890fb0ca61bcb4dbbb8b71bb94a079006adf794e887fecb
3
+ size 7641
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ gunicorn
2
+ flask
3
+ scikit-learn==1.5.2
4
+ tensorflow==2.17.0
5
+ numpy
6
+ requests
7
+ pillow
8
+ pandas
9
+ joblib==1.4.2
soil_classification_model.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ec920ea24020443d3ad2a5aa228d8f3db0493fcb0ff4384666d1b5c2e8d0a952
3
+ size 57993504
static/alarn_tune.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:22fdba64b8d71c233e8659af532b588d7ee216bb4567c73ca87a7c71df97f293
3
+ size 228700
static/images/irrigation_image.jpeg ADDED
templates/index.html ADDED
@@ -0,0 +1,522 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Smart Irrigation System - Pump Monitor</title>
7
+
8
+ <!-- Google Fonts -->
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
12
+
13
+ <!-- Bootstrap Icons -->
14
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
15
+
16
+ <style>
17
+ /* ===== CSS Variables (Color System) ===== */
18
+ :root {
19
+ --deep-green: #1a5d3a;
20
+ --accent-green: #198754;
21
+ --darker-accent: #143d2e;
22
+ --bg-light: #f8f9fa;
23
+ --surface-white: #ffffff;
24
+ --text-dark: #212529;
25
+ --text-muted: #6c757d;
26
+ --border-color: #dee2e6;
27
+ }
28
+
29
+ /* ===== Global Styles ===== */
30
+ * {
31
+ margin: 0;
32
+ padding: 0;
33
+ box-sizing: border-box;
34
+ }
35
+
36
+ body {
37
+ font-family: 'Outfit', sans-serif;
38
+ background-color: var(--bg-light);
39
+ color: var(--text-dark);
40
+ line-height: 1.6;
41
+ }
42
+
43
+ /* ===== Curved Bottom Hero Header ===== */
44
+ .hero-header {
45
+ background: var(--deep-green);
46
+ color: white;
47
+ padding: 3rem 1rem 3rem;
48
+ text-align: center;
49
+
50
+ margin-bottom: 30px;
51
+ }
52
+
53
+ .hero-header h1 {
54
+ font-size: 2.5rem;
55
+ font-weight: 700;
56
+ margin-bottom: 0.5rem;
57
+ }
58
+
59
+ .hero-header p {
60
+ font-size: 1.1rem;
61
+ font-weight: 300;
62
+ opacity: 0.95;
63
+ }
64
+
65
+ /* ===== 3-Step Process Visual ===== */
66
+ .process-steps {
67
+ max-width: 900px;
68
+ margin: 0 auto 2rem;
69
+ padding: 0 1rem;
70
+ position: relative;
71
+ z-index: 10;
72
+ }
73
+
74
+ .steps-container {
75
+ display: flex;
76
+ justify-content: space-between;
77
+ align-items: center;
78
+ position: relative;
79
+ }
80
+
81
+ .step {
82
+ flex: 1;
83
+ text-align: center;
84
+ position: relative;
85
+ }
86
+
87
+ .step-icon {
88
+ width: 70px;
89
+ height: 70px;
90
+ background: var(--surface-white);
91
+ border-radius: 50%;
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: center;
95
+ margin: 0 auto 0.75rem;
96
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
97
+ font-size: 1.8rem;
98
+ color: var(--accent-green);
99
+ }
100
+
101
+ .step-label {
102
+ font-size: 0.9rem;
103
+ font-weight: 500;
104
+ color: var(--text-dark);
105
+ }
106
+
107
+ .step-connector {
108
+ position: absolute;
109
+ top: 35px;
110
+ left: 50%;
111
+ width: 100%;
112
+ height: 2px;
113
+ background: var(--border-color);
114
+ z-index: -1;
115
+ }
116
+
117
+ .step:last-child .step-connector {
118
+ display: none;
119
+ }
120
+
121
+ /* ===== Floating Card ===== */
122
+ .main-card {
123
+ max-width: 1100px;
124
+ margin: 0 auto;
125
+ background: var(--surface-white);
126
+ border-radius: 20px;
127
+ box-shadow: 0 10px 40px rgba(0,0,0,0.08);
128
+ padding: 3rem;
129
+ position: relative;
130
+ z-index: 5;
131
+ border: 5px dashed #1a5d3a;
132
+ }
133
+
134
+ /* ===== Form Elements ===== */
135
+ .form-row {
136
+ display: grid;
137
+ grid-template-columns: 1fr 1fr;
138
+ gap: 2rem;
139
+ margin-bottom: 2rem;
140
+ }
141
+
142
+ .form-group {
143
+ margin-bottom: 1.5rem;
144
+ }
145
+
146
+ .form-group label {
147
+ display: block;
148
+ font-weight: 500;
149
+ font-size: 0.95rem;
150
+ margin-bottom: 0.5rem;
151
+ color: var(--text-dark);
152
+ }
153
+
154
+ .form-control {
155
+ width: 100%;
156
+ padding: 0.75rem 1rem;
157
+ background: var(--bg-light);
158
+ border: 2px solid #1a5d3a;
159
+ border-radius: 8px;
160
+ font-family: 'Outfit', sans-serif;
161
+ font-size: 1rem;
162
+ transition: all 0.3s ease;
163
+ }
164
+
165
+ .form-control:focus {
166
+ outline: none;
167
+ background: var(--surface-white);
168
+ border-color: var(--accent-green);
169
+ box-shadow: 0 0 0 4px rgba(25, 135, 84, 0.1);
170
+ }
171
+
172
+ /* ===== Upload Zone ===== */
173
+ .upload-zone {
174
+ border: 2px dashed #1a5d3a;
175
+ background: #fcfcfc;
176
+ border-radius: 12px;
177
+ padding: 2rem;
178
+ text-align: center;
179
+ transition: all 0.3s ease;
180
+ cursor: pointer;
181
+ }
182
+
183
+ .upload-zone:hover,
184
+ .upload-zone.active {
185
+ background: #f0fff4;
186
+ border-color: var(--accent-green);
187
+ }
188
+
189
+ .upload-zone i {
190
+ font-size: 2.5rem;
191
+ color: var(--accent-green);
192
+ margin-bottom: 0.75rem;
193
+ }
194
+
195
+ .upload-zone p {
196
+ margin: 0.5rem 0;
197
+ color: var(--text-muted);
198
+ font-size: 0.9rem;
199
+ }
200
+
201
+ .upload-zone input[type="file"] {
202
+ display: none;
203
+ }
204
+
205
+ /* ===== Image Preview ===== */
206
+ .image-preview {
207
+ margin-top: 1rem;
208
+ text-align: center;
209
+ }
210
+
211
+ .image-preview img {
212
+ max-width: 150px;
213
+ max-height: 150px;
214
+ border-radius: 12px;
215
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
216
+ object-fit: cover;
217
+ }
218
+
219
+ /* ===== Weather Box ===== */
220
+ .weather-box {
221
+ background: var(--surface-white);
222
+ border: 1px solid var(--border-color);
223
+ border-radius: 12px;
224
+ padding: 1.5rem;
225
+ margin-top: 1rem;
226
+ }
227
+
228
+ .weather-box h3 {
229
+ font-size: 1.1rem;
230
+ font-weight: 600;
231
+ margin-bottom: 1rem;
232
+ color: var(--deep-green);
233
+ }
234
+
235
+ .weather-grid {
236
+ display: grid;
237
+ grid-template-columns: repeat(2, 1fr);
238
+ gap: 1rem;
239
+ }
240
+
241
+ .weather-item {
242
+ display: flex;
243
+ align-items: center;
244
+ gap: 0.5rem;
245
+ }
246
+
247
+ .weather-item i {
248
+ color: var(--accent-green);
249
+ font-size: 1.2rem;
250
+ }
251
+
252
+ .weather-item span {
253
+ font-size: 0.9rem;
254
+ color: var(--text-dark);
255
+ }
256
+
257
+ .weather-item strong {
258
+ font-weight: 600;
259
+ }
260
+
261
+ /* ===== Buttons ===== */
262
+ .btn {
263
+ display: inline-flex;
264
+ align-items: center;
265
+ justify-content: center;
266
+ gap: 0.5rem;
267
+ padding: 0.875rem 2rem;
268
+ font-family: 'Outfit', sans-serif;
269
+ font-size: 1rem;
270
+ font-weight: 500;
271
+ border-radius: 8px;
272
+ border: none;
273
+ cursor: pointer;
274
+ transition: all 0.3s ease;
275
+ text-decoration: none;
276
+ }
277
+
278
+ .btn-primary {
279
+ background: var(--deep-green);
280
+ color: white;
281
+ width: 100%;
282
+ margin-top: 1rem;
283
+ }
284
+
285
+ .btn-primary:hover {
286
+ background: var(--darker-accent);
287
+ transform: translateY(-2px);
288
+ box-shadow: 0 6px 20px rgba(26, 93, 58, 0.3);
289
+ }
290
+
291
+ .btn-secondary {
292
+ background: var(--surface-white);
293
+ color: var(--accent-green);
294
+ border: 1px solid var(--accent-green);
295
+ padding: 0.625rem 1.5rem;
296
+ font-size: 0.9rem;
297
+ }
298
+
299
+ .btn-secondary:hover {
300
+ background: var(--accent-green);
301
+ color: white;
302
+ }
303
+
304
+ /* ===== Right Side Image ===== */
305
+ .decorative-section {
306
+ display: flex;
307
+ align-items: center;
308
+ justify-content: center;
309
+ }
310
+
311
+ .decorative-image {
312
+ width: 100%;
313
+ height: 100%;
314
+ max-height: 500px;
315
+ object-fit: cover;
316
+ border-radius: 16px;
317
+ box-shadow: 0 8px 24px rgba(0,0,0,0.1);
318
+ }
319
+
320
+ /* ===== Responsive Design ===== */
321
+ @media (max-width: 768px) {
322
+ .hero-header h1 {
323
+ font-size: 2rem;
324
+ }
325
+
326
+ .form-row {
327
+ grid-template-columns: 1fr;
328
+ gap: 0;
329
+ }
330
+
331
+ .main-card {
332
+ padding: 2rem 1.5rem;
333
+ }
334
+
335
+ .steps-container {
336
+ flex-direction: column;
337
+ gap: 1.5rem;
338
+ }
339
+
340
+ .step-connector {
341
+ display: none;
342
+ }
343
+
344
+ .decorative-section {
345
+ display: none;
346
+ }
347
+ }
348
+ </style>
349
+ </head>
350
+ <body>
351
+ <!-- Curved Bottom Hero Header -->
352
+ <div class="hero-header">
353
+
354
+ <h1><i class="bi bi-droplet"></i>
355
+ Smart Irrigation System</h1>
356
+ <p>AI-Powered Pump Monitoring & Control</p>
357
+ </div>
358
+
359
+ <!-- 3-Step Process Visual -->
360
+
361
+
362
+ <!-- Main Floating Card -->
363
+ <div class="main-card">
364
+ <form method="POST" action="/" enctype="multipart/form-data">
365
+ <div class="form-row">
366
+ <!-- Left Column: Form Inputs -->
367
+ <div class="form-column">
368
+ <!-- Crop Type -->
369
+ <div class="form-group">
370
+ <label for="crop_type"><i class="bi bi-flower1"></i> Crop Type</label>
371
+ <select class="form-control" id="crop_type" name="crop_type" required>
372
+ <option value="">--Select Crop Type--</option>
373
+ {% for crop, value in crop_types.items() %}
374
+ <option value="{{ crop }}" {% if crop == crop_type %}selected{% endif %}>{{ crop }}</option>
375
+ {% endfor %}
376
+ </select>
377
+ </div>
378
+
379
+ <!-- Crop Age -->
380
+ <div class="form-group">
381
+ <label for="crop_age"><i class="bi bi-calendar-event"></i> Crop Age (days)</label>
382
+ <input type="number" class="form-control" id="crop_age" name="crop_age" value="{{ crop_age }}" placeholder="Enter crop age in days" required>
383
+ </div>
384
+
385
+ <!-- Region -->
386
+ <div class="form-group">
387
+ <label for="region"><i class="bi bi-geo-alt"></i> Region</label>
388
+ <select class="form-control" id="region" name="region" required>
389
+ <option value="">--Select Region--</option>
390
+ {% for region, value in regions.items() %}
391
+ <option value="{{ region }}" {% if region == region %}selected{% endif %}>{{ region }}</option>
392
+ {% endfor %}
393
+ </select>
394
+ </div>
395
+
396
+ <!-- Irrigation Type -->
397
+ <div class="form-group">
398
+ <label for="irrigation_type"><i class="bi bi-droplet"></i> Irrigation Type</label>
399
+ <select class="form-control" id="irrigation_type" name="irrigation_type" required>
400
+ <option value="">--Select Irrigation Type--</option>
401
+ {% for irrigation, value in irrigation_types.items() %}
402
+ <option value="{{ irrigation }}" {% if irrigation == irrigation_type %}selected{% endif %}>{{ irrigation }}</option>
403
+ {% endfor %}
404
+ </select>
405
+ </div>
406
+
407
+ <!-- City for Weather -->
408
+ <div class="form-group">
409
+ <label for="city"><i class="bi bi-cloud-sun"></i> City (for Weather Data)</label>
410
+ <input type="text" class="form-control" id="city" name="city" value="{{ city }}" placeholder="Enter your city name">
411
+ <button type="submit" name="fetch_weather" class="btn btn-secondary" style="margin-top: 0.75rem;">
412
+ <i class="bi bi-download"></i> Fetch Weather
413
+ </button>
414
+ </div>
415
+
416
+ <!-- Weather Display -->
417
+ {% if temperature %}
418
+ <div class="weather-box">
419
+ <h3><i class="bi bi-cloud-sun"></i> Weather Information</h3>
420
+ <div class="weather-grid">
421
+ <div class="weather-item">
422
+ <i class="bi bi-thermometer-half"></i>
423
+ <span><strong>{{ temperature }}°C</strong></span>
424
+ </div>
425
+ <div class="weather-item">
426
+ <i class="bi bi-moisture"></i>
427
+ <span><strong>{{ humidity }}%</strong> Humidity</span>
428
+ </div>
429
+ <div class="weather-item">
430
+ <i class="bi bi-speedometer"></i>
431
+ <span><strong>{{ pressure }}</strong> hPa</span>
432
+ </div>
433
+ <div class="weather-item">
434
+ <i class="bi bi-cloud"></i>
435
+ <span><strong>{{ weather_desc }}</strong></span>
436
+ </div>
437
+ </div>
438
+ </div>
439
+ {% endif %}
440
+ </div>
441
+
442
+ <!-- Right Column: Uploads & Image -->
443
+ <div class="form-column">
444
+ <!-- Decorative Image -->
445
+ <div class="decorative-section" style="margin-bottom: 2rem;">
446
+ <img src="static/images/irrigation_image.jpeg" alt="Irrigation System" class="decorative-image">
447
+ </div>
448
+
449
+ <!-- Soil Moisture CSV Upload -->
450
+ <div class="form-group">
451
+ <label for="soil_moisture"><i class="bi bi-file-earmark-spreadsheet"></i> Soil Moisture Data (CSV)</label>
452
+ <div class="upload-zone" onclick="document.getElementById('soil_moisture').click()">
453
+ <i class="bi bi-cloud-arrow-up"></i>
454
+ <p><strong>Click to upload CSV file</strong></p>
455
+ <p>Must contain "Soil Moisture" column</p>
456
+ <input type="file" class="form-control-file" id="soil_moisture" name="soil_moisture" accept=".csv" required>
457
+ </div>
458
+ </div>
459
+
460
+ <!-- Soil Image Upload -->
461
+ <div class="form-group">
462
+ <label for="soil_image"><i class="bi bi-image"></i> Soil Image (Optional)</label>
463
+ <div class="upload-zone" onclick="document.getElementById('soil_image').click()">
464
+ <i class="bi bi-camera"></i>
465
+ <p><strong>Click to upload soil image</strong></p>
466
+ <p>For automatic soil type detection</p>
467
+ <input type="file" class="form-control-file" id="soil_image" name="soil_image" accept="image/*" onchange="previewImage(event)">
468
+ </div>
469
+
470
+ <!-- Image Preview -->
471
+ <div class="image-preview" id="preview-container" style="display: none;">
472
+ <img id="image-preview" src="" alt="Soil Image Preview">
473
+ </div>
474
+ </div>
475
+ </div>
476
+ </div>
477
+
478
+ <!-- Submit Button -->
479
+ <button type="submit" name="predict" class="btn btn-primary">
480
+ <i class="bi bi-play-circle"></i> Start Pump Analysis
481
+ </button>
482
+ </form>
483
+ </div>
484
+
485
+ <script>
486
+ // Preview uploaded image
487
+ function previewImage(event) {
488
+ const file = event.target.files[0];
489
+ const preview = document.getElementById('image-preview');
490
+ const container = document.getElementById('preview-container');
491
+
492
+ if (file) {
493
+ const reader = new FileReader();
494
+ reader.onload = function(e) {
495
+ preview.src = e.target.result;
496
+ container.style.display = 'block';
497
+ }
498
+ reader.readAsDataURL(file);
499
+ } else {
500
+ container.style.display = 'none';
501
+ }
502
+ }
503
+
504
+ // Add active state to upload zones
505
+ document.querySelectorAll('.upload-zone').forEach(zone => {
506
+ zone.addEventListener('dragover', (e) => {
507
+ e.preventDefault();
508
+ zone.classList.add('active');
509
+ });
510
+
511
+ zone.addEventListener('dragleave', () => {
512
+ zone.classList.remove('active');
513
+ });
514
+
515
+ zone.addEventListener('drop', (e) => {
516
+ e.preventDefault();
517
+ zone.classList.remove('active');
518
+ });
519
+ });
520
+ </script>
521
+ </body>
522
+ </html>
templates/predict.html ADDED
@@ -0,0 +1,634 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Pump Analysis Results - Smart Irrigation</title>
7
+
8
+ <!-- Google Fonts -->
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
12
+
13
+ <!-- Bootstrap Icons -->
14
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
15
+
16
+ <!-- Plotly for Charts -->
17
+ <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
18
+
19
+ <style>
20
+ /* ===== CSS Variables (Color System) ===== */
21
+ :root {
22
+ --deep-green: #1a5d3a;
23
+ --accent-green: #198754;
24
+ --darker-accent: #143d2e;
25
+ --bg-light: #f8f9fa;
26
+ --surface-white: #ffffff;
27
+ --text-dark: #212529;
28
+ --text-muted: #6c757d;
29
+ --border-color: #dee2e6;
30
+ }
31
+
32
+ /* ===== Global Styles ===== */
33
+ * {
34
+ margin: 0;
35
+ padding: 0;
36
+ box-sizing: border-box;
37
+ }
38
+
39
+ body {
40
+ font-family: 'Outfit', sans-serif;
41
+ background-color: var(--bg-light);
42
+ color: var(--text-dark);
43
+ line-height: 1.6;
44
+ overflow-x: hidden;
45
+ }
46
+
47
+ /* ===== Split-Screen Layout ===== */
48
+ .dashboard-container {
49
+ display: grid;
50
+ grid-template-columns: 350px 1fr;
51
+ min-height: 100vh;
52
+ gap: 0;
53
+ }
54
+
55
+ /* ===== Left Sidebar (Sticky) ===== */
56
+ .sidebar {
57
+ background: var(--surface-white);
58
+ border-right: 5px solid #1a5d3a;
59
+ padding: 2rem 1.5rem;
60
+ position: sticky;
61
+ top: 0;
62
+ height: 100vh;
63
+ overflow-y: auto;
64
+ }
65
+
66
+ .sidebar-header {
67
+ margin-bottom: 2rem;
68
+ }
69
+
70
+ .sidebar-header h1 {
71
+ font-size: 1.5rem;
72
+ font-weight: 700;
73
+ color: var(--deep-green);
74
+ margin-bottom: 0.5rem;
75
+ }
76
+
77
+ .sidebar-header p {
78
+ font-size: 0.9rem;
79
+ color: var(--text-muted);
80
+ font-weight: 300;
81
+ }
82
+
83
+ /* ===== Status Badge ===== */
84
+ .status-badge {
85
+ padding: 1.25rem;
86
+ border-radius: 12px;
87
+ text-align: center;
88
+ margin-bottom: 1.5rem;
89
+ box-shadow: 0 4px 12px rgba(0,0,0,0.08);
90
+ border: 2px solid #1a5d3a;
91
+ }
92
+
93
+ .status-badge.on {
94
+ background: var(--accent-green);
95
+ color: white;
96
+ }
97
+
98
+ .status-badge.off {
99
+ background: #dc3545;
100
+ color: white;
101
+ }
102
+
103
+ .status-badge i {
104
+ font-size: 2rem;
105
+ margin-bottom: 0.5rem;
106
+ display: block;
107
+ }
108
+
109
+ .status-badge .status-label {
110
+ font-size: 0.85rem;
111
+ font-weight: 500;
112
+ text-transform: uppercase;
113
+ letter-spacing: 0.5px;
114
+ opacity: 0.9;
115
+ }
116
+
117
+ .status-badge .status-value {
118
+ font-size: 1.5rem;
119
+ font-weight: 700;
120
+ margin-top: 0.25rem;
121
+ }
122
+
123
+ /* ===== Time Counter ===== */
124
+ .time-counter {
125
+ background: var(--bg-light);
126
+ padding: 1rem;
127
+ border-radius: 8px;
128
+ text-align: center;
129
+ margin-bottom: 1.5rem;
130
+ }
131
+
132
+ .time-counter i {
133
+ color: var(--accent-green);
134
+ font-size: 1.2rem;
135
+ margin-right: 0.5rem;
136
+ }
137
+
138
+ .time-counter span {
139
+ font-size: 1.1rem;
140
+ font-weight: 600;
141
+ color: var(--text-dark);
142
+ }
143
+
144
+ /* ===== Gauge Container ===== */
145
+ .gauge-card {
146
+ background: var(--surface-white);
147
+ border: 2px solid #1a5d3a;
148
+ border-radius: 12px;
149
+ padding: 1.5rem;
150
+ margin-bottom: 1.5rem;
151
+ }
152
+
153
+ .gauge-card h3 {
154
+ font-size: 1rem;
155
+ font-weight: 600;
156
+ color: var(--deep-green);
157
+ margin-bottom: 1rem;
158
+ text-align: center;
159
+ }
160
+
161
+ #gauge {
162
+ width: 100%;
163
+ height: 280px;
164
+ }
165
+
166
+ /* ===== Right Panel (Scrollable) ===== */
167
+ .content-panel {
168
+ padding: 2rem;
169
+ overflow-y: auto;
170
+ }
171
+
172
+ /* ===== Diagnostic Timeline (Exclusive to Results Page) ===== */
173
+ .diagnostic-timeline {
174
+ background: var(--surface-white);
175
+ border-radius: 20px;
176
+ box-shadow: 0 10px 40px rgba(0,0,0,0.08);
177
+ padding: 2.5rem;
178
+ margin-bottom: 2rem;
179
+ border: 5px dashed #1a5d3a;
180
+ }
181
+
182
+ .timeline-header {
183
+ text-align: center;
184
+ margin-bottom: 2.5rem;
185
+ }
186
+
187
+ .timeline-header h2 {
188
+ font-size: 1.75rem;
189
+ font-weight: 700;
190
+ color: var(--deep-green);
191
+ margin-bottom: 0.5rem;
192
+ }
193
+
194
+ .timeline-header p {
195
+ color: var(--text-muted);
196
+ font-size: 0.95rem;
197
+ }
198
+
199
+ .timeline-steps {
200
+ display: flex;
201
+ justify-content: space-between;
202
+ align-items: center;
203
+ position: relative;
204
+ max-width: 700px;
205
+ margin: 0 auto;
206
+ }
207
+
208
+ .timeline-step {
209
+ flex: 1;
210
+ text-align: center;
211
+ position: relative;
212
+ }
213
+
214
+ .timeline-icon {
215
+ width: 80px;
216
+ height: 80px;
217
+ background: linear-gradient(135deg, var(--accent-green), var(--deep-green));
218
+ border-radius: 50%;
219
+ display: flex;
220
+ align-items: center;
221
+ justify-content: center;
222
+ margin: 0 auto 1rem;
223
+ box-shadow: 0 6px 20px rgba(25, 135, 84, 0.3);
224
+ font-size: 2rem;
225
+ color: white;
226
+ animation: pulse 2s infinite;
227
+ }
228
+
229
+ @keyframes pulse {
230
+ 0%, 100% { transform: scale(1); }
231
+ 50% { transform: scale(1.05); }
232
+ }
233
+
234
+ .timeline-label {
235
+ font-size: 1rem;
236
+ font-weight: 600;
237
+ color: var(--text-dark);
238
+ margin-bottom: 0.25rem;
239
+ }
240
+
241
+ .timeline-desc {
242
+ font-size: 0.85rem;
243
+ color: var(--text-muted);
244
+ }
245
+
246
+ .timeline-connector {
247
+ position: absolute;
248
+ top: 40px;
249
+ left: 50%;
250
+ width: 100%;
251
+ height: 3px;
252
+ background: linear-gradient(90deg, var(--accent-green), var(--deep-green));
253
+ z-index: -1;
254
+ }
255
+
256
+ .timeline-step:last-child .timeline-connector {
257
+ display: none;
258
+ }
259
+
260
+ /* ===== Chart Cards ===== */
261
+ .chart-card {
262
+ background: var(--surface-white);
263
+ border-radius: 20px;
264
+ box-shadow: 0 10px 40px rgba(0,0,0,0.08);
265
+ padding: 2rem;
266
+ margin-bottom: 2rem;
267
+ border: 5px dashed #1a5d3a;
268
+ }
269
+
270
+ .chart-card h3 {
271
+ font-size: 1.25rem;
272
+ font-weight: 600;
273
+ color: var(--deep-green);
274
+ margin-bottom: 1.5rem;
275
+ display: flex;
276
+ align-items: center;
277
+ gap: 0.5rem;
278
+ }
279
+
280
+ .chart-card h3 i {
281
+ color: var(--accent-green);
282
+ }
283
+
284
+ .chart-container {
285
+ width: 100%;
286
+ height: 400px;
287
+ }
288
+
289
+ /* ===== Info Alert ===== */
290
+ .info-alert {
291
+ background: #e7f3ff;
292
+ border-left: 4px solid #0066cc;
293
+ padding: 1rem 1.25rem;
294
+ border-radius: 8px;
295
+ margin-bottom: 2rem;
296
+ display: flex;
297
+ align-items: start;
298
+ gap: 0.75rem;
299
+ }
300
+
301
+ .info-alert i {
302
+ color: #0066cc;
303
+ font-size: 1.25rem;
304
+ margin-top: 0.125rem;
305
+ }
306
+
307
+ .info-alert p {
308
+ margin: 0;
309
+ color: var(--text-dark);
310
+ font-size: 0.9rem;
311
+ line-height: 1.5;
312
+ }
313
+
314
+ /* ===== Responsive Design ===== */
315
+ @media (max-width: 1024px) {
316
+ .dashboard-container {
317
+ grid-template-columns: 1fr;
318
+ }
319
+
320
+ .sidebar {
321
+ position: relative;
322
+ height: auto;
323
+ border-right: none;
324
+ border-bottom: 1px solid var(--border-color);
325
+ }
326
+
327
+ .timeline-steps {
328
+ flex-direction: column;
329
+ gap: 2rem;
330
+ }
331
+
332
+ .timeline-connector {
333
+ display: none;
334
+ }
335
+ }
336
+
337
+ @media (max-width: 768px) {
338
+ .content-panel {
339
+ padding: 1rem;
340
+ }
341
+
342
+ .diagnostic-timeline {
343
+ padding: 1.5rem;
344
+ }
345
+
346
+ .chart-card {
347
+ padding: 1.5rem;
348
+ }
349
+ }
350
+ </style>
351
+
352
+ <script>
353
+ let alertSound = new Audio('{{ url_for("static", filename="alarn_tune.mp3") }}');
354
+
355
+ function playAlertSound() {
356
+ alertSound.currentTime = 0;
357
+ alertSound.play().catch(error => console.error("Error playing sound:", error));
358
+ }
359
+
360
+ function fetchPumpStatus() {
361
+ fetch('/update_pump_status')
362
+ .then(response => response.json())
363
+ .then(data => {
364
+ const statusElement = document.getElementById('pumpStatus');
365
+ const statusValue = document.getElementById('statusValue');
366
+ const statusIcon = document.getElementById('statusIcon');
367
+
368
+ statusValue.textContent = data.pump_status;
369
+ statusElement.className = data.pump_status === 'On' ? 'status-badge on' : 'status-badge off';
370
+ statusIcon.className = data.pump_status === 'On' ? 'bi bi-power' : 'bi bi-exclamation-triangle';
371
+
372
+ if (data.pump_status === 'Off') {
373
+ console.log("Pump status is Off. Playing alert sound.");
374
+ playAlertSound();
375
+ }
376
+ })
377
+ .catch(err => console.error("Error fetching pump status:", err));
378
+ }
379
+
380
+ function fetchGraphData() {
381
+ fetch('/update_graph')
382
+ .then(response => response.json())
383
+ .then(data => {
384
+ const time = data.map((_, index) => index + 1);
385
+ const soilMoisture = data.map(entry => entry[0]);
386
+ const pumpStatus = data.map(entry => entry[1]);
387
+
388
+ // Update Gauge chart with soil moisture
389
+ const currentSoilMoisture = soilMoisture[soilMoisture.length - 1];
390
+ updateGaugeChart(currentSoilMoisture);
391
+
392
+ // Pump Status vs. Time
393
+ const binaryPumpStatus = pumpStatus.map(status => status);
394
+ const trace1 = {
395
+ x: time,
396
+ y: binaryPumpStatus,
397
+ mode: 'lines+markers',
398
+ type: 'scatter',
399
+ name: 'Pump Status',
400
+ line: { color: '#198754', width: 3 },
401
+ marker: { size: 8 }
402
+ };
403
+
404
+ const layout1 = {
405
+ title: '',
406
+ xaxis: { title: 'Time (seconds)', gridcolor: '#e9ecef' },
407
+ yaxis: { title: 'Pump Status', tickvals: [-1, 1], ticktext: ['Off', 'On'], range: [-1.5, 1.5], gridcolor: '#e9ecef' },
408
+ showlegend: false,
409
+ plot_bgcolor: '#f8f9fa',
410
+ paper_bgcolor: 'white',
411
+ font: { family: 'Outfit, sans-serif' }
412
+ };
413
+
414
+ Plotly.newPlot('graph1', [trace1], layout1, {responsive: true});
415
+
416
+ // Soil Moisture vs. Time
417
+ const trace2 = {
418
+ x: time,
419
+ y: soilMoisture,
420
+ mode: 'lines+markers',
421
+ type: 'scatter',
422
+ name: 'Soil Moisture',
423
+ line: { color: '#1a5d3a', width: 3 },
424
+ marker: { size: 8 },
425
+ fill: 'tozeroy',
426
+ fillcolor: 'rgba(26, 93, 58, 0.1)'
427
+ };
428
+
429
+ const layout2 = {
430
+ title: '',
431
+ xaxis: { title: 'Time (seconds)', gridcolor: '#e9ecef' },
432
+ yaxis: { title: 'Soil Moisture (%)', gridcolor: '#e9ecef' },
433
+ showlegend: false,
434
+ plot_bgcolor: '#f8f9fa',
435
+ paper_bgcolor: 'white',
436
+ font: { family: 'Outfit, sans-serif' }
437
+ };
438
+
439
+ Plotly.newPlot('graph2', [trace2], layout2, {responsive: true});
440
+
441
+ // Combined Graph for Soil Moisture vs. Pump Status
442
+ const trace3 = {
443
+ x: soilMoisture,
444
+ y: binaryPumpStatus,
445
+ mode: 'markers+lines',
446
+ type: 'scatter',
447
+ name: 'Pump Status',
448
+ line: { color: '#198754', width: 2 },
449
+ marker: { size: 10, color: '#1a5d3a' }
450
+ };
451
+
452
+ const layout3 = {
453
+ title: '',
454
+ xaxis: { title: 'Soil Moisture (%)', gridcolor: '#e9ecef' },
455
+ yaxis: { title: 'Pump Status', tickvals: [-1, 1], ticktext: ['Off', 'On'], range: [-1.5, 1.5], gridcolor: '#e9ecef' },
456
+ showlegend: false,
457
+ plot_bgcolor: '#f8f9fa',
458
+ paper_bgcolor: 'white',
459
+ font: { family: 'Outfit, sans-serif' }
460
+ };
461
+
462
+ Plotly.newPlot('graph3', [trace3], layout3, {responsive: true});
463
+
464
+ // Histogram for Soil Moisture
465
+ const trace4 = {
466
+ x: soilMoisture,
467
+ type: 'histogram',
468
+ marker: { color: '#198754' },
469
+ nbinsx: 20
470
+ };
471
+
472
+ const layout4 = {
473
+ title: '',
474
+ xaxis: { title: 'Soil Moisture (%)', gridcolor: '#e9ecef' },
475
+ yaxis: { title: 'Frequency', gridcolor: '#e9ecef' },
476
+ plot_bgcolor: '#f8f9fa',
477
+ paper_bgcolor: 'white',
478
+ font: { family: 'Outfit, sans-serif' }
479
+ };
480
+
481
+ Plotly.newPlot('graph4', [trace4], layout4, {responsive: true});
482
+ })
483
+ .catch(err => console.error("Error fetching graph data:", err));
484
+ }
485
+
486
+ // Function to update the gauge chart
487
+ function updateGaugeChart(value) {
488
+ const data = [
489
+ {
490
+ type: "indicator",
491
+ mode: "gauge+number",
492
+ value: value,
493
+ title: { text: "", font: { size: 16, family: 'Outfit, sans-serif' } },
494
+ gauge: {
495
+ axis: { range: [0, 100], tickfont: { family: 'Outfit, sans-serif' } },
496
+ steps: [
497
+ { range: [0, 30], color: "#ffebee" },
498
+ { range: [30, 60], color: "#fff9e6" },
499
+ { range: [60, 100], color: "#e8f5e9" }
500
+ ],
501
+ bar: { color: "#1a5d3a" },
502
+ threshold: {
503
+ line: { color: "#198754", width: 4 },
504
+ thickness: 0.75,
505
+ value: value
506
+ }
507
+ }
508
+ }
509
+ ];
510
+
511
+ const layout = {
512
+ margin: { t: 20, b: 20, l: 20, r: 20 },
513
+ paper_bgcolor: 'white',
514
+ font: { family: 'Outfit, sans-serif' }
515
+ };
516
+
517
+ Plotly.newPlot('gauge', data, layout, {responsive: true});
518
+ }
519
+
520
+ // Time counter functionality
521
+ let timeCounter = 0;
522
+ function updateTimeCounter() {
523
+ timeCounter++;
524
+ document.getElementById('time-counter').textContent = `${timeCounter}s`;
525
+ }
526
+
527
+ // Set intervals for data fetching and time counting
528
+ setInterval(fetchPumpStatus, 2000);
529
+ setInterval(fetchGraphData, 2000);
530
+ setInterval(updateTimeCounter, 1000);
531
+
532
+ document.addEventListener('click', () => {
533
+ alertSound.load();
534
+ });
535
+ </script>
536
+ </head>
537
+ <body>
538
+ <div class="dashboard-container">
539
+ <!-- Left Sidebar (Sticky) -->
540
+ <div class="sidebar">
541
+ <div class="sidebar-header">
542
+ <h1><i class="bi bi-speedometer2"></i> Live Monitor</h1>
543
+ <p>Real-time pump analysis</p>
544
+ </div>
545
+
546
+ <!-- Pump Status Badge -->
547
+ <div id="pumpStatus" class="status-badge {{ 'on' if pump_status == 'On' else 'off' }}">
548
+ <i id="statusIcon" class="bi {{ 'bi-power' if pump_status == 'On' else 'bi-exclamation-triangle' }}"></i>
549
+ <div class="status-label">Pump Status</div>
550
+ <div class="status-value" id="statusValue">{{ pump_status }}</div>
551
+ </div>
552
+
553
+ <!-- Time Counter -->
554
+ <div class="time-counter">
555
+ <i class="bi bi-clock"></i>
556
+ <span>Elapsed: <strong id="time-counter">0s</strong></span>
557
+ </div>
558
+
559
+ <!-- Soil Moisture Gauge -->
560
+ <div class="gauge-card">
561
+ <h3><i class="bi bi-moisture"></i> Soil Moisture</h3>
562
+ <div id="gauge"></div>
563
+ </div>
564
+ </div>
565
+
566
+ <!-- Right Content Panel (Scrollable) -->
567
+ <div class="content-panel">
568
+ <!-- Diagnostic Timeline (Exclusive to Results Page) -->
569
+ <div class="diagnostic-timeline">
570
+ <div class="timeline-header">
571
+ <h2>Diagnostic Timeline</h2>
572
+ <p>AI-powered analysis workflow</p>
573
+ </div>
574
+
575
+ <div class="timeline-steps">
576
+ <div class="timeline-step">
577
+ <div class="timeline-icon">
578
+ <i class="bi bi-database"></i>
579
+ </div>
580
+ <div class="timeline-label">Collect Data</div>
581
+ <div class="timeline-desc">Gathering sensor inputs</div>
582
+ <div class="timeline-connector"></div>
583
+ </div>
584
+ <div class="timeline-step">
585
+ <div class="timeline-icon">
586
+ <i class="bi bi-cpu"></i>
587
+ </div>
588
+ <div class="timeline-label">Process Analysis</div>
589
+ <div class="timeline-desc">AI model prediction</div>
590
+ <div class="timeline-connector"></div>
591
+ </div>
592
+ <div class="timeline-step">
593
+ <div class="timeline-icon">
594
+ <i class="bi bi-graph-up"></i>
595
+ </div>
596
+ <div class="timeline-label">Monitor Results</div>
597
+ <div class="timeline-desc">Live status tracking</div>
598
+ </div>
599
+ </div>
600
+ </div>
601
+
602
+ <!-- Info Alert -->
603
+ <div class="info-alert">
604
+ <i class="bi bi-info-circle-fill"></i>
605
+ <p><strong>Real-time Monitoring:</strong> Charts update every 2 seconds with new predictions. The pump status will automatically adjust based on soil moisture levels and environmental conditions.</p>
606
+ </div>
607
+
608
+ <!-- Chart: Pump Status vs Time -->
609
+ <div class="chart-card">
610
+ <h3><i class="bi bi-activity"></i> Pump Status Over Time</h3>
611
+ <div id="graph1" class="chart-container"></div>
612
+ </div>
613
+
614
+ <!-- Chart: Soil Moisture vs Time -->
615
+ <div class="chart-card">
616
+ <h3><i class="bi bi-moisture"></i> Soil Moisture Trends</h3>
617
+ <div id="graph2" class="chart-container"></div>
618
+ </div>
619
+
620
+ <!-- Chart: Pump Status vs Soil Moisture -->
621
+ <div class="chart-card">
622
+ <h3><i class="bi bi-diagram-3"></i> Correlation Analysis</h3>
623
+ <div id="graph3" class="chart-container"></div>
624
+ </div>
625
+
626
+ <!-- Chart: Soil Moisture Distribution -->
627
+ <div class="chart-card">
628
+ <h3><i class="bi bi-bar-chart"></i> Moisture Distribution</h3>
629
+ <div id="graph4" class="chart-container"></div>
630
+ </div>
631
+ </div>
632
+ </div>
633
+ </body>
634
+ </html>
templates/sample.html ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Random Value Beep</title>
8
+ <style>
9
+ body {
10
+ font-family: Arial, sans-serif;
11
+ text-align: center;
12
+ margin-top: 50px;
13
+ }
14
+ button {
15
+ padding: 10px 20px;
16
+ font-size: 16px;
17
+ cursor: pointer;
18
+ }
19
+ </style>
20
+ </head>
21
+ <body>
22
+
23
+ <h1>Random Value Checker</h1>
24
+ <button id="generateButton">Generate Random Value</button>
25
+ <p id="result"></p>
26
+
27
+ <audio id="beepSound" src="/alarn_tune.mp3"></audio>
28
+
29
+ <script>
30
+ document.getElementById("generateButton").onclick = function() {
31
+ // Generate a random value between 0 and 20
32
+ var randomValue = Math.floor(Math.random() * 21);
33
+ document.getElementById("result").innerText = "Generated Value: " + randomValue;
34
+
35
+ // Check if the random value is greater than 10
36
+ if (randomValue > 10) {
37
+ document.getElementById("beepSound").play();
38
+ }
39
+ }
40
+ </script>
41
+ </body>
42
+ </html>