最近我在反思,为什么我看了那么多书,为什么还是写不出大型的程序?我也很苦恼,我想了下。应该还是看的源码少的过,古人曾经说过熟读唐诗三百首,不会吟诗也会吟 。在读源码的选择上,我没有选择太复杂的开源库,而是选择了自己比较熟悉的TT无人机库,而且代码行数选择在1k行以内的库。就是下面这个库了~ 首先我们在文章开头先简要的说明一下,什么是工厂函数 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中,出现了这个,下面开始阅读 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 datetime import threading
LOG_ERROR = 0 LOG_WARN = 1 LOG_INFO = 2 LOG_DEBUG = 3 LOG_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 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 = ("%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 = 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' 低级协议的编码 # 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 翻转的版本 写了很久了,下篇文章继续读 |
|