루아 입문 시리즈 #10: NodeMCU IoT 프로젝트
NodeMCU와 루아 스크립트를 활용한 ESP8266/ESP32 기반 IoT 센서 제어 및 WiFi 통신 프로젝트 완벽 가이드
루아 입문 시리즈의 열 번째 시간입니다.
이번에는 실제 하드웨어와 연동하여 IoT 프로젝트를 구현해보겠습니다.
NodeMCU는 ESP8266 또는 ESP32 기반의 개발 보드로, 루아 스크립트를 통해 쉽게 프로그래밍할 수 있는 IoT 플랫폼입니다.
본 글에서는 센서 제어부터 WiFi 통신까지 포괄적인 IoT 프로젝트 개발 방법을 다룹니다.
NodeMCU 개요 및 특징
NodeMCU는 루아 기반의 오픈소스 IoT 플랫폼으로, ESP8266 또는 ESP32 마이크로컨트롤러를 기반으로 합니다.
NodeMCU 공식 문서에서 제공하는 자료를 통해 더 자세한 정보를 확인할 수 있습니다.
NodeMCU의 주요 특징은 다음과 같습니다:
내장 WiFi 모듈: 별도의 WiFi 모듈 없이도 무선 통신이 가능합니다.
루아 스크립트 지원: C/C++ 대신 간단한 루아 문법으로 프로그래밍할 수 있습니다.
다양한 센서 지원: GPIO, ADC, PWM, I2C, SPI 등 다양한 인터페이스를 제공합니다.
저전력 운영: 배터리 기반 IoT 디바이스에 적합한 저전력 모드를 지원합니다.
ESP8266과 ESP32의 주요 차이점을 비교해보겠습니다:
항목 | ESP8266 | ESP32 |
---|---|---|
CPU 코어 | 단일 코어 80MHz | 듀얼 코어 240MHz |
RAM | 80KB | 320KB |
Flash | 1-16MB | 4-16MB |
GPIO 핀 | 17개 | 34개 |
ADC | 1개 (10bit) | 2개 (12bit) |
WiFi | 802.11 b/g/n | 802.11 b/g/n |
Bluetooth | 없음 | 4.2 LE |
가격 | 저렴 | 약간 높음 |
ESP32는 더 강력한 성능을 제공하지만, 간단한 IoT 프로젝트라면 ESP8266도 충분합니다.
개발 환경 설정
NodeMCU 개발을 위한 환경 설정 과정을 단계별로 설명하겠습니다.
펌웨어 설치
먼저 NodeMCU 펌웨어를 설치해야 합니다.
NodeMCU 빌더에서 필요한 모듈을 선택하여 커스텀 펌웨어를 생성할 수 있습니다.
기본적으로 다음 모듈들을 포함하는 것을 권장합니다:
- file (파일 시스템)
- gpio (GPIO 제어)
- net (네트워크)
- node (기본 노드 기능)
- timer (타이머)
- uart (시리얼 통신)
- wifi (WiFi 기능)
- adc (아날로그 입력)
개발 도구 설정
NodeMCU 개발을 위한 주요 도구들입니다:
ESPlorer: 가장 널리 사용되는 NodeMCU IDE입니다.
Java 기반으로 크로스 플랫폼을 지원하며, 루아 스크립트 편집과 업로드가 간편합니다.
Arduino IDE: NodeMCU를 Arduino 보드로 설정하여 사용할 수도 있습니다.
VS Code: Lua 확장을 설치하면 강력한 코드 편집 환경을 구성할 수 있습니다.
WiFi 연결 설정
IoT 프로젝트의 기본은 WiFi 연결입니다.
NodeMCU에서 WiFi 연결을 설정하는 방법을 알아보겠습니다.
기본 WiFi 연결
-- WiFi 모드 설정 (Station 모드)
wifi.setmode(wifi.STATION)
-- WiFi 네트워크 설정
wifi.sta.config("your_wifi_ssid", "your_wifi_password")
-- 연결 시도
wifi.sta.connect()
-- 연결 상태 확인
tmr.alarm(1, 1000, tmr.ALARM_AUTO, function()
if wifi.sta.getip() then
print("WiFi connected!")
print("IP address: " .. wifi.sta.getip())
tmr.stop(1)
else
print("Connecting to WiFi...")
end
end)
고급 WiFi 설정
보다 안정적인 WiFi 연결을 위한 고급 설정입니다:
-- WiFi 이벤트 핸들러 설정
wifi.eventmon.register(wifi.eventmon.STA_CONNECTED, function(T)
print("Connected to " .. T.SSID .. " at channel " .. T.channel)
end)
wifi.eventmon.register(wifi.eventmon.STA_GOT_IP, function(T)
print("IP: " .. T.IP .. ", Gateway: " .. T.gateway .. ", Netmask: " .. T.netmask)
end)
wifi.eventmon.register(wifi.eventmon.STA_DISCONNECTED, function(T)
print("Disconnected from " .. T.SSID .. " (reason: " .. T.reason .. ")")
-- 재연결 시도
wifi.sta.connect()
end)
-- 자동 연결 활성화
wifi.sta.autoconnect(1)
WiFi 라이브러리 문서에서 더 자세한 API 정보를 확인할 수 있습니다.
센서 제어 기초
NodeMCU에서 다양한 센서를 제어하는 방법을 알아보겠습니다.
GPIO 기본 제어
디지털 입출력을 위한 GPIO 제어는 IoT 프로젝트의 기본입니다:
-- GPIO 핀 모드 설정
gpio.mode(1, gpio.OUTPUT) -- GPIO1을 출력으로 설정
gpio.mode(2, gpio.INPUT) -- GPIO2를 입력으로 설정
-- 디지털 출력
gpio.write(1, gpio.HIGH) -- LED 켜기
gpio.write(1, gpio.LOW) -- LED 끄기
-- 디지털 입력
local button_state = gpio.read(2)
if button_state == gpio.HIGH then
print("Button pressed!")
end
아날로그 센서 읽기
온도, 습도, 조도 등의 아날로그 센서 값을 읽는 방법입니다:
-- ADC로 센서 값 읽기
function read_sensor()
local adc_value = adc.read(0) -- A0 핀에서 값 읽기
local voltage = adc_value * 3.3 / 1024 -- 전압으로 변환
-- 온도 센서 (LM35) 계산 예시
local temperature = voltage * 100 -- LM35: 10mV/°C
print("ADC Value: " .. adc_value)
print("Voltage: " .. voltage .. "V")
print("Temperature: " .. temperature .. "°C")
return temperature
end
-- 주기적으로 센서 값 읽기
tmr.alarm(2, 5000, tmr.ALARM_AUTO, function()
read_sensor()
end)
실전 IoT 프로젝트: 스마트 환경 모니터링
실제 IoT 프로젝트를 통해 NodeMCU의 활용법을 익혀보겠습니다.
온도, 습도를 측정하고 웹 서버를 통해 실시간으로 확인할 수 있는 시스템을 구축해보겠습니다.
DHT22 센서 연동
DHT22 센서를 사용하여 온습도를 측정하는 코드입니다:
-- DHT22 센서 설정
local dht_pin = 4 -- GPIO4에 연결
function read_dht22()
local status, temp, humi, temp_dec, humi_dec = dht.read(dht_pin)
if status == dht.OK then
local temperature = temp + temp_dec / 100
local humidity = humi + humi_dec / 100
print("Temperature: " .. temperature .. "°C")
print("Humidity: " .. humidity .. "%")
return temperature, humidity
else
print("DHT22 read error: " .. status)
return nil, nil
end
end
웹 서버 구현
측정한 데이터를 웹 브라우저에서 확인할 수 있는 간단한 웹 서버를 구현합니다:
-- 웹 서버 생성
local srv = net.createServer(net.TCP)
srv:listen(80, function(conn)
conn:on("receive", function(client, request)
local temperature, humidity = read_dht22()
-- HTML 응답 생성
local html = [[
<!DOCTYPE html>
<html>
<head>
<title>Smart Environment Monitor</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.sensor-data { background: #f0f0f0; padding: 20px; border-radius: 10px; margin: 20px 0; }
.value { font-size: 2em; color: #333; }
</style>
</head>
<body>
<h1>Environment Monitor</h1>
<div class="sensor-data">
<h2>Temperature</h2>
<div class="value">]] .. (temperature or "N/A") .. [[°C</div>
</div>
<div class="sensor-data">
<h2>Humidity</h2>
<div class="value">]] .. (humidity or "N/A") .. [[%</div>
</div>
<p>Last updated: ]] .. tmr.now() .. [[</p>
</body>
</html>
]]
-- HTTP 응답 전송
client:send("HTTP/1.1 200 OK\r\n")
client:send("Content-Type: text/html\r\n")
client:send("Connection: close\r\n\r\n")
client:send(html)
end)
conn:on("sent", function(client)
client:close()
end)
end)
print("Web server started on port 80")
데이터 로깅
센서 데이터를 파일로 저장하여 기록을 남기는 기능을 추가할 수 있습니다:
-- 파일 시스템에 데이터 저장
function log_data(temperature, humidity)
local timestamp = tmr.now()
local log_entry = timestamp .. "," .. temperature .. "," .. humidity .. "\n"
-- 로그 파일에 추가
local file = file.open("sensor_log.csv", "a+")
if file then
file:write(log_entry)
file:close()
print("Data logged: " .. log_entry)
else
print("Failed to open log file")
end
end
-- 주기적으로 데이터 로깅
tmr.alarm(3, 60000, tmr.ALARM_AUTO, function() -- 1분마다
local temp, humi = read_dht22()
if temp and humi then
log_data(temp, humi)
end
end)
HTTP 통신 및 클라우드 연동
IoT 디바이스의 진정한 가치는 클라우드 서비스와의 연동에서 나타납니다.
NodeMCU에서 HTTP 요청을 통해 외부 서비스와 통신하는 방법을 알아보겠습니다.
HTTP 클라이언트 구현
-- HTTP POST 요청으로 데이터 전송
function send_to_cloud(temperature, humidity)
local url = "https://api.thingspeak.com/update"
local api_key = "YOUR_API_KEY"
local data = "api_key=" .. api_key ..
"&field1=" .. temperature ..
"&field2=" .. humidity
http.post(url,
'Content-Type: application/x-www-form-urlencoded\r\n',
data,
function(code, data)
if code < 0 then
print("HTTP request failed")
else
print("Data sent to cloud, response code: " .. code)
end
end
)
end
JSON 데이터 처리
현대적인 IoT 시스템에서는 JSON 형태로 데이터를 주고받는 경우가 많습니다:
-- JSON 인코딩 함수
function encode_json(temperature, humidity)
local timestamp = tmr.now()
local json_data = '{"timestamp":' .. timestamp ..
',"temperature":' .. temperature ..
',"humidity":' .. humidity .. '}'
return json_data
end
-- REST API로 JSON 데이터 전송
function send_json_data(temperature, humidity)
local json_payload = encode_json(temperature, humidity)
http.post("https://your-api-endpoint.com/sensors",
'Content-Type: application/json\r\n',
json_payload,
function(code, response)
print("Response: " .. code .. " " .. response)
end
)
end
HTTP 모듈 문서에서 더 자세한 HTTP 통신 방법을 확인할 수 있습니다.
고급 기능 활용
NodeMCU의 고급 기능들을 활용하여 더욱 전문적인 IoT 프로젝트를 구현할 수 있습니다.
딥슬립 모드 활용
배터리 기반 IoT 디바이스에서는 전력 소모를 최소화하는 것이 중요합니다:
-- 센서 데이터 수집 후 딥슬립 모드 진입
function collect_and_sleep()
local temp, humi = read_dht22()
if temp and humi then
send_to_cloud(temp, humi)
-- 5분 후 깨어나도록 설정 (마이크로초 단위)
node.dsleep(5 * 60 * 1000000) -- 5분 = 300초
else
-- 에러 발생 시 1분 후 재시도
node.dsleep(60 * 1000000)
end
end
-- WiFi 연결 완료 후 데이터 수집 및 슬립
wifi.eventmon.register(wifi.eventmon.STA_GOT_IP, function()
tmr.alarm(4, 2000, tmr.ALARM_SINGLE, collect_and_sleep)
end)
인터럽트 기반 입력 처리
효율적인 입력 처리를 위한 인터럽트 활용법입니다:
-- 버튼 인터럽트 설정
local button_pin = 3
gpio.mode(button_pin, gpio.INT, gpio.PULLUP)
gpio.trig(button_pin, "down", function(level, when)
print("Button pressed at: " .. when)
-- 디바운싱을 위한 지연
tmr.alarm(5, 50, tmr.ALARM_SINGLE, function()
if gpio.read(button_pin) == gpio.LOW then
handle_button_press()
end
end)
end)
function handle_button_press()
print("Button press confirmed!")
-- 버튼 동작 구현
local temp, humi = read_dht22()
if temp and humi then
print("Current readings - Temp: " .. temp .. "°C, Humidity: " .. humi .. "%")
end
end
파일 시스템 활용
설정 값이나 데이터를 파일로 관리하는 방법입니다:
-- 설정 파일 읽기
function load_config()
local config = {}
if file.exists("config.lua") then
dofile("config.lua")
config = _G.config or {}
else
-- 기본 설정
config = {
wifi_ssid = "default_ssid",
wifi_password = "default_password",
update_interval = 60
}
save_config(config)
end
return config
end
-- 설정 파일 저장
function save_config(config)
local file_handle = file.open("config.lua", "w+")
if file_handle then
file_handle:write("config = {\n")
file_handle:write(' wifi_ssid = "' .. config.wifi_ssid .. '",\n')
file_handle:write(' wifi_password = "' .. config.wifi_password .. '",\n')
file_handle:write(' update_interval = ' .. config.update_interval .. '\n')
file_handle:write("}\n")
file_handle:close()
print("Configuration saved")
end
end
최적화 및 디버깅
안정적인 IoT 시스템을 위한 최적화 기법과 디버깅 방법을 알아보겠습니다.
메모리 관리
NodeMCU는 제한된 메모리를 가지고 있으므로 효율적인 메모리 관리가 필수입니다:
-- 메모리 사용량 모니터링
function print_memory_usage()
collectgarbage() -- 가비지 컬렉션 강제 실행
print("Free heap: " .. node.heap() .. " bytes")
end
-- 주기적으로 메모리 상태 확인
tmr.alarm(6, 30000, tmr.ALARM_AUTO, print_memory_usage)
-- 큰 데이터 처리 후 명시적 가비지 컬렉션
function process_large_data()
-- 대용량 데이터 처리
local large_array = {}
for i = 1, 1000 do
large_array[i] = math.random()
end
-- 처리 완료 후 메모리 정리
large_array = nil
collectgarbage()
end
에러 처리 및 복구
-- 안전한 함수 실행을 위한 래퍼
function safe_execute(func, error_message)
local status, result = pcall(func)
if not status then
print("Error: " .. (error_message or "Unknown error"))
print("Details: " .. result)
return false
end
return true, result
end
-- WiFi 연결 실패 시 재시도 로직
function connect_wifi_with_retry(max_retries)
local retries = 0
local function try_connect()
wifi.sta.connect()
tmr.alarm(7, 10000, tmr.ALARM_SINGLE, function()
if wifi.sta.getip() then
print("WiFi connected successfully!")
else
retries = retries + 1
if retries < max_retries then
print("WiFi connection failed, retrying... (" .. retries .. "/" .. max_retries .. ")")
try_connect()
else
print("WiFi connection failed after " .. max_retries .. " attempts")
-- 재부팅 또는 대체 동작
node.restart()
end
end
end)
end
try_connect()
end
보안 고려사항
IoT 디바이스의 보안은 매우 중요한 요소입니다.
기본적인 보안 사항들을 살펴보겠습니다.
안전한 WiFi 연결
-- WPA2-Enterprise 연결 (가능한 경우)
wifi.sta.config({
ssid = "enterprise_network",
pwd = "password",
identity = "user_identity",
auth = wifi.WPA2_ENTERPRISE
})
-- MAC 주소 기반 필터링을 위한 MAC 주소 확인
print("Device MAC address: " .. wifi.sta.getmac())
HTTPS 통신
민감한 데이터 전송 시에는 반드시 HTTPS를 사용해야 합니다:
-- SSL/TLS를 사용한 안전한 HTTP 통신
function secure_send_data(data)
local url = "https://secure-api.example.com/data"
http.post(url,
'Content-Type: application/json\r\n' ..
'Authorization: Bearer YOUR_API_TOKEN\r\n',
data,
function(code, response)
if code == 200 then
print("Data sent securely")
else
print("Secure transmission failed: " .. code)
end
end
)
end
NodeMCU 보안 가이드에서 더 자세한 보안 정보를 확인할 수 있습니다.
성능 최적화 팁
NodeMCU 프로젝트의 성능을 최대화하기 위한 실용적인 팁들입니다:
효율적인 타이머 사용: 불필요한 타이머는 정리하고, 가능한 한 적은 수의 타이머를 사용합니다.
배치 처리: 여러 센서 값을 한 번에 읽고 처리하여 전력 소모를 줄입니다.
적절한 업데이트 주기: 센서 특성에 맞는 적절한 측정 주기를 설정합니다.
코드 최적화: 루프 내에서 불필요한 계산을 피하고, 자주 사용되는 값은 캐시합니다.
마무리
이번 글에서는 NodeMCU를 활용한 IoT 프로젝트 개발의 전 과정을 다뤘습니다.
WiFi 연결 설정부터 센서 제어, 웹 서버 구현, 클라우드 연동까지 실제 프로젝트에 필요한 모든 요소들을 살펴봤습니다.
NodeMCU와 루아의 조합은 빠른 프로토타이핑과 간편한 IoT 개발을 가능하게 합니다.
특히 복잡한 C/C++ 코드 없이도 강력한 기능을 구현할 수 있다는 점이 큰 장점입니다.
다음 시리즈에서는 더욱 고급 주제들을 다룰 예정입니다.
궁금한 점이나 개선 사항이 있다면 댓글로 남겨주세요!
관련 시리즈:
루아 입문 시리즈 #9: LÖVE 2D 게임 개발 입문
참고 자료: