스마트 디바이스 10주차 Firebase - yubiine/25-1_smartdevice GitHub Wiki

⭐️목표

ESP32에서 수집한 센서 데이터를 Firebase Realtime Database에 저장하고 이를 활용해 데이터를 시각화 및 제어하는 방법을 익힌다.

📌Firebase란?

  • Firebase는 Google이 제공하는 모바일 및 웹 애플리케이션 개발 플랫폼입니다.
  • 서버 없이 빠르게 앱을 개발하고, 사용자 데이터를 저장·분석하며, 실시간으로 기능을 확장할 수 있습니다.

🔹 Firebase의 특징

특징 설명
BaaS (Backend-as-a-Service) 서버 구축 없이 데이터 저장, 인증, 호스팅 등 백엔드 기능 제공
실시간 데이터 처리 데이터 변경을 클라이언트에 실시간으로 반영
크로스 플랫폼 지원 Android, iOS, 웹 등 다양한 플랫폼 지원
구글 생태계와의 통합 Google Cloud, Analytics, AdMob 등과 손쉽게 연동

🔹 Firebase의 흐름도

  1. ESP32가 센서 데이터를 수집
  2. Firebase Realtime Database에 저장
  3. 웹앱에서 실시간 모니터링
  4. Firebase Hosting을 통해 전 세계 어디서나 접근 가능

🔹 Firebase의 장점

  • 서버 개발 없이 빠른 MVP 제작 가능
  • 실시간 데이터 처리에 최적화
  • 보안 규칙 설정이 유연함
  • 다양한 라이브러리 및 SDK 제공
  • IoT, 모바일, 웹 프로젝트에 모두 적합

📌Firebase 환경설정

프로젝트 생성

image

Realtime Database 생성

  • 메뉴에서 ‘Build > Realtime Database’ 선택
  • 테스트 모드로 설정
image

규칙 설정

image

Authentication

  • 익명(Anonymous) 방식을 선택하고 사용 설정 On
image

프로젝트 설정으로 이동

  • 웹 API키를 확인하고 복사
image

라이브러리 다운

image

📌실습 1 (기본 LED 제어)

코드

#include <WiFi.h>
#include <FirebaseESP32.h>

//Provide the token generation process info.
#include "addons/TokenHelper.h"
//Provide the RTDB payload printing info and other helper functions.
#include "addons/RTDBHelper.h"

// Firebase 설정
#define FIREBASE_HOST ""
#define FIREBASE_AUTH ""
#define WIFI_SSID ""
#define WIFI_PASSWORD ""

// 내장 LED 핀 설정
#define LED_PIN 2

// Firebase 객체 생성
FirebaseData firebaseData;

FirebaseAuth auth;
FirebaseConfig config;
bool signupOK = false;
unsigned long sendDataPrevMillis = 0;

void setup_wifi() {
  delay(10);
  // Wi-Fi 네트워크에 연결 시작
  Serial.println();
  Serial.print("연결 중인 Wi-Fi: ");
  Serial.println(WIFI_SSID);

  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("Wi-Fi 연결됨");
  Serial.println("IP 주소: ");
  Serial.println(WiFi.localIP());
}

void setup() {
  // 시리얼 통신 초기화
  Serial.begin(115200);

  // WiFi 연결
  setup_wifi();

  // API 키를 할당합니다.(필수)
  config.api_key = FIREBASE_AUTH;

  //RTDB URL을 할당합니다.(필수)
  config.database_url = FIREBASE_HOST;

  /* Sign up */
  if (Firebase.signUp(&config, &auth, "", "")){
    Serial.println("ok");
    signupOK = true;
  }
  else{
    Serial.printf("%s\n", config.signer.signupError.message.c_str());
  }

  // 긴 실행 시간이 필요한 토큰 생성 작업을 위한 콜백 함수를 할당합니다.
  config.token_status_callback = tokenStatusCallback; 

  Firebase.begin(&config, &auth);
  Firebase.reconnectWiFi(true);
  // 내장 LED 핀 설정
  pinMode(LED_PIN, OUTPUT);

  // "/ledState" 경로를 0으로 초기화
  if (Firebase.ready() && signupOK)
  {
    if (Firebase.setInt(firebaseData, "/ledState", 0)){
      Serial.println("PASSED");
      Serial.println("PATH: " + firebaseData.dataPath());
      Serial.println("TYPE: " + firebaseData.dataType());
    }
    else {
      Serial.println("FAILED");
      Serial.println("REASON: " + firebaseData.errorReason());
    }
  }
}
void loop() {
  // Firebase.ready() 함수는 인증 작업을 처리하기 위해 반복적으로 호출되어야 합니다.
  if (Firebase.ready() && signupOK && \
  (millis() - sendDataPrevMillis > 2000 || sendDataPrevMillis == 0))
  {
    sendDataPrevMillis = millis();

    // Firebase에서 LED 상태 가져오기
    int ledState = 0;
    if (Firebase.getInt(firebaseData, "/ledState")) {
      if (firebaseData.dataType() == "int") {
        ledState = firebaseData.intData();
        Serial.println(ledState);
        if (ledState == 1) {
          digitalWrite(LED_PIN, HIGH);
        } else {
          digitalWrite(LED_PIN, LOW);
        }
      }
    }
    else {
        Serial.println(firebaseData.errorReason());
    }
  }
}
IMG_7459.MOV

📌실습 2 (빛 감지 센서값 읽기)

image

코드

int sensorPin = 34;
int value = 0;

void setup() {
  Serial.begin(115200);
}

void loop() {
  value = analogRead(sensorPin);

  Serial.println(value);

  delay(500);
}
IMG_7460.MOV

📌실습 3 (빛 감지 센서 값 수집 및 로깅)

코드

#include <WiFi.h>
#include <FirebaseESP32.h>

// 토큰 생성 프로세스 정보 제공
#include "addons/TokenHelper.h"
// RTDB 페이로드 출력 정보 및 기타 도움 함수 제공
#include "addons/RTDBHelper.h"

// Firebase 설정
#define FIREBASE_HOST ""
#define FIREBASE_AUTH ""
#define WIFI_SSID ""
#define WIFI_PASSWORD ""


// Firebase 객체 정의
FirebaseData fbdo;
FirebaseAuth auth;
FirebaseConfig config;
bool signupOK = false;

// 데이터베이스 주 경로
String databasePath = "/room1";
String sensorPath = "/lightsensor";
String timePath = "/timestamp";

/// 부모 노드 (현재 시간 정보로 매 루프마다 업데이트)
String parentPath;

int timestamp;
FirebaseJson json;

const char* ntpServer = "pool.ntp.org";

// 빛 센서 핀
int sensorPin = 34;

// 타이머 변수 (새로운 측정 값을 20초마다 전송)
unsigned long sendDataPrevMillis = 0;
unsigned long timerDelay = 60000;  //1 minutes

// WiFi 초기화
void setup_wifi() {
  delay(10);
  // Wi-Fi 네트워크에 연결 시작
  Serial.println();
  Serial.print("연결 중인 Wi-Fi: ");
  Serial.println(WIFI_SSID);

  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("Wi-Fi 연결됨");
  Serial.println("IP 주소: ");
  Serial.println(WiFi.localIP());
}
// 현재 epoch 시간을 가져오는 함수
unsigned long getTime() {
  time_t now;
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    //Serial.println("Failed to obtain time");
    return (0);
  }
  time(&now);
  return now;
}

void setup() {
  Serial.begin(115200);

  // WiFi 연결
  setup_wifi();
  configTime(0, 0, ntpServer);

  // API 키 할당 (필수)
  config.api_key = FIREBASE_AUTH;

  // RTDB URL 할당 (필수)
  config.database_url = FIREBASE_HOST;

  Firebase.reconnectWiFi(true);
  fbdo.setResponseSize(4096);

  /* 회원 가입 */
  if (Firebase.signUp(&config, &auth, "", "")) {
    Serial.println("ok");
    signupOK = true;
  } else {
    Serial.printf("%s\n", config.signer.signupError.message.c_str());
  }

  // 장기 실행 토큰 생성 작업에 대한 콜백 함수 할당
  config.token_status_callback = tokenStatusCallback;

  // 토큰 생성 최대 재시도 횟수 할당
  config.max_token_generation_retry = 5;

  // Firebase 인증 및 설정과 함께 라이브러리 초기화
  Firebase.begin(&config, &auth);
}

void loop() {

  // Send new readings to database
  if (Firebase.ready() && signupOK
      && (millis() - sendDataPrevMillis > timerDelay
          || sendDataPrevMillis == 0)) {
    sendDataPrevMillis = millis();

    // 현재 타임스탬프 가져오기
    timestamp = getTime();
    Serial.print("time: ");
    Serial.println(timestamp);

    parentPath = databasePath + "/" + String(timestamp);

    json.set(sensorPath.c_str(), String(analogRead(sensorPin)));
    json.set(timePath, String(timestamp));
    Serial.println("Set json... ");
    if (Firebase.setJSON(fbdo, parentPath.c_str(), json))
      Serial.println("ok");
    else
      Serial.println(fbdo.errorReason());
  }
}

결과

image

📌실습 4 (Firebase 앱 구현 및 HTML 시간별 조도 그래프)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
    <meta http-equiv="Pragma" content="no-cache" />
    <meta http-equiv="Expires" content="0" />
    <title>ESP 데이터 기록 Firebase 앱</title>
    <style>
        #chart_div {
            width: 1200px;
            height: 500px;
        }

        #gauge_div {
            height: 300px;
        }

        div {
            display: table;
            margin-right: auto;
            margin-left: auto;
        }
    </style>
    <!-- Firebase SDK 포함 -->
    <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>

    <!-- 필요한 Firebase 기능만 포함 -->
    <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-auth.js"></script>
    <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-database.js"></script>

    <script>
    // 웹 앱의 Firebase 구성
    const firebaseConfig = {
        apiKey: "API 키",
        authDomain: "esp32-light-sensor-49d0d.firebaseapp.com",
        databaseURL: "https://esp32-light-sensor-49d0d-default-rtdb.firebaseio.com",
        projectId: "esp32-light-sensor-49d0d",
        storageBucket: "esp32-light-sensor-49d0d.appspot.com",
        messagingSenderId: "473819592135",
        appId: "1:473819592135:web:f839002c4c51d1daa4d5e2"
    };

    // Firebase 초기화
    firebase.initializeApp(firebaseConfig);

    // 인증과 데이터베이스 참조 생성
    const auth = firebase.auth();
    const db = firebase.database();
    </script>
</head>
<body>
    <!-- 차트를 위한 컨테이너 -->
    <div>
        <div id="chart_div"></div>
    </div>
    <div>
        <div id="gauge_div"></div>
    </div>
    <script type="text/javascript"
            src="https://www.gstatic.com/charts/loader.js"></script>
    <script>
        // 현재 차트 패키지 로드
        google.charts.load('current', {
        packages: ['corechart', 'line', 'gauge'],
        });
		    // API가 로드되었을 때 콜백 함수 설정
    google.charts.setOnLoadCallback(drawChart);

    function drawChart() {
    // 기본 값으로 데이터 객체 생성
    let data = google.visualization.arrayToDataTable([
    ['Time', 'Light Sensor'],
    ["00:00", 0],
    ]);

    let gauge_data = google.visualization.arrayToDataTable([
    ['Light Sensor'],
    [0],
    ]);

    // 제목, 색상 등이 포함된 옵션 객체 생성
    let options = {
    max: 2048, //4096,
    hAxis: {
    //textPosition: 'none',
    },
    vAxis: {

    },
    };

    // 차트 그리기
    let chart = new google.visualization.LineChart(
    document.getElementById('chart_div')
    );
    chart.draw(data, options);

    let gauge_chart = new google.visualization.Gauge(
        document.getElementById('gauge_div')
    );
    gauge_chart.draw(gauge_data, options);

    // 데이터베이스 경로
    var dbPath = 'room1';

    // 데이터베이스 참조
    var dbRef = firebase.database().ref(dbPath);

    // 표시할 최대 데이터 행 수
    let maxDatas = 50;

    // 최신 측정값 가져와서 차트에 표시 (표시되는 측정값 수는 chartRange 값에 해당)
    dbRef.orderByKey().limitToLast(maxDatas).on('child_added', snapshot =>{
    var jsonData = snapshot.toJSON(); // 예: {lightsensor: 2502, timestamp:1641317355}

    // 값 저장
    var lightsensor = Number(jsonData.lightsensor);
    var timestamp = epochToDateTime(jsonData.timestamp);

    // 차트에 값 표시
    if (data.getNumberOfRows() > maxDatas) {
    data.removeRows(0, data.getNumberOfRows() - maxDatas);
    }
    data.addRow([timestamp, lightsensor]);
    chart.draw(data, options);

    gauge_data.setValue(0, 0, lightsensor);
    gauge_chart.draw(gauge_data, options);
    });
    }
    // 에포크 Time을 JavaScript Date 객체로 변환
    function epochToJsDate(epochTime){
    return new Date(epochTime*1000);
    }

    // Time을 사람이 읽을 수 있는 형식 (HH:MM)으로 변환
    function epochToDateTime(epochTime) {
    var epochDate = new Date(epochToJsDate(epochTime));
    var dateTime =
    ("00" + epochDate.getHours()).slice(-2) +
    ":" +
    ("00" + epochDate.getMinutes()).slice(-2);
    return dateTime;
    }
    </script>
</body>
</html>

앱 설정

image
  • 앱 스크립트 복사 후 html에 활용

결과

image

📌실습 5 (Firebase 웹 앱 Hosting)

⚠️ **GitHub.com Fallback** ⚠️