cvl-robot's diary

研究ノート メモメモ https://github.com/dotchang/

pyzmqとpyOSCでOSC over ZeroMQっぽいもの

普段はc++ばかりなのですが、少しだけpythonで書く必要ができました。
極力、普段と同じようにぬるく書きたいので、使い慣れたZeroMQとOSCを使えるようにしたいと思います。

1.準備

zeromqとoscをpipでインストールします。

> pip install pyzmq
> pip install pyosc

2.テストプログラム

[2][3]のexamplesと[1]のpyOSCの実装を参考に、OSCMessageのbinaryをzeroMQを通して送受信するサンプルを書きたいと思います。

実装のポイント

OSCMessageをバイナリ形式で送受信することだけを考えます。
どの環境の、どのOSCの実装でもネットワーク上を飛び交うパケットは同じバイナリのはず、なので。

OSCMessageのバイナリを取得する方法は、普通に書くだけです。
送られてきたバイナリ形式のOSCMessageを、OSCMessageに戻すには(効率悪いですが、手を抜いて)

recv = decodeOSC(recv_binary)

で文字列や数値にデコードした結果を元に、

recv_osc = OSCMessage(recv[0])
recv_osc += recv[2:]

OSCMessageを作り直しているだけです。イベントハンドラ等は自分で好きに書いてください。

ソースコード

req.py

import zmq
from OSC import decodeOSC, OSCMessage

ctx = zmq.Context()
sock = ctx.socket(zmq.REQ)
sock.connect('tcp://127.0.0.1:5555')

# Prepairing an OSCMessage
msg = OSCMessage("/my/osc/address")
msg.append('something')
msg.insert(0, 'something else')
msg[1] = 'entirely'
msg.extend([1,2,3.])
msg += [4,5,6]
del msg[3:6]
msg.pop(-2)
print 'send'
print 'OSCMessage:\t', msg
print 'OSCMessage(binary):\t', msg.getBinary()

# Send the OSCMessage
sock.send(msg.getBinary())

# Receive the echo
recv_binary = sock.recv()
print 'echo'
print 'binary:\t', recv_binary
recv = decodeOSC(recv_binary)
print 'string:\t', recv
recv_osc = OSCMessage(recv[0])
recv_osc += recv[2:]
print 'OSCMessage:\t', recv_osc
print 'OSCMessage(binary):\t', recv_osc.getBinary()

rep.py

import zmq
from zmq.eventloop import ioloop
from OSC import decodeOSC, OSCMessage

loop = ioloop.IOLoop.instance()

ctx = zmq.Context()
sock = ctx.socket(zmq.REP)
sock.bind('tcp://127.0.0.1:5555')

def rep_handler(sock, events):
  # Receive an OSC packet
  osc_binary = sock.recv()
  print 'received'
  print 'binary:\t', osc_binary
  # Decode the binary to OSCMessage
  msg = decodeOSC(osc_binary)
  osc_msg = OSCMessage(msg[0])
  osc_msg += msg[2:]
  print 'OSCMessage:\t', osc_msg
  # Reply the decoded OSCMessage
  sock.send(osc_msg.getBinary())

loop.add_handler(sock, rep_handler, zmq.POLLIN)

loop.start()

実行結果

> E:\workspace\python\req.py
send
OSCMessage: /my/osc/address ['something else', 'entirely', 1, 6]
OSCMessage(binary): /my/osc/address ,ssii something else entirely
echo
binary: /my/osc/address ,ssii something else entirely
string: ['/my/osc/address', ',ssii', 'something else', 'entirely', 1, 6]
OSCMessage: /my/osc/address ['something else', 'entirely', 1, 6]
OSCMessage(binary): /my/osc/address ,ssii something else entirely

> E:\workspace\python\rep.py
received
binary: /my/osc/address ,ssii something else entirely
OSCMessage: /my/osc/address ['something else', 'entirely', 1, 6]

c++の実装と繋いでみる

cvl-robot.hateblo.jp
ここで昔書いたテストプログラムのpub,subと、pythonで書いたpub,subとを繋いでみます。

sub.py

import zmq
from zmq.eventloop import ioloop
from OSC import decodeOSC, OSCMessage

loop = ioloop.IOLoop.instance()

ctx = zmq.Context()
sock = ctx.socket(zmq.SUB)
sock.connect('tcp://127.0.0.1:5555')

sock.setsockopt(zmq.SUBSCRIBE, '')

def sub_handler(sock, events):
  # Receive an OSC packet
  osc_binary = sock.recv()
  print 'received'
  print 'binary:\t', osc_binary
  # Decode the binary to OSCMessage
  msg = decodeOSC(osc_binary)
  osc_msg = OSCMessage(msg[0])
  osc_msg += msg[2:]
  print 'OSCMessage:\t', osc_msg

loop.add_handler(sock, sub_handler, zmq.POLLIN)

loop.start()

pub.py

import time
import zmq
from OSC import decodeOSC, OSCMessage

ctx = zmq.Context()
sock = ctx.socket(zmq.PUB)
sock.bind('tcp://127.0.0.1:9999')

# Prepairing an OSCMessage
msg = OSCMessage("/my/osc/address")
msg.append('something')
msg.insert(0, 'something else')
msg[1] = 'entirely'
msg.extend([1,2,3.])
msg += [4,5,6]
del msg[3:6]
msg.pop(-2)
print 'send'
print 'OSCMessage:\t', msg
print 'OSCMessage(binary):\t', msg.getBinary()

# Send the OSCMessage
time.sleep(1.0)
sock.send(msg.getBinary())

c++の方も,送受信先のフィルタを

publisher->bind("tcp://*:5555");
subscriber->connect("tcp://127.0.0.1:9999");

適当に調整してください。

実行結果

received
binary: /mouse/button ,siii pressed $ [
OSCMessage: /mouse/button ['pressed', 548, 347, 2]
received
binary: /mouse/button ,siii pressed ( _
OSCMessage: /mouse/button ['pressed', 296, 351, 0]
received
binary: /mouse/button ,siii pressed \
OSCMessage: /mouse/button ['pressed', 260, 604, 0]
received
binary: /key/pressed ,i c
OSCMessage: /key/pressed [99]
received
binary: /key/pressed ,i v
OSCMessage: /key/pressed [118]
received
binary: /key/pressed ,i l
OSCMessage: /key/pressed [108]

Received: /my/osc/address something else entirely 1 6
Received: /my/osc/address something else entirely 1 6

問題なく動きますね。

[1] pythonでOSC Messageのデコード
https://github.com/ptone/pyosc/blob/master/OSC.py
[2] pyzmqのreq,repのexample
zmq の python binding で echo テスト - Twisted Mind
[3] pyOSCのexample
pyosc/examples at master · ptone/pyosc · GitHub

[4] pyzmqのpub, subのexample
pyzmqでZeroMQを触ってみる (PUB/SUB) - YAMAGUCHI::weblog
[5] int64を送受信したいとき
pyOSCでint64が受け取れなくてはまったメモ - Qiita