ListUI for as3を作った。

FLASHer その2 Advent Calendar 2016の20日目の記事です。

github.com

どんなライブラリなの!

 タッチでスクロールが出来てドラッグで並べ替えが出来る気持ちの良いListUIライブラリです。リストのセルや右側に出るスクロールバー等、すべてが拡張(自作)することを想定して作られているため、使用のハードルがやや高いかもしれません。デフォルトスキンなどが存在しません。作ってください!
 最近は特にflashはオワコン!とか言われてはいますが、AIR等を使ったアプリやソフト。そういったものは意外と世の中で生きています。(展示物系等でも多いです。)そういった部分で必要になったので作りました。
 as3がある程度分かる人に向けた解説になります。分かる人はgithubのサンプルのソース読んでください笑。

サンプルを用いて解説

サンプルを用意しました。
サンプルの機能:
・セルをタッチすると、そのデータがログに出る。
・右の方をドラッグで並べ替え。
・左の方クリックで削除。
・ドラッグや削除でデータに変更があるとログに出る。

f:id:takumus:20161219182319p:plain

demo : http://takumus.com/labs/list-ui-for-as3/samples/demo/
demoのソース : github.com

Listクラス

demoのメインクラスである、Demo.asから重要な部分を切り出しました。
gist.github.com  Listクラスは、初期化時にセルクラス、セルの高さ、セル同士の間隔、スクロールバークラスを要求します。 必須なのはセルクラス、セルの高さだけです。セルクラスについての説明は下で行います。
 Listにデータをセットするにはlist.setDataを呼びます。Arrayを渡すだけです。demoでは、A~Uまでを渡しています。
 セルから送られてくるメッセージを受け取るには、ListEvent.MESSAGEを追加します。メッセージについても下で解説します。
 Listのデータに変更があった場合、ListEvent.UPDATEが発火されます。この時、変更後のデータはlist.getData()で取得できます。

ListCellクラス

 demoのSimpleCell.asを見てみましょう。これはDemoのセルクラスです。削除ボタンや、ソートボタン、そしてラベルなどが配置されているのが分かります。List初期化時に渡していたセルクラスとはこれの事です。ListCellを継承して作ったオリジナルのセルです。このように渡すことで、Listはこれを生成し画面に配置してくれます。

resizeメソッド

 ListCellにはresizeというメソッドがあります。これはCellがレスポンシブであるために用意されたもので、Listのresize時に呼ばれます。SimpleCellでもresizeをオーバーライドして、テキストやボタンの位置、背景の再描画を行っています。基本的にはresizeはオーバーライドしてください。

setDataメソッド

 ListCellにはsetDataというメソッドがあります。これはセルのデータが更新された時に呼ばれます。今回、ListにsetData("ABCDEFGH...".split(""))という具合でStringの配列を渡したので、setData(data:CellData)のdata.dataはStringになります。(ジェネリクス使った方が良かったですね。今度直します。)
 例えば今回のSimpleCell.asで、setDataメソッドは
_label.text = data.data.toString();
と実装されています。dataはCellDataなのでdata.dataとしなければいけない事に注意してください。

クリック、タッチ、そしてmessage、remove、sort...

 細かい解説の前に、注意点があります。タッチイベント、マウスイベントはどちらも必ずListCellMouseEventを使います。bodyに対して通常のマウスイベントやタッチイベントを追加してはいけません
 その理由として、例えばスクロールのためにセルをドラッグしてリリースする過程で、通常のイベントは、タッチもしくはクリックを発行します。スクロールしたときはイベントが出ないようにしたり、その他おかしな挙動が起こらないようなListCellMouseEventを定義してあります。なのでこちらを使いましょう。  SimpleCell.asに、このような記述があります。これはCellがマウスや指によって押された時の処理です。 gist.github.com  まずListCellMouseEvent.MOUSE_DOWN時に、マウスが右の方(ドラッグアイコンの位置)だったら、beginSort()を呼んでいます。これはListCellのメソッドで、as3で言うstartDrag()のようなもので、ドラッグアンドドロップによるソートを開始するというものです。マウスやタッチが解除され次第、勝手に終了するので、ソートを開始させたいボタン等がListCellMouseEvent.MOUSE_DOWNされた時にbeginSort()を呼べばdemoのような挙動をします。ソート完了時、ListEvent.UPDATEを発火します。
 次に、ListCellMouseEvent.CLICK時に、マウスが左の方(削除アイコンの位置)だったらremove()が呼ばれています。これもListCellのメソッドで、このセルを削除するという命令を出すものになっています。これが呼ばれた瞬間削除されます。削除確認ボタンを点けたい場合は、自前で実装してください。※削除完了時に、ListEvent.REMOVEを発火します。
 また、マウスがセル自体をクリックした場合にmessage()が呼ばれています。これは、ListCellからListへメッセージを渡すことが出来るListCellのメソッドです。引数に何らかのデータを入れることも出来ます。
 今回、message("Hi. My data is [" + data.data + "]")となっていますので、demoではセルタッチ時にログにこのようなメッセージが出ると思います。これは、Listクラスのインスタンスに対し、ListEvent.MESSAGEでハンドル出来ます。Demo.asを見ていただければ分かると思います。

スクロールバーのカスタマイズ

 正確にはバー自体をドラッグでスクロールする事はできないので、ただのスクロール(する位置を示す)バーですが!
 これについては、SimpleScrollBar.asを見ていただきたいと思います。
 resize時にレンダリングするようにresizeをオーバーライドするだけで完了です。new List()するときに4番目にそのインスタンスを渡せばよいです。

ListCellのインスタンス生成数とデータ数の関係について(割と重要)

 今回のUIは、ListCellのインスタンス数を極力減らすように心がけています。画面内に収まる最低の個数だけ生成し、データ数が1万件あろうが画面に5セルしか表示できないのであれば5つのインスタンスしか生成しません。
 これにより、パフォーマンスの向上を実現しています。一つのセルインスタンスとデータは対になっている訳ではないので、設計時は注意が必要です。setDataが全てです。

謝罪

 ライブラリの開発経験も浅く、自分のライブラリの説明などしたことが無いので随分とごちゃごちゃしてしまいました。それでも使用してくださる方がおりましたら、長い時間をかけてサンプルを見て解読していただくか、私に連絡していただけると幸いです! → me(あっ)takumus.com

ArduinoでDVDドライブのステッピングモーターを回す(スイッチでキャリブレーションする)

DVDドライブのモーターの回路は↓で紹介しています。 blog.takumus.com ステッピングモーター制御ライブラリSuteppaについては↓です。 blog.takumus.com

 DVDドライブのモーターがSuteppaを使って回せる!という事は紹介しました。そこで今回はもっと実用的なデモを行います。

私が手に入れたDVDドライブの仕様

 キャリブレーションの前に、まず私の手元にあるDVDドライブの仕様を調べましょう。以下のようになっています。
f:id:takumus:20161016124250p:plain  センサー部分が上下に動くように、軸がネジ状になっています。また、一番下に一番端に来たことを調べるためのスイッチが付いていました。
 一番下から上まで大体488ステップ(1-2相励磁で)必要なことも分かりました。ではこの仕組みをそのまま利用してセンサーを上下させてみたいと思います。

キャリブレーションする

 ステッピングモーターが現在の角度を把握できない(ロータリーエンコーダーなしで)という事はご存知だと思います。そんなステッピングモーターを制御するにはまず初期角度を設定する必要があります。こういった処理をキャリブレーションと言います
 今回は、とりあえず最大まで回してスイッチに触れたらそこをhomeとする。みたいなキャリブレーションを行いたいと思います。

スイッチへの食い込み?

 注意しなければならないことがあります。既にスイッチに深く食い込んでしまった状態でキャリブレーションを行うと、キャリブレーション開始と同時にその地点がhomeになります。モーターの力とスピードでスイッチを押した時と、元々押されたスイッチとで、homeにずれが生じます。

スイッチへの食い込み対策

 それを解決する方法として思いついたのが、まずスイッチ方向へゆっくり近づくのは同じですが、スイッチを押したら少し逆回転させ、再びスイッチを探す。というものです。確実にスイッチをモーターの力とスピードで押す。こうすることで正確なキャリブレーションが行えます。

スイッチへの食い込み対策の利点

 この方法を取る利点はもう一つあります。キャリブレーションは正確に行うため、基本低速で行うべきなのです。しかし、バーがスイッチから遠かった場合、スイッチに到達するまでとても時間がかかります。そこで今回の方法のように2度スイッチを押すことで、1度目のキャリブレーションを高速化し、スイッチを押したら2度目はその場所から少し戻り、ゆっくりキャリブレーションをするという事が出来ます。

ソース

gist.github.com

動画

 右側にスイッチがあります。一度右端に行って、ちょこっと戻る動作が確認できると思います。これがキャリブレーションです。その後は、紙で作った矢印とマーカーの位置から、正確に動いていることが分かると思います。
 Arduinoをリセットしてキャリブレーションからやり直させる。というのを何度か行っています。 www.youtube.com

Arduinoでステッピングモーターを制御するライブラリ「Suteppa」

このライブラリ開発記録のその1はこちらです。

 ライブラリの改良等をしていたため少し書くのが遅くなりましたが、公開できるレベルにはなったので公開したいと思います。

github.com

Suteppaとは

 Suteppaは、Arduinoでのステッピングモーターの制御を助けるためのライブラリです。注意して頂きたいのは、このライブラリ自体にステッピングモーターを回転させる機能は無いという事です。少しややこしいですが、実際にモーターを回すコードを貼ります。

「1回転→1秒→逆に1回転→1秒」を繰り返すソース

gist.github.com

解説

void step(int d)
{
    static const int hs[8] = {省略};
    static int i;
    i += d;
    if(i > 7) i = 0;
    if(i < 0) i = 7;
    byte b = hs[i];
    digitalWrite(IN1, bitRead(b, 0));
    digitalWrite(IN2, bitRead(b, 1));
    digitalWrite(IN3, bitRead(b, 2));
    digitalWrite(IN4, bitRead(b, 3));
}

 stepという関数がありますが、これは1-2相励磁でステップさせるために用意した関数です。この関数をSuteppaのinitに渡しています。Suteppaは渡されたstepを使って回転させます。このような設計にした理由は、1-2相励磁以外のいろいろな方法に対応する為です。ほかの励磁方法や、またマイクロステップをさせたい場合もあると思います。そういった場合に備えてステップを刻む関数は自分で作って渡してもらう事にしました。
 引数のint dですが、これはステップ方向を示しています。Suteppaがstepを呼ぶとき、dには-11が入ります。dによって回転方向が変わるような設計にしてください。

s.init(4096, step);

 ここでSuteppaをinitしています。一回転のステップ数と、上で説明したstep用関数を渡しています。1回転のステップ数は用途によっては必要ですが、分からない場合や、DVDのモーターのような回転せずスライドするタイプの場合等は0でも良いです。これが必要になるケースは後ほど説明しますが、基本的に0で良いと考えてください。

//100ステップを加減速に使い, 開始速度は2000us
s.beginSmooth(100, 2000);
//通常速度は700us
s.setSpeed(700);

 beginSmoothメソッドは加減速をスムーズに行うための設定用メソッドです。ここでは、加減速に最大100ステップ(合わせて200ステップ)を使い、開始速度は2000usという設定にしています。
 setSpeedで通常の速度を設定します。つまり今回の場合、100ステップかけて2000usから700usまで加速し、100ステップかけて700usから2000usまで減速します。

※usが小さい方が早く、大きい方が遅く回るので注意です。

//4096ステップ回転
s.rotate(Suteppa::RELATIVE, 4096);
delay(1000);

//0ステップへ戻る
s.rotate(Suteppa::ABSOLUTE, 0);
delay(1000);

 rotateメソッドは第1引数で回転モードを、第2引数にはステップを指定ます。ステップは負にすれば逆回転します。
 Suteppa::RELATIVEは相対ステップで、Suteppa::ABSOLUTEは絶対ステップで回転させます。よって今回の場合、現在の位置から、4096ステップ回し、その後0ステップに戻る。という事になります。  第3引数を含めた詳しいrotateの解説は下で行います。以上でデモのソースコードの解説を終わります。

rotateメソッドとtick

 rotate(int mode, long step, bool sync)

Suteppa::RELATIVE

 相対ステップです。現在の位置から相対的にステップします。

Suteppa::ABSOLUTE

 絶対ステップです。デフォルト位置からのステップへ移動します。

Suteppa::ABSOLUTE_SKIP

 同じく絶対ステップですが、最短で移動します。たとえば、一回転360ステップのモーターの場合、270ステップ地点から、0ステップへ移動するためには、+90ステップするか、-270ステップするかの2通りがあります。このモードでは、+90して最短で移動します。注意すべきはここでステップは0になります。360にはなりません。

tick

 rotateメソッドのsyncは省略できます。デフォルトではtrueです。ただ、このままだとモーターが1台しか制御できないという問題点があります。以下がその例です。s1s2の2台のモーターを制御します。

Suteppa s1;
Suteppa s2;
//初期化省略
s1.rotate(Suteppa:ABSOLUTE, 100);
s2.rotate(Suteppa:ABSOLUTE, 100);
Serial.println("ok");

 しかし、これだとs1の回転が完了した後にs2が回転します。同時には回転しません。rotateメソッドが処理を止めてしまうのです。
 この問題を解決すべく、tickで回転を行うという方法を用意しました。

s1.rotate(Suteppa:ABSOLUTE, 100, false);
s2.rotate(Suteppa:ABSOLUTE, 100, false);
while(s1.tick() || s2.tick()){}
Serial.println("ok");

 こうするとs1s2が同時に回ります。tickはモーターを回すためのメソッドで、回転中はtrueを返し、完了すればfalseを返します。回転完了後にtickを呼び続けても平気です。それ以上回転することはありません。
 ソースを見ればわかりますが、rotateメソッドはsyncがtrueだった場合最後にwhile(tick()){}をしています。tickを呼ぶ周期は可能な限り短い方が良いです。最低でも50usごとには呼んでほしいです。それ以上だとスムーズな加減速が出来なくなります。

スピードとスムーズモード

 最初のサンプルコードでも使っていますが、スムーズモードがこのライブラリの特徴です。加減速を調整することで脱調を防ぐことが出来ます。またステッピングモーターを確実に最高速度で回すことが出来ます。

setSpeed(通常速度us);
beginSmooth(ステップ数, 開始速度us);

 開始速度usから通常速度usまでステップ数分のステップでスムージングします。

スムーズモードの有効化と無効化

s.setSpeed(900);
s.beginSmooth(100, 2000);
s.rotate(Suteppa::RELATIVE, 1024);
s.endSmooth();
s.rotate(Suteppa::RELATIVE, -1024);

 スムーズモードはbeginSmoothメソッドで有効化します。endSmoothメソッドを呼ぶことでスムーズモードを解除できます。上記の場合、1024ステップをスムーズモード(2000usから900usまでを100ステップでスムーズに変化)で移動し、スムーズを無効化したのち1024ステップ戻ります。

脱調する場合

 脱調は、急な加速や減速が原因なので、脱調する場合はステップ数を増やすか、開始速度usを下げます(数値を上げます)。ただし開始速度us通常速度usの差が開きすぎるのも良くありません。その分ステップ数を増やせばよいですが、そこらへんは調整してください。基本的にステッピングモーターが始動できる速度を開始速度usに当てはめるのが良いでしょう。

デフォルトスムーズ

 頻繁にスムーズモードを有効化したり無効化する場合、いちいち値をセットするのは面倒です。そこでsetDefaultSmoothを用意しました。

s.setDefaultSmooth(ステップ数, 開始速度us);
//色々省略
s.beginSmooth();
s.rotate(省略);
s.endSmooth();
s.rotate(省略);
s.beginSmooth();
s.rotate(省略);
s.endSmooth();

 一度defautSmoothをセットすればbeginSmoothするだけでスムーズモードを有効化できます。

基準点のセット

 setHomeメソッドを用意しました。このメソッドを呼んだ位置が0ステップとしてセットされます。キャリブレーションを行う時に使えます。例えば以下のように。

s.rotate(Suteppa::RELATIVE, -9999, false);
while(s.tick()){
    if(digitalRead(SW) == 1){
        break;
    }
}
s.setHome();
s.rotate(Suteppa::ABSOLUTE, 1024);

 先頭につけたスイッチ(SW)を押すまでマイナスへ回り続けます。tickごとにdigitalRead(SW)で先頭スイッチが押されたかを確認します。押されたらwhileを抜けて回転を中止。その位置をhomeにセット。その後1024絶対ステップで回す。という流れです。キャリブレーションも簡単に実装できますね。キャリブレーションのデモは別記事で詳しく書く予定です。

おまけ

static const int RELATIVE = 0;
static const int ABSOLUTE = 1;
static const int ABSOLUTE_SKIP = 2;

こうなっているので、長くて面倒な場合は数字でも良いです。