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 말이다.
최근댓글