mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[veSync] New VeSync binding addition (#12219)
* [veSync] New VeSync binding addition Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] New VeSync binding addition - LUH-D301S support added. Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] New VeSync binding addition - AH channel corrections Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] New VeSync binding addition - AH D301S night light removal Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] New VeSync binding addition - AH docs mistLevel correction Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] New VeSync binding addition - Debug output correction Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] New VeSync binding addition - Dual200S adjustments Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR adjustments Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR adjustments - removal of TODOs Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR adjustments - markdown table formatting Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR adjustments - Air Purifier doc's and bug fix Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR adjustments - Air Humidifiers doc's Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR adjustments - HttpClient handling management to move api instance to the correct location Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR adjustments - ThingTypeUID additions Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR adjustments - Documentation correction - airPurifierPollinterval Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR adjustments - Documentation correction - configuration parameters Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR adjustments - Module documentation correction - description update. Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR adjustments - thing-types - bridge configuration updates Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR adjustments - thing-types - description updates to cut length where possible. Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR adjustments - discovery - representation prop adjustments Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR adjustments - documentation - configuration block adjustments. Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR Adjustments - Humidity set point channel renames Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR Adjustments - Initalize direct call cleanup Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR Adjustments - getDeviceUID override removal Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR Adjustments - unit adjustments Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PMD Error correction - file naming correction Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR Adjustment - HttpClient handling simplified. Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR Adjustment - Removal of dead code. Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR Adjustment - Readme OpenHab to openHAB Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR Adjustment - Comment cleanup Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR Adjustment - Handler Error removal to comm issue Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR Adjustment - Quick spotless fix Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR Adjustment - Removal of debug log - as status has message in now. Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PPM to PM correction for Air Quality units. Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] ReadMe Units PM Update Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] Constant name correction for air quality units adjustment. Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] Humidifier Percentage Units addition. Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] Air Filter Life Remaining units addition Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PM25 update based on other bindings, to correct the units. Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] ReadMe PM25 updates Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR Updates: Thing Type Ids to lower case Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR: Removal of unrequired createThing override Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR: Removal of unused channel-type nightLightBrightnessType Signed-off-by: David Goodyear <david.goodyear@gmail.com> * [veSync] PR: Readme correction Signed-off-by: David Goodyear <david.goodyear@gmail.com>
This commit is contained in:
parent
7323b2e4eb
commit
0a46724b38
@ -1681,6 +1681,11 @@
|
||||
<artifactId>org.openhab.binding.verisure</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.vesync</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.vigicrues</artifactId>
|
||||
|
13
bundles/org.openhab.binding.vesync/NOTICE
Normal file
13
bundles/org.openhab.binding.vesync/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
|
303
bundles/org.openhab.binding.vesync/README.md
Normal file
303
bundles/org.openhab.binding.vesync/README.md
Normal file
@ -0,0 +1,303 @@
|
||||
# VeSync Binding
|
||||
|
||||
Its current support is for the Air Purifiers & Humidifer's branded as Levoit which utilise the VeSync app based on the V2 protocol.
|
||||
|
||||
### Verified Models
|
||||
|
||||
Air Filtering models supported are Core300S, Core400S.
|
||||
Air Humidifier models supported are Dual 200S, Classic 300S, 600S.
|
||||
|
||||
### Awaiting User Verification Models
|
||||
|
||||
Air Filtering models supported are Core200S and Core600S.
|
||||
Air Humidifier Classic 200S (Same as 300S without the nightlight from initial checks)
|
||||
|
||||
## Supported Things
|
||||
|
||||
This binding supports the follow thing types:
|
||||
|
||||
| Thing | Thing Type | Thing Type UID | Discovery | Description |
|
||||
|----------------|------------|----------------|-----------|----------------------------------------------------------------------|
|
||||
| Bridge | Bridge | bridge | Manual | A single connection to the VeSync API |
|
||||
| Air Purifier | Thing | airPurifier | Automatic | A Air Purifier supporting V2 e.g. Core200S/Core300S or Core400S unit |
|
||||
| Air Humidifier | Thing | airHumidifier | Automatic | A Air Humidifier supporting V2 e.g. Classic300S or 600s |
|
||||
|
||||
|
||||
|
||||
This binding was developed from the great work in the listed projects.
|
||||
|
||||
The only Air Filter unit it has been tested against is the Core400S unit, **I'm looking for others to confirm** my queries regarding **the Core200S and Core300S** units.
|
||||
The ***Classic 300S Humidifier*** has been tested, and ***600S with current warm mode restrictions***.
|
||||
|
||||
## Discovery
|
||||
|
||||
Once the bridge is configured auto discovery will discover supported devices from the VeSync API.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
### Bridge configuration parameters
|
||||
|
||||
| Name | Type | Description | Recommended Values |
|
||||
|----------------------------------|--------|-----------------------------------------------------------|--------------------|
|
||||
| username | String | The username as used in the VeSync mobile application | |
|
||||
| password | String | The password as used in the VeSync mobile application | |
|
||||
| airPurifierPollInterval | Number | The poll interval (seconds) for air filters / humidifiers | 60 |
|
||||
| backgroundDeviceDiscovery | Switch | Should the system scan periodically for new devices | ON |
|
||||
| refreshBackgroundDeviceDiscovery | Number | Frequency (seconds) of scans for new new devices | 120 |
|
||||
|
||||
* Note Air PM Levels don't usually change quickly - 60s seems reasonable if openHAB is controlling it and your don't want near instant feedback of physical interactions with the devices.
|
||||
|
||||
### AirPurifier configuration parameters
|
||||
|
||||
It is recommended to use the device name, for locating devices. For this to work all the devices should have a unique
|
||||
name in the VeSync mobile application.
|
||||
|
||||
The mac address from the VeSync mobile application may not align to the one the API
|
||||
uses, therefore it's best left not configured or taken from auto-discovered information.
|
||||
|
||||
Device's will be found communicated with via the MAC Id first and if unsuccessful then by the deviceName.
|
||||
|
||||
| Name | Type | Description |
|
||||
|------------------------|-------------------------|---------------------------------------------------------------------|
|
||||
| deviceName | String | The name given to the device under Settings -> Device Name |
|
||||
| macId | String | The mac for the device under Settings -> Device Info -> MAC Address |
|
||||
|
||||
|
||||
## Channels
|
||||
|
||||
Channel names in **bold** are read/write, everything else is read-only
|
||||
|
||||
### AirPurifier Thing
|
||||
|
||||
| Channel | Type | Description | Model's Supported | Controllable Values |
|
||||
|----------------------|----------------------|------------------------------------------------------------|-------------------|-----------------------|
|
||||
| **enabled** | Switch | Whether the hardware device is enabled (Switched on) | 600S, 400S, 300S | [ON, OFF] |
|
||||
| **childLock** | Switch | Whether the child lock (display lock is enabled) | 600S, 400S, 300S | [ON, OFF] |
|
||||
| **display** | Switch | Whether the display is enabled (display is shown) | 600S, 400S, 300S | [ON, OFF] |
|
||||
| **fanMode** | String | The operation mode of the fan | 600S, 400S | [auto, manual, sleep] |
|
||||
| **fanMode** | String | The operation mode of the fan | 200S, 300S, | [manual, sleep] |
|
||||
| **manualFanSpeed** | Number:Dimensionless | The speed of the fan when in manual mode | 600S, 400S | [1...4] |
|
||||
| **manualFanSpeed** | Number:Dimensionless | The speed of the fan when in manual mode | 300S | [1...3] |
|
||||
| **nightLightMode** | String | The night lights mode | 200S, 300S | [on, dim, off] |
|
||||
| filterLifePercentage | Number:Dimensionless | The remaining filter life as a percentage | 600S, 400S, 300S | |
|
||||
| airQuality | Number:Dimensionless | The air quality as represented by the Core200S / Core300S | 600S, 400S, 300S | |
|
||||
| airQualityPM25 | Number:Density | The air quality as represented by the Core400S | 600S, 400S, 300S | |
|
||||
| errorCode | Number:Dimensionless | The error code reported by the device | 600S, 400S, 300S | |
|
||||
| timerExpiry | DateTime | The expected expiry time of the current timer | 600S, 400S | |
|
||||
| schedulesCount | Number:Dimensionless | The number schedules configured | 600S, 400S | |
|
||||
| configDisplayForever | Switch | Config: Whether the display will disable when not active | 600S, 400S, 300S | |
|
||||
| configAutoMode | String | Config: The mode of operation when auto is active | 600S, 400S, 300S | |
|
||||
| configAutoRoomSize | Number:Dimensionless | Config: The room size set when auto utilises the room size | 600S, 400S, 300S | |
|
||||
|
||||
|
||||
### AirHumidifier Thing
|
||||
|
||||
| Channel | Type | Description | Model's Supported | Controllable Values |
|
||||
|----------------------------|----------------------|---------------------------------------------------------------|----------------------------|---------------------|
|
||||
| **enabled** | Switch | Whether the hardware device is enabled (Switched on) | 200S, Dual200S, 300S, 600S | [ON, OFF] |
|
||||
| **display** | Switch | Whether the display is enabled (display is shown) | 200S, Dual200S, 300S, 600S | [ON, OFF] |
|
||||
| waterLacking | Switch | Indicator whether the unit is lacking water | 200S, Dual200S, 300S, 600S | |
|
||||
| humidityHigh | Switch | Indicator for high humidity | 200S, Dual200S, 300S, 600S | |
|
||||
| waterTankLifted | Switch | Indicator for whether the water tank is removed | 200S, Dual200S, 300S, 600S | |
|
||||
| **stopAtHumiditySetpoint** | Switch | Whether the unit is set to stop when the set point is reached | 200S, Dual200S, 300S, 600S | [ON, OFF] |
|
||||
| humidity | Number:Dimensionless | Indicator for the currently measured humidity % level | 200S, Dual200S, 300S, 600S | |
|
||||
| **mistLevel** | Number:Dimensionless | The current mist level set | 300S | [1...2] |
|
||||
| **mistLevel** | Number:Dimensionless | The current mist level set | 200S, Dual200S, 600S | [1...3] |
|
||||
| **humidifierMode** | String | The current mode of operation | 200S, Dual200S, 300S, 600S | [auto, sleep] |
|
||||
| **nightLightMode** | String | The night light mode | 200S, Dual200S, 300S | [on, dim, off] |
|
||||
| **humiditySetpoint** | Number:Dimensionless | Humidity % set point to reach | 200S, Dual200S, 300S, 600S | [30...80] |
|
||||
| warmEnabled | Switch | Indicator for warm mist mode | 600S | |
|
||||
|
||||
|
||||
## Full Example
|
||||
|
||||
### Configuration (*.things)
|
||||
|
||||
#### Air Purifiers Core 200S/300S/400S Models & Air Humidifier Classic300S/600S Models
|
||||
|
||||
```
|
||||
Bridge vesync:bridge:vesyncServers [username="<USERNAME>", password="<PASSWORD>", airPurifierPollInterval=60] {
|
||||
airPurifier loungeAirFilter [deviceName="<DEVICE NAME FROM APP>"]
|
||||
airPurifier bedroomAirFilter [deviceName="<DEVICE NAME FROM APP>"]
|
||||
airHumidifier loungeHumidifier [deviceName="<DEVICE NAME FROM APP>"]
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration (*.items)
|
||||
|
||||
#### Air Purifier Core 400S / 600S Model
|
||||
|
||||
```
|
||||
Switch LoungeAPPower "Lounge Air Purifier Power" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:enabled" }
|
||||
Switch LoungeAPDisplay "Lounge Air Purifier Display" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:display" }
|
||||
Switch LoungeAPControlsLock "Lounge Air Purifier Controls Locked" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:childLock" }
|
||||
Number:Dimensionless LoungeAPFilterRemainingUse "Lounge Air Purifier Filter Remaining [%.0f %unit%]" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:filterLifePercentage" }
|
||||
String LoungeAPMode "Lounge Air Purifier Mode [%s]" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:fanMode" }
|
||||
Number:Dimensionless LoungeAPManualFanSpeed "Lounge Air Purifier Manual Fan Speed" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:manualFanSpeed" }
|
||||
Number:Density LoungeAPAirQuality "Lounge Air Purifier Air Quality [%.0f% %unit%]" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:airQualityPM25" }
|
||||
Number LoungeAPErrorCode "Lounge Air Purifier Error Code" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:errorCode" }
|
||||
String LoungeAPAutoMode "Lounge Air Purifier Auto Mode" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:configAutoMode" }
|
||||
Number LoungeAPAutoRoomSize "Lounge Air Purifier Auto Room Size [%.0f% sqft]" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:configAutoRoomSize" }
|
||||
Number:Time LoungeAPTimerLeft "Lounge Air Purifier Timer Left [%1$Tp]" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:timerRemain" }
|
||||
DateTime LoungeAPTimerExpiry "Lounge Air Purifier Timer Expiry [%1$tA %1$tI:%1$tM %1$Tp]" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:timerExpiry" }
|
||||
Number LoungeAPSchedulesCount "Lounge Air Purifier Schedules Count" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:schedulesCount" }
|
||||
```
|
||||
|
||||
#### Air Purifier Core 200S/300S Model
|
||||
|
||||
```
|
||||
Switch LoungeAPPower "Lounge Air Purifier Power" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:enabled" }
|
||||
Switch LoungeAPDisplay "Lounge Air Purifier Display" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:display" }
|
||||
String LoungeAPNightLightMode "Lounge Air Purifier Night Light Mode" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:nightLightMode" }
|
||||
Switch LoungeAPControlsLock "Lounge Air Purifier Controls Locked" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:childLock" }
|
||||
Number:Dimensionless LoungeAPFilterRemainingUse "Lounge Air Purifier Filter Remaining [%.0f %unit%]" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:filterLifePercentage" }
|
||||
String LoungeAPMode "Lounge Air Purifier Mode [%s]" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:fanMode" }
|
||||
Number:Dimensionless LoungeAPManualFanSpeed "Lounge Air Purifier Manual Fan Speed" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:manualFanSpeed" }
|
||||
Number:Density LoungeAPAirQuality "Lounge Air Purifier Air Quality [%.0f%]" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:airQuality" }
|
||||
Number LoungeAPErrorCode "Lounge Air Purifier Error Code" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:errorCode" }
|
||||
String LoungeAPAutoMode "Lounge Air Purifier Auto Mode" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:configAutoMode" }
|
||||
Number LoungeAPAutoRoomSize "Lounge Air Purifier Auto Room Size [%.0f% sqft]" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:configAutoRoomSize" }
|
||||
Number:Time LoungeAPTimerLeft "Lounge Air Purifier Timer Left [%1$Tp]" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:timerRemain" }
|
||||
DateTime LoungeAPTimerExpiry "Lounge Air Purifier Timer Expiry [%1$tA %1$tI:%1$tM %1$Tp]" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:timerExpiry" }
|
||||
Number LoungeAPSchedulesCount "Lounge Air Purifier Schedules Count" { channel="vesync:airPurifier:vesyncServers:loungeAirFilter:schedulesCount" }
|
||||
```
|
||||
|
||||
#### Air Humidifier Classic 200S / Dual 200S Model
|
||||
|
||||
```
|
||||
Switch LoungeAHPower "Lounge Air Humidifier Power" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:enabled" }
|
||||
Switch LoungeAHDisplay "Lounge Air Humidifier Display" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:display" }
|
||||
String LoungeAHMode "Lounge Air Humidifier Mode" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:humidifierMode" }
|
||||
Switch LoungeAHWaterLacking "Lounge Air Humidifier Water Lacking" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:waterLacking" }
|
||||
Switch LoungeAHHighHumidity "Lounge Air Humidifier High Humidity" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:humidityHigh" }
|
||||
Switch LoungeAHWaterTankRemoved "Lounge Air Humidifier Water Tank Removed" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:waterTankLifted" }
|
||||
Number:Dimensionless LoungeAHHumidity "Lounge Air Humidifier Measured Humidity [%.0f %unit%]" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:humidity" }
|
||||
Switch LoungeAHTargetStop "Lounge Air Humidifier Stop at target" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:stopAtTargetLevel" }
|
||||
Number:Dimensionless LoungeAHTarget "Lounge Air Humidifier Target Humidity [%.0f %unit%]" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:humiditySetpoint" }
|
||||
Number:Dimensionless LoungeAHMistLevel "Lounge Air Humidifier Mist Level" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:mistLevel" }
|
||||
```
|
||||
|
||||
#### Air Humidifier Classic 300S Model
|
||||
|
||||
```
|
||||
Switch LoungeAHPower "Lounge Air Humidifier Power" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:enabled" }
|
||||
Switch LoungeAHDisplay "Lounge Air Humidifier Display" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:display" }
|
||||
String LoungeAHNightLightMode "Lounge Air Humidifier Night Light Mode" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:nightLightMode }
|
||||
String LoungeAHMode "Lounge Air Humidifier Mode" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:humidifierMode" }
|
||||
Switch LoungeAHWaterLacking "Lounge Air Humidifier Water Lacking" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:waterLacking" }
|
||||
Switch LoungeAHHighHumidity "Lounge Air Humidifier High Humidity" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:humidityHigh" }
|
||||
Switch LoungeAHWaterTankRemoved "Lounge Air Humidifier Water Tank Removed" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:waterTankLifted" }
|
||||
Number:Dimensionless LoungeAHHumidity "Lounge Air Humidifier Measured Humidity [%.0f %unit%]" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:humidity" }
|
||||
Switch LoungeAHTargetStop "Lounge Air Humidifier Stop at target" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:stopAtTargetLevel" }
|
||||
Number:Dimensionless LoungeAHTarget "Lounge Air Humidifier Target Humidity [%.0f %unit%]" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:humiditySetpoint" }
|
||||
Number:Dimensionless LoungeAHMistLevel "Lounge Air Humidifier Mist Level" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:mistLevel" }
|
||||
```
|
||||
|
||||
#### Air Humidifier 600S Model
|
||||
|
||||
```
|
||||
Switch LoungeAHPower "Lounge Air Humidifier Power" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:enabled" }
|
||||
Switch LoungeAHDisplay "Lounge Air Humidifier Display" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:display" }
|
||||
String LoungeAHMode "Lounge Air Humidifier Mode" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:humidifierMode" }
|
||||
Switch LoungeAHWaterLacking "Lounge Air Humidifier Water Lacking" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:waterLacking" }
|
||||
Switch LoungeAHHighHumidity "Lounge Air Humidifier High Humidity" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:humidityHigh" }
|
||||
Switch LoungeAHWaterTankRemoved "Lounge Air Humidifier Water Tank Removed" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:waterTankLifted" }
|
||||
Number:Dimensionless LoungeAHHumidity "Lounge Air Humidifier Measured Humidity [%.0f %unit%]" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:humidity" }
|
||||
Switch LoungeAHTargetStop "Lounge Air Humidifier Stop at target" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:stopAtTargetLevel" }
|
||||
Number:Dimensionless LoungeAHTarget "Lounge Air Humidifier Target Humidity [%.0f %unit%]" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:humiditySetpoint" }
|
||||
Number:Dimensionless LoungeAHMistLevel "Lounge Air Humidifier Mist Level" { channel="vesync:airHumidifier:vesyncServers:loungeHumidifier:mistLevel" }
|
||||
```
|
||||
|
||||
### Configuration (*.sitemap)
|
||||
|
||||
#### Air Purifier Core 400S / 600S Model
|
||||
|
||||
```
|
||||
Frame {
|
||||
Switch item=LoungeAPPower label="Power"
|
||||
Text item=LoungeAPFilterRemainingUse label="Filter Remaining"
|
||||
Switch item=LoungeAPDisplay label="Display"
|
||||
Text item=LoungeAPAirQuality label="Air Quality [%.0f (PM2.5)]"
|
||||
Switch item=LoungeAPControlsLock label="Controls Locked"
|
||||
Text item=LoungeAPTimerExpiry label="Timer Shutdown @" icon="clock"
|
||||
Switch item=LoungeAPMode label="Mode" mappings=[auto="Auto", manual="Manual Fan Control", sleep="Sleeping"] icon="settings"
|
||||
Text item=LoungeAPErrorCode label="Error Code [%.0f]"
|
||||
Switch item=LoungeAPManualFanSpeed label="Manual Fan Speed [%.0f]" mappings=[1="1", 2="2", 3="3", 4="4"] icon="settings"
|
||||
}
|
||||
```
|
||||
|
||||
#### Air Purifier Core 200S/300S Model
|
||||
|
||||
```
|
||||
Frame {
|
||||
Switch item=LoungeAPPower label="Power"
|
||||
Text item=LoungeAPFilterRemainingUse label="Filter Remaining"
|
||||
Switch item=LoungeAPDisplay label="Display"
|
||||
Switch item=LoungeAPNightLightMode label="Night Light Mode" mappings=[on="On", dim="Dimmed", off="Off"] icon="settings"
|
||||
Text item=LoungeAPAirQuality label="Air Quality [%.0f]"
|
||||
Switch item=LoungeAPControlsLock label="Controls Locked"
|
||||
Text item=LoungeAPTimerExpiry label="Timer Shutdown @" icon="clock"
|
||||
Switch item=LoungeAPMode label="Mode" mappings=[manual="Manual Fan Control", sleep="Sleeping"] icon="settings"
|
||||
Text item=LoungeAPErrorCode label="Error Code [%.0f]"
|
||||
Switch item=LoungeAPManualFanSpeed label="Manual Fan Speed [%.0f]" mappings=[1="1", 2="2", 3="3"] icon="settings"
|
||||
}
|
||||
```
|
||||
|
||||
#### Air Humidifier Classic 200S / Dual 200S Model
|
||||
|
||||
```
|
||||
Frame {
|
||||
Switch item=LoungeAHPower
|
||||
Switch item=LoungeAHDisplay
|
||||
Switch item=LoungeAHMode label="Mode" mappings=[auto="Auto", sleep="Sleeping"] icon="settings"
|
||||
Text icon="none" item=LoungeAHWaterLacking
|
||||
Text icon="none" item=LoungeAHHighHumidity
|
||||
Text icon="none" item=LoungeAHWaterTankRemoved
|
||||
Text icon="none" item=LoungeAHHumidity
|
||||
Switch item=LoungeAHTargetStop
|
||||
Slider item=LoungeAHTarget minValue=30 maxValue=80
|
||||
Slider item=LoungeAHMistLevel minValue=1 maxValue=3
|
||||
}
|
||||
```
|
||||
|
||||
#### Air Humidifier Classic 300S Model
|
||||
|
||||
```
|
||||
Frame {
|
||||
Switch item=LoungeAHPower
|
||||
Switch item=LoungeAHDisplay
|
||||
Switch item=LoungeAHNightLightMode label="Night Light Mode" mappings=[on="On", dim="Dimmed", off="Off"] icon="settings"
|
||||
Switch item=LoungeAHMode label="Mode" mappings=[auto="Auto", sleep="Sleeping"] icon="settings"
|
||||
Text icon="none" item=LoungeAHWaterLacking
|
||||
Text icon="none" item=LoungeAHHighHumidity
|
||||
Text icon="none" item=LoungeAHWaterTankRemoved
|
||||
Text icon="none" item=LoungeAHHumidity
|
||||
Switch item=LoungeAHTargetStop
|
||||
Slider item=LoungeAHTarget minValue=30 maxValue=80
|
||||
Slider item=LoungeAHMistLevel minValue=1 maxValue=3
|
||||
}
|
||||
```
|
||||
|
||||
#### Air Humidifier 600S Model
|
||||
|
||||
```
|
||||
Frame {
|
||||
Switch item=LoungeAHPower
|
||||
Switch item=LoungeAHDisplay
|
||||
Switch item=LoungeAHMode label="Mode" mappings=[auto="Auto", sleep="Sleeping"] icon="settings"
|
||||
Text icon="none" item=LoungeAHWaterLacking
|
||||
Text icon="none" item=LoungeAHHighHumidity
|
||||
Text icon="none" item=LoungeAHWaterTankRemoved
|
||||
Text icon="none" item=LoungeAHHumidity
|
||||
Switch item=LoungeAHTargetStop
|
||||
Slider item=LoungeAHTarget minValue=30 maxValue=80
|
||||
Slider item=LoungeAHMistLevel minValue=1 maxValue=3
|
||||
}
|
||||
```
|
||||
|
||||
### Credits
|
||||
|
||||
The binding code is based on a lot of work done by other developers:
|
||||
|
||||
- Contributors of (https://github.com/webdjoe/pyvesync) - Python interface for VeSync
|
||||
- Rene Scherer, Holger Eisold - (https://www.openhab.org/addons/bindings/surepetcare) Sure Petcare Binding for openHAB as a reference point for the starting blocks of this code
|
17
bundles/org.openhab.binding.vesync/pom.xml
Normal file
17
bundles/org.openhab.binding.vesync/pom.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?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 http://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>3.3.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.vesync</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: VeSync Binding</name>
|
||||
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.vesync-${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-vesync" description="VeSync Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.vesync/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncBridgeConfiguration} class contains fields mapping the configuration parameters for the bridge.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VeSyncBridgeConfiguration {
|
||||
|
||||
/**
|
||||
* The clear text password to access the vesync API.
|
||||
*/
|
||||
@Nullable
|
||||
public String password = "";
|
||||
|
||||
/**
|
||||
* The email address / username to access the vesync API.
|
||||
*/
|
||||
@Nullable
|
||||
public String username = "";
|
||||
|
||||
/**
|
||||
* The polling interval to use for air purifier devices.
|
||||
*/
|
||||
@Nullable
|
||||
public Integer airPurifierPollInterval;
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VeSyncConstants {
|
||||
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).setPrettyPrinting()
|
||||
.disableHtmlEscaping().serializeNulls().create();
|
||||
|
||||
private static final String BINDING_ID = "vesync";
|
||||
|
||||
public static final long DEFAULT_REFRESH_INTERVAL_DISCOVERED_DEVICES = 3600;
|
||||
public static final long DEFAULT_POLL_INTERVAL_AIR_FILTERS_DEVICES = 10;
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
|
||||
public static final ThingTypeUID THING_TYPE_AIR_PURIFIER = new ThingTypeUID(BINDING_ID, "airPurifier");
|
||||
public static final ThingTypeUID THING_TYPE_AIR_HUMIDIFIER = new ThingTypeUID(BINDING_ID, "airHumidifier");
|
||||
|
||||
// Thing configuration properties
|
||||
public static final String DEVICE_MAC_ID = "macAddress";
|
||||
|
||||
public static final String EMPTY_STRING = "";
|
||||
|
||||
// Base Device Channel Names
|
||||
public static final String DEVICE_CHANNEL_ENABLED = "enabled";
|
||||
public static final String DEVICE_CHANNEL_DISPLAY_ENABLED = "display";
|
||||
public static final String DEVICE_CHANNEL_CHILD_LOCK_ENABLED = "childLock";
|
||||
public static final String DEVICE_CHANNEL_AIR_FILTER_LIFE_PERCENTAGE_REMAINING = "filterLifePercentage";
|
||||
public static final String DEVICE_CHANNEL_FAN_MODE_ENABLED = "fanMode";
|
||||
public static final String DEVICE_CHANNEL_FAN_SPEED_ENABLED = "manualFanSpeed";
|
||||
public static final String DEVICE_CHANNEL_ERROR_CODE = "errorCode";
|
||||
public static final String DEVICE_CHANNEL_AIRQUALITY_BASIC = "airQuality";
|
||||
public static final String DEVICE_CHANNEL_AIRQUALITY_PM25 = "airQualityPM25";
|
||||
|
||||
public static final String DEVICE_CHANNEL_AF_CONFIG_DISPLAY_FOREVER = "configDisplayForever";
|
||||
public static final String DEVICE_CHANNEL_AF_CONFIG_AUTO_MODE_PREF = "configAutoMode";
|
||||
|
||||
public static final String DEVICE_CHANNEL_AF_AUTO_OFF_CALC_TIME = "timerExpiry";
|
||||
public static final String DEVICE_CHANNEL_AF_CONFIG_AUTO_ROOM_SIZE = "configAutoRoomSize";
|
||||
public static final String DEVICE_CHANNEL_AF_SCHEDULES_COUNT = "schedulesCount";
|
||||
public static final String DEVICE_CHANNEL_AF_NIGHT_LIGHT = "nightLightMode";
|
||||
|
||||
// Humidity related channels
|
||||
public static final String DEVICE_CHANNEL_WATER_LACKS = "waterLacking";
|
||||
public static final String DEVICE_CHANNEL_HUMIDITY_HIGH = "humidityHigh";
|
||||
public static final String DEVICE_CHANNEL_WATER_TANK_LIFTED = "waterTankLifted";
|
||||
public static final String DEVICE_CHANNEL_STOP_AT_TARGET = "stopAtHumiditySetpoint";
|
||||
public static final String DEVICE_CHANNEL_HUMIDITY = "humidity";
|
||||
public static final String DEVICE_CHANNEL_MIST_LEVEL = "mistLevel";
|
||||
public static final String DEVICE_CHANNEL_HUMIDIFIER_MODE = "humidifierMode";
|
||||
public static final String DEVICE_CHANNEL_WARM_ENABLED = "warmEnabled";
|
||||
public static final String DEVICE_CHANNEL_WARM_LEVEL = "warmLevel";
|
||||
|
||||
public static final String DEVICE_CHANNEL_CONFIG_TARGET_HUMIDITY = "humiditySetpoint";
|
||||
|
||||
// Property name constants
|
||||
public static final String DEVICE_PROP_DEVICE_NAME = "Device Name";
|
||||
public static final String DEVICE_PROP_DEVICE_TYPE = "Device Type";
|
||||
public static final String DEVICE_PROP_DEVICE_MAC_ID = "MAC Id";
|
||||
public static final String DEVICE_PROP_DEVICE_UUID = "UUID";
|
||||
|
||||
// Property name for config constants
|
||||
public static final String DEVICE_PROP_CONFIG_DEVICE_NAME = "deviceName";
|
||||
public static final String DEVICE_PROP_CONFIG_DEVICE_MAC = "macId";
|
||||
|
||||
// Bridge name constants
|
||||
public static final String DEVICE_PROP_BRIDGE_REG_TS = "Registration Time";
|
||||
public static final String DEVICE_PROP_BRIDGE_COUNTRY_CODE = "Country Code";
|
||||
public static final String DEVICE_PROP_BRIDGE_ACCEPT_LANG = "Accept Language";
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncDeviceConfiguration} class contains fields mapping the configuration parameters for a VeSync
|
||||
* device's configuration.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VeSyncDeviceConfiguration {
|
||||
|
||||
/**
|
||||
* The clear text device name as reported by the API.
|
||||
*/
|
||||
@Nullable
|
||||
public String deviceName;
|
||||
|
||||
/**
|
||||
* The mac address of the device as reported by the API.
|
||||
*/
|
||||
@Nullable
|
||||
public String macId;
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal;
|
||||
|
||||
import static org.openhab.binding.vesync.internal.VeSyncConstants.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.vesync.internal.api.IHttpClientProvider;
|
||||
import org.openhab.binding.vesync.internal.handlers.VeSyncBridgeHandler;
|
||||
import org.openhab.binding.vesync.internal.handlers.VeSyncDeviceAirHumidifierHandler;
|
||||
import org.openhab.binding.vesync.internal.handlers.VeSyncDeviceAirPurifierHandler;
|
||||
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.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link org.openhab.binding.vesync.internal.VeSyncHandlerFactory} is responsible for creating
|
||||
* things and thing handlers.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.vesync", service = ThingHandlerFactory.class)
|
||||
public class VeSyncHandlerFactory extends BaseThingHandlerFactory implements IHttpClientProvider {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BRIDGE,
|
||||
THING_TYPE_AIR_PURIFIER, THING_TYPE_AIR_HUMIDIFIER);
|
||||
|
||||
private @Nullable HttpClient httpClientRef = null;
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
final ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (VeSyncDeviceAirPurifierHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
return new VeSyncDeviceAirPurifierHandler(thing);
|
||||
} else if (VeSyncDeviceAirHumidifierHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
return new VeSyncDeviceAirHumidifierHandler(thing);
|
||||
} else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) {
|
||||
return new VeSyncBridgeHandler((Bridge) thing, this);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setHttpClientFactory(HttpClientFactory httpClientFactory) {
|
||||
httpClientRef = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable HttpClient getHttpClient() {
|
||||
return httpClientRef;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
|
||||
/**
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IHttpClientProvider {
|
||||
@Nullable
|
||||
HttpClient getHttpClient();
|
||||
}
|
@ -0,0 +1,254 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.api;
|
||||
|
||||
import static org.openhab.binding.vesync.internal.dto.requests.VeSyncProtocolConstants.*;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
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.openhab.binding.vesync.internal.VeSyncConstants;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VeSyncAuthenticatedRequest;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VeSyncLoginCredentials;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VeSyncRequestManagedDeviceBypassV2;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VeSyncRequestManagedDevicesPage;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VeSyncLoginResponse;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VeSyncManagedDeviceBase;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VeSyncManagedDevicesPage;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VeSyncResponse;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VeSyncUserSession;
|
||||
import org.openhab.binding.vesync.internal.exceptions.AuthenticationException;
|
||||
import org.openhab.binding.vesync.internal.exceptions.DeviceUnknownException;
|
||||
import org.openhab.binding.vesync.internal.handlers.VeSyncBridgeHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VeSyncV2ApiHelper {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VeSyncV2ApiHelper.class);
|
||||
|
||||
private @NonNullByDefault({}) HttpClient httpClient;
|
||||
|
||||
private volatile @Nullable VeSyncUserSession loggedInSession;
|
||||
|
||||
private Map<String, @NotNull VeSyncManagedDeviceBase> macLookup;
|
||||
|
||||
public VeSyncV2ApiHelper() {
|
||||
macLookup = new HashMap<>();
|
||||
}
|
||||
|
||||
public Map<String, @NotNull VeSyncManagedDeviceBase> getMacLookupMap() {
|
||||
return macLookup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the httpClient object to be used for API calls to Vesync.
|
||||
*
|
||||
* @param httpClient the client to be used.
|
||||
*/
|
||||
public void setHttpClient(@Nullable HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
public static @NotNull String calculateMd5(final @Nullable String password) {
|
||||
if (password == null) {
|
||||
return "";
|
||||
}
|
||||
MessageDigest md5;
|
||||
StringBuilder md5Result = new StringBuilder();
|
||||
try {
|
||||
md5 = MessageDigest.getInstance("MD5");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return "";
|
||||
}
|
||||
byte[] handshakeHash = md5.digest(password.getBytes(StandardCharsets.UTF_8));
|
||||
for (byte handshakeByte : handshakeHash) {
|
||||
md5Result.append(String.format("%02x", handshakeByte));
|
||||
}
|
||||
return md5Result.toString();
|
||||
}
|
||||
|
||||
public void discoverDevices() throws AuthenticationException {
|
||||
try {
|
||||
VeSyncRequestManagedDevicesPage reqDevPage = new VeSyncRequestManagedDevicesPage(loggedInSession);
|
||||
boolean finished = false;
|
||||
int pageNo = 1;
|
||||
HashMap<String, VeSyncManagedDeviceBase> generatedMacLookup = new HashMap<>();
|
||||
while (!finished) {
|
||||
reqDevPage.pageNo = String.valueOf(pageNo);
|
||||
reqDevPage.pageSize = String.valueOf(100);
|
||||
final String result = reqV1Authorized(V1_MANAGED_DEVICES_ENDPOINT, reqDevPage);
|
||||
|
||||
VeSyncManagedDevicesPage resultsPage = VeSyncConstants.GSON.fromJson(result,
|
||||
VeSyncManagedDevicesPage.class);
|
||||
if (resultsPage == null || !resultsPage.outcome.getTotal().equals(resultsPage.outcome.getPageSize())) {
|
||||
finished = true;
|
||||
} else {
|
||||
++pageNo;
|
||||
}
|
||||
|
||||
if (resultsPage != null) {
|
||||
for (VeSyncManagedDeviceBase device : resultsPage.outcome.list) {
|
||||
logger.debug(
|
||||
"Found device : {}, type: {}, deviceType: {}, connectionState: {}, deviceStatus: {}, deviceRegion: {}, cid: {}, configModule: {}, macID: {}, uuid: {}",
|
||||
device.getDeviceName(), device.getType(), device.getDeviceType(),
|
||||
device.getConnectionStatus(), device.getDeviceStatus(), device.getDeviceRegion(),
|
||||
device.getCid(), device.getConfigModule(), device.getMacId(), device.getUuid());
|
||||
|
||||
// Update the mac address -> device table
|
||||
generatedMacLookup.put(device.getMacId(), device);
|
||||
}
|
||||
}
|
||||
}
|
||||
macLookup = Collections.unmodifiableMap(generatedMacLookup);
|
||||
} catch (final AuthenticationException ae) {
|
||||
logger.warn("Failed background device scan : {}", ae.getMessage());
|
||||
throw ae;
|
||||
}
|
||||
}
|
||||
|
||||
public String reqV2Authorized(final String url, final String macId, final VeSyncAuthenticatedRequest requestData)
|
||||
throws AuthenticationException, DeviceUnknownException {
|
||||
if (loggedInSession == null) {
|
||||
throw new AuthenticationException("User is not logged in");
|
||||
}
|
||||
// Apply current session authentication data
|
||||
requestData.applyAuthentication(loggedInSession);
|
||||
|
||||
// Apply specific addressing parameters
|
||||
if (requestData instanceof VeSyncRequestManagedDeviceBypassV2) {
|
||||
final VeSyncManagedDeviceBase deviceData = macLookup.get(macId);
|
||||
if (deviceData == null) {
|
||||
throw new DeviceUnknownException(String.format("Device not discovered with mac id: %s", macId));
|
||||
}
|
||||
((VeSyncRequestManagedDeviceBypassV2) requestData).cid = deviceData.cid;
|
||||
((VeSyncRequestManagedDeviceBypassV2) requestData).configModule = deviceData.configModule;
|
||||
((VeSyncRequestManagedDeviceBypassV2) requestData).deviceRegion = deviceData.deviceRegion;
|
||||
}
|
||||
return reqV1Authorized(url, requestData);
|
||||
}
|
||||
|
||||
public String reqV1Authorized(final String url, final VeSyncAuthenticatedRequest requestData)
|
||||
throws AuthenticationException {
|
||||
try {
|
||||
return directReqV1Authorized(url, requestData);
|
||||
} catch (final AuthenticationException ae) {
|
||||
throw ae;
|
||||
}
|
||||
}
|
||||
|
||||
private String directReqV1Authorized(final String url, final VeSyncAuthenticatedRequest requestData)
|
||||
throws AuthenticationException {
|
||||
try {
|
||||
Request request = httpClient.POST(url);
|
||||
|
||||
// No headers for login
|
||||
request.content(new StringContentProvider(VeSyncConstants.GSON.toJson(requestData)));
|
||||
|
||||
logger.debug("POST @ {} with content\r\n{}", url, VeSyncConstants.GSON.toJson(requestData));
|
||||
|
||||
request.header(HttpHeader.CONTENT_TYPE, "application/json; utf-8");
|
||||
|
||||
ContentResponse response = request.timeout(5, TimeUnit.SECONDS).send();
|
||||
if (response.getStatus() == HttpURLConnection.HTTP_OK) {
|
||||
VeSyncResponse commResponse = VeSyncConstants.GSON.fromJson(response.getContentAsString(),
|
||||
VeSyncResponse.class);
|
||||
|
||||
if (commResponse != null && (commResponse.isMsgSuccess() || commResponse.isMsgDeviceOffline())) {
|
||||
logger.debug("Got OK response {}", response.getContentAsString());
|
||||
return response.getContentAsString();
|
||||
} else {
|
||||
logger.debug("Got FAILED response {}", response.getContentAsString());
|
||||
throw new AuthenticationException("Invalid JSON response from login");
|
||||
}
|
||||
} else {
|
||||
logger.debug("HTTP Response Code: {}", response.getStatus());
|
||||
logger.debug("HTTP Response Msg: {}", response.getReason());
|
||||
throw new AuthenticationException(
|
||||
"HTTP response " + response.getStatus() + " - " + response.getReason());
|
||||
}
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
throw new AuthenticationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void login(final @Nullable String username, final @Nullable String password,
|
||||
final @Nullable String timezone) throws AuthenticationException {
|
||||
if (username == null || password == null || timezone == null) {
|
||||
loggedInSession = null;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
loggedInSession = processLogin(username, password, timezone).getUserSession();
|
||||
} catch (final AuthenticationException ae) {
|
||||
loggedInSession = null;
|
||||
throw ae;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateBridgeData(final VeSyncBridgeHandler bridge) {
|
||||
bridge.handleNewUserSession(loggedInSession);
|
||||
}
|
||||
|
||||
private VeSyncLoginResponse processLogin(String username, String password, String timezone)
|
||||
throws AuthenticationException {
|
||||
try {
|
||||
Request request = httpClient.POST(V1_LOGIN_ENDPOINT);
|
||||
|
||||
// No headers for login
|
||||
request.content(new StringContentProvider(
|
||||
VeSyncConstants.GSON.toJson(new VeSyncLoginCredentials(username, password))));
|
||||
|
||||
request.header(HttpHeader.CONTENT_TYPE, "application/json; utf-8");
|
||||
|
||||
ContentResponse response = request.timeout(5, TimeUnit.SECONDS).send();
|
||||
if (response.getStatus() == HttpURLConnection.HTTP_OK) {
|
||||
VeSyncLoginResponse loginResponse = VeSyncConstants.GSON.fromJson(response.getContentAsString(),
|
||||
VeSyncLoginResponse.class);
|
||||
if (loginResponse != null && loginResponse.isMsgSuccess()) {
|
||||
logger.debug("Login successful");
|
||||
return loginResponse;
|
||||
} else {
|
||||
throw new AuthenticationException("Invalid / unexpected JSON response from login");
|
||||
}
|
||||
} else {
|
||||
logger.warn("Login Failed - HTTP Response Code: {} - {}", response.getStatus(), response.getReason());
|
||||
throw new AuthenticationException(
|
||||
"HTTP response " + response.getStatus() + " - " + response.getReason());
|
||||
}
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
throw new AuthenticationException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.discovery;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.vesync.internal.handlers.VeSyncBridgeHandler;
|
||||
|
||||
/**
|
||||
* The {@link DeviceMetaDataUpdatedHandler} enables call-backs for when the device meta-data is updated from a bridge.
|
||||
* (VeSync Server Account).
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface DeviceMetaDataUpdatedHandler {
|
||||
void handleMetadataRetrieved(VeSyncBridgeHandler handler);
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.vesync.internal.VeSyncConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.vesync.internal.handlers.VeSyncBridgeHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncDiscoveryService} is an implementation of a discovery service for VeSync devices. The meta-data is
|
||||
* read by the bridge, and the discovery data updated via a callback implemented by the DeviceMetaDataUpdatedHandler.
|
||||
*
|
||||
* @author David Godyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.vesync")
|
||||
public class VeSyncDiscoveryService extends AbstractDiscoveryService
|
||||
implements DiscoveryService, ThingHandlerService, DeviceMetaDataUpdatedHandler {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
|
||||
|
||||
private static final int DISCOVER_TIMEOUT_SECONDS = 5;
|
||||
|
||||
private @NonNullByDefault({}) VeSyncBridgeHandler bridgeHandler;
|
||||
private @NonNullByDefault({}) ThingUID bridgeUID;
|
||||
|
||||
/**
|
||||
* Creates a VeSyncDiscoveryService with enabled autostart.
|
||||
*/
|
||||
public VeSyncDiscoveryService() {
|
||||
super(SUPPORTED_THING_TYPES, DISCOVER_TIMEOUT_SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypes() {
|
||||
return SUPPORTED_THING_TYPES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate() {
|
||||
final Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY, Boolean.TRUE);
|
||||
super.activate(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof VeSyncBridgeHandler) {
|
||||
bridgeHandler = (VeSyncBridgeHandler) handler;
|
||||
bridgeUID = bridgeHandler.getUID();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return bridgeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
if (bridgeHandler != null) {
|
||||
bridgeHandler.registerMetaDataUpdatedHandler(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
if (bridgeHandler != null) {
|
||||
bridgeHandler.unregisterMetaDataUpdatedHandler(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
// If the bridge is not online no other thing devices can be found, so no reason to scan at this moment.
|
||||
removeOlderResults(getTimestampOfLastScan());
|
||||
if (ThingStatus.ONLINE.equals(bridgeHandler.getThing().getStatus())) {
|
||||
bridgeHandler.runDeviceScanSequenceNoAuthErrors();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMetadataRetrieved(VeSyncBridgeHandler handler) {
|
||||
bridgeHandler.getAirPurifiersMetadata().map(apMeta -> {
|
||||
final Map<String, Object> properties = new HashMap<>(6);
|
||||
final String deviceUUID = apMeta.getUuid();
|
||||
properties.put(DEVICE_PROP_DEVICE_NAME, apMeta.getDeviceName());
|
||||
properties.put(DEVICE_PROP_DEVICE_TYPE, apMeta.getDeviceType());
|
||||
properties.put(DEVICE_PROP_DEVICE_MAC_ID, apMeta.getMacId());
|
||||
properties.put(DEVICE_PROP_DEVICE_UUID, deviceUUID);
|
||||
properties.put(DEVICE_PROP_CONFIG_DEVICE_MAC, apMeta.getMacId());
|
||||
properties.put(DEVICE_PROP_CONFIG_DEVICE_NAME, apMeta.getDeviceName());
|
||||
return DiscoveryResultBuilder.create(new ThingUID(THING_TYPE_AIR_PURIFIER, bridgeUID, deviceUUID))
|
||||
.withLabel(apMeta.getDeviceName()).withBridge(bridgeUID).withProperties(properties)
|
||||
.withRepresentationProperty(DEVICE_PROP_DEVICE_MAC_ID).build();
|
||||
}).forEach(this::thingDiscovered);
|
||||
|
||||
bridgeHandler.getAirHumidifiersMetadata().map(apMeta -> {
|
||||
final Map<String, Object> properties = new HashMap<>(6);
|
||||
final String deviceUUID = apMeta.getUuid();
|
||||
properties.put(DEVICE_PROP_DEVICE_NAME, apMeta.getDeviceName());
|
||||
properties.put(DEVICE_PROP_DEVICE_TYPE, apMeta.getDeviceType());
|
||||
properties.put(DEVICE_PROP_DEVICE_MAC_ID, apMeta.getMacId());
|
||||
properties.put(DEVICE_PROP_DEVICE_UUID, deviceUUID);
|
||||
properties.put(DEVICE_PROP_CONFIG_DEVICE_MAC, apMeta.getMacId());
|
||||
properties.put(DEVICE_PROP_CONFIG_DEVICE_NAME, apMeta.getDeviceName());
|
||||
return DiscoveryResultBuilder.create(new ThingUID(THING_TYPE_AIR_HUMIDIFIER, bridgeUID, deviceUUID))
|
||||
.withLabel(apMeta.getDeviceName()).withBridge(bridgeUID).withProperties(properties)
|
||||
.withRepresentationProperty(DEVICE_PROP_DEVICE_MAC_ID).build();
|
||||
}).forEach(this::thingDiscovered);
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.dto;
|
||||
|
||||
import static org.openhab.binding.vesync.internal.VeSyncConstants.DEFAULT_POLL_INTERVAL_AIR_FILTERS_DEVICES;
|
||||
import static org.openhab.binding.vesync.internal.VeSyncConstants.DEFAULT_REFRESH_INTERVAL_DISCOVERED_DEVICES;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncBridgeConfiguration} is a container for all the bridge configuration.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
public class VeSyncBridgeConfiguration {
|
||||
|
||||
public String username;
|
||||
public String password;
|
||||
public long airPurifierPollInterval = DEFAULT_POLL_INTERVAL_AIR_FILTERS_DEVICES;
|
||||
public boolean backgroundDeviceDiscovery;
|
||||
public long refreshBackgroundDeviceDiscovery = DEFAULT_REFRESH_INTERVAL_DISCOVERED_DEVICES;
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.dto.requests;
|
||||
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VeSyncUserSession;
|
||||
import org.openhab.binding.vesync.internal.exceptions.AuthenticationException;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncAuthenticatedRequest} is a Java class used as a DTO to hold the Vesync's API's common request data.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
public class VeSyncAuthenticatedRequest extends VeSyncRequest {
|
||||
|
||||
@SerializedName("accountID")
|
||||
public String accountId;
|
||||
|
||||
@SerializedName("token")
|
||||
public String token;
|
||||
|
||||
public VeSyncAuthenticatedRequest() {
|
||||
super();
|
||||
}
|
||||
|
||||
public VeSyncAuthenticatedRequest(final VeSyncUserSession user) throws AuthenticationException {
|
||||
super();
|
||||
if (user == null) {
|
||||
throw new AuthenticationException("User is not logged in");
|
||||
}
|
||||
this.token = user.getToken();
|
||||
this.accountId = user.getAccountId();
|
||||
}
|
||||
|
||||
public void applyAuthentication(final VeSyncUserSession userSession) throws AuthenticationException {
|
||||
if (userSession == null) {
|
||||
throw new AuthenticationException("User is not logged in");
|
||||
}
|
||||
this.accountId = userSession.getAccountId();
|
||||
this.token = userSession.getToken();
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.dto.requests;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncLoginCredentials} is the Java class as a DTO to hold login credentials for the Vesync
|
||||
* API.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
public class VeSyncLoginCredentials extends VeSyncRequest {
|
||||
|
||||
@SerializedName("email")
|
||||
public String email;
|
||||
@SerializedName("password")
|
||||
public String passwordMd5;
|
||||
@SerializedName("userType")
|
||||
public String userType;
|
||||
@SerializedName("devToken")
|
||||
public String devToken = "";
|
||||
|
||||
public VeSyncLoginCredentials() {
|
||||
super();
|
||||
userType = "1";
|
||||
method = "login";
|
||||
}
|
||||
|
||||
public VeSyncLoginCredentials(String email, String password) {
|
||||
this();
|
||||
this.email = email;
|
||||
this.passwordMd5 = password;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.dto.requests;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncProtocolConstants} contains common Strings used by various elements of the protocol.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
public interface VeSyncProtocolConstants {
|
||||
|
||||
// Common Payloads
|
||||
String MODE_AUTO = "auto";
|
||||
String MODE_MANUAL = "manual";
|
||||
String MODE_SLEEP = "sleep";
|
||||
|
||||
String MODE_ON = "on";
|
||||
String MODE_DIM = "dim";
|
||||
String MODE_OFF = "off";
|
||||
|
||||
// Common Commands
|
||||
String DEVICE_SET_SWITCH = "setSwitch";
|
||||
String DEVICE_SET_DISPLAY = "setDisplay";
|
||||
String DEVICE_SET_LEVEL = "setLevel";
|
||||
|
||||
// Humidifier Commands
|
||||
String DEVICE_SET_AUTOMATIC_STOP = "setAutomaticStop";
|
||||
String DEVICE_SET_HUMIDITY_MODE = "setHumidityMode";
|
||||
String DEVICE_SET_TARGET_HUMIDITY_MODE = "setTargetHumidity";
|
||||
String DEVICE_SET_VIRTUAL_LEVEL = "setVirtualLevel";
|
||||
String DEVICE_SET_NIGHT_LIGHT_BRIGHTNESS = "setNightLightBrightness";
|
||||
String DEVICE_GET_HUMIDIFIER_STATUS = "getHumidifierStatus";
|
||||
|
||||
String DEVICE_LEVEL_TYPE_MIST = "mist";
|
||||
|
||||
// Air Purifier Commands
|
||||
String DEVICE_SET_PURIFIER_MODE = "setPurifierMode";
|
||||
String DEVICE_SET_CHILD_LOCK = "setChildLock";
|
||||
String DEVICE_SET_NIGHT_LIGHT = "setNightLight";
|
||||
String DEVICE_GET_PURIFIER_STATUS = "getPurifierStatus";
|
||||
String DEVICE_LEVEL_TYPE_WIND = "wind";
|
||||
|
||||
/**
|
||||
* Base URL for AUTHENTICATION REQUESTS
|
||||
*/
|
||||
String PROTOCOL = "https";
|
||||
String HOST_ENDPOINT = PROTOCOL + "://smartapi.vesync.com/cloud";
|
||||
String V1_LOGIN_ENDPOINT = HOST_ENDPOINT + "/v1/user/login";
|
||||
String V1_MANAGED_DEVICES_ENDPOINT = HOST_ENDPOINT + "/v1/deviceManaged/devices";
|
||||
String V2_BYPASS_ENDPOINT = HOST_ENDPOINT + "/v2/deviceManaged/bypassV2";
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.dto.requests;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncRequest} is a Java class used as a DTO to hold the Vesync's API's common request data.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
public class VeSyncRequest {
|
||||
|
||||
@SerializedName("timeZone")
|
||||
public String timeZone = "America/New_York";
|
||||
|
||||
@SerializedName("acceptLanguage")
|
||||
public String acceptLanguage = "en";
|
||||
|
||||
@SerializedName("appVersion")
|
||||
public String appVersion = "2.5.1";
|
||||
|
||||
@SerializedName("phoneBrand")
|
||||
public String phoneBrand = "SM N9005";
|
||||
|
||||
@SerializedName("phoneOS")
|
||||
public String phoneOS = "Android";
|
||||
|
||||
@SerializedName("traceId")
|
||||
public String traceId = "";
|
||||
|
||||
@SerializedName("method")
|
||||
public String method;
|
||||
|
||||
public VeSyncRequest() {
|
||||
traceId = String.valueOf(System.currentTimeMillis());
|
||||
}
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.dto.requests;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncRequestManagedDeviceBypassV2} is a Java class used as a DTO to hold the Vesync's API's common
|
||||
* request data for V2 ByPass payloads.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
public class VeSyncRequestManagedDeviceBypassV2 extends VeSyncAuthenticatedRequest {
|
||||
|
||||
@SerializedName("deviceRegion")
|
||||
public String deviceRegion = "";
|
||||
|
||||
@SerializedName("debugMode")
|
||||
public boolean debugMode = false;
|
||||
|
||||
@SerializedName("cid")
|
||||
public String cid = "";
|
||||
|
||||
@SerializedName("configModule")
|
||||
public String configModule = "";
|
||||
|
||||
@SerializedName("payload")
|
||||
public VesyncManagedDeviceBase payload = new VesyncManagedDeviceBase();
|
||||
|
||||
/**
|
||||
* Contains basic information about the device.
|
||||
*/
|
||||
public class VesyncManagedDeviceBase {
|
||||
|
||||
@SerializedName("method")
|
||||
public String method;
|
||||
|
||||
@SerializedName("source")
|
||||
public String source = "APP";
|
||||
|
||||
@SerializedName("data")
|
||||
public EmptyPayload data = new EmptyPayload();
|
||||
}
|
||||
|
||||
public static class EmptyPayload {
|
||||
}
|
||||
|
||||
public static class SetSwitchPayload extends EmptyPayload {
|
||||
|
||||
public SetSwitchPayload(final boolean enabled, final int id) {
|
||||
this.enabled = enabled;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@SerializedName("enabled")
|
||||
public boolean enabled = true;
|
||||
|
||||
@SerializedName("id")
|
||||
public int id = -1;
|
||||
}
|
||||
|
||||
public static class EnabledPayload extends EmptyPayload {
|
||||
|
||||
public EnabledPayload(final boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@SerializedName("enabled")
|
||||
public boolean enabled = true;
|
||||
}
|
||||
|
||||
public static class SetLevelPayload extends EmptyPayload {
|
||||
|
||||
public SetLevelPayload(final int id, final String type, final int level) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
@SerializedName("id")
|
||||
public int id = -1;
|
||||
|
||||
@SerializedName("level")
|
||||
public int level = -1;
|
||||
|
||||
@SerializedName("type")
|
||||
public String type = "";
|
||||
}
|
||||
|
||||
public static class SetState extends EmptyPayload {
|
||||
|
||||
public SetState(final boolean state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@SerializedName("state")
|
||||
public boolean state = false;
|
||||
}
|
||||
|
||||
public static class SetNightLight extends EmptyPayload {
|
||||
|
||||
public SetNightLight(final String state) {
|
||||
this.nightLight = state;
|
||||
}
|
||||
|
||||
@SerializedName("night_light")
|
||||
public String nightLight = "";
|
||||
}
|
||||
|
||||
public static class SetNightLightBrightness extends EmptyPayload {
|
||||
|
||||
public SetNightLightBrightness(final int state) {
|
||||
this.nightLightLevel = state;
|
||||
}
|
||||
|
||||
@SerializedName("night_light_brightness")
|
||||
public int nightLightLevel = 0;
|
||||
}
|
||||
|
||||
public static class SetTargetHumidity extends EmptyPayload {
|
||||
|
||||
public SetTargetHumidity(final int state) {
|
||||
this.targetHumidity = state;
|
||||
}
|
||||
|
||||
@SerializedName("target_humidity")
|
||||
public int targetHumidity = 0;
|
||||
}
|
||||
|
||||
public static class SetChildLock extends EmptyPayload {
|
||||
|
||||
public SetChildLock(final boolean childLock) {
|
||||
this.childLock = childLock;
|
||||
}
|
||||
|
||||
@SerializedName("child_lock")
|
||||
public boolean childLock = false;
|
||||
}
|
||||
|
||||
public static class SetMode extends EmptyPayload {
|
||||
|
||||
public SetMode(final String mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
@SerializedName("mode")
|
||||
public String mode = "";
|
||||
}
|
||||
|
||||
public VeSyncRequestManagedDeviceBypassV2() {
|
||||
super();
|
||||
method = "bypassV2";
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.dto.requests;
|
||||
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VeSyncUserSession;
|
||||
import org.openhab.binding.vesync.internal.exceptions.AuthenticationException;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncRequestManagedDevicesPage} is the Java class as a DTO to hold login credentials for the Vesync
|
||||
* API.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
public class VeSyncRequestManagedDevicesPage extends VeSyncAuthenticatedRequest {
|
||||
|
||||
@SerializedName("pageNo")
|
||||
public String pageNo;
|
||||
|
||||
@SerializedName("pageSize")
|
||||
public String pageSize;
|
||||
|
||||
public VeSyncRequestManagedDevicesPage(final VeSyncUserSession user) throws AuthenticationException {
|
||||
super(user);
|
||||
method = "devices";
|
||||
}
|
||||
|
||||
public VeSyncRequestManagedDevicesPage(final VeSyncUserSession user, int pageNo, int pageSize)
|
||||
throws AuthenticationException {
|
||||
this(user);
|
||||
this.pageNo = String.valueOf(pageNo);
|
||||
this.pageSize = String.valueOf(pageSize);
|
||||
}
|
||||
|
||||
public String getPageNo() {
|
||||
return pageNo;
|
||||
}
|
||||
|
||||
public String getPageSize() {
|
||||
return pageSize;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.dto.requests;
|
||||
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VeSyncUserSession;
|
||||
import org.openhab.binding.vesync.internal.exceptions.AuthenticationException;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncRequestV1ManagedDeviceDetails} is the Java class as a DTO to hold login credentials for the Vesync
|
||||
* API.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
public class VeSyncRequestV1ManagedDeviceDetails extends VeSyncAuthenticatedRequest {
|
||||
|
||||
@SerializedName("mobileId")
|
||||
public String mobileId = "1234567890123456";
|
||||
|
||||
@SerializedName("uuid")
|
||||
public String uuid = null;
|
||||
|
||||
public VeSyncRequestV1ManagedDeviceDetails(final String deviceUuid) {
|
||||
uuid = deviceUuid;
|
||||
method = "deviceDetail";
|
||||
}
|
||||
|
||||
public VeSyncRequestV1ManagedDeviceDetails(final VeSyncUserSession user) throws AuthenticationException {
|
||||
super(user);
|
||||
method = "deviceDetail";
|
||||
}
|
||||
|
||||
public VeSyncRequestV1ManagedDeviceDetails(final VeSyncUserSession user, String deviceUuid)
|
||||
throws AuthenticationException {
|
||||
this(user);
|
||||
uuid = deviceUuid;
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public String getMobileId() {
|
||||
return mobileId;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.dto.responses;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncLoginResponse} is a Java class used as a DTO to hold the Vesync's API's login response.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
public class VeSyncLoginResponse extends VeSyncResponse {
|
||||
|
||||
public VeSyncUserSession result;
|
||||
|
||||
public VeSyncUserSession getUserSession() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return (result == null) ? null : result.token;
|
||||
}
|
||||
|
||||
public String getAccountId() {
|
||||
return (result == null) ? null : result.accountId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VesyncLoginResponse [msg=" + getMsg() + ", result=" + result + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.dto.responses;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Contains basic information about a single device, from within a VeSyncManagedDevicesPage.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
public class VeSyncManagedDeviceBase {
|
||||
|
||||
@SerializedName("deviceRegion")
|
||||
public String deviceRegion;
|
||||
|
||||
public String getDeviceRegion() {
|
||||
return deviceRegion;
|
||||
}
|
||||
|
||||
@SerializedName("deviceType")
|
||||
public String deviceType;
|
||||
|
||||
public String getDeviceType() {
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
@SerializedName("deviceName")
|
||||
public String deviceName;
|
||||
|
||||
public String getDeviceName() {
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
@SerializedName("deviceImg")
|
||||
public String deviceImg;
|
||||
|
||||
public String getDeviceImg() {
|
||||
return deviceImg;
|
||||
}
|
||||
|
||||
@SerializedName("deviceStatus")
|
||||
public String deviceStatus;
|
||||
|
||||
public String getDeviceStatus() {
|
||||
return deviceStatus;
|
||||
}
|
||||
|
||||
@SerializedName("cid")
|
||||
public String cid;
|
||||
|
||||
public String getCid() {
|
||||
return cid;
|
||||
}
|
||||
|
||||
@SerializedName("connectionStatus")
|
||||
public String connectionStatus;
|
||||
|
||||
public String getConnectionStatus() {
|
||||
return connectionStatus;
|
||||
}
|
||||
|
||||
@SerializedName("connectionType")
|
||||
public String connectionType;
|
||||
|
||||
public String getConnectionType() {
|
||||
return connectionType;
|
||||
}
|
||||
|
||||
@SerializedName("type")
|
||||
public String type;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@SerializedName("subDeviceNo")
|
||||
public String subDeviceNo;
|
||||
|
||||
public String getSubDeviceNo() {
|
||||
return subDeviceNo;
|
||||
}
|
||||
|
||||
@SerializedName("subDeviceType")
|
||||
public String subDeviceType;
|
||||
|
||||
public String getSubDeviceType() {
|
||||
return subDeviceType;
|
||||
}
|
||||
|
||||
@SerializedName("uuid")
|
||||
public String uuid;
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@SerializedName("macID")
|
||||
public String macId;
|
||||
|
||||
public String getMacId() {
|
||||
return macId;
|
||||
}
|
||||
|
||||
@SerializedName("currentFirmVersion")
|
||||
public String currentFirmVersion;
|
||||
|
||||
public String getCurrentFirmVersion() {
|
||||
return currentFirmVersion;
|
||||
}
|
||||
|
||||
@SerializedName("configModule")
|
||||
public String configModule;
|
||||
|
||||
public String getConfigModule() {
|
||||
return configModule;
|
||||
}
|
||||
|
||||
@SerializedName("mode")
|
||||
public String mode;
|
||||
|
||||
public String getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
@SerializedName("speed")
|
||||
public String speed;
|
||||
|
||||
public String getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.dto.responses;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncManagedDevicesPage} is a Java class used as a DTO to hold the Vesync's API's response data to a
|
||||
* page of data requesting the manages devices.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
public class VeSyncManagedDevicesPage extends VeSyncResponse {
|
||||
|
||||
@SerializedName("result")
|
||||
public Outcome outcome;
|
||||
|
||||
public class Outcome {
|
||||
@SerializedName("pageNo")
|
||||
public String pageNo;
|
||||
|
||||
@SerializedName("total")
|
||||
public String total;
|
||||
|
||||
@SerializedName("pageSize")
|
||||
public String pageSize;
|
||||
|
||||
@SerializedName("list")
|
||||
public VeSyncManagedDeviceBase[] list;
|
||||
|
||||
public String getPageNo() {
|
||||
return pageNo;
|
||||
}
|
||||
|
||||
public String getPageSize() {
|
||||
return pageSize;
|
||||
}
|
||||
|
||||
public String getTotal() {
|
||||
return total;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.dto.responses;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncResponse} is a Java class used as a DTO to hold the Vesync's API's common response data.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
public class VeSyncResponse {
|
||||
|
||||
@SerializedName("traceId")
|
||||
public String traceId;
|
||||
|
||||
@SerializedName("code")
|
||||
public String code;
|
||||
|
||||
@SerializedName("msg")
|
||||
public String msg;
|
||||
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public String getTraceId() {
|
||||
return traceId;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public boolean isMsgSuccess() {
|
||||
return (msg != null) ? "request success".equals(msg) : false;
|
||||
}
|
||||
|
||||
public boolean isMsgDeviceOffline() {
|
||||
return (msg != null) ? "device offline".equals(msg) : false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VesyncResponse [traceId=\"" + traceId + "\", msg=\"" + msg + "\", code=\"" + code + "\"]";
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.dto.responses;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncResponseManagedDeviceBypassV2} is a Java class used as a DTO to hold the Vesync's API's common
|
||||
* response data.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
public class VeSyncResponseManagedDeviceBypassV2 extends VeSyncResponse {
|
||||
|
||||
@SerializedName("result")
|
||||
public ManagedDeviceByPassV2Payload result;
|
||||
|
||||
public class ManagedDeviceByPassV2Payload extends VeSyncResponse {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.dto.responses;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Contains data about the logged in user - including the accountID and token's used
|
||||
* for authenticating other payload's.
|
||||
*
|
||||
* @see unit test - Result may not be in respone if not authenticated
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
public class VeSyncUserSession {
|
||||
|
||||
public String token;
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
@SerializedName("registerTime")
|
||||
public String registerTime;
|
||||
|
||||
@SerializedName("accountID")
|
||||
public String accountId;
|
||||
|
||||
public String getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
@SerializedName("registerAppVersion")
|
||||
public String registerAppVersion;
|
||||
|
||||
@SerializedName("countryCode")
|
||||
public String countryCode;
|
||||
|
||||
@SerializedName("acceptLanguage")
|
||||
public String acceptLanguage;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Data [user=AB" + ", token=" + token + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.dto.responses;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncV2BypassHumidifierStatus} is a Java class used as a DTO to hold the Vesync's API's common response
|
||||
* data, in regards to a Air Humidifier device.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
public class VeSyncV2BypassHumidifierStatus extends VeSyncResponse {
|
||||
|
||||
@SerializedName("result")
|
||||
public HumidifierrStatus result;
|
||||
|
||||
public class HumidifierrStatus extends VeSyncResponse {
|
||||
|
||||
@SerializedName("result")
|
||||
public AirHumidifierStatus result;
|
||||
|
||||
public class AirHumidifierStatus {
|
||||
@SerializedName("enabled")
|
||||
public boolean enabled;
|
||||
|
||||
@SerializedName("humidity")
|
||||
public int humidity;
|
||||
|
||||
@SerializedName("mist_virtual_level")
|
||||
public int mistVirtualLevel;
|
||||
|
||||
@SerializedName("mist_level")
|
||||
public int mistLevel;
|
||||
|
||||
@SerializedName("mode")
|
||||
public String mode;
|
||||
|
||||
@SerializedName("water_lacks")
|
||||
public boolean waterLacks;
|
||||
|
||||
@SerializedName("humidity_high")
|
||||
public boolean humidityHigh;
|
||||
|
||||
@SerializedName("water_tank_lifted")
|
||||
public boolean waterTankLifted;
|
||||
|
||||
@SerializedName("display")
|
||||
public boolean display;
|
||||
|
||||
@SerializedName("automatic_stop_reach_target")
|
||||
public boolean automaticStopReachTarget;
|
||||
|
||||
@SerializedName("configuration")
|
||||
public HumidityPurifierConfig configuration;
|
||||
|
||||
@SerializedName("night_light_brightness")
|
||||
public int nightLightBrightness;
|
||||
|
||||
@SerializedName("warm_enabled")
|
||||
public boolean warnEnabled;
|
||||
|
||||
@SerializedName("warm_level")
|
||||
public int warmLevel;
|
||||
|
||||
public class HumidityPurifierConfig {
|
||||
@SerializedName("auto_target_humidity")
|
||||
public int autoTargetHumidity;
|
||||
|
||||
@SerializedName("display")
|
||||
public boolean display;
|
||||
|
||||
@SerializedName("automatic_stop")
|
||||
public boolean automaticStop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.dto.responses;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncV2BypassPurifierStatus} is a Java class used as a DTO to hold the Vesync's API's common response
|
||||
* data,
|
||||
* in regards to a Air Purifier device.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
public class VeSyncV2BypassPurifierStatus extends VeSyncResponse {
|
||||
|
||||
@SerializedName("result")
|
||||
public PurifierStatus result;
|
||||
|
||||
public class PurifierStatus extends VeSyncResponse {
|
||||
|
||||
@SerializedName("result")
|
||||
public AirPurifierStatus result;
|
||||
|
||||
public class AirPurifierStatus {
|
||||
@SerializedName("enabled")
|
||||
public boolean enabled;
|
||||
|
||||
@SerializedName("filter_life")
|
||||
public int filterLife;
|
||||
|
||||
@SerializedName("mode")
|
||||
public String mode;
|
||||
|
||||
@SerializedName("level")
|
||||
public int level;
|
||||
|
||||
@SerializedName("air_quality")
|
||||
public int airQuality;
|
||||
|
||||
@SerializedName("air_quality_value")
|
||||
public int airQualityValue;
|
||||
|
||||
@SerializedName("display")
|
||||
public boolean display;
|
||||
|
||||
@SerializedName("child_lock")
|
||||
public boolean childLock;
|
||||
|
||||
@SerializedName("night_light")
|
||||
public String nightLight;
|
||||
|
||||
@SerializedName("configuration")
|
||||
public AirPurifierConfig configuration;
|
||||
|
||||
public class AirPurifierConfig {
|
||||
@SerializedName("display")
|
||||
public boolean display;
|
||||
|
||||
@SerializedName("display_forever")
|
||||
public boolean displayForever;
|
||||
|
||||
@SerializedName("auto_preference")
|
||||
public AirPurifierConfigAutoPref autoPreference;
|
||||
|
||||
public class AirPurifierConfigAutoPref {
|
||||
@SerializedName("type")
|
||||
public String autoType;
|
||||
|
||||
@SerializedName("room_size")
|
||||
public int roomSize;
|
||||
}
|
||||
}
|
||||
|
||||
@SerializedName("extension")
|
||||
public AirPurifierExtension extension;
|
||||
|
||||
public class AirPurifierExtension {
|
||||
@SerializedName("schedule_count")
|
||||
public int scheduleCount;
|
||||
|
||||
@SerializedName("timer_remain")
|
||||
public int timerRemain;
|
||||
}
|
||||
|
||||
@SerializedName("device_error_code")
|
||||
public int deviceErrorCode;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.dto.responses.v1;
|
||||
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VeSyncResponse;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncV1AirPurifierDeviceDetailsResponse} is a Java class used as a DTO to hold the Vesync's V1 API's
|
||||
* common response
|
||||
* data, in regards to a Air Purifier device.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
public class VeSyncV1AirPurifierDeviceDetailsResponse extends VeSyncResponse {
|
||||
|
||||
@SerializedName("screenStatus")
|
||||
public String screenStatus;
|
||||
|
||||
public String getScreenStatus() {
|
||||
return screenStatus;
|
||||
}
|
||||
|
||||
@SerializedName("airQuality")
|
||||
public int airQuality;
|
||||
|
||||
public int getAirQuality() {
|
||||
return airQuality;
|
||||
}
|
||||
|
||||
@SerializedName("level")
|
||||
public int level;
|
||||
|
||||
public int getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
@SerializedName("mode")
|
||||
public String mode;
|
||||
|
||||
public String getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
@SerializedName("deviceName")
|
||||
public String deviceName;
|
||||
|
||||
public String getDeviceName() {
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
@SerializedName("currentFirmVersion")
|
||||
public String currentFirmVersion;
|
||||
|
||||
public String getCurrentFirmVersion() {
|
||||
return currentFirmVersion;
|
||||
}
|
||||
|
||||
@SerializedName("childLock")
|
||||
public String childLock;
|
||||
|
||||
public String getChildLock() {
|
||||
return childLock;
|
||||
}
|
||||
|
||||
@SerializedName("deviceStatus")
|
||||
public String deviceStatus;
|
||||
|
||||
public String getDeviceStatus() {
|
||||
return deviceStatus;
|
||||
}
|
||||
|
||||
@SerializedName("deviceImg")
|
||||
public String deviceImgUrl;
|
||||
|
||||
public String getDeviceImgUrl() {
|
||||
return deviceImgUrl;
|
||||
}
|
||||
|
||||
@SerializedName("connectionStatus")
|
||||
public String connectionStatus;
|
||||
|
||||
public String getConnectionStatus() {
|
||||
return connectionStatus;
|
||||
}
|
||||
|
||||
public boolean isDeviceOnline() {
|
||||
return "online".equals(connectionStatus);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.exceptions;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link AuthenticationException} is thrown if the authentication/login process is unsuccessful.
|
||||
*
|
||||
* @author David Godyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AuthenticationException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -7786425895604150557L;
|
||||
|
||||
public AuthenticationException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public AuthenticationException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AuthenticationException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public AuthenticationException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.exceptions;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link DeviceUnknownException} is thrown if the device information could not be located for the address in
|
||||
* relation
|
||||
* to the API.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DeviceUnknownException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -7786425642285150557L;
|
||||
|
||||
public DeviceUnknownException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public DeviceUnknownException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DeviceUnknownException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public DeviceUnknownException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,508 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.handlers;
|
||||
|
||||
import static org.openhab.binding.vesync.internal.VeSyncConstants.*;
|
||||
import static org.openhab.binding.vesync.internal.dto.requests.VeSyncProtocolConstants.V2_BYPASS_ENDPOINT;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.vesync.internal.VeSyncBridgeConfiguration;
|
||||
import org.openhab.binding.vesync.internal.VeSyncDeviceConfiguration;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VeSyncAuthenticatedRequest;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VeSyncRequestManagedDeviceBypassV2;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VeSyncManagedDeviceBase;
|
||||
import org.openhab.binding.vesync.internal.exceptions.AuthenticationException;
|
||||
import org.openhab.binding.vesync.internal.exceptions.DeviceUnknownException;
|
||||
import org.openhab.core.cache.ExpiringCache;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncBaseDeviceHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class VeSyncBaseDeviceHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VeSyncBaseDeviceHandler.class);
|
||||
|
||||
private static final String MARKER_INVALID_DEVICE_KEY = "---INVALID---";
|
||||
|
||||
@NotNull
|
||||
protected String deviceLookupKey = MARKER_INVALID_DEVICE_KEY;
|
||||
|
||||
private static final int CACHE_TIMEOUT_SECOND = 5;
|
||||
|
||||
private int activePollRate = -2; // -1 is used to deactivate the poll, so default to a different value
|
||||
|
||||
private @Nullable ScheduledFuture<?> backgroundPollingScheduler;
|
||||
private final Object pollConfigLock = new Object();
|
||||
|
||||
protected @Nullable VeSyncClient veSyncClient;
|
||||
|
||||
private volatile long latestReadBackMillis = 0;
|
||||
|
||||
@Nullable
|
||||
ScheduledFuture<?> initialPollingTask = null;
|
||||
|
||||
@Nullable
|
||||
ScheduledFuture<?> readbackPollTask = null;
|
||||
|
||||
public VeSyncBaseDeviceHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
protected @Nullable Channel findChannelById(final String channelGroupId) {
|
||||
return getThing().getChannel(channelGroupId);
|
||||
}
|
||||
|
||||
protected ExpiringCache<String> lastPollResultCache = new ExpiringCache<>(Duration.ofSeconds(CACHE_TIMEOUT_SECOND),
|
||||
VeSyncBaseDeviceHandler::expireCacheContents);
|
||||
|
||||
private static @Nullable String expireCacheContents() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
super.channelLinked(channelUID);
|
||||
|
||||
scheduler.execute(this::pollForUpdate);
|
||||
}
|
||||
|
||||
protected void setBackgroundPollInterval(final int seconds) {
|
||||
if (activePollRate == seconds) {
|
||||
return;
|
||||
}
|
||||
logger.debug("Reconfiguring devices background polling to {} seconds", seconds);
|
||||
|
||||
synchronized (pollConfigLock) {
|
||||
final ScheduledFuture<?> job = backgroundPollingScheduler;
|
||||
|
||||
// Cancel the current scan's and re-schedule as required
|
||||
if (job != null && !job.isCancelled()) {
|
||||
job.cancel(true);
|
||||
backgroundPollingScheduler = null;
|
||||
}
|
||||
if (seconds > 0) {
|
||||
logger.trace("Device data is polling every {} seconds", seconds);
|
||||
backgroundPollingScheduler = scheduler.scheduleWithFixedDelay(this::pollForUpdate, seconds, seconds,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
activePollRate = seconds;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean requiresMetaDataFrequentUpdates() {
|
||||
return (MARKER_INVALID_DEVICE_KEY.equals(deviceLookupKey));
|
||||
}
|
||||
|
||||
private @Nullable BridgeHandler getBridgeHandler() {
|
||||
Bridge bridgeRef = getBridge();
|
||||
if (bridgeRef == null) {
|
||||
return null;
|
||||
} else {
|
||||
return bridgeRef.getHandler();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isDeviceOnline() {
|
||||
BridgeHandler bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler != null && bridgeHandler instanceof VeSyncBridgeHandler) {
|
||||
VeSyncBridgeHandler vesyncBridgeHandler = (VeSyncBridgeHandler) bridgeHandler;
|
||||
@Nullable
|
||||
VeSyncManagedDeviceBase metadata = vesyncBridgeHandler.api.getMacLookupMap().get(deviceLookupKey);
|
||||
|
||||
if (metadata == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ("online".equals(metadata.connectionStatus));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void updateDeviceMetaData() {
|
||||
Map<String, String> newProps = null;
|
||||
|
||||
BridgeHandler bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler != null && bridgeHandler instanceof VeSyncBridgeHandler) {
|
||||
VeSyncBridgeHandler vesyncBridgeHandler = (VeSyncBridgeHandler) bridgeHandler;
|
||||
@Nullable
|
||||
VeSyncManagedDeviceBase metadata = vesyncBridgeHandler.api.getMacLookupMap().get(deviceLookupKey);
|
||||
|
||||
if (metadata == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
newProps = getMetadataProperities(metadata);
|
||||
|
||||
// Refresh the device -> protocol mapping
|
||||
deviceLookupKey = getValidatedIdString();
|
||||
|
||||
if ("online".equals(metadata.connectionStatus)) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else if ("offline".equals(metadata.connectionStatus)) {
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
if (newProps != null && !newProps.isEmpty()) {
|
||||
this.updateProperties(newProps);
|
||||
removeChannels();
|
||||
if (!isDeviceSupported()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Device Model or Type not supported by this thing");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this in classes that extend this, to
|
||||
*/
|
||||
protected void customiseChannels() {
|
||||
}
|
||||
|
||||
protected String[] getChannelsToRemove() {
|
||||
return new String[] {};
|
||||
}
|
||||
|
||||
private void removeChannels() {
|
||||
final String[] channelsToRemove = getChannelsToRemove();
|
||||
final List<Channel> channelsToBeRemoved = new ArrayList<>();
|
||||
for (String name : channelsToRemove) {
|
||||
Channel ch = getThing().getChannel(name);
|
||||
if (ch != null) {
|
||||
channelsToBeRemoved.add(ch);
|
||||
}
|
||||
}
|
||||
|
||||
final ThingBuilder builder = editThing().withoutChannels(channelsToBeRemoved);
|
||||
updateThing(builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the common properties for all devices, from the given meta-data of a device.
|
||||
*
|
||||
* @param metadata - the meta-data of a device
|
||||
* @return - Map of common props
|
||||
*/
|
||||
public Map<String, String> getMetadataProperities(final @Nullable VeSyncManagedDeviceBase metadata) {
|
||||
if (metadata == null) {
|
||||
return Map.of();
|
||||
}
|
||||
final Map<String, String> newProps = new HashMap<>(4);
|
||||
newProps.put(DEVICE_PROP_DEVICE_MAC_ID, metadata.getMacId());
|
||||
newProps.put(DEVICE_PROP_DEVICE_NAME, metadata.getDeviceName());
|
||||
newProps.put(DEVICE_PROP_DEVICE_TYPE, metadata.getDeviceType());
|
||||
newProps.put(DEVICE_PROP_DEVICE_UUID, metadata.getUuid());
|
||||
return newProps;
|
||||
}
|
||||
|
||||
protected synchronized @Nullable VeSyncClient getVeSyncClient() {
|
||||
if (veSyncClient == null) {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
return null;
|
||||
}
|
||||
ThingHandler handler = bridge.getHandler();
|
||||
if (handler instanceof VeSyncClient) {
|
||||
veSyncClient = (VeSyncClient) handler;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return veSyncClient;
|
||||
}
|
||||
|
||||
protected void requestBridgeFreqScanMetadataIfReq() {
|
||||
if (requiresMetaDataFrequentUpdates()) {
|
||||
BridgeHandler bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler != null && bridgeHandler instanceof VeSyncBridgeHandler) {
|
||||
VeSyncBridgeHandler vesyncBridgeHandler = (VeSyncBridgeHandler) bridgeHandler;
|
||||
vesyncBridgeHandler.checkIfIncreaseScanRateRequired();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getValidatedIdString() {
|
||||
final VeSyncDeviceConfiguration config = getConfigAs(VeSyncDeviceConfiguration.class);
|
||||
|
||||
BridgeHandler bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler != null && bridgeHandler instanceof VeSyncBridgeHandler) {
|
||||
VeSyncBridgeHandler vesyncBridgeHandler = (VeSyncBridgeHandler) bridgeHandler;
|
||||
|
||||
final String configMac = config.macId;
|
||||
|
||||
// Try to use the mac directly
|
||||
if (configMac != null) {
|
||||
logger.debug("Searching for device mac id : {}", configMac);
|
||||
@Nullable
|
||||
VeSyncManagedDeviceBase metadata = vesyncBridgeHandler.api.getMacLookupMap()
|
||||
.get(configMac.toLowerCase());
|
||||
|
||||
if (metadata != null && metadata.macId != null) {
|
||||
return metadata.macId;
|
||||
}
|
||||
}
|
||||
|
||||
final String deviceName = config.deviceName;
|
||||
|
||||
// Check if the device name can be matched to a single device
|
||||
if (deviceName != null) {
|
||||
final String[] matchedMacIds = vesyncBridgeHandler.api.getMacLookupMap().values().stream()
|
||||
.filter(x -> deviceName.equals(x.deviceName)).map(x -> x.macId).toArray(String[]::new);
|
||||
|
||||
for (String val : matchedMacIds) {
|
||||
logger.debug("Found MAC match on name with : {}", val);
|
||||
}
|
||||
|
||||
if (matchedMacIds.length != 1) {
|
||||
return MARKER_INVALID_DEVICE_KEY;
|
||||
}
|
||||
|
||||
if (vesyncBridgeHandler.api.getMacLookupMap().get(matchedMacIds[0]) != null) {
|
||||
return matchedMacIds[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MARKER_INVALID_DEVICE_KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
intializeDeviceForUse();
|
||||
}
|
||||
|
||||
private void intializeDeviceForUse() {
|
||||
// Sanity check basic setup
|
||||
final VeSyncBridgeHandler bridge = (VeSyncBridgeHandler) getBridgeHandler();
|
||||
if (bridge == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, "Missing bridge for API link");
|
||||
return;
|
||||
} else {
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
}
|
||||
|
||||
deviceLookupKey = getValidatedIdString();
|
||||
|
||||
// Populate device props - this is required for polling, to cross-check the device model.
|
||||
updateDeviceMetaData();
|
||||
|
||||
// If the base device class marks it as offline there is an issue that will prevent normal operation
|
||||
if (getThing().getStatus().equals(ThingStatus.OFFLINE)) {
|
||||
return;
|
||||
}
|
||||
// This will force the bridge to push the configuration parameters for polling to the handler
|
||||
bridge.updateThing(this);
|
||||
|
||||
// Give the bridge time to build the datamaps of the devices
|
||||
scheduleInitialPoll();
|
||||
}
|
||||
|
||||
private void scheduleInitialPoll() {
|
||||
cancelInitialPoll(false);
|
||||
initialPollingTask = scheduler.schedule(this::pollForUpdate, 10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void cancelInitialPoll(final boolean interruptAllowed) {
|
||||
final ScheduledFuture<?> pollJob = initialPollingTask;
|
||||
if (pollJob != null && !pollJob.isCancelled()) {
|
||||
pollJob.cancel(interruptAllowed);
|
||||
initialPollingTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelReadbackPoll(final boolean interruptAllowed) {
|
||||
final ScheduledFuture<?> pollJob = readbackPollTask;
|
||||
if (pollJob != null && !pollJob.isCancelled()) {
|
||||
pollJob.cancel(interruptAllowed);
|
||||
readbackPollTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
cancelReadbackPoll(true);
|
||||
cancelInitialPoll(true);
|
||||
}
|
||||
|
||||
public void pollForUpdate() {
|
||||
pollForDeviceData(lastPollResultCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be implemented by subclasses to provide the implementation for polling the specific
|
||||
* data for the type the class is responsible for. (Excluding meta data).
|
||||
*
|
||||
* @param cachedResponse - An Expiring cache that can be utilised to store the responses, to prevent poll bursts by
|
||||
* coalescing the requests.
|
||||
*/
|
||||
protected abstract void pollForDeviceData(final ExpiringCache<String> cachedResponse);
|
||||
|
||||
/**
|
||||
* Send a BypassV2 command to the device. The body of the response is returned, a poll is done if the request
|
||||
* should have been dispatched.
|
||||
*
|
||||
* @param method - the V2 bypass method
|
||||
* @param payload - The payload to send in within the V2 bypass command
|
||||
* @return - The body of the response, or EMPTY_STRING if the command could not be issued.
|
||||
*/
|
||||
protected final String sendV2BypassControlCommand(final String method,
|
||||
final VeSyncRequestManagedDeviceBypassV2.EmptyPayload payload) {
|
||||
return sendV2BypassControlCommand(method, payload, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a BypassV2 command to the device. The body of the response is returned.
|
||||
*
|
||||
* @param method - the V2 bypass method
|
||||
* @param payload - The payload to send in within the V2 bypass command
|
||||
* @param readbackDevice - if set to true after the command has been issued, whether a poll of the devices data
|
||||
* should be run.
|
||||
* @return - The body of the response, or EMPTY_STRING if the command could not be issued.
|
||||
*/
|
||||
protected final String sendV2BypassControlCommand(final String method,
|
||||
final VeSyncRequestManagedDeviceBypassV2.EmptyPayload payload, final boolean readbackDevice) {
|
||||
final String result = sendV2BypassCommand(method, payload);
|
||||
if (!result.equals(EMPTY_STRING) && readbackDevice) {
|
||||
performReadbackPoll();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public final String sendV1Command(final String method, final String url, final VeSyncAuthenticatedRequest request) {
|
||||
if (ThingStatus.OFFLINE.equals(this.thing.getStatus())) {
|
||||
logger.debug("Command blocked as device is offline");
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
|
||||
try {
|
||||
if (MARKER_INVALID_DEVICE_KEY.equals(deviceLookupKey)) {
|
||||
deviceLookupKey = getValidatedIdString();
|
||||
}
|
||||
VeSyncClient client = getVeSyncClient();
|
||||
if (client != null) {
|
||||
return client.reqV2Authorized(url, deviceLookupKey, request);
|
||||
} else {
|
||||
throw new DeviceUnknownException("Missing client");
|
||||
}
|
||||
} catch (AuthenticationException e) {
|
||||
logger.debug("Auth exception {}", e.getMessage());
|
||||
return EMPTY_STRING;
|
||||
} catch (final DeviceUnknownException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Check configuration details - " + e.getMessage());
|
||||
// In case the name is updated server side - request the scan rate is increased
|
||||
requestBridgeFreqScanMetadataIfReq();
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a BypassV2 command to the device. The body of the response is returned.
|
||||
*
|
||||
* @param method - the V2 bypass method
|
||||
* @param payload - The payload to send in within the V2 bypass command
|
||||
* @return - The body of the response, or EMPTY_STRING if the command could not be issued.
|
||||
*/
|
||||
protected final String sendV2BypassCommand(final String method,
|
||||
final VeSyncRequestManagedDeviceBypassV2.EmptyPayload payload) {
|
||||
if (ThingStatus.OFFLINE.equals(this.thing.getStatus())) {
|
||||
logger.debug("Command blocked as device is offline");
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
|
||||
VeSyncRequestManagedDeviceBypassV2 readReq = new VeSyncRequestManagedDeviceBypassV2();
|
||||
readReq.payload.method = method;
|
||||
readReq.payload.data = payload;
|
||||
|
||||
try {
|
||||
if (MARKER_INVALID_DEVICE_KEY.equals(deviceLookupKey)) {
|
||||
deviceLookupKey = getValidatedIdString();
|
||||
}
|
||||
VeSyncClient client = getVeSyncClient();
|
||||
if (client != null) {
|
||||
return client.reqV2Authorized(V2_BYPASS_ENDPOINT, deviceLookupKey, readReq);
|
||||
} else {
|
||||
throw new DeviceUnknownException("Missing client");
|
||||
}
|
||||
} catch (AuthenticationException e) {
|
||||
logger.debug("Auth exception {}", e.getMessage());
|
||||
return EMPTY_STRING;
|
||||
} catch (final DeviceUnknownException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Check configuration details - " + e.getMessage());
|
||||
// In case the name is updated server side - request the scan rate is increased
|
||||
requestBridgeFreqScanMetadataIfReq();
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
}
|
||||
|
||||
// Given several changes may be done at the same time, or in close proximity, delay the read-back to catch
|
||||
// multiple read-back's, so a single update can handle them.
|
||||
public void performReadbackPoll() {
|
||||
final long requestSystemMillis = System.currentTimeMillis();
|
||||
latestReadBackMillis = requestSystemMillis;
|
||||
cancelReadbackPoll(false);
|
||||
readbackPollTask = scheduler.schedule(() -> {
|
||||
// This is a historical poll, ignore it
|
||||
if (requestSystemMillis != latestReadBackMillis) {
|
||||
logger.trace("Poll read-back cancelled, another later one is scheduled to happen");
|
||||
return;
|
||||
}
|
||||
logger.trace("Read-back poll executing");
|
||||
// Read-backs should never use the cached data - but may provide it for poll's that coincide with
|
||||
// the caches alive duration.
|
||||
lastPollResultCache.invalidateValue();
|
||||
pollForUpdate();
|
||||
}, 1L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void updateBridgeBasedPolls(VeSyncBridgeConfiguration config) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses should implement this method, and return true if the device is a model it can support
|
||||
* interoperability with. If it cannot be determind to be a mode
|
||||
*
|
||||
* @return - true if the device is supported, false if the device isn't. E.g. Unknown model id in meta-data would
|
||||
* return false.
|
||||
*/
|
||||
protected abstract boolean isDeviceSupported();
|
||||
}
|
@ -0,0 +1,241 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.handlers;
|
||||
|
||||
import static org.openhab.binding.vesync.internal.VeSyncConstants.*;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.vesync.internal.VeSyncBridgeConfiguration;
|
||||
import org.openhab.binding.vesync.internal.api.IHttpClientProvider;
|
||||
import org.openhab.binding.vesync.internal.api.VeSyncV2ApiHelper;
|
||||
import org.openhab.binding.vesync.internal.discovery.DeviceMetaDataUpdatedHandler;
|
||||
import org.openhab.binding.vesync.internal.discovery.VeSyncDiscoveryService;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VeSyncAuthenticatedRequest;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VeSyncManagedDeviceBase;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VeSyncUserSession;
|
||||
import org.openhab.binding.vesync.internal.exceptions.AuthenticationException;
|
||||
import org.openhab.binding.vesync.internal.exceptions.DeviceUnknownException;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncBridgeHandler} is responsible for handling the bridge things created to use the VeSync
|
||||
* API. This way, the user credentials may be entered only once.
|
||||
*
|
||||
* @author David Goodyear - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VeSyncBridgeHandler extends BaseBridgeHandler implements VeSyncClient {
|
||||
|
||||
private static final int DEFAULT_DEVICE_SCAN_INTERVAL = 600;
|
||||
private static final int DEFAULT_DEVICE_SCAN_RECOVERY_INTERVAL = 60;
|
||||
private static final int DEFAULT_DEVICE_SCAN_DISABLED = -1;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VeSyncBridgeHandler.class);
|
||||
|
||||
private @Nullable ScheduledFuture<?> backgroundDiscoveryPollingJob;
|
||||
|
||||
protected final VeSyncV2ApiHelper api = new VeSyncV2ApiHelper();
|
||||
private IHttpClientProvider httpClientProvider;
|
||||
|
||||
private volatile int backgroundScanTime = -1;
|
||||
private final Object scanConfigLock = new Object();
|
||||
|
||||
public VeSyncBridgeHandler(Bridge bridge, @NotNull IHttpClientProvider httpClientProvider) {
|
||||
super(bridge);
|
||||
this.httpClientProvider = httpClientProvider;
|
||||
}
|
||||
|
||||
public ThingUID getUID() {
|
||||
return thing.getUID();
|
||||
}
|
||||
|
||||
protected void checkIfIncreaseScanRateRequired() {
|
||||
logger.trace("Checking if increased background scanning for new devices / base information is required");
|
||||
boolean frequentScanReq = false;
|
||||
for (Thing th : getThing().getThings()) {
|
||||
ThingHandler handler = th.getHandler();
|
||||
if (handler instanceof VeSyncBaseDeviceHandler) {
|
||||
if (((VeSyncBaseDeviceHandler) handler).requiresMetaDataFrequentUpdates()) {
|
||||
frequentScanReq = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!frequentScanReq
|
||||
&& api.getMacLookupMap().values().stream().anyMatch(x -> "offline".equals(x.connectionStatus))) {
|
||||
frequentScanReq = true;
|
||||
}
|
||||
|
||||
if (frequentScanReq) {
|
||||
setBackgroundScanInterval(DEFAULT_DEVICE_SCAN_RECOVERY_INTERVAL);
|
||||
} else {
|
||||
setBackgroundScanInterval(DEFAULT_DEVICE_SCAN_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setBackgroundScanInterval(final int seconds) {
|
||||
synchronized (scanConfigLock) {
|
||||
ScheduledFuture<?> job = backgroundDiscoveryPollingJob;
|
||||
if (backgroundScanTime != seconds) {
|
||||
if (seconds > 0) {
|
||||
logger.trace("Scheduling background scanning for new devices / base information every {} seconds",
|
||||
seconds);
|
||||
} else {
|
||||
logger.trace("Disabling background scanning for new devices / base information");
|
||||
}
|
||||
// Cancel the current scan's and re-schedule as required
|
||||
if (job != null && !job.isCancelled()) {
|
||||
job.cancel(true);
|
||||
backgroundDiscoveryPollingJob = null;
|
||||
}
|
||||
if (seconds > 0) {
|
||||
backgroundDiscoveryPollingJob = scheduler.scheduleWithFixedDelay(
|
||||
this::runDeviceScanSequenceNoAuthErrors, seconds, seconds, TimeUnit.SECONDS);
|
||||
}
|
||||
backgroundScanTime = seconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void registerMetaDataUpdatedHandler(DeviceMetaDataUpdatedHandler dmduh) {
|
||||
handlers.add(dmduh);
|
||||
}
|
||||
|
||||
public void unregisterMetaDataUpdatedHandler(DeviceMetaDataUpdatedHandler dmduh) {
|
||||
handlers.remove(dmduh);
|
||||
}
|
||||
|
||||
private final CopyOnWriteArrayList<DeviceMetaDataUpdatedHandler> handlers = new CopyOnWriteArrayList<>();
|
||||
|
||||
public void runDeviceScanSequenceNoAuthErrors() {
|
||||
try {
|
||||
runDeviceScanSequence();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (AuthenticationException ae) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Check login credentials");
|
||||
}
|
||||
}
|
||||
|
||||
public void runDeviceScanSequence() throws AuthenticationException {
|
||||
logger.trace("Scanning for new devices / base information now");
|
||||
api.discoverDevices();
|
||||
handlers.forEach(x -> x.handleMetadataRetrieved(this));
|
||||
checkIfIncreaseScanRateRequired();
|
||||
|
||||
this.updateThings();
|
||||
}
|
||||
|
||||
public java.util.stream.Stream<@NotNull VeSyncManagedDeviceBase> getAirPurifiersMetadata() {
|
||||
return api.getMacLookupMap().values().stream()
|
||||
.filter(x -> VeSyncDeviceAirPurifierHandler.SUPPORTED_DEVICE_TYPES.contains(x.deviceType));
|
||||
}
|
||||
|
||||
public java.util.stream.Stream<@NotNull VeSyncManagedDeviceBase> getAirHumidifiersMetadata() {
|
||||
return api.getMacLookupMap().values().stream()
|
||||
.filter(x -> VeSyncDeviceAirHumidifierHandler.SUPPORTED_DEVICE_TYPES.contains(x.deviceType));
|
||||
}
|
||||
|
||||
protected void updateThings() {
|
||||
final VeSyncBridgeConfiguration config = getConfigAs(VeSyncBridgeConfiguration.class);
|
||||
getThing().getThings().forEach((th) -> updateThing(config, th.getHandler()));
|
||||
}
|
||||
|
||||
public void updateThing(ThingHandler handler) {
|
||||
final VeSyncBridgeConfiguration config = getConfigAs(VeSyncBridgeConfiguration.class);
|
||||
updateThing(config, handler);
|
||||
}
|
||||
|
||||
private void updateThing(VeSyncBridgeConfiguration config, @Nullable ThingHandler handler) {
|
||||
if (handler instanceof VeSyncBaseDeviceHandler) {
|
||||
((VeSyncBaseDeviceHandler) handler).updateDeviceMetaData();
|
||||
((VeSyncBaseDeviceHandler) handler).updateBridgeBasedPolls(config);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(VeSyncDiscoveryService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
api.setHttpClient(httpClientProvider.getHttpClient());
|
||||
|
||||
VeSyncBridgeConfiguration config = getConfigAs(VeSyncBridgeConfiguration.class);
|
||||
|
||||
scheduler.submit(() -> {
|
||||
final String passwordMd5 = VeSyncV2ApiHelper.calculateMd5(config.password);
|
||||
|
||||
try {
|
||||
api.login(config.username, passwordMd5, "Europe/London");
|
||||
api.updateBridgeData(this);
|
||||
runDeviceScanSequence();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (final AuthenticationException ae) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Check login credentials");
|
||||
// The background scan will keep trying to authenticate in case the users credentials are updated on the
|
||||
// veSync servers,
|
||||
// to match the binding's configuration.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
setBackgroundScanInterval(DEFAULT_DEVICE_SCAN_DISABLED);
|
||||
api.setHttpClient(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.warn("Handling command for VeSync bridge handler.");
|
||||
}
|
||||
|
||||
public void handleNewUserSession(final @Nullable VeSyncUserSession userSessionData) {
|
||||
final Map<String, String> newProps = new HashMap<>();
|
||||
if (userSessionData != null) {
|
||||
newProps.put(DEVICE_PROP_BRIDGE_REG_TS, userSessionData.registerTime);
|
||||
newProps.put(DEVICE_PROP_BRIDGE_COUNTRY_CODE, userSessionData.countryCode);
|
||||
newProps.put(DEVICE_PROP_BRIDGE_ACCEPT_LANG, userSessionData.acceptLanguage);
|
||||
}
|
||||
this.updateProperties(newProps);
|
||||
}
|
||||
|
||||
public String reqV2Authorized(final String url, final String macId, final VeSyncAuthenticatedRequest requestData)
|
||||
throws AuthenticationException, DeviceUnknownException {
|
||||
return api.reqV2Authorized(url, macId, requestData);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.handlers;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VeSyncAuthenticatedRequest;
|
||||
import org.openhab.binding.vesync.internal.exceptions.AuthenticationException;
|
||||
import org.openhab.binding.vesync.internal.exceptions.DeviceUnknownException;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncClient} is TBC.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface VeSyncClient {
|
||||
String reqV2Authorized(final String url, final String macId, final VeSyncAuthenticatedRequest requestData)
|
||||
throws AuthenticationException, DeviceUnknownException;
|
||||
}
|
@ -0,0 +1,349 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.handlers;
|
||||
|
||||
import static org.openhab.binding.vesync.internal.VeSyncConstants.*;
|
||||
import static org.openhab.binding.vesync.internal.dto.requests.VeSyncProtocolConstants.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.vesync.internal.VeSyncBridgeConfiguration;
|
||||
import org.openhab.binding.vesync.internal.VeSyncConstants;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VeSyncRequestManagedDeviceBypassV2;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VeSyncV2BypassHumidifierStatus;
|
||||
import org.openhab.core.cache.ExpiringCache;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncDeviceAirHumidifierHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VeSyncDeviceAirHumidifierHandler extends VeSyncBaseDeviceHandler {
|
||||
|
||||
public static final int DEFAULT_AIR_PURIFIER_POLL_RATE = 120;
|
||||
// "Device Type" values
|
||||
public static final String DEV_TYPE_DUAL_200S = "Dual200S";
|
||||
public static final String DEV_TYPE_CLASSIC_200S = "Classic200S";
|
||||
public static final String DEV_TYPE_CORE_301S = "LUH-D301S-WEU";
|
||||
public static final String DEV_TYPE_CLASSIC_300S = "Classic300S";
|
||||
public static final String DEV_TYPE_600S = "LUH-A602S-WUS";
|
||||
public static final String DEV_TYPE_600S_EU = "LUH-A602S-WEU";
|
||||
|
||||
private static final List<String> CLASSIC_300S_600S_MODES = Arrays.asList(MODE_AUTO, MODE_MANUAL, MODE_SLEEP);
|
||||
private static final List<String> CLASSIC_300S_NIGHT_LIGHT_MODES = Arrays.asList(MODE_ON, MODE_DIM, MODE_OFF);
|
||||
|
||||
public static final List<String> SUPPORTED_DEVICE_TYPES = List.of(DEV_TYPE_DUAL_200S, DEV_TYPE_CLASSIC_200S,
|
||||
DEV_TYPE_CLASSIC_300S, DEV_TYPE_CORE_301S, DEV_TYPE_600S, DEV_TYPE_600S_EU);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VeSyncDeviceAirHumidifierHandler.class);
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_HUMIDIFIER);
|
||||
|
||||
private final Object pollLock = new Object();
|
||||
|
||||
public VeSyncDeviceAirHumidifierHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getChannelsToRemove() {
|
||||
String[] toRemove = new String[] {};
|
||||
final String deviceType = getThing().getProperties().get(DEVICE_PROP_DEVICE_TYPE);
|
||||
if (deviceType != null) {
|
||||
switch (deviceType) {
|
||||
case DEV_TYPE_CLASSIC_300S:
|
||||
toRemove = new String[] { DEVICE_CHANNEL_WARM_ENABLED, DEVICE_CHANNEL_WARM_LEVEL };
|
||||
break;
|
||||
case DEV_TYPE_DUAL_200S:
|
||||
case DEV_TYPE_CLASSIC_200S:
|
||||
case DEV_TYPE_CORE_301S:
|
||||
toRemove = new String[] { DEVICE_CHANNEL_WARM_ENABLED, DEVICE_CHANNEL_WARM_LEVEL,
|
||||
DEVICE_CHANNEL_AF_NIGHT_LIGHT };
|
||||
break;
|
||||
case DEV_TYPE_600S:
|
||||
case DEV_TYPE_600S_EU:
|
||||
toRemove = new String[] { DEVICE_CHANNEL_AF_NIGHT_LIGHT };
|
||||
break;
|
||||
}
|
||||
}
|
||||
return toRemove;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
customiseChannels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBridgeBasedPolls(final VeSyncBridgeConfiguration config) {
|
||||
Integer pollRate = config.airPurifierPollInterval;
|
||||
if (pollRate == null) {
|
||||
pollRate = DEFAULT_AIR_PURIFIER_POLL_RATE;
|
||||
}
|
||||
if (ThingStatus.OFFLINE.equals(getThing().getStatus())) {
|
||||
setBackgroundPollInterval(-1);
|
||||
} else {
|
||||
setBackgroundPollInterval(pollRate);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
this.setBackgroundPollInterval(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDeviceSupported() {
|
||||
final String deviceType = getThing().getProperties().get(DEVICE_PROP_DEVICE_TYPE);
|
||||
if (deviceType == null) {
|
||||
return false;
|
||||
}
|
||||
return SUPPORTED_DEVICE_TYPES.contains(deviceType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final ChannelUID channelUID, final Command command) {
|
||||
final String deviceType = getThing().getProperties().get(DEVICE_PROP_DEVICE_TYPE);
|
||||
if (deviceType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler.submit(() -> {
|
||||
|
||||
if (command instanceof OnOffType) {
|
||||
switch (channelUID.getId()) {
|
||||
case DEVICE_CHANNEL_ENABLED:
|
||||
sendV2BypassControlCommand(DEVICE_SET_SWITCH,
|
||||
new VeSyncRequestManagedDeviceBypassV2.SetSwitchPayload(command.equals(OnOffType.ON),
|
||||
0));
|
||||
break;
|
||||
case DEVICE_CHANNEL_DISPLAY_ENABLED:
|
||||
sendV2BypassControlCommand(DEVICE_SET_DISPLAY,
|
||||
new VeSyncRequestManagedDeviceBypassV2.SetState(command.equals(OnOffType.ON)));
|
||||
break;
|
||||
case DEVICE_CHANNEL_STOP_AT_TARGET:
|
||||
sendV2BypassControlCommand(DEVICE_SET_AUTOMATIC_STOP,
|
||||
new VeSyncRequestManagedDeviceBypassV2.EnabledPayload(command.equals(OnOffType.ON)));
|
||||
break;
|
||||
case DEVICE_CHANNEL_WARM_ENABLED:
|
||||
logger.warn("Warm mode API is unknown in order to send the command");
|
||||
break;
|
||||
}
|
||||
} else if (command instanceof QuantityType) {
|
||||
switch (channelUID.getId()) {
|
||||
case DEVICE_CHANNEL_CONFIG_TARGET_HUMIDITY:
|
||||
int targetHumidity = ((QuantityType<?>) command).intValue();
|
||||
if (targetHumidity < 30) {
|
||||
logger.warn("Target Humidity less than 30 - adjusting to 30 as the valid API value");
|
||||
targetHumidity = 30;
|
||||
} else if (targetHumidity > 80) {
|
||||
logger.warn("Target Humidity greater than 80 - adjusting to 80 as the valid API value");
|
||||
targetHumidity = 80;
|
||||
}
|
||||
|
||||
sendV2BypassControlCommand(DEVICE_SET_HUMIDITY_MODE,
|
||||
new VeSyncRequestManagedDeviceBypassV2.SetMode(MODE_AUTO), false);
|
||||
|
||||
sendV2BypassControlCommand(DEVICE_SET_TARGET_HUMIDITY_MODE,
|
||||
new VeSyncRequestManagedDeviceBypassV2.SetTargetHumidity(targetHumidity));
|
||||
break;
|
||||
case DEVICE_CHANNEL_MIST_LEVEL:
|
||||
int targetMistLevel = ((QuantityType<?>) command).intValue();
|
||||
// If more devices have this the hope is it's those with the prefix LUH so the check can
|
||||
// be simplified, originally devices mapped 1/5/9 to 1/2/3.
|
||||
if (DEV_TYPE_CORE_301S.equals(deviceType)) {
|
||||
if (targetMistLevel < 1) {
|
||||
logger.warn("Target Mist Level less than 1 - adjusting to 1 as the valid API value");
|
||||
targetMistLevel = 1;
|
||||
} else if (targetMistLevel > 2) {
|
||||
logger.warn("Target Mist Level greater than 2 - adjusting to 2 as the valid API value");
|
||||
targetMistLevel = 2;
|
||||
}
|
||||
} else {
|
||||
if (targetMistLevel < 1) {
|
||||
logger.warn("Target Mist Level less than 1 - adjusting to 1 as the valid API value");
|
||||
targetMistLevel = 1;
|
||||
} else if (targetMistLevel > 3) {
|
||||
logger.warn("Target Mist Level greater than 3 - adjusting to 3 as the valid API value");
|
||||
targetMistLevel = 3;
|
||||
}
|
||||
// Re-map to what appears to be bitwise encoding of the states
|
||||
switch (targetMistLevel) {
|
||||
case 1:
|
||||
targetMistLevel = 1;
|
||||
break;
|
||||
case 2:
|
||||
targetMistLevel = 5;
|
||||
break;
|
||||
case 3:
|
||||
targetMistLevel = 9;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sendV2BypassControlCommand(DEVICE_SET_HUMIDITY_MODE,
|
||||
new VeSyncRequestManagedDeviceBypassV2.SetMode(MODE_MANUAL), false);
|
||||
|
||||
sendV2BypassControlCommand(DEVICE_SET_VIRTUAL_LEVEL,
|
||||
new VeSyncRequestManagedDeviceBypassV2.SetLevelPayload(0, DEVICE_LEVEL_TYPE_MIST,
|
||||
targetMistLevel));
|
||||
break;
|
||||
case DEVICE_CHANNEL_WARM_LEVEL:
|
||||
logger.warn("Warm level API is unknown in order to send the command");
|
||||
break;
|
||||
}
|
||||
} else if (command instanceof StringType) {
|
||||
final String targetMode = command.toString().toLowerCase();
|
||||
switch (channelUID.getId()) {
|
||||
case DEVICE_CHANNEL_HUMIDIFIER_MODE:
|
||||
if (!CLASSIC_300S_600S_MODES.contains(targetMode)) {
|
||||
logger.warn(
|
||||
"Humidifier mode command for \"{}\" is not valid in the (Classic300S/600S) API possible options {}",
|
||||
command, String.join(",", CLASSIC_300S_NIGHT_LIGHT_MODES));
|
||||
return;
|
||||
}
|
||||
sendV2BypassControlCommand(DEVICE_SET_HUMIDITY_MODE,
|
||||
new VeSyncRequestManagedDeviceBypassV2.SetMode(targetMode));
|
||||
break;
|
||||
case DEVICE_CHANNEL_AF_NIGHT_LIGHT:
|
||||
if (!DEV_TYPE_CLASSIC_300S.equals(deviceType) && !DEV_TYPE_CORE_301S.equals(deviceType)) {
|
||||
logger.warn("Humidifier night light is not valid for your device ({}})", deviceType);
|
||||
return;
|
||||
}
|
||||
if (!CLASSIC_300S_NIGHT_LIGHT_MODES.contains(targetMode)) {
|
||||
logger.warn(
|
||||
"Humidifier night light mode command for \"{}\" is not valid in the (Classic300S) API possible options {}",
|
||||
command, String.join(",", CLASSIC_300S_NIGHT_LIGHT_MODES));
|
||||
return;
|
||||
}
|
||||
int targetValue;
|
||||
switch (targetMode) {
|
||||
case MODE_OFF:
|
||||
targetValue = 0;
|
||||
break;
|
||||
case MODE_DIM:
|
||||
targetValue = 50;
|
||||
break;
|
||||
case MODE_ON:
|
||||
targetValue = 100;
|
||||
break;
|
||||
default:
|
||||
return; // should never hit
|
||||
}
|
||||
sendV2BypassControlCommand(DEVICE_SET_NIGHT_LIGHT_BRIGHTNESS,
|
||||
new VeSyncRequestManagedDeviceBypassV2.SetNightLightBrightness(targetValue));
|
||||
}
|
||||
} else if (command instanceof RefreshType) {
|
||||
pollForUpdate();
|
||||
} else {
|
||||
logger.trace("UNKNOWN COMMAND: {} {}", command.getClass().toString(), channelUID);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void pollForDeviceData(final ExpiringCache<String> cachedResponse) {
|
||||
String response;
|
||||
VeSyncV2BypassHumidifierStatus humidifierStatus;
|
||||
synchronized (pollLock) {
|
||||
response = cachedResponse.getValue();
|
||||
boolean cachedDataUsed = response != null;
|
||||
if (response == null) {
|
||||
logger.trace("Requesting fresh response");
|
||||
response = sendV2BypassCommand(DEVICE_GET_HUMIDIFIER_STATUS,
|
||||
new VeSyncRequestManagedDeviceBypassV2.EmptyPayload());
|
||||
} else {
|
||||
logger.trace("Using cached response {}", response);
|
||||
}
|
||||
|
||||
if (response.equals(EMPTY_STRING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
humidifierStatus = VeSyncConstants.GSON.fromJson(response, VeSyncV2BypassHumidifierStatus.class);
|
||||
|
||||
if (humidifierStatus == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cachedDataUsed) {
|
||||
cachedResponse.putValue(response);
|
||||
}
|
||||
}
|
||||
|
||||
// Bail and update the status of the thing - it will be updated to online by the next search
|
||||
// that detects it is online.
|
||||
if (humidifierStatus.isMsgDeviceOffline()) {
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
return;
|
||||
} else if (humidifierStatus.isMsgSuccess()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
if (!"0".equals(humidifierStatus.result.getCode())) {
|
||||
logger.warn("Check correct Thing type has been set - API gave a unexpected response for an Air Humidifier");
|
||||
return;
|
||||
}
|
||||
|
||||
final String deviceType = getThing().getProperties().get(DEVICE_PROP_DEVICE_TYPE);
|
||||
|
||||
updateState(DEVICE_CHANNEL_ENABLED, OnOffType.from(humidifierStatus.result.result.enabled));
|
||||
updateState(DEVICE_CHANNEL_DISPLAY_ENABLED, OnOffType.from(humidifierStatus.result.result.display));
|
||||
updateState(DEVICE_CHANNEL_WATER_LACKS, OnOffType.from(humidifierStatus.result.result.waterLacks));
|
||||
updateState(DEVICE_CHANNEL_HUMIDITY_HIGH, OnOffType.from(humidifierStatus.result.result.humidityHigh));
|
||||
updateState(DEVICE_CHANNEL_WATER_TANK_LIFTED, OnOffType.from(humidifierStatus.result.result.waterTankLifted));
|
||||
updateState(DEVICE_CHANNEL_STOP_AT_TARGET,
|
||||
OnOffType.from(humidifierStatus.result.result.automaticStopReachTarget));
|
||||
updateState(DEVICE_CHANNEL_HUMIDITY,
|
||||
new QuantityType<>(humidifierStatus.result.result.humidity, Units.PERCENT));
|
||||
updateState(DEVICE_CHANNEL_MIST_LEVEL, new DecimalType(humidifierStatus.result.result.mistLevel));
|
||||
updateState(DEVICE_CHANNEL_HUMIDIFIER_MODE, new StringType(humidifierStatus.result.result.mode));
|
||||
|
||||
// Only the 300S supports nightlight currently of tested devices.
|
||||
if (DEV_TYPE_CLASSIC_300S.equals(deviceType) || DEV_TYPE_CORE_301S.equals(deviceType)) {
|
||||
// Map the numeric that only applies to the same modes as the Air Filter 300S series.
|
||||
if (humidifierStatus.result.result.nightLightBrightness == 0) {
|
||||
updateState(DEVICE_CHANNEL_AF_NIGHT_LIGHT, new StringType(MODE_OFF));
|
||||
} else if (humidifierStatus.result.result.nightLightBrightness == 100) {
|
||||
updateState(DEVICE_CHANNEL_AF_NIGHT_LIGHT, new StringType(MODE_ON));
|
||||
} else {
|
||||
updateState(DEVICE_CHANNEL_AF_NIGHT_LIGHT, new StringType(MODE_DIM));
|
||||
}
|
||||
} else if (DEV_TYPE_600S.equals(deviceType) || DEV_TYPE_600S_EU.equals(deviceType)) {
|
||||
updateState(DEVICE_CHANNEL_WARM_ENABLED, OnOffType.from(humidifierStatus.result.result.warnEnabled));
|
||||
updateState(DEVICE_CHANNEL_WARM_LEVEL, new DecimalType(humidifierStatus.result.result.warmLevel));
|
||||
}
|
||||
|
||||
updateState(DEVICE_CHANNEL_CONFIG_TARGET_HUMIDITY,
|
||||
new QuantityType<>(humidifierStatus.result.result.configuration.autoTargetHumidity, Units.PERCENT));
|
||||
}
|
||||
}
|
@ -0,0 +1,423 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.handlers;
|
||||
|
||||
import static org.openhab.binding.vesync.internal.VeSyncConstants.*;
|
||||
import static org.openhab.binding.vesync.internal.dto.requests.VeSyncProtocolConstants.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.vesync.internal.VeSyncBridgeConfiguration;
|
||||
import org.openhab.binding.vesync.internal.VeSyncConstants;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VeSyncRequestManagedDeviceBypassV2;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VeSyncRequestV1ManagedDeviceDetails;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VeSyncV2BypassPurifierStatus;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.v1.VeSyncV1AirPurifierDeviceDetailsResponse;
|
||||
import org.openhab.core.cache.ExpiringCache;
|
||||
import org.openhab.core.library.items.DateTimeItem;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncDeviceAirPurifierHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VeSyncDeviceAirPurifierHandler extends VeSyncBaseDeviceHandler {
|
||||
|
||||
public static final int DEFAULT_AIR_PURIFIER_POLL_RATE = 120;
|
||||
// "Device Type" values
|
||||
public static final String DEV_TYPE_CORE_600S = "LAP-C601S-WUS";
|
||||
public static final String DEV_TYPE_CORE_400S = "Core400S";
|
||||
public static final String DEV_TYPE_CORE_300S = "Core300S";
|
||||
public static final String DEV_TYPE_CORE_201S = "LAP-C201S-AUSR";
|
||||
public static final String DEV_TYPE_CORE_200S = "Core200S";
|
||||
public static final String DEV_TYPE_LV_PUR131S = "LV-PUR131S";
|
||||
public static final List<String> SUPPORTED_DEVICE_TYPES = Arrays.asList(DEV_TYPE_CORE_600S, DEV_TYPE_CORE_400S,
|
||||
DEV_TYPE_CORE_300S, DEV_TYPE_CORE_201S, DEV_TYPE_CORE_200S, DEV_TYPE_LV_PUR131S);
|
||||
|
||||
private static final List<String> CORE_400S600S_FAN_MODES = Arrays.asList(MODE_AUTO, MODE_MANUAL, MODE_SLEEP);
|
||||
private static final List<String> CORE_200S300S_FAN_MODES = Arrays.asList(MODE_MANUAL, MODE_SLEEP);
|
||||
private static final List<String> CORE_200S300S_NIGHT_LIGHT_MODES = Arrays.asList(MODE_ON, MODE_DIM, MODE_OFF);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VeSyncDeviceAirPurifierHandler.class);
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_PURIFIER);
|
||||
|
||||
private final Object pollLock = new Object();
|
||||
|
||||
public VeSyncDeviceAirPurifierHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
customiseChannels();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull String[] getChannelsToRemove() {
|
||||
String[] toRemove = new String[] {};
|
||||
final String deviceType = getThing().getProperties().get(DEVICE_PROP_DEVICE_TYPE);
|
||||
if (deviceType != null) {
|
||||
switch (deviceType) {
|
||||
case DEV_TYPE_CORE_600S:
|
||||
case DEV_TYPE_CORE_400S:
|
||||
toRemove = new String[] { DEVICE_CHANNEL_AF_NIGHT_LIGHT };
|
||||
break;
|
||||
case DEV_TYPE_LV_PUR131S:
|
||||
toRemove = new String[] { DEVICE_CHANNEL_AF_NIGHT_LIGHT, DEVICE_CHANNEL_AF_CONFIG_AUTO_ROOM_SIZE,
|
||||
DEVICE_CHANNEL_AF_CONFIG_AUTO_MODE_PREF, DEVICE_CHANNEL_AF_AUTO_OFF_CALC_TIME,
|
||||
DEVICE_CHANNEL_AIR_FILTER_LIFE_PERCENTAGE_REMAINING, DEVICE_CHANNEL_AIRQUALITY_PM25,
|
||||
DEVICE_CHANNEL_AF_SCHEDULES_COUNT, DEVICE_CHANNEL_AF_CONFIG_DISPLAY_FOREVER };
|
||||
break;
|
||||
default:
|
||||
toRemove = new String[] { DEVICE_CHANNEL_AF_AUTO_OFF_CALC_TIME, DEVICE_CHANNEL_AF_SCHEDULES_COUNT };
|
||||
}
|
||||
}
|
||||
return toRemove;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBridgeBasedPolls(final VeSyncBridgeConfiguration config) {
|
||||
Integer pollRate = config.airPurifierPollInterval;
|
||||
if (pollRate == null) {
|
||||
pollRate = DEFAULT_AIR_PURIFIER_POLL_RATE;
|
||||
}
|
||||
|
||||
if (ThingStatus.OFFLINE.equals(getThing().getStatus())) {
|
||||
setBackgroundPollInterval(-1);
|
||||
} else {
|
||||
setBackgroundPollInterval(pollRate);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
this.setBackgroundPollInterval(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDeviceSupported() {
|
||||
final String deviceType = getThing().getProperties().get(DEVICE_PROP_DEVICE_TYPE);
|
||||
if (deviceType == null) {
|
||||
return false;
|
||||
}
|
||||
return SUPPORTED_DEVICE_TYPES.contains(deviceType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final ChannelUID channelUID, final Command command) {
|
||||
final String deviceType = getThing().getProperties().get(DEVICE_PROP_DEVICE_TYPE);
|
||||
if (deviceType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler.submit(() -> {
|
||||
|
||||
if (command instanceof OnOffType) {
|
||||
switch (channelUID.getId()) {
|
||||
case DEVICE_CHANNEL_ENABLED:
|
||||
sendV2BypassControlCommand(DEVICE_SET_SWITCH,
|
||||
new VeSyncRequestManagedDeviceBypassV2.SetSwitchPayload(command.equals(OnOffType.ON),
|
||||
0));
|
||||
break;
|
||||
case DEVICE_CHANNEL_DISPLAY_ENABLED:
|
||||
sendV2BypassControlCommand(DEVICE_SET_DISPLAY,
|
||||
new VeSyncRequestManagedDeviceBypassV2.SetState(command.equals(OnOffType.ON)));
|
||||
break;
|
||||
case DEVICE_CHANNEL_CHILD_LOCK_ENABLED:
|
||||
sendV2BypassControlCommand(DEVICE_SET_CHILD_LOCK,
|
||||
new VeSyncRequestManagedDeviceBypassV2.SetChildLock(command.equals(OnOffType.ON)));
|
||||
break;
|
||||
}
|
||||
} else if (command instanceof StringType) {
|
||||
switch (channelUID.getId()) {
|
||||
case DEVICE_CHANNEL_FAN_MODE_ENABLED:
|
||||
final String targetFanMode = command.toString().toLowerCase();
|
||||
switch (deviceType) {
|
||||
case DEV_TYPE_CORE_600S:
|
||||
case DEV_TYPE_CORE_400S:
|
||||
if (!CORE_400S600S_FAN_MODES.contains(targetFanMode)) {
|
||||
logger.warn(
|
||||
"Fan mode command for \"{}\" is not valid in the (Core400S) API possible options {}",
|
||||
command, String.join(",", CORE_400S600S_FAN_MODES));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case DEV_TYPE_CORE_200S:
|
||||
case DEV_TYPE_CORE_201S:
|
||||
case DEV_TYPE_CORE_300S:
|
||||
if (!CORE_200S300S_FAN_MODES.contains(targetFanMode)) {
|
||||
logger.warn(
|
||||
"Fan mode command for \"{}\" is not valid in the (Core200S/Core300S) API possible options {}",
|
||||
command, String.join(",", CORE_200S300S_FAN_MODES));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
sendV2BypassControlCommand(DEVICE_SET_PURIFIER_MODE,
|
||||
new VeSyncRequestManagedDeviceBypassV2.SetMode(targetFanMode));
|
||||
break;
|
||||
case DEVICE_CHANNEL_AF_NIGHT_LIGHT:
|
||||
final String targetNightLightMode = command.toString().toLowerCase();
|
||||
switch (deviceType) {
|
||||
case DEV_TYPE_CORE_600S:
|
||||
case DEV_TYPE_CORE_400S:
|
||||
logger.warn("Core400S API does not support night light");
|
||||
return;
|
||||
case DEV_TYPE_CORE_200S:
|
||||
case DEV_TYPE_CORE_201S:
|
||||
case DEV_TYPE_CORE_300S:
|
||||
if (!CORE_200S300S_NIGHT_LIGHT_MODES.contains(targetNightLightMode)) {
|
||||
logger.warn(
|
||||
"Night light mode command for \"{}\" is not valid in the (Core200S/Core300S) API possible options {}",
|
||||
command, String.join(",", CORE_200S300S_NIGHT_LIGHT_MODES));
|
||||
return;
|
||||
}
|
||||
|
||||
sendV2BypassControlCommand(DEVICE_SET_NIGHT_LIGHT,
|
||||
new VeSyncRequestManagedDeviceBypassV2.SetNightLight(targetNightLightMode));
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (command instanceof QuantityType) {
|
||||
switch (channelUID.getId()) {
|
||||
case DEVICE_CHANNEL_FAN_SPEED_ENABLED:
|
||||
// If the fan speed is being set enforce manual mode
|
||||
sendV2BypassControlCommand(DEVICE_SET_PURIFIER_MODE,
|
||||
new VeSyncRequestManagedDeviceBypassV2.SetMode(MODE_MANUAL), false);
|
||||
|
||||
int requestedLevel = ((QuantityType<?>) command).intValue();
|
||||
if (requestedLevel < 1) {
|
||||
logger.warn("Fan speed command less than 1 - adjusting to 1 as the valid API value");
|
||||
requestedLevel = 1;
|
||||
}
|
||||
|
||||
switch (deviceType) {
|
||||
case DEV_TYPE_CORE_600S:
|
||||
case DEV_TYPE_CORE_400S:
|
||||
if (requestedLevel > 4) {
|
||||
logger.warn(
|
||||
"Fan speed command greater than 4 - adjusting to 4 as the valid (Core400S) API value");
|
||||
requestedLevel = 4;
|
||||
}
|
||||
break;
|
||||
case DEV_TYPE_CORE_200S:
|
||||
case DEV_TYPE_CORE_201S:
|
||||
case DEV_TYPE_CORE_300S:
|
||||
if (requestedLevel > 3) {
|
||||
logger.warn(
|
||||
"Fan speed command greater than 3 - adjusting to 3 as the valid (Core200S/Core300S) API value");
|
||||
requestedLevel = 3;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
sendV2BypassControlCommand(DEVICE_SET_LEVEL,
|
||||
new VeSyncRequestManagedDeviceBypassV2.SetLevelPayload(0, DEVICE_LEVEL_TYPE_WIND,
|
||||
requestedLevel));
|
||||
break;
|
||||
}
|
||||
} else if (command instanceof RefreshType) {
|
||||
pollForUpdate();
|
||||
} else {
|
||||
logger.trace("UNKNOWN COMMAND: {} {}", command.getClass().toString(), channelUID);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void pollForDeviceData(final ExpiringCache<String> cachedResponse) {
|
||||
final String deviceType = getThing().getProperties().get(DEVICE_PROP_DEVICE_TYPE);
|
||||
if (deviceType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (deviceType) {
|
||||
case DEV_TYPE_CORE_600S:
|
||||
case DEV_TYPE_CORE_400S:
|
||||
case DEV_TYPE_CORE_300S:
|
||||
case DEV_TYPE_CORE_201S:
|
||||
case DEV_TYPE_CORE_200S:
|
||||
processV2BypassPoll(cachedResponse);
|
||||
break;
|
||||
case DEV_TYPE_LV_PUR131S:
|
||||
processV1AirPurifierPoll(cachedResponse);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void processV1AirPurifierPoll(final ExpiringCache<String> cachedResponse) {
|
||||
final String deviceUuid = getThing().getProperties().get(DEVICE_PROP_DEVICE_UUID);
|
||||
if (deviceUuid == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String response;
|
||||
VeSyncV1AirPurifierDeviceDetailsResponse purifierStatus;
|
||||
synchronized (pollLock) {
|
||||
response = cachedResponse.getValue();
|
||||
boolean cachedDataUsed = response != null;
|
||||
if (response == null) {
|
||||
logger.trace("Requesting fresh response");
|
||||
response = sendV1Command("POST", "https://smartapi.vesync.com/131airPurifier/v1/device/deviceDetail",
|
||||
new VeSyncRequestV1ManagedDeviceDetails(deviceUuid));
|
||||
} else {
|
||||
logger.trace("Using cached response {}", response);
|
||||
}
|
||||
|
||||
if (response.equals(EMPTY_STRING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
purifierStatus = VeSyncConstants.GSON.fromJson(response, VeSyncV1AirPurifierDeviceDetailsResponse.class);
|
||||
|
||||
if (purifierStatus == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cachedDataUsed) {
|
||||
cachedResponse.putValue(response);
|
||||
}
|
||||
}
|
||||
|
||||
// Bail and update the status of the thing - it will be updated to online by the next search
|
||||
// that detects it is online.
|
||||
if (purifierStatus.isDeviceOnline()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!"0".equals(purifierStatus.getCode())) {
|
||||
logger.warn("Check Thing type has been set - API gave a unexpected response for an Air Purifier");
|
||||
return;
|
||||
}
|
||||
|
||||
updateState(DEVICE_CHANNEL_ENABLED, OnOffType.from(MODE_ON.equals(purifierStatus.getDeviceStatus())));
|
||||
updateState(DEVICE_CHANNEL_CHILD_LOCK_ENABLED, OnOffType.from(MODE_ON.equals(purifierStatus.getChildLock())));
|
||||
updateState(DEVICE_CHANNEL_FAN_MODE_ENABLED, new StringType(purifierStatus.getMode()));
|
||||
updateState(DEVICE_CHANNEL_FAN_SPEED_ENABLED, new DecimalType(String.valueOf(purifierStatus.getLevel())));
|
||||
updateState(DEVICE_CHANNEL_DISPLAY_ENABLED, OnOffType.from(MODE_ON.equals(purifierStatus.getScreenStatus())));
|
||||
updateState(DEVICE_CHANNEL_AIRQUALITY_BASIC, new DecimalType(purifierStatus.getAirQuality()));
|
||||
}
|
||||
|
||||
private void processV2BypassPoll(final ExpiringCache<String> cachedResponse) {
|
||||
String response;
|
||||
VeSyncV2BypassPurifierStatus purifierStatus;
|
||||
synchronized (pollLock) {
|
||||
response = cachedResponse.getValue();
|
||||
boolean cachedDataUsed = response != null;
|
||||
if (response == null) {
|
||||
logger.trace("Requesting fresh response");
|
||||
response = sendV2BypassCommand(DEVICE_GET_PURIFIER_STATUS,
|
||||
new VeSyncRequestManagedDeviceBypassV2.EmptyPayload());
|
||||
} else {
|
||||
logger.trace("Using cached response {}", response);
|
||||
}
|
||||
|
||||
if (response.equals(EMPTY_STRING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
purifierStatus = VeSyncConstants.GSON.fromJson(response, VeSyncV2BypassPurifierStatus.class);
|
||||
|
||||
if (purifierStatus == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cachedDataUsed) {
|
||||
cachedResponse.putValue(response);
|
||||
}
|
||||
}
|
||||
|
||||
// Bail and update the status of the thing - it will be updated to online by the next search
|
||||
// that detects it is online.
|
||||
if (purifierStatus.isMsgDeviceOffline()) {
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
return;
|
||||
} else if (purifierStatus.isMsgSuccess()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
if (!"0".equals(purifierStatus.result.getCode())) {
|
||||
logger.warn("Check Thing type has been set - API gave a unexpected response for an Air Purifier");
|
||||
return;
|
||||
}
|
||||
|
||||
updateState(DEVICE_CHANNEL_ENABLED, OnOffType.from(purifierStatus.result.result.enabled));
|
||||
updateState(DEVICE_CHANNEL_CHILD_LOCK_ENABLED, OnOffType.from(purifierStatus.result.result.childLock));
|
||||
updateState(DEVICE_CHANNEL_DISPLAY_ENABLED, OnOffType.from(purifierStatus.result.result.display));
|
||||
updateState(DEVICE_CHANNEL_AIR_FILTER_LIFE_PERCENTAGE_REMAINING,
|
||||
new QuantityType<>(purifierStatus.result.result.filterLife, Units.PERCENT));
|
||||
updateState(DEVICE_CHANNEL_FAN_MODE_ENABLED, new StringType(purifierStatus.result.result.mode));
|
||||
updateState(DEVICE_CHANNEL_FAN_SPEED_ENABLED, new DecimalType(purifierStatus.result.result.level));
|
||||
updateState(DEVICE_CHANNEL_ERROR_CODE, new DecimalType(purifierStatus.result.result.deviceErrorCode));
|
||||
updateState(DEVICE_CHANNEL_AIRQUALITY_BASIC, new DecimalType(purifierStatus.result.result.airQuality));
|
||||
updateState(DEVICE_CHANNEL_AIRQUALITY_PM25,
|
||||
new QuantityType<>(purifierStatus.result.result.airQualityValue, Units.MICROGRAM_PER_CUBICMETRE));
|
||||
|
||||
updateState(DEVICE_CHANNEL_AF_CONFIG_DISPLAY_FOREVER,
|
||||
OnOffType.from(purifierStatus.result.result.configuration.displayForever));
|
||||
|
||||
updateState(DEVICE_CHANNEL_AF_CONFIG_AUTO_MODE_PREF,
|
||||
new StringType(purifierStatus.result.result.configuration.autoPreference.autoType));
|
||||
|
||||
updateState(DEVICE_CHANNEL_AF_CONFIG_AUTO_ROOM_SIZE,
|
||||
new DecimalType(purifierStatus.result.result.configuration.autoPreference.roomSize));
|
||||
|
||||
// Only 400S appears to have this JSON extension object
|
||||
if (purifierStatus.result.result.extension != null) {
|
||||
if (purifierStatus.result.result.extension.timerRemain > 0) {
|
||||
updateState(DEVICE_CHANNEL_AF_AUTO_OFF_CALC_TIME, new DateTimeType(LocalDateTime.now()
|
||||
.plus(purifierStatus.result.result.extension.timerRemain, ChronoUnit.SECONDS).toString()));
|
||||
} else {
|
||||
updateState(DEVICE_CHANNEL_AF_AUTO_OFF_CALC_TIME, new DateTimeItem("nullEnforcements").getState());
|
||||
}
|
||||
updateState(DEVICE_CHANNEL_AF_SCHEDULES_COUNT,
|
||||
new DecimalType(purifierStatus.result.result.extension.scheduleCount));
|
||||
}
|
||||
|
||||
// Not applicable to 400S payload's
|
||||
if (purifierStatus.result.result.nightLight != null) {
|
||||
updateState(DEVICE_CHANNEL_AF_NIGHT_LIGHT, new DecimalType(purifierStatus.result.result.nightLight));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="vesync" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>VeSync Binding</name>
|
||||
<description>This is the binding for the VeSync products. Currently, this supports the Levoit branded Air Purifiers and
|
||||
Humidifiers using the v2 protocol.</description>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,334 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="vesync"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="bridge">
|
||||
<label>VeSync Bridge</label>
|
||||
<description>The VeSync bridge represents the VeSync cloud service.</description>
|
||||
|
||||
<properties>
|
||||
<property name="Registration Time"/>
|
||||
<property name="Country Code"/>
|
||||
<property name="Accept Language"/>
|
||||
</properties>
|
||||
|
||||
<config-description>
|
||||
<parameter name="username" type="text">
|
||||
<context>email</context>
|
||||
<required>true</required>
|
||||
<label>Username</label>
|
||||
<description>Name of a registered VeSync user, that allows to access the mobile application.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text">
|
||||
<context>password</context>
|
||||
<required>true</required>
|
||||
<label>Password</label>
|
||||
<description>Password for the registered VeSync username, that allows to access the mobile application.</description>
|
||||
</parameter>
|
||||
<parameter name="backgroundDeviceDiscovery" type="boolean">
|
||||
<label>Background Device Scans</label>
|
||||
<description>Enable background scanning for new devices.</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="airPurifierPollInterval" type="integer" min="5" step="1" unit="s">
|
||||
<label>Air Filters/Humidifiers Poll Rate</label>
|
||||
<description>Seconds between fetching background updates about the air purifiers / humidifiers.</description>
|
||||
<default>60</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<thing-type id="airPurifier">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Air Purifier via VeSync</label>
|
||||
<description>A Air Purifier uplinking to VeSync</description>
|
||||
|
||||
<channels>
|
||||
<channel id="enabled" typeId="deviceEnabledType"/>
|
||||
<channel id="childLock" typeId="deviceChildLockEnabledType"/>
|
||||
<channel id="display" typeId="deviceDisplayEnabledType"/>
|
||||
<channel id="filterLifePercentage" typeId="deviceFilterLifePercentageType"/>
|
||||
<channel id="fanMode" typeId="airPurifierModeType"/>
|
||||
<channel id="manualFanSpeed" typeId="airPurifierFanLevelType"/>
|
||||
<channel id="errorCode" typeId="deviceErrorCodeType"/>
|
||||
<channel id="airQuality" typeId="deviceAirQualityBasicType"/>
|
||||
<channel id="airQualityPM25" typeId="airQualityPM25"/>
|
||||
<channel id="configDisplayForever" typeId="deviceAFConfigDisplayForever"/>
|
||||
<channel id="configAutoMode" typeId="deviceAFConfigAutoPrefType"/>
|
||||
<channel id="timerExpiry" typeId="deviceAFTimerExpiry"/>
|
||||
<channel id="configAutoRoomSize" typeId="deviceAFConfigAutoPrefRoomSizeType"/>
|
||||
<channel id="schedulesCount" typeId="deviceAFConfigAutoScheduleCountType"/>
|
||||
<channel id="nightLightMode" typeId="deviceAFNightLight"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="Device Name"/>
|
||||
<property name="Device Type"/>
|
||||
<property name="MAC Id"/>
|
||||
</properties>
|
||||
<representation-property>macId</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="macId" type="text">
|
||||
<label>MAC Id</label>
|
||||
<description>The MAC Id of the device as reported by the API.</description>
|
||||
</parameter>
|
||||
<parameter name="deviceName" type="text">
|
||||
<label>Device Name</label>
|
||||
<description>The name allocated to the device by the app. (Must be unique if used)</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="airHumidifier">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Air Humidifier via VeSync</label>
|
||||
<description>A Air Humidifier uplinking to VeSync</description>
|
||||
|
||||
<channels>
|
||||
<channel id="enabled" typeId="deviceEnabledType"/>
|
||||
<channel id="display" typeId="deviceDisplayEnabledType"/>
|
||||
<channel id="waterLacking" typeId="deviceWaterLackingType"/>
|
||||
<channel id="humidityHigh" typeId="deviceHighHumidityType"/>
|
||||
<channel id="waterTankLifted" typeId="deviceWaterTankLiftedType"/>
|
||||
<channel id="stopAtHumiditySetpoint" typeId="deviceAutomaticStopReachTargetType"/>
|
||||
<channel id="humidity" typeId="deviceHumidityType"/>
|
||||
<channel id="mistLevel" typeId="deviceMistLevelType"/>
|
||||
<channel id="humidifierMode" typeId="airHumidifierModeType"/>
|
||||
<channel id="nightLightMode" typeId="deviceAFNightLight"/>
|
||||
<channel id="humiditySetpoint" typeId="deviceConfigTargetHumidity"/>
|
||||
<channel id="warmEnabled" typeId="warmModeEnabled"/>
|
||||
<channel id="warmLevel" typeId="warmLevel"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="Device Name"/>
|
||||
<property name="Device Type"/>
|
||||
<property name="MAC Id"/>
|
||||
</properties>
|
||||
<representation-property>macId</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="macId" type="text">
|
||||
<label>MAC Id</label>
|
||||
<description>The MAC Id of the device as reported by the API.</description>
|
||||
</parameter>
|
||||
<parameter name="deviceName" type="text">
|
||||
<label>Device Name</label>
|
||||
<description>The name allocated to the device by the app. (Must be unique if used)</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="deviceEnabledType">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Switched On</label>
|
||||
<description>Indicator if the device is switched on</description>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceChildLockEnabledType">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Display Lock</label>
|
||||
<description>Indicator if the devices child lock is enabled (Display Lock)</description>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceDisplayEnabledType">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Display</label>
|
||||
<description>Indicator if the devices display is enabled</description>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceFilterLifePercentageType">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Filter Life Remaining</label>
|
||||
<description>Indicator of the remaining filter life</description>
|
||||
<state readOnly="true" pattern="%.0f %%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="airPurifierModeType">
|
||||
<item-type>String</item-type>
|
||||
<label>Operation Mode</label>
|
||||
<description>The operating mode the air purifier is currently set to</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="auto">Auto</option>
|
||||
<option value="manual">Manual Fan Control</option>
|
||||
<option value="sleep">Sleeping Auto</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceAFNightLight">
|
||||
<item-type>String</item-type>
|
||||
<label>Night Light</label>
|
||||
<description>The operating mode of the night light functionality</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="on">On</option>
|
||||
<option value="dim">Dimmed</option>
|
||||
<option value="off">Off</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
|
||||
<channel-type id="airPurifierFanLevelType">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Fan Speed</label>
|
||||
<description>Indicator of the current fan speed</description>
|
||||
<state readOnly="true" pattern="%.0f"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceErrorCodeType">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Device Error Code</label>
|
||||
<description>Indicator of the current error code of the device</description>
|
||||
<state readOnly="true" pattern="%.0f"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceAirQualityBasicType">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Air Quality</label>
|
||||
<description>System representation of air quality</description>
|
||||
<state readOnly="true" pattern="%.0f"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="airQualityPM25">
|
||||
<item-type>Number:Density</item-type>
|
||||
<label>Air Quality PPM2.5</label>
|
||||
<description>Indicator of current air quality</description>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceAFConfigDisplayForever">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Config: Display Forever</label>
|
||||
<description>Configuration: If the devices display is enabled forever</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceAFConfigAutoPrefType">
|
||||
<item-type>String</item-type>
|
||||
<label>Config: Auto Mode</label>
|
||||
<description>The operating mode when the air purifier is set to auto</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="default">Auto (Air Quality)</option>
|
||||
<option value="quiet">Quiet (No High Speed)</option>
|
||||
<option value="efficient">Auto (Room Size)</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceAFTimerExpiry">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Auto Off Expiry</label>
|
||||
<description>The time when the auto off timer will be reached</description>
|
||||
<state readOnly="true" pattern="%1$tF %1$tR"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceAFConfigAutoPrefRoomSizeType">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Config: Room size</label>
|
||||
<description>Room size (foot sq) for efficient auto mode</description>
|
||||
<state readOnly="true" pattern="%.0f sq ft"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceAFConfigAutoScheduleCountType">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Config: Schedules Count</label>
|
||||
<description>The current number of schedules configured</description>
|
||||
<state readOnly="true" pattern="%.0f"/>
|
||||
</channel-type>
|
||||
|
||||
|
||||
<channel-type id="deviceWaterLackingType">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Water Low/Empty</label>
|
||||
<description>Indicator if the devices water is low or empty</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceHighHumidityType">
|
||||
<item-type>Switch</item-type>
|
||||
<label>High Humidity</label>
|
||||
<description>Indicator if the device is measuring high humidity</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceWaterTankLiftedType">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Water Tank Removed</label>
|
||||
<description>Indicator if the device is reporting the water tank as removed</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceAutomaticStopReachTargetType">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Stop @ Set Point</label>
|
||||
<description>Indicator if the device is set to stop when the humidity set point has been reached</description>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceHumidityType">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Humidity Level</label>
|
||||
<description>System representation of humidity</description>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceConfigTargetHumidity">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Humidity Set Point</label>
|
||||
<description>Humidity Set Point</description>
|
||||
<state readOnly="false" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceMistLevelType">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Mist Level</label>
|
||||
<description>System representation of mist level</description>
|
||||
<state readOnly="false" pattern="%.0f"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="airHumidifierModeType">
|
||||
<item-type>String</item-type>
|
||||
<label>Operation Mode</label>
|
||||
<description>The operating mode the air humidifier is currently set to</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="auto">Auto</option>
|
||||
<option value="manual">Manual Control</option>
|
||||
<option value="sleep">Sleeping Auto</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="warmModeEnabled">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Warm Mode Enabled</label>
|
||||
<description>Indicator if the device is set to warm mist</description>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="warmLevel">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Warm Level</label>
|
||||
<description>Warm Level</description>
|
||||
<state readOnly="false" pattern="%.0f"/>
|
||||
</channel-type>
|
||||
|
||||
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.handler.requests;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.vesync.internal.exceptions.AuthenticationException;
|
||||
import org.openhab.binding.vesync.internal.VeSyncConstants;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VesyncAuthenticatedRequest;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VesyncLoginCredentials;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VesyncLoginResponse;
|
||||
|
||||
/**
|
||||
* The {@link VesyncLoginCredentials} class implements unit test case for {@link VesyncLoginResponse}
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VesyncAuthenticatedRequestTest {
|
||||
|
||||
public final static VesyncLoginResponse.VesyncUserSession testUser = VeSyncConstants.GSON.fromJson(
|
||||
org.openhab.binding.vesync.internal.handler.responses.VesyncLoginResponseTest.testGoodLoginResponseBody,
|
||||
VesyncLoginResponse.class).result;
|
||||
|
||||
@Test
|
||||
public void checkBaseFieldsExist() {
|
||||
String content = VeSyncConstants.GSON.toJson(new VesyncLoginCredentials("username", "passmd5"));
|
||||
|
||||
assertEquals(true, content.contains("\"timeZone\": \"America/New_York\""));
|
||||
assertEquals(true, content.contains("\"acceptLanguage\": \"en\""));
|
||||
|
||||
assertEquals(true, content.contains("\"appVersion\": \"2.5.1\""));
|
||||
assertEquals(true, content.contains("\"phoneBrand\": \"SM N9005\""));
|
||||
assertEquals(true, content.contains("\"phoneOS\": \"Android\""));
|
||||
|
||||
Pattern p = Pattern.compile("\"traceId\": \"\\d+\"");
|
||||
Matcher m = p.matcher(content);
|
||||
assertEquals(true, m.find());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkAuthenicationData() {
|
||||
|
||||
// Simulate as the code flow should run - parse data and then use it
|
||||
VesyncLoginResponse response = VeSyncConstants.GSON
|
||||
.fromJson(org.openhab.binding.vesync.internal.handler.responses.VesyncLoginResponseTest.testGoodLoginResponseBody, VesyncLoginResponse.class);
|
||||
|
||||
String content = "";
|
||||
|
||||
try {
|
||||
content = VeSyncConstants.GSON.toJson(new VesyncAuthenticatedRequest(response.result));
|
||||
} catch (AuthenticationException ae) {
|
||||
|
||||
}
|
||||
|
||||
assertEquals(true, content.contains("\"token\": \"AccessTokenString=\""));
|
||||
assertEquals(true, content.contains("\"accountID\": \"5328043\""));
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.handler.requests;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.vesync.internal.VeSyncConstants;
|
||||
import org.openhab.binding.vesync.internal.api.VesyncV2ApiHelper;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VesyncLoginCredentials;
|
||||
|
||||
/**
|
||||
* The {@link VesyncLoginCredentials} class implements unit test case for {@link VesyncLoginResponse}
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VesyncLoginCredentialsTest {
|
||||
|
||||
@Test
|
||||
public void checkMd5Calculation() {
|
||||
assertEquals("577441848f056cd02d4c500b25fdd76a",VesyncV2ApiHelper.calculateMd5("TestHashInPythonLib=+"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkBaseFieldsExist() {
|
||||
String content = VeSyncConstants.GSON.toJson(new VesyncLoginCredentials("username", "passmd5"));
|
||||
|
||||
assertEquals(true, content.contains("\"timeZone\": \"America/New_York\""));
|
||||
assertEquals(true, content.contains("\"acceptLanguage\": \"en\""));
|
||||
|
||||
assertEquals(true, content.contains("\"appVersion\": \"2.5.1\""));
|
||||
assertEquals(true, content.contains("\"phoneBrand\": \"SM N9005\""));
|
||||
assertEquals(true, content.contains("\"phoneOS\": \"Android\""));
|
||||
|
||||
Pattern p = Pattern.compile("\"traceId\": \"\\d+\"");
|
||||
Matcher m = p.matcher(content);
|
||||
assertEquals(true, m.find());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkLoginMethodJson() {
|
||||
|
||||
String content = VeSyncConstants.GSON.toJson(new VesyncLoginCredentials("username", "passmd5"));
|
||||
|
||||
assertEquals(true, content.contains("\"method\": \"login\""));
|
||||
assertEquals(true, content.contains("\"email\": \"username\""));
|
||||
assertEquals(true, content.contains("\"password\": \"passmd5\""));
|
||||
assertEquals(true, content.contains("\"userType\": \"1\""));
|
||||
assertEquals(true, content.contains("\"devToken\": \"\""));
|
||||
}
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.handler.requests;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.vesync.internal.VeSyncConstants;
|
||||
import org.openhab.binding.vesync.internal.api.VesyncV2ApiHelper;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VesyncLoginCredentials;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VesyncRequestManagedDeviceBypassV2;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* The {@link VesyncLoginCredentials} class implements unit test case for {@link VesyncLoginResponse}
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VesyncRequestManagedDeviceBypassV2Test {
|
||||
|
||||
@Test
|
||||
public void checkMd5Calculation() {
|
||||
assertEquals("577441848f056cd02d4c500b25fdd76a",VesyncV2ApiHelper.calculateMd5("TestHashInPythonLib=+"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkBaseFieldsExist() {
|
||||
String content = VeSyncConstants.GSON.toJson(new VesyncRequestManagedDeviceBypassV2());
|
||||
|
||||
assertEquals(true, content.contains("\"method\": \"bypassV2\""));
|
||||
assertEquals(true, content.contains("\"data\": {}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkEmptyPayload() {
|
||||
final VesyncRequestManagedDeviceBypassV2.EmptyPayload testPaylaod = new VesyncRequestManagedDeviceBypassV2.EmptyPayload();
|
||||
final String contentTest1 = VeSyncConstants.GSON.toJson(testPaylaod);
|
||||
assertEquals(true, contentTest1.equals("{}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkSetLevelPayload() {
|
||||
final VesyncRequestManagedDeviceBypassV2.SetLevelPayload testPaylaod = new VesyncRequestManagedDeviceBypassV2.SetLevelPayload(1,"stringval",2);
|
||||
final String contentTest1 = VeSyncConstants.GSON.toJson(testPaylaod);
|
||||
assertEquals(true, contentTest1.contains("\"id\": 1"));
|
||||
assertEquals(true,contentTest1.contains("\"type\": \"stringval\""));
|
||||
assertEquals(true,contentTest1.contains("\"level\": 2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkSetChildLockPayload() {
|
||||
final VesyncRequestManagedDeviceBypassV2.SetChildLock testPaylaod = new VesyncRequestManagedDeviceBypassV2.SetChildLock(false);
|
||||
final String contentTest1 = VeSyncConstants.GSON.toJson(testPaylaod);
|
||||
assertEquals(true,contentTest1.contains("\"child_lock\": false"));
|
||||
|
||||
testPaylaod.childLock = true;
|
||||
final String contentTest2 = VeSyncConstants.GSON.toJson(testPaylaod);
|
||||
assertEquals(true,contentTest2.contains("\"child_lock\": true"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkSetSwitchPayload() {
|
||||
final VesyncRequestManagedDeviceBypassV2.SetSwitchPayload testPaylaod = new VesyncRequestManagedDeviceBypassV2.SetSwitchPayload(true,0);
|
||||
final String contentTest1 = VeSyncConstants.GSON.toJson(testPaylaod);
|
||||
assertEquals(true, contentTest1.contains("\"enabled\": true"));
|
||||
assertEquals(true, contentTest1.contains("\"id\": 0"));
|
||||
|
||||
testPaylaod.enabled = false;
|
||||
testPaylaod.id = 100;
|
||||
final String contentTest2 = VeSyncConstants.GSON.toJson(testPaylaod);
|
||||
assertEquals(true, contentTest2.contains("\"enabled\": false"));
|
||||
assertEquals(true, contentTest2.contains("\"id\": 100"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkSetNightLightPayload() {
|
||||
final VesyncRequestManagedDeviceBypassV2.SetNightLight testPaylaod = new VesyncRequestManagedDeviceBypassV2.SetNightLight("myValue");
|
||||
final String contentTest1 = VeSyncConstants.GSON.toJson(testPaylaod);
|
||||
assertEquals(true, contentTest1.contains("\"night_light\": \"myValue\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkSetTargetHumidityPayload() {
|
||||
final VesyncRequestManagedDeviceBypassV2.SetTargetHumidity test0Paylaod = new VesyncRequestManagedDeviceBypassV2.SetTargetHumidity(0);
|
||||
final String contentTest1 = VeSyncConstants.GSON.toJson(test0Paylaod);
|
||||
assertEquals(true, contentTest1.contains("\"target_humidity\": 0"));
|
||||
|
||||
final VesyncRequestManagedDeviceBypassV2.SetTargetHumidity test100Paylaod = new VesyncRequestManagedDeviceBypassV2.SetTargetHumidity(100);
|
||||
final String contentTest2 = VeSyncConstants.GSON.toJson(test100Paylaod);
|
||||
assertEquals(true, contentTest2.contains("\"target_humidity\": 100"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkSetNightLightBrightnessPayload() {
|
||||
final VesyncRequestManagedDeviceBypassV2.SetNightLightBrightness test0Paylaod = new VesyncRequestManagedDeviceBypassV2.SetNightLightBrightness(0);
|
||||
final String contentTest1 = VeSyncConstants.GSON.toJson(test0Paylaod);
|
||||
assertEquals(true, contentTest1.contains("\"night_light_brightness\": 0"));
|
||||
|
||||
final VesyncRequestManagedDeviceBypassV2.SetNightLightBrightness test100Paylaod = new VesyncRequestManagedDeviceBypassV2.SetNightLightBrightness(100);
|
||||
final String contentTest2 = VeSyncConstants.GSON.toJson(test100Paylaod);
|
||||
assertEquals(true, contentTest2.contains("\"night_light_brightness\": 100"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkEnabledPayload() {
|
||||
final VesyncRequestManagedDeviceBypassV2.EnabledPayload enabledOn = new VesyncRequestManagedDeviceBypassV2.EnabledPayload(true);
|
||||
final String contentTest1 = VeSyncConstants.GSON.toJson(enabledOn);
|
||||
assertEquals(true, contentTest1.contains("\"enabled\": true"));
|
||||
|
||||
final VesyncRequestManagedDeviceBypassV2.EnabledPayload enabledOff = new VesyncRequestManagedDeviceBypassV2.EnabledPayload(false);
|
||||
final String contentTest2 = VeSyncConstants.GSON.toJson(enabledOff);
|
||||
assertEquals(true, contentTest2.contains("\"enabled\": false"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkLoginMethodJson() {
|
||||
|
||||
String content = VeSyncConstants.GSON.toJson(new VesyncLoginCredentials("username", "passmd5"));
|
||||
|
||||
assertEquals(true, content.contains("\"method\": \"login\""));
|
||||
assertEquals(true, content.contains("\"email\": \"username\""));
|
||||
assertEquals(true, content.contains("\"password\": \"passmd5\""));
|
||||
assertEquals(true, content.contains("\"userType\": \"1\""));
|
||||
assertEquals(true, content.contains("\"devToken\": \"\""));
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.handler.requests;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.vesync.internal.exceptions.AuthenticationException;
|
||||
import org.openhab.binding.vesync.internal.VeSyncConstants;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VesyncLoginCredentials;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VesyncRequestManagedDevicesPage;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* The {@link VesyncRequestManagedDevicesPageTest} class implements unit test case for
|
||||
* {@link VesyncRequestManagedDevicesPage}
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VesyncRequestManagedDevicesPageTest {
|
||||
|
||||
@Test
|
||||
public void checkBaseFieldsExist() {
|
||||
String content = VeSyncConstants.GSON.toJson(new VesyncLoginCredentials("username", "passmd5"));
|
||||
|
||||
assertEquals(true, content.contains("\"timeZone\": \"America/New_York\""));
|
||||
assertEquals(true, content.contains("\"acceptLanguage\": \"en\""));
|
||||
|
||||
assertEquals(true, content.contains("\"appVersion\": \"2.5.1\""));
|
||||
assertEquals(true, content.contains("\"phoneBrand\": \"SM N9005\""));
|
||||
assertEquals(true, content.contains("\"phoneOS\": \"Android\""));
|
||||
|
||||
Pattern p = Pattern.compile("\"traceId\": \"\\d+\"");
|
||||
Matcher m = p.matcher(content);
|
||||
assertEquals(true, m.find());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkRequestDevicesFields() {
|
||||
|
||||
String content = "";
|
||||
try {
|
||||
content = VeSyncConstants.GSON
|
||||
.toJson(new VesyncRequestManagedDevicesPage(org.openhab.binding.vesync.internal.handler.requests.VesyncAuthenticatedRequestTest.testUser, 1, 100));
|
||||
} catch (AuthenticationException ae) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
assertEquals(true, content.contains("\"method\": \"devices\""));
|
||||
assertEquals(true, content.contains("\"pageNo\": \"1\""));
|
||||
assertEquals(true, content.contains("\"pageSize\": \"100\""));
|
||||
assertEquals(true, content.contains("\"token\": \"AccessTokenString=\""));
|
||||
assertEquals(true, content.contains("\"accountID\": \"5328043\""));
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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 java.org.openhab.binding.vesync.internal.handler.requests;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.vesync.internal.VeSyncConstants;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VesyncRequest;
|
||||
|
||||
/**
|
||||
* The {@link VesyncRequestTest} class implements unit test case for {@link VesyncRequest}
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VesyncRequestTest {
|
||||
|
||||
@Test
|
||||
public void checkBaseFieldsExist() {
|
||||
String content = VeSyncConstants.GSON.toJson(new VesyncRequest());
|
||||
|
||||
assertEquals(true, content.contains("\"timeZone\": \"America/New_York\""));
|
||||
assertEquals(true, content.contains("\"acceptLanguage\": \"en\""));
|
||||
|
||||
assertEquals(true, content.contains("\"appVersion\": \"2.5.1\""));
|
||||
assertEquals(true, content.contains("\"phoneBrand\": \"SM N9005\""));
|
||||
assertEquals(true, content.contains("\"phoneOS\": \"Android\""));
|
||||
|
||||
Pattern p = Pattern.compile("\"traceId\": \"\\d+\"");
|
||||
Matcher m = p.matcher(content);
|
||||
assertEquals(true, m.find());
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.handler.requests;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.vesync.internal.exceptions.AuthenticationException;
|
||||
import org.openhab.binding.vesync.internal.VeSyncConstants;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VesyncLoginCredentials;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VesyncRequestManagedDevicesPage;
|
||||
import org.openhab.binding.vesync.internal.dto.requests.VesyncRequestV1ManagedDeviceDetails;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* The {@link VesyncRequestV1ManagedDeviceDetails} class implements unit test case for
|
||||
* {@link VesyncRequestManagedDevicesPage}
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VesyncRequestV1ManagedDeviceDetailsTest {
|
||||
|
||||
// Verified content URLS
|
||||
// https://smartapi.vesync.com/131airPurifier/v1/device/deviceDetail
|
||||
|
||||
@Test
|
||||
public void checkBaseFieldsExist() {
|
||||
String content = VeSyncConstants.GSON.toJson(new VesyncLoginCredentials("username", "passmd5"));
|
||||
|
||||
assertEquals(true, content.contains("\"timeZone\": \"America/New_York\""));
|
||||
assertEquals(true, content.contains("\"acceptLanguage\": \"en\""));
|
||||
|
||||
assertEquals(true, content.contains("\"appVersion\": \"2.5.1\""));
|
||||
assertEquals(true, content.contains("\"phoneBrand\": \"SM N9005\""));
|
||||
assertEquals(true, content.contains("\"phoneOS\": \"Android\""));
|
||||
|
||||
Pattern p = Pattern.compile("\"traceId\": \"\\d+\"");
|
||||
Matcher m = p.matcher(content);
|
||||
assertEquals(true, m.find());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkRequestDevicesFields() {
|
||||
|
||||
String content = "";
|
||||
try {
|
||||
content = VeSyncConstants.GSON
|
||||
.toJson(new VesyncRequestV1ManagedDeviceDetails(VesyncAuthenticatedRequestTest.testUser, "MyDeviceUUID"));
|
||||
} catch (AuthenticationException ae) {
|
||||
|
||||
}
|
||||
|
||||
assertEquals(true, content.contains("\"uuid\": \"MyDeviceUUID\""));
|
||||
assertEquals(true, content.contains("\"mobileId\": \"1234567890123456\""));
|
||||
assertEquals(true, content.contains("\"token\": \"AccessTokenString=\""));
|
||||
assertEquals(true, content.contains("\"accountID\": \"5328043\""));
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.handler.responses;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.vesync.internal.VeSyncConstants;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VesyncLoginResponse;
|
||||
|
||||
/**
|
||||
* The {@link VesyncLoginResponseTest} class implements unit test case for {@link VesyncLoginResponse}
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VesyncLoginResponseTest {
|
||||
|
||||
public final static String testGoodLoginResponseBody = "{\r\n" + " \"traceId\": \"1634253816\",\r\n"
|
||||
+ " \"code\": 0,\r\n" + " \"msg\": \"request success\",\r\n" + " \"result\": {\r\n"
|
||||
+ " \"isRequiredVerify\": true,\r\n" + " \"accountID\": \"5328043\",\r\n"
|
||||
+ " \"avatarIcon\": \"https://image.vesync.com/defaultImages/user/avatar_nor.png\",\r\n"
|
||||
+ " \"birthday\": \"\",\r\n" + " \"gender\": \"\",\r\n"
|
||||
+ " \"acceptLanguage\": \"en\",\r\n" + " \"userType\": \"1\",\r\n"
|
||||
+ " \"nickName\": \"david.goodyear\",\r\n" + " \"mailConfirmation\": true,\r\n"
|
||||
+ " \"termsStatus\": true,\r\n" + " \"gdprStatus\": true,\r\n"
|
||||
+ " \"countryCode\": \"GB\",\r\n" + " \"registerAppVersion\": \"VeSync 3.1.37 build3\",\r\n"
|
||||
+ " \"registerTime\": \"2021-10-14 17:35:50\",\r\n"
|
||||
+ " \"verifyEmail\": \"david.goodyear@gmail.com\",\r\n" + " \"heightCm\": 0.0,\r\n"
|
||||
+ " \"weightTargetSt\": 0.0,\r\n" + " \"heightUnit\": \"FT\",\r\n"
|
||||
+ " \"heightFt\": 0.0,\r\n" + " \"weightTargetKg\": 0.0,\r\n"
|
||||
+ " \"weightTargetLb\": 0.0,\r\n" + " \"weightUnit\": \"LB\",\r\n"
|
||||
+ " \"targetBfr\": 0.0,\r\n" + " \"displayFlag\": [],\r\n"
|
||||
+ " \"real_weight_kg\": 0.0,\r\n" + " \"real_weight_lb\": 0.0,\r\n"
|
||||
+ " \"real_weight_unit\": \"lb\",\r\n" + " \"heart_rate_zones\": 0.0,\r\n"
|
||||
+ " \"run_step_long_cm\": 0.0,\r\n" + " \"walk_step_long_cm\": 0.0,\r\n"
|
||||
+ " \"step_target\": 0.0,\r\n" + " \"sleep_target_mins\": 0.0,\r\n"
|
||||
+ " \"token\": \"AccessTokenString=\"\r\n" + " }\r\n" + "}";
|
||||
|
||||
@Test
|
||||
public void testParseLoginGoodResponse() {
|
||||
VesyncLoginResponse response = VeSyncConstants.GSON.fromJson(testGoodLoginResponseBody,
|
||||
VesyncLoginResponse.class);
|
||||
if (response != null) {
|
||||
assertEquals("1634253816", response.getTraceId());
|
||||
assertEquals("AccessTokenString=", response.result.token);
|
||||
assertEquals("request success", response.msg);
|
||||
assertEquals("5328043", response.result.accountId);
|
||||
assertEquals("VeSync 3.1.37 build3", response.result.registerAppVersion);
|
||||
assertEquals("GB", response.result.countryCode);
|
||||
assertTrue(response.isMsgSuccess());
|
||||
} else {
|
||||
fail("GSON returned null");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseLoginFailResponse() {
|
||||
String testReponse = "{\r\n" + " \"traceId\": \"1634253816\",\r\n" + " \"code\": -11201022,\r\n"
|
||||
+ " \"msg\": \"password incorrect\",\r\n" + " \"result\": null\r\n" + "}";
|
||||
VesyncLoginResponse response = VeSyncConstants.GSON.fromJson(testReponse,
|
||||
VesyncLoginResponse.class);
|
||||
if (response != null) {
|
||||
assertEquals("password incorrect", response.msg);
|
||||
assertFalse(response.isMsgSuccess());
|
||||
} else {
|
||||
fail("GSON returned null");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.handler.responses;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.vesync.internal.VeSyncConstants;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VesyncLoginResponse;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VesyncManagedDevicesPage;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* The {@link VesyncManagedDevicesPageTest} class implements unit test case for {@link VesyncLoginResponse}
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VesyncManagedDevicesPageTest {
|
||||
|
||||
public final static String testGoodSearchResponsePageBody = "{\n" +
|
||||
" \"traceId\": \"1634387642\",\n" +
|
||||
" \"code\": 0,\n" +
|
||||
" \"msg\": \"request success\",\n" +
|
||||
" \"result\": {\n" +
|
||||
" \"total\": 2,\n" +
|
||||
" \"pageSize\": 100,\n" +
|
||||
" \"pageNo\": 1,\n" +
|
||||
" \"list\": [\n" +
|
||||
" {\n" +
|
||||
" \"deviceRegion\": \"EU\",\n" +
|
||||
" \"isOwner\": true,\n" +
|
||||
" \"authKey\": null,\n" +
|
||||
" \"deviceName\": \"Air Filter\",\n" +
|
||||
" \"deviceImg\": \"https://image.vesync.com/defaultImages/Core_400S_Series/icon_core400s_purifier_160.png\",\n" +
|
||||
" \"cid\": \"cidValue1\",\n" +
|
||||
" \"deviceStatus\": \"on\",\n" +
|
||||
" \"connectionStatus\": \"online\",\n" +
|
||||
" \"connectionType\": \"WiFi+BTOnboarding+BTNotify\",\n" +
|
||||
" \"deviceType\": \"Core400S\",\n" +
|
||||
" \"type\": \"wifi-air\",\n" +
|
||||
" \"uuid\": \"abcdefab-1234-1234-abcd-123498761234\",\n" +
|
||||
" \"configModule\": \"WiFiBTOnboardingNotify_AirPurifier_Core400S_EU\",\n" +
|
||||
" \"macID\": \"ab:cd:ef:12:34:56\",\n" +
|
||||
" \"mode\": \"simModeData\",\n" +
|
||||
" \"speed\": 4,\n" +
|
||||
" \"extension\": {\n" +
|
||||
" \"airQuality\": -1,\n" +
|
||||
" \"airQualityLevel\": 1,\n" +
|
||||
" \"mode\": \"auto\",\n" +
|
||||
" \"fanSpeedLevel\": \"1\"\n" +
|
||||
" },\n" +
|
||||
" \"currentFirmVersion\": null,\n" +
|
||||
" \"subDeviceNo\": \"simSubDevice\",\n" +
|
||||
" \"subDeviceType\": \"simSubDeviceType\",\n" +
|
||||
" \"deviceFirstSetupTime\": \"Oct 15, 2021 3:43:02 PM\"\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
|
||||
@Test
|
||||
public void testParseManagedDevicesSearchGoodResponse() {
|
||||
VesyncManagedDevicesPage response = VeSyncConstants.GSON.fromJson(testGoodSearchResponsePageBody,
|
||||
VesyncManagedDevicesPage.class);
|
||||
if (response != null) {
|
||||
assertEquals("1634387642", response.getTraceId());
|
||||
assertEquals("1", response.result.getPageNo());
|
||||
assertEquals("100", response.result.getPageSize());
|
||||
assertEquals("2", response.result.getTotal());
|
||||
assertEquals("1", String.valueOf(response.result.list.length));
|
||||
|
||||
assertEquals("EU", response.result.list[0].getDeviceRegion());
|
||||
assertEquals("Air Filter", response.result.list[0].getDeviceName());
|
||||
assertEquals("https://image.vesync.com/defaultImages/Core_400S_Series/icon_core400s_purifier_160.png", response.result.list[0].getDeviceImg());
|
||||
assertEquals("on", response.result.list[0].getDeviceStatus());
|
||||
assertEquals("online", response.result.list[0].getConnectionStatus());
|
||||
assertEquals("WiFi+BTOnboarding+BTNotify", response.result.list[0].getConnectionType());
|
||||
assertEquals("Core400S", response.result.list[0].getDeviceType());
|
||||
assertEquals("wifi-air", response.result.list[0].getType());
|
||||
assertEquals("abcdefab-1234-1234-abcd-123498761234", response.result.list[0].getUuid());
|
||||
assertEquals("WiFiBTOnboardingNotify_AirPurifier_Core400S_EU", response.result.list[0].getConfigModule());
|
||||
assertEquals("simModeData",response.result.list[0].getMode());
|
||||
assertEquals("simSubDevice", response.result.list[0].getSubDeviceNo());
|
||||
assertEquals("simSubDeviceType", response.result.list[0].getSubDeviceType());
|
||||
assertEquals( "4", response.result.list[0].getSpeed());
|
||||
assertEquals("cidValue1",response.result.list[0].getCid());
|
||||
assertEquals("ab:cd:ef:12:34:56", response.result.list[0].getMacId());
|
||||
} else {
|
||||
fail("GSON returned null");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.handler.responses;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.vesync.internal.VeSyncConstants;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VesyncResponse;
|
||||
|
||||
/**
|
||||
* The {@link VesyncResponseTest} class implements unit test case for {@link VesyncResponse}
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VesyncResponseTest {
|
||||
|
||||
@Test
|
||||
public void checkBaseFields() {
|
||||
String baseTestResponse = "{\"traceId\":\"1234569876\",\r\n\"code\": 142,\r\n\"msg\": \"Response Text\"\r\n}";
|
||||
VesyncResponse response = VeSyncConstants.GSON.fromJson(baseTestResponse, VesyncResponse.class);
|
||||
if (response != null) {
|
||||
assertEquals("1234569876", response.getTraceId());
|
||||
assertEquals("142", response.getCode());
|
||||
assertEquals("Response Text", response.msg);
|
||||
assertEquals(false, response.isMsgSuccess());
|
||||
} else {
|
||||
fail("GSON returned null");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkResponseSuccessMsg() {
|
||||
String baseTestResponse = "{\"traceId\":\"1234569876\",\r\n\"code\": 142,\r\n\"msg\": \"request success\"\r\n}";
|
||||
VesyncResponse response = VeSyncConstants.GSON.fromJson(baseTestResponse, VesyncResponse.class);
|
||||
if (response != null) {
|
||||
assertEquals(true, response.isMsgSuccess());
|
||||
} else {
|
||||
fail("GSON returned null");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.vesync.internal.handler.responses.v1;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.vesync.internal.VeSyncConstants;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VesyncLoginResponse;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.v1.VesyncV1AirPurifierDeviceDetailsResponse;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* The {@link VesyncV1AirPurifierDeviceDetailsTest} class implements unit test case for {@link VesyncLoginResponse}
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VesyncV1AirPurifierDeviceDetailsTest {
|
||||
|
||||
public final static String testAirPurifierResponseBasedOnCore400S = "{\n" +
|
||||
" \"code\": 0,\n" +
|
||||
" \"msg\": \"request success\",\n" +
|
||||
" \"traceId\": \"1634255391\",\n" +
|
||||
" \"screenStatus\": \"on1\",\n" +
|
||||
" \"airQuality\": 1,\n" +
|
||||
" \"level\": 2,\n" +
|
||||
" \"mode\": \"manual\",\n" +
|
||||
" \"deviceName\": \"Lounge Air Purifier\",\n" +
|
||||
" \"currentFirmVersion\": \"1.0.17\",\n" +
|
||||
" \"childLock\": \"off1\",\n" +
|
||||
" \"deviceStatus\": \"on2\",\n" +
|
||||
" \"deviceImg\": \"https://image.vesync.com/defaultImages/Core_400S_Series/icon_core400s_purifier_160.png\",\n" +
|
||||
" \"connectionStatus\": \"online\"\n" +
|
||||
"}";
|
||||
|
||||
@Test
|
||||
public void testParseV1AirPurifierDeviceDetailsResponse() {
|
||||
VesyncV1AirPurifierDeviceDetailsResponse response = VeSyncConstants.GSON.fromJson(testAirPurifierResponseBasedOnCore400S,
|
||||
VesyncV1AirPurifierDeviceDetailsResponse.class);
|
||||
|
||||
if (response != null) {
|
||||
assertEquals("on1", response.getScreenStatus());
|
||||
assertEquals(1, response.getAirQuality());
|
||||
assertEquals(2, response.getLevel());
|
||||
assertEquals("manual", response.getMode());
|
||||
assertEquals("Lounge Air Purifier", response.getDeviceName());
|
||||
assertEquals("1.0.17", response.getCurrentFirmVersion());
|
||||
assertEquals("off1", response.getChildLock());
|
||||
assertEquals("on2", response.getDeviceStatus());
|
||||
assertEquals("https://image.vesync.com/defaultImages/Core_400S_Series/icon_core400s_purifier_160.png", response.getDeviceImgUrl());
|
||||
assertEquals("online", response.getConnectionStatus());
|
||||
} else {
|
||||
fail("GSON returned null");
|
||||
}
|
||||
}
|
||||
}
|
@ -370,6 +370,7 @@
|
||||
<module>org.openhab.binding.venstarthermostat</module>
|
||||
<module>org.openhab.binding.ventaair</module>
|
||||
<module>org.openhab.binding.verisure</module>
|
||||
<module>org.openhab.binding.vesync</module>
|
||||
<module>org.openhab.binding.vigicrues</module>
|
||||
<module>org.openhab.binding.vitotronic</module>
|
||||
<module>org.openhab.binding.volvooncall</module>
|
||||
|
Loading…
Reference in New Issue
Block a user