Improve multicast implementation (#14199)

Fixes #14198

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
Jacob Laursen 2023-01-14 12:26:38 +01:00 committed by GitHub
parent b9bec522ee
commit f4268c4964
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -17,11 +17,17 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramPacket; import java.net.DatagramPacket;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.MulticastSocket; import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Enumeration;
import java.util.IllformedLocaleException; import java.util.IllformedLocaleException;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -69,7 +75,7 @@ import com.google.gson.JsonElement;
* @author Karel Goderis - Initial contribution * @author Karel Goderis - Initial contribution
* @author Kai Kreuzer - Fixed lifecycle issues * @author Kai Kreuzer - Fixed lifecycle issues
* @author Martin Lepsy - Added protocol information to support WiFi devices & some refactoring for HomeDevice * @author Martin Lepsy - Added protocol information to support WiFi devices & some refactoring for HomeDevice
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) * @author Jacob Laursen - Fixed multicast and protocol support (Zigbee/LAN)
**/ **/
@NonNullByDefault @NonNullByDefault
public class MieleBridgeHandler extends BaseBridgeHandler { public class MieleBridgeHandler extends BaseBridgeHandler {
@ -79,10 +85,12 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
private static final Pattern IP_PATTERN = Pattern private static final Pattern IP_PATTERN = Pattern
.compile("^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"); .compile("^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");
private static final int POLLING_PERIOD = 15; // in seconds private static final int POLLING_PERIOD_SECONDS = 15;
private static final int JSON_RPC_PORT = 2810; private static final int JSON_RPC_PORT = 2810;
private static final String JSON_RPC_MULTICAST_IP1 = "239.255.68.139"; private static final String JSON_RPC_MULTICAST_IP1 = "239.255.68.139";
private static final String JSON_RPC_MULTICAST_IP2 = "224.255.68.139"; private static final String JSON_RPC_MULTICAST_IP2 = "224.255.68.139";
private static final int MULTICAST_TIMEOUT_MILLIS = 100;
private static final int MULTICAST_SLEEP_MILLIS = 500;
private final Logger logger = LoggerFactory.getLogger(MieleBridgeHandler.class); private final Logger logger = LoggerFactory.getLogger(MieleBridgeHandler.class);
@ -308,37 +316,47 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
} }
private Runnable eventListenerRunnable = () -> { private Runnable eventListenerRunnable = () -> {
if (IP_PATTERN.matcher((String) getConfig().get(INTERFACE)).matches()) { String interfaceIpAddress = (String) getConfig().get(INTERFACE);
while (true) { if (!IP_PATTERN.matcher(interfaceIpAddress).matches()) {
// Get the address that we are going to connect to. logger.debug("Invalid IP address for the multicast interface: '{}'", interfaceIpAddress);
InetAddress address1 = null; return;
InetAddress address2 = null;
try {
address1 = InetAddress.getByName(JSON_RPC_MULTICAST_IP1);
address2 = InetAddress.getByName(JSON_RPC_MULTICAST_IP2);
} catch (UnknownHostException e) {
logger.debug("An exception occurred while setting up the multicast receiver: '{}'", e.getMessage());
} }
byte[] buf = new byte[256]; // Get the address that we are going to connect to.
MulticastSocket clientSocket = null; InetSocketAddress address1 = null;
InetSocketAddress address2 = null;
try {
address1 = new InetSocketAddress(InetAddress.getByName(JSON_RPC_MULTICAST_IP1), JSON_RPC_PORT);
address2 = new InetSocketAddress(InetAddress.getByName(JSON_RPC_MULTICAST_IP2), JSON_RPC_PORT);
} catch (UnknownHostException e) {
// This can only happen if the hardcoded literal IP addresses are invalid.
logger.debug("An exception occurred while setting up the multicast receiver: '{}'", e.getMessage());
return;
}
while (true) { while (!Thread.currentThread().isInterrupted()) {
MulticastSocket clientSocket = null;
try { try {
clientSocket = new MulticastSocket(JSON_RPC_PORT); clientSocket = new MulticastSocket(JSON_RPC_PORT);
clientSocket.setSoTimeout(100); clientSocket.setSoTimeout(MULTICAST_TIMEOUT_MILLIS);
clientSocket.setInterface(InetAddress.getByName((String) getConfig().get(INTERFACE))); NetworkInterface networkInterface = getMulticastInterface(interfaceIpAddress);
clientSocket.joinGroup(address1); if (networkInterface == null) {
clientSocket.joinGroup(address2); logger.warn("Unable to find network interface for address {}", interfaceIpAddress);
return;
}
clientSocket.setNetworkInterface(networkInterface);
clientSocket.joinGroup(address1, null);
clientSocket.joinGroup(address2, null);
while (true) { while (!Thread.currentThread().isInterrupted()) {
try { try {
buf = new byte[256]; byte[] buf = new byte[256];
DatagramPacket packet = new DatagramPacket(buf, buf.length); DatagramPacket packet = new DatagramPacket(buf, buf.length);
clientSocket.receive(packet); clientSocket.receive(packet);
String event = new String(packet.getData()); String event = new String(packet.getData(), packet.getOffset(), packet.getLength(),
StandardCharsets.ISO_8859_1);
logger.debug("Received a multicast event '{}' from '{}:{}'", event, packet.getAddress(), logger.debug("Received a multicast event '{}' from '{}:{}'", event, packet.getAddress(),
packet.getPort()); packet.getPort());
@ -352,7 +370,7 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
break; break;
} }
case "value": { case "value": {
value = subparts[1].strip().trim(); value = subparts[1];
break; break;
} }
case "id": { case "id": {
@ -389,22 +407,22 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
} }
} catch (SocketTimeoutException e) { } catch (SocketTimeoutException e) {
try { try {
Thread.sleep(500); Thread.sleep(MULTICAST_SLEEP_MILLIS);
} catch (InterruptedException ex) { } catch (InterruptedException ex) {
Thread.currentThread().interrupt();
logger.debug("Event listener has been interrupted."); logger.debug("Event listener has been interrupted.");
break; break;
} }
} }
} }
} catch (Exception ex) { } catch (IOException e) {
logger.debug("An exception occurred while receiving multicast packets: '{}'", ex.getMessage()); logger.debug("An exception occurred while receiving multicast packets: '{}'", e.getMessage());
} } finally {
// restart the cycle with a clean slate // restart the cycle with a clean slate
try { try {
if (clientSocket != null) { if (clientSocket != null) {
clientSocket.leaveGroup(address1); clientSocket.leaveGroup(address1, null);
clientSocket.leaveGroup(address2); clientSocket.leaveGroup(address2, null);
} }
} catch (IOException e) { } catch (IOException e) {
logger.debug("An exception occurred while leaving multicast group: '{}'", e.getMessage()); logger.debug("An exception occurred while leaving multicast group: '{}'", e.getMessage());
@ -414,11 +432,32 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
} }
} }
} }
} else {
logger.debug("Invalid IP address for the multicast interface: '{}'", getConfig().get(INTERFACE));
}
}; };
private @Nullable NetworkInterface getMulticastInterface(String interfaceIpAddress) throws SocketException {
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
@Nullable
NetworkInterface networkInterface;
while (networkInterfaces.hasMoreElements()) {
networkInterface = networkInterfaces.nextElement();
if (networkInterface.isLoopback()) {
continue;
}
for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
if (logger.isTraceEnabled()) {
logger.trace("Found interface address {} -> {}", interfaceAddress.toString(),
interfaceAddress.getAddress().toString());
}
if (interfaceAddress.getAddress().toString().endsWith("/" + interfaceIpAddress)) {
return networkInterface;
}
}
}
return null;
}
public JsonElement invokeOperation(String applianceId, String modelID, String methodName) throws MieleRpcException { public JsonElement invokeOperation(String applianceId, String modelID, String methodName) throws MieleRpcException {
if (getThing().getStatus() != ThingStatus.ONLINE) { if (getThing().getStatus() != ThingStatus.ONLINE) {
throw new MieleRpcException("Bridge is offline, operations can not be invoked"); throw new MieleRpcException("Bridge is offline, operations can not be invoked");
@ -437,8 +476,8 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
logger.debug("Scheduling the Miele polling job"); logger.debug("Scheduling the Miele polling job");
ScheduledFuture<?> pollingJob = this.pollingJob; ScheduledFuture<?> pollingJob = this.pollingJob;
if (pollingJob == null || pollingJob.isCancelled()) { if (pollingJob == null || pollingJob.isCancelled()) {
logger.trace("Scheduling the Miele polling job period is {}", POLLING_PERIOD); logger.trace("Scheduling the Miele polling job period is {}", POLLING_PERIOD_SECONDS);
pollingJob = scheduler.scheduleWithFixedDelay(pollingRunnable, 0, POLLING_PERIOD, TimeUnit.SECONDS); pollingJob = scheduler.scheduleWithFixedDelay(pollingRunnable, 0, POLLING_PERIOD_SECONDS, TimeUnit.SECONDS);
this.pollingJob = pollingJob; this.pollingJob = pollingJob;
logger.trace("Scheduling the Miele polling job Job is done ?{}", pollingJob.isDone()); logger.trace("Scheduling the Miele polling job Job is done ?{}", pollingJob.isDone());
} }