본문 바로가기

ESPRESSIF/ESP32-C6

[ESP32C6 SSM] Zigbee 테스트 - OnOff light EndDevice 제작 및 코드분석

ESP32C6의 가장 큰 특징으로 WiFi, BLE, Zigbee가 하나의 칩에 포함되어 있는 것이고 동시에 사용 할 수 있다. STM32WB 시리즈의 경우 BLE, Zigbee를 각각 스택을 올려 사용해야 하는 불편한 점이 있는데 (불가능한것은 아니지만 상당히 까다롭다) ESP32의 경우 기존 코드를 사용하며서 Zigbee 스택을 올려 구동할 수 있다.

특히 Zigbee Gateway 장치를 만들기에 적합할것 같다.

 

우선 ESP32에서 Zigbee 동작 테스트를 위한 가장 기본 코드를 구현해 보자.

먼저 ESP32-IDF 에서 제공하는 EndDevice예제 Light 장치를 테스트 해보면 좋을것 같다. ESP32C6 ESP-IDF 개발 환경 설정을 참고로 해서 HA_on_off_light 예제를 생성한다

 

전체 소스 코드는 아래 링크있는 코드를 수정해서 작성했다.

https://github.com/espressif/esp-zigbee-sdk/tree/main/examples/esp_zigbee_HA_sample/HA_on_off_light

 

LED 부분은 기존 예제어서 스트립 LED로 되어 있기 때문에 복잡한데 GPIO LED로  ESP32C6 SSM 보드의 핀맵에 맞도록 수정해 주었다. deferred_driver_init() 함수에 초기화 코드를 적용하면 된다.

static esp_err_t deferred_driver_init(void)
{
    static bool is_inited = false;
    if (!is_inited) {
        //light_driver_init(LIGHT_DEFAULT_OFF);
        Led1Init();
        is_inited = true;
    }
    return is_inited ? ESP_OK : ESP_FAIL;
}

 

 

생성된 예제를 컴파일 하면 에러가 발생하는데... 아직 자동 생성 코드에 문제가 있는것 같다.

일부 예제들은 헤더파일의 패스가 정의 되지 않는 현상이 있다. 

그리고 ESP32C6에서 Zigbee 예제가 정상적으로 컴파일 되려면 SDK Configuration Editor에서 Zigbee 관련 설정을 해 주어야 한다.

 

 

특히 device type 항목에서 End Device로 할것인지 Coordinator로 할것인지 설정이 중요하다. Light 예제는 End Device이므로 Zigbee End Device로 설정해 주어야 한다.

 

 


Zigbee EndDevice 코드의 메인 함수

예제 코드의 함수들만 나열해 보면 아래과 같이 구성된다.

우선 app_main 부분만 남기고 지워보면 전체 코드 구조는 아래와 같이 심플하게 구성된다.
프로그램이 실행되면 app_main에서 Zigbee 관련 Task esp_zb_task() 함수를 수행한다.

/* 프로그램 진입점 (ESP-IDF app_main) */
void app_main(void)
{
    /* Zigbee 플랫폼 설정 구조체 */
    esp_zb_platform_config_t config = {
        .radio_config = ESP_ZB_DEFAULT_RADIO_CONFIG(), // Zigbee 무선(RF) 설정
        .host_config  = ESP_ZB_DEFAULT_HOST_CONFIG(),  // 호스트(SoC) 설정
    };

    /* NVS 플래시 초기화 (Zigbee 네트워크 정보 저장 용도) */
    ESP_ERROR_CHECK(nvs_flash_init());

    /* Zigbee 플랫폼 환경 설정 적용 */
    ESP_ERROR_CHECK(esp_zb_platform_config(&config));

    /* Zigbee 실행 태스크 생성
       - 스택 크기: 4096바이트
       - 우선순위: 5
       - 이름: "Zigbee_main" */
    xTaskCreate(esp_zb_task, "Zigbee_main", 4096, NULL, 5, NULL);
}


//2)ESP32-C6에서 Zigbee Coordinator/Gateway 역할을 수행하는 핵심 태스크 생성
static void esp_zb_task(void *pvParameters)
{
//Zigbee Coordinator 역할 초기화 → 클러스터/엔드포인트 등록 → 네트워크 시작 → 메인 루프 실행
}


//3)Zigbee Coordinator(게이트웨이) 동작 시 발생하는 주요 이벤트를 처리
void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct)
{
//네트워크 초기화 → 포메이션 → steering → 신규 장치 announce → permit join
}

//BDB(기본 장치 동작) commissioning을 시작하는 콜백 함수
static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask)
{
ESP_RETURN_ON_FALSE(esp_zb_bdb_start_top_level_commissioning(mode_mask) == ESP_OK, , TAG, "Failed to start Zigbee bdb commissioning");
}

//Zigbee Core Action 핸들러
static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message)
{
}

//Zigbee Attribute 처리 함수
static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message)
{
}

 

 


Zigbee 실행  메인 태스크( esp_zb_task )

Zigbee 스택을 초기화 하고 실행하는 함수로 ZED(Zigbee End Device) 로 설정하고 ZigBee Device configuration 구조체를 초기화 하고 Endpoint를 등록한다.

#define ESP_ZB_ZED_CONFIG()                                         \
    {                                                               \
        .esp_zb_role = ESP_ZB_DEVICE_TYPE_ED,                       \
        .install_code_policy = INSTALLCODE_POLICY_ENABLE,           \
        .nwk_cfg.zed_cfg = {                                        \
            .ed_timeout = ED_AGING_TIMEOUT,                         \
            .keep_alive = ED_KEEP_ALIVE,                            \
        },                                                          \
    }

 

/* Zigbee 실행 메인 태스크 */
static void esp_zb_task(void *pvParameters)
{
    /* Zigbee 네트워크 설정 (ZED: Zigbee End Device) */
    esp_zb_cfg_t zb_nwk_cfg = ESP_ZB_ZED_CONFIG();

    /* Zigbee 스택 초기화 */
    esp_zb_init(&zb_nwk_cfg);

    /* On/Off Light 디바이스 기본 설정 생성 */
    esp_zb_on_off_light_cfg_t light_cfg = ESP_ZB_DEFAULT_ON_OFF_LIGHT_CONFIG();

    /* On/Off Light 엔드포인트 생성 (HA 프로파일 기반) */
    esp_zb_ep_list_t *esp_zb_on_off_light_ep =
        esp_zb_on_off_light_ep_create(HA_ESP_LIGHT_ENDPOINT, &light_cfg);

    /* 제조사 / 모델 정보 설정 (ZCL Basic Cluster) */
    zcl_basic_manufacturer_info_t info = {
        .manufacturer_name = ESP_MANUFACTURER_NAME,   // 제조사 이름
        .model_identifier = ESP_MODEL_IDENTIFIER,     // 모델 ID
    };

    /* 생성한 엔드포인트에 제조사 정보 추가 */
    esp_zcl_utility_add_ep_basic_manufacturer_info(
        esp_zb_on_off_light_ep,
        HA_ESP_LIGHT_ENDPOINT,
        &info
    );

    /* 엔드포인트를 Zigbee 스택에 등록 */
    esp_zb_device_register(esp_zb_on_off_light_ep);

    /* Zigbee Core Action 이벤트 콜백 등록 (ZCL 명령/속성 처리) */
    esp_zb_core_action_handler_register(zb_action_handler);

    /* Zigbee 기본 동작 채널 설정 (예: 채널 13 사용) */
    esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK);

    /* Zigbee 스택 시작
       false: 네트워크 자동 조인 안 함 (BDB 절차에 따라 진행) */
    ESP_ERROR_CHECK(esp_zb_start(false));

    /* Zigbee 메인 이벤트 루프 (종료되지 않음) */
    esp_zb_stack_main_loop();
}

 

 

 

Zigbee 애플리케이션 시그널 핸들러
Zigbee 스택 내부에서 발생하는 이벤트(시그널)를 처리하는 콜백 함수 이고 초기화, 네트워크 조인, 리부트, Steering(네트워크 탐색) 등의 처리를 한다.

 

Zigbee 실행 순서

  • 초기화 (SKIP_STARTUP) → BDB 초기화 시작.
  • 첫 시작/재부팅
  • STEERING → Steering(네트워크 탐색) 및 조인
/**
 * @brief Zigbee 애플리케이션 시그널 처리 핸들러
 * @param signal_struct Zigbee 스택에서 전달된 시그널 정보 구조체
 */
void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct)
{
    uint32_t *p_sg_p       = signal_struct->p_app_signal;   // 시그널 포인터
    esp_err_t err_status   = signal_struct->esp_err_status; // 시그널 처리 결과 상태
    esp_zb_app_signal_type_t sig_type = *p_sg_p;            // 실제 시그널 타입

    switch (sig_type) {
    case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP:
        // Zigbee 스택 초기화 이벤트
        ESP_LOGI(TAG, "Initialize Zigbee stack");
        // BDB(Basic Device Behavior) 초기화 수행
        esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_INITIALIZATION);
        break;

    case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START:  // 장치가 처음 시작될 때
    case ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT:       // 장치가 재부팅될 때
        if (err_status == ESP_OK) {
            // 드라이버 초기화 지연 처리
            ESP_LOGI(TAG, "Deferred driver initialization %s", deferred_driver_init() ? "failed" : "successful");

            // 공장 초기화 모드 여부 확인
            ESP_LOGI(TAG, "Device started up in%s factory-reset mode", esp_zb_bdb_is_factory_new() ? "" : " non");

            if (esp_zb_bdb_is_factory_new()) {
                // 새로운 장치 → 네트워크 탐색 및 조인 시도
                ESP_LOGI(TAG, "Start network steering");
                esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING);
            } else {
                // 기존 장치 → 단순히 재부팅
                ESP_LOGI(TAG, "Device rebooted");
            }
        } else {
            // 오류 발생 → 재시도 예약
            ESP_LOGW(TAG, "%s failed with status: %s, retrying", 
                     esp_zb_zdo_signal_to_string(sig_type), esp_err_to_name(err_status));
            esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb,
                                   ESP_ZB_BDB_MODE_INITIALIZATION, 1000);
        }
        break;

    case ESP_ZB_BDB_SIGNAL_STEERING:
        // 네트워크 Steering (PAN 탐색 및 조인) 결과
        if (err_status == ESP_OK) {
            // 성공적으로 네트워크에 참여한 경우 PAN 정보 출력
            esp_zb_ieee_addr_t extended_pan_id;
            esp_zb_get_extended_pan_id(extended_pan_id);
            ESP_LOGI(TAG,
                     "Joined network successfully (Extended PAN ID: "
                     "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, "
                     "PAN ID: 0x%04hx, Channel:%d, Short Address: 0x%04hx)",
                     extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4],
                     extended_pan_id[3], extended_pan_id[2], extended_pan_id[1], extended_pan_id[0],
                     esp_zb_get_pan_id(), esp_zb_get_current_channel(), esp_zb_get_short_address());
        } else {
            // 실패 시 → 일정 시간 후 다시 Steering 재시도
            ESP_LOGI(TAG, "Network steering was not successful (status: %s)", esp_err_to_name(err_status));
            esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb,
                                   ESP_ZB_BDB_MODE_NETWORK_STEERING, 1000);
        }
        break;

    default:
        // 그 외의 모든 ZDO 시그널은 단순히 로그 출력
        ESP_LOGI(TAG, "ZDO signal: %s (0x%x), status: %s",
                 esp_zb_zdo_signal_to_string(sig_type), sig_type, esp_err_to_name(err_status));
        break;
    }
}

 

 

 

BDB(Basic Device Behavior) Top-level Commissioning 시작 콜백
네트워크 초기화 / 포메이션 / 스티어링 등을 실행

static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask)
{
    // 지정된 모드로 commissioning 시작. 실패 시 로그 출력
    ESP_RETURN_ON_FALSE(
        esp_zb_bdb_start_top_level_commissioning(mode_mask) == ESP_OK,
        , TAG, "Failed to start Zigbee bdb commissioning"
    );
}

 

 

 

Zigbee Core Action 핸들러
Zigbee Core Layer에서 발생하는 동작(Action) 콜백을 처리한다. Zigbee 각 장치의 표준 Attribute 값 설정(SET_ATTR_VALUE) 이벤트를 처리 한다.
 

//Zigbee Core Action 핸들러
static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message)
{
    esp_err_t ret = ESP_OK;

    switch (callback_id) {
    case ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID:
        // Attribute 값이 변경될 때 → Attribute Handler 호출
        ret = zb_attribute_handler((esp_zb_zcl_set_attr_value_message_t *)message);
        break;

    default:
        // 지원하지 않는 액션 콜백은 경고 로그만 출력
        ESP_LOGW(TAG, "Receive Zigbee action(0x%x) callback", callback_id);
        break;
    }
    return ret;
}

 

 

 

Zigbee Attribute 처리 함수
클러스터(Attribute) 값이 변경되었을 때 호출되어, 수신된 속성(attribute)을 파싱하고 해당 동작한다. Light 장치에서는 attribute 값을 받아서 LED를 ON/OFF 한다. 이  부분을 수정해서 사용자가 원하는 처리를 해 주면 된다.

//Zigbee Attribute 처리 함수
static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message)
{
    esp_err_t ret = ESP_OK;
    bool light_state = 0;

    // 유효성 체크: 메시지가 NULL 이면 실패 반환
    ESP_RETURN_ON_FALSE(message, ESP_FAIL, TAG, "Empty message");

    // 메시지 상태가 성공이 아닌 경우 → 잘못된 인자 에러 반환
    ESP_RETURN_ON_FALSE(message->info.status == ESP_ZB_ZCL_STATUS_SUCCESS, 
                        ESP_ERR_INVALID_ARG, TAG, "Received message: error status(%d)",
                        message->info.status);

    // 수신된 메시지 로그 출력 (엔드포인트, 클러스터, 속성 ID, 데이터 크기)
    ESP_LOGI(TAG, "Received message: endpoint(%d), cluster(0x%x), attribute(0x%x), data size(%d)", 
             message->info.dst_endpoint, message->info.cluster,
             message->attribute.id, message->attribute.data.size);

    // 해당 엔드포인트가 Light 제어용 엔드포인트인지 확인
    if (message->info.dst_endpoint == HA_ESP_LIGHT_ENDPOINT) {
        // On/Off 클러스터에 대한 메시지인지 확인
        if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) {
            // On/Off 클러스터의 OnOff 속성(ID=0x0000)이면서 타입이 BOOL 인지 확인
            if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID && 
                message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL) {
                
                // 데이터 값이 존재하면 불린 값으로 변환 (조명 상태)
                light_state = message->attribute.data.value ? 
                              *(bool *)message->attribute.data.value : light_state;

                // 로그 출력: 조명 상태 (On/Off)
                ESP_LOGI(TAG, "Light sets to %s", light_state ? "On" : "Off");

                // 실제 조명 제어 함수 호출 (전원 제어)
                //light_driver_set_power(light_state);
                if(light_state)Led1On();
                else (light_state)Led1Off();
            }
        }
    }
    return ret;
}

 

프로그램 실행하면 EndDevice로 구동되고 Zigbee 네트웍에 가입하려는 시도를 한다. Zigbee Gateway나 Coordinator 장치가 있어야 하므로 간단히 Switch Coordiator 장치를 만들어 보자.

 

 

반응형