앞서 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되는 문제가 있다. 이게 전원쪽 문제인지 아니면 중국산의 문제인지는 좀더 확인이 필요할 것 같다. 시간되는데로 정식판에서 테스트를 한번 해봐야겠다. 

    반응형
    • 네이버 블러그 공유하기
    • 네이버 밴드에 공유하기
    • 페이스북 공유하기
    • 카카오스토리 공유하기