如何在Android 上面实现GATT Server

来源:互联网 发布:matlab在线编程 编辑:程序博客网 时间:2024/05/01 13:29

这篇文章主要介绍如何在android手机上实现gatt server的功能。共分两部分,第一部分是背景介绍,第二部分是程序解释。


1.背景介绍

老话说得好,用理论指导实践,再从实践中总结出理论,由此不断的升华,达到新的高度,我们在这一节介绍一点ble的背景知识。

在蓝牙的BLE中,共有四种模式, 列表如下:

角色解释

Broadcaster                                                   

我只往周围广播消息,但是你们谁也不要来连接我,因为我想静静                        

Observer 

我只接收周围广播过来的消息,我也不会连接别人的,别问我是谁,我想静静

Peripheral 

我除了会往周围广播消息之外,还接受周围设备的连接并让他们来获取我的数据

Central 

我除了接收周围广播过来的消息之外,我还会去连接周围的设备并获取他们的数据

有没有发现,其实broadcaster 是peripheral的子集,observer是central的子集,套路很深啊。

在很久以前,我们习惯了用手机作为central或者observer,用一个单独的蓝牙ble芯片来作为broadcaster或者peripheral,大家相安无事,各负其责。

突然有一天,安卓系统(好吧其实iOS也是这样的)也支持了自己作为broadcaster或者peripheral,这世界变化太快。

其实,不管是谁作为peripheral,例如一个ble的芯片,抑或是一个手机,套路总是一样的,过程也是一样的,唯一变化的是我们把开发环境换了一个地方而已。

要想在一个设备上实现peripheral(这篇文章不再提broadcaster了,提了peripheral还讲broadcaster干嘛,吃了满汉全席还再需要吃兰州拉面或者沙县小吃吗),总共分三步:

1)设置一下自己需要advertise的内容,即,要在广播包里广播出来什么东西,例如名字,或者是支持的service什么的。

2)设置一下自己能够给 远端提供什么内容呢?总得搞一个service吧,不管是sig制订的标准的,还是自己定义的,另外自己总得有数据给别人吧,例如你想做一个温度计,那么温度的这个东西总需要定义一下的,即为characteristic加入到自己系统的gatt 数据库(根据不同产品的名字不同命名不同,例如android叫gatt server)中去,抑或是更高级一点加个descriptor。并注册好相应的callback函数,例如远端来读数据了怎么办,来写数据了怎么办。

3)开动start advertise,静静读等待有缘设备过来。


这就完了,就是这么简单。


2.程序解释

这一小节来解释一下android上怎么来实现gatt server。

第一步,先获得相应的权限,这没啥好说的。

    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />    <uses-permission android:name="android.permission.BLUETOOTH" />    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
注意,如果编写ble程序的话可以把location的权限申请也加上去,我还没查为什么需要加,可能是和beacon有关系?


第二步,看本机是否支持蓝牙ble和广播,如果支持就继续往下走,如果不支持就任程序员自由发挥。

{            mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))                    .getAdapter();            // Is Bluetooth supported on this device?            if (mBluetoothAdapter != null) {                // Is Bluetooth turned on?                if (mBluetoothAdapter.isEnabled()) {                    // Are Bluetooth Advertisements supported on this device?                    if (mBluetoothAdapter.isMultipleAdvertisementSupported()) {                        // Everything is supported and enabled, load the fragments.                        setupFragments();                    } else {                        // Bluetooth Advertisements are not supported.                        showErrorText(R.string.bt_ads_not_supported);                    }                } else {                    // Prompt user to turn on Bluetooth (logic continues in onActivityResult()).                    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);                    startActivityForResult(enableBtIntent, Constants.REQUEST_ENABLE_BT);                }            } else {                // Bluetooth is not supported.                showErrorText(R.string.bt_not_supported);            }        }


第三步,构建要广播的信息的内容。ble的连接和classic的蓝牙连接不同,设备查找方式也不同。在classic蓝牙中,假设a想查找到b设备,a需要一股脑的往周围扔好多的查询的package,然后再等待周围的设备回复,而如果此时b恰巧收到了a发出来的包,则b会在一个random的delay中回复a,此时则a查到了b。而坐ble中,则是b一直往外面扔package,意思是我在这里!周围有人嘛?而若a恰巧听到了b的package,则表示a查找到了b。所以,一个ble设备要想被别人连接,那么它必须的需要往外面发点儿package才行,这一步,就是决定往外面发点儿啥好呢?反正是最低消费,不发白不发。

咱们先看个开始广播这个函数。

public void startAdvertising (AdvertiseSettings settings, AdvertiseData advertiseData, AdvertiseCallback callback)

第一个参数,advertisesettings,主要是用来设置这次广播的一些属性,例如,是要广播的密集一点(那么就比较耗电),或者是功率大一点(也耗电),以及广播多少时间(时间太长了就把电耗完了)等等。

第二个参数,advertisedata,主要是用来设置广播出去的内容是什么。例如是否包含本地设备的名字,是否包含发射功率,要包含的service是什么等等。

第三个参数,advertisecallback,主要是回调一下广播命令发送出去之后的状态,即是否成功?失败的话是什么原因?

所以,我们把这三个参数准备好,然后等待开始广播的时候作为参数传下去就行了。


/**     * Returns an AdvertiseData object which includes the Service UUID and Device Name.     */    private AdvertiseData buildAdvertiseData() {        /**         * Note: There is a strict limit of 31 Bytes on packets sent over BLE Advertisements.         *  This includes everything put into AdvertiseData including UUIDs, device info, &         *  arbitrary service or manufacturer data.         *  Attempting to send packets over this limit will result in a failure with error code         *  AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE. Catch this error in the         *  onStartFailure() method of an AdvertiseCallback implementation.         */        AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder();        dataBuilder.addServiceUuid(Constants.Service_UUID);        dataBuilder.setIncludeDeviceName(true);        /* For example - this will cause advertising to fail (exceeds size limit) */        //String failureData = "asdghkajsghalkxcjhfa;sghtalksjcfhalskfjhasldkjfhdskf";        //dataBuilder.addServiceData(Constants.Service_UUID, failureData.getBytes());        return dataBuilder.build();    }    /**     * Returns an AdvertiseSettings object set to use low power (to help preserve battery life)     * and disable the built-in timeout since this code uses its own timeout runnable.     */    private AdvertiseSettings buildAdvertiseSettings() {        AdvertiseSettings.Builder settingsBuilder = new AdvertiseSettings.Builder();        settingsBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER);        settingsBuilder.setTimeout(0);        return settingsBuilder.build();    }    /**     * Custom callback after Advertising succeeds or fails to start. Broadcasts the error code     * in an Intent to be picked up by AdvertiserFragment and stops this Service.     */    private class SampleAdvertiseCallback extends AdvertiseCallback {        @Override        public void onStartFailure(int errorCode) {            super.onStartFailure(errorCode);            Log.d(TAG, "Advertising failed");            sendFailureIntent(errorCode);            stopSelf();        }        @Override        public void onStartSuccess(AdvertiseSettings settingsInEffect) {            super.onStartSuccess(settingsInEffect);            Log.d(TAG, "Advertising successfully started");        }    }    /**     * Builds and sends a broadcast intent indicating Advertising has failed. Includes the error     * code as an extra. This is intended to be picked up by the {@code AdvertiserFragment}.     */    private void sendFailureIntent(int errorCode){        Intent failureIntent = new Intent();        failureIntent.setAction(ADVERTISING_FAILED);        failureIntent.putExtra(ADVERTISING_FAILED_EXTRA_CODE, errorCode);        sendBroadcast(failureIntent);    }

第四步,准备自己的东西,即我要给远端什么内容?远端连接过来之后,我如何与之交互?

在这里我们举个最简单的例子,即告诉远端我是一个什么样的设备,我的设备信息版本号什么的。


/*prepare service here*/    private void addDeviceInfoService() {        /*UUID can get from SIG website*/        final String SERVICE_DEVICE_INFORMATION = "0000180a-0000-1000-8000-00805f9b34fb";        final String SOFTWARE_REVISION_STRING = "00002A28-0000-1000-8000-00805f9b34fb";        BluetoothGattService previousService =                mGattServer.getService( UUID.fromString(SERVICE_DEVICE_INFORMATION));        if(null != previousService)            mGattServer.removeService(previousService);        BluetoothGattCharacteristic softwareVerCharacteristic = new BluetoothGattCharacteristic(                UUID.fromString(SOFTWARE_REVISION_STRING),                BluetoothGattCharacteristic.PROPERTY_READ,                BluetoothGattCharacteristic.PERMISSION_READ        );        BluetoothGattService deviceInfoService = new BluetoothGattService(                UUID.fromString(SERVICE_DEVICE_INFORMATION),                BluetoothGattService.SERVICE_TYPE_PRIMARY);        softwareVerCharacteristic.setValue(new String(getString(R.string.version)).getBytes());        deviceInfoService.addCharacteristic(softwareVerCharacteristic);        mGattServer.addService(deviceInfoService);    }

下面的代码就是实现gatt servercallback的回调函数,即准备远端连接来之后,我方如何与远端交互。

rivate final BluetoothGattServerCallback mGattServerCallback =            new BluetoothGattServerCallback() {                @Override                public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {                    Log.d(TAG, "onConnectionStateChange: gatt server connection state changed, new state " + Integer.toString(newState));                    super.onConnectionStateChange(device,status,newState);                }                @Override                public void onServiceAdded(int status, BluetoothGattService service) {                    Log.d(TAG, "onServiceAdded: " + Integer.toString(status));                    super.onServiceAdded(status, service);                }                @Override                public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {                    Log.d(TAG, "onCharacteristicReadRequest: " + "requestId" + Integer.toString(requestId) + "offset" + Integer.toString(offset));                    super.onCharacteristicReadRequest(device, requestId, offset, characteristic);                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());                }                @Override                public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {                    Log.d(TAG, "onCharacteristicWriteRequest: " + "data = "+ value.toString());                    super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);                    mGattServer.sendResponse(device, requestId,BluetoothGatt.GATT_SUCCESS,offset,value);                    /*store data here*/                }                @Override                public void onNotificationSent(BluetoothDevice device, int status)                {                    Log.d(TAG, "onNotificationSent: status = " + Integer.toString(status));                    super.onNotificationSent(device, status);                }                @Override                public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {                    Log.d(TAG, "onDescriptorReadRequest: requestId = " + Integer.toString(requestId));                    super.onDescriptorReadRequest(device, requestId, offset, descriptor);                    mGattServer.sendResponse(device, requestId,BluetoothGatt.GATT_SUCCESS,offset, descriptor.getValue());                }                @Override                public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {                    Log.d(TAG, "onDescriptorWriteRequest: requestId = " + Integer.toString(requestId));                    super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,offset,value);                }                @Override                public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {                    Log.d(TAG, "onExecuteWrite: requestId = " + Integer.toString(requestId));                    super.onExecuteWrite(device, requestId, execute);                    /*in case we stored data before, just execute the write action*/                }                @Override                public void onMtuChanged (BluetoothDevice device, int mtu) {                    Log.d(TAG, "onMtuChanged: mtu = " + Integer.toString(mtu));                }            };

第五步,申请一下adapter以及gattserver等。

/**     * Get references to system Bluetooth objects if we don't have them already.     */    private void initialize() {        if (mBluetoothLeAdvertiser == null) {            BluetoothManager mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);            if (mBluetoothManager != null) {                BluetoothAdapter mBluetoothAdapter = mBluetoothManager.getAdapter();                if (mBluetoothAdapter != null) {                    mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();                    mGattServer = mBluetoothManager.openGattServer(this,mGattServerCallback);                    if (mGattServer == null) {                        Toast.makeText(this, getString(R.string.bt_gatt_server_null), Toast.LENGTH_LONG).show();                    } else {                        addDeviceInfoService();/*build gatt server data here*/                    }                } else {                    Toast.makeText(this, getString(R.string.bt_null), Toast.LENGTH_LONG).show();                }            } else {                Toast.makeText(this, getString(R.string.bt_null), Toast.LENGTH_LONG).show();            }        }    }

第六部,获得某个事件之后,例如click等开始广播,至此,就没啥事儿了。

/**     * Starts BLE Advertising.     */    private void startAdvertising() {        Log.d(TAG, "Service: Starting Advertising");        if (mAdvertiseCallback == null) {            AdvertiseSettings settings = buildAdvertiseSettings();            AdvertiseData data = buildAdvertiseData();            mAdvertiseCallback = new SampleAdvertiseCallback();            if (mBluetoothLeAdvertiser != null) {                mBluetoothLeAdvertiser.startAdvertising(settings, data,                        mAdvertiseCallback);            }        }    }


下面等图是在三星盖乐世作为gattserver,苹果手机作为gattclient的测试结果,勉强能用。


0 0
原创粉丝点击