From: snickersun Date: Thu, 21 May 2026 06:54:42 +0000 (+0800) Subject: FactoryTool-2.1源码+编译后的工具+k8产测脚本 X-Git-Url: http://git.snicker.com.cn/?a=commitdiff_plain;h=4d89ca5e905744486a015003e5245cf7bedeca8b;p=mirrors%2FASR1603_G5.git FactoryTool-2.1源码+编译后的工具+k8产测脚本 --- diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1.zip" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1.zip" new file mode 100644 index 00000000..db0436e5 Binary files /dev/null and "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1.zip" differ diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/.gitignore" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/.gitignore" new file mode 100644 index 00000000..0329cbb7 --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/.gitignore" @@ -0,0 +1,131 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# log +log/ \ No newline at end of file diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/.gitmodules" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/.gitmodules" new file mode 100644 index 00000000..3a95fa13 --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/.gitmodules" @@ -0,0 +1,3 @@ +[submodule "QuecPython_Ymodem"] + path = QuecPython_Ymodem + url = git@github.com:supremeys/QuecPython_Ymodem.git diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/LICENSE" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/LICENSE" new file mode 100644 index 00000000..34f8805a --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/LICENSE" @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 瓜袁 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/README.md" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/README.md" new file mode 100644 index 00000000..2759bd86 --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/README.md" @@ -0,0 +1,190 @@ +# QuecPython产测工具使用指导手册 + +## 简介 + +QuecPython产测工具是专用于QuecPython项目对模块功能测试的工具,方便对出厂前已经烧录正式固件的模块进行相关的功能测试,本文档主要介绍如何使用该工具,在电脑使用Windows7及以上系统都可使用。 + +适用模块: + +所有使用QuecPython开发的模块均可使用 + +## 功能介绍 +1. 支持4台设备同步测试 +2. 支持自动测试和人工断点测试 +3. 可自定义测试脚本,测试方式(自动测试和人工测试) + +## 工具获取 + +[FactoryTool-Tags](https://github.com/QuecPython/FactoryTool/tags) + +github上进入FactoryTool项目最新的tag,可以下载可执行包( FactoryTool.zip ) 和 源代码( Source code(zip/tar.gz) ) + +## 页面介绍 + +**菜单栏** 包括编辑菜单和日志菜单 + +**编辑菜单** 用于编辑测试脚本和Excel日志文件 + +**日志菜单** 用于查看测试日志文件和工具运行日志文件 + +**加载栏** 包括 加载测试文件和配置文件,其中测试文件为要测试的测试文件,配置文件为需要测试function,测试的顺序,测试项的名称,测试的方式(人工测试/自动测试) + +**测试区域** 包括 端口、测试项名称、测试的方式、测试结果,工具有四个测试栏位最多可一次性同时测试四个设备,也可以单独测试某个设备。 + +> **注意:测试的模块需要保证交互口不处于堵塞状态,否则测试将失败** + +![1692598668142](./media/1692598668142.jpg) + +## 测试脚本 + +测试脚本编写时需要注意不得改动原有模板代码结构,建议编写完之后先在模块中手动运行测试,确认运行结果是否满足预期,测试脚本模板中需要改动的位置有两个 + +**导入模块**:导入测试中用到的python库 + +**测试函数**:自行编写测试函数,函数名可以自定义,但是需要是静态方法且无法传参,函数体内容根据测试需求自定义,函数需要有返回值,必须是bool值 + +测试脚本示例: + +```python +# Write the modules that need to be imported here +import sim +import net +import uos +import utime + +class TestBase(object): + # ------This area is for testing code------ + @staticmethod + def det_signal(): + utime.sleep(2) + if sim.getStatus() == 1: + if net.getConfig()[0] == 5: + return True + else: + return False + else: + return False + + @staticmethod + def det_file_space(): + utime.sleep(2) + if uos.statvfs('usr')[3] > 5: + return True + else: + return False + # ------This area is for testing code------ +``` + +## 配置文件 +配置文件为json文件,需要配置三维数据,分别为: 测项名称, 测项的方法名称, 测试方式(1为人工测试 0为自动测试) + + +最后一列 含义 +0 自动开始 + 自动判结果 +1 OK 确认开始 + 人工判结果 +2 OK 确认开始 + 自动判结果(20s 超时) + +测试脚本示例: + +```json +// 测项名称 function 测试方式: 1为人工测试 0为自动测试 +{ + "info": [["Memory test", "det_file_space", 0, 1], + ["Signal test", "det_signal", 0 , 0 ]] +} +``` + + +## 测试原理 + +产测工具利用python脚本的形式来测试QuecPython产品的业务功能,通过CDC口来执行测试脚本,测试脚本根据测试需求可以自行调整以达成完成生产测试的功能 + +1. 通过QuecPython的API接口来获取模组参数和运行状态 +2. 通过访问正在运行的业务代码中的对象来获取业务运行情况 +3. 通过调用业务代码提供的接口才测试业务功能或者硬件功能 +4. 通过QuecPython API接口传输配置文件或者写入产品参数 + +## 测试步骤 + +**步骤一**:编辑测试文件和配置文件 + +编辑测试**module_test.py**代码 和 配置文件**sort_setting.json**,测试代码和配置文件案例如上图所示 + +**步骤二**:打开工具,选择测试文件和配置文件 + +打开工具后,点击 **选择** 按钮,分别选择上一步编辑好的测试代码和配置文件,若有检测到串口,则该串口下会显示每一项测试名称以及测试方式 + +![1692599729648](./media/1692599729648.jpg) + +**步骤三**:开始测试 + +点击 **全部开始** 按钮: 开始测试所有已接入的模块 + +点击 **开始** 按钮: 开始测试对应的那一个模块 + +若是人工检测会弹窗提示是否成功,可人工点击 **是/否** 来确认该项结果 +![1692599919725](./media/1692599919725.jpg) + +**步骤四**:查看测试结果 + +可在测试结果栏位直接看到测试结果: +1. 通过: 绿色背景色,测试结果栏位标记: 通过 +2. 不通过: 红色背景色,测试结果栏位标记: 不通过 +3. 测试中: 黄色背景色,表示该测项正在检测中 + +![1692599801694](./media/1692599801694.jpg) + +**步骤五**:查看测试日志 + +在工具同级目录下生成**Test-Result.xlsx**文件,包含测试项目内容以及测试结果日志 + +## 测试结果 + +每一个模块的的测试结果会写入到excel中导出,可以通过菜单栏的 **编辑** 中的 **编辑Excel文件**菜单打开该excel文件 + +每一次测试会在excel中生成一条测试记录(无论测试成功或者失败),多次测试会有多条记录,根据测试设备的COM口区分 + +该文件会保存在工具同级目录下,测试过程中自动追加 + + +## 如何拉取仓库 + +若有二次开发需求,可直接拉取代码仓库 + +```shell +git clone --recurse-submodules https://github.com/QuecPython/FactoryTool.git -b interventionable +``` + +## 环境配置 + +**安装依赖库** + +```shell +pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple +# 如果上面方式安装失败,可以使用单独安装的形式 +pip install wxpython -i https://pypi.tuna.tsinghua.edu.cn/simple +pip install pypubsub -i https://pypi.tuna.tsinghua.edu.cn/simple +``` +**运行代码** + +```shell +python main.py +``` +**编译成可执行程序** + +```shell +# 安装打包依赖(首次) +pip install pyinstaller polib -i https://pypi.tuna.tsinghua.edu.cn/simple + +# 方式一:一键脚本(推荐,生成 factorytool.exe 到本目录) +python build.py +# 或双击 build.bat + +# 方式二:手动命令 +pyinstaller -F -w --clean --name factorytool --icon media/quectel.ico --distpath . --add-data "locale;locale" main.py + +# 方式三:使用 spec +pyinstaller --clean --distpath . main.spec +``` + +打包完成后,可执行文件为 **`factorytool.exe`**(与 `config.ini`、测试脚本、json 配置放在同一目录运行)。 diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/Test-Result.xlsx" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/Test-Result.xlsx" new file mode 100644 index 00000000..1dc9d233 Binary files /dev/null and "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/Test-Result.xlsx" differ diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/build.bat" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/build.bat" new file mode 100644 index 00000000..ba46b7ad --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/build.bat" @@ -0,0 +1,12 @@ +@echo off +chcp 65001 >nul +cd /d "%~dp0" +echo ===== FactoryTool 打包 ===== +python build.py +if errorlevel 1 ( + echo 打包失败。 + pause + exit /b 1 +) +echo. +pause diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/build.py" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/build.py" new file mode 100644 index 00000000..22b1a9e7 --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/build.py" @@ -0,0 +1,137 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +""" +打包 FactoryTool-2.1 为单文件 factorytool.exe(输出到本目录)。 +用法: python build.py +""" + +from __future__ import print_function + +import os +import shutil +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent +EXE_NAME = "factorytool" +MAIN_PY = ROOT / "main.py" +ICON = ROOT / "media" / "quectel.ico" + + +def ensure_pyinstaller(): + try: + import PyInstaller # noqa: F401 + except ImportError: + print("正在安装 PyInstaller ...") + subprocess.check_call( + [sys.executable, "-m", "pip", "install", "pyinstaller", "-q"], + cwd=str(ROOT), + ) + + +def compile_po_files(): + """将 locale 下 .po 编译为 .mo,供 gettext 在 exe 中使用。""" + po_files = list((ROOT / "locale").rglob("*.po")) + if not po_files: + return + try: + import polib + except ImportError: + print("正在安装 polib(编译语言包)...") + subprocess.check_call( + [sys.executable, "-m", "pip", "install", "polib", "-q"], + cwd=str(ROOT), + ) + import polib + + for po in po_files: + mo = po.with_suffix(".mo") + if mo.exists() and mo.stat().st_mtime >= po.stat().st_mtime: + continue + polib.pofile(str(po)).save_as_mofile(str(mo)) + print("compiled:", mo.relative_to(ROOT)) + + +def run_pyinstaller(): + sep = os.pathsep + add_data = "locale{sep}locale".format(sep=sep) + + cmd = [ + sys.executable, + "-m", + "PyInstaller", + "-F", + "-w", + "--clean", + "--noconfirm", + "--name", + EXE_NAME, + "--icon", + str(ICON), + "--distpath", + str(ROOT), + "--workpath", + str(ROOT / "build"), + "--specpath", + str(ROOT), + "--add-data", + add_data, + "--hidden-import", + "serial.tools.list_ports", + "--hidden-import", + "pubsub", + "--hidden-import", + "pubsub.core", + str(MAIN_PY), + ] + + print("执行:", " ".join(cmd)) + subprocess.check_call(cmd, cwd=str(ROOT)) + + +def locate_exe(): + candidates = [ + ROOT / (EXE_NAME + ".exe"), + ROOT / "dist" / (EXE_NAME + ".exe"), + ] + for path in candidates: + if path.is_file(): + return path + return None + + +def main(): + os.chdir(str(ROOT)) + + if not MAIN_PY.is_file(): + print("错误: 找不到 main.py") + sys.exit(1) + if not ICON.is_file(): + print("错误: 找不到图标 media/quectel.ico") + sys.exit(1) + + ensure_pyinstaller() + compile_po_files() + run_pyinstaller() + + built = locate_exe() + if not built: + print("错误: 未生成 {0}.exe".format(EXE_NAME)) + sys.exit(1) + + target = ROOT / (EXE_NAME + ".exe") + if built.resolve() != target.resolve(): + shutil.copy2(str(built), str(target)) + + dist_dir = ROOT / "dist" + if dist_dir.is_dir() and not any(dist_dir.iterdir()): + dist_dir.rmdir() + + print("") + print("打包完成:", target) + print("运行: 双击 factorytool.exe 或在本目录放置 config.ini / 测试脚本 / json 配置") + + +if __name__ == "__main__": + main() diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/cloud_upload.py" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/cloud_upload.py" new file mode 100644 index 00000000..08be496d --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/cloud_upload.py" @@ -0,0 +1,10 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +""" +@Project:Factory_test +@File:cloud_upload.py +@Author:rivern.yuan +@Date:2022/9/7 10:06 +""" + +# TODO 执行结果上传云端 diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/config.ini" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/config.ini" new file mode 100644 index 00000000..00a540cf --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/config.ini" @@ -0,0 +1,4 @@ +[software] +py_file = D:\workspace\EG810\ASR1603_JSWM3\产测工具\FactoryTool-2.1\k8_module_test.py +json_file = D:\workspace\EG810\ASR1603_JSWM3\产测工具\FactoryTool-2.1\k8_sort_setting.json + diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/factorytool.exe" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/factorytool.exe" new file mode 100644 index 00000000..c4ded81c Binary files /dev/null and "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/factorytool.exe" differ diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/file_handler.py" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/file_handler.py" new file mode 100644 index 00000000..cec7c9df --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/file_handler.py" @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +""" +@Project:Factory_test +@File:file_handler.py +@Author:rivern.yuan +@Date:2022/9/7 10:09 +""" +import serial_handler +import openpyxl +import codecs +import json +import sys +import re +import os + + +class ExcelHandler(object): + """ + handle test result Excel + excel_file: filename include full path + """ + def __init__(self, excel_file): + self.file = excel_file + if os.path.exists(self.file): + pass + else: + wb = openpyxl.Workbook() + wb.save(self.file) + self.wb = openpyxl.load_workbook(self.file) + self.ws = self.wb[self.wb.sheetnames[0]] + + + def get_rows_columns(self): + rows = self.ws.max_row + columns = self.ws.max_column + return rows, columns + + def get_cell_value(self, row, column): + return self.ws.cell(row=row, column=column).value + + def get_col_values(self, column): + rows = self.ws.max_row + columns_data = [] + for i in range(1, rows + 1): + columns_data.append(self.ws.cell(row=i, column=column).value) + return columns_data + + def get_row_values(self, row): + columns = self.ws.max_column + rows_data = [] + for i in range(1, columns + 1): + rows_data.append(self.ws.cell(row=row, column=i).value) + return rows_data + + def set_cell_value(self, row, column, cell_value): + try: + self.ws.cell(row=row, column=column).value = cell_value + except KeyError as e: + self.ws.cell(row=row, column=column).value = "Write Fail" + print(e) + finally: + self.wb.save(self.file) + + def close(self): + self.wb.close() + + +class JsonHandler(object): + """ + json_file: filename include full path + """ + def __init__(self, json_file): + self._json_name = json_file + + def read_json(self): + with codecs.open(self._json_name, 'r', 'utf-8') as f: + data = json.load(f) + return data + + @staticmethod + def write_json(data, json_name): + with codecs.open(json_name, 'w', 'utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=2) + + +class ScriptHandler(object): + """ + QuecPython test script + script_file: filename include full path + """ + def __init__(self, script_file: str): + self.script_file = script_file + self.pattern_py = {re.compile(r'#.*'), re.compile(r'(""".*?""")*|(\'\'\'.*?\'\'\')*'), } + + """compile py script""" + def script_check(self): + try: + if self.script_file[-3:] == ".py": + with open(self.script_file, "r", encoding='utf-8') as f: + script_data = f.read() + compile(script_data, '', 'exec') + return True + else: + raise TypeError("File %s is not python file!" % self.script_file) + except: + compileInfo = sys.exc_info() + print("测试脚本语法错误" + str(compileInfo[0]) + ":" + str(compileInfo[1])) + + """get effective py cmd""" + def script_parse(self): + content = [] + with open(self.script_file, "r") as f: + for line in f.readlines(): + result = re.sub(line, "", line.split("\n")[0]) + if result != "": + content.append(result) + return content + + +if __name__ == '__main__': + excel_test = ExcelHandler("test\\Test-Result.xlsx") + print(excel_test.get_rows_columns()) + # excel_test.set_cell_value(1, 1, "No.") + # excel_test.set_cell_value(1, 2, "SerialNum") + # print(excel_test.get_col_values(1)) + # print(excel_test.get_row_values(1)) + # print(excel_test.get_cell_value(1, 1)) + + result = ["com63", "972658346523","128934928734","success", " True ,False"] + rows, columns = excel_test.get_rows_columns() + excel_test.set_cell_value(rows + 1, 1, rows) + for i, value in enumerate(result): + excel_test.set_cell_value(rows + 1, i + 2, value) + + + excel_test.close() + + # py_test = ScriptHandler("module\\module_test.py") + # if py_test.script_check(): + # print(py_test.script_parse()) + # ser = serial_handler.SerialHandler("COM63") + # print(ser.run_cmd(py_test.script_parse())) diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/k8_module_test.py" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/k8_module_test.py" new file mode 100644 index 00000000..e9c00c00 --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/k8_module_test.py" @@ -0,0 +1,179 @@ +import sim +import net +import uos +import utime +try : + import quecgnss +except Exception as e: + print("quecgnss :{}".format(e)) +import wifiScan +from misc import USB,Power +import modem +import ql_fs +import ujson +from machine import I2C +import audio +from machine import Pin +from machine import ExtInt + +usb = USB() + + +def extint_cb(args): + pass +class TestBase(object): + + @staticmethod + def Wifi_test(): + wifiScan.control(1) + Wifi_s = wifiScan.start() + if Wifi_s != -1: + wifiScan.control(0) + return True + else: + wifiScan.control(0) + return False + + @staticmethod + def usb_callback(): + status = usb.getStatus() + if status != -1: + return True + else: + return False + + @staticmethod + def modem_imei(): + imei_value = modem.getDevImei() + print("modem_imei:{}".format(imei_value)) + if imei_value != 864430010001091: + return True + else: + if imei_value == 864430010001091: + return False + + @staticmethod + def fw_version(): + try: + import version_util + version = version_util.get_unified_version() + except Exception as e: + print("version_util :{}".format(e)) + return False + print("product_version:{}".format(version)) + return True + + @staticmethod + def write_test(): + if ql_fs.path_exists("/ext") == False: + ldev = uos.VfsLfs1(32, 32, 32, "ext_fs", 0, 3) + uos.mount(ldev, '/ext') + write_data = {"aaa": "aba"} + with open("/ext/tmp.json", "w", encoding="utf-8") as fd: + ujson.dump(write_data, fd) + read_data = [] + with open("/ext/tmp.json", "r") as fd: + read_data = ujson.load(fd) + if len(write_data) == len(read_data): + return True + else: + return False + + @staticmethod + def sim_test(): + if sim.getIccid() == -1: + return False + print("sim_test:{}".format(sim.getIccid())) + return True + + @staticmethod + def signal_test(): + signal = net.csqQueryPoll() + print("signal_test:{}".format(signal)) + if signal > 20 and signal<=31: + return True + print("signal_test:测试失败! 信号不在(20,31]范围signal:{}".format(signal)) + return False + + @staticmethod + def microphone_test(): + # 16k 流式录放(与 vc_session 一致),人工听音 + SR = 16000 + aud = audio.Audio(0) + aud.setVolume(11) + aud.stopAll() + rec = audio.Record() + rec.gain_set(4, 10) + buf = bytearray(4096) + pcm = bytearray() + rec.stream_start(audio.Record.WAV, SR, 0) + print("microphone_test: speak...") + for _ in range(20): + n = rec.stream_read(buf, 4096) + if n > 0: + pcm.extend(buf[:n]) + utime.sleep_ms(100) + rec.stream_stop() + rec.stop() + aud.stopAll() + utime.sleep_ms(50) + play = audio.Audio.PCM(0, 1, SR, 1, 1, 2) + play.stream_write_start() + off = 0 + while off < len(pcm): + play.stream_write(pcm[off:off + 4096], min(4096, len(pcm) - off)) + off += 4096 + utime.sleep_ms(max(500, len(pcm) // 32)) + play.stream_stop() + play.close() + print("microphone_test: done, bytes={}".format(len(pcm))) + return True + + @staticmethod + def power_key_test(): + extint = ExtInt(ExtInt.GPIO26, ExtInt.IRQ_RISING_FALLING, ExtInt.PULL_DISABLE, extint_cb) + extint.disable() + key26 = Pin(Pin.GPIO26, Pin.IN, Pin.PULL_PU, 0) + i = 1000 + key_v = key26.read() + print("power_key_test:",key_v) + while 1: + if key26.read() != key_v: + return True + utime.sleep_ms(20) + i-=1 + if i <= 0: + break + return False + + @staticmethod + def key1_test(): + extint = ExtInt(ExtInt.GPIO22, ExtInt.IRQ_RISING_FALLING, ExtInt.PULL_DISABLE, extint_cb) + extint.disable() + key20 = Pin(Pin.GPIO22, Pin.IN, Pin.PULL_PU, 0) + i = 1000 + key_v = key20.read() + print("key1_test1:",key_v) + while 1: + print("key1_test:",key20.read()) + if key20.read() != key_v: + return True + utime.sleep_ms(20) + i-=1 + if i <= 0: + break + return False + + def motor_test(): + motor_pin = Pin(Pin.GPIO20, Pin.OUT, Pin.PULL_PD, 0) + motor_pin.write(1) + utime.sleep_ms(1000) + motor_pin.write(0) + return True + def det_changedir_end(): + #uos.remove("/usr/test.py") + Power.powerRestart() + + return True + + diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/k8_sort_setting.json" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/k8_sort_setting.json" new file mode 100644 index 00000000..c56dc736 --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/k8_sort_setting.json" @@ -0,0 +1,30 @@ +{ + "info": [ + ["固件版本", "fw_version()", 0,0], + ["充电测试", "usb_callback()", 0,0], + ["RF校准", "modem_imei()", 0,0], + ["flash读写", "write_test()", 0,0], + ["SIM卡测试","sim_test()",0,0], + ["4G信号测试","signal_test()",0,0], + ["电源键测试","power_key_test()",0,2], + ["按键1测试","key1_test()",0,2], + ["麦克风喇叭测试","microphone_test()",0,1], + ["震动马达测试","motor_test()",0,1], + ["测试结束","det_changedir_end()",0,2] + + ], + "tips": { + "固件版本":"正在测试固件版本...", + "充电测试":"正在测试充电...", + "RF校准":"正在确认RF参数是否已写入...", + "flash读写":"正在外部flash读写测试...", + "SIM卡测试":"需插卡测试!!!正在读取Iccid...", + "4G信号测试":"需插卡测试!!!正在读取信号强度...", + "电源键测试":"点击确认后,请在20秒内按下电源按键", + "按键1测试":"点击确认后,请在20秒内按下按键1", + "麦克风喇叭测试":"点击确认后,请对着麦克风说话,并留意喇叭是否有声音输出!!!!", + "震动马达测试":"震动马达测试...", + "测试结束":"测试完毕,将自动重启,请留意测试结果" + + } +} \ No newline at end of file diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/locale/en/LC_MESSAGES/English.mo" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/locale/en/LC_MESSAGES/English.mo" new file mode 100644 index 00000000..c206697a Binary files /dev/null and "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/locale/en/LC_MESSAGES/English.mo" differ diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/locale/en/LC_MESSAGES/English.po" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/locale/en/LC_MESSAGES/English.po" new file mode 100644 index 00000000..e4f539e0 --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/locale/en/LC_MESSAGES/English.po" @@ -0,0 +1,124 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: 2020-11-04 11:03+0800\n" +"PO-Revision-Date: 2022-05-05 15:33+0800\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=cp936\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" +"X-Generator: Poedit 3.0.1\n" + +msgid "±à¼­PyÎļþ" +msgstr "Edit Py File" + +msgid "±à¼­ExcelÎļþ" +msgstr "Edit Excel File" + +msgid "±à¼­" +msgstr "Edit" + +msgid "¹¤¾ßÈÕÖ¾" +msgstr "Tool Log" + +msgid "Ä£×éÈÕÖ¾" +msgstr "Module Log" + +msgid "ÈÕÖ¾" +msgstr "Log" + +msgid "Ñ¡Ôñ" +msgstr "Load" + +msgid "È«²¿¿ªÊ¼" +msgstr "Start All" + +msgid "¿ªÊ¼" +msgstr "Start" + +msgid "¹¤³§²âÊÔ" +msgstr "Factory Test" + +msgid "Ñ¡ÔñÅäÖÃÎļþ" +msgstr "Load Config File" + +msgid "Ñ¡Ôñ²âÊԽű¾" +msgstr "Load Test Script" + +msgid "¶Ë¿Ú" +msgstr "Port" + +msgid "IMEI" +msgstr "IMEI" + +msgid "ICCID" +msgstr "ICCID" + +msgid "²âÊÔʱ¼ä" +msgstr "Test Time" + +msgid "²âÊÔ½á¹û" +msgstr "T-Result" + +msgid "²é¿´ÈÕÖ¾" +msgstr "View Log" + + +msgid "py´úÂëÓÐÓï·¨´íÎó" +msgstr "The py code has syntax errors" + +msgid "ÇëÏÈÑ¡Ôñ²âÊÔÎļþ" +msgstr "Please select test file" + +msgid "²âÊÔÈÕÖ¾" +msgstr "Test log" + +msgid "µ±Ç°×´Ì¬: ¾ÍÐ÷..." +msgstr " Current status: ready..." + +msgid "»¶Ó­Ê¹ÓÃQuecPython" +msgstr " Welcome to QuecPython" + +msgid "Ä£¿éÈÕÖ¾" +msgstr "Module Log" + +msgid "²âÏî" +msgstr "Test-Item" + +msgid "²âÊÔ·½Ê½" +msgstr "T-Method" + +msgid "È˹¤¼ì²â" +msgstr "Manual Test" + +msgid "×Ô¶¯¼ì²â" +msgstr "Automated Test" + +msgid "ͨ¹ý" +msgstr "Pass" + +msgid "²»Í¨¹ý" +msgstr "Fail" + +msgid "²âÊÔÖÐ" +msgstr "Testing" + +msgid " µ±Ç°²âÊÔÏîΪ: " +msgstr " The current test item is: " + +msgid "\r\n\r\n ÇëÈ·ÈϸòâÊÔÏî ¡¾ÊÇ·ñͨ¹ý¡¿" +msgstr "\r\n\r\n Please confirm the test result [Passed or Not]" + +msgid "ÊÇ·ñ»Ö¸´ÉÏ´ÎÎļþ" +msgstr "Do you want to restore the last file" + +msgid "Îļþ²»´æÔÚ" +msgstr "File does not exist" + diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/locale/zh_CN/LC_MESSAGES/Chinese.mo" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/locale/zh_CN/LC_MESSAGES/Chinese.mo" new file mode 100644 index 00000000..a90c0659 Binary files /dev/null and "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/locale/zh_CN/LC_MESSAGES/Chinese.mo" differ diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/locale/zh_CN/LC_MESSAGES/Chinese.po" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/locale/zh_CN/LC_MESSAGES/Chinese.po" new file mode 100644 index 00000000..6ad87022 --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/locale/zh_CN/LC_MESSAGES/Chinese.po" @@ -0,0 +1,17 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: 2020-11-04 11:03+0800\n" +"PO-Revision-Date: 2020-11-04 20:56+0800\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=cp936\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: en\n" +"X-Generator: Poedit 2.4.1\n" \ No newline at end of file diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/log_redirect.py" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/log_redirect.py" new file mode 100644 index 00000000..450cafb7 --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/log_redirect.py" @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +""" +@Project:FactoryTool +@File:log_redirect.py +@Author:rivern.yuan +@Date:2022/10/11 9:47 +""" + +import os +import datetime + + +class RedirectErr: + """""" + + def __init__(self, obj, logpath): + """Constructor""" + if not os.path.exists(logpath + "\\logs\\software\\err\\"): + os.makedirs(logpath + "\\logs\\software\\err\\") + + file_path = logpath + "\\logs\\software\\err\\" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + "_run_err.log" + self.filename = open(file_path, "a", encoding='utf-8') + + def write(self, text): + """""" + if self.filename.closed: + pass + else: + curr_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.filename.write('[' + str(curr_time) + '] ' + text) + self.filename.flush() + + def flush(self): + pass + + +class RedirectStd: + """""" + + def __init__(self, obj, logpath): + """Constructor""" + if not os.path.exists(logpath + "\\logs\\software\\std\\"): + os.makedirs(logpath + "\\logs\\software\\std\\") + file_path = logpath + "\\logs\\software\\std\\" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + "_run_std.log" + self.filename = open(file_path, "a", encoding='utf-8') + + def write(self, text): + """""" + if self.filename.closed: + pass + else: + curr_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + if text != "": + self.filename.write('[' + str(curr_time) + '] ' + text) + self.filename.flush() + + def flush(self): + pass + + +# class StandardOutWrite: +# def write(self, x): +# old_std.write(x.replace("\n", " [[%s]]\n" % str(datetime.datetime.now()))) diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/main.py" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/main.py" new file mode 100644 index 00000000..05f9127d --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/main.py" @@ -0,0 +1,749 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +""" +@Project:Factory_test +@File:serial_handler.py +@Author:rivern.yuan +@Date:2022/9/15 21:09 +""" + +import sys +import os +import time +import wx +import locale +import gettext +import asyncio +import datetime +import subprocess +import threading +import file_handler +import serial_list +import serial_handler +import configparser +from log_redirect import RedirectErr, RedirectStd +from pubsub import pub +from queue import Queue +import codecs +import json + +if hasattr(sys, '_MEIPASS'): + PROJECT_ABSOLUTE_PATH = os.path.dirname(os.path.realpath(sys.executable)) +else: + PROJECT_ABSOLUTE_PATH = os.path.dirname(os.path.abspath(__file__)) + + +INTERACTIVE_TEST_TIMEOUT = 20 # flag==2:确认开始后再计时,超时无串口回显则失败 + +TEST_MODE_AUTO = 0 # 自动开始,自动判定结果 +TEST_MODE_MANUAL = 1 # 弹窗确认开始,人工判定结果 +TEST_MODE_SEMI_AUTO = 2 # 弹窗确认开始,自动判定结果(开始后计时) + + +def _get_test_mode(test_function): + """配置 [名称, 函数, ?, flag],flag 在 testFunctions 最后一列。""" + if not test_function: + return TEST_MODE_AUTO + if len(test_function) >= 3: + return int(test_function[2]) + if len(test_function) >= 2: + return int(test_function[1]) + return TEST_MODE_AUTO + + +def _test_method_label(mode): + if mode == TEST_MODE_MANUAL: + return _(u"人工检测") + return _(u"自动检测") + + +def _build_test_cmd(test_function): + """JSON 中函数名可能已带 (),避免拼出 fw_version()()。""" + func = test_function[0].strip() + if func.endswith("()"): + func = func[:-2] + return "TestBase." + func + "()" + + +def _parse_test_result(test_result, cmd): + lines = [ln.strip() for ln in test_result.replace("\r\n", "\n").split("\n") if ln.strip()] + for line in reversed(lines): + if line in ("True", "False"): + return line == "True" + if line == cmd: + return True + if "Traceback" in test_result or "TypeError" in test_result: + return False + return False + + +def _repl_finished(test_result): + return test_result.endswith(">>> ") + + +def _is_restart_test(test_function): + """会触发模组重启、串口不会再返回 >>> 的测项。""" + func = test_function[0].strip().lower() + return "det_changedir_end" in func or "powerrestart" in func + + +def _system_language_tag(): + """检测系统语言,供 gettext 选择 zh_CN / en(不用已废弃的 getdefaultlocale)。""" + cats = [] + if hasattr(locale, 'LC_MESSAGES'): + cats.append(locale.LC_MESSAGES) + cats.extend((locale.LC_ALL, locale.LC_CTYPE, None)) + for cat in cats: + try: + loc, _enc = locale.getlocale(cat) if cat is not None else locale.getlocale() + except Exception: + loc = None + if loc: + s = loc.lower().replace('-', '_') + if s.startswith('zh') or 'chinese' in s: + return 'zh_CN' + if s.startswith('en') or 'english' in s: + return 'en' + break + lang = os.environ.get('LANG', '') or os.environ.get('LC_ALL', '') + if lang.lower().startswith('zh'): + return 'zh_CN' + return 'en' + + +def set_config(PROJECT_ABSOLUTE_PATH, frame): + if not os.path.exists(PROJECT_ABSOLUTE_PATH+"\\config.ini"): + return + conf = configparser.ConfigParser(interpolation=None) + conf.read(PROJECT_ABSOLUTE_PATH+"\\config.ini", encoding='utf-8') + py_file = conf.get("software", "py_file") + json_file = conf.get("software", "json_file") + + if py_file or json_file: + no_exist = [] + msg = wx.MessageBox(_(u'是否恢复上次文件'), _(u'提示'), wx.YES_NO) + if msg == wx.YES: + if py_file: + if os.path.exists(py_file): + frame.text_ctrl_py.SetValue(py_file) + else: + conf.set("software", "py_file", "") + wx.MessageBox(py_file + _(u"文件不存在"), _(u"提示"), wx.YES_DEFAULT | wx.ICON_INFORMATION) + + if json_file: + if os.path.exists(py_file): + frame.text_ctrl_json.SetValue(json_file) + frame.load_json_file(0, json_file) + else: + conf.set("software", "json_file", "") + wx.MessageBox(json_file + _(u"文件不存在"), _(u"提示"), wx.YES_DEFAULT | wx.ICON_INFORMATION) + + with open(PROJECT_ABSOLUTE_PATH + "\\config.ini", "w+", encoding='utf-8') as f: + conf.write(f) + + elif msg == wx.NO: + conf.set("software", "py_file", "") + conf.set("software", "json_file", "") + with open(PROJECT_ABSOLUTE_PATH + "\\config.ini", "w+", encoding='utf-8') as f: + conf.write(f) + + +class FactoryFrame(wx.Frame): + def __init__(self, *args, **kwargs): + kwargs["style"] = kwargs.get("style", 0) | wx.DEFAULT_FRAME_STYLE + wx.Frame.__init__(self, *args, **kwargs) + self.SetMinSize((1000, 820)) + self.SetSize((1120, 900)) + self.Center() + # stdout log + sys.stderr = RedirectErr(self, PROJECT_ABSOLUTE_PATH) + sys.stdout = RedirectStd(self, PROJECT_ABSOLUTE_PATH) + # module log + self.logger = ModuleLog() + self.testTips = {} + # Menu Bar + self.menuBar = wx.MenuBar() + wxg_tmp_menu = wx.Menu() + # wxg_tmp_menu.Append(1001, u"Auto detect Port", "") + # self.Bind(wx.EVT_MENU, self.__menu_handler, id=1001) + # wxg_tmp_menu.AppendSeparator() + wxg_tmp_menu.Append(1002, _(u"编辑Py文件"), "") + self.Bind(wx.EVT_MENU, self.__menu_handler, id=1002) + wxg_tmp_menu.Append(1003, _(u"编辑Excel文件"), "") + self.Bind(wx.EVT_MENU, self.__menu_handler, id=1003) + self.menuBar.Append(wxg_tmp_menu, _(u"编辑")) + wxg_tmp_menu_1 = wx.Menu() + wxg_tmp_menu_1.Append(2001, _(u"工具日志"), "") + self.Bind(wx.EVT_MENU, self.__menu_handler, id=2001) + wxg_tmp_menu_1.AppendSeparator() + wxg_tmp_menu_1.Append(2002, _(u"模块日志"), "") + self.Bind(wx.EVT_MENU, self.__menu_handler, id=2002) + self.menuBar.Append(wxg_tmp_menu_1, _(u"日志")) + self.SetMenuBar(self.menuBar) + # Menu Bar end + # self.statusBar = self.CreateStatusBar(3) + self.main_panel = wx.Panel(self, wx.ID_ANY) + self.load_py = wx.Button(self.main_panel, wx.ID_ANY, _("选择")) + self.text_ctrl_py = wx.TextCtrl(self.main_panel, wx.ID_ANY, "", style=wx.TE_READONLY) + self.load_json = wx.Button(self.main_panel, wx.ID_ANY, _("选择")) + self.text_ctrl_json = wx.TextCtrl(self.main_panel, wx.ID_ANY, "", style=wx.TE_READONLY) + + self.button_all_start = wx.Button(self.main_panel, wx.ID_ANY, _("全部开始")) + + self.button_start_list = [] + self.ListCtrl_list = [] + self.log_ctrl_list = [] + self.port_ctrl_list = [] + self.port_imei_list = [] + self.port_iccid_list = [] + for i in range(4): + ListCtrl = wx.ListCtrl(self.main_panel, 200+i, style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES) + ListCtrl.SetMinSize((-1, 300)) + ListCtrl.AppendColumn(_(u"测项"), format=wx.LIST_FORMAT_LEFT, width=130) + ListCtrl.AppendColumn(_(u"测试方式"), format=wx.LIST_FORMAT_LEFT, width=70) + ListCtrl.AppendColumn(_(u"测试结果"), format=wx.LIST_FORMAT_LEFT, width=70) + + log_ctrl = wx.TextCtrl( + self.main_panel, 400+i, "", + style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_WORDWRAP) + log_ctrl.SetMinSize((-1, 88)) + log_ctrl.SetForegroundColour(wx.Colour(220, 0, 0)) + + button = wx.Button(self.main_panel, 100+i, _("开始")) + text_ctrl = wx.TextCtrl(self.main_panel, 300+i, "", style=wx.TE_READONLY) + + self.ListCtrl_list.append(ListCtrl) + self.log_ctrl_list.append(log_ctrl) + self.button_start_list.append(button) + self.port_ctrl_list.append(text_ctrl) + # self.port_imei_list.append(text_ctrl_imei) + # self.port_iccid_list.append(text_ctrl_iccid) + + self.__exec_py_cmd_list = ["from usr.test import TestBase"] + self.__init_excel() + + # event message queue + self.message_queue = Queue(maxsize=10) + self._channel_list = [] + threading.Thread(target=self.port_process_handler, args=()).start() + + # bind Func + self.Bind(wx.EVT_BUTTON, self.__load_py_file, self.load_py) + self.Bind(wx.EVT_BUTTON, self.load_json_file, self.load_json) + self.Bind(wx.EVT_BUTTON, self.test_all_start, self.button_all_start) + + + self.procese_num = 0 + + for i in self.button_start_list: + self.Bind(wx.EVT_BUTTON, self.test_start, i) + self.Bind(wx.EVT_CLOSE, self.close_window) + + # pub message + pub.subscribe(self.port_update, "serialUpdate") + + self.conf = configparser.ConfigParser(interpolation=None) + if not os.path.exists(PROJECT_ABSOLUTE_PATH+"\\config.ini"): + self.initConfigFile() + self.conf.read(PROJECT_ABSOLUTE_PATH+"\\config.ini", encoding='utf-8') + + self.__set_properties() + self.__do_layout() + + def __set_properties(self): + for i in range(4): + self.button_start_list[i].SetMinSize((150, 50)) + self.button_all_start.SetMinSize((150, 50)) + + def __do_layout(self): + """垂直:文件选择(固定高度) -> 工厂测试区(随窗口拉伸) -> 全部开始(底栏固定)。""" + root = wx.BoxSizer(wx.VERTICAL) + main = wx.BoxSizer(wx.VERTICAL) + + sizer_3 = wx.StaticBoxSizer( + wx.StaticBox(self.main_panel, wx.ID_ANY, _("选择测试脚本")), wx.HORIZONTAL) + sizer_3.Add(self.load_py, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 8) + sizer_3.Add(self.text_ctrl_py, 1, wx.EXPAND | wx.ALL, 8) + + sizer_4 = wx.StaticBoxSizer( + wx.StaticBox(self.main_panel, wx.ID_ANY, _("选择配置文件")), wx.HORIZONTAL) + sizer_4.Add(self.load_json, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 8) + sizer_4.Add(self.text_ctrl_json, 1, wx.EXPAND | wx.ALL, 8) + + main.Add(sizer_3, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 6) + main.Add(sizer_4, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 6) + + sizer_5 = wx.StaticBoxSizer( + wx.StaticBox(self.main_panel, wx.ID_ANY, _("工厂测试")), wx.HORIZONTAL) + for i in range(4): + col = wx.BoxSizer(wx.VERTICAL) + col.Add(self.port_ctrl_list[i], 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 6) + # 测项表占主要拉伸比例,日志与按钮在其下方 + col.Add(self.ListCtrl_list[i], 5, wx.EXPAND | wx.ALL, 4) + col.Add(self.log_ctrl_list[i], 2, wx.EXPAND | wx.LEFT | wx.RIGHT, 4) + col.Add(self.button_start_list[i], 0, + wx.ALIGN_CENTER_HORIZONTAL | wx.TOP | wx.BOTTOM, 8) + sizer_5.Add(col, 1, wx.EXPAND | wx.ALL, 4) + + main.Add(sizer_5, 1, wx.EXPAND | wx.ALL, 6) + + btn_row = wx.BoxSizer(wx.HORIZONTAL) + btn_row.AddStretchSpacer(1) + btn_row.Add(self.button_all_start, 0, wx.ALIGN_CENTER_VERTICAL) + btn_row.AddStretchSpacer(1) + main.Add(btn_row, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 12) + + self.main_panel.SetSizer(main) + root.Add(self.main_panel, 1, wx.EXPAND) + self.SetSizer(root) + self.Layout() + + def __menu_handler(self, event): # wxGlade: FactoryFrame. + if event.GetId() == 1001: + DialogControl(self, "demo").Show() + + elif event.GetId() == 1002: + file_path, app_path = "notepad.exe", PROJECT_ABSOLUTE_PATH + "\\module_test.py" + raw_file_path = r'%s' % file_path + raw_app_path = r'%s' % app_path + subprocess.call([raw_file_path, raw_app_path]) + elif event.GetId() == 1003: + excel_file = subprocess.Popen(["start", "/WAIT", PROJECT_ABSOLUTE_PATH + "\\Test-Result.xlsx"], shell=True) + # psutil.Process(excel_file.pid).get_children()[0].kill() + excel_file.poll() + elif event.GetId() == 2001: + p = subprocess.Popen("explorer.exe " + PROJECT_ABSOLUTE_PATH + "\\logs\\software\\", shell=True) + elif event.GetId() == 2002: + p = subprocess.Popen("explorer.exe " + PROJECT_ABSOLUTE_PATH + "\\logs\\apps\\", shell=True) + event.Skip() + + def initConfigFile(self): + self.conf.add_section("software") + self.conf.set("software", "py_file", "") + self.conf.set("software", "json_file", "") + with open(PROJECT_ABSOLUTE_PATH + "\\config.ini", "w+", encoding='utf-8') as f: + self.conf.write(f) + + def __load_py_file(self, event): + defDir, defFile = '', '' # default dir/ default file + dlg = wx.FileDialog(self, u'Open Py File', defDir, defFile, 'Python file (*.py)|*.py', + wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) + if dlg.ShowModal() == wx.ID_CANCEL: + return + py_cmd = file_handler.ScriptHandler(dlg.GetPath()) + if py_cmd.script_check(): + self.text_ctrl_py.SetValue(dlg.GetPath()) + self.conf.set("software", "py_file", dlg.GetPath()) + with open(PROJECT_ABSOLUTE_PATH + "\\config.ini", "w+", encoding='utf-8') as f: + self.conf.write(f) + else: + wx.MessageBox(_(u'py代码有语法错误'), u'Warn', wx.YES_DEFAULT | wx.ICON_INFORMATION) + + def load_json_file(self, event, json_file=None): + print("json button is press") + if event != 0: + defDir, defFile = '', '' # default dir/ default file + dlg = wx.FileDialog(self, u'Open Config File', defDir, defFile, 'Config file (*.json)|*.json', + wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) + if dlg.ShowModal() == wx.ID_CANCEL: + return + json_file = dlg.GetPath() + try: + with codecs.open(json_file, 'r', 'utf-8') as f: + data = json.load(f) + print(data) + info = data["info"] + self.testFunctions = [i[1:] for i in info] + self.testMessages = [i[0] for i in info] + self.testTips = data.get("tips", {}) + self.text_ctrl_json.SetValue(json_file) + self.conf.set("software", "json_file", json_file) + with open(PROJECT_ABSOLUTE_PATH + "\\config.ini", "w+", encoding='utf-8') as f: + self.conf.write(f) + + for i, port_ctrl in enumerate(self.port_ctrl_list): + if port_ctrl.GetValue(): + self.ListCtrl_list[i].DeleteAllItems() + for j, element in enumerate(self.testFunctions): + ListCtrl = self.ListCtrl_list[i] + # testFunction = self.testFunctions[j] + ListCtrl.InsertItem(j, j) + ListCtrl.SetItem(j, 0, self.testMessages[j]) + ListCtrl.SetItem(j, 1, _test_method_label(_get_test_mode(element))) + except Exception as e: + print(e) + wx.MessageBox(_(u'请选择正确的配置文件'), u'提示', wx.YES_DEFAULT | wx.ICON_INFORMATION) + return + + def test_start(self, event): + button_event_id = event.GetId() - 100 # button ID 100开始 + + if self.text_ctrl_json.GetValue(): + pass + else: + wx.MessageBox(_(u'请先选择配置文件'), u'提示', wx.YES_DEFAULT | wx.ICON_INFORMATION) + return + + if self.text_ctrl_py.GetValue(): + if self.port_ctrl_list[button_event_id].GetValue(): + # 点击开始时再初始化 + self.__port_det(True) # 检测停止 + + self.procese_num += 1 + self.button_all_start.Enable(False) + self.button_start_list[button_event_id].Enable(False) + self.message_queue.put( + {"id": button_event_id, "msg_id": "PortInit", "PortInfo": self.port_ctrl_list[button_event_id].GetValue()}) + else: + return None + else: + wx.MessageBox(_(u'请先选择测试文件'), u'提示', wx.YES_DEFAULT | wx.ICON_INFORMATION) + + def test_all_start(self, event): + self.__port_det(True) # 检测停止 + + if self.text_ctrl_json.GetValue(): + pass + else: + wx.MessageBox(_(u'请先选择配置文件'), u'提示', wx.YES_DEFAULT | wx.ICON_INFORMATION) + return + + if self.text_ctrl_py.GetValue(): + pass + else: + wx.MessageBox(_(u'请先选择测试文件'), u'提示', wx.YES_DEFAULT | wx.ICON_INFORMATION) + return None + + for i in self.port_ctrl_list: + button_event_id = i.GetId() - 300 + if i.GetValue(): + self.procese_num += 1 + self.button_all_start.Enable(False) + self.button_start_list[button_event_id].Enable(False) + self.message_queue.put( + {"id": button_event_id, "msg_id": "PortInit", + "PortInfo": self.port_ctrl_list[button_event_id].GetValue()}) + else: + return None + + def port_update(self, arg1): + print("message:{}".format(arg1)) + self.message_queue.put({"msg_id": "PortUpdate", "PortInfo": arg1}) + + def port_update_handler(self, arg1): + # TODO 设置log按钮状态 + port_list = arg1["PortInfo"][:4] if len(arg1["PortInfo"]) > 4 else arg1["PortInfo"] + if len(port_list) < 4: + self.button_all_start.Enable(False) if len(port_list) == 0 else self.button_all_start.Enable(True) + port_list = port_list + [""] * (4 - len(port_list)) + if self._channel_list == [] or self._channel_list != port_list: + self._channel_list = port_list + for port_ctrl in self.port_ctrl_list: + port_ctrl.SetValue("") + for ListCtrl in self.ListCtrl_list: + ListCtrl.DeleteAllItems() + + for i, element in enumerate(self.button_start_list): + if self._channel_list[i]: + self.port_ctrl_list[i].SetValue(self._channel_list[i]) + self.button_start_list[i].Enable(True) + + if self.text_ctrl_json.GetValue(): + for j, element in enumerate(self.testFunctions): + ListCtrl = self.ListCtrl_list[i] + # testFunction = self.testFunctions[j] + ListCtrl.InsertItem(j, j) + ListCtrl.SetItem(j, 0, self.testMessages[j]) + ListCtrl.SetItem(j, 1, _test_method_label(_get_test_mode(element))) + else: + self.button_start_list[i].Enable(False) + + def _set_channel_tip(self, channel_id, tip, countdown=None): + """在通道日志区显示当前测项 tip(红色)。""" + lines = [] + if countdown is not None: + lines.append(_(u"倒计时: ") + str(countdown)) + if tip: + lines.append(tip) + self.log_ctrl_list[channel_id].SetValue("\n".join(lines)) + + def _clear_channel_tip(self, channel_id): + self.log_ctrl_list[channel_id].SetValue("") + + def _confirm_start_test(self, port_title, tip): + """弹窗确认开始测试,仅一个确认键。""" + msg = tip if tip else _(u"请点击确认开始测试") + wx.MessageBox(msg, port_title, wx.OK | wx.ICON_INFORMATION) + + def _confirm_manual_result(self, port_title, message): + """人工判定测试结果。""" + prompt = _(u" 当前测试项为: ") + message + _(u"\r\n\r\n 请确认该测试项 【是否通过】") + return wx.MessageBox(prompt, port_title, wx.YES_NO | wx.ICON_QUESTION) == wx.YES + + def _ret_result_countdown(self, ser, channel_id, tip, timeout_sec): + """等待串口回显,mode==2 时在日志区刷新倒计时。""" + data = "" + deadline = time.time() + timeout_sec + while time.time() < deadline: + remaining = max(0, int(deadline - time.time()) + 1) + self._set_channel_tip(channel_id, tip, countdown=remaining) + waiting = ser._conn.inWaiting() + if waiting: + data += ser._conn.read(waiting).decode("utf-8", errors="ignore") + if data.endswith(">>> "): + break + time.sleep(0.1) + return data + + def _apply_test_row_result(self, channel_id, row, passed): + if passed: + self.ListCtrl_list[channel_id].SetItem(row, 2, _(u"通过")) + self.ListCtrl_list[channel_id].SetItemBackgroundColour(row, "Green") + else: + self.ListCtrl_list[channel_id].SetItem(row, 2, _(u"不通过")) + self.ListCtrl_list[channel_id].SetItemBackgroundColour(row, "Red") + + def port_init_handler(self, arg1): + ID = arg1["id"] + self._clear_channel_tip(ID) + for i in range(len(self.testFunctions)): + ListCtrl = self.ListCtrl_list[ID] + ListCtrl.SetItem(i, 2, "") + ListCtrl.SetItemBackgroundColour(i, (255, 255, 255, 255)) + + # 重复测试结果重置 + # ser = serial_handler.SerialHandler(arg1["PortInfo"]) + # imei, iccid = asyncio.run(ser.init_module(arg1["imei"], arg1["iccid"])) + self.__py_test_fp = open(self.text_ctrl_py.GetValue(), "r", encoding="utf8") + self.message_queue.put( + {"id": arg1["id"], "msg_id": "PortTest", "PortInfo": self.port_ctrl_list[arg1["id"]].GetValue(), + "script": self.__py_test_fp}) + + def port_test_handler(self, arg1): + print("start test") + ID = arg1["id"] + try: + ser = serial_handler.SerialHandler(arg1["PortInfo"]) + ser.write_module(arg1["script"], self.__exec_py_cmd_list) # 写入脚本开始测试 + ser.ret_result() + except Exception as e: + print(arg1["PortInfo"], e) + wx.MessageBox(arg1["PortInfo"]+"串口异常,测试脚本写入失败, error %s"%str(e), u'Error', wx.YES_DEFAULT | wx.ICON_INFORMATION) + self.message_queue.put({"id": arg1["id"], "msg_id": "PortTestEnd", "result": 1}) + self.button_start_list[arg1["id"]].Enable(True) + self.procese_num -= 1 + if self.procese_num == 0: + self.button_all_start.Enable(True) + return + + ret_result = [] + port_title = self.port_ctrl_list[ID].GetValue() + tips = getattr(self, "testTips", {}) + self._clear_channel_tip(ID) + for i, testFunction in enumerate(self.testFunctions): + passed = False + message = self.testMessages[i] + mode = _get_test_mode(testFunction) + tip = tips.get(message, "") + try: + cmd = _build_test_cmd(testFunction) + self._set_channel_tip(ID, tip) + self.ListCtrl_list[ID].SetItem(i, 2, _(u"测试中")) + self.ListCtrl_list[ID].SetItemBackgroundColour(i, "Yellow") + + if mode in (TEST_MODE_MANUAL, TEST_MODE_SEMI_AUTO): + self._confirm_start_test(port_title, tip) + + if _is_restart_test(testFunction): + # 重启类测项:指令发出即完成,不能等待 >>> + ser.exec_cmd(cmd) + time.sleep(0.8) + try: + pending = ser._conn.inWaiting() + if pending: + ser._conn.read(pending) + except Exception: + pass + test_result = cmd + "\r\n(restart sent, no repl)\r\n" + self.logger.write_file(arg1["PortInfo"], test_result) + passed = True + elif mode == TEST_MODE_SEMI_AUTO: + ser.exec_cmd(cmd) + test_result = self._ret_result_countdown( + ser, ID, tip, INTERACTIVE_TEST_TIMEOUT) + self.logger.write_file(arg1["PortInfo"], test_result) + passed = _repl_finished(test_result) and _parse_test_result(test_result, cmd) + else: + ser.exec_cmd(cmd) + test_result = ser.ret_result() + self.logger.write_file(arg1["PortInfo"], test_result) + passed = _parse_test_result(test_result, cmd) + if mode == TEST_MODE_MANUAL: + passed = self._confirm_manual_result(port_title, message) + + except Exception as e: + print(e) + passed = False + + self._apply_test_row_result(ID, i, passed) + ret_result.append("True" if passed else "False") + + test_results = [] + for i in ret_result: + if i == "True": + test_results.append(_(u"通过")) + elif i == "False": + test_results.append(_(u"不通过")) + else: + test_results.append(i) + + test_method = [_test_method_label(_get_test_mode(i)) for i in self.testFunctions] + + # TODO excel写入不要列表形式 + if "False" not in ret_result: + self.message_queue.put({"id": arg1["id"], "msg_id": "PortTestEnd", "result": 1}) + self.__excel_write([arg1["PortInfo"], "Success", str(list(zip(self.testMessages, ret_result)))]) + else: + self.message_queue.put({"id": arg1["id"], "msg_id": "PortTestEnd", "result": 2}) + self.__excel_write([arg1["PortInfo"], "Fail", str(list(zip(self.testMessages, ret_result)))]) + self._clear_channel_tip(ID) + self.button_start_list[arg1["id"]].Enable(True) + self.procese_num -= 1 + if self.procese_num == 0: + self.button_all_start.Enable(True) + + def port_test_end_handler(self, arg1): + self.__port_det(False) + + def __init_excel(self): + self.__excel_handler = file_handler.ExcelHandler(PROJECT_ABSOLUTE_PATH + "\\Test-Result.xlsx") # Init Excel + rows, columns = self.__excel_handler.get_rows_columns() + if rows == 1 and columns == 1: + self.__excel_handler.set_cell_value(1, 1, "No.") + self.__excel_handler.set_cell_value(1, 2, "Com Port") + self.__excel_handler.set_cell_value(1, 3, "Test Result") + self.__excel_handler.set_cell_value(1, 4, "Test Log") + + def __excel_write(self, result): + if self.__excel_handler: + rows, columns = self.__excel_handler.get_rows_columns() + self.__excel_handler.set_cell_value(rows + 1, 1, rows) + for i, value in enumerate(result): + self.__excel_handler.set_cell_value(rows + 1, i + 2, value) + + def port_process_handler(self): + while True: + try: + message = self.message_queue.get(True, 5) + except Exception as e: + message = None + try: + if message: + # print("message:{}".format(message)) + msg_id = message.get("msg_id") + if msg_id == "exit": + pass + elif msg_id == "PortUpdate": + # threading.Thread(target=self.port_update_handler, args=(message,)).start() + self.port_update_handler(message) + elif msg_id == "PortInit": + # threading.Thread(target=self.port_init_handler, args=(message,)).start() + self.port_init_handler(message) + elif msg_id == "PortTest": + threading.Thread(target=self.port_test_handler, args=(message,)).start() + # self.port_test_handler(message) + elif msg_id == "PortTestEnd": + threading.Thread(target=self.port_test_end_handler, args=(message,)).start() + # self.port_test_end_handler(message) + else: + pass + except Exception as e: + print(e) + + @staticmethod + def __port_det(flag): + if flag: + tSerialDet.exit_event() + else: + tSerialDet.enter_event() + + # end of class FactoryFrame + def close_window(self, event): + self.Destroy() + try: + self.__excel_handler.close() + except Exception as e: + pass + time.sleep(0.2) + wx.GetApp().ExitMainLoop() + wx.Exit() + process_name = "taskkill /F /IM " + file_name + p = subprocess.Popen(process_name, shell=True) + + +class MyApp(wx.App): + languageTab = _system_language_tag() + print("languageTab: ", languageTab) + # languageTab = "en" + # 根据系统语言自动设置语言 + if languageTab == "zh_CN": + t = gettext.translation('Chinese', PROJECT_ABSOLUTE_PATH + "\\locale", languages=["zh_CN"]) + t.install() + elif languageTab == "en": + t = gettext.translation('English', PROJECT_ABSOLUTE_PATH + "\\locale", languages=["en"]) + t.install() + else: + languageTab = "en" + t = gettext.translation('English', PROJECT_ABSOLUTE_PATH + "\\locale", languages=["en"]) + t.install() + + def __init__(self): + # print("init-----") + wx.App.__init__(self, redirect=False, filename="", useBestVisual=True, clearSigInt=True) + # wx.App.__init__(self, redirect=False, filename=PROJECT_ABSOLUTE_PATH + "\\logs\\software\\run.log", + # useBestVisual=True, clearSigInt=True) + # print("init finish----") + + def OnInit(self): + self.factory_frame = FactoryFrame(None, wx.ID_ANY, "") + self.SetTopWindow(self.factory_frame) + self.factory_frame.Show() + + # 串口检测线程 + global tSerialDet + tSerialDet = serial_list.SerialDetection() + tSerialDet.setDaemon(True) # set as deamon, stop thread while main frame exit + tSerialDet.start() + + set_config(PROJECT_ABSOLUTE_PATH, self.factory_frame) + return True + + +class ModuleLog(object): + """log output file""" + def __init__(self): + if not os.path.exists(PROJECT_ABSOLUTE_PATH + "\\logs\\apps\\"): + os.makedirs(PROJECT_ABSOLUTE_PATH + "\\logs\\apps\\") + for i in os.walk(PROJECT_ABSOLUTE_PATH + "\\logs\\apps\\"): + self._log_file_list = i[2] + if len(self._log_file_list) >= 50: + os.remove(PROJECT_ABSOLUTE_PATH + "\\logs\\apps\\" + self._log_file_list[0]) + self._log_name = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + ".log" + + def write_file(self, port, data): + try: + tmp_data = "[" + port + "]\n" + data + "\n" + with open(PROJECT_ABSOLUTE_PATH + "\\logs\\apps\\" + self._log_name, "a+", encoding="utf-8")as f: + f.write(tmp_data) + return True + except: + info = sys.exc_info() + print("write file error.") + print(info[0], info[1]) + return False + + +if __name__ == "__main__": + file_name = os.path.basename(sys.executable) + app = MyApp() + app.MainLoop() \ No newline at end of file diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/media/quectel.ico" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/media/quectel.ico" new file mode 100644 index 00000000..a9b82f9f Binary files /dev/null and "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/media/quectel.ico" differ diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/module_test.py" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/module_test.py" new file mode 100644 index 00000000..cc45f299 --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/module_test.py" @@ -0,0 +1,28 @@ +# 这里写需要导入的模块 +import sim +import net +import uos +import utime + +class TestBase(object): + # ------该区域为测试代码------ + @staticmethod + def det_signal(): + utime.sleep(2) + if sim.getStatus() == 1: + if net.getConfig()[0] == 5: + return True + else: + return False + else: + return False + + @staticmethod + def det_file_space(): + utime.sleep(2) + if uos.statvfs('usr')[3] > 5: + return True + else: + return False + # ------该区域为测试代码------ + diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/requirements.txt" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/requirements.txt" new file mode 100644 index 00000000..6208bef2 --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/requirements.txt" @@ -0,0 +1,5 @@ +Pypubsub==4.0.3 +wxPython>=4.2.0 +openpyxl==3.0.7 +pyserial==3.5 + diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/serial_handler.py" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/serial_handler.py" new file mode 100644 index 00000000..0c1fbe8d --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/serial_handler.py" @@ -0,0 +1,144 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +""" +@Project:Factory_test +@File:serial_handler.py +@Author:rivern.yuan +@Date:2022/9/6 15:03 +""" +import serial +import time +import asyncio + + +class SerialPort(object): + ''' + class for serial operation + ''' + def __init__(self, port, baud): + self._port = port + self._baud = baud + self._conn = None + + def _open_conn(self): + self._conn = serial.Serial(self._port, self._baud) + return self._conn + + def _close_conn(self): + self._conn.close() + self._conn = None + + def _send_cmd(self, cmd): + self._conn.write((cmd + '\r\n').encode()) + + def _recv_cmd(self): + time.sleep(.2) + byte_n = self._conn.inWaiting() + if byte_n > 0: + return self._conn.read(byte_n) + + def _test_conn(self): + self._send_cmd("1") + # recv: b'1\r\n1\r\n>>> ' 测试正常交互 + test_recv = self._recv_cmd() + if test_recv: + if len(test_recv) == 10: + return True + else: + print(test_recv) + raise SerialError(self._port, "串口持续输出中,请检查运行状态") + else: + raise SerialError(self._port, "串口堵塞,请检查串口连通性") + + +class SerialHandler(SerialPort): + """write test script & read test result """ + def __init__(self, port, baud=115200): + super(SerialHandler, self).__init__(port, baud) + self.init() + + def write_module(self, source, py_cmd, filename="test.py"): + self._test_conn() + + self._conn.write(b"\x01") + self._send_cmd("f=open('/usr/" + filename + "','wb')") # 写入文件 + self._send_cmd("w=f.write") + while True: + time.sleep(.1) + data = source.read(255) + if not data: + break + else: + self._send_cmd("w(" + repr(data) + ")") + self._conn.write(b"\x04") + self._send_cmd("f.close()") + self._conn.write(b"\x04") + self._conn.write(b"\x02") + self._conn.write(b"\x04") + + source.close() + self.exec_cmd(py_cmd[0]) + return True + + def exec_py(self, cmd): + self._send_cmd(cmd[0]) + self._conn.flushInput() + self._send_cmd(cmd[1]) + time.sleep(1) + return self.ret_result() + + def exec_cmd(self, cmd): + self._send_cmd(cmd) + + async def run_cmd(self,source:list, log_text_ctrl): + result_list = "" + for i in source: + self._send_cmd(i) + await asyncio.sleep(.1) + result_list += self.ret_result() + log_text_ctrl.SetValue(result_list) + return result_list + + def ret_result(self, timeout_sec=None): + data = "" + if timeout_sec is None: + # 单条自动测项最长约 15s,避免异常时卡住 30s + for i in range(30): + data += self._conn.read(self._conn.inWaiting()).decode("utf-8", errors="ignore") + if data.endswith(">>> "): + break + time.sleep(0.5) + else: + deadline = time.time() + timeout_sec + while time.time() < deadline: + waiting = self._conn.inWaiting() + if waiting: + data += self._conn.read(waiting).decode("utf-8", errors="ignore") + if data.endswith(">>> "): + break + time.sleep(0.1) + return data + + def exit_test(self): + self._close_conn() + + def init(self): + self._open_conn() + + +class SerialError(Exception): + ''' + exception for serial blocking connection + ''' + def __init__(self, _port, _error): + self._port = _port + self._error = _error + + def __str__(self): + return self._port + " " + self._error + + +if __name__ == '__main__': + ser = SerialHandler("COM63") + + diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/serial_list.py" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/serial_list.py" new file mode 100644 index 00000000..18fa3357 --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/serial_list.py" @@ -0,0 +1,80 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +""" +@Project:Factory_test +@File:serial_list.py +@Author:rivern.yuan +@Date:2022/9/6 15:59 +""" + +import time +import threading +import serial.tools.list_ports +from pubsub import pub + + +class SerialDetection(threading.Thread): + """ + class for detection port list + """ + def __init__(self): + super(SerialDetection, self).__init__() + self.serPort = serial.tools.list_ports + self._exit = False + + def exit_event(self): + self._exit = True + + def enter_event(self): + self._exit = False + + def get_com_number(self, vid_pid, location): + port_list = [] + vid = int(vid_pid.split(":")[0], 16) + pid = int(vid_pid.split(":")[1], 16) + for p in list(self.serPort.comports()): + if p.vid == vid and p.pid == pid and p.location and location in p.location: + port_list.append(p.device) + return port_list + + def get_repl_ports(self): + """按 Quectel REPL 口描述识别产测串口(兼容不同 USB 接口编号)。""" + port_list = [] + for p in list(self.serPort.comports()): + if p.vid == 0x2C7C and p.description and "REPL" in p.description: + port_list.append(p.device) + return port_list + + def scan_test_ports(self): + """汇总产测可用 REPL 串口,兼容旧版与 EG810 等新模组 USB 布局。""" + serial_port = self.get_repl_ports() + if serial_port: + return serial_port + + serial_port = self.get_com_number("0x2C7C:0x0901", "x.8") + serial_port.extend(self.get_com_number("0x2C7C:0x6002", "x.5")) + serial_port.extend(self.get_com_number("0x2C7C:0x6002", "x.20")) # EG810 等 REPL + serial_port.extend(self.get_com_number("0x2C7C:0x6005", "x.5")) + serial_port.extend(self.get_com_number("0x2C7C:0x6005", "x.8")) + return list(dict.fromkeys(serial_port)) + + def run(self): + serial_list = [] + last_ports = [] + while True: + if self._exit: + pass + else: + if serial_list == [] or serial_list != self.serPort.comports(): + serial_list = self.serPort.comports() + serial_port = self.scan_test_ports() + if serial_port != last_ports: + last_ports = serial_port + pub.sendMessage('serialUpdate', arg1=serial_port) + time.sleep(1) + + +if __name__ == '__main__': + ser = SerialDetection() + ser.setDaemon(False) + ser.start() diff --git "a/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/sort_setting.json" "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/sort_setting.json" new file mode 100644 index 00000000..f4b6e975 --- /dev/null +++ "b/\344\272\247\346\265\213\345\267\245\345\205\267/FactoryTool-2.1/sort_setting.json" @@ -0,0 +1,4 @@ +{ + "info": [["Memory test", "det_file_space", 1], + ["Signal test", "det_signal", 0]] +} \ No newline at end of file