# qframe **Repository Path**: dustin-wei/qframe ## Basic Information - **Project Name**: qframe - **Description**: Programing Framework for QuecPython Platform - **Primary Language**: Python - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2023-11-10 - **Last Updated**: 2024-07-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Programing Framework for Quecpython Platform ## 应用对象 一个基于 QuecPython 的应用必须有一个协调各种外设的中心调用对象。 在 QFrame 中,中心调用对象是一个 `Application` 类的实例。每个 QFrame 应用必须创建一个该类的实例,并且把模块的名称传递给该实例。应用参数也是通过该对象配置。 下面的代码: ```python from usr.qframe import Application # init application instance app = Application(__name__) # read settings from json file app.config.from_json('/usr/dev.json') # app.config is a python dict, you can use to update settings as below: app.config.update( { "UART": { "port":2, "baudrate":115200, "bytesize":8, "parity":0, "stopbits":1, "flowctl":0 } } ) ``` ## 应用拓展 应用拓展指的是为 QFrame 应用增加业务功能的模块/包, 一个基于 QuecPython 平台的应用可能存在多种业务模块,QFrame 将每种业务约定成应用拓展的形式存在,这意味着您在开发业务的时候,需要将业务模块以应用拓展的形式封装(具体封装形式见后续讲解)。 ### 设计模式 由于业务模块之间可能存在交互耦合,框架设计中,业务模块之间通信采用**星型结构设计**,如下图所示: 下图Meditor为一个中介对象(通常被框架设计成**Application**对象),各个业务对象之间通过Application对象通信,这种设计被称之为**中介模式**。 ![](docs/media/星型结构.png) **QFrame** 拟采用星型结构的设计模式,不同业务以**应用拓展**方式存在,而各应用拓展之间的交互一律通过中心对象 **Application** 调用。 ### 使用拓展 至此,我们已了解到应用拓展即为具体业务在 QFrame 中的表现形式。 我们在编写应用拓展时,一般来说,扩展从 `app.config` 获取其自身的配置并在初始化时传递给应用实例。 以下是拓展定义的抽象基类。 ```python class AppExtensionABC(object): """Abstract Application Extension Class""" def __init__(self, name, app=None): self.name = name # extension name if app: self.init_app(app) def init_app(self, app): # register into app, then, you can use `app.{extesion.name}` to get current extension instance app.append_extesion(self) def load(self): # loading extension functions, this method will be called in `app.mainloop` raise NotImplementedError ``` 初始化方法 `__init__` 中我们需要传入 `Application` 应用程序对象,如果传入,则即刻调用 `init_app` 来完成拓展的初始化动作。亦可不传入应用对象直接实例化拓展对象,后续再手动调用 `init_app` 来完成初始化。 > 注:拓展定义中的`name`属性非常关键,因为该属性作为当前拓展再app中的标识。假设我们拓展`name="serial"`,那么再注册拓展到app后,我们可以通过`app.serial`来访问该拓展对象。 ### 拓展交互 至此我们认识到,在 QFrame 框架中,各种业务是以应用拓展形式存在的。业务之间必然存在交互,在 QFrame框架中,我们将每个应用拓展注册进 Application 后,每个应用拓展就可以通过应用程序对象来调用其他应用拓展的接口了。 在每个业务实现中,我们可以通过导入全局`CurrentApp`,来获取当前应用程序对象,而不需要从实例化应用程序的模块中导入。如下: ```python # import CurrentApp from usr.qframe import CurrentApp # get global current application app = CurrentApp() ``` 在多个拓展应用中使用`CurrentApp`来实现各拓展之间的接口调用。 现假设我们有2各应用拓展: (1)TCP客户端:收发tcp服务器数据 ```python # client.py from usr.qframe import CurrentApp class TcpClient(AppExtensionABC): def __init__(self, name, app=None): self.name = name if app is not None: self.init_app(app) def init_app(self, app): # register TcpClient instance into app app.append_extension(self) def load(self): # start tcp business, like connecting server pass def send(self, data): # send data to tcp server pass def recv_callback(self, data): # recv data, then send to uart CurrentApp().uart.write(data) tcp_client = TcpClient('tcp_client') ``` (2)串口:收发串口数据 ```python # uart.py from usr.qframe import CurrentApp class Uart(AppExtensionABC): def __init__(self, name, app=None) self.name = name if app is not None: self.init_app(app) def init_app(self, app): # register Uart object instance into app app.append_extension(self) def load(self): # start uart business pass def write(self, data): # write data to uart pass def recv_callback(self, data): # recv data from uart, then send to tcp server CurrentApp().tcp_client.send(data) uart = Uart('uart') ``` 应用脚本编写如下: ```python # main.py from usr.uart import uart from usr.client import tcp_client app = Application() uart.init_app(app) tcp_client.init_app(app) app.mainloop() ``` 在主脚本中`app.mainloop()`函数会逐个调用应用拓展的`load`方法来启动拓展的各项业务功能。比如,在`TcpClient.load`中用户应该实现如连接服务器,监听服务器下行数据等功能;在`Uart.load`中应该实现监听串口数据等功能。 使用`CurrentApp`来访问当前应用程序对象,以便调用各应用拓展接口: ![](./docs/media/currentapp.png) 各个拓展中可以使用`CurrentApp()`来获取当前全局唯一应用程序对象,并通过应用程序对象来获取各个拓展对象,继而调用各拓展的业务接口。 如示例代码中,串口收到数据后,通过`CurrentApp().tcp_client`来获取tcp客户端对象,继而使用该对象`send`方法将串口数据透传至tcp服务器;tcp客户端收到数据后,通过`CurrentApp().uart`来获取串口对象,接入使用该对象`write`方法将服务器数据透传给串口。图示如下: ## 组件框图 ![](docs/media/system.png) Application:主应用对象 - 内建应用拓展组件 - Network:网络检测组件。提供异常断网恢复。 - Uart:串口组件,提供串口读写功能。 - TcpClient:TCP客户端组件, 提供tcp读写和客户端重连功能。 - SmsClient:短信客户端组件,提供短信读写功能。 - 基础组件 - qsocket:提供创建socket接口。 - ota:提供ota升级接口。 - serial:提供串口读写基本接口。 - threading:提供创建线程接口、互斥锁、条件变量、线程安全队列、线程池等接口。 - logging:提供日志接口。 - led:提供led灯控制接口。 ## 初始化流程图 ![](docs/media/init.png) 系统流程初始化步骤: 1. 实例化应用对象 2. 导入配置json文件 3. 初始化各应用拓展组件(此步骤会将各个应用拓展注册进主应用对象中,方便各拓展之间通信) 4. 检测网路(此步骤会阻塞等待网络就绪,若等待超时则尝试cfun切换以恢复网络) 5. 加载应用拓展,并启动相关服务(用户可自定义实现) 6. 系统进入正常运行状态(默认开启sim卡和网络检测,若出现掉网情况,会自行尝试cfun切换以恢复网络) ## 内置组件 ### TCP客户端组件`TcpClient` 定义在`qframe/builtins/clients.py`模块中。 开放用户接口`recv_callback`,用户通过重写该方法,实现对tcp下行数据的业务处理。该类同时实现了tcp客户端的重连操作。 开放用户接口`send`,用户可使用该方法发送上行数据。 ```python class TcpClient(AppExtensionABC): # ... def recv_callback(self, data): raise NotImplementedError('you must implement this method to handle data received by tcp.') def send(self, data): # TODO: uplink data method pass ``` ### 串口通信组件`Uart` 定义在`qframe/builtins/uart.py`模块中。 开放用户接口`recv_callback`,用户通过重写该方法,实现对uart数据的业务处理。该方法会在串口读线程中被调用,当串口有数据时候,会通过该方法将数据交由用户处理。 开放用户接口`write`,用户通过该方法可以向串口写数据。 ```python class Uart(AppExtensionABC): # ... def recv_callback(self, data): raise NotImplementedError('you must implement this method to handle data received from device.') def write(self, data): # TODO: write data to uart pass ``` ### 网络组件`NetWork` 定义在`qframe/builtins/network.py`模块中。 开放用户接口`wait_network_ready`,此接口将以阻塞方式等待网络重连,自动CFun切换以期恢复网络。 开放用户接口`register_net_callback`,此接口注册一个网络异常回调,当网络连接或断开时候会被调用。 开放用户接口`register_sim_callback`,此接口注册一个sim热插拔回调,当sim卡发生插拔动作时候会被调用。 ```python class NetWorker(AppExtensionABC): def wait_network_ready(self): # blocking until network ready pass def register_net_callback(self, cb): # register a net change callback pass def register_sim_callback(self, cb): # register a sim change callback pass ``` ### 短信客户端组件`SmsClient` 定义在`qframe/builtins/clients.py`模块中。 开发用户接口`recv_callback`,用户重写此接口来处理接收到的短信消息。 ```python class SmsClient(AppExtensionABC): # ... def recv_callback(self, phone, msg, length): # recv a sms message pass def start(self): # start a thread, listen new sms message coming pass ``` ## 串口与TCP服务器透传demo ```python # demo.py import checkNet from usr.qframe import Application, CurrentApp from usr.qframe import TcpClient, Uart from usr.qframe.logging import getLogger logger = getLogger(__name__) PROJECT_NAME = 'Sample DTU' PROJECT_VERSION = '1.0.0' def poweron_print_once(): checknet = checkNet.CheckNetwork( PROJECT_NAME, PROJECT_VERSION, ) checknet.poweron_print_once() class BusinessClient(TcpClient): def recv_callback(self, data): """implement this method to handle data received from tcp server :param data: data bytes received from tcp server :return: """ logger.info('recv data from tcp server, then post to uart') CurrentApp().uart.write(data) class UartService(Uart): def recv_callback(self, data): """implement this method to handle data received from UART :param data: data bytes received from UART :return: """ logger.info('read data from uart, then post to tcp server') CurrentApp().client.send(data) def create_app(name='DTU', config_path='/usr/dev.json'): # init application _app = Application(name) # read settings from json file _app.config.from_json(config_path) # init business tcp client client = BusinessClient('client') client.init_app(_app) # init business uart uart = UartService('uart') uart.init_app(_app) return _app app = create_app() if __name__ == '__main__': poweron_print_once() app.mainloop() ```