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