概述
这个项目主要是定位50米走廊的位置,共有5个Ibeacon设备,每隔10米放置一个。显示也不像之前放在pc端上显示,而是实时显示在手机屏幕上。Android扫描到Ibeacon设备的RSSi后,先进行卡尔曼滤波,得到一个相对稳定的RSSI,再根据可调的距离算法(该距离算法是买IBeacon设备厂家提供的算法,其实也是一个衰减模型)获得距离。距离进行sma平滑后,通过5个ibeacon设备相互迭代,得到坐标。
需要源码的小伙伴:留言
最近太忙,不能一一回复最新的程序。
需要的可以看看最初的demo:https://github.com/zhoudatang/SimpleIbeacon。
Appconfig类
该类是APP所有配置的配置类,可以根据实际环境,调节这个类。配置内容有:绘图坐标点大小,蓝牙扫描时间,5个ibeacon部署位置,sma平滑精度,卡尔曼算法的误差调节以及距离衰减模型的参数调节。(注意::使用时候,由于beacon设备uuid,蓝牙地址等不同,需要在ibeacondevice中,更改你们的ibeacon设备的uuid,major,minor,以及蓝牙地址,否者不会识别你们的beacon设备)如下:代码
public class AppConfig {
/*
MAIN_ACTIVITY
*/
static private int MAIN_ACTIVITY_SCAN_TIME=3000;
/*
POINT
*/
static private int POINT_RADIUS=25;//绘制点半径
/*
CALMAN
*/
static private double KALMAN_Q1=16; //卡尔曼预测误差的方差
static private double KALMAN_R1=100; //卡尔曼噪声误差的方差
Point类
存储坐标点的数据类
public class Point {
//点的X坐标
private int x;
//点的Y坐标
private int y;
//点的半径,默认为10像素
private int radius = AppConfig.getPointRadius();
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public Point(int x, int y, int radius) {
this.x = x;
this.y = y;
this.radius = radius;
}
public int getX() {
return x;
}
.......
}
queue类
一个自己写的队列类,用于sma平滑数据,队列没满则会一直放即时的距离值进去,满了则计算队列(默认5个值)的平均数,为当前的距离值,然后出一个值,再进最新值
public class queue {
private float[] data ;// 队列
private int front;// 队列头,允许删除
private int rear;// 队列尾,允许插入
private int LENGTH=AppConfig.getQueueLength();
private int full=0;
private int t=0;//判断队列是否装满
private int relute=0;
public queue() {
data = new float[LENGTH];
front = rear = 0;
}
// 入队
public void offer(float date) {
if (rear>=LENGTH)
{
rear=0;
}
data[rear++] = date;
if (t++>=LENGTH)
{
full=1;
//relute=(int)(data[0]+data[1]+data[2]+data[3]+data[4])/5;
for (int i=0;i<LENGTH;i++)
{
relute=0;
relute += data[i];
}
}
}
// 出队
public float poll() {
if (front<LENGTH)
{
float value = data[front];// 保留队列的front端的元素的值
front++;
return value;
}
else
{
front=0;
float value = data[front];
return value;
}
}
public boolean full()
{
if (full==1)
return true;
else
return false;
}
public int getRelute() {
return relute;
}
}
ibeacondevice类
该类是五个Ibeacon设备数据的类,存放五个Ibeacon的uuid,蓝牙地址等信息
public class IbeaconDevice {
public int major;
public int minor;
public String proximityUuid;
public String bluetoothAddress;
int x;
int y;
public IbeaconDevice(String bluetoothAddress,int x,int y)
{
this.bluetoothAddress=bluetoothAddress;
this.x=x;
this.y=y;
major=10;
minor=7;
proximityUuid="fda50693-a4e2-4fb1-afcf-c6eb07647825";
}
public int getMajor() {
return major;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
ibeacon类
该类存放扫描获得的实时beacon对象,与ibeacondevice最大的区别在于RSSI
public class iBeacon {
public String name;
public int major;
public int minor;
public String proximityUuid;
public String bluetoothAddress;
public int txPower;
public int rssi;
public double distance=0;
public int x;
public int y;
}
Mainactivity类
UI界面,主要进行图像的显示和蓝牙的扫描。
敲击扫描按钮,开始扫描
//开启蓝牙扫描
findViewById(R.id.mStartBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mBLE.startLeScan(mScanCallback);
}
});
将我的蓝牙显示的函数:
private void getBlueToothDetail() {
if (mBLE == null) return;
mBluetoothAdapter = mBLE.getBluetoothAdapter();
if (mBluetoothAdapter == null) return;
Textview1.setText("我的蓝牙:" + mBluetoothAdapter.getName() + "\n地址:" + mBluetoothAdapter.getAddress());
}
对了,这次使用的扫描是使用的第三方库,扫描更快更稳定,如下是扫描的回调
private PeriodScanCallback mScanCallback = new PeriodScanCallback(TIME_OUT_SCAN) {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
logDevice(device, rssi, scanRecord);
}
@Override
public void onScanTimeout() {
mBLE.startLeScan(mScanCallback);
toast("扫描超时");
}
};
private void logDevice(BluetoothDevice device, int rssi, byte[] scanRecord) {
ibeacon = iBeaconClass.fromScanData(device, rssi, scanRecord,sma1);//先通过ibeaconclass获得ibeacon类
if (ibeacon == null) return;
Textview1.setText("\n" +"蓝牙地址"+ibeacon.bluetoothAddress);
Textview1.append("\n" + "rssi" + ibeacon.rssi);
Textview1.append("\n" + "distance" + ibeacon.distance);
double t=mtwoPointAlgorithm.algorithm(ibeacon);
Textview1.append("\nt=="+t);
mPointView.drawPoint(new Point(mPointView.getWidth()/2+30,(int)((1-t/50)*mPointView.getHeight())));
//绘制坐标,x坐标默认是图像宽度的一半。走道使用x,y和只使用y差距不大
}
Ibeaconclass类
主要是将获得的广播数据,解析出一个ibeacon类对象。在最开始,会调用卡尔曼算法的类,先过滤RSSi值。
class iBeaconClass {
static Kalman kalman1=new Kalman(AppConfig.getKalmanQ1(),AppConfig.getKalmanR1());//5个卡尔曼滤波器分别对5个ibeacon滤波
static Kalman kalman2=new Kalman(AppConfig.getKalmanQ1(),AppConfig.getKalmanR1());
static Kalman kalman3=new Kalman(AppConfig.getKalmanQ1(),AppConfig.getKalmanR1());
static Kalman kalman4=new Kalman(AppConfig.getKalmanQ1(),AppConfig.getKalmanR1());
static Kalman kalman5=new Kalman(AppConfig.getKalmanQ1(),AppConfig.getKalmanR1());
static IbeaconDevice dev1 = new IbeaconDevice("88:3F:4A:EA:50:8E", 0, AppConfig.getBeaconDevice1Y());
static IbeaconDevice dev2 = new IbeaconDevice("88:3F:4A:EA:4D:CE", 0, AppConfig.getBeaconDevice2Y());
static IbeaconDevice dev3 = new IbeaconDevice("88:3F:4A:EA:4D:D6", 0, AppConfig.getBeaconDevice3Y());
static IbeaconDevice dev4 = new IbeaconDevice("88:3F:4A:EA:4D:B3", 0, AppConfig.getBeaconDevice4Y());
static IbeaconDevice dev5 = new IbeaconDevice("88:3F:4A:EA:50:85", 0, AppConfig.getBeaconDevice5Y());
//解析数据
static iBeacon fromScanData(BluetoothDevice device, int rssi, byte[] scanData,sma sma1) {
// rssi=sma1.run(device.getAddress(),rssi);//平滑数据算法
//卡尔曼滤波
if (device.getAddress().equals(iBeaconClass.dev1.bluetoothAddress))
rssi=(int)iBeaconClass.kalman1.KalmanFilter((double) rssi);
if (device.getAddress().equals(iBeaconClass.dev2.bluetoothAddress))
rssi=(int)iBeaconClass.kalman2.KalmanFilter((double) rssi);
if (device.getAddress().equals(iBeaconClass.dev3.bluetoothAddress))
rssi=(int)iBeaconClass.kalman3.KalmanFilter((double) rssi);
if (device.getAddress().equals(iBeaconClass.dev4.bluetoothAddress))
rssi=(int)iBeaconClass.kalman4.KalmanFilter((double) rssi);
if (device.getAddress().equals(iBeaconClass.dev5.bluetoothAddress))
rssi=(int)iBeaconClass.kalman5.KalmanFilter((double) rssi);
//这个类代码很多。。。。。。。
sma类
简单平均算法,用来平滑滤波后得到的距离。
public class sma {
IbeaconDevice dev1, dev2, dev3, dev4, dev5;
public ArrayList<queue> arr_queue;//存放 5个ibeacon的动态数组 每个队列有5个值
public ArrayList<IbeaconDevice> arr_ineacon_device;
sma()
{
arr_queue=new ArrayList<>();
arr_ineacon_device=new ArrayList<>();
dev1 = new IbeaconDevice("88:3F:4A:EA:50:8E", 0, AppConfig.getBeaconDevice1Y());
dev2 = new IbeaconDevice("88:3F:4A:EA:4D:CE", 0, AppConfig.getBeaconDevice2Y());
dev3 = new IbeaconDevice("88:3F:4A:EA:4D:D6", 0, AppConfig.getBeaconDevice3Y());
dev4 = new IbeaconDevice("88:3F:4A:EA:4D:B3", 0, AppConfig.getBeaconDevice4Y());
dev5 = new IbeaconDevice("88:3F:4A:EA:50:85", 0, AppConfig.getBeaconDevice5Y());
arr_queue.add(new queue());
arr_queue.add(new queue());
arr_queue.add(new queue());
arr_queue.add(new queue());
arr_queue.add(new queue());
arr_ineacon_device.add(dev1);
arr_ineacon_device.add(dev2);
arr_ineacon_device.add(dev3);
arr_ineacon_device.add(dev4);
arr_ineacon_device.add(dev5);
}
public int run(String address,int rssi)
{
if (address.equals(dev1.bluetoothAddress))
{
arr_queue.get(0).offer(rssi);
return 0;
}
else if (address.equals(dev2.bluetoothAddress))
{
arr_queue.get(1).offer(rssi);
return 0;
}
else if (address.equals(dev3.bluetoothAddress))
{
arr_queue.get(2).offer(rssi);
return 0;
}
else if (address.equals(dev4.bluetoothAddress))
{
arr_queue.get(3).offer(rssi);
return 0;
}
else if (address.equals(dev5.bluetoothAddress))
{
arr_queue.get(4).offer(rssi);
return 0;
}
else {
return 0;
}
}
Kalman类
使用的是之前写好的类
Algorithm类
类中有,在滤波和平滑后进行的 迭代,两点距离算法,最后获得的是显示的坐标。外部调用algorithm,并传入一个即时ibeacon值,先判断队列是否满了(sma算法),满了则执行迭代算法。
public double algorithm(iBeacon now_ibeacon) {
sma1.run(now_ibeacon.bluetoothAddress,now_ibeacon.rssi);//入队
double buff=0;
int temp=0;
for(int i=0;i<5;i++)//循环遍历5个队列
{
if(sma1.arr_queue.get(i).full())//如果大于5)
{
for (int j = i + 1; j < 5; j++)
if (sma1.arr_queue.get(j).full())//如果大于5
{
buff+=twopoint(sma1.arr_ineacon_device.get(i),sma1.arr_ineacon_device.get(j),sma1.arr_queue.get(i).getRelute(),sma1.arr_queue.get(j).getRelute());
temp++;
}
}
}
buff=buff/temp;
return buff;
}
public double twopoint(IbeaconDevice now_ibeacon,IbeaconDevice last_ibeacon,double now_dis,double last_dis)
{
double relute = 0;
double buf1, buf2;
if (now_dis + last_dis > Math.abs(now_ibeacon.y - last_ibeacon.y))//如果是在两点外
{
if (now_ibeacon.y > last_ibeacon.y)//now是距离更远的信标
{
buf1 = now_ibeacon.y + now_dis;
buf2 = last_ibeacon.y + last_dis;
relute = (buf1 + buf2) / 2;
} else//now是距离更近的信标
{
buf1 = now_ibeacon.y - now_dis;
buf2 = last_ibeacon.y - last_dis;
relute = (buf1 + buf2) / 2;
}
} else//在两点内
{
if (now_ibeacon.y > last_ibeacon.y)//now是距离更远的信标
{
buf1 = now_ibeacon.y - now_dis;
buf2 = last_ibeacon.y + last_dis;
relute = (buf1 + buf2) / 2;
} else//last是距离更远的信标
{
buf1 = now_ibeacon.y + now_dis;
buf2 = last_ibeacon.y - last_dis;
relute = (buf1 + buf2) / 2;
}
}
return relute;
}
PointImageView类
用来显示的类
public class PointImageView extends AppCompatImageView {
public PointImageView(Context context) {
super(context);
}
public PointImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PointImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private Point mPoint = null;
private Paint mPointPaint = new Paint();
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPointPaint.setColor(Color.RED);
if (mPoint != null) {
canvas.drawCircle(mPoint.getX(), mPoint.getY(), mPoint.getRadius(), mPointPaint);
}
}
public void drawPoint(Point p) {
mPoint = p;
invalidate();
}