본문 바로가기
Hardware/MCU(Arduino,ESP8266)

ESP01 모듈로 만드는 IoT 웹서버 - WIFI로 전등 켜기

by lovey25 2020. 8. 28.
반응형

요즘은 ESP8266 보드를 아두이노 보다 더 많이 사용하고 있습니다. USB 포트가 없어서 별도로 USB-UART 변환 도구가 있어야 하는 불편함이 있지만 크기도 작고 와이파이도 사용할 수 있기 때문에 실생활의 활용도 면에서 더 장점이 많지 않나 싶습니다.

오늘은 지금까지 ESP01 모듈에 Blynk 라이브러리를 올려서 만들었던 와이파이 스위치를 Blynk 대신 ESP8266WebServer.h 라이브러리를 이용해서 ESP01 모듈 자체를 웹서버로 만들어서 와이파이 스위치 기능을 구현하려고 합니다. 이전 포스팅 내용은 아래 2개의 링크 참고해 주세요.

 

ESP01 모듈 활용, 핸드폰으로 켜고 끄는 IoT 전등 만들기

지난 포스팅에서는 간단하게 ESP01 모듈에 Blynk를 이용해서 모듈의 내장 LED를 핸드폰으로 껐다가 켜는 예제를 한번 따라 해 봤었습니다. 원격으로 스위치 제어를 하기 위한 로직을 구현해 봤으니

kwonkyo.tistory.com

 

ESP8266-01 모듈로 Blynk 시작하기 - 원격으로 LED 켜고 끄기

Blynk라는 솔루션을 아시나요? 엄청 편리하고 디자인도 이쁘고 거기다가 무료(일부만)라서 많이들 사용하실텐데요. 아두이노, ESP8266 등 컨트롤러를 컴퓨터와 유선으로 혹은 BT, WIFI를 이용해서 무�

kwonkyo.tistory.com

하드웨어의 구성은 [https://kwonkyo.tistory.com/397]에서 만들었던 IoT 전등과 동일한데요. 왜 굳이 편리한 Blynk를 대신할 방법을 찾냐면, Blynk가 제한적 무료 서비스이기 때문입니다.

IoT 전등을 만들었으면 온가족이 편리하게 사용할 수 있도록 해야 할 텐데 Blynk를 이용하게 되면 핸드폰에서도 Blynk 클라이언트를 사용해야 합니다. 물론 Blynk니까 클라이언트 쉐어링 역시 매우 편리하게 할 수 있도록 엄청 쉬운 인터페이스를 제공하고 있습니다. 그런데 클라이언트 프로그램을 다른 사람에게 배포를 하기 위해서는 결재가 필요했습니다.

클라이언트 앱 설정 메뉴에서 Shared access 메뉴를 통해 앱 공유가 가능하지만 환불 불가한 1000 유닛의 결재가 필요합니다.

그래서 아쉽지만 Blynk를 포기하고 직접 웹서버를 올리는 방법을 사용하기로 했습니다. 인터넷에 웹서버 관련된 예제가 무수히 많이 돌아다니고 있어서 맘에 드는 거 아무거나 골라서 선택하면 되는데요. 대략적으로 2~3가지 형태로 예제가 구분이 되는 것 같았습니다. 하지만, 제가 실력이 부족한 탓이겠지만 인터넷에서 찾은 예제들은 그 어느 하나 완벽하게 동작하는 예제가 없었습니다. 어떤 건 웹 디자인은 용의 하지만 메모리 접근 에러가 발생해서 서버 구동이 안되고 어떤 건 모바일에서 접속이 제한되는 등 사소한 문제들이 하나씩 있었고, 결국 이것저것 조금씩 짜깁기를 해서 또 한 번 누더기 코드를 만들 수밖에 없었습니다.

IoT 스위치 웹 화면 디자인

그렇게 결정된 웹 화면 디자인은 위 그림과 같습니다. 스탠드를 켜고 끄는 토글 방식의 버튼을 구성해서 누르면 그 상태가 전등의 상태와 동일하게 변경이 되도록 하였습니다. 그리고 ESP01 모듈에는 총 4개의 GPIO 핀이 있기 때문에 혹시나 기능을 추가할 것을 대비해서 예비용 버튼을 하나 더 만들었습니다.

펌웨어

ESP01 모듈에 올라갈 펌웨어입니다. 펌웨어는 로직을 만드는 메인 코드와 웹페이지 UI를 구성하는 보조 파일로 구분을 하였습니다.

ESP01_WebServer.ino

먼저 메인코드입니다.

#include <ESP8266WiFi.h>
#include <WiFiClient.h> 
#include <ESP8266WebServer.h>
#include "mainPage.html"

const char *ssid = "[SSID를입력]";
const char *password = "[네트워크비밀번호를입력]";

#define load0  0           // Realy 컨트롤 핀
#define load1  1           // 예비용

bool isFirstTime = true;   // 서버 초기 기동여부를 파악하기 위한 변수

String L1Status, L2Status, myLocalIP;
ESP8266WebServer server(80);  // server on port 80
IPAddress myIP;
EspClass esp;

// 서버 루트 페이지를 호출하는 함수
void FunctionRoot() {
  String s = (const __FlashStringHelper *)MAIN_page;
  
  // 서버가 처음 실행되었을 때만 OFF 상태로 초기화
  if(isFirstTime)           
  {
    L1Status = (const __FlashStringHelper *)BTN1_OFF;
    L2Status = (const __FlashStringHelper *)BTN2_OFF;
    isFirstTime = false;
  }
  
  // 스위치 상태에 맞게 HTML 코드 업데이트
  s.replace("@@L1@@", L1Status);
  s.replace("@@L2@@", L2Status);
  server.send(200, "text/html", s);
}

// 1번 스위치 클릭시 호출되는 함수
void FunctionForm1() {             
  String t_state = server.arg("submit");
  
  if(t_state=="ON1") {                      // 스위치 선택에 따라 GPIO 상태 전환
    L1Status=(const __FlashStringHelper *)BTN1_ON;    
    digitalWrite(load0, HIGH);          
    Serial.println("load0 ON");
  }
  if(t_state=="OFF1") {
    L1Status=(const __FlashStringHelper *)BTN1_OFF;;    
    digitalWrite(load0, LOW);         
  }
  server.sendHeader("Location", "/");       // This Line Keeps It on Same Page
  server.send(302, "text/plain", "Updated-- Press Back Button");  
  delay(500);
}

// 2번 스위치 클릭시 호출되는 함수
void FunctionForm2() {             
  String t_state = server.arg("submit");
  
  if(t_state=="ON2") {                      // 스위치 선택에 따라 GPIO 상태 전환
    L2Status=(const __FlashStringHelper *)BTN2_ON;    
    digitalWrite(load1, HIGH);          
    Serial.println("load1 ON");
  }
  if(t_state=="OFF2") {
    L2Status=(const __FlashStringHelper *)BTN2_OFF;    
    digitalWrite(load1, LOW);         
  }
  server.sendHeader("Location", "/");       // This Line Keeps It on Same Page
  server.send(302, "text/plain", "Updated-- Press Back Button");  
  delay(500);
}

void setup() {                      
  Serial.begin(115200);
  delay(10);

  pinMode(load0, OUTPUT);              // Relay 제어용 핀을 디지털 출력모드로 설정
  digitalWrite(load0, LOW);            // LOW상태로 초기화

  WiFi.begin(ssid, password);          // WIFI네트워크에 접속 시작!
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }  
  myIP = WiFi.localIP();
  char buf[18];                         // https://gist.github.com/loosak/76019faaefd5409fca67
  sprintf(buf, "%d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]);
  myLocalIP = String(buf);

  Serial.println("IP: " + myLocalIP);       
  server.on("/", FunctionRoot);           // 클라이언트가 "/"를 요청했을 때 FunctionRoot 함수를 호출
  server.on("/form1", FunctionForm1);     // 클라이언트가 "/form1"를 요청했을 때 FunctionForm1 함수를 호출
  server.on("/form2", FunctionForm2);
  server.begin();
  delay(1000);
}

void loop() {                           
  server.handleClient();
  delay(0);
}

ESP01 모듈이 접속하고자 하는 네트워크를 지정해서 온라인 상태가 되면 웹서버를 시작합니다. 그리고 아래 HTML 문법으로 구성된 UI 코드의 문자열을 가져와서 전등 상태에 맞게 바꿔치기하는 구성으로 되어 있습니다.

mainPage.html

다음으로 UI 부분 보조 코드입니다.

실제로 HTML 파일은 아니지만 문자열의 대부분이 HTML 문법으로 구성되어 있기 때문에 코드 편집기에서 HTML로 구문 강조가 되도록 확장자를 HTML로 하였습니다.

HTML 코드가 저장되는 변수는 PROGMEM 키워드를 사용했는데요. 데이터를 용량이 부족한 SRAM 대신 플래시 메모리에 저장하도록 하는 방법이라고 합니다. (참조:www.arduino.cc/reference/ko/language/variables/utilities/progmem/) 그리고 PROGMEM 키워드로 정의된 문자열은 String변수에 바로 저장할 경우 에러가 생겨서 "(const __FlashStringHelper *)"의 형 변환을 사용해야 했습니다. (아직 정확한 이유는 모르지만 메모리 접근 위반 에러가 발생해서 몇 날 며칠을 고생하다가 우연히 알아내어 해결한 방법입니다.)

그리고 HTML 코드 앞뒤를 감싸고 있는 R"=====( **** )=====" 키워드가 궁금하신 분들이 계실텐데요. 많은 예제에서 이 키워드를 사용은 하고 있지만 설명은 찾기가 힘들었는데요. 바로 ESP8266보드에서 블록 전체를 문자열로 인식하게하는 키워드 입니다. 키워드 안쪽의 모든 문자들을 순수한 문자열로 인식하기 때문에 블럭 내부의 세미콜론(;), 따옴표("), 슬래시(/) 같은 특수문자도 자유롭게 사용할 수 있습니다.

const char BTN1_ON[] PROGMEM = R"=====(
<button style=background-color:#fdfa3d; TYPE=SUBMIT; VALUE="OFF1"; name=submit>OFF1</button>
)=====";

const char BTN1_OFF[] PROGMEM = R"=====(
<button TYPE=SUBMIT; VALUE="ON1"; name=submit>ON1</button>
)=====";

const char BTN2_ON[] PROGMEM = R"=====(
<button style=background-color:#fdfa3d; TYPE=SUBMIT; VALUE="OFF2"; name=submit>OFF2</button>
)=====";

const char BTN2_OFF[] PROGMEM = R"=====(
<button TYPE=SUBMIT; VALUE="ON2"; name=submit>ON2</button>
)=====";

const char MAIN_page[] PROGMEM = R"=====(
<!DOCTYPE html>
<html lang="ko">
<head>
<meta name="viewport"content="width=device-width,initial-scale=1,user-scalable=no"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="icon" href="data:,">
<style>
body{text-align:center;font-family:verdana;}
button{border:0;border-radius:0.3rem;background-color:#1fa3ec;color:rgb(236, 93, 201);line-height:2.4rem;font-size:1.2rem;width:100%}
</style>
</head>

<TITLE>
WIFI Controler
</TITLE>

<BODY>
<div style="text-align:center;display:inline-block;min-width:260px;">
<CENTER>
<h2>우리집 IOT System</h2>
<p>거실스탠드</p>
<form method="post" action="/form1">
<p>@@L1@@</p>
</form>
<p>확장 예비용</p>
<form method="post" action="/form2">
<p>@@L2@@</p>
</form>
</CENTER>
</BODY>
</HTML>
)=====";

 

이상 ESP01에서 초간단 IoT 서버를 구성하는 예제였습니다.

 

끝!

반응형

댓글