2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.miele.internal.handler;
15 import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
17 import java.util.HashMap;
20 import java.util.stream.Collectors;
21 import java.util.stream.Stream;
23 import org.apache.commons.lang.StringUtils;
24 import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceClassObject;
25 import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceMetaData;
26 import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceOperation;
27 import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceProperty;
28 import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.HomeDevice;
29 import org.openhab.core.thing.Bridge;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingStatusInfo;
34 import org.openhab.core.thing.ThingTypeUID;
35 import org.openhab.core.thing.binding.BaseThingHandler;
36 import org.openhab.core.thing.binding.ThingHandler;
37 import org.openhab.core.types.Command;
38 import org.openhab.core.types.RefreshType;
39 import org.openhab.core.types.UnDefType;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
43 import com.google.gson.Gson;
44 import com.google.gson.JsonElement;
45 import com.google.gson.JsonObject;
46 import com.google.gson.JsonParser;
49 * The {@link MieleApplianceHandler} is an abstract class
50 * responsible for handling commands, which are sent to one
51 * of the channels of the appliance that understands/"talks"
52 * the {@link ApplianceChannelSelector} datapoints
54 * @author Karel Goderis - Initial contribution
55 * @author Martin Lepsy - Added check for JsonNull result
57 public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannelSelector> extends BaseThingHandler
58 implements ApplianceStatusListener {
60 private final Logger logger = LoggerFactory.getLogger(MieleApplianceHandler.class);
62 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream
63 .of(THING_TYPE_DISHWASHER, THING_TYPE_OVEN, THING_TYPE_FRIDGE, THING_TYPE_DRYER, THING_TYPE_HOB,
64 THING_TYPE_FRIDGEFREEZER, THING_TYPE_HOOD, THING_TYPE_WASHINGMACHINE, THING_TYPE_COFFEEMACHINE)
65 .collect(Collectors.toSet());
67 protected Gson gson = new Gson();
70 protected MieleBridgeHandler bridgeHandler;
71 private Class<E> selectorType;
72 protected String modelID;
74 protected Map<String, String> metaDataCache = new HashMap<>();
76 public MieleApplianceHandler(Thing thing, Class<E> selectorType, String modelID) {
78 this.selectorType = selectorType;
79 this.modelID = modelID;
82 public ApplianceChannelSelector getValueSelectorFromChannelID(String valueSelectorText)
83 throws IllegalArgumentException {
84 for (ApplianceChannelSelector c : selectorType.getEnumConstants()) {
85 if (c.getChannelID() != null && c.getChannelID().equals(valueSelectorText)) {
90 throw new IllegalArgumentException("Not valid value selector");
93 public ApplianceChannelSelector getValueSelectorFromMieleID(String valueSelectorText)
94 throws IllegalArgumentException {
95 for (ApplianceChannelSelector c : selectorType.getEnumConstants()) {
96 if (c.getMieleID() != null && c.getMieleID().equals(valueSelectorText)) {
101 throw new IllegalArgumentException("Not valid value selector");
105 public void initialize() {
106 logger.debug("Initializing Miele appliance handler.");
107 final String uid = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
110 if (getMieleBridgeHandler() != null) {
111 ThingStatusInfo statusInfo = getBridge().getStatusInfo();
112 updateStatus(statusInfo.getStatus(), statusInfo.getStatusDetail(), statusInfo.getDescription());
117 public void onBridgeConnectionResumed() {
118 if (getMieleBridgeHandler() != null) {
119 ThingStatusInfo statusInfo = getBridge().getStatusInfo();
120 updateStatus(statusInfo.getStatus(), statusInfo.getStatusDetail(), statusInfo.getDescription());
125 public void dispose() {
126 logger.debug("Handler disposes. Unregistering listener.");
128 MieleBridgeHandler bridgeHandler = getMieleBridgeHandler();
129 if (bridgeHandler != null) {
130 getMieleBridgeHandler().unregisterApplianceStatusListener(this);
137 public void handleCommand(ChannelUID channelUID, Command command) {
138 // Here we could handle commands that are common to all Miele Appliances, but so far I don't know of any
139 if (command instanceof RefreshType) {
140 // Placeholder for future refinement
146 public void onApplianceStateChanged(String UID, DeviceClassObject dco) {
147 String myUID = ((String) getThing().getProperties().get(PROTOCOL_PROPERTY_NAME))
148 + (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
149 String modelID = StringUtils.right(dco.DeviceClass,
150 dco.DeviceClass.length() - new String("com.miele.xgw3000.gateway.hdm.deviceclasses.Miele").length());
152 if (myUID.equals(UID)) {
153 if (modelID.equals(this.modelID)) {
154 for (JsonElement prop : dco.Properties.getAsJsonArray()) {
156 DeviceProperty dp = gson.fromJson(prop, DeviceProperty.class);
157 dp.Value = StringUtils.trim(dp.Value);
158 dp.Value = StringUtils.strip(dp.Value);
160 onAppliancePropertyChanged(UID, dp);
161 } catch (Exception p) {
162 // Ignore - this is due to an unrecognized and not yet reverse-engineered array property
166 for (JsonElement operation : dco.Operations.getAsJsonArray()) {
168 DeviceOperation devop = gson.fromJson(operation, DeviceOperation.class);
169 DeviceMetaData pmd = gson.fromJson(devop.Metadata, DeviceMetaData.class);
170 } catch (Exception p) {
171 // Ignore - this is due to an unrecognized and not yet reverse-engineered array property
179 public void onAppliancePropertyChanged(String UID, DeviceProperty dp) {
180 String myUID = ((String) getThing().getProperties().get(PROTOCOL_PROPERTY_NAME))
181 + (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
183 if (myUID.equals(UID)) {
185 DeviceMetaData dmd = null;
186 if (dp.Metadata == null) {
187 String metadata = metaDataCache.get(new StringBuilder().append(dp.Name).toString().trim());
188 if (metadata != null) {
189 JsonParser parser = new JsonParser();
190 JsonObject jsonMetaData = (JsonObject) parser.parse(metadata);
191 dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class);
192 // only keep the enum, if any - that's all we care for events we receive via multicast
193 // all other fields are nulled
194 dmd.LocalizedID = null;
195 dmd.LocalizedValue = null;
197 dmd.description = null;
200 if (dp.Metadata != null) {
201 String metadata = StringUtils.replace(dp.Metadata.toString(), "enum", "MieleEnum");
202 JsonParser parser = new JsonParser();
203 JsonObject jsonMetaData = (JsonObject) parser.parse(metadata);
204 dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class);
205 metaDataCache.put(new StringBuilder().append(dp.Name).toString().trim(), metadata);
208 ApplianceChannelSelector selector = null;
210 selector = getValueSelectorFromMieleID(dp.Name);
211 } catch (Exception h) {
212 logger.trace("{} is not a valid channel for a {}", dp.Name, modelID);
215 String dpValue = StringUtils.trim(StringUtils.strip(dp.Value));
217 if (selector != null) {
218 if (!selector.isProperty()) {
219 ChannelUID theChannelUID = new ChannelUID(getThing().getUID(), selector.getChannelID());
221 if (dp.Value != null) {
222 logger.trace("Update state of {} with getState '{}'", theChannelUID,
223 selector.getState(dpValue, dmd));
224 updateState(theChannelUID, selector.getState(dpValue, dmd));
226 updateState(theChannelUID, UnDefType.UNDEF);
228 } else if (dpValue != null) {
229 logger.debug("Updating the property '{}' of '{}' to '{}'", selector.getChannelID(),
230 getThing().getUID(), selector.getState(dpValue, dmd).toString());
231 Map<String, String> properties = editProperties();
232 properties.put(selector.getChannelID(), selector.getState(dpValue, dmd).toString());
233 updateProperties(properties);
236 } catch (IllegalArgumentException e) {
237 logger.error("An exception occurred while processing a changed device property :'{}'", e.getMessage());
243 public void onApplianceRemoved(HomeDevice appliance) {
245 if (uid.equals(appliance.UID)) {
246 updateStatus(ThingStatus.OFFLINE);
252 public void onApplianceAdded(HomeDevice appliance) {
254 if (uid.equals(appliance.UID)) {
255 updateStatus(ThingStatus.ONLINE);
260 private synchronized MieleBridgeHandler getMieleBridgeHandler() {
261 if (this.bridgeHandler == null) {
262 Bridge bridge = getBridge();
263 if (bridge == null) {
266 ThingHandler handler = bridge.getHandler();
267 if (handler instanceof MieleBridgeHandler) {
268 this.bridgeHandler = (MieleBridgeHandler) handler;
269 this.bridgeHandler.registerApplianceStatusListener(this);
274 return this.bridgeHandler;
277 protected boolean isResultProcessable(JsonElement result) {
278 return result != null && !result.isJsonNull();