HoloLens2からUDPでESP32のLEDをオンオフする

HoloLens2が受信側になってUDP通信をしている事例はたくさん見つかりましたが，HoloLens2が送信側の例はあまりなく，実現まで少してこずったので残しておきます．

HoloLensにビルドしたアプリはUWPという形式になります~~が，このUWPではソケットというものを使ってひと手間加えないとUDP通信ができないようです（この辺は調べても私にはあまりよくわかりませんでした）~~．

【追記】
上記打ち消し部分について，ググってもよくわからないので同僚のChatGPTちゃんに聞いてみた結果は以下になります．なんとなく合ってる気はしますが，真偽のほどは定かではないです．
![uwp.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459660/8dbd9645-f8fc-09c7-02c1-ae6618657123.jpeg)


つまり，最近は普通にSystem.Net.SocketsをインクルードすればUdpClientも使えます．
とりあえず動いたので，そのコードを載せておきます．

## 送信側

送信側のHoloLens2 (Unity) コードは以下になります．
```C#:UdpSender.cs
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;

public class UdpSender : MonoBehaviour {
    public string targetIpAddress = "ここにESP32のIPアドレスを入力"; 
    //ESP32に書き込んだときシリアルモニタに表示されるESP32のIPアドレスを入力
    
    public int targetPort = 3333;
    private UdpClient udpClient;

    void Start() {
        udpClient = new UdpClient();
    }

    public void SendLedOn() {
        Send("LED_ON");
    }

    public void SendLedOff() {
        Send("LED_OFF");
    }

    private void Send(string message) {
        try
        {
            byte[] data = Encoding.UTF8.GetBytes(message);
            udpClient.Send(data, data.Length, targetIpAddress, targetPort);
        }
        catch (SocketException e) {       
            Debug.LogError($"SocketException: {e}");
        }
    }

    private void OnDestroy() {
        udpClient.Close();
    }
}
```
Unityのインスペクタもしくはコードエディタを開いて，上記コードのESPのIPアドレス指定部分に，通信したいESP32のIPアドレスを入力してください．

ESP32のIPアドレスは，後述するプログラムをマイコンボードに書き込むときにあらかじめ「シリアルモニタ」を開いておくことで確認できます．

関数を実行できれば方法はなんでもOKです．私は，空のオブジェクトにスクリプトをアタッチし，MRTKのボタンからSendLedOn()とSendLedOff()関数を実行するようにしました．
この記事の最後にデモ動画を載せておきます．


## 受信側

受信側のESP32のコードは以下になります．SSIDとpasswordの部分を書き換えて，Arduino IDEからESP32に書き込んでください．
```C++
#include <WiFi.h>
#include <WiFiUdp.h>
//必要な場合はライブラリをインストールしてください

#ifndef LED_BUILTIN
#define LED_BUILTIN 2 //ESP32の場合、内蔵LEDは通常GPIO2に接続されています
#endif

const char* ssid = "SSID";
const char* password = "password";
const int udpPort = 3333;

WiFiUDP Udp; //グローバル変数としてWiFiUDPインスタンスを宣言

void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("WiFi connected");
  Serial.println(WiFi.localIP());

  Udp.begin(udpPort);
}

void loop() {
  int packetSize = Udp.parsePacket();
  if (packetSize) {
    char incomingPacket[255];
    int len = Udp.read(incomingPacket, 255);
    incomingPacket[len] = '\0';
    Serial.printf("UDP packet contents: %s\n", incomingPacket);

    //LEDを制御するコード
    if (strcmp(incomingPacket, "LED_ON") == 0) {
      digitalWrite(LED_BUILTIN, HIGH);
    } else if (strcmp(incomingPacket, "LED_OFF") == 0) {
      digitalWrite(LED_BUILTIN, LOW);
    }
  }
}
```
書き込む前にシリアルモニタを開いておくことで，接続状況やESP32のIPアドレスを確認できます．
![esp32書き込んだ時.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459660/84417523-ab49-4867-b47b-7b26890dc8a0.png)

## デモ
実際の動作は以下のようになります．
わかりにくいですが，青色の光がESP32の内蔵LEDです．

https://twitter.com/T_4c3/status/1645709759935545344?s=20

## うまくいかないときに確認すべきこと
- HoloLens2とESP32を同じWi-Fiに接続しているか？
- IPアドレス，ポート番号は正しいか？
- HoloLens2 (Unity)側で通信の許可をしているか？
- Unity側でUDPを送る関数の実行はされているか？
- Arduino IDEに必要なライブラリがインストールされているか？



## 関連話題のリンク
https://learn.microsoft.com/ja-jp/windows/mixed-reality/develop/unity/udp-packets-in-unity

https://learn.microsoft.com/ja-jp/windows/uwp/networking/sockets#build-a-basic-udp-socket-client-and-server

https://learn.microsoft.com/ja-jp/windows/uwp/gaming/missing-dot-net-apis-in-unity-and-uwp


https://qiita.com/AtsushiYamashita/items/153f13ff644717326c12

https://knasa.hateblo.jp/entry/2017/07/07/034834


https://tips.hecomi.com/entry/2017/04/19/011320

## 検索用
<details><summary>ChatGPTさんの解説</summary><div>


<b>質問</b>「UWPアプリでUdpclientが使えるようになったのはいつですか．」

<b>GPT-4の回答</b>「私の知識の範囲では、UWP（Universal Windows Platform）アプリでは、.NET Coreが導入されたことで、UdpClientを含むSystem.Net.Sockets名前空間の機能が利用可能になりました。これは、UWPアプリでUdpClientを利用できるようにするための重要な変更でした。

.NET Core 3.0は2019年9月にリリースされましたが、UWPアプリのソケットサポートは以前から存在していました。ただし、それらはWindows.Networking.Sockets名前空間にあるクラス（DatagramSocketなど）を使用していました。

したがって、.NET Core 3.0のリリースにより、UWPアプリでより一般的なSystem.Net.Sockets名前空間のUdpClientが使えるようになりました。」

</div></details>