unfolder 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- unfolder/__init__.py +6 -0
- unfolder/__main__.py +35 -0
- unfolder/data/LDR_DHT22_COMBINED.txt +82 -0
- unfolder/data/Q01_DHT_Sensor.txt +77 -0
- unfolder/data/Q02_LDR_Sensor.txt +70 -0
- unfolder/data/Q03_PIR_Sensor.txt +64 -0
- unfolder/data/Q04_DHT_MQTT.txt +181 -0
- unfolder/data/Q05_REST_API_Supabase.txt +283 -0
- unfolder/data/Q06_MQTT_Supabase.txt +376 -0
- unfolder/data/Q07_ThingsBoard_MQTT.txt +116 -0
- unfolder/data/Q08_CoAP_Server_Client.txt +277 -0
- unfolder/data/Q09_MQTT_HiveMQ_Mosquitto.txt +179 -0
- unfolder/data/Q10_CoAP_OpenMeteo_Supabase.txt +504 -0
- unfolder/data/Q11_ISS_Tracking.txt +108 -0
- unfolder/data/Q12_Earthquake_Analysis.txt +275 -0
- unfolder/data/Q13_Weather_Analysis.txt +126 -0
- unfolder/data/Q14_Air_Quality_Analysis.txt +138 -0
- unfolder/data/Q15_Decision_Tree_Occupancy.txt +133 -0
- unfolder/data/Q16_Linear_Regression_Temperature.txt +158 -0
- unfolder/data/Q17_Random_Forest_Air_Quality.txt +173 -0
- unfolder/data/temp.txt +3266 -0
- unfolder/downloader.py +41 -0
- unfolder-0.1.0.dist-info/METADATA +79 -0
- unfolder-0.1.0.dist-info/RECORD +28 -0
- unfolder-0.1.0.dist-info/WHEEL +5 -0
- unfolder-0.1.0.dist-info/entry_points.txt +2 -0
- unfolder-0.1.0.dist-info/licenses/LICENSE +17 -0
- unfolder-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
5.Develop an application that send temperature and humidity sensor data to a cloud server through an HTTP-based REST API, stores the received data in a cloud database(Supabase / Firebase), and displays the sensor readings on a web-based dashboard.
|
|
2
|
+
|
|
3
|
+
pip install flask requests supabase python-dotenv
|
|
4
|
+
|
|
5
|
+
iot_http_dashboard/
|
|
6
|
+
│
|
|
7
|
+
├── app.py
|
|
8
|
+
├── sensor.py
|
|
9
|
+
├── .env
|
|
10
|
+
├── requirements.txt
|
|
11
|
+
│
|
|
12
|
+
└── templates/
|
|
13
|
+
└── index.html
|
|
14
|
+
|
|
15
|
+
supabase:
|
|
16
|
+
|
|
17
|
+
CREATE TABLE sensor_readings (
|
|
18
|
+
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
19
|
+
temperature FLOAT NOT NULL,
|
|
20
|
+
humidity FLOAT NOT NULL,
|
|
21
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
.env:
|
|
25
|
+
|
|
26
|
+
SUPABASE_URL=https://YOUR_PROJECT_ID.supabase.co
|
|
27
|
+
SUPABASE_KEY=YOUR_SERVICE_ROLE_KEY
|
|
28
|
+
|
|
29
|
+
app.py:
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
from flask import Flask, request, jsonify, render_template
|
|
33
|
+
from supabase import create_client
|
|
34
|
+
from dotenv import load_dotenv
|
|
35
|
+
import os
|
|
36
|
+
|
|
37
|
+
load_dotenv()
|
|
38
|
+
|
|
39
|
+
app = Flask(__name__)
|
|
40
|
+
|
|
41
|
+
# Supabase connection
|
|
42
|
+
SUPABASE_URL = os.getenv("SUPABASE_URL")
|
|
43
|
+
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
|
|
44
|
+
|
|
45
|
+
if not SUPABASE_URL or not SUPABASE_KEY:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
"SUPABASE_URL and SUPABASE_KEY must be set in the .env file"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Dashboard
|
|
54
|
+
@app.route("/")
|
|
55
|
+
def dashboard():
|
|
56
|
+
return render_template("index.html")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# REST API: Receive and store sensor data
|
|
60
|
+
@app.route("/api/readings", methods=["POST"])
|
|
61
|
+
def receive_data():
|
|
62
|
+
data = request.get_json(silent=True)
|
|
63
|
+
|
|
64
|
+
if not isinstance(data, dict):
|
|
65
|
+
return jsonify({"error": "Invalid JSON data"}), 400
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
temperature = float(data["temperature"])
|
|
69
|
+
humidity = float(data["humidity"])
|
|
70
|
+
|
|
71
|
+
if not (-50 <= temperature <= 100):
|
|
72
|
+
return jsonify({"error": "Invalid temperature value"}), 400
|
|
73
|
+
|
|
74
|
+
if not (0 <= humidity <= 100):
|
|
75
|
+
return jsonify({"error": "Invalid humidity value"}), 400
|
|
76
|
+
|
|
77
|
+
except (KeyError, TypeError, ValueError):
|
|
78
|
+
return jsonify({"error": "Temperature and humidity must be valid numbers"}), 400
|
|
79
|
+
|
|
80
|
+
reading = {
|
|
81
|
+
"temperature": temperature,
|
|
82
|
+
"humidity": humidity
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
result = supabase.table("sensor_readings").insert(reading).execute()
|
|
87
|
+
|
|
88
|
+
return jsonify({
|
|
89
|
+
"message": "Data stored successfully",
|
|
90
|
+
"data": result.data
|
|
91
|
+
}), 201
|
|
92
|
+
|
|
93
|
+
except Exception as e:
|
|
94
|
+
return jsonify({"error": str(e)}), 500
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# REST API: Retrieve sensor records
|
|
98
|
+
@app.route("/api/readings", methods=["GET"])
|
|
99
|
+
def get_data():
|
|
100
|
+
result = (
|
|
101
|
+
supabase.table("sensor_readings")
|
|
102
|
+
.select("*")
|
|
103
|
+
.order("created_at", desc=True)
|
|
104
|
+
.limit(50)
|
|
105
|
+
.execute()
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return jsonify(result.data)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
if __name__ == "__main__":
|
|
112
|
+
app.run(debug=True, use_reloader=False)
|
|
113
|
+
|
|
114
|
+
sensor.py:
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
import requests
|
|
118
|
+
import random
|
|
119
|
+
import time
|
|
120
|
+
|
|
121
|
+
URL = "http://127.0.0.1:5000/api/readings"
|
|
122
|
+
|
|
123
|
+
print("Temperature and Humidity Sensor Started")
|
|
124
|
+
|
|
125
|
+
while True:
|
|
126
|
+
|
|
127
|
+
temperature = round(random.uniform(20, 40), 2)
|
|
128
|
+
humidity = round(random.uniform(40, 90), 2)
|
|
129
|
+
|
|
130
|
+
data = {
|
|
131
|
+
"temperature": temperature,
|
|
132
|
+
"humidity": humidity
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
response = requests.post(URL, json=data, timeout=10)
|
|
137
|
+
response.raise_for_status()
|
|
138
|
+
|
|
139
|
+
print("Temperature:", temperature, "°C")
|
|
140
|
+
print("Humidity:", humidity, "%")
|
|
141
|
+
print("Server Response:", response.json())
|
|
142
|
+
print("--------------------------")
|
|
143
|
+
|
|
144
|
+
except requests.exceptions.RequestException as e:
|
|
145
|
+
print("Connection Error:", e)
|
|
146
|
+
|
|
147
|
+
time.sleep(5)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
templates/index.html:
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
<!DOCTYPE html>
|
|
154
|
+
<html>
|
|
155
|
+
<head>
|
|
156
|
+
<title>IoT Sensor Dashboard</title>
|
|
157
|
+
|
|
158
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
159
|
+
|
|
160
|
+
<style>
|
|
161
|
+
body {
|
|
162
|
+
font-family: Arial;
|
|
163
|
+
background: #eef2f7;
|
|
164
|
+
text-align: center;
|
|
165
|
+
padding: 30px;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
h1 {
|
|
169
|
+
color: #1e293b;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.cards {
|
|
173
|
+
display: flex;
|
|
174
|
+
justify-content: center;
|
|
175
|
+
gap: 20px;
|
|
176
|
+
flex-wrap: wrap;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.card {
|
|
180
|
+
background: white;
|
|
181
|
+
padding: 25px;
|
|
182
|
+
width: 220px;
|
|
183
|
+
border-radius: 12px;
|
|
184
|
+
box-shadow: 0 3px 10px #ccc;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.value {
|
|
188
|
+
font-size: 35px;
|
|
189
|
+
color: #2563eb;
|
|
190
|
+
font-weight: bold;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
table {
|
|
194
|
+
margin: 30px auto;
|
|
195
|
+
width: 90%;
|
|
196
|
+
background: white;
|
|
197
|
+
border-collapse: collapse;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
th, td {
|
|
201
|
+
padding: 12px;
|
|
202
|
+
border: 1px solid #ddd;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
th {
|
|
206
|
+
background: #2563eb;
|
|
207
|
+
color: white;
|
|
208
|
+
}
|
|
209
|
+
</style>
|
|
210
|
+
</head>
|
|
211
|
+
|
|
212
|
+
<body>
|
|
213
|
+
|
|
214
|
+
<h1>IoT Temperature & Humidity Dashboard</h1>
|
|
215
|
+
|
|
216
|
+
<div class="cards">
|
|
217
|
+
|
|
218
|
+
<div class="card">
|
|
219
|
+
<h2>Temperature</h2>
|
|
220
|
+
<div class="value" id="temperature">-- °C</div>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<div class="card">
|
|
224
|
+
<h2>Humidity</h2>
|
|
225
|
+
<div class="value" id="humidity">-- %</div>
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<h2>Sensor Reading History</h2>
|
|
231
|
+
|
|
232
|
+
<table>
|
|
233
|
+
<thead>
|
|
234
|
+
<tr>
|
|
235
|
+
<th>Temperature (°C)</th>
|
|
236
|
+
<th>Humidity (%)</th>
|
|
237
|
+
<th>Timestamp</th>
|
|
238
|
+
</tr>
|
|
239
|
+
</thead>
|
|
240
|
+
|
|
241
|
+
<tbody id="readings"></tbody>
|
|
242
|
+
</table>
|
|
243
|
+
|
|
244
|
+
<script>
|
|
245
|
+
async function loadReadings() {
|
|
246
|
+
|
|
247
|
+
const response = await fetch("/api/readings");
|
|
248
|
+
const data = await response.json();
|
|
249
|
+
|
|
250
|
+
if (data.length > 0) {
|
|
251
|
+
|
|
252
|
+
document.getElementById("temperature").innerText =
|
|
253
|
+
data[0].temperature + " °C";
|
|
254
|
+
|
|
255
|
+
document.getElementById("humidity").innerText =
|
|
256
|
+
data[0].humidity + " %";
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const table = document.getElementById("readings");
|
|
260
|
+
|
|
261
|
+
table.innerHTML = "";
|
|
262
|
+
|
|
263
|
+
data.forEach(item => {
|
|
264
|
+
|
|
265
|
+
const row = `
|
|
266
|
+
<tr>
|
|
267
|
+
<td>${item.temperature}</td>
|
|
268
|
+
<td>${item.humidity}</td>
|
|
269
|
+
<td>${new Date(item.created_at).toLocaleString()}</td>
|
|
270
|
+
</tr>
|
|
271
|
+
`;
|
|
272
|
+
|
|
273
|
+
table.innerHTML += row;
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
loadReadings();
|
|
278
|
+
|
|
279
|
+
setInterval(loadReadings, 5000);
|
|
280
|
+
</script>
|
|
281
|
+
|
|
282
|
+
</body>
|
|
283
|
+
</html>
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
6.Develop a dashboard that receives sensor data through the MQTT protocol, stores the data in a cloud database (Supabase / Firebase), retrieves the stored records using a web application, and displays the sensor readings on a web dashboard.
|
|
2
|
+
|
|
3
|
+
pip install flask paho-mqtt supabase python-dotenv
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
iot_mqtt_dashboard/
|
|
7
|
+
│
|
|
8
|
+
├── app.py
|
|
9
|
+
├── publisher.py
|
|
10
|
+
├── .env
|
|
11
|
+
├── requirements.txt
|
|
12
|
+
│
|
|
13
|
+
└── templates/
|
|
14
|
+
└── index.html
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
supabase:
|
|
18
|
+
|
|
19
|
+
CREATE TABLE mqtt_readings (
|
|
20
|
+
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
21
|
+
temperature FLOAT NOT NULL,
|
|
22
|
+
humidity FLOAT NOT NULL,
|
|
23
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
.env:
|
|
28
|
+
|
|
29
|
+
SUPABASE_URL=https://YOUR_PROJECT_ID.supabase.co
|
|
30
|
+
SUPABASE_KEY=YOUR_SERVICE_ROLE_KEY
|
|
31
|
+
|
|
32
|
+
publisher.py:
|
|
33
|
+
|
|
34
|
+
import paho.mqtt.client as mqtt
|
|
35
|
+
import random
|
|
36
|
+
import json
|
|
37
|
+
import time
|
|
38
|
+
import threading
|
|
39
|
+
|
|
40
|
+
# MQTT configuration
|
|
41
|
+
BROKER = "test.mosquitto.org"
|
|
42
|
+
PORT = 1883
|
|
43
|
+
TOPIC = "iot/sensor"
|
|
44
|
+
|
|
45
|
+
connected = threading.Event()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def on_connect(client, userdata, flags, reason_code, properties):
|
|
49
|
+
if reason_code.is_failure:
|
|
50
|
+
connected.clear()
|
|
51
|
+
print("MQTT connection failed:", reason_code)
|
|
52
|
+
else:
|
|
53
|
+
connected.set()
|
|
54
|
+
print("Connected to MQTT Broker!")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def on_disconnect(client, userdata, disconnect_flags, reason_code, properties):
|
|
58
|
+
connected.clear()
|
|
59
|
+
print("Disconnected from MQTT Broker:", reason_code)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
client = mqtt.Client(
|
|
63
|
+
callback_api_version=mqtt.CallbackAPIVersion.VERSION2,
|
|
64
|
+
client_id="sensor_publisher",
|
|
65
|
+
protocol=mqtt.MQTTv311
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
client.on_connect = on_connect
|
|
69
|
+
client.on_disconnect = on_disconnect
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
print("Connecting to MQTT Broker...")
|
|
73
|
+
client.connect(BROKER, PORT, 60)
|
|
74
|
+
client.loop_start()
|
|
75
|
+
|
|
76
|
+
while True:
|
|
77
|
+
if not connected.wait(timeout=2):
|
|
78
|
+
print("Waiting for MQTT connection...")
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
temperature = round(random.uniform(20, 40), 2)
|
|
82
|
+
humidity = round(random.uniform(40, 90), 2)
|
|
83
|
+
|
|
84
|
+
data = {
|
|
85
|
+
"temperature": temperature,
|
|
86
|
+
"humidity": humidity
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
payload = json.dumps(data)
|
|
90
|
+
|
|
91
|
+
result = client.publish(TOPIC, payload, qos=1)
|
|
92
|
+
|
|
93
|
+
if result.rc == mqtt.MQTT_ERR_SUCCESS:
|
|
94
|
+
try:
|
|
95
|
+
result.wait_for_publish(timeout=10)
|
|
96
|
+
|
|
97
|
+
if result.is_published():
|
|
98
|
+
print("Published successfully:", payload)
|
|
99
|
+
else:
|
|
100
|
+
print("Publishing timed out")
|
|
101
|
+
|
|
102
|
+
except RuntimeError as e:
|
|
103
|
+
print("Publishing error:", e)
|
|
104
|
+
else:
|
|
105
|
+
print("Publish failed:", mqtt.error_string(result.rc))
|
|
106
|
+
|
|
107
|
+
time.sleep(5)
|
|
108
|
+
|
|
109
|
+
except KeyboardInterrupt:
|
|
110
|
+
print("\nPublisher stopped.")
|
|
111
|
+
|
|
112
|
+
except Exception as e:
|
|
113
|
+
print("Error:", e)
|
|
114
|
+
|
|
115
|
+
finally:
|
|
116
|
+
client.loop_stop()
|
|
117
|
+
client.disconnect()
|
|
118
|
+
print("MQTT connection closed.")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
app.py:
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
from flask import Flask, jsonify, render_template
|
|
125
|
+
from supabase import create_client
|
|
126
|
+
from dotenv import load_dotenv
|
|
127
|
+
import paho.mqtt.client as mqtt
|
|
128
|
+
import os
|
|
129
|
+
import json
|
|
130
|
+
import threading
|
|
131
|
+
|
|
132
|
+
load_dotenv()
|
|
133
|
+
|
|
134
|
+
app = Flask(__name__)
|
|
135
|
+
|
|
136
|
+
# Supabase connection
|
|
137
|
+
SUPABASE_URL = os.getenv("SUPABASE_URL")
|
|
138
|
+
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
|
|
139
|
+
|
|
140
|
+
if not SUPABASE_URL or not SUPABASE_KEY:
|
|
141
|
+
raise ValueError(
|
|
142
|
+
"SUPABASE_URL and SUPABASE_KEY must be set in the .env file"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
|
|
146
|
+
|
|
147
|
+
# MQTT configuration
|
|
148
|
+
BROKER = "test.mosquitto.org"
|
|
149
|
+
PORT = 1883
|
|
150
|
+
TOPIC = "iot/sensor"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# MQTT callback
|
|
154
|
+
def on_connect(client, userdata, flags, reason_code, properties):
|
|
155
|
+
if reason_code.is_failure:
|
|
156
|
+
print("MQTT connection failed:", reason_code)
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
print("Connected to MQTT Broker!")
|
|
160
|
+
|
|
161
|
+
result, mid = client.subscribe(TOPIC)
|
|
162
|
+
|
|
163
|
+
if result == mqtt.MQTT_ERR_SUCCESS:
|
|
164
|
+
print("Subscribed to:", TOPIC)
|
|
165
|
+
else:
|
|
166
|
+
print("Subscription failed:", mqtt.error_string(result))
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# Receive MQTT messages
|
|
170
|
+
def on_message(client, userdata, msg):
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
data = json.loads(msg.payload.decode())
|
|
174
|
+
|
|
175
|
+
temperature = float(data["temperature"])
|
|
176
|
+
humidity = float(data["humidity"])
|
|
177
|
+
|
|
178
|
+
reading = {
|
|
179
|
+
"temperature": temperature,
|
|
180
|
+
"humidity": humidity
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# Store in Supabase
|
|
184
|
+
supabase.table("mqtt_readings").insert(reading).execute()
|
|
185
|
+
|
|
186
|
+
print("Received and Stored:", reading)
|
|
187
|
+
|
|
188
|
+
except Exception as e:
|
|
189
|
+
print("Error:", e)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# MQTT subscriber
|
|
193
|
+
mqtt_client = mqtt.Client(
|
|
194
|
+
callback_api_version=mqtt.CallbackAPIVersion.VERSION2,
|
|
195
|
+
client_id="dashboard_subscriber"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
mqtt_client.on_connect = on_connect
|
|
199
|
+
mqtt_client.on_message = on_message
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# Start MQTT in background thread
|
|
203
|
+
def start_mqtt():
|
|
204
|
+
while True:
|
|
205
|
+
try:
|
|
206
|
+
print("Connecting to MQTT broker...")
|
|
207
|
+
mqtt_client.connect(BROKER, PORT, 60)
|
|
208
|
+
mqtt_client.loop_forever()
|
|
209
|
+
|
|
210
|
+
except Exception as e:
|
|
211
|
+
print("MQTT connection error:", e)
|
|
212
|
+
print("Retrying in 5 seconds...")
|
|
213
|
+
import time
|
|
214
|
+
time.sleep(5)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
threading.Thread(target=start_mqtt, daemon=True).start()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# Dashboard
|
|
221
|
+
@app.route("/")
|
|
222
|
+
def dashboard():
|
|
223
|
+
return render_template("index.html")
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# Retrieve records from Supabase
|
|
227
|
+
@app.route("/api/readings")
|
|
228
|
+
def get_readings():
|
|
229
|
+
|
|
230
|
+
result = (
|
|
231
|
+
supabase.table("mqtt_readings")
|
|
232
|
+
.select("*")
|
|
233
|
+
.order("created_at", desc=True)
|
|
234
|
+
.limit(50)
|
|
235
|
+
.execute()
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
return jsonify(result.data)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
if __name__ == "__main__":
|
|
242
|
+
app.run(debug=True, use_reloader=False)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
templates/index.html:
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
<!DOCTYPE html>
|
|
249
|
+
<html>
|
|
250
|
+
<head>
|
|
251
|
+
<title>MQTT IoT Dashboard</title>
|
|
252
|
+
|
|
253
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
254
|
+
|
|
255
|
+
<style>
|
|
256
|
+
body {
|
|
257
|
+
font-family: Arial;
|
|
258
|
+
background: #eef2f7;
|
|
259
|
+
text-align: center;
|
|
260
|
+
padding: 30px;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
h1 {
|
|
264
|
+
color: #1e293b;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.cards {
|
|
268
|
+
display: flex;
|
|
269
|
+
justify-content: center;
|
|
270
|
+
gap: 20px;
|
|
271
|
+
flex-wrap: wrap;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.card {
|
|
275
|
+
background: white;
|
|
276
|
+
padding: 25px;
|
|
277
|
+
width: 220px;
|
|
278
|
+
border-radius: 12px;
|
|
279
|
+
box-shadow: 0 3px 10px #ccc;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.value {
|
|
283
|
+
font-size: 35px;
|
|
284
|
+
color: #2563eb;
|
|
285
|
+
font-weight: bold;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
table {
|
|
289
|
+
margin: 30px auto;
|
|
290
|
+
width: 90%;
|
|
291
|
+
background: white;
|
|
292
|
+
border-collapse: collapse;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
th, td {
|
|
296
|
+
padding: 12px;
|
|
297
|
+
border: 1px solid #ddd;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
th {
|
|
301
|
+
background: #2563eb;
|
|
302
|
+
color: white;
|
|
303
|
+
}
|
|
304
|
+
</style>
|
|
305
|
+
</head>
|
|
306
|
+
|
|
307
|
+
<body>
|
|
308
|
+
|
|
309
|
+
<h1>MQTT IoT Sensor Dashboard</h1>
|
|
310
|
+
|
|
311
|
+
<div class="cards">
|
|
312
|
+
|
|
313
|
+
<div class="card">
|
|
314
|
+
<h2>Temperature</h2>
|
|
315
|
+
<div class="value" id="temperature">-- °C</div>
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
<div class="card">
|
|
319
|
+
<h2>Humidity</h2>
|
|
320
|
+
<div class="value" id="humidity">-- %</div>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
<h2>Sensor Reading History</h2>
|
|
326
|
+
|
|
327
|
+
<table>
|
|
328
|
+
<thead>
|
|
329
|
+
<tr>
|
|
330
|
+
<th>Temperature (°C)</th>
|
|
331
|
+
<th>Humidity (%)</th>
|
|
332
|
+
<th>Timestamp</th>
|
|
333
|
+
</tr>
|
|
334
|
+
</thead>
|
|
335
|
+
|
|
336
|
+
<tbody id="readings"></tbody>
|
|
337
|
+
</table>
|
|
338
|
+
|
|
339
|
+
<script>
|
|
340
|
+
async function loadReadings() {
|
|
341
|
+
|
|
342
|
+
const response = await fetch("/api/readings");
|
|
343
|
+
const data = await response.json();
|
|
344
|
+
|
|
345
|
+
if (data.length > 0) {
|
|
346
|
+
|
|
347
|
+
document.getElementById("temperature").innerText =
|
|
348
|
+
data[0].temperature + " °C";
|
|
349
|
+
|
|
350
|
+
document.getElementById("humidity").innerText =
|
|
351
|
+
data[0].humidity + " %";
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const table = document.getElementById("readings");
|
|
355
|
+
|
|
356
|
+
table.innerHTML = "";
|
|
357
|
+
|
|
358
|
+
data.forEach(item => {
|
|
359
|
+
|
|
360
|
+
table.innerHTML += `
|
|
361
|
+
<tr>
|
|
362
|
+
<td>${item.temperature}</td>
|
|
363
|
+
<td>${item.humidity}</td>
|
|
364
|
+
<td>${new Date(item.created_at).toLocaleString()}</td>
|
|
365
|
+
</tr>
|
|
366
|
+
`;
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
loadReadings();
|
|
371
|
+
|
|
372
|
+
setInterval(loadReadings, 5000);
|
|
373
|
+
</script>
|
|
374
|
+
|
|
375
|
+
</body>
|
|
376
|
+
</html>
|