2 * Copyright (c) 2010-2021 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.Function;
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 Function<SmartHomeDeviceHandler, HandlerBase> creator = Constants.HANDLER_FACTORY.get(interfaceName);
97 if (creator != null) {
98 handler = creator.apply(this);
99 handlers.put(interfaceName, handler);
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)) {
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 accountHandler.forceDelayedSmartHomeStateUpdate(getId()); // block updates
293 if (handlerBase.handleCommand(connection, shd, entityId, shd.getCapabilities(), channelUID.getId(),
295 accountHandler.forceDelayedSmartHomeStateUpdate(getId()); // force update again to restart
297 logger.debug("Command {} sent to {}", command, shd.findId());
301 } catch (Exception e) {
302 logger.warn("Handle command failed", e);
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<>()))
318 if (device instanceof SmartHomeGroup) {
319 for (SmartHomeDevice shd : getSupportedSmartHomeDevices(device,
320 accountHandler.getLastKnownSmartHomeDevices())) {
321 getCapabilities(result, accountHandler, shd);
326 public static Set<SmartHomeDevice> getSupportedSmartHomeDevices(@Nullable SmartHomeBaseDevice baseDevice,
327 List<SmartHomeBaseDevice> allDevices) {
328 if (baseDevice == null) {
329 return Collections.emptySet();
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)) {
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;
346 JsonSmartHomeGroupIdentity.SmartHomeGroupIdentity tagNameToValueSetMap = tags.tagNameToValueSetMap;
347 JsonSmartHomeGroupIdentifiers.SmartHomeGroupIdentifier applianceGroupIdentifier = shg.applianceGroupIdentifier;
348 if (tagNameToValueSetMap != null) {
349 List<String> groupIdentity = Objects.requireNonNullElse(tagNameToValueSetMap.groupIdentity,
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)) {
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);