ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Embedded Swift: ESP32-C6을 Wifi에 연결시켜 웹 서버로 사용하기, 브라우저에서 LED 제어
    공부/Swift(프로그래밍 언어) 2026. 3. 31. 01:57
    반응형

    ESP32-C6를 Wi-Fi 공유기에 연결한 뒤, 인터넷 브라우저로 접속해 LED를 켜고 끄는 작업 등을 할 수 있습니다. ESP32-C6 + Embedded Swift 환경에서는 “Swift → C(ESP-IDF HTTP 서버)”를 브리징해서 쓰는 방식으로 구현합니다.

     

    전체 흐름

    • Wi-Fi 연결
    • HTTP 서버 실행
    • URL에 따라 LED 제어

     

    이전 글

     

    Wi-Fi 연결 (C 함수 사용)

    Swift에서 직접 구현하기 어렵기 때문에 보통 C helper 함수 하나 만들어서 씁니다.

     

    왜 어렵나요?

    1. C 매크로(C Macro)의 존재 👈 가장 큰 이유 (더보기 클릭)
    2. 메모리에 직접 접근하려면 Unsafe 관련 기능을 사용해야함
    더보기

    C의 "function-like macro"는 Swift에서 직접 사용 불가

    문제 코드

    cfg = WIFI_INIT_CONFIG_DEFAULT()

    이건 C에서는 매크로라는 것으로, 매크로는 #define 전처리 지시어를 사용하여 코드 내의 특정 식별자를 정의된 문자열로 단순 치환하는 기능입니다. 컴파일 이전에 전처리기에 의해 실행되며, 자주 사용되는 상수나 복잡한 명령어를 묶어 코드의 가독성을 높이고 반복 작업을 줄이거나 자동화하는 데 활용됩니다.

    #define WIFI_INIT_CONFIG_DEFAULT() { ... }

    Swift에서는 매크로로 사용할 수 없으며 함수로도 사용할 수 없습니다.

     

    해결 방법 2가지

    방법 1

    C에서 함수 하나 만들어서 우회

    wifi_helper.c

    #include "esp_wifi.h"
    
    wifi_init_config_t get_wifi_default_config(void) {
        return WIFI_INIT_CONFIG_DEFAULT();
    }

     

    wifi_helper.h

    #pragma once
    
    wifi_init_config_t get_wifi_default_config(void);

     

    BridgingHeader.h

    #include "wifi_helper.h"

     

    Swift에서 사용

    var cfg = get_wifi_default_config()
    esp_wifi_init(&cfg)

     

    장점

    • 안정적
    • ESP-IDF 그대로 사용
    • 유지보수 쉬움

     

    방법 2 (비추천)

    Swift에서 직접 struct 구성

    var cfg = wifi_init_config_t()
    cfg = wifi_init_config_t(
      // 필드 하나하나 직접 채워야 함 (거의 불가능 수준)
    )

    문제

    • 내부 필드 많음
    • ESP-IDF 버전마다 변경됨
    • 사실상 유지 불가

     

    👉 WIFI_INIT_CONFIG_DEFAULT()는 Swift에서 못 쓰니까 C 함수로 감싸서 가져와야 합니다

     

    와이파이를 접속할 wifi_init_sta 함수를 만들어야 합니다. 프로젝트 메인 디렉터리에 wifi_connect.c 파일을 추가하고 아래 코드를 작성합니다.

    wifi_connect.c

    #include "esp_wifi.h"
    #include "esp_event.h"
    #include "nvs_flash.h"
    
    void wifi_init_sta(void) {
        nvs_flash_init();
        esp_netif_init();
        esp_event_loop_create_default();
        esp_netif_create_default_wifi_sta();
    
        wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
        esp_wifi_init(&cfg);
    
        wifi_config_t wifi_config = {
            .sta = {
                .ssid = "YOUR_SSID",
                .password = "YOUR_PASSWORD",
            },
        };
    
        esp_wifi_set_mode(WIFI_MODE_STA);
        esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
        esp_wifi_start();
        esp_wifi_connect();
    }
    • nvs_flash_init(): Wi-Fi 설정, 인증 정보 저장,  비휘발성 저장소 초기화, ESP-IDF에서 Wi-Fi 쓰려면 필수
    • 네트워크 스택 초기화
      • esp_netif_init(): TCP/IP 스택 초기화
      • esp_event_loop_create_default(): WIFI 연결, IP 할당 이벤트 시스템 생성
      • esp_netif_create_default_wifi_sta(): Station 모드 네트워크 인터페이스 생성하여 공유기에 접속
    • WiFi 드라이버 초기화
      • wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(): cfg 변수에 WIFI 드라이버 기본 설정(매크로) 할당
      • esp_wifi_init(&cfg): WiFi 드라이버 시작
    • 접속 정보 설정
      • wifi_config: 와이파이 이름, 비밀번호 입력
    • esp_wifi_set_mode(WIFI_MODE_STA): STA(공유기에 접속) 모드 설정
    • esp_wifi_set_config(WIFI_IF_STA, &wifi_config): 와이파이 정보 설정 적용
    • esp_wifi_start(): 드라이버 실행
    • esp_wifi_connect(): 공유기에 접속 시도

     

    다음, 이 C 파일을 Swift에서 접근하기 위해 헤더 파일을 만들어야 합니다. wifi_connect.h 파일을 만듭니다.

    wifi_connect.h

    #pragma once
    
    void wifi_init_sta(void);
    • #pragma once: 이 헤더 파일은 한 번만 include 하라는 뜻
    • void wifi_init_sta(void): 이러한 함수가 있다는 것을 선언

     

    HTTP 서버 (C로 구현)

    Wi-Fi 연결 함수와 마찬가지로 Swift로 구현하기 어렵기 때문에 C 파일을 작성하고 헤더를 추가합니다.

    http_server.c

    #include "esp_http_server.h"
    
    extern void led_on();
    extern void led_off();
    
    esp_err_t on_handler(httpd_req_t *req) {
        led_on();
        httpd_resp_send(req, "LED ON", HTTPD_RESP_USE_STRLEN);
        return ESP_OK;
    }
    
    esp_err_t off_handler(httpd_req_t *req) {
        led_off();
        httpd_resp_send(req, "LED OFF", HTTPD_RESP_USE_STRLEN);
        return ESP_OK;
    }
    
    void start_webserver(void) {
        httpd_config_t config = HTTPD_DEFAULT_CONFIG();
        httpd_handle_t server = NULL;
    
        httpd_start(&server, &config);
    
        httpd_uri_t on_uri = {
            .uri = "/on",
            .method = HTTP_GET,
            .handler = on_handler,
        };
    
        httpd_uri_t off_uri = {
            .uri = "/off",
            .method = HTTP_GET,
            .handler = off_handler,
        };
    
        httpd_register_uri_handler(server, &on_uri);
        httpd_register_uri_handler(server, &off_uri);
    }
    • 전체 동작 흐름
      • 브라우저에서 /on 접속 → LED 켜짐
      • /off 접속 → LED 꺼짐
    • #include "esp_http_server.h": ESP-IDF의 HTTP 서버 기능 사용
    • extern 키워드: Swift에서 선언한 함수를 C에서 사용하기 위해 필요
      • extern void led_on(): Swift 또는 다른 C 파일에 있는 LED 켜기 함수 선언
      • extern void led_off(): LED 끄기 함수 선언
    • on_handler
      • /on 요청 처리
      • led_on() 호출 → LED 켜기
      • "LED ON" 문자열을 HTTP 응답으로 전송
    • off_handler
      • /off 요청 처리
      • led_off() 호출 → LED 끄기
      • "LED OFF" 문자열 응답
    • start_webserver: HTTP 서버 시작 함수
    • httpd_config_t config = HTTPD_DEFAULT_CONFIG(): 서버 기본 설정 생성 (포트, 스택 등)
    • httpd_handle_t server = NULL: 서버 핸들 변수
    • httpd_start(&server, &config): HTTP 서버 실행
    • httpd_uri_t on_uri: /on 경로 정의, GET 요청일 때 on_handler 실행
    • httpd_uri_t off_uri: /off 경로 정의, GET 요청일 때 off_handler 실행
    • httpd_register_uri_handler
      • URL과 handler 연결
      • /onon_handler
      • /offoff_handler

     

    마찬가지로 헤더 파일도 추가합니다.

    http_server.h

    #pragma once
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    void start_webserver(void);
    
    #ifdef __cplusplus
    }
    #endif
    • C/C++ 호환을 고려한 함수 선언 헤더 파일
    • #pragma once: 헤더 파일이 한 번만 포함되도록 보장, 중복 include 방지
    • #ifdef __cplusplus: 현재 코드가 C++로 컴파일되는지 확인
    • extern "C" {
      • C++에서 사용할 때 C 방식으로 함수 이름 유지 (name mangling 방지)
    • void start_webserver(void): 함수 선언 (프로토타입), 실제 구현은 .c 파일에 있음
    • #endif
      • #ifdef __cplusplus 조건 종료

     

    Swift에서 LED 제어 함수 제공

    C에서 호출할 수 있게 _cdecl를 사용합니다.

    @_cdecl("led_on")
    func led_on() {
      let strip = LedStrip(gpioPin: 8, maxLeds: 1)
      strip.setPixel(index: 0, color: .lightWhite)
      strip.refresh()
    }
    
    @_cdecl("led_off")
    func led_off() {
      let strip = LedStrip(gpioPin: 8, maxLeds: 1)
      strip.setPixel(index: 0, color: .off)
      strip.refresh()
    }
    •  
    • @_cdecl("led_on"): Swift 함수를 C에서 led_on으로 인식하도록 설정
    • LedStrip은 LED 스트립을 관리하는 구조체로 이전 글 참조

     

    main에서 실행

    @_cdecl("app_main")
    func main() {
      wifi_init_sta()
      start_webserver()
    
      while true {
        vTaskDelay(1000 / (1000 / UInt32(configTICK_RATE_HZ)))
      }
    }
    • @_cdecl("app_main")
      • Swift 함수를 C에서 app_main으로 인식하도록 설정
      • ESP-IDF가 이 함수를 프로그램 시작점(entry point)으로 호출
    • wifi_init_sta(): 앞에서 작성한 wifi_init_sta 함수(와이파이 설정 및 시작)를 호출
    • start_webserver(): HTTP 서버 실행
    • vTaskDelay 사용 이유
      • FreeRTOS에서 현재 Task를 일정 시간 동안 잠시 멈춤(sleep)
      • CPU 점유율 낮추기: delay 없이 while true 돌리면 CPU 100% 사용
      • 다른 Task 실행 기회 제공: Wi-Fi, TCP/IP 스택, HTTP 서버
      • 시스템 안정성 유지: FreeRTOS는 멀티태스킹 기반이라 Task가 양보해야 정상 동작
      • delay 없이 돌리면 생기는 문제: Wi-Fi 연결 불안정, HTTP 서버 응답 느림 또는 멈춤, 발열 증가, 배터리 소모 증가

     

    CMakeLists.txt 및 BridingHeader.h 업데이트 (중요)

    main 디렉터리 안에 있는 CMakeLists.txt 파일에 새로 만든 C 파일 목록을 추가합니다.

    target_sources(${COMPONENT_LIB}
      PRIVATE
      Main.swift
      LedStrip.swift
      wifi_connect.c
      http_server.c
    )

     

    BridgingHeader.h에 다음 부분을 추가합니다.

    // Wifi
    #include "wifi_connect.h"
    #include "http_server.h"

     

    빌드 및 플래싱

    애플리케이션을 컴파일하고 ESP32-C6에 USB-C 케이블을 연결한 후 플래싱하세요.

    idf.py build
    idf.py flash
    idf.py monitor
    # 빌드, 플래시, 모니터를 한 번에 하기
    idf.py build flash monitor

     

    모니터에서 접속 URL을 확인한 뒤 접속합니다.

     

    URL 에 접속해서 LED가 켜고 꺼지는지 확인합니다.

     

    [응용] URL에서 쿼리 파라미터를 받아 LED의 Hue 값 조절하기

    Swift 함수 led_hue 추가

    @_cdecl("led_hue")
    func led_hue(_ hue: Float) {
      let strip: LedStrip = LedStrip(gpioPin: 8, maxLeds: 1)
      strip.setPixel(index: 0, color: hsvToRgb(h: hue, s: 1, v: 0.1))
      strip.refresh()
    }
    • hsvToRgb: H, S, V 값을 받아 RGB로 변환, 코드는 이전 글 참조, 과다한 밝기 방지 위해 V: 0.1로 설정

     

    http_server.c 파일에 extern 함수로 추가합니다

    extern void led_hue(float hue);

     

    hue_hander 함수 추가하고 쿼리 파라미터를 받을 수 있도록 합니다. 키 이름은 value 이며, URL에서 hue?value=값 과 같이 사용합니다.

    esp_err_t hue_handler(httpd_req_t *req) {
        char query[64];
    
        if (httpd_req_get_url_query_str(req, query, sizeof(query)) == ESP_OK) {
    
            char value[16];
    
            if(httpd_query_key_value(query, "value", value, sizeof(value)) == ESP_OK) {
                
                float hue = strtof(value, NULL);
                led_hue(hue);
    
                char resp[64];
                sprintf(resp, "Hue set to %.2f", hue);
                httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
                return ESP_OK;
            }
        }
    
        httpd_resp_send(req, "Missing hue value ", HTTPD_RESP_USE_STRLEN);
        return ESP_OK;
    }
    • esp_err_t hue_handler(httpd_req_t *req)
      • /hue 요청을 처리하는 HTTP 핸들러 함수
      • 요청 정보는 req에 담겨 있음
    • char query[64];
      • URL의 ? 뒤 문자열을 저장할 버퍼
      • 오류 발생 방지 위해 크기를 충분하게 설정
      • 예: "value=120"
    • httpd_req_get_url_query_str(...)
      • 쿼리 문자열 전체 추출
      • 성공 시 query"value=..." 형태로 저장
    • char value[16]:
      • 실제 값만 저장할 버퍼
      • 오류 발생 방지 위해 크기를 충분하게 설정
      • 예: "120"
    • httpd_query_key_value(query, "value", value, sizeof(value))
      • "value=120"에서 "120"만 추출
      • key = "value"
    • float hue = strtof(value, NULL);
      • 문자열 "120" → 실수 120.0 변환
    • led_hue(hue);
      • Swift에서 구현한 함수 호출
      • LED 색상(hue) 변경
    • char resp[64];
      • 응답 메시지 저장용 버퍼
    • sprintf(resp, "Hue set to %.2f", hue);
      • 클라이언트에 보낼 문자열 생성
      • 소수점 2자리까지 출력
    • httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
      • HTTP 응답 전송
    • return ESP_OK;
      • 정상 처리 완료
    • 실패 케이스
      • httpd_resp_send(req, "Missing hue value ", HTTPD_RESP_USE_STRLEN);
      • ?value=...가 없을 때 실행

     

    start_webserver 함수에 위 핸들러를 처리할 URI를 추가합니다.

    void start_webserver(void) {
        // ...
    
        httpd_uri_t hue_uri = {
            .uri = "/hue",
            .method = HTTP_GET,
            .handler = hue_handler,
        };
    
        httpd_register_uri_handler(server, &hue_uri);
        
        // ...
    }

     

    hue?value=값 을 URL로 넣어 확인합니다.

     

    반응형
Designed by Tistory.