mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[atlona] Replace discovery service with core SDDP discovery (#16832)
* Replace discovery service with core SDDP discovery Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
31b23ab8bf
commit
7544f92f8d
@ -17,9 +17,9 @@ This binding supports the following thing types:
|
||||
|
||||
## Discovery
|
||||
|
||||
The Atlona AT-UHD-PRO3 switch can be discovered by starting a discovery scan in the UI and then logging into your switch and pressing the "SDDP" button on the "Network" tab.
|
||||
The "SDDP" (simple device discovery protocol) button will initiate the discovery process.
|
||||
If "Telnet Login" is enabled ("Network" tab from the switch configuration UI), you will need to set the username and password in the configuration of the newly discovered thing before a connection can be made.
|
||||
Supported things should be discovered automatically upon receipt of periodic SDDP announcements from the switch.
|
||||
If the thing is not discovered automatically, login to the switch configuration UI and press the "SDDP" button on the "Network" tab to force the switch to send the SDDP announcement.
|
||||
If "Telnet Login" is enabled in the switch configuration, you will need to set the username and password in the newly discovered thing before a connection can be made.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
<feature name="openhab-binding-atlona" description="Atlona Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-core-config-discovery-sddp</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.atlona/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
|
@ -1,273 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.atlona.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.atlona.internal.AtlonaBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.MulticastSocket;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.openhab.binding.atlona.internal.pro3.AtlonaPro3Config;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
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.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Discovery class for the Atlona PRO3 line. The PRO3 line uses SDDP (simple device discovery protocol) for discovery
|
||||
* (similar to UPNP but defined by Control4). The user should start the discovery process in openhab and then log into
|
||||
* the switch, go to the Network options and press the SDDP button (which initiates the SDDP conversation).
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@Component(service = DiscoveryService.class, configurationPid = "discovery.atlona")
|
||||
public class AtlonaDiscovery extends AbstractDiscoveryService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AtlonaDiscovery.class);
|
||||
|
||||
/**
|
||||
* Address SDDP broadcasts on
|
||||
*/
|
||||
private static final String SDDP_ADDR = "239.255.255.250";
|
||||
|
||||
/**
|
||||
* Port number SDDP uses
|
||||
*/
|
||||
private static final int SDDP_PORT = 1902;
|
||||
|
||||
/**
|
||||
* SDDP packet should be only 512 in size - make it 600 to give us some room
|
||||
*/
|
||||
private static final int BUFFER_SIZE = 600;
|
||||
|
||||
/**
|
||||
* Socket read timeout (in ms) - allows us to shutdown the listening every TIMEOUT
|
||||
*/
|
||||
private static final int TIMEOUT = 1000;
|
||||
|
||||
/**
|
||||
* Whether we are currently scanning or not
|
||||
*/
|
||||
private boolean scanning;
|
||||
|
||||
/**
|
||||
* The {@link ExecutorService} to run the listening threads on.
|
||||
*/
|
||||
private ExecutorService executorService;
|
||||
|
||||
/**
|
||||
* Constructs the discovery class using the thing IDs that we can discover.
|
||||
*/
|
||||
public AtlonaDiscovery() {
|
||||
super(Collections.unmodifiableSet(
|
||||
Stream.of(THING_TYPE_PRO3_44M, THING_TYPE_PRO3_66M, THING_TYPE_PRO3_88M, THING_TYPE_PRO3_1616M)
|
||||
.collect(Collectors.toSet())),
|
||||
30, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Starts the scan. This discovery will:
|
||||
* <ul>
|
||||
* <li>Request all the network interfaces</li>
|
||||
* <li>For each network interface, create a listening thread using {@link #executorService}</li>
|
||||
* <li>Each listening thread will open up a {@link MulticastSocket} using {@link #SDDP_ADDR} and {@link #SDDP_PORT}
|
||||
* and
|
||||
* will receive any {@link DatagramPacket} that comes in</li>
|
||||
* <li>The {@link DatagramPacket} is then investigated to see if is a SDDP packet and will create a new thing from
|
||||
* it</li>
|
||||
* </ul>
|
||||
* The process will continue until {@link #stopScan()} is called.
|
||||
*/
|
||||
@Override
|
||||
protected void startScan() {
|
||||
if (executorService != null) {
|
||||
stopScan();
|
||||
}
|
||||
|
||||
logger.debug("Starting Discovery");
|
||||
|
||||
try {
|
||||
final InetAddress addr = InetAddress.getByName(SDDP_ADDR);
|
||||
final List<NetworkInterface> networkInterfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
|
||||
|
||||
executorService = Executors.newFixedThreadPool(networkInterfaces.size());
|
||||
scanning = true;
|
||||
for (final NetworkInterface netint : networkInterfaces) {
|
||||
|
||||
executorService.execute(() -> {
|
||||
try {
|
||||
MulticastSocket multiSocket = new MulticastSocket(SDDP_PORT);
|
||||
multiSocket.setSoTimeout(TIMEOUT);
|
||||
multiSocket.setNetworkInterface(netint);
|
||||
multiSocket.joinGroup(addr);
|
||||
|
||||
while (scanning) {
|
||||
DatagramPacket receivePacket = new DatagramPacket(new byte[BUFFER_SIZE], BUFFER_SIZE);
|
||||
try {
|
||||
multiSocket.receive(receivePacket);
|
||||
|
||||
String message = new String(receivePacket.getData()).trim();
|
||||
if (message.length() > 0) {
|
||||
messageReceive(message);
|
||||
}
|
||||
} catch (SocketTimeoutException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
multiSocket.close();
|
||||
} catch (Exception e) {
|
||||
if (!e.getMessage().contains("No IP addresses bound to interface")) {
|
||||
logger.debug("Error getting ip addresses: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error getting ip addresses: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SDDP message has the following format
|
||||
*
|
||||
* <pre>
|
||||
* NOTIFY ALIVE SDDP/1.0
|
||||
* From: "192.168.1.30:1902"
|
||||
* Host: "AT-UHD-PRO3-88M_B898B0030F4D"
|
||||
* Type: "AT-UHD-PRO3-88M"
|
||||
* Max-Age: 1800
|
||||
* Primary-Proxy: "avswitch"
|
||||
* Proxies: "avswitch"
|
||||
* Manufacturer: "Atlona"
|
||||
* Model: "AT-UHD-PRO3-88M"
|
||||
* Driver: "avswitch_Atlona_AT-UHD-PRO3-88M_IP.c4i"
|
||||
* Config-URL: "http://192.168.1.30/"
|
||||
* </pre>
|
||||
*
|
||||
* First parse the manufacturer, host, model and IP address from the message. For the "Host" field, we parse out the
|
||||
* serial #. For the From field, we parse out the IP address (minus the port #). If we successfully found all four
|
||||
* and the manufacturer is "Atlona" and it's a model we recognize, we then create our thing from it.
|
||||
*
|
||||
* @param message possibly null, possibly empty SDDP message
|
||||
*/
|
||||
private void messageReceive(String message) {
|
||||
if (message == null || message.trim().length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String host = null;
|
||||
String model = null;
|
||||
String from = null;
|
||||
String manufacturer = null;
|
||||
|
||||
for (String msg : message.split("\r\n")) {
|
||||
int idx = msg.indexOf(':');
|
||||
if (idx > 0) {
|
||||
String name = msg.substring(0, idx);
|
||||
|
||||
if ("Host".equalsIgnoreCase(name)) {
|
||||
host = msg.substring(idx + 1).trim().replace("\"", "");
|
||||
int sep = host.indexOf('_');
|
||||
if (sep >= 0) {
|
||||
host = host.substring(sep + 1);
|
||||
}
|
||||
} else if ("Model".equalsIgnoreCase(name)) {
|
||||
model = msg.substring(idx + 1).trim().replace("\"", "");
|
||||
} else if ("Manufacturer".equalsIgnoreCase(name)) {
|
||||
manufacturer = msg.substring(idx + 1).trim().replace("\"", "");
|
||||
} else if ("From".equalsIgnoreCase(name)) {
|
||||
from = msg.substring(idx + 1).trim().replace("\"", "");
|
||||
int sep = from.indexOf(':');
|
||||
if (sep >= 0) {
|
||||
from = from.substring(0, sep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!"Atlona".equalsIgnoreCase(manufacturer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (host != null && model != null && from != null) {
|
||||
ThingTypeUID typeId = null;
|
||||
if ("AT-UHD-PRO3-44M".equalsIgnoreCase(model)) {
|
||||
typeId = THING_TYPE_PRO3_44M;
|
||||
} else if ("AT-UHD-PRO3-66M".equalsIgnoreCase(model)) {
|
||||
typeId = THING_TYPE_PRO3_66M;
|
||||
} else if ("AT-UHD-PRO3-88M".equalsIgnoreCase(model)) {
|
||||
typeId = THING_TYPE_PRO3_88M;
|
||||
} else if ("AT-UHD-PRO3-1616M".equalsIgnoreCase(model)) {
|
||||
typeId = THING_TYPE_PRO3_1616M;
|
||||
} else {
|
||||
logger.warn("Unknown model #: {}", model);
|
||||
}
|
||||
|
||||
if (typeId != null) {
|
||||
logger.debug("Creating binding for {} ({})", model, from);
|
||||
ThingUID j = new ThingUID(typeId, host);
|
||||
|
||||
Map<String, Object> properties = new HashMap<>(1);
|
||||
properties.put(AtlonaPro3Config.IP_ADDRESS, from);
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(j).withProperties(properties)
|
||||
.withLabel(model + " (" + from + ")").build();
|
||||
thingDiscovered(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Stops the discovery scan. We set {@link #scanning} to false (allowing the listening threads to end naturally
|
||||
* within {@link #TIMEOUT} * 5 time then shutdown the {@link ExecutorService}
|
||||
*/
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
super.stopScan();
|
||||
if (executorService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scanning = false;
|
||||
|
||||
try {
|
||||
executorService.awaitTermination(TIMEOUT * 5, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
executorService.shutdown();
|
||||
executorService = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.atlona.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.atlona.internal.AtlonaBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.atlona.internal.pro3.AtlonaPro3Config;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.sddp.SddpDevice;
|
||||
import org.openhab.core.config.discovery.sddp.SddpDiscoveryParticipant;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* Discovery Service for Atlona HDMI matrices that support SDDP.
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(immediate = true)
|
||||
public class AtlonaDiscoveryParticipant implements SddpDiscoveryParticipant {
|
||||
private final Logger logger = LoggerFactory.getLogger(AtlonaDiscoveryParticipant.class);
|
||||
|
||||
private static final String ATLONA = "ATLONA";
|
||||
private static final String PROXY_AVSWITCH = "avswitch";
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return Set.of(THING_TYPE_PRO3_44M, THING_TYPE_PRO3_66M, THING_TYPE_PRO3_88M, THING_TYPE_PRO3_1616M,
|
||||
THING_TYPE_PRO3HD_44M, THING_TYPE_PRO3HD_66M);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscoveryResult createResult(SddpDevice device) {
|
||||
final ThingUID uid = getThingUID(device);
|
||||
if (uid != null) {
|
||||
final Map<String, Object> properties = new HashMap<>(2);
|
||||
final String label = device.model + " (" + device.ipAddress + ")";
|
||||
|
||||
properties.put(Thing.PROPERTY_MAC_ADDRESS, device.macAddress);
|
||||
properties.put(AtlonaPro3Config.IP_ADDRESS, device.ipAddress);
|
||||
|
||||
final DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties)
|
||||
.withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).withLabel(label).build();
|
||||
|
||||
logger.debug("Created a DiscoveryResult for device '{}' with UID '{}'", label, uid.getId());
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The Atlona SDDP message has the following format
|
||||
*
|
||||
* <pre>
|
||||
* NOTIFY ALIVE SDDP/1.0
|
||||
* From: "192.168.1.30:1902"
|
||||
* Host: "AT-UHD-PRO3-88M_B898B0030F4D"
|
||||
* Type: "AT-UHD-PRO3-88M"
|
||||
* Max-Age: 1800
|
||||
* Primary-Proxy: "avswitch"
|
||||
* Proxies: "avswitch"
|
||||
* Manufacturer: "Atlona"
|
||||
* Model: "AT-UHD-PRO3-88M"
|
||||
* Driver: "avswitch_Atlona_AT-UHD-PRO3-88M_IP.c4i"
|
||||
* Config-URL: "http://192.168.1.30/"
|
||||
* </pre>
|
||||
*/
|
||||
@Override
|
||||
public @Nullable ThingUID getThingUID(SddpDevice device) {
|
||||
if (device.manufacturer.toUpperCase(Locale.ENGLISH).contains(ATLONA)
|
||||
&& PROXY_AVSWITCH.equals(device.primaryProxy) && !device.macAddress.isBlank()
|
||||
&& !device.ipAddress.isBlank()) {
|
||||
final ThingTypeUID typeId;
|
||||
|
||||
switch (device.model) {
|
||||
case "AT-UHD-PRO3-44M":
|
||||
typeId = THING_TYPE_PRO3_44M;
|
||||
break;
|
||||
case "AT-UHD-PRO3-66M":
|
||||
typeId = THING_TYPE_PRO3_66M;
|
||||
break;
|
||||
case "AT-UHD-PRO3-88M":
|
||||
typeId = THING_TYPE_PRO3_88M;
|
||||
break;
|
||||
case "AT-UHD-PRO3-1616M":
|
||||
typeId = THING_TYPE_PRO3_1616M;
|
||||
break;
|
||||
case "AT-PRO3HD44M":
|
||||
typeId = THING_TYPE_PRO3HD_44M;
|
||||
break;
|
||||
case "AT-PRO3HD66M":
|
||||
typeId = THING_TYPE_PRO3HD_66M;
|
||||
break;
|
||||
default:
|
||||
logger.warn("Unknown model #: {}", device.model);
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.debug("Atlona matrix with mac {} found at {}", device.macAddress, device.ipAddress);
|
||||
return new ThingUID(typeId, device.macAddress);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -8,4 +8,20 @@
|
||||
<description>Binding for Atlona PRO3 HDBaseT Matrix switches.</description>
|
||||
<connection>local</connection>
|
||||
|
||||
<discovery-methods>
|
||||
<discovery-method>
|
||||
<service-type>sddp</service-type>
|
||||
<match-properties>
|
||||
<match-property>
|
||||
<name>manufacturer</name>
|
||||
<regex>(?i).*atlona.*</regex>
|
||||
</match-property>
|
||||
<match-property>
|
||||
<name>model</name>
|
||||
<regex>(?i).*(AT-UHD-PRO3|AT-PRO3HD).*</regex>
|
||||
</match-property>
|
||||
</match-properties>
|
||||
</discovery-method>
|
||||
</discovery-methods>
|
||||
|
||||
</addon:addon>
|
||||
|
Loading…
Reference in New Issue
Block a user