]> git.basschouten.com Git - openhab-addons.git/blob
c2bba5ad79a73fd2857e7e6ded77cb6eeb465e1f
[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.Function;
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                 Function<SmartHomeDeviceHandler, HandlerBase> creator = Constants.HANDLER_FACTORY.get(interfaceName);
97                 if (creator != null) {
98                     handler = creator.apply(this);
99                     handlers.put(interfaceName, handler);
100                 }
101             }
102             if (handler != null) {
103                 Collection<ChannelInfo> required = handler
104                         .initialize(capabilities.getOrDefault(interfaceName, List.of()));
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                     accountHandler.forceDelayedSmartHomeStateUpdate(getId()); // block updates
293                     if (handlerBase.handleCommand(connection, shd, entityId, shd.getCapabilities(), channelUID.getId(),
294                             command)) {
295                         accountHandler.forceDelayedSmartHomeStateUpdate(getId()); // force update again to restart
296                         // update timer
297                         logger.debug("Command {} sent to {}", command, shd.findId());
298                     }
299                 }
300             }
301         } catch (Exception e) {
302             logger.warn("Handle command failed", e);
303         }
304     }
305
306     private static void getCapabilities(Map<String, List<SmartHomeCapability>> result, AccountHandler accountHandler,
307             SmartHomeBaseDevice device) {
308         if (device instanceof SmartHomeDevice) {
309             SmartHomeDevice shd = (SmartHomeDevice) device;
310             for (SmartHomeCapability capability : shd.getCapabilities()) {
311                 String interfaceName = capability.interfaceName;
312                 if (interfaceName != null) {
313                     Objects.requireNonNull(result.computeIfAbsent(interfaceName, name -> new ArrayList<>()))
314                             .add(capability);
315                 }
316             }
317         }
318         if (device instanceof SmartHomeGroup) {
319             for (SmartHomeDevice shd : getSupportedSmartHomeDevices(device,
320                     accountHandler.getLastKnownSmartHomeDevices())) {
321                 getCapabilities(result, accountHandler, shd);
322             }
323         }
324     }
325
326     public static Set<SmartHomeDevice> getSupportedSmartHomeDevices(@Nullable SmartHomeBaseDevice baseDevice,
327             List<SmartHomeBaseDevice> allDevices) {
328         if (baseDevice == null) {
329             return Collections.emptySet();
330         }
331         Set<SmartHomeDevice> result = new HashSet<>();
332         if (baseDevice instanceof SmartHomeDevice) {
333             SmartHomeDevice shd = (SmartHomeDevice) baseDevice;
334             if (shd.getCapabilities().stream().map(capability -> capability.interfaceName)
335                     .anyMatch(SUPPORTED_INTERFACES::contains)) {
336                 result.add(shd);
337
338             }
339         } else {
340             SmartHomeGroup shg = (SmartHomeGroup) baseDevice;
341             for (SmartHomeBaseDevice device : allDevices) {
342                 if (device instanceof SmartHomeDevice) {
343                     SmartHomeDevice shd = (SmartHomeDevice) device;
344                     JsonSmartHomeTags.JsonSmartHomeTag tags = shd.tags;
345                     if (tags != null) {
346                         JsonSmartHomeGroupIdentity.SmartHomeGroupIdentity tagNameToValueSetMap = tags.tagNameToValueSetMap;
347                         JsonSmartHomeGroupIdentifiers.SmartHomeGroupIdentifier applianceGroupIdentifier = shg.applianceGroupIdentifier;
348                         if (tagNameToValueSetMap != null) {
349                             List<String> groupIdentity = Objects.requireNonNullElse(tagNameToValueSetMap.groupIdentity,
350                                     List.of());
351                             if (applianceGroupIdentifier != null && applianceGroupIdentifier.value != null
352                                     && groupIdentity.contains(applianceGroupIdentifier.value)) {
353                                 if (shd.getCapabilities().stream().map(capability -> capability.interfaceName)
354                                         .anyMatch(SUPPORTED_INTERFACES::contains)) {
355                                     result.add(shd);
356                                 }
357                             }
358                         }
359                     }
360                 }
361             }
362         }
363         return result;
364     }
365
366     public @Nullable StateDescription findStateDescription(Channel channel, StateDescription originalStateDescription,
367             @Nullable Locale locale) {
368         String channelId = channel.getUID().getId();
369         for (HandlerBase handler : handlers.values()) {
370             if (handler.hasChannel(channelId)) {
371                 return handler.findStateDescription(channelId, originalStateDescription, locale);
372             }
373         }
374         return null;
375     }
376 }