diff --git a/CODEOWNERS b/CODEOWNERS
index 9da5cbb79ee..d2acdc2d780 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -96,6 +96,7 @@
/bundles/org.openhab.binding.ecovacs/ @maniac103
/bundles/org.openhab.binding.ecowatt/ @lolodomo
/bundles/org.openhab.binding.ekey/ @hmerk
+/bundles/org.openhab.binding.electroluxappliance/ @jannegpriv
/bundles/org.openhab.binding.elerotransmitterstick/ @vbier
/bundles/org.openhab.binding.elroconnects/ @mherwege
/bundles/org.openhab.binding.emotiva/ @espenaf
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index 3bd18dbc0ba..992b960a5e9 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -471,6 +471,11 @@
org.openhab.binding.ekey
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.electroluxappliance
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.elerotransmitterstick
diff --git a/bundles/org.openhab.binding.electroluxappliance/NOTICE b/bundles/org.openhab.binding.electroluxappliance/NOTICE
new file mode 100644
index 00000000000..38d625e3492
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/NOTICE
@@ -0,0 +1,13 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
diff --git a/bundles/org.openhab.binding.electroluxappliance/README.md b/bundles/org.openhab.binding.electroluxappliance/README.md
new file mode 100644
index 00000000000..a1cc8f7f7e2
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/README.md
@@ -0,0 +1,122 @@
+# Electrolux Appliance Binding
+
+This is a binding for Electrolux appliances.
+
+## Supported Things
+
+This binding supports the following thing types:
+
+- api: Bridge - Implements the Electrolux Group API that is used to communicate with the different appliances
+- air-purifier: The Electrolux Air Purifier
+- washing-machine: The Electrolux Washing Machine
+
+## Discovery
+
+After the configuration of the `api` bridge, your Electrolux appliances will be automatically discovered and placed as a thing in the inbox.
+
+### Configuration Options
+
+Only the bridge requires manual configuration.
+The Electrolux appliance things can be added by hand, or you can let the discovery mechanism automatically find them.
+
+#### `api` Bridge
+
+| Parameter | Description | Type | Default | Required |
+|--------------|--------------------------------------------------------|--------|----------|----------|
+| apiKey | Your created API key on developer.electrolux.one | String | NA | yes |
+| refreshToken | Your created refresh token on developer.electrolux.one | String | NA | yes |
+| refresh | Specifies the refresh interval in second | Number | 600 | yes |
+
+#### `air-purifier` Electrolux Air Purifier
+
+| Parameter | Description | Type | Default | Required |
+|--------------|--------------------------------------------------------------------------|--------|----------|----------|
+| serialNumber | Serial Number of your Electrolux appliance found in the Electrolux app | Number | NA | yes |
+
+#### `washing-machine` Electrolux Washing Machine
+
+| Parameter | Description | Type | Default | Required |
+|--------------|--------------------------------------------------------------------------|--------|----------|----------|
+| serialNumber | Serial Number of your Electrolux appliance found in the Electrolux app | Number | NA | yes |
+
+## Channels
+
+### Electrolux Air Purifier
+
+The following channels are supported:
+
+| Channel Type ID | Item Type | Description |
+|-----------------------------|-----------------------|--------------------------------------------------------------------------------|
+| temperature | Number:Temperature | This channel reports the current temperature. |
+| humidity | Number:Dimensionless | This channel reports the current humidity in percentage. |
+| tvoc | Number:Dimensionless | This channel reports the total Volatile Organic Compounds in ppb. |
+| pm1 | Number:Density | This channel reports the Particulate Matter 1 in microgram/m3. |
+| pm2_5 | Number:Density | This channel reports the Particulate Matter 2.5 in microgram/m3. |
+| pm10 | Number:Density | This channel reports the Particulate Matter 10 in microgram/m3. |
+| co2 | Number:Dimensionless | This channel reports the CO2 level in ppm. |
+| fan-speed | Number | This channel sets and reports the current fan speed (1-9). |
+| filter-life | Number:Dimensionless | This channel reports the remaining filter life in %. |
+| ionizer | Switch | This channel sets and reports the status of the Ionizer function (On/Off). |
+| door-state | Contact | This channel reports the status of the door (Opened/Closed). |
+| work-mode | String | This channel sets and reports the current work mode (Auto, Manual, PowerOff.) |
+| ui-light | Switch | This channel sets and reports the status of the UI Light function (On/Off). |
+| safety-lock | Switch | This channel sets and reports the status of the Safety Lock function. |
+| status | String | This channel is used to fetch latest status from the API. |
+
+### Electrolux Washing Machine
+
+The following channels are supported:
+
+| Channel Type ID | Item Type | Description |
+|------------------------------|-----------------------|--------------------------------------------------------------------------------|
+| door-state | Contact | This channel reports the status of the door (Opened/Closed). |
+| door-lock | Contact | This channel reports the status of the door lock. |
+| time-to-start | Number:Time | This channel reports the remaining time for a delayed start washing program. |
+| time-to-end | Number:Time | This channel reports the remaining time to the end for a washing program. |
+| cycle-phase | String | This channel reports the washing cycle phase. |
+| analog-temperature | String | This channel reports the washing temperature. |
+| steam-value | String | This channel reports the washing steam value. |
+| programs-order | String | This channel reports the washing program. |
+| analog-spin-speed | String | This channel reports the washing spin speed. |
+| appliance-state | String | This channel reports the appliance state. |
+| appliance-mode | String | This channel reports the appliance mode. |
+| appliance-total-working-time | Number:Time | This channel reports the total working time for the washing machine. |
+| appliance-ui-sw-version | String | This channel reports the appliance UI SW version. |
+| optisense-result | String | This channel reports the optisense result. |
+| detergent-extradosage | String | This channel reports the detergent extra dosage. |
+| softener-extradosage | String | This channel reports the softener extra dosage. |
+| water-usage | Number:Volume | This channel reports the water usage in litres. |
+| total-wash-cycles-count | Number | This channel reports the total number of washing cycles. |
+| status | String | This channel is used to fetch latest status from the API. |
+
+## Full Example
+
+### `demo.things` Example
+
+```java
+// Bridge configuration
+Bridge electroluxappliance:api:myAPI "Electrolux Group API" [apiKey="12345678", refreshToken="12345678", refresh="300"] {
+ Thing air-purifier myair-purifier "Electrolux Pure A9" [ serialNummber="123456789" ]
+}
+```
+
+## `demo.items` Example
+
+```java
+// CO2
+Number:Dimensionless electroluxapplianceCO2 "Electrolux Air CO2 [%d ppm]" {channel="electroluxappliance:air-purifier:myAPI:myair-purifier:co2"}
+// Temperature
+Number:Temperature electroluxapplianceTemperature "Electrolux Air Temperature" {channel="electroluxappliance:air-purifier:myAPI:myair-purifier:temperature"}
+// Door status
+Contact electroluxapplianceDoor "Electrolux Air Door Status" {channel="electroluxappliance:air-purifier:myAPI:myair-purifier:doorOpen"}
+// Work mode
+String electroluxapplianceWorkModeSetting "electroluxappliance Work Mode Setting" {channel="electroluxappliance:air-purifier:myAPI:myair-purifier:workMode"}
+// Fan speed
+Number electroluxapplianceFanSpeed "Electrolux Air Fan Speed Setting" {channel="electroluxappliance:air-purifier:myAPI:myair-purifier:fanSpeed"}
+// UI Light
+Switch electroluxapplianceUILight "Electrolux Air UI Light Setting" {channel="electroluxappliance:air-purifier:myAPI:myair-purifier:uiLight"}
+// Ionizer
+Switch electroluxapplianceIonizer "Electrolux Air Ionizer Setting" {channel="electroluxappliance:air-purifier:myAPI:myair-purifier:ionizer"}
+// Safety Lock
+Switch electroluxapplianceSafetyLock "Electrolux Air Safety Lock Setting" {channel="electroluxappliance:air-purifier:myAPI:myair-purifier:safetyLock"}
+```
diff --git a/bundles/org.openhab.binding.electroluxappliance/pom.xml b/bundles/org.openhab.binding.electroluxappliance/pom.xml
new file mode 100644
index 00000000000..f6875793970
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 4.3.0-SNAPSHOT
+
+
+ org.openhab.binding.electroluxappliance
+
+ openHAB Add-ons :: Bundles :: Electrolux Appliance Binding
+
+
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/feature/feature.xml b/bundles/org.openhab.binding.electroluxappliance/src/main/feature/feature.xml
new file mode 100644
index 00000000000..40075eb948e
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/feature/feature.xml
@@ -0,0 +1,9 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ mvn:org.openhab.addons.bundles/org.openhab.binding.electroluxappliance/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/ElectroluxApplianceBindingConstants.java b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/ElectroluxApplianceBindingConstants.java
new file mode 100644
index 00000000000..36d5572c90d
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/ElectroluxApplianceBindingConstants.java
@@ -0,0 +1,91 @@
+/**
+ * Copyright (c) 2010-2024 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.electroluxappliance.internal;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link ElectroluxApplianceBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+@NonNullByDefault
+public class ElectroluxApplianceBindingConstants {
+
+ public static final String BINDING_ID = "electroluxappliance";
+
+ // List of all Thing Type UIDs
+ public static final ThingTypeUID THING_TYPE_ELECTROLUX_AIR_PURIFIER = new ThingTypeUID(BINDING_ID, "air-purifier");
+ public static final ThingTypeUID THING_TYPE_ELECTROLUX_WASHING_MACHINE = new ThingTypeUID(BINDING_ID,
+ "washing-machine");
+ public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "api");
+
+ // List of all common Channel ids
+ public static final String CHANNEL_DOOR_STATE = "door-state";
+
+ // List of all Channel ids for Air Purifers
+ public static final String CHANNEL_STATUS = "status";
+ public static final String CHANNEL_TEMPERATURE = "temperature";
+ public static final String CHANNEL_HUMIDITY = "humidity";
+ public static final String CHANNEL_TVOC = "tvoc";
+ public static final String CHANNEL_PM1 = "pm1";
+ public static final String CHANNEL_PM25 = "pm2_5";
+ public static final String CHANNEL_PM10 = "pm10";
+ public static final String CHANNEL_CO2 = "co2";
+ public static final String CHANNEL_FILTER_LIFE = "filter-life";
+ public static final String CHANNEL_FAN_SPEED = "fan-speed";
+ public static final String CHANNEL_WORK_MODE = "work-mode";
+ public static final String CHANNEL_IONIZER = "ionizer";
+ public static final String CHANNEL_UI_LIGHT = "ui-light";
+ public static final String CHANNEL_SAFETY_LOCK = "safety-lock";
+
+ // List of all Channel ids for Washing Machines
+ public static final String CHANNEL_DOOR_LOCK = "door-lock";
+ public static final String CHANNEL_TIME_TO_START = "time-to-start";
+ public static final String CHANNEL_TIME_TO_END = "time-to-end";
+ public static final String CHANNEL_APPLIANCE_UI_SW_VERSION = "appliance-ui-sw-version";
+ public static final String CHANNEL_APPLIANCE_TOTAL_WORKING_TIME = "appliance-total-working-time";
+ public static final String CHANNEL_APPLIANCE_STATE = "appliance-state";
+ public static final String CHANNEL_APPLIANCE_MODE = "appliance-mode";
+ public static final String CHANNEL_OPTISENSE_RESULT = "optisense-result";
+ public static final String CHANNEL_DETERGENT_EXTRA_DOSAGE = "detergent-extradosage";
+ public static final String CHANNEL_SOFTENER_EXTRA_DOSAGE = "softener-extradosage";
+ public static final String CHANNEL_WATER_USAGE = "water-usage";
+ public static final String CHANNEL_CYCLE_PHASE = "cycle-phase";
+ public static final String CHANNEL_TOTAL_WASH_CYCLES_COUNT = "total-wash-cycles-count";
+ public static final String CHANNEL_ANALOG_TEMPERATURE = "analog-temperature";
+ public static final String CHANNEL_ANALOG_SPIN_SPEED = "analog-spin-speed";
+ public static final String CHANNEL_STEAM_VALUE = "steam-value";
+ public static final String CHANNEL_PROGRAMS_ORDER = "programs-order";
+
+ // List of all Properties ids
+ public static final String PROPERTY_BRAND = "brand";
+ public static final String PROPERTY_COLOUR = "colour";
+ public static final String PROPERTY_MODEL = "model";
+ public static final String PROPERTY_DEVICE = "device";
+ public static final String PROPERTY_FW_VERSION = "fwVersion";
+ public static final String PROPERTY_SERIAL_NUMBER = "serialNumber";
+ public static final String PROPERTY_WORKMODE = "workmode";
+
+ // List of all Commands for Air Purifiers
+ public static final String COMMAND_WORKMODE_POWEROFF = "PowerOff";
+ public static final String COMMAND_WORKMODE_AUTO = "Auto";
+ public static final String COMMAND_WORKMODE_MANUAL = "Manual";
+
+ public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BRIDGE,
+ THING_TYPE_ELECTROLUX_AIR_PURIFIER, THING_TYPE_ELECTROLUX_WASHING_MACHINE);
+}
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/ElectroluxApplianceBridgeConfiguration.java b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/ElectroluxApplianceBridgeConfiguration.java
new file mode 100644
index 00000000000..98d57686edc
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/ElectroluxApplianceBridgeConfiguration.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2010-2024 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.electroluxappliance.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link ElectroluxApplianceBridgeConfiguration} class contains fields mapping bridge configuration parameters.
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+@NonNullByDefault
+public class ElectroluxApplianceBridgeConfiguration {
+ public String apiKey = "";
+ public String refreshToken = "";
+ public int refresh = 600;
+}
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/ElectroluxApplianceConfiguration.java b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/ElectroluxApplianceConfiguration.java
new file mode 100644
index 00000000000..814d5b3e060
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/ElectroluxApplianceConfiguration.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2024 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.electroluxappliance.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link ElectroluxApplianceConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+@NonNullByDefault
+public class ElectroluxApplianceConfiguration {
+ public static final String SERIAL_NUMBER_LABEL = "serialNumber";
+
+ private String serialNumber = "";
+
+ public String getSerialNumber() {
+ return serialNumber;
+ }
+}
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/ElectroluxApplianceException.java b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/ElectroluxApplianceException.java
new file mode 100644
index 00000000000..3fe00ef70c4
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/ElectroluxApplianceException.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2010-2024 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.electroluxappliance.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * {@link ElectroluxApplianceException} is used when there is exception communicating with Electrolux Delta API.
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+@NonNullByDefault
+public class ElectroluxApplianceException extends Exception {
+
+ private static final long serialVersionUID = 2543564118231301159L;
+
+ public ElectroluxApplianceException(Exception source) {
+ super(source);
+ }
+
+ public ElectroluxApplianceException(String message) {
+ super(message);
+ }
+
+ @Override
+ public @Nullable String getMessage() {
+ Throwable throwable = getCause();
+ if (throwable != null) {
+ String localMessage = throwable.getMessage();
+ if (localMessage != null) {
+ return localMessage;
+ }
+ }
+ return "";
+ }
+}
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/api/ElectroluxGroupAPI.java b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/api/ElectroluxGroupAPI.java
new file mode 100644
index 00000000000..5b2366fefd8
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/api/ElectroluxGroupAPI.java
@@ -0,0 +1,322 @@
+/**
+ * Copyright (c) 2010-2024 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.electroluxappliance.internal.api;
+
+import java.time.Instant;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.ws.rs.core.MediaType;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+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.electroluxappliance.internal.ElectroluxApplianceBridgeConfiguration;
+import org.openhab.binding.electroluxappliance.internal.ElectroluxApplianceException;
+import org.openhab.binding.electroluxappliance.internal.dto.AirPurifierStateDTO;
+import org.openhab.binding.electroluxappliance.internal.dto.ApplianceDTO;
+import org.openhab.binding.electroluxappliance.internal.dto.ApplianceInfoDTO;
+import org.openhab.binding.electroluxappliance.internal.dto.ApplianceStateDTO;
+import org.openhab.binding.electroluxappliance.internal.dto.WashingMachineStateDTO;
+import org.openhab.binding.electroluxappliance.internal.listener.TokenUpdateListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The {@link ElectroluxGroupAPI} class defines the Elextrolux Group API
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+@NonNullByDefault
+public class ElectroluxGroupAPI {
+
+ private static final String BASE_URL = "https://api.developer.electrolux.one";
+ private static final String TOKEN_URL = BASE_URL + "/api/v1/token/refresh";
+ private static final String APPLIANCES_URL = BASE_URL + "/api/v1/appliances";
+
+ private static final int MAX_RETRIES = 3;
+ private static final int REQUEST_TIMEOUT_MS = 10_000;
+
+ private final Logger logger = LoggerFactory.getLogger(ElectroluxGroupAPI.class);
+ private final Gson gson;
+ private final HttpClient httpClient;
+ private final ElectroluxApplianceBridgeConfiguration configuration;
+ private String accessToken = "";
+ private Instant tokenExpiry = Instant.MAX;
+ private final TokenUpdateListener tokenUpdateListener;
+
+ public ElectroluxGroupAPI(ElectroluxApplianceBridgeConfiguration configuration, Gson gson, HttpClient httpClient,
+ TokenUpdateListener listener) {
+ this.gson = gson;
+ this.configuration = configuration;
+ this.httpClient = httpClient;
+ this.tokenUpdateListener = listener;
+ }
+
+ public boolean refresh(Map electroluxApplianceThings, boolean isCommunicationError) {
+ try {
+ if (Instant.now().isAfter(this.tokenExpiry) || isCommunicationError) {
+ logger.debug("Is communication error: {}", isCommunicationError);
+ // Refresh since token has expired
+ refreshToken();
+ } else {
+ logger.debug("Now: {} Token expiry: {}", Instant.now(), this.tokenExpiry);
+
+ }
+ // Get all appliances
+ String json = getAppliances();
+ ApplianceDTO[] dtos = gson.fromJson(json, ApplianceDTO[].class);
+ if (dtos != null) {
+ for (ApplianceDTO dto : dtos) {
+ String applianceId = dto.getApplianceId();
+ // Get appliance info
+ String jsonApplianceInfo = getApplianceInfo(applianceId);
+ ApplianceInfoDTO applianceInfo = gson.fromJson(jsonApplianceInfo, ApplianceInfoDTO.class);
+ if (applianceInfo != null) {
+ dto.setApplianceInfo(applianceInfo);
+ if ("AIR_PURIFIER".equals(applianceInfo.getApplianceInfo().getDeviceType())) {
+ // Get appliance state
+ String jsonApplianceState = getApplianceState(applianceId);
+ ApplianceStateDTO applianceState = gson.fromJson(jsonApplianceState,
+ AirPurifierStateDTO.class);
+ if (applianceState != null) {
+ dto.setApplianceState(applianceState);
+ }
+ electroluxApplianceThings.put(applianceInfo.getApplianceInfo().getSerialNumber(), dto);
+ } else if ("WASHING_MACHINE".equals(applianceInfo.getApplianceInfo().getDeviceType())) {
+ // Get appliance state
+ String jsonApplianceState = getApplianceState(applianceId);
+ ApplianceStateDTO applianceState = gson.fromJson(jsonApplianceState,
+ WashingMachineStateDTO.class);
+ if (applianceState != null) {
+ dto.setApplianceState(applianceState);
+ }
+ electroluxApplianceThings.put(applianceInfo.getApplianceInfo().getSerialNumber(), dto);
+ }
+ }
+ }
+ return true;
+ }
+ } catch (JsonSyntaxException | ElectroluxApplianceException e) {
+ logger.warn("Failed to refresh! {}", e.getMessage());
+ }
+ return false;
+ }
+
+ public boolean workModePowerOff(String applianceId) {
+ String commandJSON = "{ \"Workmode\": \"PowerOff\" }";
+ try {
+ return sendCommand(commandJSON, applianceId);
+ } catch (ElectroluxApplianceException e) {
+ logger.warn("Work mode powerOff failed {}", e.getMessage());
+ }
+ return false;
+ }
+
+ public boolean workModeAuto(String applianceId) {
+ String commandJSON = "{ \"Workmode\": \"Auto\" }";
+ try {
+ return sendCommand(commandJSON, applianceId);
+ } catch (ElectroluxApplianceException e) {
+ logger.warn("Work mode auto failed {}", e.getMessage());
+ }
+ return false;
+ }
+
+ public boolean workModeManual(String applianceId) {
+ String commandJSON = "{ \"Workmode\": \"Manual\" }";
+ try {
+ return sendCommand(commandJSON, applianceId);
+ } catch (ElectroluxApplianceException e) {
+ logger.warn("Work mode manual failed {}", e.getMessage());
+ }
+ return false;
+ }
+
+ public boolean setFanSpeedLevel(String applianceId, int fanSpeedLevel) {
+ if (fanSpeedLevel < 1 && fanSpeedLevel > 10) {
+ return false;
+ } else {
+ String commandJSON = "{ \"Fanspeed\": " + fanSpeedLevel + "}";
+ try {
+ return sendCommand(commandJSON, applianceId);
+ } catch (ElectroluxApplianceException e) {
+ logger.warn("Work mode manual failed {}", e.getMessage());
+ }
+ }
+ return false;
+ }
+
+ public boolean setIonizer(String applianceId, String ionizerStatus) {
+ String commandJSON = "{ \"Ionizer\": " + ionizerStatus + "}";
+ try {
+ return sendCommand(commandJSON, applianceId);
+ } catch (ElectroluxApplianceException e) {
+ logger.warn("Work mode manual failed {}", e.getMessage());
+ }
+ return false;
+ }
+
+ public boolean setUILight(String applianceId, String uiLightStatus) {
+ String commandJSON = "{ \"UILight\": " + uiLightStatus + "}";
+ try {
+ return sendCommand(commandJSON, applianceId);
+ } catch (ElectroluxApplianceException e) {
+ logger.warn("Work mode manual failed {}", e.getMessage());
+ }
+ return false;
+ }
+
+ public boolean setSafetyLock(String applianceId, String safetyLockStatus) {
+ String commandJSON = "{ \"SafetyLock\": " + safetyLockStatus + "}";
+ try {
+ return sendCommand(commandJSON, applianceId);
+ } catch (ElectroluxApplianceException e) {
+ logger.warn("Work mode manual failed {}", e.getMessage());
+ }
+ return false;
+ }
+
+ private Request createRequest(String uri, HttpMethod httpMethod) {
+ Request request = httpClient.newRequest(uri).method(httpMethod);
+ request.timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ request.header(HttpHeader.ACCEPT, MediaType.APPLICATION_JSON);
+ request.header(HttpHeader.CONTENT_TYPE, MediaType.APPLICATION_JSON);
+
+ logger.trace("HTTP Request {}.", request.toString());
+
+ return request;
+ }
+
+ private void refreshToken() throws ElectroluxApplianceException {
+ try {
+ String json = "{\"refreshToken\": \"" + this.configuration.refreshToken + "\"}";
+ Request request = createRequest(TOKEN_URL, HttpMethod.POST);
+ request.content(new StringContentProvider(json), MediaType.APPLICATION_JSON);
+ logger.debug("HTTP POST Request {}.", request.toString());
+ ContentResponse httpResponse;
+ httpResponse = request.send();
+ if (httpResponse.getStatus() != HttpStatus.OK_200) {
+ throw new ElectroluxApplianceException("Failed to refresh tokens" + httpResponse.getContentAsString());
+ }
+ json = httpResponse.getContentAsString();
+ logger.trace("Tokens: {}", json);
+ JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject();
+ this.accessToken = jsonObject.get("accessToken").getAsString();
+ this.configuration.refreshToken = jsonObject.get("refreshToken").getAsString();
+ // Notify the listener about the updated tokens
+ tokenUpdateListener.onTokenUpdated(this.configuration.refreshToken);
+ long expiresIn = jsonObject.get("expiresIn").getAsLong();
+ logger.debug("Token expires in: {}s", expiresIn);
+ this.tokenExpiry = Instant.now().plusSeconds(expiresIn);
+ logger.debug("Token expires: {}", this.tokenExpiry);
+ } catch (InterruptedException | TimeoutException | ExecutionException e) {
+ throw new ElectroluxApplianceException(e);
+ }
+ }
+
+ private String getFromApi(String uri) throws ElectroluxApplianceException, InterruptedException {
+ try {
+ for (int i = 0; i < MAX_RETRIES; i++) {
+ try {
+ Request request = createRequest(uri, HttpMethod.GET);
+ request.header("x-api-key", this.configuration.apiKey);
+ request.header(HttpHeader.AUTHORIZATION, "Bearer " + this.accessToken);
+ logger.trace("Request header {}", request);
+
+ ContentResponse response = request.send();
+ String content = response.getContentAsString();
+ logger.trace("API response: {}", content);
+
+ if (response.getStatus() != HttpStatus.OK_200) {
+ logger.debug("getFromApi failed, HTTP status: {}", response.getStatus());
+ refreshToken();
+ } else {
+ return content;
+ }
+ } catch (TimeoutException e) {
+ logger.debug("TimeoutException error in get: {}", e.getMessage());
+ }
+ }
+ throw new ElectroluxApplianceException("Failed to fetch from API!");
+ } catch (JsonSyntaxException | ElectroluxApplianceException | ExecutionException e) {
+ throw new ElectroluxApplianceException(e);
+ }
+ }
+
+ private String getAppliances() throws ElectroluxApplianceException {
+ try {
+ return getFromApi(APPLIANCES_URL);
+ } catch (ElectroluxApplianceException | InterruptedException e) {
+ throw new ElectroluxApplianceException(e);
+ }
+ }
+
+ private String getApplianceInfo(String applianceId) throws ElectroluxApplianceException {
+ try {
+ return getFromApi(APPLIANCES_URL + "/" + applianceId + "/info");
+ } catch (ElectroluxApplianceException | InterruptedException e) {
+ throw new ElectroluxApplianceException(e);
+ }
+ }
+
+ private String getApplianceState(String applianceId) throws ElectroluxApplianceException {
+ try {
+ return getFromApi(APPLIANCES_URL + "/" + applianceId + "/state");
+ } catch (ElectroluxApplianceException | InterruptedException e) {
+ throw new ElectroluxApplianceException(e);
+ }
+ }
+
+ private boolean sendCommand(String commandJSON, String applianceId) throws ElectroluxApplianceException {
+ try {
+ for (int i = 0; i < MAX_RETRIES; i++) {
+ try {
+ Request request = createRequest(APPLIANCES_URL + "/" + applianceId + "/command", HttpMethod.PUT);
+ request.header(HttpHeader.AUTHORIZATION, "Bearer " + this.accessToken);
+ request.header("x-api-key", this.configuration.apiKey);
+ request.content(new StringContentProvider(commandJSON), MediaType.APPLICATION_JSON);
+ logger.trace("Command JSON: {}", commandJSON);
+
+ ContentResponse response = request.send();
+ String content = response.getContentAsString();
+ logger.trace("API response: {}", content);
+
+ if (response.getStatus() != HttpStatus.OK_200) {
+ logger.debug("sendCommand failed, HTTP status: {}", response.getStatus());
+ refreshToken();
+ } else {
+ return true;
+ }
+ } catch (TimeoutException | InterruptedException e) {
+ logger.warn("TimeoutException error in get");
+ }
+ }
+ } catch (JsonSyntaxException | ElectroluxApplianceException | ExecutionException e) {
+ throw new ElectroluxApplianceException(e);
+ }
+ return false;
+ }
+}
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/discovery/ElectroluxApplianceDiscoveryService.java b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/discovery/ElectroluxApplianceDiscoveryService.java
new file mode 100644
index 00000000000..3a3e54159b4
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/discovery/ElectroluxApplianceDiscoveryService.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2010-2024 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.electroluxappliance.internal.discovery;
+
+import static org.openhab.binding.electroluxappliance.internal.ElectroluxApplianceBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.electroluxappliance.internal.ElectroluxApplianceConfiguration;
+import org.openhab.binding.electroluxappliance.internal.handler.ElectroluxApplianceBridgeHandler;
+import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.thing.ThingUID;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ServiceScope;
+
+/**
+ * The {@link ElectroluxApplianceDiscoveryService} searches for available
+ * Electrolux Pure A9 discoverable through Electrolux Delta API.
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+@Component(scope = ServiceScope.PROTOTYPE, service = ElectroluxApplianceDiscoveryService.class)
+@NonNullByDefault
+public class ElectroluxApplianceDiscoveryService
+ extends AbstractThingHandlerDiscoveryService {
+ private static final int SEARCH_TIME = 10;
+
+ public ElectroluxApplianceDiscoveryService() {
+ super(ElectroluxApplianceBridgeHandler.class, SUPPORTED_THING_TYPES_UIDS, SEARCH_TIME);
+ }
+
+ @Override
+ protected void startScan() {
+ ThingUID bridgeUID = thingHandler.getThing().getUID();
+ thingHandler.getElectroluxApplianceThings().entrySet().stream().forEach(thing -> {
+ String applianceType = thing.getValue().getApplianceType();
+
+ if ("PUREA9".equalsIgnoreCase(applianceType)) {
+ thingDiscovered(DiscoveryResultBuilder
+ .create(new ThingUID(THING_TYPE_ELECTROLUX_AIR_PURIFIER, bridgeUID, thing.getKey()))
+ .withLabel("Electrolux Pure A9").withBridge(bridgeUID)
+ .withProperty(ElectroluxApplianceConfiguration.SERIAL_NUMBER_LABEL, thing.getKey())
+ .withRepresentationProperty(ElectroluxApplianceConfiguration.SERIAL_NUMBER_LABEL).build());
+ } else if ("WM".equalsIgnoreCase(applianceType)) {
+ thingDiscovered(DiscoveryResultBuilder
+ .create(new ThingUID(THING_TYPE_ELECTROLUX_WASHING_MACHINE, bridgeUID, thing.getKey()))
+ .withLabel("Electrolux Washing Machine").withBridge(bridgeUID)
+ .withProperty(ElectroluxApplianceConfiguration.SERIAL_NUMBER_LABEL, thing.getKey())
+ .withRepresentationProperty(ElectroluxApplianceConfiguration.SERIAL_NUMBER_LABEL).build());
+ }
+ });
+
+ stopScan();
+ }
+}
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/dto/AirPurifierStateDTO.java b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/dto/AirPurifierStateDTO.java
new file mode 100644
index 00000000000..a4790dedb6f
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/dto/AirPurifierStateDTO.java
@@ -0,0 +1,346 @@
+/**
+ * Copyright (c) 2010-2024 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.electroluxappliance.internal.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link AirPurifierStateDTO} class defines the DTO for the Electrolux Purifiers.
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+@NonNullByDefault
+public class AirPurifierStateDTO extends ApplianceStateDTO {
+
+ @SerializedName("properties")
+ private Properties properties = new Properties();
+
+ public Properties getProperties() {
+ return properties;
+ }
+
+ // Inner class for Properties
+ public static class Properties {
+ @SerializedName("reported")
+ private Reported reported = new Reported();
+
+ public Reported getReported() {
+ return reported;
+ }
+ }
+
+ // Inner class for Reported properties
+ public static class Reported {
+ @SerializedName("FrmVer_NIU")
+ private String frmVerNIU = "";
+
+ @SerializedName("Workmode")
+ private String workmode = "";
+
+ @SerializedName("FilterRFID")
+ private String filterRFID = "";
+
+ @SerializedName("FilterLife")
+ private int filterLife;
+
+ @SerializedName("Fanspeed")
+ private int fanspeed;
+
+ @SerializedName("UILight")
+ private boolean uiLight;
+
+ @SerializedName("SafetyLock")
+ private boolean safetyLock;
+
+ @SerializedName("Ionizer")
+ private boolean ionizer;
+
+ @SerializedName("Sleep")
+ private boolean sleep;
+
+ @SerializedName("Scheduler")
+ private boolean scheduler;
+
+ @SerializedName("FilterType")
+ private int filterType;
+
+ @SerializedName("DspIcoPM2_5")
+ private boolean dspIcoPM25;
+
+ @SerializedName("DspIcoPM1")
+ private boolean dspIcoPM1;
+
+ @SerializedName("DspIcoPM10")
+ private boolean dspIcoPM10;
+
+ @SerializedName("DspIcoTVOC")
+ private boolean dspIcoTVOC;
+
+ @SerializedName("ErrPM2_5")
+ private boolean errPM25;
+
+ @SerializedName("ErrTVOC")
+ private boolean errTVOC;
+
+ @SerializedName("ErrTempHumidity")
+ private boolean errTempHumidity;
+
+ @SerializedName("ErrFanMtr")
+ private boolean errFanMtr;
+
+ @SerializedName("ErrCommSensorDisplayBrd")
+ private boolean errCommSensorDisplayBrd;
+
+ @SerializedName("DoorOpen")
+ private boolean doorOpen;
+
+ @SerializedName("ErrRFID")
+ private boolean errRFID;
+
+ @SerializedName("SignalStrength")
+ private String signalStrength = "";
+
+ @SerializedName("logE")
+ private int logE;
+
+ @SerializedName("logW")
+ private int logW;
+
+ @SerializedName("InterfaceVer")
+ private int interfaceVer;
+
+ @SerializedName("VmNo_NIU")
+ private String vmNoNIU = "";
+
+ @SerializedName("TVOCBrand")
+ private String tvocBrand = "";
+
+ private Capabilities capabilities = new Capabilities();
+
+ private Tasks tasks = new Tasks();
+
+ @SerializedName("$version")
+ private int version;
+
+ private String deviceId = "";
+
+ @SerializedName("CO2")
+ private int co2;
+
+ @SerializedName("TVOC")
+ private int tvoc;
+
+ @SerializedName("Temp")
+ private int temp;
+
+ @SerializedName("Humidity")
+ private int humidity;
+
+ @SerializedName("RSSI")
+ private int rssi;
+
+ @SerializedName("PM1")
+ private int pm1;
+
+ @SerializedName("PM2_5")
+ private int pm25;
+
+ @SerializedName("PM10")
+ private int pm10;
+
+ @SerializedName("ECO2")
+ private int eco2;
+
+ // Getters for all fields
+ public String getFrmVerNIU() {
+ return frmVerNIU;
+ }
+
+ public String getWorkmode() {
+ return workmode;
+ }
+
+ public String getFilterRFID() {
+ return filterRFID;
+ }
+
+ public int getFilterLife() {
+ return filterLife;
+ }
+
+ public int getFanspeed() {
+ return fanspeed;
+ }
+
+ public boolean isUiLight() {
+ return uiLight;
+ }
+
+ public boolean isSafetyLock() {
+ return safetyLock;
+ }
+
+ public boolean isIonizer() {
+ return ionizer;
+ }
+
+ public boolean isSleep() {
+ return sleep;
+ }
+
+ public boolean isScheduler() {
+ return scheduler;
+ }
+
+ public int getFilterType() {
+ return filterType;
+ }
+
+ public boolean isDspIcoPM25() {
+ return dspIcoPM25;
+ }
+
+ public boolean isDspIcoPM1() {
+ return dspIcoPM1;
+ }
+
+ public boolean isDspIcoPM10() {
+ return dspIcoPM10;
+ }
+
+ public boolean isDspIcoTVOC() {
+ return dspIcoTVOC;
+ }
+
+ public boolean isErrPM25() {
+ return errPM25;
+ }
+
+ public boolean isErrTVOC() {
+ return errTVOC;
+ }
+
+ public boolean isErrTempHumidity() {
+ return errTempHumidity;
+ }
+
+ public boolean isErrFanMtr() {
+ return errFanMtr;
+ }
+
+ public boolean isErrCommSensorDisplayBrd() {
+ return errCommSensorDisplayBrd;
+ }
+
+ public boolean isDoorOpen() {
+ return doorOpen;
+ }
+
+ public boolean isErrRFID() {
+ return errRFID;
+ }
+
+ public String getSignalStrength() {
+ return signalStrength;
+ }
+
+ public int getLogE() {
+ return logE;
+ }
+
+ public int getLogW() {
+ return logW;
+ }
+
+ public int getInterfaceVer() {
+ return interfaceVer;
+ }
+
+ public String getVmNoNIU() {
+ return vmNoNIU;
+ }
+
+ public String getTvocBrand() {
+ return tvocBrand;
+ }
+
+ public Capabilities getCapabilities() {
+ return capabilities;
+ }
+
+ public Tasks getTasks() {
+ return tasks;
+ }
+
+ public int getVersion() {
+ return version;
+ }
+
+ public String getDeviceId() {
+ return deviceId;
+ }
+
+ public int getCo2() {
+ return co2;
+ }
+
+ public int getTvoc() {
+ return tvoc;
+ }
+
+ public int getTemp() {
+ return temp;
+ }
+
+ public int getHumidity() {
+ return humidity;
+ }
+
+ public int getRssi() {
+ return rssi;
+ }
+
+ public int getPm1() {
+ return pm1;
+ }
+
+ public int getPm25() {
+ return pm25;
+ }
+
+ public int getPm10() {
+ return pm10;
+ }
+
+ public int getEco2() {
+ return eco2;
+ }
+ }
+
+ // Inner class for Capabilities
+ public static class Capabilities {
+ @SerializedName("tasks")
+ private Tasks tasks = new Tasks();
+
+ public Tasks getTasks() {
+ return tasks;
+ }
+ }
+
+ // Inner class for Tasks (assuming it's empty as shown in the JSON)
+ public static class Tasks {
+ // No fields; can be extended as needed
+ }
+}
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/dto/ApplianceDTO.java b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/dto/ApplianceDTO.java
new file mode 100644
index 00000000000..b68a5ad8f6a
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/dto/ApplianceDTO.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2010-2024 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.electroluxappliance.internal.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link ApplianceDTO} class defines the DTO for the Electrolux Appliances.
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+@NonNullByDefault
+public class ApplianceDTO {
+ private String applianceId = "";
+ private String applianceName = "";
+ private String applianceType = "";
+ private String created = "";
+ private ApplianceInfoDTO applianceInfo = new ApplianceInfoDTO();
+ private ApplianceStateDTO applianceState = new ApplianceStateDTO();
+
+ public void setApplianceInfo(ApplianceInfoDTO applianceInfo) {
+ this.applianceInfo = applianceInfo;
+ }
+
+ public void setApplianceState(ApplianceStateDTO applianceState) {
+ this.applianceState = applianceState;
+ }
+
+ public ApplianceInfoDTO getApplianceInfo() {
+ return applianceInfo;
+ }
+
+ public ApplianceStateDTO getApplianceState() {
+ return applianceState;
+ }
+
+ // Getters for each field
+ public String getApplianceId() {
+ return applianceId;
+ }
+
+ public String getApplianceName() {
+ return applianceName;
+ }
+
+ public String getApplianceType() {
+ return applianceType;
+ }
+
+ public String getCreated() {
+ return created;
+ }
+
+ // Optional toString method for easier debugging and logging
+ @Override
+ public String toString() {
+ return "Appliance{" + "applianceId='" + applianceId + '\'' + ", applianceName='" + applianceName + '\''
+ + ", applianceType='" + applianceType + '\'' + ", created='" + created + '\'' + '}';
+ }
+}
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/dto/ApplianceInfoDTO.java b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/dto/ApplianceInfoDTO.java
new file mode 100644
index 00000000000..64b6afd743d
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/dto/ApplianceInfoDTO.java
@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) 2010-2024 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.electroluxappliance.internal.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link ApplianceInfoDTO} class defines the DTO for the Electrolux Appliance Info.
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+
+@NonNullByDefault
+public class ApplianceInfoDTO {
+
+ private ApplianceInfo applianceInfo = new ApplianceInfo();
+
+ // Map capabilities to Object, so details are not parsed
+ private Object capabilities = new Object();
+
+ public ApplianceInfo getApplianceInfo() {
+ return applianceInfo;
+ }
+
+ public Object getCapabilities() {
+ return capabilities;
+ }
+
+ public static class ApplianceInfo {
+ private String serialNumber = "";
+ private String pnc = "";
+ private String brand = "";
+ private String deviceType = "";
+ private String model = "";
+ private String variant = "";
+ private String colour = "";
+
+ // Getters
+ public String getSerialNumber() {
+ return serialNumber;
+ }
+
+ public String getPnc() {
+ return pnc;
+ }
+
+ public String getBrand() {
+ return brand;
+ }
+
+ public String getDeviceType() {
+ return deviceType;
+ }
+
+ public String getModel() {
+ return model;
+ }
+
+ public String getVariant() {
+ return variant;
+ }
+
+ public String getColour() {
+ return colour;
+ }
+
+ @Override
+ public String toString() {
+ return "ApplianceInfo{" + "serialNumber='" + serialNumber + '\'' + ", pnc='" + pnc + '\'' + ", brand='"
+ + brand + '\'' + ", deviceType='" + deviceType + '\'' + ", model='" + model + '\'' + ", variant='"
+ + variant + '\'' + ", colour='" + colour + '\'' + '}';
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/dto/ApplianceStateDTO.java b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/dto/ApplianceStateDTO.java
new file mode 100644
index 00000000000..7193c62cd8d
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/dto/ApplianceStateDTO.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2010-2024 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.electroluxappliance.internal.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link ApplianceStateDTO} class defines the DTO for the Electrolux Appliance State.
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+@NonNullByDefault
+public class ApplianceStateDTO {
+ private String applianceId = "";
+ private String connectionState = "";
+ private String status = "";
+
+ public String getApplianceId() {
+ return applianceId;
+ }
+
+ public String getConnectionState() {
+ return connectionState;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ // You can optionally add a toString() method for easier debugging
+ @Override
+ public String toString() {
+ return "ApplianceStateDTO{" + "applianceId='" + applianceId + '\'' + ", connectionState='" + connectionState
+ + '\'' + ", status='" + status + '\'' + '}';
+ }
+}
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/dto/WashingMachineStateDTO.java b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/dto/WashingMachineStateDTO.java
new file mode 100644
index 00000000000..2d02644b6c0
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/dto/WashingMachineStateDTO.java
@@ -0,0 +1,496 @@
+/**
+ * Copyright (c) 2010-2024 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.electroluxappliance.internal.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link AirPurifierStateDTO} class defines the DTO for the Electrolux Washing Machines.
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+@NonNullByDefault
+public class WashingMachineStateDTO extends ApplianceStateDTO {
+
+ private Properties properties = new Properties();
+
+ public Properties getProperties() {
+ return properties;
+ }
+
+ public static class Properties {
+ private Reported reported = new Reported();
+
+ public Reported getReported() {
+ return reported;
+ }
+ }
+
+ public static class Reported {
+ private String displayLight = "";
+ private String doorState = "";
+ private int timeToEnd;
+ private Miscellaneous miscellaneous = new Miscellaneous();
+ private String applianceUiSwVersion = "";
+ private int applianceTotalWorkingTime;
+ private String remoteControl = "";
+ private String language = "";
+ private FCMiscellaneousState fCMiscellaneousState = new FCMiscellaneousState();
+ private String cyclePhase = "";
+ private String endOfCycleSound = "";
+ private int startTime;
+ private UserSelections userSelections = new UserSelections();
+ private String waterHardness = "";
+ private String defaultExtraRinse = "";
+ private int totalWashingTime;
+ private ApplianceInfo applianceInfo = new ApplianceInfo();
+ private String doorLock = "";
+ private boolean uiLockMode;
+ private int washingNominalLoadWeight;
+ private int totalWashCyclesCount;
+ private int fcOptisenseLoadWeight;
+ private String waterSoftenerMode = "";
+ private String applianceState = "";
+ private String applianceMode = "";
+ private String applianceMainBoardSwVersion = "";
+ private int totalCycleCounter;
+ private int measuredLoadWeight;
+ private Object[] alerts = new Object[0];
+ private Maintenance applianceCareAndMaintenance0 = new Maintenance();
+ private NetworkInterface networkInterface = new NetworkInterface();
+ private Maintenance applianceCareAndMaintenance1 = new Maintenance();
+ private Maintenance applianceCareAndMaintenance2 = new Maintenance();
+ private Maintenance applianceCareAndMaintenance3 = new Maintenance();
+ private AutoDosing autoDosing = new AutoDosing();
+ private String cycleSubPhase = "";
+ private String connectivityState = "";
+
+ // Getters for all fields
+ public String getDisplayLight() {
+ return displayLight;
+ }
+
+ public String getDoorState() {
+ return doorState;
+ }
+
+ public int getTimeToEnd() {
+ return timeToEnd;
+ }
+
+ public Miscellaneous getMiscellaneous() {
+ return miscellaneous;
+ }
+
+ public String getApplianceUiSwVersion() {
+ return applianceUiSwVersion;
+ }
+
+ public int getApplianceTotalWorkingTime() {
+ return applianceTotalWorkingTime;
+ }
+
+ public String getRemoteControl() {
+ return remoteControl;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public FCMiscellaneousState getFCMiscellaneousState() {
+ return fCMiscellaneousState;
+ }
+
+ public String getCyclePhase() {
+ return cyclePhase;
+ }
+
+ public String getEndOfCycleSound() {
+ return endOfCycleSound;
+ }
+
+ public int getStartTime() {
+ return startTime;
+ }
+
+ public UserSelections getUserSelections() {
+ return userSelections;
+ }
+
+ public String getWaterHardness() {
+ return waterHardness;
+ }
+
+ public String getDefaultExtraRinse() {
+ return defaultExtraRinse;
+ }
+
+ public int getTotalWashingTime() {
+ return totalWashingTime;
+ }
+
+ public ApplianceInfo getApplianceInfo() {
+ return applianceInfo;
+ }
+
+ public String getDoorLock() {
+ return doorLock;
+ }
+
+ public boolean isUiLockMode() {
+ return uiLockMode;
+ }
+
+ public int getWashingNominalLoadWeight() {
+ return washingNominalLoadWeight;
+ }
+
+ public int getTotalWashCyclesCount() {
+ return totalWashCyclesCount;
+ }
+
+ public int getFcOptisenseLoadWeight() {
+ return fcOptisenseLoadWeight;
+ }
+
+ public String getWaterSoftenerMode() {
+ return waterSoftenerMode;
+ }
+
+ public String getApplianceState() {
+ return applianceState;
+ }
+
+ public String getApplianceMode() {
+ return applianceMode;
+ }
+
+ public String getApplianceMainBoardSwVersion() {
+ return applianceMainBoardSwVersion;
+ }
+
+ public int getTotalCycleCounter() {
+ return totalCycleCounter;
+ }
+
+ public int getMeasuredLoadWeight() {
+ return measuredLoadWeight;
+ }
+
+ public Object[] getAlerts() {
+ return alerts;
+ }
+
+ public Maintenance getApplianceCareAndMaintenance0() {
+ return applianceCareAndMaintenance0;
+ }
+
+ public NetworkInterface getNetworkInterface() {
+ return networkInterface;
+ }
+
+ public Maintenance getApplianceCareAndMaintenance1() {
+ return applianceCareAndMaintenance1;
+ }
+
+ public Maintenance getApplianceCareAndMaintenance2() {
+ return applianceCareAndMaintenance2;
+ }
+
+ public Maintenance getApplianceCareAndMaintenance3() {
+ return applianceCareAndMaintenance3;
+ }
+
+ public AutoDosing getAutoDosing() {
+ return autoDosing;
+ }
+
+ public String getCycleSubPhase() {
+ return cycleSubPhase;
+ }
+
+ public String getConnectivityState() {
+ return connectivityState;
+ }
+ }
+
+ public static class Miscellaneous {
+ private boolean defaultSoftPlus;
+
+ public boolean isDefaultSoftPlus() {
+ return defaultSoftPlus;
+ }
+ }
+
+ public static class FCMiscellaneousState {
+ private int optisenseResult;
+ private int detergentExtradosage;
+ private boolean tankAReserve;
+ private boolean tankBReserve;
+ private int softenerExtradosage;
+ private int waterUsage;
+ private int tankADetLoadForNominalWeight;
+ private int tankBDetLoadForNominalWeight;
+
+ public int getOptisenseResult() {
+ return optisenseResult;
+ }
+
+ public int getDetergentExtradosage() {
+ return detergentExtradosage;
+ }
+
+ public boolean isTankAReserve() {
+ return tankAReserve;
+ }
+
+ public boolean isTankBReserve() {
+ return tankBReserve;
+ }
+
+ public int getSoftenerExtradosage() {
+ return softenerExtradosage;
+ }
+
+ public int getWaterUsage() {
+ return waterUsage;
+ }
+
+ public int getTankADetLoadForNominalWeight() {
+ return tankADetLoadForNominalWeight;
+ }
+
+ public int getTankBDetLoadForNominalWeight() {
+ return tankBDetLoadForNominalWeight;
+ }
+ }
+
+ public static class UserSelections {
+ private boolean EWX1493A_ultraMix;
+ private boolean EWX1493A_stain;
+ private String adTankBSel = "";
+ private String adFineTuneSoftLevel = "";
+ private boolean EWX1493A_wetMode;
+ private String analogSpinSpeed = "";
+ private boolean EWX1493A_easyIron;
+ private boolean EWX1493A_rinseHold;
+ private boolean EWX1493A_wmEconomy;
+ private boolean EWX1493A_tcSensor;
+ private String programUID = "";
+ private boolean EWX1493A_anticreaseNoSteam;
+ private boolean EWX1493A_anticreaseWSteam;
+ private String adTankASel = "";
+ private String timeManagerLevel = "";
+ private boolean EWX1493A_dryMode;
+ private boolean EWX1493A_preWashPhase;
+ private String extraRinseNumber = "";
+ private String adFineTuneDetLevel = "";
+ private boolean EWX1493A_steamMode;
+ private String analogTemperature = "";
+ private boolean EWX1493A_nightCycle;
+ private String steamValue = "";
+ private boolean EWX1493A_intensive;
+ private boolean EWX1493A_pod;
+
+ public boolean isEWX1493A_ultraMix() {
+ return EWX1493A_ultraMix;
+ }
+
+ public boolean isEWX1493A_stain() {
+ return EWX1493A_stain;
+ }
+
+ public String getAdTankBSel() {
+ return adTankBSel;
+ }
+
+ public String getAdFineTuneSoftLevel() {
+ return adFineTuneSoftLevel;
+ }
+
+ public boolean isEWX1493A_wetMode() {
+ return EWX1493A_wetMode;
+ }
+
+ public String getAnalogSpinSpeed() {
+ return analogSpinSpeed;
+ }
+
+ public boolean isEWX1493A_easyIron() {
+ return EWX1493A_easyIron;
+ }
+
+ public boolean isEWX1493A_rinseHold() {
+ return EWX1493A_rinseHold;
+ }
+
+ public boolean isEWX1493A_wmEconomy() {
+ return EWX1493A_wmEconomy;
+ }
+
+ public boolean isEWX1493A_tcSensor() {
+ return EWX1493A_tcSensor;
+ }
+
+ public String getProgramUID() {
+ return programUID;
+ }
+
+ public boolean isEWX1493A_anticreaseNoSteam() {
+ return EWX1493A_anticreaseNoSteam;
+ }
+
+ public boolean isEWX1493A_anticreaseWSteam() {
+ return EWX1493A_anticreaseWSteam;
+ }
+
+ public String getAdTankASel() {
+ return adTankASel;
+ }
+
+ public String getTimeManagerLevel() {
+ return timeManagerLevel;
+ }
+
+ public boolean isEWX1493A_dryMode() {
+ return EWX1493A_dryMode;
+ }
+
+ public boolean isEWX1493A_preWashPhase() {
+ return EWX1493A_preWashPhase;
+ }
+
+ public String getExtraRinseNumber() {
+ return extraRinseNumber;
+ }
+
+ public String getAdFineTuneDetLevel() {
+ return adFineTuneDetLevel;
+ }
+
+ public boolean isEWX1493A_steamMode() {
+ return EWX1493A_steamMode;
+ }
+
+ public String getAnalogTemperature() {
+ return analogTemperature;
+ }
+
+ public boolean isEWX1493A_nightCycle() {
+ return EWX1493A_nightCycle;
+ }
+
+ public String getSteamValue() {
+ return steamValue;
+ }
+
+ public boolean isEWX1493A_intensive() {
+ return EWX1493A_intensive;
+ }
+
+ public boolean isEWX1493A_pod() {
+ return EWX1493A_pod;
+ }
+ }
+
+ public static class ApplianceInfo {
+ private String applianceType = "";
+
+ public String getApplianceType() {
+ return applianceType;
+ }
+ }
+
+ public static class Maintenance {
+ private CareThreshold careThreshold = new CareThreshold();
+
+ public CareThreshold getCareThreshold() {
+ return careThreshold;
+ }
+
+ public static class CareThreshold {
+ private boolean occurred;
+ private int threshold;
+
+ public boolean isOccurred() {
+ return occurred;
+ }
+
+ public int getThreshold() {
+ return threshold;
+ }
+ }
+ }
+
+ public static class NetworkInterface {
+ private String swVersion = "";
+ private String linkQualityIndicator = "";
+ private String otaState = "";
+ private String niuSwUpdateCurrentDescription = "";
+ private String swAncAndRevision = "";
+
+ public String getSwVersion() {
+ return swVersion;
+ }
+
+ public String getLinkQualityIndicator() {
+ return linkQualityIndicator;
+ }
+
+ public String getOtaState() {
+ return otaState;
+ }
+
+ public String getNiuSwUpdateCurrentDescription() {
+ return niuSwUpdateCurrentDescription;
+ }
+
+ public String getSwAncAndRevision() {
+ return swAncAndRevision;
+ }
+ }
+
+ public static class AutoDosing {
+ private int adTankBDetStandardDose;
+ private boolean adLocalFineTuning;
+ private String adTankAConfiguration = "";
+ private int adTankBSoftStandardDose;
+ private String adTankBConfiguration = "";
+ private int adTankADetStandardDose;
+
+ public int getAdTankBDetStandardDose() {
+ return adTankBDetStandardDose;
+ }
+
+ public boolean isAdLocalFineTuning() {
+ return adLocalFineTuning;
+ }
+
+ public String getAdTankAConfiguration() {
+ return adTankAConfiguration;
+ }
+
+ public int getAdTankBSoftStandardDose() {
+ return adTankBSoftStandardDose;
+ }
+
+ public String getAdTankBConfiguration() {
+ return adTankBConfiguration;
+ }
+
+ public int getAdTankADetStandardDose() {
+ return adTankADetStandardDose;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/handler/ElectroluxAirPurifierHandler.java b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/handler/ElectroluxAirPurifierHandler.java
new file mode 100644
index 00000000000..2be88d2f98e
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/handler/ElectroluxAirPurifierHandler.java
@@ -0,0 +1,192 @@
+/**
+ * Copyright (c) 2010-2024 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.electroluxappliance.internal.handler;
+
+import static org.openhab.binding.electroluxappliance.internal.ElectroluxApplianceBindingConstants.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.electroluxappliance.internal.ElectroluxApplianceBindingConstants;
+import org.openhab.binding.electroluxappliance.internal.ElectroluxApplianceConfiguration;
+import org.openhab.binding.electroluxappliance.internal.api.ElectroluxGroupAPI;
+import org.openhab.binding.electroluxappliance.internal.dto.AirPurifierStateDTO;
+import org.openhab.binding.electroluxappliance.internal.dto.ApplianceDTO;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.OpenClosedType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link ElectroluxAirPurifierHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+@NonNullByDefault
+public class ElectroluxAirPurifierHandler extends ElectroluxApplianceHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(ElectroluxAirPurifierHandler.class);
+
+ private ElectroluxApplianceConfiguration config = new ElectroluxApplianceConfiguration();
+
+ public ElectroluxAirPurifierHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("Command received: {} on channelID: {}", command, channelUID);
+ if (CHANNEL_STATUS.equals(channelUID.getId()) || command instanceof RefreshType) {
+ super.handleCommand(channelUID, command);
+ } else {
+ ApplianceDTO dto = getApplianceDTO();
+ ElectroluxGroupAPI api = getElectroluxGroupAPI();
+ if (api != null && dto != null) {
+ if (CHANNEL_WORK_MODE.equals(channelUID.getId())) {
+ if (command.toString().equals(COMMAND_WORKMODE_POWEROFF)) {
+ api.workModePowerOff(dto.getApplianceId());
+ } else if (command.toString().equals(COMMAND_WORKMODE_AUTO)) {
+ api.workModeAuto(dto.getApplianceId());
+ } else if (command.toString().equals(COMMAND_WORKMODE_MANUAL)) {
+ api.workModeManual(dto.getApplianceId());
+ }
+ } else if (CHANNEL_FAN_SPEED.equals(channelUID.getId())) {
+ api.setFanSpeedLevel(dto.getApplianceId(), Integer.parseInt(command.toString()));
+ } else if (CHANNEL_IONIZER.equals(channelUID.getId())) {
+ if (command == OnOffType.OFF) {
+ api.setIonizer(dto.getApplianceId(), "false");
+ } else if (command == OnOffType.ON) {
+ api.setIonizer(dto.getApplianceId(), "true");
+ } else {
+ logger.debug("Unknown command! {}", command);
+ }
+ } else if (CHANNEL_UI_LIGHT.equals(channelUID.getId())) {
+ if (command == OnOffType.OFF) {
+ api.setUILight(dto.getApplianceId(), "false");
+ } else if (command == OnOffType.ON) {
+ api.setUILight(dto.getApplianceId(), "true");
+ } else {
+ logger.debug("Unknown command! {}", command);
+ }
+ } else if (CHANNEL_SAFETY_LOCK.equals(channelUID.getId())) {
+ if (command == OnOffType.OFF) {
+ api.setSafetyLock(dto.getApplianceId(), "false");
+ } else if (command == OnOffType.ON) {
+ api.setSafetyLock(dto.getApplianceId(), "true");
+ } else {
+ logger.debug("Unknown command! {}", command);
+ }
+ }
+
+ final Bridge bridge = getBridge();
+ if (bridge != null && bridge.getHandler() instanceof ElectroluxApplianceBridgeHandler bridgeHandler) {
+ bridgeHandler.handleCommand(
+ new ChannelUID(this.thing.getUID(), ElectroluxApplianceBindingConstants.CHANNEL_STATUS),
+ RefreshType.REFRESH);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void update(@Nullable ApplianceDTO dto) {
+ if (dto != null) {
+ // Update all channels from the updated data
+ getThing().getChannels().stream().map(Channel::getUID).filter(channelUID -> isLinked(channelUID))
+ .forEach(channelUID -> {
+ State state = getValue(channelUID.getId(), dto);
+ logger.trace("Channel: {}, State: {}", channelUID, state);
+ updateState(channelUID, state);
+ });
+ if ("Connected".equalsIgnoreCase(dto.getApplianceState().getConnectionState())) {
+ updateStatus(ThingStatus.ONLINE);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Air Purifier not connected");
+ }
+ }
+ }
+
+ private State getValue(String channelId, ApplianceDTO dto) {
+ var reported = ((AirPurifierStateDTO) dto.getApplianceState()).getProperties().getReported();
+ switch (channelId) {
+ case CHANNEL_TEMPERATURE:
+ return new QuantityType<>(reported.getTemp(), SIUnits.CELSIUS);
+ case CHANNEL_HUMIDITY:
+ return new QuantityType<>(reported.getHumidity(), Units.PERCENT);
+ case CHANNEL_TVOC:
+ return new QuantityType<>(reported.getTvoc(), Units.PARTS_PER_BILLION);
+ case CHANNEL_PM1:
+ return new QuantityType<>(reported.getPm1(), Units.MICROGRAM_PER_CUBICMETRE);
+ case CHANNEL_PM25:
+ return new QuantityType<>(reported.getPm25(), Units.MICROGRAM_PER_CUBICMETRE);
+ case CHANNEL_PM10:
+ return new QuantityType<>(reported.getPm10(), Units.MICROGRAM_PER_CUBICMETRE);
+ case CHANNEL_CO2:
+ return new QuantityType<>(reported.getCo2(), Units.PARTS_PER_MILLION);
+ case CHANNEL_FAN_SPEED:
+ return new StringType(Integer.toString(reported.getFanspeed()));
+ case CHANNEL_FILTER_LIFE:
+ return new QuantityType<>(reported.getFilterLife(), Units.PERCENT);
+ case CHANNEL_IONIZER:
+ return OnOffType.from(reported.isIonizer());
+ case CHANNEL_UI_LIGHT:
+ return OnOffType.from(reported.isUiLight());
+ case CHANNEL_SAFETY_LOCK:
+ return OnOffType.from(reported.isSafetyLock());
+ case CHANNEL_WORK_MODE:
+ return new StringType(reported.getWorkmode());
+ case CHANNEL_DOOR_STATE:
+ return reported.isDoorOpen() ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
+ }
+ return UnDefType.UNDEF;
+ }
+
+ @Override
+ public Map refreshProperties() {
+ Map properties = new HashMap<>();
+
+ final Bridge bridge = getBridge();
+ if (bridge != null && bridge.getHandler() instanceof ElectroluxApplianceBridgeHandler bridgeHandler) {
+ ApplianceDTO dto = bridgeHandler.getElectroluxApplianceThings().get(config.getSerialNumber());
+ if (dto != null) {
+ var applianceInfo = dto.getApplianceInfo().getApplianceInfo();
+ properties.put(Thing.PROPERTY_VENDOR, applianceInfo.getBrand());
+ properties.put(PROPERTY_COLOUR, applianceInfo.getColour());
+ properties.put(PROPERTY_DEVICE, applianceInfo.getDeviceType());
+ properties.put(Thing.PROPERTY_MODEL_ID, applianceInfo.getModel());
+ properties.put(Thing.PROPERTY_SERIAL_NUMBER, applianceInfo.getSerialNumber());
+ properties.put(Thing.PROPERTY_FIRMWARE_VERSION,
+ ((AirPurifierStateDTO) dto.getApplianceState()).getProperties().getReported().getFrmVerNIU());
+
+ }
+ }
+ return properties;
+ }
+}
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/handler/ElectroluxApplianceBridgeHandler.java b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/handler/ElectroluxApplianceBridgeHandler.java
new file mode 100644
index 00000000000..4a0f66897dd
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/handler/ElectroluxApplianceBridgeHandler.java
@@ -0,0 +1,187 @@
+/**
+ * Copyright (c) 2010-2024 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.electroluxappliance.internal.handler;
+
+import static org.openhab.binding.electroluxappliance.internal.ElectroluxApplianceBindingConstants.*;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.electroluxappliance.internal.ElectroluxApplianceBridgeConfiguration;
+import org.openhab.binding.electroluxappliance.internal.api.ElectroluxGroupAPI;
+import org.openhab.binding.electroluxappliance.internal.discovery.ElectroluxApplianceDiscoveryService;
+import org.openhab.binding.electroluxappliance.internal.dto.ApplianceDTO;
+import org.openhab.binding.electroluxappliance.internal.listener.TokenUpdateListener;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+
+/**
+ * The {@link ElectroluxApplianceBridgeHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+@NonNullByDefault
+public class ElectroluxApplianceBridgeHandler extends BaseBridgeHandler implements TokenUpdateListener {
+
+ private final Logger logger = LoggerFactory.getLogger(ElectroluxApplianceBridgeHandler.class);
+
+ public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BRIDGE);
+
+ private int refreshTimeInSeconds = 300;
+ private boolean isCommunicationError = false;
+
+ private final Gson gson;
+ private final HttpClient httpClient;
+ private final Map electroluxApplianceThings = new ConcurrentHashMap<>();
+
+ private @Nullable ElectroluxGroupAPI api;
+ private @Nullable ScheduledFuture> refreshJob;
+ private @Nullable ScheduledFuture> instantUpdate;
+
+ public ElectroluxApplianceBridgeHandler(Bridge bridge, HttpClient httpClient, Gson gson) {
+ super(bridge);
+ this.httpClient = httpClient;
+ this.gson = gson;
+ }
+
+ @Override
+ public void initialize() {
+ ElectroluxApplianceBridgeConfiguration config = getConfigAs(ElectroluxApplianceBridgeConfiguration.class);
+
+ refreshTimeInSeconds = config.refresh;
+
+ if (config.apiKey.isBlank() || config.refreshToken.isBlank()) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "Configuration of API key, access and refresh token is mandatory");
+ } else if (refreshTimeInSeconds < 10) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "Refresh time cannot be less than 10!");
+ } else {
+ try {
+ this.api = new ElectroluxGroupAPI(config, gson, httpClient, this);
+ scheduler.execute(() -> {
+ updateStatus(ThingStatus.UNKNOWN);
+ startAutomaticRefresh();
+ });
+ } catch (RuntimeException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ }
+ }
+
+ @Override
+ public void onTokenUpdated(@Nullable String newRefreshToken) {
+ // Create a new configuration object with the updated tokens
+ Configuration configuration = editConfiguration();
+ configuration.put("refreshToken", newRefreshToken);
+ // Update the configuration
+ updateConfiguration(configuration);
+ }
+
+ public Map getElectroluxApplianceThings() {
+ return electroluxApplianceThings;
+ }
+
+ @Override
+ public Collection> getServices() {
+ return Set.of(ElectroluxApplianceDiscoveryService.class);
+ }
+
+ @Override
+ public void dispose() {
+ stopAutomaticRefresh();
+ }
+
+ public @Nullable ElectroluxGroupAPI getElectroluxDeltaAPI() {
+ return api;
+ }
+
+ private boolean refreshAndUpdateStatus() {
+ if (api != null) {
+ if (api.refresh(electroluxApplianceThings, isCommunicationError)) {
+ getThing().getThings().stream().forEach(thing -> {
+ ElectroluxApplianceHandler handler = (ElectroluxApplianceHandler) thing.getHandler();
+ if (handler != null) {
+ handler.update();
+ }
+ });
+ updateStatus(ThingStatus.ONLINE);
+ isCommunicationError = false;
+ return true;
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
+ isCommunicationError = true;
+ }
+ }
+ return false;
+ }
+
+ private void startAutomaticRefresh() {
+ ScheduledFuture> refreshJob = this.refreshJob;
+ if (refreshJob == null || refreshJob.isCancelled()) {
+ this.refreshJob = scheduler.scheduleWithFixedDelay(this::refreshAndUpdateStatus, 0, refreshTimeInSeconds,
+ TimeUnit.SECONDS);
+ }
+ }
+
+ private void stopAutomaticRefresh() {
+ ScheduledFuture> refreshJob = this.refreshJob;
+ if (refreshJob != null) {
+ refreshJob.cancel(true);
+ this.refreshJob = null;
+ }
+ refreshJob = this.instantUpdate;
+ if (refreshJob != null) {
+ refreshJob.cancel(true);
+ this.refreshJob = null;
+ }
+ }
+
+ private synchronized void updateNow() {
+ Future> localRef = instantUpdate;
+ if (localRef == null || localRef.isDone()) {
+ instantUpdate = scheduler.schedule(this::refreshAndUpdateStatus, 0, TimeUnit.SECONDS);
+ } else {
+ logger.debug("Already waiting for scheduled refresh");
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("Command received: {} on channelID: {}", command, channelUID);
+ if (CHANNEL_STATUS.equals(channelUID.getId()) || command instanceof RefreshType) {
+ updateNow();
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/handler/ElectroluxApplianceHandler.java b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/handler/ElectroluxApplianceHandler.java
new file mode 100644
index 00000000000..af599de2ab2
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/handler/ElectroluxApplianceHandler.java
@@ -0,0 +1,108 @@
+/**
+ * Copyright (c) 2010-2024 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.electroluxappliance.internal.handler;
+
+import static org.openhab.binding.electroluxappliance.internal.ElectroluxApplianceBindingConstants.CHANNEL_STATUS;
+
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.electroluxappliance.internal.ElectroluxApplianceConfiguration;
+import org.openhab.binding.electroluxappliance.internal.api.ElectroluxGroupAPI;
+import org.openhab.binding.electroluxappliance.internal.dto.ApplianceDTO;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link ElectroluxApplianceHandler} is
+ *
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+@NonNullByDefault
+public abstract class ElectroluxApplianceHandler extends BaseThingHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(ElectroluxApplianceHandler.class);
+
+ private ElectroluxApplianceConfiguration config = new ElectroluxApplianceConfiguration();
+
+ public ElectroluxApplianceHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("Command received: {} on channelID: {}", command, channelUID);
+ if (CHANNEL_STATUS.equals(channelUID.getId()) || command instanceof RefreshType) {
+ final Bridge bridge = getBridge();
+ if (bridge != null && bridge.getHandler() instanceof ElectroluxApplianceBridgeHandler bridgeHandler) {
+ bridgeHandler.handleCommand(channelUID, command);
+ }
+ }
+ }
+
+ @Override
+ public void initialize() {
+ config = getConfigAs(ElectroluxApplianceConfiguration.class);
+ if (config.getSerialNumber().isBlank()) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "Configuration of Serial Number is mandatory");
+ } else {
+ updateStatus(ThingStatus.UNKNOWN);
+
+ scheduler.execute(() -> {
+ update();
+ Map properties = refreshProperties();
+ updateProperties(properties);
+ });
+ }
+ }
+
+ public void update() {
+ ApplianceDTO dto = getApplianceDTO();
+ if (dto != null) {
+ update(dto);
+ } else {
+ logger.warn("AppliancedDTO is null!");
+ }
+ }
+
+ protected @Nullable ElectroluxGroupAPI getElectroluxGroupAPI() {
+ final Bridge bridge = getBridge();
+ if (bridge != null && bridge.getHandler() instanceof ElectroluxApplianceBridgeHandler bridgeHandler) {
+ return bridgeHandler.getElectroluxDeltaAPI();
+ }
+ return null;
+ }
+
+ protected @Nullable ApplianceDTO getApplianceDTO() {
+ final Bridge bridge = getBridge();
+ if (bridge != null && bridge.getHandler() instanceof ElectroluxApplianceBridgeHandler bridgeHandler) {
+ return bridgeHandler.getElectroluxApplianceThings().get(config.getSerialNumber());
+ }
+ return null;
+ }
+
+ public abstract void update(@Nullable ApplianceDTO dto);
+
+ public abstract Map refreshProperties();
+}
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/handler/ElectroluxApplianceHandlerFactory.java b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/handler/ElectroluxApplianceHandlerFactory.java
new file mode 100644
index 00000000000..68d2ee1cd82
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/handler/ElectroluxApplianceHandlerFactory.java
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2010-2024 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.electroluxappliance.internal.handler;
+
+import static org.openhab.binding.electroluxappliance.internal.ElectroluxApplianceBindingConstants.*;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.core.io.net.http.HttpClientFactory;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandlerFactory;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+
+/**
+ * The {@link ElectroluxApplianceHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.electroluxappliance", service = ThingHandlerFactory.class)
+public class ElectroluxApplianceHandlerFactory extends BaseThingHandlerFactory {
+
+ private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ELECTROLUX_AIR_PURIFIER,
+ THING_TYPE_ELECTROLUX_WASHING_MACHINE, THING_TYPE_BRIDGE);
+ private final Gson gson;
+ private HttpClient httpClient;
+ private final Logger logger = LoggerFactory.getLogger(ElectroluxApplianceHandlerFactory.class);
+
+ @Activate
+ public ElectroluxApplianceHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
+ this.httpClient = httpClientFactory.getCommonHttpClient();
+ this.gson = new Gson();
+ }
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+ }
+
+ @Override
+ protected @Nullable ThingHandler createHandler(Thing thing) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+ if (THING_TYPE_ELECTROLUX_AIR_PURIFIER.equals(thingTypeUID)) {
+ return new ElectroluxAirPurifierHandler(thing);
+ } else if (THING_TYPE_ELECTROLUX_WASHING_MACHINE.equals(thingTypeUID)) {
+ return new ElectroluxWashingMachineHandler(thing);
+ } else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) {
+ return new ElectroluxApplianceBridgeHandler((Bridge) thing, httpClient, gson);
+ }
+ return null;
+ }
+}
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/handler/ElectroluxWashingMachineHandler.java b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/handler/ElectroluxWashingMachineHandler.java
new file mode 100644
index 00000000000..ae598ace6f1
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/handler/ElectroluxWashingMachineHandler.java
@@ -0,0 +1,150 @@
+/**
+ * Copyright (c) 2010-2024 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.electroluxappliance.internal.handler;
+
+import static org.openhab.binding.electroluxappliance.internal.ElectroluxApplianceBindingConstants.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.electroluxappliance.internal.ElectroluxApplianceConfiguration;
+import org.openhab.binding.electroluxappliance.internal.dto.ApplianceDTO;
+import org.openhab.binding.electroluxappliance.internal.dto.WashingMachineStateDTO;
+import org.openhab.core.library.types.OpenClosedType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link ElectroluxWashingMachineHandler} is responsible for handling commands and status updates for
+ * Electrolux washing machines.
+ *
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+@NonNullByDefault
+public class ElectroluxWashingMachineHandler extends ElectroluxApplianceHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(ElectroluxWashingMachineHandler.class);
+
+ private ElectroluxApplianceConfiguration config = new ElectroluxApplianceConfiguration();
+
+ public ElectroluxWashingMachineHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("Command received: {} on channelID: {}", command, channelUID);
+ if (CHANNEL_STATUS.equals(channelUID.getId()) || command instanceof RefreshType) {
+ super.handleCommand(channelUID, command);
+ }
+ }
+
+ @Override
+ public void update(@Nullable ApplianceDTO dto) {
+ if (dto != null) {
+ // Update all channels from the updated data
+ getThing().getChannels().stream().map(Channel::getUID).filter(channelUID -> isLinked(channelUID))
+ .forEach(channelUID -> {
+ State state = getValue(channelUID.getId(), dto);
+ logger.trace("Channel: {}, State: {}", channelUID, state);
+ updateState(channelUID, state);
+ });
+ if ("Connected".equalsIgnoreCase(dto.getApplianceState().getConnectionState())) {
+ updateStatus(ThingStatus.ONLINE);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "Washing Machine not connected");
+ }
+ }
+ }
+
+ private State getValue(String channelId, ApplianceDTO dto) {
+ var reported = ((WashingMachineStateDTO) dto.getApplianceState()).getProperties().getReported();
+ switch (channelId) {
+ case CHANNEL_DOOR_STATE:
+ return "OPEN".equals(reported.getDoorState()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
+ case CHANNEL_DOOR_LOCK:
+ return "ON".equals(reported.getDoorLock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
+ case CHANNEL_TIME_TO_START:
+ return new QuantityType<>(reported.getStartTime(), Units.SECOND);
+ case CHANNEL_TIME_TO_END:
+ return new QuantityType<>(reported.getTimeToEnd(), Units.SECOND);
+ case CHANNEL_APPLIANCE_UI_SW_VERSION:
+ return new StringType(reported.getApplianceUiSwVersion());
+ case CHANNEL_OPTISENSE_RESULT:
+ return new StringType(Integer.toString(reported.getFCMiscellaneousState().getOptisenseResult()));
+ case CHANNEL_DETERGENT_EXTRA_DOSAGE:
+ return new StringType(Integer.toString(reported.getFCMiscellaneousState().getDetergentExtradosage()));
+ case CHANNEL_SOFTENER_EXTRA_DOSAGE:
+ return new StringType(Integer.toString(reported.getFCMiscellaneousState().getSoftenerExtradosage()));
+ case CHANNEL_WATER_USAGE:
+ return new QuantityType<>(reported.getFCMiscellaneousState().getWaterUsage(), Units.LITRE);
+ case CHANNEL_TOTAL_WASH_CYCLES_COUNT:
+ return new StringType(Integer.toString(reported.getTotalWashCyclesCount()));
+ case CHANNEL_CYCLE_PHASE:
+ return new StringType(reported.getCyclePhase());
+ case CHANNEL_APPLIANCE_TOTAL_WORKING_TIME:
+ return new StringType(Integer.toString(reported.getApplianceTotalWorkingTime()));
+ case CHANNEL_APPLIANCE_STATE:
+ return new StringType(reported.getApplianceState());
+ case CHANNEL_APPLIANCE_MODE:
+ return new StringType(reported.getApplianceMode());
+ case CHANNEL_ANALOG_TEMPERATURE:
+ return new StringType(reported.getUserSelections().getAnalogTemperature());
+ case CHANNEL_ANALOG_SPIN_SPEED:
+ return new StringType(reported.getUserSelections().getAnalogSpinSpeed());
+ case CHANNEL_STEAM_VALUE:
+ return new StringType(reported.getUserSelections().getSteamValue());
+ case CHANNEL_PROGRAMS_ORDER:
+ return new StringType(reported.getUserSelections().getProgramUID());
+
+ }
+ return UnDefType.UNDEF;
+ }
+
+ @Override
+ public Map refreshProperties() {
+ Map properties = new HashMap<>();
+ final Bridge bridge = getBridge();
+ if (bridge != null && bridge.getHandler() instanceof ElectroluxApplianceBridgeHandler bridgeHandler) {
+ ApplianceDTO dto = bridgeHandler.getElectroluxApplianceThings().get(config.getSerialNumber());
+ if (dto != null) {
+ var applianceInfo = dto.getApplianceInfo().getApplianceInfo();
+ properties.put(Thing.PROPERTY_VENDOR, applianceInfo.getBrand());
+ properties.put(PROPERTY_COLOUR, applianceInfo.getColour());
+ properties.put(PROPERTY_DEVICE, applianceInfo.getDeviceType());
+ properties.put(Thing.PROPERTY_MODEL_ID, applianceInfo.getModel());
+ properties.put(Thing.PROPERTY_SERIAL_NUMBER, applianceInfo.getSerialNumber());
+ properties.put(Thing.PROPERTY_FIRMWARE_VERSION, ((WashingMachineStateDTO) dto.getApplianceState())
+ .getProperties().getReported().getNetworkInterface().getSwVersion());
+ }
+ }
+ return properties;
+ }
+}
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/listener/TokenUpdateListener.java b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/listener/TokenUpdateListener.java
new file mode 100644
index 00000000000..4362c4e9778
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/java/org/openhab/binding/electroluxappliance/internal/listener/TokenUpdateListener.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2024 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.electroluxappliance.internal.listener;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link TokenUpdateListener} callback interface for notifying about token updates
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+@NonNullByDefault
+public interface TokenUpdateListener {
+ /**
+ * Called when the access token and refresh token are updated.
+ *
+ * @param newAccessToken the new access token
+ * @param newRefreshToken the new refresh token
+ */
+ void onTokenUpdated(String newRefreshToken);
+}
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.electroluxappliance/src/main/resources/OH-INF/addon/addon.xml
new file mode 100644
index 00000000000..ea9b1b3f653
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/resources/OH-INF/addon/addon.xml
@@ -0,0 +1,11 @@
+
+
+
+ binding
+ Electrolux Appliances Binding
+ This is the binding for Electrolux Appliances.
+ cloud
+
+
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/resources/OH-INF/i18n/electroluxappliance.properties b/bundles/org.openhab.binding.electroluxappliance/src/main/resources/OH-INF/i18n/electroluxappliance.properties
new file mode 100644
index 00000000000..53801fd00e0
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/resources/OH-INF/i18n/electroluxappliance.properties
@@ -0,0 +1,171 @@
+# add-on
+
+addon.electroluxappliance.name = Electrolux Appliances Binding
+addon.electroluxappliance.description = This is the binding for Electrolux Appliances.
+
+# thing types
+
+thing-type.electroluxappliance.air-purifier.label = Air Purifier
+thing-type.electroluxappliance.air-purifier.description = This thing represents the Electrolux Air Purifier.
+thing-type.electroluxappliance.api.label = Electrolux Group API
+thing-type.electroluxappliance.api.description = This bridge represents the web API connector.
+thing-type.electroluxappliance.washing-machine.label = Washing Machine
+thing-type.electroluxappliance.washing-machine.description = This thing represents the Electrolux Washing Machine.
+
+# thing types config
+
+thing-type.config.electroluxappliance.air-purifier.serialNumber.label = Serial Number
+thing-type.config.electroluxappliance.air-purifier.serialNumber.description = The appliance serial number.
+thing-type.config.electroluxappliance.api.apiKey.label = API Key
+thing-type.config.electroluxappliance.api.apiKey.description = Your personal API key.
+thing-type.config.electroluxappliance.api.refresh.label = Refresh Interval
+thing-type.config.electroluxappliance.api.refresh.description = Specifies the refresh interval in seconds.
+thing-type.config.electroluxappliance.api.refreshToken.label = Refresh Token
+thing-type.config.electroluxappliance.api.refreshToken.description = Your personal Refresh Token.
+thing-type.config.electroluxappliance.washing-machine.serialNumber.label = Serial Number
+thing-type.config.electroluxappliance.washing-machine.serialNumber.description = The appliance serial number.
+
+# channel types
+
+channel-type.electroluxappliance.analog-spin-speed.label = Spin Speed
+channel-type.electroluxappliance.analog-spin-speed.description = The spin speed.
+channel-type.electroluxappliance.analog-spin-speed.state.option.DISABLED = Disabled
+channel-type.electroluxappliance.analog-spin-speed.state.option.0_RPM = 0 rpm
+channel-type.electroluxappliance.analog-spin-speed.state.option.400_RPM = 400 rpm
+channel-type.electroluxappliance.analog-spin-speed.state.option.600_RPM = 600 rpm
+channel-type.electroluxappliance.analog-spin-speed.state.option.800_RPM = 800 rpm
+channel-type.electroluxappliance.analog-spin-speed.state.option.1000_RPM = 1000 rpm
+channel-type.electroluxappliance.analog-spin-speed.state.option.1200_RPM = 1200 rpm
+channel-type.electroluxappliance.analog-spin-speed.state.option.1400_RPM = 1400 rpm
+channel-type.electroluxappliance.analog-spin-speed.state.option.1600_RPM = 1600 rpm
+channel-type.electroluxappliance.analog-temperature.label = Washing Temperature
+channel-type.electroluxappliance.analog-temperature.description = The user configured washing temperature.
+channel-type.electroluxappliance.analog-temperature.state.option.20_CELSIUS = 20°C
+channel-type.electroluxappliance.analog-temperature.state.option.30_CELSIUS = 30°C
+channel-type.electroluxappliance.analog-temperature.state.option.40_CELSIUS = 40°C
+channel-type.electroluxappliance.analog-temperature.state.option.50_CELSIUS = 50°C
+channel-type.electroluxappliance.analog-temperature.state.option.60_CELSIUS = 60°C
+channel-type.electroluxappliance.analog-temperature.state.option.90_CELSIUS = 90°C
+channel-type.electroluxappliance.analog-temperature.state.option.COLD = Cold
+channel-type.electroluxappliance.appliance-mode.label = Appliance Mode
+channel-type.electroluxappliance.appliance-mode.description = The appliance mode.
+channel-type.electroluxappliance.appliance-mode.state.option.DEMO = Demo
+channel-type.electroluxappliance.appliance-mode.state.option.DIAGNOSTIC = Diagnostic
+channel-type.electroluxappliance.appliance-mode.state.option.NORMAL = Normal
+channel-type.electroluxappliance.appliance-mode.state.option.SERVICE = Service
+channel-type.electroluxappliance.appliance-state.label = Appliance State
+channel-type.electroluxappliance.appliance-state.description = The appliance state.
+channel-type.electroluxappliance.appliance-state.state.option.ALARM = Alarm
+channel-type.electroluxappliance.appliance-state.state.option.DELAYED_START = Delayed start
+channel-type.electroluxappliance.appliance-state.state.option.END_OF_CYCLE = End of cycle
+channel-type.electroluxappliance.appliance-state.state.option.IDLE = Idle
+channel-type.electroluxappliance.appliance-state.state.option.OFF = Off
+channel-type.electroluxappliance.appliance-state.state.option.PAUSED = Paused
+channel-type.electroluxappliance.appliance-state.state.option.READY_TO_START = Ready to start
+channel-type.electroluxappliance.appliance-state.state.option.RUNNING = Running
+channel-type.electroluxappliance.appliance-total-working-time.label = Appliance Total Working Time
+channel-type.electroluxappliance.appliance-total-working-time.description = The appliance total working time.
+channel-type.electroluxappliance.appliance-ui-sw-version.label = Appliance UI SW Version
+channel-type.electroluxappliance.appliance-ui-sw-version.description = The appliance UI SW version.
+channel-type.electroluxappliance.co2.label = CO2
+channel-type.electroluxappliance.co2.description = The measured CarbonDioxide.
+channel-type.electroluxappliance.cycle-phase.label = Cycle Phase
+channel-type.electroluxappliance.cycle-phase.description = The washing cycle phase.
+channel-type.electroluxappliance.cycle-phase.state.option.UNAVAILABLE = Unavailable
+channel-type.electroluxappliance.cycle-phase.state.option.ANTICREASE = Anicrease
+channel-type.electroluxappliance.cycle-phase.state.option.DRAIN = Drain
+channel-type.electroluxappliance.cycle-phase.state.option.DRY = Dry
+channel-type.electroluxappliance.cycle-phase.state.option.PREWASH = Prewash
+channel-type.electroluxappliance.cycle-phase.state.option.RINSE = Rinse
+channel-type.electroluxappliance.cycle-phase.state.option.SPIN = Spin
+channel-type.electroluxappliance.cycle-phase.state.option.STEAM = Steam
+channel-type.electroluxappliance.cycle-phase.state.option.WASH = Wash
+channel-type.electroluxappliance.detergent-extradosage.label = Detergent Extra Dosage
+channel-type.electroluxappliance.detergent-extradosage.description = The detergent extra dosage.
+channel-type.electroluxappliance.door-lock.label = Door Lock
+channel-type.electroluxappliance.door-lock.description = The door lock status Open/Closed.
+channel-type.electroluxappliance.door-state.label = Door State
+channel-type.electroluxappliance.door-state.description = The door status Open/Closed.
+channel-type.electroluxappliance.fan-speed.label = Fan Speed Setting
+channel-type.electroluxappliance.fan-speed.description = The fan speed setting.
+channel-type.electroluxappliance.fan-speed.state.option.1 = Level 1
+channel-type.electroluxappliance.fan-speed.state.option.2 = Level 2
+channel-type.electroluxappliance.fan-speed.state.option.3 = Level 3
+channel-type.electroluxappliance.fan-speed.state.option.4 = Level 4
+channel-type.electroluxappliance.fan-speed.state.option.5 = Level 5
+channel-type.electroluxappliance.fan-speed.state.option.6 = Level 6
+channel-type.electroluxappliance.fan-speed.state.option.7 = Level 7
+channel-type.electroluxappliance.fan-speed.state.option.8 = Level 8
+channel-type.electroluxappliance.fan-speed.state.option.9 = Level 9
+channel-type.electroluxappliance.filter-life.label = Remaining Filter Life
+channel-type.electroluxappliance.filter-life.description = The remaining filter life indication in percent.
+channel-type.electroluxappliance.humidity.label = Humidity
+channel-type.electroluxappliance.humidity.description = The measured humidity.
+channel-type.electroluxappliance.ionizer.label = Ionizer Status
+channel-type.electroluxappliance.ionizer.description = The ionizer status On/Off.
+channel-type.electroluxappliance.optisense-result.label = Optisense Result
+channel-type.electroluxappliance.optisense-result.description = The optisense result.
+channel-type.electroluxappliance.pm1.label = PM1
+channel-type.electroluxappliance.pm1.description = The Particulate Matter 1 (0.001mm).
+channel-type.electroluxappliance.pm10.label = PM10
+channel-type.electroluxappliance.pm10.description = The Particulate Matter 10 (0.01mm).
+channel-type.electroluxappliance.pm2_5.label = PM2.5
+channel-type.electroluxappliance.pm2_5.description = The Particulate Matter 2.5 (0.0025mm).
+channel-type.electroluxappliance.programs-order.label = Programs Order
+channel-type.electroluxappliance.programs-order.description = The user configured washing program.
+channel-type.electroluxappliance.programs-order.state.option.MACHINE_SETTINGS_HIDDEN_TEST = Machine settings hidden test
+channel-type.electroluxappliance.programs-order.state.option.COTTON_PR_ECO40-60 = Cotton Eco 40°C-60°C
+channel-type.electroluxappliance.programs-order.state.option.COTTON_PR_COTTONS = Cotton
+channel-type.electroluxappliance.programs-order.state.option.SYNTHETIC_PR_SYNTHETICS = Synthetics
+channel-type.electroluxappliance.programs-order.state.option.DELICATE_PR_DELICATES = Delicates
+channel-type.electroluxappliance.programs-order.state.option.WOOL_PR_WOOL_SILK = Wool Silk
+channel-type.electroluxappliance.programs-order.state.option.STEAM_REFRESH_PR_STEAMFRESHSCENT = Steam fresh cent
+channel-type.electroluxappliance.programs-order.state.option.SPIN_PR_DRAIN_SPIN = Drain spin
+channel-type.electroluxappliance.programs-order.state.option.SOFTENER_PR_RINSE = Softener rinse
+channel-type.electroluxappliance.programs-order.state.option.QUICK_20_MIN_PR_RAPID20MIN = Rapid 20min
+channel-type.electroluxappliance.programs-order.state.option.SPORT_JACKETS_PR_OUTDOOR = Sport jackets outdoor
+channel-type.electroluxappliance.programs-order.state.option.SYNTHETIC_PR_SPORT = Synthetic sport
+channel-type.electroluxappliance.programs-order.state.option.DENIM_PR_DENIM = Denim
+channel-type.electroluxappliance.programs-order.state.option.BLANKET_PR_DUVET = Blanket duvet
+channel-type.electroluxappliance.programs-order.state.option.SANITISE60_PR_ANTIALLERGYVAPOUR = Anti allergy vapour
+channel-type.electroluxappliance.programs-order.state.option.COTTON_PR_BUSINESSSHIRTS = Cotton business shirts
+channel-type.electroluxappliance.programs-order.state.option.DRUM_CLEAN_PR_MACHINECLEAN = Drum machine clean
+channel-type.electroluxappliance.programs-order.state.option.STEAM_DEWRINKLER_PR_STEAMCASHMERE = Steam cashmere
+channel-type.electroluxappliance.programs-order.state.option.SYNTHETIC_PR_BEDLINEN = Synthetic bedlinen
+channel-type.electroluxappliance.programs-order.state.option.COTTON_PR_TOWELS = Cotton towels
+channel-type.electroluxappliance.programs-order.state.option.DELICATE_PR_CURTAINS = Delicate curtains
+channel-type.electroluxappliance.programs-order.state.option.SYNTHETIC_PR_FLEECE = Synthetic fleece
+channel-type.electroluxappliance.programs-order.state.option.COTTON_PR_WORKINGCLOTHES = Cotton working clothes
+channel-type.electroluxappliance.programs-order.state.option.SYNTHETIC_PR_MICROFIBRE = Synthetic microfibre
+channel-type.electroluxappliance.programs-order.state.option.DELICATE_PR_BABY = Delicate baby
+channel-type.electroluxappliance.safety-lock.label = Safety Lock
+channel-type.electroluxappliance.safety-lock.description = The safety lock status.
+channel-type.electroluxappliance.softener-extradosage.label = Softener Extra Dosage
+channel-type.electroluxappliance.softener-extradosage.description = The softener extra dosage.
+channel-type.electroluxappliance.status.label = Fetch Current Status
+channel-type.electroluxappliance.status.description = Used to fetch latest status from API.
+channel-type.electroluxappliance.steam-value.label = Steam Value
+channel-type.electroluxappliance.steam-value.description = The user configured steam value.
+channel-type.electroluxappliance.steam-value.state.option.STEAM_OFF = Steam off
+channel-type.electroluxappliance.steam-value.state.option.STEAM_MIN = Steam minumum
+channel-type.electroluxappliance.steam-value.state.option.STEAM_MED = Steam medium
+channel-type.electroluxappliance.steam-value.state.option.STEAM_MAX = Steam max
+channel-type.electroluxappliance.temperature.label = Temperature
+channel-type.electroluxappliance.temperature.description = The measured temperature.
+channel-type.electroluxappliance.time-to-end.label = Time To End
+channel-type.electroluxappliance.time-to-end.description = The time remaining until the program will end.
+channel-type.electroluxappliance.time-to-start.label = Time To Delayed Start
+channel-type.electroluxappliance.time-to-start.description = The time remaining until the delayed start.
+channel-type.electroluxappliance.total-wash-cycles-count.label = Total Wash Cycles Count
+channel-type.electroluxappliance.total-wash-cycles-count.description = The total wash cycles count.
+channel-type.electroluxappliance.tvoc.label = TVOC
+channel-type.electroluxappliance.tvoc.description = The total Volatile Organic Compounds.
+channel-type.electroluxappliance.ui-light.label = UI Light
+channel-type.electroluxappliance.ui-light.description = The air quality light status indication.
+channel-type.electroluxappliance.water-usage.label = Water Usage
+channel-type.electroluxappliance.water-usage.description = The water usage in litres.
+channel-type.electroluxappliance.work-mode.label = Work Mode Setting
+channel-type.electroluxappliance.work-mode.description = The work mode setting.
+channel-type.electroluxappliance.work-mode.state.option.PowerOff = Power Off
+channel-type.electroluxappliance.work-mode.state.option.Auto = Automatic
+channel-type.electroluxappliance.work-mode.state.option.Manual = Manual
diff --git a/bundles/org.openhab.binding.electroluxappliance/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.electroluxappliance/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644
index 00000000000..b8219a94577
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxappliance/src/main/resources/OH-INF/thing/thing-types.xml
@@ -0,0 +1,450 @@
+
+
+
+
+
+ This bridge represents the web API connector.
+
+
+ Electrolux
+
+
+
+
+
+ Your personal API key.
+
+
+
+ Your personal Refresh Token.
+
+
+
+ Specifies the refresh interval in seconds.
+ 300
+
+
+
+
+
+
+
+
+
+
+ This thing represents the Electrolux Air Purifier.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Electrolux
+
+
+ serialNumber
+
+
+
+
+ The appliance serial number.
+
+
+
+
+
+
+
+
+
+
+
+ This thing represents the Electrolux Washing Machine.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Electrolux
+
+
+ serialNumber
+
+
+
+
+ The appliance serial number.
+
+
+
+
+
+
+
+ String
+
+ Used to fetch latest status from API.
+
+
+
+
+ Number:Temperature
+
+ The measured temperature.
+ Temperature
+
+
+
+
+
+ Number:Dimensionless
+
+ The measured humidity.
+ Humidity
+
+
+
+
+ Number:Dimensionless
+
+ The total Volatile Organic Compounds.
+
+
+
+
+
+ Number:Density
+
+ The Particulate Matter 1 (0.001mm).
+
+
+
+
+ Number:Density
+
+ The Particulate Matter 2.5 (0.0025mm).
+
+
+
+
+ Number:Density
+
+ The Particulate Matter 10 (0.01mm).
+
+
+
+
+ Number:Dimensionless
+
+ The measured CarbonDioxide.
+
+
+
+
+ Number:Dimensionless
+
+ The remaining filter life indication in percent.
+
+
+
+
+ Number
+
+ The fan speed setting.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ String
+
+ The work mode setting.
+
+
+
+
+
+
+
+
+
+
+ Switch
+
+ The ionizer status On/Off.
+
+
+
+ Switch
+
+ The air quality light status indication.
+
+
+
+ Switch
+
+ The safety lock status.
+
+
+
+ Contact
+
+ The door lock status Open/Closed.
+
+
+
+
+ Contact
+
+ The door status Open/Closed.
+
+
+
+
+ Number:Time
+
+ The time remaining until the delayed start.
+
+
+
+
+ Number:Time
+
+ The time remaining until the program will end.
+
+
+
+
+ String
+
+ The appliance UI SW version.
+
+
+
+
+ Number:Time
+
+ The appliance total working time.
+
+
+
+
+ Number
+
+ The optisense result.
+
+
+
+
+
+
+
+
+ Number:Volume
+
+ The water usage in litres.
+
+
+
+
+ Number
+
+ The total wash cycles count.
+
+
+
+
+ String
+
+ The washing cycle phase.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ String
+
+ The appliance state.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ String
+
+ The appliance mode.
+
+
+
+
+
+
+
+
+
+
+
+ String
+
+ The user configured washing temperature.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ String
+
+ The spin speed.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ String
+
+ The user configured steam value.
+
+
+
+
+
+
+
+
+
+
+
+ String
+
+ The user configured washing program.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/pom.xml b/bundles/pom.xml
index 9f68a5bd69f..7ea7fb5e48f 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -130,6 +130,7 @@
org.openhab.binding.ecovacs
org.openhab.binding.ecowatt
org.openhab.binding.ekey
+ org.openhab.binding.electroluxappliance
org.openhab.binding.elerotransmitterstick
org.openhab.binding.elroconnects
org.openhab.binding.emotiva