본문 바로가기

ESP32

ESP32 센서 데이터 대시보드 BLE, Flask 서버, SQLite

반응형

 

센서 데이터 대시보드 - 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;
      }
    }
  }
}

 

 

 

https://www.youtube.com/watch?v=TH0vp7v0U4s

 

반응형

더욱 좋은 정보를 제공하겠습니다.~ ^^