[miio] add new custom refresh methods (#10957)

* Improve matching the command responses to the sending channel
* Use newer method to get device list using `device_list_page`
* Adding the ability to send custom requests to cloud
* Adding the ability to send custom commands with additional elements in
the json

Signed-off-by: Marcel Verpaalen <marcel@verpaalen.com>
This commit is contained in:
Marcel 2021-07-12 09:55:16 +02:00 committed by GitHub
parent c0ab022e4e
commit 072a6045c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 72 additions and 22 deletions

View File

@ -124,6 +124,14 @@ public class CloudConnector {
return cl.sendRPCCommand(device, country.trim().toLowerCase(), command.getCommandString()); return cl.sendRPCCommand(device, country.trim().toLowerCase(), command.getCommandString());
} }
public String sendCloudCommand(String urlPart, String country, String parameters) throws MiCloudException {
final @Nullable MiCloudConnector cl = this.cloudConnector;
if (cl == null || !isConnected()) {
throw new MiCloudException("Cannot execute request. Cloud service not available");
}
return cl.request(urlPart, country, parameters);
}
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;

View File

@ -237,7 +237,7 @@ public class MiCloudConnector {
public String getDeviceString(String country) { public String getDeviceString(String country) {
String resp; String resp;
try { try {
resp = request("/home/device_list", country, "{\"getVirtualModel\":false,\"getHuamiDevices\":0}"); resp = request("/home/device_list_page", country, "{\"getVirtualModel\":false,\"getHuamiDevices\":1}");
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);
@ -260,7 +260,7 @@ public class MiCloudConnector {
String url = urlPart.trim(); String url = urlPart.trim();
url = getApiUrl(country) + (url.startsWith("/app") ? url.substring(4) : url); 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;
} }
@ -270,7 +270,7 @@ public class MiCloudConnector {
} }
loginFailedCounterCheck(); loginFailedCounterCheck();
startClient(); startClient();
logger.debug("Send request: {} to {}", params.get("data"), url); logger.debug("Send request to {} with data '{}'", url, params.get("data"));
Request request = httpClient.newRequest(url).timeout(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); Request request = httpClient.newRequest(url).timeout(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
request.agent(USERAGENT); request.agent(USERAGENT);
request.header("x-xiaomi-protocal-flag-cli", "PROTOCAL-HTTP2"); request.header("x-xiaomi-protocal-flag-cli", "PROTOCAL-HTTP2");

View File

@ -112,11 +112,11 @@ public abstract class MiIoAbstractHandler extends BaseThingHandler implements Mi
protected boolean handleCommandsChannels(ChannelUID channelUID, Command command) { protected boolean handleCommandsChannels(ChannelUID channelUID, Command command) {
if (channelUID.getId().equals(CHANNEL_COMMAND)) { if (channelUID.getId().equals(CHANNEL_COMMAND)) {
cmds.put(sendCommand(command.toString(), ""), command.toString()); cmds.put(sendCommand(command.toString()), channelUID.getId());
return true; return true;
} }
if (channelUID.getId().equals(CHANNEL_RPC)) { if (channelUID.getId().equals(CHANNEL_RPC)) {
cmds.put(sendCommand(command.toString(), cloudServer), command.toString()); cmds.put(sendCommand(command.toString(), cloudServer), channelUID.getId());
return true; return true;
} }
return false; return false;
@ -553,12 +553,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()) { String channel = cmds.get(response.getId());
updateState(CHANNEL_COMMAND, new StringType(response.getResponse().toString())); if (channel != null && (CHANNEL_COMMAND.contentEquals(channel) || CHANNEL_RPC.contentEquals(channel))) {
} else { updateState(channel, new StringType(response.getResponse().toString()));
updateState(CHANNEL_RPC, new StringType(response.getResponse().toString())); cmds.remove(response.getId());
} }
cmds.remove(response.getId());
} }
} catch (Exception e) { } catch (Exception e) {
logger.debug("Error while handing message {}", response.getResponse(), e); logger.debug("Error while handing message {}", response.getResponse(), e);

View File

@ -328,7 +328,17 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
getThing().getUID()); getThing().getUID());
continue; continue;
} }
sendCommand(miChannel.getChannelCustomRefreshCommand()); String cmd = miChannel.getChannelCustomRefreshCommand();
if (!cmd.startsWith("/")) {
cmds.put(sendCommand(miChannel.getChannelCustomRefreshCommand()), miChannel.getChannel());
} else {
if (cloudServer.isBlank()) {
logger.debug("Cloudserver empty. Skipping refresh for {} channel '{}'", getThing().getUID(),
miChannel.getChannel());
} else {
cmds.put(sendCommand(cmd, cloudServer), miChannel.getChannel());
}
}
} }
} }
@ -515,6 +525,16 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
return null; return null;
} }
private @Nullable MiIoBasicChannel getCustomRefreshChannel(String channelName) {
for (MiIoBasicChannel refreshEntry : refreshListCustomCommands.values()) {
if (refreshEntry.getChannel().equals(channelName)) {
return refreshEntry;
}
}
logger.trace("Did not find channel for {} in {}", channelName, refreshList);
return null;
}
private void updatePropsFromJsonArray(MiIoSendCommand response) { private void updatePropsFromJsonArray(MiIoSendCommand response) {
JsonArray res = response.getResult().getAsJsonArray(); JsonArray res = response.getResult().getAsJsonArray();
JsonArray para = JsonParser.parseString(response.getCommandString()).getAsJsonObject().get("params") JsonArray para = JsonParser.parseString(response.getCommandString()).getAsJsonObject().get("params")
@ -679,10 +699,11 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
} }
break; break;
default: default:
if (refreshListCustomCommands.containsKey(response.getMethod())) { String channel = cmds.get(response.getId());
if (channel != null) {
logger.debug("Processing custom refresh command response for '{}' - {}", response.getMethod(), logger.debug("Processing custom refresh command response for '{}' - {}", response.getMethod(),
response.getResult()); response.getResult());
final MiIoBasicChannel ch = refreshListCustomCommands.get(response.getMethod()); final MiIoBasicChannel ch = getCustomRefreshChannel(channel);
if (ch != null) { if (ch != null) {
if (response.getResult().isJsonArray()) { if (response.getResult().isJsonArray()) {
JsonArray cmdResponse = response.getResult().getAsJsonArray(); JsonArray cmdResponse = response.getResult().getAsJsonArray();
@ -698,6 +719,7 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
updateChannel(ch, ch.getChannel(), new JsonPrimitive(response.getResult().toString())); updateChannel(ch, ch.getChannel(), new JsonPrimitive(response.getResult().toString()));
} }
} }
cmds.remove(response.getId());
} }
break; break;
} }

View File

@ -146,9 +146,17 @@ public class MiIoAsyncCommunication {
if (cmdId > MAX_ID) { if (cmdId > MAX_ID) {
id.set(0); id.set(0);
} }
fullCommand.addProperty("id", cmdId); if (command.startsWith("{") && command.endsWith("}")) {
fullCommand.addProperty("method", command); fullCommand = JsonParser.parseString(command).getAsJsonObject();
fullCommand.add("params", JsonParser.parseString(params)); fullCommand.addProperty("id", cmdId);
if (!fullCommand.has("params") && !params.isBlank()) {
fullCommand.add("params", JsonParser.parseString(params));
}
} else {
fullCommand.addProperty("id", cmdId);
fullCommand.addProperty("method", command);
fullCommand.add("params", JsonParser.parseString(params));
}
MiIoSendCommand sendCmd = new MiIoSendCommand(cmdId, MiIoCommand.getCommand(command), fullCommand, MiIoSendCommand sendCmd = new MiIoSendCommand(cmdId, MiIoCommand.getCommand(command), fullCommand,
cloudServer); cloudServer);
concurrentLinkedQueue.add(sendCmd); concurrentLinkedQueue.add(sendCmd);
@ -163,7 +171,7 @@ public class MiIoAsyncCommunication {
sendPing(ip); sendPing(ip);
} }
return cmdId; return cmdId;
} catch (JsonSyntaxException e) { } catch (JsonSyntaxException | IllegalStateException e) {
logger.warn("Send command '{}' with parameters {} -> {} (Device: {}) gave error {}", command, params, ip, logger.warn("Send command '{}' with parameters {} -> {} (Device: {}) gave error {}", command, params, ip,
deviceId, e.getMessage()); deviceId, e.getMessage());
throw e; throw e;
@ -177,11 +185,24 @@ public class MiIoAsyncCommunication {
if (miIoSendCommand.getCloudServer().isBlank()) { if (miIoSendCommand.getCloudServer().isBlank()) {
decryptedResponse = sendCommand(miIoSendCommand.getCommandString(), token, ip, deviceId); decryptedResponse = sendCommand(miIoSendCommand.getCommandString(), token, ip, deviceId);
} else { } else {
decryptedResponse = cloudConnector.sendRPCCommand(Utils.getHexId(deviceId), if (!miIoSendCommand.getMethod().startsWith("/")) {
miIoSendCommand.getCloudServer(), miIoSendCommand); decryptedResponse = cloudConnector.sendRPCCommand(Utils.getHexId(deviceId),
logger.debug("Command {} send via cloudserver {}", miIoSendCommand.getCommandString(), miIoSendCommand.getCloudServer(), miIoSendCommand);
miIoSendCommand.getCloudServer()); logger.debug("Command {} send via cloudserver {}", miIoSendCommand.getCommandString(),
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE); miIoSendCommand.getCloudServer());
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
} else {
String data = miIoSendCommand.getParams().isJsonArray()
&& miIoSendCommand.getParams().getAsJsonArray().size() > 0
? miIoSendCommand.getParams().getAsJsonArray().get(0).toString()
: "";
logger.debug("Custom cloud request send to url '{}' with data '{}'", miIoSendCommand.getMethod(),
data);
decryptedResponse = cloudConnector.sendCloudCommand(miIoSendCommand.getMethod(),
miIoSendCommand.getCloudServer(), data);
miIoSendCommand.setResponse(JsonParser.parseString(decryptedResponse).getAsJsonObject());
return miIoSendCommand;
}
} }
// 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(",,", ",");