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