这条线是什么
前六章已经把「手」做完:交叉编译、滞回、继电器风扇、DHT22、MQTT、三线程。综合项目不再另开第七章,也不再做 weights.h 手写 tiny 前向。现场还是这套温控,换的是谁做决定、人怎么下意图。
人在终端里打字。DeepSeek Harness 跑 Agent 循环:当前模型根据你的话和工具列表,决定调用读状态、开/关风扇、控灯、改阈值。工具经 MQTT 打到荔枝派。板上三线程自己的滞回一直在转——没人打字,风扇仍按温度动作。
荔枝派继续管风扇与 MQTT;K3 上跑量化后的本地模型;你在终端里像用 Claude Code 一样跟 DeepSeek Harness 打字。同一套工具,本地和云端只换一个地址。
前六章已经把「手」做完:交叉编译、滞回、继电器风扇、DHT22、MQTT、三线程。综合项目不再另开第七章,也不再做 weights.h 手写 tiny 前向。现场还是这套温控,换的是谁做决定、人怎么下意图。
人在终端里打字。DeepSeek Harness 跑 Agent 循环:当前模型根据你的话和工具列表,决定调用读状态、开/关风扇、控灯、改阈值。工具经 MQTT 打到荔枝派。板上三线程自己的滞回一直在转——没人打字,风扇仍按温度动作。
DeepSeek Harness(dsh)是 Agent 运行时:会话、工具、循环、界面都是插件。它默认可以挂云端 DeepSeek,也可以挂任何 OpenAI 兼容接口。源码不用改。
DeepSeek 模型只是脑子的一种。本地量化模型是另一种脑子。对 Harness 来说,两者都是「模型供应商」:协议相同,baseURL 不同。
https://api.deepseek.com。能力强,要密钥和网络。
K3 上 llama-server 提供 http://<K3>:8080/v1。量化后的指令模型,离线也能验收。
切换发生在 Harness 的模型配置里。对话框、工具列表、板上程序都不改。
K3 适合跑 llama.cpp(厂商 IME2 加速),针脚不像荔枝派那样接 DHT 和继电器。所以综合项目用两块板,用已经教过的 MQTT 当总线。
| 机器 | 跑什么 | 本课工具 |
|---|---|---|
| 荔枝派 4A | ch06 三线程:采温、滞回、风扇、灯、MQTT | C + RuyiSDK 交叉编译 |
| 进迭 K3 | llama-server + 终端里的 DeepSeek Harness | Bianbu 上的 llama.cpp-tools-spacemit |
| 个人电脑 | 量化(llama-quantize)、改工具插件、SSH | RuyiSDK / 本机 gcc |
ruyi install 就能吃到 IME2。普通 gcc 自编的 llama.cpp 跑在 A100 核上会非常慢。
本课不把网页当成对话入口。你在终端里启动 DeepSeek Harness,像 Claude Code 那样打字、看工具调用、看回复。官方 dsh web 会起浏览器,主路径不用它。
交互式终端界面是 Harness 的一层 UI 插件。装好 dsh 之后,用终端 profile 进入当前目录的会话:
# 安装 CLI(Node 22+) npm i -g @deepseek-ai/dsh@next # 终端 UI(Claude Code 同款打字界面) dsh plugin --profile tui add @dsh-tui/dsh-tui dsh --profile tui
模型配置里加两个供应商:一个 baseURL 指向本机或 K3 的 llama-server(api: openai-completions),一个指向云端 DeepSeek。之后在会话里切换模型,工具不变。
dsh --profile headless "一句话任务",跑完一轮就退出,适合脚本。本课验收用交互式终端会话:你打字,模型调工具,板上有现象。
具体叫哪一个指令模型不钉死。课要的是:在电脑上用 llama.cpp 把一份较高精度权重压成 GGUF(至少做到 Q4),拷到 K3,用带 IME2 的 llama.cpp 起服务。
# 主机:量化(示例文件名按你下载的模型改) llama-quantize model-f16.gguf model-q4_0.gguf Q4_0 # K3:加速推理 + OpenAI 兼容 HTTP sudo apt install llama.cpp-tools-spacemit llama-server -m ~/.cache/models/llm/model-q4_0.gguf -t 4 --port 8080
量化前后记下体积,并在终端 Harness 里用同一句「现在热不热」各问一次:工具调用还在,才算压成功。极致压缩可以再出一份 Q2,对照质量与速度;主验收以能稳定调工具的那一档为准。
K3 带 IME2 时,中小指令模型(大约 1.5B–4B、Q4)做「读状态、选工具、回一句人话」是够用的。把很大的稠密模型压进内存,常见结果是能启动、生成慢、工具调用不稳,本课不走那条路。
工具在 Harness 里登记(TypeScript 的 defineTool)。execute 里不要重写 GPIO。它只做转发:发 MQTT,或调用你用 C 编好的小命令。荔枝派上的 tri-thread 继续采温、滞回、驱动继电器(本轮温度/湿度为模拟值,见下)。
| 工具 | 模型看见的含义 | 落到板上 |
|---|---|---|
read_status | 读当前温度、湿度、风扇、阈值 | 订阅读状态主题,或向板问一句 status |
set_fan | 开或关风扇 | 发布命令主题,通信线程执行 |
set_led | 开或关灯 | 沿用第五章的灯主题 |
set_threshold | 改温度上下限 | 与第四章 set temp 同一类下行;非法值/关系不成立/未生效都报错 |
人打的是自然语言:「太吵了把风扇关了」「现在多少度」。模型选工具、填参数。传感器数字由 read_status 提供,模型自己不去读针脚。
tri-thread 用
SIMULATE_SENSOR 1 编译(chapters/ch06/code/tri-thread/main.c),
read_status 读到的温度、湿度是程序内部生成的模拟波形(约每 500 ms 在 24–31 °C
之间来回),不是 DHT22 实测。要换成真实采集,把 SIMULATE_SENSOR
改为 0,接上第四章的 DHT22 位带实现(main.c 里 dht22_read
的 #else 分支)后重编。set_fan / set_led 经 MQTT 落到
IO1_5 继电器与 IO1_4 外接 LED,实物现象可见——「谁做决定」这一段是真的,只有温度数字是模拟的。
set_threshold 是三段式校验:先查参数格式,再查阈值关系(板端对非法关系是静默忽略的),最后读回状态确认真的生效。实机(high=28 low=26 时):
# 上限 20 低于下限 26:板端会忽略,工具直接拦下 $ node mqtt.mjs threshold high 20 Error: invalid threshold relation: high=20 must be > low=26 # 合法值:发命令后读回,值真的变了才算成功 $ node mqtt.mjs threshold high 30 set high 30 (confirmed high=30) # which / 数值 / 精度非法一律拒绝 $ node mqtt.mjs threshold max 25 Error: invalid threshold: max (expected high|low) $ node mqtt.mjs threshold high abc Error: invalid threshold value: abc (expected a number) $ node mqtt.mjs threshold high 27.05 Error: threshold value out of range: 27.05 (use 0.1 °C resolution)
荔枝派上的控制线程一直按阈值拉继电器。Harness 是指挥官,不是唯一的心跳。这样前六章的验收在综合项目里仍然成立:把终端关掉,风扇还在跟温度走。
打字用来查状态、改阈值、强制开/关、换一句人话意图。Agent 的决定落到同一套 MQTT 和 GPIO 上。
| 项 | 通过条件 |
|---|---|
| 滞回仍在 | 不打开 Harness 时,超上限风扇转、低下限停 |
| 量化 | 有一份学生自己压出的 GGUF;记下体积;K3 上 llama-server 能起来 |
| 终端对话 | 在终端 Harness 里打字,能看到模型调用本课工具 |
| 读得动 | read_status 返回的温度与板上日志一致(本轮板上是 SIMULATE_SENSOR 1 的模拟温湿度,两边对得上即算通过;真实 DHT22 需另开 SIMULATE_SENSOR 0) |
| 动得了 | 打字让风扇或灯动作,板上现象可见 |
| 换模型 | 同一句话,本地 llama-server 与云端 DeepSeek 各走一遍,工具仍是同一套 |
| 干净停 | 板上 Ctrl+C 仍先停风扇再退出(ch06 契约保留) |
llama-quantize 得到 Q4 GGUF,记下体积。llama.cpp-tools-spacemit,llama-server 监听 8080。dsh --profile tui 进会话;登记本地与云端两个模型供应商。read_status / set_fan / set_led / set_threshold,全部转发到已有 MQTT。量化用的是 Qwen2.5-1.5B-Instruct 的 FP16 GGUF。K3 上 llama.cpp-tools-spacemit 自带的 llama-quantize 把它压成 Q4_0。主机没有这份量化程序,所以这一步在 K3 上做。Q8 不能再压,工具会拒绝。
| 文件 | 体积 |
|---|---|
| FP16 | 3 560 416 288 字节(量化日志 3389.80 MiB,16.00 BPW) |
| Q4_0 | 1 066 227 232 字节(1011.16 MiB,4.77 BPW) |
服务是 llama-server -m …q4_0.gguf -t 4 --host 0.0.0.0 --port 8080。启动日志里 use_ime2: 1,/v1/models 的量化档是 Q4_0。云端用 deepseek-flash,thinking: disabled。密钥只放在 K3 的环境变量里。
同一句「现在热不热?先调用 read_status…」:本地 Q4 和云端 flash 都叫了 read_status。板上日志的温度和回答一致。接着在 K3 终端里再打一句,让它把风扇关掉。
deepseek-flash,调用 read_status 后按返回值作答。回到 fan auto 之后,终端可以关掉,荔枝派上的风扇仍按阈值自己开关。
除了上面章节实验时的实拍,本项目还有一条**主机侧驱动实录**:本机直接往 course/thermo/cmd 与 course/led/cmd 发命令,荔枝派上的继电器与 LED 跟着动(风扇转/停、灯亮/灭)。
fan on → fan off → fan auto 加 led on/off,板端日志逐条对应。
[MQTT] cmd payload=fan on|off|auto 与 [INFO] fan ON/OFF 一一对应。
course/led/cmd 的 on/off 分别打出 [INFO] LED ON/OFF。把整条链路摊开看一遍:主机侧发命令、Broker 中转、荔枝派抓包与执行、K3 侧也能驱动同一套工具。
fan on/off/auto + led on/off,K3 侧再发一条 fan auto,同时看 Broker 与板端抓包。
course/# 看到 course/thermo/cmd、course/led/cmd 与状态主题的完整中转。
tcpdump -i wlan0 'tcp port 1883' 抓到与 Broker 192.168.31.206:1883 的真实 TCP 报文(不是 mock)。
course-board)发 fan auto,说明两台板子共用一条 MQTT 通道。综合项目用的就是前面几章那套硬件,实物接线沿用第三、四、五章的实拍:
fan on|off|auto 就是驱动这条信号链。
read_status 的温湿度取自 SIMULATE_SENSOR 1 模拟波形,不是这颗 DHT22 的实测(改 SIMULATE_SENSOR 0 即启用真实采集)。
course/led/cmd 控的就是这颗灯。实物动作录像(手机拍摄):由本机发 fan on/off/auto 与 led on/off,可看到继电器吸合、风扇转停与 LED 亮灭。
工具在 project/course-board/。它们只发 MQTT:course/thermo/cmd 上的 status / fan on|off|auto / set high|low,灯仍走第五章的 course/led/cmd。@dsh-tui/dsh-tui@0.1.2 会重复插入 storage,和 dsh 0.1.5-rc.2 冲突;headless 配置和修法写在该目录的 README。