This is an old revision of the document!
Proiectul consta intr-o statie meteo remote care aduna informatii despre temperatura, umiditate, presiunea atmosferica, nivelul de intensitate luminoasa si predictie a ploii si transmite toate aceste date prin wifi catre un webserver. Webserverul le afiseaza intr-un mod interactiv si usor de vizualizat si analizat pe un dashboard.
Varianta finala si functionala a proiectului se poate vedea pe urmatorul link https://youtu.be/XK-8px-DiMA?feature=shared
Mediul de dezvoltare:
Pe Arduino am implementat citirea senzorului DHT si trimiterea informatiilor catre ESP32.
SoftwareSerial espSerial(rxPin, txPin);
void setup() {
pinMode(rxPin, INPUT);
pinMode(txPin, OUTPUT);
Serial.begin(9600);
espSerial.begin(9600);
dht.begin();
}
void loop() {
Serial.println("Sending to ESP32...");
int16_t value = 1234;
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();
if (isnan(humidity) || isnan(temperature)) {
Serial.println("Failed to read from DHT sensor!");
return;
}
espSerial.println(temperature);
espSerial.println(humidity);
Serial.print("Humidity: ");
Serial.print(humidity);
Serial.print(" %\t");
Serial.print("Temperature: ");
Serial.print(temperature);
Serial.println(" *C");
delay(2000);
}
Pe ESP32 citesc informatiile de la senzorul bmp, primesc datele de la Arduino si le trimit pe toate prin Wifi catre un server implementat de mine.
void setup() {
Serial.begin(9600); // For Serial Monitor
Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2); // UART2
int counter = 0;
delay(2000); // Wait for power to stabilize
WiFi.disconnect(true); // Clear previous settings
delay(1000);
Serial.println("Scanning Wi-Fi...");
int networks = WiFi.scanNetworks();
if (networks == 0) {
Serial.println("No networks found.");
} else {
Serial.println("Networks found:");
for (int i = 0; i < networks; ++i) {
Serial.println(WiFi.SSID(i));
}
}
WiFi.begin(ssid, password);
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" Connected!");
Wire.begin(21, 22); // SDA = 21, SCL = 22
if (!bmp.begin()) {
Serial.println("Could not find a valid BMP180 sensor, check wiring!");
while (1) {}
}
}
void loop() {
while (Serial2.available() > 0) {
Serial2.read();
}
delay(2000);
String temperature = "";
String humidity = "";
temperature = Serial2.readStringUntil('\n'); // Read first line
temperature.trim();
humidity = Serial2.readStringUntil('\n'); // Read second line
humidity.trim();
float p = bmp.readPressure();
String pressure = String(p);
float a = bmp.readAltitude();
String altitude = String(a);
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(serverURL);
http.addHeader("Content-Type", "application/json");
String jsonPayload = "{\"temperature\":" + temperature +
",\"humidity\":" + humidity +
",\"pressure\":" + pressure +
",\"altitude\":" + altitude +
"}";
Serial.println(jsonPayload);
int httpResponseCode = http.POST(jsonPayload);
Serial.println("HTTP Response code: " + String(httpResponseCode));
String response = http.getString();
Serial.println("Server response: " + response);
if (httpResponseCode > 0) {
String response = http.getString();
Serial.println(httpResponseCode);
Serial.println(response);
} else {
Serial.print("Error in sending POST: ");
Serial.println(http.errorToString(httpResponseCode).c_str());
}
http.end();
}
}
Am implementat un server in Python folosind Flask, aceasta fiind partea de backend.
from flask import Flask, request, jsonify, render_template
from datetime import datetime
app = Flask(__name__)
latest_data = {
"temperature": None,
"humidity": None,
"pressure": None,
"timestamp": None
}
@app.route('/')
def index():
return render_template('index.html')
@app.route('/update', methods=['GET'])
def get_data():
return jsonify(latest_data)
@app.route('/data', methods=['POST'])
def receive_data():
data = request.get_json(force=True)
if data:
latest_data["temperature"] = float(data.get("temperature", 0))
latest_data["humidity"] = float(data.get("humidity", 0))
latest_data["pressure"] = float(data.get("pressure", 0))
latest_data["timestamp"] = datetime.now().strftime("%H:%M:%S")
return jsonify({"status": "success"}), 200
return jsonify({"status": "fail"}), 400
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Pentru a vizualiza datele in mod real intr-un mod cat mai placut si interactiv, am imple,mentat si partea de frontend a serverului, in HTML si JavaScript.
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Sensor Dashboard</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-streaming"></script>
<script src="https://cdn.jsdelivr.net/npm/@bernaferrari/gauge-chart"></script>
<script defer src="/static/script.js"></script>
<style>
body { font-family: sans-serif; text-align: center; padding: 20px; background: #f5f5f5; }
h1 { color: #333; }
.container { display: flex; flex-wrap: wrap; justify-content: center; gap: 20px; }
.card { background: white; padding: 20px; border-radius: 10px; box-shadow: 0 4px 10px rgba(0,0,0,0.1); }
canvas { max-width: 600px; }
.gauge-container { width: 250px; height: 150px; margin: auto; }
</style>
</head>
<body>
<h1>🌡️ Real-Time Sensor Dashboard</h1>
<div class="container">
<div class="card">
<h3>Temperature Gauge</h3>
<div id="gauge-temp" class="gauge-container"></div>
<p>Temp: <span id="temp">--</span> °C</p>
</div>
<div class="card">
<h3>Humidity Gauge</h3>
<div id="gauge-hum" class="gauge-container"></div>
<p>Humidity: <span id="hum">--</span> %</p>
</div>
</div>
<div class="container">
<div class="card">
<h3>Temperature - Last 10 Minutes</h3>
<canvas id="chart-temp" height="100"></canvas>
</div>
<div class="card">
<h3>Humidity - Last 10 Minutes</h3>
<canvas id="chart-hum" height="100"></canvas>
</div>
</div>
<div class="card">
<h3>Pressure</h3>
<p>📈 Pressure: <span id="pres">--</span> Pa</p>
<p>⏱️ Time: <span id="time">--:--:--</span></p>
</div>
</body>
</html>
JavaScript:
const tempSpan = document.getElementById('temp');
const humSpan = document.getElementById('hum');
const presSpan = document.getElementById('pres');
const timeSpan = document.getElementById('time');
const gaugeTemp = GaugeChart.gaugeChart(document.getElementById("gauge-temp"), {
arcDelimiters: [0.3, 0.6, 1],
arcColors: ["#00bfff", "#ffa500", "#ff0000"],
arcLabels: ["Low", "Medium", "High"],
rangeLabel: ["0°C", "50°C"],
centralLabel: ""
});
const gaugeHum = GaugeChart.gaugeChart(document.getElementById("gauge-hum"), {
arcDelimiters: [0.3, 0.6, 1],
arcColors: ["#add8e6", "#00ced1", "#007bff"],
arcLabels: ["Dry", "Moderate", "Humid"],
rangeLabel: ["0%", "100%"],
centralLabel: ""
});
const ctxTemp = document.getElementById('chart-temp').getContext('2d');
const ctxHum = document.getElementById('chart-hum').getContext('2d');
const tempChart = new Chart(ctxTemp, {
type: 'line',
data: {
datasets: [{
label: 'Temperature (°C)',
borderColor: 'rgba(255, 99, 132, 1)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
data: []
}]
},
options: {
plugins: {
streaming: {
frameRate: 30
}
},
scales: {
x: {
type: 'realtime',
realtime: {
duration: 600000,
refresh: 2000,
delay: 2000,
onRefresh: async chart => {
await fetchAndUpdate(chart, 'temperature');
}
}
},
y: {
beginAtZero: true
}
}
}
});
const humChart = new Chart(ctxHum, {
type: 'line',
data: {
datasets: [{
label: 'Humidity (%)',
borderColor: 'rgba(54, 162, 235, 1)',
backgroundColor: 'rgba(54, 162, 235, 0.2)',
data: []
}]
},
options: {
plugins: {
streaming: {
frameRate: 30
}
},
scales: {
x: {
type: 'realtime',
realtime: {
duration: 600000,
refresh: 2000,
delay: 2000,
onRefresh: async chart => {
await fetchAndUpdate(chart, 'humidity');
}
}
},
y: {
beginAtZero: true
}
}
}
});
async function fetchAndUpdate(chart, type) {
try {
const res = await fetch('/update');
const data = await res.json();
const now = Date.now();
if (type === 'temperature') {
chart.data.datasets[0].data.push({ x: now, y: data.temperature });
} else if (type === 'humidity') {
chart.data.datasets[0].data.push({ x: now, y: data.humidity });
}
// Update UI
tempSpan.textContent = data.temperature?.toFixed(1) ?? '--';
humSpan.textContent = data.humidity?.toFixed(1) ?? '--';
presSpan.textContent = data.pressure ?? '--';
timeSpan.textContent = data.timestamp ?? '--';
if (data.temperature != null) {
gaugeTemp.update({ percent: Math.min(data.temperature / 50, 1) });
}
if (data.humidity != null) {
gaugeHum.update({ percent: Math.min(data.humidity / 100, 1) });
}
} catch (err) {
console.error("Fetch error:", err);
}
}
Rezultatele obtinute in acest proiect se pot vedea pe urmatorul link: https://youtu.be/XK-8px-DiMA?feature=shared
Fişierele se încarcă pe wiki folosind facilitatea Add Images or other files. Namespace-ul în care se încarcă fişierele este de tipul :pm:prj20??:c? sau :pm:prj20??:c?:nume_student (dacă este cazul). Exemplu: Dumitru Alin, 331CC → :pm:prj2009:cc:dumitru_alin.