kyumina

イベントの度に書きます

raspberry pi zero w & noderedで行うライントレース

学校の課題でライントレースをやっていたんですが、いくらか詰まったのでまとめておきます。

raspberry pi zero wのセットアップ

microHDMIがあると捗ります。が、一度設定したら使わないでしょうね。

一般的な人はノートパソコンから設定を行いたいと考えるでしょうから、次のサイトが参考になります。

qiita.com

が、bonjourのエラーかホスト名で接続できなくなることもあるので、私は最後手元にあったraspberrypi3(これは2でもいいと思う)にsdカードを指して設定を行いました。ブルジョアジーな解決策ですね。

接続できなくなった人にはこんな解決策もあるみたいですね。

qiita.com

Linuxが母艦の人にはこんな方法もあるそうです。

qiita.com

noderedのセットアップまでにやるといいかも

noderedというわけでも無いんですが、i2cをオンにする必要があります。

sudo raspi-config

↑で設定してください。

node-redのセットアップ

公式サイトのトップページに従い、npmから取ってくると面倒なので、公式docsよりここを読んでやりましょう。私みたいに二度手間にならないように。

Node-RED日本ユーザ会 : Running on Raspberry Pi

ip固定化するの?

私は面倒だったので、母艦のpcから毎度NetEnumやarpscanしてました。今考えれば、そっちのほうが面倒ですね。そもそもbonjour入れてたのでip指定しなくても入れましたしね。

forest.watch.impress.co.jp

qiita.com

nodered個人的悩みどころさん

これはうちの教員が悪いんですが、まるでソースコードを書かなくてもいいかのように学生に言いふらしてましたが、あくまでうまく使いこなせたらの話です。

そもそもjavascriptはおろかプログラミング初心者にはハードルが高いですね。それに加えて公式ドキュメントがわからない人にはわからないのもbadですね。

Q1: noderedのmsgって何?

ノードからノードへ流れる辞書型のオブジェクトです。noderedでは基本的にキー"payload"に対して、ノードの出力を保管します。

var msg={ payload:hoge };

なので、functionノードやchangeノードなどで、payload以外のキーに対して値を割り当てることができます。そういえば、msg中の値のことプロパティって呼ぶんですね。jsあまり使わないんで知りませんでした。(アトリビュート風に呼べるからかな??)

Q2: 複数の入力って出来ますか?

まず、複数のノードからの入力というのがnoderedではありえません。入力となるノードは非同期的に発火して、msgが流れてfunctionノードや出力に対して流れるからです。これに対し、単一のノードから複数の値を送ることはキーをそれぞれごとに作ることで出来ます。

下は複数のキーを設定する解決策です。

msg.color = "red";
msg.isflesh = true;
msg.accel = 9.8;
return msg;

他にも、payloadに配列をぶちこむ方法もあります。あまり好きではないです。

msg.payload=[msg.payload,"red",true,9.8];
return msg;

Q3: 複数の出力って出来ますか?

出来ます。正直Q2の解決策を応用するのもありですが、noderedのfunctionノードには出力数というものがあります。

f:id:kyumina:20180724014619p:plain

これを使うことで出力の出口が複数個出来ます。それぞれの出口への指定方法は、msgを配列で並べます。

return [msg1,msg2,msg3,msg4];

ここでいうmsgは、Q1で紹介した配列型のものです。任意の型には対応してません。

Q4:グローバル変数とかありますか?

ありまぁす!

global.set("ghoge",hoge); //global変数ghogeにhogeという値を追加
hoge=global.get("ghoge"); //global変数ghogeから値を読み出す

僕は最初のころQ1の仕組みを知らなかったのでこればかり使ってました。 実はglobalのところをflowと書くだけでフローに対して変数を割り当てることも出来るそうです。やったことないけど。

ライントレースカーを作る上で悩んだこと

ads1115というADコンバータを用いてセンサからのデータをデジタルに変換しましたが、noderedにはどうやらnode-red-contrib-ads1x15というのがあるらしく、それを利用するのが一般的だそうです。

http://node-red-contrib-ads1x15

が、

このnodeは本来adsでサポートしてる作動入力とシングル入力のうち片方しか対応せず、動作も不安定だったため(これは僕の設定ミス)、ファイルライクオブジェクトからデータをとってくる方式にしました。

5ドル!ラズパイ・ゼロ(Raspberry pi Zero)でIoT (5) A-Dコンバータの利用2 ADS1115 | 電子工作の環境向上

この記事の/boot/config.txtを編集する場面で1行目だけを記述すると、/sys/class/hwmon/hwmon0/device/にinx_inputというファイル(xは0~7)が出現するので、使いたい入力と対応するファイルをnoderedのfileノードを使って読み込みました。

さいごに

noderedはある程度プログラミングが出来たほうが使いやすいと思います。ので教員ゆるさん。

追記: 複数入出力の具体例

友人から「前進・後退・停止ボタンを押したときに一つのfunctionノードでモータの動作を変えるにはどうすればいい?」と聞かれたときのものです。

単純にボタンのpayloadをそれぞれ1,2,3とか指定して、msg.payloadをswitch文で分岐させて、それぞれに対応したmsg配列を作ってもよかったんですが、芸がないので、再利用しにくいものを作ったときの思い出です。

まず、モータドライバに対する入力は、

  1. 右モータに対して2つ
  2. 左モータに対して2つ

となっており、モータドライバの動作は、

f:id:kyumina:20180724021556p:plain

となっています。CW/CCWで前進、CCW/CWで後退するように作ってるものとします。また、右モータのIN1をR1、IN2をR2、左モータのIN1をL1、IN2をL2とします。

ここで、L1、L2、R1、R2はボタンの入力に対してそれぞれ、

  • 前進 → 1,0,1,0
  • 後退 → 0,1,0,1
  • 停止 → 0,0,0,0

となればいいです。なんか2進数に見えてきましたね。なので、それぞれを2進数とみて10進数に変換すると、上から、10、5、0です。

それぞれの値をdashboardの出力payloadに指定してあげて、functionにつなげます。だいたいこんな感じです。

f:id:kyumina:20180724022140p:plain

すると、ボタンが押されるたびにfunctionノードへ値が送られてくるので、これをパースして、出力へ送ります。コードは次のようになってます。

var values=[];
for (var i=0;i<4;i++){
    values.push({payload:1&(msg.payload>>>i)});
}
return values;

LSBだけを抜き出して、出力msg配列にセットして、ビットシフトしてというのを繰り返すことで、それぞれのビットに対応した値を4出力へと送っています。

f:id:kyumina:20180724022434p:plain

最後のノードの繋がりかたは、だいたいこんな感じですね。 最初のtrueはプログラム開始時に、停止を行う為のノードです。

一応コードを上げときます。

[{"id":"e5b9a49f.8f20c8","type":"ui_button","z":"2ad9e2be.28d47e","name":"","group":"f5ad6fa7.e9b01","order":0,"width":0,"height":0,"passthru":false,"label":"前進","color":"","bgcolor":"","icon":"","payload":"10","payloadType":"num","topic":"","x":310,"y":180,"wires":[["5f8bafcd.07a35"]]},{"id":"35ac18ee.8734e8","type":"ui_button","z":"2ad9e2be.28d47e","name":"","group":"f5ad6fa7.e9b01","order":0,"width":0,"height":0,"passthru":false,"label":"後退","color":"","bgcolor":"","icon":"","payload":"5","payloadType":"num","topic":"","x":310,"y":220,"wires":[["5f8bafcd.07a35"]]},{"id":"767b8f5b.39859","type":"ui_button","z":"2ad9e2be.28d47e","name":"","group":"f5ad6fa7.e9b01","order":0,"width":0,"height":0,"passthru":false,"label":"停止","color":"","bgcolor":"","icon":"","payload":"0","payloadType":"num","topic":"","x":310,"y":140,"wires":[["5f8bafcd.07a35"]]},{"id":"5f8bafcd.07a35","type":"function","z":"2ad9e2be.28d47e","name":"motor_con","func":"var values=[];\nfor (var i=0;i<4;i++){\n    values.push({payload:1&(msg.payload>>>i)});\n}\nreturn values;","outputs":4,"noerr":0,"x":530,"y":180,"wires":[["bfec39b0.66a638"],["8e8fcac5.d9e608"],["c96489d3.9fc3f8"],["2fe215ed.e7453a"]]},{"id":"bfec39b0.66a638","type":"ui_text","z":"2ad9e2be.28d47e","group":"f5ad6fa7.e9b01","order":0,"width":0,"height":0,"name":"","label":"R2","format":"{{msg.payload}}","layout":"row-spread","x":690,"y":120,"wires":[]},{"id":"8e8fcac5.d9e608","type":"ui_text","z":"2ad9e2be.28d47e","group":"f5ad6fa7.e9b01","order":0,"width":0,"height":0,"name":"","label":"R1","format":"{{msg.payload}}","layout":"row-spread","x":690,"y":160,"wires":[]},{"id":"c96489d3.9fc3f8","type":"ui_text","z":"2ad9e2be.28d47e","group":"f5ad6fa7.e9b01","order":0,"width":0,"height":0,"name":"","label":"L2","format":"{{msg.payload}}","layout":"row-spread","x":690,"y":200,"wires":[]},{"id":"2fe215ed.e7453a","type":"ui_text","z":"2ad9e2be.28d47e","group":"f5ad6fa7.e9b01","order":0,"width":0,"height":0,"name":"","label":"L1","format":"{{msg.payload}}","layout":"row-spread","x":690,"y":240,"wires":[]},{"id":"80749560.d2b998","type":"inject","z":"2ad9e2be.28d47e","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":"0","x":150,"y":140,"wires":[["767b8f5b.39859"]]},{"id":"f5ad6fa7.e9b01","type":"ui_group","z":"","name":"Group 1","tab":"20b08a2d.f3fca6","order":1,"disp":true,"width":"6","collapse":false},{"id":"20b08a2d.f3fca6","type":"ui_tab","name":"Tab 1","icon":"dashboard","order":1}]

さらに追記

普通は次のコードで達成するでしょうね

var msg0 = { payload:0 };
var msg1 = { payload:1 };
switch (msg.payload){
    case 0:
        return [msg0,msg0,msg0,msg0]; //停止
        break;
    case 1:
        return [msg1,msg0,msg1,msg0]; //前進
        break;
    case 2:
        return [msg0,msg1,msg0,msg1]; //後退
        break;
}
return msg;