语音设置闹钟提醒的HiChatBox实现

你有没有过这样的经历?深夜加班,困得眼皮打架,却还得挣扎着打开手机、滑动屏幕、点进闹钟App……就为了设个“半小时后提醒我休息”。🤯 而现在,只需要一句:“Hi ChatBox,半小时后叫我休息”,一切搞定——不用动手,不看屏幕,甚至不用联网。

这听起来像科幻片?其实它已经悄悄走进我们的生活。今天要聊的,就是如何在一个叫 HiChatBox 的嵌入式语音终端上,用纯本地化的方式,实现“一句话设闹钟”的完整闭环。🎯

这个小盒子不靠云端,不依赖手机,自己就能听懂你说的话、理解你的意图、记住时间,并准时提醒你。它是怎么做到的?我们来一层层拆开看。


🎤 听见你:从“你好小盒”开始的唤醒之旅

一切始于声音。但让设备一直开着麦克风听着,岂不是耗电如飞?而且万一它老是误唤醒,半夜突然说一句“我在听”,那可太吓人了。

所以,我们用了两段式设计:先“唤醒”,再“识别”。

第一阶段是 关键词唤醒(KWS) ——系统像个睡着的哨兵,耳朵一直竖着,只等那一句特定的话:“你好小盒”或者英文“Hi ChatBox”。一旦听到,立刻睁眼!

这个KWS模型非常轻量,比如基于TensorFlow Lite Micro的小型神经网络,能在ESP32这种双核MCU上跑得飞起。它的延迟控制在300ms以内,误唤醒率低到每天不到一次,关键是——所有计算都在本地完成,录音不会上传任何地方,隐私安全拉满!🔐

检测到唤醒词后,系统播放一声清脆的“滴”🔊,表示“我醒了,请说吧”,然后马上切换到第二阶段:连续语音识别(ASR)。

这时候才真正开始录你说的整句话。为了节省资源,我们限制词汇量在200个常用词内,比如时间、动作、数字等。模型也做了极致压缩,可能是简化版的RNN-T或CTC架构,确保在没有GPU的情况下也能实时转录。

下面这段代码,就是在ESP32上实现KWS监听的核心逻辑:

// 示例:ESP32上KWS与ASR状态切换逻辑(基于ESP-SR SDK)
#include "esp_sr_iface.h"
#include "esp_mn_iface.h"

const esp_mn_iface_t *model = &esp_mn_wakenet5_quantized;
void *model_data = NULL;
audio_frontend_t *frontend = NULL;

void start_listening() {
    model_data = model->create(model_name, &config);
    frontend = create_audio_frontend();

    while (true) {
        int16_t *buffer = get_audio_buffer(); // 获取PCM数据
        int len = read_microphone(buffer, 1024);

        float *mfcc_input = compute_mfcc(frontend, buffer); // 提取MFCC特征
        int result = model->detect(model_data, mfcc_input);

        if (result == WAKEUP_WORD_DETECTED) {
            play_beep(); // 播放提示音
            stop_kws();
            start_asr_recognition(); // 启动ASR
            break;
        }
    }
}

看到没?MFCC特征提取 + 本地推理,全程离线。整个过程就像一个微型“耳朵+大脑”组合,安静又高效地守候在你身边。


💬 听懂你:把“七点半叫我起床”变成机器能执行的命令

语音变文字只是第一步。接下来才是真正的挑战: 你怎么知道用户说的是“明天早上七点半”而不是“今晚七点半”?

这就轮到 自然语言理解(NLU)模块 登场了。它不像大模型那样天马行空,而是专注在一个小领域里做到精准——毕竟我们只关心“设闹钟”这件事。

举个例子:

“十分钟后提醒我喝水”

这句话对人来说很简单,但对机器来说,需要拆解出两个关键信息:
- 意图(intent) :我要设一个闹钟
- 时间(time) :当前时间 + 10分钟

我们可以用规则匹配来做初步处理。比如正则表达式抓取“X分钟后”、“明天Y点”这类模式,再配合一个轻量级时间解析库(比如Python里的 dateutil.parser ),就能把模糊的时间描述转化成精确的时间戳。

来看一段模拟实现:

import re
from dateutil import parser as time_parser
from datetime import datetime, timedelta

def parse_alarm_command(text):
    patterns = [
        r'(?P<time>.+)(?:叫|提醒|喊)(?P<user>.+)?(起床|开会|吃药)',
        r'设置(?:一个)?闹钟(?:在|为)(?P<time>.+)',
    ]

    for pattern in patterns:
        match = re.search(pattern, text)
        if match:
            time_str = match.group('time')
            try:
                alarm_time = time_parser.parse(time_str, fuzzy=True, default=datetime.now())

                # 特殊处理相对时间
                if "分钟后" in time_str:
                    mins = int(re.search(r'(\d+)分钟', time_str).group(1))
                    alarm_time = datetime.now() + timedelta(minutes=mins)
                elif "小时后" in time_str:
                    hrs = int(re.search(r'(\d+)小时', time_str).group(1))
                    alarm_time = datetime.now() + timedelta(hours=hrs)

                return {
                    "intent": "set_alarm",
                    "timestamp": int(alarm_time.timestamp()),
                    "description": text.replace(time_str, "").strip()
                }
            except Exception as e:
                print(f"时间解析失败: {e}")
                return None
    return None

虽然这是Python写的,但在实际部署时会编译成C/C++版本运行在MCU上,内存占用压到64KB以下。如果想更智能一点,也可以集成TinyNLP或MobileBERT这类微型语义模型,提升复杂语句的理解能力。

重点在于: 不做全能选手,只做专精专家 。在这个限定场景下,准确率轻松超过92%,完全够用。


⏰ 记住你:RTC + 调度器,让提醒永不迟到

终于拿到了结构化指令:“intent: set_alarm, timestamp: 1712345678”。下一步,就是让它按时响起来。

这里的关键是 实时时钟(RTC) 闹钟调度器 。RTC通常搭配一个32.768kHz的晶振,即使主芯片休眠,它也能靠纽扣电池继续走,精度可以做到±2秒/天,比很多手表还准。🕒

而调度器则像个贴心的日程管家,负责管理最多10个闹钟任务。每个闹钟都存进Flash里,断电也不丢。每天主循环中,它都会检查一遍:“现在有没有该响的闹钟?”

核心逻辑长这样:

struct Alarm {
    uint32_t timestamp;
    bool enabled;
    bool repeat_daily;
    char label[32];
};

Alarm alarms[MAX_ALARMS] = {0};
RTC_TimeTypeDef current_time;
RTC_DateTypeDef current_date;

void check_alarms() {
    get_current_rtc_time(&current_time, &current_date);
    uint32_t now_ts = convert_to_timestamp(&current_time, &current_date);

    for (int i = 0; i < MAX_ALARMS; i++) {
        if (!alarms[i].enabled) continue;

        if (abs(now_ts - alarms[i].timestamp) < 60) { // 容差1分钟
            trigger_alarm(i);
            if (!alarms[i].repeat_daily) {
                alarms[i].enabled = false;
            } else {
                alarms[i].timestamp += 86400; // 明天同一时间
            }
            save_alarms_to_flash();
            break;
        }
    }
}

void trigger_alarm(int idx) {
    play_alarm_tone();
    flash_led(5);
    display_show_message("闹钟响了!", alarms[idx].label);
}

支持一次性、每日重复、每周循环……甚至连“贪睡(Snooze)”功能都能加进去:按一下按钮,推迟5分钟再响。

而且为了避免尴尬,系统还会自动拦截“过去时间”的设置请求,比如说“昨天下午三点提醒我开会”——这种无效指令会被默默修正为最近可行的时间点,用户体验瞬间丝滑不少。✨


🔧 整体协作:一条完整的语音指令是如何被执行的?

让我们把镜头拉远,看看整个系统的协作流程:

[麦克风] 
   ↓ (PCM音频流)
[KWS引擎] → 检测“Hi ChatBox”
   ↓ (唤醒触发)
[ASR引擎] → 转录“十分钟后提醒我喝水”
   ↓ (文本串)
[NLU引擎] → 解析出 intent=set_alarm, time=+10min
   ↓ 
[闹钟调度器] ↔ [RTC模块]
   ↓
[蜂鸣器 / LED / LCD] → 到点提醒

各模块之间通过事件队列通信,彼此独立又紧密配合。这种松耦合设计不仅提升了稳定性,也让未来扩展变得容易——比如新增“语音查天气”、“语音控制灯”等功能,只需插入新的NLU规则和执行模块即可。


🛠 实战中的那些细节考量

真正落地一个产品,光有技术链还不够,还得考虑现实世界的“坑”。

  • 麦克风选型 :我们选了INMP441这类数字MEMS麦克风,信噪比>60dB,远场拾音效果好,哪怕你在房间另一头说话也能听清。
  • 抗噪能力 :加入谱减法降噪算法,在厨房、客厅等嘈杂环境依然稳定工作。
  • 电源管理 :KWS运行在低功耗核心上,平均待机电流不到1mA,电池供电可用数周。
  • 用户反馈 :每次成功设置后,系统会用TTS播报确认信息,比如“已为您设置10分钟后喝水提醒”——听得见的安心感很重要。
  • 交互容错 :支持取消指令,如“取消刚才的提醒”,避免误操作带来的困扰。

这些看似微小的设计,恰恰决定了用户体验是从“能用”到“好用”的跨越。


🚀 结语:语音,正在成为最自然的操作方式

回头看,这个小小的HiChatBox,其实完成了一次完整的AI闭环:
听见 → 听懂 → 记住 → 执行

它不需要连接云服务器,不依赖智能手机,也不需要复杂的界面。只要一句话,就能帮你管理时间。而这背后,是KWS、ASR、NLU、RTC、调度器等多个模块的精密协作,是在有限资源下的极限优化。

更重要的是,它的架构足够模块化、可移植性强,完全可以迁移到其他场景:
- 给老人做的语音备忘录?
- 工厂里的免手操设备控制?
- 孩子的学习助手?

都不是梦。🌈

未来的智能设备,不该是让人去适应机器,而应该是机器主动理解人。语音,作为人类最原始、最自然的交流方式,注定将成为下一代人机交互的核心入口。

而像HiChatBox这样的轻量化终端,正在让“听得懂、记得住、做得准”的智能体验,一步步走进千家万户。🏡💬

也许有一天,我们不再需要点击图标、滑动页面,只需要轻轻说一句:“帮我记住这件事。”
世界就会替我们记下。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐