2026-08-19 10:11:54 +08:00
|
|
|
|
# -*- 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)"],
|
2026-09-02 10:50:21 +08:00
|
|
|
|
["通信接口", "串口(UART),波特率默认500000可修改"],
|
2026-08-19 10:11:54 +08:00
|
|
|
|
["运行环境", "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)"),
|
2026-09-01 11:43:47 +08:00
|
|
|
|
(" 3.3", "Lora信道配置"),
|
|
|
|
|
|
(" 3.4", "自定义指令"),
|
|
|
|
|
|
(" 3.5", "通讯日志"),
|
2026-08-19 10:11:54 +08:00
|
|
|
|
("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 = [
|
|
|
|
|
|
["功能模块", "说明"],
|
2026-09-02 10:50:21 +08:00
|
|
|
|
["串口通信", "通过USB转串口连接网关设备,波特率默认500000可自行修改,实时收发指令"],
|
2026-08-19 10:11:54 +08:00
|
|
|
|
["配置读取", "一键读取网关全部参数,包括软件/硬件版本、MAC地址、电池状态、注册状态等"],
|
2026-09-07 15:34:03 +08:00
|
|
|
|
["Lora信道配置", "选择并设置Lora通信信道频率(410.1MHz ~ 492.6MHz)"],
|
2026-08-19 10:11:54 +08:00
|
|
|
|
["自定义指令", "支持手动输入任意指令与网关交互"],
|
|
|
|
|
|
["通讯日志", "实时显示发送和接收的通讯数据,便于调试"],
|
|
|
|
|
|
]
|
|
|
|
|
|
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 / 11(64位推荐)"],
|
|
|
|
|
|
["接口", "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 = [
|
2026-09-01 11:43:47 +08:00
|
|
|
|
[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"手动输入任意指令发送"],
|
2026-08-19 10:11:54 +08:00
|
|
|
|
]
|
|
|
|
|
|
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 串口连接"))
|
2026-09-02 10:50:21 +08:00
|
|
|
|
story.append(body("串口连接是使用本工具的前提。串口参数(波特率可修改,默认500000):"))
|
2026-08-19 10:11:54 +08:00
|
|
|
|
serial_rows = [
|
2026-09-02 10:50:21 +08:00
|
|
|
|
["波特率", "500000(可在下拉框中选择或手动输入)"],
|
2026-08-19 10:11:54 +08:00
|
|
|
|
["数据位", "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地址"],
|
2026-09-01 11:43:47 +08:00
|
|
|
|
["电池电量", "Battery", "当前电池电量"],
|
|
|
|
|
|
["注册状态", "MuchReg", "true=已注册,false/null=未注册"],
|
|
|
|
|
|
["通讯方式", "Much", "当前主通讯方式(eth/cat1/lora)"],
|
2026-08-19 10:11:54 +08:00
|
|
|
|
["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
|
2026-09-01 11:43:47 +08:00
|
|
|
|
story.append(heading2("3.3 Lora信道配置"))
|
2026-08-19 10:11:54 +08:00
|
|
|
|
story.append(body(
|
2026-09-07 15:34:03 +08:00
|
|
|
|
"Lora信道配置用于设置网关的Lora无线通信频率。信道列表覆盖410.1MHz至492.6MHz范围,"
|
|
|
|
|
|
"频率间隔0.5MHz,共166个信道可供选择。"
|
2026-08-19 10:11:54 +08:00
|
|
|
|
))
|
|
|
|
|
|
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 = [
|
2026-09-07 15:34:03 +08:00
|
|
|
|
["起始频率", "410.1 MHz(信道0)"],
|
|
|
|
|
|
["终止频率", "492.6 MHz(信道165)"],
|
2026-09-02 10:50:21 +08:00
|
|
|
|
["频率间隔", "0.5 MHz(500 kHz)"],
|
2026-08-19 10:11:54 +08:00
|
|
|
|
["指令格式", "lora fc [频率值,单位Hz]"],
|
|
|
|
|
|
]
|
|
|
|
|
|
story.append(make_table(["项目", "值"], lora_range, col_widths=[35 * mm, 115 * mm]))
|
|
|
|
|
|
|
2026-09-01 11:43:47 +08:00
|
|
|
|
# 3.4
|
|
|
|
|
|
story.append(heading2("3.4 自定义指令"))
|
2026-08-19 10:11:54 +08:00
|
|
|
|
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))
|
|
|
|
|
|
|
2026-09-01 11:43:47 +08:00
|
|
|
|
# 3.5
|
|
|
|
|
|
story.append(heading2("3.5 通讯日志"))
|
2026-08-19 10:11:54 +08:00
|
|
|
|
story.append(body("通讯日志区域实时显示与网关的通讯数据,采用深色背景方便阅读:"))
|
|
|
|
|
|
log_rows = [
|
2026-09-01 11:43:47 +08:00
|
|
|
|
["蓝色文字", "发送的指令(>>> 前缀)", "发送的指令内容"],
|
|
|
|
|
|
["橙色文字", "接收的数据(<<< 前缀)", "网关返回的原始数据"],
|
2026-08-19 10:11:54 +08:00
|
|
|
|
]
|
|
|
|
|
|
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 <频率>", "设置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)尝试重新连接串口。"),
|
2026-09-01 11:43:47 +08:00
|
|
|
|
("Q3:Lora信道列表很长,如何快速选择?",
|
2026-08-19 10:11:54 +08:00
|
|
|
|
"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>同一串口在同一时间只能被一个程序打开,请确保使用前关闭其他可能占用串口的软件。",
|
2026-09-02 10:50:21 +08:00
|
|
|
|
"<b>波特率:</b>波特率默认500000,可在下拉框选择或手动输入修改,请与网关固件实际波特率保持一致。",
|
2026-08-19 10:11:54 +08:00
|
|
|
|
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}")
|