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