]> git.basschouten.com Git - openhab-addons.git/blob
f4911c06ae0519258f9eb70e3f06364ff4315c6b
[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.SmartHomeUnits;
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         for (Channel channel : createDynamicChannels()) {
106             // we only want to add each channel, not replace all of them
107             builder.withChannel(channel);
108         }
109         updateThing(builder.build());
110
111         updateStatus(ThingStatus.UNKNOWN);
112     }
113
114     private Channel buildChannel(String channelType, String itemType) {
115         return ChannelBuilder.create(new ChannelUID(getThing().getUID(), channelType), itemType).build();
116     }
117
118     protected List<Channel> createDynamicChannels() {
119         List<Channel> channels = new ArrayList<>();
120         channels.add(buildChannel(BluetoothBindingConstants.CHANNEL_TYPE_RSSI, "Number:Power"));
121         if (device instanceof DelegateBluetoothDevice) {
122             channels.add(buildChannel(BluetoothBindingConstants.CHANNEL_TYPE_ADAPTER, "String"));
123             channels.add(buildChannel(BluetoothBindingConstants.CHANNEL_TYPE_ADAPTER_LOCATION, "String"));
124         }
125         return channels;
126     }
127
128     @Override
129     public void dispose() {
130         try {
131             deviceLock.lock();
132             if (device != null) {
133                 device.removeListener(this);
134                 device.disconnect();
135                 device = null;
136             }
137         } finally {
138             deviceLock.unlock();
139         }
140     }
141
142     @Override
143     public void handleCommand(ChannelUID channelUID, Command command) {
144         if (command == RefreshType.REFRESH) {
145             switch (channelUID.getId()) {
146                 case BluetoothBindingConstants.CHANNEL_TYPE_RSSI:
147                     updateRSSI();
148                     break;
149                 case BluetoothBindingConstants.CHANNEL_TYPE_ADAPTER:
150                     updateAdapter();
151                     break;
152                 case BluetoothBindingConstants.CHANNEL_TYPE_ADAPTER_LOCATION:
153                     updateAdapterLocation();
154                     break;
155             }
156         }
157     }
158
159     /**
160      * Updates the RSSI channel and the Thing status according to the new received rssi value
161      */
162     protected void updateRSSI() {
163         if (device != null) {
164             updateRSSI(device.getRssi());
165         }
166     }
167
168     private void updateRSSI(@Nullable Integer rssi) {
169         if (rssi != null && rssi != 0) {
170             QuantityType<Power> quantity = new QuantityType<>(rssi, SmartHomeUnits.DECIBEL_MILLIWATTS);
171             updateState(BluetoothBindingConstants.CHANNEL_TYPE_RSSI, quantity);
172             updateStatusBasedOnRssi(true);
173         } else {
174             updateState(BluetoothBindingConstants.CHANNEL_TYPE_RSSI, UnDefType.NULL);
175             updateStatusBasedOnRssi(false);
176         }
177     }
178
179     protected void updateAdapter() {
180         if (device != null) {
181             BluetoothAdapter adapter = device.getAdapter();
182             updateState(BluetoothBindingConstants.CHANNEL_TYPE_ADAPTER, new StringType(adapter.getUID().getId()));
183         }
184     }
185
186     protected void updateAdapterLocation() {
187         if (device != null) {
188             BluetoothAdapter adapter = device.getAdapter();
189             String location = adapter.getLocation();
190             if (location != null || StringUtils.isBlank(location)) {
191                 updateState(BluetoothBindingConstants.CHANNEL_TYPE_ADAPTER_LOCATION, new StringType(location));
192             } else {
193                 updateState(BluetoothBindingConstants.CHANNEL_TYPE_ADAPTER_LOCATION, UnDefType.NULL);
194             }
195         }
196     }
197
198     /**
199      * This method sets the Thing status based on whether or not we can receive a signal from it.
200      * This is the best logic for beacons, but connected devices might want to deactivate this by overriding the method.
201      *
202      * @param receivedSignal true, if the device is in reach
203      */
204     protected void updateStatusBasedOnRssi(boolean receivedSignal) {
205         if (receivedSignal) {
206             updateStatus(ThingStatus.ONLINE);
207         } else {
208             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
209         }
210     }
211
212     private void onActivity() {
213         this.lastActivityTime = ZonedDateTime.now();
214     }
215
216     @Override
217     public void onScanRecordReceived(BluetoothScanNotification scanNotification) {
218         onActivity();
219         int rssi = scanNotification.getRssi();
220         if (rssi != Integer.MIN_VALUE) {
221             updateRSSI(rssi);
222         }
223     }
224
225     @Override
226     public void onConnectionStateChange(BluetoothConnectionStatusNotification connectionNotification) {
227         // a disconnection doesn't count as activity
228         if (connectionNotification.getConnectionState() != ConnectionState.DISCONNECTED) {
229             onActivity();
230         }
231     }
232
233     @Override
234     public void onServicesDiscovered() {
235         onActivity();
236     }
237
238     @Override
239     public void onCharacteristicReadComplete(BluetoothCharacteristic characteristic, BluetoothCompletionStatus status) {
240         if (status == BluetoothCompletionStatus.SUCCESS) {
241             onActivity();
242         }
243     }
244
245     @Override
246     public void onCharacteristicWriteComplete(BluetoothCharacteristic characteristic,
247             BluetoothCompletionStatus status) {
248         if (status == BluetoothCompletionStatus.SUCCESS) {
249             onActivity();
250         }
251     }
252
253     @Override
254     public void onCharacteristicUpdate(BluetoothCharacteristic characteristic) {
255         onActivity();
256     }
257
258     @Override
259     public void onDescriptorUpdate(BluetoothDescriptor bluetoothDescriptor) {
260         onActivity();
261     }
262
263     @Override
264     public void onAdapterChanged(BluetoothAdapter adapter) {
265         updateAdapter();
266         updateAdapterLocation();
267     }
268 }