TelloPy-develop-0.7.0源码阅读.1

最近我在反思,为什么我看了那么多书,为什么还是写不出大型的程序?我也很苦恼,我想了下。应该还是看的源码少的过,古人曾经说过熟读唐诗三百首,不会吟诗也会吟 。在读源码的选择上,我没有选择太复杂的开源库,而是选择了自己比较熟悉的TT无人机库,而且代码行数选择在1k行以内的库。就是下面这个库了~

首先我们在文章开头先简要的说明一下,什么是工厂函数

from setuptools import setup, find_packagesfrom codecs import openfrom 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中,出现了这个,下面开始阅读

https://docs.python.org/zh-cn/3/library/codecs.html?highlight=codecs

这些函数最好去看官网的doc

常用的有获得当前文件的绝对路径

讲一个path加到环境变量中

获得目录名称

__file__这个变量不知道是什么意思

开了调试看了一下

'C:\\Users\\yunswj\\AppData\\Local\\Programs\\Python\\Python37\\lib\\ntpath.py'

接着向下跳,又看到了路径

延时函数

开始读主文件了

看到了这种导入的方式

搜索了一下,大概是同级导包的意思

然后试了一下指向包的地址,也成功了、确实是在同级的目录下跳转

继续看,这个utils经常可以看见

搜索了一下,就是小工具的意思

接着读主干,出现了一个日志类

import datetimeimport threading
LOG_ERROR = 0LOG_WARN = 1LOG_INFO = 2LOG_DEBUG = 3LOG_ALL = 99

找到源码,可以看到就使用了线程和时间库

下面是定义了日志的等级

Logger库的结构

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 = ("%02d:%02d:%02d.%03d" % (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))

先看初始化,日志等级

和日志头,以及一个线程锁

是一个类

这个头是一个个时间头,也好理解

我们平时的日志也是时间打头

return "%s: %s" % (self.header_string, ts)

这样的话就打印出一个自己喜欢的以一个输入的字符串开始的log,当然这里什么也没有写,就是一个时间头了

然后一个输出的函数必不可少

获得一下锁,打印,然后,释放

下面的函数都一样,就看一个,先判断一下

然后调用上面的输出函数

打印一个log,header+msg

用__main__修饰一下,模块也可以当,单独也能打

import datetimeimport threading
LOG_ERROR = 0LOG_WARN = 1LOG_INFO = 2LOG_DEBUG = 3LOG_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 = ("%02d:%02d:%02d.%03d" 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

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发包大小

连接状态

线程锁状态

视频预览时间

视频大小

视频解码速率

日志长度等,蛮丰富的

视频变焦,还有就是要不要解锁快速飞行状态

把类型安排了

网络编程基操了

设置为阻塞模式,可不是那得堵住呗

上面得类得名字是调度

下面是两个线程

接收+视频流

连接

断开

发送,这里先等一下

这段调度得代码我没有看太懂

这个是状态机的函数,我们看看

放了四个参数

获得以一个锁

事件连接假

断开连接假

def byte(c): if isinstance(c, str): return ord(c) return c

返回是一个Unicode码

def le16(val): return (val & 0xff), ((val >> 8) & 0xff)
def int16(val0, val1): if (val1 & 0xff) is not 0: return ((val0 & 0xff) | ((val1 & 0xff) << 8)) - 0x10000 else: return (val0 & 0xff) | ((val1 & 0xff) << 8)
def byte_to_hexstring(buf): if isinstance(buf, str): return ''.join(["%02x " % ord(x) for x in buf]).strip()
return ''.join(["%02x " % ord(chr(x)) for x in buf]).strip()
def float_to_hex(f): return hex(struct.unpack('<I', struct.pack('<f', f))[0])
def show_exception(ex): exc_type, exc_value, exc_traceback = sys.exc_info() traceback.print_exception(exc_type, exc_value, exc_traceback

给出异常信息

然后打印错误的堆栈信息

以上的函数都是一些数据类型转换函数,在协议的处理的时候,频繁的用到所以这里提起封装。

https://tellopilots.com/wiki/protocol/#PacketTypeInfo

这个库牛逼的地方在于,不是简单的封装SDK那么简单,而是逆向出了低级的协议:

这个是逆向出来的低级的UDP结构包,大部分也是这个样子的

如果英文较好,可以看这个

数据包大小采用奇怪的小端格式,其中低位(第一个)字节是正常的,但高位(第二个)字节向左移动 3 位。所以解码大小看起来像这样:size = buffer[1] + ((buffer[2]<<8)>>3)

信息包类型信息格式

数据包的类型

Tello 消息 ID 和含义

https://bitbucket.org/PingguSoft/pytello/src/master/

以上包的获得都是通过这个wireshark抓到的

START_OF_PACKET = 0xccSSID_MSG = 0x0011SSID_CMD = 0x0012SSID_PASSWORD_MSG = 0x0013SSID_PASSWORD_CMD = 0x0014WIFI_REGION_MSG = 0x0015WIFI_REGION_CMD = 0x0016WIFI_MSG = 0x001aVIDEO_ENCODER_RATE_CMD = 0x0020VIDEO_DYN_ADJ_RATE_CMD = 0x0021EIS_CMD = 0x0024VIDEO_START_CMD = 0x0025VIDEO_RATE_QUERY = 0x0028TAKE_PICTURE_COMMAND = 0x0030VIDEO_MODE_CMD = 0x0031VIDEO_RECORD_CMD = 0x0032EXPOSURE_CMD = 0x0034LIGHT_MSG = 0x0035JPEG_QUALITY_MSG = 0x0037ERROR_1_MSG = 0x0043ERROR_2_MSG = 0x0044VERSION_MSG = 0x0045TIME_CMD = 0x0046ACTIVATION_TIME_MSG = 0x0047LOADER_VERSION_MSG = 0x0049STICK_CMD = 0x0050TAKEOFF_CMD = 0x0054LAND_CMD = 0x0055FLIGHT_MSG = 0x0056SET_ALT_LIMIT_CMD = 0x0058FLIP_CMD = 0x005cTHROW_AND_GO_CMD = 0x005dPALM_LAND_CMD = 0x005eTELLO_CMD_FILE_SIZE = 0x0062 # pt50TELLO_CMD_FILE_DATA = 0x0063 # pt50TELLO_CMD_FILE_COMPLETE = 0x0064 # pt48SMART_VIDEO_CMD = 0x0080SMART_VIDEO_STATUS_MSG = 0x0081LOG_HEADER_MSG = 0x1050LOG_DATA_MSG = 0x1051LOG_CONFIG_MSG = 0x1052BOUNCE_CMD = 0x1053CALIBRATE_CMD = 0x1054LOW_BAT_THRESHOLD_CMD = 0x1055ALT_LIMIT_MSG = 0x1056LOW_BAT_THRESHOLD_MSG = 0x1057ATT_LIMIT_CMD = 0x1058 #wiki是错误的ATT_LIMIT_MSG = 0x1059
EMERGENCY_CMD = 'emergency'

低级协议的编码

# 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

翻转的版本

写了很久了,下篇文章继续读

(0)

相关推荐

  • python笔记46-史上最强大最好用的python日志模块nb_log

    前言 python的日志模块如何封装一值都是一个头疼的问题,封装的不好总是会出现重复打印等头疼问题. 现在终于找到一个最好用的日志模块nb_log,此日志模块由这位大佬开发的https://www.c ...

  • Selenium2+python自动化72-logging日志使用

    前言 脚本运行的时候,有时候不知道用例的执行情况,这时候可以加入日志,这样出现问题后方便查阅,也容易排查哪些用例执行了,哪些没有执行. 一.封装logging模块 1.关于logging日志的介绍,我 ...

  • python 日志 按日期或大小归档

    随着回调的事件与越来越多,如果每个事件都写一个方法,这就显得代码的冗余,所以,今天将日志都归档,分类 事件日志,看情况,我们一般保留10天,至于错误日志,一般不会报错,所以我们按文件的大小进行分类 # ...

  • redis 5.0.7 源码阅读——动态字符串sds

    redis中动态字符串sds相关的文件为:sds.h与sds.c 一.数据结构 redis中定义了自己的数据类型"sds",用于描述 char*,与一些数据结构 1 typedef ...

  • redis 5.0.7 源码阅读——双向链表

    redis中双向链表相关的文件为:adlist.h与adlist.c 一.数据结构 redis里定义的双向链表,与普通双向链表大致相同 单个节点: 1 typedef struct listNode ...

  • 一个超级实用的源码阅读小技巧

    在学习编程的路途漫漫,优秀的源码是非常珍贵的学习资源,阅读源码也是有效提高自己的一个好方法. 工欲善其事必先利其器: 我发现函数调用图可以让我们更加直观地了解到源码函数直接的调用和层次关系,提高阅读源 ...

  • spark源码阅读--shuffle过程分析 ShuffleManager(一)

    ShuffleManager(一) 本篇,我们来看一下spark内核中另一个重要的模块,Shuffle管理器ShuffleManager.shuffle可以说是分布式计算中最重要的一个概念了,数据的j ...

  • dubbo源码阅读之服务目录

    服务目录 服务目录对应的接口是Directory,这个接口里主要的方法是 List<Invoker<T>> list(Invocation invocation) throws ...

  • 手把手教学APK反编译实现源码阅读

    手把手教学APK反编译实现源码阅读

  • Vue2 源码阅读(三) 双向绑定原理

    Vue2 源码阅读(三) 双向绑定原理

  • Vue2.x 响应式部分源码阅读记录

    之前也用了一段时间Vue,对其用法也较为熟练了,但是对各种用法和各种api使用都是只知其然而不知其所以然.最近利用空闲时间尝试的去看看Vue的源码,以便更了解其具体原理实现,跟着学习学习. Proxy ...

  • Mybatis源码阅读套路,一次性打包发您~

    很多人看源码都不知道如何看,今天来教教大家如何看源码. 前提是我们需要对整个Mybatis的原理.工作流程和模块进行一个整体的直知晓,另外还要有使用经验. 建议先看这两篇文章: 本文主要内容: 源码下 ...