調整の手間を省こう
この記事はMice Advent Calendar 7日目の記事です。
全日本について
更新してなかったので、この場で。
まずは、2017年度お疲れ様でした。全日本ではクラシック競技で我々Bustersの圧倒的勝利でしたね。
自分は4位とMice系トップは守りました。U宮さんとは今年こそ、日本人が優勝取り返しましょう話していましたが、ふがいない結果・・・。これで晴れて地球人に戻させていただけたと思います。いやしかし、10/19に基板が届いてから丁度1か月で4位なので、実は相当良いのでは??
あとakoちゃんは個人的にバスター対象にしたので、来年はシーズン通して完封します。
さて、今日の記事ですが、 web系わからない人には初見バイバイです。さようなら。
パラメータ調整の手間を軽減しよう。
もう、ただただ調整がツライ。 いうことで。ツライ要因を上げる。
- 理論がいまいち
- 調整パラメータはすべてハードコーディングされているため都度、ビルド⇒書き込みが発生していた。
前者は皆努力しても無駄なので、後者を専用システム構築をして改善する。
構成
図にはデータフラッシュとか書いてあるので、RX系マイコンを前提に話をしますが、ロボット側に不揮発性メモリがあればよいです。
socket.ioは双方向通信のために使用しているのは高速な更新と、サーバーからのレスポンスを反映を容易にするためです。
Node.jsを利用した理由は環境構築がものすごく楽だから。OS依存ないし。
コンソールGUI
コンソールGUIはWebアプリで作成。どの道どんなGUIでも内部でXML使っているし、HTML5でそこらへんに転がってるサンプルを使えば、おしゃれに作れる。
ここら辺使うのはどうだろうか
フレームワークにAngular.JSなど、双方向データバインディングがあると楽です。
- Angular.JS/Angular (2+)
- React
- Vue
- Riot
jQueryは不要。以下を参照。
You might not need jQuery.
socket.ioから送信するデータの体裁
{ "type":"update", "id":123456, "name":"TIRE", "value":24.5, "descript":"タイヤ直径[mm]" }
ロボットにはid(マイコン上の書き込み先アドレス)とvalueだけ渡せばよいですが、コンソール画面上には、変数名とそれが何なのかを示すため、nameとdescriptも送信。
サーバー
今回Node.jsにはwebサーバーはいらないです。expressを使って、socket.ioサーバーと同時に建てられますが、サーバーからhtml貰う必要ないので、webリソースは全てローカルファイル参照でいいでしょう。HTTPリクエストは飛ばさないので、CORSの考慮も不要。 コンソールとの通信はsocket.ioにすべて任せます。
以下、必要なnode_modulesのインストール。
npm install --save-dev socket.io serialport
上の2ついればOKです。serialportはpython前提になります。こちらを参照。
サーバーにはコンソールGUIからリクエストしたファイルをローカルファイルに書き込む。わざわざDB立てるのももったいない上、別環境での再構築や復元が面倒です。
var fs = require('fs'); function readFile(path, success) { fs.readFile(path, 'utf8', function (err, data) { if (err) { throw err; } success(data); }); } function writeFile(path, data) { fs.writeFile(path, data, function (err) { if (err) { throw err; } }); }
コンソールGUIから来たデータをファイルに書き込んだら、いよいよシリアル通信です。 自分は以下のようにデータを送信してます。コンソールからはnameなど色々送信してますが、ロボット上での解析が面倒なので、必要なデータだけ送ります。(json-cがあればもっと複雑な形式で送信できますね。必要なのかは別として)
// {key:value} {123456:24.5}
ロボット側の処理
サーバーからの送信データは、接頭に{ 、接尾に}、Key-Valueのセパレータにコロンを使用しています。
ロボット側には接頭文字を検知したら、受信バッファを初期化、以降のコロンと接尾文字を除き、バッファに貯める。接尾を検知したら、バッファから送信データを構築します。
key(id)には4バイト以内の自然数、valueには浮動小数として認識させます。
#define MAX_BUFFER 20 typedef struct { int index; int length; char buffer[MAX_BUFFER]; } s_key; volatile s_key keys, values; void detectChar() { SCI1.SSR.BIT.RDRF = 0; // 受信フラグを解除 char recieveData = SCI1.RDR; switch (recieveData) { case '{': recieveMode = KEY; flushData(); break; case '}': recieveMode = END; applyRecieveData(recieveMode, recieveData); break; case ':': recieveMode = VALUE; break; default: applyRecieveData(recieveMode, recieveData); break; } } void applyRecieveData(char type, char data) { if (type == KEY) { pushKey(data); } else if (type == VALUE) { pushValue(data); } else if (type == END) { mapping(); } else if (type == STAY) { } } void mapping() { const char* keyBuffer = &(keys.buffer); const char* valueBuffer = &(values.buffer); long key = atoi(keyBuffer); double value = atof(valueBuffer); save(key, value); } void pushKey(char key) { if (keys.length < MAX_BUFFER) { keys.buffer[keys.length] = key; keys.length++; } } void pushValue(char val) { if (values.length < MAX_BUFFER) { values.buffer[values.length] = val; values.length++; } } void flushData() { for (char i = 0; i < MAX_BUFFER; i++) { keys.buffer[i] = values.buffer[i] = 0; } keys.index = keys.length = values.index = values.length = 0; }
かつて、CとJSのソースを1つの記事に書いたヤツがいるだろうか・・・。
画面イメージ
途中まで作ってみました。左の写真には各センサー値を表示、値を見ながら、閾値を調整するという形式にしてみました。
今後の課題。
- パラメータの調整だけではなく、テストシナリオも同様にバイナリ書き込みではなく、フラッシュからの読み出しから実行したい。
- json-cを使う。
- 大量のデータを一度に更新できるようにする。