]> git.basschouten.com Git - openhab-addons.git/blob
8f0db9dd3c706ec016840696b10a829bef4bb852
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.miele.internal.handler;
14
15 import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
16
17 import java.util.HashMap;
18 import java.util.Map;
19 import java.util.Set;
20 import java.util.stream.Collectors;
21 import java.util.stream.Stream;
22
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;
42
43 import com.google.gson.Gson;
44 import com.google.gson.JsonElement;
45 import com.google.gson.JsonObject;
46 import com.google.gson.JsonParser;
47
48 /**
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
53  *
54  * @author Karel Goderis - Initial contribution
55  * @author Martin Lepsy - Added check for JsonNull result
56  */
57 public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannelSelector> extends BaseThingHandler
58         implements ApplianceStatusListener {
59
60     private final Logger logger = LoggerFactory.getLogger(MieleApplianceHandler.class);
61
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());
66
67     protected Gson gson = new Gson();
68
69     protected String uid;
70     protected MieleBridgeHandler bridgeHandler;
71     private Class<E> selectorType;
72     protected String modelID;
73
74     protected Map<String, String> metaDataCache = new HashMap<>();
75
76     public MieleApplianceHandler(Thing thing, Class<E> selectorType, String modelID) {
77         super(thing);
78         this.selectorType = selectorType;
79         this.modelID = modelID;
80     }
81
82     public ApplianceChannelSelector getValueSelectorFromChannelID(String valueSelectorText)
83             throws IllegalArgumentException {
84         for (ApplianceChannelSelector c : selectorType.getEnumConstants()) {
85             if (c.getChannelID() != null && c.getChannelID().equals(valueSelectorText)) {
86                 return c;
87             }
88         }
89
90         throw new IllegalArgumentException("Not valid value selector");
91     }
92
93     public ApplianceChannelSelector getValueSelectorFromMieleID(String valueSelectorText)
94             throws IllegalArgumentException {
95         for (ApplianceChannelSelector c : selectorType.getEnumConstants()) {
96             if (c.getMieleID() != null && c.getMieleID().equals(valueSelectorText)) {
97                 return c;
98             }
99         }
100
101         throw new IllegalArgumentException("Not valid value selector");
102     }
103
104     @Override
105     public void initialize() {
106         logger.debug("Initializing Miele appliance handler.");
107         final String uid = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
108         if (uid != null) {
109             this.uid = uid;
110             if (getMieleBridgeHandler() != null) {
111                 ThingStatusInfo statusInfo = getBridge().getStatusInfo();
112                 updateStatus(statusInfo.getStatus(), statusInfo.getStatusDetail(), statusInfo.getDescription());
113             }
114         }
115     }
116
117     public void onBridgeConnectionResumed() {
118         if (getMieleBridgeHandler() != null) {
119             ThingStatusInfo statusInfo = getBridge().getStatusInfo();
120             updateStatus(statusInfo.getStatus(), statusInfo.getStatusDetail(), statusInfo.getDescription());
121         }
122     }
123
124     @Override
125     public void dispose() {
126         logger.debug("Handler disposes. Unregistering listener.");
127         if (uid != null) {
128             MieleBridgeHandler bridgeHandler = getMieleBridgeHandler();
129             if (bridgeHandler != null) {
130                 getMieleBridgeHandler().unregisterApplianceStatusListener(this);
131             }
132             uid = null;
133         }
134     }
135
136     @Override
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
141             return;
142         }
143     }
144
145     @Override
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());
151
152         if (myUID.equals(UID)) {
153             if (modelID.equals(this.modelID)) {
154                 for (JsonElement prop : dco.Properties.getAsJsonArray()) {
155                     try {
156                         DeviceProperty dp = gson.fromJson(prop, DeviceProperty.class);
157                         dp.Value = StringUtils.trim(dp.Value);
158                         dp.Value = StringUtils.strip(dp.Value);
159
160                         onAppliancePropertyChanged(UID, dp);
161                     } catch (Exception p) {
162                         // Ignore - this is due to an unrecognized and not yet reverse-engineered array property
163                     }
164                 }
165
166                 for (JsonElement operation : dco.Operations.getAsJsonArray()) {
167                     try {
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
172                     }
173                 }
174             }
175         }
176     }
177
178     @Override
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);
182
183         if (myUID.equals(UID)) {
184             try {
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;
196                         dmd.Filter = null;
197                         dmd.description = null;
198                     }
199                 }
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);
206                 }
207
208                 ApplianceChannelSelector selector = null;
209                 try {
210                     selector = getValueSelectorFromMieleID(dp.Name);
211                 } catch (Exception h) {
212                     logger.trace("{} is not a valid channel for a {}", dp.Name, modelID);
213                 }
214
215                 String dpValue = StringUtils.trim(StringUtils.strip(dp.Value));
216
217                 if (selector != null) {
218                     if (!selector.isProperty()) {
219                         ChannelUID theChannelUID = new ChannelUID(getThing().getUID(), selector.getChannelID());
220
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));
225                         } else {
226                             updateState(theChannelUID, UnDefType.UNDEF);
227                         }
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);
234                     }
235                 }
236             } catch (IllegalArgumentException e) {
237                 logger.error("An exception occurred while processing a changed device property :'{}'", e.getMessage());
238             }
239         }
240     }
241
242     @Override
243     public void onApplianceRemoved(HomeDevice appliance) {
244         if (uid != null) {
245             if (uid.equals(appliance.UID)) {
246                 updateStatus(ThingStatus.OFFLINE);
247             }
248         }
249     }
250
251     @Override
252     public void onApplianceAdded(HomeDevice appliance) {
253         if (uid != null) {
254             if (uid.equals(appliance.UID)) {
255                 updateStatus(ThingStatus.ONLINE);
256             }
257         }
258     }
259
260     private synchronized MieleBridgeHandler getMieleBridgeHandler() {
261         if (this.bridgeHandler == null) {
262             Bridge bridge = getBridge();
263             if (bridge == null) {
264                 return null;
265             }
266             ThingHandler handler = bridge.getHandler();
267             if (handler instanceof MieleBridgeHandler) {
268                 this.bridgeHandler = (MieleBridgeHandler) handler;
269                 this.bridgeHandler.registerApplianceStatusListener(this);
270             } else {
271                 return null;
272             }
273         }
274         return this.bridgeHandler;
275     }
276
277     protected boolean isResultProcessable(JsonElement result) {
278         return result != null && !result.isJsonNull();
279     }
280 }