Skip to content

Commit

Permalink
[VeSync] Add support for wifi outlets (#17844)
Browse files Browse the repository at this point in the history
* Add support for wifi outlets

Signed-off-by: Marcel Goerentz <[email protected]>
  • Loading branch information
marcelGoerentz authored Dec 19, 2024
1 parent f78c618 commit 65664f2
Show file tree
Hide file tree
Showing 15 changed files with 614 additions and 5 deletions.
17 changes: 17 additions & 0 deletions bundles/org.openhab.binding.vesync/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ This binding supports the follow thing types:
| Bridge | Bridge | bridge | Manual | A single connection to the VeSync API |
| Air Purifier | Thing | airPurifier | Automatic | An Air Purifier supporting V2 e.g. Core200S/Core300S or Core400S unit |
| Air Humidifier | Thing | airHumidifier | Automatic | An Air Humidifier supporting V2 e.g. Classic300S or 600s |
| Outlet | Thing | outlet | Automatic | An Outlet supporting V2 eg WHOGPLUG |

This binding was developed from the great work in the listed projects.

Expand All @@ -40,6 +41,7 @@ Once the bridge is configured auto discovery will discover supported devices fro
| 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 |
| outletPollInterval | Number | The poll interval (seconds) for outlets | 60 |
| backgroundDeviceDiscovery | Switch | Should the system scan periodically for new devices | ON |
| refreshBackgroundDeviceDiscovery | Number | Frequency (seconds) of scans for new new devices | 120 |

Expand Down Expand Up @@ -111,6 +113,21 @@ Channel names in **bold** are read/write, everything else is read-only
| timerExpiry | DateTime | The expected expiry time of the current timer | OasisMist1000 | | |
| schedulesCount | Number:Dimensionless | The number schedules configured | OasisMist1000 | | one |

### Outlet Thing

| Channel | Type | Description | Model's Supported | Controllable Values |
|-----------------|------------------------|------------------------------------------------------|-------------------|---------------------|
| **enabled** | Switch | Whether the hardware device is enabled (Switched on) | WHOGPLUG | [ON, OFF] |
| current | Number:ElectricCurrent | Actual current in A | WHOGPLUG | |
| energy | Number:Energy | Today's energy in kWh | WHOGPLUG | |
| power | Number:Power | Current power in W | WHOGPLUG | |
| voltage | ElectricPotential | Current Voltage | WHOGPLUG | |
| highestVoltage | ElectricPotential | Highest Voltage ever measured by the outlet | WHOGPLUG | |
| energyWeek | Number:Energy | Total energy of week in kWh | WHOGPLUG | |
| energyMonth | Number:Energy | Total energy of month in kWh | WHOGPLUG | |
| energyYear | Number:Energy | Total energy of year in kWh | WHOGPLUG | |


## Full Example

### Configuration (*.things)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,7 @@ public class VeSyncBridgeConfiguration {
*/
@Nullable
public Integer airPurifierPollInterval;

@Nullable
public Integer outletPollInterval;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* used across the whole binding.
*
* @author David Goodyear - Initial contribution
* @author Marcel Goerentz - Add constants for outlets
*/
@NonNullByDefault
public class VeSyncConstants {
Expand All @@ -36,11 +37,13 @@ public class VeSyncConstants {

public static final long DEFAULT_REFRESH_INTERVAL_DISCOVERED_DEVICES = 3600;
public static final long DEFAULT_POLL_INTERVAL_AIR_FILTERS_DEVICES = 10;
public static final long SECONDS_IN_MONTH = 2592000;

// 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");
public static final ThingTypeUID THING_TYPE_OUTLET = new ThingTypeUID(BINDING_ID, "outlet");

// Thing configuration properties
public static final String DEVICE_MAC_ID = "macAddress";
Expand Down Expand Up @@ -68,6 +71,17 @@ public class VeSyncConstants {
public static final String DEVICE_CHANNEL_AF_LIGHT_DETECTION = "lightDetection";
public static final String DEVICE_CHANNEL_AF_LIGHT_DETECTED = "lightDetected";

// Energy Related Channel Names
public static final String DEVICE_CHANNEL_CURRENT = "current";
public static final String DEVICE_CHANNEL_ENERGY = "energy";
public static final String DEVICE_CHANNEL_POWER = "power";
public static final String DEVICE_CHANNEL_VOLTAGE = "voltage";
public static final String DEVICE_CHANNEL_VOLTAGE_PT_STATUS = "voltagePTStatus";
public static final String DEVICE_CHANNEL_HIGHEST_VOLTAGE = "highestVoltage";
public static final String DEVICE_CHANNEL_ENERGY_WEEK = "energyWeek";
public static final String DEVICE_CHANNEL_ENERGY_MONTH = "energyMonth";
public static final String DEVICE_CHANNEL_ENERGY_YEAR = "energyYear";

// Humidity related channels
public static final String DEVICE_CHANNEL_WATER_LACKS = "waterLacking";
public static final String DEVICE_CHANNEL_HUMIDITY_HIGH = "humidityHigh";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
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.binding.vesync.internal.handlers.VeSyncDeviceOutletHandler;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
Expand All @@ -45,7 +46,7 @@
public class VeSyncHandlerFactory extends BaseThingHandlerFactory {

private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BRIDGE,
THING_TYPE_AIR_PURIFIER, THING_TYPE_AIR_HUMIDIFIER);
THING_TYPE_AIR_PURIFIER, THING_TYPE_AIR_HUMIDIFIER, THING_TYPE_OUTLET);

private final HttpClientFactory httpClientFactory;
private final TranslationProvider translationProvider;
Expand Down Expand Up @@ -73,6 +74,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return new VeSyncDeviceAirPurifierHandler(thing, translationProvider, localeProvider);
} else if (VeSyncDeviceAirHumidifierHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
return new VeSyncDeviceAirHumidifierHandler(thing, translationProvider, localeProvider);
} else if (VeSyncDeviceOutletHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
return new VeSyncDeviceOutletHandler(thing, translationProvider, localeProvider);
} else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) {
return new VeSyncBridgeHandler((Bridge) thing, httpClientFactory, translationProvider, localeProvider);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
*/
package org.openhab.binding.vesync.internal.api;

import static org.openhab.binding.vesync.internal.dto.requests.VeSyncProtocolConstants.*;
import static org.openhab.binding.vesync.internal.dto.requests.VeSyncProtocolConstants.V1_LOGIN_ENDPOINT;
import static org.openhab.binding.vesync.internal.dto.requests.VeSyncProtocolConstants.V1_MANAGED_DEVICES_ENDPOINT;

import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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.binding.vesync.internal.handlers.VeSyncDeviceOutletHandler;
import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
Expand All @@ -37,6 +38,7 @@
* read by the bridge, and the discovery data updated via a callback implemented by the DeviceMetaDataUpdatedHandler.
*
* @author David Godyear - Initial contribution
* @author Marcel Goerentz - Add support for outlets
*/
@NonNullByDefault
@Component(scope = ServiceScope.PROTOTYPE, service = VeSyncDiscoveryService.class, configurationPid = "discovery.vesync")
Expand Down Expand Up @@ -94,6 +96,24 @@ protected void startScan() {

@Override
public void handleMetadataRetrieved(VeSyncBridgeHandler handler) {
thingHandler.getOutletMetaData().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_FAMILY,
VeSyncBaseDeviceHandler.getDeviceFamilyMetadata(apMeta.getDeviceType(),
VeSyncDeviceOutletHandler.DEV_TYPE_FAMILY_OUTLET,
VeSyncDeviceOutletHandler.SUPPORTED_MODEL_FAMILIES));
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_OUTLET, bridgeUID, deviceUUID))
.withLabel(apMeta.getDeviceName()).withBridge(bridgeUID).withProperties(properties)
.withRepresentationProperty(DEVICE_PROP_DEVICE_MAC_ID).build();
}).forEach(this::thingDiscovered);

thingHandler.getAirPurifiersMetadata().map(apMeta -> {
final Map<String, Object> properties = new HashMap<>(6);
final String deviceUUID = apMeta.getUuid();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public interface VeSyncProtocolConstants {
String DEVICE_SET_DISPLAY = "setDisplay";
String DEVICE_SET_LEVEL = "setLevel";

// Outlet Commands
String DEVICE_GET_OUTLET_STATUS = "getOutletStatus";
String DEVICE_GET_ENEGERGY_HISTORY = "getEnergyHistory";

// Humidifier Commands
String DEVICE_SET_AUTOMATIC_STOP = "setAutomaticStop";
String DEVICE_SET_HUMIDITY_MODE = "setHumidityMode";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vesync.internal.dto.requests;

import org.eclipse.jdt.annotation.NonNullByDefault;

import com.google.gson.annotations.SerializedName;

/**
* The {@link VeSyncRequestGetOutletStatus} is a Java class used as a DTO to hold the Vesync's API's common
* request data for V2 ByPass payloads.
*
* @author Marcel Goerentz - Initial contribution
*/
@NonNullByDefault
public class VeSyncRequestGetOutletStatus extends VeSyncRequest {

@SerializedName("cid")
public String cid = "";

@SerializedName("configModule")
public String configModule = "";

@SerializedName("debugMode")
public boolean debugMode = false;

@SerializedName("subDeviceNo")
public int subDeviceNo = 0;

@SerializedName("token")
public String token = "";

@SerializedName("userCountryCode")
public String userCountryCode = "";

@SerializedName("deviceId")
public String deviceId = "";

@SerializedName("configModel")
public String configModel = "";

@SerializedName("payload")
public Payload payload = new Payload();

public class Payload {

@SerializedName("data")
public Data data = new Data();

// Empty class
public class Data {
}

@SerializedName("method")
public String method = "";

@SerializedName("subDeviceNo")
public int subDeviceNo = 0;

@SerializedName("source")
public String source = "APP";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ public class VesyncManagedDeviceBase {

@SerializedName("data")
public EmptyPayload data = new EmptyPayload();

@SerializedName("subDeviceNo")
public int subDeviceNo = 0;
}

public static class EmptyPayload {
Expand Down Expand Up @@ -229,6 +232,20 @@ public SetMode(final String mode) {
public String mode = "";
}

public static class GetEnergyHistory extends EmptyPayload {

public GetEnergyHistory(final long start, final long end) {
this.start = start;
this.end = end;
}

@SerializedName("fromDay")
public long start = 0;

@SerializedName("toDay")
public long end = 0;
}

public VeSyncRequestManagedDeviceBypassV2() {
super();
method = "bypassV2";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vesync.internal.dto.responses;

import java.util.ArrayList;
import java.util.List;

import com.google.gson.annotations.SerializedName;

/**
* The {@link VeSyncV2BypassEnergyHistory} is a Java class used as a DTO to hold the Vesync's API's common response
* data, in regard to an outlet device.
*
* @author Marcel Goerentz - Initial contribution
*/
public class VeSyncV2BypassEnergyHistory extends VeSyncResponse {

@SerializedName("result")
public EnergyHistory result;

public class EnergyHistory extends VeSyncResponse {

@SerializedName("result")
public Result result = new Result();

public class Result {

@SerializedName("energyInfos")
public List<EnergyInfo> energyInfos = new ArrayList<EnergyInfo>();

public class EnergyInfo {

@SerializedName("timestamp")
public long timestamp = 0;

@SerializedName("energy")
public double energy = 0.00;
}

@SerializedName("total")
public int total = 0;
}
}
}
Loading

0 comments on commit 65664f2

Please sign in to comment.