深圳で買ったトイドローン Macから動かしたい...(操作編)


はじめに

こんにちは.
半年前に深圳で4000円ぐらいで購入したドローンをMacから操作したいということで,前回の記事で動画を再生する記事を書きました.↓
深圳で買ったトイドローン Macから動かしたい.(動画編)

今回久しぶりに時間ができたのでホコリを被っていた中華激安ドローンの操作パケットを解析しました.一通りの操作は分かったので簡単に記事にしようと思います.綺麗にコードの実装まで持って行けたら「操作編2」も書くかもしれないです.

達成したこと

  • ドローンのパケットの観測
  • 操作パケットの判別
  • 操作パケットの解析
  • pythonでドローンに離陸命令

以上のことができました.操作パケットの解析まで済ましたので,あとはそれに則ったUDPのパケットを投げるプログラムを書いたらMacから操作ができます.
(pythonのキーボードイベント処理がうまくいきません.swiftでやったら楽やけど,windowsでも触れるようにしたいなー)
以下で確かめて分かったことを書いていきます.

前提

ドローン

純正(?)アプリ

本来はこれらのアプリをスマホでダウンロードして使います.
google:https://play.google.com/store/apps/details?id=com.h8&hl=ja
apple:https://apps.apple.com/jp/app/hfun/id1273665773

今回はアプリとドローンとの通信を見ることでドローンをハックします.

操作

パケット解析

ドローンとの通信はWifiの2.4GHz帯を使って行っています.スマホからの接続はドローンの電源を入れたあとスマホのwifi接続からドローンのアクセスポイントに接続します.今回はWiresharkを使ってこの通信を確認します.WiresharkでiPhoneの通信パケットを確認する方法はこちらの記事を参考にしました.

ビデオパケット

ドローンで撮影された動画はwifi経由で送られています.その動画をmacで映す方法は前回の記事で紹介しました.深圳で買ったトイドローン Macから動かしたい.(動画編)を確認ください.

操作パケット

操作はスマホ側から行うということでwiresharkのフィルターをid.src==192.168.100.137をかけます.
するとTCPパケットとUDPパケットがいっぱい送信されていることが確認できます.

今回見ていく操作パケットはUDPの方になります.(TCPパケットの方はポート番号からビデオ通信関連です)

TCP・UDP って?

トランスポート層のプロトコルです. 学部の授業や情報技術者試験で習った程度で詳しくないのでざっくんばらんに書くと,
TCPはパケットを送信すると,そのパケットが届いたかどうかがわかるものに対して,
UDPはパケットを送信元から送信したあと送信先に届こうと届かまいと構わないって感じ
の通信プロトコルです.

これは通信ってことではTCPのほうが断然いいように思うかもしれませんが,UDPにもメリットがあって,処理が簡単なために遅延が少なくなります.詳しくは他の記事やサイトを見ていただいたほうが詳しく正確な表現だと思われます.TCP vs UDP のジョーク画像の意味を調べてみた←こちらの記事のジョーク写真がお気に入りでTCP・UDPをよく表せていると思っています!

操作パケットの解析

192.168.100.137(iPhone)から送られているUDPパケットを確認すると,Data部が20bytesあることがわかりました.この20bytesにドローンを操作する情報が含まれています..

解析した過程を書くのは少し面倒なのでわかった結果だけ書いていきます.
※一部ビデオ録画開始,3D ROLLなど確認できていない操作があります.

ちょっと見難いですが表にまとめてみました.

不明となっているところはまだ確認が取れていない操作になります.おそらくビデオ録画開始,3D ROOLといったものが割り当てられていると思われます.

  • 離陸・着陸
  • 右スティック(上下)
  • 右スティック(回転)
  • 左スティック(前後)
  • 左スティック(左右)
  • チェックサム①,②

(説明後で書きます...ほとんどできたから公開しますごめんなさい...)

Pythonから離陸

こちらのコードをWiFiよりドローンに接続したMacで起動するとドローンが離陸しました.

takeOFF.py
from socket import socket, AF_INET, SOCK_DGRAM
import time
HOST = ''
PORT = 19798
ADDRESS = "192.168.100.1"
s = socket(AF_INET, SOCK_DGRAM)

def send(mes):
    s.sendto(mes,(ADDRESS,PORT))

def takeOFF_ON():
    mess = b'\x66\x0a\x80\x80\x80\x80\x01\x02\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x03\x99'
    for i in range(100):
        send(mess)
        if i == 50:
            time.sleep(1)
        else:     
            time.sleep(0.002)

# 実行後2秒後に離陸,その後3秒後に着陸を行うテスト
def testTakeOFF():
    mess = b'\x66\x0a\x80\x80\x80\x80\x00\x02\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x02\x99'
    for i in range(3000):
        if i == 1000:
            takeOFF_ON()
        if i == 2500:
            takeOFF_ON()
        send(mess)
        time.sleep(0.002)

def main():
    testTakeOFF()
    s.close()

if __name__ == "__main__":
    main()