1. 项目概述一个用mPythonX实现的AI疫情信息助手最近在整理一些旧项目翻到了之前用mPythonX做的一个小玩意儿我把它叫做“AI新冠疫情查询器”。这名字听起来有点唬人其实核心就是一个运行在掌控板这类开源硬件上的本地化信息查询工具。它的诞生背景很简单当时大家获取疫情信息主要依赖手机App和网页信息繁杂且更新不及时我就想能不能做一个离线的、能快速回答简单问题的“信息终端”比如放在社区服务站、学校门口让不擅长用智能手机的老人或学生通过语音或按键就能问“今天本地新增多少病例”或者“发烧了该怎么办”然后设备能给出基于本地数据库的、结构化的回答。mPythonX是面向青少年编程教育和物联网开发的一款图形化编程软件基于Python能很好地驱动掌控板这类集成了屏幕、按键、传感器和Wi-Fi模块的硬件。这个项目的核心思路就是利用mPythonX的易用性结合一个轻量级的本地知识库和简单的自然语言理解逻辑在资源有限的硬件上实现一个问答系统。它不依赖云端大模型所有逻辑和数据处理都在本地完成响应快、无网络依赖且完全保护隐私。虽然现在疫情已成过去但这个项目的技术框架——如何在嵌入式设备上实现一个轻量级、可交互的AI问答应用——对于学习物联网、边缘AI和交互设计依然很有价值。无论你是教育工作者想设计教学案例还是硬件爱好者想折腾点有趣的智能终端这个项目都能给你带来不少启发。2. 核心设计思路与架构拆解2.1 为什么选择mPythonX与本地化方案首先得明确这个“AI”并非指ChatGPT那样的生成式大模型。在掌控板通常搭载ESP32芯片内存仅几百KB上跑动辄数十亿参数的大模型是天方夜谭。这里的“AI”更贴近其本意——让机器具备一定的“智能”来理解和处理任务。我们实现的是基于规则和模式匹配的意图识别以及一个结构化的本地知识库查询系统。选择mPythonX有以下几个关键考量硬件亲和与快速原型mPythonX为掌控板做了深度优化封装了屏幕、网络、传感器等硬件的控制接口用图形化积木或Python代码都能快速调用。这让我们能把精力集中在应用逻辑而非底层驱动上。Python生态优势mPythonX支持MicroPython意味着我们可以使用Python的字符串处理、列表、字典等数据结构这对于实现文本解析和知识库管理至关重要。离线与隐私所有数据知识库、用户问答记录都存储在掌控板的Flash中无需连接外网杜绝了隐私泄露风险也保证了在无网络环境下的可用性。低成本与可部署性一套掌控板加扩展板成本可控易于批量制作和部署到特定线下场景如社区、诊所、学校的问询台。整个系统的架构可以概括为“输入-处理-输出”三层输入层支持两种方式。一是通过掌控板的物理按键A/B键进行菜单选择式交互二是通过连接外接的语音识别模块如LD3320进行语音输入后者体验更自然。处理层核心意图识别模块对输入的文本无论是直接输入还是语音转文本进行关键词提取和意图分类。例如识别用户是想查询“数据”、“政策”还是“防护知识”。知识库模块一个存储在板载Flash或外置SD卡中的结构化文件如JSON或CSV包含了疫情相关的问答对、统计数据、指南等。查询与匹配引擎根据识别出的意图在知识库中进行检索找到最匹配的答案。输出层将匹配到的答案通过掌控板的OLED屏幕显示出来同时可以通过板载的蜂鸣器播放提示音或通过语音合成模块如SYN6288进行语音播报实现多模态交互。2.2 知识库的设计与构建要点知识库是这个系统的“大脑”其质量直接决定查询效果。我们不能直接爬取动态网页数据必须构建一个静态、结构化的本地知识库。知识库结构设计我采用JSON格式因为它易于MicroPython解析且结构清晰。一个基础的知识库条目包含以下字段{ intent: data_query, keywords: [新增, 确诊, 今天, 本地, 病例], question_template: [今天新增多少, 本地有新增病例吗, 最新确诊数据], answer: 根据最新更新您所在区域今日无新增本土确诊病例。请继续保持良好卫生习惯。, data_source: local_health_commission_2023-10-27, category: epidemic_data }intent: 意图标签用于快速分类。keywords: 关键词列表用于模糊匹配用户输入。question_template: 一些常见问法示例可用于优化匹配或作为示例提示。answer: 标准答案文本。data_source和category: 用于管理和追溯信息。知识库内容从哪里来内容需要手动整理和录入确保权威性和时效性。主要来源包括国家及地方卫生健康委员会官方发布的常态化防控指南、科普知识。权威媒体发布的总结性数据需注明非实时和科普文章。将常见的用户咨询问题如“发烧了怎么办”“口罩怎么选”整理成标准QA。注意事项知识库的更新是最大挑战。我们采取“定期更新”策略。开发一个简单的PC端工具将整理好的新知识库JSON文件通过USB连接或Wi-Fi OTA空中升级的方式烧录到掌控板的文件系统中。在程序初始化时会检查知识库文件的版本号提示是否需要更新。3. 核心功能模块的代码实现与解析3.1 硬件初始化与外围设备驱动在mPythonX中硬件初始化变得非常简单。我们首先需要引入必要的库并设置硬件。# 导入mPythonX相关模块 from mpython import * from machine import Pin, I2C import time import json import uos # 初始化OLED屏幕 (通常使用I2C0) oled OLED() oled.poweron() oled.init_display() oled.fill(0) # 清屏 # 初始化按键 (假设使用板载A、B键) button_a Pin(button_a.pin, Pin.IN, Pin.PULL_UP) button_b Pin(button_b.pin, Pin.IN, Pin.PULL_UP) # 初始化蜂鸣器 (假设连接在P0口) buzzer PWM(Pin(Pin.P0), freq1000, duty0) # 知识库文件路径 KB_FILE /flash/covid_kb.json如果需要连接语音模块通常会使用UART或I2C接口。以UART语音识别模块为例from machine import UART # 初始化UART用于语音识别模块 uart_voice UART(1, baudrate9600, txPin(Pin.P4), rxPin(Pin.P5))3.2 意图识别器的轻量化实现在资源受限的环境下我们采用“关键词匹配 意图标签”的规则方法。虽然不如神经网络精准但对于限定领域疫情查询和有限问答对效果足够且速度极快。class SimpleIntentRecognizer: def __init__(self, kb_path): self.knowledge_base self.load_knowledge_base(kb_path) self.intent_keywords { data_query: [新增, 确诊, 病例, 数据, 多少, 今天, 本地], policy_query: [政策, 规定, 隔离, 核酸, 要求, 出入], prevention_query: [预防, 口罩, 洗手, 消毒, 发烧, 症状, 怎么办], greeting: [你好, 您好, 嗨, 在吗], farewell: [谢谢, 再见, 拜拜] } def load_knowledge_base(self, path): try: with open(path, r, encodingutf-8) as f: # 注意MicroPython的json.load可能需要处理文件对象不同 content f.read() return json.loads(content) except Exception as e: print(加载知识库失败:, e) return [] def recognize(self, user_input): user_input user_input.strip() if not user_input: return None, 输入为空 # 1. 先进行精确意图关键词匹配 for intent, keywords in self.intent_keywords.items(): for kw in keywords: if kw in user_input: # 找到意图后进一步去知识库匹配具体问题 best_match self._match_in_kb(user_input, intent) if best_match: return intent, best_match[answer] else: return intent, f我理解您想查询{intent}但知识库中未找到精确匹配。请尝试其他问法。 # 2. 如果未匹配到预设意图则在知识库中全局进行关键词相似度匹配 best_match self._match_in_kb(user_input, None) if best_match: return best_match.get(intent, general), best_match[answer] return unknown, 抱歉我暂时无法理解您的问题。您可以尝试询问疫情数据、政策或防护知识。 def _match_in_kb(self, user_input, target_intentNone): 在知识库中寻找最佳匹配 best_score 0 best_item None for item in self.knowledge_base: # 如果指定了意图先过滤 if target_intent and item.get(intent) ! target_intent: continue score 0 # 计算关键词匹配得分 for kw in item.get(keywords, []): if kw in user_input: score 1 # 也可以简单计算字符重叠率作为加分 # 这里简化处理实际可以更复杂 if score best_score: best_score score best_item item # 设置一个阈值比如至少匹配一个关键词才返回 return best_item if best_score 0 else None实操心得关键词的设计需要反复打磨。初期我罗列了很多词但发现匹配过于粗糙。后来我采用了“核心词场景词”的组合。例如对于数据查询“新增”、“确诊”是核心词“今天”、“本地”、“多少”是场景词。只有当核心词匹配且场景词有一定匹配度时才判定为高置信度匹配。这大大减少了误判。3.3 交互逻辑与用户界面实现交互流程需要清晰且友好。我们设计一个简单的状态机来控制整个流程。class CovidQueryApp: def __init__(self): self.recognizer SimpleIntentRecognizer(KB_FILE) self.current_mode menu # 状态menu, listening, processing, showing_result self.menu_options [语音提问, 按键选择常见问题, 更新知识库, 关于] self.selected_index 0 def run(self): while True: if self.current_mode menu: self._show_menu() self._handle_menu_input() elif self.current_mode listening: self._listen_for_voice() elif self.current_mode processing: self._process_input() elif self.current_mode showing_result: # 显示结果等待用户返回菜单 time.sleep(3) self.current_mode menu time.sleep_ms(50) # 防止忙等待 def _show_menu(self): oled.fill(0) oled.DispChar(疫情查询器, 0, 0) for i, opt in enumerate(self.menu_options): prefix if i self.selected_index else oled.DispChar(f{prefix}{opt}, 5, 16 i*12) oled.show() def _handle_menu_input(self): if button_a.value() 0: # 按键A按下 time.sleep_ms(20) # 消抖 if button_a.value() 0: self.selected_index (self.selected_index - 1) % len(self.menu_options) self._beep_short() elif button_b.value() 0: # 按键B按下确认 time.sleep_ms(20) if button_b.value() 0: self._beep_short() if self.selected_index 0: self.current_mode listening oled.fill(0) oled.DispChar(请说话..., 20, 30) oled.show() elif self.selected_index 1: self._show_faq_list() # ... 处理其他菜单选项 def _listen_for_voice(self): # 假设语音模块通过UART返回识别结果格式为RESULT:xxx\n if uart_voice.any(): raw_data uart_voice.readline() if raw_data: try: text raw_data.decode(utf-8).strip() if text.startswith(RESULT:): user_speech text[7:] # 提取识别文本 self.user_input user_speech self.current_mode processing oled.fill(0) oled.DispChar(处理中..., 30, 30) oled.show() except Exception as e: print(语音识别解析错误:, e) def _process_input(self): intent, answer self.recognizer.recognize(self.user_input) # 显示结果 oled.fill(0) oled.DispChar(Q: self.user_input[:16], 0, 0) # 显示问题前16字符 oled.DispChar(A:, 0, 16) # 答案可能很长需要分页显示 self._display_long_text(answer, start_y24) oled.show() self._beep_done() self.current_mode showing_result def _display_long_text(self, text, start_y, chars_per_line16, line_height12): 在OLED上分页显示长文本 lines [] for i in range(0, len(text), chars_per_line): lines.append(text[i:ichars_per_line]) for i, line in enumerate(lines[:4]): # 最多显示4行 oled.DispChar(line, 0, start_y i * line_height) def _beep_short(self): buzzer.duty(50) time.sleep_ms(50) buzzer.duty(0) def _beep_done(self): for freq in [523, 659, 784]: # 简单旋律 buzzer.freq(freq) buzzer.duty(50) time.sleep_ms(80) buzzer.duty(0)注意事项OLED屏幕显示长文本是个挑战。上面的分页显示函数是基础版。更好的做法是实现一个滚动显示或者增加“下一页”的按键控制。在实际部署中答案应尽量简洁控制在3行约48个汉字以内确保一屏显示核心信息。4. 知识库的维护与更新机制4.1 本地知识库的版本化管理一个静态知识库最大的问题是过时。我们设计一个简单的版本管理机制。在知识库JSON文件的根对象中加入版本信息。{ version: 1.2.0, update_date: 2023-10-27, data: [ // ... 具体的QA条目数组 ] }在设备启动时程序读取该版本号并可以与设备内部存储的上一版本号对比。如果发现版本更新可以在屏幕上提示“知识库已更新至v1.2.0”。4.2 实现无线OTA更新知识库对于已部署的设备通过USB线更新不现实。我们可以利用掌控板的Wi-Fi功能实现简单的OTA更新。搭建一个简单的更新服务器可以在内网搭建一个HTTP服务器放置一个名为covid_kb_latest.json的文件和一个version.txt文件只包含版本号字符串如“1.2.0”。设备端更新逻辑import network import urequests def check_and_update_kb(): # 连接Wi-Fi (SSID和密码可固化在代码或通过配网获取) wlan network.WLAN(network.STA_IF) wlan.active(True) if not wlan.isconnected(): wlan.connect(Your_SSID, Your_Password) # 等待连接此处省略重试逻辑 # 获取服务器上的版本号 try: resp urequests.get(http://your-server/version.txt, timeout5) latest_version resp.text.strip() resp.close() # 读取本地版本号 with open(/flash/version.info, r) as f: local_version f.read().strip() if latest_version ! local_version: oled.fill(0) oled.DispChar(发现新知识库, 15, 20) oled.DispChar(正在更新..., 15, 35) oled.show() # 下载新的知识库文件 resp urequests.get(http://your-server/covid_kb_latest.json, timeout10) new_kb_data resp.json() # 注意urequests的json()方法可能内存不足大文件建议流式处理或分块 resp.close() # 保存到文件系统 (注意备份旧文件) with open(/flash/covid_kb_new.json, w) as f: import json json.dump(new_kb_data, f) # 更新版本文件 with open(/flash/version.info, w) as f: f.write(latest_version) oled.fill(0) oled.DispChar(更新完成, 25, 30) oled.show() time.sleep(2) # 可以在这里重启设备或热加载新知识库 machine.reset() # 重启生效 except Exception as e: print(更新检查失败:, e) # 失败不影响主流程继续使用旧知识库警告在实际产品中OTA更新需要非常谨慎。必须加入完整性校验如MD5/SHA1校验和并实现回滚机制保留旧版本文件防止因更新文件损坏导致系统无法启动。对于极其重要的设备甚至需要双备份系统。4.3 知识库内容的优化策略初始的知识库是基于通用问题构建的。部署后我们可以通过收集匿名查询日志来优化它。记录未知问题当意图识别器返回unknown时可以将用户输入的问题经过脱敏不记录任何个人信息记录到一个本地的unknown_questions.log文件中。定期分析维护人员定期通过USB导出日志文件分析高频出现的“未知问题”。这些问题就是知识库需要补充的盲点。同义问题扩展对于已覆盖的问题通过日志发现用户的不同问法将这些新问法作为keywords或question_template补充到原有条目中提高匹配率。这种“部署-收集-优化-更新”的闭环能让这个本地AI系统越用越聪明。5. 项目扩展与优化方向虽然核心功能已经实现但作为一个可深入挖掘的项目还有不少可以优化和扩展的空间。5.1 增强交互体验引入简单的对话记忆目前的系统是单轮问答。我们可以引入一个非常简单的上下文记忆实现两轮对话。例如用户问“北京今天新增多少”系统回答后用户接着问“那上海呢”。系统需要能记住上一轮对话的意图数据查询和实体“今天”并将新的实体“上海”代入。实现方法在应用类中增加几个状态变量。self.last_intent None self.last_entities {} # 例如 {time: 今天, type: 新增} self.last_answer_template None当识别到“那...呢”、“还有呢”这类指代性问句时复用上一轮的意图和部分实体结合新输入中的实体进行查询。这需要更复杂的自然语言处理但在限定场景下通过几十行规则代码也能实现不错的效果。5.2 接入传感器实现环境联动掌控板本身集成了光线、声音、加速度等传感器。我们可以让查询器变得更“主动”和“智能”。环境触发播报利用光线传感器当检测到有人靠近阴影变化时自动播放一条欢迎语或最新的重要通知如“今日无新增请放心”。手势控制利用加速度传感器识别简单的敲击或翻转手势。例如敲击两下设备直接播报当前时间段的防护提示。结合温湿度传感器连接外置的温湿度传感器如DHT11当检测到环境湿度较低时在回答完用户问题后追加提示“当前空气干燥请注意补充水分保持呼吸道湿润”。这些联动功能不仅增加了趣味性也提升了设备的实用性和用户体验。5.3 性能优化与稳定性保障随着知识库扩大和功能增加需要关注性能和稳定性。知识库索引当QA对超过100条时线性遍历匹配可能变慢。可以提前为知识库建立倒排索引。例如建立一个keyword - [list of QA ids]的字典。匹配时先提取用户输入中的关键词然后直接查找这些关键词关联的QA列表再进行精细匹配大幅提升速度。内存管理MicroPython内存有限。避免在循环中创建大对象。对于长字符串操作使用micropython.mem_info()定期检查内存碎片。考虑将知识库按意图分类存储成多个小文件按需加载。看门狗与异常恢复引入硬件看门狗machine.WDT()防止程序跑飞。在关键函数外用try...except捕获异常并记录到日志文件。在发生不可恢复错误时看门狗复位设备并尝试加载一个最简化的“安全模式”知识库保证基本功能可用。6. 常见问题与故障排查实录在实际开发和部署过程中我遇到了不少坑。这里把典型问题和解决方法记录下来希望能帮你节省时间。6.1 语音识别模块不工作或识别率低问题现象UART读不到数据或者读到的都是乱码或识别结果完全不对。排查步骤检查硬件连接这是最常见的问题。确认TX、RX是否接反模块的TX接掌控板的RX模块的RX接掌控板的TX。确认供电是否稳定语音模块通常需要3.3V电流可能超过200mA确保电源能带动。检查波特率用uart_voice.write(btest)发送数据同时用电脑串口助手监听模块的TX线看是否有数据发出确认模块的通信波特率是否与代码中设置的一致常见有9600, 115200等。环境噪音在嘈杂环境下识别率必然下降。尝试在相对安静的环境测试或给模块加上简单的海绵防风罩。有些模块如LD3320支持设置识别阈值可以尝试调高。关键词列表优化语音识别模块通常需要你预先设定一个候选词列表。确保这个列表里的词都是清晰、无歧义、且与你的知识库keywords对应的。例如你说“新增病例”模块的词列表里最好就有“新增”和“病例”这两个词。6.2 OLED显示乱码或内容不全问题现象屏幕显示方块、乱码或者长文本显示一半。排查步骤字体编码mPythonX的oled.DispChar默认支持GB2312编码的汉字。确保你的中文字符串是GB2312编码。如果你从网络获取的字符串是UTF-8需要转换。MicroPython中可以用.encode(gb2312)试试但更可靠的方法是将所有固定显示的文字直接写在代码里。内存不足在显示很长的动态字符串如网络获取的答案时如果字符串超长处理过程可能导致内存分配失败。务必做好字符串长度截断。显示驱动不同厂商的OLED屏如SSD1306, SH1106驱动略有差异。确认你使用的mpython库中的OLED类是否与你的屏幕型号匹配。有时需要调整初始化参数或使用专门的驱动库。6.3 知识库JSON文件读取失败问题现象程序启动时报错OSError: [Errno 2] ENOENT或ValueError: syntax error in JSON。排查步骤文件路径与存在性首先确认文件是否成功上传到了掌控板的/flash/目录。可以在mPythonX的“文件”面板中查看。路径要写对区分大小写。JSON格式错误这是最头疼的。在电脑上用专业的JSON验证工具如VS Code的JSON插件、在线JSON校验网站检查你的covid_kb.json文件格式是否正确。特别注意尾部的逗号、中文字符是否用了正确的引号。MicroPython的json模块限制MicroPython的json.loads()可能对文件大小有要求或者不支持某些标准JSON的特性。尝试将知识库拆分成几个小文件。加载时使用ujson模块如果可用它通常更快、内存效率更高。字符编码确保保存JSON文件时使用UTF-8 without BOM编码。在代码中打开文件时指定encodingutf-8。6.4 程序运行一段时间后死机或无响应问题现象设备刚开始工作正常运行几分钟或几小时后屏幕卡住按键无反应。排查步骤内存泄漏在循环中不断创建新的对象如列表、字典而不释放会导致内存耗尽。使用gc.collect()定期进行垃圾回收并在不需要时及时将大变量设为None。看门狗未喂食如果你启用了看门狗必须在主循环中定期调用wdt.feed()否则看门狗超时会复位设备。阻塞操作避免使用time.sleep()进行长时间阻塞这会导致整个程序停止响应。对于需要延时的操作如显示结果3秒使用状态机和时间戳来判断而不是直接sleep(3)。异常未捕获某个函数抛出异常但没有被捕获可能导致程序线程终止。用try...except包裹可能出错的代码块至少在主循环最外层要有异常捕获并记录错误信息。这个项目从构思到实现再到不断优化让我对在资源受限的嵌入式设备上实现“轻智能”有了更深的理解。它不像云端AI那样强大但它的即时性、隐私性和在特定场景下的可靠性是无可替代的。如果你正在寻找一个结合了硬件、软件和简单AI思维的入门项目这个新冠疫情查询器是一个非常好的起点。你可以把它改造成校园天气查询器、图书馆书目查询终端甚至是智能家居的控制面板其核心架构是相通的。最关键的是动手去做在调试和解决问题的过程中收获远比读十篇教程要多。