diff --git a/bundles/org.openhab.core.config.discovery.addon.ip/src/main/java/org/openhab/core/config/discovery/addon/ip/IpAddonFinder.java b/bundles/org.openhab.core.config.discovery.addon.ip/src/main/java/org/openhab/core/config/discovery/addon/ip/IpAddonFinder.java
index 1f658db18..822e29e2f 100644
--- a/bundles/org.openhab.core.config.discovery.addon.ip/src/main/java/org/openhab/core/config/discovery/addon/ip/IpAddonFinder.java
+++ b/bundles/org.openhab.core.config.discovery.addon.ip/src/main/java/org/openhab/core/config/discovery/addon/ip/IpAddonFinder.java
@@ -12,7 +12,8 @@
*/
package org.openhab.core.config.discovery.addon.ip;
-import static org.openhab.core.config.discovery.addon.AddonFinderConstants.*;
+import static org.openhab.core.config.discovery.addon.AddonFinderConstants.SERVICE_NAME_IP;
+import static org.openhab.core.config.discovery.addon.AddonFinderConstants.SERVICE_TYPE_IP;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -21,7 +22,9 @@ import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
import java.net.SocketAddress;
+import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions;
@@ -34,6 +37,7 @@ import java.text.ParseException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.HexFormat;
+import java.util.IllegalFormatException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -139,6 +143,11 @@ import org.slf4j.LoggerFactory;
*
timeout to wait for a answers |
* |
*
+ *
+ * {@code fmtMac} |
+ * format specifier string for mac address |
+ * e.g. '%02X', '%02X:', '%02x-' |
+ *
*
*
*
@@ -154,6 +163,11 @@ import org.slf4j.LoggerFactory;
* {@code $srcPort} |
* source port |
*
+ *
+ * {@code $srcMac} |
+ * source mac address |
+ *
+ *
* {@code $uuid} |
* String returned by {@code java.util.UUID.randomUUID()} |
*
@@ -201,6 +215,8 @@ public class IpAddonFinder extends BaseAddonFinder implements NetworkAddressChan
private static final String PARAMETER_REQUEST_PLAIN = "requestPlain";
private static final String PARAMETER_SRC_IP = "srcIp";
private static final String PARAMETER_SRC_PORT = "srcPort";
+ private static final String PARAMETER_SRC_MAC = "srcMac";
+ private static final String PARAMETER_MAC_FORMAT = "fmtMac";
private static final String PARAMETER_TIMEOUT_MS = "timeoutMs";
private static final String REPLACEMENT_UUID = "uuid";
@@ -346,16 +362,22 @@ public class IpAddonFinder extends BaseAddonFinder implements NetworkAddressChan
continue;
}
}
+ String macFormat = parameters.getOrDefault(PARAMETER_MAC_FORMAT, "%02X:");
+ try {
+ String.format(macFormat, 123);
+ } catch (IllegalFormatException e) {
+ logger.warn("{}: discovery-parameter '{}' invalid format specifier", candidate.getUID(), macFormat);
+ }
// handle known types
try {
switch (Objects.toString(type)) {
case TYPE_IP_BROADCAST:
- scanBroadcast(candidate, request, requestPlain, response, timeoutMs, destPort);
+ scanBroadcast(candidate, request, requestPlain, response, timeoutMs, destPort, macFormat);
break;
case TYPE_IP_MULTICAST:
scanMulticast(candidate, request, requestPlain, response, timeoutMs, listenPort, destIp,
- destPort);
+ destPort, macFormat);
break;
default:
logger.warn("{}: discovery-parameter type \"{}\" is unknown", candidate.getUID(), type);
@@ -369,7 +391,7 @@ public class IpAddonFinder extends BaseAddonFinder implements NetworkAddressChan
}
private void scanBroadcast(AddonInfo candidate, String request, String requestPlain, String response, int timeoutMs,
- int destPort) throws ParseException {
+ int destPort, String macFormat) throws ParseException {
if (request.isEmpty() && requestPlain.isEmpty()) {
logger.warn("{}: match-property request and requestPlain \"{}\" is unknown", candidate.getUID(),
TYPE_IP_BROADCAST);
@@ -391,7 +413,7 @@ public class IpAddonFinder extends BaseAddonFinder implements NetworkAddressChan
socket.setBroadcast(true);
socket.setSoTimeout(timeoutMs);
byte[] sendBuffer = requestPlain.isEmpty() ? buildRequestArray(socket.getLocalSocketAddress(), request)
- : buildRequestArrayPlain(socket.getLocalSocketAddress(), requestPlain);
+ : buildRequestArrayPlain(socket.getLocalSocketAddress(), requestPlain, macFormat);
DatagramPacket sendPacket = new DatagramPacket(sendBuffer, sendBuffer.length,
InetAddress.getByName(broadcastAddress), destPort);
socket.send(sendPacket);
@@ -431,7 +453,7 @@ public class IpAddonFinder extends BaseAddonFinder implements NetworkAddressChan
}
private void scanMulticast(AddonInfo candidate, String request, String requestPlain, String response, int timeoutMs,
- int listenPort, @Nullable InetAddress destIp, int destPort) throws ParseException {
+ int listenPort, @Nullable InetAddress destIp, int destPort, String macFormat) throws ParseException {
List ipAddresses = NetUtil.getAllInterfaceAddresses().stream()
.filter(a -> a.getAddress() instanceof Inet4Address).map(a -> a.getAddress().getHostAddress()).toList();
@@ -443,7 +465,7 @@ public class IpAddonFinder extends BaseAddonFinder implements NetworkAddressChan
Selector selector = Selector.open()) {
byte[] requestArray = "".equals(requestPlain)
? buildRequestArray(channel.getLocalAddress(), Objects.toString(request))
- : buildRequestArrayPlain(channel.getLocalAddress(), Objects.toString(requestPlain));
+ : buildRequestArrayPlain(channel.getLocalAddress(), Objects.toString(requestPlain), macFormat);
if (logger.isTraceEnabled()) {
InetSocketAddress sock = (InetSocketAddress) channel.getLocalAddress();
String id = candidate.getUID();
@@ -489,7 +511,7 @@ public class IpAddonFinder extends BaseAddonFinder implements NetworkAddressChan
}
// build from plaintext string
- private byte[] buildRequestArrayPlain(SocketAddress address, String request)
+ private byte[] buildRequestArrayPlain(SocketAddress address, String request, String macFormat)
throws java.io.IOException, ParseException {
InetSocketAddress sock = (InetSocketAddress) address;
@@ -502,6 +524,9 @@ public class IpAddonFinder extends BaseAddonFinder implements NetworkAddressChan
while ((p = req.indexOf("$" + PARAMETER_SRC_PORT)) != -1) {
req.replace(p, p + PARAMETER_SRC_PORT.length() + 1, "" + sock.getPort());
}
+ while ((p = req.indexOf("$" + PARAMETER_SRC_MAC)) != -1) {
+ req.replace(p, p + PARAMETER_SRC_MAC.length() + 1, macFormat(macFormat, macBytesFrom(sock)));
+ }
while ((p = req.indexOf("$" + REPLACEMENT_UUID)) != -1) {
req.replace(p, p + REPLACEMENT_UUID.length() + 1, UUID.randomUUID().toString());
}
@@ -531,6 +556,10 @@ public class IpAddonFinder extends BaseAddonFinder implements NetworkAddressChan
requestFrame.write((byte) ((dPort >> 8) & 0xff));
requestFrame.write((byte) (dPort & 0xff));
break;
+ case "$" + PARAMETER_SRC_MAC:
+ byte[] mac = macBytesFrom(sock);
+ requestFrame.write(mac);
+ break;
case "$" + REPLACEMENT_UUID:
String uuid = UUID.randomUUID().toString();
requestFrame.write(uuid.getBytes());
@@ -567,4 +596,36 @@ public class IpAddonFinder extends BaseAddonFinder implements NetworkAddressChan
}
return false;
}
+
+ /**
+ * Get mac address bytes associated with the given Internet socket address
+ *
+ * @param inetSocketAddress the Internet address
+ * @return the mac address as an array of bytes
+ * @throws SocketException if address is not on this PC, or no mac address is associated
+ */
+ private byte[] macBytesFrom(InetSocketAddress inetSocketAddress) throws SocketException {
+ NetworkInterface networkInterface = NetworkInterface.getByInetAddress(inetSocketAddress.getAddress());
+ if (networkInterface == null) {
+ throw new SocketException("No network interface");
+ }
+ return networkInterface.getHardwareAddress();
+ }
+
+ /**
+ * Use the given format specifier to format an array of mac address bytes
+ *
+ * @param format a standard format specifier; optionally ends with a delimiter e.g. "%02x:" or "%02X"
+ * @param bytes the mac address as an array of bytes
+ *
+ * @return e.g. '01:02:03:04:A5:B6:C7:D8', '01-02-03-04:a5:b6:c7:d8', or '01020304A5B6C7D8'
+ */
+ private String macFormat(String format, byte[] bytes) {
+ StringBuilder result = new StringBuilder();
+ for (byte byt : bytes) {
+ result.append(String.format(format, byt));
+ }
+ boolean isDelimited = Set.of(':', '-', '.').contains(format.charAt(format.length() - 1));
+ return (isDelimited ? result.substring(0, result.length() - 1) : result).toString();
+ }
}