diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/CoapEfentoTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/CoapEfentoTransportResource.java index 6e481cc21d..562a633920 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/CoapEfentoTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/CoapEfentoTransportResource.java @@ -28,18 +28,17 @@ import org.eclipse.californium.core.network.Exchange; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.core.server.resources.Resource; import org.springframework.util.CollectionUtils; +import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.common.adaptor.ProtoConverter; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.EfentoCoapDeviceTypeConfiguration; -import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.coap.ConfigProtos; import org.thingsboard.server.gen.transport.coap.DeviceInfoProtos; -import org.thingsboard.server.gen.transport.coap.MeasurementTypeProtos; import org.thingsboard.server.gen.transport.coap.MeasurementsProtos; import org.thingsboard.server.gen.transport.coap.MeasurementsProtos.ProtoChannel; import org.thingsboard.server.transport.coap.AbstractCoapTransportResource; @@ -59,13 +58,12 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static com.google.gson.JsonParser.parseString; -import static org.thingsboard.server.gen.transport.coap.MeasurementTypeProtos.MeasurementType.MEASUREMENT_TYPE_FLOODING; -import static org.thingsboard.server.gen.transport.coap.MeasurementTypeProtos.MeasurementType.MEASUREMENT_TYPE_OK_ALARM; -import static org.thingsboard.server.gen.transport.coap.MeasurementTypeProtos.MeasurementType.MEASUREMENT_TYPE_OUTPUT_CONTROL; import static org.thingsboard.server.transport.coap.CoapTransportService.CONFIGURATION; import static org.thingsboard.server.transport.coap.CoapTransportService.CURRENT_TIMESTAMP; import static org.thingsboard.server.transport.coap.CoapTransportService.DEVICE_INFO; import static org.thingsboard.server.transport.coap.CoapTransportService.MEASUREMENTS; +import static org.thingsboard.server.transport.coap.efento.utils.CoapEfentoUtils.isBinarySensor; +import static org.thingsboard.server.transport.coap.efento.utils.CoapEfentoUtils.isSensorError; @Slf4j public class CoapEfentoTransportResource extends AbstractCoapTransportResource { @@ -224,7 +222,7 @@ public class CoapEfentoTransportResource extends AbstractCoapTransportResource { } } - private List getEfentoMeasurements(MeasurementsProtos.ProtoMeasurements protoMeasurements, UUID sessionId) { + public List getEfentoMeasurements(MeasurementsProtos.ProtoMeasurements protoMeasurements, UUID sessionId) { String serialNumber = CoapEfentoUtils.convertByteArrayToString(protoMeasurements.getSerialNum().toByteArray()); boolean batteryStatus = protoMeasurements.getBatteryStatus(); int measurementPeriodBase = protoMeasurements.getMeasurementPeriodBase(); @@ -240,17 +238,16 @@ public class CoapEfentoTransportResource extends AbstractCoapTransportResource { Map valuesMap = new TreeMap<>(); for (int channel = 0; channel < channelsList.size(); channel++) { ProtoChannel protoChannel = channelsList.get(channel); + List sampleOffsetsList = protoChannel.getSampleOffsetsList(); + if (CollectionUtils.isEmpty(sampleOffsetsList)) { + log.trace("[{}][{}] sampleOffsetsList list is empty!", sessionId, protoChannel.getType().name()); + continue; + } boolean isBinarySensor = isBinarySensor(protoChannel.getType()); int channelPeriodFactor = (measurementPeriodFactor == 0 ? (isBinarySensor ? 14 : 1) : measurementPeriodFactor); int measurementPeriod = measurementPeriodBase * channelPeriodFactor; long measurementPeriodMillis = TimeUnit.SECONDS.toMillis(measurementPeriod); long startTimestampMillis = TimeUnit.SECONDS.toMillis(protoChannel.getTimestamp()); - List sampleOffsetsList = protoChannel.getSampleOffsetsList(); - - if (CollectionUtils.isEmpty(sampleOffsetsList)) { - log.trace("[{}][{}] sampleOffsetsList list is empty!", sessionId, protoChannel.getType().name()); - continue; - } for (int i = 0; i < sampleOffsetsList.size(); i++) { int sampleOffset = sampleOffsetsList.get(i); @@ -261,17 +258,19 @@ public class CoapEfentoTransportResource extends AbstractCoapTransportResource { JsonObject values; if (isBinarySensor) { + boolean currentIsOk = sampleOffset < 0; + Integer previousSampleOffset = i > 0 ? sampleOffsetsList.get(i - 1) : null; + if (previousSampleOffset != null) { //compare with previous value + boolean previousIsOk = previousSampleOffset < 0; + if (currentIsOk == previousIsOk) { + break; + } + } long sampleOffsetMillis = TimeUnit.SECONDS.toMillis(sampleOffset); - long measurementTimestamp = startTimestampMillis + Math.abs(sampleOffsetMillis) - 1; + long measurementTimestamp = startTimestampMillis + Math.abs(sampleOffsetMillis); values = valuesMap.computeIfAbsent(measurementTimestamp - 1000, k -> CoapEfentoUtils.setDefaultMeasurements(serialNumber, batteryStatus, measurementPeriod, nextTransmissionAtMillis, signal, k)); - Integer previousSampleOffset = i > 0 ? sampleOffsetsList.get(i - 1) : null; - boolean previousValueIsOk = previousSampleOffset != null && previousSampleOffset < 0; - boolean valueIsOk = sampleOffset < 0; - if (binaryValueNotChanged(valueIsOk, previousValueIsOk)) { - break; - } - addBinarySample(protoChannel, valueIsOk, values, channel + 1); + addBinarySample(protoChannel, currentIsOk, values, channel + 1, sessionId); } else { long timestampMillis = startTimestampMillis + i * measurementPeriodMillis; values = valuesMap.computeIfAbsent(timestampMillis, k -> CoapEfentoUtils.setDefaultMeasurements( @@ -290,14 +289,6 @@ public class CoapEfentoTransportResource extends AbstractCoapTransportResource { .collect(Collectors.toList()); } - private boolean isBinarySensor(MeasurementTypeProtos.MeasurementType type) { - return type == MEASUREMENT_TYPE_OK_ALARM || type == MEASUREMENT_TYPE_FLOODING || type == MEASUREMENT_TYPE_OUTPUT_CONTROL; - } - - private boolean isSensorError(int sampleOffset) { - return sampleOffset >= 8355840 && sampleOffset <= 8388607; - } - private void addContinuesSample(ProtoChannel protoChannel, int sampleOffset, JsonObject values, int channelNumber, UUID sessionId) { int startPoint = protoChannel.getStartPoint(); @@ -396,26 +387,23 @@ public class CoapEfentoTransportResource extends AbstractCoapTransportResource { } } - private void addBinarySample(ProtoChannel protoChannel, boolean valueIsOk, JsonObject values, int channel) { + private void addBinarySample(ProtoChannel protoChannel, boolean valueIsOk, JsonObject values, int channel, UUID sessionId) { switch (protoChannel.getType()) { case MEASUREMENT_TYPE_OK_ALARM: values.addProperty("ok_alarm_" + channel, valueIsOk ? "OK" : "ALARM"); + break; case MEASUREMENT_TYPE_FLOODING: values.addProperty("flooding_" + channel, valueIsOk ? "OK" : "WATER_DETECTED"); + break; case MEASUREMENT_TYPE_OUTPUT_CONTROL: values.addProperty("output_control_" + channel, valueIsOk ? "OFF" : "ON"); + break; + default: + log.trace("[{}],[{}] Unsupported binary measurementType! Ignoring.", sessionId, protoChannel.getType().name()); + break; } } - private static boolean binaryValueNotChanged(boolean currentIsOk, boolean previousIsOk) { - boolean isOk = previousIsOk && currentIsOk; - boolean isAlarm = !previousIsOk && !currentIsOk; - if (isOk || isAlarm) { - return true; - } - return false; - } - private EfentoTelemetry getEfentoDeviceInfo(DeviceInfoProtos.ProtoDeviceInfo protoDeviceInfo) { JsonObject values = new JsonObject(); values.addProperty("sw_version", protoDeviceInfo.getSwVersion()); diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/utils/CoapEfentoUtils.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/utils/CoapEfentoUtils.java index d87c97543e..96c1805be3 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/utils/CoapEfentoUtils.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/utils/CoapEfentoUtils.java @@ -16,11 +16,16 @@ package org.thingsboard.server.transport.coap.efento.utils; import com.google.gson.JsonObject; +import org.thingsboard.server.gen.transport.coap.MeasurementTypeProtos.MeasurementType; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; +import static org.thingsboard.server.gen.transport.coap.MeasurementTypeProtos.MeasurementType.MEASUREMENT_TYPE_FLOODING; +import static org.thingsboard.server.gen.transport.coap.MeasurementTypeProtos.MeasurementType.MEASUREMENT_TYPE_OK_ALARM; +import static org.thingsboard.server.gen.transport.coap.MeasurementTypeProtos.MeasurementType.MEASUREMENT_TYPE_OUTPUT_CONTROL; + public class CoapEfentoUtils { public static String convertByteArrayToString(byte[] a) { @@ -50,4 +55,12 @@ public class CoapEfentoUtils { return values; } + public static boolean isBinarySensor(MeasurementType type) { + return type == MEASUREMENT_TYPE_OK_ALARM || type == MEASUREMENT_TYPE_FLOODING || type == MEASUREMENT_TYPE_OUTPUT_CONTROL; + } + + public static boolean isSensorError(int sampleOffset) { + return sampleOffset >= 8355840 && sampleOffset <= 8388607; + } + } diff --git a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/efento/CoapEfentTransportResourceTest.java b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/efento/CoapEfentTransportResourceTest.java new file mode 100644 index 0000000000..5faaaee1d4 --- /dev/null +++ b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/efento/CoapEfentTransportResourceTest.java @@ -0,0 +1,144 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed 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.thingsboard.server.transport.coap.efento; + +import com.google.protobuf.ByteString; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.thingsboard.server.gen.transport.coap.MeasurementTypeProtos; +import org.thingsboard.server.gen.transport.coap.MeasurementsProtos; +import org.thingsboard.server.transport.coap.CoapTransportContext; + +import java.nio.ByteBuffer; +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class CoapEfentTransportResourceTest { + + private static CoapEfentoTransportResource coapEfentoTransportResource; + + @BeforeAll + static void setUp() { + var ctxMock = mock(CoapTransportContext.class); + coapEfentoTransportResource = new CoapEfentoTransportResource(ctxMock, "testName"); + } + + @Test + void checkContinuousSensor() { + long tsInSec = Instant.now().getEpochSecond(); + MeasurementsProtos.ProtoMeasurements measurements = MeasurementsProtos.ProtoMeasurements.newBuilder() + .setSerialNum(integerToByteString(1234)) + .setCloudToken("test_token") + .setMeasurementPeriodBase(180) + .setMeasurementPeriodFactor(1) + .setBatteryStatus(true) + .setSignal(0) + .setNextTransmissionAt(1000) + .setTransferReason(0) + .setHash(0) + .addAllChannels(List.of(MeasurementsProtos.ProtoChannel.newBuilder() + .setType(MeasurementTypeProtos.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) + .setTimestamp(Math.toIntExact(tsInSec)) + .addAllSampleOffsets(List.of(223, 224)) + .build(), + MeasurementsProtos.ProtoChannel.newBuilder() + .setType(MeasurementTypeProtos.MeasurementType.MEASUREMENT_TYPE_HUMIDITY) + .setTimestamp(Math.toIntExact(tsInSec)) + .addAllSampleOffsets(List.of(20, 30)) + .build() + )) + .build(); + List efentoMeasurements = coapEfentoTransportResource.getEfentoMeasurements(measurements, UUID.randomUUID()); + assertThat(efentoMeasurements).hasSize(2); + assertThat(efentoMeasurements.get(0).getTs()).isEqualTo(tsInSec * 1000); + assertThat(efentoMeasurements.get(0).getValues().getAsJsonObject().get("temperature_1").getAsDouble()).isEqualTo(22.3); + assertThat(efentoMeasurements.get(0).getValues().getAsJsonObject().get("humidity_2").getAsDouble()).isEqualTo(20); + assertThat(efentoMeasurements.get(1).getTs()).isEqualTo((tsInSec + 180) * 1000); + assertThat(efentoMeasurements.get(1).getValues().getAsJsonObject().get("temperature_1").getAsDouble()).isEqualTo(22.4); + assertThat(efentoMeasurements.get(1).getValues().getAsJsonObject().get("humidity_2").getAsDouble()).isEqualTo(30); + } + + @Test + void checkBinarySensor() { + long tsInSec = Instant.now().getEpochSecond(); + MeasurementsProtos.ProtoMeasurements measurements = MeasurementsProtos.ProtoMeasurements.newBuilder() + .setSerialNum(integerToByteString(1234)) + .setCloudToken("test_token") + .setMeasurementPeriodBase(180) + .setMeasurementPeriodFactor(1) + .setBatteryStatus(true) + .setSignal(0) + .setNextTransmissionAt(1000) + .setTransferReason(0) + .setHash(0) + .addChannels(MeasurementsProtos.ProtoChannel.newBuilder() + .setType(MeasurementTypeProtos.MeasurementType.MEASUREMENT_TYPE_OK_ALARM) + .setTimestamp(Math.toIntExact(tsInSec)) + .addAllSampleOffsets(List.of(1, 1)) + .build()) + .build(); + List efentoMeasurements = coapEfentoTransportResource.getEfentoMeasurements(measurements, UUID.randomUUID()); + assertThat(efentoMeasurements).hasSize(1); + assertThat(efentoMeasurements.get(0).getTs()).isEqualTo(tsInSec * 1000); + assertThat(efentoMeasurements.get(0).getValues().getAsJsonObject().get("ok_alarm_1").getAsString()).isEqualTo("ALARM"); + } + + @Test + void checkBinarySensorWhenValueIsVarying() { + long tsInSec = Instant.now().getEpochSecond(); + MeasurementsProtos.ProtoMeasurements measurements = MeasurementsProtos.ProtoMeasurements.newBuilder() + .setSerialNum(integerToByteString(1234)) + .setCloudToken("test_token") + .setMeasurementPeriodBase(180) + .setMeasurementPeriodFactor(1) + .setBatteryStatus(true) + .setSignal(0) + .setNextTransmissionAt(1000) + .setTransferReason(0) + .setHash(0) + .addChannels(MeasurementsProtos.ProtoChannel.newBuilder() + .setType(MeasurementTypeProtos.MeasurementType.MEASUREMENT_TYPE_OK_ALARM) + .setTimestamp(Math.toIntExact(tsInSec)) + .addAllSampleOffsets(List.of(1, -10)) + .build()) + .build(); + List efentoMeasurements = coapEfentoTransportResource.getEfentoMeasurements(measurements, UUID.randomUUID()); + assertThat(efentoMeasurements).hasSize(2); + assertThat(efentoMeasurements.get(0).getTs()).isEqualTo(tsInSec * 1000); + assertThat(efentoMeasurements.get(0).getValues().getAsJsonObject().get("ok_alarm_1").getAsString()).isEqualTo("ALARM"); + assertThat(efentoMeasurements.get(1).getTs()).isEqualTo((tsInSec + 9) * 1000); + assertThat(efentoMeasurements.get(1).getValues().getAsJsonObject().get("ok_alarm_1").getAsString()).isEqualTo("OK"); + } + + public static ByteString integerToByteString(Integer intValue) { + // Allocate a ByteBuffer with the size of an integer (4 bytes) + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + + // Put the integer value into the ByteBuffer + buffer.putInt(intValue); + + // Convert the ByteBuffer to a byte array + byte[] byteArray = buffer.array(); + + // Create a ByteString from the byte array + return ByteString.copyFrom(byteArray); + } + +}