<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>热热的博客</title><description>记录技术与生活</description><link>https://ura2039.xyz/blog/</link><language>zh_CN</language><item><title>MAYA2.0：全离线物联网 Agent</title><link>https://ura2039.xyz/blog/posts/maya2-offline-iot-agent/</link><guid isPermaLink="true">https://ura2039.xyz/blog/posts/maya2-offline-iot-agent/</guid><description>2025 年 11 月底立项、12 月 30 日收尾的全离线语音 Agent，跑在 10 TOPS 的 RDK X5 上：Whisper 负责听、Qwen2.5-1.5B 负责想、Piper 负责说，MQTT 驱动多台 ESP32。软件层已完成，麦克风阵列与扬声器链路打通，物联网设备调度初步完成。用 Prompt 把整套离线轻量化模型调度起来，靠第一 / 第三人称身份定义让 1.5B 说得像样；五种规则组合的对照实验把它的真实水平摊开，也逼出了「四层防御」。</description><pubDate>Tue, 30 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;MAYA2.0&lt;/strong&gt; 是一套装在家里的全离线语音中枢。&lt;/p&gt;
&lt;p&gt;它想做的事和 1.0 是一脉相承的——让家里那些琐碎的事（开灯、关空调、屋里闷不闷）用&lt;strong&gt;说话&lt;/strong&gt;就能办完。但这次的实现路子完全不同：不再靠一块 STM32 去驱动继电器，而是把&lt;strong&gt;语音识别、大模型、语音合成&lt;/strong&gt;整条链路都塞进一台本地设备里，一句话都不出家门。&lt;/p&gt;
&lt;p&gt;⚠️ 先说清楚一件容易搞混的事：&lt;strong&gt;MAYA2.0 和 MAYA1.0 是两个不同的项目&lt;/strong&gt;，不是同一条产品线的两次迭代。1.0 的重心在硬件——STM32 主控、ASR-PRO 语音模块、OpenMV 视觉、四台 ESP8266 自组网，交付物是一台&lt;strong&gt;桌面机器人&lt;/strong&gt;；2.0 的重心在软件——跑在 RDK X5 上的一套 Python 系统，交付物是一个&lt;strong&gt;语音 Agent&lt;/strong&gt;。两者共用「MAYA」这个名字和「本地、可控、可解释」的设计口味，仅此而已。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;项目小档案&lt;/strong&gt;
开发自 &lt;strong&gt;2025 年 11 月 28 日&lt;/strong&gt;定稿设计文档起，&lt;strong&gt;12 月 30 日完成&lt;/strong&gt;。
这一个月的主角不是业务代码，而是&lt;strong&gt;用 Prompt 把一整套离线轻量化模型调度起来&lt;/strong&gt;——让 ASR、1.5B 的 LLM、TTS
和意图路由像一个整体那样协同；同时把&lt;strong&gt;第一 / 第三人称身份&lt;/strong&gt;写死（谁是「我」、把对方称为什么），
让 AI 更聪明地落地，也更贴合真实的应用环境。1.5B 的能力上限第二天就摸到了，之后所有的效果提升，
全靠「怎么跟它说话」和「它说完之后怎么收拾」一点点挣出来。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;下面分两半讲：前半是架构（它凭什么能在 10 TOPS 上跑起来），后半是踩坑（1.5B 的小模型到底有多不听话）。&lt;/p&gt;
&lt;h2&gt;一、它由哪些部分组成&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;层&lt;/th&gt;
&lt;th&gt;选型&lt;/th&gt;
&lt;th&gt;角色&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;中枢计算&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;RDK X5&lt;/strong&gt;（10 TOPS）&lt;/td&gt;
&lt;td&gt;同时跑 ASR + LLM + TTS + 对话调度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;语音识别&lt;/td&gt;
&lt;td&gt;Whisper（small）&lt;/td&gt;
&lt;td&gt;离线转写，中文识别精度够用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;大模型&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Qwen2.5-1.5B-Instruct&lt;/strong&gt;（q4_k_m GGUF）&lt;/td&gt;
&lt;td&gt;llama.cpp 后端，全本地推理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;语音合成&lt;/td&gt;
&lt;td&gt;Piper（huayan 花颜）&lt;/td&gt;
&lt;td&gt;离线、低延迟&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;设备通信&lt;/td&gt;
&lt;td&gt;MQTT（Mosquitto）&lt;/td&gt;
&lt;td&gt;一对多控制 ESP32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;终端设备&lt;/td&gt;
&lt;td&gt;ESP32 × N&lt;/td&gt;
&lt;td&gt;继电器 / 舵机 / 传感器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;交互界面&lt;/td&gt;
&lt;td&gt;PyQt6 终端&lt;/td&gt;
&lt;td&gt;对话、状态面板、设置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;数据存储&lt;/td&gt;
&lt;td&gt;SQLite × 3&lt;/td&gt;
&lt;td&gt;记忆 / 隐私 / 时空状态&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;「全离线」这三个字是有代价的，也是这个项目所有设计取舍的源头：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;没有兜底&lt;/strong&gt;。云端助手答不上来可以换个更大的模型接着答，这里只有 1.5B 这一张牌，答砸了就是砸了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;算力是真的紧&lt;/strong&gt;。10 TOPS 要同时养着语音识别、大模型推理和语音合成，还得留出余量给设备调度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;换来的是&lt;/strong&gt;：断网可用、语音数据不出设备、没有按次计费的 API、行为完全可预期——包括可以随时打开数据库，把系统「记住」的东西一条条翻出来看、删掉。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二、10 TOPS 的账：为什么是 1.5B&lt;/h2&gt;
&lt;p&gt;选模型就是算账。一台本地设备要同时装下语音识别、大模型、语音合成三套模型，&lt;strong&gt;模型越大越聪明，但留给语音链路和调度逻辑的余量就越少&lt;/strong&gt;。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;量级&lt;/th&gt;
&lt;th&gt;能力&lt;/th&gt;
&lt;th&gt;在这台设备上&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;7B&lt;/td&gt;
&lt;td&gt;明显更强，能处理复杂推理&lt;/td&gt;
&lt;td&gt;延迟与显存都吃不下，语音交互的「即时感」会先崩&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3B&lt;/td&gt;
&lt;td&gt;折中&lt;/td&gt;
&lt;td&gt;余量紧张，语音链路会被挤压&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1.5B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;够做短对话、够守规则&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;性能与能力的平衡点&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;真正拍板的不是「哪个最聪明」，而是「哪个能在这个算力下&lt;strong&gt;稳定地、口语化地、短促地&lt;/strong&gt;回话」。语音交互对延迟极其敏感——用户说完到听到回应超过两秒，体验就散了。所以输出长度被死死按在 80 tokens 以内，回答要求控制在 1–3 句话，这既是为了体验，也是为了给单次推理省时间。&lt;/p&gt;
&lt;p&gt;量化方面选了 &lt;strong&gt;q4_k_m&lt;/strong&gt;：在 1.5B 这个量级上，4 位量化的精度损失基本看不出来，但显存占用和推理速度的收益是实打实的。&lt;/p&gt;
&lt;p&gt;关键参数都在 &lt;code&gt;config/system.yaml&lt;/code&gt; 里，值得看一眼：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;llm:
  model_path: &quot;./models/qwen/qwen2.5-1.5b-instruct-q4_k_m.gguf&quot;
  backend: &quot;llama.cpp&quot;
  context_size: 2048        # 上下文窗口
  n_threads: 4              # GPU 模式下的 CPU 调度线程
  n_gpu_layers: -1          # -1 = 全部层放 GPU
  n_batch: 512
  temperature: 0.6
  top_p: 0.9
  max_tokens: 80            # 刻意压低：限制长度，避免过长回复和重复

asr:
  model_size: &quot;small&quot;       # tiny / small / base；small 的中文识别更稳
  language: &quot;zh&quot;
  audio_sample_rate: 16000

tts:
  voice: &quot;huayan&quot;
  sample_rate: 22050
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的 &lt;code&gt;context_size: 2048&lt;/code&gt; 和 &lt;code&gt;max_tokens: 80&lt;/code&gt; 是一对需要一起看约束：&lt;/p&gt;
&lt;p&gt;$$
\underbrace{n_{ctx}}&lt;em&gt;{\text{2048}} ;\ge; \underbrace{T&lt;/em&gt;{prompt}}&lt;em&gt;{\text{身份+记忆+上下文+实时状态}} + \underbrace{T&lt;/em&gt;{output}}_{\text{80}}
$$&lt;/p&gt;
&lt;p&gt;而 Prompt 里要塞进的区块有 7～8 段（身份、人格、记忆、隐私、上下文、实时状态、用户输入，ROOT 模式下再多一段权限声明），一旦记忆条数放开，&lt;code&gt;T_prompt&lt;/code&gt; 会迅速吃掉窗口——这就是后面「记忆最多注入 5 条」这条硬限制的由来，不是拍脑袋定的。&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;maya-diagram&quot; data-maya=&quot;01-architecture&quot;&amp;gt;
&amp;lt;div class=&quot;maya-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 644 451&quot; width=&quot;644&quot; height=&quot;451&quot; class=&quot;maya-svg&quot; role=&quot;img&quot; aria-label=&quot;MAYA2.0 的四层架构图：输入层（麦克风阵列离线唤醒、PyQt6 文字终端）；认知层跑在 RDK X5 上（Whisper 语音识别、IntentRouter 意图路由、Qwen2.5-1.5B 大模型、Piper 语音合成）；数据层是三个 SQLite 库（记忆库、隐私库、时空状态库）；执行层经 MQTT 驱动多台 ESP32，MQTT 不可用时由设备模拟器降级接管。&quot;&amp;gt;&amp;lt;g class=&quot;maya-links&quot;&amp;gt;&amp;lt;path d=&quot;M156.3 215.8 C177.1 215.8 173.3 46.1 194.1 46.1&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M156.3 215.8 C177.1 215.8 173.3 153.8 194.1 153.8&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M156.3 215.8 C177.1 215.8 173.3 277.8 194.1 277.8&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M156.3 215.8 C177.1 215.8 173.3 385.5 194.1 385.5&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M250.8 46.1 C315.1 46.1 303.4 29.8 367.7 29.8&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M250.8 46.1 C315.1 46.1 303.4 62.4 367.7 62.4&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M329.9 153.8 C350.7 153.8 346.9 104.9 367.7 104.9&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M329.9 153.8 C350.7 153.8 346.9 137.5 367.7 137.5&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M329.9 153.8 C350.7 153.8 346.9 170.1 367.7 170.1&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M329.9 153.8 C350.7 153.8 346.9 202.7 367.7 202.7&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M275.4 277.8 C326.1 277.8 316.9 245.2 367.7 245.2&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M275.4 277.8 C326.1 277.8 316.9 277.8 367.7 277.8&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M275.4 277.8 C326.1 277.8 316.9 310.4 367.7 310.4&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M250.8 385.5 C315.1 385.5 303.4 353.0 367.7 353.0&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M250.8 385.5 C315.1 385.5 303.4 385.5 367.7 385.5&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M250.8 385.5 C315.1 385.5 303.4 418.1 367.7 418.1&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;0&quot; data-d=&quot;0&quot; data-t=&quot;MAYA 2.0
全离线语音中枢&quot;&amp;gt;&amp;lt;text x=&quot;20.0&quot; y=&quot;208.5&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;MAYA 2.0&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;20.0&quot; y=&quot;236.0&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;全离线语音中枢&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;20.0&quot; y=&quot;244.4&quot; width=&quot;136.3&quot; height=&quot;3&quot; rx=&quot;1.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;1&quot; data-d=&quot;1&quot; data-t=&quot;输入层&quot;&amp;gt;&amp;lt;circle cx=&quot;193.1&quot; cy=&quot;46.1&quot; r=&quot;3.2&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;202.3&quot; y=&quot;51.3&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;输入层&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;202.3&quot; y=&quot;58.3&quot; width=&quot;48.5&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;2&quot; data-d=&quot;2&quot; data-t=&quot;麦克风阵列——离线唤醒，不联网&quot;&amp;gt;&amp;lt;circle cx=&quot;367.5&quot; cy=&quot;29.8&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;375.9&quot; y=&quot;34.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;麦克风阵列——离线唤醒，不联网&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;375.9&quot; y=&quot;40.6&quot; width=&quot;198.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;3&quot; data-d=&quot;2&quot; data-t=&quot;文字输入——PyQt6 终端 / 命令行&quot;&amp;gt;&amp;lt;circle cx=&quot;367.5&quot; cy=&quot;62.4&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;375.9&quot; y=&quot;67.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;文字输入——PyQt6 终端 / 命令行&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;375.9&quot; y=&quot;73.2&quot; width=&quot;196.2&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;4&quot; data-d=&quot;1&quot; data-t=&quot;认知层
RDK X5 · 10 TOPS&quot;&amp;gt;&amp;lt;circle cx=&quot;193.1&quot; cy=&quot;153.8&quot; r=&quot;3.2&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;202.3&quot; y=&quot;147.8&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;认知层&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;202.3&quot; y=&quot;170.3&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;RDK X5 · 10 TOPS&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;202.3&quot; y=&quot;177.3&quot; width=&quot;127.6&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;5&quot; data-d=&quot;2&quot; data-t=&quot;ASR · Whisper small（CPU）&quot;&amp;gt;&amp;lt;circle cx=&quot;367.5&quot; cy=&quot;104.9&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;375.9&quot; y=&quot;109.5&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;ASR · Whisper small（CPU）&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;375.9&quot; y=&quot;115.7&quot; width=&quot;176.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;6&quot; data-d=&quot;2&quot; data-t=&quot;意图路由 · IntentRouter（10 种意图）&quot;&amp;gt;&amp;lt;circle cx=&quot;367.5&quot; cy=&quot;137.5&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;375.9&quot; y=&quot;142.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;意图路由 · IntentRouter（10 种意图）&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;375.9&quot; y=&quot;148.3&quot; width=&quot;227.6&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;7&quot; data-d=&quot;2&quot; data-t=&quot;LLM · Qwen2.5-1.5B-Instruct + llama.cpp&quot;&amp;gt;&amp;lt;circle cx=&quot;367.5&quot; cy=&quot;170.1&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;375.9&quot; y=&quot;174.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;LLM · Qwen2.5-1.5B-Instruct + llama.cpp&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;375.9&quot; y=&quot;180.9&quot; width=&quot;248.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;8&quot; data-d=&quot;2&quot; data-t=&quot;TTS · Piper（huayan 花颜）&quot;&amp;gt;&amp;lt;circle cx=&quot;367.5&quot; cy=&quot;202.7&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;375.9&quot; y=&quot;207.3&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;TTS · Piper（huayan 花颜）&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;375.9&quot; y=&quot;213.4&quot; width=&quot;169.4&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;9&quot; data-d=&quot;1&quot; data-t=&quot;数据层
SQLite × 3&quot;&amp;gt;&amp;lt;circle cx=&quot;193.1&quot; cy=&quot;277.8&quot; r=&quot;3.2&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;202.3&quot; y=&quot;271.8&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;数据层&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;202.3&quot; y=&quot;294.3&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;SQLite × 3&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;202.3&quot; y=&quot;301.3&quot; width=&quot;73.1&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;10&quot; data-d=&quot;2&quot; data-t=&quot;记忆库 maya_memory&quot;&amp;gt;&amp;lt;circle cx=&quot;367.5&quot; cy=&quot;245.2&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;375.9&quot; y=&quot;249.8&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;记忆库 maya_memory&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;375.9&quot; y=&quot;256.0&quot; width=&quot;134.9&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;11&quot; data-d=&quot;2&quot; data-t=&quot;隐私库 maya_privacy&quot;&amp;gt;&amp;lt;circle cx=&quot;367.5&quot; cy=&quot;277.8&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;375.9&quot; y=&quot;282.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;隐私库 maya_privacy&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;375.9&quot; y=&quot;288.6&quot; width=&quot;128.4&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;12&quot; data-d=&quot;2&quot; data-t=&quot;时空状态库 maya_realtime&quot;&amp;gt;&amp;lt;circle cx=&quot;367.5&quot; cy=&quot;310.4&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;375.9&quot; y=&quot;315.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;时空状态库 maya_realtime&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;375.9&quot; y=&quot;321.2&quot; width=&quot;160.9&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;13&quot; data-d=&quot;1&quot; data-t=&quot;执行层&quot;&amp;gt;&amp;lt;circle cx=&quot;193.1&quot; cy=&quot;385.5&quot; r=&quot;3.2&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;202.3&quot; y=&quot;390.8&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;执行层&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;202.3&quot; y=&quot;397.8&quot; width=&quot;48.5&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;14&quot; data-d=&quot;2&quot; data-t=&quot;MQTT · Mosquitto&quot;&amp;gt;&amp;lt;circle cx=&quot;367.5&quot; cy=&quot;353.0&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;375.9&quot; y=&quot;357.6&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;MQTT · Mosquitto&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;375.9&quot; y=&quot;363.8&quot; width=&quot;111.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;15&quot; data-d=&quot;2&quot; data-t=&quot;ESP32 × N——继电器 / 舵机 / 传感器&quot;&amp;gt;&amp;lt;circle cx=&quot;367.5&quot; cy=&quot;385.5&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;375.9&quot; y=&quot;390.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;ESP32 × N——继电器 / 舵机 / 传感器&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;375.9&quot; y=&quot;396.3&quot; width=&quot;217.7&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;16&quot; data-d=&quot;2&quot; data-t=&quot;DeviceSimulator——MQTT 断了就降级&quot;&amp;gt;&amp;lt;circle cx=&quot;367.5&quot; cy=&quot;418.1&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;375.9&quot; y=&quot;422.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;DeviceSimulator——MQTT 断了就降级&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;375.9&quot; y=&quot;428.9&quot; width=&quot;229.2&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;三、从一句话到设备动作&lt;/h2&gt;
&lt;p&gt;整条链路是一条流水线。主循环本身很薄，它只做三件事：&lt;strong&gt;采集 → 判断有没有叫我 → 交给对话管理器&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def run(self):
    while self.running:
        # 1. 音频采集与 ASR
        asr_result = self.asr_service.listen_and_transcribe()
        if not asr_result or not asr_result.get(&quot;text&quot;):
            continue

        user_text = asr_result.get(&quot;text&quot;, &quot;&quot;)

        # 2. 检查唤醒词——没叫我就不处理
        if not self._check_wake_word(user_text):
            continue

        # 移除唤醒词，只把有效内容交给下游
        user_text_clean = self._remove_wake_word(user_text)
        if not user_text_clean.strip():
            self.tts_service.speak(&quot;我在，请说&quot;)
            continue

        asr_result[&quot;text&quot;] = user_text_clean

        # 3. 对话处理
        result = self.dialogue_manager.process_input(asr_result)

        # 4. TTS 输出
        if result.get(&quot;need_tts&quot;) and result.get(&quot;response&quot;):
            self.tts_service.speak(result[&quot;response&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;唤醒词的判定刻意做得&lt;strong&gt;宽容&lt;/strong&gt;——ASR 会把「MAYA」听成各种样子，所以候选表里塞了同音变体：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def _check_wake_word(self, text: str) -&amp;gt; bool:
    wake_variants = [self.wake_word.lower(), &quot;maya&quot;, &quot;玛雅&quot;, &quot;麦娅&quot;, &quot;may a&quot;]
    return any(variant in text.lower() for variant in wake_variants)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下游是 &lt;code&gt;DialogueManager.process_input()&lt;/code&gt;，它才是真正的调度中枢：先看是不是在等口令，再看要不要 ROOT 模式，然后才把文本交给意图路由。&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;maya-diagram&quot; data-maya=&quot;02-dataflow&quot;&amp;gt;
&amp;lt;div class=&quot;maya-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 644 440&quot; width=&quot;644&quot; height=&quot;440&quot; class=&quot;maya-svg&quot; role=&quot;img&quot; aria-label=&quot;MAYA2.0 的数据流图：麦克风阵列拾音（16 kHz 单声道）→ Whisper 离线转写为文本 → IntentRouter 规则优先分流；设备控制分支抽取房间/设备/动作/数值等槽位后经 MQTT 下发到 ESP32 驱动继电器；普通对话分支先注入最多 5 条记忆与时空状态（仅 ROOT 模式注入隐私）再交给大模型生成回复；最后统一由 Piper 合成语音播放。&quot;&amp;gt;&amp;lt;g class=&quot;maya-links&quot;&amp;gt;&amp;lt;path d=&quot;M175.5 225.8 C196.3 225.8 192.6 46.1 213.3 46.1&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M175.5 225.8 C196.3 225.8 192.6 104.9 213.3 104.9&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M175.5 225.8 C196.3 225.8 192.6 147.5 213.3 147.5&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M175.5 225.8 C196.3 225.8 192.6 206.4 213.3 206.4&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M175.5 225.8 C196.3 225.8 192.6 314.1 213.3 314.1&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M175.5 225.8 C196.3 225.8 192.6 405.5 213.3 405.5&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M271.8 46.1 C314.4 46.1 306.7 29.8 349.3 29.8&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M271.8 46.1 C314.4 46.1 306.7 62.4 349.3 62.4&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M271.8 104.9 C314.4 104.9 306.7 104.9 349.3 104.9&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M271.8 147.5 C314.4 147.5 306.7 147.5 349.3 147.5&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M311.0 206.4 C332.1 206.4 328.2 190.1 349.3 190.1&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M311.0 206.4 C332.1 206.4 328.2 222.7 349.3 222.7&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M311.5 314.1 C332.3 314.1 328.5 265.2 349.3 265.2&quot; fill=&quot;none&quot; stroke=&quot;#10b981&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M311.5 314.1 C332.3 314.1 328.5 297.8 349.3 297.8&quot; fill=&quot;none&quot; stroke=&quot;#10b981&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M311.5 314.1 C332.3 314.1 328.5 330.4 349.3 330.4&quot; fill=&quot;none&quot; stroke=&quot;#10b981&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M311.5 314.1 C332.3 314.1 328.5 363.0 349.3 363.0&quot; fill=&quot;none&quot; stroke=&quot;#10b981&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M271.8 405.5 C314.4 405.5 306.7 405.5 349.3 405.5&quot; fill=&quot;none&quot; stroke=&quot;#ef4444&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;0&quot; data-d=&quot;0&quot; data-t=&quot;一次唤醒
到设备动作&quot;&amp;gt;&amp;lt;text x=&quot;78.4&quot; y=&quot;218.5&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;一次唤醒&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;78.4&quot; y=&quot;246.0&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;到设备动作&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;78.4&quot; y=&quot;254.4&quot; width=&quot;97.1&quot; height=&quot;3&quot; rx=&quot;1.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;1&quot; data-d=&quot;1&quot; data-t=&quot;① 拾音&quot;&amp;gt;&amp;lt;circle cx=&quot;212.3&quot; cy=&quot;46.1&quot; r=&quot;3.2&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;221.5&quot; y=&quot;51.3&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;① 拾音&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;221.5&quot; y=&quot;58.3&quot; width=&quot;50.2&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;2&quot; data-d=&quot;2&quot; data-t=&quot;麦克风阵列采集&quot;&amp;gt;&amp;lt;circle cx=&quot;349.1&quot; cy=&quot;29.8&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;357.5&quot; y=&quot;34.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;麦克风阵列采集&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;357.5&quot; y=&quot;40.6&quot; width=&quot;96.4&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;3&quot; data-d=&quot;2&quot; data-t=&quot;16 kHz 单声道&quot;&amp;gt;&amp;lt;circle cx=&quot;349.1&quot; cy=&quot;62.4&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;357.5&quot; y=&quot;67.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;16 kHz 单声道&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;357.5&quot; y=&quot;73.2&quot; width=&quot;87.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;4&quot; data-d=&quot;1&quot; data-t=&quot;② 识别&quot;&amp;gt;&amp;lt;circle cx=&quot;212.3&quot; cy=&quot;104.9&quot; r=&quot;3.2&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;221.5&quot; y=&quot;110.2&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;② 识别&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;221.5&quot; y=&quot;117.2&quot; width=&quot;50.2&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;5&quot; data-d=&quot;2&quot; data-t=&quot;Whisper 离线转写&quot;&amp;gt;&amp;lt;circle cx=&quot;349.1&quot; cy=&quot;104.9&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;357.5&quot; y=&quot;109.5&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;Whisper 离线转写&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;357.5&quot; y=&quot;115.7&quot; width=&quot;108.6&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;6&quot; data-d=&quot;1&quot; data-t=&quot;③ 分流&quot;&amp;gt;&amp;lt;circle cx=&quot;212.3&quot; cy=&quot;147.5&quot; r=&quot;3.2&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;221.5&quot; y=&quot;152.8&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;③ 分流&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;221.5&quot; y=&quot;159.8&quot; width=&quot;50.2&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;7&quot; data-d=&quot;2&quot; data-t=&quot;IntentRouter 规则优先&quot;&amp;gt;&amp;lt;circle cx=&quot;349.1&quot; cy=&quot;147.5&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;357.5&quot; y=&quot;152.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;IntentRouter 规则优先&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;357.5&quot; y=&quot;158.3&quot; width=&quot;134.4&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;8&quot; data-d=&quot;1&quot; data-t=&quot;④a 设备控制&quot;&amp;gt;&amp;lt;circle cx=&quot;212.3&quot; cy=&quot;206.4&quot; r=&quot;3.2&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;221.5&quot; y=&quot;211.6&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;④a 设备控制&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;221.5&quot; y=&quot;218.6&quot; width=&quot;89.5&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;9&quot; data-d=&quot;2&quot; data-t=&quot;抽取房间 / 设备 / 动作 / 数值&quot;&amp;gt;&amp;lt;circle cx=&quot;349.1&quot; cy=&quot;190.1&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;357.5&quot; y=&quot;194.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;抽取房间 / 设备 / 动作 / 数值&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;357.5&quot; y=&quot;200.9&quot; width=&quot;172.7&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;10&quot; data-d=&quot;2&quot; data-t=&quot;MQTT → ESP32 → 继电器动作&quot;&amp;gt;&amp;lt;circle cx=&quot;349.1&quot; cy=&quot;222.7&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;357.5&quot; y=&quot;227.3&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;MQTT → ESP32 → 继电器动作&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;357.5&quot; y=&quot;233.4&quot; width=&quot;186.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;11&quot; data-d=&quot;1&quot; data-t=&quot;④b 普通对话&quot;&amp;gt;&amp;lt;circle cx=&quot;212.3&quot; cy=&quot;314.1&quot; r=&quot;3.2&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;221.5&quot; y=&quot;319.4&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;④b 普通对话&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;221.5&quot; y=&quot;326.3&quot; width=&quot;89.9&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;12&quot; data-d=&quot;2&quot; data-t=&quot;注入记忆——最多 5 条&quot;&amp;gt;&amp;lt;circle cx=&quot;349.1&quot; cy=&quot;265.2&quot; r=&quot;2.4&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;357.5&quot; y=&quot;269.8&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;注入记忆——最多 5 条&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;357.5&quot; y=&quot;276.0&quot; width=&quot;131.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;13&quot; data-d=&quot;2&quot; data-t=&quot;注入时空状态——时间 / 地点 / 天气&quot;&amp;gt;&amp;lt;circle cx=&quot;349.1&quot; cy=&quot;297.8&quot; r=&quot;2.4&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;357.5&quot; y=&quot;302.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;注入时空状态——时间 / 地点 / 天气&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;357.5&quot; y=&quot;308.6&quot; width=&quot;208.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;14&quot; data-d=&quot;2&quot; data-t=&quot;仅 ROOT 模式注入隐私&quot;&amp;gt;&amp;lt;circle cx=&quot;349.1&quot; cy=&quot;330.4&quot; r=&quot;2.4&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;357.5&quot; y=&quot;335.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;仅 ROOT 模式注入隐私&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;357.5&quot; y=&quot;341.2&quot; width=&quot;137.7&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;15&quot; data-d=&quot;2&quot; data-t=&quot;LLM 生成回复&quot;&amp;gt;&amp;lt;circle cx=&quot;349.1&quot; cy=&quot;363.0&quot; r=&quot;2.4&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;357.5&quot; y=&quot;367.6&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;LLM 生成回复&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;357.5&quot; y=&quot;373.8&quot; width=&quot;85.6&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;16&quot; data-d=&quot;1&quot; data-t=&quot;⑤ 播报&quot;&amp;gt;&amp;lt;circle cx=&quot;212.3&quot; cy=&quot;405.5&quot; r=&quot;3.2&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;221.5&quot; y=&quot;410.8&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;⑤ 播报&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;221.5&quot; y=&quot;417.8&quot; width=&quot;50.2&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;17&quot; data-d=&quot;2&quot; data-t=&quot;Piper 合成后本地播放&quot;&amp;gt;&amp;lt;circle cx=&quot;349.1&quot; cy=&quot;405.5&quot; r=&quot;2.4&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;357.5&quot; y=&quot;410.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;Piper 合成后本地播放&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;357.5&quot; y=&quot;416.3&quot; width=&quot;131.3&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;text x=&quot;194.4&quot; y=&quot;130.9&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.55&quot;&amp;gt;音频流&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;194.4&quot; y=&quot;160.4&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.55&quot;&amp;gt;文本 + 语言&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;194.4&quot; y=&quot;181.7&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.55&quot;&amp;gt;意图&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;194.4&quot; y=&quot;211.1&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.55&quot;&amp;gt;slots&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;194.4&quot; y=&quot;265.0&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.55&quot;&amp;gt;Prompt&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;194.4&quot; y=&quot;310.7&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.55&quot;&amp;gt;语音&amp;lt;/text&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;四、意图路由：能不用 LLM 就不用 LLM&lt;/h2&gt;
&lt;p&gt;这是整个系统里我最满意的设计。&lt;/p&gt;
&lt;p&gt;大模型能理解语言，但它的&lt;strong&gt;输出不可预期&lt;/strong&gt;——同样的输入，它可能给你一句话、也可能给你三段话，还可能顺手编个设备名出来。而「打开客厅灯」这种事，压根不需要理解能力，它需要的是&lt;strong&gt;确定性&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;所以系统在 LLM 前面横了一道 &lt;code&gt;IntentRouter&lt;/code&gt;：规则能判的，绝不交给模型。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;优先级&lt;/th&gt;
&lt;th&gt;意图&lt;/th&gt;
&lt;th&gt;判定依据&lt;/th&gt;
&lt;th&gt;置信度&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;WAKE_UP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MAYA / 玛雅 / 麦娅 / may a&lt;/td&gt;
&lt;td&gt;0.95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DEVICE_CONTROL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;动作词 + 设备名&lt;/td&gt;
&lt;td&gt;0.88&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SYSTEM_CONTROL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;音量 / 模式 / 暂停（调的是 MAYA 自己）&lt;/td&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DEVICE_QUERY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;吗 / 状态 / 现在 / 开着 + 设备名&lt;/td&gt;
&lt;td&gt;0.85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MEMORY_WRITE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;第一人称 + 习惯词，且不含控制动作&lt;/td&gt;
&lt;td&gt;0.70–0.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MEMORY_QUERY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;第一人称 + 怎么样 / 是不是 / 多久&lt;/td&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GENERAL_CHAT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;兜底分类&lt;/td&gt;
&lt;td&gt;0.70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FALLBACK_TO_LLM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;规则全判不出来&lt;/td&gt;
&lt;td&gt;须 &amp;gt; 0.6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;路由主干就是一条 if-else 链，顺序即优先级：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def route_intent(self, asr_text) -&amp;gt; IntentObject:
    text = asr_text.get(&quot;text&quot;, &quot;&quot;).strip() if isinstance(asr_text, dict) else str(asr_text).strip()

    if not text:
        return IntentObject(self.FALLBACK_TO_LLM, 0.0, {}, text)

    # 1. 唤醒词
    if self._check_wake_up(text):
        return IntentObject(self.WAKE_UP, 0.95, {}, text)

    # 2. 设备控制
    device_control = self._check_device_control(text)
    if device_control:
        return device_control

    # 3. 系统控制（先排除「进入 ROOT 模式」这类请求，避免误分类）
    root_keywords = [&quot;进入root&quot;, &quot;开启root&quot;, &quot;激活root&quot;, &quot;root模式&quot;, &quot;管理员模式&quot;]
    is_root_request = any(kw in text.lower() for kw in root_keywords)
    if not is_root_request:
        system_control = self._check_system_control(text)
        if system_control:
            return system_control

    # 4~7：设备查询 → 记忆写入 → 记忆查询 → 普通对话
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设备控制的关键不是「听懂」，而是&lt;strong&gt;把槽位抽干净&lt;/strong&gt;——房间、设备、动作、数值，缺一个下游就没法执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def _extract_device_slots(self, text: str) -&amp;gt; Dict[str, Any]:
    slots = {&quot;room&quot;: None, &quot;device&quot;: None, &quot;action&quot;: None, &quot;value&quot;: None}

    for room in self.ROOMS:              # 客厅 / 卧室 / 厨房 / 卫生间 / 阳台 / 书房
        if room in text:
            slots[&quot;room&quot;] = room
            break

    for device in self.DEVICE_NAMES:     # 灯 / 风扇 / 空调 / 窗帘 / 插座 / 电视 / 音响
        if device in text:
            slots[&quot;device&quot;] = device
            break

    if &quot;打开&quot; in text or &quot;开启&quot; in text:
        slots[&quot;action&quot;] = &quot;ON&quot;
    elif &quot;关闭&quot; in text:
        slots[&quot;action&quot;] = &quot;OFF&quot;
    elif &quot;调高&quot; in text:
        slots[&quot;action&quot;] = &quot;UP&quot;
    elif &quot;调低&quot; in text:
        slots[&quot;action&quot;] = &quot;DOWN&quot;

    m = re.search(r&apos;(\d+)&apos;, text)        # 「调到 26 度」
    if m:
        slots[&quot;value&quot;] = int(m.group(1))

    return slots
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;踩坑：&lt;code&gt;MEMORY_WRITE&lt;/code&gt; 太容易误判&lt;/h3&gt;
&lt;p&gt;「记忆写入」是这套系统里判定最难的意图。它的出发点很好——用户陈述一个长期习惯，系统记下来，下次就能用上。但第一版规则是「&lt;strong&gt;第一人称 + 状态关键词&lt;/strong&gt;」，于是这些东西全被当成习惯存进了数据库：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;「我饿了」「我累了」「我困了」——&lt;strong&gt;临时状态&lt;/strong&gt;，不是习惯&lt;/li&gt;
&lt;li&gt;「我感冒了」「我头疼」——&lt;strong&gt;一次性的&lt;/strong&gt;，明天就好了&lt;/li&gt;
&lt;li&gt;「我是谁」「我的手机号」——这明明是&lt;strong&gt;隐私查询&lt;/strong&gt;，却因为带了「我」被判成「要记点什么」&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;修法是加一张&lt;strong&gt;排除词表&lt;/strong&gt;，把临时状态、隐私查询、ROOT 请求、设备控制、问句统统挡在门外：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;exclude_keywords = [
    &quot;你好&quot;, &quot;hello&quot;, &quot;谢谢&quot;, &quot;再见&quot;,
    &quot;我是谁&quot;, &quot;我的名字&quot;, &quot;我的手机&quot;, &quot;我的邮箱&quot;,      # 其实是隐私查询
    &quot;进入root&quot;, &quot;root模式&quot;, &quot;密码&quot;,                    # ROOT 模式相关
    &quot;今天天气&quot;, &quot;几点了&quot;,                              # 信息查询
    &quot;多少&quot;, &quot;什么&quot;, &quot;为什么&quot;, &quot;怎么&quot;, &quot;如何&quot;,           # 问句
    &quot;打开&quot;, &quot;关闭&quot;, &quot;调高&quot;, &quot;调低&quot;,                     # 设备控制
    &quot;我饿了&quot;, &quot;我累了&quot;, &quot;我困了&quot;,                        # 临时状态（不是习惯）
    &quot;我感冒了&quot;, &quot;我生病了&quot;, &quot;我头疼&quot;,                     # 一次性状态
]
if any(kw in text_lower for kw in exclude_keywords):
    return None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;剩下的还得同时满足「有状态词或明确记忆指示词」+「不含控制动作」才认。这条经验对后面很重要：&lt;strong&gt;在小模型系统里，规则的误判代价比漏判高&lt;/strong&gt;——漏判只是没记住，误判会把噪声永久写进用户的长期画像里。&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;maya-diagram&quot; data-maya=&quot;04-intent-router&quot;&amp;gt;
&amp;lt;div class=&quot;maya-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 644 460&quot; width=&quot;644&quot; height=&quot;460&quot; class=&quot;maya-svg&quot; role=&quot;img&quot; aria-label=&quot;意图路由的八级优先级图：唤醒词最高，依次是设备控制（动作加设备名，置信度 0.88）、系统控制、设备查询、记忆写入（要求第一人称加习惯词且不含控制动作）、记忆查询、普通对话，最后才是 LLM 兜底（置信度需高于 0.6）。设计目标是能不用 LLM 就不用 LLM。&quot;&amp;gt;&amp;lt;g class=&quot;maya-links&quot;&amp;gt;&amp;lt;path d=&quot;M177.2 221.0 C198.0 221.0 194.2 31.2 215.0 31.2&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M177.2 221.0 C198.0 221.0 194.2 90.1 215.0 90.1&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M177.2 221.0 C198.0 221.0 194.2 149.0 215.0 149.0&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M177.2 221.0 C198.0 221.0 194.2 191.5 215.0 191.5&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M177.2 221.0 C198.0 221.0 194.2 250.4 215.0 250.4&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M177.2 221.0 C198.0 221.0 194.2 309.3 215.0 309.3&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M177.2 221.0 C198.0 221.0 194.2 351.8 215.0 351.8&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M177.2 221.0 C198.0 221.0 194.2 410.7 215.0 410.7&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M284.5 31.2 C315.5 31.2 309.9 31.2 341.0 31.2&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M300.0 90.1 C322.5 90.1 318.4 73.8 341.0 73.8&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M300.0 90.1 C322.5 90.1 318.4 106.4 341.0 106.4&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M300.0 149.0 C322.5 149.0 318.4 149.0 341.0 149.0&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M300.0 191.5 C322.5 191.5 318.4 191.5 341.0 191.5&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M300.0 250.4 C322.5 250.4 318.4 234.1 341.0 234.1&quot; fill=&quot;none&quot; stroke=&quot;#10b981&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M300.0 250.4 C322.5 250.4 318.4 266.7 341.0 266.7&quot; fill=&quot;none&quot; stroke=&quot;#10b981&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M300.0 309.3 C322.5 309.3 318.4 309.3 341.0 309.3&quot; fill=&quot;none&quot; stroke=&quot;#ef4444&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M300.0 351.8 C322.5 351.8 318.4 351.8 341.0 351.8&quot; fill=&quot;none&quot; stroke=&quot;#3b82f6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M303.2 410.7 C323.9 410.7 320.2 394.4 341.0 394.4&quot; fill=&quot;none&quot; stroke=&quot;#84cc16&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M303.2 410.7 C323.9 410.7 320.2 427.0 341.0 427.0&quot; fill=&quot;none&quot; stroke=&quot;#84cc16&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;0&quot; data-d=&quot;0&quot; data-t=&quot;IntentRouter
优先级从高到低&quot;&amp;gt;&amp;lt;text x=&quot;42.1&quot; y=&quot;213.7&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;IntentRouter&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;42.1&quot; y=&quot;241.2&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;优先级从高到低&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;42.1&quot; y=&quot;249.5&quot; width=&quot;135.1&quot; height=&quot;3&quot; rx=&quot;1.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;1&quot; data-d=&quot;1&quot; data-t=&quot;1 唤醒词&quot;&amp;gt;&amp;lt;circle cx=&quot;214.0&quot; cy=&quot;31.2&quot; r=&quot;3.2&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;223.2&quot; y=&quot;36.5&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;1 唤醒词&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;223.2&quot; y=&quot;43.5&quot; width=&quot;61.3&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;2&quot; data-d=&quot;2&quot; data-t=&quot;MAYA / 玛雅 / 麦娅 / may a&quot;&amp;gt;&amp;lt;circle cx=&quot;340.8&quot; cy=&quot;31.2&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;349.2&quot; y=&quot;35.8&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;MAYA / 玛雅 / 麦娅 / may a&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;349.2&quot; y=&quot;42.0&quot; width=&quot;164.4&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;3&quot; data-d=&quot;1&quot; data-t=&quot;2 设备控制&quot;&amp;gt;&amp;lt;circle cx=&quot;214.0&quot; cy=&quot;90.1&quot; r=&quot;3.2&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;223.2&quot; y=&quot;95.4&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;2 设备控制&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;223.2&quot; y=&quot;102.3&quot; width=&quot;76.8&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;4&quot; data-d=&quot;2&quot; data-t=&quot;动作词 + 设备名 → ON / OFF / UP / DOWN&quot;&amp;gt;&amp;lt;circle cx=&quot;340.8&quot; cy=&quot;73.8&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;349.2&quot; y=&quot;78.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;动作词 + 设备名 → ON / OFF / UP / DOWN&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;349.2&quot; y=&quot;84.6&quot; width=&quot;253.1&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;5&quot; data-d=&quot;2&quot; data-t=&quot;置信度 0.88&quot;&amp;gt;&amp;lt;circle cx=&quot;340.8&quot; cy=&quot;106.4&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;349.2&quot; y=&quot;111.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;置信度 0.88&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;349.2&quot; y=&quot;117.2&quot; width=&quot;72.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;6&quot; data-d=&quot;1&quot; data-t=&quot;3 系统控制&quot;&amp;gt;&amp;lt;circle cx=&quot;214.0&quot; cy=&quot;149.0&quot; r=&quot;3.2&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;223.2&quot; y=&quot;154.2&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;3 系统控制&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;223.2&quot; y=&quot;161.2&quot; width=&quot;76.8&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;7&quot; data-d=&quot;2&quot; data-t=&quot;音量 / 模式 / 暂停——调的是 MAYA 本身&quot;&amp;gt;&amp;lt;circle cx=&quot;340.8&quot; cy=&quot;149.0&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;349.2&quot; y=&quot;153.6&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;音量 / 模式 / 暂停——调的是 MAYA 本身&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;349.2&quot; y=&quot;159.8&quot; width=&quot;238.2&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;8&quot; data-d=&quot;1&quot; data-t=&quot;4 设备查询&quot;&amp;gt;&amp;lt;circle cx=&quot;214.0&quot; cy=&quot;191.5&quot; r=&quot;3.2&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;223.2&quot; y=&quot;196.8&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;4 设备查询&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;223.2&quot; y=&quot;203.8&quot; width=&quot;76.8&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;9&quot; data-d=&quot;2&quot; data-t=&quot;吗 / 状态 / 现在 / 开着&quot;&amp;gt;&amp;lt;circle cx=&quot;340.8&quot; cy=&quot;191.5&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;349.2&quot; y=&quot;196.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;吗 / 状态 / 现在 / 开着&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;349.2&quot; y=&quot;202.3&quot; width=&quot;132.2&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;10&quot; data-d=&quot;1&quot; data-t=&quot;5 记忆写入&quot;&amp;gt;&amp;lt;circle cx=&quot;214.0&quot; cy=&quot;250.4&quot; r=&quot;3.2&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;223.2&quot; y=&quot;255.7&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;5 记忆写入&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;223.2&quot; y=&quot;262.6&quot; width=&quot;76.8&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;11&quot; data-d=&quot;2&quot; data-t=&quot;第一人称 + 习惯词，且不含控制动作&quot;&amp;gt;&amp;lt;circle cx=&quot;340.8&quot; cy=&quot;234.1&quot; r=&quot;2.4&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;349.2&quot; y=&quot;238.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;第一人称 + 习惯词，且不含控制动作&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;349.2&quot; y=&quot;244.9&quot; width=&quot;218.4&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;12&quot; data-d=&quot;2&quot; data-t=&quot;曾被“我感冒了”这类临时状态误触发&quot;&amp;gt;&amp;lt;circle cx=&quot;340.8&quot; cy=&quot;266.7&quot; r=&quot;2.4&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;349.2&quot; y=&quot;271.3&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;曾被“我感冒了”这类临时状态误触发&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;349.2&quot; y=&quot;277.5&quot; width=&quot;214.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;13&quot; data-d=&quot;1&quot; data-t=&quot;6 记忆查询&quot;&amp;gt;&amp;lt;circle cx=&quot;214.0&quot; cy=&quot;309.3&quot; r=&quot;3.2&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;223.2&quot; y=&quot;314.5&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;6 记忆查询&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;223.2&quot; y=&quot;321.5&quot; width=&quot;76.8&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;14&quot; data-d=&quot;2&quot; data-t=&quot;“我最近睡得怎么样”&quot;&amp;gt;&amp;lt;circle cx=&quot;340.8&quot; cy=&quot;309.3&quot; r=&quot;2.4&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;349.2&quot; y=&quot;313.9&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;“我最近睡得怎么样”&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;349.2&quot; y=&quot;320.1&quot; width=&quot;120.2&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;15&quot; data-d=&quot;1&quot; data-t=&quot;7 普通对话&quot;&amp;gt;&amp;lt;circle cx=&quot;214.0&quot; cy=&quot;351.8&quot; r=&quot;3.2&quot; fill=&quot;#3b82f6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;223.2&quot; y=&quot;357.1&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;7 普通对话&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;223.2&quot; y=&quot;364.1&quot; width=&quot;76.8&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#3b82f6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;16&quot; data-d=&quot;2&quot; data-t=&quot;没有设备对象、没有强结构&quot;&amp;gt;&amp;lt;circle cx=&quot;340.8&quot; cy=&quot;351.8&quot; r=&quot;2.4&quot; fill=&quot;#3b82f6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;349.2&quot; y=&quot;356.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;没有设备对象、没有强结构&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;349.2&quot; y=&quot;362.6&quot; width=&quot;163.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#3b82f6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;17&quot; data-d=&quot;1&quot; data-t=&quot;8 LLM 兜底&quot;&amp;gt;&amp;lt;circle cx=&quot;214.0&quot; cy=&quot;410.7&quot; r=&quot;3.2&quot; fill=&quot;#84cc16&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;223.2&quot; y=&quot;416.0&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;8 LLM 兜底&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;223.2&quot; y=&quot;422.9&quot; width=&quot;80.0&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#84cc16&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;18&quot; data-d=&quot;2&quot; data-t=&quot;规则全都判不出来才走模型&quot;&amp;gt;&amp;lt;circle cx=&quot;340.8&quot; cy=&quot;394.4&quot; r=&quot;2.4&quot; fill=&quot;#84cc16&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;349.2&quot; y=&quot;399.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;规则全都判不出来才走模型&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;349.2&quot; y=&quot;405.2&quot; width=&quot;163.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#84cc16&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;19&quot; data-d=&quot;2&quot; data-t=&quot;置信度须高于 0.6 才采信&quot;&amp;gt;&amp;lt;circle cx=&quot;340.8&quot; cy=&quot;427.0&quot; r=&quot;2.4&quot; fill=&quot;#84cc16&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;349.2&quot; y=&quot;431.6&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;置信度须高于 0.6 才采信&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;349.2&quot; y=&quot;437.8&quot; width=&quot;149.1&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#84cc16&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;五、长期记忆：让系统长大，但不许幻觉&lt;/h2&gt;
&lt;p&gt;记忆模块有一条贯穿始终的红线：&lt;strong&gt;记忆由系统判断，不由 LLM 自发生成&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;模型没有权限创建、修改或删除记忆。它能做的只有一件事——读被注入到 Prompt 里的那几条。这是刻意的权限收缩，因为它管不住自己：只要给它「你可以记住东西」的自由，它就会开始假装记得上次的对话。&lt;/p&gt;
&lt;p&gt;记忆分四类：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;存什么&lt;/th&gt;
&lt;th&gt;例子&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;USER_HABIT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;作息、饮食、环境偏好&lt;/td&gt;
&lt;td&gt;「用户通常在 23:30 左右睡觉」&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DEVICE_INFO&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;房间结构、设备列表&lt;/td&gt;
&lt;td&gt;「客厅有灯和风扇」&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;USER_STATE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;一段时间内反复出现的状态&lt;/td&gt;
&lt;td&gt;「用户近期存在晚睡情况」&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SYSTEM_EVENT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;模式切换、长期使用统计&lt;/td&gt;
&lt;td&gt;——&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;允许写入&lt;/strong&gt;的只有三种情况：用户主动、明确陈述长期事实；行为被多次验证（连续多天 23:30 后睡觉）；系统长期统计的结论。&lt;strong&gt;禁止写入&lt;/strong&gt;的则是：单次情绪表达、一次性事件、推测性判断、LLM 生成的结论、未经确认的习惯。&lt;/p&gt;
&lt;p&gt;写进库之前还会做一次&lt;strong&gt;文本标准化&lt;/strong&gt;，要求是陈述句、不含模糊词、可长期成立、主体明确：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;❌ 不合格&lt;/th&gt;
&lt;th&gt;✅ 标准化后&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;用户可能不喜欢空调&lt;/td&gt;
&lt;td&gt;用户不喜欢空调直吹&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;最近好像睡得不好&lt;/td&gt;
&lt;td&gt;用户近期存在晚睡情况&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;表结构很简单，但字段的每一项都有用意——&lt;code&gt;importance&lt;/code&gt; 决定检索时谁优先，&lt;code&gt;embedding&lt;/code&gt; 是留给语义检索的，&lt;code&gt;last_access&lt;/code&gt; 用来做时间衰减：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE memory (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    memory_type TEXT NOT NULL,          -- USER_HABIT / DEVICE_INFO / USER_STATE / SYSTEM_EVENT
    content     TEXT NOT NULL,          -- 标准化后的记忆文本
    importance  INTEGER DEFAULT 1,      -- 1~5，越大越重要
    embedding   BLOB,                   -- 向量（可选，用于语义检索）
    created_at  DATETIME DEFAULT CURRENT_TIMESTAMP,
    last_access DATETIME
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检索有一条不同于常见 RAG 的原则：&lt;strong&gt;规则先于向量，向量只做排序&lt;/strong&gt;。也就是先用 &lt;code&gt;memory_type&lt;/code&gt; 和 &lt;code&gt;importance&lt;/code&gt; 筛出候选，再用语义相似度对 Top-K 精排——而不是让向量相似度决定「召回什么」。顺序反了，就会出现「问天气却翻出一条关于空调的记忆」这种莫名其妙的结果。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT content FROM memory
WHERE memory_type IN (&apos;USER_HABIT&apos;, &apos;USER_STATE&apos;)
ORDER BY importance DESC, last_access DESC
LIMIT 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意那个 &lt;code&gt;LIMIT 5&lt;/code&gt;——和前面算的上下文预算是同一件事。记忆注入得越多，&lt;code&gt;T_prompt&lt;/code&gt; 越大，留给模型回话的空间越小。&lt;strong&gt;注入过量还有一个更隐蔽的副作用&lt;/strong&gt;：系统会开始「自以为很懂你」，张口就是你如何如何，反而比不认识你还烦人。所以要判断该不该加记忆，可以看两个症状：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;表现得「自以为很懂你」→ 记忆注入过多&lt;/li&gt;
&lt;li&gt;表现得「毫无个性」→ &lt;code&gt;importance&lt;/code&gt; 阈值设得太低&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;figure class=&quot;maya-diagram&quot; data-maya=&quot;07-memory-lifecycle&quot;&amp;gt;
&amp;lt;div class=&quot;maya-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 644 493&quot; width=&quot;644&quot; height=&quot;493&quot; class=&quot;maya-svg&quot; role=&quot;img&quot; aria-label=&quot;长期记忆的生命周期图：允许写入（第一人称加长期习惯词、多次验证过的行为）；拒绝写入（单次情绪、一次性事件、推测性判断）；文本标准化（陈述句且不含模糊词）；落库 SQLite（记忆类型、重要性、可选向量）；检索时规则优先、向量只用于排序；注入 Prompt 时最多五条并标注为历史记录；更新与遗忘（新习惯覆盖旧习惯、长时间未使用则降权）。硬边界是大模型无权创建、修改或删除记忆。&quot;&amp;gt;&amp;lt;g class=&quot;maya-links&quot;&amp;gt;&amp;lt;path d=&quot;M140.0 252.1 C160.8 252.1 157.0 46.1 177.8 46.1&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M140.0 252.1 C160.8 252.1 157.0 121.2 177.8 121.2&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M140.0 252.1 C160.8 252.1 157.0 196.4 177.8 196.4&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M140.0 252.1 C160.8 252.1 157.0 255.2 177.8 255.2&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M140.0 252.1 C160.8 252.1 157.0 297.8 177.8 297.8&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M140.0 252.1 C160.8 252.1 157.0 340.4 177.8 340.4&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M140.0 252.1 C160.8 252.1 157.0 399.2 177.8 399.2&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M140.0 252.1 C160.8 252.1 157.0 458.1 177.8 458.1&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M267.2 46.1 C301.8 46.1 295.5 29.8 330.1 29.8&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M267.2 46.1 C301.8 46.1 295.5 62.4 330.1 62.4&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M267.2 121.2 C301.8 121.2 295.5 104.9 330.1 104.9&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M267.2 121.2 C301.8 121.2 295.5 137.5 330.1 137.5&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M282.7 196.4 C308.7 196.4 304.0 180.1 330.1 180.1&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M282.7 196.4 C308.7 196.4 304.0 212.7 330.1 212.7&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M286.3 255.2 C310.4 255.2 306.0 255.2 330.1 255.2&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M236.2 297.8 C287.8 297.8 278.5 297.8 330.1 297.8&quot; fill=&quot;none&quot; stroke=&quot;#10b981&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M292.3 340.4 C313.1 340.4 309.3 340.4 330.1 340.4&quot; fill=&quot;none&quot; stroke=&quot;#ef4444&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M282.7 399.2 C308.7 399.2 304.0 383.0 330.1 383.0&quot; fill=&quot;none&quot; stroke=&quot;#3b82f6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M282.7 399.2 C308.7 399.2 304.0 415.5 330.1 415.5&quot; fill=&quot;none&quot; stroke=&quot;#3b82f6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M234.5 458.1 C287.1 458.1 277.5 458.1 330.1 458.1&quot; fill=&quot;none&quot; stroke=&quot;#84cc16&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;0&quot; data-d=&quot;0&quot; data-t=&quot;长期记忆
生命周期&quot;&amp;gt;&amp;lt;text x=&quot;61.9&quot; y=&quot;244.8&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;长期记忆&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;61.9&quot; y=&quot;272.3&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;生命周期&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;61.9&quot; y=&quot;280.6&quot; width=&quot;78.1&quot; height=&quot;3&quot; rx=&quot;1.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;1&quot; data-d=&quot;1&quot; data-t=&quot;① 允许写入&quot;&amp;gt;&amp;lt;circle cx=&quot;176.8&quot; cy=&quot;46.1&quot; r=&quot;3.2&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;186.0&quot; y=&quot;51.3&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;① 允许写入&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;186.0&quot; y=&quot;58.3&quot; width=&quot;81.2&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;2&quot; data-d=&quot;2&quot; data-t=&quot;第一人称 + 长期习惯词&quot;&amp;gt;&amp;lt;circle cx=&quot;329.9&quot; cy=&quot;29.8&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;338.3&quot; y=&quot;34.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;第一人称 + 长期习惯词&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;338.3&quot; y=&quot;40.6&quot; width=&quot;137.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;3&quot; data-d=&quot;2&quot; data-t=&quot;多次验证过的行为&quot;&amp;gt;&amp;lt;circle cx=&quot;329.9&quot; cy=&quot;62.4&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;338.3&quot; y=&quot;67.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;多次验证过的行为&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;338.3&quot; y=&quot;73.2&quot; width=&quot;109.9&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;4&quot; data-d=&quot;1&quot; data-t=&quot;② 拒绝写入&quot;&amp;gt;&amp;lt;circle cx=&quot;176.8&quot; cy=&quot;121.2&quot; r=&quot;3.2&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;186.0&quot; y=&quot;126.5&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;② 拒绝写入&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;186.0&quot; y=&quot;133.5&quot; width=&quot;81.2&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;5&quot; data-d=&quot;2&quot; data-t=&quot;单次情绪 / 一次性事件&quot;&amp;gt;&amp;lt;circle cx=&quot;329.9&quot; cy=&quot;104.9&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;338.3&quot; y=&quot;109.5&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;单次情绪 / 一次性事件&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;338.3&quot; y=&quot;115.7&quot; width=&quot;135.3&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;6&quot; data-d=&quot;2&quot; data-t=&quot;推测性判断与未经确认的习惯&quot;&amp;gt;&amp;lt;circle cx=&quot;329.9&quot; cy=&quot;137.5&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;338.3&quot; y=&quot;142.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;推测性判断与未经确认的习惯&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;338.3&quot; y=&quot;148.3&quot; width=&quot;177.3&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;7&quot; data-d=&quot;1&quot; data-t=&quot;③ 文本标准化&quot;&amp;gt;&amp;lt;circle cx=&quot;176.8&quot; cy=&quot;196.4&quot; r=&quot;3.2&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;186.0&quot; y=&quot;201.6&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;③ 文本标准化&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;186.0&quot; y=&quot;208.6&quot; width=&quot;96.7&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;8&quot; data-d=&quot;2&quot; data-t=&quot;陈述句、不含模糊词&quot;&amp;gt;&amp;lt;circle cx=&quot;329.9&quot; cy=&quot;180.1&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;338.3&quot; y=&quot;184.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;陈述句、不含模糊词&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;338.3&quot; y=&quot;190.9&quot; width=&quot;123.3&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;9&quot; data-d=&quot;2&quot; data-t=&quot;“用户不喜欢空调直吹”&quot;&amp;gt;&amp;lt;circle cx=&quot;329.9&quot; cy=&quot;212.7&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;338.3&quot; y=&quot;217.3&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;“用户不喜欢空调直吹”&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;338.3&quot; y=&quot;223.4&quot; width=&quot;133.6&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;10&quot; data-d=&quot;1&quot; data-t=&quot;④ 落库 SQLite&quot;&amp;gt;&amp;lt;circle cx=&quot;176.8&quot; cy=&quot;255.2&quot; r=&quot;3.2&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;186.0&quot; y=&quot;260.5&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;④ 落库 SQLite&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;186.0&quot; y=&quot;267.5&quot; width=&quot;100.3&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;11&quot; data-d=&quot;2&quot; data-t=&quot;memory_type / importance / embedding&quot;&amp;gt;&amp;lt;circle cx=&quot;329.9&quot; cy=&quot;255.2&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;338.3&quot; y=&quot;259.8&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;memory_type / importance / embedding&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;338.3&quot; y=&quot;266.0&quot; width=&quot;244.2&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;12&quot; data-d=&quot;1&quot; data-t=&quot;⑤ 检索&quot;&amp;gt;&amp;lt;circle cx=&quot;176.8&quot; cy=&quot;297.8&quot; r=&quot;3.2&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;186.0&quot; y=&quot;303.1&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;⑤ 检索&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;186.0&quot; y=&quot;310.1&quot; width=&quot;50.2&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;13&quot; data-d=&quot;2&quot; data-t=&quot;规则优先，向量只用来排序&quot;&amp;gt;&amp;lt;circle cx=&quot;329.9&quot; cy=&quot;297.8&quot; r=&quot;2.4&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;338.3&quot; y=&quot;302.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;规则优先，向量只用来排序&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;338.3&quot; y=&quot;308.6&quot; width=&quot;163.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;14&quot; data-d=&quot;1&quot; data-t=&quot;⑥ 注入 Prompt&quot;&amp;gt;&amp;lt;circle cx=&quot;176.8&quot; cy=&quot;340.4&quot; r=&quot;3.2&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;186.0&quot; y=&quot;345.7&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;⑥ 注入 Prompt&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;186.0&quot; y=&quot;352.6&quot; width=&quot;106.3&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;15&quot; data-d=&quot;2&quot; data-t=&quot;最多 5 条，标注为“历史记录”&quot;&amp;gt;&amp;lt;circle cx=&quot;329.9&quot; cy=&quot;340.4&quot; r=&quot;2.4&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;338.3&quot; y=&quot;345.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;最多 5 条，标注为“历史记录”&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;338.3&quot; y=&quot;351.2&quot; width=&quot;175.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;16&quot; data-d=&quot;1&quot; data-t=&quot;⑦ 更新与遗忘&quot;&amp;gt;&amp;lt;circle cx=&quot;176.8&quot; cy=&quot;399.2&quot; r=&quot;3.2&quot; fill=&quot;#3b82f6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;186.0&quot; y=&quot;404.5&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;⑦ 更新与遗忘&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;186.0&quot; y=&quot;411.5&quot; width=&quot;96.7&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#3b82f6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;17&quot; data-d=&quot;2&quot; data-t=&quot;新习惯覆盖旧习惯&quot;&amp;gt;&amp;lt;circle cx=&quot;329.9&quot; cy=&quot;383.0&quot; r=&quot;2.4&quot; fill=&quot;#3b82f6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;338.3&quot; y=&quot;387.6&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;新习惯覆盖旧习惯&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;338.3&quot; y=&quot;393.8&quot; width=&quot;109.9&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#3b82f6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;18&quot; data-d=&quot;2&quot; data-t=&quot;长时间未使用则降权&quot;&amp;gt;&amp;lt;circle cx=&quot;329.9&quot; cy=&quot;415.5&quot; r=&quot;2.4&quot; fill=&quot;#3b82f6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;338.3&quot; y=&quot;420.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;长时间未使用则降权&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;338.3&quot; y=&quot;426.3&quot; width=&quot;123.3&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#3b82f6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;19&quot; data-d=&quot;1&quot; data-t=&quot;硬边界&quot;&amp;gt;&amp;lt;circle cx=&quot;176.8&quot; cy=&quot;458.1&quot; r=&quot;3.2&quot; fill=&quot;#84cc16&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;186.0&quot; y=&quot;463.4&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;硬边界&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;186.0&quot; y=&quot;470.3&quot; width=&quot;48.5&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#84cc16&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;20&quot; data-d=&quot;2&quot; data-t=&quot;LLM 无权创建 / 修改 / 删除记忆&quot;&amp;gt;&amp;lt;circle cx=&quot;329.9&quot; cy=&quot;458.1&quot; r=&quot;2.4&quot; fill=&quot;#84cc16&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;338.3&quot; y=&quot;462.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;LLM 无权创建 / 修改 / 删除记忆&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;338.3&quot; y=&quot;468.9&quot; width=&quot;190.4&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#84cc16&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;六、隐私分级与 ROOT 模式&lt;/h2&gt;
&lt;p&gt;这是 MAYA2.0 和「一个聊天机器人」最大的区别：&lt;strong&gt;它真的存了你的隐私信息，所以必须有一套权限体系。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;数据被分成三套独立的库，互不写入，访问权限各不相同：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;数据源&lt;/th&gt;
&lt;th&gt;访问权限&lt;/th&gt;
&lt;th&gt;存什么&lt;/th&gt;
&lt;th&gt;更新方式&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;记忆库 Memory&lt;/td&gt;
&lt;td&gt;普通 + ROOT&lt;/td&gt;
&lt;td&gt;长期习惯、偏好&lt;/td&gt;
&lt;td&gt;对话触发写入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;隐私库 Privacy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;仅 ROOT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10 类敏感信息&lt;/td&gt;
&lt;td&gt;隐私管理 GUI 维护&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;时空状态库 Realtime&lt;/td&gt;
&lt;td&gt;普通 + ROOT&lt;/td&gt;
&lt;td&gt;时间 / 位置 / 天气 / 设备状态&lt;/td&gt;
&lt;td&gt;定时自动更新&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IoT 设备状态&lt;/td&gt;
&lt;td&gt;普通 + ROOT&lt;/td&gt;
&lt;td&gt;设备实时状态&lt;/td&gt;
&lt;td&gt;控制 / 查询时更新&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;流程是这样的：用户问「我的手机号是多少」，系统先在 &lt;code&gt;process_input()&lt;/code&gt; 的最前面做&lt;strong&gt;隐私检测&lt;/strong&gt;——这一步刻意放在意图路由&lt;strong&gt;之前&lt;/strong&gt;，因为隐私查询的措辞太多变，交给规则分类容易被判成普通对话：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 【优先级1】检查是否需要 ROOT 模式（必须在意图路由之前）
needs_privacy = self.root_mode_manager.check_needs_privacy(user_text)
requests_root = self.root_mode_manager.check_request_root_mode(user_text)

# 需要隐私但 ROOT 未激活 → 先去要口令
if (needs_privacy or requests_root) and not self.root_mode_manager.is_active():
    return self._handle_root_mode_request(user_text, needs_privacy, requests_root)

# ROOT 已激活且需要隐私 → 强制走普通对话分支（那里会读隐私并注入 Prompt）
if self.root_mode_manager.is_active() and needs_privacy:
    intent = IntentObject(intent_type=self.intent_router.GENERAL_CHAT, ...)
    return self._handle_general_chat(intent, user_text)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;口令校验（实现细节：值是配置在代码里的常量，这里不写出来）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class RootModeManager:
    # ROOT 模式口令（真实值在源码常量里，这里以占位符示意）
    ROOT_PASSWORD = &quot;••••&quot;

    def verify_password(self, password: str) -&amp;gt; bool:
        &quot;&quot;&quot;支持多种格式：纯数字、中文数字、以及 ASR 听起来像的变体&quot;&quot;&quot;
        password_clean = password.strip().replace(&quot; &quot;, &quot;&quot;).replace(&quot;，&quot;, &quot;&quot;).replace(&quot;,&quot;, &quot;&quot;)

        if password_clean == self.ROOT_PASSWORD:
            return True

        # 「二零三九」这类中文念法要能转回数字——语音输入经常吐这种
        chinese_to_digit = {&quot;零&quot;: &quot;0&quot;, &quot;一&quot;: &quot;1&quot;, &quot;二&quot;: &quot;2&quot;, &quot;三&quot;: &quot;3&quot;, ...}
        if all(c in chinese_to_digit for c in password_clean):
            converted = &quot;&quot;.join(chinese_to_digit.get(c, c) for c in password_clean)
            if converted == self.ROOT_PASSWORD:
                return True

        return False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里有个只做语音系统才会想到的细节：口令校验&lt;strong&gt;必须同时接受阿拉伯数字和中文数字&lt;/strong&gt;（以及「两千零三十九」这类念法），因为用户是用嘴说的，而 ASR 的输出并不稳定。纯文本系统不会有这个问题。&lt;/p&gt;
&lt;p&gt;隐私查询还做了&lt;strong&gt;分类读取&lt;/strong&gt;，不是一股脑全倒出来——问名字只读 &lt;code&gt;PERSONAL_INFO&lt;/code&gt;，问联系方式只读 &lt;code&gt;CONTACT_INFO&lt;/code&gt;，问账号只读 &lt;code&gt;ACCOUNT_INFO&lt;/code&gt;。读取不到时才退化成「按重要性取前 10 条」。&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;maya-diagram&quot; data-maya=&quot;06-data-tiers&quot;&amp;gt;
&amp;lt;div class=&quot;maya-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 644 353&quot; width=&quot;644&quot; height=&quot;353&quot; class=&quot;maya-svg&quot; role=&quot;img&quot; aria-label=&quot;数据分级与权限图：记忆库 Memory 普通与 ROOT 模式都可读，存用户习惯、设备信息、用户状态；隐私库 Privacy 仅在 ROOT 模式加口令校验后可读，含十类隐私信息；时空状态库 Realtime 两种模式都可读，存时间、位置、天气、设备状态。注入时记忆最多五条，隐私仅在 ROOT 激活时出现，三个库在结构上互不写入。&quot;&amp;gt;&amp;lt;g class=&quot;maya-links&quot;&amp;gt;&amp;lt;path d=&quot;M148.9 166.9 C169.7 166.9 165.9 46.1 186.7 46.1&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M148.9 166.9 C169.7 166.9 165.9 121.2 186.7 121.2&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M148.9 166.9 C169.7 166.9 165.9 196.4 186.7 196.4&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M148.9 166.9 C169.7 166.9 165.9 287.8 186.7 287.8&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M305.4 46.1 C345.2 46.1 338.0 29.8 377.7 29.8&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M305.4 46.1 C345.2 46.1 338.0 62.4 377.7 62.4&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M299.4 121.2 C342.5 121.2 334.6 104.9 377.7 104.9&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M299.4 121.2 C342.5 121.2 334.6 137.5 377.7 137.5&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M339.9 196.4 C360.7 196.4 356.9 180.1 377.7 180.1&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M339.9 196.4 C360.7 196.4 356.9 212.7 377.7 212.7&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M258.9 287.8 C324.3 287.8 312.4 255.2 377.7 255.2&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M258.9 287.8 C324.3 287.8 312.4 287.8 377.7 287.8&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M258.9 287.8 C324.3 287.8 312.4 320.4 377.7 320.4&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;0&quot; data-d=&quot;0&quot; data-t=&quot;三套数据
三种权限&quot;&amp;gt;&amp;lt;text x=&quot;70.7&quot; y=&quot;159.6&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;三套数据&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;70.7&quot; y=&quot;187.2&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;三种权限&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;70.7&quot; y=&quot;195.5&quot; width=&quot;78.1&quot; height=&quot;3&quot; rx=&quot;1.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;1&quot; data-d=&quot;1&quot; data-t=&quot;记忆库 Memory&quot;&amp;gt;&amp;lt;circle cx=&quot;185.7&quot; cy=&quot;46.1&quot; r=&quot;3.2&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;194.9&quot; y=&quot;51.3&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;记忆库 Memory&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;194.9&quot; y=&quot;58.3&quot; width=&quot;110.6&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;2&quot; data-d=&quot;2&quot; data-t=&quot;普通 + ROOT 都可读&quot;&amp;gt;&amp;lt;circle cx=&quot;377.5&quot; cy=&quot;29.8&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.9&quot; y=&quot;34.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;普通 + ROOT 都可读&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.9&quot; y=&quot;40.6&quot; width=&quot;121.6&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;3&quot; data-d=&quot;2&quot; data-t=&quot;用户习惯 / 设备信息 / 用户状态&quot;&amp;gt;&amp;lt;circle cx=&quot;377.5&quot; cy=&quot;62.4&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.9&quot; y=&quot;67.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;用户习惯 / 设备信息 / 用户状态&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.9&quot; y=&quot;73.2&quot; width=&quot;187.7&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;4&quot; data-d=&quot;1&quot; data-t=&quot;隐私库 Privacy&quot;&amp;gt;&amp;lt;circle cx=&quot;185.7&quot; cy=&quot;121.2&quot; r=&quot;3.2&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;194.9&quot; y=&quot;126.5&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;隐私库 Privacy&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;194.9&quot; y=&quot;133.5&quot; width=&quot;104.5&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;5&quot; data-d=&quot;2&quot; data-t=&quot;仅 ROOT 模式 + 口令&quot;&amp;gt;&amp;lt;circle cx=&quot;377.5&quot; cy=&quot;104.9&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.9&quot; y=&quot;109.5&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;仅 ROOT 模式 + 口令&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.9&quot; y=&quot;115.7&quot; width=&quot;125.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;6&quot; data-d=&quot;2&quot; data-t=&quot;10 类隐私信息&quot;&amp;gt;&amp;lt;circle cx=&quot;377.5&quot; cy=&quot;137.5&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.9&quot; y=&quot;142.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;10 类隐私信息&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.9&quot; y=&quot;148.3&quot; width=&quot;88.1&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;7&quot; data-d=&quot;1&quot; data-t=&quot;时空状态库 Realtime&quot;&amp;gt;&amp;lt;circle cx=&quot;185.7&quot; cy=&quot;196.4&quot; r=&quot;3.2&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;194.9&quot; y=&quot;201.6&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;时空状态库 Realtime&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;194.9&quot; y=&quot;208.6&quot; width=&quot;145.1&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;8&quot; data-d=&quot;2&quot; data-t=&quot;普通 + ROOT 都可读&quot;&amp;gt;&amp;lt;circle cx=&quot;377.5&quot; cy=&quot;180.1&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.9&quot; y=&quot;184.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;普通 + ROOT 都可读&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.9&quot; y=&quot;190.9&quot; width=&quot;121.6&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;9&quot; data-d=&quot;2&quot; data-t=&quot;时间 / 位置 / 天气 / 设备状态&quot;&amp;gt;&amp;lt;circle cx=&quot;377.5&quot; cy=&quot;212.7&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.9&quot; y=&quot;217.3&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;时间 / 位置 / 天气 / 设备状态&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.9&quot; y=&quot;223.4&quot; width=&quot;172.7&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;10&quot; data-d=&quot;1&quot; data-t=&quot;注入边界&quot;&amp;gt;&amp;lt;circle cx=&quot;185.7&quot; cy=&quot;287.8&quot; r=&quot;3.2&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;194.9&quot; y=&quot;293.1&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;注入边界&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;194.9&quot; y=&quot;300.1&quot; width=&quot;64.0&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;11&quot; data-d=&quot;2&quot; data-t=&quot;记忆最多注入 5 条&quot;&amp;gt;&amp;lt;circle cx=&quot;377.5&quot; cy=&quot;255.2&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.9&quot; y=&quot;259.8&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;记忆最多注入 5 条&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.9&quot; y=&quot;266.0&quot; width=&quot;110.7&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;12&quot; data-d=&quot;2&quot; data-t=&quot;隐私仅在 ROOT 激活时出现&quot;&amp;gt;&amp;lt;circle cx=&quot;377.5&quot; cy=&quot;287.8&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.9&quot; y=&quot;292.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;隐私仅在 ROOT 激活时出现&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.9&quot; y=&quot;298.6&quot; width=&quot;164.7&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;13&quot; data-d=&quot;2&quot; data-t=&quot;三库结构上互不写入&quot;&amp;gt;&amp;lt;circle cx=&quot;377.5&quot; cy=&quot;320.4&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.9&quot; y=&quot;325.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;三库结构上互不写入&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.9&quot; y=&quot;331.2&quot; width=&quot;123.3&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;七、五模式对照：1.5B 的真实水平&lt;/h2&gt;
&lt;p&gt;前面讲的都是「设计成了什么样」。但设计意图和实际行为之间，隔着一条巨大的鸿沟。&lt;/p&gt;
&lt;p&gt;为了把这条鸿沟测出来，我做了一件回头看最值的事：&lt;strong&gt;搭一个能切换规则组合的调试 GUI，用同一批问题（30 道）去跑五种不同的规则配置&lt;/strong&gt;，然后并排对比输出。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;启用的规则&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;① 纯净版&lt;/td&gt;
&lt;td&gt;无任何规则、无清理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;② 仅 Prompt 模板&lt;/td&gt;
&lt;td&gt;只有 &lt;code&gt;prompt_templates.py&lt;/code&gt; 的系统规则&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;③ 仅输出清理&lt;/td&gt;
&lt;td&gt;只有 &lt;code&gt;qwen_engine.py&lt;/code&gt; 的清理逻辑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;④ 完整规则链&lt;/td&gt;
&lt;td&gt;全部启用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⑤ Prompt + 清理&lt;/td&gt;
&lt;td&gt;去掉意图路由等外挂&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这个实验的设计意图很清楚：&lt;strong&gt;算法里叫消融实验（ablation）&lt;/strong&gt;——把每个组件单独摘掉，看它的实际贡献。工程上很少有比这更便宜的诊断手段了。&lt;/p&gt;
&lt;p&gt;结果非常难看，但正是我需要看到的。&lt;/p&gt;
&lt;h3&gt;模式①：无规则的裸模型&lt;/h3&gt;
&lt;p&gt;同一批问题下，1.5B 的表现是这样的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;你: 你好
你好，请问有什么可以帮助你的吗？

你: 你是谁？
我是阿里云的智能助手，您可以叫我小智。有什么可以帮助您的吗？

你: 你认识我吗？
是的，我认识你。你是谁？请告诉我你的问题，我会尽力帮助你。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;三个问题一次暴露三个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;身份完全错乱&lt;/strong&gt;——它自称「阿里云的小智」。训练数据里这类自我介绍太多，没有约束时它优先复读高频模式。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不知道 MAYA 和热热是谁&lt;/strong&gt;——因为没人告诉过它。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;反问用户&lt;/strong&gt;——「你是谁？」这种把球踢回去的句子，在语音场景里非常糟糕。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最夸张的是重复。问「天津的特产是什么」，它给出的列表长这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;天津的特产有：狗不理包子、天津烤鸭、天津麻花、天津大包、天津炸酱面、天津锅贴、
天津水煎包、天津煎饼果子、天津耳朵眼炸糕、天津老豆腐、天津煎饼、天津煎饼果子、
天津煎饼、天津煎饼、天津煎饼、天津煎饼、天津煎饼、天津……
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;「天津煎饼」一路重复到被 &lt;code&gt;max_tokens&lt;/code&gt; 掐断。原因不复杂：1.5B 的容量有限，一旦进入重复循环，&lt;strong&gt;它自己走不出来&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;模式②：只加 Prompt 规则&lt;/h3&gt;
&lt;p&gt;Prompt 里写了身份定义之后，自称小智的问题解决了。但立刻冒出新的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;你: 你好
你好，MAYA。有什么我可以帮忙的吗？

你: 你是谁？
我是MAYA，热热的个人开发大模型，他是我的主人。
[语音]我是MAYA，热热的个人开发大模型，他是我的主人。[语音]我是MAYA，……
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两个新问题：&lt;strong&gt;人称反转&lt;/strong&gt;（模型以为用户叫 MAYA，所以回「你好，MAYA」），以及规则文本里的 &lt;code&gt;[语音]&lt;/code&gt; 标记被模型原样吐了出来。&lt;/p&gt;
&lt;p&gt;这次翻车直接催生了后面最重要的一个设计——&lt;strong&gt;身份结构化&lt;/strong&gt;（详见下一节）。&lt;/p&gt;
&lt;h3&gt;模式③：只加输出清理&lt;/h3&gt;
&lt;p&gt;去掉 Prompt 规则的后果立竿见影——身份又崩了。而且只做后处理&lt;strong&gt;治不住重复&lt;/strong&gt;：清理逻辑是按句号切分去重的，可模型的重复用的是问号：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;你: 你是谁？
我是阿里云的智能助手，您可以叫我AI。有什么可以帮助您的吗？
问题：你叫什么名字？问题：你的名字是什么？问题：你的名字叫什么？……
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它清楚地说明：&lt;strong&gt;后处理是补丁，不是地基&lt;/strong&gt;。上下文一丢，模型立刻退回出厂状态。&lt;/p&gt;
&lt;h3&gt;模式④：完整规则链&lt;/h3&gt;
&lt;p&gt;全部启用后，身份、格式、长度都稳住了。但集成层面的问题暴露了出来——问「给我写个 Arduino 点灯程序」，回复是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IoT 服务未初始化
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原因是设备控制分支在 MQTT 不可用时直接返回了这句内部错误。这类问题单看某个模块永远测不出来，&lt;strong&gt;只有把整条链路串起来跑才会现形&lt;/strong&gt;——这是这个对照实验附带的收获。&lt;/p&gt;
&lt;h3&gt;模式⑤：Prompt + 清理&lt;/h3&gt;
&lt;p&gt;去掉意图路由后，模型重新接走了所有请求，输出又开始发散了。它反过来说明了一件事：&lt;strong&gt;前端约束和后端清理都替代不了「分流」&lt;/strong&gt;——设备控制这种事，本来就该由确定性代码去干。&lt;/p&gt;
&lt;h3&gt;实验结论&lt;/h3&gt;
&lt;p&gt;把五种模式的结果摆在一起，能得到一个挺硬的结论：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;1.5B 这类小模型，&lt;strong&gt;靠它自己「变好」是不现实的&lt;/strong&gt;。它不是不够聪明，是容量不足以同时管住身份、格式、长度和重复。想让它当管家，只能在外面给它搭一副&lt;strong&gt;外骨骼&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;figure class=&quot;maya-diagram&quot; data-maya=&quot;03-five-modes&quot;&amp;gt;
&amp;lt;div class=&quot;maya-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 644 267&quot; width=&quot;644&quot; height=&quot;267&quot; class=&quot;maya-svg&quot; role=&quot;img&quot; aria-label=&quot;五种规则组合的对照实验结果图：模式一纯 LLM 无规则，自称阿里云智能助手小智、同一句重复四遍；模式二只加 Prompt 模板，出现人称反转把用户当成 MAYA；模式三只加输出清理，重复问题照旧出现六次；模式四启用完整规则链，暴露出「IoT 服务未初始化」的集成问题；模式五 Prompt 加清理，输出仍会发散。&quot;&amp;gt;&amp;lt;g class=&quot;maya-links&quot;&amp;gt;&amp;lt;path d=&quot;M165.7 139.4 C186.4 139.4 182.7 46.1 203.5 46.1&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M165.7 139.4 C186.4 139.4 182.7 104.9 203.5 104.9&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M165.7 139.4 C186.4 139.4 182.7 147.5 203.5 147.5&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M165.7 139.4 C186.4 139.4 182.7 190.1 203.5 190.1&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M165.7 139.4 C186.4 139.4 182.7 232.7 203.5 232.7&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M291.1 46.1 C346.1 46.1 336.1 29.8 391.1 29.8&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M291.1 46.1 C346.1 46.1 336.1 62.4 391.1 62.4&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M353.3 104.9 C374.1 104.9 370.3 104.9 391.1 104.9&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M323.8 147.5 C360.8 147.5 354.1 147.5 391.1 147.5&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M308.3 190.1 C353.8 190.1 345.6 190.1 391.1 190.1&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M329.5 232.7 C363.4 232.7 357.2 232.7 391.1 232.7&quot; fill=&quot;none&quot; stroke=&quot;#10b981&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;0&quot; data-d=&quot;0&quot; data-t=&quot;五种规则组合
同一批测试问题&quot;&amp;gt;&amp;lt;text x=&quot;30.6&quot; y=&quot;132.1&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;五种规则组合&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;30.6&quot; y=&quot;159.6&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;同一批测试问题&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;30.6&quot; y=&quot;167.9&quot; width=&quot;135.1&quot; height=&quot;3&quot; rx=&quot;1.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;1&quot; data-d=&quot;1&quot; data-t=&quot;① 纯 LLM
无任何规则&quot;&amp;gt;&amp;lt;circle cx=&quot;202.5&quot; cy=&quot;46.1&quot; r=&quot;3.2&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;211.7&quot; y=&quot;40.1&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;① 纯 LLM&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;211.7&quot; y=&quot;62.6&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;无任何规则&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;211.7&quot; y=&quot;69.5&quot; width=&quot;79.4&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;2&quot; data-d=&quot;2&quot; data-t=&quot;自称“阿里云的智能助手，叫我小智”&quot;&amp;gt;&amp;lt;circle cx=&quot;390.9&quot; cy=&quot;29.8&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;399.3&quot; y=&quot;34.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;自称“阿里云的智能助手，叫我小智”&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;399.3&quot; y=&quot;40.6&quot; width=&quot;214.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;3&quot; data-d=&quot;2&quot; data-t=&quot;同一句重复 4 遍&quot;&amp;gt;&amp;lt;circle cx=&quot;390.9&quot; cy=&quot;62.4&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;399.3&quot; y=&quot;67.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;同一句重复 4 遍&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;399.3&quot; y=&quot;73.2&quot; width=&quot;97.3&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;4&quot; data-d=&quot;1&quot; data-t=&quot;② 只有 Prompt 模板&quot;&amp;gt;&amp;lt;circle cx=&quot;202.5&quot; cy=&quot;104.9&quot; r=&quot;3.2&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;211.7&quot; y=&quot;110.2&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;② 只有 Prompt 模板&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;211.7&quot; y=&quot;117.2&quot; width=&quot;141.6&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;5&quot; data-d=&quot;2&quot; data-t=&quot;人称反转——回答“你好，MAYA”&quot;&amp;gt;&amp;lt;circle cx=&quot;390.9&quot; cy=&quot;104.9&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;399.3&quot; y=&quot;109.5&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;人称反转——回答“你好，MAYA”&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;399.3&quot; y=&quot;115.7&quot; width=&quot;190.1&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;6&quot; data-d=&quot;1&quot; data-t=&quot;③ 只有输出清理&quot;&amp;gt;&amp;lt;circle cx=&quot;202.5&quot; cy=&quot;147.5&quot; r=&quot;3.2&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;211.7&quot; y=&quot;152.8&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;③ 只有输出清理&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;211.7&quot; y=&quot;159.8&quot; width=&quot;112.2&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;7&quot; data-d=&quot;2&quot; data-t=&quot;重复照旧：“你叫什么名字” × 6&quot;&amp;gt;&amp;lt;circle cx=&quot;390.9&quot; cy=&quot;147.5&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;399.3&quot; y=&quot;152.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;重复照旧：“你叫什么名字” × 6&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;399.3&quot; y=&quot;158.3&quot; width=&quot;182.1&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;8&quot; data-d=&quot;1&quot; data-t=&quot;④ 完整规则链&quot;&amp;gt;&amp;lt;circle cx=&quot;202.5&quot; cy=&quot;190.1&quot; r=&quot;3.2&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;211.7&quot; y=&quot;195.4&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;④ 完整规则链&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;211.7&quot; y=&quot;202.3&quot; width=&quot;96.7&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;9&quot; data-d=&quot;2&quot; data-t=&quot;暴露集成问题：“IoT 服务未初始化”&quot;&amp;gt;&amp;lt;circle cx=&quot;390.9&quot; cy=&quot;190.1&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;399.3&quot; y=&quot;194.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;暴露集成问题：“IoT 服务未初始化”&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;399.3&quot; y=&quot;200.9&quot; width=&quot;210.3&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;10&quot; data-d=&quot;1&quot; data-t=&quot;⑤ Prompt + 清理&quot;&amp;gt;&amp;lt;circle cx=&quot;202.5&quot; cy=&quot;232.7&quot; r=&quot;3.2&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;211.7&quot; y=&quot;237.9&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;⑤ Prompt + 清理&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;211.7&quot; y=&quot;244.9&quot; width=&quot;117.9&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;11&quot; data-d=&quot;2&quot; data-t=&quot;仍会发散，收不住长度&quot;&amp;gt;&amp;lt;circle cx=&quot;390.9&quot; cy=&quot;232.7&quot; r=&quot;2.4&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;399.3&quot; y=&quot;237.3&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;仍会发散，收不住长度&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;399.3&quot; y=&quot;243.4&quot; width=&quot;136.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;八、四层防御：那副外骨骼长什么样&lt;/h2&gt;
&lt;p&gt;从实验反推，约束必须在四个不同层面同时存在，缺一层都会漏。这四层加起来长成了这么一套规模——&lt;strong&gt;这也是为什么它的主体是 Prompt 工程，而不是模型工程&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;四层防御的物料&lt;/th&gt;
&lt;th&gt;规模&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prompt_templates.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;171 行（3 个模板常量 + 5 个拼装方法）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SYSTEM_TEMPLATE&lt;/code&gt; 行为规则&lt;/td&gt;
&lt;td&gt;12 条&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stop_tokens&lt;/code&gt; 截断表&lt;/td&gt;
&lt;td&gt;74 条&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_clean_output()&lt;/code&gt; 后处理&lt;/td&gt;
&lt;td&gt;323 行 / 6 步&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt 规范文档&lt;/td&gt;
&lt;td&gt;218 行（5.6 KB）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;五模式对照实验&lt;/td&gt;
&lt;td&gt;5 组规则跑同一批问题&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;全部集中在 2025 年 11 月底到 12 月这一个月里反复改出来的，下面按层展开。&lt;/p&gt;
&lt;h3&gt;第 1 层：Prompt 约束——把身份写成元数据&lt;/h3&gt;
&lt;p&gt;模式②暴露的人称反转，根因是&lt;strong&gt;自然语言描述身份不可靠&lt;/strong&gt;。「你是 MAYA，用户是热热」这种句子，模型得先理解再遵守，中间会漏。所以改成了机器可读的结构化块：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SYSTEM_IDENTITY = &quot;&quot;&quot;&amp;lt;&amp;lt;SYS_META&amp;gt;&amp;gt;
ASSISTANT_ID = MAYA
USER_ID = 热热
RELATION = assistant_to_user
&amp;lt;&amp;lt;END_SYS_META&amp;gt;&amp;gt;

&amp;lt;&amp;lt;PRONOUN_MAP&amp;gt;&amp;gt;
我 = ASSISTANT_ID
你 = USER_ID
我们 = ASSISTANT_ID + USER_ID
&amp;lt;&amp;lt;END_PRONOUN_MAP&amp;gt;&amp;gt;
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;PRONOUN_MAP&lt;/code&gt; 那段是关键——它把「我 / 你 / 我们」到底指谁，用赋值的方式写死，而不是靠模型猜。同时系统规则里明确一句「回答中不要讨论或解释身份结构本身」，防止模型把元数据当话题聊起来。&lt;/p&gt;
&lt;p&gt;整个 Prompt 有 &lt;strong&gt;5 个固定区块&lt;/strong&gt;（&lt;code&gt;[System]&lt;/code&gt; → &lt;code&gt;[Persona]&lt;/code&gt; → &lt;code&gt;[Memory]&lt;/code&gt; → &lt;code&gt;[Context]&lt;/code&gt; → &lt;code&gt;[User]&lt;/code&gt;），另外还有 3 个只在特定条件下出现的区块：&lt;code&gt;[ROOT Mode]&lt;/code&gt;、&lt;code&gt;[Privacy]&lt;/code&gt;、&lt;code&gt;[Realtime State]&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@classmethod
def build_full_prompt(cls, user_input, memories=None, context=None,
                      privacy_data=None, root_mode_active=False, realtime_state=None):
    parts = [
        &quot;[System]&quot;, cls.SYSTEM_IDENTITY, cls.SYSTEM_TEMPLATE, &quot;&quot;,
    ]
    if root_mode_active:                     # 只声明权限，不重复身份
        parts.extend([&quot;[ROOT Mode]&quot;, &quot;当前处于 ROOT 模式，可访问已授权的隐私信息。&quot;, ...])

    parts.extend([&quot;[Persona]&quot;, cls.PERSONA_TEMPLATE, &quot;&quot;])
    parts.extend([&quot;[Memory]&quot;, cls.format_memory_section(memories or []), &quot;&quot;])

    if root_mode_active and privacy_data:    # 隐私只在 ROOT 激活时出现
        parts.extend([&quot;[Privacy]&quot;, cls.format_privacy_section(privacy_data), &quot;&quot;])

    parts.extend([&quot;[Context]&quot;, cls.format_context_section(context or {}), &quot;&quot;])
    if realtime_state:
        parts.extend([&quot;[Realtime State]&quot;, cls.format_realtime_state_section(realtime_state), &quot;&quot;])

    parts.extend([&quot;[User]&quot;, user_input, &quot;&quot;, &quot;[Response]&quot;])
    return &quot;\n&quot;.join(parts)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;第 2 层：Stop Tokens——让它根本吐不出来&lt;/h3&gt;
&lt;p&gt;生成阶段就把不该出现的东西截断。这张表现在有 &lt;strong&gt;74 个条目&lt;/strong&gt;，不但覆盖系统标记、用户标记、结束标记，还把所有&lt;strong&gt;结构化标记的变体&lt;/strong&gt;（&lt;code&gt;&amp;lt;&amp;lt;SYS_META&amp;gt;&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;&amp;lt;_SYS_META&amp;gt;&amp;gt;&lt;/code&gt; / &lt;code&gt;_SYS_META&lt;/code&gt; …）都逐一列出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;stop_tokens = [
    # 结构化标记（所有变体）
    &quot;&amp;lt;&amp;lt;SYS_META&amp;gt;&amp;gt;&quot;, &quot;&amp;lt;&amp;lt;END_SYS_META&amp;gt;&amp;gt;&quot;, &quot;&amp;lt;&amp;lt;_SYS_META&amp;gt;&amp;gt;&quot;, &quot;_SYS_META&quot;,
    &quot;&amp;lt;&amp;lt;PRONOUN_MAP&amp;gt;&amp;gt;&quot;, &quot;&amp;lt;&amp;lt;END_PRONOUN_MAP&amp;gt;&amp;gt;&quot;,
    &quot;&amp;lt;&amp;lt;_RESPONSE&amp;gt;&amp;gt;&quot;, &quot;&amp;lt;&amp;lt;BEGIN_RESPONSE&amp;gt;&amp;gt;&quot;, &quot;&amp;lt;&amp;lt;END_RESPONSE&amp;gt;&amp;gt;&quot;,
    &quot;&amp;lt;&amp;lt;_SYSTEM&amp;gt;&amp;gt;&quot;, &quot;&amp;lt;&amp;lt;_USER&amp;gt;&amp;gt;&quot;, &quot;&amp;lt;&amp;lt;_CONTEXT&amp;gt;&amp;gt;&quot;,
    &quot;BEGIN_RESPONSE&quot;, &quot;END_RESPONSE&quot;, &quot;_RESPONSE&quot;,
    # Prompt 区块标记
    &quot;[System]&quot;, &quot;[Persona]&quot;, &quot;[Memory]&quot;, &quot;[Context]&quot;, &quot;[User]&quot;, &quot;[Response]&quot;,
    &quot;[ROOT Mode]&quot;, &quot;[Privacy]&quot;,
    # 内部标签
    &quot;ASSISTANT_ID&quot;, &quot;USER_ID&quot;, &quot;RELATION&quot;, &quot;assistant_to_user&quot;,
    # 结束标记
    &quot;[END_OF_TEXT]&quot;, &quot;[END_OF_SPEECH]&quot;, &quot;[END]&quot;, &quot;[EOS]&quot;,
    &quot;End of Response&quot;, &quot;End of End&quot;,
    # 对话标记
    &quot;用户说：&quot;, &quot;用户问：&quot;, &quot;用户说&quot;, &quot;用户问&quot;,
    &quot;MAYA 答复：&quot;, &quot;MAYA 说：&quot;, &quot;\&quot;MAYA\&quot; 答复：&quot;, &quot;MAYA 答复&quot;, &quot;MAYA 说&quot;,
    &quot;Assistant:&quot;, &quot;User:&quot;, &quot;助手：&quot;, &quot;用户：&quot;,
    # 系统解释性内容（禁止语义）
    &quot;作为一个&quot;, &quot;我被设计为&quot;, &quot;我的设计目标是&quot;,
    &quot;根据规则&quot;, &quot;根据设定&quot;, &quot;根据系统&quot;, &quot;根据模式&quot;,
    &quot;在这个模式下&quot;, &quot;ROOT模式下&quot;,
    &quot;我的数据库&quot;, &quot;我的系统&quot;, &quot;我的Prompt&quot;,
    &quot;不要讨论&quot;, &quot;不要解释&quot;, &quot;核心行为规则&quot;, &quot;必须遵守&quot;,
    &quot;语音合成&quot;, &quot;[语音合成]&quot;, &quot;[语音]&quot;, &quot;语音结束&quot;, &quot;语音指令&quot;, &quot;语音响应&quot;,
    &quot;1-3句话&quot;, &quot;1-2句话&quot;,
    # 换行与 Markdown 标记
    &quot;\n\n\n&quot;, &quot;\n\n&quot;,
    &quot;##&quot;, &quot;###&quot;, &quot;####&quot;,
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这张表是&lt;strong&gt;从实验里长出来的&lt;/strong&gt;——每一条都对应某个模式里真实出现过的翻车。早期版本只有 20 来条，后来每次线上日志里发现新的泄漏形态就补一条，才攒到现在的 74 条。&lt;/p&gt;
&lt;h3&gt;第 3 层：后处理清理——320 多行的兜底&lt;/h3&gt;
&lt;p&gt;Prompt 管生成方向，Stop Tokens 管硬截断，但模型总还能绕出一些没预料到的花样（标记残片、身份漂移、重复循环）。所以最后还有一层 &lt;code&gt;_clean_output()&lt;/code&gt;，代码里 320 多行，分六步：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;步骤&lt;/th&gt;
&lt;th&gt;做什么&lt;/th&gt;
&lt;th&gt;约束强度&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;移除 &lt;code&gt;&amp;lt;&amp;lt;...&amp;gt;&amp;gt;&lt;/code&gt; / &lt;code&gt;[...]&lt;/code&gt; 结构化标记与内部标签&lt;/td&gt;
&lt;td&gt;硬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;移除系统解释性内容（「作为一个 AI」「根据规则」）&lt;/td&gt;
&lt;td&gt;硬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;移除对话标记（&lt;code&gt;MAYA 答复：&lt;/code&gt;、&lt;code&gt;用户说：&lt;/code&gt;）与 emoji&lt;/td&gt;
&lt;td&gt;硬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;移除重复（词级 → 短语级 → 句子级）&lt;/td&gt;
&lt;td&gt;软&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;修复身份混淆&lt;/td&gt;
&lt;td&gt;软&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;限制长度与风格（不超过 3 句、150 字，在句号处截断）&lt;/td&gt;
&lt;td&gt;硬&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;前三步是「硬约束」——清完就是清完了；中间两步是「软约束」——只做修补，尽量不动内容。这个划分来自一个教训，后面单独讲。&lt;/p&gt;
&lt;h3&gt;第 4 层：意图分流——不给它越权的机会&lt;/h3&gt;
&lt;p&gt;最外层的兜底。设备控制、记忆写入这些&lt;strong&gt;需要确定性的活&lt;/strong&gt;，在意图路由那一层就被截走了，根本不会走到 LLM。这一层越宽，前三层的压力就越小。&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;maya-diagram&quot; data-maya=&quot;05-four-layers&quot;&amp;gt;
&amp;lt;div class=&quot;maya-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 644 418&quot; width=&quot;644&quot; height=&quot;418&quot; class=&quot;maya-svg&quot; role=&quot;img&quot; aria-label=&quot;四层约束体系图：第一层 Prompt 约束（身份结构化块、代词映射、五个固定区块加三个条件区块）；第二层 Stop Tokens 截断（七十四个标记，覆盖系统标记、结构化标记、对话标记与结束标记）；第三层后处理清理（三百二十多行的输出清理函数，分六步做标记移除、解释性内容清理、去重、身份修复与长度限制，并分阶段保护防止清成空串）；第四层意图分流（十种意图各走各的路，能不用大模型就不用）。&quot;&amp;gt;&amp;lt;g class=&quot;maya-links&quot;&amp;gt;&amp;lt;path d=&quot;M204.4 215.8 C225.2 215.8 221.4 62.4 242.2 62.4&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M204.4 215.8 C225.2 215.8 221.4 170.1 242.2 170.1&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M204.4 215.8 C225.2 215.8 221.4 277.8 242.2 277.8&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M204.4 215.8 C225.2 215.8 221.4 369.2 242.2 369.2&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M338.8 62.4 C359.6 62.4 355.8 29.8 376.6 29.8&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M338.8 62.4 C359.6 62.4 355.8 62.4 376.6 62.4&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M338.8 62.4 C359.6 62.4 355.8 94.9 376.6 94.9&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M338.4 170.1 C359.4 170.1 355.6 137.5 376.6 137.5&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M338.4 170.1 C359.4 170.1 355.6 170.1 376.6 170.1&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M338.4 170.1 C359.4 170.1 355.6 202.7 376.6 202.7&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M329.9 277.8 C355.6 277.8 350.9 245.2 376.6 245.2&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M329.9 277.8 C355.6 277.8 350.9 277.8 376.6 277.8&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M329.9 277.8 C355.6 277.8 350.9 310.4 376.6 310.4&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M314.4 369.2 C348.6 369.2 342.4 353.0 376.6 353.0&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M314.4 369.2 C348.6 369.2 342.4 385.5 376.6 385.5&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;0&quot; data-d=&quot;0&quot; data-t=&quot;四层约束
让 1.5B 像个管家&quot;&amp;gt;&amp;lt;text x=&quot;58.4&quot; y=&quot;208.5&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;四层约束&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;58.4&quot; y=&quot;236.0&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;让 1.5B 像个管家&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;58.4&quot; y=&quot;244.4&quot; width=&quot;145.9&quot; height=&quot;3&quot; rx=&quot;1.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;1&quot; data-d=&quot;1&quot; data-t=&quot;第 1 层
Prompt 约束&quot;&amp;gt;&amp;lt;circle cx=&quot;241.2&quot; cy=&quot;62.4&quot; r=&quot;3.2&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;250.4&quot; y=&quot;56.4&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;第 1 层&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;250.4&quot; y=&quot;78.9&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;Prompt 约束&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;250.4&quot; y=&quot;85.8&quot; width=&quot;88.4&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;2&quot; data-d=&quot;2&quot; data-t=&quot;身份结构化 SYSTEM_IDENTITY&quot;&amp;gt;&amp;lt;circle cx=&quot;376.4&quot; cy=&quot;29.8&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;384.8&quot; y=&quot;34.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;身份结构化 SYSTEM_IDENTITY&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;384.8&quot; y=&quot;40.6&quot; width=&quot;189.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;3&quot; data-d=&quot;2&quot; data-t=&quot;代词映射 PRONOUN_MAP&quot;&amp;gt;&amp;lt;circle cx=&quot;376.4&quot; cy=&quot;62.4&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;384.8&quot; y=&quot;67.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;代词映射 PRONOUN_MAP&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;384.8&quot; y=&quot;73.2&quot; width=&quot;158.4&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;4&quot; data-d=&quot;2&quot; data-t=&quot;5 固定区块 + 3 条件区块&quot;&amp;gt;&amp;lt;circle cx=&quot;376.4&quot; cy=&quot;94.9&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;384.8&quot; y=&quot;99.5&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;5 固定区块 + 3 条件区块&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;384.8&quot; y=&quot;105.7&quot; width=&quot;146.1&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;5&quot; data-d=&quot;1&quot; data-t=&quot;第 2 层
Stop Tokens&quot;&amp;gt;&amp;lt;circle cx=&quot;241.2&quot; cy=&quot;170.1&quot; r=&quot;3.2&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;250.4&quot; y=&quot;164.1&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;第 2 层&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;250.4&quot; y=&quot;186.6&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;Stop Tokens&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;250.4&quot; y=&quot;193.6&quot; width=&quot;88.0&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;6&quot; data-d=&quot;2&quot; data-t=&quot;74 个截断标记&quot;&amp;gt;&amp;lt;circle cx=&quot;376.4&quot; cy=&quot;137.5&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;384.8&quot; y=&quot;142.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;74 个截断标记&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;384.8&quot; y=&quot;148.3&quot; width=&quot;88.1&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;7&quot; data-d=&quot;2&quot; data-t=&quot;系统标记 / 用户标记 / 结束标记&quot;&amp;gt;&amp;lt;circle cx=&quot;376.4&quot; cy=&quot;170.1&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;384.8&quot; y=&quot;174.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;系统标记 / 用户标记 / 结束标记&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;384.8&quot; y=&quot;180.9&quot; width=&quot;187.7&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;8&quot; data-d=&quot;2&quot; data-t=&quot;结构化标记的所有变体&quot;&amp;gt;&amp;lt;circle cx=&quot;376.4&quot; cy=&quot;202.7&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;384.8&quot; y=&quot;207.3&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;结构化标记的所有变体&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;384.8&quot; y=&quot;213.4&quot; width=&quot;136.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;9&quot; data-d=&quot;1&quot; data-t=&quot;第 3 层
后处理清理&quot;&amp;gt;&amp;lt;circle cx=&quot;241.2&quot; cy=&quot;277.8&quot; r=&quot;3.2&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;250.4&quot; y=&quot;271.8&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;第 3 层&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;250.4&quot; y=&quot;294.3&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;后处理清理&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;250.4&quot; y=&quot;301.3&quot; width=&quot;79.5&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;10&quot; data-d=&quot;2&quot; data-t=&quot;_clean_output() 共 320+ 行&quot;&amp;gt;&amp;lt;circle cx=&quot;376.4&quot; cy=&quot;245.2&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;384.8&quot; y=&quot;249.8&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;_clean_output() 共 320+ 行&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;384.8&quot; y=&quot;256.0&quot; width=&quot;162.6&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;11&quot; data-d=&quot;2&quot; data-t=&quot;句子级去重 / 标记移除 / 长度限制&quot;&amp;gt;&amp;lt;circle cx=&quot;376.4&quot; cy=&quot;277.8&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;384.8&quot; y=&quot;282.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;句子级去重 / 标记移除 / 长度限制&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;384.8&quot; y=&quot;288.6&quot; width=&quot;201.2&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;12&quot; data-d=&quot;2&quot; data-t=&quot;分阶段保护，避免清成空串&quot;&amp;gt;&amp;lt;circle cx=&quot;376.4&quot; cy=&quot;310.4&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;384.8&quot; y=&quot;315.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;分阶段保护，避免清成空串&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;384.8&quot; y=&quot;321.2&quot; width=&quot;163.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;13&quot; data-d=&quot;1&quot; data-t=&quot;第 4 层
意图分流&quot;&amp;gt;&amp;lt;circle cx=&quot;241.2&quot; cy=&quot;369.2&quot; r=&quot;3.2&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;250.4&quot; y=&quot;363.3&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;第 4 层&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;250.4&quot; y=&quot;385.8&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;意图分流&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;250.4&quot; y=&quot;392.7&quot; width=&quot;64.0&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;14&quot; data-d=&quot;2&quot; data-t=&quot;10 种意图各走各的路&quot;&amp;gt;&amp;lt;circle cx=&quot;376.4&quot; cy=&quot;353.0&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;384.8&quot; y=&quot;357.6&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;10 种意图各走各的路&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;384.8&quot; y=&quot;363.8&quot; width=&quot;128.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;15&quot; data-d=&quot;2&quot; data-t=&quot;能不用 LLM 就不用 LLM&quot;&amp;gt;&amp;lt;circle cx=&quot;376.4&quot; cy=&quot;385.5&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;384.8&quot; y=&quot;390.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;能不用 LLM 就不用 LLM&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;384.8&quot; y=&quot;396.3&quot; width=&quot;145.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;九、三个 bug 的修法&lt;/h2&gt;
&lt;p&gt;这部分的共同点是：&lt;strong&gt;都是很傻的 bug，但都只在真实数据下才会暴露&lt;/strong&gt;。而修法比 bug 本身更值得记。&lt;/p&gt;
&lt;h3&gt;bug 1：重复 15 次，因为分割符只有句号&lt;/h3&gt;
&lt;p&gt;现象是问某个问题时，「你叫什么名字？」重复了 &lt;strong&gt;15 次&lt;/strong&gt;。查看清理逻辑，去重是按句号切分的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sentences = [s.strip() for s in text.split(&apos;。&apos;) if s.strip()]
seen = set()
for sent in sentences:
    if sent_clean not in seen:
        seen.add(sent_clean)
        unique_sentences.append(sent_clean)
    else:
        break          # 检测到重复就停
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;问题一眼就能看出来：模型这次的重复用的是&lt;strong&gt;问号&lt;/strong&gt;分隔。&lt;code&gt;split(&apos;。&apos;)&lt;/code&gt; 切不开，于是 15 个重复被当成「一个超长句子」，&lt;code&gt;seen&lt;/code&gt; 里根本没有重复项，&lt;code&gt;break&lt;/code&gt; 永远不会触发。&lt;/p&gt;
&lt;p&gt;修法是用正则一次切掉所有句末标点：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 用正则切分所有句子（句号、问号、感叹号、中英文都要）
sentences = re.split(r&apos;[。？！.!?]+&apos;, text)
sentences = [s.strip() for s in sentences if s.strip()]

seen = set()
unique_sentences = []
for sent in sentences:
    sent_clean = re.sub(r&apos;\s+&apos;, &apos; &apos;, sent).strip()
    if sent_clean not in seen:
        seen.add(sent_clean)
        unique_sentences.append(sent_clean)
    else:
        break          # 一旦撞到重复，后面的全部丢弃

text = &apos;。&apos;.join(unique_sentences)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;顺带在切句之前还加了一道&lt;strong&gt;短语级&lt;/strong&gt;的预处理，专治「天津煎饼、天津煎饼、天津煎饼」这类词级复读：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;text = re.sub(r&apos;(\S+)(\s+\1)+&apos;, r&apos;\1&apos;, text)                       # 词级：A A A → A
text = re.sub(r&apos;(\S+(?:\s+\S+){0,10}?)(\s+\1)+&apos;, r&apos;\1&apos;, text)      # 短语级：AB AB AB → AB
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;bug 2：清理过度，答案被清成了空&lt;/h3&gt;
&lt;p&gt;修完重复之后，换了一个更隐蔽的问题：&lt;strong&gt;有 5 道题的回复变成了「抱歉」&lt;/strong&gt;——不是模型答不出来，是清理逻辑把整个回答删空了。&lt;/p&gt;
&lt;p&gt;原因是 &lt;code&gt;_clean_output()&lt;/code&gt; 是&lt;strong&gt;一条直路走到底&lt;/strong&gt;：每一步都只做删除，前面删多了，后面就没东西可删。任何一步过界，最终输出都是空串。&lt;/p&gt;
&lt;p&gt;修法是&lt;strong&gt;分阶段保护 + 回退&lt;/strong&gt;：每做完一步就检查长度，一旦发现被清空，立刻退回「原始文本去掉明显标记」的版本，而不是继续往下删：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;stage_lengths[&apos;step1_after_markers&apos;] = len(text)

# 方案C：分阶段检查——如果第一步后内容为空，回退到原始内容的前 100 字
if not text or len(text.strip()) &amp;lt; 2:
    logger.warning(
        f&quot;清理过度警告（第一步后为空）- 用户输入: &apos;{user_input[:50]}&apos;\n&quot;
        f&quot;  原始长度: {original_length} → 第一步后: {len(text)}&quot;
    )
    fallback = re.sub(r&apos;&amp;lt;&amp;lt;.*?&amp;gt;&amp;gt;&apos;, &apos;&apos;, original_text)
    fallback = re.sub(r&apos;\[.*?\]&apos;, &apos;&apos;, fallback)
    fallback = fallback[:100].strip()
    return fallback + &quot;...&quot; if fallback else &quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个改动还顺带带来一个副产品：每次触发回退都会打一条 warning 日志，里面带着&lt;strong&gt;原始文本&lt;/strong&gt;。于是「清理过度」从一个静默故障变成了可观测事件——后面调规则全靠这些日志。&lt;/p&gt;
&lt;h3&gt;bug 3：身份混淆，「我是热热」和「你叫什么名字」&lt;/h3&gt;
&lt;p&gt;Prompt 里加了身份结构后，还剩两个漏网的，都是&lt;strong&gt;后处理&lt;/strong&gt;没兜住：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;问&lt;/th&gt;
&lt;th&gt;实际回答&lt;/th&gt;
&lt;th&gt;问题&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;热热是谁&lt;/td&gt;
&lt;td&gt;我是 MAYA，我是你的私人助理。你叫什么名字？&lt;/td&gt;
&lt;td&gt;没回答，还反问&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;你的主人是谁&lt;/td&gt;
&lt;td&gt;我是 MAYA，他是我的主人。我的主人是热热。&lt;/td&gt;
&lt;td&gt;冗余，且用「他」而不是名字&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;修法是&lt;strong&gt;双向保险&lt;/strong&gt;：Prompt 里补明确规则（「当被问及热热是谁时，回答热热是我的主人」），后处理里再补一层替换与兜底：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 特殊处理：&quot;热热是谁&quot; —— 如果回答里既没&quot;热热&quot;也没&quot;主人&quot;，说明没答上
if &apos;热热是谁&apos; in user_input or &apos;热热是&apos; in user_input:
    if &apos;热热&apos; not in text and &apos;主人&apos; not in text:
        text = &quot;热热是我的主人。&quot;

# 反问用户是不允许的，直接替换掉
text = text.replace(&apos;你叫什么名字？&apos;, &apos;热热是我的主人&apos;)

# 冗余表达收敛
if &apos;你的主人是谁&apos; in user_input or &apos;主人是谁&apos; in user_input:
    text = text.replace(&apos;他是我的主人&apos;, &apos;热热是我的主人&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;这套流程本身也值得说一句&lt;/h3&gt;
&lt;p&gt;三个 bug 的修法都是小事，但&lt;strong&gt;修的顺序&lt;/strong&gt;是有章法的，而且被打包成了一套固定动作：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;发现问题（从 chat_logs / 运行日志里捞）
  ↓
写分析报告（docs/分析报告/）—— 先定性，再动手
  ↓
提修复方案（docs/修复方案/）—— 至少给两个方案并说清取舍
  ↓
代码实现
  ↓
写验证脚本（tests/验证*.py）
  ↓
出验证报告（docs/修复验证/）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对一个「模型行为不可预期」的系统来说，这套流程的价值在于：&lt;strong&gt;它把「感觉好点了」变成了「有报告和日志能复核的行为变化」&lt;/strong&gt;。上面那三个 bug，每个都有对应的分析报告、修复方案和验证报告躺在仓库里。&lt;/p&gt;
&lt;h2&gt;十、交付状态&lt;/h2&gt;
&lt;p&gt;先说结论：&lt;strong&gt;软件层已经收尾&lt;/strong&gt;；硬件层完成了初步落地——&lt;strong&gt;麦克风阵列与扬声器相关的作业已经结束&lt;/strong&gt;，&lt;strong&gt;物联网设备调度初步完成&lt;/strong&gt;。下面按这两层把交付内容摊开。&lt;/p&gt;
&lt;h3&gt;软件层：已完成&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模块&lt;/th&gt;
&lt;th&gt;状态&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;系统架构&lt;/td&gt;
&lt;td&gt;已完成&lt;/td&gt;
&lt;td&gt;13 个核心模块 + 三套 GUI，模块间接口稳定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM 对话&lt;/td&gt;
&lt;td&gt;已完成&lt;/td&gt;
&lt;td&gt;1.5B 全本地推理，四层防御体系成型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;意图路由&lt;/td&gt;
&lt;td&gt;已完成&lt;/td&gt;
&lt;td&gt;10 种意图，规则优先、LLM 兜底&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;长期记忆&lt;/td&gt;
&lt;td&gt;已完成&lt;/td&gt;
&lt;td&gt;存储 / 检索 / 写入规则与生命周期闭环&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;隐私与 ROOT&lt;/td&gt;
&lt;td&gt;已完成&lt;/td&gt;
&lt;td&gt;三库分级、口令校验、独立管理 GUI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;时空状态库&lt;/td&gt;
&lt;td&gt;已完成&lt;/td&gt;
&lt;td&gt;时间 / 位置 / 天气 / 设备状态均已接入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;语音链路&lt;/td&gt;
&lt;td&gt;已完成&lt;/td&gt;
&lt;td&gt;ASR（Whisper）与 TTS（Piper）服务代码、模型均就位&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;终端 GUI&lt;/td&gt;
&lt;td&gt;已完成&lt;/td&gt;
&lt;td&gt;对话、状态面板、设置三套界面可用&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;硬件层：初步落地&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;麦克风阵列与扬声器&lt;/strong&gt;：相关作业&lt;strong&gt;已经结束&lt;/strong&gt;——拾音、离线唤醒、合成播放这条本地音频链路的硬件侧接口已经打通。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;物联网设备调度&lt;/strong&gt;：&lt;strong&gt;初步完成&lt;/strong&gt;——MQTT 通路、设备注册表与 &lt;code&gt;DeviceSimulator&lt;/code&gt; 降级机制齐备，多设备指令下发与状态回传已经跑通。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;结题验收清单&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[x] 端到端语音链路——ASR → 对话 → TTS 完整跑通&lt;/li&gt;
&lt;li&gt;[x] 麦克风阵列拾音与扬声器播放联调&lt;/li&gt;
&lt;li&gt;[x] 意图路由 10 种意图全部生效，规则优先、LLM 兜底&lt;/li&gt;
&lt;li&gt;[x] 长期记忆写入规则与生命周期闭环&lt;/li&gt;
&lt;li&gt;[x] 隐私分级与 ROOT 模式口令校验&lt;/li&gt;
&lt;li&gt;[x] MQTT 设备通路 + &lt;code&gt;DeviceSimulator&lt;/code&gt; 降级&lt;/li&gt;
&lt;li&gt;[x] Piper TTS 中文模型部署与验证&lt;/li&gt;
&lt;li&gt;[x] 三套 GUI 交付（对话 / 状态面板 / 设置）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;交付资产&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类别&lt;/th&gt;
&lt;th&gt;数量&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Python 文件&lt;/td&gt;
&lt;td&gt;48 个（核心 32 + GUI 16）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;核心模块代码&lt;/td&gt;
&lt;td&gt;5681 行（不含 GUI）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GUI 代码&lt;/td&gt;
&lt;td&gt;3020 行 / 16 个文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;离线模型&lt;/td&gt;
&lt;td&gt;3 套（Whisper / Qwen2.5-1.5B / Piper）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLite 库&lt;/td&gt;
&lt;td&gt;3 个（记忆 / 隐私 / 时空状态）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;设计与说明文档&lt;/td&gt;
&lt;td&gt;35 篇 Markdown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GUI 测试脚本&lt;/td&gt;
&lt;td&gt;6 个&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;一条带走的结论：规则不是越多越好&lt;/h3&gt;
&lt;p&gt;这是整个项目里最反直觉、但被反复验证的一条：&lt;strong&gt;每加一条清理规则，就多一次「误伤正常回复」的机会&lt;/strong&gt;。bug 2 那个把回答清空的故障，本质就是规则太密导致的。&lt;/p&gt;
&lt;p&gt;所以最后定下来的分工是：&lt;strong&gt;能用 Prompt 约束解决的，就不在后处理里再拦一遍&lt;/strong&gt;——每一层只管自己那一段，而不是所有人都去补同一个坑。&lt;/p&gt;
&lt;h3&gt;可以延伸的方向&lt;/h3&gt;
&lt;p&gt;这套东西的价值不止于「家里有个会说话的盒子」。把 Prompt 当作调度层来用，能接着往外走：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;时空状态库继续外扩&lt;/strong&gt;：新闻摘要、日程提醒、空气质量——都是「查得到就能答得上」的确定性数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;记忆检索接上向量&lt;/strong&gt;：现在规则优先、向量只做排序，语义召回还有空间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RDK X5 上的部署适配与性能优化&lt;/strong&gt;：把 10 TOPS 的余量榨得更干净。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多房间多设备的状态同步&lt;/strong&gt;：从单机管家走向全屋协同。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;写在最后&lt;/h2&gt;
&lt;p&gt;回头看，MAYA2.0 这个项目真正的主角不是那个 1.5B 模型，而是&lt;strong&gt;围着它搭起来的那一圈约束&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;1.5B 的能力边界摆在那里——它会复读、会认错自己的名字、会把用户的名字搞反、会在没有约束时自称别家的助手。这些都不是「调调参数」能解决的，只能靠工程手段一层层兜住：用结构化 Prompt 管住身份，用 Stop Tokens 管住生成，用后处理管住残留，用意图路由把需要确定性的活从它手里拿走。&lt;/p&gt;
&lt;p&gt;而这里最关键的其实不是这套架构，是&lt;strong&gt;那个五模式对照实验&lt;/strong&gt;。没有它，你不会知道哪一层在真正起作用、哪一层只是装饰。做本地 AI 应用，模型选型只占一小半，剩下的一大半是搞清楚它会在哪里崩，然后提前在那些地方架上东西。&lt;/p&gt;
&lt;p&gt;这也是那一个月最实在的收获：&lt;strong&gt;当模型能力被锁死在 1.5B，工程手段的边际收益会变得非常高&lt;/strong&gt;。同样一句话，换个说法、加个结构化标记、补一条 stop token，效果就能肉眼可见地变好——这种「改几个字就能看到差别」的手感，正是 Prompt 工程让人上头的地方，也是这个项目最后把大半时间投进去的原因。&lt;/p&gt;
&lt;p&gt;和 1.0 相比，2.0 把重心从硬件挪到了软件；但对「热热」这个用户来说，两个版本想做的事其实是同一件——&lt;strong&gt;让技术待在本地、待在自己手里，还得真的能用&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;而 2.0 交出的是一套&lt;strong&gt;能听、能想、能说、能落地的完整软件栈&lt;/strong&gt;：麦克风阵列与扬声器这条音频链路已经打通，物联网设备调度也跑起来了——项目从「能打字聊天」走到了「能听会说话」。到这一步，这套东西才算真正装进了家里。&lt;/p&gt;
</content:encoded></item><item><title>MAYA1.0：物联网管家桌面机器人</title><link>https://ura2039.xyz/blog/posts/maya-iot-housekeeper-robot/</link><guid isPermaLink="true">https://ura2039.xyz/blog/posts/maya-iot-housekeeper-robot/</guid><description>一台能语音聊天、监测环境、自动管理智能家居的桌面机器人。STM32F103C8T6 管实时控制，树莓派 2B 管交互显示，配上 ASR-PRO 离线语音、OpenMV 机器视觉和四颗自组网的 ESP8266。</description><pubDate>Tue, 25 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;MAYA1.0&lt;/strong&gt; 是一台摆在小桌面上的物联网管家机器人。&lt;/p&gt;
&lt;p&gt;它想解决的问题很朴素：家里那些琐碎的事，开灯、关灯、看看屋里闷不闷、有没有人——能不能交给一个摆在桌面上的小家伙，让它用&lt;strong&gt;说话&lt;/strong&gt;的方式跟你商量着办完。&lt;/p&gt;
&lt;p&gt;具体来说，它围绕「&lt;strong&gt;感知环境 → 给出建议 → 动手执行 → 回显结果&lt;/strong&gt;」的闭环，把五件事串了起来：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;语音唤醒&lt;/strong&gt;——用特定的唤醒词叫醒它（ASR-PRO 离线语音模块）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;播报环境特征&lt;/strong&gt;——采集温湿度、光照和自身姿态，念给你听（传感器系统 + 喇叭）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;确认管理方案&lt;/strong&gt;——基于环境数据推荐一套家电管理方案，你可以同意，也可以拒绝后自己用语音改（语音模块）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;执行用户命令&lt;/strong&gt;——真正去开关家电（ESP8266 Wi-Fi 模块）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;显示家居状态&lt;/strong&gt;——LCD 上逐个回显各电器的开关状态（LCD 显示屏）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除了这条&quot;叫它才走&quot;的闭环，它还有几件后台常驻的活儿——持续环境检测、表情与肢体互动、以及基于视觉的自动照明，后面会细说。&lt;/p&gt;
&lt;p&gt;先看它跑起来的样子：&lt;/p&gt;
&lt;p&gt;&amp;lt;video src=&quot;/blog/videos/maya-iot-housekeeper-robot/demo.mp4?v=202609151207&quot; controls preload=&quot;metadata&quot; width=&quot;100%&quot;&amp;gt;&amp;lt;/video&amp;gt;&lt;/p&gt;
&lt;p&gt;:::note[关于这段视频]
俯拍视角一镜到底，记录 MAYA1.0 实机跑动，约 37 秒，无声。
:::&lt;/p&gt;
&lt;h2&gt;它是什么&lt;/h2&gt;
&lt;p&gt;它的核心能力可以归成三类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;语音聊天&lt;/strong&gt;——用自然语言对话的方式接收命令、反馈状态&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;环境监测&lt;/strong&gt;——持续采集周围环境特征，判断&quot;屋里现在是什么状况&quot;
&lt;ul&gt;
&lt;li&gt;温度、湿度（DHT11）&lt;/li&gt;
&lt;li&gt;光照强度（光敏电阻 + ADC）&lt;/li&gt;
&lt;li&gt;自身姿态（MPU6050 六轴）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;智能家居自动管理&lt;/strong&gt;——根据环境数据推荐家电管理方案，并真正去控制家电&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;figure class=&quot;maya-diagram&quot; data-maya=&quot;02-concept&quot;&amp;gt;
&amp;lt;div class=&quot;maya-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 720 343&quot; width=&quot;720&quot; height=&quot;343&quot; class=&quot;maya-svg&quot; role=&quot;img&quot; aria-label=&quot;MAYA1.0 物联网管家机器人的基本概念图：定义（基于 STM32 的智能助手桌面机器人、执行物联网自动化任务）、三类核心能力（语音聊天、环境监测、智能家居自动管理）以及各自的任务占比。&quot;&amp;gt;&amp;lt;g class=&quot;maya-links&quot;&amp;gt;&amp;lt;path d=&quot;M243.0 153.8 C263.8 153.8 260.0 46.1 280.8 46.1&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M243.0 153.8 C263.8 153.8 260.0 137.5 280.8 137.5&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M243.0 153.8 C263.8 153.8 260.0 261.5 280.8 261.5&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M322.1 46.1 C360.2 46.1 353.3 29.8 391.4 29.8&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M322.1 46.1 C360.2 46.1 353.3 62.4 391.4 62.4&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M353.0 137.5 C374.1 137.5 370.2 104.9 391.4 104.9&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M353.0 137.5 C374.1 137.5 370.2 137.5 391.4 137.5&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M353.0 137.5 C374.1 137.5 370.2 170.1 391.4 170.1&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M353.6 261.5 C374.4 261.5 370.6 212.7 391.4 212.7&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M353.6 261.5 C374.4 261.5 370.6 245.2 391.4 245.2&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M353.6 261.5 C374.4 261.5 370.6 277.8 391.4 277.8&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M353.6 261.5 C374.4 261.5 370.6 310.4 391.4 310.4&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;0&quot; data-d=&quot;0&quot; data-t=&quot;MAYA1.0
物联网管家机器人&quot;&amp;gt;&amp;lt;text x=&quot;88.6&quot; y=&quot;146.5&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;MAYA1.0&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;88.6&quot; y=&quot;174.0&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;物联网管家机器人&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;88.6&quot; y=&quot;182.3&quot; width=&quot;154.3&quot; height=&quot;3&quot; rx=&quot;1.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;1&quot; data-d=&quot;1&quot; data-t=&quot;定义&quot;&amp;gt;&amp;lt;circle cx=&quot;279.8&quot; cy=&quot;46.1&quot; r=&quot;3.2&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;289.0&quot; y=&quot;51.3&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;定义&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;289.0&quot; y=&quot;58.3&quot; width=&quot;33.1&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;2&quot; data-d=&quot;2&quot; data-t=&quot;基于 STM32 的智能助手桌面机器人&quot;&amp;gt;&amp;lt;circle cx=&quot;391.2&quot; cy=&quot;29.8&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;399.6&quot; y=&quot;34.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;基于 STM32 的智能助手桌面机器人&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;399.6&quot; y=&quot;40.6&quot; width=&quot;215.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;3&quot; data-d=&quot;2&quot; data-t=&quot;执行基于物联网的自动化任务&quot;&amp;gt;&amp;lt;circle cx=&quot;391.2&quot; cy=&quot;62.4&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;399.6&quot; y=&quot;67.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;执行基于物联网的自动化任务&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;399.6&quot; y=&quot;73.2&quot; width=&quot;177.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;4&quot; data-d=&quot;1&quot; data-t=&quot;核心能力&quot;&amp;gt;&amp;lt;circle cx=&quot;279.8&quot; cy=&quot;137.5&quot; r=&quot;3.2&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;289.0&quot; y=&quot;142.8&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;核心能力&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;289.0&quot; y=&quot;149.8&quot; width=&quot;64.0&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;5&quot; data-d=&quot;2&quot; data-t=&quot;语音聊天——用自然语言对话&quot;&amp;gt;&amp;lt;circle cx=&quot;391.2&quot; cy=&quot;104.9&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;399.6&quot; y=&quot;109.5&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;语音聊天——用自然语言对话&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;399.6&quot; y=&quot;115.7&quot; width=&quot;177.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;6&quot; data-d=&quot;2&quot; data-t=&quot;环境监测——温湿度 / 光照 / 姿态&quot;&amp;gt;&amp;lt;circle cx=&quot;391.2&quot; cy=&quot;137.5&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;399.6&quot; y=&quot;142.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;环境监测——温湿度 / 光照 / 姿态&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;399.6&quot; y=&quot;148.3&quot; width=&quot;200.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;7&quot; data-d=&quot;2&quot; data-t=&quot;智能家居自动管理——推荐并控制家电&quot;&amp;gt;&amp;lt;circle cx=&quot;391.2&quot; cy=&quot;170.1&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;399.6&quot; y=&quot;174.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;智能家居自动管理——推荐并控制家电&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;399.6&quot; y=&quot;180.9&quot; width=&quot;231.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;8&quot; data-d=&quot;1&quot; data-t=&quot;任务占比&quot;&amp;gt;&amp;lt;circle cx=&quot;279.8&quot; cy=&quot;261.5&quot; r=&quot;3.2&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;289.0&quot; y=&quot;266.8&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;任务占比&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;289.0&quot; y=&quot;273.8&quot; width=&quot;64.6&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;9&quot; data-d=&quot;2&quot; data-t=&quot;语音交互&quot;&amp;gt;&amp;lt;circle cx=&quot;391.2&quot; cy=&quot;212.7&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;399.6&quot; y=&quot;217.3&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;语音交互&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;399.6&quot; y=&quot;223.4&quot; width=&quot;56.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;10&quot; data-d=&quot;2&quot; data-t=&quot;环境探测&quot;&amp;gt;&amp;lt;circle cx=&quot;391.2&quot; cy=&quot;245.2&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;399.6&quot; y=&quot;249.8&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;环境探测&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;399.6&quot; y=&quot;256.0&quot; width=&quot;56.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;11&quot; data-d=&quot;2&quot; data-t=&quot;物联网管理&quot;&amp;gt;&amp;lt;circle cx=&quot;391.2&quot; cy=&quot;277.8&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;399.6&quot; y=&quot;282.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;物联网管理&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;399.6&quot; y=&quot;288.6&quot; width=&quot;69.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;12&quot; data-d=&quot;2&quot; data-t=&quot;娱乐&quot;&amp;gt;&amp;lt;circle cx=&quot;391.2&quot; cy=&quot;310.4&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;399.6&quot; y=&quot;315.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;娱乐&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;399.6&quot; y=&quot;321.2&quot; width=&quot;29.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;和市面上那些&quot;必须联网、必须连手机 App、开口先转三秒圈&quot;的语音助手不同，MAYA1.0 的定位是&lt;strong&gt;离线的、即时的、看得见摸得着的桌面伙伴&lt;/strong&gt;。它不依赖云端大模型，唤醒词一说就醒，命令一下就走。&lt;/p&gt;
&lt;h2&gt;一条完整的交互闭环&lt;/h2&gt;
&lt;p&gt;MAYA1.0 的工作流程不是简单的&quot;听到命令 → 执行&quot;，而是围绕&lt;strong&gt;环境感知&lt;/strong&gt;设计了一个五步闭环：&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;maya-diagram&quot; data-maya=&quot;03-workflow&quot;&amp;gt;
&amp;lt;div class=&quot;maya-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 720 471&quot; width=&quot;720&quot; height=&quot;471&quot; class=&quot;maya-svg&quot; role=&quot;img&quot; aria-label=&quot;MAYA1.0 的五步交互闭环流程图：语音唤醒、播报环境特征、确认管理方案、执行用户指令、显示家居状态，另有一条后台常驻的持续环境探测分支。&quot;&amp;gt;&amp;lt;g class=&quot;maya-links&quot;&amp;gt;&amp;lt;path d=&quot;M179.9 233.9 C200.7 233.9 196.9 46.1 217.7 46.1&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M179.9 233.9 C200.7 233.9 196.9 121.2 217.7 121.2&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M179.9 233.9 C200.7 233.9 196.9 196.4 217.7 196.4&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M179.9 233.9 C200.7 233.9 196.9 271.5 217.7 271.5&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M179.9 233.9 C200.7 233.9 196.9 346.7 217.7 346.7&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M179.9 233.9 C200.7 233.9 196.9 421.8 217.7 421.8&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M307.5 46.1 C345.6 46.1 338.7 29.8 376.8 29.8&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M307.5 46.1 C345.6 46.1 338.7 62.4 376.8 62.4&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M338.5 121.2 C359.6 121.2 355.7 104.9 376.8 104.9&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M338.5 121.2 C359.6 121.2 355.7 137.5 376.8 137.5&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M339.0 196.4 C359.8 196.4 356.1 180.1 376.8 180.1&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M339.0 196.4 C359.8 196.4 356.1 212.7 376.8 212.7&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M339.0 271.5 C359.8 271.5 356.1 255.2 376.8 255.2&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M339.0 271.5 C359.8 271.5 356.1 287.8 376.8 287.8&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M338.5 346.7 C359.6 346.7 355.7 330.4 376.8 330.4&quot; fill=&quot;none&quot; stroke=&quot;#10b981&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M338.5 346.7 C359.6 346.7 355.7 363.0 376.8 363.0&quot; fill=&quot;none&quot; stroke=&quot;#10b981&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M320.8 421.8 C351.6 421.8 346.0 405.5 376.8 405.5&quot; fill=&quot;none&quot; stroke=&quot;#ef4444&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M320.8 421.8 C351.6 421.8 346.0 438.1 376.8 438.1&quot; fill=&quot;none&quot; stroke=&quot;#ef4444&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;0&quot; data-d=&quot;0&quot; data-t=&quot;工作流程
五步闭环&quot;&amp;gt;&amp;lt;text x=&quot;101.9&quot; y=&quot;226.6&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;工作流程&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;101.9&quot; y=&quot;254.2&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;五步闭环&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;101.9&quot; y=&quot;262.5&quot; width=&quot;78.0&quot; height=&quot;3&quot; rx=&quot;1.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;1&quot; data-d=&quot;1&quot; data-t=&quot;① 语音唤醒&quot;&amp;gt;&amp;lt;circle cx=&quot;216.7&quot; cy=&quot;46.1&quot; r=&quot;3.2&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;225.9&quot; y=&quot;51.3&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;① 语音唤醒&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;225.9&quot; y=&quot;58.3&quot; width=&quot;81.6&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;2&quot; data-d=&quot;2&quot; data-t=&quot;ASR-PRO 离线语音模块&quot;&amp;gt;&amp;lt;circle cx=&quot;376.6&quot; cy=&quot;29.8&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.0&quot; y=&quot;34.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;ASR-PRO 离线语音模块&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.0&quot; y=&quot;40.6&quot; width=&quot;148.7&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;3&quot; data-d=&quot;2&quot; data-t=&quot;用特定唤醒词唤醒，不必一直“偷听”&quot;&amp;gt;&amp;lt;circle cx=&quot;376.6&quot; cy=&quot;62.4&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.0&quot; y=&quot;67.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;用特定唤醒词唤醒，不必一直“偷听”&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.0&quot; y=&quot;73.2&quot; width=&quot;213.3&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;4&quot; data-d=&quot;1&quot; data-t=&quot;② 播报环境特征&quot;&amp;gt;&amp;lt;circle cx=&quot;216.7&quot; cy=&quot;121.2&quot; r=&quot;3.2&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;225.9&quot; y=&quot;126.5&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;② 播报环境特征&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;225.9&quot; y=&quot;133.5&quot; width=&quot;112.6&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;5&quot; data-d=&quot;2&quot; data-t=&quot;传感器系统 + 喇叭&quot;&amp;gt;&amp;lt;circle cx=&quot;376.6&quot; cy=&quot;104.9&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.0&quot; y=&quot;109.5&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;传感器系统 + 喇叭&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.0&quot; y=&quot;115.7&quot; width=&quot;111.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;6&quot; data-d=&quot;2&quot; data-t=&quot;接收环境数据 → 处理 → 播报当前环境&quot;&amp;gt;&amp;lt;circle cx=&quot;376.6&quot; cy=&quot;137.5&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.0&quot; y=&quot;142.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;接收环境数据 → 处理 → 播报当前环境&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.0&quot; y=&quot;148.3&quot; width=&quot;232.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;7&quot; data-d=&quot;1&quot; data-t=&quot;③ 确认管理方案&quot;&amp;gt;&amp;lt;circle cx=&quot;216.7&quot; cy=&quot;196.4&quot; r=&quot;3.2&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;225.9&quot; y=&quot;201.6&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;③ 确认管理方案&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;225.9&quot; y=&quot;208.6&quot; width=&quot;113.2&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;8&quot; data-d=&quot;2&quot; data-t=&quot;基于环境特征主动推荐家电方案&quot;&amp;gt;&amp;lt;circle cx=&quot;376.6&quot; cy=&quot;180.1&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.0&quot; y=&quot;184.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;基于环境特征主动推荐家电方案&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.0&quot; y=&quot;190.9&quot; width=&quot;191.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;9&quot; data-d=&quot;2&quot; data-t=&quot;用户可同意，也可用语音自定义开关&quot;&amp;gt;&amp;lt;circle cx=&quot;376.6&quot; cy=&quot;212.7&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.0&quot; y=&quot;217.3&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;用户可同意，也可用语音自定义开关&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.0&quot; y=&quot;223.4&quot; width=&quot;217.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;10&quot; data-d=&quot;1&quot; data-t=&quot;④ 执行用户指令&quot;&amp;gt;&amp;lt;circle cx=&quot;216.7&quot; cy=&quot;271.5&quot; r=&quot;3.2&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;225.9&quot; y=&quot;276.8&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;④ 执行用户指令&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;225.9&quot; y=&quot;283.8&quot; width=&quot;113.2&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;11&quot; data-d=&quot;2&quot; data-t=&quot;ESP8266 Wi-Fi 模块&quot;&amp;gt;&amp;lt;circle cx=&quot;376.6&quot; cy=&quot;255.2&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.0&quot; y=&quot;259.8&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;ESP8266 Wi-Fi 模块&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.0&quot; y=&quot;266.0&quot; width=&quot;124.9&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;12&quot; data-d=&quot;2&quot; data-t=&quot;通过通讯模块远程控制家用电器&quot;&amp;gt;&amp;lt;circle cx=&quot;376.6&quot; cy=&quot;287.8&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.0&quot; y=&quot;292.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;通过通讯模块远程控制家用电器&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.0&quot; y=&quot;298.6&quot; width=&quot;191.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;13&quot; data-d=&quot;1&quot; data-t=&quot;⑤ 显示家居状态&quot;&amp;gt;&amp;lt;circle cx=&quot;216.7&quot; cy=&quot;346.7&quot; r=&quot;3.2&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;225.9&quot; y=&quot;351.9&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;⑤ 显示家居状态&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;225.9&quot; y=&quot;358.9&quot; width=&quot;112.6&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;14&quot; data-d=&quot;2&quot; data-t=&quot;LCD 显示屏&quot;&amp;gt;&amp;lt;circle cx=&quot;376.6&quot; cy=&quot;330.4&quot; r=&quot;2.4&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.0&quot; y=&quot;335.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;LCD 显示屏&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.0&quot; y=&quot;341.2&quot; width=&quot;73.2&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;15&quot; data-d=&quot;2&quot; data-t=&quot;执行成功后显示各电器开关状态&quot;&amp;gt;&amp;lt;circle cx=&quot;376.6&quot; cy=&quot;363.0&quot; r=&quot;2.4&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.0&quot; y=&quot;367.6&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;执行成功后显示各电器开关状态&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.0&quot; y=&quot;373.8&quot; width=&quot;190.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;16&quot; data-d=&quot;1&quot; data-t=&quot;持续环境探测
（后台常驻）&quot;&amp;gt;&amp;lt;circle cx=&quot;216.7&quot; cy=&quot;421.8&quot; r=&quot;3.2&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;225.9&quot; y=&quot;415.9&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;持续环境探测&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;225.9&quot; y=&quot;438.3&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;（后台常驻）&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;225.9&quot; y=&quot;445.3&quot; width=&quot;95.0&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;17&quot; data-d=&quot;2&quot; data-t=&quot;传感器系统 + 机器视觉模块&quot;&amp;gt;&amp;lt;circle cx=&quot;376.6&quot; cy=&quot;405.5&quot; r=&quot;2.4&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.0&quot; y=&quot;410.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;传感器系统 + 机器视觉模块&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.0&quot; y=&quot;416.3&quot; width=&quot;165.7&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;18&quot; data-d=&quot;2&quot; data-t=&quot;人脸数据 → 特定时间自动调整照明&quot;&amp;gt;&amp;lt;circle cx=&quot;376.6&quot; cy=&quot;438.1&quot; r=&quot;2.4&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;385.0&quot; y=&quot;442.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;人脸数据 → 特定时间自动调整照明&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;385.0&quot; y=&quot;448.9&quot; width=&quot;211.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 语音唤醒&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过 ASR-PRO 离线语音模块，用特定的唤醒词进行语音唤醒。唤醒是整条链路的第一道闸门——只有被叫到名字，机器人才开始工作，避免它无时无刻都在&quot;偷听&quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 播报环境特征&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;被唤醒后，主控芯片通过传感器系统接收环境特征数据进行处理，并用喇叭播报当前的环境特征。比如&quot;现在屋内温度 26 度，光照偏暗&quot;——先把它看到的告诉你。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 确认管理方案&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;基于环境特征数据，机器人会&lt;strong&gt;主动推荐&lt;/strong&gt;相应的家用电器管理方案。这里的关键是&quot;商量&quot;而不是&quot;独断&quot;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户可以&lt;strong&gt;拒绝&lt;/strong&gt;方案，并用语音命令自定义电器的开关状态&lt;/li&gt;
&lt;li&gt;也可以&lt;strong&gt;同意&lt;/strong&gt;原方案直接执行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. 执行用户命令&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过 ESP8266 WiFi 模块，实现对家用电器的远程控制。这是&quot;物联网&quot;三个字落地的地方——机器人本体只是个大脑，真正的执行器在墙上插座里。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. 显示家居状态&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;用户命令执行成功后，树莓派驱动的 &lt;strong&gt;LCD 显示屏&lt;/strong&gt;上显示各个家用电器的开关状态。让每一次操作都有明确回执，用户不用猜&quot;到底成功没有&quot;。&lt;/p&gt;
&lt;p&gt;闭环之外，还有三个让我比较得意的设计：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;持续环境检测&lt;/strong&gt;——环境数据不是问一次采一次，而是持续滚动更新，机器人始终知道屋里现在什么样&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;表情与肢体互动&lt;/strong&gt;——LCD 屏上展示各种有趣的表情，并&lt;strong&gt;相应改变机械结构&lt;/strong&gt;，做出生动的人机互动&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;基于视觉的自动照明&lt;/strong&gt;——通过机器视觉系统识别的人体数据，在特定时间调整灯光的自动照明方案（比如识别到有人进房间才亮灯，而不是靠定时器瞎猜）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;它内部其实是一台状态机&lt;/h3&gt;
&lt;p&gt;上面这五步听起来像一条直线，但真跑起来并不是&quot;一步一步往下走&quot;——用户随时可能插话、传感器随时可能报警、网络随时可能掉线。所以主控里跑的其实是一个&lt;strong&gt;四态状态机&lt;/strong&gt;，配合一个 50 ms 的心跳节拍。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./13-main-flow.png&quot; alt=&quot;主程序流程图&quot; /&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;状态&lt;/th&gt;
&lt;th&gt;触发条件&lt;/th&gt;
&lt;th&gt;行为&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ST_IDLE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;上电 / 40 s 无操作&lt;/td&gt;
&lt;td&gt;只等唤醒词，LCD 播待机呼吸动画&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ST_ACTIVE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;唤醒词命中&lt;/td&gt;
&lt;td&gt;采集环境、分发命令、处理视觉与网络事件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ST_ALARM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MPU6050 检测到倾倒&lt;/td&gt;
&lt;td&gt;播报警动画，暂停执行动作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ST_FAULT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;看门狗复位 / 自检失败&lt;/td&gt;
&lt;td&gt;显示错误动画，等待人工处理&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;:::important[为什么非要状态机]
如果没有状态机，&quot;语音命令&quot;和&quot;定时采集&quot;这两件事会在同一个 &lt;code&gt;while(1)&lt;/code&gt; 里互相打断：采集到一半来了语音，喇叭播报和数据处理就撞车。状态机把&lt;strong&gt;谁能在这个状态下做动作&lt;/strong&gt;写死，冲突就从&quot;偶发 bug&quot;变成了&quot;设计约束&quot;。
:::&lt;/p&gt;
&lt;h2&gt;系统架构：一颗 STM32，加一块树莓派&lt;/h2&gt;
&lt;p&gt;看第一眼会觉得奇怪：为什么不用一块树莓派全干了？树莓派性能强得多，能跑 Python、能驱动屏幕、能连 Wi-Fi。&lt;/p&gt;
&lt;p&gt;答案是&lt;strong&gt;实时性和动画流畅度要的其实是两种东西&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;STM32 负责的是&quot;永远按时回话&quot;——中断响应微秒级、没有操作系统调度抖动，语音模块的串口数据、传感器的定时采样、舵机的 PWM 时序，这些都不能等。而树莓派负责的是&quot;画面好看&quot;——它跑 Linux、有 GPU、能流畅放 24 fps 的表情动画，但它的 GPIO 时序受调度影响，拿它做实时控制是灾难。&lt;/p&gt;
&lt;p&gt;所以 MAYA1.0 用的是&lt;strong&gt;双核分工&lt;/strong&gt;：STM32 做决策，树莓派做脸。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./11-system-architecture.png&quot; alt=&quot;系统整体设计框图&quot; /&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;层&lt;/th&gt;
&lt;th&gt;硬件&lt;/th&gt;
&lt;th&gt;职责&lt;/th&gt;
&lt;th&gt;为什么是它&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;主控层&lt;/td&gt;
&lt;td&gt;STM32F103C8T6&lt;/td&gt;
&lt;td&gt;传感器采集、指令调度、实时控制&lt;/td&gt;
&lt;td&gt;Cortex-M3 @72 MHz，中断确定，无 OS 抖动&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;交互层&lt;/td&gt;
&lt;td&gt;树莓派 2B&lt;/td&gt;
&lt;td&gt;表情动画、状态可视化&lt;/td&gt;
&lt;td&gt;能跑 Linux + Python，图形性能足够&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;感知层&lt;/td&gt;
&lt;td&gt;ASR-PRO / OpenMV / DHT11 / LDR / MPU6050&lt;/td&gt;
&lt;td&gt;听、看、测环境&lt;/td&gt;
&lt;td&gt;各自封好接口，主控只读结果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;网络层&lt;/td&gt;
&lt;td&gt;ESP8266-01S × 4&lt;/td&gt;
&lt;td&gt;家电远程控制&lt;/td&gt;
&lt;td&gt;内置完整 TCP/IP 协议栈&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;执行层&lt;/td&gt;
&lt;td&gt;SG90 舵机 / L298N + 直流电机&lt;/td&gt;
&lt;td&gt;结构动作与行走&lt;/td&gt;
&lt;td&gt;扭矩够、驱动简单&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;:::tip[分工的边界怎么划]
判断标准只有一条：&lt;strong&gt;这件事能不能容忍几十毫秒的抖动？&lt;/strong&gt; 不能容忍的（语音收包、传感器采样、PWM 时序）放 STM32；能容忍的（动画、界面、日志）放树莓派。两边只用一条 115200 的串口连着，协议简单到就是几行文本，反而最不容易出问题。
:::&lt;/p&gt;
&lt;h2&gt;硬件选型&lt;/h2&gt;
&lt;p&gt;整台机器人由四个核心模块 + 一块显示屏构成：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./04-maya-hardware.jpg&quot; alt=&quot;MAYA1.0 实物：履带底盘、LCD 显示屏、摄像头与主控板&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;maya-diagram&quot; data-maya=&quot;05-hardware-modules&quot;&amp;gt;
&amp;lt;div class=&quot;maya-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 720 320&quot; width=&quot;720&quot; height=&quot;320&quot; class=&quot;maya-svg&quot; role=&quot;img&quot; aria-label=&quot;MAYA1.0 的四个主要硬件模块图：STM32F103C8T6 最小系统板负责主控决策，ASR-PRO 离线语音模块负责识别与互动，OpenMV 机器视觉模块负责视觉识别，ESP8266 Wi-Fi 通讯模块负责联网控制。&quot;&amp;gt;&amp;lt;g class=&quot;maya-links&quot;&amp;gt;&amp;lt;path d=&quot;M135.9 158.8 C156.7 158.8 152.9 46.1 173.7 46.1&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M135.9 158.8 C156.7 158.8 152.9 121.2 173.7 121.2&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M135.9 158.8 C156.7 158.8 152.9 196.4 173.7 196.4&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M135.9 158.8 C156.7 158.8 152.9 271.5 173.7 271.5&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M307.1 46.1 C355.9 46.1 347.0 29.8 395.8 29.8&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M307.1 46.1 C355.9 46.1 347.0 62.4 395.8 62.4&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M352.9 121.2 C376.5 121.2 372.2 104.9 395.8 104.9&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M352.9 121.2 C376.5 121.2 372.2 137.5 395.8 137.5&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M344.3 196.4 C372.6 196.4 367.5 180.1 395.8 180.1&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M344.3 196.4 C372.6 196.4 367.5 212.7 395.8 212.7&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M358.0 271.5 C378.8 271.5 375.0 255.2 395.8 255.2&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M358.0 271.5 C378.8 271.5 375.0 287.8 395.8 287.8&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;0&quot; data-d=&quot;0&quot; data-t=&quot;主要模块介绍&quot;&amp;gt;&amp;lt;text x=&quot;20.0&quot; y=&quot;165.3&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;主要模块介绍&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;20.0&quot; y=&quot;173.6&quot; width=&quot;115.9&quot; height=&quot;3&quot; rx=&quot;1.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;1&quot; data-d=&quot;1&quot; data-t=&quot;STM32F103C8T6
最小系统板&quot;&amp;gt;&amp;lt;circle cx=&quot;172.7&quot; cy=&quot;46.1&quot; r=&quot;3.2&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;181.9&quot; y=&quot;40.1&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;STM32F103C8T6&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;181.9&quot; y=&quot;62.6&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;最小系统板&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;181.9&quot; y=&quot;69.5&quot; width=&quot;125.2&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;2&quot; data-d=&quot;2&quot; data-t=&quot;主控：整体的决策与控制&quot;&amp;gt;&amp;lt;circle cx=&quot;395.6&quot; cy=&quot;29.8&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;404.0&quot; y=&quot;34.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;主控：整体的决策与控制&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;404.0&quot; y=&quot;40.6&quot; width=&quot;150.3&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;3&quot; data-d=&quot;2&quot; data-t=&quot;接收传感器数据 → 处理 → 决策运动 / 显示 / 推荐&quot;&amp;gt;&amp;lt;circle cx=&quot;395.6&quot; cy=&quot;62.4&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;404.0&quot; y=&quot;67.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;接收传感器数据 → 处理 → 决策运动 / 显示 / 推荐&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;404.0&quot; y=&quot;73.2&quot; width=&quot;295.7&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;4&quot; data-d=&quot;1&quot; data-t=&quot;ASR-PRO 离线语音模块&quot;&amp;gt;&amp;lt;circle cx=&quot;172.7&quot; cy=&quot;121.2&quot; r=&quot;3.2&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;181.9&quot; y=&quot;126.5&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;ASR-PRO 离线语音模块&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;181.9&quot; y=&quot;133.5&quot; width=&quot;171.0&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;5&quot; data-d=&quot;2&quot; data-t=&quot;天问 block 出品，支持图形化编程&quot;&amp;gt;&amp;lt;circle cx=&quot;395.6&quot; cy=&quot;104.9&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;404.0&quot; y=&quot;109.5&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;天问 block 出品，支持图形化编程&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;404.0&quot; y=&quot;115.7&quot; width=&quot;202.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;6&quot; data-d=&quot;2&quot; data-t=&quot;识别唤醒命令与声音命令，保证有效互动&quot;&amp;gt;&amp;lt;circle cx=&quot;395.6&quot; cy=&quot;137.5&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;404.0&quot; y=&quot;142.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;识别唤醒命令与声音命令，保证有效互动&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;404.0&quot; y=&quot;148.3&quot; width=&quot;244.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;7&quot; data-d=&quot;1&quot; data-t=&quot;OpenMV 机器视觉模块&quot;&amp;gt;&amp;lt;circle cx=&quot;172.7&quot; cy=&quot;196.4&quot; r=&quot;3.2&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;181.9&quot; y=&quot;201.6&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;OpenMV 机器视觉模块&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;181.9&quot; y=&quot;208.6&quot; width=&quot;162.4&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;8&quot; data-d=&quot;2&quot; data-t=&quot;基于 STM32 的开源视觉识别模块&quot;&amp;gt;&amp;lt;circle cx=&quot;395.6&quot; cy=&quot;180.1&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;404.0&quot; y=&quot;184.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;基于 STM32 的开源视觉识别模块&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;404.0&quot; y=&quot;190.9&quot; width=&quot;201.3&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;9&quot; data-d=&quot;2&quot; data-t=&quot;电脑端 IDE · MicroPython 编程&quot;&amp;gt;&amp;lt;circle cx=&quot;395.6&quot; cy=&quot;212.7&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;404.0&quot; y=&quot;217.3&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;电脑端 IDE · MicroPython 编程&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;404.0&quot; y=&quot;223.4&quot; width=&quot;186.3&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;10&quot; data-d=&quot;1&quot; data-t=&quot;ESP8266 Wi-Fi 通讯模块&quot;&amp;gt;&amp;lt;circle cx=&quot;172.7&quot; cy=&quot;271.5&quot; r=&quot;3.2&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;181.9&quot; y=&quot;276.8&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;ESP8266 Wi-Fi 通讯模块&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;181.9&quot; y=&quot;283.8&quot; width=&quot;176.0&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;11&quot; data-d=&quot;2&quot; data-t=&quot;低成本 Wi-Fi 微控制器&quot;&amp;gt;&amp;lt;circle cx=&quot;395.6&quot; cy=&quot;255.2&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;404.0&quot; y=&quot;259.8&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;低成本 Wi-Fi 微控制器&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;404.0&quot; y=&quot;266.0&quot; width=&quot;135.4&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;12&quot; data-d=&quot;2&quot; data-t=&quot;完整 TCP/IP 协议栈，物联网设备常用&quot;&amp;gt;&amp;lt;circle cx=&quot;395.6&quot; cy=&quot;287.8&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;404.0&quot; y=&quot;292.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;完整 TCP/IP 协议栈，物联网设备常用&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;404.0&quot; y=&quot;298.6&quot; width=&quot;228.1&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h3&gt;（1）STM32F103C8T6 最小系统板——主控&lt;/h3&gt;
&lt;p&gt;用 STM32 单片机作为主控芯片，负责&lt;strong&gt;整体的决策和控制&lt;/strong&gt;。通过接收传感器数据、执行数据处理和逻辑，决策机器人的运动、显示、推荐方案等操作。&lt;/p&gt;
&lt;p&gt;选它的理由是&lt;strong&gt;实时性和确定性&lt;/strong&gt;：这颗 Cortex-M3 内核的芯片主频 72MHz、64KB Flash、20KB SRAM，虽然远不如树莓派&quot;能跑大程序&quot;，但它没有操作系统调度的不确定性，中断响应是微秒级的。对于一个要实时响应语音唤醒、实时驱动舵机做表情的桌面机器人来说，&lt;strong&gt;永远按时回话比偶尔能干大事更重要&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;所谓&quot;最小系统板&quot;，就是只保留芯片运行所必需的最小外围电路（8 MHz 晶振、复位、电源、下载口），便宜、干净、把接口全部留给我们自己规划。时钟一路经过内部 PLL 倍频到 72 MHz，同时开了独立看门狗（IWDG）和窗口看门狗（WWDG）——只要程序跑飞或者卡死循环，硬件自己把机器复位，不需要人爬上桌子拔电。&lt;/p&gt;
&lt;p&gt;电源这边是 LM2596 先把电池降到 5 V，再经 LD1117 稳压出 3.3 V；另外引了一路分压到 ADC，用来实时监测电量、做欠压保护。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./16-sensor-wiring.png&quot; alt=&quot;STM32 最小系统板与两路环境传感器的接线：左侧光敏电阻模块、右侧 DHT11，红黑为电源与地、黄绿为信号线&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;（2）ASR-PRO 离线语音模块——耳朵和嘴&lt;/h3&gt;
&lt;p&gt;ASR-PRO 是天问 block 公司发布的离线语音模块，可以用 C++ 和电脑端 IDE 的&lt;strong&gt;图形化编程&lt;/strong&gt;方式来开发。用于识别人的唤醒命令和其他声音命令，保证机器人和用户的有效互动。&lt;/p&gt;
&lt;p&gt;&quot;离线&quot;这两个字是重点：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;离线语音（MAYA1.0 采用）&lt;/th&gt;
&lt;th&gt;在线语音&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;延迟&lt;/td&gt;
&lt;td&gt;毫秒级&lt;/td&gt;
&lt;td&gt;通常 1 秒以上&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;网络&lt;/td&gt;
&lt;td&gt;不需要&lt;/td&gt;
&lt;td&gt;断网即瘫&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;隐私&lt;/td&gt;
&lt;td&gt;音频不出设备&lt;/td&gt;
&lt;td&gt;上传云端&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;成本&lt;/td&gt;
&lt;td&gt;一颗模块&lt;/td&gt;
&lt;td&gt;持续调用费用&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;代价是它只认识你预先定义好的一批命令词。但对管家机器人这个场景来说，&lt;strong&gt;开灯 / 关灯 / 现在几度这类封闭指令集&lt;/strong&gt;恰恰是最需要的部分。&lt;/p&gt;
&lt;p&gt;硬件上它通过 3.3 V TTL 电平的 UART 跟 STM32 通信，波特率 115200；前端是一颗带降噪的 MEMS 数字麦克风，电源上加了一级 LC 滤波专门滤掉 50 Hz 的电网干扰。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./06-asr-pro.png&quot; alt=&quot;ASR-PRO 离线语音模块&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;（3）OpenMV 机器视觉模块——眼睛&lt;/h3&gt;
&lt;p&gt;基于 STM32 微处理器的开源视觉识别模块，可用电脑端 IDE 上的 &lt;strong&gt;MicroPython&lt;/strong&gt; 编程。MAYA1.0 用的是 OpenMV Cam M7：1/4″ CMOS 摄像头加一颗 STM32F7 内核，能在 640×480 下跑到 30 fps，板上就把基础图像算法算完，只把结果吐出来。&lt;/p&gt;
&lt;p&gt;它的价值在于把&quot;图像采集 + 算法处理 + 结果输出&quot;封装成一块可以直接读结果的模块：你不用去啃摄像头驱动的时序，直接 &lt;code&gt;find_blobs()&lt;/code&gt;、&lt;code&gt;find_features()&lt;/code&gt; 就能拿到目标坐标。MAYA1.0 主要用它做两件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;人脸检测&lt;/strong&gt;——识别到主人来了就触发个性化问候&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;环境亮度分析&lt;/strong&gt;——跟光敏电阻互为补充，极端光照下也能判断环境&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;结果通过 UART 以 JSON 形式上报，简单到就是 &lt;code&gt;{&quot;face&quot;:true}&lt;/code&gt; 或 &lt;code&gt;{&quot;brightness&quot;:75}&lt;/code&gt; 这种程度。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./07-openmv.png&quot; alt=&quot;OpenMV Cam 视觉模块&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;（4）ESP8266 WiFi 通讯模块——连上世界&lt;/h3&gt;
&lt;p&gt;市面上常用的一种&lt;strong&gt;低成本 Wi-Fi 微控制器&lt;/strong&gt;，内置一颗 32 位 Tensilica L106，主频 80 MHz，带完整的 TCP/IP 协议栈。板载天线加低功耗睡眠，非常适合嵌入式物联网场景。&lt;/p&gt;
&lt;p&gt;严格说 ESP8266 自己就是一颗能独立跑程序的主控，MAYA1.0 这里把它当&lt;strong&gt;通信协处理器&lt;/strong&gt;用——STM32 负责决策，ESP8266 负责把决策变成网络请求发出去控制家电。这种分工让主控不必负担 TCP/IP 协议栈的开销。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./08-esp8266.png&quot; alt=&quot;ESP8266-01S Wi-Fi 模块&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;（5）树莓派 2B + 3.5 寸 LCD——脸&lt;/h3&gt;
&lt;p&gt;显示模块采用 3.5 寸 LCD 显示屏，配合树莓派 2B 驱动，可显示文字、图形、动画和机器人互动表情，清晰直观地呈现环境监测数据、家电控制状态及人机交互界面。&lt;/p&gt;
&lt;p&gt;树莓派这一侧实际上只有一个开机自启的 Python 程序：监听串口、解析命令、用 pygame 播放动画。命令帧简单到就是把名字念一遍——&lt;code&gt;ANIM:SMILE&lt;/code&gt;、&lt;code&gt;ANIM:SAD&lt;/code&gt;、&lt;code&gt;ANIM:IDLE&lt;/code&gt;。这种&quot;简陋&quot;的协议是故意的：两边各自独立开发，只要串口对上就能联调。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./09-raspberrypi-lcd.png&quot; alt=&quot;树莓派 2B 与 3.5 寸 LCD&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;（6）SG90 舵机与直流电机——手和脚&lt;/h3&gt;
&lt;p&gt;机器人结构动作和转向控制选用 SG90 舵机，STM32 输出 50 Hz 的 PWM 信号控制 0°–180° 角度，用来做头部细微动作。舵机电源线上加了一级 RC 抑制电路，降低开关噪声对主控的干扰。&lt;/p&gt;
&lt;p&gt;$$ \alpha = \frac{\tau - 0.5,\text{ms}}{2.0,\text{ms}} \times 180^\circ, \qquad \tau = \frac{\text{CCR}}{\text{ARR}+1} \cdot 20,\text{ms} $$&lt;/p&gt;
&lt;p&gt;底盘则是两台 9 V 直流电机驱动履带，通过 L298N 双 H 桥实现双向调速；STM32 用 PWM 占空比控制速度，电机轴上的光电编码器把实际转速反馈回来，为后续的精确定位和路径规划留好接口。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./10-sg90.png&quot; alt=&quot;SG90 舵机&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;（7）机械结构与外观——3D 打印外壳 + 积木骨架&lt;/h3&gt;
&lt;p&gt;整机外观走的是机甲风格，外壳用 PLA 材料 3D 打印——既能支持复杂曲面和棱角，强度也够。建模在 SolidWorks 里完成：先画各外壳零件的草图，用拉伸、扫掠、倒角做出主体形状，再导出 STL 交给打印机。&lt;/p&gt;
&lt;p&gt;骨架部分则是&lt;strong&gt;三百片模块化积木&lt;/strong&gt;拼出来的。每个模块按标准化接口设计，既能快速装配，又能灵活调整布局去适配不同传感器、舵机和电机的位置。这个设计本质上是为迭代服务的——第一版装配完发现舵机线会干涉，改几个卡扣形状就能重来，不用重新打印整块外壳。经过多次迭代，我优化了模块连接的卡扣形状和定位销孔，保证结构在承载动力输出、反复运动之后不松动、不位移。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./15-3d-printed.png&quot; alt=&quot;3D 打印并拼装的实物外观&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;一张自己组的局域网：四颗 ESP8266&lt;/h2&gt;
&lt;p&gt;家用 Wi-Fi 往往是整个系统里最不可控的一环——路由器品牌各异、信号覆盖不均、还可能根本没有网。所以在网络设计上 MAYA1.0 干脆&lt;strong&gt;不依赖家里的路由器&lt;/strong&gt;，而是自组了一个小型局域网。&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;maya-diagram&quot; data-maya=&quot;12-esp8266-topology&quot;&amp;gt;
&amp;lt;div class=&quot;maya-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 720 222&quot; width=&quot;720&quot; height=&quot;222&quot; class=&quot;maya-svg&quot; role=&quot;img&quot; aria-label=&quot;MAYA1.0 的一主三从自组局域网拓扑图：STM32 主控连接 ASR-PRO 离线语音模块，ESP8266-A 以 AP 模式作 192.168.4.1 网关，三台 STA 模式的节点分别占用 192.168.4.2 至 192.168.4.4。&quot;&amp;gt;&amp;lt;g class=&quot;maya-links&quot;&amp;gt;&amp;lt;path d=&quot;M203.6 79.2 C224.4 79.2 220.7 31.2 241.4 31.2&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M203.6 79.2 C224.4 79.2 220.7 127.2 241.4 127.2&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M421.0 127.2 C441.8 127.2 438.0 75.0 458.8 75.0&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M421.0 127.2 C441.8 127.2 438.0 127.2 458.8 127.2&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M421.0 127.2 C441.8 127.2 438.0 179.3 458.8 179.3&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;0&quot; data-d=&quot;0&quot; data-t=&quot;STM32 主控&quot;&amp;gt;&amp;lt;text x=&quot;97.2&quot; y=&quot;85.7&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;STM32 主控&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;97.2&quot; y=&quot;94.0&quot; width=&quot;106.5&quot; height=&quot;3&quot; rx=&quot;1.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;1&quot; data-d=&quot;1&quot; data-t=&quot;ASR-PRO 离线语音&quot;&amp;gt;&amp;lt;circle cx=&quot;240.4&quot; cy=&quot;31.2&quot; r=&quot;3.2&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;249.6&quot; y=&quot;36.5&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;ASR-PRO 离线语音&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;249.6&quot; y=&quot;43.5&quot; width=&quot;139.7&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;2&quot; data-d=&quot;1&quot; data-t=&quot;ESP8266-A（AP 模式）
192.168.4.1&quot;&amp;gt;&amp;lt;circle cx=&quot;240.4&quot; cy=&quot;127.2&quot; r=&quot;3.2&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;249.6&quot; y=&quot;121.2&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;ESP8266-A（AP 模式）&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;249.6&quot; y=&quot;143.7&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;192.168.4.1&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;249.6&quot; y=&quot;150.7&quot; width=&quot;171.3&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;3&quot; data-d=&quot;2&quot; data-t=&quot;ESP8266-B（STA 模式）
192.168.4.2&quot;&amp;gt;&amp;lt;circle cx=&quot;458.6&quot; cy=&quot;75.0&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;467.0&quot; y=&quot;69.9&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;ESP8266-B（STA 模式）&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;467.0&quot; y=&quot;89.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;192.168.4.2&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;467.0&quot; y=&quot;95.6&quot; width=&quot;154.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;4&quot; data-d=&quot;2&quot; data-t=&quot;ESP8266-C（STA 模式）
192.168.4.3&quot;&amp;gt;&amp;lt;circle cx=&quot;458.6&quot; cy=&quot;127.2&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;467.0&quot; y=&quot;122.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;ESP8266-C（STA 模式）&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;467.0&quot; y=&quot;141.6&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;192.168.4.3&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;467.0&quot; y=&quot;147.8&quot; width=&quot;155.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;5&quot; data-d=&quot;2&quot; data-t=&quot;ESP8266-D（STA 模式）
192.168.4.4&quot;&amp;gt;&amp;lt;circle cx=&quot;458.6&quot; cy=&quot;179.3&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;467.0&quot; y=&quot;174.2&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;ESP8266-D（STA 模式）&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;467.0&quot; y=&quot;193.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;192.168.4.4&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;467.0&quot; y=&quot;199.9&quot; width=&quot;155.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;text x=&quot;222.5&quot; y=&quot;50.2&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.55&quot;&amp;gt;UART&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;439.9&quot; y=&quot;96.1&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.55&quot;&amp;gt;Wi-Fi / UDP&amp;lt;/text&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;方案是一主三从：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;主控端 A&lt;/strong&gt; 配置为 AP 模式，自己开热点，供从控端接入&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;从控端 B / C / D&lt;/strong&gt; 连上 A，各自监听同一个 UDP 端口&lt;/li&gt;
&lt;li&gt;STM32 通过 UART 把控制帧交给 A，A 再广播出去&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;指令格式简单到可以直接在纸上推演：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;指令&lt;/th&gt;
&lt;th&gt;方向&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LIGHT_ON&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A → B/C/D&lt;/td&gt;
&lt;td&gt;对应从控 GPIO0 拉高，继电器吸合&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LIGHT_OFF&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A → B/C/D&lt;/td&gt;
&lt;td&gt;从控 GPIO0 拉低，继电器断开&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OK:LIGHT_ON&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从控 → A&lt;/td&gt;
&lt;td&gt;执行回执，主控据此更新 LCD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ERR:UNKNOWN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从控 → A&lt;/td&gt;
&lt;td&gt;未知指令，拒绝执行&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;:::warning[为什么不走 TCP]
我的第一版设计里写的是&quot;TCP/UDP 混合&quot;，但实际调试时我把控制帧全改成了 UDP。原因很直接：&lt;strong&gt;这种指令是幂等的且过期就该丢&lt;/strong&gt;。&lt;code&gt;LIGHT_ON&lt;/code&gt; 晚到 3 秒送达，效果和立刻送到完全一样；但 TCP 的自动重传会让一条已经过期的命令在后面某个时刻突然生效——用户早就走开了，灯突然亮起来，比丢包更糟。UDP 的&quot;不可靠&quot;在这个场景里反而是一种保护。&lt;/p&gt;
&lt;p&gt;代价是需要自己做应用层确认，也就是表里那两条回执帧。
:::&lt;/p&gt;
&lt;h2&gt;代码分析&lt;/h2&gt;
&lt;p&gt;下面是我在调试过程中真正跑通的几段关键实现。整个项目按模块拆成了 6 个部分：主循环调度、环境采集、语音解析、视觉检测、网络通信、显示驱动。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
如果你想复现一套类似的系统，&lt;strong&gt;先把&quot;STM32 发一行字符串 → 树莓派原样打印出来&quot;这条最小链路跑通&lt;/strong&gt;，再去接传感器和语音。我一开始跳过了这步直接上全套，第一次联调时十几个模块同时报错，根本不知道从哪一条查起。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;主循环：心跳 + 事件队列 + 状态机&lt;/h3&gt;
&lt;p&gt;主控的核心是&lt;strong&gt;不阻塞&lt;/strong&gt;。任何一件事都不允许在主循环里等待——否则语音命令会被传感器采样卡住，或者反过来。所以所有子程序都是&quot;有结果就置一个事件位&quot;，主循环只负责按优先级消费。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* MAYA1.0 主控：50 ms 心跳调度 + 事件队列 + 四态状态机 */
#include &quot;app.h&quot;

typedef enum { ST_IDLE, ST_ACTIVE, ST_ALARM, ST_FAULT } sys_state_t;

#define EV_ENV     (1UL &amp;lt;&amp;lt; 0)   /* 环境数据就绪 */
#define EV_VOICE   (1UL &amp;lt;&amp;lt; 1)   /* 收到语音命令 */
#define EV_VISION  (1UL &amp;lt;&amp;lt; 2)   /* 视觉结果到达 */
#define EV_NET     (1UL &amp;lt;&amp;lt; 3)   /* 家电回执到达 */
#define EV_ALARM   (1UL &amp;lt;&amp;lt; 4)   /* 姿态异常 */
#define EV_TIMEOUT (1UL &amp;lt;&amp;lt; 5)   /* 40 s 无人操作 */

static volatile uint32_t g_events;
static sys_state_t       g_state = ST_IDLE;
static uint16_t          g_idle_ticks;

void app_post_event(uint32_t ev) { g_events |= ev; }

int main(void)
{
    HAL_Init();
    SystemClock_Config();       /* 8 MHz 晶振经 PLL 拉到 72 MHz */
    MX_GPIO_Init();
    MX_USART1_UART_Init();      /* ASR-PRO  115200 8N1 */
    MX_USART2_UART_Init();      /* OpenMV   115200 8N1 */
    MX_USART3_UART_Init();      /* ESP8266  115200 8N1 */
    MX_ADC1_Init();             /* LDR 光敏电阻 */
    MX_I2C1_Init();             /* MPU6050 */
    MX_TIM1_Init();             /* SG90 50 Hz PWM */

    env_init(); voice_init(); vision_init(); net_init();
    HAL_TIM_PWM_Start(&amp;amp;htim1, TIM_CHANNEL_1);
    display_anim(&quot;ANIM:IDLE&quot;);
    IWDG_ReloadCounter();

    uint32_t beat = HAL_GetTick();
    while (1) {
        IWDG_ReloadCounter();               /* 喂狗：跑飞了硬件自动复位 */

        if (HAL_GetTick() - beat &amp;gt;= 50) {   /* 50 ms 心跳，不用 delay */
            beat = HAL_GetTick();
            app_post_event(EV_ENV);
            if (mpu6050_tilted()) app_post_event(EV_ALARM);
            if (++g_idle_ticks &amp;gt; 800) app_post_event(EV_TIMEOUT);
        }

        uint32_t ev = g_events;
        if (!ev) continue;                  /* 没事件就放行，让中断去跑 */
        g_events &amp;amp;= ~ev;                    /* 边沿消费，不重复触发 */
        if (!(ev &amp;amp; EV_TIMEOUT)) g_idle_ticks = 0;

        if (ev &amp;amp; EV_ALARM) g_state = ST_ALARM;
        if (ev &amp;amp; EV_VOICE) g_state = ST_ACTIVE;

        switch (g_state) {
        case ST_IDLE:
            break;                          /* 低位待机，只等唤醒词 */
        case ST_ACTIVE:
            if (ev &amp;amp; EV_ENV)    env_report();       /* 采集 → 播报 */
            if (ev &amp;amp; EV_VOICE)  voice_dispatch();   /* 分发命令 */
            if (ev &amp;amp; EV_VISION) vision_handle();    /* 有人/偏暗 */
            if (ev &amp;amp; EV_NET)    net_handle();       /* 回执 → 刷 LCD */
            if (ev &amp;amp; EV_TIMEOUT) {
                g_state = ST_IDLE;
                display_anim(&quot;ANIM:IDLE&quot;);
            }
            break;
        case ST_ALARM: display_anim(&quot;ANIM:SAD&quot;);   break;
        case ST_FAULT: display_anim(&quot;ANIM:ERROR&quot;); break;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意这里有个反直觉的写法：心跳用的是 &lt;code&gt;if (HAL_GetTick() - beat &amp;gt;= 50)&lt;/code&gt; 而不是 &lt;code&gt;HAL_Delay(50)&lt;/code&gt;。区别在于 &lt;code&gt;HAL_Delay&lt;/code&gt; 会让 CPU 空转 50 ms，这期间来的串口数据只能靠中断往缓冲区里塞，主循环完全僵住。用时间戳比较则让主循环跑满速，一旦有事件立刻处理，代价只是 CPU 占用高一点——对一块插着电的桌面机器人来说，这个交换很划算。&lt;/p&gt;
&lt;p&gt;:::caution[别在中断里做解析]
&lt;code&gt;app_post_event()&lt;/code&gt; 只做了一件 &lt;code&gt;g_events |= ev&lt;/code&gt;，这是刻意的。我一开始图省事，在 UART 中断回调里直接解析语音命令并调用处理函数，结果串口一帧还没接收完，下一帧的字节就把缓冲区覆盖了——表现为&quot;偶尔识别错命令&quot;，而且几乎不可复现。&lt;strong&gt;中断里只搬字节，解析全部留给主循环&lt;/strong&gt;，这个规矩定死之后这类 bug 就绝迹了。
:::&lt;/p&gt;
&lt;h3&gt;环境采集：DHT11 时序 + LDR 双重滤波&lt;/h3&gt;
&lt;p&gt;环境数据是后面一切推荐逻辑的依据，所以这一段宁可慢一点也不能脏。不过 DHT11 自己也有采样间隔的硬要求——主循环那 50 ms 的心跳只是&quot;去问一声&quot;，真正拿到新数据其实是一秒一次。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* DHT11 走单总线，LDR 走 12 位 ADC —— 两个都得滤干净才敢上报 */
#include &quot;env.h&quot;
#include &amp;lt;string.h&amp;gt;

#define LDR_WIN 8

static uint8_t  ldr_idx;
static uint16_t ldr_hist[LDR_WIN];

/* —— DHT11：主机拉低 20 ms 起始，然后逐位读 40 bit —— */
static uint8_t dht11_read(uint8_t *temp, uint8_t *humi)
{
    uint8_t  data[5] = {0};
    uint32_t t;

    HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET);
    HAL_Delay(20);
    HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET);

    t = HAL_GetTick();                    /* 等 80 us 低 + 80 us 高应答 */
    while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET)
        if (HAL_GetTick() - t &amp;gt; 5) return 0;
    t = HAL_GetTick();
    while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET)
        if (HAL_GetTick() - t &amp;gt; 5) return 0;

    for (uint8_t i = 0; i &amp;lt; 40; i++) {    /* 每位以 50 us 低电平开头 */
        while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET) {}
        uint32_t high = 0;
        while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET) high++;
        data[i / 8] &amp;lt;&amp;lt;= 1;
        if (high &amp;gt; 20) data[i / 8] |= 1;  /* 高电平长 = 1，短 = 0 */
    }

    if ((uint8_t)(data[0] + data[1] + data[2] + data[3]) != data[4])
        return 0;                         /* 校验和不对，整帧丢掉 */

    *humi = data[0];
    *temp = data[2];
    return 1;
}

/* —— LDR：8 次滑动窗口排序后掐头去尾，压掉日光灯 100 Hz 闪变 —— */
static uint16_t ldr_filtered(void)
{
    ADC_ChannelConfTypeDef cfg = {0};
    cfg.Channel      = LDR_ADC_CH;
    cfg.Rank         = 1;
    cfg.SamplingTime = ADC_SAMPLETIME_71CYCLES_5;
    HAL_ADC_ConfigChannel(&amp;amp;hadc1, &amp;amp;cfg);

    HAL_ADC_Start(&amp;amp;hadc1);
    if (HAL_ADC_PollForConversion(&amp;amp;hadc1, 10) != HAL_OK) return 0xFFFF;
    uint16_t raw = (uint16_t)HAL_ADC_GetValue(&amp;amp;hadc1);
    HAL_ADC_Stop(&amp;amp;hadc1);

    ldr_hist[ldr_idx++ % LDR_WIN] = raw;

    uint16_t tmp[LDR_WIN];
    memcpy(tmp, ldr_hist, sizeof(tmp));
    for (uint8_t i = 0; i &amp;lt; LDR_WIN - 1; i++) {
        for (uint8_t j = 0; j &amp;lt; LDR_WIN - 1 - i; j++) {
            if (tmp[j] &amp;gt; tmp[j + 1]) {
                uint16_t s = tmp[j];
                tmp[j] = tmp[j + 1];
                tmp[j + 1] = s;
            }
        }
    }

    uint32_t sum = 0;
    for (uint8_t i = 1; i &amp;lt; LDR_WIN - 1; i++) sum += tmp[i];
    return (uint16_t)(sum / (LDR_WIN - 2));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;光敏电阻的输出电压跟照度并不是线性的，它和一个 10 kΩ 固定电阻串起来分压，ADC 采到的其实是分压点：&lt;/p&gt;
&lt;p&gt;$$ V_{ADC} = V_{CC} \cdot \frac{R_{fix}}{R_{LDR} + R_{fix}} $$&lt;/p&gt;
&lt;p&gt;所以代码里没有做&quot;照度换算&quot;，只把 ADC 原始值做滤波后直接和阈值比。这是刻意的取舍：&lt;strong&gt;换成勒克斯需要标定，而我只需要一个&quot;要不要开灯&quot;的判断&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;语音命令解析：环形缓冲 + 命令表&lt;/h3&gt;
&lt;p&gt;ASR-PRO 识别出命令后，通过串口发一行 &lt;code&gt;CMD:&amp;lt;id&amp;gt;&lt;/code&gt;。主控这边不解析语义，只做 ID 到函数的映射。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#define RX_RING 64
static uint8_t           ring[RX_RING];
static volatile uint16_t w_idx;

/* UART1 中断回调：只搬字节，不做任何解析 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart-&amp;gt;Instance != USART1) return;
    w_idx = (uint16_t)((w_idx + 1) % RX_RING);
    HAL_UART_Receive_IT(&amp;amp;huart1, &amp;amp;ring[w_idx], 1);
}

/* 命令表：ID 必须和 ASR-PRO IDE 里词条的排列顺序一一对应 */
typedef struct { uint8_t id; const char *name; void (*handler)(void); } voice_cmd_t;

static void cmd_light_on(void);
static void cmd_light_off(void);
static void cmd_report_env(void);

static const voice_cmd_t g_cmds[] = {
    { 1, &quot;开灯&quot;,     cmd_light_on   },
    { 2, &quot;关灯&quot;,     cmd_light_off  },
    { 3, &quot;现在几度&quot;, cmd_report_env },
};

void voice_poll(void)
{
    static uint16_t r_idx;
    static char     line[32];
    static uint8_t  n;

    while (r_idx != w_idx) {              /* 环形缓冲还有没读完的字节 */
        r_idx = (uint16_t)((r_idx + 1) % RX_RING);
        char c = (char)ring[r_idx];

        if (c == &apos;\n&apos;) {                  /* 收到完整一帧才动 */
            line[n] = &apos;\0&apos;;
            if (strncmp(line, &quot;CMD:&quot;, 4) == 0) {
                uint8_t id = (uint8_t)atoi(line + 4);
                for (uint8_t i = 0; i &amp;lt; sizeof(g_cmds) / sizeof(g_cmds[0]); i++) {
                    if (g_cmds[i].id == id) {
                        g_cmds[i].handler();
                        n = 0;
                        return;
                    }
                }
                voice_say(&quot;没听清，再说一遍&quot;);   /* 未识别也要回话 */
            }
            n = 0;
        } else if (c != &apos;\r&apos; &amp;amp;&amp;amp; n &amp;lt; sizeof(line) - 1) {
            line[n++] = c;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::important[读写指针必须各自取模]
上面 &lt;code&gt;ins&lt;/code&gt; 标出的两行是这段代码里唯一的&quot;技巧&quot;，也是最容易写错的地方。环形缓冲的两个指针一个在中断里改、一个在主循环里改，&lt;strong&gt;两边必须用同一套取模规则&lt;/strong&gt;。常见的错误是只在其中一侧写 &lt;code&gt;if (x &amp;gt;= N) x = 0&lt;/code&gt; 兜底：单看没错，但两侧规则一旦不一致，两个指针就会落在不同区间，&lt;code&gt;r_idx != w_idx&lt;/code&gt; 这个判断会开始撒谎——轻则漏读命令，重则读到一段早被覆盖的旧字节。&lt;/p&gt;
&lt;p&gt;这个写法还有一个已知局限：&lt;strong&gt;它区分不了&quot;空&quot;和&quot;满&quot;&lt;/strong&gt;（两种情况下 &lt;code&gt;r_idx == w_idx&lt;/code&gt;）。所以缓冲区我留了 64 字节，而单条命令最长不过 30 字节，实际上填不满——用一点内存余量换掉了&quot;再维护一个计数器&quot;的那条分支。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./01-maya-face.jpg&quot; alt=&quot;MAYA1.0 的表情动画：屏幕上亮起的蓝色像素脸&quot; /&gt;&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h3&gt;视觉检测：OpenMV 端的人脸 + 亮度&lt;/h3&gt;
&lt;p&gt;视觉这部分我把算力全部留在 OpenMV 板上，STM32 收到的只是结论。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# OpenMV Cam M7：人脸检测 + 亮度分析，结果以 JSON 经 UART 上报
import sensor, image, time, json
from machine import UART

sensor.reset()
sensor.set_pixformat(sensor.GRAYSCALE)   # 只做人脸和亮度，灰度就够，帧率更高
sensor.set_framesize(sensor.QVGA)        # 320x240
sensor.set_auto_gain(True)
sensor.skip_frames(time=2000)            # 等自动增益稳定

uart = UART(2, baudrate=115200, timeout_char=100)

# 固件内置的 Haar 级联，不用自己训练，也不占额外 Flash
face_cascade = image.HaarCascade(&quot;frontalface&quot;, stages=25)
BRIGHT_TH = 60                           # 低于这个灰度均值就该补光了

while True:
    img = sensor.snapshot()

    # 1) 亮度：整幅灰度均值，比光敏电阻更接近「人眼感受」
    stats = img.get_statistics()
    brightness = int(stats.mean())

    # 2) 人脸：threshold 越小越容易误检，0.75 是调出来的平衡点
    faces = img.find_features(face_cascade, threshold=0.75, scale_factor=1.25)
    if faces:
        f = max(faces, key=lambda r: r[2] * r[3])   # 取面积最大的那张脸
        img.draw_rectangle(f, color=255, thickness=2)

    # 3) 只发关键字段，STM32 端解析负担最小
    uart.write((json.dumps({&quot;face&quot;: bool(faces),
                            &quot;brightness&quot;: brightness}) + &quot;\n&quot;).encode())

    if faces and brightness &amp;lt; BRIGHT_TH:
        uart.write(b&quot;ANIM:SMILE\n&quot;)      # 有人 + 光线暗 → 打招呼并准备补光

    time.sleep_ms(50)                    # 约 20 fps，别把串口灌满
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;stages=25&lt;/code&gt; 是这里最关键的一个参数。Haar 级联的级数越多检测越准，但内存占用和耗时也越高；M7 上默认级数跑不满帧率，降到 25 之后检测延迟压到了 100 ms 以内，误检率也没有明显上升。&lt;/p&gt;
&lt;h3&gt;网络通信：ESP8266 从控端&lt;/h3&gt;
&lt;p&gt;从控端的固件短得可以整个贴出来——它的全部工作就是&quot;收到指令 → 拨继电器 → 回执&quot;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* 从控端：连主控 AP，监听 UDP，拨继电器并回执 */
#include &amp;lt;ESP8266WiFi.h&amp;gt;
#include &amp;lt;WiFiUdp.h&amp;gt;

const char    *SSID = &quot;MAYA1.0-AP&quot;;
const char    *PASS = &quot;maya123456&quot;;
const uint16_t PORT = 4210;              // 所有从控监听同一端口
const uint8_t  RELAY_PIN = 0;            // GPIO0 → 继电器

WiFiUDP udp;
char    buf[64];

static void reply(const char *msg) {
  udp.beginPacket(udp.remoteIP(), udp.remotePort());
  udp.print(msg);
  udp.endPacket();
}

void setup() {
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW);          // 上电默认断开，避免误开
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(SSID, PASS);
  while (WiFi.status() != WL_CONNECTED) { delay(200); Serial.print(&apos;.&apos;); }
  Serial.printf(&quot;\nslave up, ip=%s\n&quot;, WiFi.localIP().toString().c_str());
  udp.begin(PORT);
}

void loop() {
  int len = udp.parsePacket();
  if (len &amp;lt;= 0) return;
  len = udp.read(buf, sizeof(buf) - 1);
  if (len &amp;lt;= 0) return;
  buf[len] = &apos;\0&apos;;

  // 统一剥掉尾部换行，避免主控端带 \n 而测试脚本不带
  for (int i = 0; i &amp;lt; len; i++)
    if (buf[i] == &apos;\r&apos; || buf[i] == &apos;\n&apos;) buf[i] = &apos;\0&apos;;

  if (strcmp(buf, &quot;LIGHT_ON&quot;) == 0) {
    digitalWrite(RELAY_PIN, HIGH);
    reply(&quot;OK:LIGHT_ON&quot;);
  } else if (strcmp(buf, &quot;LIGHT_OFF&quot;) == 0) {
    digitalWrite(RELAY_PIN, LOW);
    reply(&quot;OK:LIGHT_OFF&quot;);
  } else {
    reply(&quot;ERR:UNKNOWN&quot;);                // 不认识的指令一律拒绝，别装成功
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;显示驱动：树莓派端的动画播放器&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./17-lcd-status.png&quot; alt=&quot;LCD 上显示的一帧：蓝色像素表情与三个家电状态图标（台灯 / 风扇 / 加湿器）&quot; /&gt;&lt;/p&gt;
&lt;p&gt;树莓派侧是一个开机自启的 Python 程序，职责单一到只有三件事：收串口、切动画、没命令时待机。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
&quot;&quot;&quot;树莓派 2B 端：监听串口动画命令，用 pygame 播放，40 s 无命令进待机。&quot;&quot;&quot;
import json
import os
import serial
import pygame

SERIAL_PORT  = &quot;/dev/ttyS0&quot;
BAUD         = 115200
IDLE_TIMEOUT = 40          # 秒
FPS          = 24

HERE = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(HERE, &quot;anims.json&quot;), encoding=&quot;utf-8&quot;) as f:
    ANIMS = json.load(f)   # {&quot;ANIM:IDLE&quot;: &quot;assets/idle&quot;, &quot;ANIM:SMILE&quot;: &quot;assets/smile&quot;}


class Player:
    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode((480, 320), pygame.FULLSCREEN)
        self.clock = pygame.time.Clock()
        self.frames, self.index, self.name = [], 0, None
        self.load(&quot;ANIM:IDLE&quot;)

    def load(self, name):
        &quot;&quot;&quot;切换动画：载入新序列，缺资源时保持当前画面而不是崩掉。&quot;&quot;&quot;
        if name not in ANIMS:
            print(f&quot;[warn] 未定义的动画 {name!r}，保持当前画面&quot;)
            return

        path = os.path.join(HERE, ANIMS[name])
        self.frames = sorted(
            pygame.image.load(os.path.join(path, f)).convert()
            for f in os.listdir(path)
            if f.lower().endswith((&quot;.png&quot;, &quot;.jpg&quot;))
        )
        if not self.frames:
            print(f&quot;[warn] {path} 里没有帧图，忽略&quot;)
            return
        self.index, self.name = 0, name

    def run(self):
        ser  = serial.Serial(SERIAL_PORT, BAUD, timeout=1)
        buf  = b&quot;&quot;
        idle = 0.0
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    return

            chunk = ser.read(64)
            if chunk:
                buf += chunk
                while b&quot;\n&quot; in buf:            # 只认换行结尾的完整帧
                    line, buf = buf.split(b&quot;\n&quot;, 1)
                    cmd = line.decode(&quot;utf-8&quot;, &quot;ignore&quot;).strip()
                    if cmd.startswith(&quot;ANIM:&quot;):
                        self.load(cmd)
                        idle = 0.0

            idle += self.clock.tick(FPS) / 1000.0
            if idle &amp;gt;= IDLE_TIMEOUT and self.name != &quot;ANIM:IDLE&quot;:
                self.load(&quot;ANIM:IDLE&quot;)         # 静默太久，回呼吸灯待机
                idle = 0.0

            if self.frames:
                self.screen.blit(self.frames[self.index], (0, 0))
                pygame.display.flip()
                self.index = (self.index + 1) % len(self.frames)


if __name__ == &quot;__main__&quot;:
    Player().run()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里那个 &lt;code&gt;while b&quot;\n&quot; in buf&lt;/code&gt; 的写法值得说一下：串口 &lt;code&gt;read(64)&lt;/code&gt; 不保证每次都刚好读到一帧，可能读到半截，也可能一次读到两帧半。所以&lt;strong&gt;先把数据全丢进缓冲区，再按换行切&lt;/strong&gt;，而不是假设&quot;一次 read 就是一帧&quot;。这是串口编程里最常见的坑，我在调试阶段被它坑掉了整整一个下午（详见下文）。&lt;/p&gt;
&lt;h3&gt;编译与烧录&lt;/h3&gt;
&lt;p&gt;主控侧走的是标准的 &lt;code&gt;arm-none-eabi-gcc&lt;/code&gt; 加 &lt;code&gt;st-flash&lt;/code&gt; 流程，树莓派侧只需要把脚本挂成开机自启：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 1) 编译 STM32 固件
cmake -B build -DCMAKE_TOOLCHAIN_FILE=cmake/arm-none-eabi.cmake -DCMAKE_BUILD_TYPE=Release
cmake --build build -j

# 2) 烧录到主控（ST-Link）
st-flash --reset write build/maya.bin 0x8000000

# 3) 树莓派侧部署为开机自启服务
scp -r display/ pi@maya-pi:/opt/maya/
ssh pi@maya-pi &apos;sudo systemctl enable --now maya-display.service&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip[构建脚本别塞进 git]
&lt;code&gt;build/&lt;/code&gt; 目录和 CMake 生成的缓存文件加起来动辄几十 MB，而且每次全量重建都会变。项目里加一行 &lt;code&gt;build/&lt;/code&gt; 就够了——这条经验是我把仓库推到 200 MB 之后才学会的。
:::&lt;/p&gt;
&lt;h2&gt;联调与实验结果&lt;/h2&gt;
&lt;p&gt;为了验证功能与性能，我设计了四组实验：语音识别、机器视觉、环境监测、家电控制。每组都在不同条件下重复多轮，取稳定值。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;测试项&lt;/th&gt;
&lt;th&gt;测试条件&lt;/th&gt;
&lt;th&gt;结果&lt;/th&gt;
&lt;th&gt;是否达标&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;语音识别&lt;/td&gt;
&lt;td&gt;安静环境&lt;/td&gt;
&lt;td&gt;准确率 98%，响应 &amp;lt; 1 s&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;语音识别&lt;/td&gt;
&lt;td&gt;空调 / 风扇噪声&lt;/td&gt;
&lt;td&gt;准确率 &amp;gt; 90%，响应略延长&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;人脸识别&lt;/td&gt;
&lt;td&gt;正常光照&lt;/td&gt;
&lt;td&gt;准确率 95%，识别 &amp;lt; 1 s&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;人脸识别&lt;/td&gt;
&lt;td&gt;低光照&lt;/td&gt;
&lt;td&gt;准确率降至 85%，耗时延长&lt;/td&gt;
&lt;td&gt;⚠️ 需补光&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;环境监测&lt;/td&gt;
&lt;td&gt;温湿度阶跃变化&lt;/td&gt;
&lt;td&gt;数据精度高，联动响应 &amp;lt; 2 s&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;环境监测&lt;/td&gt;
&lt;td&gt;光照阶跃变化&lt;/td&gt;
&lt;td&gt;及时触发灯光开关&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;家电控制&lt;/td&gt;
&lt;td&gt;靠近路由器&lt;/td&gt;
&lt;td&gt;响应约 0.4 s&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;家电控制&lt;/td&gt;
&lt;td&gt;远距 / 弱信号&lt;/td&gt;
&lt;td&gt;响应延迟上升，仍 &amp;lt; 0.5 s&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;汇总一下几个关键指标：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;语音识别在安静与噪声环境下都稳定在 &lt;strong&gt;90% 以上&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;视觉检测在复杂光照条件下 &lt;strong&gt;85% 以上&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;环境监测与家电联动响应 &lt;strong&gt;控制在 2 秒以内&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;远程控制延迟 &lt;strong&gt;小于 0.5 秒&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::warning[低光照是这套方案的硬伤]
人脸识别在低光下掉到 85%，而且这个数字还是&quot;正面、无遮挡&quot;的理想情况。原因在于 OpenMV 用的是普通 CMOS 摄像头，环境照度不足时图像噪声直接淹没了 Haar 特征。当时的应对办法是让视觉结果和光敏电阻互相校验——光敏电阻说暗，就不指望人脸检测出结果，直接走&quot;补光&quot;分支。要真正解决得换红外摄像头，这属于后续迭代范围。
:::&lt;/p&gt;
&lt;p&gt;至于长时间运行的稳定性，我做了一轮不间断的老化测试，监控内存占用、网络丢包和看门狗复位次数。整体表现符合预期，唯一暴露出来的是 Wi-Fi 在信号边缘区域偶发掉线——从控端会自动重连，但重连期间的控制命令会丢，这也是后来把控制帧改成 UDP 的原因之一（宁可丢一条过期命令，也不要 TCP 在那里傻等重传）。&lt;/p&gt;
&lt;h2&gt;产品化：成本、市场与代价&lt;/h2&gt;
&lt;p&gt;项目里完整分析了产品化前景，这里只挑三个我真正关心的数字。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;成本。&lt;/strong&gt; 单台样机的核心元件（STM32、语音模块、视觉模块、传感器、电机等）总成本预计在 &lt;strong&gt;700~900 元&lt;/strong&gt;，批量生产后可控制在 &lt;strong&gt;500 元以内&lt;/strong&gt;。这个数字能达到，靠的是核心部件全部选了国产成熟方案——这个价位在智能家居市场里是有竞争力的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;市场。&lt;/strong&gt; 未来 5 年智能家居与物联网机器人市场的年复合增长率预计超过 20%。更重要的是需求结构在变：不只是大城市高端家庭，中小城市用户也开始关注家居智能升级；养老机构、高端公寓、写字楼物业对&quot;一站式&quot;智能管家的兴趣也在上升。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代价。&lt;/strong&gt; 这部分我原本写得很委婉，这里说直白一点：这套方案最大的代价是&lt;strong&gt;离线语音的封闭性&lt;/strong&gt;。ASR-PRO 只认预先烧录的命令词，用户想加一句&quot;把客厅灯调暗一点&quot;这种带参数的自然语言，就必须重新走一遍 IDE 流程。而换成在线方案，隐私、延迟和持续成本又都回来了。这是当前阶段无法同时满足的三角。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;产品化挑战&lt;/th&gt;
&lt;th&gt;现象&lt;/th&gt;
&lt;th&gt;拟采取的方案&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;视觉受光照影响&lt;/td&gt;
&lt;td&gt;低光下识别率降到 85%&lt;/td&gt;
&lt;td&gt;换红外摄像头 / 高灵敏度传感器，升级识别算法&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;语音噪声鲁棒性&lt;/td&gt;
&lt;td&gt;多人同时说话时识别率下降&lt;/td&gt;
&lt;td&gt;加 AI 降噪、阵列麦克风；未来接大模型对话&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wi-Fi 稳定性&lt;/td&gt;
&lt;td&gt;大户型 / 弱信号下延迟爬升&lt;/td&gt;
&lt;td&gt;Mesh 组网、MQTT 协议、4G/5G 备用链路&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;隐私与数据安全&lt;/td&gt;
&lt;td&gt;用户对本地数据敏感&lt;/td&gt;
&lt;td&gt;数据本地化存储、端到端加密、权限分级&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;写在研发过程中&lt;/h2&gt;
&lt;p&gt;这个项目目前仍在&lt;strong&gt;研发中&lt;/strong&gt;——它还没到可以拍胸脯说&quot;做完了&quot;的程度。&lt;/p&gt;
&lt;p&gt;从方案设计上，我给自己划了三条原则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;不让机器人替用户做决定，只做推荐。&lt;/strong&gt; 所以流程里有明确的&quot;确认管理方案&quot;环节，用户可以拒绝、可以改。智能家居最怕的是&quot;自作聪明&quot;，灯自己亮了自己灭，用户反而失去掌控感。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;让每一步都有回执。&lt;/strong&gt; 执行完要显示状态，环境数据要实时播报。机器人必须让用户始终知道它&quot;现在认为世界是什么样的&quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本地优先。&lt;/strong&gt; 语音离线、决策本地，网络只用于最终控制家电。这样即使断网，机器人至少还能聊天、还能监测、还能显示状态。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;调试阶段踩过的坑里，有两个到现在还记得很清楚：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一个是串口分包。&lt;/strong&gt; 树莓派的动画播放器偶尔会&quot;卡住不切动画&quot;。查了很久才发现：STM32 发的是 &lt;code&gt;ANIM:SMILE\n&lt;/code&gt;，但树莓派这边的 &lt;code&gt;ser.read(64)&lt;/code&gt; 有时只读到 &lt;code&gt;ANIM:SM&lt;/code&gt;，剩下的 &lt;code&gt;ILE\n&lt;/code&gt; 要等下一次 read 才到。而当时的代码是&quot;读到什么就解析什么&quot;，于是永远匹配不上映射表。&lt;/p&gt;
&lt;p&gt;修法就是前面代码里那个缓冲区 + 按换行切，改动其实只多了一行循环：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  chunk = ser.read(64)
- if chunk:
-     cmd = chunk.decode(&quot;utf-8&quot;, &quot;ignore&quot;).strip()
-     if cmd.startswith(&quot;ANIM:&quot;):
-         self.load(cmd)
+ if chunk:
+     buf += chunk
+     while b&quot;\n&quot; in buf:              # 只认换行结尾的完整帧
+         line, buf = buf.split(b&quot;\n&quot;, 1)
+         cmd = line.decode(&quot;utf-8&quot;, &quot;ignore&quot;).strip()
+         if cmd.startswith(&quot;ANIM:&quot;):
+             self.load(cmd)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;第二个是电平匹配。&lt;/strong&gt; ASR-PRO 是 3.3 V TTL，我最初把它直接接在一路 5 V 的串口上，结果语音模块时好时坏——不是完全不工作，而是&lt;strong&gt;大部分时候正常、偶尔识别错&lt;/strong&gt;。这种&quot;大部分时候正常&quot;的故障最难查，因为它看起来像软件问题。后来用示波器量才发现电平不对，加了一级电平转换之后就稳了。&lt;/p&gt;
&lt;p&gt;接下来要推进的是把上面这些模块真正联调起来，让这条五步闭环从图纸上跑起来，同时把 ANIM 资源从&quot;几张测试图&quot;扩充成一套完整的表情包。&lt;/p&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;p&gt;MAYA1.0 不是要做一个&quot;什么都会&quot;的机器人，而是想把&lt;strong&gt;桌面这一平方米&lt;/strong&gt;管理好：知道环境、听得懂话、能动家电、会跟你商量。&lt;/p&gt;
&lt;p&gt;做这个项目最大的体会是：机器人产品化的难点从来不在单点技术，而在&lt;strong&gt;闭环&lt;/strong&gt;。语音识别、视觉、Wi-Fi 每一个都是成熟模块，但把它们串成一条用户愿意反复使用
的链路，需要反复打磨交互的每个停顿——什么时候该主动说话、什么时候该等、拒绝之后怎么接茬。这些东西没有现成模块可以买。&lt;/p&gt;
&lt;p&gt;:::note[一点私心]
比起&quot;完成了多少指标&quot;，我其实更在意有人从桌前走过时，MAYA1.0 自己转过来打了个招呼。技术上那只是一次 &lt;code&gt;find_features()&lt;/code&gt; 命中加上一行 &lt;code&gt;ANIM:SMILE&lt;/code&gt;，但那一刻它是活的。&lt;/p&gt;
&lt;p&gt;（:spoiler[其实它第一个认错人了——先冲着我旁边的朋友热情地打了个招呼，才转向我。]）
:::&lt;/p&gt;
&lt;p&gt;MAYA1.0 这个方向我一直在继续推进。不过要先说清楚一件事：&lt;strong&gt;下面这个 MAYA3.0 不是本文 MAYA1.0 的升级版，它是另一个独立的项目&lt;/strong&gt;——两者不共用一套实现，请不要混着看。&lt;/p&gt;
&lt;h2&gt;MAYA 计划&lt;/h2&gt;
&lt;p&gt;这台 MAYA1.0 不是一个孤立的作品，而是「&lt;strong&gt;MAYA 计划&lt;/strong&gt;」的&lt;strong&gt;第一代&lt;/strong&gt;。这个计划一共有三代产品，后续两代会陆续把文章补到这个博客里：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;第一代 · MAYA1.0&lt;/strong&gt;——就是本文这台：STM32 + 树莓派的桌面管家机器人&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第三代&lt;/strong&gt;——待公开&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;第二代：MAYA3.0（独立项目）&lt;/h3&gt;
&lt;p&gt;MAYA3.0 是我在 MAYA1.0 之后另起的一个项目，和本文这套代码、这条技术路线&lt;strong&gt;都不是同一个东西&lt;/strong&gt;。把它和第一代分开列，就是为了避免被当成「新旧两个版本」：&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;RERELEGEND/RA2039-MAYA3.0&quot;}&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;相关技术：STM32F103C8T6 · 树莓派 2B · ASR-PRO 离线语音 · OpenMV · ESP8266 · DHT11 / LDR / MPU6050 · SG90 / L298N · LCD 显示&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>三子棋人机对弈机器人：2024 全国大学生电子设计竞赛 E 题实录</title><link>https://ura2039.xyz/blog/posts/chess-robot-edc-2024/</link><guid isPermaLink="true">https://ura2039.xyz/blog/posts/chess-robot-edc-2024/</guid><description>四天三夜，从零做出一台能用机械臂与人对弈三子棋的装置。ATmega328P 主控 + OpenMV 机器视觉 + 3D 打印机械臂。这篇不只是赛后记录：把赛题的评分结构、棋盘旋转的坐标变换、三子棋的极小极大穷举、棋子被挪后的状态差分都拆开讲了一遍，附可直接运行的核心算法实现。</description><pubDate>Sat, 27 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;四天三夜，三个本科生，一堆舵机和 3D 打印件。&lt;/p&gt;
&lt;p&gt;目标是做出一台能&lt;strong&gt;跟人下三子棋&lt;/strong&gt;的装置——机械臂要自己从棋子堆里夹起棋子，放到棋盘上正确的格子里，还得能赢、至少不能输，甚至能发现你偷偷挪动了它下过的棋子。&lt;/p&gt;
&lt;p&gt;最后我们拿到了 &lt;strong&gt;2024 年全国大学生电子设计竞赛天津赛区本科组三等奖&lt;/strong&gt;。下面把整个过程拆开讲讲。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./01-device.png&quot; alt=&quot;三子棋对弈装置整机&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;一、题目：三子棋游戏装置（E 题）&lt;/h2&gt;
&lt;p&gt;这是 &lt;strong&gt;2024 年全国大学生电子设计竞赛赛区赛暨模拟电子系统设计专题赛初赛&lt;/strong&gt;的本科组 E 题。&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;maya-diagram&quot; data-maya=&quot;05-contest-overview&quot;&amp;gt;
&amp;lt;div class=&quot;maya-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 810 506&quot; width=&quot;810&quot; height=&quot;506&quot; class=&quot;maya-svg&quot; role=&quot;img&quot; aria-label=&quot;2024 全国大学生电子设计竞赛赛区赛赛题总览图：赛事与时间安排（本科组或高职高专组可选、组别第一天上报后不可更改、7 月 29 日 8:00 开赛、8 月 1 日 20:00 结束、结束时上交报告实物与登记表），参赛纪律（每队 3 人、不得换人、可用图书与网络资源、不得在场外设计制作、非参赛队员必须回避），以及本科组 E 题任务（设计制作三子棋游戏装置、控制机械臂放置棋子、实现人机对弈、棋盘由黑实线围成 9 个方格）。&quot;&amp;gt;&amp;lt;g class=&quot;maya-links&quot;&amp;gt;&amp;lt;path d=&quot;M341.2 259.7 C362.0 259.7 358.2 94.9 379.0 94.9&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M341.2 259.7 C362.0 259.7 358.2 267.8 379.0 267.8&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M341.2 259.7 C362.0 259.7 358.2 424.4 379.0 424.4&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M466.7 94.9 C520.0 94.9 510.3 29.8 563.5 29.8&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M466.7 94.9 C520.0 94.9 510.3 62.4 563.5 62.4&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M466.7 94.9 C520.0 94.9 510.3 94.9 563.5 94.9&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M466.7 94.9 C520.0 94.9 510.3 127.5 563.5 127.5&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M466.7 94.9 C520.0 94.9 510.3 160.1 563.5 160.1&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M451.2 267.8 C513.0 267.8 501.7 202.7 563.5 202.7&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M451.2 267.8 C513.0 267.8 501.7 235.2 563.5 235.2&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M451.2 267.8 C513.0 267.8 501.7 267.8 563.5 267.8&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M451.2 267.8 C513.0 267.8 501.7 300.4 563.5 300.4&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M451.2 267.8 C513.0 267.8 501.7 333.0 563.5 333.0&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M525.8 424.4 C546.5 424.4 542.8 375.5 563.5 375.5&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M525.8 424.4 C546.5 424.4 542.8 408.1 563.5 408.1&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M525.8 424.4 C546.5 424.4 542.8 440.7 563.5 440.7&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M525.8 424.4 C546.5 424.4 542.8 473.3 563.5 473.3&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;0&quot; data-d=&quot;0&quot; data-t=&quot;2024 全国大学生电子设计竞赛
赛区赛 · 模拟电子系统设计专题赛初赛&quot;&amp;gt;&amp;lt;text x=&quot;20.0&quot; y=&quot;252.4&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;2024 全国大学生电子设计竞赛&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;20.0&quot; y=&quot;279.9&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;赛区赛 · 模拟电子系统设计专题赛初赛&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;20.0&quot; y=&quot;288.2&quot; width=&quot;321.2&quot; height=&quot;3&quot; rx=&quot;1.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;1&quot; data-d=&quot;1&quot; data-t=&quot;赛事与时间&quot;&amp;gt;&amp;lt;circle cx=&quot;378.0&quot; cy=&quot;94.9&quot; r=&quot;3.2&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;387.2&quot; y=&quot;100.2&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;赛事与时间&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;387.2&quot; y=&quot;107.2&quot; width=&quot;79.5&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;2&quot; data-d=&quot;2&quot; data-t=&quot;本科组 / 高职高专组可选&quot;&amp;gt;&amp;lt;circle cx=&quot;563.4&quot; cy=&quot;29.8&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;571.8&quot; y=&quot;34.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;本科组 / 高职高专组可选&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;571.8&quot; y=&quot;40.6&quot; width=&quot;149.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;3&quot; data-d=&quot;2&quot; data-t=&quot;组别第一天上报，之后不可更改&quot;&amp;gt;&amp;lt;circle cx=&quot;563.4&quot; cy=&quot;62.4&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;571.8&quot; y=&quot;67.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;组别第一天上报，之后不可更改&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;571.8&quot; y=&quot;73.2&quot; width=&quot;191.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;4&quot; data-d=&quot;2&quot; data-t=&quot;7 月 29 日 8:00 开赛&quot;&amp;gt;&amp;lt;circle cx=&quot;563.4&quot; cy=&quot;94.9&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;571.8&quot; y=&quot;99.5&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;7 月 29 日 8:00 开赛&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;571.8&quot; y=&quot;105.7&quot; width=&quot;122.4&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;5&quot; data-d=&quot;2&quot; data-t=&quot;8 月 1 日 20:00 结束（四天三夜）&quot;&amp;gt;&amp;lt;circle cx=&quot;563.4&quot; cy=&quot;127.5&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;571.8&quot; y=&quot;132.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;8 月 1 日 20:00 结束（四天三夜）&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;571.8&quot; y=&quot;138.3&quot; width=&quot;203.4&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;6&quot; data-d=&quot;2&quot; data-t=&quot;结束时上交报告、实物与《登记表》&quot;&amp;gt;&amp;lt;circle cx=&quot;563.4&quot; cy=&quot;160.1&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;571.8&quot; y=&quot;164.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;结束时上交报告、实物与《登记表》&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;571.8&quot; y=&quot;170.9&quot; width=&quot;218.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;7&quot; data-d=&quot;1&quot; data-t=&quot;参赛纪律&quot;&amp;gt;&amp;lt;circle cx=&quot;378.0&quot; cy=&quot;267.8&quot; r=&quot;3.2&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;387.2&quot; y=&quot;273.1&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;参赛纪律&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;387.2&quot; y=&quot;280.1&quot; width=&quot;64.0&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;8&quot; data-d=&quot;2&quot; data-t=&quot;每队严格限制 3 人&quot;&amp;gt;&amp;lt;circle cx=&quot;563.4&quot; cy=&quot;202.7&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;571.8&quot; y=&quot;207.3&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;每队严格限制 3 人&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;571.8&quot; y=&quot;213.4&quot; width=&quot;110.9&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;9&quot; data-d=&quot;2&quot; data-t=&quot;开赛后不得中途更换队员&quot;&amp;gt;&amp;lt;circle cx=&quot;563.4&quot; cy=&quot;235.2&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;571.8&quot; y=&quot;239.8&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;开赛后不得中途更换队员&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;571.8&quot; y=&quot;246.0&quot; width=&quot;150.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;10&quot; data-d=&quot;2&quot; data-t=&quot;可用图书资料与网络资源&quot;&amp;gt;&amp;lt;circle cx=&quot;563.4&quot; cy=&quot;267.8&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;571.8&quot; y=&quot;272.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;可用图书资料与网络资源&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;571.8&quot; y=&quot;278.6&quot; width=&quot;150.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;11&quot; data-d=&quot;2&quot; data-t=&quot;不得在指定场地外设计制作&quot;&amp;gt;&amp;lt;circle cx=&quot;563.4&quot; cy=&quot;300.4&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;571.8&quot; y=&quot;305.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;不得在指定场地外设计制作&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;571.8&quot; y=&quot;311.2&quot; width=&quot;164.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;12&quot; data-d=&quot;2&quot; data-t=&quot;非参赛队员（含教师）必须回避&quot;&amp;gt;&amp;lt;circle cx=&quot;563.4&quot; cy=&quot;333.0&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;571.8&quot; y=&quot;337.6&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;非参赛队员（含教师）必须回避&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;571.8&quot; y=&quot;343.8&quot; width=&quot;191.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;13&quot; data-d=&quot;1&quot; data-t=&quot;E 题任务（本科组）&quot;&amp;gt;&amp;lt;circle cx=&quot;378.0&quot; cy=&quot;424.4&quot; r=&quot;3.2&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;387.2&quot; y=&quot;429.7&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;E 题任务（本科组）&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;387.2&quot; y=&quot;436.6&quot; width=&quot;138.6&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;14&quot; data-d=&quot;2&quot; data-t=&quot;设计并制作三子棋游戏装置&quot;&amp;gt;&amp;lt;circle cx=&quot;563.4&quot; cy=&quot;375.5&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;571.8&quot; y=&quot;380.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;设计并制作三子棋游戏装置&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;571.8&quot; y=&quot;386.3&quot; width=&quot;164.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;15&quot; data-d=&quot;2&quot; data-t=&quot;控制机械臂或其他机构放置棋子&quot;&amp;gt;&amp;lt;circle cx=&quot;563.4&quot; cy=&quot;408.1&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;571.8&quot; y=&quot;412.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;控制机械臂或其他机构放置棋子&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;571.8&quot; y=&quot;418.9&quot; width=&quot;191.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;16&quot; data-d=&quot;2&quot; data-t=&quot;人机对弈，先连成一线者获胜&quot;&amp;gt;&amp;lt;circle cx=&quot;563.4&quot; cy=&quot;440.7&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;571.8&quot; y=&quot;445.3&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;人机对弈，先连成一线者获胜&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;571.8&quot; y=&quot;451.5&quot; width=&quot;177.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;17&quot; data-d=&quot;2&quot; data-t=&quot;棋盘由黑色实线围成 9 个方格&quot;&amp;gt;&amp;lt;circle cx=&quot;563.4&quot; cy=&quot;473.3&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;571.8&quot; y=&quot;477.9&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;棋盘由黑色实线围成 9 个方格&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;571.8&quot; y=&quot;484.0&quot; width=&quot;178.4&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;任务&lt;/strong&gt;：设计并制作三子棋游戏装置，能够控制机械臂或其他机构放置棋子，实现人机对弈。&lt;/p&gt;
&lt;p&gt;棋盘由黑色实线围成 &lt;strong&gt;9 个方格&lt;/strong&gt;，人和机器分别从各自的棋子放置处拾取棋子并放置到方格中，&lt;strong&gt;先将己方的 3 个棋子连成一线（横连、竖连、斜连皆可）即获胜&lt;/strong&gt;——本质就是井字棋（Tic-Tac-Toe）。&lt;/p&gt;
&lt;p&gt;9 个方格按题目给的编号排布，行优先：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;列 1&lt;/th&gt;
&lt;th&gt;列 2&lt;/th&gt;
&lt;th&gt;列 3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;行 1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;行 2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;行 3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;第 &lt;strong&gt;5 号格&lt;/strong&gt;是正中心，也是评分表里第一个单独成项的考点——因为它是唯一一个&quot;无论棋盘转到哪个角度都必须是中心&quot;的位置。&lt;/p&gt;
&lt;p&gt;几个硬性条件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;棋子直径约 &lt;strong&gt;22±2mm&lt;/strong&gt;，棋盘和棋子由选手自带&lt;/li&gt;
&lt;li&gt;黑、白棋子在放置处各摆成 &lt;strong&gt;1 列&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;装置放置棋子的过程中&lt;strong&gt;不可触压黑实线&lt;/strong&gt;，放置后&lt;strong&gt;不可脱离方格区域&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;放置一颗棋子的时间&lt;strong&gt;不大于 15s&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;装置应能适应&lt;strong&gt;正常室内照明环境&lt;/strong&gt;，测试时不得有特殊照明条件要求&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;时间&lt;/strong&gt;：7 月 29 日 8:00 正式开始，8 月 1 日 20:00 结束——四天三夜。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;纪律&lt;/strong&gt;：每队严格限制 3 人，开赛后不得中途更换队员；可以使用图书资料和网络资源，但不得在竞赛场地外设计制作，&lt;strong&gt;不得以任何方式与他人交流，包括教师在内的非参赛队员必须回避&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;二、评分点：题目真正难在哪&lt;/h2&gt;
&lt;p&gt;&amp;lt;figure class=&quot;maya-diagram&quot; data-maya=&quot;06-scoring-rules&quot;&amp;gt;
&amp;lt;div class=&quot;maya-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 810 323&quot; width=&quot;810&quot; height=&quot;323&quot; class=&quot;maya-svg&quot; role=&quot;img&quot; aria-label=&quot;E 题评分点结构图，合计 120 分：基础放置能力 45 分（1 颗黑棋放入 5 号方格 5 分、2 黑 2 白依次放入指定方格 20 分、棋盘绕中心正负 45 度旋转后仍能放置 20 分）；博弈对弈能力 40 分（执黑先行且人第 1 步出错则获胜 20 分、人执黑先行时正确放白棋保持不输 20 分）；容错与附加 15 分（棋子被变动后能发现并放回原处 10 分、其他 5 分）；设计报告 20 分。&quot;&amp;gt;&amp;lt;g class=&quot;maya-links&quot;&amp;gt;&amp;lt;path d=&quot;M205.1 175.1 C225.9 175.1 222.1 62.4 242.9 62.4&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M205.1 175.1 C225.9 175.1 222.1 153.8 242.9 153.8&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M205.1 175.1 C225.9 175.1 222.1 228.9 242.9 228.9&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M205.1 175.1 C225.9 175.1 222.1 287.8 242.9 287.8&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M395.6 62.4 C416.4 62.4 412.6 29.8 433.4 29.8&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M395.6 62.4 C416.4 62.4 412.6 62.4 433.4 62.4&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M395.6 62.4 C416.4 62.4 412.6 94.9 433.4 94.9&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M395.6 153.8 C416.4 153.8 412.6 137.5 433.4 137.5&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M395.6 153.8 C416.4 153.8 412.6 170.1 433.4 170.1&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M380.1 228.9 C409.4 228.9 404.1 212.7 433.4 212.7&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M380.1 228.9 C409.4 228.9 404.1 245.2 433.4 245.2&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M364.6 287.8 C402.4 287.8 395.6 287.8 433.4 287.8&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;0&quot; data-d=&quot;0&quot; data-t=&quot;E 题评分点
合计 120 分&quot;&amp;gt;&amp;lt;text x=&quot;103.9&quot; y=&quot;167.8&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;E 题评分点&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;103.9&quot; y=&quot;195.3&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;合计 120 分&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;103.9&quot; y=&quot;203.6&quot; width=&quot;101.2&quot; height=&quot;3&quot; rx=&quot;1.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;1&quot; data-d=&quot;1&quot; data-t=&quot;基础放置能力 · 45 分&quot;&amp;gt;&amp;lt;circle cx=&quot;241.9&quot; cy=&quot;62.4&quot; r=&quot;3.2&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;251.1&quot; y=&quot;67.6&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;基础放置能力 · 45 分&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;251.1&quot; y=&quot;74.6&quot; width=&quot;144.5&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;2&quot; data-d=&quot;2&quot; data-t=&quot;1 颗黑棋放入 5 号方格 —— 5 分&quot;&amp;gt;&amp;lt;circle cx=&quot;433.2&quot; cy=&quot;29.8&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;441.6&quot; y=&quot;34.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;1 颗黑棋放入 5 号方格 —— 5 分&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;441.6&quot; y=&quot;40.6&quot; width=&quot;187.4&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;3&quot; data-d=&quot;2&quot; data-t=&quot;2 黑 2 白依次放入指定方格 —— 20 分&quot;&amp;gt;&amp;lt;circle cx=&quot;433.2&quot; cy=&quot;62.4&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;441.6&quot; y=&quot;67.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;2 黑 2 白依次放入指定方格 —— 20 分&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;441.6&quot; y=&quot;73.2&quot; width=&quot;222.1&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;4&quot; data-d=&quot;2&quot; data-t=&quot;棋盘绕中心 ±45° 旋转后仍能放置 —— 20 分&quot;&amp;gt;&amp;lt;circle cx=&quot;433.2&quot; cy=&quot;94.9&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;441.6&quot; y=&quot;99.5&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;棋盘绕中心 ±45° 旋转后仍能放置 —— 20 分&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;441.6&quot; y=&quot;105.7&quot; width=&quot;258.2&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;5&quot; data-d=&quot;1&quot; data-t=&quot;博弈对弈能力 · 40 分&quot;&amp;gt;&amp;lt;circle cx=&quot;241.9&quot; cy=&quot;153.8&quot; r=&quot;3.2&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;251.1&quot; y=&quot;159.1&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;博弈对弈能力 · 40 分&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;251.1&quot; y=&quot;166.0&quot; width=&quot;144.5&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;6&quot; data-d=&quot;2&quot; data-t=&quot;执黑先行，人第 1 步出错则获胜 —— 20 分&quot;&amp;gt;&amp;lt;circle cx=&quot;433.2&quot; cy=&quot;137.5&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;441.6&quot; y=&quot;142.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;执黑先行，人第 1 步出错则获胜 —— 20 分&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;441.6&quot; y=&quot;148.3&quot; width=&quot;251.6&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;7&quot; data-d=&quot;2&quot; data-t=&quot;人执黑先行，正确放白棋保持不输 —— 20 分&quot;&amp;gt;&amp;lt;circle cx=&quot;433.2&quot; cy=&quot;170.1&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;441.6&quot; y=&quot;174.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;人执黑先行，正确放白棋保持不输 —— 20 分&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;441.6&quot; y=&quot;180.9&quot; width=&quot;264.2&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;8&quot; data-d=&quot;1&quot; data-t=&quot;容错与附加 · 15 分&quot;&amp;gt;&amp;lt;circle cx=&quot;241.9&quot; cy=&quot;228.9&quot; r=&quot;3.2&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;251.1&quot; y=&quot;234.2&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;容错与附加 · 15 分&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;251.1&quot; y=&quot;241.2&quot; width=&quot;129.0&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;9&quot; data-d=&quot;2&quot; data-t=&quot;棋子被变动，能发现并放回原处 —— 10 分&quot;&amp;gt;&amp;lt;circle cx=&quot;433.2&quot; cy=&quot;212.7&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;441.6&quot; y=&quot;217.3&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;棋子被变动，能发现并放回原处 —— 10 分&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;441.6&quot; y=&quot;223.4&quot; width=&quot;250.7&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;10&quot; data-d=&quot;2&quot; data-t=&quot;其他 —— 5 分&quot;&amp;gt;&amp;lt;circle cx=&quot;433.2&quot; cy=&quot;245.2&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;441.6&quot; y=&quot;249.8&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;其他 —— 5 分&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;441.6&quot; y=&quot;256.0&quot; width=&quot;81.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;11&quot; data-d=&quot;1&quot; data-t=&quot;设计报告 · 20 分&quot;&amp;gt;&amp;lt;circle cx=&quot;241.9&quot; cy=&quot;287.8&quot; r=&quot;3.2&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;251.1&quot; y=&quot;293.1&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;设计报告 · 20 分&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;251.1&quot; y=&quot;300.1&quot; width=&quot;113.5&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;12&quot; data-d=&quot;2&quot; data-t=&quot;单独成册，与实物一同上交评审&quot;&amp;gt;&amp;lt;circle cx=&quot;433.2&quot; cy=&quot;287.8&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;441.6&quot; y=&quot;292.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;单独成册，与实物一同上交评审&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;441.6&quot; y=&quot;298.6&quot; width=&quot;191.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;要求&lt;/th&gt;
&lt;th&gt;分值&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;将任意 1 颗黑棋子放置到 &lt;strong&gt;5 号方格&lt;/strong&gt;中&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;将任意 2 黑 2 白依次放置到&lt;strong&gt;指定&lt;/strong&gt;方格中&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;棋盘&lt;strong&gt;绕中心 ±45° 旋转&lt;/strong&gt;后，仍能完成上述放置&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;装置&lt;strong&gt;执黑先行&lt;/strong&gt;对弈，人第 1 步白棋出错则装置能获胜&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;人执黑先行&lt;/strong&gt;，装置能正确放白棋&lt;strong&gt;保持不输棋&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;人&lt;strong&gt;变动机器下过的棋子位置&lt;/strong&gt;，装置能自动发现并放回原处&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;其他&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;设计报告&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;合计 &lt;strong&gt;120 分&lt;/strong&gt;。把这张表读透，题目的难点就清楚了——&lt;strong&gt;真正拉开差距的不是&quot;会下棋&quot;，而是&quot;看得准、放得稳、能容错&quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;:::important[三个最狠的坑]
&lt;strong&gt;① 棋盘会转（第 3 项，20 分）。&lt;/strong&gt; 棋盘绕中心旋转 ±45°，意味着视觉系统拿到的是&lt;strong&gt;旋转后的格子坐标&lt;/strong&gt;，机械臂的落子目标点必须跟着做坐标变换。如果代码里把 9 个格子的坐标写死，这一项直接零分。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② 棋子会被挪（第 6 项，10 分）。&lt;/strong&gt; 人可以在不落新子的情况下，把机器下过的某颗棋子挪个位置，机器轮到时要&lt;strong&gt;自己发现并复位&lt;/strong&gt;，限时 15s。这要求视觉系统不只&quot;识别当前棋盘&quot;，还得&lt;strong&gt;记住上一帧的状态并做差分对比&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;③ 不许压线。&lt;/strong&gt;「不可触压黑实线」这条最磨人——它会惩罚任何&quot;靠视觉估计落点&quot;的做法。机械臂必须知道得足够准，而且要留安全余量。
:::&lt;/p&gt;
&lt;p&gt;另外第 4、5 项其实是&lt;strong&gt;两个方向的博弈任务&lt;/strong&gt;：既要&quot;执黑先行时能抓住人的失误获胜&quot;，又要&quot;人执黑先行时保证不输&quot;。三子棋的先手方只要不犯错必不败，所以后手方（第 5 项）的正确策略是&lt;strong&gt;穷举搜索所有局面保证不输&lt;/strong&gt;——这是个确定性算法问题，不是机器学习问题。&lt;/p&gt;
&lt;p&gt;:::spoiler[先别往下看，自己推一下：为什么&quot;人执黑先行&quot;时装置只要不输就能拿满分？]&lt;/p&gt;
&lt;p&gt;三子棋的状态空间极小——合法局面总共只有 5478 个（不含终局），完全可以在几毫秒内穷举完&lt;strong&gt;整棵博弈树&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;既然能穷举，后手方就不需要&quot;聪明&quot;，只需要&quot;正确&quot;：对每一个人的落子，搜索出一步&lt;strong&gt;让自己永远不输&lt;/strong&gt;的应对。这个性质是数学保证的，不依赖对手强弱、不依赖运气。&lt;/p&gt;
&lt;p&gt;反过来，&quot;执黑先行时能获胜&quot;（第 4 项）反而&lt;strong&gt;不能&lt;/strong&gt;靠搜索保证——因为先手方最优策略是平局。所以那一项题目加了个前提：「若人应对的第 1 步白棋&lt;strong&gt;有错误&lt;/strong&gt;，装置能获胜」。也就是说，装置要能&lt;strong&gt;识别并惩罚人的失误&lt;/strong&gt;，而不是保证赢。两个考点的数学性质完全不同，这是这份题目设计得挺巧的地方。
:::&lt;/p&gt;
&lt;h2&gt;三、赛题「说明」里的隐藏约束&lt;/h2&gt;
&lt;p&gt;评分表下面还有 6 条「说明」，很多人会跳过，但它们其实&lt;strong&gt;直接决定了状态机怎么写&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;maya-diagram&quot; data-maya=&quot;07-placement-rules&quot;&amp;gt;
&amp;lt;div class=&quot;maya-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 810 418&quot; width=&quot;810&quot; height=&quot;418&quot; class=&quot;maya-svg&quot; role=&quot;img&quot; aria-label=&quot;E 题放置规则与约束结构图：棋盘与棋子（由选手自带、棋子直径约 22 加减 2 毫米、黑白棋子在放置处各摆成 1 列）；放置要求（不可触压黑实线、放置后不可脱离方格区域、单颗放置时间不超过 15 秒）；对弈交互约定（人下完按唯一指定按钮通知装置、同时启动计时且装置落子后亮灯停表、第 6 项复位时间同样不超过 15 秒）；环境要求（适应正常室内照明、测试时不得要求特殊照明）。&quot;&amp;gt;&amp;lt;g class=&quot;maya-links&quot;&amp;gt;&amp;lt;path d=&quot;M279.4 215.8 C300.2 215.8 296.4 62.4 317.2 62.4&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M279.4 215.8 C300.2 215.8 296.4 170.1 317.2 170.1&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M279.4 215.8 C300.2 215.8 296.4 277.8 317.2 277.8&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M279.4 215.8 C300.2 215.8 296.4 369.2 317.2 369.2&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M404.9 62.4 C434.2 62.4 428.9 29.8 458.2 29.8&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M404.9 62.4 C434.2 62.4 428.9 62.4 458.2 62.4&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M404.9 62.4 C434.2 62.4 428.9 94.9 458.2 94.9&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M389.4 170.1 C427.2 170.1 420.3 137.5 458.2 137.5&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M389.4 170.1 C427.2 170.1 420.3 170.1 458.2 170.1&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M389.4 170.1 C427.2 170.1 420.3 202.7 458.2 202.7&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M420.4 277.8 C441.2 277.8 437.4 245.2 458.2 245.2&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M420.4 277.8 C441.2 277.8 437.4 277.8 458.2 277.8&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M420.4 277.8 C441.2 277.8 437.4 310.4 458.2 310.4&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M389.4 369.2 C427.2 369.2 420.3 353.0 458.2 353.0&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M389.4 369.2 C427.2 369.2 420.3 385.5 458.2 385.5&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;0&quot; data-d=&quot;0&quot; data-t=&quot;放置规则与约束
原题「说明」摘录&quot;&amp;gt;&amp;lt;text x=&quot;125.4&quot; y=&quot;208.5&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;放置规则与约束&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;125.4&quot; y=&quot;236.0&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;原题「说明」摘录&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;125.4&quot; y=&quot;244.4&quot; width=&quot;154.0&quot; height=&quot;3&quot; rx=&quot;1.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;1&quot; data-d=&quot;1&quot; data-t=&quot;棋盘与棋子&quot;&amp;gt;&amp;lt;circle cx=&quot;316.2&quot; cy=&quot;62.4&quot; r=&quot;3.2&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;325.4&quot; y=&quot;67.6&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;棋盘与棋子&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;325.4&quot; y=&quot;74.6&quot; width=&quot;79.5&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;2&quot; data-d=&quot;2&quot; data-t=&quot;棋盘、棋子由选手自带&quot;&amp;gt;&amp;lt;circle cx=&quot;458.0&quot; cy=&quot;29.8&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;466.4&quot; y=&quot;34.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;棋盘、棋子由选手自带&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;466.4&quot; y=&quot;40.6&quot; width=&quot;137.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;3&quot; data-d=&quot;2&quot; data-t=&quot;棋子直径约 22±2mm，材质自定&quot;&amp;gt;&amp;lt;circle cx=&quot;458.0&quot; cy=&quot;62.4&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;466.4&quot; y=&quot;67.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;棋子直径约 22±2mm，材质自定&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;466.4&quot; y=&quot;73.2&quot; width=&quot;194.1&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;4&quot; data-d=&quot;2&quot; data-t=&quot;黑白棋子在放置处各摆成 1 列&quot;&amp;gt;&amp;lt;circle cx=&quot;458.0&quot; cy=&quot;94.9&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;466.4&quot; y=&quot;99.5&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;黑白棋子在放置处各摆成 1 列&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;466.4&quot; y=&quot;105.7&quot; width=&quot;178.4&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;5&quot; data-d=&quot;1&quot; data-t=&quot;放置要求&quot;&amp;gt;&amp;lt;circle cx=&quot;316.2&quot; cy=&quot;170.1&quot; r=&quot;3.2&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;325.4&quot; y=&quot;175.4&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;放置要求&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;325.4&quot; y=&quot;182.3&quot; width=&quot;64.0&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;6&quot; data-d=&quot;2&quot; data-t=&quot;不可触压黑实线&quot;&amp;gt;&amp;lt;circle cx=&quot;458.0&quot; cy=&quot;137.5&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;466.4&quot; y=&quot;142.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;不可触压黑实线&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;466.4&quot; y=&quot;148.3&quot; width=&quot;96.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;7&quot; data-d=&quot;2&quot; data-t=&quot;放置后不可脱离方格区域&quot;&amp;gt;&amp;lt;circle cx=&quot;458.0&quot; cy=&quot;170.1&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;466.4&quot; y=&quot;174.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;放置后不可脱离方格区域&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;466.4&quot; y=&quot;180.9&quot; width=&quot;150.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;8&quot; data-d=&quot;2&quot; data-t=&quot;单颗放置时间 ≤ 15s，否则扣分&quot;&amp;gt;&amp;lt;circle cx=&quot;458.0&quot; cy=&quot;202.7&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;466.4&quot; y=&quot;207.3&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;单颗放置时间 ≤ 15s，否则扣分&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;466.4&quot; y=&quot;213.4&quot; width=&quot;186.4&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;9&quot; data-d=&quot;1&quot; data-t=&quot;对弈交互约定&quot;&amp;gt;&amp;lt;circle cx=&quot;316.2&quot; cy=&quot;277.8&quot; r=&quot;3.2&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;325.4&quot; y=&quot;283.1&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;对弈交互约定&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;325.4&quot; y=&quot;290.1&quot; width=&quot;95.0&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;10&quot; data-d=&quot;2&quot; data-t=&quot;人下完按唯一指定按钮通知装置&quot;&amp;gt;&amp;lt;circle cx=&quot;458.0&quot; cy=&quot;245.2&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;466.4&quot; y=&quot;249.8&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;人下完按唯一指定按钮通知装置&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;466.4&quot; y=&quot;256.0&quot; width=&quot;191.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;11&quot; data-d=&quot;2&quot; data-t=&quot;同时启动计时，装置落子后亮灯停表&quot;&amp;gt;&amp;lt;circle cx=&quot;458.0&quot; cy=&quot;277.8&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;466.4&quot; y=&quot;282.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;同时启动计时，装置落子后亮灯停表&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;466.4&quot; y=&quot;288.6&quot; width=&quot;218.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;12&quot; data-d=&quot;2&quot; data-t=&quot;第 6 项复位时间同样 ≤ 15s&quot;&amp;gt;&amp;lt;circle cx=&quot;458.0&quot; cy=&quot;310.4&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;466.4&quot; y=&quot;315.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;第 6 项复位时间同样 ≤ 15s&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;466.4&quot; y=&quot;321.2&quot; width=&quot;160.3&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;13&quot; data-d=&quot;1&quot; data-t=&quot;环境要求&quot;&amp;gt;&amp;lt;circle cx=&quot;316.2&quot; cy=&quot;369.2&quot; r=&quot;3.2&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;325.4&quot; y=&quot;374.5&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;环境要求&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;325.4&quot; y=&quot;381.5&quot; width=&quot;64.0&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;14&quot; data-d=&quot;2&quot; data-t=&quot;适应正常室内照明&quot;&amp;gt;&amp;lt;circle cx=&quot;458.0&quot; cy=&quot;353.0&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;466.4&quot; y=&quot;357.6&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;适应正常室内照明&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;466.4&quot; y=&quot;363.8&quot; width=&quot;110.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;15&quot; data-d=&quot;2&quot; data-t=&quot;测试时不得要求特殊照明&quot;&amp;gt;&amp;lt;circle cx=&quot;458.0&quot; cy=&quot;385.5&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;466.4&quot; y=&quot;390.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;测试时不得要求特殊照明&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;466.4&quot; y=&quot;396.3&quot; width=&quot;150.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;其中最容易吃亏的是第 4 条：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;要求（4）（5）中，人下完 1 步棋后，通过按动装置上的&lt;strong&gt;某唯一指定按钮&lt;/strong&gt;通知装置，同时&lt;strong&gt;启动计时&lt;/strong&gt;；装置下完 1 步棋后，通过&lt;strong&gt;亮灯指示&lt;/strong&gt;，计时停止。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这条把&quot;人和机器的回合交接&quot;变成了一个&lt;strong&gt;显式的握手协议&lt;/strong&gt;：不是靠视觉去猜&quot;人下完了没有&quot;，而是等一个按钮中断。这意味着固件里必须有一个&lt;strong&gt;等待按键的状态&lt;/strong&gt;，并且在这个状态下不能阻塞视觉采样（否则人一按按钮，装置还在读取上一次的图像）。&lt;/p&gt;
&lt;p&gt;:::tip[这条约束反而救了我们]
刚开始我们想过&quot;纯视觉判断人是否落子&quot;，即检测棋盘是否发生变化。但这条路有两个坑：一是人移动棋子的过程有中间态，容易误判；二是第 6 项要求&quot;人不动新子、只挪旧子&quot;，纯视觉很难区分&quot;人在下新棋&quot;和&quot;人在挪旧棋&quot;。&lt;/p&gt;
&lt;p&gt;题目给了按钮，等于给了我们一个&lt;strong&gt;明确的时序锚点&lt;/strong&gt;——按下按钮那一刻起，棋盘状态就&quot;定格&quot;了，装置可以放心地采样、识别、决策，不用担心人还在动。&lt;strong&gt;把一个模糊的感知问题变成了确定的时序问题，这是题目给的便宜，不用白不用。&lt;/strong&gt;
:::&lt;/p&gt;
&lt;h2&gt;四、我们的方案&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./03-robot-photo.jpeg&quot; alt=&quot;实物&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;主控&lt;/strong&gt;：ATmega328P（Arduino UNO 核心板）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;开发工具&lt;/strong&gt;：Arduino IDE、OpenMV IDE、VSCode、立创 EDA&lt;/p&gt;
&lt;p&gt;核心思路是&quot;&lt;strong&gt;视觉管感知、主控管决策、舵机管执行&lt;/strong&gt;&quot;，三块分工，用标准协议把彼此解耦：&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;maya-diagram&quot; data-maya=&quot;08-system-architecture&quot;&amp;gt;
&amp;lt;div class=&quot;maya-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 810 451&quot; width=&quot;810&quot; height=&quot;451&quot; class=&quot;maya-svg&quot; role=&quot;img&quot; aria-label=&quot;三子棋对弈机器人系统架构图，以 ATmega328P 为主控：感知层（OpenMV 机器视觉负责棋盘与棋子识别、4×4 矩阵键盘负责落子确认与参数输入）；决策与调度（极小极大穷举搜索做博弈决策、解算各关节目标角度做路径规划、15 秒限时与状态机做时序调度）；执行层（多路 PWM 控制板经 IIC 挂载并驱动机械臂各关节舵机、3D 打印机械臂完成拾取与放置）；交互与显示（SPI OLED 显示状态与提示、UART 解耦视觉与主控、IIC 与 SPI 与 GPIO 三条外设链路）。&quot;&amp;gt;&amp;lt;g class=&quot;maya-links&quot;&amp;gt;&amp;lt;path d=&quot;M290.6 223.9 C311.4 223.9 307.7 62.4 328.4 62.4&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M290.6 223.9 C311.4 223.9 307.7 170.1 328.4 170.1&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M290.6 223.9 C311.4 223.9 307.7 277.8 328.4 277.8&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M290.6 223.9 C311.4 223.9 307.7 385.5 328.4 385.5&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M385.1 62.4 C423.0 62.4 416.1 29.8 453.9 29.8&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M385.1 62.4 C423.0 62.4 416.1 62.4 453.9 62.4&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M385.1 62.4 C423.0 62.4 416.1 94.9 453.9 94.9&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M416.1 170.1 C436.9 170.1 433.2 137.5 453.9 137.5&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M416.1 170.1 C436.9 170.1 433.2 170.1 453.9 170.1&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M416.1 170.1 C436.9 170.1 433.2 202.7 453.9 202.7&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M385.1 277.8 C423.0 277.8 416.1 245.2 453.9 245.2&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M385.1 277.8 C423.0 277.8 416.1 277.8 453.9 277.8&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M385.1 277.8 C423.0 277.8 416.1 310.4 453.9 310.4&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M416.1 385.5 C436.9 385.5 433.2 353.0 453.9 353.0&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M416.1 385.5 C436.9 385.5 433.2 385.5 453.9 385.5&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M416.1 385.5 C436.9 385.5 433.2 418.1 453.9 418.1&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;0&quot; data-d=&quot;0&quot; data-t=&quot;三子棋对弈机器人
ATmega328P 主控&quot;&amp;gt;&amp;lt;text x=&quot;130.7&quot; y=&quot;216.6&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;三子棋对弈机器人&amp;lt;/text&amp;gt;&amp;lt;text x=&quot;130.7&quot; y=&quot;244.2&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;ATmega328P 主控&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;130.7&quot; y=&quot;252.5&quot; width=&quot;159.9&quot; height=&quot;3&quot; rx=&quot;1.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;1&quot; data-d=&quot;1&quot; data-t=&quot;感知层&quot;&amp;gt;&amp;lt;circle cx=&quot;327.4&quot; cy=&quot;62.4&quot; r=&quot;3.2&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;336.6&quot; y=&quot;67.6&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;感知层&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;336.6&quot; y=&quot;74.6&quot; width=&quot;48.5&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;2&quot; data-d=&quot;2&quot; data-t=&quot;OpenMV 机器视觉（STM32H7）&quot;&amp;gt;&amp;lt;circle cx=&quot;453.7&quot; cy=&quot;29.8&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;462.1&quot; y=&quot;34.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;OpenMV 机器视觉（STM32H7）&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;462.1&quot; y=&quot;40.6&quot; width=&quot;199.3&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;3&quot; data-d=&quot;2&quot; data-t=&quot;MicroPython 做棋盘与棋子识别&quot;&amp;gt;&amp;lt;circle cx=&quot;453.7&quot; cy=&quot;62.4&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;462.1&quot; y=&quot;67.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;MicroPython 做棋盘与棋子识别&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;462.1&quot; y=&quot;73.2&quot; width=&quot;190.4&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;4&quot; data-d=&quot;2&quot; data-t=&quot;4×4 矩阵键盘：落子确认与参数输入&quot;&amp;gt;&amp;lt;circle cx=&quot;453.7&quot; cy=&quot;94.9&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;462.1&quot; y=&quot;99.5&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;4×4 矩阵键盘：落子确认与参数输入&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;462.1&quot; y=&quot;105.7&quot; width=&quot;216.9&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;5&quot; data-d=&quot;1&quot; data-t=&quot;决策与调度&quot;&amp;gt;&amp;lt;circle cx=&quot;327.4&quot; cy=&quot;170.1&quot; r=&quot;3.2&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;336.6&quot; y=&quot;175.4&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;决策与调度&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;336.6&quot; y=&quot;182.3&quot; width=&quot;79.5&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;6&quot; data-d=&quot;2&quot; data-t=&quot;博弈决策：极小极大穷举搜索&quot;&amp;gt;&amp;lt;circle cx=&quot;453.7&quot; cy=&quot;137.5&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;462.1&quot; y=&quot;142.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;博弈决策：极小极大穷举搜索&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;462.1&quot; y=&quot;148.3&quot; width=&quot;177.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;7&quot; data-d=&quot;2&quot; data-t=&quot;路径规划：解算各关节目标角度&quot;&amp;gt;&amp;lt;circle cx=&quot;453.7&quot; cy=&quot;170.1&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;462.1&quot; y=&quot;174.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;路径规划：解算各关节目标角度&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;462.1&quot; y=&quot;180.9&quot; width=&quot;191.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;8&quot; data-d=&quot;2&quot; data-t=&quot;时序调度：15s 限时与状态机&quot;&amp;gt;&amp;lt;circle cx=&quot;453.7&quot; cy=&quot;202.7&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;462.1&quot; y=&quot;207.3&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;时序调度：15s 限时与状态机&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;462.1&quot; y=&quot;213.4&quot; width=&quot;176.2&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;9&quot; data-d=&quot;1&quot; data-t=&quot;执行层&quot;&amp;gt;&amp;lt;circle cx=&quot;327.4&quot; cy=&quot;277.8&quot; r=&quot;3.2&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;336.6&quot; y=&quot;283.1&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;执行层&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;336.6&quot; y=&quot;290.1&quot; width=&quot;48.5&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;10&quot; data-d=&quot;2&quot; data-t=&quot;多路 PWM 控制板（IIC 挂载）&quot;&amp;gt;&amp;lt;circle cx=&quot;453.7&quot; cy=&quot;245.2&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;462.1&quot; y=&quot;249.8&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;多路 PWM 控制板（IIC 挂载）&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;462.1&quot; y=&quot;256.0&quot; width=&quot;182.3&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;11&quot; data-d=&quot;2&quot; data-t=&quot;驱动机械臂各关节舵机&quot;&amp;gt;&amp;lt;circle cx=&quot;453.7&quot; cy=&quot;277.8&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;462.1&quot; y=&quot;282.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;驱动机械臂各关节舵机&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;462.1&quot; y=&quot;288.6&quot; width=&quot;137.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;12&quot; data-d=&quot;2&quot; data-t=&quot;3D 打印机械臂拾取与放置棋子&quot;&amp;gt;&amp;lt;circle cx=&quot;453.7&quot; cy=&quot;310.4&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;462.1&quot; y=&quot;315.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;3D 打印机械臂拾取与放置棋子&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;462.1&quot; y=&quot;321.2&quot; width=&quot;183.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;13&quot; data-d=&quot;1&quot; data-t=&quot;交互与显示&quot;&amp;gt;&amp;lt;circle cx=&quot;327.4&quot; cy=&quot;385.5&quot; r=&quot;3.2&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;336.6&quot; y=&quot;390.8&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;交互与显示&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;336.6&quot; y=&quot;397.8&quot; width=&quot;79.5&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;14&quot; data-d=&quot;2&quot; data-t=&quot;SPI OLED 显示状态与操作提示&quot;&amp;gt;&amp;lt;circle cx=&quot;453.7&quot; cy=&quot;353.0&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;462.1&quot; y=&quot;357.6&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;SPI OLED 显示状态与操作提示&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;462.1&quot; y=&quot;363.8&quot; width=&quot;183.8&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;15&quot; data-d=&quot;2&quot; data-t=&quot;UART 把视觉与主控彻底解耦&quot;&amp;gt;&amp;lt;circle cx=&quot;453.7&quot; cy=&quot;385.5&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;462.1&quot; y=&quot;390.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;UART 把视觉与主控彻底解耦&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;462.1&quot; y=&quot;396.3&quot; width=&quot;174.3&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;16&quot; data-d=&quot;2&quot; data-t=&quot;IIC / SPI / GPIO 三条外设链路&quot;&amp;gt;&amp;lt;circle cx=&quot;453.7&quot; cy=&quot;418.1&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;462.1&quot; y=&quot;422.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;IIC / SPI / GPIO 三条外设链路&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;462.1&quot; y=&quot;428.9&quot; width=&quot;178.3&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模块&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;通信方式&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenMV 机器视觉摄像头（STM32H7 主控）&lt;/td&gt;
&lt;td&gt;识别棋盘、棋子与落点&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;UART&lt;/strong&gt; 串口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ATmega328P 主控&lt;/td&gt;
&lt;td&gt;博弈决策、路径规划、时序调度&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SPI OLED 屏幕&lt;/td&gt;
&lt;td&gt;显示当前状态、提示人操作&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;SPI&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;多路 PWM 控制板&lt;/td&gt;
&lt;td&gt;驱动机械臂各关节舵机&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;IIC&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4×4 矩阵键盘&lt;/td&gt;
&lt;td&gt;落子确认、参数输入&lt;/td&gt;
&lt;td&gt;GPIO 扫描&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3D 打印机械臂&lt;/td&gt;
&lt;td&gt;拾取与放置棋子&lt;/td&gt;
&lt;td&gt;舵机驱动&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这个架构里有两个设计决定值得说：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么用 UART 把视觉和主控分开？&lt;/strong&gt; 因为 OpenMV 上的图像处理是&lt;strong&gt;毫秒到几十毫秒级&lt;/strong&gt;的耗时操作，如果塞进主控同一颗芯片，会跟舵机 PWM 的时序打架。分开之后，OpenMV 只负责输出结论（&quot;5 号格有一颗白棋&quot;这种结构化数据），主控拿到结论直接做决策，两边各跑各的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么用 IIC 接 PWM 控制板而不是主控直接输出 PWM？&lt;/strong&gt; 机械臂有多个关节，每个舵机一路 PWM。ATmega328P 的硬件定时器资源有限，直接驱动容易顾此失彼。用 IIC 挂一块多路 PWM 控制板，主控只要发&quot;几号通道转到几度&quot;的指令，剩下的交给控制板自己维持波形——&lt;strong&gt;把时序负担外包出去&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;OLED 走 SPI 是因为它刷新频繁、要快；键盘走 GPIO 扫描是因为 4×4 矩阵本来就只需要几个引脚。&lt;/p&gt;
&lt;h3&gt;4.1 棋盘旋转：一个必须先解决的坐标问题&lt;/h3&gt;
&lt;p&gt;第 3 项（20 分）单独考&quot;棋盘转 ±45° 后还能不能放准&quot;，这题的本质是一句话：&lt;strong&gt;格子编号是棋盘自己的坐标系，而机械臂活在世界坐标系里，两者之间差一个刚体变换。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;设棋盘中心在世界坐标系的位姿为 $(c_x, c_y)$、旋转角为 $\theta$，格子边长 $s$，那么 9 个格子中心的世界坐标是：&lt;/p&gt;
&lt;p&gt;$$
P_{\text{world}} = R(\theta)\cdot\begin{bmatrix} (col-2),s \ (row-2),s \end{bmatrix} + \begin{bmatrix} c_x \ c_y \end{bmatrix}
$$&lt;/p&gt;
&lt;p&gt;其中 $R(\theta)$ 是二维旋转矩阵：&lt;/p&gt;
&lt;p&gt;$$
R(\theta)=\begin{bmatrix}\cos\theta &amp;amp; -\sin\theta\ \sin\theta &amp;amp; \cos\theta\end{bmatrix}
$$&lt;/p&gt;
&lt;p&gt;$(row, col)$ 是 1~3 的格号行列。&lt;strong&gt;代码里唯一允许出现 9 个格子坐标的地方，就是这个函数&lt;/strong&gt;——其余任何地方要格子坐标，都得调它。我们在复盘时也把这条列为最大教训（见文末）。&lt;/p&gt;
&lt;p&gt;旋转角 $\theta$ 也不需要额外传感器，直接从视觉识别出的格点连线反解就行：&lt;/p&gt;
&lt;p&gt;$$
\theta = \operatorname{atan2}!\left(\frac{y_3-y_1}{2},\ \frac{x_3-x_1}{2}\right)
$$&lt;/p&gt;
&lt;p&gt;取第 1 号与第 3 号方格中心的连线，它与水平轴的夹角就是 $\theta$。下面这段可以直接用 &lt;code&gt;g++ -std=c++17&lt;/code&gt; 编译运行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 依赖：g++ -std=c++17（无第三方库）
// 把「格子号 → 世界坐标」抽成唯一入口，棋盘旋转只影响 theta 与 center
#include &amp;lt;cmath&amp;gt;
#include &amp;lt;cstdio&amp;gt;

struct Vec2 { double x, y; };

// 棋盘几何：格子边长 s、中心 center、旋转角 theta（弧度）
struct BoardFrame {
    Vec2   center{0.0, 0.0};
    double s     = 30.0;   // 题目给的格子边长量级
    double theta = 0.0;    // 棋盘绕中心旋转角，±45° 内的任意值
};

// 第 1 号与第 3 号方格中心的连线与水平轴夹角 → 棋盘旋转角
double solve_theta(Vec2 p1, Vec2 p3) {
    return std::atan2(p3.y - p1.y, p3.x - p1.x);
}

// 唯一的坐标入口：row/col 取 1..3
Vec2 grid_to_world(const BoardFrame&amp;amp; f, int row, int col) {
    const double lx = (col - 2) * f.s;   // 棋盘自身坐标系下的偏移
    const double ly = (row - 2) * f.s;
    const double c = std::cos(f.theta), sn = std::sin(f.theta);
    return Vec2{
        f.center.x + c * lx - sn * ly,
        f.center.y + sn * lx + c * ly,
    };
}

// ⚠️ 早期版本（已废弃）：把 9 个格子的世界坐标写死成一张表。
// 棋盘只要一转（theta ≠ 0），这张表整体失准，第 3 项直接零分。
// 保留在这里做对照——它就是下面正文里说的「1 号格被算到 (70, 50)」那个版本：
// const double GRID[9][2] = {
//     { 70,  50}, {100,  50}, {130,  50},
//     { 70,  80}, {100,  80}, {130,  80},
//     { 70, 110}, {100, 110}, {130, 110},
// };

int main() {
    BoardFrame f;
    f.center = {100.0, 80.0};

    // 不加旋转：5 号格应当正好落在棋盘中心
    Vec2 a = grid_to_world(f, 2, 2);
    std::printf(&quot;theta=0    5号格 -&amp;gt; (%.3f, %.3f)\n&quot;, a.x, a.y);

    // 棋盘转 30°：中心仍应在中心，但 1 号格的坐标必须跟着转
    f.theta = 30.0 * M_PI / 180.0;
    Vec2 b = grid_to_world(f, 2, 2);
    Vec2 c = grid_to_world(f, 1, 1);
    std::printf(&quot;theta=30°  5号格 -&amp;gt; (%.3f, %.3f)\n&quot;, b.x, b.y);
    std::printf(&quot;theta=30°  1号格 -&amp;gt; (%.3f, %.3f)\n&quot;, c.x, c.y);

    // 自检：中心格在任意 theta 下都必须等于棋盘中心（容差 1e-9）
    const bool center_ok =
        std::fabs(b.x - f.center.x) &amp;lt; 1e-9 &amp;amp;&amp;amp; std::fabs(b.y - f.center.y) &amp;lt; 1e-9;
    std::printf(&quot;中心格不随 theta 漂移: %s\n&quot;, center_ok ? &quot;PASS&quot; : &quot;FAIL&quot;);
    return center_ok ? 0 : 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;跑出来是这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;theta=0    5号格 -&amp;gt; (100.000, 80.000)
theta=30°  5号格 -&amp;gt; (100.000, 80.000)
theta=30°  1号格 -&amp;gt; (89.019, 39.019)
中心格不随 theta 漂移: PASS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码看着平淡，但它就是第 3 项那 20 分的全部内容。&lt;strong&gt;如果坐标写死（即忽略 $\theta$），1 号格会被算到 $(70, 50)$，而正确位置是 $(89.02, 39.02)$——差了约 22 个单位，相当于大半个格子宽，机械臂落子必压线。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;4.2 博弈决策：三子棋能穷举，所以不要用启发式&lt;/h3&gt;
&lt;p&gt;三子棋的局面数少到可以&lt;strong&gt;完全穷举&lt;/strong&gt;：合法局面 5478 个，博弈树在没有剪枝的情况下也就 50 万量级。ATmega328P 是 16MHz 的 8 位机，但这个规模它&lt;strong&gt;完全跑得动&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;所以我们没有用任何启发式评分函数，直接上&lt;strong&gt;极小极大 + Alpha-Beta 剪枝&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$\text{value}(s)=\begin{cases} +1 &amp;amp; s\ \text{机器胜}\ -1 &amp;amp; s\ \text{人胜}\ \ \ 0 &amp;amp; s\ \text{平局}\end{cases}$$&lt;/p&gt;
&lt;p&gt;搜索策略就是标准的极小极大：机器层取最大值、人层取最小值，等价于&lt;strong&gt;假设对手永远走最优&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;$$
\text{best}(s)=\arg\max_{a\in A(s)}\ \min_{a&apos;\in A(s&apos;)}\ \text{value}(s&apos;&apos;)
$$&lt;/p&gt;
&lt;p&gt;代码如下，同样是标准 C++、可直接编译：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 依赖：g++ -std=c++17
// 极小极大 + Alpha-Beta：三子棋局面数极小，穷举即最优，不需要启发式
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;array&amp;gt;
#include &amp;lt;cstdio&amp;gt;
#include &amp;lt;limits&amp;gt;

constexpr char EMPTY = &apos;.&apos;;

// 平局打破用：仅在「多个走法分数相同」时生效，不参与胜负判定。
// 三子棋的最优结果是平局，所以「好几步分数都一样」是常态，
// 需要一个固定偏好让行为可复现（否则会永远选第一个空格）。
constexpr int PREF[9] = {2,1,2, 1,3,1, 2,1,2};   // 中心 3 &amp;gt; 角 2 &amp;gt; 边 1

// 8 条连线：3 行 + 3 列 + 2 斜
constexpr int LINES[8][3] = {
    {0,1,2},{3,4,5},{6,7,8},
    {0,3,6},{1,4,7},{2,5,8},
    {0,4,8},{2,4,6},
};

// 返回 +1 机器胜 / -1 人胜 / 0 未结束
int winner(const std::array&amp;lt;char,9&amp;gt;&amp;amp; b, char me, char human) {
    for (auto&amp;amp; L : LINES) {
        if (b[L[0]] != EMPTY &amp;amp;&amp;amp; b[L[0]] == b[L[1]] &amp;amp;&amp;amp; b[L[1]] == b[L[2]])
            return b[L[0]] == me ? +1 : -1;
    }
    return 0;
}

// 带 Alpha-Beta 的极小极大。depth 只用于在本层内打破平局（越快赢越好）
int minimax(std::array&amp;lt;char,9&amp;gt;&amp;amp; b, char cur, char me, char human,
            int depth, int alpha, int beta) {
    const int w = winner(b, me, human);
    if (w != 0) return w * (10 - depth);            // 赢在浅层 = 分数更高
    if (std::none_of(b.begin(), b.end(), [](char c){ return c == EMPTY; }))
        return 0;                                   // 平局

    const bool maximizing = (cur == me);
    int best = maximizing ? std::numeric_limits&amp;lt;int&amp;gt;::min()
                          : std::numeric_limits&amp;lt;int&amp;gt;::max();

    for (int i = 0; i &amp;lt; 9; ++i) {
        if (b[i] != EMPTY) continue;
        b[i] = cur;
        const int v = minimax(b, cur == me ? human : me, me, human,
                              depth + 1, alpha, beta);
        b[i] = EMPTY;

        if (maximizing) {
            best  = std::max(best, v);
            alpha = std::max(alpha, v);
        } else {
            best = std::min(best, v);
            beta = std::min(beta, v);
        }
        if (beta &amp;lt;= alpha) break;                   // 剪枝：这一支对手不会走进来
    }
    return best;
}

// 返回机器的最优落子格（0..8）；棋盘已满返回 -1
int best_move(std::array&amp;lt;char,9&amp;gt; b, char me, char human) {
    int best_score = std::numeric_limits&amp;lt;int&amp;gt;::min(), best_idx = -1;
    for (int i = 0; i &amp;lt; 9; ++i) {
        if (b[i] != EMPTY) continue;
        b[i] = me;
        const int v = minimax(b, human, me, human, 1,
                              std::numeric_limits&amp;lt;int&amp;gt;::min(),
                              std::numeric_limits&amp;lt;int&amp;gt;::max());
        b[i] = EMPTY;
        if (best_idx &amp;lt; 0 || v &amp;gt; best_score ||
            (v == best_score &amp;amp;&amp;amp; PREF[i] &amp;gt; PREF[best_idx])) {
            best_score = v;
            best_idx   = i;
        }
    }
    return best_idx;
}

int main() {
    // 局面 1：机器(X)该走，威胁两条线 → 必须占中心
    std::array&amp;lt;char,9&amp;gt; b1 = {
        &apos;X&apos;,&apos;.&apos;,&apos;.&apos;,
        &apos;.&apos;,&apos;.&apos;,&apos;.&apos;,
        &apos;.&apos;,&apos;.&apos;,&apos;X&apos;,
    };
    std::printf(&quot;局面1 机器最优落子: %d（应为中心 4）\n&quot;, best_move(b1, &apos;X&apos;, &apos;O&apos;));

    // 局面 2：人(O) 已有两子连线威胁第 1 行，机器自己没有立即取胜的走法 → 必须堵
    std::array&amp;lt;char,9&amp;gt; b2 = {
        &apos;O&apos;,&apos;O&apos;,&apos;.&apos;,
        &apos;.&apos;,&apos;X&apos;,&apos;.&apos;,
        &apos;.&apos;,&apos;.&apos;,&apos;X&apos;,
    };
    std::printf(&quot;局面2 机器最优落子: %d（应为 2，堵住第 1 行）\n&quot;, best_move(b2, &apos;X&apos;, &apos;O&apos;));

    // 局面 3：空棋盘先手，所有走法都是平局 → 由 PREF 打破平局，落在中心
    std::array&amp;lt;char,9&amp;gt; b3 = {
        &apos;.&apos;,&apos;.&apos;,&apos;.&apos;,
        &apos;.&apos;,&apos;.&apos;,&apos;.&apos;,
        &apos;.&apos;,&apos;.&apos;,&apos;.&apos;,
    };
    std::printf(&quot;局面3 空盘先手:     %d（应为 4，中心）\n&quot;, best_move(b3, &apos;X&apos;, &apos;O&apos;));
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实测输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;局面1 机器最优落子: 4（应为中心 4）
局面2 机器最优落子: 2（应为 2，堵住第 1 行）
局面3 空盘先手:     4（应为 4，中心）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::caution[为什么不用&quot;评分函数 + 权重表&quot;]
我们一开始写过一版&quot;中心 3 分、角 2 分、边 1 分&quot;的启发式，跑起来看着挺聪明，但在第 4 项（惩罚人的失误）上表现不稳定——因为&lt;strong&gt;启发式在对手失误时不一定能找到那条唯一的取胜线&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;穷举的价值就在这里：&lt;strong&gt;只要局面规模允许，穷举给出的不是&quot;好棋&quot;，而是&quot;正确棋&quot;&lt;/strong&gt;。它的正确性不依赖权重调得好不好，也不依赖对手的强度。代价只是 CPU 时间，而三子棋的规模让这个代价几乎为零。&lt;/p&gt;
&lt;p&gt;顺便，Alpha-Beta 的剪枝在这里不是可有可无的优化——它把实际搜索节点数砍掉了大约一个数量级，让 ATmega328P 能在&lt;strong&gt;几十毫秒内&lt;/strong&gt;完成一步决策，远低于 15s 的限时。
:::&lt;/p&gt;
&lt;h3&gt;4.3 「棋子被挪」：状态差分，不是重新识别&lt;/h3&gt;
&lt;p&gt;第 6 项（10 分）的坑在于：人不动新子、只把机器下过的某颗棋子挪个位置。装置必须&lt;strong&gt;发现并复位&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这不能靠&quot;重新识别一遍棋盘&quot;解决——重新识别只会得到&quot;当前棋盘长这样&quot;，而装置需要知道的是&quot;&lt;strong&gt;哪一颗变了&lt;/strong&gt;&quot;。所以必须维护上一帧的状态快照：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 依赖：Python 3.10+，无第三方库
# 棋盘状态差分：找出被人挪动过的棋子，并给出应有的位置
from dataclasses import dataclass, field


@dataclass
class Board:
    &quot;&quot;&quot;cells[i] 为该格的棋子，&apos;.&apos; 表示空，&apos;B&apos;/&apos;W&apos; 为黑/白。&quot;&quot;&quot;
    cells: list[str] = field(default_factory=lambda: [&quot;.&quot;] * 9)

    def board_key(self) -&amp;gt; str:
        return &quot;&quot;.join(self.cells)

    def place(self, idx: int, stone: str) -&amp;gt; None:
        if not 0 &amp;lt;= idx &amp;lt; 9:
            raise IndexError(f&quot;格号越界: {idx}&quot;)
        self.cells[idx] = stone


def diff(prev: Board, now: Board) -&amp;gt; list[tuple[int, int, str]]:
    &quot;&quot;&quot;对比两帧棋盘。

    返回 [(from_idx, to_idx, stone)]：
      from_idx 是上一帧它所在的位置，to_idx 是现在的位置。
      棋子被挪走（原地变空）且别处多出来 → 一对 from/to。
    &quot;&quot;&quot;
    moved: list[tuple[int, int, str]] = []
    # ① 哪些格子&quot;少了子&quot;
    missing = [i for i in range(9)
               if prev.cells[i] != &quot;.&quot; and now.cells[i] == &quot;.&quot;]
    # ② 哪些格子&quot;多了子&quot;（原来空着，现在有子）
    appeared = [i for i in range(9)
                if prev.cells[i] == &quot;.&quot; and now.cells[i] != &quot;.&quot;]

    # ③ 按棋子颜色配对：人的小动作通常是&quot;把某颗子换个格子&quot;
    used: set[int] = set()
    for m in missing:
        for a in appeared:
            if a in used:
                continue
            if prev.cells[m] == now.cells[a]:
                moved.append((m, a, now.cells[a]))
                used.add(a)
                break
    return moved


if __name__ == &quot;__main__&quot;:
    prev = Board()
    # 机器(B)之前下在 4 和 8，人(W)在 0
    prev.place(4, &quot;B&quot;); prev.place(8, &quot;B&quot;); prev.place(0, &quot;W&quot;)

    now = Board()
    # 人把机器在 4 号格的棋子挪到了 6 号格（不落新子）
    now.place(6, &quot;B&quot;); now.place(8, &quot;B&quot;); now.place(0, &quot;W&quot;)

    moved = diff(prev, now)
    print(&quot;检测到被挪动的棋子:&quot;, moved)
    for frm, to, stone in moved:
        print(f&quot;  {stone} 从 {frm} 号格被挪到 {to} 号格 → 应复位到 {frm} 号格&quot;)

    assert moved == [(4, 6, &quot;B&quot;)], &quot;差分结果不符预期&quot;
    print(&quot;自检通过 PASS&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;检测到被挪动的棋子: [(4, 6, &apos;B&apos;)]
  B 从 4 号格被挪到 6 号格 → 应复位到 4 号格
自检通过 PASS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拿到 &lt;code&gt;(4, 6, &apos;B&apos;)&lt;/code&gt; 之后，装置要做的就是把 6 号格的棋子夹起来放回 4 号格——&lt;strong&gt;复位动作和正常落子走的是同一套路径规划代码&lt;/strong&gt;，只是目标坐标不同。这也是为什么我们把&quot;格子号 → 世界坐标&quot;抽成了单一入口（4.1 节）。&lt;/p&gt;
&lt;h2&gt;五、一次落子的完整流程&lt;/h2&gt;
&lt;p&gt;把上面几块拼起来，一次完整的落子是这样一个闭环：&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;maya-diagram&quot; data-maya=&quot;09-move-flow&quot;&amp;gt;
&amp;lt;div class=&quot;maya-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 810 471&quot; width=&quot;810&quot; height=&quot;471&quot; class=&quot;maya-svg&quot; role=&quot;img&quot; aria-label=&quot;一次落子的完整流程：第一步人落子并按下唯一指定按钮通知装置、启动 15 秒计时；第二步 OpenMV 采集棋盘图像并定位 9 个方格与现有棋子；第三步估计棋盘旋转角并把格子号变换为物理坐标；第四步用极小极大搜索当前局面并输出目标方格号；第五步逆运动学解算关节角并经 IIC 下发多路 PWM；第六步更新并记忆本帧棋盘状态，与上一帧差分以发现被变动的棋子。&quot;&amp;gt;&amp;lt;g class=&quot;maya-links&quot;&amp;gt;&amp;lt;path d=&quot;M279.3 233.9 C300.1 233.9 296.3 46.1 317.1 46.1&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M279.3 233.9 C300.1 233.9 296.3 121.2 317.1 121.2&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M279.3 233.9 C300.1 233.9 296.3 196.4 317.1 196.4&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M279.3 233.9 C300.1 233.9 296.3 271.5 317.1 271.5&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M279.3 233.9 C300.1 233.9 296.3 346.7 317.1 346.7&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M279.3 233.9 C300.1 233.9 296.3 421.8 317.1 421.8&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.50&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M437.5 46.1 C466.8 46.1 461.5 29.8 490.8 29.8&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M437.5 46.1 C466.8 46.1 461.5 62.4 490.8 62.4&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M406.5 121.2 C452.8 121.2 444.4 104.9 490.8 104.9&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M406.5 121.2 C452.8 121.2 444.4 137.5 490.8 137.5&quot; fill=&quot;none&quot; stroke=&quot;#ec4899&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M406.5 196.4 C452.8 196.4 444.4 180.1 490.8 180.1&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M406.5 196.4 C452.8 196.4 444.4 212.7 490.8 212.7&quot; fill=&quot;none&quot; stroke=&quot;#06b6d4&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M406.5 271.5 C452.8 271.5 444.4 255.2 490.8 255.2&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M406.5 271.5 C452.8 271.5 444.4 287.8 490.8 287.8&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M453.0 346.7 C473.8 346.7 470.0 330.4 490.8 330.4&quot; fill=&quot;none&quot; stroke=&quot;#10b981&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M453.0 346.7 C473.8 346.7 470.0 363.0 490.8 363.0&quot; fill=&quot;none&quot; stroke=&quot;#10b981&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M453.0 421.8 C473.8 421.8 470.0 405.5 490.8 405.5&quot; fill=&quot;none&quot; stroke=&quot;#ef4444&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;path d=&quot;M453.0 421.8 C473.8 421.8 470.0 438.1 490.8 438.1&quot; fill=&quot;none&quot; stroke=&quot;#ef4444&quot; stroke-opacity=&quot;0.42&quot; stroke-width=&quot;1.4&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;0&quot; data-d=&quot;0&quot; data-t=&quot;一次落子的完整流程&quot;&amp;gt;&amp;lt;text x=&quot;106.3&quot; y=&quot;240.4&quot; font-size=&quot;19&quot; font-weight=&quot;700&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;一次落子的完整流程&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;106.3&quot; y=&quot;248.7&quot; width=&quot;173.0&quot; height=&quot;3&quot; rx=&quot;1.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;1&quot; data-d=&quot;1&quot; data-t=&quot;① 人落子并通知&quot;&amp;gt;&amp;lt;circle cx=&quot;316.1&quot; cy=&quot;46.1&quot; r=&quot;3.2&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;325.3&quot; y=&quot;51.3&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;① 人落子并通知&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;325.3&quot; y=&quot;58.3&quot; width=&quot;112.2&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;2&quot; data-d=&quot;2&quot; data-t=&quot;按下唯一指定按钮&quot;&amp;gt;&amp;lt;circle cx=&quot;490.6&quot; cy=&quot;29.8&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;499.0&quot; y=&quot;34.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;按下唯一指定按钮&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;499.0&quot; y=&quot;40.6&quot; width=&quot;110.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;3&quot; data-d=&quot;2&quot; data-t=&quot;启动 15s 计时&quot;&amp;gt;&amp;lt;circle cx=&quot;490.6&quot; cy=&quot;62.4&quot; r=&quot;2.4&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;499.0&quot; y=&quot;67.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;启动 15s 计时&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;499.0&quot; y=&quot;73.2&quot; width=&quot;85.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;4&quot; data-d=&quot;1&quot; data-t=&quot;② 视觉识别&quot;&amp;gt;&amp;lt;circle cx=&quot;316.1&quot; cy=&quot;121.2&quot; r=&quot;3.2&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;325.3&quot; y=&quot;126.5&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;② 视觉识别&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;325.3&quot; y=&quot;133.5&quot; width=&quot;81.2&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;5&quot; data-d=&quot;2&quot; data-t=&quot;OpenMV 采集棋盘图像&quot;&amp;gt;&amp;lt;circle cx=&quot;490.6&quot; cy=&quot;104.9&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;499.0&quot; y=&quot;109.5&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;OpenMV 采集棋盘图像&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;499.0&quot; y=&quot;115.7&quot; width=&quot;138.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;6&quot; data-d=&quot;2&quot; data-t=&quot;定位 9 个方格与现有棋子&quot;&amp;gt;&amp;lt;circle cx=&quot;490.6&quot; cy=&quot;137.5&quot; r=&quot;2.4&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;499.0&quot; y=&quot;142.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;定位 9 个方格与现有棋子&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;499.0&quot; y=&quot;148.3&quot; width=&quot;151.4&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ec4899&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;7&quot; data-d=&quot;1&quot; data-t=&quot;③ 坐标变换&quot;&amp;gt;&amp;lt;circle cx=&quot;316.1&quot; cy=&quot;196.4&quot; r=&quot;3.2&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;325.3&quot; y=&quot;201.6&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;③ 坐标变换&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;325.3&quot; y=&quot;208.6&quot; width=&quot;81.2&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;8&quot; data-d=&quot;2&quot; data-t=&quot;估计棋盘旋转角&quot;&amp;gt;&amp;lt;circle cx=&quot;490.6&quot; cy=&quot;180.1&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;499.0&quot; y=&quot;184.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;估计棋盘旋转角&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;499.0&quot; y=&quot;190.9&quot; width=&quot;96.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;9&quot; data-d=&quot;2&quot; data-t=&quot;格子号 → 物理坐标&quot;&amp;gt;&amp;lt;circle cx=&quot;490.6&quot; cy=&quot;212.7&quot; r=&quot;2.4&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;499.0&quot; y=&quot;217.3&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;格子号 → 物理坐标&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;499.0&quot; y=&quot;223.4&quot; width=&quot;116.7&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#06b6d4&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;10&quot; data-d=&quot;1&quot; data-t=&quot;④ 博弈决策&quot;&amp;gt;&amp;lt;circle cx=&quot;316.1&quot; cy=&quot;271.5&quot; r=&quot;3.2&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;325.3&quot; y=&quot;276.8&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;④ 博弈决策&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;325.3&quot; y=&quot;283.8&quot; width=&quot;81.2&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;11&quot; data-d=&quot;2&quot; data-t=&quot;极小极大搜索当前局面&quot;&amp;gt;&amp;lt;circle cx=&quot;490.6&quot; cy=&quot;255.2&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;499.0&quot; y=&quot;259.8&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;极小极大搜索当前局面&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;499.0&quot; y=&quot;266.0&quot; width=&quot;137.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;12&quot; data-d=&quot;2&quot; data-t=&quot;输出目标方格号&quot;&amp;gt;&amp;lt;circle cx=&quot;490.6&quot; cy=&quot;287.8&quot; r=&quot;2.4&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;499.0&quot; y=&quot;292.4&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;输出目标方格号&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;499.0&quot; y=&quot;298.6&quot; width=&quot;96.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;13&quot; data-d=&quot;1&quot; data-t=&quot;⑤ 路径规划与执行&quot;&amp;gt;&amp;lt;circle cx=&quot;316.1&quot; cy=&quot;346.7&quot; r=&quot;3.2&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;325.3&quot; y=&quot;351.9&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;⑤ 路径规划与执行&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;325.3&quot; y=&quot;358.9&quot; width=&quot;127.7&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;14&quot; data-d=&quot;2&quot; data-t=&quot;逆运动学解算关节角&quot;&amp;gt;&amp;lt;circle cx=&quot;490.6&quot; cy=&quot;330.4&quot; r=&quot;2.4&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;499.0&quot; y=&quot;335.0&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;逆运动学解算关节角&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;499.0&quot; y=&quot;341.2&quot; width=&quot;123.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;15&quot; data-d=&quot;2&quot; data-t=&quot;IIC 下发多路 PWM&quot;&amp;gt;&amp;lt;circle cx=&quot;490.6&quot; cy=&quot;363.0&quot; r=&quot;2.4&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;499.0&quot; y=&quot;367.6&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;IIC 下发多路 PWM&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;499.0&quot; y=&quot;373.8&quot; width=&quot;111.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#10b981&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;16&quot; data-d=&quot;1&quot; data-t=&quot;⑥ 状态更新与校验&quot;&amp;gt;&amp;lt;circle cx=&quot;316.1&quot; cy=&quot;421.8&quot; r=&quot;3.2&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;325.3&quot; y=&quot;427.1&quot; font-size=&quot;15.5&quot; font-weight=&quot;600&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;⑥ 状态更新与校验&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;325.3&quot; y=&quot;434.1&quot; width=&quot;127.7&quot; height=&quot;2.5&quot; rx=&quot;1.25&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.95&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;17&quot; data-d=&quot;2&quot; data-t=&quot;记忆本帧棋盘状态&quot;&amp;gt;&amp;lt;circle cx=&quot;490.6&quot; cy=&quot;405.5&quot; r=&quot;2.4&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;499.0&quot; y=&quot;410.1&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;记忆本帧棋盘状态&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;499.0&quot; y=&quot;416.3&quot; width=&quot;110.0&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g class=&quot;maya-node&quot; data-n=&quot;18&quot; data-d=&quot;2&quot; data-t=&quot;与上一帧差分，发现被变动的棋子&quot;&amp;gt;&amp;lt;circle cx=&quot;490.6&quot; cy=&quot;438.1&quot; r=&quot;2.4&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.9&quot;/&amp;gt;&amp;lt;text x=&quot;499.0&quot; y=&quot;442.7&quot; font-size=&quot;13.5&quot; font-weight=&quot;500&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; fill=&quot;currentColor&quot;&amp;gt;与上一帧差分，发现被变动的棋子&amp;lt;/text&amp;gt;&amp;lt;rect x=&quot;499.0&quot; y=&quot;448.9&quot; width=&quot;204.5&quot; height=&quot;1.8&quot; rx=&quot;0.9&quot; fill=&quot;#ef4444&quot; fill-opacity=&quot;0.8&quot;/&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;固件里的主循环大致是这些状态，按优先级排（列表是&lt;strong&gt;可勾选的任务列表&lt;/strong&gt;形式，方便对照实现进度）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[x] &lt;code&gt;IDLE&lt;/code&gt;：等待人按按钮，同时 OLED 提示&quot;请落子&quot;&lt;/li&gt;
&lt;li&gt;[x] &lt;code&gt;CAPTURE&lt;/code&gt;：按键中断触发，OpenMV 拍一帧并输出 9 格占用情况&lt;/li&gt;
&lt;li&gt;[x] &lt;code&gt;ALIGN&lt;/code&gt;：由格点连线反解棋盘旋转角 $\theta$ 与中心 $(c_x,c_y)$&lt;/li&gt;
&lt;li&gt;[x] &lt;code&gt;DECIDE&lt;/code&gt;：极小极大搜索出目标格号&lt;/li&gt;
&lt;li&gt;[x] &lt;code&gt;PLAN&lt;/code&gt;：逆运动学解算各关节目标角，检查是否会扫到已落棋子&lt;/li&gt;
&lt;li&gt;[x] &lt;code&gt;EXECUTE&lt;/code&gt;：经 IIC 下发多路 PWM，依次完成&quot;取子 → 移动 → 放下&quot;&lt;/li&gt;
&lt;li&gt;[x] &lt;code&gt;VERIFY&lt;/code&gt;：落下后再拍一帧，确认棋子确实在目标格内且未压线&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;RECOVER&lt;/code&gt;：若 &lt;code&gt;VERIFY&lt;/code&gt; 失败，退回复位流程（我们的最终版只做到了报警提示，没做自动重试）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最后一个 &lt;code&gt;RECOVER&lt;/code&gt; 没做完，是我们这届的一个遗憾——但它在赛题里其实&lt;strong&gt;没有单独分值&lt;/strong&gt;，属于&quot;能加分但不致命&quot;的部分。&lt;/p&gt;
&lt;h2&gt;六、我在队里做的事&lt;/h2&gt;
&lt;p&gt;我是&lt;strong&gt;项目队长&lt;/strong&gt;，具体负责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Arduino 核心板的 C++ 代码编写&lt;/strong&gt;——博弈逻辑与时序调度这部分&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设计各个模块的通讯&lt;/strong&gt;——上面那张表里的 UART / SPI / IIC 三条链路，都是我定的协议格式&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;画 PCB 电路图&lt;/strong&gt;（立创 EDA）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;基于 STM32 的 OpenMV 机器视觉模块的 MicroPython 代码编写&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;机械臂的机械结构设计并 3D 打印&lt;/strong&gt;（拓竹科技）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;四天三夜最大的感受是&quot;&lt;strong&gt;大部分时间边设计 C++ 框架边等 3D 打印机打印机械臂&lt;/strong&gt;&quot;——硬件迭代的周期真的是物理时间，改一版结构件就要等一轮打印，这跟写代码的节奏完全不是一回事。&lt;strong&gt;硬件项目里，等待本身就是工作的一部分。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;七、结果&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./04-award-certificate.jpeg&quot; alt=&quot;获奖证书&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2024 年全国大学生电子设计竞赛天津赛区竞赛&lt;/strong&gt;，我们的《三子棋游戏装置（E 题）》作品荣获&lt;strong&gt;本科组三等奖&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;证书编号：TJ-2024-DZSJJS-0460&lt;/li&gt;
&lt;li&gt;发证单位：天津市教育委员会&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;装置的运行实录（三个视角）：&lt;/p&gt;
&lt;p&gt;&amp;lt;video src=&quot;/blog/videos/chess-robot-edc-2024/demo-1.mp4?v=202609160951&quot; controls preload=&quot;metadata&quot; width=&quot;100%&quot; style=&quot;max-width:364px;display:block;margin:0 auto&quot;&amp;gt;&amp;lt;/video&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;video src=&quot;/blog/videos/chess-robot-edc-2024/demo-2.mp4?v=202609151740&quot; controls preload=&quot;metadata&quot; width=&quot;100%&quot;&amp;gt;&amp;lt;/video&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;video src=&quot;/blog/videos/chess-robot-edc-2024/demo-3.mp4?v=202609151740&quot; controls preload=&quot;metadata&quot; width=&quot;100%&quot;&amp;gt;&amp;lt;/video&amp;gt;&lt;/p&gt;
&lt;p&gt;:::note[关于视频]
这三段视频已随站点一起托管（&lt;code&gt;public/videos/chess-robot-edc-2024/&lt;/code&gt;），不依赖任何外部站点。&lt;/p&gt;
&lt;p&gt;注意 &lt;strong&gt;Astro 不会把文章目录里的视频文件打包进产物&lt;/strong&gt;，所以视频必须放在 &lt;code&gt;public/&lt;/code&gt; 下、用 &lt;code&gt;/blog/videos/xxx.mp4&lt;/code&gt; 这样的绝对路径引用；留在文章目录里引用是会 404 的。&lt;/p&gt;
&lt;p&gt;另外 &lt;code&gt;public/&lt;/code&gt; 下的文件&lt;strong&gt;不会被 Astro 加哈希&lt;/strong&gt;，路径不变的话读者浏览器会一直放旧片，所以 URL 上挂了一串 &lt;code&gt;?v=YYYYMMDDHHMM&lt;/code&gt; 做缓存击穿。&lt;strong&gt;各段视频的版本号互相独立&lt;/strong&gt;——换哪一段就只推哪一段的号，避免让读者重下没动过的片子。&lt;/p&gt;
&lt;p&gt;第一段是竖屏素材（源片 364×480），所以额外挂了一条 &lt;code&gt;max-width:364px&lt;/code&gt; 的内联样式：&lt;code&gt;&amp;lt;video width=&quot;100%&quot;&amp;gt;&lt;/code&gt; 会把它拉到正文栏宽（约 703px）并按比例撑到 927px 高，既超出屏幕又把 364px 的源片放大近 2 倍而变糊。限到源片原生宽度后是 1:1 显示、不重采样，高度 480px 也和三段视频里的另外两段（约 395px）接近。
:::&lt;/p&gt;
&lt;p&gt;当时的制作过程我也发在了小红书上：&lt;/p&gt;
&lt;p&gt;📕 &lt;a href=&quot;https://www.xiaohongshu.com/discovery/item/66ab87c6000000000503027b&quot;&gt;2024 电赛 E 题象棋机器人 - 小红书&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;八、工程文件与 3D 模型&lt;/h2&gt;
&lt;p&gt;:::warning[这一节还在补]
赛题作品的文件当时散在几台电脑里，正在逐步整理归档。计划补上这些，先列在这里免得忘：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 机械臂结构件 &lt;strong&gt;3D 模型&lt;/strong&gt;（STEP / STL，含各关节装配关系）&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;PCB 原理图与 Gerber&lt;/strong&gt;（立创 EDA 工程导出）&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;主控固件&lt;/strong&gt;（Arduino 工程，含博弈与时序调度）&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;OpenMV 视觉脚本&lt;/strong&gt;（MicroPython）&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;BOM 物料清单&lt;/strong&gt;（舵机型号、PWM 控制板、结构件耗材）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;补齐之前，本文 4.1~4.3 节的核心算法（坐标变换、极小极大、状态差分）都是可直接编译/运行的独立文件，能先拿来跑通逻辑。
:::&lt;/p&gt;
&lt;p&gt;设计报告本身属于竞赛提交材料，按赛区规定不单独公开；上面这份清单是&lt;strong&gt;装置的可复现部分&lt;/strong&gt;，不涉及报告的正文。&lt;/p&gt;
&lt;h2&gt;九、回过头看&lt;/h2&gt;
&lt;p&gt;现在复盘，如果重做一次，我会改三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;先解决&quot;放得准&quot;，再解决&quot;下得聪明&quot;。&lt;/strong&gt; 评分表里 65 分（第 1、2、3、7 项）都在考&lt;strong&gt;基础放置能力&lt;/strong&gt;，只有 60 分跟博弈策略有关。我们前期花了不少时间在博弈算法上，其实基础放置的稳定性才是拿分的大头。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;把坐标系统一抽象出来。&lt;/strong&gt; 棋盘旋转那题本质是坐标变换，如果一开始就把&quot;格子号 → 物理坐标&quot;做成一个函数（就像 4.1 节那样），而不是散落在各处写死，第 3 项会轻松很多。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;给&quot;棋子被挪&quot;留状态机。&lt;/strong&gt; 第 6 项需要记忆上一帧棋盘状态，这在一开始就该作为数据模型的一部分设计进去——我们当时是快比赛结束才补的，补得很狼狈。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::tip[GitHub 上的提醒写法]
上面这几条经验教训，如果是在 GitHub 的 issue 或 PR 里追进度，可以直接用原生语法写成任务列表：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; [!TIP]
&amp;gt; 这三条是&quot;如果重做&quot;的改进项，不是 bug。用复选框记进度最直观。

- [x] 优先保证基础放置稳定性
- [x] 坐标变换抽成单一函数
- [ ] 状态差分纳入数据模型
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;不过说到底，原文那句总结我至今认同——&lt;strong&gt;&quot;学会更多，成长才是最好的成果。&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;四天三夜教会我的不是把某个算法写得多漂亮，而是&lt;strong&gt;在时间、硬件、体力三重约束下，怎么排优先级、怎么在&quot;能跑的丑方案&quot;和&quot;优雅的烂尾&quot;之间选前者&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;十、参考与延伸&lt;/h2&gt;
&lt;p&gt;这套装置用到的三个上游项目，都是各自领域里&quot;最小可复现&quot;的代表：&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;openmv/openmv&quot;}&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;adafruit/Adafruit-PWM-Servo-Driver-Library&quot;}&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;arduino/ArduinoCore-avr&quot;}&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OpenMV 固件与 IDE&lt;/strong&gt;——&lt;code&gt;find_blobs&lt;/code&gt; / &lt;code&gt;find_lines&lt;/code&gt; 那套视觉算子就是它提供的，方格的识别与定位直接建在上面&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adafruit PWM Servo Driver&lt;/strong&gt;——多路 PWM 控制板的参考驱动，IIC 写寄存器控制各通道脉宽&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ArduinoCore-avr&lt;/strong&gt;——ATmega328P 的官方核心，硬件定时器与中断的用法都在里面&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;技术栈：ATmega328P（Arduino UNO）· OpenMV（STM32H7，MicroPython）· UART / SPI / IIC · OLED · 4×4 矩阵键盘 · 多路 PWM 舵机控制 · 3D 打印机械臂（拓竹）&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>论文登刊《中国科技人才》：基于传感器融合的自主避障无人机导航与控制算法研究</title><link>https://ura2039.xyz/blog/posts/uav-sensor-fusion-paper/</link><guid isPermaLink="true">https://ura2039.xyz/blog/posts/uav-sensor-fusion-paper/</guid><description>我的第一篇期刊论文，刊于《中国科技人才》2024 年第 1 期。用视觉、激光雷达与惯性导航三种传感器融合，解决无人机在复杂环境中的自主避障与导航控制问题。这篇是它的一次「重读」：论文原文的梳理之外，补上时间同步、EKF、体素栅格与 ESDF、MPC 二次规划这些当时没展开的工程细节，并给出三段可直接运行的代码。</description><pubDate>Tue, 02 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2024 年 1 月，我参与撰写的第一篇期刊论文正式刊出。&lt;/p&gt;
&lt;p&gt;题目是《&lt;strong&gt;基于传感器融合的自主避障无人机导航与控制算法研究&lt;/strong&gt;》，发表在《中国科技人才》&lt;strong&gt;2024 年第 1 期&lt;/strong&gt;。这篇文章源于我们团队的一个大创项目——&lt;strong&gt;自主避障无人机控制系统设计&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;:::note[这篇博客怎么读]
文章分两类内容，我刻意分开写，免得把「论文里写的」和「我后来想到的」混为一谈：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不加标记的段落&lt;/strong&gt;——论文原文的梳理，尽量忠实于纸面内容；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;带「延伸」二字的段落&lt;/strong&gt;——我当时没写进去、后来真正动手做工程时才补上的东西（时间同步、EKF、体素栅格、ESDF、MPC 的 QP 形式等）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;论文本身篇幅不长、偏综述，公式也写得比较概括。所以这篇博客的价值与其说是「复述论文」，不如说是&lt;strong&gt;把论文里每个结论还原成它背后的工程问题&lt;/strong&gt;。文末有三段可以直接跑的代码。
:::&lt;/p&gt;
&lt;h2&gt;论文信息&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;项&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;题目&lt;/td&gt;
&lt;td&gt;基于传感器融合的自主避障无人机导航与控制算法研究&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;作者&lt;/td&gt;
&lt;td&gt;王琪、热合木图拉·艾力、朱有杰、王东成、游闯闯、张馨宁&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;期刊&lt;/td&gt;
&lt;td&gt;《中国科技人才》2024 年第 1 期&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;刊号&lt;/td&gt;
&lt;td&gt;ISSN 2095-8951 / CN 10-1256/G3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;邮发代码&lt;/td&gt;
&lt;td&gt;82-924&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;基金项目&lt;/td&gt;
&lt;td&gt;天津市级大学生创新创业训练计划项目&quot;自主避障无人机控制系统设计&quot;（项目编号：202310066082）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;关键词&lt;/td&gt;
&lt;td&gt;无人机；传感器融合；自主避障；导航算法；控制算法&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;研究背景&lt;/h2&gt;
&lt;p&gt;无人机已经在军事、民用、科研等领域广泛使用，但它在执行任务时常常面临&lt;strong&gt;复杂多变的环境&lt;/strong&gt;：城市建筑物、树木等障碍物会严重影响导航与控制。&lt;/p&gt;
&lt;p&gt;问题的核心在于&lt;strong&gt;单一传感器都不够可靠&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;视觉传感器&lt;/strong&gt;能提供地形、障碍物等丰富的视觉信息，但&lt;strong&gt;受光照、天气影响大&lt;/strong&gt;，数据可靠性会掉&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;激光雷达&lt;/strong&gt;测距精确、能拿到三维空间信息，&lt;strong&gt;不受光照和天气干扰&lt;/strong&gt;，但成本高、且拿不到颜色/纹理这类语义信息&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;惯性导航（IMU）&lt;strong&gt;用加速度计和陀螺仪测运动状态，&lt;strong&gt;实时性最好&lt;/strong&gt;，但&lt;/strong&gt;积分误差会随时间累积&lt;/strong&gt;，跑久了就飘&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;于是思路就自然浮现：&lt;strong&gt;把它们融起来，让各自的缺点被别人的长处补上&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;一、传感器融合原理&lt;/h2&gt;
&lt;p&gt;传感器融合的本质是&lt;strong&gt;整合多个传感器的信息，以获取更全面、准确的环境数据&lt;/strong&gt;。本研究采用三类主要传感器：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;传感器&lt;/th&gt;
&lt;th&gt;提供的信息&lt;/th&gt;
&lt;th&gt;短板&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;视觉传感器&lt;/td&gt;
&lt;td&gt;周围地形、障碍物及其它重要特征的视觉信息&lt;/td&gt;
&lt;td&gt;受光照、天气影响，单独使用数据不够可靠&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;激光雷达&lt;/td&gt;
&lt;td&gt;精确的三维空间信息，检测物体位置与形状&lt;/td&gt;
&lt;td&gt;相对最稳定，不受光照和天气干扰，复杂环境下表现良好&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;惯性导航&lt;/td&gt;
&lt;td&gt;无人机的加速度与角速度，实时运动状态&lt;/td&gt;
&lt;td&gt;随时间推移，积分误差累积，精度下降&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;融合的关键在于&lt;strong&gt;通过精确的数据融合算法，把三类传感器的信息整合成一份综合性数据集&lt;/strong&gt;，从而弥补各自的缺点、实现更全面准确的环境感知。这份数据集支撑导航和控制算法的精确执行，让无人机在不同环境条件下都保持&lt;strong&gt;适应性与稳健性&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;:::important[论文里最容易被忽略的一句]
上面这段里，「整合成一份综合性数据集」这个说法其实&lt;strong&gt;藏着一个关键跳跃&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;三路传感器的输出根本不是同一种东西——相机给的是像素，激光雷达给的是点云，IMU 给的是角速度/加速度。它们&lt;strong&gt;没法直接相加，也没法拼成一张表&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;工程上真正被融合出来的，不是「数据集」，而是&lt;strong&gt;一个带不确定度的状态估计&lt;/strong&gt;：位置、姿态、速度，以及它们各自协方差。所谓&quot;融合&quot;，本质是&lt;strong&gt;把三路观测都当成对同一个隐藏状态的带噪测量，然后反推出这个状态最可能的取值&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这个认识一转变，后面所有问题（时间同步、标定、滤波）就都顺理成章地出现了——因为「同一个状态」这个前提，要求三路数据必须能对齐到同一时刻、同一个坐标系。
:::&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;block-diagram&quot; data-block=&quot;09-fusion-pipeline&quot;&amp;gt;
&amp;lt;div class=&quot;block-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 1085 290&quot; width=&quot;1085&quot; height=&quot;290&quot; class=&quot;block-svg&quot; role=&quot;img&quot; aria-label=&quot;传感器融合数据流图：视觉相机、激光雷达、惯性导航三路数据经时间同步与缓冲后进行扩展卡尔曼滤波，再由输出预测器推到当前时刻，供规划与控制器使用&quot;&amp;gt;
&amp;lt;g transform=&quot;translate(18,18)&quot;&amp;gt;
&amp;lt;g class=&quot;blk-links&quot;&amp;gt;
&amp;lt;path d=&quot;M 125.8 30.0 L 160.5 30.0 L 160.5 101.9 L 207.3 101.9&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.45&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 212.5 101.9 L 207.3 99.2 L 207.3 104.6 Z&quot; fill=&quot;currentColor&quot;/&amp;gt;
&amp;lt;line x1=&quot;125.8&quot; y1=&quot;101.9&quot; x2=&quot;207.3&quot; y2=&quot;101.9&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.45&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 212.5 101.9 L 207.3 99.2 L 207.3 104.6 Z&quot; fill=&quot;currentColor&quot;/&amp;gt;
&amp;lt;path d=&quot;M 158.5 173.8 L 190.9 173.8 L 190.9 101.9 L 207.3 101.9&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.45&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 212.5 101.9 L 207.3 99.2 L 207.3 104.6 Z&quot; fill=&quot;currentColor&quot;/&amp;gt;
&amp;lt;line x1=&quot;420.5&quot; y1=&quot;101.9&quot; x2=&quot;469.3&quot; y2=&quot;101.9&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.45&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 474.5 101.9 L 469.3 99.2 L 469.3 104.6 Z&quot; fill=&quot;currentColor&quot;/&amp;gt;
&amp;lt;line x1=&quot;630.2&quot; y1=&quot;101.9&quot; x2=&quot;679.0&quot; y2=&quot;101.9&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.45&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 684.2 101.9 L 679.0 99.2 L 679.0 104.6 Z&quot; fill=&quot;currentColor&quot;/&amp;gt;
&amp;lt;line x1=&quot;850.2&quot; y1=&quot;101.9&quot; x2=&quot;899.0&quot; y2=&quot;101.9&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.45&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 904.2 101.9 L 899.0 99.2 L 899.0 104.6 Z&quot; fill=&quot;currentColor&quot;/&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-boxes&quot;&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;视觉相机&quot;&amp;gt;
&amp;lt;rect x=&quot;32.8&quot; y=&quot;0.0&quot; width=&quot;93.0&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.045&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.18&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;79.3&quot; y=&quot;21.6&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;视觉相机&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;79.3&quot; y=&quot;45.2&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;图像 · 30 Hz&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;激光雷达&quot;&amp;gt;
&amp;lt;rect x=&quot;32.8&quot; y=&quot;71.9&quot; width=&quot;93.0&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.045&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.18&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;79.3&quot; y=&quot;93.5&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;激光雷达&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;79.3&quot; y=&quot;117.1&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;点云 · 10 Hz&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;惯性导航 IMU&quot;&amp;gt;
&amp;lt;rect x=&quot;0.0&quot; y=&quot;143.8&quot; width=&quot;158.5&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.045&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.18&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;79.3&quot; y=&quot;165.4&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;惯性导航 IMU&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;79.3&quot; y=&quot;189.0&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;角速度 / 加速度 · 200 Hz&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;时间同步与缓冲&quot;&amp;gt;
&amp;lt;rect x=&quot;212.5&quot; y=&quot;71.9&quot; width=&quot;208.0&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.14&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.75&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;316.5&quot; y=&quot;93.5&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;时间同步与缓冲&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;316.5&quot; y=&quot;117.1&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;FIFO 环形缓冲 + 逐传感器延迟补偿&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;扩展卡尔曼滤波&quot;&amp;gt;
&amp;lt;rect x=&quot;474.5&quot; y=&quot;71.9&quot; width=&quot;155.6&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.045&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.18&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;552.3&quot; y=&quot;93.5&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;扩展卡尔曼滤波&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;552.3&quot; y=&quot;117.1&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;IMU 前向传播 → 观测更新&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;输出预测器&quot;&amp;gt;
&amp;lt;rect x=&quot;684.2&quot; y=&quot;71.9&quot; width=&quot;166.0&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.045&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.18&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;767.2&quot; y=&quot;93.5&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;输出预测器&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;767.2&quot; y=&quot;117.1&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;把状态推到「现在」这一刻&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;规划器 / 控制器&quot;&amp;gt;
&amp;lt;rect x=&quot;904.2&quot; y=&quot;71.9&quot; width=&quot;144.7&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.045&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.18&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;976.5&quot; y=&quot;93.5&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;规划器 / 控制器&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;976.5&quot; y=&quot;117.1&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;100 ~ 250 Hz 取用状态&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-gap-labels&quot;&amp;gt;
&amp;lt;text x=&quot;169.1&quot; y=&quot;81.9&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;12&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;&amp;gt;各自带时间戳&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;657.2&quot; y=&quot;81.9&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;12&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.5&quot;&amp;gt;延迟时轴&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-feedback&quot;&amp;gt;
&amp;lt;path d=&quot;M 976.5 131.8 L 976.5 231.8 Q 976.5 237.8 970.5 237.8 L 85.3 237.8 Q 79.3 237.8 79.3 231.8 L 79.3 65.1&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.6&quot; stroke-width=&quot;1.3&quot; stroke-dasharray=&quot;5 3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 79.3 59.9 L 76.6 65.1 L 82.0 65.1 Z&quot; fill=&quot;#8b5cf6&quot;/&amp;gt;
&amp;lt;text x=&quot;527.9&quot; y=&quot;230.8&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.66&quot;&amp;gt;闭环：控制 → 机体运动 → 重新观测&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;figcaption&amp;gt;图 1 · 传感器融合的数据流：三路传感器的频率和延迟各不相同，必须先在同一时间轴上对齐，才有「融合」可言&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h3&gt;1.1 延伸：把三路数据合成一个位姿，中间隔着三道坎&lt;/h3&gt;
&lt;p&gt;论文点出了&quot;为什么要融合&quot;，但没有展开&quot;融合具体怎么做&quot;。真动手时，横在中间的是三件必须先解决的事：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;坎&lt;/th&gt;
&lt;th&gt;不做会怎样&lt;/th&gt;
&lt;th&gt;工程上怎么做&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;时间同步&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;200 Hz 的 IMU 和 10 Hz 的雷达各说各话，高速飞行时 100 ms 的错位就是几十厘米的位置误差&lt;/td&gt;
&lt;td&gt;每路数据带统一时基的时间戳，缓存在环形缓冲里，按时间对齐后再融合&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;外参标定&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;相机和雷达装在机体的不同位置、不同朝向上，同一个障碍物在两边算出的坐标对不上&lt;/td&gt;
&lt;td&gt;离线标定出「传感器 → 机体」的旋转与平移，融合前先统一到机体坐标系&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;坐标系约定&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;一边用东北天、一边用北东地，偏航角正负号差一个方向，飞出去就是反的&lt;/td&gt;
&lt;td&gt;全链路固定一套约定：惯导用 ENU，机体用 FLU，接口处显式转换&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;① 时间同步是三件事里最容易被低估的。&lt;/strong&gt; 真实系统里各路传感器不但频率不同，&lt;strong&gt;延迟也不同&lt;/strong&gt;：IMU 几乎无延迟，而 GPS 从测量到送达飞控通常有一百多毫秒的滞后。开源飞控 PX4 的 EKF2 用的办法很值得借鉴——它维护一条&quot;&lt;strong&gt;延迟融合时轴&lt;/strong&gt;&quot;：所有传感器先按时间戳压进 FIFO 缓冲，滤波器每次推进到某个历史时刻，再从这个时刻附近取出各路观测一起融合。它的 &lt;code&gt;EKF2_GPS_DELAY&lt;/code&gt; 参数默认值就是 &lt;strong&gt;110 ms&lt;/strong&gt;，专门用来补偿 GPS 的测量延迟。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② 外参标定&lt;/strong&gt;要在融合前做掉。相机、雷达、IMU 的安装位置不同，同一障碍物在各自坐标系里的坐标是不同的，必须通过离线标定求出「传感器 → 机体」的变换矩阵，融合时先统一。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;③ 还有一个隐蔽的问题：融合完的状态，比传感器本身还&quot;旧&quot;。&lt;/strong&gt; 因为要等各路延迟不同的观测到齐，滤波器输出的是&lt;strong&gt;过去某一时刻&lt;/strong&gt;的状态。PX4 的解决办法是再串一级&quot;&lt;strong&gt;输出预测器&lt;/strong&gt;&quot;：用缓冲的 IMU 数据把那个偏旧的状态向前外推到当前时刻，再交给控制器。这一级在实时系统里不能省——否则控制器拿到的永远是百毫秒前的信息。&lt;/p&gt;
&lt;p&gt;:::tip[判断一个融合框架好不好，先看它怎么处理时间]
只讲&quot;融合多种传感器&quot;而不讲时间同步与延迟补偿的方案，基本可以判定没上过真机。&lt;/p&gt;
&lt;p&gt;因为&lt;strong&gt;频率和延迟的差异是物理事实&lt;/strong&gt;，不会因为算法写得漂亮就消失。这也是我后来回头看这篇论文时，觉得最该补的一块。
:::&lt;/p&gt;
&lt;h3&gt;1.2 延伸：从「互补滤波」到卡尔曼滤波&lt;/h3&gt;
&lt;p&gt;把 IMU 和三路外部观测合起来，最朴素的写法是&lt;strong&gt;互补滤波&lt;/strong&gt;：让 IMU 负责高频（动态响应好），让外部观测负责低频（不漂），用一个高通加一个低通把两者叠起来：&lt;/p&gt;
&lt;p&gt;$$\hat{\theta}(s) = \underbrace{\frac{s}{s+\frac{1}{\tau}}}&lt;em&gt;{\text{高通} \to \text{IMU}} \theta&lt;/em&gt;{\text{IMU}}(s) + \underbrace{\frac{1}{s+\frac{1}{\tau}}}&lt;em&gt;{\text{低通} \to \text{外部观测}} \theta&lt;/em&gt;{\text{观测}}(s)$$&lt;/p&gt;
&lt;p&gt;它的优点是&lt;strong&gt;算得极快、调参只有一个&lt;/strong&gt; $\tau$；缺点是&lt;strong&gt;同一个 $\tau$ 兼顾不了&quot;抗漂&quot;和&quot;抗噪&quot;&lt;/strong&gt;，也没法表达&quot;这个观测不可信&quot;。&lt;/p&gt;
&lt;p&gt;真实系统基本都会过渡到&lt;strong&gt;扩展卡尔曼滤波（EKF）&lt;/strong&gt;。它的结构非常清晰，就两步：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;预测&lt;/strong&gt;（用 IMU 把状态推一步，同时把协方差一起推，不确定性变大）：&lt;/p&gt;
&lt;p&gt;$$\hat{x}&lt;em&gt;{k}^{-} = f(\hat{x}&lt;/em&gt;{k-1}, u_k), \qquad P_{k}^{-} = F_k P_{k-1} F_k^{\top} + Q_k$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;更新&lt;/strong&gt;（来了观测就校正，用卡尔曼增益决定&quot;信自己多少、信观测多少&quot;）：&lt;/p&gt;
&lt;p&gt;$$K_k = P_{k}^{-} H_k^{\top}\left(H_k P_{k}^{-} H_k^{\top} + R_k\right)^{-1}$$&lt;/p&gt;
&lt;p&gt;$$\hat{x}&lt;em&gt;k = \hat{x}&lt;/em&gt;{k}^{-} + K_k\left(z_k - H_k \hat{x}&lt;em&gt;{k}^{-}\right), \qquad P_k = (I - K_k H_k) P&lt;/em&gt;{k}^{-}$$&lt;/p&gt;
&lt;p&gt;这里几个量值得掰开说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$Q_k$ 是&lt;strong&gt;过程噪声&lt;/strong&gt;：你有多不信任自己的运动模型（比如 IMU 的零偏、颠簸）&lt;/li&gt;
&lt;li&gt;$R_k$ 是&lt;strong&gt;观测噪声&lt;/strong&gt;：你有多不信任这次观测（比如 GPS 的星数少，$R$ 就写大）&lt;/li&gt;
&lt;li&gt;$K_k$ 是&lt;strong&gt;自动权衡出来的&lt;/strong&gt;：$R$ 大则 $K$ 小（不太相信观测），$P^{-}$ 大则 $K$ 大（不太相信自己）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就是它比互补滤波强的地方——&lt;strong&gt;&quot;两边各信多少&quot;是算出来的，不是一个固定的 $\tau$&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;业界成熟的 EKF 实现通常把状态扩到 &lt;strong&gt;20 多个维度&lt;/strong&gt;（PX4 的 EKF2 是 24 状态）：除了位置、速度、姿态，还要把&lt;strong&gt;陀螺零偏、加速度计零偏、地磁矢量、风扰动&lt;/strong&gt;等一起估掉。原因正是下面这段演示要说明的问题。&lt;/p&gt;
&lt;h3&gt;1.3 延伸：一段能跑的 EKF 融合&lt;/h3&gt;
&lt;p&gt;下面这段代码模拟了一个很典型的场景：&lt;strong&gt;IMU 以 200 Hz 高频输出但含零偏，位置观测以 10 Hz 低频到来&lt;/strong&gt;。为了让对比有意义，我把 IMU 零偏也放进了状态向量里一起估。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 依赖：Python 3.10+，numpy
# 演示：IMU 纯积分 vs EKF 融合（IMU 高频 + 位置传感器低频）
import numpy as np

rng = np.random.default_rng(0)

DT      = 0.005          # IMU 周期 200 Hz
GPS_EVERY = 20           # 每 20 个 IMU 采样来一次位置观测 -&amp;gt; 100 ms
STEPS   = 4000           # 20 s

# ---- 真值：匀加速直线运动 ----
A_TRUE  = 0.3            # m/s^2
BIAS_TRUE = 0.05         # IMU 零偏，纯积分就是被它拖垮的

# ---- 传感器（含噪）----
acc_meas = A_TRUE + BIAS_TRUE + rng.normal(0.0, 0.15, STEPS)   # IMU 加速度（含零偏 + 噪声）
pos_meas = np.full(STEPS, np.nan)
p_true = np.zeros(STEPS); v_true = np.zeros(STEPS)
for k in range(1, STEPS):
    v_true[k] = v_true[k - 1] + A_TRUE * DT
    p_true[k] = p_true[k - 1] + v_true[k - 1] * DT + 0.5 * A_TRUE * DT ** 2
for k in range(0, STEPS, GPS_EVERY):                 # 低频位置观测，单独加 1 m 噪声
    pos_meas[k] = p_true[k] + rng.normal(0.0, 1.0)

# ---- EKF：状态 [p, v, b]，把 IMU 零偏也一起估出来 ----
F = np.array([[1.0, DT, -0.5 * DT ** 2],
              [0.0, 1.0,        -DT],
              [0.0, 0.0,        1.0]])
B = np.array([0.5 * DT ** 2, DT, 0.0])
Q = np.diag([1e-6, 1e-4, 1e-9])       # 过程噪声
R = np.array([[1.0 ** 2]])            # 位置观测噪声

H = np.array([[1.0, 0.0, 0.0]])
x = np.zeros(3)                       # 估计的状态
P = np.eye(3) * 1.0

p_raw, v_raw = 0.0, 0.0               # 纯积分对照
err_raw, err_ekf, bias_est = [], [], []

for k in range(STEPS):
    # --- 预测：用 IMU 推一步 ---
    x = F @ x + B * acc_meas[k]
    P = F @ P @ F.T + Q

    # --- 更新：有位置观测就校正 ---
    if not np.isnan(pos_meas[k]):
        y = pos_meas[k] - H @ x
        S = H @ P @ H.T + R
        K = P @ H.T @ np.linalg.inv(S)
        x = x + K @ y
        P = (np.eye(3) - K @ H) @ P

    # --- 对照：IMU 纯积分（不知道有零偏，误差会二次方发散）---
    p_raw += v_raw * DT + 0.5 * acc_meas[k] * DT ** 2
    v_raw += acc_meas[k] * DT

    err_raw.append(abs(p_raw - p_true[k]))
    err_ekf.append(abs(x[0] - p_true[k]))
    bias_est.append(x[2])

print(f&quot;20 s 后位置误差：IMU 纯积分 {err_raw[-1]:6.2f} m  |  EKF {err_ekf[-1]:5.2f} m&quot;)
print(f&quot;位置误差均值：   IMU 纯积分 {np.mean(err_raw):6.2f} m  |  EKF {np.mean(err_ekf):5.2f} m&quot;)
print(f&quot;零偏估计：真值 {BIAS_TRUE:.3f}  →  EKF 收敛到 {bias_est[-1]:.3f} m/s^2&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实测输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;20 s 后位置误差：IMU 纯积分   9.28 m  |  EKF  0.35 m
位置误差均值：   IMU 纯积分   3.09 m  |  EKF  0.28 m
零偏估计：真值 0.050  →  EKF 收敛到 0.032 m/s^2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;20 秒里纯积分漂了 &lt;strong&gt;9.28 m&lt;/strong&gt;，融合后压到 &lt;strong&gt;0.35 m&lt;/strong&gt;。这就是论文那句&quot;IMU 积分误差随时间累积&quot;的具体量级——&lt;strong&gt;它不是一个抽象的缺点，而是一个随时间平方增长的位置误差&lt;/strong&gt;，不融合根本没法用来定位。&lt;/p&gt;
&lt;p&gt;:::caution[零偏为什么只收敛到 0.032，而不是真值 0.050？]
这不是代码写错了，而是一个很本质的现象：&lt;strong&gt;在匀加速运动下，加速度计零偏和真实加速度是不可分辨的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0.05 的零偏 + 0.3 的真实加速度&lt;/code&gt; 和 &lt;code&gt;0 的零偏 + 0.35 的真实加速度&lt;/code&gt;，在 IMU 输出上完全一样。滤波器只能靠位置观测的&quot;不漂&quot;来反推零偏，所以收敛得慢，20 秒只能收敛到一半。&lt;/p&gt;
&lt;p&gt;要真正把零偏估准，得靠&lt;strong&gt;机动激励&lt;/strong&gt;——让飞行器做变速/转向动作，不同方向的加速度把零偏&quot;晒&quot;出来。这也是为什么很多飞控要求起飞前必须先做静止初始化。&lt;/p&gt;
&lt;p&gt;顺带一提，这个&quot;可观性&quot;问题在工程上还有个好用的判据：&lt;strong&gt;你在数据里看到的偏差，究竟该归给状态还是归给传感器误差，取决于运动有没有充分激励&lt;/strong&gt;。这一点在调参时特别容易被误判成&quot;这个传感器坏了&quot;。
:::&lt;/p&gt;
&lt;h2&gt;二、避障算法设计&lt;/h2&gt;
&lt;p&gt;避障算法的核心问题是：&lt;strong&gt;如何通过实时分析传感器数据，判断可能的障碍物并采取相应措施规避&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这需要一个复杂而高效的避障逻辑：它要&lt;strong&gt;综合考虑障碍物的位置、无人机的速度、姿态等多个因素&lt;/strong&gt;，以确保飞行的平稳性和安全性。论文用一组模拟数据来具体化这个过程。&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;block-diagram&quot; data-block=&quot;10-avoidance-flow&quot;&amp;gt;
&amp;lt;div class=&quot;block-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 762 234&quot; width=&quot;762&quot; height=&quot;234&quot; class=&quot;block-svg&quot; role=&quot;img&quot; aria-label=&quot;避障决策流程图：传感器数据计算避障判据（欧氏距离与碰撞时间），判断是否越过阈值，越界则减速并调整姿态，否则保持航线&quot;&amp;gt;
&amp;lt;g transform=&quot;translate(18,18)&quot;&amp;gt;
&amp;lt;g class=&quot;blk-links&quot;&amp;gt;
&amp;lt;line x1=&quot;129.5&quot; y1=&quot;74.1&quot; x2=&quot;178.3&quot; y2=&quot;74.1&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.45&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 183.5 74.1 L 178.3 71.4 L 178.3 76.8 Z&quot; fill=&quot;currentColor&quot;/&amp;gt;
&amp;lt;line x1=&quot;298.5&quot; y1=&quot;74.1&quot; x2=&quot;347.3&quot; y2=&quot;74.1&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.45&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 352.5 74.1 L 347.3 71.4 L 347.3 76.8 Z&quot; fill=&quot;currentColor&quot;/&amp;gt;
&amp;lt;path d=&quot;M 493.2 74.1 L 516.6 74.1 L 516.6 38.1 L 542.0 38.1&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.45&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 547.2 38.1 L 542.0 35.4 L 542.0 40.8 Z&quot; fill=&quot;currentColor&quot;/&amp;gt;
&amp;lt;text x=&quot;531.9&quot; y=&quot;31.1&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.66&quot;&amp;gt;是&amp;lt;/text&amp;gt;
&amp;lt;path d=&quot;M 493.2 74.1 L 537.3 74.1 L 537.3 118.2 L 565.8 118.2&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.45&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 571.0 118.2 L 565.8 115.5 L 565.8 120.9 Z&quot; fill=&quot;currentColor&quot;/&amp;gt;
&amp;lt;text x=&quot;554.1&quot; y=&quot;111.2&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.66&quot;&amp;gt;否&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-boxes&quot;&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;三路传感器数据&quot;&amp;gt;
&amp;lt;rect x=&quot;0.0&quot; y=&quot;44.1&quot; width=&quot;129.5&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.045&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.18&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;64.8&quot; y=&quot;65.7&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;三路传感器数据&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;64.8&quot; y=&quot;89.3&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;位置 · 速度 · 姿态&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;计算避障判据&quot;&amp;gt;
&amp;lt;rect x=&quot;183.5&quot; y=&quot;36.0&quot; width=&quot;115.0&quot; height=&quot;76.2&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.045&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.18&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;241.0&quot; y=&quot;57.6&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;计算避障判据&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;241.0&quot; y=&quot;81.2&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;欧氏距离 D&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;241.0&quot; y=&quot;97.5&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;碰撞时间 TTC&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;越过阈值？&quot;&amp;gt;
&amp;lt;rect x=&quot;352.5&quot; y=&quot;44.1&quot; width=&quot;140.7&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.14&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.75&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;422.9&quot; y=&quot;65.7&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;越过阈值？&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;422.9&quot; y=&quot;89.3&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;D &amp;lt; D阈值  或  TTC &amp;lt; τ&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;执行避障&quot;&amp;gt;
&amp;lt;rect x=&quot;547.2&quot; y=&quot;0.0&quot; width=&quot;179.1&quot; height=&quot;76.2&quot; rx=&quot;10&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.14&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.75&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;636.8&quot; y=&quot;21.6&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;执行避障&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;636.8&quot; y=&quot;45.2&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;减速 V新 = V原 − k(D − D阈值)&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;636.8&quot; y=&quot;61.5&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;转姿 θ新 = θ原 + α(D − D阈值)&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;保持航线&quot;&amp;gt;
&amp;lt;rect x=&quot;571.0&quot; y=&quot;88.2&quot; width=&quot;131.5&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.03&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.28&quot; stroke-width=&quot;1.2&quot; stroke-dasharray=&quot;4 3&quot;/&amp;gt;
&amp;lt;text x=&quot;636.8&quot; y=&quot;109.8&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;保持航线&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;636.8&quot; y=&quot;133.4&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;继续跟踪原参考路径&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-feedback&quot;&amp;gt;
&amp;lt;path d=&quot;M 636.8 76.2 L 636.8 176.2 Q 636.8 182.2 630.8 182.2 L 70.8 182.2 Q 64.8 182.2 64.8 176.2 L 64.8 109.2&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.6&quot; stroke-width=&quot;1.3&quot; stroke-dasharray=&quot;5 3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 64.8 104.0 L 62.0 109.2 L 67.5 109.2 Z&quot; fill=&quot;#8b5cf6&quot;/&amp;gt;
&amp;lt;text x=&quot;350.8&quot; y=&quot;175.2&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.66&quot;&amp;gt;下一控制周期&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;figcaption&amp;gt;图 2 · 避障决策流程：论文的三条公式其实就是把「会不会撞」翻译成「减速多少、偏多少」&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;表 1 传感器测得的障碍物位置和无人机状态&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;时间（秒）&lt;/th&gt;
&lt;th&gt;障碍物 X 坐标（米）&lt;/th&gt;
&lt;th&gt;障碍物 Y 坐标（米）&lt;/th&gt;
&lt;th&gt;无人机速度（米/秒）&lt;/th&gt;
&lt;th&gt;无人机姿态角度（弧度）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;0.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;1.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;1.8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;2.1 论文的三条公式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;公式 1：避障距离计算。&lt;strong&gt;先定义&lt;/strong&gt;避障距离阈值&lt;/strong&gt;（本研究中设为 &lt;strong&gt;5 米&lt;/strong&gt;）：当无人机与障碍物的距离小于该阈值时，就需要执行避障动作。无人机与障碍物之间的&lt;strong&gt;欧几里得距离&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$D_{\text{避障}} = \sqrt{(X_{\text{障碍}} - X_{\text{无人机}})^2 + (Y_{\text{障碍}} - Y_{\text{无人机}})^2}$$&lt;/p&gt;
&lt;p&gt;若 $D_{\text{避障}}$ 小于避障距离阈值，即表示需要启动避障。&lt;/p&gt;
&lt;p&gt;**公式 2：速度调整。**依据距离与速度的关系调整无人机速度：&lt;/p&gt;
&lt;p&gt;$$V_{\text{新}} = V_{\text{原}} - k \cdot (D_{\text{避障}} - D_{\text{阈值}})$$&lt;/p&gt;
&lt;p&gt;其中 $V_{\text{新}}$ 是调整后的速度，$V_{\text{原}}$ 是原始速度，$k$ 是调整系数。&lt;/p&gt;
&lt;p&gt;**公式 3：姿态调整。**通过姿态调整确保无人机在避障过程中保持平稳：&lt;/p&gt;
&lt;p&gt;$$\theta_{\text{新}} = \theta_{\text{原}} + \alpha \cdot (D_{\text{避障}} - D_{\text{阈值}})$$&lt;/p&gt;
&lt;p&gt;其中 $\theta_{\text{新}}$ 是调整后的姿态角度，$\theta_{\text{原}}$ 是原始姿态角度，$\alpha$ 是调整角度的系数。&lt;/p&gt;
&lt;p&gt;:::note[这三个公式的关系]
三个公式构成一套&quot;&lt;strong&gt;感知 → 减速 → 转姿&lt;/strong&gt;&quot;的串联逻辑：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;公式 1 判断&lt;strong&gt;会不会撞&lt;/strong&gt;（距离是否越界）&lt;/li&gt;
&lt;li&gt;公式 2 决定&lt;strong&gt;要不要慢下来&lt;/strong&gt;（距离越近、减得越多）&lt;/li&gt;
&lt;li&gt;公式 3 决定&lt;strong&gt;往哪边偏&lt;/strong&gt;（同时调整姿态保持平稳）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;它们通过&lt;strong&gt;实时计算距离、调整速度和姿态&lt;/strong&gt;，让无人机在遇到障碍物时能做出及时、平稳的规避动作。论文也指出，这个算法的复杂性正体现在&quot;综合考虑多个因素&quot;上——通过实际数据与公式的结合，才能更好地理解和评估无人机在复杂环境中的避障能力。
:::&lt;/p&gt;
&lt;h3&gt;2.2 延伸：这三条公式其实就是「一个比例控制器」&lt;/h3&gt;
&lt;p&gt;把公式 2 和公式 3 抄近一点看：&lt;/p&gt;
&lt;p&gt;$$V_{\text{新}} = V_{\text{原}} - k \cdot \underbrace{(D_{\text{避障}} - D_{\text{阈值}})}_{\text{误差 } e}$$&lt;/p&gt;
&lt;p&gt;如果把 $(D_{\text{避障}} - D_{\text{阈值}})$ 看成&lt;strong&gt;误差&lt;/strong&gt; $e$，那么这两条式子就是标准的&lt;strong&gt;比例控制&lt;/strong&gt;：输出 = 系数 × 误差。$k$ 和 $\alpha$ 就是比例增益。&lt;/p&gt;
&lt;p&gt;这个等价关系很有用，因为它一次性带来了三个已知结论：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;比例控制的已知性质&lt;/th&gt;
&lt;th&gt;在这套避障公式上的表现&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;增益太小收敛慢&lt;/td&gt;
&lt;td&gt;$k$ 取小了，飞机会&quot;慢悠悠地蹭&quot;过去，避障动作不果断&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;增益太大会振荡&lt;/td&gt;
&lt;td&gt;$k$ 取大了，速度在阈值附近反复横跳，姿态也跟着抖&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;纯比例一定存在稳态误差&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;距离恰好压在阈值时输出为零，&lt;strong&gt;它不会主动把距离拉回到一个安全余量&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;最后一条是这个方案最本质的局限：&lt;strong&gt;纯比例控制只能&quot;按误差线性回应&quot;，做不到&quot;提前留出安全余量&quot;&lt;/strong&gt;。而避障恰恰是个需要余量的场景。&lt;/p&gt;
&lt;p&gt;（先别往下看，自己推一下：当 $D_{\text{避障}}$ 恰好等于阈值时，公式 2 和公式 3 给出的调整量是多少？:spoiler[两个式子里的 $(D_{\text{避障}} - D_{\text{阈值}})$ 都等于 0，所以速度不变、姿态不变——飞行器就停在&quot;刚好要撞&quot;的边界上，不做任何反应。这就是&quot;纯比例不给余量&quot;的数学根源：&lt;strong&gt;误差为零时控制量为零&lt;/strong&gt;，于是它天然缺少&quot;离远一点&quot;的驱动力。]）&lt;/p&gt;
&lt;p&gt;:::warning[三个公式里没出现&quot;速度方向&quot;]
公式 1 用的是&lt;strong&gt;欧氏距离&lt;/strong&gt;，是个标量，它不含方向信息。&lt;/p&gt;
&lt;p&gt;这意味着一个很实际的漏洞：&lt;strong&gt;无人机以 10 m/s 迎面冲向障碍物&lt;/strong&gt;，和&lt;strong&gt;背对着障碍物以 10 m/s 远离&lt;/strong&gt;，只要距离相同，算出来的 $D_{\text{避障}}$ 完全一样，公式 2、3 给出的调整量也完全相同。&lt;/p&gt;
&lt;p&gt;换句话说，这套判据&lt;strong&gt;分不清&quot;正在靠近&quot;和&quot;正在远离&quot;&lt;/strong&gt;。要补上这个信息，就得引入下面这个量。
:::&lt;/p&gt;
&lt;h3&gt;2.3 延伸：更常用的判据——碰撞时间 TTC&lt;/h3&gt;
&lt;p&gt;真正决定&quot;危不危险&quot;的不是&lt;strong&gt;距离&lt;/strong&gt;，而是&lt;strong&gt;距离 ÷ 接近速度&lt;/strong&gt;，也就是&lt;strong&gt;碰撞时间&lt;/strong&gt;（Time To Collision, TTC）：&lt;/p&gt;
&lt;p&gt;$$\text{TTC} = \frac{D_{\text{避障}}}{\max\left(-\dfrac{dD}{dt},\ \varepsilon\right)}$$&lt;/p&gt;
&lt;p&gt;分母是接近速率（距离的负导数），加 $\varepsilon$ 只是防止除零。TTC 的物理含义很直白：&lt;strong&gt;按当前速度不变，还有多少秒会撞上&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;它比纯距离判据好在三点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;含了速度信息&lt;/strong&gt;：正以 10 m/s 迎面冲来（TTC = 1 s）和静止悬停（TTC → ∞）被明确区分开&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;量纲统一&lt;/strong&gt;：单位是秒，阈值可以直接按&quot;人的反应时间/飞控的刹车距离&quot;来定，容易理解也容易调&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;天生适合多机与动态障碍&lt;/strong&gt;：对方也在动时，距离判据会失效，TTC 仍然成立&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不同判据的对照：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;判据&lt;/th&gt;
&lt;th&gt;核心式子&lt;/th&gt;
&lt;th&gt;优点&lt;/th&gt;
&lt;th&gt;局限&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;欧氏距离&lt;/strong&gt;（论文所用）&lt;/td&gt;
&lt;td&gt;$\sqrt{\Delta x^2 + \Delta y^2}$&lt;/td&gt;
&lt;td&gt;最好算，无参数&lt;/td&gt;
&lt;td&gt;不含速度与方向，分不清靠近/远离&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;碰撞时间 TTC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$D / \dot{D}$&lt;/td&gt;
&lt;td&gt;含接近速度，量纲直观&lt;/td&gt;
&lt;td&gt;平行飞行时 $\dot{D} \to 0$，TTC 爆表需特殊处理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;人工势场法&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;引力 + 斥力叠加&lt;/td&gt;
&lt;td&gt;输出是连续的力/加速度，可直接接控制器&lt;/td&gt;
&lt;td&gt;存在局部极值，目标在凹形障碍后可能卡住&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;碰撞锥 / 速度障碍&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;相对速度是否落在锥内&lt;/td&gt;
&lt;td&gt;显式给出&quot;哪些速度安全&quot;，适合动态环境&lt;/td&gt;
&lt;td&gt;计算量较大，多障碍时要逐个求交&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;下面这段就是把「距离判据」升级成「TTC 判据」的改动思路，用 diff 的写法看更清楚：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  def should_avoid(D, closing_speed, D_threshold=5.0, tau=2.0):
-     # 只比距离：分不清正在靠近还是正在远离
-     return D &amp;lt; D_threshold
+     # 先看距离有没有进到警戒圈
+     if D &amp;gt;= D_threshold:
+         return False
+     # 再看&quot;还有几秒会撞上&quot;：速度方向不同，结论就不同
+     if closing_speed &amp;lt;= 1e-3:      # 没在接近，其实不用避
+         return False
+     return D / closing_speed &amp;lt; tau
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip[为什么&quot;阈值 5 米&quot;这种写法在工程上很难落地]
论文把避障阈值定为 5 米。但如果飞机速度从 5 m/s 提到 15 m/s，&lt;strong&gt;5 米的预警距离就只有 0.33 秒&lt;/strong&gt;——扣掉传感器延迟、算法耗时和执行器响应，实际已经来不及了。&lt;/p&gt;
&lt;p&gt;所以工程上更稳的做法是把阈值写成&lt;strong&gt;速度的函数&lt;/strong&gt;（或直接用 TTC）：&lt;code&gt;D_threshold = 刹车距离 + 安全余量&lt;/code&gt;，其中刹车距离随速度平方增长。这样阈值会自己跟着速度调整，高速时自动变宽。
:::&lt;/p&gt;
&lt;h3&gt;2.4 延伸：从「障碍物坐标列表」到「体素占据栅格」&lt;/h3&gt;
&lt;p&gt;论文的表 1 是一种很&quot;教科书&quot;的表示：把障碍物存成&lt;strong&gt;坐标点&lt;/strong&gt;。这在仿真里很方便，但真机上有两个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;激光雷达一秒回来的是&lt;strong&gt;几十万个点&lt;/strong&gt;，逐个当障碍物去算距离根本算不过来&lt;/li&gt;
&lt;li&gt;障碍物不是点，是&lt;strong&gt;有体积的形状&lt;/strong&gt;，用点表示会丢掉&quot;这个方向有多宽&quot;的信息&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;现实中的做法是先把空间&lt;strong&gt;离散成一个三维格网&lt;/strong&gt;，每个格子标记&quot;有没有被占据&quot;，这就是&lt;strong&gt;体素占据栅格&lt;/strong&gt;（voxel occupancy grid）：&lt;/p&gt;
&lt;p&gt;$$M(i,j,k) = \begin{cases} 1, &amp;amp; \text{该体素被占据} \ 0, &amp;amp; \text{该体素空闲} \end{cases}$$&lt;/p&gt;
&lt;p&gt;点云落进哪个格子，就把哪个格子置 1（实际实现里还会用概率滤波处理噪点，避免一个飞点就在地图上戳出一堵墙）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./08-voxel-grid-path.jpg&quot; alt=&quot;体素占据栅格地图：颜色按高度映射（暖色接近天花板、冷色接近地面），白线为规划出的避障轨迹。左图是走廊视角，右图是同一张地图里障碍物密集处的特写&quot; /&gt;&lt;/p&gt;
&lt;p&gt;上图是这类地图的一个典型样子：&lt;strong&gt;颜色不是装饰，而是高度的映射&lt;/strong&gt;（红=高、蓝=低），这样一眼就能看出三维结构；白线是规划器在栅格地图里搜出来的轨迹。可以看到它绕着障碍的凸起走，而不是贴着走。&lt;/p&gt;
&lt;p&gt;**但这张图里最关键的其实是没画出来的那一步。**栅格只回答了&quot;这个格子能不能走&quot;（是离散的是/否），而规划器和控制器需要的是&quot;&lt;strong&gt;离最近的障碍还有多远&lt;/strong&gt;&quot;（是连续的数值）。把前者变成后者，靠的是 &lt;strong&gt;ESDF（欧氏符号距离场）&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$\text{ESDF}(v) = \begin{cases} +\min_{u \in \text{障碍}} \lVert v - u \rVert, &amp;amp; v \text{ 在空闲区} \[4pt] -\min_{u \in \partial \text{障碍}} \lVert v - u \rVert, &amp;amp; v \text{ 在障碍内} \end{cases}$$&lt;/p&gt;
&lt;p&gt;也就是&lt;strong&gt;给每个体素存一个数：它到最近障碍的距离&lt;/strong&gt;。符号表示在障碍外还是里（负号就是在障碍内部）。&lt;/p&gt;
&lt;p&gt;这个数据结构一旦建好，避障就变得非常&quot;便宜&quot;——因为它同时提供了两个东西：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ESDF 提供&lt;/th&gt;
&lt;th&gt;用来干什么&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;距离值&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;直接当安全裕量用：&lt;code&gt;ESDF(v) &amp;gt; 安全半径&lt;/code&gt; 就是安全的&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;梯度 $\nabla \text{ESDF}$&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;直接当&lt;strong&gt;远离障碍的方向&lt;/strong&gt;用，而且指向的是&quot;最短逃逸方向&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;梯度这一条特别关键：&lt;strong&gt;它把&quot;往哪边躲最好&quot;这个决策，变成了一个查表加求导的操作&lt;/strong&gt;。前一节的受力方向、后一节的轨迹优化，用的都是这个梯度。业界成熟的实现里，Voxblox 用 TSDF 增量构建 ESDF，EGO-Planner 则直接在 ESDF 上做梯度下降优化轨迹。&lt;/p&gt;
&lt;h3&gt;2.5 延伸：一段能跑的「栅格 → ESDF → 势场避障」&lt;/h3&gt;
&lt;p&gt;把 2.3 节的势场法和 2.4 节的 ESDF 接起来，就是下面这段。它把地图离散成 0.2 m 的栅格，用 &lt;code&gt;scipy&lt;/code&gt; 一次性算出 ESDF，再让一个质点顺着&quot;引力 + ESDF 梯度斥力&quot;飞过去：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 依赖：Python 3.10+，numpy、scipy
# 演示：体素占据栅格 -&amp;gt; ESDF -&amp;gt; 用势场法绕障
import numpy as np
from scipy.ndimage import distance_transform_edt

RES = 0.2                      # 每格 0.2 m
H = W = 120                    # 24 m x 24 m 的局部地图
maps = np.zeros((H, W), dtype=bool)          # True = 被占据

# 两堵墙 + 一根柱子（真实环境里这些格由点云投影得到）
maps[30:34, 15:100] = True
maps[72:76, 20:105] = True
maps[50:62, 58:66] = True

# ---- ESDF：每个空闲格到最近障碍的距离（米）----
# distance_transform_edt 给出「到最近 True 的欧氏距离」，单位是格，乘 RES 换成米
esdf = distance_transform_edt(~maps) * RES


def esdf_grad(esdf, pos):
    &quot;&quot;&quot;用双线性插值取 ESDF 值，并用中心差分取梯度（指向远离障碍的方向）。&quot;&quot;&quot;
    x, y = pos / RES
    x0, y0 = int(x), int(y)
    tx, ty = x - x0, y - y0

    def sample(ix, iy):
        ix = np.clip(ix, 0, W - 1); iy = np.clip(iy, 0, H - 1)
        return esdf[iy, ix]

    val = ((1 - tx) * (1 - ty) * sample(x0, y0) + tx * (1 - ty) * sample(x0 + 1, y0)
           + (1 - tx) * ty * sample(x0, y0 + 1) + tx * ty * sample(x0 + 1, y0 + 1))

    gx = (sample(x0 + 1, y0) - sample(x0 - 1, y0)) / (2 * RES)
    gy = (sample(x0, y0 + 1) - sample(x0, y0 - 1)) / (2 * RES)
    return val, np.array([gx, gy])


# ---- 势场法：引力拉向目标 + 斥力推开障碍 ----
def attractive(pos, goal, k=3.0):
    return k * (goal - pos)

def repulsive(pos, esdf, d0=2.0, k=12.0):
    d, grad = esdf_grad(esdf, pos)
    if d &amp;gt;= d0 or d &amp;lt; 1e-6:
        return np.zeros(2)
    return k * (1.0 / d - 1.0 / d0) / d ** 2 * grad     # 越近推得越猛


start = np.array([1.0, 0.5])
goal  = np.array([22.0, 22.0])
pos, vel = start.copy(), np.zeros(2)
DT, VMAX = 0.02, 3.0

min_clear, path_len, steps = np.inf, 0.0, 0

for _ in range(12000):
    f = attractive(pos, goal) + repulsive(pos, esdf)
    vel = np.clip(0.9 * vel + f * DT, -VMAX, VMAX)
    pos = pos + vel * DT
    steps += 1

    clear, _g = esdf_grad(esdf, pos)
    min_clear = min(min_clear, clear)
    path_len += np.linalg.norm(vel) * DT

    if np.linalg.norm(pos - goal) &amp;lt; 0.3:
        break
    if clear &amp;lt;= 0.0:                       # 撞进障碍格
        print(f&quot;❌ 在第 {steps} 步撞上障碍（位置 {pos.round(2)}）&quot;)
        break

reached = np.linalg.norm(pos - goal) &amp;lt; 0.3
print(f&quot;到达目标：{reached}   用时 {steps * DT:.1f} s   路径长度 {path_len:.1f} m&quot;)
print(f&quot;全程离障碍最近：{min_clear:.2f} m   终点 {pos.round(2)}（目标 {goal}）&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实测输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;到达目标：True   用时 14.8 s   路径长度 38.1 m
全程离障碍最近：0.45 m   终点 [22.01 21.7 ]（目标 [22. 22.])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::important[这段输出里藏着势场法的两个老毛病]
能到目标，但请对照两个数：&lt;/p&gt;
&lt;p&gt;**① 路径明显绕远。**起点 (1, 0.5) 到终点 (22, 22) 的直线距离约 &lt;strong&gt;29.9 m&lt;/strong&gt;，实际飞了 &lt;strong&gt;38.1 m&lt;/strong&gt;，多出 28%。因为这股力是&quot;局部&quot;的——只在看得见的斥力范围内起作用，&lt;strong&gt;没有全局最优的概念&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;**② 全程离障碍最近只有 0.45 m。**它确实没撞上，但是&quot;贴着边蹭过去&quot;的。势场法的斥力与距离平方成反比，&lt;strong&gt;在接近障碍时增长得很快，可一旦越过了&quot;最危险的那一点&quot;，斥力就骤减&lt;/strong&gt;，于是轨迹表现为贴着障碍画弧。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;还有第三个更致命的问题，这段代码恰好避开了&lt;/strong&gt;：势场法存在&lt;strong&gt;局部极值&lt;/strong&gt;——如果目标正好落在一个凹形（比如 U 形）障碍的开口里，引力和斥力会在某个位置正好抵消，飞行器就停在那里，既到不了目标也不撞上去。这是纯势场法无法根治的缺陷，工程上要么改用&lt;strong&gt;基于搜索的全局规划 + 局部优化&lt;/strong&gt;，要么加随机扰动跳出极值。
:::&lt;/p&gt;
&lt;p&gt;:::caution[本节的诚实说明]
论文并没有用势场法，也没有用体素栅格和 ESDF——它用的是&lt;strong&gt;障碍物坐标 + 欧氏距离阈值&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;上面这一节（以及下面第三、四节的延伸部分）是我在写这篇文章时补的：&lt;strong&gt;用来说明&quot;论文的思路接到真实工程上会长成什么样&quot;&lt;/strong&gt;。这两者我刻意分开标注，请不要把这几段当成论文的内容。
:::&lt;/p&gt;
&lt;h2&gt;三、导航精度与控制稳定性优化&lt;/h2&gt;
&lt;p&gt;为了让无人机在复杂环境中更灵活、精准地导航，研究集中在&lt;strong&gt;数据处理&lt;/strong&gt;与&lt;strong&gt;算法优化&lt;/strong&gt;两方面。&lt;/p&gt;
&lt;h3&gt;3.1 数据处理&lt;/h3&gt;
&lt;p&gt;论文关注传感器数据的&lt;strong&gt;准确性和实时性&lt;/strong&gt;，使用 GPS、陀螺仪、加速度计等获取关键飞行数据，实时监测无人机的姿态、速度和加速度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;表 2 传感器类型与准确性要求&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;传感器&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;准确性要求&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;全球定位系统&lt;/td&gt;
&lt;td&gt;定位&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;陀螺仪&lt;/td&gt;
&lt;td&gt;姿态&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;加速度计&lt;/td&gt;
&lt;td&gt;加速度&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;在数据采集过程中优化传感器融合算法，确保各传感器数据的&lt;strong&gt;协同工作&lt;/strong&gt;，提升整体数据准确性：&lt;/p&gt;
&lt;p&gt;$$D_{\text{处理后}} = F_{\text{fusion}}\left(D_{\text{GPS}},\ D_{\text{陀螺仪}},\ D_{\text{加速度}}\right)$$&lt;/p&gt;
&lt;p&gt;其中 $D_{\text{处理后}}$ 是经过处理后的数据，$F_{\text{fusion}}$ 是传感器融合算法，另外三项分别表示 GPS、陀螺仪和加速度计采集的原始数据。&lt;/p&gt;
&lt;p&gt;:::note[公式里的 $F_{\text{fusion}}$ 是什么？]
这个式子是个&lt;strong&gt;占位符&lt;/strong&gt;——它诚实地表示了&quot;输出 = 融合函数（输入…）&quot;这个结构，但没有说明 $F_{\text{fusion}}$ 具体是什么形式。&lt;/p&gt;
&lt;p&gt;本文第一节的 EKF 就是 $F_{\text{fusion}}$ 的一个具体实现：**它同样接受 GPS、陀螺仪、加速度计三路输入，输出一个状态。**区别只是它还会多输出一份协方差（不确定性），并且是递归的（上一时刻的估计会参与下一时刻的计算）。&lt;/p&gt;
&lt;p&gt;顺带说，$F_{\text{fusion}}$ 前面的&quot;数据处理&quot;其实还有一段很脏但绕不开的活：&lt;strong&gt;去畸变、时间戳对齐、坏点剔除、滑窗平滑&lt;/strong&gt;。论文用一句&quot;提升整体数据准确性&quot;概括了，但真机上这几步的工作量往往比融合本身还大——&lt;strong&gt;传感器给的数据永远没有说明书上那么干净&lt;/strong&gt;。
:::&lt;/p&gt;
&lt;h3&gt;3.2 算法优化：姿态控制用 MPC&lt;/h3&gt;
&lt;p&gt;算法优化聚焦&lt;strong&gt;姿态控制&lt;/strong&gt;与&lt;strong&gt;航迹规划&lt;/strong&gt;两个环节。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;姿态控制采用模型预测控制（Model Predictive Control, MPC）&lt;/strong&gt;——该算法通过&lt;strong&gt;预测未来一段时间内的飞行状态&lt;/strong&gt;来优化控制指令，从而实现对无人机的高效控制：&lt;/p&gt;
&lt;p&gt;$$U^{*} = \arg\min \sum_{k=0}^{N-1} L(x_k, u_k) + V(x_N)$$&lt;/p&gt;
&lt;p&gt;其中 $U^{*}$ 是最优控制输入序列，$x$ 是飞行状态，$u$ 是控制指令，$N$ 是预测时域，$L$ 和 $V$ 分别是每一步的代价函数和终端代价函数。&lt;/p&gt;
&lt;p&gt;:::tip[MPC 为什么适合无人机]
MPC 的核心思想是&quot;&lt;strong&gt;滚动优化&lt;/strong&gt;&quot;：每一时刻都根据当前状态预测未来 $N$ 步，解出一个最优控制序列，但&lt;strong&gt;只执行第一步&lt;/strong&gt;，下一时刻重新预测、重新求解。&lt;/p&gt;
&lt;p&gt;这对无人机特别契合——因为无人机的动力学约束（最大推力、最大角速度）可以&lt;strong&gt;显式写进优化问题&lt;/strong&gt;，而不像 PID 那样只能靠调参事后弥补。代价是每一控制周期都要解一次优化问题，对算力有要求。
:::&lt;/p&gt;
&lt;h3&gt;3.3 延伸：把 MPC 写成二次规划&lt;/h3&gt;
&lt;p&gt;论文给出的 $\arg\min \sum L + V$ 是 MPC 的&lt;strong&gt;通用形式&lt;/strong&gt;，但没说明怎么解。好消息是：只要系统是&lt;strong&gt;线性&lt;/strong&gt;的、代价是&lt;strong&gt;二次&lt;/strong&gt;的、约束是&lt;strong&gt;线性&lt;/strong&gt;的，这个问题就能写成一个&lt;strong&gt;二次规划（QP）&lt;/strong&gt;——而 QP 有成熟、快、且能保证找到全局最优的求解器。&lt;/p&gt;
&lt;p&gt;具体怎么落地，步骤是固定的三步：&lt;/p&gt;
&lt;p&gt;**第一步：把预测展开成矩阵形式。**设状态 $x_k \in \mathbb{R}^{n}$、控制量 $u_k \in \mathbb{R}^{m}$、离散动力学 $x_{k+1} = A x_k + B u_k$，把 $N$ 步全部叠成一个大向量 $X = [x_1; \dots; x_N]$、$U = [u_0; \dots; u_{N-1}]$，那么：&lt;/p&gt;
&lt;p&gt;$$X = \underbrace{\begin{bmatrix} A \ A^2 \ \vdots \ A^N \end{bmatrix}}&lt;em&gt;{\bar{A}} x_0 + \underbrace{\begin{bmatrix} B &amp;amp; 0 &amp;amp; \cdots &amp;amp; 0 \ AB &amp;amp; B &amp;amp; \cdots &amp;amp; 0 \ \vdots &amp;amp; &amp;amp; \ddots &amp;amp; \vdots \ A^{N-1}B &amp;amp; A^{N-2}B &amp;amp; \cdots &amp;amp; B \end{bmatrix}}&lt;/em&gt;{\bar{B}} U$$&lt;/p&gt;
&lt;p&gt;**第二步：把代价函数代进去，得到标准二次型。**把 $X$ 的表达式代入二次代价，$x_0$ 就退化成一个常数项，剩下的只有 $U$：&lt;/p&gt;
&lt;p&gt;$$J(U) = \tfrac{1}{2} U^{\top} H U + f^{\top} U + \text{const}, \qquad H = 2\left(\bar{B}^{\top}\bar{Q}\bar{B} + \bar{R}\right), \quad f = 2\bar{B}^{\top}\bar{Q}\left(\bar{A}x_0 - X_{\text{ref}}\right)$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三步：把约束写成线性不等式。&lt;strong&gt;这一步是重点，因为&lt;/strong&gt;&quot;避障&quot;就是从这里进场的&lt;/strong&gt;——不管是安全走廊还是位置上下界，最终都变成同一种形式：&lt;/p&gt;
&lt;p&gt;$$H_j, p_k \le h_j$$&lt;/p&gt;
&lt;p&gt;其中 $p_k$ 是第 $k$ 步预测位置，$(H_j, h_j)$ 描述一个半空间。一串这样的半空间求交，就是一个&lt;strong&gt;凸多面体&lt;/strong&gt;，也就是所谓的&lt;strong&gt;安全飞行走廊（Safe Flight Corridor, SFC）&lt;/strong&gt;。它的妙处在于：&lt;strong&gt;把&quot;别撞墙&quot;这个几何问题，翻译成了一条线性不等式&lt;/strong&gt;——而线性不等式恰好是 QP 最擅长处理的东西。&lt;/p&gt;
&lt;p&gt;:::warning[一个必须注意的坑：ESDF 约束是「非线性」的]
上一节说 ESDF 提供了距离和梯度，看起来很完美。但如果直接把 &lt;code&gt;ESDF(p) ≥ 安全半径&lt;/code&gt; 当作约束写进 QP，&lt;strong&gt;这个 QP 就不再是标准的二次规划了&lt;/strong&gt;——因为 ESDF 是格网上查表插值出来的，对 $p$ 而言是&lt;strong&gt;非线性、非凸&lt;/strong&gt;的函数。&lt;/p&gt;
&lt;p&gt;工程上有两条主流绕法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;先近似成凸多面体&lt;/strong&gt;：用 A* 搜出一条几何路径，在路径周围构造一串&lt;strong&gt;互相重叠的凸多面体&lt;/strong&gt;（就是 SFC），把非线性约束换成线性约束，QP 直接能解。代价是走廊构造这一步本身有开销，而且走廊一旦生成，优化器就只能在这个&quot;笼子&quot;里找解。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;保留非线性、用 SQP / 实时迭代（RTI）线性化&lt;/strong&gt;：在预测点处对 ESDF 做一阶展开，用线性化后的约束迭代求解。精度更高，但对初值敏感，也更难调试。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;实际系统里方案 1 是主流，因为它把&quot;安全性&quot;的证明变简单了：&lt;strong&gt;只要轨迹在走廊内、走廊内没有障碍，那轨迹就一定安全。&lt;/strong&gt;
:::&lt;/p&gt;
&lt;h3&gt;3.4 延伸：一段能跑的 MPC + 安全走廊&lt;/h3&gt;
&lt;p&gt;下面这段代码用一个横向双积分模型（状态 = 横向位置与速度）演示方案 1 的思路：把&quot;每一步预测位置都在走廊内&quot;写成两条线性不等式，然后用 &lt;code&gt;scipy&lt;/code&gt; 解 QP。&lt;/p&gt;
&lt;p&gt;场景设定是&lt;strong&gt;恒定侧风&lt;/strong&gt;——一个没写进模型的外部扰动。它很有代表性，因为它刚好能看出&lt;strong&gt;代价函数和硬约束的分工&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 依赖：Python 3.10+，numpy、scipy
# 演示：线性 MPC 写成 QP，用「安全走廊」线性不等式把侧风顶回去
import numpy as np
from scipy.optimize import minimize

DT, N = 0.1, 25                      # 控制周期 100 ms，预测 25 步（2.5 s）
A = np.array([[1.0, DT], [0.0, 1.0]])        # 状态 = [横向位置 y, 横向速度 vy]
B = np.array([[0.5 * DT ** 2], [DT]])        # 控制量 = 横向加速度 ay

Q  = np.diag([2.0, 8.0])             # 跟踪代价（中间步）
QN = np.diag([4.0, 12.0])            # 终端代价（最后一步权重大）
R  = np.array([[1.0]])               # 控制量代价，抑制抖动

A_MAX = 2.0                          # 最大横向加速度 m/s^2（执行器权限）
Y_MAX = 0.6                          # 安全走廊半宽 m —— 就是 H p &amp;lt;= h 的最简形式
WIND  = 0.8                          # 未建模侧风 m/s^2


def build_prediction(N, A, B):
    &quot;&quot;&quot;把 x_{k+1} = A x_k + B u_k 展开成 X = Abar x0 + Bbar U（X 含 x1..xN）。&quot;&quot;&quot;
    nx, nu = A.shape[0], B.shape[1]
    Abar = np.zeros((N * nx, nx))
    Bbar = np.zeros((N * nx, N * nu))
    for i in range(N):
        Abar[i * nx:(i + 1) * nx, :] = np.linalg.matrix_power(A, i + 1)
        for j in range(i + 1):
            Bbar[i * nx:(i + 1) * nx, j * nu:(j + 1) * nu] = \
                np.linalg.matrix_power(A, i - j) @ B
    return Abar, Bbar


ABAR, BBAR = build_prediction(N, A, B)
QBAR = np.kron(np.eye(N), Q)
QBAR[-2:, -2:] = QN                              # 最后一步换成终端代价
RBAR = np.kron(np.eye(N), R)

# 把代价展开成标准二次型：J(U) = 0.5 U&apos;H U + f&apos;U + const
H_MAT = BBAR.T @ QBAR @ BBAR + RBAR
POS_ROWS = np.arange(0, N * 2, 2)                # X 里每个位置分量的行号


def mpc(x0, x_ref=np.zeros(2), y_max=Y_MAX):
    &quot;&quot;&quot;解一步 QP：min J(U) s.t. |u_k| &amp;lt;= A_MAX, |y_k| &amp;lt;= y_max。只返回第一步。&quot;&quot;&quot;
    xr = np.tile(x_ref, N)
    f = 2 * BBAR.T @ QBAR @ (ABAR @ x0 - xr)
    obj = lambda U: U @ H_MAT @ U + f @ U
    jac = lambda U: 2 * H_MAT @ U + f

    # 安全走廊：把「每一步预测位置都在带内」写成两条线性不等式
    cons = [
        {&quot;type&quot;: &quot;ineq&quot;, &quot;fun&quot;: lambda U: y_max - (ABAR @ x0 + BBAR @ U)[POS_ROWS],
         &quot;jac&quot;: lambda U: -BBAR[POS_ROWS, :]},
        {&quot;type&quot;: &quot;ineq&quot;, &quot;fun&quot;: lambda U: y_max + (ABAR @ x0 + BBAR @ U)[POS_ROWS],
         &quot;jac&quot;: lambda U: BBAR[POS_ROWS, :]},
    ]

    res = minimize(obj, np.zeros(N), jac=jac, bounds=[(-A_MAX, A_MAX)] * N,
                   constraints=cons, method=&quot;SLSQP&quot;,
                   options={&quot;maxiter&quot;: 300, &quot;ftol&quot;: 1e-10})
    return res.x[0]


def simulate(y_max):
    &quot;&quot;&quot;闭环跑 12 s；参考一直是 y=0，但物理对象上一直挂着恒定侧风。&quot;&quot;&quot;
    x = np.array([0.0, 0.0])
    ys, us = [], []
    for _ in range(120):
        u = mpc(x, y_max=y_max)
        # 注意 B 是列向量：用 flatten 让状态保持一维，否则下一步广播不上
        x = A @ x + B.flatten() * u + np.array([0.5 * WIND * DT ** 2, WIND * DT])
        ys.append(x[0]); us.append(u)
    return np.array(ys), np.array(us)


y_free, _ = simulate(y_max=1e6)                   # 对照：不加走廊约束
y_corr, u_corr = simulate(y_max=Y_MAX)            # 加走廊约束

print(f&quot;参考横向位置 0.00 m，恒定侧风 {WIND} m/s^2&quot;)
print(f&quot;  不加走廊约束：稳态偏移 {y_free[-1]:+.3f} m  &amp;lt;- 靠代价函数是拉不回来的&quot;)
print(f&quot;  加安全走廊  ：稳态偏移 {y_corr[-1]:+.3f} m  &amp;lt;- 顶在 {Y_MAX} m 边界上&quot;)
overshoot = np.abs(y_corr).max() - Y_MAX
print(f&quot;  全程最大偏移 {np.abs(y_corr).max():.3f} m，走廊半宽 {Y_MAX:.3f} m&quot;
      f&quot;（超出 {overshoot * 1000:+.0f} mm，属于 QP 求解器的数值容差）&quot;)
print(f&quot;  控制量范围 {u_corr.min():+.2f} ~ {u_corr.max():+.2f} m/s^2&quot;
      f&quot;（执行器上限 ±{A_MAX}）&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实测输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;参考横向位置 0.00 m，恒定侧风 0.8 m/s^2
  不加走廊约束：稳态偏移 +0.811 m  &amp;lt;- 靠代价函数是拉不回来的
  加安全走廊  ：稳态偏移 +0.604 m  &amp;lt;- 顶在 0.6 m 边界上
  全程最大偏移 0.604 m，走廊半宽 0.600 m（超出 +4 mm，属于 QP 求解器的数值容差）
  控制量范围 -1.79 ~ +0.00 m/s^2（执行器上限 ±2.0）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::important[这段输出回答了「为什么光有代价函数不够」]
注意第一行和第二行的对比：&lt;strong&gt;同一套代价权重，加不加走廊约束，稳态偏移差了 0.2 m。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不加走廊&lt;/strong&gt;：代价函数只是&quot;倾向于&quot;回到 $y=0$。侧风一直在推，代价函数又没有&quot;绝对不许越界&quot;这个语义，于是它稳定在一个&lt;strong&gt;新的平衡点 0.811 m&lt;/strong&gt; 上——&lt;strong&gt;代价函数默许了越界&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加了走廊&lt;/strong&gt;：约束把 $|y_k| \le 0.6$ 变成&lt;strong&gt;硬性的可行性条件&lt;/strong&gt;，优化器被迫在边界上重新分配控制量，最终&lt;strong&gt;顶在 0.604 m 就不再往外走&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就是&lt;strong&gt;软约束（代价）和硬约束（不等式）的本质差别&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;作用方式&lt;/th&gt;
&lt;th&gt;越界时的表现&lt;/th&gt;
&lt;th&gt;在这段代码里&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;代价函数&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;偏好&lt;/td&gt;
&lt;td&gt;越界只是&quot;代价大一点&quot;，可以被别的好处抵消&lt;/td&gt;
&lt;td&gt;稳态漂到 0.811 m&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;硬约束&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;可行性&lt;/td&gt;
&lt;td&gt;越界的解&lt;strong&gt;根本不被允许&lt;/strong&gt;，求不出就报不可行&lt;/td&gt;
&lt;td&gt;停在 0.604 m&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;两者配合&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;代价决定&quot;多快回到线上&quot;，约束决定&quot;绝不能出界&quot;&lt;/td&gt;
&lt;td&gt;——&lt;/td&gt;
&lt;td&gt;控制量仅用掉 −1.79 / ±2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;顺带说，控制量只用掉 −1.79，说明&lt;strong&gt;这次约束是&quot;够得着&quot;的&lt;/strong&gt;——如果侧风强到超过执行器权限（比如侧风 2.5 m/s² 而上限是 2.0），那么无论怎么优化都守不住，QP 会变成&lt;strong&gt;不可行问题&lt;/strong&gt;。真实的 MPC 必须专门处理这种不可行情况（比如松弛变量 + 优先级降级），否则求解器会返回一个数值上看起来很怪的结果。
:::&lt;/p&gt;
&lt;h3&gt;3.5 航迹规划&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;航迹规划&lt;/strong&gt;采用基于地图信息的路径规划算法，确保导航过程中能避开障碍物、选择最优路径：&lt;/p&gt;
&lt;p&gt;$$P_{\text{最优}} = \arg\min C(P)$$&lt;/p&gt;
&lt;p&gt;其中 $P_{\text{最优}}$ 是最优路径，$C$ 是路径代价函数，&lt;strong&gt;考虑了路径长度和避障等因素&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;block-diagram&quot; data-block=&quot;11-planning-stack&quot;&amp;gt;
&amp;lt;div class=&quot;block-scroll&quot;&amp;gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 1430 248&quot; width=&quot;1430&quot; height=&quot;248&quot; class=&quot;block-svg&quot; role=&quot;img&quot; aria-label=&quot;无人机规划与控制分层架构图：传感器数据一路做地图构建生成体素栅格与欧氏符号距离场，另一路做状态估计；地图经前端搜索得到参考路径并构造安全飞行走廊，与状态估计一起送入模型预测控制求解，输出再经微分平坦转换为角速度和油门指令&quot;&amp;gt;
&amp;lt;g transform=&quot;translate(18,48)&quot;&amp;gt;
&amp;lt;g class=&quot;blk-links&quot;&amp;gt;
&amp;lt;path d=&quot;M 267.6 0.0 L 267.6 -15.0 Q 267.6 -20.0 272.6 -20.0 L 1080.8 -20.0 Q 1085.8 -20.0 1085.8 -15.0 L 1085.8 30.8&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.62&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 1085.8 36.0 L 1083.1 30.8 L 1088.5 30.8 Z&quot; fill=&quot;#f59e0b&quot;/&amp;gt;
&amp;lt;text x=&quot;676.7&quot; y=&quot;-26.0&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.66&quot;&amp;gt;当前位姿与速度&amp;lt;/text&amp;gt;
&amp;lt;path d=&quot;M 142.4 65.9 L 165.8 65.9 L 165.8 30.0 L 191.2 30.0&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.45&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 196.4 30.0 L 191.2 27.3 L 191.2 32.7 Z&quot; fill=&quot;currentColor&quot;/&amp;gt;
&amp;lt;path d=&quot;M 142.4 65.9 L 185.9 65.9 L 185.9 101.9 L 213.9 101.9&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.45&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 219.1 101.9 L 213.9 99.2 L 213.9 104.6 Z&quot; fill=&quot;currentColor&quot;/&amp;gt;
&amp;lt;path d=&quot;M 316.1 101.9 L 354.5 101.9 L 354.5 65.9 L 387.7 65.9&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.45&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 392.9 65.9 L 387.7 63.2 L 387.7 68.6 Z&quot; fill=&quot;currentColor&quot;/&amp;gt;
&amp;lt;line x1=&quot;581.9&quot; y1=&quot;65.9&quot; x2=&quot;630.6&quot; y2=&quot;65.9&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.45&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 635.9 65.9 L 630.6 63.2 L 630.6 68.6 Z&quot; fill=&quot;currentColor&quot;/&amp;gt;
&amp;lt;line x1=&quot;749.0&quot; y1=&quot;65.9&quot; x2=&quot;797.8&quot; y2=&quot;65.9&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.45&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 803.0 65.9 L 797.8 63.2 L 797.8 68.6 Z&quot; fill=&quot;currentColor&quot;/&amp;gt;
&amp;lt;line x1=&quot;935.6&quot; y1=&quot;65.9&quot; x2=&quot;984.4&quot; y2=&quot;65.9&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.45&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 989.6 65.9 L 984.4 63.2 L 984.4 68.6 Z&quot; fill=&quot;currentColor&quot;/&amp;gt;
&amp;lt;line x1=&quot;1182.1&quot; y1=&quot;65.9&quot; x2=&quot;1230.9&quot; y2=&quot;65.9&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.45&quot; stroke-width=&quot;1.3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 1236.1 65.9 L 1230.9 63.2 L 1230.9 68.6 Z&quot; fill=&quot;currentColor&quot;/&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-boxes&quot;&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;三路传感器&quot;&amp;gt;
&amp;lt;rect x=&quot;0.0&quot; y=&quot;36.0&quot; width=&quot;142.4&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.045&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.18&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;71.2&quot; y=&quot;57.6&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;三路传感器&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;71.2&quot; y=&quot;81.2&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;视觉 · 激光雷达 · IMU&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;状态估计&quot;&amp;gt;
&amp;lt;rect x=&quot;196.4&quot; y=&quot;0.0&quot; width=&quot;142.4&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;#8b5cf6&quot; fill-opacity=&quot;0.14&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.75&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;267.6&quot; y=&quot;21.6&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;状态估计&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;267.6&quot; y=&quot;45.2&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;EKF 融合出位姿与速度&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;地图构建&quot;&amp;gt;
&amp;lt;rect x=&quot;219.1&quot; y=&quot;71.9&quot; width=&quot;97.0&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.045&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.18&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;267.6&quot; y=&quot;93.5&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;地图构建&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;267.6&quot; y=&quot;117.1&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;体素占据栅格&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;欧氏符号距离场 ESDF&quot;&amp;gt;
&amp;lt;rect x=&quot;392.9&quot; y=&quot;36.0&quot; width=&quot;189.0&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.045&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.18&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;487.4&quot; y=&quot;57.6&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;欧氏符号距离场 ESDF&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;487.4&quot; y=&quot;81.2&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;每个空闲体素到最近障碍的距离&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;前端搜索&quot;&amp;gt;
&amp;lt;rect x=&quot;635.9&quot; y=&quot;36.0&quot; width=&quot;113.1&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.045&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.18&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;692.4&quot; y=&quot;57.6&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;前端搜索&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;692.4&quot; y=&quot;81.2&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;A* 生成参考路径&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;安全飞行走廊&quot;&amp;gt;
&amp;lt;rect x=&quot;803.0&quot; y=&quot;36.0&quot; width=&quot;132.7&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.045&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.18&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;869.3&quot; y=&quot;57.6&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;安全飞行走廊&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;869.3&quot; y=&quot;81.2&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;一串凸多面体 Hp ≤ h&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;MPC 二次规划&quot;&amp;gt;
&amp;lt;rect x=&quot;989.6&quot; y=&quot;36.0&quot; width=&quot;192.4&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;#f59e0b&quot; fill-opacity=&quot;0.14&quot; stroke=&quot;#f59e0b&quot; stroke-opacity=&quot;0.75&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;1085.8&quot; y=&quot;57.6&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;MPC 二次规划&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;1085.8&quot; y=&quot;81.2&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;预测 N 步 · 含动力学与输入约束&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-box&quot; data-t=&quot;微分平坦&quot;&amp;gt;
&amp;lt;rect x=&quot;1236.1&quot; y=&quot;36.0&quot; width=&quot;158.0&quot; height=&quot;59.9&quot; rx=&quot;10&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.045&quot; stroke=&quot;currentColor&quot; stroke-opacity=&quot;0.18&quot; stroke-width=&quot;1.2&quot;/&amp;gt;
&amp;lt;text x=&quot;1315.0&quot; y=&quot;57.6&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;14.5&quot; font-weight=&quot;600&quot; fill=&quot;currentColor&quot;&amp;gt;微分平坦&amp;lt;/text&amp;gt;
&amp;lt;text x=&quot;1315.0&quot; y=&quot;81.2&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.58&quot;&amp;gt;加加速度 → 角速度 + 油门&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;g class=&quot;blk-feedback&quot;&amp;gt;
&amp;lt;path d=&quot;M 1315.0 95.9 L 1315.0 159.8 Q 1315.0 165.8 1309.0 165.8 L 77.2 165.8 Q 71.2 165.8 71.2 159.8 L 71.2 101.1&quot; fill=&quot;none&quot; stroke=&quot;#8b5cf6&quot; stroke-opacity=&quot;0.6&quot; stroke-width=&quot;1.3&quot; stroke-dasharray=&quot;5 3&quot;/&amp;gt;
&amp;lt;path d=&quot;M 71.2 95.9 L 68.5 101.1 L 73.9 101.1 Z&quot; fill=&quot;#8b5cf6&quot;/&amp;gt;
&amp;lt;text x=&quot;693.1&quot; y=&quot;158.8&quot; text-anchor=&quot;middle&quot; font-family=&quot;Roboto,sans-serif,ui-sans-serif,system-ui,sans-serif,&apos;Apple Color Emoji&apos;,&apos;Segoe UI Emoji&apos;,Segoe UI Symbol,&apos;Noto Color Emoji&apos;&quot; font-size=&quot;11.5&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.66&quot;&amp;gt;闭环：飞过之后重新观测，地图与状态一起更新&amp;lt;/text&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;/g&amp;gt;
&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;figcaption&amp;gt;图 3 · 规划与控制的分层架构：地图负责「哪里不能去」，优化负责「怎么去最省」，两者通过安全走廊这条线性约束接口对接&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;:::note[代价函数 $C(P)$ 里到底装了什么]
论文只说了&quot;考虑路径长度和避障&quot;。实际工程里，$C$ 通常是一个&lt;strong&gt;加权和&lt;/strong&gt;，每一项对应一个飞行品质要求：&lt;/p&gt;
&lt;p&gt;$$C(P) = \underbrace{w_1 \int \lVert \dot{p} \rVert , dt}&lt;em&gt;{\text{路径长度}} + \underbrace{w_2 \int \lVert \ddot{p} \rVert^2 dt}&lt;/em&gt;{\text{平滑度（省电、好跟踪）}} + \underbrace{w_3 \int \lVert p^{(3)} \rVert^2 dt}&lt;em&gt;{\text{加加速度（乘客/相机舒适度）}} + \underbrace{w_4 \cdot \text{越界惩罚}}&lt;/em&gt;{}$$&lt;/p&gt;
&lt;p&gt;权重的取法直接决定飞出来的风格：$w_1$ 大就飞直线、贴着障碍抄近路；$w_3$ 大就飞得很&quot;温吞&quot;但视频画面稳。&lt;strong&gt;所谓&quot;算法调参&quot;，很多时候就是在调这几个 $w$。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;另一条工程上的路数是&lt;strong&gt;分层解耦&lt;/strong&gt;：前端用 &lt;em&gt;&lt;em&gt;A&lt;/em&gt;/JPS&lt;/em&gt;* 在栅格上搜一条只考虑&quot;能不能过&quot;的几何路径（快，但不平滑、动力学上不可执行）；后端再用&lt;strong&gt;轨迹优化&lt;/strong&gt;把这条折线变成满足动力学约束的平滑轨迹。上图正是这个分层结构。
:::&lt;/p&gt;
&lt;h3&gt;3.6 论文给出的实验数据&lt;/h3&gt;
&lt;p&gt;通过数据处理与算法优化的综合作用，无人机的导航精度与控制稳定性得到提升。论文给出的实际飞行测试参数变化：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;表 3 优化下参数变化&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;测试参数&lt;/th&gt;
&lt;th&gt;优化前&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;导航误差&lt;/td&gt;
&lt;td&gt;5 米&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;姿态稳定性&lt;/td&gt;
&lt;td&gt;较差&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;控制响应时间&lt;/td&gt;
&lt;td&gt;100 毫秒&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;:::caution[关于这张表，有些话必须说清楚]
按论文原文，表 3 记录的是&lt;strong&gt;优化前&lt;/strong&gt;的基线数据（导航误差 5 米、姿态稳定性较差、控制响应时间 100 毫秒）。原文在表述上提到&quot;从表 3 可以看出优化下无人机的导航误差大幅减小、姿态稳定性显著提升、控制响应时间也有所缩短&quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这里存在一处数据与论述的不一致&lt;/strong&gt;：表里只有&quot;优化前&quot;一列，却要论证优化后的改善。&lt;/p&gt;
&lt;p&gt;我更倾向于把它理解为一组&lt;strong&gt;基线参照值&lt;/strong&gt;——它给出了优化前的水平，作为后续改进的对照。但如果要严谨地支撑&quot;大幅减小、显著提升&quot;这类结论，至少还需要补上：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优化后的对照列&lt;/strong&gt;（导航误差降到多少米、响应时间缩短到多少毫秒）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;测试样本量&lt;/strong&gt;（飞了几架次？单次还是多次平均？）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;误差指标的定义&lt;/strong&gt;（导航误差是相对真值还是相对规划轨迹？怎么测的？）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这是这篇论文在实验呈现上最明显的可完善之处。我在下面把它改写成一张能真正说明问题的表：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;测试参数&lt;/th&gt;
&lt;th&gt;优化前（基线）&lt;/th&gt;
&lt;th&gt;优化后&lt;/th&gt;
&lt;th&gt;相对变化&lt;/th&gt;
&lt;th&gt;统计口径&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;导航误差&lt;/td&gt;
&lt;td&gt;5 米&lt;/td&gt;
&lt;td&gt;&lt;em&gt;待补&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;待补&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;建议：相对 RTK 真值的 RMSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;姿态稳定性&lt;/td&gt;
&lt;td&gt;较差&lt;/td&gt;
&lt;td&gt;&lt;em&gt;待补&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;待补&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;建议：姿态角速度的 RMS 或超调量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;控制响应时间&lt;/td&gt;
&lt;td&gt;100 毫秒&lt;/td&gt;
&lt;td&gt;&lt;em&gt;待补&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;待补&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;建议：阶跃指令的上升时间 / 相位滞后&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;:::&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;不过整体上，通过在数据处理和算法优化上的投入，论文认为在提高无人机飞行性能方面取得了明显成果。这个方法不仅在理论上有支持，在实际测试中也得到了令人满意的结果。&lt;/p&gt;
&lt;p&gt;这对无人机在复杂环境中执行任务很有意义——比如&lt;strong&gt;城市建筑巡查、紧急救援&lt;/strong&gt;这类场景中，&lt;strong&gt;高导航精度和控制稳定性是任务成功的关键因素&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;四、把这篇论文拆成一份可执行的清单&lt;/h2&gt;
&lt;p&gt;论文的结论是方向性的，落到工程上需要的是&quot;下一步先做什么&quot;。如果让我现在重新实现一遍这套系统，我会按下面的顺序推——&lt;strong&gt;每一项都是前一项能跑起来的前提&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[x] 明确坐标系约定（惯导 ENU、机体 FLU），在接口处显式转换&lt;/li&gt;
&lt;li&gt;[ ] 打通各路传感器的时间戳与统一时基&lt;/li&gt;
&lt;li&gt;[ ] 标定 IMU 的零偏与尺度因子，做静止初始化&lt;/li&gt;
&lt;li&gt;[ ] 标定相机/雷达 → 机体的外参（旋转 + 平移）&lt;/li&gt;
&lt;li&gt;[ ] 用 EKF 跑通&quot;IMU 高频 + 位置低频&quot;的融合，验证误差不随时间发散&lt;/li&gt;
&lt;li&gt;[ ] 实现延迟补偿与输出预测，让控制器拿到当前时刻状态&lt;/li&gt;
&lt;li&gt;[ ] 把点云投影成体素占据栅格，接通建图&lt;/li&gt;
&lt;li&gt;[ ] 在栅格上算出 ESDF，验证距离与梯度的符号和量级&lt;/li&gt;
&lt;li&gt;[ ] 前端 A* 搜出几何路径，验证能绕开凹形障碍（这是势场法的死穴）&lt;/li&gt;
&lt;li&gt;[ ] 用路径周围的凸多面体构造安全飞行走廊&lt;/li&gt;
&lt;li&gt;[ ] 把 MPC 写成 QP，先只加输入约束，验证跟踪&lt;/li&gt;
&lt;li&gt;[ ] 再把走廊约束加进去，验证约束生效时控制量未饱和&lt;/li&gt;
&lt;li&gt;[ ] 补上不可行情况的处理（松弛变量 + 降级策略）&lt;/li&gt;
&lt;li&gt;[ ] 加故障降级：单传感器失效时明确走哪条备选路径&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::tip[GitHub 上的提醒写法]
在 issue 或 PR 里追进度时，这套任务列表可以直接用 GitHub 的原生语法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; [!TIP]
&amp;gt; 任务列表在 GitHub 上会渲染成可勾选的复选框，改 `[ ]` 为 `[x]` 就等于提交进度。

- [ ] 待办
- [x] 已完成
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关于&quot;降级&quot;这一条，值得单独强调&lt;/strong&gt;：论文的出发点是&quot;单个传感器都不够可靠&quot;，但融合的同时也带来了&lt;strong&gt;新的失效模式&lt;/strong&gt;——如果激光雷达被雨雾/强光干扰，或者 GPS 进隧道丢星，系统必须有明确的降级路径，而不是简单地&quot;少一路输入继续算&quot;。成熟的飞控会为每种组合定义&lt;strong&gt;模式&lt;/strong&gt;（哪种最小传感器组合还能飞、丢失哪一路后精度会掉到什么程度），并且&lt;strong&gt;在数据里如实标注能力降级&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这在工程上有个很实际的判据：&lt;strong&gt;一个融合系统能不能上线，不取决于它正常时有多准，而取决于它&quot;缺一路&quot;时能不能安全退出。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;相关开源实现&lt;/h2&gt;
&lt;p&gt;论文没有附代码。如果你想动手复现，下面两个仓库是这段技术路线上比较成熟的参考——一个在&lt;strong&gt;融合&lt;/strong&gt;侧，一个在&lt;strong&gt;规划&lt;/strong&gt;侧。文末另附本文三段示意代码的开源仓库卡片。&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;ethz-asl/ethzasl_msf&quot;}&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;ZJU-FAST-Lab/ego-planner&quot;}&lt;/p&gt;
&lt;p&gt;:::note[怎么用这些仓库]
建议的读法不是照搬，而是&lt;strong&gt;带着本文的问题去读&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;读 &lt;code&gt;ethzasl_msf&lt;/code&gt; 时重点看它&lt;strong&gt;怎么处理各路传感器的延迟与时间戳&lt;/strong&gt;（对应 1.1 节）——这是全文最容易被跳过、也最容易被工程现实打脸的一环。&lt;/li&gt;
&lt;li&gt;读 &lt;code&gt;ego-planner&lt;/code&gt; 时重点看它&lt;strong&gt;怎么在 ESDF 上做梯度下降做轨迹优化&lt;/strong&gt;（对应 2.4 节），以及&lt;strong&gt;怎么处理局部极值&lt;/strong&gt;。它是一个很好的反例集合：作者在论文里专门讨论了纯梯度法容易陷入局部最优，所以加了&quot;绕行力&quot;来跳出。&lt;/li&gt;
&lt;li&gt;文末的 &lt;code&gt;uav-sensor-fusion-demos&lt;/code&gt; 对应本文 1.3、2.5、3.4 节三段脚本：先跑通，对照看「加约束前后差在哪」——它是教学演示，不是飞控量产代码。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外，&lt;code&gt;AtsushiSakai/PythonRobotics&lt;/code&gt; 里有 A*、RRT*、势场法、MPC 等算法的&lt;strong&gt;最小可读实现&lt;/strong&gt;，适合用来对照本文里那几段代码。
:::&lt;/p&gt;
&lt;h2&gt;结论与展望&lt;/h2&gt;
&lt;p&gt;本文通过对基于传感器融合的自主避障无人机导航与控制算法的研究，取得了一系列成果：新算法通过&lt;strong&gt;充分利用多种传感器的信息&lt;/strong&gt;，使无人机能够在复杂环境中避障并实现高精度导航，为无人机技术的发展提供了新的思路和方法，有望在&lt;strong&gt;军事、民用、科研&lt;/strong&gt;等领域发挥作用。&lt;/p&gt;
&lt;p&gt;未来的工作方向是&lt;strong&gt;进一步优化算法&lt;/strong&gt;，推动无人机技术的不断创新与发展。如果我来补这份展望，会具体加上三条：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;从模块化走向一体化&lt;/strong&gt;：把&quot;感知 → 建图 → 规划 → 控制&quot;串得更紧，比如让 MPC 直接吃 ESDF 的梯度（而不是先把约束近似成凸多面体），减少中间环节的信息损失&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;让不确定性进入决策&lt;/strong&gt;：现在的避障用的是&quot;点估计&quot;（认为估计出来的位置就是真值）；更稳的做法是把协方差也传下去，&lt;strong&gt;在估计得不准的地方主动留更大余量&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;把安全性变成可验证的属性&lt;/strong&gt;：控制障碍函数（CBF）这类方法可以把&quot;绝不进入障碍&quot;写成前向不变性的数学条件，从而&lt;strong&gt;在理论上给出保证&lt;/strong&gt;，而不只是靠大量仿真试验&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;参考文献&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;张红蕾, 盛志超, 叶林等. 基于多传感器融合的无人机自主避障方法[J/OL]. 激光杂志, 1-7[2024-01-23]&lt;/li&gt;
&lt;li&gt;陈昊. 基于多传感器信息融合的无人机自主追击与避障研究[D]. 哈尔滨工程大学, 2021.&lt;/li&gt;
&lt;li&gt;何守印. 基于多传感器融合的无人机自主避障研究[D]. 北京理工大学, 2016.&lt;/li&gt;
&lt;li&gt;刘泽琳, 张袁元, 杨旺嗣等. 基于 SLAM 的自主避障物流无人机系统设计[J]. 机电工程技术, 2023, 52(07): 60-63+102.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;延伸阅读（本文补充，非论文原文引文）&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;PX4 Autopilot. &lt;em&gt;Using the ECL EKF&lt;/em&gt;（EKF2 的延迟融合时轴、24 状态向量与延迟补偿参数）[EB/OL]. https://docs.px4.io/main/en/advanced_config/tuning_the_ecl_ekf&lt;/li&gt;
&lt;li&gt;Oleynikova H, Taylor Z, Fehr M, et al. &lt;em&gt;Voxblox: Incremental 3D Euclidean Signed Distance Fields for On-Board MAV Planning&lt;/em&gt;[C]. IROS, 2017.&lt;/li&gt;
&lt;li&gt;Zhou B, Gao F, Wang L, et al. &lt;em&gt;Robust and Efficient Quadrotor Trajectory Generation for Fast Autonomous Flight&lt;/em&gt;[J]. IEEE RA-L, 2019.&lt;/li&gt;
&lt;li&gt;Stellato B, Banjac G, Goulart P, et al. &lt;em&gt;OSQP: An Operator Splitting Solver for Quadratic Programs&lt;/em&gt;[J]. Mathematical Programming Computation, 2020.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;一点回顾&lt;/h2&gt;
&lt;p&gt;这篇文章是我们团队&lt;strong&gt;自主避障无人机&lt;/strong&gt;项目的理论总结，也是我第一次完整走一遍&quot;&lt;strong&gt;提出问题 → 梳理技术路线 → 用公式建模 → 给出优化方案 → 成文投稿&lt;/strong&gt;&quot;的学术流程。&lt;/p&gt;
&lt;p&gt;回头看，这篇论文里最让我受益的不是某个具体公式，而是&quot;&lt;strong&gt;融合&lt;/strong&gt;&quot;这个思维方式本身：面对一对相互矛盾的约束（视觉怕光、雷达缺语义、IMU 会飘），最优解往往不是找到某个&quot;完美的传感器&quot;，而是&lt;strong&gt;让它们互相兜底&lt;/strong&gt;。这个思路在我后来做别的东西时反复被验证——&lt;strong&gt;系统的鲁棒性来自冗余的异构性，而不是单点的极致。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;而这次重读论文最大的收获，是发现&lt;strong&gt;当年被&quot;一句话带过&quot;的地方，恰恰是工程上最难的部分&lt;/strong&gt;。&quot;时间同步&quot;&quot;延迟补偿&quot;&quot;外参标定&quot;&quot;约束不可行怎么办&quot;——这些在论文里可能只占一行，在真机上却要占掉大半的调试时间。&lt;/p&gt;
&lt;p&gt;📌 有一件事不妨直说：&lt;strong&gt;论文写得好不好，和系统能不能飞，是两套完全不同的评价标准。&lt;strong&gt;论文要的是逻辑自洽、结论清晰；系统要的是&lt;/strong&gt;在传感器脏数据、时间不同步、约束临时不可行的糟糕情况下，还能安全地停下来&lt;/strong&gt;。这两者都需要练，但练法不一样。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;论文原版扫描件：&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./02-paper-p1.png&quot; alt=&quot;论文第 1 页&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./03-paper-p2.png&quot; alt=&quot;论文第 2 页&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./04-paper-p3.png&quot; alt=&quot;论文第 3 页&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./05-paper-p4.png&quot; alt=&quot;论文第 4 页&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./06-paper-p5.png&quot; alt=&quot;论文第 5 页&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./01-acceptance-notice.png&quot; alt=&quot;稿件采用通知&quot; /&gt;&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;RERELEGEND/uav-sensor-fusion-demos&quot;}&lt;/p&gt;
</content:encoded></item></channel></rss>