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

ロボットには出来ないこと

(編集中)
慣用句として、ロボットには出来ない、という言葉が良くつかわれます。

とくに伝統工芸や芸能などに多いような気がします。
本当に出来ないのかどうか検証したいので、ロボットには出来ない、という言葉を聞いた時にここにメモしておこうと思います。

・ジーンズを作れない

IoT最強プラットフォームを目指して、RaspberryPi3にopenFrameworksをインストールして、ofxZmqをWindow(openGL)無しでも動くようにする(その1)

(編集中)
raspberry Piは安くて早くて、arduinoと違ってネットワークに簡単に繋がる利点があります。
また、ネットワークはzeroMQを使えば、高速・安定・簡単に書けることも覚えてしまいました。
さらに、openFrameworksは様々な用途のライブラリaddonをいろいろな人が開発していて、様々な用途に便利に使えます。

そこで、raspberry Piの上のopenFrameworksでzeroMQが使える環境を整えておきたいと思います。
バイス制御などの用途では、GUI Windowがあることが却ってネックになってしまうことがあるので、
サーバー向けにGUI無しでも動かせるようにしておきます。

1. 最新のRaspbian(jessie)を普通にインストールする.

2. openFrameworksのソースをダウンロードしてインストールする.

このページを参考にします。
raspberry pi | openFrameworks
ARMv6版とARMv7版が選択肢にありますが、Raspbianではv6、Armlinuxではv7を選ぶとよいようです。

3. zeromqのcpp bindingsをダウンロードして/usr/includeにコピー

http://zeromq.org/bindings:cpp

> cd /usr/include/
> sudo wget https://raw.githubusercontent.com/zeromq/cppzmq/master/zmq.hpp

4. 作者に感謝しながら、ofxZmqをダウンロード

cd /home/pi/openFrameworks/addons
wget --no-check-certificate https://github.com/satoruhiga/ofxZmq/archive/master.tar.gz
tar xvzf master.tar.gz
mv ofxZmq-master ofxZmq
rm master.tar.gz

5. sodiumをインストール

[1]を参考に。

sudo apt-get install libsodium-dev

6. 空の新規プロジェクトを作る

RaspberryPiでは、残念ながらプロジェクトジェネレータを使えないそうなので、ファイルコピーで空のプロジェクトを用意します。

cp -R /home/pi/openFrameworks/examples/empty/emptyExample /home/pi/openFrameworks/apps/myApps/myRpiApp
cd /home/pi/openFrameworks/apps/myApps/myRpiApp
make
make run

7. ofxZmqをプロジェクトに追加するためaddons.makeを編集。

> cd ~/openFrameworks/apps/myApps/myRpiApp
> nano addons.make

ofxZmq

8. sodiumをリンクするようにconfig.makeを編集

PROJECT_LDFLAGSの行を探して、コメントアウトを解除。

PROJECT_LDFLAGS=-Wl,-rpath=./libs -lsodium

9. ofxZmqのexampleのソースコードをコピーして持ってくる

cd ~/openFrameworks/apps/myApps/myRpiApp/src
cp /home/pi/openFrameworks/addons/ofxZmq/example/src/testApp.cpp ./
cp /home/pi/openFrameworks/addons/ofxZmq/example/src/testApp.h ./

main.cppを次のように編集します。

#include "ofMain.h"

//define _NO_WINDOW
#ifdef _NO_WINDOW
#include "ofAppNoWindow.h"
#endif // _NO_WINDOW

#include "testApp.h"

int main(int argc, char* argv[])
{
#ifndef _NO_WINDOW
    ofSetupOpenGL(1024, 768, OF_WINDOW);
#else
    ofAppNoWindow window;
    ofSetupOpenGL(window, 0, 0, OF_WINDOW);
#endif

    ofRunApp(new testApp());

    ruturn 0;
}

もしくは、

ofSetFramerate(0);

をしておく。

10. platform.hppにZMQ_USE_EPOLLの定義を追加

nano ~/openFrameworks/addons/ofxZmq/libs/zmq/src/platform.hpp

#define ZMQ_USE_EPOLL
#define ZMQ_HAVE_UIO 1

ZMQ_HAVE_UIOはstruct iovecが多重定義になるのを防ぐのに必要だとのこと。

コンパイルを通すために適当に立てたフラグなので、環境に合わせてちゃんといろいろ設定しないとダメかもしれない。[2]を参考に。要確認。

11. makeして実行

> make
> make run

GUIなしにすると、キー入力も拾ってもらえませんので、testApp.cppを適当に書き換える必要があります。


これで、IoT最強に成れるのではないだろうか!

[1] osdevlab.blogspot.jp
[2] github.com
[3] qiita.com

モバイルバッテリー、有名メーカーの製品が良いと思い込んでいると時代に置いて行かれるかも?

Ankerの大容量モバイルバッテリーが良い、という記事を読んだ。
www.gurinovation.com
まさしくこの機種Anker PowerCore 20100を購入して使っていたので悪くない製品だということは知っているのだが、購入して1年ちょっと経った今、へたってしまってただ大きくて重い物体になってしまっている。

安価なので買い替えてもいいのだが、トランジスタ技術2017年1月号に気になる記事が載っている。
トランジスタ技術 2017年 1月号

151~157ページ、大塚康二さんの書かれた全力実験!No.1モバイルバッテリを探せ!の中で、いくつかのバッテリーの寿命テストとランキングが紹介されている。
詳細は載せられないが、ここで一番だったのは全然知らない中華メーカーRockの製品で、サイクル寿命が長いことが絶賛されていた。

2位はソニーのCP-10A、3位はAnker5。

何が言いたいのかというと、
リチウムイオン電池の性能は日本製が一番!やアメリカが強い!などと思い込んでいると、電池は電子機器の基幹部品なので、あっという間に中国に置いて行かれて没落してしまう危機にあるのではないかということ。

AS5047D-EK-ABのピン配置のメモ

f:id:cvl-robot:20170131212151p:plain

USB I&P BOXのソフト上の文字が見切れる問題

f:id:cvl-robot:20170215171610j:plain

Zero Position

[Set] [Reset]

Configuration

Rotation Direction
Dynamic Angle Compensation
ABI-Pulses
UVW-Pole Pairs, Hysteresis
Incremental Out Error Status, PWM Out

[Load Config] [Export Config]
[Read Config] [Write Config] [Burn Config]

ネットワークの実時間性を保障してほしいので、DDSを調べる

DDSは、RTPSパケットを使って、Pub-Sub形式でネットワーク上のデータをやり取りする仕組み(分散ネットワーキングミドルウェア)だそうです。
https://ja.wikipedia.org/wiki/Data_Distribution_Service

諸悪の根源であるところのCORBAの系統のようなので使い勝手が心配ですが、レイテンシやディレイが一定範囲内であることを期待できるリアルタイムのデータ通信の手法としては本命視されています。
商用やオープンソースでいくつかの実装があるようです。オープンソースで使い勝手のよさそうなものを探していきます。

RTI Connext DDS

https://www.rti.com/products
商用で最も普及しているのがこれです。ただし、150万円ぐらいするようです。
大学や研究機関向けにはオープンソースで機能限定版を申請により提供してくれるようですが、プロジェクトに他の企業からの予算が少しでもついていると、使用できないそうです。
今時の大学は、一生懸命外部予算の獲得に励んでいますから、この使用条件を満たすところなんてほとんどないのではないでしょうか?
登録しようとすると、連絡はアメリカの本社に行きますが、結局日本には日本の代理店があるからそっちに聞いてくれ、と言われます。
ということで、使えません。

openSplice

かつてRTI社と一緒にDDS規格の仕様策定にかかわったThalesという会社が開発していたのを、Prismtechという会社が引き継いでオープンソースで提供されているライブラリです。
一見、良さそうに見えますが、最近はほとんどメンテナンスされていないようなので、開発が止まっているように見えます。
初期にはROS2での採用も検討されていたようですが、外されてしまいました。
http://qiita.com/sobeit@github/items/20d9f1478bbc1a33d096
Hello World exampleを動かしてみようとすればわかりますが、release.batという環境設定用のバッチファイルを実行したコマンドプロンプト窓から実行ファイルを呼び出す必要があります。
やりようによっては要らない作業なのかもしれませんが、よくわからない処理の上に面倒くさいです。
ということで、使えません。

FastRTPS

www.eprosima.com
eProsimaという会社がオープンソースとして管理しているライブラリです。RTPSはパケットの形式でDDSとは謡っていないのですが、RTPSでPub-Sub形式でデータをやり取りするライブラリなので、そのものですね。
ROS2でも採用されるようなので、大本命だと思います。

ROS2では、ほかにもTwin Oaks Softwareという会社のDDSも検討されているようです。
CoreDX DDS Data Distribution Service Middleware | Twin Oaks Computing, Inc
眺めた限り、HelloWorldExampleが一番簡素に書けているのでいずれこれも使ってみたいです。

FastRTPSをちょっと触ってみる

Windows10 64bit、VisualStudio 2015の環境です。
ソースも落ちていますが、コンパイルにはboostが必要で面倒くさいので、バイナリ提供版を使います。
上記ページのDownload Now!ボタンを押せばダウンロードページに飛びますが、なぜか異様に重いです。今日ここの環境では4分ぐらいかかりました。簡単なユーザー登録を済ませれば、ダウンロードページに飛びます。
ダウンロードしたexeを実行すればインストールが始まり、特に問題なく進むとは思いますが、そのままでは管理者権限の必要なC:\Program Files\eProsimaフォルダの下にインストールされてしまい、後で少し問題が出ます。できれば、c:\workspaceなど適当な場所にしておいたほうがよさそうに思います。
もっとも簡単そうなC:\Program Files\eProsima\fastrtps 1.3.1\examples\C++\HelloWorldExampleを見てみると,cmake形式でプロジェクトが提供されています。
ProgramFilesフォルダの下にファイルを書き込むことが億劫なので、適当な場所にコピーして適当にcmakeを通してやれば、slnファイルができるので普通にビルドします。
HelloWorldPublisher.bat

HelloWorldExample.exe publisher

HelloWorldSubscriber.bat

HelloWorldExample.exe subscriber

などの適当なバッチファイルを作って2つを実行してやれば、特に問題なくプログラムは動くかと思います。

自前のデータ形式をやり取りするプログラムを書くには。

お、簡単じゃん、などと思いながらソースコードを見てみることにすると、やたらとファイルが多いことに気が付きます。
HelloWorldPublisher.cxxと.h、HelloWorldSubscriber.cppと.hはわかるとしてもまだ余計なものが一杯ありすぎます。
HelloWorld_main.cppをのぞいてみると、これは単に引数によって2つのプログラムの呼び出しを変えているだけのものなので無視していいことが分かります。
残ったHelloWorldPubSubTypes.cxxはなんぞこれと見てみると、わけのわからないことが書かれています。こんなもの、書いてらんないぞ、と焦って頭のほうのコメントをよく見てみると、

This file was generated by the tool fastcdrgen.

なんてことが書かれていて、自動生成されたファイルだということが分かり、胸をなでおろします。
fastcdrgenとは何ぞ、と思ってフォルダの中を探してみても同じファイル名のものはありません。すわ、別のプロジェクトを入れる必要があるのかな、面倒くせーななどと思っていると、
C:\Program Files\eProsima\fastrtps 1.3.1\binの下にfastrtpsgen.bat
という名前がよく似た感じのファイルを見つけることができます。これかなー、と思ってググってみるとコレでした。このムービーを見るとよくわかりますし、最初からちゃんとマニュアルを追っていけば書いてあります。
www.youtube.com
ちゃんとマニュアルは読みましょう、という教訓ですね。

.idlファイルに定義したデータパケットの形式をfastrtpsgen.bat(中でjarが呼ばれているのでjavaプログラム)で、idlをコンパイルするとPub-Subテンプレートファイルが生成される、のでこれを各自で書き換えて使えということのようです。
main関数を乗っ取られた形で提供されるのが、とても気に入らないしスレッドを分けることが必須なのが多少厄介ですが、この枠組みに乗れば簡単に使うことはできるようです。
個人的には、CORBA時代の悪夢を思い出すようでアレルギーが発症しそうなのですが、stringだけをやり取りするように決めてしまって、stringの中をOSCパケットなどにして、openFrameworksなどから呼び出せるようにラップしてしまえば、何とか使えるようになりそうです。

まとめ

使えそうなことはわかったので、性能はどうなんだろう?ということを調べてみると、ちょうどいいことにZeroMQとの性能比較がまとめられていました。
www.eprosima.com
FastRTPSの方が良いよ、と言いたいのだと思いますが、ディレイもレイテンシもほとんど変わりませんね。メッセージサイズが大きいときにはややFastRTPSが有利で、小さいときにはZeroMQのほうが有利と読めます。
ロボットで使われるメッセージは、画像や音を除けば大半は短いコマンドなので、ZeroMQでいいじゃん、ということになります。
ネットワークの管理を厳密にやりたいときはFastRTPSの方がずっと良さそうです。
(が、正直、超絶面倒くさいから、当面はZeroMQでよくってROS2が本格的に動き出したら使わせてもらおう、という結論。)

今日の必要最小限パソコン

一応、日本のメーカーNEC製(中身はレノボに乗っ取られていますが)で、
MicrosoftのOfficeが付いていて、
拡張性は皆無だけど最低限度のスペックがあり、見た目がわりとカッコよくて、ぎりぎり頑張れるぐらいの値段、なので親に使わせるPCとかなら有り、かなぁ、と。

raspberryPi2/3に(オーディオ用では無い)複数の普通のDAコンバータmcp4725を接続して、4ch以上のアナログ出力を得る方法

以前書いた記事の続編です。
cvl-robot.hateblo.jp

mcp4725は、安価で簡単に12ビット分解能のアナログ出力を得ることができる良いICなのですが、I2Cアドレスが0x60,0x61の2つからしか選択できません。
したがって、素直に使うと2chの出力しか得られません。
このページでは、もっと多くの出力数が必要で困る場合の対処方法を紹介します。

FXMA2102のOEピンを制御して時分割でI2Cの出力先を切り替える

4chの場合の参考回路図を載せておきます。
今回の回路図では、5V出力ではなく3.3V出力ですので注意してください。VCCBに5V電源をつないでやれば5V出力も取れます。
もっと数を増やしていくこともできますが、FXMA2102の数に応じて応答時間がどんどん遅くなることに留意してください。
f:id:cvl-robot:20170125225552p:plain
スルーホール基板でも、ほとんどジャンパ飛ばすことなく回路が引けます。

参考ソースコード

2つのFXMA2102のOEピンをそれぞれ、4番ピンと11番ピンに接続した場合の例です。
I2Cバスを切り替えたときに、毎回EEPROMに焼きこまなくても、稼働中は出力電圧をちゃんと維持してくれます。

#include <stdio.h>
#include <unistd.h>
#include <wiringPi.h>
#include <wiringPiI2C.h>

#define BCM_4  4
#define BCM_11 11

void setDACVoltage(int deviceid, int voltage, int writeToEEPROM)
{
        int fd = wiringPiI2CSetup(deviceid);

        voltage = (voltage > 4095) ? 4095 : voltage;
        voltage = (voltage >> 4);

        if(writeToEEPROM)
                wiringPiI2CWriteReg16(fd, 0b01100000, voltage);
        else
                wiringPiI2CWriteReg16(fd, 0b01000000, voltage);

        close(fd);
}

int main(int argc, char* argv[])
{
        if(wiringPiSetupGpio() == -1){
                printf("wiringPi GPIO error\n");
                return -1;
        }

        pinMode(BCM_4, OUTPUT);
        pullUpDnControl(BCM_4, PUD_OFF); // PUD_OFF, PUD_UP, PUD_DOWN
        pinMode(BCM_11, OUTPUT);
        pullUpDnControl(BCM_11, PUD_OFF);

        while(1){
                for(int i=0; i<4096; i++){
                        digitalWrite(BCM_4, 0);
                        digitalWrite(BCM_11, 1);
                        setDACVoltage(0x60, i, 0);
                        setDACVoltage(0x61, i/2, 0);

                        digitalWrite(BCM_11, 0);
                        digitalWrite(BCM_4, 1);
                        setDACVoltage(0x60, 4095-i, 0);
                        setDACVoltage(0x61, (4095-i)/2, 0);
                }
        }
        return 0;
}

今日のオシロスコープ

今、趣味に毛が生えたレベルでオシロスコープを買うなら、これしか無いでしょう!
4chで5万円台は、(一昔前の感覚からすれば)驚異的です。
RIGOL(リゴル) デジタルオシロスコープ 50MHz 4ch 1GSa/s DS1054Z