]> git.basschouten.com Git - openhab-addons.git/blob
c9b7900ab303b556c026c659f5e971f7a30f35a5
[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, @Nullable HandlerBase> handlers = new HashMap<>();
74     private final Map<String, @Nullable 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, capabilities.get(interfaceName));
113                 for (ChannelInfo channelInfo : required) {
114                     unusedChannels.remove(channelInfo.channelId);
115                     if (addChannelToDevice(thingBuilder, channelInfo.channelId, channelInfo.itemType,
116                             channelInfo.channelTypeUID)) {
117                         changed = true;
118                     }
119                 }
120             }
121         }
122
123         unusedHandlers.forEach(handlers::remove);
124         if (!unusedChannels.isEmpty()) {
125             changed = true;
126             unusedChannels.stream().map(id -> new ChannelUID(thing.getUID(), id)).forEach(thingBuilder::withoutChannel);
127         }
128
129         if (changed) {
130             updateThing(thingBuilder.build());
131             updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Thing has changed.");
132             accountHandler.forceDelayedSmartHomeStateUpdate(getId());
133         }
134     }
135
136     public String getId() {
137         String id = (String) getConfig().get(DEVICE_PROPERTY_ID);
138         if (id == null) {
139             return "";
140         }
141         return id;
142     }
143
144     @Override
145     public void updateState(String channelId, State state) {
146         super.updateState(new ChannelUID(thing.getUID(), channelId), state);
147     }
148
149     @Override
150     public void initialize() {
151         AccountHandler accountHandler = getAccountHandler();
152         if (accountHandler != null) {
153             accountHandler.addSmartHomeDeviceHandler(this);
154             setDeviceAndUpdateThingState(accountHandler, smartHomeBaseDevice);
155         } else {
156             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Bridgehandler not found");
157         }
158     }
159
160     private boolean addChannelToDevice(ThingBuilder thingBuilder, String channelId, String itemType,
161             ChannelTypeUID channelTypeUID) {
162         Channel channel = thing.getChannel(channelId);
163         if (channel != null) {
164             if (channelTypeUID.equals(channel.getChannelTypeUID()) && itemType.equals(channel.getAcceptedItemType())) {
165                 // channel exist with the same settings
166                 return false;
167             }
168             // channel exist with other settings, remove it first
169             thingBuilder.withoutChannel(channel.getUID());
170         }
171         thingBuilder.withChannel(ChannelBuilder.create(new ChannelUID(thing.getUID(), channelId), itemType)
172                 .withType(channelTypeUID).build());
173         return true;
174     }
175
176     public void updateChannelStates(List<SmartHomeBaseDevice> allDevices,
177             Map<String, JsonArray> applianceIdToCapabilityStates) {
178         AccountHandler accountHandler = getAccountHandler();
179         SmartHomeBaseDevice smartHomeBaseDevice = this.smartHomeBaseDevice;
180         if (smartHomeBaseDevice == null) {
181             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Can't find smarthomeBaseDevice!");
182             return;
183         }
184
185         boolean stateFound = false;
186         Map<String, List<JsonObject>> mapInterfaceToStates = new HashMap<>();
187         SmartHomeDevice firstDevice = null;
188         for (SmartHomeDevice shd : getSupportedSmartHomeDevices(smartHomeBaseDevice, allDevices)) {
189             JsonArray states = applianceIdToCapabilityStates.get(shd.applianceId);
190             String applianceId = shd.applianceId;
191             if (applianceId == null) {
192                 continue;
193             }
194             if (states != null) {
195                 stateFound = true;
196                 if (smartHomeBaseDevice.isGroup()) {
197                     // for groups, store the last state of all devices
198                     lastStates.put(applianceId, states);
199                 }
200             } else {
201                 states = lastStates.get(applianceId);
202                 if (states == null) {
203                     continue;
204                 }
205             }
206             if (firstDevice == null) {
207                 firstDevice = shd;
208             }
209             for (JsonElement stateElement : states) {
210                 String stateJson = stateElement.getAsString();
211                 if (stateJson.startsWith("{") && stateJson.endsWith("}")) {
212                     JsonObject state = gson.fromJson(stateJson, JsonObject.class);
213                     String interfaceName = state.get("namespace").getAsString();
214                     mapInterfaceToStates.computeIfAbsent(interfaceName, k -> new ArrayList<>()).add(state);
215                 }
216             }
217         }
218         for (HandlerBase handlerBase : handlers.values()) {
219             if (handlerBase == null) {
220                 continue;
221             }
222             UpdateChannelResult result = new UpdateChannelResult();
223
224             for (String interfaceName : handlerBase.getSupportedInterface()) {
225                 List<JsonObject> stateList = mapInterfaceToStates.getOrDefault(interfaceName, Collections.emptyList());
226                 try {
227                     handlerBase.updateChannels(interfaceName, stateList, result);
228                 } catch (Exception e) {
229                     // We catch all exceptions, otherwise all other things are not updated!
230                     logger.debug("Updating states failed", e);
231                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage());
232                 }
233             }
234
235             if (result.needSingleUpdate && smartHomeBaseDevice instanceof SmartHomeDevice && accountHandler != null) {
236                 SmartHomeDevice shd = (SmartHomeDevice) smartHomeBaseDevice;
237                 accountHandler.forceDelayedSmartHomeStateUpdate(shd.findId());
238             }
239         }
240
241         if (stateFound) {
242             updateStatus(ThingStatus.ONLINE);
243         } else {
244             updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "State not found");
245         }
246     }
247
248     private @Nullable AccountHandler getAccountHandler() {
249         Bridge bridge = getBridge();
250         if (bridge != null) {
251             BridgeHandler bridgeHandler = bridge.getHandler();
252             if (bridgeHandler instanceof AccountHandler) {
253                 return (AccountHandler) bridgeHandler;
254             }
255         }
256
257         return null;
258     }
259
260     @Override
261     public void handleCommand(ChannelUID channelUID, Command command) {
262         AccountHandler accountHandler = getAccountHandler();
263         if (accountHandler == null) {
264             logger.debug("accountHandler is null in {}", thing.getUID());
265             return;
266         }
267         Connection connection = accountHandler.findConnection();
268         if (connection == null) {
269             logger.debug("connection is null in {}", thing.getUID());
270             return;
271         }
272
273         try {
274             if (command instanceof RefreshType) {
275                 accountHandler.forceDelayedSmartHomeStateUpdate(getId());
276                 return;
277             }
278             SmartHomeBaseDevice smartHomeBaseDevice = this.smartHomeBaseDevice;
279             if (smartHomeBaseDevice == null) {
280                 logger.debug("smarthomeBaseDevice is null in {}", thing.getUID());
281                 return;
282             }
283             Set<SmartHomeDevice> devices = getSupportedSmartHomeDevices(smartHomeBaseDevice,
284                     accountHandler.getLastKnownSmartHomeDevices());
285             String channelId = channelUID.getId();
286
287             for (String interfaceName : handlers.keySet()) {
288                 HandlerBase handlerBase = handlers.get(interfaceName);
289                 if (handlerBase == null || !handlerBase.hasChannel(channelId)) {
290                     continue;
291                 }
292                 for (SmartHomeDevice shd : devices) {
293                     String entityId = shd.entityId;
294                     if (entityId == null) {
295                         continue;
296                     }
297                     SmartHomeCapability[] capabilities = shd.capabilities;
298                     if (capabilities == null) {
299                         logger.debug("capabilities is null in {}", thing.getUID());
300                         return;
301                     }
302                     accountHandler.forceDelayedSmartHomeStateUpdate(getId()); // block updates
303                     if (handlerBase.handleCommand(connection, shd, entityId, capabilities, channelUID.getId(),
304                             command)) {
305                         accountHandler.forceDelayedSmartHomeStateUpdate(getId()); // force update again to restart
306                         // update timer
307                         logger.debug("Command {} sent to {}", command, shd.findId());
308                     }
309                 }
310             }
311         } catch (Exception e) {
312             logger.warn("Handle command failed", e);
313         }
314     }
315
316     private static void getCapabilities(Map<String, List<SmartHomeCapability>> result, AccountHandler accountHandler,
317             SmartHomeBaseDevice device) {
318         if (device instanceof SmartHomeDevice) {
319             SmartHomeDevice shd = (SmartHomeDevice) device;
320             SmartHomeCapability[] capabilities = shd.capabilities;
321             if (capabilities == null) {
322                 return;
323             }
324             for (SmartHomeCapability capability : capabilities) {
325                 String interfaceName = capability.interfaceName;
326                 if (interfaceName != null) {
327                     result.computeIfAbsent(interfaceName, name -> new ArrayList<>()).add(capability);
328                 }
329             }
330         }
331         if (device instanceof SmartHomeGroup) {
332             for (SmartHomeDevice shd : getSupportedSmartHomeDevices(device,
333                     accountHandler.getLastKnownSmartHomeDevices())) {
334                 getCapabilities(result, accountHandler, shd);
335             }
336         }
337     }
338
339     public static Set<SmartHomeDevice> getSupportedSmartHomeDevices(@Nullable SmartHomeBaseDevice baseDevice,
340             List<SmartHomeBaseDevice> allDevices) {
341         if (baseDevice == null) {
342             return Collections.emptySet();
343         }
344         Set<SmartHomeDevice> result = new HashSet<>();
345         if (baseDevice instanceof SmartHomeDevice) {
346             SmartHomeDevice shd = (SmartHomeDevice) baseDevice;
347             SmartHomeCapability[] capabilities = shd.capabilities;
348             if (capabilities != null) {
349                 if (Arrays.stream(capabilities).map(capability -> capability.interfaceName)
350                         .anyMatch(SUPPORTED_INTERFACES::contains)) {
351                     result.add(shd);
352                 }
353             }
354         } else {
355             SmartHomeGroup shg = (SmartHomeGroup) baseDevice;
356             for (SmartHomeBaseDevice device : allDevices) {
357                 if (device instanceof SmartHomeDevice) {
358                     SmartHomeDevice shd = (SmartHomeDevice) device;
359                     if (shd.tags != null && shd.tags.tagNameToValueSetMap != null
360                             && shd.tags.tagNameToValueSetMap.groupIdentity != null
361                             && shg.applianceGroupIdentifier != null && shg.applianceGroupIdentifier.value != null
362                             && Arrays.asList(shd.tags.tagNameToValueSetMap.groupIdentity)
363                                     .contains(shg.applianceGroupIdentifier.value)) {
364                         SmartHomeCapability[] capabilities = shd.capabilities;
365                         if (capabilities != null) {
366                             if (Arrays.stream(capabilities).map(capability -> capability.interfaceName)
367                                     .anyMatch(SUPPORTED_INTERFACES::contains)) {
368                                 result.add(shd);
369                             }
370                         }
371                     }
372                 }
373             }
374         }
375         return result;
376     }
377
378     public @Nullable StateDescription findStateDescription(Channel channel, StateDescription originalStateDescription,
379             @Nullable Locale locale) {
380         String channelId = channel.getUID().getId();
381         for (HandlerBase handler : handlers.values()) {
382             if (handler != null && handler.hasChannel(channelId)) {
383                 return handler.findStateDescription(channelId, originalStateDescription, locale);
384             }
385         }
386         return null;
387     }
388 }