[miele] Add null annotations and improve error handling robustness (#12497)

* Add null annotations and improve error handling robustness
* Fix compliancy with rule ConstantNameCheck

Fixes #12496

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
Jacob Laursen 2022-03-21 16:49:03 +01:00 committed by GitHub
parent 7c29e4d565
commit f21bbc5945
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 991 additions and 666 deletions

View File

@ -17,6 +17,9 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
@ -29,13 +32,14 @@ import org.openhab.core.types.UnDefType;
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class DeviceUtil {
private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
private static final String TEMPERATURE_UNDEFINED = "32768";
private static final String TEMPERATURE_COLD = "-32760";
private static final String TEXT_PREFIX = "miele.";
private static final Map<String, String> states = Map.ofEntries(Map.entry("1", "off"), Map.entry("2", "stand-by"),
private static final Map<String, String> STATES = Map.ofEntries(Map.entry("1", "off"), Map.entry("2", "stand-by"),
Map.entry("3", "programmed"), Map.entry("4", "waiting-to-start"), Map.entry("5", "running"),
Map.entry("6", "paused"), Map.entry("7", "end"), Map.entry("8", "failure"), Map.entry("9", "abort"),
Map.entry("10", "idle"), Map.entry("11", "rinse-hold"), Map.entry("12", "service"),
@ -84,8 +88,9 @@ public class DeviceUtil {
* Get state text for provided string taking into consideration {@link DeviceMetaData}
* as well as built-in/translated strings.
*/
public static State getStateTextState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
return getTextState(s, dmd, translationProvider, states, MISSING_STATE_TEXT_PREFIX, "");
public static State getStateTextState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTextState(s, dmd, translationProvider, STATES, MISSING_STATE_TEXT_PREFIX, "");
}
/**
@ -100,8 +105,9 @@ public class DeviceUtil {
* @param appliancePrefix Appliance prefix appended to text key (including dot)
* @return Text string as State
*/
public static State getTextState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider,
Map<String, String> valueMap, String propertyPrefix, String appliancePrefix) {
public static State getTextState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider, Map<String, String> valueMap, String propertyPrefix,
String appliancePrefix) {
if ("0".equals(s)) {
return UnDefType.UNDEF;
}
@ -116,7 +122,7 @@ public class DeviceUtil {
}
String value = valueMap.get(s);
if (value != null) {
if (value != null && translationProvider != null) {
String key = TEXT_PREFIX + propertyPrefix + appliancePrefix + value;
return new StringType(
translationProvider.getText(key, gatewayText != null ? gatewayText : propertyPrefix + s));

View File

@ -12,12 +12,15 @@
*/
package org.openhab.binding.miele.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link FullyQualifiedApplianceIdentifier} class represents a fully qualified appliance identifier.
* Example: "hdm:ZigBee:0123456789abcdef#210"
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class FullyQualifiedApplianceIdentifier {
private String uid;
private String protocol;
@ -56,7 +59,7 @@ public class FullyQualifiedApplianceIdentifier {
}
/**
* @return Protocol prefix of fully qualified appliance identifier (e.g. "hdmi:ZigBee:"")
* @return Protocol prefix of fully qualified appliance identifier (e.g. "hdmi:ZigBee:")
*/
public String getProtocol() {
return this.protocol;

View File

@ -28,6 +28,7 @@ public class MieleBindingConstants {
public static final String BINDING_ID = "miele";
public static final String APPLIANCE_ID = "uid";
public static final String MIELE_CLASS = "com.miele.xgw3000.gateway.hdm.deviceclasses.Miele";
// Properties
public static final String PROPERTY_DEVICE_CLASS = "deviceClass";

View File

@ -21,6 +21,8 @@ import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.discovery.MieleApplianceDiscoveryService;
import org.openhab.binding.miele.internal.handler.CoffeeMachineHandler;
import org.openhab.binding.miele.internal.handler.DishWasherHandler;
@ -56,6 +58,7 @@ import org.osgi.service.component.annotations.Reference;
*
* @author Karel Goderis - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.miele")
public class MieleHandlerFactory extends BaseThingHandlerFactory {
@ -82,8 +85,8 @@ public class MieleHandlerFactory extends BaseThingHandlerFactory {
}
@Override
public Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, ThingUID thingUID,
ThingUID bridgeUID) {
public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
@Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
if (MieleBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
ThingUID mieleBridgeUID = getBridgeThingUID(thingTypeUID, thingUID, configuration);
return super.createThing(thingTypeUID, configuration, mieleBridgeUID, null);
@ -97,7 +100,7 @@ public class MieleHandlerFactory extends BaseThingHandlerFactory {
}
@Override
protected ThingHandler createHandler(Thing thing) {
protected @Nullable ThingHandler createHandler(Thing thing) {
if (MieleBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
MieleBridgeHandler handler = new MieleBridgeHandler((Bridge) thing);
registerApplianceDiscoveryService(handler);
@ -135,7 +138,8 @@ public class MieleHandlerFactory extends BaseThingHandlerFactory {
return null;
}
private ThingUID getBridgeThingUID(ThingTypeUID thingTypeUID, ThingUID thingUID, Configuration configuration) {
private ThingUID getBridgeThingUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID,
Configuration configuration) {
if (thingUID == null) {
String hostID = (String) configuration.get(HOST);
thingUID = new ThingUID(thingTypeUID, hostID);
@ -143,12 +147,16 @@ public class MieleHandlerFactory extends BaseThingHandlerFactory {
return thingUID;
}
private ThingUID getApplianceUID(ThingTypeUID thingTypeUID, ThingUID thingUID, Configuration configuration,
ThingUID bridgeUID) {
private ThingUID getApplianceUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID,
Configuration configuration, @Nullable ThingUID bridgeUID) {
String applianceId = (String) configuration.get(APPLIANCE_ID);
if (thingUID == null) {
thingUID = new ThingUID(thingTypeUID, applianceId, bridgeUID.getId());
if (bridgeUID == null) {
thingUID = new ThingUID(thingTypeUID, applianceId);
} else {
thingUID = new ThingUID(thingTypeUID, bridgeUID, applianceId);
}
}
return thingUID;
}

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2022 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.api.dto;
import com.google.gson.JsonArray;
/**
* The {@link DeviceClassObject} class represents the DeviceClassObject node in the response JSON.
*
* @author Jacob Laursen - Initial contribution
**/
public class DeviceClassObject {
public String DeviceClassType;
public JsonArray Operations;
public String DeviceClass;
public JsonArray Properties;
public DeviceClassObject() {
}
}

View File

@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.miele.internal;
package org.openhab.binding.miele.internal.api.dto;
import java.util.Map.Entry;

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2022 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.api.dto;
import com.google.gson.JsonObject;
/**
* The {@link DeviceProperty} class represents the DeviceProperty node in the response JSON.
*
* @author Jacob Laursen - Initial contribution
**/
public class DeviceProperty {
public String Name;
public String Value;
public int Polling;
public JsonObject Metadata;
public DeviceProperty() {
}
}

View File

@ -0,0 +1,103 @@
/**
* Copyright (c) 2010-2022 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.api.dto;
import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
import org.openhab.binding.miele.internal.MieleBindingConstants;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
/**
* The {@link HomeDevice} class represents the HomeDevice node in the response JSON.
*
* @author Jacob Laursen - Initial contribution
**/
public class HomeDevice {
private static final String MIELE_APPLIANCE_CLASS = "com.miele.xgw3000.gateway.hdm.deviceclasses.MieleAppliance";
public String Name;
public String Status;
public String ParentUID;
public String ProtocolAdapterName;
public String Vendor;
public String UID;
public String Type;
public JsonArray DeviceClasses;
public String Version;
public String TimestampAdded;
public JsonObject Error;
public JsonObject Properties;
public HomeDevice() {
}
public FullyQualifiedApplianceIdentifier getApplianceIdentifier() {
return new FullyQualifiedApplianceIdentifier(this.UID);
}
public String getSerialNumber() {
return Properties.get("serial.number").getAsString();
}
public String getFirmwareVersion() {
return Properties.get("firmware.version").getAsString();
}
public String getRemoteUid() {
JsonElement remoteUid = Properties.get("remote.uid");
if (remoteUid == null) {
// remote.uid and serial.number seems to be the same. If remote.uid
// is missing for some reason, it makes sense to provide fallback
// to serial number.
return getSerialNumber();
}
return remoteUid.getAsString();
}
public String getConnectionType() {
JsonElement connectionType = Properties.get("connection.type");
if (connectionType == null) {
return null;
}
return connectionType.getAsString();
}
public String getConnectionBaudRate() {
JsonElement baudRate = Properties.get("connection.baud.rate");
if (baudRate == null) {
return null;
}
return baudRate.getAsString();
}
public String getApplianceModel() {
JsonElement model = Properties.get("miele.model");
if (model == null) {
return "";
}
return model.getAsString();
}
public String getDeviceClass() {
for (JsonElement dc : DeviceClasses) {
String dcStr = dc.getAsString();
if (dcStr.contains(MieleBindingConstants.MIELE_CLASS) && !dcStr.equals(MIELE_APPLIANCE_CLASS)) {
return dcStr.substring(MieleBindingConstants.MIELE_CLASS.length());
}
}
return null;
}
}

View File

@ -20,13 +20,15 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
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.handler.ApplianceStatusListener;
import org.openhab.binding.miele.internal.handler.MieleApplianceHandler;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceClassObject;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceProperty;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.HomeDevice;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
@ -44,6 +46,7 @@ import org.slf4j.LoggerFactory;
* @author Martin Lepsy - Added protocol information in order so support WiFi devices
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
*/
@NonNullByDefault
public class MieleApplianceDiscoveryService extends AbstractDiscoveryService implements ApplianceStatusListener {
private final Logger logger = LoggerFactory.getLogger(MieleApplianceDiscoveryService.class);
@ -75,10 +78,8 @@ public class MieleApplianceDiscoveryService extends AbstractDiscoveryService imp
@Override
public void startScan() {
List<HomeDevice> appliances = mieleBridgeHandler.getHomeDevices();
if (appliances != null) {
for (HomeDevice l : appliances) {
onApplianceAddedInternal(l);
}
for (HomeDevice l : appliances) {
onApplianceAddedInternal(l);
}
}
@ -100,11 +101,17 @@ public class MieleApplianceDiscoveryService extends AbstractDiscoveryService imp
Map<String, Object> properties = new HashMap<>(9);
FullyQualifiedApplianceIdentifier applianceIdentifier = appliance.getApplianceIdentifier();
properties.put(Thing.PROPERTY_VENDOR, appliance.Vendor);
String vendor = appliance.Vendor;
if (vendor != null) {
properties.put(Thing.PROPERTY_VENDOR, vendor);
}
properties.put(Thing.PROPERTY_MODEL_ID, appliance.getApplianceModel());
properties.put(Thing.PROPERTY_SERIAL_NUMBER, appliance.getSerialNumber());
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, appliance.getFirmwareVersion());
properties.put(PROPERTY_PROTOCOL_ADAPTER, appliance.ProtocolAdapterName);
String protocolAdapterName = appliance.ProtocolAdapterName;
if (protocolAdapterName != null) {
properties.put(PROPERTY_PROTOCOL_ADAPTER, protocolAdapterName);
}
properties.put(APPLIANCE_ID, applianceIdentifier.getApplianceId());
String deviceClass = appliance.getDeviceClass();
if (deviceClass != null) {
@ -149,7 +156,7 @@ public class MieleApplianceDiscoveryService extends AbstractDiscoveryService imp
// nothing to do
}
private ThingUID getThingUID(HomeDevice appliance) {
private @Nullable ThingUID getThingUID(HomeDevice appliance) {
ThingUID bridgeUID = mieleBridgeHandler.getThing().getUID();
String modelId = appliance.getDeviceClass();

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2022 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.exceptions;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link MieleRpcException} indicates failure to perform JSON-RPC call.
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class MieleRpcException extends IOException {
private static final long serialVersionUID = -8147063891196639054L;
public MieleRpcException(String message) {
super(message);
}
public MieleRpcException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -12,8 +12,10 @@
*/
package org.openhab.binding.miele.internal.handler;
import org.openhab.binding.miele.internal.DeviceMetaData;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.types.State;
/**
@ -25,6 +27,7 @@ import org.openhab.core.types.State;
* @author Karel Goderis - Initial contribution
* @author Jacob Laursen - Added power/water consumption channels
*/
@NonNullByDefault
public interface ApplianceChannelSelector {
@Override
@ -61,7 +64,7 @@ public interface ApplianceChannelSelector {
* @param dmd - the device meta data
* @param translationProvider {@link MieleTranslationProvider} instance
*/
State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider);
State getState(String s, @Nullable DeviceMetaData dmd, @Nullable MieleTranslationProvider translationProvider);
/**
* Returns a State for the given string, taking into
@ -71,7 +74,7 @@ public interface ApplianceChannelSelector {
* @param s - the value to be used to instantiate the State
* @param dmd - the device meta data
*/
State getState(String s, DeviceMetaData dmd);
State getState(String s, @Nullable DeviceMetaData dmd);
/**
* Returns a raw State for the given string, not taking into

View File

@ -12,10 +12,11 @@
*/
package org.openhab.binding.miele.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceClassObject;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceProperty;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.HomeDevice;
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;
/**
*
@ -25,6 +26,7 @@ import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.HomeDevice;
* @author Karel Goderis - Initial contribution
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
*/
@NonNullByDefault
public interface ApplianceStatusListener {
/**

View File

@ -15,12 +15,13 @@ package org.openhab.binding.miele.internal.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import org.openhab.binding.miele.internal.DeviceMetaData;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
@ -37,50 +38,43 @@ import org.slf4j.LoggerFactory;
* @author Stephan Esch - Initial contribution
* @author Jacob Laursen - Added raw channels
*/
@NonNullByDefault
public enum CoffeeMachineChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getStateTextState(s, dmd, translationProvider);
}
},
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false),
STATE("", STATE_CHANNEL_ID, DecimalType.class, false),
PROGRAM_TEXT(PROGRAM_ID_PROPERTY_NAME, PROGRAM_TEXT_CHANNEL_ID, StringType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getTextState(s, dmd, translationProvider, programs, MISSING_PROGRAM_TEXT_PREFIX,
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PROGRAMS, MISSING_PROGRAM_TEXT_PREFIX,
MIELE_COFFEE_MACHINE_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
}
},
PROGRAM(null, PROGRAM_CHANNEL_ID, DecimalType.class, false),
PROGRAM("", PROGRAM_CHANNEL_ID, DecimalType.class, false),
PROGRAMTYPE("programType", "type", StringType.class, false),
PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getTextState(s, dmd, translationProvider, phases, MISSING_PHASE_TEXT_PREFIX,
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PHASES, MISSING_PHASE_TEXT_PREFIX,
MIELE_COFFEE_MACHINE_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
}
},
PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false),
// lightingStatus signalFailure signalInfo
DOOR("signalDoor", "door", OpenClosedType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
if ("true".equals(s)) {
return getState("OPEN");
}
@ -92,13 +86,13 @@ public enum CoffeeMachineChannelSelector implements ApplianceChannelSelector {
return UnDefType.UNDEF;
}
},
SWITCH(null, "switch", OnOffType.class, false);
SWITCH("", "switch", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(CoffeeMachineChannelSelector.class);
private static final Map<String, String> programs = Collections.<String, String> emptyMap();
private static final Map<String, String> PROGRAMS = Map.of();
private static final Map<String, String> phases = Collections.<String, String> emptyMap();
private static final Map<String, String> PHASES = Map.of();
private final String mieleID;
private final String channelID;
@ -139,12 +133,13 @@ public enum CoffeeMachineChannelSelector implements ApplianceChannelSelector {
}
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd);
}
@Override
public State getState(String s, DeviceMetaData dmd) {
public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) {
@ -171,6 +166,6 @@ public enum CoffeeMachineChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
return UnDefType.UNDEF;
}
}

View File

@ -15,6 +15,8 @@ package org.openhab.binding.miele.internal.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_COFFEE_SYSTEM;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType;
@ -35,6 +37,7 @@ import com.google.gson.JsonElement;
* @author Martin Lepsy - fixed handling of empty JSON results
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
*/
@NonNullByDefault
public class CoffeeMachineHandler extends MieleApplianceHandler<CoffeeMachineChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(CoffeeMachineHandler.class);
@ -50,26 +53,33 @@ public class CoffeeMachineHandler extends MieleApplianceHandler<CoffeeMachineCha
String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId == null) {
logger.warn("Command '{}' failed, appliance id is unknown", command);
return;
}
CoffeeMachineChannelSelector selector = (CoffeeMachineChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
try {
if (selector != null) {
switch (selector) {
case SWITCH: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOn");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOff");
}
break;
switch (selector) {
case SWITCH: {
MieleBridgeHandler bridgeHandler = this.bridgeHandler;
if (bridgeHandler == null) {
logger.warn("Command '{}' failed, missing bridge handler", command);
return;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOn");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOff");
}
break;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
}
}
@ -81,6 +91,14 @@ public class CoffeeMachineHandler extends MieleApplianceHandler<CoffeeMachineCha
logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString());
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("An error occurred while trying to invoke operation: {}", e.getMessage());
} else {
logger.warn("An error occurred while trying to invoke operation: {} -> {}", e.getMessage(),
cause.getMessage());
}
}
}
}

View File

@ -19,6 +19,8 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.WATER_CON
import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType;
@ -42,6 +44,7 @@ import com.google.gson.JsonElement;
* @author Martin Lepsy - fixed handling of empty JSON results
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN), added power/water consumption channels
*/
@NonNullByDefault
public class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSelector>
implements ExtendedDeviceStateListener {
@ -61,26 +64,33 @@ public class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSe
String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId == null) {
logger.warn("Command '{}' failed, appliance id is unknown", command);
return;
}
DishwasherChannelSelector selector = (DishwasherChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
try {
if (selector != null) {
switch (selector) {
case SWITCH: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "start");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
}
break;
switch (selector) {
case SWITCH: {
MieleBridgeHandler bridgeHandler = this.bridgeHandler;
if (bridgeHandler == null) {
logger.warn("Command '{}' failed, missing bridge handler", command);
return;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "start");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
}
break;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
}
}
@ -92,6 +102,14 @@ public class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSe
logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString());
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("An error occurred while trying to invoke operation: {}", e.getMessage());
} else {
logger.warn("An error occurred while trying to invoke operation: {} -> {}", e.getMessage(),
cause.getMessage());
}
}
}

View File

@ -21,9 +21,11 @@ import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import org.openhab.binding.miele.internal.DeviceMetaData;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
@ -43,48 +45,41 @@ import org.slf4j.LoggerFactory;
* @author Kai Kreuzer - Changed START_TIME to DateTimeType
* @author Jacob Laursen - Added power/water consumption channels, raw channels
*/
@NonNullByDefault
public enum DishwasherChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true, false),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true, false),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getStateTextState(s, dmd, translationProvider);
}
},
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false, false),
STATE("", STATE_CHANNEL_ID, DecimalType.class, false, false),
PROGRAM_TEXT(PROGRAM_ID_PROPERTY_NAME, PROGRAM_TEXT_CHANNEL_ID, StringType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getTextState(s, dmd, translationProvider, programs, MISSING_PROGRAM_TEXT_PREFIX,
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PROGRAMS, MISSING_PROGRAM_TEXT_PREFIX,
MIELE_DISHWASHER_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
}
},
PROGRAM(null, PROGRAM_CHANNEL_ID, DecimalType.class, false, false),
PROGRAM("", PROGRAM_CHANNEL_ID, DecimalType.class, false, false),
PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getTextState(s, dmd, translationProvider, phases, MISSING_PHASE_TEXT_PREFIX,
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PHASES, MISSING_PHASE_TEXT_PREFIX,
MIELE_DISHWASHER_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
}
},
PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false, false),
START_TIME("startTime", "start", DateTimeType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -98,7 +93,8 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
},
DURATION("duration", "duration", DateTimeType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -112,7 +108,8 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
},
ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -126,7 +123,8 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
},
FINISH_TIME("finishTime", "finish", DateTimeType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -140,7 +138,8 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
},
DOOR("signalDoor", "door", OpenClosedType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
if ("true".equals(s)) {
return getState("OPEN");
}
@ -152,7 +151,7 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
return UnDefType.UNDEF;
}
},
SWITCH(null, "switch", OnOffType.class, false, false),
SWITCH("", "switch", OnOffType.class, false, false),
POWER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, POWER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false,
true),
WATER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, WATER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false,
@ -160,12 +159,12 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
private final Logger logger = LoggerFactory.getLogger(DishwasherChannelSelector.class);
private static final Map<String, String> programs = Map.ofEntries(entry("26", "intensive"),
private static final Map<String, String> PROGRAMS = Map.ofEntries(entry("26", "intensive"),
entry("27", "maintenance-programme"), entry("28", "eco"), entry("30", "normal"), entry("32", "automatic"),
entry("34", "solarsave"), entry("35", "gentle"), entry("36", "extra-quiet"), entry("37", "hygiene"),
entry("38", "quickpowerwash"), entry("42", "tall-items"));
private static final Map<String, String> phases = Map.ofEntries(entry("2", "pre-wash"), entry("3", "main-wash"),
private static final Map<String, String> PHASES = Map.ofEntries(entry("2", "pre-wash"), entry("3", "main-wash"),
entry("4", "rinses"), entry("6", "final-rinse"), entry("7", "drying"), entry("8", "finished"));
private final String mieleID;
@ -209,12 +208,13 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
}
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd);
}
@Override
public State getState(String s, DeviceMetaData dmd) {
public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) {
@ -241,6 +241,6 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
return UnDefType.UNDEF;
}
}

View File

@ -12,12 +12,15 @@
*/
package org.openhab.binding.miele.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Appliance handlers can implement the {@link ExtendedDeviceStateListener} interface
* to extract additional information from the ExtendedDeviceState property.
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public interface ExtendedDeviceStateListener {
void onApplianceExtendedStateChanged(byte[] extendedDeviceState);
}

View File

@ -16,9 +16,11 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.lang.reflect.Method;
import org.openhab.binding.miele.internal.DeviceMetaData;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
@ -36,38 +38,39 @@ import org.slf4j.LoggerFactory;
* @author Karel Goderis - Initial contribution
* @author Jacob Laursen - Added UoM for temperatures, raw channels
*/
@NonNullByDefault
public enum FridgeChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getStateTextState(s, dmd, translationProvider);
}
},
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false),
SUPERCOOL(null, SUPERCOOL_CHANNEL_ID, OnOffType.class, false),
STATE("", STATE_CHANNEL_ID, DecimalType.class, false),
SUPERCOOL("", SUPERCOOL_CHANNEL_ID, OnOffType.class, false),
FRIDGECURRENTTEMP("currentTemperature", "current", QuantityType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s);
}
},
FRIDGETARGETTEMP("targetTemperature", "target", QuantityType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s);
}
},
DOOR("signalDoor", "door", OpenClosedType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
if ("true".equals(s)) {
return getState("OPEN");
}
@ -79,7 +82,7 @@ public enum FridgeChannelSelector implements ApplianceChannelSelector {
return UnDefType.UNDEF;
}
},
START(null, "start", OnOffType.class, false);
START("", "start", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(FridgeChannelSelector.class);
@ -121,12 +124,13 @@ public enum FridgeChannelSelector implements ApplianceChannelSelector {
}
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd);
}
@Override
public State getState(String s, DeviceMetaData dmd) {
public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) {
@ -153,7 +157,7 @@ public enum FridgeChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
return UnDefType.UNDEF;
}
public State getTemperatureState(String s) {

View File

@ -16,9 +16,11 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.lang.reflect.Method;
import org.openhab.binding.miele.internal.DeviceMetaData;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
@ -37,53 +39,55 @@ import org.slf4j.LoggerFactory;
* @author Karel Goderis - Initial contribution
* @author Jacob Laursen - Added UoM for temperatures, raw channels
*/
@NonNullByDefault
public enum FridgeFreezerChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getStateTextState(s, dmd, translationProvider);
}
},
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false),
STATE("", STATE_CHANNEL_ID, DecimalType.class, false),
FREEZERSTATE("freezerState", "freezerstate", StringType.class, false),
FRIDGESTATE("fridgeState", "fridgestate", StringType.class, false),
SUPERCOOL(null, SUPERCOOL_CHANNEL_ID, OnOffType.class, false),
SUPERFREEZE(null, SUPERFREEZE_CHANNEL_ID, OnOffType.class, false),
SUPERCOOL("", SUPERCOOL_CHANNEL_ID, OnOffType.class, false),
SUPERFREEZE("", SUPERFREEZE_CHANNEL_ID, OnOffType.class, false),
FREEZERCURRENTTEMP("freezerCurrentTemperature", "freezercurrent", QuantityType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s);
}
},
FREEZERTARGETTEMP("freezerTargetTemperature", "freezertarget", QuantityType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s);
}
},
FRIDGECURRENTTEMP("fridgeCurrentTemperature", "fridgecurrent", QuantityType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s);
}
},
FRIDGETARGETTEMP("fridgeTargetTemperature", "fridgetarget", QuantityType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s);
}
},
DOOR("signalDoor", "door", OpenClosedType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
if ("true".equals(s)) {
return getState("OPEN");
}
@ -95,7 +99,7 @@ public enum FridgeFreezerChannelSelector implements ApplianceChannelSelector {
return UnDefType.UNDEF;
}
},
START(null, "start", OnOffType.class, false);
START("", "start", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(FridgeFreezerChannelSelector.class);
@ -138,12 +142,13 @@ public enum FridgeFreezerChannelSelector implements ApplianceChannelSelector {
}
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd);
}
@Override
public State getState(String s, DeviceMetaData dmd) {
public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) {
@ -170,7 +175,7 @@ public enum FridgeFreezerChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
return UnDefType.UNDEF;
}
public State getTemperatureState(String s) {

View File

@ -14,7 +14,9 @@ package org.openhab.binding.miele.internal.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceProperty;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType;
@ -35,6 +37,7 @@ import com.google.gson.JsonElement;
* @author Martin Lepsy - fixed handling of empty JSON results
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
*/
@NonNullByDefault
public class FridgeFreezerHandler extends MieleApplianceHandler<FridgeFreezerChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(FridgeFreezerHandler.class);
@ -50,33 +53,39 @@ public class FridgeFreezerHandler extends MieleApplianceHandler<FridgeFreezerCha
String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId == null) {
logger.warn("Command '{}' failed, appliance id is unknown", command);
return;
}
FridgeFreezerChannelSelector selector = (FridgeFreezerChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
try {
if (selector != null) {
switch (selector) {
case SUPERCOOL: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperCooling");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperCooling");
}
break;
MieleBridgeHandler bridgeHandler = this.bridgeHandler;
if (bridgeHandler == null) {
logger.warn("Command '{}' failed, missing bridge handler", command);
return;
}
switch (selector) {
case SUPERCOOL: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperCooling");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperCooling");
}
case SUPERFREEZE: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperFreezing");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperFreezing");
}
break;
}
default: {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
break;
}
case SUPERFREEZE: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperFreezing");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperFreezing");
}
break;
}
default: {
logger.debug("{} is a read-only channel that does not accept commands", selector.getChannelID());
}
}
// process result
@ -87,6 +96,14 @@ public class FridgeFreezerHandler extends MieleApplianceHandler<FridgeFreezerCha
logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString());
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("An error occurred while trying to invoke operation: {}", e.getMessage());
} else {
logger.warn("An error occurred while trying to invoke operation: {} -> {}", e.getMessage(),
cause.getMessage());
}
}
}

View File

@ -14,7 +14,9 @@ package org.openhab.binding.miele.internal.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceProperty;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType;
@ -36,6 +38,7 @@ import com.google.gson.JsonElement;
* @author Martin Lepsy - fixed handling of empty JSON results
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
*/
@NonNullByDefault
public class FridgeHandler extends MieleApplianceHandler<FridgeChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(FridgeHandler.class);
@ -50,32 +53,39 @@ public class FridgeHandler extends MieleApplianceHandler<FridgeChannelSelector>
String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId == null) {
logger.warn("Command '{}' failed, appliance id is unknown", command);
return;
}
FridgeChannelSelector selector = (FridgeChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
try {
if (selector != null) {
switch (selector) {
case SUPERCOOL: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperCooling");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperCooling");
}
break;
MieleBridgeHandler bridgeHandler = this.bridgeHandler;
if (bridgeHandler == null) {
logger.warn("Command '{}' failed, missing bridge handler", command);
return;
}
switch (selector) {
case SUPERCOOL: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperCooling");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperCooling");
}
case START: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "start");
}
break;
break;
}
case START: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "start");
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
break;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
}
}
@ -87,6 +97,14 @@ public class FridgeHandler extends MieleApplianceHandler<FridgeChannelSelector>
logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString());
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("An error occurred while trying to invoke operation: {}", e.getMessage());
} else {
logger.warn("An error occurred while trying to invoke operation: {} -> {}", e.getMessage(),
cause.getMessage());
}
}
}

View File

@ -16,13 +16,16 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.lang.reflect.Method;
import org.openhab.binding.miele.internal.DeviceMetaData;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.State;
import org.openhab.core.types.Type;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -32,26 +35,25 @@ import org.slf4j.LoggerFactory;
* @author Karel Goderis - Initial contribution
* @author Jacob Laursen - Added raw channels
*/
@NonNullByDefault
public enum HobChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getStateTextState(s, dmd, translationProvider);
}
},
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false),
STATE("", STATE_CHANNEL_ID, DecimalType.class, false),
PLATES("plateNumbers", "plates", DecimalType.class, true),
PLATE1_POWER("plate1PowerStep", "plate1power", DecimalType.class, false),
PLATE1_HEAT("plate1RemainingHeat", "plate1heat", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
// If there is remaining heat, the device metadata contains some informative string which can not be
// converted into a DecimalType. We therefore ignore the metadata and return the device property value as a
// State
@ -62,7 +64,8 @@ public enum HobChannelSelector implements ApplianceChannelSelector {
PLATE2_POWER("plate2PowerStep", "plate2power", DecimalType.class, false),
PLATE2_HEAT("plate2RemainingHeat", "plate2heat", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getState(s);
}
},
@ -70,7 +73,8 @@ public enum HobChannelSelector implements ApplianceChannelSelector {
PLATE3_POWER("plate3PowerStep", "plate3power", DecimalType.class, false),
PLATE3_HEAT("plate3RemainingHeat", "plate3heat", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getState(s);
}
},
@ -78,7 +82,8 @@ public enum HobChannelSelector implements ApplianceChannelSelector {
PLATE4_POWER("plate4PowerStep", "plate4power", DecimalType.class, false),
PLATE4_HEAT("plate4RemainingHeat", "plate4heat", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getState(s);
}
},
@ -86,7 +91,8 @@ public enum HobChannelSelector implements ApplianceChannelSelector {
PLATE5_POWER("plate5PowerStep", "plate5power", DecimalType.class, false),
PLATE5_HEAT("plate5RemainingHeat", "plate5heat", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getState(s);
}
},
@ -94,7 +100,8 @@ public enum HobChannelSelector implements ApplianceChannelSelector {
PLATE6_POWER("plate6PowerStep", "plate6power", DecimalType.class, false),
PLATE6_HEAT("plate6RemainingHeat", "plate6heat", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getState(s);
}
},
@ -140,12 +147,13 @@ public enum HobChannelSelector implements ApplianceChannelSelector {
}
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd);
}
@Override
public State getState(String s, DeviceMetaData dmd) {
public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) {
@ -172,6 +180,6 @@ public enum HobChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
return UnDefType.UNDEF;
}
}

View File

@ -14,6 +14,7 @@ package org.openhab.binding.miele.internal.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_HOB;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.thing.ChannelUID;
@ -27,6 +28,7 @@ import org.openhab.core.types.Command;
* @author Karel Goderis - Initial contribution
* @author Kai Kreuzer - fixed handling of REFRESH commands
*/
@NonNullByDefault
public class HobHandler extends MieleApplianceHandler<HobChannelSelector> {
public HobHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider) {

View File

@ -16,9 +16,11 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.lang.reflect.Method;
import org.openhab.binding.miele.internal.DeviceMetaData;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
@ -34,26 +36,25 @@ import org.slf4j.LoggerFactory;
* @author Karel Goderis - Initial contribution
* @author Jacob Laursen - Added raw channels
*/
@NonNullByDefault
public enum HoodChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getStateTextState(s, dmd, translationProvider);
}
},
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false),
STATE("", STATE_CHANNEL_ID, DecimalType.class, false),
VENTILATION("ventilationPower", "ventilation", DecimalType.class, false),
LIGHT("lightingStatus", "light", OnOffType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
if ("true".equals(s)) {
return getState("ON");
}
@ -65,7 +66,7 @@ public enum HoodChannelSelector implements ApplianceChannelSelector {
return UnDefType.UNDEF;
}
},
STOP(null, "stop", OnOffType.class, false);
STOP("", "stop", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(HoodChannelSelector.class);
@ -107,12 +108,13 @@ public enum HoodChannelSelector implements ApplianceChannelSelector {
}
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd);
}
@Override
public State getState(String s, DeviceMetaData dmd) {
public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) {
@ -139,6 +141,6 @@ public enum HoodChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
return UnDefType.UNDEF;
}
}

View File

@ -15,6 +15,8 @@ package org.openhab.binding.miele.internal.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_HOOD;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType;
@ -35,6 +37,7 @@ import com.google.gson.JsonElement;
* @author Martin Lepsy - fixed handling of empty JSON results
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
**/
@NonNullByDefault
public class HoodHandler extends MieleApplianceHandler<HoodChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(HoodHandler.class);
@ -49,31 +52,37 @@ public class HoodHandler extends MieleApplianceHandler<HoodChannelSelector> {
String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId == null) {
logger.warn("Command '{}' failed, appliance id is unknown", command);
return;
}
HoodChannelSelector selector = (HoodChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
try {
if (selector != null) {
switch (selector) {
case LIGHT: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "startLighting");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopLighting");
}
break;
MieleBridgeHandler bridgeHandler = this.bridgeHandler;
if (bridgeHandler == null) {
logger.warn("Command '{}' failed, missing bridge handler", command);
return;
}
switch (selector) {
case LIGHT: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "startLighting");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopLighting");
}
case STOP: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
}
break;
}
default: {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
break;
}
case STOP: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
}
break;
}
default: {
logger.debug("{} is a read-only channel that does not accept commands", selector.getChannelID());
}
}
// process result
@ -84,6 +93,14 @@ public class HoodHandler extends MieleApplianceHandler<HoodChannelSelector> {
logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString());
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("An error occurred while trying to invoke operation: {}", e.getMessage());
} else {
logger.warn("An error occurred while trying to invoke operation: {} -> {}", e.getMessage(),
cause.getMessage());
}
}
}
}

View File

@ -19,17 +19,16 @@ import java.util.IllformedLocaleException;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNull;
import org.openhab.binding.miele.internal.DeviceMetaData;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceClassObject;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceProperty;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.HomeDevice;
import org.openhab.binding.miele.internal.api.dto.DeviceClassObject;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
import org.openhab.binding.miele.internal.api.dto.HomeDevice;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.thing.Bridge;
@ -43,11 +42,11 @@ import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@ -62,23 +61,23 @@ import com.google.gson.JsonParser;
* @author Martin Lepsy - Added check for JsonNull result
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
*/
@NonNullByDefault
public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannelSelector> extends BaseThingHandler
implements ApplianceStatusListener {
private final Logger logger = LoggerFactory.getLogger(MieleApplianceHandler.class);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream
.of(THING_TYPE_DISHWASHER, THING_TYPE_OVEN, THING_TYPE_FRIDGE, THING_TYPE_DRYER, THING_TYPE_HOB,
THING_TYPE_FRIDGEFREEZER, THING_TYPE_HOOD, THING_TYPE_WASHINGMACHINE, THING_TYPE_COFFEEMACHINE)
.collect(Collectors.toSet());
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_DISHWASHER, THING_TYPE_OVEN,
THING_TYPE_FRIDGE, THING_TYPE_DRYER, THING_TYPE_HOB, THING_TYPE_FRIDGEFREEZER, THING_TYPE_HOOD,
THING_TYPE_WASHINGMACHINE, THING_TYPE_COFFEEMACHINE);
protected Gson gson = new Gson();
protected String applianceId;
protected MieleBridgeHandler bridgeHandler;
protected @Nullable String applianceId;
protected @Nullable MieleBridgeHandler bridgeHandler;
protected TranslationProvider i18nProvider;
protected LocaleProvider localeProvider;
protected MieleTranslationProvider translationProvider;
protected @Nullable MieleTranslationProvider translationProvider;
private Class<E> selectorType;
protected String modelID;
@ -101,7 +100,7 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
String.format("Could not get enum constants for value selector: %s", valueSelectorText));
}
for (ApplianceChannelSelector c : enumConstants) {
if (c != null && c.getChannelID() != null && c.getChannelID().equals(valueSelectorText)) {
if (c.getChannelID().equals(valueSelectorText)) {
return c;
}
}
@ -117,7 +116,7 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
String.format("Could not get enum constants for value selector: %s", valueSelectorText));
}
for (ApplianceChannelSelector c : enumConstants) {
if (c != null && c.getMieleID() != null && c.getMieleID().equals(valueSelectorText)) {
if (!c.getMieleID().isEmpty() && c.getMieleID().equals(valueSelectorText)) {
return c;
}
}
@ -168,7 +167,7 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
if (applianceId != null) {
MieleBridgeHandler bridgeHandler = getMieleBridgeHandler();
if (bridgeHandler != null) {
getMieleBridgeHandler().unregisterApplianceStatusListener(this);
bridgeHandler.unregisterApplianceStatusListener(this);
}
applianceId = null;
}
@ -186,12 +185,17 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
@Override
public void onApplianceStateChanged(FullyQualifiedApplianceIdentifier applicationIdentifier,
DeviceClassObject dco) {
String myApplianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (myApplianceId == null || !myApplianceId.equals(applicationIdentifier.getApplianceId())) {
String applianceId = this.applianceId;
if (applianceId == null || !applianceId.equals(applicationIdentifier.getApplianceId())) {
return;
}
for (JsonElement prop : dco.Properties.getAsJsonArray()) {
JsonArray properties = dco.Properties;
if (properties == null) {
return;
}
for (JsonElement prop : properties.getAsJsonArray()) {
try {
DeviceProperty dp = gson.fromJson(prop, DeviceProperty.class);
if (dp == null) {
@ -210,9 +214,9 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
@Override
public void onAppliancePropertyChanged(FullyQualifiedApplianceIdentifier applicationIdentifier, DeviceProperty dp) {
String myApplianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
String applianceId = this.applianceId;
if (myApplianceId == null || !myApplianceId.equals(applicationIdentifier.getApplianceId())) {
if (applianceId == null || !applianceId.equals(applicationIdentifier.getApplianceId())) {
return;
}
@ -225,8 +229,8 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
if (dp.Metadata == null) {
String metadata = metaDataCache.get(new StringBuilder().append(dp.Name).toString().trim());
if (metadata != null) {
JsonObject jsonMetaData = (JsonObject) JsonParser.parseString(metadata);
dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class);
JsonObject jsonMetadata = (JsonObject) JsonParser.parseString(metadata);
dmd = gson.fromJson(jsonMetadata, DeviceMetaData.class);
// only keep the enum, if any - that's all we care for events we receive via multicast
// all other fields are nulled
if (dmd != null) {
@ -237,8 +241,9 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
}
}
}
if (dp.Metadata != null) {
String metadata = dp.Metadata.toString().replace("enum", "MieleEnum");
JsonObject jsonMetadata = dp.Metadata;
if (jsonMetadata != null) {
String metadata = jsonMetadata.toString().replace("enum", "MieleEnum");
JsonObject jsonMetaData = (JsonObject) JsonParser.parseString(metadata);
dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class);
metaDataCache.put(new StringBuilder().append(dp.Name).toString().trim(), metadata);
@ -269,19 +274,14 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
if (!selector.isProperty()) {
ChannelUID theChannelUID = new ChannelUID(getThing().getUID(), selector.getChannelID());
if (dp.Value != null) {
State state = selector.getState(dpValue, dmd, this.translationProvider);
logger.trace("Update state of {} with getState '{}'", theChannelUID, state);
updateState(theChannelUID, state);
updateRawChannel(dp.Name, dpValue);
} else {
updateState(theChannelUID, UnDefType.UNDEF);
}
State state = selector.getState(dpValue, dmd, this.translationProvider);
logger.trace("Update state of {} with getState '{}'", theChannelUID, state);
updateState(theChannelUID, state);
updateRawChannel(dp.Name, dpValue);
} else {
logger.debug("Updating the property '{}' of '{}' to '{}'", selector.getChannelID(),
getThing().getUID(), selector.getState(dpValue, dmd, this.translationProvider).toString());
@NonNull
Map<@NonNull String, @NonNull String> properties = editProperties();
Map<String, String> properties = editProperties();
properties.put(selector.getChannelID(),
selector.getState(dpValue, dmd, this.translationProvider).toString());
updateProperties(properties);
@ -330,31 +330,46 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
@Override
public void onApplianceRemoved(HomeDevice appliance) {
String applianceId = this.applianceId;
if (applianceId == null) {
return;
}
if (applianceId.equals(appliance.getApplianceIdentifier().getApplianceId())) {
FullyQualifiedApplianceIdentifier applianceIdentifier = appliance.getApplianceIdentifier();
if (applianceIdentifier == null) {
return;
}
if (applianceId.equals(applianceIdentifier.getApplianceId())) {
updateStatus(ThingStatus.OFFLINE);
}
}
@Override
public void onApplianceAdded(HomeDevice appliance) {
String applianceId = this.applianceId;
if (applianceId == null) {
return;
}
FullyQualifiedApplianceIdentifier applianceIdentifier = appliance.getApplianceIdentifier();
if (applianceIdentifier == null) {
return;
}
if (applianceId.equals(applianceIdentifier.getApplianceId())) {
@NonNull
Map<@NonNull String, @NonNull String> properties = editProperties();
properties.put(Thing.PROPERTY_VENDOR, appliance.Vendor);
Map<String, String> properties = editProperties();
String vendor = appliance.Vendor;
if (vendor != null) {
properties.put(Thing.PROPERTY_VENDOR, vendor);
}
properties.put(Thing.PROPERTY_MODEL_ID, appliance.getApplianceModel());
properties.put(Thing.PROPERTY_SERIAL_NUMBER, appliance.getSerialNumber());
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, appliance.getFirmwareVersion());
properties.put(PROPERTY_PROTOCOL_ADAPTER, appliance.ProtocolAdapterName);
String protocolAdapterName = appliance.ProtocolAdapterName;
if (protocolAdapterName != null) {
properties.put(PROPERTY_PROTOCOL_ADAPTER, protocolAdapterName);
}
String deviceClass = appliance.getDeviceClass();
if (deviceClass != null) {
properties.put(PROPERTY_DEVICE_CLASS, deviceClass);
@ -372,7 +387,7 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
}
}
private synchronized MieleBridgeHandler getMieleBridgeHandler() {
private synchronized @Nullable MieleBridgeHandler getMieleBridgeHandler() {
if (this.bridgeHandler == null) {
Bridge bridge = getBridge();
if (bridge == null) {
@ -380,8 +395,9 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
}
ThingHandler handler = bridge.getHandler();
if (handler instanceof MieleBridgeHandler) {
this.bridgeHandler = (MieleBridgeHandler) handler;
this.bridgeHandler.registerApplianceStatusListener(this);
var bridgeHandler = (MieleBridgeHandler) handler;
this.bridgeHandler = bridgeHandler;
bridgeHandler.registerApplianceStatusListener(this);
} else {
return null;
}
@ -390,9 +406,6 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
}
protected boolean isResultProcessable(JsonElement result) {
if (result == null) {
throw new IllegalArgumentException("Provided result is null");
}
return !result.isJsonNull();
}
}

View File

@ -30,7 +30,6 @@ import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IllformedLocaleException;
import java.util.Iterator;
import java.util.List;
@ -49,8 +48,13 @@ import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
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.exceptions.MieleRpcException;
import org.openhab.core.common.NamedThreadFactory;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.thing.Bridge;
@ -69,6 +73,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
/**
@ -80,12 +85,10 @@ import com.google.gson.JsonParser;
* @author Martin Lepsy - Added protocol information to support WiFi devices & some refactoring for HomeDevice
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
**/
@NonNullByDefault
public class MieleBridgeHandler extends BaseBridgeHandler {
@NonNull
public static final Set<@NonNull ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_XGW3000);
private static final String MIELE_CLASS = "com.miele.xgw3000.gateway.hdm.deviceclasses.Miele";
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])$");
@ -102,128 +105,14 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(MieleBridgeHandler.class);
protected List<ApplianceStatusListener> applianceStatusListeners = new CopyOnWriteArrayList<>();
protected ScheduledFuture<?> pollingJob;
protected ExecutorService executor;
protected Future<?> eventListenerJob;
protected @Nullable ScheduledFuture<?> pollingJob;
protected @Nullable ExecutorService executor;
protected @Nullable Future<?> eventListenerJob;
@NonNull
protected Map<String, HomeDevice> cachedHomeDevicesByApplianceId = new ConcurrentHashMap<String, HomeDevice>();
protected Map<String, HomeDevice> cachedHomeDevicesByRemoteUid = new ConcurrentHashMap<String, HomeDevice>();
protected URL url;
protected Map<String, String> headers;
// Data structures to de-JSONify whatever Miele appliances are sending us
public class HomeDevice {
private static final String MIELE_APPLIANCE_CLASS = "com.miele.xgw3000.gateway.hdm.deviceclasses.MieleAppliance";
public String Name;
public String Status;
public String ParentUID;
public String ProtocolAdapterName;
public String Vendor;
public String UID;
public String Type;
public JsonArray DeviceClasses;
public String Version;
public String TimestampAdded;
public JsonObject Error;
public JsonObject Properties;
HomeDevice() {
}
public FullyQualifiedApplianceIdentifier getApplianceIdentifier() {
return new FullyQualifiedApplianceIdentifier(this.UID);
}
@NonNull
public String getSerialNumber() {
return Properties.get("serial.number").getAsString();
}
@NonNull
public String getFirmwareVersion() {
return Properties.get("firmware.version").getAsString();
}
@NonNull
public String getRemoteUid() {
JsonElement remoteUid = Properties.get("remote.uid");
if (remoteUid == null) {
// remote.uid and serial.number seems to be the same. If remote.uid
// is missing for some reason, it makes sense to provide fallback
// to serial number.
return getSerialNumber();
}
return remoteUid.getAsString();
}
public String getConnectionType() {
JsonElement connectionType = Properties.get("connection.type");
if (connectionType == null) {
return null;
}
return connectionType.getAsString();
}
public String getConnectionBaudRate() {
JsonElement baudRate = Properties.get("connection.baud.rate");
if (baudRate == null) {
return null;
}
return baudRate.getAsString();
}
@NonNull
public String getApplianceModel() {
JsonElement model = Properties.get("miele.model");
if (model == null) {
return "";
}
return model.getAsString();
}
public String getDeviceClass() {
for (JsonElement dc : DeviceClasses) {
String dcStr = dc.getAsString();
if (dcStr.contains(MIELE_CLASS) && !dcStr.equals(MIELE_APPLIANCE_CLASS)) {
return dcStr.substring(MIELE_CLASS.length());
}
}
return null;
}
}
public class DeviceClassObject {
public String DeviceClassType;
public JsonArray Operations;
public String DeviceClass;
public JsonArray Properties;
DeviceClassObject() {
}
}
public class DeviceOperation {
public String Name;
public String Arguments;
public JsonObject Metadata;
DeviceOperation() {
}
}
public class DeviceProperty {
public String Name;
public String Value;
public int Polling;
public JsonObject Metadata;
DeviceProperty() {
}
}
protected @Nullable URL url;
public MieleBridgeHandler(Bridge bridge) {
super(bridge);
@ -245,9 +134,6 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
return;
}
// for future usage - no headers to be set for now
headers = new HashMap<>();
onUpdate();
lastBridgeConnectionState = false;
updateStatus(ThingStatus.UNKNOWN);
@ -328,10 +214,8 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
cachedHomeDevicesByRemoteUid.put(hd.getRemoteUid(), hd);
}
@NonNull
Set<@NonNull Entry<String, HomeDevice>> cachedEntries = cachedHomeDevicesByApplianceId.entrySet();
@NonNull
Iterator<@NonNull Entry<String, HomeDevice>> iterator = cachedEntries.iterator();
Set<Entry<String, HomeDevice>> cachedEntries = cachedHomeDevicesByApplianceId.entrySet();
Iterator<Entry<String, HomeDevice>> iterator = cachedEntries.iterator();
while (iterator.hasNext()) {
Entry<String, HomeDevice> cachedEntry = iterator.next();
@ -349,11 +233,13 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
for (Thing appliance : getThing().getThings()) {
if (appliance.getStatus() == ThingStatus.ONLINE) {
String applianceId = (String) appliance.getConfiguration().getProperties().get(APPLIANCE_ID);
FullyQualifiedApplianceIdentifier applianceIdentifier = getApplianceIdentifierFromApplianceId(
applianceId);
FullyQualifiedApplianceIdentifier applianceIdentifier = null;
if (applianceId != null) {
applianceIdentifier = getApplianceIdentifierFromApplianceId(applianceId);
}
if (applianceIdentifier == null) {
logger.error("The appliance with ID '{}' was not found in appliance list from bridge.",
logger.warn("The appliance with ID '{}' was not found in appliance list from bridge.",
applianceId);
continue;
}
@ -363,29 +249,33 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
args[1] = true;
JsonElement result = invokeRPC("HDAccess/getDeviceClassObjects", args);
if (result != null) {
for (JsonElement obj : result.getAsJsonArray()) {
try {
DeviceClassObject dco = gson.fromJson(obj, DeviceClassObject.class);
for (JsonElement obj : result.getAsJsonArray()) {
try {
DeviceClassObject dco = gson.fromJson(obj, DeviceClassObject.class);
// Skip com.prosyst.mbs.services.zigbee.hdm.deviceclasses.ReportingControl
if (dco == null || !dco.DeviceClass.startsWith(MIELE_CLASS)) {
continue;
}
for (ApplianceStatusListener listener : applianceStatusListeners) {
listener.onApplianceStateChanged(applianceIdentifier, dco);
}
} catch (Exception e) {
logger.debug("An exception occurred while querying an appliance : '{}'",
e.getMessage());
// Skip com.prosyst.mbs.services.zigbee.hdm.deviceclasses.ReportingControl
if (dco == null || !dco.DeviceClass.startsWith(MIELE_CLASS)) {
continue;
}
for (ApplianceStatusListener listener : applianceStatusListeners) {
listener.onApplianceStateChanged(applianceIdentifier, dco);
}
} catch (Exception e) {
logger.debug("An exception occurred while querying an appliance : '{}'",
e.getMessage());
}
}
}
}
} catch (Exception e) {
logger.debug("An exception occurred while polling an appliance :'{}'", e.getMessage());
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.debug("An exception occurred while polling an appliance: '{}'", e.getMessage());
} else {
logger.debug("An exception occurred while polling an appliance: '{}' -> '{}'", e.getMessage(),
cause.getMessage());
}
}
}
@ -396,12 +286,9 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
// That's why we do an HTTP access instead
// If there is no connection, this line will fail
JsonElement result = invokeRPC("system.listMethods", null);
if (result == null) {
logger.debug("{} is not reachable", ipAddress);
return false;
}
} catch (Exception e) {
invokeRPC("system.listMethods", new Object[0]);
} catch (MieleRpcException e) {
logger.debug("{} is not reachable", ipAddress);
return false;
}
@ -421,16 +308,24 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
for (JsonElement obj : result.getAsJsonArray()) {
HomeDevice hd = gson.fromJson(obj, HomeDevice.class);
devices.add(hd);
if (hd != null) {
devices.add(hd);
}
}
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.debug("An exception occurred while getting the home devices: '{}'", e.getMessage());
} else {
logger.debug("An exception occurred while getting the home devices: '{}' -> '{}", e.getMessage(),
cause.getMessage());
}
} catch (Exception e) {
logger.debug("An exception occurred while getting the home devices :'{}'", e.getMessage());
}
}
return devices;
}
private FullyQualifiedApplianceIdentifier getApplianceIdentifierFromApplianceId(String applianceId) {
private @Nullable FullyQualifiedApplianceIdentifier getApplianceIdentifierFromApplianceId(String applianceId) {
HomeDevice homeDevice = this.cachedHomeDevicesByApplianceId.get(applianceId);
if (homeDevice == null) {
return null;
@ -475,19 +370,17 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
logger.debug("Received a multicast event '{}' from '{}:{}'", event, packet.getAddress(),
packet.getPort());
DeviceProperty dp = new DeviceProperty();
String id = null;
String[] parts = event.split("&");
String id = null, name = null, value = null;
for (String p : parts) {
String[] subparts = p.split("=");
switch (subparts[0]) {
case "property": {
dp.Name = subparts[1];
name = subparts[1];
break;
}
case "value": {
dp.Value = subparts[1].strip().trim();
value = subparts[1].strip().trim();
break;
}
case "id": {
@ -497,7 +390,7 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
}
}
if (id == null) {
if (id == null || name == null || value == null) {
continue;
}
@ -514,8 +407,11 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
}
applianceIdentifier = device.getApplianceIdentifier();
}
var deviceProperty = new DeviceProperty();
deviceProperty.Name = name;
deviceProperty.Value = value;
for (ApplianceStatusListener listener : applianceStatusListeners) {
listener.onAppliancePropertyChanged(applianceIdentifier, dp);
listener.onAppliancePropertyChanged(applianceIdentifier, deviceProperty);
}
} catch (SocketTimeoutException e) {
try {
@ -549,18 +445,15 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
}
};
public JsonElement invokeOperation(String applianceId, String modelID, String methodName) {
public JsonElement invokeOperation(String applianceId, String modelID, String methodName) throws MieleRpcException {
if (getThing().getStatus() != ThingStatus.ONLINE) {
logger.debug("The Bridge is offline - operations can not be invoked.");
return null;
throw new MieleRpcException("Bridge is offline, operations can not be invoked");
}
FullyQualifiedApplianceIdentifier applianceIdentifier = getApplianceIdentifierFromApplianceId(applianceId);
if (applianceIdentifier == null) {
logger.error(
"The appliance with ID '{}' was not found in appliance list from bridge - operations can not be invoked.",
applianceId);
return null;
throw new MieleRpcException("Appliance with ID" + applianceId
+ " was not found in appliance list from gateway - operations can not be invoked");
}
Object[] args = new Object[4];
@ -572,65 +465,72 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
return invokeRPC("HDAccess/invokeDCOOperation", args);
}
protected JsonElement invokeRPC(String methodName, Object[] args) {
int id = rand.nextInt(Integer.MAX_VALUE);
protected JsonElement invokeRPC(String methodName, Object[] args) throws MieleRpcException {
JsonElement result = null;
URL url = this.url;
if (url == null) {
throw new MieleRpcException("URL is not set");
}
JsonObject req = new JsonObject();
int id = rand.nextInt(Integer.MAX_VALUE);
req.addProperty("jsonrpc", "2.0");
req.addProperty("id", id);
req.addProperty("method", methodName);
JsonElement result = null;
JsonArray params = new JsonArray();
if (args != null) {
for (Object o : args) {
params.add(gson.toJsonTree(o));
}
for (Object o : args) {
params.add(gson.toJsonTree(o));
}
req.add("params", params);
String requestData = req.toString();
String responseData = null;
try {
responseData = post(url, headers, requestData);
} catch (Exception e) {
logger.debug("An exception occurred while posting data : '{}'", e.getMessage());
responseData = post(url, Collections.emptyMap(), requestData);
} catch (IOException e) {
throw new MieleRpcException("Exception occurred while posting data", e);
}
if (responseData != null) {
logger.trace("The request '{}' yields '{}'", requestData, responseData);
JsonObject resp = (JsonObject) JsonParser.parseReader(new StringReader(responseData));
logger.trace("The request '{}' yields '{}'", requestData, responseData);
JsonObject parsedResponse = null;
try {
parsedResponse = (JsonObject) JsonParser.parseReader(new StringReader(responseData));
} catch (JsonParseException e) {
throw new MieleRpcException("Error parsing JSON response", e);
}
result = resp.get("result");
JsonElement error = resp.get("error");
if (error != null && !error.isJsonNull()) {
if (error.isJsonPrimitive()) {
logger.debug("A remote exception occurred: '{}'", error.getAsString());
} else if (error.isJsonObject()) {
JsonObject o = error.getAsJsonObject();
Integer code = (o.has("code") ? o.get("code").getAsInt() : null);
String message = (o.has("message") ? o.get("message").getAsString() : null);
String data = (o.has("data") ? (o.get("data") instanceof JsonObject ? o.get("data").toString()
: o.get("data").getAsString()) : null);
logger.debug("A remote exception occurred: '{}':'{}':'{}'", code, message, data);
} else {
logger.debug("An unknown remote exception occurred: '{}'", error.toString());
}
JsonElement error = parsedResponse.get("error");
if (error != null && !error.isJsonNull()) {
if (error.isJsonPrimitive()) {
throw new MieleRpcException("Remote exception occurred: '" + error.getAsString() + "'");
} else if (error.isJsonObject()) {
JsonObject o = error.getAsJsonObject();
Integer code = (o.has("code") ? o.get("code").getAsInt() : null);
String message = (o.has("message") ? o.get("message").getAsString() : null);
String data = (o.has("data")
? (o.get("data") instanceof JsonObject ? o.get("data").toString() : o.get("data").getAsString())
: null);
throw new MieleRpcException(
"Remote exception occurred: '" + code + "':'" + message + "':'" + data + "'");
} else {
throw new MieleRpcException("Unknown remote exception occurred: '" + error.toString() + "'");
}
}
result = parsedResponse.get("result");
if (result == null) {
throw new MieleRpcException("Result is missing in response");
}
return result;
}
protected String post(URL url, Map<String, String> headers, String data) throws IOException {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
if (headers != null) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
connection.addRequestProperty(entry.getKey(), entry.getValue());
}
for (Map.Entry<String, String> entry : headers.entrySet()) {
connection.addRequestProperty(entry.getKey(), entry.getValue());
}
connection.addRequestProperty("Accept-Encoding", "gzip");
@ -688,16 +588,21 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
private synchronized void onUpdate() {
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);
this.pollingJob = pollingJob;
logger.trace("Scheduling the Miele polling job Job is done ?{}", pollingJob.isDone());
}
logger.debug("Scheduling the Miele event listener job");
Future<?> eventListenerJob = this.eventListenerJob;
if (eventListenerJob == null || eventListenerJob.isCancelled()) {
executor = Executors.newSingleThreadExecutor(new NamedThreadFactory("binding-miele"));
eventListenerJob = executor.submit(eventListenerRunnable);
ExecutorService executor = Executors
.newSingleThreadExecutor(new NamedThreadFactory("binding-" + BINDING_ID));
this.executor = executor;
this.eventListenerJob = executor.submit(eventListenerRunnable);
}
}
@ -725,9 +630,6 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
}
public boolean registerApplianceStatusListener(ApplianceStatusListener applianceStatusListener) {
if (applianceStatusListener == null) {
throw new IllegalArgumentException("It's not allowed to pass a null ApplianceStatusListener.");
}
boolean result = applianceStatusListeners.add(applianceStatusListener);
if (result && isInitialized()) {
onUpdate();
@ -759,17 +661,20 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
@Override
public void dispose() {
super.dispose();
ScheduledFuture<?> pollingJob = this.pollingJob;
if (pollingJob != null) {
pollingJob.cancel(true);
pollingJob = null;
this.pollingJob = null;
}
Future<?> eventListenerJob = this.eventListenerJob;
if (eventListenerJob != null) {
eventListenerJob.cancel(true);
eventListenerJob = null;
this.eventListenerJob = null;
}
ExecutorService executor = this.executor;
if (executor != null) {
executor.shutdownNow();
executor = null;
this.executor = null;
}
}
}

View File

@ -21,9 +21,11 @@ import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import org.openhab.binding.miele.internal.DeviceMetaData;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
@ -43,39 +45,35 @@ import org.slf4j.LoggerFactory;
* @author Kai Kreuzer - Changed START_TIME to DateTimeType
* @author Jacob Laursen - Added UoM for temperatures, raw channels
*/
@NonNullByDefault
public enum OvenChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getStateTextState(s, dmd, translationProvider);
}
},
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false),
STATE("", STATE_CHANNEL_ID, DecimalType.class, false),
PROGRAM_TEXT(PROGRAM_ID_PROPERTY_NAME, PROGRAM_TEXT_CHANNEL_ID, StringType.class, false),
PROGRAM(null, PROGRAM_CHANNEL_ID, DecimalType.class, false),
PROGRAM("", PROGRAM_CHANNEL_ID, DecimalType.class, false),
PROGRAMTYPE("programType", "type", StringType.class, false),
PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getTextState(s, dmd, translationProvider, phases, MISSING_PHASE_TEXT_PREFIX,
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PHASES, MISSING_PHASE_TEXT_PREFIX,
MIELE_OVEN_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
}
},
PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false),
START_TIME("startTime", "start", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -89,7 +87,8 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
},
DURATION("duration", "duration", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -103,7 +102,8 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
},
ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -117,7 +117,8 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
},
FINISH_TIME("finishTime", "finish", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -131,32 +132,37 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
},
TARGET_TEMP("targetTemperature", "target", QuantityType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s);
}
},
MEASURED_TEMP("measuredTemperature", "measured", QuantityType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s);
}
},
DEVICE_TEMP_ONE("deviceTemperature1", "temp1", QuantityType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s);
}
},
DEVICE_TEMP_TWO("deviceTemperature2", "temp2", QuantityType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s);
}
},
DOOR("signalDoor", "door", OpenClosedType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
if ("true".equals(s)) {
return getState("OPEN");
}
@ -168,12 +174,12 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
return UnDefType.UNDEF;
}
},
STOP(null, "stop", OnOffType.class, false),
SWITCH(null, "switch", OnOffType.class, false);
STOP("", "stop", OnOffType.class, false),
SWITCH("", "switch", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(OvenChannelSelector.class);
private static final Map<String, String> phases = Map.ofEntries(entry("1", "heating"), entry("2", "temp-hold"),
private static final Map<String, String> PHASES = Map.ofEntries(entry("1", "heating"), entry("2", "temp-hold"),
entry("3", "door-open"), entry("4", "pyrolysis"), entry("7", "lighting"), entry("8", "searing-phase"),
entry("10", "defrost"), entry("11", "cooling-down"), entry("12", "energy-save-phase"));
@ -215,12 +221,13 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
}
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd);
}
@Override
public State getState(String s, DeviceMetaData dmd) {
public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) {
@ -247,7 +254,7 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
return UnDefType.UNDEF;
}
public State getTemperatureState(String s) {

View File

@ -15,6 +15,8 @@ package org.openhab.binding.miele.internal.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_OVEN;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType;
@ -36,6 +38,7 @@ import com.google.gson.JsonElement;
* @author Martin Lepsy - fixed handling of empty JSON results
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
*/
@NonNullByDefault
public class OvenHandler extends MieleApplianceHandler<OvenChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(OvenHandler.class);
@ -50,32 +53,39 @@ public class OvenHandler extends MieleApplianceHandler<OvenChannelSelector> {
String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId == null) {
logger.warn("Command '{}' failed, appliance id is unknown", command);
return;
}
OvenChannelSelector selector = (OvenChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
try {
if (selector != null) {
switch (selector) {
case SWITCH: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOn");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOff");
}
break;
MieleBridgeHandler bridgeHandler = this.bridgeHandler;
if (bridgeHandler == null) {
logger.warn("Command '{}' failed, missing bridge handler", command);
return;
}
switch (selector) {
case SWITCH: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOn");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOff");
}
case STOP: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
}
break;
break;
}
case STOP: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
break;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
}
}
@ -87,6 +97,14 @@ public class OvenHandler extends MieleApplianceHandler<OvenChannelSelector> {
logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString());
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("An error occurred while trying to invoke operation: {}", e.getMessage());
} else {
logger.warn("An error occurred while trying to invoke operation: {} -> {}", e.getMessage(),
cause.getMessage());
}
}
}
}

View File

@ -21,9 +21,11 @@ import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import org.openhab.binding.miele.internal.DeviceMetaData;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
@ -42,49 +44,42 @@ import org.slf4j.LoggerFactory;
* @author Kai Kreuzer - Changed START_TIME to DateTimeType
* @author Jacob Laursen - Added raw channels
*/
@NonNullByDefault
public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getStateTextState(s, dmd, translationProvider);
}
},
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false),
STATE("", STATE_CHANNEL_ID, DecimalType.class, false),
PROGRAM_TEXT(PROGRAM_ID_PROPERTY_NAME, PROGRAM_TEXT_CHANNEL_ID, StringType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getTextState(s, dmd, translationProvider, programs, MISSING_PROGRAM_TEXT_PREFIX,
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PROGRAMS, MISSING_PROGRAM_TEXT_PREFIX,
MIELE_TUMBLE_DRYER_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
}
},
PROGRAM(null, PROGRAM_CHANNEL_ID, DecimalType.class, false),
PROGRAM("", PROGRAM_CHANNEL_ID, DecimalType.class, false),
PROGRAMTYPE("programType", "type", StringType.class, false),
PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getTextState(s, dmd, translationProvider, phases, MISSING_PHASE_TEXT_PREFIX,
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PHASES, MISSING_PHASE_TEXT_PREFIX,
MIELE_TUMBLE_DRYER_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
}
},
PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false),
START_TIME("startTime", "start", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -98,7 +93,8 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
},
DURATION("duration", "duration", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -112,7 +108,8 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
},
ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -126,7 +123,8 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
},
FINISH_TIME("finishTime", "finish", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -140,14 +138,16 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
},
DRYING_STEP("dryingStep", "step", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getState(s);
}
},
DOOR("signalDoor", "door", OpenClosedType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
if ("true".equals(s)) {
return getState("OPEN");
}
@ -159,11 +159,11 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
return UnDefType.UNDEF;
}
},
SWITCH(null, "switch", OnOffType.class, false);
SWITCH("", "switch", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(TumbleDryerChannelSelector.class);
private static final Map<String, String> programs = Map.ofEntries(entry("10", "automatic-plus"),
private static final Map<String, String> PROGRAMS = Map.ofEntries(entry("10", "automatic-plus"),
entry("20", "cottons"), entry("23", "cottons-hygiene"), entry("30", "minimum-iron"),
entry("31", "gentle-minimum-iron"), entry("40", "woollens-handcare"), entry("50", "delicates"),
entry("60", "warm-air"), entry("70", "cool-air"), entry("80", "express"), entry("90", "cottons-eco"),
@ -173,7 +173,7 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
entry("190", "standard-pillows"), entry("220", "basket-programme"), entry("240", "smoothing"),
entry("65000", "cottons-auto-load-control"), entry("65001", "minimum-iron-auto-load-control"));
private static final Map<String, String> phases = Map.ofEntries(entry("1", "programme-running"),
private static final Map<String, String> PHASES = Map.ofEntries(entry("1", "programme-running"),
entry("2", "drying"), entry("3", "drying-machine-iron"), entry("4", "drying-hand-iron"),
entry("5", "drying-normal"), entry("6", "drying-normal-plus"), entry("7", "cooling-down"),
entry("8", "drying-hand-iron"), entry("10", "finished"));
@ -217,12 +217,13 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
}
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd);
}
@Override
public State getState(String s, DeviceMetaData dmd) {
public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) {
@ -249,6 +250,6 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
return UnDefType.UNDEF;
}
}

View File

@ -15,6 +15,8 @@ package org.openhab.binding.miele.internal.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_TUMBLE_DRYER;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType;
@ -36,6 +38,7 @@ import com.google.gson.JsonElement;
* @author Martin Lepsy - fixed handling of empty JSON results
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
**/
@NonNullByDefault
public class TumbleDryerHandler extends MieleApplianceHandler<TumbleDryerChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(TumbleDryerHandler.class);
@ -50,26 +53,33 @@ public class TumbleDryerHandler extends MieleApplianceHandler<TumbleDryerChannel
String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId == null) {
logger.warn("Command '{}' failed, appliance id is unknown", command);
return;
}
TumbleDryerChannelSelector selector = (TumbleDryerChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
try {
if (selector != null) {
switch (selector) {
case SWITCH: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "start");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
}
break;
switch (selector) {
case SWITCH: {
MieleBridgeHandler bridgeHandler = this.bridgeHandler;
if (bridgeHandler == null) {
logger.warn("Command '{}' failed, missing bridge handler", command);
return;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "start");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
}
break;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
}
}
@ -81,6 +91,14 @@ public class TumbleDryerHandler extends MieleApplianceHandler<TumbleDryerChannel
logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString());
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("An error occurred while trying to invoke operation: {}", e.getMessage());
} else {
logger.warn("An error occurred while trying to invoke operation: {} -> {}", e.getMessage(),
cause.getMessage());
}
}
}
}

View File

@ -21,9 +21,11 @@ import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import org.openhab.binding.miele.internal.DeviceMetaData;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
@ -43,49 +45,42 @@ import org.slf4j.LoggerFactory;
* @author Kai Kreuzer - Changed START_TIME to DateTimeType
* @author Jacob Laursen - Added power/water consumption channels, UoM for temperatures, raw channels
*/
@NonNullByDefault
public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true, false),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true, false),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getStateTextState(s, dmd, translationProvider);
}
},
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false, false),
STATE("", STATE_CHANNEL_ID, DecimalType.class, false, false),
PROGRAM_TEXT(PROGRAM_ID_PROPERTY_NAME, PROGRAM_TEXT_CHANNEL_ID, StringType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getTextState(s, dmd, translationProvider, programs, MISSING_PROGRAM_TEXT_PREFIX,
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PROGRAMS, MISSING_PROGRAM_TEXT_PREFIX,
MIELE_WASHING_MACHINE_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
}
},
PROGRAM(null, PROGRAM_CHANNEL_ID, DecimalType.class, false, false),
PROGRAM("", PROGRAM_CHANNEL_ID, DecimalType.class, false, false),
PROGRAMTYPE("programType", "type", StringType.class, false, false),
PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
State state = DeviceUtil.getTextState(s, dmd, translationProvider, phases, MISSING_PHASE_TEXT_PREFIX,
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PHASES, MISSING_PHASE_TEXT_PREFIX,
MIELE_WASHING_MACHINE_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
}
},
PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false, false),
START_TIME("startTime", "start", DateTimeType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -99,7 +94,8 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
},
DURATION("duration", "duration", DateTimeType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -113,7 +109,8 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
},
ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -127,7 +124,8 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
},
FINISH_TIME("finishTime", "finish", DateTimeType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -141,13 +139,15 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
},
TARGET_TEMP("targetTemperature", "target", QuantityType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s);
}
},
SPINNING_SPEED("spinningSpeed", "spinningspeed", StringType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
if ("0".equals(s)) {
return getState("Without spinning");
}
@ -159,8 +159,8 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
},
DOOR("signalDoor", "door", OpenClosedType.class, false, false) {
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
if ("true".equals(s)) {
return getState("OPEN");
}
@ -172,7 +172,7 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
return UnDefType.UNDEF;
}
},
SWITCH(null, "switch", OnOffType.class, false, false),
SWITCH("", "switch", OnOffType.class, false, false),
POWER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, POWER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false,
true),
WATER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, WATER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false,
@ -180,7 +180,7 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
private final Logger logger = LoggerFactory.getLogger(WashingMachineChannelSelector.class);
private static final Map<String, String> programs = Map.ofEntries(entry("1", "cottons"), entry("3", "minimum-iron"),
private static final Map<String, String> PROGRAMS = Map.ofEntries(entry("1", "cottons"), entry("3", "minimum-iron"),
entry("4", "delicates"), entry("8", "woollens"), entry("9", "silks"), entry("17", "starch"),
entry("18", "rinse"), entry("21", "drain-spin"), entry("22", "curtains"), entry("23", "shirts"),
entry("24", "denim"), entry("27", "proofing"), entry("29", "sportswear"), entry("31", "automatic-plus"),
@ -189,7 +189,7 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
entry("95", "down-duvets"), entry("122", "express-20"), entry("129", "down-filled-items"),
entry("133", "cottons-eco"), entry("146", "quickpowerwash"), entry("65532", "mix"));
private static final Map<String, String> phases = Map.ofEntries(entry("1", "pre-wash"), entry("4", "washing"),
private static final Map<String, String> PHASES = Map.ofEntries(entry("1", "pre-wash"), entry("4", "washing"),
entry("5", "rinses"), entry("7", "clean"), entry("9", "drain"), entry("10", "spin"),
entry("11", "anti-crease"), entry("12", "finished"));
@ -234,12 +234,13 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
}
@Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd);
}
@Override
public State getState(String s, DeviceMetaData dmd) {
public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) {
@ -266,7 +267,7 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
return UnDefType.UNDEF;
}
public State getTemperatureState(String s) {

View File

@ -19,6 +19,8 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.WATER_CON
import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType;
@ -42,6 +44,7 @@ import com.google.gson.JsonElement;
* @author Martin Lepsy - fixed handling of empty JSON results
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN), added power/water consumption channels
**/
@NonNullByDefault
public class WashingMachineHandler extends MieleApplianceHandler<WashingMachineChannelSelector>
implements ExtendedDeviceStateListener {
@ -62,27 +65,34 @@ public class WashingMachineHandler extends MieleApplianceHandler<WashingMachineC
String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId == null) {
logger.warn("Command '{}' failed, appliance id is unknown", command);
return;
}
WashingMachineChannelSelector selector = (WashingMachineChannelSelector) getValueSelectorFromChannelID(
channelID);
JsonElement result = null;
try {
if (selector != null) {
switch (selector) {
case SWITCH: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "start");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
}
break;
switch (selector) {
case SWITCH: {
MieleBridgeHandler bridgeHandler = this.bridgeHandler;
if (bridgeHandler == null) {
logger.warn("Command '{}' failed, missing bridge handler", command);
return;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "start");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
}
break;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
}
}
@ -94,6 +104,14 @@ public class WashingMachineHandler extends MieleApplianceHandler<WashingMachineC
logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString());
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("An error occurred while trying to invoke operation: {}", e.getMessage());
} else {
logger.warn("An error occurred while trying to invoke operation: {} -> {}", e.getMessage(),
cause.getMessage());
}
}
}

View File

@ -13,10 +13,15 @@
package org.openhab.binding.miele.internal;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.test.java.JavaTest;
@ -28,9 +33,12 @@ import org.openhab.core.types.UnDefType;
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
@ExtendWith(MockitoExtension.class)
public class DeviceUtilTest extends JavaTest {
private @NonNullByDefault({}) @Mock MieleTranslationProvider translationProvider;
@Test
public void bytesToHexWhenTopBitIsUsedReturnsCorrectString() {
String actual = DeviceUtil.bytesToHex(new byte[] { (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef });
@ -70,11 +78,6 @@ public class DeviceUtilTest extends JavaTest {
assertThrows(NumberFormatException.class, () -> DeviceUtil.getTemperatureState("A"));
}
@Test
public void getTemperatureStateNullValueThrowsNumberFormatException() {
assertThrows(NumberFormatException.class, () -> DeviceUtil.getTemperatureState(null));
}
@Test
public void getStateTextStateProviderHasPrecedence() {
assertEquals("I brug", this.getStateTextState("5", "Running", "miele.state.running", "I brug"));
@ -82,19 +85,22 @@ public class DeviceUtilTest extends JavaTest {
@Test
public void getStateTextStateGatewayTextIsReturnedWhenKeyIsUnknown() {
assertEquals("Running", this.getStateTextState("-1", "Running", "key.unknown", "I brug"));
assertEquals("Running", this.getStateTextState("-1", "Running"));
}
@Test
public void getStateTextStateKeyIsReturnedWhenUnknownByGatewayAndProvider() {
assertEquals("state.99", this.getStateTextState("99", null, "key.unknown", "I brug"));
assertEquals("state.99", this.getStateTextState("99", null));
}
private String getStateTextState(String value, String localizedValue, String mockedKey, String mockedValue) {
when(translationProvider.getText(mockedKey, localizedValue)).thenReturn(mockedValue);
return getStateTextState(value, localizedValue);
}
private String getStateTextState(String value, @Nullable String localizedValue) {
var metaData = new DeviceMetaData();
metaData.LocalizedValue = localizedValue;
var translationProvider = mock(MieleTranslationProvider.class);
when(translationProvider.getText(mockedKey, metaData.LocalizedValue)).thenReturn(mockedValue);
return DeviceUtil.getStateTextState(value, metaData, translationProvider).toString();
}

View File

@ -14,6 +14,7 @@ package org.openhab.binding.miele.internal;
import static org.junit.jupiter.api.Assertions.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.core.test.java.JavaTest;
@ -23,6 +24,7 @@ import org.openhab.core.test.java.JavaTest;
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class FullyQualifiedApplianceIdentifierTest extends JavaTest {
@Test