Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 2 additions & 2 deletions bundles/org.openhab.binding.miele/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ 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. |
| multicastInterface | Name of the 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 +423,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", multicastInterface="eth0"] {
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,7 @@ public class MieleBindingConstants {

// Bridge config properties
public static final String HOST = "ipAddress";
public static final String INTERFACE = "interface";
public static final String MULTICAST_INTERFACE = "multicastInterface";
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,29 @@
/*
* 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 multicastInterface = "";
public String userName = "";
public String password = "";
public String language = "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -124,12 +125,13 @@ public String getServiceType() {
try {
socket = new Socket(addresses[0], 80);
InetAddress ourAddress = socket.getLocalAddress();
String interfaceIpAddress = ourAddress.getHostAddress();
NetworkInterface ni = NetworkInterface.getByInetAddress(ourAddress);
String interfaceName = ni.getName();
socket.close();

properties.put(MieleBindingConstants.INTERFACE, interfaceIpAddress);
logger.debug("Discovered Miele@home gateway with IP address {} and interface IP address {}", ipAddress,
interfaceIpAddress);
properties.put(MieleBindingConstants.MULTICAST_INTERFACE, interfaceName);
logger.debug("Discovered Miele@home gateway with IP address {} and multicast interface {}", ipAddress,
interfaceName);
} catch (IOException e) {
logger.warn("An exception occurred while connecting to the Miele Gateway: '{}'", e.getMessage());
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
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;
Expand All @@ -28,7 +27,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 +40,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,6 +49,7 @@
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;
Expand Down Expand Up @@ -85,22 +83,21 @@ 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";
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 static final String LEGACY_INTERFACE_PARAMETER = "interface";

private final Logger logger = LoggerFactory.getLogger(MieleBridgeHandler.class);

private boolean lastBridgeConnectionState = false;

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 +112,25 @@ 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())) {
migrateMulticastInterfaceConfiguration();

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 +146,57 @@ 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()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"@text/offline.configuration-error.ip-address-not-set");
return false;
private void migrateMulticastInterfaceConfiguration() {
Configuration config = editConfiguration();

String interfaceIpAddress = (String) config.get(LEGACY_INTERFACE_PARAMETER);
if (interfaceIpAddress == null || interfaceIpAddress.isBlank()) {
// Nothing to migrate
return;
}
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;

String interfaceName = (String) config.get(MULTICAST_INTERFACE);
if (interfaceName != null && !interfaceName.isBlank()) {
// Already migrated
return;
}

logger.debug("Attempting to migrate multicast interface IP address: '{}'", interfaceIpAddress);
try {
InetAddress addr = InetAddress.getByName(interfaceIpAddress);
NetworkInterface ni = NetworkInterface.getByInetAddress(addr);
if (ni == null) {
logger.warn("Failed to migrate network interface. No network interface found for IP address '{}'",
interfaceIpAddress);
return;
}

interfaceName = ni.getName();

config.put(MULTICAST_INTERFACE, interfaceName);
config.remove(LEGACY_INTERFACE_PARAMETER);
updateConfiguration(config);

logger.info(
"Migrated network interface '{}' from IP address '{}'. For unmanaged Things, please adapt configuration manually",
interfaceName, interfaceIpAddress);
} catch (SocketException | UnknownHostException e) {
logger.warn("Multicast interface migration failed: '{}'", e.getMessage());
}
if (!IP_PATTERN.matcher((String) config.get(INTERFACE)).matches()) {
}

private boolean validateConfig() {
if (config.ipAddress.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"@text/offline.configuration-error.invalid-ip-multicast-interface [\"" + config.get(INTERFACE)
+ "\"]");
"@text/offline.configuration-error.ip-address-not-set");
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 +206,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 +261,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,9 +354,8 @@ 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);
if (config.multicastInterface.isBlank()) {
logger.debug("No multicast interface configured");
return;
}

Expand All @@ -348,9 +377,9 @@ private List<HomeDevice> getHomeDevices() throws MieleRpcException {
clientSocket = new MulticastSocket(JSON_RPC_PORT);
clientSocket.setSoTimeout(MULTICAST_TIMEOUT_MILLIS);

NetworkInterface networkInterface = getMulticastInterface(interfaceIpAddress);
NetworkInterface networkInterface = NetworkInterface.getByName(config.multicastInterface);
if (networkInterface == null) {
logger.warn("Unable to find network interface for address {}", interfaceIpAddress);
logger.warn("Unable to get network interface for '{}'", config.multicastInterface);
return;
}
clientSocket.setNetworkInterface(networkInterface);
Expand Down Expand Up @@ -442,30 +471,6 @@ private List<HomeDevice> getHomeDevices() throws MieleRpcException {
}
};

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 {
if (getThing().getStatus() != ThingStatus.ONLINE) {
throw new MieleRpcException("Bridge is offline, operations can not be invoked");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ 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
thing-type.config.miele.xgw3000.language.description = Language for state, program and phase texts. Leave blank for system language.
thing-type.config.miele.xgw3000.multicastInterface.label = Name of the Multicast Interface
thing-type.config.miele.xgw3000.multicastInterface.description = Name of the openHAB host interface where the binding will listen for multicast events coming from the Miele@home gateway.
thing-type.config.miele.xgw3000.password.label = Password
thing-type.config.miele.xgw3000.password.description = Password for the registered Miele@home user.
thing-type.config.miele.xgw3000.userName.label = Username
Expand Down Expand Up @@ -134,8 +134,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,22 @@
<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 name="multicastInterface" type="text">
<context>network-interface</context>
<label>Name of the Multicast Interface</label>
<description>Name of the 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