Imaginary Code

from kougaku-navi.net

Processingで自作クラスの中にserialEvent()を作る

Processingにおいて外部デバイスとシリアル通信をやるとき、データ受信時の処理を行う関数としてserialEvent()という関数を作ります。serialEvent()の中に書かれるコードは大抵の場合、受信データをパースして欲しいデータを取り出すような処理になるのですが、結局のところそれは対象のデバイス固有の処理なので、そのデバイスを扱うクラスの中にserialEvent()をまるっと入れることができれば、スコープがすっきりします。具体的なテクニックを紹介します。

方法1:PAppletの代理となるクラスを自作クラス内に作る

import processing.serial.*; 

// 接続先のデバイスをMyDeviceというクラスで表現
MyDevice device;

void setup() {
  device = new MyDevice(this, Serial.list()[0], 115200);
}

void draw() {
}

// シリアル通信を行うデバイスに関する自作クラス
public class MyDevice {
  PApplet parent;  // 親のPApplet
  Serial serial;  // シリアル
  SerialProxy serialProxy;  // プロキシ

  // コンストラクタ
  public MyDevice(PApplet parent, String serial_name, int serial_rate) {
    this.parent = parent;
    this.serialProxy = new SerialProxy();
    this.serial = new Serial(serialProxy, serial_name, serial_rate);
    parent.registerMethod("dispose", this);
  }

  // プロキシ(PAppletの代理)
  public class SerialProxy extends PApplet {
    public SerialProxy() {
    }
    // MyDeviceクラスに内包されたserialEvent()関数
    public void serialEvent(Serial port) {
      // 受信データに関する処理(以下は受信データを表示する例)
      if ( port.available() > 0 ) {
        String line = port.readStringUntil('\n');
        if ( line != null ) {
          print(line);
        }
      }
    }
  }

  // データ送信用のメソッド
  void sendData() {
    this.serial.write(12345);  // 送信データはダミー
  }

  // シリアルポートの廃棄
  public void dispose() {
    this.serial.dispose();
  }
}


ご覧の通り、MyDeviceクラスの中にそのデバイス用のserialEvent()が内包されているので、MyDeviceを使う人はserialEvent()を新たに作る必要がありません。ギミックとしては、自作クラス内部にPAppletの代理となるクラスSerialProxyを作り、そこへserialEvent()を入れてしまうというものです。この巧妙な仕掛けはfirmataライブラリのソースから拝借しました。

github.com

方法2:より雑な方法(非推奨)

一晩経って見直して、より雑な方法もあるなと思ったのでそれも書いておきます。あくまで実装可能な方法として紹介するもので、おすすめはしません。要はSerialをnewする際に第1引数で紐づける先がPAppletであれば良いので、自作クラス自体をPAppletの派生クラスとしてしまうという乱暴な手でも実現できます。

import processing.serial.*; 

MyDevice device;

void setup() {
  device = new MyDevice(Serial.list()[0], 115200);
}

void draw() {
}

public class MyDevice extends PApplet {
  Serial serial;

  public MyDevice(String serial_name, int serial_rate) {
    serial = new Serial(this, serial_name, serial_rate);
  }

  void sendData() {
    serial.write(12345);
  }

  void serialEvent(Serial port) {
    if ( port.available() > 0 ) {
      String line = port.readStringUntil('\n');
      if ( line != null ) {
        print(line);
      }
    }
  }
}


このやり方だと一見すっきりしたコードに見えますが、MyDeviceがPAppletの機能を継承しているのはいかがなものかという問題があります。クラス化している対象がハードウェアであることを考えると、それはちょっと無理がありますね。やはり方法1のように内部にPAppletのプロキシをたてるやり方のほうが設計としては適切でしょう。