Java tutorial
/** * Copyright 2015 Smart Society Services B.V. * * 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 */ package org.osgp.adapter.protocol.dlms.domain.commands; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.TimeoutException; import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.openmuc.jdlms.AccessResultCode; import org.openmuc.jdlms.AttributeAddress; import org.openmuc.jdlms.ClientConnection; import org.openmuc.jdlms.GetResult; import org.openmuc.jdlms.datatypes.BitString; import org.openmuc.jdlms.datatypes.CosemDate; import org.openmuc.jdlms.datatypes.CosemDateFormat; import org.openmuc.jdlms.datatypes.CosemDateTime; import org.openmuc.jdlms.datatypes.CosemDateTime.ClockStatus; import org.openmuc.jdlms.datatypes.CosemTime; import org.openmuc.jdlms.datatypes.DataObject; import org.openmuc.jdlms.internal.asn1.cosem.Data.Choices; import org.osgp.adapter.protocol.dlms.domain.entities.DlmsDevice; import org.osgp.adapter.protocol.dlms.exceptions.ProtocolAdapterException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import com.alliander.osgp.dto.valueobjects.smartmetering.CosemObisCode; import com.alliander.osgp.dto.valueobjects.smartmetering.CosemObjectDefinition; import com.alliander.osgp.dto.valueobjects.smartmetering.DlmsMeterValue; import com.alliander.osgp.dto.valueobjects.smartmetering.DlmsUnit; import com.alliander.osgp.dto.valueobjects.smartmetering.MessageType; import com.alliander.osgp.dto.valueobjects.smartmetering.SendDestinationAndMethod; import com.alliander.osgp.dto.valueobjects.smartmetering.TransportServiceType; import com.alliander.osgp.dto.valueobjects.smartmetering.WindowElement; @Service(value = "dlmsHelperService") public class DlmsHelperService { private static final Logger LOGGER = LoggerFactory.getLogger(DlmsHelperService.class); private static final Map<Integer, TransportServiceType> TRANSPORT_SERVICE_TYPE_PER_ENUM_VALUE = new TreeMap<>(); static { TRANSPORT_SERVICE_TYPE_PER_ENUM_VALUE.put(0, TransportServiceType.TCP); TRANSPORT_SERVICE_TYPE_PER_ENUM_VALUE.put(1, TransportServiceType.UDP); TRANSPORT_SERVICE_TYPE_PER_ENUM_VALUE.put(2, TransportServiceType.FTP); TRANSPORT_SERVICE_TYPE_PER_ENUM_VALUE.put(3, TransportServiceType.SMTP); TRANSPORT_SERVICE_TYPE_PER_ENUM_VALUE.put(4, TransportServiceType.SMS); TRANSPORT_SERVICE_TYPE_PER_ENUM_VALUE.put(5, TransportServiceType.HDLC); TRANSPORT_SERVICE_TYPE_PER_ENUM_VALUE.put(6, TransportServiceType.M_BUS); TRANSPORT_SERVICE_TYPE_PER_ENUM_VALUE.put(7, TransportServiceType.ZIG_BEE); } public static final int MILLISECONDS_PER_MINUTE = 60000; /** * get results from the meter and check if the number of results equals the * number of attribute addresses provided. * * @param conn * @param device * @param description * @param params * @return * @throws ProtocolAdapterException */ public List<GetResult> getAndCheck(final ClientConnection conn, final DlmsDevice device, final String description, final AttributeAddress... params) throws ProtocolAdapterException { final List<GetResult> getResults = this.getWithList(conn, device, params); this.checkResultList(getResults, params.length, description); return getResults; } /** * Check if the number of result matches the number of expected results, * when there is only one result the {@link AccessResultCode} of that result * is checked. * * @param getResultList * the list of results to be checked, when null a * nullpointerexception is thrown * @param expectedResults * the number of results expected * @param description * a description that will be used in exceptions thrown, may be * null * @throws ProtocolAdapterException * when the number of results does not match the expected number * or when the one and only result is erroneous. */ public void checkResultList(final List<GetResult> getResultList, final int expectedResults, final String description) throws ProtocolAdapterException { if (getResultList.isEmpty()) { throw new ProtocolAdapterException("No GetResult received: " + description); } else if (getResultList.size() == 1 && AccessResultCode.SUCCESS != getResultList.get(0).resultCode()) { throw new ProtocolAdapterException(getResultList.get(0).resultCode().name()); } if (getResultList.size() != expectedResults) { throw new ProtocolAdapterException("Expected " + expectedResults + " GetResults: " + description + ", got " + getResultList.size()); } } public List<GetResult> getWithList(final ClientConnection conn, final DlmsDevice device, final AttributeAddress... params) throws ProtocolAdapterException { try { if (device.isWithListSupported()) { return conn.get(params); } else { return this.getWithListWorkaround(conn, params); } } catch (final Exception e) { throw new ProtocolAdapterException("Error retrieving values with-list.", e); } } public DataObject getClockDefinition() { return DataObjectDefinitions.getClockDefinition(); } /** * create a dlms meter value, apply the scaler and determine the unit on the * meter. * * @param value * @param dataObject * @return the meter value with dlms unit or null when * {@link #readLong(GetResult, String)} is null * @throws ProtocolAdapterException */ public DlmsMeterValue getScaledMeterValue(final GetResult value, final GetResult scalerUnit, final String description) throws ProtocolAdapterException { return this.getScaledMeterValue(value.resultData(), scalerUnit.resultData(), description); } public DlmsMeterValue getScaledMeterValue(final DataObject value, final DataObject scalerUnitObject, final String description) throws ProtocolAdapterException { LOGGER.debug(this.getDebugInfo(value)); LOGGER.debug(this.getDebugInfo(scalerUnitObject)); final Long rawValue = this.readLong(value, description); if (rawValue == null) { return null; } if (!scalerUnitObject.isComplex()) { throw new ProtocolAdapterException("complex data (structure) expected while retrieving scaler and unit." + this.getDebugInfo(scalerUnitObject)); } final List<DataObject> dataObjects = scalerUnitObject.value(); if (dataObjects.size() != 2) { throw new ProtocolAdapterException( "expected 2 values while retrieving scaler and unit." + this.getDebugInfo(scalerUnitObject)); } final int scaler = this.readLongNotNull(dataObjects.get(0), description).intValue(); final DlmsUnit unit = DlmsUnit .fromDlmsEnum(this.readLongNotNull(dataObjects.get(1), description).intValue()); // determine value BigDecimal scaledValue = BigDecimal.valueOf(rawValue); if (scaler != 0) { scaledValue = scaledValue.multiply(BigDecimal.valueOf(Math.pow(10, scaler))); } return new DlmsMeterValue(scaledValue, unit); } public DataObject getAMRProfileDefinition() { return DataObjectDefinitions.getAMRProfileDefinition(); } /** * Workaround method mimicking a Get-Request with-list for devices that do * not support the actual functionality from DLMS. * * @throws IOException * @throws TimeoutException * * @see #getWithList(ClientConnection, DlmsDevice, AttributeAddress...) */ private List<GetResult> getWithListWorkaround(final ClientConnection conn, final AttributeAddress... params) throws IOException, TimeoutException { final List<GetResult> getResultList = new ArrayList<>(); for (final AttributeAddress param : params) { final List<GetResult> getResultListForParam = conn.get(param); if (getResultListForParam.size() != 1) { throw new AssertionError( "GetResult list contains " + getResultListForParam.size() + " elements instead of 1"); } getResultList.add(getResultListForParam.get(0)); } return getResultList; } private void checkResultCode(final GetResult getResult, final String description) throws ProtocolAdapterException { final AccessResultCode resultCode = getResult.resultCode(); LOGGER.debug(description + " - AccessResultCode: {}", resultCode); if (resultCode != AccessResultCode.SUCCESS) { throw new ProtocolAdapterException( "No success retrieving " + description + ": AccessResultCode = " + resultCode); } } public Long readLong(final GetResult getResult, final String description) throws ProtocolAdapterException { this.checkResultCode(getResult, description); return this.readLong(getResult.resultData(), description); } public Long readLongNotNull(final GetResult getResult, final String description) throws ProtocolAdapterException { this.checkResultCode(getResult, description); return this.readLongNotNull(getResult.resultData(), description); } public Long readLongNotNull(final DataObject resultData, final String description) throws ProtocolAdapterException { final Long result = this.readLong(resultData, description); if (result == null) { throw new ProtocolAdapterException(String.format("Unexpected null value for %s,", description)); } return result; } public DataObject readDataObject(final GetResult getResult, final String description) throws ProtocolAdapterException { this.checkResultCode(getResult, description); return getResult.resultData(); } public Long readLong(final DataObject resultData, final String description) throws ProtocolAdapterException { final Number number = this.readNumber(resultData, description); if (number == null) { return null; } return number.longValue(); } public String readString(final DataObject resultData, final String description) throws ProtocolAdapterException { final byte[] bytes = this.readByteArray(resultData, description, "String"); if (bytes == null) { return null; } return new String(bytes, StandardCharsets.UTF_8); } public com.alliander.osgp.dto.valueobjects.smartmetering.CosemDateTime readDateTime(final GetResult getResult, final String description) throws ProtocolAdapterException { this.checkResultCode(getResult, description); return this.readDateTime(getResult.resultData(), description); } public com.alliander.osgp.dto.valueobjects.smartmetering.CosemDateTime readDateTime(final DataObject resultData, final String description) throws ProtocolAdapterException { this.logDebugResultData(resultData, description); if (resultData == null || resultData.isNull()) { return null; } if (resultData.isByteArray()) { return this.fromDateTimeValue((byte[]) resultData.value()); } else if (resultData.isCosemDateFormat()) { return this.fromDateTimeValue(((CosemDateTime) resultData.value()).encode()); } else { LOGGER.error("Unexpected ResultData for DateTime value: {}", this.getDebugInfo(resultData)); throw new ProtocolAdapterException( "Expected ResultData of ByteArray or CosemDateFormat, got: " + resultData.choiceIndex()); } } public com.alliander.osgp.dto.valueobjects.smartmetering.CosemDateTime readCosemDateTime( final DataObject resultData, final String description) throws ProtocolAdapterException { this.logDebugResultData(resultData, description); if (resultData == null || resultData.isNull()) { return null; } CosemDateTime jdlmsCosemDateTime = null; if (resultData.isByteArray()) { jdlmsCosemDateTime = CosemDateTime.decode((byte[]) resultData.value()); } else if (resultData.isCosemDateFormat()) { jdlmsCosemDateTime = (CosemDateTime) resultData.value(); } else { this.logAndThrowExceptionForUnexpectedResultData(resultData, "ByteArray or CosemDateFormat"); } return this.getDtoDateTimeForJdlmsDateTime(jdlmsCosemDateTime); } private com.alliander.osgp.dto.valueobjects.smartmetering.CosemDateTime getDtoDateTimeForJdlmsDateTime( final CosemDateTime jdlmsCosemDateTime) { if (jdlmsCosemDateTime == null) { return null; } final int year = jdlmsCosemDateTime.valueFor(CosemDateFormat.Field.YEAR); // valueFor makes the month start at 0, cosemdate month starts at 1 final int month = jdlmsCosemDateTime.valueFor(CosemDateFormat.Field.MONTH) + 1; final int dayOfMonth = jdlmsCosemDateTime.valueFor(CosemDateFormat.Field.DAY_OF_MONTH); final int dayOfWeek = jdlmsCosemDateTime.valueFor(CosemDateFormat.Field.DAY_OF_WEEK); final com.alliander.osgp.dto.valueobjects.smartmetering.CosemDate date = new com.alliander.osgp.dto.valueobjects.smartmetering.CosemDate( year, month, dayOfMonth, dayOfWeek); final int hour = jdlmsCosemDateTime.valueFor(CosemDateFormat.Field.HOUR); final int minute = jdlmsCosemDateTime.valueFor(CosemDateFormat.Field.MINUTE); final int second = jdlmsCosemDateTime.valueFor(CosemDateFormat.Field.SECOND); final int hundredths = jdlmsCosemDateTime.valueFor(CosemDateFormat.Field.HUNDREDTHS); final com.alliander.osgp.dto.valueobjects.smartmetering.CosemTime time = new com.alliander.osgp.dto.valueobjects.smartmetering.CosemTime( hour, minute, second, hundredths); final int deviation = jdlmsCosemDateTime.valueFor(CosemDateFormat.Field.DEVIATION); final int clockStatusValue = jdlmsCosemDateTime.valueFor(CosemDateFormat.Field.CLOCK_STATUS); final com.alliander.osgp.dto.valueobjects.smartmetering.ClockStatus clockStatus = new com.alliander.osgp.dto.valueobjects.smartmetering.ClockStatus( clockStatusValue); return new com.alliander.osgp.dto.valueobjects.smartmetering.CosemDateTime(date, time, deviation, clockStatus); } public com.alliander.osgp.dto.valueobjects.smartmetering.CosemDateTime convertDataObjectToDateTime( final DataObject object) throws ProtocolAdapterException { com.alliander.osgp.dto.valueobjects.smartmetering.CosemDateTime dateTime = null; if (object.isByteArray()) { dateTime = this.fromDateTimeValue((byte[]) object.value()); } else if (object.isCosemDateFormat()) { dateTime = this.fromDateTimeValue(((CosemDateTime) object.value()).encode()); } else { this.logAndThrowExceptionForUnexpectedResultData(object, "ByteArray or CosemDateFormat"); } return dateTime; } public com.alliander.osgp.dto.valueobjects.smartmetering.CosemDateTime fromDateTimeValue( final byte[] dateTimeValue) throws ProtocolAdapterException { final ByteBuffer bb = ByteBuffer.wrap(dateTimeValue); final int year = bb.getShort() & 0xFFFF; final int monthOfYear = bb.get() & 0xFF; final int dayOfMonth = bb.get() & 0xFF; final int dayOfWeek = bb.get() & 0xFF; final int hourOfDay = bb.get() & 0xFF; final int minuteOfHour = bb.get() & 0xFF; final int secondOfMinute = bb.get() & 0xFF; final int hundredthsOfSecond = bb.get() & 0xFF; final int deviation = bb.getShort(); final byte clockStatusValue = bb.get(); final com.alliander.osgp.dto.valueobjects.smartmetering.CosemDate date = new com.alliander.osgp.dto.valueobjects.smartmetering.CosemDate( year, monthOfYear, dayOfMonth, dayOfWeek); final com.alliander.osgp.dto.valueobjects.smartmetering.CosemTime time = new com.alliander.osgp.dto.valueobjects.smartmetering.CosemTime( hourOfDay, minuteOfHour, secondOfMinute, hundredthsOfSecond); final com.alliander.osgp.dto.valueobjects.smartmetering.ClockStatus clockStatus = new com.alliander.osgp.dto.valueobjects.smartmetering.ClockStatus( clockStatusValue); return new com.alliander.osgp.dto.valueobjects.smartmetering.CosemDateTime(date, time, deviation, clockStatus); } public DataObject asDataObject(final DateTime dateTime) { final CosemDate cosemDate = new CosemDate(dateTime.getYear(), dateTime.getMonthOfYear(), dateTime.getDayOfMonth()); final CosemTime cosemTime = new CosemTime(dateTime.getHourOfDay(), dateTime.getMinuteOfHour(), dateTime.getSecondOfMinute(), dateTime.getMillisOfSecond() / 10); final int deviation = -(dateTime.getZone().getOffset(dateTime.getMillis()) / MILLISECONDS_PER_MINUTE); final ClockStatus[] clockStatusBits; if (dateTime.getZone().isStandardOffset(dateTime.getMillis())) { clockStatusBits = new ClockStatus[0]; } else { clockStatusBits = new ClockStatus[1]; clockStatusBits[0] = ClockStatus.DAYLIGHT_SAVING_ACTIVE; } final CosemDateTime cosemDateTime = new CosemDateTime(cosemDate, cosemTime, deviation, clockStatusBits); return DataObject.newDateTimeData(cosemDateTime); } public DataObject asDataObject(final com.alliander.osgp.dto.valueobjects.smartmetering.CosemDate date) { final CosemDate cosemDate = new CosemDate(date.getYear(), date.getMonth(), date.getDayOfMonth(), date.getDayOfWeek()); return DataObject.newDateData(cosemDate); } public List<CosemObjectDefinition> readListOfObjectDefinition(final GetResult getResult, final String description) throws ProtocolAdapterException { this.checkResultCode(getResult, description); return this.readListOfObjectDefinition(getResult.resultData(), description); } public List<CosemObjectDefinition> readListOfObjectDefinition(final DataObject resultData, final String description) throws ProtocolAdapterException { final List<DataObject> listOfObjectDefinition = this.readList(resultData, description); if (listOfObjectDefinition == null) { return Collections.emptyList(); } final List<CosemObjectDefinition> objectDefinitionList = new ArrayList<>(); for (final DataObject objectDefinitionObject : listOfObjectDefinition) { objectDefinitionList.add( this.readObjectDefinition(objectDefinitionObject, "Object Definition from " + description)); } return objectDefinitionList; } public CosemObjectDefinition readObjectDefinition(final DataObject resultData, final String description) throws ProtocolAdapterException { final List<DataObject> objectDefinitionElements = this.readList(resultData, description); if (objectDefinitionElements == null) { return null; } if (objectDefinitionElements.size() != 4) { LOGGER.error("Unexpected ResultData for Object Definition value: {}", this.getDebugInfo(resultData)); throw new ProtocolAdapterException("Expected list for Object Definition to contain 4 elements, got: " + objectDefinitionElements.size()); } final Long classId = this.readLongNotNull(objectDefinitionElements.get(0), "Class ID from " + description); final CosemObisCode logicalName = this.readLogicalName(objectDefinitionElements.get(1), "Logical Name from " + description); final Long attributeIndex = this.readLongNotNull(objectDefinitionElements.get(2), "Attribute Index from " + description); final Long dataIndex = this.readLongNotNull(objectDefinitionElements.get(0), "Data Index from " + description); return new CosemObjectDefinition(classId.intValue(), logicalName, attributeIndex.intValue(), dataIndex.intValue()); } public CosemObisCode readLogicalName(final DataObject resultData, final String description) throws ProtocolAdapterException { final byte[] bytes = this.readByteArray(resultData, description, "Logical Name"); if (bytes == null) { return null; } return new CosemObisCode(bytes); } public SendDestinationAndMethod readSendDestinationAndMethod(final GetResult getResult, final String description) throws ProtocolAdapterException { this.checkResultCode(getResult, description); return this.readSendDestinationAndMethod(getResult.resultData(), description); } public SendDestinationAndMethod readSendDestinationAndMethod(final DataObject resultData, final String description) throws ProtocolAdapterException { final List<DataObject> sendDestinationAndMethodElements = this.readList(resultData, description); if (sendDestinationAndMethodElements == null) { return null; } final TransportServiceType transportService = this.readTransportServiceType( sendDestinationAndMethodElements.get(0), "Transport Service from " + description); final String destination = this.readString(sendDestinationAndMethodElements.get(1), "Destination from " + description); final MessageType message = this.readMessageType(sendDestinationAndMethodElements.get(2), "Message from " + description); return new SendDestinationAndMethod(transportService, destination, message); } public TransportServiceType readTransportServiceType(final DataObject resultData, final String description) throws ProtocolAdapterException { final Number number = this.readNumber(resultData, description, "Enum"); if (number == null) { return null; } final int enumValue = number.intValue(); final TransportServiceType transportService = this.getTransportServiceTypeForEnumValue(enumValue); if (transportService == null) { LOGGER.error("Unexpected Enum value for TransportServiceType: {}", enumValue); throw new ProtocolAdapterException("Unknown Enum value for TransportServiceType: " + enumValue); } return transportService; } private TransportServiceType getTransportServiceTypeForEnumValue(final int enumValue) { if (enumValue >= 200 && enumValue <= 255) { return TransportServiceType.MANUFACTURER_SPECIFIC; } return TRANSPORT_SERVICE_TYPE_PER_ENUM_VALUE.get(enumValue); } public MessageType readMessageType(final DataObject resultData, final String description) throws ProtocolAdapterException { final Number number = this.readNumber(resultData, description, "Enum"); if (number == null) { return null; } final MessageType message; final int enumValue = number.intValue(); switch (enumValue) { case 0: message = MessageType.A_XDR_ENCODED_X_DLMS_APDU; break; case 1: message = MessageType.XML_ENCODED_X_DLMS_APDU; break; default: if (enumValue < 128 || enumValue > 255) { LOGGER.error("Unexpected Enum value for MessageType: {}", enumValue); throw new ProtocolAdapterException("Unknown Enum value for MessageType: " + enumValue); } message = MessageType.MANUFACTURER_SPECIFIC; } return message; } public List<WindowElement> readListOfWindowElement(final GetResult getResult, final String description) throws ProtocolAdapterException { this.checkResultCode(getResult, description); return this.readListOfWindowElement(getResult.resultData(), description); } public List<WindowElement> readListOfWindowElement(final DataObject resultData, final String description) throws ProtocolAdapterException { final List<DataObject> listOfWindowElement = this.readList(resultData, description); if (listOfWindowElement == null) { return Collections.emptyList(); } final List<WindowElement> windowElementList = new ArrayList<>(); for (final DataObject windowElementObject : listOfWindowElement) { windowElementList .add(this.readWindowElement(windowElementObject, "Window Element from " + description)); } return windowElementList; } public WindowElement readWindowElement(final DataObject resultData, final String description) throws ProtocolAdapterException { final List<DataObject> windowElementElements = this.readList(resultData, description); if (windowElementElements == null) { return null; } return this.buildWindowElementFromDataObjects(windowElementElements, description); } private WindowElement buildWindowElementFromDataObjects(final List<DataObject> elements, final String description) throws ProtocolAdapterException { if (elements.size() != 2) { LOGGER.error("Unexpected number of ResultData elements for WindowElement value: {}", elements.size()); throw new ProtocolAdapterException( "Expected list for WindowElement to contain 2 elements, got: " + elements.size()); } final com.alliander.osgp.dto.valueobjects.smartmetering.CosemDateTime startTime = this .readCosemDateTime(elements.get(0), "Start Time from " + description); final com.alliander.osgp.dto.valueobjects.smartmetering.CosemDateTime endTime = this .readCosemDateTime(elements.get(1), "End Time from " + description); return new WindowElement(startTime, endTime); } public String getDebugInfo(final DataObject dataObject) { if (dataObject == null) { return null; } final String dataType = getDataType(dataObject); final String objectText = this.getObjectTextForDebugInfo(dataObject); final String choiceText = this.getChoiceTextForDebugInfo(dataObject); final String rawValueClass = this.getRawValueClassForDebugInfo(dataObject); return "DataObject: Choice=" + choiceText + ", ResultData is" + dataType + ", value=[" + rawValueClass + "]: " + objectText; } private String getObjectTextForDebugInfo(final DataObject dataObject) { final String objectText; if (dataObject.isComplex()) { if (dataObject.value() instanceof List) { final StringBuilder builder = new StringBuilder(); builder.append("["); builder.append(System.lineSeparator()); this.appendItemValues(dataObject, builder); builder.append("]"); builder.append(System.lineSeparator()); objectText = builder.toString(); } else { objectText = String.valueOf(dataObject.rawValue()); } } else if (dataObject.isByteArray()) { objectText = this.getDebugInfoByteArray((byte[]) dataObject.value()); } else if (dataObject.isBitString()) { objectText = this.getDebugInfoBitStringBytes(((BitString) dataObject.value()).bitString()); } else if (dataObject.isCosemDateFormat() && dataObject.value() instanceof CosemDateTime) { objectText = this.getDebugInfoDateTimeBytes(((CosemDateTime) dataObject.value()).encode()); } else { objectText = String.valueOf(dataObject.rawValue()); } return objectText; } private String getChoiceTextForDebugInfo(final DataObject dataObject) { final Choices choiceIndex = dataObject.choiceIndex(); if (choiceIndex == null) { return "null"; } return choiceIndex.name() + "(" + choiceIndex.getValue() + ")"; } private String getRawValueClassForDebugInfo(final DataObject dataObject) { final Object rawValue = dataObject.rawValue(); if (rawValue == null) { return "null"; } return rawValue.getClass().getName(); } private void appendItemValues(final DataObject dataObject, final StringBuilder builder) { for (final Object obj : (List<?>) dataObject.value()) { builder.append("\t"); if (obj instanceof DataObject) { builder.append(this.getDebugInfo((DataObject) obj)); } else { builder.append(String.valueOf(obj)); } builder.append(System.lineSeparator()); } } private static String getDataType(final DataObject dataObject) { String dataType; if (dataObject.isBitString()) { dataType = "BitString"; } else if (dataObject.isBoolean()) { dataType = "Boolean"; } else if (dataObject.isByteArray()) { dataType = "ByteArray"; } else if (dataObject.isComplex()) { dataType = "Complex"; } else if (dataObject.isCosemDateFormat()) { dataType = "CosemDateFormat"; } else if (dataObject.isNull()) { dataType = "Null"; } else if (dataObject.isNumber()) { dataType = "Number"; } else { dataType = "?"; } return dataType; } public String getDebugInfoByteArray(final byte[] bytes) { /* * The guessing of the object type by byte length may turn out to be * ambiguous at some time. If this occurs the debug info will have to be * determined in some more robust way. Until now this appears to work OK * for debugging purposes. */ if (bytes.length == 6) { return this.getDebugInfoLogicalName(bytes); } else if (bytes.length == 12) { return this.getDebugInfoDateTimeBytes(bytes); } final StringBuilder sb = new StringBuilder(); // list the unsigned values of the bytes for (final byte b : bytes) { sb.append(b & 0xFF).append(", "); } if (sb.length() > 0) { // remove the last ", " sb.setLength(sb.length() - 2); } return "bytes[" + sb.toString() + "]"; } public String getDebugInfoLogicalName(final byte[] logicalNameValue) { if (logicalNameValue.length != 6) { throw new IllegalArgumentException( "LogicalName values should be 6 bytes long: " + logicalNameValue.length); } final StringBuilder sb = new StringBuilder(); sb.append("logical name: ").append(logicalNameValue[0] & 0xFF).append('-') .append(logicalNameValue[1] & 0xFF).append(':').append(logicalNameValue[2] & 0xFF).append('.') .append(logicalNameValue[3] & 0xFF).append('.').append(logicalNameValue[4] & 0xFF).append('.') .append(logicalNameValue[5] & 0xFF); return sb.toString(); } public String getDebugInfoDateTimeBytes(final byte[] dateTimeValue) { if (dateTimeValue.length != 12) { throw new IllegalArgumentException("DateTime values should be 12 bytes long: " + dateTimeValue.length); } final StringBuilder sb = new StringBuilder(); final ByteBuffer bb = ByteBuffer.wrap(dateTimeValue); final int year = bb.getShort(); final int monthOfYear = bb.get(); final int dayOfMonth = bb.get(); final int dayOfWeek = bb.get(); final int hourOfDay = bb.get(); final int minuteOfHour = bb.get(); final int secondOfMinute = bb.get(); final int hundredthsOfSecond = bb.get(); final int deviation = bb.getShort(); final int clockStatus = bb.get(); sb.append("year=").append(year).append(", month=").append(monthOfYear).append(", day=").append(dayOfMonth) .append(", weekday=").append(dayOfWeek).append(", hour=").append(hourOfDay).append(", minute=") .append(minuteOfHour).append(", second=").append(secondOfMinute).append(", hundredths=") .append(hundredthsOfSecond).append(", deviation=").append(deviation).append(", clockstatus=") .append(clockStatus); return sb.toString(); } public String getDebugInfoBitStringBytes(final byte[] bitStringValue) { final BigInteger bigValue = this.byteArrayToBigInteger(bitStringValue); final String stringValue = this.byteArrayToString(bitStringValue); final StringBuilder sb = new StringBuilder(); sb.append("number of bytes=").append(bitStringValue.length).append(", value=").append(bigValue) .append(", bits=").append(stringValue); return sb.toString(); } private String byteArrayToString(final byte[] bitStringValue) { if (bitStringValue == null || bitStringValue.length == 0) { return null; } final StringBuilder sb = new StringBuilder(); for (final byte element : bitStringValue) { sb.append(StringUtils.leftPad(Integer.toBinaryString(element & 0xFF), 8, "0")); sb.append(" "); } return sb.toString(); } private BigInteger byteArrayToBigInteger(final byte[] bitStringValue) { if (bitStringValue == null || bitStringValue.length == 0) { return null; } BigInteger value = BigInteger.valueOf(0); for (final byte element : bitStringValue) { value = value.shiftLeft(8); value = value.add(BigInteger.valueOf(element & 0xFF)); } return value; } private Number readNumber(final DataObject resultData, final String description) throws ProtocolAdapterException { return this.readNumber(resultData, description, "Number"); } private Number readNumber(final DataObject resultData, final String description, final String interpretation) throws ProtocolAdapterException { this.logDebugResultData(resultData, description); if (resultData == null || resultData.isNull()) { return null; } final Object resultValue = resultData.value(); if (!resultData.isNumber() || !(resultValue instanceof Number)) { this.logAndThrowExceptionForUnexpectedResultData(resultData, interpretation); } return (Number) resultValue; } private byte[] readByteArray(final DataObject resultData, final String description, final String interpretation) throws ProtocolAdapterException { this.logDebugResultData(resultData, description); if (resultData == null || resultData.isNull()) { return new byte[0]; } final Object resultValue = resultData.value(); if (!resultData.isByteArray() || !(resultValue instanceof byte[])) { this.logAndThrowExceptionForUnexpectedResultData(resultData, "byte array to be interpreted as " + interpretation); } return (byte[]) resultValue; } @SuppressWarnings("unchecked") private List<DataObject> readList(final DataObject resultData, final String description) throws ProtocolAdapterException { this.logDebugResultData(resultData, description); if (resultData == null || resultData.isNull()) { return Collections.emptyList(); } final Object resultValue = resultData.value(); if (!resultData.isComplex() || !(resultValue instanceof List)) { this.logAndThrowExceptionForUnexpectedResultData(resultData, "List"); } return (List<DataObject>) resultValue; } private void logDebugResultData(final DataObject resultData, final String description) { LOGGER.debug(description + " - ResultData: {}", this.getDebugInfo(resultData)); } private void logAndThrowExceptionForUnexpectedResultData(final DataObject resultData, final String expectedType) throws ProtocolAdapterException { LOGGER.error("Unexpected ResultData for {} value: {}", expectedType, this.getDebugInfo(resultData)); final String resultDataType = resultData.value() == null ? "null" : resultData.value().getClass().getName(); throw new ProtocolAdapterException("Expected ResultData of " + expectedType + ", got: " + resultData.choiceIndex() + ", value type: " + resultDataType); } }