Skip to content

Commit

Permalink
[miio] Automatic create experimental support for (unsupported) miot d…
Browse files Browse the repository at this point in the history
…evices (#11149)

Signed-off-by: Marcel Verpaalen <[email protected]>
  • Loading branch information
marcelrv authored Sep 19, 2021
1 parent b0cbefd commit 2fb86d7
Show file tree
Hide file tree
Showing 20 changed files with 1,283 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public final class MiIoBindingConstants {
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";
public static final String CHANNEL_TESTMIOT = "actions#testmiot";
public static final String CHANNEL_POWER = "actions#power";

public static final String CHANNEL_SSID = "network#ssid";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.openhab.binding.miio.internal.handler.MiIoUnsupportedHandler;
import org.openhab.binding.miio.internal.handler.MiIoVacuumHandler;
import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
Expand All @@ -54,6 +55,7 @@ public class MiIoHandlerFactory extends BaseThingHandlerFactory {
private static final String THING_HANDLER_THREADPOOL_NAME = "thingHandler";
protected final ScheduledExecutorService scheduler = ThreadPoolManager
.getScheduledPool(THING_HANDLER_THREADPOOL_NAME);
private final HttpClientFactory httpClientFactory;
private MiIoDatabaseWatchService miIoDatabaseWatchService;
private CloudConnector cloudConnector;
private ChannelTypeRegistry channelTypeRegistry;
Expand All @@ -62,9 +64,11 @@ public class MiIoHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(MiIoHandlerFactory.class);

@Activate
public MiIoHandlerFactory(@Reference ChannelTypeRegistry channelTypeRegistry,
public MiIoHandlerFactory(@Reference HttpClientFactory httpClientFactory,
@Reference ChannelTypeRegistry channelTypeRegistry,
@Reference MiIoDatabaseWatchService miIoDatabaseWatchService, @Reference CloudConnector cloudConnector,
@Reference BasicChannelTypeProvider basicChannelTypeProvider, Map<String, Object> properties) {
this.httpClientFactory = httpClientFactory;
this.miIoDatabaseWatchService = miIoDatabaseWatchService;
this.channelTypeRegistry = channelTypeRegistry;
this.basicChannelTypeProvider = basicChannelTypeProvider;
Expand Down Expand Up @@ -113,6 +117,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
if (thingTypeUID.equals(THING_TYPE_VACUUM)) {
return new MiIoVacuumHandler(thing, miIoDatabaseWatchService, cloudConnector, channelTypeRegistry);
}
return new MiIoUnsupportedHandler(thing, miIoDatabaseWatchService, cloudConnector);
return new MiIoUnsupportedHandler(thing, miIoDatabaseWatchService, cloudConnector,
httpClientFactory.getCommonHttpClient());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public enum MiIoQuantiyTypes {
MILLI_AMPERE(MILLI(Units.AMPERE), "mA"),
VOLT(Units.VOLT),
MILLI_VOLT(MILLI(Units.VOLT), "mV"),
WATT(Units.WATT),
WATT(Units.WATT, "W", "w"),
LITRE(Units.LITRE, "liter"),
LUX(Units.LUX),
RADIANS(Units.RADIAN, "radians"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public BigDecimal getMinimum() {
return minimum;
}

public void setMinimum(BigDecimal minimum) {
public void setMinimum(@Nullable BigDecimal minimum) {
this.minimum = minimum;
}

Expand All @@ -68,7 +68,7 @@ public BigDecimal getMaximum() {
return maximum;
}

public void setMaximum(BigDecimal maximum) {
public void setMaximum(@Nullable BigDecimal maximum) {
this.maximum = maximum;
}

Expand All @@ -77,7 +77,7 @@ public BigDecimal getStep() {
return step;
}

public void setStep(BigDecimal step) {
public void setStep(@Nullable BigDecimal step) {
this.step = step;
}

Expand All @@ -86,7 +86,7 @@ public String getPattern() {
return pattern;
}

public void setPattern(String pattern) {
public void setPattern(@Nullable String pattern) {
this.pattern = pattern;
}

Expand All @@ -95,7 +95,7 @@ public Boolean getReadOnly() {
return readOnly;
}

public void setReadOnly(Boolean readOnly) {
public void setReadOnly(@Nullable Boolean readOnly) {
this.readOnly = readOnly;
}

Expand All @@ -104,7 +104,7 @@ public List<OptionsValueListDTO> getOptions() {
return options;
}

public void setOptions(List<OptionsValueListDTO> options) {
public void setOptions(@Nullable List<OptionsValueListDTO> options) {
this.options = options;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.miio.internal.MiIoBindingConfiguration;
import org.openhab.binding.miio.internal.MiIoCommand;
import org.openhab.binding.miio.internal.MiIoDevices;
Expand All @@ -41,6 +42,7 @@
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.binding.miio.internal.miot.MiotParser;
import org.openhab.core.cache.ExpiringCache;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
Expand All @@ -67,6 +69,7 @@ public class MiIoUnsupportedHandler extends MiIoAbstractHandler {

private static final DateTimeFormatter DATEFORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss");
private static final Gson GSONP = new GsonBuilder().setPrettyPrinting().create();
private final HttpClient httpClient;

private final Logger logger = LoggerFactory.getLogger(MiIoUnsupportedHandler.class);
private final MiIoBindingConfiguration conf = getConfigAs(MiIoBindingConfiguration.class);
Expand All @@ -84,8 +87,9 @@ public class MiIoUnsupportedHandler extends MiIoAbstractHandler {
});

public MiIoUnsupportedHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService,
CloudConnector cloudConnector) {
CloudConnector cloudConnector, HttpClient httpClientFactory) {
super(thing, miIoDatabaseWatchService, cloudConnector);
this.httpClient = httpClientFactory;
}

@Override
Expand All @@ -112,6 +116,9 @@ public void handleCommand(ChannelUID channelUID, Command command) {
if (channelUID.getId().equals(CHANNEL_TESTCOMMANDS)) {
executeExperimentalCommands();
}
if (channelUID.getId().equals(CHANNEL_TESTMIOT)) {
executeCreateMiotTestFile();
}
}

@Override
Expand Down Expand Up @@ -189,6 +196,59 @@ private void executeExperimentalCommands() {
logger.info("{}", sb.toString());
}

private void executeCreateMiotTestFile() {
sb = new StringBuilder();
try {
MiotParser miotParser;
miotParser = MiotParser.parse(model, httpClient);
logger.info("urn: {}", miotParser.getUrn());
logger.info("{}", miotParser.getUrnData());
MiIoBasicDevice device = miotParser.getDevice();
if (device == null) {
logger.debug("Error while creating experimental miot db file for {}.", conf.model);
return;
}
sendCommand(MiIoCommand.MIIO_INFO);
logger.info("Start experimental creation of database file based on MIOT spec for device '{}'. ",
miDevice.toString());
sb.append("Info for ");
sb.append(conf.model);
sb.append("\r\n");
sb.append("Database file created:");
sb.append(writeDevice(device, true));
sb.append("\r\n");
sb.append(MiotParser.toJson(device));
sb.append("\r\n");
sb.append("Testing Properties:\r\n");
int lastCommand = -1;
for (MiIoBasicChannel ch : device.getDevice().getChannels()) {
if (ch.isMiOt() && ch.getRefresh()) {
JsonObject json = new JsonObject();
json.addProperty("did", ch.getProperty());
json.addProperty("siid", ch.getSiid());
json.addProperty("piid", ch.getPiid());
String cmd = ch.getChannelCustomRefreshCommand().isBlank()
? ("get_properties[" + json.toString() + "]")
: ch.getChannelCustomRefreshCommand();
sb.append(ch.getChannel());
sb.append(" -> ");
sb.append(cmd);
sb.append(" -> ");
lastCommand = sendCommand(cmd);
sb.append(lastCommand);
sb.append(", \r\n");
testChannelList.put(lastCommand, ch);
}
}
this.lastCommand = lastCommand;
sb.append("\r\n");
logger.info("{}", sb.toString());
} catch (Exception e) {
logger.debug("Error while creating experimental miot db file for {}", conf.model);
logger.info("{}", sb.toString());
}
}

private LinkedHashMap<String, MiIoBasicChannel> collectProperties(@Nullable String model) {
LinkedHashMap<String, MiIoBasicChannel> testChannelsList = new LinkedHashMap<>();
LinkedHashSet<MiIoDevices> testDeviceList = new LinkedHashSet<>();
Expand Down Expand Up @@ -255,7 +315,14 @@ private void finishChannelTest() {
sb.append("===================================\r\n");
sb.append("Device Info: ");
sb.append(info);
sb.append("\r\n");
sb.append(supportedChannelList.size());
sb.append(" channels with responses.\r\n");
int miotChannels = 0;
for (MiIoBasicChannel ch : supportedChannelList.keySet()) {
if (ch.isMiOt()) {
miotChannels++;
}
sb.append("Property: ");
sb.append(Utils.minLengthString(ch.getProperty(), 15));
sb.append(" Friendly Name: ");
Expand All @@ -264,15 +331,17 @@ private void finishChannelTest() {
sb.append(supportedChannelList.get(ch));
sb.append("\r\n");
}
if (!supportedChannelList.isEmpty()) {
boolean isMiot = miotChannels > supportedChannelList.size() / 2;
if (!supportedChannelList.isEmpty() && !isMiot) {
MiIoBasicDevice mbd = createBasicDeviceDb(model, new ArrayList<>(supportedChannelList.keySet()));
sb.append("Created experimental database for your device:\r\n");
sb.append(GSONP.toJson(mbd));
sb.append("\r\nDatabase file saved to: ");
sb.append(writeDevice(mbd));
sb.append(writeDevice(mbd, false));
isIdentified = false;
} else {
sb.append("No supported channels found.\r\n");
sb.append(isMiot ? "Miot file already created. Manually remove non-functional channels.\r\n"
: "No supported channels found.\r\n");
}
sb.append("\r\nDevice testing file saved to: ");
sb.append(writeLog());
Expand Down Expand Up @@ -303,14 +372,14 @@ private MiIoBasicDevice createBasicDeviceDb(String model, List<MiIoBasicChannel>
return device;
}

private String writeDevice(MiIoBasicDevice device) {
private String writeDevice(MiIoBasicDevice device, boolean miot) {
File folder = new File(BINDING_DATABASE_PATH);
if (!folder.exists()) {
folder.mkdirs();
}
File dataFile = new File(folder, model + "-experimental.json");
File dataFile = new File(folder, model + (miot ? "-miot" : "") + "-experimental.json");
try (FileWriter writer = new FileWriter(dataFile)) {
writer.write(GSONP.toJson(device));
writer.write(miot ? MiotParser.toJson(device) : GSONP.toJson(device));
logger.debug("Experimental database file created: {}", dataFile.getAbsolutePath());
return dataFile.getAbsolutePath().toString();
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.miio.internal.miot;

import java.util.List;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

/**
* Mapping properties from json for miot device info
*
* @author Marcel Verpaalen - Initial contribution
*/
public class ActionDTO {

@SerializedName("iid")
@Expose
public Integer iid;
@SerializedName("type")
@Expose
public String type;
@SerializedName("description")
@Expose
public String description;
@SerializedName("in")
@Expose
public List<Object> in = null;
@SerializedName("out")
@Expose
public List<Object> out = null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.miio.internal.miot;

import java.util.List;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

/**
* Mapping properties from json for miot device info
*
* @author Marcel Verpaalen - Initial contribution
*/
public class EventDTO {

@SerializedName("iid")
@Expose
public Integer iid;
@SerializedName("type")
@Expose
public String type;
@SerializedName("description")
@Expose
public String description;
@SerializedName("arguments")
@Expose
public List<Integer> arguments = null;
}
Loading

0 comments on commit 2fb86d7

Please sign in to comment.