[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 <marcel@verpaalen.com> Co-authored-by: Connor Petty <mistercpp2000@gmail.com>
This commit is contained in:
@@ -66,21 +66,24 @@ The configuration for model is automatically retrieved from the device in normal
|
|||||||
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.
|
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 |
|
| Parameter | Type | Required | Description |
|
||||||
|-----------------|---------|----------|-------------------------------------------------------------------|
|
|-----------------|---------|----------|---------------------------------------------------------------------|
|
||||||
| host | text | true | Device IP address |
|
| host | text | true | Device IP address |
|
||||||
| token | text | true | Token for communication (in Hex) |
|
| token | text | true | Token for communication (in Hex) |
|
||||||
| deviceId | text | true | Device ID number 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 |
|
| model | text | false | Device model string, used to determine the subtype |
|
||||||
| refreshInterval | integer | false | Refresh interval for refreshing the data in seconds. (0=disabled) |
|
| refreshInterval | integer | false | Refresh interval for refreshing the data in seconds. (0=disabled) |
|
||||||
| timeout | integer | false | Timeout time in milliseconds |
|
| 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
|
### 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:
|
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
|
# 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?_
|
_Your device is on a different subnet?_
|
||||||
This is in most cases not working.
|
This is in most cases not working.
|
||||||
Firmware of the device don't accept commands coming from other subnets.
|
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_
|
_Cloud connectivity is not working_
|
||||||
The most common problem is a wrong userId/password. Try to fix your userId/password.
|
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#bssid | String | Network BSSID |
|
||||||
| network#rssi | Number | Network RSSI |
|
| network#rssi | Number | Network RSSI |
|
||||||
| network#life | Number | Network Life |
|
| 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.
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -66,21 +66,24 @@ The configuration for model is automatically retrieved from the device in normal
|
|||||||
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.
|
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 |
|
| Parameter | Type | Required | Description |
|
||||||
|-----------------|---------|----------|-------------------------------------------------------------------|
|
|-----------------|---------|----------|---------------------------------------------------------------------|
|
||||||
| host | text | true | Device IP address |
|
| host | text | true | Device IP address |
|
||||||
| token | text | true | Token for communication (in Hex) |
|
| token | text | true | Token for communication (in Hex) |
|
||||||
| deviceId | text | true | Device ID number 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 |
|
| model | text | false | Device model string, used to determine the subtype |
|
||||||
| refreshInterval | integer | false | Refresh interval for refreshing the data in seconds. (0=disabled) |
|
| refreshInterval | integer | false | Refresh interval for refreshing the data in seconds. (0=disabled) |
|
||||||
| timeout | integer | false | Timeout time in milliseconds |
|
| 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
|
### 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:
|
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
|
# 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?_
|
_Your device is on a different subnet?_
|
||||||
This is in most cases not working.
|
This is in most cases not working.
|
||||||
Firmware of the device don't accept commands coming from other subnets.
|
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_
|
_Cloud connectivity is not working_
|
||||||
The most common problem is a wrong userId/password. Try to fix your userId/password.
|
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#bssid | String | Network BSSID |
|
||||||
| network#rssi | Number | Network RSSI |
|
| network#rssi | Number | Network RSSI |
|
||||||
| network#life | Number | Network Life |
|
| 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.
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
@@ -24,6 +24,7 @@ public final class MiIoBindingConfiguration {
|
|||||||
public String token;
|
public String token;
|
||||||
public String deviceId;
|
public String deviceId;
|
||||||
public String model;
|
public String model;
|
||||||
|
public String communication;
|
||||||
public int refreshInterval;
|
public int refreshInterval;
|
||||||
public int timeout;
|
public int timeout;
|
||||||
public String cloudServer;
|
public String cloudServer;
|
||||||
|
|||||||
+1
@@ -61,6 +61,7 @@ public final class MiIoBindingConstants {
|
|||||||
|
|
||||||
public static final String CHANNEL_CONTROL = "actions#control";
|
public static final String CHANNEL_CONTROL = "actions#control";
|
||||||
public static final String CHANNEL_COMMAND = "actions#commands";
|
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_VACUUM = "actions#vacuum";
|
||||||
public static final String CHANNEL_FAN_CONTROL = "actions#fan";
|
public static final String CHANNEL_FAN_CONTROL = "actions#fan";
|
||||||
public static final String CHANNEL_TESTCOMMANDS = "actions#testcommands";
|
public static final String CHANNEL_TESTCOMMANDS = "actions#testcommands";
|
||||||
|
|||||||
+3
-3
@@ -79,14 +79,14 @@ public class MiIoHandlerFactory extends BaseThingHandlerFactory {
|
|||||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||||
if (thingTypeUID.equals(THING_TYPE_MIIO)) {
|
if (thingTypeUID.equals(THING_TYPE_MIIO)) {
|
||||||
return new MiIoGenericHandler(thing, miIoDatabaseWatchService);
|
return new MiIoGenericHandler(thing, miIoDatabaseWatchService, cloudConnector);
|
||||||
}
|
}
|
||||||
if (thingTypeUID.equals(THING_TYPE_BASIC)) {
|
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)) {
|
if (thingTypeUID.equals(THING_TYPE_VACUUM)) {
|
||||||
return new MiIoVacuumHandler(thing, miIoDatabaseWatchService, cloudConnector, channelTypeRegistry);
|
return new MiIoVacuumHandler(thing, miIoDatabaseWatchService, cloudConnector, channelTypeRegistry);
|
||||||
}
|
}
|
||||||
return new MiIoUnsupportedHandler(thing, miIoDatabaseWatchService);
|
return new MiIoUnsupportedHandler(thing, miIoDatabaseWatchService, cloudConnector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
@@ -31,6 +31,7 @@ public class MiIoSendCommand {
|
|||||||
private final MiIoCommand command;
|
private final MiIoCommand command;
|
||||||
private final JsonObject commandJson;
|
private final JsonObject commandJson;
|
||||||
private @Nullable JsonObject response;
|
private @Nullable JsonObject response;
|
||||||
|
private String cloudServer = "";
|
||||||
|
|
||||||
public void setResponse(JsonObject response) {
|
public void setResponse(JsonObject response) {
|
||||||
this.response = response;
|
this.response = response;
|
||||||
@@ -42,6 +43,13 @@ public class MiIoSendCommand {
|
|||||||
this.commandJson = fullCommand;
|
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() {
|
public int getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -86,4 +94,12 @@ public class MiIoSendCommand {
|
|||||||
}
|
}
|
||||||
return new JsonObject();
|
return new JsonObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCloudServer() {
|
||||||
|
return cloudServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCloudServer(String cloudServer) {
|
||||||
|
this.cloudServer = cloudServer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -21,6 +21,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.openhab.binding.miio.internal.MiIoSendCommand;
|
||||||
import org.openhab.core.cache.ExpiringCache;
|
import org.openhab.core.cache.ExpiringCache;
|
||||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
import org.openhab.core.io.net.http.HttpUtil;
|
import org.openhab.core.io.net.http.HttpUtil;
|
||||||
@@ -115,13 +116,21 @@ public class CloudConnector {
|
|||||||
return false;
|
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 {
|
public @Nullable RawType getMap(String mapId, String country) throws MiCloudException {
|
||||||
logger.debug("Getting vacuum map {} from Xiaomi cloud server: '{}'", mapId, country);
|
logger.debug("Getting vacuum map {} from Xiaomi cloud server: '{}'", mapId, country);
|
||||||
String mapCountry;
|
String mapCountry;
|
||||||
String mapUrl = "";
|
String mapUrl = "";
|
||||||
final @Nullable MiCloudConnector cl = this.cloudConnector;
|
final @Nullable MiCloudConnector cl = this.cloudConnector;
|
||||||
if (cl == null || !isConnected()) {
|
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()) {
|
if (country.isEmpty()) {
|
||||||
logger.debug("Server not defined in thing. Trying servers: {}", this.country);
|
logger.debug("Server not defined in thing. Trying servers: {}", this.country);
|
||||||
|
|||||||
+32
-10
@@ -176,10 +176,27 @@ public class MiCloudConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getDeviceStatus(String device, String country) throws MiCloudException {
|
public String getDeviceStatus(String device, String country) throws MiCloudException {
|
||||||
String url = getApiUrl(country) + "/home/device_list";
|
final String response = request("/home/device_list", country, "{\"dids\":[\"" + device + "\"]}");
|
||||||
Map<String, String> map = new HashMap<String, String>();
|
logger.debug("response: {}", response);
|
||||||
map.put("data", "{\"dids\":[\"" + device + "\"]}");
|
return response;
|
||||||
final String response = request(url, map);
|
}
|
||||||
|
|
||||||
|
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);
|
logger.debug("response: {}", response);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@@ -211,12 +228,9 @@ public class MiCloudConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getDeviceString(String country) {
|
public String getDeviceString(String country) {
|
||||||
String url = getApiUrl(country) + "/home/device_list";
|
|
||||||
Map<String, String> map = new HashMap<String, String>();
|
|
||||||
map.put("data", "{\"getVirtualModel\":false,\"getHuamiDevices\":0}");
|
|
||||||
String resp;
|
String resp;
|
||||||
try {
|
try {
|
||||||
resp = request(url, map);
|
resp = request("/home/device_list", country, "{\"getVirtualModel\":false,\"getHuamiDevices\":0}");
|
||||||
logger.trace("Get devices response: {}", resp);
|
logger.trace("Get devices response: {}", resp);
|
||||||
if (resp.length() > 2) {
|
if (resp.length() > 2) {
|
||||||
CloudUtil.saveDeviceInfoFile(resp, country, logger);
|
CloudUtil.saveDeviceInfoFile(resp, country, logger);
|
||||||
@@ -228,8 +242,15 @@ public class MiCloudConnector {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String request(String urlPart, String country, String params) throws MiCloudException {
|
||||||
|
Map<String, String> map = new HashMap<String, String>();
|
||||||
|
map.put("data", params);
|
||||||
|
return request(urlPart, country, map);
|
||||||
|
}
|
||||||
|
|
||||||
public String request(String urlPart, String country, Map<String, String> params) throws MiCloudException {
|
public String request(String urlPart, String country, Map<String, String> 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);
|
String response = request(url, params);
|
||||||
logger.debug("Request to {} server {}. Response: {}", country, urlPart, response);
|
logger.debug("Request to {} server {}. Response: {}", country, urlPart, response);
|
||||||
return response;
|
return response;
|
||||||
@@ -276,7 +297,8 @@ public class MiCloudConnector {
|
|||||||
|
|
||||||
logger.trace("fieldcontent: {}", fields.toString());
|
logger.trace("fieldcontent: {}", fields.toString());
|
||||||
final ContentResponse response = request.send();
|
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 = "";
|
this.serviceToken = "";
|
||||||
}
|
}
|
||||||
return response.getContentAsString();
|
return response.getContentAsString();
|
||||||
|
|||||||
+71
-18
@@ -19,7 +19,9 @@ import java.time.LocalDateTime;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
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.MiIoSendCommand;
|
||||||
import org.openhab.binding.miio.internal.Utils;
|
import org.openhab.binding.miio.internal.Utils;
|
||||||
import org.openhab.binding.miio.internal.basic.MiIoDatabaseWatchService;
|
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.binding.miio.internal.transport.MiIoAsyncCommunication;
|
||||||
import org.openhab.core.cache.ExpiringCache;
|
import org.openhab.core.cache.ExpiringCache;
|
||||||
|
import org.openhab.core.common.NamedThreadFactory;
|
||||||
import org.openhab.core.config.core.Configuration;
|
import org.openhab.core.config.core.Configuration;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
import org.openhab.core.library.types.StringType;
|
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 int MAX_QUEUE = 5;
|
||||||
protected static final Gson GSON = new GsonBuilder().create();
|
protected static final Gson GSON = new GsonBuilder().create();
|
||||||
|
|
||||||
|
protected ScheduledExecutorService miIoScheduler = scheduler;
|
||||||
protected @Nullable ScheduledFuture<?> pollingJob;
|
protected @Nullable ScheduledFuture<?> pollingJob;
|
||||||
protected MiIoDevices miDevice = MiIoDevices.UNKNOWN;
|
protected MiIoDevices miDevice = MiIoDevices.UNKNOWN;
|
||||||
protected boolean isIdentified;
|
protected boolean isIdentified;
|
||||||
@@ -76,6 +81,8 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi
|
|||||||
|
|
||||||
protected @Nullable MiIoBindingConfiguration configuration;
|
protected @Nullable MiIoBindingConfiguration configuration;
|
||||||
protected @Nullable MiIoAsyncCommunication miioCom;
|
protected @Nullable MiIoAsyncCommunication miioCom;
|
||||||
|
protected CloudConnector cloudConnector;
|
||||||
|
protected String cloudServer = "";
|
||||||
protected int lastId;
|
protected int lastId;
|
||||||
|
|
||||||
protected Map<Integer, String> cmds = new ConcurrentHashMap<>();
|
protected Map<Integer, String> cmds = new ConcurrentHashMap<>();
|
||||||
@@ -93,18 +100,39 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi
|
|||||||
private final Logger logger = LoggerFactory.getLogger(MiIoAbstractHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(MiIoAbstractHandler.class);
|
||||||
protected MiIoDatabaseWatchService miIoDatabaseWatchService;
|
protected MiIoDatabaseWatchService miIoDatabaseWatchService;
|
||||||
|
|
||||||
public MiIoAbstractHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService) {
|
public MiIoAbstractHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService,
|
||||||
|
CloudConnector cloudConnector) {
|
||||||
super(thing);
|
super(thing);
|
||||||
this.miIoDatabaseWatchService = miIoDatabaseWatchService;
|
this.miIoDatabaseWatchService = miIoDatabaseWatchService;
|
||||||
|
this.cloudConnector = cloudConnector;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public abstract void handleCommand(ChannelUID channelUID, Command command);
|
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
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
logger.debug("Initializing Mi IO device handler '{}' with thingType {}", getThing().getUID(),
|
logger.debug("Initializing Mi IO device handler '{}' with thingType {}", getThing().getUID(),
|
||||||
getThing().getThingTypeUID());
|
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);
|
final MiIoBindingConfiguration configuration = getConfigAs(MiIoBindingConfiguration.class);
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
if (configuration.host == null || configuration.host.isEmpty()) {
|
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");
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Token required. Configure token");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
cloudServer = (configuration.cloudServer != null) ? configuration.cloudServer : "";
|
||||||
isIdentified = false;
|
isIdentified = false;
|
||||||
scheduler.schedule(this::initializeData, 1, TimeUnit.SECONDS);
|
miIoScheduler.schedule(this::initializeData, 1, TimeUnit.SECONDS);
|
||||||
int pollingPeriod = configuration.refreshInterval;
|
int pollingPeriod = configuration.refreshInterval;
|
||||||
if (pollingPeriod > 0) {
|
if (pollingPeriod > 0) {
|
||||||
pollingJob = scheduler.scheduleWithFixedDelay(() -> {
|
pollingJob = miIoScheduler.scheduleWithFixedDelay(() -> {
|
||||||
try {
|
try {
|
||||||
updateData();
|
updateData();
|
||||||
} catch (Exception e) {
|
} 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());
|
logger.debug("Polling job scheduled to run every {} sec. for '{}'", pollingPeriod, getThing().getUID());
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Polling job disabled. for '{}'", getThing().getUID());
|
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);
|
updateStatus(ThingStatus.OFFLINE);
|
||||||
}
|
}
|
||||||
@@ -166,6 +195,7 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi
|
|||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
logger.debug("Disposing Xiaomi Mi IO handler '{}'", getThing().getUID());
|
logger.debug("Disposing Xiaomi Mi IO handler '{}'", getThing().getUID());
|
||||||
|
miIoScheduler.shutdown();
|
||||||
final ScheduledFuture<?> pollingJob = this.pollingJob;
|
final ScheduledFuture<?> pollingJob = this.pollingJob;
|
||||||
if (pollingJob != null) {
|
if (pollingJob != null) {
|
||||||
pollingJob.cancel(true);
|
pollingJob.cancel(true);
|
||||||
@@ -178,6 +208,7 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi
|
|||||||
miioCom.close();
|
miioCom.close();
|
||||||
this.miioCom = null;
|
this.miioCom = null;
|
||||||
}
|
}
|
||||||
|
miIoScheduler.shutdownNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int sendCommand(MiIoCommand command) {
|
protected int sendCommand(MiIoCommand command) {
|
||||||
@@ -187,7 +218,7 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi
|
|||||||
protected int sendCommand(MiIoCommand command, String params) {
|
protected int sendCommand(MiIoCommand command, String params) {
|
||||||
try {
|
try {
|
||||||
final MiIoAsyncCommunication connection = getConnection();
|
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) {
|
} catch (MiIoCryptoException | IOException e) {
|
||||||
logger.debug("Command {} for {} failed (type: {}): {}", command.toString(), getThing().getUID(),
|
logger.debug("Command {} for {} failed (type: {}): {}", command.toString(), getThing().getUID(),
|
||||||
getThing().getThingTypeUID(), e.getLocalizedMessage());
|
getThing().getThingTypeUID(), e.getLocalizedMessage());
|
||||||
@@ -195,6 +226,10 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi
|
|||||||
return 0;
|
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
|
* This is used to execute arbitrary commands by sending to the commands channel. Command parameters to be added
|
||||||
* between
|
* between
|
||||||
@@ -202,9 +237,10 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi
|
|||||||
* records)
|
* records)
|
||||||
*
|
*
|
||||||
* @param commandString command to be executed
|
* @param commandString command to be executed
|
||||||
|
* @param cloud server to be used or empty string for direct sending to the device
|
||||||
* @return vacuum response
|
* @return vacuum response
|
||||||
*/
|
*/
|
||||||
protected int sendCommand(String commandString) {
|
protected int sendCommand(String commandString, String cloudServer) {
|
||||||
final MiIoAsyncCommunication connection = getConnection();
|
final MiIoAsyncCommunication connection = getConnection();
|
||||||
try {
|
try {
|
||||||
String command = commandString.trim();
|
String command = commandString.trim();
|
||||||
@@ -216,13 +252,24 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi
|
|||||||
param = command.substring(loc).trim();
|
param = command.substring(loc).trim();
|
||||||
command = command.substring(0, 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) {
|
} catch (MiIoCryptoException | IOException e) {
|
||||||
disconnected(e.getMessage());
|
disconnected(e.getMessage());
|
||||||
}
|
}
|
||||||
return 0;
|
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() {
|
protected boolean skipUpdate() {
|
||||||
final MiIoAsyncCommunication miioCom = this.miioCom;
|
final MiIoAsyncCommunication miioCom = this.miioCom;
|
||||||
if (!hasConnection() || miioCom == null) {
|
if (!hasConnection() || miioCom == null) {
|
||||||
@@ -232,11 +279,7 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi
|
|||||||
if (getThing().getStatusInfo().getStatusDetail().equals(ThingStatusDetail.CONFIGURATION_ERROR)) {
|
if (getThing().getStatusInfo().getStatusDetail().equals(ThingStatusDetail.CONFIGURATION_ERROR)) {
|
||||||
logger.debug("Skipping periodic update for '{}'. Thing Status {}", getThing().getUID().toString(),
|
logger.debug("Skipping periodic update for '{}'. Thing Status {}", getThing().getUID().toString(),
|
||||||
getThing().getStatusInfo().getStatusDetail());
|
getThing().getStatusInfo().getStatusDetail());
|
||||||
try {
|
sendCommand(MiIoCommand.MIIO_INFO);
|
||||||
miioCom.queueCommand(MiIoCommand.MIIO_INFO);
|
|
||||||
} catch (MiIoCryptoException | IOException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (miioCom.getQueueLength() > MAX_QUEUE) {
|
if (miioCom.getQueueLength() > MAX_QUEUE) {
|
||||||
@@ -299,24 +342,30 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi
|
|||||||
String deviceId = configuration.deviceId;
|
String deviceId = configuration.deviceId;
|
||||||
try {
|
try {
|
||||||
if (deviceId != null && deviceId.length() == 8 && tokenCheckPass(configuration.token)) {
|
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,
|
final MiIoAsyncCommunication miioCom = new MiIoAsyncCommunication(configuration.host, token,
|
||||||
Utils.hexStringToByteArray(deviceId), lastId, configuration.timeout);
|
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);
|
Message miIoResponse = miioCom.sendPing(configuration.host);
|
||||||
if (miIoResponse != null) {
|
if (miIoResponse != null) {
|
||||||
logger.debug("Ping response from device {} at {}. Time stamp: {}, OH time {}, delta {}",
|
logger.debug("Ping response from device {} at {}. Time stamp: {}, OH time {}, delta {}",
|
||||||
Utils.getHex(miIoResponse.getDeviceId()), configuration.host, miIoResponse.getTimestamp(),
|
Utils.getHex(miIoResponse.getDeviceId()), configuration.host,
|
||||||
LocalDateTime.now(), miioCom.getTimeDelta());
|
miIoResponse.getTimestamp(), LocalDateTime.now(), miioCom.getTimeDelta());
|
||||||
miioCom.registerListener(this);
|
miioCom.registerListener(this);
|
||||||
this.miioCom = miioCom;
|
this.miioCom = miioCom;
|
||||||
return miioCom;
|
return miioCom;
|
||||||
} else {
|
} else {
|
||||||
miioCom.close();
|
miioCom.close();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
miioCom.registerListener(this);
|
||||||
|
this.miioCom = miioCom;
|
||||||
|
return miioCom;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.debug("No device ID defined. Retrieving Mi device ID");
|
logger.debug("No device ID defined. Retrieving Mi device ID");
|
||||||
final MiIoAsyncCommunication miioCom = new MiIoAsyncCommunication(configuration.host, token,
|
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);
|
Message miIoResponse = miioCom.sendPing(configuration.host);
|
||||||
if (miIoResponse != null) {
|
if (miIoResponse != null) {
|
||||||
logger.debug("Ping response from device {} at {}. Time stamp: {}, OH time {}, delta {}",
|
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);
|
pollingJob.cancel(true);
|
||||||
this.pollingJob = null;
|
this.pollingJob = null;
|
||||||
}
|
}
|
||||||
scheduler.schedule(() -> {
|
miIoScheduler.schedule(() -> {
|
||||||
ThingBuilder thingBuilder = editThing();
|
ThingBuilder thingBuilder = editThing();
|
||||||
thingBuilder.withLabel(miDevice.getDescription());
|
thingBuilder.withLabel(miDevice.getDescription());
|
||||||
updateThing(thingBuilder.build());
|
updateThing(thingBuilder.build());
|
||||||
@@ -484,7 +533,11 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (cmds.containsKey(response.getId())) {
|
if (cmds.containsKey(response.getId())) {
|
||||||
|
if (response.getCloudServer().isBlank()) {
|
||||||
updateState(CHANNEL_COMMAND, new StringType(response.getResponse().toString()));
|
updateState(CHANNEL_COMMAND, new StringType(response.getResponse().toString()));
|
||||||
|
} else {
|
||||||
|
updateState(CHANNEL_RPC, new StringType(response.getResponse().toString()));
|
||||||
|
}
|
||||||
cmds.remove(response.getId());
|
cmds.remove(response.getId());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
+7
-14
@@ -30,7 +30,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.miio.internal.MiIoBindingConfiguration;
|
import org.openhab.binding.miio.internal.MiIoBindingConfiguration;
|
||||||
import org.openhab.binding.miio.internal.MiIoCommand;
|
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.MiIoQuantiyTypes;
|
||||||
import org.openhab.binding.miio.internal.MiIoSendCommand;
|
import org.openhab.binding.miio.internal.MiIoSendCommand;
|
||||||
import org.openhab.binding.miio.internal.Utils;
|
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.MiIoDatabaseWatchService;
|
||||||
import org.openhab.binding.miio.internal.basic.MiIoDeviceAction;
|
import org.openhab.binding.miio.internal.basic.MiIoDeviceAction;
|
||||||
import org.openhab.binding.miio.internal.basic.MiIoDeviceActionCondition;
|
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.binding.miio.internal.transport.MiIoAsyncCommunication;
|
||||||
import org.openhab.core.cache.ExpiringCache;
|
import org.openhab.core.cache.ExpiringCache;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
@@ -85,7 +85,7 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
|
|||||||
private boolean hasChannelStructure;
|
private boolean hasChannelStructure;
|
||||||
|
|
||||||
private final ExpiringCache<Boolean> updateDataCache = new ExpiringCache<>(CACHE_EXPIRY, () -> {
|
private final ExpiringCache<Boolean> updateDataCache = new ExpiringCache<>(CACHE_EXPIRY, () -> {
|
||||||
scheduler.schedule(this::updateData, 0, TimeUnit.SECONDS);
|
miIoScheduler.schedule(this::updateData, 0, TimeUnit.SECONDS);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -97,8 +97,8 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
|
|||||||
private ChannelTypeRegistry channelTypeRegistry;
|
private ChannelTypeRegistry channelTypeRegistry;
|
||||||
|
|
||||||
public MiIoBasicHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService,
|
public MiIoBasicHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService,
|
||||||
ChannelTypeRegistry channelTypeRegistry) {
|
CloudConnector cloudConnector, ChannelTypeRegistry channelTypeRegistry) {
|
||||||
super(thing, miIoDatabaseWatchService);
|
super(thing, miIoDatabaseWatchService, cloudConnector);
|
||||||
this.channelTypeRegistry = channelTypeRegistry;
|
this.channelTypeRegistry = channelTypeRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +255,7 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateDataCache.invalidateValue();
|
updateDataCache.invalidateValue();
|
||||||
scheduler.schedule(() -> {
|
miIoScheduler.schedule(() -> {
|
||||||
updateData();
|
updateData();
|
||||||
}, 3000, TimeUnit.MILLISECONDS);
|
}, 3000, TimeUnit.MILLISECONDS);
|
||||||
} else {
|
} else {
|
||||||
@@ -294,7 +294,7 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
|
|||||||
}
|
}
|
||||||
checkChannelStructure();
|
checkChannelStructure();
|
||||||
if (!isIdentified) {
|
if (!isIdentified) {
|
||||||
miioCom.queueCommand(MiIoCommand.MIIO_INFO);
|
sendCommand(MiIoCommand.MIIO_INFO);
|
||||||
}
|
}
|
||||||
final MiIoBasicDevice midevice = miioDevice;
|
final MiIoBasicDevice midevice = miioDevice;
|
||||||
if (midevice != null) {
|
if (midevice != null) {
|
||||||
@@ -341,14 +341,7 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void sendRefreshProperties(MiIoCommand command, JsonArray getPropString) {
|
private void sendRefreshProperties(MiIoCommand command, JsonArray getPropString) {
|
||||||
try {
|
sendCommand(command, getPropString.toString());
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+6
-6
@@ -12,10 +12,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.miio.internal.handler;
|
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.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.binding.miio.internal.basic.MiIoDatabaseWatchService;
|
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.ChannelUID;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
@@ -33,8 +32,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
public class MiIoGenericHandler extends MiIoAbstractHandler {
|
public class MiIoGenericHandler extends MiIoAbstractHandler {
|
||||||
private final Logger logger = LoggerFactory.getLogger(MiIoGenericHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(MiIoGenericHandler.class);
|
||||||
|
|
||||||
public MiIoGenericHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService) {
|
public MiIoGenericHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService,
|
||||||
super(thing, miIoDatabaseWatchService);
|
CloudConnector cloudConnector) {
|
||||||
|
super(thing, miIoDatabaseWatchService, cloudConnector);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -44,8 +44,8 @@ public class MiIoGenericHandler extends MiIoAbstractHandler {
|
|||||||
updateData();
|
updateData();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (channelUID.getId().equals(CHANNEL_COMMAND)) {
|
if (handleCommandsChannels(channelUID, command)) {
|
||||||
cmds.put(sendCommand(command.toString()), command.toString());
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-5
@@ -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.MiIoBasicChannel;
|
||||||
import org.openhab.binding.miio.internal.basic.MiIoBasicDevice;
|
import org.openhab.binding.miio.internal.basic.MiIoBasicDevice;
|
||||||
import org.openhab.binding.miio.internal.basic.MiIoDatabaseWatchService;
|
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.cache.ExpiringCache;
|
||||||
import org.openhab.core.library.types.OnOffType;
|
import org.openhab.core.library.types.OnOffType;
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
@@ -78,12 +79,13 @@ public class MiIoUnsupportedHandler extends MiIoAbstractHandler {
|
|||||||
private String model = conf.model != null ? conf.model : "";
|
private String model = conf.model != null ? conf.model : "";
|
||||||
|
|
||||||
private final ExpiringCache<Boolean> updateDataCache = new ExpiringCache<>(CACHE_EXPIRY, () -> {
|
private final ExpiringCache<Boolean> updateDataCache = new ExpiringCache<>(CACHE_EXPIRY, () -> {
|
||||||
scheduler.schedule(this::updateData, 0, TimeUnit.SECONDS);
|
miIoScheduler.schedule(this::updateData, 0, TimeUnit.SECONDS);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
public MiIoUnsupportedHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService) {
|
public MiIoUnsupportedHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService,
|
||||||
super(thing, miIoDatabaseWatchService);
|
CloudConnector cloudConnector) {
|
||||||
|
super(thing, miIoDatabaseWatchService, cloudConnector);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -104,8 +106,8 @@ public class MiIoUnsupportedHandler extends MiIoAbstractHandler {
|
|||||||
sendCommand("set_power[\"off\"]");
|
sendCommand("set_power[\"off\"]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (channelUID.getId().equals(CHANNEL_COMMAND)) {
|
if (handleCommandsChannels(channelUID, command)) {
|
||||||
cmds.put(sendCommand(command.toString()), command.toString());
|
return;
|
||||||
}
|
}
|
||||||
if (channelUID.getId().equals(CHANNEL_TESTCOMMANDS)) {
|
if (channelUID.getId().equals(CHANNEL_TESTCOMMANDS)) {
|
||||||
executeExperimentalCommands();
|
executeExperimentalCommands();
|
||||||
|
|||||||
+9
-12
@@ -97,15 +97,13 @@ public class MiIoVacuumHandler extends MiIoAbstractHandler {
|
|||||||
private ExpiringCache<String> map;
|
private ExpiringCache<String> map;
|
||||||
private String lastHistoryId = "";
|
private String lastHistoryId = "";
|
||||||
private String lastMap = "";
|
private String lastMap = "";
|
||||||
private CloudConnector cloudConnector;
|
|
||||||
private boolean hasChannelStructure;
|
private boolean hasChannelStructure;
|
||||||
private ConcurrentHashMap<RobotCababilities, Boolean> deviceCapabilities = new ConcurrentHashMap<>();
|
private ConcurrentHashMap<RobotCababilities, Boolean> deviceCapabilities = new ConcurrentHashMap<>();
|
||||||
private ChannelTypeRegistry channelTypeRegistry;
|
private ChannelTypeRegistry channelTypeRegistry;
|
||||||
|
|
||||||
public MiIoVacuumHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService,
|
public MiIoVacuumHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService,
|
||||||
CloudConnector cloudConnector, ChannelTypeRegistry channelTypeRegistry) {
|
CloudConnector cloudConnector, ChannelTypeRegistry channelTypeRegistry) {
|
||||||
super(thing, miIoDatabaseWatchService);
|
super(thing, miIoDatabaseWatchService, cloudConnector);
|
||||||
this.cloudConnector = cloudConnector;
|
|
||||||
this.channelTypeRegistry = channelTypeRegistry;
|
this.channelTypeRegistry = channelTypeRegistry;
|
||||||
mapChannelUid = new ChannelUID(thing.getUID(), CHANNEL_VACUUM_MAP);
|
mapChannelUid = new ChannelUID(thing.getUID(), CHANNEL_VACUUM_MAP);
|
||||||
status = new ExpiringCache<>(CACHE_EXPIRY, () -> {
|
status = new ExpiringCache<>(CACHE_EXPIRY, () -> {
|
||||||
@@ -180,6 +178,9 @@ public class MiIoVacuumHandler extends MiIoAbstractHandler {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (handleCommandsChannels(channelUID, command)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (channelUID.getId().equals(CHANNEL_VACUUM)) {
|
if (channelUID.getId().equals(CHANNEL_VACUUM)) {
|
||||||
if (command instanceof OnOffType) {
|
if (command instanceof OnOffType) {
|
||||||
if (command.equals(OnOffType.ON)) {
|
if (command.equals(OnOffType.ON)) {
|
||||||
@@ -188,7 +189,7 @@ public class MiIoVacuumHandler extends MiIoAbstractHandler {
|
|||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
sendCommand(MiIoCommand.STOP_VACUUM);
|
sendCommand(MiIoCommand.STOP_VACUUM);
|
||||||
scheduler.schedule(() -> {
|
miIoScheduler.schedule(() -> {
|
||||||
sendCommand(MiIoCommand.CHARGE);
|
sendCommand(MiIoCommand.CHARGE);
|
||||||
forceStatusUpdate();
|
forceStatusUpdate();
|
||||||
}, 2000, TimeUnit.MILLISECONDS);
|
}, 2000, TimeUnit.MILLISECONDS);
|
||||||
@@ -205,7 +206,7 @@ public class MiIoVacuumHandler extends MiIoAbstractHandler {
|
|||||||
sendCommand(MiIoCommand.PAUSE);
|
sendCommand(MiIoCommand.PAUSE);
|
||||||
} else if (command.toString().equals("dock")) {
|
} else if (command.toString().equals("dock")) {
|
||||||
sendCommand(MiIoCommand.STOP_VACUUM);
|
sendCommand(MiIoCommand.STOP_VACUUM);
|
||||||
scheduler.schedule(() -> {
|
miIoScheduler.schedule(() -> {
|
||||||
sendCommand(MiIoCommand.CHARGE);
|
sendCommand(MiIoCommand.CHARGE);
|
||||||
forceStatusUpdate();
|
forceStatusUpdate();
|
||||||
}, 2000, TimeUnit.MILLISECONDS);
|
}, 2000, TimeUnit.MILLISECONDS);
|
||||||
@@ -243,14 +244,13 @@ public class MiIoVacuumHandler extends MiIoAbstractHandler {
|
|||||||
sendCommand(MiIoCommand.CONSUMABLES_RESET, "[" + command.toString() + "]");
|
sendCommand(MiIoCommand.CONSUMABLES_RESET, "[" + command.toString() + "]");
|
||||||
updateState(CHANNEL_CONSUMABLE_RESET, new StringType("none"));
|
updateState(CHANNEL_CONSUMABLE_RESET, new StringType("none"));
|
||||||
}
|
}
|
||||||
if (channelUID.getId().equals(CHANNEL_COMMAND)) {
|
|
||||||
cmds.put(sendCommand(command.toString()), command.toString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void forceStatusUpdate() {
|
private void forceStatusUpdate() {
|
||||||
status.invalidateValue();
|
status.invalidateValue();
|
||||||
|
miIoScheduler.schedule(() -> {
|
||||||
status.getValue();
|
status.getValue();
|
||||||
|
}, 3000, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void safeUpdateState(String channelID, @Nullable Integer state) {
|
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();
|
String mapresponse = response.getResult().getAsJsonArray().get(0).getAsString();
|
||||||
if (!mapresponse.contentEquals("retry") && !mapresponse.contentEquals(lastMap)) {
|
if (!mapresponse.contentEquals("retry") && !mapresponse.contentEquals(lastMap)) {
|
||||||
lastMap = mapresponse;
|
lastMap = mapresponse;
|
||||||
scheduler.submit(() -> updateState(CHANNEL_VACUUM_MAP, getMap(mapresponse)));
|
miIoScheduler.submit(() -> updateState(CHANNEL_VACUUM_MAP, getMap(mapresponse)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case UNKNOWN:
|
|
||||||
updateState(CHANNEL_COMMAND, new StringType(response.getResponse().toString()));
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-10
@@ -37,6 +37,8 @@ import org.openhab.binding.miio.internal.MiIoCryptoException;
|
|||||||
import org.openhab.binding.miio.internal.MiIoMessageListener;
|
import org.openhab.binding.miio.internal.MiIoMessageListener;
|
||||||
import org.openhab.binding.miio.internal.MiIoSendCommand;
|
import org.openhab.binding.miio.internal.MiIoSendCommand;
|
||||||
import org.openhab.binding.miio.internal.Utils;
|
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.ThingStatus;
|
||||||
import org.openhab.core.thing.ThingStatusDetail;
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -78,14 +80,17 @@ public class MiIoAsyncCommunication {
|
|||||||
private boolean needPing = true;
|
private boolean needPing = true;
|
||||||
private static final int MAX_ERRORS = 3;
|
private static final int MAX_ERRORS = 3;
|
||||||
private static final int MAX_ID = 15000;
|
private static final int MAX_ID = 15000;
|
||||||
|
private final CloudConnector cloudConnector;
|
||||||
|
|
||||||
private ConcurrentLinkedQueue<MiIoSendCommand> concurrentLinkedQueue = new ConcurrentLinkedQueue<>();
|
private ConcurrentLinkedQueue<MiIoSendCommand> 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.ip = ip;
|
||||||
this.token = token;
|
this.token = token;
|
||||||
this.deviceId = did;
|
this.deviceId = did;
|
||||||
this.timeout = timeout;
|
this.timeout = timeout;
|
||||||
|
this.cloudConnector = cloudConnector;
|
||||||
setId(id);
|
setId(id);
|
||||||
parser = new JsonParser();
|
parser = new JsonParser();
|
||||||
startReceiver();
|
startReceiver();
|
||||||
@@ -124,15 +129,16 @@ public class MiIoAsyncCommunication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int queueCommand(MiIoCommand command) throws MiIoCryptoException, IOException {
|
public int queueCommand(MiIoCommand command, String cloudServer) throws MiIoCryptoException, IOException {
|
||||||
return queueCommand(command, "[]");
|
return queueCommand(command, "[]", cloudServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int queueCommand(MiIoCommand command, String params) throws MiIoCryptoException, IOException {
|
public int queueCommand(MiIoCommand command, String params, String cloudServer)
|
||||||
return queueCommand(command.getCommand(), params);
|
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 {
|
throws MiIoCryptoException, IOException, JsonSyntaxException {
|
||||||
try {
|
try {
|
||||||
JsonObject fullCommand = new JsonObject();
|
JsonObject fullCommand = new JsonObject();
|
||||||
@@ -143,15 +149,17 @@ public class MiIoAsyncCommunication {
|
|||||||
fullCommand.addProperty("id", cmdId);
|
fullCommand.addProperty("id", cmdId);
|
||||||
fullCommand.addProperty("method", command);
|
fullCommand.addProperty("method", command);
|
||||||
fullCommand.add("params", parser.parse(params));
|
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);
|
concurrentLinkedQueue.add(sendCmd);
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
// Obfuscate part of the token to allow sharing of the logfiles
|
// Obfuscate part of the token to allow sharing of the logfiles
|
||||||
String tokenText = Utils.obfuscateToken(Utils.getHex(token));
|
String tokenText = Utils.obfuscateToken(Utils.getHex(token));
|
||||||
logger.debug("Command added to Queue {} -> {} (Device: {} token: {} Queue: {})", fullCommand.toString(),
|
logger.debug("Command added to Queue {} -> {} (Device: {} token: {} Queue: {}).{}{}",
|
||||||
ip, Utils.getHex(deviceId), tokenText, concurrentLinkedQueue.size());
|
fullCommand.toString(), ip, Utils.getHex(deviceId), tokenText, concurrentLinkedQueue.size(),
|
||||||
|
cloudServer.isBlank() ? "" : " Send via cloudserver: ", cloudServer);
|
||||||
}
|
}
|
||||||
if (needPing) {
|
if (needPing && cloudServer.isBlank()) {
|
||||||
sendPing(ip);
|
sendPing(ip);
|
||||||
}
|
}
|
||||||
return cmdId;
|
return cmdId;
|
||||||
@@ -166,7 +174,15 @@ public class MiIoAsyncCommunication {
|
|||||||
String errorMsg = "Unknown Error while sending command";
|
String errorMsg = "Unknown Error while sending command";
|
||||||
String decryptedResponse = "";
|
String decryptedResponse = "";
|
||||||
try {
|
try {
|
||||||
|
if (miIoSendCommand.getCloudServer().isBlank()) {
|
||||||
decryptedResponse = sendCommand(miIoSendCommand.getCommandString(), token, ip, deviceId);
|
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
|
// hack due to avoid invalid json errors from some misbehaving device firmwares
|
||||||
decryptedResponse = decryptedResponse.replace(",,", ",");
|
decryptedResponse = decryptedResponse.replace(",,", ",");
|
||||||
JsonElement response;
|
JsonElement response;
|
||||||
@@ -188,6 +204,12 @@ public class MiIoAsyncCommunication {
|
|||||||
logger.warn("Could not parse '{}' <- {} (Device: {}) gave error {}", decryptedResponse,
|
logger.warn("Could not parse '{}' <- {} (Device: {}) gave error {}", decryptedResponse,
|
||||||
miIoSendCommand.getCommandString(), Utils.getHex(deviceId), e.getMessage());
|
miIoSendCommand.getCommandString(), Utils.getHex(deviceId), e.getMessage());
|
||||||
errorMsg = "Received message is invalid JSON";
|
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();
|
JsonObject erroResp = new JsonObject();
|
||||||
erroResp.addProperty("error", errorMsg);
|
erroResp.addProperty("error", errorMsg);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<config-description:config-descriptions
|
<config-description:config-descriptions
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0https://openhab.org/schemas/config-description-1.0.0.xsd">
|
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||||
|
|
||||||
<config-description uri="thing-type:miio:config">
|
<config-description uri="thing-type:miio:config">
|
||||||
<parameter name="host" type="text" required="true">
|
<parameter name="host" type="text" required="true">
|
||||||
@@ -23,6 +23,16 @@
|
|||||||
<description>Device model string, used to determine the subtype.</description>
|
<description>Device model string, used to determine the subtype.</description>
|
||||||
<advanced>true</advanced>
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
|
<parameter name="communication" type="text" required="false">
|
||||||
|
<default>direct</default>
|
||||||
|
<label>Communication Method</label>
|
||||||
|
<description>Determines how the binding communicates with this device</description>
|
||||||
|
<options>
|
||||||
|
<option value="direct">Direct (Default)</option>
|
||||||
|
<option value="cloud">Cloud</option>
|
||||||
|
</options>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
<parameter name="refreshInterval" type="integer" min="0" max="9999" required="false">
|
<parameter name="refreshInterval" type="integer" min="0" max="9999" required="false">
|
||||||
<label>Refresh Interval</label>
|
<label>Refresh Interval</label>
|
||||||
<description>Refresh interval for refreshing the data in seconds. (0=disabled)</description>
|
<description>Refresh interval for refreshing the data in seconds. (0=disabled)</description>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
<label>Actions</label>
|
<label>Actions</label>
|
||||||
<channels>
|
<channels>
|
||||||
<channel id="commands" typeId="commands"/>
|
<channel id="commands" typeId="commands"/>
|
||||||
|
<channel id="rpc" typeId="rpc"/>
|
||||||
</channels>
|
</channels>
|
||||||
</channel-group-type>
|
</channel-group-type>
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,10 @@
|
|||||||
<item-type>String</item-type>
|
<item-type>String</item-type>
|
||||||
<label>Execute Command</label>
|
<label>Execute Command</label>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
<channel-type id="rpc" advanced="true">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Execute RPC (cloud) Command</label>
|
||||||
|
</channel-type>
|
||||||
<channel-type id="power">
|
<channel-type id="power">
|
||||||
<item-type>Switch</item-type>
|
<item-type>Switch</item-type>
|
||||||
<label>Power On/Off</label>
|
<label>Power On/Off</label>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
<channels>
|
<channels>
|
||||||
<channel id="power" typeId="power"/>
|
<channel id="power" typeId="power"/>
|
||||||
<channel id="commands" typeId="commands"/>
|
<channel id="commands" typeId="commands"/>
|
||||||
|
<channel id="rpc" typeId="rpc"/>
|
||||||
<channel id="testcommands" typeId="testcommands"/>
|
<channel id="testcommands" typeId="testcommands"/>
|
||||||
</channels>
|
</channels>
|
||||||
</channel-group-type>
|
</channel-group-type>
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
<channels>
|
<channels>
|
||||||
<channel id="control" typeId="control"/>
|
<channel id="control" typeId="control"/>
|
||||||
<channel id="commands" typeId="commands"/>
|
<channel id="commands" typeId="commands"/>
|
||||||
|
<channel id="rpc" typeId="rpc"/>
|
||||||
<channel id="fan" typeId="fan"/>
|
<channel id="fan" typeId="fan"/>
|
||||||
<channel id="vacuum" typeId="vacuum"/>
|
<channel id="vacuum" typeId="vacuum"/>
|
||||||
<channel id="segment" typeId="segment"/>
|
<channel id="segment" typeId="segment"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user