Skip to content

Commit

Permalink
[veSync] 131 and Vital Purifiers base support (#15296)
Browse files Browse the repository at this point in the history
* [veSync] Device support enhancements

Device support enhancements
Signed-off-by: David Goodyear <[email protected]>
  • Loading branch information
dag81 authored Dec 3, 2024
1 parent fd7fd8a commit b00a44a
Show file tree
Hide file tree
Showing 28 changed files with 1,530 additions and 337 deletions.
205 changes: 160 additions & 45 deletions bundles/org.openhab.binding.vesync/README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class VeSyncConstants {

public static final Gson GSON = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).setPrettyPrinting()
.disableHtmlEscaping().serializeNulls().create();
.disableHtmlEscaping().create();

private static final String BINDING_ID = "vesync";

Expand Down Expand Up @@ -65,6 +65,8 @@ public class VeSyncConstants {
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";
public static final String DEVICE_CHANNEL_AF_LIGHT_DETECTION = "lightDetection";
public static final String DEVICE_CHANNEL_AF_LIGHT_DETECTED = "lightDetected";

// Humidity related channels
public static final String DEVICE_CHANNEL_WATER_LACKS = "waterLacking";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,19 @@

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.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

Expand All @@ -41,12 +42,23 @@
*/
@NonNullByDefault
@Component(configurationPid = "binding.vesync", service = ThingHandlerFactory.class)
public class VeSyncHandlerFactory extends BaseThingHandlerFactory implements IHttpClientProvider {
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);

private @Nullable HttpClient httpClientRef = null;
private final HttpClientFactory httpClientFactory;
private final TranslationProvider translationProvider;
private final LocaleProvider localeProvider;

@Activate
public VeSyncHandlerFactory(@Reference HttpClientFactory httpClientFactory,
@Reference TranslationProvider translationProvider, @Reference LocaleProvider localeProvider) {
super();
this.httpClientFactory = httpClientFactory;
this.translationProvider = translationProvider;
this.localeProvider = localeProvider;
}

@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
Expand All @@ -58,23 +70,13 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
final ThingTypeUID thingTypeUID = thing.getThingTypeUID();

if (VeSyncDeviceAirPurifierHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
return new VeSyncDeviceAirPurifierHandler(thing);
return new VeSyncDeviceAirPurifierHandler(thing, translationProvider, localeProvider);
} else if (VeSyncDeviceAirHumidifierHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
return new VeSyncDeviceAirHumidifierHandler(thing);
return new VeSyncDeviceAirHumidifierHandler(thing, translationProvider, localeProvider);
} else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) {
return new VeSyncBridgeHandler((Bridge) thing, this);
return new VeSyncBridgeHandler((Bridge) thing, httpClientFactory, translationProvider, localeProvider);
}

return null;
}

@Reference
protected void setHttpClientFactory(HttpClientFactory httpClientFactory) {
httpClientRef = httpClientFactory.getCommonHttpClient();
}

@Override
public @Nullable HttpClient getHttpClient() {
return httpClientRef;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.openhab.binding.vesync.internal.VeSyncConstants;
import org.openhab.binding.vesync.internal.dto.requests.VeSyncAuthenticatedRequest;
import org.openhab.binding.vesync.internal.dto.requests.VeSyncLoginCredentials;
Expand All @@ -58,27 +59,26 @@ public class VeSyncV2ApiHelper {

private final Logger logger = LoggerFactory.getLogger(VeSyncV2ApiHelper.class);

private @NonNullByDefault({}) HttpClient httpClient;
private static final int RESPONSE_TIMEOUT_SEC = 5;

private volatile @Nullable VeSyncUserSession loggedInSession;

private final @Nullable HttpClient httpClient;

private Map<String, @NotNull VeSyncManagedDeviceBase> macLookup;

public VeSyncV2ApiHelper() {
public VeSyncV2ApiHelper(final HttpClient httpClient) {
this.httpClient = httpClient;
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 void dispose() {
loggedInSession = null;
macLookup.clear();
}

public static @NotNull String calculateMd5(final @Nullable String password) {
Expand Down Expand Up @@ -154,6 +154,7 @@ public String reqV2Authorized(final String url, final String macId, final VeSync
}
veSyncRequestManagedDeviceBypassV2.cid = deviceData.cid;
veSyncRequestManagedDeviceBypassV2.configModule = deviceData.configModule;
veSyncRequestManagedDeviceBypassV2.configModel = deviceData.configModule;
veSyncRequestManagedDeviceBypassV2.deviceRegion = deviceData.deviceRegion;
}
return reqV1Authorized(url, requestData);
Expand All @@ -167,16 +168,22 @@ public String reqV1Authorized(final String url, final VeSyncAuthenticatedRequest
private String directReqV1Authorized(final String url, final VeSyncAuthenticatedRequest requestData)
throws AuthenticationException {
try {
Request request = httpClient.POST(url);
final HttpClient client = httpClient;
if (client == null) {
throw new AuthenticationException("No HTTP Client");
}
Request request = client.newRequest(url).method(requestData.httpMethod).timeout(RESPONSE_TIMEOUT_SEC,
TimeUnit.SECONDS);

// No headers for login
request.content(new StringContentProvider(VeSyncConstants.GSON.toJson(requestData)));

logger.debug("POST @ {} with content\r\n{}", url, VeSyncConstants.GSON.toJson(requestData));
logger.debug("{} @ {} with content\r\n{}", requestData.httpMethod, url,
VeSyncConstants.GSON.toJson(requestData));

request.header(HttpHeader.CONTENT_TYPE, "application/json; utf-8");

ContentResponse response = request.timeout(5, TimeUnit.SECONDS).send();
ContentResponse response = request.send();
if (response.getStatus() == HttpURLConnection.HTTP_OK) {
VeSyncResponse commResponse = VeSyncConstants.GSON.fromJson(response.getContentAsString(),
VeSyncResponse.class);
Expand Down Expand Up @@ -220,15 +227,20 @@ public void updateBridgeData(final VeSyncBridgeHandler bridge) {
private VeSyncLoginResponse processLogin(String username, String password, String timezone)
throws AuthenticationException {
try {
Request request = httpClient.POST(V1_LOGIN_ENDPOINT);
final HttpClient client = httpClient;
if (client == null) {
throw new AuthenticationException("No HTTP Client");
}
Request request = client.newRequest(V1_LOGIN_ENDPOINT).method(HttpMethod.POST).timeout(RESPONSE_TIMEOUT_SEC,
TimeUnit.SECONDS);

// 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();
ContentResponse response = request.send();
if (response.getStatus() == HttpURLConnection.HTTP_OK) {
VeSyncLoginResponse loginResponse = VeSyncConstants.GSON.fromJson(response.getContentAsString(),
VeSyncLoginResponse.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public interface VeSyncProtocolConstants {
String MODE_AUTO = "auto";
String MODE_MANUAL = "manual";
String MODE_SLEEP = "sleep";
String MODE_PET = "pet";
String MODE_AUTO_HUMIDITY = "humidity";

String MODE_ON = "on";
String MODE_DIM = "dim";
Expand All @@ -42,19 +44,24 @@ public interface VeSyncProtocolConstants {
String DEVICE_GET_HUMIDIFIER_STATUS = "getHumidifierStatus";

String DEVICE_LEVEL_TYPE_MIST = "mist";
String DEVICE_LEVEL_TYPE_WARM_MIST = "warm";

// 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";
String DEVICE_SET_LIGHT_DETECTION = "setLightDetection";

/**
* Base URL for AUTHENTICATION REQUESTS
*/
String PROTOCOL = "https";
String HOST_ENDPOINT = PROTOCOL + "://smartapi.vesync.com/cloud";
String SERVER_ADDRESS = "smartapi.vesync.com";
String SERVER_ENDPOINT = PROTOCOL + "://" + SERVER_ADDRESS;

String HOST_ENDPOINT = SERVER_ENDPOINT + "/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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
package org.openhab.binding.vesync.internal.dto.requests;

import org.eclipse.jetty.http.HttpMethod;

import com.google.gson.annotations.SerializedName;

/**
Expand All @@ -21,6 +23,8 @@
*/
public class VeSyncRequest {

public transient HttpMethod httpMethod;

@SerializedName("timeZone")
public String timeZone = "America/New_York";

Expand All @@ -42,7 +46,11 @@ public class VeSyncRequest {
@SerializedName("method")
public String method;

@SerializedName("deviceId")
public String deviceId;

public VeSyncRequest() {
traceId = String.valueOf(System.currentTimeMillis());
httpMethod = HttpMethod.POST;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public class VeSyncRequestManagedDeviceBypassV2 extends VeSyncAuthenticatedReque
@SerializedName("configModule")
public String configModule = "";

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

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

Expand All @@ -55,6 +58,75 @@ public class VesyncManagedDeviceBase {
public static class EmptyPayload {
}

public static class SetLightDetectionPayload extends EmptyPayload {

public SetLightDetectionPayload(final boolean enabled) {
lightDetectionSwitch = enabled ? 1 : 0;
}

@SerializedName("lightDetectionSwitch")
public int lightDetectionSwitch = -1;
}

public static class SetPowerPayload extends EmptyPayload {

public SetPowerPayload(final boolean enabled, final int switchIdx) {
this.powerSwitch = enabled ? 1 : 0;
this.switchIdx = switchIdx;
}

@SerializedName("switchIdx")
public int switchIdx = -1;

@SerializedName("powerSwitch")
public int powerSwitch = -1;
}

public static class SetChildLockPayload extends EmptyPayload {

public SetChildLockPayload(final boolean enabled) {
this.childLockSwitch = enabled ? 1 : 0;
}

@SerializedName("childLockSwitch")
public int childLockSwitch = -1;
}

public static class SetScreenSwitchPayload extends EmptyPayload {

public SetScreenSwitchPayload(final boolean enabled) {
this.screenSwitch = enabled ? 1 : 0;
}

@SerializedName("screenSwitch")
public int screenSwitch = -1;
}

public static class SetManualSpeedLevelPayload extends EmptyPayload {

public SetManualSpeedLevelPayload(final int manualSpeedLevel) {
this.manualSpeedLevel = manualSpeedLevel;
}

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

@SerializedName("levelType")
public String levelType = "wind";

@SerializedName("manualSpeedLevel")
public int manualSpeedLevel = -1;
}

public static class SetWorkModePayload extends EmptyPayload {
public SetWorkModePayload(final String workMode) {
this.workMode = workMode;
}

@SerializedName("workMode")
public String workMode = "";
}

public static class SetSwitchPayload extends EmptyPayload {

public SetSwitchPayload(final boolean enabled, final int id) {
Expand Down
Loading

0 comments on commit b00a44a

Please sign in to comment.