2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.amazonechocontrol.internal.handler;
15 import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.DEVICE_PROPERTY_ID;
16 import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.SUPPORTED_INTERFACES;
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;
28 import java.util.function.Supplier;
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;
59 import com.google.gson.Gson;
60 import com.google.gson.JsonArray;
61 import com.google.gson.JsonElement;
62 import com.google.gson.JsonObject;
65 * @author Lukas Knoeller - Initial contribution
68 public class SmartHomeDeviceHandler extends BaseThingHandler {
69 private final Logger logger = LoggerFactory.getLogger(SmartHomeDeviceHandler.class);
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<>();
76 public SmartHomeDeviceHandler(Thing thing, Gson gson) {
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");
87 boolean changed = this.smartHomeBaseDevice == null;
88 this.smartHomeBaseDevice = smartHomeBaseDevice;
90 Set<String> unusedChannels = new HashSet<>();
91 thing.getChannels().forEach(channel -> unusedChannels.add(channel.getUID().getId()));
93 Set<String> unusedHandlers = new HashSet<>(handlers.keySet());
95 Map<String, List<SmartHomeCapability>> capabilities = new HashMap<>();
96 getCapabilities(capabilities, accountHandler, smartHomeBaseDevice);
98 ThingBuilder thingBuilder = editThing();
100 for (String interfaceName : capabilities.keySet()) {
101 HandlerBase handler = handlers.get(interfaceName);
102 if (handler != null) {
103 unusedHandlers.remove(interfaceName);
105 Supplier<HandlerBase> creator = Constants.HANDLER_FACTORY.get(interfaceName);
106 if (creator != null) {
107 handler = creator.get();
108 handlers.put(interfaceName, handler);
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)) {
124 unusedHandlers.forEach(handlers::remove);
125 if (!unusedChannels.isEmpty()) {
127 unusedChannels.stream().map(id -> new ChannelUID(thing.getUID(), id)).forEach(thingBuilder::withoutChannel);
131 updateThing(thingBuilder.build());
132 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Thing has changed.");
133 accountHandler.forceDelayedSmartHomeStateUpdate(getId());
137 public String getId() {
138 String id = (String) getConfig().get(DEVICE_PROPERTY_ID);
146 public void updateState(String channelId, State state) {
147 super.updateState(new ChannelUID(thing.getUID(), channelId), state);
151 public void initialize() {
152 AccountHandler accountHandler = getAccountHandler();
153 if (accountHandler != null) {
154 accountHandler.addSmartHomeDeviceHandler(this);
155 setDeviceAndUpdateThingState(accountHandler, smartHomeBaseDevice);
157 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Bridgehandler not found");
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
169 // channel exist with other settings, remove it first
170 thingBuilder.withoutChannel(channel.getUID());
172 thingBuilder.withChannel(ChannelBuilder.create(new ChannelUID(thing.getUID(), channelId), itemType)
173 .withType(channelTypeUID).build());
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!");
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) {
195 if (states != null) {
197 if (smartHomeBaseDevice.isGroup()) {
198 // for groups, store the last state of all devices
199 lastStates.put(applianceId, states);
202 states = lastStates.get(applianceId);
203 if (states == null) {
207 if (firstDevice == null) {
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);
219 for (HandlerBase handlerBase : handlers.values()) {
220 if (handlerBase == null) {
223 UpdateChannelResult result = new UpdateChannelResult();
225 for (String interfaceName : handlerBase.getSupportedInterface()) {
226 List<JsonObject> stateList = mapInterfaceToStates.getOrDefault(interfaceName, Collections.emptyList());
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());
236 if (result.needSingleUpdate && smartHomeBaseDevice instanceof SmartHomeDevice && accountHandler != null) {
237 SmartHomeDevice shd = (SmartHomeDevice) smartHomeBaseDevice;
238 accountHandler.forceDelayedSmartHomeStateUpdate(shd.findId());
243 updateStatus(ThingStatus.ONLINE);
245 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "State not found");
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;
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());
268 Connection connection = accountHandler.findConnection();
269 if (connection == null) {
270 logger.debug("connection is null in {}", thing.getUID());
275 if (command instanceof RefreshType) {
276 accountHandler.forceDelayedSmartHomeStateUpdate(getId());
279 SmartHomeBaseDevice smartHomeBaseDevice = this.smartHomeBaseDevice;
280 if (smartHomeBaseDevice == null) {
281 logger.debug("smarthomeBaseDevice is null in {}", thing.getUID());
284 Set<SmartHomeDevice> devices = getSupportedSmartHomeDevices(smartHomeBaseDevice,
285 accountHandler.getLastKnownSmartHomeDevices());
286 String channelId = channelUID.getId();
288 for (String interfaceName : handlers.keySet()) {
289 HandlerBase handlerBase = handlers.get(interfaceName);
290 if (handlerBase == null || !handlerBase.hasChannel(channelId)) {
293 for (SmartHomeDevice shd : devices) {
294 String entityId = shd.entityId;
295 if (entityId == null) {
298 SmartHomeCapability[] capabilities = shd.capabilities;
299 if (capabilities == null) {
300 logger.debug("capabilities is null in {}", thing.getUID());
303 accountHandler.forceDelayedSmartHomeStateUpdate(getId()); // block updates
304 if (handlerBase.handleCommand(connection, shd, entityId, capabilities, channelUID.getId(),
306 accountHandler.forceDelayedSmartHomeStateUpdate(getId()); // force update again to restart
308 logger.debug("Command {} sent to {}", command, shd.findId());
312 } catch (Exception e) {
313 logger.warn("Handle command failed", e);
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) {
325 for (SmartHomeCapability capability : capabilities) {
326 String interfaceName = capability.interfaceName;
327 if (interfaceName != null) {
328 result.computeIfAbsent(interfaceName, name -> new ArrayList<>()).add(capability);
332 if (device instanceof SmartHomeGroup) {
333 for (SmartHomeDevice shd : getSupportedSmartHomeDevices(device,
334 accountHandler.getLastKnownSmartHomeDevices())) {
335 getCapabilities(result, accountHandler, shd);
340 public static Set<SmartHomeDevice> getSupportedSmartHomeDevices(@Nullable SmartHomeBaseDevice baseDevice,
341 List<SmartHomeBaseDevice> allDevices) {
342 if (baseDevice == null) {
343 return Collections.emptySet();
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)) {
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)) {
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);