[electroluxappliance] Initial contribution (#17663)

* First version

Signed-off-by: Jan Gustafsson <jannegpriv@gmail.com>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
Jan Gustafsson 2024-11-10 13:30:04 +01:00 committed by Ciprian Pascu
parent 16353e95c2
commit d28eaf8e9c
27 changed files with 3170 additions and 0 deletions

View File

@ -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

View File

@ -471,6 +471,11 @@
<artifactId>org.openhab.binding.ekey</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.electroluxappliance</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.elerotransmitterstick</artifactId>

View File

@ -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

View File

@ -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"}
```

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>4.3.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.electroluxappliance</artifactId>
<name>openHAB Add-ons :: Bundles :: Electrolux Appliance Binding</name>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.electroluxappliance-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-electroluxappliance" description="Electrolux Appliance Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.electroluxappliance/${project.version}</bundle>
</feature>
</features>

View File

@ -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<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BRIDGE,
THING_TYPE_ELECTROLUX_AIR_PURIFIER, THING_TYPE_ELECTROLUX_WASHING_MACHINE);
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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 "";
}
}

View File

@ -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<String, ApplianceDTO> 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;
}
}

View File

@ -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<ElectroluxApplianceBridgeHandler> {
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();
}
}

View File

@ -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
}
}

View File

@ -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 + '\'' + '}';
}
}

View File

@ -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 + '\'' + '}';
}
}
}

View File

@ -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 + '\'' + '}';
}
}

View File

@ -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;
}
}
}

View File

@ -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<String, String> refreshProperties() {
Map<String, String> 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;
}
}

View File

@ -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<ThingTypeUID> 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<String, ApplianceDTO> 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<String, ApplianceDTO> getElectroluxApplianceThings() {
return electroluxApplianceThings;
}
@Override
public Collection<Class<? extends ThingHandlerService>> 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();
}
}
}

View File

@ -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<String, String> 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<String, String> refreshProperties();
}

View File

@ -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<ThingTypeUID> 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;
}
}

View File

@ -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<String, String> refreshProperties() {
Map<String, String> 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;
}
}

View File

@ -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);
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon:addon id="electroluxappliance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
<type>binding</type>
<name>Electrolux Appliances Binding</name>
<description>This is the binding for Electrolux Appliances.</description>
<connection>cloud</connection>
</addon:addon>

View File

@ -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

View File

@ -0,0 +1,450 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="electroluxappliance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="api">
<label>Electrolux Group API</label>
<description>This bridge represents the web API connector.</description>
<properties>
<property name="vendor">Electrolux</property>
</properties>
<config-description>
<parameter name="apiKey" type="text" required="true">
<label>API Key</label>
<description>Your personal API key.</description>
</parameter>
<parameter name="refreshToken" type="text" required="true">
<label>Refresh Token</label>
<description>Your personal Refresh Token.</description>
</parameter>
<parameter name="refresh" type="integer" min="10" unit="s">
<label>Refresh Interval</label>
<description>Specifies the refresh interval in seconds.</description>
<default>300</default>
</parameter>
</config-description>
</bridge-type>
<thing-type id="air-purifier">
<supported-bridge-type-refs>
<bridge-type-ref id="api"/>
</supported-bridge-type-refs>
<label>Air Purifier</label>
<description>This thing represents the Electrolux Air Purifier.</description>
<channels>
<channel id="temperature" typeId="temperature"/>
<channel id="humidity" typeId="humidity"/>
<channel id="tvoc" typeId="tvoc"/>
<channel id="pm1" typeId="pm1"/>
<channel id="pm2_5" typeId="pm2_5"/>
<channel id="pm10" typeId="pm10"/>
<channel id="co2" typeId="co2"/>
<channel id="filter-life" typeId="filter-life"/>
<channel id="door-state" typeId="door-state"/>
<channel id="fan-speed" typeId="fan-speed"/>
<channel id="work-mode" typeId="work-mode"/>
<channel id="ionizer" typeId="ionizer"/>
<channel id="ui-light" typeId="ui-light"/>
<channel id="safety-lock" typeId="safety-lock"/>
<channel id="status" typeId="status"/>
</channels>
<properties>
<property name="vendor">Electrolux</property>
</properties>
<representation-property>serialNumber</representation-property>
<config-description>
<parameter name="serialNumber" type="text" required="true">
<label>Serial Number</label>
<description>The appliance serial number.</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="washing-machine">
<supported-bridge-type-refs>
<bridge-type-ref id="api"/>
</supported-bridge-type-refs>
<label>Washing Machine</label>
<description>This thing represents the Electrolux Washing Machine.</description>
<channels>
<channel id="door-state" typeId="door-state"/>
<channel id="door-lock" typeId="door-lock"/>
<channel id="time-to-start" typeId="time-to-start"/>
<channel id="time-to-end" typeId="time-to-end"/>
<channel id="cycle-phase" typeId="cycle-phase"/>
<channel id="analog-temperature" typeId="analog-temperature"/>
<channel id="analog-spin-speed" typeId="analog-spin-speed"/>
<channel id="steam-value" typeId="steam-value"/>
<channel id="programs-order" typeId="programs-order"/>
<channel id="appliance-state" typeId="appliance-state"/>
<channel id="appliance-mode" typeId="appliance-mode"/>
<channel id="appliance-ui-sw-version" typeId="appliance-ui-sw-version"/>
<channel id="appliance-total-working-time" typeId="appliance-total-working-time"/>
<channel id="optisense-result" typeId="optisense-result"/>
<channel id="detergent-extradosage" typeId="detergent-extradosage"/>
<channel id="softener-extradosage" typeId="softener-extradosage"/>
<channel id="water-usage" typeId="water-usage"/>
<channel id="total-wash-cycles-count" typeId="total-wash-cycles-count"/>
<channel id="status" typeId="status"/>
</channels>
<properties>
<property name="vendor">Electrolux</property>
</properties>
<representation-property>serialNumber</representation-property>
<config-description>
<parameter name="serialNumber" type="text" required="true">
<label>Serial Number</label>
<description>The appliance serial number.</description>
</parameter>
</config-description>
</thing-type>
<channel-type id="status">
<item-type>String</item-type>
<label>Fetch Current Status</label>
<description>Used to fetch latest status from API.</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="temperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>The measured temperature.</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%">
</state>
</channel-type>
<channel-type id="humidity">
<item-type unitHint="%">Number:Dimensionless</item-type>
<label>Humidity</label>
<description>The measured humidity.</description>
<category>Humidity</category>
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
</channel-type>
<channel-type id="tvoc">
<item-type>Number:Dimensionless</item-type>
<label>TVOC</label>
<description>The total Volatile Organic Compounds.</description>
<state readOnly="true" pattern="%d ppb"/>
</channel-type>
<channel-type id="pm1">
<item-type>Number:Density</item-type>
<label>PM1</label>
<description>The Particulate Matter 1 (0.001mm).</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="pm2_5">
<item-type>Number:Density</item-type>
<label>PM2.5</label>
<description>The Particulate Matter 2.5 (0.0025mm).</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="pm10">
<item-type>Number:Density</item-type>
<label>PM10</label>
<description>The Particulate Matter 10 (0.01mm).</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="co2">
<item-type>Number:Dimensionless</item-type>
<label>CO2</label>
<description>The measured CarbonDioxide.</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="filter-life">
<item-type unitHint="%">Number:Dimensionless</item-type>
<label>Remaining Filter Life</label>
<description>The remaining filter life indication in percent.</description>
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
</channel-type>
<channel-type id="fan-speed">
<item-type>Number</item-type>
<label>Fan Speed Setting</label>
<description>The fan speed setting.</description>
<state>
<options>
<option value="1">Level 1</option>
<option value="2">Level 2</option>
<option value="3">Level 3</option>
<option value="4">Level 4</option>
<option value="5">Level 5</option>
<option value="6">Level 6</option>
<option value="7">Level 7</option>
<option value="8">Level 8</option>
<option value="9">Level 9</option>
</options>
</state>
</channel-type>
<channel-type id="work-mode">
<item-type>String</item-type>
<label>Work Mode Setting</label>
<description>The work mode setting.</description>
<state>
<options>
<option value="PowerOff">Power Off</option>
<option value="Auto">Automatic</option>
<option value="Manual">Manual</option>
</options>
</state>
</channel-type>
<channel-type id="ionizer">
<item-type>Switch</item-type>
<label>Ionizer Status</label>
<description>The ionizer status On/Off.</description>
</channel-type>
<channel-type id="ui-light">
<item-type>Switch</item-type>
<label>UI Light</label>
<description>The air quality light status indication.</description>
</channel-type>
<channel-type id="safety-lock">
<item-type>Switch</item-type>
<label>Safety Lock</label>
<description>The safety lock status.</description>
</channel-type>
<channel-type id="door-lock">
<item-type>Contact</item-type>
<label>Door Lock</label>
<description>The door lock status Open/Closed.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="door-state">
<item-type>Contact</item-type>
<label>Door State</label>
<description>The door status Open/Closed.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="time-to-start">
<item-type unitHint="s">Number:Time</item-type>
<label>Time To Delayed Start</label>
<description>The time remaining until the delayed start.</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="time-to-end">
<item-type unitHint="s">Number:Time</item-type>
<label>Time To End</label>
<description>The time remaining until the program will end.</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="appliance-ui-sw-version">
<item-type>String</item-type>
<label>Appliance UI SW Version</label>
<description>The appliance UI SW version.</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="appliance-total-working-time">
<item-type unitHint="s">Number:Time</item-type>
<label>Appliance Total Working Time</label>
<description>The appliance total working time.</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="optisense-result">
<item-type>Number</item-type>
<label>Optisense Result</label>
<description>The optisense result.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="detergent-extradosage">
<item-type>Number</item-type>
<label>Detergent Extra Dosage</label>
<description>The detergent extra dosage.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="softener-extradosage">
<item-type>Number</item-type>
<label>Softener Extra Dosage</label>
<description>The softener extra dosage.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="water-usage">
<item-type unitHint="l">Number:Volume</item-type>
<label>Water Usage</label>
<description>The water usage in litres.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="total-wash-cycles-count">
<item-type>Number</item-type>
<label>Total Wash Cycles Count</label>
<description>The total wash cycles count.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="cycle-phase">
<item-type>String</item-type>
<label>Cycle Phase</label>
<description>The washing cycle phase.</description>
<state readOnly="true">
<options>
<option value="UNAVAILABLE">Unavailable</option>
<option value="ANTICREASE">Anicrease</option>
<option value="DRAIN">Drain</option>
<option value="DRY">Dry</option>
<option value="PREWASH">Prewash</option>
<option value="RINSE">Rinse</option>
<option value="SPIN">Spin</option>
<option value="STEAM">Steam</option>
<option value="WASH">Wash</option>
</options>
</state>
</channel-type>
<channel-type id="appliance-state">
<item-type>String</item-type>
<label>Appliance State</label>
<description>The appliance state.</description>
<state readOnly="true">
<options>
<option value="ALARM">Alarm</option>
<option value="DELAYED_START">Delayed start</option>
<option value="END_OF_CYCLE">End of cycle</option>
<option value="IDLE">Idle</option>
<option value="OFF">Off</option>
<option value="PAUSED">Paused</option>
<option value="READY_TO_START">Ready to start</option>
<option value="RUNNING">Running</option>
</options>
</state>
</channel-type>
<channel-type id="appliance-mode">
<item-type>String</item-type>
<label>Appliance Mode</label>
<description>The appliance mode.</description>
<state readOnly="true">
<options>
<option value="DEMO">Demo</option>
<option value="DIAGNOSTIC">Diagnostic</option>
<option value="NORMAL">Normal</option>
<option value="SERVICE">Service</option>
</options>
</state>
</channel-type>
<channel-type id="analog-temperature">
<item-type>String</item-type>
<label>Washing Temperature </label>
<description>The user configured washing temperature.</description>
<state readOnly="true">
<options>
<option value="20_CELSIUS">20°C</option>
<option value="30_CELSIUS">30°C</option>
<option value="40_CELSIUS">40°C</option>
<option value="50_CELSIUS">50°C</option>
<option value="60_CELSIUS">60°C</option>
<option value="90_CELSIUS">90°C</option>
<option value="COLD">Cold</option>
</options>
</state>
</channel-type>
<channel-type id="analog-spin-speed">
<item-type>String</item-type>
<label>Spin Speed</label>
<description>The spin speed.</description>
<state readOnly="true">
<options>
<option value="DISABLED">Disabled</option>
<option value="0_RPM">0 rpm</option>
<option value="400_RPM">400 rpm</option>
<option value="600_RPM">600 rpm</option>
<option value="800_RPM">800 rpm</option>
<option value="1000_RPM">1000 rpm</option>
<option value="1200_RPM">1200 rpm</option>
<option value="1400_RPM">1400 rpm</option>
<option value="1600_RPM">1600 rpm</option>
</options>
</state>
</channel-type>
<channel-type id="steam-value">
<item-type>String</item-type>
<label>Steam Value</label>
<description>The user configured steam value.</description>
<state readOnly="true">
<options>
<option value="STEAM_OFF">Steam off</option>
<option value="STEAM_MIN">Steam minumum</option>
<option value="STEAM_MED">Steam medium</option>
<option value="STEAM_MAX">Steam max</option>
</options>
</state>
</channel-type>
<channel-type id="programs-order">
<item-type>String</item-type>
<label>Programs Order</label>
<description>The user configured washing program.</description>
<state readOnly="true">
<options>
<option value="MACHINE_SETTINGS_HIDDEN_TEST">Machine settings hidden test</option>
<option value="COTTON_PR_ECO40-60">Cotton Eco 40°C-60°C</option>
<option value="COTTON_PR_COTTONS">Cotton</option>
<option value="SYNTHETIC_PR_SYNTHETICS">Synthetics</option>
<option value="DELICATE_PR_DELICATES">Delicates</option>
<option value="WOOL_PR_WOOL_SILK">Wool Silk</option>
<option value="STEAM_REFRESH_PR_STEAMFRESHSCENT">Steam fresh cent</option>
<option value="SPIN_PR_DRAIN_SPIN">Drain spin</option>
<option value="SOFTENER_PR_RINSE">Softener rinse</option>
<option value="QUICK_20_MIN_PR_RAPID20MIN">Rapid 20min</option>
<option value="SPORT_JACKETS_PR_OUTDOOR">Sport jackets outdoor</option>
<option value="SYNTHETIC_PR_SPORT">Synthetic sport</option>
<option value="DENIM_PR_DENIM">Denim</option>
<option value="BLANKET_PR_DUVET">Blanket duvet</option>
<option value="SANITISE60_PR_ANTIALLERGYVAPOUR">Anti allergy vapour</option>
<option value="COTTON_PR_BUSINESSSHIRTS">Cotton business shirts</option>
<option value="DRUM_CLEAN_PR_MACHINECLEAN">Drum machine clean</option>
<option value="STEAM_DEWRINKLER_PR_STEAMCASHMERE">Steam cashmere</option>
<option value="SYNTHETIC_PR_BEDLINEN">Synthetic bedlinen</option>
<option value="COTTON_PR_TOWELS">Cotton towels</option>
<option value="DELICATE_PR_CURTAINS">Delicate curtains</option>
<option value="SYNTHETIC_PR_FLEECE">Synthetic fleece</option>
<option value="COTTON_PR_WORKINGCLOTHES">Cotton working clothes</option>
<option value="SYNTHETIC_PR_MICROFIBRE">Synthetic microfibre</option>
<option value="DELICATE_PR_BABY">Delicate baby</option>
</options>
</state>
</channel-type>
</thing:thing-descriptions>

View File

@ -130,6 +130,7 @@
<module>org.openhab.binding.ecovacs</module>
<module>org.openhab.binding.ecowatt</module>
<module>org.openhab.binding.ekey</module>
<module>org.openhab.binding.electroluxappliance</module>
<module>org.openhab.binding.elerotransmitterstick</module>
<module>org.openhab.binding.elroconnects</module>
<module>org.openhab.binding.emotiva</module>