news 2026/10/16 3:25:43

Excel配置表转protobuf二进制:类型安全与跨端共用的工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Excel配置表转protobuf二进制:类型安全与跨端共用的工程实践

简介:面向Unity游戏开发者的xls数据转换工具,基于Google Protocol Buffers序列化协议,借助其体积小、速度快、跨平台等特性,将Excel表格解析并生成C#类文件,使策划人员可直接修改xls调整角色属性、物品参数等游戏数值,省去频繁改动源码和重新打包的麻烦。工具包共34个文件,以C++源文件(12个cpp、12个h)和SConstruct构建脚本为主,前者承担核心数据处理与内存管理,后者负责自动化构建;配合Python转换脚本、Markdown说明文档、示例xls及Git忽略配置,整体压缩包仅375KB,结构紧凑。已有661人学习下载。资源内含完整的tnt-master工程,覆盖共享内存队列、环形队列、代码收件箱、日志模块、应用框架与定时任务等基础组件,可作为Unity数据配置管线的参考实现,也能提取核心逻辑用于其他游戏引擎或后端服务;配套的README与TODO文档有助于快速理解源码结构与改进方向,还保留了示例xls与构建配置,便于直接跑通流程,适合具备Python和基础C++阅读能力的开发者二次开发。

1. 把 Excel 配置表变成 protobuf 二进制:这工具到底解决什么问题

做客户端或服务端的开发者,迟早会遇到同一类诉求:策划把道具表、技能表、活动配置写在 Excel 里,改得比代码还勤,程序这边却要一份启动快、类型安全、跨端共用的数据格式。一个能把 xls 解析成 protobuf 二进制的工具,基本就是配置驱动开发的标配。

我最早在某个模拟项目里体验到差距:同样一份道具表,JSON 下发接近 2 MB、解析要几十毫秒,换成 protobuf 后体积降了一个数量级,解析时间几乎可忽略,字段类型改了编译期就报错。这个工具要打通的就是这条链路:Excel 是人编辑的界面,protobuf 是机器加载的格式,中间的翻译自动化完成。

适合谁?有配置表纠结要不要上 protobuf 的开发者,被“表改了程序没同步”坑过的工具链工程师,以及想从零跑通最小可用版本的新手。接下来先讲选型,再给实现,最后是踩坑清单。

2. 为什么是 protobuf 而不是 JSON 或 CSV:配置下发场景的选型逻辑

很多人拿到需求第一反应是“工具怎么写”,其实先得回答“为什么要用 protobuf”。选型错了,工具写得再顺也是白费。我见过有项目配置链路都搭好了,半年后才发现 JSON 完全够用,protobuf 的 schema 维护反而成了额外负担。所以这一章先把选型逻辑讲清楚,再给出表结构到 message 的映射规则。

2.1 JSON、CSV、protobuf 三选一:体积、解析速度与类型安全

先看一张对比表:

维度JSONCSVprotobuf
体积大,字段名逐条重复中等,字段名只出现一次最小,字段名只在 schema 里出现
解析速度慢,字符串逐字符解析快,但类型全丢快,二进制直接映射
类型安全弱,运行期才能发现更弱,一切皆字符串强,编译期可校验
可读性好,肉眼可读一般差,必须配导出工具
结构演进靠人肉兼容靠人肉兼容字段号保留即可兼容

配置表的典型特征是:数据量不大,但会被客户端和服务端同时加载,热点路径上反复访问。JSON 的问题不在解析速度——几百 KB 解析也没慢到哪去,而在于没有 schema,字段改名、类型调整全靠口头约定,出问题很难定位。CSV 更省空间,但 int、float、string 全变成字符串,消费方要自己猜类型,和 JSON 是同一个病根。

protobuf 的 schema 即契约。字段改名、枚举多了一个值、repeated 被拆成子表,编译期就能暴露。代价是二进制不可读,调试时要额外写导出工具。所以我的判断标准很简单:配置要跨端共用、结构会持续演进、消费方超过一个,就上 protobuf;如果只是单机脚本自己用一份小表,JSON 足够,别给自己加戏。

提示:工具链里最贵的不是解析逻辑,而是“格式约定”本身。选了 protobuf,就意味着 .proto 文件要纳入版本管理,成为和代码同级的产物。

2.2 Excel 表到 protobuf message 的映射规则:sheet 行 列 与字段

映射规则其实非常机械:一个 sheet 对应一个消息类型,一行对应一条消息实例,一列对应一个字段,第一列通常是主键 id。比如一张道具表:

syntax = "proto3"; package config; message ItemCfg { int32 id = 1; string name = 2; repeated int32 attr_ids = 3; int32 quality = 4; }

对应到 Excel 就是表头一行、数据往下铺开。attr_ids 这类一对多字段在表里用分号分隔字符串表达,解析时再拆成 repeated。

输出文件有两种组织方式。第一种:每个 sheet 直接输出一个“记录数 + 逐条消息”的裸二进制文件,简单粗暴。第二种:外层套一个表级 message,把整张表塞进去:

message ItemCfgTable { int32 version = 1; repeated ItemCfg items = 2; }

我强烈推荐第二种,理由很实际:外层 message 可以带版本号、生效时间、数据源备注这些元信息,客户端拿到文件先校验版本再加载,表结构怎么加字段都不影响老客户端。裸文件一旦中间有一条消息损坏,后面全部错位,连“从哪断的”都很难定位。

2.3 表头约定的两种主流写法:纯列名 vs 列名内嵌类型

映射规则定了,下一个问题是 Excel 表头怎么和 proto 字段对上。最常见的做法是表头直接写字段名,类型由 .proto 决定,配表的人不需要懂类型。另一个做法是在表头里内嵌类型,比如name:str、attr_ids:rep<i32>,工具解析表头后自动生成 .proto。两种我都用过,也说一下取舍。

纯列名方案维护成本低,但每加一张表都要手动写一份 .proto,表和 proto 的对应关系靠人记。内嵌类型方案灵活,改表不用动 proto,可表头写着写着就花了,比如有人写rep<i32>有人写repeated int32,你的解析器得兼容所有风格。

我现在的习惯是两阶段走:表数量少、结构稳定时用纯列名;表超过一百张、手工维护 proto 跟不上节奏时,切换到内嵌类型加自动生成。还有第三种外部映射文件方案,用一个配置文件描述 sheet 名到 message 名、列名到字段名的对应关系,适合列名和历史包袱重的老项目,但本身又引入了一层要维护的映射,新手阶段不建议碰。

不管用哪种风格,都要守一条铁律:第一列必须是 id,且不允许为空。后面所有校验、diff、引用检查都建立在这个前提下,这条约定省掉的坑比任何代码都多。

3. 用 Python 从零搭一个可用的 protobuf 解析 xls 工具:最小可运行版本

这一章直接给能跑的东西。先配环境,再编译 proto,然后写解析脚本,最后落成一条命令行。我不讲花哨的框架,就讲怎么用最少的依赖把链路打通。

3.1 依赖选型:xlrd 读老 xls,openpyxl 读 xlsx

Python 读 Excel 主要就两个库:xlrd 2.0 之后只支持 .xls,openpyxl 只支持 .xlsx。如果项目里还有大量历史遗留的 .xls,继续用 xlrd;新建配表一律 xlsx,交给 openpyxl。protobuf 运行时库建议 3.19 以上,并且和编译 .proto 用的 protoc 版本保持一致。

pip install xlrd==2.0.1 openpyxl protobuf

装 xlrd 时特别注意版本:2.0.1 之后不再读 xlsx,网上大量老教程让你装 1.2.0 去读 xlsx,但那个版本已经不维护了。我一般让工具同时支持两种入口:.xls 走 xlrd,.xlsx 走 openpyxl,避免配表人员“另存为”一下换了个扩展名,工具直接哑火。

3.2 编译 .proto:protoc 生成 pb2 文件

先用 protoc 把 .proto 编译成 Python 类。命令很简单:

protoc -I ./proto --python_out=./gen ./proto/config.proto

执行之后会在 ./gen 下生成 config_pb2.py,每个消息变成一个类,对象自带 SerializeToString() 和 ParseFromString() 方法,不用手写二进制打包。参数说明:-I 指定 .proto 的搜索路径,--python_out 指定输出目录。路径里别带中文和空格,否则后面 import 时会遇到一些说不清的玄学报错。

注意:生成的 pb2 文件是编译产物,不要手工改,也不要指望跨 protobuf 版本通用。生成它的 protoc 版本和运行时库版本不一致,是最隐蔽的坑之一,第 5 章专门讲。

3.3 核心解析脚本:逐行读表、填入消息、序列化落盘

下面是最小可用版的解析脚本,一次把一张道具表从 xls 变成 .pb:

import sys import xlrd import config_pb2 def load_items(xls_path): book = xlrd.open_workbook(xls_path) sheet = book.sheet_by_name("item") headers = [str(sheet.cell_value(0, c)).strip() for c in range(sheet.ncols)] items = {} for r in range(1, sheet.nrows): row = {} for c, h in enumerate(headers): if h: # 空表头直接跳过 row[h] = sheet.cell_value(r, c) if not row.get("id"): # id 为空的行视为空行 continue cfg = config_pb2.ItemCfg() cfg.id = int(float(row["id"])) # 数字单元格读出来是 float cfg.name = str(row.get("name") or "").strip() cfg.quality = int(float(row.get("quality") or 0)) for seg in str(row.get("attr_ids") or "").split(";"): if seg.strip(): cfg.attr_ids.append(int(float(seg.strip()))) items[cfg.id] = cfg return items if __name__ == "__main__": xls_path = sys.argv[1] out_path = sys.argv[2] items = load_items(xls_path) table = config_pb2.ItemCfgTable() table.version = 1 # 实际项目里建议从配置读取或命令行传入 for i in sorted(items): table.items.append(items[i]) with open(out_path, "wb") as f: f.write(table.SerializeToString()) print(f"序列化 {len(items)} 条 -> {out_path}")

逻辑说明:表头取第 0 行,数据从第 1 行开始;id 为空的行直接跳过,这样 Excel 里的分隔空行不会混进数据。单元格取值后按 proto 字段类型手动转换一次,repeated 字段按分号拆开后逐个 append。最后用一个表级 message 包住全部记录,序列化写入输出文件。

参数说明里有两个容易被忽略的细节。一是int(float(row["id"])):xlrd 对数字单元格返回 float,直接 int() 会报错,必须经过 float 兜底。二是分隔符选了分号而不是逗号,因为配表人员用中文输入法时容易打出全角逗号,分号出问题的概率低很多;如果你要兼容|、/这类分隔符,把它做成工具的可配置参数,别写死。

3.4 命令行参数与目录规范:从一条命令到一套流程

脚本能跑起来之后,下一步是定目录和参数。我常用的目录长这样:

./config # 配表源文件 xls/xlsx ./proto # .proto 定义 ./gen # protoc 生成代码 ./output # 序列化后的 .pb 文件

命令入口保持简单:

python xls2pb.py --xls ./config/item.xls --out ./output/item.pb --version 3

version 参数写进外层 message,客户端用它判断配置是否变化。再加一个 --strict 开关:开启后遇到表头里没见过的列名直接报错,而不是悄悄忽略。默认可以不开启,但真正进入发布流程时建议强制打开,它能帮你在最早阶段抓住“列名手误”这种低级问题。解析日志里把每个 sheet 的名字、行数、跳过的行号打出来,配表人员反馈“我明明改了”的时候,这些日志就是最好的证据。

4. 字段类型映射与复杂结构:repeated、enum、嵌套和自动生成 proto

基础链路跑通后,真正花时间的是字段的多样性。这一章把类型映射规则、一对多结构、枚举处理、半自动生成 proto 这四块一次讲完。

4.1 单元格类型到 protobuf 标量字段的映射表与判定顺序

Excel 单元格的类型很有限,protobuf 的标量类型却很多,映射时要定一套规则。我用的映射表如下:

Excel 单元格常见写法目标 proto 类型读取时注意
整数1001int32 / int64int(float(v)) 后判断范围
浮点3.14float / doublefloat(v)
文本火球术stringstr(v).strip()
布尔TRUE / FALSE / 1 / 0bool按字符串判断
日期2024-01-01string原样保留,不做时间戳转换

判定顺序建议:先看单元格的原始类型,是文本还是数字;数字再判断小数点后是否有非零值,决定走整数还是浮点通道。一个常见的坑是“看起来是数字”的列,实际被配表人员设置成了文本格式,读出来是带空格的字符串,所以所有文本通道都要 strip。

日期列尤其要小心。有人习惯写2024/1/1,有人写2024-01-01,还有人写日期序列号。我的建议是配置表里日期一律按字符串写、字符串存,不做任何解析。真要按时间生效的配置,把生效时间直接写成时间戳整数列,让配表人员用工具转换成数字,而不是让解析器去猜日期格式。

4.2 repeated 字段的两种表达:单元格分隔符与子表关联

一对多字段是配表里最常出现的结构,有两种成熟写法。第一种是单元格内用分隔符区分,适合列表短、元素不带额外属性的场景,核心代码已经在 3.3 里出现过。第二种是子表关联,适合元素本身还有概率、权重、数量等附属信息的场景:

def load_attrs(book, item_id): sheet = book.sheet_by_name("item_attr") result = [] for r in range(1, sheet.nrows): if int(float(sheet.cell_value(r, 0))) != item_id: continue attr = config_pb2.ItemAttr() attr.attr_id = int(float(sheet.cell_value(r, 1))) attr.weight = float(sheet.cell_value(r, 2)) result.append(attr) return result

逻辑说明:子表的第一列是主表 id,外层循环查到每件道具时调用一次这个函数,把隶属于它的属性行全部解析成消息列表。这样“列表元素的属性”就由子表的列来承载,而不是靠分隔符字符串拼。注意子表方案要求第一列(外键)非空且能转成整数,解析时同样要做类型兜底。

两个方案怎么选?我的经验是:列表长度小于 5、元素无附属属性,用分隔符;超过 5 或元素有概率权重这类字段,直接上子表。等配置表膨胀以后,把分隔符方案改成子表方案是很痛苦的,因为所有消费方都要跟着改结构。

4.3 enum 字段的三种写法与取值校验

配置表里枚举很常见,比如品质、类型、状态。配表人员习惯三种写法:直接写数字 2、写枚举名 GREEN、写带前缀的完整枚举名 QUALITY_GREEN。工具侧要统一处理:

QUALITY_MAP = {"NONE": 0, "WHITE": 1, "GREEN": 2} def parse_quality(v): if isinstance(v, float): v = int(v) if isinstance(v, int): if v not in QUALITY_MAP.values(): raise ValueError(f"未知品质值: {v}") return v return QUALITY_MAP[str(v).strip()] # 枚举名映射为数字

逻辑说明:数字和枚举名都接受,内部统一转成数字存进消息。前提是枚举名在 .proto 里已有定义,且 0 永远留给占位。proto3 有一条硬性规定:枚举的第一个值必须是 0,所以 NONE=0 必须存在,不能用 0 表示任何真实业务语义。

提示:枚举校验一定要放在序列化之前。配表写入一个不存在的枚举名,运行时反序列化不会报错,但业务逻辑拿到的值可能是未定义状态,这类 bug 极难追踪。

4.4 从表头自动生成 .proto:半自动 schema 推导

表结构还在频繁调整的阶段,手动维护 .proto 很累。可以从表头内嵌类型那一栏自动推导出 message 定义:

import re TYPE_MAP = { "i32": "int32", "u32": "uint32", "i64": "int64", "f32": "float", "f64": "double", "str": "string", "bool": "bool", } def parse_header(col): m = re.match(r"^(\w+):(rep<(\w+)>|(\w+))$", col) if not m: raise ValueError(f"无法解析表头: {col}") name = m.group(1) repeated = m.group(2).startswith("rep") subtype = m.group(3) or m.group(4) if subtype not in TYPE_MAP: raise ValueError(f"未知类型: {subtype}") return name, TYPE_MAP[subtype], repeated def gen_proto(sheet_name, headers): lines = [f"message {sheet_name} {{"] for i, h in enumerate(headers, start=1): name, pb_type, repeated = parse_header(h) rep = "repeated " if repeated else "" lines.append(f" {rep}{pb_type} {name} = {i};") lines.append("}") return "\n".join(lines)

这段代码的边界要讲清楚:字段号按表头顺序从 1 递增,rep<>前缀映射成 repeated,类型映射表里没有的类型直接报错。自动生成适合结构不稳定阶段,但一旦表稳定下来,我建议把生成的 .proto 固定住、纳入版本管理,不要每次构建都重新生成。字段号一旦发布就不可复用,删掉字段后重新生成,老客户端按旧字段号解析新数据就会错位。这是 protobuf 的铁律,没有例外。

5. protobuf 解析 xls 的避坑手册:最常见的 5 个翻车细节

配置解析工具写起来不难,难的是“偶尔跑出错、偶尔产量不对”。下面 5 个坑来自真实踩过的路,按出现频率排。

5.1 数字精度丢失:id 被读成科学计数法或末尾归零

现象:id 列写成 1234567890123,读出来变成 1234567890122,或者显示成 1.234568e+12;序列化后再反序列化,关键 ID 对不上。

原因:Excel 数字单元格底层按 IEEE 754 double 存储,超过 2 的 53 次方的整数本身就存不准。解析脚本再经过 float 到 int 的转换,精度丢得更厉害。这不是解析器的问题,是数据源的问题。

解决:让配表人员把 id 类长数字列设成文本格式;工具侧对 id 字段优先按文本读,读到的 float 用 format(v, ".0f") 转字符串再转 int。序列化前后各算一次关键字段的哈希,不一致就直接终止构建。宁可构建失败,也不能带着脏数据上线。

5.2 合并单元格让整行错位:读取前必须先做填充

现象:某一列只有第一行有值,其余全是 None,导致大量字段被默认值填掉,repeated 字段莫名少一段。

原因:配表人员为了让同值列好看,把列拉通了合并。Excel 只保留合并区域左上角的值,读取时其他单元格返回空。

解决:读取前先做一次合并填充,把左上角的值铺到整个区域:

for mr in ws.merged_cells.ranges: v = ws.cell(mr.min_row, mr.min_col).value for r in range(mr.min_row, mr.max_row + 1): for c in range(mr.min_col, mr.max_col + 1): ws.cell(r, c).value = v

这是 openpyxl 的写法;如果走 xlrd 读 .xls,我建议更激进一点:发现 sheet 存在合并单元格就报错,让配表人员重新导出一份不合并的表。老格式下合并信息读取本来就不可靠,与其猜,不如拒绝。

5.3 公式单元格读不到计算结果:缓存值与 data_only 的坑

现象:某列是=A1*0.5,openpyxl 默认读出来是公式字符串,加上 data_only=True 之后反而读出来是 None。

原因:公式的计算结果只缓存在文件里。如果文件从未被表格软件打开过,文件里根本没有缓存值;openpyxl 不做公式重算,只负责把缓存读出来。

解决:约定配表导出前必须用表格软件打开另存一次,强制刷新缓存。工具侧对公式型单元格做检测,发现就打印警告。更彻底的方案是引入无头模式的表格程序做一次重算,把重算后的文件再喂给解析器,适合已经跑在自动化流水线里的团队。别指望解析库替你算公式,这不现实。

5.4 空行、备注行混进数据:行有效性的判定策略

现象:序列化产物里出现 id=0 的记录、整行 None 的记录、以 # 开头的注释行,客户端加载出一堆垃圾配置。

原因:配表人员习惯用空行分段、用行首 # 写说明,工具没做行有效性过滤。

解决:行有效性用三层判断:id 非空、id 转 int 大于 0、行首不是注释符号。建议把 id 列的读取单独抽成一个函数,先扫一遍全列,把异常行的行号全部收集起来,解析结束后一次性输出。这样做的好处是配表人员能一次改完所有问题,而不是改一行跑一次工具。日志里打 sheet 名加行号,是这类工具最被低估的价值。

5.5 protobuf 版本不一致:序列化成功但反序列化失败

现象:本机序列化一切正常,换一台机器反序列化报错,或客户端解析到一半断掉、字段错乱。

原因:生成 pb2 的 protoc 版本和运行时 protobuf 库版本不一致,常见于团队成员各自升级了依赖。proto2/proto3 语法混用时,optional 字段的默认值语义也会触发类似问题。

解决:工具启动时打印 protoc 版本和运行时库版本,写进构建日志;requirements 里锁死版本号;生成的 pb2 文件头部自动注记编译工具版本,出现不匹配时一眼能看出来。protobuf 的兼容性靠“版本对齐”而不是“代码修补”,不要让任何人手工改 pb2。

6. 把工具接进配置发布链路:校验、diff 与热更新

工具能输出 .pb 只是第一步,真正省心的是把它接进发布流程,让每次改表都走同一套检查。

6.1 序列化前的四层校验:必填、枚举、引用与范围

四层校验缺一不可:必填项非空、枚举值合法、引用字段存在、数值在约定范围内。最容易被忽略的是引用校验——attr_ids 里的 ID 在属性表中不存在,客户端往往要运行到那个道具被使用时才崩。

def validate(table, allowed_attr_ids): for cfg in table.items: if cfg.id <= 0: raise ValueError(f"非法 id: {cfg.id}") for aid in cfg.attr_ids: if aid not in allowed_attr_ids: raise ValueError(f"道具 {cfg.id} 引用了不存在的 attr: {aid}")

这段代码放在写完 message 之后、写文件之前,失败直接中断构建。

6.2 两张配置表的 diff:改表之后怎么快速核对

配表最怕“不知道这次改了什么”。把两份 xls 解析成以 id 为键的 dict,逐字段比对:

def diff(old, new): for k in sorted(new): if k not in old: print(f"+ 新增 {k}") elif old[k] != new[k]: print(f"~ 修改 {k}: {old[k]} -> {new[k]}")

放进提交前检查脚本里,比人眼对着 Excel 找差异快一个数量级,也顺便把“这次配置到底有没有动”这个问题变成一条命令的答案。

6.3 多语言消费与调试导出:同一份 .pb 给客户端和服务端

.pb 是语言无关的,客户端、服务端各自用同一份 .proto 生成代码,加载同一个二进制文件。热更新时只需要替换 .pb,不用重新出包,字段号保留就兼容。调试时补一个反向导出:把 .pb ParseFromString 读回来,再写回 xlsx,线上版本到底发了什么,导出来给人眼核对,这是最后一片后悔药。

我现在的工作习惯是:任何配表改动,先跑解析、校验、diff 三步,全绿才提交。刚做这个工具时我也觉得多此一举,直到某次线上配置少了一条数据、客户端加载到一半崩了,才明白这几步省掉的全是线上事故。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 3:25:19

Android自绘树形控件:递归布局与显式栈清除的实战方案

简介&#xff1a;面向Android初中级开发者的思维导图实现项目&#xff0c;围绕递归算法与堆栈数据结构解决节点绘制与清除问题&#xff0c;覆盖自定义View绘图、触摸事件交互、树形数据模型、布局管理、动画效果及性能优化等核心知识点。项目为完整Android Studio工程&#xff…

作者头像 李华
网站建设 2026/10/16 3:24:47

告别AI信息焦虑:OpenClaw如何用每日一份简报帮你高效获取全网AI资讯

每天早上醒来第一件事&#xff0c;就是摸手机刷各种AI资讯。公众号推文、行业群消息、技术论坛热帖、大佬的推文转发——一口气刷下来半小时过去&#xff0c;真正记住的没几条&#xff0c;焦虑倒是增加了不少&#xff1a;总觉得自己错过了什么重要信息&#xff0c;总怕别人都知…

作者头像 李华
网站建设 2026/10/16 3:24:38

Docker一键部署Redis三主三从集群Shell脚本实战

简介&#xff1a;这份资源面向需要在 CentOS 7.x 环境下快速搭建 Redis 集群的运维与后端开发人员&#xff0c;提供基于 Docker 的一键部署方案&#xff0c;省去手工配置节点、拉取镜像与逐个启动容器的繁琐流程。压缩包共 6 个文件&#xff0c;以 3 个 shell 脚本为主&#xf…

作者头像 李华
网站建设 2026/10/16 3:24:11

USBViewer实战:Windows注册表里的U盘使用记录查询技巧

简介&#xff1a;USBViewer是一款面向系统管理员、IT审计人员及普通用户的U盘使用记录查询工具&#xff0c;能够展示计算机上曾经连接过的所有USB设备历史。该工具不仅采集设备ID、制造商、产品名称和序列号&#xff0c;还会记录首次连接、最后一次连接以及每次插拔的具体时间&…

作者头像 李华
网站建设 2026/10/16 3:22:56

飞易来硬件盒子Java串口通信实战:从Demo到生产级上位机模块

简介&#xff1a;这份Java源码Demo围绕飞易来硬件盒子的调用展开&#xff0c;面向需要对接该硬件设备的Java开发者&#xff0c;尤其是刚接触USB通信与第三方SDK集成、希望快速跑通示例的初中级程序员。压缩包共31个文件&#xff0c;约1.72MB&#xff0c;以7个jar依赖包、7个xml…

作者头像 李华
网站建设 2026/10/16 3:22:46

同一份代码驱动六类载具的开源飞控软件 ArduPilot 入门教程

同一份代码驱动六类载具的开源飞控软件 ArduPilot 入门教程 【免费下载链接】ardupilot ArduPlane, ArduCopter, ArduRover, ArduSub source 项目地址: https://gitcode.com/GitHub_Trending/ar/ardupilot ArduPilot 是一款开源飞控软件&#xff0c;同一套代码支持四旋翼…

作者头像 李华