小嶋秀樹 | 研究室
日本語 | English
OSC で ESP と PC を無線でつなぐ
【OSC とは】

OSC(Open Sound Control)とは,送信者(クライアント)から受信者(サーバ)へ,つぎのようなデータを送るための仕組みです.ひとつの通信データは,アドレスパターン(たとえば /kozPhone/uniVRtutorial2/message)とそれに続く引数の列(たとえば 320 150 "Game Over")からなります.アドレスパターンによって,ディレクトリ階層のように,データの意味づけを表示します.引数としては,整数(int32)・実数(float)・文字列などが使えます.

もともとは電子楽器のコンピュータ制御のためにつくられましたが,今ではさまざまな用途に使われています.通信の実装部分には UDP を利用することが多いようです.UDP とは,TCP と同様に,IP(Internet Protocol)を使ったデータ通信方法のひとつです.TCP/IP と比べて,UDP/IP は(確実性を一定犠牲にして)高速の通信ができます.ここでも UDP を使った OSC について解説します.

【OSC でデータを ESP から PC に送る】

ESP 側のプログラム: ESP(Arduino)側では,IO4 に接続したスイッチを操作するたび,OSC メッセージ("/sw 1" または "/sw 0")を PC に送ります。PC はクライアントとなり,192.168.0.88 の IP アドレスを持つことを想定しています。通信に使用するポート番号は 8888 番とします。

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <OSCMessage.h>

const char *ssid = "xkozima";            //  ご近所と重複ないように
const char *pass = "dongbeino";          //  8文字以上
IPAddress ipServer(192, 168, 0, 1);      //  Dongbeino の固定アドレス
IPAddress ipGateway(192, 168, 0, 1);     //  ゲートウェイ(上と同値)
IPAddress subnet(255, 255, 255, 0);      //  サブネットマスク
IPAddress ipClient(192, 168, 0, 88);     //  クライアント(PC)の固定アドレス

WiFiUDP UDP;
const unsigned int port = 8888;          //  OSC通信のポート番号

void setup() {
  //  スイッチ入力
  pinMode(4, INPUT_PULLUP);
  //  シリアルモニタ(動作ログ)
  Serial.begin(115200); delay(100);
  Serial.println("\n*** Dongbeino ***");
  //  アクセスポイントの構成(固定アドレス)
  WiFi.mode(WIFI_AP);
  WiFi.softAP(ssid, pass);
  WiFi.softAPConfig(ipServer, ipGateway, subnet);
  Serial.print("network: "); Serial.println(ssid);
  Serial.print("address: "); Serial.println(WiFi.softAPIP());
  Serial.print("client : "); Serial.println(ipClient);
}
void loop() {
  //  PC への送信(スイッチ入力)
  int state = digitalRead(4);
  OSCMessage mess("/sw");
  if (state == HIGH) {        //  スイッチは押されていない
    mess.add(1);
    Serial.println("/sw 1");
  }
  else {                      //  スイッチが押された
    mess.add(0);
    Serial.println("/sw 0");
  }
  UDP.beginPacket(ipClient, port);
  mess.send(UDP);
  UDP.endPacket();
  mess.empty();
  //  つぎへ
  delay(30);                  //  少し待つ(通信速度の調整)
}

PC 側のプログラム: PC(openFrameworks)側のプログラムでは,8888番ポートで受信した OSC メッセージ("/sw 1" または "/sw 0")に応じて,赤丸または黒丸を描画します。

#pragma once

#include "ofMain.h"
#include "ofxOsc.h"
#define PORT 8888

class ofApp : public ofBaseApp {
    private:
        ofxOscReceiver receiver;
        int state;
    public:
	void setup();
	void update();
	void draw();
        ...(以下省略)...
};    
#include "ofApp.h"
    
void ofApp::setup(){
    ofSetWindowShape(600, 400);
    ofSetFrameRate(60);
    //  OSC
    receiver.setup(PORT);
    //  graphics
    ofBackground(255, 255, 255);
    //  debug output
    printf("*** start server ***\n");
}
void ofApp::update() {
    //  OSC receive
    ofxOscMessage mess;
    while (receiver.getNextMessage(mess)) {
        if (mess.getAddress() == "/sw") {
            //  get first(0-th) data
            state = mess.getArgAsInt(0);
        }
    }
}
void ofApp::draw(){
    //  graphics
    if (state == 0) {
        ofSetColor(255, 0, 0);
    }
    else {
        ofSetColor(0, 0, 0);
    }
    ofDrawCircle(300, 200, 120);
}
...(以下省略)...

動かし方: ESP を動作させると SSID(上の例では xkozima)が見えるので,PC から接続します。このとき PC 側は固定 IP アドレス 192.168.0.88 とし,ゲートウェイは 192.168.0.1,サブネットマスクは 255.255.255.0 としてください。接続できたら,openFrameworks 側のプログラムを動かします。ESP 側でスイッチを操作すると,それに応じて PC 画面上のグラフィック描画が変化するはずです。

【OSC でデータを PC から ESP に送る】

つぎに,PC から ESP にも OSC データを送る機能を加えたいと思います.ここでは,PC 上の openFrameworks プログラムから,キー操作に応じて,OSC メッセージ("/led 0" または "/led 1") を ESP に送信します.動作条件は,上にあげた「OSC でデータを ESP から PC に送る」と同様に,IP アドレスが 192.168.0.1 をもつ ESP がサブネット(SSID: xkozima) を提供し,PC はその中で 192.168.0.88 をもつようにします.ESP からの通信を受け取る PC 側のポート番号は 8888 で,逆に,PC からの通信を受け取る ESP 側のポート番号を 8889 とします.

ESP 側のプログラム: 前出のプログラムに加えて,PC からのデータ(OSC パケット)を受け取る処理を記述します.また,ESP の IO13 に(1kΩ の抵抗を介して)LED が接続されていることを確認してください.もし LED が無ければ,IO13〜1kΩ〜LED(A)/LED(K)〜GND と接続してください.

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <OSCMessage.h>
#include <OSCBundle.h>

const char *ssid = "xkozima";            //  ご近所と重複ないように
const char *pass = "dongbeino";          //  8文字以上
IPAddress ipServer(192, 168, 0, 1);      //  Dongbeino の固定アドレス
IPAddress ipGateway(192, 168, 0, 1);     //  ゲートウェイ(上と同値)
IPAddress subnet(255, 255, 255, 0);      //  サブネットマスク
IPAddress ipClient(192, 168, 0, 88);     //  クライアント(PC)の固定アドレス

WiFiUDP UDP;
const unsigned int port_out = 8888;      //  送信先ポート番号
const unsigned int port_in  = 8889;      //  受信用ポート番号

void setup() {
  //  スイッチ入力
  pinMode(4, INPUT_PULLUP);
  //  LED 出力
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
  //  シリアルモニタ(動作ログ)
  Serial.begin(115200); delay(100);
  Serial.println("\n*** Dongbeino ***");
  //  アクセスポイントの構成(固定アドレス)
  WiFi.mode(WIFI_AP);
  WiFi.softAP(ssid, pass);
  WiFi.softAPConfig(ipServer, ipGateway, subnet);
  Serial.print("network: "); Serial.println(ssid);
  Serial.print("address: "); Serial.println(WiFi.softAPIP());
  Serial.print("client : "); Serial.println(ipClient);
  //  UDP 受信開始
  UDP.begin(port_in);
}

void led_control(OSCMessage &mess) {
  //  OSC メッセージに応じて LED を制御(オン・オフ)
  int val = mess.getInt(0);
  digitalWrite(13, val);
}

void loop() {
  //  PC からの受信(LED に出力)
  OSCBundle bundle;
  int packetCount = UDP.parsePacket();
  if (packetCount > 0) {
    while (packetCount--) {
      bundle.fill(UDP.read());
    }
    if (! bundle.hasError()) {
      bundle.dispatch("/led", led_control);
    }
  }
  //  PC への送信(スイッチ入力)
  int state = digitalRead(0);
  OSCMessage mess("/sw");
  if (state == HIGH) {        //  スイッチは押されていない
    mess.add(1);
    Serial.println("/sw 1");
  }
  else {                      //  スイッチが押された
    mess.add(0);
    Serial.println("/sw 0");
  }
  UDP.beginPacket(ipClient, port_out);
  mess.send(UDP);
  UDP.endPacket();
  mess.empty();
  //  つぎへ
  delay(30);                  //  少し待つ(通信速度の調整)
}

PC 側のプログラム: PC(openFrameworks)側のプログラムには,キー入力によって OSC メッセージ("/led 0" または "/led 1")を送信する機能を加えます.

#pragma once

#include "ofMain.h"
#include "ofxOsc.h"
#define PORT_IN  8888
#define PORT_OUT 8889

class ofApp : public ofBaseApp {
    private:
        ofxOscReceiver receiver;
        ofxOscSenderr sender;
        int state;
    public:
	void setup();
	void update();
	void draw();
        ...(以下省略)...
};    
#include "ofApp.h"
    
void ofApp::setup(){
    ofSetWindowShape(600, 400);
    ofSetFrameRate(60);
    //  OSC
    receiver.setup(PORT_IN);
    sender.setup("192.168.0.1", PORT_OUT);    
    //  graphics
    ofBackground(255, 255, 255);
    //  debug output
    printf("*** start server ***\n");
}
void ofApp::update() {
    //  OSC receive
    ofxOscMessage mess;
    while (receiver.getNextMessage(mess)) {
        if (mess.getAddress() == "/sw") {
            //  get first(0-th) data
            state = mess.getArgAsInt(0);
        }
    }
}
void ofApp::draw(){
    //  graphics
    if (state == 0) {
        ofSetColor(255, 0, 0);
    }
    else {
        ofSetColor(0, 0, 0);
    }
    ofDrawCircle(300, 200, 120);
}
void ofApp::keyPressed(int key){
    //  OSC send "/led 1"
    ofxOscMessage mess;
    mess.setAddress("/led");
    mess.addIntArg(1);
    sender.sendMessage(mess);
    mess.clear();
}
void ofApp::keyReleased(int key){
    //  OSC send "/led 0"
    ofxOscMessage mess;
    mess.setAddress("/led");
    mess.addIntArg(0);
    sender.sendMessage(mess);
    mess.clear();
}
...(以下省略)...

動かし方: 前出の例と同じように,ESP が提供するネットワークに PC を接続し,192.168.0.88 の固定アドレスを設定します.openFrameworks 側のプログラムを動作させ,ESP 側のスイッチを操作すると,それに応じて PC 画面上のグラフィックス描画が変化します.また,PC 上でいずれかのキーを押し下げると,ESP 上の LED が点灯し,キーから指を離すと消灯します.