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, @Nullable HandlerBase> handlers = new HashMap<>();
74 private final Map<String, @Nullable 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, capabilities.get(interfaceName));
113 for (ChannelInfo channelInfo : required) {
114 unusedChannels.remove(channelInfo.channelId);
115 if (addChannelToDevice(thingBuilder, channelInfo.channelId, channelInfo.itemType,
116 channelInfo.channelTypeUID)) {
123 unusedHandlers.forEach(handlers::remove);
124 if (!unusedChannels.isEmpty()) {
126 unusedChannels.stream().map(id -> new ChannelUID(thing.getUID(), id)).forEach(thingBuilder::withoutChannel);
130 updateThing(thingBuilder.build());
131 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Thing has changed.");
132 accountHandler.forceDelayedSmartHomeStateUpdate(getId());
136 public String getId() {
137 String id = (String) getConfig().get(DEVICE_PROPERTY_ID);
145 public void updateState(String channelId, State state) {
146 super.updateState(new ChannelUID(thing.getUID(), channelId), state);
150 public void initialize() {
151 AccountHandler accountHandler = getAccountHandler();
152 if (accountHandler != null) {
153 accountHandler.addSmartHomeDeviceHandler(this);
154 setDeviceAndUpdateThingState(accountHandler, smartHomeBaseDevice);
156 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Bridgehandler not found");
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
168 // channel exist with other settings, remove it first
169 thingBuilder.withoutChannel(channel.getUID());
171 thingBuilder.withChannel(ChannelBuilder.create(new ChannelUID(thing.getUID(), channelId), itemType)
172 .withType(channelTypeUID).build());
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!");
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) {
194 if (states != null) {
196 if (smartHomeBaseDevice.isGroup()) {
197 // for groups, store the last state of all devices
198 lastStates.put(applianceId, states);
201 states = lastStates.get(applianceId);
202 if (states == null) {
206 if (firstDevice == null) {
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);
218 for (HandlerBase handlerBase : handlers.values()) {
219 if (handlerBase == null) {
222 UpdateChannelResult result = new UpdateChannelResult();
224 for (String interfaceName : handlerBase.getSupportedInterface()) {
225 List<JsonObject> stateList = mapInterfaceToStates.getOrDefault(interfaceName, Collections.emptyList());
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());
235 if (result.needSingleUpdate && smartHomeBaseDevice instanceof SmartHomeDevice && accountHandler != null) {
236 SmartHomeDevice shd = (SmartHomeDevice) smartHomeBaseDevice;
237 accountHandler.forceDelayedSmartHomeStateUpdate(shd.findId());
242 updateStatus(ThingStatus.ONLINE);
244 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "State not found");
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;
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());
267 Connection connection = accountHandler.findConnection();
268 if (connection == null) {
269 logger.debug("connection is null in {}", thing.getUID());
274 if (command instanceof RefreshType) {
275 accountHandler.forceDelayedSmartHomeStateUpdate(getId());
278 SmartHomeBaseDevice smartHomeBaseDevice = this.smartHomeBaseDevice;
279 if (smartHomeBaseDevice == null) {
280 logger.debug("smarthomeBaseDevice is null in {}", thing.getUID());
283 Set<SmartHomeDevice> devices = getSupportedSmartHomeDevices(smartHomeBaseDevice,
284 accountHandler.getLastKnownSmartHomeDevices());
285 String channelId = channelUID.getId();
287 for (String interfaceName : handlers.keySet()) {
288 HandlerBase handlerBase = handlers.get(interfaceName);
289 if (handlerBase == null || !handlerBase.hasChannel(channelId)) {
292 for (SmartHomeDevice shd : devices) {
293 String entityId = shd.entityId;
294 if (entityId == null) {
297 SmartHomeCapability[] capabilities = shd.capabilities;
298 if (capabilities == null) {
299 logger.debug("capabilities is null in {}", thing.getUID());
302 accountHandler.forceDelayedSmartHomeStateUpdate(getId()); // block updates
303 if (handlerBase.handleCommand(connection, shd, entityId, capabilities, channelUID.getId(),
305 accountHandler.forceDelayedSmartHomeStateUpdate(getId()); // force update again to restart
307 logger.debug("Command {} sent to {}", command, shd.findId());
311 } catch (Exception e) {
312 logger.warn("Handle command failed", e);
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) {
324 for (SmartHomeCapability capability : capabilities) {
325 String interfaceName = capability.interfaceName;
326 if (interfaceName != null) {
327 result.computeIfAbsent(interfaceName, name -> new ArrayList<>()).add(capability);
331 if (device instanceof SmartHomeGroup) {
332 for (SmartHomeDevice shd : getSupportedSmartHomeDevices(device,
333 accountHandler.getLastKnownSmartHomeDevices())) {
334 getCapabilities(result, accountHandler, shd);
339 public static Set<SmartHomeDevice> getSupportedSmartHomeDevices(@Nullable SmartHomeBaseDevice baseDevice,
340 List<SmartHomeBaseDevice> allDevices) {
341 if (baseDevice == null) {
342 return Collections.emptySet();
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)) {
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)) {
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);