Files
GateWay_ConfigureAPP/generate_manual.py
T
YuanHongbin 1bccbb873f 新增设备管理与升级功能,优化参数信息显示
- 修复点击设备升级闪退问题
- 升级弹窗MAC提示改为81-00-00-06-00-00,进度条颜色不再随失焦变化
- 新增设备查询(culs)/设备删除(cudel)功能
- 设备列表显示Laser/FPO/LpCfg参数
- 设备参数信息区重新排布,显示通讯单元读取间隔
- 说明书同步更新
2026-09-01 11:43:47 +08:00

423 lines
18 KiB
Python
Raw 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
"""
import os
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm, cm
from reportlab.lib.colors import HexColor, white, black
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
PageBreak, KeepTogether, HRFlowable
)
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
# ── 注册中文字体 ──────────────────────────────────────────────
FONT_DIR = os.path.join(os.environ.get("WINDIR", r"C:\Windows"), "Fonts")
FONT_NAME = "SimSun"
FONT_BOLD = "SimHei"
pdfmetrics.registerFont(TTFont(FONT_NAME, os.path.join(FONT_DIR, "simsun.ttc")))
pdfmetrics.registerFont(TTFont(FONT_BOLD, os.path.join(FONT_DIR, "simhei.ttf")))
# ── 颜色 ─────────────────────────────────────────────────────
PRIMARY = HexColor("#1565C0")
ACCENT = HexColor("#4CAF50")
LIGHT_BG = HexColor("#E3F2FD")
TABLE_HEAD_BG = HexColor("#1565C0")
TABLE_ALT_BG = HexColor("#F5F5F5")
BORDER_COLOR = HexColor("#BDBDBD")
# ── 样式 ──────────────────────────────────────────────────────
styles = getSampleStyleSheet()
style_title = ParagraphStyle(
"CoverTitle", fontName=FONT_BOLD, fontSize=28, leading=40,
alignment=TA_CENTER, textColor=PRIMARY, spaceAfter=10 * mm
)
style_subtitle = ParagraphStyle(
"CoverSub", fontName=FONT_NAME, fontSize=14, leading=20,
alignment=TA_CENTER, textColor=HexColor("#616161"), spaceAfter=6 * mm
)
style_h1 = ParagraphStyle(
"H1", fontName=FONT_BOLD, fontSize=18, leading=26,
textColor=PRIMARY, spaceBefore=12 * mm, spaceAfter=4 * mm
)
style_h2 = ParagraphStyle(
"H2", fontName=FONT_BOLD, fontSize=14, leading=20,
textColor=HexColor("#1976D2"), spaceBefore=8 * mm, spaceAfter=3 * mm
)
style_h3 = ParagraphStyle(
"H3", fontName=FONT_BOLD, fontSize=12, leading=18,
textColor=HexColor("#388E3C"), spaceBefore=5 * mm, spaceAfter=2 * mm
)
style_body = ParagraphStyle(
"BodyCN", fontName=FONT_NAME, fontSize=10.5, leading=18,
alignment=TA_JUSTIFY, spaceAfter=2 * mm
)
style_note = ParagraphStyle(
"Note", fontName=FONT_NAME, fontSize=9.5, leading=16,
textColor=HexColor("#757575"), leftIndent=8 * mm, spaceAfter=2 * mm
)
style_toc = ParagraphStyle(
"TOC", fontName=FONT_NAME, fontSize=12, leading=22,
leftIndent=0, spaceAfter=1 * mm
)
style_toc_sub = ParagraphStyle(
"TOCSub", fontName=FONT_NAME, fontSize=11, leading=20,
leftIndent=10 * mm, spaceAfter=1 * mm
)
# ── 工具函数 ──────────────────────────────────────────────────
LQ = "\u300c" # 左角括号
RQ = "\u300d" # 右角括号
def heading1(text):
return Paragraph(text, style_h1)
def heading2(text):
return Paragraph(text, style_h2)
def heading3(text):
return Paragraph(text, style_h3)
def body(text):
return Paragraph(text, style_body)
def note(text):
return Paragraph(text, style_note)
def hr():
return HRFlowable(width="100%", thickness=0.5, color=BORDER_COLOR, spaceAfter=3 * mm)
def make_table(headers, rows, col_widths=None):
data = [headers] + rows
wrapped = []
for i, row in enumerate(data):
wrapped_row = []
for cell in row:
if i == 0:
s = ParagraphStyle("th", fontName=FONT_BOLD, fontSize=10, leading=14,
textColor=white, alignment=TA_CENTER)
else:
s = ParagraphStyle("td", fontName=FONT_NAME, fontSize=9.5, leading=14,
alignment=TA_CENTER)
wrapped_row.append(Paragraph(str(cell), s))
wrapped.append(wrapped_row)
t = Table(wrapped, colWidths=col_widths, repeatRows=1)
style_cmds = [
("BACKGROUND", (0, 0), (-1, 0), TABLE_HEAD_BG),
("TEXTCOLOR", (0, 0), (-1, 0), white),
("ALIGN", (0, 0), (-1, -1), "CENTER"),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("GRID", (0, 0), (-1, -1), 0.5, BORDER_COLOR),
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
("LEFTPADDING", (0, 0), (-1, -1), 6),
("RIGHTPADDING", (0, 0), (-1, -1), 6),
]
for i in range(1, len(data)):
if i % 2 == 0:
style_cmds.append(("BACKGROUND", (0, i), (-1, i), TABLE_ALT_BG))
t.setStyle(TableStyle(style_cmds))
return t
# ── 构建文档 ──────────────────────────────────────────────────
OUTPUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "网关配置工具使用说明书.pdf")
doc = SimpleDocTemplate(
OUTPUT, pagesize=A4,
topMargin=20 * mm, bottomMargin=20 * mm,
leftMargin=20 * mm, rightMargin=20 * mm
)
story = []
# ==================== 封面 ====================
story.append(Spacer(1, 50 * mm))
story.append(Paragraph("网关配置工具", style_title))
story.append(Paragraph("使用说明书", style_title))
story.append(Spacer(1, 15 * mm))
story.append(HRFlowable(width="60%", thickness=2, color=PRIMARY, spaceAfter=6 * mm))
story.append(Paragraph("适用于物联网网关设备的串口配置管理", style_subtitle))
story.append(Spacer(1, 10 * mm))
cover_info = [
["软件名称", "网关配置工具"],
["适用设备", "物联网网关(HC32F460 + 4G/Cat1 + Ethernet"],
["通信接口", "串口(UART),波特率 500000"],
["运行环境", "Windows 7/10/11"],
["文档版本", "V1.0"],
]
cover_table = Table(cover_info, colWidths=[40 * mm, 90 * mm])
cover_table.setStyle(TableStyle([
("FONTNAME", (0, 0), (0, -1), FONT_BOLD),
("FONTNAME", (1, 0), (1, -1), FONT_NAME),
("FONTSIZE", (0, 0), (-1, -1), 11),
("TEXTCOLOR", (0, 0), (0, -1), PRIMARY),
("ALIGN", (0, 0), (0, -1), "RIGHT"),
("ALIGN", (1, 0), (1, -1), "LEFT"),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("TOPPADDING", (0, 0), (-1, -1), 6),
("BOTTOMPADDING", (0, 0), (-1, -1), 6),
("LEFTPADDING", (0, 0), (-1, -1), 8),
("RIGHTPADDING", (0, 0), (-1, -1), 8),
("LINEBELOW", (0, 0), (-1, -2), 0.3, BORDER_COLOR),
]))
story.append(cover_table)
story.append(PageBreak())
# ==================== 目录 ====================
story.append(heading1("目 录"))
story.append(hr())
toc_items = [
("1", "产品概述"),
(" 1.1", "功能简介"),
(" 1.2", "系统要求"),
(" 1.3", "软件界面总览"),
("2", "快速入门"),
(" 2.1", "连接设备"),
(" 2.2", "查看网关配置"),
("3", "功能详解"),
(" 3.1", "串口连接"),
(" 3.2", "查看配置(para"),
(" 3.3", "Lora信道配置"),
(" 3.4", "自定义指令"),
(" 3.5", "通讯日志"),
("4", "指令参考"),
("5", "常见问题(FAQ"),
("6", "注意事项"),
]
for num, title in toc_items:
s = style_toc if not num.startswith(" ") else style_toc_sub
story.append(Paragraph(f"{num} {title}", s))
story.append(PageBreak())
# ==================== 1. 产品概述 ====================
story.append(heading1("1 产品概述"))
story.append(hr())
story.append(heading2("1.1 功能简介"))
story.append(body(
f"网关配置工具是一款基于串口通信的物联网网关设备配置管理软件。通过本工具,用户可以方便地完成以下操作:"
))
story.append(Spacer(1, 2 * mm))
features = [
["功能模块", "说明"],
["串口通信", "通过USB转串口连接网关设备,波特率500000,实时收发指令"],
["配置读取", "一键读取网关全部参数,包括软件/硬件版本、MAC地址、电池状态、注册状态等"],
["Lora信道配置", "选择并设置Lora通信信道频率(410.1MHz ~ 492.9MHz"],
["自定义指令", "支持手动输入任意指令与网关交互"],
["通讯日志", "实时显示发送和接收的通讯数据,便于调试"],
]
story.append(make_table(features[0], features[1:], col_widths=[35 * mm, 115 * mm]))
story.append(Spacer(1, 3 * mm))
story.append(heading2("1.2 系统要求"))
req_rows = [
["操作系统", "Windows 7 / 10 / 1164位推荐)"],
["接口", "USB接口(用于连接USB转串口线)"],
["驱动", "USB转串口芯片驱动(如CH340/CP2102等)"],
["运行依赖", "无需安装Python环境,直接运行可执行文件"],
]
story.append(make_table(["项目", "要求"], req_rows, col_widths=[35 * mm, 115 * mm]))
story.append(heading2("1.3 软件界面总览"))
story.append(body("软件界面自上而下分为以下几个区域:"))
ui_rows = [
[f"{LQ}串口连接{RQ}", "选择串口、刷新端口列表、打开/关闭串口"],
[f"{LQ}参数配置{RQ}", f"设置LoRa信道频率、{LQ}查看配置(para){RQ}{LQ}设备升级{RQ}按钮"],
[f"{LQ}设备参数信息{RQ}", f"显示网关读取到的各项参数(版本、MAC、电池、注册状态等)"],
[f"{LQ}通讯日志{RQ}", f"实时显示收发数据(蓝色=发送,橙色=接收)"],
[f"{LQ}自定义指令{RQ}", f"手动输入任意指令发送"],
]
story.append(make_table(["区域", "功能说明"], ui_rows, col_widths=[35 * mm, 115 * mm]))
story.append(PageBreak())
# ==================== 2. 快速入门 ====================
story.append(heading1("2 快速入门"))
story.append(hr())
story.append(heading2("2.1 连接设备"))
story.append(body("按照以下步骤连接网关设备:"))
steps = [
"使用USB转串口线连接电脑与网关设备的调试串口;",
"确认电脑已安装对应的USB转串口驱动;",
f"启动{LQ}网关配置工具{RQ}软件;",
f"{LQ}串口连接{RQ}区域,点击<b>刷新</b>按钮,扫描可用串口;",
"从下拉列表中选择对应的串口号(如COM3);",
"点击<b>打开串口</b>按钮,建立连接。",
]
for i, s in enumerate(steps, 1):
story.append(Paragraph(f"<b>步骤{i}</b>{s}", style_body))
story.append(Spacer(1, 2 * mm))
story.append(note(f"连接成功后,按钮变为红色{LQ}关闭串口{RQ},状态栏显示已连接的串口号。"))
story.append(heading2("2.2 查看网关配置"))
story.append(body(
f"串口连接成功后,点击<b>查看配置(para)</b>按钮,软件将自动向网关发送{LQ}para{RQ}指令,"
f"网关返回配置信息后,界面各区域将自动更新显示当前参数值。"
))
story.append(PageBreak())
# ==================== 3. 功能详解 ====================
story.append(heading1("3 功能详解"))
story.append(hr())
# 3.1
story.append(heading2("3.1 串口连接"))
story.append(body("串口连接是使用本工具的前提。软件固定使用以下串口参数:"))
serial_rows = [
["波特率", "500000"],
["数据位", "8"],
["停止位", "1"],
["校验位", "None(无校验)"],
]
story.append(make_table(["参数", ""], serial_rows, col_widths=[40 * mm, 60 * mm]))
story.append(Spacer(1, 2 * mm))
story.append(note("如无法连接,请检查串口驱动是否正确安装,以及串口是否被其他程序占用。"))
# 3.2
story.append(heading2("3.2 查看配置(para"))
story.append(body(
f"点击{LQ}查看配置(para){RQ}按钮后,软件发送{LQ}para{RQ}指令,网关返回包含以下信息的配置数据块:"
))
para_rows = [
["软件版本", "SoftWare V", "网关固件版本号"],
["硬件版本", "HardWare V", "网关硬件版本号"],
["网关MAC", "GateWay MAC", "网关唯一MAC地址"],
["电池电量", "Battery", "当前电池电量"],
["注册状态", "MuchReg", "true=已注册,false/null=未注册"],
["通讯方式", "Much", "当前主通讯方式(eth/cat1/lora"],
["Cat1 IMEI", "Cat1 IMEI", "4G模块IMEI号"],
["Cat1 SIM", "Cat1 SIM", "SIM卡信息"],
["Lora频率", "Lora fc", "当前Lora信道频率"],
]
story.append(make_table(["显示项", "指令关键字", "说明"], para_rows, col_widths=[30 * mm, 45 * mm, 75 * mm]))
# 3.3
story.append(heading2("3.3 Lora信道配置"))
story.append(body(
"Lora信道配置用于设置网关的Lora无线通信频率。信道列表覆盖410.1MHz至492.9MHz范围,"
"频率间隔0.2MHz,共415个信道可供选择。"
))
story.append(Spacer(1, 2 * mm))
story.append(heading3("操作步骤"))
lora_steps = [
"在Lora信道下拉框中选择目标信道(显示信道编号和对应频率);",
f"点击<b>发送</b>按钮,发送{LQ}lora fc [频率值]{RQ}指令。",
]
for i, s in enumerate(lora_steps, 1):
story.append(Paragraph(f"{i}. {s}", style_body))
story.append(Spacer(1, 2 * mm))
lora_range = [
["起始频率", "410.1 MHz(信道1"],
["终止频率", "492.9 MHz(信道415"],
["频率间隔", "0.2 MHz200 kHz"],
["指令格式", "lora fc [频率值,单位Hz]"],
]
story.append(make_table(["项目", ""], lora_range, col_widths=[35 * mm, 115 * mm]))
# 3.4
story.append(heading2("3.4 自定义指令"))
story.append(body(
"自定义指令区域允许用户手动输入任意指令与网关交互,适用于高级调试和特殊配置场景。"
))
story.append(Spacer(1, 2 * mm))
custom_steps = [
"在指令输入框中输入要发送的指令文本;",
"按<b>Enter</b>键或点击<b>发送</b>按钮发送指令;",
"发送后输入框自动清空,回复数据将在通讯日志中显示。",
]
for i, s in enumerate(custom_steps, 1):
story.append(Paragraph(f"{i}. {s}", style_body))
# 3.5
story.append(heading2("3.5 通讯日志"))
story.append(body("通讯日志区域实时显示与网关的通讯数据,采用深色背景方便阅读:"))
log_rows = [
["蓝色文字", "发送的指令(&gt;&gt;&gt; 前缀)", "发送的指令内容"],
["橙色文字", "接收的数据(&lt;&lt;&lt; 前缀)", "网关返回的原始数据"],
]
story.append(make_table(["颜色", "类型", "说明"], log_rows, col_widths=[25 * mm, 45 * mm, 80 * mm]))
story.append(Spacer(1, 2 * mm))
story.append(note(f"点击{LQ}清空日志{RQ}按钮可清除当前日志内容。日志数据仅保留在内存中,关闭软件后自动清除。"))
story.append(PageBreak())
# ==================== 4. 指令参考 ====================
story.append(heading1("4 指令参考"))
story.append(hr())
story.append(body("以下为网关配置工具使用的全部指令汇总:"))
story.append(Spacer(1, 2 * mm))
cmd_rows = [
["para", "读取网关全部配置参数", "查看配置(para)"],
["lora fc &lt;频率&gt;", "设置Lora信道频率(Hz)", "Lora信道配置"],
]
story.append(make_table(
["指令格式", "功能说明", "对应操作"],
cmd_rows, col_widths=[42 * mm, 58 * mm, 50 * mm]
))
story.append(Spacer(1, 3 * mm))
story.append(note("所有指令以回车换行(\\r\\n)结尾。自定义指令区域可发送以上指令及网关支持的其他指令。"))
story.append(PageBreak())
# ==================== 5. 常见问题 ====================
story.append(heading1("5 常见问题(FAQ"))
story.append(hr())
faqs = [
("Q1:打开串口失败怎么办?",
"1)检查USB转串口线是否正确连接;2)确认串口驱动已安装(设备管理器中可见COM口);"
f"3)确认该串口未被其他程序占用;4)尝试点击{LQ}刷新{RQ}重新扫描串口列表。"),
("Q2:发送指令后没有回复?",
f"1)确认串口已成功打开(按钮显示红色{LQ}关闭串口{RQ});2)检查串口线RX/TX是否接反;"
"3)确认网关设备已上电且正常运行;4)尝试重新连接串口。"),
("Q3:Lora信道列表很长,如何快速选择?",
"Lora信道下拉框支持鼠标滚轮滚动选择,也可以点击下拉后直接滚动浏览。"
"信道编号和频率一一对应,选择后点击发送即可。"),
]
for q, a in faqs:
story.append(Paragraph(f"<b>{q}</b>", style_body))
story.append(Paragraph(f"\u3000\u3000{a}", style_body))
story.append(Spacer(1, 2 * mm))
story.append(PageBreak())
# ==================== 6. 注意事项 ====================
story.append(heading1("6 注意事项"))
story.append(hr())
warnings = [
"<b>串口独占:</b>同一串口在同一时间只能被一个程序打开,请确保使用前关闭其他可能占用串口的软件。",
"<b>波特率固定:</b>本工具固定使用500000波特率,与网关固件匹配,请勿尝试修改。",
f"<b>数据备份:</b>修改配置前建议先通过{LQ}查看配置{RQ}记录当前参数,以便需要时恢复。",
"<b>断电保护:</b>配置修改后请确认网关已保存参数,避免在参数写入过程中断电。",
"<b>驱动安装:</b>首次使用需安装USB转串口芯片驱动(如CH340、CP2102等),具体型号取决于使用的转接线。",
]
for w in warnings:
story.append(Paragraph(f"\u26a0 {w}", style_body))
story.append(Spacer(1, 1 * mm))
story.append(Spacer(1, 20 * mm))
story.append(hr())
story.append(Paragraph("\u2014 文档结束 \u2014", ParagraphStyle(
"End", fontName=FONT_NAME, fontSize=10, alignment=TA_CENTER,
textColor=HexColor("#9E9E9E")
)))
# ── 生成PDF ──────────────────────────────────────────────────
doc.build(story)
print(f"PDF已生成: {OUTPUT}")