[miio] dynamically generate channelTypes (#9158)
* [miio] dynamically generate channelTypes Simplify the json database creation for new models and less chance for errors Related to #7276 Signed-off-by: Marcel Verpaalen <marcel@verpaalen.com> Co-authored-by: Connor Petty <mistercpp2000+gitsignoff@gmail.com>
This commit is contained in:
parent
97d9bda0a1
commit
2597ff20d4
@ -19,6 +19,7 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.miio.internal.basic.BasicChannelTypeProvider;
|
||||
import org.openhab.binding.miio.internal.basic.MiIoDatabaseWatchService;
|
||||
import org.openhab.binding.miio.internal.cloud.CloudConnector;
|
||||
import org.openhab.binding.miio.internal.handler.MiIoBasicHandler;
|
||||
@ -52,11 +53,12 @@ public class MiIoHandlerFactory extends BaseThingHandlerFactory {
|
||||
private MiIoDatabaseWatchService miIoDatabaseWatchService;
|
||||
private CloudConnector cloudConnector;
|
||||
private ChannelTypeRegistry channelTypeRegistry;
|
||||
private BasicChannelTypeProvider basicChannelTypeProvider;
|
||||
|
||||
@Activate
|
||||
public MiIoHandlerFactory(@Reference ChannelTypeRegistry channelTypeRegistry,
|
||||
@Reference MiIoDatabaseWatchService miIoDatabaseWatchService, @Reference CloudConnector cloudConnector,
|
||||
Map<String, Object> properties) {
|
||||
@Reference BasicChannelTypeProvider basicChannelTypeProvider, Map<String, Object> properties) {
|
||||
this.miIoDatabaseWatchService = miIoDatabaseWatchService;
|
||||
this.cloudConnector = cloudConnector;
|
||||
@Nullable
|
||||
@ -68,6 +70,7 @@ public class MiIoHandlerFactory extends BaseThingHandlerFactory {
|
||||
cloudConnector.setCredentials(username, password, country);
|
||||
scheduler.submit(() -> cloudConnector.isConnected());
|
||||
this.channelTypeRegistry = channelTypeRegistry;
|
||||
this.basicChannelTypeProvider = basicChannelTypeProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -82,7 +85,8 @@ public class MiIoHandlerFactory extends BaseThingHandlerFactory {
|
||||
return new MiIoGenericHandler(thing, miIoDatabaseWatchService, cloudConnector);
|
||||
}
|
||||
if (thingTypeUID.equals(THING_TYPE_BASIC)) {
|
||||
return new MiIoBasicHandler(thing, miIoDatabaseWatchService, cloudConnector, channelTypeRegistry);
|
||||
return new MiIoBasicHandler(thing, miIoDatabaseWatchService, cloudConnector, channelTypeRegistry,
|
||||
basicChannelTypeProvider);
|
||||
}
|
||||
if (thingTypeUID.equals(THING_TYPE_VACUUM)) {
|
||||
return new MiIoVacuumHandler(thing, miIoDatabaseWatchService, cloudConnector, channelTypeRegistry);
|
||||
|
||||
@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.basic;
|
||||
|
||||
import static org.openhab.binding.miio.internal.MiIoBindingConstants.BINDING_ID;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.thing.type.ChannelTypeBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeProvider;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.thing.type.StateChannelTypeBuilder;
|
||||
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Provide channelTypes for Mi IO Basic devices
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@Component(service = { ChannelTypeProvider.class, BasicChannelTypeProvider.class })
|
||||
@NonNullByDefault
|
||||
public class BasicChannelTypeProvider implements ChannelTypeProvider {
|
||||
private final Map<String, ChannelType> channelTypes = new ConcurrentHashMap<>();
|
||||
private final Logger logger = LoggerFactory.getLogger(BasicChannelTypeProvider.class);
|
||||
|
||||
@Override
|
||||
public Collection<ChannelType> getChannelTypes(@Nullable Locale locale) {
|
||||
return channelTypes.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) {
|
||||
if (channelTypes.containsKey(channelTypeUID.getAsString())) {
|
||||
return channelTypes.get(channelTypeUID.getAsString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addChannelType(MiIoBasicChannel miChannel, String model) {
|
||||
ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID,
|
||||
model.toUpperCase().replace(".", "_") + "_" + miChannel.getChannel());
|
||||
logger.debug("Adding channel definitions for {} -> {}", channelTypeUID, miChannel.getFriendlyName());
|
||||
try {
|
||||
final StateDescriptionDTO stateDescription = miChannel.getStateDescription();
|
||||
StateChannelTypeBuilder channelTypeBuilder = ChannelTypeBuilder.state(channelTypeUID,
|
||||
miChannel.getFriendlyName(), miChannel.getType()); //
|
||||
if (stateDescription != null) {
|
||||
StateDescriptionFragmentBuilder sdf = StateDescriptionFragmentBuilder.create();
|
||||
final BigDecimal maximum = stateDescription.getMaximum();
|
||||
if (maximum != null) {
|
||||
sdf.withMaximum(maximum);
|
||||
}
|
||||
final BigDecimal minimum = stateDescription.getMinimum();
|
||||
if (minimum != null) {
|
||||
sdf.withMinimum(minimum);
|
||||
}
|
||||
final BigDecimal step = stateDescription.getStep();
|
||||
if (step != null) {
|
||||
sdf.withStep(step);
|
||||
}
|
||||
final String pattern = stateDescription.getPattern();
|
||||
if (pattern != null) {
|
||||
sdf.withPattern(pattern);
|
||||
}
|
||||
final Boolean readOnly = stateDescription.getReadOnly();
|
||||
if (readOnly != null) {
|
||||
sdf.withReadOnly(readOnly);
|
||||
}
|
||||
List<OptionsValueListDTO> optionList = stateDescription.getOptions();
|
||||
if (optionList != null) {
|
||||
List<StateOption> options = new ArrayList<>();
|
||||
for (OptionsValueListDTO option : optionList) {
|
||||
String value = option.getValue();
|
||||
if (value != null) {
|
||||
options.add(new StateOption(value, option.getLabel()));
|
||||
}
|
||||
}
|
||||
sdf.withOptions(options);
|
||||
}
|
||||
channelTypeBuilder.withStateDescriptionFragment(sdf.build());
|
||||
logger.debug("added stateDescription: {}", sdf);
|
||||
}
|
||||
final String category = miChannel.getCategory();
|
||||
if (category != null) {
|
||||
channelTypeBuilder.withCategory(category);
|
||||
}
|
||||
final LinkedHashSet<String> tags = miChannel.getTags();
|
||||
if (tags != null && tags.size() > 0) {
|
||||
channelTypeBuilder.withTags(tags);
|
||||
}
|
||||
channelTypes.put(channelTypeUID.getAsString(), channelTypeBuilder.build());
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed creating channelType {}: {} ", channelTypeUID, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,7 @@ import static org.openhab.binding.miio.internal.MiIoBindingConstants.BINDING_ID;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
@ -56,6 +57,9 @@ public class MiIoBasicChannel {
|
||||
@SerializedName("unit")
|
||||
@Expose
|
||||
private @Nullable String unit;
|
||||
@SerializedName("stateDescription")
|
||||
@Expose
|
||||
private @Nullable StateDescriptionDTO stateDescription;
|
||||
@SerializedName("refresh")
|
||||
@Expose
|
||||
private @Nullable Boolean refresh;
|
||||
@ -71,6 +75,12 @@ public class MiIoBasicChannel {
|
||||
@SerializedName("actions")
|
||||
@Expose
|
||||
private @Nullable List<MiIoDeviceAction> miIoDeviceActions = new ArrayList<>();
|
||||
@SerializedName("category")
|
||||
@Expose
|
||||
private @Nullable String category;
|
||||
@SerializedName("tags")
|
||||
@Expose
|
||||
private @Nullable LinkedHashSet<String> tags;
|
||||
@SerializedName("readmeComment")
|
||||
@Expose
|
||||
private @Nullable String readmeComment;
|
||||
@ -167,6 +177,14 @@ public class MiIoBasicChannel {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public @Nullable StateDescriptionDTO getStateDescription() {
|
||||
return stateDescription;
|
||||
}
|
||||
|
||||
public void setStateDescription(@Nullable StateDescriptionDTO stateDescription) {
|
||||
this.stateDescription = stateDescription;
|
||||
}
|
||||
|
||||
public Boolean getRefresh() {
|
||||
final @Nullable Boolean rf = refresh;
|
||||
return rf != null && rf.booleanValue() && !getProperty().isEmpty();
|
||||
@ -211,6 +229,22 @@ public class MiIoBasicChannel {
|
||||
this.transfortmation = transfortmation;
|
||||
}
|
||||
|
||||
public @Nullable String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(String category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public @Nullable LinkedHashSet<String> getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
public void setTags(LinkedHashSet<String> tags) {
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
public String getReadmeComment() {
|
||||
final String readmeComment = this.readmeComment;
|
||||
return (readmeComment != null) ? readmeComment : "";
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.basic;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Mapping properties from json for channel options
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OptionsValueListDTO {
|
||||
|
||||
@SerializedName("value")
|
||||
@Expose
|
||||
public @Nullable String value;
|
||||
|
||||
@SerializedName("label")
|
||||
@Expose
|
||||
public @Nullable String label;
|
||||
|
||||
public @Nullable String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public @Nullable String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.basic;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Mapping properties from json for state descriptions
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class StateDescriptionDTO {
|
||||
|
||||
@SerializedName("minimum")
|
||||
@Expose
|
||||
@Nullable
|
||||
private BigDecimal minimum;
|
||||
@SerializedName("maximum")
|
||||
@Expose
|
||||
@Nullable
|
||||
private BigDecimal maximum;
|
||||
@SerializedName("step")
|
||||
@Expose
|
||||
@Nullable
|
||||
private BigDecimal step;
|
||||
@SerializedName("pattern")
|
||||
@Expose
|
||||
@Nullable
|
||||
private String pattern;
|
||||
@SerializedName("readOnly")
|
||||
@Expose
|
||||
@Nullable
|
||||
private Boolean readOnly;
|
||||
@SerializedName("options")
|
||||
@Expose
|
||||
@Nullable
|
||||
public List<OptionsValueListDTO> options = null;
|
||||
|
||||
@Nullable
|
||||
public BigDecimal getMinimum() {
|
||||
return minimum;
|
||||
}
|
||||
|
||||
public void setMinimum(BigDecimal minimum) {
|
||||
this.minimum = minimum;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BigDecimal getMaximum() {
|
||||
return maximum;
|
||||
}
|
||||
|
||||
public void setMaximum(BigDecimal maximum) {
|
||||
this.maximum = maximum;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BigDecimal getStep() {
|
||||
return step;
|
||||
}
|
||||
|
||||
public void setStep(BigDecimal step) {
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
public void setPattern(String pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Boolean getReadOnly() {
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
public void setReadOnly(Boolean readOnly) {
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<OptionsValueListDTO> getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public void setOptions(List<OptionsValueListDTO> options) {
|
||||
this.options = options;
|
||||
}
|
||||
}
|
||||
@ -19,6 +19,7 @@ import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -34,6 +35,7 @@ import org.openhab.binding.miio.internal.MiIoQuantiyTypes;
|
||||
import org.openhab.binding.miio.internal.MiIoSendCommand;
|
||||
import org.openhab.binding.miio.internal.Utils;
|
||||
import org.openhab.binding.miio.internal.basic.ActionConditions;
|
||||
import org.openhab.binding.miio.internal.basic.BasicChannelTypeProvider;
|
||||
import org.openhab.binding.miio.internal.basic.CommandParameterType;
|
||||
import org.openhab.binding.miio.internal.basic.Conversions;
|
||||
import org.openhab.binding.miio.internal.basic.MiIoBasicChannel;
|
||||
@ -95,11 +97,14 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
|
||||
private @Nullable MiIoBasicDevice miioDevice;
|
||||
private Map<ChannelUID, MiIoBasicChannel> actions = new HashMap<>();
|
||||
private ChannelTypeRegistry channelTypeRegistry;
|
||||
private BasicChannelTypeProvider basicChannelTypeProvider;
|
||||
|
||||
public MiIoBasicHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService,
|
||||
CloudConnector cloudConnector, ChannelTypeRegistry channelTypeRegistry) {
|
||||
CloudConnector cloudConnector, ChannelTypeRegistry channelTypeRegistry,
|
||||
BasicChannelTypeProvider basicChannelTypeProvider) {
|
||||
super(thing, miIoDatabaseWatchService, cloudConnector);
|
||||
this.channelTypeRegistry = channelTypeRegistry;
|
||||
this.basicChannelTypeProvider = basicChannelTypeProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -259,7 +264,7 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
|
||||
updateData();
|
||||
}, 3000, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
logger.debug("Actions not loaded yet");
|
||||
logger.debug("Actions not loaded yet, or none available");
|
||||
}
|
||||
}
|
||||
|
||||
@ -409,8 +414,8 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
|
||||
for (MiIoBasicChannel miChannel : device.getDevice().getChannels()) {
|
||||
logger.debug("properties {}", miChannel);
|
||||
if (!miChannel.getType().isEmpty()) {
|
||||
ChannelUID channelUID = addChannel(thingBuilder, miChannel.getChannel(),
|
||||
miChannel.getChannelType(), miChannel.getType(), miChannel.getFriendlyName());
|
||||
basicChannelTypeProvider.addChannelType(miChannel, deviceName);
|
||||
ChannelUID channelUID = addChannel(thingBuilder, miChannel, deviceName);
|
||||
if (channelUID != null) {
|
||||
actions.put(channelUID, miChannel);
|
||||
channelsAdded++;
|
||||
@ -440,9 +445,10 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
private @Nullable ChannelUID addChannel(ThingBuilder thingBuilder, @Nullable String channel, String channelType,
|
||||
@Nullable String datatype, String friendlyName) {
|
||||
if (channel == null || channel.isEmpty() || datatype == null || datatype.isEmpty()) {
|
||||
private @Nullable ChannelUID addChannel(ThingBuilder thingBuilder, MiIoBasicChannel miChannel, String model) {
|
||||
String channel = miChannel.getChannel();
|
||||
String dataType = miChannel.getType();
|
||||
if (channel.isEmpty() || dataType.isEmpty()) {
|
||||
logger.info("Channel '{}', UID '{}' cannot be added incorrectly configured database. ", channel,
|
||||
getThing().getUID());
|
||||
return null;
|
||||
@ -455,22 +461,30 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
|
||||
logger.info("Channel '{}' for thing {} already exist... removing", channel, getThing().getUID());
|
||||
thingBuilder.withoutChannel(new ChannelUID(getThing().getUID(), channel));
|
||||
}
|
||||
ChannelBuilder newChannel = ChannelBuilder.create(channelUID, datatype).withLabel(friendlyName);
|
||||
boolean useGenericChannelType = false;
|
||||
if (!channelType.isBlank()) {
|
||||
ChannelTypeUID channelTypeUID = new ChannelTypeUID(channelType);
|
||||
ChannelBuilder newChannel = ChannelBuilder.create(channelUID, dataType).withLabel(miChannel.getFriendlyName());
|
||||
boolean useGeneratedChannelType = false;
|
||||
if (!miChannel.getChannelType().isBlank()) {
|
||||
ChannelTypeUID channelTypeUID = new ChannelTypeUID(miChannel.getChannelType());
|
||||
if (channelTypeRegistry.getChannelType(channelTypeUID) != null) {
|
||||
newChannel = newChannel.withType(channelTypeUID);
|
||||
final LinkedHashSet<String> tags = miChannel.getTags();
|
||||
if (tags != null && tags.size() > 0) {
|
||||
newChannel.withDefaultTags(tags);
|
||||
}
|
||||
} else {
|
||||
logger.debug("ChannelType '{}' is not available. Check the Json file for {}", channelTypeUID,
|
||||
getThing().getUID());
|
||||
useGenericChannelType = true;
|
||||
logger.debug("ChannelType '{}' is not available. Check the Json file for {}", channelTypeUID, model);
|
||||
useGeneratedChannelType = true;
|
||||
}
|
||||
} else {
|
||||
useGenericChannelType = true;
|
||||
useGeneratedChannelType = true;
|
||||
}
|
||||
if (useGenericChannelType) {
|
||||
newChannel = newChannel.withType(new ChannelTypeUID(BINDING_ID, datatype.toLowerCase()));
|
||||
if (useGeneratedChannelType) {
|
||||
newChannel = newChannel
|
||||
.withType(new ChannelTypeUID(BINDING_ID, model.toUpperCase().replace(".", "_") + "_" + channel));
|
||||
final LinkedHashSet<String> tags = miChannel.getTags();
|
||||
if (tags != null && tags.size() > 0) {
|
||||
newChannel.withDefaultTags(tags);
|
||||
}
|
||||
}
|
||||
thingBuilder.withChannel(newChannel.build());
|
||||
return channelUID;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user