# 设备配网

对于有屏设备,一般选择自行入网即可,对于无屏设备,推荐使用小微提供的配网功能来完成配网和绑定。小微提供以下配网方式,以满足不同场景的需求。

  • 腾讯小微小程序:蓝牙BLE配网,声波配网
  • 腾讯小微APP:蓝牙BLE配网,声波配网,AP热点配网

# 蓝牙BLE配网

# 介绍

蓝牙BLE配网是最推荐的方式,该方式流程简单,用户体验好,若硬件设备支持蓝牙BLE,可采用该方式配网。简单来说,流程如下:

  1. 设备启动一个service并进行广播,声明自己的身份
  2. APP/小程序扫描到广播后与设备建立连接
  3. APP/小程序通过蓝牙将wifi信息传给设备
  4. 设备自行联网,此时配网流程已完成
  5. 配网成功后设备登录小微SDK,获取绑定ticket,并回传给APP/小程序
  6. APP/小程序根据ticket完成设备绑定

对于接入厂商来说,蓝牙配网需要厂商实现我们定义的配网协议,具体协议如下。

# BLE配网协议

# 1. 准备工作

使用小微蓝牙配网的设备,需要在小微官网注册设备时选择蓝牙配网方式,并且按照官网要求上传配网时的指示图片(该图片将在小程序/APP中显示,用于引导用户启动配网模式,如长按某按钮,等待语音或LED它提示等)。

# 2. 启动主Service

设备在进入正式配网流程前,需要先启动一个Service,该Service要包含下面三个特征:

# 2.1 ssid_info
内容 说明
特证名 ssid_info
UUID 需包含"A1CE",例如"0000A1CE-0000-1000-8000-00805f9b34fb"
权限 write(with response)
说明 用于接收wifi信息

对应的数据结构如下代码所示:

typedef struct _SSID_Info{
    unsigned short length; // 总长度 2byte
    unsigned char ssidLength; // 无线名长度 1byte
    unsigned char passwordLength;// 密码长度 1 byte
    unsigned char ssid[1]; //无线名,UTF8编码 可变长
    unsigned char password[1]; //无线密码,UTF8编码 可变长
}SSID_Info;

注意:如果此结构大于20字节,app/小程序端会分包发送,每个包最大20字节。为了保证分包发送正确,需要该特征有write(with response)权限。数据采用little endian的方式。例如以下接收wifiSSID信息的伪代码示例:

void onGetSsidInfoData(const char[] value, size_t length) {
	if (length < 5 && !is_ssid_start) {
	    printf("data length error");
	    return;
	}
    if (!is_ssid_start) {
        is_ssid_start = true;
        ssid_total_len = value[0] + value[1]*256;
        ssid_id_len = value[2];
        ssid_password_len = value[3];
        ssid = new char[ssid_id_len + 1];
        memset(ssid, 0, ssid_id_len + 1);
        ssid_password = new char[ssid_password_len + 1];
        memset(ssid_password, 0, ssid_password_len + 1);

        for (int i = 4; i < ssid_id_len + 4 && i<length; i++) {
            ssid[ssid_id_offset++] = value[i];
        }
        if (ssid_id_len == ssid_id_offset) {
            for (int i = 4 + ssid_id_len; i < ssid_id_len + 4 + ssid_password_len && i<value.length; i++) {
                ssid_password[ssid_password_offset++] = value[i];
            }
        }
    }
    else {
        int i = 0;
        if (ssid_id_len > ssid_id_offset) {
            int left = ssid_id_len - ssid_id_offset;
            for(; i < left && i < length; i++) {
                ssid[ssid_id_offset++] = value[i];
            }
        }
        if (ssid_id_len == ssid_id_offset) {
            for(; i < length; i++){
                ssid_password[ssid_password_offset++] = value[i];
            }
        }
    }

    if (ssid_password_len == ssid_password_offset && ssid_id_len == ssid_id_offset) {
        is_ssid_finish = true;
		// todo, connect wifi and init xiaoweiSDK    
    }
}
                        
# 2.2 xiaowei_specify
内容 说明
特证名 xiaowei_specify
UUID 需包含"6196"
权限 read
说明 用于传输绑定信息,为了与老协议兼容,有一些无效字段,同样little endian

对应的数据结构如下代码所示:

typedef struct _Xiaowei_Spec{
    unsigned short length; // 总长度 2byte
    unsigned char snLength; // sn长度 1byte,固定为0
    unsigned char ticketLength;// ticket长度 1byte
    unsigned char sn[1]; //sn,UTF8编码 可变长,固定为空
    unsigned char ticket[1]; //token,UTF8编码 可变长
}Xiaowei_Spec;

注意,这里的ticket是需要联网成功之后,登录小微SDK,然后利用int device_get_bind_ticket(bind_ticket_callback callback);接口来获取。获取成功后刷新这个Spec的值,同时通知APP/小程序来读取。

内容 说明
特证名 link_state
UUID 需包含"A007"
权限 notify
说明 通知app/小程序,联网和ticket获取进度

对应的数据结构如下:

notifyState(1byte,值定义见下面枚举) + msg(可选, utf8 string)

enum XWLink_Notify{
    XWLink_unkown = 0,
    XWLink_SSID_Received = 1, //收到配网信息(可选)
    XWLink_Linking = 2, //配网中(可选)
    XWLink_LinkSuccess = 3, //success、failed和wifiErr必须有一个通知
    XWLink_LinkFailed = 4, //综合原因的失败,如无法获取ticket等
    XWLink_LinkWiFiErr = 5 //Wifi无法连接导致的失败
};

当配网成功后,不要立即通知XWLink_LinkSuccess,应在成功获取到ticket后再进行通知,一旦发出通知,APP/小程序就会尝试读取上述的xiaowei_specify,若读取失败,会提示配网失败。

为了提升用户体验,设备若能确定wifi无法连接,应notify XWLink_LinkWiFiErr。此时APP/小程序会提示用户修改wifi信息。

# 3. 设备发起广播

设备进入配网流程后,首先需要发起广播,广播数据:

广播字段
Service UUID 包含FFB1,如"0000FFB1-0000-1000-8000-00805f9b34fb"
Manufacture Data 厂商ID(2byte)+pattern("xiaoweilink"字符串,11byte)+version(uchar,1byte,当前协议版本号为1)+设备pid(uint32,4byte,little endian)

注意:根据蓝牙规范,厂商自定义的数据中,前两个字节表示厂商ID,剩下的是厂商自定义数据,厂商实现时不要漏掉厂商ID。

一个合法的Manufacture Data如:"xwxiaoweilink\1a010",在这里,厂商id是"xw",设备pid是'a' << 0 + '0' << 8 + '1' << 16 + '0' << 24,注意这里显示的'0',其实是十进制的30。

假如productId 为 16909060 ,16进制为0x01020304。那么广播包最后4个字节应该是0x04 0x03 0x02 0x01,这些字符是显示不出来的。

APP/小程序在配网页面会扫描蓝牙广播,若匹配成功,则会向上述的ssid_info特证中写数据来完成wifi信息的传输。

# 4. 总结

配网时序如下:

  • 小微设备->小微App: 广播数据
  • 小微App->小微App: 检查Manufacture Data
  • 小微App->小微设备: 写入配网信息
  • 小微设备->小微设备: 自行联网,并登陆SDK,获取ticket
  • 小微设备->小微App: 通知App联网成功
  • 小微App->小微设备: 读取ticket
  • 小微App->小微App: 尝试绑定设备

# 声波配网

# 介绍

声波配网是以特定频率的声音作为信息载体,由小微App/小程序将某个路由器的SSID和密码同步给设备。这个技术类似无线广播,在小微App/小程序端,会将ssid_info进行软调制,以高频声波作为载波,将信息广播出去;在设备端通过麦克风接收声波并软解调,从而获取wifi信息。

该方法对设备的收音性能有要求,需要在配网时保持安静,可作为蓝牙BLE配网失败时的备选方案。小微SDK内置了声波配网解调模块,设备端使用此功能时,初始化解调模块并将音频数据送入,即可获取解调后的信息。

与BLE配网的不同之处在于,设备端无法再向APP/小程序端传递数据,因此配网协议略有不同,总体可以概括为:在APP/小程序向设备端发送ssid_info的同时,还会携带一个token,用于标记这一次配网。设备配网并获取ticket成功后,不再直接通知APP/小程序,而是将token和ticket组成一个键值对,通过SDK的接口发送至小微后台,小微后台会完成绑定,并通知APP/小程序配网成功。

# 接口

# 1. 初始化SDK

int voicelink_init_decoder(VL_FUNC_NOTIFY func, int samplerate); 调用该接口初始化配网模块,设置通知回调和采样率。注意这里设置的采样率一定要和设备真实的录音采样率一致。

# 2. 持续采音并fill buffer。

void voicelink_fill_audio(signed short *audio, int nlen);每当采集一定的音频数据后,调用此接口将数据传给SDK,建议每帧数据长度为20ms,数据过长会导致失败。这里的nlen是采样点数,而不是长度。例如采样率8000,20ms数据的nlen就是160。

持续调用此接口直到VL_FUNC_NOTIFY到来。

# 3. 获得wifi信息和token并联网

初始化时设置的回调函数中的VL_FUNC_NOTIFY包含了wifi信息和token。设备自行使用wifi信息联网,联网成功后登录小微SDK,登录成功后调用int device_get_bind_ticket(bind_ticket_callback callback);获得ticket。

# 4. 通知小程序/App配网结果,并完成绑定

device_upload_voice_link_result(const char *ticket, const char *token);调用此接口将上述ticket和token上传。

# 总结

配网时序如下:

  • 设备进入声波配网模式,初始化SDK的decode接口,开始收音,并将音频数据持续输入SDK的decode接口。
  • 小程序/App进入声波模式,播放声音。
  • SDK decode成功,设备拿到wifi_ssidwifi_passwordtoken
  • 设备使用wifi_ssidwifi_password自行联网。
  • 设备联网成功后登录SDK,成功之后获取ticket。
  • 设备调用SDK接口将ticket和token上传后台,至此设备端工作结束。
  • 小程序/App收到后台消息,完成绑定。
  • 配网流程结束。

# 蓝牙和声波配网协同

在小微小程序/App的交互流程中,会优先使用蓝牙配网,配网失败后使用声波配网。在设备端若支持声波配网,应同时打开声波配网和蓝牙配网,二者有一个成功之后便退出配网流程即可。需要注意以下几点:

  • 配网成功是指设备拿到wifi信息并且成功login,拿到ticket并且成功将ticket给到小程序/App(蓝牙)或后台(声波)。
  • 两种方式同时打开时,只需要获取一次ticket,这个ticket对于两种方式均有效,重复get ticket会导致之前的ticket失效。