micro:witch で Tello を制御する(その2)

前回の記事に引き続いて、micro:witch で Tello を制御するまでの道のりを記す。

UART-UDP 部分

まずは、M5StickC で UART で受け取った文字列を UDP に流すプログラムを作成した。

https://github.com/EiichiroIto/m5stickcUartUdpBridge

プログラムの流れは以下の通りである。

  1. アクセスポイントをスキャンする
  2. Tello- で始まるアクセスポイントを見つけたらパスワードなしで接続する。
  3. (なければ終了)
  4. シリアル受信したらそのままUDPで送信する。
  5. 以降、繰り返す。

テストのため、Aボタンが押されたら “takeoff” を、Bボタンが押されたら “land” をUDP送信するようにした。(この辺りのインターフェイスは大幅に見直す予定)

Tello の電源をオンにした状態で、M5StickC の電源を入れると、無事 Tello のAPに接続した。ボタンAとボタンBで昇降することも確認できた。ついでに、USB-UART アダプタを使い、arduino のシリアルモニタからのテキスト入力による昇降も確認した。

radio-UART 部分

次に、radio-UART 部分に着手。micro:witch には UART のブロックがなかったので、通信カテゴリに追加した。

そのブロックを使って作成した、micro:bit (B) の radio-UART ブリッジのスクリプトがこちら。

無線をオンにして、少し待ってからUART の設定を行い、受け取った無線メッセージをUARTに書き込むだけの簡単なもの。

なぜUARTまで3秒待っているかというと、UART の出力先のピンを変更すると、USB経由でのUARTが使えなくなって、それ以降 micro:witch からコントロールできなくなってしまうため。最悪ファームウェアの転送からやり直さないといけない。多少のウェイトがあることで、micro:witch でstopかけて止めることができる。

わざわざ micro:bit を2台使わずに UART だけで Tello を制御することもできるのだが、 micro:bit の UART 使用には上のような注意点があるのと、radio で受信するようにしておけば、教室にある複数の micro:bit から1台の Tello を使うこともできるので、このような構成にしている。

micro:bit (B) と M5StickC の接続

先ほどの radio-UART のスクリプトでは、UART の TX を Pin0 に設定してある。また、UART-UDP のプログラムでは、RX を GPIO36 にしたので、これらのピンを互いに接続した。

加えて、M5StickC の GND と 3V3 を、micro:bit の GND と 3V にそれぞれつなげば、2つのデバイスを一体的に運用できるし、ある程度なら M5StickC のバッテリーだけで動かすことができる。

これで全てのお膳立てが整った。

micro:bit (A) から radio 経由で昇降させる。

最後に micro:witch で、radio 経由で文字列を送るプログラムを作って Tello を昇降できるようにした。後で気づいたのだが、メッセージを送った後で少し待たないと、メッセージを連結して受信してしまう。実際に使うときは、その注意が必要である。

ようやく、依頼いただいた先生に見てもらうことができる状態になった。

micro:witch で Tello を制御する(その1)

micro:witch を使って下さっている、とある学校の先生から、「micro:witchでドローンのTelloを操作したいのだけど、どうしたらよいか?」という問い合わせがあった。

調べてみると、UDP を使った簡単なプログラムで Tello を制御することができるらしく、MakeCode のプログラムを作って micro:bit から Tello を制御する例も見つかった。方法としては、micro:bit から bluetooth 経由でPCにデータを送り、PC側で受け取ったデータを UDP に流せば良いようだ。

残念ながら micro:witch が内部的に利用している MicroPython では、メモリ容量の関係から bluetooth のサポートがない。つまり、micro:witch でも bluetooth 関連のブロックを追加することはできない。

ある程度自由にドローンの制御を行うとするなら、Tello のコマンドを micro:bit で生成・送信できるようにするべきだろう。幸い、単なる文字列で一定の操作はできるようだ。一方で、 micro:bit がUDP通信できない以上、PCやそれに類する中継装置が必要となる。

そこで、以下のような構成を考えてみた。

  1. MicroPython の radio 機能を使って micro:bit (A) が文字列を送信するようなプログラムを micro:witch で作る。
  2. micro:bit (B) が文字列を受け取ったら、UART で送る。(radio-UART ブリッジ)
  3. M5StickC が UART で文字列を受け取ったら、UDP で送る。(UART-UDP ブリッジ)
  4. Tello が動く。

超面倒くさい流れではあるが、原理的には不可能ではなさそうだ。それだけでなく、メリットも少しある。

PCを使う場合には、Tello のアクセスポイントにwifiで接続させることになる。他にネットの接続装置がなければ、インターネットに接続されてないPCを扱うことになる。MakeCode などのプログラミング環境を利用するには、プログラミングのたびにネット接続を切り替える必要があって面倒だ。

その点、上の構成であれば 、常に Tello のアクセスポイントに接続するようにM5StickC のプログラムを作っておいたり、M5StickC から(B)に電源を供給することで、radio->UDP の一体的なモジュールができるので、PCレスで Tello の接続環境を構築できることになる。

実現までの課題は以下の通り。

  • micro:witch にUART関係のブロックを追加すること。
  • micro:witch で radio-UART のプログラムを作成すること。
  • M5StickC に UART-UDP のブリッジプログラムを作成すること。
  • Tello の実機を手に入れること(笑)

ということを思いついたのは昨日である。アマゾンで Tello を注文したら今日の夕方に届いた(アマゾン⇒ヤマトさまさま)ので、突貫工事でコーディングしてみたら、動いた。具体的には(A)で “takeoff” や “land” を radio 送信したら、Tello が離陸したり、着陸した。

ただ、時間切れでパッケージとしてまとめられなかったので、明日やる予定。

micro:bitにMicroPythonのファームウェアを転送する

この記事では、BBC micro:bit に MicroPython のファームウェアを転送する方法について説明します。

主な流れは以下の通りです。

  1. MicroPython のファームウェアを入手する。
  2. ファームウェアを micro:bit に転送する。

MicroPython のファームウェアを入手する

ブラウザで以下のサイトを開きます。

https://github.com/bbcmicrobit/micropython/releases

以下のような画面が表示されるので、Assetsの下にある「microbit-micropython-v1.0.1.hex」をクリックして、ダウンロードしてください。(この例では v1.0.1 が表示されていますが、最新のリリースを選んでください)

ファームウェアを micro:bit に転送する

micro:bit をPCに接続し、micro:bit がUSBドライブとして認識されることを確認してください。

さきほどダウンロードした「microbit-micropython-v1.0.1.hex」ファイルを、micro:bit として認識されたドライブにコピーしてください。

転送が終われば作業は完了です。

3D Turtle Graphics

3Dプリンタは便利な道具である。好きなものが自由に作れる。3Dモデリングソフトを使いこなすことができれば、であるが。

使い始めの頃から123D Designというソフトを使っていたのだが、慣れるのに時間がかかり、慣れたと思ったところで提供が中止されてしまった。別のアプリを使え、ということなので推奨されたものを使ってみたものの、すんなりと使いこなせる感じではなく、習熟には多少の時間がかかりそうだった。

同じ頃、3Dプリンタを商っている知人がいて、子ども向けのワークショップもやっていた。時間の限られたワークショップで、3Dプリンタののんびりした出力を待つわけにもいかない。一部は当日出力し、参加者全員分の出力は後日という形をとったようだ。

そうなるとワークショップの中心は3Dモデリングになる。ゼロから自在に形状を作れるのがモデリングソフトの良いところだが、小学生ではなかなか操作が難しいらしく、教えるのも並大抵の苦労ではなかったという。

「モデリングソフトの習熟がボトルネックになっちゃいますね」と笑って言った。

3D Turtle Graphics

ふと思ったのは、流行りのブロック型のプログラミング環境で3Dモデリングはできないのだろうかということだった。タートルグラフィックスは、画面で(あるいは地面で)絵を描きながら幾何学について学ぶ。タートルグラフィックスの3D版があれば、三次元幾何学について学びながら自由に形状を作り上げることができるのではないか。

「3D Turtle Graphics」で検索すると、さまざまなサイトがヒットする。Python や Javascript を使ったものが良く見つかるし、おそらくProcessingを使っても似たようなことはできるだろう。とはいえ、実際のコードを見るとすぐに取り掛かりたくなるような代物ではないように見える。(個人的な偏見に聞こえるかもしれないが)

取り掛かりやすさでいえば「スクラッチのようなブロックで」と考えられるし、以前にtwitterでそのような例を見た気がするのだが、何度調べてみても到達できない。ブックマークを残さなかったことを今でも後悔している。(誰か知っていたら教えて欲しい)

そんなようなことを3Dプリンタを使い始めてしばらく後に考えていた。ブロック型の3Dモデリング環境があればいいな、なければ、作ってみようかなどと。

実際に作ってみた

コロナ禍のせいで外出制限や職場の開始時期の遅れもあり、まとまった時間ができたのでこのプロジェクトに取り掛かることにした。一番の課題は3Dの表示環境や3Dソリッドの表現方法である。OpenGL を使うつもりだったのが、どうやってもWodenがうまく使えず、何度やってもPharoごと落ちてしまうことを繰り返して挫折していた。

そこで、 OpenGL は当面あきらめてイチから3D表示環境を作ることにした。車輪の再発明である。きょうび、そこまで低レベルを詳しく解説している書籍は少ないので、書棚に埋もれていた古典的な本を掘り出して、勉強しながら改めて実装することにした。

ちなみに参考にした書籍は以下のものである。

アルゴリズムとプログラムによるコンピュータグラフィックス〈2〉 (COMPUTATION & SOFTWARE SCIENCE) (日本語) 単行本 – 1984/2
https://www.amazon.co.jp/gp/product/4895013014/

たしか学生の頃、3Dグラフィックスの学習用に買ったのだと思うが詳細は覚えていない。アルゴリズムはALGOLで書かれている。Boldの大文字プログラムに面食らうが、コードは程よく分割されていて読みやすく説明も詳しい。当時の雰囲気が感じられて読み物としても楽しい。

(当然)ALGOL ではなく Pharo Smalltalk で実装するため、説明を読んで理解して、コードを組んで確かめての繰り返しとなった。ブロック部分は今までのプロジェクトでかなりこなれてきたコードを再利用して比較的簡単に実装できた。3月中旬から3週間程度で実装したが、2週間を3Dグラフィックス部分、1週間をブロックプログラミング部分に費やした感じである。

Knead3D

これが出来上がったアプリ画面である。MicroWiz のスタイルを踏襲(コピペ)している。要はブロックで作ったコードにより作られた3Dモデルが表示される。ご覧の通りモデルはワイヤーフレームだし、陰線処理も行っていない。このあたりはいつか Woden で OpenGL が使えるようになったら置き換えればいいだろう。

スクリプトは下のようなものである。

まず2D平面上でタートルグラフィックスで線画を描き、それを版として立体を造形する。上のプログラムで作られる立体は以下のようなものだ。

このやり方でもっと複雑な形状も表現できる。例えば、造形の前に版の位置を変更して繰り返し造形すれば、回転図形も作れる。

これは10角形をベースに、少しずつ角度を変えて造形する。出来上がりはこんな感じ。

ビデオも作ってみた。

STLフォーマット

3Dプリンタで出力できるように、STL フォーマットに変換する機能を追加した。STL フォーマットにするには多角形を三角形の集合にしなければならない。複雑な形状の多角形に対応できるさまざまなアルゴリズムがあるが、ちゃんと理解できていないので、手っ取り早く凸多角形のみ対応するような実装を行った。それによって生成したSTLデータから出力したのが記事冒頭の写真である。

作ってみて

まだ複雑な形状をたくさん作ったわけではないし、自分以外の誰も使っていないので「使い勝手」的なことはよくわからない。ただ、うまくブロックを設計すれば少ないブロックで多様なものが作れそうな気がした。

一般的なモデリングツールに比べれば機能も貧弱だが、スクリプトで書かれているため何度でも作り直すことができるので失敗が怖くない。今まではうまくできた形状を維持しようと腐心していたが、試行錯誤を繰り返すのが苦でなくなった。

例のように1つのスクリプトに作り込むこともできるし、別のスクリプトにしておいて出力時に合わせることもできる。凹多角形の出力やブーリアン処理など課題は山積しているが、地道に発展させていこうと思う。

Spec2入門(その6)

これはSmalltalk Advent Calendar 2019の記事です。

Spec2は、Pharo Smalltalk で採用されているUIフレームワークであるSpec の新しいバージョンです。Pharo 7.0 では Spec が使われていましたが、Pharo 8 では(ほぼ?)全てのUIが Spec2 で書き直されるようです。この記事では Spec2 を使った UI の実装方法について簡単に紹介します。

Squeak/Pharo Smalltalk では、UIの実現のために長らく Morphic を用いてきました。Morphic はシンプルで強力なフレームワークですが、複数のコンポーネントを組み合わせていくとアナーキーな実装をしがちで、リファクタリングや再利用の際に泣きたくなるような状況に陥りがちです。Spec/Spec2 はUIの構築に秩序をもたらし、コンポーネントの再利用がしやすく設計されています。

前回の記事では、Scratch 1.4風のファイル選択ダイアログに、親ディレクトリへ移動するボタンと、ディレクトリ階層がわかるドロップリストを追加しました。


前回つくったドロップリストはガワだけで機能がないので、今回はディレクトリ階層を表示したり、途中の親ディレクトリに移動できるような機能を追加します。

ドロップリストの内容を設定する

スクラッチではドロップリストをクリックすると以下のようにディレクトリ階層が表示されます。途中のディレクトリをクリックすると、そのディレクトリ内容がファイル一覧に表示されます。

まず、現在のディレクトから、ディレクトリ階層の文字列を取り出すメソッド directoryHierarchy を作成します。

directoryHierarchy
     ^ directory path segments
         withIndexCollect:
             [ :each :index | (String new: index withAll: Character space) , each ]

directory は FileReference のインスタンスなので、path で AbsolutePath オブジェクトに変換し、segments でパスを部分文字列の配列に変換します。その後、インデックスを使ってインデントの空白を作ります。

次にドロップリストにディレクトリ階層を設定するメソッド listCurrentContents: を作ります。現在のディレクトリ名が表示されるよう、インデックス位置を設定します。

listCurrentContents: aCollection
     listCurrent selectIndex: 0.
     listCurrent items: aCollection.
     listCurrent selectIndex: aCollection size

ディレクトリ階層は、ディレクトリが変更されたタイミングで設定するべきなので、directory: メソッドの中で設定するようにします。

directory: aFileReference
     directory := aFileReference.
     listEntries
         unselectAll;
         items: #();
         items: self getEntries.
     self listCurrentContents: self directoryHierarchy

ドロップリストで選択できるようにする

表示されたディレクトリ階層を選んだら、その親(系列の)ディレクトリに移動するようにしましょう。そのためには、ドロップリストが変更された時の処理を connectPresenters に追加する必要があります。

connectPresenters
     listEntries
         display: [ :m | self showEntry: m ];
         whenActivatedDo: [ :selection | self entriesChanged: selection ].
     buttonParent action: [ self changeParentDirectory ].
     listCurrent
         whenSelectedItemChangedDo: [ :selection | self currentChanged: selection ]

ここでは、ブロック引数をつけて whenSelectedItemChangedDo: メッセージを送っています。これで、ドロップリストの項目が選択されたタイミングで 、選択された項目の文字列を引数として currentChanged: メッセージが送られるようになります。

currentChanged: メソッドは以下のように定義します。

currentChanged: aString
     | dir |
     listCurrent selectedIndex = 0
         ifTrue: [ ^ self ].
     dir := directory.
     directory path segments size - listCurrent selectedIndex
         timesRepeat: [ dir := dir parent ].
     self directory: dir

ディレクトリのパスの個数から選択された位置を引いた回数だけ親ディレクトリをたどって、選ばれたディレクトリを特定しています。不格好なやり方ですが、とりあえず動きます。

ドリップリストの項目を選択すれば親系列のディレクトリに移動できるのですが、ドロップリストにリストを設定するたびに上のメッセージが送られることになるため、初期化などの際にも不用意なメッセージ送信が発生してしまいます。そこで、ドロップリストの生成時に startWithoutSelection を送ることで、これを回避します。

initializePresenters
     listEntries := self newList
         beSingleSelection;
         activateOnDoubleClick;
         items: self getEntries.
     buttonParent := self newButton
         icon: (Smalltalk ui icons iconNamed: #up).
     listCurrent := self newDropList startWithoutSelection.
     (以下略)

初期化のための調整

ドロップリストに関する最後の調整をします。 initialize 時に directory 変数を初期化していますが、これではドロップリストの内容が設定されないので、初期化のタイミングを後にずらすことにします。

まず、最初に作成した initialize メソッドを削除します。その上で、 initializePresenters に以下の変更を加えます。

initializePresenters
     listEntries := self newList
         beSingleSelection;
         activateOnDoubleClick.
     buttonParent := self newButton
         icon: (Smalltalk ui icons iconNamed: #up).
     listCurrent := self newDropList
         startWithoutSelection.
     self directory: FileSystem workingDirectory.
     self focusOrder
         add: listCurrent;
         add: buttonParent;
         add: listEntries

directory: メッセージを使うことで、ファイル一覧もディレクトリ階層もあわせて初期化されることになります。

Spec2入門(その5)

これはSmalltalk Advent Calendar 2019の記事です。

Spec2は、Pharo Smalltalk で採用されているUIフレームワークであるSpec の新しいバージョンです。Pharo 7.0 では Spec が使われていましたが、Pharo 8 では(ほぼ?)全てのUIが Spec2 で書き直されるようです。この記事では Spec2 を使った UI の実装方法について簡単に紹介します。

Squeak/Pharo Smalltalk では、UIの実現のために長らく Morphic を用いてきました。Morphic はシンプルで強力なフレームワークですが、複数のコンポーネントを組み合わせていくとアナーキーな実装をしがちで、リファクタリングや再利用の際に泣きたくなるような状況に陥りがちです。Spec/Spec2 はUIの構築に秩序をもたらし、コンポーネントの再利用がしやすく設計されています。

前回の記事では、Scratch 1.4風のファイル選択ダイアログに、ディレクトリ移動の機能を追加しました。


前回はファイル一覧でディレクトリ名をダブルクリックしたらサブディレクトリに移動する機能を付けましたが、それだと深くディレクトリに入っていくだけなので、今回は親ディレクトリに移動するボタンを追加します。また、現在のディレクトリ階層がわかるようなドロップリストも追加します。

ドロップリストとボタンを追加する

新しくボタンとドロップリストを追加するので、それぞれを格納するインスタンス変数を加えてクラスを再定義します。

SpPresenter subclass: #SpecFileListSample
     instanceVariableNames: 'directory listEntries buttonParent listCurrent'
     classVariableNames: ''
     package: 'SpecSample'

追加したのは buttonParent と listCurrent で、それぞれ親ディレクトリへの移動ボタンとディレクトリ階層を表すドロップリストを格納します。

いつものように initializePresenters で、それぞれのウィジェットを初期化します。

initializePresenters
     listEntries := self newList
         beSingleSelection;
         activateOnDoubleClick;
         items: self getEntries.
     buttonParent := self newButton
         icon: (Smalltalk ui icons iconNamed: #up).
     listCurrent := self newDropList.
     (以下略)

以前に紹介したとおり、ボタンは self newButton で生成します。また、ドロップリストは self newDropList で生成します。ボタンには上矢印のアイコンを付けておきます。

ボタンの機能の設定は、 connectPresenters メソッドで行います。ボタンが押されたら changeParentDirectory メッセージを送って親ディレクトリに移動するようにします。(太字部分を追加します)

ボタンの機能を設定する

connectPresenters
     listEntries
         display: [ :m | self showEntry: m ];
         whenActivatedDo: [ :selection | self entriesChanged: selection ].
     buttonParent action: [ self changeParentDirectory ].

changeParentDirectory のメソッドを実装します。単にルートディレクトリでなければ親ディレクトリに設定しなおすだけです。

changeParentDirectory
     directory isRoot ifTrue: [ ^ self ].
     self directory: directory parent

レイアウトを修正する

レイアウトを Scratch 1.4 のダイアログボックスのように変更します。

上の図では、ファイル一覧の上側にドロップリストとボタンが一列にならんでいます。このようなレイアウトを実現するには、クラスメソッドの defaultSpec を以下のようにします。

defaultSpec
     ^ SpBoxLayout newVertical
         add:
             (SpBoxLayout newHorizontal
                 add: #listCurrent;
                 add: #buttonParent;
                 yourself);
         add: #listEntries;
         yourself

SpBoxLayout newVertical で生成された縦並びのレイアウトオブジェクトに、SpBoxLayout newHorizontal で生成された横並びのレイアウトオブジェクトを追加して、そこでドロップリストとボタンを追加しています。

このレイアウト設定で表示させると、上のように高さや幅が均等な状態になって不格好です。そこで、子ウィジェットの幅や高さを制限するようにします。

defaultSpec
     ^ SpBoxLayout newVertical
         add:
             (SpBoxLayout newHorizontal
                 add: #listCurrent;
                 add: #buttonParent withConstraints: [ :c | c expand: false ];
                 yourself)
             withConstraints: [ :c | c expand: false ];
         add: #listEntries;
         yourself

withConstraints: メッセージをレイアウトオブジェクトに送ると、幅や高さなどを細かく設定できます。width: や height: で直接値を指定することもできますが、ここでは expand: false メッセージを送ることで幅や高さを拡張しないようにしています。

ウィジェット間にスペースを入れる

今度は上限のウィジェットが詰まりすぎなので、間にスペースを入れます。

defaultSpec
     ^ SpBoxLayout newVertical
         spacing: 4;
         add:
             (SpBoxLayout newHorizontal

spacing: メッセージでウィジェット間のスペースを調整できます。これで下図のようにすっきりしました。

右側のボタンを押せば親ディレクトリに移動することも確認できます。

親ディレクトリに移動するボタンの追加は以上です。リストボックスの機能は実現できませんでしたので、次回に回したいと思います。

Spec2入門(その4)

これはSmalltalk Advent Calendar 2019の記事です。

Spec2は、Pharo Smalltalk で採用されているUIフレームワークであるSpec の新しいバージョンです。Pharo 7.0 では Spec が使われていましたが、Pharo 8 では(ほぼ?)全てのUIが Spec2 で書き直されるようです。この記事では Spec2 を使った UI の実装方法について簡単に紹介します。

Squeak/Pharo Smalltalk では、UIの実現のために長らく Morphic を用いてきました。Morphic はシンプルで強力なフレームワークですが、複数のコンポーネントを組み合わせていくとアナーキーな実装をしがちで、リファクタリングや再利用の際に泣きたくなるような状況に陥りがちです。Spec/Spec2 はUIの構築に秩序をもたらし、コンポーネントの再利用がしやすく設計されています。

前回の記事から、Scratch 1.4風のファイル選択ダイアログを作っています。


今回は前回のファイル選択ダイアログに、ディレクトリ移動の機能を付けます。具体的にはファイル一覧でディレクトリ名をダブルクリックしたらサブディレクトリに移動する機能を加えます。

一覧でのダブルクリックに反応する

ファイル一覧は self newList で生成されるリストウィジェットで表示しています。リストウィジェットは、一覧を操作するための様々な機能を備えています。

デフォルトで生成されるリストウィジェットは項目を複数選択できるものですが、今回の例には合わないので項目を1つだけ選択できる(beSingleSelection)ようにします。また、ダブルクリックに反応できる(activateOnDoubleClick)よう、 initializePresenters を太字のように変更します。

initializePresenters
     listCurrent := self newDropList.
     listEntries := self newList
         beSingleSelection;
         activateOnDoubleClick;
         items: self getEntries.

項目をダブルクリックされたときに反応するには、ブロックを引数にしてリストウィジェットへ whenActivatedDo: メッセージを送って設定します。これは connectPresenters で行います。

connectPresenters
     listEntries
         display: [ :m | self showEntry: m ];
         whenActivatedDo: [ :selection | self entriesChanged: selection ].

これで、項目がダブルクリックされたときに entriesChanged: メッセージが送られるようになるので、次は entriesChanged: メソッドを定義します。

entriesChanged: aSelectionMode
     | dir |
     dir := aSelectionMode selectedItem.
     dir isDirectory
         ifFalse: [ ^ self ].
     self directory: dir asFileReference

entriesChanged: の引数は、SpSingleSelectionMode のインスタンスで、ダブルクリックされた項目に関する情報を保持しています。このオブジェクトに対して selectedItem を送れば、項目のオブジェクトが得られるので、ダイアログの新たなディレクトリとして設定します。

最後に directory: メソッドを定義して、作成しているダイアログのディレクトリを変更し、リストウィジェットの内容を再構築します。

directory: aFileReference
     directory := aFileReference.
     listEntries
         unselectAll;
         items: #();
         items: self getEntries

以上で、リストウィジェットのディレクトリ(カッコで囲まれたエントリ)をダブルクリックすると、サブディレクトリが表示されるようになりました。