Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[miele] Improve multicast implementation #14199

Merged
merged 1 commit into from
Jan 14, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.IllformedLocaleException;
import java.util.Iterator;
import java.util.List;
Expand Down Expand Up @@ -69,7 +75,7 @@
* @author Karel Goderis - Initial contribution
* @author Kai Kreuzer - Fixed lifecycle issues
* @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
public class MieleBridgeHandler extends BaseBridgeHandler {
Expand All @@ -79,10 +85,12 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
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])$");

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 String JSON_RPC_MULTICAST_IP1 = "239.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);

Expand Down Expand Up @@ -308,117 +316,148 @@ private List<HomeDevice> getHomeDevices() throws MieleRpcException {
}

private Runnable eventListenerRunnable = () -> {
if (IP_PATTERN.matcher((String) getConfig().get(INTERFACE)).matches()) {
while (true) {
// Get the address that we are going to connect to.
InetAddress address1 = null;
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());
}
String interfaceIpAddress = (String) getConfig().get(INTERFACE);
if (!IP_PATTERN.matcher(interfaceIpAddress).matches()) {
logger.debug("Invalid IP address for the multicast interface: '{}'", interfaceIpAddress);
return;
}

byte[] buf = new byte[256];
MulticastSocket clientSocket = null;
// Get the address that we are going to connect to.
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) {
try {
clientSocket = new MulticastSocket(JSON_RPC_PORT);
clientSocket.setSoTimeout(100);

clientSocket.setInterface(InetAddress.getByName((String) getConfig().get(INTERFACE)));
clientSocket.joinGroup(address1);
clientSocket.joinGroup(address2);

while (true) {
try {
buf = new byte[256];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
clientSocket.receive(packet);

String event = new String(packet.getData());
logger.debug("Received a multicast event '{}' from '{}:{}'", event, packet.getAddress(),
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;
}
}
}
while (!Thread.currentThread().isInterrupted()) {
MulticastSocket clientSocket = null;
try {
clientSocket = new MulticastSocket(JSON_RPC_PORT);
clientSocket.setSoTimeout(MULTICAST_TIMEOUT_MILLIS);

if (id == null || name == null || value == null) {
continue;
}
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);

// 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();
while (!Thread.currentThread().isInterrupted()) {
try {
byte[] buf = new byte[256];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
clientSocket.receive(packet);

String event = new String(packet.getData(), packet.getOffset(), packet.getLength(),
StandardCharsets.ISO_8859_1);
logger.debug("Received a multicast event '{}' from '{}:{}'", event, packet.getAddress(),
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;
}
var deviceProperty = new DeviceProperty();
deviceProperty.Name = name;
deviceProperty.Value = value;
ApplianceStatusListener listener = applianceStatusListeners
.get(applianceIdentifier.getApplianceId());
if (listener != null) {
listener.onAppliancePropertyChanged(deviceProperty);
case "value": {
value = subparts[1];
break;
}
} catch (SocketTimeoutException e) {
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
logger.debug("Event listener has been interrupted.");
case "id": {
id = subparts[1];
break;
}
}
}
} catch (Exception ex) {
logger.debug("An exception occurred while receiving multicast packets: '{}'", ex.getMessage());
}

// restart the cycle with a clean slate
try {
if (clientSocket != null) {
clientSocket.leaveGroup(address1);
clientSocket.leaveGroup(address2);
if (id == null || name == null || value == null) {
continue;
}

// 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());
}
}
} 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.close();
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());
}
lolodomo marked this conversation as resolved.
Show resolved Hide resolved
if (interfaceAddress.getAddress().toString().endsWith("/" + interfaceIpAddress)) {
return networkInterface;
}
}
}

return null;
}

public JsonElement invokeOperation(String applianceId, String modelID, String methodName) throws MieleRpcException {
if (getThing().getStatus() != ThingStatus.ONLINE) {
throw new MieleRpcException("Bridge is offline, operations can not be invoked");
Expand All @@ -437,8 +476,8 @@ private synchronized void schedulePollingAndEventListener() {
logger.debug("Scheduling the Miele polling job");
ScheduledFuture<?> pollingJob = this.pollingJob;
if (pollingJob == null || pollingJob.isCancelled()) {
logger.trace("Scheduling the Miele polling job period is {}", POLLING_PERIOD);
pollingJob = scheduler.scheduleWithFixedDelay(pollingRunnable, 0, POLLING_PERIOD, TimeUnit.SECONDS);
logger.trace("Scheduling the Miele polling job period is {}", POLLING_PERIOD_SECONDS);
pollingJob = scheduler.scheduleWithFixedDelay(pollingRunnable, 0, POLLING_PERIOD_SECONDS, TimeUnit.SECONDS);
this.pollingJob = pollingJob;
logger.trace("Scheduling the Miele polling job Job is done ?{}", pollingJob.isDone());
}
Expand Down