最近我在反思,为什么我看了那么多书,为什么还是写不出大型的程序?我也很苦恼,我想了下。应该还是看的源码少的过,古人曾经说过熟读唐诗三百首,不会吟诗也会吟 。在读源码的选择上,我没有选择太复杂的开源库,而是选择了自己比较熟悉的TT无人机库,而且代码行数选择在1k行以内的库。就是下面这个库了~
首先我们在文章开头先简要的说明一下,什么是工厂函数
代码语言:javascript复制from setuptools import setup, find_packages
from codecs import open
from os import path
here = path.abspath(path.dirname(__file__))
with open(path.join(here, 'README.md'), encoding='utf-8') as f:
long_description = f.read()
在setup中,出现了这个,下面开始阅读
代码语言:javascript复制https://docs.python.org/zh-cn/3/library/codecs.html?highlight=codecs
这些函数最好去看官网的doc
常用的有获得当前文件的绝对路径
讲一个path加到环境变量中
获得目录名称
__file__这个变量不知道是什么意思
开了调试看了一下
代码语言:javascript复制'C:\Users\yunswj\AppData\Local\Programs\Python\Python37\lib\ntpath.py'
接着向下跳,又看到了路径
延时函数
开始读主文件了
看到了这种导入的方式
搜索了一下,大概是同级导包的意思
然后试了一下指向包的地址,也成功了、确实是在同级的目录下跳转
继续看,这个utils经常可以看见
搜索了一下,就是小工具的意思
接着读主干,出现了一个日志类
代码语言:javascript复制import datetime
import threading
LOG_ERROR = 0
LOG_WARN = 1
LOG_INFO = 2
LOG_DEBUG = 3
LOG_ALL = 99
找到源码,可以看到就使用了线程和时间库
下面是定义了日志的等级
Logger库的结构
代码语言:javascript复制class Logger(object): # Object 继承 for child 'RospyLogger' (Jordy)
def __init__(self, header=''):
self.log_level = LOG_INFO
self.header_string = header
self.lock = threading.Lock()
def header(self):
now = datetime.datetime.now()
ts = ("d:d:d.d" %
(now.hour, now.minute, now.second, now.microsecond/1000))
return "%s: %s" % (self.header_string, ts)
def set_level(self, level):
self.log_level = level
def output(self, msg):
self.lock.acquire()
print(msg)
self.lock.release()
def error(self, str):
if self.log_level < LOG_ERROR:
return
self.output("%s: Error: %s" % (self.header(), str))
def warn(self, str):
if self.log_level < LOG_WARN:
return
self.output("%s: Warn: %s" % (self.header(), str))
def info(self, str):
if self.log_level < LOG_INFO:
return
self.output("%s: Info: %s" % (self.header(), str))
def debug(self, str):
if self.log_level < LOG_DEBUG:
return
self.output("%s: Debug: %s" % (self.header(), str))
先看初始化,日志等级
和日志头,以及一个线程锁
是一个类
这个头是一个个时间头,也好理解
我们平时的日志也是时间打头
代码语言:javascript复制return "%s: %s" % (self.header_string, ts)
这样的话就打印出一个自己喜欢的以一个输入的字符串开始的log,当然这里什么也没有写,就是一个时间头了
然后一个输出的函数必不可少
获得一下锁,打印,然后,释放
下面的函数都一样,就看一个,先判断一下
然后调用上面的输出函数
打印一个log,header msg
用__main__修饰一下,模块也可以当,单独也能打
代码语言:javascript复制import datetime
import threading
LOG_ERROR = 0
LOG_WARN = 1
LOG_INFO = 2
LOG_DEBUG = 3
LOG_ALL = 99
class Logger(object): # Object 继承 for child 'RospyLogger' (Jordy)
def __init__(self, header=''):
self.log_level = LOG_INFO
self.header_string = header
self.lock = threading.Lock()
def header(self):
now = datetime.datetime.now()
ts = ("d:d:d.d" x%(now.hour, now.minute, now.second, now.microsecond/1000))
return "%s: %s" % (self.header_string, ts)
def set_level(self, level):
self.log_level = level
def output(self, msg):
self.lock.acquire()
print(msg)
self.lock.release()
def error(self, str):
if self.log_level < LOG_ERROR:
return
self.output("%s: Error: %s" % (self.header(), str))
def warn(self, str):
if self.log_level < LOG_WARN:
return
self.output("%s: Warn: %s" % (self.header(), str))
def info(self, str):
if self.log_level < LOG_INFO:
return
self.output("%s: Info: %s" % (self.header(), str))
def debug(self, str):
if self.log_level < LOG_DEBUG:
return
self.output("%s: Debug: %s" % (self.header(), str))
if __name__ == '__main__':
log = Logger('test')
log.error('This is an error message')
log.warn('This is a warning message')
log.info('This is an info message')
log.debug('This should ** NOT ** be displayed')
log.set_level(LOG_ALL)
log.debug('This is a debug message')
最后附上完整的日志代码,这段代码的移植性极好。因为依赖的库全是标准库,而且还完成了基础的日志分级功能,日后可以慢慢重构
那么我们知道了,调用之前传一个str进去当header
我们继续看,所有的地方都是用了event类的函数
就这么点,你能想到什么?
对!他是自己基于已有的数据模型,自己又做了一个数据模型
下面是两个必须要加的函数
一个给人看,友好的格式
一个机器用,丰富的info
所以你看出来了什么?到底是在干嘛?我觉得是python没有宏定义
这个类是用类本身的特性完成了宏定义的功能
你看这些大写的str
代码语言:javascript复制class Event:
def __init__(self, name='annoymous'):
# 恼人的,烦人的
self.name = name
def __repr__(self):
return self.__str__()
def __str__(self):
return '%s::%s' % (self.__class__.__name__, self.name)
def getname(self):
return self.name
if __name__ == '__main__':
ev = Event()
print(ev)
ev = Event('test event')
print(ev)
这段实现宏的代码也是移植性极好的
同理
分别是tello ip port
调试开关
UDP发包大小
连接状态
线程锁状态
视频预览时间
视频大小
视频解码速率
日志长度等,蛮丰富的
视频变焦,还有就是要不要解锁快速飞行状态
把类型安排了
网络编程基操了
设置为阻塞模式,可不是那得堵住呗
上面得类得名字是调度
下面是两个线程
接收 视频流
连接
断开
发送,这里先等一下
这段调度得代码我没有看太懂
这个是状态机的函数,我们看看
放了四个参数
获得以一个锁
事件连接假
断开连接假
代码语言:javascript复制def byte(c):
if isinstance(c, str):
return ord(c)
return c
返回是一个Unicode码
代码语言:javascript复制def le16(val):
return (val & 0xff), ((val >> 8) & 0xff)
代码语言:javascript复制def int16(val0, val1):
if (val1 & 0xff) is not 0:
return ((val0 & 0xff) | ((val1 & 0xff) << 8)) - 0x10000
else:
return (val0 & 0xff) | ((val1 & 0xff) << 8)
代码语言:javascript复制def byte_to_hexstring(buf):
if isinstance(buf, str):
return ''.join(["x " % ord(x) for x in buf]).strip()
return ''.join(["x " % ord(chr(x)) for x in buf]).strip()
代码语言:javascript复制def float_to_hex(f):
return hex(struct.unpack('<I', struct.pack('<f', f))[0])
代码语言:javascript复制def show_exception(ex):
exc_type, exc_value, exc_traceback = sys.exc_info()
traceback.print_exception(exc_type, exc_value, exc_traceback
给出异常信息
然后打印错误的堆栈信息
以上的函数都是一些数据类型转换函数,在协议的处理的时候,频繁的用到所以这里提起封装。
代码语言:javascript复制https://tellopilots.com/wiki/protocol/#PacketTypeInfo
这个库牛逼的地方在于,不是简单的封装SDK那么简单,而是逆向出了低级的协议:
这个是逆向出来的低级的UDP结构包,大部分也是这个样子的
如果英文较好,可以看这个
数据包大小采用奇怪的小端格式,其中低位(第一个)字节是正常的,但高位(第二个)字节向左移动 3 位。所以解码大小看起来像这样:size = buffer[1] ((buffer[2]<<8)>>3)
信息包类型信息格式
数据包的类型
Tello 消息 ID 和含义
代码语言:javascript复制https://bitbucket.org/PingguSoft/pytello/src/master/
以上包的获得都是通过这个wireshark抓到的
代码语言:javascript复制START_OF_PACKET = 0xcc
SSID_MSG = 0x0011
SSID_CMD = 0x0012
SSID_PASSWORD_MSG = 0x0013
SSID_PASSWORD_CMD = 0x0014
WIFI_REGION_MSG = 0x0015
WIFI_REGION_CMD = 0x0016
WIFI_MSG = 0x001a
VIDEO_ENCODER_RATE_CMD = 0x0020
VIDEO_DYN_ADJ_RATE_CMD = 0x0021
EIS_CMD = 0x0024
VIDEO_START_CMD = 0x0025
VIDEO_RATE_QUERY = 0x0028
TAKE_PICTURE_COMMAND = 0x0030
VIDEO_MODE_CMD = 0x0031
VIDEO_RECORD_CMD = 0x0032
EXPOSURE_CMD = 0x0034
LIGHT_MSG = 0x0035
JPEG_QUALITY_MSG = 0x0037
ERROR_1_MSG = 0x0043
ERROR_2_MSG = 0x0044
VERSION_MSG = 0x0045
TIME_CMD = 0x0046
ACTIVATION_TIME_MSG = 0x0047
LOADER_VERSION_MSG = 0x0049
STICK_CMD = 0x0050
TAKEOFF_CMD = 0x0054
LAND_CMD = 0x0055
FLIGHT_MSG = 0x0056
SET_ALT_LIMIT_CMD = 0x0058
FLIP_CMD = 0x005c
THROW_AND_GO_CMD = 0x005d
PALM_LAND_CMD = 0x005e
TELLO_CMD_FILE_SIZE = 0x0062 # pt50
TELLO_CMD_FILE_DATA = 0x0063 # pt50
TELLO_CMD_FILE_COMPLETE = 0x0064 # pt48
SMART_VIDEO_CMD = 0x0080
SMART_VIDEO_STATUS_MSG = 0x0081
LOG_HEADER_MSG = 0x1050
LOG_DATA_MSG = 0x1051
LOG_CONFIG_MSG = 0x1052
BOUNCE_CMD = 0x1053
CALIBRATE_CMD = 0x1054
LOW_BAT_THRESHOLD_CMD = 0x1055
ALT_LIMIT_MSG = 0x1056
LOW_BAT_THRESHOLD_MSG = 0x1057
ATT_LIMIT_CMD = 0x1058 #wiki是错误的
ATT_LIMIT_MSG = 0x1059
EMERGENCY_CMD = 'emergency'
低级协议的编码
代码语言:javascript复制# Flip命令取自Go版本的代码
# FlipFront向前翻转。
FlipFront = 0
# FlipLeft向左翻转。
FlipLeft = 1
# FlipBack flips backwards.
FlipBack = 2
# FlipRight flips to the right.
FlipRight = 3
# FlipForwardLeft flips forwards and to the left.
FlipForwardLeft = 4
# FlipBackLeft flips backwards and to the left.
FlipBackLeft = 5
# FlipBackRight flips backwards and to the right.
FlipBackRight = 6
# FlipForwardRight向前和向右翻转。FlipForwardRight = 7
翻转的版本
写了很久了,下篇文章继续读