1. 从一个折腾的下午说起为什么要让 AI 自己看功耗计去年冬天我在调一块低功耗传感器板子目标是让它在纽扣电池上撑够两年。理论上算下来休眠电流应该压在 8 微安以内可实测数据总是飘一会儿 12 微安一会儿又跳到 30 微安。问题在于我手上那台 IoT Power 功耗计虽然能记录电流曲线但每次改完固件、烧录、跑测试、导出 CSV、再拿脚本分析这一套流程走下来少说十几分钟一天下来光在看数据这件事上就耗掉大半天。更烦的是功耗异常往往出现在某个特定的时间窗口——比如射频模块唤醒的那 200 毫秒或者某个外设在特定状态下没进休眠。这些瞬态尖峰靠人眼盯曲线基本抓不住得写脚本去筛。可每换一个测试场景脚本就得改一遍改到最后我自己都记不清哪个版本对应哪次实验。后来我接触到 MCPModel Context Protocol也就是模型上下文协议思路一下子打开了。MCP 本质上是一套让 AI 模型能够调用外部工具、读取外部资源的标准化协议。它把AI 能做什么和外部世界有什么解耦开来AI 负责理解和决策MCP 服务端负责把真实世界的设备能力暴露成 AI 能调用的工具。那我完全可以给 IoT Power 写一个 MCP 服务端让 AI 直接去读功耗计的实时数据、去筛选异常区间、去对比不同固件版本的功耗表现。这篇文章就是把这个想法落地的完整记录。我会讲清楚 MCP 服务端到底是怎么回事、IoT Power 这类功耗计的数据怎么接进来、工具函数怎么设计、AI 调用时有哪些坑。适合已经有一点嵌入式或 IoT 测试经验、想把手上的仪器接进 AI 工作流的同学。哪怕你用的是别的功耗计、别的数据采集设备这套思路也能直接迁移。2. 先搞明白 MCP 服务端到底在干什么2.1 MCP 协议的核心抽象工具、资源、提示很多人第一次听到 MCP 会以为是什么高深的模型内部机制其实它非常朴素。你可以把它理解成一份AI 和外部程序之间的接口约定。这份约定里主要有三类东西工具ToolsAI 可以主动调用的函数。比如read_power_now()读当前功耗list_capture_sessions()列出所有采集会话。工具是有副作用的、需要参数的、会返回结果的。资源ResourcesAI 可以读取的数据。比如某次采集的原始电流曲线、某个固件版本的功耗报告。资源通常是只读的、通过 URI 定位的。提示Prompts预定义的提示模板让用户或 AI 快速发起某类任务。比如分析这次采集里所有超过阈值的尖峰。这三者的分工很关键。我一开始图省事把所有能力都塞进工具里结果 AI 每次分析曲线都要先调一个工具把整条曲线拉回来token 消耗巨大还容易超上下文。后来把原始曲线改成资源AI 只在需要时按 URI 读取片段效率立刻上来了。提示工具适合动作资源适合数据。分不清的时候问自己一句——这个操作会不会改变外部状态会就是工具不会就是资源。2.2 为什么是服务端而不是插件有同学会问直接写个 Python 脚本让 AI 调用不就行了为什么要搞个服务端这里有个实际考量。功耗测试往往是长周期的一次采集可能跑几个小时甚至过夜。如果 AI 和采集程序在同一个进程里AI 一崩采集就断了。做成独立的服务端之后采集进程稳定跑着AI 只是通过协议来查询两者生命周期解耦。另一个原因是复用。我实验室里不止一台功耗计还有电子负载、温箱、示波器。如果每台设备都写一套 AI 对接逻辑维护成本爆炸。统一走 MCP 服务端之后AI 侧只需要知道有这么几个工具可用底层换设备、加设备都不用改 AI 的调用逻辑。从架构上看典型的部署是这样IoT Power 通过 USB 或串口连到一台常开的采集主机采集主机上跑着数据落库程序和一个 MCP 服务端。AI 客户端可以是桌面应用、也可以是命令行 agent通过标准输入输出或本地网络连到这个服务端。整个链路里AI 永远不直接碰硬件只碰服务端暴露的抽象接口。2.3 数据流的关键设计采样、落库、查询三段分离我在实际做的时候踩过一个坑一开始让 MCP 服务端直接去读功耗计的串口。结果 AI 一发起查询服务端就去抢占串口采集程序那边就丢数据。后来改成三段分离采样层一个常驻进程独占串口以固定频率我用的 1kHz读取电流电压写入本地时序数据库。存储层时序数据库按会话session组织数据每个会话对应一次固件版本加一组测试条件。查询层MCP 服务端只读数据库永远不碰硬件。这个分离带来的好处是AI 查询再频繁也不会影响采集质量。而且历史数据可以反复分析不用重新跑实验。代价是多了一个数据库组件但用 SQLite 加时间索引就够用了单机场景完全扛得住。3. 把 IoT Power 的数据接进来采样与存储实操3.1 采样频率与量程的选择逻辑功耗计的参数选择直接决定后面 AI 能不能分析出有用结论。这里给几个我实测下来比较稳的配置思路。采样频率方面如果你只关心平均功耗和休眠电流100Hz 足够。但如果要抓射频唤醒、外设瞬态这类毫秒级事件至少得上 1kHz我一般用 2kHz 留余量。频率再高数据量会爆炸——2kHz 跑一小时就是 720 万个采样点SQLite 单表就有点吃力了得考虑分表或降采样存储。量程方面IoT Power 这类设备通常有多个电流档位。低功耗场景一定要用最小档位否则小电流的分辨率不够。我遇到过 8 微安和 12 微安分不出来的情况就是因为档位选大了ADC 的有效位数被浪费掉。切换档位时会有短暂的量程切换毛刺分析时要记得把这几十毫秒的数据标记为无效。测试场景建议采样率建议档位单小时数据量休眠电流评估100Hz最小档约 36 万点射频瞬态分析2kHz中档约 720 万点整机平均功耗10Hz自动约 3.6 万点电池续航预估1Hz自动约 3600 点3.2 落库表结构设计数据库表我设计得很简单但有几个字段是必须的缺了后面 AI 分析会很痛苦。CREATE TABLE sessions ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, firmware_version TEXT, test_condition TEXT, started_at INTEGER NOT NULL, ended_at INTEGER, sample_rate INTEGER NOT NULL, current_range TEXT ); CREATE TABLE samples ( session_id INTEGER NOT NULL, ts INTEGER NOT NULL, current_ua REAL NOT NULL, voltage_mv REAL, valid INTEGER DEFAULT 1 ); CREATE INDEX idx_samples_session_ts ON samples(session_id, ts);几个设计要点值得展开说。firmware_version和test_condition这两个字段是我后来加的一开始没加结果 AI 分析时没法把功耗差异归因到具体改动上只能干巴巴地说这次比上次高。加上之后AI 就能说出v1.2.3 相比 v1.2.2 在射频唤醒阶段平均电流高了 15%这种结论才有指导意义。valid字段用来标记量程切换、设备重连等异常区间的数据。AI 分析时默认只读valid1的点避免被毛刺误导。这个字段看起来不起眼但没有它AI 会把量程切换的尖峰当成真实的功耗异常给出完全错误的结论。3.3 采集进程的稳定性处理采集进程要能扛住各种意外。我踩过的坑包括USB 线接触不良导致串口断开、功耗计固件偶发死机、主机休眠导致采集中断。处理方式是在采集进程里加心跳检测和自动重连。def acquire_loop(device, db, session_id): while running: try: frame device.read_frame(timeout1.0) if frame is None: reconnect(device) continue db.insert_sample(session_id, frame.ts, frame.current_ua, frame.voltage_mv, validframe.valid) except DeviceError: mark_invalid_window(db, session_id, window_ms200) reconnect(device)注意mark_invalid_window这一步。设备重连期间的数据是缺失的如果不标记AI 会以为那段时间功耗为零得出设备进入了超低功耗状态的荒谬结论。把重连前后的 200 毫秒标记为无效是保证数据可信的关键细节。4. MCP 服务端的工具与资源设计4.1 工具函数清单与参数设计工具设计的原则是粒度适中参数明确返回结构化。我最终定下来的工具集大概是这些list_sessions(limit, firmware_version)列出采集会话支持按固件版本过滤。get_session_summary(session_id)返回某会话的平均电流、峰值、最小值、时长。find_current_spikes(session_id, threshold_ua, min_duration_ms)找出超过阈值的电流尖峰。compare_sessions(session_id_a, session_id_b, metric)对比两个会话的指定指标。get_power_profile(session_id, start_ts, end_ts, bucket_ms)返回指定时间窗口的降采样曲线。参数设计上有个经验阈值类参数一定要带单位后缀。我一开始写的是thresholdAI 调用时传了个 100我以为是微安结果它理解成毫安找出来的尖峰全是噪声。改成threshold_ua之后AI 再也没传错过。这种命名上的小细节能省掉大量调试时间。返回结构统一用 JSON字段名也带单位。比如{avg_current_ua: 8.3, peak_current_ua: 45200, duration_ms: 3600000}。AI 拿到这种结构几乎不会误读。4.2 资源 URI 的设计资源这块我用 URI 来定位数据格式是power://session/{id}/raw和power://session/{id}/summary。AI 需要原始曲线时读 raw需要概览时读 summary。这里有个坑原始曲线可能几百万个点直接作为资源返回会撑爆上下文。我的做法是 raw 资源默认只返回前 10000 个点并在返回里带上truncated: true和total_points字段。AI 看到截断标记后会改用get_power_profile工具去取降采样版本。这个资源有上限、工具做聚合的分工是我试了好几版才定下来的。4.3 提示模板的实用化提示模板我做了三个都是高频场景analyze_sleep_current分析休眠电流是否达标给出与目标值的差距。diagnose_spike针对某个尖峰结合时间戳分析可能的原因。regression_check对比新旧固件判断是否有功耗回归。模板里预置了分析步骤和输出格式AI 调用后能直接产出结构化报告。这比让用户每次手打一长串提示要省事得多也保证了分析口径的一致性。5. 完整实操从零跑通一次 AI 功耗分析5.1 环境准备与依赖我用的技术栈是 Python 加 SQLite 加官方 MCP SDK。依赖不多但版本要对齐。pip install mcp pyserialMCP SDK 的版本更新比较快接口偶有变动建议锁定一个稳定版本。我用的版本在工具注册的装饰器写法上和早期版本有差异如果你照抄网上的老教程发现跑不起来先检查 SDK 版本。采集主机上要确保串口权限正确Linux 下把用户加进dialout组Windows 下确认驱动装好、端口号没被占用。这些基础问题看似简单但我见过太多人卡在设备连不上上耗掉一整天。5.2 服务端骨架代码服务端主体结构如下我做了精简保留核心逻辑from mcp.server import Server from mcp.server.stdio import stdio_server import sqlite3 app Server(iot-power-mcp) DB power.db app.tool() def list_sessions(limit: int 20, firmware_version: str None) - list: conn sqlite3.connect(DB) sql SELECT id, name, firmware_version, started_at FROM sessions params [] if firmware_version: sql WHERE firmware_version ? params.append(firmware_version) sql ORDER BY started_at DESC LIMIT ? params.append(limit) rows conn.execute(sql, params).fetchall() conn.close() return [{id: r[0], name: r[1], fw: r[2], started_at: r[3]} for r in rows] app.tool() def get_session_summary(session_id: int) - dict: conn sqlite3.connect(DB) row conn.execute( SELECT AVG(current_ua), MAX(current_ua), MIN(current_ua), COUNT(*), (MAX(ts)-MIN(ts)) FROM samples WHERE session_id ? AND valid 1, (session_id,) ).fetchone() conn.close() return { avg_current_ua: round(row[0], 2), peak_current_ua: round(row[1], 2), min_current_ua: round(row[2], 2), sample_count: row[3], duration_ms: row[4] } async def main(): async with stdio_server() as (read, write): await app.run(read, write, app.create_initialization_options()) if __name__ __main__: import asyncio asyncio.run(main())这段代码里app.tool()装饰器把普通函数注册成 MCP 工具类型注解会被自动转成工具的输入 schema。所以注解一定要写全、写准AI 是靠这个 schema 来决定怎么传参的。5.3 一次真实的 AI 分析过程环境跑通后我在 AI 客户端里发了一句帮我看看 session 42 的休眠电流有没有达标目标是 8 微安。AI 的调用链是这样的先调get_session_summary(42)拿到平均电流 9.7 微安、峰值 45 毫安、最小值 6.2 微安。然后它判断平均值超标接着调find_current_spikes(42, threshold_ua1000, min_duration_ms1)发现有三个持续 2 到 5 毫秒的尖峰分别在采集开始后第 12 分钟、第 34 分钟、第 51 分钟。AI 接着调get_power_profile(42, start_ts, end_ts, bucket_ms10)把这三个尖峰附近 200 毫秒的曲线拉出来发现尖峰形态一致都是先冲高再缓慢回落。它给出的结论是这三个尖峰很可能是同一个周期性任务触发的建议检查定时器周期和射频唤醒逻辑。整个过程我一句话没多说AI 自己完成了从概览到定位到归因的完整链路。这就是 MCP 服务端的价值——把仪器的能力变成 AI 的感官。5.4 参数计算怎么定尖峰阈值find_current_spikes的阈值不是拍脑袋定的。我的做法是先算基线取会话里电流的中位数作为基线然后阈值设为基线的 10 倍或者绝对值的 1 毫安取两者较大值。def auto_threshold(session_id): conn sqlite3.connect(DB) row conn.execute( SELECT current_ua FROM samples WHERE session_id? AND valid1 ORDER BY current_ua LIMIT 1 OFFSET (SELECT COUNT(*)/2 FROM samples WHERE session_id? AND valid1), (session_id, session_id) ).fetchone() conn.close() median row[0] return max(median * 10, 1000)用中位数而不是平均值是因为平均值会被尖峰本身拉高导致阈值定得过高、漏掉真实异常。这个细节我在第一版里没注意结果 AI 老是报未发现异常其实异常就在眼前。6. 踩坑记录与常见问题排查6.1 工具调用超时与大数据量返回最常见的坑是工具返回数据太大导致超时。我一开始让get_power_profile不限制返回点数AI 要一小时的数据就返回几百万个点序列化加传输直接卡死。解决办法是强制降采样工具内部根据请求的时间窗口自动计算 bucket 大小保证返回点数不超过 5000。如果用户或 AI 要更细的粒度就缩小时间窗口再请求。这个窗口换精度的策略比单纯限制点数更符合分析习惯。6.2 AI 误读单位与量纲前面提过阈值单位的问题这里再补充一个。电流值我用微安存储但 AI 有时会在报告里写成毫安。根源是返回字段名不够明确。后来我把所有返回字段都带上单位后缀avg_current_ua、peak_current_uaAI 就再没搞错过。字段命名这件事多花五分钟省下五小时。6.3 会话对比时的条件不一致compare_sessions最容易出的问题是拿两个测试条件不同的会话去比。比如一个跑在 25 度、一个跑在 60 度功耗差异可能全来自温度。我的处理是在对比工具里先检查两个会话的test_condition是否一致不一致就返回警告让 AI 知道这个对比有前提。问题现象可能原因排查方向AI 报未发现异常阈值定得过高检查基线计算是否用了中位数工具调用超时返回数据量过大加降采样限制返回点数单位读错字段名无单位后缀统一加_ua、_mv后缀对比结论不可信测试条件不一致对比前校验 test_condition数据有零值段设备重连未标记检查 valid 字段标记逻辑6.4 采集与查询的并发冲突如果偷懒让 MCP 服务端直接读串口并发冲突几乎必然发生。SQLite 在并发读写时也可能报database is locked。我的做法是采集进程用 WAL 模式写库查询进程只读并且设置合理的 busy timeout。PRAGMA journal_modeWAL; PRAGMA busy_timeout5000;WAL 模式下读写可以并发查询不会阻塞采集。这个设置对长周期采集特别重要否则每次 AI 查询都可能让采集丢一小段数据。7. 这套方案还能怎么扩展跑通基础版本之后我陆续加了几个扩展都挺实用。一个是自动回归检测。每次新固件采集完服务端自动触发一次与基线会话的对比如果平均功耗或峰值超标就在会话记录里打上标记。AI 查询时能直接看到哪些版本有回归。另一个是多设备聚合。实验室里几台功耗计同时跑不同板子服务端按设备 ID 分区存储AI 可以跨设备对比同一固件在不同硬件上的表现。这个在选型阶段特别有用。还有就是与固件仓库联动。把会话的firmware_version和代码仓库的 commit 关联起来AI 分析出功耗回归后能直接定位到是哪次提交引入的。这一步需要额外的集成工作但对定位问题帮助巨大。我个人在实际操作中的体会是MCP 服务端的价值不在于让 AI 更聪明而在于把仪器的数据变成 AI 能理解的结构化信息。工具和资源的边界划清楚、字段命名带单位、异常数据标记好这三点做到位AI 分析功耗的准确率会远超预期。至于具体用哪台功耗计、哪个数据库反而是次要的思路对了换什么设备都能套。