added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.miele-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-miele" description="Miele@home Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-upnp</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.miele/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2020 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;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link MieleBinding} class defines common constants, which are
* used across the whole binding.
*
* @author Karel Goderis - Initial contribution
* @author Martin Lepsy - added constants for support of WiFi devices & protocol
*/
@NonNullByDefault
public class MieleBindingConstants {
public static final String BINDING_ID = "miele";
public static final String APPLIANCE_ID = "uid";
public static final String DEVICE_CLASS = "dc";
public static final String HDM_LAN = "hdm:LAN:";
public static final String HDM_ZIGBEE = "hdm:ZigBee:";
public static final String PROTOCOL_PROPERTY_NAME = "protocol";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_XGW3000 = new ThingTypeUID(BINDING_ID, "xgw3000");
public static final ThingTypeUID THING_TYPE_DISHWASHER = new ThingTypeUID(BINDING_ID, "dishwasher");
public static final ThingTypeUID THING_TYPE_OVEN = new ThingTypeUID(BINDING_ID, "oven");
public static final ThingTypeUID THING_TYPE_FRIDGE = new ThingTypeUID(BINDING_ID, "fridge");
public static final ThingTypeUID THING_TYPE_DRYER = new ThingTypeUID(BINDING_ID, "tumbledryer");
public static final ThingTypeUID THING_TYPE_HOB = new ThingTypeUID(BINDING_ID, "hob");
public static final ThingTypeUID THING_TYPE_FRIDGEFREEZER = new ThingTypeUID(BINDING_ID, "fridgefreezer");
public static final ThingTypeUID THING_TYPE_HOOD = new ThingTypeUID(BINDING_ID, "hood");
public static final ThingTypeUID THING_TYPE_WASHINGMACHINE = new ThingTypeUID(BINDING_ID, "washingmachine");
public static final ThingTypeUID THING_TYPE_COFFEEMACHINE = new ThingTypeUID(BINDING_ID, "coffeemachine");
// Bridge config properties
public static final String HOST = "ipAddress";
public static final String INTERFACE = "interface";
public static final String USER_NAME = "userName";
public static final String PASSWORD = "password";
}

View File

@@ -0,0 +1,163 @@
/**
* Copyright (c) 2010-2020 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;
import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openhab.binding.miele.internal.discovery.MieleApplianceDiscoveryService;
import org.openhab.binding.miele.internal.handler.CoffeeMachineHandler;
import org.openhab.binding.miele.internal.handler.DishWasherHandler;
import org.openhab.binding.miele.internal.handler.FridgeFreezerHandler;
import org.openhab.binding.miele.internal.handler.FridgeHandler;
import org.openhab.binding.miele.internal.handler.HobHandler;
import org.openhab.binding.miele.internal.handler.HoodHandler;
import org.openhab.binding.miele.internal.handler.MieleApplianceHandler;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler;
import org.openhab.binding.miele.internal.handler.OvenHandler;
import org.openhab.binding.miele.internal.handler.TumbleDryerHandler;
import org.openhab.binding.miele.internal.handler.WashingMachineHandler;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Component;
/**
* The {@link MieleHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Karel Goderis - Initial contribution
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.miele")
public class MieleHandlerFactory extends BaseThingHandlerFactory {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
.concat(MieleBridgeHandler.SUPPORTED_THING_TYPES.stream(),
MieleApplianceHandler.SUPPORTED_THING_TYPES.stream())
.collect(Collectors.toSet());
private Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
public Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, ThingUID thingUID,
ThingUID bridgeUID) {
if (MieleBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
ThingUID mieleBridgeUID = getBridgeThingUID(thingTypeUID, thingUID, configuration);
return super.createThing(thingTypeUID, configuration, mieleBridgeUID, null);
}
if (MieleApplianceHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
ThingUID mieleApplianceUID = getApplianceUID(thingTypeUID, thingUID, configuration, bridgeUID);
return super.createThing(thingTypeUID, configuration, mieleApplianceUID, bridgeUID);
}
throw new IllegalArgumentException(
"The thing type " + thingTypeUID + " is not supported by the miele binding.");
}
@Override
protected ThingHandler createHandler(Thing thing) {
if (MieleBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
MieleBridgeHandler handler = new MieleBridgeHandler((Bridge) thing);
registerApplianceDiscoveryService(handler);
return handler;
} else if (MieleApplianceHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
if (thing.getThingTypeUID().equals(THING_TYPE_HOOD)) {
return new HoodHandler(thing);
}
if (thing.getThingTypeUID().equals(THING_TYPE_FRIDGEFREEZER)) {
return new FridgeFreezerHandler(thing);
}
if (thing.getThingTypeUID().equals(THING_TYPE_FRIDGE)) {
return new FridgeHandler(thing);
}
if (thing.getThingTypeUID().equals(THING_TYPE_OVEN)) {
return new OvenHandler(thing);
}
if (thing.getThingTypeUID().equals(THING_TYPE_HOB)) {
return new HobHandler(thing);
}
if (thing.getThingTypeUID().equals(THING_TYPE_WASHINGMACHINE)) {
return new WashingMachineHandler(thing);
}
if (thing.getThingTypeUID().equals(THING_TYPE_DRYER)) {
return new TumbleDryerHandler(thing);
}
if (thing.getThingTypeUID().equals(THING_TYPE_DISHWASHER)) {
return new DishWasherHandler(thing);
}
if (thing.getThingTypeUID().equals(THING_TYPE_COFFEEMACHINE)) {
return new CoffeeMachineHandler(thing);
}
}
return null;
}
private ThingUID getBridgeThingUID(ThingTypeUID thingTypeUID, ThingUID thingUID, Configuration configuration) {
if (thingUID == null) {
String hostID = (String) configuration.get(HOST);
thingUID = new ThingUID(thingTypeUID, hostID);
}
return thingUID;
}
private ThingUID getApplianceUID(ThingTypeUID thingTypeUID, ThingUID thingUID, Configuration configuration,
ThingUID bridgeUID) {
String applianceId = (String) configuration.get(APPLIANCE_ID);
if (thingUID == null) {
thingUID = new ThingUID(thingTypeUID, applianceId, bridgeUID.getId());
}
return thingUID;
}
private synchronized void registerApplianceDiscoveryService(MieleBridgeHandler bridgeHandler) {
MieleApplianceDiscoveryService discoveryService = new MieleApplianceDiscoveryService(bridgeHandler);
discoveryService.activate();
this.discoveryServiceRegs.put(bridgeHandler.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
}
@Override
protected synchronized void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof MieleBridgeHandler) {
ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.remove(thingHandler.getThing().getUID());
if (serviceReg != null) {
// remove discovery service, if bridge handler is removed
MieleApplianceDiscoveryService service = (MieleApplianceDiscoveryService) bundleContext
.getService(serviceReg.getReference());
serviceReg.unregister();
if (service != null) {
service.deactivate();
}
}
}
}
}

View File

@@ -0,0 +1,170 @@
/**
* Copyright (c) 2010-2020 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.discovery;
import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
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;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement;
/**
* The {@link MieleApplianceDiscoveryService} tracks appliances that are
* associated with the Miele@Home gateway
*
* @author Karel Goderis - Initial contribution
* @author Martin Lepsy - Added protocol information in order so support WiFi devices
*/
public class MieleApplianceDiscoveryService extends AbstractDiscoveryService implements ApplianceStatusListener {
private final Logger logger = LoggerFactory.getLogger(MieleApplianceDiscoveryService.class);
private static final int SEARCH_TIME = 60;
private MieleBridgeHandler mieleBridgeHandler;
public MieleApplianceDiscoveryService(MieleBridgeHandler mieleBridgeHandler) {
super(MieleApplianceHandler.SUPPORTED_THING_TYPES, SEARCH_TIME, false);
this.mieleBridgeHandler = mieleBridgeHandler;
}
public void activate() {
mieleBridgeHandler.registerApplianceStatusListener(this);
}
@Override
public void deactivate() {
removeOlderResults(new Date().getTime());
mieleBridgeHandler.unregisterApplianceStatusListener(this);
}
@Override
public Set<ThingTypeUID> getSupportedThingTypes() {
return MieleApplianceHandler.SUPPORTED_THING_TYPES;
}
@Override
public void startScan() {
List<HomeDevice> appliances = mieleBridgeHandler.getHomeDevices();
if (appliances != null) {
for (HomeDevice l : appliances) {
onApplianceAddedInternal(l);
}
}
}
@Override
protected synchronized void stopScan() {
super.stopScan();
removeOlderResults(getTimestampOfLastScan());
}
@Override
public void onApplianceAdded(HomeDevice appliance) {
onApplianceAddedInternal(appliance);
}
private void onApplianceAddedInternal(HomeDevice appliance) {
ThingUID thingUID = getThingUID(appliance);
if (thingUID != null) {
ThingUID bridgeUID = mieleBridgeHandler.getThing().getUID();
Map<String, Object> properties = new HashMap<>(2);
properties.put(PROTOCOL_PROPERTY_NAME, appliance.getProtocol());
properties.put(APPLIANCE_ID, appliance.getApplianceId());
for (JsonElement dc : appliance.DeviceClasses) {
if (dc.getAsString().contains("com.miele.xgw3000.gateway.hdm.deviceclasses.Miele")
&& !dc.getAsString().equals("com.miele.xgw3000.gateway.hdm.deviceclasses.MieleAppliance")) {
properties.put(DEVICE_CLASS, StringUtils.right(dc.getAsString(), dc.getAsString().length()
- new String("com.miele.xgw3000.gateway.hdm.deviceclasses.Miele").length()));
break;
}
}
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
.withBridge(bridgeUID).withLabel((String) properties.get(DEVICE_CLASS)).build();
thingDiscovered(discoveryResult);
} else {
logger.debug("Discovered an unsupported appliance of vendor '{}' with id {}", appliance.Vendor,
appliance.UID);
}
}
@Override
public void onApplianceRemoved(HomeDevice appliance) {
ThingUID thingUID = getThingUID(appliance);
if (thingUID != null) {
thingRemoved(thingUID);
}
}
@Override
public void onApplianceStateChanged(String uid, DeviceClassObject dco) {
// nothing to do
}
@Override
public void onAppliancePropertyChanged(String uid, DeviceProperty dp) {
// nothing to do
}
private ThingUID getThingUID(HomeDevice appliance) {
ThingUID bridgeUID = mieleBridgeHandler.getThing().getUID();
String modelID = null;
for (JsonElement dc : appliance.DeviceClasses) {
if (dc.getAsString().contains("com.miele.xgw3000.gateway.hdm.deviceclasses.Miele")
&& !dc.getAsString().equals("com.miele.xgw3000.gateway.hdm.deviceclasses.MieleAppliance")) {
modelID = StringUtils.right(dc.getAsString(), dc.getAsString().length()
- new String("com.miele.xgw3000.gateway.hdm.deviceclasses.Miele").length());
break;
}
}
if (modelID != null) {
ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID,
StringUtils.lowerCase(modelID.replaceAll("[^a-zA-Z0-9_]", "_")));
if (getSupportedThingTypes().contains(thingTypeUID)) {
ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, appliance.getId());
return thingUID;
} else {
return null;
}
} else {
return null;
}
}
}

View File

@@ -0,0 +1,117 @@
/**
* Copyright (c) 2010-2020 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.discovery;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.jmdns.ServiceInfo;
import org.openhab.binding.miele.internal.MieleBindingConstants;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
import org.openhab.core.config.discovery.mdns.internal.MDNSDiscoveryService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link MieleMDNSDiscoveryParticipant} is responsible for discovering Miele XGW3000 Gateways. It uses the central
* {@link MDNSDiscoveryService}.
*
* @author Karel Goderis - Initial contribution
* @author Martin Lepsy - Added check for Miele gateway for cleaner discovery
*
*/
@Component(immediate = true)
public class MieleMDNSDiscoveryParticipant implements MDNSDiscoveryParticipant {
private final Logger logger = LoggerFactory.getLogger(MieleMDNSDiscoveryParticipant.class);
private static final String PATH_TO_CHECK_FOR_XGW3000 = "/rest/";
private static final String SERVICE_NAME = "mieleathome";
private static final String PATH_PROPERTY_NAME = "path";
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return Collections.singleton(MieleBindingConstants.THING_TYPE_XGW3000);
}
@Override
public String getServiceType() {
return "_mieleathome._tcp.local.";
}
@Override
public DiscoveryResult createResult(ServiceInfo service) {
if (isMieleGateway(service)) {
ThingUID uid = getThingUID(service);
if (uid != null) {
Map<String, Object> properties = new HashMap<>(2);
InetAddress[] addresses = service.getInetAddresses();
if (addresses.length > 0 && addresses[0] != null) {
properties.put(MieleBindingConstants.HOST, addresses[0].getHostAddress());
Socket socket = null;
try {
socket = new Socket(addresses[0], 80);
InetAddress ourAddress = socket.getLocalAddress();
properties.put(MieleBindingConstants.INTERFACE, ourAddress.getHostAddress());
} catch (IOException e) {
logger.error("An exception occurred while connecting to the Miele Gateway : '{}'",
e.getMessage());
}
}
return DiscoveryResultBuilder.create(uid).withProperties(properties)
.withRepresentationProperty(uid.getId()).withLabel("Miele XGW3000 Gateway").build();
}
}
return null;
}
@Override
public ThingUID getThingUID(ServiceInfo service) {
if (service.getType() != null) {
if (service.getType().equals(getServiceType())) {
logger.trace("Discovered a Miele@Home gateway thing with name '{}'", service.getName());
return new ThingUID(MieleBindingConstants.THING_TYPE_XGW3000, service.getName().replace(" ", "_"));
}
}
return null;
}
/**
* Checks if service is a Miele XGW3000 Gateway
*
* application must be mieleathome
* must contain path with value /rest/
*
* @param service the service to check
* @return true, if the discovered service is a Miele XGW3000 Gateway
*/
private boolean isMieleGateway(ServiceInfo service) {
return service.getApplication().contains(SERVICE_NAME) && service.getPropertyString(PATH_PROPERTY_NAME) != null
&& service.getPropertyString(PATH_PROPERTY_NAME).equalsIgnoreCase(PATH_TO_CHECK_FOR_XGW3000);
}
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2010-2020 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.handler;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceMetaData;
import org.openhab.core.types.State;
import org.openhab.core.types.Type;
/**
* The {@link ApplianceChannelSelector} class defines a common interface for
* all the data structures used by appliance thing handlers. It is used to traverse
* the channels that possibly exist for an appliance, and convert data
* returned by the appliance to a ESH compatible State
*
* @author Karel Goderis - Initial contribution
*/
public interface ApplianceChannelSelector {
@Override
String toString();
/**
* Returns the ESH ChannelID for the given datapoint
*/
String getChannelID();
/**
* Returns the Miele defined ID for the given datapoint
*/
String getMieleID();
/**
* Returns true if the given datapoint is to be considered as a Property
* instead of a regular modifiable datapoint
*/
boolean isProperty();
/**
*
* Returns a State for the given string, taking into
* account the metadata provided. The meta data is sent by
* the Miele appliance and is used to decide the State type
*
* @param s - the value to be used to instantiate the State
* @param dmd - the device meta data
*/
State getState(String s, DeviceMetaData dmd);
/**
* Returns "compatible" Type for this datapoint
*/
Class<? extends Type> getTypeClass();
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2020 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.handler;
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;
/**
*
* The {@link ApplianceStatusListener} is notified when an appliance status has changed or
* an appliance has been removed or added.
*
* @author Karel Goderis - Initial contribution
*/
public interface ApplianceStatusListener {
/**
* This method is called whenever the state of the given appliance has changed.
*
* @param uid the UID of the aplliance that has changed
* @param dco the POJO containing the new state (properties and/or operations)
*/
void onApplianceStateChanged(String uid, DeviceClassObject dco);
/**
* This method is called whenever a "property" of the given appliance has changed.
*
* @param uid the UID of the aplliance that has changed
* @param dco the POJO containing the new state of the property
*/
void onAppliancePropertyChanged(String uid, DeviceProperty dp);
/**
* This method us called whenever an appliance is removed.
*
* @param appliance The XGW homedevice definition of the appliance that was removed
*/
void onApplianceRemoved(HomeDevice appliance);
/**
* This method us called whenever an appliance is added.
*
* @param appliance The XGW homedevice definition of the appliance that was removed
*/
void onApplianceAdded(HomeDevice appliance);
}

View File

@@ -0,0 +1,144 @@
/**
* Copyright (c) 2010-2020 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.handler;
import java.lang.reflect.Method;
import java.util.Map.Entry;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceMetaData;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
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;
import com.google.gson.JsonElement;
/**
* The {@link ApplianceChannelSelector} for coffee machines
*
* @author Stephan Esch - Initial contribution
*/
public enum CoffeeMachineChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
BRAND_ID("brandId", "brandId", StringType.class, true),
COMPANY_ID("companyId", "companyId", StringType.class, true),
STATE("state", "state", StringType.class, false),
PROGRAMID("programId", "program", StringType.class, false),
PROGRAMTYPE("programType", "type", StringType.class, false),
PROGRAMPHASE("phase", "phase", StringType.class, false),
// lightingStatus signalFailure signalInfo
DOOR("signalDoor", "door", OpenClosedType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
if ("true".equals(s)) {
return getState("OPEN");
}
if ("false".equals(s)) {
return getState("CLOSED");
}
return UnDefType.UNDEF;
}
},
SWITCH(null, "switch", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(CoffeeMachineChannelSelector.class);
private final String mieleID;
private final String channelID;
private final Class<? extends Type> typeClass;
private final boolean isProperty;
CoffeeMachineChannelSelector(String propertyID, String channelID, Class<? extends Type> typeClass,
boolean isProperty) {
this.mieleID = propertyID;
this.channelID = channelID;
this.typeClass = typeClass;
this.isProperty = isProperty;
}
@Override
public String toString() {
return mieleID;
}
@Override
public String getMieleID() {
return mieleID;
}
@Override
public String getChannelID() {
return channelID;
}
@Override
public Class<? extends Type> getTypeClass() {
return typeClass;
}
@Override
public boolean isProperty() {
return isProperty;
}
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = getMieleEnum(s, dmd);
if (localizedValue == null) {
localizedValue = dmd.LocalizedValue;
}
if (localizedValue == null) {
localizedValue = s;
}
return getState(localizedValue);
} else {
return getState(s);
}
}
public State getState(String s) {
try {
Method valueOf = typeClass.getMethod("valueOf", String.class);
State state = (State) valueOf.invoke(typeClass, s);
if (state != null) {
return state;
}
} catch (Exception e) {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
}
public String getMieleEnum(String s, DeviceMetaData dmd) {
if (dmd.MieleEnum != null) {
for (Entry<String, JsonElement> enumEntry : dmd.MieleEnum.entrySet()) {
if (enumEntry.getValue().getAsString().trim().equals(s.trim())) {
return enumEntry.getKey();
}
}
}
return null;
}
}

View File

@@ -0,0 +1,81 @@
/**
* Copyright (c) 2010-2020 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.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement;
/**
* The {@link CoffeeMachineHandler} is responsible for handling commands,
* which are sent to one of the channels
*
* @author Stephan Esch - Initial contribution
* @author Martin Lepsy - fixed handling of empty JSON results
*/
public class CoffeeMachineHandler extends MieleApplianceHandler<CoffeeMachineChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(CoffeeMachineHandler.class);
public CoffeeMachineHandler(Thing thing) {
super(thing, CoffeeMachineChannelSelector.class, "CoffeeSystem");
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
String channelID = channelUID.getId();
String uid = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
CoffeeMachineChannelSelector selector = (CoffeeMachineChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
try {
if (selector != null) {
switch (selector) {
case SWITCH: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(uid, modelID, "switchOn");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(uid, modelID, "switchOff");
}
break;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
}
}
}
// process result
if (isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString());
}
}
}

View File

@@ -0,0 +1,82 @@
/**
* Copyright (c) 2010-2020 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.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement;
/**
* The {@link DishWasherHandler} is responsible for handling commands,
* which are sent to one of the channels
*
* @author Karel Goderis - Initial contribution
* @author Kai Kreuzer - fixed handling of REFRESH commands
* @author Martin Lepsy - fixed handling of empty JSON results
*/
public class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(DishWasherHandler.class);
public DishWasherHandler(Thing thing) {
super(thing, DishwasherChannelSelector.class, "Dishwasher");
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
String channelID = channelUID.getId();
String uid = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
DishwasherChannelSelector selector = (DishwasherChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
try {
if (selector != null) {
switch (selector) {
case SWITCH: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(uid, modelID, "start");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(uid, modelID, "stop");
}
break;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
}
}
}
// process result
if (isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString());
}
}
}

View File

@@ -0,0 +1,203 @@
/**
* Copyright (c) 2010-2020 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.handler;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map.Entry;
import java.util.TimeZone;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceMetaData;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
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;
import com.google.gson.JsonElement;
/**
* The {@link ApplianceChannelSelector} for dishwashers
*
* @author Karel Goderis - Initial contribution
* @author Kai Kreuzer - Changed START_TIME to DateTimeType
*/
public enum DishwasherChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
BRAND_ID("brandId", "brandId", StringType.class, true),
COMPANY_ID("companyId", "companyId", StringType.class, true),
STATE("state", "state", StringType.class, false),
PROGRAMID("programId", "program", StringType.class, false),
PROGRAMPHASE("phase", "phase", StringType.class, false),
START_TIME("startTime", "start", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
DURATION("duration", "duration", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
FINISH_TIME("finishTime", "finish", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
DOOR("signalDoor", "door", OpenClosedType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
if ("true".equals(s)) {
return getState("OPEN");
}
if ("false".equals(s)) {
return getState("CLOSED");
}
return UnDefType.UNDEF;
}
},
SWITCH(null, "switch", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(DishwasherChannelSelector.class);
private final String mieleID;
private final String channelID;
private final Class<? extends Type> typeClass;
private final boolean isProperty;
DishwasherChannelSelector(String propertyID, String channelID, Class<? extends Type> typeClass,
boolean isProperty) {
this.mieleID = propertyID;
this.channelID = channelID;
this.typeClass = typeClass;
this.isProperty = isProperty;
}
@Override
public String toString() {
return mieleID;
}
@Override
public String getMieleID() {
return mieleID;
}
@Override
public String getChannelID() {
return channelID;
}
@Override
public Class<? extends Type> getTypeClass() {
return typeClass;
}
@Override
public boolean isProperty() {
return isProperty;
}
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = getMieleEnum(s, dmd);
if (localizedValue == null) {
localizedValue = dmd.LocalizedValue;
}
if (localizedValue == null) {
localizedValue = s;
}
return getState(localizedValue);
} else {
return getState(s);
}
}
public State getState(String s) {
try {
Method valueOf = typeClass.getMethod("valueOf", String.class);
State state = (State) valueOf.invoke(typeClass, s);
if (state != null) {
return state;
}
} catch (Exception e) {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
}
public String getMieleEnum(String s, DeviceMetaData dmd) {
if (dmd.MieleEnum != null) {
for (Entry<String, JsonElement> enumEntry : dmd.MieleEnum.entrySet()) {
if (enumEntry.getValue().getAsString().trim().equals(s.trim())) {
return enumEntry.getKey();
}
}
}
return null;
}
}

View File

@@ -0,0 +1,154 @@
/**
* Copyright (c) 2010-2020 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.handler;
import java.lang.reflect.Method;
import java.util.Map.Entry;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceMetaData;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
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;
import com.google.gson.JsonElement;
/**
* The {@link ApplianceChannelSelector} for fridges
*
* @author Karel Goderis - Initial contribution
*/
public enum FridgeChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
BRAND_ID("brandId", "brandId", StringType.class, true),
COMPANY_ID("companyId", "companyId", StringType.class, true),
STATE("state", "state", StringType.class, false),
SUPERCOOL(null, "supercool", OnOffType.class, false),
FRIDGECURRENTTEMP("currentTemperature", "current", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
return getState(s);
}
},
FRIDGETARGETTEMP("targetTemperature", "target", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
return getState(s);
}
},
DOOR("signalDoor", "door", OpenClosedType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
if ("true".equals(s)) {
return getState("OPEN");
}
if ("false".equals(s)) {
return getState("CLOSED");
}
return UnDefType.UNDEF;
}
},
START(null, "start", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(FridgeChannelSelector.class);
private final String mieleID;
private final String channelID;
private final Class<? extends Type> typeClass;
private final boolean isProperty;
FridgeChannelSelector(String propertyID, String channelID, Class<? extends Type> typeClass, boolean isProperty) {
this.mieleID = propertyID;
this.channelID = channelID;
this.typeClass = typeClass;
this.isProperty = isProperty;
}
@Override
public String toString() {
return mieleID;
}
@Override
public String getMieleID() {
return mieleID;
}
@Override
public String getChannelID() {
return channelID;
}
@Override
public Class<? extends Type> getTypeClass() {
return typeClass;
}
@Override
public boolean isProperty() {
return isProperty;
}
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = getMieleEnum(s, dmd);
if (localizedValue == null) {
localizedValue = dmd.LocalizedValue;
}
if (localizedValue == null) {
localizedValue = s;
}
return getState(localizedValue);
} else {
return getState(s);
}
}
public State getState(String s) {
try {
Method valueOf = typeClass.getMethod("valueOf", String.class);
State state = (State) valueOf.invoke(typeClass, s);
if (state != null) {
return state;
}
} catch (Exception e) {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
}
public String getMieleEnum(String s, DeviceMetaData dmd) {
if (dmd.MieleEnum != null) {
for (Entry<String, JsonElement> enumEntry : dmd.MieleEnum.entrySet()) {
if (enumEntry.getValue().getAsString().trim().equals(s.trim())) {
return enumEntry.getKey();
}
}
}
return null;
}
}

View File

@@ -0,0 +1,171 @@
/**
* Copyright (c) 2010-2020 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.handler;
import java.lang.reflect.Method;
import java.util.Map.Entry;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceMetaData;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
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;
import com.google.gson.JsonElement;
/**
* The {@link ApplianceChannelSelector} for fridges with
* a freezer compartment
*
* @author Karel Goderis - Initial contribution
*/
public enum FridgeFreezerChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
BRAND_ID("brandId", "brandId", StringType.class, true),
COMPANY_ID("companyId", "companyId", StringType.class, true),
STATE("state", "state", StringType.class, false),
FREEZERSTATE("freezerState", "freezerstate", StringType.class, false),
FRIDGESTATE("fridgeState", "fridgestate", StringType.class, false),
SUPERCOOL(null, "supercool", OnOffType.class, false),
SUPERFREEZE(null, "superfreeze", OnOffType.class, false),
FREEZERCURRENTTEMP("freezerCurrentTemperature", "freezercurrent", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
return getState(s);
}
},
FREEZERTARGETTEMP("freezerTargetTemperature", "freezertarget", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
return getState(s);
}
},
FRIDGECURRENTTEMP("fridgeCurrentTemperature", "fridgecurrent", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
return getState(s);
}
},
FRIDGETARGETTEMP("fridgeTargetTemperature", "fridgetarget", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
return getState(s);
}
},
DOOR("signalDoor", "door", OpenClosedType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
if ("true".equals(s)) {
return getState("OPEN");
}
if ("false".equals(s)) {
return getState("CLOSED");
}
return UnDefType.UNDEF;
}
},
START(null, "start", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(FridgeFreezerChannelSelector.class);
private final String mieleID;
private final String channelID;
private final Class<? extends Type> typeClass;
private final boolean isProperty;
FridgeFreezerChannelSelector(String propertyID, String channelID, Class<? extends Type> typeClass,
boolean isProperty) {
this.mieleID = propertyID;
this.channelID = channelID;
this.typeClass = typeClass;
this.isProperty = isProperty;
}
@Override
public String toString() {
return mieleID;
}
@Override
public String getMieleID() {
return mieleID;
}
@Override
public String getChannelID() {
return channelID;
}
@Override
public Class<? extends Type> getTypeClass() {
return typeClass;
}
@Override
public boolean isProperty() {
return isProperty;
}
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = getMieleEnum(s, dmd);
if (localizedValue == null) {
localizedValue = dmd.LocalizedValue;
}
if (localizedValue == null) {
localizedValue = s;
}
return getState(localizedValue);
} else {
return getState(s);
}
}
public State getState(String s) {
try {
Method valueOf = typeClass.getMethod("valueOf", String.class);
State state = (State) valueOf.invoke(typeClass, s);
if (state != null) {
return state;
}
} catch (Exception e) {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
}
public String getMieleEnum(String s, DeviceMetaData dmd) {
if (dmd.MieleEnum != null) {
for (Entry<String, JsonElement> enumEntry : dmd.MieleEnum.entrySet()) {
if (enumEntry.getValue().getAsString().trim().equals(s.trim())) {
return enumEntry.getKey();
}
}
}
return null;
}
}

View File

@@ -0,0 +1,87 @@
/**
* Copyright (c) 2010-2020 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.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement;
/**
* The {@link FridgeFreezerHandler} is responsible for handling commands,
* which are sent to one of the channels
*
* @author Karel Goderis - Initial contribution
* @author Kai Kreuzer - fixed handling of REFRESH commands
* @author Martin Lepsy - fixed handling of empty JSON results
*/
public class FridgeFreezerHandler extends MieleApplianceHandler<FridgeFreezerChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(FridgeFreezerHandler.class);
public FridgeFreezerHandler(Thing thing) {
super(thing, FridgeFreezerChannelSelector.class, "FridgeFreezer");
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
String channelID = channelUID.getId();
String uid = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
FridgeFreezerChannelSelector selector = (FridgeFreezerChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
try {
if (selector != null) {
switch (selector) {
case SUPERCOOL: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(uid, modelID, "startSuperCooling");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(uid, modelID, "stopSuperCooling");
}
break;
}
case SUPERFREEZE: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(uid, modelID, "startSuperFreezing");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(uid, modelID, "stopSuperFreezing");
}
break;
}
default: {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
}
}
// process result
if (isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString());
}
}
}

View File

@@ -0,0 +1,87 @@
/**
* Copyright (c) 2010-2020 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.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement;
/**
* The {@link FridgeHandler} is responsible for handling commands,
* which are sent to one of the channels
*
* @author Karel Goderis - Initial contribution
* @author Martin Lepsy - fixed handling of empty JSON results
*/
public class FridgeHandler extends MieleApplianceHandler<FridgeChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(FridgeHandler.class);
public FridgeHandler(Thing thing) {
super(thing, FridgeChannelSelector.class, "Fridge");
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
String channelID = channelUID.getId();
String uid = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
FridgeChannelSelector selector = (FridgeChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
try {
if (selector != null) {
switch (selector) {
case SUPERCOOL: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(uid, modelID, "startSuperCooling");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(uid, modelID, "stopSuperCooling");
}
break;
}
case START: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(uid, modelID, "start");
}
break;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
}
}
}
// process result
if (isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString());
}
}
}

View File

@@ -0,0 +1,174 @@
/**
* Copyright (c) 2010-2020 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.handler;
import java.lang.reflect.Method;
import java.util.Map.Entry;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement;
/**
* The {@link ApplianceChannelSelector} for hobs
*
* @author Karel Goderis - Initial contribution
*/
public enum HobChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
BRAND_ID("brandId", "brandId", StringType.class, true),
COMPANY_ID("companyId", "companyId", StringType.class, true),
STATE("state", "state", StringType.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) {
// 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
return getState(s);
}
},
PLATE1_TIME("plate1RemainingTime", "plate1time", StringType.class, false),
PLATE2_POWER("plate2PowerStep", "plate2power", DecimalType.class, false),
PLATE2_HEAT("plate2RemainingHeat", "plate2heat", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
return getState(s);
}
},
PLATE2_TIME("plate2RemainingTime", "plate2time", StringType.class, false),
PLATE3_POWER("plate3PowerStep", "plate3power", DecimalType.class, false),
PLATE3_HEAT("plate3RemainingHeat", "plate3heat", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
return getState(s);
}
},
PLATE3_TIME("plate3RemainingTime", "plate3time", StringType.class, false),
PLATE4_POWER("plate4PowerStep", "plate4power", DecimalType.class, false),
PLATE4_HEAT("plate4RemainingHeat", "plate4heat", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
return getState(s);
}
},
PLATE4_TIME("plate4RemainingTime", "plate4time", StringType.class, false),
PLATE5_POWER("plate5PowerStep", "plate5power", DecimalType.class, false),
PLATE5_HEAT("plate5RemainingHeat", "plate5heat", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
return getState(s);
}
},
PLATE5_TIME("plate5RemainingTime", "plate5time", StringType.class, false),
PLATE6_POWER("plate6PowerStep", "plate6power", DecimalType.class, false),
PLATE6_HEAT("plate6RemainingHeat", "plate6heat", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
return getState(s);
}
},
PLATE6_TIME("plate6RemainingTime", "plate6time", StringType.class, false);
private final Logger logger = LoggerFactory.getLogger(HobChannelSelector.class);
private final String mieleID;
private final String channelID;
private final Class<? extends Type> typeClass;
private final boolean isProperty;
HobChannelSelector(String propertyID, String channelID, Class<? extends Type> typeClass, boolean isProperty) {
this.mieleID = propertyID;
this.channelID = channelID;
this.typeClass = typeClass;
this.isProperty = isProperty;
}
@Override
public String toString() {
return mieleID;
}
@Override
public String getMieleID() {
return mieleID;
}
@Override
public String getChannelID() {
return channelID;
}
@Override
public Class<? extends Type> getTypeClass() {
return typeClass;
}
@Override
public boolean isProperty() {
return isProperty;
}
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = getMieleEnum(s, dmd);
if (localizedValue == null) {
localizedValue = dmd.LocalizedValue;
}
if (localizedValue == null) {
localizedValue = s;
}
return getState(localizedValue);
} else {
return getState(s);
}
}
public State getState(String s) {
try {
Method valueOf = typeClass.getMethod("valueOf", String.class);
State state = (State) valueOf.invoke(typeClass, s);
if (state != null) {
return state;
}
} catch (Exception e) {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
}
public String getMieleEnum(String s, DeviceMetaData dmd) {
if (dmd.MieleEnum != null) {
for (Entry<String, JsonElement> enumEntry : dmd.MieleEnum.entrySet()) {
if (enumEntry.getValue().getAsString().trim().equals(s.trim())) {
return enumEntry.getKey();
}
}
}
return null;
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2020 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.handler;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
/**
* The {@link HobHandler} is responsible for handling commands,
* which are sent to one of the channels
*
* @author Karel Goderis - Initial contribution
* @author Kai Kreuzer - fixed handling of REFRESH commands
*/
public class HobHandler extends MieleApplianceHandler<HobChannelSelector> {
public HobHandler(Thing thing) {
super(thing, HobChannelSelector.class, "Hob");
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
}
}

View File

@@ -0,0 +1,141 @@
/**
* Copyright (c) 2010-2020 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.handler;
import java.lang.reflect.Method;
import java.util.Map.Entry;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceMetaData;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
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;
import com.google.gson.JsonElement;
/**
* The {@link ApplianceChannelSelector} for ventilation hoods
*
* @author Karel Goderis - Initial contribution
*/
public enum HoodChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
BRAND_ID("brandId", "brandId", StringType.class, true),
COMPANY_ID("companyId", "companyId", StringType.class, true),
STATE("state", "state", StringType.class, false),
VENTILATION("ventilationPower", "ventilation", DecimalType.class, false),
LIGHT("lightingStatus", "light", OnOffType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
if ("true".equals(s)) {
return getState("ON");
}
if ("false".equals(s)) {
return getState("OFF");
}
return UnDefType.UNDEF;
}
},
STOP(null, "stop", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(HoodChannelSelector.class);
private final String mieleID;
private final String channelID;
private final Class<? extends Type> typeClass;
private final boolean isProperty;
HoodChannelSelector(String propertyID, String channelID, Class<? extends Type> typeClass, boolean isProperty) {
this.mieleID = propertyID;
this.channelID = channelID;
this.typeClass = typeClass;
this.isProperty = isProperty;
}
@Override
public String toString() {
return mieleID;
}
@Override
public String getMieleID() {
return mieleID;
}
@Override
public String getChannelID() {
return channelID;
}
@Override
public Class<? extends Type> getTypeClass() {
return typeClass;
}
@Override
public boolean isProperty() {
return isProperty;
}
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = getMieleEnum(s, dmd);
if (localizedValue == null) {
localizedValue = dmd.LocalizedValue;
}
if (localizedValue == null) {
localizedValue = s;
}
return getState(localizedValue);
} else {
return getState(s);
}
}
public State getState(String s) {
try {
Method valueOf = typeClass.getMethod("valueOf", String.class);
State state = (State) valueOf.invoke(typeClass, s);
if (state != null) {
return state;
}
} catch (Exception e) {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
}
public String getMieleEnum(String s, DeviceMetaData dmd) {
if (dmd.MieleEnum != null) {
for (Entry<String, JsonElement> enumEntry : dmd.MieleEnum.entrySet()) {
if (enumEntry.getValue().getAsString().trim().equals(s.trim())) {
return enumEntry.getKey();
}
}
}
return null;
}
}

View File

@@ -0,0 +1,85 @@
/**
* Copyright (c) 2010-2020 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.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement;
/**
* The {@link HoodHandler} is responsible for handling commands,
* which are sent to one of the channels
*
* @author Karel Goderis - Initial contribution
* @author Kai Kreuzer - fixed handling of REFRESH commands
* @author Martin Lepsy - fixed handling of empty JSON results
*/
public class HoodHandler extends MieleApplianceHandler<HoodChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(HoodHandler.class);
public HoodHandler(Thing thing) {
super(thing, HoodChannelSelector.class, "Hood");
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
String channelID = channelUID.getId();
String uid = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
HoodChannelSelector selector = (HoodChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
try {
if (selector != null) {
switch (selector) {
case LIGHT: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(uid, modelID, "startLighting");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(uid, modelID, "stopLighting");
}
break;
}
case STOP: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(uid, modelID, "stop");
}
break;
}
default: {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
}
}
// process result
if (isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString());
}
}
}

View File

@@ -0,0 +1,280 @@
/**
* Copyright (c) 2010-2020 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.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceClassObject;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceMetaData;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceOperation;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceProperty;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.HomeDevice;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
/**
* The {@link MieleApplianceHandler} is an abstract class
* responsible for handling commands, which are sent to one
* of the channels of the appliance that understands/"talks"
* the {@link ApplianceChannelSelector} datapoints
*
* @author Karel Goderis - Initial contribution
* @author Martin Lepsy - Added check for JsonNull result
*/
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());
protected Gson gson = new Gson();
protected String uid;
protected MieleBridgeHandler bridgeHandler;
private Class<E> selectorType;
protected String modelID;
protected Map<String, String> metaDataCache = new HashMap<>();
public MieleApplianceHandler(Thing thing, Class<E> selectorType, String modelID) {
super(thing);
this.selectorType = selectorType;
this.modelID = modelID;
}
public ApplianceChannelSelector getValueSelectorFromChannelID(String valueSelectorText)
throws IllegalArgumentException {
for (ApplianceChannelSelector c : selectorType.getEnumConstants()) {
if (c.getChannelID() != null && c.getChannelID().equals(valueSelectorText)) {
return c;
}
}
throw new IllegalArgumentException("Not valid value selector");
}
public ApplianceChannelSelector getValueSelectorFromMieleID(String valueSelectorText)
throws IllegalArgumentException {
for (ApplianceChannelSelector c : selectorType.getEnumConstants()) {
if (c.getMieleID() != null && c.getMieleID().equals(valueSelectorText)) {
return c;
}
}
throw new IllegalArgumentException("Not valid value selector");
}
@Override
public void initialize() {
logger.debug("Initializing Miele appliance handler.");
final String uid = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (uid != null) {
this.uid = uid;
if (getMieleBridgeHandler() != null) {
ThingStatusInfo statusInfo = getBridge().getStatusInfo();
updateStatus(statusInfo.getStatus(), statusInfo.getStatusDetail(), statusInfo.getDescription());
}
}
}
public void onBridgeConnectionResumed() {
if (getMieleBridgeHandler() != null) {
ThingStatusInfo statusInfo = getBridge().getStatusInfo();
updateStatus(statusInfo.getStatus(), statusInfo.getStatusDetail(), statusInfo.getDescription());
}
}
@Override
public void dispose() {
logger.debug("Handler disposes. Unregistering listener.");
if (uid != null) {
MieleBridgeHandler bridgeHandler = getMieleBridgeHandler();
if (bridgeHandler != null) {
getMieleBridgeHandler().unregisterApplianceStatusListener(this);
}
uid = null;
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// Here we could handle commands that are common to all Miele Appliances, but so far I don't know of any
if (command instanceof RefreshType) {
// Placeholder for future refinement
return;
}
}
@Override
public void onApplianceStateChanged(String UID, DeviceClassObject dco) {
String myUID = ((String) getThing().getProperties().get(PROTOCOL_PROPERTY_NAME))
+ (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
String modelID = StringUtils.right(dco.DeviceClass,
dco.DeviceClass.length() - new String("com.miele.xgw3000.gateway.hdm.deviceclasses.Miele").length());
if (myUID.equals(UID)) {
if (modelID.equals(this.modelID)) {
for (JsonElement prop : dco.Properties.getAsJsonArray()) {
try {
DeviceProperty dp = gson.fromJson(prop, DeviceProperty.class);
dp.Value = StringUtils.trim(dp.Value);
dp.Value = StringUtils.strip(dp.Value);
onAppliancePropertyChanged(UID, dp);
} catch (Exception p) {
// Ignore - this is due to an unrecognized and not yet reverse-engineered array property
}
}
for (JsonElement operation : dco.Operations.getAsJsonArray()) {
try {
DeviceOperation devop = gson.fromJson(operation, DeviceOperation.class);
DeviceMetaData pmd = gson.fromJson(devop.Metadata, DeviceMetaData.class);
} catch (Exception p) {
// Ignore - this is due to an unrecognized and not yet reverse-engineered array property
}
}
}
}
}
@Override
public void onAppliancePropertyChanged(String UID, DeviceProperty dp) {
String myUID = ((String) getThing().getProperties().get(PROTOCOL_PROPERTY_NAME))
+ (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (myUID.equals(UID)) {
try {
DeviceMetaData dmd = null;
if (dp.Metadata == null) {
String metadata = metaDataCache.get(new StringBuilder().append(dp.Name).toString().trim());
if (metadata != null) {
JsonParser parser = new JsonParser();
JsonObject jsonMetaData = (JsonObject) parser.parse(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
dmd.LocalizedID = null;
dmd.LocalizedValue = null;
dmd.Filter = null;
dmd.description = null;
}
}
if (dp.Metadata != null) {
String metadata = StringUtils.replace(dp.Metadata.toString(), "enum", "MieleEnum");
JsonParser parser = new JsonParser();
JsonObject jsonMetaData = (JsonObject) parser.parse(metadata);
dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class);
metaDataCache.put(new StringBuilder().append(dp.Name).toString().trim(), metadata);
}
ApplianceChannelSelector selector = null;
try {
selector = getValueSelectorFromMieleID(dp.Name);
} catch (Exception h) {
logger.trace("{} is not a valid channel for a {}", dp.Name, modelID);
}
String dpValue = StringUtils.trim(StringUtils.strip(dp.Value));
if (selector != null) {
if (!selector.isProperty()) {
ChannelUID theChannelUID = new ChannelUID(getThing().getUID(), selector.getChannelID());
if (dp.Value != null) {
logger.trace("Update state of {} with getState '{}'", theChannelUID,
selector.getState(dpValue, dmd));
updateState(theChannelUID, selector.getState(dpValue, dmd));
} else {
updateState(theChannelUID, UnDefType.UNDEF);
}
} else if (dpValue != null) {
logger.debug("Updating the property '{}' of '{}' to '{}'", selector.getChannelID(),
getThing().getUID(), selector.getState(dpValue, dmd).toString());
Map<String, String> properties = editProperties();
properties.put(selector.getChannelID(), selector.getState(dpValue, dmd).toString());
updateProperties(properties);
}
}
} catch (IllegalArgumentException e) {
logger.error("An exception occurred while processing a changed device property :'{}'", e.getMessage());
}
}
}
@Override
public void onApplianceRemoved(HomeDevice appliance) {
if (uid != null) {
if (uid.equals(appliance.UID)) {
updateStatus(ThingStatus.OFFLINE);
}
}
}
@Override
public void onApplianceAdded(HomeDevice appliance) {
if (uid != null) {
if (uid.equals(appliance.UID)) {
updateStatus(ThingStatus.ONLINE);
}
}
}
private synchronized MieleBridgeHandler getMieleBridgeHandler() {
if (this.bridgeHandler == null) {
Bridge bridge = getBridge();
if (bridge == null) {
return null;
}
ThingHandler handler = bridge.getHandler();
if (handler instanceof MieleBridgeHandler) {
this.bridgeHandler = (MieleBridgeHandler) handler;
this.bridgeHandler.registerApplianceStatusListener(this);
} else {
return null;
}
}
return this.bridgeHandler;
}
protected boolean isResultProcessable(JsonElement result) {
return result != null && !result.isJsonNull();
}
}

View File

@@ -0,0 +1,637 @@
/**
* Copyright (c) 2010-2020 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.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.DatagramPacket;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.MulticastSocket;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import org.apache.commons.lang.StringUtils;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
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;
/**
* The {@link MieleBridgeHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Karel Goderis - Initial contribution
* @author Kai Kreuzer - Fixed lifecycle issues
* @author Martin Lepsy - Added protocol information to support WiFi devices & some refactoring for HomeDevice
*/
public class MieleBridgeHandler extends BaseBridgeHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(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])$");
protected static final int POLLING_PERIOD = 15; // in seconds
protected static final int JSON_RPC_PORT = 2810;
protected static final String JSON_RPC_MULTICAST_IP1 = "239.255.68.139";
protected static final String JSON_RPC_MULTICAST_IP2 = "224.255.68.139";
private boolean lastBridgeConnectionState = false;
private boolean currentBridgeConnectionState = false;
protected Random rand = new Random();
protected Gson gson = new Gson();
private final Logger logger = LoggerFactory.getLogger(MieleBridgeHandler.class);
protected List<ApplianceStatusListener> applianceStatusListeners = new CopyOnWriteArrayList<>();
protected ScheduledFuture<?> pollingJob;
protected ScheduledFuture<?> eventListenerJob;
protected List<HomeDevice> previousHomeDevices = new CopyOnWriteArrayList<>();
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 PROTOCOL_LAN = "LAN";
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 String getId() {
return getApplianceId().replaceAll("[^a-zA-Z0-9_]", "_");
}
public String getProtocol() {
return ProtocolAdapterName.equals(PROTOCOL_LAN) ? HDM_LAN : HDM_ZIGBEE;
}
public String getApplianceId() {
return ProtocolAdapterName.equals(PROTOCOL_LAN) ? StringUtils.right(UID, UID.length() - HDM_LAN.length())
: StringUtils.right(UID, UID.length() - HDM_ZIGBEE.length());
}
}
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() {
}
}
public class DeviceMetaData {
public String Filter;
public String description;
public String LocalizedID;
public String LocalizedValue;
public JsonObject MieleEnum;
public String access;
}
public MieleBridgeHandler(Bridge bridge) {
super(bridge);
}
@Override
public void initialize() {
logger.debug("Initializing the Miele bridge handler.");
if (getConfig().get(HOST) != null && getConfig().get(INTERFACE) != null) {
if (IP_PATTERN.matcher((String) getConfig().get(HOST)).matches()
&& IP_PATTERN.matcher((String) getConfig().get(INTERFACE)).matches()) {
try {
url = new URL("http://" + (String) getConfig().get(HOST) + "/remote/json-rpc");
} catch (MalformedURLException e) {
logger.debug("An exception occurred while defining an URL :'{}'", e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, e.getMessage());
return;
}
// for future usage - no headers to be set for now
headers = new HashMap<>();
onUpdate();
updateStatus(ThingStatus.UNKNOWN);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"Invalid IP address for the Miele@Home gateway or multicast interface:" + getConfig().get(HOST)
+ "/" + getConfig().get(INTERFACE));
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"Cannot connect to the Miele gateway. host IP address or multicast interface are not set.");
}
}
private Runnable pollingRunnable = new Runnable() {
@Override
public void run() {
if (IP_PATTERN.matcher((String) getConfig().get(HOST)).matches()) {
try {
if (isReachable((String) getConfig().get(HOST))) {
currentBridgeConnectionState = true;
} else {
currentBridgeConnectionState = false;
lastBridgeConnectionState = false;
onConnectionLost();
}
if (!lastBridgeConnectionState && currentBridgeConnectionState) {
logger.debug("Connection to Miele Gateway {} established.", getConfig().get(HOST));
lastBridgeConnectionState = true;
onConnectionResumed();
}
if (currentBridgeConnectionState) {
if (getThing().getStatus() == ThingStatus.ONLINE) {
List<HomeDevice> currentHomeDevices = getHomeDevices();
for (HomeDevice hd : currentHomeDevices) {
boolean isExisting = false;
for (HomeDevice phd : previousHomeDevices) {
if (phd.UID.equals(hd.UID)) {
isExisting = true;
break;
}
}
if (!isExisting) {
logger.debug("A new appliance with ID '{}' has been added", hd.UID);
for (ApplianceStatusListener listener : applianceStatusListeners) {
listener.onApplianceAdded(hd);
}
}
}
for (HomeDevice hd : previousHomeDevices) {
boolean isCurrent = false;
for (HomeDevice chd : currentHomeDevices) {
if (chd.UID.equals(hd.UID)) {
isCurrent = true;
break;
}
}
if (!isCurrent) {
logger.debug("The appliance with ID '{}' has been removed", hd);
for (ApplianceStatusListener listener : applianceStatusListeners) {
listener.onApplianceRemoved(hd);
}
}
}
previousHomeDevices = currentHomeDevices;
for (Thing appliance : getThing().getThings()) {
if (appliance.getStatus() == ThingStatus.ONLINE) {
String UID = appliance.getProperties().get(PROTOCOL_PROPERTY_NAME)
+ (String) appliance.getConfiguration().getProperties().get(APPLIANCE_ID);
Object[] args = new Object[2];
args[0] = UID;
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 (ApplianceStatusListener listener : applianceStatusListeners) {
listener.onApplianceStateChanged(UID, dco);
}
} catch (Exception e) {
logger.debug("An exception occurred while quering an appliance : '{}'",
e.getMessage());
}
}
}
}
}
}
}
} catch (Exception e) {
logger.debug("An exception occurred while polling an appliance :'{}'", e.getMessage());
}
} else {
logger.debug("Invalid IP address for the Miele@Home gateway : '{}'", getConfig().get(HOST));
}
}
private boolean isReachable(String ipAddress) {
try {
// note that InetAddress.isReachable is unreliable, see
// http://stackoverflow.com/questions/9922543/why-does-inetaddress-isreachable-return-false-when-i-can-ping-the-ip-address
// 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) {
return false;
}
logger.debug("{} is reachable", ipAddress);
return true;
}
};
public List<HomeDevice> getHomeDevices() {
List<HomeDevice> devices = new ArrayList<>();
if (getThing().getStatus() == ThingStatus.ONLINE) {
try {
String[] args = new String[1];
args[0] = "(type=SuperVision)";
JsonElement result = invokeRPC("HDAccess/getHomeDevices", args);
for (JsonElement obj : result.getAsJsonArray()) {
HomeDevice hd = gson.fromJson(obj, HomeDevice.class);
devices.add(hd);
}
} catch (Exception e) {
logger.debug("An exception occurred while getting the home devices :'{}'", e.getMessage());
}
}
return devices;
}
private Runnable eventListenerRunnable = () -> {
if (IP_PATTERN.matcher((String) getConfig().get(INTERFACE)).matches()) {
while (true) {
// Get the address that we are going to connect to.
InetAddress address1 = null;
InetAddress address2 = null;
try {
address1 = InetAddress.getByName(JSON_RPC_MULTICAST_IP1);
address2 = InetAddress.getByName(JSON_RPC_MULTICAST_IP2);
} catch (UnknownHostException e) {
logger.debug("An exception occurred while setting up the multicast receiver : '{}'",
e.getMessage());
}
byte[] buf = new byte[256];
MulticastSocket clientSocket = null;
while (true) {
try {
clientSocket = new MulticastSocket(JSON_RPC_PORT);
clientSocket.setSoTimeout(100);
clientSocket.setInterface(InetAddress.getByName((String) getConfig().get(INTERFACE)));
clientSocket.joinGroup(address1);
clientSocket.joinGroup(address2);
while (true) {
try {
buf = new byte[256];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
clientSocket.receive(packet);
String event = new String(packet.getData());
logger.debug("Received a multicast event '{}' from '{}:{}'", event, packet.getAddress(),
packet.getPort());
DeviceProperty dp = new DeviceProperty();
String uid = null;
String[] parts = StringUtils.split(event, "&");
for (String p : parts) {
String[] subparts = StringUtils.split(p, "=");
switch (subparts[0]) {
case "property": {
dp.Name = subparts[1];
break;
}
case "value": {
dp.Value = subparts[1];
break;
}
case "id": {
uid = subparts[1];
break;
}
}
}
for (ApplianceStatusListener listener : applianceStatusListeners) {
listener.onAppliancePropertyChanged(uid, dp);
}
} catch (SocketTimeoutException e) {
Thread.sleep(500);
}
}
} catch (Exception ex) {
logger.debug("An exception occurred while receiving multicast packets : '{}'", ex.getMessage());
}
// restart the cycle with a clean slate
try {
if (clientSocket != null) {
clientSocket.leaveGroup(address1);
clientSocket.leaveGroup(address2);
}
} catch (IOException e) {
logger.debug("An exception occurred while leaving multicast group : '{}'", e.getMessage());
}
if (clientSocket != null) {
clientSocket.close();
}
}
}
} else {
logger.debug("Invalid IP address for the multicast interface : '{}'", getConfig().get(INTERFACE));
}
};
public JsonElement invokeOperation(String UID, String modelID, String methodName) {
return invokeOperation(UID, modelID, methodName, HDM_ZIGBEE);
}
public JsonElement invokeOperation(String UID, String modelID, String methodName, String protocol) {
if (getThing().getStatus() == ThingStatus.ONLINE) {
Object[] args = new Object[4];
args[0] = protocol + UID;
args[1] = "com.miele.xgw3000.gateway.hdm.deviceclasses.Miele" + modelID;
args[2] = methodName;
args[3] = null;
return invokeRPC("HDAccess/invokeDCOOperation", args);
} else {
logger.debug("The Bridge is offline - operations can not be invoked.");
return null;
}
}
protected JsonElement invokeRPC(String methodName, Object[] args) {
int id = rand.nextInt(Integer.MAX_VALUE);
JsonObject req = new JsonObject();
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));
}
}
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());
}
if (responseData != null) {
logger.debug("The request '{}' yields '{}'", requestData, responseData);
JsonParser parser = new JsonParser();
JsonObject resp = (JsonObject) parser.parse(new StringReader(responseData));
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());
}
}
}
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());
}
}
connection.addRequestProperty("Accept-Encoding", "gzip");
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.connect();
OutputStream out = null;
try {
out = connection.getOutputStream();
out.write(data.getBytes());
out.flush();
int statusCode = connection.getResponseCode();
if (statusCode != HttpURLConnection.HTTP_OK) {
logger.debug("An unexpected status code was returned: '{}'", statusCode);
}
} finally {
if (out != null) {
out.close();
}
}
String responseEncoding = connection.getHeaderField("Content-Encoding");
responseEncoding = (responseEncoding == null ? "" : responseEncoding.trim());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
InputStream in = connection.getInputStream();
try {
in = connection.getInputStream();
if ("gzip".equalsIgnoreCase(responseEncoding)) {
in = new GZIPInputStream(in);
}
in = new BufferedInputStream(in);
byte[] buff = new byte[1024];
int n;
while ((n = in.read(buff)) > 0) {
bos.write(buff, 0, n);
}
bos.flush();
bos.close();
} finally {
if (in != null) {
in.close();
}
}
return bos.toString();
}
private synchronized void onUpdate() {
logger.debug("Scheduling the Miele polling job");
if (pollingJob == null || pollingJob.isCancelled()) {
logger.trace("Scheduling the Miele polling job period is {}", POLLING_PERIOD);
pollingJob = scheduler.scheduleWithFixedDelay(pollingRunnable, 0, POLLING_PERIOD, TimeUnit.SECONDS);
logger.trace("Scheduling the Miele polling job Job is done ?{}", pollingJob.isDone());
}
logger.debug("Scheduling the Miele event listener job");
if (eventListenerJob == null || eventListenerJob.isCancelled()) {
eventListenerJob = scheduler.schedule(eventListenerRunnable, 0, TimeUnit.SECONDS);
}
}
/**
* This method is called whenever the connection to the given {@link MieleBridge} is lost.
*
*/
public void onConnectionLost() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR);
}
/**
* This method is called whenever the connection to the given {@link MieleBridge} is resumed.
*
* @param bridge the hue bridge the connection is resumed to
*/
public void onConnectionResumed() {
updateStatus(ThingStatus.ONLINE);
for (Thing thing : getThing().getThings()) {
MieleApplianceHandler<?> handler = (MieleApplianceHandler<?>) thing.getHandler();
if (handler != null) {
handler.onBridgeConnectionResumed();
}
}
}
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();
for (HomeDevice hd : getHomeDevices()) {
applianceStatusListener.onApplianceAdded(hd);
}
}
return result;
}
public boolean unregisterApplianceStatusListener(ApplianceStatusListener applianceStatusListener) {
boolean result = applianceStatusListeners.remove(applianceStatusListener);
if (result && isInitialized()) {
onUpdate();
}
return result;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// Nothing to do here - the XGW bridge does not handle commands, for now
if (command instanceof RefreshType) {
// Placeholder for future refinement
return;
}
}
@Override
public void dispose() {
super.dispose();
if (pollingJob != null) {
pollingJob.cancel(true);
pollingJob = null;
}
}
}

View File

@@ -0,0 +1,229 @@
/**
* Copyright (c) 2010-2020 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.handler;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map.Entry;
import java.util.TimeZone;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceMetaData;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
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;
import com.google.gson.JsonElement;
/**
* The {@link ApplianceChannelSelector} for ovens
*
* @author Karel Goderis - Initial contribution
* @author Kai Kreuzer - Changed START_TIME to DateTimeType
*/
public enum OvenChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
BRAND_ID("brandId", "brandId", StringType.class, true),
COMPANY_ID("companyId", "companyId", StringType.class, true),
STATE("state", "state", StringType.class, false),
PROGRAMID("programId", "program", StringType.class, false),
PROGRAMPHASE("phase", "phase", StringType.class, false),
START_TIME("startTime", "start", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
DURATION("duration", "duration", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
FINISH_TIME("finishTime", "finish", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
TARGET_TEMP("targetTemperature", "target", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
return getState(s);
}
},
MEASURED_TEMP("measuredTemperature", "measured", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
return getState(s);
}
},
DEVICE_TEMP_ONE("deviceTemperature1", "temp1", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
return getState(s);
}
},
DEVICE_TEMP_TWO("deviceTemperature2", "temp2", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
return getState(s);
}
},
DOOR("signalDoor", "door", OpenClosedType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
if ("true".equals(s)) {
return getState("OPEN");
}
if ("false".equals(s)) {
return getState("CLOSED");
}
return UnDefType.UNDEF;
}
},
STOP(null, "stop", OnOffType.class, false),
SWITCH(null, "switch", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(OvenChannelSelector.class);
private final String mieleID;
private final String channelID;
private final Class<? extends Type> typeClass;
private final boolean isProperty;
OvenChannelSelector(String propertyID, String channelID, Class<? extends Type> typeClass, boolean isProperty) {
this.mieleID = propertyID;
this.channelID = channelID;
this.typeClass = typeClass;
this.isProperty = isProperty;
}
@Override
public String toString() {
return mieleID;
}
@Override
public String getMieleID() {
return mieleID;
}
@Override
public String getChannelID() {
return channelID;
}
@Override
public Class<? extends Type> getTypeClass() {
return typeClass;
}
@Override
public boolean isProperty() {
return isProperty;
}
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = getMieleEnum(s, dmd);
if (localizedValue == null) {
localizedValue = dmd.LocalizedValue;
}
if (localizedValue == null) {
localizedValue = s;
}
return getState(localizedValue);
} else {
return getState(s);
}
}
public State getState(String s) {
try {
Method valueOf = typeClass.getMethod("valueOf", String.class);
State state = (State) valueOf.invoke(typeClass, s);
if (state != null) {
return state;
}
} catch (Exception e) {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
}
public String getMieleEnum(String s, DeviceMetaData dmd) {
if (dmd.MieleEnum != null) {
for (Entry<String, JsonElement> enumEntry : dmd.MieleEnum.entrySet()) {
if (enumEntry.getValue().getAsString().trim().equals(s.trim())) {
return enumEntry.getKey();
}
}
}
return null;
}
}

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2010-2020 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.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
import static org.openhab.binding.miele.internal.MieleBindingConstants.PROTOCOL_PROPERTY_NAME;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement;
/**
* The {@link OvenHandler} is responsible for handling commands,
* which are sent to one of the channels
*
* @author Karel Goderis - Initial contribution
* @author Kai Kreuzer - fixed handling of REFRESH commands
* @author Martin Lepsy - fixed handling of empty JSON results
*/
public class OvenHandler extends MieleApplianceHandler<OvenChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(OvenHandler.class);
public OvenHandler(Thing thing) {
super(thing, OvenChannelSelector.class, "Oven");
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
String channelID = channelUID.getId();
String uid = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
String protocol = (String) getThing().getProperties().get(PROTOCOL_PROPERTY_NAME);
OvenChannelSelector selector = (OvenChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
try {
if (selector != null) {
switch (selector) {
case SWITCH: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(uid, modelID, "switchOn", protocol);
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(uid, modelID, "switchOff", protocol);
}
break;
}
case STOP: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(uid, modelID, "stop", protocol);
}
break;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
}
}
}
// process result
if (isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString());
}
}
}

View File

@@ -0,0 +1,212 @@
/**
* Copyright (c) 2010-2020 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.handler;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map.Entry;
import java.util.TimeZone;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceMetaData;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
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;
import com.google.gson.JsonElement;
/**
* The {@link ApplianceChannelSelector} for tumble dryers
*
* @author Karel Goderis - Initial contribution
* @author Kai Kreuzer - Changed START_TIME to DateTimeType
*/
public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
BRAND_ID("brandId", "brandId", StringType.class, true),
COMPANY_ID("companyId", "companyId", StringType.class, true),
STATE("state", "state", StringType.class, false),
PROGRAMID("programId", "program", StringType.class, false),
PROGRAMTYPE("programType", "type", StringType.class, false),
PROGRAMPHASE("phase", "phase", StringType.class, false),
START_TIME("startTime", "start", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
DURATION("duration", "duration", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
FINISH_TIME("finishTime", "finish", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
DRYING_STEP("dryingStep", "step", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
return getState(s);
}
},
DOOR("signalDoor", "door", OpenClosedType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
if ("true".equals(s)) {
return getState("OPEN");
}
if ("false".equals(s)) {
return getState("CLOSED");
}
return UnDefType.UNDEF;
}
},
SWITCH(null, "switch", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(TumbleDryerChannelSelector.class);
private final String mieleID;
private final String channelID;
private final Class<? extends Type> typeClass;
private final boolean isProperty;
TumbleDryerChannelSelector(String propertyID, String channelID, Class<? extends Type> typeClass,
boolean isProperty) {
this.mieleID = propertyID;
this.channelID = channelID;
this.typeClass = typeClass;
this.isProperty = isProperty;
}
@Override
public String toString() {
return mieleID;
}
@Override
public String getMieleID() {
return mieleID;
}
@Override
public String getChannelID() {
return channelID;
}
@Override
public Class<? extends Type> getTypeClass() {
return typeClass;
}
@Override
public boolean isProperty() {
return isProperty;
}
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = getMieleEnum(s, dmd);
if (localizedValue == null) {
localizedValue = dmd.LocalizedValue;
}
if (localizedValue == null) {
localizedValue = s;
}
return getState(localizedValue);
} else {
return getState(s);
}
}
public State getState(String s) {
try {
Method valueOf = typeClass.getMethod("valueOf", String.class);
State state = (State) valueOf.invoke(typeClass, s);
if (state != null) {
return state;
}
} catch (Exception e) {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
}
public String getMieleEnum(String s, DeviceMetaData dmd) {
if (dmd.MieleEnum != null) {
for (Entry<String, JsonElement> enumEntry : dmd.MieleEnum.entrySet()) {
if (enumEntry.getValue().getAsString().trim().equals(s.trim())) {
return enumEntry.getKey();
}
}
}
return null;
}
}

View File

@@ -0,0 +1,82 @@
/**
* Copyright (c) 2010-2020 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.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement;
/**
* The {@link TumbleDryerHandler} is responsible for handling commands,
* which are sent to one of the channels
*
* @author Karel Goderis - Initial contribution
* @author Kai Kreuzer - fixed handling of REFRESH commands
* @author Martin Lepsy - fixed handling of empty JSON results
*/
public class TumbleDryerHandler extends MieleApplianceHandler<TumbleDryerChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(TumbleDryerHandler.class);
public TumbleDryerHandler(Thing thing) {
super(thing, TumbleDryerChannelSelector.class, "TumbleDryer");
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
String channelID = channelUID.getId();
String uid = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
TumbleDryerChannelSelector selector = (TumbleDryerChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
try {
if (selector != null) {
switch (selector) {
case SWITCH: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(uid, modelID, "start");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(uid, modelID, "stop");
}
break;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
}
}
}
// process result
if (isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString());
}
}
}

View File

@@ -0,0 +1,225 @@
/**
* Copyright (c) 2010-2020 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.handler;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map.Entry;
import java.util.TimeZone;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceMetaData;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
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;
import com.google.gson.JsonElement;
/**
* The {@link ApplianceChannelSelector} for washing machines
*
* @author Karel Goderis - Initial contribution
* @author Kai Kreuzer - Changed START_TIME to DateTimeType
*/
public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
BRAND_ID("brandId", "brandId", StringType.class, true),
COMPANY_ID("companyId", "companyId", StringType.class, true),
STATE("state", "state", StringType.class, false),
PROGRAMID("programId", "program", StringType.class, false),
PROGRAMTYPE("programType", "type", StringType.class, false),
PROGRAMPHASE("phase", "phase", StringType.class, false),
START_TIME("startTime", "start", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
DURATION("duration", "duration", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(StringUtils.trim(s)) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
FINISH_TIME("finishTime", "finish", DateTimeType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
TARGET_TEMP("targetTemperature", "target", DecimalType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
return getState(s);
}
},
SPINNING_SPEED("spinningSpeed", "spinningspeed", StringType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
if ("0".equals(s)) {
return getState("Without spinning");
}
if ("256".equals(s)) {
return getState("Rinsing");
}
return getState(Integer.toString((Integer.valueOf(s) * 10)));
}
},
DOOR("signalDoor", "door", OpenClosedType.class, false) {
@Override
public State getState(String s, DeviceMetaData dmd) {
if ("true".equals(s)) {
return getState("OPEN");
}
if ("false".equals(s)) {
return getState("CLOSED");
}
return UnDefType.UNDEF;
}
},
SWITCH(null, "switch", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(WashingMachineChannelSelector.class);
private final String mieleID;
private final String channelID;
private final Class<? extends Type> typeClass;
private final boolean isProperty;
WashingMachineChannelSelector(String propertyID, String channelID, Class<? extends Type> typeClass,
boolean isProperty) {
this.mieleID = propertyID;
this.channelID = channelID;
this.typeClass = typeClass;
this.isProperty = isProperty;
}
@Override
public String toString() {
return mieleID;
}
@Override
public String getMieleID() {
return mieleID;
}
@Override
public String getChannelID() {
return channelID;
}
@Override
public Class<? extends Type> getTypeClass() {
return typeClass;
}
@Override
public boolean isProperty() {
return isProperty;
}
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
String localizedValue = getMieleEnum(s, dmd);
if (localizedValue == null) {
localizedValue = dmd.LocalizedValue;
}
if (localizedValue == null) {
localizedValue = s;
}
return getState(localizedValue);
} else {
return getState(s);
}
}
public State getState(String s) {
try {
Method valueOf = typeClass.getMethod("valueOf", String.class);
State state = (State) valueOf.invoke(typeClass, s);
if (state != null) {
return state;
}
} catch (Exception e) {
logger.error("An exception occurred while converting '{}' into a State", s);
}
return null;
}
public String getMieleEnum(String s, DeviceMetaData dmd) {
if (dmd.MieleEnum != null) {
for (Entry<String, JsonElement> enumEntry : dmd.MieleEnum.entrySet()) {
if (enumEntry.getValue().getAsString().trim().equals(s.trim())) {
return enumEntry.getKey();
}
}
}
return null;
}
}

View File

@@ -0,0 +1,83 @@
/**
* Copyright (c) 2010-2020 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.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement;
/**
* The {@link WashingMachineHandler} is responsible for handling commands,
* which are sent to one of the channels
*
* @author Karel Goderis - Initial contribution
* @author Kai Kreuzer - fixed handling of REFRESH commands
* @author Martin Lepsy - fixed handling of empty JSON results
*/
public class WashingMachineHandler extends MieleApplianceHandler<WashingMachineChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(WashingMachineHandler.class);
public WashingMachineHandler(Thing thing) {
super(thing, WashingMachineChannelSelector.class, "WashingMachine");
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
String channelID = channelUID.getId();
String uid = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
WashingMachineChannelSelector selector = (WashingMachineChannelSelector) getValueSelectorFromChannelID(
channelID);
JsonElement result = null;
try {
if (selector != null) {
switch (selector) {
case SWITCH: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(uid, modelID, "start");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(uid, modelID, "stop");
}
break;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
}
}
}
}
// process result
if (isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString());
}
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="miele" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Miele Binding</name>
<description>This is the binding for Miele@home appliances</description>
<author>Karel Goderis</author>
</binding:binding>

View File

@@ -0,0 +1,215 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="miele"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-type id="state" advanced="false">
<item-type>String</item-type>
<label>Status</label>
<description>Current status of the appliance</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="program" advanced="false">
<item-type>String</item-type>
<label>Program</label>
<description>Current program or function running on the appliance</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="type" advanced="true">
<item-type>String</item-type>
<label>Program Type</label>
<description>Type of the program running on the appliance</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="phase" advanced="false">
<item-type>String</item-type>
<label>Phase</label>
<description>Current phase of the program running on the appliance</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="start" advanced="true">
<item-type>DateTime</item-type>
<label>Start Time</label>
<description>Programmed start time of the program</description>
<state readOnly="true" pattern="%1$tH:%1$tM"></state>
</channel-type>
<channel-type id="duration" advanced="true">
<item-type>DateTime</item-type>
<label>Duration</label>
<description>Duration of the program running on the appliance</description>
<state readOnly="true" pattern="%1$tH:%1$tM"></state>
</channel-type>
<channel-type id="elapsed" advanced="true">
<item-type>DateTime</item-type>
<label>Elapsed Time</label>
<description>Time elapsed in the program running on the appliance</description>
<state readOnly="true" pattern="%1$tH:%1$tM"></state>
</channel-type>
<channel-type id="finish" advanced="true">
<item-type>DateTime</item-type>
<label>Finish Time</label>
<description>Time to finish the program running on the appliance</description>
<state readOnly="true" pattern="%1$tH:%1$tM"></state>
</channel-type>
<channel-type id="door" advanced="false">
<item-type>Contact</item-type>
<label>Door</label>
<description>Current state of the door of the appliance</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="switch" advanced="false">
<item-type>Switch</item-type>
<label>Switch</label>
<description>Switch the appliance on or off</description>
</channel-type>
<channel-type id="stop" advanced="false">
<item-type>Switch</item-type>
<label>Stop</label>
<description>Stop the hood</description>
</channel-type>
<channel-type id="step" advanced="true">
<item-type>Number</item-type>
<label>Step</label>
<description>Current step in the program running on the appliance</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="target" advanced="true">
<item-type>Number</item-type>
<label>Target Temperature</label>
<description>Target temperature to be reached by the oven</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="measured" advanced="true">
<item-type>Number</item-type>
<label>Measured Temperature</label>
<description>Actual measured temperature in the oven</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="temp" advanced="true">
<item-type>Number</item-type>
<label>Temperature</label>
<description>Program temperature in the oven</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="supercool" advanced="false">
<item-type>String</item-type>
<label>Super Cool</label>
<description>Start Super Cooling</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="current" advanced="false">
<item-type>Number</item-type>
<label>Current Temperature</label>
<description>Current temperature in the fridge</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="freezerstate" advanced="false">
<item-type>String</item-type>
<label>Status</label>
<description>Current status of the freezer compartment</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="fridgestate" advanced="false">
<item-type>String</item-type>
<label>Status</label>
<description>Current status of the fridge compartment</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="superfreeze" advanced="false">
<item-type>String</item-type>
<label>Super Freeze</label>
<description>Start Super Freezing</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="freezercurrent" advanced="false">
<item-type>Number</item-type>
<label>Current Temperature</label>
<description>Current temperature in the freezer compartment</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="freezertarget" advanced="true">
<item-type>Number</item-type>
<label>Target Temperature</label>
<description>Target temperature to be reached by the freezer compartment</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="fridgecurrent" advanced="false">
<item-type>Number</item-type>
<label>Current Temperature</label>
<description>Current temperature in the fridge compartment</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="fridgetarget" advanced="true">
<item-type>Number</item-type>
<label>Target Temperature</label>
<description>Target temperature to be reached by the fridge compartment</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="plates" advanced="false">
<item-type>Number</item-type>
<label>Plates</label>
<description>Number of heating zones/plates on the hob</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="power" advanced="false">
<item-type>Number</item-type>
<label>Power Step</label>
<description>Power level of the heating zone/plate</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="heat" advanced="true">
<item-type>Number</item-type>
<label>Remaining Heat</label>
<description>Remaining heat level of the heating zone/plate</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="time" advanced="true">
<item-type>String</item-type>
<label>Remaining Time</label>
<description>Remaining time of the heating zone/plate</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="ventilation" advanced="false">
<item-type>Number</item-type>
<label>Ventilation Power</label>
<description>Current ventilation power</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="spinningspeed" advanced="true">
<item-type>String</item-type>
<label>Spinning Speed</label>
<description>Spinning speed in the program running on the appliance</description>
<state readOnly="true"></state>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="miele"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Coffee machine -->
<thing-type id="coffeemachine">
<supported-bridge-type-refs>
<bridge-type-ref id="xgw3000"/>
</supported-bridge-type-refs>
<label>Coffee Machine</label>
<description>This is a Miele@home compatible coffee machine</description>
<channels>
<channel id="state" typeId="state"/>
<channel id="program" typeId="program"/>
<channel id="type" typeId="type"/>
<channel id="phase" typeId="phase"/>
<channel id="door" typeId="door"/>
<channel id="switch" typeId="switch"/>
</channels>
<config-description>
<parameter name="uid" type="text">
<label>ID</label>
<description>The identifier identifies one certain appliance on the ZigBee network.</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="miele"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Dishwasher -->
<thing-type id="dishwasher">
<supported-bridge-type-refs>
<bridge-type-ref id="xgw3000"/>
</supported-bridge-type-refs>
<label>Dishwasher</label>
<description>This is a Miele@home compatible dishwasher</description>
<channels>
<channel id="state" typeId="state"/>
<channel id="program" typeId="program"/>
<channel id="phase" typeId="phase"/>
<channel id="start" typeId="start"/>
<channel id="duration" typeId="duration"/>
<channel id="elapsed" typeId="elapsed"/>
<channel id="finish" typeId="finish"/>
<channel id="door" typeId="door"/>
<channel id="switch" typeId="switch"/>
</channels>
<config-description>
<parameter name="uid" type="text">
<label>ID</label>
<description>The identifier identifies one certain appliance on the ZigBee network.</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="miele"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Fridge -->
<thing-type id="fridge">
<supported-bridge-type-refs>
<bridge-type-ref id="xgw3000"/>
</supported-bridge-type-refs>
<label>Fridge</label>
<description>This is a Miele@home compatible fridge</description>
<channels>
<channel id="state" typeId="state"/>
<channel id="supercool" typeId="supercool"/>
<channel id="current" typeId="current"/>
<channel id="target" typeId="target"/>
<channel id="door" typeId="door"/>
<channel id="start" typeId="switch"/>
</channels>
<config-description>
<parameter name="uid" type="text">
<label>ID</label>
<description>The identifier identifies one certain appliance on the ZigBee network.</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="miele"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Fridge freezer -->
<thing-type id="fridgefreezer">
<supported-bridge-type-refs>
<bridge-type-ref id="xgw3000"/>
</supported-bridge-type-refs>
<label>Fridge Freezer</label>
<description>This is a Miele@home compatible fridgefreezer</description>
<channels>
<channel id="state" typeId="state"/>
<channel id="freezerstate" typeId="freezerstate"/>
<channel id="fridgestate" typeId="fridgestate"/>
<channel id="supercool" typeId="supercool"/>
<channel id="superfreeze" typeId="superfreeze"/>
<channel id="freezercurrent" typeId="freezercurrent"/>
<channel id="freezertarget" typeId="freezertarget"/>
<channel id="fridgecurrent" typeId="fridgecurrent"/>
<channel id="fridgetarget" typeId="fridgetarget"/>
<channel id="door" typeId="door"/>
<channel id="start" typeId="switch"/>
</channels>
<config-description>
<parameter name="uid" type="text">
<label>ID</label>
<description>The identifier identifies one certain appliance on the ZigBee network.</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="miele"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Hob -->
<thing-type id="hob">
<supported-bridge-type-refs>
<bridge-type-ref id="xgw3000"/>
</supported-bridge-type-refs>
<label>Hob</label>
<description>This is a Miele@home compatible hob</description>
<channels>
<channel id="state" typeId="state"/>
<channel id="plate1power" typeId="power"/>
<channel id="plate1heat" typeId="heat"/>
<channel id="plate1time" typeId="time"/>
<channel id="plate2power" typeId="power"/>
<channel id="plate2heat" typeId="heat"/>
<channel id="plate2time" typeId="time"/>
<channel id="plate3power" typeId="power"/>
<channel id="plate3heat" typeId="heat"/>
<channel id="plate3time" typeId="time"/>
<channel id="plate4power" typeId="power"/>
<channel id="plate4heat" typeId="heat"/>
<channel id="plate4time" typeId="time"/>
<channel id="plate5power" typeId="power"/>
<channel id="plate5heat" typeId="heat"/>
<channel id="plate5time" typeId="time"/>
<channel id="plate6power" typeId="power"/>
<channel id="plate6heat" typeId="heat"/>
<channel id="plate6time" typeId="time"/>
</channels>
<config-description>
<parameter name="uid" type="text">
<label>ID</label>
<description>The identifier identifies one certain appliance on the ZigBee network.</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="miele"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Hood -->
<thing-type id="hood">
<supported-bridge-type-refs>
<bridge-type-ref id="xgw3000"/>
</supported-bridge-type-refs>
<label>Hood</label>
<description>This is a Miele@home compatible hood</description>
<channels>
<channel id="state" typeId="state"/>
<channel id="light" typeId="switch"/>
<channel id="ventilation" typeId="ventilation"/>
<channel id="stop" typeId="stop"/>
</channels>
<config-description>
<parameter name="uid" type="text">
<label>ID</label>
<description>The identifier identifies the appliance on the ZigBee network.</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="miele"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Oven -->
<thing-type id="oven">
<supported-bridge-type-refs>
<bridge-type-ref id="xgw3000"/>
</supported-bridge-type-refs>
<label>Oven</label>
<description>This is a Miele@home compatible oven</description>
<channels>
<channel id="state" typeId="state"/>
<channel id="program" typeId="program"/>
<channel id="type" typeId="type"/>
<channel id="phase" typeId="phase"/>
<channel id="start" typeId="start"/>
<channel id="duration" typeId="duration"/>
<channel id="elapsed" typeId="elapsed"/>
<channel id="finish" typeId="finish"/>
<channel id="target" typeId="target"/>
<channel id="measured" typeId="measured"/>
<channel id="temp1" typeId="temp"/>
<channel id="temp2" typeId="temp"/>
<channel id="door" typeId="door"/>
<channel id="stop" typeId="stop"/>
<channel id="switch" typeId="switch"/>
</channels>
<config-description>
<parameter name="uid" type="text">
<label>ID</label>
<description>The identifier identifies one certain appliance on the ZigBee network.</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="miele"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Tumble dryer -->
<thing-type id="tumbledryer">
<supported-bridge-type-refs>
<bridge-type-ref id="xgw3000"/>
</supported-bridge-type-refs>
<label>Tumbledryer</label>
<description>This is a Miele@home compatible tumbledryer</description>
<channels>
<channel id="state" typeId="state"/>
<channel id="program" typeId="program"/>
<channel id="type" typeId="type"/>
<channel id="phase" typeId="phase"/>
<channel id="start" typeId="start"/>
<channel id="duration" typeId="duration"/>
<channel id="elapsed" typeId="elapsed"/>
<channel id="finish" typeId="finish"/>
<channel id="door" typeId="door"/>
<channel id="switch" typeId="switch"/>
<channel id="step" typeId="step"/>
</channels>
<config-description>
<parameter name="uid" type="text">
<label>ID</label>
<description>The identifier identifies one certain appliance on the ZigBee network.</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="miele"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Washing machine -->
<thing-type id="washingmachine">
<supported-bridge-type-refs>
<bridge-type-ref id="xgw3000"/>
</supported-bridge-type-refs>
<label>Washing Machine</label>
<description>This is a Miele@home compatible washing machine</description>
<channels>
<channel id="state" typeId="state"/>
<channel id="program" typeId="program"/>
<channel id="type" typeId="type"/>
<channel id="phase" typeId="phase"/>
<channel id="start" typeId="start"/>
<channel id="duration" typeId="duration"/>
<channel id="elapsed" typeId="elapsed"/>
<channel id="finish" typeId="finish"/>
<channel id="door" typeId="door"/>
<channel id="switch" typeId="switch"/>
<channel id="target" typeId="target"/>
<channel id="spinningspeed" typeId="spinningspeed"/>
</channels>
<config-description>
<parameter name="uid" type="text">
<label>ID</label>
<description>The identifier identifies one certain appliance on the ZigBee network.</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="miele"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Miele Bridge -->
<bridge-type id="xgw3000">
<label>Miele XGW3000</label>
<description>The miele bridge represents the Miele@home XGW3000 gateway.</description>
<properties>
<property name="vendor">Miele</property>
</properties>
<config-description>
<parameter name="ipAddress" type="text">
<context>network-address</context>
<label>Network Address</label>
<description>Network address of the Miele@home gateway.</description>
<required>true</required>
</parameter>
<parameter name="interface" type="text">
<context>network-address</context>
<label>Network Address of the Multicast Interface</label>
<description>Network address of openHAB host interface where the binding will listen for multicast events coming
from the Miele@home gateway</description>
<required>true</required>
</parameter>
<parameter name="userName" type="text">
<label>Username</label>
<description>
Name of a registered Miele@home user.
</description>
<required>false</required>
</parameter>
<parameter name="password" type="text">
<context>password</context>
<label>Password</label>
<description>Password for the registered Miele@home</description>
<required>false</required>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>