diff --git a/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/client/InnogyClient.java b/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/client/InnogyClient.java index d319e2bfa..3a841962a 100644 --- a/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/client/InnogyClient.java +++ b/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/client/InnogyClient.java @@ -12,19 +12,15 @@ */ package org.openhab.binding.innogysmarthome.internal.client; -import static org.openhab.binding.innogysmarthome.internal.InnogyBindingConstants.*; import static org.openhab.binding.innogysmarthome.internal.client.Constants.*; import java.io.IOException; import java.net.URI; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; import org.apache.commons.lang.StringUtils; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -36,7 +32,6 @@ import org.eclipse.jetty.client.util.StringContentProvider; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; -import org.openhab.binding.innogysmarthome.internal.InnogyBindingConstants; import org.openhab.binding.innogysmarthome.internal.client.entity.StatusResponse; import org.openhab.binding.innogysmarthome.internal.client.entity.action.Action; import org.openhab.binding.innogysmarthome.internal.client.entity.action.ShutterAction; @@ -48,7 +43,6 @@ import org.openhab.binding.innogysmarthome.internal.client.entity.device.DeviceS import org.openhab.binding.innogysmarthome.internal.client.entity.device.Gateway; import org.openhab.binding.innogysmarthome.internal.client.entity.device.State; import org.openhab.binding.innogysmarthome.internal.client.entity.error.ErrorResponse; -import org.openhab.binding.innogysmarthome.internal.client.entity.link.Link; import org.openhab.binding.innogysmarthome.internal.client.entity.location.Location; import org.openhab.binding.innogysmarthome.internal.client.entity.message.Message; import org.openhab.binding.innogysmarthome.internal.client.exception.ApiException; @@ -103,13 +97,6 @@ public class InnogyClient { this.httpClient = httpClient; } - /** - * @return the bridgeInfo - */ - public @Nullable Gateway getBridgeDetails() { - return bridgeDetails; - } - /** * Gets the status * @@ -117,8 +104,6 @@ public class InnogyClient { * the {@link #configVersion} is set. * * @throws SessionExistsException thrown, if a session already exists - * @throws IOException - * @throws ApiException */ public void refreshStatus() throws IOException, ApiException, AuthenticationException { logger.debug("Get innogy SmartHome status..."); @@ -133,12 +118,9 @@ public class InnogyClient { /** * Executes a HTTP GET request with default headers and returns data as object of type T. * - * @param url + * @param url request URL * @param clazz type of data to return - * @return - * @throws IOException - * @throws AuthenticationException - * @throws ApiException + * @return response content */ private T executeGet(final String url, final Class clazz) throws IOException, AuthenticationException, ApiException { @@ -150,11 +132,9 @@ public class InnogyClient { /** * Executes a HTTP GET request with default headers and returns data as List of type T. * - * @param url + * @param url request URL * @param clazz array type of data to return as list - * @throws IOException - * @throws AuthenticationException - * @throws ApiException + * @return response content (as a List) */ private List executeGetList(final String url, final Class clazz) throws IOException, AuthenticationException, ApiException { @@ -164,19 +144,15 @@ public class InnogyClient { /** * Executes a HTTP POST request with the given {@link Action} as content. * - * @param url - * @param action - * @return - * @throws IOException - * @throws AuthenticationException - * @throws ApiException + * @param url request URL + * @param action action to execute */ - private ContentResponse executePost(final String url, final Action action) + private void executePost(final String url, final Action action) throws IOException, AuthenticationException, ApiException { final String json = gson.toJson(action); logger.debug("Action {} JSON: {}", action.getType(), json); - return request(httpClient.newRequest(url).method(HttpMethod.POST) + request(httpClient.newRequest(url).method(HttpMethod.POST) .content(new StringContentProvider(json), CONTENT_TYPE).accept(CONTENT_TYPE)); } @@ -197,6 +173,7 @@ public class InnogyClient { } public AccessTokenResponse getAccessTokenResponse() throws AuthenticationException, IOException { + @Nullable final AccessTokenResponse accessTokenResponse; try { accessTokenResponse = oAuthService.getAccessTokenResponse(); @@ -212,17 +189,11 @@ public class InnogyClient { /** * Handles errors from the {@link ContentResponse} and throws the following errors: * - * @param response + * @param response response * @param uri uri of api call made - * @throws SessionExistsException - * @throws SessionNotFoundException * @throws ControllerOfflineException thrown, if the innogy SmartHome controller (SHC) is offline. - * @throws IOException - * @throws ApiException - * @throws AuthenticationException */ - private void handleResponseErrors(final ContentResponse response, final URI uri) - throws IOException, ApiException, AuthenticationException { + private void handleResponseErrors(final ContentResponse response, final URI uri) throws IOException, ApiException { String content = ""; switch (response.getStatus()) { @@ -275,11 +246,6 @@ public class InnogyClient { /** * Sets a new state of a SwitchActuator. - * - * @param capabilityId - * @param state - * @throws IOException - * @throws ApiException */ public void setSwitchActuatorState(final String capabilityId, final boolean state) throws IOException, ApiException, AuthenticationException { @@ -288,11 +254,6 @@ public class InnogyClient { /** * Sets the dimmer level of a DimmerActuator. - * - * @param capabilityId - * @param dimLevel - * @throws IOException - * @throws ApiException */ public void setDimmerActuatorState(final String capabilityId, final int dimLevel) throws IOException, ApiException, AuthenticationException { @@ -301,12 +262,6 @@ public class InnogyClient { /** * Sets the roller shutter level of a RollerShutterActuator. - * - * @param capabilityId - * @param rollerShutterLevel - * @throws IOException - * @throws ApiException - * @throws AuthenticationException */ public void setRollerShutterActuatorState(final String capabilityId, final int rollerShutterLevel) throws IOException, ApiException, AuthenticationException { @@ -316,12 +271,6 @@ public class InnogyClient { /** * Starts or stops moving a RollerShutterActuator - * - * @param capabilityId - * @param rollerShutterAction - * @throws IOException - * @throws ApiException - * @throws AuthenticationException */ public void setRollerShutterAction(final String capabilityId, final ShutterAction.ShutterActions rollerShutterAction) @@ -331,11 +280,6 @@ public class InnogyClient { /** * Sets a new state of a VariableActuator. - * - * @param capabilityId - * @param state - * @throws IOException - * @throws ApiException */ public void setVariableActuatorState(final String capabilityId, final boolean state) throws IOException, ApiException, AuthenticationException { @@ -344,11 +288,6 @@ public class InnogyClient { /** * Sets the point temperature. - * - * @param capabilityId - * @param pointTemperature - * @throws IOException - * @throws ApiException */ public void setPointTemperatureState(final String capabilityId, final double pointTemperature) throws IOException, ApiException, AuthenticationException { @@ -358,11 +297,6 @@ public class InnogyClient { /** * Sets the operation mode to "Auto" or "Manu". - * - * @param capabilityId - * @param autoMode - * @throws IOException - * @throws ApiException */ public void setOperationMode(final String capabilityId, final boolean autoMode) throws IOException, ApiException, AuthenticationException { @@ -374,11 +308,6 @@ public class InnogyClient { /** * Sets the alarm state. - * - * @param capabilityId - * @param alarmState - * @throws IOException - * @throws ApiException */ public void setAlarmActuatorState(final String capabilityId, final boolean alarmState) throws IOException, ApiException, AuthenticationException { @@ -388,222 +317,26 @@ public class InnogyClient { /** * Load the device and returns a {@link List} of {@link Device}s.. * + * @param deviceIds Ids of the devices to return * @return List of Devices - * @throws IOException - * @throws ApiException */ - public List getDevices() throws IOException, ApiException, AuthenticationException { + public List getDevices(Collection deviceIds) + throws IOException, ApiException, AuthenticationException { logger.debug("Loading innogy devices..."); - return executeGetList(API_URL_DEVICE, Device[].class); + List devices = executeGetList(API_URL_DEVICE, Device[].class); + return devices.stream().filter(d -> deviceIds.contains(d.getId())).collect(Collectors.toList()); } /** * Loads the {@link Device} with the given deviceId. - * - * @param deviceId - * @return - * @throws IOException - * @throws ApiException */ public Device getDeviceById(final String deviceId) throws IOException, ApiException, AuthenticationException { logger.debug("Loading device with id {}...", deviceId); return executeGet(API_URL_DEVICE_ID.replace("{id}", deviceId), Device.class); } - /** - * Returns a {@link List} of all {@link Device}s with the full configuration details, {@link Capability}s and - * states. Calling this may take a while... - * - * @return - * @throws IOException - * @throws ApiException - */ - public List getFullDevices() throws IOException, ApiException, AuthenticationException { - // LOCATIONS - final List locationList = getLocations(); - final Map locationMap = new HashMap<>(); - for (final Location l : locationList) { - locationMap.put(l.getId(), l); - } - - // CAPABILITIES - final List capabilityList = getCapabilities(); - final Map capabilityMap = new HashMap<>(); - for (final Capability c : capabilityList) { - capabilityMap.put(c.getId(), c); - } - - // CAPABILITY STATES - final List capabilityStateList = getCapabilityStates(); - final Map capabilityStateMap = new HashMap<>(); - for (final CapabilityState cs : capabilityStateList) { - capabilityStateMap.put(cs.getId(), cs); - } - - // DEVICE STATES - final List deviceStateList = getDeviceStates(); - final Map deviceStateMap = new HashMap<>(); - for (final DeviceState es : deviceStateList) { - deviceStateMap.put(es.getId(), es); - } - - // MESSAGES - final List messageList = getMessages(); - final Map> deviceMessageMap = new HashMap<>(); - for (final Message m : messageList) { - if (m.getDevices() != null && !m.getDevices().isEmpty()) { - final String deviceId = m.getDevices().get(0).replace("/device/", ""); - List ml; - if (deviceMessageMap.containsKey(deviceId)) { - ml = deviceMessageMap.get(deviceId); - } else { - ml = new ArrayList<>(); - } - ml.add(m); - deviceMessageMap.put(deviceId, ml); - } - } - - // DEVICES - final List deviceList = getDevices(); - for (final Device d : deviceList) { - if (InnogyBindingConstants.BATTERY_POWERED_DEVICES.contains(d.getType())) { - d.setIsBatteryPowered(true); - } - - // location - d.setLocation(locationMap.get(d.getLocationId())); - final HashMap deviceCapabilityMap = new HashMap<>(); - - // capabilities and their states - for (final String cl : d.getCapabilityLinkList()) { - final Capability c = capabilityMap.get(Link.getId(cl)); - final String capabilityId = c.getId(); - final CapabilityState capabilityState = capabilityStateMap.get(capabilityId); - c.setCapabilityState(capabilityState); - deviceCapabilityMap.put(capabilityId, c); - } - d.setCapabilityMap(deviceCapabilityMap); - - // device states - d.setDeviceState(deviceStateMap.get(d.getId())); - - // messages - if (deviceMessageMap.containsKey(d.getId())) { - d.setMessageList(deviceMessageMap.get(d.getId())); - for (final Message m : d.getMessageList()) { - switch (m.getType()) { - case Message.TYPE_DEVICE_LOW_BATTERY: - d.setLowBattery(true); - d.setLowBatteryMessageId(m.getId()); - break; - } - } - } - } - - return deviceList; - } - - /** - * Returns the {@link Device} with the given deviceId with full configuration details, {@link Capability}s and - * states. Calling this may take a little bit longer... - * - * @param deviceId - * @return - * @throws IOException - * @throws ApiException - */ - public Device getFullDeviceById(final String deviceId) throws IOException, ApiException, AuthenticationException { - // LOCATIONS - final List locationList = getLocations(); - final Map locationMap = new HashMap<>(); - for (final Location l : locationList) { - locationMap.put(l.getId(), l); - } - - // CAPABILITIES FOR DEVICE - final List capabilityList = getCapabilitiesForDevice(deviceId); - final Map capabilityMap = new HashMap<>(); - for (final Capability c : capabilityList) { - capabilityMap.put(c.getId(), c); - } - - // CAPABILITY STATES - final List capabilityStateList = getCapabilityStates(); - final Map capabilityStateMap = new HashMap<>(); - for (final CapabilityState cs : capabilityStateList) { - capabilityStateMap.put(cs.getId(), cs); - } - - // DEVICE STATE - final State state = getDeviceStateByDeviceId(deviceId); - final DeviceState deviceState = new DeviceState(); - deviceState.setId(deviceId); - deviceState.setState(state); - - // MESSAGES - final List messageList = getMessages(); - final List ml = new ArrayList<>(); - final String deviceIdPath = "/device/" + deviceId; - - for (final Message m : messageList) { - logger.trace("Message Type {} with ID {}", m.getType(), m.getId()); - if (m.getDevices() != null && !m.getDevices().isEmpty()) { - for (final String li : m.getDevices()) { - if (deviceIdPath.equals(li)) { - ml.add(m); - } - } - } - } - - // DEVICE - final Device d = getDeviceById(deviceId); - if (BATTERY_POWERED_DEVICES.contains(d.getType())) { - d.setIsBatteryPowered(true); - d.setLowBattery(false); - } - - // location - d.setLocation(locationMap.get(d.getLocationId())); - - // capabilities and their states - final HashMap deviceCapabilityMap = new HashMap<>(); - for (final String cl : d.getCapabilityLinkList()) { - - final Capability c = capabilityMap.get(Link.getId(cl)); - c.setCapabilityState(capabilityStateMap.get(c.getId())); - deviceCapabilityMap.put(c.getId(), c); - - } - d.setCapabilityMap(deviceCapabilityMap); - - // device states - d.setDeviceState(deviceState); - - // messages - if (!ml.isEmpty()) { - d.setMessageList(ml); - for (final Message m : d.getMessageList()) { - switch (m.getType()) { - case Message.TYPE_DEVICE_LOW_BATTERY: - d.setLowBattery(true); - d.setLowBatteryMessageId(m.getId()); - break; - } - } - } - - return d; - } - /** * Loads the states for all {@link Device}s. - * - * @return - * @throws IOException - * @throws ApiException */ public List getDeviceStates() throws IOException, ApiException, AuthenticationException { logger.debug("Loading device states..."); @@ -612,11 +345,6 @@ public class InnogyClient { /** * Loads the device state for the given deviceId. - * - * @param deviceId - * @return - * @throws IOException - * @throws ApiException */ public State getDeviceStateByDeviceId(final String deviceId) throws IOException, ApiException, AuthenticationException { @@ -628,8 +356,6 @@ public class InnogyClient { * Loads the locations and returns a {@link List} of {@link Location}s. * * @return a List of Devices - * @throws IOException - * @throws ApiException */ public List getLocations() throws IOException, ApiException, AuthenticationException { logger.debug("Loading locations..."); @@ -640,9 +366,7 @@ public class InnogyClient { * Loads and returns a {@link List} of {@link Capability}s for the given deviceId. * * @param deviceId the id of the {@link Device} - * @return - * @throws IOException - * @throws ApiException + * @return capabilities of the device */ public List getCapabilitiesForDevice(final String deviceId) throws IOException, ApiException, AuthenticationException { @@ -652,10 +376,6 @@ public class InnogyClient { /** * Loads and returns a {@link List} of all {@link Capability}s. - * - * @return - * @throws IOException - * @throws ApiException */ public List getCapabilities() throws IOException, ApiException, AuthenticationException { logger.debug("Loading capabilities..."); @@ -664,10 +384,6 @@ public class InnogyClient { /** * Loads and returns a {@link List} of all {@link Capability}States. - * - * @return - * @throws IOException - * @throws ApiException */ public List getCapabilityStates() throws IOException, ApiException, AuthenticationException { logger.debug("Loading capability states..."); @@ -676,10 +392,6 @@ public class InnogyClient { /** * Returns a {@link List} of all {@link Message}s. - * - * @return - * @throws IOException - * @throws ApiException */ public List getMessages() throws IOException, ApiException, AuthenticationException { logger.debug("Loading messages..."); @@ -692,11 +404,4 @@ public class InnogyClient { public String getConfigVersion() { return configVersion; } - - /** - * @param configVersion the configVersion to set - */ - public void setConfigVersion(final String configVersion) { - this.configVersion = configVersion; - } } diff --git a/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/client/entity/device/Device.java b/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/client/entity/device/Device.java index 46bee1d5e..8a0f2014e 100644 --- a/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/client/entity/device/Device.java +++ b/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/client/entity/device/Device.java @@ -14,9 +14,7 @@ package org.openhab.binding.innogysmarthome.internal.client.entity.device; import static org.openhab.binding.innogysmarthome.internal.InnogyBindingConstants.*; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; +import java.util.*; import org.openhab.binding.innogysmarthome.internal.client.entity.capability.Capability; import org.openhab.binding.innogysmarthome.internal.client.entity.location.Location; @@ -38,8 +36,6 @@ public class Device { protected static final String PROTOCOL_ID_VIRTUAL = "Virtual"; protected static final String PROTOCOL_ID_WMBUS = "wMBus"; - public static final List EMPTY_CAPABILITY_LINK_LIST = new ArrayList<>(); - /** * Unique id for the device, always available in model. */ @@ -82,15 +78,9 @@ public class Device { private DeviceConfig config; - /** - * Contains a list of the device capabilities. - * - * Optional. - */ - @SerializedName("capabilities") - private List capabilityLinkList; + private List capabilities; - private HashMap capabilityMap; + private Map capabilityMap; private DeviceState deviceState; @@ -115,12 +105,6 @@ public class Device { private List messageList; private boolean lowBattery; - /** - * Stores the message id, that contains the low battery state. This is needed to identify the device, when the - * message - * with that id is deleted (thus low battery state is false again). - */ - private String lowBatteryMessageId; /** * Stores, if the {@link Device} is battery powered. @@ -263,32 +247,28 @@ public class Device { /** * @return the capabilityList */ - public List getCapabilityLinkList() { - if (capabilityLinkList != null) { - return capabilityLinkList; - } else { - return EMPTY_CAPABILITY_LINK_LIST; - } + public List getCapabilities() { + return Objects.requireNonNullElse(capabilities, Collections.emptyList()); } /** * @param capabilityList the capabilityList to set */ - public void setCapabilityList(List capabilityList) { - this.capabilityLinkList = capabilityList; + public void setCapabilities(List capabilityList) { + this.capabilities = capabilityList; } /** * @param capabilityMap the capabilityMap to set */ - public void setCapabilityMap(HashMap capabilityMap) { + public void setCapabilityMap(Map capabilityMap) { this.capabilityMap = capabilityMap; } /** * @return the capabilityMap */ - public HashMap getCapabilityMap() { + public Map getCapabilityMap() { return this.capabilityMap; } @@ -310,7 +290,7 @@ public class Device { } /** - * @param locationList the locationList to set + * @param locationLink the locationList to set */ public void setLocation(String locationLink) { this.locationLink = locationLink; @@ -366,10 +346,31 @@ public class Device { */ public void setMessageList(List messageList) { this.messageList = messageList; + applyMessageList(messageList); + } - for (final Message m : messageList) { - setLowBattery(Message.TYPE_DEVICE_LOW_BATTERY.equals(m.getType())); - setReachable(!Message.TYPE_DEVICE_UNREACHABLE.equals(m.getType())); + private void applyMessageList(List messageList) { + if (messageList != null && !messageList.isEmpty()) { + boolean isUnreachableMessageFound = false; + boolean isLowBatteryMessageFound = false; + for (final Message message : messageList) { + switch (message.getType()) { + case Message.TYPE_DEVICE_UNREACHABLE: + isUnreachableMessageFound = true; + break; + case Message.TYPE_DEVICE_LOW_BATTERY: + isLowBatteryMessageFound = true; + break; + } + } + if (isUnreachableMessageFound) { + setReachable(false); // overwrite only when there is a corresponding message (to keep the state of the + // API in other cases) + } + if (isLowBatteryMessageFound) { + setLowBattery(true); // overwrite only when there is a corresponding message (to keep the state of the + // API in other cases) + } } } @@ -378,7 +379,7 @@ public class Device { * * @param isReachable */ - public void setReachable(boolean isReachable) { + private void setReachable(boolean isReachable) { if (getDeviceState().hasIsReachableState()) { getDeviceState().setReachable(isReachable); } @@ -398,7 +399,7 @@ public class Device { * * @param hasLowBattery */ - public void setLowBattery(boolean hasLowBattery) { + private void setLowBattery(boolean hasLowBattery) { this.lowBattery = hasLowBattery; } @@ -411,14 +412,6 @@ public class Device { return lowBattery; } - public String getLowBatteryMessageId() { - return this.lowBatteryMessageId; - } - - public void setLowBatteryMessageId(String messageId) { - this.lowBatteryMessageId = messageId; - } - /** * Returns true, if the {@link Device} is battery powered. * diff --git a/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/handler/InnogyBridgeHandler.java b/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/handler/InnogyBridgeHandler.java index 308d40b49..a9e74b1b7 100644 --- a/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/handler/InnogyBridgeHandler.java +++ b/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/handler/InnogyBridgeHandler.java @@ -49,6 +49,7 @@ import org.openhab.binding.innogysmarthome.internal.discovery.InnogyDeviceDiscov import org.openhab.binding.innogysmarthome.internal.listener.DeviceStatusListener; import org.openhab.binding.innogysmarthome.internal.listener.EventListener; import org.openhab.binding.innogysmarthome.internal.manager.DeviceStructureManager; +import org.openhab.binding.innogysmarthome.internal.manager.FullDeviceManager; import org.openhab.core.auth.client.oauth2.AccessTokenRefreshListener; import org.openhab.core.auth.client.oauth2.AccessTokenResponse; import org.openhab.core.auth.client.oauth2.OAuthClientService; @@ -167,7 +168,7 @@ public class InnogyBridgeHandler extends BaseBridgeHandler if (checkOnAuthCode()) { final InnogyClient localClient = createInnogyClient(oAuthService, httpClient); client = localClient; - deviceStructMan = new DeviceStructureManager(localClient); + deviceStructMan = new DeviceStructureManager(createFullDeviceManager(localClient)); oAuthService.addAccessTokenRefreshListener(this); registerDeviceStatusListener(InnogyBridgeHandler.this); scheduleRestartClient(false); @@ -892,6 +893,10 @@ public class InnogyBridgeHandler extends BaseBridgeHandler return scheduler; } + FullDeviceManager createFullDeviceManager(InnogyClient client) { + return new FullDeviceManager(client); + } + InnogyClient createInnogyClient(final OAuthClientService oAuthService, final HttpClient httpClient) { return new InnogyClient(oAuthService, httpClient); } diff --git a/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/handler/InnogyDeviceHandler.java b/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/handler/InnogyDeviceHandler.java index 7ccc456ce..e3fc306b0 100644 --- a/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/handler/InnogyDeviceHandler.java +++ b/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/handler/InnogyDeviceHandler.java @@ -16,7 +16,6 @@ import static org.openhab.binding.innogysmarthome.internal.InnogyBindingConstant import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; -import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -708,7 +707,7 @@ public class InnogyDeviceHandler extends BaseThingHandler implements DeviceStatu boolean deviceChanged = false; final String linkedCapabilityId = event.getSourceId(); - HashMap capabilityMap = device.getCapabilityMap(); + Map capabilityMap = device.getCapabilityMap(); Capability capability = capabilityMap.get(linkedCapabilityId); logger.trace("Loaded Capability {}, {} with id {}, device {} from device id {}", capability.getType(), capability.getName(), capability.getId(), capability.getDeviceLink(), device.getId()); diff --git a/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/manager/DeviceStructureManager.java b/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/manager/DeviceStructureManager.java index 967e51568..5be3b1dee 100644 --- a/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/manager/DeviceStructureManager.java +++ b/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/manager/DeviceStructureManager.java @@ -46,7 +46,7 @@ public class DeviceStructureManager { private final Logger logger = LoggerFactory.getLogger(DeviceStructureManager.class); - private final InnogyClient client; + private final FullDeviceManager deviceManager; private final Map deviceMap; private final Map capabilityIdToDeviceMap; private String bridgeDeviceId = ""; @@ -54,10 +54,10 @@ public class DeviceStructureManager { /** * Constructs the {@link DeviceStructureManager}. * - * @param client the {@link InnogyClient} + * @param deviceManager the {@link FullDeviceManager} */ - public DeviceStructureManager(InnogyClient client) { - this.client = client; + public DeviceStructureManager(FullDeviceManager deviceManager) { + this.deviceManager = deviceManager; deviceMap = Collections.synchronizedMap(new HashMap<>()); capabilityIdToDeviceMap = new ConcurrentHashMap<>(); } @@ -82,7 +82,7 @@ public class DeviceStructureManager { public void refreshDevices() throws IOException, ApiException, AuthenticationException { deviceMap.clear(); capabilityIdToDeviceMap.clear(); - List devices = client.getFullDevices(); + List devices = deviceManager.getFullDevices(); for (Device d : devices) { handleRefreshedDevice(d); } @@ -98,7 +98,7 @@ public class DeviceStructureManager { */ public void refreshDevice(String deviceId) throws IOException, ApiException, AuthenticationException { logger.trace("Refreshing Device with id '{}'", deviceId); - Device d = client.getFullDeviceById(deviceId); + Device d = deviceManager.getFullDeviceById(deviceId); handleRefreshedDevice(d); } @@ -155,7 +155,7 @@ public class DeviceStructureManager { getDeviceMap().put(device.getId(), device); } - for (String cl : device.getCapabilityLinkList()) { + for (String cl : device.getCapabilities()) { capabilityIdToDeviceMap.put(Link.getId(cl), device); } } diff --git a/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/manager/FullDeviceManager.java b/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/manager/FullDeviceManager.java new file mode 100644 index 000000000..09acbd946 --- /dev/null +++ b/bundles/org.openhab.binding.innogysmarthome/src/main/java/org/openhab/binding/innogysmarthome/internal/manager/FullDeviceManager.java @@ -0,0 +1,223 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.innogysmarthome.internal.manager; + +import static org.openhab.binding.innogysmarthome.internal.InnogyBindingConstants.BATTERY_POWERED_DEVICES; + +import java.io.IOException; +import java.util.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.innogysmarthome.internal.client.InnogyClient; +import org.openhab.binding.innogysmarthome.internal.client.entity.capability.Capability; +import org.openhab.binding.innogysmarthome.internal.client.entity.capability.CapabilityState; +import org.openhab.binding.innogysmarthome.internal.client.entity.device.Device; +import org.openhab.binding.innogysmarthome.internal.client.entity.device.DeviceState; +import org.openhab.binding.innogysmarthome.internal.client.entity.link.Link; +import org.openhab.binding.innogysmarthome.internal.client.entity.location.Location; +import org.openhab.binding.innogysmarthome.internal.client.entity.message.Message; +import org.openhab.binding.innogysmarthome.internal.client.exception.ApiException; +import org.openhab.binding.innogysmarthome.internal.client.exception.AuthenticationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Sven Strohschein - Initial contribution (but only created by refactoring the InnogyClient class) + */ +@NonNullByDefault +public class FullDeviceManager { + + private final Logger logger = LoggerFactory.getLogger(FullDeviceManager.class); + + private final InnogyClient client; + + public FullDeviceManager(InnogyClient client) { + this.client = client; + } + + /** + * Returns a {@link List} of all {@link Device}s with the full configuration details, {@link Capability}s and + * states. Calling this may take a while... + */ + public List getFullDevices() throws IOException, ApiException, AuthenticationException { + + final Map locationMap = createLocationMap(client); + final Map capabilityMap = createCapabilityMap(client); + final Map deviceStateMap = createDeviceStateMap(client); + final Map> messageMap = createMessageMap(client); + + final List deviceList = client.getDevices(deviceStateMap.keySet()); + for (final Device device : deviceList) { + final String deviceId = device.getId(); + initializeDevice(device, deviceStateMap.get(deviceId), locationMap, capabilityMap, + getMessageList(device, messageMap)); + } + return deviceList; + } + + /** + * Returns the {@link Device} with the given deviceId with full configuration details, {@link Capability}s and + * states. Calling this may take a little bit longer... + */ + public Device getFullDeviceById(final String deviceId) throws IOException, ApiException, AuthenticationException { + final Map locationMap = createLocationMap(client); + final Map capabilityMap = createCapabilityMap(deviceId, client); + final List messageMap = createMessageMap(deviceId, client); + + final DeviceState deviceState = new DeviceState(); + deviceState.setId(deviceId); + deviceState.setState(client.getDeviceStateByDeviceId(deviceId)); + + final Device device = client.getDeviceById(deviceId); + initializeDevice(device, deviceState, locationMap, capabilityMap, messageMap); + return device; + } + + private void initializeDevice(Device device, @Nullable DeviceState deviceState, Map locationMap, + Map capabilityMap, List messageList) { + + device.setDeviceState(deviceState); + + if (isBatteryPowered(device)) { + device.setIsBatteryPowered(true); + } + + device.setLocation(locationMap.get(device.getLocationId())); + + device.setCapabilityMap(createDeviceCapabilityMap(device, capabilityMap)); + + device.setMessageList(messageList); + } + + private static boolean isBatteryPowered(Device device) { + return BATTERY_POWERED_DEVICES.contains(device.getType()); + } + + private List getMessageList(Device device, Map> messageMap) { + return Objects.requireNonNullElse(messageMap.get(device.getId()), Collections.emptyList()); + } + + private static Map createLocationMap(InnogyClient client) + throws IOException, ApiException, AuthenticationException { + final List locationList = client.getLocations(); + final Map locationMap = new HashMap<>(locationList.size()); + for (final Location location : locationList) { + locationMap.put(location.getId(), location); + } + return locationMap; + } + + private static Map createCapabilityStateMap(InnogyClient client) + throws IOException, ApiException, AuthenticationException { + final List capabilityStateList = client.getCapabilityStates(); + final Map capabilityStateMap = new HashMap<>(capabilityStateList.size()); + for (final CapabilityState capabilityState : capabilityStateList) { + capabilityStateMap.put(capabilityState.getId(), capabilityState); + } + return capabilityStateMap; + } + + private static Map createCapabilityMap(InnogyClient client) + throws IOException, ApiException, AuthenticationException { + + final Map capabilityStateMap = createCapabilityStateMap(client); + final List capabilityList = client.getCapabilities(); + + return initializeCapabilities(capabilityStateMap, capabilityList); + } + + private static Map createCapabilityMap(String deviceId, InnogyClient client) + throws IOException, ApiException, AuthenticationException { + + final Map capabilityStateMap = createCapabilityStateMap(client); + final List capabilityList = client.getCapabilitiesForDevice(deviceId); + + return initializeCapabilities(capabilityStateMap, capabilityList); + } + + private static Map initializeCapabilities(Map capabilityStateMap, + List capabilityList) { + final Map capabilityMap = new HashMap<>(capabilityList.size()); + for (final Capability capability : capabilityList) { + String capabilityId = capability.getId(); + + CapabilityState capabilityState = capabilityStateMap.get(capabilityId); + capability.setCapabilityState(capabilityState); + + capabilityMap.put(capabilityId, capability); + } + return capabilityMap; + } + + private static Map createDeviceCapabilityMap(Device device, + Map capabilityMap) { + + final HashMap deviceCapabilityMap = new HashMap<>(); + for (final String capabilityValue : device.getCapabilities()) { + final Capability capability = capabilityMap.get(Link.getId(capabilityValue)); + final String capabilityId = capability.getId(); + deviceCapabilityMap.put(capabilityId, capability); + } + return deviceCapabilityMap; + } + + private static Map createDeviceStateMap(InnogyClient client) + throws IOException, ApiException, AuthenticationException { + final List deviceStateList = client.getDeviceStates(); + final Map deviceStateMap = new HashMap<>(deviceStateList.size()); + for (final DeviceState deviceState : deviceStateList) { + deviceStateMap.put(deviceState.getId(), deviceState); + } + return deviceStateMap; + } + + private List createMessageMap(String deviceId, InnogyClient client) + throws IOException, ApiException, AuthenticationException { + final List messages = client.getMessages(); + final List messageList = new ArrayList<>(); + final String deviceIdPath = "/device/" + deviceId; + + for (final Message message : messages) { + logger.trace("Message Type {} with ID {}", message.getType(), message.getId()); + if (message.getDevices() != null && !message.getDevices().isEmpty()) { + for (final String li : message.getDevices()) { + if (deviceIdPath.equals(li)) { + messageList.add(message); + } + } + } + } + return messageList; + } + + private static Map> createMessageMap(InnogyClient client) + throws IOException, ApiException, AuthenticationException { + final List messageList = client.getMessages(); + final Map> deviceMessageMap = new HashMap<>(); + for (final Message message : messageList) { + if (message.getDevices() != null && !message.getDevices().isEmpty()) { + final String deviceId = message.getDevices().get(0).replace("/device/", ""); + List ml; + if (deviceMessageMap.containsKey(deviceId)) { + ml = deviceMessageMap.get(deviceId); + } else { + ml = new ArrayList<>(); + } + ml.add(message); + deviceMessageMap.put(deviceId, ml); + } + } + return deviceMessageMap; + } +} diff --git a/bundles/org.openhab.binding.innogysmarthome/src/test/java/org/openhab/binding/innogysmarthome/internal/client/entity/device/DeviceTest.java b/bundles/org.openhab.binding.innogysmarthome/src/test/java/org/openhab/binding/innogysmarthome/internal/client/entity/device/DeviceTest.java new file mode 100644 index 000000000..b8871cf3d --- /dev/null +++ b/bundles/org.openhab.binding.innogysmarthome/src/test/java/org/openhab/binding/innogysmarthome/internal/client/entity/device/DeviceTest.java @@ -0,0 +1,194 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.innogysmarthome.internal.client.entity.device; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.openhab.binding.innogysmarthome.internal.client.entity.message.Message; +import org.openhab.binding.innogysmarthome.internal.client.entity.state.BooleanState; + +/** + * @author Sven Strohschein - Initial contribution + */ +public class DeviceTest { + + @Test + public void testSetMessageListLowBatteryMessage() { + Device device = createDevice(); + + assertTrue(device.isReachable()); + assertFalse(device.hasLowBattery()); + + device.setMessageList(Collections.singletonList(createMessage(Message.TYPE_DEVICE_LOW_BATTERY))); + + assertTrue(device.isReachable()); + assertTrue(device.hasLowBattery()); + } + + @Test + public void testSetMessageListUnreachableMessage() { + Device device = createDevice(); + + assertTrue(device.isReachable()); + assertFalse(device.hasLowBattery()); + + device.setMessageList(Collections.singletonList(createMessage(Message.TYPE_DEVICE_UNREACHABLE))); + + assertFalse(device.isReachable()); + assertFalse(device.hasLowBattery()); + } + + @Test + public void testSetMessageListResetByEmpty() { + Device device = createDevice(); + + assertNull(device.getMessageList()); + assertTrue(device.isReachable()); + assertFalse(device.hasLowBattery()); + + List messages = Arrays.asList(createMessage(Message.TYPE_DEVICE_LOW_BATTERY), + createMessage(Message.TYPE_DEVICE_UNREACHABLE)); + device.setMessageList(messages); + + assertEquals(messages, device.getMessageList()); + assertFalse(device.isReachable()); + assertTrue(device.hasLowBattery()); + + device.setMessageList(Collections.emptyList()); + + // Nothing should get changed. + // New messages are only set in real-life when the device is refreshed with new data of the API. + // Therefore the data of the API should be kept / not overwritten when no corresponding messages are available. + assertEquals(Collections.emptyList(), device.getMessageList()); + assertFalse(device.isReachable()); + assertTrue(device.hasLowBattery()); + } + + @Test + public void testSetMessageListResetByNULL() { + Device device = createDevice(); + + assertNull(device.getMessageList()); + assertTrue(device.isReachable()); + assertFalse(device.hasLowBattery()); + + List messages = Arrays.asList(createMessage(Message.TYPE_DEVICE_LOW_BATTERY), + createMessage(Message.TYPE_DEVICE_UNREACHABLE)); + device.setMessageList(messages); + + assertEquals(messages, device.getMessageList()); + assertFalse(device.isReachable()); + assertTrue(device.hasLowBattery()); + + device.setMessageList(null); + + // Nothing should get changed. + // New messages are only set in real-life when the device is refreshed with new data of the API. + // Therefore the data of the API should be kept / not overwritten when no corresponding messages are available. + assertNull(device.getMessageList()); + assertFalse(device.isReachable()); + assertTrue(device.hasLowBattery()); + } + + @Test + public void testSetMessageListResetByUnimportantMessage() { + Device device = createDevice(); + + assertNull(device.getMessageList()); + assertTrue(device.isReachable()); + assertFalse(device.hasLowBattery()); + + List messages = Arrays.asList(createMessage(Message.TYPE_DEVICE_LOW_BATTERY), + createMessage(Message.TYPE_DEVICE_UNREACHABLE)); + device.setMessageList(messages); + + assertEquals(messages, device.getMessageList()); + assertFalse(device.isReachable()); + assertTrue(device.hasLowBattery()); + + messages = Collections.singletonList(createMessage("UNKNOWN")); + device.setMessageList(messages); + + // Nothing should get changed. + // New messages are only set in real-life when the device is refreshed with new data of the API. + // Therefore the data of the API should be kept / not overwritten when no corresponding messages are available. + assertEquals(messages, device.getMessageList()); + assertFalse(device.isReachable()); + assertTrue(device.hasLowBattery()); + } + + @Test + public void testSetMessageListUnimportantMessage() { + Device device = createDevice(); + + assertTrue(device.isReachable()); + assertFalse(device.hasLowBattery()); + + device.setMessageList(Collections.singletonList(createMessage("UNKNOWN"))); + + assertTrue(device.isReachable()); + assertFalse(device.hasLowBattery()); + } + + private Message createMessage(String messageType) { + Message message = new Message(); + message.setType(messageType); + return message; + } + + @Test + public void testSetMessageListNULL() { + Device device = createDevice(); + + assertTrue(device.isReachable()); + assertFalse(device.hasLowBattery()); + + device.setMessageList(null); + + assertTrue(device.isReachable()); + assertFalse(device.hasLowBattery()); + } + + @Test + public void testSetMessageListEmpty() { + Device device = createDevice(); + + assertTrue(device.isReachable()); + assertFalse(device.hasLowBattery()); + + device.setMessageList(Collections.emptyList()); + + assertTrue(device.isReachable()); + assertFalse(device.hasLowBattery()); + } + + private static Device createDevice() { + BooleanState isReachableState = new BooleanState(); + isReachableState.setValue(true); + + State state = new State(); + state.setIsReachable(isReachableState); + + DeviceState deviceState = new DeviceState(); + deviceState.setState(state); + + Device device = new Device(); + device.setDeviceState(deviceState); + return device; + } +} diff --git a/bundles/org.openhab.binding.innogysmarthome/src/test/java/org/openhab/binding/innogysmarthome/internal/handler/InnogyBridgeHandlerTest.java b/bundles/org.openhab.binding.innogysmarthome/src/test/java/org/openhab/binding/innogysmarthome/internal/handler/InnogyBridgeHandlerTest.java index 1a64bb0b5..c7aba6d71 100644 --- a/bundles/org.openhab.binding.innogysmarthome/src/test/java/org/openhab/binding/innogysmarthome/internal/handler/InnogyBridgeHandlerTest.java +++ b/bundles/org.openhab.binding.innogysmarthome/src/test/java/org/openhab/binding/innogysmarthome/internal/handler/InnogyBridgeHandlerTest.java @@ -30,6 +30,7 @@ import org.openhab.binding.innogysmarthome.internal.InnogyWebSocket; import org.openhab.binding.innogysmarthome.internal.client.InnogyClient; import org.openhab.binding.innogysmarthome.internal.client.entity.device.Device; import org.openhab.binding.innogysmarthome.internal.client.entity.device.DeviceConfig; +import org.openhab.binding.innogysmarthome.internal.manager.FullDeviceManager; import org.openhab.core.auth.client.oauth2.OAuthClientService; import org.openhab.core.auth.client.oauth2.OAuthFactory; import org.openhab.core.config.core.Configuration; @@ -174,6 +175,7 @@ public class InnogyBridgeHandlerTest { private class InnogyBridgeHandlerAccessible extends InnogyBridgeHandler { private final InnogyClient innogyClientMock; + private final FullDeviceManager fullDeviceManagerMock; private final ScheduledExecutorService schedulerMock; private int executionCount; private int directExecutionCount; @@ -188,7 +190,8 @@ public class InnogyBridgeHandlerTest { bridgeDevice.setConfig(new DeviceConfig()); innogyClientMock = mock(InnogyClient.class); - when(innogyClientMock.getFullDevices()).thenReturn(Collections.singletonList(bridgeDevice)); + fullDeviceManagerMock = mock(FullDeviceManager.class); + when(fullDeviceManagerMock.getFullDevices()).thenReturn(Collections.singletonList(bridgeDevice)); schedulerMock = mock(ScheduledExecutorService.class); @@ -218,6 +221,12 @@ public class InnogyBridgeHandlerTest { return directExecutionCount; } + @Override + @NonNull + FullDeviceManager createFullDeviceManager(@NonNull InnogyClient client) { + return fullDeviceManagerMock; + } + @Override @NonNull InnogyClient createInnogyClient(@NonNull OAuthClientService oAuthService, @NonNull HttpClient httpClient) {