起点是一盏碍眼的灯
桌上那盏小夜灯是搬家时随手买的,开关在底座后面,每次关灯都要摸黑找半天。有天晚上我盯着它想:如果它能听懂我说「关灯」,这事就解决了。这个念头本身没什么稀奇,稀奇的是我居然真的去做了。
我手边有一块吃灰的树莓派 Zero 2 W、一个 USB 麦克风、一个继电器模块和几根杜邦线。目标很朴素:对着它说一句话,灯亮或灯灭,最好还能回我一句。
硬件先跑通
第一步不是写代码,是确认继电器真的能控制那盏灯。小夜灯是 USB 供电的,我没有拆它,而是把 USB 线剪开,正极串进继电器的常开触点。这样树莓派只负责给继电器信号,灯的供电还是原装的,安全上也省心。
接线前我把树莓派断电,接好后再上电,用 GPIO 命令行工具手动拉高拉低引脚,听到继电器「咔哒」一声,灯亮了。那一刻比后面写多少代码都踏实。
要注意的是继电器模块有高低电平触发的区别,我买的那块是低电平触发,第一次测试时灯的状态和预期相反,查了模块丝印才反应过来。这种小坑不值得写进教程,但每个动手的人都会遇到。
让声音变成动作
语音识别我没有用云端服务。桌面灯是个常开设备,一直上传音频让我不舒服,而且断网就废了。我选了本地方案:用 Vosk 的小模型做离线识别,只认几个固定词——「开灯」「关灯」「亮一点」「暗一点」。
代码结构很简单:一个 Python 脚本循环读麦克风数据,喂给识别器,命中关键词就调用对应的 GPIO 操作。识别率不算高,安静环境下够用,电视开着的时候经常听错。我的处理办法是加一个确认音:识别到指令先响一声短音,再执行动作,这样至少我知道它听懂了什么。
「亮一点」和「暗一点」需要调光,继电器做不到。我换了个思路,用 PWM 控制一个 MOS 管模块来调灯的实际亮度,继电器只负责总开关。两套控制并存,代码里用一个状态变量记住当前亮度,避免互相打架。
让它回一句话
只会听不会说,总觉得少了点什么。我用 espeak-ng 做本地语音合成,声音机械,但胜在离线、启动快。每次执行完指令,它回一句「好」或者「灯关了」,短句比长句自然得多。
这里有个细节:麦克风和喇叭同时工作时会互相干扰,喇叭的声音被麦克风收进去,偶尔触发误识别。我在代码里加了一个简单的静音窗口,说话期间不处理麦克风输入,问题就没了。
整套东西塞进一个木盒里,灯放在盒子上,麦克风从侧面开孔伸出来。走线很乱,但盖上盖子看不见。
它现在的作用
这盏灯现在每天在用,主要场景是睡前躺床上不想起身。它也会出错,比如我把「关灯」说快了它听成「开灯」,然后我再说一遍。这种不完美反而让它像个真实存在的东西,而不是一个演示视频。
做这个的过程中我最大的收获不是语音识别,而是发现很多「智能」需求其实可以用很笨的办法满足:固定词、本地模型、一句确认音,成本不到一顿饭钱。下一步我想给它加一个光线传感器,天黑了自动亮,连话都不用说。
