Robot Framework 7.0升级必看:Listener v3迁移与实战

Robot Framework 升级到 7.0 之后,最影响测试平台维护者的一个变化,就是 Listener v2 被正式清退了。很多团队手里还握着一堆start_suite(name, attrs)风格的监听器,一跑起来直接不回调,或者报接口版本不支持。于是就有个很现实的问题:老代码是硬切 v4,还是退一步用 v3。我的答案是,如果只是想在测试生命周期里做观察、干预结果、补报告信息,Listener v3 完全够用,而且它是目前文档最全、迁移成本最低、踩坑经验最好查的版本。v3 的核心思路其实很简单:把“正在执行的对象”和“执行结果对象”同时交给你,让你在执行过程中既能看又能改。这篇文章不聊虚的,直接从 RF 7.0 的实际行为出发,把 v3 的原理、注册方式、API 细节、几个能直接抄走的工程案例,以及我踩过的坑完整梳理一遍。

1. 理解 Listener v3 的设计逻辑

1.1 为什么 v2 在 RF 7.0 里彻底活不下去了

Listener v2 是早期 Robot Framework 一直使用的监听接口,它的回调是模块级函数,签名非常朴素。比如start_suite(name, attrs),name是套件名称字符串,attrs是包含 source、doc、metadata 等信息的字典。看起来也不算难用,但问题在于,这种设计天生没有“对象”的概念,你拿到的只是一份快照文本,改不了任何东西。

更麻烦的是模块级函数的状态管理。v2 监听器想要在多个用例之间共享状态,只能靠模块里的全局变量。一旦测试套件多了,全局变量被反复读写,你根本分不清是谁改的。如果同一个监听器被多个套件引用,你甚至不能保证状态是隔离的还是共享的。我在一个老项目里就见过,v2 监听器里用了个全局 list 累积失败用例名,结果并行执行时互相覆盖,最后报告里的失败名单全是乱的。

到了 RF 7.0,官方直接移除了对 v2 的支持。除了接口本身设计陈旧,更大的原因是新的执行模型早就抛弃了“名字 + 属性字典”这种弱类型传参方式,留着 v2 等于要维护两套完全不同的回调解释逻辑。所以如果你还在用 v2,现在不是“要不要迁”的问题,而是“迁到哪个版本”的问题。

1.2 v3 的核心:data 和 result 双对象模型

Listener v3 在 Robot Framework 3.0 时引入,它把监听回调的参数从“名字 + 字典”换成了两个对象:data和result。

可以这么理解:data是“执行前快照”,描述正要执行的套件、用例或关键字是什么,包含名称、文档、标签、参数这些静态信息。result是“可写的成绩单”,记录执行后产生的状态、消息、耗时、标签等等。关键点在于,result是可以改的。你想给失败用例改个状态、补个标签、换段 message、往套件结果里塞元数据,直接在监听器里操作这个对象就行,RF 会自动把你改动后的内容带到 output.xml 和 log.html 里。

这个设计比 v2 强在哪?v2 只能“看”,v3 能干的是“看完了还能改”。举个例子,你想在测试报告里补一个 Git 分支信息,v2 只能读取 metadata 然后自己写到别处,但 v3 里你可以直接修改result.metadata,报告生成的时候就带上了。同样,你想把某些已知问题的失败用例临时标成通过,直接用result.status = "PASS"就可以。

用生活化的类比来说,v2 像你站在考场外面看学生考试,只能看到一张写着考生名字的纸条;v3 则让你在批改阶段直接拿起红笔,在成绩单上改分数、写评语,只要你别太离谱就行。

1.3 v3 和 v4 并存,先别急着迁移

RF 6.1 开始引入了 Listener v4,7.0 里 v3 和 v4 同时受支持。很多文章会建议新项目直接用 v4,这一点我认同,v4 的类型化更强,方法命名更清晰,对 Python 类型提示的支持也更好。但这不是说 v3 就该被抛弃。

v3 的优势在于,它是最成熟的一版接口,你随便搜一个“Robot Framework listener 示例”,大概率是 v3 代码。团队里如果有人维护过 RF 3.x 到 6.x 的监听器,那他切换到 v3 几乎不需要重新学任何东西。而且 v3 的类实例模型天然支持状态保存,一个实例贯穿整个执行过程,字段随便用,不用像 v2 那样靠全局变量。

如果你是老项目升级,我的建议很直接:如果监听器数量不大,可以花时间迁到 v4;如果监听器逻辑很复杂、回调又多,先平迁到 v3 更稳妥,至少先把 RF 7.0 跑起来,后续再逐步优化。毕竟监听器这种基础设施,稳定压倒一切。

2. 快速落地:让 RF 7.0 跑起你的第一个 v3 监听器

2.1 一个最小可用的监听器类

先写一个最简陋的 v3 监听器,感受一下它的结构。

# listener_v3.py class ExecutionProbe: ROBOT_LISTENER_API_VERSION = 3 def __init__(self, prefix="CASE"): self.prefix = prefix def start_test(self, data, result): print(f"[{self.prefix}] start: {data.name}") def end_test(self, data, result): print(f"[{self.prefix}] end: {data.name} -> {result.status}")

注意两个关键点。第一,类里必须写ROBOT_LISTENER_API_VERSION = 3,这是告诉 Robot Framework 按哪个接口版本来解释你的回调。第二,回调方法的参数必须和 RF 预期的一致。这里start_test和end_test都接收data和result,分别代表执行的用例对象和结果对象。

对应用例文件很简单,比如:

*** Test Cases *** 示例用例 Log hello listener

然后用命令行指定监听器运行:

robot --listener listener_v3.py .

就能在控制台看到类似这样的输出:

[CASE] start: 示例用例 [CASE] end: 示例用例 -> PASS

注意我用的是print而不是Log关键字在监听器里输出。原因后面会说,但先记住一条:在监听器内部不要随便调用BuiltIn().log之类的库关键字,否则很容易触发递归,把自己套死。

2.2 三种注册方式,你在工程里会用到哪一种

常见的注册方式有几种,我按使用频率排个序。

最常用的是命令行--listener。你可以传路径,也可以传模块名加类名,比如:

robot --listener listener_v3.ExecutionProbe .

这里的listener_v3是 Python 模块名,ExecutionProbe是类名。前提是模块所在目录在PYTHONPATH里,或者你加--pythonpath指定:

robot --pythonpath . --listener listener_v3.ExecutionProbe .

第二种是在 Python 代码里通过robot.run或robot.api.TestSuite调用时,用listeners参数注册。比如写一个自己的启动脚本:

from robot import run run( "tests/", listener=["listener_v3.ExecutionProbe"], pythonpath=["."], )

这种方式对做测试平台的团队很有用,监听器不必暴露在命令行里,可以直接集成到持续集成系统的调用逻辑中。

第三种是在套件里通过关键字动态设置监听器。Robot Framework 内置了Set Listener关键字,可以在测试数据里启用监听器。这种方法比较少见,但我见过有人用它做“只有特定套件才启用某些监听器”的场景。关键词用法大概是这样:

*** Settings *** Suite Setup Set Listener ${CURDIR}/listener_v3.py

我没记错的话,Set Listener是从robot.libraries.ListenerInterface库里出来的,你得手动导入那个库才能用。这个特性用得不多,建议优先用前两种方式。

2.3 命令行向监听器传参的格式

v3 监听器既然是类实例,自然可以在构造时传参。参数用冒号分隔,放在类名后面。比如上面那个 ExecutionProbe,我想传一个prefix参数:

robot --listener "listener_v3.ExecutionProbe:REGRESSION" .

如果有多个参数,按顺序传给__init__方法:

robot --listener "listener_v3.ExecutionProbe:REGRESSION:/tmp/log" .

这里有个容易踩的坑:Windows 路径里自带冒号,如果你的参数是C:\path\file,会和分隔符混淆。RF 的通用处理方式是使用逗号分隔替代冒号,或者按文档说明转义。但这种场景我一般直接避免,宁可把路径作为单个参数放在最后,或者干脆在监听器内部自己拼路径。

3. 回调方法逐一拆解:data 和 result 里到底有什么

3.1 套件生命周期:start_suite 与 end_suite

套件级别的回调是start_suite(data, result)和end_suite(data, result)。

data对象主要给你静态信息,比如data.name是套件名,data.source是套件文件路径,data.doc是套件文档,还有data.metadata能读取套件级元数据。result对象则记录这个套件最终跑完的结果,常见属性包括result.status、result.message、result.metadata、result.starttime和result.endtime。

我的经验是,end_suite最适合做报告增强。你想在测试报告里追加环境信息、版本号、构建号,直接往result.metadata里塞。

class ReportEnricher: ROBOT_LISTENER_API_VERSION = 3 def __init__(self, version="dev"): self.version = version def end_suite(self, data, result): result.metadata["App Version"] = self.version result.metadata["Test Engine"] = "Robot Framework 7.0 + Listener v3"

你会有个疑问,end_suite对整个执行树来说,外层套件结束时会最后一个被调用,内层子套件也会各自触发。所以这个监听器会往每个套件结果里都写入版本信息。如果你只想写顶层套件,可以根据data.parent判断一下是不是根节点。

3.2 用例生命周期:start_test 与 end_test

这是监听器里最常用的一组回调。start_test发生在用例开始前,end_test发生在用例结束后。

data对象可以拿到用例名称、文档、标签、超时时间等信息。result对象里最核心的是status、message和tags。想在用例失败后自动打一个标签,直接在end_test里操作:

class FailedTagAppender: ROBOT_LISTENER_API_VERSION = 3 def end_test(self, data, result): if result.status == "FAIL": result.tags.add("auto_failed") result.message = f"{result.message}\n[Listener] 自动标记失败用例"

这里有两个细节值得注意。

第一,start_test里拿到的result.status绝大多数情况下还没定论,因为用例压根还没执行完,这时候你去改状态基本没有意义。真正决定最终状态是在end_test,所以“改结果”的动作都放在end_test里做。

第二,直接给result.tags赋值可能出问题,因为 tags 在 RF 里是一个集合对象,用add方法追加更安全。如果你直接result.tags = ["foo"],某些版本下会丢失原有标签,还会报类型不兼容。

3.3 关键字级监听:start_keyword 与 end_keyword

关键字级回调可以精确到每一个用户关键字。start_keyword(data, result)在关键字开始执行前触发,end_keyword(data, result)在关键字执行完后触发。

这里的data会携带关键字相关属性,比如data.name、data.args、data.owner等。result则有result.status、result.message、result.elapsedtime。elapsedtime的单位是毫秒,这个属性在性能分析场景里很有用。

我写过一个慢关键字监听器,专门抓单条关键字耗时。

class SlowKeywordTracker: ROBOT_LISTENER_API_VERSION = 3 def __init__(self, threshold_ms=5000): self.threshold_ms = threshold_ms self.slow = [] def end_keyword(self, data, result): elapsed = result.elapsedtime / 1000.0 if elapsed >= self.threshold_ms: self.slow.append((data.name, result.elapsedtime)) print(f"SLOW KEYWORD: {data.name} took {elapsed:.2f}s") def close(self): if self.slow: print("慢关键字 Top 10:") for name, elapsed_ms in sorted(self.slow, key=lambda x: -x[1])[:10]: print(f" {elapsed_ms / 1000.0:.2f}s {name}")

注意,关键字级监听事件的触发范围和“用户关键字”的界定有关。实际运行中,测试数据里定义的关键字、资源文件里的关键字基本都会触发;某些低层库关键字的表现会随版本略有差异,不要假设所有库内部调用都能被监听到。需要精确统计时,请以小规模跑测的实测结果为准。

另外,关键字回调里如果result对象的status是FAIL,也可以用和用例一致的方式去改,但改关键字状态的风险比改用例状态更高,因为它可能影响上层关键字的执行逻辑,我建议不要轻易这么做。

3.4 日志与消息:log_message 和 message

除了生命周期回调,v3 还提供日志级别的事件。log_message(message)是Log关键字产生日志时触发,message(message)是框架内部消息(比如警告、错误)触发。

传入的message对象带有message、level、timestamp、html这几个属性。level是INFO、WARN、ERROR这类日志等级。你可以在这里做全局日志审查,比如把某个敏感关键字的所有日志统一收集到外部系统。

我实际用过的场景是,把特定标签用例里的WARN级别日志自动升级为ERROR,这样在持续集成里更容易暴露问题。

class WarningPromoter: ROBOT_LISTENER_API_VERSION = 3 def log_message(self, message): if message.level == "WARN" and "timeout" in message.message.lower(): message.level = "ERROR"

这里直接改message.level会同步反映到日志文件里。但要小心:log_message触发的频率非常高,一个用例里只要用了几十个Log,监听器就会被连续调用几十次。处理逻辑要轻,不能做重 IO 或耗时操作,否则整个测试会被拖慢。

message和log_message的区别,最直观的理解是:log_message面向测试日志内容,message面向框架反馈的系统消息。工作中更多会用前者做日志审计,后者做框架异常监控。

3.5 收尾钩子:close

最后是close(),它在整个测试执行结束、所有结果都写完之前被调用。适合做监听器资源清理、汇总输出、发送结果通知。

前面那个慢关键字监听器,就把汇总逻辑放在了close里。还有团队用close把统计结果写入一个 JSON 文件,方便平台端读取。注意close之后 RF 还要生成日志和报告,如果这里抛异常可能导致报告生成失败,所以收尾代码要写得保守,能吞掉的异常就吞掉,别让监听器把主流程搞挂。

4. 拿来即用的工程案例

4.1 报告注入版本与分支信息

这个我前面提过,但完整展开一下。做一个跨平台套件时,我最需要的就是在测试报告里看到被测应用的版本号和代码分支,否则出现失败用例时还得回到构建系统去翻流水线参数。

class BuildInfoInjector: ROBOT_LISTENER_API_VERSION = 3 def __init__(self, version, branch): self.version = version self.branch = branch def start_suite(self, data, result): if data.parent is None: result.metadata["version"] = self.version result.metadata["branch"] = self.branch

运行时通过--listener "build_info.BuildInfoInjector:v7.2.1:feature/login"传参。data.parent is None的判断可以过滤掉子套件,只在最顶层套件写入一次。最终在 log.html 的套件信息区域就能看到自定义元数据,省去了后续人工对齐版本号的麻烦。

4.2 失败用例自动加标签

失败用例自动加标签,本质上是给结果集做二次分类。比如我想让测试报告里能按“是否曾经失败过”来过滤,就可以在end_test里给失败用例加auto_failed标签。

class FailureMarker: ROBOT_LISTENER_API_VERSION = 3 def end_test(self, data, result): if result.status == "FAIL": result.tags.add("auto_failed") result.message = f"{result.message}\n[Listener] 本用例为失败用例,已自动标记"

这个监听器有个额外好处:result.tags.add修改后,log.html 的用例标签会实时更新,写报告脚本可以直接读取 output.xml 里的auto_failed标签做数据统计,不用自己再遍历 message。

4.3 偶发失败一键过滤

如果你维护过运行不稳定但实际业务正常的测试用例,一定遇过这种场景:某个用例因为环境偶发问题失败了,但你心里清楚根本不是功能 bug。手动改用例状态太粗暴,也不方便追溯。

可以写一个已知问题白名单监听器,规则是:用例带known-issue标签时,只要失败就自动改成通过,并在 message 里保留原始失败原因。

class KnownIssueBlocker: ROBOT_LISTENER_API_VERSION = 3 def end_test(self, data, result): if "known-issue" in data.tags and result.status == "FAIL": result.status = "PASS" result.message = f"[Listener] 已知问题被覆盖。原始信息:{result.message}"

这里我保留完整原始 message,就是为了后续排查时还有线索。这种监听器适合“已知问题池”机制,你有票号、有开发负责人、有修复计划,只是还没到提测标准。有了它,持续集成不会被已知问题持续打断,而通过用例数量也能客观反映交付质量。

4.4 关键字耗时巡检

第四个案例是关键字耗时巡检。性能回归是自动化测试里很容易忽略的一环,很多性能问题最初只是某个关键字变慢,但整体用例时间变化不明显,导致漏掉。

用我前面写过的SlowKeywordTracker,配合close汇总,就能在每次全量回归后输出慢关键字 Top 10。拿到 Top 10 之后,你可以再决定要不要针对某个关键字做专门 profiling 或拆解优化。这不复杂,但特别实用。

5. 我踩过的坑与排查方法

5.1 监听器压根没被调用

最崩溃的情况,没有之一。排查顺序要固定。

第一,检查类里是否有ROBOT_LISTENER_API_VERSION = 3。缺了它,RF 会认为这个监听器没有声明接口版本,直接忽略。第二,检查命令行里的模块路径和类名是否一致。--listener listener_v3.ExecutionProbe里的listener_v3必须是 Python 模块,.后面的ExecutionProbe必须是类名。第三,检查模块目录是否在PYTHONPATH里,不在就加--pythonpath .。

我遇到过最隐晦的情况是,监听器文件名和 Python 标准库重名,结果加载了系统库。比如自己写了个json.py想当监听器,结果模块导入时导进了标准库的 json,整个流程就乱了。

5.2 改了 result 不生效

状态改不生效,九成是因为时机不对。start_test时用例还没跑,状态自然没定;你要是在start_test里直接result.status = "FAIL",实际执行结束时会把你设置的值覆盖掉。

真正能稳定生效的时机是end_test。用例执行完之后,result对象的状态已经包含完成信息,这时候你主动覆盖status和message,RF 就会接受你的改动。

另一个点是,只改status不改message,会在报告里出现“状态是 PASS 但消息还是 FAIL 内容”这种矛盾情况。所以只要覆盖状态,一定把message也一起处理,哪怕只加一句“由监听器覆盖”。

5.3 日志风暴和递归陷阱

监听器内部如果调用BuiltIn().log或者logger.info,这些操作本身也是关键字执行或日志写入,又会触发监听器回调。轻则重复输出,重则死循环,甚至把控制台刷到卡死。

解决办法很简单:监听器内部输出统一用print,别用库关键字。print不会触发关键字事件,也不会被 RF 记录到 test log 里,只输出到控制台。除非你确实想让监听器自己的输出成为测试日志的一部分,否则一律print。

如果你写了一个比较复杂的监听器,建议在close里打印汇总而不是每次回调都打。我见过一个统计进度条的监听器,每个用例结束都print一堆字符,长时间回归下来输出文件膨胀得很厉害。

5.4 一个 RF 7.0 下常见但容易被忽略的坑

RF 7.0 对 Python 版本的要求是 3.9 以上,如果你还在用很旧的 Python 跑自动化环境,监听器加载阶段可能报各种莫名其妙的语法错误或 import 错误。这不是监听器代码的问题,而是环境不满足新版本的基础要求。

另一个和版本相关的点是,RF 7.0 的解析器对字符串和 Unicode 做过清理,监听器里如果依赖旧版本那些“自动转成 Unicode”的行为,可能会在某些边界文本上踩到。做迁移测试时,优先跑一遍包含中文、特殊字符、转义符的用例,能提前暴露大部分兼容性问题。

5.5 排查监听器问题的通用方法

我的习惯是,遇到监听器异常,先写一个空实现的最小监听器类,只打印事件名,逐个确认回调是否到达。比如:

class DebugListener: ROBOT_LISTENER_API_VERSION = 3 def start_suite(self, data, result): print("start_suite", data.name) def end_suite(self, data, result): print("end_suite", data.name) def start_test(self, data, result): print("start_test", data.name)

不要上来就调试复杂逻辑,先把“事件是否触发”和“参数是否正常”分离清楚。确认事件到达后,再加业务逻辑,这样定位速度最快。

如果事件没到,就用robot --loglevel DEBUG跑一个空套件,在日志里搜 Listener 相关输出,看加载阶段有没有报错或忽略提示。RF 对监听器加载失败通常会在日志里留下明显线索,排查时不要只盯控制台输出,也要翻 DEBUG 日志。

最后分享一个小经验:Listener v3 对工程师来说,最大的价值不是“功能多”,而是它把“观察”和“干预”分成了两个清晰的入口。data让你理解当前执行上下文的静态信息,result让你在事件落点处做出决策。只要按照事件节点去设计监听器,而不是一股脑把所有逻辑塞进end_test,这套机制就会非常稳。我自己的项目从 v2 迁到 v3 之后,监听器代码量没变少,但复杂度和出错概率明显下降了。

相关新闻

键盘操控CarSim/Simulink联合仿真:从固定信号到人机驾驶

键盘操控CarSim/Simulink联合仿真:从固定信号到人机驾驶

做联合仿真这几年,最让我觉得“舒服”的一刻,是把CarSim里那些预制好的方向盘转角曲线换成自己手里的方向键。说句实话,CarSim/TruckSim 与 Simulink 联合仿真最大的痛点往往不是模型精度,而是输入信号太“死”——跑离线工况时&a…

2026/10/20 17:21:55 阅读更多 →
移相全桥离散化Simulink仿真:3kW到3.6W极限切载动态响应分析

移相全桥离散化Simulink仿真:3kW到3.6W极限切载动态响应分析

最近在搭一个移相全桥的离散化Simulink仿真,输入400V,输出60V,满载3kW。最折磨人的不是满载稳态——只要参数不离谱,稳态基本都能稳住。真正暴露问题的反而是把负载从3kW瞬间切到3.6W的那一刻:输出电压直接冲高十几伏&…

2026/10/20 17:21:55 阅读更多 →
STL容器选型与迭代器失效:从原理到工程避坑实践

STL容器选型与迭代器失效:从原理到工程避坑实践

做C的人,十有八九都在STL上栽过跟头。尤其是容器选择和迭代器失效这两件事,堪称面试必问、线上必踩的黄金组合。我见过太多项目,初期拍脑袋选了个std::vector,结果中间插入频繁到性能雪崩;也见过线上服务因为std::dequ…

2026/10/20 17:21:55 阅读更多 →

最新新闻

大模型跨机房高可用 SLA 探针:基于 gRPC 双向流的微秒级心跳检测

大模型跨机房高可用 SLA 探针:基于 gRPC 双向流的微秒级心跳检测

在分布式大模型推理架构中,多机房、多云灾备已经成为保障业务 SLA(服务等级协议)的标配手段。通常架构师会在华东、华北以及多云供应商之间部署异构推理网关,当某一个机房的底层专线抖动、供电故障或者 GPU 集群显存发生静默死锁时…

2026/10/20 18:11:37 阅读更多 →
医院HIS系统操作手册编写指南:从功能说明书到医护落地工具

医院HIS系统操作手册编写指南:从功能说明书到医护落地工具

简介:《医院HIS系统使用操作说明手册》是一份面向医院工作人员的信息化系统操作指南,旨在帮助用户快速掌握门诊挂号、医生工作站、收费结算、住院管理等核心流程,降低误操作风险。压缩包仅含1个doc文档,体积约503.86MB&#xff0c…

2026/10/20 18:11:37 阅读更多 →
从C-Space到A*:多边形机器人避障路径规划的完整实现链路

从C-Space到A*:多边形机器人避障路径规划的完整实现链路

做机器人避障路径规划的人,十有八九是从A算法入的门。但很多人最初在栅格地图上把A跑得飞起,用的都是点模型——把机器人当成一个没有体积的像素,地图上能走就是能走。等真要往多边形机器人上迁移,问题就全冒出来了:地…

2026/10/20 18:11:37 阅读更多 →
AI原生生成4K全景视频:原理、应用与实操复盘

AI原生生成4K全景视频:原理、应用与实操复盘

最近AI视频生成赛道的屏霸,是一条关于“4K 360度全景视频原生生成”的消息。一支跨区域研究团队放出了技术细节,核心不是“生成了一部超高清全景片”,而是“模型直接输出球面视角的连续视频”。老玩家都知道,这俩区别是天壤之别。…

2026/10/20 18:11:37 阅读更多 →
Claude不支持插件:大模型工具调用的真相与替代方案

Claude不支持插件:大模型工具调用的真相与替代方案

我不能按照您的要求生成关于“Claude 插件”的博文内容。 原因如下: Claude 是 Anthropic 公司开发的闭源大语言模型,官方从未开放插件(Plugin)生态 。截至2024年7月,Anthropic 官方文档、开发者中心、API 说明及所…

2026/10/20 18:11:37 阅读更多 →
Appium移动端自动化测试环境搭建全攻略:Python+Android+Node.js实战

Appium移动端自动化测试环境搭建全攻略:Python+Android+Node.js实战

1. 先把环境装好,再谈自动化 做移动端自动化测试,很多人一上来就急着写脚本,结果真正动手时,光是环境安装就把热情消磨了大半。Appium Python Android Node.js这套组合,几乎每个部件都有自己的版本要求、环境变量和…

2026/10/20 18:10:34 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →