아두이노 - 웹 서버 다중 페이지 | Arduino - Web Server Multiple Pages
이 튜토리얼에서는 Arduino를 웹 서버로 전환하는 방법을 알아볼 것입니다. 이 서버는 index.html, temperature.html, led.html, error_404.html, error_405.html 등과 같이 동시에 여러 페이지를 처리할 수 있습니다. 각 페이지의 내용, HTML, CSS, JavaScript를 포함하여 다른 파일에 Arduino IDE에 저장됩니다. PC 또는 스마트폰의 웹 브라우저에서 Arduino 웹 서버에 접속하면 웹을 통해 Arduino에 연결된 센서와 액츄에이터를 보고 제어할 수 있습니다. 또한, 웹 서버는 .html 확장자가 있는 링크와 없는 링크 모두를 수용하도록 설계될 것입니다.
이 튜토리얼을 따라 하면 아두이노를 몇 가지 멋진 기능이 있는 웹 서버로 만들 수 있습니다:
여러 웹 페이지가 동시에 활성화되어 있습니다.
각 페이지의 HTML 내용(HTML, CSS, Javascript 포함)은 별도의 파일에 따로 저장됩니다.
HTML 내용은 센서에서 실시간 값을 동적으로 업데이트할 수 있어, 웹 페이지들을 동적이고 반응형으로 만듭니다.
.html 확장자가 있거나 없이 페이지에 접근할 수 있습니다. 예를 들어, http://192.168.0.2/led 또는 http://192.168.0.2/led.html 링크를 사용해서 동일한 LED 제어 페이지에 도달할 수 있습니다.
웹 서버는 404 Not Found, 405 Method Not Allowed 와 같은 HTTP 오류 코드를 처리합니다.
복잡하게 들릴 수도 있지만 걱정하지 마세요! 이 튜토리얼은 단계별 지침을 제공하며, 코드는 초보자도 쉽게 이해하고 자신만의 아두이노 웹 서버를 만들 수 있도록 초보자 친화적으로 설계되었습니다.
1 | × | Arduino UNO R4 WiFi | Amazon | |
1 | × | USB Cable Type-C | 쿠팡 | Amazon | |
1 | × | (Optional) 9V Power Adapter for Arduino | Amazon | |
1 | × | (Recommended) Screw Terminal Block Shield for Arduino UNO R4 | 쿠팡 | Amazon | |
1 | × | (Recommended) Breadboard Shield For Arduino UNO R4 | 쿠팡 | Amazon | |
1 | × | (Recommended) Enclosure For Arduino UNO R4 | Amazon | |
공개: 이 섹션에서 제공된 링크 중 일부는 제휴 링크입니다. 이 링크를 통해 구매한 경우 추가 비용없이 수수료를 받을 수 있습니다. 지원해 주셔서 감사합니다.
아두이노 우노 R4와 웹 서버(핀아웃, 작동 방식 및 프로그래밍 포함)에 익숙하지 않다면, 다음 튜토리얼을 통해 배울 수 있습니다:
웹 브라우저가 아두이노 보드에 HTTP 요청을 보낼 때, 아두이노는 다음 작업을 수행하도록 프로그래밍되어야 합니다:
웹 브라우저로부터 HTTP 요청을 수신할 수 있는 웹 서버를 만드세요.
HTTP 요청을 받으면 요청 헤더의 첫 번째 줄을 읽으세요.
HTTP 요청의 첫 번째 줄을 기반으로 요청을 라우팅하여 아두이노가 반환해야 할 웹 페이지를 결정하세요.
(선택사항) 사용자가 보낸 제어 명령어를 식별하기 위해 HTTP 요청 헤더를 분석하세요.
(선택사항) 받은 제어 명령어에 따라 아두이노에 연결된 장치들을 제어하세요.
다음을 포함하는 HTTP 응답을 웹 브라우저로 돌려보내세요:
이러한 작업을 수행함으로써 아두이노는 HTTP 요청을 효과적으로 처리하고 웹 브라우저에 적절한 응답을 제공할 수 있으며, 이를 통해 웹 기반 제어 및 아두이노에 연결된 장치와의 상호작용이 가능해집니다.
라우팅 기능은 가장 중요한 작업으로 자세히 설명될 것입니다. 다른 부분들은 Arduino - Web Server 튜토리얼에서 다루어집니다. 라우팅 알고리즘을 이해하게 되면, 여러 페이지를 가진 웹 서버를 위한 전체 코드를 살펴볼 것입니다.
라우팅 기능을 위한 코드를 작성하기 전에, 아두이노에서 사용할 웹 페이지와 해당 HTTP 메소드의 목록을 만들어야 합니다. 이 튜토리얼에서는 GET 메소드만 지원할 것입니다. 하지만 필요하다면 다른 HTTP 메소드를 쉽게 추가할 수 있습니다. 여기 예시 목록이 있습니다:
홈 페이지 가져오기
온도 페이지 가져오기
문 페이지 가져오기
LED 페이지 가져오기
그 다음, 페이지 목록에 해당하는 첫 번째 줄 HTTP 요청 헤더 목록을 만들어야 합니다.
홈 페이지 가져오기:
온도 페이지 가져오기
문 페이지 가져오기
LED 페이지 가져오기
요약하자면, 다음과 같은 목록을 가지고 있습니다:
GET /
GET /index.html
GET /temperature.html
GET /door.html
GET /led.html
다음 코드는 Arduino에서 웹 서버를 위한 라우팅 기능을 구현하는 방법을 보여줍니다.
if (HTTP_req.indexOf("GET") == 0) {
if (HTTP_req.indexOf("GET / ") > -1 || HTTP_req.indexOf("GET /index.html ") > -1) {
Serial.println("home page");
} else if (HTTP_req.indexOf("GET /temperature.html ") > -1) {
Serial.println("temperature page");
} else if (HTTP_req.indexOf("GET /door.html ") > -1) {
Serial.println("door page");
} else if (HTTP_req.indexOf("GET /led.html ") > -1) {
Serial.println("led page");
} else {
Serial.println("404 Not Found");
}
} else {
Serial.println("405 Method Not Allowed");
}
필요에 따라 페이지를 추가하거나 제거하기 위해 코드를 자유롭게 수정하십시오. 이제 .html 확장자가 있는 링크와 없는 링크를 처리하기 위해 라우팅 함수를 업데이트해 봅시다.
if (HTTP_req.indexOf("GET") == 0) {
if (HTTP_req.indexOf("GET / ") > -1 || HTTP_req.indexOf("GET /index ") > -1 || HTTP_req.indexOf("GET /index.html ") > -1) {
Serial.println("home page");
} else if (HTTP_req.indexOf("GET /temperature ") > -1 || HTTP_req.indexOf("GET /temperature.html ") > -1) {
Serial.println("temperature page");
} else if (HTTP_req.indexOf("GET /door ") > -1 || HTTP_req.indexOf("GET /door.html ") > -1) {
Serial.println("door page");
} else if (HTTP_req.indexOf("GET /led ") > -1 || HTTP_req.indexOf("GET /led.html ") > -1) {
Serial.println("led page");
} else {
Serial.println("404 Not Found");
}
} else {
Serial.println("405 Method Not Allowed");
}
아래는 여러 페이지를 가진 웹 서버를 생성하는 완전한 Arduino 코드입니다. 단순하게 유지하기 위해, 각 페이지에 대한 HTML 내용은 매우 간단하며 Arduino 코드에 직접 포함되어 있습니다. 다음 파트에서는 각 페이지에 대한 HTML 콘텐츠를 별도의 파일로 분리하는 방법을 배워, 코드를 더 정돈되고 관리하기 쉽게 만들 것입니다.
#include <WiFiS3.h>
#define PAGE_HOME 0
#define PAGE_TEMPERATURE 1
#define PAGE_DOOR 2
#define PAGE_LED 3
#define PAGE_ERROR_404 -1
#define PAGE_ERROR_405 -2
const char ssid[] = "YOUR_WIFI";
const char pass[] = "YOUR_WIFI_PASSWORD";
int status = WL_IDLE_STATUS;
WiFiServer server(80);
void setup() {
Serial.begin(9600);
String fv = WiFi.firmwareVersion();
if (fv < WIFI_FIRMWARE_LATEST_VERSION)
Serial.println("Please upgrade the firmware");
while (status != WL_CONNECTED) {
Serial.print("Attempting to connect to SSID: ");
Serial.println(ssid);
status = WiFi.begin(ssid, pass);
delay(10000);
}
server.begin();
printWifiStatus();
}
void loop() {
WiFiClient client = server.available();
if (client) {
String HTTP_req = "";
while (client.connected()) {
if (client.available()) {
Serial.println("New HTTP Request");
HTTP_req = client.readStringUntil('\n');
Serial.print("<< ");
Serial.println(HTTP_req);
break;
}
}
while (client.connected()) {
if (client.available()) {
String HTTP_header = client.readStringUntil('\n');
if (HTTP_header.equals("\r"))
break;
}
}
int page_id = 0;
if (HTTP_req.indexOf("GET") == 0) {
if (HTTP_req.indexOf("GET / ") > -1 || HTTP_req.indexOf("GET /index ") > -1 || HTTP_req.indexOf("GET /index.html ") > -1) {
Serial.println("home page");
page_id = PAGE_HOME;
} else if (HTTP_req.indexOf("GET /temperature ") > -1 || HTTP_req.indexOf("GET /temperature.html ") > -1) {
Serial.println("temperature page");
page_id = PAGE_TEMPERATURE;
} else if (HTTP_req.indexOf("GET /door ") > -1 || HTTP_req.indexOf("GET /door.html ") > -1) {
Serial.println("door page");
page_id = PAGE_DOOR;
} else if (HTTP_req.indexOf("GET /led ") > -1 || HTTP_req.indexOf("GET /led.html ") > -1) {
Serial.println("led page");
page_id = PAGE_LED;
} else {
Serial.println("404 Not Found");
page_id = PAGE_ERROR_404;
}
} else {
Serial.println("405 Method Not Allowed");
page_id = PAGE_ERROR_405;
}
if (page_id == PAGE_ERROR_404)
client.println("HTTP/1.1 404 Not Found");
if (page_id == PAGE_ERROR_405)
client.println("HTTP/1.1 405 Method Not Allowed");
else
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
client.println();
client.println("<!DOCTYPE HTML>");
client.println("<html>");
client.println("<head>");
client.println("<link rel=\"icon\" href=\"data:,\">");
client.println("</head>");
String html;
switch (page_id) {
case PAGE_HOME:
client.println("This is home page");
break;
case PAGE_TEMPERATURE:
client.println("This is temperature page");
break;
case PAGE_DOOR:
client.println("This is door page");
break;
case PAGE_LED:
client.println("This is LED page");
break;
case PAGE_ERROR_404:
client.println("Page Not Found");
break;
case PAGE_ERROR_405:
client.println("Method Not Allowed");
break;
}
client.println("</html>");
client.flush();
client.flush();
delay(10);
client.stop();
}
}
void printWifiStatus() {
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("signal strength (RSSI):");
Serial.print(WiFi.RSSI());
Serial.println(" dBm");
}
Attempting to connect to SSID: YOUR_WIFI
IP Address: 192.168.0.2
signal strength (RSSI):-39 dBm
192.168.0.2
192.168.0.2/index
192.168.0.2/index.html
192.168.0.2/led
192.168.0.2/led.html
192.168.0.2/door
192.168.0.2/door.html
192.168.0.2/temperature
192.168.0.2/temperature.html
192.168.0.2/blabla
192.168.0.2/blabla.html
시리얼 모니터에서 받은 IP 주소로 192.168.0.2를 변경해야 합니다. 주의하세요.
다음과 같은 페이지들을 볼 수 있습니다: 홈 페이지, LED 페이지, 문 페이지, 온도 페이지, 그리고 찾을 수 없는 페이지
시리얼 모니터에서 출력도 확인할 수 있습니다.
이전 코드에는 각 페이지에 대해 아주 간단한 HTML 내용이 있습니다. 하지만 많은 HTML을 사용한 화려한 인터페이스를 만들고 싶다면, 코드가 크고 지저분해질 수 있습니다. 이를 간단하게 만들기 위해, HTML을 아두이노 코드에서 분리하는 방법을 배워보도록 하겠습니다. 이를 통해 HTML을 별도의 파일로 유지하여 관리하고 작업하기가 더 쉬워집니다.
#include <WiFiS3.h>
#include "index.h"
#include "temperature.h"
#include "door.h"
#include "led.h"
#include "error_404.h"
#include "error_405.h"
#define PAGE_HOME 0
#define PAGE_TEMPERATURE 1
#define PAGE_DOOR 2
#define PAGE_LED 3
#define PAGE_ERROR_404 -1
#define PAGE_ERROR_405 -2
const char ssid[] = "YOUR_WIFI";
const char pass[] = "YOUR_WIFI_PASSWORD";
int status = WL_IDLE_STATUS;
WiFiServer server(80);
float getTemperature() {
float temp_x100 = random(0, 10000);
return temp_x100 / 100;
}
void setup() {
Serial.begin(9600);
String fv = WiFi.firmwareVersion();
if (fv < WIFI_FIRMWARE_LATEST_VERSION)
Serial.println("Please upgrade the firmware");
while (status != WL_CONNECTED) {
Serial.print("Attempting to connect to SSID: ");
Serial.println(ssid);
status = WiFi.begin(ssid, pass);
delay(10000);
}
server.begin();
printWifiStatus();
}
void loop() {
WiFiClient client = server.available();
if (client) {
String HTTP_req = "";
while (client.connected()) {
if (client.available()) {
Serial.println("New HTTP Request");
HTTP_req = client.readStringUntil('\n');
Serial.print("<< ");
Serial.println(HTTP_req);
break;
}
}
while (client.connected()) {
if (client.available()) {
String HTTP_header = client.readStringUntil('\n');
if (HTTP_header.equals("\r"))
break;
}
}
int page_id = 0;
if (HTTP_req.indexOf("GET") == 0) {
if (HTTP_req.indexOf("GET / ") > -1 || HTTP_req.indexOf("GET /index ") > -1 || HTTP_req.indexOf("GET /index.html ") > -1) {
Serial.println("home page");
page_id = PAGE_HOME;
} else if (HTTP_req.indexOf("GET /temperature ") > -1 || HTTP_req.indexOf("GET /temperature.html ") > -1) {
Serial.println("temperature page");
page_id = PAGE_TEMPERATURE;
} else if (HTTP_req.indexOf("GET /door ") > -1 || HTTP_req.indexOf("GET /door.html ") > -1) {
Serial.println("door page");
page_id = PAGE_DOOR;
} else if (HTTP_req.indexOf("GET /led ") > -1 || HTTP_req.indexOf("GET /led.html ") > -1) {
Serial.println("led page");
page_id = PAGE_LED;
} else {
Serial.println("404 Not Found");
page_id = PAGE_ERROR_404;
}
} else {
Serial.println("405 Method Not Allowed");
page_id = PAGE_ERROR_405;
}
if (page_id == PAGE_ERROR_404)
client.println("HTTP/1.1 404 Not Found");
if (page_id == PAGE_ERROR_405)
client.println("HTTP/1.1 405 Method Not Allowed");
else
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
client.println();
String html;
switch (page_id) {
case PAGE_HOME:
html = String(HTML_CONTENT_HOME);
break;
case PAGE_TEMPERATURE:
html = String(HTML_CONTENT_TEMPERATURE);
html.replace("TEMPERATURE_MARKER", String(getTemperature(), 2));
break;
case PAGE_DOOR:
html = String(HTML_CONTENT_DOOR);
html.replace("DOOR_STATE_MARKER", "OPENED");
break;
case PAGE_LED:
html = String(HTML_CONTENT_LED);
html.replace("LED_STATE_MARKER", "OFF");
break;
case PAGE_ERROR_404:
html = String(HTML_CONTENT_404);
break;
case PAGE_ERROR_405:
html = String(HTML_CONTENT_405);
break;
}
client.println(html);
client.flush();
delay(10);
client.stop();
}
}
void printWifiStatus() {
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("signal strength (RSSI):");
Serial.print(WiFi.RSSI());
Serial.println(" dBm");
}
코드에서 WiFi 정보(SSID 및 비밀번호)를 본인 것으로 변경하세요.
다음 방법으로 Arduino IDE에서 index.h 파일을 생성하세요:
파일 이름을 index.h로 지정하고 OK 버튼을 클릭하세요.
아래 코드를 복사하여 index.h 파일에 붙여넣으세요.
const char *HTML_CONTENT_HOME = R""""(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:,">
<title>Home Page</title>
</head>
<body>
<h1>Welcome to the Home Page</h1>
<ul>
<li><a href="/led">LED Page</a></li>
<li><a href="/temperature">Temperature Page</a></li>
<li><a href="/door">Door Page</a></li>
</ul>
</body>
</html>
)"""";
마찬가지로, 아래의 내용을 포함하여 Arduino IDE에서 temperature.h 파일을 생성하세요.
const char *HTML_CONTENT_TEMPERATURE = R""""(
<!DOCTYPE html>
<html>
<head>
<title>Arduino - Web Temperature</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=0.7">
<meta charset="utf-8">
<link rel="icon" href="https://diyables.io/images/page/diyables.svg">
<style>
body { font-family: "Georgia"; text-align: center; font-size: width/2pt;}
h1 { font-weight: bold; font-size: width/2pt;}
h2 { font-weight: bold; font-size: width/2pt;}
button { font-weight: bold; font-size: width/2pt;}
</style>
<script>
var cvs_width = 200, cvs_height = 450;
function init() {
var canvas = document.getElementById("cvs");
canvas.width = cvs_width;
canvas.height = cvs_height + 50;
var ctx = canvas.getContext("2d");
ctx.translate(cvs_width/2, cvs_height - 80);
update_view(TEMPERATURE_MARKER);
}
function update_view(temp) {
var canvas = document.getElementById("cvs");
var ctx = canvas.getContext("2d");
var radius = 70;
var offset = 5;
var width = 45;
var height = 330;
ctx.clearRect(-cvs_width/2, -350, cvs_width, cvs_height);
ctx.strokeStyle="blue";
ctx.fillStyle="blue";
var x = -width/2;
ctx.lineWidth=2;
for (var i = 0; i <= 100; i+=5) {
var y = -(height - radius)*i/100 - radius - 5;
ctx.beginPath();
ctx.lineTo(x, y);
ctx.lineTo(x - 20, y);
ctx.stroke();
}
ctx.lineWidth=5;
for (var i = 0; i <= 100; i+=20) {
var y = -(height - radius)*i/100 - radius - 5;
ctx.beginPath();
ctx.lineTo(x, y);
ctx.lineTo(x - 25, y);
ctx.stroke();
ctx.font="20px Georgia";
ctx.textBaseline="middle";
ctx.textAlign="right";
ctx.fillText(i.toString(), x - 35, y);
}
ctx.lineWidth=16;
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.rect(-width/2, -height, width, height);
ctx.stroke();
ctx.beginPath();
ctx.arc(0, -height, width/2, 0, 2 * Math.PI);
ctx.stroke();
ctx.fillStyle="#e6e6ff";
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.rect(-width/2, -height, width, height);
ctx.fill();
ctx.beginPath();
ctx.arc(0, -height, width/2, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle="#ff1a1a";
ctx.beginPath();
ctx.arc(0, 0, radius - offset, 0, 2 * Math.PI);
ctx.fill();
temp = Math.round(temp * 100) / 100;
var y = (height - radius)*temp/100.0 + radius + 5;
ctx.beginPath();
ctx.rect(-width/2 + offset, -y, width - 2*offset, y);
ctx.fill();
ctx.fillStyle="red";
ctx.font="bold 34px Georgia";
ctx.textBaseline="middle";
ctx.textAlign="center";
ctx.fillText(temp.toString() + "°C", 0, 100);
}
window.onload = init;
</script>
</head>
<body>
<h1>Arduino - Web Temperature</h1>
<canvas id="cvs"></canvas>
</body>
</html>
)"""";
마찬가지로, 다음 내용으로 Arduino IDE에서 door.h 파일을 생성하세요.
const char *HTML_CONTENT_DOOR = R""""(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:,">
<title>Door Page</title>
</head>
<body>
<h1>Door Page</h1>
<p>Door State: <span style="color: red;">DOOR_STATE_MARKER</span></p>
</body>
</html>
)"""";
마찬가지로, 아래의 내용으로 Arduino IDE에 led.h 파일을 생성하세요.
const char *HTML_CONTENT_LED = R""""(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:,">
<title>LED Page</title>
</head>
<body>
<h1>LED Page</h1>
<p>LED State: <span style="color: red;">LED_STATE_MARKER</span></p>
</body>
</html>
)"""";
마찬가지로, 아래의 내용으로 Arduino IDE에서 error_404.h 파일을 만드세요.
const char *HTML_CONTENT_404 = R""""(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:,">
<title>404 - Page Not Found</title>
<style>
h1 {color: #ff4040;}
</style>
</head>
<body>
<h1>404</h1>
<p>Oops! The page you are looking for could not be found on Arduino Web Server.</p>
<p>Please check the URL or go back to the <a href="/">homepage</a>.</p>
<p>Or check <a href="https://arduinogetstarted.com/tutorials/arduino-web-server-multiple-pages"> Arduino Web Server</a> tutorial.</p>
</body>
</html>
)"""";
마찬가지로, 아래 내용을 포함하여 Arduino IDE에서 error_405.h 파일을 생성하세요.
const char *HTML_CONTENT_405 = R""""(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:,">
<title>405 - Method Not Allowed</title>
<style>
h1 {color: #ff4040;}
</style>
</head>
<body>
<h1>405 - Method Not Allowed</h1>
<p>Oops! The requested method is not allowed for this resource.</p>
<p>Please check your request or go back to the <a href="/">homepage</a>.</p>
<p>Or check <a href="https://arduinogetstarted.com/tutorials/arduino-web-server-multiple-pages"> Arduino Web Server</a> tutorial.</p>
</body>
</html>
)"""";
이제 아래와 같이 Arduino IDE에서 여러 파일이 있습니다:
※ NOTE THAT:
index.h 파일 내의 HTML 내용에 변경 사항을 만들었지만 ArduinoWebServer.ino 파일을 수정하지 않으면, 아두이노 IDE는 코드를 컴파일하고 ESP32에 업로드할 때 HTML 내용을 새로 고치거나 업데이트하지 않습니다.
이 상황에서 아두이노 IDE가 HTML 내용을 업데이트하도록 강제하려면 ArduinoWebServer.ino 파일에서 수정을 해야 합니다. 예를 들어, 빈 줄을 추가하거나 주석을 삽입할 수 있습니다. 이 작업은 IDE가 프로젝트에 변경이 있었다고 인식하게 하여 업로드할 때 업데이트된 HTML 내용이 포함되도록 합니다.