Skip to content

Commit

Permalink
More work on BLE
Browse files Browse the repository at this point in the history
  • Loading branch information
seime committed Jun 27, 2024
1 parent e3f1191 commit e9b48a2
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import java.util.concurrent.CompletableFuture;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bluetooth.*;
import org.openhab.binding.bluetooth.notification.BluetoothScanNotification;

import io.esphome.api.BluetoothLEAdvertisementResponse;
import no.seime.openhab.binding.esphome.internal.handler.ESPHomeHandler;

@NonNullByDefault
public class ESPHomeBluetoothDevice extends BaseBluetoothDevice {
Expand All @@ -17,8 +19,14 @@ public class ESPHomeBluetoothDevice extends BaseBluetoothDevice {
* @param address
*/

@Nullable
private ESPHomeHandler lockToHandler;

private final ESPHomeBluetoothProxyHandler proxyHandler;

public ESPHomeBluetoothDevice(BluetoothAdapter adapter, BluetoothAddress address) {
super(adapter, address);
proxyHandler = (ESPHomeBluetoothProxyHandler) adapter;
}

public void handleAdvertisementPacket(BluetoothLEAdvertisementResponse packet) {
Expand All @@ -45,11 +53,25 @@ private String to128BitUUID(String UUID16bit) {

@Override
public boolean connect() {
ESPHomeHandler nearestESPHomeDevice = proxyHandler
.getNearestESPHomeDevice(proxyHandler.convertAddressToLong(address));
if (nearestESPHomeDevice != null) {
lockToHandler = nearestESPHomeDevice;
// Connect to the device
return true;
}

return false;
}

@Override
public boolean disconnect() {
if (lockToHandler != null) {
lockToHandler = null;
// Disconnect from the device
return true;

}
return false;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package no.seime.openhab.binding.esphome.internal.bluetooth;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import io.esphome.api.BluetoothLEAdvertisementResponse;
import no.seime.openhab.binding.esphome.internal.BindingConstants;
import no.seime.openhab.binding.esphome.internal.handler.ESPHomeHandler;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.*;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
Expand All @@ -16,13 +14,13 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HexFormat;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import io.esphome.api.BluetoothLEAdvertisementResponse;
import no.seime.openhab.binding.esphome.internal.BindingConstants;
import no.seime.openhab.binding.esphome.internal.handler.ESPHomeHandler;

@NonNullByDefault
public class ESPHomeBluetoothProxyHandler extends AbstractBluetoothBridgeHandler<ESPHomeBluetoothDevice> {
Expand All @@ -38,6 +36,8 @@ public class ESPHomeBluetoothProxyHandler extends AbstractBluetoothBridgeHandler

private final LoadingCache<Long, Optional<BluetoothLEAdvertisementResponse>> cache;

private Map<Long, SortedSet<DeviceAndRSSI>> knownDevices = new ConcurrentHashMap<>();

/**
* Creates a new instance of this class for the {@link Thing}.
*
Expand Down Expand Up @@ -150,8 +150,21 @@ private BluetoothAddress createAddress(long address) {
return new BluetoothAddress(addressBuilder.toString().toUpperCase());
}

public long convertAddressToLong(BluetoothAddress address) {
String[] parts = address.toString().split(":");
long result = 0;
for (int i = 0; i < parts.length; i++) {
result = result << 8;
result |= Integer.parseInt(parts[i], 16);
}
return result;
}

public void handleAdvertisement(@NonNull BluetoothLEAdvertisementResponse rsp, ESPHomeHandler handler) {

// Update RSSi list
updateDeviceLocation(rsp, handler);

try {
Optional<BluetoothLEAdvertisementResponse> cachedAdvertisement = cache.get(rsp.getAddress());
if (cachedAdvertisement.isPresent() && equalsExceptRssi(rsp, cachedAdvertisement.get())) {
Expand All @@ -178,7 +191,6 @@ public void handleAdvertisement(@NonNull BluetoothLEAdvertisementResponse rsp, E
String uuid = manufacturerData.getUuid();
int manufacturerId = parseManufacturerIdToInt(uuid);
device.setManufacturerId(manufacturerId);

});

deviceDiscovered(device);
Expand All @@ -190,6 +202,31 @@ public void handleAdvertisement(@NonNull BluetoothLEAdvertisementResponse rsp, E
}
}

@Nullable
public ESPHomeHandler getNearestESPHomeDevice(long address) {
SortedSet<DeviceAndRSSI> deviceAndRSSIS = knownDevices.get(address);
if (deviceAndRSSIS == null || deviceAndRSSIS.isEmpty()) {
return null;
}

ThingUID device = deviceAndRSSIS.first().device;
@Nullable
Thing esphomeThing = thingRegistry.get(device);
if (esphomeThing != null) {
return (ESPHomeHandler) esphomeThing.getHandler();
} else {
return null;
}
}

private void updateDeviceLocation(BluetoothLEAdvertisementResponse rsp, ESPHomeHandler handler) {
SortedSet<DeviceAndRSSI> deviceAndRSSIS = knownDevices.computeIfAbsent(rsp.getAddress(),
k -> new ConcurrentSkipListSet<>());
deviceAndRSSIS.removeIf(e -> e.device.equals(handler.getThing().getUID())); // Remove previous entry for this
// esphome device
deviceAndRSSIS.add(new DeviceAndRSSI(handler.getThing().getUID(), rsp.getRssi(), Instant.now()));
}

private boolean equalsExceptRssi(BluetoothLEAdvertisementResponse rsp1, BluetoothLEAdvertisementResponse rsp2) {
return rsp1.getAddress() == rsp2.getAddress() && rsp1.getName().equals(rsp2.getName())
&& rsp1.getManufacturerDataList().equals(rsp2.getManufacturerDataList())
Expand All @@ -209,4 +246,21 @@ private int parseManufacturerIdToInt(String uuid) {
public @Nullable BluetoothAddress getAddress() {
return null;
}

private static class DeviceAndRSSI implements Comparable<DeviceAndRSSI> {
private final ThingUID device;
private final int rssi;
private final Instant lastSeen;

public DeviceAndRSSI(ThingUID device, int rssi, Instant lastSeen) {
this.device = device;
this.rssi = rssi;
this.lastSeen = lastSeen;
}

@Override
public int compareTo(DeviceAndRSSI o) {
return Integer.compare(o.rssi, rssi);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,13 @@
*/
package no.seime.openhab.binding.esphome.internal.handler;

import com.google.protobuf.GeneratedMessageV3;
import io.esphome.api.*;
import no.seime.openhab.binding.esphome.internal.BindingConstants;
import no.seime.openhab.binding.esphome.internal.CommunicationListener;
import no.seime.openhab.binding.esphome.internal.ESPHomeConfiguration;
import no.seime.openhab.binding.esphome.internal.LogLevel;
import no.seime.openhab.binding.esphome.internal.bluetooth.ESPHomeBluetoothProxyHandler;
import no.seime.openhab.binding.esphome.internal.comm.*;
import no.seime.openhab.binding.esphome.internal.message.*;
import no.seime.openhab.binding.esphome.internal.message.statesubscription.ESPHomeEventSubscriber;
import no.seime.openhab.binding.esphome.internal.message.statesubscription.EventSubscription;
import java.math.BigDecimal;
import java.net.InetSocketAddress;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
Expand All @@ -36,12 +32,18 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.net.InetSocketAddress;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import com.google.protobuf.GeneratedMessageV3;

import io.esphome.api.*;
import no.seime.openhab.binding.esphome.internal.BindingConstants;
import no.seime.openhab.binding.esphome.internal.CommunicationListener;
import no.seime.openhab.binding.esphome.internal.ESPHomeConfiguration;
import no.seime.openhab.binding.esphome.internal.LogLevel;
import no.seime.openhab.binding.esphome.internal.bluetooth.ESPHomeBluetoothProxyHandler;
import no.seime.openhab.binding.esphome.internal.comm.*;
import no.seime.openhab.binding.esphome.internal.message.*;
import no.seime.openhab.binding.esphome.internal.message.statesubscription.ESPHomeEventSubscriber;
import no.seime.openhab.binding.esphome.internal.message.statesubscription.EventSubscription;

/**
* The {@link ESPHomeHandler} is responsible for handling commands, which are
Expand Down

0 comments on commit e9b48a2

Please sign in to comment.