mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[ecovacs] Initial contribution (#12231)
* [ecovacs] Initial contribution Add initial version of a binding for vacuum cleaners made by Ecovacs. Signed-off-by: Danny Baumann <dannybaumann@web.de>
This commit is contained in:
parent
98b8d7225c
commit
b47a205f44
@ -82,6 +82,7 @@
|
||||
/bundles/org.openhab.binding.echonetlite/ @mikeb01
|
||||
/bundles/org.openhab.binding.ecobee/ @mhilbush
|
||||
/bundles/org.openhab.binding.ecotouch/ @sibbi77
|
||||
/bundles/org.openhab.binding.ecovacs/ @maniac103
|
||||
/bundles/org.openhab.binding.ecowatt/ @lolodomo
|
||||
/bundles/org.openhab.binding.ekey/ @hmerk
|
||||
/bundles/org.openhab.binding.electroluxair/ @jannegpriv
|
||||
|
@ -406,6 +406,11 @@
|
||||
<artifactId>org.openhab.binding.ecotouch</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.ecovacs</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.ecowatt</artifactId>
|
||||
|
13
bundles/org.openhab.binding.ecovacs/NOTICE
Normal file
13
bundles/org.openhab.binding.ecovacs/NOTICE
Normal 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
|
175
bundles/org.openhab.binding.ecovacs/README.md
Normal file
175
bundles/org.openhab.binding.ecovacs/README.md
Normal file
@ -0,0 +1,175 @@
|
||||
# Ecovacs Binding
|
||||
|
||||
This binding provides integration for vacuum cleaning / mopping robots made by Ecovacs (<https://www.ecovacs.com/>).
|
||||
It discovers devices and communicates to them by using Ecovacs' cloud services.
|
||||
|
||||
## Supported Things
|
||||
|
||||
- Ecovacs cloud API (`ecovacsapi`)
|
||||
- Vacuum cleaner (`vacuum`)
|
||||
|
||||
At this point, the following devices are fully supported and verified to be working:
|
||||
|
||||
- Deebot OZMO 900/905
|
||||
- Deebot OZMO 920
|
||||
- Deebot OZMO 930
|
||||
- Deebot OZMO 950
|
||||
- Deebot OZMO Slim 10/11
|
||||
- Deebot N8 series
|
||||
|
||||
The following devices will likely work because they are using similar protocols as the above ones:
|
||||
|
||||
- Deebot 600/601/605
|
||||
- Deebot 900/901
|
||||
- Deebot OZMO 610
|
||||
- Deebot 710/711/711s
|
||||
- Deebot OZMO T5
|
||||
- Deebot (OZMO) T8 series
|
||||
- Deebot T9 series
|
||||
- Deebot Slim 2
|
||||
- Deebot N3 MAX
|
||||
- Deebot N7
|
||||
- Deebot U2 series
|
||||
- Deebot X1 Omni
|
||||
|
||||
## Discovery
|
||||
|
||||
At first, you need to manually create the bridge thing for the cloud API.
|
||||
Once that is done, the supported devices will be automatically discovered and added to the inbox.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
For the cloud API thing, the following parameters must be configured:
|
||||
|
||||
| Config | Description |
|
||||
|-----------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| email | The email address you used when registering the Ecovacs cloud account |
|
||||
| password | The cloud account password |
|
||||
| continent | The continent you are residing on, or 'World' if none matches. This is used to select the correct cloud server to connect to. |
|
||||
|
||||
For the vacuum things, there is no required configuration (when using discovery). The following parameters exist:
|
||||
|
||||
| Config | Description |
|
||||
|--------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| serialNumber | Required: The device's serial number as printed on the barcode below the dust bin. Filled automatically when using discovery. |
|
||||
| refresh | Refresh interval for polled data (see below), in minutes. By default set to 5 minutes. |
|
||||
|
||||
## Channels
|
||||
|
||||
The list below lists all channels supported by the binding.
|
||||
In case a particular channel is not supported by a given device (see remarks), it is automatically removed from the given thing.
|
||||
|
||||
| Channel | Type | Description | Read Only | Updated By | Remarks |
|
||||
|-----------------------------------------|----------------------|-----------------------------------------------------------|-----------|------------|----------|
|
||||
| actions#command | String | Command to execute | No | Event | [1] |
|
||||
| status#state | String | Current operational state | Yes | Event | [2] |
|
||||
| status#current-cleaning-mode | String | Mode used in current cleaning run | Yes | Event | [3], [4] |
|
||||
| status#current-cleaning-time | Number:Time | Time spent in current cleaning run | Yes | Event | [4] |
|
||||
| status#current-cleaned-area | Number:Area | Area cleaned in current cleaning run | Yes | Event | [4] |
|
||||
| status#current-cleaning-spot-definition | String | The spot to clean in current cleaning run | Yes | Event | [4], [5] |
|
||||
| status#water-system-present | Switch | Whether the device is currently ready for mopping | Yes | Event | [6] |
|
||||
| status#wifi-rssi | Number:Power | The current Wi-Fi signal strength of the device | Yes | Polling | [7] |
|
||||
| consumables#main-brush-lifetime | Number:Dimensionless | The remaining life time of the main brush in percent | Yes | Polling | [8] |
|
||||
| consumables#side-brush-lifetime | Number:Dimensionless | The remaining life time of the side brush in percent | Yes | Polling | |
|
||||
| consumables#dust-filter-lifetime | Number:Dimensionless | The remaining life time of the dust bin filter in percent | Yes | Polling | |
|
||||
| consumables#other-component-lifetime | Number:Dimensionless | The remaining time until device maintenance in percent | Yes | Polling | [9] |
|
||||
| last-clean#last-clean-start | DateTime | The start time of the last completed cleaning run | Yes | Polling | |
|
||||
| last-clean#last-clean-duration | Number:Time | The duration of the last completed cleaning run | Yes | Polling | |
|
||||
| last-clean#last-clean-area | Number:Area | The area cleaned in the last completed cleaning run | Yes | Polling | |
|
||||
| last-clean#last-clean-mode | String | The mode used for the last completed cleaning run | Yes | Polling | [3] |
|
||||
| last-clean#last-clean-map | Image | The map image of the last completed cleaning run | Yes | Polling | |
|
||||
| total-stats#total-cleaning-time | Number:Time | The total time spent cleaning during the device life time | Yes | Polling | |
|
||||
| total-stats#total-cleaned-area | Number:Area | The total area cleaned during the device life time | Yes | Polling | |
|
||||
| total-stats#total-clean-runs | Number | The total number of clean runs in the device life time | Yes | Polling | |
|
||||
| settings#auto-empty | Switch | Whether dust bin auto empty to station is enabled | No | Polling | [10] |
|
||||
| settings#cleaning-passes | Number | Number of cleaning passes to be used (1 or 2) | No | Polling | [9] |
|
||||
| settings#continuous-cleaning | Switch | Whether unfinished cleaning resumes after charging | No | Polling | |
|
||||
| settings#suction-power | String | The power level used during cleaning | No | Polling | [11] |
|
||||
| settings#true-detect-3d | Switch | Whether True Detect 3D is enabled | No | Polling | [12] |
|
||||
| settings#voice-volume | Dimmer | The voice volume level in percent | No | Polling | [13] |
|
||||
| settings#water-amount | String | The amount of water to be used when mopping | No | Polling | [14] |
|
||||
|
||||
Remarks:
|
||||
|
||||
- [1] See [section below](#command-channel-actions)
|
||||
- [2] Possible states: 'cleaning', 'pause', 'stop', 'drying', 'washing', 'returning' and 'charging' (where 'drying' and 'washing' are only available on newer models with auto empty station)
|
||||
- [3] Possible states: 'auto', 'edge', 'spot', 'spotArea', 'customArea', 'singleRoom' (some of which depend on device capabilities)
|
||||
- [4] Current cleaning status is only valid if the device is currently cleaning
|
||||
- [5] Only valid for 'spot', 'spotArea' and 'customArea' cleaning modes; value can be used for 'spotArea' and 'customArea' commands (see below)
|
||||
- [6] Only present if device has a mopping system
|
||||
- [7] Only present on newer generation devices (Deebot OZMO 950 and newer)
|
||||
- [8] Only present if device has a main brush
|
||||
- [9] Only present on newer generation devices (Deebot N8/T8 or newer)
|
||||
- [10] Only present if device has a dustbin auto empty station; supports both on/off command (to turn on/off the setting) and the string 'trigger' (to trigger immediate auto empty)
|
||||
- [11] Only present if device can control power level. Possible values vary by device: 'normal' and 'high' are always supported, 'silent' and 'higher' are supported for some models
|
||||
- [12] Only present if device supports True Detect 3D
|
||||
- [13] Only present if device has voice reporting
|
||||
- [14] Only present if device has a mopping system. Possible values include 'low', 'medium', 'high' and 'veryhigh'
|
||||
|
||||
## Command Channel Actions
|
||||
|
||||
The following actions are supported by the `command` channel:
|
||||
|
||||
| Name | Action | Remarks |
|
||||
|--------------|-------------------------------------------|------------------------------------------------------|
|
||||
| `clean` | Start cleaning in automatic mode. | |
|
||||
| `spotArea` | Start cleaning specific rooms. | <ul><li>Only if supported by device, which can be recognized by `spotArea` being present in the list of possible states of the `current-cleaning-mode` channel.</li><li>Format: `spotArea:<room IDs>`, where `room IDs` is a semicolon separated list of room letters as shown in Ecovacs' app, so a valid command could e.g. be `spotArea:A;D;E`.</li><li>If you want to run 2 clean passes, amend `:x2` to the command, e.g. `spotArea:A;C;B:x2`.</li></ul> |
|
||||
| `customArea` | Start cleaning specific areas. | <ul><li>Only if supported by device, which can be recognized by `customArea` being present in the list of possible states of the `current-cleaning-mode` channel.</li><li>Format: `customArea:<x1>;<y1>;<x2>;<y2>, where the parameters are coordinates (in mm) relative to the map.</li><li>The coordinates can be obtained from the `current-cleaning-spot-definition` channel when starting a custom area run from the app.</li><li>If you want to run 2 clean passes, amend `:x2` to the command, e.g. `customArea:100;100;1000;1000:x2`.</li></ul> |
|
||||
| `pause` | Pause cleaning if it's currently active. | If the device is idle, the command is ignored. |
|
||||
| `resume` | Resume cleaning if it's currently paused. | If the device is not paused, the command is ignored. |
|
||||
| `stop` | Stop cleaning immediately. | |
|
||||
| `charge` | Send device to charging station. | |
|
||||
|
||||
## Rule actions
|
||||
|
||||
This binding includes a rule action, which allows playback of specific sounds on the device in case the device has a speaker.
|
||||
There is a separate instance for each device, which can be retrieved like this:
|
||||
|
||||
```java
|
||||
val vacuumActions = getActions("ecovacs","ecovacs:vacuum:1234567890")
|
||||
```
|
||||
|
||||
where the first parameter always has to be `ecovacs` and the second is the full Thing UID of the device that should be used.
|
||||
Once this action instance is retrieved, you can invoke the `playSound(String type)` method on it:
|
||||
|
||||
```java
|
||||
vacuumActions.playSound("beep")
|
||||
```
|
||||
|
||||
Supported sound types include:
|
||||
|
||||
- `beep`
|
||||
- `iAmHere`
|
||||
- `startup`
|
||||
- `suspended`
|
||||
- `batteryLow`
|
||||
|
||||
For special use cases, there is also a `playSoundWithId(int soundId)` method, where you can pass the numeric ID of the sound to play.
|
||||
The exact meaning of the number depends on the specific device; you'll need to experiment with different numbers to see how the number-to-sound mapping looks like.
|
||||
For reference, a list for the Deebot 900 can be found [here](https://github.com/bmartin5692/sucks/blob/D901/protocol.md#user-content-sounds).
|
||||
|
||||
## File Based Configuration
|
||||
|
||||
If you want to create the API bridge in a .things file, the entry has to look as follows:
|
||||
|
||||
```java
|
||||
Bridge ecovacs:ecovacsapi:ecovacsapi [ email="your.email@provider.com", password="yourpassword", continent="ww" ]
|
||||
```
|
||||
|
||||
The possible values for `continent` include the following values:
|
||||
|
||||
- `ww` for World
|
||||
- `eu` for Europe
|
||||
- `na` for North America
|
||||
- `as` for Asia
|
||||
|
||||
The devices are detected automatically.
|
||||
If you also want to enter those manually, the syntax is as follows:
|
||||
|
||||
```java
|
||||
Bridge ecovacs:ecovacsapi:ecovacsapi [ email="your.email@provider.com", password="yourpassword", continent="ww" ]
|
||||
{
|
||||
Thing vacuum myDeebot "Deebot Vacuum" [ serialNumber="serial as printed on label below dust bin" ]
|
||||
}
|
||||
```
|
||||
|
51
bundles/org.openhab.binding.ecovacs/pom.xml
Normal file
51
bundles/org.openhab.binding.ecovacs/pom.xml
Normal file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.ecovacs</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Ecovacs Binding</name>
|
||||
<properties>
|
||||
<smack.version>4.3.3</smack.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.igniterealtime.smack</groupId>
|
||||
<artifactId>smack-tcp</artifactId>
|
||||
<version>${smack.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.igniterealtime.smack</groupId>
|
||||
<artifactId>smack-im</artifactId>
|
||||
<version>${smack.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.igniterealtime.smack</groupId>
|
||||
<artifactId>smack-extensions</artifactId>
|
||||
<version>${smack.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.igniterealtime.smack</groupId>
|
||||
<artifactId>smack-java7</artifactId>
|
||||
<version>${smack.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.igniterealtime.smack</groupId>
|
||||
<artifactId>smack-resolver-javax</artifactId>
|
||||
<version>${smack.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.ecovacs-${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-ecovacs" description="Ecovacs Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature dependency="true">openhab.tp-hivemqclient</feature>
|
||||
<bundle dependency="true">mvn:org.igniterealtime.smack/smack-tcp/4.3.3</bundle>
|
||||
<bundle dependency="true">mvn:org.jxmpp/jxmpp-core/0.6.3</bundle>
|
||||
<bundle dependency="true">mvn:org.jxmpp/jxmpp-jid/0.6.3</bundle>
|
||||
<bundle dependency="true">mvn:org.jxmpp/jxmpp-util-cache/0.6.3</bundle>
|
||||
<bundle dependency="true">mvn:org.minidns/minidns-core/0.3.3</bundle>
|
||||
<bundle dependency="true">mvn:org.igniterealtime.smack/smack-core/4.3.3</bundle>
|
||||
<bundle dependency="true">mvn:org.igniterealtime.smack/smack-im/4.3.3</bundle>
|
||||
<bundle dependency="true">mvn:org.igniterealtime.smack/smack-extensions/4.3.3</bundle>
|
||||
<bundle dependency="true">mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.xpp3/1.1.4c_7</bundle>
|
||||
<bundle start-level="80">mvn:org.igniterealtime.smack/smack-resolver-javax/4.3.3</bundle>
|
||||
<bundle start-level="80">mvn:org.igniterealtime.smack/smack-java7/4.3.3</bundle>
|
||||
<bundle start-level="80">mvn:org.igniterealtime.smack/smack-sasl-javax/4.3.3</bundle>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.ecovacs/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.commands.PlaySoundCommand.SoundType;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanMode;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.DeviceCapability;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.MoppingWaterAmount;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.SuctionPower;
|
||||
import org.openhab.binding.ecovacs.internal.util.StateOptionEntry;
|
||||
import org.openhab.binding.ecovacs.internal.util.StateOptionMapping;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link EcovacsBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EcovacsBindingConstants {
|
||||
private static final String BINDING_ID = "ecovacs";
|
||||
|
||||
// Client keys and secrets used for API authentication (extracted from Ecovacs app)
|
||||
public static final String CLIENT_KEY = "1520391301804";
|
||||
public static final String CLIENT_SECRET = "6c319b2a5cd3e66e39159c2e28f2fce9";
|
||||
public static final String AUTH_CLIENT_KEY = "1520391491841";
|
||||
public static final String AUTH_CLIENT_SECRET = "77ef58ce3afbe337da74aa8c5ab963a9";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_API = new ThingTypeUID(BINDING_ID, "ecovacsapi");
|
||||
public static final ThingTypeUID THING_TYPE_VACUUM = new ThingTypeUID(BINDING_ID, "vacuum");
|
||||
|
||||
// List of all channel UIDs
|
||||
public static final String CHANNEL_ID_AUTO_EMPTY = "settings#auto-empty";
|
||||
public static final String CHANNEL_ID_BATTERY_LEVEL = "status#battery";
|
||||
public static final String CHANNEL_ID_CLEANING_MODE = "status#current-cleaning-mode";
|
||||
public static final String CHANNEL_ID_CLEANING_TIME = "status#current-cleaning-time";
|
||||
public static final String CHANNEL_ID_CLEANED_AREA = "status#current-cleaned-area";
|
||||
public static final String CHANNEL_ID_CLEANING_PASSES = "settings#cleaning-passes";
|
||||
public static final String CHANNEL_ID_CLEANING_SPOT_DEFINITION = "status#current-cleaning-spot-definition";
|
||||
public static final String CHANNEL_ID_CONTINUOUS_CLEANING = "settings#continuous-cleaning";
|
||||
public static final String CHANNEL_ID_COMMAND = "actions#command";
|
||||
public static final String CHANNEL_ID_DUST_FILTER_LIFETIME = "consumables#dust-filter-lifetime";
|
||||
public static final String CHANNEL_ID_ERROR_CODE = "status#error-code";
|
||||
public static final String CHANNEL_ID_ERROR_DESCRIPTION = "status#error-description";
|
||||
public static final String CHANNEL_ID_LAST_CLEAN_START = "last-clean#last-clean-start";
|
||||
public static final String CHANNEL_ID_LAST_CLEAN_DURATION = "last-clean#last-clean-duration";
|
||||
public static final String CHANNEL_ID_LAST_CLEAN_AREA = "last-clean#last-clean-area";
|
||||
public static final String CHANNEL_ID_LAST_CLEAN_MODE = "last-clean#last-clean-mode";
|
||||
public static final String CHANNEL_ID_LAST_CLEAN_MAP = "last-clean#last-clean-map";
|
||||
public static final String CHANNEL_ID_MAIN_BRUSH_LIFETIME = "consumables#main-brush-lifetime";
|
||||
public static final String CHANNEL_ID_OTHER_COMPONENT_LIFETIME = "consumables#other-component-lifetime";
|
||||
public static final String CHANNEL_ID_SIDE_BRUSH_LIFETIME = "consumables#side-brush-lifetime";
|
||||
public static final String CHANNEL_ID_STATE = "status#state";
|
||||
public static final String CHANNEL_ID_SUCTION_POWER = "settings#suction-power";
|
||||
public static final String CHANNEL_ID_TOTAL_CLEANING_TIME = "total-stats#total-cleaning-time";
|
||||
public static final String CHANNEL_ID_TOTAL_CLEANED_AREA = "total-stats#total-cleaned-area";
|
||||
public static final String CHANNEL_ID_TOTAL_CLEAN_RUNS = "total-stats#total-clean-runs";
|
||||
public static final String CHANNEL_ID_TRUE_DETECT_3D = "settings#true-detect-3d";
|
||||
public static final String CHANNEL_ID_VOICE_VOLUME = "settings#voice-volume";
|
||||
public static final String CHANNEL_ID_WATER_PLATE_PRESENT = "status#water-system-present";
|
||||
public static final String CHANNEL_ID_WATER_AMOUNT = "settings#water-amount";
|
||||
public static final String CHANNEL_ID_WIFI_RSSI = "status#wifi-rssi";
|
||||
|
||||
public static final String CHANNEL_TYPE_ID_CLEAN_MODE = "current-cleaning-mode";
|
||||
public static final String CHANNEL_TYPE_ID_LAST_CLEAN_MODE = "last-clean-mode";
|
||||
|
||||
public static final String CMD_AUTO_CLEAN = "clean";
|
||||
public static final String CMD_PAUSE = "pause";
|
||||
public static final String CMD_RESUME = "resume";
|
||||
public static final String CMD_CHARGE = "charge";
|
||||
public static final String CMD_STOP = "stop";
|
||||
public static final String CMD_SPOT_AREA = "spotArea";
|
||||
public static final String CMD_CUSTOM_AREA = "customArea";
|
||||
|
||||
public static final StateOptionMapping<CleanMode> CLEAN_MODE_MAPPING = StateOptionMapping.<CleanMode> of(
|
||||
new StateOptionEntry<CleanMode>(CleanMode.AUTO, "auto"),
|
||||
new StateOptionEntry<CleanMode>(CleanMode.EDGE, "edge", DeviceCapability.EDGE_CLEANING),
|
||||
new StateOptionEntry<CleanMode>(CleanMode.SPOT, "spot", DeviceCapability.SPOT_CLEANING),
|
||||
new StateOptionEntry<CleanMode>(CleanMode.SPOT_AREA, "spotArea", DeviceCapability.SPOT_AREA_CLEANING),
|
||||
new StateOptionEntry<CleanMode>(CleanMode.CUSTOM_AREA, "customArea", DeviceCapability.CUSTOM_AREA_CLEANING),
|
||||
new StateOptionEntry<CleanMode>(CleanMode.SINGLE_ROOM, "singleRoom", DeviceCapability.SINGLE_ROOM_CLEANING),
|
||||
new StateOptionEntry<CleanMode>(CleanMode.PAUSE, "pause"),
|
||||
new StateOptionEntry<CleanMode>(CleanMode.STOP, "stop"),
|
||||
new StateOptionEntry<CleanMode>(CleanMode.WASHING, "washing"),
|
||||
new StateOptionEntry<CleanMode>(CleanMode.DRYING, "drying"),
|
||||
new StateOptionEntry<CleanMode>(CleanMode.RETURNING, "returning"));
|
||||
|
||||
public static final StateOptionMapping<MoppingWaterAmount> WATER_AMOUNT_MAPPING = StateOptionMapping
|
||||
.<MoppingWaterAmount> of(new StateOptionEntry<MoppingWaterAmount>(MoppingWaterAmount.LOW, "low"),
|
||||
new StateOptionEntry<MoppingWaterAmount>(MoppingWaterAmount.MEDIUM, "medium"),
|
||||
new StateOptionEntry<MoppingWaterAmount>(MoppingWaterAmount.HIGH, "high"),
|
||||
new StateOptionEntry<MoppingWaterAmount>(MoppingWaterAmount.VERY_HIGH, "veryhigh"));
|
||||
|
||||
public static final StateOptionMapping<SuctionPower> SUCTION_POWER_MAPPING = StateOptionMapping.<SuctionPower> of(
|
||||
new StateOptionEntry<SuctionPower>(SuctionPower.SILENT, "silent",
|
||||
DeviceCapability.EXTENDED_CLEAN_SPEED_CONTROL),
|
||||
new StateOptionEntry<SuctionPower>(SuctionPower.NORMAL, "normal"),
|
||||
new StateOptionEntry<SuctionPower>(SuctionPower.HIGH, "high"), new StateOptionEntry<SuctionPower>(
|
||||
SuctionPower.HIGHER, "higher", DeviceCapability.EXTENDED_CLEAN_SPEED_CONTROL));
|
||||
|
||||
public static final StateOptionMapping<SoundType> SOUND_TYPE_MAPPING = StateOptionMapping.<SoundType> of(
|
||||
new StateOptionEntry<SoundType>(SoundType.BEEP, "beep"),
|
||||
new StateOptionEntry<SoundType>(SoundType.I_AM_HERE, "iAmHere"),
|
||||
new StateOptionEntry<SoundType>(SoundType.STARTUP, "startup"),
|
||||
new StateOptionEntry<SoundType>(SoundType.SUSPENDED, "suspended"),
|
||||
new StateOptionEntry<SoundType>(SoundType.BATTERY_LOW, "batteryLow"));
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal;
|
||||
|
||||
import static org.openhab.binding.ecovacs.internal.EcovacsBindingConstants.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
|
||||
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
|
||||
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.osgi.framework.Bundle;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { DynamicStateDescriptionProvider.class, EcovacsDynamicStateDescriptionProvider.class })
|
||||
public class EcovacsDynamicStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
|
||||
private final TranslationProvider i18nProvider;
|
||||
|
||||
@Activate
|
||||
public EcovacsDynamicStateDescriptionProvider(final @Reference EventPublisher eventPublisher,
|
||||
final @Reference TranslationProvider i18nProvider,
|
||||
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry,
|
||||
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.i18nProvider = i18nProvider;
|
||||
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
|
||||
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<StateOption> localizedStateOptions(List<StateOption> options, Channel channel,
|
||||
@Nullable Locale locale) {
|
||||
@Nullable
|
||||
ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
|
||||
String channelTypeId = channelTypeUID != null ? channelTypeUID.getId() : "";
|
||||
if (CHANNEL_TYPE_ID_CLEAN_MODE.equals(channelTypeId) || CHANNEL_TYPE_ID_LAST_CLEAN_MODE.equals(channelTypeId)) {
|
||||
final Bundle bundle = bundleContext.getBundle();
|
||||
return options.stream().map(opt -> {
|
||||
String key = "ecovacs.cleaning-mode." + opt.getValue();
|
||||
String label = this.i18nProvider.getText(bundle, key, opt.getLabel(), locale);
|
||||
return new StateOption(opt.getValue(), label);
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
return super.localizedStateOptions(options, channel, locale);
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal;
|
||||
|
||||
import static org.openhab.binding.ecovacs.internal.EcovacsBindingConstants.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.handler.EcovacsApiHandler;
|
||||
import org.openhab.binding.ecovacs.internal.handler.EcovacsVacuumHandler;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
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;
|
||||
|
||||
/**
|
||||
* The {@link EcovacsHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.ecovacs", service = ThingHandlerFactory.class)
|
||||
public class EcovacsHandlerFactory extends BaseThingHandlerFactory {
|
||||
private final HttpClientFactory httpClientFactory;
|
||||
private final LocaleProvider localeProvider;
|
||||
private final TranslationProvider i18Provider;
|
||||
private final EcovacsDynamicStateDescriptionProvider stateDescriptionProvider;
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_API, THING_TYPE_VACUUM);
|
||||
|
||||
@Activate
|
||||
public EcovacsHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
|
||||
final @Reference EcovacsDynamicStateDescriptionProvider stateDescriptionProvider,
|
||||
final @Reference LocaleProvider localeProvider, final @Reference TranslationProvider i18Provider) {
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||
this.localeProvider = localeProvider;
|
||||
this.i18Provider = i18Provider;
|
||||
}
|
||||
|
||||
@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_API.equals(thingTypeUID)) {
|
||||
return new EcovacsApiHandler((Bridge) thing, httpClientFactory.getCommonHttpClient(), localeProvider);
|
||||
} else {
|
||||
return new EcovacsVacuumHandler(thing, i18Provider, localeProvider, stateDescriptionProvider);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.action;
|
||||
|
||||
import static org.openhab.binding.ecovacs.internal.EcovacsBindingConstants.*;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.commands.PlaySoundCommand;
|
||||
import org.openhab.binding.ecovacs.internal.handler.EcovacsVacuumHandler;
|
||||
import org.openhab.core.automation.annotation.ActionInput;
|
||||
import org.openhab.core.automation.annotation.RuleAction;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@ThingActionsScope(name = "ecovacs")
|
||||
@NonNullByDefault
|
||||
public class EcovacsVacuumActions implements ThingActions {
|
||||
private final Logger logger = LoggerFactory.getLogger(EcovacsVacuumActions.class);
|
||||
private @Nullable EcovacsVacuumHandler handler;
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
this.handler = (EcovacsVacuumHandler) handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@RuleAction(label = "@text/playSoundActionLabel", description = "@text/playSoundActionDesc")
|
||||
public void playSound(
|
||||
@ActionInput(name = "type", label = "@text/actionInputSoundTypeLabel", description = "@text/actionInputSoundTypeDesc") String type) {
|
||||
EcovacsVacuumHandler handler = this.handler;
|
||||
if (handler != null) {
|
||||
Optional<PlaySoundCommand.SoundType> soundType = SOUND_TYPE_MAPPING.findMappedEnumValue(type);
|
||||
if (soundType.isPresent()) {
|
||||
handler.playSound(new PlaySoundCommand(soundType.get()));
|
||||
} else {
|
||||
logger.debug("Sound type '{}' is unknown, ignoring", type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RuleAction(label = "@text/playSoundActionLabel", description = "@text/playSoundActionDesc")
|
||||
public void playSoundWithId(
|
||||
@ActionInput(name = "soundId", label = "@text/actionInputSoundIdLabel", description = "@text/actionInputSoundIdDesc") int soundId) {
|
||||
EcovacsVacuumHandler handler = this.handler;
|
||||
if (handler != null) {
|
||||
handler.playSound(new PlaySoundCommand(soundId));
|
||||
}
|
||||
}
|
||||
|
||||
public static void playSound(@Nullable ThingActions actions, String type) {
|
||||
if (actions instanceof EcovacsVacuumActions) {
|
||||
((EcovacsVacuumActions) actions).playSound(type);
|
||||
}
|
||||
}
|
||||
|
||||
public static void playSoundWithId(@Nullable ThingActions actions, int soundId) {
|
||||
if (actions instanceof EcovacsVacuumActions) {
|
||||
((EcovacsVacuumActions) actions).playSoundWithId(soundId);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.EcovacsApiImpl;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface EcovacsApi {
|
||||
public static EcovacsApi create(HttpClient httpClient, EcovacsApiConfiguration configuration) {
|
||||
return new EcovacsApiImpl(httpClient, configuration);
|
||||
}
|
||||
|
||||
public void loginAndGetAccessToken() throws EcovacsApiException, InterruptedException;
|
||||
|
||||
public List<EcovacsDevice> getDevices() throws EcovacsApiException, InterruptedException;
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.MD5Util;
|
||||
|
||||
/**
|
||||
* @author Johannes Ptaszyk - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class EcovacsApiConfiguration {
|
||||
private final String deviceId;
|
||||
private final String username;
|
||||
private final String password;
|
||||
private final String continent;
|
||||
private final String country;
|
||||
private final String language;
|
||||
private final String clientKey;
|
||||
private final String clientSecret;
|
||||
private final String authClientKey;
|
||||
private final String authClientSecret;
|
||||
|
||||
public EcovacsApiConfiguration(String deviceId, String username, String password, String continent, String country,
|
||||
String language, String clientKey, String clientSecret, String authClientKey, String authClientSecret) {
|
||||
this.deviceId = MD5Util.getMD5Hash(deviceId);
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.continent = continent;
|
||||
this.country = country;
|
||||
this.language = language;
|
||||
this.clientKey = clientKey;
|
||||
this.clientSecret = clientSecret;
|
||||
this.authClientKey = authClientKey;
|
||||
this.authClientSecret = authClientSecret;
|
||||
}
|
||||
|
||||
public String getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public String getContinent() {
|
||||
return continent;
|
||||
}
|
||||
|
||||
public String getCountry() {
|
||||
if ("gb".equalsIgnoreCase(country)) {
|
||||
// United Kingdom's ISO 3166 abbreviation is 'gb', but Ecovacs wants the TLD instead, which is 'uk' for
|
||||
// historical reasons
|
||||
return "uk";
|
||||
}
|
||||
return country.toLowerCase();
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public String getResource() {
|
||||
return deviceId.substring(0, 8);
|
||||
}
|
||||
|
||||
public String getAuthOpenId() {
|
||||
return "global";
|
||||
}
|
||||
|
||||
public String getTimeZone() {
|
||||
return "GMT-8";
|
||||
}
|
||||
|
||||
public String getRealm() {
|
||||
return "ecouser.net";
|
||||
}
|
||||
|
||||
public String getPortalAUthRequestWith() {
|
||||
return "users";
|
||||
}
|
||||
|
||||
public String getOrg() {
|
||||
return "ECOWW";
|
||||
}
|
||||
|
||||
public String getEdition() {
|
||||
return "ECOGLOBLE";
|
||||
}
|
||||
|
||||
public String getBizType() {
|
||||
return "ECOVACS_IOT";
|
||||
}
|
||||
|
||||
public String getChannel() {
|
||||
return "google_play";
|
||||
}
|
||||
|
||||
public String getAppCode() {
|
||||
return "global_e";
|
||||
}
|
||||
|
||||
public String getAppVersion() {
|
||||
return "1.6.3";
|
||||
}
|
||||
|
||||
public String getDeviceType() {
|
||||
return "1";
|
||||
}
|
||||
|
||||
public String getClientKey() {
|
||||
return clientKey;
|
||||
}
|
||||
|
||||
public String getClientSecret() {
|
||||
return clientSecret;
|
||||
}
|
||||
|
||||
public String getAuthClientKey() {
|
||||
return authClientKey;
|
||||
}
|
||||
|
||||
public String getAuthClientSecret() {
|
||||
return authClientSecret;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EcovacsApiException extends Exception {
|
||||
private static final long serialVersionUID = -5903398729974682356L;
|
||||
public final boolean isAuthFailure;
|
||||
|
||||
public EcovacsApiException(String reason) {
|
||||
this(reason, false);
|
||||
}
|
||||
|
||||
public EcovacsApiException(String reason, boolean isAuthFailure) {
|
||||
super(reason);
|
||||
this.isAuthFailure = isAuthFailure;
|
||||
}
|
||||
|
||||
public EcovacsApiException(Response response) {
|
||||
super("HTTP status " + response.getStatus());
|
||||
isAuthFailure = response.getStatus() == 401;
|
||||
}
|
||||
|
||||
public EcovacsApiException(Throwable cause) {
|
||||
this(cause, false);
|
||||
}
|
||||
|
||||
public EcovacsApiException(Throwable cause, boolean isAuthFailure) {
|
||||
super(cause);
|
||||
this.isAuthFailure = isAuthFailure;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.commands.IotDeviceCommand;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanLogRecord;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanMode;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.DeviceCapability;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface EcovacsDevice {
|
||||
public interface EventListener {
|
||||
void onFirmwareVersionChanged(EcovacsDevice device, String fwVersion);
|
||||
|
||||
void onBatteryLevelUpdated(EcovacsDevice device, int newLevelPercent);
|
||||
|
||||
void onChargingStateUpdated(EcovacsDevice device, boolean charging);
|
||||
|
||||
void onCleaningModeUpdated(EcovacsDevice device, CleanMode newMode, Optional<String> areaDefinition);
|
||||
|
||||
void onCleaningStatsUpdated(EcovacsDevice device, int cleanedArea, int cleaningTimeSeconds);
|
||||
|
||||
void onWaterSystemPresentUpdated(EcovacsDevice device, boolean present);
|
||||
|
||||
void onErrorReported(EcovacsDevice device, int errorCode);
|
||||
|
||||
void onEventStreamFailure(EcovacsDevice device, Throwable error);
|
||||
}
|
||||
|
||||
String getSerialNumber();
|
||||
|
||||
String getModelName();
|
||||
|
||||
boolean hasCapability(DeviceCapability cap);
|
||||
|
||||
void connect(EventListener listener, ScheduledExecutorService scheduler)
|
||||
throws EcovacsApiException, InterruptedException;
|
||||
|
||||
void disconnect(ScheduledExecutorService scheduler);
|
||||
|
||||
<T> T sendCommand(IotDeviceCommand<T> command) throws EcovacsApiException, InterruptedException;
|
||||
|
||||
List<CleanLogRecord> getCleanLogs() throws EcovacsApiException, InterruptedException;
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class AbstractAreaCleaningCommand extends AbstractNoResponseCommand {
|
||||
private final String jsonTypeName;
|
||||
private final String areaDefinition;
|
||||
private final int cleanPasses;
|
||||
|
||||
AbstractAreaCleaningCommand(String jsonTypeName, String areaDefinition, int cleanPasses) {
|
||||
this.jsonTypeName = jsonTypeName;
|
||||
this.areaDefinition = areaDefinition;
|
||||
this.cleanPasses = cleanPasses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
switch (version) {
|
||||
case XML:
|
||||
return "Clean";
|
||||
case JSON:
|
||||
return "clean";
|
||||
case JSON_V2:
|
||||
return "clean_V2";
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyXmlPayload(Document doc, Element ctl) {
|
||||
Element clean = doc.createElement("clean");
|
||||
clean.setAttribute("act", "s");
|
||||
clean.setAttribute("type", "SpotArea");
|
||||
clean.setAttribute("speed", "standard");
|
||||
clean.setAttribute("p", areaDefinition);
|
||||
clean.setAttribute("deep", String.valueOf(cleanPasses));
|
||||
ctl.appendChild(clean);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable JsonElement getJsonPayloadArgs(ProtocolVersion version) {
|
||||
JsonObject args = new JsonObject();
|
||||
args.addProperty("act", "start");
|
||||
|
||||
JsonObject payload = args;
|
||||
if (version == ProtocolVersion.JSON_V2) {
|
||||
JsonObject content = new JsonObject();
|
||||
args.add("content", content);
|
||||
payload = content;
|
||||
payload.addProperty("value", this.areaDefinition);
|
||||
payload.addProperty("donotClean", 0);
|
||||
payload.addProperty("total", 0);
|
||||
} else {
|
||||
payload.addProperty("content", this.areaDefinition);
|
||||
}
|
||||
payload.addProperty("count", cleanPasses);
|
||||
payload.addProperty("type", this.jsonTypeName);
|
||||
return args;
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanMode;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
abstract class AbstractCleaningCommand extends AbstractNoResponseCommand {
|
||||
private final String xmlAction;
|
||||
private final String jsonAction;
|
||||
private final Optional<CleanMode> mode;
|
||||
|
||||
protected AbstractCleaningCommand(String xmlAction, String jsonAction, @Nullable CleanMode mode) {
|
||||
super();
|
||||
this.xmlAction = xmlAction;
|
||||
this.jsonAction = jsonAction;
|
||||
this.mode = Optional.ofNullable(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
switch (version) {
|
||||
case XML:
|
||||
return "Clean";
|
||||
case JSON:
|
||||
return "clean";
|
||||
case JSON_V2:
|
||||
return "clean_V2";
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyXmlPayload(Document doc, Element ctl) {
|
||||
Element clean = doc.createElement("clean");
|
||||
getCleanModeProperty(ProtocolVersion.XML).ifPresent(m -> clean.setAttribute("type", m));
|
||||
clean.setAttribute("speed", "standard");
|
||||
clean.setAttribute("act", xmlAction);
|
||||
ctl.appendChild(clean);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable JsonElement getJsonPayloadArgs(ProtocolVersion version) {
|
||||
JsonObject args = new JsonObject();
|
||||
args.addProperty("act", jsonAction);
|
||||
getCleanModeProperty(version).ifPresent(m -> {
|
||||
JsonObject payload = args;
|
||||
if (version == ProtocolVersion.JSON_V2) {
|
||||
JsonObject content = new JsonObject();
|
||||
args.add("content", content);
|
||||
payload = content;
|
||||
}
|
||||
payload.addProperty("type", m);
|
||||
});
|
||||
return args;
|
||||
}
|
||||
|
||||
private Optional<String> getCleanModeProperty(ProtocolVersion version) {
|
||||
return mode.flatMap(m -> {
|
||||
switch (m) {
|
||||
case AUTO:
|
||||
return Optional.of("auto");
|
||||
case CUSTOM_AREA:
|
||||
return Optional.of(version == ProtocolVersion.XML ? "CustomArea" : "customArea");
|
||||
case EDGE:
|
||||
return Optional.of("border");
|
||||
case SPOT:
|
||||
return Optional.of("spot");
|
||||
case SPOT_AREA:
|
||||
return Optional.of(version == ProtocolVersion.XML ? "SpotArea" : "spotArea");
|
||||
case SINGLE_ROOM:
|
||||
return Optional.of("singleRoom");
|
||||
case STOP:
|
||||
return Optional.of("stop");
|
||||
default:
|
||||
return Optional.empty();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractNoResponseCommand extends IotDeviceCommand<AbstractNoResponseCommand.Nothing> {
|
||||
public static class Nothing {
|
||||
private Nothing() {
|
||||
}
|
||||
|
||||
private static final Nothing INSTANCE = new Nothing();
|
||||
}
|
||||
|
||||
protected AbstractNoResponseCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Nothing convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version, Gson gson) {
|
||||
return Nothing.INSTANCE;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CustomAreaCleaningCommand extends AbstractAreaCleaningCommand {
|
||||
public CustomAreaCleaningCommand(String areaDefinition, int cleanPasses) {
|
||||
super("customArea", areaDefinition, cleanPasses);
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EmptyDustbinCommand extends AbstractNoResponseCommand {
|
||||
public EmptyDustbinCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
if (version == ProtocolVersion.XML) {
|
||||
throw new IllegalStateException("Empty dust bin is not supported for XML");
|
||||
}
|
||||
return "setAutoEmpty";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable JsonElement getJsonPayloadArgs(ProtocolVersion version) {
|
||||
JsonObject args = new JsonObject();
|
||||
args.addProperty("act", "start");
|
||||
return args;
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.CachedMapInfoReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.XPathUtils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetActiveMapIdCommand extends IotDeviceCommand<String> {
|
||||
public GetActiveMapIdCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
return version == ProtocolVersion.XML ? "GetMapM" : "getCachedMapInfo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version, Gson gson)
|
||||
throws DataParsingException {
|
||||
if (response instanceof PortalIotCommandJsonResponse) {
|
||||
CachedMapInfoReport resp = ((PortalIotCommandJsonResponse) response).getResponsePayloadAs(gson,
|
||||
CachedMapInfoReport.class);
|
||||
return resp.mapInfos.stream().filter(i -> i.used != 0).map(i -> i.mapId).findFirst().orElse("");
|
||||
} else {
|
||||
String payload = ((PortalIotCommandXmlResponse) response).getResponsePayloadXml();
|
||||
return XPathUtils.getFirstXPathMatch(payload, "//@i").getNodeValue();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.BatteryReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.xml.DeviceInfo;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetBatteryInfoCommand extends IotDeviceCommand<Integer> {
|
||||
public GetBatteryInfoCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
return version == ProtocolVersion.XML ? "GetBatteryInfo" : "getBattery";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version, Gson gson)
|
||||
throws DataParsingException {
|
||||
if (response instanceof PortalIotCommandJsonResponse) {
|
||||
BatteryReport resp = ((PortalIotCommandJsonResponse) response).getResponsePayloadAs(gson,
|
||||
BatteryReport.class);
|
||||
return resp.percent;
|
||||
} else {
|
||||
String payload = ((PortalIotCommandXmlResponse) response).getResponsePayloadXml();
|
||||
return DeviceInfo.parseBatteryInfo(payload);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.ChargeReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.xml.DeviceInfo;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.ChargeMode;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetChargeStateCommand extends IotDeviceCommand<ChargeMode> {
|
||||
public GetChargeStateCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
return version == ProtocolVersion.XML ? "GetChargeState" : "getChargeState";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChargeMode convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version, Gson gson)
|
||||
throws DataParsingException {
|
||||
if (response instanceof PortalIotCommandJsonResponse) {
|
||||
ChargeReport resp = ((PortalIotCommandJsonResponse) response).getResponsePayloadAs(gson,
|
||||
ChargeReport.class);
|
||||
return resp.isCharging != 0 ? ChargeMode.CHARGING : ChargeMode.IDLE;
|
||||
} else {
|
||||
String payload = ((PortalIotCommandXmlResponse) response).getResponsePayloadXml();
|
||||
return DeviceInfo.parseChargeInfo(payload, gson);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanLogRecord;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanMode;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetCleanLogsCommand extends IotDeviceCommand<List<CleanLogRecord>> {
|
||||
private static final int LOG_SIZE = 20;
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
if (version != ProtocolVersion.XML) {
|
||||
throw new IllegalStateException("Command is only supported for XML");
|
||||
}
|
||||
return "GetCleanLogs";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyXmlPayload(Document doc, Element ctl) {
|
||||
ctl.setAttribute("count", String.valueOf(LOG_SIZE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CleanLogRecord> convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version,
|
||||
Gson gson) throws DataParsingException {
|
||||
String payload = ((PortalIotCommandXmlResponse) response).getResponsePayloadXml();
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
try {
|
||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
NodeList entryNodes = db.parse(new ByteArrayInputStream(payload.getBytes("UTF-8"))).getFirstChild()
|
||||
.getChildNodes();
|
||||
List<CleanLogRecord> result = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < entryNodes.getLength(); i++) {
|
||||
NamedNodeMap attrs = entryNodes.item(i).getAttributes();
|
||||
String area = attrs.getNamedItem("a").getNodeValue();
|
||||
String startTime = attrs.getNamedItem("s").getNodeValue();
|
||||
String duration = attrs.getNamedItem("l").getNodeValue();
|
||||
|
||||
result.add(new CleanLogRecord(Long.parseLong(startTime), Integer.parseInt(duration),
|
||||
Integer.parseInt(area), Optional.empty(), CleanMode.IDLE));
|
||||
}
|
||||
return result;
|
||||
} catch (ParserConfigurationException | SAXException | IOException e) {
|
||||
throw new DataParsingException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.CleanReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.CleanReportV2;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.xml.CleaningInfo;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanMode;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetCleanStateCommand extends IotDeviceCommand<CleanMode> {
|
||||
public GetCleanStateCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
switch (version) {
|
||||
case XML:
|
||||
return "GetCleanState";
|
||||
case JSON:
|
||||
return "getCleanInfo";
|
||||
case JSON_V2:
|
||||
return "getCleanInfo_V2";
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CleanMode convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version, Gson gson)
|
||||
throws DataParsingException {
|
||||
if (response instanceof PortalIotCommandJsonResponse) {
|
||||
final PortalIotCommandJsonResponse jsonResponse = (PortalIotCommandJsonResponse) response;
|
||||
final CleanMode mode;
|
||||
if (version == ProtocolVersion.JSON) {
|
||||
CleanReport resp = jsonResponse.getResponsePayloadAs(gson, CleanReport.class);
|
||||
mode = resp.determineCleanMode(gson);
|
||||
} else {
|
||||
CleanReportV2 resp = jsonResponse.getResponsePayloadAs(gson, CleanReportV2.class);
|
||||
mode = resp.determineCleanMode(gson);
|
||||
}
|
||||
if (mode == null) {
|
||||
throw new DataParsingException("Could not get clean mode from response " + jsonResponse.response);
|
||||
}
|
||||
return mode;
|
||||
} else {
|
||||
String payload = ((PortalIotCommandXmlResponse) response).getResponsePayloadXml();
|
||||
return CleaningInfo.parseCleanStateInfo(payload, gson).mode;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.ComponentLifeSpanReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.xml.DeviceInfo;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.Component;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetComponentLifeSpanCommand extends IotDeviceCommand<Integer> {
|
||||
private final Component type;
|
||||
|
||||
public GetComponentLifeSpanCommand(Component type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
return version == ProtocolVersion.XML ? "GetLifeSpan" : "getLifeSpan";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyXmlPayload(Document doc, Element ctl) {
|
||||
ctl.setAttribute("type", type.xmlValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable JsonElement getJsonPayloadArgs(ProtocolVersion version) {
|
||||
JsonArray args = new JsonArray(1);
|
||||
args.add(type.jsonValue);
|
||||
return args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version, Gson gson)
|
||||
throws DataParsingException {
|
||||
if (response instanceof PortalIotCommandJsonResponse) {
|
||||
JsonElement respPayloadRaw = ((PortalIotCommandJsonResponse) response).getResponsePayload(gson);
|
||||
Type type = new TypeToken<List<ComponentLifeSpanReport>>() {
|
||||
}.getType();
|
||||
try {
|
||||
List<ComponentLifeSpanReport> resp = gson.fromJson(respPayloadRaw, type);
|
||||
if (resp == null || resp.isEmpty()) {
|
||||
throw new DataParsingException("Invalid lifespan response " + respPayloadRaw);
|
||||
}
|
||||
return (int) Math.round(100.0 * resp.get(0).left / resp.get(0).total);
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new DataParsingException(e);
|
||||
}
|
||||
} else {
|
||||
String payload = ((PortalIotCommandXmlResponse) response).getResponsePayloadXml();
|
||||
return DeviceInfo.parseComponentLifespanInfo(payload);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.EnabledStateReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.xml.DeviceInfo;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetContinuousCleaningCommand extends IotDeviceCommand<Boolean> {
|
||||
public GetContinuousCleaningCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
return version == ProtocolVersion.XML ? "GetOnOff" : "getBreakPoint";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyXmlPayload(Document doc, Element ctl) {
|
||||
ctl.setAttribute("t", "g");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version, Gson gson)
|
||||
throws DataParsingException {
|
||||
if (response instanceof PortalIotCommandJsonResponse) {
|
||||
EnabledStateReport resp = ((PortalIotCommandJsonResponse) response).getResponsePayloadAs(gson,
|
||||
EnabledStateReport.class);
|
||||
return resp.enabled != 0;
|
||||
} else {
|
||||
String payload = ((PortalIotCommandXmlResponse) response).getResponsePayloadXml();
|
||||
return DeviceInfo.parseEnabledStateInfo(payload);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.DefaultCleanCountReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetDefaultCleanPassesCommand extends IotDeviceCommand<Integer> {
|
||||
public GetDefaultCleanPassesCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
if (version == ProtocolVersion.XML) {
|
||||
throw new IllegalStateException("Command is not supported for XML");
|
||||
}
|
||||
return "getCleanCount";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version, Gson gson)
|
||||
throws DataParsingException {
|
||||
DefaultCleanCountReport resp = ((PortalIotCommandJsonResponse) response).getResponsePayloadAs(gson,
|
||||
DefaultCleanCountReport.class);
|
||||
return resp.count;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.EnabledStateReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetDustbinAutoEmptyCommand extends IotDeviceCommand<Boolean> {
|
||||
public GetDustbinAutoEmptyCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
if (version == ProtocolVersion.XML) {
|
||||
throw new IllegalStateException("Command is not supported for XML");
|
||||
}
|
||||
return "getAutoEmpty";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version, Gson gson)
|
||||
throws DataParsingException {
|
||||
EnabledStateReport resp = ((PortalIotCommandJsonResponse) response).getResponsePayloadAs(gson,
|
||||
EnabledStateReport.class);
|
||||
return resp.enabled != 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.ErrorReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.xml.DeviceInfo;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetErrorCommand extends IotDeviceCommand<Optional<Integer>> {
|
||||
public GetErrorCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
return version == ProtocolVersion.XML ? "GetError" : "getError";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Integer> convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version,
|
||||
Gson gson) throws DataParsingException {
|
||||
if (response instanceof PortalIotCommandJsonResponse) {
|
||||
ErrorReport resp = ((PortalIotCommandJsonResponse) response).getResponsePayloadAs(gson, ErrorReport.class);
|
||||
if (resp.errorCodes.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(resp.errorCodes.get(0));
|
||||
} else {
|
||||
String payload = ((PortalIotCommandXmlResponse) response).getResponsePayloadXml();
|
||||
return DeviceInfo.parseErrorInfo(payload);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.XPathUtils;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetFirmwareVersionCommand extends IotDeviceCommand<String> {
|
||||
public GetFirmwareVersionCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
if (version != ProtocolVersion.XML) {
|
||||
throw new IllegalStateException("Get FW version is only supported for XML");
|
||||
}
|
||||
return "GetVersion";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyXmlPayload(Document doc, Element ctl) {
|
||||
ctl.setAttribute("name", "FW");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version, Gson gson)
|
||||
throws DataParsingException {
|
||||
String payload = ((PortalIotCommandXmlResponse) response).getResponsePayloadXml();
|
||||
return XPathUtils.getFirstXPathMatch(payload, "//ver[@name='FW']").getTextContent();
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.MapSetReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.XPathUtils;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetMapSpotAreasWithMapIdCommand extends IotDeviceCommand<List<String>> {
|
||||
private final String mapId;
|
||||
|
||||
public GetMapSpotAreasWithMapIdCommand(String mapId) {
|
||||
this.mapId = mapId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
return version == ProtocolVersion.XML ? "GetMapSet" : "getMapSet";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyXmlPayload(Document doc, Element ctl) {
|
||||
ctl.setAttribute("tp", "sa");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable JsonElement getJsonPayloadArgs(ProtocolVersion version) {
|
||||
JsonObject args = new JsonObject();
|
||||
args.addProperty("mid", mapId);
|
||||
args.addProperty("type", "ar");
|
||||
return args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version, Gson gson)
|
||||
throws DataParsingException {
|
||||
if (response instanceof PortalIotCommandJsonResponse) {
|
||||
MapSetReport resp = ((PortalIotCommandJsonResponse) response).getResponsePayloadAs(gson,
|
||||
MapSetReport.class);
|
||||
return resp.subsets.stream().map(i -> i.id).collect(Collectors.toList());
|
||||
} else {
|
||||
String payload = ((PortalIotCommandXmlResponse) response).getResponsePayloadXml();
|
||||
NodeList mapIds = XPathUtils.getXPathMatches(payload, "//m/@mid");
|
||||
List<String> result = new ArrayList<>();
|
||||
for (int i = 0; i < mapIds.getLength(); i++) {
|
||||
result.add(mapIds.item(i).getNodeValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.WaterInfoReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.xml.WaterSystemInfo;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.MoppingWaterAmount;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetMoppingWaterAmountCommand extends IotDeviceCommand<MoppingWaterAmount> {
|
||||
public GetMoppingWaterAmountCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
return version == ProtocolVersion.XML ? "GetWaterPermeability" : "getWaterInfo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public MoppingWaterAmount convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version,
|
||||
Gson gson) throws DataParsingException {
|
||||
if (response instanceof PortalIotCommandJsonResponse) {
|
||||
WaterInfoReport resp = ((PortalIotCommandJsonResponse) response).getResponsePayloadAs(gson,
|
||||
WaterInfoReport.class);
|
||||
return MoppingWaterAmount.fromApiValue(resp.waterAmount);
|
||||
} else {
|
||||
String payload = ((PortalIotCommandXmlResponse) response).getResponsePayloadXml();
|
||||
return WaterSystemInfo.parseWaterPermeabilityInfo(payload);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.NetworkInfoReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.NetworkInfo;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.XPathUtils;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetNetworkInfoCommand extends IotDeviceCommand<NetworkInfo> {
|
||||
public GetNetworkInfoCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
return version == ProtocolVersion.XML ? "GetNetInfo" : "getNetInfo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetworkInfo convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version, Gson gson)
|
||||
throws DataParsingException {
|
||||
if (response instanceof PortalIotCommandJsonResponse) {
|
||||
NetworkInfoReport resp = ((PortalIotCommandJsonResponse) response).getResponsePayloadAs(gson,
|
||||
NetworkInfoReport.class);
|
||||
try {
|
||||
return new NetworkInfo(resp.ip, resp.mac, resp.ssid, Integer.valueOf(resp.rssi));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new DataParsingException(e);
|
||||
}
|
||||
} else {
|
||||
String payload = ((PortalIotCommandXmlResponse) response).getResponsePayloadXml();
|
||||
Node ipAttr = XPathUtils.getFirstXPathMatch(payload, "//@wi");
|
||||
Node ssidAttr = XPathUtils.getFirstXPathMatch(payload, "//@s");
|
||||
return new NetworkInfo(ipAttr.getNodeValue(), "", ssidAttr.getNodeValue(), 0);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.SpeedReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.xml.CleaningInfo;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.SuctionPower;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetSuctionPowerCommand extends IotDeviceCommand<SuctionPower> {
|
||||
public GetSuctionPowerCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
return version == ProtocolVersion.XML ? "GetCleanSpeed" : "getSpeed";
|
||||
}
|
||||
|
||||
@Override
|
||||
public SuctionPower convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version, Gson gson)
|
||||
throws DataParsingException {
|
||||
if (response instanceof PortalIotCommandJsonResponse) {
|
||||
SpeedReport resp = ((PortalIotCommandJsonResponse) response).getResponsePayloadAs(gson, SpeedReport.class);
|
||||
return SuctionPower.fromJsonValue(resp.speedLevel);
|
||||
} else {
|
||||
String payload = ((PortalIotCommandXmlResponse) response).getResponsePayloadXml();
|
||||
return CleaningInfo.parseCleanSpeedInfo(payload, gson);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.XPathUtils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetTotalStatsCommand extends IotDeviceCommand<GetTotalStatsCommand.TotalStats> {
|
||||
public class TotalStats {
|
||||
@SerializedName("area")
|
||||
public final int totalArea;
|
||||
@SerializedName("time")
|
||||
public final int totalRuntime;
|
||||
@SerializedName("count")
|
||||
public final int cleanRuns;
|
||||
|
||||
private TotalStats(int area, int runtime, int runs) {
|
||||
this.totalArea = area;
|
||||
this.totalRuntime = runtime;
|
||||
this.cleanRuns = runs;
|
||||
}
|
||||
}
|
||||
|
||||
public GetTotalStatsCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
return version == ProtocolVersion.XML ? "GetCleanSum" : "getTotalStats";
|
||||
}
|
||||
|
||||
@Override
|
||||
public TotalStats convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version, Gson gson)
|
||||
throws DataParsingException {
|
||||
if (response instanceof PortalIotCommandJsonResponse) {
|
||||
return ((PortalIotCommandJsonResponse) response).getResponsePayloadAs(gson, TotalStats.class);
|
||||
} else {
|
||||
String payload = ((PortalIotCommandXmlResponse) response).getResponsePayloadXml();
|
||||
String area = XPathUtils.getFirstXPathMatch(payload, "//@a").getNodeValue();
|
||||
String time = XPathUtils.getFirstXPathMatch(payload, "//@l").getNodeValue();
|
||||
String count = XPathUtils.getFirstXPathMatch(payload, "//@c").getNodeValue();
|
||||
try {
|
||||
return new TotalStats(Integer.valueOf(area), Integer.valueOf(time), Integer.valueOf(count));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new DataParsingException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.EnabledStateReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetTrueDetectCommand extends IotDeviceCommand<Boolean> {
|
||||
public GetTrueDetectCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
if (version == ProtocolVersion.XML) {
|
||||
throw new IllegalStateException("Command is not supported for XML");
|
||||
}
|
||||
return "getTrueDetect";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version, Gson gson)
|
||||
throws DataParsingException {
|
||||
EnabledStateReport resp = ((PortalIotCommandJsonResponse) response).getResponsePayloadAs(gson,
|
||||
EnabledStateReport.class);
|
||||
return resp.enabled != 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetVolumeCommand extends IotDeviceCommand<Integer> {
|
||||
public GetVolumeCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
if (version == ProtocolVersion.XML) {
|
||||
throw new IllegalStateException("Get volume command is not supported for XML");
|
||||
}
|
||||
return "getVolume";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version, Gson gson)
|
||||
throws DataParsingException {
|
||||
if (response instanceof PortalIotCommandJsonResponse) {
|
||||
JsonResponse resp = ((PortalIotCommandJsonResponse) response).getResponsePayloadAs(gson,
|
||||
JsonResponse.class);
|
||||
return resp.volume;
|
||||
} else {
|
||||
// unsupported in XML case?
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static class JsonResponse {
|
||||
@SerializedName("volume")
|
||||
public int volume;
|
||||
|
||||
@SerializedName("total")
|
||||
public int maxVolume;
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.WaterInfoReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.xml.WaterSystemInfo;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetWaterSystemPresentCommand extends IotDeviceCommand<Boolean> {
|
||||
public GetWaterSystemPresentCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
return version == ProtocolVersion.XML ? "GetWaterBoxInfo" : "getWaterInfo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version, Gson gson)
|
||||
throws DataParsingException {
|
||||
if (response instanceof PortalIotCommandJsonResponse) {
|
||||
WaterInfoReport resp = ((PortalIotCommandJsonResponse) response).getResponsePayloadAs(gson,
|
||||
WaterInfoReport.class);
|
||||
return resp.waterPlatePresent != 0;
|
||||
} else {
|
||||
String payload = ((PortalIotCommandXmlResponse) response).getResponsePayloadXml();
|
||||
return WaterSystemInfo.parseWaterBoxInfo(payload);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GoChargingCommand extends AbstractNoResponseCommand {
|
||||
public GoChargingCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
return version == ProtocolVersion.XML ? "Charge" : "charge";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyXmlPayload(Document doc, Element ctl) {
|
||||
Element charge = doc.createElement("charge");
|
||||
charge.setAttribute("type", "go");
|
||||
ctl.appendChild(charge);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable JsonElement getJsonPayloadArgs(ProtocolVersion version) {
|
||||
JsonObject args = new JsonObject();
|
||||
args.addProperty("act", "go");
|
||||
return args;
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import java.io.StringWriter;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.request.portal.PortalIotCommandRequest.JsonPayloadHeader;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class IotDeviceCommand<RESPONSETYPE> {
|
||||
protected IotDeviceCommand() {
|
||||
}
|
||||
|
||||
public abstract String getName(ProtocolVersion version);
|
||||
|
||||
public final String getXmlPayload(@Nullable String id) throws ParserConfigurationException, TransformerException {
|
||||
Document xmlDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
|
||||
Element ctl = xmlDoc.createElement("ctl");
|
||||
ctl.setAttribute("td", getName(ProtocolVersion.XML));
|
||||
if (id != null) {
|
||||
ctl.setAttribute("id", id);
|
||||
}
|
||||
applyXmlPayload(xmlDoc, ctl);
|
||||
xmlDoc.appendChild(ctl);
|
||||
Transformer tf = TransformerFactory.newInstance().newTransformer();
|
||||
tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||
StringWriter writer = new StringWriter();
|
||||
tf.transform(new DOMSource(xmlDoc), new StreamResult(writer));
|
||||
return writer.getBuffer().toString().replaceAll("\n|\r", "");
|
||||
}
|
||||
|
||||
public final JsonElement getJsonPayload(ProtocolVersion version, Gson gson) {
|
||||
JsonObject result = new JsonObject();
|
||||
result.add("header", gson.toJsonTree(new JsonPayloadHeader()));
|
||||
@Nullable
|
||||
JsonElement args = getJsonPayloadArgs(version);
|
||||
if (args != null) {
|
||||
JsonObject body = new JsonObject();
|
||||
body.add("data", args);
|
||||
result.add("body", body);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected @Nullable JsonElement getJsonPayloadArgs(ProtocolVersion version) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void applyXmlPayload(Document doc, Element ctl) {
|
||||
}
|
||||
|
||||
public abstract RESPONSETYPE convertResponse(AbstractPortalIotCommandResponse response, ProtocolVersion version,
|
||||
Gson gson) throws DataParsingException;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanMode;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PauseCleaningCommand extends AbstractCleaningCommand {
|
||||
public PauseCleaningCommand(CleanMode mode) {
|
||||
super("p", "pause", mode);
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PlaySoundCommand extends AbstractNoResponseCommand {
|
||||
public enum SoundType {
|
||||
STARTUP(0),
|
||||
SUSPENDED(3),
|
||||
CHECK_WHEELS(4),
|
||||
HELP_ME_OUT(5),
|
||||
INSTALL_DUST_BIN(6),
|
||||
BEEP(17),
|
||||
BATTERY_LOW(18),
|
||||
POWER_ON_BEFORE_CHARGE(29),
|
||||
I_AM_HERE(30),
|
||||
PLEASE_CLEAN_BRUSH(31),
|
||||
PLEASE_CLEAN_SENSORS(35),
|
||||
BRUSH_IS_TANGLED(48),
|
||||
RELOCATING(55),
|
||||
UPGRADE_DONE(56),
|
||||
RETURNING_TO_CHARGE(63),
|
||||
CLEANING_PAUSED(65),
|
||||
CONNECTED_IN_SETUP(69),
|
||||
RESTORING_MAP(71),
|
||||
BATTERY_LOW_RETURNING_TO_DOCK(73),
|
||||
DIFFICULT_TO_LOCATE(74),
|
||||
RESUMING_CLEANING(75),
|
||||
UPGRADE_FAILED(76),
|
||||
PLACE_ON_CHARGING_DOCK(77),
|
||||
RESUME_CLEANING(79),
|
||||
STARTING_CLEANING(80),
|
||||
READY_FOR_MOPPING(84),
|
||||
REMOVE_MOPPING_PLATE(85),
|
||||
CLEANING_COMPLETE(86),
|
||||
LDS_MALFUNCTION(89),
|
||||
UPGRADING(90);
|
||||
|
||||
final int id;
|
||||
|
||||
private SoundType(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
private final int soundId;
|
||||
|
||||
public PlaySoundCommand(SoundType type) {
|
||||
super();
|
||||
this.soundId = type.id;
|
||||
}
|
||||
|
||||
public PlaySoundCommand(int soundId) {
|
||||
super();
|
||||
this.soundId = soundId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
return version == ProtocolVersion.XML ? "PlaySound" : "playSound";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyXmlPayload(Document doc, Element ctl) {
|
||||
ctl.setAttribute("sid", String.valueOf(soundId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable JsonElement getJsonPayloadArgs(ProtocolVersion version) {
|
||||
JsonObject args = new JsonObject();
|
||||
args.addProperty("sid", soundId);
|
||||
return args;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanMode;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ResumeCleaningCommand extends AbstractCleaningCommand {
|
||||
public ResumeCleaningCommand(CleanMode mode) {
|
||||
super("r", "resume", mode);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetContinuousCleaningCommand extends AbstractNoResponseCommand {
|
||||
private final boolean enabled;
|
||||
|
||||
public SetContinuousCleaningCommand(boolean enabled) {
|
||||
super();
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
return version == ProtocolVersion.XML ? "SetOnOff" : "setBreakPoint";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyXmlPayload(Document doc, Element ctl) {
|
||||
ctl.setAttribute("t", "g");
|
||||
ctl.setAttribute("on", enabled ? "1" : "0");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable JsonElement getJsonPayloadArgs(ProtocolVersion version) {
|
||||
JsonObject args = new JsonObject();
|
||||
args.addProperty("enable", enabled ? 1 : 0);
|
||||
return args;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetDefaultCleanPassesCommand extends AbstractNoResponseCommand {
|
||||
private final int count;
|
||||
|
||||
public SetDefaultCleanPassesCommand(int count) {
|
||||
if (count < 1 || count > 2) {
|
||||
throw new IllegalArgumentException("Number of cleaning passes must be between 1 and 2");
|
||||
}
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
if (version == ProtocolVersion.XML) {
|
||||
throw new IllegalStateException("Set default clean count is not supported for XML");
|
||||
}
|
||||
return "setCleanCount";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable JsonElement getJsonPayloadArgs(ProtocolVersion version) {
|
||||
JsonObject args = new JsonObject();
|
||||
args.addProperty("count", count);
|
||||
return args;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetDustbinAutoEmptyCommand extends AbstractNoResponseCommand {
|
||||
private final boolean on;
|
||||
|
||||
public SetDustbinAutoEmptyCommand(boolean on) {
|
||||
this.on = on;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
if (version == ProtocolVersion.XML) {
|
||||
throw new IllegalStateException("Set dust bin auto empty is not supported for XML");
|
||||
}
|
||||
return "setAutoEmpty";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable JsonElement getJsonPayloadArgs(ProtocolVersion version) {
|
||||
JsonObject args = new JsonObject();
|
||||
args.addProperty("enable", on ? 1 : 0);
|
||||
return args;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.MoppingWaterAmount;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetMoppingWaterAmountCommand extends AbstractNoResponseCommand {
|
||||
private final int level;
|
||||
|
||||
public SetMoppingWaterAmountCommand(MoppingWaterAmount amount) {
|
||||
super();
|
||||
this.level = amount.toApiValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
return version == ProtocolVersion.XML ? "SetWaterPermeability" : "setWaterInfo";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyXmlPayload(Document doc, Element ctl) {
|
||||
ctl.setAttribute("v", String.valueOf(level));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable JsonElement getJsonPayloadArgs(ProtocolVersion version) {
|
||||
JsonObject args = new JsonObject();
|
||||
args.addProperty("amount", level);
|
||||
return args;
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.SuctionPower;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetSuctionPowerCommand extends AbstractNoResponseCommand {
|
||||
private final SuctionPower power;
|
||||
|
||||
public SetSuctionPowerCommand(SuctionPower power) {
|
||||
this.power = power;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
return version == ProtocolVersion.XML ? "SetCleanSpeed" : "setSpeed";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable JsonElement getJsonPayloadArgs(ProtocolVersion version) {
|
||||
JsonObject args = new JsonObject();
|
||||
args.addProperty("speed", power.toJsonValue());
|
||||
return args;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyXmlPayload(Document doc, Element ctl) {
|
||||
ctl.setAttribute("speed", power.toXmlValue());
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetTrueDetectCommand extends AbstractNoResponseCommand {
|
||||
private final boolean on;
|
||||
|
||||
public SetTrueDetectCommand(boolean on) {
|
||||
this.on = on;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
if (version == ProtocolVersion.XML) {
|
||||
throw new IllegalStateException("Set true detect is not supported for XML");
|
||||
}
|
||||
return "setTrueDetect";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable JsonElement getJsonPayloadArgs(ProtocolVersion version) {
|
||||
JsonObject args = new JsonObject();
|
||||
args.addProperty("enable", on ? 1 : 0);
|
||||
return args;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.ProtocolVersion;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetVolumeCommand extends AbstractNoResponseCommand {
|
||||
private final int volume;
|
||||
|
||||
public SetVolumeCommand(int volume) {
|
||||
if (volume < 0 || volume > 10) {
|
||||
throw new IllegalArgumentException("Volume must be between 0 and 10");
|
||||
}
|
||||
this.volume = volume;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProtocolVersion version) {
|
||||
if (version == ProtocolVersion.XML) {
|
||||
throw new IllegalStateException("Set volume is not supported for XML");
|
||||
}
|
||||
return "setVolume";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable JsonElement getJsonPayloadArgs(ProtocolVersion version) {
|
||||
JsonObject args = new JsonObject();
|
||||
args.addProperty("volume", volume);
|
||||
return args;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SpotAreaCleaningCommand extends AbstractAreaCleaningCommand {
|
||||
public SpotAreaCleaningCommand(List<String> roomIds, int cleanPasses) {
|
||||
super("spotArea", String.join(",", roomIds), cleanPasses);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanMode;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class StartAutoCleaningCommand extends AbstractCleaningCommand {
|
||||
public StartAutoCleaningCommand() {
|
||||
super("s", "start", CleanMode.AUTO);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.commands;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanMode;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class StopCleaningCommand extends AbstractCleaningCommand {
|
||||
public StopCleaningCommand() {
|
||||
super("h", "stop", CleanMode.STOP);
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.DeviceCapability;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DeviceDescription {
|
||||
public final String modelName;
|
||||
public final String deviceClass;
|
||||
public final @Nullable String deviceClassLink;
|
||||
public final ProtocolVersion protoVersion;
|
||||
public final boolean usesMqtt;
|
||||
public final Set<DeviceCapability> capabilities;
|
||||
|
||||
public DeviceDescription(String modelName, String deviceClass, @Nullable String deviceClassLink,
|
||||
ProtocolVersion protoVersion, boolean usesMqtt, Set<DeviceCapability> capabilities) {
|
||||
this.modelName = modelName;
|
||||
this.capabilities = capabilities;
|
||||
this.deviceClass = deviceClass;
|
||||
this.deviceClassLink = deviceClassLink;
|
||||
this.protoVersion = protoVersion;
|
||||
this.usesMqtt = usesMqtt;
|
||||
}
|
||||
|
||||
public DeviceDescription resolveLinkWith(DeviceDescription other) {
|
||||
return new DeviceDescription(modelName, deviceClass, null, other.protoVersion, other.usesMqtt,
|
||||
other.capabilities);
|
||||
}
|
||||
|
||||
public void addImplicitCapabilities() {
|
||||
if (protoVersion != ProtocolVersion.XML && capabilities.contains(DeviceCapability.CLEAN_SPEED_CONTROL)) {
|
||||
capabilities.add(DeviceCapability.EXTENDED_CLEAN_SPEED_CONTROL);
|
||||
}
|
||||
if (protoVersion != ProtocolVersion.XML) {
|
||||
capabilities.add(DeviceCapability.EXTENDED_CLEAN_LOG_RECORD);
|
||||
}
|
||||
if (!capabilities.contains(DeviceCapability.SPOT_AREA_CLEANING)) {
|
||||
capabilities.add(DeviceCapability.EDGE_CLEANING);
|
||||
capabilities.add(DeviceCapability.SPOT_CLEANING);
|
||||
}
|
||||
if (protoVersion == ProtocolVersion.JSON_V2) {
|
||||
capabilities.add(DeviceCapability.DEFAULT_CLEAN_COUNT_SETTING);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,361 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.ecovacs.internal.api.EcovacsApi;
|
||||
import org.openhab.binding.ecovacs.internal.api.EcovacsApiConfiguration;
|
||||
import org.openhab.binding.ecovacs.internal.api.EcovacsApiException;
|
||||
import org.openhab.binding.ecovacs.internal.api.EcovacsDevice;
|
||||
import org.openhab.binding.ecovacs.internal.api.commands.IotDeviceCommand;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.request.portal.PortalAuthRequest;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.request.portal.PortalAuthRequestParameter;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.request.portal.PortalCleanLogsRequest;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.request.portal.PortalIotCommandRequest;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.request.portal.PortalIotProductRequest;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.request.portal.PortalLoginRequest;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.main.AccessData;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.main.AuthCode;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.main.ResponseWrapper;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.Device;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.IotProduct;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalCleanLogsResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalDeviceResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotProductResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalLoginResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.MD5Util;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
* @author Johannes Ptaszyk - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class EcovacsApiImpl implements EcovacsApi {
|
||||
private final Logger logger = LoggerFactory.getLogger(EcovacsApiImpl.class);
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
private final EcovacsApiConfiguration configuration;
|
||||
private @Nullable PortalLoginResponse loginData;
|
||||
|
||||
public EcovacsApiImpl(HttpClient httpClient, EcovacsApiConfiguration configuration) {
|
||||
this.httpClient = httpClient;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loginAndGetAccessToken() throws EcovacsApiException, InterruptedException {
|
||||
loginData = null;
|
||||
|
||||
AccessData accessData = login();
|
||||
AuthCode authCode = getAuthCode(accessData);
|
||||
loginData = portalLogin(authCode, accessData);
|
||||
}
|
||||
|
||||
EcovacsApiConfiguration getConfig() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
PortalLoginResponse getLoginData() {
|
||||
return loginData;
|
||||
}
|
||||
|
||||
private AccessData login() throws EcovacsApiException, InterruptedException {
|
||||
HashMap<String, String> loginParameters = new HashMap<>();
|
||||
loginParameters.put("account", configuration.getUsername());
|
||||
loginParameters.put("password", MD5Util.getMD5Hash(configuration.getPassword()));
|
||||
loginParameters.put("requestId", MD5Util.getMD5Hash(String.valueOf(System.currentTimeMillis())));
|
||||
loginParameters.put("authTimeZone", configuration.getTimeZone());
|
||||
loginParameters.put("country", configuration.getCountry());
|
||||
loginParameters.put("lang", configuration.getLanguage());
|
||||
loginParameters.put("deviceId", configuration.getDeviceId());
|
||||
loginParameters.put("appCode", configuration.getAppCode());
|
||||
loginParameters.put("appVersion", configuration.getAppVersion());
|
||||
loginParameters.put("channel", configuration.getChannel());
|
||||
loginParameters.put("deviceType", configuration.getDeviceType());
|
||||
|
||||
Request loginRequest = createAuthRequest(EcovacsApiUrlFactory.getLoginUrl(configuration),
|
||||
configuration.getClientKey(), configuration.getClientSecret(), loginParameters);
|
||||
ContentResponse loginResponse = executeRequest(loginRequest);
|
||||
Type responseType = new TypeToken<ResponseWrapper<AccessData>>() {
|
||||
}.getType();
|
||||
return handleResponseWrapper(gson.fromJson(loginResponse.getContentAsString(), responseType));
|
||||
}
|
||||
|
||||
private AuthCode getAuthCode(AccessData accessData) throws EcovacsApiException, InterruptedException {
|
||||
HashMap<String, String> authCodeParameters = new HashMap<>();
|
||||
authCodeParameters.put("uid", accessData.getUid());
|
||||
authCodeParameters.put("accessToken", accessData.getAccessToken());
|
||||
authCodeParameters.put("bizType", configuration.getBizType());
|
||||
authCodeParameters.put("deviceId", configuration.getDeviceId());
|
||||
authCodeParameters.put("openId", configuration.getAuthOpenId());
|
||||
|
||||
Request authCodeRequest = createAuthRequest(EcovacsApiUrlFactory.getAuthUrl(configuration),
|
||||
configuration.getAuthClientKey(), configuration.getAuthClientSecret(), authCodeParameters);
|
||||
ContentResponse authCodeResponse = executeRequest(authCodeRequest);
|
||||
Type responseType = new TypeToken<ResponseWrapper<AuthCode>>() {
|
||||
}.getType();
|
||||
return handleResponseWrapper(gson.fromJson(authCodeResponse.getContentAsString(), responseType));
|
||||
}
|
||||
|
||||
private PortalLoginResponse portalLogin(AuthCode authCode, AccessData accessData)
|
||||
throws EcovacsApiException, InterruptedException {
|
||||
PortalLoginRequest loginRequestData = new PortalLoginRequest(PortalTodo.LOGIN_BY_TOKEN,
|
||||
configuration.getCountry().toUpperCase(), "", configuration.getOrg(), configuration.getResource(),
|
||||
configuration.getRealm(), authCode.getAuthCode(), accessData.getUid(), configuration.getEdition());
|
||||
String userUrl = EcovacsApiUrlFactory.getPortalUsersUrl(configuration);
|
||||
ContentResponse portalLoginResponse = executeRequest(createJsonRequest(userUrl, loginRequestData));
|
||||
PortalLoginResponse response = handleResponse(portalLoginResponse, PortalLoginResponse.class);
|
||||
if (!response.wasSuccessful()) {
|
||||
throw new EcovacsApiException("Login failed");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EcovacsDevice> getDevices() throws EcovacsApiException, InterruptedException {
|
||||
List<DeviceDescription> descriptions = getSupportedDeviceList();
|
||||
List<IotProduct> products = null;
|
||||
List<EcovacsDevice> devices = new ArrayList<>();
|
||||
for (Device dev : getDeviceList()) {
|
||||
Optional<DeviceDescription> descOpt = descriptions.stream()
|
||||
.filter(d -> dev.getDeviceClass().equals(d.deviceClass)).findFirst();
|
||||
if (!descOpt.isPresent()) {
|
||||
if (products == null) {
|
||||
products = getIotProductMap();
|
||||
}
|
||||
String modelName = products.stream().filter(prod -> dev.getDeviceClass().equals(prod.getClassId()))
|
||||
.findFirst().map(p -> p.getDefinition().name).orElse("UNKNOWN");
|
||||
logger.info("Found unsupported device {} (class {}, company {}), ignoring.", modelName,
|
||||
dev.getDeviceClass(), dev.getCompany());
|
||||
continue;
|
||||
}
|
||||
DeviceDescription desc = descOpt.get();
|
||||
if (desc.usesMqtt) {
|
||||
devices.add(new EcovacsIotMqDevice(dev, desc, this, gson));
|
||||
} else {
|
||||
devices.add(new EcovacsXmppDevice(dev, desc, this, gson));
|
||||
}
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
|
||||
private List<DeviceDescription> getSupportedDeviceList() {
|
||||
ClassLoader cl = Objects.requireNonNull(getClass().getClassLoader());
|
||||
InputStream is = cl.getResourceAsStream("devices/supported_device_list.json");
|
||||
JsonReader reader = new JsonReader(new InputStreamReader(is));
|
||||
Type type = new TypeToken<List<DeviceDescription>>() {
|
||||
}.getType();
|
||||
List<DeviceDescription> descs = gson.fromJson(reader, type);
|
||||
return descs.stream().map(desc -> {
|
||||
final DeviceDescription result;
|
||||
if (desc.deviceClassLink != null) {
|
||||
Optional<DeviceDescription> linkedDescOpt = descs.stream()
|
||||
.filter(d -> d.deviceClass.equals(desc.deviceClassLink)).findFirst();
|
||||
if (!linkedDescOpt.isPresent()) {
|
||||
throw new IllegalStateException(
|
||||
"Desc " + desc.deviceClass + " links unknown desc " + desc.deviceClassLink);
|
||||
}
|
||||
result = desc.resolveLinkWith(linkedDescOpt.get());
|
||||
} else {
|
||||
result = desc;
|
||||
}
|
||||
result.addImplicitCapabilities();
|
||||
return result;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<Device> getDeviceList() throws EcovacsApiException, InterruptedException {
|
||||
PortalAuthRequest data = new PortalAuthRequest(PortalTodo.GET_DEVICE_LIST, createAuthData());
|
||||
String userUrl = EcovacsApiUrlFactory.getPortalUsersUrl(configuration);
|
||||
ContentResponse deviceResponse = executeRequest(createJsonRequest(userUrl, data));
|
||||
logger.trace("Got device list response {}", deviceResponse.getContentAsString());
|
||||
List<Device> devices = handleResponse(deviceResponse, PortalDeviceResponse.class).getDevices();
|
||||
return devices != null ? devices : Collections.emptyList();
|
||||
}
|
||||
|
||||
private List<IotProduct> getIotProductMap() throws EcovacsApiException, InterruptedException {
|
||||
PortalIotProductRequest data = new PortalIotProductRequest(createAuthData());
|
||||
String url = EcovacsApiUrlFactory.getPortalProductIotMapUrl(configuration);
|
||||
ContentResponse productResponse = executeRequest(createJsonRequest(url, data));
|
||||
logger.trace("Got product list response {}", productResponse.getContentAsString());
|
||||
List<IotProduct> products = handleResponse(productResponse, PortalIotProductResponse.class).getProducts();
|
||||
return products != null ? products : Collections.emptyList();
|
||||
}
|
||||
|
||||
public <T> T sendIotCommand(Device device, DeviceDescription desc, IotDeviceCommand<T> command)
|
||||
throws EcovacsApiException, InterruptedException {
|
||||
String commandName = command.getName(desc.protoVersion);
|
||||
final Object payload;
|
||||
try {
|
||||
if (desc.protoVersion == ProtocolVersion.XML) {
|
||||
payload = command.getXmlPayload(null);
|
||||
logger.trace("{}: Sending IOT command {} with payload {}", device.getName(), commandName, payload);
|
||||
} else {
|
||||
payload = command.getJsonPayload(desc.protoVersion, gson);
|
||||
logger.trace("{}: Sending IOT command {} with payload {}", device.getName(), commandName,
|
||||
gson.toJson(payload));
|
||||
}
|
||||
} catch (ParserConfigurationException | TransformerException e) {
|
||||
logger.debug("Could not convert payload for {}", command, e);
|
||||
throw new EcovacsApiException(e);
|
||||
}
|
||||
|
||||
PortalIotCommandRequest data = new PortalIotCommandRequest(createAuthData(), commandName, payload,
|
||||
device.getDid(), device.getResource(), device.getDeviceClass(),
|
||||
desc.protoVersion != ProtocolVersion.XML);
|
||||
String url = EcovacsApiUrlFactory.getPortalIotDeviceManagerUrl(configuration);
|
||||
ContentResponse response = executeRequest(createJsonRequest(url, data));
|
||||
|
||||
final AbstractPortalIotCommandResponse commandResponse;
|
||||
if (desc.protoVersion == ProtocolVersion.XML) {
|
||||
commandResponse = handleResponse(response, PortalIotCommandXmlResponse.class);
|
||||
logger.trace("{}: Got response payload {}", device.getName(),
|
||||
((PortalIotCommandXmlResponse) commandResponse).getResponsePayloadXml());
|
||||
} else {
|
||||
commandResponse = handleResponse(response, PortalIotCommandJsonResponse.class);
|
||||
logger.trace("{}: Got response payload {}", device.getName(),
|
||||
((PortalIotCommandJsonResponse) commandResponse).response);
|
||||
}
|
||||
if (!commandResponse.wasSuccessful()) {
|
||||
final String msg = "Sending IOT command " + commandName + " failed: " + commandResponse.getErrorMessage();
|
||||
throw new EcovacsApiException(msg, commandResponse.failedDueToAuthProblem());
|
||||
}
|
||||
try {
|
||||
return command.convertResponse(commandResponse, desc.protoVersion, gson);
|
||||
} catch (DataParsingException e) {
|
||||
logger.debug("Converting response for command {} failed", command, e);
|
||||
throw new EcovacsApiException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<PortalCleanLogsResponse.LogRecord> fetchCleanLogs(Device device)
|
||||
throws EcovacsApiException, InterruptedException {
|
||||
PortalCleanLogsRequest data = new PortalCleanLogsRequest(createAuthData(), device.getDid(),
|
||||
device.getResource());
|
||||
String url = EcovacsApiUrlFactory.getPortalLogUrl(configuration);
|
||||
ContentResponse response = executeRequest(createJsonRequest(url, data));
|
||||
PortalCleanLogsResponse responseObj = handleResponse(response, PortalCleanLogsResponse.class);
|
||||
if (!responseObj.wasSuccessful()) {
|
||||
throw new EcovacsApiException("Fetching clean logs failed");
|
||||
}
|
||||
logger.trace("{}: Fetching cleaning logs yields {} records", device.getName(), responseObj.records.size());
|
||||
return responseObj.records;
|
||||
}
|
||||
|
||||
private PortalAuthRequestParameter createAuthData() {
|
||||
PortalLoginResponse loginData = this.loginData;
|
||||
if (loginData == null) {
|
||||
throw new IllegalStateException("Not logged in");
|
||||
}
|
||||
return new PortalAuthRequestParameter(configuration.getPortalAUthRequestWith(), loginData.getUserId(),
|
||||
configuration.getRealm(), loginData.getToken(), configuration.getResource());
|
||||
}
|
||||
|
||||
private <T> T handleResponseWrapper(@Nullable ResponseWrapper<T> response) throws EcovacsApiException {
|
||||
if (response == null) {
|
||||
// should not happen in practice
|
||||
throw new EcovacsApiException("No response received");
|
||||
}
|
||||
if (!response.isSuccess()) {
|
||||
throw new EcovacsApiException("API call failed: " + response.getMessage() + ", code " + response.getCode());
|
||||
}
|
||||
return response.getData();
|
||||
}
|
||||
|
||||
private <T> T handleResponse(ContentResponse response, Class<T> clazz) throws EcovacsApiException {
|
||||
@Nullable
|
||||
T respObject = gson.fromJson(response.getContentAsString(), clazz);
|
||||
if (respObject == null) {
|
||||
// should not happen in practice
|
||||
throw new EcovacsApiException("No response received");
|
||||
}
|
||||
return respObject;
|
||||
}
|
||||
|
||||
private Request createAuthRequest(String url, String clientKey, String clientSecret,
|
||||
Map<String, String> requestSpecificParameters) {
|
||||
HashMap<String, String> signedRequestParameters = new HashMap<>(requestSpecificParameters);
|
||||
signedRequestParameters.put("authTimespan", String.valueOf(System.currentTimeMillis()));
|
||||
|
||||
StringBuilder signOnText = new StringBuilder(clientKey);
|
||||
signedRequestParameters.keySet().stream().sorted().forEach(key -> {
|
||||
signOnText.append(key).append("=").append(signedRequestParameters.get(key));
|
||||
});
|
||||
signOnText.append(clientSecret);
|
||||
|
||||
signedRequestParameters.put("authAppkey", clientKey);
|
||||
signedRequestParameters.put("authSign", MD5Util.getMD5Hash(signOnText.toString()));
|
||||
|
||||
Request request = httpClient.newRequest(url).method(HttpMethod.GET);
|
||||
signedRequestParameters.forEach(request::param);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private Request createJsonRequest(String url, Object data) {
|
||||
return httpClient.newRequest(url).method(HttpMethod.POST).header(HttpHeader.CONTENT_TYPE, "application/json")
|
||||
.content(new StringContentProvider(gson.toJson(data)));
|
||||
}
|
||||
|
||||
private ContentResponse executeRequest(Request request) throws EcovacsApiException, InterruptedException {
|
||||
request.timeout(10, TimeUnit.SECONDS);
|
||||
try {
|
||||
ContentResponse response = request.send();
|
||||
if (response.getStatus() != HttpStatus.OK_200) {
|
||||
throw new EcovacsApiException(response);
|
||||
}
|
||||
return response;
|
||||
} catch (TimeoutException | ExecutionException e) {
|
||||
throw new EcovacsApiException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.EcovacsApiConfiguration;
|
||||
|
||||
/**
|
||||
* @author Johannes Ptaszyk - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class EcovacsApiUrlFactory {
|
||||
|
||||
private EcovacsApiUrlFactory() {
|
||||
// Prevent instantiation
|
||||
}
|
||||
|
||||
private static final String MAIN_URL_LOGIN_PATH = "/user/login";
|
||||
|
||||
private static final String PORTAL_USERS_PATH = "/users/user.do";
|
||||
private static final String PORTAL_IOT_PRODUCT_PATH = "/pim/product/getProductIotMap";
|
||||
private static final String PORTAL_IOT_DEVMANAGER_PATH = "/iot/devmanager.do";
|
||||
private static final String PORTAL_LOG_PATH = "/lg/log.do";
|
||||
|
||||
public static String getLoginUrl(EcovacsApiConfiguration config) {
|
||||
return getMainUrl(config) + MAIN_URL_LOGIN_PATH;
|
||||
}
|
||||
|
||||
public static String getAuthUrl(EcovacsApiConfiguration config) {
|
||||
return String.format("https://gl-%1$s-openapi.ecovacs.%2$s/v1/global/auth/getAuthCode", config.getCountry(),
|
||||
getApiUrlTld(config));
|
||||
}
|
||||
|
||||
public static String getPortalUsersUrl(EcovacsApiConfiguration config) {
|
||||
return getPortalUrl(config) + PORTAL_USERS_PATH;
|
||||
}
|
||||
|
||||
public static String getPortalProductIotMapUrl(EcovacsApiConfiguration config) {
|
||||
return getPortalUrl(config) + PORTAL_IOT_PRODUCT_PATH;
|
||||
}
|
||||
|
||||
public static String getPortalIotDeviceManagerUrl(EcovacsApiConfiguration config) {
|
||||
return getPortalUrl(config) + PORTAL_IOT_DEVMANAGER_PATH;
|
||||
}
|
||||
|
||||
public static String getPortalLogUrl(EcovacsApiConfiguration config) {
|
||||
return getPortalUrl(config) + PORTAL_LOG_PATH;
|
||||
}
|
||||
|
||||
private static String getPortalUrl(EcovacsApiConfiguration config) {
|
||||
String continentSuffix = "cn".equalsIgnoreCase(config.getCountry()) ? "" : "-" + config.getContinent();
|
||||
return String.format("https://portal%1$s.ecouser.net/api", continentSuffix);
|
||||
}
|
||||
|
||||
private static String getMainUrl(EcovacsApiConfiguration config) {
|
||||
return String.format("https://gl-%1$s-api.ecovacs.%2$s/v1/private/%1$s/%3$s/%4$s/%5$s/%6$s/%7$s/%8$s",
|
||||
config.getCountry(), getApiUrlTld(config), config.getLanguage(), config.getDeviceId(),
|
||||
config.getAppCode(), config.getAppVersion(), config.getChannel(), config.getDeviceType());
|
||||
}
|
||||
|
||||
private static String getApiUrlTld(EcovacsApiConfiguration config) {
|
||||
return "cn".equalsIgnoreCase(config.getCountry()) ? "cn" : "com";
|
||||
}
|
||||
}
|
@ -0,0 +1,211 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl;
|
||||
|
||||
import java.security.KeyStore;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.net.ssl.ManagerFactoryParameters;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.EcovacsApiConfiguration;
|
||||
import org.openhab.binding.ecovacs.internal.api.EcovacsApiException;
|
||||
import org.openhab.binding.ecovacs.internal.api.EcovacsDevice;
|
||||
import org.openhab.binding.ecovacs.internal.api.commands.GetCleanLogsCommand;
|
||||
import org.openhab.binding.ecovacs.internal.api.commands.GetFirmwareVersionCommand;
|
||||
import org.openhab.binding.ecovacs.internal.api.commands.IotDeviceCommand;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.Device;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalLoginResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanLogRecord;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.DeviceCapability;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
import org.openhab.core.io.net.http.TrustAllTrustManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.hivemq.client.mqtt.MqttClient;
|
||||
import com.hivemq.client.mqtt.MqttClientSslConfig;
|
||||
import com.hivemq.client.mqtt.lifecycle.MqttClientDisconnectedListener;
|
||||
import com.hivemq.client.mqtt.lifecycle.MqttDisconnectSource;
|
||||
import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient;
|
||||
import com.hivemq.client.mqtt.mqtt3.exceptions.Mqtt3ConnAckException;
|
||||
import com.hivemq.client.mqtt.mqtt3.exceptions.Mqtt3DisconnectException;
|
||||
import com.hivemq.client.mqtt.mqtt3.message.auth.Mqtt3SimpleAuth;
|
||||
import com.hivemq.client.mqtt.mqtt3.message.connect.connack.Mqtt3ConnAckReturnCode;
|
||||
import com.hivemq.client.mqtt.mqtt3.message.publish.Mqtt3Publish;
|
||||
|
||||
import io.netty.handler.ssl.util.SimpleTrustManagerFactory;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EcovacsIotMqDevice implements EcovacsDevice {
|
||||
private final Logger logger = LoggerFactory.getLogger(EcovacsIotMqDevice.class);
|
||||
|
||||
private final Device device;
|
||||
private final DeviceDescription desc;
|
||||
private final EcovacsApiImpl api;
|
||||
private final Gson gson;
|
||||
private @Nullable Mqtt3AsyncClient mqttClient;
|
||||
|
||||
EcovacsIotMqDevice(Device device, DeviceDescription desc, EcovacsApiImpl api, Gson gson)
|
||||
throws EcovacsApiException {
|
||||
this.device = device;
|
||||
this.desc = desc;
|
||||
this.api = api;
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerialNumber() {
|
||||
return device.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return desc.modelName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCapability(DeviceCapability cap) {
|
||||
return desc.capabilities.contains(cap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T sendCommand(IotDeviceCommand<T> command) throws EcovacsApiException, InterruptedException {
|
||||
return api.sendIotCommand(device, desc, command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CleanLogRecord> getCleanLogs() throws EcovacsApiException, InterruptedException {
|
||||
Stream<CleanLogRecord> logEntries;
|
||||
if (desc.protoVersion == ProtocolVersion.XML) {
|
||||
logEntries = sendCommand(new GetCleanLogsCommand()).stream();
|
||||
} else {
|
||||
logEntries = api.fetchCleanLogs(device).stream().map(record -> new CleanLogRecord(record.timestamp,
|
||||
record.duration, record.area, Optional.ofNullable(record.imageUrl), record.type));
|
||||
}
|
||||
return logEntries.sorted((lhs, rhs) -> rhs.timestamp.compareTo(lhs.timestamp)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(final EventListener listener, ScheduledExecutorService scheduler)
|
||||
throws EcovacsApiException, InterruptedException {
|
||||
EcovacsApiConfiguration config = api.getConfig();
|
||||
PortalLoginResponse loginData = api.getLoginData();
|
||||
if (loginData == null) {
|
||||
throw new EcovacsApiException("Can not connect when not logged in");
|
||||
}
|
||||
|
||||
// XML message handler does not receive firmware version information with events, so fetch in advance
|
||||
if (desc.protoVersion == ProtocolVersion.XML) {
|
||||
listener.onFirmwareVersionChanged(this, sendCommand(new GetFirmwareVersionCommand()));
|
||||
}
|
||||
|
||||
String userName = String.format("%s@%s", loginData.getUserId(), config.getRealm().split("\\.")[0]);
|
||||
String host = String.format("mq-%s.%s", config.getContinent(), config.getRealm());
|
||||
|
||||
Mqtt3SimpleAuth auth = Mqtt3SimpleAuth.builder().username(userName).password(loginData.getToken().getBytes())
|
||||
.build();
|
||||
|
||||
MqttClientSslConfig sslConfig = MqttClientSslConfig.builder().trustManagerFactory(createTrustManagerFactory())
|
||||
.build();
|
||||
|
||||
final MqttClientDisconnectedListener disconnectListener = ctx -> {
|
||||
boolean expectedShutdown = ctx.getSource() == MqttDisconnectSource.USER
|
||||
&& ctx.getCause() instanceof Mqtt3DisconnectException;
|
||||
// As the client already was disconnected, there's no need to do it again in disconnect() later
|
||||
this.mqttClient = null;
|
||||
if (!expectedShutdown) {
|
||||
logger.debug("{}: MQTT disconnected (source {}): {}", getSerialNumber(), ctx.getSource(),
|
||||
ctx.getCause().getMessage());
|
||||
listener.onEventStreamFailure(EcovacsIotMqDevice.this, ctx.getCause());
|
||||
}
|
||||
};
|
||||
|
||||
final Mqtt3AsyncClient client = MqttClient.builder().useMqttVersion3()
|
||||
.identifier(userName + "/" + loginData.getResource()).simpleAuth(auth).serverHost(host).serverPort(8883)
|
||||
.sslConfig(sslConfig).addDisconnectedListener(disconnectListener).buildAsync();
|
||||
|
||||
try {
|
||||
this.mqttClient = client;
|
||||
client.connect().get();
|
||||
|
||||
final ReportParser parser = desc.protoVersion == ProtocolVersion.XML
|
||||
? new XmlReportParser(this, listener, gson, logger)
|
||||
: new JsonReportParser(this, listener, desc.protoVersion, gson, logger);
|
||||
final Consumer<@Nullable Mqtt3Publish> eventCallback = publish -> {
|
||||
if (publish == null) {
|
||||
return;
|
||||
}
|
||||
String receivedTopic = publish.getTopic().toString();
|
||||
String payload = new String(publish.getPayloadAsBytes());
|
||||
try {
|
||||
String eventName = receivedTopic.split("/")[2].toLowerCase();
|
||||
logger.trace("{}: Got MQTT message on topic {}: {}", getSerialNumber(), receivedTopic, payload);
|
||||
parser.handleMessage(eventName, payload);
|
||||
} catch (DataParsingException e) {
|
||||
listener.onEventStreamFailure(this, e);
|
||||
}
|
||||
};
|
||||
|
||||
String topic = String.format("iot/atr/+/%s/%s/%s/+", device.getDid(), device.getDeviceClass(),
|
||||
device.getResource());
|
||||
|
||||
client.subscribeWith().topicFilter(topic).callback(eventCallback).send().get();
|
||||
logger.debug("Established MQTT connection to device {}", getSerialNumber());
|
||||
} catch (ExecutionException e) {
|
||||
Throwable cause = e.getCause();
|
||||
boolean isAuthFailure = cause instanceof Mqtt3ConnAckException && ((Mqtt3ConnAckException) cause)
|
||||
.getMqttMessage().getReturnCode() == Mqtt3ConnAckReturnCode.NOT_AUTHORIZED;
|
||||
throw new EcovacsApiException(e, isAuthFailure);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(ScheduledExecutorService scheduler) {
|
||||
Mqtt3AsyncClient client = this.mqttClient;
|
||||
if (client != null) {
|
||||
client.disconnect();
|
||||
}
|
||||
this.mqttClient = null;
|
||||
}
|
||||
|
||||
private TrustManagerFactory createTrustManagerFactory() {
|
||||
return new SimpleTrustManagerFactory() {
|
||||
@Override
|
||||
protected void engineInit(@Nullable KeyStore keyStore) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineInit(@Nullable ManagerFactoryParameters managerFactoryParameters) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TrustManager[] engineGetTrustManagers() {
|
||||
return new TrustManager[] { TrustAllTrustManager.getInstance() };
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,467 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.jivesoftware.smack.ConnectionListener;
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler;
|
||||
import org.jivesoftware.smack.packet.ErrorIQ;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.IQ.Type;
|
||||
import org.jivesoftware.smack.packet.StanzaError;
|
||||
import org.jivesoftware.smack.provider.IQProvider;
|
||||
import org.jivesoftware.smack.provider.ProviderManager;
|
||||
import org.jivesoftware.smack.roster.Roster;
|
||||
import org.jivesoftware.smack.sasl.SASLErrorException;
|
||||
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
|
||||
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
|
||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||
import org.jivesoftware.smackx.ping.PingManager;
|
||||
import org.jxmpp.jid.Jid;
|
||||
import org.jxmpp.jid.impl.JidCreate;
|
||||
import org.openhab.binding.ecovacs.internal.api.EcovacsApiConfiguration;
|
||||
import org.openhab.binding.ecovacs.internal.api.EcovacsApiException;
|
||||
import org.openhab.binding.ecovacs.internal.api.EcovacsDevice;
|
||||
import org.openhab.binding.ecovacs.internal.api.commands.GetCleanLogsCommand;
|
||||
import org.openhab.binding.ecovacs.internal.api.commands.GetFirmwareVersionCommand;
|
||||
import org.openhab.binding.ecovacs.internal.api.commands.IotDeviceCommand;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.Device;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalLoginResponse;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanLogRecord;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.DeviceCapability;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.SchedulerTask;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.XPathUtils;
|
||||
import org.openhab.core.io.net.http.TrustAllTrustManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EcovacsXmppDevice implements EcovacsDevice {
|
||||
private final Logger logger = LoggerFactory.getLogger(EcovacsXmppDevice.class);
|
||||
|
||||
private final Device device;
|
||||
private final DeviceDescription desc;
|
||||
private final EcovacsApiImpl api;
|
||||
private final Gson gson;
|
||||
private @Nullable IncomingMessageHandler messageHandler;
|
||||
private @Nullable PingHandler pingHandler;
|
||||
private @Nullable XMPPTCPConnection connection;
|
||||
private @Nullable Jid ownAddress;
|
||||
private @Nullable Jid targetAddress;
|
||||
|
||||
EcovacsXmppDevice(Device device, DeviceDescription desc, EcovacsApiImpl api, Gson gson) {
|
||||
this.device = device;
|
||||
this.desc = desc;
|
||||
this.api = api;
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerialNumber() {
|
||||
return device.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return desc.modelName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCapability(DeviceCapability cap) {
|
||||
return desc.capabilities.contains(cap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T sendCommand(IotDeviceCommand<T> command) throws EcovacsApiException, InterruptedException {
|
||||
IncomingMessageHandler handler = this.messageHandler;
|
||||
XMPPConnection conn = this.connection;
|
||||
Jid from = this.ownAddress;
|
||||
Jid to = this.targetAddress;
|
||||
if (handler == null || conn == null || from == null || to == null) {
|
||||
throw new EcovacsApiException("Not connected to device");
|
||||
}
|
||||
|
||||
try {
|
||||
// Devices sometimes send no answer to commands for unknown reasons. Ecovacs'
|
||||
// app employs a similar retry mechanism, so this seems to be 'normal'.
|
||||
for (int retry = 0; retry < 3; retry++) {
|
||||
DeviceCommandIQ request = new DeviceCommandIQ(command, from, to);
|
||||
CommandResponseHolder responseHolder = new CommandResponseHolder();
|
||||
|
||||
try {
|
||||
handler.registerPendingCommand(request.id, responseHolder);
|
||||
|
||||
logger.trace("{}: sending command {}, retry {}", getSerialNumber(),
|
||||
command.getName(ProtocolVersion.XML), retry);
|
||||
synchronized (responseHolder) {
|
||||
conn.sendIqRequestAsync(request);
|
||||
responseHolder.wait(1500);
|
||||
}
|
||||
} finally {
|
||||
handler.unregisterPendingCommand(request.id);
|
||||
}
|
||||
|
||||
String response = responseHolder.response;
|
||||
if (response != null) {
|
||||
logger.trace("{}: Received command response XML {}", getSerialNumber(), response);
|
||||
|
||||
PortalIotCommandXmlResponse responseObj = new PortalIotCommandXmlResponse("", response, 0, "");
|
||||
return command.convertResponse(responseObj, ProtocolVersion.XML, gson);
|
||||
}
|
||||
}
|
||||
} catch (DataParsingException | ParserConfigurationException | TransformerException e) {
|
||||
throw new EcovacsApiException(e);
|
||||
}
|
||||
|
||||
throw new EcovacsApiException("No response for command " + command.getName(ProtocolVersion.XML));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CleanLogRecord> getCleanLogs() throws EcovacsApiException, InterruptedException {
|
||||
return sendCommand(new GetCleanLogsCommand());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(final EventListener listener, final ScheduledExecutorService scheduler)
|
||||
throws EcovacsApiException {
|
||||
EcovacsApiConfiguration config = api.getConfig();
|
||||
PortalLoginResponse loginData = api.getLoginData();
|
||||
if (loginData == null) {
|
||||
throw new EcovacsApiException("Can not connect when not logged in");
|
||||
}
|
||||
|
||||
logger.trace("{}: Connecting to XMPP", getSerialNumber());
|
||||
|
||||
String password = String.format("0/%s/%s", loginData.getResource(), loginData.getToken());
|
||||
String host = String.format("msg-%s.%s", config.getContinent(), config.getRealm());
|
||||
|
||||
try {
|
||||
Jid ownAddress = JidCreate.from(loginData.getUserId(), config.getRealm(), loginData.getResource());
|
||||
Jid targetAddress = JidCreate.from(device.getDid(), device.getDeviceClass() + ".ecorobot.net", "atom");
|
||||
|
||||
XMPPTCPConnectionConfiguration connConfig = XMPPTCPConnectionConfiguration.builder().setHost(host)
|
||||
.setPort(5223).setUsernameAndPassword(loginData.getUserId(), password)
|
||||
.setResource(loginData.getResource()).setXmppDomain(config.getRealm())
|
||||
.setCustomX509TrustManager(TrustAllTrustManager.getInstance()).setSendPresence(false).build();
|
||||
|
||||
XMPPTCPConnection conn = new XMPPTCPConnection(connConfig);
|
||||
conn.addConnectionListener(new ConnectionListener() {
|
||||
@Override
|
||||
public void connected(@Nullable XMPPConnection connection) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticated(@Nullable XMPPConnection connection, boolean resumed) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionClosed() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionClosedOnError(@Nullable Exception e) {
|
||||
logger.trace("{}: XMPP connection failed", getSerialNumber(), e);
|
||||
if (e != null) {
|
||||
listener.onEventStreamFailure(EcovacsXmppDevice.this, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
PingHandler pingHandler = new PingHandler(conn, scheduler, listener, targetAddress);
|
||||
messageHandler = new IncomingMessageHandler(listener);
|
||||
|
||||
Roster roster = Roster.getInstanceFor(conn);
|
||||
roster.setRosterLoadedAtLogin(false);
|
||||
|
||||
conn.registerIQRequestHandler(messageHandler);
|
||||
conn.connect();
|
||||
|
||||
this.connection = conn;
|
||||
this.ownAddress = ownAddress;
|
||||
this.targetAddress = targetAddress;
|
||||
this.pingHandler = pingHandler;
|
||||
|
||||
conn.login();
|
||||
conn.setReplyTimeout(1000);
|
||||
|
||||
logger.trace("{}: XMPP connection established", getSerialNumber());
|
||||
|
||||
listener.onFirmwareVersionChanged(this, sendCommand(new GetFirmwareVersionCommand()));
|
||||
pingHandler.start();
|
||||
} catch (SASLErrorException e) {
|
||||
throw new EcovacsApiException(e, true);
|
||||
} catch (XMPPException | SmackException | InterruptedException | IOException e) {
|
||||
throw new EcovacsApiException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(ScheduledExecutorService scheduler) {
|
||||
PingHandler pingHandler = this.pingHandler;
|
||||
if (pingHandler != null) {
|
||||
pingHandler.stop();
|
||||
}
|
||||
this.pingHandler = null;
|
||||
|
||||
IncomingMessageHandler handler = this.messageHandler;
|
||||
if (handler != null) {
|
||||
handler.dispose();
|
||||
}
|
||||
this.messageHandler = null;
|
||||
|
||||
final XMPPTCPConnection conn = this.connection;
|
||||
if (conn != null) {
|
||||
scheduler.execute(() -> conn.disconnect());
|
||||
}
|
||||
this.connection = null;
|
||||
}
|
||||
|
||||
private class PingHandler {
|
||||
private static final long INTERVAL_SECONDS = 30;
|
||||
// After a failure, use shorter intervals since subsequent further failure is likely
|
||||
private static final long POST_FAILURE_INTERVAL_SECONDS = 5;
|
||||
private static final int MAX_FAILURES = 4;
|
||||
|
||||
private final XMPPTCPConnection connection;
|
||||
private final PingManager pingManager;
|
||||
private final EventListener listener;
|
||||
private final Jid toAddress;
|
||||
private final SchedulerTask pingTask;
|
||||
private boolean started = false;
|
||||
private int failedPings = 0;
|
||||
|
||||
PingHandler(XMPPTCPConnection connection, ScheduledExecutorService scheduler, EventListener listener, Jid to) {
|
||||
this.connection = connection;
|
||||
this.pingManager = PingManager.getInstanceFor(connection);
|
||||
this.pingTask = new SchedulerTask(scheduler, logger, "Ping", this::sendPing);
|
||||
this.listener = listener;
|
||||
this.toAddress = to;
|
||||
this.pingTask.setNamePrefix(getSerialNumber());
|
||||
}
|
||||
|
||||
public void start() {
|
||||
started = true;
|
||||
scheduleNextPing(0);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
started = false;
|
||||
pingTask.cancel();
|
||||
}
|
||||
|
||||
private void sendPing() {
|
||||
long timeSinceLastStanza = (System.currentTimeMillis() - connection.getLastStanzaReceived()) / 1000;
|
||||
if (timeSinceLastStanza < currentPingInterval()) {
|
||||
scheduleNextPing(timeSinceLastStanza);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (pingManager.ping(this.toAddress)) {
|
||||
logger.trace("{}: Pinged device", getSerialNumber());
|
||||
failedPings = 0;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// only happens when we're stopped
|
||||
} catch (SmackException e) {
|
||||
++failedPings;
|
||||
logger.debug("{}: Ping failed (#{}): {})", getSerialNumber(), failedPings, e.getMessage());
|
||||
if (failedPings >= MAX_FAILURES) {
|
||||
listener.onEventStreamFailure(EcovacsXmppDevice.this, e);
|
||||
}
|
||||
}
|
||||
scheduleNextPing(0);
|
||||
}
|
||||
|
||||
private synchronized void scheduleNextPing(long delta) {
|
||||
pingTask.cancel();
|
||||
if (started) {
|
||||
pingTask.schedule(currentPingInterval() - delta);
|
||||
}
|
||||
}
|
||||
|
||||
private long currentPingInterval() {
|
||||
return failedPings > 0 ? POST_FAILURE_INTERVAL_SECONDS : INTERVAL_SECONDS;
|
||||
}
|
||||
}
|
||||
|
||||
private class IncomingMessageHandler extends AbstractIqRequestHandler {
|
||||
private final EventListener listener;
|
||||
private final ReportParser parser;
|
||||
private final ConcurrentHashMap<String, CommandResponseHolder> pendingCommands = new ConcurrentHashMap<>();
|
||||
private boolean disposed;
|
||||
|
||||
IncomingMessageHandler(EventListener listener) {
|
||||
super("query", "com:ctl", Type.set, Mode.async);
|
||||
this.listener = listener;
|
||||
this.parser = new XmlReportParser(EcovacsXmppDevice.this, listener, gson, logger);
|
||||
}
|
||||
|
||||
void registerPendingCommand(String id, CommandResponseHolder responseHolder) {
|
||||
pendingCommands.put(id, responseHolder);
|
||||
}
|
||||
|
||||
void unregisterPendingCommand(String id) {
|
||||
pendingCommands.remove(id);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable IQ handleIQRequest(@Nullable IQ iqRequest) {
|
||||
if (disposed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (iqRequest instanceof DeviceCommandIQ) {
|
||||
DeviceCommandIQ iq = (DeviceCommandIQ) iqRequest;
|
||||
|
||||
try {
|
||||
if (!iq.id.isEmpty()) {
|
||||
CommandResponseHolder responseHolder = pendingCommands.remove(iq.id);
|
||||
if (responseHolder != null) {
|
||||
synchronized (responseHolder) {
|
||||
responseHolder.response = iq.payload;
|
||||
responseHolder.notifyAll();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Optional<String> eventNameOpt = XPathUtils.getFirstXPathMatchOpt(iq.payload, "//ctl/@td")
|
||||
.map(n -> n.getNodeValue());
|
||||
if (eventNameOpt.isPresent()) {
|
||||
logger.trace("{}: Received event message XML {}", getSerialNumber(), iq.payload);
|
||||
parser.handleMessage(eventNameOpt.get(), iq.payload);
|
||||
} else {
|
||||
logger.debug("{}: Got unexpected XML payload {}", getSerialNumber(), iq.payload);
|
||||
}
|
||||
}
|
||||
} catch (DataParsingException e) {
|
||||
listener.onEventStreamFailure(EcovacsXmppDevice.this, e);
|
||||
}
|
||||
} else if (iqRequest instanceof ErrorIQ) {
|
||||
StanzaError error = ((ErrorIQ) iqRequest).getError();
|
||||
logger.trace("{}: Got error response {}", getSerialNumber(), error);
|
||||
listener.onEventStreamFailure(EcovacsXmppDevice.this,
|
||||
new XMPPException.XMPPErrorException(iqRequest, error));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CommandResponseHolder {
|
||||
@Nullable
|
||||
String response;
|
||||
}
|
||||
|
||||
private static class DeviceCommandIQ extends IQ {
|
||||
static final String TAG_NAME = "query";
|
||||
static final String NAMESPACE = "com:ctl";
|
||||
|
||||
private final String payload;
|
||||
final String id;
|
||||
|
||||
// request
|
||||
public DeviceCommandIQ(IotDeviceCommand<?> cmd, Jid from, Jid to)
|
||||
throws ParserConfigurationException, TransformerException {
|
||||
super(TAG_NAME, NAMESPACE);
|
||||
setType(Type.set);
|
||||
setTo(to);
|
||||
setFrom(from);
|
||||
|
||||
this.id = createRequestId();
|
||||
this.payload = cmd.getXmlPayload(id);
|
||||
}
|
||||
|
||||
// response
|
||||
public DeviceCommandIQ(@Nullable String id, String payload) {
|
||||
super(TAG_NAME, NAMESPACE);
|
||||
this.id = id != null ? id : "";
|
||||
this.payload = payload.replaceAll("\n|\r", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable IQChildElementXmlStringBuilder getIQChildElementBuilder(
|
||||
@Nullable IQChildElementXmlStringBuilder xml) {
|
||||
if (xml != null) {
|
||||
xml.rightAngleBracket();
|
||||
xml.append(payload);
|
||||
}
|
||||
return xml;
|
||||
}
|
||||
|
||||
private String createRequestId() {
|
||||
// Ecovacs' app uses numbers for request IDs, so better constrain ourselves to that as well
|
||||
int random8DigitNumber = 10000000 + new Random().nextInt(90000000);
|
||||
return Integer.toString(random8DigitNumber);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CommandIQProvider extends IQProvider<@Nullable DeviceCommandIQ> {
|
||||
@Override
|
||||
public @Nullable DeviceCommandIQ parse(@Nullable XmlPullParser parser, int initialDepth) throws Exception {
|
||||
@Nullable
|
||||
DeviceCommandIQ packet = null;
|
||||
|
||||
if (parser == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
outerloop: while (true) {
|
||||
switch (parser.next()) {
|
||||
case XmlPullParser.START_TAG:
|
||||
if (parser.getDepth() == initialDepth + 1) {
|
||||
String id = parser.getAttributeValue("", "id");
|
||||
String payload = PacketParserUtils.parseElement(parser).toString();
|
||||
packet = new DeviceCommandIQ(id, payload);
|
||||
}
|
||||
break;
|
||||
case XmlPullParser.END_TAG:
|
||||
if (parser.getDepth() == initialDepth) {
|
||||
break outerloop;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
ProviderManager.addIQProvider(DeviceCommandIQ.TAG_NAME, DeviceCommandIQ.NAMESPACE, new CommandIQProvider());
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.EcovacsDevice;
|
||||
import org.openhab.binding.ecovacs.internal.api.EcovacsDevice.EventListener;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.BatteryReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.ChargeReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.CleanReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.CleanReportV2;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.ErrorReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.StatsReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.json.WaterInfoReport;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse.JsonResponsePayloadWrapper;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanMode;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class JsonReportParser implements ReportParser {
|
||||
private final EcovacsDevice device;
|
||||
private final EventListener listener;
|
||||
private final Gson gson;
|
||||
private final Logger logger;
|
||||
private String lastFirmwareVersion = "";
|
||||
|
||||
JsonReportParser(EcovacsDevice device, EventListener listener, ProtocolVersion version, Gson gson, Logger logger) {
|
||||
this.device = device;
|
||||
this.listener = listener;
|
||||
this.gson = gson;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(String eventName, String payload) throws DataParsingException {
|
||||
JsonResponsePayloadWrapper response;
|
||||
try {
|
||||
response = gson.fromJson(payload, JsonResponsePayloadWrapper.class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
// The onFwBuryPoint-bd_sysinfo sends a JSON array instead of the expected JsonResponsePayloadBody object.
|
||||
// Since we don't do anything with it anyway, just ignore it
|
||||
logger.debug("{}: Got invalid JSON message payload, ignoring: {}", device.getSerialNumber(), payload, e);
|
||||
response = null;
|
||||
}
|
||||
if (response == null) {
|
||||
return;
|
||||
}
|
||||
if (!lastFirmwareVersion.equals(response.header.firmwareVersion)) {
|
||||
lastFirmwareVersion = response.header.firmwareVersion;
|
||||
listener.onFirmwareVersionChanged(device, lastFirmwareVersion);
|
||||
}
|
||||
if (eventName.startsWith("on")) {
|
||||
eventName = eventName.substring(2);
|
||||
} else if (eventName.startsWith("report")) {
|
||||
eventName = eventName.substring(6);
|
||||
}
|
||||
switch (eventName) {
|
||||
case "battery": {
|
||||
BatteryReport report = payloadAs(response, BatteryReport.class);
|
||||
listener.onBatteryLevelUpdated(device, report.percent);
|
||||
break;
|
||||
}
|
||||
case "chargestate": {
|
||||
ChargeReport report = payloadAs(response, ChargeReport.class);
|
||||
listener.onChargingStateUpdated(device, report.isCharging != 0);
|
||||
break;
|
||||
}
|
||||
case "cleaninfo": {
|
||||
CleanReport report = payloadAs(response, CleanReport.class);
|
||||
CleanMode mode = report.determineCleanMode(gson);
|
||||
if (mode == null) {
|
||||
throw new DataParsingException("Could not get clean mode from response " + payload);
|
||||
}
|
||||
String area = report.cleanState != null ? report.cleanState.areaDefinition : null;
|
||||
handleCleanModeChange(mode, area);
|
||||
break;
|
||||
}
|
||||
case "cleaninfo_v2": {
|
||||
CleanReportV2 report = payloadAs(response, CleanReportV2.class);
|
||||
CleanMode mode = report.determineCleanMode(gson);
|
||||
if (mode == null) {
|
||||
throw new DataParsingException("Could not get clean mode from response " + payload);
|
||||
}
|
||||
String area = report.cleanState != null && report.cleanState.content != null
|
||||
? report.cleanState.content.areaDefinition
|
||||
: null;
|
||||
handleCleanModeChange(mode, area);
|
||||
break;
|
||||
}
|
||||
case "error": {
|
||||
ErrorReport report = payloadAs(response, ErrorReport.class);
|
||||
for (Integer code : report.errorCodes) {
|
||||
listener.onErrorReported(device, code);
|
||||
}
|
||||
}
|
||||
case "stats": {
|
||||
StatsReport report = payloadAs(response, StatsReport.class);
|
||||
listener.onCleaningStatsUpdated(device, report.area, report.timeInSeconds);
|
||||
break;
|
||||
}
|
||||
case "waterinfo": {
|
||||
WaterInfoReport report = payloadAs(response, WaterInfoReport.class);
|
||||
listener.onWaterSystemPresentUpdated(device, report.waterPlatePresent != 0);
|
||||
break;
|
||||
}
|
||||
// more possible events (unused for now):
|
||||
// - "evt" -> EventReport
|
||||
// - "lifespan" -> ComponentLifeSpanReport
|
||||
// - "speed" -> SpeedReport
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCleanModeChange(CleanMode mode, @Nullable String areaDefinition) {
|
||||
if (mode == CleanMode.CUSTOM_AREA) {
|
||||
logger.debug("{}: Custom area cleaning stated with area definition {}", device.getSerialNumber(),
|
||||
areaDefinition);
|
||||
}
|
||||
listener.onCleaningModeUpdated(device, mode, Optional.ofNullable(areaDefinition));
|
||||
}
|
||||
|
||||
private <T> T payloadAs(JsonResponsePayloadWrapper response, Class<T> clazz) throws DataParsingException {
|
||||
@Nullable
|
||||
T payload = gson.fromJson(response.body.payload, clazz);
|
||||
if (payload == null) {
|
||||
throw new DataParsingException("Null payload in response " + response);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Johannes Ptaszyk - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum PortalTodo {
|
||||
@SerializedName("GetDeviceList")
|
||||
GET_DEVICE_LIST,
|
||||
@SerializedName("loginByItToken")
|
||||
LOGIN_BY_TOKEN;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum ProtocolVersion {
|
||||
@SerializedName("xml")
|
||||
XML,
|
||||
@SerializedName("json")
|
||||
JSON,
|
||||
@SerializedName("json_v2")
|
||||
JSON_V2
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ReportParser {
|
||||
void handleMessage(String eventName, String payload) throws DataParsingException;
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.EcovacsDevice;
|
||||
import org.openhab.binding.ecovacs.internal.api.EcovacsDevice.EventListener;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.xml.CleaningInfo;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.xml.DeviceInfo;
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.dto.response.deviceapi.xml.WaterSystemInfo;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.ChargeMode;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanMode;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.XPathUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class XmlReportParser implements ReportParser {
|
||||
private final EcovacsDevice device;
|
||||
private final EventListener listener;
|
||||
private final Gson gson;
|
||||
private final Logger logger;
|
||||
|
||||
XmlReportParser(EcovacsDevice device, EventListener listener, Gson gson, Logger logger) {
|
||||
this.device = device;
|
||||
this.listener = listener;
|
||||
this.gson = gson;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(String eventName, String payload) throws DataParsingException {
|
||||
switch (eventName.toLowerCase()) {
|
||||
case "batteryinfo":
|
||||
listener.onBatteryLevelUpdated(device, DeviceInfo.parseBatteryInfo(payload));
|
||||
break;
|
||||
case "chargestate": {
|
||||
ChargeMode mode = DeviceInfo.parseChargeInfo(payload, gson);
|
||||
if (mode == ChargeMode.RETURNING) {
|
||||
listener.onCleaningModeUpdated(device, CleanMode.RETURNING, Optional.empty());
|
||||
}
|
||||
listener.onChargingStateUpdated(device, mode == ChargeMode.CHARGING);
|
||||
break;
|
||||
}
|
||||
case "cleanreport": {
|
||||
CleaningInfo.CleanStateInfo info = CleaningInfo.parseCleanStateInfo(payload, gson);
|
||||
if (info.mode == CleanMode.CUSTOM_AREA) {
|
||||
logger.debug("{}: Custom area cleaning stated with area definition {}", device.getSerialNumber(),
|
||||
info.areaDefinition);
|
||||
}
|
||||
listener.onCleaningModeUpdated(device, info.mode, info.areaDefinition);
|
||||
// Full report:
|
||||
// <ctl td='CleanReport'><clean type='auto' speed='standard' st='s' rsn='a'/></ctl>
|
||||
break;
|
||||
}
|
||||
case "cleanrptbgdata": {
|
||||
Node fromChargerNode = XPathUtils.getFirstXPathMatch(payload, "//@IsFrmCharger");
|
||||
if ("1".equals(fromChargerNode.getNodeValue())) {
|
||||
// Device just started cleaning, but likely won't send us a ChargeState report,
|
||||
// so update charging state from here
|
||||
listener.onChargingStateUpdated(device, false);
|
||||
}
|
||||
// Full report:
|
||||
// <ctl td='CleanRptBgdata' ts='1643044172' Battery='102' CleanID='1333688018' iCleanID='0497265223'
|
||||
// MapID='1430814334' rsn='a' IsFrmCharger='1' CleanType='auto' Speed='standard' OnOffRag='0'
|
||||
// WorkMode='s' Spray='2' WorkArea='002'/>
|
||||
break;
|
||||
}
|
||||
case "cleanst": {
|
||||
String area = XPathUtils.getFirstXPathMatch(payload, "//@a").getNodeValue();
|
||||
String duration = XPathUtils.getFirstXPathMatch(payload, "//@l").getNodeValue();
|
||||
listener.onCleaningStatsUpdated(device, Integer.valueOf(area), Integer.valueOf(duration));
|
||||
break;
|
||||
}
|
||||
case "error":
|
||||
DeviceInfo.parseErrorInfo(payload).ifPresent(errorCode -> {
|
||||
listener.onErrorReported(device, errorCode);
|
||||
});
|
||||
break;
|
||||
case "waterboxinfo":
|
||||
listener.onWaterSystemPresentUpdated(device, WaterSystemInfo.parseWaterBoxInfo(payload));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.request.portal;
|
||||
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.PortalTodo;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Johannes Ptaszyk - Initial contribution
|
||||
*/
|
||||
public class PortalAuthRequest {
|
||||
|
||||
@SerializedName("todo")
|
||||
final PortalTodo todo;
|
||||
|
||||
@SerializedName("userid")
|
||||
final String userId;
|
||||
|
||||
@SerializedName("auth")
|
||||
final PortalAuthRequestParameter auth;
|
||||
|
||||
public PortalAuthRequest(PortalTodo todo, PortalAuthRequestParameter auth) {
|
||||
this.todo = todo;
|
||||
this.userId = auth.userId;
|
||||
this.auth = auth;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.request.portal;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Johannes Ptaszyk - Initial contribution
|
||||
*/
|
||||
public class PortalAuthRequestParameter {
|
||||
|
||||
@SerializedName("with")
|
||||
final String with;
|
||||
|
||||
@SerializedName("userid")
|
||||
final String userId;
|
||||
|
||||
@SerializedName("realm")
|
||||
final String realm;
|
||||
|
||||
@SerializedName("token")
|
||||
final String token;
|
||||
|
||||
@SerializedName("resource")
|
||||
final String resource;
|
||||
|
||||
public PortalAuthRequestParameter(String with, String userid, String realm, String token, String resource) {
|
||||
this.with = with;
|
||||
this.userId = userid;
|
||||
this.realm = realm;
|
||||
this.token = token;
|
||||
this.resource = resource;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.request.portal;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class PortalCleanLogsRequest {
|
||||
|
||||
@SerializedName("auth")
|
||||
final PortalAuthRequestParameter auth;
|
||||
|
||||
@SerializedName("td")
|
||||
final String commandName = "GetCleanLogs";
|
||||
|
||||
@SerializedName("did")
|
||||
final String targetDeviceId;
|
||||
|
||||
@SerializedName("resource")
|
||||
final String targetResource;
|
||||
|
||||
public PortalCleanLogsRequest(PortalAuthRequestParameter auth, String targetDeviceId, String targetResource) {
|
||||
this.auth = auth;
|
||||
this.targetDeviceId = targetDeviceId;
|
||||
this.targetResource = targetResource;
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.request.portal;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class PortalIotCommandRequest {
|
||||
|
||||
@SerializedName("auth")
|
||||
final PortalAuthRequestParameter auth;
|
||||
|
||||
@SerializedName("cmdName")
|
||||
final String commandName;
|
||||
|
||||
@SerializedName("payload")
|
||||
final Object payload;
|
||||
|
||||
@SerializedName("payloadType")
|
||||
final String payloadType;
|
||||
|
||||
@SerializedName("td")
|
||||
final String td = "q";
|
||||
|
||||
@SerializedName("toId")
|
||||
final String targetDeviceId;
|
||||
|
||||
@SerializedName("toRes")
|
||||
final String targetResource;
|
||||
|
||||
@SerializedName("toType")
|
||||
final String targetClass;
|
||||
|
||||
public PortalIotCommandRequest(PortalAuthRequestParameter auth, String commandName, Object payload,
|
||||
String targetDeviceId, String targetResource, String targetClass, boolean json) {
|
||||
this.auth = auth;
|
||||
this.commandName = commandName;
|
||||
this.payload = payload;
|
||||
this.targetDeviceId = targetDeviceId;
|
||||
this.targetResource = targetResource;
|
||||
this.targetClass = targetClass;
|
||||
this.payloadType = json ? "j" : "x";
|
||||
}
|
||||
|
||||
public static class JsonPayloadHeader {
|
||||
@SerializedName("pri")
|
||||
public final int pri = 1;
|
||||
@SerializedName("ts")
|
||||
public final long timestamp;
|
||||
@SerializedName("tzm")
|
||||
public final int tzm = 480;
|
||||
@SerializedName("ver")
|
||||
public final String version = "0.0.50";
|
||||
|
||||
public JsonPayloadHeader() {
|
||||
timestamp = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.request.portal;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class PortalIotProductRequest {
|
||||
|
||||
@SerializedName("todo")
|
||||
final String todo = "";
|
||||
|
||||
@SerializedName("channel")
|
||||
final String channel = "";
|
||||
|
||||
@SerializedName("auth")
|
||||
final PortalAuthRequestParameter auth;
|
||||
|
||||
public PortalIotProductRequest(PortalAuthRequestParameter auth) {
|
||||
this.auth = auth;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.request.portal;
|
||||
|
||||
import org.openhab.binding.ecovacs.internal.api.impl.PortalTodo;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Johannes Ptaszyk - Initial contribution
|
||||
*/
|
||||
public class PortalLoginRequest {
|
||||
|
||||
@SerializedName("todo")
|
||||
final PortalTodo todo;
|
||||
|
||||
@SerializedName("country")
|
||||
final String country;
|
||||
|
||||
@SerializedName("last")
|
||||
final String last;
|
||||
|
||||
@SerializedName("org")
|
||||
final String org;
|
||||
|
||||
@SerializedName("resource")
|
||||
final String resource;
|
||||
|
||||
@SerializedName("realm")
|
||||
final String realm;
|
||||
|
||||
@SerializedName("token")
|
||||
final String token;
|
||||
|
||||
@SerializedName("userid")
|
||||
final String userId;
|
||||
|
||||
@SerializedName("edition")
|
||||
final String edition;
|
||||
|
||||
public PortalLoginRequest(PortalTodo todo, String country, String last, String org, String resource, String realm,
|
||||
String token, String userId, String edition) {
|
||||
this.todo = todo;
|
||||
this.country = country;
|
||||
this.last = last;
|
||||
this.org = org;
|
||||
this.resource = resource;
|
||||
this.realm = realm;
|
||||
this.token = token;
|
||||
this.userId = userId;
|
||||
this.edition = edition;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.json;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class BatteryReport {
|
||||
@SerializedName("value")
|
||||
public int percent;
|
||||
@SerializedName("isLow")
|
||||
public int batteryIsLow;
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.json;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class CachedMapInfoReport {
|
||||
@SerializedName("enable")
|
||||
public int enable;
|
||||
|
||||
@SerializedName("info")
|
||||
public List<CachedMapInfo> mapInfos;
|
||||
|
||||
public static class CachedMapInfo {
|
||||
@SerializedName("mid")
|
||||
public String mapId;
|
||||
public int index;
|
||||
public int status;
|
||||
@SerializedName("using")
|
||||
public int used;
|
||||
public int built;
|
||||
public String name;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.json;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class ChargeReport {
|
||||
@SerializedName("isCharging")
|
||||
public int isCharging;
|
||||
@SerializedName("mode")
|
||||
public String mode; // slot, ...?
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.json;
|
||||
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanMode;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class CleanReport {
|
||||
@SerializedName("trigger")
|
||||
public String trigger; // app, workComplete, ...?
|
||||
@SerializedName("state")
|
||||
public String state;
|
||||
@SerializedName("cleanState")
|
||||
public CleanStateReport cleanState;
|
||||
|
||||
public static class CleanStateReport {
|
||||
@SerializedName("router")
|
||||
public String router; // plan, ...?
|
||||
@SerializedName("type")
|
||||
public String type;
|
||||
@SerializedName("motionState")
|
||||
public String motionState;
|
||||
@SerializedName("content")
|
||||
public String areaDefinition;
|
||||
}
|
||||
|
||||
public CleanMode determineCleanMode(Gson gson) {
|
||||
final String modeValue;
|
||||
if (cleanState != null) {
|
||||
if ("working".equals(cleanState.motionState)) {
|
||||
modeValue = cleanState.type;
|
||||
} else {
|
||||
modeValue = cleanState.motionState;
|
||||
}
|
||||
} else {
|
||||
modeValue = state;
|
||||
}
|
||||
return gson.fromJson(modeValue, CleanMode.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.json;
|
||||
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanMode;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class CleanReportV2 {
|
||||
@SerializedName("trigger")
|
||||
public String trigger; // app, workComplete, ...?
|
||||
@SerializedName("state")
|
||||
public String state;
|
||||
@SerializedName("cleanState")
|
||||
public CleanStateReportV2 cleanState;
|
||||
|
||||
public static class CleanStateReportV2 {
|
||||
@SerializedName("router")
|
||||
public String router; // plan, ...?
|
||||
@SerializedName("motionState")
|
||||
public String motionState;
|
||||
@SerializedName("content")
|
||||
public CleanStateReportV2Content content;
|
||||
}
|
||||
|
||||
public static class CleanStateReportV2Content {
|
||||
@SerializedName("type")
|
||||
public String type;
|
||||
@SerializedName("value")
|
||||
public String areaDefinition;
|
||||
}
|
||||
|
||||
public CleanMode determineCleanMode(Gson gson) {
|
||||
final String modeValue;
|
||||
if ("clean".equals(state) && cleanState != null) {
|
||||
if ("working".equals(cleanState.motionState)) {
|
||||
modeValue = cleanState.content.type;
|
||||
} else {
|
||||
modeValue = cleanState.motionState;
|
||||
}
|
||||
} else {
|
||||
modeValue = state;
|
||||
}
|
||||
return gson.fromJson(modeValue, CleanMode.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.json;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class ComponentLifeSpanReport {
|
||||
@SerializedName("type")
|
||||
public String type;
|
||||
|
||||
@SerializedName("left")
|
||||
public int left;
|
||||
|
||||
@SerializedName("total")
|
||||
public int total;
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.json;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class DefaultCleanCountReport {
|
||||
public int count;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.json;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class EnabledStateReport {
|
||||
@SerializedName("enable")
|
||||
public int enabled;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.json;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class ErrorReport {
|
||||
@SerializedName("code")
|
||||
public List<Integer> errorCodes;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.json;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class EventReport {
|
||||
@SerializedName("code")
|
||||
public int eventCode;
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.json;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class MapSetReport {
|
||||
public String type;
|
||||
public int count;
|
||||
@SerializedName("mid")
|
||||
public String mapId;
|
||||
@SerializedName("msid")
|
||||
public String mapSetId;
|
||||
public List<MapSubSetInfo> subsets;
|
||||
|
||||
public static class MapSubSetInfo {
|
||||
@SerializedName("mssid")
|
||||
public String id;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.json;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class NetworkInfoReport {
|
||||
public String ip;
|
||||
public String mac;
|
||||
public String ssid;
|
||||
public String rssi;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.json;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class SleepReport {
|
||||
@SerializedName("enable")
|
||||
public int sleeping;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.json;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class SpeedReport {
|
||||
@SerializedName("speed")
|
||||
public int speedLevel;
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.json;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class StatsReport {
|
||||
@SerializedName("area")
|
||||
public int area;
|
||||
@SerializedName("time")
|
||||
public int timeInSeconds;
|
||||
@SerializedName("cid")
|
||||
public String cid;
|
||||
@SerializedName("start")
|
||||
public long startTimestamp;
|
||||
@SerializedName("type")
|
||||
public String type; // auto, ... ?
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.json;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class WaterInfoReport {
|
||||
@SerializedName("enable")
|
||||
public int waterPlatePresent;
|
||||
@SerializedName("amount")
|
||||
public int waterAmount;
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.xml;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanMode;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.SuctionPower;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.XPathUtils;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CleaningInfo {
|
||||
public static class CleanStateInfo {
|
||||
public final CleanMode mode;
|
||||
public final Optional<String> areaDefinition;
|
||||
|
||||
CleanStateInfo(CleanMode mode) {
|
||||
this(mode, Optional.empty());
|
||||
}
|
||||
|
||||
CleanStateInfo(CleanMode mode, Optional<String> areaDefinition) {
|
||||
this.mode = mode;
|
||||
this.areaDefinition = areaDefinition;
|
||||
}
|
||||
}
|
||||
|
||||
public static CleanStateInfo parseCleanStateInfo(String xml, Gson gson) throws DataParsingException {
|
||||
String stateString = XPathUtils.getFirstXPathMatchOpt(xml, "//clean/@st").map(n -> n.getNodeValue()).orElse("");
|
||||
|
||||
if ("h".equals(stateString)) {
|
||||
return new CleanStateInfo(CleanMode.STOP);
|
||||
} else if ("p".equals(stateString)) {
|
||||
return new CleanStateInfo(CleanMode.PAUSE);
|
||||
} else {
|
||||
String modeString = XPathUtils.getFirstXPathMatch(xml, "//clean/@type").getNodeValue();
|
||||
CleanMode parsedMode = gson.fromJson(modeString, CleanMode.class);
|
||||
if (parsedMode == CleanMode.SPOT_AREA) {
|
||||
Optional<Node> pointOpt = XPathUtils.getFirstXPathMatchOpt(xml, "//clean/@p");
|
||||
if (pointOpt.isPresent()) {
|
||||
return new CleanStateInfo(CleanMode.CUSTOM_AREA, pointOpt.map(n -> n.getNodeValue()));
|
||||
}
|
||||
Optional<Node> midOpt = XPathUtils.getFirstXPathMatchOpt(xml, "//clean/@mid");
|
||||
return new CleanStateInfo(CleanMode.SPOT_AREA, midOpt.map(n -> n.getNodeValue()));
|
||||
}
|
||||
if (parsedMode != null) {
|
||||
return new CleanStateInfo(parsedMode);
|
||||
}
|
||||
}
|
||||
throw new DataParsingException("Unexpected clean state report: " + xml);
|
||||
}
|
||||
|
||||
public static SuctionPower parseCleanSpeedInfo(String xml, Gson gson) throws DataParsingException {
|
||||
String levelString = XPathUtils.getFirstXPathMatch(xml, "//@speed").getNodeValue();
|
||||
SuctionPower level = gson.fromJson(levelString, SuctionPower.class);
|
||||
if (level == null) {
|
||||
throw new DataParsingException("Could not parse power level " + levelString);
|
||||
}
|
||||
return level;
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.xml;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.ChargeMode;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.XPathUtils;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DeviceInfo {
|
||||
private static final Set<String> ERROR_ATTR_NAMES = Set.of("code", "error", "errno", "errs");
|
||||
|
||||
public static int parseBatteryInfo(String xml) throws DataParsingException {
|
||||
Node batteryAttr = XPathUtils.getFirstXPathMatch(xml, "//battery/@power");
|
||||
return Integer.valueOf(batteryAttr.getNodeValue());
|
||||
}
|
||||
|
||||
public static ChargeMode parseChargeInfo(String xml, Gson gson) throws DataParsingException {
|
||||
String modeString = XPathUtils.getFirstXPathMatch(xml, "//charge/@type").getNodeValue();
|
||||
ChargeMode mode = gson.fromJson(modeString, ChargeMode.class);
|
||||
if (mode == null) {
|
||||
throw new IllegalArgumentException("Could not parse charge mode " + modeString);
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
|
||||
public static Optional<Integer> parseErrorInfo(String xml) throws DataParsingException {
|
||||
for (String attr : ERROR_ATTR_NAMES) {
|
||||
Optional<Node> node = XPathUtils.getFirstXPathMatchOpt(xml, "//@" + attr);
|
||||
if (node.isPresent()) {
|
||||
try {
|
||||
String value = node.get().getNodeValue();
|
||||
return value.isEmpty() ? Optional.empty() : Optional.of(Integer.valueOf(value));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new DataParsingException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static int parseComponentLifespanInfo(String xml) throws DataParsingException {
|
||||
Optional<Integer> value = nodeValueToInt(xml, "value");
|
||||
Optional<Integer> total = nodeValueToInt(xml, "total");
|
||||
Optional<Integer> left = nodeValueToInt(xml, "left");
|
||||
if (value.isPresent() && total.isPresent()) {
|
||||
return (int) Math.round(100.0 * value.get() / total.get());
|
||||
} else if (value.isPresent()) {
|
||||
return (int) Math.round(0.01 * value.get());
|
||||
} else if (left.isPresent() && total.isPresent()) {
|
||||
return (int) Math.round(100.0 * left.get() / total.get());
|
||||
} else if (left.isPresent()) {
|
||||
return (int) Math.round((double) left.get() / 60.0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static boolean parseEnabledStateInfo(String xml) throws DataParsingException {
|
||||
String value = XPathUtils.getFirstXPathMatch(xml, "//@on").getNodeValue();
|
||||
try {
|
||||
return Integer.valueOf(value) != 0;
|
||||
} catch (NumberFormatException e) {
|
||||
throw new DataParsingException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<Integer> nodeValueToInt(String xml, String attrName) throws DataParsingException {
|
||||
try {
|
||||
return XPathUtils.getFirstXPathMatchOpt(xml, "//ctl/@" + attrName)
|
||||
.map(n -> Integer.valueOf(n.getNodeValue()));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new DataParsingException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.deviceapi.xml;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ecovacs.internal.api.model.MoppingWaterAmount;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.XPathUtils;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WaterSystemInfo {
|
||||
/**
|
||||
* @return Whether water system is present
|
||||
*/
|
||||
public static boolean parseWaterBoxInfo(String xml) throws DataParsingException {
|
||||
Node node = XPathUtils.getFirstXPathMatch(xml, "//@on");
|
||||
return Integer.valueOf(node.getNodeValue()) != 0;
|
||||
}
|
||||
|
||||
public static MoppingWaterAmount parseWaterPermeabilityInfo(String xml) throws DataParsingException {
|
||||
Node node = XPathUtils.getFirstXPathMatch(xml, "//@v");
|
||||
try {
|
||||
return MoppingWaterAmount.fromApiValue(Integer.valueOf(node.getNodeValue()));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new DataParsingException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.main;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Johannes Ptaszyk - Initial contribution
|
||||
*/
|
||||
public class AccessData {
|
||||
|
||||
@SerializedName("uid")
|
||||
private final String uid;
|
||||
|
||||
@SerializedName("accessToken")
|
||||
private final String accessToken;
|
||||
|
||||
@SerializedName("userName")
|
||||
private final String userName;
|
||||
|
||||
@SerializedName("email")
|
||||
private final String email;
|
||||
|
||||
@SerializedName("mobile")
|
||||
private final String mobile;
|
||||
|
||||
@SerializedName("isNew")
|
||||
private final boolean isNew;
|
||||
|
||||
@SerializedName("loginName")
|
||||
private final String loginName;
|
||||
|
||||
@SerializedName("ucUid")
|
||||
private final String ucUid;
|
||||
|
||||
public AccessData(String uid, String accessToken, String userName, String email, String mobile, boolean isNew,
|
||||
String loginName, String ucUid) {
|
||||
this.uid = uid;
|
||||
this.accessToken = accessToken;
|
||||
this.userName = userName;
|
||||
this.email = email;
|
||||
this.mobile = mobile;
|
||||
this.isNew = isNew;
|
||||
this.loginName = loginName;
|
||||
this.ucUid = ucUid;
|
||||
}
|
||||
|
||||
public String getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public String getMobile() {
|
||||
return mobile;
|
||||
}
|
||||
|
||||
public boolean isNew() {
|
||||
return isNew;
|
||||
}
|
||||
|
||||
public String getLoginName() {
|
||||
return loginName;
|
||||
}
|
||||
|
||||
public String getUcUid() {
|
||||
return ucUid;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.main;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Johannes Ptaszyk - Initial contribution
|
||||
*/
|
||||
public class AuthCode {
|
||||
|
||||
@SerializedName("ecovacsUid")
|
||||
private final String ecovacsUid;
|
||||
|
||||
@SerializedName("authCode")
|
||||
private final String authCode;
|
||||
|
||||
public AuthCode(String ecovacsUid, String authCode) {
|
||||
this.ecovacsUid = ecovacsUid;
|
||||
this.authCode = authCode;
|
||||
}
|
||||
|
||||
public String getEcovacsUid() {
|
||||
return ecovacsUid;
|
||||
}
|
||||
|
||||
public String getAuthCode() {
|
||||
return authCode;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.main;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Johannes Ptaszyk - Initial contribution
|
||||
*/
|
||||
public class ResponseWrapper<T> {
|
||||
@SerializedName("code")
|
||||
private final String code;
|
||||
|
||||
@SerializedName("time")
|
||||
private final String time;
|
||||
|
||||
@SerializedName("msg")
|
||||
private final String message;
|
||||
|
||||
@SerializedName("data")
|
||||
private final T data;
|
||||
|
||||
@SerializedName("success")
|
||||
private final boolean success;
|
||||
|
||||
public ResponseWrapper(String code, String time, String message, T data, boolean success) {
|
||||
this.code = code;
|
||||
this.time = time;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.portal;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class AbstractPortalIotCommandResponse {
|
||||
@SerializedName("ret")
|
||||
private final String result;
|
||||
|
||||
@SerializedName("errno")
|
||||
private final int errorCode;
|
||||
@SerializedName("error")
|
||||
private final String errorMessage;
|
||||
|
||||
// unused field: 'id' (string)
|
||||
|
||||
public AbstractPortalIotCommandResponse(String result, int errorCode, String errorMessage) {
|
||||
this.result = result;
|
||||
this.errorCode = errorCode;
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public boolean wasSuccessful() {
|
||||
return "ok".equals(result);
|
||||
}
|
||||
|
||||
public boolean failedDueToAuthProblem() {
|
||||
return "fail".equals(result) && errorMessage != null && errorMessage.toLowerCase().contains("auth error");
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
if (wasSuccessful()) {
|
||||
return null;
|
||||
}
|
||||
return "result=" + result + ", errno=" + errorCode + ", error=" + errorMessage;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.portal;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Johannes Ptaszyk - Initial contribution
|
||||
*/
|
||||
public abstract class AbstractPortalResponse {
|
||||
@SerializedName("result")
|
||||
private final String result;
|
||||
|
||||
// unused field: 'todo' (string)
|
||||
|
||||
protected AbstractPortalResponse(String result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public boolean wasSuccessful() {
|
||||
return "ok".equals(result);
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.portal;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Johannes Ptaszyk - Initial contribution
|
||||
*/
|
||||
public class Device {
|
||||
@SerializedName("did")
|
||||
private final String did;
|
||||
|
||||
@SerializedName("name")
|
||||
private final String name;
|
||||
|
||||
@SerializedName("class")
|
||||
private final String deviceClass;
|
||||
|
||||
@SerializedName("resource")
|
||||
private final String resource;
|
||||
|
||||
@SerializedName("nick")
|
||||
private final String nick;
|
||||
|
||||
@SerializedName("company")
|
||||
private final String company;
|
||||
|
||||
@SerializedName("bindTs")
|
||||
private final long bindTs;
|
||||
|
||||
@SerializedName("service")
|
||||
private final Service service;
|
||||
|
||||
public Device(String did, String name, String deviceClass, String resource, String nick, String company,
|
||||
long bindTs, Service service) {
|
||||
this.did = did;
|
||||
this.name = name;
|
||||
this.deviceClass = deviceClass;
|
||||
this.resource = resource;
|
||||
this.nick = nick;
|
||||
this.company = company;
|
||||
this.bindTs = bindTs;
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public String getDid() {
|
||||
return did;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDeviceClass() {
|
||||
return deviceClass;
|
||||
}
|
||||
|
||||
public String getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
public String getNick() {
|
||||
return nick;
|
||||
}
|
||||
|
||||
public String getCompany() {
|
||||
return company;
|
||||
}
|
||||
|
||||
public long getBindTs() {
|
||||
return bindTs;
|
||||
}
|
||||
|
||||
public Service getService() {
|
||||
return service;
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.portal;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class IotProduct {
|
||||
@SerializedName("classid")
|
||||
private final String classId;
|
||||
|
||||
@SerializedName("product")
|
||||
private final ProductDefinition productDef;
|
||||
|
||||
public IotProduct(String classId, ProductDefinition productDef) {
|
||||
this.classId = classId;
|
||||
this.productDef = productDef;
|
||||
}
|
||||
|
||||
public String getClassId() {
|
||||
return classId;
|
||||
}
|
||||
|
||||
public ProductDefinition getDefinition() {
|
||||
return productDef;
|
||||
}
|
||||
|
||||
public static class ProductDefinition {
|
||||
@SerializedName("_id")
|
||||
public final String id;
|
||||
|
||||
@SerializedName("materialNo")
|
||||
public final String materialNumber;
|
||||
|
||||
@SerializedName("name")
|
||||
public final String name;
|
||||
|
||||
@SerializedName("icon")
|
||||
public final String icon;
|
||||
|
||||
@SerializedName("iconUrl")
|
||||
public final String iconUrl;
|
||||
|
||||
@SerializedName("model")
|
||||
public final String model;
|
||||
|
||||
@SerializedName("UILogicId")
|
||||
public final String uiLogicId;
|
||||
|
||||
@SerializedName("ota")
|
||||
public final boolean otaCapable;
|
||||
|
||||
@SerializedName("supportType")
|
||||
public final SupportFlags supportFlags;
|
||||
|
||||
public ProductDefinition(String id, String materialNumber, String name, String icon, String iconUrl,
|
||||
String model, String uiLogicId, boolean otaCapable, SupportFlags supportFlags) {
|
||||
this.id = id;
|
||||
this.materialNumber = materialNumber;
|
||||
this.name = name;
|
||||
this.icon = icon;
|
||||
this.iconUrl = iconUrl;
|
||||
this.model = model;
|
||||
this.uiLogicId = uiLogicId;
|
||||
this.otaCapable = otaCapable;
|
||||
this.supportFlags = supportFlags;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SupportFlags {
|
||||
@SerializedName("share")
|
||||
public final boolean canShare;
|
||||
|
||||
@SerializedName("tmjl")
|
||||
public final boolean tmjl; // ???
|
||||
|
||||
@SerializedName("assistant")
|
||||
public final boolean canUseAssistant;
|
||||
|
||||
@SerializedName("alexa")
|
||||
public final boolean canUseAlexa;
|
||||
|
||||
public SupportFlags(boolean share, boolean tmjl, boolean assistant, boolean alexa) {
|
||||
this.canShare = share;
|
||||
this.tmjl = tmjl;
|
||||
this.canUseAssistant = assistant;
|
||||
this.canUseAlexa = alexa;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.portal;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.binding.ecovacs.internal.api.model.CleanMode;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Johannes Ptaszyk - Initial contribution
|
||||
*/
|
||||
public class PortalCleanLogsResponse {
|
||||
public static class LogRecord {
|
||||
@SerializedName("ts")
|
||||
public final long timestamp;
|
||||
|
||||
@SerializedName("last")
|
||||
public final long duration;
|
||||
|
||||
public final int area;
|
||||
|
||||
public final String id;
|
||||
|
||||
public final String imageUrl;
|
||||
|
||||
public final CleanMode type;
|
||||
|
||||
// more possible fields: aiavoid (int), aitypes (list of something), stopReason (int)
|
||||
|
||||
LogRecord(long timestamp, long duration, int area, String id, String imageUrl, CleanMode type) {
|
||||
this.timestamp = timestamp;
|
||||
this.duration = duration;
|
||||
this.area = area;
|
||||
this.id = id;
|
||||
this.imageUrl = imageUrl;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
@SerializedName("logs")
|
||||
public final List<LogRecord> records;
|
||||
|
||||
@SerializedName("ret")
|
||||
final String result;
|
||||
|
||||
PortalCleanLogsResponse(String result, List<LogRecord> records) {
|
||||
this.result = result;
|
||||
this.records = records;
|
||||
}
|
||||
|
||||
public boolean wasSuccessful() {
|
||||
return "ok".equals(result);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.portal;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Johannes Ptaszyk - Initial contribution
|
||||
*/
|
||||
public class PortalDeviceResponse extends AbstractPortalResponse {
|
||||
|
||||
@SerializedName("devices")
|
||||
private final List<Device> devices;
|
||||
|
||||
public PortalDeviceResponse(String result, List<Device> devices) {
|
||||
super(result);
|
||||
this.devices = devices;
|
||||
}
|
||||
|
||||
public List<Device> getDevices() {
|
||||
return devices;
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.portal;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ecovacs.internal.api.util.DataParsingException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class PortalIotCommandJsonResponse extends AbstractPortalIotCommandResponse {
|
||||
@SerializedName("resp")
|
||||
public final JsonElement response;
|
||||
|
||||
public PortalIotCommandJsonResponse(String result, JsonElement response, int errorCode, String errorMessage) {
|
||||
super(result, errorCode, errorMessage);
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
public <T> T getResponsePayloadAs(Gson gson, Class<T> clazz) throws DataParsingException {
|
||||
try {
|
||||
JsonElement payloadRaw = getResponsePayload(gson);
|
||||
@Nullable
|
||||
T payload = gson.fromJson(payloadRaw, clazz);
|
||||
if (payload == null) {
|
||||
throw new DataParsingException("Empty JSON payload");
|
||||
}
|
||||
return payload;
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new DataParsingException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public JsonElement getResponsePayload(Gson gson) throws DataParsingException {
|
||||
try {
|
||||
@Nullable
|
||||
JsonResponsePayloadWrapper wrapper = gson.fromJson(response, JsonResponsePayloadWrapper.class);
|
||||
if (wrapper == null) {
|
||||
throw new DataParsingException("Empty JSON payload");
|
||||
}
|
||||
return wrapper.body.payload;
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new DataParsingException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class JsonPayloadHeader {
|
||||
@SerializedName("pri")
|
||||
public int pri;
|
||||
@SerializedName("ts")
|
||||
public long timestamp;
|
||||
@SerializedName("tzm")
|
||||
public int tzm;
|
||||
@SerializedName("fwVer")
|
||||
public String firmwareVersion;
|
||||
@SerializedName("hwVer")
|
||||
public String hardwareVersion;
|
||||
}
|
||||
|
||||
public static class JsonResponsePayloadWrapper {
|
||||
@SerializedName("header")
|
||||
public JsonPayloadHeader header;
|
||||
@SerializedName("body")
|
||||
public JsonResponsePayloadBody body;
|
||||
}
|
||||
|
||||
public static class JsonResponsePayloadBody {
|
||||
@SerializedName("code")
|
||||
public int code;
|
||||
@SerializedName("msg")
|
||||
public String message;
|
||||
@SerializedName("data")
|
||||
public JsonElement payload;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.portal;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class PortalIotCommandXmlResponse extends AbstractPortalIotCommandResponse {
|
||||
@SerializedName("resp")
|
||||
private final String responseXml;
|
||||
|
||||
public PortalIotCommandXmlResponse(String result, String responseXml, int errorCode, String errorMessage) {
|
||||
super(result, errorCode, errorMessage);
|
||||
this.responseXml = responseXml;
|
||||
}
|
||||
|
||||
public String getResponsePayloadXml() {
|
||||
return responseXml != null ? responseXml.replaceAll("\n|\r", "") : null;
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.ecovacs.internal.api.impl.dto.response.portal;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Danny Baumann - Initial contribution
|
||||
*/
|
||||
public class PortalIotProductResponse {
|
||||
@SerializedName("data")
|
||||
private final List<IotProduct> products;
|
||||
|
||||
// unused field: 'code' (integer)
|
||||
|
||||
public PortalIotProductResponse(List<IotProduct> products) {
|
||||
this.products = products;
|
||||
}
|
||||
|
||||
public List<IotProduct> getProducts() {
|
||||
return products;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user