간단한 IOT 무드등 만들기
예젠에 회사 블로그에 올렸던 글...
준비물
1. NodeMCU v2
WIFI 기능이 탑재된 가성비 좋은 마이크로 컨트롤러 보드입니다.
2. USB 와이어 LED
오픈마켓 등에서 'USB 와이어 LED' 로 검색하여 구입 가능합니다.
3. 브레드보드와 점퍼 케이블
납땜 없이 전자 회로를 구성하고 테스트 할 수 있습니다.
4. NPN 트렌지스터 - 2N2222
NodeMCU v2 소개
NodeMCU v2 에는 중국 에스프레시프 시스템사에서 개발된 wifi 기능이 탑재된 MCU, ESP8266-12E 모듈이 탑재되었습니다. 이 기기의 스펙을 간략하게 적어보면 다음과 같습니다.
- 802.11 b/g/n 프로토콜
- Wi-Fi Direct (P2P), soft-AP
- TCP/IP 프로토콜
- 80Mhz 클럭 스피드를 갖는 저전력 32bit CPU 통합.
- 3.3v 전원으로 동작. (입력전원 5-12v)
- 디지털 입출력핀(GPIO)
- UART, SPI, I2C 프로토콜 지원
- 64Kbyte SRAM
- 4MB 플래시 메모리
아두이노 부트로더를 올리고 C/C++ 를 이용하여 편하게 개발할 수 있으며 가격도 5000원 정도로 참 저렴합니다. ESP8266-12E 는 시리얼 통신 방식 중에 하나인 UART로 프로그램을 올리거나 디버깅 할 수 있지만, UART 와 USB 통신을 변환하는 CP2012 드라이버 칩을 탑재하고 있습니다. 전원부는 AMS1117 를 이용하여 5v에서 12v까지의 전압을 갖는 전원을 3.3v으로 변환하여 공급해줍니다. 하지만, 3.3v를 이용하는 탓에 5v를 사용하는 센서나 기타 개발 보드 등의 기기와 맞물려 사용할 때는 스위치 회로를 구성해야 합니다.
아래 그림은 NodeMCU v2 의 핀과 역할에 대한 맵 입니다. 주로 사용하게 될 디지털 입출력(GPIO) 핀은 출력(OUTPUT)모드에서 HIGH 상태와 LOW 상태를 가질 수 있습니다. HIGH 상태에서는 해당 핀에 3.3v의 전압을 갖는 전류가 흐르게 되지만, LOW 상태에서는 전류가 흐르지 않습니다. GPIO 핀에는 각각의 번호가 있습니다. 아두이노 IDE 를 이용하여 코딩할 때, 이 핀 번호를 지정하여 직접 제어할 수 있습니다.
Step1. 아두이노 IDE 기본 설정
NodeMCU 에서 아두이노를 사용하기 위해서는 다음과 같은 기본 설정 과정을 거쳐야 합니다.
(1) https://www.arduino.cc/ 페이지에 접속후 Software -> Downloads 페이지에 들어가 아두이노 IDE 최신 버전을 다운로드 받습니다. Windows10 운영체제인 경우 스토어에서 아두이노 최신 버전을 받을 수 있습니다.
(2) 아두이노 IDE 를 실행한 뒤 상단 메뉴의 파일 -> 환경설정 에 들어갑니다. 그 뒤 아래 그림처럼 '추가적인 보드 매니저 URLs' 에 주소 https://arduino.esp8266.com/stable/package_esp8266com_index.json 를 추가하고 확인 버튼을 누릅니다.
(3) 상단메뉴 툴 -> 보드 -> 보 매니저...(가장 상단) 에 들어갑니다. 아래 그림처럼 보드 매니저 창이 뜨는 것을 확인할 수 있습니다. 검색창에 'esp8266' 를 입력하고 나온 'esp8266 by ESP8266 Community' 패키지를 설치합니다.
(3) 마지막으로 보드 설정을 해줍니다. 여기서는 NodeMCU v2 를 사용할 것이기 때문에 아래와 같이 설정해 줍니다.
Step2. 웹서버 만들기
상단 메뉴의 '툴 -> 보드' 에서 NodeMCU 1.0 을 선택하면 '파일 -> 예제' 메뉴에 ESP8266WebServer 라는 항목이 생깁니다. 예제를 통하여 간단한 웹서버를 바로 만들어볼수도 있습니다. 여기서는 IOT 무드등을 컨트롤하기 위한 웹 서버를 만들어보려고 합니다.
우선 간단한 HTTP API 를 호출할 수 있도록 구현해 보았습니다.
표 : HTTP API 정의
패스 |
파라미터 |
반환값 |
설명 |
/state |
없음 |
{"isOn" : true | false, "brightness" : 1-100 } |
상태 값(전원, 밝기)을 가져온다. |
/onoff |
isOn=[true | false] |
전원을 끄거나 켠다. |
|
/brightness |
value=[1-100] |
밝기를 조절한다. |
코드: API 서버
#include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> #ifndef STASSID #define STASSID "공유기 이름" #define STAPSK "패스워드" #endif const char *_ssid = STASSID; const char *_password = STAPSK; ESP8266WebServer _server(80); // on/off 상태 bool _isOn = false; // 밝기 int _brightness = 100; void setup(void) { // 시리얼 통신 속도(baud rate) 설정. Serial.begin(115200); // WIFI 동작모드. // WIFI_STA, WIFI_AP, WIFI_AP_STA 모드 중에 하나를 사용할 수 있습니다. WiFi.mode(WIFI_STA); // 연결할 WIFI AP의 ssid 값과 패스워드를 설정합니다. WiFi.begin(_ssid, _password); Serial.println(""); // WIFI AP에 연결될 때까지 기다립니다. while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Connected to "); Serial.println(_ssid); Serial.print("IP address: "); Serial.println(WiFi.localIP()); // 상태 확인 API path 및 함수 설정 _server.on("/state", onRequestState); // LED램프 on/off API path 및 함수 설정 _server.on("/onoff", onRequestOnOff); // 밝기조절 API path 및 함수 설정 _server.on("/brightness", onRequestBrightness); // 404 에러 페이지 _server.onNotFound(onNotFound); _server.begin(); Serial.println("HTTP _server started"); } void loop(void) { _server.handleClient(); } // 현재 상태 값을 JSON 형태로 반환합니다. void onRequestState() { String result = String("{\"isOn\":") + (_isOn ? "true": "false"); result += String(",\"brightness\":") + _brightness + "}"; _server.sendHeader("Access-Control-Allow-Origin", "*"); _server.send(200, "application/json",result); } void onRequestOnOff() { // 파라미터 'isOn'의 값을 가져옵니다. _isOn = String("true") == _server.arg("isOn"); setOnOff(); onRequestState(); } void onRequestBrightness() { // 파라미터 'value' 의 값을 가져옵니다. // 이 값을 1 부터 100까지의 값을 가질 수 있도록 합니다. int value = (String("") + _server.arg("value")).toInt(); if(value < 1) value = 1; else if(value > 100) value = 100; _brightness = value; setBright(); onRequestState(); } void setOnOff() { // 추후 구현을 위하여 비워놓습니다. } void setBright() { // 추후 구현을 위하여 비워놓습니다. } void onNotFound() { String message = "404 File Not Found\n\n"; message += "URI: "; message += _server.uri(); message += "\nMethod: "; message += (_server.method() == HTTP_GET) ? "GET" : "POST"; message += "\nArguments: "; message += _server.args(); message += "\n"; for (uint8_t i = 0; i < _server.args(); i++) { message += " " + _server.argName(i) + ": " + _server.arg(i) + "\n"; } _server.send(404, "text/plain", message); }
이 코드를 NodeMCU 에 업로드하기 위하여 USB 5 pin 케이블을 아래 사진과 같이 연결하고 아두이노 IDE 상단 메뉴의 '툴 -> 포트' 에서 새로 추가된 포트를 선택합니다. 그 다음 '스케치 -> 업로드' 를 선택하여 기기에 코드를 올릴 수 있습니다.
위 코드를 자세히 보면 setup() 함수와 loop() 함수가 있는 것을 발견할 수 있습니다. 이 두개의 함수는 아두이노 생명주기를 담당합니다. setup() 이벤트 함수 내부에서 디지털 입출력 핀 및 라이브러리를 초기화 할 수 있으며, 최초 한 번반 실행됩니다. 그 이후 loop() 이벤트 함수가 무한 연속으로 호출됩니다.
아두이노 디버깅은 주로 시리얼 통신(UART)과 아두이노 IDE 에 포함된 시리얼 모니터를 통하여 할 수 있습니다. 시리얼 모니터 창은 상단 메뉴의 '툴 -> 시리얼 모니터' 를 선택하여 띄울 수 있습니다. 시리얼 통신을 사용하기 위해서는 setup() 함수 내부에서 Serial.begin(int) 를 호출하여 초기화 시켜줘야 합니다. Serial.begin(int) 함수의 인자값으로 시리얼 통신 속도(baud rate)를 줘야하며, 시리얼 모니터 우측 하단에서 동일한 값으로(아래 이미지 빨간 밑줄) 맞춰줘야 내용이 제대로 출력됩니다.
방금 올렸던 API 서버 코드가 제대로 동작하는지 확인하기 위하여 시리얼 모니터를 띄워보았습니다.
192.168.10.39 라는 ip 주소를 할당받았음을 알 수 있습니다. 웹 서버를 만들어 기기를 컨트롤 하는 방식은, 조만간 소개할 MQTT 활용법에 비하여 여러가지 불편한점들이 많습니다. 특히 무선 AP 에 접속한 이후 DHCP 를 통하여 동적 ip 주소를 받는 경우 매번 이렇게 시리얼 모니터를 통하여 할당받은 ip 주소를 확인해야 합니다. 가능하다면, 공유기 등의 DHCP 장비 환경 설정에서 접속된 NodeMCU 기기의 맥 어드레스에 특정 ip 를 고정 할당하도록 설정해 놓는 것이 좋습니다.
아래 코드를 wifi 연결 완료 부분 뒤에 추가하면 시리얼 모니터를 통하여 맥 어드레스도 확인할 수 있습니다.
Serial.print("MAC: ");
Serial.println(WiFi.macAddress());
시리얼 모니터로 알아낸 ip 주소로 각각의 API 를 호출해 보았습니다.
모두 정상 동작하는 것을 확인할 수 있습니다.
위 API 를 UI 를 통하여 호출할 수 있도록 간단한 웹 페이지를 만들어 보았습니다.
NodeMCU v2 의 ESP8266-12E 는 플래시 메모리가 4Mbyte 로 다른 아두이노 기기들에 비해 적은 편은 아니지만, 그래도 복잡하고 화려한 웹 페이지를 넣기에는 한계가 있습니다. 따라서 웹 페이지 코드를 아래처럼 간단하게 구성 하였습니다.
코드 : index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no"> <title>Simple Mood Lamp</title> <link rel="stylesheet" type="text/css" href="./index.css" /> </head> <body> <div class="out-box"> <div class="content"> <div> <input type="button" value="" id="onoff-button" onclick="onClickOnOffButton()"> </div> <div class="brightness-content"> <div class="float-parent"> <div class="float-left"> <input type="range" min="1" max="100" value="0" id="brightness-slider" oninput="onInputSlider()" onchange="onChangeBrightness()"> </div> <div class="float-left" id="brightness-value"/> </div> </div> </div> </div> </body> <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script> <script src="index.js"></script> </html>
코드 : index.js
html body { padding: 0; margin: 0; width: 100%; height: 100%; } .out-box { display: table; position: fixed; width: 100%; height: 100%; } .content { display: table-cell; vertical-align: middle; text-align: center; width: 100%; } .brightness-content { display: inline-block; margin-top: 20px; } .float-left { float: left } .float-parent { height: 30px; width: 100%; text-align: center; float: none; } #brightness-slider { width: 200px; } #onoff-button { background-color: gray; /* Green */ border: none; color: white; width: 150px; height: 45px; display: inline-block; font-size: 20px; }
코드 : index.css
var _brightnessSliderEle = null; var _brightnessValueEle = null; var _buttonEle = null; var _state = null; $(document).ready(function() { init(); onState(); }); function init() { _brightnessSliderEle = $('#brightness-slider'); _brightnessValueEle = $('#brightness-value'); _buttonEle = $('#onoff-button'); } function onState() { $.get("http://192.168.10.39/state").done(function (data) { setState(data); }).fail(function (err) { }); } function setState(state) { _state = state; setBrightness(state.brightness); setOnOff(state.isOn); } function onClickOnOffButton() { _state.isOn = !_state.isOn; var param = {isOn : _state.isOn }; $.post("http://192.168.10.39/onoff",param ).done(function (data) { setState(data); }).fail(function (err) { }); } function onChangeBrightness() { _state.brightness = _brightnessSliderEle.val(); var param = {value : _state.brightness}; $.post("http://192.168.10.39/brightness",param ).done(function (data) { setState(data); }).fail(function (err) { }); } function onInputSlider() { _brightnessValueEle.text(_brightnessSliderEle.val() + "%"); } function setBrightness(value) { _brightnessValueEle.text(value + "%"); _brightnessSliderEle.val(value); } function setOnOff(isOn) { if(isOn === true) { _buttonEle.val("ON"); _buttonEle.css("background-color","#4CAF50") } else { _buttonEle.val("OFF"); _buttonEle.css("background-color","#f54b42") } }
위 세개의 웹 소스 코드를 압축하여 API 서버 코드 상단에 다음과 같이 추가합니다.
#define INDEX_CSS "html body{padding:0;margin:0;width:100%;height:100%}.out-box{display:table;position:fixed;width:.... #define INDEX_JS "var _brightnessSliderEle=null,_brightnessValueEle=null,_buttonEle=null,_state=null;function init(.... #define INDEX_HTML "<!DOCTYPE html><html lang='en'><head><meta charset='UTF-8'><meta name='viewport' content='widt ....
그리고, 아래와 같이 코드를 추가하여 루트 경로로 접속할 때 웹페이지를 출력할 수 있도록 합니다.
setup() { // ... 중략 ... _server.on("/", onRequestIndexHTML); _server.on("/index.css", onRequestIndexCSS); _server.on("/index.js", onRequestIndexJS); // ... } void onRequestIndexHTML() { _server.send(200, "text/html ",INDEX_HTML); } void onRequestIndexJS() { _server.send(200, "application/javascript ",INDEX_JS); } void onRequestIndexCSS() { _server.send(200, "text/css",INDEX_CSS); }
아직 LED 조명은 붙이지 않았지만, 웹페이지로 제어할 수 있도록 구현 하였습니다.
예제 다운로드 :
Step3. LED 조명 제어하기
와이어 LED 조명에 전원을 연결한 모습입니다. USB 의 5v 로 동작하지만 상당히 밝은편 입니다.
니퍼를 이용하여 와이어 LED 조명의 USB 전원 케이블을 끊고, 내부에 있는 구리 케이블을 꺼내서 가늘게 말았습니다. 브레드보드 구멍에 넣기 위해서 끝을 뾰족하게 만들어야 합니다.
브레드 보드에 NodeMCU 를 붙이고 점퍼 케이블을 이용하여 회로를 구성해줍니다.
위 사진에 나온 회로를 fritzing 을 이용하여 그려보면 아래와 같습니다. LED 전구는 와이어 조명을 나타냅니다.
이 프로젝트에서 사용되는 NPN 트렌지스터는 2N2222 를 사용하였습니다. NPN 트랜지스터를 사용하는 이유는 다음과 같습니다. 와이어 조명은 5v 사용시 300mA 전류를 소비하지만 NodeMCU 의 입출력 핀은 3.3v 에 고작 12mA 으로 동작합니다. 따라서 스위치 역할을 해주는 NPN 트렌지스터를 사용하는 것 입니다. 즉, 낮은 전압과 전류로 높은 전압과 전류를 사용하는 기기를 컨트롤 하기 위함입니다.
디지털 회로에서 NPN 트렌지스터의 역할은 아래 그림과 같이 전류가 흐를 수 있도록 스위칭해주는 것 입니다. collector, emitter, base 핀의 위치는 종류와 제조사마다 다르므로 사용하기 전에 데이터 시트를 확인해야 합니다. 제가 사용한 2N2222 NPN 트렌지스터는 평평한쪽 기준으로 왼쪽부터 collector, base, emitter 입니다. (참고 - NPN 트랜지스터의 과열과 파손을 방지하기 위하여 Base 에 저항을 연결해야 합니다. 이 예제에서 사용한 2N2222 는 Base 와 Emitter 간의 전위차가 0.65v 이상을 넘어가면 안됩니다. 하지만, Base 단에 저항을 연결하면 LED 조명의 밝기가 어두워지므로 트랜지스터가 과열되는 것을 감안하고 이런 회로를 구성 하였습니다. 현재 회로대로 구성하고 확인해보니 0.7v 가 나옵니다. )
위 회도로에서 보이는 것과 같이 트렌지스터의 Base 를 NodeMCU 의 'D1' 핀에 연결 하였습니다. 'D1' 핀은 5 라는 번호를 갖고 있습니다. 아주 간단한 입출력 테스트 위하여 아두이노 IDE 의 기본 예제인 Blink 를 열어봅니다. 상단 메뉴에서 '파일 -> 예제 -> Basics -> Blink' 순서대로 들어가서 예제를 불러올 수 있습니다.
기본 예제에서 LED 번호만 우리가 연결한 5번 디지털 출력핀으로 변경합니다.
void setup() { // 5번 디지털 핀 출력 모드로 초기화. pinMode(5, OUTPUT); } void loop() { digitalWrite(5, HIGH); // LED 조명 켜기 delay(1000); // 1000ms 딜레이 digitalWrite(5, LOW); // LED 조명 끄기 delay(1000); // 1000ms 딜레이 }
이 코드를 업로드하여 실행하면 1초 간격으로 LED 조명이 깜빡이는 것을 볼 수 있습니다.
다음으로 밝기 조절과 관련된 테스트입니다. 우선 설명에 앞서 관련 코드를 올리고 테스트부터 해보도록 하겠습니다.
void setup() { // 5번 디지털 핀 출력 모드로 초기화 pinMode(5, OUTPUT); } void loop() { // 밝아졌다 어두워졌다를 반복하게 합니다. // 0 : 꺼짐, 1023 : 가장 밝게. for(int i = 1; i < 1024; ++i) { analogWrite(5, i); delay(1); } for(int i = 1023; i > 0; --i) { analogWrite(5, i); delay(1); } }
아래 영상은 위 코드를 올려서 실행한 모습입니다.
이런 밝기 조절은 analogWrite() 함수를 이용하여 할 수 있습니다.
digitalWrite() 함수는 단지 끄고 켜는 것 (HIGH, LOW) 밖에 할 수 없습니다. 하지만, analogWirte() 는 0에서 1023 까지의 총 1024 단계를 입력할 수 있습니다. (NodeMCU (ESP8266-12E) 는 1024 단계가 기본 값이지만, 다른 아두이노 기기들은 기본 255단계를 갖고 있습니다.) 이 값의 의미는 PWM 제어에 필요한 주파수를 입력하는 것 입니다.
PWM 제어란, 간단히 비유해서 스위치에 연결된 전구가 하나 있다고 가정했을 때, 1초에 스위치를 500 번 정도 아주 빠르게 껐다켰다 반복하면 사람 눈에는 깜빡이는 것처럼 보이지는 않겠지만 스위치를 계속 켜놓고 있는 상태보다는 어둡게 보일 것 입니다. 1초에 500번에서 60번 정도로 줄인다면 더 어둡게 보이겠죠.
이런 PWM 제어는 NodeMCU 의 모든 디지털 출력 핀에서 사용 가능한 것은 아닙니다.
(D1) GPIO 5, (D2) GPIO 4, (D3) GPIO 0, (D4) GPIO 2, (D5) GPIO 14, (D6) GPIO 12, (D7) GPIO 13, (D8) GPIO 15, (RX) GPIO 3, (TX) GPIO 1 핀에서만 사용 가능합니다.
Step4. 웹으로 LED 제어하기
드디어 Step2 에서 만들었던 조명 제어에 필요한 웹 API 서버에 실제로 조명을 제어하는 코드를 붙여보겠습니다.
우선 Step2 에서 가장 마지막으로 수정한 코드에 비어있는 setBright() 함수와 setOnOff() 함수 내부를 아래와 같이 채워줍니다.
void setOnOff() { if(!_isOn) { digitalWrite(_ledControlPin, LOW); } else { setBright(); } } void setBright() { if(!_isOn) return; // 1 에서 100사이의 값을 100에서 1024 사이의 값으로 치환 int value = map(_brightness, 1,100, 100,1024); analogWrite(_ledControlPin, value); }
이제 완성된 코드를 올려봅시다.
크리스마스 트리에도 응용해 보았습니다.
마치며
ESP8266 를 사용하는 가성비 좋은 마이크로 컨트롤러 보드와 아두이노를 이용하여 아주 간단한 IOT 무드등을 만들어 봤습니다. 아주 기본적인 예제로 구현한 탓에 아쉬운점이 있습니다. 우선, 기기를 서버로 구현하였기 때문에 IP 주소를 알아내어 접속해야 하고, 공유기를 사용한다면 외부망에서도 접근 가능하도록 포트를 개방해줘야할 것 입니다. 만약 조명에 스위치를 달아준다면, 웹 페이지와 동기화도 쉽지 않으며 보안에도 취약할 수 있습니다. 만약 본격적으로 IOT 기기를 만들어야 한다면 MQTT를 활용하는 것이 더 나은 방법입니다. 그럼에도 이번 포스팅은 이렇게 간단한 방법으로도 IOT 기기를 만들 수 있다는 것을 소개하는데 의의를 두고 작성 하였습니다. 만약 앞으로 기회가 된다면 MQTT를 이용하여 구현하는 방법에 대하여 다루겠습니다.
<IOT 조명을 구글홈과 연동한 모습입니다.>