Skip to content

Commit

Permalink
Added auto discovery.
Browse files Browse the repository at this point in the history
Signed-off-by: Matthew Skinner <matt@pcmus.com>
  • Loading branch information
Skinah committed Jun 6, 2021
1 parent 9b2c910 commit 28025d2
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 3 deletions.
2 changes: 1 addition & 1 deletion bundles/org.openhab.binding.ipobserver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ There is only one thing that can be added and is called `weatherstation`.

## Discovery

Auto discovery is not supported as it would require a IP scan that would take longer than simply adding an IP or host name to the binding.
Auto discovery is supported and may take a while to complete as it scans all IP addresses on your network one by one.

## Thing Configuration

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class IpObserverBindingConstants {
public static final String REBOOT_URL = "/msgreboot.htm";
public static final String LIVE_DATA_URL = "/livedata.htm";
public static final String STATION_SETTINGS_URL = "/station.htm";
public static final int DISCOVERY_THREAD_POOL_SIZE = 15;

// List of all Thing Type UIDs
public static final ThingTypeUID THING_WEATHER_STATION = new ThingTypeUID(BINDING_ID, "weatherstation");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* 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.ipobserver.internal;

import static org.openhab.binding.ipobserver.internal.IpObserverBindingConstants.LIVE_DATA_URL;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;

/**
* The {@link IpObserverDiscoveryJob} class allows auto discovery of
* devices for a single IP address. This is used
* for threading to make discovery faster.
*
* @author Matthew Skinner - Initial contribution
*/
@NonNullByDefault
public class IpObserverDiscoveryJob implements Runnable {
private IpObserverDiscoveryService discoveryClass;
private String ipAddress;

public IpObserverDiscoveryJob(IpObserverDiscoveryService service, String ip) {
this.discoveryClass = service;
this.ipAddress = ip;
}

@Override
public void run() {
if (isIpObserverDevice(this.ipAddress)) {
discoveryClass.submitDiscoveryResults(this.ipAddress);
}
}

private boolean isIpObserverDevice(String ip) {
Request request = discoveryClass.getHttpClient().newRequest("http://" + ip + LIVE_DATA_URL);
request.method(HttpMethod.GET).timeout(5, TimeUnit.SECONDS).header(HttpHeader.ACCEPT_ENCODING, "gzip");
ContentResponse contentResponse;
try {
contentResponse = request.send();
if (contentResponse.getStatus() == 200) {
return true;
}
} catch (InterruptedException | TimeoutException | ExecutionException e) {
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/**
* 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.ipobserver.internal;

import static org.openhab.binding.ipobserver.internal.IpObserverBindingConstants.*;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The {@link IpObserverDiscoveryService} is responsible for finding ipObserver devices.
*
* @author Matthew Skinner - Initial contribution.
*/
@Component(service = DiscoveryService.class, configurationPid = "discovery.ipobserver")
@NonNullByDefault
public class IpObserverDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<>(
Arrays.asList(THING_WEATHER_STATION));
private ExecutorService discoverySearchPool = scheduler;
private HttpClient httpClient;

@Activate
public IpObserverDiscoveryService(@Reference IpObserverHandlerFactory handlerFactory) {
super(SUPPORTED_THING_TYPES_UIDS, 240);
httpClient = handlerFactory.getHttpClient();
}

protected HttpClient getHttpClient() {
return httpClient;
}

public void submitDiscoveryResults(String ip) {
ThingUID thingUID = new ThingUID(THING_WEATHER_STATION, ip.replace('.', '_'));
HashMap<String, Object> properties = new HashMap<>();
properties.put("address", ip);
thingDiscovered(DiscoveryResultBuilder.create(thingUID).withProperties(properties)
.withLabel("OpenSprinkler HTTP Bridge").withRepresentationProperty("address").build());
}

private void scanSingleSubnet(InterfaceAddress hostAddress) {
byte[] broadcastAddress = hostAddress.getBroadcast().getAddress();
// Create subnet mask from length
int shft = 0xffffffff << (32 - hostAddress.getNetworkPrefixLength());
byte oct1 = (byte) (((byte) ((shft & 0xff000000) >> 24)) & 0xff);
byte oct2 = (byte) (((byte) ((shft & 0x00ff0000) >> 16)) & 0xff);
byte oct3 = (byte) (((byte) ((shft & 0x0000ff00) >> 8)) & 0xff);
byte oct4 = (byte) (((byte) (shft & 0x000000ff)) & 0xff);
byte[] subnetMask = new byte[] { oct1, oct2, oct3, oct4 };
// calc first IP to start scanning from on this subnet
byte[] startAddress = new byte[4];
startAddress[0] = (byte) (broadcastAddress[0] & subnetMask[0]);
startAddress[1] = (byte) (broadcastAddress[1] & subnetMask[1]);
startAddress[2] = (byte) (broadcastAddress[2] & subnetMask[2]);
startAddress[3] = (byte) (broadcastAddress[3] & subnetMask[3]);
// Loop from start of subnet to the broadcast address.
for (int i = ByteBuffer.wrap(startAddress).getInt(); i < ByteBuffer.wrap(broadcastAddress).getInt(); i++) {
try {
InetAddress currentIP = InetAddress.getByAddress(ByteBuffer.allocate(4).putInt(i).array());
// Try to reach each IP with a timeout of 500ms which is enough for local network
if (currentIP.isReachable(500)) {
String host = currentIP.getHostAddress().toString();
logger.debug("Unknown device was found at: {}", host);
discoverySearchPool.execute(new IpObserverDiscoveryJob(this, host));
}
} catch (IOException e) {
}
}
}

@Override
protected void startScan() {
discoverySearchPool = Executors.newFixedThreadPool(DISCOVERY_THREAD_POOL_SIZE);
try {
ipAddressScan();
} catch (Exception exp) {
logger.debug("IpObserver discovery service encountered an error while scanning for devices: {}",
exp.getMessage());
}
}

private void ipAddressScan() {
try {
for (Enumeration<NetworkInterface> enumNetworks = NetworkInterface.getNetworkInterfaces(); enumNetworks
.hasMoreElements();) {
NetworkInterface networkInterface = enumNetworks.nextElement();
List<InterfaceAddress> list = networkInterface.getInterfaceAddresses();
for (InterfaceAddress hostAddress : list) {
InetAddress inetAddress = hostAddress.getAddress();
if (!inetAddress.isLoopbackAddress() && inetAddress.isSiteLocalAddress()) {
logger.debug("Scanning all IP address's that IP {}/{} is on", hostAddress.getAddress(),
hostAddress.getNetworkPrefixLength());
scanSingleSubnet(hostAddress);
}
}
}
} catch (SocketException ex) {
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,18 @@
@NonNullByDefault
@Component(configurationPid = "binding.ipobserver", service = ThingHandlerFactory.class)
public class IpObserverHandlerFactory extends BaseThingHandlerFactory {

private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_WEATHER_STATION);
private final HttpClient httpClient;
protected final HttpClient httpClient;

@Activate
public IpObserverHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
this.httpClient = httpClientFactory.getCommonHttpClient();
}

protected HttpClient getHttpClient() {
return httpClient;
}

@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
Expand Down

0 comments on commit 28025d2

Please sign in to comment.