mirror of
https://github.com/danieldemus/openhab-addons
synced 2026-07-29 12:34:21 +02:00
[miele] Add auto-detection of network interface for multicast events (#19516)
* Improve multicast interface configuration Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
@@ -48,7 +48,6 @@ discovery.miele:removalGracePeriod=30
|
||||
| Configuration Parameter | Description |
|
||||
|-------------------------|----------------------------------------------------------------------------------------------------------------------------------|
|
||||
| ipAddress | Network address of the Miele@home gateway |
|
||||
| interface | Network address of openHAB host interface where the binding will listen for multicast events coming from the Miele@home gateway. |
|
||||
| userName | Name of a registered Miele@home user. |
|
||||
| password | Password for the registered Miele@home user. |
|
||||
| language | Language for state, program and phase texts. Leave blank for system language. |
|
||||
@@ -423,7 +422,7 @@ See oven.
|
||||
## things/miele.things
|
||||
|
||||
```java
|
||||
Bridge miele:xgw3000:home [ipAddress="192.168.0.18", interface="192.168.0.5"] {
|
||||
Bridge miele:xgw3000:home [ipAddress="192.168.0.18"] {
|
||||
Things:
|
||||
Thing fridgefreezer freezer [uid="00124b000424be44#2"]
|
||||
Thing hood hood [uid="001d63fffe020685#210"]
|
||||
|
||||
-1
@@ -119,7 +119,6 @@ public class MieleBindingConstants {
|
||||
|
||||
// Bridge config properties
|
||||
public static final String HOST = "ipAddress";
|
||||
public static final String INTERFACE = "interface";
|
||||
public static final String USER_NAME = "userName";
|
||||
public static final String PASSWORD = "password";
|
||||
public static final String LANGUAGE = "language";
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.miele.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link XGW3000Configuration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Jacob Laursen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class XGW3000Configuration {
|
||||
public String ipAddress = "";
|
||||
public String userName = "";
|
||||
public String password = "";
|
||||
public String language = "";
|
||||
}
|
||||
+2
-23
@@ -12,10 +12,7 @@
|
||||
*/
|
||||
package org.openhab.binding.miele.internal.discovery;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -116,26 +113,8 @@ public class MieleMDNSDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||
return null;
|
||||
}
|
||||
|
||||
String ipAddress = addresses[0].getHostAddress();
|
||||
Map<String, Object> properties = new HashMap<>(2);
|
||||
properties.put(MieleBindingConstants.HOST, ipAddress);
|
||||
|
||||
Socket socket = null;
|
||||
try {
|
||||
socket = new Socket(addresses[0], 80);
|
||||
InetAddress ourAddress = socket.getLocalAddress();
|
||||
String interfaceIpAddress = ourAddress.getHostAddress();
|
||||
socket.close();
|
||||
|
||||
properties.put(MieleBindingConstants.INTERFACE, interfaceIpAddress);
|
||||
logger.debug("Discovered Miele@home gateway with IP address {} and interface IP address {}", ipAddress,
|
||||
interfaceIpAddress);
|
||||
} catch (IOException e) {
|
||||
logger.warn("An exception occurred while connecting to the Miele Gateway: '{}'", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
return DiscoveryResultBuilder.create(uid).withProperties(properties)
|
||||
return DiscoveryResultBuilder.create(uid)
|
||||
.withProperty(MieleBindingConstants.HOST, addresses[0].getHostAddress())
|
||||
.withRepresentationProperty(MieleBindingConstants.HOST).withLabel("@text/discovery.xgw3000.label")
|
||||
.build();
|
||||
}
|
||||
|
||||
+32
-55
@@ -16,9 +16,9 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.MulticastSocket;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
@@ -28,7 +28,6 @@ import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.IllformedLocaleException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -42,7 +41,6 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
@@ -52,10 +50,10 @@ import org.openhab.binding.miele.internal.MieleGatewayCommunicationController;
|
||||
import org.openhab.binding.miele.internal.api.dto.DeviceClassObject;
|
||||
import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
|
||||
import org.openhab.binding.miele.internal.api.dto.HomeDevice;
|
||||
import org.openhab.binding.miele.internal.config.XGW3000Configuration;
|
||||
import org.openhab.binding.miele.internal.discovery.MieleApplianceDiscoveryService;
|
||||
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
|
||||
import org.openhab.core.common.NamedThreadFactory;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
@@ -85,9 +83,6 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_XGW3000);
|
||||
|
||||
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_SECONDS = 15;
|
||||
private static final int JSON_RPC_PORT = 2810;
|
||||
private static final String JSON_RPC_MULTICAST_IP1 = "239.255.68.139";
|
||||
@@ -101,6 +96,7 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final Gson gson = new Gson();
|
||||
private XGW3000Configuration config;
|
||||
private @NonNullByDefault({}) MieleGatewayCommunicationController gatewayCommunication;
|
||||
|
||||
private Set<DiscoveryListener> discoveryListeners = ConcurrentHashMap.newKeySet();
|
||||
@@ -115,18 +111,23 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
|
||||
public MieleBridgeHandler(Bridge bridge, HttpClient httpClient) {
|
||||
super(bridge);
|
||||
this.httpClient = httpClient;
|
||||
|
||||
// Default configuration
|
||||
this.config = new XGW3000Configuration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing handler for bridge {}", getThing().getUID());
|
||||
|
||||
if (!validateConfig(getConfig())) {
|
||||
config = getConfigAs(XGW3000Configuration.class);
|
||||
|
||||
if (!validateConfig()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
gatewayCommunication = new MieleGatewayCommunicationController(httpClient, (String) getConfig().get(HOST));
|
||||
gatewayCommunication = new MieleGatewayCommunicationController(httpClient, config.ipAddress);
|
||||
} catch (URISyntaxException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, e.getMessage());
|
||||
return;
|
||||
@@ -142,30 +143,18 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
|
||||
return Set.of(MieleApplianceDiscoveryService.class);
|
||||
}
|
||||
|
||||
private boolean validateConfig(Configuration config) {
|
||||
if (config.get(HOST) == null || ((String) config.get(HOST)).isBlank()) {
|
||||
private boolean validateConfig() {
|
||||
if (config.ipAddress.isBlank()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
|
||||
"@text/offline.configuration-error.ip-address-not-set");
|
||||
return false;
|
||||
}
|
||||
if (config.get(INTERFACE) == null || ((String) config.get(INTERFACE)).isBlank()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
|
||||
"@text/offline.configuration-error.ip-multicast-interface-not-set");
|
||||
return false;
|
||||
}
|
||||
if (!IP_PATTERN.matcher((String) config.get(INTERFACE)).matches()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
|
||||
"@text/offline.configuration-error.invalid-ip-multicast-interface [\"" + config.get(INTERFACE)
|
||||
+ "\"]");
|
||||
return false;
|
||||
}
|
||||
String language = (String) config.get(LANGUAGE);
|
||||
if (language != null && !language.isBlank()) {
|
||||
if (!config.language.isBlank()) {
|
||||
try {
|
||||
new Locale.Builder().setLanguageTag(language).build();
|
||||
new Locale.Builder().setLanguageTag(config.language).build();
|
||||
} catch (IllformedLocaleException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
|
||||
"@text/offline.configuration-error.invalid-language [\"" + language + "\"]");
|
||||
"@text/offline.configuration-error.invalid-language [\"" + config.language + "\"]");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -175,12 +164,11 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
|
||||
private Runnable pollingRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String host = (String) getConfig().get(HOST);
|
||||
try {
|
||||
List<HomeDevice> homeDevices = getHomeDevices();
|
||||
|
||||
if (!lastBridgeConnectionState) {
|
||||
logger.debug("Connection to Miele Gateway {} established.", host);
|
||||
logger.debug("Connection to Miele Gateway {} established.", config.ipAddress);
|
||||
lastBridgeConnectionState = true;
|
||||
}
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
@@ -231,7 +219,7 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
|
||||
message);
|
||||
}
|
||||
if (lastBridgeConnectionState) {
|
||||
logger.debug("Connection to Miele Gateway {} lost.", host);
|
||||
logger.debug("Connection to Miele Gateway {} lost.", config.ipAddress);
|
||||
lastBridgeConnectionState = false;
|
||||
}
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, message);
|
||||
@@ -324,12 +312,13 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
|
||||
}
|
||||
|
||||
private Runnable eventListenerRunnable = () -> {
|
||||
String interfaceIpAddress = (String) getConfig().get(INTERFACE);
|
||||
if (!IP_PATTERN.matcher(interfaceIpAddress).matches()) {
|
||||
logger.debug("Invalid IP address for the multicast interface: '{}'", interfaceIpAddress);
|
||||
NetworkInterface networkInterface = getMulticastNetworkInterface(config.ipAddress);
|
||||
if (networkInterface == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Using multicast interface {} for gateway {}", networkInterface.getName(), config.ipAddress);
|
||||
|
||||
// Get the address that we are going to connect to.
|
||||
InetSocketAddress address1 = null;
|
||||
InetSocketAddress address2 = null;
|
||||
@@ -347,12 +336,6 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
|
||||
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);
|
||||
@@ -442,25 +425,19 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
|
||||
}
|
||||
};
|
||||
|
||||
private @Nullable NetworkInterface getMulticastInterface(String interfaceIpAddress) throws SocketException {
|
||||
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
|
||||
private @Nullable NetworkInterface getMulticastNetworkInterface(String gatewayIpAddress) {
|
||||
try {
|
||||
InetAddress gatewayAddress = InetAddress.getByName(gatewayIpAddress);
|
||||
|
||||
@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;
|
||||
}
|
||||
try (DatagramSocket socket = new DatagramSocket()) {
|
||||
socket.connect(gatewayAddress, 80);
|
||||
InetAddress localAddress = socket.getLocalAddress();
|
||||
return NetworkInterface.getByInetAddress(localAddress);
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
logger.warn("Invalid gateway '{}': {}", gatewayIpAddress, e.getMessage());
|
||||
} catch (SocketException e) {
|
||||
logger.warn("Failed to determine network interface for '{}': {}", gatewayIpAddress, e.getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -45,8 +45,6 @@ thing-type.miele.xgw3000.description = The Miele bridge represents the Miele@hom
|
||||
|
||||
thing-type.config.miele.appliance.uid.label = ID
|
||||
thing-type.config.miele.appliance.uid.description = Unique identifier for specific appliance on the gateway.
|
||||
thing-type.config.miele.xgw3000.interface.label = Network Address of the Multicast Interface
|
||||
thing-type.config.miele.xgw3000.interface.description = Network address of openHAB host interface where the binding will listen for multicast events coming from the Miele@home gateway.
|
||||
thing-type.config.miele.xgw3000.ipAddress.label = Network Address
|
||||
thing-type.config.miele.xgw3000.ipAddress.description = Network address of the Miele@home gateway.
|
||||
thing-type.config.miele.xgw3000.language.label = Language
|
||||
@@ -134,8 +132,6 @@ channel-type.miele.water-consumption.description = Water consumption by the curr
|
||||
|
||||
offline.configuration-error.bridge-missing = Bridge is missing
|
||||
offline.configuration-error.ip-address-not-set = Cannot connect to the Miele gateway: host IP address is not set.
|
||||
offline.configuration-error.ip-multicast-interface-not-set = Cannot connect to the Miele gateway: multicast interface is not set.
|
||||
offline.configuration-error.invalid-ip-multicast-interface = Invalid IP address for the multicast interface: {0}
|
||||
offline.configuration-error.invalid-language = Invalid language: {0}
|
||||
offline.configuration-error.uid-not-set = Appliance ID is not set
|
||||
|
||||
|
||||
@@ -22,24 +22,16 @@
|
||||
<label>Network Address</label>
|
||||
<description>Network address of the Miele@home gateway.</description>
|
||||
</parameter>
|
||||
<parameter name="interface" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>Network Address of the Multicast Interface</label>
|
||||
<description>Network address of openHAB host interface where the binding will listen for multicast events coming
|
||||
from the Miele@home gateway.</description>
|
||||
</parameter>
|
||||
<parameter name="userName" type="text" required="false">
|
||||
<parameter name="userName" type="text">
|
||||
<label>Username</label>
|
||||
<description>
|
||||
Name of a registered Miele@home user.
|
||||
</description>
|
||||
<description>Name of a registered Miele@home user.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<parameter name="password" type="text">
|
||||
<context>password</context>
|
||||
<label>Password</label>
|
||||
<description>Password for the registered Miele@home user.</description>
|
||||
</parameter>
|
||||
<parameter name="language" type="text" required="false">
|
||||
<parameter name="language" type="text">
|
||||
<label>Language</label>
|
||||
<description>Language for state, program and phase texts. Leave blank for system language.</description>
|
||||
</parameter>
|
||||
|
||||
Reference in New Issue
Block a user