Skip to content

Commit

Permalink
Merge pull request #1103 from OSGP/feature/SMHE-1797_udp_alarm_dsmr_2_2
Browse files Browse the repository at this point in the history
  • Loading branch information
kroesctrl authored Oct 25, 2023
2 parents 545ee38 + 1c9d236 commit 21040d5
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* SPDX-FileCopyrightText: Copyright Contributors to the GXF project
*
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensmartgridplatform.adapter.protocol.dlms.infra.networking;

public enum ConnectionProtocol {
TCP,
UDP
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ public class DlmsPushNotificationDecoder {
private static final byte DATA_NOTIFICATION = 0x0F;
private static final byte EVENT_NOTIFICATION_REQUEST = (byte) 0xC2;

public DlmsPushNotification decode(final byte[] message) throws UnrecognizedMessageDataException {
public DlmsPushNotification decode(
final byte[] message, final ConnectionProtocol connectionProtocol)
throws UnrecognizedMessageDataException {
/**
* MX382 alarm examples (in HEX bytes):
*
Expand Down Expand Up @@ -83,7 +85,7 @@ public DlmsPushNotification decode(final byte[] message) throws UnrecognizedMess
pushNotification = alarmDecoder.decodeSmr5alarm(inputStream);
} else if (mx382alarm) {
final Mx382AlarmDecoder alarmDecoder = new Mx382AlarmDecoder();
pushNotification = alarmDecoder.decodeMx382alarm(inputStream);
pushNotification = alarmDecoder.decodeMx382alarm(inputStream, connectionProtocol);
} else {
final Dsmr4AlarmDecoder alarmDecoder = new Dsmr4AlarmDecoder();
pushNotification = alarmDecoder.decodeDsmr4alarm(inputStream);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ protected void decode(
final byte[] byteArray = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(byteArray);

final DlmsPushNotification dlmsPushNotification = this.decoder.decode(byteArray);
final DlmsPushNotification dlmsPushNotification =
this.decoder.decode(byteArray, ConnectionProtocol.TCP);

LOGGER.info("Decoded push notification: {}", dlmsPushNotification);
out.add(dlmsPushNotification);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

package org.opensmartgridplatform.adapter.protocol.dlms.infra.networking;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
Expand All @@ -16,10 +18,7 @@
public class Mx382AlarmDecoder extends AlarmDecoder {

private static final byte EVENT_NOTIFICATION_REQUEST = (byte) 0xC2;
private static final byte[] WPDU_HEADER =
new byte[] {0x00, 0x01, 0x00, 0x67, 0x00, 0x66, 0x00, 0x1d};
private static final byte[] WPDU_HEADER_WITH_DATE =
new byte[] {0x00, 0x01, 0x00, 0x67, 0x00, 0x66, 0x00, 0x2a};
private static final byte[] WPDU_HEADER = new byte[] {0x00, 0x01, 0x00, 0x67, 0x00, 0x66};
private static final byte[] AMM_FORWARDED_ALARM_VERSION_0 =
new byte[] {(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x00};
static final byte[] CLASS_ID_1 = new byte[] {0x00, 0x01};
Expand All @@ -31,9 +30,18 @@ public class Mx382AlarmDecoder extends AlarmDecoder {

private final DlmsPushNotification.Builder builder = new DlmsPushNotification.Builder();

public DlmsPushNotification decodeMx382alarm(final InputStream inputStream)
public DlmsPushNotification decodeMx382alarm(
final InputStream inputStream, final ConnectionProtocol connectionProtocol)
throws UnrecognizedMessageDataException {

if (connectionProtocol == ConnectionProtocol.UDP) {
return this.decodeMx382alarmUdp(inputStream);
}
return this.decodeMx382alarmTcp(inputStream);
}

private DlmsPushNotification decodeMx382alarmTcp(final InputStream inputStream)
throws UnrecognizedMessageDataException {
this.handleWpduHeaderBytes(inputStream, "WPDU-header");
this.checkByte(inputStream, EVENT_NOTIFICATION_REQUEST, "event-notification-request");

Expand All @@ -43,9 +51,25 @@ public DlmsPushNotification decodeMx382alarm(final InputStream inputStream)
this.handleCosemAttributeDescriptor(inputStream);
// Get equipment identifier and add it to the new push notification
this.handleEquipmentIdentifier(inputStream);
// Add alarmbits to the new push notification
this.addTriggerType(PUSH_ALARM_TRIGGER);
this.addAlarm(AlarmTypeDto.LAST_GASP);

return this.builder.build();
}

private DlmsPushNotification decodeMx382alarmUdp(final InputStream inputStream)
throws UnrecognizedMessageDataException {
this.handleWpduHeaderBytes(inputStream, "WPDU-header");
this.checkByte(inputStream, EVENT_NOTIFICATION_REQUEST, "event-notification-request");

// Datetime is read and skipped
this.handleDateTime(inputStream);

// Get equipment identifier and add it to the new push notification
this.readEquipmentIdentifierAtEndOfMessage(inputStream);
// Add alarmbits to the new push notification
this.addAlarm();
this.addTriggerType(PUSH_SMS_TRIGGER);

return this.builder.build();
}
Expand Down Expand Up @@ -77,6 +101,22 @@ private void handleEquipmentIdentifier(final InputStream inputStream)
new String(equipmentIdentifierBytes, StandardCharsets.US_ASCII));
}

private void readEquipmentIdentifierAtEndOfMessage(final InputStream inputStream)
throws UnrecognizedMessageDataException {
try {
this.readBytes(inputStream, inputStream.available() - 17);
// Read the equipment identifier from incoming mx382 message
final byte[] equipmentIdentifierBytes = this.readBytes(inputStream, 17);
final String equipmentIdentifier =
new String(equipmentIdentifierBytes, StandardCharsets.US_ASCII).trim();
if (equipmentIdentifier.matches("[a-zA-Z0-9]+")) {
this.builder.withEquipmentIdentifier(equipmentIdentifier);
}
} catch (final IOException io) {
throw new UnrecognizedMessageDataException(io.getMessage(), io);
}
}

private void handleCosemAttributeDescriptor(final InputStream inputStream)
throws UnrecognizedMessageDataException {
// Check bytes of incoming mx382 message
Expand All @@ -85,10 +125,13 @@ private void handleCosemAttributeDescriptor(final InputStream inputStream)
this.checkByte(inputStream, ATTRIBUTE_ID_2, "attribute-id");
}

private void addAlarm() {
this.builder.withTriggerType(PUSH_ALARM_TRIGGER);
private void addTriggerType(final String triggerType) {
this.builder.withTriggerType(triggerType);
}

private void addAlarm(final AlarmTypeDto alarmType) {
final Set<AlarmTypeDto> alarmSet = new HashSet<>();
alarmSet.add(AlarmTypeDto.LAST_GASP);
alarmSet.add(alarmType);
this.builder.addAlarms(alarmSet);
}

Expand All @@ -110,16 +153,23 @@ private void handleWpduHeaderBytes(final InputStream inputStream, final String n
throws UnrecognizedMessageDataException {
final byte[] readBytes = this.readBytes(inputStream, WPDU_HEADER.length);
this.builder.appendBytes(readBytes);
if (!Arrays.equals(readBytes, WPDU_HEADER)
&& !Arrays.equals(readBytes, WPDU_HEADER_WITH_DATE)) {

if (!Arrays.equals(readBytes, WPDU_HEADER)) {
throw new UnrecognizedMessageDataException(
String.format(
EXPECTED_MESSAGE_TEMPLATE,
toHexString(WPDU_HEADER) + " or " + toHexString(WPDU_HEADER_WITH_DATE),
toHexString(WPDU_HEADER),
name,
toHexString(this.resetAndReadAllBytes(inputStream))));
}

final byte[] apduLengthBytes = this.readBytes(inputStream, 2);
this.builder.appendBytes(apduLengthBytes);
final int adpuLength = new BigInteger(apduLengthBytes).intValue();
if (adpuLength != 29 && adpuLength != 30 && adpuLength != 42 && adpuLength != 43) {
throw new UnrecognizedMessageDataException(
String.format(
"Expected length value of 29,30,42 or 43, but found length of %d", adpuLength));
}
}

private void checkBytes(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Map;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import org.opensmartgridplatform.dlms.DlmsPushNotification;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.MessageEndpoint;
Expand All @@ -28,9 +29,13 @@ public class UdpInboundMessageHandler {
public void handeMessage(final Message message, @Headers final Map<String, Object> headerMap)
throws UnrecognizedMessageDataException {
final byte[] payload = (byte[]) message.getPayload();
log.info("Received UDP message: {}", new String(payload));
log.info(
"Received UDP message: {}, hex-representation: {}",
new String(payload),
Hex.encodeHexString(payload));

final DlmsPushNotification dlmsPushNotification = this.decoder.decode(payload);
final DlmsPushNotification dlmsPushNotification =
this.decoder.decode(payload, ConnectionProtocol.UDP);

final String correlationId = UUID.randomUUID().toString().replace("-", "");
final String deviceIdentification = dlmsPushNotification.getEquipmentIdentifier();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.ThrowableAssert.catchThrowable;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import javax.xml.bind.DatatypeConverter;
import lombok.Builder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.opensmartgridplatform.dlms.DlmsPushNotification;
import org.opensmartgridplatform.dto.valueobjects.smartmetering.AlarmTypeDto;

Expand All @@ -24,25 +27,28 @@ class Mx382AlarmDecoderTest {
private final byte preconfiguredClient = 0x66;
private final byte apduType = (byte) 0xC2;

@Test
void decodeWithoutDate() throws UnrecognizedMessageDataException {
@ParameterizedTest
@ValueSource(strings = {"KA6P005039021110", "KAL70005088116712"})
void decodeWithoutDate(final String deviceIdentification)
throws UnrecognizedMessageDataException {

final byte[] mx382message =
Mx382AlarmMessage.builder()
.equipmentIdentifier(this.equipmentIdentifier)
.equipmentIdentifier(deviceIdentification)
.obiscode(this.obiscode)
.client(this.preconfiguredClient)
.apduType(this.apduType)
.build()
.encode();
final Mx382AlarmDecoder decoder = new Mx382AlarmDecoder();
final InputStream is = new ByteArrayInputStream(mx382message);
final DlmsPushNotification dlmsPushNotification = decoder.decodeMx382alarm(is);
final DlmsPushNotification dlmsPushNotification =
decoder.decodeMx382alarm(is, ConnectionProtocol.TCP);
assertThat(dlmsPushNotification.getAlarms()).hasSize(1);
assertThat(dlmsPushNotification.getAlarms().iterator().next())
.isEqualTo(AlarmTypeDto.LAST_GASP);
assertThat(dlmsPushNotification.getTriggerType()).isEqualTo(AlarmDecoder.PUSH_ALARM_TRIGGER);
assertThat(dlmsPushNotification.getEquipmentIdentifier()).isEqualTo(this.equipmentIdentifier);
assertThat(dlmsPushNotification.getEquipmentIdentifier()).isEqualTo(deviceIdentification);
}

/*
Expand Down Expand Up @@ -71,14 +77,61 @@ void decodeWithDateTimeShouldPass() throws UnrecognizedMessageDataException {

final Mx382AlarmDecoder decoder = new Mx382AlarmDecoder();
final InputStream is = new ByteArrayInputStream(mx382message);
final DlmsPushNotification dlmsPushNotification = decoder.decodeMx382alarm(is);
final DlmsPushNotification dlmsPushNotification =
decoder.decodeMx382alarm(is, ConnectionProtocol.TCP);
assertThat(dlmsPushNotification.getAlarms()).hasSize(1);
assertThat(dlmsPushNotification.getAlarms().iterator().next())
.isEqualTo(AlarmTypeDto.LAST_GASP);
assertThat(dlmsPushNotification.getTriggerType()).isEqualTo(AlarmDecoder.PUSH_ALARM_TRIGGER);
assertThat(dlmsPushNotification.getEquipmentIdentifier()).isEqualTo(this.equipmentIdentifier);
}

@Test
void decodeWakeupAlarm() throws UnrecognizedMessageDataException {
final String deviceIdentification = "KAL7005088116712";
final String message =
"000100670066002ac2090c07e70a19030b122900ff888000010000600101ff0209104b414c37303035303838313136373132";

final byte[] mx382message =
Mx382AlarmMessage.builder() //
.message(message)
.build()
.encode();

final Mx382AlarmDecoder decoder = new Mx382AlarmDecoder();
final InputStream is = new ByteArrayInputStream(mx382message);
final DlmsPushNotification dlmsPushNotification =
decoder.decodeMx382alarm(is, ConnectionProtocol.UDP);
assertThat(dlmsPushNotification.getAlarms()).isEmpty();
assertThat(dlmsPushNotification.getTriggerType()).isEqualTo(AlarmDecoder.PUSH_SMS_TRIGGER);
assertThat(dlmsPushNotification.getEquipmentIdentifier()).isEqualTo(deviceIdentification);
}

@Test
void invalidDeviceIdentificationLength() throws UnrecognizedMessageDataException {
final String deviceIdentification = "KAL123";

final byte[] mx382message =
Mx382AlarmMessage.builder()
.equipmentIdentifier(deviceIdentification)
.obiscode(this.obiscode)
.client(this.preconfiguredClient)
.apduType(this.apduType)
.build()
.encode();
final Mx382AlarmDecoder decoder = new Mx382AlarmDecoder();
final InputStream is = new ByteArrayInputStream(mx382message);
final UnrecognizedMessageDataException exception =
assertThrows(
UnrecognizedMessageDataException.class,
() -> {
decoder.decodeMx382alarm(is, ConnectionProtocol.TCP);
});
assertThat(exception.getMessage())
.contains(
"Data in DLMS Push Notification cannot be decoded. Reason: Expected length value of 29,30,42 or 43, but found length of 19");
}

@Test
void testCheckOnObiscode() {

Expand Down Expand Up @@ -127,7 +180,8 @@ private static void assertDecodingException(final byte[] mx382message) {
final Mx382AlarmDecoder decoder = new Mx382AlarmDecoder();
final InputStream is = new ByteArrayInputStream(mx382message);

final Throwable actual = catchThrowable(() -> decoder.decodeMx382alarm(is));
final Throwable actual =
catchThrowable(() -> decoder.decodeMx382alarm(is, ConnectionProtocol.TCP));
assertThat(actual).isInstanceOf(UnrecognizedMessageDataException.class);
}

Expand Down

0 comments on commit 21040d5

Please sign in to comment.