前言
iBeacon是苹果公司推出的一项低耗能蓝牙技术,由蓝牙设备发射包含指定信息的信号,再由移动设备接收信号,从而实现近场通信。微信小程序2017年开始支持iBeacon,摇一摇附近就是基于iBeacon实现的,此外iBeacon还可以实现距离测量,本文将介绍如何基于微信小程序实现iBeacon测距。
iBeacon测距原理
蓝牙信标发射的信号强度(rssi)与收发设备之间的距离,某种程度上呈正相关,因此通过合理的运算转化,可以通过rssi的值反推出与接收设备间的距离。
蓝牙信标的rssi值是一个参考值,没有固定标准。想要计算出蓝牙信标的距离,还必须知道这个信标设备的txPower值。txPower是指当距离蓝牙信标1m时的rssi值,不同的蓝牙设备或相同设备不同的工况甚至不同的场地环境,都会影响txPower值,因此这个值虽然可以测量,但一定程度上是个经验值,无法测准。
rssi测距公式
知道rssi和txPower后就可以计算距离了,有两种计算公式:
一、
这个公式里的三个变量A、B、C都是经验值,需要根据手机系统或硬件型号精确调校,通常会将所有设备的校准结果保存成一个设备信息表,移动终端先检测本机型号,然后匹配设备信息调取相应的计算配置,再进行计算。很明显这个公式是比较依赖硬件调校的,没有数据储备的前提下这个公式会很难用。
转换成js代码:
const calculateAccuracy = function (txPower, rssi) { return (0.89976) * Math.pow(rssi / txPower, 7.7095) + 0.111 }
未精校情况下的测距表现:
先说这个图怎么看。
- 纵轴代表测量距离,横轴代表时间,每隔一秒取样一次,图中是近10次取样的数值曲线。
- 绿线是设备接收到的rssi值,反应硬件真实接收到的数据情况;
- 红线是套用公式计算得出的瞬时距离;
- 黄线是微信小程序自带的瞬时测距结果。
蓝牙信标与手机的实际距离1m,测试设备为红米Note7。
从上图可见,rssi值相对稳定,说明硬件没有太大问题。红线和黄线的波动都很大,说明准确度不咋地。二者的波动趋势几乎一致,所以有理由怀疑微信小程序内部也是用的这个测距公式。从结果来看,这个公式的准确度比较差,可能是因为没有精校的原因。
二、
这个公式里的A就是rssi,tx是txPower,n是经验值,n的取值跟物理环境有关。
const calculateAccuracy = function (txPower, rssi) { return Math.pow(10, Math.abs(rssi - txPower) / (10 * 4)) }
公式二的测距表现:
人比人得死,货比货得扔啊。
图中黄线还是波动的那么疯狂,但红线却异常稳定,而且呈现出跟绿线一致的波动幅度,说明测距精度靠谱。这个公式只有一个参数,生产环境中的调校相对简单,这里我们选择公式二作为测距公式。
iBeacon测距稳定程序
蓝牙信号本身就有波动性,加上现实环境中的很多因素也会影响到信号强度,比如物体遮挡、设备方向变化、硬件自身的稳定性等,所以接收设备检测到的rssi值通常是“跳动”的,直接使用测距公式算出的结果,往往不可用。必须实现一个稳定程序,让计算结果呈现出连续性和稳定性。
数据滤波
稳定程序主要做的事就是对波段数据“削峰填谷”,也可以称作数据滤波。最简单的滤波处理,就是收集一段时间的值求平均,只要硬件不出问题,固定距离的蓝牙信标rssi值总是会在一个相对稳定的区间内变化,采样时间越长,采样的平均值就会越接近真实值,因此在静态测距场景中,求平均是最佳方式。
//求数组平均值 const arrayAverage = arr => arr.reduce((acc, val) => acc + val, 0) / arr.length; return arrayAverage([...])
具体实现是,当程序源源不断的接收到信标的rssi时,先用公式计算出瞬时测距结果,然后将结果存进一个数组,然后计算这个数组的平均值。静态测距时,测量结果还是非常准的,2m以内的距离误差可以低至0.1m。
实际应用中往往都是动态测距,所以采样数据的长度要加以限制,比如按后进先出的顺序,取最近10组数据。具体采样队列设为多长,要根据项目实际需求而定。采样队列的长度越长,测距结果越平滑,但对移动端的动态捕捉越迟钝;反之采样队列越短,结果越锐利,对移动端的动态捕捉越灵敏。
有时因为一些偶然因素,采样队列中会出现个别大幅偏离真实值的“燥音”数据,即使求平均也难以有效抹除影响,为消除这种影响,可以在求平均前先用高斯模糊算法对“偏大值”和“偏小值”做平滑处理,最大限度的降低数据噪音的干扰。
高斯模糊算法的关键是根据平均差求权重,一维高斯模糊的权重计算公式:
转换成js代码:
//求一维队列某点的高斯模糊权重 @param(队列长度,目标位置, 平均差) const getOneGuassionArray = function (size, kerR, sigma) { if (size % 2 > 0) { size -= 1 } if (!size) { return [] } if (kerR > size-1){ return [] } let sum = 0; let arr = new Array(size); for (let i = 0; i < size; i++) { arr[i] = Math.exp(-((i - kerR) * (i - kerR)) / (2 * sigma * sigma)); sum += arr[i]; } return arr.map(e => e / sum); }
关于“偏大值”和“偏小值”的概念将在下文介绍,这里只要知道我们的模糊目标是那些“极端数据”就行了。
时间加权
基于采样队列求平均的处理方式,不可避免的会让结果产生滞后性,这时可以引入时间加权的补偿算法。
所谓时间加权,是指在求平均值的时候,给距离当前时间较近的值更高的计算权重,反之给距离当前时间较远的值较低的计算权重,实现起来也非常简单。
以最简单的权重分配为例,将采样队列一分为二,按时间远近定位为“当前组”和“过去组”,比如说我想让当前组的权重是过去组的2倍,那么只要将当前组数据全部复制一份加入队列,然后再计算新队列的平均值。
//时间加权处理 queue = queue.slice(0, parseInt(queue.length / 2)).concat(queue) //求平均 return arrayAverage(queue)
动态跟进
经过时间加权处理后,数据的滞后性会得到一定的抑制,但如果遇到比较“陡峭”的距离变化,这种处理仍然会给出一个相对“平滑”的反馈,为了让稳定程序能更好的感知动态变化,并且做出跟进反应,还需要人为的设置一些特殊条件。
首先,如何判断移动设备正在远离或靠近?
这里有一个简单的思路,可以先找出采样队列中的最大值和最小值,然后以一定的阈值找出偏大值和偏小值。比如队列中的最大值是3,最小值是1,阈值设置为0.1m,那么大于2.9m的数据都算偏大值,小于1.1m的数据都算偏小值。偏大值和偏小值的队列长度最长不超过总队列的二分之一。
然后,如果偏大值集中在队列的前三分之一部分,那么我们可以认为移动设备正在果断远离;反之偏小值集中在队列的前三分之一部分,则可以认为移动设备正在靠近。
//maxCount为偏大值的序号数组 //minCount为偏小值的序号数组 //queueLength为队列长度 if (arrayAverage(maxCount) < parseInt(queueLength / 3)) { console.log(`正在远离`) } else if (arrayAverage(minCount) < parseInt(queueLength / 3)) { console.log(`正在靠近`) }
基于这种远离和靠近的趋势判断,我们可以人为的让数据向运动方向做更激进的倾斜。怎么做呢?跳过时间加权逻辑,如果判断为正在远离,那么就将队列中的偏小值过滤掉,反之则将偏大值过滤掉,只计算剩下的数据;这种处理会得到一个明显过激的结果,但考虑到现实世界中的运动往往具有惯性,这种激进处理,可能会更贴合真实的运动情况,而且让数据的响应更“灵敏”。
效果检验
做到目前为止效果怎么样呢,直接看图吧。
下图中,绿线依然是rssi值,红线是根据rssi直接算出来的瞬时测距结果,黄线是加入稳定程序后的测距结果。
第一张图是相对静止的条件,可以看到黄线相对红线明显更加平稳,说明稳定程序还是起作用的。
第二张图是模拟快速远离的场景,可以看到黄线在保证平稳的前提下紧跟红线,没有被甩掉,主要体现的是稳定程序的动态跟进效果。
第三张图是抡胳膊甩手机+遮挡信号模拟出的场景,貌似稳定程序也架不住了,有点飘忽。
以上是关于稳定程序的简要实现思路,生产环境中肯定会面临更加复杂的情况,免不了还要做大量调试,这里只是抛砖引玉。
总结
蓝牙测距简单来说就是一个公式的应用,本身比较简单,基于测距可以实现很多近场应用,比如近场签到、近场推送等等,更进一步甚至可以实现对移动设备的定位,有了定位信息,很多室内定位、室内导航相关的应用就都可以实现了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。