11주차_Firebase와 빛 감지 센서 - sookite22/SmartDivice_24 GitHub Wiki
- Firebase
- 실습
- Firebase 프로젝트 만들기
- 빛 감지 센서값 읽기
- 빛 감지 센서(조도 센서)
- 빛 감지 센서값 데이터 로깅
- Firebase 데이터 활용
- EPS32에 시간대별로 저장된 빛 센서의 변화량을 실시간으로 표시해 주는 앱
- Firebase 웹 앱 호스팅
- 후기
Google에서 제공하는 모바일 및 웹 앱을 개발하기 위한 통합 플랫폼 이다. 사용자 인증, 데이터베이스, 호스팅, 스토리지, 클라우드 함수, 푸시 알림 등 다양한 기능을 제공하여 개발자가 앱을 더 쉽게 개발하고 관리할 수 있도록 도와준다.
특히 본 실습에서 주로 이용할 데이터베이스와 인증 기능은 앱의 핵심 기능을 구현하는 데 매우 유용하다.
-
인증(Authentication) 기능: Firebase 사용자 인증 , 즉 이메일/비밀번호, 소셜 로그인(Google, Facebook, Twitter 등), 전화번호 인증 등을 위한 강력한 기능을 지원한다. 또한 사용자 관리, 비밀번호 재설정, 이메일 확인 등의 기능을 제공하여 개발자가 사용자 관리를 쉽게 할 수 있도록 도와준다.
-
IoT 시스템에서 데이터베이스 사용 시 장점
- 데이터 저장: 데이터베이스를 사용하여 장치에서 생성된 데이터를 저장. 데이터를 장기적으로 보존하고 분석, 추세 식별 및 장치 성능 모니터링에 활용.
- 데이터 공유: 데이터베이스를 사용하여 장치 간에 데이터를 공유하여 장치 간 상호 작용 및 정보를 공유.
- 데이터 분석: 생성된 데이터를 분석을 통해 추세를 파악하고 분석 결과를 바탕으로 장치 성능을 개선.
- 데이터 보안: 데이터베이스는 무단 액세스, 사용, 공개, 중단, 수정 또는 파괴로부터 데이터를 보호.
- 데이터 백업: 데이터 손실로부터 데이터를 보호하고, 중요한 정보를 복구할 수 있는 안정성을 제공.
- Firebase와 데이터베이스로 인증 기능 위주로 실습이 진행될 것이다. 이는 크게 4가지로 구성된다.
대표적인 데이터 베이스인 Firebase에 ESP32의 센서값을 저장하는 프로젝트를 해보자.
-
"Firebase 콘솔(https://console.firebase.google.com)" 에 구글 아이디로 로그인한다.
-
로그인 첫 화면에서 "프로젝트 만들기"를 선택한다.
-
프로젝트 이름을 입력한 후, 다음과 같이 계속 진행한다.
- 빌드 > Realtime Database" 섹션으로 이동 후, "데이터베이스 만들기"를 클릭한다.
- "데이터베이스 만들기"를 클릭한 후, 데이터베이스 서버의 위치를 설정하고 테스트 모드로 사용 설정해 준다.
- 데이터베이스의 URL을 확인하고 복사해 둔다. Database의 읽기, 쓰기 규칙을 설정해 준다.
- Firebase 콘솔의 “빌드 → Authentication” 섹션으로 이동하여 '시작하기'를 클릭한다.
- 익명(Anonymous) 방식을 선택한 후, 사용 설정 ON
- “프로젝트 설정”으로 이동한다. 탭에서 웹 API키를 확인하고 복사해 둔다.
- “Firebase ESP32 Client by Mobizt” 를 검색하고 설치한다.
- 가장 최신 버전에서는 에러가 발생하므로 위와 동일한 4.3.10 버전으로 다운로드한다.
#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.RTDB.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.RTDB.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());
}
}
}
- Firebase의 ledState값을 0 혹은 1로 입력함에 따라 ESP32의 내장 LED가 켜지거나 꺼짐을 확인할 수 있다.
조도 센서(황화카드뮴(CDS)셀)는 측정된 빛의 세기에 따라 저항값 이 변하는 센서이다.
ESP32에서는 이 저항값을 읽을 수 없다. 그러므로 본 실습에서는 저항값을 전압(아날로그값 0V~5V)으로 변환해주는 회로가 필요하다. 그러나 ESP32에서 아날로그값을 읽을 수 없기 때문에 이를 디지털값으로 변환(ADC 내장 모듈 이용)해준다.
-
옴의 법칙: 옴의 법칙은 전기 회로에서 전압(Voltage), 전류(Current), 저항(Resistance) 사이의 관계를 설명한다. 'V = I * R'로 설명된다. (V = 전압, I = 전류, R = 저항) 조도 센서에서도 옴의 법칙이 적용된다. 센서가 빛을 감지하면 CDS의 저항이 변화하게 되며 이에 따라 전압이 변한다. 이 변화된 전압을 측정하여 빛의 강도를 파악할 수 있다.
-
CDS(Cadmium Sulfide): 빛 감지 센서에서 가장 일반적으로 사용되는 물질이며 빛에 반응하여 전기적 특성이 변화하는 반도체 소재이다. 빛의 강도가 증가하면 CDS의 전기 저항이 감소하고, 빛의 강도가 감소하면 CDS의 전기 저항이 증가한다. 이러한 특성으로 조도 센서에서 빛의 강도를 측정 가능하다. CDS의 저항이 변화하면 전압도 변화하므로, 이를 측정하여 빛의 강도를 결정할 수 있다.
int sensorPin = 34; // 34번 핀은 ADC를 지원하는 핀이다.
int value = 0;
void setup() {
Serial.begin(115200);
}
void loop() {
value = analogRead(sensorPin);
Serial.println(value);
delay(500);
}
- 시리얼 모니터를 통해 빛 감지 센서를 가리면 값이 감소하고 빛을 비추면 값이 증가하는 것을 확인할 수 있다.
-
ESP32의 타임스탬프를 사용하여 Firebase 실시간 데이터베이스에 데이터 로깅하는 법을 알아보자.
-
실습 1번과 같이 설정하여 "ESP32-LIGHT-SENSOR"를 생성한다.
#include <WiFi.h>
#include <FirebaseESP32.h>
// 토큰 생성 프로세스 정보 제공
#include "addons/TokenHelper.h"
// RTDB 페이로드 출력 정보 및 기타 도움 함수 제공
#include "addons/RTDBHelper.h"
// Firebase 설정
#define FIREBASE_HOST "your_firebase_host" //"https://esp32-light-sensor-49d0d-default-rtdb.firebaseio.com/"
#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());
}
}
- room1 폴더에 새로운 노드가 생기는 것을 확인할 수 있다. 이는 timestamp마다 받아온 빛의 양의 수치를 저장한 json 형식의 데이터이다.
- “ESP32-LIGHT-SENSOR” 프로젝트를 생성하고, 웹 앱 아이콘을 선택하여 앱 설정으로 진입한다.
- 앱 이름을 정하고 'Register app(앱 등록)' 버튼을 클릭한다.
- “Use a <script>tag(<script> 태그 사용)”를 선택하고, 아래에 나타나는 script를 복사해 둔다. 이는 나중에 html 파일 작성 시에 사용한다.
메모장이나 기타 편집기에서 HTML, JAVA SCRIPT 코드를 작성한 뒤, index.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 type="module">
import { initializeApp } from "firebase/app";
// 웹 앱의 Firebase 구성
const firebaseConfig = {
apiKey: "your_API",
authDomain: "esp32-light-sensor-723cb.firebaseapp.com",
projectId: "esp32-light-sensor-723cb",
storageBucket: "esp32-light-sensor-723cb.appspot.com",
messagingSenderId: "11757607492",
appId: "1:11757607492:web:405776f52f8b78c1c6ba11"
};
// Firebase 초기화
const app = initializeApp(firebaseConfig);
</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>
- <script> 태그 부분을 앱 등록 시 복사한 스크립트의 내용에 맞게 변경해준다.
- 브라우저 창에서 열어주면 ESP32에서 보내서 Firebase에 저장되는 데이터들이 실시간으로 그래프로 그려지는 것을 확인할 수 있다.
Firebase 호스팅은 정적 웹 페이지 또는 앱을 호스팅하기 위한 간편한 방법을 제공한다. Firebase CLI(Command Line Interface)를 사용하여 앱을 배포하고 관리할 수 있으며, SSL 인증서 및 CDN(Content Delivery Network)을 자동으로 제공하여 빠르고 안전한 웹 호스팅 환경을 제공한다.
생성해 놓은 index.html은 자신의 PC에서만 실행된다. 이것을 전 세계 어디서나 접속 가능하도록 하려면 호스팅을 해야 한다.
- Firebase 웹 앱을 어디서나 접속하여 조회할 수 있도록 웹사이트 호스팅을 해 보자.
https://firebase.google.com/docs/cli?hl=ko 에서 cli 실행 파일을 다운로드한다. C: 드라이브의 영문 폴더에 다운로드한다.
다운로드받은 "firebase-tools-instant-win.exe"를 더블 클릭하여 실행한다.
실행하면 Firebase 로그인 메시지가 발생하고 로그인을 할 수 있는 인터넷 브라우저 창이 열린다. 로그인이 완료되면 Firebase 프롬프트를 사용할 수 있다.
실행 파일을 프로젝트 폴더로 복사하여 실행하거나 아래 명령어를 실행하여 Working Directory를 설정한다.
- cd: 현재 폴더 확인하거나 지정한 폴더로 이동
- d: d:\ 드라이브로 이동
- mkdir project: 현재 폴더에 project폴더 만들기
- cd project: project폴더로 이동
- cd: 현재 폴더 확인 “d:\project"
"firebase projects:list" 명령어를 실행하여 Firebase 프로젝트 리스트를 확인한다.
"firebase init" 명령어를 실행하여 프로젝트 및 hosting 설정을 진행한다.
- “Use an existing project”를 선택한다.
- 화살표 키로 “ESP32-LIGHT-SENSOR”를 선택 후 엔터를 친다.
- 호스팅 설정은 기본값을 사용할 예정이므로 다음 질문에 모두 엔터를 쳐 종료한다. What do you want to use as your public directory? (public) <엔터> Configure as a single-page app (rewrite all urls to /index.html)? (y/N) <엔터> Set up automatic builds and deploys with GitHub? (y/N) <엔터>
-
챕터 7.4.2.에서 작성한 index.html을 d:\project\public 폴더로 복사한다.
-
"firebase deploy" 명령어를 사용하여 웹 앱 호스팅을 시작한다. 그러면 보안이 적용된 Hosting URL을 받을 수 있다.
CLI화면에 표시된 Hosting URL을 브라우저에 입력하면 세계 어디서나 ESP32에서 보내는 빛 센서 값을 모니터링할 수 있다.
Firebase를 이용하여 로깅과 호스팅 실습을 해보았다. 이는 지금까지 해 보았던 실습 중에 가장 양이 많고 어려웠던 것 같다. 코드를 작성하는 과정에서 약간의 어려움을 겪었지만 이러한 과정을 통해 한 단계 더 성장하게 되는 계기가 되었다고 생각한다. 다른 복잡한 실습을 하게 되더라도 겁내지 않고 차근차근 진행할 수 있을 것 같다.