Travis CI で継続的インテグレーション

前回の記事でPharo のプロジェクトを github.com で公開する方法について説明した。

今回は、 Travis CI を使ってテストを自動実行させる方法について説明する。

前提

  • Travis のアカウントを持っていること。
  • github.com と連携済みであること。
  • 自分のプロジェクトにテストパッケージを作っていること。

【重要】パッケージを保存する

先に進む前に、Monticello などでパッケージが保存されていることを確認する。テストも含め、いつでもパッケージ内容を元に戻せる状態にしておく方が安全・安心。

設定ファイルを作る

Travis CI でテストを実行させるために、リポジトリに2つのテキストファイルを作成する。

1つめは、 .smalltalk.ston である。内容は以下のようなもの。

SmalltalkCISpec {
  #loading : [
    SCIMetacelloLoadSpec {
      #baseline : 'MyProject',
      #directory : 'src',
      #platforms : [ #pharo ]
    }
  ]
}

MyProject の箇所は適宜修正する必要がある。

2つめは、 .travis.yml である。

language: smalltalk
sudo: false

notifications:
  email:
    on_success: never
    on_failure: always

os:
  - linux

smalltalk:
  - Pharo32-8.0
  - Pharo64-8.0

変更する必要のある箇所は、osのところと、smalltalkのところ。osは、他に osx が指定できる。(きっとwindowsも。詳しくは Travis CI のドキュメントを参照のこと)

smalltalk にはテストしたいイメージのバージョンを書く。このあたりの情報は以下のサイトで確認できる。

https://github.com/hpi-swa/smalltalkCI

上記を適当なテキストエディタで作成して、ローカルレポジトリのフォルダに保存しておく。

Shellで作業する

ローカルレポジトリ内で行った修正作業は、Iceberg では Commit できないので、Shell でコミットとプッシュを行う必要がある。作成したファイルをadd したあと、適当なコメントで commit して push する。

% git add .smalltalk.ston .travis.yml
% git commit -a -m 'add travis ci settings'
% git push

プッシュが完了したら Travis CI が自動的にテストを始めるはず。

Pharo で作業する

Pharo の環境にもどり、Iceberg でレポジトリのエントリを右クリックして Fetch を選ぶと、Up to date だったステータスが赤色の Detached Working Copy になる。

再度、右クリックして、 Repair Repository を選ぶ。そこで、Discard image changes and load repository version を選ぶ。ここで、Monticello などに保存していないパッケージの変更があると全て失われてしまうので、必ず保存しておく。問題なければ OK を押す。

続く画面で Checkout を押してレポジトリの状態が Up to date に戻れば完了。これ以降は、Iceberg でもコミット&プッシュ後に Travis CI が自動的に動き出すようになる。

Pharo のプロジェクトを github に登録するまで

SqueakMap とか、SmalltalkHub とか、Smalltalk のプロジェクトを残す方法はいろいろあったけど、いまは github.com に残すのがナウい。

今年、立て続けに何個かプロジェクトを登録したけど、毎回忘れてしまうので備忘のために残しておく。

参考にしたサイトはこちら

前提

  • 自分の github アカウントが存在すること。
  • 登録するプロジェクトのパッケージが作ってあること。(この記事では MyProject とする)
  • travis でCIするなら、テスト用のパッケージも作っておく。(推奨、この記事では MyProject-Tests とする)

転ばぬ先の杖

何かのトラブルでせっかくのコードが失われないように、Monticello Browser でパッケージ(Testsパッケージも)を保存しておく。(方法は省略)

github.com での仕事

レポジトリの作成

github.com にログインして、新しいレポジトリを作る。

ギッハブで新たなレポジトリをつくる
  • Repository name は適当なものでよし。
  • Description は適当なものでよし。
  • 無料アカウントなので Public 一択。
  • Initialize this repository with a README はチェックを入れておく。
  • Add .gitignore では、Smalltalk を選ぶ。
  • Add a license では、適当なライセンスを選ぶ。(自分はいつも MIT License)
  • おわったら Create repository をクリックする。

レポジトリURLのコピー

出来上がったばかりのレポジトリが開かれたら、そのgithubアドレスをコピーする。

ギッハブのアドレスをコピーしておく

Code をクリックして、 Clone with SSH の下の欄の git@github.com: のテキストを全てコピーするか、その隣のアイコンをクリックする。

Pharo での仕事

Iceberg でローカルレポジトリを作る

Pharo を起動して Tools – Iceberg を選んで Iceberg を起動する。

Iceberg 画面

右上のAddをクリックする。

ギッハブのレポジトリを登録する
  • Clone remote repository を選ぶ。
  • Remote URL に、先ほどコピーしたgithubレポジトリのアドレスを貼り付ける。

このままOKを押すと、いま使っているイメージのあるフォルダの下にローカルレポジトリが作られる。PharoLauncher を使っていて、イメージごと削除するのがイヤな場合は、必要に応じて Local directory の場所を変えておく。

問題なければ OK を押す。

ローカルレポジトリに src フォルダを作成する

ソース群を格納するためのsrc フォルダを作る。

Iceberg で作成したばかりのレポジトリのエントリを右クリックし、Extra を選んで Inspect … をクリックする。

ローカルレポジトリにフォルダを作る

Inspector が現れたら Raw タブを押して、コードエリアに以下を入力して do it する。

(self location / 'src') ensureCreateDirectory
Smalltalkerは必要なければShellに降りない

特に問題が生じなければ、 Inspector を閉じて構わない。

Meta データを生成する

次にプロジェクトのメタデータを生成するために、再度レポジトリのエントリを右クリックして Repair repository を選ぶ。現れたダイアログで、Create project meta-data を選んで、OKを押すと、Edit Project のダイアログが現れる。

Code directory に先ほど作成した src フォルダが選ばれており、Format が Tonel になっていることを確認したら OK を押す。

BaselineOf パッケージを作る

パッケージの依存関係を明確にするために、BaselineOf クラスのサブクラスを作成する。作成したいパッケージが MyProject なら、以下のようにクラス定義する。

BaselineOf subclass: #BaselineOfMyProject
    instanceVariableNames: ''
    classVariableNames: ''
    package: 'BaselineOfMyProject'

必要なインスタンスメソッドは2つで、1つ目は projectClass メソッドである。

projectClass
^ MetacelloCypressBaselineProject

もう一つは baseline: メソッドである。必要なパッケージに応じて内容を適宜修正する必要がある。他のパッケージを参考に依存関係を示せばよい。

baseline: spec
  spec
    for: #common
    do: [ spec
            package: 'MyProject';
            package: 'MyProject-Tests'
            with: [ spec requires: #('MyProject') ] ]

パッケージを追加する

最後に関係する全てのパッケージを追加する。レポジトリのエントリをダブルクリックして Working copy of のダイアログを表示し、右上の Add Packages ボタンでパッケージ(本体、テスト、BaselineOf)を全てチェックし、Add で追加する。(図のようにパッケージ名を入力して絞り込んで置くとラク)

パッケージを登録する

Commit + Push する

Iceberg の作業もほぼ終わり。Commit ボタンを押してコミット内容を確認して適当なコメントを書いたら、再度右下の Commit ボタンでコミット完了し、Push ボタンで github にプッシュする。

なお、下図のように Push changes to origin/main をチェックしておけば、コミットと同時にプッシュが行われる。

以上で github.com にパッケージがアップロードされた。

パッケージの内容を変更したときは、同様に Commit + Push すれば、その都度 github.com の内容も更新されていく。

MicroPythonでDF-ROBOT micro:bit Driver Expansion Board(v2.0)を使う

DF-ROBOTのmicro:bit Driver Expansion Board(v2.0)は、安価な割に機能の多いモーター制御ボードである。

https://www.dfrobot.com/product-1738.html

micro:bit 上のGPIOへのアクセスに加えて、8個のサーボモータと4個のDCモータを独立して制御することができる。(全てを使った訳ではないが)

ただ、制御のためのプログラムを作成するには MakeCode なりのブロックプログラミング環境を必要としており、MicroPython で使えるコードを見つけることができなかった。

https://github.com/DFRobot/pxt-motor

このボードは PCA9685 という定番のサーボドライバを使っており、I2C接続で何とかなると思ったら、何とかなったので記事にした。

後述するプログラムリストは、上のMakeCodeブロック対応コードに、SG90へ対応するよう修正を加えた Javascript のプログラムを、micro:bit用の MicroPython に移植しただけの単純なものである。参考にしたのはこちら。

https://github.com/nekoma-seisakusho/pxt-motor

ポイントは、サーボモータもDCモータもPCA9685を使って制御するということである。当初、DCモータドライバもI2Cで別に接続されているかと思って調べていたのだが、PWM入力のDCモータドライバに、PCA9685のPWM出力をつなげたもののようだ。ちなみにDCモータドライバとして、HR8835 が2個使われている。

ということで、MicroPython のコードは以下の通り。

import microbit
import utime

initialized = False

def i2cWrite(addr, reg, value):
    buf = [0, 0]
    buf[0] = reg
    buf[1] = value
    microbit.i2c.write(addr, bytes(buf))

def i2cRead(addr, reg):
    microbit.i2c.write(addr, bytes([reg]))
    ret = microbit.i2c.read(addr, 1)
    return ret[0]

def initPCA9685():
    global initialized
    i2cWrite(64, 0, 0)
    oldmode = i2cRead(64, 0)
    newmode = (oldmode & 127) | 16
    i2cWrite(64, 0, newmode)
    i2cWrite(64, 254, 121)
    i2cWrite(64, 0, oldmode)
    utime.sleep_us(5000)
    i2cWrite(64, 0, oldmode | 161)
    initialized = True

def setPwm(channel, off):
    if channel < 0 or channel > 15:
        return
    print("setPWM",channel,off)
    buf = [0] * 5
    buf[0] = 6 + 4 * channel
    buf[1] = 0
    buf[2] = 0
    buf[3] = off & 255
    buf[4] = (off >> 8) & 255
    microbit.i2c.write(64, bytes(buf))

def Servo(index, degree):
    if not initialized:
        initPCA9685()
    value = (degree * 10 + 600) * 4095 // 20000
    setPwm(16 - index, value)

def MotorRun(index, speed):
    if index < 1 or index > 4:
        return
    if not initialized:
        initPCA9685()
    speed = speed * 16
    if speed >= 4096:
        speed = 4095
    if speed <= -4096:
        speed = -4095
    dp = speed
    dn = 0
    if speed < 0:
        dp = 0
        dn = -speed
    ch = (4 - index) * 2
    setPwm(ch + 1, dp)
    setPwm(ch, dn)

使い方は、Servo(チャネル番号, 角度) で、チャネル番号に1から8を、角度は数値を与えれば対応するサーボモータが動く。また、MotorRun(チャネル番号, 速度) で、チャネル番号に1から4を、速度に -255 から 255 の数値を与えればDCモータが動く。

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

2020/8/12追記

単純多角形を三角形分割する簡単なアルゴリズムを実装したので、githubに公開した。リリースページにwindows向けバイナリを用意したのですぐに使うことができる。