]> git.basschouten.com Git - openhab-addons.git/blob
dd591acc723f65058572c72fd283caac64d1a2c6
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.bluetooth;
14
15 import java.nio.charset.StandardCharsets;
16 import java.util.ArrayList;
17 import java.util.HashMap;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.UUID;
21
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24
25 /**
26  * The {@link BluetoothCharacteristic} class defines the Bluetooth characteristic.
27  * <p>
28  * Characteristics are defined attribute types that contain a single logical value.
29  * <p>
30  * https://www.bluetooth.com/specifications/gatt/characteristics
31  *
32  * @author Chris Jackson - Initial contribution
33  * @author Kai Kreuzer - Cleaned up code
34  * @author Peter Rosenberg - Improve properties support
35  */
36 public class BluetoothCharacteristic {
37     public static final int FORMAT_UINT8 = 0x11;
38     public static final int FORMAT_UINT16 = 0x12;
39     public static final int FORMAT_UINT32 = 0x14;
40     public static final int FORMAT_SINT8 = 0x21;
41     public static final int FORMAT_SINT16 = 0x22;
42     public static final int FORMAT_SINT32 = 0x24;
43     public static final int FORMAT_SFLOAT = 0x32;
44     public static final int FORMAT_FLOAT = 0x34;
45
46     public static final int PROPERTY_BROADCAST = 0x01;
47     public static final int PROPERTY_READ = 0x02;
48     public static final int PROPERTY_WRITE_NO_RESPONSE = 0x04;
49     public static final int PROPERTY_WRITE = 0x08;
50     public static final int PROPERTY_NOTIFY = 0x10;
51     public static final int PROPERTY_INDICATE = 0x20;
52     public static final int PROPERTY_SIGNED_WRITE = 0x40;
53     public static final int PROPERTY_EXTENDED_PROPS = 0x80;
54
55     public static final int PERMISSION_READ = 0x01;
56     public static final int PERMISSION_READ_ENCRYPTED = 0x02;
57     public static final int PERMISSION_READ_ENCRYPTED_MITM = 0x04;
58     public static final int PERMISSION_WRITE = 0x10;
59     public static final int PERMISSION_WRITE_ENCRYPTED = 0x20;
60     public static final int PERMISSION_WRITE_ENCRYPTED_MITM = 0x40;
61     public static final int PERMISSION_WRITE_SIGNED = 0x80;
62     public static final int PERMISSION_WRITE_SIGNED_MITM = 0x100;
63
64     public static final int WRITE_TYPE_DEFAULT = 0x02;
65     public static final int WRITE_TYPE_NO_RESPONSE = 0x01;
66     public static final int WRITE_TYPE_SIGNED = 0x04;
67
68     private final Logger logger = LoggerFactory.getLogger(BluetoothCharacteristic.class);
69
70     /**
71      * The {@link UUID} for this characteristic
72      */
73     protected UUID uuid;
74
75     /**
76      * The handle for this characteristic
77      */
78     protected int handle;
79
80     /**
81      * A map of {@link BluetoothDescriptor}s applicable to this characteristic
82      */
83     protected Map<UUID, BluetoothDescriptor> gattDescriptors = new HashMap<>();
84     protected int instance;
85     protected int properties;
86     protected int permissions;
87     protected int writeType;
88
89     /**
90      * The raw data value for this characteristic
91      */
92     protected int[] value = new int[0];
93
94     /**
95      * The {@link BluetoothService} to which this characteristic belongs
96      */
97     protected BluetoothService service;
98
99     /**
100      * Create a new BluetoothCharacteristic.
101      *
102      * @param uuid the {@link UUID} of the new characteristic
103      * @param handle
104      */
105     public BluetoothCharacteristic(UUID uuid, int handle) {
106         this.uuid = uuid;
107         this.handle = handle;
108     }
109
110     /**
111      * Adds a descriptor to this characteristic.
112      *
113      * @param descriptor {@link BluetoothDescriptor} to be added to this characteristic.
114      * @return true, if the descriptor was added to the characteristic
115      */
116     public boolean addDescriptor(BluetoothDescriptor descriptor) {
117         if (gattDescriptors.get(descriptor.getUuid()) != null) {
118             return false;
119         }
120
121         gattDescriptors.put(descriptor.getUuid(), descriptor);
122         return true;
123     }
124
125     /**
126      * Returns the {@link UUID} of this characteristic
127      *
128      * @return UUID of this characteristic
129      */
130     public UUID getUuid() {
131         return uuid;
132     }
133
134     /**
135      * Returns the instance ID for this characteristic.
136      *
137      * If a remote device offers multiple characteristics with the same UUID, the instance ID is used to distinguish
138      * between characteristics.
139      *
140      * @return Instance ID of this characteristic
141      */
142     public int getInstanceId() {
143         return instance;
144     }
145
146     /**
147      * Set the raw properties. The individual properties are represented as bits inside
148      * of this int value.
149      *
150      * @param properties of this Characteristic
151      */
152     public void setProperties(int properties) {
153         this.properties = properties;
154     }
155
156     /**
157      * Returns the properties of this characteristic.
158      *
159      * The properties contain a bit mask of property flags indicating the features of this characteristic.
160      *
161      */
162     public int getProperties() {
163         return properties;
164     }
165
166     /**
167      * Returns if the given characteristics property is enabled or not.
168      *
169      * @param property one of the Constants BluetoothCharacteristic.PROPERTY_XX
170      * @return true if this characteristic has the given property enabled, false if properties not set or
171      *         the given property is not enabled.
172      */
173     public boolean hasPropertyEnabled(int property) {
174         return (properties & property) != 0;
175     }
176
177     /**
178      * Returns if notifications can be enabled on this characteristic.
179      *
180      * @return true if notifications can be enabled, false if notifications are not supported, characteristic is missing
181      *         on device or notifications are not supported.
182      */
183     public boolean canNotify() {
184         return hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_NOTIFY);
185     }
186
187     /**
188      * Returns if the value can be read on this characteristic.
189      *
190      * @return true if the value can be read, false otherwise.
191      */
192     public boolean canRead() {
193         return hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_READ);
194     }
195
196     /**
197      * Returns if the value can be written on this characteristic.
198      *
199      * @return true if the value can be written with of without a response, false otherwise.
200      */
201     public boolean canWrite() {
202         return hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE)
203                 || hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE_NO_RESPONSE);
204     }
205
206     /**
207      * Returns the permissions for this characteristic.
208      */
209     public int getPermissions() {
210         return permissions;
211     }
212
213     /**
214      * Gets the write type for this characteristic.
215      *
216      */
217     public int getWriteType() {
218         return writeType;
219     }
220
221     /**
222      * Set the write type for this characteristic
223      *
224      * @param writeType
225      */
226     public void setWriteType(int writeType) {
227         this.writeType = writeType;
228     }
229
230     /**
231      * Get the service to which this characteristic belongs
232      *
233      * @return the {@link BluetoothService}
234      */
235     public BluetoothService getService() {
236         return service;
237     }
238
239     /**
240      * Returns the handle for this characteristic
241      *
242      * @return the handle for the characteristic
243      */
244     public int getHandle() {
245         return handle;
246     }
247
248     /**
249      * Get the service to which this characteristic belongs
250      *
251      * @return the {@link BluetoothService}
252      */
253     public void setService(BluetoothService service) {
254         this.service = service;
255     }
256
257     /**
258      * Returns a list of descriptors for this characteristic.
259      *
260      */
261     public List<BluetoothDescriptor> getDescriptors() {
262         return new ArrayList<>(gattDescriptors.values());
263     }
264
265     /**
266      * Returns a descriptor with a given UUID out of the list of
267      * descriptors for this characteristic.
268      *
269      * @return the {@link BluetoothDescriptor}
270      */
271     public BluetoothDescriptor getDescriptor(UUID uuid) {
272         return gattDescriptors.get(uuid);
273     }
274
275     @Override
276     public int hashCode() {
277         final int prime = 31;
278         int result = 1;
279         result = prime * result + instance;
280         result = prime * result + ((service == null) ? 0 : service.hashCode());
281         result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
282         return result;
283     }
284
285     @Override
286     public boolean equals(Object obj) {
287         if (this == obj) {
288             return true;
289         }
290         if (obj == null) {
291             return false;
292         }
293         if (getClass() != obj.getClass()) {
294             return false;
295         }
296         BluetoothCharacteristic other = (BluetoothCharacteristic) obj;
297         if (instance != other.instance) {
298             return false;
299         }
300         if (service == null) {
301             if (other.service != null) {
302                 return false;
303             }
304         } else if (!service.equals(other.service)) {
305             return false;
306         }
307         if (uuid == null) {
308             if (other.uuid != null) {
309                 return false;
310             }
311         } else if (!uuid.equals(other.uuid)) {
312             return false;
313         }
314         return true;
315     }
316
317     /**
318      * Get the stored value for this characteristic.
319      *
320      */
321     public int[] getValue() {
322         return value;
323     }
324
325     /**
326      * Get the stored value for this characteristic.
327      *
328      */
329     public byte[] getByteValue() {
330         byte[] byteValue = new byte[value.length];
331         for (int cnt = 0; cnt < value.length; cnt++) {
332             byteValue[cnt] = (byte) (value[cnt] & 0xFF);
333         }
334         return byteValue;
335     }
336
337     /**
338      * Return the stored value of this characteristic.
339      *
340      */
341     public Integer getIntValue(int formatType, int offset) {
342         if ((offset + getTypeLen(formatType)) > value.length) {
343             return null;
344         }
345
346         switch (formatType) {
347             case FORMAT_UINT8:
348                 return unsignedByteToInt(value[offset]);
349
350             case FORMAT_UINT16:
351                 return unsignedBytesToInt(value[offset], value[offset + 1]);
352
353             case FORMAT_UINT32:
354                 return unsignedBytesToInt(value[offset], value[offset + 1], value[offset + 2], value[offset + 3]);
355
356             case FORMAT_SINT8:
357                 return unsignedToSigned(unsignedByteToInt(value[offset]), 8);
358
359             case FORMAT_SINT16:
360                 return unsignedToSigned(unsignedBytesToInt(value[offset], value[offset + 1]), 16);
361
362             case FORMAT_SINT32:
363                 return unsignedToSigned(
364                         unsignedBytesToInt(value[offset], value[offset + 1], value[offset + 2], value[offset + 3]), 32);
365             default:
366                 logger.error("Unknown format type {} - no int value can be provided for it.", formatType);
367         }
368
369         return null;
370     }
371
372     /**
373      * Return the stored value of this characteristic. This doesn't read the remote data.
374      *
375      */
376     public Float getFloatValue(int formatType, int offset) {
377         if ((offset + getTypeLen(formatType)) > value.length) {
378             return null;
379         }
380
381         switch (formatType) {
382             case FORMAT_SFLOAT:
383                 return bytesToFloat(value[offset], value[offset + 1]);
384             case FORMAT_FLOAT:
385                 return bytesToFloat(value[offset], value[offset + 1], value[offset + 2], value[offset + 3]);
386             default:
387                 logger.error("Unknown format type {} - no float value can be provided for it.", formatType);
388         }
389
390         return null;
391     }
392
393     /**
394      * Return the stored value of this characteristic. This doesn't read the remote data.
395      *
396      */
397     public String getStringValue(int offset) {
398         if (value == null || offset > value.length) {
399             return null;
400         }
401         byte[] strBytes = new byte[value.length - offset];
402         for (int i = 0; i < (value.length - offset); ++i) {
403             strBytes[i] = (byte) value[offset + i];
404         }
405         return new String(strBytes, StandardCharsets.UTF_8);
406     }
407
408     /**
409      * Updates the locally stored value of this characteristic.
410      *
411      * @param value the value to set
412      * @return true, if it has been set successfully
413      */
414     public boolean setValue(int[] value) {
415         this.value = value;
416         return true;
417     }
418
419     /**
420      * Set the local value of this characteristic.
421      *
422      * @param value the value to set
423      * @param formatType the format of the value (as one of the FORMAT_* constants in this class)
424      * @param offset the offset to use when interpreting the value
425      * @return true, if it has been set successfully
426      */
427     public boolean setValue(int value, int formatType, int offset) {
428         int len = offset + getTypeLen(formatType);
429         if (this.value == null) {
430             this.value = new int[len];
431         }
432         if (len > this.value.length) {
433             return false;
434         }
435         int val = value;
436         switch (formatType) {
437             case FORMAT_SINT8:
438                 val = intToSignedBits(value, 8);
439                 // Fall-through intended
440             case FORMAT_UINT8:
441                 this.value[offset] = (byte) (val & 0xFF);
442                 break;
443
444             case FORMAT_SINT16:
445                 val = intToSignedBits(value, 16);
446                 // Fall-through intended
447             case FORMAT_UINT16:
448                 this.value[offset] = (byte) (val & 0xFF);
449                 this.value[offset + 1] = (byte) ((val >> 8) & 0xFF);
450                 break;
451
452             case FORMAT_SINT32:
453                 val = intToSignedBits(value, 32);
454                 // Fall-through intended
455             case FORMAT_UINT32:
456                 this.value[offset] = (byte) (val & 0xFF);
457                 this.value[offset + 1] = (byte) ((val >> 8) & 0xFF);
458                 this.value[offset + 2] = (byte) ((val >> 16) & 0xFF);
459                 this.value[offset + 2] = (byte) ((val >> 24) & 0xFF);
460                 break;
461
462             default:
463                 return false;
464         }
465         return true;
466     }
467
468     /**
469      * Set the local value of this characteristic.
470      *
471      * @param mantissa the mantissa of the value
472      * @param exponent the exponent of the value
473      * @param formatType the format of the value (as one of the FORMAT_* constants in this class)
474      * @param offset the offset to use when interpreting the value
475      * @return true, if it has been set successfully
476      *
477      */
478     public boolean setValue(int mantissa, int exponent, int formatType, int offset) {
479         int len = offset + getTypeLen(formatType);
480         if (value == null) {
481             value = new int[len];
482         }
483         if (len > value.length) {
484             return false;
485         }
486
487         switch (formatType) {
488             case FORMAT_SFLOAT:
489                 int m = intToSignedBits(mantissa, 12);
490                 int exp = intToSignedBits(exponent, 4);
491                 value[offset] = (byte) (m & 0xFF);
492                 value[offset + 1] = (byte) ((m >> 8) & 0x0F);
493                 value[offset + 1] += (byte) ((exp & 0x0F) << 4);
494                 break;
495
496             case FORMAT_FLOAT:
497                 m = intToSignedBits(mantissa, 24);
498                 exp = intToSignedBits(exponent, 8);
499                 value[offset] = (byte) (m & 0xFF);
500                 value[offset + 1] = (byte) ((m >> 8) & 0xFF);
501                 value[offset + 2] = (byte) ((m >> 16) & 0xFF);
502                 value[offset + 2] += (byte) (exp & 0xFF);
503                 break;
504
505             default:
506                 return false;
507         }
508
509         return true;
510     }
511
512     /**
513      * Set the local value of this characteristic.
514      *
515      * @param value the value to set
516      * @return true, if it has been set successfully
517      */
518     public boolean setValue(byte[] value) {
519         this.value = new int[value.length];
520         int cnt = 0;
521         for (byte val : value) {
522             this.value[cnt++] = val;
523         }
524         return true;
525     }
526
527     /**
528      * Set the local value of this characteristic.
529      *
530      * @param value the value to set
531      * @return true, if it has been set successfully
532      */
533     public boolean setValue(String value) {
534         this.value = new int[value.getBytes().length];
535         int cnt = 0;
536         for (byte val : value.getBytes()) {
537             this.value[cnt++] = val;
538         }
539         return true;
540     }
541
542     /**
543      * Returns the size of the requested value type.
544      */
545     private int getTypeLen(int formatType) {
546         return formatType & 0xF;
547     }
548
549     /**
550      * Convert a signed byte to an unsigned int.
551      */
552     private int unsignedByteToInt(int value) {
553         return value & 0xFF;
554     }
555
556     /**
557      * Convert signed bytes to a 16-bit unsigned int.
558      */
559     private int unsignedBytesToInt(int value1, int value2) {
560         return value1 + (value2 << 8);
561     }
562
563     /**
564      * Convert signed bytes to a 32-bit unsigned int.
565      */
566     private int unsignedBytesToInt(int value1, int value2, int value3, int value4) {
567         return value1 + (value2 << 8) + (value3 << 16) + (value4 << 24);
568     }
569
570     /**
571      * Convert signed bytes to a 16-bit short float value.
572      */
573     private float bytesToFloat(int value1, int value2) {
574         int mantissa = unsignedToSigned(unsignedByteToInt(value1) + ((unsignedByteToInt(value2) & 0x0F) << 8), 12);
575         int exponent = unsignedToSigned(unsignedByteToInt(value2) >> 4, 4);
576         return (float) (mantissa * Math.pow(10, exponent));
577     }
578
579     /**
580      * Convert signed bytes to a 32-bit short float value.
581      */
582     private float bytesToFloat(int value1, int value2, int value3, int value4) {
583         int mantissa = unsignedToSigned(
584                 unsignedByteToInt(value1) + (unsignedByteToInt(value2) << 8) + (unsignedByteToInt(value3) << 16), 24);
585         return (float) (mantissa * Math.pow(10, value4));
586     }
587
588     /**
589      * Convert an unsigned integer to a two's-complement signed value.
590      */
591     private int unsignedToSigned(int unsigned, int size) {
592         if ((unsigned & (1 << size - 1)) != 0) {
593             return -1 * ((1 << size - 1) - (unsigned & ((1 << size - 1) - 1)));
594         } else {
595             return unsigned;
596         }
597     }
598
599     /**
600      * Convert an integer into the signed bits of the specified length.
601      */
602     private int intToSignedBits(int i, int size) {
603         if (i < 0) {
604             return (1 << size - 1) + (i & ((1 << size - 1) - 1));
605         } else {
606             return i;
607         }
608     }
609
610     public GattCharacteristic getGattCharacteristic() {
611         return GattCharacteristic.getCharacteristic(uuid);
612     }
613
614     public enum GattCharacteristic {
615         // Characteristic
616         ALERT_CATEGORY_ID(0x2A43),
617         ALERT_CATEGORY_ID_BIT_MASK(0x2A42),
618         ALERT_LEVEL(0x2A06),
619         ALERT_NOTIFICATION_CONTROL_POINT(0x2A44),
620         ALERT_STATUS(0x2A3F),
621         APPEARANCE(0x2A01),
622         BATTERY_LEVEL(0x2A19),
623         BLOOD_PRESSURE_FEATURE(0x2A49),
624         BLOOD_PRESSURE_MEASUREMENT(0x2A35),
625         BODY_SENSOR_LOCATION(0x2A38),
626         BOOT_KEYOBARD_INPUT_REPORT(0x2A22),
627         BOOT_KEYOBARD_OUTPUT_REPORT(0x2A32),
628         BOOT_MOUSE_INPUT_REPORT(0x2A33),
629         CSC_FEATURE(0x2A5C),
630         CSC_MEASUREMENT(0x2A5B),
631         CURRENT_TIME(0x2A2B),
632         CYCLING_POWER_CONTROL_POINT(0x2A66),
633         CYCLING_POWER_FEATURE(0x2A65),
634         CYCLING_POWER_MEASUREMENT(0x2A63),
635         CYCLING_POWER_VECTOR(0x2A64),
636         DATE_TIME(0x2A08),
637         DAY_DATE_TIME(0x2A0A),
638         DAY_OF_WEEK(0x2A09),
639         DEVICE_NAME(0x2A00),
640         DST_OFFSET(0x2A0D),
641         EXACT_TIME_256(0x2A0C),
642         FIRMWARE_REVISION_STRING(0x2A26),
643         GLUCOSE_FEATURE(0x2A51),
644         GLUCOSE_MEASUREMENT(0x2A18),
645         GLUCOSE_MEASUREMENT_CONTROL(0x2A34),
646         HARDWARE_REVISION_STRING(0x2A27),
647         HEART_RATE_CONTROL_POINT(0x2A39),
648         HEART_RATE_MEASUREMENT(0x2A37),
649         HID_CONTROL_POINT(0x2A4C),
650         HID_INFORMATION(0x2A4A),
651         IEEE11073_20601_REGULATORY_CERTIFICATION_DATA_LIST(0x2A2A),
652         INTERMEDIATE_CUFF_PRESSURE(0x2A36),
653         INTERMEDIATE_TEMPERATURE(0x2A1E),
654         LN_CONTROL_POINT(0x2A6B),
655         LN_FEATURE(0x2A6A),
656         LOCAL_TIME_INFORMATION(0x2A0F),
657         LOCATION_AND_SPEED(0x2A67),
658         MANUFACTURER_NAME_STRING(0x2A29),
659         MEASUREMENT_INTERVAL(0x2A21),
660         MODEL_NUMBER_STRING(0x2A24),
661         NAVIGATION(0x2A68),
662         NEW_ALERT(0x2A46),
663         PERIPERAL_PREFFERED_CONNECTION_PARAMETERS(0x2A04),
664         PERIPHERAL_PRIVACY_FLAG(0x2A02),
665         PN_PID(0x2A50),
666         POSITION_QUALITY(0x2A69),
667         PROTOCOL_MODE(0x2A4E),
668         RECONNECTION_ADDRESS(0x2A03),
669         RECORD_ACCESS_CONTROL_POINT(0x2A52),
670         REFERENCE_TIME_INFORMATION(0x2A14),
671         REPORT(0x2A4D),
672         REPORT_MAP(0x2A4B),
673         RINGER_CONTROL_POINT(0x2A40),
674         RINGER_SETTING(0x2A41),
675         RSC_FEATURE(0x2A54),
676         RSC_MEASUREMENT(0x2A53),
677         SC_CONTROL_POINT(0x2A55),
678         SCAN_INTERVAL_WINDOW(0x2A4F),
679         SCAN_REFRESH(0x2A31),
680         SENSOR_LOCATION(0x2A5D),
681         SERIAL_NUMBER_STRING(0x2A25),
682         SERVICE_CHANGED(0x2A05),
683         SOFTWARE_REVISION_STRING(0x2A28),
684         SUPPORTED_NEW_ALERT_CATEGORY(0x2A47),
685         SUPPORTED_UNREAD_ALERT_CATEGORY(0x2A48),
686         SYSTEM_ID(0x2A23),
687         TEMPERATURE_MEASUREMENT(0x2A1C),
688         TEMPERATURE_TYPE(0x2A1D),
689         TIME_ACCURACY(0x2A12),
690         TIME_SOURCE(0x2A13),
691         TIME_UPDATE_CONTROL_POINT(0x2A16),
692         TIME_UPDATE_STATE(0x2A17),
693         TIME_WITH_DST(0x2A11),
694         TIME_ZONE(0x2A0E),
695         TX_POWER_LEVEL(0x2A07),
696         UNREAD_ALERT_STATUS(0x2A45),
697         AGGREGATE_INPUT(0x2A5A),
698         ANALOG_INPUT(0x2A58),
699         ANALOG_OUTPUT(0x2A59),
700         DIGITAL_INPUT(0x2A56),
701         DIGITAL_OUTPUT(0x2A57),
702         EXACT_TIME_100(0x2A0B),
703         NETWORK_AVAILABILITY(0x2A3E),
704         SCIENTIFIC_TEMPERATURE_IN_CELSIUS(0x2A3C),
705         SECONDARY_TIME_ZONE(0x2A10),
706         STRING(0x2A3D),
707         TEMPERATURE_IN_CELSIUS(0x2A1F),
708         TEMPERATURE_IN_FAHRENHEIT(0x2A20),
709         TIME_BROADCAST(0x2A15),
710         BATTERY_LEVEL_STATE(0x2A1B),
711         BATTERY_POWER_STATE(0x2A1A),
712         PULSE_OXIMETRY_CONTINUOUS_MEASUREMENT(0x2A5F),
713         PULSE_OXIMETRY_CONTROL_POINT(0x2A62),
714         PULSE_OXIMETRY_FEATURES(0x2A61),
715         PULSE_OXIMETRY_PULSATILE_EVENT(0x2A60),
716         PULSE_OXIMETRY_SPOT_CHECK_MEASUREMENT(0x2A5E),
717         RECORD_ACCESS_CONTROL_POINT_TESTVERSION(0x2A52),
718         REMOVABLE(0x2A3A),
719         SERVICE_REQUIRED(0x2A3B);
720
721         private static Map<UUID, GattCharacteristic> uuidToServiceMapping;
722
723         private UUID uuid;
724
725         private GattCharacteristic(long key) {
726             this.uuid = BluetoothBindingConstants.createBluetoothUUID(key);
727         }
728
729         private static void initMapping() {
730             uuidToServiceMapping = new HashMap<>();
731             for (GattCharacteristic s : values()) {
732                 uuidToServiceMapping.put(s.uuid, s);
733             }
734         }
735
736         public static GattCharacteristic getCharacteristic(UUID uuid) {
737             if (uuidToServiceMapping == null) {
738                 initMapping();
739             }
740             return uuidToServiceMapping.get(uuid);
741         }
742
743         /**
744          * @return the key
745          */
746         public UUID getUUID() {
747             return uuid;
748         }
749     }
750 }