Node-REDでTelloを動かすためのノードを作って公開してみた

この記事は個人開発 Advent Calendar 202013日目の記事です。

これまでNode-REDを使ってきましたが、どこか物足りなさを感じていました。

それは自分で自作ノードを作ってみたいということです。

最近ではいろんな人達がノードを公開しているので、探せばやりたいことを実現できると思ってそこで止まっていました。

それで話変わりまして、数ヶ月前にTelloを衝動買いして早速触ったことも無いScratchを使ってTelloを制御してみました。

これを動かしていたときにいろいろ思うことがありました。

  • Scratch動かしてるけど実際はローカルで立てたNode.jsにコマンドをHTTPリクエストしてる→準備がめんどくさい
  • Scratch2.0で動かす必要がある→Macでは起動自体めんどくさい
  • 同じNode.jsならNode-REDで動かしたほうが楽そう(小並感)

そこで、TelloをNode-REDで動かす事例を調べてみるとデフォルトのUDPノードを使った事例が紹介されていました。

flxy.jp

ありものを使うのもいいですが、やはりScratchのときのような手軽さを追求したいという気持ちが湧いてきました。

というわけでTelloをNode-REDで簡単に操作するためのノードを作ってみようと思いました。

要件定義

特に明確にしたわけではありませんが、Node-REDで使えるようにするなら外せない条件をリストアップしてみました。

  • Scratch版で提供している基本動作(離陸、着陸、上下左右など)は最低限実装する
  • 実行結果をmsg.payloadで出力する
  • ステータス(バッテリー残量、温度など)を出力する機能も実装する

これだけ入れておけば、ただのScratchを移植しただけではない、Node-REDを生かしたものになるんじゃないかなと思います。

公開したノード

node-red-contrib-telloという名前で公開しています。

READMEは英語ですが、Node-REDを使ったことある方なら多分雰囲気で動かせると思います。

このブログでも実装面の解説をしているので、なんとなく使い方が分かると思います

flows.nodered.org

実装

細かい実装については、以下のGithubレポジトリを眺めてもらうといいですが、ここではポイントを絞ってお話します。

github.com

基本動作

このノードではすべて裏側でUDP送信を行うsendCommand関数を用意しています。

この辺はScratchのノードを動かすときに使っていたNode.jsのコードを参考にしています。

離陸と着陸

これはべつに値が変化することでも無いので、それぞれつなげるだけ実行するようにしてみました。

値の受け取り(数値)

距離や角度を指定するノードではノードの設定で数値を指定することはできますが、入力欄を空欄にするとmsg.payloadで受け取った値をコマンドの入力として使います。

こうすることで例えばセンサーの値を受け取ってそれをTelloに反映するということをやることもできます。(そのうちサンプルを用意します)

f:id:KMiura:20201213230443p:plain

値の受け取り(セレクトボックス)

セレクトボックスを使ったノード(flipノードなど)はHTMLのセレクトボックスを使っているので値はvalue属性で担保しているので、Scratchのときよりもメニューが見やすくなったと思います。

ちなみにセレクトボックスの値は必ずどれか選択するようにしています。(これをmsg.payloadで受け取れるようにしたら、ノードのゲシュタルト崩壊してしまう…)

f:id:KMiura:20201213232536p:plain

stateノード

stateノード以外は挙動がほぼ同じなので、基本的にはフロントのHTMLを修正したりコマンドを送信する前の処理の修正してあとはコピペするだけでそんなに時間はかかりません。

ところが、このstateノードについては一番実装に苦戦しました。

というのもUDPはHTTPリクエストと違って受信と送信は一方通行になっています。

そのためUDPで受け取った値はグローバル変数に格納しています。

そのときにstateコマンド(ケツに?を入れるコマンド)を実行するとその返答はコマンド送信を完了したときに受け取るOKの後に連続でstateコマンドで聞いた値を返すようになっています。

これをどうやってノードを使って返そうか悩みましたが、単純にPromiseを使って時間差でコードを実行するようにして最終的にstateコマンドの結果を取れるようにしてみました。

JavaScriptって非同期のせいかスリープの処理にもクセがあって毎回実装辛いですね…

node.on("input", function (msg) {
      Promise.resolve()
        .then(function () {
          sendCommand("command");
        })
        .then(function () {
          return new Promise(function (resolve, reject) {
            setTimeout(function () {
              sendCommand(node.command);
              resolve();
            }, 500);
          });
        })
        .then(function () {
          return new Promise(function (resolve, reject) {
            setTimeout(function () {
              RED.log.info("Result state command: " + telloState);
              msg.payload = telloState;
              node.send(msg);
              resolve();
            }, 500);
          });
        });
    });

だいぶ力技ですが、これでちゃんと値が取れるようになりました。

トラブルシューティング

  • レスポンスが出力されず、文字列が空
    • Telloの動作が不安定な可能性があります。Telloを再起動するか、バッテリーをフル充電しましょう
  • ノードに値を入れたのに動作しない
    • No invalid imuと出る場合はimuセンサーが機能していないことがあります。Telloを再起動かバッテリーをフル充電しましょう。
    • 本体を触って熱かったら電源を落として冷めるまで待ちましょう。
  • そもそもtakeoffしない
    • Node-REDがTelloと接続できていない可能性があります。TelloとPCの接続を確認して問題なさそうでしたら、Node-REDを再起動して試してみましょう。

まとめ

今回は僕が初めてリリースしたNode-REDライブラリの話をしました。

最初は難しそうだったので、なかなか作る機会がなかったのですが、多少JavaScriptがわかっていればそんなに実装に困ることはなくて意外と簡単にできました。

改善点がありましたらレポジトリへのissueやPR大歓迎です!