]> git.basschouten.com Git - openhab-addons.git/commitdiff
[homeconnect] Predefined temp / spin speeds options for unsupported washer programs...
authorlolodomo <lg.hc@free.fr>
Sun, 25 Jul 2021 09:49:19 +0000 (11:49 +0200)
committerGitHub <noreply@github.com>
Sun, 25 Jul 2021 09:49:19 +0000 (11:49 +0200)
* [homeconnect] Predefined temp / spin speeds options for unsupported washer programs

Fix #10701

Also save in programs cache the unuspported program

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
* Use constants OPTION_WASHER_TEMPERATURE and OPTION_WASHER_SPIN_SPEED

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
* Review comment : using constants

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/HomeConnectBindingConstants.java
bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/client/HomeConnectApiClient.java
bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/client/model/AvailableProgram.java
bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/handler/AbstractHomeConnectThingHandler.java
bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/handler/HomeConnectHoodHandler.java
bundles/org.openhab.binding.homeconnect/src/main/java/org/openhab/binding/homeconnect/internal/handler/HomeConnectWasherHandler.java

index 4d2dd432c08cab42c9d56865e74f96864be33eec..f07647d256cc8cb1f25ed9e6078cdf5ba852f4c2 100644 (file)
@@ -195,6 +195,26 @@ public class HomeConnectBindingConstants {
     public static final String OPTION_HOOD_VENTING_LEVEL = "Cooking.Common.Option.Hood.VentingLevel";
     public static final String OPTION_HOOD_INTENSIVE_LEVEL = "Cooking.Common.Option.Hood.IntensiveLevel";
 
+    // List of washer temperatures
+    public static final String TEMPERATURE_PREFIX = "LaundryCare.Washer.EnumType.Temperature.";
+    public static final String TEMPERATURE_AUTO = TEMPERATURE_PREFIX + "Auto";
+    public static final String TEMPERATURE_COLD = TEMPERATURE_PREFIX + "Cold";
+    public static final String TEMPERATURE_20 = TEMPERATURE_PREFIX + "GC20";
+    public static final String TEMPERATURE_30 = TEMPERATURE_PREFIX + "GC30";
+    public static final String TEMPERATURE_40 = TEMPERATURE_PREFIX + "GC40";
+    public static final String TEMPERATURE_60 = TEMPERATURE_PREFIX + "GC60";
+    public static final String TEMPERATURE_90 = TEMPERATURE_PREFIX + "GC90";
+
+    // List of spin speeds
+    public static final String SPIN_SPEED_PREFIX = "LaundryCare.Washer.EnumType.SpinSpeed.";
+    public static final String SPIN_SPEED_AUTO = SPIN_SPEED_PREFIX + "Auto";
+    public static final String SPIN_SPEED_OFF = SPIN_SPEED_PREFIX + "Off";
+    public static final String SPIN_SPEED_400 = SPIN_SPEED_PREFIX + "RPM400";
+    public static final String SPIN_SPEED_600 = SPIN_SPEED_PREFIX + "RPM600";
+    public static final String SPIN_SPEED_800 = SPIN_SPEED_PREFIX + "RPM800";
+    public static final String SPIN_SPEED_1200 = SPIN_SPEED_PREFIX + "RPM1200";
+    public static final String SPIN_SPEED_1400 = SPIN_SPEED_PREFIX + "RPM1400";
+
     // List of stages
     public static final String STAGE_FAN_OFF = "Cooking.Hood.EnumType.Stage.FanOff";
     public static final String STAGE_FAN_STAGE_01 = "Cooking.Hood.EnumType.Stage.FanStage01";
index 0b1e4473d0c3020fa083e963ed1f0993e97788c0..5c48d1f1c7d58ab311f7c06015ba1fc01c322233 100644 (file)
@@ -19,11 +19,9 @@ import static org.openhab.binding.homeconnect.internal.client.HttpHelper.*;
 import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -79,7 +77,6 @@ public class HomeConnectApiClient {
     private final Logger logger = LoggerFactory.getLogger(HomeConnectApiClient.class);
     private final HttpClient client;
     private final String apiUrl;
-    private final Map<String, List<AvailableProgram>> programsCache;
     private final OAuthClientService oAuthClientService;
     private final CircularQueue<ApiRequest> communicationQueue;
     private final ApiBridgeConfiguration apiBridgeConfiguration;
@@ -90,7 +87,6 @@ public class HomeConnectApiClient {
         this.oAuthClientService = oAuthClientService;
         this.apiBridgeConfiguration = apiBridgeConfiguration;
 
-        programsCache = new ConcurrentHashMap<>();
         apiUrl = simulated ? API_SIMULATOR_BASE_URL : API_BASE_URL;
         communicationQueue = new CircularQueue<>(COMMUNICATION_QUEUE_SIZE);
         if (apiRequestHistory != null) {
@@ -610,16 +606,7 @@ public class HomeConnectApiClient {
 
     public List<AvailableProgram> getPrograms(String haId)
             throws CommunicationException, AuthorizationException, ApplianceOfflineException {
-        List<AvailableProgram> programs;
-        if (programsCache.containsKey(haId)) {
-            logger.debug("Returning cached programs for '{}'.", haId);
-            programs = programsCache.get(haId);
-            programs = programs != null ? programs : Collections.emptyList();
-        } else {
-            programs = getAvailablePrograms(haId, BASE_PATH + haId + "/programs");
-            programsCache.put(haId, programs);
-        }
-        return programs;
+        return getAvailablePrograms(haId, BASE_PATH + haId + "/programs");
     }
 
     public List<AvailableProgram> getAvailablePrograms(String haId)
index 54acbcc73819db52d66c3227c5d4edee542608e4..80ee852c6ab305b9d92643281d1e0a2e661ac288 100644 (file)
@@ -18,24 +18,39 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
  * AvailableProgram model
  *
  * @author Jonas Brüstel - Initial contribution
+ * @author Laurent Garnier - field "supported" added
  *
  */
 @NonNullByDefault
 public class AvailableProgram {
     private final String key;
+    private final boolean supported;
     private final boolean available;
     private final String execution;
 
-    public AvailableProgram(String key, boolean available, String execution) {
+    public AvailableProgram(String key, boolean supported, boolean available, String execution) {
         this.key = key;
+        this.supported = supported;
         this.available = available;
         this.execution = execution;
     }
 
+    public AvailableProgram(String key, boolean available, String execution) {
+        this(key, true, available, execution);
+    }
+
+    public AvailableProgram(String key, boolean supported) {
+        this(key, supported, true, "");
+    }
+
     public String getKey() {
         return key;
     }
 
+    public boolean isSupported() {
+        return supported;
+    }
+
     public boolean isAvailable() {
         return available;
     }
@@ -46,6 +61,7 @@ public class AvailableProgram {
 
     @Override
     public String toString() {
-        return "AvailableProgram [key=" + key + ", available=" + available + ", execution=" + execution + "]";
+        return "AvailableProgram [key=" + key + ", supported=" + supported + ", available=" + available + ", execution="
+                + execution + "]";
     }
 }
index 906d9d721e7c13107ccde7ddaad88c18aa4bd46b..836715ba5b21a331316924af20f40d92688e6193 100644 (file)
@@ -26,6 +26,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -44,6 +45,7 @@ import org.openhab.binding.homeconnect.internal.client.exception.ApplianceOfflin
 import org.openhab.binding.homeconnect.internal.client.exception.AuthorizationException;
 import org.openhab.binding.homeconnect.internal.client.exception.CommunicationException;
 import org.openhab.binding.homeconnect.internal.client.listener.HomeConnectEventListener;
+import org.openhab.binding.homeconnect.internal.client.model.AvailableProgram;
 import org.openhab.binding.homeconnect.internal.client.model.AvailableProgramOption;
 import org.openhab.binding.homeconnect.internal.client.model.Data;
 import org.openhab.binding.homeconnect.internal.client.model.Event;
@@ -83,6 +85,7 @@ import org.slf4j.LoggerFactory;
  * sent to one of the channels.
  *
  * @author Jonas Brüstel - Initial contribution
+ * @author Laurent Garnier - programs cache moved and enhanced to allow adding unsupported programs
  */
 @NonNullByDefault
 public abstract class AbstractHomeConnectThingHandler extends BaseThingHandler implements HomeConnectEventListener {
@@ -105,7 +108,9 @@ public abstract class AbstractHomeConnectThingHandler extends BaseThingHandler i
     private final ExpiringStateMap expiringStateMap;
     private final AtomicBoolean accessible;
     private final Logger logger = LoggerFactory.getLogger(AbstractHomeConnectThingHandler.class);
+    private final List<AvailableProgram> programsCache;
     private final Map<String, List<AvailableProgramOption>> availableProgramOptionsCache;
+    private final Map<String, List<AvailableProgramOption>> unsupportedProgramOptions;
 
     public AbstractHomeConnectThingHandler(Thing thing,
             HomeConnectDynamicStateDescriptionProvider dynamicStateDescriptionProvider) {
@@ -115,10 +120,13 @@ public abstract class AbstractHomeConnectThingHandler extends BaseThingHandler i
         this.dynamicStateDescriptionProvider = dynamicStateDescriptionProvider;
         expiringStateMap = new ExpiringStateMap(Duration.ofSeconds(CACHE_TTL_SEC));
         accessible = new AtomicBoolean(false);
+        programsCache = new CopyOnWriteArrayList<>();
         availableProgramOptionsCache = new ConcurrentHashMap<>();
+        unsupportedProgramOptions = new ConcurrentHashMap<>();
 
         configureEventHandlers(eventHandlers);
         configureChannelUpdateHandlers(channelUpdateHandlers);
+        configureUnsupportedProgramOptions(unsupportedProgramOptions);
     }
 
     @Override
@@ -207,7 +215,8 @@ public abstract class AbstractHomeConnectThingHandler extends BaseThingHandler i
                 logger.debug("Start custom program. command={} haId={}", command.toFullString(), getThingHaId());
                 apiClient.startCustomProgram(getThingHaId(), command.toFullString());
             }
-        } else if (command instanceof StringType && CHANNEL_SELECTED_PROGRAM_STATE.equals(channelUID.getId())) {
+        } else if (command instanceof StringType && CHANNEL_SELECTED_PROGRAM_STATE.equals(channelUID.getId())
+                && isProgramSupported(command.toFullString())) {
             apiClient.setSelectedProgram(getThingHaId(), command.toFullString());
         }
     }
@@ -347,20 +356,15 @@ public abstract class AbstractHomeConnectThingHandler extends BaseThingHandler i
             return;
         }
 
-        Optional<HomeConnectApiClient> apiClient = getApiClient();
-        if (apiClient.isPresent()) {
-            try {
-                List<StateOption> stateOptions = apiClient.get().getPrograms(getThingHaId()).stream()
-                        .map(p -> new StateOption(p.getKey(), mapStringType(p.getKey()))).collect(Collectors.toList());
+        try {
+            List<StateOption> stateOptions = getPrograms().stream()
+                    .map(p -> new StateOption(p.getKey(), mapStringType(p.getKey()))).collect(Collectors.toList());
 
-                getThingChannel(CHANNEL_SELECTED_PROGRAM_STATE).ifPresent(
-                        channel -> dynamicStateDescriptionProvider.setStateOptions(channel.getUID(), stateOptions));
-            } catch (CommunicationException | ApplianceOfflineException | AuthorizationException e) {
-                logger.debug("Could not fetch available programs. thing={}, haId={}, error={}", getThingLabel(),
-                        getThingHaId(), e.getMessage());
-                removeSelectedProgramStateDescription();
-            }
-        } else {
+            getThingChannel(CHANNEL_SELECTED_PROGRAM_STATE).ifPresent(
+                    channel -> dynamicStateDescriptionProvider.setStateOptions(channel.getUID(), stateOptions));
+        } catch (CommunicationException | ApplianceOfflineException | AuthorizationException e) {
+            logger.debug("Could not fetch available programs. thing={}, haId={}, error={}", getThingLabel(),
+                    getThingHaId(), e.getMessage());
             removeSelectedProgramStateDescription();
         }
     }
@@ -485,6 +489,9 @@ public abstract class AbstractHomeConnectThingHandler extends BaseThingHandler i
      */
     protected abstract void configureEventHandlers(final Map<String, EventHandler> handlers);
 
+    protected void configureUnsupportedProgramOptions(final Map<String, List<AvailableProgramOption>> programOptions) {
+    }
+
     protected boolean isChannelLinkedToProgramOptionNotFullySupportedByApi() {
         return false;
     }
@@ -1433,24 +1440,24 @@ public abstract class AbstractHomeConnectThingHandler extends BaseThingHandler i
     }
 
     protected String convertWasherTemperature(String value) {
-        if (value.startsWith("LaundryCare.Washer.EnumType.Temperature.GC")) {
-            return value.replace("LaundryCare.Washer.EnumType.Temperature.GC", "") + "°C";
+        if (value.startsWith(TEMPERATURE_PREFIX + "GC")) {
+            return value.replace(TEMPERATURE_PREFIX + "GC", "") + "°C";
         }
 
-        if (value.startsWith("LaundryCare.Washer.EnumType.Temperature.Ul")) {
-            return mapStringType(value.replace("LaundryCare.Washer.EnumType.Temperature.Ul", ""));
+        if (value.startsWith(TEMPERATURE_PREFIX + "Ul")) {
+            return mapStringType(value.replace(TEMPERATURE_PREFIX + "Ul", ""));
         }
 
         return mapStringType(value);
     }
 
     protected String convertWasherSpinSpeed(String value) {
-        if (value.startsWith("LaundryCare.Washer.EnumType.SpinSpeed.RPM")) {
-            return value.replace("LaundryCare.Washer.EnumType.SpinSpeed.RPM", "") + " RPM";
+        if (value.startsWith(SPIN_SPEED_PREFIX + "RPM")) {
+            return value.replace(SPIN_SPEED_PREFIX + "RPM", "") + " RPM";
         }
 
-        if (value.startsWith("LaundryCare.Washer.EnumType.SpinSpeed.Ul")) {
-            return value.replace("LaundryCare.Washer.EnumType.SpinSpeed.Ul", "");
+        if (value.startsWith(SPIN_SPEED_PREFIX + "Ul")) {
+            return value.replace(SPIN_SPEED_PREFIX + "Ul", "");
         }
 
         return mapStringType(value);
@@ -1473,12 +1480,31 @@ public abstract class AbstractHomeConnectThingHandler extends BaseThingHandler i
                 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);
+                        // Program is unsupported, to avoid calling again the API for this program, save in cache either
+                        // the predefined options provided by the binding if they exist, or an empty list of options
+                        if (unsupportedProgramOptions.containsKey(programKey)) {
+                            availableProgramOptions = unsupportedProgramOptions.get(programKey);
+                            availableProgramOptions = availableProgramOptions != null ? availableProgramOptions
+                                    : emptyList();
+                            logger.debug("Saving predefined options in cache for unsupported program '{}'.",
+                                    programKey);
+                        } else {
+                            availableProgramOptions = emptyList();
+                            logger.debug("Saving empty options in cache for unsupported program '{}'.", programKey);
+                        }
                         availableProgramOptionsCache.put(programKey, availableProgramOptions);
+
+                        // Add the unsupported program in programs cache and refresh the dynamic state description
+                        if (addUnsupportedProgramInCache(programKey)) {
+                            updateSelectedProgramStateDescription();
+                        }
                     } else {
+                        // If no options are returned by the API, using predefined options if available
+                        if (availableProgramOptions.isEmpty() && unsupportedProgramOptions.containsKey(programKey)) {
+                            availableProgramOptions = unsupportedProgramOptions.get(programKey);
+                            availableProgramOptions = availableProgramOptions != null ? availableProgramOptions
+                                    : emptyList();
+                        }
                         cacheToSet = true;
                     }
                 } catch (CommunicationException e) {
@@ -1599,4 +1625,49 @@ public abstract class AbstractHomeConnectThingHandler extends BaseThingHandler i
             this.reinitializationFuture3 = null;
         }
     }
+
+    protected List<AvailableProgram> getPrograms()
+            throws CommunicationException, AuthorizationException, ApplianceOfflineException {
+        if (!programsCache.isEmpty()) {
+            logger.debug("Returning cached programs for '{}'.", getThingHaId());
+            return programsCache;
+        } else {
+            Optional<HomeConnectApiClient> apiClient = getApiClient();
+            if (apiClient.isPresent()) {
+                programsCache.addAll(apiClient.get().getPrograms(getThingHaId()));
+                return programsCache;
+            } else {
+                throw new CommunicationException("API not initialized");
+            }
+        }
+    }
+
+    /**
+     * Add an entry in the programs cache and mark it as unsupported
+     *
+     * @param programKey program id
+     * @return true if an entry was added in the cache
+     */
+    private boolean addUnsupportedProgramInCache(String programKey) {
+        Optional<AvailableProgram> prog = programsCache.stream().filter(program -> programKey.equals(program.getKey()))
+                .findFirst();
+        if (!prog.isPresent()) {
+            programsCache.add(new AvailableProgram(programKey, false));
+            logger.debug("{} added in programs cache as an unsupported program", programKey);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Check if a program is marked as supported in the programs cache
+     *
+     * @param programKey program id
+     * @return true if the program is in the cache and marked as supported
+     */
+    protected boolean isProgramSupported(String programKey) {
+        Optional<AvailableProgram> prog = programsCache.stream().filter(program -> programKey.equals(program.getKey()))
+                .findFirst();
+        return prog.isPresent() && prog.get().isSupported();
+    }
 }
index f9fc735eacef9430106d3026238684eb4d04298a..497e4a7ffdb3deda076042f7310257f77f582884 100644 (file)
@@ -206,7 +206,7 @@ public class HomeConnectHoodHandler extends AbstractHomeConnectThingHandler {
         if (apiClient.isPresent()) {
             try {
                 ArrayList<StateOption> stateOptions = new ArrayList<>();
-                apiClient.get().getPrograms(getThingHaId()).forEach(availableProgram -> {
+                getPrograms().forEach(availableProgram -> {
                     if (PROGRAM_HOOD_AUTOMATIC.equals(availableProgram.getKey())) {
                         stateOptions.add(new StateOption(COMMAND_AUTOMATIC, mapStringType(availableProgram.getKey())));
                     } else if (PROGRAM_HOOD_DELAYED_SHUT_OFF.equals(availableProgram.getKey())) {
index 4d62ede7d87490bcad1fe8676a1baa30913a6c67..59c47fbb0adb4b414c2c2482641b5cfcd263a138 100644 (file)
@@ -23,6 +23,7 @@ import org.openhab.binding.homeconnect.internal.client.HomeConnectApiClient;
 import org.openhab.binding.homeconnect.internal.client.exception.ApplianceOfflineException;
 import org.openhab.binding.homeconnect.internal.client.exception.AuthorizationException;
 import org.openhab.binding.homeconnect.internal.client.exception.CommunicationException;
+import org.openhab.binding.homeconnect.internal.client.model.AvailableProgramOption;
 import org.openhab.binding.homeconnect.internal.type.HomeConnectDynamicStateDescriptionProvider;
 import org.openhab.core.library.types.StringType;
 import org.openhab.core.thing.ChannelUID;
@@ -121,6 +122,47 @@ public class HomeConnectWasherHandler extends AbstractHomeConnectThingHandler {
                         event.getValue() == null ? UnDefType.UNDEF : new StringType(event.getValue()))));
     }
 
+    @Override
+    protected void configureUnsupportedProgramOptions(Map<String, List<AvailableProgramOption>> programOptions) {
+        programOptions.put("LaundryCare.Washer.Program.Cotton.Eco4060", List.of(
+                new AvailableProgramOption(OPTION_WASHER_TEMPERATURE, List.of(TEMPERATURE_AUTO)),
+                new AvailableProgramOption(OPTION_WASHER_SPIN_SPEED,
+                        List.of(SPIN_SPEED_400, SPIN_SPEED_600, SPIN_SPEED_800, SPIN_SPEED_1200, SPIN_SPEED_1400))));
+
+        programOptions.put("LaundryCare.Washer.Program.Cotton.Colour", List.of(
+                new AvailableProgramOption(OPTION_WASHER_TEMPERATURE,
+                        List.of(TEMPERATURE_COLD, TEMPERATURE_20, TEMPERATURE_30, TEMPERATURE_40, TEMPERATURE_60,
+                                TEMPERATURE_90)),
+                new AvailableProgramOption(OPTION_WASHER_SPIN_SPEED,
+                        List.of(SPIN_SPEED_400, SPIN_SPEED_600, SPIN_SPEED_800, SPIN_SPEED_1200, SPIN_SPEED_1400))));
+
+        // Auto30 is a supported program provided by the API but the API returns empty options, so we defined predefined
+        // values for this program
+        programOptions.put("LaundryCare.Washer.Program.Auto30",
+                List.of(new AvailableProgramOption(OPTION_WASHER_TEMPERATURE, List.of(TEMPERATURE_AUTO)),
+                        new AvailableProgramOption(OPTION_WASHER_SPIN_SPEED, List.of(SPIN_SPEED_AUTO))));
+
+        programOptions.put("LaundryCare.Washer.Program.Super153045.Super1530",
+                List.of(new AvailableProgramOption(OPTION_WASHER_TEMPERATURE,
+                        List.of(TEMPERATURE_COLD, TEMPERATURE_20, TEMPERATURE_30, TEMPERATURE_40)),
+                        new AvailableProgramOption(OPTION_WASHER_SPIN_SPEED,
+                                List.of(SPIN_SPEED_400, SPIN_SPEED_600, SPIN_SPEED_800, SPIN_SPEED_1200))));
+
+        programOptions.put("LaundryCare.Washer.Program.Rinse",
+                List.of(new AvailableProgramOption(OPTION_WASHER_TEMPERATURE, List.of()),
+                        new AvailableProgramOption(OPTION_WASHER_SPIN_SPEED, List.of(SPIN_SPEED_OFF, SPIN_SPEED_400,
+                                SPIN_SPEED_600, SPIN_SPEED_800, SPIN_SPEED_1200, SPIN_SPEED_1400))));
+
+        programOptions.put("LaundryCare.Washer.Program.Spin.SpinDrain",
+                List.of(new AvailableProgramOption(OPTION_WASHER_TEMPERATURE, List.of()),
+                        new AvailableProgramOption(OPTION_WASHER_SPIN_SPEED, List.of(SPIN_SPEED_OFF, SPIN_SPEED_400,
+                                SPIN_SPEED_600, SPIN_SPEED_800, SPIN_SPEED_1200, SPIN_SPEED_1400))));
+
+        programOptions.put("LaundryCare.Washer.Program.DrumClean",
+                List.of(new AvailableProgramOption(OPTION_WASHER_TEMPERATURE, List.of()),
+                        new AvailableProgramOption(OPTION_WASHER_SPIN_SPEED, List.of(SPIN_SPEED_1200))));
+    }
+
     @Override
     protected boolean isChannelLinkedToProgramOptionNotFullySupportedByApi() {
         return (getThingChannel(CHANNEL_WASHER_IDOS1).isPresent() && isLinked(CHANNEL_WASHER_IDOS1))