传统智能音箱的缺点:

  • 成本高,严重依赖各家生态,各家生态互不兼容,用户一旦入坑就只能一条路走到黑。
  • 执行一条语音命令需要先语音唤醒,效率低,影响使用心情。
  • 智能音箱一直联网监听,隐私风险大。

下面先来看看我制作的语音控制节点,不管是识别率,识别速度,识别距离都是令人满意的,而且不光可以控制接入Home Assistant的智能家电,还可以控制各种红外遥控的传统家电。

之所以叫做“语音控制节点”是因为目前这个手工焊接的版本实在是太过简陋,实在不像是一个“音箱”。另一方面这一个板子上不光有语音控制功能,还可以添加各种传感器,未来可以将自身数据与其他不同位置的设备共享,是我智能家居这个大工程当中的一个“节点”。

硬件成本

硬件 成本
ESP32开发板 16元
SU-03T 10.5元/片+6元运费
喇叭 4欧3瓦 2.8元
咪头 0.21元
万用板5x7cm 0.68元/片
5mm红外发射头(940nm) 1.9元/20只
红外接收头(型号随意) 0.23元/只

另外还需要运行Debian系统的x86架构的设备(软路由、NAS等),用来安装Home Assistant平台。

部署Home Assistant并安装ESPHome

Home Assistant是是一个成熟完整的基于 Python 的开源智能家居系统,设备支持度高,支持自动化(Automation)、群组化(Group)、UI 客制化(Theme) 等等高度定制化设置。
ESPHome 是一个可以刷进ESP系列芯片的系统,可以通过简单但功能强大的配置文件来控制你的 ESP8266/ESP32,并通过Home Assistant对其进行远程控制。
Home Assistant就像是大脑,刷了ESPHome的ESP32就像是耳朵、眼睛、手。 Home Assistant根据ESPHome传感器传来的信息感知外界的环境变化,然后控制ESPHome或者其他设备执行设定好的指令。
大家可以看看目前我Home Assistant的界面
HA主界面

  • 开关控制NAS开关机,详见Homeassistant远程开关NAS
  • WLED总开关,灯光效果切换,详见用WLED控制流光灯带并接入Home Assistant
  • 控制空调的红外遥控开关,详见利用ESPHome自制万能遥控器
  • 探测小米手环蓝牙信号强度的指示器,详见利用ESPHome进行蓝牙设备定位
  • 智能插座的控制开关(我买的BroadLink MP1-1K3S2U可以直接接入Home Assistant)
  • DLNA控制投影仪,天气……
    安装Home Assistant需要有NAS、软路由或者树莓派之类的开发板。尽管Home Assistant支持Windows、macOS、Linux等平台安装,兼容x86、ARM、AArch64架构。但我强烈建议在x86架构并安装Debian的设备上部署Home Assistant,因为要在Home Assistant上安装ESPHome,而ESPHome的编译链对架构和平台有要求。要注意Home Assistant有多种安装方式,不同安装方式对设备的要求不同,提供的功能有差别,详情见下图:
    HA安装方式比较
    因为我们需要Add-ons功能来安装ESPHome,必须选Supervised或OS的安装方式。OS安装方式只适用于树莓派等开发板,大部分人只能Supervised方式安装。个人认为这一步需要一点Linux知识,篇幅有限大家自己按照官方教程安装Home Assistant
    当然如果你有树莓派那当我前面都没说,直接选择32-bit版本的HA OS系统镜像刷进去就行。不选64位的原因是ESPHome的编译链不支持AArch64架构,ARM应该是可以的,但我没有测试过。
    安装完Home Assistant,浏览器进入http://你的服务器IP:8123,进行一些初始化设置,就进入Home Assistant的主界面了。后面安装ESPHome就简单了,详见ESPHome官方教程 Installing ESPHome Dashboard

焊接

SU-03T自带功放,所以喇叭和咪头都是直接接在SU-03T上,如下图所示。
SU-03T示意图
红外发射头和红外接收头都接在ESP32上,焊接电路图见之前的帖子利用ESPHome自制万能遥控器
焊接其他部分其实很简单,主要是将SU-03T中可以输出脉冲的GPIO同ESP32中可以输入的GPIO连起来,VCC和GND都连起来。
我是按照下表连接的

SU-03T的引脚 ESP32的引脚
B2 GPIO25
B8 GPIO26
A27 GPIO32
B3 GPIO33
A25 GPIO34
A26 GPIO35
GND GND
VCC VCC

最后找ESP32的VCC时发现这个开发板没有引出来,无奈从PCB上找到了一个直接与VCC相连的芯片焊脚接了出来。
成品高清大图,这实在太丑,有空去打个板子:
正面
反面

对SU-03T语音模块编程

SU-03T有以下优点:

  • 价格便宜,识别率不错,可用GPIO口多
  • 编程简单而且免费,而且普通USB-TTL就可以刷入
  • 可以自定义唤醒词,而且支持免唤醒词就执行语音命令
    SU-03T语音模块的固件完全在网页上编辑和生成,目前完全免费,访问智能公元
    部分配置如图所示,B2、A25、A26、A27这四个GPIO依次对应开灯、关灯、开空调、关空调这四个指令,当其中一条语音命令被接受到,对应的GPIO就会发出一次周期为200ms的脉冲信号(引脚其余时间默认低电平)。
    智能公元
    编辑好了就在线生成SDK,等待10-30分钟就可以下载SDK了。烧录工具、固件、说明书等都在SDK压缩包中。
    烧录固件很简单,先不要给模块上电,用USB-TTL的TX接连SU-03T的B6,用USB-TTL的RX接连SU-03T的B7,打开烧录软件并点击“烧录”,最后给模块上电并等待烧录结束。
    烧录接线图
    UniOne

使用ESPHome编写和烧录ESP32的固件

由于ESP32的Flash只有4MB,并不能将所有的功能都放下,所以ESPHome的思路是根据使用者需要的功能编译固件,需要什么功能就把什么功能编译进去。
在ESPHome界面,如图所示新建一个ESP32设备
ESPHome新建设备
按向导提示输入Wi-Fi的SSID和密码,向导的最后一步就是点击“install”,然后回弹出如下的提示框。
ESPHome下载固件
第一次往ESP32中烧录固件必须使用有线的方式。首先选择“Manual download”,等待固件编译完成后下载固件到本地电脑。然后返回,再刚刚新建的那个配置的右下角点击菜单,再次选择“install”,这次选择“Plug into this computer”,这个功能可以直接利用你的网页浏览器(仅限Chrome内核浏览器,如Edge)控制串口给ESP32刷机。刷机前先将ESP32的GPIO0和GND用一根杜邦线短接起来(开机前拉低GPIO0使ESP32进入下载模式),然后用一根USB数据线将ESP32连接电脑。网页端按照如图所示让浏览器连接USB-Serial(不同电脑上可以COM后的数字不一样,这是正常的)
ESPHome连接串口
然后上传固件,刷写固件,等待1-2分钟烧写完成,将ESP32的USB数据线拔掉,连接GPIO0的杜邦线拔掉,然后再插回USB数据线。
等待片刻,你的ESP32应该就自动连接上你家的Wi-Fi了,在ESPHome界面应该就能看到刚刚配置的ESP32配置文件右上角有个“ONLINE”
ESPHome编辑配置
下面我们就可以对ESP32的固件当中添加各种我们需要的功能了,点击“EDIT”,我就直接粘贴配置文件了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
esphome:
name: esp32

esp32:
board: esp32dev
framework:
type: esp-idf
version: latest
# Custom sdkconfig options
sdkconfig_options:
CONFIG_COMPILER_OPTIMIZATION_SIZE: y
# Advanced tweaking options
advanced:
ignore_efuse_mac_crc: false

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:
password: ""

wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password

# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Esp32 Fallback Hotspot"
password: "PMQ3aD1W8AVF"


binary_sensor:
- platform: gpio
id: wledon
pin:
number: GPIO25
mode: INPUT
on_click:
min_length: 50ms
max_length: 300ms
then:
- homeassistant.service:
service: light.turn_on
data:
entity_id: light.wled

- platform: gpio
id: wledoff
pin:
number: GPIO34
mode: INPUT
on_click:
min_length: 50ms
max_length: 300ms
then:
- homeassistant.service:
service: light.turn_off
data:
entity_id: light.wled

- platform: gpio
id: acoff
pin:
number: GPIO32
mode: INPUT
on_click:
min_length: 50ms
max_length: 300ms
then:
- homeassistant.service:
service: switch.turn_off
data:
entity_id: switch.bedroom_ac_switch

- platform: gpio
id: acon
pin:
number: GPIO35
mode: INPUT
on_click:
min_length: 50ms
max_length: 300ms
then:
- homeassistant.service:
service: switch.turn_on
data:
entity_id: switch.bedroom_ac_switch

remote_receiver:
pin:
number: GPIO14
inverted: True
mode: INPUT_PULLUP
dump: raw
idle: 40ms

remote_transmitter:
pin:
number: GPIO27
carrier_duty_percent: 50%

switch:
- platform: template
name: "BedRoom AC Switch"
id: esp32_ac_swi
turn_on_action:
then:
- switch.template.publish:
id: esp32_ac_swi
state: ON
- if:
condition:
lambda: 'return id(temperature).state < 20;'
then:
- script.execute: turn_ac_20c
else:
- script.execute: turn_ac_27c

turn_off_action:
then:
- switch.template.publish:
id: esp32_ac_swi
state: OFF
- script.execute: turn_ac_off

esp32_ble_tracker:
scan_parameters:
interval: 1s
window: 500ms
active: false
sensor:
# RSSI based on MAC address
- platform: ble_rssi
mac_address: FA:4C:19:74:CF:31
name: "MI Band RSSI value"
- platform: homeassistant
entity_id: weather.wo_de_jia
id: temperature
attribute: temperature

script:
- id: turn_ac_20c
then:
- remote_transmitter.transmit_raw:
carrier_frequency: 38kHz
code: [9016, -4481, 620, -570, 620, -570, 619, -1670, 620, -1671, 620, -570, 621, -569, 621, -1668, 619, -572, 620, -569, 620, -570, 621, -1669, 621, -570, 621, -568, 620, -570, 621, -569, 621, -569, 622, -568, 619, -572, 619, -570, 621, -569, 619, -572, 619, -1671, 618, -571, 620, -1670, 620, -571, 619, -571, 618, -571, 619, -570, 621, -1670, 620, -570, 646, -1643, 621, -569, 621, -569, 621, -1669, 620, -571, 619, -19990, 620, -1670, 620, -571, 620, -568, 622, -569, 621, -1669, 621, -569, 617, -573, 617, -573, 620, -570, 645, -545, 621, -569, 620, -570, 619, -570, 621, -1669, 618, -572, 645, -546, 619, -571, 645, -544, 620, -571, 617, -573, 643, -547, 620, -569, 620, -571, 617, -572, 644, -546, 618, -572, 612, -578, 621, -569, 619, -1671, 617, -573, 620, -1670, 644, -1646, 619, -7223, 9010, -4489, 617, -573, 615, -575, 643, -1647, 620, -1670, 643, -546, 619, -572, 616, -1674, 617, -573, 644, -545, 618, -573, 617, -1673, 615, -574, 642, -549, 617, -572, 618, -572, 617, -573, 643, -547, 644, -546, 614, -576, 643, -547, 593, -598, 643, -1646, 643, -547, 616, -1674, 639, -551, 610, -580, 616, -574, 641, -549, 617, -1673, 592, -1699, 617, -1672, 639, -551, 642, -547, 593, -1698, 592, -598, 640, -19969, 637, -553, 593, -597, 642, -548, 642, -548, 640, -550, 592, -598, 592, -598, 593, -597, 592, -597, 642, -549, 593, -597, 594, -596, 593, -597, 592, -598, 640, -550, 593, -597, 593, -597, 593, -597, 592, -597, 593, -598, 641, -548, 640, -551, 592, -597, 642, -549, 593, -597, 593, -596, 594, -597, 593, -596, 594, -596, 593, -1697, 594, -596, 594, -1696, 594]
- id: turn_ac_off
then:
- remote_transmitter.transmit_raw:
carrier_frequency: 38kHz
code: [9021, -4476, 625, -1666, 624, -565, 624, -567, 625, -567, 621, -567, 626, -566, 622, -1666, 624, -566, 623, -1666, 626, -1691, 597, -566, 626, -1664, 625, -593, 597, -566, 624, -566, 625, -565, 624, -565, 622, -569, 624, -565, 625, -565, 625, -592, 599, -564, 624, -566, 626, -564, 626, -565, 625, -566, 623, -565, 624, -566, 624, -1666, 625, -565, 625, -1665, 626, -564, 623, -567, 624, -1666, 623, -567, 626, -19983, 625, -1665, 624, -567, 625, -564, 624, -566, 626, -1664, 623, -593, 599, -565, 624, -566, 624, -566, 624, -594, 598, -566, 622, -567, 624, -591, 598, -1666, 625, -565, 623, -567, 624, -569, 622, -564, 625, -568, 621, -569, 624, -564, 623, -567, 625, -564, 624, -566, 623, -568, 624, -566, 623, -566, 626, -566, 622, -1666, 623, -568, 623, -567, 624, -1665, 624, -7194, 9041, -4481, 626, -1665, 624, -568, 621, -566, 622, -569, 625, -567, 622, -568, 621, -1668, 622, -566, 624, -1666, 625, -1666, 623, -567, 625, -1664, 623, -594, 598, -591, 599, -566, 621, -595, 599, -565, 625, -564, 624, -566, 625, -567, 622, -569, 622, -565, 623, -569, 623, -568, 622, -567, 622, -568, 621, -567, 624, -566, 623, -1669, 623, -1692, 597, -1666, 624, -565, 625, -566, 624, -1668, 622, -565, 623, -19985, 627, -565, 626, -564, 626, -565, 623, -566, 625, -566, 623, -593, 595, -594, 600, -565, 622, -593, 597, -567, 624, -566, 623, -567, 624, -566, 625, -565, 625, -565, 626, -564, 623, -567, 623, -567, 623, -569, 622, -566, 622, -568, 622, -569, 623, -566, 623, -567, 623, -566, 624, -567, 623, -566, 624, -568, 623, -566, 622, -1667, 625, -1665, 623, -594, 596]
- id: turn_ac_27c
then:
- remote_transmitter.transmit_raw:
carrier_frequency: 38kHz
code: [9048, -4448, 653, -1665, 623, -539, 653, -537, 650, -1668, 625, -537, 654, -537, 650, -1640, 650, -539, 652, -1639, 651, -1638, 651, -539, 652, -1638, 653, -538, 651, -539, 650, -540, 652, -538, 650, -539, 652, -539, 651, -537, 652, -538, 652, -538, 652, -1638, 651, -540, 652, -537, 653, -538, 650, -567, 623, -539, 651, -539, 651, -1639, 652, -566, 623, -1639, 652, -538, 653, -537, 652, -1638, 652, -539, 651, -19956, 653, -1638, 653, -539, 652, -536, 650, -541, 650, -1666, 627, -536, 654, -536, 650, -541, 653, -537, 650, -539, 653, -565, 625, -537, 652, -538, 652, -1638, 652, -540, 650, -541, 649, -565, 625, -566, 624, -538, 651, -539, 651, -539, 653, -537, 652, -565, 624, -539, 652, -538, 652, -539, 649, -539, 653, -564, 625, -1639, 652, -539, 650, -539, 652, -565, 625, -7187, 9047, -4453, 653, -1640, 649, -566, 625, -540, 651, -1637, 652, -538, 651, -539, 651, -1638, 653, -538, 650, -1640, 651, -1640, 652, -537, 652, -1637, 653, -537, 652, -541, 649, -538, 653, -538, 652, -540, 649, -565, 626, -539, 651, -539, 653, -537, 652, -1637, 652, -538, 653, -537, 652, -565, 625, -564, 627, -537, 653, -537, 652, -1664, 627, -1636, 653, -1637, 654, -563, 626, -538, 653, -1637, 653, -537, 651, -19956, 653, -539, 654, -537, 650, -566, 625, -538, 652, -538, 652, -538, 654, -536, 651, -539, 652, -538, 653, -564, 624, -539, 651, -539, 651, -539, 651, -538, 651, -540, 651, -538, 653, -538, 652, -537, 652, -538, 652, -565, 624, -567, 625, -538, 653, -562, 626, -538, 652, -538, 651, -540, 651, -538, 654, -536, 653, -537, 651, -1665, 627, -1637, 652, -1638, 652]

改好配置文件后,点击“install”,然后用“Wireless”模式,也就是OTA升级就可以把新固件烧写进ESP32了。
这里只详细讲述配置中的binary_sensor:字键,其余配置详见我的另外三篇文章利用ESPHome自制万能遥控器利用ESPHome进行蓝牙设备定位里面都有提到。 binary_sensor:里我创建了4个传感器去检测上文提到的4个GPIO的电平变化,其中on_click:就是在GPIO口从低电平到高电平再回落到低电平的这个过程被检测到后执行的动作。所以min_length:是高电平状态最小的保持时间,必须高于这个值才会被认为是一次“click”。而之前我们对SU-03T编程时,设置一次脉冲周期是200ms,高电平和低电平时间在一个周期内应该是一半一半,所以理论上触发一次动作高电平会保持100ms,min_length:应该低于这个值,但不能太低,不然供电一个不稳定也可能会引起误触。