From 0da9ada296527aaab86aec87b049eb6317a51aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dustin=20Wei=28=E9=9F=A6=E4=BC=9F=29?= Date: Tue, 25 Jun 2024 11:12:50 +0800 Subject: [PATCH 1/2] update documents --- README.md | 396 +----------------- README_ZH.md | 395 +---------------- docs/en/Example_Code.md | 86 ++++ docs/en/User_Guider.md | 314 ++++++++++++++ ...50\346\210\267\346\214\207\345\257\274.md" | 314 ++++++++++++++ ...72\344\276\213\344\273\243\347\240\201.md" | 85 ++++ 6 files changed, 842 insertions(+), 748 deletions(-) create mode 100644 docs/en/Example_Code.md create mode 100644 docs/en/User_Guider.md create mode 100644 "docs/zh/\347\224\250\346\210\267\346\214\207\345\257\274.md" create mode 100644 "docs/zh/\347\244\272\344\276\213\344\273\243\347\240\201.md" diff --git a/README.md b/README.md index f16ef8b..1f686af 100644 --- a/README.md +++ b/README.md @@ -1,388 +1,36 @@ -# QuecPython Application Framework - QFrame +# QuecPython APP Developing Framework -- QFrame -[[简体中文](./README_ZH.md)] +[中文](README_ZH.md) | English -## Overview +## Summary -The `QFrame` application framework is a basic application framework developed by QuecPython. +`QFrame` is a basic application framework based on the QuecPython platform, aimed at making it more convenient and simple for users to use the QuecPython platform for business development. -An application often relies on multiple business modules, and there may be coupling between the business modules. -In the framework design, communication between business modules adopts a **star structure design**, as shown below: +The overall functions include: -![](docs/media/star-structure.png) +- Basic Modules:For the secondary encapsulation of platform basic API functions, such as threading, asynchrony, etc., make the API usage more in line with the CPython style. +- API Extensions:Expand QuecPython basic libs, such as bidirectional linked lists, ordered dictionaries, priority queues, heap sorting, etc. +- Framwork:Provide a detachable component based programming framework, and convenient component interaction mechanism. -The Meditor in the diagram is a mediator object (usually named `Application`). The business modules communicate through the `Application` object. This design is called the **mediator pattern**. +## Usage -The business modules are plugged into the application program in the form of application extensions. The interaction between the application extensions is unifiedly dispatched through the `Application` object. +- [User Guider](./docs/en/User_Guider.md) +- [Example Code](./docs/en/Example_Code.md) -## Application Object +## Contribution -Applications based on the `QFrame` framework must have a central object to dispatch various business modules, namely the `Application` object mentioned above. Application parameters are also configured through this object. +We welcome contributions to improve this project! Please follow these steps to contribute: -Sample code: +1. Fork the repository. +2. Create a new branch (`git checkout -b feature/your-feature`). +3. Commit your changes (`git commit -m 'Add your feature'`). +4. Push to the branch (`git push origin feature/your-feature`). +5. Open a Pull Request. -```python -from usr.qframe import Application +## License -# init application instance -app = Application(__name__) +This project is licensed under the Apache License. See the [LICENSE](LICENSE) file for details. -# read settings from json file -app.config.from_json('/usr/dev.json') +## Support -# 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 - } - } -) -``` - -## Application Extensions - -Application extensions refer to the plugged-in business modules that are loaded by the `Application` object. - -In general, the application extension gets its own configuration from `app.config` and passes it to the application instance during initialization. - -The use of application extensions contains two parts: definition and initialization. - -### Definition and Initialization of Application Extensions - -The application extension provides a base class called `AppExtensionABC`, defined as follows: - -```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 - - def load(self): - # loading extension functions, this method will be called in `app.mainloop` - raise NotImplementedError -``` - -This base class is inherited by the specific application extension class to constrain the interface definition of the application extension class. - -- We need to pass the `Application` application object to the initialization method `__init__`. When creating the application extension object, call `init_app` to complete the initialization of the extension; you can also directly create the application extension object without passing in the application object, and then explicitly call `init_app` later to complete the initialization. - -- The `load` method is called by the `Application` object and is used to load the respective application extensions. - -> The `name` attribute in the application extension definition is very critical because this attribute serves as the identity of the current application extension in app. Assuming the `name` attribute of the application extension is `name="serial"`, after registering the application extension into app, we can access the application extension object through `app.serial`. - -### Interaction between Application Extensions - -As mentioned earlier, business modules are plugged into the application program in the form of application extensions. There must be interactions between businesses, and in the QFrame framework, after registering each application extension into `Application`, each application extension can call the interfaces of other application extensions through the application object. - -In each business implementation, we can import the global `CurrentApp` to get the current application object instead of importing from the module that instantiates the application. As follows: - -```python -# import CurrentApp -from usr.qframe import CurrentApp - -# get global current application -app = CurrentApp() -``` - -Use `CurrentApp` in multiple application extensions to implement interface calls between various application extensions. - -Now assume we have 2 application extensions: - -(1) TCP client: receive and send TCP server data - -```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) Serial port: receive and transmit serial port data - -```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') -``` - -The application script is written as follows: - -```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() -``` - -In the `main.py` script, the `app.mainloop()` function will call the `load` method of each application extension one by one to start the business functions of the application extension. For example, in `TcpClient.load`, the user should implement functions such as connecting to the server and listening to downstream data from the server; functions such as listening to serial port data should be implemented in `Uart.load`. - -Use `CurrentApp` to access the current global application object to call the interfaces of each application extension: - -![](docs/media/currentapp.png) - -Each application extension can use `CurrentApp()` to obtain the current globally unique application object, and then obtain the objects of each application extension through the application object, and then call the business interfaces of each application extension. - -As shown in the above code, after receiving data from the serial port, get the TCP client object via `CurrentApp().tcp_client` and then use its `send` method to relay the serial port data to the TCP server; after the TCP client receives data, get the serial port object via `CurrentApp().uart` and then use its `write` method to relay the server data to the serial port. - -## Component Diagram - -![](docs/media/app-block-digram.png) - -`Application`: Main application object - -- Built-in application extension components - - `Network`: Network detection component. Provides abnormal network recovery. - - `Uart`: Serial port component, provides serial read and write functionality. - - `TcpClient`: TCP client component, provides TCP read/write and client reconnection capabilities. - - `SmsClient`: SMS client component, provides SMS read/write capabilities. -- Basic components - - `qsocket`: Provides socket creation interface. - - `ota`: Provides ota upgrade interface. - - `serial`: Provides basic serial read/write interfaces. - - `threading`: Provides thread creation interface, mutex locks, condition variables, thread-safe queues, thread pools, etc. - - `logging`: Provides log interface. - - `led`: Provides LED control interface. - -## Initialization Process - -![](docs/media/init-flow.png) - -System initialization process: - -1. Instantiate application object -2. Import configuration json file -3. Initialize each application extension component (this step will register each application extension into the main application object to facilitate communication between extensions) -4. Detect network (this step will block waiting for network readiness, if the timeout expires, try cfun switching to recover the network) -5. Load application extensions and start related services (custom implementation by user) -6. The system enters normal running state (network detection is enabled by default. In case of network disconnection, it will try cfun switching automatically to restore network) - -## Built-in Components - -### TCP Client Component `TcpClient` - -This class exposes two interfaces to the user: - -- The `recv_callback` method. The user overrides this method to handle downstream data from the TCP server. -- The `send` method. The user can call this method to send data to the server. - -At the same time, this class provides server auto-reconnection capability. - -Code: - -```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 -``` - -### Serial Communication Component `Uart` - -This class exposes two interfaces to the user: - -- The `recv_callback` method. The user overrides this method to handle the received serial port data. -- The `send` method. The user can call this method to send data to the serial port. - -Code: - -```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 Component `NetWork` - -This class exposes three interfaces to the user: - -- The `wait_network_ready` method. This interface will block and wait for the network to reconnect, automatically perform CFun switching in an attempt to restore the network. -- The `register_net_callback` method. This interface registers a network exception callback which will be invoked when the network connects or disconnects. -- The `register_sim_callback` method. This interface registers a SIM hot swap callback which will be invoked when the SIM card is inserted or removed. - -Code: - -```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 -``` - -### SMS Client Component `SmsClient` - -This class exposes the `recv_callback` method. The user overrides this interface to process received SMS messages. - -Code: - -```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 -``` - -## Serial Port and TCP Server Relay 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() -``` \ No newline at end of file +If you have any questions or need support, please refer to the [QuecPython documentation](https://python.quectel.com/doc/en) or open an issue in this repository. \ No newline at end of file diff --git a/README_ZH.md b/README_ZH.md index fdcf710..42a88e2 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -1,389 +1,36 @@ -# QuecPython 应用框架——QFrame +# QuecPython 应用开发框架 —— QFrame -[[English](./README.md)] +中文 | [English](README.md) ## 概述 -`QFrame` 应用框架是 QuecPython 开发的一个基础应用框架。 +`QFrame` 是 一款基于 QuecPython 平台的一个基础应用框架,旨在使得用户更加方便简单地使用 QuecPython 平台进行业务开发。 -一个应用程序往往会依赖多个业务模块,各业务模块之间可能存在耦合现象。 -在框架设计中,业务模块之间通信是采用**星型结构设计**,如下图所示: +整体功能包含: -![](docs/media/star-structure.png) +- 基础模块:针对平台基础功能 api 二次封装,如线程、异步等,使得api使用更加贴近 cpython 风格。 +- 拓展类型:针对平台类型的拓展,如双向链表、有序字典、优先级队列、堆排序等。 +- 框架部分:提供可拆卸组件式的编程框架。 -图中的 Meditor是一个中介对象(通常命名为 `Application`),各个业务模块之间通过 `Application` 对象通信,这种设计被称之为**中介模式**。 +## 用法 -业务模块以应用拓展的形式安插在应用程序中,而各应用拓展之间的交互通过 `Application` 对象进行统一调度。 +- [用户指导](./docs/zh/用户指导.md) +- [示例代码](./docs/zh/示例代码.md) -## 应用对象 +## 贡献 -基于 `QFrame` 框架的应用程序必须有一个调度各业务模块的的中心对象,即上文提到的 `Application` 对象;应用参数也是通过该对象配置。 +我们欢迎对本项目的改进做出贡献!请按照以下步骤进行贡献: -示例代码如下: +1. Fork 此仓库。 +2. 创建一个新分支(`git checkout -b feature/your-feature`)。 +3. 提交您的更改(`git commit -m 'Add your feature'`)。 +4. 推送到分支(`git push origin feature/your-feature`)。 +5. 打开一个 Pull Request。 -```python -from usr.qframe import Application +## 许可证 -# init application instance -app = Application(__name__) +本项目使用 Apache 许可证。详细信息请参阅 [LICENSE](LICENSE) 文件。 -# 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 - } - } -) -``` - -## 应用拓展 - -应用拓展指的是被 `Application` 对象加载的业务模块。 - -一般来说,应用拓展从 `app.config` 获取其自身的配置并在初始化时传递给应用实例。 - -应用拓展的使用包含定义和初始化两部分。 - -### 应用拓展的定义与初始化 - -应用拓展提供一个名为 `AppExtensionABC` 的基类,定义如下: - -```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` 来完成初始化。 -- `load` 方法用来被 `Application` 对象调用,用于加载各应用拓展。 - -> 应用拓展定义中的 `name` 属性非常关键,因为该属性作为当前应用拓展在app中的标识。假设应用拓展的 `name` 属性为 `name="serial"`,那么在注册应用拓展到 app 后,我们可以通过 `app.serial` 来访问该应用拓展对象。 - -### 应用拓展交互 - -上文提到,业务模块以应用拓展的形式安插在应用程序中。业务之间必然存在交互,在 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() -``` - -在 `main.py` 脚本中,`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/app-block-digram.png) - -`Application`:主应用对象 - -- 内建应用拓展组件 - - `Network`:网络检测组件。提供异常断网恢复。 - - `Uart`:串口组件,提供串口读写功能。 - - `TcpClient`:TCP 客户端组件, 提供 tcp 读写和客户端重连功能。 - - `SmsClient`:短信客户端组件,提供短信读写功能。 -- 基础组件 - - `qsocket`:提供创建 socket 接口。 - - `ota`:提供 ota 升级接口。 - - `serial`:提供串口读写基本接口。 - - `threading`:提供创建线程接口、互斥锁、条件变量、线程安全队列、线程池等接口。 - - `logging`:提供日志接口。 - - `led`:提供 led 灯控制接口。 - -## 初始化流程图 - -![](docs/media/init-flow.png) - -系统流程初始化步骤: - -1. 实例化应用对象 -2. 导入配置 json 文件 -3. 初始化各应用拓展组件(此步骤会将各个应用拓展注册进主应用对象中,方便各拓展之间通信) -4. 检测网路(此步骤会阻塞等待网络就绪,若等待超时则尝试 cfun 切换以恢复网络) -5. 加载应用拓展,并启动相关服务(用户可自定义实现) -6. 系统进入正常运行状态(默认开启sim卡和网络检测,若出现掉网情况,会自行尝试cfun切换以恢复网络) - -## 内置组件 - -### TCP 客户端组件 `TcpClient` - -该类向用户开放了两个接口: - -- `recv_callback` 方法,用户通过重写该方法,实现对 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` - -该类向用户开放了两个接口: - -- `recv_callback` 方法,用户通过重写该方法,实现对接收到的串口数据的业务处理。 -- `send` 方法,用户可调用该方法向串口发送数据。 - -代码如下: - -```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` - -该类向用户开放了三个接口: - -- `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` - -该类开放 `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() -``` +## 支持 +如果您有任何问题或需要支持,请参阅 [QuecPython 文档](https://python.quectel.com/doc) 或在本仓库中打开一个 issue。 diff --git a/docs/en/Example_Code.md b/docs/en/Example_Code.md new file mode 100644 index 0000000..6945d94 --- /dev/null +++ b/docs/en/Example_Code.md @@ -0,0 +1,86 @@ +## Serial Port and TCP Server Relay Demo + +This routine test uses the QFrame framework to implement a simple data transmission between a serial port and a TCP server. + +Pre preparation: + +1. Download the source code of QFrame: `https://github.com/QuecPython/QFrame.git` +2. Download the source code to the module's `usr` directory +3. coding `demo.py` (Example source code can be found in the following text) +4. running`demo.py` + +source code in `demo.py` as below: + +```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() +``` + +> NOTICE: In this routine test, `from usr. qframe import ` indicates that we imported the various functional modules of `qframe` from the `usr` directory. + diff --git a/docs/en/User_Guider.md b/docs/en/User_Guider.md new file mode 100644 index 0000000..190a89e --- /dev/null +++ b/docs/en/User_Guider.md @@ -0,0 +1,314 @@ +# QuecPython Application Framework - QFrame + +## Overview + +The `QFrame` application framework is a basic application framework developed by QuecPython. + +An application often relies on multiple business modules, and there may be coupling between the business modules. +In the framework design, communication between business modules adopts a **star structure design**, as shown below: + +![](../media/star-structure.png) + +The Meditor in the diagram is a mediator object (usually named `Application`). The business modules communicate through the `Application` object. This design is called the **mediator pattern**. + +The business modules are plugged into the application program in the form of application extensions. The interaction between the application extensions is unifiedly dispatched through the `Application` object. + +## Application Object + +Applications based on the `QFrame` framework must have a central object to dispatch various business modules, namely the `Application` object mentioned above. Application parameters are also configured through this object. + +Sample code: + +```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 + } + } +) +``` + +## Application Extensions + +Application extensions refer to the plugged-in business modules that are loaded by the `Application` object. + +In general, the application extension gets its own configuration from `app.config` and passes it to the application instance during initialization. + +The use of application extensions contains two parts: definition and initialization. + +### Definition and Initialization of Application Extensions + +The application extension provides a base class called `AppExtensionABC`, defined as follows: + +```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 + + def load(self): + # loading extension functions, this method will be called in `app.mainloop` + raise NotImplementedError +``` + +This base class is inherited by the specific application extension class to constrain the interface definition of the application extension class. + +- We need to pass the `Application` application object to the initialization method `__init__`. When creating the application extension object, call `init_app` to complete the initialization of the extension; you can also directly create the application extension object without passing in the application object, and then explicitly call `init_app` later to complete the initialization. + +- The `load` method is called by the `Application` object and is used to load the respective application extensions. + +> The `name` attribute in the application extension definition is very critical because this attribute serves as the identity of the current application extension in app. Assuming the `name` attribute of the application extension is `name="serial"`, after registering the application extension into app, we can access the application extension object through `app.serial`. + +### Interaction between Application Extensions + +As mentioned earlier, business modules are plugged into the application program in the form of application extensions. There must be interactions between businesses, and in the QFrame framework, after registering each application extension into `Application`, each application extension can call the interfaces of other application extensions through the application object. + +In each business implementation, we can import the global `CurrentApp` to get the current application object instead of importing from the module that instantiates the application. As follows: + +```python +# import CurrentApp +from usr.qframe import CurrentApp + +# get global current application +app = CurrentApp() +``` + +Use `CurrentApp` in multiple application extensions to implement interface calls between various application extensions. + +Now assume we have 2 application extensions: + +(1) TCP client: receive and send TCP server data + +```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) Serial port: receive and transmit serial port data + +```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') +``` + +The application script is written as follows: + +```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() +``` + +In the `main.py` script, the `app.mainloop()` function will call the `load` method of each application extension one by one to start the business functions of the application extension. For example, in `TcpClient.load`, the user should implement functions such as connecting to the server and listening to downstream data from the server; functions such as listening to serial port data should be implemented in `Uart.load`. + +Use `CurrentApp` to access the current global application object to call the interfaces of each application extension: + +![](../media/currentapp.png) + +Each application extension can use `CurrentApp()` to obtain the current globally unique application object, and then obtain the objects of each application extension through the application object, and then call the business interfaces of each application extension. + +As shown in the above code, after receiving data from the serial port, get the TCP client object via `CurrentApp().tcp_client` and then use its `send` method to relay the serial port data to the TCP server; after the TCP client receives data, get the serial port object via `CurrentApp().uart` and then use its `write` method to relay the server data to the serial port. + +## Component Diagram + +![](../media/app-block-digram.png) + +`Application`: Main application object + +- Built-in application extension components + - `Network`: Network detection component. Provides abnormal network recovery. + - `Uart`: Serial port component, provides serial read and write functionality. + - `TcpClient`: TCP client component, provides TCP read/write and client reconnection capabilities. + - `SmsClient`: SMS client component, provides SMS read/write capabilities. +- Basic components + - `qsocket`: Provides socket creation interface. + - `ota`: Provides ota upgrade interface. + - `serial`: Provides basic serial read/write interfaces. + - `threading`: Provides thread creation interface, mutex locks, condition variables, thread-safe queues, thread pools, etc. + - `logging`: Provides log interface. + - `led`: Provides LED control interface. + +## Initialization Process + +![](../media/init-flow.png) + +System initialization process: + +1. Instantiate application object +2. Import configuration json file +3. Initialize each application extension component (this step will register each application extension into the main application object to facilitate communication between extensions) +4. Detect network (this step will block waiting for network readiness, if the timeout expires, try cfun switching to recover the network) +5. Load application extensions and start related services (custom implementation by user) +6. The system enters normal running state (network detection is enabled by default. In case of network disconnection, it will try cfun switching automatically to restore network) + +## Built-in Components + +### TCP Client Component `TcpClient` + +This class exposes two interfaces to the user: + +- The `recv_callback` method. The user overrides this method to handle downstream data from the TCP server. +- The `send` method. The user can call this method to send data to the server. + +At the same time, this class provides server auto-reconnection capability. + +Code: + +```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 +``` + +### Serial Communication Component `Uart` + +This class exposes two interfaces to the user: + +- The `recv_callback` method. The user overrides this method to handle the received serial port data. +- The `send` method. The user can call this method to send data to the serial port. + +Code: + +```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 Component `NetWork` + +This class exposes three interfaces to the user: + +- The `wait_network_ready` method. This interface will block and wait for the network to reconnect, automatically perform CFun switching in an attempt to restore the network. +- The `register_net_callback` method. This interface registers a network exception callback which will be invoked when the network connects or disconnects. +- The `register_sim_callback` method. This interface registers a SIM hot swap callback which will be invoked when the SIM card is inserted or removed. + +Code: + +```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 +``` + +### SMS Client Component `SmsClient` + +This class exposes the `recv_callback` method. The user overrides this interface to process received SMS messages. + +Code: + +```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 +``` + diff --git "a/docs/zh/\347\224\250\346\210\267\346\214\207\345\257\274.md" "b/docs/zh/\347\224\250\346\210\267\346\214\207\345\257\274.md" new file mode 100644 index 0000000..19c652e --- /dev/null +++ "b/docs/zh/\347\224\250\346\210\267\346\214\207\345\257\274.md" @@ -0,0 +1,314 @@ +# QuecPython 应用框架——QFrame + +## 概述 + +`QFrame` 应用框架是 QuecPython 开发的一个基础应用框架。 + +一个应用程序往往会依赖多个业务模块,各业务模块之间可能存在耦合现象。 +在框架设计中,业务模块之间通信是采用**星型结构设计**,如下图所示: + +![](../media/star-structure.png) + +图中的 Meditor是一个中介对象(通常命名为 `Application`),各个业务模块之间通过 `Application` 对象通信,这种设计被称之为**中介模式**。 + +业务模块以应用拓展的形式安插在应用程序中,而各应用拓展之间的交互通过 `Application` 对象进行统一调度。 + +## 应用对象 + +基于 `QFrame` 框架的应用程序必须有一个调度各业务模块的的中心对象,即上文提到的 `Application` 对象;应用参数也是通过该对象配置。 + +示例代码如下: + +```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 + } + } +) +``` + +## 应用拓展 + +应用拓展指的是被 `Application` 对象加载的业务模块。 + +一般来说,应用拓展从 `app.config` 获取其自身的配置并在初始化时传递给应用实例。 + +应用拓展的使用包含定义和初始化两部分。 + +### 应用拓展的定义与初始化 + +应用拓展提供一个名为 `AppExtensionABC` 的基类,定义如下: + +```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` 来完成初始化。 +- `load` 方法用来被 `Application` 对象调用,用于加载各应用拓展。 + +> 应用拓展定义中的 `name` 属性非常关键,因为该属性作为当前应用拓展在app中的标识。假设应用拓展的 `name` 属性为 `name="serial"`,那么在注册应用拓展到 app 后,我们可以通过 `app.serial` 来访问该应用拓展对象。 + +### 应用拓展交互 + +上文提到,业务模块以应用拓展的形式安插在应用程序中。业务之间必然存在交互,在 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() +``` + +在 `main.py` 脚本中,`app.mainloop()` 函数会逐个调用应用拓展的 `load` 方法来启动应用拓展的各项业务功能。比如,在 `TcpClient.load` 中用户应该实现如连接服务器、监听服务器下行数据等功能;应该在 `Uart.load` 中实现监听串口数据等功能。 + +使用 `CurrentApp` 来访问当前应用程序对象,以便调用各应用拓展接口: + +![](../media/currentapp.png) + +各个应用拓展中可以使用 `CurrentApp()` 来获取当前全局唯一应用程序对象,并通过应用程序对象来获取各个应用拓展对象,继而调用各应用拓展的业务接口。 + +如上述代码所示,串口收到数据后,通过 `CurrentApp().tcp_client` 来获取 tcp 客户端对象,继而使用该对象 `send` 方法将串口数据透传至 tcp 服务器;tcp 客户端收到数据后,通过 `CurrentApp().uart` 来获取串口对象,接入使用该对象 `write` 方法将服务器数据透传给串口。 + +## 组件框图 + +![](../media/app-block-digram.png) + +`Application`:主应用对象 + +- 内建应用拓展组件 + - `Network`:网络检测组件。提供异常断网恢复。 + - `Uart`:串口组件,提供串口读写功能。 + - `TcpClient`:TCP 客户端组件, 提供 tcp 读写和客户端重连功能。 + - `SmsClient`:短信客户端组件,提供短信读写功能。 +- 基础组件 + - `qsocket`:提供创建 socket 接口。 + - `ota`:提供 ota 升级接口。 + - `serial`:提供串口读写基本接口。 + - `threading`:提供创建线程接口、互斥锁、条件变量、线程安全队列、线程池等接口。 + - `logging`:提供日志接口。 + - `led`:提供 led 灯控制接口。 + +## 初始化流程图 + +![](../media/init-flow.png) + +系统流程初始化步骤: + +1. 实例化应用对象 +2. 导入配置 json 文件 +3. 初始化各应用拓展组件(此步骤会将各个应用拓展注册进主应用对象中,方便各拓展之间通信) +4. 检测网路(此步骤会阻塞等待网络就绪,若等待超时则尝试 cfun 切换以恢复网络) +5. 加载应用拓展,并启动相关服务(用户可自定义实现) +6. 系统进入正常运行状态(默认开启sim卡和网络检测,若出现掉网情况,会自行尝试cfun切换以恢复网络) + +## 内置组件 + +### TCP 客户端组件 `TcpClient` + +该类向用户开放了两个接口: + +- `recv_callback` 方法,用户通过重写该方法,实现对 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` + +该类向用户开放了两个接口: + +- `recv_callback` 方法,用户通过重写该方法,实现对接收到的串口数据的业务处理。 +- `send` 方法,用户可调用该方法向串口发送数据。 + +代码如下: + +```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` + +该类向用户开放了三个接口: + +- `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` + +该类开放 `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 +``` + diff --git "a/docs/zh/\347\244\272\344\276\213\344\273\243\347\240\201.md" "b/docs/zh/\347\244\272\344\276\213\344\273\243\347\240\201.md" new file mode 100644 index 0000000..e4c490c --- /dev/null +++ "b/docs/zh/\347\244\272\344\276\213\344\273\243\347\240\201.md" @@ -0,0 +1,85 @@ +## 串口与TCP服务器透传demo + +本例程使用`QFrame`框架来实现一个简单的串口与TCP服务器的数传。 + +前置准备: + +1. 下载`QFrame`源代码:`https://github.com/QuecPython/QFrame.git` +2. 将源代码下载到模组`usr`目录下 +3. 编写`demo.py`(示例源码见下文) +4. 运行`demo.py` + +参考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() +``` + +> 温馨提示:本例程中`from usr.qframe import `表明我们从`usr`目录下导入`qframe`的各功能模块。 \ No newline at end of file From fbab8dbf0c2c13e3ad7aeed1aa903c65fd8d70dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dustin=20Wei=28=E9=9F=A6=E4=BC=9F=29?= Date: Tue, 25 Jun 2024 11:16:11 +0800 Subject: [PATCH 2/2] rename doc link name for API Reference --- README.md | 2 +- README_ZH.md | 2 +- docs/en/{User_Guider.md => API_Reference.md} | 0 .../zh/API\345\217\202\350\200\203\346\211\213\345\206\214.md" | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename docs/en/{User_Guider.md => API_Reference.md} (100%) rename "docs/zh/\347\224\250\346\210\267\346\214\207\345\257\274.md" => "docs/zh/API\345\217\202\350\200\203\346\211\213\345\206\214.md" (100%) diff --git a/README.md b/README.md index 1f686af..1cf3709 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The overall functions include: ## Usage -- [User Guider](./docs/en/User_Guider.md) +- [API Reference Manual](./docs/en/API_Reference.md) - [Example Code](./docs/en/Example_Code.md) ## Contribution diff --git a/README_ZH.md b/README_ZH.md index 42a88e2..c0269c8 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -14,7 +14,7 @@ ## 用法 -- [用户指导](./docs/zh/用户指导.md) +- [API 参考手册](./docs/zh/API参考手册.md) - [示例代码](./docs/zh/示例代码.md) ## 贡献 diff --git a/docs/en/User_Guider.md b/docs/en/API_Reference.md similarity index 100% rename from docs/en/User_Guider.md rename to docs/en/API_Reference.md diff --git "a/docs/zh/\347\224\250\346\210\267\346\214\207\345\257\274.md" "b/docs/zh/API\345\217\202\350\200\203\346\211\213\345\206\214.md" similarity index 100% rename from "docs/zh/\347\224\250\346\210\267\346\214\207\345\257\274.md" rename to "docs/zh/API\345\217\202\350\200\203\346\211\213\345\206\214.md"