UART가 끝났으니, 이제 WiFi를 써보고자 한다. ESP-IDF에 SoftAP예제가 있는데, 이 예제는 ESP32가 Access Point(이하, AP)로 동작되도록한다. 필자가 만들고자 하는 최종 기능은 다음과 같다. 

     

    --- 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 (기본예제) : 현재글 

     

    A1. [ESP32] 오류 - Fatal Error : No such file or directory : https://makeutil.tistory.com/308

    ------------------

     

     

      디버그 포트를 자주 사용하는 필자는 케이블 때문에 귀찮은 경우가 많다. 뿐만 아니라 한번에 여러 장비를 연결해야되는 경우가 있다보니 다음과 같은 경우에 사용하기 위함이다. 

     

    첫번째로 1:1로 연결하여 디버깅 터미널을 이용할 경우,

     

     

    두번째로 2:2 연결을 하여 디버깅 터미널을 이용할 경우.

     

      마지막으로 데이터 통신을 위해서 1:n을 이용할 경우이다. 이 경우는 ESP32에서 시리얼로 특정 데이터를 시리얼로 보내야 되는경우 데이터를 장비의 장치가 호스트 측 ESP32에 연결되고, 호스트는 특정 프로토콜로 데이터를 보내면 ESP32가 목적지를 선택하여 각 장치로 데이터를 보낼 수 있다. 물론, 라즈베리파이던 i.MX던 WiFi로 직접 연결하여 네트워크 프로그래밍을 해도 되지만, 만약 기존에 시리얼 포트를 이용해서 데이터를 받고 있었다면, 굳이 소스를 변경할 필요가 없어지니까 나름 슬데가 있을 수 있다고 생각한다. 아마도...

     

     

      어쨋든 그럴려면 최소한 ESP32가 AP로 동작되어야 되는데, 여기서 ESP32에서 해당 기능을 사용하기 위해서 SoftAP 기능을 사용해야 된다는 결론이다. 

     

    자 그러면 ESP-IDF의 샘플을 실행해서 결과를 확인해 보도록 하자.

     

    1. SoftAP 

    1.1. SoftAP 소스코드 

      편의상 AP가 되는 ESP를 서버라고 하겠다. 

     

     

    #include <string.h>
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "esp_mac.h"
    #include "esp_wifi.h"
    #include "esp_event.h"
    #include "esp_log.h"
    #include "nvs_flash.h"
    
    #include "lwip/err.h"
    #include "lwip/sys.h"
    
     
    #define EXAMPLE_ESP_WIFI_SSID      "ESP32-AP"     // ESSID 지정
    #define EXAMPLE_ESP_WIFI_PASS      "qwerty123!"   // 접속 패스워드 지정
    #define EXAMPLE_ESP_WIFI_CHANNEL   1              // 사용할 채널 지정
    #define EXAMPLE_MAX_STA_CONN       4              // 최대 연결 가능한 스테이션(클라이언트)
    
    static const char *TAG = "wifi softAP";
    
    // WiFi 이벤트 핸들러
    static void wifi_event_handler(void* arg, esp_event_base_t event_base,
                                        int32_t event_id, void* event_data)
    {
        if (event_id == WIFI_EVENT_AP_STACONNECTED) {  // 연결이 일어나면
            wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
            ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
                     MAC2STR(event->mac), event->aid);
        } else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {   // 연결이 끊어지면
            wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
            ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d, reason=%d",
                     MAC2STR(event->mac), event->aid, event->reason);
        }
    }
    
    void wifi_init_softap(void)  // 초기화
    {
        ESP_ERROR_CHECK(esp_netif_init()); // ESP32의 네트워크 인터페이스 초기화
        ESP_ERROR_CHECK(esp_event_loop_create_default());  // 재 연결등을 위한 이벤트루프 설정
        esp_netif_create_default_wifi_ap(); // WiFi AP용 네트워크 인터페이스 생성
    
        wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); // WiFi 초기설정 적용
        ESP_ERROR_CHECK(esp_wifi_init(&cfg)); // 기본 설정값으로 초기화 진행
    
        // 이벤트 핸들러 등록시 모든 WIFI 이벤트에 대해서 wifi_event_handler에서 처리하도록 설정
        ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                            ESP_EVENT_ANY_ID,
                                                            &wifi_event_handler,
                                                            NULL,
                                                            NULL));
    
        wifi_config_t wifi_config = {  // AP 설정정보를 지정
            .ap = {
                .ssid = EXAMPLE_ESP_WIFI_SSID,
                .ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID),
                .channel = EXAMPLE_ESP_WIFI_CHANNEL,
                .password = EXAMPLE_ESP_WIFI_PASS,
                .max_connection = EXAMPLE_MAX_STA_CONN,
    #ifdef CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT
                .authmode = WIFI_AUTH_WPA3_PSK,
                .sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
    #else /* CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT */
                .authmode = WIFI_AUTH_WPA2_PSK,
    #endif
                .pmf_cfg = {  // Wifi 관리 프레임(Protected Management Frames) 사용설정
                        .required = true,
                },
            },
        };
        if (strlen(EXAMPLE_ESP_WIFI_PASS) == 0) { // 비밀번호가 없을 경우 개방모드 적용
            wifi_config.ap.authmode = WIFI_AUTH_OPEN;
        }
    
        ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));  // Wifi Mode를 AP로 설정
        ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));  // 앞서 설정한 정보 적용
        ESP_ERROR_CHECK(esp_wifi_start());  // WiFi 시작
    
        // 설정된 정보 출력
        ESP_LOGI(TAG, "wifi_init_softap finished. SSID:%s password:%s channel:%d",
                 EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS, EXAMPLE_ESP_WIFI_CHANNEL);
    }
    
    void app_main(void)
    {
        // Non-Volatile Storage 초기화
        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()); 
          ret = nvs_flash_init();
        }
        ESP_ERROR_CHECK(ret);
    
        ESP_LOGI(TAG, "ESP_WIFI_MODE_AP");
        wifi_init_softap();  // AP 모드 동작함수 호출
    }

     

     

     

    1.2. 실행결과

      실행되는 결과는 하나씩 확인해 보도록 하자. 

     

      VSCode 터미널에 아래와 같이 메시지가 표시된다. 보면 초기화 완료되었고 SSID와 패스워드 그리고 채널을 출력하고 있다. 그리고 DHCP를 통해서 IP 를 할당 받는데, SoftAP가 동작되는 ESP32는 192.168.4.1을 할당 받았다.

     

    I (588) wifi softAP: wifi_init_softap finished. SSID:ESP32-AP password:qwerty123! channel:1
    I (588) esp_netif_lwip: DHCP server started on interface WIFI_AP_DEF with IP: 192.168.4.1
    I (608) main_task: Returned from app_main()

     

     

      어찌되었건, 그러면 연결될 클라이언트 아니 Station은 DHCP에 의해서 192.168.1.n (n은 중복되지 않는 정수로 1~254)의 ip를 할당받게 된다. 

     

     

    2. Station

    2.1. Station 소스코드

      다음은 SoftAP에 연결될 클라이언트.. 아니 Station에 대한 예제이다. 이 예제도 프로젝트 생성시에 탬플릿에서 softap_sta를 선택하면된다. 

     

      소스코드는 아래와 같다. 설명은 간단히 주석을 참고 하도록 하자.

    #include <string.h>
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "freertos/event_groups.h"
    #include "esp_system.h"
    #include "esp_wifi.h"
    #include "esp_event.h"
    #include "esp_log.h"
    #include "nvs_flash.h"
    
    #include "lwip/err.h"
    #include "lwip/sys.h"
    
    
    #define EXAMPLE_ESP_WIFI_SSID      "ESP32-AP"    // 연결할 ESSID (SoftAP)
    #define EXAMPLE_ESP_WIFI_PASS      "qwerty123!"  // 패스워드
    #define EXAMPLE_ESP_MAXIMUM_RETRY  10            // 연결 재시도 회수
    
    // 보호 모드에 대한 설정 
    #if CONFIG_ESP_WPA3_SAE_PWE_HUNT_AND_PECK
    #define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_HUNT_AND_PECK
    #define EXAMPLE_H2E_IDENTIFIER ""
    #elif CONFIG_ESP_WPA3_SAE_PWE_HASH_TO_ELEMENT
    #define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_HASH_TO_ELEMENT
    #define EXAMPLE_H2E_IDENTIFIER CONFIG_ESP_WIFI_PW_ID
    #elif CONFIG_ESP_WPA3_SAE_PWE_BOTH
    #define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_BOTH
    #define EXAMPLE_H2E_IDENTIFIER CONFIG_ESP_WIFI_PW_ID
    #endif
    #if CONFIG_ESP_WIFI_AUTH_OPEN
    #define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_OPEN
    #elif CONFIG_ESP_WIFI_AUTH_WEP
    #define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WEP
    #elif CONFIG_ESP_WIFI_AUTH_WPA_PSK
    #define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_PSK
    #elif CONFIG_ESP_WIFI_AUTH_WPA2_PSK
    #define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_PSK
    #elif CONFIG_ESP_WIFI_AUTH_WPA_WPA2_PSK
    #define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_WPA2_PSK
    #elif CONFIG_ESP_WIFI_AUTH_WPA3_PSK
    #define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA3_PSK
    #elif CONFIG_ESP_WIFI_AUTH_WPA2_WPA3_PSK
    #define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_WPA3_PSK
    #elif CONFIG_ESP_WIFI_AUTH_WAPI_PSK
    #define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WAPI_PSK
    #endif
    
    /* FreeRTOS event group to signal when we are connected*/
    static EventGroupHandle_t s_wifi_event_group;  // 여러 상태를 담을수 있는 핸들
    
    /* The event group allows multiple bits for each event, but we only care about two events:
     * - we are connected to the AP with an IP
     * - we failed to connect after the maximum amount of retries */
    #define WIFI_CONNECTED_BIT BIT0    // 0번 비트를 1로 설정, 00000001
    #define WIFI_FAIL_BIT      BIT1    // 1번 비트를 1로 설정, 00000010
    
    static const char *TAG = "wifi station";
    
    static int s_retry_num = 0;  // 반복회수 누적
    
    // 이벤트 핸들러 등록
    static void event_handler(void* arg, esp_event_base_t event_base,
                                    int32_t event_id, void* event_data)
    {
        // 이벤트 핸들이 STATION_START인경우
        if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
            esp_wifi_connect();  
        // 연결이 끊어진 경우
        } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
            // 재시도 시작
            if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
                esp_wifi_connect();
                s_retry_num++;
                ESP_LOGI(TAG, "retry to connect to the AP");
            } else {
                // 연결오류 처리
                xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
            }
            ESP_LOGI(TAG,"connect to the AP fail");
            // IP할당 받아오면 
        } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
            ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
            ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
            s_retry_num = 0;
            xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
        }
    }
    
    void wifi_init_sta(void)
    {
        s_wifi_event_group = xEventGroupCreate();  // 이벤트 그룹 생성
    
        ESP_ERROR_CHECK(esp_netif_init());
    
        ESP_ERROR_CHECK(esp_event_loop_create_default());
        esp_netif_create_default_wifi_sta();
    
        wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
        ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    
    
        esp_event_handler_instance_t instance_any_id;
        esp_event_handler_instance_t instance_got_ip;
    
        // WIFI 이벤트 관련 모든 이벤트를 처리하도록 핸들러 등록
        ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                            ESP_EVENT_ANY_ID,
                                                            &event_handler,
                                                            NULL,
                                                            &instance_any_id));
    
        // 이벤트 핸들러 등록 (IP할당시 동작)
        ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                            IP_EVENT_STA_GOT_IP,
                                                            &event_handler,
                                                            NULL,
                                                            &instance_got_ip));
    
        wifi_config_t wifi_config = {
            .sta = {
                .ssid = EXAMPLE_ESP_WIFI_SSID,
                .password = EXAMPLE_ESP_WIFI_PASS,
                /* Authmode threshold resets to WPA2 as default if password matches WPA2 standards (password len => 8).
                 * If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value
                 * to WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK and set the password with length and format matching to
                 * WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK standards.
                 */
                .threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD,  // 최쇠요구 인증모드(기본값 : WPA2_PSK)
                //.sae_pwe_h2e = ESP_WIFI_SAE_MODE,              // WPA3 SAE/H2E 사용 설정
                //.sae_h2e_identifier = EXAMPLE_H2E_IDENTIFIER,  // PSK2 쓸꺼면 상관없음.
            },
        };
    
        ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );  // STA 모드 동작
        ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );  //  WIFI 설정 적용
        ESP_ERROR_CHECK(esp_wifi_start() );  // WIFI 시작
    
        ESP_LOGI(TAG, "wifi_init_sta finished.");
    
        /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
         * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
        EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,  // WIFI 연결결과를 위해 대기
                WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
                pdFALSE,
                pdFALSE,
                portMAX_DELAY);
    
        /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
         * happened. */
        if (bits & WIFI_CONNECTED_BIT) {    // 연결되면 연결된 ESSID와 패스워드를 출력
            ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
                     EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
        } else if (bits & WIFI_FAIL_BIT) {  // 연결에 시래하면 실패했다고 메시지를 출력
            ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
                     EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
        } else {
            ESP_LOGE(TAG, "UNEXPECTED EVENT");
        }
    }
    
    void app_main(void)
    {
        //Initialize 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());
          ret = nvs_flash_init();
        }
        ESP_ERROR_CHECK(ret);
    
        ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
        wifi_init_sta();  // WiFi Station 초기화 함수 호출
    }

     

     

    2.2. 실행결과

      아무것도 수정하지말고 바로 빌드하여 ESP32에 설치해보도록 하자. 정상적으로 빌드되고 실행되면 VSCode의 터미널에서 아래와 같은 메시지를 확인할 수 있다. 

     

      연결이 정상적으로 이루어지면, 연결된  station의 ip가 출력된다. 여기에서는 192.168.4.2이고, 게이트웨이는 192.168.4.1 (SoftAP의 IP)임을 확인할 수 있다. 그리고 ㅇ녀결된 AP의 ESSID와 비번을 출력해준다.

     

     I (2710) esp_netif_handlers: sta ip: 192.168.4.2, mask: 255.255.255.0, gw: 192.168.4.1
     I (2710) wifi station: got ip:192.168.4.2
     I (2710) wifi station: connected to ap SSID:ESP32-AP password:qwerty123!
     I (2710) main_task: Returned from app_main()

     

     

      Station이 연결될 때 SoftAP의 터미널을 확인해보자. 연결되는 시점에서 DHCP가 ip를 할당하고, 세부 정보를 출력해주는 것을 알수 있다. 

     

      I (1919498) wifi softAP: station cc:ba:97:14:b8:b0 leave, AID=1, reason=2
      I (1919508) wifi:new:<1,1>, old:<1,1>, ap:<1,1>, sta:<255,255>, prof:1, snd_ch_cfg:0x0
      I (1919508) wifi:station: cc:ba:97:14:b8:b0 join, AID=1, bgn, 40U
      I (1919578) wifi softAP: station cc:ba:97:14:b8:b0 join, AID=1
      I (1919648) esp_netif_lwip: DHCP server assigned IP to a client, IP is: 192.168.4.2

     

     

      테스트를 해보면, STA 를 껏다가 켜면, 그때마다 연결이 일어나는 것을 볼 수 있다. 조금 테스트를 진행해봤지만, 나름 잘 유지해주는 것을 알 수 있었다.

     

      자.. 그러면 이 SoftAP와 SoftAP STA 소스에 uart 소스를 추가하여 필자가 원하는 기능을 만들면 된다. 대부분 소스가 잘 되어있어서 잘 가져다가 쓰기만 하면 충분히 필요한 곳에 적용해서 사용하기에는 정말 편리하다. 이 ESP32 말이다.

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