-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Kai Kreuzer <[email protected]>
- Loading branch information
1 parent
e2877fa
commit 6a4151a
Showing
12 changed files
with
593 additions
and
378 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
241 changes: 241 additions & 0 deletions
241
.../main/java/org/openhab/binding/bluetooth/airthings/internal/AbstractAirthingsHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
/** | ||
* Copyright (c) 2010-2021 Contributors to the openHAB project | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.openhab.binding.bluetooth.airthings.internal; | ||
|
||
import java.util.Optional; | ||
import java.util.UUID; | ||
import java.util.concurrent.ScheduledFuture; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
|
||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
import org.eclipse.jdt.annotation.Nullable; | ||
import org.openhab.binding.bluetooth.BeaconBluetoothHandler; | ||
import org.openhab.binding.bluetooth.BluetoothCharacteristic; | ||
import org.openhab.binding.bluetooth.BluetoothCompletionStatus; | ||
import org.openhab.binding.bluetooth.BluetoothDevice.ConnectionState; | ||
import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification; | ||
import org.openhab.core.thing.Thing; | ||
import org.openhab.core.thing.ThingStatus; | ||
import org.openhab.core.thing.ThingStatusDetail; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* The {@link AbstractAirthingsHandler} is responsible for handling commands, which are | ||
* sent to one of the channels. | ||
* | ||
* @author Pauli Anttila - Initial contribution | ||
* @author Kai Kreuzer - Added Airthings Wave Mini support | ||
*/ | ||
@NonNullByDefault | ||
abstract public class AbstractAirthingsHandler extends BeaconBluetoothHandler { | ||
|
||
private static final int CHECK_PERIOD_SEC = 10; | ||
|
||
private final Logger logger = LoggerFactory.getLogger(AbstractAirthingsHandler.class); | ||
|
||
private AtomicInteger sinceLastReadSec = new AtomicInteger(); | ||
private Optional<AirthingsConfiguration> configuration = Optional.empty(); | ||
private @Nullable ScheduledFuture<?> scheduledTask; | ||
|
||
private volatile int refreshInterval; | ||
|
||
private volatile ServiceState serviceState = ServiceState.NOT_RESOLVED; | ||
private volatile ReadState readState = ReadState.IDLE; | ||
|
||
private enum ServiceState { | ||
NOT_RESOLVED, | ||
RESOLVING, | ||
RESOLVED, | ||
} | ||
|
||
private enum ReadState { | ||
IDLE, | ||
READING, | ||
} | ||
|
||
public AbstractAirthingsHandler(Thing thing) { | ||
super(thing); | ||
} | ||
|
||
@Override | ||
public void initialize() { | ||
logger.debug("Initialize"); | ||
super.initialize(); | ||
configuration = Optional.of(getConfigAs(AirthingsConfiguration.class)); | ||
logger.debug("Using configuration: {}", configuration.get()); | ||
cancelScheduledTask(); | ||
configuration.ifPresent(cfg -> { | ||
refreshInterval = cfg.refreshInterval; | ||
logger.debug("Start scheduled task to read device in every {} seconds", refreshInterval); | ||
scheduledTask = scheduler.scheduleWithFixedDelay(this::executePeridioc, CHECK_PERIOD_SEC, CHECK_PERIOD_SEC, | ||
TimeUnit.SECONDS); | ||
}); | ||
sinceLastReadSec.set(refreshInterval); // update immediately | ||
} | ||
|
||
@Override | ||
public void dispose() { | ||
logger.debug("Dispose"); | ||
cancelScheduledTask(); | ||
serviceState = ServiceState.NOT_RESOLVED; | ||
readState = ReadState.IDLE; | ||
super.dispose(); | ||
} | ||
|
||
private void cancelScheduledTask() { | ||
if (scheduledTask != null) { | ||
scheduledTask.cancel(true); | ||
scheduledTask = null; | ||
} | ||
} | ||
|
||
private void executePeridioc() { | ||
sinceLastReadSec.addAndGet(CHECK_PERIOD_SEC); | ||
execute(); | ||
} | ||
|
||
private synchronized void execute() { | ||
ConnectionState connectionState = device.getConnectionState(); | ||
logger.debug("Device {} state is {}, serviceState {}, readState {}", address, connectionState, serviceState, | ||
readState); | ||
|
||
switch (connectionState) { | ||
case DISCOVERING: | ||
case DISCOVERED: | ||
case DISCONNECTED: | ||
if (isTimeToRead()) { | ||
connect(); | ||
} | ||
break; | ||
case CONNECTED: | ||
read(); | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
|
||
private void connect() { | ||
logger.debug("Connect to device {}...", address); | ||
if (!device.connect()) { | ||
logger.debug("Connecting to device {} failed", address); | ||
} | ||
} | ||
|
||
private void disconnect() { | ||
logger.debug("Disconnect from device {}...", address); | ||
if (!device.disconnect()) { | ||
logger.debug("Disconnect from device {} failed", address); | ||
} | ||
} | ||
|
||
private void read() { | ||
switch (serviceState) { | ||
case NOT_RESOLVED: | ||
discoverServices(); | ||
break; | ||
case RESOLVED: | ||
switch (readState) { | ||
case IDLE: | ||
logger.debug("Read data from device {}...", address); | ||
BluetoothCharacteristic characteristic = device.getCharacteristic(getDataUUID()); | ||
if (characteristic != null && device.readCharacteristic(characteristic)) { | ||
readState = ReadState.READING; | ||
} else { | ||
logger.debug("Read data from device {} failed", address); | ||
disconnect(); | ||
} | ||
break; | ||
default: | ||
break; | ||
} | ||
default: | ||
break; | ||
} | ||
} | ||
|
||
private void discoverServices() { | ||
logger.debug("Discover services for device {}", address); | ||
serviceState = ServiceState.RESOLVING; | ||
device.discoverServices(); | ||
} | ||
|
||
@Override | ||
public void onServicesDiscovered() { | ||
serviceState = ServiceState.RESOLVED; | ||
logger.debug("Service discovery completed for device {}", address); | ||
printServices(); | ||
execute(); | ||
} | ||
|
||
private void printServices() { | ||
device.getServices().forEach(service -> logger.debug("Device {} Service '{}'", address, service)); | ||
} | ||
|
||
@Override | ||
public void onConnectionStateChange(BluetoothConnectionStatusNotification connectionNotification) { | ||
switch (connectionNotification.getConnectionState()) { | ||
case DISCONNECTED: | ||
if (serviceState == ServiceState.RESOLVING) { | ||
serviceState = ServiceState.NOT_RESOLVED; | ||
} | ||
readState = ReadState.IDLE; | ||
break; | ||
default: | ||
break; | ||
|
||
} | ||
execute(); | ||
} | ||
|
||
@Override | ||
public void onCharacteristicReadComplete(BluetoothCharacteristic characteristic, BluetoothCompletionStatus status) { | ||
try { | ||
if (status == BluetoothCompletionStatus.SUCCESS) { | ||
logger.debug("Characteristic {} from device {}: {}", characteristic.getUuid(), address, | ||
characteristic.getValue()); | ||
updateStatus(ThingStatus.ONLINE); | ||
sinceLastReadSec.set(0); | ||
updateChannels(characteristic.getValue()); | ||
} else { | ||
logger.debug("Characteristic {} from device {} failed", characteristic.getUuid(), address); | ||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "No response from device"); | ||
} | ||
} finally { | ||
readState = ReadState.IDLE; | ||
disconnect(); | ||
} | ||
} | ||
|
||
private boolean isTimeToRead() { | ||
int sinceLastRead = sinceLastReadSec.get(); | ||
logger.debug("Time since last update: {} sec", sinceLastRead); | ||
return sinceLastRead >= refreshInterval; | ||
} | ||
|
||
/** | ||
* Provides the UUID of the characteristic, which holds the sensor data | ||
* | ||
* @return the UUID of the data characteristic | ||
*/ | ||
protected abstract UUID getDataUUID(); | ||
|
||
/** | ||
* This method parses the content of the bluetooth characteristic and updates the Thing channels accordingly. | ||
* | ||
* @param is the content of the bluetooth characteristic | ||
*/ | ||
abstract protected void updateChannels(int[] is); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.