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;
19 import java.util.function.Supplier;
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;
53 import com.google.gson.*;
56 * @author Lukas Knoeller - Initial contribution
59 public class SmartHomeDeviceHandler extends BaseThingHandler {
60 private final Logger logger = LoggerFactory.getLogger(SmartHomeDeviceHandler.class);
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<>();
67 public SmartHomeDeviceHandler(Thing thing, Gson gson) {
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");
78 boolean changed = this.smartHomeBaseDevice == null;
79 this.smartHomeBaseDevice = smartHomeBaseDevice;
81 Set<String> unusedChannels = new HashSet<>();
82 thing.getChannels().forEach(channel -> unusedChannels.add(channel.getUID().getId()));
84 Set<String> unusedHandlers = new HashSet<>(handlers.keySet());
86 Map<String, List<SmartHomeCapability>> capabilities = new HashMap<>();
87 getCapabilities(capabilities, accountHandler, smartHomeBaseDevice);
89 ThingBuilder thingBuilder = editThing();
91 for (String interfaceName : capabilities.keySet()) {
92 HandlerBase handler = handlers.get(interfaceName);
93 if (handler != null) {
94 unusedHandlers.remove(interfaceName);
96 Supplier<HandlerBase> creator = Constants.HANDLER_FACTORY.get(interfaceName);
97 if (creator != null) {
98 handler = creator.get();
99 handlers.put(interfaceName, handler);
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)) {
115 unusedHandlers.forEach(handlers::remove);
116 if (!unusedChannels.isEmpty()) {
118 unusedChannels.stream().map(id -> new ChannelUID(thing.getUID(), id)).forEach(thingBuilder::withoutChannel);
122 updateThing(thingBuilder.build());
123 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Thing has changed.");
124 accountHandler.forceDelayedSmartHomeStateUpdate(getId());
128 public String getId() {
129 String id = (String) getConfig().get(DEVICE_PROPERTY_ID);
137 public void updateState(String channelId, State state) {
138 super.updateState(new ChannelUID(thing.getUID(), channelId), state);
142 public void initialize() {
143 AccountHandler accountHandler = getAccountHandler();
144 if (accountHandler != null) {
145 accountHandler.addSmartHomeDeviceHandler(this);
146 setDeviceAndUpdateThingState(accountHandler, smartHomeBaseDevice);
148 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Bridgehandler not found");
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
160 // channel exist with other settings, remove it first
161 thingBuilder.withoutChannel(channel.getUID());
163 thingBuilder.withChannel(ChannelBuilder.create(new ChannelUID(thing.getUID(), channelId), itemType)
164 .withType(channelTypeUID).build());
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!");
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) {
186 JsonArray states = applianceIdToCapabilityStates.get(applianceId);
187 if (states != null) {
189 if (smartHomeBaseDevice.isGroup()) {
190 // for groups, store the last state of all devices
191 lastStates.put(applianceId, states);
194 states = lastStates.get(applianceId);
195 if (states == null) {
199 if (firstDevice == null) {
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)
208 Objects.requireNonNull(mapInterfaceToStates.computeIfAbsent(interfaceName, k -> new ArrayList<>()))
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) {
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());
230 if (result.needSingleUpdate && smartHomeBaseDevice instanceof SmartHomeDevice && accountHandler != null) {
231 SmartHomeDevice shd = (SmartHomeDevice) smartHomeBaseDevice;
232 accountHandler.forceDelayedSmartHomeStateUpdate(shd.findId());
237 updateStatus(ThingStatus.ONLINE);
239 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "State not found");
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;
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());
262 Connection connection = accountHandler.findConnection();
263 if (connection == null) {
264 logger.debug("connection is null in {}", thing.getUID());
269 if (command instanceof RefreshType) {
270 accountHandler.forceDelayedSmartHomeStateUpdate(getId());
273 SmartHomeBaseDevice smartHomeBaseDevice = this.smartHomeBaseDevice;
274 if (smartHomeBaseDevice == null) {
275 logger.debug("smarthomeBaseDevice is null in {}", thing.getUID());
278 Set<SmartHomeDevice> devices = getSupportedSmartHomeDevices(smartHomeBaseDevice,
279 accountHandler.getLastKnownSmartHomeDevices());
280 String channelId = channelUID.getId();
282 for (String interfaceName : handlers.keySet()) {
283 HandlerBase handlerBase = handlers.get(interfaceName);
284 if (handlerBase == null || !handlerBase.hasChannel(channelId)) {
287 for (SmartHomeDevice shd : devices) {
288 String entityId = shd.entityId;
289 if (entityId == null) {
292 SmartHomeCapability[] capabilities = shd.capabilities;
293 if (capabilities == null) {
294 logger.debug("capabilities is null in {}", thing.getUID());
297 accountHandler.forceDelayedSmartHomeStateUpdate(getId()); // block updates
298 if (handlerBase.handleCommand(connection, shd, entityId, capabilities, channelUID.getId(),
300 accountHandler.forceDelayedSmartHomeStateUpdate(getId()); // force update again to restart
302 logger.debug("Command {} sent to {}", command, shd.findId());
306 } catch (Exception e) {
307 logger.warn("Handle command failed", e);
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) {
319 for (SmartHomeCapability capability : capabilities) {
320 String interfaceName = capability.interfaceName;
321 if (interfaceName != null) {
322 Objects.requireNonNull(result.computeIfAbsent(interfaceName, name -> new ArrayList<>()))
327 if (device instanceof SmartHomeGroup) {
328 for (SmartHomeDevice shd : getSupportedSmartHomeDevices(device,
329 accountHandler.getLastKnownSmartHomeDevices())) {
330 getCapabilities(result, accountHandler, shd);
335 public static Set<SmartHomeDevice> getSupportedSmartHomeDevices(@Nullable SmartHomeBaseDevice baseDevice,
336 List<SmartHomeBaseDevice> allDevices) {
337 if (baseDevice == null) {
338 return Collections.emptySet();
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)) {
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;
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)) {
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);