# 声波配网

适用声波配网的方式,你的设备需要满足以下要求:

  • 设备具备麦克风,可以拾音。不需要额外配置蓝牙或者Wifi模块

选择声波配网方式,你需要做:

iFLYOS 使用的声波识别方案,使用了 Quiet (opens new window) 的开源方案。

# 流程介绍

设备进入等待配置的模式后,App 会持续发送声波数据,设备需要将录音数据持续传到解码器中,等待解码器回调识别结果。

  1. 声波数据格式

App 向设备发送的声波数据为字符串,形如以下格式

<flag>
<ssid>
<password>

其中,

flag 代表是否仅联网而不触发重新授权,1 代表重新授权,其他则代表仅配置网络。

ssid 代表要连接的 WiFi 名称。

password 代表要连接的 WiFi 密码,可能为空。

  1. 请求授权

设备通过上述数据连接到 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

请等待后续文档完善。