mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-26 15:21:41 +01:00
Improve multicast implementation (#14199)
Fixes #14198 Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
parent
b9bec522ee
commit
f4268c4964
@ -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,117 +316,148 @@ 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);
|
// Get the address that we are going to connect to.
|
||||||
address2 = InetAddress.getByName(JSON_RPC_MULTICAST_IP2);
|
InetSocketAddress address1 = null;
|
||||||
} catch (UnknownHostException e) {
|
InetSocketAddress address2 = null;
|
||||||
logger.debug("An exception occurred while setting up the multicast receiver: '{}'", e.getMessage());
|
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 (!Thread.currentThread().isInterrupted()) {
|
||||||
|
MulticastSocket clientSocket = null;
|
||||||
|
try {
|
||||||
|
clientSocket = new MulticastSocket(JSON_RPC_PORT);
|
||||||
|
clientSocket.setSoTimeout(MULTICAST_TIMEOUT_MILLIS);
|
||||||
|
|
||||||
|
NetworkInterface networkInterface = getMulticastInterface(interfaceIpAddress);
|
||||||
|
if (networkInterface == null) {
|
||||||
|
logger.warn("Unable to find network interface for address {}", interfaceIpAddress);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
clientSocket.setNetworkInterface(networkInterface);
|
||||||
|
clientSocket.joinGroup(address1, null);
|
||||||
|
clientSocket.joinGroup(address2, null);
|
||||||
|
|
||||||
byte[] buf = new byte[256];
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
MulticastSocket clientSocket = null;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
try {
|
try {
|
||||||
clientSocket = new MulticastSocket(JSON_RPC_PORT);
|
byte[] buf = new byte[256];
|
||||||
clientSocket.setSoTimeout(100);
|
DatagramPacket packet = new DatagramPacket(buf, buf.length);
|
||||||
|
clientSocket.receive(packet);
|
||||||
|
|
||||||
clientSocket.setInterface(InetAddress.getByName((String) getConfig().get(INTERFACE)));
|
String event = new String(packet.getData(), packet.getOffset(), packet.getLength(),
|
||||||
clientSocket.joinGroup(address1);
|
StandardCharsets.ISO_8859_1);
|
||||||
clientSocket.joinGroup(address2);
|
logger.debug("Received a multicast event '{}' from '{}:{}'", event, packet.getAddress(),
|
||||||
|
packet.getPort());
|
||||||
|
|
||||||
while (true) {
|
String[] parts = event.split("&");
|
||||||
try {
|
String id = null, name = null, value = null;
|
||||||
buf = new byte[256];
|
for (String p : parts) {
|
||||||
DatagramPacket packet = new DatagramPacket(buf, buf.length);
|
String[] subparts = p.split("=");
|
||||||
clientSocket.receive(packet);
|
switch (subparts[0]) {
|
||||||
|
case "property": {
|
||||||
String event = new String(packet.getData());
|
name = subparts[1];
|
||||||
logger.debug("Received a multicast event '{}' from '{}:{}'", event, packet.getAddress(),
|
break;
|
||||||
packet.getPort());
|
|
||||||
|
|
||||||
String[] parts = event.split("&");
|
|
||||||
String id = null, name = null, value = null;
|
|
||||||
for (String p : parts) {
|
|
||||||
String[] subparts = p.split("=");
|
|
||||||
switch (subparts[0]) {
|
|
||||||
case "property": {
|
|
||||||
name = subparts[1];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "value": {
|
|
||||||
value = subparts[1].strip().trim();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "id": {
|
|
||||||
id = subparts[1];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
case "value": {
|
||||||
if (id == null || name == null || value == null) {
|
value = subparts[1];
|
||||||
continue;
|
break;
|
||||||
}
|
}
|
||||||
|
case "id": {
|
||||||
// In XGW 3000 firmware 2.03 this was changed from UID (hdm:ZigBee:0123456789abcdef#210)
|
id = subparts[1];
|
||||||
// to serial number (001234567890)
|
|
||||||
FullyQualifiedApplianceIdentifier applianceIdentifier;
|
|
||||||
if (id.startsWith("hdm:")) {
|
|
||||||
applianceIdentifier = new FullyQualifiedApplianceIdentifier(id);
|
|
||||||
} else {
|
|
||||||
HomeDevice device = cachedHomeDevicesByRemoteUid.get(id);
|
|
||||||
if (device == null) {
|
|
||||||
logger.debug("Multicast event not handled as id {} is unknown.", id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
applianceIdentifier = device.getApplianceIdentifier();
|
|
||||||
}
|
|
||||||
var deviceProperty = new DeviceProperty();
|
|
||||||
deviceProperty.Name = name;
|
|
||||||
deviceProperty.Value = value;
|
|
||||||
ApplianceStatusListener listener = applianceStatusListeners
|
|
||||||
.get(applianceIdentifier.getApplianceId());
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onAppliancePropertyChanged(deviceProperty);
|
|
||||||
}
|
|
||||||
} catch (SocketTimeoutException e) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(500);
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
logger.debug("Event listener has been interrupted.");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
|
||||||
logger.debug("An exception occurred while receiving multicast packets: '{}'", ex.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
// restart the cycle with a clean slate
|
if (id == null || name == null || value == null) {
|
||||||
try {
|
continue;
|
||||||
if (clientSocket != null) {
|
}
|
||||||
clientSocket.leaveGroup(address1);
|
|
||||||
clientSocket.leaveGroup(address2);
|
// In XGW 3000 firmware 2.03 this was changed from UID (hdm:ZigBee:0123456789abcdef#210)
|
||||||
|
// to serial number (001234567890)
|
||||||
|
FullyQualifiedApplianceIdentifier applianceIdentifier;
|
||||||
|
if (id.startsWith("hdm:")) {
|
||||||
|
applianceIdentifier = new FullyQualifiedApplianceIdentifier(id);
|
||||||
|
} else {
|
||||||
|
HomeDevice device = cachedHomeDevicesByRemoteUid.get(id);
|
||||||
|
if (device == null) {
|
||||||
|
logger.debug("Multicast event not handled as id {} is unknown.", id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
applianceIdentifier = device.getApplianceIdentifier();
|
||||||
|
}
|
||||||
|
var deviceProperty = new DeviceProperty();
|
||||||
|
deviceProperty.Name = name;
|
||||||
|
deviceProperty.Value = value;
|
||||||
|
ApplianceStatusListener listener = applianceStatusListeners
|
||||||
|
.get(applianceIdentifier.getApplianceId());
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onAppliancePropertyChanged(deviceProperty);
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(MULTICAST_SLEEP_MILLIS);
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
logger.debug("Event listener has been interrupted.");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
|
||||||
logger.debug("An exception occurred while leaving multicast group: '{}'", e.getMessage());
|
|
||||||
}
|
|
||||||
if (clientSocket != null) {
|
|
||||||
clientSocket.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.debug("An exception occurred while receiving multicast packets: '{}'", e.getMessage());
|
||||||
|
} finally {
|
||||||
|
// restart the cycle with a clean slate
|
||||||
|
try {
|
||||||
|
if (clientSocket != null) {
|
||||||
|
clientSocket.leaveGroup(address1, null);
|
||||||
|
clientSocket.leaveGroup(address2, null);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.debug("An exception occurred while leaving multicast group: '{}'", e.getMessage());
|
||||||
|
}
|
||||||
|
if (clientSocket != null) {
|
||||||
|
clientSocket.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} 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());
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user