土鍋で雑多煮

UnityでXR・ゲーム開発をしています。学んだことや備忘録、趣味の記録などを書いていきます。

Unityと複数台のESP32間でUDP通信をする

どうも、土鍋です。

IVRCに向けた開発を行っている際に使用した「Unityと複数台のESP32間でのUDP通信」の知見をまとめました~。

Unityがサーバー側、ESP32がクライアント側で、Unityからデータを全部のESP32に向けて送信しています。また、今回のIVRC作品では使用しませんでしたが、一応ESP32側からのデータの受信もできます。
ルーターIPアドレスの固定を行ってください。

ちなみに、ESP32側のコードは@suzakutakumi3 にお願いしました。

github.com

サーバー側(Unity)

UniTaskで非同期処理を行っていて、一応送受信できる設計になっています。

using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;

namespace UDP
{
    public class UDPServer
    {
        private UdpClient udpClient;

        public UDPServer(int port)
        {
            udpClient = new UdpClient(port);
        }
        
        public async UniTaskVoid Send(string data, IPEndPoint endPoint, CancellationToken token = default)
        {
            byte[] message = Encoding.UTF8.GetBytes(data);
            await udpClient.SendAsync(message, message.Length, endPoint);
            Debug.Log("Send:\"" + data + "\" To:" + endPoint.Address + "," + endPoint.Port);
        }
        
        public async UniTask<string> Receive(CancellationToken token = default)
        {
            var result = await udpClient.ReceiveAsync();
            IPEndPoint endPoint = result.RemoteEndPoint;
            Debug.Log("Receive:" + endPoint.Address + "," + endPoint.Port);
            byte[] getByte = result.Buffer;
            string data = Encoding.UTF8.GetString(getByte);
            return data;
        }
    }
}

Sendメソッドはstring型で送信データ、IPEndPoint型で送信先を設定して送信します。 Receiveメソッドはデータを受け取るまで待機して、データが送られてきたらstringをリターンします。

クライアント側(ESP32)

UDPLibrary.h

#pragma once
#include <WiFi.h>
#include <WiFiUdp.h>

class UDPLib {
  private:
    WiFiUDP wifiUdp;
    String host;
    int send_port;
    int receive_port;
  public:
    void begin(String ip, int send_p, int rec_p);
    void send(String);
    String read();
};

UDPLibrary.ino

#include "UDPLibrary.h"

void UDPLib::begin(String ip, int send_p, int rec_p) {
  host = ip;
  send_port = send_p;
  receive_port = rec_p;
  wifiUdp.begin(receive_port);
}

void UDPLib::send(String text) {
  byte Data[256];
  char hostC[256];
  host.toCharArray(hostC,text.length());
  Serial.println(hostC);
  text.getBytes(Data, text.length() + 1);
  wifiUdp.beginPacket(hostC, send_port);
  wifiUdp.write(Data, text.length() + 1); //10進数のASCiiで送信される
  wifiUdp.endPacket();
}

String UDPLib::read() {
  char x[512] = "";
  if (wifiUdp.parsePacket()) {
    for (int i = 0; i < 512; i++) {
      int v = wifiUdp.read();
      if (v == -1) {
        x[i] = '\0';
        break;
      }
      x[i] = v;
    }
  }
  String r=x;
  return r;
}

テスト用コード

以下はテスト用に作成したコードです。
適当な空のオブジェクト作ってスクリプトを貼っつけてください。

サーバー側

using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.InputSystem;


namespace UDP
{
    public class UdpTest : MonoBehaviour
    {
        [SerializeField]
        private string _sendData = "test";
        [SerializeField]
        private List<string> address;
        [SerializeField]
        private int receivePort = 9000;
        [SerializeField]
        private int sendPort = 9001;
        
        private UDPServer _udp;
        private List<IPEndPoint> _ipEndPoints = new List<IPEndPoint>();

        private void Start()
        {
            _udp = new UDPServer(receivePort);
            
            var token = this.GetCancellationTokenOnDestroy();
            WaitReceive(token).Forget();

            for (int i = 0; i < address.Count; i++)
            {
                _ipEndPoints.Add(new IPEndPoint(IPAddress.Parse(address[i]),sendPort));
            }
        }

        private void Update()
        {
            if (Keyboard.current.digit0Key.wasPressedThisFrame)
            {
                _udp.Send(_sendData,_ipEndPoints[0]).Forget();
            }
            if (Keyboard.current.digit1Key.wasPressedThisFrame)
            {
                _udp.Send(_sendData,_ipEndPoints[1]).Forget();
            }
            if (Keyboard.current.digit2Key.wasPressedThisFrame)
            {
                _udp.Send(_sendData,_ipEndPoints[2]).Forget();
            }
        }
        
        async UniTaskVoid WaitReceive(CancellationToken token)
        {
            while (!token.IsCancellationRequested)
            {
                string data = await _udp.Receive(token);
                Debug.Log(data);
                //await UniTask.Delay(TimeSpan.FromSeconds(3), cancellationToken: token);
            }
        }
    }
}

インスペクターで送信先のESP32のIPアドレスを設定します。 このテスト用コードでは個別にキーを押して送信していますが、実際使用した際はfor文回して全ESP32に同じデータを送信していました。

クライアント側

#include"UDPLibrary.h"

UDPLib _udp;

char* ssid="ssid";
char* password="pass";

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  while ( WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println("connected "+String(ssid));
  Serial.println(WiFi.localIP());
  _udp.begin("192.168.0.32",9000,9001);
}

void loop() {
  if(Serial.available()>0){
    String text=Serial.readStringUntil('\n');
    _udp.send(text);
  }
  String r=_udp.read();
  if(r!=""){
    Serial.println(r);
  }
}

Arduino IDEのシリアルモニタでデータの確認できます。

参考記事

【Unity】UDP通信する|Hiko|note

C# UdpClientの使い方解説 - Qiita