ESP-IDFを使ってみる

PING通信とIPアドレス構造体


こちら
にping(ICMP echo)のやり方が公開されていますが、initialize_ping()のコードがメチャクチャ間違っています。
色々テストしましたが、ようやく以下のコードで動きました。
initialize_ping()以外のコード(CallBack関数)はドキュメントのままで大丈夫です。
ESP_PING_COUNT_INFINITEの定数を使うと、永遠にpingを打ち続けます。
esp_ping_stop()でpingを打つのをやめます。
void initialize_ping()
{
        /* convert URL to IP address */
        ip_addr_t target_addr;
        memset(&target_addr, 0, sizeof(target_addr));
        struct addrinfo hint;
        memset(&hint, 0, sizeof(hint));
        struct addrinfo *res = NULL;
        int err = getaddrinfo("www.espressif.com", NULL, &hint, &res);
        if(err != 0 || res == NULL) {
                ESP_LOGE(TAG, "DNS lookup failed err=%d res=%p", err, res);
                return;
        } else {
                ESP_LOGI(TAG, "DNS lookup success");
        }

        if (res->ai_family == AF_INET) {
                struct in_addr addr4 = ((struct sockaddr_in *) (res->ai_addr))->sin_addr;
                inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);
        } else {
                struct in6_addr addr6 = ((struct sockaddr_in6 *) (res->ai_addr))->sin6_addr;
                inet6_addr_to_ip6addr(ip_2_ip6(&target_addr), &addr6);
        }
        freeaddrinfo(res);
        ESP_LOGI(TAG, "target_addr.type=%d", target_addr.type);
        ESP_LOGI(TAG, "target_addr=%s", ip4addr_ntoa(&(target_addr.u_addr.ip4)));

        esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG();
        ping_config.target_addr = target_addr;                  // target IP address

        ping_config.count = ESP_PING_COUNT_INFINITE;    // ping in infinite mode, esp_ping_stop can stop it

        /* set callback functions */
        esp_ping_callbacks_t cbs = {
                .on_ping_success = cmd_ping_on_ping_success,
                .on_ping_timeout = cmd_ping_on_ping_timeout,
                .on_ping_end = cmd_ping_on_ping_end,
                .cb_args = NULL
        };
        esp_ping_handle_t ping;
        esp_ping_new_session(&ping_config, &cbs, &ping);
        esp_ping_start(ping);
        ESP_LOGI(TAG, "esp_ping_start");
}

ビルドすると「www.espressif.com」宛にpingを打ちます。
From 119.9.92.99 icmp_seq=1070 timeout
64 bytes from 119.9.92.99 icmp_seq=1071 ttl=51 time=108 ms
64 bytes from 119.9.92.99 icmp_seq=1072 ttl=51 time=266 ms
64 bytes from 119.9.92.99 icmp_seq=1073 ttl=51 time=270 ms
From 119.9.92.99 icmp_seq=1074 timeout
64 bytes from 119.9.92.99 icmp_seq=1075 ttl=51 time=615 ms
64 bytes from 119.9.92.99 icmp_seq=1076 ttl=51 time=115 ms
64 bytes from 119.9.92.99 icmp_seq=1077 ttl=51 time=110 ms
64 bytes from 119.9.92.99 icmp_seq=1078 ttl=51 time=187 ms
64 bytes from 119.9.92.99 icmp_seq=1079 ttl=51 time=420 ms
64 bytes from 119.9.92.99 icmp_seq=1080 ttl=51 time=95 ms
64 bytes from 119.9.92.99 icmp_seq=1081 ttl=51 time=83 ms
64 bytes from 119.9.92.99 icmp_seq=1082 ttl=51 time=143 ms

espressifには報告済なので、そのうちドキュメントは修正されると思います。 → 修正されました。
Gateway宛に永遠にpingを打つコードをこちらで公開しています。



こ ちらのping(ICMP echo)のドキュメントにはもう1つ不備が有ります。
ESP_PING_DEFAULT_CONFIG()でDefaultの設定を行うことができますが、このマクロで設定される値がどこにも記載さ れていません。
調べたらcomponents/lwip/include/apps/ping/ping_sock.hに初期値が定義されていました。
interval_ms(ping周期)が1000mSec、timeout_msが1000mSec、task_prioが2です。
pingはバックグラウンドで別タスクが実行します。
タスクが多いときはtask_prio(最大のpriorityは24)はもっと上げた方がいいです。
#define ESP_PING_DEFAULT_CONFIG()        \
    {                                    \
        .count = 5,                      \
        .interval_ms = 1000,             \
        .timeout_ms = 1000,              \
        .data_size = 56,                 \
        .tos = 0,                        \
        .target_addr = ip_addr_any_type, \
        .task_stack_size = 2048,         \
        .task_prio = 2,                  \
    }



ESP-IDFにはIPアドレスを表す変数の型が沢山定義されています。
元々はlwipが定義する構造体だけを使っていましたが、Ver4からesp-idf独自の構造体が採用されました。

esp-idf/components/lwip/lwip/src/include/lwip/ip4_addr.h

[ip4_addr_t]はlwipが定義している構造体で、IP4アドレスを示す構造体です。
/** This is the aligned version of ip4_addr_t,
   used as local variable, on the stack, etc. */
struct ip4_addr {
  u32_t addr;
};

/** ip4_addr_t uses a struct for convenience only, so that the same defines can
 * operate both on ip4_addr_t as well as on ip4_addr_p_t. */
typedef struct ip4_addr ip4_addr_t;

esp-idf/components/lwip/lwip/src/include/lwip/ip6_addr.h

[ip6_addr_t]はlwipが定義している構造体で、IP6アドレスを示す構造体です。
/** This is the aligned version of ip6_addr_t,
    used as local variable, on the stack, etc. */
struct ip6_addr {
  u32_t addr[4];
#if LWIP_IPV6_SCOPES
  u8_t zone;
#endif /* LWIP_IPV6_SCOPES */
};

/** IPv6 address */
typedef struct ip6_addr ip6_addr_t;

esp-idf/components/lwip/lwip/src/include/lwip/ip_addr.h

[ip_addr_t]はlwipが定義している構造体で、IP4/IP6両方のアドレスを示す構造体です。
typedef struct ip_addr {
  union {
    ip6_addr_t ip6;
    ip4_addr_t ip4;
  } u_addr;
  /** @ref lwip_ip_addr_type */
  u8_t type;
} ip_addr_t;

typeの値は以下のいずれかの値になります。
この値でip4/ip6のどちらが格納されているのかを識別します。
enum lwip_ip_addr_type {
  /** IPv4 */
  IPADDR_TYPE_V4 =   0U,
  /** IPv6 */
  IPADDR_TYPE_V6 =   6U,
  /** IPv4+IPv6 ("dual-stack") */
  IPADDR_TYPE_ANY = 46U
};

esp-idf/components/tcpip_adapter/include/tcpip_adapter_types.h → V5で廃止

[tcpip_adapter_ip_info]はesp-idfが定義している構造体で、IPV4 Address、IPV4 netmask、IPV4 gatewayの3つをまとめて格納する構造体です。
この構造体はesp-idf Ver4まで使えましたが、esp-idf Ver5で廃止され、esp_ip_addr_tに置き換わりました。
typedef struct {
    ip4_addr_t ip;      /**< Interface IPV4 address */
    ip4_addr_t netmask; /**< Interface IPV4 netmask */
    ip4_addr_t gw;      /**< Interface IPV4 gateway address */
} tcpip_adapter_ip_info_t;

esp-idf/components/esp_netif/include/esp_netif_ip_addr.h

[esp_ip_addr_t]はesp-idfが定義している構造体で、IP4/IP6両方のアドレスを示す構造体です。
esp-idf Ver4から使えるようになりました。
この構造体のメンバーは、今後拡張されていく可能性が有ります。
struct esp_ip6_addr {
    uint32_t addr[4];
    uint8_t zone;
};

struct esp_ip4_addr {
    uint32_t addr;
};

typedef struct esp_ip4_addr esp_ip4_addr_t;

typedef struct esp_ip6_addr esp_ip6_addr_t;

typedef struct _ip_addr {
    union {
        esp_ip6_addr_t ip6;
        esp_ip4_addr_t ip4;
    } u_addr;
    uint8_t type;
} esp_ip_addr_t;

esp-idf/components/esp_netif/include/esp_netif_types.h

[esp_netif_ip_info_t]はesp-idfが定義している構造体で、IPV4 Address、IPV4 netmask、IPV4 gatewayの3つをまとめて格納する構造体です。
typedef struct {
    esp_ip4_addr_t ip;      /**< Interface IPV4 address */
    esp_ip4_addr_t netmask; /**< Interface IPV4 netmask */
    esp_ip4_addr_t gw;      /**< Interface IPV4 gateway address */
} esp_netif_ip_info_t;



pingを使う場合、esp_ping_config_t型の構造体を使います。この構造体はlwipライブラリの中で定義されています。
target_addrはip_addr_t型の変数です。
typedef struct {
    uint32_t count;           /*!< A "ping" session contains count procedures */
    uint32_t interval_ms;     /*!< Milliseconds between each ping procedure */
    uint32_t timeout_ms;      /*!< Timeout value (in milliseconds) of each ping procedure */
    uint32_t data_size;       /*!< Size of the data next to ICMP packet header */
    uint8_t tos;              /*!< Type of Service, a field specified in the IP header */
    ip_addr_t target_addr;    /*!< Target IP address, either IPv4 or IPv6 */
    uint32_t task_stack_size; /*!< Stack size of internal ping task */
    uint32_t task_prio;       /*!< Priority of internal ping task */
    uint32_t interface;       /*!< Netif index, interface=0 means NETIF_NO_INDEX*/
} esp_ping_config_t;

使用中のアクセスポイントの情報を取り出す場合、esp_netif_get_ip_info()を使います。
この関数では、esp_netif_ip_info_t型の構造体を使います。
アドレスはesp_ip4_addr_t型の変数です。
typedef struct {
    esp_ip4_addr_t ip;      /**< Interface IPV4 address */
    esp_ip4_addr_t netmask; /**< Interface IPV4 netmask */
    esp_ip4_addr_t gw;      /**< Interface IPV4 gateway address */
} esp_netif_ip_info_t;

この様にlwipが定義している構造体と、esp-idfが定義している構造体の型が違っているので、
pingのTarget IP addressに、アクセスポイントのGatewayアドレスを設定する場合、 esp_ip4_addr_t → ip_addr_tへの型変換が必要になり ます。
単純に代入すると以下のエラーとなります。
../main/ping.c:135:27: error: incompatible types when assigning to type 'ip_addr_t' {aka 'struct ip_addr'} from type 'esp_ip4_addr_t' {aka 'struct esp_ip4_addr'}
   ping_config.target_addr = ip_info.gw;

esp_ip4_addr_t 型の変数を無理やりip4_addr_t型の変数に代入し、ip4_addr_t型の変数を使ってtarget_addrの変数に代入します。
// get current STA information
esp_netif_ip_info_t ip_info;
esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info);

ip_addr_t gw_addr;
memcpy((char *)&gw_addr.u_addr.ip4, (char *)&ip_info.gw, sizeof(ip4_addr));
gw_addr.type = IPADDR_TYPE_V4;
ESP_LOGI(TAG, "ip4addr_ntoa(gw_addr.u_addr.ip4)=%s", ip4addr_ntoa(&gw_addr.u_addr.ip4));

ping.config.target_addr = gw_addr;

あ〜〜めんど!!



以下のコードでIPアドレスを相互に変換しています。
IP2STR()はuint32_tの引数でも、u32_tの引数でもコンパイルが通りますが、
ip4addr_ntoa()はu32_tの引数しかコンパイルが通りません。

{前略}

void app_main()
{
        //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");
        if (wifi_init_sta() == ESP_OK) {
                ESP_LOGI(TAG, "Connection success");
        } else {
                ESP_LOGE(TAG, "Connection failed");
                while(1) { vTaskDelay(1); }
        }

        esp_netif_ip_info_t ip_info;
        esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info);
        ESP_LOGI(TAG, "ip_info.ip=%s", ip4addr_ntoa((const ip4_addr_t *)&ip_info.ip));
        ESP_LOGI(TAG, "ip_info.ip=" IPSTR, IP2STR(&ip_info.ip));

        ESP_LOGI(TAG, "esp_ip4_addr_get_byte(0)=%d", esp_ip4_addr_get_byte(&ip_info.ip, 0));
        ESP_LOGI(TAG, "esp_ip4_addr_get_byte(1)=%d", esp_ip4_addr_get_byte(&ip_info.ip, 1));
        ESP_LOGI(TAG, "esp_ip4_addr_get_byte(2)=%d", esp_ip4_addr_get_byte(&ip_info.ip, 2));
        ESP_LOGI(TAG, "esp_ip4_addr_get_byte(3)=%d", esp_ip4_addr_get_byte(&ip_info.ip, 3));

        esp_ip4_addr_t esp_ip4_addr; // 4 byte
        ESP_LOGD(TAG, "sizeof(esp_ip4_addr_t)=%d", sizeof(esp_ip4_addr_t));
        esp_ip4_addr = ip_info.ip;
        ESP_LOGI(TAG, "esp_ip4_addr=" IPSTR, IP2STR(&esp_ip4_addr));

        esp_ip_addr_t esp_ip_addr; // 24 byte
        ESP_LOGD(TAG, "sizeof(esp_ip_addr_t)=%d", sizeof(esp_ip_addr_t));
        esp_ip_addr.u_addr.ip4 = esp_ip4_addr;
        ESP_LOGI(TAG, "esp_ip_addr.u_addr.ip4=" IPSTR, IP2STR(&esp_ip_addr.u_addr.ip4));

        const ip4_addr_t ip4_addr; // 4 byte
        ESP_LOGD(TAG, "sizeof(ip4_addr_t)=%d", sizeof(ip4_addr_t));
        memcpy((char *)&ip4_addr, (char *)&ip_info.ip, sizeof(ip4_addr));
        ESP_LOGI(TAG, "ip4_addr=%s", ip4addr_ntoa(&ip4_addr));

        ip_addr_t ip_addr; // 24 byte
        ESP_LOGD(TAG, "sizeof(ip_addr_t)=%d", sizeof(ip_addr_t));
        ip_addr.u_addr.ip4 = ip4_addr;
        ip_addr.type = IPADDR_TYPE_V4;
        ESP_LOGI(TAG, "ip_addr.u_addr.ip4=%s", ip4addr_ntoa(&ip_addr.u_addr.ip4));
}

結果は全て同じIPアドレスを表示します。
I (6093) IP: Connection success
I (6093) IP: ip_info.ip=192.168.10.148
I (6103) IP: ip_info.ip=192.168.10.148
I (6103) IP: esp_ip4_addr_get_byte(0)=192
I (6103) IP: esp_ip4_addr_get_byte(1)=168
I (6113) IP: esp_ip4_addr_get_byte(2)=10
I (6113) IP: esp_ip4_addr_get_byte(3)=148
I (6123) IP: esp_ip4_addr=192.168.10.148
I (6123) IP: esp_ip_addr.u_addr.ip4=192.168.10.148
I (6133) IP: ip4_addr=192.168.10.148
I (6133) IP: ip_addr.u_addr.ip4=192.168.10.148

続く...