diff --git a/bundles/org.openhab.binding.electroluxair/README.md b/bundles/org.openhab.binding.electroluxair/README.md index 3492197056c..638876a8eee 100644 --- a/bundles/org.openhab.binding.electroluxair/README.md +++ b/bundles/org.openhab.binding.electroluxair/README.md @@ -2,8 +2,6 @@ This is an openHAB binding for the Pure A9 Air Purifier, by Electrolux. -This binding uses the Electrolux Delta REST API. - ![Electrolux Pure A9](doc/electrolux_pure_a9.png) ## Supported Things @@ -26,15 +24,15 @@ Only the bridge require manual configuration. The Electrolux Pure A9 thing can b | Parameter | Description | Type | Default | Required | |-----------|--------------------------------------------------------------|--------|----------|----------| -| username | The username used to connect to the Electrolux Wellbeing app | String | NA | yes | -| password | The password used to connect to the Electrolux Wellbeing app | String | NA | yes | +| username | The username used to connect to the Electrolux app | String | NA | yes | +| password | The password used to connect to the Electrolux app | String | NA | yes | | refresh | Specifies the refresh interval in second | Number | 600 | yes | #### Electrolux Pure A9 | Parameter | Description | Type | Default | Required | |-----------|-------------------------------------------------------------------------|--------|----------|----------| -| deviceId | Product ID of your Electrolux Pure A9 found in Electrolux Wellbeing app | Number | NA | yes | +| deviceId | Product ID of your Electrolux Pure A9 found in Electrolux app | Number | NA | yes | ## Channels @@ -53,9 +51,10 @@ The following channels are supported: | co2 | Number:Dimensionless | This channel reports the CO2 level in ppm. | | fanSpeed | Number | This channel sets and reports the current fan speed (1-9). | | filterLife | 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). | +| ionizer | Switch | This channel sets and reports the status of the Ionizer function (On/Off). | | doorOpen | Contact | This channel reports the status of door (Opened/Closed). | -| workMode | String | This channel sets and reports the current work mode (Auto, Manual, PowerOff.)| +| workMode | String | This channel sets and reports the current work mode (Auto, Manual, PowerOff). | + ## Full Example @@ -83,4 +82,7 @@ Contact ElectroluxAirDoor "Electrolux Air Door Status" {channel="electroluxair:e String ElectroluxAirWorkModeSetting "ElectroluxAir Work Mode Setting" {channel="electroluxair:electroluxpurea9:myAPI:myElectroluxPureA9:workMode"} // Fan speed Number ElectroluxAirFanSpeed "Electrolux Air Fan Speed Setting" {channel="electroluxair:electroluxpurea9:myAPI:myElectroluxPureA9:fanSpeed"} + +// Ionizer +Switch ElectroluxAirIonizer "Electrolux Air Ionizer Setting" {channel="electroluxair:electroluxpurea9:myAPI:myElectroluxPureA9:ionizer"} ``` diff --git a/bundles/org.openhab.binding.electroluxair/pom.xml b/bundles/org.openhab.binding.electroluxair/pom.xml index f3f14e6f922..a414f8af1b9 100644 --- a/bundles/org.openhab.binding.electroluxair/pom.xml +++ b/bundles/org.openhab.binding.electroluxair/pom.xml @@ -1,4 +1,6 @@ - + + 4.0.0 diff --git a/bundles/org.openhab.binding.electroluxair/src/main/java/org/openhab/binding/electroluxair/internal/api/ElectroluxDeltaAPI.java b/bundles/org.openhab.binding.electroluxair/src/main/java/org/openhab/binding/electroluxair/internal/api/ElectroluxDeltaAPI.java index a2b0d85a7ce..367bff0a1a8 100644 --- a/bundles/org.openhab.binding.electroluxair/src/main/java/org/openhab/binding/electroluxair/internal/api/ElectroluxDeltaAPI.java +++ b/bundles/org.openhab.binding.electroluxair/src/main/java/org/openhab/binding/electroluxair/internal/api/ElectroluxDeltaAPI.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.electroluxair.internal.api; +import java.time.Instant; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; @@ -27,17 +28,13 @@ import org.eclipse.jetty.http.HttpStatus; import org.openhab.binding.electroluxair.internal.ElectroluxAirBridgeConfiguration; import org.openhab.binding.electroluxair.internal.ElectroluxAirException; import org.openhab.binding.electroluxair.internal.dto.ElectroluxPureA9DTO; -import org.openhab.binding.electroluxair.internal.dto.ElectroluxPureA9DTO.AppliancesInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; -import com.google.gson.annotations.SerializedName; /** * The {@link ElectroluxDeltaAPI} class defines the Elextrolux Delta API @@ -46,10 +43,17 @@ import com.google.gson.annotations.SerializedName; */ @NonNullByDefault public class ElectroluxDeltaAPI { - private static final String CLIENT_URL = "https://electrolux-wellbeing-client.vercel.app/api/mu52m5PR9X"; - private static final String SERVICE_URL = "https://api.delta.electrolux.com/api/"; + private static final String CLIENT_ID = "ElxOneApp"; + private static final String CLIENT_SECRET = "8UKrsKD7jH9zvTV7rz5HeCLkit67Mmj68FvRVTlYygwJYy4dW6KF2cVLPKeWzUQUd6KJMtTifFf4NkDnjI7ZLdfnwcPtTSNtYvbP7OzEkmQD9IjhMOf5e1zeAQYtt2yN"; + private static final String X_API_KEY = "2AMqwEV5MqVhTKrRCyYfVF8gmKrd2rAmp7cUsfky"; + + private static final String BASE_URL = "https://api.ocp.electrolux.one"; + private static final String TOKEN_URL = BASE_URL + "/one-account-authorization/api/v1/token"; + private static final String AUTHENTICATION_URL = BASE_URL + "/one-account-authentication/api/v1/authenticate"; + private static final String API_URL = BASE_URL + "/appliance/api/v2"; + private static final String APPLIANCES_URL = API_URL + "/appliances"; + private static final String JSON_CONTENT_TYPE = "application/json"; - private static final String LOGIN = "Users/Login"; private static final int MAX_RETRIES = 3; private final Logger logger = LoggerFactory.getLogger(ElectroluxDeltaAPI.class); @@ -57,6 +61,7 @@ public class ElectroluxDeltaAPI { private final HttpClient httpClient; private final ElectroluxAirBridgeConfiguration configuration; private String authToken = ""; + private Instant tokenExpiry = Instant.MIN; public ElectroluxDeltaAPI(ElectroluxAirBridgeConfiguration configuration, Gson gson, HttpClient httpClient) { this.gson = gson; @@ -66,70 +71,73 @@ public class ElectroluxDeltaAPI { public boolean refresh(Map electroluxAirThings) { try { - // Login - login(); + if (Instant.now().isAfter(this.tokenExpiry)) { + // Login again since token is expired + login(); + } // Get all appliances String json = getAppliances(); - JsonArray jsonArray = JsonParser.parseString(json).getAsJsonArray(); - - for (JsonElement jsonElement : jsonArray) { - String pncId = jsonElement.getAsJsonObject().get("pncId").getAsString(); - - // Get appliance info - String jsonApplianceInfo = getAppliancesInfo(pncId); - AppliancesInfo appliancesInfo = gson.fromJson(jsonApplianceInfo, AppliancesInfo.class); - - // Get applicance data - ElectroluxPureA9DTO dto = getAppliancesData(pncId, ElectroluxPureA9DTO.class); - if (appliancesInfo != null) { - dto.setApplicancesInfo(appliancesInfo); + ElectroluxPureA9DTO[] dtos = gson.fromJson(json, ElectroluxPureA9DTO[].class); + if (dtos != null) { + for (ElectroluxPureA9DTO dto : dtos) { + String applianceId = dto.getApplianceId(); + // Get appliance info + String jsonApplianceInfo = getAppliancesInfo(applianceId); + ElectroluxPureA9DTO.ApplianceInfo applianceInfo = gson.fromJson(jsonApplianceInfo, + ElectroluxPureA9DTO.ApplianceInfo.class); + if (applianceInfo != null) { + if ("AIR_PURIFIER".equals(applianceInfo.getDeviceType())) { + dto.setApplianceInfo(applianceInfo); + electroluxAirThings.put(dto.getProperties().getReported().getDeviceId(), dto); + } + } } - electroluxAirThings.put(dto.getTwin().getProperties().getReported().deviceId, dto); + return true; } - return true; - } catch (ElectroluxAirException e) { + } catch (JsonSyntaxException | ElectroluxAirException e) { logger.warn("Failed to refresh! {}", e.getMessage()); + } return false; } - public boolean workModePowerOff(String pncId) { + public boolean workModePowerOff(String applianceId) { String commandJSON = "{ \"WorkMode\": \"PowerOff\" }"; try { - return sendCommand(commandJSON, pncId); + return sendCommand(commandJSON, applianceId); } catch (ElectroluxAirException e) { logger.warn("Work mode powerOff failed {}", e.getMessage()); } return false; } - public boolean workModeAuto(String pncId) { + public boolean workModeAuto(String applianceId) { String commandJSON = "{ \"WorkMode\": \"Auto\" }"; try { - return sendCommand(commandJSON, pncId); + return sendCommand(commandJSON, applianceId); } catch (ElectroluxAirException e) { logger.warn("Work mode auto failed {}", e.getMessage()); } return false; } - public boolean workModeManual(String pncId) { + public boolean workModeManual(String applianceId) { String commandJSON = "{ \"WorkMode\": \"Manual\" }"; try { - return sendCommand(commandJSON, pncId); + return sendCommand(commandJSON, applianceId); } catch (ElectroluxAirException e) { logger.warn("Work mode manual failed {}", e.getMessage()); } return false; } - public boolean setFanSpeedLevel(String pncId, int fanSpeedLevel) { + public boolean setFanSpeedLevel(String applianceId, int fanSpeedLevel) { if (fanSpeedLevel < 1 && fanSpeedLevel > 10) { return false; } else { String commandJSON = "{ \"Fanspeed\": " + fanSpeedLevel + "}"; try { - return sendCommand(commandJSON, pncId); + return sendCommand(commandJSON, applianceId); } catch (ElectroluxAirException e) { logger.warn("Work mode manual failed {}", e.getMessage()); } @@ -137,40 +145,54 @@ public class ElectroluxDeltaAPI { return false; } - public boolean setIonizer(String pncId, String ionizerStatus) { + public boolean setIonizer(String applianceId, String ionizerStatus) { String commandJSON = "{ \"Ionizer\": " + ionizerStatus + "}"; try { - return sendCommand(commandJSON, pncId); + return sendCommand(commandJSON, applianceId); } catch (ElectroluxAirException e) { logger.warn("Work mode manual failed {}", e.getMessage()); } return false; } - private void login() throws ElectroluxAirException { - // Fetch ClientToken - Request request = httpClient.newRequest(CLIENT_URL).method(HttpMethod.GET); + private Request createRequest(String uri, HttpMethod httpMethod) { + Request request = httpClient.newRequest(uri).method(httpMethod); request.header(HttpHeader.ACCEPT, JSON_CONTENT_TYPE); request.header(HttpHeader.CONTENT_TYPE, JSON_CONTENT_TYPE); - logger.debug("HTTP GET Request {}.", request.toString()); + logger.debug("HTTP POST Request {}.", request.toString()); + + return request; + } + + private void login() throws ElectroluxAirException { try { + String json = "{\"clientId\": \"" + CLIENT_ID + "\", \"clientSecret\": \"" + CLIENT_SECRET + + "\", \"grantType\": \"client_credentials\"}"; + + // Fetch ClientToken + Request request = createRequest(TOKEN_URL, HttpMethod.POST); + request.content(new StringContentProvider(json), JSON_CONTENT_TYPE); + + logger.debug("HTTP POST Request {}.", request.toString()); + ContentResponse httpResponse = request.send(); if (httpResponse.getStatus() != HttpStatus.OK_200) { - throw new ElectroluxAirException("Failed to login " + httpResponse.getContentAsString()); + throw new ElectroluxAirException("Failed to get token 1" + httpResponse.getContentAsString()); } - String json = httpResponse.getContentAsString(); + json = httpResponse.getContentAsString(); + logger.trace("Token 1: {}", json); JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); String clientToken = jsonObject.get("accessToken").getAsString(); - // Login using ClientToken - json = "{ \"Username\": \"" + configuration.username + "\", \"Password\": \"" + configuration.password + // Login using access token 1 + json = "{ \"username\": \"" + configuration.username + "\", \"password\": \"" + configuration.password + "\" }"; - request = httpClient.newRequest(SERVICE_URL + LOGIN).method(HttpMethod.POST); - request.header(HttpHeader.ACCEPT, JSON_CONTENT_TYPE); - request.header(HttpHeader.CONTENT_TYPE, JSON_CONTENT_TYPE); + request = createRequest(AUTHENTICATION_URL, HttpMethod.POST); request.header(HttpHeader.AUTHORIZATION, "Bearer " + clientToken); + request.header("x-api-key", X_API_KEY); + request.content(new StringContentProvider(json), JSON_CONTENT_TYPE); logger.debug("HTTP POST Request {}.", request.toString()); @@ -179,10 +201,33 @@ public class ElectroluxDeltaAPI { if (httpResponse.getStatus() != HttpStatus.OK_200) { throw new ElectroluxAirException("Failed to login " + httpResponse.getContentAsString()); } + json = httpResponse.getContentAsString(); + logger.trace("Token 2: {}", json); + jsonObject = JsonParser.parseString(json).getAsJsonObject(); + String idToken = jsonObject.get("idToken").getAsString(); + String countryCode = jsonObject.get("countryCode").getAsString(); + String credentials = "{\"clientId\": \"" + CLIENT_ID + "\", \"idToken\": \"" + idToken + + "\", \"grantType\": \"urn:ietf:params:oauth:grant-type:token-exchange\"}"; + + // Fetch access token 2 + request = createRequest(TOKEN_URL, HttpMethod.POST); + request.header("Origin-Country-Code", countryCode); + request.content(new StringContentProvider(credentials), JSON_CONTENT_TYPE); + + logger.debug("HTTP POST Request {}.", request.toString()); + + httpResponse = request.send(); + if (httpResponse.getStatus() != HttpStatus.OK_200) { + throw new ElectroluxAirException("Failed to get token 1" + httpResponse.getContentAsString()); + } + // Fetch AccessToken json = httpResponse.getContentAsString(); + logger.trace("AccessToken: {}", json); jsonObject = JsonParser.parseString(json).getAsJsonObject(); this.authToken = jsonObject.get("accessToken").getAsString(); + int expiresIn = jsonObject.get("expiresIn").getAsInt(); + this.tokenExpiry = Instant.now().plusSeconds(expiresIn); } catch (InterruptedException | TimeoutException | ExecutionException e) { throw new ElectroluxAirException(e); } @@ -192,10 +237,9 @@ public class ElectroluxDeltaAPI { try { for (int i = 0; i < MAX_RETRIES; i++) { try { - Request request = httpClient.newRequest(SERVICE_URL + uri).method(HttpMethod.GET); + Request request = createRequest(uri, HttpMethod.GET); request.header(HttpHeader.AUTHORIZATION, "Bearer " + authToken); - request.header(HttpHeader.ACCEPT, JSON_CONTENT_TYPE); - request.header(HttpHeader.CONTENT_TYPE, JSON_CONTENT_TYPE); + request.header("x-api-key", X_API_KEY); ContentResponse response = request.send(); String content = response.getContentAsString(); @@ -218,44 +262,28 @@ public class ElectroluxDeltaAPI { } private String getAppliances() throws ElectroluxAirException { - String uri = "Domains/Appliances"; try { - return getFromApi(uri); + return getFromApi(APPLIANCES_URL); } catch (ElectroluxAirException | InterruptedException e) { throw new ElectroluxAirException(e); } } - private String getAppliancesInfo(String pncId) throws ElectroluxAirException { - String uri = "AppliancesInfo/" + pncId; + private String getAppliancesInfo(String applianceId) throws ElectroluxAirException { try { - return getFromApi(uri); + return getFromApi(APPLIANCES_URL + "/" + applianceId + "/info"); } catch (ElectroluxAirException | InterruptedException e) { throw new ElectroluxAirException(e); } } - private T getAppliancesData(String pncId, Class dto) throws ElectroluxAirException { - String uri = "Appliances/" + pncId; - String json; - - try { - json = getFromApi(uri); - } catch (ElectroluxAirException | InterruptedException e) { - throw new ElectroluxAirException(e); - } - return gson.fromJson(json, dto); - } - - private boolean sendCommand(String commandJSON, String pncId) throws ElectroluxAirException { - String uri = "Appliances/" + pncId + "/Commands"; + private boolean sendCommand(String commandJSON, String applianceId) throws ElectroluxAirException { try { for (int i = 0; i < MAX_RETRIES; i++) { try { - Request request = httpClient.newRequest(SERVICE_URL + uri).method(HttpMethod.PUT); + Request request = createRequest(APPLIANCES_URL + "/" + applianceId + "/command", HttpMethod.PUT); request.header(HttpHeader.AUTHORIZATION, "Bearer " + authToken); - request.header(HttpHeader.ACCEPT, JSON_CONTENT_TYPE); - request.header(HttpHeader.CONTENT_TYPE, JSON_CONTENT_TYPE); + request.header("x-api-key", X_API_KEY); request.content(new StringContentProvider(commandJSON), JSON_CONTENT_TYPE); ContentResponse response = request.send(); @@ -266,19 +294,7 @@ public class ElectroluxDeltaAPI { logger.debug("sendCommand failed, HTTP status: {}", response.getStatus()); login(); } else { - CommandResponseDTO commandResponse = gson.fromJson(content, CommandResponseDTO.class); - if (commandResponse != null) { - if (commandResponse.code == 200000) { - return true; - } else { - logger.warn("Failed to send command, error code: {}, description: {}", - commandResponse.code, commandResponse.codeDescription); - return false; - } - } else { - logger.warn("Failed to send command, commandResponse is null!"); - return false; - } + return true; } } catch (TimeoutException | InterruptedException e) { logger.warn("TimeoutException error in get"); @@ -289,26 +305,4 @@ public class ElectroluxDeltaAPI { } return false; } - - @SuppressWarnings("unused") - private static class CommandResponseDTO { - public int code; - public String codeDescription = ""; - public String information = ""; - public String message = ""; - public PayloadDTO payload = new PayloadDTO(); - public int status; - } - - private static class PayloadDTO { - @SerializedName("Ok") - public boolean ok; - @SerializedName("Response") - public ResponseDTO response = new ResponseDTO(); - } - - private static class ResponseDTO { - @SerializedName("Workmode") - public String workmode = ""; - } } diff --git a/bundles/org.openhab.binding.electroluxair/src/main/java/org/openhab/binding/electroluxair/internal/dto/ElectroluxPureA9DTO.java b/bundles/org.openhab.binding.electroluxair/src/main/java/org/openhab/binding/electroluxair/internal/dto/ElectroluxPureA9DTO.java index d05751da7f1..28d0d2660db 100644 --- a/bundles/org.openhab.binding.electroluxair/src/main/java/org/openhab/binding/electroluxair/internal/dto/ElectroluxPureA9DTO.java +++ b/bundles/org.openhab.binding.electroluxair/src/main/java/org/openhab/binding/electroluxair/internal/dto/ElectroluxPureA9DTO.java @@ -12,6 +12,9 @@ */ package org.openhab.binding.electroluxair.internal.dto; +import java.util.HashMap; +import java.util.Map; + import org.eclipse.jdt.annotation.NonNullByDefault; import com.google.gson.annotations.SerializedName; @@ -23,354 +26,232 @@ import com.google.gson.annotations.SerializedName; */ @NonNullByDefault public class ElectroluxPureA9DTO { - public String pncId = ""; - public ApplianceData applianceData = new ApplianceData(); - public AppliancesInfo applicancesInfo = new AppliancesInfo(); + private String applianceId = ""; + private ApplianceInfo applianceInfo = new ApplianceInfo(); + private ApplianceData applianceData = new ApplianceData(); + private Properties properties = new Properties(); + private String status = ""; + private String connectionState = ""; - public Twin twin = new Twin(); - public String telemetry = ""; + public String getApplianceId() { + return applianceId; + } - public String getPncId() { - return pncId; + public ApplianceInfo getApplianceInfo() { + return applianceInfo; + } + + public void setApplianceInfo(ApplianceInfo applianceInfo) { + this.applianceInfo = applianceInfo; } public ApplianceData getApplianceData() { return applianceData; } - public AppliancesInfo getApplicancesInfo() { - return applicancesInfo; + public Properties getProperties() { + return properties; } - public void setApplicancesInfo(AppliancesInfo applicancesInfo) { - this.applicancesInfo = applicancesInfo; + public String getStatus() { + return status; } - public Twin getTwin() { - return twin; + public String getConnectionState() { + return connectionState; } - public String getTelemetry() { - return telemetry; + public class ApplianceInfo { + private String manufacturingDateCode = ""; + private String serialNumber = ""; + private String pnc = ""; + private String brand = ""; + private String market = ""; + private String productArea = ""; + private String deviceType = ""; + private String project = ""; + private String model = ""; + private String variant = ""; + private String colour = ""; + + public String getManufacturingDateCode() { + return manufacturingDateCode; + } + + public String getSerialNumber() { + return serialNumber; + } + + public String getPnc() { + return pnc; + } + + public String getBrand() { + return brand; + } + + public String getMarket() { + return market; + } + + public String getProductArea() { + return productArea; + } + + public String getDeviceType() { + return deviceType; + } + + public String getProject() { + return project; + } + + public String getModel() { + return model; + } + + public String getVariant() { + return variant; + } + + public String getColour() { + return colour; + } } - public class MetaData1 { + class ApplianceData { + private String applianceName = ""; + private String created = ""; + private String modelName = ""; - @SerializedName("$lastUpdated") - public String lastUpdated1 = ""; - @SerializedName("$lastUpdatedVersion") - public int lastUpdatedVersion1; - @SerializedName("TimeZoneStandardName") - public TimeZoneStandardName timeZoneStandardName = new TimeZoneStandardName(); - @SerializedName("FrmVer_NIU") - public FrmVerNIU frmVerNIU = new FrmVerNIU(); - } + public String getApplianceName() { + return applianceName; + } - public class Metadata2 { + public String getCreated() { + return created; + } - @SerializedName("$lastUpdated") - public String lastUpdated2 = ""; - @SerializedName("FrmVer_NIU") - public FrmVerNIU frmVerNIU = new FrmVerNIU(); - @SerializedName("Workmode") - public Workmode workmode = new Workmode(); - @SerializedName("FilterRFID") - public FilterRFID filterRFID = new FilterRFID(); - @SerializedName("FilterLife") - public FilterLife filterLife = new FilterLife(); - @SerializedName("Fanspeed") - public Fanspeed fanspeed = new Fanspeed(); - @SerializedName("UILight") - public UILight uILight = new UILight(); - @SerializedName("SafetyLock") - public SafetyLock safetyLock = new SafetyLock(); - @SerializedName("Ionizer") - public Ionizer ionizer = new Ionizer(); - @SerializedName("Sleep") - public Sleep sleep = new Sleep(); - @SerializedName("Scheduler") - public Scheduler scheduler = new Scheduler(); - @SerializedName("FilterType") - public FilterType filterType = new FilterType(); - @SerializedName("DspIcoPM2_5") - public DspIcoPM25 dspIcoPM25 = new DspIcoPM25(); - @SerializedName("DspIcoPM1") - public DspIcoPM1 dspIcoPM1 = new DspIcoPM1(); - @SerializedName("DspIcoPM10") - public DspIcoPM10 dspIcoPM10 = new DspIcoPM10(); - @SerializedName("DspIcoTVOC") - public DspIcoTVOC dspIcoTVOC = new DspIcoTVOC(); - @SerializedName("ErrPM2_5") - public ErrPM25 errPM25 = new ErrPM25(); - @SerializedName("ErrTVOC") - public ErrTVOC errTVOC = new ErrTVOC(); - @SerializedName("ErrTempHumidity") - public ErrTempHumidity errTempHumidity = new ErrTempHumidity(); - @SerializedName("ErrFanMtr") - public ErrFanMtr errFanMtr = new ErrFanMtr(); - @SerializedName("ErrCommSensorDisplayBrd") - public ErrCommSensorDisplayBrd errCommSensorDisplayBrd = new ErrCommSensorDisplayBrd(); - @SerializedName("DoorOpen") - public DoorOpen doorOpen = new DoorOpen(); - @SerializedName("ErrRFID") - public ErrRFID errRFID = new ErrRFID(); - @SerializedName("SignalStrength") - public SignalStrength signalStrength = new SignalStrength(); - @SerializedName("PM1") - public PM1 pM1 = new PM1(); - @SerializedName("PM2_5") - public PM25 pM25 = new PM25(); - @SerializedName("PM10") - public PM10 pM10 = new PM10(); - @SerializedName("TVOC") - public TVOC tVOC = new TVOC(); - @SerializedName("CO2") - public CO2 cO2 = new CO2(); - @SerializedName("Temp") - public Temp temp = new Temp(); - @SerializedName("Humidity") - public Humidity humidity = new Humidity(); - @SerializedName("EnvLightLvl") - public EnvLightLvl envLightLvl = new EnvLightLvl(); - @SerializedName("RSSI") - public RSSI rSSI = new RSSI(); - } - - public class ApplianceData { - - public String applianceName = ""; - public String created = ""; - public String modelName = ""; - public String pncId = ""; - } - - public class AppliancesInfo { - public String brand = ""; - public String colour = ""; - public String device = ""; - public String model = ""; - public String serialNumber = ""; - } - - public class CO2 { - @SerializedName("$lastUpdated") - public String lastUpdated3 = ""; - } - - public class Desired { - - @SerializedName("TimeZoneStandardName") - public String timeZoneStandardName = ""; - @SerializedName("FrmVer_NIU") - public String frmVerNIU = ""; - @SerializedName("$metadata") - public MetaData1 metadata3 = new MetaData1(); - @SerializedName("$version") - public int version; - } - - public class DoorOpen { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class DspIcoPM1 { - @SerializedName("lastUpdated") - public String lastUpdated = ""; - } - - public class DspIcoPM10 { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class DspIcoPM25 { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class DspIcoTVOC { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class EnvLightLvl { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class ErrCommSensorDisplayBrd { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class ErrFanMtr { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class ErrPM25 { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class ErrRFID { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class ErrTVOC { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class ErrTempHumidity { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class Fanspeed { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class FilterLife { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class FilterRFID { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class FilterType { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class FrmVerNIU { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - @SerializedName("$lastUpdatedVersion") - public int lastUpdatedVersion; - } - - // public class FrmVerNIU_ { - // @SerializedName("$lastUpdated") - // public String lastUpdated = ""; - // } - - public class Humidity { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class Ionizer { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class PM1 { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class PM10 { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class PM25 { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; + public String getModelName() { + return modelName; + } } public class Properties { - public Desired desired = new Desired(); - public Reported reported = new Reported(); + private Desired desired = new Desired(); + private Reported reported = new Reported(); + private Object metadata = new Object(); + + public Desired getDesired() { + return desired; + } public Reported getReported() { return reported; } + + public Object getMetadata() { + return metadata; + } } - public class RSSI { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; + class Desired { + @SerializedName("TimeZoneStandardName") + private String timeZoneStandardName = ""; + @SerializedName("FrmVer_NIU") + private String frmVerNIU = ""; + @SerializedName("LocationReq") + private boolean locationReq; + private Map metadata = new HashMap<>(); + private int version; + + public String getTimeZoneStandardName() { + return timeZoneStandardName; + } + + public String getFrmVerNIU() { + return frmVerNIU; + } + + public boolean isLocationReq() { + return locationReq; + } + + public Map getMetadata() { + return metadata; + } + + public int getVersion() { + return version; + } } public class Reported { - @SerializedName("FrmVer_NIU") - public String frmVerNIU = ""; + private String frmVerNIU = ""; @SerializedName("Workmode") - public String workmode = ""; + private String workmode = ""; @SerializedName("FilterRFID") - public String filterRFID = ""; + private String filterRFID = ""; @SerializedName("FilterLife") - public int filterLife; + private int filterLife = 0; @SerializedName("Fanspeed") - public int fanspeed; + private int fanSpeed = 0; @SerializedName("UILight") - public boolean uILight; + private boolean uiLight = false; @SerializedName("SafetyLock") - public boolean safetyLock; + private boolean safetyLock = false; @SerializedName("Ionizer") - public boolean ionizer; + private boolean ionizer = false; @SerializedName("Sleep") - public boolean sleep; + private boolean sleep = false; @SerializedName("Scheduler") - public boolean scheduler; + private boolean scheduler = false; @SerializedName("FilterType") - public int filterType; + private int filterType = 0; @SerializedName("DspIcoPM2_5") - public boolean dspIcoPM25; + private boolean dspIcoPM25 = false; @SerializedName("DspIcoPM1") - public boolean dspIcoPM1; + private boolean dspIcoPM1 = false; @SerializedName("DspIcoPM10") - public boolean dspIcoPM10; + private boolean dspIcoPM10 = false; @SerializedName("DspIcoTVOC") - public boolean dspIcoTVOC; + private boolean dspIcoTVOC = false; @SerializedName("ErrPM2_5") - public boolean errPM25; + private boolean errPM25 = false; @SerializedName("ErrTVOC") - public boolean errTVOC; + private boolean errTVOC = false; @SerializedName("ErrTempHumidity") - public boolean errTempHumidity; + private boolean errTempHumidity = false; @SerializedName("ErrFanMtr") - public boolean errFanMtr; + private boolean errFanMtr = false; @SerializedName("ErrCommSensorDisplayBrd") - public boolean errCommSensorDisplayBrd; + private boolean errCommSensorDisplayBrd = false; @SerializedName("DoorOpen") - public boolean doorOpen; + private boolean doorOpen = false; @SerializedName("ErrRFID") - public boolean errRFID; + private boolean errRFID = false; @SerializedName("SignalStrength") - public String signalStrength = ""; - @SerializedName("$metadata") - public Metadata2 metadata2 = new Metadata2(); - @SerializedName("$version") - public int version; - public String deviceId = ""; - @SerializedName("PM1") - public int pM1; - @SerializedName("PM2_5") - public int pM25; - @SerializedName("PM10") - public int pM10; - @SerializedName("TVOC") - public int tVOC; + private String signalStrength = ""; + private Map metadata = new HashMap<>(); + private int version = 0; + private String deviceId = ""; @SerializedName("CO2") - public int cO2; + private int co2 = 0; + @SerializedName("TVOC") + private int tvoc = 0; @SerializedName("Temp") - public int temp; + private int temp = 0; @SerializedName("Humidity") - public int humidity; - @SerializedName("EnvLightLvl") - public int envLightLvl; + private int humidity = 0; @SerializedName("RSSI") - public int rSSI; + private int rssi = 0; + @SerializedName("PM1") + private int pm1 = 0; + @SerializedName("PM2_5") + private int pm25 = 0; + @SerializedName("PM10") + private int pm10 = 0; public String getFrmVerNIU() { return frmVerNIU; @@ -389,11 +270,11 @@ public class ElectroluxPureA9DTO { } public int getFanspeed() { - return fanspeed; + return fanSpeed; } - public boolean isuILight() { - return uILight; + public boolean isUILight() { + return uiLight; } public boolean isSafetyLock() { @@ -464,6 +345,10 @@ public class ElectroluxPureA9DTO { return signalStrength; } + public Map getMetadata() { + return metadata; + } + public int getVersion() { return version; } @@ -472,24 +357,12 @@ public class ElectroluxPureA9DTO { return deviceId; } - public int getpM1() { - return pM1; + public int getCO2() { + return co2; } - public int getpM25() { - return pM25; - } - - public int getpM10() { - return pM10; - } - - public int gettVOC() { - return tVOC; - } - - public int getcO2() { - return cO2; + public int getTVOC() { + return tvoc; } public int getTemp() { @@ -500,82 +373,20 @@ public class ElectroluxPureA9DTO { return humidity; } - public int getEnvLightLvl() { - return envLightLvl; + public int getRSSI() { + return rssi; } - public int getrSSI() { - return rSSI; - } - } - - public class SafetyLock { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class Scheduler { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class SignalStrength { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class Sleep { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class TVOC { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class Temp { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class TimeZoneStandardName { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - @SerializedName("$lastUpdatedVersion") - public int lastUpdatedVersion; - } - - public class Twin { - public String deviceId = ""; - public Properties properties = new Properties(); - public String status = ""; - public String connectionState = ""; - - public String getDeviceId() { - return deviceId; + public int getPM1() { + return pm1; } - public Properties getProperties() { - return properties; + public int getPM25() { + return pm25; } - public String getStatus() { - return status; + public int getPM10() { + return pm10; } - - public String getConnectionState() { - return connectionState; - } - } - - public class UILight { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; - } - - public class Workmode { - @SerializedName("$lastUpdated") - public String lastUpdated = ""; } } diff --git a/bundles/org.openhab.binding.electroluxair/src/main/java/org/openhab/binding/electroluxair/internal/handler/ElectroluxAirBridgeHandler.java b/bundles/org.openhab.binding.electroluxair/src/main/java/org/openhab/binding/electroluxair/internal/handler/ElectroluxAirBridgeHandler.java index 0edf43206b5..f5106eac2da 100644 --- a/bundles/org.openhab.binding.electroluxair/src/main/java/org/openhab/binding/electroluxair/internal/handler/ElectroluxAirBridgeHandler.java +++ b/bundles/org.openhab.binding.electroluxair/src/main/java/org/openhab/binding/electroluxair/internal/handler/ElectroluxAirBridgeHandler.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.electroluxair.internal.handler; -import static org.openhab.binding.electroluxair.internal.ElectroluxAirBindingConstants.THING_TYPE_BRIDGE; +import static org.openhab.binding.electroluxair.internal.ElectroluxAirBindingConstants.*; import java.util.Collection; import java.util.Collections; @@ -37,6 +37,7 @@ 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 com.google.gson.Gson; @@ -147,6 +148,8 @@ public class ElectroluxAirBridgeHandler extends BaseBridgeHandler { @Override public void handleCommand(ChannelUID channelUID, Command command) { - return; + if (CHANNEL_STATUS.equals(channelUID.getId()) && command instanceof RefreshType) { + scheduler.schedule(this::refreshAndUpdateStatus, 1, TimeUnit.SECONDS); + } } } diff --git a/bundles/org.openhab.binding.electroluxair/src/main/java/org/openhab/binding/electroluxair/internal/handler/ElectroluxAirHandler.java b/bundles/org.openhab.binding.electroluxair/src/main/java/org/openhab/binding/electroluxair/internal/handler/ElectroluxAirHandler.java index 0b2f377170e..949ef8c04e4 100644 --- a/bundles/org.openhab.binding.electroluxair/src/main/java/org/openhab/binding/electroluxair/internal/handler/ElectroluxAirHandler.java +++ b/bundles/org.openhab.binding.electroluxair/src/main/java/org/openhab/binding/electroluxair/internal/handler/ElectroluxAirHandler.java @@ -22,6 +22,7 @@ import javax.measure.quantity.Temperature; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.electroluxair.internal.ElectroluxAirBindingConstants; import org.openhab.binding.electroluxair.internal.ElectroluxAirConfiguration; import org.openhab.binding.electroluxair.internal.api.ElectroluxDeltaAPI; import org.openhab.binding.electroluxair.internal.dto.ElectroluxPureA9DTO; @@ -38,6 +39,7 @@ import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.BridgeHandler; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.openhab.core.types.State; @@ -66,30 +68,46 @@ public class ElectroluxAirHandler extends BaseThingHandler { public void handleCommand(ChannelUID channelUID, Command command) { logger.debug("Command received: {}", command); if (CHANNEL_STATUS.equals(channelUID.getId()) || command instanceof RefreshType) { - update(); + Bridge bridge = getBridge(); + if (bridge != null) { + BridgeHandler bridgeHandler = bridge.getHandler(); + if (bridgeHandler != null) { + bridgeHandler.handleCommand(channelUID, command); + } + } } else { ElectroluxPureA9DTO dto = getElectroluxPureA9DTO(); - ElectroluxDeltaAPI api = getElectroluxDeltaAPO(); + ElectroluxDeltaAPI api = getElectroluxDeltaAPI(); if (api != null && dto != null) { if (CHANNEL_WORK_MODE.equals(channelUID.getId())) { if (command.toString().equals(COMMAND_WORKMODE_POWEROFF)) { - api.workModePowerOff(dto.getPncId()); + api.workModePowerOff(dto.getApplianceId()); } else if (command.toString().equals(COMMAND_WORKMODE_AUTO)) { - api.workModeAuto(dto.getPncId()); + api.workModeAuto(dto.getApplianceId()); } else if (command.toString().equals(COMMAND_WORKMODE_MANUAL)) { - api.workModeManual(dto.getPncId()); + api.workModeManual(dto.getApplianceId()); } } else if (CHANNEL_FAN_SPEED.equals(channelUID.getId())) { - api.setFanSpeedLevel(dto.getPncId(), Integer.parseInt(command.toString())); + api.setFanSpeedLevel(dto.getApplianceId(), Integer.parseInt(command.toString())); } else if (CHANNEL_IONIZER.equals(channelUID.getId())) { if (command == OnOffType.OFF) { - api.setIonizer(dto.getPncId(), "false"); + api.setIonizer(dto.getApplianceId(), "false"); } else if (command == OnOffType.ON) { - api.setIonizer(dto.getPncId(), "true"); + api.setIonizer(dto.getApplianceId(), "true"); } else { logger.debug("Unknown command! {}", command); } } + + Bridge bridge = getBridge(); + if (bridge != null) { + BridgeHandler bridgeHandler = bridge.getHandler(); + if (bridgeHandler != null) { + bridgeHandler.handleCommand( + new ChannelUID(this.thing.getUID(), ElectroluxAirBindingConstants.CHANNEL_STATUS), + RefreshType.REFRESH); + } + } } } } @@ -115,7 +133,7 @@ public class ElectroluxAirHandler extends BaseThingHandler { } } - private @Nullable ElectroluxDeltaAPI getElectroluxDeltaAPO() { + private @Nullable ElectroluxDeltaAPI getElectroluxDeltaAPI() { Bridge bridge = getBridge(); if (bridge != null) { ElectroluxAirBridgeHandler handler = (ElectroluxAirBridgeHandler) bridge.getHandler(); @@ -143,6 +161,7 @@ public class ElectroluxAirHandler extends BaseThingHandler { 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); }); updateStatus(ThingStatus.ONLINE); @@ -152,38 +171,35 @@ public class ElectroluxAirHandler extends BaseThingHandler { private State getValue(String channelId, ElectroluxPureA9DTO dto) { switch (channelId) { case CHANNEL_TEMPERATURE: - return new QuantityType(dto.getTwin().getProperties().getReported().getTemp(), - SIUnits.CELSIUS); + return new QuantityType(dto.getProperties().getReported().getTemp(), SIUnits.CELSIUS); case CHANNEL_HUMIDITY: - return new QuantityType(dto.getTwin().getProperties().getReported().getHumidity(), - Units.PERCENT); + return new QuantityType(dto.getProperties().getReported().getHumidity(), Units.PERCENT); case CHANNEL_TVOC: - return new QuantityType(dto.getTwin().getProperties().getReported().gettVOC(), + return new QuantityType(dto.getProperties().getReported().getTVOC(), Units.MICROGRAM_PER_CUBICMETRE); case CHANNEL_PM1: - return new QuantityType(dto.getTwin().getProperties().getReported().getpM1(), + return new QuantityType(dto.getProperties().getReported().getPM1(), Units.PARTS_PER_BILLION); case CHANNEL_PM25: - return new QuantityType(dto.getTwin().getProperties().getReported().getpM25(), + return new QuantityType(dto.getProperties().getReported().getPM25(), Units.PARTS_PER_BILLION); case CHANNEL_PM10: - return new QuantityType(dto.getTwin().getProperties().getReported().getpM10(), + return new QuantityType(dto.getProperties().getReported().getPM10(), Units.PARTS_PER_BILLION); case CHANNEL_CO2: - return new QuantityType(dto.getTwin().getProperties().getReported().getcO2(), + return new QuantityType(dto.getProperties().getReported().getCO2(), Units.PARTS_PER_MILLION); case CHANNEL_FAN_SPEED: - return new StringType(Integer.toString(dto.getTwin().getProperties().getReported().getFanspeed())); + return new StringType(Integer.toString(dto.getProperties().getReported().getFanspeed())); case CHANNEL_FILTER_LIFE: - return new QuantityType(dto.getTwin().getProperties().getReported().getFilterLife(), + return new QuantityType(dto.getProperties().getReported().getFilterLife(), Units.PERCENT); case CHANNEL_IONIZER: - return OnOffType.from(dto.getTwin().getProperties().getReported().ionizer); + return OnOffType.from(dto.getProperties().getReported().isIonizer()); case CHANNEL_WORK_MODE: - return new StringType(dto.getTwin().getProperties().getReported().workmode); + return new StringType(dto.getProperties().getReported().getWorkmode()); case CHANNEL_DOOR_OPEN: - return dto.getTwin().getProperties().getReported().doorOpen ? OpenClosedType.OPEN - : OpenClosedType.CLOSED; + return dto.getProperties().getReported().isDoorOpen() ? OpenClosedType.OPEN : OpenClosedType.CLOSED; } return UnDefType.UNDEF; } @@ -196,13 +212,12 @@ public class ElectroluxAirHandler extends BaseThingHandler { if (bridgeHandler != null) { ElectroluxPureA9DTO dto = bridgeHandler.getElectroluxAirThings().get(config.getDeviceId()); if (dto != null) { - properties.put(Thing.PROPERTY_VENDOR, dto.getApplicancesInfo().brand); - properties.put(PROPERTY_COLOUR, dto.getApplicancesInfo().colour); - properties.put(PROPERTY_DEVICE, dto.getApplicancesInfo().device); - properties.put(Thing.PROPERTY_MODEL_ID, dto.getApplicancesInfo().model); - properties.put(Thing.PROPERTY_SERIAL_NUMBER, dto.getApplicancesInfo().serialNumber); - properties.put(Thing.PROPERTY_FIRMWARE_VERSION, - dto.getTwin().getProperties().getReported().frmVerNIU); + properties.put(Thing.PROPERTY_VENDOR, dto.getApplianceInfo().getBrand()); + properties.put(PROPERTY_COLOUR, dto.getApplianceInfo().getColour()); + properties.put(PROPERTY_DEVICE, dto.getApplianceInfo().getDeviceType()); + properties.put(Thing.PROPERTY_MODEL_ID, dto.getApplianceInfo().getModel()); + properties.put(Thing.PROPERTY_SERIAL_NUMBER, dto.getApplianceInfo().getSerialNumber()); + properties.put(Thing.PROPERTY_FIRMWARE_VERSION, dto.getProperties().getReported().getFrmVerNIU()); } } } diff --git a/bundles/org.openhab.binding.electroluxair/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.electroluxair/src/main/resources/OH-INF/thing/thing-types.xml index a69ca57505f..5e085edb75e 100644 --- a/bundles/org.openhab.binding.electroluxair/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.electroluxair/src/main/resources/OH-INF/thing/thing-types.xml @@ -181,5 +181,4 @@ Ionizer Status -