| /********************************************************************************
|
| * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
|
| *
|
| * See the NOTICE file(s) distributed with this work for additional
|
| * information regarding copyright ownership.
|
| *
|
| * This program and the accompanying materials are made available under the
|
| * terms of the Eclipse Public License v. 2.0 which is available at
|
| * http://www.eclipse.org/legal/epl-2.0.
|
| *
|
| * SPDX-License-Identifier: EPL-2.0
|
| *
|
| ********************************************************************************/
|
| |
| |
| package org.eclipse.mdm.openatfx.mdf.mdf4; |
| |
| import java.io.IOException; |
| import java.math.BigInteger; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.nio.channels.SeekableByteChannel; |
| import java.util.Arrays; |
| |
| /** |
| * <p> |
| * THE CHANNEL CONVERSION BLOCK <code>CCBLOCK<code> |
| * </p> |
| * The data records can be used to store raw values (often also denoted as |
| * implementation values or internal values). The CCBLOCK serves to specify a |
| * conversion formula to convert the raw values to physical values with a |
| * physical unit. The result of a conversion always is either a floating-point |
| * value (REAL) or a character string (UTF-8). |
| * |
| * @author Christian Rechner |
| */ |
| class CCBLOCK extends BLOCK { |
| |
| public static String BLOCK_ID = "##CC"; |
| |
| /** Link section */ |
| |
| // Link to TXBLOCK with name (identifier) of conversion (can be NIL). |
| // LINK |
| private long lnkTxName; |
| |
| // Link to TXBLOCK/MDBLOCK with physical unit of signal data (after |
| // conversion). (can be NIL) |
| // LINK |
| private long lnkMdUnit; |
| |
| // Link to TXBLOCK/MDBLOCK with comment of conversion and additional |
| // information. (can be NIL) |
| // LINK |
| private long lnkMdComment; |
| |
| // Link to CCBLOCK for inverse formula (can be NIL, must be NIL for CCBLOCK |
| // of the inverse formula (no cyclic |
| // reference allowed). |
| // LINK |
| private long lnkCcInverse; |
| |
| // List of additional links to TXBLOCKs with strings or to CCBLOCKs with |
| // partial conversion rules. Length of list is |
| // given by cc_ref_count. The list can be empty. Details are explained in |
| // formula-specific block supplement. |
| // LINK |
| private long[] lnkCcRef; |
| |
| /** Data section */ |
| |
| // Conversion type (formula identifier) |
| // 0 = 1:1 conversion (in this case, the CCBLOCK can be omitted) |
| // 1 = linear conversion |
| // 2 = rational conversion |
| // 3 = algebraic conversion (MCD-2 MC text formula) |
| // 4 = value to value tabular look-up with interpolation |
| // 5 = value to value tabular look-up without interpolation |
| // 6 = value range to value tabular look-up |
| // 7 = value to text/scale conversion tabular look-up |
| // 8 = value range to text/scale conversion tabular look-up |
| // 9 = text to value tabular look-up |
| // 10 = text to text tabular look-up (translation) |
| // UINT8 |
| private byte type; |
| |
| // Precision for display of floating point values. |
| // 0xFF means unrestricted precision (infinite) |
| // Any other value specifies the number of decimal places to use for display |
| // of floating point values. |
| // Note: only valid if "precision valid" flag (bit 0) is set and if |
| // cn_precision of the parent CNBLOCK is invalid, |
| // otherwise cn_precision must be used. |
| // UINT8 |
| private byte precision; |
| |
| // Flags |
| // The value contains the following bit flags (Bit 0 = LSB): |
| // Bit 0: Precision valid flag |
| // Bit 1: Physical value range valid flag |
| // Bit 2: Status string flag |
| // UINT16 |
| private int flags; |
| |
| // Length M of cc_ref list with additional links. |
| // UINT16 |
| private int refCount; |
| |
| // Length N of cc_val list with additional parameters. |
| // UINT16 |
| private int valCount; |
| |
| // Minimum physical signal value that occurred for this signal. |
| // REAL |
| private double phyRangeMin; |
| |
| // Maximum physical signal value that occurred for this signal. |
| // REAL |
| private double phyRangeMax; |
| |
| // List of additional conversion parameters. |
| // Length of list is given by cc_val_count. The list can be empty. |
| // REAL N |
| private double[] val;
|
|
|
| private CCBLOCK defaultValueBlock; |
| |
| /** |
| * Constructor. |
| * |
| * @param sbc |
| * The byte channel pointing to the MDF file. |
| * @param pos |
| * The position of the block within the MDF file. |
| */ |
| private CCBLOCK(SeekableByteChannel sbc, long pos) { |
| super(sbc, pos); |
| } |
| |
| public long getLnkTxName() { |
| return lnkTxName; |
| } |
| |
| public long getLnkMdUnit() { |
| return lnkMdUnit; |
| } |
| |
| public long getLnkMdComment() { |
| return lnkMdComment; |
| } |
| |
| public long getLnkCcInverse() { |
| return lnkCcInverse; |
| } |
| |
| public long[] getLnkCcRef() { |
| return lnkCcRef; |
| } |
| |
| public byte getType() { |
| return type; |
| } |
| |
| public byte getPrecision() { |
| return precision; |
| } |
| |
| public int getFlags() { |
| return flags; |
| } |
| |
| public boolean isPhysicalRangeValid() { |
| return BigInteger.valueOf(flags).testBit(1); |
| } |
| |
| public int getRefCount() { |
| return refCount; |
| } |
| |
| public int getValCount() { |
| return valCount; |
| } |
| |
| public double getPhyRangeMin() { |
| return phyRangeMin; |
| } |
| |
| public double getPhyRangeMax() { |
| return phyRangeMax; |
| } |
| |
| public double[] getVal() { |
| return val; |
| } |
| |
| private void setLnkTxName(long lnkTxName) { |
| this.lnkTxName = lnkTxName; |
| } |
| |
| private void setLnkMdUnit(long lnkMdUnit) { |
| this.lnkMdUnit = lnkMdUnit; |
| } |
| |
| private void setLnkMdComment(long lnkMdComment) { |
| this.lnkMdComment = lnkMdComment; |
| } |
| |
| private void setLnkCcInverse(long lnkCcInverse) { |
| this.lnkCcInverse = lnkCcInverse; |
| } |
| |
| private void setLnkCcRef(long[] lnkCcRef) { |
| this.lnkCcRef = lnkCcRef; |
| } |
| |
| private void setType(byte type) { |
| this.type = type; |
| } |
| |
| private void setPrecision(byte precision) { |
| this.precision = precision; |
| } |
| |
| private void setFlags(int flags) { |
| this.flags = flags; |
| } |
| |
| private void setRefCount(int refCount) { |
| this.refCount = refCount; |
| } |
| |
| private void setValCount(int valCount) { |
| this.valCount = valCount; |
| } |
| |
| private void setPhyRangeMin(double phyRangeMin) { |
| this.phyRangeMin = phyRangeMin; |
| } |
| |
| private void setPhyRangeMax(double phyRangeMax) { |
| this.phyRangeMax = phyRangeMax; |
| } |
| |
| private void setVal(double[] val) { |
| this.val = val; |
| } |
| |
| public TXBLOCK getCnTxNameBlock() throws IOException { |
| if (lnkTxName > 0) { |
| return TXBLOCK.read(sbc, lnkTxName); |
| } |
| return null; |
| } |
| |
| public BLOCK getMdUnitBlock() throws IOException { |
| if (lnkMdUnit > 0) { |
| String blockType = getBlockType(sbc, lnkMdUnit); |
| // link points to a MDBLOCK |
| if (blockType.equals(MDBLOCK.BLOCK_ID)) { |
| return MDBLOCK.read(sbc, lnkMdUnit); |
| } |
| // links points to TXBLOCK |
| else if (blockType.equals(TXBLOCK.BLOCK_ID)) { |
| return TXBLOCK.read(sbc, lnkMdUnit); |
| } |
| // unknown |
| else { |
| throw new IOException("Unsupported block type for MdUnit: " + blockType); |
| } |
| } |
| return null; |
| } |
| |
| public BLOCK getMdCommentBlock() throws IOException { |
| if (lnkMdComment > 0) { |
| String blockType = getBlockType(sbc, lnkMdComment); |
| // link points to a MDBLOCK |
| if (blockType.equals(MDBLOCK.BLOCK_ID)) { |
| return MDBLOCK.read(sbc, lnkMdComment); |
| } |
| // links points to TXBLOCK |
| else if (blockType.equals(TXBLOCK.BLOCK_ID)) { |
| return TXBLOCK.read(sbc, lnkMdComment); |
| } |
| // unknown |
| else { |
| throw new IOException("Unsupported block type for MdComment: " + blockType); |
| } |
| } |
| return null; |
| } |
| |
| public CCBLOCK getCcInverseBlock() throws IOException { |
| if (lnkCcInverse > 0) { |
| return CCBLOCK.read(sbc, lnkCcInverse); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns all referenced TXBLOCKS. |
| * |
| * @return |
| * @throws IOException |
| */ |
| public TXBLOCK[] getCcRefBlocks() throws IOException { |
| if (lnkCcRef.length > 0) { |
| TXBLOCK[] ccRef = new TXBLOCK[lnkCcRef.length]; |
| for (int i = 0; i < ccRef.length; i++) { |
| if (lnkCcRef[i] > 0) { |
| String blockType = getBlockType(sbc, lnkCcRef[i]); |
| // link points to a TXBLOCK |
| if (blockType.equals(TXBLOCK.BLOCK_ID)) { |
| ccRef[i] = TXBLOCK.read(sbc, lnkCcRef[i]); |
| } |
| // link points to CCBLOCK containing formula for default value
|
| else if (blockType.equals(CCBLOCK.BLOCK_ID) && defaultValueBlock == null) {
|
| defaultValueBlock = CCBLOCK.read(sbc, lnkCcRef[i]);
|
| } |
| // unknown |
| else { |
| throw new IOException("Unsupported block type for Conversion Reference: " + blockType); |
| } |
| |
| } |
| } |
| return ccRef; |
| } |
| return null; |
| } |
| |
| /** |
| * Tests whether there are referenced CC blocks (scale conversion). |
| * |
| * @return true if at least one CC block is referenced |
| * @throws IOException |
| * thrown if unable to read file |
| */ |
| public boolean hasCCRefs() throws IOException { |
| if (lnkCcRef.length < 1) { |
| return false; |
| } |
| |
| for (int i = 0; i < lnkCcRef.length; i++) { |
| if (lnkCcRef[i] > 0) { |
| String blockType = getBlockType(sbc, lnkCcRef[i]); |
| if (blockType.equals(CCBLOCK.BLOCK_ID)) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public String toString() { |
| return "CCBLOCK [lnkTxName=" + lnkTxName + ", lnkMdUnit=" + lnkMdUnit + ", lnkMdComment=" + lnkMdComment |
| + ", lnkCcInverse=" + lnkCcInverse + ", lnkCcRef=" + Arrays.toString(lnkCcRef) + ", type=" + type |
| + ", precision=" + precision + ", flags=" + flags + ", refCount=" + refCount + ", valCount=" + valCount |
| + ", phyRangeMin=" + phyRangeMin + ", phyRangeMax=" + phyRangeMax + ", val=" + Arrays.toString(val) |
| + "]"; |
| } |
| |
| /** |
| * Reads a CCBLOCK from the channel starting at current channel position. |
| * |
| * @param channel |
| * The channel to read from. |
| * @param pos |
| * The position |
| * @return The block data. |
| * @throws IOException |
| * The exception. |
| */ |
| public static CCBLOCK read(SeekableByteChannel channel, long pos) throws IOException { |
| CCBLOCK block = new CCBLOCK(channel, pos); |
| |
| // read block header |
| ByteBuffer bb = ByteBuffer.allocate(24); |
| bb.order(ByteOrder.LITTLE_ENDIAN); |
| channel.position(pos); |
| channel.read(bb); |
| bb.rewind(); |
| |
| // CHAR 4: Block type identifier |
| block.setId(MDF4Util.readCharsISO8859(bb, 4)); |
| if (!block.getId().equals(BLOCK_ID)) { |
| throw new IOException("Wrong block type - expected '" + BLOCK_ID + "', found '" + block.getId() + "'"); |
| } |
| |
| // BYTE 4: Reserved used for 8-Byte alignment |
| bb.get(new byte[4]); |
| |
| // UINT64: Length of block |
| block.setLength(MDF4Util.readUInt64(bb)); |
| |
| // UINT64: Number of links |
| block.setLinkCount(MDF4Util.readUInt64(bb)); |
| |
| // read block content |
| bb = ByteBuffer.allocate((int) block.getLength() + 24); |
| bb.order(ByteOrder.LITTLE_ENDIAN); |
| channel.position(pos + 24); |
| channel.read(bb); |
| bb.rewind(); |
| |
| // read links |
| long[] lnks = new long[(int) block.getLinkCount()]; |
| for (int i = 0; i < lnks.length; i++) { |
| lnks[i] = MDF4Util.readLink(bb); |
| } |
| |
| // read data |
| |
| // UINT8: Conversion type (formula identifier) |
| block.setType(MDF4Util.readUInt8(bb)); |
| |
| // UINT8: Precision for display of floating point values. |
| block.setPrecision(MDF4Util.readUInt8(bb)); |
| |
| // UINT16: Flags. |
| block.setFlags(MDF4Util.readUInt16(bb)); |
| |
| // UINT16: Length M of cc_ref list with additional links. |
| block.setRefCount(MDF4Util.readUInt16(bb)); |
| |
| // UINT16: Length N of cc_val list with additional. |
| block.setValCount(MDF4Util.readUInt16(bb)); |
| |
| // REAL: Minimum physical signal value that occurred for this signal. |
| block.setPhyRangeMin(MDF4Util.readReal(bb)); |
| |
| // REAL: Maximum physical signal value that occurred for this signal. |
| block.setPhyRangeMax(MDF4Util.readReal(bb)); |
| |
| // REAL N: List of additional conversion parameters. |
| double[] val = new double[block.getValCount()]; |
| for (int i = 0; i < val.length; i++) { |
| val[i] = MDF4Util.readReal(bb); |
| } |
| block.setVal(val); |
| |
| // extract links after reading data (then we know how many attachments) |
| block.setLnkTxName(lnks[0]); |
| block.setLnkMdUnit(lnks[1]); |
| block.setLnkMdComment(lnks[2]); |
| block.setLnkCcInverse(lnks[3]); |
| long[] lnkCcRef = new long[block.getRefCount()]; |
| for (int i = 0; i < lnkCcRef.length; i++) { |
| lnkCcRef[i] = lnks[i + 4]; |
| } |
| block.setLnkCcRef(lnkCcRef); |
| |
| return block; |
| } |
| |
| /** |
| * Returns the values WITHOUT the default value. |
| * |
| * @return |
| * @throws IOException |
| */ |
| public String[] getValuesForTextTable() throws IOException { |
| if (type == 7 || type == 8) { |
| TXBLOCK[] txblks = getCcRefBlocks(); |
| String[] ret = new String[getRefCount() - 1]; // without default |
| // value |
| for (int i = 0; i < ret.length; i++) { |
| ret[i] = txblks[i].getTxData(); |
| } |
| return ret; |
| } else { |
| return new String[0]; |
| } |
| } |
| |
| /** |
| * Returns the values of all linked TextBlocks. |
| * |
| * @return The values as String-Array. |
| * @throws IOException |
| */ |
| public String[] getRefValues() throws IOException { |
| if (type == 7 || type == 8) { |
| TXBLOCK[] txblks = getCcRefBlocks(); |
| String[] ret = new String[getRefCount()]; // all values |
| for (int i = 0; i < ret.length; i++) { |
| ret[i] = txblks[i].getTxData(); |
| } |
| return ret; |
| } else { |
| return new String[0]; |
| } |
| } |
| |
| /** |
| * Returns the values WITHOUT the default value. (only used for text to |
| * value table) |
| * |
| * @return The values without default. |
| * @throws IOException |
| */ |
| public double[] getValuesForTextToValueTable() throws IOException { |
| if (type == 9) { |
| double[] ret = new double[val.length - 1]; |
| System.arraycopy(val, 0, ret, 0, ret.length); |
| return ret; |
| } else { |
| return new double[0]; |
| } |
| } |
| |
| /**
|
| * Returns the default value for this conversion. For types 7, 8 and 10 the
|
| * default value is specified at last cc_ref value and it may be not defined,
|
| * if no default value is available.
|
| *
|
| * @return The default value.
|
| * @throws IOException
|
| */ |
| public String getDefaultValue() throws IOException { |
| if (type == 7 || type == 8 || type == 10) { |
| long lnkLastRef = lnkCcRef[getRefCount() - 1]; |
| if (lnkLastRef > 0) { |
| // There might be a CC Block, but this is not supported. |
| String blockType = getBlockType(sbc, lnkLastRef); |
| // link points to a TXBLOCK, return content. |
| if (blockType.equals(TXBLOCK.BLOCK_ID)) { |
| return TXBLOCK.read(sbc, lnkLastRef).getTxData(); |
| }
|
| // link points to CCBLOCK
|
| else if (blockType.equals(CCBLOCK.BLOCK_ID)) {
|
| if (defaultValueBlock == null) {
|
| defaultValueBlock = CCBLOCK.read(sbc, lnkLastRef);
|
| }
|
| // throw new IOException("Scale conversions with CCBlocks are not supported by ASAM ODS.");
|
| }
|
| // unknown |
| else { |
| throw new IOException("Unsupported block type for Default Value Conversion Reference: " + blockType); |
| } |
| } |
| } |
| return null; // NO default value. |
| } |
| |
| public double getDefaultValueDouble() throws IOException { |
| if (type == 6) { |
| return val[val.length - 1]; // return last value |
| } |
| return 0; // NO default value. |
| } |
| |
| public double[] getSecondValues(boolean even) { |
| if (type == 4 || type == 5 || type == 8) { |
| double[] ret = new double[val.length / 2]; |
| for (int i = even ? 0 : 1; i < val.length; i += 2) { |
| ret[i / 2] = val[i]; |
| } |
| return ret; |
| } else { |
| return new double[0]; |
| } |
| } |
| |
| public double[] getThirdValues(int start) { |
| if (type == 6) { |
| double[] ret = new double[val.length / 3]; |
| for (int i = start; i < val.length - val.length % 3; i += 3) { |
| ret[i / 3] = val[i]; |
| } |
| return ret; |
| } else { |
| return new double[0]; |
| } |
| } |
| |
| public String[] getSecondTexts(boolean even) throws IOException { |
| if (type == 10) { |
| String[] texts = getValuesForTextTable(); |
| String[] ret = new String[texts.length / 2]; |
| for (int i = even ? 0 : 1; i < texts.length; i += 2) { |
| ret[i / 2] = texts[i]; |
| } |
| return ret; |
| } else { |
| return new String[0]; |
| } |
| } |
| } |