Harmony Ble蓝牙App(三)特性和属性

简介: Harmony Ble蓝牙App(三)特性和属性

前言


  在上一篇中我们完成了连接和发现服务两个动作,那么再发现服务之后要做什么呢?发现服务只是让你知道设备有什么服务,可以做什么事情。

45809330997d4d388f511adf2639fecb.gif


正文


  本篇要做的是显示服务下的特性,首先我们了解一下特性的基本知识。在蓝牙低功耗(BLE)中,特性(Characteristic)是蓝牙设备提供的一种数据单元,用于描述设备的某个属性或功能。特性包含了一系列的属性和值,可以用于读取、写入和通知数据。


BLE特性相关的关键概念和说明:

  1. UUID(Universally Unique Identifier):每个特性都会有一个唯一的UUID,用于标识该特性。
  2. 值(Value):特性包含一个值,可以是字节数组、字符串或其他数据类型。该值代表特性的当前状态或数据内容。
  3. 属性(Properties):特性具有一组属性,包括读、写、通知等。属性决定了可以对特性进行哪些操作。
  4. 读(Read):允许外部设备从特性中读取当前的值。
  5. 写(Write):允许外部设备向特性写入一个新的值。
  6. 通知(Notify):当特性的值发生变化时,可以通过通知方式将新的值发送给订阅该特性的外部设备。
  7. 描述符(Descriptor):特性可以附带一个或多个描述符,用于提供关于特性的额外信息或配置。


 使用BLE特性,可以实现各种功能和数据交互,例如传感器数据的读取、设备状态的监控、远程控制等。特性的读写和通知操作可以通过与蓝牙设备的交互来实现。需要注意的是,BLE特性的操作和功能是由设备的厂商定义的,并在设备的GATT(Generic Attribute Profile)配置文件中进行描述。


  首先理清一下思路,我们现在知道服务下面有特性,特性下面有一些属性值,其中属性(Properties)尤为重要,因为它决定了你的特性可以进行那些操作。用一个图来说明服务,特性,属性之间的关系。

fe1770ae961e4f78b9f50bfc2de560cd.png


一、获取属性列表


下面我们先获取最下面的属性,这是一个列表,属性值的处理有一些不同,首先我们在BleUtils中增加一个函数,代码如下所示:

  /**
     * 获取属性
     */
    public static List<String> getProperties(int property) {
        List<String> properties = new ArrayList<>();
        for (int i = 0; i < 8; i++) {
            switch (property & (1 << i)) {
                case 0x01:
                    properties.add("Broadcast");
                    break;
                case 0x02:
                    properties.add("Read");
                    break;
                case 0x04:
                    properties.add("Write No Response");
                    break;
                case 0x08:
                    properties.add("Write");
                    break;
                case 0x10:
                    properties.add("Notify");
                    break;
                case 0x20:
                    properties.add("Indicate");
                    break;
                case 0x40:
                    properties.add("Authenticated Signed Writes");
                    break;
                case 0x80:
                    properties.add("Extended Properties");
                    break;
            }
        }
        return properties;
    }


  这里是通过位运算进行计算属性的值,首先是循环遍历,先左移再按位与,得到最终的值,根据值得到属性描述,这些描述就是具体的功能操作。会返回一个属性列表,有了列表我们就可以写一个适配器了。


二、属性提供者


首先我们在layout下创建一个item_property.xml,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<Text
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:id="$+id:tx_property"
    ohos:height="match_content"
    ohos:width="match_content"
    ohos:end_margin="8vp"
    ohos:text="property"
    ohos:text_color="$color:blue"
    ohos:text_size="14fp"/>


  因为是String类型,所以我们就直接用一个Text显示即可,下面我们写提供者,在provider包下新建一个PropertyProvider类,代码如下所示:

public class PropertyProvider extends BaseItemProvider {
    private final List<String> propertyList;
    private final AbilitySlice slice;
    public PropertyProvider(List<String> list, AbilitySlice slice) {
        this.propertyList = list;
        this.slice = slice;
    }
    @Override
    public int getCount() {
        return propertyList == null ? 0 : propertyList.size();
    }
    @Override
    public Object getItem(int position) {
        if (propertyList != null && position >= 0 && position < propertyList.size()) {
            return propertyList.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;
        ServiceHolder holder;
        if (component == null) {
            cpt = LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_item_property, null, false);
            holder = new ServiceHolder(cpt);
            //将获取到的子组件信息绑定到列表项的实例中
            cpt.setTag(holder);
        } else {
            cpt = component;
            // 从缓存中获取到列表项实例后,直接使用绑定的子组件信息进行数据填充。
            holder = (ServiceHolder) cpt.getTag();
        }
        holder.txProperty.setText(propertyList.get(position));
        return cpt;
    }
    /**
     * 用于保存列表项的子组件信息
     */
    public static class ServiceHolder {
        Text txProperty;
        public ServiceHolder(Component component) {
            txProperty = (Text) component.findComponentById(ResourceTable.Id_tx_property);
        }
    }
}


这里进行了属性的点击监听,我们可以回调到特性适配器中去处理,下面我们要处理的就是特性了。


三、获取特性名称


  首先是特性名称,同样是根据UUID,同样是那个PDF文档,在BleUtils中增加一个getCharacteristicsName()函数,代码有点多,如下所示:

    /**
     * 获取特性名称
     * @param uuid UUID
     */
    public static String getCharacteristicsName(UUID uuid) {
        String targetUuid = getShortUUID(uuid);
        switch (targetUuid) {
            case "0x2A00":
                return "Device Name";
            case "0x2A01":
                return "Appearance";
            case "0x2A02":
                return "Peripheral Privacy Flag";
            case "0x2A03":
                return "Reconnection Address";
            case "0x2A04":
                return "Peripheral Preferred Connection Parameters";
            case "0x2A05":
                return "Service Changed";
            case "0x2A06":
                return "Alert Level";
            case "0x2A07":
                return "Tx Power Level";
            case "0x2A08":
                return "Date Time";
            case "0x2A09":
                return "Day of Week";
            case "0x2A0A":
                return "Day Date Time";
            case "0x2A0C":
                return "Exact Time 256";
            case "0x2A0D":
                return "DST Offset";
            case "0x2A0E":
                return "Time Zone";
            case "0x2A0F":
                return "Local Time Information";
            case "0x2A11":
                return "Time with DST";
            case "0x2A12":
                return "Time Accuracy";
            case "0x2A13":
                return "Time Source";
            case "0x2A14":
                return "Reference Time Information";
            case "0x2A16":
                return "Time Update Control Point";
            case "0x2A17":
                return "Time Update State";
            case "0x2A18":
                return "Glucose Measurement";
            case "0x2A19":
                return "Battery Level";
            case "0x2A1C":
                return "Temperature Measurement";
            case "0x2A1D":
                return "Temperature Type";
            case "0x2A1E":
                return "Intermediate Temperature";
            case "0x2A21":
                return "Measurement Interval";
            case "0x2A22":
                return "Boot Keyboard Input Report";
            case "0x2A23":
                return "System ID";
            case "0x2A24":
                return "Model Number String";
            case "0x2A25":
                return "Serial Number String";
            case "0x2A26":
                return "Firmware Revision String";
            case "0x2A27":
                return "Hardware Revision String";
            case "0x2A28":
                return "Software Revision String";
            case "0x2A29":
                return "Manufacturer Name String";
            case "0x2A2A":
                return "IEEE 11073-20601 Regulatory Certification Data List";
            case "0x2A2B":
                return "Current Time";
            case "0x2A2C":
                return "Magnetic Declination";
            case "0x2A31":
                return "Scan Refresh";
            case "0x2A32":
                return "Boot Keyboard Output Report";
            case "0x2A33":
                return "Boot Mouse Input Report";
            case "0x2A34":
                return "Glucose Measurement Context";
            case "0x2A35":
                return "Blood Pressure Measurement";
            case "0x2A36":
                return "Intermediate Cuff Pressure";
            case "0x2A37":
                return "Heart Rate Measurement";
            case "0x2A38":
                return "Body Sensor Location";
            case "0x2A39":
                return "Heart Rate Control Point";
            case "0x2A3F":
                return "Alert Status";
            case "0x2A40":
                return "Ringer Control Point";
            case "0x2A41":
                return "Ringer Setting";
            case "0x2A42":
                return "Alert Category ID Bit Mask";
            case "0x2A43":
                return "Alert Category ID";
            case "0x2A44":
                return "Alert Notification Control Point";
            case "0x2A45":
                return "Unread Alert Status";
            case "0x2A46":
                return "New Alert";
            case "0x2A47":
                return "Supported New Alert Category";
            case "0x2A48":
                return "Supported Unread Alert Category";
            case "0x2A49":
                return "Blood Pressure Feature";
            case "0x2A4A":
                return "HID Information";
            case "0x2A4B":
                return "Report Map";
            case "0x2A4C":
                return "HID Control Point";
            case "0x2A4D":
                return "Report";
            case "0x2A4E":
                return "Protocol Mode";
            case "0x2A4F":
                return "Scan Interval Window";
            case "0x2A50":
                return "PnP ID";
            case "0x2A51":
                return "Glucose Feature";
            case "0x2A52":
                return "Record Access Control Point";
            case "0x2A53":
                return "RSC Measurement";
            case "0x2A54":
                return "RSC Feature";
            case "0x2A55":
                return "SC Control Point";
            case "0x2A5A":
                return "Aggregate";
            case "0x2A5B":
                return "CSC Measurement";
            case "0x2A5C":
                return "CSC Feature";
            case "0x2A5D":
                return "Sensor Location";
            case "0x2A5E":
                return "PLX Spot-Check Measurement";
            case "0x2A5F":
                return "PLX Continuous Measurement";
            case "0x2A60":
                return "PLX Features";
            case "0x2A63":
                return "Cycling Power Measurement";
            case "0x2A64":
                return "Cycling Power Vector";
            case "0x2A65":
                return "Cycling Power Feature";
            case "0x2A66":
                return "Cycling Power Control Point";
            case "0x2A67":
                return "Location and Speed";
            case "0x2A68":
                return "Navigation";
            case "0x2A69":
                return "Position Quality";
            case "0x2A6A":
                return "LN Feature";
            case "0x2A6B":
                return "LN Control Point";
            case "0x2A6C":
                return "Elevation";
            case "0x2A6D":
                return "Pressure";
            case "0x2A6E":
                return "Temperature";
            case "0x2A6F":
                return "Humidity";
            case "0x2A70":
                return "True Wind Speed";
            case "0x2A71":
                return "True Wind Direction";
            case "0x2A72":
                return "Apparent Wind Speed";
            case "0x2A73":
                return "Apparent Wind Direction";
            case "0x2A74":
                return "Gust Factor";
            case "0x2A75":
                return "Pollen Concentration";
            case "0x2A76":
                return "UV Index";
            case "0x2A77":
                return "Irradiance";
            case "0x2A78":
                return "Rainfall";
            case "0x2A79":
                return "Wind Chill";
            case "0x2A7A":
                return "Heat Index";
            case "0x2A7B":
                return "Dew Point";
            case "0x2A7D":
                return "Descriptor Value Changed";
            case "0x2A7E":
                return "Aerobic Heart Rate Lower Limit";
            case "0x2A7F":
                return "Aerobic Threshold";
            case "0x2A80":
                return "Age";
            case "0x2A81":
                return "Anaerobic Heart Rate Lower Limit";
            case "0x2A82":
                return "Anaerobic Heart Rate Upper Limit";
            case "0x2A83":
                return "Anaerobic Threshold";
            case "0x2A84":
                return "Aerobic Heart Rate Upper Limit";
            case "0x2A85":
                return "Date of Birth";
            case "0x2A86":
                return "Date of Threshold Assessment";
            case "0x2A87":
                return "Email Address";
            case "0x2A88":
                return "Fat Burn Heart Rate Lower Limit";
            case "0x2A89":
                return "Fat Burn Heart Rate Upper Limit";
            case "0x2A8A":
                return "First Name";
            case "0x2A8B":
                return "Five Zone Heart Rate Limits";
            case "0x2A8C":
                return "Gender";
            case "0x2A8D":
                return "Heart Rate Max";
            case "0x2A8E":
                return "Height";
            case "0x2A8F":
                return "Hip Circumference";
            case "0x2A90":
                return "Last Name";
            case "0x2A91":
                return "Maximum Recommended Heart Rate";
            case "0x2A92":
                return "Resting Heart Rate";
            case "0x2A93":
                return "Sport Type for Aerobic and Anaerobic Thresholds";
            case "0x2A94":
                return "Three Zone Heart Rate Limits";
            case "0x2A95":
                return "Two Zone Heart Rate Limits";
            case "0x2A96":
                return "VO2 Max";
            case "0x2A97":
                return "Waist Circumference";
            case "0x2A98":
                return "Weight";
            case "0x2A99":
                return "Database Change Increment";
            case "0x2A9A":
                return "User Index";
            case "0x2A9B":
                return "Body Composition Feature";
            case "0x2A9C":
                return "Body Composition Measurement";
            case "0x2A9D":
                return "Weight Measurement";
            case "0x2A9E":
                return "Weight Scale Feature";
            case "0x2A9F":
                return "User Control Point";
            case "0x2AA0":
                return "Magnetic Flux Density - 2D";
            case "0x2AA1":
                return "Magnetic Flux Density - 3D";
            case "0x2AA2":
                return "Language";
            case "0x2AA3":
                return "Barometric Pressure Trend";
            case "0x2AA4":
                return "Bond Management Control Point";
            case "0x2AA5":
                return "Bond Management Feature";
            case "0x2AA6":
                return "Central Address Resolution";
            case "0x2AA7":
                return "CGM Measurement";
            case "0x2AA8":
                return "CGM Feature";
            case "0x2AA9":
                return "CGM Status";
            case "0x2AAA":
                return "CGM Session Start Time";
            case "0x2AAB":
                return "CGM Session Run Time";
            case "0x2AAC":
                return "CGM Specific Ops Control Point";
            case "0x2AAD":
                return "Indoor Positioning Configuration";
            case "0x2AAE":
                return "Latitude";
            case "0x2AAF":
                return "Longitude";
            case "0x2AB0":
                return "Local North Coordinate";
            case "0x2AB1":
                return "Local East Coordinate";
            case "0x2AB2":
                return "Floor Number";
            case "0x2AB3":
                return "Altitude";
            case "0x2AB4":
                return "Uncertainty";
            case "0x2AB5":
                return "Location Name";
            case "0x2AB6":
                return "URI";
            case "0x2AB7":
                return "HTTP Headers";
            case "0x2AB8":
                return "HTTP Status Code";
            case "0x2AB9":
                return "HTTP Entity Body";
            case "0x2ABA":
                return "HTTP Control Point";
            case "0x2ABB":
                return "HTTPS Security";
            case "0x2ABC":
                return "TDS Control Point";
            case "0x2ABD":
                return "OTS Feature";
            case "0x2ABE":
                return "Object Name";
            case "0x2ABF":
                return "Object Type";
            case "0x2AC0":
                return "Object Size";
            case "0x2AC1":
                return "Object First -Created";
            case "0x2AC2":
                return "Object Last - Modified";
            case "0x2AC3":
                return "Object ID";
            case "0x2AC4":
                return "Object Properties";
            case "0x2AC5":
                return "Object Action Control Point";
            case "0x2AC6":
                return "Object List Control Point";
            case "0x2AC7":
                return "Object List Filter";
            case "0x2AC8":
                return "Object Changed";
            case "0x2AC9":
                return "Resolvable Private Address Only";
            case "0x2ACC":
                return "Fitness Machine Feature";
            case "0x2ACD":
                return "Treadmill Data";
            case "0x2ACE":
                return "Cross Trainer Data";
            case "0x2ACF":
                return "Step Climber Data";
            case "0x2AD0":
                return "Stair Climber Data";
            case "0x2AD1":
                return "Rower Data";
            case "0x2AD2":
                return "Indoor Bike Data";
            case "0x2AD3":
                return "Training Status";
            case "0x2AD4":
                return "Supported Speed Range";
            case "0x2AD5":
                return "Supported Inclination Range";
            case "0x2AD6":
                return "Supported Resistance Level Range";
            case "0x2AD7":
                return "Supported Heart Rate Range";
            case "0x2AD8":
                return "Supported Power Range";
            case "0x2AD9":
                return "Fitness Machine Control Point";
            case "0x2ADA":
                return "Fitness Machine Status";
            case "0x2ADB":
                return "Mesh Provisioning Data In";
            case "0x2ADC":
                return "Mesh Provisioning Data Out";
            case "0x2ADD":
                return "Mesh Proxy Data In";
            case "0x2ADE":
                return "Mesh Proxy Data Out";
            case "0x2AE0":
                return "Average Current";
            case "0x2AE1":
                return "Average Voltage";
            case "0x2AE2":
                return "Boolean";
            case "0x2AE3":
                return "Chromatic Distance from Planckian";
            case "0x2AE4":
                return "Chromaticity Coordinates";
            case "0x2AE5":
                return "Chromaticity in CCT and Duv Values";
            case "0x2AE6":
                return "Chromaticity Tolerance";
            case "0x2AE7":
                return "CIE 13.3 - 1995 Color Rendering Index";
            case "0x2AE8":
                return "Coefficient";
            case "0x2AE9":
                return "Correlated Color Temperature";
            case "0x2AEA":
                return "Count 16";
            case "0x2AEB":
                return "Count 24";
            case "0x2AEC":
                return "Country Code";
            case "0x2AED":
                return "Date UTC";
            case "0x2AEE":
                return "Electric Current";
            case "0x2AEF":
                return "Electric Current Range";
            case "0x2AF0":
                return "Electric Current Specification";
            case "0x2AF1":
                return "Electric Current Statistics";
            case "0x2AF2":
                return "Energy";
            case "0x2AF3":
                return "Energy in a Period of Day";
            case "0x2AF4":
                return "Event Statistics";
            case "0x2AF5":
                return "Fixed String 16";
            case "0x2AF6":
                return "Fixed String 24";
            case "0x2AF7":
                return "Fixed String 36";
            case "0x2AF8":
                return "Fixed String 8";
            case "0x2AF9":
                return "Generic Level";
            case "0x2AFA":
                return "Global Trade Item Number";
            case "0x2AFB":
                return "Illuminance";
            case "0x2AFC":
                return "Luminous Efficacy";
            case "0x2AFD":
                return "Luminous Energy";
            case "0x2AFE":
                return "Luminous Exposure";
            case "0x2AFF":
                return "Luminous Flux";
            case "0x2B00":
                return "Luminous Flux Range";
            case "0x2B01":
                return "Luminous Intensity";
            case "0x2B02":
                return "Mass Flow";
            case "0x2B03":
                return "Perceived Lightness";
            case "0x2B04":
                return "Percentage 8";
            case "0x2B05":
                return "Power";
            case "0x2B06":
                return "Power Specification";
            case "0x2B07":
                return "Relative Runtime in a Current Range";
            case "0x2B08":
                return "Relative Runtime in a Generic Level Range";
            case "0x2B09":
                return "Relative Value in a Voltage Range";
            case "0x2B0A":
                return "Relative Value in an Illuminance Range";
            case "0x2B0B":
                return "Relative Value in a Period of Day";
            case "0x2B0C":
                return "Relative Value in a Temperature Range";
            case "0x2B0D":
                return "Temperature 8";
            case "0x2B0E":
                return "Temperature 8 in a Period of Day";
            case "0x2B0F":
                return "Temperature 8 Statistics";
            case "0x2B10":
                return "Temperature Range";
            case "0x2B11":
                return "Temperature Statistics";
            case "0x2B12":
                return "Time Decihour 8";
            case "0x2B13":
                return "Time Exponential 8";
            case "0x2B14":
                return "Time Hour 24";
            case "0x2B15":
                return "Time Millisecond 24";
            case "0x2B16":
                return "Time Second 16";
            case "0x2B17":
                return "Time Second 8";
            case "0x2B18":
                return "Voltage";
            case "0x2B19":
                return "Voltage Specification";
            case "0x2B1A":
                return "Voltage Statistics";
            case "0x2B1B":
                return "Volume Flow";
            case "0x2B1C":
                return "Chromaticity Coordinate";
            case "0x2B1D":
                return "RC Feature";
            case "0x2B1E":
                return "RC Settings";
            case "0x2B1F":
                return "Reconnection Configuration Control Point";
            case "0x2B20":
                return "IDD Status Changed";
            case "0x2B21":
                return "IDD Status";
            case "0x2B22":
                return "IDD Annunciation Status";
            case "0x2B23":
                return "IDD Features";
            case "0x2B24":
                return "IDD Status Reader Control Point";
            case "0x2B25":
                return "IDD Command Control Point";
            case "0x2B26":
                return "IDD Command Data";
            case "0x2B27":
                return "IDD Record Access Control Point";
            case "0x2B28":
                return "IDD History Data";
            case "0x2B29":
                return "Client Supported Features";
            case "0x2B2A":
                return "Database Hash";
            case "0x2B2B":
                return "BSS Control Point";
            case "0x2B2C":
                return "BSS Response";
            case "0x2B2D":
                return "Emergency ID";
            case "0x2B2E":
                return "Emergency Text";
            case "0x2B2F":
                return "ACS Status";
            case "0x2B30":
                return "ACS Data In";
            case "0x2B31":
                return "ACS Data Out Notify";
            case "0x2B32":
                return "ACS Data Out Indicate";
            case "0x2B33":
                return "ACS Control Point";
            case "0x2B34":
                return "Enhanced Blood Pressure Measurement";
            case "0x2B35":
                return "Enhanced Intermediate Cuff Pressure";
            case "0x2B36":
                return "Blood Pressure Record";
            case "0x2B37":
                return "Registered User";
            case "0x2B38":
                return "BR - EDR Handover Data";
            case "0x2B39":
                return "Bluetooth SIG Data";
            case "0x2B3A":
                return "Server Supported Features";
            case "0x2B3B":
                return "Physical Activity Monitor Features";
            case "0x2B3C":
                return "General Activity Instantaneous Data";
            case "0x2B3D":
                return "General Activity Summary Data";
            case "0x2B3E":
                return "CardioRespiratory Activity Instantaneous Data";
            case "0x2B3F":
                return "CardioRespiratory Activity Summary Data";
            case "0x2B40":
                return "Step Counter Activity Summary Data";
            case "0x2B41":
                return "Sleep Activity Instantaneous Data";
            case "0x2B42":
                return "Sleep Activity Summary Data";
            case "0x2B43":
                return "Physical Activity Monitor Control Point";
            case "0x2B44":
                return "Activity Current Session";
            case "0x2B45":
                return "Physical Activity Session Descriptor";
            case "0x2B46":
                return "Preferred Units";
            case "0x2B47":
                return "High Resolution Height";
            case "0x2B48":
                return "Middle Name";
            case "0x2B49":
                return "Stride Length";
            case "0x2B4A":
                return "Handedness";
            case "0x2B4B":
                return "Device Wearing Position";
            case "0x2B4C":
                return "Four Zone Heart Rate Limits";
            case "0x2B4D":
                return "High Intensity Exercise Threshold";
            case "0x2B4E":
                return "Activity Goal";
            case "0x2B4F":
                return "Sedentary Interval Notification";
            case "0x2B50":
                return "Caloric Intake";
            case "0x2B51":
                return "TMAP Role";
            case "0x2B77":
                return "Audio Input State";
            case "0x2B78":
                return "Gain Settings Attribute";
            case "0x2B79":
                return "Audio Input Type";
            case "0x2B7A":
                return "Audio Input Status";
            case "0x2B7B":
                return "Audio Input Control Point";
            case "0x2B7C":
                return "Audio Input Description";
            case "0x2B7D":
                return "Volume State";
            case "0x2B7E":
                return "Volume Control Point";
            case "0x2B7F":
                return "Volume Flags";
            case "0x2B80":
                return "Volume Offset State";
            case "0x2B81":
                return "Audio Location";
            case "0x2B82":
                return "Volume Offset Control Point";
            case "0x2B83":
                return "Audio Output Description";
            case "0x2B84":
                return "Set Identity Resolving Key";
            case "0x2B85":
                return "Coordinated Set Size";
            case "0x2B86":
                return "Set Member Lock";
            case "0x2B87":
                return "Set Member Rank";
            case "0x2B88":
                return "Encrypted Data Key Material";
            case "0x2B89":
                return "Apparent Energy 32";
            case "0x2B8A":
                return "Apparent Power";
            case "0x2B8B":
                return "Live Health Observations";
            case "0x2B8C":
                return "CO \\{} text-subscript { 2 } Concentration";
            case "0x2B8D":
                return "Cosine of the Angle";
            case "0x2B8E":
                return "Device Time Feature";
            case "0x2B8F":
                return "Device Time Parameters";
            case "0x2B90":
                return "Device Time";
            case "0x2B91":
                return "Device Time Control Point";
            case "0x2B92":
                return "Time Change Log Data";
            case "0x2B93":
                return "Media Player Name";
            case "0x2B94":
                return "Media Player Icon Object ID";
            case "0x2B95":
                return "Media Player Icon URL";
            case "0x2B96":
                return "Track Changed";
            case "0x2B97":
                return "Track Title";
            case "0x2B98":
                return "Track Duration";
            case "0x2B99":
                return "Track Position";
            case "0x2B9A":
                return "Playback Speed";
            case "0x2B9B":
                return "Seeking Speed";
            case "0x2B9C":
                return "Current Track Segments Object ID";
            case "0x2B9D":
                return "Current Track Object ID";
            case "0x2B9E":
                return "Next Track Object ID";
            case "0x2B9F":
                return "Parent Group Object ID";
            case "0x2BA0":
                return "Current Group Object ID";
            case "0x2BA1":
                return "Playing Order";
            case "0x2BA2":
                return "Playing Orders Supported";
            case "0x2BA3":
                return "Media State";
            case "0x2BA4":
                return "Media Control Point";
            case "0x2BA5":
                return "Media Control Point Opcodes Supported";
            case "0x2BA6":
                return "Search Results Object ID";
            case "0x2BA7":
                return "Search Control Point";
            case "0x2BA8":
                return "Energy 32";
            case "0x2BA9":
                return "Media Player Icon Object Type";
            case "0x2BAA":
                return "Track Segments Object Type";
            case "0x2BAB":
                return "Track Object Type";
            case "0x2BAC":
                return "Group Object Type";
            case "0x2BAD":
                return "Constant Tone Extension Enable";
            case "0x2BAE":
                return "Advertising Constant Tone Extension Minimum Length";
            case "0x2BAF":
                return "Advertising Constant Tone Extension Minimum Transmit Count";
            case "0x2BB0":
                return "Advertising Constant Tone Extension Transmit Duration";
            case "0x2BB1":
                return "Advertising Constant Tone Extension Interval";
            case "0x2BB2":
                return "Advertising Constant Tone Extension PHY";
            case "0x2BB3":
                return "Bearer Provider Name";
            case "0x2BB4":
                return "Bearer UCI";
            case "0x2BB5":
                return "Bearer Technology";
            case "0x2BB6":
                return "Bearer URI Schemes Supported List";
            case "0x2BB7":
                return "Bearer Signal Strength";
            case "0x2BB8":
                return "Bearer Signal Strength Reporting Interval";
            case "0x2BB9":
                return "Bearer List Current Calls";
            case "0x2BBA":
                return "Content Control ID";
            case "0x2BBB":
                return "Status Flags";
            case "0x2BBC":
                return "Incoming Call Target Bearer URI";
            case "0x2BBD":
                return "Call State";
            case "0x2BBE":
                return "Call Control Point";
            case "0x2BBF":
                return "Call Control Point Optional Opcodes";
            case "0x2BC0":
                return "Termination Reason";
            case "0x2BC1":
                return "Incoming Call";
            case "0x2BC2":
                return "Call Friendly Name";
            case "0x2BC3":
                return "Mute";
            case "0x2BC4":
                return "Sink ASE";
            case "0x2BC5":
                return "Source ASE";
            case "0x2BC6":
                return "ASE Control Point";
            case "0x2BC7":
                return "Broadcast Audio Scan Control Point";
            case "0x2BC8":
                return "Broadcast Receive State";
            case "0x2BC9":
                return "Sink PAC";
            case "0x2BCA":
                return "Sink Audio Locations";
            case "0x2BCB":
                return "Source PAC";
            case "0x2BCC":
                return "Source Audio Locations";
            case "0x2BCD":
                return "Available Audio Contexts";
            case "0x2BCE":
                return "Supported Audio Contexts";
            case "0x2BCF":
                return "Ammonia Concentration";
            case "0x2BD0":
                return "Carbon Monoxide Concentration";
            case "0x2BD1":
                return "Methane Concentration";
            case "0x2BD2":
                return "Nitrogen Dioxide Concentration";
            case "0x2BD3":
                return "Non -Methane Volatile Organic Compounds Concentration";
            case "0x2BD4":
                return "Ozone Concentration";
            case "0x2BD5":
                return "Particulate Matter - PM1 Concentration";
            case "0x2BD6":
                return "Particulate Matter - PM2.5 Concentration";
            case "0x2BD7":
                return "Particulate Matter - PM10 Concentration";
            case "0x2BD8":
                return "Sulfur Dioxide Concentration";
            case "0x2BD9":
                return "Sulfur Hexafluoride Concentration";
            case "0x2BDA":
                return "Hearing Aid Features";
            case "0x2BDB":
                return "Hearing Aid Preset Control Point";
            case "0x2BDC":
                return "Active Preset Index";
            case "0x2BDD":
                return "Stored Health Observations";
            case "0x2BDE":
                return "Fixed String 64";
            case "0x2BDF":
                return "High Temperature";
            case "0x2BE0":
                return "High Voltage";
            case "0x2BE1":
                return "Light Distribution";
            case "0x2BE2":
                return "Light Output";
            case "0x2BE3":
                return "Light Source Type";
            case "0x2BE4":
                return "Noise";
            case "0x2BE5":
                return "Relative Runtime in a Correlated Color Temperature Range";
            case "0x2BE6":
                return "Time Second 32";
            case "0x2BE7":
                return "VOC Concentration";
            case "0x2BE8":
                return "Voltage Frequency";
            case "0x2BE9":
                return "Battery Critical Status";
            case "0x2BEA":
                return "Battery Health Status";
            case "0x2BEB":
                return "Battery Health Information";
            case "0x2BEC":
                return "Battery Information";
            case "0x2BED":
                return "Battery Level Status";
            case "0x2BEE":
                return "Battery Time Status";
            case "0x2BEF":
                return "Estimated Service Date";
            case "0x2BF0":
                return "Battery Energy Status";
            case "0x2BF1":
                return "Observation Schedule Changed";
            case "0x2BF2":
                return "Current Elapsed Time";
            case "0x2BF3":
                return "Health Sensor Features";
            case "0x2BF4":
                return "GHS Control Point";
            case "0x2BF5":
                return "LE GATT Security Levels";
            case "0x2BF6":
                return "ESL Address";
            case "0x2BF7":
                return "AP Sync Key Material";
            case "0x2BF8":
                return "ESL Response Key Material";
            case "0x2BF9":
                return "ESL Current Absolute Time";
            case "0x2BFA":
                return "ESL Display Information";
            case "0x2BFB":
                return "ESL Image Information";
            case "0x2BFC":
                return "ESL Sensor Information";
            case "0x2BFD":
                return "ESL LED Information";
            case "0x2BFE":
                return "ESL Control Point";
            case "0x2BFF":
                return "UDI for Medical Devices";
            default:
                return "Unknown Characteristics";
        }
    }


  同修改一下原有的getServiceUUID()getShortUUID(),只改名字而已,之前命名有点不太严谨,记得改一下使用的地方,如下图所示,在ServiceProvider

4b895d7ae09d434dbfcdb320b4d1664b.png


四、特性提供者


首先我们在layout下创建一个item_characteristic.xml,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<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:orientation="horizontal"
        ohos:align_bottom="$id:tx_property_title"
        ohos:align_top="$id:tx_property_title"
        ohos:end_of="$id:tx_property_title"/>
</DependentLayout>


  这个布局中唯一说明的一点就是ListContainerorientation="horizontal"属性,这表示列表是横向,默认是纵向的。这里显示特性的名称和UUIID,同时加载属性列表,然后写适配器,因为需要操作属性的缘故,这些写一个接口,在provider包下新建一个OperateCallback接口,代码如下所示:

public interface OperateCallback {
    /**
     * 属性操作
     */
    void onPropertyOperate(GattCharacteristic characteristic, String operateName);
}


  通过这个接口可以知道当前操作的是那个特性和属性名称。下面我们写适配器,在provider包下新建一个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;
        ServiceHolder holder;
        GattCharacteristic characteristic = characteristicList.get(position);
        if (component == null) {
            cpt = LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_item_characteristic, null, false);
            holder = new ServiceHolder(cpt);
            //将获取到的子组件信息绑定到列表项的实例中
            cpt.setTag(holder);
        } else {
            cpt = component;
            // 从缓存中获取到列表项实例后,直接使用绑定的子组件信息进行数据填充。
            holder = (ServiceHolder) cpt.getTag();
        }
        holder.txCharacterName.setText(BleUtils.getCharacteristicsName(characteristic.getUuid()));
        holder.txUuid.setText(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));
            }
        });
        return cpt;
    }
    /**
     * 用于保存列表项的子组件信息
     */
    public static class ServiceHolder {
        Text txCharacterName;
        Text txUuid;
        ListContainer lcProperty;
        public ServiceHolder(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);
        }
    }
}


在这里我们就可以处理特性的名称和UUID显示,同时加载属性提供者,显示出来。


五、加载特性


  因为特性是在服务下的,所以我们可以在服务适配器中加载特性适配器。首先我们修改一下item_service.xml,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    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:orientation="vertical">
    <DependentLayout
        xmlns:ohos="http://schemas.huawei.com/res/ohos"
        ohos:id="$+id:item_service"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:background_element="#FFFFFF"
        ohos:bottom_padding="8vp"
        ohos:end_padding="16vp"
        ohos:start_padding="16vp"
        ohos:top_padding="8vp">
        <Text
            ohos:id="$+id:tx_service_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_service_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_service_name"
            ohos:end_of="$id:tx_uuid_title"
            ohos:text="UUID"
            ohos:text_size="16fp"
            ohos:top_margin="2vp"/>
        <Text
            ohos:id="$+id:tx_service_info"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:below="$id:tx_uuid_title"
            ohos:text="PRIMARY SERVICE"
            ohos:text_color="$color:gray"
            ohos:text_size="16fp"
            ohos:top_margin="2vp"/>
        <Image
            ohos:id="$+id:iv_state"
            ohos:height="24vp"
            ohos:width="24vp"
            ohos:align_parent_end="true"
            ohos:end_margin="16vp"
            ohos:background_element="$graphic:ic_right_24"
            ohos:vertical_center="true"/>
    </DependentLayout>
    <ListContainer
        ohos:id="$+id:lc_characteristics"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:start_padding="16vp"
        ohos:visibility="hide"/>
</DirectionalLayout>


  整体上变化不大,只是加了一个ListContainer,同时增加了一个Image,用于显示当前的服务下的特性列表是否显示,可以通过当前的服务item的方式控制是否显示特性列表,这里用到两个图标,在graphic下创建ic_right_24.xml,代码如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<vector
    xmlns:ohos="http://schemas.android.com/apk/res/android"
    ohos:height="24vp"
    ohos:tint="#000000"
    ohos:viewportHeight="24"
    ohos:viewportWidth="24"
    ohos:width="24vp">
    <path
        ohos:fillColor="#000000"
        ohos:pathData="M10,17l5,-5 -5,-5v10z"/>
</vector>


还有一个ic_down_24.xml

<?xml version="1.0" encoding="utf-8"?>
<vector
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="24vp"
    ohos:width="24vp"
    ohos:tint="#000000"
    ohos:viewportHeight="24"
    ohos:viewportWidth="24">
    <path
        ohos:fillColor="#000000"
        ohos:pathData="M7,10l5,5 5,-5z"/>
</vector>


下面修改一下ServiceAdapter,代码如下所示:

public class ServiceProvider extends BaseItemProvider {
    private final List<GattService> serviceList;
    private final AbilitySlice slice;
    private OperateCallback operateCallback;
    public void setOperateCallback(OperateCallback operateCallback) {
        this.operateCallback = operateCallback;
    }
    public ServiceProvider(List<GattService> list, AbilitySlice slice) {
        this.serviceList = list;
        this.slice = slice;
    }
    @Override
    public int getCount() {
        return serviceList == null ? 0 : serviceList.size();
    }
    @Override
    public Object getItem(int position) {
        if (serviceList != null && position >= 0 && position < serviceList.size()) {
            return serviceList.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;
        ServiceHolder holder;
        GattService service = serviceList.get(position);
        if (component == null) {
            cpt = LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_item_service, null, false);
            holder = new ServiceHolder(cpt);
            //将获取到的子组件信息绑定到列表项的实例中
            cpt.setTag(holder);
        } else {
            cpt = component;
            // 从缓存中获取到列表项实例后,直接使用绑定的子组件信息进行数据填充。
            holder = (ServiceHolder) cpt.getTag();
        }
        holder.itemService.setClickedListener(component1 -> {
            boolean isShow = holder.lcCharacteristics.getVisibility() == Component.VISIBLE;
            //显示特性列表
            holder.lcCharacteristics.setVisibility(isShow ? Component.HIDE : Component.VISIBLE);
            //更换图标
            VectorElement vectorElement = new VectorElement(slice.getContext(), isShow ? ResourceTable.Graphic_ic_right_24 : ResourceTable.Graphic_ic_down_24);
            holder.ivState.setBackground(vectorElement);
            //刷新Item高度,这个很重要,不加会造成内容覆盖。
            notifyDataSetItemChanged(position);
        });
        //加载特性 设置属性回调
        holder.lcCharacteristics.setItemProvider(new CharacteristicProvider(service.getCharacteristics(), slice, operateCallback));
        holder.txServiceName.setText(BleUtils.getServiceName(service.getUuid()));
        holder.txUuid.setText(BleUtils.getShortUUID(service.getUuid()));
        return cpt;
    }
    /**
     * 用于保存列表项的子组件信息
     */
    public static class ServiceHolder {
        DependentLayout itemService;
        Text txServiceName;
        Text txUuid;
        Image ivState;
        ListContainer lcCharacteristics;
        public ServiceHolder(Component component) {
            itemService = (DependentLayout) component.findComponentById(ResourceTable.Id_item_service);
            txServiceName = (Text) component.findComponentById(ResourceTable.Id_tx_service_name);
            txUuid = (Text) component.findComponentById(ResourceTable.Id_tx_uuid);
            ivState = (Image) component.findComponentById(ResourceTable.Id_iv_state);
            lcCharacteristics = (ListContainer) component.findComponentById(ResourceTable.Id_lc_characteristics);
        }
    }
}


  和之前的区别就在于构造的时候增加了一个回调,并且在getComponent()方法中就处理了服务Item的点击事件,而不是像之前一样回调到页面中,在服务Item的点击事件中判断是否显示特性列表同时修改图标资源。最后再将接口回调到页面中。

  另外还需要注意一点,那就是Image我在xml中是设置的背景,而不是图片资源,因为在java代码中无法设置矢量图的资源,所以我就改成使用背景资源,需要注意的是背景资源要设置Image具体的大小,否则不会显示,在Java代码中通过VectorElement来加载矢量图资源,然后设置背景。同时notifyDataSetItemChanged(position)这样代码也很重要,因为我们的服务Item实际上有两部分内容,服务本身内容和特性列表内容,默认情况下显示服务内容,当点击服务Item时显示特性列表内容,此时如果你按照Android的习惯去搞,你就会发现,Item展开的内容会被其他Item遮挡,所以我们需要加上这样一行代码,让Item进行刷新,这样就不会被其他Item遮挡了。

  最后我们在MainAbilitySlice中添加serviceProvider.setOperateCallback(this);,然后再实现接口

a2f14279bca749f5b0e5ca7389adba1d.png

重写里面的onPropertyOperate()方法。这个方法在下一篇文章中会用到。

    @Override
    public void onPropertyOperate(GattCharacteristic characteristic, String operateName) {
    }


运行一下看看效果。

image.gif


六、源码


如果对你有所帮助的话,不妨 StarFork,山高水长,后会有期~

源码地址:HarmonyBle-Java

相关文章
|
4月前
|
存储 物联网 数据库
App Inventor 2 低功耗蓝牙 BlueToothLE 拓展中文文档(完整翻译加强版)
低功耗蓝牙,也称为蓝牙LE 或简称 BLE,是一种类似于经典蓝牙的新通信协议,不同之处在于它旨在消耗更少的功耗和成本,同时保持同等的功能。 因此,低功耗蓝牙是与耗电资源有限的物联网设备进行通信的首选。
152 0
|
6月前
|
前端开发 网络协议 程序员
软件开发过程中,速度和质量的追求与统一,完美app基本特性。
软件开发过程中,速度和质量的追求与统一,完美app基本特性。
35 0
|
6月前
|
移动开发 数据安全/隐私保护
HC05蓝牙模块与手机APP连接
HC05蓝牙模块与手机APP连接
214 1
|
6月前
|
物联网 Java 数据安全/隐私保护
App Inventor 2 低功耗蓝牙(BLE) 硬件接入、数据通信及IO控制
低功耗蓝牙(BLE)以低功耗、低成本、开发简便逐渐被广泛应用,本文主要介绍一款较为通用、价格低廉的BLE设备从零开始如何利用App Inventor 2开发一款自己专属的手机蓝牙App应用。 本文主要通过一款常见的BLE硬件接入控制,介绍硬件接入App Inventor 2 的通用方法,类似的硬件接入都是大同小异的。
360 1
|
6月前
|
传感器 内存技术
毕业设计 江科大STM32的智能温室控制蓝牙声光报警APP系统设计
毕业设计 江科大STM32的智能温室控制蓝牙声光报警APP系统设计
185 0
|
测试技术 API Android开发
干货 | APP自动化Android之属性获取与断言
干货 | APP自动化Android之属性获取与断言
|
测试技术 API Android开发
干货 | APP自动化Android之属性获取与断言
断言是 UI 自动化测试的三要素之一,是 UI 自动化不可或缺的部分。我们使用定位器定位到元素后,通过脚本进行业务操作的交互,想要验证交互过程中的正确性就需要用到断言。 ## 常规的UI自动化断言 分析正确的输出结果,常规的断言一般包含以下的几个情形: - 比较大小 - 包含或者不包含 - 验证布尔值 示例代码: ``` #比较大小 price = driver.find_element
|
测试技术 API Android开发
干货 | APP自动化Android之属性获取与断言
断言是 UI 自动化测试的三要素之一,是 UI 自动化不可或缺的部分。我们使用定位器定位到元素后,通过脚本进行业务操作的交互,想要验证交互过程中的正确性就需要用到断言。 ## 常规的UI自动化断言 分析正确的输出结果,常规的断言一般包含以下的几个情形: - 比较大小 - 包含或者不包含 - 验证布尔值 示例代码: ``` #比较大小 price = driver.find_element
|
28天前
|
JSON 小程序 JavaScript
uni-app开发微信小程序的报错[渲染层错误]排查及解决
uni-app开发微信小程序的报错[渲染层错误]排查及解决
424 7
|
28天前
|
小程序 JavaScript 前端开发
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
484 1

热门文章

最新文章