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ライブラリのソースから拝借しました。
方法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のプロキシをたてるやり方のほうが設計としては適切でしょう。