功能模块:BCPloger及BCPViewer - Ligcox/BTP_DM GitHub Wiki

概述

BCPloger及BCPViewer是两个相互配合、相互依赖的模块,BCPloger主要记录当前模块中出现的各个决策信息和关键数据,BCPViewer主要对各个决策信息和关键数据进行显示。BCPViewer包括了CMD和桌面版两个版本。

面向切面的编程和BCPloger

BCPloger的切面概述

面向切面编程(Aspect Oriented Programming,AOP),通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP旨在通过允许横切关注点的分离来增加模块化。它通过向现有代码添加额外行为而不修改代码本身来实现这一点,而是通过“切面”规范单独指定修改了哪些代码。在BTPDM中,AOP的思想主要体现为各个关键方法的 装饰器 ,包含在BCPloger及BCPViewer中,使得核心任务逻辑与非功能业务逻辑相剥离。

从记录FPS开始

动机

记录BTPDM的主函数运行时间对调节参数,特别是调整下位机云台对于上位机相应速度至关重要。以摄像头的主要自瞄任务为例,正常的方式是:

def run(self):
    while True:
        # 获取该loop的运行开始时间
        start_time = time.time()
        # ==============运行主函数==================
        # 心跳数据
        self.robot.heartbeat()
        # 获取串口数据和图像
        self.frame = self.vIn.getFrame()
        if self.frame is not None:
            if self.conn.status["mode"] == 0:
                self.task_auto_aiming()
            if self.conn.status["mode"] == 2:
                self.task_auto_Energy()
        # ==============主函数结束运行==================
        # 获取该loop的运行结束时间
        end_time = time.time()
        print(1/(start_time-end_time))

在一次loop进入和结束时分别计时,在结束时计算该loop的FPS并打印出来。这样做的问题使得代码中出现了很多与主函数核心任务逻辑无关的代码,一方面使得run方法的变得繁琐,另一方面也使得函数的耦合关系降低,使得代码无法被很好的复用,增加了后期维护的难度。因此,我们希望将的计时的部分与代码核心逻辑剥离。

使用AOP改写

首先在utils.py中实例化BCPlogger对象。在utils.py中初始化对象的目的是使得loger变成一个 类似 于全局变量的对象。utils.py中主要定义了程序中通用的一些功能模块和第三方包,一般在其他模块使用时会引入utils.py,进而可以使用utils.py中定义的函数和对象。

from BCPloger import BCPloger
loger = BCPloger()

BCPloger.py中实现了MainFPSLoger方法,该方法本质上是对传统的计数函数的一种改写,调用 某个函数 时,函数对象会首先被传入MainFPSLoger方法,并在wrapper方法的内部去调用这个函数,非功能的计时部分被包含在了MainFPSLoger方法中。可以看到,程序块在本质上并没有什么不同。

def MainFPSLoger(self, func):
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        log_data["MainFPS"] = [1/(time.time()-log_data["MainFPS"][1]), time.time()]
        return res
    return wrapper

之后改写我们的run方法。将功能需求全部打包进入main_task方法,并在run中通过一个死循环不断调用这个方法。main_task方法中使用了loger.MainFPSLoger装饰,loger对象在utils.py中实例化,通过这样的方式将main_task方法传入MainFPSLoger进行修饰实现了对关键数据的监控。

def run(self):
    while True:
        self.main_task()

@loger.MainFPSLoger
def main_task(self):
    # 心跳数据
    self.robot.heartbeat()
    # 获取串口数据和图像
    self.frame = self.vIn.getFrame()
    if self.frame is not None:
        if self.conn.status["mode"] == 0:
            self.task_auto_aiming()
        if self.conn.status["mode"] == 2:
            self.task_auto_Energy()
    loger.dispaly_loger()

云台、模式控制和其他的相关数据记录方式与MainFPSLoger的实现方式类似,在此不在赘述。

BCPViewer

目的

制作BCPLogger动机其实源于繁琐的调试过程。在实际在机器人上运行和调试BTPDM时,经常会发现,机器人的实际表现与期望不符。造成问题的原因是复杂的,其中有可能是:

下位机的BCP数据帧数据解析错误或没有正常调用相关执行机构(嵌入式部分问题)
由于机械设计缺陷,导致功能无法正常执行(机械部分问题)
在数据传输(CAN、USART等链路上)中数据丢失或传输不问题(硬件部分问题)
本身由于自身识别错误或决策逻辑缺陷(视觉部分问题)

解决这些问题一般是简单快速的,但另一方面,这些问题的发现和定位确实复杂繁琐的,通常定位问题需要耗费大量时间。制作BCPViewer的动机在于能够帮助调试过程快速发现和定位问题。关键数据由BCPLogger记录,然后在BCPViewer显示出来,这样与机器人的实际表现做对比,就可以提供解决问题的思路。

BCPViewer CMD版本

CMD版本的BCPViewer仅仅是对关键数据的刷新显示,并不对数据进行额外的加工处理。 BCPViewerCMD

BCPViewer 桌面版

BCPViewer 桌面版是一个基于matplotlib的图形化工具,能够对关键的历史决策数据进行曲线绘制。目前尚在研发阶段。 BCPViewerUI