nRF Connect SDK:nRF7002を動かそう

# Nordic TechTourでもらった評価ボード
少し前にNordic TechTourというセミナーがありました。参加した人たちはお土産(？)でnRF7002DKをもらったと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340602/1351d127-476a-624e-b169-c858026e25d1.png)
ちなみに配布した理由は「技適を取っていないので公には使えないから」ということですので、くれぐれも自己責任で使いましょう。参考までに届け出を出すことで180日までは技適のない装置も使用することが可能です。

https://www.soumu.go.jp/main_content/000859884.pdf

# サンプルはあるけど…
nRF7002に対応したのがSDK v2.3.0からだということで、TechTourではv2.3.0をインストールするような説明がありました。サンプルプロジェクトも一通りは揃っているように思えるのですが、残念ながらどれもちゃんとWiFiで一通りの通信をするには何かが不足しています。また、v2.4.2まではそもそもTCPに致命的なエラーがあって、TCPを動かそうとするとFatal Errorで止まってしまうという問題もあって非常に苦労しました。

::: note
v2.5.0で解消しました…が今回はTCPでは動かしません。
:::

## Zephyrサンプルは役に立たない
DevZoneによるとZephyrベースのサンプルプロジェクトはどれもNordicでは未確認ということだそうです。ちなみに僕も動かそうとしてみましたが諦めました(笑)。おそらくまともには動かないと思います。
# 接続から通信まで
ちょっと長くなりますが、接続から通信までを行うプロジェクトを以下に掲載します。ボタン1を押すと接続して通信が始まります。ボタン2を押すと切断するのですが使ったことがないので分かりません(笑)。

``` main.c
/*
 * Copyright (c) 2012-2014 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <dk_buttons_and_leds.h>
#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/wifi_mgmt.h>

LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);

#define NET_EVENT_WIFI_EVENTS (			\
	NET_EVENT_WIFI_SCAN_RESULT			|	\
	NET_EVENT_WIFI_SCAN_DONE			|	\
	NET_EVENT_WIFI_CONNECT_RESULT		|	\
	NET_EVENT_WIFI_DISCONNECT_RESULT	|	\
	NET_EVENT_WIFI_IFACE_STATUS			|	\
	NET_EVENT_WIFI_TWT					|	\
	NET_EVENT_WIFI_TWT_SLEEP_STATE		|	\
	NET_EVENT_WIFI_RAW_SCAN_RESULT		|	\
	NET_EVENT_WIFI_DISCONNECT_COMPLETE		\
)

#define NET_EVENT_IPV4_EVENTS (				\
	NET_EVENT_IPV4_ADDR_ADD		|			\
	NET_EVENT_IPV4_ADDR_DEL		|			\
	NET_EVENT_IPV4_MADDR_ADD	|			\
	NET_EVENT_IPV4_MADDR_DEL	|			\
	NET_EVENT_IPV4_ROUTER_ADD	|			\
	NET_EVENT_IPV4_ROUTER_DEL	|			\
	NET_EVENT_IPV4_DHCP_START	|			\
	NET_EVENT_IPV4_DHCP_BOUND	|			\
	NET_EVENT_IPV4_DHCP_STOP	|			\
	NET_EVENT_IPV4_MCAST_JOIN	|			\
	NET_EVENT_IPV4_MCAST_LEAVE				\
)

// for User queue(s)
static struct k_work_q uq_button;
K_THREAD_STACK_DEFINE(us_button, 512);
// Semaphore(s)
K_SEM_DEFINE(sem_udp_send, 0, 1);
K_SEM_DEFINE(sem_udp_send_wait, 0, 1);

// Variable(s)
static struct net_mgmt_event_callback wifi_shell_mgmt_cb;
static struct net_mgmt_event_callback net_shell_mgmt_cb;
static struct net_context *udp_ctx;
static char *host = "xxx.xxx.xxx.xxx";
static uint16_t port = 12345;
static uint8_t packet[32];

/* ----- Timer Function Start ----- */
static void udp_rcvd(struct net_context *context, struct net_pkt *pkt, union net_ip_header *ip_hdr, union net_proto_header *proto_hdr, int status, void *user_data)
{
	if (pkt) {
		size_t len = net_pkt_remaining_data(pkt);
		uint8_t byte;

		LOG_INF("Received UDP packet: ");
		for (size_t i = 0; i < len; ++i) {
			net_pkt_read_u8(pkt, &byte);
			LOG_INF("%02x ", byte);
		}

		net_pkt_unref(pkt);
	}
}

static void udp_sent(struct net_context *context, int status, void *user_data)
{
	ARG_UNUSED(context);
	ARG_UNUSED(status);
	ARG_UNUSED(user_data);

	LOG_DBG("Message sent");
	k_sem_give(&sem_udp_send_wait);
}

static void timer_handler(struct k_timer *timer)
{
    // Send
	k_sem_give(&sem_udp_send);
}
K_TIMER_DEFINE(timer, timer_handler, NULL);
/* ----- Timer Function End ----- */

static void wifi_disconnect_work_handler(struct k_work *work)
{
	struct net_if *iface;

	// Parameter(s)
	iface = net_if_get_default();

	// Connect
	if (net_mgmt(NET_REQUEST_WIFI_CONNECT, iface, NULL, 0)) {
		LOG_ERR("Disconnection request failed.");
	}
}
K_WORK_DELAYABLE_DEFINE(wifi_disconnect, wifi_disconnect_work_handler);

static void wifi_connect_work_handler(struct k_work *work)
{
	struct net_if *iface;
	struct wifi_connect_req_params cnx_params;

	// if (context.connected) {
	// 	LOG_WRN("Already connected.");
	// 	return;
	// }

	// Parameter(s)
	iface = net_if_get_default();
	cnx_params.timeout = 30;
	cnx_params.ssid = "ssid";
	cnx_params.ssid_length = strlen(cnx_params.ssid);
	cnx_params.security = WIFI_SECURITY_TYPE_PSK;
	cnx_params.psk = "password";
	cnx_params.psk_length = strlen(cnx_params.psk);
	cnx_params.channel = WIFI_CHANNEL_ANY;
	cnx_params.mfp = WIFI_MFP_OPTIONAL;

	// Connect
	if (net_mgmt(NET_REQUEST_WIFI_CONNECT, iface, &cnx_params, sizeof(struct wifi_connect_req_params))) {
		LOG_ERR("Connection request failed.");
	}
}
K_WORK_DELAYABLE_DEFINE(wifi_connect, wifi_connect_work_handler);

static void print_dhcp_ip(struct net_mgmt_event_callback *cb)
{
	/* Get DHCP info from struct net_if_dhcpv4 and print */
	const struct net_if_dhcpv4 *dhcpv4 = cb->info;
	const struct in_addr *addr = &dhcpv4->requested_ip;
	char dhcp_info[128];

	net_addr_ntop(AF_INET, addr, dhcp_info, sizeof(dhcp_info));
	LOG_INF("DHCP IP address: %s", dhcp_info);
}

static void net_mgmt_event_handler(struct net_mgmt_event_callback *cb, uint32_t mgmt_event, struct net_if *iface)
{
	switch (mgmt_event) {
        case NET_EVENT_IPV4_ADDR_ADD:
        LOG_INF("NET_EVENT_IPV4_ADDR_ADD");
        break;

        case NET_EVENT_IPV4_ADDR_DEL:
        LOG_INF("NET_EVENT_IPV4_ADDR_DEL");
        break;

        case NET_EVENT_IPV4_MADDR_ADD:
        LOG_INF("NET_EVENT_IPV4_MADDR_ADD");
        break;

        case NET_EVENT_IPV4_MADDR_DEL:
        LOG_INF("NET_EVENT_IPV4_MADDR_DEL");
        break;

        case NET_EVENT_IPV4_ROUTER_ADD:
        LOG_INF("NET_EVENT_IPV4_ROUTER_ADD");
        break;

        case NET_EVENT_IPV4_ROUTER_DEL:
        LOG_INF("NET_EVENT_IPV4_ROUTER_DEL");
        break;

        case NET_EVENT_IPV4_DHCP_START:
        LOG_INF("NET_EVENT_IPV4_DHCP_START");
        break;

        case NET_EVENT_IPV4_DHCP_BOUND:
        LOG_INF("NET_EVENT_IPV4_DHCP_BOUND");
		print_dhcp_ip(cb);
        k_timer_start(&timer, K_NO_WAIT, K_MSEC(100));
		break;

        case NET_EVENT_IPV4_DHCP_STOP:
        LOG_INF("NET_EVENT_IPV4_DHCP_STOP");
        break;

        case NET_EVENT_IPV4_MCAST_JOIN:
        LOG_INF("NET_EVENT_IPV4_MCAST_JOIN");
        break;

        case NET_EVENT_IPV4_MCAST_LEAVE:
        LOG_INF("NET_EVENT_IPV4_MCAST_LEAVE");
        break;

        default:
        LOG_INF("NET_EVENT_IPV4_OTHER");
		break;
	}
}

static void handle_wifi_disconnect_result(struct net_mgmt_event_callback *cb)
{
	const struct wifi_status *status = (const struct wifi_status *) cb->info;

    LOG_INF("Disconnection request %s (%d)", status->status ? "failed" : "done", status->status);
}

static void handle_wifi_connect_result(struct net_mgmt_event_callback *cb)
{
	const struct wifi_status *status = (const struct wifi_status *) cb->info;

	if (status->status) {
		LOG_ERR("Connection failed.(%d)", status->status);
	} else {
		LOG_INF("Connected");
	}
}

// Callback
static void wifi_mgmt_event_handler(struct net_mgmt_event_callback *cb, uint32_t mgmt_event, struct net_if *iface)
{
	switch (mgmt_event) {
        case NET_EVENT_WIFI_SCAN_RESULT:
        LOG_INF("NET_EVENT_WIFI_SCAN_RESULT");
        break;

        case NET_EVENT_WIFI_SCAN_DONE:
        LOG_INF("NET_EVENT_WIFI_SCAN_DONE");
        break;

        case NET_EVENT_WIFI_CONNECT_RESULT:
        LOG_INF("NET_EVENT_WIFI_CONNECT_RESULT");
        handle_wifi_connect_result(cb);
        break;

        case NET_EVENT_WIFI_DISCONNECT_RESULT:
        LOG_INF("NET_EVENT_WIFI_DISCONNECT_RESULT");
		handle_wifi_disconnect_result(cb);
        break;

        case NET_EVENT_WIFI_IFACE_STATUS:
        LOG_INF("NET_EVENT_WIFI_IFACE_STATUS");
        break;

        case NET_EVENT_WIFI_TWT:
        LOG_INF("NET_EVENT_WIFI_TWT");
        break;

        case NET_EVENT_WIFI_TWT_SLEEP_STATE:
        LOG_INF("NET_EVENT_WIFI_TWT_SLEEP_STATE");
        break;

        case NET_EVENT_WIFI_RAW_SCAN_RESULT:
        LOG_INF("NET_EVENT_WIFI_RAW_SCAN_RESULT");
        break;

        case NET_EVENT_WIFI_DISCONNECT_COMPLETE:
        LOG_INF("NET_EVENT_WIFI_DISCONNECT_COMPLETE");
        break;

        default:
        LOG_WRN("NET_EVENT_WIFI_OTHER");
        break;
	}
}

static void button_holding_work_handler(struct k_work *work)
{
    LOG_INF("Button holding!");
}
K_WORK_DELAYABLE_DEFINE(button_holding, button_holding_work_handler);

void button_changed(uint32_t button_state, uint32_t has_changed)
{
    if (has_changed & 0x01) {
        if (button_state & has_changed) {
            LOG_DBG("button 0 changes to on");
			// WiFi connect
			k_work_schedule(&wifi_connect, K_NO_WAIT);
            // Long push
            k_work_schedule_for_queue(&uq_button, &button_holding, K_MSEC(2000));
        } else {
            LOG_DBG("button 0 changes to off.");
            k_work_cancel_delayable(&button_holding);
        }
    }

#if DT_NODE_EXISTS(DT_ALIAS(sw1))
    if (has_changed & 0x02) {
        if (button_state & has_changed) {
            LOG_DBG("button 1 changes to on");
			// WiFi disconnect
			k_work_schedule(&wifi_disconnect, K_NO_WAIT);
            // Long push
            k_work_schedule_for_queue(&uq_button, &button_holding, K_MSEC(2000));
        } else {
            LOG_DBG("button 1 changes to off.");
            k_work_cancel_delayable(&button_holding);
        }
    }
#endif

#if DT_NODE_EXISTS(DT_ALIAS(sw2))
    if (has_changed & 0x04) {
        if (button_state & has_changed) {
            LOG_INF("button 2 changes to on");
        } else {
            LOG_INF("button 2 changes to off.");
        }
    }
#endif

#if DT_NODE_EXISTS(DT_ALIAS(sw3))
    if (has_changed & 0x08) {
        if (button_state & has_changed) {
            LOG_INF("button 3 changes to on");
        } else {
            LOG_INF("button 3 changes to off.");
        }
    }
#endif
}

int main(void)
{
    int err, ret;
    struct net_if *iface;
    struct sockaddr addr;
    int addrlen;

    // Initialize variable(s)
    memset(packet, 0x31, sizeof(packet));

    // Initialize Buttons and LEDs
    err = dk_buttons_init(button_changed);
    if (err) {
        LOG_ERR("Failed to initialize button[s].(%d)", err);
        return -1;
    }
    err = dk_leds_init();
    if (err) {
        LOG_ERR("Failed to initialize led[s].(%d)", err);
        return -1;
    }

	// User queue for button holding
    k_work_queue_start(&uq_button, us_button, K_THREAD_STACK_SIZEOF(us_button), K_PRIO_PREEMPT(0), NULL);

    // WiFi callback
	net_mgmt_init_event_callback(&wifi_shell_mgmt_cb, wifi_mgmt_event_handler, NET_EVENT_WIFI_EVENTS);
	net_mgmt_add_event_callback(&wifi_shell_mgmt_cb);
	net_mgmt_init_event_callback(&net_shell_mgmt_cb, net_mgmt_event_handler, NET_EVENT_IPV4_EVENTS);
	net_mgmt_add_event_callback(&net_shell_mgmt_cb);

    while (true) {
        if (!k_sem_take(&sem_udp_send, K_MSEC(500))) {
            if (udp_ctx && net_context_is_used(udp_ctx)) {
                LOG_WRN("Network context already in use");
                continue;
            }

            memset(&addr, 0, sizeof(addr));
            ret = net_ipaddr_parse(host, strlen(host), &addr);
            if (ret < 0) {
                LOG_WRN("Cannot parse address \"%s\"", host);
                continue;
            }

            ret = net_context_get(addr.sa_family, SOCK_DGRAM, IPPROTO_UDP, &udp_ctx);
            if (ret < 0) {
                LOG_WRN("Cannot get UDP context.(%d)", ret);
                continue;
            }

            if (IS_ENABLED(CONFIG_NET_IPV4) && addr.sa_family == AF_INET) {
                net_sin(&addr)->sin_port = htons(port);
                addrlen = sizeof(struct sockaddr_in);

                iface = net_if_ipv4_select_src_iface(&net_sin(&addr)->sin_addr);
            } else {
                LOG_WRN("IPv4 is disabled, cannot %s.", "send");
                goto release_ctx;
            }

            if (!iface) {
                LOG_WRN("No interface to send to given host");
                goto release_ctx;
            }

            net_context_set_iface(udp_ctx, iface);

            ret = net_context_recv(udp_ctx, udp_rcvd, K_NO_WAIT, NULL);
            if (ret < 0) {
                LOG_WRN("Setting rcv callback failed (%d)", ret);
                goto release_ctx;
            }

            ret = net_context_sendto(udp_ctx, packet, sizeof(packet), &addr, addrlen, udp_sent, K_FOREVER, NULL);
            if (ret < 0) {
                LOG_WRN("Sending packet failed (%d)", ret);
                goto release_ctx;
            }

            ret = k_sem_take(&sem_udp_send_wait, K_SECONDS(2));
            if (ret == -EAGAIN) {
                LOG_WRN("UDP packet sending failed");
            }

release_ctx:
            ret = net_context_put(udp_ctx);
            if (ret < 0) {
                LOG_WRN("Cannot put UDP context (%d)", ret);
            }
        }
    }
}
```

``` prj.conf
CONFIG_WIFI=y
CONFIG_NET_L2_ETHERNET=y
CONFIG_NET_IPV6=n
CONFIG_HEAP_MEM_POOL_SIZE=153600
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
CONFIG_WPA_SUPP=y
CONFIG_WPA_SUPP_ADVANCED_FEATURES=n
CONFIG_WPA_SUPP_WPA3=n
CONFIG_DK_LIBRARY=y
CONFIG_WIFI_NRF700X=y
CONFIG_NET_TX_STACK_SIZE=8192
CONFIG_NET_RX_STACK_SIZE=8192
CONFIG_USE_SEGGER_RTT=y
CONFIG_FLASH=y
CONFIG_COMMON_LIBC_MALLOC=n
CONFIG_DEBUG_THREAD_INFO=y
CONFIG_NVS=y
CONFIG_LOG_PROCESS_THREAD_SLEEP_MS=100
CONFIG_NETWORKING=y
CONFIG_NET_IPV4=y
CONFIG_NET_DHCPV4=y
CONFIG_NET_TCP=y
CONFIG_NET_LOG=y
CONFIG_NET_CONN_LOG_LEVEL_INF=y
CONFIG_NET_CONFIG_INIT_TIMEOUT=0
CONFIG_NET_CONFIG_SETTINGS=y
CONFIG_SETTINGS=y
CONFIG_FLASH_MAP=y
CONFIG_DEBUG_OPTIMIZATIONS=y
```

設定が必要な場所が2ヶ所あります。一つは冒頭にある送信先IPアドレスおよびポート番号で、もう一つはソースコード中ほどにあるSSIDとパスワードを埋め込むところです。この2つは自身の環境に合わせて手を加える必要があります。
## 送信先IPアドレス
この記事を読んでいる人はこんな説明が必要な初心者とも思えないのですが、念のため説明しておきます。送信先のIPアドレスはほとんどの場合、この作業をしているPCになると思います。PCのIPアドレスはいろんな確認手段がありますが、コマンドプロンプトからipconfig /allと入力して確認するのが確実です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340602/b82f3e1e-0e94-667e-15a7-3e7453e31a10.png)


# UDPで送信しているのは分かったけれど…
冒頭でTCPは使わないと書いたのは、TCPはソケットを作ってバインドしてリッスンして…という長い手続きがあってめんどくさいのでその必要がないUDPを使ったほうがいいと思ったからです。
・・・。
・・・ん？
いやいやいや、ちょっと待てよ。TCPだろうがUDPだろうが受信側がないと本当に送信できているか分からないじゃん！と思ったあなたは正しいです(笑)。
でもそんなことのためだけにWindowsアプリとか作るのって大変ですよね…。でも今はPythonというと～っても便利なものがあったりします。

``` udp_server.py
import socket
import threading
import time

def receive_data():
    while True:
        data, addr = udp_socket.recvfrom(1024)
        message = data.decode()
        print(f"受信したデータ: {message}")
        print(f"送信元アドレス: {addr}")

listen_ip = ""
listen_port = 12345
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.bind((listen_ip, listen_port))

receiver_thread = threading.Thread(target=receive_data)
receiver_thread.daemon = True
receiver_thread.start()

# ここでメインスレッドで他の処理を実行できます
while True:
    try:
#        print("Thread: " + str(receiver_thread.is_alive()))
        time.sleep(0.5)
        pass

    except KeyboardInterrupt:
        print('Interrupted.')
        break
```
はい、たったこれだけです。listen_ipは空っぽでよいです。listen_portはファームウェア側で設定したポートと同じ番号にします。ポート番号が一致していないと受信できません。
ちなみにこのPythonのUDP受信コードはほとんど全部ChatGPTに作らせました(笑)。

::: note
スレッドがデーモンじゃないとCTRL+Cで中断しても終わらない、というところだけ修正しています
:::

ボタン1を押すとOKといっぱい出たのちに接続が完了します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340602/94d05c7d-5871-b9f2-b0e7-fc3f39c93ef9.png)
Python側も起動して待ち構えていますが…何も送られてこない…？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340602/042e538f-f982-4838-4d13-1f43609050a9.png)

## ウィルス監視ソフトがある人はオフにしよう
特に何もせずに通信が始まる人もいると思いますが、僕のように「あれ？受信していないのか…？」と思う人もいると思います。受信ができていない人は高確率でウィルス検出ソフトのファイヤーウォールに引っかかっていると思われます。
こんな感じでファイヤーウォールを一時的にオフにしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340602/4dcef3e8-1968-401b-cbc3-4088b98bec60.png)
# 受信ｷﾀ━━━━(ﾟ∀ﾟ)━━━━!!
無事受信できるようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340602/f4bf8e1b-b342-21a2-53ff-d97b45f5060e.png)