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