package org.openmuc.framework.driver.iec60870; import java.nio.ByteBuffer; import javax.naming.ConfigurationException; import org.openmuc.framework.data.BooleanValue; import org.openmuc.framework.data.ByteArrayValue; import org.openmuc.framework.data.DoubleValue; import org.openmuc.framework.data.Flag; import org.openmuc.framework.data.IntValue; import org.openmuc.framework.data.Record; import org.openmuc.framework.driver.iec60870.settings.ChannelAddress; import org.openmuc.j60870.ASdu; import org.openmuc.j60870.IeBinaryCounterReading; import org.openmuc.j60870.IeBinaryStateInformation; import org.openmuc.j60870.IeDoublePointWithQuality; import org.openmuc.j60870.IeNormalizedValue; import org.openmuc.j60870.IeProtectionQuality; import org.openmuc.j60870.IeQuality; import org.openmuc.j60870.IeScaledValue; import org.openmuc.j60870.IeShortFloat; import org.openmuc.j60870.IeSinglePointWithQuality; import org.openmuc.j60870.InformationElement; import org.openmuc.j60870.InformationObject; import org.openmuc.j60870.TypeId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class IEC60870DataHandling { private final static Logger logger = LoggerFactory.getLogger(IEC60870DataHandling.class); static Record handleInformationObject(ASdu aSdu, long timestamp, ChannelAddress channelAddress, InformationObject informationObject) { Record record; if (channelAddress.multiple() > 1) { record = handleMultipleElementObjects(aSdu, timestamp, channelAddress, informationObject); } else { InformationElement[] informationElements; try { informationElements = handleSingleElementObject(aSdu, timestamp, channelAddress, informationObject); } catch (ConfigurationException e) { logger.warn(e.getMessage()); return new Record(Flag.DRIVER_ERROR_CHANNEL_ADDRESS_SYNTAX_INVALID); } if (informationElements != null) { record = IEC60870DataHandling.creatNewRecord(informationElements, aSdu.getTypeIdentification(), channelAddress, timestamp); } else { record = new Record(Flag.UNKNOWN_ERROR); } } return record; } private static Record creatNewRecord(InformationElement[] informationElements, TypeId typeIdentification, ChannelAddress channelAddress, long timestamp) { if (!channelAddress.dataType().equals("v")) { return getQualityDescriptorAsRecord(channelAddress.dataType(), informationElements, typeIdentification, timestamp); } else { switch (typeIdentification) { case M_ME_NA_1: case M_ME_TA_1: case M_ME_ND_1: case M_ME_TD_1: case C_SE_NA_1: case P_ME_NA_1: IeNormalizedValue normalizedValue = (IeNormalizedValue) informationElements[0]; // TODO: is 0 correct? return new Record(new DoubleValue(normalizedValue.getValue() / 32768.), timestamp); case M_ME_NB_1: case M_ME_TB_1: case M_ME_TE_1: case C_SE_NB_1: case P_ME_NB_1: IeScaledValue scaledValue = (IeScaledValue) informationElements[0];// TODO: is 0 correct? return new Record(new IntValue(scaledValue.getValue()), timestamp); // test this case M_ME_NC_1: case M_ME_TC_1: case M_ME_TF_1: case C_SE_NC_1: case P_ME_NC_1: IeShortFloat shortFloat = (IeShortFloat) informationElements[0]; return new Record(new DoubleValue(shortFloat.getValue()), timestamp); case M_BO_NA_1: case M_BO_TA_1: case M_BO_TB_1: IeBinaryStateInformation binaryStateInformation = (IeBinaryStateInformation) informationElements[0]; return new Record( new ByteArrayValue(ByteBuffer.allocate(4).putInt(binaryStateInformation.getValue()).array()), timestamp); case M_SP_NA_1: case M_SP_TA_1: case M_PS_NA_1: case M_SP_TB_1: case M_ST_NA_1: // TODO: test this!!! It's not really a SinglePointInformation case M_ST_TA_1: // TODO: test this!!! It's not really a SinglePointInformation case M_ST_TB_1: // TODO: test this!!! It's not really a SinglePointInformation IeSinglePointWithQuality singlePointWithQuality = (IeSinglePointWithQuality) informationElements[0]; return new Record(new BooleanValue(singlePointWithQuality.isOn()), timestamp); case M_DP_NA_1: case M_DP_TA_1: case M_DP_TB_1: IeDoublePointWithQuality doublePointWithQuality = (IeDoublePointWithQuality) informationElements[0]; return new Record(new IntValue(doublePointWithQuality.getDoublePointInformation().ordinal()), timestamp); // TODO: check this solution. Is Enum to int correct? case M_IT_NA_1: case M_IT_TA_1: case M_IT_TB_1: IeBinaryCounterReading binaryCounterReading = (IeBinaryCounterReading) informationElements[0]; // TODO: change to String because of more values e.g. getSequenceNumber, isCarry, ... ? return new Record(new IntValue(binaryCounterReading.getCounterReading()), timestamp); default: logger.debug("Not supported Type Identification."); return new Record(Flag.DRIVER_ERROR_CHANNEL_VALUE_TYPE_CONVERSION_EXCEPTION); } } } private static Record getQualityDescriptorAsRecord(String dataType, InformationElement[] informationElements, TypeId typeIdentification, long timestamp) { Record record = null; InformationElement informationElement = informationElements[informationElements.length - 1]; if (typeIdentification.getId() <= 14 || typeIdentification.getId() == 20 || typeIdentification.getId() >= 30 && typeIdentification.getId() <= 36) { record = quality(dataType, timestamp, informationElement); } else if (typeIdentification.getId() >= 15 && typeIdentification.getId() <= 16 || typeIdentification.getId() == 37) { record = binaryCounterReading(dataType, timestamp, informationElement); } else if (typeIdentification.getId() >= 17 && typeIdentification.getId() <= 19 || typeIdentification.getId() >= 38 && typeIdentification.getId() <= 40) { record = protectionQuality(dataType, timestamp, informationElement); } if (record == null) { logger.debug("Not supported Quality Descriptor."); record = new Record(Flag.DRIVER_ERROR_CHANNEL_VALUE_TYPE_CONVERSION_EXCEPTION); } return record; } private static Record quality(String dataType, long timestamp, InformationElement informationElement) { IeQuality quality = (IeQuality) informationElement; Record record = null; switch (dataType) { // iv nt sb bl case "iv": record = new Record(new BooleanValue(quality.isInvalid()), timestamp); break; case "sb": record = new Record(new BooleanValue(quality.isSubstituted()), timestamp); break; case "nt": record = new Record(new BooleanValue(quality.isNotTopical()), timestamp); break; case "bl": record = new Record(new BooleanValue(quality.isBlocked()), timestamp); break; default: } return record; } private static Record protectionQuality(String dataType, long timestamp, InformationElement informationElement) { IeProtectionQuality quality = (IeProtectionQuality) informationElement; Record record = null; switch (dataType) { // iv nt sb bl ei case "iv": record = new Record(new BooleanValue(quality.isInvalid()), timestamp); break; case "sb": record = new Record(new BooleanValue(quality.isSubstituted()), timestamp); break; case "nt": record = new Record(new BooleanValue(quality.isNotTopical()), timestamp); break; case "bl": record = new Record(new BooleanValue(quality.isBlocked()), timestamp); break; case "ei": record = new Record(new BooleanValue(quality.isElapsedTimeInvalid()), timestamp); break; default: } return record; } private static Record binaryCounterReading(String dataType, long timestamp, InformationElement informationElement) { IeBinaryCounterReading quality = (IeBinaryCounterReading) informationElement; Record record = null; switch (dataType) {// iv ca cy case "iv": record = new Record(new BooleanValue(quality.isInvalid()), timestamp); break; case "ca": record = new Record(new BooleanValue(quality.isCounterAdjusted()), timestamp); break; case "cy": record = new Record(new BooleanValue(quality.isCarry()), timestamp); break; default: } return record; } private static Record handleMultipleElementObjects(ASdu aSdu, long timestamp, ChannelAddress channelAddress, InformationObject informationObject) { int singleSize = sizeOfType(aSdu.getTypeIdentification()); int arrayLength = singleSize * channelAddress.multiple(); ByteBuffer byteBuffer = ByteBuffer.allocate(arrayLength); for (int i = 0; i < channelAddress.multiple(); ++i) { InformationElement[] informationElements; try { informationElements = handleSingleElementObject(aSdu, timestamp, channelAddress, informationObject); IeBinaryStateInformation binaryStateInformation = (IeBinaryStateInformation) informationElements[0]; byteBuffer.putInt(binaryStateInformation.getValue()); } catch (ConfigurationException e) { logger.warn(e.getMessage()); return new Record(Flag.DRIVER_ERROR_CHANNEL_ADDRESS_SYNTAX_INVALID); } } byte[] value = byteBuffer.array(); Record record = new Record(new ByteArrayValue(value), timestamp); return record; } private static InformationElement[] handleSingleElementObject(ASdu aSdu, long timestamp, ChannelAddress channelAddress, InformationObject informationObject) throws ConfigurationException { InformationElement[] informationElements = null; if (channelAddress.ioa() == informationObject.getInformationObjectAddress()) { if (aSdu.isSequenceOfElements()) { informationElements = sequenceOfElements(aSdu, timestamp, channelAddress, informationObject); } else { informationElements = informationObject.getInformationElements()[0]; } } return informationElements; } private static InformationElement[] sequenceOfElements(ASdu aSdu, long timestamp, ChannelAddress channelAddress, InformationObject informationObject) throws ConfigurationException { InformationElement[] informationElements = null; if (channelAddress.index() >= -1) { informationElements = informationObject.getInformationElements()[channelAddress.index()]; } else { throw new ConfigurationException( "Got ASdu with same TypeId, Common Address and IOA, but it is a Sequence Of Elements. For this index in ChannelAddress is needed."); } return informationElements; } private static int sizeOfType(TypeId typeIdentification) { int size = -1; // size in byte; switch (typeIdentification) { case M_BO_NA_1: case M_BO_TA_1: case M_BO_TB_1: size = 4; break; default: logger.debug( "Not able to set Data Type " + typeIdentification.toString() + " as multiple IOAs or Indices."); break; } return size; } }