微信小程序AP配网-WiFi部分详解

在上一篇中文章中主要讲了一下AP配网概念、WiFi连接设备流程以及遇到的机型的问题,但是当时的实现思路也存在着缺陷、部分场景没有处理好,导致后续设备升级、小程序版本迭代中出现了问题。

本篇文章主要对出现的问题进行阐述、剖析、解决方法进行总结,希望能给有这方面开发需求的掘友们带来帮助。

好了,话不多说,让我们步入今天的主题。

暴露出的问题

提出的新的需求 : 本次主要是用户提出了个需求:针对配网的设备能否批量配置(这一批设备配置链接相同的WiFi)、给指定某个进入AP状态的设备进行配置(指定给单个设备配置单独的WiFi);
思考针对这种情况下之前的处理逻辑就不适用了,需要进行调整,主要有以下几个问题:

  1. 怎样区分设备
  2. 怎样进行批量的设置,批量下发配置信息
    其实以上的问题归根到底主要就是:我们如何区分设备,只要我们能够区分出设备,那么我们就可以连接上、然后来一个循环进行配置(理想很美好,后续却遇到了一些问题)

解决方法

怎样区分设备呢?这种问题在上层软件肯定是没法做到的,必须要让硬件部门的同事对设备生成的SSID进行修改,因为目前设备生成的都是一个名称,对于用户来说肯定没法区分。

那么我们就成功的将压力转移给了硬件人员,他们只需要根据设备的MAC,生成唯一的SSID即可。(其实对于他们来说也简单,只要将MAC 进行组合动态生成即可,可以生成:公共标识_mac后五位拓展-什么是SSID、BSSID

  1. SSID 我们可以看做是 WiFi名
  2. BSSID 可以理解为每个无线接入的MAC地址
    更详细的大家可自行查询相关资料。

主体思路

当设备动态生成了SSID,接下来就是前端工程师的任务了,在这里我先主要描述一下设备配网的流程以及配置WiFi的主要流程。
设备配置流程 WiFi配置流程 以上就是我们思路的整体流程,后面会按照该流程进行实现。

开发环境

  1. HBuilder X 3.6.18
  2. 微信开发者工具 1.06.2303220;调试基础库 3.3.4

WIFI配置

进入具体的WiFi配置部分,一些基本的在这里我会简单说一下,部分代码与上一篇文章中的是一致的,但是本次讲解的更加细致;对于实际操作中我栽的坑,这里我会详细说明。

全局标识

这里我先将展示一下后续用到的一些公共标识。

js 复制代码
let udp = null;
let configInfo = {
    ssid: '',
    pwd: '',
    url: ''
};

const deviceInfo = {
    SSID: '旧版的SSID',
    password: '',
}

let sendTimer = null; // 发送UDP消息
let maxUdp = 10;
let connectUdpNum = 0;

// udp 传输超时显示
let udpOverTimer = null;

// 手动链接wifi的callback 回调,这个主要是显示页面弹框,提醒用户手动连接
// page 中的显示弹框回调
let manualWifiCallback

1、手机版本判断

js 复制代码
wx.getSystemInfo({
    success: function(res) {
        phoneType = res.platform;
	var system = '';
	if (res.platform == 'android') system = parseInt(res.system.substr(8));
        if (res.platform == 'ios') system = parseInt(res.system.substr(4));
        if (res.platform == 'android' && system < 6) {
            wx.showToast({
                title: '手机版本不支持',
            })
            return;
        }
        if (res.platform == 'ios' && system < 11.2) {
            wx.showToast({
                title: '手机版本不支持',
            })
            return;
        }
        // 初始化WiFi模块
        startWifi();
   }
})

2、初始化WiFi模块

2-1 注意

  1. 初始化WiFi模块,是指微信小程序内部初始化,并不代表了当前的用户手机WiFi是否打开
    现象 :手机不打开WiFi在调用 uni.startWfif 时也会进入success回调。
    那么我们怎样判断用户是否打开了WiFi呢,在后边我们可以通过获取WiFi列表来给用户进行弹窗提醒,大家请耐心继续往后看。
js 复制代码
function startWifi() {
    uni.showLoading({
	title: '初始化WIFI模块'
    })
    uni.startWifi({
        success(res) {
            console.log('wifi 初始化成功');
            /**
             * 判断当前用户连接的是不是就是 设备的 WiFi
             */
            uni.getConnectedWifi({
                success: (e) => {
                    console.log('获取wifi名', e);
                    let {
                       SSID
                    } = e.wifi;
                    if (
                        SSID === '之前的旧SSID' ||
                        SSID.split('_')[0] === '新SSID公共部分'
                    ) {
                        uni.showLoading({
                             title: '设置中...'
                        })
                        sendCfg();
                    } else {
                        // 获取用户权限
                        getUserAuthorize();
                        // 监听WiFi的连接
                        onWifiConnected();
                    }
                },
                fail: (err) => {
                    // 获取连接的 WiFi 失败
                    uni.showLoading({
                        title: '未获取到当前连接的WiFi...'
                    })
                    getUserAuthorize();
                    onWifiConnected();
                }
            })
        },
        fail(err) {
            uni.hideLoading();
            uni.showToast({
                title: 'WIFI初始化失败',
                icon: 'error'
            })
        }
    })
}

2-2 开启WiFi连接事件监听

js 复制代码
/**
 * 监听当前的 WiFi 连接事件
 */
function onWifiConnected() {
    uni.onWifiConnected((res) => {
        console.log('监听 WiFi 连接', res);
        let {
            wifi: {
                SSID
            }
        } = res;

        if (
            SSID === '旧标识' ||
            SSID.split('_')[0] === '新公共标识'
        ) {
            connectSsid = SSID;
            // 连接上了设备进行配置
            uni.showLoading({
                title: '设备wifi连接成功',
            })
            sendCfg()
        }
    })
}

3、判断当前用户的权限授权,引导授权定位

3-1 注意

我们为什么要获取用户的定位授权呢?

  1. 针对不同的设备,生成的不同的SSID,我们需要获取WiFi列表,
    微信官方说明-2019-04-28 :当前我们发现在Android平台下,部分小程序会通过wx.getWifiList接口,嗅探周边Wi-Fi热点来推断用户所在的位置信息。为了更好地保护用户隐私,现策略调整为: Android 平台下,所有小程序在调用wx.getWifiList接口前,需获取用户位置信息授权(scope.userLocation)。

3-2 注意

  1. 因为我们需要用户授权定位,这个就涉及到了"隐私与安全",我们需要登录小程序更新我们的"用户隐私保护指引",描述我们处于什么目的需要用户进行授权;该部分内容在授权弹框、资料页中会显示给用户。
    这些是微信官方要求的没办法避免,否则会影响获取用户的定位授权。
    我们进入微信公众平台登录小程序,设置 =》 基本设置 =》 服务内容声明 =》 用户隐私保护指引 然后点击进行更新完善内容即可。

3-3 注意

出现的问题 :在实际测试过程中,发现 IOS 在获取定位授权时无法显示定位授权弹框,并且诡异的是,我们进入 小程序的"设置"中,并没有显示授权定位权限,但是在 Android 中正常 解决方法 :这是由于我们采用的uniapp写的小程序,使用的 HBuilder X 工具,我们完成了前两项的后,还需要调整一下我们工程中manifest.json => "微信小程序配置" =》 "微信小程序权限配置" 勾选上 "位置接口"并填写申请原因,这样就可以解决 IOS 中出现的问题了

js 复制代码
function getUserAuthorize() {
    uni.showLoading({
        title: '获取定位权限...'
    })
    uni.getSetting({
        success(res) {
            if (!res.authSetting['scope.userLocation']) {
                uni.authorize({
                    scope: 'scope.userLocation',
                    success: (res) => {
                        // 用户已经同意小程序使用定位功能,
                        // 后续调用 不会弹窗询问
                        getWifiList();
                    },
                    fail: (err) => {
                        uni.hideLoading();
                        uni.showModal({
                            content: '请授权获取定位权限...',
                            showCancel: false,
                            success: function (res) {
                                // 引导进入授权
                                uni.openSetting({
                                    success: (res) => {
                                        /**
                                         * 用户从 设置 - 授权页面跳转回小程序
                                         */
                                        if (!res.authSetting['scope.userLocation']) {
                                            // 未授权
                                            uni.showModal({
                                                content: '请授权获取定位权限后,再次点击配置网络按钮',
                                                showCancel: false,
                                            })
                                        } else {
                                            // 授权完成,进行配置
                                            getWifiList();
                                        }
                                    },
                                    fail: (err) => {
                                        console.log('失败了 err', err);
                                    }
                                });
                            }
                        })
                    }
                })
            } else if (res.authSetting['scope.userLocation']) {
                // 已经进行了授权
                getWifiList();
            }
        },
        fail: (err) => {
            uni.hideLoading();
        }
    })
}

4、获取当前的WiFi列表

4-1 注意

  1. IOS 中 # wx.onGetWifiList在ios中不进入回调
    官方说明 2019-09-19:需要跳转到系统 WiFi 列表,等到列表刷新出 WiFi,在微信前台的小程序才会收到 onGetWifiList 的回调;这是苹果系统的限制,暂时无法规避

4-2 注意,批量配置、单个配置

  1. 针对我们一开始提到的,怎样批量给进入AP模式的设备,设置WiFi呢,再获取WiFi列表的逻辑中,我们就可以将属于设备的WiFi筛选出来,然后再后面进行循环配置。
    单个配置,我们可以提供给用户让其输入指定设备的信息,然后在配置的时候进行处理。
js 复制代码
function getWifiList() {
    uni.showLoading({
        title: '获取当前WiFi列表...'
    })
    uni.getWifiList({
        success: (res) => {
            uni.showLoading({
                title: '获取当前WiFi列表成功'
            })
            /**
             * 判断如果是苹果手机
             * 无法 进行  uni.onGetWifiList 监听
             */
            setTimeout(() => {
                if(phoneType == 'ios') {
                    uni.hideLoading();
                    uni.showModal({
                        content: 'IOS手机无法主动获取WiFi列表,请先进入【设置-WiFi】页面,带加载完毕WiFi列表后,返回小程序完成后续配置',
                        showCancel: false,
                    })
                }
            }, 2000)
        },
        fail: (err) => {
            let {
                errno
            } = err;
            uni.hideLoading();
            uni.showModal({
                content: '获取WiFi列表失败,请确认打开了手机WiFi,并授权获取定位;确认无误后,再次点击【配置网络】按钮,进行配置',
            })
        },
    })
    // 监听获取到的 WiFi列表
    uni.onGetWifiList((res) => {
        uni.showLoading({
            title: '匹配查询当前配网设备...'
        })
        let {
            wifiList
        } = res;
        wifiList.forEach(item => {
            if(
                item.SSID.indexOf('旧SSID') !== -1 || 
                item.SSID.indexOf('新SSID') !== -1
            ) {
                // 存储当前设备产生的热点
                configDeviceSSIDList.add(item.SSID);
            }
        })
        if (configDeviceSSIDList.size !== 0) {
            connectWifi();
        } else {
            // 当前没有设备进入配网模式
            uni.hideLoading();
            uni.showModal({
                content: '未搜索到设备生成的WiFi,请确定当前设备已经入配网模式,查看一下当前的WiFi列表,然后返回小程序重新点击【配置网络】按钮',
            })
        }
    })
}

5、进行 WiFi 链接

注意 5-1

问题 :在测试过程中部分手机是无法直接正常连接到 WiFi
解决方法

  1. 将设备生成的热点,密码设置为空; 这种方式会提升在华为、小米手机中连接WiFi的成功率
  2. 针对无法正常连接WiFi的情况,进行兜底措施,引导用户跳转到系统设置页进行连接。
    通过 wx.connectWifi 官方文档中提供的 maunal 参数,可以做到这一点。官方文档
js 复制代码
function connectWifi() {
    let {
        ssid,
        pwd
    } = configInfo;
    uni.showLoading({
        title: '连接设备WIFI'
    })
    let configDeviceSsidArr = Array.from(configDeviceSSIDList);
    connectSsid = configDeviceSsidArr[0];
    uni.connectWifi({
        SSID: configDeviceSsidArr[0],
        password: '',
        // maunal: true,
        success(res) {
            uni.showLoading({
                title: '设置中...'
            })
            sendCfg();
        },
        fail(err) {
            wx.hideLoading();
            manualWifiCallback();
        }
    })
}

5-2、当前wifi连接失败,进行重连

js 复制代码
function manualConnectWifi() {
    let configDeviceSsidArr = Array.from(configDeviceSSIDList);
    connectSsid = configDeviceSsidArr[0];
    uni.connectWifi({
        SSID: configDeviceSsidArr[0],
        password: '',
        maunal: true,
        success: (res) => {
            console.log('手动链接完毕 res', res);
        },
        fail(err) {
            uni.hideLoading();
            if (err.errCode === 12005) {
                uni.showModal({
                    content: '请打开WIFI开关,然后再点击配置网络按钮',
                    success: (res) => {
                        if (res.confirm) {
                            console.log('用户点击确定')
                        }
                    }
                })
            } else if (err.errCode === 12007) {
                uni.showModal({
                    title: '用户拒绝授权连接 Wi-Fi,请手动进行连接',
                    content: 'WIFI连接失败,请手动连接设备WIFI,成功后返回小程序点击配置网络按钮',
                    success: (res) => {
                        if (res.confirm) {
                            // console.log('用户点击确定')
                        }
                    }
                })
            } else if (err.errCode === 12010) {
                uni.showModal({
                    title: '系统其他错误,请手动进行连接',
                    content: 'WIFI连接失败,请手动连接设备WIFI,成功后返回小程序点击配置网络按钮',
                    success: (res) => {
                        if (res.confirm) {
                           
                        }
                    }
                })
            } else {
                uni.showModal({
                    title: '设备WIFI连接失败,请手动进行连接',
                    content: 'WIFI连接失败,请手动连接设备WIFI;成功后返回小程序点击配置网络按钮',
                    success: (res) => {
                        if (res.confirm) {
                            console.log('用户点击确定')
                        }
                    }
                })
            }
        }
    })
}

7、建立UDP连接,发送消息

7-1 注意:

  1. 为了避免设备无法及时接收到发送的信息,我们进行周期性发送,发送多次消息
  2. 为了防止UDP信息传输过程中,数据丢失,保证设备接收到的数据完整性;通过与设备配合将它接收到的信息返回,小程序监听UDP信息,比对是否一致
js 复制代码
function sendCfg() {
    if (!configInfo.ssid) {
         // 当链接成功、或超时之后 关闭定时器以及重置计数器
        initCfgSign();
        uni.showModal({
            content: '请重新点击【配置网络】按钮,进行配网操作',
            showCancel: false,
            success: (res) => {

            }
        })
    }

    let jo = {
        "ssid": configInfo.ssid,
        "pwd": configInfo.pwd,
        "url": configInfo.url
    };

    if (configInfo.ip && configInfo.ip != '') {
        jo.ip = configInfo.ip;
        jo.sub = configInfo.sub;
        jo.gw = configInfo.gw;
        jo.dns1 = configInfo.dns1;
        jo.dns2 = configInfo.dns2;
    }

    uni.showLoading({
        title: 'UDP信息传输中...',
    })

    let js = JSON.stringify(jo);
    udp = uni.createUDPSocket();
    udp.bind();

    udp.send({
        address: '地址',
        port: 'udp 端口',
        message: '字符串信息'
    });

    if (!sendTimer) {
        sendTimer = setInterval(() => {
            console.log('周期发送UDP消息', udp);
            if (connectUdpNum > maxUdp) {
                initCfgSign(true);
                uni.showToast({
                    title: 'UDP连接超时,请重新配置网络',
                    icon: 'none',
                    duration: 3000
                })
                return;
            }
            udp.send({
                address: '地址',
                port: 'udp 端口',
                message: '字符串信息'
            });
            connectUdpNum++
        }, 1000);
    } else {
        // 已存在该定时器
        console.log('定时器已存在');
    }
    
    // 监听 udp 传送的信息,判断传递的数据信息是否正确,有没有丢失
    udp.onMessage(onUdpMessage);
}

7-2 监听UDP 信息传输

js 复制代码
function onUdpMessage(res) {
    if (res.remoteInfo.size > 0) {
        // 将 ArrayBuffer类型的res.message取出来
        let unit8Arr = new Uint8Array(res.message)
        let encodedString = String.fromCharCode.apply(null, unit8Arr);
        // 没有这一步中文会乱码
        let decodedString = decodeURIComponent(escape((encodedString)))
        let jo = JSON.parse(decodedString);
        console.log('jo', jo);
        /**
         * 判断返回的连接信息与设备接收到的是否一致
         * 防止 UDP传输过程中 数据丢失
         */
        let {
            ssid,
            pwd,
            url
        } = configInfo;
            if (
                ssid === jo.ssid &&
                pwd === jo.pwd &&
                url === jo.url
            ) {
                /**
                 * 已收到正确的消息,
                 * 关闭发送UDP 超时监听
                 */
                if (sendTimer) clearInterval(sendTimer);
                uni.showLoading({
                    title: 'UDP信息传输成功...',
                })

                let msg = {
                    err: 0
                }
                udp.send({
                    address: '地址',
                    port: 端口,
                    message: JSON.stringify(msg),
                }); 

                /**
                 * 防止长时间过长没有返回 err 0
                 */
                udpOverTimer = setTimeout(() => {
                    uni.hideLoading();
                    initCfgSign();
                    uni.showModal({
                        title: 'UDP信息传输完毕',
                        content: '请确认是否配置成功,如果失败请重新进行配网操作',
                    })
                }, 1000 * 10)
            } else if (
                jo.err === 0
            ) {
                initCfgSign();
                /**
                 * 设备成功接收到数据
                 */
                uni.showToast({
                    title: '网络配置成功',
                    icon: 'success'
                })
            } else {
                initCfgSign();
                uni.showModal({
                    title: 'UDP信息传输数据丢失',
                    content: 'UDP信息传输过程中数据丢失,请重新进行配置',
                })
            } 
	}
}

8、重置当前的标识,关闭WiFi模块、移除WiFi连接事件监听

8-1 重置当前的标识

js 复制代码
function initCfgSign(isOverTime = false) {
    /**
     * 判断当前 WiFi 列表中还有无其他 待配置的设备
     * 没有的话,关闭当前WiFi列表
     * 有的话,继续进行配置
     */
    configDeviceSSIDList.delete(connectSsid);
    // console.log('configDeviceSSIDList', configDeviceSSIDList);
    if(configDeviceSSIDList.size !== 0 && !isOverTime) {
        // 继续调用配置功能进行配置
        connectWifi();
        return;
    }

    connectSsid = '';
    configInfo = null;
    maxUdp = 20;
    connectUdpNum = 0;
    uni.hideLoading();
    udp.close();
    udp = null;
    closeWifi();
    if (sendTimer) clearInterval(sendTimer);
    if (udpOverTimer) clearTimeout(udpOverTimer);
}

8-2 关闭WiFi模块

js 复制代码
function closeWifi() {
    offWifiConnected();
    uni.stopWifi({
        success(res) {
            console.log('关闭当前wifi模块', res.errMsg)
        }
    })
}

8-3 移除当前监听 WiFi 连接事件

js 复制代码
function offWifiConnected() {
    uni.offWifiConnected();
}

其他注意事项

以上就是整体我们在实现微信小程序AP配网的过程中,WiFi部分的详细讲解。我们将上述的各部分代码串联起来,并针对自己的实际需求,就可以完成在微信小程序开发过程中WiFi的逻辑处理。

但是,在这个过程中,最开始我还踩了个坑,主要是对实际应用场景考虑的不全面。

针对三方,中间商给设备进行配网

这个场景就有点儿特殊了,当我们的设备卖给中间商,然后他们去给用户进行配置,这个时候有个特殊的情景:

  1. 配置设备的现场并不是实际使用的地方
    举个例子:就是中间商或者一些用户,他们要给设备配置WiFi,但是他在家里配置,但是最终要用到工厂中,这就造成,用户配置的时候有可能当前WiFi开关并没有开、或者当前WiFi打开了,但是家里并没有WiFi
    在这种情况下,我们就不能通过获取用户当前的网络类型uni.getNetworkType,来决定是否初始化WiFi了。

总结

如果你看到这里,那么恭喜你也成功的梳理了一遍微信小程序WiFi 相关的知识。到这儿,本次主要的配置流程、实际操作中WiFi的注意事项就讲完了。

针对文中一些不足欢迎大家评论区中指出,创作不易,希望大家能多点赞收藏。

技术的路上记录点点滴滴,大家携手前行,与君共勉!!!

相关推荐
喵叔哟9 分钟前
重构代码之取消临时字段
java·前端·重构
还是大剑师兰特1 小时前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解1 小时前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~1 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding1 小时前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
丶21361 小时前
【WEB】深入理解 CORS(跨域资源共享):原理、配置与常见问题
前端·架构·web
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
Mr.咕咕1 小时前
Django 搭建数据管理web——商品管理
前端·python·django