센서 데이터 대시보드 - ESP32, WiFi, Bluetooth, Android
이 저장소에는 ESP32 기반 장치와 DHT11 센서의 온도 및 습도 데이터와 같이 센서에서 데이터를 독립적으로 수집하기 위한 Flask 서버가 포함되어 있습니다. 이 시스템은 Bluetooth를 통해 Wi-Fi 자격 증명을 수신하고 HTTP를 통해 서버로 데이터를 전송합니다. 서버는 SQLite 데이터베이스에 데이터를 저장하고 시각화를 위한 간단한 웹 기반 대시보드를 제공합니다. 이 설계를 통해 다른 유형의 센서 데이터를 쉽게 추가하고 통합할 수 있습니다. 원문은 깃허브 소스 링크합니다.
목차
ESP32 장치 코드
Flask 서버 코드
대시보드
설치
프로젝트 실행
ESP32 장치 코드
single-node.ino 파일에는 ESP32 마이크로컨트롤러용 Arduino 코드가 포함되어 있습니다. 이 코드는 DHT11 센서에서 온도 및 습도 데이터를 읽고 Bluetooth를 통해 Wi-Fi 자격 증명을 수신하고 HTTP를 통해 Flask 서버로 데이터를 전송합니다.
라이브러리
다음 라이브러리는 코드를 컴파일하는 데 필요합니다.
- WiFi.h
- HTTPClient.h
- BluetoothSerial.h
- ArduinoJson.h
- time.h
- ctime
- iomanip
- DHT.h
하드웨어
이 코드는 DHT11 센서가 ESP32의 D4 핀에 연결되어 있다고 가정합니다.
Flask 서버 코드
이미지
app.py 파일에는 API를 통해 들어오는 센서 데이터를 수신하고 SQLite 데이터베이스에 저장하며 데이터를 보기 위한 간단한 대시보드를 제공하는 Flask 서버 코드가 포함되어 있습니다.
종속성
다음 Python 패키지를 설치해야 합니다.
- Flask
- Flask-SQLAlchemy
대시보드
index.html 파일은 센서 데이터를 보기 위한 간단한 HTML 대시보드입니다. Flask 서버에서 데이터를 가져와 표에 표시합니다.
종속성
대시보드는 표를 렌더링하기 위해 Tabulator 라이브러리를 사용합니다. 라이브러리는 CDN에서 로드됩니다.
설치
- 아직 설치하지 않았다면 Python 3과 pip를 설치합니다.
- 다음을 실행하여 필요한 Python 패키지를 설치합니다. pip install Flask Flask-SQLAlchemy
- Arduino IDE를 설치하고 ESP32에 맞게 구성합니다.
- Arduino IDE의 라이브러리 관리자를 사용하여 필요한 Arduino 라이브러리를 설치합니다.
- DHT 센서 라이브러리
- ArduinoJson
프로젝트 실행
1. Arduino IDE를 사용하여 single-node.ino 스케치를 ESP32에 업로드합니다.
2. 터미널이나 명령 프롬프트에서 다음 명령을 실행하여 Flask 서버를 실행합니다. python3 server.py
서버는 http://0.0.0.0:5000/에서 시작됩니다.
3. 웹 브라우저를 열고 http://localhost:5000/으로 이동하여 대시보드를 봅니다.
4. 스마트폰이나 컴퓨터에서 Bluetooth 직렬 터미널 앱을 사용하여 Bluetooth를 통해 ESP32에 연결합니다. Wi-Fi 자격 증명을 다음 형식의 JSON 문자열로 보냅니다. { "ssid": "your_wifi_ssid", "password": "your_wifi_password" }
ESP32는 Wi-Fi 네트워크에 연결하고 10초마다 센서 데이터를 Flask 서버로 전송하기 시작합니다.
5. 대시보드가 새 센서 데이터로 업데이트됩니다. 페이지를 새로 고쳐 최신 데이터를 확인하세요.
참고: `esp32_device.ino에서 serverUrl 변수를 바꿔야 합니다.
프로젝트 구조
- single-node.ino: 센서 데이터를 읽고, Wi-Fi에 연결하고, 서버로 데이터를 전송하는 ESP32 Arduino 코드입니다.
- app.py: 들어오는 데이터를 처리하고, SQLite 데이터베이스에 저장하고, 대시보드를 제공하는 Flask 서버 코드입니다.
- index.html: 대시보드용 HTML 파일로, 센서 데이터를 테이블 형식으로 표시합니다.
- sensor_data.db: 센서 데이터를 저장하는 SQLite 데이터베이스 파일입니다(Flask 서버가 실행될 때 자동으로 생성됨).
문제 해결
1. ESP32가 Wi-Fi 네트워크에 연결할 수 없는 경우 제공된 자격 증명이 올바르고 ESP32가 Wi-Fi 네트워크 범위 내에 있는지 확인하세요.
2. ESP32가 Flask 서버로 데이터를 전송할 수 없는 경우 서버가 실행 중이고 esp32_device.ino 파일의 IP 주소와 포트가 올바른지 확인하세요.
3. 대시보드에 데이터가 표시되지 않는 경우 Flask 서버가 실행 중이고 ESP32가 데이터를 올바르게 전송하는지 확인하세요.
라이센스
이 프로젝트는 GNU General Public License v3.0(GPL-3.0)에 따라 제공됩니다. 자세한 내용은 LICENSE 파일을 참조하세요.
아두이노 코드를 여기 카피해 둔다.
#include <WiFi.h>
#include <HTTPClient.h>
#include <BluetoothSerial.h>
#include <ArduinoJson.h>
#include <time.h>
#include <ctime>
#include <iomanip>
#include <DHT.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <Preferences.h>
#define DHTPIN 4 // Pin D4
#define DHTTYPE DHT11 // DHT 11
bool waitingForNewCredentials = false;
String lastReceivedDescription = "";
String receivedDescription = "";
Preferences preferences;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org");
DHT dht(DHTPIN, DHTTYPE); // Initialize DHT sensor
BluetoothSerial SerialBT;
String ssid = "";
String password = "";
// Replace with your desired server URL
const char *serverUrl = "http://192.168.1.100/api/sensor_data";
const char *serverUrlMetadata = "http://192.168.1.100/api/metadata";
String device_name_prefix = "ESP32_";
String device_name;
typedef struct struct_peerInfo {
uint8_t mac[6];
int deviceNumber;
} peerInfo_t;
int deviceNumber;
void onDataReceived(const uint8_t *mac_addr, const uint8_t *data, int len) {
int receivedDeviceNumber = *(int *)data;
if (receivedDeviceNumber >= deviceNumber) {
deviceNumber = receivedDeviceNumber + 1;
}
}
// Rest of the code (parseJsonData, getIsoTimeString, connectToWiFi, sendDataToServer)
bool parseJsonData(String &json, String &ssid, String &password) {
StaticJsonDocument<256> doc;
DeserializationError error = deserializeJson(doc, json);
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.c_str());
return false;
}
ssid = doc["ssid"].as<String>();
password = doc["password"].as<String>();
if (!doc["description"].isNull()) {
receivedDescription = doc["description"].as<String>();
Serial.print("Parsed description: ");
Serial.println(receivedDescription);
} else {
receivedDescription = "";
Serial.println("No description found in JSON data.");
}
return true;
}
String getIsoTimeString() {
timeClient.update();
unsigned long epochTime = timeClient.getEpochTime();
time_t now = epochTime;
struct tm timeinfo;
char iso_time[21];
gmtime_r(&now, &timeinfo); // Use gmtime_r for UTC time
strftime(iso_time, sizeof(iso_time), "%Y-%m-%dT%H:%M:%S", &timeinfo);
return String(iso_time);
}
void connectToWiFi(const char *ssid, const char *password) {
unsigned long startTime;
unsigned long timeout = 3000; // 3 seconds timeout for the connection attempt
int maxRetries = 2;
int retryCount = 0;
bool connected = false;
while (!connected && retryCount < maxRetries) {
WiFi.disconnect();
WiFi.begin(ssid, password);
Serial.print("Connecting to Wi-Fi");
startTime = millis();
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
// Check for timeout
if (millis() - startTime > timeout) {
Serial.println("");
Serial.print("Connection attempt timed out. Retry ");
Serial.print(retryCount + 1);
Serial.print(" of ");
Serial.print(maxRetries);
Serial.println(".");
break;
}
}
if (WiFi.status() == WL_CONNECTED) {
connected = true;
} else {
retryCount++;
}
}
if (connected) {
Serial.println("");
Serial.print("Connected. IP address: ");
Serial.println(WiFi.localIP());
timeClient.begin();
timeClient.update();
preferences.putString("ssid", ssid);
preferences.putString("password", password);
} else {
Serial.print("Failed to connect after ");
Serial.print(maxRetries);
Serial.println(" attempts. Giving up.");
WiFi.disconnect();
preferences.putString("ssid", "");
preferences.putString("password", "");
}
}
void sendDataToServer(float temperature, float humidity) {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(serverUrl);
// Get the current timestamp
String timestamp = getIsoTimeString();
// Prepare the JSON payload
String payload = "{";
payload += "\"device_name\":\"" + String(device_name) + "\","; // Include the device_name in the payload
payload += "\"timestamp\":\"" + timestamp + "\",";
payload += "\"data\":["; // Start data array
payload += "{\"key\":\"temperature\",\"value\":" + String(temperature) + "},"; // Add temperature object
payload += "{\"key\":\"humidity\",\"value\":" + String(humidity) + "}"; // Add humidity object
payload += "]"; // End data array
payload += "}";
Serial.print("Payload: ");
Serial.println(payload);
http.addHeader("Content-Type", "application/json");
int httpResponseCode = http.POST(payload);
if (httpResponseCode > 0) {
String response = http.getString();
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
Serial.print("Response: ");
Serial.println(response);
} else {
Serial.print("Error sending data. HTTP error code: ");
Serial.println(httpResponseCode);
}
http.end();
} else {
Serial.println("Error: Not connected to Wi-Fi");
}
}
void sendMetaDataToServer(const String &description) {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(serverUrlMetadata);
// Get the current timestamp
String timestamp = getIsoTimeString();
// Prepare the JSON payload
String payload = "{";
payload += "\"device_name\":\"" + String(device_name) + "\","; // Include the device_name in the payload
payload += "\"timestamp\":\"" + timestamp + "\",";
payload += "\"metadata\":["; // Start metadata array
payload += "{\"key\":\"description\",\"value\":\"" + description + "\"}"; // Add description object
payload += "]"; // End metadata array
payload += "}";
Serial.print("Metadata Payload: ");
Serial.println(payload);
http.addHeader("Content-Type", "application/json");
int httpResponseCode = http.POST(payload);
if (httpResponseCode > 0) {
String response = http.getString();
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
Serial.print("Response: ");
Serial.println(response);
} else {
Serial.print("Error sending metadata. HTTP error code: ");
Serial.println(httpResponseCode);
}
http.end();
} else {
Serial.println("Error: Not connected to Wi-Fi");
}
}
String getDeviceName() {
uint8_t baseMac[6];
esp_read_mac(baseMac, ESP_MAC_WIFI_STA);
char baseMacChr[18] = {0};
sprintf(baseMacChr, "ESP32_%02X%02X%02X", baseMac[3], baseMac[4], baseMac[5]);
return String(baseMacChr);
}
void setup() {
Serial.begin(115200);
preferences.begin("wifi", false);
device_name = getDeviceName();
SerialBT.begin(device_name);
Serial.println("Bluetooth device started, waiting for connections...");
dht.begin(); // Initialize DHT sensor
}
void loop() {
// Check for incoming JSON data via Bluetooth
if (SerialBT.available()) {
waitingForNewCredentials = true;
String receivedData = SerialBT.readStringUntil('\n');
receivedData.trim();
Serial.print("Received data: ");
Serial.println(receivedData);
String ssid, password;
if (parseJsonData(receivedData, ssid, password)) {
Serial.println("Parsed data:");
Serial.print(" SSID: ");
Serial.println(ssid);
Serial.print(" Password: ");
Serial.println(password);
// Connect to Wi-Fi using the received credentials
if (WiFi.status() != WL_CONNECTED) {
connectToWiFi(ssid.c_str(), password.c_str());
}
waitingForNewCredentials = false;
// Check if a new description is received and send it
Serial.print("Check condition: ");
Serial.print("waitingForNewCredentials: ");
Serial.print(waitingForNewCredentials);
Serial.print(", receivedDescription.length(): ");
Serial.print(receivedDescription.length());
Serial.print(", receivedDescription: ");
Serial.print(receivedDescription);
Serial.print(", lastReceivedDescription: ");
Serial.println(lastReceivedDescription);
if (!waitingForNewCredentials && receivedDescription.length() > 0 && receivedDescription != lastReceivedDescription) {
sendMetaDataToServer(receivedDescription);
lastReceivedDescription = receivedDescription;
receivedDescription = ""; // Clear received description
}
} else {
Serial.println("Error: Failed to parse JSON data");
}
}
if (WiFi.status() == WL_CONNECTED) {
// Read actual sensor readings
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();
if (isnan(humidity) || isnan(temperature)) {
Serial.println("Failed to read from DHT sensor!");
delay(10000); // Add a delay here before trying again
return;
}
sendDataToServer(temperature, humidity);
delay(10000); // Wait for 10 seconds before sending the next set of data
} else {
// If not connected and not waiting for new credentials, try to reconnect
if (!waitingForNewCredentials) {
ssid = preferences.getString("ssid", "");
password = preferences.getString("password", "");
Serial.print("Stored credentials - SSID: ");
Serial.println(ssid);
Serial.print("Stored credentials - Password: ");
Serial.println(password);
if (ssid.length() > 0 && password.length() > 0) {
connectToWiFi(ssid.c_str(), password.c_str());
}
// If still not connected, set the flag to wait for new credentials
if (WiFi.status() != WL_CONNECTED) {
waitingForNewCredentials = true;
}
}
}
}
'ESP32' 카테고리의 다른 글
TM74 LED Driver - 4 digit 7 segment (1) | 2024.11.11 |
---|---|
ESP32 4-digit 7-segment display (4) | 2024.11.11 |
Android 폰에서 ESP32로 데이터를 보내는 방법 (5) | 2024.11.11 |
esp32에서 블루투스를 사용하여 스마트폰과 데이터를 주고받는 방법 (2) | 2024.11.10 |
ESP32 74HC595 4자리 7세그먼트 디스플레이 (2) | 2024.11.08 |
ESP32-wroom-32 high resolution pinout and specs (2) | 2024.11.08 |
1.69inch LCD Display Module, 240×280 (2) | 2024.11.06 |
ds18b20 온도 센서 여러 개 스캔 주소 얻기 (3) | 2024.11.04 |
더욱 좋은 정보를 제공하겠습니다.~ ^^