]> git.basschouten.com Git - openhab-addons.git/blob
d6c000340b79ea8ac68c7c6d29589f58dbe7f35b
[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.DeviceProperty;
27 import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.HomeDevice;
28 import org.openhab.core.thing.Bridge;
29 import org.openhab.core.thing.ChannelUID;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingStatusInfo;
33 import org.openhab.core.thing.ThingTypeUID;
34 import org.openhab.core.thing.binding.BaseThingHandler;
35 import org.openhab.core.thing.binding.ThingHandler;
36 import org.openhab.core.types.Command;
37 import org.openhab.core.types.RefreshType;
38 import org.openhab.core.types.UnDefType;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import com.google.gson.Gson;
43 import com.google.gson.JsonElement;
44 import com.google.gson.JsonObject;
45 import com.google.gson.JsonParser;
46
47 /**
48  * The {@link MieleApplianceHandler} is an abstract class
49  * responsible for handling commands, which are sent to one
50  * of the channels of the appliance that understands/"talks"
51  * the {@link ApplianceChannelSelector} datapoints
52  *
53  * @author Karel Goderis - Initial contribution
54  * @author Martin Lepsy - Added check for JsonNull result
55  */
56 public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannelSelector> extends BaseThingHandler
57         implements ApplianceStatusListener {
58
59     private final Logger logger = LoggerFactory.getLogger(MieleApplianceHandler.class);
60
61     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream
62             .of(THING_TYPE_DISHWASHER, THING_TYPE_OVEN, THING_TYPE_FRIDGE, THING_TYPE_DRYER, THING_TYPE_HOB,
63                     THING_TYPE_FRIDGEFREEZER, THING_TYPE_HOOD, THING_TYPE_WASHINGMACHINE, THING_TYPE_COFFEEMACHINE)
64             .collect(Collectors.toSet());
65
66     protected Gson gson = new Gson();
67
68     protected String uid;
69     protected MieleBridgeHandler bridgeHandler;
70     private Class<E> selectorType;
71     protected String modelID;
72
73     protected Map<String, String> metaDataCache = new HashMap<>();
74
75     public MieleApplianceHandler(Thing thing, Class<E> selectorType, String modelID) {
76         super(thing);
77         this.selectorType = selectorType;
78         this.modelID = modelID;
79     }
80
81     public ApplianceChannelSelector getValueSelectorFromChannelID(String valueSelectorText)
82             throws IllegalArgumentException {
83         for (ApplianceChannelSelector c : selectorType.getEnumConstants()) {
84             if (c.getChannelID() != null && c.getChannelID().equals(valueSelectorText)) {
85                 return c;
86             }
87         }
88
89         throw new IllegalArgumentException("Not valid value selector");
90     }
91
92     public ApplianceChannelSelector getValueSelectorFromMieleID(String valueSelectorText)
93             throws IllegalArgumentException {
94         for (ApplianceChannelSelector c : selectorType.getEnumConstants()) {
95             if (c.getMieleID() != null && c.getMieleID().equals(valueSelectorText)) {
96                 return c;
97             }
98         }
99
100         throw new IllegalArgumentException("Not valid value selector");
101     }
102
103     @Override
104     public void initialize() {
105         logger.debug("Initializing Miele appliance handler.");
106         final String uid = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
107         if (uid != null) {
108             this.uid = uid;
109             if (getMieleBridgeHandler() != null) {
110                 ThingStatusInfo statusInfo = getBridge().getStatusInfo();
111                 updateStatus(statusInfo.getStatus(), statusInfo.getStatusDetail(), statusInfo.getDescription());
112             }
113         }
114     }
115
116     public void onBridgeConnectionResumed() {
117         if (getMieleBridgeHandler() != null) {
118             ThingStatusInfo statusInfo = getBridge().getStatusInfo();
119             updateStatus(statusInfo.getStatus(), statusInfo.getStatusDetail(), statusInfo.getDescription());
120         }
121     }
122
123     @Override
124     public void dispose() {
125         logger.debug("Handler disposes. Unregistering listener.");
126         if (uid != null) {
127             MieleBridgeHandler bridgeHandler = getMieleBridgeHandler();
128             if (bridgeHandler != null) {
129                 getMieleBridgeHandler().unregisterApplianceStatusListener(this);
130             }
131             uid = null;
132         }
133     }
134
135     @Override
136     public void handleCommand(ChannelUID channelUID, Command command) {
137         // Here we could handle commands that are common to all Miele Appliances, but so far I don't know of any
138         if (command instanceof RefreshType) {
139             // Placeholder for future refinement
140             return;
141         }
142     }
143
144     @Override
145     public void onApplianceStateChanged(String UID, DeviceClassObject dco) {
146         String myUID = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
147         String modelID = StringUtils.right(dco.DeviceClass,
148                 dco.DeviceClass.length() - new String("com.miele.xgw3000.gateway.hdm.deviceclasses.Miele").length());
149
150         if (myUID.equals(UID)) {
151             if (modelID.equals(this.modelID)) {
152                 for (JsonElement prop : dco.Properties.getAsJsonArray()) {
153                     try {
154                         DeviceProperty dp = gson.fromJson(prop, DeviceProperty.class);
155                         dp.Value = StringUtils.trim(dp.Value);
156                         dp.Value = StringUtils.strip(dp.Value);
157
158                         onAppliancePropertyChanged(UID, dp);
159                     } catch (Exception p) {
160                         // Ignore - this is due to an unrecognized and not yet reverse-engineered array property
161                     }
162                 }
163             }
164         }
165     }
166
167     @Override
168     public void onAppliancePropertyChanged(String UID, DeviceProperty dp) {
169         String myUID = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
170
171         if (myUID.equals(UID)) {
172             try {
173                 DeviceMetaData dmd = null;
174                 if (dp.Metadata == null) {
175                     String metadata = metaDataCache.get(new StringBuilder().append(dp.Name).toString().trim());
176                     if (metadata != null) {
177                         JsonObject jsonMetaData = (JsonObject) JsonParser.parseString(metadata);
178                         dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class);
179                         // only keep the enum, if any - that's all we care for events we receive via multicast
180                         // all other fields are nulled
181                         dmd.LocalizedID = null;
182                         dmd.LocalizedValue = null;
183                         dmd.Filter = null;
184                         dmd.description = null;
185                     }
186                 }
187                 if (dp.Metadata != null) {
188                     String metadata = StringUtils.replace(dp.Metadata.toString(), "enum", "MieleEnum");
189                     JsonObject jsonMetaData = (JsonObject) JsonParser.parseString(metadata);
190                     dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class);
191                     metaDataCache.put(new StringBuilder().append(dp.Name).toString().trim(), metadata);
192                 }
193
194                 ApplianceChannelSelector selector = null;
195                 try {
196                     selector = getValueSelectorFromMieleID(dp.Name);
197                 } catch (Exception h) {
198                     logger.trace("{} is not a valid channel for a {}", dp.Name, modelID);
199                 }
200
201                 String dpValue = StringUtils.trim(StringUtils.strip(dp.Value));
202
203                 if (selector != null) {
204                     if (!selector.isProperty()) {
205                         ChannelUID theChannelUID = new ChannelUID(getThing().getUID(), selector.getChannelID());
206
207                         if (dp.Value != null) {
208                             logger.trace("Update state of {} with getState '{}'", theChannelUID,
209                                     selector.getState(dpValue, dmd));
210                             updateState(theChannelUID, selector.getState(dpValue, dmd));
211                         } else {
212                             updateState(theChannelUID, UnDefType.UNDEF);
213                         }
214                     } else if (dpValue != null) {
215                         logger.debug("Updating the property '{}' of '{}' to '{}'", selector.getChannelID(),
216                                 getThing().getUID(), selector.getState(dpValue, dmd).toString());
217                         Map<String, String> properties = editProperties();
218                         properties.put(selector.getChannelID(), selector.getState(dpValue, dmd).toString());
219                         updateProperties(properties);
220                     }
221                 }
222             } catch (IllegalArgumentException e) {
223                 logger.error("An exception occurred while processing a changed device property :'{}'", e.getMessage());
224             }
225         }
226     }
227
228     @Override
229     public void onApplianceRemoved(HomeDevice appliance) {
230         if (uid != null) {
231             if (uid.equals(appliance.getApplianceId())) {
232                 updateStatus(ThingStatus.OFFLINE);
233             }
234         }
235     }
236
237     @Override
238     public void onApplianceAdded(HomeDevice appliance) {
239         if (uid != null) {
240             if (uid.equals(appliance.getApplianceId())) {
241                 Map<String, String> properties = editProperties();
242                 properties.put(PROTOCOL_PROPERTY_NAME, appliance.getProtocol());
243                 updateProperties(properties);
244
245                 updateStatus(ThingStatus.ONLINE);
246             }
247         }
248     }
249
250     private synchronized MieleBridgeHandler getMieleBridgeHandler() {
251         if (this.bridgeHandler == null) {
252             Bridge bridge = getBridge();
253             if (bridge == null) {
254                 return null;
255             }
256             ThingHandler handler = bridge.getHandler();
257             if (handler instanceof MieleBridgeHandler) {
258                 this.bridgeHandler = (MieleBridgeHandler) handler;
259                 this.bridgeHandler.registerApplianceStatusListener(this);
260             } else {
261                 return null;
262             }
263         }
264         return this.bridgeHandler;
265     }
266
267     protected boolean isResultProcessable(JsonElement result) {
268         return result != null && !result.isJsonNull();
269     }
270 }