iBeacon
蓝牙设备分为3种类型:
- Bluetooth设备(蓝牙BR/EDR):只支持传统蓝牙的设备。
- Bluetooth Smart Ready 设备(蓝牙4.0双模):同时支持传统蓝牙和LE模式的设备。
- Bluetooth Smart(BLE单模):只支持LE模式的设备。Beacon设备只支持low energy protocols(LE低功耗协议),因此能靠一颗纽扣电池就能运行很长时间。
BLE起源
BLE 起源于2006年的Nokia的Wibree技术,后被整合进蓝牙,在2010年发布的蓝牙4.0技术规范中成为其中一部分,协议栈入下图所示(它是一组与传统蓝牙不同的协议)
BLE 与传统蓝牙异同点
- BLE 与传统蓝牙使用的都是相同的波段(2.4GHz - 2.4835GHz)。BLE协议的船速速率比较低,因此除了用于发现设备和做一些简单通信之外,不太适合用于传输大量的数据流。
- 一台蓝牙设备可同时与其它七台蓝牙设备建立连接
- 使用跳频频谱扩展技术,把频带分成若干个跳频信道(hop channel),在一次连接中,无线电收发器按一定的码序列不断地从一个信道“跳”到另一个信道
- 数据传输速率可达1Mbit/s
- BLE和传统蓝牙信号都能覆盖到100米的范围
- BLE最大的优势是功耗降低了90%,同时传输距离超过传统蓝牙的100米,安全和稳定性提高(支持AES加密和CRC验证)
iBeacon概述
iBeacon是苹果公司2013年9月发布的移动设备用OS(iOS7)上配备的新功能。其工作方式是,配备有低功耗蓝牙(BLE)通信功能的设备使用BLE技术向周围发送自己特有的ID,接收到该ID的应用软件会根据该ID采取一些行动。
例如:
- 在店铺里设置iBeacon通信模块的话,便可让iPhone和iPad上运行一资讯告知服务器,或者由服务器向顾客发送折扣券及进店积分。
- 在家电发生故障或停止工作时使用iBeacon向应用软件发送资讯。
- 微信的签到就是用iBeacon
iBeacon 的特点
- 它采用BLE的广播信道传送信号,因此无需配对
- iBeacon 不具备传统意义上的数据传输功能。因为Beacon基站只推送位置信息,采用的是不可连接模式。
- 可以通过Beacon基站进行定位,iBeacon本质上是通过rssi来判断设备与基站的距离。
iBeacon 原理
iBeacon中一般有两个角色
- 基站/从机/外围设备(peripheral),发射端
- 手机/主机/中心设备(central),接收者
发射端通过BLE的广告通信信道,以一定时间间隔向外广播数据包(Adverting Data,一般是每秒2-3次),每个信号中至少携带了三个主要信息:UUID、Major‘、Minor,这三个信号组成了一个iBeacon的唯一标识符。
当某个监听设备监听到这个广播数据的时候,就好发送 Scan Response Request,请求广播发送方发送扫描响应数据。
这两部分数据的长度都是固定的 31 字节。
在 Android 中,系统会把这两个数据拼接在一起,返回一个 62 字节的数组。
系统支持
- 外围设备(Peripheral,发射信号)
Android 5.0+,即 api level >= 21
iOS7 以上 - 中心设备(Central,接收信号)
Android 4.3+,即 api level >= 18
Android 实现 iBeacon 的收发
添加权限
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- Android 6.0以后需要添加位置权限,才能搜索的到蓝牙设备 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
检查BLE是否可用
private boolean initBluetooth(Context context) {
//判断Android设备是否具有蓝牙特性
if(context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
bluetoothAdapter = bluetoothManager.getAdapter();
//获取不到BluetoothAdapter,说明不支持BLE
return bluetoothAdapter != null;
} else {
bluetoothAdapter = null;
return false;
}
}
开始广播
private boolean startAdvertiser() {
if(Build.VERSION.SDK_INT < 21) return false;
if(!enableBluetooth()) return false;
//获取BLE广播
bluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
if(bluetoothLeAdvertiser == null) {
//设备不支持peripheral
return false;
}
String beaconUuid = "fda50693-a4e2-4fb1-afcf-c6eb07647825";
//java没有提供16bit的uuid,只有16字节(128bit)的uuid,所以不能设置serviceData
String serviceDataUuid = "0-0-0-0-4386";
String data = "7F7D7D26647D6564807CFEB947FA53745B5097C2b97C3A8363837C";
try {
bluetoothLeAdvertiser.startAdvertising(
createAdvertiseSettings(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY, true, 0, AdvertiseSettings.ADVERTISE_TX_POWER_HIGH),
createAdvertiseData(beaconUuid, (short)10028, (short)60350, (byte) -59, serviceDataUuid, data),
advertiseCallback);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
private AdvertiseSettings createAdvertiseSettings(int advertiseMode, boolean connectable, int timeoutMillis, int txPowerLevel) {
AdvertiseSettings.Builder builder = new AdvertiseSettings.Builder();
//广播模式:低功率、平衡、低延迟
builder.setAdvertiseMode(advertiseMode)
//是否允许连接
.setConnectable(connectable)
//默认会广播3分钟(180秒),如果超时时间设置为0,则会取消时间限制
.setTimeout(timeoutMillis)
//发射功率:极低、低、中、高
.setTxPowerLevel(txPowerLevel);
return builder.build();
}
/**
* iBeacon 的 ManufacturerData(厂商自定义信息)
* 01 byte type = 0x02 指明它是 iBeacon 帧
* 01 byte len = 0x15 = 21
* 16 byte UUID
* 02 byte major
* 02 byte minor
* 01 byte tx power
*/
private AdvertiseData createAdvertiseData(String beaconUuidStr, short major, short minor, byte txPower, String serviceDataUuidStr, String serviceData) {
//Beacon 标识符
byte beaconType = 0x02;
//Beacon 厂商自定义信息长度
byte beaconLen = 0x15;
//Company: Apple, Inc. <0x004C>
byte manufacturerId = 0x004c;
UUID beaconUuid = UUID.fromString(beaconUuidStr);
byte[] manufacturerSpecificData = new byte[23];
ByteBuffer bb = ByteBuffer.wrap(manufacturerSpecificData);
bb.order(ByteOrder.BIG_ENDIAN);
bb.put(beaconType)
.put(beaconLen)
.putLong(beaconUuid.getMostSignificantBits())
.putLong(beaconUuid.getLeastSignificantBits())
.putShort(major)
.putShort(minor)
.put(txPower);
AdvertiseData.Builder builder = new AdvertiseData.Builder();
//广播服务的UUID
// builder.addServiceUuid(uuid)
//添加服务数据UUID和服务数据
// builder.addServiceData(ParcelUuid.fromString(serviceDataUuidStr), str2bcd(serviceData))
//添加制造商ID和数据
builder.addManufacturerData(manufacturerId, manufacturerSpecificData)
//广播是否包含设备名
.setIncludeDeviceName(false)
//广播是否包含发射功率等级
.setIncludeTxPowerLevel(false);
return builder.build();
}
停止广播
private void stopAdvertising() {
if(bluetoothLeAdvertiser != null) {
bluetoothLeAdvertiser.stopAdvertising(advertiseCallback);
}
}
下面分析一下 startAdvertising 源码
/**
开启BLE广播。如果操作成功,广播数据将会被广播出去。当设备扫描到这个广播之后,会发送一个扫描请求,请求发送方返回扫描响应数据。该方法调用之后会立即返回,操作的结果通过回调函数分发。
* Start Bluetooth LE Advertising. The {@code advertiseData} will be broadcasted if the
* operation succeeds. The {@code scanResponse} is returned when a scanning device sends an
* active scan request. This method returns immediately, the operation status is delivered
* through {@code callback}.
* <p>
* Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
*
* @param settings Settings for Bluetooth LE advertising.
* @param advertiseData Advertisement data to be advertised in advertisement packet.
* @param scanResponse Scan response associated with the advertisement data.
* @param callback Callback for advertising status.
*/
public void startAdvertising(AdvertiseSettings settings,
AdvertiseData advertiseData, AdvertiseData scanResponse,
final AdvertiseCallback callback) {
synchronized (mLegacyAdvertisers) {
//判断蓝牙状态
BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
}
//获取周边设备是否可连接
boolean isConnectable = settings.isConnectable();
//计算总字节数, 当可连接时,初始3字节。最大数据为31字节
if (totalBytes(advertiseData, isConnectable) > MAX_LEGACY_ADVERTISING_DATA_BYTES
|| totalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) {
//当错误时,通过 new Handler(Looper.getMainLooper()) 主线程的消息循环处理器,将事件在主线程中回调
postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
return;
}
//如果start成功,那么callback会被当作键值存储到mLegacyAdvertisers列表中(如果列表已经包含该项,则说明已启动)
if (mLegacyAdvertisers.containsKey(callback)) {
postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);
return;
}
//Builder模式(建造者模式),属于设计模式中的一种,用来解决构造函数参数太多的问题。Builder模式的代码量会比正常多不少,但是随之带来的好处是代码的可读性和可维护性。但是,往往一个好的设计模式或架构,就是在牺牲某一种能力从而大大增强另一种能力
AdvertisingSetParameters.Builder parameters = new AdvertisingSetParameters.Builder();
//当设置为true的时候,会发送符合4.x规范的广播
parameters.setLegacyMode(true);
parameters.setConnectable(isConnectable);
parameters.setScannable(true); // legacy advertisements we support are always scannable
//根据事先定义的常量,配置广播间隔
if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) {
parameters.setInterval(1600); // 1s
} else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) {
parameters.setInterval(400); // 250ms
} else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) {
parameters.setInterval(160); // 100ms
}
//根据事先定义的常量,设置信号强度等级
if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW) {
parameters.setTxPowerLevel(-21);
} else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_LOW) {
parameters.setTxPowerLevel(-15);
} else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) {
parameters.setTxPowerLevel(-7);
} else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) {
parameters.setTxPowerLevel(1);
}
//广播间隔,单位10ms
int duration = 0;
int timeoutMillis = settings.getTimeout();
if (timeoutMillis > 0) {
duration = (timeoutMillis < 10) ? 1 : timeoutMillis / 10;
}
AdvertisingSetCallback wrapped = wrapOldCallback(callback, settings);
//键是BLE广播操作的回调,值是改变BLE广播设置的回调
mLegacyAdvertisers.put(callback, wrapped);
//通过gatt开启广播
startAdvertisingSet(parameters.build(), advertiseData, scanResponse, null, null,
duration, 0, wrapped);
}
}
参数简析
- AdvertiseSettings settings:广播参数设置
- int mAdvertiseMode(电量模式)
- int mAdvertiseTxPowerLevel(信号强度)
- int mAdvertiseTimeoutMillis(发射间隔)
- boolean mAdvertiseConnectable(是否允许连接)
- AdvertiseData advertiseData:外围设备按照一定的时间间隔向空中发送广播包
- List<ParcelUuid> mManufacturerSpecificData(厂商自定义UUID)
- Map<ParcelUuid, byte[]> mServiceData(UUID对应的数据)
- boolean mIncludeTxPowerLevel(是否包含发射信号等级)
- boolean mIncludeDeviceName(是否包含设备名)
- AdvertiseData scanResponse:当某个设备监听到这个广播数据的时候,会通过发送Scan Response Request
- AdvertiseCallback callback: 广播回调函数
- void onStartSuccess(AdvertiseSettings settingsInEffect)
- void onStartFailure(int errorCode)
AdvertiseSettings
public final class AdvertiseSettings implements Parcelable {
/** 低功耗广播,
* Perform Bluetooth LE advertising in low power mode. This is the default and preferred
* advertising mode as it consumes the least power.
*/
public static final int ADVERTISE_MODE_LOW_POWER = 0;
/** 性能平衡方式广播
* Perform Bluetooth LE advertising in balanced power mode. This is balanced between advertising
* frequency and power consumption.
*/
public static final int ADVERTISE_MODE_BALANCED = 1;
/** 低延迟广播
* Perform Bluetooth LE advertising in low latency, high power mode. This has the highest power
* consumption and should not be used for continuous background advertising.
*/
public static final int ADVERTISE_MODE_LOW_LATENCY = 2;
/** 极低的功率
* Advertise using the lowest transmission (TX) power level. Low transmission power can be used
* to restrict the visibility range of advertising packets.
*/
public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0;
/** 低功率
* Advertise using low TX power level.
*/
public static final int ADVERTISE_TX_POWER_LOW = 1;
/** 中等功率
* Advertise using medium TX power level.
*/
public static final int ADVERTISE_TX_POWER_MEDIUM = 2;
/** 高功率
* Advertise using high TX power level. This corresponds to largest visibility range of the
* advertising packet.
*/
public static final int ADVERTISE_TX_POWER_HIGH = 3;
/** 最大的广播发送间隔(SIG,Special Interest Group,蓝牙兴趣小组)
* The maximum limited advertisement duration as specified by the Bluetooth SIG
*/
private static final int LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000;
AdvertiseCallback
/**
BLE广播的回调,用来分发广播操作的状态
* Bluetooth LE advertising callbacks, used to deliver advertising operation status.
*/
public abstract class AdvertiseCallback {
/** 广播成功
* The requested operation was successful.
*
* @hide
*/
public static final int ADVERTISE_SUCCESS = 0;
/** 广播失败,数据超过了31字节
* Failed to start advertising as the advertise data to be broadcasted is larger than 31 bytes.
*/
public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 1;
/** 广播失败,无可用的实例
* Failed to start advertising because no advertising instance is available.
*/
public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2;
/** 广播已经开启
* Failed to start advertising as the advertising is already started.
*/
public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3;
/** 广播失败,由于内部错误
* Operation failed due to an internal error.
*/
public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 4;
/** 平台不支持该特性
* This feature is not supported on this platform.
*/
public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5;
AdvertisingSetCallback
/**
BLE广播设置改变的回调,用来分发广播操作的状态
* Bluetooth LE advertising set callbacks, used to deliver advertising operation
* status.
*/
public abstract class AdvertisingSetCallback {
//省略的常量定义和 AdvertiseCallback 一致
...
/**
* Callback triggered in response to {@link BluetoothLeAdvertiser#startAdvertisingSet}
* indicating result of the operation. If status is ADVERTISE_SUCCESS, then advertisingSet
* contains the started set and it is advertising. If error occured, advertisingSet is
* null, and status will be set to proper error code.
*
* @param advertisingSet The advertising set that was started or null if error.
* @param txPower tx power that will be used for this set.
* @param status Status of the operation.
*/
public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, int status) {
}
/**
* Callback triggered in response to {@link BluetoothLeAdvertiser#stopAdvertisingSet}
* indicating advertising set is stopped.
*
* @param advertisingSet The advertising set.
*/
public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {
}
/**
* Callback triggered in response to {@link BluetoothLeAdvertiser#startAdvertisingSet}
* indicating result of the operation. If status is ADVERTISE_SUCCESS, then advertising set is
* advertising.
*
* @param advertisingSet The advertising set.
* @param status Status of the operation.
*/
public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable, int status) {
}
/**
* Callback triggered in response to {@link AdvertisingSet#setAdvertisingData} indicating
* result of the operation. If status is ADVERTISE_SUCCESS, then data was changed.
*
* @param advertisingSet The advertising set.
* @param status Status of the operation.
*/
public void onAdvertisingDataSet(AdvertisingSet advertisingSet, int status) {
}
/**
* Callback triggered in response to {@link AdvertisingSet#setAdvertisingData} indicating
* result of the operation.
*
* @param advertisingSet The advertising set.
* @param status Status of the operation.
*/
public void onScanResponseDataSet(AdvertisingSet advertisingSet, int status) {
}
/**
* Callback triggered in response to {@link AdvertisingSet#setAdvertisingParameters}
* indicating result of the operation.
*
* @param advertisingSet The advertising set.
* @param txPower tx power that will be used for this set.
* @param status Status of the operation.
*/
public void onAdvertisingParametersUpdated(AdvertisingSet advertisingSet,
int txPower, int status) {
}
/**
* Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingParameters}
* indicating result of the operation.
*
* @param advertisingSet The advertising set.
* @param status Status of the operation.
*/
public void onPeriodicAdvertisingParametersUpdated(AdvertisingSet advertisingSet, int status) {
}
/**
* Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingData}
* indicating result of the operation.
*
* @param advertisingSet The advertising set.
* @param status Status of the operation.
*/
public void onPeriodicAdvertisingDataSet(AdvertisingSet advertisingSet,
int status) {
}
/**
advertisingSet.setPeriodicAdvertisingEnabled返回void,它通过调用mGatt.setPeriodicAdvertisingEnabled(mAdvertiserId, enable)异步设置是否启用,然后通过回调函数返回
* Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingEnabled}
* indicating result of the operation.
*
* @param advertisingSet The advertising set.
* @param status Status of the operation.
*/
public void onPeriodicAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable,
int status) {
}
/**
advertisingSet.getOwnAddress返回void,它通过调用mGatt.getOwnAddress(mAdvertiserId)异步获取地址,获取到之后,通过回调函数返回
* Callback triggered in response to {@link AdvertisingSet#getOwnAddress()}
* indicating result of the operation.
*
* @param advertisingSet The advertising set.
* @param addressType type of address.
* @param address advertising set bluetooth address.
* @hide
*/
public void onOwnAddressRead(AdvertisingSet advertisingSet, int addressType, String address) {
}
}
接收广播
(略)
测试工具推荐
nRF Connect
https://connect.nrf.com/
完整代码
public class MainActivity extends Activity {
private static final int REQUEST_ENABLE_BT = 1001;
private TextView textview;
private BluetoothAdapter bluetoothAdapter;
private BluetoothLeAdvertiser bluetoothLeAdvertiser;
private AdvertiseCallback advertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
//开启广播成功的回调
textview.setText("微信摇一摇开启成功");
}
@Override
public void onStartFailure(int errorCode) {
//开启广播失败的回调
textview.setText("微信摇一摇开启失败," + errorCode);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textview = findViewById(R.id.textview);
textview.setText("需要安卓5.0及以上的手机才能使用该功能");
startAdvertiser();
}
@Override
protected void onDestroy() {
super.onDestroy();
stopAdvertising();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == REQUEST_ENABLE_BT) {
if(resultCode == Activity.RESULT_OK) {
startAdvertiser();
} else {
textview.setText("不打开蓝牙无法使用微信摇一摇");
}
}
}
private boolean enableBluetooth() {
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
return false;
}
return true;
}
private void stopAdvertising() {
if(bluetoothLeAdvertiser != null) {
bluetoothLeAdvertiser.stopAdvertising(advertiseCallback);
}
}
private boolean startAdvertiser() {
if(Build.VERSION.SDK_INT < 21) return false;
if(!enableBluetooth()) return false;
//获取BLE广播
bluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
if(bluetoothLeAdvertiser == null) {
//设备不支持peripheral
return false;
}
String beaconUuid = "fda50693-a4e2-4fb1-afcf-c6eb07647825";
//java没有提供16bit的uuid,只有16字节(128bit)的uuid,所以不能设置serviceData
String serviceDataUuid = "0-0-0-0-4386";
String data = "7F7D7D26647D6564807CFEB947FA53745B5097C2b97C3A8363837C";
try {
bluetoothLeAdvertiser.startAdvertising(
createAdvertiseSettings(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY, true, 0, AdvertiseSettings.ADVERTISE_TX_POWER_HIGH),
createAdvertiseData(beaconUuid, (short)10028, (short)60350, (byte) -59, serviceDataUuid, data),
advertiseCallback);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
private AdvertiseSettings createAdvertiseSettings(int advertiseMode, boolean connectable, int timeoutMillis, int txPowerLevel) {
AdvertiseSettings.Builder builder = new AdvertiseSettings.Builder();
//广播模式:低功率、平衡、低延迟
builder.setAdvertiseMode(advertiseMode)
//是否允许连接
.setConnectable(connectable)
//默认会广播3分钟(180秒),如果超时时间设置为0,则会取消时间限制
.setTimeout(timeoutMillis)
//发射功率:极低、低、中、高
.setTxPowerLevel(txPowerLevel);
return builder.build();
}
/**
* iBeacon 的 ManufacturerData(厂商自定义信息)
* 01 byte type = 0x02 指明它是 iBeacon 帧
* 01 byte len = 0x15 = 21
* 16 byte UUID
* 02 byte major
* 02 byte minor
* 01 byte tx power
*/
private AdvertiseData createAdvertiseData(String beaconUuidStr, short major, short minor, byte txPower, String serviceDataUuidStr, String serviceData) {
//Beacon 标识符
byte beaconType = 0x02;
//Beacon 厂商自定义信息长度
byte beaconLen = 0x15;
//Company: Apple, Inc. <0x004C>
byte manufacturerId = 0x004c;
UUID beaconUuid = UUID.fromString(beaconUuidStr);
byte[] manufacturerSpecificData = new byte[23];
ByteBuffer bb = ByteBuffer.wrap(manufacturerSpecificData);
bb.order(ByteOrder.BIG_ENDIAN);
bb.put(beaconType)
.put(beaconLen)
.putLong(beaconUuid.getMostSignificantBits())
.putLong(beaconUuid.getLeastSignificantBits())
.putShort(major)
.putShort(minor)
.put(txPower);
AdvertiseData.Builder builder = new AdvertiseData.Builder();
//广播服务的UUID
// builder.addServiceUuid(uuid)
//添加服务数据UUID和服务数据
// builder.addServiceData(ParcelUuid.fromString(serviceDataUuidStr), str2bcd(serviceData))
//添加制造商ID和数据
builder.addManufacturerData(manufacturerId, manufacturerSpecificData)
//广播是否包含设备名
.setIncludeDeviceName(false)
//广播是否包含发射功率等级
.setIncludeTxPowerLevel(false);
return builder.build();
}
public static String bcd2Str(byte[] bytes) {
StringBuffer temp = new StringBuffer(bytes.length * 2);
for (int i = 0; i < bytes.length; i++) {
temp.append((byte) ((bytes[i] & 0xf0) >>> 4));
temp.append((byte) (bytes[i] & 0x0f));
}
return temp.toString();
}
public static byte [] str2bcd(String s)
{
if(s.length () % 2 != 0) {
s = "0" + s;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream ();
char [] cs = s.toCharArray ();
for (int i = 0; i < cs.length; i += 2)
{
int high = cs [i] - 48;
int low = cs [i + 1] - 48;
baos.write (high << 4 | low);
}
return baos.toByteArray ();
}
}
以上测试代码主要用来模拟本公司微信签到的 iBeacon,实测可用。
但是 ParcelUUID 和 UUID都只提供了128-bit 的构造方法(内部都是两个long实现的),没有找到 16bit 的构造方法,所以 ServiceData 模拟不出来!!!