ESP-IDFを使ってみる

BSD Socket API


esp-idfではSocket API(BSD Socket API)を使うことができます。
BSD Socket APIはLinuxで標準的に使われるネットワークAPIです。
Linux用のSocketアプリが何も変更せずに動きます。

こちらがLinuxで動くTCP Server(tcp-server.c)のソースです。
クライアントから通知された文字列を小文字->大文字、大文字->小文字に変換して応答します。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>

#define SERVER_PORT 8080

int main(void){
        /* set up address to connect to */
        struct sockaddr_in srcAddr;
        struct sockaddr_in dstAddr;
        memset(&srcAddr, 0, sizeof(srcAddr));
        //srcAddr.sin_len = sizeof(srcAddr);
        srcAddr.sin_family = AF_INET;
        //srcAddr.sin_port = PP_HTONS(SERVER_PORT);
        srcAddr.sin_port = htons(SERVER_PORT);
        srcAddr.sin_addr.s_addr = INADDR_ANY;

        /* create the socket */
        int srcSocket;
        int dstSocket;
        socklen_t dstAddrSize;
        int ret;
        int numrcv;

        srcSocket = socket(AF_INET, SOCK_STREAM, 0);
        assert(srcSocket >= 0);

        /* bind socket */
        ret = bind(srcSocket, (struct sockaddr *)&srcAddr, sizeof(srcAddr));
        /* should succeed */
        assert(ret == 0);

        /* listen socket */
        ret = listen(srcSocket, 5);
        /* should succeed */
        assert(ret == 0);

        char buf[1024];
        while(1) {
                // 接続の受付け
                printf("接続を待っています クライアントプログラムを動かして下さい\n");
                dstAddrSize = sizeof(dstAddr);
                dstSocket = accept(srcSocket, (struct sockaddr *)&dstAddr, &dstAddrSize);
                printf("%s から接続を受けました\n", inet_ntoa(dstAddr.sin_addr));

                while(1) { // クライアントがSocketをクローズしてからこちらもクローズする
                        /* read something */
                        memset(buf,0,sizeof(buf));
                        numrcv = read(dstSocket, buf, sizeof(buf));
                        printf("numrcv=%d\n", numrcv);
                        if(numrcv ==0 || numrcv ==-1 ){ // client close socket
                                close(dstSocket); break;
                        }
                        printf("Recv=[%s]",buf);

                        for (int i=0; i< numrcv; i++){ // bufの中の小文字を大文字に変換
                                if(isalpha((int)buf[i])) {
                                        if(islower((int)buf[i])) {
                                                buf[i] = toupper((int)buf[i]);
                                        } else {
                                                buf[i] = tolower((int)buf[i]);
                                        }
                                }
                        }
                        /* write something */
                        ret = write(dstSocket, buf, numrcv);
                        assert(ret == numrcv);
                        printf("->Send=[%s]\n",buf);
                } // end while
        } // end for


        /* close (never come here) */
        ret = close(srcSocket);
        assert(ret == 0);
}

こちらがLinuxで動くTCP Client(tcp-client.c)のソースです。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>

#define SERVER_IP "192.168.10.42"
#define SERVER_PORT 8080

int main(void){
        /* set up address to connect to */
        struct sockaddr_in addr;
        memset(&addr, 0, sizeof(addr));
        //addr.sin_len = sizeof(addr);
        addr.sin_family = AF_INET;
        //addr.sin_port = PP_HTONS(SERVER_PORT);
        addr.sin_port = htons(SERVER_PORT);
        addr.sin_addr.s_addr = inet_addr(SERVER_IP);

        /* create the socket */
        int fd;
        int ret;
        fd = socket(AF_INET, SOCK_STREAM, 0);
        assert(fd >= 0);

        /* connect */
        ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));
        /* should succeed */
        assert(ret == 0);

        for (int i=0;i<10;i++) {
                /* write something */
                char txBuf[32];
                memset(txBuf, 0, 32);
                memset(txBuf, 0x61 + i, 16);
                ret = write(fd, txBuf, strlen(txBuf));
                assert(ret == strlen(txBuf));
                printf("write ret=%d\n",ret);

                /* read something */
                char rxBuf[32];
                memset(rxBuf,0,sizeof(rxBuf));
                ret = read(fd, rxBuf, sizeof(rxBuf));
                assert(ret > 0);
                printf("read ret=%d\n",ret);
                if (ret > 0) {
                        printf("ret=%d\n", ret);
                        printf("%s --> %s\n", txBuf, rxBuf);
                }
        }

        /* close */
        ret = close(fd);
        assert(ret == 0);
}

サーバーを起動した状態でクライアントを起動すると、このような表示になります。
$ ./tcp-client
write ret=16
read ret=16
ret=16
aaaaaaaaaaaaaaaa --> AAAAAAAAAAAAAAAA
write ret=16
read ret=16
ret=16
bbbbbbbbbbbbbbbb --> BBBBBBBBBBBBBBBB
write ret=16
read ret=16
ret=16
cccccccccccccccc --> CCCCCCCCCCCCCCCC
write ret=16
read ret=16
ret=16
dddddddddddddddd --> DDDDDDDDDDDDDDDD
write ret=16
read ret=16
ret=16
eeeeeeeeeeeeeeee --> EEEEEEEEEEEEEEEE
write ret=16
read ret=16
ret=16
ffffffffffffffff --> FFFFFFFFFFFFFFFF
write ret=16
read ret=16
ret=16
gggggggggggggggg --> GGGGGGGGGGGGGGGG
write ret=16
read ret=16
ret=16
hhhhhhhhhhhhhhhh --> HHHHHHHHHHHHHHHH
write ret=16
read ret=16
ret=16
iiiiiiiiiiiiiiii --> IIIIIIIIIIIIIIII
write ret=16
read ret=16
ret=16
jjjjjjjjjjjjjjjj --> JJJJJJJJJJJJJJJJ

クライアント側をesp-idfに移植するとこの様になります。
task関数はLinuxのソースのまま変更していません。
WiFi接続に必要なヘッダーファイルと、main関数としてWiFi接続のコードを追加しています。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"

#define SERVER_IP "192.168.10.42"
#define SERVER_PORT 8080

void task(void *pvParameter)
{
        /* set up address to connect to */
        struct sockaddr_in addr;
        memset(&addr, 0, sizeof(addr));
        //addr.sin_len = sizeof(addr);
        addr.sin_family = AF_INET;
        //addr.sin_port = PP_HTONS(SERVER_PORT);
        addr.sin_port = htons(SERVER_PORT);
        addr.sin_addr.s_addr = inet_addr(SERVER_IP);

        /* create the socket */
        int fd = socket(AF_INET, SOCK_STREAM, 0);
        assert(fd >= 0);

        /* connect */
        int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));
        /* should succeed */
        assert(ret == 0);

        for (int i=0;i<10;i++) {
                /* write something */
                char txBuf[32];
                memset(txBuf, 0, 32);
                memset(txBuf, 0x61 + i, 16);
                ret = write(fd, txBuf, strlen(txBuf));
                assert(ret == strlen(txBuf));
                printf("write ret=%d\n",ret);

                /* read something */
                char rxBuf[32];
                memset(rxBuf,0,sizeof(rxBuf));
                ret = read(fd, rxBuf, sizeof(rxBuf));
                assert(ret > 0);
                printf("read ret=%d\n",ret);
                if (ret > 0) {
                        printf("ret=%d\n", ret);
                        printf("%s --> %s\n", txBuf, rxBuf);
                }
        }

        /* close */
        ret = close(fd);
        assert(ret == 0);
        vTaskDelete( NULL );
}

void app_main()
{
        ESP_ERROR_CHECK( nvs_flash_init() );
        ESP_ERROR_CHECK(esp_netif_init());
        ESP_ERROR_CHECK(esp_event_loop_create_default());

        /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
         * Read "Establishing Wi-Fi or Ethernet Connection" section in
         * examples/protocols/README.md for more information about this function.
         */
        ESP_ERROR_CHECK(example_connect());

        xTaskCreate(&task, "TASK", 1024*4, NULL, 5, NULL);
}

esp-idfでビルドするとこの様な表示になります。
一瞬でLinuxのアプリがesp-idfで動きます。
但し、printf()はマルチタスクの環境では使えません。
複数のタスクが同時に出力すると、表示が混じります。
ESP_LOGI()などのロギング関数はマルチタスクに対応しているので、printf()はESP_LOGI()に変更する必要が有ります。
I (0) cpu_start: App cpu up.
I (435) cpu_start: Pro cpu start user code
I (436) cpu_start: cpu freq: 160000000 Hz
I (436) cpu_start: Application information:
I (440) cpu_start: Project name:     test
I (445) cpu_start: App version:      1
I (449) cpu_start: Compile time:     Feb 20 2024 20:22:53
I (455) cpu_start: ELF file SHA256:  082f7f7c0a8159cb...
I (461) cpu_start: ESP-IDF:          v5.1.2-602-gdb1e54a0c5-dirty
I (468) cpu_start: Min chip rev:     v0.0
I (473) cpu_start: Max chip rev:     v3.99
I (478) cpu_start: Chip rev:         v3.0
I (483) heap_init: Initializing. RAM available for dynamic allocation:
I (490) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (496) heap_init: At 3FFB7B68 len 00028498 (161 KiB): DRAM
I (502) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (508) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (515) heap_init: At 40095F40 len 0000A0C0 (40 KiB): IRAM
I (522) spi_flash: detected chip: generic
I (526) spi_flash: flash io: dio
W (530) spi_flash: Detected size(4096k) larger than the size in the binary image header(2048k). Using the size in the binary image header.
I (543) app_start: Starting scheduler on CPU0
I (548) app_start: Starting scheduler on CPU1
I (548) main_task: Started on CPU0
I (558) main_task: Calling app_main()
I (588) example_connect: Start example_connect.
I (598) wifi:wifi driver task: 3ffbf814, prio:23, stack:6656, core=0
I (608) wifi:wifi firmware version: e309346
I (608) wifi:wifi certification version: v7.0
I (608) wifi:config NVS flash: enabled
I (608) wifi:config nano formating: disabled
I (618) wifi:Init data frame dynamic rx buffer num: 32
I (618) wifi:Init static rx mgmt buffer num: 5
I (628) wifi:Init management short buffer num: 32
I (628) wifi:Init dynamic tx buffer num: 32
I (638) wifi:Init static rx buffer size: 1600
I (638) wifi:Init static rx buffer num: 10
I (638) wifi:Init dynamic rx buffer num: 32
I (648) wifi_init: rx ba win: 6
I (648) wifi_init: tcpip mbox: 32
I (658) wifi_init: udp mbox: 6
I (658) wifi_init: tcp mbox: 6
I (658) wifi_init: tcp tx win: 5760
I (668) wifi_init: tcp rx win: 5760
I (668) wifi_init: tcp mss: 1440
I (678) wifi_init: WiFi IRAM OP enabled
I (678) wifi_init: WiFi RX IRAM OP enabled
I (688) phy_init: phy_version 4791,2c4672b,Dec 20 2023,16:06:06
I (768) wifi:mode : sta (b8:d6:1a:67:76:60)
I (768) wifi:enable tsf
I (778) example_connect: Connecting to aterm-d5a4ee-g...
I (778) example_connect: Waiting for IP(s)
I (3188) wifi:new:<1,0>, old:<1,0>, ap:<255,255>, sta:<1,0>, prof:1
I (3438) wifi:state: init -> auth (b0)
I (3438) wifi:state: auth -> assoc (0)
I (3448) wifi:Association refused temporarily, comeback time 1024 (TUs)
I (4498) wifi:state: assoc -> assoc (0)
I (5498) wifi:state: assoc -> init (200)
I (5508) wifi:new:<1,0>, old:<1,0>, ap:<255,255>, sta:<1,0>, prof:1
I (5508) example_connect: Wi-Fi disconnected, trying to reconnect...
I (7918) example_connect: Wi-Fi disconnected, trying to reconnect...
I (10338) wifi:new:<1,0>, old:<1,0>, ap:<255,255>, sta:<1,0>, prof:1
I (10338) wifi:state: init -> auth (b0)
I (10338) wifi:state: auth -> assoc (0)
I (10368) wifi:state: assoc -> run (10)
I (10478) wifi:connected with aterm-d5a4ee-g, aid = 6, channel 1, BW20, bssid = f8:b7:97:36:de:52
I (10478) wifi:security: WPA2-PSK, phy: bgn, rssi: -41
I (10478) wifi:pm start, type: 1

I (10478) wifi:dp: 1, bi: 102400, li: 3, scale listen interval from 307200 us to 307200 us
I (10538) wifi:AP's beacon interval = 102400 us, DTIM period = 1
I (11488) esp_netif_handlers: example_netif_sta ip: 192.168.10.110, mask: 255.255.255.0, gw: 192.168.10.1
I (11488) example_connect: Got IPv4 event: Interface "example_netif_sta" address: 192.168.10.110
I (11498) example_common: Connected to example_netif_sta
I (11498) example_common: - IPv4 address: 192.168.10.110,
I (11508) main_task: Returned from app_main()
write ret=16
read ret=16
ret=16
aaaaaaaaaaaaaaaa --> AAAAAAAAAAAAAAAA
write ret=16
read ret=16
ret=16
bbbbbbbbbbbbbbbb --> BBBBBBBBBBBBBBBB
write ret=16
read ret=16
ret=16
cccccccccccccccc --> CCCCCCCCCCCCCCCC
write ret=16
read ret=16
ret=16
dddddddddddddddd --> DDDDDDDDDDDDDDDD
write ret=16
read ret=16
ret=16
eeeeeeeeeeeeeeee --> EEEEEEEEEEEEEEEE
write ret=16
read ret=16
ret=16
ffffffffffffffff --> FFFFFFFFFFFFFFFF
write ret=16
read ret=16
ret=16
gggggggggggggggg --> GGGGGGGGGGGGGGGG
write ret=16
read ret=16
ret=16
hhhhhhhhhhhhhhhh --> HHHHHHHHHHHHHHHH
write ret=16
read ret=16
ret=16
iiiiiiiiiiiiiiii --> IIIIIIIIIIIIIIII
write ret=16
read ret=16
ret=16
jjjjjjjjjjjjjjjj --> JJJJJJJJJJJJJJJJ
I (12488) wifi:<ba-add>idx:0 (ifx:0, f8:b7:97:36:de:52), tid:5, ssn:0, winSize:64
I (12488) wifi:<ba-add>idx:1 (ifx:0, f8:b7:97:36:de:52), tid:0, ssn:18, winSize:64

続く....