Pharoでフィジカルコンピューティング(2)

これはSmalltalk Advent Calendar 2014の12/12の記事です。
前回の記事では、拙作のパッケージを用いてPharoでScratch Sensor BoardとGainerを扱う方法について述べましたが、今回はArduinoを扱う方法について紹介します。

Physical Etoys

Smalltalkでさまざまなデバイスを動かすことについては、既に先行しているプロジェクトがあります。
Physical Etoysは、(Pharoのベースとなった)Squeakのビジュアルプログラミング環境であるeToys上で、Arduinoを始めとする多様なデバイスを扱うことができるようにするプロジェクトです。
ウェブサイトでも紹介されているように、Physical Etoysでは、以下のようなデバイスをサポートしています。聞いたことのないデバイスもありますが、SpheroやKinect、LeapMotionなど比較的新しいハードウェアも扱えることがわかります。

  • Femisapien
  • Isobot
  • Kinect
  • Lego Dacta Control Lab
  • Puerto paralelo
  • Roboquad
  • Robosapien
  • Sphero
  • Wiimote
  • Leap Motion

SqueakとPharoは親戚同士で、eToys関連のクラス以外ならかなり利用することができます。そこで、今回はPhysical Etoysを使ってArduinoを扱う方法について解説します。

Arduino

Arduinoの準備

ArduinoとはFirmataプロトコルで通信するため、Arduino IDEでArduinoにFirmataのスケッチをUploadしておく。とりあえずAllInputsFirmataで良い。

Arduinoパッケージの導入

ArduinoのパッケージはSqueakSource3から入手できるが、ここにあるのは古いものなのでうまく動かない。
面倒だが以下のようにする必要がある。
まずTerminalなどでgithubのリポジトリをcloneする。

git clone https://github.com/GIRA/PE

Pharoを起動後、Monticelloを開いて+Repositoryボタンを押し、filetree://を選んでcloneしたフォルダ内のPE/stを選ぶ。
リポジトリが開いたら、左側のペインでArduino-Coreを選び、右側のペインでArduino-Core.packageを選んでLoadボタンを押す。
次にBrowserでArduinoクラスを開き、initializeメソッドの中身をコメントアウトする。
また、ArduinoTypeクラスを開き、以下の内容のinitializeメソッドを登録する。

initialize
    digitalPins := 2 to: 13.
    analogPins := 0 to: 5

最後にWorkspaceで以下をDo itする。

ArduinoType initialize.

これで準備は完了。

Arduinoオブジェクトの生成

Arduinoオブジェクトを生成するには以下のようにする。

arduino := Arduino on: Firmata new.

接続と解除

#connectOnPort:と#disconnectを用いる。具体例は以下の通り。ポート部分はOS等にあわせて適切なものを指定する。

arduino connectOnPort: '/dev/cu.usbmodem1421'.
arduino disconnect.

ディジタル・アナログピンの制御

Arduinoのディジタル・アナログピンを操作するには、AttachableDeviceのサブクラスのオブジェクトに対してArduino上のピンをattachする。
例えば、pin13に接続されたLEDを扱うには、LightEmittingDiodeクラスのオブジェクトを生成してArduinoにattachする。

led := LightEmittingDiode new.
led attach: (arduino digitalPin: 13).

接続されたLEDをオン・オフするには以下のようにする。

led value: 1. "オン"
led value: 0. "オフ"

アナログピンの状態を調べるのは以下のようにする。

analog := Potentiometer new.
analog attach: (arduino analogPin: 1).
analog value. "値を読み取る"

なお、我が家の環境ではStuduinoはうまく使えなかった。

Pharoでフィジカルコンピューティング(1)

これはSmalltalk Advent Calendar 2014の12/9の記事です。
教科書的なイメージだとSmalltalkでフィジカルコンピューティングするとは思えないけど、それっぽいことをする人向けの情報になればと思います。
ScratchのSensor Board(PicoBoardやなのぼーど)、Gainer、ArduinoなどをPharoで制御するパッケージがあるので、その辺の情報をまとめておきます。
Sensor BoardとGainerについては拙作のパッケージを、Arduino(Firmata)については他の方のパッケージを(別の記事で)紹介します。
なお、デバイスをPC等に接続するために必要なドライバやインストール方法などの情報は、適宜参照して適切なものを導入しておいてください。おそらく他のツール(なのぼーどならScratch、Gainer miniならProcessing等)で使えることを確認した後で以下を試された方が良いでしょう。

Sensor BoardとGainerのためのパッケージ導入

Sensor Boardは「なのぼーど」、Gainerは「Gainer mini」しか実績はありませんが、拙作のパッケージで扱うことができます。
Pharo 3.0でWorkspaceを開き、以下を選択してDo itします。

Gofer new
  url: 'http://smalltalkhub.com/mc/oohito/Fluo/main';
  package: 'Fluo Devices';
  load.

Fluo Devicesのパッケージには他のデバイス用のクラスも入っていますが、それらについて後日紹介する予定です。

Sensor Boardの使い方

インスタンス生成

ScratchSensorBoard new

インスタンス生成するにはScratchSensorBoardクラスにnewメッセージを送る。

SensorBoardへの接続(connect)と解除(disconnect)

ScratchSensorBoardオブジェクトに対し、ポート名を引数として#connectOnPort:メッセージを送ると接続できる。また、#disconnectメッセージを送ると解除できる。

| sensor |
sensor := ScratchSensorBoard new.
sensor connectOnPort: '/dev/cu.usbmodem'.
sensor disconnect.

上記のポート名はサンプルです。実際のポート名はOSにより異なります。

SensorBoardデータの取得(1)

以下のメッセージを送ることで、ScratchSensorBoardのデータを取得する。

  • #button — ボタンが押されればTrue、離されていればFalseを返す
  • #slider — スライダーセンサの値を0から100の範囲で返す
  • #sound — サウンドセンサの値を0から100の範囲で返す
  • #light — ライトセンサの値を0から100の範囲で返す
  • #registanceA,#registanceB,#registanceC,#registanceD — A,B,C,Dの抵抗値を0から100の範囲で返す

SensorBoardではセンサー入力の取得のみ対応しています。なのぼーどで拡張されたサーボの制御や超音波センサには対応していません。

SensorBoardデータの取得(2)

ブロックを設定することでScratchSensorBoardの情報を取得する。
1つのパラメータを持つブロックを引数として#receiveBlock:メッセージを送ると、SensorBoardからデータを受信する度に受信データを引数としてブロックが評価される。

| sensor |
sensor := ScratchSensorBoard new
sensor connectOnPort: '/dev/cu.usbmodem'.
sensor reeiveBlock: [:data| Transcript show: data; cr].

Sensor Boardの互換デバイスについて

阿部さんが日本で買えるScratchセンサーボードというページで互換デバイスを紹介しています。
なのぼーど以外のデバイスでも標準的な機能は使えると思います。

Gainerの使い方

インスタンス生成

Gainer new

インスタンス生成するにはGainerクラスにnewメッセージを送る。

Gainerへの接続(connect)と解除(disconnect)

Gainerオブジェクトに対し、ポート名を引数として#connectOnPort:メッセージを送ると接続できる。また、#disconnectメッセージを送ると解除できる。

| gainer |
gainer := Gainer new.
gainer connectOnPort: '/dev/cu.usbmodem1411'.
gainer disconnect.

上記のポート名はサンプルです。実際のポート名はOSにより異なります。

Gainerからのデータの取得(1)

以下のメッセージを送ることで、Gainerの入力ポートのデータを取得できる。

  • #button — ボタンが押されればTrue、離されていればFalseを返す
  • #digitalAt: — 指定したディジタルピンの値をTrue/Falseで返す
  • #analogAt: — 指定したアナログピンの値を0から255の範囲で返す

ディジタルピン、アナログピンの番号は0から3までの範囲で指定します。

Gainerからのデータの取得(2)

ブロックを設定することでGainerの情報を取得することもできる。
1つのパラメータを持つブロックを引数として#receiveBlock:メッセージを送ると、Gainerからデータを受信する度に受信データを引数としてブロックが評価される。

| gainer |
gainer := Gainer new
gainer connectOnPort: '/dev/cu.usbmodem1411'.
gainer reeiveBlock: [:data| Transcript show: data; cr].

Gainerへのデータの設定

以下のメッセージを送ることで、Gainerの出力ポートへデータを設定できる。

  • #led: — 引数に指定した真理値によってLEDを点灯(True)・消灯(False)する
  • #digitalAt:put: — 指定したディジタルピンを真理値で指定した状態にする
  • #analogAt:put: — 指定したアナログピンを0から255の値に設定する

ディジタルピン、アナログピンの番号は0から3までの範囲で指定します。

以上です。
Arduino(Firmata)については後日紹介します。

学園祭で用いたFluoグラフ

今年の学園祭ではTurtleBot1をタブレットで操作するというデモを行った。
TurtleBot1に付けたシャフトの先端にWebカメラを載せて、リアルタイムでタブレットに画像を送りつつ、画面上のボタンからTurtleBot1を操作するというもの。
Pythonとかで作れば何の変哲もないプログラムだけど、今回はFluoを用いて制御してみた。
(ちなみに画像はMJPG-Streamerを使った)
作成したグラフはこんな感じ。

TurtleBot1WebControl
TurtleBot1WebControl

左上にあるWebServerProxyが、8081番にきたリクエストを受け取って/moveというURLであれば、その引数cmdに与えられたleft, right, forwardという値を検出する。その後、それぞれの値に応じて、linear, angularというデータをROSの/cmd_vel_mux/input/teleopトピックにPublishする。
Webからの入力とROSへの出力は思ったよりシンプルになった。
やはり実演を伴うと、いろいろ工夫した中で現実的な解を見つけやすい。

MJPG-streamerの導入

通常の場合

本家サイトに行けばわかる話ですが、いちおう備忘録のため。
SVNでソースを入手してからmakeでビルドする。SVNのリポジトリは変更になったようなので古い情報とは異なっている場合がある。
Raspberry-piなどでビルドする場合には、 subversion imagemagick libjpeg-dev のパッケージをインストールしておく。

svn checkout svn://svn.code.sf.net/p/mjpg-streamer/code/ mjpg-streamer-code
cd cd mjpg-streamer-code/mjpg-streamer
make

動作を確認するには以下のようにする。なお、/dev/video0 は適切なビデオのデバイスファイルにする。

sudo ./mjpg_streamer -i "./input_uvc.so -d /dev/video0 -y" -o "./output_http.so -w ./www -p 8080"

Firewallの設定をしている場合はポート8080を許可するのを忘れずに。
http://localhost:8080 にアクセスすると、MJPG-streamerのページが表示され、カメラの画像が現れるはず。

Raspberry-piのカメラモジュールを使う場合

このサイトを参考にする。
ポイントは、

  1. raspi-configでカメラモジュールを有効にしておく。
  2. gitを使って別のリポジトリからソースを得る。

ことぐらい。

SerialPortの謎

「なのぼーど」をつなげたPC上で、Fluoのプログラミングを行っているのだが、例によってSerialPortで悩まされている。
ずっとMac OS X上で開発していて特にトラブルがなかったので見過ごしていたが、Windowsで利用すると問題が生じることに気づいた。
現象としては一度オープンしたポートをクローズすると再オープンできなくなるということ。
コードで示すと、2番目のopenPort:でエラーになる。

s := SerialPort new openPort: 'COM3'.
s close.
s openPort: 'COM3'.

これについては既に議論がなされていた。
http://forum.world.st/Package-for-manage-USB-RS232-td4744124.html
SerialPortのソースを眺めてみると、primitiveClosePortByName:に問題があるようだ。

primClosePortByName: portName
	
	^ nil"(DNS)"
	"self primitiveFailed."

わざわざself primitiveFailedがコメントアウトされているのは、コードを書いた人がこの問題を認識していて放置したってことか。
SerialPortではあたかもcloseが成功したかのように見せかけているのだが、実際にはこのプリミティブを呼んだ後、primOpenPortByName:を呼ぶと失敗し、SerialPort>>#openPort:の中でエラーと判定される。
先ほどのメーリングリストのやり取りではSerialPort>>#openPort:内のエラー判定部分を削除すれば動くとあり、試してみたらちゃんと2度目もオープンできた。該当箇所はここ。

	"result isNil ifTrue:[ self error:'Cannot open ', portId printString. ]."

このようにコメントアウトしておけば冒頭のトラブルは回避できる。
これでいいのか甚だ疑問であるが。
プラグインに問題ありそうだから暇を見つけて原因究明してみよう。
とかいってプラグイン関係はいつも挫折してるけど。
(ここから追記)
プラグインのソースを眺めて見ると、プラグインの方でハンドルの管理を行っていた。ポートを名前でなく番号でも呼べるようになっていたのはそのせいか。
WindowsではCOM3に対応する番号は3なので、以下のコードを試してみたらオープンもクローズも問題なく動くではないか。

s := SerialPort new.
s openPort: 3.
s primClosePort: 3.
s openPort: 3.
s primClosePort: 3.

ということでWindows上では番号でポートを指定すれば大丈夫なようだ。

Pharo 3.0 + Seaside 3.0 + jQuery Mobile for Seaside

ときどき宇宙に自分一人しかいないんじゃないかと思うことがある。
Pharoの話。
「いや、あの人もあの人も使っているはずじゃないか。俺一人じゃない」とあわてて思う。
ほんとかな。
Pharo 3.0にSeaside 3.0を載せるのは簡単だ。

MetacelloConfigurationBrowser open.

をDo itした後で、Seaside3をInstall Stable Versionすればいいのだから。
jQuery Mobile for Seasideを入れるのが問題だ。
サイトには、

Gofer it
  url: 'http://ss3.gemstone.com/ss/jQueryMobile';
  package: 'ConfigurationOfJQueryMobile';
  load.
ConfigurationOfJQueryMobile loadBleedingEdge.

すればいい風に書いてあるがウソっぱちだ。というより、ウソになってしまった。

Gofer it
  url: 'http://ss3.gemstone.com/ss/jQueryMobile';
  package: 'ConfigurationOfJQueryMobile';
  load.

だけして、(ConfigurationOfはしないで)ConfigurationOfJQueryMobileクラスのbaselineを覗いてみればわかる。
なんだConfigurationOfSeaside30は!こんなもんインストールしようとするな!!二重にSeaside3.0を入れそうになるじゃないか。OmniBrowserとかインストールするなよ。
まあ、これはjQuery Mobile for Seasideよりも、Seaside 3.0側の問題か。
回避するにはbaselineからSeasideCoreとSeasideTestsに関する記述を全て除去してから、

ConfigurationOfJQueryMobile loadBleedingEdge.

すれば良い。
途中で2度ほどWarningが出るが、見なかったことにしてProceed。
なんとかインストールできる。
でも、jQueryのサイトを表示させると下の方にエラーが表示されてしまう。
これを回避するには、JQMHtmlRoot>>closeOn:の一部をコメントアウトする。

closeOn: aDocument
  "aDocument scriptGenerator
   close: self on: aDocument pageId: self pageId."
  self writeFootOn: aDocument

本当にこれでいいのだろうか。
SeasideでjQuery Mobileを使っているのは宇宙で俺一人なのだろうか。
(そういやiliad使ってたときも同じこと考えたな)

ConfigurationOf無限地獄

いままで自分のプロジェクト用のConfigurationOfを一生懸命書いて来た。
あっちこっちのリポジトリに散らばったパッケージを自動的にインストールするように。
でも、なかなか思い通りにならないので諦めた。
ConfigurationOf無限地獄に陥ると本業が全然進まなくなる。
Goferで書いたテキストをコピペ&Do itした方がずっとすっきりする。
結局、ConfigurationOfはあくまで自分の管轄下のパッケージだけにした。
なんだかむなしい。
気分転換にサイトのテーマを変更してみた。なぜだか背景が表示されない。まあシンプルでいいか。

特定のTraitを使っているクラスを列挙する

Traitを削除しようとして、どのクラスに影響があるのか調べたかったのだけど、NautilusのAnalyzeにそんな項目がなかったので、Traitを使っているクラスを抽出するコードを書いた。

(Smalltalk allClasses select: [:each | each hasTraitComposition ])
  select: [ :class| class traitComposition includesTrait: 【Trait名】 ]

どうせすぐに忘れるので備忘用。

TurtleBot(CREATE)が動かなくなった

久しぶりにTurtleBotを動かそうと、接続しているマシンのubuntuをアップデートした。
イヤな予感がした通り、アップデート後にTurtleBotが動かなくなってしまった。
他と問題を切り分けるために、turtlebot_bringupではなく、create_nodeだけで起動してみたら、以下のような結果となった。

$ rosrun create_node turtlebot_node.py
[INFO] [WallTime: 1409911201.387855] serial port: /dev/ttyUSB0
[INFO] [WallTime: 1409911201.389007] update_rate: 30.0
[INFO] [WallTime: 1409911201.391291] drive mode: twist
[INFO] [WallTime: 1409911201.392122] has gyro: True
[INFO] [WallTime: 1409911201.559079] self.gyro_measurement_range 150.000000
[INFO] [WallTime: 1409911201.560099] self.gyro_scale_correction 1.350000
[ERROR] [WallTime: 1409911204.652150] Failed to open port /dev/ttyUSB0.  Please make sure the Create cable is plugged into the computer.
Failed to open port /dev/ttyUSB0.  Please make sure the Create cable is plugged into the computer. 

続きを読む →