]> git.basschouten.com Git - openhab-addons.git/blob
4a92faf1195ac238be28993caa5f53c9c7836a59
[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.time.ZonedDateTime;
16 import java.util.ArrayList;
17 import java.util.List;
18 import java.util.concurrent.locks.ReentrantLock;
19
20 import javax.measure.quantity.Power;
21
22 import org.apache.commons.lang.StringUtils;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.bluetooth.BluetoothDevice.ConnectionState;
26 import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification;
27 import org.openhab.binding.bluetooth.notification.BluetoothScanNotification;
28 import org.openhab.core.library.types.QuantityType;
29 import org.openhab.core.library.types.StringType;
30 import org.openhab.core.library.unit.Units;
31 import org.openhab.core.thing.Bridge;
32 import org.openhab.core.thing.Channel;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.thing.binding.BaseThingHandler;
38 import org.openhab.core.thing.binding.BridgeHandler;
39 import org.openhab.core.thing.binding.builder.ChannelBuilder;
40 import org.openhab.core.thing.binding.builder.ThingBuilder;
41 import org.openhab.core.types.Command;
42 import org.openhab.core.types.RefreshType;
43 import org.openhab.core.types.UnDefType;
44
45 /**
46  * This is a handler for generic Bluetooth devices in beacon-mode (i.e. not connected), which at the same time can be
47  * used as a base implementation for more specific thing handlers.
48  *
49  * @author Kai Kreuzer - Initial contribution and API
50  */
51 @NonNullByDefault
52 public class BeaconBluetoothHandler extends BaseThingHandler implements BluetoothDeviceListener {
53
54     @NonNullByDefault({} /* non-null if initialized */)
55     protected BluetoothAdapter adapter;
56
57     @NonNullByDefault({} /* non-null if initialized */)
58     protected BluetoothAddress address;
59
60     @NonNullByDefault({} /* non-null if initialized */)
61     protected BluetoothDevice device;
62
63     protected final ReentrantLock deviceLock;
64
65     private @Nullable ZonedDateTime lastActivityTime;
66
67     public BeaconBluetoothHandler(Thing thing) {
68         super(thing);
69         deviceLock = new ReentrantLock();
70     }
71
72     @Override
73     public void initialize() {
74         try {
75             address = new BluetoothAddress(getConfig().get(BluetoothBindingConstants.CONFIGURATION_ADDRESS).toString());
76         } catch (IllegalArgumentException e) {
77             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getLocalizedMessage());
78             return;
79         }
80
81         Bridge bridge = getBridge();
82         if (bridge == null) {
83             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Not associated with any bridge");
84             return;
85         }
86
87         BridgeHandler bridgeHandler = bridge.getHandler();
88         if (!(bridgeHandler instanceof BluetoothAdapter)) {
89             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
90                     "Associated with an unsupported bridge");
91             return;
92         }
93
94         adapter = (BluetoothAdapter) bridgeHandler;
95
96         try {
97             deviceLock.lock();
98             device = adapter.getDevice(address);
99             device.addListener(this);
100         } finally {
101             deviceLock.unlock();
102         }
103
104         ThingBuilder builder = editThing();
105         boolean changed = false;
106         for (Channel channel : createDynamicChannels()) {
107             // we only want to add each channel, not replace all of them
108             if (getThing().getChannel(channel.getUID()) == null) {
109                 builder.withChannel(channel);
110                 changed = true;
111             }
112         }
113         if (changed) {
114             updateThing(builder.build());
115         }
116
117         updateStatus(ThingStatus.UNKNOWN);
118     }
119
120     private Channel buildChannel(String channelType, String itemType) {
121         return ChannelBuilder.create(new ChannelUID(getThing().getUID(), channelType), itemType).build();
122     }
123
124     protected List<Channel> createDynamicChannels() {
125         List<Channel> channels = new ArrayList<>();
126         channels.add(buildChannel(BluetoothBindingConstants.CHANNEL_TYPE_RSSI, "Number:Power"));
127         if (device instanceof DelegateBluetoothDevice) {
128             channels.add(buildChannel(BluetoothBindingConstants.CHANNEL_TYPE_ADAPTER, "String"));
129             channels.add(buildChannel(BluetoothBindingConstants.CHANNEL_TYPE_ADAPTER_LOCATION, "String"));
130         }
131         return channels;
132     }
133
134     @Override
135     public void dispose() {
136         try {
137             deviceLock.lock();
138             if (device != null) {
139                 device.removeListener(this);
140                 device.disconnect();
141                 device = null;
142             }
143         } finally {
144             deviceLock.unlock();
145         }
146     }
147
148     @Override
149     public void handleCommand(ChannelUID channelUID, Command command) {
150         if (command == RefreshType.REFRESH) {
151             switch (channelUID.getId()) {
152                 case BluetoothBindingConstants.CHANNEL_TYPE_RSSI:
153                     updateRSSI();
154                     break;
155                 case BluetoothBindingConstants.CHANNEL_TYPE_ADAPTER:
156                     updateAdapter();
157                     break;
158                 case BluetoothBindingConstants.CHANNEL_TYPE_ADAPTER_LOCATION:
159                     updateAdapterLocation();
160                     break;
161             }
162         }
163     }
164
165     /**
166      * Updates the RSSI channel and the Thing status according to the new received rssi value
167      */
168     protected void updateRSSI() {
169         if (device != null) {
170             updateRSSI(device.getRssi());
171         }
172     }
173
174     private void updateRSSI(@Nullable Integer rssi) {
175         if (rssi != null && rssi != 0) {
176             QuantityType<Power> quantity = new QuantityType<>(rssi, Units.DECIBEL_MILLIWATTS);
177             updateState(BluetoothBindingConstants.CHANNEL_TYPE_RSSI, quantity);
178             updateStatusBasedOnRssi(true);
179         } else {
180             updateState(BluetoothBindingConstants.CHANNEL_TYPE_RSSI, UnDefType.NULL);
181             updateStatusBasedOnRssi(false);
182         }
183     }
184
185     protected void updateAdapter() {
186         if (device != null) {
187             BluetoothAdapter adapter = device.getAdapter();
188             updateState(BluetoothBindingConstants.CHANNEL_TYPE_ADAPTER, new StringType(adapter.getUID().getId()));
189         }
190     }
191
192     protected void updateAdapterLocation() {
193         if (device != null) {
194             BluetoothAdapter adapter = device.getAdapter();
195             String location = adapter.getLocation();
196             if (location != null || StringUtils.isBlank(location)) {
197                 updateState(BluetoothBindingConstants.CHANNEL_TYPE_ADAPTER_LOCATION, new StringType(location));
198             } else {
199                 updateState(BluetoothBindingConstants.CHANNEL_TYPE_ADAPTER_LOCATION, UnDefType.NULL);
200             }
201         }
202     }
203
204     /**
205      * This method sets the Thing status based on whether or not we can receive a signal from it.
206      * This is the best logic for beacons, but connected devices might want to deactivate this by overriding the method.
207      *
208      * @param receivedSignal true, if the device is in reach
209      */
210     protected void updateStatusBasedOnRssi(boolean receivedSignal) {
211         if (receivedSignal) {
212             updateStatus(ThingStatus.ONLINE);
213         } else {
214             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
215         }
216     }
217
218     private void onActivity() {
219         this.lastActivityTime = ZonedDateTime.now();
220     }
221
222     @Override
223     public void onScanRecordReceived(BluetoothScanNotification scanNotification) {
224         onActivity();
225         int rssi = scanNotification.getRssi();
226         if (rssi != Integer.MIN_VALUE) {
227             updateRSSI(rssi);
228         }
229     }
230
231     @Override
232     public void onConnectionStateChange(BluetoothConnectionStatusNotification connectionNotification) {
233         // a disconnection doesn't count as activity
234         if (connectionNotification.getConnectionState() != ConnectionState.DISCONNECTED) {
235             onActivity();
236         }
237     }
238
239     @Override
240     public void onServicesDiscovered() {
241         onActivity();
242     }
243
244     @Override
245     public void onCharacteristicReadComplete(BluetoothCharacteristic characteristic, BluetoothCompletionStatus status) {
246         if (status == BluetoothCompletionStatus.SUCCESS) {
247             onActivity();
248         }
249     }
250
251     @Override
252     public void onCharacteristicWriteComplete(BluetoothCharacteristic characteristic,
253             BluetoothCompletionStatus status) {
254         if (status == BluetoothCompletionStatus.SUCCESS) {
255             onActivity();
256         }
257     }
258
259     @Override
260     public void onCharacteristicUpdate(BluetoothCharacteristic characteristic) {
261         onActivity();
262     }
263
264     @Override
265     public void onDescriptorUpdate(BluetoothDescriptor bluetoothDescriptor) {
266         onActivity();
267     }
268
269     @Override
270     public void onAdapterChanged(BluetoothAdapter adapter) {
271         updateAdapter();
272         updateAdapterLocation();
273     }
274 }