すこしふしぎ.

VR/HI系院生による技術ブログ.まったりいきましょ.(友人ズとブログリレー中.さぼったら焼肉おごらなきゃいけない)

蛇とstrとシリアルと 〜かめさんの場合〜

最近研究でシリアル通信でロボット動かすことが続いています.最近ではarduinoとかで外界のセンシング値をプログラムコードに取り込む手段としてシリアル通信つかったりしますね.自分は今回、接続した機器に対して命令を送る手段としてシリアル通信を使いました.言語はpython.なぜならミニマム機能だけに集中してサクッと作れそうだなぁと思ったからです.

思ってしまったのです.


pythonでシリアル通信といえばpyserialというモジュールが便利です.でまぁこれについてはそこそこいろんな記事があったりします.


ただし.

python2系に関しての情報が多い

という注意が必要です.


そうです.python2系→3系への移行に伴うあれこれにより,pyserialの利用でどハマりしてしまいました.
つわけで今回の記事はその備忘録です.python3系でシリアル通信したい人の手助けになればうれしいな.いるのかな.いないか.
(未来の自分への記事になるかもしれません)

pyserialの使い方.きほん.

さて、先述のpyserial.基本はとてもかんたんです.

まずはpipでインストールしましょう.

$pip install PySerial

なお,

$pip freeze
pyserial-py3k==2.6

みたいな感じでインストールは確認できますね.

では簡単なコードを.なお環境はMBA13inch,OSX 10.8.3です.

とりあえずコマンドラインで.

>>> import serial
>>> port = "/dev/tty.**" #自分の環境に合わせる
>>> baudrate = 9600 #ボーレートの指定
>>> ser = serial.Serial(port, baudrate) #インスタンス作成時にポートが開くみたい
>>> ser.write("hello")
>>> ser.close()

ポートとボーレートを指定してインスタンス作成するだけ.これでコードの世界と実世界がつながります.
例のようにwriteメソッドを用いればシリアルを通じてバイト列を送信できます.送られてくる情報を読みたいときはreadメソッドを使えばよいのです.詳しくは公式ドキュメントを参照です.
ポートやボーレートを後から指定することもできるみたいですね.

なお,接続するポートは

ls /dev/tty*

とかで確認しましょう.bluetoothとか明らかに違うものとかも出てきます.なんかおもしろいですね.

シリアルの機能は文字列(いわゆるchar型)を書き出す、もしくは読み込むというだけです.シンプルイズベストです.
文字列を書き出したければ

ser.write("hello")
ser.write(chr(120))

とかこんな感じ.もう名前そのままです.注目すべきは2行目.数字とかはchrでバイト列にかえてしまえばシリアルに送れるのです.
逆にchrにしないで書き込もうとすると怒られたりします.

実機を動かそう!~on python2~

今回のターゲットは2体います.一つはturtlebot.かめさんです.なんかかわいいです.ROSで動かせよ,って突っ込みは無しにします.急だったもので.
要はルンバをシリアルで動かそうって感じです.

もう一つはpantilt雲台です.こことか参考になります.余談ですがうちのラボの先輩です.[python 雲台]でトップに出てきたときはびっくりしました.

ではかめさんから動かしていきます.
まずはpython2系でシリアル通信.
こちらのルンバ仕様書とにらめっこして,どのようなコマンドを送ればよいのかを勉強します.
このpdfだと10ページ目くらいにあるやつが参考になりそうです.いわく

Serial sequence: [137] [Velocity high byte] [Velocity low byte] [Radius high byte] [Radius low byte]

だそうです.数字を順に送ればいいみたいですね.負値は補数で表現するようです.
ともあれとりあえず送ってみましょう.
(なお本来は移動コマンドを送る前にモード指定のコマンドを送る必要があります)

ser.write(137)
ser.write(255)
ser.write(200)
ser.write(1)
ser.write(240)

...しーん.

そりゃそうですね.さきほど言及したとおり,シリアル通信においてはデータはchrを用いてバイト列で送るのでした.

数字をchr()でバイト列に変換するのを忘れると怒られます.

あらためて.

ser.write(chr(137))
ser.write(chr(255))
ser.write(chr(200))
ser.write(chr(1))
ser.write(chr(240))

ういーん.

あ,うごいた!やった!なんだかんたんじゃん!
これなら簡単に扱えそう.よかったよかった.


...と.

ここから悲劇の始まりです.

実機で動かそう!~on python3~

今更ですがpythonには2系と3系があります.未だに2系も現役です. 今回さーばを叩くのにoscを使うことを選択、このライブラリの依存関係の結果python3系を扱うことにしました.なんか新しいのってかっこいいと思っちゃうじゃないですか.

いちいち書いたコード書き直すの面倒だな、と思ってたらなんと!python3系には2系のコードをかきかえるプログラムが用意されています.その名も2to3というプログラム.今回はじめてしりましたが,すこくべんりです.


とはいえ苦手はあるようなので、こちらとか
参考にするとよいかも.


さて、変換したコードでpyserialを叩いてみましょう.
とはいえどうやらシリアル部分のコードは変換されずそのまま使えるようですが,.

ser.write(chr(137))
.
.


ん...?うごかないぞ?

エラーメッセージではstrはシリアルに送れないよーといわれています.
いやさっきおくってたじゃないですか.

strがダメといわれたので、試しに数字のままおくってみよう.

ser.write(137)

..ダメだ.

うーん..

バイト列になってないのか?

bytesとかでキャストしてみる.

ser.write(bytes(137))


これもだめか.


なにがあかんのや..


ぐぐった.

こんなページをみつけた.


なるほど!
python2->3で,内部での文字列型がかわってたのね.そりゃきづかんわ.

てことでこんどこそbytes型を目指す.

stringをbytesに変換するにはencodeメソッドを使えばよいらしい.

シリアル書き込みの引数を、こんなのにして再挑戦

ser.write(chr(137).encode())

うごいた!!

しかし

一部命令でうごかなくなる.

なぜだ?

送ってる値をダンプしてみた

for i in range(255):
  print(i,chr(i).encode())
.
.
.
122 b'z'
123 b'{'
124 b'|'
125 b'}'
126 b'~'
127 b'\x7f'
128 b'\xc2\x80'
129 b'\xc2\x81'
130 b'\xc2\x82'
131 b'\xc2\x83'
132 b'\xc2\x84'
133 b'\xc2\x85'
.
.
189 b'\xc2\xbd'
190 b'\xc2\xbe'
191 b'\xc2\xbf'
192 b'\xc3\x80'
193 b'\xc3\x81'
194 b'\xc3\x82'
.
.

!?


途中から同じ値がl繰り返されているよ..?

192のあたりに注目.

serialで送る以上192は0xc0となるはずなのに??

どーやらchr.encode()でもダメなようだ..

あらためてbytes関数をよくみる.

さっき数字をbyteにかえようとしたときなんでダメだったんだろう.とおもいつつもっかい調べる.

python3.3 bytes関数

引数がリストなのか..てきとーにやってたからきづかなかった.

つわけでbytes関数つかってダンプする.

for i in range(255):
  print(i,bytes([i]))
.
.
188 b'\xbc'
189 b'\xbd'
190 b'\xbe'
191 b'\xbf'
192 b'\xc0'
193 b'\xc1'
194 b'\xc2'
.
.

あ、それっぽい!

実際これでかめさんもうごいてくれました.よかったよかった.

まとめ

ドキュメントはよく読みましょう

python2->3でバイト列の型がstrからbytesに変化していることに注意!

python2では

ser.write(chr(137))

python3では

ser.write(bytes([137]))

でいける!

新たなる問題

てなわけで無事turtlebotが動き,次は雲台を動かすことになるのですが..

まぁ一筋縄では行かないですね.

長くなってきたので続きはまた次回.

文字列と数字混合での送信です.