diff --git a/kura/org.eclipse.kura.api/META-INF/MANIFEST.MF b/kura/org.eclipse.kura.api/META-INF/MANIFEST.MF index fe4ef5dd9fe..05084dfce46 100644 --- a/kura/org.eclipse.kura.api/META-INF/MANIFEST.MF +++ b/kura/org.eclipse.kura.api/META-INF/MANIFEST.MF @@ -15,7 +15,7 @@ Export-Package: org.eclipse.kura;version="1.7.0", org.eclipse.kura.bluetooth.le.beacon.listener;version="1.0.0", org.eclipse.kura.certificate;version="2.1.0", org.eclipse.kura.certificate.enrollment;version="1.0.0", - org.eclipse.kura.channel;version="1.2.0", + org.eclipse.kura.channel;version="1.3.0", org.eclipse.kura.channel.listener;version="1.0.0", org.eclipse.kura.clock;version="1.0.1", org.eclipse.kura.cloud;version="1.1.0", @@ -77,7 +77,7 @@ Export-Package: org.eclipse.kura;version="1.7.0", org.eclipse.kura.ssl;version="2.1.0", org.eclipse.kura.status;version="1.0.2", org.eclipse.kura.system;version="1.7.0", - org.eclipse.kura.type;version="1.1.0", + org.eclipse.kura.type;version="1.2.0", org.eclipse.kura.usb;version="1.3.0", org.eclipse.kura.watchdog;version="1.0.2", org.eclipse.kura.wire;version="2.0.0", diff --git a/kura/org.eclipse.kura.api/src/main/java/org/eclipse/kura/channel/Channel.java b/kura/org.eclipse.kura.api/src/main/java/org/eclipse/kura/channel/Channel.java index 142247ce536..df874c10246 100644 --- a/kura/org.eclipse.kura.api/src/main/java/org/eclipse/kura/channel/Channel.java +++ b/kura/org.eclipse.kura.api/src/main/java/org/eclipse/kura/channel/Channel.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2021 Eurotech and/or its affiliates and others + * Copyright (c) 2017, 2024 Eurotech and/or its affiliates and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -21,6 +21,7 @@ import org.eclipse.kura.annotation.NotThreadSafe; import org.eclipse.kura.type.DataType; import org.eclipse.kura.type.TypedValue; +import org.eclipse.kura.type.TypedValues; import org.osgi.annotation.versioning.ProviderType; /** @@ -49,9 +50,15 @@ public class Channel { */ private DataType valueType; - private double valueScale; + /* + * The value used to scale the value + */ + private TypedValue valueScale; - private double valueOffset; + /** + * The value used as offset of the value + */ + private TypedValue valueOffset; private String unit; @@ -85,8 +92,59 @@ public Channel(final String name, final ChannelType type, final DataType valueTy this.name = name; this.type = type; this.valueType = valueType; - this.valueScale = 1.0d; - this.valueOffset = 0d; + + this.valueScale = TypedValues.newDoubleValue(1.0d); + this.valueOffset = TypedValues.newDoubleValue(0.0d); + + this.unit = ""; + } + + /** + * Instantiates a new channel. + * + * @param name + * the name for this channel + * @param type + * the type + * @param valueType + * the value type + * @param valueScale + * the value used to scale the value, must have the same {@link DataType} as valueOffset + * @param valueOffset + * the value used as offset of the value, must have the same {@link DataType} as valueScale + * @param config + * the configuration + * @throws NullPointerException + * if any of the arguments is null + * @throws IllegalArgumentException + * if any of the valueScale and valueOffset have different types + * + * @since 3.0 + */ + public Channel(final String name, final ChannelType type, final DataType valueType, + final TypedValue valueScale, final TypedValue valueOffset, + final Map config) { + requireNonNull(name, "Channel name cannot be null"); + requireNonNull(type, "Channel type cannot be null"); + requireNonNull(valueType, "Channel value type cannot be null"); + + requireNonNull(valueScale, "Channel value scale cannot be null"); + requireNonNull(valueOffset, "Channel value offset cannot be null"); + + requireNonNull(config, "Channel configuration cannot be null"); + + this.configuration = Collections.unmodifiableMap(config); + this.name = name; + this.type = type; + this.valueType = valueType; + + if (valueScale.getType() != valueOffset.getType()) { + throw new IllegalArgumentException("Channel value scale and offset must have the same type"); + } + + this.valueScale = valueScale; + this.valueOffset = valueOffset; + this.unit = ""; } @@ -142,9 +200,30 @@ public boolean isEnabled() { * * @return a double that represents the scale factor to be applied to the read value * + * @throws IllegalArgumentException + * * @since 2.3 + * + * @deprecated Use {@link #getValueScaleAsTypedValue()} */ + @Deprecated public double getValueScale() { + if (this.valueScale.getType() != DataType.DOUBLE) { + throw new IllegalStateException( + "the type of the scale is " + this.valueScale.getType().toString() + ".Expected a double"); + } + return (Double) this.valueScale.getValue(); + } + + /** + * Returns a {@link TypedValue} that represents the scale factor to be applied to the read + * value + * + * @return a {@link TypedValue} that represents the scale factor to be applied to the read value + * + * @since 3.0 + */ + public TypedValue getValueScaleAsTypedValue() { return this.valueScale; } @@ -152,10 +231,31 @@ public double getValueScale() { * Returns a double that represents the offset to be applied to the read value * * @return a double that represents the offset to be applied to the read value + * + * @throws IllegalArgumentException * * @since 2.3 + * + * @deprecated Use {@link #getValueOffsetAsTypedValue()} */ + @Deprecated public double getValueOffset() { + if (this.valueOffset.getType() != DataType.DOUBLE) { + throw new IllegalStateException( + "the type of the offset is " + this.valueOffset.getType().toString() + ".Expected a double"); + } + return (Double) this.valueOffset.getValue(); + } + + /** + * Returns a {@link TypedValue} that represents the offset factor to be applied to the read + * value + * + * @return a {@link TypedValue} that represents the offset factor to be applied to the read value + * + * @since 3.0 + */ + public TypedValue getValueOffsetAsTypedValue() { return this.valueOffset; } @@ -224,6 +324,17 @@ public void setEnabled(boolean isEnabled) { * @since 2.3 */ public void setScale(double scale) { + this.valueScale = TypedValues.newDoubleValue(scale); + } + + /** + * Specifies the scale to be applied to the channel value + * + * @param scale + * a {@link TypedValue} value that specifies the scale to be applied to the channel value + * @since 3.0 + */ + public void setScale(TypedValue scale) { this.valueScale = scale; } @@ -235,6 +346,17 @@ public void setScale(double scale) { * @since 2.3 */ public void setOffset(double offset) { + this.valueOffset = TypedValues.newDoubleValue(offset); + } + + /** + * Specifies the offset to be applied to the channel value + * + * @param offset + * a {@link TypedValue} value that specifies the offset to be applied to the channel value + * @since 3.0 + */ + public void setOffset(TypedValue offset) { this.valueOffset = offset; } @@ -301,9 +423,9 @@ public int hashCode() { result = prime * result + (this.type == null ? 0 : this.type.hashCode()); result = prime * result + (this.unit == null ? 0 : this.unit.hashCode()); long temp; - temp = Double.doubleToLongBits(this.valueOffset); + temp = Double.doubleToLongBits((Double) this.valueOffset.getValue()); result = prime * result + (int) (temp ^ temp >>> 32); - temp = Double.doubleToLongBits(this.valueScale); + temp = Double.doubleToLongBits((Double) this.valueScale.getValue()); result = prime * result + (int) (temp ^ temp >>> 32); result = prime * result + (this.valueType == null ? 0 : this.valueType.hashCode()); return result; @@ -338,10 +460,10 @@ public boolean equals(Object obj) { } else if (!this.unit.equals(other.unit)) { return false; } - if (Double.doubleToLongBits(this.valueOffset) != Double.doubleToLongBits(other.valueOffset)) { + if (!this.valueOffset.equals(other.valueOffset)) { return false; } - if (Double.doubleToLongBits(this.valueScale) != Double.doubleToLongBits(other.valueScale)) { + if (!this.valueScale.equals(other.valueScale)) { return false; } if (this.valueType != other.valueType) { diff --git a/kura/org.eclipse.kura.api/src/main/java/org/eclipse/kura/type/DataType.java b/kura/org.eclipse.kura.api/src/main/java/org/eclipse/kura/type/DataType.java index f1e36b09976..17d5fde76a3 100644 --- a/kura/org.eclipse.kura.api/src/main/java/org/eclipse/kura/type/DataType.java +++ b/kura/org.eclipse.kura.api/src/main/java/org/eclipse/kura/type/DataType.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2020 Eurotech and/or its affiliates and others + * Copyright (c) 2016, 2024 Eurotech and/or its affiliates and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -69,4 +69,32 @@ public static DataType getDataType(String stringDataType) { throw new IllegalArgumentException("Cannot convert to DataType"); } + + /** + * Converts {@code stringDataType}, if possible, to the related numeric {@link DataType}. + * + * @param stringDataType + * String that we want to use to get the respective numeric {@link DataType}. + * @return a numeric DataType that corresponds to the String passed as argument. + * @throws IllegalArgumentException + * if the passed string does not correspond to an existing numeric {@link DataType}. + * + * @since 3.0 + */ + public static DataType getNumericDataType(String stringDataType) { + if (INTEGER.name().equalsIgnoreCase(stringDataType)) { + return INTEGER; + } + if (FLOAT.name().equalsIgnoreCase(stringDataType)) { + return FLOAT; + } + if (DOUBLE.name().equalsIgnoreCase(stringDataType)) { + return DOUBLE; + } + if (LONG.name().equalsIgnoreCase(stringDataType)) { + return LONG; + } + + throw new IllegalArgumentException("Cannot convert to Numeric DataType"); + } } diff --git a/kura/org.eclipse.kura.api/src/main/java/org/eclipse/kura/type/TypedValues.java b/kura/org.eclipse.kura.api/src/main/java/org/eclipse/kura/type/TypedValues.java index c1f97e65a15..b8bc5bb39cb 100644 --- a/kura/org.eclipse.kura.api/src/main/java/org/eclipse/kura/type/TypedValues.java +++ b/kura/org.eclipse.kura.api/src/main/java/org/eclipse/kura/type/TypedValues.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2020 Eurotech and/or its affiliates and others + * Copyright (c) 2016, 2024 Eurotech and/or its affiliates and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -144,12 +144,42 @@ public static TypedValue newTypedValue(final Object value) { } /** - * Parses a TypedValue of given type from a String. - * + * Creates new numeric {@link TypedValue} of given type + * + * @param type + * the {@link DataType} of the returned {@link TypedValue} * @param value - * the String to be parsed into a {@link TypedValue} + * a {@link Number} that needs to be represented as {@link TypedValue} + * @return a {@link TypedValue} that represents the conversion of {@code value} + * @throws IllegalArgumentException + * if {@code value} cannot be represented as a numeric {@link TypedValue} + */ + public static TypedValue newNumericTypedValue(final DataType type, final Number value) { + Objects.requireNonNull(type, "type cannot be null"); + Objects.requireNonNull(value, "value cannot be null"); + + switch (type) { + case DOUBLE: + return newDoubleValue(value.doubleValue()); + case FLOAT: + return newFloatValue(value.floatValue()); + case INTEGER: + return newIntegerValue(value.intValue()); + case LONG: + return newLongValue(value.longValue()); + default: + throw new IllegalArgumentException(value + " cannot be converted into a TypedValue of type " + type); + } + + } + + /** + * Parses a {@link TypedValue} of given type from a String. + * * @param type * the {@link DataType} of the returned {@link TypedValue} + * @param value + * the String to be parsed into a {@link TypedValue} * @return a {@link TypedValue} that represents the conversion of {@code value} * @throws IllegalArgumentException * if {@code value} cannot be represented as {@link TypedValue} @@ -178,4 +208,38 @@ public static TypedValue parseTypedValue(final DataType type, final String va } throw new IllegalArgumentException(value + " cannot be converted into a TypedValue of type " + type); } + + /** + * Parses a Numeric {@link TypedValue} of given type from a String. + * + * @param type + * the {@link DataType} of the returned {@link TypedValue} + * @param value + * the String to be parsed into a {@link TypedValue} * + * @return a Numeric {@link TypedValue} that represents the conversion of {@code value} + * @throws IllegalArgumentException + * if {@code value} cannot be represented as a Numeric {@link TypedValue} + * + * @since 3.0 + */ + + public static TypedValue parseNumericTypedValue(DataType type, String value) { + Objects.requireNonNull(type, "type cannot be null"); + Objects.requireNonNull(value, "value cannot be null"); + + switch (type) { + case DOUBLE: + return newDoubleValue(Double.parseDouble(value)); + case FLOAT: + return newFloatValue(Float.parseFloat(value)); + case INTEGER: + return newIntegerValue(Integer.parseInt(value)); + case LONG: + return newLongValue(Long.parseLong(value)); + default: + throw new IllegalArgumentException(value + " cannot be converted into a TypedValue of type " + type); + } + + } + } diff --git a/kura/org.eclipse.kura.asset.provider/META-INF/MANIFEST.MF b/kura/org.eclipse.kura.asset.provider/META-INF/MANIFEST.MF index 83a6fc0e072..7b213a75efc 100644 --- a/kura/org.eclipse.kura.asset.provider/META-INF/MANIFEST.MF +++ b/kura/org.eclipse.kura.asset.provider/META-INF/MANIFEST.MF @@ -10,7 +10,7 @@ Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))" Import-Package: org.eclipse.kura;version="[1.2,2.0)", org.eclipse.kura.annotation;version="[1.0,2.0)", org.eclipse.kura.asset;version="[1.0,1.1)", - org.eclipse.kura.channel;version="[1.1,2.0)", + org.eclipse.kura.channel;version="[1.2,2.0)", org.eclipse.kura.channel.listener;version="[1.0,1.1)", org.eclipse.kura.configuration;version="[1.1,2.0)", org.eclipse.kura.configuration.metatype;version="[1.1,2.0)", @@ -26,6 +26,6 @@ Import-Package: org.eclipse.kura;version="[1.2,2.0)", org.osgi.service.component;version="1.2.0", org.osgi.util.tracker;version="1.5.0", org.slf4j;version="1.6.4" -Export-Package: org.eclipse.kura.asset.provider;version="2.0.0" +Export-Package: org.eclipse.kura.asset.provider;version="2.1.0" Service-Component: OSGI-INF/*.xml Bundle-ActivationPolicy: lazy diff --git a/kura/org.eclipse.kura.asset.provider/src/main/java/org/eclipse/kura/asset/provider/AssetConstants.java b/kura/org.eclipse.kura.asset.provider/src/main/java/org/eclipse/kura/asset/provider/AssetConstants.java index a095c80df0d..914d37a85e5 100644 --- a/kura/org.eclipse.kura.asset.provider/src/main/java/org/eclipse/kura/asset/provider/AssetConstants.java +++ b/kura/org.eclipse.kura.asset.provider/src/main/java/org/eclipse/kura/asset/provider/AssetConstants.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2021 Eurotech and/or its affiliates and others + * Copyright (c) 2016, 2024 Eurotech and/or its affiliates and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -45,6 +45,9 @@ public enum AssetConstants { /** Value type Property to be used in the configuration. */ VALUE_TYPE(CHANNEL_DEFAULT_PROPERTY_PREFIX.value() + "value.type"), + /** Scale Offset type Property to be used in the configuration. */ + SCALE_OFFSET_TYPE(CHANNEL_DEFAULT_PROPERTY_PREFIX.value() + "scaleoffset.type"), + /** Scale Property to be used in the configuration. */ VALUE_SCALE(CHANNEL_DEFAULT_PROPERTY_PREFIX.value() + "scale"), diff --git a/kura/org.eclipse.kura.asset.provider/src/main/java/org/eclipse/kura/asset/provider/BaseAsset.java b/kura/org.eclipse.kura.asset.provider/src/main/java/org/eclipse/kura/asset/provider/BaseAsset.java index 47cf964ce8d..979bc06ae96 100644 --- a/kura/org.eclipse.kura.asset.provider/src/main/java/org/eclipse/kura/asset/provider/BaseAsset.java +++ b/kura/org.eclipse.kura.asset.provider/src/main/java/org/eclipse/kura/asset/provider/BaseAsset.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2023 Eurotech and/or its affiliates and others + * Copyright (c) 2016, 2024 Eurotech and/or its affiliates and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -60,11 +60,8 @@ import org.eclipse.kura.driver.PreparedRead; import org.eclipse.kura.internal.asset.provider.BaseAssetConfiguration; import org.eclipse.kura.internal.asset.provider.DriverTrackerCustomizer; -import org.eclipse.kura.type.DataType; -import org.eclipse.kura.type.DoubleValue; -import org.eclipse.kura.type.FloatValue; -import org.eclipse.kura.type.IntegerValue; -import org.eclipse.kura.type.LongValue; +import org.eclipse.kura.type.TypedValue; +import org.eclipse.kura.type.TypedValues; import org.osgi.service.component.ComponentContext; import org.osgi.util.tracker.ServiceTracker; import org.slf4j.Logger; @@ -158,9 +155,9 @@ public class BaseAsset implements Asset, SelfConfiguringComponent { * OSGi service component callback while activation. * * @param componentContext - * the component context + * the component context * @param properties - * the service properties + * the service properties */ protected void activate(final ComponentContext componentContext, final Map properties) { logger.info("activating..."); @@ -174,7 +171,7 @@ protected void activate(final ComponentContext componentContext, final Map properties) { @@ -194,7 +191,7 @@ public void updated(final Map properties) { * OSGi service component callback while deactivation. * * @param context - * the component context + * the component context */ protected void deactivate(final ComponentContext context) { logger.debug("deactivating..."); @@ -213,9 +210,9 @@ protected void deactivate(final ComponentContext context) { * PID. * * @param driverId - * the identifier of the driver + * the identifier of the driver * @throws NullPointerException - * if driver id provided is null + * if driver id provided is null */ private void reopenDriverTracker(final String driverId) { requireNonNull(driverId, "Driver PID cannot be null"); @@ -382,16 +379,16 @@ public List read(final Set channelNames) throws KuraExcep validateChannel(channel, EnumSet.of(READ, READ_WRITE), "Channel type not within expected types (READ or READ_WRITE)"); } catch (Exception e) { - final ChannelRecord record = ChannelRecord.createStatusRecord(name, + final ChannelRecord channelRecord = ChannelRecord.createStatusRecord(name, new ChannelStatus(FAILURE, e.getMessage(), e)); - record.setTimestamp(System.currentTimeMillis()); - channelRecords.add(record); + channelRecord.setTimestamp(System.currentTimeMillis()); + channelRecords.add(channelRecord); continue; } - final ChannelRecord record = channel.createReadRecord(); - validRecords.add(record); - channelRecords.add(record); + final ChannelRecord channelRecord = channel.createReadRecord(); + validRecords.add(channelRecord); + channelRecords.add(channelRecord); } if (!validRecords.isEmpty()) { @@ -405,42 +402,80 @@ public List read(final Set channelNames) throws KuraExcep } protected List getFinalRecords(List channelRecords, Map channels) { - channelRecords.stream() - .forEach(channelRecord -> { - Channel channel = channels.get(channelRecord.getChannelName()); + channelRecords.stream().forEach(channelRecord -> { + Channel channel = channels.get(channelRecord.getChannelName()); - if (shouldApplyScaleAndOffset(channelRecord, channel)) { - applyScaleAndOffset(channelRecord, channel); - } - }); + if (shouldApplyScaleAndOffset(channelRecord, channel)) { + applyScaleAndOffset(channelRecord, channel); + } + }); return channelRecords; } private boolean shouldApplyScaleAndOffset(final ChannelRecord channelRecord, final Channel channel) { - return !isNull(channelRecord) && !isNull(channelRecord.getValueType()) && !isNull(channelRecord.getValue()) - && (channel.getValueScale() != 1.0d || channel.getValueOffset() != 0.0d); + return !isNull(channelRecord) && // + !isNull(channelRecord.getValueType()) && // + !isNull(channelRecord.getValue()) && // + (!channel.getValueScaleAsTypedValue().equals(TypedValues.newDoubleValue(1.0d)) + || channel.getValueScaleAsTypedValue().equals(TypedValues.newDoubleValue(0.0d))); } + @SuppressWarnings("unchecked") private void applyScaleAndOffset(final ChannelRecord channelRecord, final Channel channel) { - final double channelScale = channel.getValueScale(); - final double channelOffset = channel.getValueOffset(); - - if (channelRecord.getValueType().equals(DataType.DOUBLE)) { - channelRecord.setValue(new DoubleValue( - (double) channelRecord.getValue().getValue() * channelScale + channelOffset)); - } else if (channelRecord.getValueType().equals(DataType.FLOAT)) { - channelRecord.setValue( - new FloatValue((float) channelRecord.getValue().getValue() * (float) channelScale - + (float) channelOffset)); - } else if (channelRecord.getValueType().equals(DataType.INTEGER)) { - channelRecord.setValue(new IntegerValue( - (int) channelRecord.getValue().getValue() * (int) channelScale + (int) channelOffset)); - } else if (channelRecord.getValueType().equals(DataType.LONG)) { - channelRecord - .setValue(new LongValue((long) channelRecord.getValue().getValue() * (long) channelScale - + (long) channelOffset)); + final TypedValue channelScale = channel.getValueScaleAsTypedValue(); + final TypedValue channelOffset = channel.getValueOffsetAsTypedValue(); + + switch (channel.getValueType()) { + case DOUBLE: + case FLOAT: + case INTEGER: + case LONG: + channelRecord.setValue(calculateScaleAndOffset((TypedValue) channelRecord.getValue(), + channelScale, channelOffset)); + break; + case BOOLEAN: + case BYTE_ARRAY: + case STRING: + // DO NOTHING + break; + default: + throw new IllegalStateException("Unsupported channel type: " + channel.getValueType()); + } + + } + + private TypedValue calculateScaleAndOffset(TypedValue typedValue, + TypedValue typedScale, TypedValue typedOffset) { + + Number result = null; + + switch (typedScale.getType()) { + case DOUBLE: + result = typedScale.getValue().doubleValue() * typedValue.getValue().doubleValue() + + typedOffset.getValue().doubleValue(); + break; + + case FLOAT: + result = typedScale.getValue().floatValue() * typedValue.getValue().floatValue() + + typedOffset.getValue().floatValue(); + break; + + case INTEGER: + result = typedScale.getValue().intValue() * typedValue.getValue().intValue() + + typedOffset.getValue().intValue(); + break; + + case LONG: + result = typedScale.getValue().longValue() * typedValue.getValue().longValue() + + typedOffset.getValue().longValue(); + break; + default: + throw new IllegalArgumentException("Invalid scale/offset type"); } + + return TypedValues.newNumericTypedValue(typedValue.getType(), result); + } public boolean hasReadChannels() { @@ -619,8 +654,7 @@ protected class ChannelListenerHolder implements ChannelListener { private final ChannelListener listener; private final Channel channel; - public ChannelListenerHolder(Channel channel, - ChannelListener listener) { + public ChannelListenerHolder(Channel channel, ChannelListener listener) { this.channel = channel; this.listener = listener; } diff --git a/kura/org.eclipse.kura.asset.provider/src/main/java/org/eclipse/kura/asset/provider/BaseChannelDescriptor.java b/kura/org.eclipse.kura.asset.provider/src/main/java/org/eclipse/kura/asset/provider/BaseChannelDescriptor.java index 66ea7cd9184..4e00edeb744 100644 --- a/kura/org.eclipse.kura.asset.provider/src/main/java/org/eclipse/kura/asset/provider/BaseChannelDescriptor.java +++ b/kura/org.eclipse.kura.asset.provider/src/main/java/org/eclipse/kura/asset/provider/BaseChannelDescriptor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2021 Eurotech and/or its affiliates and others + * Copyright (c) 2016, 2024 Eurotech and/or its affiliates and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -133,6 +133,18 @@ protected BaseChannelDescriptor() { this.defaultElements.add(valueType); + final Tad scaleOffsetType = new Tad(); + scaleOffsetType.setName(AssetConstants.SCALE_OFFSET_TYPE.value().substring(1)); + scaleOffsetType.setId(AssetConstants.SCALE_OFFSET_TYPE.value()); + scaleOffsetType.setDescription("Scale/Offset type of the channel"); + scaleOffsetType.setType(Tscalar.STRING); + scaleOffsetType.setRequired(true); + scaleOffsetType.setDefault(DataType.DOUBLE.name()); + + addOptions(scaleOffsetType, DataType.values()); + + this.defaultElements.add(scaleOffsetType); + final Tad valueScale = new Tad(); valueScale.setName(VALUE_SCALE.value().substring(1)); valueScale.setId(VALUE_SCALE.value()); diff --git a/kura/org.eclipse.kura.asset.provider/src/main/java/org/eclipse/kura/internal/asset/provider/BaseAssetConfiguration.java b/kura/org.eclipse.kura.asset.provider/src/main/java/org/eclipse/kura/internal/asset/provider/BaseAssetConfiguration.java index 74de53fa19d..6645dac1557 100644 --- a/kura/org.eclipse.kura.asset.provider/src/main/java/org/eclipse/kura/internal/asset/provider/BaseAssetConfiguration.java +++ b/kura/org.eclipse.kura.asset.provider/src/main/java/org/eclipse/kura/internal/asset/provider/BaseAssetConfiguration.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2021 Eurotech and/or its affiliates and others + * Copyright (c) 2016, 2024 Eurotech and/or its affiliates and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -51,6 +51,8 @@ import org.eclipse.kura.driver.ChannelDescriptor; import org.eclipse.kura.driver.Driver; import org.eclipse.kura.type.DataType; +import org.eclipse.kura.type.TypedValue; +import org.eclipse.kura.type.TypedValues; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -328,22 +330,31 @@ private static DataType getDataType(final Map properties) { return DataType.getDataType(valueTypeProp); } - private static double getValueScale(final Map properties) { + private static DataType getScaleOffsetType(final Map properties) { + final String scaleOffsetypeProp = (String) properties.get(AssetConstants.SCALE_OFFSET_TYPE.value()); + + if (scaleOffsetypeProp == null) { + return DataType.DOUBLE; + } + return DataType.getNumericDataType(scaleOffsetypeProp); + } + + private static TypedValue getValueScale(final Map properties) { final String valueScale = (String) properties.get(VALUE_SCALE.value()); if (valueScale == null) { - return 1.0d; + return TypedValues.newDoubleValue(1.0d); } - return Double.valueOf(valueScale); + return TypedValues.parseNumericTypedValue(getScaleOffsetType(properties), valueScale); } - private static double getValueOffset(final Map properties) { - final String valueScale = (String) properties.get(VALUE_OFFSET.value()); + private static TypedValue getValueOffset(final Map properties) { + final String valueOffset = (String) properties.get(VALUE_OFFSET.value()); - if (valueScale == null) { - return 0.0d; + if (valueOffset == null) { + return TypedValues.newDoubleValue(0.0d); } - return Double.valueOf(valueScale); + return TypedValues.parseNumericTypedValue(getScaleOffsetType(properties), valueOffset); } private static String getUnit(final Map properties) { @@ -376,9 +387,9 @@ private static Channel extractChannel(final String channelName, final Map valueScale = getValueScale(channelConfig); - final double valueOffset = getValueOffset(channelConfig); + final TypedValue valueOffset = getValueOffset(channelConfig); final Channel channel = new Channel(channelName, channelType, dataType, channelConfig); channel.setEnabled(isEnabled); diff --git a/kura/org.eclipse.kura.web2/META-INF/MANIFEST.MF b/kura/org.eclipse.kura.web2/META-INF/MANIFEST.MF index f2105a32f56..5ca6aa81455 100644 --- a/kura/org.eclipse.kura.web2/META-INF/MANIFEST.MF +++ b/kura/org.eclipse.kura.web2/META-INF/MANIFEST.MF @@ -27,7 +27,7 @@ Import-Package: com.eclipsesource.json;version="0.9.5", org.eclipse.kura;version="[1.2,2.0)", org.eclipse.kura.annotation;version="[1.0,2.0)", org.eclipse.kura.asset;version="[0.9,2.0)", - org.eclipse.kura.asset.provider;version="[2.0,2.1)", + org.eclipse.kura.asset.provider;version="[2.1,2.2)", org.eclipse.kura.audit;version="[1.0,2.0)", org.eclipse.kura.certificate;version="[2.1,3.0)", org.eclipse.kura.channel;version="[1.0,2.0)", diff --git a/kura/org.eclipse.kura.wire.component.provider/META-INF/MANIFEST.MF b/kura/org.eclipse.kura.wire.component.provider/META-INF/MANIFEST.MF index 89285207779..5fad72791f9 100644 --- a/kura/org.eclipse.kura.wire.component.provider/META-INF/MANIFEST.MF +++ b/kura/org.eclipse.kura.wire.component.provider/META-INF/MANIFEST.MF @@ -10,7 +10,7 @@ Import-Package: org.apache.logging.log4j;version="2.8.2", org.apache.logging.log4j.util;version="2.8.2", org.eclipse.kura;version="[1.2,2.0)", org.eclipse.kura.asset;version="[1.0,2.0)", - org.eclipse.kura.asset.provider;version="[2.0,2.1)", + org.eclipse.kura.asset.provider;version="[2.1,2.2)", org.eclipse.kura.channel;version="[1.0,2.0)", org.eclipse.kura.channel.listener;version="[1.0,2.0)", org.eclipse.kura.clock;version="[1.0,2.0)", diff --git a/kura/test/org.eclipse.kura.asset.provider.test/src/main/java/org/eclipse/kura/asset/provider/test/AssetTest.java b/kura/test/org.eclipse.kura.asset.provider.test/src/main/java/org/eclipse/kura/asset/provider/test/AssetTest.java index e098ae1ef14..a7c1e3bfbc4 100644 --- a/kura/test/org.eclipse.kura.asset.provider.test/src/main/java/org/eclipse/kura/asset/provider/test/AssetTest.java +++ b/kura/test/org.eclipse.kura.asset.provider.test/src/main/java/org/eclipse/kura/asset/provider/test/AssetTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2021 Eurotech and/or its affiliates and others + * Copyright (c) 2016, 2024 Eurotech and/or its affiliates and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -486,7 +486,7 @@ public void testGetConfiguration() throws KuraException { List ads = ocd.getAD(); assertNotNull(ads); - assertEquals(34, ads.size()); // description, driver, 32 from BaseChannelDescriptor and StubChannelDescriptor + assertEquals(38, ads.size()); // description, driver, 32 from BaseChannelDescriptor and StubChannelDescriptor assertEquals("asset.desc", ads.get(0).getId()); assertEquals("driver.pid", ads.get(1).getId()); diff --git a/kura/test/org.eclipse.kura.wire.component.provider.test/src/main/java/org/eclipse/kura/internal/wire/asset/test/ScaleOffsetTest.java b/kura/test/org.eclipse.kura.wire.component.provider.test/src/main/java/org/eclipse/kura/internal/wire/asset/test/ScaleOffsetTest.java index c4e099fbbb6..69a3f468fe6 100644 --- a/kura/test/org.eclipse.kura.wire.component.provider.test/src/main/java/org/eclipse/kura/internal/wire/asset/test/ScaleOffsetTest.java +++ b/kura/test/org.eclipse.kura.wire.component.provider.test/src/main/java/org/eclipse/kura/internal/wire/asset/test/ScaleOffsetTest.java @@ -1,18 +1,18 @@ /******************************************************************************* - * Copyright (c) 2023 Eurotech and/or its affiliates and others - * + * Copyright (c) 2024 Eurotech and/or its affiliates and others + * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ - * + * * SPDX-License-Identifier: EPL-2.0 - * + * * Contributors: * Eurotech *******************************************************************************/ package org.eclipse.kura.internal.wire.asset.test; -import java.util.OptionalDouble; +import java.util.Optional; import org.eclipse.kura.type.DataType; import org.junit.Test; @@ -24,7 +24,7 @@ public class ScaleOffsetTest extends WireAssetTestBase { @Test public void shouldSupportMissingScaleOffset() { - givenAssetChannel("foo", DataType.DOUBLE, OptionalDouble.empty(), OptionalDouble.empty()); + givenAssetChannel("foo", DataType.DOUBLE, DataType.DOUBLE, Optional.empty(), Optional.empty()); whenDriverProducesValue("foo", 1.0d); @@ -33,7 +33,7 @@ public void shouldSupportMissingScaleOffset() { @Test public void shouldApplyScaleToDouble() { - givenAssetChannel("foo", DataType.DOUBLE, OptionalDouble.of(3.0d), OptionalDouble.empty()); + givenAssetChannel("foo", DataType.DOUBLE, DataType.DOUBLE, Optional.of(3.0d), Optional.empty()); whenDriverProducesValue("foo", 1.0d); @@ -42,7 +42,7 @@ public void shouldApplyScaleToDouble() { @Test public void shouldApplyScaleToFloat() { - givenAssetChannel("foo", DataType.FLOAT, OptionalDouble.of(3.0d), OptionalDouble.empty()); + givenAssetChannel("foo", DataType.FLOAT, DataType.DOUBLE, Optional.of(3.0d), Optional.empty()); whenDriverProducesValue("foo", 1.0f); @@ -50,8 +50,35 @@ public void shouldApplyScaleToFloat() { } @Test - public void shouldApplyScaleToIngeger() { - givenAssetChannel("foo", DataType.INTEGER, OptionalDouble.of(3.0d), OptionalDouble.empty()); + public void shouldApplyScaleToInteger() { + givenAssetChannel("foo", DataType.INTEGER, DataType.DOUBLE, Optional.of(3.0d), Optional.empty()); + + whenDriverProducesValue("foo", 1); + + thenAssetOutputContains(0, "foo", 3); + } + + @Test + public void shouldApplyScaleToIntegerWithFloatScale() { + givenAssetChannel("foo", DataType.INTEGER, DataType.FLOAT, Optional.of(3.0f), Optional.empty()); + + whenDriverProducesValue("foo", 1); + + thenAssetOutputContains(0, "foo", 3); + } + + @Test + public void shouldApplyScaleToIntegerWithIntegerScale() { + givenAssetChannel("foo", DataType.INTEGER, DataType.INTEGER, Optional.of(3), Optional.empty()); + + whenDriverProducesValue("foo", 1); + + thenAssetOutputContains(0, "foo", 3); + } + + @Test + public void shouldApplyScaleToIntegerWithLongScale() { + givenAssetChannel("foo", DataType.INTEGER, DataType.LONG, Optional.of(3l), Optional.empty()); whenDriverProducesValue("foo", 1); @@ -60,7 +87,7 @@ public void shouldApplyScaleToIngeger() { @Test public void shouldApplyScaleToLong() { - givenAssetChannel("foo", DataType.LONG, OptionalDouble.of(3.0d), OptionalDouble.empty()); + givenAssetChannel("foo", DataType.LONG, DataType.DOUBLE, Optional.of(3.0d), Optional.empty()); whenDriverProducesValue("foo", 1l); @@ -69,7 +96,7 @@ public void shouldApplyScaleToLong() { @Test public void shouldApplyOffsetToDouble() { - givenAssetChannel("foo", DataType.DOUBLE, OptionalDouble.empty(), OptionalDouble.of(10.0d)); + givenAssetChannel("foo", DataType.DOUBLE, DataType.DOUBLE, Optional.empty(), Optional.of(10.0d)); whenDriverProducesValue("foo", 1.0d); @@ -78,7 +105,7 @@ public void shouldApplyOffsetToDouble() { @Test public void shouldApplyOffsetToFloat() { - givenAssetChannel("foo", DataType.FLOAT, OptionalDouble.empty(), OptionalDouble.of(-2.0d)); + givenAssetChannel("foo", DataType.FLOAT, DataType.DOUBLE, Optional.empty(), Optional.of(-2.0d)); whenDriverProducesValue("foo", 1.0f); @@ -87,7 +114,7 @@ public void shouldApplyOffsetToFloat() { @Test public void shouldApplyOffsetToIngeger() { - givenAssetChannel("foo", DataType.INTEGER, OptionalDouble.empty(), OptionalDouble.of(10.0d)); + givenAssetChannel("foo", DataType.INTEGER, DataType.DOUBLE, Optional.empty(), Optional.of(10.0d)); whenDriverProducesValue("foo", 1); @@ -96,7 +123,7 @@ public void shouldApplyOffsetToIngeger() { @Test public void shouldApplyOffsetToLong() { - givenAssetChannel("foo", DataType.LONG, OptionalDouble.empty(), OptionalDouble.of(-2.0d)); + givenAssetChannel("foo", DataType.LONG, DataType.DOUBLE, Optional.empty(), Optional.of(-2.0d)); whenDriverProducesValue("foo", 1l); @@ -105,7 +132,7 @@ public void shouldApplyOffsetToLong() { @Test public void shouldApplyBothScaleAndOffset() { - givenAssetChannel("foo", DataType.LONG, OptionalDouble.of(6.0f), OptionalDouble.of(-2.0d)); + givenAssetChannel("foo", DataType.LONG, DataType.DOUBLE, Optional.of(6.0f), Optional.of(-2.0d)); whenDriverProducesValue("foo", 2l); @@ -114,7 +141,7 @@ public void shouldApplyBothScaleAndOffset() { @Test public void shouldTolerateScaleAndOffsetOnBoolean() { - givenAssetChannel("foo", DataType.BOOLEAN, OptionalDouble.of(6.0f), OptionalDouble.of(-2.0d)); + givenAssetChannel("foo", DataType.BOOLEAN, DataType.DOUBLE, Optional.of(6.0f), Optional.of(-2.0d)); whenDriverProducesValue("foo", true); @@ -123,7 +150,7 @@ public void shouldTolerateScaleAndOffsetOnBoolean() { @Test public void shouldTolerateScaleAndOffsetOnString() { - givenAssetChannel("foo", DataType.STRING, OptionalDouble.of(6.0f), OptionalDouble.of(-2.0d)); + givenAssetChannel("foo", DataType.STRING, DataType.DOUBLE, Optional.of(6.0f), Optional.of(-2.0d)); whenDriverProducesValue("foo", "bar"); @@ -132,7 +159,7 @@ public void shouldTolerateScaleAndOffsetOnString() { @Test public void shouldTolerateScaleAndOffsetOnByteArray() { - givenAssetChannel("foo", DataType.BYTE_ARRAY, OptionalDouble.of(6.0f), OptionalDouble.of(-2.0d)); + givenAssetChannel("foo", DataType.BYTE_ARRAY, DataType.DOUBLE, Optional.of(6.0f), Optional.of(-2.0d)); whenDriverProducesValue("foo", new byte[] { 1, 2, 3, 4 }); @@ -155,9 +182,9 @@ public ScaleOffsetTest(final TriggerMode triggerMode) { this.triggerMode = triggerMode; } - private void givenAssetChannel(String name, DataType dataType, OptionalDouble scale, - OptionalDouble offset) { - super.givenAssetChannel(name, this.triggerMode == TriggerMode.LISTEN, dataType, scale, offset); + private void givenAssetChannel(String name, DataType dataType, DataType scaleOffsetType, + Optional scale, Optional offset) { + super.givenAssetChannel(name, this.triggerMode == TriggerMode.LISTEN, dataType, scaleOffsetType, scale, offset); } private void whenDriverProducesValue(final String channelName, final Object value) { diff --git a/kura/test/org.eclipse.kura.wire.component.provider.test/src/main/java/org/eclipse/kura/internal/wire/asset/test/WireAssetTestBase.java b/kura/test/org.eclipse.kura.wire.component.provider.test/src/main/java/org/eclipse/kura/internal/wire/asset/test/WireAssetTestBase.java index 75533d2ced7..77894c9d822 100644 --- a/kura/test/org.eclipse.kura.wire.component.provider.test/src/main/java/org/eclipse/kura/internal/wire/asset/test/WireAssetTestBase.java +++ b/kura/test/org.eclipse.kura.wire.component.provider.test/src/main/java/org/eclipse/kura/internal/wire/asset/test/WireAssetTestBase.java @@ -1,12 +1,12 @@ /******************************************************************************* - * Copyright (c) 2023 Eurotech and/or its affiliates and others - * + * Copyright (c) 2024 Eurotech and/or its affiliates and others + * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ - * + * * SPDX-License-Identifier: EPL-2.0 - * + * * Contributors: * Eurotech *******************************************************************************/ @@ -28,7 +28,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.OptionalDouble; import java.util.concurrent.TimeUnit; import org.eclipse.kura.channel.ChannelType; @@ -53,27 +52,29 @@ public class WireAssetTestBase { private TestEmitterReceiver testReceiver; private TestEmitterReceiver testEmitter; - private MockDriver driver = new MockDriver(); + private final MockDriver driver = new MockDriver(); - private List envelopes = new ArrayList<>(); + private final List envelopes = new ArrayList<>(); protected void givenAssetChannel(final String name, final boolean listen, final DataType dataType, - final OptionalDouble scale, final OptionalDouble offset) { + final DataType scaleOffsetType, final Optional scale, + final Optional offset) { final Map config = new HashMap<>(); config.put("driver.pid", "testDriver"); config.put(name + "#+name", name); config.put(name + "#+type", ChannelType.READ.name()); config.put(name + "#+value.type", dataType.name()); + config.put(name + "#+scaleoffset.type", scaleOffsetType.name()); config.put(name + "#+enabled", true); config.put(name + "#+listen", listen); if (scale.isPresent()) { - config.put(name + "#+scale", Double.toString(scale.getAsDouble())); + config.put(name + "#+scale", scale.get().toString()); } if (offset.isPresent()) { - config.put(name + "#+offset", Double.toString(offset.getAsDouble())); + config.put(name + "#+offset", offset.get().toString()); } givenAssetConfig(config); @@ -88,8 +89,7 @@ protected void givenAssetConfig(final Map assetConfig) { final BundleContext bundleContext = FrameworkUtil.getBundle(OnChangeCacheTest.class).getBundleContext(); final WireGraphService wireGraphService = WireTestUtil - .trackService(WireGraphService.class, Optional.empty()) - .get(30, TimeUnit.SECONDS); + .trackService(WireGraphService.class, Optional.empty()).get(30, TimeUnit.SECONDS); final GraphBuilder graphBuilder = new GraphBuilder().addTestEmitterReceiver("emitter") .addTestEmitterReceiver("receiver") @@ -101,11 +101,11 @@ protected void givenAssetConfig(final Map assetConfig) { graphBuilder.replaceExistingGraph(bundleContext, wireGraphService).get(30, TimeUnit.SECONDS); - testEmitter = graphBuilder.getTrackedWireComponent("emitter"); - testReceiver = graphBuilder.getTrackedWireComponent("receiver"); + this.testEmitter = graphBuilder.getTrackedWireComponent("emitter"); + this.testReceiver = graphBuilder.getTrackedWireComponent("receiver"); - testReceiver.setConsumer(e -> { - synchronized (envelopes) { + this.testReceiver.setConsumer(e -> { + synchronized (this.envelopes) { this.envelopes.add(e); this.envelopes.notifyAll(); } @@ -114,10 +114,10 @@ protected void givenAssetConfig(final Map assetConfig) { final Dictionary properties = new Hashtable<>(); properties.put("kura.service.pid", "testDriver"); - driverRegistration = Optional.of(bundleContext.registerService(Driver.class, driver, properties)); + driverRegistration = Optional.of(bundleContext.registerService(Driver.class, this.driver, properties)); try { - driver.preparedReadCalled.get(30, TimeUnit.SECONDS); + this.driver.preparedReadCalled.get(30, TimeUnit.SECONDS); } catch (Exception e) { fail("driver not ready"); } @@ -131,12 +131,11 @@ protected void givenAssetConfig(final Map assetConfig) { return split[1].equals("+listen"); - }).filter(k -> Objects.equals(true, assetConfig.get(k))) - .count(); + }).filter(k -> Objects.equals(true, assetConfig.get(k))).count(); - synchronized (driver.listeners) { - while (driver.listeners.size() != listenChannelCount) { - driver.listeners.wait(30000); + synchronized (this.driver.listeners) { + while (this.driver.listeners.size() != listenChannelCount) { + this.driver.listeners.wait(30000); } } @@ -146,7 +145,7 @@ protected void givenAssetConfig(final Map assetConfig) { } protected void givenChannelValue(final String key, final Object value) { - driver.addReadResult(key, TypedValues.newTypedValue(value)); + this.driver.addReadResult(key, TypedValues.newTypedValue(value)); } protected void givenChannelValues(final String key, final Object... values) { @@ -157,7 +156,7 @@ protected void givenChannelValues(final String key, final Object... values) { protected void whenAssetReceivesEnvelope() { - testEmitter.emit(); + this.testEmitter.emit(); } protected void whenDriverEmitsEvents(final Object... values) { @@ -167,7 +166,7 @@ protected void whenDriverEmitsEvents(final Object... values) { final String channelName = (String) iter.next(); final TypedValue value = TypedValues.newTypedValue(iter.next()); - driver.emitChannelEvent(channelName, value); + this.driver.emitChannelEvent(channelName, value); } } @@ -178,15 +177,15 @@ protected void whenAssetReceivesEnvelopes(final int count) { } protected void awaitEnvelope(final int index) { - synchronized (envelopes) { - if (index >= envelopes.size()) { + synchronized (this.envelopes) { + if (index >= this.envelopes.size()) { try { - envelopes.wait(30000); + this.envelopes.wait(30000); } catch (InterruptedException e) { throw new IllegalStateException("Interrupted while waiting for envelope"); } - if (index >= envelopes.size()) { + if (index >= this.envelopes.size()) { fail("expected to receive at least " + (index + 1) + " envelopes"); } } @@ -196,7 +195,7 @@ protected void awaitEnvelope(final int index) { protected void thenAssetOutputContains(final int index, final Object... properties) { awaitEnvelope(index); - final WireEnvelope envelope = envelopes.get(index); + final WireEnvelope envelope = this.envelopes.get(index); final Iterator iter = Arrays.asList(properties).iterator(); @@ -211,7 +210,7 @@ protected void thenAssetOutputContains(final int index, final Object... properti protected void thenAssetOutputContainsKey(final int index, final String key) { awaitEnvelope(index); - final WireEnvelope envelope = envelopes.get(index); + final WireEnvelope envelope = this.envelopes.get(index); assertTrue(envelope.getRecords().get(0).getProperties().containsKey(key)); } @@ -219,7 +218,7 @@ protected void thenAssetOutputContainsKey(final int index, final String key) { protected void thenAssetOutputPropertyCountIs(final int index, final int expectedCount) { awaitEnvelope(index); - final WireEnvelope envelope = envelopes.get(index); + final WireEnvelope envelope = this.envelopes.get(index); assertEquals(expectedCount, envelope.getRecords().get(0).getProperties().size()); } @@ -227,13 +226,9 @@ protected void thenAssetOutputPropertyCountIs(final int index, final int expecte protected void thenAssetOutputDoesNotContain(final int index, final String... properties) { awaitEnvelope(index); - final WireEnvelope envelope = envelopes.get(index); - - final Iterator iter = Arrays.asList(properties).iterator(); - - while (iter.hasNext()) { - final String key = (String) iter.next(); + final WireEnvelope envelope = this.envelopes.get(index); + for (String key : Arrays.asList(properties)) { assertFalse(envelope.getRecords().get(0).getProperties().containsKey(key)); } } @@ -245,7 +240,7 @@ protected void thenTotalEmittedEnvelopeCountAfter1SecIs(final int expectedCount) throw new IllegalStateException("sleep interrupted"); } - assertEquals(expectedCount, envelopes.size()); + assertEquals(expectedCount, this.envelopes.size()); } }