Tello-Pythonのサンプル「tello_state.py」を動かす

#はじめに
このページは，

[公式SDK「Tello-Python」を試そう](https://qiita.com/hsgucci/items/3327cc29ddf10a321f3c)

の１ページです．
全体を見たい場合は上記ページへお戻りください．

#概要

DJI公式のTello用Pythonサンプルプログラム「Tello-Python」のうち，

　[tello_state.py](https://github.com/dji-sdk/Tello-Python/blob/master/tello_state.py)

を試す方法を記述します．

#前提条件

既にホームフォルダにTello-Pythonがダウンロードされているという前提で話を進めます．

Linuxマシンであれば　`/home/(ユーザー名)/`　に，`Tello-Python`が既にあるという前提です．

詳しい手順は，
　[Tello-Pythonのダウンロード](https://qiita.com/hsgucci/items/3327cc29ddf10a321f3c#tello-python%E3%81%AE%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89)
に書いてあります．

#ディレクトリの移動
まずはコンソール(端末)を開き，以下のコマンドを打って，フォルダを移動します．

```bash:cd(change_directory)
cd Tello-Python
```
`ls`コマンドでフォルダの中にあるファイルを見てみると,

```bash:Linux：lsの結果
$ ls
LICENSE.md               Tello_Video
README.md                Tello_Video_With_Pose_Recognition
Single_Tello_Test        doc
TelloPython_FAQ(CH).txt  tello_state.py
TelloPython_FAQ.txt      tello_video_dll(ForWin64).zip
```

**<font color="blue">tello_state.py</font>**　というファイルがあることがわかります．

これを実行すれば試すことができるのですが，まずはＰＣをTelloに接続しましょう

#tello_state.pyの実行

では，tello_state.pyを実行してみましょう．
以下のコマンドを打ちます．

```bash:tello_state.pyの実行
$ python tello_state.py
```

Telloに正しく接続できていないと，何も表示されません．
Ctrl + cでプログラムを終了し，WiFi接続を確認してください．
サーマルシャットダウンして電源が落ちているかもしれません．


#実行結果

tello_state.pyの実行に成功した場合は，以下の画面が表示されます．
(無印TelloとEDUでは先頭データが異なります)

```python:実行中の画面(EDUの場合)
Tello State:
mid:-1;
x:0;
y:0;
z:0;
mpry:0,0,0;
pitch:0;
roll:-3;
yaw:0;
vgx:0;
vgy:0;
vgz:0;
templ:56;
temph:58;
tof:10;
h:0;
bat:92;
baro:-10.95;
time:0;
agx:-11.00;
agy:64.00;
agz:-998.00;
```
この画面が出ている時に，Telloを手で持って機体の姿勢を変えたり，上下に動かしたりしてみてください．
結構遅延がありますが，ピッチ角(pitch)やロール角(roll)，下方距離センサ(tof)，気圧(baro)などのデータが変化するのがわかると思います．

プログラムを終了するときは　Ctrl + c　です．


#表示されたデータ
まず，実行結果のデータが何だったのかというと，

・[Tello SDK 1.3のPDF](https://terra-1-g.djicdn.com/2d4dce68897a46b19fc717f3576b7c6a/Tello%20%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/For%20Tello/Tello%20SDK%20Documentation%20EN_1.3_1122.pdf)　の7ページ第５章
・[Tello SDK 2.0](https://dl-cdn.ryzerobotics.com/downloads/Tello/Tello%20SDK%202.0%20User%20Guide.pdf)　の6ページ

に書かれているものと同じです．

すなわち，以下の項目になります．

**Tello State(Telloの現在ステータス)** 
pitch = 機体のピッチ角度
roll = 機体のロール角度
yaw = 機体のヨー角度
vgx = X軸方向の速度
vgy = Y軸方向の速度
vgz = Z軸方向の速度
templ = 最小温度（単位は摂氏）
temph = 最高温度
tof = tof方式の距離センサの検出距離（単位はcm）
h = 高度（単位はcm）
bat = バッテリー残量（単位はパーセント）
baro = 気圧センサによる高度（単位はcm）
time = モーターがONになってからの経過時間
agx = X軸方向の加速度
agy = Y軸方向の加速度
agz = Z軸方向の加速度

※EDUのミッションパッド関連のデータは省きました．

では，どういう経緯でこのデータが表示されたのか，次項で解説します．

#Telloのシステム構成

Tello SDK [1.3](https://terra-1-g.djicdn.com/2d4dce68897a46b19fc717f3576b7c6a/Tello%20%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/For%20Tello/Tello%20SDK%20Documentation%20EN_1.3_1122.pdf) & [2.0](https://dl-cdn.ryzerobotics.com/downloads/Tello/Tello%20SDK%202.0%20User%20Guide.pdf)の２ページ目を読むと，
Telloとの通信は３種類あることが分かります．
すなわち下図の様になります．
![tello_system.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/77a7b4bd-d3d3-0098-5f17-5f68e07e7834.png)




###8889ポートの役割

8889ポートの役割は「**コマンドの送信＆応答の受信**」で，送受信双方向です．

Tello側に立っているUDPのサーバーで，Telloへの離着陸や移動，フリップなどのすべての命令は全てこの8889ポートを使って送信します．

まず最初にこのポートに`command`という文字列を送って，SDKモードにしないと他のコマンドを送っても反応しませんし，8890と11111ポートのデータ送信も始まりません．

また，Telloは15秒間コマンドが来ないと自動的に着陸してしまうので，定期的にコマンドを送るようプログラムを組んでおいた方が良いでしょう．

###8890ポートの役割

8890ポートの役割は「**Telloステータスの受信**」で，受信専用です．

ＰＣのプログラム側でUDPサーバー(ポート8890を使用)を立てると，Telloがそこに自分のステータス情報を送信してくれます．
ＰＣ側がサーバなので，データの送信タイミングはTelloが決めます．「わんこそば」状態で延々とデータが送られて来ます．

###11111ポートの役割

11111ポートの役割は「**Telloビデオストリーミングの受信**」で，受信専用です．

ＰＣのプログラム側でUDPサーバー(ポート11111を使用)を立てると，Telloがそこにカメラ映像(h.264でエンコードされている）を送信し続けてくれます．
h.264のデコードはＰＣ側がやらなければなりません．詳しくは「Tello_Video」で説明します．

※8889ポートに`streamon`(ストリーム オン)というコマンドを送らないと，映像の送信が始まりません．


##プログラム解説

「ポート8889に`command`文字列を送らないと何も始まらない」
「ポート8890でUDPサーバーを立てる．」
の２点を理解したうえで，PythonのUDP通信を学べば，tello_state.pyは難しくありません．

参考：
　　[pythonでsocket通信を勉強しよう](https://qiita.com/__init__/items/5c89fa5b37b8c5ed32a4)　・・・ソケット通信に関する基礎
　　[PythonによるUDP通信](https://qiita.com/akakou/items/e9fbcfc0c69cc957152e)　・・・今回の目的近いシンプルなプログラム
　　[Pythonで異なるPC間でsocket通信するときのIPアドレスは何にしたらいいですか？](https://teratail.com/questions/138411)　・・・UDPサーバを立てる時のホスト指定(' 'または'0.0.0.0')の意味


　
tello_state.pyに日本語でコメントをつけておきました．

```python:tello_state.py
import socket # UDP通信のため
from time import sleep # sleepを使うため
import curses # cursesモジュールを使って，画面表示をちょっとオシャレにする

INTERVAL = 0.2 # スリープ時間の設定．0.2秒

# 関数の定義
def report(str):
    stdscr.addstr(0, 0, str)
    stdscr.refresh()

# ここからプログラムスタート
if __name__ == "__main__": # "python tello_state.py"として実行されたかどうか判定している．
    # curses関連の初期化
    stdscr = curses.initscr()
    curses.noecho()
    curses.cbreak()

    # ステータス受信用のUDPサーバの設定
    local_ip = '' # '0.0.0.0'と同じ意味．すなわち「全てのネットワークインターフェイスを使う」
    local_port = 8890 # ステータス受信は8890ポート
    socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # ソケットを作成
    socket.bind((local_ip, local_port)) # サーバー側はバインドが必要

    # コマンド送信用の設定
    tello_ip = '192.168.10.1' # TelloのIPアドレス
    tello_port = 8889 # コマンドは8889ポートへ送る
    tello_adderss = (tello_ip, tello_port) # アドレスを作成

    # 最初に"command"を送ってSDKモードを開始しないとステータスが出てこない
    socket.sendto('command'.encode('utf-8'), tello_adderss)

    # Ctrl + cが押されるまで繰り返す
    try:
        index = 0 # indexにループした回数が入る
        while True: # 永久ループ
            index += 1
            response, ip = socket.recvfrom(1024) # 受信は最大1024バイトまで．受信結果はresponse変数に入る
            if response == 'ok': # 受信データがokだけだったら再ループ
                continue
            # 受信データに手を加える
            out = response.replace(';', ';\n') # セミコロンの部分に改行コードを挿入
            out = 'Tello State:\n' + out # 冒頭部分にちょっと装飾
            report(out) # 上の方に定義されているreport関数を使って画面出力する
            sleep(INTERVAL) # 一定時間待つ
    except KeyboardInterrupt:
        # ctrl + cが押されたあとの終了処理
        curses.echo()
        curses.nocbreak()
        curses.endwin()
```

#おわりに

TelloとはWiFiを介してUDPのソケット通信を行い，
「ポート8889に最初に`command`文字列を送る必要がある」
「ポート8890でUDPサーバーを立てれば，Telloの機体ステータスを受信できる」
がこのサンプルプログラムで理解できました．

とはいえ，機体の傾きが取れたところで全然おもしろくないですね．
次回は「Single_Tello_Test」で実際にTelloにコマンドを送って飛ばしてみましょう．