Skip to content

Commit

Permalink
[bluetooth] Refactor and unify BluetoothAdapter implementation logic (o…
Browse files Browse the repository at this point in the history
…penhab#7129)

* Refactor and unify BluetoothAdapter implementation logic

Signed-off-by: Connor Petty <[email protected]>
  • Loading branch information
cpmeister authored and J-N-K committed Jul 14, 2020
1 parent 4f127a6 commit 329ec08
Show file tree
Hide file tree
Showing 16 changed files with 366 additions and 377 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
*/
package org.openhab.binding.bluetooth.bluegiga;

import java.time.ZonedDateTime;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
Expand Down Expand Up @@ -81,8 +80,6 @@ private enum BlueGigaProcedure {
// The connection handle if the device is connected
private int connection = -1;

private ZonedDateTime lastSeenTime = ZonedDateTime.now();

private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool("bluetooth");

private @Nullable ScheduledFuture<?> connectTimer;
Expand Down Expand Up @@ -524,6 +521,7 @@ private void handleAttributeValueEvent(BlueGigaAttributeValueEvent event) {
/**
* Clean up and release memory.
*/
@Override
public void dispose() {
if (connectionState == ConnectionState.CONNECTED) {
disconnect();
Expand All @@ -536,19 +534,6 @@ public void dispose() {
connection = -1;
}

/**
* Return last seen Time
*
* @return last seen Time
*/
public ZonedDateTime getLastSeenTime() {
return lastSeenTime;
}

private void updateLastSeenTime() {
this.lastSeenTime = ZonedDateTime.now();
}

private void cancelTimer(@Nullable ScheduledFuture<?> task) {
if (task != null) {
task.cancel(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,10 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.ZonedDateTime;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
Expand All @@ -34,25 +31,17 @@
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.common.ThreadPoolManager;
import org.eclipse.smarthome.core.thing.Bridge;
import org.eclipse.smarthome.core.thing.ChannelUID;
import org.eclipse.smarthome.core.thing.Thing;
import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.thing.ThingStatusDetail;
import org.eclipse.smarthome.core.thing.ThingUID;
import org.eclipse.smarthome.core.thing.binding.BaseBridgeHandler;
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.io.transport.serial.PortInUseException;
import org.eclipse.smarthome.io.transport.serial.SerialPort;
import org.eclipse.smarthome.io.transport.serial.SerialPortIdentifier;
import org.eclipse.smarthome.io.transport.serial.SerialPortManager;
import org.eclipse.smarthome.io.transport.serial.UnsupportedCommOperationException;
import org.openhab.binding.bluetooth.BluetoothAdapter;
import org.openhab.binding.bluetooth.AbstractBluetoothBridgeHandler;
import org.openhab.binding.bluetooth.BluetoothAddress;
import org.openhab.binding.bluetooth.BluetoothBindingConstants;
import org.openhab.binding.bluetooth.BluetoothDevice;
import org.openhab.binding.bluetooth.BluetoothDevice.ConnectionState;
import org.openhab.binding.bluetooth.BluetoothDeviceListener;
import org.openhab.binding.bluetooth.BluetoothDiscoveryListener;
import org.openhab.binding.bluetooth.bluegiga.BlueGigaAdapterConstants;
import org.openhab.binding.bluetooth.bluegiga.BlueGigaBluetoothDevice;
import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaCommand;
Expand Down Expand Up @@ -117,8 +106,8 @@
* @author Pauli Anttila - Many improvements
*/
@NonNullByDefault
public class BlueGigaBridgeHandler extends BaseBridgeHandler
implements BluetoothAdapter, BlueGigaEventListener, BlueGigaHandlerListener {
public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGigaBluetoothDevice>
implements BlueGigaEventListener, BlueGigaHandlerListener {

private final Logger logger = LoggerFactory.getLogger(BlueGigaBridgeHandler.class);

Expand Down Expand Up @@ -159,40 +148,22 @@ public class BlueGigaBridgeHandler extends BaseBridgeHandler
// Map of open connections
private final Map<Integer, BluetoothAddress> connections = new ConcurrentHashMap<>();

// Set of discovery listeners
protected final Set<BluetoothDiscoveryListener> discoveryListeners = new CopyOnWriteArraySet<>();

// List of device listeners
protected final ConcurrentHashMap<BluetoothAddress, BluetoothDeviceListener> deviceListeners = new ConcurrentHashMap<>();

private volatile boolean initComplete = false;

private @Nullable ScheduledFuture<?> initTask;
private @Nullable ScheduledFuture<?> removeInactiveDevicesTask;
private @Nullable ScheduledFuture<?> discoveryTask;

private volatile boolean activeScanEnabled = false;

private @Nullable Future<?> passiveScanIdleTimer;

public BlueGigaBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
super(bridge);
this.serialPortManager = serialPortManager;
}

@Override
public ThingUID getUID() {
// being a BluetoothAdapter, we use the UID of our bridge
return getThing().getUID();
}

@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// No commands supported for the bridge
}

@Override
public void initialize() {
super.initialize();
Optional<BlueGigaConfiguration> cfg = Optional.of(getConfigAs(BlueGigaConfiguration.class));
if (cfg.isPresent()) {
configuration = cfg.get();
Expand All @@ -204,15 +175,20 @@ public void initialize() {

@Override
public void dispose() {
stop(true);
stop();
stopScheduledTasks();
if (initTask != null) {
initTask.cancel(true);
}
super.dispose();
}

private void start() {
try {
if (!initComplete) {
logger.debug("Initialize BlueGiga");
logger.debug("Using configuration: {}", configuration);
stop(false);
stop();
if (openSerialPort(configuration.port, 115200)) {
serialHandler = Optional.of(new BlueGigaSerialHandler(inputStream.get(), outputStream.get()));
transactionManager = Optional.of(new BlueGigaTransactionManager(serialHandler.get(), executor));
Expand Down Expand Up @@ -258,7 +234,7 @@ private void start() {
}
}

private void stop(boolean exit) {
private void stop() {
if (transactionManager.isPresent()) {
transactionManager.get().removeEventListener(this);
transactionManager.get().close();
Expand All @@ -273,17 +249,6 @@ private void stop(boolean exit) {
initComplete = false;
connections.clear();
closeSerialPort();

if (exit) {
stopScheduledTasks();
if (initTask != null) {
initTask.cancel(true);
}
devices.forEach((address, device) -> {
device.dispose();
});
devices.clear();
}
}

private void schedulePassiveScan() {
Expand All @@ -308,8 +273,6 @@ private void cancelScheduledPassiveScan() {
private void startScheduledTasks() {
schedulePassiveScan();
logger.debug("Start scheduled task to remove inactive devices");
removeInactiveDevicesTask = scheduler.scheduleWithFixedDelay(this::removeInactiveDevices, 1, 1,
TimeUnit.MINUTES);
discoveryTask = scheduler.scheduleWithFixedDelay(this::refreshDiscoveredDevices, 0, 10, TimeUnit.SECONDS);
}

Expand All @@ -325,38 +288,6 @@ private void stopScheduledTasks() {
}
}

private void removeInactiveDevices() {
logger.debug("Check inactive devices, count {}", devices.size());
devices.forEach((address, device) -> {
if (shouldRemove(device)) {
logger.debug("Removing device '{}' due to inactivity, last seen: {}", address,
device.getLastSeenTime());
device.dispose();
devices.remove(address);
}
});
}

private void refreshDiscoveredDevices() {
logger.debug("Refreshing Bluetooth device list...");
devices.forEach((address, device) -> {
deviceDiscovered(device);
});
}

private boolean shouldRemove(BlueGigaBluetoothDevice device) {
// we can't remove devices with listeners since that means they have a handler.
if (device.hasListeners()) {
return false;
}
// devices that are connected won't receive any scan notifications so we can't remove them for being idle
if (device.getConnectionState() == ConnectionState.CONNECTED) {
return false;
}

return device.getLastSeenTime().plusMinutes(5).isBefore(ZonedDateTime.now());
}

private BlueGigaGetConnectionsResponse readMaxConnections() throws BlueGigaException {
return sendCommandWithoutChecks(new BlueGigaGetConnectionsCommand(), BlueGigaGetConnectionsResponse.class);
}
Expand Down Expand Up @@ -448,24 +379,20 @@ private void closeSerialPort() {

@Override
public void scanStart() {
super.scanStart();
logger.debug("Start active scan");
activeScanEnabled = true;
// Stop the passive scan
cancelScheduledPassiveScan();
bgEndProcedure();

// Start a active scan
bgStartScanning(true, configuration.activeScanInterval, configuration.activeScanWindow);

for (BluetoothDevice device : devices.values()) {
deviceDiscovered(device);
}
}

@Override
public void scanStop() {
super.scanStop();
logger.debug("Stop active scan");
activeScanEnabled = false;

// Stop the active scan
bgEndProcedure();
Expand All @@ -484,21 +411,9 @@ public BluetoothAddress getAddress() {
}
}

@SuppressWarnings({ "null", "unused" })
@Override
public BluetoothDevice getDevice(BluetoothAddress address) {
BlueGigaBluetoothDevice device = devices.get(address);
if (device == null) {
// This method always needs to return a device, even if we don't currently know about it.
device = new BlueGigaBluetoothDevice(this, address, BluetoothAddressType.UNKNOWN);
devices.put(address, device);
}
return device;
}

@Override
public boolean hasDevice(BluetoothAddress address) {
return devices.containsKey(address);
protected BlueGigaBluetoothDevice createDevice(BluetoothAddress address) {
return new BlueGigaBluetoothDevice(this, address, BluetoothAddressType.UNKNOWN);
}

/**
Expand Down Expand Up @@ -566,17 +481,6 @@ public boolean bgDisconnect(int connectionHandle) {
}
}

/**
* Device discovered. This simply passes the discover information to the discovery service for processing.
*/
public void deviceDiscovered(BluetoothDevice device) {
if (configuration.discovery || activeScanEnabled) {
for (BluetoothDiscoveryListener listener : discoveryListeners) {
listener.deviceDiscovered(device);
}
}
}

/**
* Start a read of all primary services using {@link BlueGigaReadByGroupTypeCommand}
*
Expand Down Expand Up @@ -795,16 +699,6 @@ public void removeEventListener(BlueGigaEventListener listener) {
});
}

@Override
public void addDiscoveryListener(BluetoothDiscoveryListener listener) {
discoveryListeners.add(listener);
}

@Override
public void removeDiscoveryListener(@Nullable BluetoothDiscoveryListener listener) {
discoveryListeners.remove(listener);
}

@Override
public void bluegigaEventReceived(@Nullable BlueGigaResponse event) {
if (event instanceof BlueGigaScanResponseEvent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
package org.openhab.binding.bluetooth.bluegiga.internal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.bluetooth.BaseBluetoothBridgeHandlerConfiguration;

/**
* Configuration class for {@link BlueGigaConfiguration} device.
*
* @author Pauli Anttila - Initial contribution
*/
@NonNullByDefault
public class BlueGigaConfiguration {
public boolean discovery;
public class BlueGigaConfiguration extends BaseBluetoothBridgeHandlerConfiguration {
public String port = "";
public int passiveScanIdleTime;
public int passiveScanInterval;
Expand All @@ -39,7 +39,7 @@ public String toString() {
"[discovery=%b, port=%s, passiveScanIdleTime=%d, passiveScanInterval=%d, passiveScanWindow=%d"
+ ", activeScanInterval=%d, activeScanWindow=%d, connIntervalMin=%d, connIntervalMax=%d"
+ ", connLatency=%d, connTimeout=%d]",
discovery, port, passiveScanIdleTime, passiveScanInterval, passiveScanWindow, activeScanInterval,
activeScanWindow, connIntervalMin, connIntervalMax, connLatency, connTimeout);
backgroundDiscovery, port, passiveScanIdleTime, passiveScanInterval, passiveScanWindow,
activeScanInterval, activeScanWindow, connIntervalMin, connIntervalMax, connLatency, connTimeout);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,24 @@
<context>serial-port</context>
<description>Serial Port</description>
</parameter>
<parameter name="discovery" type="boolean">
<label>Device Discovery</label>
<description>Whether this adapter actively participates in Bluetooth device discovery</description>
<parameter name="backgroundDiscovery" type="boolean">
<label>Background Discovery</label>
<description>Whether this adapter performs background discovery of Bluetooth devices</description>
<advanced>true</advanced>
<default>false</default>
</parameter>
<parameter name="inactiveDeviceCleanupInterval" type="integer" min="1" unit="s">
<label>Device Cleanup Interval</label>
<description>How often device cleanup is performed</description>
<advanced>true</advanced>
<default>60</default>
</parameter>
<parameter name="inactiveDeviceCleanupThreshold" type="integer" min="1" unit="s">
<label>Device Cleanup Threshold</label>
<description>Timespan a device can remain radio silent before it is eligible for cleanup</description>
<advanced>true</advanced>
<default>300</default>
</parameter>
<parameter name="passiveScanIdleTime" type="integer" min="100" max="60000">
<label>Passive Scan Idle Time</label>
<description>Passive scan idle time defines the time how long to wait in milliseconds before start passive scan.</description>
Expand Down
Loading

0 comments on commit 329ec08

Please sign in to comment.