見出し画像

SwiftでTelloと通信するサンプル

バイナリコマンドでTelloを動かすアプリのSwift版を書いてみようかと思って着手。まずは通信開始と簡単なコマンド送信から。

細かいこと書くの面倒なので、ViewController.swiftまるごと貼っておきます。読んでください。

↓これのソースコードです。


//
//  ViewController.swift
//  TelloTest
//

import UIKit
import SwiftSocket

class ViewController: UIViewController {
	static let KEY_SETTING_UNIT_TYPE:String = "KEY_SETTING_UNIT_TYPE"
	static let KEY_UNIT_METER:Int = 0
	static let KEY_UNIT_IMPERIAL:Int = 1

	var _client:UDPClient? = nil
	var _unitType:Int = KEY_UNIT_METER
	
	var _isConnect = false
	var _isSendingAltitude = false

	var _isEnd:Bool = false

	@IBOutlet var _labelAlart: UILabel!
	@IBOutlet var _labelCurrentAlt: UILabel!
	@IBOutlet var _labelUsage: UILabel!
	@IBOutlet var _labelNewAlt: UILabel!
	@IBOutlet var _sliderAlt: UISlider!
	@IBOutlet var _segUnitType: UISegmentedControl!
	
	@IBOutlet var _buttonSend: UIButton!
	override func viewDidLoad() {
		super.viewDidLoad()

		//Load unit setting from user default
		let ud = UserDefaults.standard
		_unitType = ud.integer(forKey:ViewController.KEY_SETTING_UNIT_TYPE)
		if(_unitType < ViewController.KEY_UNIT_METER || _unitType > ViewController.KEY_UNIT_IMPERIAL) {
			_unitType = ViewController.KEY_UNIT_METER
			ud.set(_unitType, forKey: ViewController.KEY_SETTING_UNIT_TYPE)
		}
		self._segUnitType.selectedSegmentIndex = _unitType
	}

	func enableControl() {
		_labelCurrentAlt.isHidden = false
		_labelAlart.isHidden = true
		_labelUsage.isHidden = false
		_buttonSend.isEnabled = true
		_labelNewAlt.isEnabled = true
		_sliderAlt.isEnabled = true
		changeAltSlider(self._sliderAlt)
	}
	
	func disableControl() {
		_labelCurrentAlt.isHidden = true
		_labelAlart.isHidden = false
		_labelUsage.isHidden = true
		_buttonSend.isEnabled = false
		_labelNewAlt.isEnabled = false
		_sliderAlt.isEnabled = false
	}
	
	//connect to Tello
	func connect() {
		disableControl()
		self._client = UDPClient(address: "192.168.10.1", port: 8889)
		if let client = self._client {
			self._isConnect = false
			self._isSendingAltitude = false
			startReceive()
			let res:Result = client.send(data: TelloPacket.createConnectReqPacket())
			if(res.isSuccess) {
			}
		}
	}

	//Close connection and dispose
	func closeConnection() {
		self._isEnd = false
		disableControl()
		self._isConnect = false
		self._isSendingAltitude = false

		//ちょっと待ってから破棄する
		DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
			if let client = self._client {
				client.close()
				self._client = nil
			}
		}
	}
	
	//Start Receive thread
	func startReceive() {
		self._isEnd = true
		DispatchQueue(label: "jp.keiziweb.TALS.queue").async {
			
			while(self._isEnd) {
				if let client = self._client {
					let recvData = client.recv(512) //recvData(data, address, port)
					//print(TelloPacket.packetToHexString(packet: recvData.0!))
					
					//Receive success
					if let data = recvData.0 {
						let bytes:[UInt8] = [UInt8](data)

						//ack
						if(bytes[0] == 0x63) {
							DispatchQueue.main.async {
								self.enableControl()
								self._isConnect = true
							}
							let resGetAlt:Result = client.send(data: TelloPacket.createGetAltitudePacket())
							if(resGetAlt.isFailure) {
								print("Error:Send GetAltitudePacket")
								self.closeConnection()
							}
						}
						//Binary packet
						else if(bytes[0] == 0xcc) {
							//Parse
							if let packet = TelloPacket.parsePacket(data:Data(data)) {
								//4182 == TELLO_CMD_ALT_LIMIT
								if(packet._commandID == 4182) {
									//print(packet._data![0],packet._data![1],packet._data![2])
									
									//Update current altitude label
									let currentAltitude:Int = TelloPacket.littleEndianToInt(packet._data![1], packet._data![2])
									DispatchQueue.main.async {
										self._sliderAlt.value = Float(currentAltitude)
										self._labelNewAlt?.text = self.makeAltitudeString(currentAltitude)
										self._labelCurrentAlt?.text = String(format: NSLocalizedString("CurrentAltFormat",comment:""), self.makeAltitudeString(currentAltitude) )
										
										if(self._isSendingAltitude) {
											UIUtility.showAlertWithOK(vc: self, title: NSLocalizedString("Succeeded",comment:""), message: NSLocalizedString("UpdateSucceeded",comment:""))
										}
										self._isSendingAltitude = false

									}
								}
							}
						}
					}
				}
			}
			
			DispatchQueue.main.async {
			}
		}
	}
	
	override func didReceiveMemoryWarning() {
		super.didReceiveMemoryWarning()
		// Dispose of any resources that can be recreated.
	}
	
	//Send altitude value to Tello
	@IBAction func tapChangeAltLimit(_ sender: Any) {
		if(!self._isConnect) {
			closeConnection()
			return
		}
		
		let altitude:Int = Int(self._sliderAlt.value)
		if(altitude >= 20) {
			UIUtility.showAlertWithYESAndNO(vc: self, title: NSLocalizedString("confirm",comment:""), message:
				String(format: NSLocalizedString("not_recommend",comment:""), makeAltitudeString(20))
				, handlerYES: {
				(action: UIAlertAction!) in
					self.sendSetAltLimitCommand(altitude: altitude)
			}
				, handlerNO:nil)

		}
		else {
			sendSetAltLimitCommand(altitude: altitude)
		}
	}

	//Send "Set altitude limit command" to Tello.
	public func sendSetAltLimitCommand(altitude:Int) {
		if let client = self._client {
			let resSetAlt:Result = client.send(data: TelloPacket.createSetAltitudePacket(altitudeM: altitude))
			if(resSetAlt.isSuccess) {
				_isSendingAltitude = true
				DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
					if(self._isSendingAltitude) {
						self.closeConnection()
						UIUtility.showAlertWithOK(vc: self, title: NSLocalizedString("error",comment:""), message: NSLocalizedString("FaultGetAlt",comment:""))
					}
				}

				let resGetAlt:Result = client.send(data: TelloPacket.createGetAltitudePacket())
				if(resGetAlt.isFailure) {
					print("Error:Send GetAltitudePacket")
				}
			}
			else {
				print("Error:Send SetAltitudePacket")
			}
		}
	}

	//changeUnitType save type
	@IBAction func changeUnitType(_ sender: UISegmentedControl) {
		let ud = UserDefaults.standard
		_unitType = sender.selectedSegmentIndex
		ud.set(_unitType, forKey: ViewController.KEY_SETTING_UNIT_TYPE)
	}
	
	//changeAltSlider update label
	@IBAction func changeAltSlider(_ sender: UISlider) {
		let altitude:Int = Int(self._sliderAlt.value)
		self._sliderAlt.value = Float(altitude)
		self._labelNewAlt?.text = self.makeAltitudeString(Int(sender.value))
	}

	//make Altitude string with unit type
	func makeAltitudeString(_ altitude:Int)->String {
		if(_unitType == ViewController.KEY_UNIT_METER) {
			return String(format: "%d m", altitude)
		}
		else {
			return String(format: "%.1f ft", Float(altitude) * 3.2808)
		}
	}
	
	//Takeoff
	@IBAction func tapTakeoff(_ sender: Any) {
		if let client = self._client {
			let res:Result = client.send(data: TelloPacket.takeOffPacket)
			if(!res.isSuccess) {
				print("Error:Send TakeOff")
			}
		}

	}

	//Land
	@IBAction func tapLand(_ sender: Any) {
		if let client = self._client {
			let res:Result = client.send(data: TelloPacket.landPacket)
			if(!res.isSuccess) {
				print("Error:Send Land")
			}
		}

	}
}

UIUtilityはダイアログを出したりするオリジナルのクラスライブラリですが大したコードじゃないです。

//
//  UIUtility.swift
//  Quiz
//
//  Created by matsumoto keiji on 2017/05/29.
//  Copyright © 2017年 matsumoto keiji. All rights reserved.
//

import Foundation
import UIKit

struct UIUtility {
	//OKボタンのアラートを出す
	static func showAlertWithOK(vc:UIViewController,title:String,message:String,handler: ((UIAlertAction) -> Swift.Void)? = nil,completion: (() -> Swift.Void)? = nil) {
		let alert:UIAlertController = UIAlertController(title:title, message:message, preferredStyle: UIAlertControllerStyle.alert)
		alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment:""), style: UIAlertActionStyle.default, handler:handler))
		vc.present(alert, animated: true, completion:completion)
	}
	
	//OKボタンとキャンセルのアラートを出す
	static func showAlertWithOKAndCansel(vc:UIViewController,title:String,message:String,handlerOK: ((UIAlertAction) -> Swift.Void)? = nil,handlerCancel: ((UIAlertAction) -> Swift.Void)? = nil,completion: (() -> Swift.Void)? = nil) {
		let alert:UIAlertController = UIAlertController(title:title, message:message, preferredStyle: UIAlertControllerStyle.alert)
		alert.addAction(UIAlertAction(title: NSLocalizedString("cancel", comment:""), style: UIAlertActionStyle.default, handler:handlerCancel))
		alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment:""), style: UIAlertActionStyle.default, handler:handlerOK))

		vc.present(alert, animated: true, completion:completion)
	}
	
	//YESボタンとNOのアラートを出す
	static func showAlertWithYESAndNO(vc:UIViewController,title:String,message:String,handlerYES: ((UIAlertAction) -> Swift.Void)? = nil,handlerNO: ((UIAlertAction) -> Swift.Void)? = nil,completion: (() -> Swift.Void)? = nil) {
		let alert:UIAlertController = UIAlertController(title:title, message:message, preferredStyle: UIAlertControllerStyle.alert)
		alert.addAction(UIAlertAction(title: NSLocalizedString("no", comment:""), style: UIAlertActionStyle.default, handler:handlerNO))
		alert.addAction(UIAlertAction(title: NSLocalizedString("yes", comment:""), style: UIAlertActionStyle.default, handler:handlerYES))

		vc.present(alert, animated: true, completion:completion)
		
	}
	
	//アクションシートを表示する
	static func showAction(vc:UIViewController,title:String,message:String,actions:[UIAlertAction],isNeedCancel:Bool) {
		let alertSheet = UIAlertController(title:title, message:message, preferredStyle: UIAlertControllerStyle.actionSheet)
		

		for action in actions {
			alertSheet.addAction(action)
		}

		if(isNeedCancel) {
			let actionCancel = UIAlertAction(title: NSLocalizedString("cancel",comment:""), style: UIAlertActionStyle.cancel, handler: {
				(action: UIAlertAction!) in
			})

			alertSheet.addAction(actionCancel)
		}
		vc.present(alertSheet, animated: true, completion: nil)

	}
}

実は結構簡単でした

たったの4日前にサッパリわからなかった事がアッサリわかるこの感じ。なぜ4日前はあんなに苦労したんだろうか(答え:プロトコルとUDP通信を理解してなかったから)。

受信したバイナリデータを16進数で流す関数はこんな感じ。

static func packetToHexString(packet:[UInt8])->String {
	var hexString:String = "";

	for i in (0 ..< packet.count) {
		hexString.append(String(format:"%02x ",packet[i]))
	}
	return hexString
}

流れてくるパケットには姿勢とか高度とかバッテリー残量とかWi-Fi強度とか入ってるっぽいですが、中身については謎です。Telloの姿勢を変えるとガーッと0xffが出るので加速度とか高度とか入ってるはずです。今のところ真面目に調べてはいません。

ビデオストリームは別のポートで受信するらしく、H.264だそうなので、あとで頑張ります。アプリでやれてることなので読み込めるでしょう。

UDP通信はSwiftSocketを使っているので超簡単です。CocoaPodsでインストールしましょう。

 pod 'SwiftSocket'

Javaで書いた方はTelloPCを参考にしましたが、Swiftは型の違いとかオプショナルとか色々あるのでめんどい。

詳しい通信手順とかは下記エントリで書きました。お役立てください。

Telloのバイナリコマンドパケットをいじるクラス

パケットデータを作ったり解釈したりするクラスを作りました。

//
//  TelloPacket.swift
//
/*Reference
https://github.com/Kragrathea/TelloPC
https://dl-cdn.ryzerobotics.com/downloads/tello/0228/Tello+SDK+Readme.pdf
https://drive.google.com/file/d/1t12MK-jG4df90gMjD8syPrAZ8VmdqCDA/view
https://gobot.io/blog/2018/04/20/hello-tello-hacking-drones-with-go/

Written article
Tello & C#
https://note.mu/keizi666/n/n68eb25c8aa59

Tello & Java
https://note.mu/keizi666/n/nddef168ffb76

Tello & Swift
https://note.mu/keizi666/n/nc0e3d3cd3a21
*/

import Foundation

class TelloPacket {
	var _size:Int = 0
	var _crc8:Int = 0
	var _typeID:Int = 0
	var _commandID:Int = 0
	var _seqNo:Int = 0
	var _data:[UInt8]? = nil
	var _crc16:Int = 0
	
	var _isCheckCRC:Bool = false

	//Calc CRC ------------------------------------------------------------
	static let poly:Int = 13970
	static let fcstab:[UInt16] = [0, 4489, 8978, 12955, 17956, 22445, 25910, 29887, 35912, 40385, 44890, 48851, 51820, 56293, 59774, 63735, 4225, 264, 13203, 8730, 22181, 18220, 30135, 25662, 40137, 36160, 49115, 44626, 56045, 52068, 63999, 59510, 8450, 12427, 528, 5017, 26406, 30383, 17460, 21949, 44362, 48323, 36440, 40913, 60270, 64231, 51324, 55797, 12675, 8202, 4753, 792, 30631, 26158, 21685, 17724, 48587, 44098, 40665, 36688, 64495, 60006, 55549, 51572, 16900, 21389, 24854, 28831, 1056, 5545, 10034, 14011, 52812, 57285, 60766, 64727, 34920, 39393, 43898, 47859, 21125, 17164, 29079, 24606, 5281, 1320, 14259, 9786, 57037, 53060, 64991, 60502, 39145, 35168, 48123, 43634, 25350, 29327, 16404, 20893, 9506, 13483, 1584, 6073, 61262, 65223, 52316, 56789, 43370, 47331, 35448, 39921, 29575, 25102, 20629, 16668, 13731, 9258, 5809, 1848, 65487, 60998, 56541, 52564, 47595, 43106, 39673, 35696, 33800, 38273, 42778, 46739, 49708, 54181, 57662, 61623, 2112, 6601, 11090, 15067, 20068, 24557, 28022, 31999, 38025, 34048, 47003, 42514, 53933, 49956, 61887, 57398, 6337, 2376, 15315, 10842, 24293, 20332, 32247, 27774, 42250, 46211, 34328, 38801, 58158, 62119, 49212, 53685, 10562, 14539, 2640, 7129, 28518, 32495, 19572, 24061, 46475, 41986, 38553, 34576, 62383, 57894, 53437, 49460, 14787, 10314, 6865, 2904, 32743, 28270, 23797, 19836, 50700, 55173, 58654, 62615, 32808, 37281, 41786, 45747, 19012, 23501, 26966, 30943, 3168, 7657, 12146, 16123, 54925, 50948, 62879, 58390, 37033, 33056, 46011, 41522, 23237, 19276, 31191, 26718, 7393, 3432, 16371, 11898, 59150, 63111, 50204, 54677, 41258, 45219, 33336, 37809, 27462, 31439, 18516, 23005, 11618, 15595, 3696, 8185, 63375, 58886, 54429, 50452, 45483, 40994, 37561, 33584, 31687, 27214, 22741, 18780, 15843, 11370, 7921, 3960 ];

	//uCRC
	static let uCRCTable:[UInt8] = [ 0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65, 157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220, 35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98, 190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255, 70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7, 219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154, 101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36, 248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185, 140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205, 17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80, 175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238, 50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115, 202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139, 87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22, 233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168, 116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53 ]

	
	static let takeOffPacket:[UInt8] = [0xcc, 0x58, 0x00, 0x7c, 0x68,  0x54,  0x00,  0xe4,  0x01,  0xc2,  0x16]
	static let landPacket:[UInt8] = [0xcc, 0x60, 0x00, 0x27, 0x68, 0x55, 0x00, 0xe5, 0x01, 0x00, 0xba, 0xc7]

	static func packetToHexString(packet:[UInt8])->String {
		var hexString:String = "";

		for i in (0 ..< packet.count) {
			hexString.append(String(format:"%02x ",packet[i]))
		}
		return hexString
	}
	
	static func fsc16(_ bytes:[UInt8],_ len:Int,_ poly:Int)->UInt16 {
		var i:Int = 0
		var j:Int = poly
		var poly:Int = len
		var len:Int = j
		while (true) {
			j = len
			if (poly == 0) {
				break
			}
			j = Int(bytes[i])
			len = Int(fcstab[((len ^ j) & 0xFF)] ^ UInt16(len) >> 8)
			i += 1
			poly = poly - 1
		}
		return UInt16(j)
	}

	static func calcCrcToInt(bytes:[UInt8],len:Int)->UInt16	{
		if (len <= 2) {
			return 0
		}
		return fsc16(bytes, len - 2, poly)
	}

	static func calcCrc(bytes:inout[UInt8],len:Int)	{
		let i:UInt16 = calcCrcToInt(bytes: bytes,len: len)
		bytes[(len - 2)] = UInt8((i & 0xFF))
		bytes[(len - 1)] = UInt8((i >> 8 & 0xFF))
	}

	static func uCRC(bytes:[UInt8],len:Int,poly:Int)->UInt8 {
		var j:Int = 0
		var i:Int = poly
		var poly = j
		var len = len

		while (len != 0) {
			j = Int(bytes[poly]) ^ i
			i = j
			if (j < 0) {
				i = j + 256
			}
			i = Int(uCRCTable[i])
			poly += 1
			len = len - 1
		}
		return UInt8(i)
	}

	static func calcUCRC(bytes:inout[UInt8],len:Int) {
		let i:Int = calcUCRCBToInt(bytes: bytes,len: len)
		bytes[(len - 1)] = UInt8(i & 0xFF)
	}

	static func calcUCRCBToInt(bytes:[UInt8],len:Int)->Int {
		if ((bytes.count == 0) || (len <= 2)) {
			return 0
		}
		return Int(uCRC(bytes: bytes, len: len - 1, poly: 119)) & 0xff
	}
	//---------------------------------------------------------------------
	
	//Build 11 Byte Packet type, commnad, none Data
	static func build11Packet(type:UInt8,command:UInt16)->[UInt8] {
		//template
		var packet:[UInt8] = [0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
		//                   start size        crc   type  command     seq         crc       11byte
		let len:UInt8 = UInt8(packet.count)
		packet[ 1] = UInt8(len << 3)
		calcUCRC(bytes: &packet, len: 4)
		
		packet[4] = type
		
		packet[5] = UInt8(command & 0xff)
		packet[6] = UInt8((command >> 8) & 0xff)
		
		calcCrc(bytes: &packet, len: packet.count)
		return packet
	}
	
	//Create Conn_req(Send req -> Return ack)
	static func createConnectReqPacket()->Data {
		let strConnReq = "conn_req:00"
		var byteConnReq = strConnReq.data(using: .utf8)!
		byteConnReq[byteConnReq.count - 2] = 0x96
		byteConnReq[byteConnReq.count - 1] = 0x17

		return byteConnReq
	}
	//Create ack packet(To use for verification)
	static func createConnectAckPacket()->Data {
		let strConnAck = "conn_ack:00"
		var byteConnAck = strConnAck.data(using: .utf8)!
		byteConnAck[byteConnAck.count - 2] = 0x96
		byteConnAck[byteConnAck.count - 1] = 0x17
		
		return byteConnAck
	}

	//Create Get Altitude Packet
	static func createGetAltitudePacket()->Data {
		return Data(bytes: build11Packet(type: 0x48,command: 0x1056))
	}

	//Create Set Altitude Packet
	static func createSetAltitudePacket(altitudeM:Int)->Data {
		//template
		var packet:[UInt8] = [0xcc, 0x00, 0x00, 0x00, 0x68, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
		//                    start size        crc   type  command     seq         data        crc       13byte
		
		let len:UInt8 = UInt8(packet.count)
		packet[ 1] = UInt8(len << 3)
		calcUCRC(bytes: &packet, len: 4)

		packet[ 9] = UInt8(altitudeM & 0xff)
		packet[10] = ((UInt8(altitudeM) >> 8) & 0xff)
		
		calcCrc(bytes: &packet, len: packet.count)

		return Data(packet)
		
		/*
		ex:
		30m:cc 68 00 51 68 58 00 00 00 1e 00 a8 5c
		01m:cc 68 00 51 68 58 00 00 00 01 00 f1 4a
		16m:cc 68 00 51 68 58 00 00 00 10 00 b8 c6
		21m:cc 68 00 51 68 58 00 00 00 15 00 00 b8
		*/
	}
	
	//Parse packet and make TelloPacket.
	static func parsePacket(data:Data)->TelloPacket? {
		let bytes:[UInt8] = [UInt8](data)
		
		let start:Int = Int(bytes[0] & 0xff)
		if(	bytes.count < 11) {
			return nil
		}
		if(start != 0xcc) {
			return nil
		}
		
		let retPacket:TelloPacket = TelloPacket()
	
		retPacket._size = littleEndianToIntWithShift(bytes[1],bytes[2])
		retPacket._crc8 = Int(bytes[3] & 0xff)
	
		retPacket._typeID = Int(bytes[4] & 0xff)
	
		retPacket._commandID =  littleEndianToInt(bytes[5],bytes[6])
		retPacket._seqNo = littleEndianToInt(bytes[7],bytes[8])
	
		let dataSize:Int = retPacket._size - 11
		if(dataSize > 0) {
			retPacket._data = [UInt8](repeating: 0x0,count: dataSize)
			
			for i in (0 ..< dataSize) {
				retPacket._data?[i] = bytes[i + 9]
			}
		}
		retPacket._crc16 = littleEndianToInt(bytes[(bytes.count - 2)],bytes[(bytes.count - 1)])
		
		let crc8Check:Int = calcUCRCBToInt(bytes:bytes, len:4)
		let crc16Check:Int = Int(calcCrcToInt(bytes:bytes, len:bytes.count))
		
		if(crc8Check != retPacket._crc8 || crc16Check != retPacket._crc16) {
			retPacket._isCheckCRC = false;
		}
		else {
			retPacket._isCheckCRC = true;
		}
		return retPacket
	}

	//LittleEndian Functions. Int to LE,LE to Int
	static func intToLittleEndianWithShift(_ value:Int,_ src:inout[UInt8]) {
		src[0] = UInt8((value << 3) & 0xff)
		src[1] = UInt8((value >> 8) & 0xff)
	}
	static func intToLittleEndian(_ value:Int,_ src:inout[UInt8]) {
		src[0] = UInt8(value & 0xff)
		src[1] = UInt8((value >> 8) & 0xff)
	}
	static func littleEndianToIntWithShift(_ b0:UInt8,_ b1:UInt8)->Int {
		return  (((Int(b1) & 0xff) << 8) + (Int(b0) >> 3))
	}
	static func littleEndianToInt(_ b0:UInt8,_ b1:UInt8)->Int {
		return  (((Int(b1)) & 0xff) << 8)  + (Int(b0) & 0xff);
	}

}

extension Data {
	var hexString: String {
		return reduce("") {$0 + String(format: "%02x ", $1)}
	}
}

これでパケットを作ると色々出来ます。作ったパケットをTelloに送れば反応が返ってきます。まだ大した事出来ないですが、今後機能を増やしていこうかと思います。

で、参考URLは毎度のことですがコチラ。

高度制限解除とかは通過点で、最終的にはカメラ画像を使った自律飛行をさせたいので、しばらく暇な時にいじっていこうかと思います。

最新版はGithubに載せてます。

ビデオストリームをデコードしてFPVを実現した人もいます。

僕はまだ試してませんが、自作のFPVコントロールアプリを作るための情報は揃ってるってことですね。

今後も情報公開していきますので、みんなで楽しくアプリを作りましょう!

わぁい、サポート、あかりサポートだい好きー。