プログラミング初心者向け！トイドローン「Tello」をScratchで飛ばす

仕事でTelloを使って職場を撮影するというミッションを与えられたので、先ずはプログラム制御できるようにScratchと繋いでみました。途中ちょっとつまずいたので備忘録がてら記事にしていきます。

##Telloとは
中国のスタートアップ企業Ryze Techが開発・販売しているトイドローンです。
価格の割に性能がよく、ホバリングや姿勢制御などある程度のことは自動でやってくれる上に軽くてカメラの性能も結構良い感じです。
![20210511_161037.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1264471/d21f1e42-ff72-4ee1-ab76-548a24d03d6f.jpeg)
こんな見た目です。

[Tello公式ホームページ](https://www.ryzerobotics.com/tello)

また、Telloには専用のSDKが用意されており、PythonやScratchなどで制御することができます。
今回は**[初心者でも簡単にプログラミングができるScratch](https://scratch.mit.edu/)**を使ってTelloを動かしてみました。

##手順
[Telloの公式HPからScratchの導入方法を見ると](https://terra-1-g.djicdn.com/2d4dce68897a46b19fc717f3576b7c6a/Tello%20%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/For%20Tello/Scratch%200320%20%E4%BF%AE%E6%94%B9/Tello%20Scratch%20README%200320.pdf)以下のように書かれています。

>Tello Scratch README
1. Visit https://scratch.mit.edu/download and follow the
instructions to install the Scratch 2.0 Offline Editor.
2. Download and install node.js from https://nodejs.org/en/.
3. Click here to download Tello.js and Tello.s2e, open the
terminal, go to the file directory where you saved the previous
files, and type "node Tello.js"
4. Open Scratch 2.0, hold the “Shift” key, click the “File”
menu, click "Import Experimental HTTP Extension," and select
"Tello.s2e" file in the file directory.
5. The Tello interface will be shown in Scratch under "More
Blocks.”

順番に見ていきましょう。

###①Scratch2.0(オフライン用)のインストール

先ずはScratchの導入からです。ただ、Scratchなら何でも良い訳ではなく。
■オフラインで動くもの
■Tello用の拡張モジュールを追加できるもの
を入れておく必要があります。これが「Scratch2.0（オフライン用）」です。

以下のリンクからダウンロードできます。
[Scratch2.0（オフライン用）のダウンロードページ](https://scratch.mit.edu/download/scratch2)

ダウンロードページに行くと、最初にAdobeAIRを導入しておくように促されますが、私の環境(Windows10)だとScratch2.0をインストールしたらAdobeAIRも一緒にインストールされました。
**（というか、先にAdobeAIRの最新版を入れたらScratch2.0のインストールができませんでした。。。）**

###②Node.jsの導入
次にNode.jsを導入します。これは次の手順でダウンロードするプログラムを動かすのに必要になります。
ダウンロードは以下からできます。
[Node.jsダウンロードページ](https://nodejs.org/ja/download/)
特にこだわりが無ければ推奨版を入れておけば大丈夫だと思います。
（ちなみに筆者の環境は15.12.0です。）

###③Tello用ファイルのダウンロード
公式が配布している制御用プログラムとScratchの拡張モジュールをダウンロードします。
ダウンロードリンクは以下です。
[モジュールダウンロード](https://terra-1-g.djicdn.com/2d4dce68897a46b19fc717f3576b7c6a/Tello%20%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/For%20Tello/Scratch_For%20Tello.7z)
※リンク切れの場合は公式のREADME本文からダウンロードしてください。

ダウンロードしたモジュールは7z形式で圧縮されているので、デスクトップなど適当な場所に解凍しましょう。
解凍すると中にファイルが3つあります。
・Tello.js
　　Scratchで書いた命令をTelloに伝える役割を果たすプログラムです。Node.js環境で動作させます。
・Tello.s2e
　　Scratch2.0にTello制御用のブロックを追加します。
・TelloChs.s2e
　　Tello.s2eの中国語版です。今回は使用しません。

###④ScratchにTello用のブロックを追加
先ほどインストールしたScratchを立ち上げましょう。
以下の様な画面が開くと思います。
![scratch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1264471/74822d74-86df-1181-7815-6712c7bd0c0d.png)

開いたら左上にある地球儀のようなマークを押して言語を「日本語」に変えましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1264471/caaecce2-3549-5426-4153-fc23c4161d35.png)

次に、Shiftキーを押しながらファイルを押してみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1264471/d01d3acf-cd08-e4cc-39cc-ef57ed485fa3.png)
画像のようなメニューが表示されるので「実践的なHTTP拡張を読み込み」を選択してください。
ファイルを選択するダイアログが表示されるので③でダウンロードした「**Tello.s2e**」を選択しましょう。
拡張機能が追加されて「その他」にTello用のブロックが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1264471/0f28d897-1e16-d233-e3f1-f4fe0b138de5.png)

これらのブロックを組み合わせてTelloを操作していきます。
主なブロックの使い方を表にしました。

|ブロック|概要|備考|
|:---|:---|:--|
|takeoff|離陸します||
|land|着陸します||
|fly up x cm|x cm 上昇します|xは20～500の範囲で指定可能|
|fly down x cm|x cm 下降します|xは20～500の範囲で指定可能|
|fly left x cm|x cm 左へ移動します|xは20～500の範囲で指定可能|
|fly right x cm|x cm 右へ移動します|xは20～500の範囲で指定可能|
|fly forward x cm|x cm 前へ移動します|xは20～500の範囲で指定可能|
|fly backward x cm|x cm 後ろへ移動します|xは20～500の範囲で指定可能|
|rotate x degree clockwise|x度分 時計回りに回転します|xは1～3600の範囲で指定可能|
|rotate x degree counter clockwise|x度分 反時計回りに回転します|xは1～3600の範囲で指定可能|
|flip x|縦方向に宙返りします。|xの指定で方向が変わります。l(左)、r(右)、f(前)、b(後)、bl(後左)、br(後右)、fl(前左)、fr(前右)|
|set speed to x cm/s|移動速度を x cm/sに設定します。|xは1～100の範囲で指定可能|

###⑤Telloに制御を伝えるためのプログラムを起動
Scratch側の準備が整ったら実際に動かす準備に入ります。
先ず、コマンドプロンプトを起動してください。
Windowsキー＋Rで「ファイルを指定して実行」の画面を立ち上げ、cmdと入力してエンターキーを押すと立ち上がります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1264471/5a9748ba-2f4d-9526-9b30-964cda547b73.png)

コマンドプロンプトが立ち上がったら先ほどファイルを解凍した場所まで移動しましょう。
コマンドプロンプトで
>cd [移動先のファイルパス]

と入力すると移動できます。
上記の方法でコマンドプロンプトを立ち上げると自分のユーザーフォルダ直下に居ると思いますので、

>C:¥Users¥[自身のユーザー名]＞cd "Desktop\Scratch_For Tello\Scratch_普通版"

と実行すると解凍先に移動できると思います。
※デスクトップ以外の場所で回答した人はその場所のパスを入力するか、「Scratch For Tello」フォルダをデスクトップに移動してから実行してみてください。

次に、node [ファイル名]でプログラムを実行します。


>C:¥Users¥[自身のユーザー名]¥Desktop¥Scratch_For Tello¥Scratch_普通版>node Tello.js

と実行するとTello.jsファイルに記載されているプログラムが実行されます。

ここで、次のようなエラーが出た方は少し別の手順を踏む必要があります。
（私はここで大分つまづきました・・・）

```
node:events:346
      throw er; // Unhandled 'error' event
      ^

Error [ERR_STREAM_WRITE_AFTER_END]: write after end
    at new NodeError (node:internal/errors:329:5)
    at ServerResponse.end (node:_http_outgoing:826:15)
    at Server.<anonymous> (C:\Users\伊藤翔\Desktop\Scratch_For Tello\Scratch_普通版\Tello.js:168:11)
    at Server.emit (node:events:369:20)
    at parserOnIncoming (node:_http_server:933:12)
    at HTTPParser.parserOnHeadersComplete (node:_http_common:129:17)
Emitted 'error' event on ServerResponse instance at:
    at emitErrorNt (node:_http_outgoing:706:9)
    at processTicksAndRejections (node:internal/process/task_queues:82:21) {
  code: 'ERR_STREAM_WRITE_AFTER_END'
}
```


**※エラーが出た方は以下の折り畳みを開いて中を確認してください。**
**<details><summary><font color="Red">エラー解消方法</font></summary><div>**

エラー内容に表示されているERR_STREAM_WRITE_AFTER_ENDですが、
これは既に閉じられたストリームに書き込みを使用としてエラーになっていることを示しています。
また、このエラーが何処で出ているのかを追っていくと、以下の関数にたどり着きます。

```JavaScript
http.createServer(function (request, response) 
{
	let url_params = request.url.split('/');
	if (url_params.length < 2) {
		response.end('Hello Tello.\n');
		return;
	} 
	let command = url_params[1];
	if(command=='poll') {
		let rst = '';
		for(let k in osdData) {
			rst += `${k} ${osdData[k]}\n`;
		}
		response.end(rst);
	} else if(command=='takeoff') {
		sendCmd('command');
		sendCmd('takeoff');
	} else {
		let cmd = url_params.slice(1).join(' ');
		console.log('[lhp_debug]request.url.split:  %s\n', cmd);
		sendCmd(cmd);
	}
	response.end('Hello Tello.\n');
}).listen(8001);
```

このプログラムの下から2行目の

```JavaScript
	response.end('Hello Tello.\n');
```

でエラーが出ていることが分かります。
これはプログラムの分岐によってresponse.end()が2回呼ばれているのが原因っぽいので、
以下のように修正してあげます。（コピーして関数ごと上書きで大丈夫です。）

```JavaScript
http.createServer(function (request, response) 
{
	let url_params = request.url.split('/');
	if (url_params.length < 2) {
		response.end('Hello Tello.\n');
		return;
	} 
	let command = url_params[1];
	if(command=='poll') {
		let rst = '';
		for(let k in osdData) {
			rst += `${k} ${osdData[k]}\n`;
		}
		response.end(rst);
	} else if(command=='takeoff') {
		sendCmd('command');
		sendCmd('takeoff');
	} else {
		let cmd = url_params.slice(1).join(' ');
		console.log('[lhp_debug]request.url.split:  %s\n', cmd);
		sendCmd(cmd);
		response.end('Hello Tello.\n');
	}	
}).listen(8001);
```

とりあえず、私はこれで元気に動いています。
※単純に行を消すだけだと、離陸以外のコマンドを指示した後に、結果が戻って来ず、次のコマンドを実行できない状態になります。

</div></details>

###⑥PCとTelloを接続
もう少しです。Scratchで作成したプログラムをTelloに転送するために、PCとTelloを接続する必要があります。
Telloの電源を入れると、PCからWifiネットワークが見つけられますので、接続しましょう。

###⑦Scratchでプログラムを開始
PCとTelloが接続できたらScratchで組んだプログラムを実行してみましょう。
上手くいけばTelloがプログラム通りに動くはずです。
動かない場合はコマンドプロンプト上にエラーが出ていないか確認してみてください。

##終わりに
長文になってしまいましたが、ScratchでTelloを動かす方法を書いてみました。
次回はTelloで撮影した画像をPC上に表示する方法を書きたいと思います。