[bluetooth.bluegiga] Add characteristic notification support (#9067)
* Add support for characteristic notifications. * Also fixed bluegiga initialize/dispose bugs. Signed-off-by: Connor Petty <mistercpp2000+gitsignoff@gmail.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bluetooth.bluegiga;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.openhab.binding.bluetooth.BluetoothCharacteristic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link BlueGigaBluetoothCharacteristic} class extends BluetoothCharacteristic
|
||||||
|
* to provide write access to certain BluetoothCharacteristic fields that BlueGiga
|
||||||
|
* may not be initially aware of during characteristic construction but must be discovered
|
||||||
|
* later.
|
||||||
|
*
|
||||||
|
* @author Connor Petty - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class BlueGigaBluetoothCharacteristic extends BluetoothCharacteristic {
|
||||||
|
|
||||||
|
private boolean notificationEnabled;
|
||||||
|
|
||||||
|
public BlueGigaBluetoothCharacteristic(int handle) {
|
||||||
|
super(null, handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProperties(int properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHandle(int handle) {
|
||||||
|
this.handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUUID(UUID uuid) {
|
||||||
|
this.uuid = uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNotificationEnabled() {
|
||||||
|
return notificationEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotificationEnabled(boolean enable) {
|
||||||
|
this.notificationEnabled = enable;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,13 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.bluetooth.bluegiga;
|
package org.openhab.binding.bluetooth.bluegiga;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.NavigableMap;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -21,6 +27,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.bluetooth.BaseBluetoothDevice;
|
import org.openhab.binding.bluetooth.BaseBluetoothDevice;
|
||||||
import org.openhab.binding.bluetooth.BluetoothAddress;
|
import org.openhab.binding.bluetooth.BluetoothAddress;
|
||||||
|
import org.openhab.binding.bluetooth.BluetoothBindingConstants;
|
||||||
import org.openhab.binding.bluetooth.BluetoothCharacteristic;
|
import org.openhab.binding.bluetooth.BluetoothCharacteristic;
|
||||||
import org.openhab.binding.bluetooth.BluetoothCompletionStatus;
|
import org.openhab.binding.bluetooth.BluetoothCompletionStatus;
|
||||||
import org.openhab.binding.bluetooth.BluetoothDescriptor;
|
import org.openhab.binding.bluetooth.BluetoothDescriptor;
|
||||||
@@ -59,6 +66,9 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
|||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(BlueGigaBluetoothDevice.class);
|
private final Logger logger = LoggerFactory.getLogger(BlueGigaBluetoothDevice.class);
|
||||||
|
|
||||||
|
private Map<Integer, UUID> handleToUUID = new HashMap<>();
|
||||||
|
private NavigableMap<Integer, BlueGigaBluetoothCharacteristic> handleToCharacteristic = new TreeMap<>();
|
||||||
|
|
||||||
// BlueGiga needs to know the address type when connecting
|
// BlueGiga needs to know the address type when connecting
|
||||||
private BluetoothAddressType addressType = BluetoothAddressType.UNKNOWN;
|
private BluetoothAddressType addressType = BluetoothAddressType.UNKNOWN;
|
||||||
|
|
||||||
@@ -70,8 +80,11 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
|||||||
NONE,
|
NONE,
|
||||||
GET_SERVICES,
|
GET_SERVICES,
|
||||||
GET_CHARACTERISTICS,
|
GET_CHARACTERISTICS,
|
||||||
|
READ_CHARACTERISTIC_DECL,
|
||||||
CHARACTERISTIC_READ,
|
CHARACTERISTIC_READ,
|
||||||
CHARACTERISTIC_WRITE
|
CHARACTERISTIC_WRITE,
|
||||||
|
NOTIFICATION_ENABLE,
|
||||||
|
NOTIFICATION_DISABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
private BlueGigaProcedure procedureProgress = BlueGigaProcedure.NONE;
|
private BlueGigaProcedure procedureProgress = BlueGigaProcedure.NONE;
|
||||||
@@ -135,13 +148,13 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
|||||||
public boolean connect() {
|
public boolean connect() {
|
||||||
if (connection != -1) {
|
if (connection != -1) {
|
||||||
// We're already connected
|
// We're already connected
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelTimer(connectTimer);
|
cancelTimer(connectTimer);
|
||||||
if (bgHandler.bgConnect(address, addressType)) {
|
if (bgHandler.bgConnect(address, addressType)) {
|
||||||
connectionState = ConnectionState.CONNECTING;
|
connectionState = ConnectionState.CONNECTING;
|
||||||
connectTimer = startTimer(connectTimeoutTask, TIMEOUT_SEC);
|
connectTimer = startTimer(connectTimeoutTask, 10);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
connectionState = ConnectionState.DISCONNECTED;
|
connectionState = ConnectionState.DISCONNECTED;
|
||||||
@@ -153,7 +166,7 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
|||||||
public boolean disconnect() {
|
public boolean disconnect() {
|
||||||
if (connection == -1) {
|
if (connection == -1) {
|
||||||
// We're already disconnected
|
// We're already disconnected
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return bgHandler.bgDisconnect(connection);
|
return bgHandler.bgDisconnect(connection);
|
||||||
@@ -177,16 +190,104 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean enableNotifications(BluetoothCharacteristic characteristic) {
|
public boolean enableNotifications(BluetoothCharacteristic characteristic) {
|
||||||
// TODO will be implemented in a followup PR
|
if (connection == -1) {
|
||||||
|
logger.debug("Cannot enable notifications, device not connected {}", this);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BlueGigaBluetoothCharacteristic ch = (BlueGigaBluetoothCharacteristic) characteristic;
|
||||||
|
if (ch.isNotificationEnabled()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothDescriptor descriptor = ch
|
||||||
|
.getDescriptor(BluetoothDescriptor.GattDescriptor.CLIENT_CHARACTERISTIC_CONFIGURATION.getUUID());
|
||||||
|
|
||||||
|
if (descriptor == null || descriptor.getHandle() == 0) {
|
||||||
|
logger.debug("unable to find CCC for characteristic {}", characteristic.getUuid());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (procedureProgress != BlueGigaProcedure.NONE) {
|
||||||
|
logger.debug("Procedure already in progress {}", procedureProgress);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] value = { 1, 0 };
|
||||||
|
byte[] bvalue = toBytes(value);
|
||||||
|
descriptor.setValue(bvalue);
|
||||||
|
|
||||||
|
cancelTimer(procedureTimer);
|
||||||
|
if (!bgHandler.bgWriteCharacteristic(connection, descriptor.getHandle(), value)) {
|
||||||
|
logger.debug("bgWriteCharacteristic returned false");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
procedureTimer = startTimer(procedureTimeoutTask, TIMEOUT_SEC);
|
||||||
|
procedureProgress = BlueGigaProcedure.NOTIFICATION_ENABLE;
|
||||||
|
procedureCharacteristic = characteristic;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// we intentionally sleep here in order to give this procedure a chance to complete.
|
||||||
|
// ideally we would use locks/conditions to make this wait until completiong but
|
||||||
|
// I have a better solution planned for later. - Connor Petty
|
||||||
|
Thread.sleep(500);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean disableNotifications(BluetoothCharacteristic characteristic) {
|
public boolean disableNotifications(BluetoothCharacteristic characteristic) {
|
||||||
// TODO will be implemented in a followup PR
|
if (connection == -1) {
|
||||||
|
logger.debug("Cannot enable notifications, device not connected {}", this);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BlueGigaBluetoothCharacteristic ch = (BlueGigaBluetoothCharacteristic) characteristic;
|
||||||
|
if (ch.isNotificationEnabled()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothDescriptor descriptor = ch
|
||||||
|
.getDescriptor(BluetoothDescriptor.GattDescriptor.CLIENT_CHARACTERISTIC_CONFIGURATION.getUUID());
|
||||||
|
|
||||||
|
if (descriptor == null || descriptor.getHandle() == 0) {
|
||||||
|
logger.debug("unable to find CCC for characteristic {}", characteristic.getUuid());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (procedureProgress != BlueGigaProcedure.NONE) {
|
||||||
|
logger.debug("Procedure already in progress {}", procedureProgress);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] value = { 0, 0 };
|
||||||
|
byte[] bvalue = toBytes(value);
|
||||||
|
descriptor.setValue(bvalue);
|
||||||
|
|
||||||
|
cancelTimer(procedureTimer);
|
||||||
|
if (!bgHandler.bgWriteCharacteristic(connection, descriptor.getHandle(), value)) {
|
||||||
|
logger.debug("bgWriteCharacteristic returned false");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
procedureTimer = startTimer(procedureTimeoutTask, TIMEOUT_SEC);
|
||||||
|
procedureProgress = BlueGigaProcedure.NOTIFICATION_DISABLE;
|
||||||
|
procedureCharacteristic = characteristic;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// we intentionally sleep here in order to give this procedure a chance to complete.
|
||||||
|
// ideally we would use locks/conditions to make this wait until completiong but
|
||||||
|
// I have a better solution planned for later. - Connor Petty
|
||||||
|
Thread.sleep(500);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean enableNotifications(BluetoothDescriptor descriptor) {
|
public boolean enableNotifications(BluetoothDescriptor descriptor) {
|
||||||
// TODO will be implemented in a followup PR
|
// TODO will be implemented in a followup PR
|
||||||
@@ -204,6 +305,9 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
|||||||
if (characteristic == null || characteristic.getHandle() == 0) {
|
if (characteristic == null || characteristic.getHandle() == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (connection == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (procedureProgress != BlueGigaProcedure.NONE) {
|
if (procedureProgress != BlueGigaProcedure.NONE) {
|
||||||
return false;
|
return false;
|
||||||
@@ -225,6 +329,9 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
|||||||
if (characteristic == null || characteristic.getHandle() == 0) {
|
if (characteristic == null || characteristic.getHandle() == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (connection == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (procedureProgress != BlueGigaProcedure.NONE) {
|
if (procedureProgress != BlueGigaProcedure.NONE) {
|
||||||
return false;
|
return false;
|
||||||
@@ -404,7 +511,7 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.trace("BlueGiga Group: {} svcs={}", this, supportedServices);
|
logger.trace("BlueGiga Group: {} event={}", this, event);
|
||||||
updateLastSeenTime();
|
updateLastSeenTime();
|
||||||
|
|
||||||
BluetoothService service = new BluetoothService(event.getUuid(), true, event.getStart(), event.getEnd());
|
BluetoothService service = new BluetoothService(event.getUuid(), true, event.getStart(), event.getEnd());
|
||||||
@@ -417,18 +524,32 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.trace("BlueGiga FindInfo: {} svcs={}", this, supportedServices);
|
logger.trace("BlueGiga FindInfo: {} event={}", this, event);
|
||||||
updateLastSeenTime();
|
updateLastSeenTime();
|
||||||
|
|
||||||
BluetoothCharacteristic characteristic = new BluetoothCharacteristic(event.getUuid(), event.getChrHandle());
|
int handle = event.getChrHandle();
|
||||||
|
UUID attUUID = event.getUuid();
|
||||||
|
|
||||||
BluetoothService service = getServiceByHandle(characteristic.getHandle());
|
BluetoothService service = getServiceByHandle(handle);
|
||||||
if (service == null) {
|
if (service == null) {
|
||||||
logger.debug("BlueGiga: Unable to find service for handle {}", characteristic.getHandle());
|
logger.debug("BlueGiga: Unable to find service for handle {}", handle);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
handleToUUID.put(handle, attUUID);
|
||||||
|
|
||||||
|
if (BluetoothBindingConstants.ATTR_CHARACTERISTIC_DECLARATION.equals(attUUID)) {
|
||||||
|
BlueGigaBluetoothCharacteristic characteristic = new BlueGigaBluetoothCharacteristic(handle);
|
||||||
characteristic.setService(service);
|
characteristic.setService(service);
|
||||||
service.addCharacteristic(characteristic);
|
handleToCharacteristic.put(handle, characteristic);
|
||||||
|
} else {
|
||||||
|
Integer chrHandle = handleToCharacteristic.floorKey(handle);
|
||||||
|
if (chrHandle == null) {
|
||||||
|
logger.debug("BlueGiga: Unable to find characteristic for handle {}", handle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BlueGigaBluetoothCharacteristic characteristic = handleToCharacteristic.get(chrHandle);
|
||||||
|
characteristic.addDescriptor(new BluetoothDescriptor(characteristic, attUUID, handle));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleProcedureCompletedEvent(BlueGigaProcedureCompletedEvent event) {
|
private void handleProcedureCompletedEvent(BlueGigaProcedureCompletedEvent event) {
|
||||||
@@ -458,7 +579,16 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case GET_CHARACTERISTICS:
|
case GET_CHARACTERISTICS:
|
||||||
// We've downloaded all characteristics
|
// We've downloaded all attributes, now read the characteristic declarations
|
||||||
|
if (bgHandler.bgReadCharacteristicDeclarations(connection)) {
|
||||||
|
procedureTimer = startTimer(procedureTimeoutTask, TIMEOUT_SEC);
|
||||||
|
procedureProgress = BlueGigaProcedure.READ_CHARACTERISTIC_DECL;
|
||||||
|
} else {
|
||||||
|
procedureProgress = BlueGigaProcedure.NONE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case READ_CHARACTERISTIC_DECL:
|
||||||
|
// We've downloaded read all the declarations, we are done now
|
||||||
procedureProgress = BlueGigaProcedure.NONE;
|
procedureProgress = BlueGigaProcedure.NONE;
|
||||||
notifyListeners(BluetoothEventType.SERVICES_DISCOVERED);
|
notifyListeners(BluetoothEventType.SERVICES_DISCOVERED);
|
||||||
break;
|
break;
|
||||||
@@ -478,6 +608,24 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
|||||||
procedureProgress = BlueGigaProcedure.NONE;
|
procedureProgress = BlueGigaProcedure.NONE;
|
||||||
procedureCharacteristic = null;
|
procedureCharacteristic = null;
|
||||||
break;
|
break;
|
||||||
|
case NOTIFICATION_ENABLE:
|
||||||
|
boolean success = event.getResult() == BgApiResponse.SUCCESS;
|
||||||
|
if (!success) {
|
||||||
|
logger.debug("write to descriptor failed");
|
||||||
|
}
|
||||||
|
((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotificationEnabled(success);
|
||||||
|
procedureProgress = BlueGigaProcedure.NONE;
|
||||||
|
procedureCharacteristic = null;
|
||||||
|
break;
|
||||||
|
case NOTIFICATION_DISABLE:
|
||||||
|
success = event.getResult() == BgApiResponse.SUCCESS;
|
||||||
|
if (!success) {
|
||||||
|
logger.debug("write to descriptor failed");
|
||||||
|
}
|
||||||
|
((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotificationEnabled(!success);
|
||||||
|
procedureProgress = BlueGigaProcedure.NONE;
|
||||||
|
procedureCharacteristic = null;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -507,6 +655,10 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (BlueGigaBluetoothCharacteristic ch : handleToCharacteristic.values()) {
|
||||||
|
ch.setNotificationEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
cancelTimer(procedureTimer);
|
cancelTimer(procedureTimer);
|
||||||
connectionState = ConnectionState.DISCONNECTED;
|
connectionState = ConnectionState.DISCONNECTED;
|
||||||
connection = -1;
|
connection = -1;
|
||||||
@@ -524,10 +676,31 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
|||||||
|
|
||||||
updateLastSeenTime();
|
updateLastSeenTime();
|
||||||
|
|
||||||
BluetoothCharacteristic characteristic = getCharacteristicByHandle(event.getAttHandle());
|
logger.trace("BlueGiga AttributeValue: {} event={}", this, event);
|
||||||
if (characteristic == null) {
|
|
||||||
|
int handle = event.getAttHandle();
|
||||||
|
|
||||||
|
Map.Entry<Integer, BlueGigaBluetoothCharacteristic> entry = handleToCharacteristic.floorEntry(handle);
|
||||||
|
if (entry == null) {
|
||||||
logger.debug("BlueGiga didn't find characteristic for event {}", event);
|
logger.debug("BlueGiga didn't find characteristic for event {}", event);
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlueGigaBluetoothCharacteristic characteristic = entry.getValue();
|
||||||
|
|
||||||
|
if (handle == entry.getKey()) {
|
||||||
|
// this is the declaration
|
||||||
|
if (parseDeclaration(characteristic, event.getValue())) {
|
||||||
|
BluetoothService service = getServiceByHandle(handle);
|
||||||
|
if (service == null) {
|
||||||
|
logger.debug("BlueGiga: Unable to find service for handle {}", handle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
service.addCharacteristic(characteristic);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (handle == characteristic.getHandle()) {
|
||||||
characteristic.setValue(event.getValue().clone());
|
characteristic.setValue(event.getValue().clone());
|
||||||
|
|
||||||
// If this is the characteristic we were reading, then send a read completion
|
// If this is the characteristic we were reading, then send a read completion
|
||||||
@@ -537,10 +710,55 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
|||||||
procedureCharacteristic = null;
|
procedureCharacteristic = null;
|
||||||
notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic,
|
notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic,
|
||||||
BluetoothCompletionStatus.SUCCESS);
|
BluetoothCompletionStatus.SUCCESS);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify the user of the updated value
|
// Notify the user of the updated value
|
||||||
notifyListeners(BluetoothEventType.CHARACTERISTIC_UPDATED, characteristic);
|
notifyListeners(BluetoothEventType.CHARACTERISTIC_UPDATED, characteristic);
|
||||||
|
} else {
|
||||||
|
// it must be one of the descriptors we need to update
|
||||||
|
UUID attUUID = handleToUUID.get(handle);
|
||||||
|
BluetoothDescriptor descriptor = characteristic.getDescriptor(attUUID);
|
||||||
|
descriptor.setValue(toBytes(event.getValue()));
|
||||||
|
notifyListeners(BluetoothEventType.DESCRIPTOR_UPDATED, descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte @Nullable [] toBytes(int @Nullable [] value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] ret = new byte[value.length];
|
||||||
|
for (int i = 0; i < value.length; i++) {
|
||||||
|
ret[i] = (byte) value[i];
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean parseDeclaration(BlueGigaBluetoothCharacteristic ch, int[] value) {
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(toBytes(value));
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
ch.setProperties(Byte.toUnsignedInt(buffer.get()));
|
||||||
|
ch.setHandle(Short.toUnsignedInt(buffer.getShort()));
|
||||||
|
|
||||||
|
switch (buffer.remaining()) {
|
||||||
|
case 2:
|
||||||
|
long key = Short.toUnsignedLong(buffer.getShort());
|
||||||
|
ch.setUUID(BluetoothBindingConstants.createBluetoothUUID(key));
|
||||||
|
return true;
|
||||||
|
case 4:
|
||||||
|
key = Integer.toUnsignedLong(buffer.getInt());
|
||||||
|
ch.setUUID(BluetoothBindingConstants.createBluetoothUUID(key));
|
||||||
|
return true;
|
||||||
|
case 16:
|
||||||
|
long lower = buffer.getLong();
|
||||||
|
long upper = buffer.getLong();
|
||||||
|
ch.setUUID(new UUID(upper, lower));
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
logger.debug("Unexpected uuid length: {}", buffer.remaining());
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ import java.io.OutputStream;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
@@ -50,6 +53,8 @@ import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.B
|
|||||||
import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByGroupTypeResponse;
|
import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByGroupTypeResponse;
|
||||||
import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByHandleCommand;
|
import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByHandleCommand;
|
||||||
import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByHandleResponse;
|
import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByHandleResponse;
|
||||||
|
import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByTypeCommand;
|
||||||
|
import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByTypeResponse;
|
||||||
import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaConnectionStatusEvent;
|
import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaConnectionStatusEvent;
|
||||||
import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaDisconnectCommand;
|
import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaDisconnectCommand;
|
||||||
import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaDisconnectResponse;
|
import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaDisconnectResponse;
|
||||||
@@ -76,6 +81,8 @@ import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.BluetoothAddr
|
|||||||
import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.GapConnectableMode;
|
import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.GapConnectableMode;
|
||||||
import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.GapDiscoverMode;
|
import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.GapDiscoverMode;
|
||||||
import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.GapDiscoverableMode;
|
import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.GapDiscoverableMode;
|
||||||
|
import org.openhab.binding.bluetooth.util.RetryException;
|
||||||
|
import org.openhab.binding.bluetooth.util.RetryFuture;
|
||||||
import org.openhab.core.common.ThreadPoolManager;
|
import org.openhab.core.common.ThreadPoolManager;
|
||||||
import org.openhab.core.io.transport.serial.PortInUseException;
|
import org.openhab.core.io.transport.serial.PortInUseException;
|
||||||
import org.openhab.core.io.transport.serial.SerialPort;
|
import org.openhab.core.io.transport.serial.SerialPort;
|
||||||
@@ -118,9 +125,6 @@ public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGi
|
|||||||
|
|
||||||
private final ScheduledExecutorService executor = ThreadPoolManager.getScheduledPool("BlueGiga");
|
private final ScheduledExecutorService executor = ThreadPoolManager.getScheduledPool("BlueGiga");
|
||||||
|
|
||||||
// The serial port.
|
|
||||||
private Optional<SerialPort> serialPort = Optional.empty();
|
|
||||||
|
|
||||||
private BlueGigaConfiguration configuration = new BlueGigaConfiguration();
|
private BlueGigaConfiguration configuration = new BlueGigaConfiguration();
|
||||||
|
|
||||||
// The serial port input stream.
|
// The serial port input stream.
|
||||||
@@ -130,10 +134,13 @@ public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGi
|
|||||||
private Optional<OutputStream> outputStream = Optional.empty();
|
private Optional<OutputStream> outputStream = Optional.empty();
|
||||||
|
|
||||||
// The BlueGiga API handler
|
// The BlueGiga API handler
|
||||||
private Optional<BlueGigaSerialHandler> serialHandler = Optional.empty();
|
private CompletableFuture<BlueGigaSerialHandler> serialHandler = CompletableFuture
|
||||||
|
.failedFuture(new IllegalStateException("Uninitialized"));
|
||||||
|
|
||||||
// The BlueGiga transaction manager
|
// The BlueGiga transaction manager
|
||||||
private Optional<BlueGigaTransactionManager> transactionManager = Optional.empty();
|
@NonNullByDefault({})
|
||||||
|
private CompletableFuture<BlueGigaTransactionManager> transactionManager = CompletableFuture
|
||||||
|
.failedFuture(new IllegalStateException("Uninitialized"));
|
||||||
|
|
||||||
// The maximum number of connections this interface supports
|
// The maximum number of connections this interface supports
|
||||||
private int maxConnections = 0;
|
private int maxConnections = 0;
|
||||||
@@ -146,7 +153,9 @@ public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGi
|
|||||||
|
|
||||||
private volatile boolean initComplete = false;
|
private volatile boolean initComplete = false;
|
||||||
|
|
||||||
private @Nullable ScheduledFuture<?> initTask;
|
private CompletableFuture<SerialPort> serialPortFuture = CompletableFuture
|
||||||
|
.failedFuture(new IllegalStateException("Uninitialized"));
|
||||||
|
|
||||||
private @Nullable ScheduledFuture<?> removeInactiveDevicesTask;
|
private @Nullable ScheduledFuture<?> removeInactiveDevicesTask;
|
||||||
private @Nullable ScheduledFuture<?> discoveryTask;
|
private @Nullable ScheduledFuture<?> discoveryTask;
|
||||||
|
|
||||||
@@ -159,39 +168,80 @@ public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
logger.info("Initializing BlueGiga");
|
||||||
super.initialize();
|
super.initialize();
|
||||||
Optional<BlueGigaConfiguration> cfg = Optional.of(getConfigAs(BlueGigaConfiguration.class));
|
Optional<BlueGigaConfiguration> cfg = Optional.of(getConfigAs(BlueGigaConfiguration.class));
|
||||||
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
if (cfg.isPresent()) {
|
if (cfg.isPresent()) {
|
||||||
configuration = cfg.get();
|
configuration = cfg.get();
|
||||||
initTask = executor.scheduleWithFixedDelay(this::start, 0, INITIALIZATION_INTERVAL_SEC, TimeUnit.SECONDS);
|
serialPortFuture = RetryFuture.callWithRetry(() -> {
|
||||||
} else {
|
var localFuture = serialPortFuture;
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dispose() {
|
|
||||||
stop();
|
|
||||||
stopScheduledTasks();
|
|
||||||
if (initTask != null) {
|
|
||||||
initTask.cancel(true);
|
|
||||||
}
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void start() {
|
|
||||||
try {
|
|
||||||
if (!initComplete) {
|
|
||||||
logger.debug("Initialize BlueGiga");
|
logger.debug("Initialize BlueGiga");
|
||||||
logger.debug("Using configuration: {}", configuration);
|
logger.debug("Using configuration: {}", configuration);
|
||||||
stop();
|
|
||||||
if (openSerialPort(configuration.port, 115200)) {
|
|
||||||
serialHandler = Optional.of(new BlueGigaSerialHandler(inputStream.get(), outputStream.get()));
|
|
||||||
transactionManager = Optional.of(new BlueGigaTransactionManager(serialHandler.get(), executor));
|
|
||||||
serialHandler.get().addHandlerListener(this);
|
|
||||||
transactionManager.get().addEventListener(this);
|
|
||||||
updateStatus(ThingStatus.UNKNOWN);
|
|
||||||
|
|
||||||
|
String serialPortName = configuration.port;
|
||||||
|
int baudRate = 115200;
|
||||||
|
|
||||||
|
logger.debug("Connecting to serial port '{}'", serialPortName);
|
||||||
|
try {
|
||||||
|
SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(serialPortName);
|
||||||
|
if (portIdentifier == null) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Port does not exist");
|
||||||
|
throw new RetryException(INITIALIZATION_INTERVAL_SEC, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
SerialPort sp = portIdentifier.open("org.openhab.binding.bluetooth.bluegiga", 2000);
|
||||||
|
sp.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
|
||||||
|
SerialPort.PARITY_NONE);
|
||||||
|
|
||||||
|
sp.setFlowControlMode(SerialPort.FLOWCONTROL_RTSCTS_OUT);
|
||||||
|
sp.enableReceiveThreshold(1);
|
||||||
|
sp.enableReceiveTimeout(2000);
|
||||||
|
|
||||||
|
// RXTX serial port library causes high CPU load
|
||||||
|
// Start event listener, which will just sleep and slow down event loop
|
||||||
|
sp.notifyOnDataAvailable(true);
|
||||||
|
|
||||||
|
logger.info("Connected to serial port '{}'.", serialPortName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
inputStream = Optional.of(new BufferedInputStream(sp.getInputStream()));
|
||||||
|
outputStream = Optional.of(new BufferedOutputStream(sp.getOutputStream()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error getting serial streams", e);
|
||||||
|
throw new RetryException(INITIALIZATION_INTERVAL_SEC, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
// if this future has been cancelled while this was running, then we
|
||||||
|
// need to make sure that we close this port
|
||||||
|
localFuture.whenComplete((port, th) -> {
|
||||||
|
if (th != null) {
|
||||||
|
// we need to shut down the port now.
|
||||||
|
closeSerialPort(sp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return sp;
|
||||||
|
} catch (PortInUseException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
|
||||||
|
"Serial Error: Port in use");
|
||||||
|
throw new RetryException(INITIALIZATION_INTERVAL_SEC, TimeUnit.SECONDS);
|
||||||
|
} catch (UnsupportedCommOperationException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||||
|
"Serial Error: Unsupported operation");
|
||||||
|
throw new RetryException(INITIALIZATION_INTERVAL_SEC, TimeUnit.SECONDS);
|
||||||
|
} catch (RuntimeException ex) {
|
||||||
|
logger.debug("Start failed", ex);
|
||||||
|
throw new RetryException(INITIALIZATION_INTERVAL_SEC, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}, executor);
|
||||||
|
|
||||||
|
serialHandler = serialPortFuture
|
||||||
|
.thenApply(sp -> new BlueGigaSerialHandler(inputStream.get(), outputStream.get()));
|
||||||
|
transactionManager = serialHandler.thenApply(sh -> {
|
||||||
|
BlueGigaTransactionManager th = new BlueGigaTransactionManager(sh, executor);
|
||||||
|
sh.addHandlerListener(this);
|
||||||
|
th.addEventListener(this);
|
||||||
|
return th;
|
||||||
|
});
|
||||||
|
transactionManager.thenRun(() -> {
|
||||||
try {
|
try {
|
||||||
// Stop any procedures that are running
|
// Stop any procedures that are running
|
||||||
bgEndProcedure();
|
bgEndProcedure();
|
||||||
@@ -221,30 +271,43 @@ public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGi
|
|||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||||
"Initialization of BlueGiga controller failed");
|
"Initialization of BlueGiga controller failed");
|
||||||
}
|
}
|
||||||
|
}).exceptionally(th -> {
|
||||||
|
if (th instanceof CompletionException && th.getCause() instanceof CancellationException) {
|
||||||
|
// cancellation is a normal reason for failure, so no need to print it.
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
logger.warn("Error initializing bluegiga", th);
|
||||||
} catch (RuntimeException e) {
|
return null;
|
||||||
// Avoid scheduled task to shutdown
|
});
|
||||||
// e.g. when BlueGiga module is detached
|
|
||||||
logger.debug("Start failed", e);
|
} else {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
logger.info("Disposing BlueGiga");
|
||||||
|
stop();
|
||||||
|
stopScheduledTasks();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
private void stop() {
|
private void stop() {
|
||||||
if (transactionManager.isPresent()) {
|
transactionManager.thenAccept(tman -> {
|
||||||
transactionManager.get().removeEventListener(this);
|
tman.removeEventListener(this);
|
||||||
transactionManager.get().close();
|
tman.close();
|
||||||
transactionManager = Optional.empty();
|
});
|
||||||
}
|
serialHandler.thenAccept(sh -> {
|
||||||
if (serialHandler.isPresent()) {
|
sh.removeHandlerListener(this);
|
||||||
serialHandler.get().removeHandlerListener(this);
|
sh.close();
|
||||||
serialHandler.get().close();
|
});
|
||||||
serialHandler = Optional.empty();
|
|
||||||
}
|
|
||||||
address = null;
|
address = null;
|
||||||
initComplete = false;
|
initComplete = false;
|
||||||
connections.clear();
|
connections.clear();
|
||||||
closeSerialPort();
|
|
||||||
|
serialPortFuture.thenAccept(this::closeSerialPort);
|
||||||
|
serialPortFuture.cancel(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void schedulePassiveScan() {
|
private void schedulePassiveScan() {
|
||||||
@@ -268,7 +331,6 @@ public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGi
|
|||||||
|
|
||||||
private void startScheduledTasks() {
|
private void startScheduledTasks() {
|
||||||
schedulePassiveScan();
|
schedulePassiveScan();
|
||||||
logger.debug("Start scheduled task to remove inactive devices");
|
|
||||||
discoveryTask = scheduler.scheduleWithFixedDelay(this::refreshDiscoveredDevices, 0, 10, TimeUnit.SECONDS);
|
discoveryTask = scheduler.scheduleWithFixedDelay(this::refreshDiscoveredDevices, 0, 10, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,49 +371,7 @@ public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGi
|
|||||||
updateProperties(properties);
|
updateProperties(properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean openSerialPort(final String serialPortName, int baudRate) {
|
private void closeSerialPort(SerialPort sp) {
|
||||||
logger.debug("Connecting to serial port '{}'", serialPortName);
|
|
||||||
try {
|
|
||||||
SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(serialPortName);
|
|
||||||
if (portIdentifier == null) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Port does not exist");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
SerialPort sp = portIdentifier.open("org.openhab.binding.bluetooth.bluegiga", 2000);
|
|
||||||
sp.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
|
|
||||||
|
|
||||||
sp.setFlowControlMode(SerialPort.FLOWCONTROL_RTSCTS_OUT);
|
|
||||||
sp.enableReceiveThreshold(1);
|
|
||||||
sp.enableReceiveTimeout(2000);
|
|
||||||
|
|
||||||
// RXTX serial port library causes high CPU load
|
|
||||||
// Start event listener, which will just sleep and slow down event loop
|
|
||||||
sp.notifyOnDataAvailable(true);
|
|
||||||
|
|
||||||
logger.info("Connected to serial port '{}'.", serialPortName);
|
|
||||||
|
|
||||||
try {
|
|
||||||
inputStream = Optional.of(new BufferedInputStream(sp.getInputStream()));
|
|
||||||
outputStream = Optional.of(new BufferedOutputStream(sp.getOutputStream()));
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Error getting serial streams", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
serialPort = Optional.of(sp);
|
|
||||||
return true;
|
|
||||||
} catch (PortInUseException e) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
|
|
||||||
"Serial Error: Port in use");
|
|
||||||
return false;
|
|
||||||
} catch (UnsupportedCommOperationException e) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
|
||||||
"Serial Error: Unsupported operation");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void closeSerialPort() {
|
|
||||||
serialPort.ifPresent(sp -> {
|
|
||||||
sp.removeEventListener();
|
sp.removeEventListener();
|
||||||
try {
|
try {
|
||||||
sp.disableReceiveTimeout();
|
sp.disableReceiveTimeout();
|
||||||
@@ -366,11 +386,9 @@ public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGi
|
|||||||
});
|
});
|
||||||
sp.close();
|
sp.close();
|
||||||
logger.debug("Closed serial port.");
|
logger.debug("Closed serial port.");
|
||||||
serialPort = Optional.empty();
|
|
||||||
inputStream = Optional.empty();
|
inputStream = Optional.empty();
|
||||||
outputStream = Optional.empty();
|
outputStream = Optional.empty();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -528,6 +546,25 @@ public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean bgReadCharacteristicDeclarations(int connectionHandle) {
|
||||||
|
logger.debug("BlueGiga Find: connection {}", connectionHandle);
|
||||||
|
// @formatter:off
|
||||||
|
BlueGigaReadByTypeCommand command = new BlueGigaReadByTypeCommand.CommandBuilder()
|
||||||
|
.withConnection(connectionHandle)
|
||||||
|
.withStart(1)
|
||||||
|
.withEnd(65535)
|
||||||
|
.withUUID(BluetoothBindingConstants.ATTR_CHARACTERISTIC_DECLARATION)
|
||||||
|
.build();
|
||||||
|
// @formatter:on
|
||||||
|
try {
|
||||||
|
return sendCommand(command, BlueGigaReadByTypeResponse.class, true).getResult() == BgApiResponse.SUCCESS;
|
||||||
|
} catch (BlueGigaException e) {
|
||||||
|
logger.debug("Error occured when sending read characteristics command to device {}, reason: {}.", address,
|
||||||
|
e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read a characteristic using {@link BlueGigaReadByHandleCommand}
|
* Read a characteristic using {@link BlueGigaReadByHandleCommand}
|
||||||
*
|
*
|
||||||
@@ -665,8 +702,9 @@ public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGi
|
|||||||
*/
|
*/
|
||||||
private <T extends BlueGigaResponse> T sendCommandWithoutChecks(BlueGigaCommand command, Class<T> expectedResponse)
|
private <T extends BlueGigaResponse> T sendCommandWithoutChecks(BlueGigaCommand command, Class<T> expectedResponse)
|
||||||
throws BlueGigaException {
|
throws BlueGigaException {
|
||||||
if (transactionManager.isPresent()) {
|
BlueGigaTransactionManager manager = transactionManager.getNow(null);
|
||||||
return transactionManager.get().sendTransaction(command, expectedResponse, COMMAND_TIMEOUT_MS);
|
if (manager != null) {
|
||||||
|
return manager.sendTransaction(command, expectedResponse, COMMAND_TIMEOUT_MS);
|
||||||
} else {
|
} else {
|
||||||
throw new BlueGigaException("Transaction manager missing");
|
throw new BlueGigaException("Transaction manager missing");
|
||||||
}
|
}
|
||||||
@@ -678,7 +716,7 @@ public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGi
|
|||||||
* @param listener the {@link BlueGigaEventListener} to add
|
* @param listener the {@link BlueGigaEventListener} to add
|
||||||
*/
|
*/
|
||||||
public void addEventListener(BlueGigaEventListener listener) {
|
public void addEventListener(BlueGigaEventListener listener) {
|
||||||
transactionManager.ifPresent(manager -> {
|
transactionManager.thenAccept(manager -> {
|
||||||
manager.addEventListener(listener);
|
manager.addEventListener(listener);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -689,7 +727,7 @@ public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGi
|
|||||||
* @param listener the {@link BlueGigaEventListener} to remove
|
* @param listener the {@link BlueGigaEventListener} to remove
|
||||||
*/
|
*/
|
||||||
public void removeEventListener(BlueGigaEventListener listener) {
|
public void removeEventListener(BlueGigaEventListener listener) {
|
||||||
transactionManager.ifPresent(manager -> {
|
transactionManager.thenAccept(manager -> {
|
||||||
manager.removeEventListener(listener);
|
manager.removeEventListener(listener);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ public class BlueGigaSerialHandler {
|
|||||||
|
|
||||||
flush();
|
flush();
|
||||||
parserThread = createBlueGigaBLEHandler();
|
parserThread = createBlueGigaBLEHandler();
|
||||||
|
parserThread.setUncaughtExceptionHandler((t, th) -> {
|
||||||
|
logger.warn("BluegigaSerialHandler terminating due to unhandled error", th);
|
||||||
|
});
|
||||||
parserThread.setDaemon(true);
|
parserThread.setDaemon(true);
|
||||||
parserThread.start();
|
parserThread.start();
|
||||||
int tries = 0;
|
int tries = 0;
|
||||||
@@ -232,11 +235,9 @@ public class BlueGigaSerialHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Thread createBlueGigaBLEHandler() {
|
private void inboundMessageHandlerLoop() {
|
||||||
final int framecheckParams[] = new int[] { 0x00, 0x7F, 0xC0, 0xF8, 0xE0 };
|
final int[] framecheckParams = { 0x00, 0x7F, 0xC0, 0xF8, 0xE0 };
|
||||||
return new Thread("BlueGigaBLEHandler") {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
int exceptionCnt = 0;
|
int exceptionCnt = 0;
|
||||||
logger.trace("BlueGiga BLE thread started");
|
logger.trace("BlueGiga BLE thread started");
|
||||||
int[] inputBuffer = new int[BLE_MAX_LENGTH];
|
int[] inputBuffer = new int[BLE_MAX_LENGTH];
|
||||||
@@ -309,6 +310,8 @@ public class BlueGigaSerialHandler {
|
|||||||
}
|
}
|
||||||
logger.debug("BlueGiga BLE exited.");
|
logger.debug("BlueGiga BLE exited.");
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
private Thread createBlueGigaBLEHandler() {
|
||||||
|
return new Thread(this::inboundMessageHandlerLoop, "BlueGigaBLEHandler");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public class BlueGigaAttributeWriteCommand extends BlueGigaDeviceCommand {
|
|||||||
if (c > 0) {
|
if (c > 0) {
|
||||||
builder.append(' ');
|
builder.append(' ');
|
||||||
}
|
}
|
||||||
builder.append(String.format("%02X", data[c]));
|
builder.append(String.format("%02X", data[c] & 0xFF));
|
||||||
}
|
}
|
||||||
builder.append(']');
|
builder.append(']');
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
|
|||||||
@@ -56,6 +56,13 @@ public class BlueGigaReadByTypeCommand extends BlueGigaDeviceCommand {
|
|||||||
*/
|
*/
|
||||||
private UUID uuid = new UUID(0, 0);
|
private UUID uuid = new UUID(0, 0);
|
||||||
|
|
||||||
|
private BlueGigaReadByTypeCommand(CommandBuilder builder) {
|
||||||
|
this.connection = builder.connection;
|
||||||
|
this.start = builder.start;
|
||||||
|
this.end = builder.end;
|
||||||
|
this.uuid = builder.uuid;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* First attribute handle
|
* First attribute handle
|
||||||
*
|
*
|
||||||
@@ -111,4 +118,55 @@ public class BlueGigaReadByTypeCommand extends BlueGigaDeviceCommand {
|
|||||||
builder.append(']');
|
builder.append(']');
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class CommandBuilder {
|
||||||
|
private int connection;
|
||||||
|
private int start;
|
||||||
|
private int end;
|
||||||
|
private UUID uuid = new UUID(0, 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set connection handle.
|
||||||
|
*
|
||||||
|
* @param connection the connection to set as {@link int}
|
||||||
|
*/
|
||||||
|
public CommandBuilder withConnection(int connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First requested handle number
|
||||||
|
*
|
||||||
|
* @param start the start to set as {@link int}
|
||||||
|
*/
|
||||||
|
public CommandBuilder withStart(int start) {
|
||||||
|
this.start = start;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last requested handle number
|
||||||
|
*
|
||||||
|
* @param end the end to set as {@link int}
|
||||||
|
*/
|
||||||
|
public CommandBuilder withEnd(int end) {
|
||||||
|
this.end = end;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute type (UUID)
|
||||||
|
*
|
||||||
|
* @param uuid the uuid to set as {@link UUID}
|
||||||
|
*/
|
||||||
|
public CommandBuilder withUUID(UUID uuid) {
|
||||||
|
this.uuid = uuid;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlueGigaReadByTypeCommand build() {
|
||||||
|
return new BlueGigaReadByTypeCommand(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user