
简介火山引擎语音识别与Home Assistant智能家居的深度集成项目是一套面向智能家居开发者和Home Assistant用户的开源式语音转文本服务方案。它重点解决在本地智能家居平台中接入云端语音识别能力、实时处理用户语音指令的问题原生支持流式语音识别与语音活动检测可在嘈杂环境中准确捕捉有效口令。压缩包共16个文件整体仅64KB核心包含3个Python脚本及编译文件、2个JSON配置、文档与图标等素材自定义组件目录提供了Home Assistant集成所需的完整结构配置文件用于设定认证参数与服务地址附带说明文档可引导快速完成安装部署。目前已有210人学习下载适合希望为智能家居系统增加自然语音交互的中高级玩家参考。包内附赠的Word与文本说明详述了配置流程、使用指南及常见问题排错思路结合HACS清单与许可证文件既便于通过社区商店集成也支持开发者按具体场景二次开发多语言支持与可配置安全机制使其能灵活适配不同用户的隐私偏好与部署环境。1. 从“智能家居听得懂人话”说起火山引擎ASR与HA集成能解决什么问题晚上躺在沙发上说一句“把客厅灯光调到影音模式”三秒后灯真的变了颜色——这不是科幻片而是你本地跑着HomeAssistant、前端挂着一个实时语音转文本服务时最普通的场景。但真去做的人会碰上一堵墙本地离线识别词库太小稍复杂的指令就翻车直接调云端HTTP录音识别又要等整段说完才返回交互像对讲机。这个zip项目解决的正是“让HA拥有低延迟、可定制的语音转文本能力”这件事。它把火山引擎ASR的流式识别能力封装成HomeAssistant的集成服务支持VAD语音活动检测、自定义认证参数和服务地址、多语言切换。也就是说你不用自己写WebSocket握手逻辑也不用纠结VAD阈值该设多少资源里已经把组件搭好你填参数就能用。适合两类人一类是本地识别玩腻了、想让中文指令识别更准的HA用户另一类是需要在私有化或内网环境里接火山引擎ASR的开发者。下文直接拆解架构、参数和踩坑记录。2. 先看架构再动手流式识别、VAD与自定义服务地址的选型逻辑2.1 为什么是火山引擎ASR而不是本地识别本地离线识别方案在HA社区里很常见比如faster-whisper、vosk优点是断网可用、隐私数据不出屋。但真正用在小户型智能家居场景里你会发现一个尴尬点识别准确率集中在“开灯”“关空调”这类固定命令上一旦你说“把主卧窗帘打开一半再关闭卫生间的灯”本地引擎大概率要断句错乱。火山引擎ASR这类云端服务在中文长句、口语化表达上明显更稳而且支持流式输出第一个词出来时你就能看见文本在跳感官上更接近“实时对话”。另一个选型理由是VAD。VADVoice Activity Detection解决“什么时候该开始识别、什么时候算说完”的问题。HA接麦克风后如果直接把原始音频流全部送给ASR那就会把空调噪声、走路声、电视声全当成语音识别结果会是一堆乱码。火山引擎提供了可配置的VAD参数你只需要把音频流持续推给它引擎自行判断语音段起点和终点。相比之下本地方案往往需要自己前置VAD逻辑调起来更玄学。当然云端识别不是没有代价你要有公网或内网可用的服务地址还要管理好API密钥这正是这个项目里“可配置认证参数”部分要解决的事。2.2 流式识别与实时语音转文本的差别传统的语音识别方式叫“一次性识别”你用录音笔录完一整段把音频文件POST给识别接口等一两秒后拿到整段文本。这种方式适合“录音转写”但不适合智能家居交互——你不能说完一句话后干等两秒再看到反馈。流式识别则是建立一个持续的WebSocket连接客户端一边采集麦克风音频一边往服务端推二进制帧服务端每识别出一段就回传文本典型表现是“边说边出字”。打开输入法语音输入那就是流式识别。那么VAD在这条链路里扮演什么角色看下表就清楚了环节无VAD有VAD静音处理全量音频送入识别引擎静音段也消耗并发额度引擎按预设阈值跳过静音段只处理有效语音断句依赖识别引擎自动识别长句容易粘连在说话停顿处自动截断一次返回一个完整短句交互延迟说完后还要等尾部静音被处理通过终点检测快速返回结果延迟可降低30%以上这个项目里VAD是内置在请求配置中的。你不需要自行实现端点检测但需要理解两个参数起始静音阈值和结束静音阈值。前者决定“大约静音多久后开始算一句话”后者决定“说完后静音多久判定结束”。阈值设得太小呼吸声都会触发识别设得太大一句话说完要等很久才有结果。后面第4章会给调参方向。2.3 自定义服务地址和认证参数集成能不能搬到内网和专属集群很多文档型集成把服务地址硬编码在代码里换环境就要改源码。这个项目真正体现“工程可用性”的地方在于服务地址、认证参数全部抽成了配置项。你可以在configuration.yaml里指定service_url它可能是火山引擎公有云的WebSocket网关也可能是你们公司私有化部署的ASR集群地址甚至是通过Nginx代理转发出来的内网地址。这让同一套HA配置能平滑地从小规模家庭环境迁移到办公室或多节点环境不需要动一行Python代码。认证参数这部分资源里至少包含app_id、access_token两样东西。如果你用的火山引擎版本还要求secret key配置模板里也会预留字段。关键在于这些参数往往是“长期有效的”但它们属于高敏信息一旦泄露别人就能消耗你的额度。所以在HA配置里填写时我建议优先使用环境变量或secrets.yaml引用不要直接明文写死。后面第3章会给出具体的配置模板和引用方式。3. 把zip包变成可用服务安装配置与认证参数逐项拆解3.1 解压后先看什么HA自定义组件的部署路径拿到这个zip包后第一步不是打开某个Python文件读代码而是先确认它的目录结构。HA的自定义组件通常是放在配置根目录下的custom_components/里每个组件一个独立文件夹。这个集成项目解压后你应该能看到类似custom_components/volcengine_asr/这样的目录具体名字以资源内实际文件夹名为准里面包含__init__.py、manifest.json、config_flow.py或sensor.py等文件。部署动作非常简单先完全停止HA进程然后把整个组件文件夹拷贝到HA配置目录下的custom_components/内。注意不要只拷贝里面文件要连文件夹一起拷否则HA找不到组件。拷贝完成后检查自家HA版本是否满足manifest.json里的requirements字段比如某些版本需要websockets库。如果缺少HA启东时会在日志里报ModuleNotFoundError到时候手动pip install websockets即可。部署完成后启动HA到「开发者工具」→「YAML」页面查看配置是否加载成功或者直接看日志里有没有Platform volcengine_asr相关字眼。3.2 configuration.yaml里的核心配置段从app_id到服务地址的完整参数表这个集成不需要触发配置流程直接在configuration.yaml里写一段配置就好。下面是一份完整的示例我把可自定义的核心参数都列出来了你在使用时可对照自己的环境调整# configuration.yaml volcengine_asr: app_id: your_app_id # 火山引擎控制台创建应用后获得 access_token: your_access_token # 调用API用的token可在控制台生成 service_url: ws://192.168.1.20:8080 # 自定义ASR服务地址支持ws/wss language: zh-CN # 识别语言多语言可填en-US sample_rate: 16000 # 音频采样率单位Hz必须与输入音频一致 vad_enabled: true # 是否启用VAD建议开启 vad_start_silence: 500 # VAD起始静音时长单位ms vad_end_silence: 800 # VAD结束静音时长单位ms auth_override: false # true时用下方自定义头否则默认用token认证这里解释几个容易踩的参数逻辑app_id和access_token是火山引擎控制台里的凭证。app_id是应用唯一标识access_token是调用凭证。部分版本会同时需要secret_key做签名如果资源里的起配置模板有对应字段记得一并填上。service_url是这套集成的灵魂。用公有云时填入火山引擎官方WebSocket地址私有化部署时填你内网ASR服务地址可以用ws://但生产环境我强烈建议弄个wss://的反代避免音频明文在网络上飞。这里的示例填了内网地址展示的就是自定义能力。language是文本识别语言。中文用zh-CN英文用en-US多语言模式下可以配置多个语言代码但实际生效以第一条为准。vad_start_silence和vad_end_silence的单位是毫秒。vad_start_silence指的是从有声音到开始识别前需要的静音先导段vad_end_silence是指一个语音段结束后再等多久帧静音就判定一句话结束。这俩参数直接决定了交互的“跟手程度”。如果你不想把这些参数明文写在configuration.yaml里可以用HA的secrets.yaml引用# secrets.yaml asr_app_id: your_app_id asr_access_token: your_access_token然后在configuration.yaml里写成app_id: !secret asr_app_id。这样以后换token只需要改secrets文件不用动主配置。3.3 启动服务并验证HA是否加载成功配置写完后到HA的「开发者工具」→「服务」里搜索这个组件提供的服务名——通常是volcengine_asr.transcribe。如果服务里能看到它说明组件加载成功了。验证方式有很多最直接的是在服务里传入一个音频文件路径或文本占位符看返回值是否正常。但这个服务有些复杂因为它不只是“转文本”还要管理音频流输入。更稳妥的验证路径是先检查日志有没有报错。执行以下步骤打开HA前端进入「系统」→「日志」。过滤关键字volcengine_asr确认没有红色报错。如果日志显示connected to ASR service说明WebSocket握手成功。如果一直显示auth failed回到第3.2节检查app_id和access_token有没有复制到位注意控制台里生成的token可能带空格粘贴后需去除。组件加载成功后再谈使用。你要把麦克风音频源接入HA常见做法是通过ESP32等设备采集音频推送到HA的MJPEG或Stream服务再用这个集成把音频流转成文本。这里的配置本身就是面向“接入音频流”的场景设计的因为流式识别必须持续推音频否则识别线程会空转超时。4. 流式语音转文本实战VAD参数与音频流处理细节4.1 开启VAD判断一句话何时说完了VAD的官方解释是语音活动检测但在智能家居场景里它的核心价值是“自动断句”。你对着HA说“把客厅空调调到二十六度”说完后如果VAD不生效组件就一直等着直到超时如果VAD的结束静音阈值设得太短你只是喘口气它就把一句话截成了两段。这个度很微妙所以资源里的配置把VAD参数单独暴露出来是有道理的。我的建议是从两个方向调先调vad_start_silence。它控制“说话前的静音容忍时间”。很多人把这理解为“唤醒词后静音多久开始识别”实际正好相反——它是说在环境持续有底噪的情况下要连续多久的静音才认为语音真正开始。家庭环境底噪通常在30-50dB建议初值设为500ms。如果发现你还没开口麦克风就开始录了说明这个值太小加大到700-900ms。再调vad_end_silence。它控制“一句话说完了的静音判定”。家用场景里人的正常语速说完一句话后停顿时长在600-1200ms之间。设太短比如300ms会把“然后”这种词拆成两次识别设太长2秒会让指令响应像卡了壳。初值建议800ms实际体验觉得反应慢就降到600ms觉得断句碎就升到1000ms。在资源组件里VAD不是单独一个开关那么简单。vad_enabled: false时整个识别链路变成一个“持续转写模式”每帧音频都被送进引擎文本结果会连续输出但不会有“一句话结束”的边界。这个模式适合做实时字幕不适合命令式控制。所以家庭自动化场景务必保持vad_enabled: true。4.2 处理音频流从HA音频流到WebSocket二进制帧的代码思路这个集成的核心工作说起来很直白把HA侧传过来的PCM音频编码成二进制帧通过WebSocket发送给ASR服务端再接收服务端返回的文本JSON。下面这段代码是这套逻辑的常见实现我拆给你看import asyncio import websockets import json class VolcengineASRClient: def __init__(self, config): self.config config self.ws None async def connect(self): # 建立WebSocket连接自定义服务地址在这里起作用 self.ws await websockets.connect( self.config[service_url], ping_interval20, # 每20秒发一次心跳防止服务端断开 ping_timeout10 ) # 先发送一个发起请求的JSON文本帧 init_payload { app: { appid: self.config[app_id], token: self.config[access_token] }, user: {uid: ha_component}, audio: { format: pcm, rate: self.config[sample_rate], bits: 16, channel: 1 }, request: { language: self.config[language], enable_vad: self.config[vad_enabled], vad_silence: self.config[vad_end_silence] } } await self.ws.send(json.dumps(init_payload)) async def send_audio(self, pcm_chunk: bytes): 把来自HA音频设备的PCM数据直接发到ASR服务端 if self.ws is None: await self.connect() await self.ws.send(pcm_chunk) # 音频帧是二进制不加JSON封装 async def receive_text(self): 接收ASR返回的识别结果格式是JSON文本帧 response await self.ws.recv() if isinstance(response, bytes): return None # 某些服务端可能回二进制这里直接忽略 data json.loads(response) if data.get(code) 0: # 返回的文本在result.payload.text里实际字段以官方为准 text data.get(result, {}).get(payload, {}).get(text, ) return text return None这段代码的逻辑分三步先发送初始化帧再持续发送二进制音频帧最后循环接收结果。ping_interval20这句很关键因为很多ASR网关在30秒内没有收到音频帧会自动断开连接。在实际HA集成里音频采集用的是HA Media Player的audio_stream回调拿到PCM数据后直接调用send_audio中间没必要做格式转换。如果你发现音频采样率不匹配比如麦克风输出是48000Hz但ASR配置要求16000Hz就需要先降采样否则识别结果会飘。参数方面vad_silence字段我这里直接用了配置里的vad_end_silence这是因为服务端的VAD引擎会用这个值作为“一句话结束”的判定。这个字段只随初始化帧发送一次中途改配置不会生效所以你每次调整VAD阈值后都要重启HA组件。4.3 多语言与文本输出格式化标题里明确写了“多语言支持”这在代码里对应的是language字段。火山引擎ASR支持中文、英文、粤语等但实际使用中我发现切换语言不只是改一个代码。比如你要让HA用英文识别但家里的设备名字是中文拼音这时候就要在HA侧做一层翻译映射。资源里的做法是识别结果以文本形式输出到sensor.asr_text实体状态然后你再通过HA自动化去匹配这个文本。所以多语言真正要做的是“识别文本”与“触发指令”之间的转换层。一个实用的配置思路是把语言参数也做成可切换的input_select辅助元素然后通过自动化动态调用集成服务并修改language字段。但注意流式识别的服务端往往在建立连接时固定采用语言代码中途切换需要重连WebSocket。所以在资源里语言配置的优先级是它只影响下一个新连接不影响当前已建立的连接。这也解释了为什么有时候你改了language等半天没反应——因为当前流的连接还活着需要你主动重启或等它超时断开。文本输出格式化方面组件会把识别到的文本统一去掉首尾空白多个短句用空格拼接成一个完整句。如果你想让它直接输出“turn_on_light”这种格式就得在HA侧用模板处理# 模板示例把识别文本转换为自动化可匹配的action value: {% set text states(sensor.asr_text) %} {{ text.lower().replace( , _) }}这样做的好处是无论识别出来的是中文还是英文都能归一化成小写连写形式方便后续自动化条件匹配。5. 避坑指南认证失败、断流、延迟与识别不准的排查记录5.1 明明填对了token却报认证失败现象HA日志里有auth failed或Invalid app_id or token但自己打开控制台复制了几遍没看出有问题。原因最常见的是access_token复制时带上了空格或换行符。尤其是从网页控制台复制UUID格式的token很多浏览器会在末尾加一个不可见字符。另一个原因是token被重置过旧token在过期后即使填对了也无法通过。解决先在命令行里做一次裸测试排除字符串空格问题。用curl直接请求一遍接口看返回是否正常。如果curl正常而HA报错那就查看日志里打印出来的token长度是否和预期一致注意日志会脱敏但能看到长度特征。我一般会把token写进secrets.yaml然后用!secret引用这样可以在secrets文件里看清楚有没有多余字符。5.2 流式识别总是断流连接被服务端自动关闭现象HA里识别文本能出几句但每隔一两分钟就中断。日志显示Connection closed by remote.原因两个方向要么是服务端在空闲状态下主动回收连接要么是音频流在HA侧没有被持续推送。VAD开启状态下如果环境安静很久服务端会认为无人说话自动关闭空闲连接以释放资源。解决在客户端代码里加心跳或者更务实一点——把VAD的结束静音阈值调大。但真正有效的方案是让HA端的音频流持续推帧即使没有声音也发送静音帧PCM全0。我见过很多集成实现会把静音帧过滤掉这反而是断流的核心原因。正确做法是保证底层音频采集始终在跑无声音时填充静音数据。还有一点是WebSocket连接要加自动重连机制断线后指数退避重连避免全家断网后永久失联。5.3 识别延迟高说完话后等了整整一秒多才出结果现象一句话说完了灯光要延迟到第二秒后才亮体验像对讲机。原因VAD结束静音阈值设置太大。如果你设vad_end_silence: 1500那你说完最后一个字后还要再等1.5秒静音服务端才判定这句话结束然后返回文本。这种延迟完全可感。解决先把vad_end_silence调到600ms。如果还是延迟明显再看是不是音频流本身的缓冲间隔太长。有些ESP32采集音频默认每200ms推一次加上网络波动累积延迟就上去了。把音频块切成更小比如50ms一帧能明显降低首句结果延迟。另外如果你不需要整句检测只想让识别快速出中间结果可以用流式识别自带的“临时结果”回调但那个在配置里通常需要额外打开enable_partial开关。5.4 多语言切换不生效改了语言配置还是老样子现象在configuration.yaml里把language改成en-US并重启HA但识别结果仍然是中文。原因服务端在连接建立时用language字段创建了识别引擎这个字段只在初始化帧里生效。很多集成在重启后会复用上一次的WebSocket连接池导致新配置没传入新连接。解决彻底重启HA或杀掉组件的连接进程。如果你记得当前正在运行中的连接ID也可以通过尝试调用disconnect服务来强制关闭然后再触发一次新的识别流程。之后再看日志里初始化帧打印的语言代码确认新值已生效。5.5 识别文本已经更新但HA自动化就是触发不起来现象sensor.asr_text状态已经变化但自动化没有执行任何动作。日志里能看到状态值但更新的时间和预期不对。原因HA的状态属性更新和平台状态实体更新是两回事。自动化通常监听的是实体状态值而不是属性。组件如果把文本写到attributes.text里而不是实体本身自动化要改为监听state带attribute的方式才能捕获。解决先确认组件把文本放在哪里。如果是state直接trigger: platform: state, entity_id: sensor.asr_text。如果是属性自动化写法为trigger: - platform: state entity_id: sensor.asr_text attribute: text另一个坑是VAD模式下如果一句话识别结果为空字符串组件不会更新状态所以自动化迟迟不触发。处理办法是让组件在空结果时返回一个特殊占位符比如“”然后自动化里过滤掉这个空值。这会让你少走好几个小时的弯路。6. 进阶技巧用日志与阈值调优把ASR接到HA自动化里6.1 调试模式打开详细日志看每次识别的置信度火山引擎ASR的返回结果里除了文本通常还带一个置信度分数confidence。这个值在0到1之间表示引擎对识别结果的把握程度。资源组件如果把这个字段透传到HA日志中你就能通过观测它来调整麦克风位置、采样率、VAD参数。打开方式通常是在configuration.yaml里加debug: truevolcengine_asr: debug: true开启后每次识别都会在日志里打印一行类似[ASR] text“打开窗帘” confidence0.93 duration1.2s的记录。我一般用这个日志做三件事一是看置信度低于0.7的指令有没有被误触发二是对比不同说话人距离下的置信度变化调整麦克风增益三是统计一句话平均时长反过来验证VAD阈值是否合理。如果组件没提供debug开关也可以自己写一个模板传感器去监听识别事件把置信度存进input_number然后在HA看板里显示出来。调试的思路是一样的——先让数据可见再谈调优。6.2 用识别结果触发HA自动化一个完整的自动化示例把ASR接到自动化里最怕的是全字符串精确匹配。比如你说“打开客厅灯”识别结果可能是“打开客厅灯”也可能带尾部语气词“啊”。更稳的做法是使用状态模板做包含匹配而不是强制相等。这里给出一个我常用的自动化模板automation: - alias: 客厅灯光语音控制 trigger: - platform: state entity_id: sensor.asr_text condition: - template: {{ 打开客厅灯 in states(sensor.asr_text) }} action: - service: light.turn_on target: entity_id: light.ke_ting_deng注意condition里用了in而不是这样即使识别结果带了多余语气词也能命中。还有一点如果一句话里包含了“关闭”和“打开”不要用条件模板去处理直接在action里用service_template根据关键词动态选择服务。我从几次翻车里学到一个习惯所有语音自动化触发条件里先加一个not: {{ 取消 in states(sensor.asr_text) }}作为锁——因为有时候你会说“算了不打开客厅灯了”如果直接匹配“打开客厅灯”就会被误触发。从那以后我每次接新的语音命令都强制走一遍“加取消语反锁”的流程别小看这一句它能帮你挡住最尴尬的误操作。希望这个习惯也能帮到你。本文还有配套的精品资源点击获取