본문 바로가기
아두이노 관련/NodeMCU

NodeMCU - 웹서버 웹페이지에서 NodeMCU LED 제어하기

by Bugwhale 2020. 2. 7.

1. 개요

이전 글에서 아두이노 개발환경으로 NodeMCU 개발보드를 사용하는 방법을 설명하며 추가적으로 보드로 LED 제어와 Wi-Fi에 연결하여 아이피를 확인해보는 간단한 테스트를 해보았다. 하지만 우리가 NodeMCU 보드를 사용하는 이유는 말하자면 ESP8266 칩, Wi-Fi 기능을 제대로 사용하기 위해서이다.

이 글에서는 HTML 로 간단하게 웹페이지를 만들어 NodeMCU 보드를 Wi-Fi에 연결 후 웹서버로 구동 시킨뒤 웹브라우저를 통해서 NodeMCU 웹서버를 접속 후 버튼을 통하여 NodeMCU 보드에 연결된 LED 를 켜고 끄는 기능을 구현할 것이다.

최대한 쉽게 설명해도 이해하기 어려운 것이 당연하다. 제대로 이해하기 위해서는 웹서버-웹클라이언트, HTTP 프로토콜, 웹언어인 HTML 등 의 지식이 필요하기 때문이다. ESP8266 칩이 내장된 NodeMCU 보드같은 개발보드를 다루다 보면 오히려 네트워크를 배우는데 마이크로컨트롤러 제어하는 기능을 부수적으로 배운다고 말할 수도 있겠다.

1.1 요약 정리

1. HTML 언어로 웹페이지 구성 (아두이노 IDE)
2. 구성한 웹페이지를 NodeMCU 보드에 업로드 (아두이노 IDE)
3. NodeMCU 보드를 Wi-Fi에 연결 (NodeMcu 보드)
4. NodeMCU 보드를 웹서버로 구동 (NodeMcu 보드)
5. 웹클라이언트로 NodeMCU 보드 웹서버에 웹페이지 요청 (크롬, 파이어폭스 등의 웹브라우저)
6. 웹클라이언트로 요청받은 웹페이지 전송 (NodeMcu 보드)
7. 웹페이즈에서 버튼(하이퍼링크)를 눌러 웹서버로 데이터 전송 (크롬, 파이어폭스 등의 웹브라우저)
8. 웹서버에서 무슨 버튼이 눌렸는지 확인 후 LED 를 ON/OFF (NodeMcu 보드)

1.2 준비물

  • NodeMCU 개발보드(이 글에서는 NodeMCU v3)
  • Micro 5pin USB
  • LED 1개
  • 130 ~ 330 Ω 저항 1개

2. LED 저항 값 선정하기(옵션)

우리가 이 글에서 사용할 저항은 220 Ω 이며 왜 이 값의 저항을 사용하는지에 대한 설명입니다. 전자와 전기에 대한 기본 지식이 있는 상황을 기준으로 설명하여 지식이 없다면 이해하기 어려울 것이니 다음 과정으로 넘어가도 상관없습니다.

아두이노를 배울 때 LED 를 사용하면 항상 330 Ω 또는 220 Ω 정도의 저항을 사용하였을 것입니다. 왜냐면 보통 아두이노에 사용하는 LED 의 정격 전류는 20 mA 전후이며 LED 의 정방향 바이오스를 0.7 V 로 봤을 때 키르히호프의 전류 법칙과 옴의 법칙을 이용하여 아래와 같이 계산하였기 때문입니다.

(5 - 0.7) [V] / 20 [mA] = 215.0 [Ω]

하지만 그건 아두이노 보드의 전원이 대부분 5 V 이었기 때문었고 이 글에서 사용할 NodeMCU 보드의 핀전압이 3.3 V 이기 때문에 계산식이 달라집니다. 위의 식을 그대로 대입하면 아래와 같은 식이 나옵니다.

(3.3 - 0.7) [V] / 20 [mA] = 130 [Ω]

위의 식에 따라 130 Ω 이상의 저항을 사용하면 되지만 하지만 130 Ω 저항값으로 딱 떨어지는 저항을 구하기 힘들 뿐더러 아두이노 보드를 가지고 있다면 대부분 220 Ω 저항을 가지고 있을 테니 220 Ω 저항으로 선정하였습니다.

(3.3 - 0.7) [V] / 220 [Ω] = 11 [mA]

위의 계산식에 따르면 LED 에는 11 mA 가 흘러 빛은 조금 약할지라도 동작하는데는 이상이 없을 것으로 추측됩니다.

3. 웹페이지 구성

LED 를 ON/OFF 하는 버튼(링크), 현재 LED 상태를 표시하는 문구를 표시할 것입니다. 이 글을 HTML 를 설명하는 글이 아니므로 자세한 설명은 하지 않겠습니다. 실제 HTML 코드와 다른 부분이라면 12번째 줄에 ON/OFF 부분을 아두이노 개발환경에서 조건에 따라 변경시키는 코드가 되야합니다. 아래 항목에서 설명합니다.

<!DOCTYPE HTML>
<html>
<head>
	<meta charset="UTF-8">
	<title>LED Control Webpage</title>
</head>
<body>
	<h2>LED Control Webpage</h2>
	<a href='/ledon'>LED ON</a>
	<br>
	<a href='/ledoff'>LED OFF</a>
	<br>
	LED Status : ON/OFF
</body>
</html>

4. 하드웨어 구성

LED 하나와 저항 하나만을 사용하니 하드웨어 구성은 사진으로 대체합니다.

 

5. 소스코드

5.1 전체 소스코드

#include <ESP8266WiFi.h>

#define PIN_LED D0

const char* ssid = "Wi-Fi 아이디";
const char* password = "Wi-Fi 패스워드";

WiFiServer server(80);

void setup() {
	pinMode(PIN_LED, OUTPUT);
	digitalWrite(PIN_LED, LOW);
	
	Serial.begin(115200);
	
	WiFi.mode(WIFI_STA);
	WiFi.begin(ssid, password);

	while (WiFi.status() != WL_CONNECTED) {
		delay(500);
		Serial.print(".");
	}
	Serial.println("");
	Serial.print("Connecting to ");
	Serial.println(ssid);
	Serial.print("IP address: ");
	Serial.println(WiFi.localIP());
	
	server.begin();
	Serial.println("Server started");
}

void loop() {
	WiFiClient client = server.available();
	if(!client) return;
	
	Serial.println("새로운 클라이언트");
	client.setTimeout(5000);
	
	String request = client.readStringUntil('\r');
	Serial.println("request: ");
	Serial.println(request);

	if(request.indexOf("/ledoff") != -1) {
		digitalWrite(PIN_LED, LOW);
	}
	else if(request.indexOf("/ledon") != -1) {
		digitalWrite(PIN_LED, HIGH);
	}
	else {
		Serial.println("invalid request");
		digitalWrite(PIN_LED, digitalRead(PIN_LED));
	}

	while(client.available()) {
		client.read();
	}

	client.print("HTTP/1.1 200 OK");
	client.print("Content-Type: text/html\r\n\r\n");
	client.print("<!DOCTYPE HTML>");
	client.print("<html>");
	client.print("<head>"); 
	client.print("<meta&nbsp;charset=\"UTF-8\">");
	client.print("<title>LED Control Webpage</title>");
	client.print("</head>");
	client.print("<body>");
	client.print("<h2>LED Control Webpage</h2>");
	client.print("<a href='/ledon'>LED ON</a>");
	client.print("<br>");
	client.print("<a href='/ledoff'>LED OFF</a>");
	client.print("<br>");
	client.print("LED Status : ");
	client.print((digitalRead(PIN_LED)) ? "ON" : "OFF");
	client.print("</body>");
	client.print("</html>");

	Serial.println("클라이언트 연결 해제");
}

5.2 소스코드 설명

5.2.1 선언 코드
#include <ESP8266WiFi.h>

ESP8266WiFi 라이브러리 사용

#define PIN_LED D0

const char* ssid = "Wi-Fi 아이디";
const char* password = "Wi-Fi 패스워드";

D0 핀 번호 설정을 해주고 5, 6 번줄은 본인의 Wi-Fi 아이디와 패스워드 제대로 작성해줍니다.

WiFiServer server(80);

server 이름으로 WiFiServer 객체를 선언합니다. 여기서 매개변수 값인 80 은 내부에서 사용될 포트번호로 HTTP 프로토콜은 80번 포트를 사용하니 특별한 이유가 없다면 변경하지 않습니다.

5.2.2 setup() 코드
	pinMode(PIN_LED, OUTPUT);
	digitalWrite(PIN_LED, LOW);
	
	Serial.begin(115200);

PIN_LED 에 대한 핀 모드를 설정해주고 초기값은 LOW 로 OFF 해줍니다. 그리고 시리얼 모니터 초기화를 시켜줍니다. 속도는 상관없지만 NodeMcu 통신속도인 115200 으로 맞춰주었습니다.

	WiFi.mode(WIFI_STA);
	WiFi.begin(ssid, password);

mode 메서드로 ESP8266 모듈의 모드를 WIFI_STA 모드(다른 기기가 이 모듈을 통하여 인터넷 접속 금지)로 설정하며 begin 메서드로 Wi-Fi 연결을 시도합니다.

	while (WiFi.status() != WL_CONNECTED) {
		delay(500);
		Serial.print(".");
	}

Wi-Fi 접속상태를 계속하여 확인하며 접속될때까지 반복합니다.

	Serial.print("Connecting to ");
	Serial.println(ssid);
	Serial.print("IP address: ");
	Serial.println(WiFi.localIP());

접속된 Wi-Fi 아이디와 IP 주소를 시리얼 모니터에 출력시켜준다. 웹브라우저를 통하여 접속할때를 대비하여 IP 주소를 기억합니다.

	server.begin();

server.begin() 로 서버를 시작합니다.

5.2.3 loop() 코드
	WiFiClient client = server.available();
	if(!client) return;

클라이언트가 접속하였는지 확인하며 접속이 되어있지 않다면 return 으로 종료시킵니다. 여기서 클라이언트란 웹브라우저를 통해 접속한 유저를 의미합니다.

	client.setTimeout(5000);

클라이언트에게 HTTP를 전송을 시도하며 5000 ms(5초) 초과 시 타임아웃으로 넘어갑니다.

	String request = client.readStringUntil('\r');
	Serial.println("request: ");
	Serial.println(request);

전송받은 데이터를 \r 까지 (HTTP/1.1 까지) 클라이언트가 서버에 요청한 정보. 여기서는 URL 을 알기위해 사용되었습니다.

	if(request.indexOf("/ledoff") != -1) {
		digitalWrite(PIN_LED, LOW);
	}

요청한 정보에 /ledoff 가 포함되어 있다면(LED OFF 버튼을 눌렀다면) LED 를 OFF 시켜줍니다.

	else if(request.indexOf("/ledon") != -1) {
		digitalWrite(PIN_LED, HIGH);
	}

요청한 정보에 /ledon 가 포함되어 있다면(LED ON 버튼을 눌렀다면) LED 를 ON 시켜줍니다.

	else {
		Serial.println("invalid request");
		digitalWrite(PIN_LED, digitalRead(PIN_LED));
	}

그 외는(메인페이지 접속 등) 현재 LED 상태를 유지해줍니다.

	while(client.available()) {
		client.read();
	}

그 이외에 request 정보는 필요없으니 수신된 request 데이터를 전부 읽어버립니다.(버퍼 비움 효과)

	client.print("HTTP/1.1 200 OK");
	client.print("Content-Type: text/html\r\n\r\n");
	client.print("<!DOCTYPE HTML>");
	client.print("<html>");
	client.print("<head>");
	client.print("<meta charset=\"UTF-8\">");
	client.print("<title>LED Control Webpage</title>");
	client.print("</head>");
	client.print("<body>");
	client.print("<h2>LED Control Webpage</h2>");
	client.print("<a href='/ledon'>LED ON</a>");
	client.print("<br>");
	client.print("<a href='/ledoff'>LED OFF</a>");
	client.print("<br>");
	client.print("LED Status : ");
	client.print((digitalRead(PIN_LED)) ? "ON") : "OFF");
	client.print("</body>");
	client.print("</html>");

위에서 구상한 웹페이지를 NodeMcu 에서 사용하기 위한 코드로 바꿔 작성하였습니다. print 메소드를 한개만 사용해서 모든 데이터를 전송할 수도 있지만 가독성을 위하여 한줄한줄 잘라서 넣었습니다. 각 줄마다 기능을 살펴보면 59, 60 번째 줄은 HTTP response 해더부분이며 \r\n\r\n 으로 그 이후가 실제로 출력되는 바디부분이라는 것을 알려줍니다.

68번 줄과 70번줄을 설명하자면 LED ON, LED OFF 버튼을 누르면 각각 /ledon, /ledoff 로 끝나는 URL 에 접속하겠다고 NodeMcu 웹서버에 request(요청)을 합니다. 즉 서버에서는 LED ON/OFF 신호를 URL(주소)로 전달받아 44 ~ 53 번 줄에서 그걸 인식하여 LED를 ON/OFF 시키는 것입니다. 추가적으로 자세하게 볼 부분은 73 번줄인데 (조건 ? A : B) 문법으로 현재 LED 상태에 따라 ON 이면 "ON"문자열을, OFF 면 "OFF"문자열을 출력시킵니다.

5. 동작 결과

6. 참조

-

댓글