ESP32のUDP通信メモ (AsyncUDP)
AsyncUDP
AsyncUDPはいくつかの派生バージョンがあるようだけど、これは追加インストールなしで使えるバージョンです。その名の通り、非同期で扱えるUDPクラスです。
受信処理はloop()とは別スレッドで非同期処理され、callback関数を指定して受信処理を行います。
ユニキャスト、ブロードキャスト、マルチキャストをサポートしています。
インスタンス毎にスレッドを起動しますので、インスタンスを複数作るだけで複数送受信をスッキリと記述できます。
あまりドキュメントが見つからなかったため、ソースなどを参照して作ったメモです。
利用方法
追加インストールは必要なし、ヘッダのincludeで使用できます。送受信を行うためのAsyncUDPクラスのインスタンスを生成して使います。
なお、送受信できるデータは1436バイトまで。エラー処理も行われないので、送れない/受け取れないこともあります。
#include <AsyncUDP.h>
AsyncUDP udp;
void setup(){}
void loop(){}
受信処理
#include <AsyncUDP.h>
AsyncUDP udp;
// 受信処理関数
void recvCB(AsyncUDPPacket& packet)
{
Serial.write(packet.data(), packet.length());
}
void setup()
{
//..WIFI等の初期化
if (udp.listen(IP_ANY_TYPE,1234)){ // すべてのIPからポート1234あてパケットを受け取る
udp.onPacket(recvCB); // 受信処理の指定
}
}
void loop()
{
// 別処理 (受信は非同期で行われる)
}
受信側に必要な処理は、以下のうちのどれか
bool listen(const ip_addr_t *addr, uint16_t port);
bool listen(const IPAddress addr, uint16_t port);
bool listen(const IPv6Address addr, uint16_t port);
bool listen(uint16_t port);
bool listenMulticast(const ip_addr_t *addr, uint16_t port, uint8_t ttl=1, tcpip_adapter_if_t tcpip_if=TCPIP_ADAPTER_IF_MAX);
bool listenMulticast(const IPAddress addr, uint16_t port, uint8_t ttl=1, tcpip_adapter_if_t tcpip_if=TCPIP_ADAPTER_IF_MAX);
bool listenMulticast(const IPv6Address addr, uint16_t port, uint8_t ttl=1, tcpip_adapter_if_t tcpip_if=TCPIP_ADAPTER_IF_MAX);
bool connect(const ip_addr_t *addr, uint16_t port);
bool connect(const IPAddress addr, uint16_t port);
bool connect(const IPv6Address addr, uint16_t port);
listen系:アドレスタイプでいくつかのバリエーション。アドレスをIP_ANY_TYPEにすると全てのアドレス宛のパケットを受け取る。
listen(port) は listen(IP_ANY_TYPE, port)のエイリアス。
listenMulticast系:マルチキャスト受け取り。受け取りアドレスにはマルチキャストアドレス(クラスD)を指定する。それ以外はエラーになる。
tcpip_if で使用するネットワークインターフェイスを指定できる。
TCPIP_ADAPTER_IF_STA = 0, /**< Wi-Fi STA (station) interface */
TCPIP_ADAPTER_IF_AP, /**< Wi-Fi soft-AP interface */
TCPIP_ADAPTER_IF_ETH, /**< Ethernet interface */
TCPIP_ADAPTER_IF_TEST, /**< tcpip stack test interface */
TCPIP_ADAPTER_IF_MAX
connect系 は特定のIPでの待うけ、特定の相手との接続。UDPではあまり使わないかも。(その場合TCPを使うと思う)
送信処理
AsyncUDPのインスタンスから送信系のメンバ関数を呼ぶ。
#include <AsyncUDP.h>
AsyncUDP udp;
void setup()
{
//..WIFI等の初期化
}
void loop()
{
udp.bloadcastTo("TEST",4,1234); // 'TEST'の4文字を 255.255.255.255 ポート1234でリミテッドブロードキャスト
udp.writeTo("TEST",4,IPAddress(192,168,255,255),1234); // 192.168.0.0/16 へのディレクティブブロードキャスト
// ここではloop()で処理しているが、例えば返信処理などは受信callbackの中で実行してもOK
}
送信系は以下のバリエーションがあります。
size_t writeTo(const uint8_t *data, size_t len, const ip_addr_t *addr, uint16_t port, tcpip_adapter_if_t tcpip_if=TCPIP_ADAPTER_IF_MAX);
size_t writeTo(const uint8_t *data, size_t len, const IPAddress addr, uint16_t port, tcpip_adapter_if_t tcpip_if=TCPIP_ADAPTER_IF_MAX);
size_t writeTo(const uint8_t *data, size_t len, const IPv6Address addr, uint16_t port, tcpip_adapter_if_t tcpip_if=TCPIP_ADAPTER_IF_MAX);
size_t write(const uint8_t *data, size_t len);
size_t write(uint8_t data);
size_t broadcastTo(uint8_t *data, size_t len, uint16_t port, tcpip_adapter_if_t tcpip_if=TCPIP_ADAPTER_IF_MAX);
size_t broadcastTo(const char * data, uint16_t port, tcpip_adapter_if_t tcpip_if=TCPIP_ADAPTER_IF_MAX);
size_t broadcast(uint8_t *data, size_t len);
size_t broadcast(const char * data);
size_t sendTo(AsyncUDPMessage &message, const ip_addr_t *addr, uint16_t port, tcpip_adapter_if_t tcpip_if=TCPIP_ADAPTER_IF_MAX);
size_t sendTo(AsyncUDPMessage &message, const IPAddress addr, uint16_t port, tcpip_adapter_if_t tcpip_if=TCPIP_ADAPTER_IF_MAX);
size_t sendTo(AsyncUDPMessage &message, const IPv6Address addr, uint16_t port, tcpip_adapter_if_t tcpip_if=TCPIP_ADAPTER_IF_MAX);
size_t send(AsyncUDPMessage &message);
size_t broadcastTo(AsyncUDPMessage &message, uint16_t port, tcpip_adapter_if_t tcpip_if=TCPIP_ADAPTER_IF_MAX);
size_t broadcast(AsyncUDPMessage &message);
ポートなしのメンバ関数は、内部的にポート番号を生成(または再利用)するようです。明確なルールを追いかけていませんが、気にせず省略するか、気になるなら明示的に指定します。
sendTo()とAsyncUDPMessage
sendTo()は、送信メッセージをバイト列+データ長で指定するのではなく、AsyncUDPMessageを経由しています。内部的にはAsyncUDPMessageの中にためられたバイト列をwriteTo()で送信しています。
AsyncUDPMessageはPrintクラスを継承しているので、sprintfなどを使わずに書式付き送信データが作成できるので便利な場合もあります。
loop()
{
AsyncUDPMessage msg; // サイズはUDPで送信できるデータのサイズ = 1436が確保される
msg.printf("Send Data is %f",3.1415); // 1436を超えた時はクリップしてくれる
udp.sendTo(msg,IP_ANY_TYPE,1234); // 送信サイズは内部で保持されている
}
AsyncUDPMessageはコンストラクタにデータ長を指定できるので、あえて1436未満のパケット長に制限することもできます。
使用コア
sdkconfig.hで下記の様に指定されています。分散されるわけではなく、全て同一コアで処理されるようです。loop()と同一コアなのがちょっと残念。
#define CONFIG_ARDUINO_UDP_RUN_CORE1 1
#define CONFIG_ARDUINO_UDP_TASK_PRIORITY 3
#define CONFIG_ARDUINO_UDP_RUNNING_CORE 1