From ad202edefa2c934609c36e4a9ddaa4bcbd52832c Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 29 Nov 2020 05:03:12 +0100 Subject: [PATCH] [miio] Cloud Communication for devices (#8981) * [miio] Cloud Communication for devices Allows to define if communication to devices is direct or send via the Xiaomi cloud. Introduce additional channel to execute commands via cloud. Other small improvements * Use common method from abstract handler to send commands * Common way to handle custom commands * Introduce small delay before refreshing robot properties after sending commands (similar to the basic handler) so devices have time to update their properties * [miio] simplify cloudconnector * [miio] Cleanup all jobs when unloading * [miio] update to use dedicated ScheduledExecutorService Use dedicated ScheduledExecutorService to avoid unloading problems * Update bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoAbstractHandler.java * [miio] fix for removeif * miio- Improve scheduler * [miio] fix communication error if device is not on the network * [miio] update with comments from feedback * remove scheduler tracking * improve status setting for cloud communication * [miio] update with feedback review Signed-off-by: Marcel Verpaalen Co-authored-by: Connor Petty --- .../org.openhab.binding.miio/README.base.md | 29 ++--- bundles/org.openhab.binding.miio/README.md | 29 ++--- .../internal/MiIoBindingConfiguration.java | 1 + .../miio/internal/MiIoBindingConstants.java | 1 + .../miio/internal/MiIoHandlerFactory.java | 6 +- .../miio/internal/MiIoSendCommand.java | 16 +++ .../miio/internal/cloud/CloudConnector.java | 11 +- .../miio/internal/cloud/MiCloudConnector.java | 42 ++++++-- .../internal/handler/MiIoAbstractHandler.java | 101 +++++++++++++----- .../internal/handler/MiIoBasicHandler.java | 21 ++-- .../internal/handler/MiIoGenericHandler.java | 12 +-- .../handler/MiIoUnsupportedHandler.java | 12 ++- .../internal/handler/MiIoVacuumHandler.java | 23 ++-- .../transport/MiIoAsyncCommunication.java | 44 ++++++-- .../main/resources/OH-INF/config/config.xml | 12 ++- .../resources/OH-INF/thing/basicThing.xml | 1 + .../resources/OH-INF/thing/commonChannels.xml | 4 + .../OH-INF/thing/unsupportedThing.xml | 1 + .../resources/OH-INF/thing/vacuumThing.xml | 1 + 19 files changed, 255 insertions(+), 112 deletions(-) diff --git a/bundles/org.openhab.binding.miio/README.base.md b/bundles/org.openhab.binding.miio/README.base.md index 2591657065b..ffe2337ec2c 100644 --- a/bundles/org.openhab.binding.miio/README.base.md +++ b/bundles/org.openhab.binding.miio/README.base.md @@ -65,22 +65,25 @@ Optional configuration is the refresh interval and the deviceID. Note that the d The configuration for model is automatically retrieved from the device in normal operation. However, for devices that are unsupported, you may override the value and try to use a model string from a similar device to experimentally use your device with the binding. -| Parameter | Type | Required | Description | -|-----------------|---------|----------|-------------------------------------------------------------------| -| host | text | true | Device IP address | -| token | text | true | Token for communication (in Hex) | -| deviceId | text | true | Device ID number for communication (in Hex) | -| model | text | false | Device model string, used to determine the subtype | -| refreshInterval | integer | false | Refresh interval for refreshing the data in seconds. (0=disabled) | -| timeout | integer | false | Timeout time in milliseconds | +| Parameter | Type | Required | Description | +|-----------------|---------|----------|---------------------------------------------------------------------| +| host | text | true | Device IP address | +| token | text | true | Token for communication (in Hex) | +| deviceId | text | true | Device ID number for communication (in Hex) | +| model | text | false | Device model string, used to determine the subtype | +| refreshInterval | integer | false | Refresh interval for refreshing the data in seconds. (0=disabled) | +| timeout | integer | false | Timeout time in milliseconds | +| communication | test | false | Communicate direct or via cloud (options values: 'direct', 'cloud') | + +Note: Suggest to use the cloud communication only for devices that require it. It is unknown at this time if Xiaomi has a rate limit or other limitations on the cloud usage. e.g. if having many devices would trigger some throttling from the cloud side. ### Example Thing file -`Thing miio:basic:light "My Light" [ host="192.168.x.x", token="put here your token", deviceId="0326xxxx", model="philips.light.bulb" ]` +`Thing miio:basic:light "My Light" [ host="192.168.x.x", token="put here your token", deviceId="0326xxxx", model="philips.light.bulb", communication="direct" ]` or in case of unknown models include the model information of a similar device that is supported: -`Thing miio:vacuum:s50 "vacuum" @ "livingroom" [ host="192.168.15.20", token="xxxxxxx", deviceId=“0470DDAA”, model="roborock.vacuum.s4" ]` +`Thing miio:vacuum:s50 "vacuum" @ "livingroom" [ host="192.168.15.20", token="xxxxxxx", deviceId=“0470DDAA”, model="roborock.vacuum.s4", communication="cloud"]` # Mi IO Devices @@ -152,6 +155,7 @@ Alternatively as described above, double check for multiple connections for sing _Your device is on a different subnet?_ This is in most cases not working. Firmware of the device don't accept commands coming from other subnets. +Set the communication in the thing configuration to 'cloud'. _Cloud connectivity is not working_ The most common problem is a wrong userId/password. Try to fix your userId/password. @@ -177,9 +181,10 @@ All devices have available the following channels (marked as advanced) besides t | network#bssid | String | Network BSSID | | network#rssi | Number | Network RSSI | | network#life | Number | Network Life | -| actions#commands | String | send commands. see below | +| actions#commands | String | send commands direct. see below | +| actions#rpc | String | send commands via cloud. see below | -note: the ADVANCED `actions#commands` channel can be used to send commands that are not automated via the binding. This is available for all devices +note: the ADVANCED `actions#commands` and `actions#rpc` channels can be used to send commands that are not automated via the binding. This is available for all devices e.g. `smarthome:send actionCommand 'upd_timer["1498595904821", "on"]'` would enable a pre-configured timer. See https://github.com/marcelrv/XiaomiRobotVacuumProtocol for all known available commands. diff --git a/bundles/org.openhab.binding.miio/README.md b/bundles/org.openhab.binding.miio/README.md index d601e28eb57..3b7432cf0a2 100644 --- a/bundles/org.openhab.binding.miio/README.md +++ b/bundles/org.openhab.binding.miio/README.md @@ -65,22 +65,25 @@ Optional configuration is the refresh interval and the deviceID. Note that the d The configuration for model is automatically retrieved from the device in normal operation. However, for devices that are unsupported, you may override the value and try to use a model string from a similar device to experimentally use your device with the binding. -| Parameter | Type | Required | Description | -|-----------------|---------|----------|-------------------------------------------------------------------| -| host | text | true | Device IP address | -| token | text | true | Token for communication (in Hex) | -| deviceId | text | true | Device ID number for communication (in Hex) | -| model | text | false | Device model string, used to determine the subtype | -| refreshInterval | integer | false | Refresh interval for refreshing the data in seconds. (0=disabled) | -| timeout | integer | false | Timeout time in milliseconds | +| Parameter | Type | Required | Description | +|-----------------|---------|----------|---------------------------------------------------------------------| +| host | text | true | Device IP address | +| token | text | true | Token for communication (in Hex) | +| deviceId | text | true | Device ID number for communication (in Hex) | +| model | text | false | Device model string, used to determine the subtype | +| refreshInterval | integer | false | Refresh interval for refreshing the data in seconds. (0=disabled) | +| timeout | integer | false | Timeout time in milliseconds | +| communication | test | false | Communicate direct or via cloud (options values: 'direct', 'cloud') | + +Note: Suggest to use the cloud communication only for devices that require it. It is unknown at this time if Xiaomi has a rate limit or other limitations on the cloud usage. e.g. if having many devices would trigger some throttling from the cloud side. ### Example Thing file -`Thing miio:basic:light "My Light" [ host="192.168.x.x", token="put here your token", deviceId="0326xxxx", model="philips.light.bulb" ]` +`Thing miio:basic:light "My Light" [ host="192.168.x.x", token="put here your token", deviceId="0326xxxx", model="philips.light.bulb", communication="direct" ]` or in case of unknown models include the model information of a similar device that is supported: -`Thing miio:vacuum:s50 "vacuum" @ "livingroom" [ host="192.168.15.20", token="xxxxxxx", deviceId=“0470DDAA”, model="roborock.vacuum.s4" ]` +`Thing miio:vacuum:s50 "vacuum" @ "livingroom" [ host="192.168.15.20", token="xxxxxxx", deviceId=“0470DDAA”, model="roborock.vacuum.s4", communication="cloud"]` # Mi IO Devices @@ -396,6 +399,7 @@ Alternatively as described above, double check for multiple connections for sing _Your device is on a different subnet?_ This is in most cases not working. Firmware of the device don't accept commands coming from other subnets. +Set the communication in the thing configuration to 'cloud'. _Cloud connectivity is not working_ The most common problem is a wrong userId/password. Try to fix your userId/password. @@ -421,9 +425,10 @@ All devices have available the following channels (marked as advanced) besides t | network#bssid | String | Network BSSID | | network#rssi | Number | Network RSSI | | network#life | Number | Network Life | -| actions#commands | String | send commands. see below | +| actions#commands | String | send commands direct. see below | +| actions#rpc | String | send commands via cloud. see below | -note: the ADVANCED `actions#commands` channel can be used to send commands that are not automated via the binding. This is available for all devices +note: the ADVANCED `actions#commands` and `actions#rpc` channels can be used to send commands that are not automated via the binding. This is available for all devices e.g. `smarthome:send actionCommand 'upd_timer["1498595904821", "on"]'` would enable a pre-configured timer. See https://github.com/marcelrv/XiaomiRobotVacuumProtocol for all known available commands. diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoBindingConfiguration.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoBindingConfiguration.java index ca71dd4cf81..153dd4d34ee 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoBindingConfiguration.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoBindingConfiguration.java @@ -24,6 +24,7 @@ public final class MiIoBindingConfiguration { public String token; public String deviceId; public String model; + public String communication; public int refreshInterval; public int timeout; public String cloudServer; diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoBindingConstants.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoBindingConstants.java index da843cd87e2..13c7668b4a4 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoBindingConstants.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoBindingConstants.java @@ -61,6 +61,7 @@ public final class MiIoBindingConstants { public static final String CHANNEL_CONTROL = "actions#control"; public static final String CHANNEL_COMMAND = "actions#commands"; + public static final String CHANNEL_RPC = "actions#rpc"; public static final String CHANNEL_VACUUM = "actions#vacuum"; public static final String CHANNEL_FAN_CONTROL = "actions#fan"; public static final String CHANNEL_TESTCOMMANDS = "actions#testcommands"; diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoHandlerFactory.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoHandlerFactory.java index 78099746323..b08b0cf23d8 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoHandlerFactory.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoHandlerFactory.java @@ -79,14 +79,14 @@ public class MiIoHandlerFactory extends BaseThingHandlerFactory { protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (thingTypeUID.equals(THING_TYPE_MIIO)) { - return new MiIoGenericHandler(thing, miIoDatabaseWatchService); + return new MiIoGenericHandler(thing, miIoDatabaseWatchService, cloudConnector); } if (thingTypeUID.equals(THING_TYPE_BASIC)) { - return new MiIoBasicHandler(thing, miIoDatabaseWatchService, channelTypeRegistry); + return new MiIoBasicHandler(thing, miIoDatabaseWatchService, cloudConnector, channelTypeRegistry); } if (thingTypeUID.equals(THING_TYPE_VACUUM)) { return new MiIoVacuumHandler(thing, miIoDatabaseWatchService, cloudConnector, channelTypeRegistry); } - return new MiIoUnsupportedHandler(thing, miIoDatabaseWatchService); + return new MiIoUnsupportedHandler(thing, miIoDatabaseWatchService, cloudConnector); } } diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoSendCommand.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoSendCommand.java index 47aa56fb506..b7463ed6815 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoSendCommand.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoSendCommand.java @@ -31,6 +31,7 @@ public class MiIoSendCommand { private final MiIoCommand command; private final JsonObject commandJson; private @Nullable JsonObject response; + private String cloudServer = ""; public void setResponse(JsonObject response) { this.response = response; @@ -42,6 +43,13 @@ public class MiIoSendCommand { this.commandJson = fullCommand; } + public MiIoSendCommand(int id, MiIoCommand command, JsonObject fullCommand, String cloudServer) { + this.id = id; + this.command = command; + this.commandJson = fullCommand; + this.cloudServer = cloudServer; + } + public int getId() { return id; } @@ -86,4 +94,12 @@ public class MiIoSendCommand { } return new JsonObject(); } + + public String getCloudServer() { + return cloudServer; + } + + public void setCloudServer(String cloudServer) { + this.cloudServer = cloudServer; + } } diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/CloudConnector.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/CloudConnector.java index e3660dada0e..d04d9734d7f 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/CloudConnector.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/CloudConnector.java @@ -21,6 +21,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.miio.internal.MiIoSendCommand; import org.openhab.core.cache.ExpiringCache; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.io.net.http.HttpUtil; @@ -115,13 +116,21 @@ public class CloudConnector { return false; } + public String sendRPCCommand(String device, String country, MiIoSendCommand command) throws MiCloudException { + final @Nullable MiCloudConnector cl = this.cloudConnector; + if (cl == null || !isConnected()) { + throw new MiCloudException("Cannot execute request. Cloud service not available"); + } + return cl.sendRPCCommand(device, country.trim().toLowerCase(), command.getCommandString()); + } + public @Nullable RawType getMap(String mapId, String country) throws MiCloudException { logger.debug("Getting vacuum map {} from Xiaomi cloud server: '{}'", mapId, country); String mapCountry; String mapUrl = ""; final @Nullable MiCloudConnector cl = this.cloudConnector; if (cl == null || !isConnected()) { - throw new MiCloudException("Cannot execute request. Cloudservice not available"); + throw new MiCloudException("Cannot execute request. Cloud service not available"); } if (country.isEmpty()) { logger.debug("Server not defined in thing. Trying servers: {}", this.country); diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/MiCloudConnector.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/MiCloudConnector.java index 085542f11ae..5077294b097 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/MiCloudConnector.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/MiCloudConnector.java @@ -176,10 +176,27 @@ public class MiCloudConnector { } public String getDeviceStatus(String device, String country) throws MiCloudException { - String url = getApiUrl(country) + "/home/device_list"; - Map map = new HashMap(); - map.put("data", "{\"dids\":[\"" + device + "\"]}"); - final String response = request(url, map); + final String response = request("/home/device_list", country, "{\"dids\":[\"" + device + "\"]}"); + logger.debug("response: {}", response); + return response; + } + + public String sendRPCCommand(String device, String country, String command) throws MiCloudException { + if (device.length() != 8) { + logger.debug("Device ID ('{}') incorrect or missing. Command not send: {}", device, command); + } + if (country.length() > 3 || country.length() < 2) { + logger.debug("Country ('{}') incorrect or missing. Command not send: {}", device, command); + } + String id = ""; + try { + id = String.valueOf(Long.parseUnsignedLong(device, 16)); + } catch (NumberFormatException e) { + String err = "Could not parse device ID ('" + device.toString() + "')"; + logger.debug("{}", err); + throw new MiCloudException(err, e); + } + final String response = request("/home/rpc/" + id, country, command); logger.debug("response: {}", response); return response; } @@ -211,12 +228,9 @@ public class MiCloudConnector { } public String getDeviceString(String country) { - String url = getApiUrl(country) + "/home/device_list"; - Map map = new HashMap(); - map.put("data", "{\"getVirtualModel\":false,\"getHuamiDevices\":0}"); String resp; try { - resp = request(url, map); + resp = request("/home/device_list", country, "{\"getVirtualModel\":false,\"getHuamiDevices\":0}"); logger.trace("Get devices response: {}", resp); if (resp.length() > 2) { CloudUtil.saveDeviceInfoFile(resp, country, logger); @@ -228,8 +242,15 @@ public class MiCloudConnector { return ""; } + public String request(String urlPart, String country, String params) throws MiCloudException { + Map map = new HashMap(); + map.put("data", params); + return request(urlPart, country, map); + } + public String request(String urlPart, String country, Map params) throws MiCloudException { - String url = getApiUrl(country) + urlPart; + String url = urlPart.trim(); + url = getApiUrl(country) + (url.startsWith("/app") ? url.substring(4) : url); String response = request(url, params); logger.debug("Request to {} server {}. Response: {}", country, urlPart, response); return response; @@ -276,7 +297,8 @@ public class MiCloudConnector { logger.trace("fieldcontent: {}", fields.toString()); final ContentResponse response = request.send(); - if (response.getStatus() == HttpStatus.FORBIDDEN_403) { + if (response.getStatus() >= HttpStatus.BAD_REQUEST_400 + && response.getStatus() < HttpStatus.INTERNAL_SERVER_ERROR_500) { this.serviceToken = ""; } return response.getContentAsString(); diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoAbstractHandler.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoAbstractHandler.java index 62298de92ab..3d2f436a896 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoAbstractHandler.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoAbstractHandler.java @@ -19,7 +19,9 @@ import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -35,8 +37,10 @@ import org.openhab.binding.miio.internal.MiIoMessageListener; import org.openhab.binding.miio.internal.MiIoSendCommand; import org.openhab.binding.miio.internal.Utils; import org.openhab.binding.miio.internal.basic.MiIoDatabaseWatchService; +import org.openhab.binding.miio.internal.cloud.CloudConnector; import org.openhab.binding.miio.internal.transport.MiIoAsyncCommunication; import org.openhab.core.cache.ExpiringCache; +import org.openhab.core.common.NamedThreadFactory; import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.StringType; @@ -67,6 +71,7 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi protected static final int MAX_QUEUE = 5; protected static final Gson GSON = new GsonBuilder().create(); + protected ScheduledExecutorService miIoScheduler = scheduler; protected @Nullable ScheduledFuture pollingJob; protected MiIoDevices miDevice = MiIoDevices.UNKNOWN; protected boolean isIdentified; @@ -76,6 +81,8 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi protected @Nullable MiIoBindingConfiguration configuration; protected @Nullable MiIoAsyncCommunication miioCom; + protected CloudConnector cloudConnector; + protected String cloudServer = ""; protected int lastId; protected Map cmds = new ConcurrentHashMap<>(); @@ -93,18 +100,39 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi private final Logger logger = LoggerFactory.getLogger(MiIoAbstractHandler.class); protected MiIoDatabaseWatchService miIoDatabaseWatchService; - public MiIoAbstractHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService) { + public MiIoAbstractHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService, + CloudConnector cloudConnector) { super(thing); this.miIoDatabaseWatchService = miIoDatabaseWatchService; + this.cloudConnector = cloudConnector; } @Override public abstract void handleCommand(ChannelUID channelUID, Command command); + protected boolean handleCommandsChannels(ChannelUID channelUID, Command command) { + if (channelUID.getId().equals(CHANNEL_COMMAND)) { + cmds.put(sendCommand(command.toString(), ""), command.toString()); + return true; + } + if (channelUID.getId().equals(CHANNEL_RPC)) { + cmds.put(sendCommand(command.toString(), cloudServer), command.toString()); + return true; + } + return false; + } + @Override public void initialize() { logger.debug("Initializing Mi IO device handler '{}' with thingType {}", getThing().getUID(), getThing().getThingTypeUID()); + + ScheduledThreadPoolExecutor miIoScheduler = new ScheduledThreadPoolExecutor(3, + new NamedThreadFactory(getThing().getUID().getAsString(), true)); + miIoScheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + miIoScheduler.setRemoveOnCancelPolicy(true); + this.miIoScheduler = miIoScheduler; + final MiIoBindingConfiguration configuration = getConfigAs(MiIoBindingConfiguration.class); this.configuration = configuration; if (configuration.host == null || configuration.host.isEmpty()) { @@ -116,11 +144,12 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Token required. Configure token"); return; } + cloudServer = (configuration.cloudServer != null) ? configuration.cloudServer : ""; isIdentified = false; - scheduler.schedule(this::initializeData, 1, TimeUnit.SECONDS); + miIoScheduler.schedule(this::initializeData, 1, TimeUnit.SECONDS); int pollingPeriod = configuration.refreshInterval; if (pollingPeriod > 0) { - pollingJob = scheduler.scheduleWithFixedDelay(() -> { + pollingJob = miIoScheduler.scheduleWithFixedDelay(() -> { try { updateData(); } catch (Exception e) { @@ -130,7 +159,7 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi logger.debug("Polling job scheduled to run every {} sec. for '{}'", pollingPeriod, getThing().getUID()); } else { logger.debug("Polling job disabled. for '{}'", getThing().getUID()); - scheduler.schedule(this::updateData, 10, TimeUnit.SECONDS); + miIoScheduler.schedule(this::updateData, 10, TimeUnit.SECONDS); } updateStatus(ThingStatus.OFFLINE); } @@ -166,6 +195,7 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi @Override public void dispose() { logger.debug("Disposing Xiaomi Mi IO handler '{}'", getThing().getUID()); + miIoScheduler.shutdown(); final ScheduledFuture pollingJob = this.pollingJob; if (pollingJob != null) { pollingJob.cancel(true); @@ -178,6 +208,7 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi miioCom.close(); this.miioCom = null; } + miIoScheduler.shutdownNow(); } protected int sendCommand(MiIoCommand command) { @@ -187,7 +218,7 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi protected int sendCommand(MiIoCommand command, String params) { try { final MiIoAsyncCommunication connection = getConnection(); - return (connection != null) ? connection.queueCommand(command, params) : 0; + return (connection != null) ? connection.queueCommand(command, params, getCloudServer()) : 0; } catch (MiIoCryptoException | IOException e) { logger.debug("Command {} for {} failed (type: {}): {}", command.toString(), getThing().getUID(), getThing().getThingTypeUID(), e.getLocalizedMessage()); @@ -195,6 +226,10 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi return 0; } + protected int sendCommand(String commandString) { + return sendCommand(commandString, getCloudServer()); + } + /** * This is used to execute arbitrary commands by sending to the commands channel. Command parameters to be added * between @@ -202,9 +237,10 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi * records) * * @param commandString command to be executed + * @param cloud server to be used or empty string for direct sending to the device * @return vacuum response */ - protected int sendCommand(String commandString) { + protected int sendCommand(String commandString, String cloudServer) { final MiIoAsyncCommunication connection = getConnection(); try { String command = commandString.trim(); @@ -216,13 +252,24 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi param = command.substring(loc).trim(); command = command.substring(0, loc).trim(); } - return (connection != null) ? connection.queueCommand(command, param) : 0; + return (connection != null) ? connection.queueCommand(command, param, cloudServer) : 0; } catch (MiIoCryptoException | IOException e) { disconnected(e.getMessage()); } return 0; } + String getCloudServer() { + // This can be improved in the future with additional / more advanced options like e.g. directFirst which would + // use direct communications and in case of failures fall back to cloud communication. For now we keep it + // simple and only have the option for cloud or direct. + final MiIoBindingConfiguration configuration = this.configuration; + if (configuration != null && configuration.communication != null) { + return configuration.communication.equals("cloud") ? cloudServer : ""; + } + return ""; + } + protected boolean skipUpdate() { final MiIoAsyncCommunication miioCom = this.miioCom; if (!hasConnection() || miioCom == null) { @@ -232,11 +279,7 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi if (getThing().getStatusInfo().getStatusDetail().equals(ThingStatusDetail.CONFIGURATION_ERROR)) { logger.debug("Skipping periodic update for '{}'. Thing Status {}", getThing().getUID().toString(), getThing().getStatusInfo().getStatusDetail()); - try { - miioCom.queueCommand(MiIoCommand.MIIO_INFO); - } catch (MiIoCryptoException | IOException e) { - // ignore - } + sendCommand(MiIoCommand.MIIO_INFO); return true; } if (miioCom.getQueueLength() > MAX_QUEUE) { @@ -299,24 +342,30 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi String deviceId = configuration.deviceId; try { if (deviceId != null && deviceId.length() == 8 && tokenCheckPass(configuration.token)) { - logger.debug("Ping Mi device {} at {}", deviceId, configuration.host); final MiIoAsyncCommunication miioCom = new MiIoAsyncCommunication(configuration.host, token, - Utils.hexStringToByteArray(deviceId), lastId, configuration.timeout); - Message miIoResponse = miioCom.sendPing(configuration.host); - if (miIoResponse != null) { - logger.debug("Ping response from device {} at {}. Time stamp: {}, OH time {}, delta {}", - Utils.getHex(miIoResponse.getDeviceId()), configuration.host, miIoResponse.getTimestamp(), - LocalDateTime.now(), miioCom.getTimeDelta()); + Utils.hexStringToByteArray(deviceId), lastId, configuration.timeout, cloudConnector); + if (getCloudServer().isBlank()) { + logger.debug("Ping Mi device {} at {}", deviceId, configuration.host); + Message miIoResponse = miioCom.sendPing(configuration.host); + if (miIoResponse != null) { + logger.debug("Ping response from device {} at {}. Time stamp: {}, OH time {}, delta {}", + Utils.getHex(miIoResponse.getDeviceId()), configuration.host, + miIoResponse.getTimestamp(), LocalDateTime.now(), miioCom.getTimeDelta()); + miioCom.registerListener(this); + this.miioCom = miioCom; + return miioCom; + } else { + miioCom.close(); + } + } else { miioCom.registerListener(this); this.miioCom = miioCom; return miioCom; - } else { - miioCom.close(); } } else { logger.debug("No device ID defined. Retrieving Mi device ID"); final MiIoAsyncCommunication miioCom = new MiIoAsyncCommunication(configuration.host, token, - new byte[0], lastId, configuration.timeout); + new byte[0], lastId, configuration.timeout, cloudConnector); Message miIoResponse = miioCom.sendPing(configuration.host); if (miIoResponse != null) { logger.debug("Ping response from device {} at {}. Time stamp: {}, OH time {}, delta {}", @@ -440,7 +489,7 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi pollingJob.cancel(true); this.pollingJob = null; } - scheduler.schedule(() -> { + miIoScheduler.schedule(() -> { ThingBuilder thingBuilder = editThing(); thingBuilder.withLabel(miDevice.getDescription()); updateThing(thingBuilder.build()); @@ -484,7 +533,11 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi break; } if (cmds.containsKey(response.getId())) { - updateState(CHANNEL_COMMAND, new StringType(response.getResponse().toString())); + if (response.getCloudServer().isBlank()) { + updateState(CHANNEL_COMMAND, new StringType(response.getResponse().toString())); + } else { + updateState(CHANNEL_RPC, new StringType(response.getResponse().toString())); + } cmds.remove(response.getId()); } } catch (Exception e) { diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoBasicHandler.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoBasicHandler.java index 31b1d6b64ad..874051dfae2 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoBasicHandler.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoBasicHandler.java @@ -30,7 +30,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.miio.internal.MiIoBindingConfiguration; import org.openhab.binding.miio.internal.MiIoCommand; -import org.openhab.binding.miio.internal.MiIoCryptoException; import org.openhab.binding.miio.internal.MiIoQuantiyTypes; import org.openhab.binding.miio.internal.MiIoSendCommand; import org.openhab.binding.miio.internal.Utils; @@ -42,6 +41,7 @@ import org.openhab.binding.miio.internal.basic.MiIoBasicDevice; import org.openhab.binding.miio.internal.basic.MiIoDatabaseWatchService; import org.openhab.binding.miio.internal.basic.MiIoDeviceAction; import org.openhab.binding.miio.internal.basic.MiIoDeviceActionCondition; +import org.openhab.binding.miio.internal.cloud.CloudConnector; import org.openhab.binding.miio.internal.transport.MiIoAsyncCommunication; import org.openhab.core.cache.ExpiringCache; import org.openhab.core.library.types.DecimalType; @@ -85,7 +85,7 @@ public class MiIoBasicHandler extends MiIoAbstractHandler { private boolean hasChannelStructure; private final ExpiringCache updateDataCache = new ExpiringCache<>(CACHE_EXPIRY, () -> { - scheduler.schedule(this::updateData, 0, TimeUnit.SECONDS); + miIoScheduler.schedule(this::updateData, 0, TimeUnit.SECONDS); return true; }); @@ -97,8 +97,8 @@ public class MiIoBasicHandler extends MiIoAbstractHandler { private ChannelTypeRegistry channelTypeRegistry; public MiIoBasicHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService, - ChannelTypeRegistry channelTypeRegistry) { - super(thing, miIoDatabaseWatchService); + CloudConnector cloudConnector, ChannelTypeRegistry channelTypeRegistry) { + super(thing, miIoDatabaseWatchService, cloudConnector); this.channelTypeRegistry = channelTypeRegistry; } @@ -255,7 +255,7 @@ public class MiIoBasicHandler extends MiIoAbstractHandler { } } updateDataCache.invalidateValue(); - scheduler.schedule(() -> { + miIoScheduler.schedule(() -> { updateData(); }, 3000, TimeUnit.MILLISECONDS); } else { @@ -294,7 +294,7 @@ public class MiIoBasicHandler extends MiIoAbstractHandler { } checkChannelStructure(); if (!isIdentified) { - miioCom.queueCommand(MiIoCommand.MIIO_INFO); + sendCommand(MiIoCommand.MIIO_INFO); } final MiIoBasicDevice midevice = miioDevice; if (midevice != null) { @@ -341,14 +341,7 @@ public class MiIoBasicHandler extends MiIoAbstractHandler { } private void sendRefreshProperties(MiIoCommand command, JsonArray getPropString) { - try { - final MiIoAsyncCommunication miioCom = this.miioCom; - if (miioCom != null) { - miioCom.queueCommand(command, getPropString.toString()); - } - } catch (MiIoCryptoException | IOException e) { - logger.debug("Send refresh failed {}", e.getMessage(), e); - } + sendCommand(command, getPropString.toString()); } /** diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoGenericHandler.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoGenericHandler.java index a77af44cf3a..3ec9f4e8a2b 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoGenericHandler.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoGenericHandler.java @@ -12,10 +12,9 @@ */ package org.openhab.binding.miio.internal.handler; -import static org.openhab.binding.miio.internal.MiIoBindingConstants.CHANNEL_COMMAND; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.miio.internal.basic.MiIoDatabaseWatchService; +import org.openhab.binding.miio.internal.cloud.CloudConnector; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.types.Command; @@ -33,8 +32,9 @@ import org.slf4j.LoggerFactory; public class MiIoGenericHandler extends MiIoAbstractHandler { private final Logger logger = LoggerFactory.getLogger(MiIoGenericHandler.class); - public MiIoGenericHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService) { - super(thing, miIoDatabaseWatchService); + public MiIoGenericHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService, + CloudConnector cloudConnector) { + super(thing, miIoDatabaseWatchService, cloudConnector); } @Override @@ -44,8 +44,8 @@ public class MiIoGenericHandler extends MiIoAbstractHandler { updateData(); return; } - if (channelUID.getId().equals(CHANNEL_COMMAND)) { - cmds.put(sendCommand(command.toString()), command.toString()); + if (handleCommandsChannels(channelUID, command)) { + return; } } diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoUnsupportedHandler.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoUnsupportedHandler.java index 9862b92e8e9..f6b735f4de9 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoUnsupportedHandler.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoUnsupportedHandler.java @@ -40,6 +40,7 @@ import org.openhab.binding.miio.internal.basic.DeviceMapping; import org.openhab.binding.miio.internal.basic.MiIoBasicChannel; import org.openhab.binding.miio.internal.basic.MiIoBasicDevice; import org.openhab.binding.miio.internal.basic.MiIoDatabaseWatchService; +import org.openhab.binding.miio.internal.cloud.CloudConnector; import org.openhab.core.cache.ExpiringCache; import org.openhab.core.library.types.OnOffType; import org.openhab.core.thing.ChannelUID; @@ -78,12 +79,13 @@ public class MiIoUnsupportedHandler extends MiIoAbstractHandler { private String model = conf.model != null ? conf.model : ""; private final ExpiringCache updateDataCache = new ExpiringCache<>(CACHE_EXPIRY, () -> { - scheduler.schedule(this::updateData, 0, TimeUnit.SECONDS); + miIoScheduler.schedule(this::updateData, 0, TimeUnit.SECONDS); return true; }); - public MiIoUnsupportedHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService) { - super(thing, miIoDatabaseWatchService); + public MiIoUnsupportedHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService, + CloudConnector cloudConnector) { + super(thing, miIoDatabaseWatchService, cloudConnector); } @Override @@ -104,8 +106,8 @@ public class MiIoUnsupportedHandler extends MiIoAbstractHandler { sendCommand("set_power[\"off\"]"); } } - if (channelUID.getId().equals(CHANNEL_COMMAND)) { - cmds.put(sendCommand(command.toString()), command.toString()); + if (handleCommandsChannels(channelUID, command)) { + return; } if (channelUID.getId().equals(CHANNEL_TESTCOMMANDS)) { executeExperimentalCommands(); diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java index 18d20e9a4d6..0e5bccf610c 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java @@ -97,15 +97,13 @@ public class MiIoVacuumHandler extends MiIoAbstractHandler { private ExpiringCache map; private String lastHistoryId = ""; private String lastMap = ""; - private CloudConnector cloudConnector; private boolean hasChannelStructure; private ConcurrentHashMap deviceCapabilities = new ConcurrentHashMap<>(); private ChannelTypeRegistry channelTypeRegistry; public MiIoVacuumHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService, CloudConnector cloudConnector, ChannelTypeRegistry channelTypeRegistry) { - super(thing, miIoDatabaseWatchService); - this.cloudConnector = cloudConnector; + super(thing, miIoDatabaseWatchService, cloudConnector); this.channelTypeRegistry = channelTypeRegistry; mapChannelUid = new ChannelUID(thing.getUID(), CHANNEL_VACUUM_MAP); status = new ExpiringCache<>(CACHE_EXPIRY, () -> { @@ -180,6 +178,9 @@ public class MiIoVacuumHandler extends MiIoAbstractHandler { } return; } + if (handleCommandsChannels(channelUID, command)) { + return; + } if (channelUID.getId().equals(CHANNEL_VACUUM)) { if (command instanceof OnOffType) { if (command.equals(OnOffType.ON)) { @@ -188,7 +189,7 @@ public class MiIoVacuumHandler extends MiIoAbstractHandler { return; } else { sendCommand(MiIoCommand.STOP_VACUUM); - scheduler.schedule(() -> { + miIoScheduler.schedule(() -> { sendCommand(MiIoCommand.CHARGE); forceStatusUpdate(); }, 2000, TimeUnit.MILLISECONDS); @@ -205,7 +206,7 @@ public class MiIoVacuumHandler extends MiIoAbstractHandler { sendCommand(MiIoCommand.PAUSE); } else if (command.toString().equals("dock")) { sendCommand(MiIoCommand.STOP_VACUUM); - scheduler.schedule(() -> { + miIoScheduler.schedule(() -> { sendCommand(MiIoCommand.CHARGE); forceStatusUpdate(); }, 2000, TimeUnit.MILLISECONDS); @@ -243,14 +244,13 @@ public class MiIoVacuumHandler extends MiIoAbstractHandler { sendCommand(MiIoCommand.CONSUMABLES_RESET, "[" + command.toString() + "]"); updateState(CHANNEL_CONSUMABLE_RESET, new StringType("none")); } - if (channelUID.getId().equals(CHANNEL_COMMAND)) { - cmds.put(sendCommand(command.toString()), command.toString()); - } } private void forceStatusUpdate() { status.invalidateValue(); - status.getValue(); + miIoScheduler.schedule(() -> { + status.getValue(); + }, 3000, TimeUnit.MILLISECONDS); } private void safeUpdateState(String channelID, @Nullable Integer state) { @@ -521,13 +521,10 @@ public class MiIoVacuumHandler extends MiIoAbstractHandler { String mapresponse = response.getResult().getAsJsonArray().get(0).getAsString(); if (!mapresponse.contentEquals("retry") && !mapresponse.contentEquals(lastMap)) { lastMap = mapresponse; - scheduler.submit(() -> updateState(CHANNEL_VACUUM_MAP, getMap(mapresponse))); + miIoScheduler.submit(() -> updateState(CHANNEL_VACUUM_MAP, getMap(mapresponse))); } } break; - case UNKNOWN: - updateState(CHANNEL_COMMAND, new StringType(response.getResponse().toString())); - break; default: break; } diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/transport/MiIoAsyncCommunication.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/transport/MiIoAsyncCommunication.java index 9fb9ccd32e2..ad32070719b 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/transport/MiIoAsyncCommunication.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/transport/MiIoAsyncCommunication.java @@ -37,6 +37,8 @@ import org.openhab.binding.miio.internal.MiIoCryptoException; import org.openhab.binding.miio.internal.MiIoMessageListener; import org.openhab.binding.miio.internal.MiIoSendCommand; import org.openhab.binding.miio.internal.Utils; +import org.openhab.binding.miio.internal.cloud.CloudConnector; +import org.openhab.binding.miio.internal.cloud.MiCloudException; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.slf4j.Logger; @@ -78,14 +80,17 @@ public class MiIoAsyncCommunication { private boolean needPing = true; private static final int MAX_ERRORS = 3; private static final int MAX_ID = 15000; + private final CloudConnector cloudConnector; private ConcurrentLinkedQueue concurrentLinkedQueue = new ConcurrentLinkedQueue<>(); - public MiIoAsyncCommunication(String ip, byte[] token, byte[] did, int id, int timeout) { + public MiIoAsyncCommunication(String ip, byte[] token, byte[] did, int id, int timeout, + CloudConnector cloudConnector) { this.ip = ip; this.token = token; this.deviceId = did; this.timeout = timeout; + this.cloudConnector = cloudConnector; setId(id); parser = new JsonParser(); startReceiver(); @@ -124,15 +129,16 @@ public class MiIoAsyncCommunication { } } - public int queueCommand(MiIoCommand command) throws MiIoCryptoException, IOException { - return queueCommand(command, "[]"); + public int queueCommand(MiIoCommand command, String cloudServer) throws MiIoCryptoException, IOException { + return queueCommand(command, "[]", cloudServer); } - public int queueCommand(MiIoCommand command, String params) throws MiIoCryptoException, IOException { - return queueCommand(command.getCommand(), params); + public int queueCommand(MiIoCommand command, String params, String cloudServer) + throws MiIoCryptoException, IOException { + return queueCommand(command.getCommand(), params, cloudServer); } - public int queueCommand(String command, String params) + public int queueCommand(String command, String params, String cloudServer) throws MiIoCryptoException, IOException, JsonSyntaxException { try { JsonObject fullCommand = new JsonObject(); @@ -143,15 +149,17 @@ public class MiIoAsyncCommunication { fullCommand.addProperty("id", cmdId); fullCommand.addProperty("method", command); fullCommand.add("params", parser.parse(params)); - MiIoSendCommand sendCmd = new MiIoSendCommand(cmdId, MiIoCommand.getCommand(command), fullCommand); + MiIoSendCommand sendCmd = new MiIoSendCommand(cmdId, MiIoCommand.getCommand(command), fullCommand, + cloudServer); concurrentLinkedQueue.add(sendCmd); if (logger.isDebugEnabled()) { // Obfuscate part of the token to allow sharing of the logfiles String tokenText = Utils.obfuscateToken(Utils.getHex(token)); - logger.debug("Command added to Queue {} -> {} (Device: {} token: {} Queue: {})", fullCommand.toString(), - ip, Utils.getHex(deviceId), tokenText, concurrentLinkedQueue.size()); + logger.debug("Command added to Queue {} -> {} (Device: {} token: {} Queue: {}).{}{}", + fullCommand.toString(), ip, Utils.getHex(deviceId), tokenText, concurrentLinkedQueue.size(), + cloudServer.isBlank() ? "" : " Send via cloudserver: ", cloudServer); } - if (needPing) { + if (needPing && cloudServer.isBlank()) { sendPing(ip); } return cmdId; @@ -166,7 +174,15 @@ public class MiIoAsyncCommunication { String errorMsg = "Unknown Error while sending command"; String decryptedResponse = ""; try { - decryptedResponse = sendCommand(miIoSendCommand.getCommandString(), token, ip, deviceId); + if (miIoSendCommand.getCloudServer().isBlank()) { + decryptedResponse = sendCommand(miIoSendCommand.getCommandString(), token, ip, deviceId); + } else { + decryptedResponse = cloudConnector.sendRPCCommand(Utils.getHex(deviceId), + miIoSendCommand.getCloudServer(), miIoSendCommand); + logger.debug("Command {} send via cloudserver {}", miIoSendCommand.getCommandString(), + miIoSendCommand.getCloudServer()); + updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE); + } // hack due to avoid invalid json errors from some misbehaving device firmwares decryptedResponse = decryptedResponse.replace(",,", ","); JsonElement response; @@ -188,6 +204,12 @@ public class MiIoAsyncCommunication { logger.warn("Could not parse '{}' <- {} (Device: {}) gave error {}", decryptedResponse, miIoSendCommand.getCommandString(), Utils.getHex(deviceId), e.getMessage()); errorMsg = "Received message is invalid JSON"; + } catch (MiCloudException e) { + logger.debug("Send command '{}' -> cloudserver '{}' (Device: {}) gave error {}", + miIoSendCommand.getCommandString(), miIoSendCommand.getCloudServer(), Utils.getHex(deviceId), + e.getMessage()); + errorMsg = e.getMessage(); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); } JsonObject erroResp = new JsonObject(); erroResp.addProperty("error", errorMsg); diff --git a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/config/config.xml index 8bc3b453637..6a099e99813 100644 --- a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/config/config.xml @@ -2,7 +2,7 @@ + xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd"> @@ -23,6 +23,16 @@ Device model string, used to determine the subtype. true + + direct + + Determines how the binding communicates with this device + + + + + true + Refresh interval for refreshing the data in seconds. (0=disabled) diff --git a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/basicThing.xml b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/basicThing.xml index 18589aadb1c..82ec8b00a9b 100644 --- a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/basicThing.xml +++ b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/basicThing.xml @@ -23,6 +23,7 @@ + diff --git a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/commonChannels.xml b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/commonChannels.xml index 0fc1ee99bc9..f7ff791ed6e 100644 --- a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/commonChannels.xml +++ b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/commonChannels.xml @@ -99,6 +99,10 @@ String + + String + + Switch diff --git a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/unsupportedThing.xml b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/unsupportedThing.xml index 34b51f1ba7d..d7c70988d85 100644 --- a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/unsupportedThing.xml +++ b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/unsupportedThing.xml @@ -24,6 +24,7 @@ + diff --git a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/vacuumThing.xml b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/vacuumThing.xml index 7faebc6bf5e..7e813654f04 100644 --- a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/vacuumThing.xml +++ b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/vacuumThing.xml @@ -29,6 +29,7 @@ +