お題

トイドローン Tello のプログラミング記事です。

前回の記事での積み残し課題を改善します。

発生した問題

前回発生した問題は「コマンド実行中に動画が更新されない」でした。

記録された動画を確認すると、コマンド実行していないときは問題ないのにコマンド実行中はピタッと動画再生が止まっていることがわかります。そしてコマンド実行が終了するとその時点から動画記録が再開されるように見えます。

原因調査

発生した現象から推測できるのは「コマンド実行が動画記録を妨げている」ということです。 そこで原因調査のためにコマンド実行関連のソースを確認してみます。関係がありそうな部分だけを抜き出すと以下のようになっています。

while True:
    # (1) 画像の取得
    image = info.get_image()

    # (2) 取得した画像の表示
    window["image"].update(data=photoImage)

    # (3) コマンド送信
    sock.sendto(msg.encode(), ("192.168.10.1", 8889))

    # (4) コマンド実行結果の受信
    data, _ = sock.recvfrom(1024)

このプログラムは「 (1) で取得した1枚の画像を(2)でウインドウ上に表示する」という処理を 高速に 繰り返す (while True) ことで動画再生を行っています。この「高速に」というのが重要で、すこし速度が落ちるとカクつきを感じますし、もっと遅ければコマ送りのように見えてしまいます。

そう考えるとこの問題の原因は明確ですね。「(3) コマンド送信」と「(4) コマンド実行結果の受信」の間は(1)と(2)の処理が行われないからです。 (3)と(4)の間は2秒~10秒程度の時間がかかります。結果的にその間は動画がピタッと止まって見えることになります。

解決方法を考える

とすれば(1)、(2)の動画表示処理と(3)、(4)のコマンド実行処理は別スレッドにして別々のタイミングで動作させるのがよさそうです。
具体的に言うと以下のような構成で動作していたものを、

スレッド分割前

以下のように変更します。

スレッド分割後

なんだか図的にもおさまりのいい感じになりました。

実装

まず Info クラスに3つの変数を追加します

class Info:
    def __init__(self):
        (...)

        # 画面入力されたコマンド
        self.__command = ""

        # ドローンに送信したコマンド
        self.__sent_command = ""

        # ドローンから受信した実行結果
        self.__result = ""


    # (GUI用) 画面で入力されたコマンドを登録する
    def entry_command(self, command):
        self.__command = command

    # (コマンド実行処理用) 画面で入力されたコマンドを取得する
    def pick_command(self):
        command = self.__command
        self.__command = ""
        return command

    # (コマンド実行処理用) ドローンに送信したコマンドを登録する
    def set_sent_command(self, command):
        self.__sent_command = command

    # (GUI用) ドローンに送信したコマンドを取得する
    def get_sent_command(self):
        return self.__sent_command


    # (コマンド実行処理用) ドローンから受信したコマンド実行結果を登録する
    def set_command_result(self, result):
        self.__result = result

    # (GUI用) ドローンから受信したコマンド実行結果を取得する
    def get_command_result(self):
        return self.__result

コマンド送受信処理をメインスレッドから抜き出して専用のスレッドで実行します。

def send_command():
    while info.is_active():
        # 画面で入力されたコマンドを info から取り出す
        msg = info.pick_command()
        if msg == "":
            continue

        # ドローンに送信
        sock.sendto(msg.encode(), ("192.168.10.1", 8889))

        # 前回のコマンド実行結果をクリアする
        info.set_command_result("")

        # 送信したコマンドを info に設定する
        info.set_sent_command(msg)
        start = time.time()

        # ドローンからコマンド実行結果を受信
        data, _ = sock.recvfrom(1024)

        # 受信したコマンド実行結果を info に設定する
        info.set_command_result(f"{data.decode()} {time.time() - start:.1f}")


command_send_thread = threading.Thread(target=send_command)
command_send_thread.start()

画面側では、上記の処理が info に設定した「ドローンに送信したコマンド」と「ドローンから受信した実行結果」を取り出して表示します。

    window["sent"].update(info.get_sent_command())
    window["recv"].update(info.get_command_result())

動作確認

動いている様子をちゃんと見れるようになりました。

プログラム改善後の飛行の様子

2022-01-18追記

コマンド実行処理を別スレッドに分割した影響で画面表示が固まったり、動画がカクツクようになることに気付いたので、若干修正を入れました。

次回

動きはかなりいい感じになったのですが、ちょーっとソースコードが長くなりすぎてどこに何があるのか追っかけるのがつらくなってきたので次回はそのあたりをきれいにしてみたいと思います。

Tello 関連記事

かわかみしんいち。島根県津和野町在住のフリーランスエンジニア。複合現実(Mixed Reality)と3DUXでおもちゃを作るのが趣味。 https://github.com/ototadana