Files
LaserTracing_ConfigureAPP/generate_manual.py
T
YuanHongbin 31612e5b95 docs: 说明书指令参考精简为exe界面涉及指令, 修复表格长文本截断
- 指令参考仅保留界面按钮对应的para与lora fc, 其余指令改为在线帮助提示
- 删除lpcfg/laser/sig等扩展操作指南章节, 恢复原有章节结构
- 表格单元格改为Paragraph自动换行, 修复超宽内容被截断的问题
- 补充界面细节: 串口参数固定8/N/1, 清空日志按钮, 升级自动切9600与超时保护
2026-09-24 11:47:00 +08:00

448 lines
21 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
# 生成配置工具使用说明书(PDF格式)
# 用法: python generate_manual.py narrow -> 生成窄带版说明书
# python generate_manual.py wide -> 生成宽带版说明书
import os
import sys
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm, mm
from reportlab.lib.colors import HexColor, black, white
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
PageBreak
)
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
# ── 版本与型号配置 ──
SOFTWARE_VERSION = "V1.0.0"
arg = sys.argv[1].lower() if len(sys.argv) > 1 else "narrow"
if arg in ("wide", "broadband", "宽带"):
EDITION = "宽带版"
FC_RANGE = "433.1MHz ~ 492.6MHz"
FC_CHANNELS = 120
FC_EXAMPLE = "433100000"
else:
EDITION = "窄带版"
FC_RANGE = "470.1MHz ~ 492.6MHz"
FC_CHANNELS = 46
FC_EXAMPLE = "470100000"
FC_STEP = "500kHz"
EXE_NAME = f"边坡变形监测-变形(位移)自动测量系统配置工具({EDITION} {SOFTWARE_VERSION}).exe"
PDF_NAME = f"边坡变形监测-变形(位移)自动测量系统配置工具使用说明书({EDITION} {SOFTWARE_VERSION}).pdf"
# 注册中文字体
font_dir = os.path.join(os.environ.get("WINDIR", r"C:\Windows"), "Fonts")
pdfmetrics.registerFont(TTFont("SimSun", os.path.join(font_dir, "simsun.ttc"), subfontIndex=0))
pdfmetrics.registerFont(TTFont("SimHei", os.path.join(font_dir, "simhei.ttf")))
# 颜色定义
C_PRIMARY = HexColor("#1A3C6E")
C_HEADER_BG = HexColor("#1A3C6E")
C_HEADER_FG = white
C_ROW_ALT = HexColor("#EDF2F9")
C_RED = HexColor("#CC0000")
C_GRAY = HexColor("#555555")
# 样式
styles = getSampleStyleSheet()
style_body = ParagraphStyle("Body", parent=styles["Normal"], fontName="SimSun", fontSize=11, leading=20, spaceAfter=6)
style_h1 = ParagraphStyle("H1", fontName="SimHei", fontSize=18, leading=28, spaceAfter=12, spaceBefore=20, textColor=C_PRIMARY)
style_h2 = ParagraphStyle("H2", fontName="SimHei", fontSize=14, leading=22, spaceAfter=8, spaceBefore=14, textColor=C_PRIMARY)
style_h3 = ParagraphStyle("H3", fontName="SimHei", fontSize=12, leading=18, spaceAfter=6, spaceBefore=10, textColor=C_PRIMARY)
style_bullet = ParagraphStyle("Bullet", parent=style_body, leftIndent=20, bulletIndent=8)
style_cover_title = ParagraphStyle("CoverTitle", fontName="SimHei", fontSize=26, leading=40, alignment=TA_CENTER, textColor=C_PRIMARY)
style_cover_sub = ParagraphStyle("CoverSub", fontName="SimSun", fontSize=14, alignment=TA_CENTER, textColor=C_GRAY)
style_toc = ParagraphStyle("TOC", fontName="SimSun", fontSize=11, leading=18)
style_toc_bold = ParagraphStyle("TOCBold", fontName="SimHei", fontSize=11, leading=18)
style_step = ParagraphStyle("Step", parent=style_body, leftIndent=20)
style_warning = ParagraphStyle("Warning", parent=style_body, textColor=C_RED, fontName="SimHei")
style_q = ParagraphStyle("Q", fontName="SimHei", fontSize=11, leading=20, spaceBefore=10, spaceAfter=2)
style_a = ParagraphStyle("A", fontName="SimSun", fontSize=11, leading=20, leftIndent=20, spaceAfter=6)
style_th = ParagraphStyle("TH", fontName="SimHei", fontSize=11, leading=15, textColor=white)
style_td = ParagraphStyle("TD", fontName="SimSun", fontSize=10, leading=14)
def make_table(headers, rows, col_widths=None):
# 单元格统一用 Paragraph 包装,超宽内容自动换行(字符串单元格会被截断)
from xml.sax.saxutils import escape as _esc
data = [[Paragraph(_esc(str(headers[0])), style_th)] + [Paragraph(_esc(str(c)), style_td) for c in headers[1:]]]
for r in rows:
data.append([Paragraph(_esc(str(c)), style_td) for c in r])
if col_widths is None:
col_widths = [doc.width / len(headers)] * len(headers)
t = Table(data, colWidths=col_widths, repeatRows=1)
style_cmds = [
("BACKGROUND", (0, 0), (-1, 0), C_HEADER_BG),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("GRID", (0, 0), (-1, -1), 0.5, HexColor("#CCCCCC")),
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
("LEFTPADDING", (0, 0), (-1, -1), 6),
]
for i in range(1, len(data)):
if i % 2 == 0:
style_cmds.append(("BACKGROUND", (0, i), (-1, i), C_ROW_ALT))
t.setStyle(TableStyle(style_cmds))
return t
def bullet(text):
return Paragraph("o " + text, style_bullet)
def step(num, text):
return Paragraph(str(num) + ". " + text, style_step)
# 构建文档
output_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), PDF_NAME)
doc = SimpleDocTemplate(output_path, pagesize=A4,
leftMargin=2.5*cm, rightMargin=2.5*cm,
topMargin=2.5*cm, bottomMargin=2.5*cm)
story = []
# 封面
story.append(Spacer(1, 6*cm))
story.append(Paragraph("边坡变形监测-变形(位移)<br/>自动测量系统配置工具(" + EDITION + ")<br/>使用说明书", style_cover_title))
story.append(Spacer(1, 1.5*cm))
story.append(Paragraph("版本 " + SOFTWARE_VERSION, style_cover_sub))
story.append(PageBreak())
# 目录
story.append(Paragraph("目 录", style_h1))
toc_items = [
(True, "1 软件概述"),
(True, "2 运行环境"),
(True, "3 软件安装与启动"),
(False, " 3.1 安装"),
(False, " 3.2 启动"),
(False, " 3.3 USB转串口驱动安装"),
(True, "4 软件界面说明"),
(False, " 4.1 串口连接区域"),
(False, " 4.2 参数配置区域"),
(False, " 4.3 设备参数信息区域"),
(False, " 4.4 低功耗配置区域"),
(False, " 4.5 通讯日志区域"),
(True, "5 操作指南"),
(False, " 5.1 连接设备"),
(False, " 5.2 查询设备参数"),
(False, " 5.3 修改Lora信道频率"),
(False, " 5.4 设备升级"),
(False, " 5.5 发送自定义指令"),
(True, "6 指令参考"),
(True, "7 常见问题与故障排除"),
]
for bold, text in toc_items:
story.append(Paragraph(text, style_toc_bold if bold else style_toc))
story.append(PageBreak())
# 1 软件概述
story.append(Paragraph("1 软件概述", style_h1))
story.append(Paragraph(
"边坡变形监测-变形(位移)自动测量系统配置工具(" + EDITION + ")是一款用于激光示踪传感器设备的网关参数配置软件。"
"软件通过RS485串口与传感器设备进行通信,支持查询设备参数、修改Lora无线通信频率、发送自定义指令等操作。",
style_body
))
story.append(Paragraph("主要功能:", style_body))
features = [
"自动识别并列出本机可用串口",
"通过RS485串口连接激光示踪传感器设备",
"一键查询设备全部参数(软件版本、硬件版本、MAC地址、电池电量、注册状态等)",
"修改Lora无线通信信道频率(" + FC_RANGE + ",步进" + FC_STEP + ",共" + str(FC_CHANNELS) + "个信道,信道0起始)",
"波特率支持修改(内置2400~115200常用波特率,也可手动输入)",
"查看设备低功耗配置参数(采集间隔、上报时间、紧急上报间隔/时长)",
"注册状态自动转换为\"已注册/未注册\"显示",
"通过通用串口升级协议(V1.1)实现设备固件升级,支持进度显示与断口复用",
"支持发送自定义AT指令",
"通讯日志实时显示,方便调试",
]
for f in features:
story.append(bullet(f))
# 2 运行环境
story.append(Paragraph("2 运行环境", style_h1))
story.append(make_table(
["项目", "要求"],
[
["操作系统", "Windows 7 / 8 / 10 / 11"],
["硬件接口", "USB转RS485串口线(或自带RS485接口)"],
["驱动要求", "USB转串口芯片驱动(如CH340/CP2102/FT232等)"],
["运行方式", "独立可执行文件(.exe),无需安装Python环境"],
["屏幕分辨率", "建议1280x720及以上"],
],
col_widths=[4*cm, 12*cm]
))
# 3 软件安装与启动
story.append(Paragraph("3 软件安装与启动", style_h1))
story.append(Paragraph("3.1 安装", style_h2))
story.append(Paragraph(
"本软件为绿色免安装版本,无需安装过程。将\"" + EXE_NAME + "\"文件复制到电脑任意目录即可使用。",
style_body
))
story.append(Paragraph("3.2 启动", style_h2))
story.append(Paragraph(
"双击\"" + EXE_NAME + "\"文件即可启动软件。窗口标题栏显示软件型号(" + EDITION + ")与版本号(" + SOFTWARE_VERSION + ")。",
style_body
))
story.append(Paragraph(
"注意:如果Windows弹出\"Windows已保护你的电脑\"提示,请点击\"更多信息\"->\"仍要运行\"。",
style_body
))
story.append(Paragraph("3.3 USB转串口驱动安装", style_h2))
story.append(Paragraph("使用前需确保USB转RS485串口线对应的驱动已正确安装。常见芯片及驱动如下:", style_body))
story.append(make_table(
["芯片型号", "驱动说明"],
[
["CH340 / CH341", "沁恒官方驱动,安装后在设备管理器中显示为COM口"],
["CP2102 / CP210x", "Silicon Labs官方驱动"],
["FT232", "FTDI官方驱动"],
],
col_widths=[5*cm, 11*cm]
))
story.append(Spacer(1, 6))
story.append(Paragraph(
"驱动安装成功后,将USB转RS485线插入电脑,在\"设备管理器->端口(COM和LPT)\"中应能看到对应的COM口编号。",
style_body
))
# 4 软件界面说明
story.append(Paragraph("4 软件界面说明", style_h1))
story.append(Paragraph("软件界面从上至下分为5个功能区域:", style_body))
story.append(Paragraph("4.1 串口连接区域", style_h2))
story.append(Paragraph("位于界面顶部,用于选择和配置串口连接参数。", style_body))
story.append(make_table(
["项目", "说明", "默认值"],
[
["串口选择", "下拉框选择可用串口(显示端口描述,同名端口自动追加COM号区分),点击\"刷新\"按钮可重新扫描", "自动选择第一个"],
["波特率", "串口通信速率,可编辑下拉框,内置2400/4800/9600/19200/38400/57600/115200,也可手动输入", "9600 bps"],
["数据位", "固定为8,与设备通信协议一致(界面仅显示,不可修改)", "8"],
["停止位", "固定为1,与设备通信协议一致(界面仅显示,不可修改)", "1"],
["校验", "固定为无校验,与设备通信协议一致(界面仅显示,不可修改)", "None(无校验)"],
["打开/关闭串口", "按钮切换串口连接状态;连接成功后串口下拉框锁定,关闭后恢复可选", "—"],
],
col_widths=[3.5*cm, 8.5*cm, 4*cm]
))
story.append(Paragraph("4.2 参数配置区域", style_h2))
story.append(Paragraph("包含Lora参数以及查询/升级操作:", style_body))
story.append(Paragraph("Lora 参数:", style_body))
story.append(Paragraph(
"信道频率(fc):下拉选择Lora通信频率,范围" + FC_RANGE + ",步进" + FC_STEP
+ "(共" + str(FC_CHANNELS) + "个信道,信道0起始,即信道0对应" + FC_RANGE.split(" ~ ")[0] + ")。"
"选择后点击\"发送\"按钮即可修改设备频率。",
style_bullet
))
story.append(Paragraph("查询与升级按钮:", style_body))
story.append(Paragraph(
"点击\"查询当前配置(para)\"按钮,软件向设备发送para指令查询全部参数。"
"查询结果将自动解析并显示在下方参数信息区域。点击\"设备升级\"按钮可打开设备升级对话框。",
style_bullet
))
story.append(Paragraph("4.3 设备参数信息区域", style_h2))
story.append(Paragraph("显示通过para指令查询到的设备参数,包括:", style_body))
story.append(make_table(
["参数名称", "说明"],
[
["软件版本", "设备固件软件版本号"],
["硬件版本", "设备硬件版本号"],
["本机MAC", "传感器设备的MAC地址"],
["电池电量", "设备当前电池电量"],
["设备类型", "设备类型标识(LocalType)"],
["通信方式", "设备当前通信方式(CommInf)"],
["设备标识", "设备唯一标识(DeviceSign)"],
["网关MAC", "所连接网关的MAC地址"],
["注册状态", "设备在网关上的注册状态,自动显示为\"已注册\"或\"未注册\""],
["Lora频率", "当前Lora通信频率"],
],
col_widths=[4*cm, 12*cm]
))
story.append(Paragraph("4.4 低功耗配置区域", style_h2))
story.append(Paragraph("显示设备的低功耗运行参数(由para指令返回的LpCfg字段解析而来):", style_body))
story.append(make_table(
["参数名称", "说明", "取值范围"],
[
["CI(采集间隔)", "传感器数据采集的时间间隔", "30~7200秒"],
["RI(上报时间)", "数据上报到网关的时间间隔", "5~720分钟"],
["EI(紧急上报间隔)", "紧急状态下数据上报的时间间隔", "1~5分钟"],
["ET(紧急上报时长)", "紧急上报模式持续时间", "10~120分钟"],
],
col_widths=[4.5*cm, 7.5*cm, 4*cm]
))
story.append(Spacer(1, 6))
story.append(Paragraph(
"显示格式说明:CI以\"秒\"显示;RI/EI/ET以\"分钟\"显示,当数值≥60分钟时自动换算为\"X时X分钟\"格式(例如90分钟显示为\"1时30分钟\")。",
style_body
))
story.append(Paragraph("4.5 通讯日志区域", style_h2))
story.append(Paragraph("界面底部,分为左右两部分:", style_body))
story.append(Paragraph(
"日志窗口:深色背景,实时显示串口通讯内容。不同类型信息用不同颜色区分:",
style_body
))
story.append(make_table(
["颜色", "含义"],
[
["蓝色", "发送的指令"],
["绿色", "接收到的数据"],
["浅绿色", "系统提示信息"],
["红色", "错误信息"],
],
col_widths=[4*cm, 12*cm]
))
story.append(Spacer(1, 6))
story.append(Paragraph(
"清空日志:点击日志窗口下方的\"清空日志\"按钮可清除当前全部日志内容,便于新一轮操作的观察。",
style_body
))
story.append(Paragraph("自定义指令:右侧面板可输入自定义指令,点击\"发送\"或按Enter键发送,发送成功后输入框自动清空。", style_body))
# 5 操作指南
story.append(Paragraph("5 操作指南", style_h1))
story.append(Paragraph("5.1 连接设备", style_h2))
steps = [
"将USB转RS485串口线连接到电脑USB接口。",
"将RS485线的A、B端分别连接到传感器设备的RS485接口(注意A接A、B接B)。",
"启动软件,在\"串口连接\"区域点击\"刷新\"按钮扫描可用串口。",
"在下拉框中选择对应的COM口。",
"确认波特率等参数与设备一致(默认9600, 8N1,波特率可直接修改)。",
"点击\"打开串口\"按钮,按钮文字变为\"关闭串口\"即表示连接成功。通讯日志中将显示连接信息。",
]
for i, s in enumerate(steps, 1):
story.append(step(i, s))
story.append(Paragraph("5.2 查询设备参数", style_h2))
steps = [
"确认串口已成功连接(按钮显示\"关闭串口\")。",
"点击\"查询当前配置(para)\"按钮。",
"软件将发送para指令,设备返回参数信息后自动解析。",
"设备参数信息和低功耗配置区域将显示查询到的数值。",
"Lora频率下拉框也会同步更新为当前设备频率。",
]
for i, s in enumerate(steps, 1):
story.append(step(i, s))
story.append(Paragraph("5.3 修改Lora信道频率", style_h2))
steps = [
"确认串口已成功连接。",
"在\"Lora 参数\"区域的下拉框中选择目标信道频率。",
"点击\"发送\"按钮,软件将发送\"lora fc <频率值>\"指令。",
"发送后建议点击\"查询当前配置\"验证修改是否成功。",
]
for i, s in enumerate(steps, 1):
story.append(step(i, s))
story.append(Paragraph(
"注意:修改Lora频率后,传感器与网关必须使用相同的信道频率才能正常通信。请确保网关的Lora频率也做了相应修改。",
style_warning
))
story.append(Paragraph("5.4 设备升级", style_h2))
story.append(Paragraph("设备升级用于通过通用串口升级协议(V1.1)对设备固件进行升级。升级期间请保持设备供电正常。", style_body))
steps = [
"确认串口已成功连接。",
"点击\"参数配置\"区域中的\"设备升级\"按钮,打开设备升级对话框。",
"在对话框中点击\"浏览\"选择要更新的固件文件(.bin),并确认设备MAC地址(默认自动填充para查询到的本机MAC;格式为6字节横线分隔,例如 09-00-01-7F-FF-FF)。",
"点击\"开始升级\"按钮,软件复用当前串口连接并自动切换至9600bps升级波特率,向设备下发固件信息(包数、大小、CRC32校验值)。",
"设备校验通过后逐包请求数据,软件按请求逐包下发固件;进度条显示\"n/m 包\"传输进度,升级日志显示握手/下发/完成等过程信息。",
"升级完成后串口自动恢复原波特率,可在同一对话框再次点击\"开始升级\"重复升级,无需关闭重开。",
]
for i, s in enumerate(steps, 1):
story.append(step(i, s))
story.append(Spacer(1, 6))
story.append(Paragraph(
"注意:升级过程中请勿断开串口或断电,否则可能导致设备无法正常启动。若设备长时间无响应(约10秒),升级将自动停止并提示未完成,可检查接线后重试。",
style_warning
))
story.append(Paragraph(
"提示:升级期间设备输出的调试信息会以\"[设备]\"前缀转发到主界面通讯日志,可用于排查升级失败原因。",
style_body
))
story.append(Paragraph("5.5 发送自定义指令", style_h2))
steps = [
"确认串口已成功连接。",
"在通讯日志区域右侧的\"自定义指令\"输入框中输入AT指令。",
"点击\"发送\"按钮或按Enter键发送,发送成功后输入框自动清空。",
"通讯日志中将显示发送的指令和设备的回应。",
]
for i, s in enumerate(steps, 1):
story.append(step(i, s))
# 6 指令参考
story.append(Paragraph("6 指令参考", style_h1))
story.append(Paragraph("软件界面涉及的指令如下:", style_body))
story.append(make_table(
["指令", "功能", "对应操作"],
[
["para", "查询设备全部参数", "点击\"查询当前配置(para)\"按钮"],
["lora fc <频率值>", "设置Lora信道频率(单位Hz)", "在\"Lora 参数\"下拉框中选择频率后点击\"发送\",范围" + FC_RANGE + ";示例:lora fc " + FC_EXAMPLE],
],
col_widths=[4.5*cm, 5.5*cm, 6*cm]
))
story.append(Spacer(1, 6))
story.append(Paragraph(
"说明:指令以回车换行(\\r\\n)结尾自动发送,无需手动添加。此外,可在自定义指令框中手动输入设备支持的其他指令,发送\"<指令> ?\"可查看该指令的在线帮助。",
style_body
))
# 7 常见问题
story.append(Paragraph("7 常见问题与故障排除", style_h1))
qa_items = [
("串口下拉框为空,无法选择串口",
"1. 检查USB转RS485线是否已插入电脑;\n"
"2. 检查设备管理器中是否出现对应的COM口;\n"
"3. 确认USB转串口驱动已正确安装;\n"
"4. 点击\"刷新\"按钮重新扫描。"),
("点击\"打开串口\"后提示连接失败",
"1. 确认该COM口未被其他软件占用(如串口助手、Keil等);\n"
"2. 尝试拔插USB线后重试;\n"
"3. 尝试更换USB接口。"),
("发送para指令后无回应",
"1. 确认RS485接线正确(A接A、B接B);\n"
"2. 确认传感器设备已上电;\n"
"3. 确认波特率设置与设备一致(默认9600);\n"
"4. 检查RS485线缆是否损坏。"),
("修改Lora频率后设备无法与网关通信",
"1. 确认传感器和网关使用相同的Lora信道频率;\n"
"2. 修改频率后需等待设备重新连接;\n"
"3. 可通过para命令验证当前频率设置。"),
("参数信息区域显示 \"-\"",
"1. 表示尚未查询或未收到设备回应;\n"
"2. 请先点击\"查询当前配置\"按钮;\n"
"3. 检查串口连接是否正常。"),
("Windows提示\"已保护你的电脑\"无法运行",
"1. 点击更多信息;\n"
"2. 点击仍要运行按钮;\n"
"3. 此提示是因为exe未做代码签名,软件本身安全。"),
("点击设备升级后无握手/升级失败",
"1. 确认设备MAC地址正确;\n"
"2. 确认固件文件为正确的.bin文件;\n"
"3. 确认串口连接正常、设备供电稳定;\n"
"4. 查看通讯日志中的设备调试信息辅助排查。"),
("升级进度长时间停留/中途停止",
"1. 软件在约10秒未收到设备响应时会自动停止升级,属正常保护机制;\n"
"2. 检查RS485接线是否松动、设备供电是否稳定;\n"
"3. 关闭升级对话框后重新打开重试;\n"
"4. 若多次失败,查看主界面通讯日志中\"[设备]\"前缀的调试信息定位原因。"),
]
for q, a in qa_items:
story.append(Paragraph("Q: " + q, style_q))
story.append(Paragraph(a.replace("\n", "<br/>"), style_a))
# 生成PDF
doc.build(story)
print("说明书已生成: " + output_path)