上一篇中了解了特性和属性,同时显示设备蓝牙服务下的特性和属性,本文中就需要来使用这些特性和属性来完成一些功能。
上一篇完成了特性,这一篇中我们增加描述符的处理,以及一些简单的优化。
这样看起来主页面在没有设备信息的时候不会显得单调,那么还有一个小细节就是,当设备的蓝牙服务和特性不属于SIG定义的,是厂商自定义时,我们最好就显示完整的UUID,为了方便使用,在BleUtils
类中增加如下代码:
public static final String APP_NAME = "GoodBle";
public static final String UNKNOWN_DEVICE = "Unknown device";
public static final String UNKNOWN_SERVICE = "Unknown Service";
public static final String UNKNOWN_CHARACTERISTICS = "Unknown Characteristics";
public static final String UNKNOWN_DESCRIPTOR = "Unknown Descriptor";
public static final String BROADCAST = "Broadcast";
public static final String READ = "Read";
public static final String WRITE_NO_RESPONSE = "Write No Response";
public static final String WRITE = "Write";
public static final String NOTIFY = "Notify";
public static final String INDICATE = "Indicate";
public static final String AUTHENTICATED_SIGNED_WRITES = "Authenticated Signed Writes";
public static final String EXTENDED_PROPERTIES = "Extended Properties";
这里定义了一些常量,包括未知服务、未知特性,和一些其他的属性,这样做在修改的时候修改一个常量就可以了。下面我们分别修改一下BleUtils中的getServiceName()
和getCharacteristicsName()
方法的else的值为UNKNOWN_SERVICE
和UNKNOWN_CHARACTERISTICS
,剩下的就可以在服务适配器和特性适配器中去修改了,首先是服务适配器,修改
@Override
public Component getComponent(int position, Component component, ComponentContainer componentContainer) {
...
String serviceName = BleUtils.getServiceName(service.getUuid());
holder.txServiceName.setText(serviceName);
holder.txUuid.setText(serviceName.equals(BleUtils.UNKNOWN_SERVICE) ? service.getUuid().toString() : BleUtils.getShortUUID(service.getUuid()));
return cpt;
}
在设置uuid的时候根据服务的名称进行判断,如果是标准的SIG服务则使用短UUID,不是则使用完整的UUID。默认是小写的,你也可以改成大写。
那么同样特性适配器也改一下:
@Override
public Component getComponent(int position, Component component, ComponentContainer componentContainer) {
...
String characteristicsName = BleUtils.getCharacteristicsName(characteristic.getUuid());
holder.txCharacterName.setText(characteristicsName);
holder.txUuid.setText(BleUtils.getShortUUID(characteristic.getUuid()));
holder.txUuid.setText(characteristicsName.equals(BleUtils.UNKNOWN_CHARACTERISTICS) ? characteristic.getUuid().toString() : BleUtils.getShortUUID(characteristic.getUuid()));
return cpt;
}
再运行一下,对于未知设备服务和特性的UUID就会显示完整的值。
在上一篇中提到了特性和属性,特性有那些功能是属性决定的,那么描述又是做什么的呢?
在蓝牙低功耗(BLE)中,Descriptor(描述符)是用于提供有关特征值的额外信息的数据结构。Descriptor 提供了特定特征的更详细描述和配置选项。Descriptor 是特征(Characteristics)的子项,用于描述特征的特定属性或行为。每个特征可以有一个或多个 Descriptor。
以下是一些常见的 BLE Descriptor 类型及其含义:
声明 Descriptor:这个 Descriptor 用于描述特征的声明信息,包括特征的唯一标识符、权限、值的格式和其他标志。它提供了特征的基本信息供其他设备了解。
用户描述(User Description)Descriptor:用于提供特征的人类可读描述信息。这个描述可以是特征的名称、标签或其他有关特征的说明性文字。
配置 Descriptor:用于描述特征的配置选项。这个 Descriptor 可以包含特征的可选设置,例如采样率、测量单位或阈值等。
通知 Descriptor:用于配置特征是否支持通知功能。这个 Descriptor 可以用于使设备可以接收特征值变化的通知。
线性区间 Descriptor:用于描述特征值的线性关系,例如数值范围和步长等。
客户端配置 Descriptor:用于允许远程设备(例如中心设备)订阅特征值的变化通知,这个很重要。
这些只是一些常见的 BLE Descriptor 类型和其含义的示例,实际上可以根据应用需求定义自定义的 Descriptor。
Descriptor 提供了对特征更详细的描述和配置,它们可以通过蓝牙协议进行传输和访问。在 BLE 应用中,Descriptor 充当了配置和元数据信息的重要角色,帮助设备之间准确地交换和理解数据。
那么现在你已经了解了描述符的作用了,而我们目前的特性下还没有描述符的,注意不是每一个特性都有描述符,下面我们就来把描述符写出来了。首先我们在item_characteristic.xml
中增加一个描述的列表控件,代码如下所示:
<DependentLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_content"
ohos:width="match_parent"
ohos:background_element="#FFFFFF"
ohos:bottom_margin="2vp"
ohos:bottom_padding="8vp"
ohos:end_padding="16vp"
ohos:start_padding="16vp"
ohos:top_padding="8vp">
<Text
ohos:id="$+id:tx_character_name"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$color:black"
ohos:text="服务"
ohos:text_size="16fp"/>
<Text
ohos:id="$+id:tx_uuid_title"
ohos:height="match_content"
ohos:width="match_content"
ohos:below="$id:tx_character_name"
ohos:text="UUID:"
ohos:text_color="$color:gray"
ohos:text_size="16fp"
ohos:top_margin="2vp"/>
<Text
ohos:id="$+id:tx_uuid"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$color:black"
ohos:below="$id:tx_character_name"
ohos:end_of="$id:tx_uuid_title"
ohos:text="UUID"
ohos:text_size="16fp"
ohos:top_margin="2vp"/>
<Text
ohos:id="$+id:tx_property_title"
ohos:height="match_content"
ohos:width="match_content"
ohos:below="$id:tx_uuid_title"
ohos:text="Properties:"
ohos:text_color="$color:gray"
ohos:text_size="16fp"
ohos:top_margin="2vp"/>
<ListContainer
ohos:id="$+id:lc_property"
ohos:height="match_content"
ohos:width="match_parent"
ohos:align_bottom="$id:tx_property_title"
ohos:align_top="$id:tx_property_title"
ohos:end_of="$id:tx_property_title"
ohos:orientation="horizontal"/>
<DirectionalLayout
ohos:id="$+id:lay_descriptors"
ohos:height="match_content"
ohos:width="match_parent"
ohos:below="$id:tx_property_title"
ohos:orientation="vertical">
<Text
ohos:height="match_content"
ohos:width="match_content"
ohos:text="Descriptors:"
ohos:text_color="#000000"
ohos:text_size="16fp"
ohos:top_margin="2vp"/>
<ListContainer
ohos:id="$+id:lc_descriptor"
ohos:height="match_content"
ohos:width="match_parent"/>
DirectionalLayout>
DependentLayout>
下面我们就可以正式去写描述符的提供者了。
首先在layout下增加一个item_descriptor.xml
,代码如下所示:
<DependentLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_content"
ohos:width="match_parent"
ohos:background_element="#FFFFFF"
ohos:bottom_margin="2vp"
ohos:bottom_padding="4vp"
ohos:top_padding="4vp">
<Text
ohos:id="$+id:tx_descriptor_name"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$color:black"
ohos:text="描述"
ohos:text_size="16fp"/>
<Text
ohos:id="$+id:tx_uuid_title"
ohos:height="match_content"
ohos:width="match_content"
ohos:below="$id:tx_descriptor_name"
ohos:text="UUID:"
ohos:text_color="$color:gray"
ohos:text_size="16fp"
ohos:top_margin="2vp"/>
<Text
ohos:id="$+id:tx_uuid"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$color:black"
ohos:below="$id:tx_descriptor_name"
ohos:end_of="$id:tx_uuid_title"
ohos:text="UUID"
ohos:text_size="16fp"
ohos:top_margin="2vp"/>
DependentLayout>
然后关于描述符的名称,我们可以在BleUtils中写一个函数,代码如下所示:
public static String getDescriptorName(UUID uuid) {
String targetUuid = getShortUUID(uuid);
switch (targetUuid) {
case "0x2900":
return "Characteristic Extended Properties";
case "0x2901":
return "Characteristic User Description";
case "0x2902":
return "Client Characteristic Configuration";
case "0x2903":
return "Server Characteristic Configuration";
case "0x2904":
return "Characteristic Presentation Format";
case "0x2905":
return "Characteristic Aggregate Format";
case "0x2906":
return "Valid Range";
case "0x2907":
return "External Report Reference";
case "0x2908":
return "Report Reference";
case "0x2909":
return "Number of Digitals";
case "0x290A":
return "Value Trigger Setting";
case "0x290B":
return "Environmental Sensing Configuration";
case "0x290C":
return "Environmental Sensing Measurement";
case "0x290D":
return "Environmental Sensing Trigger Setting";
case "0x290E":
return "Time Trigger Setting";
case "0x290F":
return "Complete BR-EDR Transport Block Data";
case "0x2910":
return "Observation Schedule";
case "0x2911":
return "Valid Range and Accuracy";
default:
return UNKNOWN_DESCRIPTOR;
}
}
下面我们写描述符适配器,在provider
包下新建一个DescriptorProvider
类,代码如下所示:
public class DescriptorProvider extends BaseItemProvider {
private final List<GattDescriptor> descriptorList;
private final AbilitySlice slice;
public DescriptorProvider(List<GattDescriptor> list, AbilitySlice slice) {
this.descriptorList = list;
this.slice = slice;
}
@Override
public int getCount() {
return descriptorList == null ? 0 : descriptorList.size();
}
@Override
public Object getItem(int position) {
if (descriptorList != null && position >= 0 && position < descriptorList.size()) {
return descriptorList.get(position);
}
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public Component getComponent(int position, Component component, ComponentContainer componentContainer) {
final Component cpt;
DescriptorHolder holder;
GattDescriptor descriptor = descriptorList.get(position);
if (component == null) {
cpt = LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_item_descriptor, null, false);
holder = new DescriptorHolder(cpt);
//将获取到的子组件信息绑定到列表项的实例中
cpt.setTag(holder);
} else {
cpt = component;
// 从缓存中获取到列表项实例后,直接使用绑定的子组件信息进行数据填充。
holder = (DescriptorHolder) cpt.getTag();
}
String descriptorName = BleUtils.getDescriptorName(descriptor.getUuid());
holder.txDescriptorName.setText(descriptorName);
holder.txUuid.setText(descriptorName.equals(BleUtils.UNKNOWN_DESCRIPTOR) ? descriptor.getUuid().toString() : BleUtils.getShortUUID(descriptor.getUuid()));
return cpt;
}
/**
* 用于保存列表项的子组件信息
*/
public static class DescriptorHolder {
Text txDescriptorName;
Text txUuid;
ListContainer lcProperty;
public DescriptorHolder(Component component) {
txDescriptorName = (Text) component.findComponentById(ResourceTable.Id_tx_descriptor_name);
txUuid = (Text) component.findComponentById(ResourceTable.Id_tx_uuid);
lcProperty = (ListContainer) component.findComponentById(ResourceTable.Id_lc_property);
}
}
}
可以看这里的代码同样对于自定义UUID展示完整数据,对于SIG的展示短UUID。
接下来就是在特性适配器中去加载显示描述符数据,修改CharacteristicProvider
中代码,所示代码:
public class CharacteristicProvider extends BaseItemProvider {
private final List<GattCharacteristic> characteristicList;
private final AbilitySlice slice;
private final OperateCallback operateCallback;
public CharacteristicProvider(List<GattCharacteristic> list, AbilitySlice slice, OperateCallback operateCallback) {
this.characteristicList = list;
this.slice = slice;
this.operateCallback = operateCallback;
}
@Override
public int getCount() {
return characteristicList == null ? 0 : characteristicList.size();
}
@Override
public Object getItem(int position) {
if (characteristicList != null && position >= 0 && position < characteristicList.size()) {
return characteristicList.get(position);
}
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public Component getComponent(int position, Component component, ComponentContainer componentContainer) {
final Component cpt;
CharacteristicHolder holder;
GattCharacteristic characteristic = characteristicList.get(position);
if (component == null) {
cpt = LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_item_characteristic, null, false);
holder = new CharacteristicHolder(cpt);
//将获取到的子组件信息绑定到列表项的实例中
cpt.setTag(holder);
} else {
cpt = component;
// 从缓存中获取到列表项实例后,直接使用绑定的子组件信息进行数据填充。
holder = (CharacteristicHolder) cpt.getTag();
}
String characteristicsName = BleUtils.getCharacteristicsName(characteristic.getUuid());
holder.txCharacterName.setText(characteristicsName);
holder.txUuid.setText(BleUtils.getShortUUID(characteristic.getUuid()));
holder.txUuid.setText(characteristicsName.equals(BleUtils.UNKNOWN_CHARACTERISTICS) ? characteristic.getUuid().toString() : BleUtils.getShortUUID(characteristic.getUuid()));
List<String> properties = BleUtils.getProperties(characteristic.getProperties());
//加载属性
holder.lcProperty.setItemProvider(new PropertyProvider(properties, slice));
//属性列表点击
holder.lcProperty.setItemClickedListener((listContainer, component1, propertyPosition, l) -> {
if (operateCallback != null) {
//属性操作回调
operateCallback.onPropertyOperate(characteristic, properties.get(propertyPosition));
}
});
//加载特性下的描述
if (characteristic.getDescriptors().size() > 0) {
holder.lcDescriptor.setItemProvider(new DescriptorProvider(characteristic.getDescriptors(), slice));
} else {
holder.layDescriptor.setVisibility(Component.HIDE);
}
return cpt;
}
/**
* 用于保存列表项的子组件信息
*/
public static class CharacteristicHolder {
Text txCharacterName;
Text txUuid;
ListContainer lcProperty;
DirectionalLayout layDescriptor;
ListContainer lcDescriptor;
public CharacteristicHolder(Component component) {
txCharacterName = (Text) component.findComponentById(ResourceTable.Id_tx_character_name);
txUuid = (Text) component.findComponentById(ResourceTable.Id_tx_uuid);
lcProperty = (ListContainer) component.findComponentById(ResourceTable.Id_lc_property);
layDescriptor = (DirectionalLayout) component.findComponentById(ResourceTable.Id_lay_descriptors);
lcDescriptor = (ListContainer) component.findComponentById(ResourceTable.Id_lc_descriptor);
}
}
}
请注意这一段代码:
//加载特性下的描述
if (characteristic.getDescriptors().size() > 0) {
holder.lcDescriptor.setItemProvider(new DescriptorProvider(characteristic.getDescriptors(), slice));
} else {
holder.layDescriptor.setVisibility(Component.HIDE);
}
这个判断和重要,因为不是每一个特性都有描述符,这个前面已经说过了,没有的我们就直接隐藏对应的描述符布局,否则就加载描述符数据,同时我们还需要修改一下服务UUID和特性UUID的Text控件的属性,因为UUID过长的话可能一行无法显示出来。
改动如下:
服务uuid:
<Text
ohos:id="$+id:tx_uuid"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$color:black"
ohos:below="$id:tx_service_name"
ohos:truncation_mode="ellipsis_at_middle"
ohos:end_margin="24vp"
ohos:text="UUID"
ohos:end_of="$id:tx_uuid_title"
ohos:align_end="$id:iv_state"
ohos:text_size="16fp"
ohos:top_margin="2vp"/>
特性uuid:
<Text
ohos:id="$+id:tx_uuid"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$color:black"
ohos:below="$id:tx_character_name"
ohos:end_of="$id:tx_uuid_title"
ohos:truncation_mode="ellipsis_at_middle"
ohos:text="UUID"
ohos:text_size="16fp"
ohos:top_margin="2vp"/>
下面运行看一下。
通过这个图就可以清晰的的看到特性下的描述符,本文就到这里了。
如果对你有所帮助的话,不妨 Star 或 Fork,山高水长,后会有期~
源码地址:HarmonyBle-Java