앞서 WebServer 데모를 기반으로 하여, HTML과 Java Script를 테스트 해보았다. 그러면 이제 하드웨어를 제어 부분을 웹에다가 붙혀 보도록 하자. 우선 간단한 GPIO를 제어해보도록하자. 그리고 그 다음에 모터나 EEPROM등을 이용하여 비번등을 저장하거나 또는 하드웨어 상태를 표시하고, 상태를 지속적으로 저장할 수 있는 예제를 작성해 보도록 하자.
--- 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 - HTML, Javascript : https://makeutil.tistory.com/320
16. [ESP32] Web Server - GPIO Control (LED 제어) : 현재글
A1. [ESP32] 오류 - Fatal Error : No such file or directory : https://makeutil.tistory.com/308
------------------
이 예제를 수행하기 위해서 우선 해야될 내용은 GPIO를 제어하는 내용이며 이는 이미 진행했으니, 해당 내용을 확인하도록 하고, 기존에 작성했던 웹서버 소스에 해당 내용을 추가하도록 하면 되겠다.
- GPIO 예제 : https://makeutil.tistory.com/318
- 웹서버 예제 : https://makeutil.tistory.com/320
1. 구동방식 설명
예상하건데, 목표는 아래와 같이 설정하도록 하자.
- 웹서버에서 LAMP에 대한 On, Off를 할 수있도록 페이지를 만든다. 기존 페이지에서 상태를 표시할 수 있도록 변경.
- 해당 버튼을 누르면 실행할 것인지 자바 스크립트로 물어본다.
- OK를 확인하면 다른페이지에서 상태를 표시한다. 그리고 5초가 지나면 자동으로 이전 페이지로 돌아온다.
- 이전 페이지로 돌아오면 현재 LED 상태를 표시한다.
2. 소스코드
다음은 소스코드를 확인해보도록 하자. 소스 이전의 웹서버와 같다. 변경된 것만 따로 적기 힘드니 전체 코드에서 내용을 확인 하면된다. 귀...차..
#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-TEST"
#define ESP_WIFI_AP_PASS "maketools"
#define ESP_MAX_STA_CONN 4
#define LAMP_GPIO 10 // LAMP GPIO 핀 번호 (10번으로 설정)
static const char *TAG = "esp32_ap_web";
// GPIO 상태가 제대로 읽히지 않아서 전역으로 상태를 처리하였다.
// 처음엔 캐쉬때문인줄 알았는데...
static int lamp_state = 0;
// GPIO 설정을 등록하고 초기화한다.
static void gpio_init()
{
esp_rom_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 / 요청 받음");
httpd_resp_set_type(req, "text/html");
httpd_resp_set_hdr(req, "Cache-Control", "no-store"); // 캐쉬 안씀.
const char *lamp_status_str = lamp_state ? "ON" : "OFF";
char resp_str[1024];
// 기존 웹서버 샘플에서 제목 테이블과 상태 셀을 하나추가하였다.
snprintf(resp_str, sizeof(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"
" <th>Device</th><th>State</th><th colspan=\"2\">Control</th>\n"
" </tr>\n"
" <tr>\n"
" <td>LAMP1</td>\n"
" <td>%s</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>", lamp_status_str);
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;
}
// LAMP On 핸들러
static esp_err_t lamp_on_handler(httpd_req_t *req) {
gpio_set_level(LAMP_GPIO, 1); // GPIO 20을 HIGH로 설정하여 LAMP 켜기
httpd_resp_set_status(req, "303 See Other");
httpd_resp_set_hdr(req, "Location", "/");
lamp_state = 1;
return httpd_resp_send(req, NULL, 0);
}
// LAMP Off 핸들러
static esp_err_t lamp_off_handler(httpd_req_t *req) {
gpio_set_level(LAMP_GPIO, 0); // GPIO 20을 LOW로 설정하여 LAMP 끄기
httpd_resp_set_status(req, "303 See Other");
httpd_resp_set_hdr(req, "Location", "/");
lamp_state = 0;
return httpd_resp_send(req, NULL, 0);
}
// URI 등록
static httpd_uri_t root = {
.uri = "/",
.method = HTTP_GET,
.handler = root_get_handler,
.user_ctx = NULL
};
static httpd_uri_t lamp_on = {
.uri = "/on",
.method = HTTP_GET,
.handler = lamp_on_handler,
.user_ctx = NULL
};
static httpd_uri_t lamp_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);
// 웹서버 시작되면 페이지(URI를 동록)
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, "앱 시작됨");
// GPIO 초기화
gpio_init();
// Wi-Fi 로그 레벨 디버그로 설정
esp_log_level_set("wifi", ESP_LOG_DEBUG);
// 메모리 상태 출력
heap_caps_print_heap_info(MALLOC_CAP_DEFAULT);
wifi_init_softap();
start_webserver();
}
3. 실행결과
실행경과는 아래와 같다. OFF 상태에서 On 버튼을 누르면 팝업이 뜨고, 확인을 눌러주면 다시 메인 화면으로 이동한다. 이때 상태는 Off에서 On으로 변경되었음을 확인할 수 있다.
모니터를 촬영하려니 케이블 길이가 작아서.. 이뿌게 안찍혔다. 뭐 확인정도는 되는것 같으니..
조금 확인해봐야되는 부분은.. GPIO의 상태를 읽도록 해놨지만, High로 설정되었음에도 0이 read되는 문제가 있다. 이게 전원쪽 문제인지 아니면 중국산의 문제인지는 좀더 확인이 필요할 것 같다. 시간되는데로 정식판에서 테스트를 한번 해봐야겠다.
최근댓글