서론
이전 글에서는 ESP32 CAN 보드에 Zephyr를 포팅하고 RGB LED 제어를 실습해 보았습니다. 이전까지 사용하던 MCU가 STM32였다면, 이번에는 ESP32로 대상을 변경하여 Zephyr를 포팅해 보았다는 데 의미가 있습니다.
ESP32 MCU에는 Wi-Fi 기능이 기본적으로 내장되어 있습니다. 따라서 IoT 애플리케이션으로 발전시키기 위해서는 ESP32의 Wi-Fi 기능을 Zephyr RTOS 환경에서 어떻게 사용할 수 있는지 살펴볼 필요가 있습니다.
이번 글에서는 그 첫 번째 단계로 Wi-Fi Scan을 실습합니다. Wi-Fi Scan은 주변의 AP(Access Point)를 검색하여 SSID, 채널(Channel), 신호 세기(RSSI), 보안 방식 등의 정보를 확인하는 기능입니다. AP에 실제로 연결하기 전에 주변의 Wi-Fi 네트워크를 확인하는 가장 기본적인 기능이라고 할 수 있습니다.
그러나 이번 실습의 목적은 단순히 주변 AP 목록을 출력하는 데에만 있지는 않습니다. Zephyr에서 Wi-Fi Scan을 실행하려면 net_if, net_mgmt(), Network Management Event Callback 등의 기능을 사용하게 됩니다. Scan을 요청한 뒤 검색 결과와 Scan 완료를 이벤트와 Callback을 통해 비동기적으로 전달받는 구조도 확인할 수 있습니다.
따라서 이번 실습은 ESP32의 Wi-Fi 기능을 처음 사용해 보는 동시에, Zephyr의 Network Management와 이벤트 기반 네트워크 처리 방식을 이해하는 첫 번째 실습이라는 의미도 있습니다.
이 글에서는 Wi-Fi Scan 프로그램을 직접 작성하고 실행해 본 뒤, NET_EVENT_WIFI_SCAN_RESULT와 NET_EVENT_WIFI_SCAN_DONE 이벤트가 어떻게 발생하고 각각의 Callback 함수가 어떻게 호출되는지를 살펴보겠습니다. 또한 이 과정에서 사용되는 주요 API가 Zephyr 공식 문서의 어떤 부분과 연결되는지도 함께 확인해 보겠습니다.
프로젝트 생성 및 구성파일 작성
이번 실습에서는 이전 글에서 Zephyr 포팅을 완료한 ESP32 CAN 보드를 그대로 사용합니다. 따라서 새로운 보드를 생성하는 과정은 반복하지 않고, Wi-Fi Scan을 위한 새로운 애플리케이션 프로젝트를 생성하여 실습을 진행합니다.
프로젝트 생성
이전에 진행했던 skpang_esp32_can_blinky 프로젝트 폴더를 복사하여 붙여넣은 다음, 폴더 이름을 skpang_esp32_can_wifiscan으로 변경합니다.
아래는 새로 생성한 프로젝트 폴더의 구조입니다.

위 구조에서 보면 기존 프로젝트의 build 폴더도 함께 복사되어 있는 것을 볼 수 있습니다. 이 폴더에는 이전 프로젝트의 빌드 결과와 CMake 캐시 등 기존 프로젝트와 관련된 정보가 포함되어 있으므로 삭제합니다.
build 폴더는 이후 west build를 실행하면 새 프로젝트의 설정에 맞게 다시 생성됩니다.
기존 프로젝트를 복사했기 때문에 CMakeLists.txt, prj.conf, src/main.c, CHANGELOG.md 파일도 그대로 포함되어 있습니다. 이번 Wi-Fi Scan 실습에 맞게 먼저 CMakeLists.txt의 프로젝트 이름을 변경하고, prj.conf에 Wi-Fi 동작에 필요한 설정을 추가한 다음 src/main.c에 Wi-Fi Scan 프로그램을 작성합니다.
구성파일 작성
CMakeLists.txt
기존 skpang_esp32_can_blinky 프로젝트를 복사하여 새로운 프로젝트를 만들었으므로 CMakeLists.txt에 정의된 프로젝트 이름도 이번 실습에 맞게 변경합니다.
기존 CMakeLists.txt에서 다음과 같이 설정되어 있던 프로젝트 이름을
project(skpang_esp32_can_blinky)
skpang_esp32_can_wifiscan으로 변경합니다.project(skpang_esp32_can_wifiscan)
수정한 전체 CMakeLists.txt는 다음과 같습니다.

CMakeLists.txt는 CMake가 프로젝트를 빌드할 때 사용하는 설정 파일입니다. Zephyr 애플리케이션에서도 CMake를 사용하여 빌드 과정을 구성합니다.
여기서 find_package(Zephyr REQUIRED ...)는 Zephyr 빌드 시스템을 불러오고, project()는 현재 애플리케이션 프로젝트의 이름을 지정합니다. 마지막의 target_sources()는 src/main.c 파일을 애플리케이션의 소스 파일로 포함시킵니다.
이번 프로젝트에서는 기존 프로젝트의 소스 구조를 그대로 사용하므로 프로젝트 이름을 제외한 나머지 부분은 변경하지 않습니다.
prj.conf
prj.conf 파일에는 이번 Wi-Fi Scan 실습에 필요한 Zephyr 기능을 활성화하는 설정을 추가합니다.
Zephyr에서는 소스 코드에서 Wi-Fi 관련 API를 사용한다고 해서 해당 기능이 자동으로 포함되는 것은 아닙니다. 필요한 기능을 Kconfig 옵션을 통해 활성화해야 하며, 애플리케이션 프로젝트에서는 이러한 설정을 주로 prj.conf 파일에 작성합니다.
이번 실습에서는 Wi-Fi와 Network Management 기능을 사용할 수 있도록 다음과 같이 설정합니다.

각 설정의 의미는 다음과 같습니다.
CONFIG_WIFI=y
Zephyr의 Wi-Fi 기능을 활성화합니다.CONFIG_NETWORKING=y
Zephyr 네트워크 스택을 활성화합니다.CONFIG_NET_MGMT=y
Network Management 기능을 활성화합니다. 이번 실습에서 사용하는net_mgmt()와 관련된 설정입니다.CONFIG_NET_MGMT_EVENT=y
Network Management의 이벤트 처리 기능을 활성화합니다. Wi-Fi Scan 결과와 Scan 완료 이벤트를 Callback으로 전달받기 위해 필요합니다.CONFIG_NET_L2_WIFI_MGMT=y
Wi-Fi Network Management 기능을 활성화합니다.NET_REQUEST_WIFI_SCAN,NET_EVENT_WIFI_SCAN_RESULT와 같은 Wi-Fi 관리 기능과 관련됩니다.CONFIG_WIFI_LOG_LEVEL_INF=y
Wi-Fi 관련 로그를 Information 수준으로 출력하도록 설정합니다. 개발과 디버깅 과정에서 Wi-Fi 동작 상태를 확인하는 데 도움이 됩니다.
여기서 특히 중요한 것은 CONFIG_NET_MGMT와 CONFIG_NET_MGMT_EVENT입니다. 이번 Wi-Fi Scan 프로그램은 단순히 Scan 함수를 호출하고 결과를 즉시 반환받는 방식이 아니라, Scan을 요청한 뒤 검색 결과와 Scan 완료 상태를 이벤트를 통해 전달받는 비동기 구조로 동작합니다.
따라서 prj.conf의 설정은 이후 작성할 main.c의 net_mgmt(), NET_EVENT_WIFI_SCAN_RESULT, NET_EVENT_WIFI_SCAN_DONE과 직접 연결됩니다.
CHANGELOG.md
CHANGELOG.md에는 프로젝트를 개발하면서 변경된 내용을 기록합니다. .md는 Markdown을 의미하며, Markdown 형식으로 작성된 텍스트 파일입니다.
이번 프로젝트에서는 변경 이력뿐만 아니라 프로젝트 정보와 실제로 사용하는 build 명령도 함께 기록해 두었습니다.
특히 Zephyr에서 커스텀 보드를 사용하는 경우 build 명령에는 보드 이름뿐만 아니라 CPU 타깃과 BOARD_ROOT 경로 등이 포함될 수 있습니다. 이러한 명령을 매번 직접 입력하면 옵션이나 경로를 잘못 입력하기 쉽습니다.
이번에 사용하는 ESP32 CAN 보드 역시 procpu를 지정해야 하므로 다음과 같이 build 명령이 비교적 길어집니다.
west build -b skpang_esp32_can/esp32/procpu -p always -- -DBOARD_ROOT="D:/Zephyr/workspace/my_boards"
따라서 한 번 정상적으로 동작한 build 명령을 CHANGELOG.md에 기록해 두고, 이후 빌드할 때 복사하여 사용하는 것이 편리합니다.
이번 Wi-Fi Scan 프로젝트의 CHANGELOG.md는 다음과 같이 작성하였습니다.

Zephyr Wi-Fi Scan의 동작 구조
Wi-Fi Scan 프로그램을 작성하기 전에 Zephyr에서 Wi-Fi Scan이 어떤 구조로 동작하는지 먼저 살펴보겠습니다.
Wi-Fi Scan은 주변에 존재하는 AP(Access Point)를 검색하고, 검색된 AP의 SSID, Channel, RSSI, 보안 방식 등의 정보를 얻는 과정입니다.
ESP32에는 Wi-Fi 기능이 하드웨어적으로 내장되어 있지만, Zephyr 애플리케이션에서 Wi-Fi Scan을 수행하려면 단순히 Wi-Fi Driver의 Scan 함수를 직접 호출하는 방식으로 접근하지 않습니다. Zephyr의 Network Management 기능을 이용하여 Scan을 요청하고, 그 결과는 Event와 Callback을 통해 전달받습니다.
따라서 이번 실습에서 가장 먼저 이해해야 할 것은 Scan을 요청하는 과정과 Scan 결과를 전달받는 과정이 서로 분리되어 있다는 점입니다.
아래 그림은 이러한 Zephyr Wi-Fi Scan의 전체적인 동작 구조를 나타냅니다.

위 그림은 Zephyr에서 Wi-Fi Scan을 수행하는 전체적인 동작 구조를 나타냅니다. Application은 먼저 ① Scan 결과와 Scan 완료를 처리할 Callback 함수를 등록합니다. 이후 net_mgmt()를 이용하여 ② Wi-Fi Scan을 요청합니다. Wi-Fi Driver가 Scan을 수행하면서 AP를 발견하거나 Scan을 완료하면 ③ 해당 Event가 발생하며, Zephyr Network Management는 Event에 미리 등록되어 있는 ④ Callback 함수를 호출합니다.
주변에 여러 개의 AP가 존재하면 NET_EVENT_WIFI_SCAN_RESULT Event 발생과 scan_result_cb() 호출은 검색된 AP마다 반복됩니다. 반면 모든 검색이 끝나면 NET_EVENT_WIFI_SCAN_DONE Event가 발생하고 scan_done_cb()가 호출됩니다.
Callback 등록
Wi-Fi Scan을 시작하기 전에 Application에서는 먼저 Scan 과정에서 발생하는 이벤트를 처리할 Callback 함수를 등록합니다.
이번 프로그램에서는 두 종류의 Callback을 사용합니다.
Scan Result → scan_result_cb
Scan Done → scan_done_cb
scan_result_cb는 Scan 과정에서 발견된 각각의 AP 정보를 처리하기 위한 Callback 함수이고, scan_done_cb는 전체 Scan 작업이 완료되었을 때 호출될 Callback 함수입니다.
Callback의 초기화와 등록에는 다음 함수가 사용됩니다.
net_mgmt_init_event_callback()
net_mgmt_add_event_callback()
net_mgmt_init_event_callback()은 Callback 구조체에 어떤 Callback 함수를 사용할 것인지와 어떤 Event를 처리할 것인지를 설정합니다.
이렇게 초기화한 Callback은 다시 net_mgmt_add_event_callback()을 이용하여 Zephyr Network Management에 등록합니다.
여기서 중요한 것은 Callback을 먼저 등록한 다음 Wi-Fi Scan을 요청한다는 것입니다.
즉, Scan 도중 Event가 발생했을 때 이를 처리할 함수가 미리 준비되어 있어야 합니다.
그림의 Callback 등록 부분에서 scan_result_cb, scan_done_cb에 괄호를 표시하지 않은 것도 이러한 이유입니다. 이 단계에서는 해당 함수를 실행하는 것이 아니라 Callback 함수로 등록하는 것입니다.
반면 그림 아래쪽의 scan_result_cb()와 scan_done_cb()는 Event가 발생한 후 Callback 함수가 실제로 호출되는 것을 의미합니다.
Wi-Fi Scan 요청
Callback 등록이 완료되면 Application에서는 net_mgmt()를 이용하여 Wi-Fi Scan을 요청합니다.
실제 프로그램에서는 다음과 같은 형태가 사용됩니다.
net_mgmt(NET_REQUEST_WIFI_SCAN, iface, NULL, 0);
여기서 NET_REQUEST_WIFI_SCAN은 Zephyr Network Management에 Wi-Fi Scan을 수행해 달라는 요청을 의미합니다.
iface는 Scan을 수행할 Network Interface를 나타냅니다.
Zephyr에서는 Ethernet, Wi-Fi와 같은 네트워크 연결을 struct net_if라는 Network Interface 객체를 통해 관리합니다. 따라서 iface를 단순히 Wi-Fi 하드웨어 장치의 포인터라고 보기보다는 Zephyr Network Stack에서 Wi-Fi 네트워크 인터페이스를 나타내는 객체의 포인터라고 이해하는 것이 좋습니다.
이 부분은 이후 실제 main.c를 살펴보면서 다시 자세히 설명하겠습니다.
Application에서 발생한 Scan 요청은 그림과 같이
Application
↓
Zephyr Network Management
↓
Wi-Fi Driver
의 흐름으로 전달됩니다.
즉, Application이 Wi-Fi Driver의 내부 Scan 동작을 직접 제어하는 것이 아니라 Zephyr Network Management를 통해 Scan을 요청하는 구조입니다.
Wi-Fi Driver의 Scan 수행
Scan 요청을 전달받으면 Wi-Fi Driver는 주변의 AP를 검색하기 시작합니다.
여기서 중요한 점은 net_mgmt() 함수가 주변 AP를 모두 검색한 후 그 결과를 한꺼번에 반환하는 방식이 아니라는 것입니다.
Wi-Fi Scan에는 일정 시간이 필요하며, Scan이 진행되는 동안 검색 결과가 발생합니다. Zephyr에서는 이러한 결과를 Event를 통해 Application에 전달합니다.
따라서 Application은 Scan 요청 후 계속 기다리면서 결과를 직접 가져오는 것이 아니라, Scan 과정에서 발생하는 Event를 Callback을 통해 전달받습니다.
이것이 이번 프로그램의 비동기적인 동작 구조입니다.
AP 발견과 Event 발생
Wi-Fi Driver가 주변을 Scan하는 동안 AP가 발견되면 다음 Event가 발생합니다.
NET_EVENT_WIFI_SCAN_RESULT
AP 발견과 NET_EVENT_WIFI_SCAN_RESULT 사이에 표시한 Event 발생이 바로 이 과정을 나타냅니다.전체적인 흐름은 다음과 같습니다.
AP 발견
↓
NET_EVENT_WIFI_SCAN_RESULT 발생
↓
등록된 Callback 호출
↓
scan_result_cb()
여기서 한 가지 중요한 점이 있습니다.
AP가 발견되었다고 해서 Wi-Fi Driver가 애플리케이션의 scan_result_cb()를 직접 호출하는 것으로 이해해서는 안 됩니다.
AP가 발견되면 Scan Result Event가 발생하고, Zephyr의 Network Management Event 처리 구조에 의해 이 Event에 대해 미리 등록된 Callback 함수가 호출됩니다.
즉,
AP 발견 → scan_result_cb()
AP 발견
↓
Event 발생
↓
NET_EVENT_WIFI_SCAN_RESULT
↓
등록된 Callback 호출
↓
scan_result_cb()
의 구조로 이해하는 것이 중요합니다.
그림에서 큰 화살표로 표시한 Callback 호출은 바로 이 관계를 나타냅니다.
scan_result_cb()의 반복 호출
주변에 AP가 하나만 존재한다면 NET_EVENT_WIFI_SCAN_RESULT도 한 번만 전달될 수 있지만, 일반적으로 주변에는 여러 개의 AP가 존재합니다.
예를 들어 AP 세 개가 검색된다면 개념적으로 다음과 같이 동작합니다.
AP #1 발견
↓
NET_EVENT_WIFI_SCAN_RESULT
↓
scan_result_cb()
AP #2 발견
↓
NET_EVENT_WIFI_SCAN_RESULT
↓
scan_result_cb()
AP #3 발견
↓
NET_EVENT_WIFI_SCAN_RESULT
↓
scan_result_cb()
scan_result_cb()는 여러 번 호출될 수 있습니다.그림 오른쪽에 표시한 AP별 반복은 이러한 동작을 의미합니다.
여기서 주의할 점은 Wi-Fi Scan 자체를 여러 번 요청하는 것이 아니라는 것입니다.
Application에서는 NET_REQUEST_WIFI_SCAN을 이용하여 한 번 Scan을 요청하였지만, 그 Scan 과정에서 여러 AP가 발견되기 때문에 NET_EVENT_WIFI_SCAN_RESULT와 scan_result_cb()의 호출이 AP별로 반복되는 것입니다.
scan_result_cb()에서는 각각의 AP에 대한 SSID, Channel, RSSI, 보안 방식 등의 정보를 확인할 수 있습니다.
Scan 완료와 NET_EVENT_WIFI_SCAN_DONE
Wi-Fi Driver가 주변 AP에 대한 Scan을 모두 마치면 이번에는 Scan 완료를 알리는 Event가 발생합니다.
NET_EVENT_WIFI_SCAN_DONE
Scan 완료
↓
NET_EVENT_WIFI_SCAN_DONE 발생
↓
등록된 Callback 호출
↓
scan_done_cb()
즉, Scan Result에는 scan_result_cb가 등록되어 있고, Scan Done에는 scan_done_cb가 등록되어 있기 때문에 각각의 Event가 발생했을 때 해당 Callback 함수가 호출됩니다.
여기서 또 하나 중요한 점은 Application이 AP의 개수를 세어서 Scan 완료 여부를 판단하는 것이 아니라는 것입니다.
몇 개의 AP가 검색될지는 미리 알 수 없습니다. 실제 Scan을 수행하는 Wi-Fi Driver와 Zephyr Wi-Fi Management 계층에서 Scan 작업의 종료를 처리하고, 그 결과가 NET_EVENT_WIFI_SCAN_DONE이라는 Event로 Application에 전달됩니다.
Application은 이 Event를 받아 전체 Scan 작업이 끝났음을 알 수 있습니다.
scan_result_cb()와 scan_done_cb()의 차이
두 Callback의 차이를 정리하면 다음과 같습니다.
| Callback | Event | 역할 | 호출 |
|---|---|---|---|
scan_result_cb() |
NET_EVENT_WIFI_SCAN_RESULT |
발견된 AP 정보 처리 | AP별로 호출 |
scan_done_cb() |
NET_EVENT_WIFI_SCAN_DONE |
전체 Scan 완료 처리 | Scan 종료 시 호출 |
scan_result_cb()는 하나의 Scan 과정에서도 여러 번 호출될 수 있다는 것이 가장 큰 특징입니다.
반면 scan_done_cb()는 개별 AP의 정보를 처리하는 함수가 아니라 전체 Wi-Fi Scan 과정이 끝났음을 처리하는 함수입니다.
Request와 Notification
이제 처음 그림으로 다시 돌아가 보면 Zephyr Wi-Fi Scan의 전체 구조를 좀 더 쉽게 이해할 수 있습니다.
Application에서
net_mgmt(NET_REQUEST_WIFI_SCAN, iface, NULL, 0);
즉,
“이 Network Interface에서 Wi-Fi Scan을 시작해 달라.”
라고 Zephyr Network Management에 요청하는 것입니다.
그 이후 Scan 과정에서 발생하는
NET_EVENT_WIFI_SCAN_RESULT
NET_EVENT_WIFI_SCAN_DONE
은 Notification입니다.
이를 간단히 정리하면 다음과 같습니다.
Request
Application
↓
net_mgmt(NET_REQUEST_WIFI_SCAN, ...)
↓
Zephyr Network Management
↓
Wi-Fi Driver
Notification
Wi-Fi Driver
↓
AP 발견
↓
NET_EVENT_WIFI_SCAN_RESULT
↓
scan_result_cb()
...
Scan 완료
↓
NET_EVENT_WIFI_SCAN_DONE
↓
scan_done_cb()
따라서 Zephyr의 Wi-Fi Scan은 함수를 호출하고 그 함수의 반환값으로 Scan 결과 전체를 받는 구조가 아닙니다.
Application은 Scan을 요청하고, 실제 Scan은 별도로 진행됩니다. 그리고 그 과정에서 발생하는 검색 결과와 Scan 완료 상태를 Event와 Callback을 통해 비동기적으로 전달받습니다.
이러한 Request → Event → Callback 구조를 이해하는 것이 이번 Wi-Fi Scan 프로그램의 핵심입니다.
Zephyr 공식 문서와의 관계
이번 실습에서 사용하는 기능은 Zephyr의 Wi-Fi 기능 하나에만 국한되어 있지 않습니다.
net_mgmt()를 이용한 요청과 Event Callback 구조는 Zephyr의 Network Management 기능과 관련되어 있으며, iface는 Network Interface와 관련됩니다. 또한 NET_REQUEST_WIFI_SCAN, NET_EVENT_WIFI_SCAN_RESULT, NET_EVENT_WIFI_SCAN_DONE 등은 Zephyr의 Wi-Fi Management 기능에서 정의된 Wi-Fi 관리 요청과 Event입니다.
따라서 Zephyr 공식 문서를 살펴볼 때 이번 실습과 관련하여 다음 세 부분을 함께 보면 좋습니다.
- Network Management
- Network Interface
- Wi-Fi Management
아래 링크에서 공식 문서를 확인 할 수 있습니다.
처음 Zephyr 네트워크 문서를 보면 각각의 API가 별도로 나열되어 있어 전체적인 관계를 파악하기 어려울 수 있습니다. 하지만 이번 Wi-Fi Scan 프로그램을 기준으로 보면 이들의 관계가 비교적 명확해집니다.
net_if는 어느 Network Interface를 사용할 것인지를 나타내고, net_mgmt()는 그 Interface에 어떤 작업을 요청할 것인지를 전달하며, Network Management Event는 그 작업에서 발생한 결과나 상태 변화를 Application에 알려주는 역할을 합니다.
이번 Wi-Fi Scan 실습은 단순히 주변 AP를 검색해 보는 것뿐만 아니라, 이러한 Zephyr Network Management의 기본적인 동작 방식을 실제 프로그램을 통해 확인한다는 데에도 의미가 있습니다.
다음으로 실제 main.c를 작성하고, 앞에서 살펴본 Callback 등록 → Scan Request → Event 발생 → Callback 호출 과정이 코드에서는 어떻게 구현되는지 하나씩 살펴보겠습니다.
Wi-Fi Scan 프로그램 작성
앞에서는 Zephyr에서 Wi-Fi Scan이 Callback 등록 → Scan 요청 → Event 발생 → Callback 호출의 순서로 동작하는 구조를 살펴보았습니다.
이제 이러한 동작 구조를 실제 프로그램으로 구현해 보겠습니다.
이번 프로그램에서는 주변의 AP를 Scan한 후 검색된 AP의 SSID, Channel, RSSI, 보안 방식 등의 정보를 콘솔에 출력합니다. 또한 Scan이 모두 끝나면 Scan 완료 메시지를 출력하도록 하였습니다.
src/main.c의 전체 코드는 다음과 같습니다.



프로그램의 주요 부분
위 프로그램은 앞에서 설명한 Wi-Fi Scan의 동작 구조를 그대로 구현한 것입니다. 여기서는 코드의 주요 부분만 간단히 살펴보겠습니다.
먼저 다음 코드로 기본 Network Interface를 가져옵니다.
iface = net_if_get_default();
iface는 이후 net_mgmt()를 이용하여 Wi-Fi Scan을 요청할 때 사용됩니다.다음 부분은 앞의 동작 구조에서 ① Callback 등록에 해당합니다.
net_mgmt_init_event_callback(&scan_result_cb,
scan_result_handler,
NET_EVENT_WIFI_SCAN_RESULT);
net_mgmt_add_event_callback(&scan_result_cb);
net_mgmt_init_event_callback(&scan_done_cb,
scan_done_handler,
NET_EVENT_WIFI_SCAN_DONE);
net_mgmt_add_event_callback(&scan_done_cb);
NET_EVENT_WIFI_SCAN_RESULT에는 scan_result_handler를, NET_EVENT_WIFI_SCAN_DONE에는 scan_done_handler를 각각 Callback으로 등록합니다.
Callback 등록이 끝나면 ② Wi-Fi Scan을 요청합니다.
ret = net_mgmt(NET_REQUEST_WIFI_SCAN,
iface,
NULL,
0);
NET_EVENT_WIFI_SCAN_RESULT Event가 발생하고, 등록되어 있던 ④ scan_result_handler()가 호출됩니다.static void scan_result_handler(...)
{
const struct wifi_scan_result *entry =
(const struct wifi_scan_result *)cb->info;
printf("SSID: %-32.*s RSSI: %d dBm Channel: %d\n",
entry->ssid_length,
entry->ssid,
entry->rssi,
entry->channel);
}
cb->info를 통해 검색된 AP의 정보를 얻을 수 있으며, 여기서는 SSID, RSSI, Channel을 출력하도록 하였습니다. 주변에 여러 AP가 있으면 이 Callback은 AP별로 반복해서 호출됩니다.
모든 Scan이 끝나면 NET_EVENT_WIFI_SCAN_DONE Event가 발생하고 scan_done_handler()가 호출됩니다.
static void scan_done_handler(...)
{
printf("\nWi-Fi scan completed.\n");
}
main.c의 핵심 흐름은 앞에서 살펴본 것과 동일합니다.① Callback 등록 → ② Scan 요청 → ③ Event 발생 → ④ Callback 호출
앞에서 동작 구조를 먼저 살펴보았기 때문에 실제 코드에서는 각각의 API가 어떤 역할을 하는지 비교적 쉽게 확인할 수 있습니다.
이제 프로그램을 빌드하여 ESP32 CAN 보드에서 실제 Wi-Fi Scan 결과를 확인해 보겠습니다.
Build 및 실행 결과
Build
이제 Build할 차례 입니다.
디렉토리를 skpang_esp32_can_wifiscan 폴더로 이동합니다.
Build 명령어는 CHANGELOG.md에 기록 해 둔 것을 복사하여 Terminal에 붙여넣기를 하여 실행 합니다.
명령어는 다음과 같습니다.

이번 프로젝트에서는 커스텀 보드인 skpang_esp32_can의 esp32/procpu 타깃을 사용하고 있으므로, 보드 타깃과 BOARD_ROOT 경로를 정확하게 지정해야 합니다.
앞에서 설명한 것처럼 이러한 Build 명령을 CHANGELOG.md에 기록해 두면 프로젝트를 다시 Build할 때 보드 타깃이나 경로를 잘못 입력하는 실수를 줄일 수 있습니다.
Build가 정상적으로 완료되면 아래와 같은 메시지가 표시됩니다

화면 마지막의
Successfully created ESP32 image.
메시지를 통해 ESP32에 다운로드할 이미지가 정상적으로 생성되었음을 확인할 수 있습니다.
실행 결과
west flash를 이용하여 펌웨어를 Target에 다운로드한 후 프로그램을 실행해 보겠습니다.
실행 결과는 PC의 Serial Terminal 프로그램인 Docklight를 이용하여 확인하였습니다.
아래는 프로그램 실행 후 Docklight에 출력된 화면입니다.

위 화면에서는 주변에서 총 4개의 Wi-Fi AP가 검색되었으며, 각각의 SSID, RSSI, Channel 정보가 출력된 것을 확인할 수 있습니다.
이 결과를 앞에서 살펴본 Wi-Fi Scan 동작 구조와 연결해 보면, AP가 발견될 때마다 NET_EVENT_WIFI_SCAN_RESULT Event가 발생하고 scan_result_handler()가 호출되면서 각 AP의 정보가 한 줄씩 출력된 것입니다.
즉, 이번 실행에서는 이 과정이 4개의 AP에 대해 4번 반복되었습니다.
모든 AP에 대한 Scan이 끝난 후에는 NET_EVENT_WIFI_SCAN_DONE Event가 발생하고 scan_done_handler()가 호출되면서 마지막에 다음 메시지가 출력됩니다.
Wi-Fi scan completed.
① Callback 등록 → ② Scan 요청 → ③ Event 발생 → ④ Callback 호출
의 동작 구조가 실제 ESP32 Wi-Fi Scan에서도 그대로 이루어지고 있음을 확인할 수 있습니다.
결론
이번 실습에서는 Zephyr가 포팅된 ESP32 CAN 보드에서 Wi-Fi Scan 기능을 구현하고, 주변의 AP 정보를 실제로 검색해 보았습니다.
처음에는 Wi-Fi Scan이라는 기능 자체가 비교적 단순해 보이지만, 프로그램을 작성하면서 Zephyr에서는 Scan 요청과 결과 처리가 서로 분리되어 있으며 Network Management의 Request와 Event Callback 구조를 통해 동작한다는 것을 확인할 수 있었습니다.
이번 실습의 전체적인 흐름은 다음과 같이 정리할 수 있습니다.
① Callback 등록 → ② Scan 요청 → ③ Event 발생 → ④ Callback 호출
Application에서는 먼저 NET_EVENT_WIFI_SCAN_RESULT와 NET_EVENT_WIFI_SCAN_DONE을 처리할 Callback을 등록한 후, net_mgmt()를 이용하여 Wi-Fi Scan을 요청합니다. 이후 AP가 발견될 때마다 NET_EVENT_WIFI_SCAN_RESULT Event가 발생하여 scan_result_handler()가 호출되고, 모든 Scan이 끝나면 NET_EVENT_WIFI_SCAN_DONE Event와 함께 scan_done_handler()가 호출됩니다.
실제 실행 결과에서도 주변의 여러 AP가 각각 출력되고 마지막에 Wi-Fi scan completed. 메시지가 출력되는 것을 확인함으로써, 앞에서 살펴본 Request → Event → Callback의 동작 구조가 실제 프로그램에서 어떻게 이루어지는지 확인할 수 있었습니다.
이번 실습에서 중요한 것은 단순히 ESP32에서 주변 Wi-Fi를 검색했다는 것만은 아닙니다. net_if, net_mgmt(), Network Management Event, Callback 등 Zephyr의 네트워크 기능을 구성하는 기본적인 요소들이 서로 어떻게 연결되어 동작하는지를 실제 프로그램을 통해 살펴보았다는 데 의미가 있습니다.
Wi-Fi Scan은 네트워크에 연결하기 전 주변의 AP를 확인하는 첫 단계입니다. 다음 실습에서는 이번에 살펴본 구조를 바탕으로 실제 AP에 연결하고, ESP32가 네트워크에 접속하는 과정을 살펴보도록 하겠습니다.