2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.bluetooth;
15 import java.nio.charset.StandardCharsets;
16 import java.util.ArrayList;
17 import java.util.HashMap;
18 import java.util.List;
20 import java.util.UUID;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
26 * The {@link BluetoothCharacteristic} class defines the Bluetooth characteristic.
28 * Characteristics are defined attribute types that contain a single logical value.
30 * https://www.bluetooth.com/specifications/gatt/characteristics
32 * @author Chris Jackson - Initial contribution
33 * @author Kai Kreuzer - Cleaned up code
34 * @author Peter Rosenberg - Improve properties support
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;
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;
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;
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;
68 private final Logger logger = LoggerFactory.getLogger(BluetoothCharacteristic.class);
71 * The {@link UUID} for this characteristic
76 * The handle for this characteristic
81 * A map of {@link BluetoothDescriptor}s applicable to this characteristic
83 protected Map<UUID, BluetoothDescriptor> gattDescriptors = new HashMap<>();
84 protected int instance;
85 protected int properties;
86 protected int permissions;
87 protected int writeType;
90 * The raw data value for this characteristic
92 protected int[] value = new int[0];
95 * The {@link BluetoothService} to which this characteristic belongs
97 protected BluetoothService service;
100 * Create a new BluetoothCharacteristic.
102 * @param uuid the {@link UUID} of the new characteristic
105 public BluetoothCharacteristic(UUID uuid, int handle) {
107 this.handle = handle;
111 * Adds a descriptor to this characteristic.
113 * @param descriptor {@link BluetoothDescriptor} to be added to this characteristic.
114 * @return true, if the descriptor was added to the characteristic
116 public boolean addDescriptor(BluetoothDescriptor descriptor) {
117 if (gattDescriptors.get(descriptor.getUuid()) != null) {
121 gattDescriptors.put(descriptor.getUuid(), descriptor);
126 * Returns the {@link UUID} of this characteristic
128 * @return UUID of this characteristic
130 public UUID getUuid() {
135 * Returns the instance ID for this characteristic.
137 * If a remote device offers multiple characteristics with the same UUID, the instance ID is used to distinguish
138 * between characteristics.
140 * @return Instance ID of this characteristic
142 public int getInstanceId() {
147 * Set the raw properties. The individual properties are represented as bits inside
150 * @param properties of this Characteristic
152 public void setProperties(int properties) {
153 this.properties = properties;
157 * Returns the properties of this characteristic.
159 * The properties contain a bit mask of property flags indicating the features of this characteristic.
162 public int getProperties() {
167 * Returns if the given characteristics property is enabled or not.
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.
173 public boolean hasPropertyEnabled(int property) {
174 return (properties & property) != 0;
178 * Returns if notifications can be enabled on this characteristic.
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.
183 public boolean canNotify() {
184 return hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_NOTIFY);
188 * Returns if the value can be read on this characteristic.
190 * @return true if the value can be read, false otherwise.
192 public boolean canRead() {
193 return hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_READ);
197 * Returns if the value can be written on this characteristic.
199 * @return true if the value can be written with of without a response, false otherwise.
201 public boolean canWrite() {
202 return hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE)
203 || hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE_NO_RESPONSE);
207 * Returns the permissions for this characteristic.
209 public int getPermissions() {
214 * Gets the write type for this characteristic.
217 public int getWriteType() {
222 * Set the write type for this characteristic
226 public void setWriteType(int writeType) {
227 this.writeType = writeType;
231 * Get the service to which this characteristic belongs
233 * @return the {@link BluetoothService}
235 public BluetoothService getService() {
240 * Returns the handle for this characteristic
242 * @return the handle for the characteristic
244 public int getHandle() {
249 * Get the service to which this characteristic belongs
251 * @return the {@link BluetoothService}
253 public void setService(BluetoothService service) {
254 this.service = service;
258 * Returns a list of descriptors for this characteristic.
261 public List<BluetoothDescriptor> getDescriptors() {
262 return new ArrayList<>(gattDescriptors.values());
266 * Returns a descriptor with a given UUID out of the list of
267 * descriptors for this characteristic.
269 * @return the {@link BluetoothDescriptor}
271 public BluetoothDescriptor getDescriptor(UUID uuid) {
272 return gattDescriptors.get(uuid);
276 public int hashCode() {
277 final int prime = 31;
279 result = prime * result + instance;
280 result = prime * result + ((service == null) ? 0 : service.hashCode());
281 result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
286 public boolean equals(Object obj) {
293 if (getClass() != obj.getClass()) {
296 BluetoothCharacteristic other = (BluetoothCharacteristic) obj;
297 if (instance != other.instance) {
300 if (service == null) {
301 if (other.service != null) {
304 } else if (!service.equals(other.service)) {
308 if (other.uuid != null) {
311 } else if (!uuid.equals(other.uuid)) {
318 * Get the stored value for this characteristic.
321 public int[] getValue() {
326 * Get the stored value for this characteristic.
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);
338 * Return the stored value of this characteristic.
341 public Integer getIntValue(int formatType, int offset) {
342 if ((offset + getTypeLen(formatType)) > value.length) {
346 switch (formatType) {
348 return unsignedByteToInt(value[offset]);
351 return unsignedBytesToInt(value[offset], value[offset + 1]);
354 return unsignedBytesToInt(value[offset], value[offset + 1], value[offset + 2], value[offset + 3]);
357 return unsignedToSigned(unsignedByteToInt(value[offset]), 8);
360 return unsignedToSigned(unsignedBytesToInt(value[offset], value[offset + 1]), 16);
363 return unsignedToSigned(
364 unsignedBytesToInt(value[offset], value[offset + 1], value[offset + 2], value[offset + 3]), 32);
366 logger.error("Unknown format type {} - no int value can be provided for it.", formatType);
373 * Return the stored value of this characteristic. This doesn't read the remote data.
376 public Float getFloatValue(int formatType, int offset) {
377 if ((offset + getTypeLen(formatType)) > value.length) {
381 switch (formatType) {
383 return bytesToFloat(value[offset], value[offset + 1]);
385 return bytesToFloat(value[offset], value[offset + 1], value[offset + 2], value[offset + 3]);
387 logger.error("Unknown format type {} - no float value can be provided for it.", formatType);
394 * Return the stored value of this characteristic. This doesn't read the remote data.
397 public String getStringValue(int offset) {
398 if (value == null || offset > value.length) {
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];
405 return new String(strBytes, StandardCharsets.UTF_8);
409 * Updates the locally stored value of this characteristic.
411 * @param value the value to set
412 * @return true, if it has been set successfully
414 public boolean setValue(int[] value) {
420 * Set the local value of this characteristic.
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
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];
432 if (len > this.value.length) {
436 switch (formatType) {
438 val = intToSignedBits(value, 8);
439 // Fall-through intended
441 this.value[offset] = (byte) (val & 0xFF);
445 val = intToSignedBits(value, 16);
446 // Fall-through intended
448 this.value[offset] = (byte) (val & 0xFF);
449 this.value[offset + 1] = (byte) ((val >> 8) & 0xFF);
453 val = intToSignedBits(value, 32);
454 // Fall-through intended
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);
469 * Set the local value of this characteristic.
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
478 public boolean setValue(int mantissa, int exponent, int formatType, int offset) {
479 int len = offset + getTypeLen(formatType);
481 value = new int[len];
483 if (len > value.length) {
487 switch (formatType) {
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);
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);
513 * Set the local value of this characteristic.
515 * @param value the value to set
516 * @return true, if it has been set successfully
518 public boolean setValue(byte[] value) {
519 this.value = new int[value.length];
521 for (byte val : value) {
522 this.value[cnt++] = val;
528 * Set the local value of this characteristic.
530 * @param value the value to set
531 * @return true, if it has been set successfully
533 public boolean setValue(String value) {
534 this.value = new int[value.getBytes().length];
536 for (byte val : value.getBytes()) {
537 this.value[cnt++] = val;
543 * Returns the size of the requested value type.
545 private int getTypeLen(int formatType) {
546 return formatType & 0xF;
550 * Convert a signed byte to an unsigned int.
552 private int unsignedByteToInt(int value) {
557 * Convert signed bytes to a 16-bit unsigned int.
559 private int unsignedBytesToInt(int value1, int value2) {
560 return value1 + (value2 << 8);
564 * Convert signed bytes to a 32-bit unsigned int.
566 private int unsignedBytesToInt(int value1, int value2, int value3, int value4) {
567 return value1 + (value2 << 8) + (value3 << 16) + (value4 << 24);
571 * Convert signed bytes to a 16-bit short float value.
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));
580 * Convert signed bytes to a 32-bit short float value.
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));
589 * Convert an unsigned integer to a two's-complement signed value.
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)));
600 * Convert an integer into the signed bits of the specified length.
602 private int intToSignedBits(int i, int size) {
604 return (1 << size - 1) + (i & ((1 << size - 1) - 1));
610 public GattCharacteristic getGattCharacteristic() {
611 return GattCharacteristic.getCharacteristic(uuid);
614 public enum GattCharacteristic {
616 ALERT_CATEGORY_ID(0x2A43),
617 ALERT_CATEGORY_ID_BIT_MASK(0x2A42),
619 ALERT_NOTIFICATION_CONTROL_POINT(0x2A44),
620 ALERT_STATUS(0x2A3F),
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),
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),
637 DAY_DATE_TIME(0x2A0A),
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),
656 LOCAL_TIME_INFORMATION(0x2A0F),
657 LOCATION_AND_SPEED(0x2A67),
658 MANUFACTURER_NAME_STRING(0x2A29),
659 MEASUREMENT_INTERVAL(0x2A21),
660 MODEL_NUMBER_STRING(0x2A24),
663 PERIPERAL_PREFFERED_CONNECTION_PARAMETERS(0x2A04),
664 PERIPHERAL_PRIVACY_FLAG(0x2A02),
666 POSITION_QUALITY(0x2A69),
667 PROTOCOL_MODE(0x2A4E),
668 RECONNECTION_ADDRESS(0x2A03),
669 RECORD_ACCESS_CONTROL_POINT(0x2A52),
670 REFERENCE_TIME_INFORMATION(0x2A14),
673 RINGER_CONTROL_POINT(0x2A40),
674 RINGER_SETTING(0x2A41),
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),
687 TEMPERATURE_MEASUREMENT(0x2A1C),
688 TEMPERATURE_TYPE(0x2A1D),
689 TIME_ACCURACY(0x2A12),
691 TIME_UPDATE_CONTROL_POINT(0x2A16),
692 TIME_UPDATE_STATE(0x2A17),
693 TIME_WITH_DST(0x2A11),
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),
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),
719 SERVICE_REQUIRED(0x2A3B);
721 private static Map<UUID, GattCharacteristic> uuidToServiceMapping;
725 private GattCharacteristic(long key) {
726 this.uuid = BluetoothBindingConstants.createBluetoothUUID(key);
729 private static void initMapping() {
730 uuidToServiceMapping = new HashMap<>();
731 for (GattCharacteristic s : values()) {
732 uuidToServiceMapping.put(s.uuid, s);
736 public static GattCharacteristic getCharacteristic(UUID uuid) {
737 if (uuidToServiceMapping == null) {
740 return uuidToServiceMapping.get(uuid);
746 public UUID getUUID() {