]> git.basschouten.com Git - openhab-addons.git/blob
f31aa533676f8f2eb65c010c8c72f5ba685abc91
[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 static org.openhab.binding.bluetooth.airthings.internal.AirthingsBindingConstants.*;
16
17 import java.util.Optional;
18 import java.util.UUID;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21 import java.util.concurrent.atomic.AtomicInteger;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.bluetooth.BeaconBluetoothHandler;
26 import org.openhab.binding.bluetooth.BluetoothCharacteristic;
27 import org.openhab.binding.bluetooth.BluetoothCompletionStatus;
28 import org.openhab.binding.bluetooth.BluetoothDevice.ConnectionState;
29 import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification;
30 import org.openhab.core.library.types.QuantityType;
31 import org.openhab.core.library.unit.SIUnits;
32 import org.openhab.core.library.unit.Units;
33 import org.openhab.core.thing.Thing;
34 import org.openhab.core.thing.ThingStatus;
35 import org.openhab.core.thing.ThingStatusDetail;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 /**
40  * The {@link AirthingsWavePlusHandler} is responsible for handling commands, which are
41  * sent to one of the channels.
42  *
43  * @author Pauli Anttila - Initial contribution
44  */
45 @NonNullByDefault
46 public class AirthingsWavePlusHandler extends BeaconBluetoothHandler {
47
48     private static final String DATA_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba";
49     private static final int CHECK_PERIOD_SEC = 10;
50
51     private final Logger logger = LoggerFactory.getLogger(AirthingsWavePlusHandler.class);
52     private final UUID uuid = UUID.fromString(DATA_UUID);
53
54     private AtomicInteger sinceLastReadSec = new AtomicInteger();
55     private Optional<AirthingsConfiguration> configuration = Optional.empty();
56     private @Nullable ScheduledFuture<?> scheduledTask;
57
58     private volatile int refreshInterval;
59
60     private volatile ServiceState serviceState = ServiceState.NOT_RESOLVED;
61     private volatile ReadState readState = ReadState.IDLE;
62
63     private enum ServiceState {
64         NOT_RESOLVED,
65         RESOLVING,
66         RESOLVED,
67     }
68
69     private enum ReadState {
70         IDLE,
71         READING,
72     }
73
74     public AirthingsWavePlusHandler(Thing thing) {
75         super(thing);
76     }
77
78     @Override
79     public void initialize() {
80         logger.debug("Initialize");
81         super.initialize();
82         configuration = Optional.of(getConfigAs(AirthingsConfiguration.class));
83         logger.debug("Using configuration: {}", configuration.get());
84         cancelScheduledTask();
85         configuration.ifPresent(cfg -> {
86             refreshInterval = cfg.refreshInterval;
87             logger.debug("Start scheduled task to read device in every {} seconds", refreshInterval);
88             scheduledTask = scheduler.scheduleWithFixedDelay(this::executePeridioc, CHECK_PERIOD_SEC, CHECK_PERIOD_SEC,
89                     TimeUnit.SECONDS);
90         });
91         sinceLastReadSec.set(refreshInterval); // update immediately
92     }
93
94     @Override
95     public void dispose() {
96         logger.debug("Dispose");
97         cancelScheduledTask();
98         serviceState = ServiceState.NOT_RESOLVED;
99         readState = ReadState.IDLE;
100         super.dispose();
101     }
102
103     private void cancelScheduledTask() {
104         if (scheduledTask != null) {
105             scheduledTask.cancel(true);
106             scheduledTask = null;
107         }
108     }
109
110     private void executePeridioc() {
111         sinceLastReadSec.addAndGet(CHECK_PERIOD_SEC);
112         execute();
113     }
114
115     private synchronized void execute() {
116         ConnectionState connectionState = device.getConnectionState();
117         logger.debug("Device {} state is {}, serviceState {}, readState {}", address, connectionState, serviceState,
118                 readState);
119
120         switch (connectionState) {
121             case DISCOVERED:
122             case DISCONNECTED:
123                 if (isTimeToRead()) {
124                     connect();
125                 }
126                 break;
127             case CONNECTED:
128                 read();
129                 break;
130             default:
131                 break;
132         }
133     }
134
135     private void connect() {
136         logger.debug("Connect to device {}...", address);
137         if (!device.connect()) {
138             logger.debug("Connecting to device {} failed", address);
139         }
140     }
141
142     private void disconnect() {
143         logger.debug("Disconnect from device {}...", address);
144         if (!device.disconnect()) {
145             logger.debug("Disconnect from device {} failed", address);
146         }
147     }
148
149     private void read() {
150         switch (serviceState) {
151             case NOT_RESOLVED:
152                 discoverServices();
153                 break;
154             case RESOLVED:
155                 switch (readState) {
156                     case IDLE:
157                         logger.debug("Read data from device {}...", address);
158                         BluetoothCharacteristic characteristic = device.getCharacteristic(uuid);
159                         if (characteristic != null && device.readCharacteristic(characteristic)) {
160                             readState = ReadState.READING;
161                         } else {
162                             logger.debug("Read data from device {} failed", address);
163                             disconnect();
164                         }
165                         break;
166                     default:
167                         break;
168                 }
169             default:
170                 break;
171         }
172     }
173
174     private void discoverServices() {
175         logger.debug("Discover services for device {}", address);
176         serviceState = ServiceState.RESOLVING;
177         device.discoverServices();
178     }
179
180     @Override
181     public void onServicesDiscovered() {
182         serviceState = ServiceState.RESOLVED;
183         logger.debug("Service discovery completed for device {}", address);
184         printServices();
185         execute();
186     }
187
188     private void printServices() {
189         device.getServices().forEach(service -> logger.debug("Device {} Service '{}'", address, service));
190     }
191
192     @Override
193     public void onConnectionStateChange(BluetoothConnectionStatusNotification connectionNotification) {
194         switch (connectionNotification.getConnectionState()) {
195             case DISCONNECTED:
196                 if (serviceState == ServiceState.RESOLVING) {
197                     serviceState = ServiceState.NOT_RESOLVED;
198                 }
199                 readState = ReadState.IDLE;
200                 break;
201             default:
202                 break;
203
204         }
205         execute();
206     }
207
208     @Override
209     public void onCharacteristicReadComplete(BluetoothCharacteristic characteristic, BluetoothCompletionStatus status) {
210         try {
211             if (status == BluetoothCompletionStatus.SUCCESS) {
212                 logger.debug("Characteristic {} from device {}: {}", characteristic.getUuid(), address,
213                         characteristic.getValue());
214                 updateStatus(ThingStatus.ONLINE);
215                 sinceLastReadSec.set(0);
216                 try {
217                     updateChannels(new AirthingsWavePlusDataParser(characteristic.getValue()));
218                 } catch (AirthingsParserException e) {
219                     logger.warn("Data parsing error occured, when parsing data from device {}, cause {}", address,
220                             e.getMessage(), e);
221                 }
222             } else {
223                 logger.debug("Characteristic {} from device {} failed", characteristic.getUuid(), address);
224                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "No response from device");
225             }
226         } finally {
227             readState = ReadState.IDLE;
228             disconnect();
229         }
230     }
231
232     private void updateChannels(AirthingsWavePlusDataParser parser) {
233         logger.debug("Parsed data: {}", parser);
234         updateState(CHANNEL_ID_HUMIDITY, QuantityType.valueOf(Double.valueOf(parser.getHumidity()), Units.PERCENT));
235         updateState(CHANNEL_ID_TEMPERATURE,
236                 QuantityType.valueOf(Double.valueOf(parser.getTemperature()), SIUnits.CELSIUS));
237         updateState(CHANNEL_ID_PRESSURE, QuantityType.valueOf(Double.valueOf(parser.getPressure()), Units.MILLIBAR));
238         updateState(CHANNEL_ID_CO2, QuantityType.valueOf(Double.valueOf(parser.getCo2()), Units.PARTS_PER_MILLION));
239         updateState(CHANNEL_ID_TVOC, QuantityType.valueOf(Double.valueOf(parser.getTvoc()), PARTS_PER_BILLION));
240         updateState(CHANNEL_ID_RADON_ST_AVG,
241                 QuantityType.valueOf(Double.valueOf(parser.getRadonShortTermAvg()), BECQUEREL_PER_CUBIC_METRE));
242         updateState(CHANNEL_ID_RADON_LT_AVG,
243                 QuantityType.valueOf(Double.valueOf(parser.getRadonLongTermAvg()), BECQUEREL_PER_CUBIC_METRE));
244     }
245
246     private boolean isTimeToRead() {
247         int sinceLastRead = sinceLastReadSec.get();
248         logger.debug("Time since last update: {} sec", sinceLastRead);
249         return sinceLastRead >= refreshInterval;
250     }
251 }