]> git.basschouten.com Git - openhab-addons.git/blob
b3ba33c1969cf4f1c048d3693d7143f83dc6edcf
[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.lang3.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 = (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 = (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                         JsonObject jsonMetaData = (JsonObject) JsonParser.parseString(metadata);
190                         dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class);
191                         // only keep the enum, if any - that's all we care for events we receive via multicast
192                         // all other fields are nulled
193                         dmd.LocalizedID = null;
194                         dmd.LocalizedValue = null;
195                         dmd.Filter = null;
196                         dmd.description = null;
197                     }
198                 }
199                 if (dp.Metadata != null) {
200                     String metadata = StringUtils.replace(dp.Metadata.toString(), "enum", "MieleEnum");
201                     JsonObject jsonMetaData = (JsonObject) JsonParser.parseString(metadata);
202                     dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class);
203                     metaDataCache.put(new StringBuilder().append(dp.Name).toString().trim(), metadata);
204                 }
205
206                 ApplianceChannelSelector selector = null;
207                 try {
208                     selector = getValueSelectorFromMieleID(dp.Name);
209                 } catch (Exception h) {
210                     logger.trace("{} is not a valid channel for a {}", dp.Name, modelID);
211                 }
212
213                 String dpValue = StringUtils.trim(StringUtils.strip(dp.Value));
214
215                 if (selector != null) {
216                     if (!selector.isProperty()) {
217                         ChannelUID theChannelUID = new ChannelUID(getThing().getUID(), selector.getChannelID());
218
219                         if (dp.Value != null) {
220                             logger.trace("Update state of {} with getState '{}'", theChannelUID,
221                                     selector.getState(dpValue, dmd));
222                             updateState(theChannelUID, selector.getState(dpValue, dmd));
223                         } else {
224                             updateState(theChannelUID, UnDefType.UNDEF);
225                         }
226                     } else if (dpValue != null) {
227                         logger.debug("Updating the property '{}' of '{}' to '{}'", selector.getChannelID(),
228                                 getThing().getUID(), selector.getState(dpValue, dmd).toString());
229                         Map<String, String> properties = editProperties();
230                         properties.put(selector.getChannelID(), selector.getState(dpValue, dmd).toString());
231                         updateProperties(properties);
232                     }
233                 }
234             } catch (IllegalArgumentException e) {
235                 logger.error("An exception occurred while processing a changed device property :'{}'", e.getMessage());
236             }
237         }
238     }
239
240     @Override
241     public void onApplianceRemoved(HomeDevice appliance) {
242         if (uid != null) {
243             if (uid.equals(appliance.UID)) {
244                 updateStatus(ThingStatus.OFFLINE);
245             }
246         }
247     }
248
249     @Override
250     public void onApplianceAdded(HomeDevice appliance) {
251         if (uid != null) {
252             if (uid.equals(appliance.UID)) {
253                 updateStatus(ThingStatus.ONLINE);
254             }
255         }
256     }
257
258     private synchronized MieleBridgeHandler getMieleBridgeHandler() {
259         if (this.bridgeHandler == null) {
260             Bridge bridge = getBridge();
261             if (bridge == null) {
262                 return null;
263             }
264             ThingHandler handler = bridge.getHandler();
265             if (handler instanceof MieleBridgeHandler) {
266                 this.bridgeHandler = (MieleBridgeHandler) handler;
267                 this.bridgeHandler.registerApplianceStatusListener(this);
268             } else {
269                 return null;
270             }
271         }
272         return this.bridgeHandler;
273     }
274
275     protected boolean isResultProcessable(JsonElement result) {
276         return result != null && !result.isJsonNull();
277     }
278 }