2 * Copyright (c) 2010-2021 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.airthings.internal;
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;
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;
34 * The {@link AbstractAirthingsHandler} is responsible for handling commands, which are
35 * sent to one of the channels.
37 * @author Pauli Anttila - Initial contribution
38 * @author Kai Kreuzer - Added Airthings Wave Mini support
41 abstract public class AbstractAirthingsHandler extends BeaconBluetoothHandler {
43 private static final int CHECK_PERIOD_SEC = 10;
45 private final Logger logger = LoggerFactory.getLogger(AbstractAirthingsHandler.class);
47 private AtomicInteger sinceLastReadSec = new AtomicInteger();
48 private Optional<AirthingsConfiguration> configuration = Optional.empty();
49 private @Nullable ScheduledFuture<?> scheduledTask;
51 private volatile int refreshInterval;
53 private volatile ServiceState serviceState = ServiceState.NOT_RESOLVED;
54 private volatile ReadState readState = ReadState.IDLE;
56 private enum ServiceState {
62 private enum ReadState {
67 public AbstractAirthingsHandler(Thing thing) {
72 public void initialize() {
73 logger.debug("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,
84 sinceLastReadSec.set(refreshInterval); // update immediately
88 public void dispose() {
89 logger.debug("Dispose");
90 cancelScheduledTask();
91 serviceState = ServiceState.NOT_RESOLVED;
92 readState = ReadState.IDLE;
96 private void cancelScheduledTask() {
97 if (scheduledTask != null) {
98 scheduledTask.cancel(true);
103 private void executePeridioc() {
104 sinceLastReadSec.addAndGet(CHECK_PERIOD_SEC);
108 private synchronized void execute() {
109 ConnectionState connectionState = device.getConnectionState();
110 logger.debug("Device {} state is {}, serviceState {}, readState {}", address, connectionState, serviceState,
113 switch (connectionState) {
117 if (isTimeToRead()) {
129 private void connect() {
130 logger.debug("Connect to device {}...", address);
131 if (!device.connect()) {
132 logger.debug("Connecting to device {} failed", address);
136 private void disconnect() {
137 logger.debug("Disconnect from device {}...", address);
138 if (!device.disconnect()) {
139 logger.debug("Disconnect from device {} failed", address);
143 private void read() {
144 switch (serviceState) {
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) -> {
157 logger.debug("Characteristic {} from device {}: {}", characteristic.getUuid(),
159 updateStatus(ThingStatus.ONLINE);
160 sinceLastReadSec.set(0);
161 updateChannels(BluetoothUtils.toIntArray(data));
163 readState = ReadState.IDLE;
168 logger.debug("Read data from device {} failed", address);
180 private void discoverServices() {
181 logger.debug("Discover services for device {}", address);
182 serviceState = ServiceState.RESOLVING;
183 device.discoverServices();
187 public void onServicesDiscovered() {
188 serviceState = ServiceState.RESOLVED;
189 logger.debug("Service discovery completed for device {}", address);
194 private void printServices() {
195 device.getServices().forEach(service -> logger.debug("Device {} Service '{}'", address, service));
199 public void onConnectionStateChange(BluetoothConnectionStatusNotification connectionNotification) {
200 switch (connectionNotification.getConnectionState()) {
202 if (serviceState == ServiceState.RESOLVING) {
203 serviceState = ServiceState.NOT_RESOLVED;
205 readState = ReadState.IDLE;
214 private boolean isTimeToRead() {
215 int sinceLastRead = sinceLastReadSec.get();
216 logger.debug("Time since last update: {} sec", sinceLastRead);
217 return sinceLastRead >= refreshInterval;
221 * Provides the UUID of the characteristic, which holds the sensor data
223 * @return the UUID of the data characteristic
225 protected abstract UUID getDataUUID();
228 * This method parses the content of the bluetooth characteristic and updates the Thing channels accordingly.
230 * @param is the content of the bluetooth characteristic
232 abstract protected void updateChannels(int[] is);