TouchDesignerからwifiでM5Stack経由のLEDをフルコントロールする

先日こういうのを作りました。

https://twitter.com/foka22ok/status/1470592253186158595

LEDはM5Stack経由でWifiでつないでいて、全体はこういう構成になってます。
![構成図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16457/2f706b48-2f21-7d01-b202-2cdad96c2d45.png)

LEDは50個一つずつRGBがコントロールできるものを使いました。今回使ったのはこれです。
https://www.amazon.co.jp/gp/product/B08PVYXJJW/ref=ppx_yo_dt_b_asin_title_o03_s00

自由な位置にLEDを取り付けられるようにしたかったので、M5Stackはwifi接続にしました。
M5StackはBluetoothでも無線対応できますが、TouchDesignerから通信したかったのでwifi接続のUDP通信にしました。

#人の動きをMediaPipeでトラッキング
公式のサンプルにUDP通信を足しただけなので省略。
https://google.github.io/mediapipe/solutions/pose#python-solution-api

# TouchDesignerから50個分のRGBデータを送信する

横50 x 縦10ピクセルのTOPデータをCHOPに変換、0～1のRGBデータを0～255に変換して`Chop Excute DAT`で`UDP Out DAT`使ってM5Stackに送信してます。

TOPの視認性のために縦10pixel使ってますが、データ上は縦1pixelで良いので、`Top to CHOP`で縦1pixel分のデータを取り出して数値化してます。
この部分のオペレータはこんな感じ。
![td_cap.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16457/5f7289af-5dd6-789f-24fa-ac449c2512b3.png)

この部分ができれば、`Null TOP`に好きなように映像渡せばTouchDesignerからLEDが自由に操作できるようになります。
`Chop Excute DAT`はValueChangeで処理するようにして、中身はこうなってます。
高速通信しても問題ないようになるべく通信サイズを抑えるため、文字列としては送らずにbyte配列にしてデータを送るようにしているので、50個のRGB用LEDに対して無駄なく150byteで送れます。

```python:chopexecの中身
udpOut = op("udpout1")
allcolorData=op("null4")
sendDataArray=[0]*150 #50個のLED x RGBの3色分のデータ

def onOffToOn(channel, sampleIndex, val, prev):
	return

def whileOn(channel, sampleIndex, val, prev):
	return

def onOnToOff(channel, sampleIndex, val, prev):
	return

def whileOff(channel, sampleIndex, val, prev):
	return

def onValueChange(channel, sampleIndex, val, prev):
	sendAllData()
	return
	
def sendAllData():
	for i in range(50):
		sendDataArray[i*3]=int(allcolorData["r"][i])
		sendDataArray[i*3+1]=int(allcolorData["g"][i])
		sendDataArray[i*3+2]=int(allcolorData["b"][i])
	#byte配列にして送信。150byteでデータが送信できる。
	data = bytearray(sendDataArray)
	udpOut.sendBytes(data)
```
# M5Stackで受信してLED制御

AsyncUDPで非同期にUDPデータを受信、uint8_tに変換して、50個のLEDを制御してます。

```C++:M5Stackの中身
#include <M5Stack.h>
#include <WiFi.h>
#include <AsyncUDP.h>
#include <Adafruit_NeoPixel.h>

#define PIN        5
#define NUMPIXELS 50

Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
bool isDataReceive=false;
uint8_t rData[NUMPIXELS];
uint8_t gData[NUMPIXELS];
uint8_t bData[NUMPIXELS];

const char SSID[] = "SSIDが入ります";       //WiFi SSID
const char PASSWORD[] = "パスワードが入ります";    //WiFi パスワード
AsyncUDP udpReciever;
const int myUdpPort=12345; //TouchDesignerと合わせておく

void setupNetwork(){
  WiFi.begin(SSID, PASSWORD);
  M5.Lcd.print("WiFi connecting\n> ");
  M5.Lcd.print(SSID);
  M5.Lcd.print("\n");

    //接続をまつ
  while (WiFi.status() != WL_CONNECTED) {
    M5.Lcd.print(".");
    delay(100);
  }

  M5.Lcd.print("WiFi connected!");

  if(udpReciever.listen(myUdpPort)) {
    //M5Stack自身のIPを画面に表示しておく。このIPをTouchDesignerのUDP OUTの送信先に入力する。
    M5.Lcd.print("UDP Listening on IP: ");
    M5.Lcd.print(WiFi.localIP());
  }

  udpReciever.onPacket([](AsyncUDPPacket packet) {
      uint8_t* colorData=packet.data();
      isDataReceive=true;
      for(int i=0; i<NUMPIXELS; i++) {
        rData[i]=(uint8_t)colorData[i*3];
        gData[i]=(uint8_t)colorData[i*3+1];
        bData[i]=(uint8_t)colorData[i*3+2];
      }
  });
}

void setup() {
  M5.begin();
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextColor(GREEN , BLACK);
  M5.Lcd.setTextSize(2);

  setupNetwork();
  
  pixels.begin();
}

void loop() {
  M5.update();
  if(isDataReceive){
    for(int i=0; i<NUMPIXELS; i++) {
      pixels.setPixelColor(i, pixels.Color(gData[i], rData[i], bData[i]));
    }
    pixels.show();
  }
  delay(1); //delayいらない気がするけど、光具合の調整の名残で残してある
}
```
# まとめ
TouchDesigner側からLEDをフルコントロールできるようになると、M5Stack側は一度作っておけば使いまわしが利きます。
あと、プログラムで数値操作するよりTouchDesignerの得意な映像操作で自由に光らせられるので直感的な制御が可能です。

今回は人の動きに反応するだけでなく、自動でカラフルに光ったり、音に反応して光ったりするものも作りました。
TouchDesignerで制御してるので、こういうのもサクッと作れるのが良いところです。

本当はLEDをクリスマスツリーに飾りたかったけど、ツリーが無いので壁に貼り付けて楽しみました。
クリスマス時期に家族に見てもらったら、時期的なこともあり、家族受けがとてもよかったので個人的にも満足しました。