TouchDesignerでお手軽データロガー

# はじめに
とあるきっかけで[TouchDesigner](https://derivative.ca/)なるビジュアルプログラミングツールの存在を知り、下記記事に触発されてESP32マイコンと組み合わせた何かを作ってみようと思い立った。

https://tech-blog.cerevo.com/archives/11319/

上記記事ではESP32搭載のM5StickCとPC間をUSB経由のシリアル通信で接続し、センサの計測データを転送している。
しかしながらシリアル通信はデバッグ用の情報を表示する用途に使いたいことが多く、計測データ転送と共存させるのは至難の業である。
そこで計測データはUSBのシリアル通信ではなくWiFiを使用したUDP通信で転送し、TouchDesignerでそのデータを受け取ってリアルタイムに波形表示する仕組みを試してみることにした。

# システム構成
PC上のTouchDesignerをUDPサーバ、ESP32が搭載されているM5StickCPlus(以下M5Stickと略記)をUDPクライアントとし、それぞれWiFiルータに接続している。
また、M5Stickには分圧回路を介して曲げセンサが接続されている。曲げセンサは曲がり具合によって抵抗が変化するため、分圧回路により曲げ具合を電圧変化として計測できる。
M5StickからTouchDesignerへ受け渡すデータフォーマットはシンプルに{計測値}\nとし、100HzでサンプリングしたデータをM5Stick内のバッファに格納しつつ100ms間隔でPCにまとめて転送する。

![diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3952487/c92b657e-bdab-7db1-bd6e-786a372dfed4.png)

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3952487/8a769dcf-39d4-0892-af51-a65240f5208d.jpeg" width="300">

# プログラム
## M5StickCPlus側
<details><summary>main.cpp</summary>

```cpp:main.cpp
#include <Arduino.h>
#include <M5Unified.h>
#include <WiFi.h>
#include <WiFiUdp.h>
#include "secret.hpp"

#define ADC_PIN 36             // ADCピン
#define TIMER_INTERVAL 1000 * 10    // タイマの割り込み間隔 (us)
#define BUFFER_SIZE 64         // バッファサイズ

// WiFi設定
const char* ssid = SECRET_SSID;
const char* password = SECRET_PASS;

// UDP設定
const char* udpServer = SECRET_IP; // TouchDesignerのPCのIP
const int udpPort = SECRET_PORT;

hw_timer_t* timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

WiFiUDP udp;
int adcBuffer[BUFFER_SIZE];
volatile int bufferIndex = 0;

void IRAM_ATTR onTimer() {
  portENTER_CRITICAL_ISR(&timerMux);
  if (bufferIndex < BUFFER_SIZE) {
    adcBuffer[bufferIndex] = analogRead(ADC_PIN);
    bufferIndex++;
  }
  portEXIT_CRITICAL_ISR(&timerMux);
}

void setup() {
  M5.begin();
  Serial.begin(115200);

  // ADC初期化
  pinMode(ADC_PIN, INPUT);
  analogReadResolution(12); // 12bit解像度に設定（0-4095）

  // WiFi初期化
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected");

  // UDP初期化
  udp.begin(WiFi.localIP(), udpPort);
  Serial.printf("UDP client started, target: %s:%d\n", udpServer, udpPort);

  // タイマ初期化
  timer = timerBegin(0, 80, true); // 80分周で1us間隔
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, TIMER_INTERVAL, true); // TIMER_INTERVAL間隔で発火
  timerAlarmEnable(timer);
}

void loop() {
  static unsigned long lastSendTime = 0;

  // 一定間隔でバッファデータを送信
  if (millis() - lastSendTime > 100) { // 100msごとに送信
    portENTER_CRITICAL(&timerMux);
    int dataCount = bufferIndex;
    bufferIndex = 0;
    portEXIT_CRITICAL(&timerMux);

    if (dataCount > 0) {
      char packet[BUFFER_SIZE * 6]; // 最大で6文字/値（余裕を持つ）

      udp.beginPacket(udpServer, udpPort);

      int packetLen = 0;
      for (int i = 0; i < dataCount; i++) {
        packetLen = snprintf(packet, sizeof(packet), "%d\n", adcBuffer[i]);
        udp.write((uint8_t*)packet, packetLen);
      }
      udp.endPacket();

      Serial.printf("Sent %d values: %s", dataCount, packet);
    }
    lastSendTime = millis();
  }
}
```

</details>

<details><summary>secret.hpp</summary>

```hpp:secret.hpp
#pragma once

#define SECRET_SSID    "YOUR-SSID"
#define SECRET_PASS    "YOUR-PASS"
#define SECRET_IP      192.168.1.10
#define SECRET_PORT    7000
```

</details>

<details><summary>platform.ini</summary>

```platform.ini
[env:m5stick-c]
platform = espressif32
board = m5stick-c
framework = arduino
lib_deps = m5stack/M5Unified@^0.2.1
monitor_speed = 115200
```

</details>


## TouchDesigner側

UDP In DAT -> Select DAT -> DAT to Chop -> Trail Chop

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3952487/7e451304-b29f-c5b1-6a52-d9bd7ee052df.png">

# デモ
曲げセンサの曲げ具合に追従して波形が変化する様子
![V_20241204_223535_ES0.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3952487/55098ad2-ebce-c792-b84c-c949f8d75229.gif)

# おわりに
TouchDesignerの操作に慣れるのにやや手こずったがあっというまに(記事を書くことが決まってから4時間程度で)リアルタイム波形モニタを作ることができた。

TouchDesigner側の機能はほんの一部しか把握できていないので今後さらなる活用法を模索していきたい。