Skip to content
Merged
Show file tree
Hide file tree
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
3 changes: 1 addition & 2 deletions bundles/org.openhab.binding.miele/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down Expand Up @@ -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"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -116,26 +113,8 @@ public String getServiceType() {
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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@

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;
Expand All @@ -28,7 +28,6 @@
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;
Expand All @@ -42,7 +41,6 @@
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;
Expand All @@ -52,10 +50,10 @@
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;
Expand Down Expand Up @@ -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";
Expand All @@ -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();
Expand All @@ -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;
Expand All @@ -142,30 +143,18 @@ public Collection<Class<? extends ThingHandlerService>> getServices() {
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;
}
}
Expand All @@ -175,12 +164,11 @@ private boolean validateConfig(Configuration config) {
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);
Expand Down Expand Up @@ -231,7 +219,7 @@ public void run() {
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);
Expand Down Expand Up @@ -324,12 +312,13 @@ private List<HomeDevice> getHomeDevices() throws MieleRpcException {
}

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;
Expand All @@ -347,12 +336,6 @@ private List<HomeDevice> getHomeDevices() throws MieleRpcException {
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);
Expand Down Expand Up @@ -442,25 +425,19 @@ private List<HomeDevice> getHomeDevices() throws MieleRpcException {
}
};

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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down