blob: db455d36083ecdc696fbe66787baa1cddea407a8 [file] [log] [blame]
/*
* Copyright (c) 2016 Audi AG
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
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;
/**
* 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) {
//There might be a CC Block, but this is not supported.
String blockType = getBlockType(sbc, lnkCcRef[i]);
// link points to a TXBLOCK
if (blockType.equals(TXBLOCK.BLOCK_ID)) {
ccRef[i] = TXBLOCK.read(sbc, lnkCcRef[i]);
}
// links points to CCBLOCK
else if (blockType.equals(CCBLOCK.BLOCK_ID)) {
throw new IOException("Scale conversions with CCBlocks are not supported.");
}
// 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.
* @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();
}
// links points to CCBLOCK
else if (blockType.equals(CCBLOCK.BLOCK_ID)) {
throw new IOException("Scale conversions with CCBlocks are not supported.");
}
// unknown
else {
throw new IOException("Unsupported block type for 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];
}
}
}