ESP32-S3对接豆包制作AI桌面数字收音机,桌面闹钟,桌面新闻播报器
基于ESP32-S3开发板,对接豆包的AI能力,制作一款集数字收音机、桌面闹钟、新闻播报功能于一体的AI桌面设备,核心是实现ESP32-S3与豆包的网络交互,借助豆包的AI能力完成新闻获取、语音合成等,同时通过硬件模块实现时间显示、音频播放等功能。
一、整体实现思路
- 硬件层:ESP32-S3作为主控,负责WiFi联网、控制显示模块(展示时间/状态)、音频模块(播放声音)、RTC模块(精准计时);
- 软件层 :ESP32-S3通过WiFi调用豆包开放平台API,获取AI能力(新闻摘要、语音合成),同时对接网络电台API实现收音机功能,基于RTC实现闹钟定时触发。
二、硬件准备(核心组件)
| 组件 | 作用 |
|---|---|
| ESP32-S3开发板 | 主控,联网+硬件控制 |
| DS3231 RTC模块 | 高精度计时(闹钟核心) |
| SSD1306 OLED屏 | 显示时间/电台/新闻标题 |
| VS1053音频解码模块 | 解码播放音频(收音机/播报) |
| MAX9814麦克风(可选) | 语音唤醒/语音输入 |
| 5V喇叭/功放 | 输出声音 |
| 5V 1A电源模块 | 稳定供电 |
三、软件环境准备
- 安装Arduino IDE,在「文件→首选项」添加ESP32开发板地址:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json; - 在「工具→开发板→开发板管理器」安装「esp32」(版本≥2.0.0),选择对应ESP32-S3开发板;
- 安装依赖库:
Adafruit SSD1306(OLED显示)RTClib(RTC时钟)VS1053(音频解码)ArduinoJson(JSON解析)WiFi、HTTPClient(网络请求,内置)
四、豆包API准备
- 前往「豆包开放平台」注册账号,创建应用,获取API Key 和Secret Key;
- 可调用的核心接口:
- 文本生成接口:获取新闻摘要、天气等信息;
- 语音合成接口:将文本转为语音音频流(需参考平台最新接口文档)。
五、核心代码实现(完整可运行)
cpp
#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <RTClib.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <VS1053.h>
// ====================== 配置参数(需替换为自己的) ======================
// WiFi配置
const char* WIFI_SSID = "你的WiFi名称";
const char* WIFI_PWD = "你的WiFi密码";
// 豆包API配置(替换为豆包开放平台获取的密钥)
const char* DOUBAO_API_KEY = "你的豆包API Key";
const char* DOUBAO_TEXT_API = "https://open.doubao.com/api/v1/chat/completions"; // 文本生成接口
// RTC模块
RTC_DS3231 rtc;
// OLED显示配置(I2C)
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// VS1053音频模块配置(SPI)
#define VS1053_CS 5
#define VS1053_DCS 6
#define VS1053_DREQ 7
VS1053 player(VS1053_CS, VS1053_DCS, VS1053_DREQ);
// 闹钟配置
int alarmHour = 8; // 闹钟小时
int alarmMinute = 0; // 闹钟分钟
bool alarmTriggered = false;
// 网络电台URL(替换为真实的MP3流地址)
const char* RADIO_URL = "http://tingtingfm.com/radio/1008.mp3";
// ====================== 函数声明 ======================
void connectWiFi();
String getNewsFromDouBao();
void playAudio(String audioUrl);
void checkAlarm();
void displayTime(DateTime now);
void setup() {
Serial.begin(115200);
// 初始化I2C(OLED/RTC共用)
Wire.begin(21, 22); // SDA=21, SCL=22(可按实际接线调整)
// 初始化OLED
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("OLED初始化失败"));
while(1);
}
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
// 初始化RTC
if (!rtc.begin()) {
Serial.println(F("RTC初始化失败"));
display.setCursor(0, 0);
display.print("RTC Error!");
display.display();
while(1);
}
// 首次使用设置时间(仅第一次运行时取消注释)
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// 初始化音频模块
SPI.begin(18, 19, 23); // SCK=18, MISO=19, MOSI=23
if (!player.begin()) {
Serial.println(F("音频模块初始化失败"));
display.setCursor(0, 10);
display.print("Audio Error!");
display.display();
while(1);
}
player.switchToMP3Mode(); // 切换到MP3播放模式
// 连接WiFi
connectWiFi();
display.clearDisplay();
display.setCursor(0, 0);
display.print("WiFi Connected!");
display.display();
delay(2000);
}
void loop() {
// 获取当前时间
DateTime now = rtc.now();
// 实时显示时间
displayTime(now);
// 检查闹钟是否触发
checkAlarm();
// 示例:每小时整点播报新闻(可自定义频率)
if(now.minute() == 0 && now.second() == 0) {
String news = getNewsFromDouBao();
// 显示新闻开头(OLED显示字数有限)
display.setCursor(0, 30);
display.print("News: ");
display.print(news.length() > 15 ? news.substring(0, 15) + "..." : news);
display.display();
// 【扩展】调用豆包语音合成接口,将新闻转为音频并播放
// String audioUrl = getAudioFromDouBao(news); // 需实现语音合成逻辑
// playAudio(audioUrl);
}
delay(1000);
}
// 连接WiFi
void connectWiFi() {
display.setCursor(0, 0);
display.print("Connecting WiFi...");
display.display();
WiFi.begin(WIFI_SSID, WIFI_PWD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi Connected! IP: " + WiFi.localIP().toString());
}
// 从豆包API获取新闻摘要
String getNewsFromDouBao() {
// 先检查WiFi连接
if(WiFi.status() != WL_CONNECTED) {
connectWiFi();
}
HTTPClient http;
http.begin(DOUBAO_TEXT_API);
// 设置请求头(参考豆包开放平台文档)
http.addHeader("Content-Type", "application/json");
http.addHeader("Authorization", "Bearer " + String(DOUBAO_API_KEY));
// 构建请求体:要求返回简短的当日热点新闻
String requestBody = "{\"model\":\"doubao-pro\",\"messages\":[{\"role\":\"user\",\"content\":\"请返回今日热点新闻摘要,100字以内,简洁易懂\"}],\"max_tokens\":100}";
int httpCode = http.POST(requestBody);
String news = "No news"; // 默认值
if(httpCode > 0) {
if(httpCode == HTTP_CODE_OK) {
String response = http.getString();
Serial.println("豆包API返回:" + response);
// 解析JSON获取新闻内容(需匹配豆包API实际返回格式)
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, response);
if(!error) {
news = doc["choices"][0]["message"]["content"].as<String>();
}
}
} else {
Serial.println("API请求失败,错误码:" + String(httpCode));
}
http.end();
return news;
}
// 播放网络音频流(收音机/语音播报)
void playAudio(String audioUrl) {
// 启动音频流播放(需根据VS1053库完善)
player.startPlayingStream(audioUrl);
while(player.playingMusic) {
// 播放中更新OLED状态
display.setCursor(0, 20);
display.print("Playing...");
display.display();
delay(100);
}
}
// 检查闹钟是否触发
void checkAlarm() {
DateTime now = rtc.now();
// 匹配闹钟时间且未触发过
if(now.hour() == alarmHour && now.minute() == alarmMinute && !alarmTriggered) {
Serial.println("闹钟触发!");
display.clearDisplay();
display.setCursor(0, 0);
display.print("Alarm!");
display.display();
// 播放闹钟提示音(可替换为豆包合成的语音)
playAudio("http://example.com/alarm.mp3");
alarmTriggered = true;
}
// 分钟变化后重置触发状态
if(now.minute() != alarmMinute) {
alarmTriggered = false;
}
}
// 格式化显示时间/日期
void displayTime(DateTime now) {
display.clearDisplay();
display.setTextSize(2);
display.setCursor(0, 0);
// 格式化时间:HH:MM:SS(补零)
String hour = now.hour() < 10 ? "0" + String(now.hour()) : String(now.hour());
String minute = now.minute() < 10 ? "0" + String(now.minute()) : String(now.minute());
String second = now.second() < 10 ? "0" + String(now.second()) : String(now.second());
String timeStr = hour + ":" + minute + ":" + second;
display.print(timeStr);
// 显示日期
display.setTextSize(1);
display.setCursor(0, 25);
String dateStr = String(now.year()) + "-" +
(now.month() < 10 ? "0" + String(now.month()) : String(now.month())) + "-" +
(now.day() < 10 ? "0" + String(now.day()) : String(now.day()));
display.print(dateStr);
display.display();
}
六、关键代码解释
- WiFi连接 :
connectWiFi()确保ESP32-S3联网,是调用豆包API的前提; - 豆包API调用 :
getNewsFromDouBao()通过HTTPClient向豆包开放平台发送POST请求,获取新闻摘要,需替换为自己的API Key并匹配最新接口格式; - 闹钟逻辑 :
checkAlarm()对比RTC时间和设定闹钟时间,触发时播放提示音,避免重复触发; - 音频播放 :
playAudio()基于VS1053库播放网络音频流(收音机/语音),需替换真实的音频URL; - OLED显示 :
displayTime()格式化显示时间/日期,同时可展示新闻、播放状态等。
七、补充说明
- 豆包API适配:需参考豆包开放平台最新文档调整请求头、请求体格式,语音合成功能需单独调用对应接口;
- 硬件接线 :
- OLED/DS3231共用I2C总线,VS1053使用SPI总线,需按代码中引脚定义接线,或自行修改引脚;
- 音频模块需接喇叭/功放,确保声音输出;
- 网络电台URL:需替换为可访问的、支持MP3流的免费网络电台地址(可自行搜索)。
总结
- 该设备核心是ESP32-S3通过WiFi调用豆包开放平台API获取AI能力,同时控制硬件完成时间显示、音频播放;
- 硬件需搭配DS3231(高精度计时)、VS1053(音频解码)、OLED(显示),确保闹钟、收音机、播报功能落地;
- 关键步骤是申请豆包API Key、配置网络请求参数、适配硬件接线,补充语音合成接口调用逻辑即可完成完整功能。