10주차 ‐ Firebase - movie-01/SmartDevice GitHub Wiki
- Firebase
- NTP
- 조도센서
- 실습
Firebase는 **Google이 제공하는 백엔드 서비스 플랫폼(Backend-as-a-Service, BaaS)**이다. 서버 인프라를 따로 구축하지 않아도 모바일 앱이나 웹 애플리케이션 개발에 필요한 핵심 기능들을 제공해줘서, 특히 스타트업이나 소규모 팀, 프로토타입 개발에서 많이 사용된다.
- 서버리스(Serverless) 아키텍처
- 서버 설정, 유지보수 없이 기능 구현 가능
- 자동 스케일링 지원
- 빠른 개발 속도
- UI → 인증 → DB 연동까지 통합 도구 제공 → MVP 개발에 최적
- 실시간 데이터 동기화
- Realtime Database 또는 Firestore를 통해, 여러 사용자 간 데이터 실시간 반영 가능
- Google 생태계와의 통합
- Google Cloud Platform(GCP)과 자연스럽게 연동됨
- BigQuery, Cloud Storage 등과 쉽게 연결
- 무료 요금제 + 탄력적 유료 요금제
- 초기에는 무료로 충분히 시작 가능, 이후 트래픽에 따라 유료 전환 가능
분야 | 예시 |
---|---|
모바일 앱 개발 (Android, iOS) | 실시간 채팅 앱, 푸시 알림 기반 서비스 |
웹 애플리케이션 | 로그인/회원가입 기능이 있는 SPA 웹사이트 |
스타트업 프로토타입 개발 | MVP 빠르게 개발할 때 |
게임 서비스 | 게임 데이터 동기화, 로그인, 실시간 알림 |
IoT 디바이스 연동 | 센서 데이터 실시간 전송/수신 |
NTP(Network Time Protocol)는 인터넷을 통해 컴퓨터나 디바이스의 시간을 정확하게 동기화하는 프로토콜이다. 특히 IoT 기기나 분산 시스템, 로그 정렬, 보안 인증서 처리 등에서 정확한 시간 정보는 필수이므로 NTP는 매우 중요한 역할을 한다.
- UDP 포트 123 사용
- **계층적 구조(Stratum)**를 가짐
- Stratum 0: GPS, 원자시계 (가장 정확한 시간원)
- Stratum 1: Stratum 0과 직접 연결된 서버
- Stratum 2: Stratum 1에서 동기화된 일반 클라이언트
- 일반적으로 수 밀리초 내외의 시간 정확도 제공
- 시간 왜곡 보정을 위해 지속적으로 시간 보정 수행
- 서버 간 로그 기록 시간 동기화
- IoT 디바이스의 실시간 센서 동작 타이밍 보정
- 보안 인증서 유효 기간 검증
- 분산 시스템의 시계 일치 유지
조도 센서는 주변 빛의 밝기(lux 단위)를 측정하는 센서로, 스마트 조명이나 환경 모니터링 등에 널리 사용된다. 가장 간단한 형태는 **LDR(Light Dependent Resistor)**이고, 고급형으로는 BH1750, TSL2561 같은 디지털 조도 센서가 있다.
센서 유형 | 설명 |
---|---|
LDR | 빛의 세기에 따라 저항이 변하는 아날로그 센서 |
광다이오드 / 광트랜지스터 | 빠른 반응 속도의 반도체 기반 센서 |
BH1750 / TSL2561 | lux 단위 디지털 출력, I2C 통신 방식 지원 |
- 스마트폰 밝기 자동 조절
- 자동 조명 제어 시스템
- 태양광 추적 장치
- 실내 환경 모니터링
- 스마트팜 조명 조절
int sensorPin = A0;
int sensorValue = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
sensorValue = analogRead(sensorPin);
Serial.println(sensorValue);
delay(500);
}
빛이 강해질수록 출력 값은 커진다 (범위: 0~1023)
- LDR 센서는 저렴하지만 환경 노이즈에 취약
- **디지털 센서(BH1750 등)**는 정밀도와 안정성이 높아 프로젝트 품질을 향상시킴
- NTP와 함께 활용하면 시간대별 조도 변화 기록과 같은 응용도 가능
- ESP32 보드
- ESP32 확장 쉴드
- 빛 감지 센서
- Firebase 계정 (구글 아이디 사용)
(1) 프로젝트 생성
- https://console.firebase.google.com/ 접속
- '프로젝트 시작하기' -> '프로젝트 만들 클릭" -> 프로젝트 이름 설정 -> 기본 설정 완료
(2) Realtime Database 생성
- 메뉴에서 ‘Build > Realtime Database’ 선택
- 테스트 모드로 설정
- 데이터베이스 URL 복사
(3) 인증(Authentication)
- 익명(Anonymous) 로그인 설정을 활성화
(4) 웹 API 키 확인
- 프로젝트 설정 > 일반 탭 > 웹 API 키 복사
#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_host"
#define FIREBASE_AUTH "your_firebase_auth"
#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());
}
}
}
- #define FIREBASE_HOST "your_firebase_host"
Firebase Realtime Database의 URL을 넣는 곳 좌측 메뉴에서 "Build" → "Realtime Database" 클릭 주소창에 보이는 데이터베이스 URL
- #define FIREBASE_AUTH "your_firebase_auth"
사용자의 Firebase 인증 토큰 또는 API 키 톱니바퀴 클릭 후, 프로젝트 설정 클릭하여 화면에 나오는 API 키
- 다음 사진처럼 모두 잘 입력했으나 코드와 하드웨어의 문제가 있는지 확인해봤지만, 원하는 값이 나오지 않고 문제가 해결되지 않았다.
#include <WiFi.h>
#include <Firebase_ESP_Client.h>
#include "addons/TokenHelper.h" // 필수: 토큰 관리
#include "addons/RTDBHelper.h" // 필수: 데이터베이스 에러 핸들링
#define FIREBASE_HOST "your_firebase_host"
#define FIREBASE_AUTH "your_firebase_auth"
#define WIFI_SSID "your_wifi_ssid"
#define WIFI_PASSWORD "your_wifi_password"
// GPIO 핀 설정
#define LED_PIN 2 // 파란 LED 연결 핀 (기본 내장 LED도 보통 GPIO 2)
// Firebase 객체 선언
FirebaseData fbdo;
FirebaseAuth auth;
FirebaseConfig config;
unsigned long lastCheckTime = 0;
const unsigned long checkInterval = 2000; // 2초 간격으로 Firebase 확인
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi 연결 완료");
config.api_key = API_KEY;
config.database_url = DATABASE_URL;
Firebase.begin(&config, &auth);
Firebase.reconnectWiFi(true);
if (!Firebase.signUp(&config, &auth, "", "")) {
Serial.print("익명 로그인 실패: ");
Serial.println(fbdo.errorReason());
} else {
Serial.println("익명 로그인 성공!");
}
}
void loop() {
if (millis() - lastCheckTime > checkInterval) {
lastCheckTime = millis();
// Firebase에서 ledState 값 읽기
if (Firebase.RTDB.getInt(&fbdo, "/ledState")) {
int ledState = fbdo.intData();
Serial.print("ledState: ");
Serial.println(ledState);
if (ledState == 1) {
digitalWrite(LED_PIN, HIGH); // LED 켜기
} else {
digitalWrite(LED_PIN, LOW); // LED 끄기
}
} else {
Serial.print("데이터 읽기 실패: ");
Serial.println(fbdo.errorReason());
}
}
}
test1.mp4
https://github.com/Reiji1024/SmartDevice_2501/wiki/Report_0514 setupTime() 함수 대신 주석처리된 setupTimeManual()함수 사용하는 방법으로도 해보았지만 해결 되지 않아서 코드를 처음부터 새로 작성하였다.
int sensorPin = 34;
int value = 0;
void setup() {
Serial.begin(115200);
}
void loop() {
value = analogRead(sensorPin);
Serial.println(value);
delay(500);
}
KakaoTalk_20250517_122037541.mp4
#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());
}
}
- 실습1과 동일하게 원하는 값이 나오지 않는다.
#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());
}
}
- 앱 등록 및 확인
<!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: "AIzaSyDrJa0Qa7hLSShla3b2ExdgkTgaJ26prOw",
authDomain: "esp32-light-sensor-f53ef.firebaseapp.com",
databaseURL: "https://esp32-light-sensor-f53ef-default-rtdb.firebaseio.com/",
projectId: "esp32-light-sensor-f53ef",
storageBucket: "esp32-light-sensor-f53ef.firebasestorage.app",
messagingSenderId: "303755532549",
appId: "1:303755532549:web:ce5d5b2862d87f2dc0bf6c"
};
// 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>
- CLI 실습