微信小程序iBeacon測距及穩(wěn)定程序的實現(xiàn)解析
前言
iBeacon是蘋果公司推出的一項低耗能藍牙技術,由藍牙設備發(fā)射包含指定信息的信號,再由移動設備接收信號,從而實現(xiàn)近場通信。微信小程序2017年開始支持iBeacon,搖一搖附近就是基于iBeacon實現(xiàn)的,此外iBeacon還可以實現(xiàn)距離測量,本文將介紹如何基于微信小程序?qū)崿F(xiàn)iBeacon測距。
iBeacon測距原理
藍牙信標發(fā)射的信號強度(rssi)與收發(fā)設備之間的距離,某種程度上呈正相關,因此通過合理的運算轉化,可以通過rssi的值反推出與接收設備間的距離。
藍牙信標的rssi值是一個參考值,沒有固定標準。想要計算出藍牙信標的距離,還必須知道這個信標設備的txPower值。txPower是指當距離藍牙信標1m時的rssi值,不同的藍牙設備或相同設備不同的工況甚至不同的場地環(huán)境,都會影響txPower值,因此這個值雖然可以測量,但一定程度上是個經(jīng)驗值,無法測準。
rssi測距公式
知道rssi和txPower后就可以計算距離了,有兩種計算公式:
一、

這個公式里的三個變量A、B、C都是經(jīng)驗值,需要根據(jù)手機系統(tǒng)或硬件型號精確調(diào)校,通常會將所有設備的校準結果保存成一個設備信息表,移動終端先檢測本機型號,然后匹配設備信息調(diào)取相應的計算配置,再進行計算。很明顯這個公式是比較依賴硬件調(diào)校的,沒有數(shù)據(jù)儲備的前提下這個公式會很難用。
轉換成js代碼:
const calculateAccuracy = function (txPower, rssi) {
return (0.89976) * Math.pow(rssi / txPower, 7.7095) + 0.111
}
未精校情況下的測距表現(xiàn):

先說這個圖怎么看。
- 縱軸代表測量距離,橫軸代表時間,每隔一秒取樣一次,圖中是近10次取樣的數(shù)值曲線。
- 綠線是設備接收到的rssi值,反應硬件真實接收到的數(shù)據(jù)情況;
- 紅線是套用公式計算得出的瞬時距離;
- 黃線是微信小程序自帶的瞬時測距結果。
藍牙信標與手機的實際距離1m,測試設備為紅米Note7。
從上圖可見,rssi值相對穩(wěn)定,說明硬件沒有太大問題。紅線和黃線的波動都很大,說明準確度不咋地。二者的波動趨勢幾乎一致,所以有理由懷疑微信小程序內(nèi)部也是用的這個測距公式。從結果來看,這個公式的準確度比較差,可能是因為沒有精校的原因。
二、

這個公式里的A就是rssi,tx是txPower,n是經(jīng)驗值,n的取值跟物理環(huán)境有關。
const calculateAccuracy = function (txPower, rssi) {
return Math.pow(10, Math.abs(rssi - txPower) / (10 * 4))
}
公式二的測距表現(xiàn):

人比人得死,貨比貨得扔啊。
圖中黃線還是波動的那么瘋狂,但紅線卻異常穩(wěn)定,而且呈現(xiàn)出跟綠線一致的波動幅度,說明測距精度靠譜。這個公式只有一個參數(shù),生產(chǎn)環(huán)境中的調(diào)校相對簡單,這里我們選擇公式二作為測距公式。
iBeacon測距穩(wěn)定程序
藍牙信號本身就有波動性,加上現(xiàn)實環(huán)境中的很多因素也會影響到信號強度,比如物體遮擋、設備方向變化、硬件自身的穩(wěn)定性等,所以接收設備檢測到的rssi值通常是“跳動”的,直接使用測距公式算出的結果,往往不可用。必須實現(xiàn)一個穩(wěn)定程序,讓計算結果呈現(xiàn)出連續(xù)性和穩(wěn)定性。
數(shù)據(jù)濾波
穩(wěn)定程序主要做的事就是對波段數(shù)據(jù)“削峰填谷”,也可以稱作數(shù)據(jù)濾波。最簡單的濾波處理,就是收集一段時間的值求平均,只要硬件不出問題,固定距離的藍牙信標rssi值總是會在一個相對穩(wěn)定的區(qū)間內(nèi)變化,采樣時間越長,采樣的平均值就會越接近真實值,因此在靜態(tài)測距場景中,求平均是最佳方式。
//求數(shù)組平均值 const arrayAverage = arr => arr.reduce((acc, val) => acc + val, 0) / arr.length; return arrayAverage([...])
具體實現(xiàn)是,當程序源源不斷的接收到信標的rssi時,先用公式計算出瞬時測距結果,然后將結果存進一個數(shù)組,然后計算這個數(shù)組的平均值。靜態(tài)測距時,測量結果還是非常準的,2m以內(nèi)的距離誤差可以低至0.1m。
實際應用中往往都是動態(tài)測距,所以采樣數(shù)據(jù)的長度要加以限制,比如按后進先出的順序,取最近10組數(shù)據(jù)。具體采樣隊列設為多長,要根據(jù)項目實際需求而定。采樣隊列的長度越長,測距結果越平滑,但對移動端的動態(tài)捕捉越遲鈍;反之采樣隊列越短,結果越銳利,對移動端的動態(tài)捕捉越靈敏。
有時因為一些偶然因素,采樣隊列中會出現(xiàn)個別大幅偏離真實值的“燥音”數(shù)據(jù),即使求平均也難以有效抹除影響,為消除這種影響,可以在求平均前先用高斯模糊算法對“偏大值”和“偏小值”做平滑處理,最大限度的降低數(shù)據(jù)噪音的干擾。
高斯模糊算法的關鍵是根據(jù)平均差求權重,一維高斯模糊的權重計算公式:

轉換成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);
}
關于“偏大值”和“偏小值”的概念將在下文介紹,這里只要知道我們的模糊目標是那些“極端數(shù)據(jù)”就行了。
時間加權
基于采樣隊列求平均的處理方式,不可避免的會讓結果產(chǎn)生滯后性,這時可以引入時間加權的補償算法。
所謂時間加權,是指在求平均值的時候,給距離當前時間較近的值更高的計算權重,反之給距離當前時間較遠的值較低的計算權重,實現(xiàn)起來也非常簡單。
以最簡單的權重分配為例,將采樣隊列一分為二,按時間遠近定位為“當前組”和“過去組”,比如說我想讓當前組的權重是過去組的2倍,那么只要將當前組數(shù)據(jù)全部復制一份加入隊列,然后再計算新隊列的平均值。
//時間加權處理 queue = queue.slice(0, parseInt(queue.length / 2)).concat(queue) //求平均 return arrayAverage(queue)
動態(tài)跟進
經(jīng)過時間加權處理后,數(shù)據(jù)的滯后性會得到一定的抑制,但如果遇到比較“陡峭”的距離變化,這種處理仍然會給出一個相對“平滑”的反饋,為了讓穩(wěn)定程序能更好的感知動態(tài)變化,并且做出跟進反應,還需要人為的設置一些特殊條件。
首先,如何判斷移動設備正在遠離或靠近?
這里有一個簡單的思路,可以先找出采樣隊列中的最大值和最小值,然后以一定的閾值找出偏大值和偏小值。比如隊列中的最大值是3,最小值是1,閾值設置為0.1m,那么大于2.9m的數(shù)據(jù)都算偏大值,小于1.1m的數(shù)據(jù)都算偏小值。偏大值和偏小值的隊列長度最長不超過總隊列的二分之一。
然后,如果偏大值集中在隊列的前三分之一部分,那么我們可以認為移動設備正在果斷遠離;反之偏小值集中在隊列的前三分之一部分,則可以認為移動設備正在靠近。
//maxCount為偏大值的序號數(shù)組
//minCount為偏小值的序號數(shù)組
//queueLength為隊列長度
if (arrayAverage(maxCount) < parseInt(queueLength / 3)) {
console.log(`正在遠離`)
} else if (arrayAverage(minCount) < parseInt(queueLength / 3)) {
console.log(`正在靠近`)
}
基于這種遠離和靠近的趨勢判斷,我們可以人為的讓數(shù)據(jù)向運動方向做更激進的傾斜。怎么做呢?跳過時間加權邏輯,如果判斷為正在遠離,那么就將隊列中的偏小值過濾掉,反之則將偏大值過濾掉,只計算剩下的數(shù)據(jù);這種處理會得到一個明顯過激的結果,但考慮到現(xiàn)實世界中的運動往往具有慣性,這種激進處理,可能會更貼合真實的運動情況,而且讓數(shù)據(jù)的響應更“靈敏”。
效果檢驗
做到目前為止效果怎么樣呢,直接看圖吧。
下圖中,綠線依然是rssi值,紅線是根據(jù)rssi直接算出來的瞬時測距結果,黃線是加入穩(wěn)定程序后的測距結果。
第一張圖是相對靜止的條件,可以看到黃線相對紅線明顯更加平穩(wěn),說明穩(wěn)定程序還是起作用的。

第二張圖是模擬快速遠離的場景,可以看到黃線在保證平穩(wěn)的前提下緊跟紅線,沒有被甩掉,主要體現(xiàn)的是穩(wěn)定程序的動態(tài)跟進效果。

第三張圖是掄胳膊甩手機+遮擋信號模擬出的場景,貌似穩(wěn)定程序也架不住了,有點飄忽。

以上是關于穩(wěn)定程序的簡要實現(xiàn)思路,生產(chǎn)環(huán)境中肯定會面臨更加復雜的情況,免不了還要做大量調(diào)試,這里只是拋磚引玉。
總結
藍牙測距簡單來說就是一個公式的應用,本身比較簡單,基于測距可以實現(xiàn)很多近場應用,比如近場簽到、近場推送等等,更進一步甚至可以實現(xiàn)對移動設備的定位,有了定位信息,很多室內(nèi)定位、室內(nèi)導航相關的應用就都可以實現(xiàn)了。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
JavaScript獲取地址欄參數(shù)的方法實現(xiàn)
這篇文章主要給大家介紹了關于JavaScript獲取地址欄參數(shù)的方法實現(xiàn),項目中經(jīng)常遇到獲取上個頁面跳轉過來獲取當前的參數(shù),文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下2023-07-07
TypeScript實現(xiàn)字符串轉樹結構的方法詳解
有一個多行字符串,每行開頭會用空格來表示它的層級關系,每間隔一層它的空格總數(shù)為2,如何將它轉為json格式的樹型數(shù)據(jù)?本文就跟大家分享下這個算法2022-09-09
JavaScript必備的斷點調(diào)試技巧總結(推薦)
打斷點操作很簡單,核心的問題在于,斷點怎么打才能夠排查出代碼的問題所在呢?下面這篇文章主要給大家總結介紹了關于JavaScript必備的斷點調(diào)試技巧,需要的朋友可以參考下2021-09-09

