]> git.basschouten.com Git - openhab-addons.git/blob
560eed510e17ca7f169a2364bdd4f29f62da3955
[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 = (getThing().getProperties().get(PROTOCOL_PROPERTY_NAME))
147                 + (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
148         String modelID = StringUtils.right(dco.DeviceClass,
149                 dco.DeviceClass.length() - new String("com.miele.xgw3000.gateway.hdm.deviceclasses.Miele").length());
150
151         if (myUID.equals(UID)) {
152             if (modelID.equals(this.modelID)) {
153                 for (JsonElement prop : dco.Properties.getAsJsonArray()) {
154                     try {
155                         DeviceProperty dp = gson.fromJson(prop, DeviceProperty.class);
156                         dp.Value = StringUtils.trim(dp.Value);
157                         dp.Value = StringUtils.strip(dp.Value);
158
159                         onAppliancePropertyChanged(UID, dp);
160                     } catch (Exception p) {
161                         // Ignore - this is due to an unrecognized and not yet reverse-engineered array property
162                     }
163                 }
164             }
165         }
166     }
167
168     @Override
169     public void onAppliancePropertyChanged(String UID, DeviceProperty dp) {
170         String myUID = (getThing().getProperties().get(PROTOCOL_PROPERTY_NAME))
171                 + (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
172
173         if (myUID.equals(UID)) {
174             try {
175                 DeviceMetaData dmd = null;
176                 if (dp.Metadata == null) {
177                     String metadata = metaDataCache.get(new StringBuilder().append(dp.Name).toString().trim());
178                     if (metadata != null) {
179                         JsonObject jsonMetaData = (JsonObject) JsonParser.parseString(metadata);
180                         dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class);
181                         // only keep the enum, if any - that's all we care for events we receive via multicast
182                         // all other fields are nulled
183                         dmd.LocalizedID = null;
184                         dmd.LocalizedValue = null;
185                         dmd.Filter = null;
186                         dmd.description = null;
187                     }
188                 }
189                 if (dp.Metadata != null) {
190                     String metadata = StringUtils.replace(dp.Metadata.toString(), "enum", "MieleEnum");
191                     JsonObject jsonMetaData = (JsonObject) JsonParser.parseString(metadata);
192                     dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class);
193                     metaDataCache.put(new StringBuilder().append(dp.Name).toString().trim(), metadata);
194                 }
195
196                 ApplianceChannelSelector selector = null;
197                 try {
198                     selector = getValueSelectorFromMieleID(dp.Name);
199                 } catch (Exception h) {
200                     logger.trace("{} is not a valid channel for a {}", dp.Name, modelID);
201                 }
202
203                 String dpValue = StringUtils.trim(StringUtils.strip(dp.Value));
204
205                 if (selector != null) {
206                     if (!selector.isProperty()) {
207                         ChannelUID theChannelUID = new ChannelUID(getThing().getUID(), selector.getChannelID());
208
209                         if (dp.Value != null) {
210                             logger.trace("Update state of {} with getState '{}'", theChannelUID,
211                                     selector.getState(dpValue, dmd));
212                             updateState(theChannelUID, selector.getState(dpValue, dmd));
213                         } else {
214                             updateState(theChannelUID, UnDefType.UNDEF);
215                         }
216                     } else if (dpValue != null) {
217                         logger.debug("Updating the property '{}' of '{}' to '{}'", selector.getChannelID(),
218                                 getThing().getUID(), selector.getState(dpValue, dmd).toString());
219                         Map<String, String> properties = editProperties();
220                         properties.put(selector.getChannelID(), selector.getState(dpValue, dmd).toString());
221                         updateProperties(properties);
222                     }
223                 }
224             } catch (IllegalArgumentException e) {
225                 logger.error("An exception occurred while processing a changed device property :'{}'", e.getMessage());
226             }
227         }
228     }
229
230     @Override
231     public void onApplianceRemoved(HomeDevice appliance) {
232         if (uid != null) {
233             if (uid.equals(appliance.UID)) {
234                 updateStatus(ThingStatus.OFFLINE);
235             }
236         }
237     }
238
239     @Override
240     public void onApplianceAdded(HomeDevice appliance) {
241         if (uid != null) {
242             if (uid.equals(appliance.UID)) {
243                 updateStatus(ThingStatus.ONLINE);
244             }
245         }
246     }
247
248     private synchronized MieleBridgeHandler getMieleBridgeHandler() {
249         if (this.bridgeHandler == null) {
250             Bridge bridge = getBridge();
251             if (bridge == null) {
252                 return null;
253             }
254             ThingHandler handler = bridge.getHandler();
255             if (handler instanceof MieleBridgeHandler) {
256                 this.bridgeHandler = (MieleBridgeHandler) handler;
257                 this.bridgeHandler.registerApplianceStatusListener(this);
258             } else {
259                 return null;
260             }
261         }
262         return this.bridgeHandler;
263     }
264
265     protected boolean isResultProcessable(JsonElement result) {
266         return result != null && !result.isJsonNull();
267     }
268 }