]> git.basschouten.com Git - openhab-addons.git/blob
967e6696c7a3f3f87628d34642cffeabf2f70c11
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.amazonechocontrol.internal.handler;
14
15 import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.DEVICE_PROPERTY_ID;
16 import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.SUPPORTED_INTERFACES;
17
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Locale;
26 import java.util.Map;
27 import java.util.Set;
28 import java.util.function.Supplier;
29
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.binding.amazonechocontrol.internal.Connection;
33 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.SmartHomeCapability;
34 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice;
35 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeGroups.SmartHomeGroup;
36 import org.openhab.binding.amazonechocontrol.internal.jsons.SmartHomeBaseDevice;
37 import org.openhab.binding.amazonechocontrol.internal.smarthome.Constants;
38 import org.openhab.binding.amazonechocontrol.internal.smarthome.HandlerBase;
39 import org.openhab.binding.amazonechocontrol.internal.smarthome.HandlerBase.ChannelInfo;
40 import org.openhab.binding.amazonechocontrol.internal.smarthome.HandlerBase.UpdateChannelResult;
41 import org.openhab.core.thing.Bridge;
42 import org.openhab.core.thing.Channel;
43 import org.openhab.core.thing.ChannelUID;
44 import org.openhab.core.thing.Thing;
45 import org.openhab.core.thing.ThingStatus;
46 import org.openhab.core.thing.ThingStatusDetail;
47 import org.openhab.core.thing.binding.BaseThingHandler;
48 import org.openhab.core.thing.binding.BridgeHandler;
49 import org.openhab.core.thing.binding.builder.ChannelBuilder;
50 import org.openhab.core.thing.binding.builder.ThingBuilder;
51 import org.openhab.core.thing.type.ChannelTypeUID;
52 import org.openhab.core.types.Command;
53 import org.openhab.core.types.RefreshType;
54 import org.openhab.core.types.State;
55 import org.openhab.core.types.StateDescription;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 import com.google.gson.Gson;
60 import com.google.gson.JsonArray;
61 import com.google.gson.JsonElement;
62 import com.google.gson.JsonObject;
63
64 /**
65  * @author Lukas Knoeller - Initial contribution
66  */
67 @NonNullByDefault
68 public class SmartHomeDeviceHandler extends BaseThingHandler {
69     private final Logger logger = LoggerFactory.getLogger(SmartHomeDeviceHandler.class);
70
71     private @Nullable SmartHomeBaseDevice smartHomeBaseDevice;
72     private final Gson gson;
73     private final Map<String, HandlerBase> handlers = new HashMap<>();
74     private final Map<String, JsonArray> lastStates = new HashMap<>();
75
76     public SmartHomeDeviceHandler(Thing thing, Gson gson) {
77         super(thing);
78         this.gson = gson;
79     }
80
81     public synchronized void setDeviceAndUpdateThingState(AccountHandler accountHandler,
82             @Nullable SmartHomeBaseDevice smartHomeBaseDevice) {
83         if (smartHomeBaseDevice == null) {
84             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Can't find smarthomeBaseDevice");
85             return;
86         }
87         boolean changed = this.smartHomeBaseDevice == null;
88         this.smartHomeBaseDevice = smartHomeBaseDevice;
89
90         Set<String> unusedChannels = new HashSet<>();
91         thing.getChannels().forEach(channel -> unusedChannels.add(channel.getUID().getId()));
92
93         Set<String> unusedHandlers = new HashSet<>(handlers.keySet());
94
95         Map<String, List<SmartHomeCapability>> capabilities = new HashMap<>();
96         getCapabilities(capabilities, accountHandler, smartHomeBaseDevice);
97
98         ThingBuilder thingBuilder = editThing();
99
100         for (String interfaceName : capabilities.keySet()) {
101             HandlerBase handler = handlers.get(interfaceName);
102             if (handler != null) {
103                 unusedHandlers.remove(interfaceName);
104             } else {
105                 Supplier<HandlerBase> creator = Constants.HANDLER_FACTORY.get(interfaceName);
106                 if (creator != null) {
107                     handler = creator.get();
108                     handlers.put(interfaceName, handler);
109                 }
110             }
111             if (handler != null) {
112                 Collection<ChannelInfo> required = handler.initialize(this,
113                         capabilities.getOrDefault(interfaceName, Collections.emptyList()));
114                 for (ChannelInfo channelInfo : required) {
115                     unusedChannels.remove(channelInfo.channelId);
116                     if (addChannelToDevice(thingBuilder, channelInfo.channelId, channelInfo.itemType,
117                             channelInfo.channelTypeUID)) {
118                         changed = true;
119                     }
120                 }
121             }
122         }
123
124         unusedHandlers.forEach(handlers::remove);
125         if (!unusedChannels.isEmpty()) {
126             changed = true;
127             unusedChannels.stream().map(id -> new ChannelUID(thing.getUID(), id)).forEach(thingBuilder::withoutChannel);
128         }
129
130         if (changed) {
131             updateThing(thingBuilder.build());
132             updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Thing has changed.");
133             accountHandler.forceDelayedSmartHomeStateUpdate(getId());
134         }
135     }
136
137     public String getId() {
138         String id = (String) getConfig().get(DEVICE_PROPERTY_ID);
139         if (id == null) {
140             return "";
141         }
142         return id;
143     }
144
145     @Override
146     public void updateState(String channelId, State state) {
147         super.updateState(new ChannelUID(thing.getUID(), channelId), state);
148     }
149
150     @Override
151     public void initialize() {
152         AccountHandler accountHandler = getAccountHandler();
153         if (accountHandler != null) {
154             accountHandler.addSmartHomeDeviceHandler(this);
155             setDeviceAndUpdateThingState(accountHandler, smartHomeBaseDevice);
156         } else {
157             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Bridgehandler not found");
158         }
159     }
160
161     private boolean addChannelToDevice(ThingBuilder thingBuilder, String channelId, String itemType,
162             ChannelTypeUID channelTypeUID) {
163         Channel channel = thing.getChannel(channelId);
164         if (channel != null) {
165             if (channelTypeUID.equals(channel.getChannelTypeUID()) && itemType.equals(channel.getAcceptedItemType())) {
166                 // channel exist with the same settings
167                 return false;
168             }
169             // channel exist with other settings, remove it first
170             thingBuilder.withoutChannel(channel.getUID());
171         }
172         thingBuilder.withChannel(ChannelBuilder.create(new ChannelUID(thing.getUID(), channelId), itemType)
173                 .withType(channelTypeUID).build());
174         return true;
175     }
176
177     public void updateChannelStates(List<SmartHomeBaseDevice> allDevices,
178             Map<String, JsonArray> applianceIdToCapabilityStates) {
179         AccountHandler accountHandler = getAccountHandler();
180         SmartHomeBaseDevice smartHomeBaseDevice = this.smartHomeBaseDevice;
181         if (smartHomeBaseDevice == null) {
182             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Can't find smarthomeBaseDevice!");
183             return;
184         }
185
186         boolean stateFound = false;
187         Map<String, List<JsonObject>> mapInterfaceToStates = new HashMap<>();
188         SmartHomeDevice firstDevice = null;
189         for (SmartHomeDevice shd : getSupportedSmartHomeDevices(smartHomeBaseDevice, allDevices)) {
190             JsonArray states = applianceIdToCapabilityStates.get(shd.applianceId);
191             String applianceId = shd.applianceId;
192             if (applianceId == null) {
193                 continue;
194             }
195             if (states != null) {
196                 stateFound = true;
197                 if (smartHomeBaseDevice.isGroup()) {
198                     // for groups, store the last state of all devices
199                     lastStates.put(applianceId, states);
200                 }
201             } else {
202                 states = lastStates.get(applianceId);
203                 if (states == null) {
204                     continue;
205                 }
206             }
207             if (firstDevice == null) {
208                 firstDevice = shd;
209             }
210             for (JsonElement stateElement : states) {
211                 String stateJson = stateElement.getAsString();
212                 if (stateJson.startsWith("{") && stateJson.endsWith("}")) {
213                     JsonObject state = gson.fromJson(stateJson, JsonObject.class);
214                     String interfaceName = state.get("namespace").getAsString();
215                     mapInterfaceToStates.computeIfAbsent(interfaceName, k -> new ArrayList<>()).add(state);
216                 }
217             }
218         }
219         for (HandlerBase handlerBase : handlers.values()) {
220             if (handlerBase == null) {
221                 continue;
222             }
223             UpdateChannelResult result = new UpdateChannelResult();
224
225             for (String interfaceName : handlerBase.getSupportedInterface()) {
226                 List<JsonObject> stateList = mapInterfaceToStates.getOrDefault(interfaceName, Collections.emptyList());
227                 try {
228                     handlerBase.updateChannels(interfaceName, stateList, result);
229                 } catch (Exception e) {
230                     // We catch all exceptions, otherwise all other things are not updated!
231                     logger.debug("Updating states failed", e);
232                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage());
233                 }
234             }
235
236             if (result.needSingleUpdate && smartHomeBaseDevice instanceof SmartHomeDevice && accountHandler != null) {
237                 SmartHomeDevice shd = (SmartHomeDevice) smartHomeBaseDevice;
238                 accountHandler.forceDelayedSmartHomeStateUpdate(shd.findId());
239             }
240         }
241
242         if (stateFound) {
243             updateStatus(ThingStatus.ONLINE);
244         } else {
245             updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "State not found");
246         }
247     }
248
249     private @Nullable AccountHandler getAccountHandler() {
250         Bridge bridge = getBridge();
251         if (bridge != null) {
252             BridgeHandler bridgeHandler = bridge.getHandler();
253             if (bridgeHandler instanceof AccountHandler) {
254                 return (AccountHandler) bridgeHandler;
255             }
256         }
257
258         return null;
259     }
260
261     @Override
262     public void handleCommand(ChannelUID channelUID, Command command) {
263         AccountHandler accountHandler = getAccountHandler();
264         if (accountHandler == null) {
265             logger.debug("accountHandler is null in {}", thing.getUID());
266             return;
267         }
268         Connection connection = accountHandler.findConnection();
269         if (connection == null) {
270             logger.debug("connection is null in {}", thing.getUID());
271             return;
272         }
273
274         try {
275             if (command instanceof RefreshType) {
276                 accountHandler.forceDelayedSmartHomeStateUpdate(getId());
277                 return;
278             }
279             SmartHomeBaseDevice smartHomeBaseDevice = this.smartHomeBaseDevice;
280             if (smartHomeBaseDevice == null) {
281                 logger.debug("smarthomeBaseDevice is null in {}", thing.getUID());
282                 return;
283             }
284             Set<SmartHomeDevice> devices = getSupportedSmartHomeDevices(smartHomeBaseDevice,
285                     accountHandler.getLastKnownSmartHomeDevices());
286             String channelId = channelUID.getId();
287
288             for (String interfaceName : handlers.keySet()) {
289                 HandlerBase handlerBase = handlers.get(interfaceName);
290                 if (handlerBase == null || !handlerBase.hasChannel(channelId)) {
291                     continue;
292                 }
293                 for (SmartHomeDevice shd : devices) {
294                     String entityId = shd.entityId;
295                     if (entityId == null) {
296                         continue;
297                     }
298                     SmartHomeCapability[] capabilities = shd.capabilities;
299                     if (capabilities == null) {
300                         logger.debug("capabilities is null in {}", thing.getUID());
301                         return;
302                     }
303                     accountHandler.forceDelayedSmartHomeStateUpdate(getId()); // block updates
304                     if (handlerBase.handleCommand(connection, shd, entityId, capabilities, channelUID.getId(),
305                             command)) {
306                         accountHandler.forceDelayedSmartHomeStateUpdate(getId()); // force update again to restart
307                         // update timer
308                         logger.debug("Command {} sent to {}", command, shd.findId());
309                     }
310                 }
311             }
312         } catch (Exception e) {
313             logger.warn("Handle command failed", e);
314         }
315     }
316
317     private static void getCapabilities(Map<String, List<SmartHomeCapability>> result, AccountHandler accountHandler,
318             SmartHomeBaseDevice device) {
319         if (device instanceof SmartHomeDevice) {
320             SmartHomeDevice shd = (SmartHomeDevice) device;
321             SmartHomeCapability[] capabilities = shd.capabilities;
322             if (capabilities == null) {
323                 return;
324             }
325             for (SmartHomeCapability capability : capabilities) {
326                 String interfaceName = capability.interfaceName;
327                 if (interfaceName != null) {
328                     result.computeIfAbsent(interfaceName, name -> new ArrayList<>()).add(capability);
329                 }
330             }
331         }
332         if (device instanceof SmartHomeGroup) {
333             for (SmartHomeDevice shd : getSupportedSmartHomeDevices(device,
334                     accountHandler.getLastKnownSmartHomeDevices())) {
335                 getCapabilities(result, accountHandler, shd);
336             }
337         }
338     }
339
340     public static Set<SmartHomeDevice> getSupportedSmartHomeDevices(@Nullable SmartHomeBaseDevice baseDevice,
341             List<SmartHomeBaseDevice> allDevices) {
342         if (baseDevice == null) {
343             return Collections.emptySet();
344         }
345         Set<SmartHomeDevice> result = new HashSet<>();
346         if (baseDevice instanceof SmartHomeDevice) {
347             SmartHomeDevice shd = (SmartHomeDevice) baseDevice;
348             SmartHomeCapability[] capabilities = shd.capabilities;
349             if (capabilities != null) {
350                 if (Arrays.stream(capabilities).map(capability -> capability.interfaceName)
351                         .anyMatch(SUPPORTED_INTERFACES::contains)) {
352                     result.add(shd);
353                 }
354             }
355         } else {
356             SmartHomeGroup shg = (SmartHomeGroup) baseDevice;
357             for (SmartHomeBaseDevice device : allDevices) {
358                 if (device instanceof SmartHomeDevice) {
359                     SmartHomeDevice shd = (SmartHomeDevice) device;
360                     if (shd.tags != null && shd.tags.tagNameToValueSetMap != null
361                             && shd.tags.tagNameToValueSetMap.groupIdentity != null
362                             && shg.applianceGroupIdentifier != null && shg.applianceGroupIdentifier.value != null
363                             && Arrays.asList(shd.tags.tagNameToValueSetMap.groupIdentity)
364                                     .contains(shg.applianceGroupIdentifier.value)) {
365                         SmartHomeCapability[] capabilities = shd.capabilities;
366                         if (capabilities != null) {
367                             if (Arrays.stream(capabilities).map(capability -> capability.interfaceName)
368                                     .anyMatch(SUPPORTED_INTERFACES::contains)) {
369                                 result.add(shd);
370                             }
371                         }
372                     }
373                 }
374             }
375         }
376         return result;
377     }
378
379     public @Nullable StateDescription findStateDescription(Channel channel, StateDescription originalStateDescription,
380             @Nullable Locale locale) {
381         String channelId = channel.getUID().getId();
382         for (HandlerBase handler : handlers.values()) {
383             if (handler != null && handler.hasChannel(channelId)) {
384                 return handler.findStateDescription(channelId, originalStateDescription, locale);
385             }
386         }
387         return null;
388     }
389 }