1. 项目缘起:当乐高EV3遇上AI语音合成
几年前,我还在用乐高EV3机器人教孩子们基础的编程逻辑,无非是让小车走个正方形,或者让机械臂抓取个积木块。那时候,EV3在大家眼里就是个高级玩具,核心玩法围绕着马达、传感器和图形化编程。直到有一次,一个学生突发奇想,问我:“老师,能不能让这个机器人像智能音箱一样,每天早晨给我播报新闻和天气?” 这个看似天马行空的问题,一下子点亮了我。对啊,为什么不能呢?EV3本身是一台运行着Linux的微型计算机,我们完全可以用它来做点更“智能”的事情。
这个想法就是“智控万物:乐高EV3播报新闻”项目的起点。它的核心目标,是打破乐高机器人只能执行预设机械动作的刻板印象,将其升级为一个能够与互联网服务交互、处理信息并最终通过语音输出的智能终端。听起来很酷,但实现起来,我们需要跨越几道坎:首先,EV3如何获取实时新闻数据?其次,它如何将文本信息转换成可以播放的语音?最后,如何让这一切自动化运行?
经过一番摸索和整合,我找到了一个非常优雅的解决方案组合:ev3dev操作系统 + Python编程 + 百度AI的语音合成服务。ev3dev让EV3变身为一台真正的、可以通过SSH远程控制的Linux设备;Python提供了强大而简洁的脚本能力,来处理网络请求和逻辑控制;而百度AI开放平台则提供了稳定、高质量的免费语音合成接口,解决了从文本到语音的核心难题。这个项目不仅有趣,更是一个绝佳的“软硬件结合”与“云边协同”的入门实践,能让你深刻理解现代物联网应用的基本架构。
2. 环境搭建:从零构建EV3的Python智能核心
要让EV3跑起来我们的新闻播报程序,第一步就是为它打造一个合适的工作环境。原厂的乐高固件功能有限,所以我们首选刷入ev3dev。这是一个专为乐高EV3等设备优化的Debian Linux发行版,它提供了完整的包管理、文件系统和开发工具链,是我们一切高级操作的基础。
2.1 刷写ev3dev与基础配置
首先,你需要一张至少4GB的Micro SD卡。从ev3dev官网下载最新的镜像文件,使用Etcher或Rufus这类工具将镜像写入SD卡。完成后,将SD卡插入EV3主机并开机。首次启动会进行初始化,时间稍长,耐心等待即可。
当EV3的屏幕上显示IP地址时,环境就准备好了。此时,你可以通过多种方式连接它:
- SSH连接(推荐):在电脑的命令行(Windows用PowerShell或CMD,Mac/Linux用终端)输入
ssh robot@[EV3的IP地址],默认密码是maker。这是最直接、最强大的控制方式。 - VS Code远程开发:安装VS Code的“Remote - SSH”扩展,然后添加EV3作为远程主机。这能让你在熟悉的IDE里直接编写和运行代码,体验极佳。
- Web界面:在浏览器中输入EV3的IP地址,可以访问一个简单的管理页面,查看设备状态、安装软件包等。
连接成功后,第一件事是更新系统并安装必要的工具:
sudo apt-get update sudo apt-get upgrade -y sudo apt-get install python3-pip git -y这里我强烈建议使用python3和pip3,因为ev3dev系统默认的Python 3才是主流,避免与可能存在的Python 2产生混淆。
2.2 Python环境与关键库部署
我们的核心程序用Python编写,因此需要安装几个关键的库。在EV3的SSH终端中依次执行:
pip3 install requestsrequests库是Python中处理HTTP请求的利器,我们将用它来获取新闻数据和调用百度AI的API。
pip3 install pyttsx3pyttsx3是一个离线的文本转语音库。虽然我们主要用百度AI的在线服务(质量更好),但安装pyttsx3作为一个备选方案是明智的。这里有一个大坑:ev3dev基于Debian,直接pip install pyttsx3可能会在运行时报错,提示找不到语音引擎。你需要先安装系统级的依赖:
sudo apt-get install espeak libespeak-dev -y然后再安装pyttsx3,这样它才能利用espeak引擎在EV3上离线合成语音(虽然音质像机器人,但能应急)。
2.3 获取并配置百度AI语音合成服务
在线语音合成的质量远非离线引擎可比。百度AI开放平台提供了免费的语音合成接口,每日有一定调用额度,对于个人项目完全够用。
注册与创建应用:访问百度AI开放平台,注册登录后,在“语音技术”类别下找到“语音合成”,创建一个新应用。创建成功后,你会得到
API Key和Secret Key,这两个是关键凭证,务必保存好。理解鉴权机制:百度AI的API调用需要Access Token,这个Token需要通过API Key和Secret Key来换取,并且有一定有效期(通常为一个月)。我们的策略是:在程序中实现一个自动获取和刷新Token的函数。
编写配置文件:为了安全,不建议将密钥硬编码在代码里。我在EV3的
/home/robot目录下创建了一个简单的配置文件config.ini:[baidu_aip] api_key = 你的API_Key secret_key = 你的Secret_Key然后在Python代码中用
configparser库来读取。这样,即使代码分享出去,你的密钥也不会泄露。
3. 核心实现:新闻获取与语音合成的流水线
环境就绪后,我们来搭建核心的数据处理流水线。整个流程可以概括为:定时触发 -> 获取新闻 -> 文本处理 -> 合成语音 -> 播放输出。
3.1 新闻数据源的选取与解析
新闻从哪里来?我们可以选择一些提供开放API的新闻平台,但考虑到稳定性和简便性,我选择了通过爬取主流新闻网站的公开RSS源来获取。RSS是一种结构化的数据格式,解析起来非常方便。
这里以抓取某个新闻门户的科技频道RSS为例。我们使用requests获取RSS的XML内容,然后用Python内置的xml.etree.ElementTree库进行解析。
import requests import xml.etree.ElementTree as ET def fetch_news_from_rss(rss_url): """从指定的RSS链接获取新闻标题列表""" try: headers = {'User-Agent': 'Mozilla/5.0 (compatible; EV3-NewsBot/1.0)'} response = requests.get(rss_url, headers=headers, timeout=10) response.raise_for_status() # 检查请求是否成功 root = ET.fromstring(response.content) # 解析XML,获取item节点下的title标签内容 # 不同RSS源结构略有差异,需要根据实际情况调整XPath或遍历方式 news_titles = [] for item in root.findall('.//item'): title_elem = item.find('title') if title_elem is not None and title_elem.text: # 简单清洗一下标题,移除换行符和多余空格 clean_title = ' '.join(title_elem.text.split()) news_titles.append(clean_title) return news_titles[:5] # 返回前5条,避免内容过长 except requests.exceptions.RequestException as e: print(f"获取新闻失败: {e}") return ["抱歉,当前无法获取最新新闻。"] except ET.ParseError as e: print(f"解析RSS数据失败: {e}") return ["新闻数据解析出错。"]关键点:一定要设置User-Agent头,模拟一个真实的浏览器请求,否则有些网站可能会拒绝爬虫。另外,要做好异常处理,网络请求失败或解析出错时,要有降级方案(比如返回一个友好的提示语),避免程序崩溃。
3.2 集成百度AI语音合成接口
有了新闻文本,下一步就是将其转化为语音。我们编写一个函数,专门与百度AI的语音合成接口打交道。
import requests import json from configparser import ConfigParser def get_baidu_token(api_key, secret_key): """获取百度AI的Access Token""" token_url = "https://aip.baidubce.com/oauth/2.0/token" params = { 'grant_type': 'client_credentials', 'client_id': api_key, 'client_secret': secret_key } response = requests.get(token_url, params=params) result = response.json() return result.get('access_token') def text_to_speech_baidu(text, token, filename='output.mp3'): """使用百度AI将文本合成为语音文件""" if not token: print("Token无效,无法合成语音") return False url = "https://tsn.baidubce.com/text2audio" headers = {'Content-Type': 'application/x-www-form-urlencoded'} data = { 'tex': text[:1024], # 百度接口单次有长度限制,需截断 'tok': token, 'cuid': 'ev3-robot', # 设备标识,可自定义 'ctp': '1', # 客户端类型,1为Web 'lan': 'zh', # 语言,中文 'spd': 5, # 语速,0-15,5为默认 'pit': 5, # 音调,0-15,5为默认 'vol': 5, # 音量,0-15,5为默认 'per': 0 # 发音人,0为女声,1为男声,3为情感合成-度逍遥,4为情感合成-度丫丫 } response = requests.post(url, headers=headers, data=data) if response.headers['content-type'] == 'audio/mp3': with open(filename, 'wb') as f: f.write(response.content) print(f"语音文件已保存: {filename}") return True else: # 合成失败,返回错误信息 error_msg = response.json() print(f"语音合成失败: {error_msg}") return False经验之谈:
- Token管理:Token有效期很长,但并非永久。一种简单的策略是在程序启动时获取一次,然后缓存起来。更健壮的做法是捕获接口返回的
token invalid错误,然后自动重新获取Token并重试请求。 - 文本长度:百度接口对单次请求的文本长度有限制(大约1024字节)。我们的新闻标题通常很短,没问题。但如果想播报新闻摘要,就需要将长文本分割成多个段落,分别合成后再拼接播放。
- 发音人选择:
per参数很有趣,3和4是情感合成音,听起来更自然,但可能在某些场景下不如0或1稳定,可以多试试选择喜欢的音色。
3.3 EV3的音频播放与控制
生成了MP3文件,如何在EV3上播放?ev3dev系统自带了强大的aplay和omxplayer等音频工具。经过测试,omxplayer对硬件解码的支持更好,资源占用也更低,是首选。
我们可以直接在Python代码里调用系统命令来播放:
import subprocess def play_audio_file(filename): """使用omxplayer播放音频文件""" try: # -o local 表示从3.5mm音频口输出 # --vol 设置音量,范围-6000到0 subprocess.run(['omxplayer', '-o', 'local', '--vol', '-1000', filename], check=True) except subprocess.CalledProcessError as e: print(f"播放音频失败: {e}") # 备选方案:尝试使用aplay播放(可能不支持mp3,需确认格式) # subprocess.run(['aplay', filename])避坑指南:EV3的扬声器音量可能默认较小。omxplayer的--vol参数接受的是毫分贝(centibel)单位,-6000对应静音,0对应最大音量。你需要根据环境调整这个值,比如-1000或-500。另外,确保音频文件路径正确,并且EV3的音频输出已连接好扬声器或耳机。
4. 系统集成与自动化:让播报机器人自主运行
现在,获取新闻、合成语音、播放音频这三个核心模块都已就位。我们需要一个“大脑”将它们串联起来,并实现自动化。
4.1 编写主控程序与逻辑流
主程序news_broadcaster.py的职责是协调各个模块,控制整个工作流。其逻辑如下:
- 初始化:读取配置文件,获取百度AI密钥,并申请初始Token。
- 新闻获取:调用
fetch_news_from_rss函数,获取最新的新闻标题列表。 - 文本组装:将多条新闻标题组合成一段连贯的播报文稿。例如:“现在是新闻播报时间。第一条新闻:... 第二条新闻:...”。
- 语音合成:调用
text_to_speech_baidu,将文稿合成为MP3文件。 - 音频播放:调用
play_audio_file,播放生成的MP3。 - 清理:播放完成后,可以选择删除临时生成的MP3文件,以节省EV3有限的存储空间。
一个简单的主循环框架如下:
# news_broadcaster.py import time from config_loader import get_config # 假设有一个读取config.ini的模块 from news_fetcher import fetch_news_from_rss from tts_engine import text_to_speech_baidu, get_baidu_token from audio_player import play_audio_file def main(): config = get_config() api_key = config['baidu_aip']['api_key'] secret_key = config['baidu_aip']['secret_key'] # 1. 获取Token token = get_baidu_token(api_key, secret_key) if not token: print("初始化失败:无法获取百度AI Token") return # 2. 获取新闻 rss_url = "http://你的新闻RSS源地址" news_list = fetch_news_from_rss(rss_url) if not news_list: news_text = "未获取到新闻内容。" else: # 3. 组装播报文稿 intro = "主人您好,现在是乐高新闻播报时间。" body = " ".join([f"第{i+1}条,{news}" for i, news in enumerate(news_list)]) ending = "今天的新闻播报完毕,谢谢收听。" news_text = f"{intro} {body} {ending}" print(f"待播报文本: {news_text}") # 4. 合成语音 audio_file = '/home/robot/news_broadcast.mp3' success = text_to_speech_baidu(news_text, token, audio_file) # 5. 播放语音 if success: play_audio_file(audio_file) # 可选:播放后删除文件 # import os # os.remove(audio_file) else: print("语音合成失败,尝试使用离线引擎播报...") # 这里可以调用pyttsx3作为降级方案 # offline_tts(news_text) if __name__ == '__main__': main()4.2 实现定时播报与开机自启
我们希望EV3能像闹钟一样,每天固定时间(比如早上8点)自动播报新闻。在Linux系统上,最常用的工具是cron。
编辑cron计划任务:在EV3的SSH终端中,输入
crontab -e来编辑当前用户(robot)的cron任务。添加定时任务:在文件末尾添加一行。例如,希望每天上午8点整运行一次播报程序:
0 8 * * * /usr/bin/python3 /home/robot/news_broadcaster.py >> /home/robot/news_log.txt 2>&10 8 * * *表示分钟(0),小时(8),任意日,任意月,任意星期。/usr/bin/python3是Python3解释器的绝对路径,使用绝对路径更可靠。/home/robot/news_broadcaster.py是你的主程序脚本的绝对路径。>> /home/robot/news_log.txt 2>&1将程序的标准输出和错误输出都重定向追加到日志文件中,方便后续排查问题。
设置开机自启(可选):如果你希望EV3一开机就启动某个监控程序或者准备好服务,可以编辑
/etc/rc.local文件(需要在root权限下)。在exit 0这一行之前,添加你的启动命令。但就本新闻播报项目而言,使用cron定时任务已经足够。
一个真实的踩坑记录:最初我直接使用python3 news_broadcaster.py作为cron命令,偶尔会失败。原因是cron执行时的环境变量(如PATH、USER)与SSH登录后的shell环境不同。解决方案就是如上所述,使用绝对路径指向Python解释器和脚本文件,并将输出重定向到日志文件,这样任何错误都能被记录下来。
5. 功能扩展与创意玩法
基础播报功能实现后,这个项目就有了无限的可能性。你可以把它当作一个平台,添加更多有趣的模块。
5.1 多数据源与内容过滤
单一的新闻源可能不够全面或不符合个人兴趣。我们可以很容易地扩展:
- 多个RSS源:修改
fetch_news_from_rss函数,让它接受一个RSS源列表,然后随机选取一个或多个源来获取新闻,让内容更多样。 - 关键词过滤:在解析新闻标题后,加入一个过滤环节。只播报包含你感兴趣的关键词(如“人工智能”、“航天”)的新闻,或者过滤掉你不想听的类别。
- 集成天气API:在播报新闻前,先调用一个天气API(如和风天气、OpenWeatherMap的免费接口),获取当地的天气情况,一并播报出来。“早上好,今天是X月X日,天气晴,气温15到22度。现在为您播报早间新闻...”
5.2 交互与触发机制升级
除了定时播报,还可以增加交互性:
- 按钮触发:利用EV3的触摸传感器。编写一个循环监听传感器状态的程序,当检测到按钮被按下时,立即执行一次新闻播报。这比定时播报更灵活。
- 语音唤醒(进阶):这是一个更有挑战性的方向。可以在EV3上运行一个轻量级的语音识别服务(如Vosk离线识别库),持续监听环境声音。当识别到“小乐小乐,播报新闻”这样的唤醒词时,再启动新闻播报流程。这需要更强的处理能力和更复杂的代码,但实现后体验会非常棒。
- 远程控制:在EV3上运行一个简单的Web服务器(例如用Flask框架),创建一个内部网页。当你从同一局域网的手机或电脑浏览器访问这个页面时,点击一个“播报”按钮,就能远程触发新闻播报。
5.3 硬件联动与表现力增强
别忘了,EV3的本质是一套机器人套件!我们可以让播报新闻这件事更有“仪式感”:
- 动作配合:编写程序,让机器人在开始播报前,先“走”到房间中央,或者让机械臂做出一个“请听”的手势。播报结束后,再点头致意,然后回到待机位置。这需要精确的马达控制和动作序列编程。
- 灯光与显示:利用EV3主机上的彩色指示灯和屏幕。在合成语音时,让指示灯闪烁蓝色表示“思考”或“下载”;在播放时,显示当前播报的新闻标题或滚动字幕;遇到错误时,亮起红灯并在屏幕上显示错误码。
- 多机器人协作:如果你有两套以上的EV3,可以尝试让它们扮演不同角色。一个负责获取和合成新闻,作为“主播”;另一个负责接收“主播”通过蓝牙或Wi-Fi发送的指令,在播报时配合跳舞或移动,作为“伴舞”或“助理”。
这个项目从一个小小的想法开始,最终演变成一个融合了网络爬虫、云计算API、Linux系统管理、Python编程和机器人控制的综合性实践。它最吸引我的地方在于,每一步都有明确的目标,遇到的问题(网络、音频、调度)都非常典型,解决问题的过程就是一次绝佳的学习。当你第一次听到自己搭建的乐高机器人,用清晰流畅的语音播报出当天的真实新闻时,那种成就感是无可比拟的。它不再仅仅是一个玩具,而是一个由你亲手赋予“智能”的创造物。