diff --git a/bundles/org.openhab.binding.atlona/README.md b/bundles/org.openhab.binding.atlona/README.md
index 121563ebe0c..9330fbb3e4e 100644
--- a/bundles/org.openhab.binding.atlona/README.md
+++ b/bundles/org.openhab.binding.atlona/README.md
@@ -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
diff --git a/bundles/org.openhab.binding.atlona/src/main/feature/feature.xml b/bundles/org.openhab.binding.atlona/src/main/feature/feature.xml
index 729fcb94d64..ea775b169e5 100644
--- a/bundles/org.openhab.binding.atlona/src/main/feature/feature.xml
+++ b/bundles/org.openhab.binding.atlona/src/main/feature/feature.xml
@@ -4,6 +4,7 @@
openhab-runtime-base
+ openhab-core-config-discovery-sddp
mvn:org.openhab.addons.bundles/org.openhab.binding.atlona/${project.version}
diff --git a/bundles/org.openhab.binding.atlona/src/main/java/org/openhab/binding/atlona/internal/discovery/AtlonaDiscovery.java b/bundles/org.openhab.binding.atlona/src/main/java/org/openhab/binding/atlona/internal/discovery/AtlonaDiscovery.java
deleted file mode 100644
index 3e9c80660db..00000000000
--- a/bundles/org.openhab.binding.atlona/src/main/java/org/openhab/binding/atlona/internal/discovery/AtlonaDiscovery.java
+++ /dev/null
@@ -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:
- *
- * - Request all the network interfaces
- * - For each network interface, create a listening thread using {@link #executorService}
- * - 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
- * - The {@link DatagramPacket} is then investigated to see if is a SDDP packet and will create a new thing from
- * it
- *
- * 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 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
- *
- *
- * 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/"
- *
- *
- * 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 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;
- }
-}
diff --git a/bundles/org.openhab.binding.atlona/src/main/java/org/openhab/binding/atlona/internal/discovery/AtlonaDiscoveryParticipant.java b/bundles/org.openhab.binding.atlona/src/main/java/org/openhab/binding/atlona/internal/discovery/AtlonaDiscoveryParticipant.java
new file mode 100644
index 00000000000..144d6f8cb7d
--- /dev/null
+++ b/bundles/org.openhab.binding.atlona/src/main/java/org/openhab/binding/atlona/internal/discovery/AtlonaDiscoveryParticipant.java
@@ -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 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 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
+ *
+ *
+ * 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/"
+ *
+ */
+ @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;
+ }
+}
diff --git a/bundles/org.openhab.binding.atlona/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.atlona/src/main/resources/OH-INF/addon/addon.xml
index 491c7b8940f..ff8c4578874 100644
--- a/bundles/org.openhab.binding.atlona/src/main/resources/OH-INF/addon/addon.xml
+++ b/bundles/org.openhab.binding.atlona/src/main/resources/OH-INF/addon/addon.xml
@@ -8,4 +8,20 @@
Binding for Atlona PRO3 HDBaseT Matrix switches.
local
+
+
+ sddp
+
+
+ manufacturer
+ (?i).*atlona.*
+
+
+ model
+ (?i).*(AT-UHD-PRO3|AT-PRO3HD).*
+
+
+
+
+