added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.bluetooth.roaming-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-bluetooth-roaming" description="Bluetooth Binding Roaming" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth/${project.version}</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.roaming/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2010-2020 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.roaming.internal;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.bluetooth.BluetoothBindingConstants;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link RoamingBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Connor Petty - Initial contribution
*/
@NonNullByDefault
public class RoamingBindingConstants {
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_ROAMING = new ThingTypeUID(BluetoothBindingConstants.BINDING_ID,
"roaming");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_ROAMING);
public static final String CONFIGURATION_GROUP_ADAPTER_UIDS = "groupUIDs";
}

View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2020 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.roaming.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.bluetooth.BluetoothAdapter;
import org.openhab.core.thing.ThingUID;
/**
* The {@link RoamingBluetoothAdapter} adds additional functionality to {@link BluetoothAdapter}
* but more importantly serves as a tagging interface to expose it as an OSGI service.
*
* @author Connor Petty - Initial contribution
*/
@NonNullByDefault
public interface RoamingBluetoothAdapter extends BluetoothAdapter {
void addBluetoothAdapter(BluetoothAdapter adapter);
void removeBluetoothAdapter(BluetoothAdapter adapter);
boolean isDiscoveryEnabled();
boolean isRoamingMember(ThingUID adapterUID);
}

View File

@@ -0,0 +1,168 @@
/**
* Copyright (c) 2010-2020 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.roaming.internal;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bluetooth.BluetoothAdapter;
import org.openhab.binding.bluetooth.BluetoothAddress;
import org.openhab.binding.bluetooth.BluetoothCharacteristic;
import org.openhab.binding.bluetooth.BluetoothCompletionStatus;
import org.openhab.binding.bluetooth.BluetoothDescriptor;
import org.openhab.binding.bluetooth.BluetoothDevice;
import org.openhab.binding.bluetooth.BluetoothDeviceListener;
import org.openhab.binding.bluetooth.DelegateBluetoothDevice;
import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification;
import org.openhab.binding.bluetooth.notification.BluetoothScanNotification;
/**
* The {@link RoamingBluetoothDevice} acts as a roaming device by delegating
* its operations to actual adapters.
*
* @author Connor Petty - Initial contribution
*/
@NonNullByDefault
public class RoamingBluetoothDevice extends DelegateBluetoothDevice {
private final Map<BluetoothDevice, Listener> devices = new ConcurrentHashMap<>();
private final List<BluetoothDeviceListener> eventListeners = new CopyOnWriteArrayList<>();
private final AtomicReference<@Nullable BluetoothDevice> currentDelegateRef = new AtomicReference<>();
protected RoamingBluetoothDevice(RoamingBridgeHandler roamingAdapter, BluetoothAddress address) {
super(roamingAdapter, address);
}
public void addBluetoothDevice(BluetoothDevice device) {
device.addListener(devices.computeIfAbsent(device, Listener::new));
}
public void removeBluetoothDevice(BluetoothDevice device) {
device.removeListener(devices.remove(device));
}
@Override
protected Collection<BluetoothDeviceListener> getListeners() {
return eventListeners;
}
@Override
protected @Nullable BluetoothDevice getDelegate() {
BluetoothDevice newDelegate = null;
int newRssi = Integer.MIN_VALUE;
for (BluetoothDevice device : devices.keySet()) {
ConnectionState state = device.getConnectionState();
if (state == ConnectionState.CONNECTING || state == ConnectionState.CONNECTED) {
newDelegate = device;
break;
}
Integer rssi = device.getRssi();
if (rssi != null && (newDelegate == null || rssi > newRssi)) {
newRssi = rssi;
newDelegate = device;
}
}
BluetoothDevice oldDelegate = currentDelegateRef.getAndSet(newDelegate);
if (oldDelegate != newDelegate) { // using reference comparison is valid in this case
notifyListeners(BluetoothEventType.ADAPTER_CHANGED, getAdapter(newDelegate));
}
return newDelegate;
}
private BluetoothAdapter getAdapter(@Nullable BluetoothDevice delegate) {
if (delegate != null) {
return delegate.getAdapter();
}
// as a last resort we return our "actual" adapter
return super.getAdapter();
}
@Override
public BluetoothAdapter getAdapter() {
return getAdapter(currentDelegateRef.get());
}
private class Listener implements BluetoothDeviceListener {
private BluetoothDevice device;
public Listener(BluetoothDevice device) {
this.device = device;
}
@Override
public void onScanRecordReceived(BluetoothScanNotification scanNotification) {
if (device == getDelegate()) {
notifyListeners(BluetoothEventType.SCAN_RECORD, scanNotification);
}
}
@Override
public void onConnectionStateChange(BluetoothConnectionStatusNotification connectionNotification) {
if (device == getDelegate()) {
notifyListeners(BluetoothEventType.CONNECTION_STATE, connectionNotification);
}
}
@Override
public void onServicesDiscovered() {
device.getServices().forEach(RoamingBluetoothDevice.this::addService);
if (device == getDelegate()) {
notifyListeners(BluetoothEventType.SERVICES_DISCOVERED);
}
}
@Override
public void onCharacteristicReadComplete(BluetoothCharacteristic characteristic,
BluetoothCompletionStatus status) {
if (device == getDelegate()) {
notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic, status);
}
}
@Override
public void onCharacteristicWriteComplete(BluetoothCharacteristic characteristic,
BluetoothCompletionStatus status) {
if (device == getDelegate()) {
notifyListeners(BluetoothEventType.CHARACTERISTIC_WRITE_COMPLETE, characteristic);
}
}
@Override
public void onCharacteristicUpdate(BluetoothCharacteristic characteristic) {
if (device == getDelegate()) {
notifyListeners(BluetoothEventType.CHARACTERISTIC_UPDATED, characteristic);
}
}
@Override
public void onDescriptorUpdate(BluetoothDescriptor bluetoothDescriptor) {
if (device == getDelegate()) {
notifyListeners(BluetoothEventType.DESCRIPTOR_UPDATED, bluetoothDescriptor);
}
}
@Override
public void onAdapterChanged(BluetoothAdapter adapter) {
// do nothing since we are the ones that are supposed to trigger this
}
}
}

View File

@@ -0,0 +1,104 @@
/**
* Copyright (c) 2010-2020 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.roaming.internal;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.BiConsumer;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bluetooth.BluetoothAdapter;
import org.openhab.binding.bluetooth.discovery.BluetoothDiscoveryDevice;
import org.openhab.binding.bluetooth.discovery.BluetoothDiscoveryParticipant;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
/**
* The {@link RoamingBluetoothDiscoveryParticipant} acts as the roaming adapter's gateway
* to the osgi layer where it can find other adapter instances to use as delegates.
* This class also serves to generate the default roaming adapter discovery result for the user.
*
* @author Connor Petty - Initial contribution
*/
@NonNullByDefault
@Component(immediate = true, service = { BluetoothDiscoveryParticipant.class })
public class RoamingBluetoothDiscoveryParticipant implements BluetoothDiscoveryParticipant {
private final Set<BluetoothAdapter> adapters = new CopyOnWriteArraySet<>();
private final Set<RoamingBluetoothAdapter> roamingAdapters = new CopyOnWriteArraySet<>();
// private Optional<RoamingBluetoothAdapter> roamingAdapter = Optional.empty();
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
protected void setRoamingBluetoothAdapter(RoamingBluetoothAdapter roamingAdapter) {
roamingAdapters.add(roamingAdapter);
adapters.forEach(roamingAdapter::addBluetoothAdapter);
}
protected void unsetRoamingBluetoothAdapter(RoamingBluetoothAdapter roamingAdapter) {
roamingAdapters.remove(roamingAdapter);
adapters.forEach(roamingAdapter::removeBluetoothAdapter);
}
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
protected void addBluetoothAdapter(BluetoothAdapter adapter) {
this.adapters.add(adapter);
roamingAdapters.forEach(ra -> {
ra.addBluetoothAdapter(adapter);
});
}
protected void removeBluetoothAdapter(BluetoothAdapter adapter) {
this.adapters.remove(adapter);
roamingAdapters.forEach(ra -> {
ra.removeBluetoothAdapter(adapter);
});
}
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return Collections.emptySet();
}
@Override
public @Nullable DiscoveryResult createResult(BluetoothDiscoveryDevice device) {
return null;
}
@Override
public @Nullable ThingUID getThingUID(BluetoothDiscoveryDevice device) {
return null;
}
@Override
public void publishAdditionalResults(DiscoveryResult result,
BiConsumer<BluetoothAdapter, DiscoveryResult> publisher) {
// we create a roaming version of every discoveryResult.
roamingAdapters.forEach(roamingAdapter -> {
ThingUID adapterUID = result.getBridgeUID();
if (adapterUID != null && roamingAdapter.isDiscoveryEnabled()
&& roamingAdapter.isRoamingMember(adapterUID)) {
publisher.accept(roamingAdapter, result);
}
});
}
}

View File

@@ -0,0 +1,245 @@
/**
* Copyright (c) 2010-2020 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.roaming.internal;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bluetooth.BluetoothAdapter;
import org.openhab.binding.bluetooth.BluetoothAddress;
import org.openhab.binding.bluetooth.BluetoothBindingConstants;
import org.openhab.binding.bluetooth.BluetoothDiscoveryListener;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
/**
* The {@link RoamingBridgeHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Connor Petty - Initial contribution
*/
@NonNullByDefault
public class RoamingBridgeHandler extends BaseBridgeHandler implements RoamingBluetoothAdapter {
private final Set<BluetoothAdapter> adapters = new CopyOnWriteArraySet<>();
/*
* Note: this will only populate from handlers calling getDevice(BluetoothAddress), so we don't need
* to do periodic cleanup.
*/
private Map<BluetoothAddress, RoamingBluetoothDevice> devices = new HashMap<>();
private ThingUID[] groupUIDs = new ThingUID[0];
public RoamingBridgeHandler(Bridge bridge) {
super(bridge);
}
@Override
public void initialize() {
Object value = getConfig().get(RoamingBindingConstants.CONFIGURATION_GROUP_ADAPTER_UIDS);
if (value == null || !(value instanceof String) || "".equals(value)) {
groupUIDs = new ThingUID[0];
} else {
String groupIds = (String) value;
groupUIDs = Stream.of(groupIds.split(",")).map(ThingUID::new).toArray(ThingUID[]::new);
}
if (adapters.stream().map(BluetoothAdapter::getUID).anyMatch(this::isGroupMember)) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"No Physical Bluetooth adapters found");
}
}
private void updateStatus() {
if (adapters.stream().anyMatch(this::isRoamingMember)) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"No Physical Bluetooth adapters found");
}
}
@Override
public void dispose() {
// nothing that needs to be done here.
// Listener cleanup will be performed by the discovery participant anyway.
}
@Override
public ThingUID getUID() {
return getThing().getUID();
}
@Override
public @Nullable String getLocation() {
return getThing().getLocation();
}
@Override
public @Nullable String getLabel() {
return getThing().getLabel();
}
private boolean isRoamingMember(BluetoothAdapter adapter) {
return isRoamingMember(adapter.getUID());
}
@Override
public boolean isRoamingMember(ThingUID adapterUID) {
if (!isInitialized()) {
// an unitialized roaming adapter has no members
return false;
}
return isGroupMember(adapterUID);
}
private boolean isGroupMember(ThingUID adapterUID) {
if (groupUIDs.length == 0) {
// if there are no members of the group then it is treated as all adapters are members.
return true;
}
for (ThingUID uid : groupUIDs) {
if (adapterUID.equals(uid)) {
return true;
}
}
return false;
}
@Override
public boolean isDiscoveryEnabled() {
if (getThing().getStatus() != ThingStatus.ONLINE) {
return false;
}
Object discovery = getConfig().get(BluetoothBindingConstants.CONFIGURATION_DISCOVERY);
if (discovery != null && discovery.toString().equalsIgnoreCase("false")) {
return false;
}
return true;
}
@Override
public void addBluetoothAdapter(BluetoothAdapter adapter) {
if (adapter == this) {
return;
}
this.adapters.add(adapter);
if (isRoamingMember(adapter)) {
synchronized (devices) {
for (RoamingBluetoothDevice roamingDevice : devices.values()) {
roamingDevice.addBluetoothDevice(adapter.getDevice(roamingDevice.getAddress()));
}
}
}
if (getThing().getStatus() == ThingStatus.OFFLINE) {
updateStatus();
}
}
@Override
public void removeBluetoothAdapter(BluetoothAdapter adapter) {
if (adapter == this) {
return;
}
this.adapters.remove(adapter);
if (isRoamingMember(adapter)) {
synchronized (devices) {
for (RoamingBluetoothDevice roamingDevice : devices.values()) {
roamingDevice.removeBluetoothDevice(adapter.getDevice(roamingDevice.getAddress()));
}
}
}
if (getThing().getStatus() == ThingStatus.ONLINE) {
updateStatus();
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
@Override
public void addDiscoveryListener(BluetoothDiscoveryListener listener) {
// we don't use this
}
@Override
public void removeDiscoveryListener(@Nullable BluetoothDiscoveryListener listener) {
// we don't use this
}
@Override
public void scanStart() {
// does nothing
}
@Override
public void scanStop() {
// does nothing
}
@Override
public @Nullable BluetoothAddress getAddress() {
// roaming adapters don't have bluetooth addresses
return null;
}
@Override
public RoamingBluetoothDevice getDevice(BluetoothAddress address) {
// this will only get called by a bluetooth device handler
synchronized (devices) {
RoamingBluetoothDevice roamingDevice = devices.computeIfAbsent(address,
addr -> new RoamingBluetoothDevice(this, addr));
adapters.stream().filter(this::isRoamingMember)
.forEach(adapter -> roamingDevice.addBluetoothDevice(adapter.getDevice(address)));
return roamingDevice;
}
}
@Override
public boolean hasHandlerForDevice(BluetoothAddress address) {
String addrStr = address.toString();
/*
* This type of search is inefficient and won't scale as the number of bluetooth Thing children increases on
* this bridge. But implementing a more efficient search would require a bit more overhead.
* Luckily though, it is reasonable to assume that the number of Thing children will remain small.
*/
for (Thing childThing : getThing().getThings()) {
Object childAddr = childThing.getConfiguration().get(BluetoothBindingConstants.CONFIGURATION_ADDRESS);
if (addrStr.equals(childAddr)) {
return childThing.getHandler() != null;
}
}
return false;
}
}

View File

@@ -0,0 +1,80 @@
/**
* Copyright (c) 2010-2020 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.roaming.internal;
import static org.openhab.binding.bluetooth.roaming.internal.RoamingBindingConstants.THING_TYPE_ROAMING;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bluetooth.BluetoothAdapter;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.UID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Component;
/**
* The {@link RoamingHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Connor Petty - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.roaming", service = ThingHandlerFactory.class)
public class RoamingHandlerFactory extends BaseThingHandlerFactory {
private final Map<ThingUID, @Nullable ServiceRegistration<?>> serviceRegs = new HashMap<>();
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return RoamingBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_ROAMING.equals(thingTypeUID)) {
RoamingBridgeHandler handler = new RoamingBridgeHandler((Bridge) thing);
registerRoamingBluetoothAdapter(handler);
return handler;
}
return null;
}
private synchronized void registerRoamingBluetoothAdapter(RoamingBluetoothAdapter adapter) {
this.serviceRegs.put(adapter.getUID(),
bundleContext.registerService(RoamingBluetoothAdapter.class.getName(), adapter, new Hashtable<>()));
}
@Override
protected synchronized void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof RoamingBluetoothAdapter) {
UID uid = ((BluetoothAdapter) thingHandler).getUID();
ServiceRegistration<?> serviceReg = this.serviceRegs.remove(uid);
if (serviceReg != null) {
serviceReg.unregister();
}
}
}
}

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bluetooth"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="roaming">
<label>Roaming Bluetooth Controller</label>
<description>A virtual Bluetooth adapter that handles roaming between other adapters</description>
<config-description>
<parameter name="groupUIDs" type="text" multiple="true">
<label>Adapter UIDs</label>
<description>
<![CDATA[ Specifies which Bluetooth adapters that roaming devices can interact through.
<br>
Should be formatted as a comma separated list of thing UIDs.
<br>
If not specified, roaming devices can interact through any other Bluetooth adapter thing.
]]>
</description>
<context>thing</context>
<advanced>true</advanced>
<default></default>
</parameter>
<parameter name="backgroundDiscovery" type="boolean">
<label>Device Discovery</label>
<description>Whether this adapter participates in Bluetooth device discovery</description>
<advanced>true</advanced>
<default>true</default>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>