mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[VeSync] Add support for wifi outlets (#17844)
* Add support for wifi outlets Signed-off-by: Marcel Goerentz <m.goerentz@t-online.de>
This commit is contained in:
parent
f78c618dad
commit
65664f2e2e
@ -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.
|
||||
|
||||
@ -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 |
|
||||
|
||||
@ -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)
|
||||
|
@ -40,4 +40,7 @@ public class VeSyncBridgeConfiguration {
|
||||
*/
|
||||
@Nullable
|
||||
public Integer airPurifierPollInterval;
|
||||
|
||||
@Nullable
|
||||
public Integer outletPollInterval;
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import com.google.gson.GsonBuilder;
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
* @author Marcel Goerentz - Add constants for outlets
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VeSyncConstants {
|
||||
@ -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";
|
||||
@ -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";
|
||||
|
@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
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;
|
||||
@ -45,7 +46,7 @@ import org.osgi.service.component.annotations.Reference;
|
||||
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;
|
||||
@ -73,6 +74,8 @@ public class VeSyncHandlerFactory extends BaseThingHandlerFactory {
|
||||
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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -23,6 +23,7 @@ import org.openhab.binding.vesync.internal.handlers.VeSyncBaseDeviceHandler;
|
||||
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;
|
||||
@ -37,6 +38,7 @@ import org.osgi.service.component.annotations.ServiceScope;
|
||||
* 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")
|
||||
@ -94,6 +96,24 @@ public class VeSyncDiscoveryService extends AbstractThingHandlerDiscoveryService
|
||||
|
||||
@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();
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
@ -53,6 +53,9 @@ public class VeSyncRequestManagedDeviceBypassV2 extends VeSyncAuthenticatedReque
|
||||
|
||||
@SerializedName("data")
|
||||
public EmptyPayload data = new EmptyPayload();
|
||||
|
||||
@SerializedName("subDeviceNo")
|
||||
public int subDeviceNo = 0;
|
||||
}
|
||||
|
||||
public static class EmptyPayload {
|
||||
@ -229,6 +232,20 @@ public class VeSyncRequestManagedDeviceBypassV2 extends VeSyncAuthenticatedReque
|
||||
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";
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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 com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncV2BypassOutletStatus} 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 VeSyncV2BypassOutletStatus extends VeSyncResponse {
|
||||
|
||||
@SerializedName("result")
|
||||
public OutletStatusResult outletResult;
|
||||
|
||||
public class OutletStatusResult extends VeSyncResponse {
|
||||
|
||||
@SerializedName("module")
|
||||
public Object object = null;
|
||||
|
||||
@SerializedName("stacktrace")
|
||||
public Object object2 = null;
|
||||
|
||||
@SerializedName("result")
|
||||
public Result result = new Result();
|
||||
|
||||
public class Result {
|
||||
|
||||
@SerializedName("enabled")
|
||||
public boolean enabled = false;
|
||||
|
||||
@SerializedName("voltage")
|
||||
public double voltage = 0.00;
|
||||
|
||||
@SerializedName("energy")
|
||||
public double energy = 0.00;
|
||||
|
||||
@SerializedName("power")
|
||||
public double power = 0.00;
|
||||
|
||||
@SerializedName("current")
|
||||
public double current = 0.00;
|
||||
|
||||
@SerializedName("highestVoltage")
|
||||
public int highestVoltage = 0;
|
||||
|
||||
@SerializedName("voltagePTStatus")
|
||||
public boolean voltagePTStatus = false;
|
||||
|
||||
public String getDeviceStatus() {
|
||||
return enabled ? "on" : "off";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -12,7 +12,9 @@
|
||||
*/
|
||||
package org.openhab.binding.vesync.internal.handlers;
|
||||
|
||||
import static org.openhab.binding.vesync.internal.VeSyncConstants.*;
|
||||
import static org.openhab.binding.vesync.internal.VeSyncConstants.DEVICE_PROP_BRIDGE_ACCEPT_LANG;
|
||||
import static org.openhab.binding.vesync.internal.VeSyncConstants.DEVICE_PROP_BRIDGE_COUNTRY_CODE;
|
||||
import static org.openhab.binding.vesync.internal.VeSyncConstants.DEVICE_PROP_BRIDGE_REG_TS;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
@ -192,6 +194,14 @@ public class VeSyncBridgeHandler extends BaseBridgeHandler implements VeSyncClie
|
||||
.equals(VeSyncBaseDeviceHandler.UNKNOWN));
|
||||
}
|
||||
|
||||
public java.util.stream.Stream<@NotNull VeSyncManagedDeviceBase> getOutletMetaData() {
|
||||
return api.getMacLookupMap().values().stream()
|
||||
.filter(x -> !VeSyncBaseDeviceHandler
|
||||
.getDeviceFamilyMetadata(x.getDeviceType(), VeSyncDeviceOutletHandler.DEV_TYPE_FAMILY_OUTLET,
|
||||
VeSyncDeviceOutletHandler.SUPPORTED_MODEL_FAMILIES)
|
||||
.equals(VeSyncBaseDeviceHandler.UNKNOWN));
|
||||
}
|
||||
|
||||
protected void updateThings() {
|
||||
final VeSyncBridgeConfiguration config = getConfigAs(VeSyncBridgeConfiguration.class);
|
||||
getThing().getThings().forEach((th) -> updateThing(config, th.getHandler()));
|
||||
|
@ -0,0 +1,217 @@
|
||||
/**
|
||||
* 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.handlers;
|
||||
|
||||
import static org.openhab.binding.vesync.internal.VeSyncConstants.*;
|
||||
import static org.openhab.binding.vesync.internal.dto.requests.VeSyncProtocolConstants.*;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
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.VeSyncV2BypassEnergyHistory;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VeSyncV2BypassEnergyHistory.EnergyHistory.Result.EnergyInfo;
|
||||
import org.openhab.binding.vesync.internal.dto.responses.VeSyncV2BypassOutletStatus;
|
||||
import org.openhab.core.cache.ExpiringCache;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.MetricPrefix;
|
||||
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.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VeSyncDeviceOutletHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Marcel Goerentz - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VeSyncDeviceOutletHandler extends VeSyncBaseDeviceHandler {
|
||||
|
||||
public static final String DEV_TYPE_FAMILY_OUTLET = "OUT";
|
||||
public static final int DEFAULT_OUTLET_POLL_RATE = 60;
|
||||
public static final String DEV_FAMILY_CORE_WHOG_PLUG = "WHOG";
|
||||
public static final VeSyncDeviceMetadata COREWHOPGPLUG = new VeSyncDeviceMetadata(DEV_FAMILY_CORE_WHOG_PLUG,
|
||||
Arrays.asList("WHOG"), List.of("WHOGPLUG"));
|
||||
public static final List<VeSyncDeviceMetadata> SUPPORTED_MODEL_FAMILIES = Arrays.asList(COREWHOPGPLUG);
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_OUTLET);
|
||||
private final Logger logger = LoggerFactory.getLogger(VeSyncDeviceOutletHandler.class);
|
||||
private final Object pollLock = new Object();
|
||||
|
||||
public VeSyncDeviceOutletHandler(Thing thing, @Reference TranslationProvider translationProvider,
|
||||
@Reference LocaleProvider localeProvider) {
|
||||
super(thing, translationProvider, localeProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
customiseChannels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDeviceFamilyProtocolPrefix() {
|
||||
return DEV_TYPE_FAMILY_OUTLET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
final String deviceFamily = getThing().getProperties().get(DEVICE_PROP_DEVICE_FAMILY);
|
||||
if (deviceFamily == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler.submit(() -> {
|
||||
if (command instanceof OnOffType) {
|
||||
if (channelUID.getId().equals(DEVICE_CHANNEL_ENABLED)) {
|
||||
sendV2BypassControlCommand(DEVICE_SET_SWITCH,
|
||||
new VeSyncRequestManagedDeviceBypassV2.SetSwitchPayload(command.equals(OnOffType.ON), 0));
|
||||
}
|
||||
} else if (command instanceof RefreshType) {
|
||||
pollForUpdate();
|
||||
} else {
|
||||
logger.trace("UNKNOWN COMMAND: {} {}", command.getClass().toString(), channelUID);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBridgeBasedPolls(final VeSyncBridgeConfiguration config) {
|
||||
Integer pollRate = config.outletPollInterval;
|
||||
if (pollRate == null) {
|
||||
pollRate = (Integer) DEFAULT_OUTLET_POLL_RATE;
|
||||
}
|
||||
if (ThingStatus.OFFLINE.equals(getThing().getStatus())) {
|
||||
setBackgroundPollInterval(-1);
|
||||
} else {
|
||||
setBackgroundPollInterval(pollRate);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void pollForDeviceData(ExpiringCache<String> cachedResponse) {
|
||||
processV2BypassPoll(cachedResponse);
|
||||
}
|
||||
|
||||
private void processV2BypassPoll(final ExpiringCache<String> cachedResponse) {
|
||||
String responseStatus = EMPTY_STRING;
|
||||
String responseEnergyHistory = EMPTY_STRING;
|
||||
String responses;
|
||||
VeSyncV2BypassOutletStatus outletStatus;
|
||||
VeSyncV2BypassEnergyHistory energyHistory;
|
||||
synchronized (pollLock) {
|
||||
responses = cachedResponse.getValue();
|
||||
boolean cachedDataUsed = responses != null;
|
||||
if (responses == null) {
|
||||
logger.trace("Requesting fresh response");
|
||||
responseStatus = sendV2BypassCommand(DEVICE_GET_OUTLET_STATUS,
|
||||
new VeSyncRequestManagedDeviceBypassV2.EmptyPayload());
|
||||
|
||||
try {
|
||||
long end = getTimestampForToday();
|
||||
long start = end - SECONDS_IN_MONTH; // 30 days
|
||||
responseEnergyHistory = sendV2BypassCommand(DEVICE_GET_ENEGERGY_HISTORY,
|
||||
new VeSyncRequestManagedDeviceBypassV2.GetEnergyHistory(start, end));
|
||||
} catch (ParseException e) {
|
||||
logger.error("Could not parse timestamp: {}", e.getMessage());
|
||||
}
|
||||
} else {
|
||||
logger.trace("Using cached response {}", responses);
|
||||
if (responses.contains("?")) {
|
||||
String[] responseStrings = responses.split("/?");
|
||||
responseStatus = responseStrings[0];
|
||||
responseEnergyHistory = responseStrings[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (responseStatus.equals(EMPTY_STRING) || responseEnergyHistory.equals(EMPTY_STRING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
outletStatus = VeSyncConstants.GSON.fromJson(responseStatus, VeSyncV2BypassOutletStatus.class);
|
||||
energyHistory = VeSyncConstants.GSON.fromJson(responseEnergyHistory, VeSyncV2BypassEnergyHistory.class);
|
||||
|
||||
if (outletStatus == null || energyHistory == null) {
|
||||
return;
|
||||
}
|
||||
if (!cachedDataUsed) {
|
||||
cachedResponse.putValue(responseStatus + "?" + responseEnergyHistory);
|
||||
}
|
||||
}
|
||||
|
||||
// Bail and update the status of the thing - it will be updated to online by the next search
|
||||
// that detects it is online.
|
||||
if (outletStatus.isMsgDeviceOffline()) {
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
return;
|
||||
} else if (outletStatus.isMsgSuccess()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
if (!"0".equals(outletStatus.getCode())) {
|
||||
logger.warn("{}", getLocalizedText("warning.device.unexpected-resp-for-wifi-outlet"));
|
||||
return;
|
||||
}
|
||||
|
||||
updateState(DEVICE_CHANNEL_ENABLED,
|
||||
OnOffType.from(MODE_ON.equals(outletStatus.outletResult.result.getDeviceStatus())));
|
||||
updateState(DEVICE_CHANNEL_CURRENT, new QuantityType<>(outletStatus.outletResult.result.current, Units.AMPERE));
|
||||
updateState(DEVICE_CHANNEL_VOLTAGE, new QuantityType<>(outletStatus.outletResult.result.voltage, Units.VOLT));
|
||||
updateState(DEVICE_CHANNEL_ENERGY, new QuantityType<>(outletStatus.outletResult.result.energy, Units.WATT));
|
||||
updateState(DEVICE_CHANNEL_POWER,
|
||||
new QuantityType<>(outletStatus.outletResult.result.power, MetricPrefix.KILO(Units.WATT_HOUR)));
|
||||
updateState(DEVICE_CHANNEL_HIGHEST_VOLTAGE,
|
||||
new QuantityType<>(outletStatus.outletResult.result.highestVoltage, Units.VOLT));
|
||||
updateState(DEVICE_CHANNEL_VOLTAGE_PT_STATUS, OnOffType.from(outletStatus.outletResult.result.voltagePTStatus));
|
||||
updateState(DEVICE_CHANNEL_ENERGY_WEEK,
|
||||
new QuantityType<>(getEnergy(energyHistory, 7), MetricPrefix.KILO(Units.WATT_HOUR)));
|
||||
updateState(DEVICE_CHANNEL_ENERGY_MONTH,
|
||||
new QuantityType<>(getEnergy(energyHistory, 30), MetricPrefix.KILO(Units.WATT_HOUR)));
|
||||
}
|
||||
|
||||
private static long getTimestampForToday() throws ParseException {
|
||||
Instant instant = Instant.now().truncatedTo(ChronoUnit.DAYS);
|
||||
return instant.toEpochMilli() / 1000;
|
||||
}
|
||||
|
||||
private static double getEnergy(VeSyncV2BypassEnergyHistory energyHistory, int days) {
|
||||
List<EnergyInfo> energyList = energyHistory.result.result.energyInfos;
|
||||
double energy = 0;
|
||||
for (byte i = 0; i < days; i++) {
|
||||
energy += energyList.get(i).energy;
|
||||
}
|
||||
return energy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VeSyncDeviceMetadata> getSupportedDeviceMetadata() {
|
||||
return SUPPORTED_MODEL_FAMILIES;
|
||||
}
|
||||
}
|
@ -119,5 +119,6 @@ warning.device.humidity-under = Target Humidity less than {0} - adjusting to {0}
|
||||
warning.device.humidity-over = Target Humidity greater than {0} - adjusting to {0} as the valid API value
|
||||
warning.device.humidity-mode = Humidifier mode command for {0} is not valid in the ({1}}) API possible options {2}
|
||||
warning.device.warm-mode-unsupported = Warm mode API is unknown in order to send the command
|
||||
warning.device.unexpected-resp-for-air-purifier = Check Thing type has been set - API gave a unexpected response for an Air Purifier
|
||||
warning.device.unexpected-resp-for-air-humidifier = Check Thing type has been set - API gave a unexpected response for an Air Humidifier
|
||||
warning.device.unexpected-resp-for-air-purifier = Check Thing type has been set - API gave an unexpected response for an Air Purifier
|
||||
warning.device.unexpected-resp-for-air-humidifier = Check Thing type has been set - API gave an unexpected response for an Air Humidifier
|
||||
warning.device.unexpected-resp-for-wifi-outlet = Check Thing type has been set - API gave an unexpected response for an Outlet
|
||||
|
@ -37,9 +37,55 @@
|
||||
<description>Seconds between fetching background updates about the air purifiers / humidifiers.</description>
|
||||
<default>60</default>
|
||||
</parameter>
|
||||
<parameter name="outletPollInterval" type="integer" min="5" step="1" unit="s">
|
||||
<label>Outlet Poll Rate</label>
|
||||
<description>Seconds between fetching background updates about the outlets.</description>
|
||||
<default>60</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<thing-type id="outlet">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Outlet via VeSync</label>
|
||||
<description>An Outlet uplinking to VeSync</description>
|
||||
|
||||
<channels>
|
||||
<channel id="enabled" typeId="deviceEnabledType"/>
|
||||
<channel id="current" typeId="currentType"/>
|
||||
<channel id="energy" typeId="energyType"/>
|
||||
<channel id="power" typeId="powerType"/>
|
||||
<channel id="voltage" typeId="voltageType"/>
|
||||
<channel id="highestVoltage" typeId="highestVoltageType"/>
|
||||
<channel id="voltagePTStatus" typeId="voltagePTStatusType"/>
|
||||
<channel id="energyWeek" typeId="energyWeekType"/>
|
||||
<channel id="energyMonth" typeId="energyMonthType"/>
|
||||
<channel id="energyYear" typeId="energyYearType"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="deviceName"/>
|
||||
<property name="deviceType"/>
|
||||
<property name="mac"/>
|
||||
<property name="deviceFamily"/>
|
||||
</properties>
|
||||
<representation-property>mac</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="mac" type="text">
|
||||
<label>MAC</label>
|
||||
<description>The MAC 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="airPurifier">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
@ -138,6 +184,68 @@
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="currentType">
|
||||
<item-type unitHint="A">Number:ElectricCurrent</item-type>
|
||||
<label>Current</label>
|
||||
<description>Actual current in A</description>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="energyType">
|
||||
<item-type unitHint="kWh">Number:Energy</item-type>
|
||||
<label>Energy</label>
|
||||
<description>Today's energy in kWh</description>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="powerType">
|
||||
<item-type unitHint="W">Number:Power</item-type>
|
||||
<label>Power</label>
|
||||
<description>Current power in W</description>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="voltageType">
|
||||
<item-type unitHint="V">Number:ElectricPotential</item-type>
|
||||
<label>Voltage</label>
|
||||
<description>Current Voltage</description>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="highestVoltageType">
|
||||
<item-type unitHint="V">Number:ElectricPotential</item-type>
|
||||
<label>Highest Voltage</label>
|
||||
<description>Highest Voltage ever measured by the outlet</description>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="voltagePTStatusType">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Voltage PT Status</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="energyWeekType">
|
||||
<item-type unitHint="kWh">Number:Energy</item-type>
|
||||
<label>Energy Week</label>
|
||||
<description>Total energy of week in kWh</description>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="energyMonthType">
|
||||
<item-type unitHint="kWh">Number:Energy</item-type>
|
||||
<label>Energy Month</label>
|
||||
<description>Total energy of month in kWh</description>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="energyYearType">
|
||||
<item-type unitHint="kWh">Number:Energy</item-type>
|
||||
<label>Energy Year</label>
|
||||
<description>Total energy of year in kWh</description>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceEnabledType">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Switched On</label>
|
||||
|
Loading…
Reference in New Issue
Block a user