# 声波配网
适用声波配网的方式,你的设备需要满足以下要求:
- 设备具备麦克风,可以拾音。不需要额外配置蓝牙或者Wifi模块
选择声波配网方式,你需要做:
- 设备进入网络配置模式后,麦克风开始拾音。
- 在设备接入控制台 (opens new window)中上传相应的引导内容,以便用户在为设备配网时有一个良好的体验。
iFLYOS 使用的声波识别方案,使用了 Quiet (opens new window) 的开源方案。
# 流程介绍
设备进入等待配置的模式后,App 会持续发送声波数据,设备需要将录音数据持续传到解码器中,等待解码器回调识别结果。
- 声波数据格式
App 向设备发送的声波数据为字符串,形如以下格式
<flag>
<ssid>
<password>
其中,
flag
代表是否仅联网而不触发重新授权,1 代表重新授权,其他则代表仅配置网络。
ssid
代表要连接的 WiFi 名称。
password
代表要连接的 WiFi 密码,可能为空。
- 请求授权
设备通过上述数据连接到 WiFi 后,如果需要重新授权,需要参考 授权 API 请求授权 URL。
请求的响应中,包含字段名为 user_code
的数据,由 6 个数字构成,称之为 授权码。
设备需要将授权码告知用户(例如,带屏设备可以通过显示文本,无屏设备可以通过语音播报),用户在 App 上输入授权码后,即可打开授权页面对设备进行授权。
在此过程中,设备应当轮询 获取 token API 接口,不断向服务端请求设备 token,直至成功或返回错误。
# Android
我们通过 JNI 实现了 Quiet 库接入到 Android 中,将 module 下载到项目中,并添加到依赖。module 下载 (opens new window)
在项目的 build.gradle
中需要添加以下依赖,版本号根据当前最新版本即可
dependencies {
......
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
# 创建声波解码对象
val decoder = QuietDecoder(DecoderConfig(context, "audible"), RecordController.SAMPLE_RATE_16K)
DecoderConfig
构造函数的第二个函数代表声波的配置,目前我们固定是 audible
,请勿随意修改。sampleRate
参数设为 16000
时测试效果良好,其他数值未经过测试。
# 开始录音
由于硬件不同,录音的 API 也存在差异。这里介绍使用 Android 原生录音 API 的实现方式。
// 创建录音实例
private fun createAudioInput(): AudioRecord? {
var audioRecord: AudioRecord? = null
val sampleRateInHz = 16000
val minBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz,
AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT)
try {
audioRecord = AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz,
AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT,
minBufferSize)
if (audioRecord.recordingState != AudioRecord.RECORDSTATE_STOPPED) {
Log.e(TAG, "Create recorder failed.")
}
} catch (e: IllegalArgumentException) {
Log.e(TAG, "Cannot create audio input. Error: " + e.message)
}
return audioRecord
}
在子线程中循环读取录音数据,并进行声波解码。
val buffer = ByteArray(640)
while (isRunning) {
mAudioRecord.let {
size = it.read(buffer, 0, buffer.size)
if (size >= 320 && isRunning) {
decoder.consume(buffer, 0, size) // 传入到声波解码对象
}
}
}
# 获取声波信息
在声波解码对象创建后,在 子线程 中循环读取解码器解码出来的数据。读取数据的函数会阻塞直到返回结果。
val buf = ByteArray(1024)
val size = decoder.receive(buf)
if (size > 0) {
val result = String(buf, 0, size) // 获取识别结果
}
对 result
进行处理可以得到网络配置信息。
val configs = result.split("\n")
val needAuth = configs[0] == "1"
val ssid = configs[1]
val password = if (configs.size > 2) config[2] else ""
# Linux
请等待后续文档完善。