]> git.basschouten.com Git - openhab-addons.git/blob
51b30d8150ec8e9b85fc1bac86ad4d1a0fe12e6e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.airthings.internal;
14
15 import java.util.Optional;
16 import java.util.UUID;
17 import java.util.concurrent.ScheduledFuture;
18 import java.util.concurrent.TimeUnit;
19 import java.util.concurrent.atomic.AtomicInteger;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.bluetooth.BeaconBluetoothHandler;
24 import org.openhab.binding.bluetooth.BluetoothCharacteristic;
25 import org.openhab.binding.bluetooth.BluetoothDevice.ConnectionState;
26 import org.openhab.binding.bluetooth.BluetoothUtils;
27 import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification;
28 import org.openhab.core.thing.Thing;
29 import org.openhab.core.thing.ThingStatus;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 /**
34  * The {@link AbstractAirthingsHandler} is responsible for handling commands, which are
35  * sent to one of the channels.
36  *
37  * @author Pauli Anttila - Initial contribution
38  * @author Kai Kreuzer - Added Airthings Wave Mini support
39  */
40 @NonNullByDefault
41 abstract public class AbstractAirthingsHandler extends BeaconBluetoothHandler {
42
43     private static final int CHECK_PERIOD_SEC = 10;
44
45     private final Logger logger = LoggerFactory.getLogger(AbstractAirthingsHandler.class);
46
47     private AtomicInteger sinceLastReadSec = new AtomicInteger();
48     private Optional<AirthingsConfiguration> configuration = Optional.empty();
49     private @Nullable ScheduledFuture<?> scheduledTask;
50
51     private volatile int refreshInterval;
52
53     private volatile ServiceState serviceState = ServiceState.NOT_RESOLVED;
54     private volatile ReadState readState = ReadState.IDLE;
55
56     private enum ServiceState {
57         NOT_RESOLVED,
58         RESOLVING,
59         RESOLVED,
60     }
61
62     private enum ReadState {
63         IDLE,
64         READING,
65     }
66
67     public AbstractAirthingsHandler(Thing thing) {
68         super(thing);
69     }
70
71     @Override
72     public void initialize() {
73         logger.debug("Initialize");
74         super.initialize();
75         configuration = Optional.of(getConfigAs(AirthingsConfiguration.class));
76         logger.debug("Using configuration: {}", configuration.get());
77         cancelScheduledTask();
78         configuration.ifPresent(cfg -> {
79             refreshInterval = cfg.refreshInterval;
80             logger.debug("Start scheduled task to read device in every {} seconds", refreshInterval);
81             scheduledTask = scheduler.scheduleWithFixedDelay(this::executePeridioc, CHECK_PERIOD_SEC, CHECK_PERIOD_SEC,
82                     TimeUnit.SECONDS);
83         });
84         sinceLastReadSec.set(refreshInterval); // update immediately
85     }
86
87     @Override
88     public void dispose() {
89         logger.debug("Dispose");
90         cancelScheduledTask();
91         serviceState = ServiceState.NOT_RESOLVED;
92         readState = ReadState.IDLE;
93         super.dispose();
94     }
95
96     private void cancelScheduledTask() {
97         if (scheduledTask != null) {
98             scheduledTask.cancel(true);
99             scheduledTask = null;
100         }
101     }
102
103     private void executePeridioc() {
104         sinceLastReadSec.addAndGet(CHECK_PERIOD_SEC);
105         execute();
106     }
107
108     private synchronized void execute() {
109         ConnectionState connectionState = device.getConnectionState();
110         logger.debug("Device {} state is {}, serviceState {}, readState {}", address, connectionState, serviceState,
111                 readState);
112
113         switch (connectionState) {
114             case DISCOVERING:
115             case DISCOVERED:
116             case DISCONNECTED:
117                 if (isTimeToRead()) {
118                     connect();
119                 }
120                 break;
121             case CONNECTED:
122                 read();
123                 break;
124             default:
125                 break;
126         }
127     }
128
129     private void connect() {
130         logger.debug("Connect to device {}...", address);
131         if (!device.connect()) {
132             logger.debug("Connecting to device {} failed", address);
133         }
134     }
135
136     private void disconnect() {
137         logger.debug("Disconnect from device {}...", address);
138         if (!device.disconnect()) {
139             logger.debug("Disconnect from device {} failed", address);
140         }
141     }
142
143     private void read() {
144         switch (serviceState) {
145             case NOT_RESOLVED:
146                 discoverServices();
147                 break;
148             case RESOLVED:
149                 switch (readState) {
150                     case IDLE:
151                         logger.debug("Read data from device {}...", address);
152                         BluetoothCharacteristic characteristic = device.getCharacteristic(getDataUUID());
153                         if (characteristic != null) {
154                             readState = ReadState.READING;
155                             device.readCharacteristic(characteristic).whenComplete((data, ex) -> {
156                                 try {
157                                     logger.debug("Characteristic {} from device {}: {}", characteristic.getUuid(),
158                                             address, data);
159                                     updateStatus(ThingStatus.ONLINE);
160                                     sinceLastReadSec.set(0);
161                                     updateChannels(BluetoothUtils.toIntArray(data));
162                                 } finally {
163                                     readState = ReadState.IDLE;
164                                     disconnect();
165                                 }
166                             });
167                         } else {
168                             logger.debug("Read data from device {} failed", address);
169                             disconnect();
170                         }
171                         break;
172                     default:
173                         break;
174                 }
175             default:
176                 break;
177         }
178     }
179
180     private void discoverServices() {
181         logger.debug("Discover services for device {}", address);
182         serviceState = ServiceState.RESOLVING;
183         device.discoverServices();
184     }
185
186     @Override
187     public void onServicesDiscovered() {
188         serviceState = ServiceState.RESOLVED;
189         logger.debug("Service discovery completed for device {}", address);
190         printServices();
191         execute();
192     }
193
194     private void printServices() {
195         device.getServices().forEach(service -> logger.debug("Device {} Service '{}'", address, service));
196     }
197
198     @Override
199     public void onConnectionStateChange(BluetoothConnectionStatusNotification connectionNotification) {
200         switch (connectionNotification.getConnectionState()) {
201             case DISCONNECTED:
202                 if (serviceState == ServiceState.RESOLVING) {
203                     serviceState = ServiceState.NOT_RESOLVED;
204                 }
205                 readState = ReadState.IDLE;
206                 break;
207             default:
208                 break;
209
210         }
211         execute();
212     }
213
214     private boolean isTimeToRead() {
215         int sinceLastRead = sinceLastReadSec.get();
216         logger.debug("Time since last update: {} sec", sinceLastRead);
217         return sinceLastRead >= refreshInterval;
218     }
219
220     /**
221      * Provides the UUID of the characteristic, which holds the sensor data
222      *
223      * @return the UUID of the data characteristic
224      */
225     protected abstract UUID getDataUUID();
226
227     /**
228      * This method parses the content of the bluetooth characteristic and updates the Thing channels accordingly.
229      *
230      * @param is the content of the bluetooth characteristic
231      */
232     abstract protected void updateChannels(int[] is);
233 }