로라(LoRa) 기반 스마트시티 대기질 모니터링 시스템 – 태양광 자립형 IoT 노드
부품 및 자재
부품
수량
Arduino UNO
1개
Seeed Base Shield V2
1개
NodeMCU ESP8266 브레이크아웃 보드
1개
Seeed 라우드니스(소음) 센서
1개
Seeed Grove – 공기질 센서 v1.3
1개
Seeed Grove – 온습도 센서 (DHT11)
1개
Reyax LoRa 모듈 (RYLR998)
1개
Seeed 소형 태양광 패널 80x100mm 1W
1개
Seeed LiPo Rider Pro (충전/부스트 회로)
1개
리튬폴리머 배터리 3.7V
1개
필요한 도구 및 장비
소프트웨어 및 온라인 서비스
Arduino IDE
Grandeur IoT 플랫폼 (백엔드 및 대시보드)
Grandeur Canvas (드래그앤드롭 대시보드 빌더)
프로젝트 개요
스마트시티와 대기질
세계은행에 따르면 스마트시티는 기술을 활용해 고효율 공공 서비스와 지속가능한 도시 환경을 제공합니다. 이 비전의 핵심은 실시간 환경 모니터링 으로, 오염원을 식별하고 데이터 기반 정책 결정(교통 흐름 최적화, 안전한 보행 경로 안내 등)을 지원합니다.
문제점과 해결책
도시 대기 오염은 심각한 건강 위험을 초래하며, 적시 데이터는 완화 대책에 필수적입니다. 저비용 센서 노드의 IoT 네트워크로 대기질과 기상 파라미터를 지속적으로 수집하고, 신뢰성 높고 저전력인 통신 계층과 결합하면 오염 핫스팟을 감지해 선제적으로 대응할 수 있습니다.
프로젝트 계획
본 솔루션은 소음, 온도, 습도, 공기질 주요 환경 지표를 모니터링하고 무선으로 클라우드 플랫폼에 전송합니다. 전체 노드는 1W 태양광 패널과 리튬이온 배터리 백업 으로 자율 구동됩니다. 클라우드 대시보드에서 실시간 데이터 시각화가 가능해 즉각적인 인사이트와 장기 추세 분석을 지원합니다.
각 센서를 개별적으로 클라우드에 연결하면 전력 소모와 비용이 증가하므로, 여러 인접 노드의 데이터를 집계해 단일 LoRa 게이트웨이로 전송 하는 방식을 채택했습니다. LoRa의 장거리·저전력 특성은 밀집된 도심 배포에 이상적입니다.
센서 노드 → LoRa → 게이트웨이(NodeMCU) → Wi-Fi → Grandeur 클라우드
백엔드 플랫폼으로 Grandeur 를 선택한 이유는 아두이노, NodeMCU, 라즈베리파이용 사용자 친화적 SDK와 드래그앤드롭 대시보드 빌더를 제공하기 때문입니다.
태양광 발전은 원격 노드에 지속가능한 무보수 에너지원을 제공하며, 리튬이온 배터리는 일조량이 적은 기간에도 동작을 보장합니다.
센서 노드 제작
노드는 그로브 공기질 센서, 그로브 소음 센서, DHT11 온습도 센서를 통합하며, 모두 Seeed Base Shield에 납땜 없이 꽂기만 하면 되어 조립과 유지보수가 간편합니다.
Base Shield에 그로브 센서 3종 장착
전원 회로
1W 태양광 패널과 Seeed LiPo Rider Pro 충전 보드로 전원을 공급합니다. 이 보드에는 5V 부스트 컨버터가 내장되어 아두이노의 동작 전압 범위를 유지합니다.
LoRa 통신 모듈
Reyax RYLR998 LoRa 트랜시버가 무선 전송을 담당합니다. 이 모듈은 3.6V 로직을 사용하므로, 아두이노의 5V TX 라인을 보호하기 위해 간단한 전압 분할 회로 를 추가했습니다.
전압 분할기(저항 2개)로 5V → 3.3V 레벨 시프트
LoRa 모듈을 Base Shield에 연결할 때, Base Shield의 전원 스위치를 3.3V로 설정 해야 합니다.
전원 선택 스위치: 3.3V 위치
조립 후 아두이노에서 LoRa 모듈로 AT 명령을 보내 통신을 검증합니다.
시리얼 모니터에서 AT 명령 응답 확인
펌웨어 업로드 및 테스트
제공된 아두이노 스케치를 업로드한 후 시리얼 모니터를 열어 센서 판독값이 정상적으로 전송되는지 확인합니다.
센서 데이터 전송 로그
케이스 조립
첨부된 STL 파일을 3D 프린터로 출력합니다. 피팅 확인 후 양면테이프나 핫글루로 구성품을 케이스 내부에 고정합니다. 태양광 패널은 상단에 장착하고 전원 케이블은 지정된 홀을 통해 배선합니다.
내부 부품 배치
상단 태양광 패널 마운트
모든 구성품 고정 후 현장 배포 준비 완료입니다.
필드 배포 준비 완료된 센서 노드
Grandeur 플랫폼 설정
Grandeur는 디바이스-유저 페어링과 실시간 대시보드를 제공하는 IoT 백엔드입니다. 다음 단계로 프로젝트를 설정하세요:
grandeur.dev 에서 새 프로젝트 생성
유저 계정 등록 (나중 디바이스 페어링에 사용)
디바이스 모델 정의 – JSON 스키마:
{"temp":0, "humid":0, "air":0, "noise":0}
디바이스 인스턴스 생성 → 고유 ID와 액세스 토큰 확보 → 아두이노 펌웨어에 저장
대시보드 또는 API(pairDevice())로 디바이스-유저 페어링
Canvas 대시보드 구성: 온도, 습도, 소음, 공기질 각각 그래프 위젯 추가 및 변수 바인딩
Canvas 인증 후 사용자는 로그인해 실시간 데이터를 조회할 수 있습니다. 게이트웨이 펌웨어에는 API 키와 디바이스 토큰이 필요합니다.
IoT LoRa 게이트웨이 구축
NodeMCU(ESP8266) 가 LoRa 게이트웨이 역할을 합니다. 센서 노드에서 UART로 데이터를 수신해 Wi-Fi로 Grandeur에 전달합니다. 라즈베리파이도 가능하지만, NodeMCU가 프로젝트의 대역폭과 지연 요구사항을 충족하는 소형·저비용 솔루션입니다.
NodeMCU + LoRa 모듈 게이트웨이
게이트웨이 펌웨어 핵심 로직
LoRa 모듈에서 수신한 콤마 구분 페이로드를 파싱해 각 값을 float로 변환하고, Grandeur SDK로 전송합니다.
void process_received_data() {
start_pos = inputString.indexOf(start_val);
end_pos = inputString.indexOf(end_val);
String data_string = inputString.substring(start_pos + 1, end_pos - 1);
int firstCommaIndex = data_string.indexOf(',');
int secondCommaIndex = data_string.indexOf(',', firstCommaIndex + 1);
int thirdCommaIndex = data_string.indexOf(',', secondCommaIndex + 1);
String temperature = data_string.substring(0, firstCommaIndex);
String humidity = data_string.substring(firstCommaIndex + 1, secondCommaIndex);
String noise_level = data_string.substring(secondCommaIndex + 1, thirdCommaIndex);
String air_quality = data_string.substring(thirdCommaIndex + 1);
temp = temperature.toFloat();
humid = humidity.toFloat();
noise = noise_level.toFloat();
air = air_quality.toFloat();
inputString = "";
stringComplete = false;
}
대시보드 화면
실시간 센서 데이터 시각화
장기 추세 분석 차트
펌웨어 소스 코드
센서 노드 (Arduino UNO)
#include "AirQuality.h" // Grove 공기질 라이브러리
#include "Arduino.h"
#include "DHT.h"
#define DHTPIN 4 // DHT11 디지털 핀
#define DHTTYPE DHT11 // 센서 모델
DHT dht(DHTPIN, DHTTYPE);
AirQuality airqualitysensor;
int current_quality = -1;
const int sampleWindow = 50; // 20Hz 샘플링
unsigned int sample;
float noise;
float temp, humid;
int airQuality;
void setup() {
Serial.begin(115200); // LoRa 통신용 UART
airqualitysensor.init(14);
dht.begin();
}
void loop() {
noise = calculate_sound_in_db();
airQuality = calculate_air_quality();
calculate_temp_humid();
String values = String(temp) + "," + String(humid) + "," + String(noise) + "," + String(airQuality);
String cmd = "AT+SEND=0," + String(values.length()) + "," + values;
Serial.println(cmd);
delay(15000); // 15초 간격 전송
}
float calculate_sound_in_db() {
unsigned long startMillis = millis();
float peakToPeak = 0;
unsigned int signalMax = 0;
unsigned int signalMin = 1024;
while (millis() - startMillis < sampleWindow) {
sample = analogRead(A1);
if (sample < 1024) {
if (sample > signalMax) signalMax = sample;
else if (sample < signalMin) signalMin = sample;
}
}
peakToPeak = signalMax - signalMin;
float db = map(peakToPeak, 0, 1000, 48, 120);
return db;
}
int calculate_air_quality() {
current_quality = airqualitysensor.slope();
return current_quality;
}
void calculate_temp_humid() {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (isnan(h) || isnan(t)) return;
temp = t;
humid = h;
}
// 공기질 센서용 타이머 인터럽트
ISR(TIMER1_OVF_vect) {
if (airqualitysensor.counter == 61) {
airqualitysensor.last_vol = airqualitysensor.first_vol;
airqualitysensor.first_vol = analogRead(A0);
airqualitysensor.counter = 0;
airqualitysensor.timer_index = 1;
PORTB = PORTB ^ 0x20;
} else {
airqualitysensor.counter++;
}
}
게이트웨이 (NodeMCU ESP8266)
/* SDK 및 WiFi 라이브러리 포함 */
#include
#include
#include
SoftwareSerial LORA(D7, D8); // RX, TX
/* 설정값 - 실제 값으로 교체 필요 */
String deviceID = "your device id";
String apiKey = "your api key";
String token = "your device token";
String ssid = "your wifi ssid";
String password = "your wifi password";
/* Grandeur 객체 */
Grandeur::Project project;
Grandeur::Project::Device device;
/* 연결 상태 및 시간 추적 */
int connected = 0;
uint32_t lastUpdate = 0;
String inputString = "";
boolean stringComplete = false;
char start_val = ':';
char end_val = '%';
int start_pos = 0;
int end_pos = 0;
float temp = 0;
float humid = 0;
float air = 0;
float noise = 0;
/* 디바이스 연결 상태 콜백 */
void onConnection(bool status) {
switch (status) {
case CONNECTED:
Serial.println("Device is connected to the cloud.");
connected = 1;
lastUpdate = millis();
return;
case DISCONNECTED:
Serial.println("Device is disconnected from the cloud.");
connected = 0;
return;
}
}
/* WiFi 연결 */
void connectWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println(WiFi.localIP());
}
/* 데이터 업데이트 확인 콜백 */
void dataCallback(const char* code, Var res) {
if (code == "DEVICE-DATA-UPDATED") {
double temp_data = (double)res["update"]["temp"];
double humid_data = (double)res["update"]["humid"];
double noise_data = (double)res["update"]["noise"];
double air_data = (double)res["update"]["air"];
Serial.printf("Temperature: %f Humidity: %f Noise Level: %f Air Quality: %f\n",
temp_data, humid_data, noise_data, air_data);
return;
}
Serial.println("Failed to Update DATA");
return;
}
/* 센서 데이터 클라우드 전송 */
void sendUpdate() {
Var data;
data["temp"] = temp;
data["humid"] = humid;
data["noise"] = noise;
data["air"] = air;
device.data().set("temp", temp, dataCallback);
device.data().set("humid", humid, dataCallback);
device.data().set("noise", noise, dataCallback);
device.data().set("air", air, dataCallback);
}
void setup() {
Serial.begin(9600);
LORA.begin(115200);
inputString.reserve(200);
connectWiFi();
project = grandeur.init(apiKey, token);
device = project.device(deviceID);
project.onConnection(onConnection);
}
void loop() {
serialEvent();
project.loop(WiFi.status() == WL_CONNECTED);
if (stringComplete) {
process_received_data();
if (connected) sendUpdate();
}
}
void serialEvent() {
while (LORA.available()) {
char inChar = (char)LORA.read();
if (inChar == '\n') {
stringComplete = true;
} else {
inputString += inChar;
}
}
}
주요 구현 포인트
센서 노드 : 15초 주기로 4개 센서 데이터 수집 → CSV 문자열 구성 → AT 명령으로 LoRa 전송
게이트웨이 : SoftwareSerial로 LoRa 수신 → ':'와 '%' 사이의 데이터 파싱 → Grandeur SDK로 개별 변수 업데이트
전력 관리 : 태양광 + LiPo Rider Pro로 완전 자립 동작, 배터리 백업으로 야간/악천후 대응
확장성 : 다중 센서 노드 → 단일 게이트웨이 구조로 비용·전력 효율 극대화