From fcae87868bb9c7c5cb685952b2683cc221410b7a Mon Sep 17 00:00:00 2001 From: lolodomo Date: Sun, 11 Jul 2021 22:58:59 +0200 Subject: [PATCH] [homeconnect] Catch exception when appropriate (#10929) * [homeconnect] Catch exception when appropriate Fix #10904 Signed-off-by: Laurent Garnier --- .../internal/client/HomeConnectApiClient.java | 19 +++-- .../AbstractHomeConnectThingHandler.java | 77 +++++++++++-------- .../handler/HomeConnectHoodHandler.java | 2 +- 3 files changed, 59 insertions(+), 39 deletions(-) diff --git a/bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/client/HomeConnectApiClient.java b/bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/client/HomeConnectApiClient.java index feba2087b4fa..0b1e4473d0c3 100644 --- a/bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/client/HomeConnectApiClient.java +++ b/bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/client/HomeConnectApiClient.java @@ -548,7 +548,7 @@ public boolean isLocalControlActive(String haId) * Get active program of device. * * @param haId home appliance id - * @return {@link Data} or null if there is no active program + * @return {@link Program} or null if there is no active program * @throws CommunicationException API communication exception * @throws AuthorizationException oAuth authorization exception * @throws ApplianceOfflineException appliance is not connected to the cloud @@ -562,7 +562,7 @@ public boolean isLocalControlActive(String haId) * Get selected program of device. * * @param haId home appliance id - * @return {@link Data} or null if there is no selected program + * @return {@link Program} or null if there is no selected program * @throws CommunicationException API communication exception * @throws AuthorizationException oAuth authorization exception * @throws ApplianceOfflineException appliance is not connected to the cloud @@ -627,7 +627,17 @@ public List getAvailablePrograms(String haId) return getAvailablePrograms(haId, BASE_PATH + haId + "/programs/available"); } - public List getProgramOptions(String haId, String programKey) + /** + * Get the available options of a program. + * + * @param haId home appliance id + * @param programKey program id + * @return list of {@link AvailableProgramOption} or null if the program is unsupported by the API + * @throws CommunicationException API communication exception + * @throws AuthorizationException oAuth authorization exception + * @throws ApplianceOfflineException appliance is not connected to the cloud + */ + public @Nullable List getProgramOptions(String haId, String programKey) throws CommunicationException, AuthorizationException, ApplianceOfflineException { Request request = createRequest(HttpMethod.GET, BASE_PATH + haId + "/programs/available/" + programKey); try { @@ -644,8 +654,7 @@ public List getProgramOptions(String haId, String progra responseBody == null ? "" : responseBody); } - return response.getStatus() == HttpStatus.OK_200 ? mapToAvailableProgramOption(responseBody, haId) - : List.of(); + return response.getStatus() == HttpStatus.OK_200 ? mapToAvailableProgramOption(responseBody, haId) : null; } catch (InterruptedException | TimeoutException | ExecutionException e) { logger.warn("Failed to get program options! haId={}, programKey={}, error={}", haId, programKey, e.getMessage()); diff --git a/bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/handler/AbstractHomeConnectThingHandler.java b/bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/handler/AbstractHomeConnectThingHandler.java index 9db0f0dd3e3b..906d9d721e7c 100644 --- a/bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/handler/AbstractHomeConnectThingHandler.java +++ b/bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/handler/AbstractHomeConnectThingHandler.java @@ -1026,7 +1026,6 @@ protected ChannelUpdateHandler defaultAmbientLightChannelUpdateHandler() { updateState(channel.getUID(), UnDefType.UNDEF); } }); - } return OnOffType.from(enabled); } else { @@ -1458,54 +1457,66 @@ protected String convertWasherSpinSpeed(String value) { } protected void updateProgramOptionsStateDescriptions(String programKey) - throws CommunicationException, AuthorizationException, ApplianceOfflineException { + throws AuthorizationException, ApplianceOfflineException { Optional apiClient = getApiClient(); if (apiClient.isPresent()) { + boolean cacheToSet = false; List availableProgramOptions; if (availableProgramOptionsCache.containsKey(programKey)) { - logger.debug("Returning cached options for '{}'.", programKey); + logger.debug("Returning cached options for program '{}'.", programKey); availableProgramOptions = availableProgramOptionsCache.get(programKey); availableProgramOptions = availableProgramOptions != null ? availableProgramOptions : Collections.emptyList(); } else { - availableProgramOptions = apiClient.get().getProgramOptions(getThingHaId(), programKey); - availableProgramOptionsCache.put(programKey, availableProgramOptions); + // Depending on the current program operation state, the APi request could trigger a + // CommunicationException exception due to returned status code 409 + try { + availableProgramOptions = apiClient.get().getProgramOptions(getThingHaId(), programKey); + if (availableProgramOptions == null) { + // Program is unsupported, save in cache an empty list of options to avoid calling again the API + // for this program + availableProgramOptions = emptyList(); + logger.debug("Saving empty options in cache for unsupported program '{}'.", programKey); + availableProgramOptionsCache.put(programKey, availableProgramOptions); + } else { + cacheToSet = true; + } + } catch (CommunicationException e) { + availableProgramOptions = emptyList(); + } } Optional channelSpinSpeed = getThingChannel(CHANNEL_WASHER_SPIN_SPEED); Optional channelTemperature = getThingChannel(CHANNEL_WASHER_TEMPERATURE); Optional channelDryingTarget = getThingChannel(CHANNEL_DRYER_DRYING_TARGET); - if (availableProgramOptions.isEmpty()) { - channelSpinSpeed.ifPresent( - channel -> dynamicStateDescriptionProvider.setStateOptions(channel.getUID(), emptyList())); - channelTemperature.ifPresent( - channel -> dynamicStateDescriptionProvider.setStateOptions(channel.getUID(), emptyList())); - channelDryingTarget.ifPresent( - channel -> dynamicStateDescriptionProvider.setStateOptions(channel.getUID(), emptyList())); + Optional optionsSpinSpeed = availableProgramOptions.stream() + .filter(option -> OPTION_WASHER_SPIN_SPEED.equals(option.getKey())).findFirst(); + Optional optionsTemperature = availableProgramOptions.stream() + .filter(option -> OPTION_WASHER_TEMPERATURE.equals(option.getKey())).findFirst(); + Optional optionsDryingTarget = availableProgramOptions.stream() + .filter(option -> OPTION_DRYER_DRYING_TARGET.equals(option.getKey())).findFirst(); + + // Save options in cache only if we got options for all expected channels + if (cacheToSet && (!channelSpinSpeed.isPresent() || optionsSpinSpeed.isPresent()) + && (!channelTemperature.isPresent() || optionsTemperature.isPresent()) + && (!channelDryingTarget.isPresent() || optionsDryingTarget.isPresent())) { + logger.debug("Saving options in cache for program '{}'.", programKey); + availableProgramOptionsCache.put(programKey, availableProgramOptions); } - availableProgramOptions.forEach(option -> { - switch (option.getKey()) { - case OPTION_WASHER_SPIN_SPEED: { - channelSpinSpeed - .ifPresent(channel -> dynamicStateDescriptionProvider.setStateOptions(channel.getUID(), - createStateOptions(option, this::convertWasherSpinSpeed))); - break; - } - case OPTION_WASHER_TEMPERATURE: { - channelTemperature - .ifPresent(channel -> dynamicStateDescriptionProvider.setStateOptions(channel.getUID(), - createStateOptions(option, this::convertWasherTemperature))); - break; - } - case OPTION_DRYER_DRYING_TARGET: { - channelDryingTarget.ifPresent(channel -> dynamicStateDescriptionProvider - .setStateOptions(channel.getUID(), createStateOptions(option, this::mapStringType))); - break; - } - } - }); + channelSpinSpeed.ifPresent(channel -> optionsSpinSpeed.ifPresentOrElse( + option -> dynamicStateDescriptionProvider.setStateOptions(channel.getUID(), + createStateOptions(option, this::convertWasherSpinSpeed)), + () -> dynamicStateDescriptionProvider.setStateOptions(channel.getUID(), emptyList()))); + channelTemperature.ifPresent(channel -> optionsTemperature.ifPresentOrElse( + option -> dynamicStateDescriptionProvider.setStateOptions(channel.getUID(), + createStateOptions(option, this::convertWasherTemperature)), + () -> dynamicStateDescriptionProvider.setStateOptions(channel.getUID(), emptyList()))); + channelDryingTarget.ifPresent(channel -> optionsDryingTarget.ifPresentOrElse( + option -> dynamicStateDescriptionProvider.setStateOptions(channel.getUID(), + createStateOptions(option, this::mapStringType)), + () -> dynamicStateDescriptionProvider.setStateOptions(channel.getUID(), emptyList()))); } } diff --git a/bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/handler/HomeConnectHoodHandler.java b/bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/handler/HomeConnectHoodHandler.java index 85bae7756cd2..f9fc735eacef 100644 --- a/bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/handler/HomeConnectHoodHandler.java +++ b/bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/handler/HomeConnectHoodHandler.java @@ -216,7 +216,7 @@ protected void updateSelectedProgramStateDescription() { try { List availableProgramOptions = apiClient.get() .getProgramOptions(getThingHaId(), PROGRAM_HOOD_VENTING); - if (availableProgramOptions.isEmpty()) { + if (availableProgramOptions == null || availableProgramOptions.isEmpty()) { throw new CommunicationException("Program " + PROGRAM_HOOD_VENTING + " is unsupported"); } availableProgramOptions.forEach(option -> {