10주차 ‐ Firebase를 활용한 ESP32 IoT 프로젝트 - gitjs523/SmartDevice2025 GitHub Wiki
- Google이 제공하는 모바일 및 웹 애플리케이션 개발 플랫폼
- 서버 없이 빠르게 앱 개발
- 실시간으로 기능 확장 가능
- 주요 특징:
- 장기간 데이터 보존 및 분석 가능
- 여러 디바이스 간 실시간 데이터 공유 가능
- 데이터 분석하여 추세 파악 및 장치 성능 개선
- 데이터 보안 및 백업으로 안전하고 안정적인 서비스 제공
- BaaS(Backend-as-a-Service): 서버 구축 없이 데이터 저장, 인증, 호스팅 등 백엔드 기능 제공
- 실시간 데이터 처리: 데이터 변경을 클라이언트에 실시간으로 반영
- 크로스 플랫폼 지원: Android, iOS, 웹 등 다양한 플랫폼 지원
- 구글 생태계와의 통합: Google Cloud, Analytics, AdMob 등과 손쉽게 연동
- Firebase Hosting을 통해 전 세계 어디서나 접근 가능
- Firebase는 IoT 데이터 수집 및 시각화에 매우 적합함
- 적용 예시: ESP32의 센서 데이터 실시간 저장 및 웹 차트로 시각화
- 복잡한 서버 인프라 없이도 실시간 모니터링 앱 구축 가능
- Firebase 홈페이지 접속하여 계정 로그인
- ‘프로젝트 만들기’ 클릭
- 프로젝트 이름 설정 ("이 프로젝트에서 Google 애널리틱스 사용 설정" 하지 않음)
- 빌드 > Realtime Database 섹션 이동 후, "데이터베이스 만들기"를 클릭
- 데이터베이스 서버의 위치를 설정하고 테스트 모드로 사용 설정
- 데이터베이스의 URL을 확인 후 Database의 읽기, 쓰기 규칙을 설정
- 빌드 → Authentication 섹션으로 이동, 시작하기를 클릭
- 익명(Anonymous) 방식을 선택하고 사용 설정 On
- 프로젝트 설정으로 이동. 탭에서 웹 API키를 확인 후 복사
- 아두이노에서 라이브러리 “Firebase ESP32 Client (by Mobizt)”를 검색하고 설치
- Firebase 프로젝트를 만들어 사전 준비한 것을 토대로 실습 시작
- 제공 받은 코드를 이용한 기본적인 LED 제어 실습
- 프로젝트의 'Realtime Database'에서 'ledState' 값에 0을 넣으면 파란색 내장 LED가 꺼지고, 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 "your_firebase_url" // url 주소를 입력
#define FIREBASE_AUTH "your_firebase_api" // API 키 값을 입력
#define WIFI_SSID "your_wifi_ssid"
#define WIFI_PASSWORD "your_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());
}
}
}
20250525_182335.mp4
- 조도 센서를 이용한 실습
- 조도 센서가 감지한 값이 시리얼 모니터에 출력됨.
사용한 코드
int sensorPin = 34;
int value = 0;
void setup() {
Serial.begin(115200);
}
void loop() {
value = analogRead(sensorPin);
Serial.println(value);
delay(500);
}
20250528_013906.mp4
- Firebase에서 'ESP32-LIGHT-SENSOR'라는 이름의 새로운 프로젝트를 만들기
- 프로젝트 만든 후 실습1 때와 다른 새로운 url 경로와 api key 값 이용하여 실습
사용한 코드
#include <WiFi.h>
#include <FirebaseESP32.h>
// 토큰 생성 프로세스 정보 제공
#include "addons/TokenHelper.h"
// RTDB 페이로드 출력 정보 및 기타 도움 함수 제공
#include "addons/RTDBHelper.h"
// Firebase 설정
#define FIREBASE_HOST "your_firebase_host"
#define FIREBASE_AUTH "your_firebase_auth"
#define WIFI_SSID "your_wifi_ssid"
#define WIFI_PASSWORD "your_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());
}
}
- "unsigned long timerDelay = 60000; //1 minutes" 본문 코드 중 이 코드로 인해 새로운 노드가 생기는 주기가 1분인 것으로 보인다.
- 특정 시간마다 room1 폴더에 새로운 노드가 생긴다.
- 시간대별로 저장된 빛 센서의 변화량을 실시간으로 표시해주는 웹 앱을 만드는 실습
- 코드는 제공 받은 index.html 파일을 실행하여 나온 웹페이지에서 'CTRL + U' 키를 누르면 페이지의 원본이 나오는데 거기서 코드를 확인할 수 있다.
- 앱 설정 진입해서 앱 이름을 정하고 앱 등록(Register app) 버튼 클릭한다.
- "<script> 태그 사용" 선택 후 아래에 있는 코드 script 카피한다.
- 제공 받은 index.html 파일을 마이크로소프트 엣지나 구글 크롬 등으로 실행하여 나오는 페이지에서 'Ctrl + U'키로 웹페이지 원본 코드를 확보한다.
- 그래프가 그려져 있지 않은 원본 페이지
- 메모장이나 기타 편집기에서 html 코드를 작성한다. 이때 웹페이지 원본 코드에 과정 '2번'에서 앱 등록 시 복사한 스크립트 코드의 정보를 집어넣는다.
- 작성한 코드를 index.html로 저장한다.
- 저장한 index.html을 실행하면 그래프가 그려져 있다.
웹페이지 코드(복사한 스크립트 코드에 있는 데이터베이스 url, api key, 프로젝트 Id 등의 값을 이 원본 코드에 대입해야 한다.)
<html>
<body>
<!--StartFragment-->
<!DOCTYPE html>
--
| <html lang="en">
| <head>
|
| <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>
<!--EndFragment-->
</body>
</html>
- 수집 된 데이터가 그래프로 성공적으로 그려졌다.
- 웹사이트를 호스팅하여 어디서든 접속 및 조회할 수 있도록 하는 실습
- 'Firebase CLI'를 다운로드 한다.(C 드라이브의 영문 폴더에 다운로드)
- 다운 받은 후 실행하면 창이 뜨고 로그인 메시지가 발생한다. 구글 계정으로 로그인 한다.
- 로그인 성공한 모습
- 로그인이 완료되면 pdf 파일에 나온 명령어를 실행한다.
- 명령어에 대한 설명
- 명령어 실행
- 'firebase projects:list' 명령어 실행하여 프로젝트 리스트를 확인한다.
- 'firebase init' 명령어를 실행하여 프로젝트 및 'hosting' 설정을 진행한다.(방향키로 선택지를 고르고, 스페이스바로 원하는 항목을 체크하고, 엔터키로 확정한다.)
- 'hosting'을 선택한 후, "Use an existing project"를 선택한다. 이후 "ESP32_LIGHT-SENSOR"를 선택한다.
- 이후 나오는 3개의 질문은 강의 자료에 나온 대로 답한다.
- 아까 진행한 4번째 실습인 "웹 앱 구현 및 데이터 시각화"를 진행할 때 작성한 index.html을 'd:\project\public' 폴더로 복사한다.(복사할 때 파일 덮어쓰기)
- 'firebase deploy' 명령어 사용하여 웹 앱 호스팅을 시작한다.(아래 이미지에 나오는 Hosting URL의 주소로 해당 웹 페이지에 접속한다.)
- CLI 화면에 표시된 호스팅 URL을 브라우저에 입력해 주면 세계 어디서나 ESP32에서 보내는 빛 센서 값을 모니터링 할 수 있다.
- 노트북에서 구글 크롬으로 접속한 이미지
- 모바일로 접속한 이미지