Craft Anything 能做什么?从逆向分析到软件重建

Craft Anything 是 ArtCraft 的开源软件研究与重建工作台。它能检查网站、应用、游戏和文件,把分析结果整理成可复核的证据,再交给编码 Agent 构建新项目、对比结果并迭代。桌面应用、CLI 和 MCP 共用一个 Rust 引擎。

这个项目最值得关注的是它如何把研究、构建和验证串起来。当前仍处于早期 Alpha 阶段,适合从网页、小工具或单项功能入手。复杂应用、供应商 SDK 和硬件行为需要逐项验证。本文基于 2026 年 11 月 3 日核对的官方文档、发布记录与部分源码。

Craft Anything 官方工作台,左侧是函数列表,中间是伪 C 和 Agent 对话,右侧是证据台账

官方工作台示例。图片来源

公开版本与使用条件

核查当天,最新公开版是 v0.3.0,北京时间 11 月 3 日 01:58 发布。发布页列出 29 个附件,包括 macOS Universal DMG 和 CLI ZIP。本文查阅的主分支提交是 09849084dfb22b4d1ae5d4abbc7411d18942f77b,其版本字段已标为 0.4.0。下文涉及主分支实现的细节,试用时需要回到实际安装的版本核对。 发布页 · Cargo.toml

  • 许可证和入口。 根 manifest 与许可证文件采用 MIT OR Apache-2.0 双许可,GitHub API 的单个许可证字段显示 Apache-2.0。项目提供 egui 桌面应用、CLI 和 MCP,WebAssembly 浏览器查看器覆盖部分能力。核查时的主分支要求 Rust 1.97+。
  • 编码 Agent。 Claude Code 接入最完整,使用本机已登录的 CLI。Codex 已有适配器,官方仍将真实安装环境下的验证列为待办,会话恢复等能力也有缺口。
  • 模型与外部工具。 支持配置 Anthropic、OpenAI、OpenRouter 和本地 OpenAI 兼容服务。官方仍将 API Key 构建列为待实测,本地模型构建也有后续工作。增强分析和运行观察按需使用 Ghidra、Chromium、Frida、adb 或游戏引擎。模型额度另计,图片与 3D 生成需要启用对应功能并提供 fal.ai 密钥。 官方 README · 路线图

核查时,README 仍写着首次公开发布待完成。发布记录和路线图均已记载 v0.3.0。

已有能力与验证范围

网站研究与重建

工作台可以采集截图、提取可见文字、测量布局并研究多个页面。生成项目后,可以在桌面、平板和手机宽度下比对。官方记录中的一个网页样本用时 1.5 分钟,达到 96% likeness。这个指标衡量该样本的布局与感知相似度,功能覆盖需要另行验证。 官方验证记录

静态分析与证据管理

原生二进制分析包括函数、字符串、交叉引用、伪 C、调用图和值流。工具也覆盖 .NET、Java/Android、Apple、Electron/JavaScript、固件、抓包文件、数据库和游戏资源。JS/Electron 已有应用关系图、版本比较、调用和事件追踪。原生值流限于寄存器,内存和栈槽超出范围。静态调用图覆盖可以解析的调用。

官方列出 298 个引擎工具,计入 AI 和设置层后共有 314 个。逐工具验证表中,adb 仍有 23 项等待真机验证。分析结果可以随调查项目保存和分页查看,证据里会标注置信度、覆盖范围、限制及未知项。 逐工具验证表 · 原生分析源码

应用与游戏重建

编码 Agent 可以按指定语言、框架或引擎生成项目,再运行构建检查。官方记录中的小型桌面应用案例用时 9 分钟,验收条件是编译成功。游戏自测仍依赖生成程序自行上报,独立观察玩法和测量关键数值还待实现。判断行为是否复现,需要观察实际运行和输入输出。 官方验证记录

社交媒体说法核对

最初的线索来自 X 上明子206(@mingzi206)的一则帖子截图。下面按官方文档和源码核对其中几项说法。查看帖子截图

  • 自动捕获全部运行行为。 已有浏览器观察、进程捕获、抓包解析和 Frida 脚本接口。应用和游戏的自动研究主要依赖静态分析,启用运行研究后可以启动目标一次,取得窗口截图和 UI 树。Frida 的 frida_session_begin 先保存 attach 或 spawn 意图,执行脚本时才调用外部 CLI 做限时操作。持续跟踪系统调用、内存变化或私有协议,需要额外验证。 Frida 工具源码
  • 自动还原完整业务架构。 静态图、反汇编、伪 C 和寄存器值流能提供线索。动态分派、内存状态和服务端逻辑仍可能缺失,通用符号执行与业务语义恢复也缺乏相应证据。 原生分析源码
  • 几分钟重建原生软件。 目前公开案例的验收分别是网页相似度和小应用编译。要判断复杂应用能否在几分钟内实现行为等价,还需要按实际功能测试。 官方验证记录
  • 极低内存占用,取代 IDA 或 Ghidra。 核心用 Rust 实现,完整流程还可能调用浏览器、游戏引擎、Ghidra 和模型。所查材料缺少全流程内存基准或反编译能力的横向实测。项目的差异主要在于将分析证据、Agent 和重建流程整合在一起。 官方 README

净室模式与试用范围

默认的 Clean room 模式依据截图、可见网页、布局测量和获准观察的窗口等外部表现。Deep 模式开放文件、脚本、反编译等内部分析。模式名称只描述工具的工作方式。审批能否约束读取和命令执行,取决于具体 Agent 适配器。路线图仍列有 Auto mode 的删除判断问题,进程捕获的隔离也取决于宿主能力。 路线图 · 进程捕获源码

和 REA 相比,REA 更侧重为已有 Agent 提供逆向工程工具和证据,Craft Anything 则把研究、构建和多轮比对放进一个带桌面界面的项目。两者能力有重叠,本文没有做性能或准确率对照。

我会先把 Craft Anything 列为观察项目。有明确目标时,网页或 Electron 的单项功能适合作为小样本,因为容易限定输入、输出和验收范围。试验可以用自有或获授权的样本,在独立、无敏感凭据的环境中接入 Claude Code,关闭自动批准,并对照正常和异常输入的实际运行结果。Android APK 或 SDK 可以用于授权排障,涉及硬件的判断仍需供应商协议、真实板卡与现场验证。

核查于 2026 年 11 月 3 日,范围包括 GitHub 元数据、发行记录、固定提交的文档与部分源码。本文未安装程序、运行目标样本或配置 MCP 与编码 Agent。

TencentDB Agent Memory 与 Hy Memory 对比

在同一台电脑上跨 Codex、WorkBuddy、Hermes 和 OpenCode 复用历史,优先看 Hy Memory/MemHub。个人要在 Mac 和 Windows 间共享记忆,则值得优先验证 TencentDB Agent Memory。多人共用项目 Wiki、代码索引、可复用 Skill,或把独立 CodeBuddy 纳入统一记忆入口,TencentDB Agent Memory 更贴合。 这是根据源码、文档与发行记录作出的场景判断。两套系统均未在本机部署,也没有用同一组会话做对照测试。

腾讯云版如何工作

  • 入口是模型代理。 Agent 把模型请求指向 Memory Proxy。代理按用户凭据识别身份并解析 Team → Agent → Task 绑定,在适用的请求上注入已授权记忆,再调用配置的上游模型。启用归档后,会话进入 Memory Core。历史会话也可以单独导入,例如 Codex 的导入工具可按目录选取本地 session。 Codex 代理源码 · Codex 历史导入
  • 对话按四层组织。 L0 保留原始对话,L1 提炼事实、偏好和约束,L2 按场景组织知识,L3 形成长期画像。L2/L3 用于快速恢复上下文,精确查询可沿关键词和向量召回查回 L1/L0,以 RRF 合并排序。 架构说明 · 召回实现
  • 知识和经验可以单独分发。 Chat Memory、Skill、LLM-Wiki、CodeGraph 都作为资产管理。Skill 沉淀可重复执行的做法,Wiki 从文档构建可查询的页面,CodeGraph 从仓库索引文件、符号和调用关系。Wiki 和 CodeGraph 异步构建,Agent 可用 /v3/tools/list 和 /v3/tools/call 按需查询。v2.0.3 已含富文档导入,变更日志也记录了私有仓 SSH 建图,实际凭据和构建链路仍需验收。 Knowledge OpenAPI · 变更日志
  • 资产权限和 Agent 绑定分别判定。 源码对 private 只允许 owner 访问,对 restricted 的普通成员要求匹配 ACL,团队管理员仍可凭角色获得部分操作权限。固定绑定另有规则,当前 canBindAsset 拒绝绑定 restricted 和 task 资产。试用时应分别验证面板读取、工具调用与代理注入。 权限判定源码
  • 可自托管。 Docker 入口启动 Core、Hub(Panel + Knowledge)和 Proxy。默认 SQLite 持久化,MongoDB 是可选实验后端。安装分别配置记忆处理模型和对话上游,两组可复用。通过代理的模型请求会流向所配上游,其中可能包含注入的记忆。 安装指南 · 容器部署

与 Hy Memory/MemHub 对比

维度 TencentDB Agent Memory Hy Memory/MemHub
产品定位 团队服务,给不同成员、Agent 和任务分配可共享的资产 Hy Memory 是 Python 记忆引擎,MemHub 是本地优先的桌面/CLI 产品,汇总获授权的历史并提供跨工具召回
采集与召回 代理采集新流量并按绑定注入。历史会话有独立导入入口 主要靠已授权的日志扫描、Hook/插件采集。通过 MCP/Skill 主动查询,部分宿主支持自动召回
记忆结构 L0–L3 对话记忆,另有 Skill、LLM-Wiki、CodeGraph Chat 的分层长期记忆、Working 的事实与经验、Coding 的经验和代码版本演化链
代码与文档 CodeGraph 回答仓库里符号在哪里、有哪些调用、修改影响哪里。Wiki 支持团队分发 Coding diff 链回答某次任务中的代码怎样演进、有哪些可复用经验。两者的图和链服务于不同查询
权限与部署 用户 Key、团队角色、资产可见性、ACL 与固定绑定。代理进入模型请求链路 来源采集、模型处理和宿主 Recall 分开授权。现有宿主仍可用原模型通道,选用外部记忆模型时会发送相关的授权内容。主分支另有 CLI/API 多用户能力,发行状态见下
开源许可 MIT Apache-2.0

腾讯云版架构 · Hy Memory SDK 架构 · MemHub 授权与宿主支持 · MemHub 隐私说明

五个常见 Agent 的接入方式

Agent 腾讯云版接入 MemHub v1.0.5
Codex 自定义 Responses provider。首次交互绑定文档要求 Plan 模式,v2.0.3 增加用户级默认绑定。桌面端实际会话、流式工具和会话恢复待测。 接入说明 扫描/Capture Hooks 写入,MCP + Skill 主动召回,无自动 Prefetch。 接入说明
独立 CodeBuddy 有 ~/.codebuddy/models.json 接法。文档注明 4.10.2–4.10.4 缺少 sessionId,无法完成初始化。独立 CLI/IDE 各自待测。 接入说明 支持矩阵仍未列独立 CodeBuddy。WorkBuddy 的插件格式名和目录覆盖变量不能证明独立版已接通。 支持矩阵
WorkBuddy Desktop 用 Responses,Web 用 Chat Completions。 接入说明 插件 Hook 可自动注入,另有 MCP + Skill 主动查询。 接入说明
Hermes 自定义上游走 Proxy,用请求头预选 Team/Agent/Task。 接入说明 v1.0.5 已提供原生只读 MemoryProvider 自动 Prefetch,并占用唯一的 memory.provider 槽位。会话写入仍由扫描负责。 接入说明
OpenCode v2.0.2 起有官方 provider 配置,记忆链路依赖适配器的路由标记。 接入说明 MCP 主动查询,DB 扫描/JS Capture Plugin 写入,无自动 Prefetch。 接入说明

个人跨机器共享同一份记忆

以一台 Mac 和一台 Windows 交替或并行工作为例。MemHub v1.0.5 有 macOS arm64 版本,Windows 无官方制品。若试 TencentDB,两端各开独立会话,在服务端绑定同一份项目记忆。并行试验用两个不同事实,再分别开新会话验证双向召回及落库时间。记忆的后台提炼有延迟,进行中的同一聊天、工作区文件和未提交改动需要各自的同步方式。 MemHub 平台支持 · Codex 会话标识

  • 常在线、自动采集和召回。 两者中优先验证 TencentDB。把一套服务放在常在线主机上,各设备通过 Tailscale 将受支持 Agent 的模型入口指向同一 Proxy,并使用同一个个人账号和同一个 Agent 绑定。这样,通过代理的会话可以汇入同一库,各设备也可以从同一库召回。未经过 Proxy 的历史需另行导入。主机或网络中断时,走这个入口的模型请求也受影响。 局域网/多机部署说明
  • 保留各机器原有模型通道或需要离线。 MemHub v1.0.5 的产品接入围绕本机 Runtime、loopback Broker 和单写锁设计。未核实多台 MemHub 自动双向合并记忆的产品能力。Hermes 可通过自行管理的 Hy Memory HTTP 服务做远程召回,但需要鉴权网关,且仅覆盖该接法。Nextcloud 可同步普通 Markdown 交接笔记,运行中的 MemHub 数据目录不适合作为多机共享目录。 MemHub Runtime 架构 · Hermes 远程直连接入边界

核验依据与适用场景

  • 版本。 腾讯云版公开最新 Release 为 v2.0.3(10 月 26 日),源码核验 2002196。MemHub 公开最新 Release 为 v1.0.5(10 月 28 日,绑定 SDK 2.0.8),有 macOS arm64 App/Runtime 和 Linux x64/arm64 Runtime。Hy Memory 主分支 2f9e028 已写 MemHub v1.0.6/SDK 2.0.14,但截至 11 月 5 日查询 GitHub Releases API 返回 404,远端也未见 v1.0.6 tag,因此按主分支能力记录。 腾讯云版 Release · MemHub 公开 Release · MemHub 主分支版本说明
  • 效果证据。 腾讯云版 README 自报 PersonaMem 从 48% 到 76%(+28 个百分点)。Hy Memory 提供 Chat 长期记忆和 CMB 编码记忆研究。现有资料不足以做同模型、同题集、同成本的横评,也没有本机延迟、记忆准确率或权限隔离实测。 腾讯云版 Benchmark · Hy Memory 论文与基准
  • 团队试点。 如果需要跨人共享项目上下文,可用一个脱敏仓库、一份文档、两个普通账号,先验证独立 CodeBuddy → 腾讯云 Proxy → 新 Codex 会话的跨工具接力,再检查未授权账号能否从面板、工具或注入结果读取同一资产,同时观察流式工具调用和 Proxy 异常时的行为。个人试用可先接 Codex 与 WorkBuddy,用少量历史检查召回、原文追溯和撤权。 MemHub 宿主接入说明

专精小模型清单与应用建议

适合交给专用模型的,通常是固定、重复、可评测的环节;复杂情况再交给规则、生成模型或人工处理。值不值得引入,取决于任务误差、人工复核成本和维护成本。“能本地运行”不等于 CPU 实时、中文可靠或可以直接商用。

资料范围与限制

这份清单来自单车酒吧搞机社第 76 集《Vibe知识大赏|别光追大模型,这12个专精小模型也许更有用》,页面显示发布时间为 2026-10-19,时长 7 分 45 秒。整理时读到视频标题、作者、章节和平台摘要,并回看了约 02:19 的“决策模型”画面及约 02:48 的 Kev、0.8B→4B 画面;没有完整核听,也没有逐字稿。下面的六类清单来自视频转述,再逐项核对官方仓库、模型卡、许可和关键许可讨论。

资料核对集中在 2026-10-21 至 2026-10-24。没有安装或下载模型权重,没有调用模型、跑基准,也没有更改任何现有检索索引。这里记录的是候选和采用边界,不是最新最优模型排名,也不构成本机性能结论。

原转述称这些模型大多支持本地部署、不少 CPU 可运行,并提醒商用前核对授权。本文保留为转述,不将其扩大成统一的硬件或许可承诺。

名称辨析:Kev 与 Jev

视频约 02:48 的画面写着 Kev、0.8B→4B。Kev 是独立的 Jev-like 决策模型项目,提供兼容 TypeSafe System One API 的接口及自行训练/运行的权重;本次核对的家族包括 0.8B、4B、9B 和 27B。它们支持加速器与 CPU 回退,官方也提供 Apple Silicon MLX 路线,但不能把所有尺寸笼统称为轻量 CPU 模型。Kev-0.8B 权重及底座标注 Apache-2.0;中文表现和实际规则判断仍需自测。原转述中的“K5”没有独立证据,不作为已确认型号或 Jev 的别名。

Jev 则是 TypeSafe 的 System One 模型:读取 state 与类型化问题,返回 Choice(选项)、Score(评分)或 Noul(是/否概率),由应用代码决定后续分支和动作。它不是完整的 agent runtime,也不负责文本生成、工具执行或任务编排。

在核对资料时,官方模型页给出托管 API;未找到可核验的公开权重或自托管安装路线,因此不能把 Jev 计为“已确认可本地 CPU 部署”。当时官方最新发布为 jev-1.14.0,但 jev-latest 仍指向 jev-1.13.0、jev-preview 指向 1.14;对照测试应固定版本。官方说明英语效果最好,中文需要实测,概率校准也不保证单次判断正确。官方列出的 1.13 已知问题包括算术、日期比较、多跳判断、对抗输入及选项顺序敏感;这些不能直接当成 1.14 的实测结论。

12 个候选

类别/对象 适用任务与边界 部署与许可要点
决策:Laya 对输入和候选选项做分类、分级或二元判断并输出概率;多语言任务应选对应检查点。 官方支持 CPU/MPS/CUDA 等;多语言版约 322M、英文版约 421M。多语言权重标注 Apache-2.0。模型卡提示默认未校准、可能过度自信,部分零样本任务较弱。
决策:Kev 基于 Qwen 底座加决策适配/读出,返回选项概率;提供兼容 Jev 的 TypeSafe System One API,但两者是独立项目。 本次核对的家族含 0.8B/4B/9B/27B,支持加速器与 CPU 回退,也有 Apple Silicon MLX 路线。Kev-0.8B 权重及底座标注 Apache-2.0;中文和领域判断须自测。
分类:SetFit 少样本文本分类训练框架;需选择句向量底座、标签和训练数据,不是一个开箱即用的统一分类模型。 小底座、低吞吐任务可从 CPU 试起;中文应选择合适的多语言/中文底座。框架为 Apache-2.0,底座、数据及产出模型许可需分别核对。
抽取:GLiNER 按给定标签抽取人名、地点、组织等实体,可作为抽取或脱敏候选;不保证理解复杂关系、否定或整份业务文档。 官方强调 CPU/消费级硬件,并支持量化与 ONNX;中文需选多语言版本验证。已核实的 gliner_multi-v2.1 标注 Apache-2.0;早期部分权重为 CC-BY-NC,不能只看代码仓库许可。
语言识别:OpenLID 基于 fastText 的语言识别,覆盖 201 种语言;不是实体或字段抽取模型。 适合轻量 CPU 文本预处理;短文本、中英混写和领域词仍需验证。模型卡标注 GPL-3.0;这不等于禁止商用,但集成或分发时仍需核对适用义务。
向量:BGE-small 将文本编码成检索向量;中文场景可明确评估 bge-small-zh-v1.5,不要只写含糊的 small。 可作为 CPU 检索候选;切块长度、索引规模和批量吞吐都要测。该权重标注 MIT。更换 embedding 通常需要重编码相应索引,不宜只为尝鲜就重建现有检索。
重排:Jina Reranker v2 对 query/候选文档对评分,用于召回后的排序;不能替代 embedding 向量模型。 官方提供 CPU 路径;逐候选计算,延迟随候选数和文本长度上升。多语言 v2 权重标注 CC-BY-NC-4.0,商业使用需另有授权或采用相应商业服务。
转写:SenseVoiceSmall 语音识别及语种/情绪/事件标签。已发布 Small 的明确语种为普通话、粤语、英语、日语和韩语;多人说话人分离需要另配模型或流水线。 有 ONNX 等本地路线;桌面 CPU 转写不能证明嵌入式端低延迟或低功耗。源码 MIT;官方权重卡指向 FunASR 自定义模型许可,商业解释仍有待确认,见下文。
合成:F5-TTS 文本转语音/参考声音条件合成,与转写方向相反。 可本地推理,但 CPU/MPS/GPU 的吞吐和实时率需实测。代码 MIT,官方预训练权重 CC-BY-NC-4.0;不能因代码许可宽松就默认权重可用于商业语音链路。
超分:Real-ESRGAN 图像放大与修复,可用于素材处理;生成的细节不能当成原图事实。 适合离线批处理;ncnn/Vulkan 路线依赖相应图形能力,不等于纯 CPU 加速。仓库 BSD-3-Clause;权重、附带组件和输入素材权利需分别核对。
修补:LaMa 按掩膜补全图像区域,区别于语言模型 LLaMA。 官方有 CPU 推理示例,但大图速度和内存仍需测。仓库 Apache-2.0;权重与素材也需分别核对,生成区域不应被当作实物证据。
预测:Chronos-Bolt 从历史时间序列预测未来值/分位数;不是通用因果推断或自动调度器。 官方支持 CPU/GPU;Tiny 9M、Mini 21M、Small 48M、Base 205M。该权重标注 Apache-2.0。应按时间回测并与季节性朴素预测等基线比较;后续已有 Chronos-2,但这不自动替换本清单的研究对象。

SenseVoice 的许可边界

官方权重卡指向 FunASR Model License:§2 授予使用、修改、分享等权利并要求署名/保留模型名,§3 则包含“仅作为参考和学习使用”的措辞。

README 链接了一条允许商用/私有微调的成员回复,但该回复顶部已有 2026-09-22 状态更新,说明早期回复不是许可方最终确认。重新打开说明也称仍在等待许可方/核心维护者确认;本次核对时 issue #334仍为 open。

因此,较准确的记录是:商业许可解释待确认,不标作“已禁止商用”,也不标作“已完全澄清”。 正式商业集成前,应结合所选权重版本、协议和许可方最终确认;第三方 GGUF/ONNX 转换物也要另行核对。

Jev:适合筛选,不替人做决定

2026-10-24 阅读的公众号文章认为,Jev 更适合把大量输入做成筛选、分类、排序和检查结果,而非替人承担“要不要写、要不要发”这类需要背景、经验和责任的最终决定。文章提出的工作流是:生成模型起草候选,Jev 过滤或检查,人决定是否采用。

文章及其引用的社区案例报告了多种速度和成本数字,例如语音浏览器约 300ms、单次约 0.0002 美元;724 条广告约 40 秒、约 0.09 美元;3,282 条历史帖子约 8 分 34 秒、约 0.13 美元。另有航班搜索约 7.1 秒、Uber 演示约 21 秒但停在付款选择页。这些是作者/社区报告,不是独立复现或统一基准:模型版本、并发、重试、网络和计费边界并不齐全,不能外推为通用性能承诺。

接入形式也要以官方资料为准:官方介绍说明 Jev 直接返回类型化结果,而不是生成文本;快速入门使用 typesafe-sdk/TypeSafeClient 的 system_one 调用,以 state 加 Choice、Score 或 Noul 提问。原文章中出现的 OpenAI(base_url=...) + chat.completions + 自定义 JSON 输出,与本次读取的官方接入方式不一致,不能直接当作官方可运行示例。本次没有运行这些代码或调用 API。

原文章小标题称“12 个案例”,正文实际编号到 10;聊天案例还同时出现“约 1 秒”和“70 毫秒”两种判断耗时,测量口径不清。Uber 演示没有完成叫车,仿真也不能代表真实控制;快速判断更不能代替原始来源核验或证明事实正确。

怎样开始评估

  1. 资料结构化:先测抽取,再决定是否加分类。 用有代表性的样本副本提取日期、型号、组织和待确认事项,保留原文位置;复杂否定、责任和历史修正交由人或更强的模型复核。固定格式和编号优先考虑规则;只有分类量和标签样本确实存在时,再评估 SetFit。留出测试集,按字段报告精确率和召回率。
  2. 离线语音转写:先量化人工修订负担。 多人分离、标点和领域词汇可能还需额外处理。开放环境还要单独验证拾音、风噪和回声;桌面 CPU 转写不能证明嵌入式设备可实时、低功耗运行。F5-TTS 默认权重不应在商业许可未收敛前直接纳入商业语音链路。
  3. 检索:先评估现有结果,再决定是否换模型。 若中文召回确有不足,用小规模副本和固定问答集比较 BGE-small-zh;重排则单独衡量排序增益与额外延迟。更换 embedding 前应确认重编码成本;Jina v2 的商业授权也需先解决。
  4. 图像工具按需使用,预测放后。 Real-ESRGAN/LaMa 可用于有权处理的资料图片或演示素材,不必常驻服务。等积累足够时间序列后,再将 Chronos-Bolt 与简单预测基线比较;预训练模型不能补出不存在的运营数据。

若把决策模型用于工单或任务路由,应先与规则/标签基线比较,保留低把握和异常输入的人工复核;在真实领域数据上验证、校准后,再讨论概率阈值。把一个大问题拆成单一、可评估的 Choice/Score/Noul 判断,规则负责确定性判断,复杂任务交给生成模型或人工处理。

支付、权限变更、发布以及其他不可逆或安全关键动作,模型只能提供候选或风险信号,不能替代确定性授权和人的最终确认。模型组件可以串接在脚本或工具流程中;能组合不代表必须部署一个总控平台,现有工具接口足够时直接复用即可。

SenseVoice 架构示意

SenseVoice 官方架构示意。图中展示 Small/Large 和家族标签;不能把全部语言标签都视为已发布 Small 的能力列表。

图片来源:FunAudioLLM/SenseVoice。该图用于说明专用语音组件,不代表整份模型清单。

基于Adafruit ESP32-S3 TFT Feather的网络应用开发实践

image

视频链接:http://training.eeworld.com.cn/video/37657

源码下载:http://download.eeworld.com.cn/detail/%E5%9B%A7%E5%A4%A7%E5%A4%A7%E7%8E%8B/629208

项目介绍

该项目是基于Adafruit ESP32-S3 TFT Feather开发板的一个简单的示例,通过该示例可以学习到如何使用Adafruit ESP32-S3 TFT Feather开发板的WiFi、GPIO控制及以Neopixel LED控制等功能。

该项目完成了"Follow me活动”第2期的任务1、任务2、任务3以及任务4的分任务1。

功能介绍

  • 基于LVGL的多页面图形界面
  • 内置 OPPOSans 字体,支持大部分常见汉字及符号的显示
  • WiFi热点网页配网
  • 日期时间显示,支持网络时钟同步
  • 通过网络获取天气信息
  • 板载NeoPixel LED控制,呼吸灯闪烁,支持通过按键更换闪烁颜色

在尚未进行配网时启动板卡会自动启动AP热点,通过手机连接该热点会启动CaptivePortal网页引导进行配网,配网成功后会自动关闭AP热点并连接到指定的WiFi SSID。

配网完成后首页会显示已连接的WiFi信息以及IP地址,屏幕上方状态栏会显示日期、时间以及天气信息。

点击屏幕左侧的Boot0按钮可切换显示页面,第二个页面是LED控制页面。进入LED控制页面会自动点亮Neopixel LED呼吸灯,在该页面长按Boot0可控制板载的Neopixel LED的颜色切换,颜色可在红绿蓝三种颜色之间进行切换,离开此页面会自动熄灭Neopixel LED。

内置AP默认连接信息:

  • SSID: https://hessian.cn/followme2
  • 密码: followme2

连接信息可通过menuconfig进行修改

Captive Portal 配网界面效果 image

Follow me活动介绍

"Follow me活动”是DigiKey联合EEWORLD发起的为期一年的“跟技术大咖学技术,完成任务返现”活动。2023年共有4期,每3个月技术大咖推荐可玩性与可学性较强的开发板/仪器套件,带着大家实际操作。

第2期活动任务要求

任务1:控制屏幕显示中文(必做任务)

完成屏幕的控制,并且能显示中文

任务2:网络功能使用(必做任务)

完成网络功能的使用,能够创建热点和连接到WiFi

任务3:控制WS2812B(必做任务)

使用按键控制板载Neopixel LED的显示和颜色切换

任务4:从下方5个分任务中选择1个感兴趣的完成即可(必做任务)

分任务1:日历&时钟——完成一个可通过互联网更新的万年历时钟,并显示当地的天气信息 分任务2:WS2812B效果控制——完成一个Neopixel(12灯珠或以上)控制器,通过按键和屏幕切换展示效果 分任务3:数据检测与记录——按一定时间间隔连续记录温度/亮度信息,保存到SD卡,并可以通过按键调用查看之前的信息,并在屏幕上绘图 分任务4:音乐播放功能——实现音乐播放器功能,可以在屏幕上显示列表信息和音乐信息,使用按键进行切换,使用扬声器进行播放 分任务5:AI功能应用——结合运动传感器,完成手势识别功能,至少要识别三种手势(如水平左右、前后、垂直上下、水平画圈、垂直画圈,或者更复杂手势

任务5:通过网络控制WS2812B(可选任务,非必做)

结合123,在手机上通过网络控制板载Neopixel LED的显示和颜色切换,屏幕同步显示状态

Adafruit ESP32-S3 TFT Feather简介

image Adafruit ESP32-S3 TFT Feather是由开源硬件行业知名公司Adafruit出品的一款富有特色的开源硬件,开发板使用乐鑫ESP32-S3芯片,支持WiFi和蓝牙能,自带高清TFT彩色显示屏。

程序实现

该项目使用C语言基于ESP-IDF进行开发。项目实现参考了ESP-Box-Lite的代码,自行封装了BSP组件,主要封装了板载TFT LCD(1.14" ST7789)的初始化。 目前该组件已上传至Github: BSP: Adafruit ESP32-S3 TFT-Feather

项目依赖

## IDF Component Manager Manifest File
dependencies:
  idf: ">=5.1"

  lvgl/lvgl: "8.3.8"
  espressif/esp_lvgl_port: "1.2.0"
  espressif/qrcode: ^0.1.0
  espressif/json_generator: ^1
  espressif/json_parser: =1.0.0
  espressif/led_strip: "^2.4.3"

目录结构 image

分区表

# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
# Name,   Type, SubType, Offset,  Size, Flags
sec_cert, data, ,        0xd000,  0x3000,
nvs,      data, nvs,     ,        0x6000,
otadata,  data, ota,     ,        0x2000,
fctry,    data, nvs,     ,        0x6000,
ota_0,    app,  ota_0,   ,        3M,
storage,  data, spiffs,  ,        500K,

联网功能处理

esp_err_t app_wifi_start(void)
{
    ESP_LOGD(TAG, "app_wifi_start() ENTER");

    wifi_init_sta();

    // 检查是否已配网成功
    esp_err_t err = wifi_prov_mgr_is_provisioned(&provisioned);
    ESP_ERROR_CHECK(err);

    ESP_LOGD(TAG, "isProvisioned %d", provisioned);

    // 未配网启动AP,否则启动STA
    if (!provisioned) {
        // 启动AP
        wifi_start_softap();

        // 启动CaptivePortal配网服务
        start_captive_portal();

        // 启动DNS服务器,并重定向所有域名查询到AP的IP地址
        dns_server_config_t config = DNS_SERVER_CONFIG_SINGLE("*" /* all A queries */, "WIFI_AP_DEF" /* softAP netif ID */);
        dns_server = start_dns_server(&config);

    } else {
        // 获取网络配置(已配网会自动从NVS加载)
        wifi_config_t config;
        esp_wifi_get_config(WIFI_IF_STA, &config);

        ESP_LOGD(TAG, "WIFI_IF_STA SSID %s / Password: %s", config.sta.ssid, config.sta.password);

        // 启动STA
        wifi_start_sta();
    };

    // 阻塞等待网络连接就绪
    xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_EVENT, false, true, portMAX_DELAY);

    ESP_LOGD(TAG, "app_wifi_start() WAIT_WIFI_CONNECT ---> OK");

    // 启动网络时间同步客户端开始同步时间
    ESP_LOGD(TAG, "app_wifi_start() APP SNTP INIT");
    app_sntp_init();

    return ESP_OK;
}

Captive Portal

Captive Portal 中文通常译作“强制主页”或“强制登录门户”。是一个登录Web页面,通常由网络运营商或网关在用户能够正常访问互联网之前拦截用户的请求并将一个强制登录或认证主页呈现(通常是通过浏览器)给用户。该页面可能要求用户输入认证信息、支付、接受某些条款或者其他用户授权等,随后用户才能被授权访问互联网。该技术广泛用于移动和个人宽带服务,包括有线电视、商业WiFi、家庭热点等,也可用于访问企业和住宅区有线网络。

在这个项目中,CaptivePortal是以ESP官方的CaptivePortal demo为基础进行实现的,为了简化开发过程,CaptivePortal中的web静态文件存储到了SPIFF中。

因为Adafruit ESP32-S3 TFT Feathe只搭载了4MB的flash,空间比较有限,我只给SPIFFS分了500K的空间,因此为了平衡体验与空间利用率,WEB 端没有采用像Bootstrap这样的前端框架,而是采用milligram.css + zepto.js 的实现,二者均是针对轻量化进行设计。

加载SPIFFS文件逻辑

// HTTP Error (404) Handler - Redirects all requests to the root page
esp_err_t http_404_error_handler(httpd_req_t *req, httpd_err_code_t err)
{
    char filename[HTTPD_MAX_URI_LEN+10];
    sprintf(filename, CONFIG_BSP_SPIFFS_MOUNT_POINT "%s", req->uri);

    return send_file_response(req, filename);
}

httpd_handle_t start_captive_portal(void)
{
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.max_open_sockets = CONFIG_LWIP_MAX_SOCKETS - 3;
    config.lru_purge_enable = true;
    config.max_resp_headers = 20;

    // Start the httpd server
    ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
    if (httpd_start(&server, &config) == ESP_OK) {
        // Set URI handlers
        ESP_LOGI(TAG, "Registering URI handlers");
        httpd_register_uri_handler(server, &root_action);
        httpd_register_uri_handler(server, &generate204_action);
        httpd_register_uri_handler(server, &generate_204_action);
        httpd_register_uri_handler(server, &config_action);
        httpd_register_uri_handler(server, &save_action);
        httpd_register_uri_handler(server, &wifi_scan_action);
        httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, http_404_error_handler);
    }
    return server;
}
static esp_err_t send_file_response(httpd_req_t *req, char* filename)
{
    esp_err_t ret = ESP_OK;
    FILE *fp = NULL;
    char *buf = NULL;
    size_t read_len = 0;

    fp = fopen(filename, "rb");

    ESP_GOTO_ON_FALSE(fp != NULL, ESP_ERR_NOT_FOUND, err, TAG, "Failed to open file %s", filename);

    buf = calloc(HTML_BUF_SIZE, sizeof(char));

    if (NULL == buf) {
        ESP_LOGE(TAG, "Failed to allocate memory for buf");
        return ESP_ERR_NO_MEM;
    }

    if (file_ext_cmp(filename, "js")) {
        httpd_resp_set_type(req, "text/javascript");
    } else if (file_ext_cmp(filename, "css")) {
        httpd_resp_set_type(req, "text/css");
    } else {
        httpd_resp_set_type(req, "text/html");
    }

    while ((read_len = fread(buf, sizeof(char), HTML_BUF_SIZE, fp)) > 0) {
        httpd_resp_send_chunk(req, buf, read_len);
    }

    ESP_GOTO_ON_FALSE(feof(fp), ESP_FAIL, err, TAG, "Failed to read file %s error: %d", filename, ferror(fp));

    // chunks send finish
    httpd_resp_send_chunk(req, NULL, 0);

    err:
    if (fp != NULL) {
        fclose(fp);
    }
    if (buf != NULL) {
        free(buf);
    }

    if (ret == ESP_ERR_NOT_FOUND) {
        ret = httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File does not exist");
    }

    return ret;
}

中文字体处理

LVGL内置的CJK宋体对一些符号和汉字的支持也不够,经常有吞字的情况。于是需要自行增加一个字体,通过查询字符表之后用以下命令转成c代码就可以嵌入到我们的工程里。

lv_font_conv --font ./OPPOSans_L.ttf -r 0x20-0x7F -r 0x2100-0x214F -r 0x3000-0x303F -r 0x4E00-0x9FFF -r 0xFE50-0xFE6F  --size 16 --format lvgl --bpp 4 --no-compress -o ~/workspace/esp/esp-followme2/main/gui/font/font_OPPOSans_L_16.c

这里我用的是OPPOSans字体16像素大小,指定了常用的ASCII字符、中英文符号和中文字符等范围,足以满足日常文本内容显示的需求。

  • 字符范围查询:https://jrgraphix.net/r/Unicode/
  • LVGL在线图标转换:https://lvgl.io/tools/imageconverter
  • LVGL字体转换程序:https://github.com/lvgl/lv_font_conv

使用方法

LV_FONT_DECLARE(font_OPPOSans_L_16);

lv_obj_t * lab_net_state = lv_label_create(parent);
lv_label_set_recolor(lab_net_state, true);
lv_obj_set_style_text_font(lab_net_state, font_OPPOSans_L_16, LV_PART_MAIN);

NeoPixel LED

开发板上搭载了一颗WS2812可寻址LED。在ESP-IDF环境中,我们可以使用官方组件库中的led_strip进行操作,免去自行编写驱动的过程。

led_strip组件: https://components.espressif.com/components/espressif/led_strip

这里要注意,这个LED的电源是直接由GPIO34提供的,使用前需要先拉高GPIO34进行供电。


#define NEOPIX_GPIO GPIO_NUM_33
#define NEOPIX_PWR_GPIO GPIO_NUM_34


static inline void led_breath(uint32_t *color)
{
    if (led_dir) {
        (*color) ++;
        if (*color == 0xff) {
            led_dir = false;
        }
    } else {
        (*color) --;
        if (*color == 0) {
            led_dir = true;
        }
    }
}

static void update_led_task(void *arg)
{
    switch (led_color) {
        case 0:
            led_breath(&r);
            break;
        case 1:
            led_breath(&g);
            break;
        case 2:
            led_breath(&b);
            break;
    }
    led_strip_set_pixel(led_strip, 0, r, g, b);
    led_strip_refresh(led_strip);
}

static void page_led_init(void)
{
    esp_err_t err;
    if (!gpio_initialized) {
        gpio_config_t io_conf = {
                .mode = GPIO_MODE_OUTPUT,
                .pin_bit_mask = (1ULL<<NEOPIX_PWR_GPIO),
                .intr_type = GPIO_INTR_DISABLE,
                .pull_down_en = 0,
                .pull_up_en = 0,
        };
        err = gpio_config(&io_conf);
        if (err != ESP_OK) {
            ESP_LOGE(TAG, "configure GPIO for NEOPIX_PWR failed");
        }
        gpio_initialized = true;
    }

    err = gpio_set_level(NEOPIX_PWR_GPIO, 1);

    if (err != ESP_OK) {
        ESP_LOGE(TAG, "gpio_set_level GPIO for NEOPIX_PWR failed");
    }

    ESP_LOGI(TAG, "NEOPIX_PWR ON");


/* LED strip initialization with the GPIO and pixels number*/
    led_strip_config_t strip_config = {
            .strip_gpio_num = NEOPIX_GPIO, // The GPIO that connected to the LED strip's data line
            .max_leds = 1, // The number of LEDs in the strip,
            .led_pixel_format = LED_PIXEL_FORMAT_GRB, // Pixel format of your LED strip
            .led_model = LED_MODEL_WS2812, // LED strip model
            .flags.invert_out = false, // whether to invert the output signal (useful when your hardware has a level inverter)
    };

    led_strip_rmt_config_t rmt_config = {
            .clk_src = RMT_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption
            .resolution_hz = 10 * 1000 * 1000, // 10MHz
            .flags.with_dma = false, // whether to enable the DMA feature
    };

    ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));


    const esp_timer_create_args_t periodic_timer_args = {
            .callback = &update_led_task,
            /* name is optional, but may help identify the timer when debugging */
            .name = "periodic"
    };

    ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
}

// LED 控制页面渲染逻辑
void page_led_render(lv_obj_t *parent)
{
    ESP_LOGD(TAG, "render start");

    if (parent == NULL) {
        return;
    }

    page_led_init();

    lv_obj_t *lab_text = lv_label_create(parent);
    lv_label_set_recolor(lab_text, true);
    lv_obj_set_style_text_font(lab_text, main_font, LV_PART_MAIN);
    lv_obj_set_align(lab_text, LV_ALIGN_CENTER);
    lv_label_set_text_fmt(lab_text, "#F2C161 LED控制#\n#CCCCCC 长按Boot0切换LED颜色#");

    // 开始呼吸灯 1000us = 1ms
    ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, 1000));

    ESP_LOGD(TAG, "render end");
}


参考资料

  • FreeRTOS官方文档:https://www.freertos.org/zh-cn-cmn-s/freertos-core/overview.html
  • ESP-IDF官方文档:https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/getstarted/index.html
  • Unicode字符范围表:https://jrgraphix.net/r/Unicode/

彻底删除Docker Desktop for Mac

Docker Desktop for Mac 4.6 增加了一个新特性VirtioFS(戳这里看Docker官方Blog介绍),虽然截止目前(4.7版本)为止还是实验性的(Experimental),但是98%的文件读写性能提升还是太有诱惑力了。

昨天手欠开了一下想尝试一番,结果点击Apply之后就一直转圈圈,等了2个钟头之后我放弃了。然后重启Docker,发现无法启动,这下就懵了。

Docker Desktop stopped

通过查看日志发现com.docker.virtualization报错:Invalid virtual machine configuration.

无奈之下只好选择重新安装。

结果不管是删除后重新安装还是降级到4.5,还是一样的问题。始终无法正常启动,猜测应该是有配置没删干净。期间尝试各种姿势删除Docker,包括官方提供的命令行删除也没有起作用。

最后经过一番查找分析,得出以下命令:

sudo rm -rf /Applications/Docker.app 
sudo rm -rf /private/var/folders/0y/frd89c5s3yx4pg6fjbn9d8j00000gn/C/com.electron.dockerdesktop 
rm -rf ~/Library/Containers/com.docker.docker 
rm -rf ~/Library/Application\ Support/Docker\ Desktop 
rm -rf ~/Library/Saved\ Application\ State/com.electron.dockerdesktop.savedState 
rm -rf ~/Library/HTTPStorages/com.docker.docker 
rm -rf ~/Library/Caches/com.docker.docker 
rm -rf ~/Library/Group\ Containers/group.com.docker 
rm -rf ~/Library/Application\ Support/com.bugsnag.Bugsnag/com.docker.docker

注意第二行的路径需要自己查找一下,中间部分会有区别。