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;
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
cloudConnector.setCredentials(username, password, country);
scheduler.submit(() -> cloudConnector.isConnected());
this.channelTypeRegistry = channelTypeRegistry;
+ this.basicChannelTypeProvider = basicChannelTypeProvider;
}
@Override
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);
--- /dev/null
+/**
+ * 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());
+ }
+ }
+}
import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
@SerializedName("unit")
@Expose
private @Nullable String unit;
+ @SerializedName("stateDescription")
+ @Expose
+ private @Nullable StateDescriptionDTO stateDescription;
@SerializedName("refresh")
@Expose
private @Nullable Boolean refresh;
@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;
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();
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 : "";
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
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;
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;
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
updateData();
}, 3000, TimeUnit.MILLISECONDS);
} else {
- logger.debug("Actions not loaded yet");
+ logger.debug("Actions not loaded yet, or none available");
}
}
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++;
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;
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;