간단한것들은 다해본 것 같고.. SPI 인터페이스 확인을 위해서 컬러 LCD를 알아보고 있는중이다. 해당 부품이 오기전에 할걸좀 찾아봤는데.. 예전에 테스트 했던 웹서버를 설치하고 HTML과 스크립트를 어디까지 사용할 수 있는지 확인을 해보고 싶어졌다. 그래서 기본 샘플을 작성하고 HTML과 Java Script를 넣어서 어느정도 까지 사용할 수 있는지 확인해보고자 한다. 사실 펌웨어 기반에서 구동되는 녀석이라 크게는 바라지 않는데, 메뉴나 이런것들에 대한 스크립트 이런게 좀 된다면...
웹기반으로 이런저런 것들을 만들 기회가 올때 도움이 될것 같아서 시작하였다.
이 부분은 조금 시간이 걸릴 것 같으니... 새 부품이 올 때까지 계속 업데이트 형태로 작성을 완료하도록 하겠다.
--- contents ---
01. [ESP32] VS Code 개발환경 구성 : https://makeutil.tistory.com/303
02. [ESP32] 첫 프로젝트 생성하기 : https://makeutil.tistory.com/304
03. [ESP32] 멀티 테스크 예제 (2 Task) : https://makeutil.tistory.com/305
04. [ESP32] Task간 데이터 공유 (Queue, Mutex) : https://makeutil.tistory.com/306
05. [ESP32] 개발보드 별 형상 및 I/O (ESP32/ESP32-S3) : https://makeutil.tistory.com/307
06. [ESP32] UART 통신 예제 #1 : https://makeutil.tistory.com/309
07. [ESP32] UART 통신 예제 #2 : https://makeutil.tistory.com/313
08. [ESP32] GPIO LED 켜기 : https://makeutil.tistory.com/311
09. [ESP32] Timer와 PWM을 이용한 LED 점멸 : https://makeutil.tistory.com/312
10. [ESP32] WiFi SoftAP와 WiFi Station (기본예제) : https://makeutil.tistory.com/314
11. [ESP32] 무선 시리얼 통신장치 만들기 1 (SoftAP) : https://makeutil.tistory.com/315
12. [ESP32] 무선 시리얼 통신장치 만들기 2 (SoftAP STA) : https://makeutil.tistory.com/316
13. [ESP32] I2C Temperature Sensor (DHT21) : https://makeutil.tistory.com/317
14. [ESP32] ESP32-S3 구매 할 때 고려할 점 : https://makeutil.tistory.com/318
15. [ESP32] Web Server : 현재글
A1. [ESP32] 오류 - Fatal Error : No such file or directory : https://makeutil.tistory.com/308
------------------
1. 기본 프로젝트 생성
프로젝트는 두 가지 방법을 이용해서 만들수 있다. https와 http인데, 최근에 단순한 http를 쓰게되면 브라우저에서 경고가 가 발생한다. 따라서 보안이 강화된 https를 쓰는것이 좋긴한데, 테스트 할때는 불편할 수도 있을것 같다. 그래서 우선 http로 만들어서 테스트 하도록 하겠다.
1.1. SoftAP 적용
우선 만들기 위해서는 SoftAP로 AP접근이 가능한 형태로 만들자. STA로 구성해도 크게 무리는 없지만, 어디엔가 접속을 해주기 귀찮으니, SoftAP로 동작하게하고 내부에 웹서버를 두도록 하자.
1.2. 웹서버
웹서버 부분은 다음과 같이 만들수 있다. 물로 ESP-IDF의 소스를 이용해서 단순화 하는것이 더 공부가 된다. 우선 웹서버에서 데이터를 주고 받는 방식읜 GET방식과 POST 방식이 있다. 단순하게 처리하려면 데이터 전달 빙식인 GET방식을 이용하여 데이터를 서버에 잔달한다. 예를들면 다음과 같은 방식이다.
http://example.co.kr/code?keyword=mykey
위와 같은 방식으로 데이터를 전송한다. POST 방식은 보통 html의 body에 구성하여 전달한다. 따라서 주소창을 이용하지 않기에 우선 보안성에서는 조금 유익하고, 데이터 길이 제한이 없거나 낮다.
#include <stdio.h>
#include <string.h>
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_http_server.h"
#define ESP_WIFI_AP_SSID "ESP32-AP"
#define ESP_WIFI_AP_PASS "maketools"
#define ESP_MAX_STA_CONN 4
static const char *TAG = "esp32_ap_web";
// HTTP GET 핸들러
static esp_err_t root_get_handler(httpd_req_t *req) {
const char *resp_str = "<!DOCTYPE html><html><body><h2>Welcome to ESP32 AP Web Server!</h2></body></html>";
httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
// URI 등록
static httpd_uri_t root = {
.uri = "/",
.method = HTTP_GET,
.handler = root_get_handler,
.user_ctx = NULL
};
// 웹서버 시작
static void start_webserver(void) {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_handle_t server = NULL;
if (httpd_start(&server, &config) == ESP_OK) {
httpd_register_uri_handler(server, &root);
ESP_LOGI(TAG, "HTTP Server started");
}
}
// Wi-Fi AP 모드 초기화
static void wifi_init_softap(void) {
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
wifi_config_t wifi_config = {
.ap = {
.ssid = ESP_WIFI_AP_SSID,
.ssid_len = strlen(ESP_WIFI_AP_SSID),
.password = ESP_WIFI_AP_PASS,
.max_connection = ESP_MAX_STA_CONN,
.authmode = WIFI_AUTH_WPA_WPA2_PSK
},
};
if (strlen(ESP_WIFI_AP_PASS) == 0) {
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
}
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "Wi-Fi AP started. SSID:%s, Password:%s",
ESP_WIFI_AP_SSID, ESP_WIFI_AP_PASS);
}
void app_main(void) {
// NVS 초기화
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ESP_ERROR_CHECK(nvs_flash_init());
}
wifi_init_softap();
start_webserver();
}
기본 샘플은 위와 같다. 결과를 확인해 보자.
1) 랩톱의 유선 이더넷을 공유기(라우터)에 연결
2) WiFi 무선을 검색하여 ESP32-AP에 연결 (비번은 코드와 같음)
3) 웹브라우저를 실행하여 웹서버가 설치된 ESP32의 주소를 입력
4) 만약, 아래와 같이 실행되지 않으면 다음을 확인
- 전원부족 (WiFi가 연결후 끊어지는 현상이 잦은 경우)
- WiFi 연결은 잘 되었지만 네트워크가 연결되지 않는다면?
명령 > cmd > ping 192.168.4.1 했을때 Reply가 없으면, 아래의 게시글 참조
와이파이, 이더넷 둘다 사용하기 : https://makeutil.tistory.com/197
2. HTML 코드 적용
2.1. HTML 코드를 입력하여 LED 상태를 입력 받기
대충 LED를 점등 하거나 소등하기 위한 단순한 HTML을 만들어본다.
const char *resp_str = "<!DOCTYPE html>\n"
"<html>\n"
"<head>\n"
" <title>Test HTML</title>\n"
" <style>\n"
" table, th, td {\n"
" border: 1px solid black;\n"
" border-collapse: collapse;\n"
" }\n"
" th, td {\n"
" padding: 5px;\n"
" }\n"
" </style>\n"
"</head>\n"
"<body>\n"
" <h1>Test HTML</h1>\n"
" <table>\n"
" <tr>\n"
" <td>LAMP1</td>\n"
" <td><button>On</button></td>\n"
" <td><button>Off</button></td>\n"
" </tr>\n"
" </table>\n"
"</body>\n"
"</html>";
결과를 보아하니 우선 간단한 CSS는 되는것 같다. 근데.. 폰에서는 연결이 잘되는것 같은데 PC에서는 왜 자꾸 끊어지는지 모르겟다. 뭔 차이가 있는건지...
2.2. Java Script 추가
다음은 간단한 자바 스크립트 추가이다. 버튼을 눌렀을 때 확인 및 취소를 할 수 있도록 하면 되겠다.
const char *resp_str = "<!DOCTYPE html>\n"
"<html>\n"
"<head>\n"
" <title>Test HTML</title>\n"
" <style>\n"
" table, th, td {\n"
" border: 1px solid black;\n"
" border-collapse: collapse;\n"
" }\n"
" th, td {\n"
" padding: 5px;\n"
" }\n"
" </style>\n"
" <script>\n"
" function confirmAction(action) {\n"
" const message = action === 'on' ? 'Turn it on?' : 'Turn it off?';\n"
" if (confirm(message)) {\n"
" alert('\"' + action.toUpperCase() + ' executed!\"');\n"
" }\n"
" }\n"
" </script>\n"
"</head>\n"
"<body>\n"
" <h1>Test HTML</h1>\n"
" <table>\n"
" <tr>\n"
" <td>LAMP1</td>\n"
" <td><button onclick=\"confirmAction('on')\">On</button></td>\n"
" <td><button onclick=\"confirmAction('off')\">Off</button></td>\n"
" </tr>\n"
" </table>\n"
"</body>\n"
"</html>";
2.3. 다른 페이지 호출하기
이부분은 일반적으로 우리가 리눅스 베이스의 웹서버나 CGI를 이용할 때와는 다소 다를 수 밖에 없다. 왜냐면, 별도의 파일을 사용하도록 시스템을 구축한 상태가 아니기 때문에 하나의 펌웨어에 여러 html을 담는게 가능한지가 궁금했다. 그래서 만약 하나의 펌웨어에 여러 페이지를 어떻게 담는지 확인해보자.
이번에는 수정되는 내용이 많으니 전체코드를 보자.
#include <stdio.h>
#include <string.h>
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_http_server.h"
#include "esp_system.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#define ESP_WIFI_AP_SSID "ESP32-AP"
#define ESP_WIFI_AP_PASS "maketools"
#define ESP_MAX_STA_CONN 4
static const char *TAG = "esp32_ap_web";
// GPIO 초기화
static void gpio_init() {
gpio_pad_select_gpio(LAMP_GPIO);
gpio_set_direction(LAMP_GPIO, GPIO_MODE_OUTPUT);
gpio_set_level(LAMP_GPIO, 0); // 초기 상태는 OFF
}
// HTTP GET 핸들러
static esp_err_t root_get_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "HTTP GET / 요청 받음");
const char *resp_str = "<!DOCTYPE html>\n"
"<html>\n"
"<head>\n"
" <title>Test HTML</title>\n"
" <style>\n"
" table, th, td {\n"
" border: 1px solid black;\n"
" border-collapse: collapse;\n"
" }\n"
" th, td {\n"
" padding: 5px;\n"
" }\n"
" </style>\n"
" <script>\n"
" function confirmAction(action) {\n"
" const message = action === 'on' ? 'Turn it on?' : 'Turn it off?';\n"
" if (confirm(message)) {\n"
" window.location.href = '/' + action;\n"
" }\n"
" }\n"
" </script>\n"
"</head>\n"
"<body>\n"
" <h1>Test HTML</h1>\n"
" <table>\n"
" <tr>\n"
" <td>LAMP1</td>\n"
" <td><button onclick=\"confirmAction('on')\">On</button></td>\n"
" <td><button onclick=\"confirmAction('off')\">Off</button></td>\n"
" </tr>\n"
" </table>\n"
"</body>\n"
"</html>";
esp_err_t ret = httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN);
ESP_LOGI(TAG, "HTTP 응답 상태: %s", esp_err_to_name(ret));
return ret;
}
// 각 헨들러가 동작되면, 내부의 response 변수가 동작된다.
// LAMP On 핸들러
static esp_err_t lamp_on_handler(httpd_req_t *req)
{
// HTML 구성
const char *resp = "<html><body><h1>LAMP On!</h1><a href=\"/\">Go Back</a></body></html>";
// 구성된 HTML을 부라우저에게 전달.
return httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
}
// LAMP Off 핸들러
static esp_err_t lamp_off_handler(httpd_req_t *req)
{
const char *resp = "<html><body><h1>LAMP Off!</h1><a href=\"/\">Go Back</a></body></html>";
return httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
}
// 각각의 페이지를 uri_t 변수로 등록한다.
static httpd_uri_t root = { // 메인
.uri = "/",
.method = HTTP_GET,
.handler = root_get_handler,
.user_ctx = NULL
};
static httpd_uri_t lamp_on = { // On 버튼 눌렀을 때
.uri = "/on",
.method = HTTP_GET,
.handler = lamp_on_handler,
.user_ctx = NULL
};
static httpd_uri_t lamp_off = { // Off 버튼 눌렀을 때
.uri = "/off",
.method = HTTP_GET,
.handler = lamp_off_handler,
.user_ctx = NULL
};
// 웹서버 시작 함수
static void start_webserver(void) {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_handle_t server = NULL;
ESP_LOGI(TAG, "HTTP 서버 포트 %d 에서 시작 중...", config.server_port);
if (httpd_start(&server, &config) == ESP_OK) {
ESP_LOGI(TAG, "HTTP 서버 시작됨");
httpd_register_uri_handler(server, &root);
httpd_register_uri_handler(server, &lamp_on);
httpd_register_uri_handler(server, &lamp_off);
} else {
ESP_LOGE(TAG, "HTTP 서버 시작 실패");
}
}
// Wi-Fi 이벤트 핸들러
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) {
if (event_base == WIFI_EVENT) {
switch (event_id) {
case WIFI_EVENT_AP_STACONNECTED:
ESP_LOGI(TAG, "클라이언트 연결됨");
break;
case WIFI_EVENT_AP_STADISCONNECTED:
ESP_LOGI(TAG, "클라이언트 연결 해제됨");
break;
}
}
}
// Wi-Fi AP 초기화
static void wifi_init_softap(void) {
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
wifi_config_t wifi_config = {
.ap = {
.ssid = ESP_WIFI_AP_SSID,
.ssid_len = strlen(ESP_WIFI_AP_SSID),
.password = ESP_WIFI_AP_PASS,
.channel = 11,
.max_connection = ESP_MAX_STA_CONN,
.authmode = WIFI_AUTH_WPA_WPA2_PSK,
.ssid_hidden = 0,
},
};
if (strlen(ESP_WIFI_AP_PASS) == 0) {
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
}
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL,
NULL));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
// 절전 모드 끄기
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
// SSID 브로드캐스트 안정화를 위한 지연
vTaskDelay(pdMS_TO_TICKS(1000)); // 1초 대기
ESP_LOGI(TAG, "Wi-Fi AP 시작됨. SSID:%s, PASSWORD:%s", ESP_WIFI_AP_SSID, ESP_WIFI_AP_PASS);
}
// 앱 메인 진입점
void app_main(void) {
// NVS 초기화
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ESP_ERROR_CHECK(nvs_flash_init());
}
ESP_LOGI(TAG, "앱 시작됨");
// Wi-Fi 로그 레벨 디버그로 설정
esp_log_level_set("wifi", ESP_LOG_DEBUG);
// 메모리 상태 출력
heap_caps_print_heap_info(MALLOC_CAP_DEFAULT);
wifi_init_softap();
start_webserver();
}
실행 결과는 아래와 같다. 먼저 페이지가 열리고, Turn On을 하면 자바 스크립트 창이 표시된다. 다음, 확인을 누르면 다른페이지로 전환된다. 사실 이정도면 단순한 제어는 충분히 할 수 있을 것으로 본다.
다음번에는 체크박스, 텍스트 에디터, 콤보박스 정도만 해보면 웬만한 기능을 만들어 쓰는데 지장이 없을 것으로 생각된다. 아.. 아이콘 정도는 해야되려나? 추가되는 부분은 이제 제어하려는 모듈을 붙이면서 해보도록 하자. 우선 기본적인 것들은 되는것 같으니까 말이다.
최근댓글