diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/ConnectAdapterIiotInit.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/ConnectAdapterIiotInit.java index 03685343ba..dcd646eacc 100644 --- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/ConnectAdapterIiotInit.java +++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/ConnectAdapterIiotInit.java @@ -18,6 +18,7 @@ package org.apache.streampipes.connect.iiot; +import org.apache.streampipes.connect.iiot.adapters.iolink.IfmAlMqttAdapter; import org.apache.streampipes.connect.iiot.adapters.opcua.OpcUaAdapter; import org.apache.streampipes.connect.iiot.adapters.plc4x.modbus.Plc4xModbusAdapter; import org.apache.streampipes.connect.iiot.adapters.plc4x.s7.Plc4xS7Adapter; @@ -49,6 +50,7 @@ public SpServiceDefinition provideServiceDefinition() { 8001) .registerAdapter(new MachineDataSimulatorAdapter()) .registerAdapter(new FileReplayAdapter()) + .registerAdapter(new IfmAlMqttAdapter()) .registerAdapter(new RosBridgeAdapter()) .registerAdapter(new OpcUaAdapter()) .registerAdapter(new Plc4xS7Adapter()) diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/iolink/IfmAlMqttAdapter.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/iolink/IfmAlMqttAdapter.java new file mode 100644 index 0000000000..c5db51d946 --- /dev/null +++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/iolink/IfmAlMqttAdapter.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.streampipes.connect.iiot.adapters.iolink; + +import org.apache.streampipes.commons.exceptions.connect.AdapterException; +import org.apache.streampipes.commons.exceptions.connect.ParseException; +import org.apache.streampipes.connect.iiot.adapters.iolink.sensor.SensorVVB001; +import org.apache.streampipes.extensions.api.connect.IAdapterConfiguration; +import org.apache.streampipes.extensions.api.connect.IEventCollector; +import org.apache.streampipes.extensions.api.connect.IParser; +import org.apache.streampipes.extensions.api.connect.StreamPipesAdapter; +import org.apache.streampipes.extensions.api.connect.context.IAdapterGuessSchemaContext; +import org.apache.streampipes.extensions.api.connect.context.IAdapterRuntimeContext; +import org.apache.streampipes.extensions.api.extractor.IAdapterParameterExtractor; +import org.apache.streampipes.extensions.api.extractor.IStaticPropertyExtractor; +import org.apache.streampipes.extensions.management.connect.adapter.parser.JsonParsers; +import org.apache.streampipes.extensions.management.connect.adapter.parser.json.JsonObjectParser; +import org.apache.streampipes.model.AdapterType; +import org.apache.streampipes.model.StreamPipesErrorMessage; +import org.apache.streampipes.model.connect.guess.GuessSchema; +import org.apache.streampipes.model.monitoring.SpLogEntry; +import org.apache.streampipes.pe.shared.config.mqtt.MqttConfig; +import org.apache.streampipes.pe.shared.config.mqtt.MqttConnectUtils; +import org.apache.streampipes.pe.shared.config.mqtt.MqttConsumer; +import org.apache.streampipes.sdk.builder.adapter.AdapterConfigurationBuilder; +import org.apache.streampipes.sdk.helpers.Labels; +import org.apache.streampipes.sdk.helpers.Locales; +import org.apache.streampipes.sdk.helpers.Options; +import org.apache.streampipes.sdk.utils.Assets; + +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +public class IfmAlMqttAdapter implements StreamPipesAdapter { + + public static final String ID = "org.apache.streampipes.connect.iiot.adapters.iolink"; + + private static final Logger LOG = LoggerFactory.getLogger(IfmAlMqttAdapter.class); + + private static final String PORTS = "ports"; + private static final String SENSOR_TYPE = "sensor_type"; + + private MqttConsumer mqttConsumer; + private MqttConfig mqttConfig; + + private final IParser parser; + + private List ports; + + public IfmAlMqttAdapter() { + parser = new JsonParsers(new JsonObjectParser()); + } + + @Override + public IAdapterConfiguration declareConfig() { + return AdapterConfigurationBuilder + .create(ID, IfmAlMqttAdapter::new) + .withLocales(Locales.EN) + .withAssets(Assets.DOCUMENTATION, Assets.ICON) + .withCategory(AdapterType.Generic, AdapterType.Manufacturing) + .requiredTextParameter(MqttConnectUtils.getBrokerUrlLabel()) + .requiredAlternatives(MqttConnectUtils.getAccessModeLabel(), MqttConnectUtils.getAlternativesOne(), + MqttConnectUtils.getAlternativesTwo()) + .requiredMultiValueSelection(Labels.withId(PORTS), + Options.from("Port 1", "Port 2", "Port 3", "Port 4")) + .requiredSingleValueSelection(Labels.withId(SENSOR_TYPE), + Options.from("VVB001")) + .requiredTextParameter(MqttConnectUtils.getTopicLabel()) + .buildConfiguration(); + } + + @Override + public void onAdapterStarted(IAdapterParameterExtractor extractor, + IEventCollector collector, + IAdapterRuntimeContext adapterRuntimeContext) throws AdapterException { + var sensor = new SensorVVB001(); + + this.applyConfiguration(extractor.getStaticPropertyExtractor()); + this.mqttConsumer = new MqttConsumer( + this.mqttConfig, + (mqttEvent) -> { + try { + InputStream in = convertByte(mqttEvent); + parser.parse(in, (event) -> { + + var data = getMap(event, "data"); + var payload = getMap(data, "payload"); + + var deviceInfo = getMap(payload, "/deviceinfo/serialnumber"); + var serialnumber = deviceInfo.get("data"); + + for (int i = 0; i < ports.size(); i++) { + + var portResult = getMap(payload, + "/iolinkmaster/port[%s]/iolinkdevice/pdin".formatted(ports.get(i))); + var eventData = (String) portResult.get("data"); + + var parsedEvent = sensor.parseEvent(eventData); + parsedEvent.put("timestamp", System.currentTimeMillis() + i); + parsedEvent.put("port", "port" + ports.get(i)); + parsedEvent.put(SensorVVB001.IO_LINK_MASTER_SN, serialnumber); + + collector.collect(parsedEvent); + } + }); + } catch (Exception e) { + adapterRuntimeContext + .getLogger() + .addErrorMessage( + extractor.getAdapterDescription().getElementId(), + SpLogEntry.from(System.currentTimeMillis(), StreamPipesErrorMessage.from(e))); + LOG.error("Could not parse event", e); + } + }); + + Thread thread = new Thread(this.mqttConsumer); + thread.start(); + } + + @Override + public void onAdapterStopped(IAdapterParameterExtractor extractor, + IAdapterRuntimeContext adapterRuntimeContext) throws AdapterException { + this.mqttConsumer.close(); + } + + @Override + public GuessSchema onSchemaRequested(IAdapterParameterExtractor extractor, + IAdapterGuessSchemaContext adapterGuessSchemaContext) throws AdapterException { + this.applyConfiguration(extractor.getStaticPropertyExtractor()); + + return new SensorVVB001().getEventSchema(); + } + + + private void applyConfiguration(IStaticPropertyExtractor extractor) { + mqttConfig = MqttConnectUtils.getMqttConfig(extractor); + String sensorType = extractor.selectedSingleValue(SENSOR_TYPE, String.class); + var selectedPorts = extractor.selectedMultiValues(PORTS, String.class); + ports = selectedPorts.stream() + .map(port -> port.substring(5)) + .toList(); + } + + private Map getMap(Map event, String key) { + if (event.containsKey(key)) { + var payload = event.get(key); + if (payload instanceof Map) { + return (Map) event.get(key); + } else { + throw new ParseException(getErrorMessage(key)); + } + } else { + throw new ParseException(getErrorMessage(key)); + } + } + + private String getErrorMessage(String key) { + return "The event does not contain key: %s. Please reconfigure the IOLink master to include this key".formatted( + key); + } + + private InputStream convertByte(byte[] event) { + return IOUtils.toInputStream(new String(event), "UTF-8"); + } +} diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/iolink/sensor/IoLinkSensor.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/iolink/sensor/IoLinkSensor.java new file mode 100644 index 0000000000..f371aa1e35 --- /dev/null +++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/iolink/sensor/IoLinkSensor.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.streampipes.connect.iiot.adapters.iolink.sensor; + +import org.apache.streampipes.model.connect.guess.GuessSchema; + +import java.util.Map; + +public interface IoLinkSensor { + GuessSchema getEventSchema(); + + Map parseEvent(String encodedEvent); +} diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/iolink/sensor/SensorVVB001.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/iolink/sensor/SensorVVB001.java new file mode 100644 index 0000000000..61bf36954a --- /dev/null +++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/iolink/sensor/SensorVVB001.java @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.streampipes.connect.iiot.adapters.iolink.sensor; + +import org.apache.streampipes.model.connect.guess.GuessSchema; +import org.apache.streampipes.model.schema.PropertyScope; +import org.apache.streampipes.sdk.builder.PrimitivePropertyBuilder; +import org.apache.streampipes.sdk.builder.adapter.GuessSchemaBuilder; +import org.apache.streampipes.sdk.utils.Datatypes; + +import java.util.HashMap; +import java.util.Map; + +import static org.apache.streampipes.sdk.helpers.EpProperties.timestampProperty; + +public class SensorVVB001 implements IoLinkSensor { + + public static final String V_RMS_NAME = "vRms"; + public static final String A_PEAK_NAME = "aPeak"; + public static final String A_RMS_NAME = "aRms"; + public static final String TEMPERATURE_NAME = "temperature"; + public static final String CREST_NAME = "crest"; + public static final String STATUS_NAME = "status"; + public static final String OUT_1_NAME = "out1"; + public static final String OUT_2_NAME = "out2"; + public static final String PORT_NAME = "port"; + + public static final String IO_LINK_MASTER_SN = "ioLinkMasterSN"; + + + @Override + public GuessSchema getEventSchema() { + + return GuessSchemaBuilder.create() + .property(timestampProperty("timestamp")) + .sample("timestamp", 1685525380729L) + .property( + PrimitivePropertyBuilder + .create(Datatypes.Float, V_RMS_NAME) + .scope(PropertyScope.MEASUREMENT_PROPERTY) + .description("Speed RMS value") + .build() + ) + .sample(V_RMS_NAME, 0.0023) + .property( + PrimitivePropertyBuilder + .create(Datatypes.Float, A_PEAK_NAME) + .scope(PropertyScope.MEASUREMENT_PROPERTY) + .description("Acceleration peak value") + .build() + ) + .sample(A_PEAK_NAME, 6.6) + .property( + PrimitivePropertyBuilder + .create(Datatypes.Float, A_RMS_NAME) + .scope(PropertyScope.MEASUREMENT_PROPERTY) + .description("Acceleration RMS value") + .build() + ) + .sample(A_RMS_NAME, 1.8) + .property( + PrimitivePropertyBuilder + .create(Datatypes.Float, TEMPERATURE_NAME) + .scope(PropertyScope.MEASUREMENT_PROPERTY) + .description("Current temperature") + .build() + ) + .sample(TEMPERATURE_NAME, 22.0) + .property( + PrimitivePropertyBuilder + .create(Datatypes.Float, CREST_NAME) + .scope(PropertyScope.MEASUREMENT_PROPERTY) + .description("Acceleration crest factor") + .build() + ) + .sample(CREST_NAME, 3.7) + .property( + PrimitivePropertyBuilder + .create(Datatypes.Integer, STATUS_NAME) + .scope(PropertyScope.MEASUREMENT_PROPERTY) + .description("Device status (0: OK, 1: Maintenance required, " + + "2: Out of specification, 3: Function Test, 4: Error)") + .build() + ) + .sample(STATUS_NAME, 0) + .property( + PrimitivePropertyBuilder + .create(Datatypes.Boolean, OUT_1_NAME) + .scope(PropertyScope.MEASUREMENT_PROPERTY) + .description("Current state of the digital signal") + .build() + ) + .sample(OUT_1_NAME, true) + .property( + PrimitivePropertyBuilder + .create(Datatypes.Boolean, OUT_2_NAME) + .scope(PropertyScope.MEASUREMENT_PROPERTY) + .description("Current state of the digital signal") + .build() + ) + .sample(OUT_2_NAME, true) + .property( + PrimitivePropertyBuilder + .create(Datatypes.String, PORT_NAME) + .scope(PropertyScope.DIMENSION_PROPERTY) + .description("Port the sensor is connected to at IOLink master") + .build() + ) + .sample(PORT_NAME, "port1") + .property( + PrimitivePropertyBuilder + .create(Datatypes.String, IO_LINK_MASTER_SN) + .scope(PropertyScope.DIMENSION_PROPERTY) + .description("This is the serial number of the IO-Link Master") + .build() + ) + .sample(PORT_NAME, "XXXXXXXXXXXX") + .build(); + } + + @Override + public Map parseEvent(String encodedEvent) { + var event = new HashMap(); + event.put(V_RMS_NAME, getValue(0, 4, 0.0001, encodedEvent)); + event.put(A_PEAK_NAME, getValue(8, 12, 0.1, encodedEvent)); + event.put(A_RMS_NAME, getValue(16, 20, 0.1, encodedEvent)); + event.put(TEMPERATURE_NAME, getValue(24, 28, 0.1, encodedEvent)); + event.put(CREST_NAME, getValue(32, 36, 0.1, encodedEvent)); + event.put(STATUS_NAME, (int) getValue(38, 39, 1.0, encodedEvent)); + + // The last two bits represent the status + var outValues = (int) getValue(39, 40, 1.0, encodedEvent); + event.put(OUT_1_NAME, (outValues & 1) != 0); + event.put(OUT_2_NAME, (outValues & 2) != 0); + + return event; + } + + private double getValue(int start, int end, double scaleFactor, String encodedEvent) { + var word = encodedEvent.substring(start, end); + + var rawValue = (double) (Integer.parseInt(word, 16)); + + return rawValue * scaleFactor; + } +} diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/resources/org.apache.streampipes.connect.iiot.adapters.iolink/documentation.md b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/resources/org.apache.streampipes.connect.iiot.adapters.iolink/documentation.md new file mode 100644 index 0000000000..37c176779a --- /dev/null +++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/resources/org.apache.streampipes.connect.iiot.adapters.iolink/documentation.md @@ -0,0 +1,84 @@ + + +## ifm IOLink + +

+ +

+ +*** + +## Description + +This adapter enables the integration of IO-Link sensor data produced by an ifm IO-Link Master +(e.g., AL1350) with Apache StreamPipes. To use this adapter, you need to configure your IO-Link +master to publish events to an MQTT broker. This can be achieved through a REST interface or via +the browser at `http://##IP_OF_IO_LINK_MASTER##/web/subscribe`. For detailed instructions, +please refer to the ifm documentation. + +### Requirements +The JSON events should include the following information: +- `deviceinfo.serialnumber` +- Only the pdin value is required for each port (e.g., `port[0]`). +- The event `timer[1].datachanged` can be used as a trigger. +Using this adapter, you can create a stream for sensors of the same type. + +### Restrictions +This version supports a single IO-Link master. If you want to connect multiple masters, they must have the same setup. +If you have different requirements, please inform us through the mailing list or GitHub discussions. + +*** + +## Configuration + +Here is a list of the configuration parameters you must provide. + +### Broker URL + +Enter the URL of the broker, including the protocol (e.g. `tcp://10.20.10.3:1883`) + +### Access Mode + +If necessary, provide broker credentials. + +### Ports + +Select the ports that are connected to the IO-Link sensors. + +### Sensor Type + +Choose the type of sensor you want to connect. (**IMPORTANT:** Currently, only the VVB001 is supported) + +## Output + +The output includes all values from the selected sensor type. Here is an example for the `VVB001 sensor`: +``` +{ + "aPeak": 6.6, + "aRms": 1.8, + "crest": 3.7, + "out1": true, + "out2": true, + "port": "000000001234", + "status": 0, + "temperature": 22, + "timestamp": 1685525380729, + "vRms": 0.0023 +} +``` diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/resources/org.apache.streampipes.connect.iiot.adapters.iolink/icon.png b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/resources/org.apache.streampipes.connect.iiot.adapters.iolink/icon.png new file mode 100644 index 0000000000..39c33a25e8 Binary files /dev/null and b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/resources/org.apache.streampipes.connect.iiot.adapters.iolink/icon.png differ diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/resources/org.apache.streampipes.connect.iiot.adapters.iolink/strings.en b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/resources/org.apache.streampipes.connect.iiot.adapters.iolink/strings.en new file mode 100644 index 0000000000..0992986274 --- /dev/null +++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/resources/org.apache.streampipes.connect.iiot.adapters.iolink/strings.en @@ -0,0 +1,50 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +org.apache.streampipes.connect.iiot.adapters.iolink.title=ifm IOLink +org.apache.streampipes.connect.iiot.adapters.iolink.description=Consumes MQTT data produced by an IOLink master of ifm + +access-mode.title=Access Mode +access-mode.description=Provide user credentials for the MQTT broker + +anonymous-alternative.title=Unauthenticated +anonymous-alternative.description= + +username-alternative.title=Username/Password +username-alternative.description= + +username-group.title=User Group +username-group.description= + +username.title=Username +username.description= + +password.title=Password +password.description= + +broker_url.title=Broker URL +broker_url.description=Example: tcp://test-server.com:1883 (Protocol required. Port required)" + +topic.title=Topic +topic.description=Example: test/topic + +ports.title=Ports +ports.description=Select the ports with the selected sensor type + +sensor_type.title=Sensor Type +sensor_type.description=Select the sensor type that you want to connect diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/test/java/org/apache/streampipes/sensor/SensorVVB001Test.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/test/java/org/apache/streampipes/sensor/SensorVVB001Test.java new file mode 100644 index 0000000000..199818d510 --- /dev/null +++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/test/java/org/apache/streampipes/sensor/SensorVVB001Test.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.streampipes.sensor; + +import org.apache.streampipes.connect.iiot.adapters.iolink.sensor.SensorVVB001; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class SensorVVB001Test { + + private SensorVVB001 sensor = new SensorVVB001(); + + private String sampleInput = "0017FC000042FF000012FF00015CFF000025FF03"; + + @Test + public void checkEventSize() { + var result = sensor.parseEvent(sampleInput); + assertEquals(8, result.keySet().size()); + } + + @Test + public void parseEvent() { + var result = sensor.parseEvent(sampleInput); + + assertEquals(0.0023, (double) result.get(SensorVVB001.V_RMS_NAME), 0.001); + assertEquals(6.600, (double) result.get(SensorVVB001.A_PEAK_NAME), 0.001); + assertEquals(1.8, (double) result.get(SensorVVB001.A_RMS_NAME), 0.001); + assertEquals(34.8, (double) result.get(SensorVVB001.TEMPERATURE_NAME), 0.001); + assertEquals(3.7, (double) result.get(SensorVVB001.CREST_NAME), 0.001); + assertEquals(0, result.get(SensorVVB001.STATUS_NAME)); + assertEquals(true, result.get(SensorVVB001.OUT_1_NAME)); + assertEquals(true, result.get(SensorVVB001.OUT_2_NAME)); + } +} \ No newline at end of file