2 * Copyright (c) 2010-2020 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;
16 import java.util.concurrent.CopyOnWriteArraySet;
17 import java.util.concurrent.ScheduledFuture;
18 import java.util.concurrent.TimeUnit;
20 import org.eclipse.jdt.annotation.DefaultLocation;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.openhab.binding.bluetooth.BluetoothDevice.ConnectionState;
23 import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification;
24 import org.openhab.core.thing.ChannelUID;
25 import org.openhab.core.thing.Thing;
26 import org.openhab.core.thing.ThingStatus;
27 import org.openhab.core.thing.ThingStatusDetail;
28 import org.openhab.core.types.Command;
29 import org.openhab.core.types.RefreshType;
30 import org.openhab.core.util.HexUtils;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
35 * This is a base implementation for more specific thing handlers that require constant connection to bluetooth devices.
37 * @author Kai Kreuzer - Initial contribution and API
39 @NonNullByDefault({ DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.ARRAY_CONTENTS,
40 DefaultLocation.TYPE_ARGUMENT, DefaultLocation.TYPE_BOUND, DefaultLocation.TYPE_PARAMETER })
41 public class ConnectedBluetoothHandler extends BeaconBluetoothHandler {
43 private final Logger logger = LoggerFactory.getLogger(ConnectedBluetoothHandler.class);
44 private ScheduledFuture<?> connectionJob;
46 // internal flag for the service resolution status
47 protected volatile boolean resolved = false;
49 protected final Set<BluetoothCharacteristic> deviceCharacteristics = new CopyOnWriteArraySet<>();
51 public ConnectedBluetoothHandler(Thing thing) {
56 public void initialize() {
59 connectionJob = scheduler.scheduleWithFixedDelay(() -> {
61 if (device.getConnectionState() != ConnectionState.CONNECTED) {
63 // we do not set the Thing status here, because we will anyhow receive a call to
64 // onConnectionStateChange
66 // just in case it was already connected to begin with
67 updateStatus(ThingStatus.ONLINE);
68 if (!resolved && !device.discoverServices()) {
69 logger.debug("Error while discovering services");
72 } catch (RuntimeException ex) {
73 logger.warn("Unexpected error occurred", ex);
75 }, 0, 30, TimeUnit.SECONDS);
79 public void dispose() {
80 if (connectionJob != null) {
81 connectionJob.cancel(true);
88 public void handleCommand(ChannelUID channelUID, Command command) {
89 super.handleCommand(channelUID, command);
92 if (command == RefreshType.REFRESH) {
93 for (BluetoothCharacteristic characteristic : deviceCharacteristics) {
94 if (characteristic.getGattCharacteristic() != null
95 && channelUID.getId().equals(characteristic.getGattCharacteristic().name())) {
96 device.readCharacteristic(characteristic);
104 public void channelLinked(ChannelUID channelUID) {
105 super.channelLinked(channelUID);
109 protected void updateStatusBasedOnRssi(boolean receivedSignal) {
110 // if there is no signal, we can be sure we are OFFLINE, but if there is a signal, we also have to check whether
112 if (receivedSignal) {
113 if (device.getConnectionState() == ConnectionState.CONNECTED) {
114 updateStatus(ThingStatus.ONLINE);
116 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Device is not connected.");
119 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
124 public void onConnectionStateChange(BluetoothConnectionStatusNotification connectionNotification) {
125 super.onConnectionStateChange(connectionNotification);
126 switch (connectionNotification.getConnectionState()) {
128 // The device is now known on the Bluetooth network, so we can do something...
129 scheduler.submit(() -> {
130 if (device.getConnectionState() != ConnectionState.CONNECTED) {
131 if (!device.connect()) {
132 logger.debug("Error connecting to device after discovery.");
138 updateStatus(ThingStatus.ONLINE);
139 scheduler.submit(() -> {
140 if (!resolved && !device.discoverServices()) {
141 logger.debug("Error while discovering services");
146 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
154 public void onServicesDiscovered() {
155 super.onServicesDiscovered();
158 logger.debug("Service discovery completed for '{}'", address);
163 public void onCharacteristicReadComplete(BluetoothCharacteristic characteristic, BluetoothCompletionStatus status) {
164 super.onCharacteristicReadComplete(characteristic, status);
165 if (status == BluetoothCompletionStatus.SUCCESS) {
166 if (logger.isDebugEnabled()) {
167 logger.debug("Characteristic {} from {} has been read - value {}", characteristic.getUuid(), address,
168 HexUtils.bytesToHex(characteristic.getByteValue()));
171 logger.debug("Characteristic {} from {} has been read - ERROR", characteristic.getUuid(), address);
176 public void onCharacteristicWriteComplete(BluetoothCharacteristic characteristic,
177 BluetoothCompletionStatus status) {
178 super.onCharacteristicWriteComplete(characteristic, status);
179 if (logger.isDebugEnabled()) {
180 logger.debug("Wrote {} to characteristic {} of device {}: {}",
181 HexUtils.bytesToHex(characteristic.getByteValue()), characteristic.getUuid(), address, status);
186 public void onCharacteristicUpdate(BluetoothCharacteristic characteristic) {
187 super.onCharacteristicUpdate(characteristic);
188 if (logger.isDebugEnabled()) {
189 logger.debug("Recieved update {} to characteristic {} of device {}",
190 HexUtils.bytesToHex(characteristic.getByteValue()), characteristic.getUuid(), address);
195 public void onDescriptorUpdate(BluetoothDescriptor descriptor) {
196 super.onDescriptorUpdate(descriptor);
197 if (logger.isDebugEnabled()) {
198 logger.debug("Received update {} to descriptor {} of device {}", HexUtils.bytesToHex(descriptor.getValue()),
199 descriptor.getUuid(), address);